nolo-cli 0.49.1 → 0.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/{_executeModel-KE4PQSZ3.js → _executeModel-JKEH7ACW.js} +48 -42
  2. package/{action-IU6ON7ZB.js → action-VSG2IYP3.js} +8 -8
  3. package/{addContentAction-5V5CCI3I.js → addContentAction-2HJJITDI.js} +8 -7
  4. package/{addDialogAgentAction-KF3KZZNZ.js → addDialogAgentAction-7BSQVVL5.js} +10 -9
  5. package/{agentDeleteCommand-Z67TJG3W.js → agentDeleteCommand-SVXKS5SC.js} +30 -28
  6. package/{agentEmailCommands-7LPIK7JE.js → agentEmailCommands-WN7QBTP3.js} +57 -52
  7. package/{agentListCommands-5J2IM3IT.js → agentListCommands-2BEBOQIG.js} +42 -33
  8. package/{agentMachineCommands-YXH6FXUQ.js → agentMachineCommands-WALP3ONU.js} +30 -28
  9. package/{agentRecordCommands-GZLCDVUS.js → agentRecordCommands-E652SW57.js} +55 -50
  10. package/{agentRunCommand-WYH5Q54Q.js → agentRunCommand-6VXWXKCS.js} +60 -53
  11. package/{agentRunControl-4GHHSXFZ.js → agentRunControl-LJVFDJGU.js} +3 -1
  12. package/{agentRuntimeLocal-A6EF3RUY.js → agentRuntimeLocal-SBG7BDFQ.js} +30 -26
  13. package/{agentSlice-ARTDF6VD.js → agentSlice-3WYXIGEO.js} +8 -8
  14. package/{agentSuperviseCommands-VB6BUS5H.js → agentSuperviseCommands-F3RFAL7K.js} +2 -2
  15. package/{appDeleteCommands-V2B7ZCLC.js → appDeleteCommands-NTUNMEXY.js} +31 -29
  16. package/{appDeployCommands-AXQU44RD.js → appDeployCommands-DISWXRTL.js} +31 -29
  17. package/{appDeployStatusCommands-FWGSC2UM.js → appDeployStatusCommands-IKRA6B6F.js} +31 -29
  18. package/{appGetCommands-67R3RFZK.js → appGetCommands-UMOP2DFV.js} +31 -29
  19. package/{appListCommands-TTASJPGV.js → appListCommands-ZKFD2AKS.js} +31 -29
  20. package/{authCommand-QOPOEYEK.js → authCommand-BS3CQC6I.js} +11 -11
  21. package/{chatQueueReduxAdapter-6CAHUQ4O.js → chatQueueReduxAdapter-A3YH4MT4.js} +24 -21
  22. package/{chunk-76UA7376.js → chunk-273FJUYI.js} +6 -6
  23. package/{chunk-7CH6U3FL.js → chunk-2GRGFCF6.js} +2 -4
  24. package/{chunk-T27IEBNQ.js → chunk-2QDDKFG5.js} +74 -1
  25. package/{chunk-IRJVFYOH.js → chunk-2TLYVDTN.js} +321 -1100
  26. package/{chunk-6BYAXNH5.js → chunk-3QOCS5IF.js} +11 -2
  27. package/chunk-6IGD47IC.js +19 -0
  28. package/{chunk-GFD7TIZU.js → chunk-6TUT5ZJD.js} +4 -6
  29. package/chunk-6YTOJY35.js +352 -0
  30. package/{chunk-B54WOZ77.js → chunk-7H7GQL6V.js} +2 -2
  31. package/{chunk-OYNDYVSE.js → chunk-B45YNDAE.js} +1 -1
  32. package/{chunk-UQWFIWHX.js → chunk-BKIDF5LV.js} +1 -1
  33. package/{chunk-5XCW6RQG.js → chunk-BUBPRCIU.js} +1 -1
  34. package/chunk-BYXBJQAS.js +0 -0
  35. package/{chunk-X7S7OFUX.js → chunk-CEVCRRHU.js} +4 -4
  36. package/{chunk-SP3DPVBH.js → chunk-DEIGUGP6.js} +2 -2
  37. package/{chunk-V2IPBG5U.js → chunk-DGEPT6FH.js} +1 -1
  38. package/{chunk-3AOLVADY.js → chunk-DVINR3PL.js} +1 -1
  39. package/{chunk-NN3AMBY3.js → chunk-DXOELY7S.js} +64 -69
  40. package/{chunk-OFZPJ3NT.js → chunk-E4MARS3Q.js} +1 -1
  41. package/{chunk-QUCSAM43.js → chunk-EAYBPWHX.js} +87 -66
  42. package/{chunk-DQLYEGP7.js → chunk-EGVY3GMT.js} +1 -1
  43. package/{chunk-2JYSSXLY.js → chunk-EJFW7TRP.js} +5 -26
  44. package/{chunk-HPZL4IBN.js → chunk-FGRCSLPV.js} +34 -45
  45. package/{chunk-WL3UOGYO.js → chunk-G4WSLTQF.js} +31 -97
  46. package/{chunk-HR5XDXYM.js → chunk-G5SQVIFZ.js} +3 -3
  47. package/{chunk-J3UALFHL.js → chunk-GHE5XMVR.js} +1 -1
  48. package/{chunk-6SMMK7MD.js → chunk-GKB4AZKV.js} +440 -2981
  49. package/{chunk-IKSFDJXB.js → chunk-GKNGRBE6.js} +150 -27
  50. package/{chunk-Q24S4NDF.js → chunk-HQTK2SFT.js} +1 -1
  51. package/{chunk-GDGIEGHV.js → chunk-IPTZ5TVU.js} +7 -6
  52. package/{chunk-4RXXP6WD.js → chunk-J7KDNHUO.js} +5 -64
  53. package/{chunk-QDHZCAH3.js → chunk-KNQ5LYVZ.js} +57 -2
  54. package/{chunk-VRJUIUIY.js → chunk-KVVNC5V3.js} +2 -2
  55. package/{chunk-CVT7L2WZ.js → chunk-LVCAWHI5.js} +4 -4
  56. package/{chunk-WX5E6HZS.js → chunk-N6SLQMSA.js} +6 -6
  57. package/{chunk-SQTVMYMU.js → chunk-NTZO3KX2.js} +3 -3
  58. package/{chunk-LGUQHYAL.js → chunk-O5FLGN2L.js} +3 -2
  59. package/{chunk-WKOEOQCI.js → chunk-OSUZKICS.js} +1600 -190
  60. package/{chunk-EXDR5D6X.js → chunk-Q2QN2RHG.js} +7 -7
  61. package/{chunk-L3JWI7WX.js → chunk-QOKAKDPE.js} +15 -11
  62. package/{chunk-757BGY6Y.js → chunk-RFYXK6KI.js} +1 -1
  63. package/chunk-RQSN75DW.js +1766 -0
  64. package/{chunk-HCMFFI2G.js → chunk-S3JNURSO.js} +2 -2
  65. package/{chunk-YZ2WE6CW.js → chunk-SAJSBPI2.js} +2 -2
  66. package/{chunk-WHVQA7PJ.js → chunk-SCBBGS2N.js} +23 -5
  67. package/chunk-SGSJBBKP.js +297 -0
  68. package/{chunk-GFUOVFQA.js → chunk-TUBNQ3OF.js} +2 -2
  69. package/{chunk-BANYWEYY.js → chunk-TY3ADTX5.js} +1 -1
  70. package/chunk-UAD2WT54.js +64 -0
  71. package/{chunk-SW7RCUGA.js → chunk-UEV5WVQV.js} +3 -3
  72. package/{chunk-EURF2SV6.js → chunk-UMCMW5R3.js} +51 -34
  73. package/{chunk-K524MUQC.js → chunk-VFDRZHPM.js} +16 -16
  74. package/{chunk-5WADL4O7.js → chunk-W22UTILW.js} +3 -3
  75. package/{chunk-IKTHA3JH.js → chunk-WERPP2S7.js} +2 -2
  76. package/{chunk-GBADO3SM.js → chunk-WSUHBABG.js} +1 -1
  77. package/{chunk-U5Z7LFZF.js → chunk-X3OGKR5Q.js} +4 -4
  78. package/{chunk-AVKUDHJO.js → chunk-X5LRMXTS.js} +10 -10
  79. package/{chunk-DIQCV4FR.js → chunk-X665UCNM.js} +1 -1
  80. package/{chunk-HCBQVVFZ.js → chunk-X7P6EJUY.js} +4 -4
  81. package/{chunk-HHBXCRJS.js → chunk-XOJRZUPF.js} +4 -4
  82. package/{chunk-MRWGHF56.js → chunk-YDH3FDFQ.js} +12 -356
  83. package/{chunk-TNPCQNGQ.js → chunk-YEFRCO5N.js} +4 -9
  84. package/{chunk-RO2IUWZE.js → chunk-YUKTCDVN.js} +1 -1
  85. package/{chunk-56W233KG.js → chunk-YWY25N4V.js} +1 -1
  86. package/{chunk-RU5D3B22.js → chunk-ZDS63Y4O.js} +2 -2
  87. package/chunk-ZJ7UHBJS.js +89 -0
  88. package/{chunk-SG65C33A.js → chunk-ZLMG6NRV.js} +4 -4
  89. package/{codingSkills-BYQI6EPT.js → codingSkills-FQPCEIWL.js} +2 -2
  90. package/contentThunks-J33BZB3E.js +71 -0
  91. package/{createAgentAutomationAction-7BBF5YXN.js → createAgentAutomationAction-HTMTGX6Q.js} +25 -21
  92. package/{createDialogAction-B5URPHP4.js → createDialogAction-WYQBSN6A.js} +27 -23
  93. package/{createPageAction-ISJKM5Q3.js → createPageAction-ICRRK3SA.js} +27 -22
  94. package/{dbSlice-FWF5F4L7.js → dbSlice-YORO4VL4.js} +6 -6
  95. package/{deleteFile-NPZ64DFN.js → deleteFile-JYKRT7C3.js} +4 -4
  96. package/{dialogCommands-XHH4MVA4.js → dialogCommands-FRSIAPKY.js} +31 -29
  97. package/{dialogSlice-NRAN5X7F.js → dialogSlice-5KPVIAWH.js} +24 -21
  98. package/{docCreateCommands-FUN2Z7HQ.js → docCreateCommands-DBRJMPDX.js} +30 -28
  99. package/{docDeleteCommands-OZDPFXPF.js → docDeleteCommands-BCYPLWXN.js} +31 -29
  100. package/{docReadCommands-FIPK7LE3.js → docReadCommands-L5JX4Q7G.js} +30 -28
  101. package/{docStore-LUS7SWC3.js → docStore-34NXLCEO.js} +8 -33
  102. package/{docUpdateCommands-BZ5GKAN4.js → docUpdateCommands-RELG7XJD.js} +30 -28
  103. package/fetchMessages-KZQVBXKY.js +1 -1
  104. package/{getPricing-HAU62AFM.js → getPricing-FM7TJZFV.js} +4 -4
  105. package/{handleSendMessageAction-RA4OD2ZH.js → handleSendMessageAction-MEYK7RPD.js} +25 -22
  106. package/index.js +64 -64
  107. package/{machineCommands-WDAYEFBE.js → machineCommands-YSXTOWHQ.js} +59 -54
  108. package/{machineWsRunDispatch-TPQ2FQVS.js → machineWsRunDispatch-DNAYOPQT.js} +54 -49
  109. package/memberThunks-KVPBG4V7.js +638 -0
  110. package/{models-4QF4BJVY.js → models-CW5EL6Z7.js} +2 -2
  111. package/package.json +2 -3
  112. package/{read-J7RVVCHH.js → read-NIOV5F7W.js} +4 -4
  113. package/{readDocTool-G2WBM4IH.js → readDocTool-ULKJ5BEM.js} +1 -1
  114. package/{readlineWorkspace-HN44KGIF.js → readlineWorkspace-KZ3LPQG2.js} +829 -393
  115. package/{removeDialogAgentAction-RMPISD42.js → removeDialogAgentAction-JGSCPML3.js} +12 -11
  116. package/{runAgentClientLoop-ZYUI22EA.js → runAgentClientLoop-O2AUHIJT.js} +46 -40
  117. package/{setDialogExtraReferencesAction-7BUTHDAL.js → setDialogExtraReferencesAction-CORU64NM.js} +9 -8
  118. package/{setPrimaryDialogAgentAction-WCYIBGZ4.js → setPrimaryDialogAgentAction-5VS2VKLZ.js} +12 -11
  119. package/{settingSlice-7NN2MI6Y.js → settingSlice-3GNGTC2T.js} +7 -7
  120. package/{skillDiscovery-COX56OBO.js → skillDiscovery-CYUCJMAG.js} +3 -3
  121. package/{spaceCommands-OJ2567D4.js → spaceCommands-OI7RIB2T.js} +30 -28
  122. package/{streamAgentChatTurn-FNFMMGNN.js → streamAgentChatTurn-TA3PGS6C.js} +61 -54
  123. package/{switchDialogAgentAction-WGMMQHDI.js → switchDialogAgentAction-LP433X4D.js} +10 -9
  124. package/{tableCommands-P2TXJ3CF.js → tableCommands-R6WFRDYF.js} +1 -1
  125. package/{toolApiClient-B2MFXMEG.js → toolApiClient-53U4UDFF.js} +3 -2
  126. package/{tools-QCDR3UMD.js → tools-6ZPQ75XA.js} +37 -32
  127. package/{tuiSummaryLlmCaller-LCGT62QC.js → tuiSummaryLlmCaller-YKYQB4ZL.js} +17 -15
  128. package/{updateDialogTitleAction-Y3OGJWDB.js → updateDialogTitleAction-LRJ7NSOS.js} +26 -22
  129. package/chunk-EUG45QJR.js +0 -6104
  130. package/spaceSlice-F7JCOZEF.js +0 -127
  131. package/{chunk-NQ4N4IGO.js → chunk-CS6TCJ7E.js} +3 -3
@@ -3,11 +3,12 @@ import {
3
3
  normalizeDialogRecord,
4
4
  readDialogSnapshot,
5
5
  sortDialogs
6
- } from "./chunk-EUG45QJR.js";
6
+ } from "./chunk-RQSN75DW.js";
7
7
  import {
8
8
  ANSI_ESCAPE_REGEX,
9
9
  COLLAPSED_PASTE_PLACEHOLDER_RE,
10
10
  THEME_PALETTES,
11
+ TUI_TREE_MARKER,
11
12
  allocateCollapsedPaste,
12
13
  applyDetectedBackground,
13
14
  applyTerminalOutputToText,
@@ -25,12 +26,12 @@ import {
25
26
  fitAnsiLine,
26
27
  formatAssistantDisplay,
27
28
  formatTokenCount,
28
- getActiveDensity,
29
29
  getActiveThemeMode,
30
30
  getActiveThemeName,
31
31
  isBalanceExhaustedError,
32
- normalizeToolDisplayMode,
33
32
  padOrTruncateToWidth,
33
+ polishBreathInsertsBlankBetween,
34
+ readAgentsMdLayerFromDisk,
34
35
  releaseCollapsedPaste,
35
36
  releaseCollapsedPasteReferences,
36
37
  renderSurfaceLine,
@@ -46,7 +47,6 @@ import {
46
47
  resolveTuiThemeMode,
47
48
  runAgentTurn,
48
49
  setActiveBrightness,
49
- setActiveDensity,
50
50
  setActiveThemeMode,
51
51
  setActiveThemeName,
52
52
  setMathRenderingEnabled,
@@ -64,21 +64,21 @@ import {
64
64
  wrapTextToLines,
65
65
  wrapTranscriptLine,
66
66
  wrapTranscriptLineWithLayout
67
- } from "./chunk-IRJVFYOH.js";
67
+ } from "./chunk-2TLYVDTN.js";
68
+ import "./chunk-LYGNQHUW.js";
69
+ import "./chunk-ESHYE7WU.js";
68
70
  import {
69
71
  listFavoriteAgentIdsAcrossServers,
70
72
  listLocalCachedAgents,
71
73
  listRemoteAgents,
72
74
  listRemoteAgentsAcrossServers,
73
75
  normalizeListedAgent
74
- } from "./chunk-5WADL4O7.js";
76
+ } from "./chunk-W22UTILW.js";
75
77
  import "./chunk-ID4G2MAR.js";
76
78
  import {
77
79
  listUserRecordsFromServers,
78
80
  readLiveDbRecordAfterTombstoneMerge
79
- } from "./chunk-Q24S4NDF.js";
80
- import "./chunk-J3UALFHL.js";
81
- import "./chunk-LYGNQHUW.js";
81
+ } from "./chunk-HQTK2SFT.js";
82
82
  import {
83
83
  createCliLocalRuntimeAdapter,
84
84
  initCliLocale,
@@ -86,8 +86,8 @@ import {
86
86
  readTimestamp,
87
87
  setCliLocale,
88
88
  t
89
- } from "./chunk-QUCSAM43.js";
90
- import "./chunk-U5Z7LFZF.js";
89
+ } from "./chunk-EAYBPWHX.js";
90
+ import "./chunk-X3OGKR5Q.js";
91
91
  import "./chunk-EADNWBAZ.js";
92
92
  import "./chunk-NI2BS6UD.js";
93
93
  import {
@@ -97,37 +97,46 @@ import {
97
97
  deleteDbRecord,
98
98
  queryUserRecords,
99
99
  readDbRecord
100
- } from "./chunk-DQLYEGP7.js";
101
- import "./chunk-OYNDYVSE.js";
100
+ } from "./chunk-EGVY3GMT.js";
101
+ import "./chunk-B45YNDAE.js";
102
102
  import {
103
103
  NOLO_DEFAULT_AGENT_KEY,
104
104
  resolveCliAgentKeyInput
105
105
  } from "./chunk-EKLRRZIN.js";
106
+ import "./chunk-GHE5XMVR.js";
107
+ import {
108
+ createChatQueueRuntime,
109
+ createTurnRequest,
110
+ normalizeRunCompletionShape
111
+ } from "./chunk-YPREESF4.js";
106
112
  import "./chunk-JDSUTNYY.js";
113
+ import "./chunk-JBTJAPUF.js";
107
114
  import {
115
+ backfillRunRecordParentDialog,
108
116
  checkStaleRun,
109
117
  isRunRecordClaimed,
110
118
  listRunRecords,
111
119
  readRunRecord
112
- } from "./chunk-WHVQA7PJ.js";
113
- import "./chunk-ESHYE7WU.js";
114
- import "./chunk-JBTJAPUF.js";
120
+ } from "./chunk-SCBBGS2N.js";
115
121
  import "./chunk-LTRXGFMS.js";
116
- import "./chunk-RO2IUWZE.js";
122
+ import "./chunk-YUKTCDVN.js";
117
123
  import "./chunk-ATNTHIZI.js";
118
124
  import "./chunk-JEXVIMYZ.js";
119
125
  import {
120
126
  prepareTools
121
- } from "./chunk-HCMFFI2G.js";
127
+ } from "./chunk-S3JNURSO.js";
128
+ import {
129
+ buildResponseGuidelines
130
+ } from "./chunk-6IGD47IC.js";
122
131
  import "./chunk-HS7MLR74.js";
123
- import "./chunk-757BGY6Y.js";
124
- import "./chunk-WKOEOQCI.js";
132
+ import "./chunk-RFYXK6KI.js";
133
+ import "./chunk-OSUZKICS.js";
125
134
  import "./chunk-5XTOKQLW.js";
126
135
  import "./chunk-NF64D32W.js";
127
136
  import "./chunk-3L2WKC3W.js";
128
- import "./chunk-RU5D3B22.js";
137
+ import "./chunk-ZDS63Y4O.js";
129
138
  import "./chunk-KQHGZWSV.js";
130
- import "./chunk-5XCW6RQG.js";
139
+ import "./chunk-BUBPRCIU.js";
131
140
  import {
132
141
  clipText,
133
142
  formatDuration,
@@ -139,43 +148,41 @@ import {
139
148
  runShowsLogTail,
140
149
  shortRunId
141
150
  } from "./chunk-KXKQO4Q6.js";
151
+ import "./chunk-SGSJBBKP.js";
152
+ import "./chunk-TY3ADTX5.js";
153
+ import "./chunk-X7P6EJUY.js";
142
154
  import "./chunk-INZZJSQF.js";
143
- import "./chunk-GFD7TIZU.js";
144
- import "./chunk-BANYWEYY.js";
145
- import "./chunk-HCBQVVFZ.js";
146
155
  import "./chunk-MEYIRKDN.js";
147
156
  import "./chunk-63W5N6P3.js";
148
157
  import "./chunk-VF4K2QE6.js";
149
- import {
150
- createChatQueueRuntime,
151
- createTurnRequest,
152
- normalizeRunCompletionShape
153
- } from "./chunk-YPREESF4.js";
154
- import "./chunk-AVKUDHJO.js";
155
- import "./chunk-6SMMK7MD.js";
158
+ import "./chunk-6TUT5ZJD.js";
159
+ import "./chunk-X5LRMXTS.js";
160
+ import "./chunk-GKB4AZKV.js";
161
+ import "./chunk-ZJ7UHBJS.js";
156
162
  import {
157
163
  extractReferenceKeysFromMessage
158
- } from "./chunk-EURF2SV6.js";
164
+ } from "./chunk-UMCMW5R3.js";
159
165
  import "./chunk-EVBFYPDC.js";
160
166
  import "./chunk-H2G6JXTR.js";
161
167
  import "./chunk-YYLHWIW6.js";
162
- import "./chunk-WX5E6HZS.js";
163
168
  import "./chunk-AGGIDPCZ.js";
164
- import "./chunk-SG65C33A.js";
169
+ import "./chunk-N6SLQMSA.js";
170
+ import "./chunk-ZLMG6NRV.js";
165
171
  import "./chunk-5RGU3J7P.js";
172
+ import "./chunk-6YTOJY35.js";
166
173
  import "./chunk-M76ECQRD.js";
167
- import "./chunk-MRWGHF56.js";
174
+ import "./chunk-YDH3FDFQ.js";
168
175
  import "./chunk-URMS4XY2.js";
169
176
  import "./chunk-IA3A5373.js";
170
177
  import "./chunk-GLKOYSRU.js";
171
178
  import {
172
179
  buildSkillDiscoveryContextBlock,
173
180
  buildSkillDiscoveryContextLayer
174
- } from "./chunk-OFZPJ3NT.js";
175
- import "./chunk-V2IPBG5U.js";
181
+ } from "./chunk-E4MARS3Q.js";
182
+ import "./chunk-DGEPT6FH.js";
176
183
  import {
177
184
  expandEnabledPacks
178
- } from "./chunk-LGUQHYAL.js";
185
+ } from "./chunk-O5FLGN2L.js";
179
186
  import "./chunk-AETZ7AD6.js";
180
187
  import "./chunk-KHCLMKZZ.js";
181
188
  import {
@@ -183,23 +190,23 @@ import {
183
190
  buildLocalWorkspaceToolset,
184
191
  getProcessRegistry,
185
192
  readCommandActionGatePayload
186
- } from "./chunk-IKSFDJXB.js";
187
- import "./chunk-GDGIEGHV.js";
193
+ } from "./chunk-GKNGRBE6.js";
188
194
  import "./chunk-ZNFJWEN4.js";
189
- import "./chunk-CVT7L2WZ.js";
190
- import "./chunk-T27IEBNQ.js";
195
+ import "./chunk-IPTZ5TVU.js";
196
+ import "./chunk-LVCAWHI5.js";
197
+ import "./chunk-2QDDKFG5.js";
191
198
  import "./chunk-N7J63Q6C.js";
192
199
  import {
193
200
  buildCurrentTimeBlock,
194
201
  buildIdentityBlock,
195
202
  buildRuntimeGuidanceBlocks,
196
203
  resolveToolGuidedSections
197
- } from "./chunk-NN3AMBY3.js";
198
- import "./chunk-6BYAXNH5.js";
204
+ } from "./chunk-DXOELY7S.js";
205
+ import "./chunk-3QOCS5IF.js";
199
206
  import {
200
207
  resolvePlatformAuthToken
201
- } from "./chunk-SP3DPVBH.js";
202
- import "./chunk-TNPCQNGQ.js";
208
+ } from "./chunk-DEIGUGP6.js";
209
+ import "./chunk-YEFRCO5N.js";
203
210
  import {
204
211
  isQuotaExhaustedError
205
212
  } from "./chunk-IWUMSAQF.js";
@@ -207,7 +214,7 @@ import "./chunk-2MBTWINN.js";
207
214
  import {
208
215
  DEFAULT_CONTEXT_WINDOW,
209
216
  getModelContextWindow
210
- } from "./chunk-SW7RCUGA.js";
217
+ } from "./chunk-UEV5WVQV.js";
211
218
  import {
212
219
  buildCompactionMetricsFromPlan,
213
220
  buildCompactionUserContent,
@@ -218,28 +225,28 @@ import {
218
225
  formatMessagesForSummaryWithTruncation,
219
226
  planCompression,
220
227
  serializeMessageContent
221
- } from "./chunk-HR5XDXYM.js";
222
- import "./chunk-VRJUIUIY.js";
223
- import "./chunk-NQ4N4IGO.js";
224
- import "./chunk-FD36SCCD.js";
225
- import "./chunk-QDHZCAH3.js";
228
+ } from "./chunk-G5SQVIFZ.js";
229
+ import "./chunk-KVVNC5V3.js";
230
+ import "./chunk-BYXBJQAS.js";
231
+ import "./chunk-KNQ5LYVZ.js";
226
232
  import {
227
233
  compactWhitespace
228
234
  } from "./chunk-DWLEOOGR.js";
235
+ import "./chunk-CS6TCJ7E.js";
236
+ import "./chunk-FD36SCCD.js";
229
237
  import "./chunk-ZHXJ6M7T.js";
230
238
  import {
231
239
  toErrorMessage
232
240
  } from "./chunk-HNMXYYJR.js";
233
241
  import {
234
- buildAgentsMdLayer,
235
242
  buildMemoryOverlayLayer,
236
243
  buildMemoryUseGuidanceLayer,
237
244
  partitionScopedBlocks,
238
245
  renderTurnContextBlocksWithScope
239
- } from "./chunk-3AOLVADY.js";
246
+ } from "./chunk-DVINR3PL.js";
240
247
  import {
241
248
  buildUserResponseLanguageContext
242
- } from "./chunk-7CH6U3FL.js";
249
+ } from "./chunk-2GRGFCF6.js";
243
250
  import {
244
251
  canonicalizeToolNames
245
252
  } from "./chunk-O6OSWFZQ.js";
@@ -268,19 +275,20 @@ import {
268
275
  import {
269
276
  DEFAULT_NOLO_SERVER_URL
270
277
  } from "./chunk-OLZBQWZU.js";
271
- import "./chunk-56W233KG.js";
272
- import "./chunk-WL3UOGYO.js";
273
- import "./chunk-SQTVMYMU.js";
274
- import "./chunk-X7S7OFUX.js";
278
+ import "./chunk-YWY25N4V.js";
279
+ import "./chunk-G4WSLTQF.js";
280
+ import "./chunk-NTZO3KX2.js";
281
+ import "./chunk-CEVCRRHU.js";
275
282
  import "./chunk-Y5GCAMGP.js";
276
283
  import "./chunk-UZ6XXCVR.js";
277
284
  import "./chunk-KX6YBIHG.js";
278
- import "./chunk-4RXXP6WD.js";
279
- import "./chunk-76UA7376.js";
285
+ import "./chunk-J7KDNHUO.js";
286
+ import "./chunk-UAD2WT54.js";
287
+ import "./chunk-273FJUYI.js";
280
288
  import "./chunk-SVEUG45B.js";
281
289
  import "./chunk-WWC7I6TA.js";
282
290
  import "./chunk-I42VC5KR.js";
283
- import "./chunk-YZ2WE6CW.js";
291
+ import "./chunk-SAJSBPI2.js";
284
292
  import {
285
293
  asTrimmedLowercaseString
286
294
  } from "./chunk-QSJ3HFEF.js";
@@ -289,18 +297,18 @@ import {
289
297
  BUILTIN_NOLO_AGENT_MODEL,
290
298
  BUILTIN_NOLO_AGENT_NAME,
291
299
  builtinAgentCatalogEntryById
292
- } from "./chunk-2JYSSXLY.js";
300
+ } from "./chunk-EJFW7TRP.js";
293
301
  import {
294
302
  extractCustomId,
295
303
  parsePublicAgentId
296
304
  } from "./chunk-BJIRLHJR.js";
297
- import "./chunk-HHBXCRJS.js";
298
- import "./chunk-L3JWI7WX.js";
305
+ import "./chunk-XOJRZUPF.js";
306
+ import "./chunk-QOKAKDPE.js";
299
307
  import "./chunk-OW765XBU.js";
300
308
  import "./chunk-GBRG75BQ.js";
301
- import "./chunk-KUG52IAP.js";
302
309
  import "./chunk-3EUW3WEO.js";
303
310
  import "./chunk-U6VC4MU4.js";
311
+ import "./chunk-KUG52IAP.js";
304
312
  import "./chunk-LCBAMY2M.js";
305
313
  import "./chunk-4SN6J65J.js";
306
314
  import "./chunk-CF7PE763.js";
@@ -624,9 +632,9 @@ function createRawKeyReader(input) {
624
632
  };
625
633
  const resolveWaiter = (value) => {
626
634
  if (!waiter) return;
627
- const resolve2 = waiter;
635
+ const resolve4 = waiter;
628
636
  waiter = null;
629
- resolve2(value);
637
+ resolve4(value);
630
638
  };
631
639
  const tryDeliver = () => {
632
640
  if (!waiter) return;
@@ -683,12 +691,12 @@ function createRawKeyReader(input) {
683
691
  input.on("close", onClose);
684
692
  input.on("end", onClose);
685
693
  input.resume?.();
686
- const reader = () => new Promise((resolve2) => {
694
+ const reader = () => new Promise((resolve4) => {
687
695
  if (closed) {
688
- resolve2(drainClosed());
696
+ resolve4(drainClosed());
689
697
  return;
690
698
  }
691
- waiter = resolve2;
699
+ waiter = resolve4;
692
700
  tryDeliver();
693
701
  });
694
702
  reader.dispose = () => {
@@ -1225,7 +1233,81 @@ function appendLocalTurn(history, command, output) {
1225
1233
  trimHistoryToBudget(history);
1226
1234
  resetStreamingTurnCache();
1227
1235
  }
1236
+ var TOOL_SUCCESS_LINE_RE = /^(▸ .*?)(?: ×(\d+))?( ✓)$/;
1237
+ function applyTreeRewrite(text, chunk) {
1238
+ if (!chunk.startsWith(TUI_TREE_MARKER)) return null;
1239
+ const payload = chunk.slice(TUI_TREE_MARKER.length).replace(/\u0002\n?$/, "");
1240
+ const tree = `\u2022 ${payload}`;
1241
+ const hadTrailingNewline = text.endsWith("\n");
1242
+ const body = hadTrailingNewline ? text.slice(0, -1) : text;
1243
+ const bodyLines = body.split("\n");
1244
+ const plainLines = bodyLines.map((line) => stripAnsi(line));
1245
+ const tail = plainLines.at(-1) ?? "";
1246
+ let prefix;
1247
+ if (tail.trimStart().startsWith("\u25B8 ")) {
1248
+ prefix = bodyLines.length < 2 ? "" : `${bodyLines.slice(0, -1).join("\n")}
1249
+ `;
1250
+ } else if (tail.trimStart().startsWith("\u2514\u2500\u2500 ")) {
1251
+ const headerIndex = plainLines.findLastIndex((line) => line.trimStart().startsWith("\u2022 "));
1252
+ if (headerIndex < 0) return null;
1253
+ prefix = `${bodyLines.slice(0, headerIndex).join("\n")}${headerIndex > 0 ? "\n" : ""}`;
1254
+ } else return null;
1255
+ return `${prefix}${tree}
1256
+ `;
1257
+ }
1258
+ function foldToolSuccessLine(text, chunk) {
1259
+ const plain = stripAnsi(chunk).replace(/\n+$/, "");
1260
+ if (!plain || plain.includes("\n") || !TOOL_SUCCESS_LINE_RE.test(plain)) return null;
1261
+ const hadTrailingNewline = text.endsWith("\n");
1262
+ const body = hadTrailingNewline ? text.slice(0, -1) : text;
1263
+ const newlineAt = body.lastIndexOf("\n");
1264
+ const tail = newlineAt === -1 ? body : body.slice(newlineAt + 1);
1265
+ const match = TOOL_SUCCESS_LINE_RE.exec(stripAnsi(tail));
1266
+ if (!match || `${match[1]}${match[3]}` !== plain) return null;
1267
+ const count = (match[2] ? Number(match[2]) : 1) + 1;
1268
+ const anchor = tail.lastIndexOf(" ");
1269
+ if (anchor < 0) return null;
1270
+ const head = tail.slice(0, anchor).replace(/\s×\d+$/, "");
1271
+ const merged = `${head} \xD7${count}${tail.slice(anchor)}`;
1272
+ const prefix = newlineAt === -1 ? "" : body.slice(0, newlineAt + 1);
1273
+ return `${prefix}${merged}${hadTrailingNewline ? "\n" : ""}`;
1274
+ }
1228
1275
  function applyOutputChunkToCurrentTurn(history, chunk, kind = "assistant") {
1276
+ if (kind === "tool") {
1277
+ if (chunk.startsWith(TUI_TREE_MARKER)) {
1278
+ const rewrittenTree = applyTreeRewrite(history.currentContent, chunk);
1279
+ if (rewrittenTree === null) {
1280
+ const safe = chunk.slice(TUI_TREE_MARKER.length).replace(/\u0002/g, "");
1281
+ const leaves = safe.split("\n").filter((line) => /^(?:├── |└── )/.test(line));
1282
+ const newest = leaves.at(-1)?.replace(/^(?:├── |└── )/, "\u25B8 ") ?? "";
1283
+ if (!newest) return false;
1284
+ const flat = `${newest}
1285
+ `;
1286
+ history.currentContent = applyTerminalOutputToText(history.currentContent, flat);
1287
+ appendCurrentBlock(history, kind, flat);
1288
+ return false;
1289
+ }
1290
+ const treeContent = rewrittenTree;
1291
+ history.currentContent = treeContent;
1292
+ const lastBlock = history.currentBlocks.at(-1);
1293
+ const treeLines = treeContent.split("\n");
1294
+ const treeHeaderIndex = treeLines.findIndex((line) => line.trimStart().startsWith("\u2022 "));
1295
+ const treeBlock = treeHeaderIndex >= 0 ? treeLines.slice(treeHeaderIndex).join("\n") : treeContent;
1296
+ if (lastBlock?.kind === "tool") lastBlock.content = lastBlock.content.replace(/[^\n]*$/, "") + treeBlock;
1297
+ else history.currentBlocks.push({ kind: "tool", content: treeBlock });
1298
+ return true;
1299
+ }
1300
+ const foldedContent = foldToolSuccessLine(history.currentContent, chunk);
1301
+ if (foldedContent !== null) {
1302
+ history.currentContent = foldedContent;
1303
+ const lastBlock = history.currentBlocks.at(-1);
1304
+ if (lastBlock?.kind === "tool") {
1305
+ const foldedBlock = foldToolSuccessLine(lastBlock.content, chunk);
1306
+ if (foldedBlock !== null) lastBlock.content = foldedBlock;
1307
+ }
1308
+ return true;
1309
+ }
1310
+ }
1229
1311
  const next = applyTerminalOutputToText(history.currentContent, chunk);
1230
1312
  if (next === history.currentContent) return false;
1231
1313
  history.currentContent = next;
@@ -1385,9 +1467,9 @@ function layoutTurnRows(role, content, contentWidth, colorEnabled, command) {
1385
1467
  }
1386
1468
  return rows.length > 0 ? rows : [{ rendered: "", sourceStart: 0, sourceEnd: 0, prefixWidth: 0 }];
1387
1469
  }
1388
- function getTurnLayoutRows(turn, contentWidth, colorEnabled, density, themeFingerprint) {
1470
+ function getTurnLayoutRows(turn, contentWidth, colorEnabled, themeFingerprint) {
1389
1471
  const cached = getCachedTurnLine(turn);
1390
- if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.themeFingerprint === themeFingerprint) {
1472
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.themeFingerprint === themeFingerprint) {
1391
1473
  return cached.layoutRows;
1392
1474
  }
1393
1475
  const layoutRows = layoutTurnRows(turn.role, turn.content, contentWidth, colorEnabled, turn.command);
@@ -1395,7 +1477,6 @@ function getTurnLayoutRows(turn, contentWidth, colorEnabled, density, themeFinge
1395
1477
  setCachedTurnLine(turn, {
1396
1478
  width: contentWidth,
1397
1479
  color: colorEnabled,
1398
- density,
1399
1480
  themeFingerprint,
1400
1481
  lines,
1401
1482
  layoutRows
@@ -1409,6 +1490,40 @@ var streamingTurnCache = null;
1409
1490
  function resetStreamingTurnCache() {
1410
1491
  streamingTurnCache = null;
1411
1492
  }
1493
+ function countFenceFlips(text) {
1494
+ const matches = text.match(/^[ \t]*```/gm);
1495
+ return matches ? matches.length : 0;
1496
+ }
1497
+ var HEADING_LIKE_RE = /^#{1,3} /;
1498
+ function isPipeRowLike(line) {
1499
+ const trimmed = line.trim();
1500
+ return trimmed.length > 0 && trimmed.startsWith("|");
1501
+ }
1502
+ function isSuspiciousAppend(newTail, fenceParityEven) {
1503
+ if (!fenceParityEven) return true;
1504
+ if (newTail.includes("\r")) return true;
1505
+ if (newTail.includes("```")) return true;
1506
+ if (newTail.includes("$$") || newTail.includes("\\[") || newTail.includes("\\]")) {
1507
+ return true;
1508
+ }
1509
+ if (/\n{4,}/.test(newTail)) return true;
1510
+ if (newTail.includes("\0")) return true;
1511
+ return hasPipeRowLine(newTail);
1512
+ }
1513
+ function hasPipeRowLine(text) {
1514
+ const lines = text.split("\n");
1515
+ return lines.some((line) => isPipeRowLike(line));
1516
+ }
1517
+ function isSuspiciousBoundary(committedLastLine, newTail) {
1518
+ const newlineIdx = newTail.indexOf("\n");
1519
+ const firstLine = newlineIdx === -1 ? newTail : newTail.slice(0, newlineIdx);
1520
+ if (HEADING_LIKE_RE.test(firstLine) && committedLastLine !== "") return true;
1521
+ if (HEADING_LIKE_RE.test(committedLastLine) && firstLine !== "") return true;
1522
+ if (isPipeRowLike(firstLine) || isPipeRowLike(committedLastLine)) {
1523
+ return true;
1524
+ }
1525
+ return polishBreathInsertsBlankBetween(committedLastLine, firstLine);
1526
+ }
1412
1527
  function renderPrefixTurnBlock(role, content, contentWidth, colorEnabled) {
1413
1528
  content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1414
1529
  if (role === "user") {
@@ -1450,75 +1565,105 @@ function renderTailTurnBlock(role, content, contentWidth, colorEnabled) {
1450
1565
  return lines;
1451
1566
  }
1452
1567
  }
1453
- function getStreamingTurnLines(role, content, contentWidth, colorEnabled, density, themeFingerprint) {
1454
- if (streamingTurnCache && (streamingTurnCache.role !== role || streamingTurnCache.contentWidth !== contentWidth || streamingTurnCache.colorEnabled !== colorEnabled || streamingTurnCache.density !== density || streamingTurnCache.themeFingerprint !== themeFingerprint || !content.startsWith(streamingTurnCache.fullContent.slice(0, streamingTurnCache.prefixLength)))) {
1455
- streamingTurnCache = null;
1456
- }
1457
- if (!streamingTurnCache) {
1458
- streamingTurnCache = {
1459
- role,
1460
- contentWidth,
1461
- colorEnabled,
1462
- density,
1463
- themeFingerprint,
1464
- fullContent: content,
1465
- prefixLength: 0,
1466
- prefixLines: [],
1467
- lastValidatedCut: 0
1468
- };
1469
- }
1470
- if (content.length > streamingTurnCache.fullContent.length) {
1471
- let searchPos = streamingTurnCache.prefixLength;
1472
- let candidateCut = -1;
1473
- while (true) {
1474
- const idx = content.indexOf("\n\n", searchPos);
1475
- if (idx === -1) break;
1476
- const cut = idx + 1;
1477
- searchPos = idx + 2;
1478
- const prefixSub = content.slice(0, cut);
1479
- const fenceCount = (prefixSub.match(/^```/gm) || []).length;
1480
- if (fenceCount % 2 === 0) {
1481
- candidateCut = cut;
1482
- break;
1483
- }
1484
- }
1485
- if (candidateCut > streamingTurnCache.prefixLength) {
1486
- const candidatePrefix = content.slice(0, candidateCut);
1487
- const candidateTail = content.slice(candidateCut);
1488
- const candPrefixLines = renderPrefixTurnBlock(role, candidatePrefix, contentWidth, colorEnabled);
1489
- const candTailLines = renderTailTurnBlock(role, candidateTail, contentWidth, colorEnabled);
1490
- const candCombined = [...candPrefixLines, ...candTailLines];
1491
- if (candidateCut !== streamingTurnCache.lastValidatedCut) {
1492
- const fullCheckLines = renderTurnBlock(role, content, contentWidth, colorEnabled);
1493
- const sameLines = candCombined.length === fullCheckLines.length && candCombined.every((line, index) => line === fullCheckLines[index]);
1494
- streamingTurnCache.lastValidatedCut = candidateCut;
1495
- if (sameLines) {
1496
- streamingTurnCache.prefixLength = candidateCut;
1497
- streamingTurnCache.prefixLines = candPrefixLines;
1498
- }
1499
- }
1500
- streamingTurnCache.fullContent = content;
1501
- } else {
1502
- streamingTurnCache.fullContent = content;
1503
- }
1504
- }
1505
- if (streamingTurnCache.prefixLength === 0) {
1506
- return renderTurnBlock(role, content, contentWidth, colorEnabled);
1568
+ function getStreamingTurnLines(role, content, contentWidth, colorEnabled, themeFingerprint) {
1569
+ const forceValidate = process.env.NOLO_TUI_RENDER_VALIDATE === "1";
1570
+ let cache = streamingTurnCache;
1571
+ const geometryChanged = !cache || cache.role !== role || cache.contentWidth !== contentWidth || cache.colorEnabled !== colorEnabled || cache.themeFingerprint !== themeFingerprint;
1572
+ if (geometryChanged || !content.startsWith(cache.fullContent)) {
1573
+ return rebuildStreamingCache(content, role, contentWidth, colorEnabled, themeFingerprint);
1574
+ }
1575
+ if (content === cache.fullContent && cache.lastResult) {
1576
+ return cache.lastResult;
1577
+ }
1578
+ const suffix = content.slice(cache.fullContent.length);
1579
+ if (suffix === "") {
1580
+ return cache.lastResult ?? cache.committedLines;
1581
+ }
1582
+ const renderFull = () => {
1583
+ const lines = renderTurnBlock(role, content, contentWidth, colorEnabled);
1584
+ cache.fullContent = content;
1585
+ cache.fenceFlips = countFenceFlips(content);
1586
+ const lastNewline = content.lastIndexOf("\n");
1587
+ cache.completeContentLength = lastNewline + 1;
1588
+ cache.committedLines = lastNewline === -1 ? [] : renderTurnBlock(role, content.slice(0, lastNewline), contentWidth, colorEnabled);
1589
+ cache.pendingTail = content.slice(lastNewline + 1);
1590
+ cache.committedLastLine = lastNewline === -1 ? "" : lastLogicalLineOf(content.slice(0, lastNewline + 1));
1591
+ cache.lastResult = lines;
1592
+ return lines;
1593
+ };
1594
+ const newTail = cache.pendingTail + suffix;
1595
+ if (!cache.fastPathEnabled || isSuspiciousAppend(newTail, cache.fenceFlips % 2 === 0) || isSuspiciousBoundary(cache.committedLastLine, newTail)) {
1596
+ return renderFull();
1597
+ }
1598
+ const tailRenderer = cache.completeContentLength === 0 ? renderPrefixTurnBlock : renderTailTurnBlock;
1599
+ const tailLines = tailRenderer(role, newTail, contentWidth, colorEnabled);
1600
+ const composed = cache.completeContentLength === 0 ? tailLines : [...cache.committedLines, ...tailLines];
1601
+ if (!cache.spliceValidated || forceValidate) {
1602
+ const full = renderTurnBlock(role, content, contentWidth, colorEnabled);
1603
+ cache.spliceValidated = true;
1604
+ if (composed.length === full.length && composed.every((line, index) => line === full[index])) {
1605
+ commitCompletedTail(cache, content, newTail, role, contentWidth, colorEnabled);
1606
+ cache.lastResult = composed;
1607
+ return composed;
1608
+ }
1609
+ cache.fastPathEnabled = false;
1610
+ return renderFull();
1611
+ }
1612
+ commitCompletedTail(cache, content, newTail, role, contentWidth, colorEnabled);
1613
+ cache.lastResult = composed;
1614
+ return composed;
1615
+ }
1616
+ function lastLogicalLineOf(content) {
1617
+ const trimmed = content.replace(/\n$/, "");
1618
+ const idx = trimmed.lastIndexOf("\n");
1619
+ return idx === -1 ? trimmed : trimmed.slice(idx + 1);
1620
+ }
1621
+ function rebuildStreamingCache(content, role, contentWidth, colorEnabled, themeFingerprint) {
1622
+ const lines = renderTurnBlock(role, content, contentWidth, colorEnabled);
1623
+ const lastNewline = content.lastIndexOf("\n");
1624
+ streamingTurnCache = {
1625
+ role,
1626
+ contentWidth,
1627
+ colorEnabled,
1628
+ themeFingerprint,
1629
+ fullContent: content,
1630
+ // committed 渲染源剥掉段尾 \n(幽灵空行修正,见 renderFull 注释)。
1631
+ committedLines: lastNewline === -1 ? [] : renderTurnBlock(role, content.slice(0, lastNewline), contentWidth, colorEnabled),
1632
+ completeContentLength: lastNewline + 1,
1633
+ pendingTail: content.slice(lastNewline + 1),
1634
+ fenceFlips: countFenceFlips(content),
1635
+ committedLastLine: lastNewline === -1 ? "" : lastLogicalLineOf(content.slice(0, lastNewline + 1)),
1636
+ fastPathEnabled: true,
1637
+ spliceValidated: false,
1638
+ lastResult: lines
1639
+ };
1640
+ return lines;
1641
+ }
1642
+ function commitCompletedTail(cache, content, newTail, role, contentWidth, colorEnabled) {
1643
+ const lastNewline = newTail.lastIndexOf("\n");
1644
+ if (lastNewline !== -1) {
1645
+ const completePart = newTail.slice(0, lastNewline + 1);
1646
+ const renderSource = completePart.slice(0, -1);
1647
+ const renderer = cache.completeContentLength === 0 ? renderPrefixTurnBlock : renderTailTurnBlock;
1648
+ cache.committedLines.push(
1649
+ ...renderer(role, renderSource, contentWidth, colorEnabled)
1650
+ );
1651
+ cache.completeContentLength += completePart.length;
1652
+ cache.fenceFlips += countFenceFlips(completePart);
1653
+ cache.committedLastLine = lastLogicalLineOf(completePart);
1654
+ cache.pendingTail = newTail.slice(lastNewline + 1);
1507
1655
  }
1508
- const tailContent = content.slice(streamingTurnCache.prefixLength);
1509
- const tailLines = renderTailTurnBlock(role, tailContent, contentWidth, colorEnabled);
1510
- return [...streamingTurnCache.prefixLines, ...tailLines];
1656
+ cache.fullContent = content;
1511
1657
  }
1512
1658
  function countTurnLines(role, content, contentWidth, command) {
1513
1659
  return layoutTurnRows(role, content, contentWidth, false, command).length;
1514
1660
  }
1515
1661
  function buildTurnOffsets(history, contentWidth) {
1516
- const density = getActiveDensity();
1517
1662
  const entries = [];
1518
1663
  let offset = 0;
1519
1664
  for (let i = 0; i < history.turns.length; i++) {
1520
1665
  const turn = history.turns[i];
1521
- const separatorAbove = density === "spacious" && (i > 0 || turn.role === "user") ? 1 : 0;
1666
+ const separatorAbove = i > 0 && turn.role === "user" ? 1 : 0;
1522
1667
  offset += separatorAbove;
1523
1668
  const cacheMap = turnLineCountCache.get(turn);
1524
1669
  let lineCount;
@@ -1547,7 +1692,6 @@ function getAllTurnEntries(history, contentWidth) {
1547
1692
  const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
1548
1693
  const turns = [...history.turns];
1549
1694
  if (history.currentRole !== null && history.currentContent) {
1550
- const density = getActiveDensity();
1551
1695
  const colorEnabled = resolveCliColorEnabled();
1552
1696
  const themeFingerprint = tuiRenderThemeFingerprint(colorEnabled);
1553
1697
  const streamingLines = getStreamingTurnLines(
@@ -1555,10 +1699,9 @@ function getAllTurnEntries(history, contentWidth) {
1555
1699
  history.currentContent,
1556
1700
  contentWidth,
1557
1701
  colorEnabled,
1558
- density,
1559
1702
  themeFingerprint
1560
1703
  );
1561
- const sep = currentTurnSeparator(history, density);
1704
+ const sep = currentTurnSeparator(history);
1562
1705
  const startRow = finalizedLines + sep;
1563
1706
  entries.push({
1564
1707
  startRow,
@@ -1573,12 +1716,11 @@ function getAllTurnEntries(history, contentWidth) {
1573
1716
  }
1574
1717
  return { entries, totalLines: finalizedLines, turns };
1575
1718
  }
1576
- function currentTurnSeparator(history, density) {
1577
- return density === "spacious" && (history.turns.length > 0 || history.currentRole === "user") ? 1 : 0;
1719
+ function currentTurnSeparator(history) {
1720
+ return history.turns.length > 0 && history.currentRole === "user" ? 1 : 0;
1578
1721
  }
1579
1722
  function buildHistoryLayoutRows(history, contentWidth) {
1580
1723
  const colorEnabled = resolveCliColorEnabled();
1581
- const density = getActiveDensity();
1582
1724
  const themeFingerprint = tuiRenderThemeFingerprint(colorEnabled);
1583
1725
  const rows = [];
1584
1726
  const separatorRow = () => ({
@@ -1589,20 +1731,19 @@ function buildHistoryLayoutRows(history, contentWidth) {
1589
1731
  });
1590
1732
  for (let i = 0; i < history.turns.length; i++) {
1591
1733
  const turn = history.turns[i];
1592
- if (density === "spacious" && (i > 0 || turn.role === "user")) {
1734
+ if (i > 0 && turn.role === "user") {
1593
1735
  rows.push(separatorRow());
1594
1736
  }
1595
1737
  rows.push(...getTurnLayoutRows(
1596
1738
  turn,
1597
1739
  contentWidth,
1598
1740
  colorEnabled,
1599
- density,
1600
1741
  themeFingerprint
1601
1742
  ));
1602
1743
  }
1603
1744
  if (history.currentRole !== null && history.currentContent) {
1604
1745
  const i = history.turns.length;
1605
- if (density === "spacious" && (i > 0 || history.currentRole === "user")) {
1746
+ if (i > 0 && history.currentRole === "user") {
1606
1747
  rows.push(separatorRow());
1607
1748
  }
1608
1749
  rows.push(
@@ -1625,7 +1766,6 @@ function renderHistory(output, history, inputLines, selection) {
1625
1766
  const visibleHeight = Math.max(1, rows - inputLines);
1626
1767
  const contentWidth = Math.max(1, columns - 1);
1627
1768
  const colorEnabled = resolveCliColorEnabled();
1628
- const density = getActiveDensity();
1629
1769
  const themeFingerprint = tuiRenderThemeFingerprint(colorEnabled);
1630
1770
  const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
1631
1771
  let currentLines = [];
@@ -1636,10 +1776,9 @@ function renderHistory(output, history, inputLines, selection) {
1636
1776
  history.currentContent,
1637
1777
  contentWidth,
1638
1778
  colorEnabled,
1639
- density,
1640
1779
  themeFingerprint
1641
1780
  );
1642
- const sep = currentTurnSeparator(history, density);
1781
+ const sep = currentTurnSeparator(history);
1643
1782
  currentStart = finalizedLines + sep;
1644
1783
  }
1645
1784
  const totalLines = currentStart >= 0 ? currentStart + currentLines.length : finalizedLines;
@@ -1670,7 +1809,7 @@ function renderHistory(output, history, inputLines, selection) {
1670
1809
  const turn = history.turns[i];
1671
1810
  const cached = getCachedTurnLine(turn);
1672
1811
  let layoutRows;
1673
- if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.themeFingerprint === themeFingerprint) {
1812
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.themeFingerprint === themeFingerprint) {
1674
1813
  layoutRows = cached.layoutRows;
1675
1814
  } else {
1676
1815
  renderCacheMissCount += 1;
@@ -1678,7 +1817,6 @@ function renderHistory(output, history, inputLines, selection) {
1678
1817
  turn,
1679
1818
  contentWidth,
1680
1819
  colorEnabled,
1681
- density,
1682
1820
  themeFingerprint
1683
1821
  );
1684
1822
  }
@@ -1690,7 +1828,7 @@ function renderHistory(output, history, inputLines, selection) {
1690
1828
  }
1691
1829
  }
1692
1830
  if (currentStart >= 0) {
1693
- const separatorAbove = currentTurnSeparator(history, density);
1831
+ const separatorAbove = currentTurnSeparator(history);
1694
1832
  if (separatorAbove > 0) {
1695
1833
  const sepRow = currentStart - 1;
1696
1834
  if (sepRow >= winStart && sepRow < winEnd) {
@@ -1750,6 +1888,7 @@ function createHistoryOutputStream(history, onUpdate) {
1750
1888
  const stream = {
1751
1889
  isTTY: true,
1752
1890
  assistantLabelManaged: true,
1891
+ tuiTrees: true,
1753
1892
  write(chunk) {
1754
1893
  const text = typeof chunk === "string" ? chunk : chunk.toString();
1755
1894
  if (applyOutputChunkToCurrentTurn(history, text)) {
@@ -1758,9 +1897,9 @@ function createHistoryOutputStream(history, onUpdate) {
1758
1897
  return true;
1759
1898
  },
1760
1899
  writeToolBlock(chunk) {
1761
- if (applyOutputChunkToCurrentTurn(history, chunk, "tool")) {
1762
- onUpdate();
1763
- }
1900
+ const applied = applyOutputChunkToCurrentTurn(history, chunk, "tool");
1901
+ if (applied) onUpdate();
1902
+ return applied;
1764
1903
  }
1765
1904
  };
1766
1905
  return stream;
@@ -1774,7 +1913,7 @@ function applyScrollAction(history, action, output, inputLines) {
1774
1913
  const { totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
1775
1914
  let totalLines = finalizedLines;
1776
1915
  if (history.currentRole !== null && history.currentContent) {
1777
- totalLines += currentTurnSeparator(history, getActiveDensity()) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
1916
+ totalLines += currentTurnSeparator(history) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
1778
1917
  }
1779
1918
  const maxScrollTop = Math.max(0, totalLines - visibleHeight);
1780
1919
  history.followBottom = false;
@@ -1823,6 +1962,14 @@ function resolveDialogBottomRow(args) {
1823
1962
  return Math.max(1, resolveTtyRows(args.output) - args.inputLines);
1824
1963
  }
1825
1964
  function createDialogHost(args) {
1965
+ let keyboardClaimed = false;
1966
+ const claimKeyboard = () => {
1967
+ keyboardClaimed = true;
1968
+ };
1969
+ const releaseKeyboard = () => {
1970
+ args.drainDecoder?.();
1971
+ keyboardClaimed = false;
1972
+ };
1826
1973
  return {
1827
1974
  async run(body) {
1828
1975
  const anchor = {
@@ -1832,14 +1979,25 @@ function createDialogHost(args) {
1832
1979
  inputLines: args.composer.getInputLines()
1833
1980
  })
1834
1981
  };
1982
+ claimKeyboard();
1835
1983
  args.composer.pause();
1836
1984
  try {
1837
1985
  return await body(anchor);
1838
1986
  } finally {
1839
1987
  resetHistoryFrameDiffCache(args.output);
1840
1988
  args.composer.resumeFromDialog();
1989
+ releaseKeyboard();
1841
1990
  }
1842
- }
1991
+ },
1992
+ async withKeyboard(body) {
1993
+ claimKeyboard();
1994
+ try {
1995
+ return await body();
1996
+ } finally {
1997
+ releaseKeyboard();
1998
+ }
1999
+ },
2000
+ isKeyboardClaimed: () => keyboardClaimed
1843
2001
  };
1844
2002
  }
1845
2003
 
@@ -2049,8 +2207,7 @@ function createRunDock(deps) {
2049
2207
  const ordered = orderedEntries();
2050
2208
  if (ordered.length === 0) return [];
2051
2209
  const at = now();
2052
- const mode = typeof deps.displayMode === "function" ? deps.displayMode() : deps.displayMode;
2053
- const displaySnapshots = mode === "normal" || mode === "hide" ? ordered.map((entry) => sanitizeRunSnapshotForNormal(entry.snapshot)) : ordered.map((entry) => entry.snapshot);
2210
+ const displaySnapshots = ordered.map((entry) => sanitizeRunSnapshotForNormal(entry.snapshot));
2054
2211
  if (displaySnapshots.length === 1) {
2055
2212
  return formatAgentRunPanelLines(displaySnapshots[0], colorEnabled, at);
2056
2213
  }
@@ -2224,8 +2381,7 @@ function createActivityIndicator(deps) {
2224
2381
  onRepaint: deps.onRepaint,
2225
2382
  now,
2226
2383
  setIntervalFn,
2227
- clearIntervalFn,
2228
- displayMode: deps.displayMode
2384
+ clearIntervalFn
2229
2385
  });
2230
2386
  const updateAgentRun = (snapshot) => {
2231
2387
  runDock.update(snapshot);
@@ -3327,6 +3483,20 @@ function summarizeAttachment(img) {
3327
3483
  ].join("\n");
3328
3484
  return themeText(text, "chrome", colorEnabled);
3329
3485
  }
3486
+ function formatComposerAttachmentLine(images) {
3487
+ if (images.length === 0) return null;
3488
+ const head = images.slice(0, 2).map((img) => `\u{1F4CE} ${img.filename} (${formatBytes(img.sizeBytes)})`);
3489
+ const extra = images.length - head.length;
3490
+ return extra > 0 ? `${head.join(", ")} +${extra}` : head.join(", ");
3491
+ }
3492
+ function popLastAttachedImage(images) {
3493
+ if (images.length === 0) return { handled: false, images };
3494
+ return {
3495
+ handled: true,
3496
+ images: images.slice(0, -1),
3497
+ removed: images[images.length - 1]
3498
+ };
3499
+ }
3330
3500
  function mergeAttachedImages(existing, incoming) {
3331
3501
  if (incoming.length === 0) return existing;
3332
3502
  if (existing.length === 0) return [...incoming];
@@ -3348,12 +3518,12 @@ function mergeAttachedImages(existing, incoming) {
3348
3518
  return out;
3349
3519
  }
3350
3520
  async function readImagePaths(paths, options = {}) {
3351
- const resolve2 = options.resolve ?? ((p) => p);
3521
+ const resolve4 = options.resolve ?? ((p) => p);
3352
3522
  const reportedFailures = /* @__PURE__ */ new Set();
3353
3523
  const images = [];
3354
3524
  const failures = [];
3355
3525
  for (const raw of paths) {
3356
- const absolute = resolve2(raw);
3526
+ const absolute = resolve4(raw);
3357
3527
  try {
3358
3528
  const img = await readImageAsDataUrl(absolute, {
3359
3529
  ...options.maxBytes !== void 0 ? { maxBytes: options.maxBytes } : {}
@@ -3393,7 +3563,7 @@ async function resolveAttachmentImageUrls({
3393
3563
  // packages/cli/tui/clipboardImage.ts
3394
3564
  import { createHash } from "node:crypto";
3395
3565
  import { mkdirSync } from "node:fs";
3396
- import { writeFile } from "node:fs/promises";
3566
+ import { readdir, stat as stat2, unlink, writeFile } from "node:fs/promises";
3397
3567
  import { tmpdir } from "node:os";
3398
3568
  import { join } from "node:path";
3399
3569
  var ClipboardImageError = class extends Error {
@@ -3405,6 +3575,34 @@ var ClipboardImageError = class extends Error {
3405
3575
  }
3406
3576
  };
3407
3577
  var DEFAULT_CLIPBOARD_TIMEOUT_MS = 3e4;
3578
+ var CLIPBOARD_TEMP_DIR_NAME = "nolo-clipboard";
3579
+ function getDefaultClipboardTempDir() {
3580
+ return join(tmpdir(), CLIPBOARD_TEMP_DIR_NAME);
3581
+ }
3582
+ var CLIPBOARD_SWEEP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
3583
+ var CLIPBOARD_SWEEP_FILE_RE = /^clip-[0-9a-f]{8,}\.(png|jpg|jpeg|gif|webp)$/;
3584
+ async function sweepStaleClipboardFiles(tempDir, opts = {}) {
3585
+ try {
3586
+ const maxAgeMs = opts.maxAgeMs ?? CLIPBOARD_SWEEP_MAX_AGE_MS;
3587
+ const now = opts.now ?? Date.now();
3588
+ const entries = await readdir(tempDir);
3589
+ let removed = 0;
3590
+ for (const name of entries) {
3591
+ if (!CLIPBOARD_SWEEP_FILE_RE.test(name)) continue;
3592
+ try {
3593
+ const fullPath = join(tempDir, name);
3594
+ const stats = await stat2(fullPath);
3595
+ if (stats.mtimeMs >= now - maxAgeMs) continue;
3596
+ await unlink(fullPath);
3597
+ removed += 1;
3598
+ } catch {
3599
+ }
3600
+ }
3601
+ return removed;
3602
+ } catch {
3603
+ return 0;
3604
+ }
3605
+ }
3408
3606
  function isRemoteSession(env = process.env) {
3409
3607
  return Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
3410
3608
  }
@@ -3420,13 +3618,20 @@ function getClipboardImageCommand(deps = {}) {
3420
3618
  }
3421
3619
  if (platform === "darwin") {
3422
3620
  const pngpaste = which("pngpaste");
3423
- if (!pngpaste) {
3621
+ if (pngpaste) {
3622
+ return { command: [pngpaste, "-"], outputFormat: "raw" };
3623
+ }
3624
+ const osascript = which("osascript");
3625
+ if (!osascript) {
3424
3626
  throw new ClipboardImageError(
3425
3627
  "binary-missing",
3426
- "\u672A\u627E\u5230 pngpaste\uFF0C\u8BF7\u8FD0\u884C brew install pngpaste \u5B89\u88C5\uFF0C\u6216\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6/\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"
3628
+ "\u672A\u627E\u5230 pngpaste\uFF0C\u4E5F\u672A\u627E\u5230\u7CFB\u7EDF\u81EA\u5E26\u7684 osascript\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u526A\u8D34\u677F\u56FE\u50CF\u3002\u8BF7\u8FD0\u884C brew install pngpaste \u5B89\u88C5\uFF0C\u6216\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6/\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"
3427
3629
  );
3428
3630
  }
3429
- return { command: [pngpaste, "-"] };
3631
+ return {
3632
+ command: [osascript, "-e", "the clipboard as \xABclass PNGf\xBB"],
3633
+ outputFormat: "osascript-hex"
3634
+ };
3430
3635
  }
3431
3636
  if (platform === "win32") {
3432
3637
  const ps = which("powershell.exe") || which("powershell") || which("pwsh.exe") || which("pwsh");
@@ -3443,7 +3648,8 @@ function getClipboardImageCommand(deps = {}) {
3443
3648
  "-NonInteractive",
3444
3649
  "-Command",
3445
3650
  "$img = Get-Clipboard -Format Image; if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [Console]::OpenStandardOutput().Write($ms.ToArray(), 0, $ms.Length) }"
3446
- ]
3651
+ ],
3652
+ outputFormat: "raw"
3447
3653
  };
3448
3654
  }
3449
3655
  if (platform === "linux") {
@@ -3456,17 +3662,18 @@ function getClipboardImageCommand(deps = {}) {
3456
3662
  "\u672A\u627E\u5230 wl-paste\uFF0C\u8BF7\u5B89\u88C5 wl-clipboard (\u4F8B\u5982 sudo apt install wl-clipboard)\uFF0C\u6216\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6/\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"
3457
3663
  );
3458
3664
  }
3459
- return { command: [wlPaste2, "--type", "image/png"] };
3665
+ return { command: [wlPaste2, "--type", "image/png"], outputFormat: "raw" };
3460
3666
  }
3461
3667
  const xclip = which("xclip");
3462
3668
  if (xclip) {
3463
3669
  return {
3464
- command: [xclip, "-selection", "clipboard", "-t", "image/png", "-o"]
3670
+ command: [xclip, "-selection", "clipboard", "-t", "image/png", "-o"],
3671
+ outputFormat: "raw"
3465
3672
  };
3466
3673
  }
3467
3674
  const wlPaste = which("wl-paste");
3468
3675
  if (wlPaste) {
3469
- return { command: [wlPaste, "--type", "image/png"] };
3676
+ return { command: [wlPaste, "--type", "image/png"], outputFormat: "raw" };
3470
3677
  }
3471
3678
  throw new ClipboardImageError(
3472
3679
  "binary-missing",
@@ -3478,6 +3685,21 @@ function getClipboardImageCommand(deps = {}) {
3478
3685
  `\u5F53\u524D\u64CD\u4F5C\u7CFB\u7EDF\u6682\u4E0D\u652F\u6301\u526A\u8D34\u677F\u56FE\u50CF\u8BFB\u53D6: ${platform}`
3479
3686
  );
3480
3687
  }
3688
+ var OSASCRIPT_EMPTY_CLIPBOARD_MESSAGE = "\u526A\u8D34\u677F\u4E2D\u6CA1\u6709\u56FE\u50CF\u6570\u636E\u3002\u53EF\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6\u3001\u8F93\u5165\u56FE\u7247\u8DEF\u5F84\uFF0C\u6216\u8FD0\u884C brew install pngpaste \u4EE5\u652F\u6301\u66F4\u591A\u56FE\u7247\u683C\u5F0F";
3689
+ function decodeOsascriptPngHex(text) {
3690
+ const match = /PNGf\s*([0-9A-Fa-f]*)/.exec(text);
3691
+ if (!match) return { kind: "empty" };
3692
+ const hex = match[1] ?? "";
3693
+ const tail = text.slice(match.index + match[0].length).replace(/[\s»]*$/, "");
3694
+ if (tail.length > 0) return { kind: "invalid" };
3695
+ if (hex.length === 0) return { kind: "empty" };
3696
+ if (hex.length % 2 !== 0) return { kind: "invalid" };
3697
+ const bytes = new Uint8Array(hex.length / 2);
3698
+ for (let i = 0; i < bytes.length; i += 1) {
3699
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
3700
+ }
3701
+ return { kind: "bytes", bytes };
3702
+ }
3481
3703
  async function readClipboardImage(deps = {}) {
3482
3704
  const env = deps.env ?? process.env;
3483
3705
  const platform = deps.platform ?? process.platform;
@@ -3485,8 +3707,8 @@ async function readClipboardImage(deps = {}) {
3485
3707
  const spawn = deps.spawn ?? Bun.spawn;
3486
3708
  const maxBytes = deps.maxBytes ?? DEFAULT_MAX_IMAGE_BYTES;
3487
3709
  const timeoutMs = deps.timeoutMs ?? DEFAULT_CLIPBOARD_TIMEOUT_MS;
3488
- const tempDir = deps.tempDir ?? join(tmpdir(), "nolo-clipboard");
3489
- const { command } = getClipboardImageCommand({
3710
+ const tempDir = deps.tempDir ?? getDefaultClipboardTempDir();
3711
+ const { command, outputFormat } = getClipboardImageCommand({
3490
3712
  env,
3491
3713
  platform,
3492
3714
  which
@@ -3520,6 +3742,12 @@ async function readClipboardImage(deps = {}) {
3520
3742
  timeoutPromise
3521
3743
  ]);
3522
3744
  if (exitCode !== 0 || stdoutArrayBuffer.byteLength === 0) {
3745
+ if (outputFormat === "osascript-hex") {
3746
+ throw new ClipboardImageError(
3747
+ "empty-clipboard",
3748
+ OSASCRIPT_EMPTY_CLIPBOARD_MESSAGE + (stderrText.trim() ? `: ${stderrText.trim()}` : "")
3749
+ );
3750
+ }
3523
3751
  throw new ClipboardImageError(
3524
3752
  "empty-clipboard",
3525
3753
  "\u526A\u8D34\u677F\u4E2D\u6CA1\u6709\u56FE\u50CF\u6570\u636E\uFF0C\u8BF7\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6\u6216\u8F93\u5165\u56FE\u7247\u8DEF\u5F84" + (stderrText.trim() ? `: ${stderrText.trim()}` : "")
@@ -3538,6 +3766,23 @@ async function readClipboardImage(deps = {}) {
3538
3766
  `\u8BFB\u53D6\u526A\u8D34\u677F\u56FE\u50CF\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
3539
3767
  );
3540
3768
  }
3769
+ if (outputFormat === "osascript-hex") {
3770
+ const text = Buffer.from(stdoutBuf).toString("utf8");
3771
+ const decoded = decodeOsascriptPngHex(text);
3772
+ if (decoded.kind === "empty") {
3773
+ throw new ClipboardImageError(
3774
+ "empty-clipboard",
3775
+ OSASCRIPT_EMPTY_CLIPBOARD_MESSAGE
3776
+ );
3777
+ }
3778
+ if (decoded.kind === "invalid") {
3779
+ throw new ClipboardImageError(
3780
+ "read-failed",
3781
+ "\u526A\u8D34\u677F\u56FE\u50CF\u6570\u636E\u65E0\u6CD5\u89E3\u6790\uFF08hex \u7F16\u7801\u635F\u574F\uFF09\uFF0C\u8BF7\u91CD\u65B0\u590D\u5236\u56FE\u7247\u540E\u91CD\u8BD5\uFF0C\u6216\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6/\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"
3782
+ );
3783
+ }
3784
+ stdoutBuf = decoded.bytes;
3785
+ }
3541
3786
  const compressResult = await compressImage(stdoutBuf, "image/png");
3542
3787
  const finalBuf = compressResult.buffer;
3543
3788
  const finalMime = compressResult.mime;
@@ -3598,7 +3843,7 @@ function parseStatusOutput(branch, status) {
3598
3843
  return { branch, modified, untracked };
3599
3844
  }
3600
3845
  function execFileUtf8(file, args, cwd) {
3601
- return new Promise((resolve2, reject) => {
3846
+ return new Promise((resolve4, reject) => {
3602
3847
  execFile(
3603
3848
  file,
3604
3849
  args,
@@ -3608,7 +3853,7 @@ function execFileUtf8(file, args, cwd) {
3608
3853
  reject(err);
3609
3854
  return;
3610
3855
  }
3611
- resolve2(typeof stdout === "string" ? stdout : String(stdout ?? ""));
3856
+ resolve4(typeof stdout === "string" ? stdout : String(stdout ?? ""));
3612
3857
  }
3613
3858
  );
3614
3859
  });
@@ -3862,7 +4107,6 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
3862
4107
  t("contextFieldSkills"),
3863
4108
  t("contextFieldProfile"),
3864
4109
  t("contextFieldRuntime"),
3865
- t("contextFieldTools"),
3866
4110
  t("contextFieldServer")
3867
4111
  ];
3868
4112
  let maxW = 0;
@@ -3892,8 +4136,7 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
3892
4136
  field(labels[4], skills),
3893
4137
  field(labels[5], state.profileName),
3894
4138
  field(labels[6], state.runtimeMode),
3895
- field(labels[7], state.toolDisplay),
3896
- field(labels[8], state.serverUrl),
4139
+ field(labels[7], state.serverUrl),
3897
4140
  "",
3898
4141
  themeText(t("contextNext"), "chrome", colorEnabled),
3899
4142
  next("/agents ", ` ${t("contextNextAgents")}`),
@@ -3923,6 +4166,9 @@ function renderKnownAgents(colorEnabled = resolveCliColorEnabled()) {
3923
4166
  }
3924
4167
 
3925
4168
  // packages/cli/tui/sessionInput.ts
4169
+ import { readdirSync, statSync } from "node:fs";
4170
+ import { homedir as homedir2 } from "node:os";
4171
+ import { resolve as resolve2 } from "node:path";
3926
4172
  var PASTE_TOKEN_PREFIX = "\0PASTE\0";
3927
4173
  function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
3928
4174
  const seq = sequence ?? "";
@@ -4027,6 +4273,16 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
4027
4273
  return { buffer, cursorPos: curPos };
4028
4274
  }
4029
4275
  if (seq === " " || key.name === "tab") {
4276
+ if (options?.cwd) {
4277
+ const pathCandidates = completeSlashCommand(buffer, options.cwd);
4278
+ if (pathCandidates.length === 1) {
4279
+ const [only] = pathCandidates;
4280
+ return { buffer: only, cursorPos: only.length };
4281
+ }
4282
+ if (pathCandidates.length > 1) {
4283
+ return { buffer, cursorPos: curPos };
4284
+ }
4285
+ }
4030
4286
  const completed = completeSlashPrefix(buffer) ?? buffer;
4031
4287
  return { buffer: completed, cursorPos: completed.length };
4032
4288
  }
@@ -4079,9 +4335,9 @@ function deleteBufferRange(buffer, start, end, pasteStore) {
4079
4335
  function releasePastesInRange(buffer, start, end, pasteStore) {
4080
4336
  if (!pasteStore || start >= end) return;
4081
4337
  const slice = buffer.slice(start, end);
4082
- const re = /\[paste #(\d+) · (\d+) lines\]/g;
4338
+ COLLAPSED_PASTE_PLACEHOLDER_RE.lastIndex = 0;
4083
4339
  let match;
4084
- while ((match = re.exec(slice)) !== null) {
4340
+ while ((match = COLLAPSED_PASTE_PLACEHOLDER_RE.exec(slice)) !== null) {
4085
4341
  releaseCollapsedPaste(pasteStore, Number(match[1]));
4086
4342
  }
4087
4343
  }
@@ -4135,12 +4391,10 @@ var SLASH_COMMANDS = [
4135
4391
  "/clear",
4136
4392
  "/compact",
4137
4393
  "/theme",
4138
- "/density",
4139
4394
  "/context",
4140
4395
  "/ctx",
4396
+ "/cd",
4141
4397
  "/runtime",
4142
- "/tools",
4143
- "/thinking",
4144
4398
  "/auto",
4145
4399
  "/switch",
4146
4400
  "/agent",
@@ -4178,12 +4432,49 @@ function completeSlashPrefix(buffer) {
4178
4432
  }
4179
4433
  return prefix.length > buffer.length ? prefix : null;
4180
4434
  }
4181
- function completeSlashCommand(buffer) {
4435
+ function completeSlashCommand(buffer, cwd) {
4182
4436
  if (!buffer.startsWith("/")) return [];
4183
4437
  const trimmed = buffer.trim();
4438
+ if (cwd && buffer.startsWith("/cd ")) {
4439
+ const prefix = buffer.slice("/cd ".length);
4440
+ return completeCdPathCandidates(prefix, cwd);
4441
+ }
4184
4442
  if (trimmed.includes(" ")) return [];
4185
4443
  return SLASH_COMMANDS.filter((cmd) => cmd.startsWith(trimmed) && cmd !== trimmed);
4186
4444
  }
4445
+ function completeCdPathCandidates(prefix, cwd) {
4446
+ const lastSlash = prefix.lastIndexOf("/");
4447
+ const head = lastSlash >= 0 ? prefix.slice(0, lastSlash + 1) : "";
4448
+ const baseName = lastSlash >= 0 ? prefix.slice(lastSlash + 1) : prefix;
4449
+ let dirPath;
4450
+ if (head === "~" || head === "~/" || head === "~\\") {
4451
+ dirPath = homedir2();
4452
+ } else if (head.startsWith("~/") || head.startsWith("~\\")) {
4453
+ dirPath = resolve2(homedir2(), head.slice(2));
4454
+ } else {
4455
+ dirPath = head.startsWith("/") ? head : resolve2(cwd, head);
4456
+ }
4457
+ let entries;
4458
+ try {
4459
+ entries = readdirSync(dirPath, { withFileTypes: true });
4460
+ } catch {
4461
+ return [];
4462
+ }
4463
+ const hiddenRequested = baseName.startsWith(".");
4464
+ const names = entries.filter((e) => {
4465
+ if (e.isDirectory()) return true;
4466
+ if (!e.isSymbolicLink()) return false;
4467
+ try {
4468
+ return statSync(resolve2(dirPath, e.name)).isDirectory();
4469
+ } catch {
4470
+ return false;
4471
+ }
4472
+ }).map((e) => e.name).filter((name) => {
4473
+ if (!name.startsWith(baseName)) return false;
4474
+ return hiddenRequested || !name.startsWith(".");
4475
+ }).sort();
4476
+ return names.map((name) => `/cd ${head}${name}/`);
4477
+ }
4187
4478
  function isLikelySlashCommand(input) {
4188
4479
  const trimmed = input.trim();
4189
4480
  if (!trimmed.startsWith("/")) return false;
@@ -4202,6 +4493,11 @@ function stripImageTokens(input, hints) {
4202
4493
  return compactWhitespace(out);
4203
4494
  }
4204
4495
 
4496
+ // packages/cli/tui/sessionDispatch.ts
4497
+ import { statSync as statSync2 } from "node:fs";
4498
+ import { homedir as homedir3 } from "node:os";
4499
+ import { resolve as resolve3 } from "node:path";
4500
+
4205
4501
  // packages/cli/tui/agentPicker.ts
4206
4502
  function toAgentPickerItems(entries) {
4207
4503
  return entries.map((entry) => ({
@@ -4363,6 +4659,10 @@ function estimateDefaultCliContextTokens(opts = {}) {
4363
4659
 
4364
4660
  // packages/cli/tui/sessionDispatch.ts
4365
4661
  var DEFAULT_TUI_SERVER_URL = DEFAULT_NOLO_SERVER_URL;
4662
+ function resolveThinkingDisplayMode(env = process.env) {
4663
+ const raw = (env.NOLO_CLI_THINKING ?? "").trim().toLowerCase();
4664
+ return raw === "hide" || raw === "false" || raw === "0" ? "hide" : "show";
4665
+ }
4366
4666
  function createInitialTuiState(env = process.env) {
4367
4667
  const agentKey = asOptionalTrimmedString(env.NOLO_AGENT) ?? DEFAULT_TUI_AGENT_KEY;
4368
4668
  const agentName = asOptionalTrimmedString(env.NOLO_AGENT_NAME) ?? "nolo";
@@ -4395,8 +4695,7 @@ function createInitialTuiState(env = process.env) {
4395
4695
  modeLabel: asOptionalTrimmedString(env.NOLO_CLI_STATUS_MODE) ?? runtimeMode,
4396
4696
  userLanguage: asOptionalTrimmedString(env.NOLO_LANG),
4397
4697
  gitStatus: void 0,
4398
- toolDisplay: normalizeToolDisplayMode(env.NOLO_CLI_TOOLS ?? env.NOLO_TOOLS, "normal"),
4399
- thinkingDisplay: "show",
4698
+ thinkingDisplay: resolveThinkingDisplayMode(env),
4400
4699
  contextWindow: resolveAgentContextWindow({ agentKey, agentName }),
4401
4700
  estimatedContextTokens: estimateDefaultCliContextTokens({
4402
4701
  cwd,
@@ -4520,28 +4819,53 @@ function handleTuiInput(input, state) {
4520
4819
  };
4521
4820
  }
4522
4821
  }
4523
- case "/density": {
4524
- const parts = argText.split(/\s+/);
4525
- const sub = parts[0]?.trim();
4526
- if (!sub) {
4822
+ case "/cd": {
4823
+ if (!argText) {
4527
4824
  return {
4528
4825
  nextState: state,
4529
- output: t("densityCurrent", getActiveDensity())
4826
+ output: `${t("cdCurrent", state.cwd)}
4827
+ ${t("cdUsage")}`
4530
4828
  };
4531
4829
  }
4532
- if (sub === "cozy" || sub === "spacious") {
4533
- setActiveDensity(sub);
4534
- return {
4535
- nextState: state,
4536
- output: t("densitySwitched", sub)
4537
- };
4538
- } else {
4830
+ if (argText === "-") {
4831
+ if (!state.prevCwd) {
4832
+ return { nextState: state, output: t("cdNoPrevious") };
4833
+ }
4834
+ const previous = state.prevCwd;
4835
+ const switchMessage2 = t("cdSwitchedMessage", state.cwd, previous);
4539
4836
  return {
4540
- nextState: state,
4541
- output: t("densityUnknown", sub)
4837
+ nextState: { ...state, cwd: previous, prevCwd: void 0, pendingCwdNotice: switchMessage2 },
4838
+ output: t("cdSwitched", previous),
4839
+ action: { type: "cwd-refresh", switchMessage: switchMessage2 }
4542
4840
  };
4543
4841
  }
4842
+ const expanded = argText === "~" ? homedir3() : argText.startsWith("~/") || argText.startsWith("~\\") ? resolve3(homedir3(), argText.slice(2)) : argText;
4843
+ const target = resolve3(state.cwd, expanded);
4844
+ try {
4845
+ if (!statSync2(target).isDirectory()) {
4846
+ return { nextState: state, output: t("cdNotDir", target) };
4847
+ }
4848
+ } catch {
4849
+ return { nextState: state, output: t("cdNotFound", target) };
4850
+ }
4851
+ const normalized = target === "/" ? "/" : target.replace(/\/+$/, "");
4852
+ if (normalized === state.cwd) {
4853
+ return { nextState: state, output: t("cdCurrent", state.cwd) };
4854
+ }
4855
+ const switchMessage = t("cdSwitchedMessage", state.cwd, normalized);
4856
+ return {
4857
+ nextState: { ...state, cwd: normalized, prevCwd: state.cwd, pendingCwdNotice: switchMessage },
4858
+ output: t("cdSwitched", normalized),
4859
+ action: { type: "cwd-refresh", switchMessage }
4860
+ };
4544
4861
  }
4862
+ case "/density":
4863
+ case "/tools":
4864
+ case "/thinking":
4865
+ return {
4866
+ nextState: state,
4867
+ output: t("displayFixedHint")
4868
+ };
4545
4869
  case "/context":
4546
4870
  case "/ctx":
4547
4871
  return { nextState: state, output: renderContextPanel(state) };
@@ -4557,45 +4881,6 @@ function handleTuiInput(input, state) {
4557
4881
  output: t("runtimeSet", argText)
4558
4882
  };
4559
4883
  }
4560
- case "/tools": {
4561
- if (!argText) {
4562
- return {
4563
- nextState: state,
4564
- output: t("toolsCurrent", state.toolDisplay)
4565
- };
4566
- }
4567
- const normalizedArg = asTrimmedLowercaseString(argText);
4568
- if (!["normal", "pro", "hide", "compact", "verbose", "on", "off"].includes(normalizedArg)) {
4569
- return {
4570
- nextState: state,
4571
- output: t("toolsUsage")
4572
- };
4573
- }
4574
- const nextMode = normalizeToolDisplayMode(normalizedArg, state.toolDisplay);
4575
- return {
4576
- nextState: { ...state, toolDisplay: nextMode },
4577
- output: t("toolsSet", nextMode)
4578
- };
4579
- }
4580
- case "/thinking": {
4581
- const normalizedArg = asTrimmedLowercaseString(argText);
4582
- if (!normalizedArg) {
4583
- return {
4584
- nextState: state,
4585
- output: t("thinkingCurrent", state.thinkingDisplay)
4586
- };
4587
- }
4588
- if (normalizedArg !== "show" && normalizedArg !== "hide") {
4589
- return {
4590
- nextState: state,
4591
- output: t("thinkingUsage")
4592
- };
4593
- }
4594
- return {
4595
- nextState: { ...state, thinkingDisplay: normalizedArg },
4596
- output: t("thinkingSet", normalizedArg)
4597
- };
4598
- }
4599
4884
  case "/auto": {
4600
4885
  const normalizedArg = asTrimmedLowercaseString(argText);
4601
4886
  if (!normalizedArg) {
@@ -5004,7 +5289,7 @@ async function probeTerminalBackground(args = {}) {
5004
5289
  if (typeof stdin.setRawMode !== "function") return null;
5005
5290
  if (!isTerminal(stdin)) return null;
5006
5291
  const wasRaw = Boolean(stdin.isRaw);
5007
- return await new Promise((resolve2) => {
5292
+ return await new Promise((resolve4) => {
5008
5293
  let buffer = "";
5009
5294
  let settled = false;
5010
5295
  let timer;
@@ -5017,7 +5302,7 @@ async function probeTerminalBackground(args = {}) {
5017
5302
  if (!wasRaw) stdin.setRawMode?.(false);
5018
5303
  } catch {
5019
5304
  }
5020
- resolve2(result);
5305
+ resolve4(result);
5021
5306
  };
5022
5307
  const onData = (chunk) => {
5023
5308
  buffer += chunk.toString("latin1");
@@ -5080,9 +5365,46 @@ function detectSystemBrightness(env = process.env, options = {}) {
5080
5365
  return null;
5081
5366
  }
5082
5367
 
5083
- // packages/cli/tui/tuiTurnRunner.ts
5084
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
5085
- import { join as join3 } from "node:path";
5368
+ // packages/cli/tui/turnInjectionInbox.ts
5369
+ function appendStreamSafeNotice(history, text, deps) {
5370
+ const wasStreamingAssistant = history.currentRole === "assistant";
5371
+ if (wasStreamingAssistant && !history.currentContent) {
5372
+ history.currentRole = null;
5373
+ history.currentBlocks = [];
5374
+ }
5375
+ deps.appendLocalTurn(history, "", text);
5376
+ if (wasStreamingAssistant) deps.startTurn(history, "assistant");
5377
+ }
5378
+ function createTurnInjectionInbox() {
5379
+ let pending = [];
5380
+ let closed = false;
5381
+ return {
5382
+ push(entry) {
5383
+ if (closed) return false;
5384
+ if (!entry || typeof entry.text !== "string" || !entry.text.trim()) return false;
5385
+ pending.push(entry);
5386
+ return true;
5387
+ },
5388
+ drain() {
5389
+ if (pending.length === 0) return [];
5390
+ const taken = pending;
5391
+ pending = [];
5392
+ return taken.map((entry) => entry.text);
5393
+ },
5394
+ close() {
5395
+ closed = true;
5396
+ const remaining = pending;
5397
+ pending = [];
5398
+ return remaining;
5399
+ },
5400
+ get size() {
5401
+ return pending.length;
5402
+ },
5403
+ get closed() {
5404
+ return closed;
5405
+ }
5406
+ };
5407
+ }
5086
5408
 
5087
5409
  // packages/ai/tools/askChoiceState.ts
5088
5410
  function normalizeAskChoiceArgs(args) {
@@ -5861,24 +6183,6 @@ function emitTerminalBell(output) {
5861
6183
  }
5862
6184
 
5863
6185
  // packages/cli/tui/tuiTurnRunner.ts
5864
- var AGENTS_MD_MAX_BYTES = 8192;
5865
- function readAgentsMdLayer(cwd) {
5866
- for (const name of ["AGENTS.md", "CLAUDE.md"]) {
5867
- const filePath = join3(cwd, name);
5868
- if (existsSync3(filePath)) {
5869
- try {
5870
- let content = readFileSync3(filePath, "utf8").trim();
5871
- if (!content) continue;
5872
- if (Buffer.byteLength(content, "utf8") > AGENTS_MD_MAX_BYTES) {
5873
- content = Buffer.from(content, "utf8").subarray(0, AGENTS_MD_MAX_BYTES).toString("utf8") + "\n\n<!-- AGENTS.md truncated -->";
5874
- }
5875
- return buildAgentsMdLayer(content, name);
5876
- } catch {
5877
- }
5878
- }
5879
- }
5880
- return null;
5881
- }
5882
6186
  async function runAgentChat(scriptDir, state, message, env, output, agentRunner = runAgentTurn, options = {}) {
5883
6187
  const effectiveAgentKey = state.agentKey;
5884
6188
  const effectiveAgentName = state.agentName;
@@ -5923,12 +6227,16 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5923
6227
  }
5924
6228
  }
5925
6229
  const layers = [
5926
- readAgentsMdLayer(state.cwd),
6230
+ readAgentsMdLayerFromDisk(state.cwd),
5927
6231
  // Skill discovery: scan conventional skill dirs for SKILL.md and inject an
5928
6232
  // index layer so the model knows what skills exist and can readFile them
5929
6233
  // on-demand. Mirrors agentRunCommand.ts and desktopAgentRuntimeTurnService.
5930
6234
  buildSkillDiscoveryContextLayer(state.cwd)
5931
6235
  ];
6236
+ const responseGuidelinesBlock = {
6237
+ content: buildResponseGuidelines(true),
6238
+ cacheScope: "session"
6239
+ };
5932
6240
  let memoryPromptBlock = state.cachedMemoryOverlay;
5933
6241
  if (memoryPromptBlock === void 0) {
5934
6242
  memoryPromptBlock = await resolveCliMemory({
@@ -5941,15 +6249,19 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5941
6249
  }
5942
6250
  const memoryOverlayLayer = buildMemoryOverlayLayer({ promptBlock: memoryPromptBlock });
5943
6251
  const memoryUseGuidanceLayer = buildMemoryUseGuidanceLayer({ promptBlock: memoryPromptBlock });
6252
+ const cwdNoticeBlocks = state.pendingCwdNotice ? [{ content: state.pendingCwdNotice, cacheScope: "turn" }] : [];
5944
6253
  const contextBlockScopes = partitionScopedBlocks([
5945
6254
  ...renderTurnContextBlocksWithScope(layers),
6255
+ responseGuidelinesBlock,
5946
6256
  ...renderTurnContextBlocksWithScope([memoryUseGuidanceLayer]),
5947
6257
  // Attached skill bodies are already self-contained sections built by
5948
6258
  // buildSkillContextBlocks; they stay turn-scope because the user can
5949
6259
  // attach/detach skills between turns.
5950
6260
  ...(skillContextBlocks ?? []).map((content) => content.trim()).filter((content) => content.length > 0).map((content) => ({ content, cacheScope: "turn" })),
5951
- ...renderTurnContextBlocksWithScope([memoryOverlayLayer])
6261
+ ...renderTurnContextBlocksWithScope([memoryOverlayLayer]),
6262
+ ...cwdNoticeBlocks
5952
6263
  ]);
6264
+ const spawnedRunsMissingParent = [];
5953
6265
  const result = await agentRunner({
5954
6266
  agentName: effectiveAgentName,
5955
6267
  agentKey: effectiveAgentKey,
@@ -5967,10 +6279,12 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5967
6279
  scriptDir,
5968
6280
  env: {
5969
6281
  ...env,
5970
- NOLO_LANG: state.userLanguage,
5971
- NOLO_CLI_TOOLS: state.toolDisplay
6282
+ NOLO_LANG: state.userLanguage
6283
+ },
6284
+ output: {
6285
+ ...output,
6286
+ tuiTrees: output.tuiTrees === true && typeof output.writeToolBlock === "function"
5972
6287
  },
5973
- output,
5974
6288
  showThinking: state.thinkingDisplay === "show",
5975
6289
  ...options.imageUrls && options.imageUrls.length > 0 ? { imageUrls: options.imageUrls } : {},
5976
6290
  ...options.actionGateHandler ? { actionGateHandler: options.actionGateHandler } : {},
@@ -5994,15 +6308,28 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5994
6308
  // run-completion watcher can attribute terminal-state wakes to this
5995
6309
  // conversation.
5996
6310
  ...state.dialogId ? { parentDialogId: state.dialogId } : {},
6311
+ // 首轮无 dialogId 时的兜底:收集失归属的 spawn,turn 结束回填。
6312
+ onBackgroundRunSpawned: (runId) => {
6313
+ spawnedRunsMissingParent.push(runId);
6314
+ },
5997
6315
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {}
5998
6316
  }),
5999
6317
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {},
6000
6318
  // 转发 dock 订阅:runAgentTurn 的输出层靠它判断「有面板」从而抑制
6001
6319
  // transcript 的进展卡片;dock 本身也靠它接收模型轮询带回来的快照。
6002
6320
  ...options.onAgentRunStatus ? { onAgentRunStatus: options.onAgentRunStatus } : {},
6321
+ ...options.drainInjections ? { drainInjections: options.drainInjections } : {},
6003
6322
  ...skillAllowedTools !== void 0 ? { allowedToolNames: skillAllowedTools } : {},
6004
6323
  ...contextBlockScopes.length > 0 ? { contextBlockScopes } : {}
6005
6324
  });
6325
+ if (result.dialogId && spawnedRunsMissingParent.length > 0) {
6326
+ for (const runId of spawnedRunsMissingParent) {
6327
+ try {
6328
+ backfillRunRecordParentDialog(runId, result.dialogId, { env });
6329
+ } catch {
6330
+ }
6331
+ }
6332
+ }
6006
6333
  return {
6007
6334
  ...result,
6008
6335
  contextWindow: resolveAgentContextWindow({
@@ -6012,17 +6339,47 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
6012
6339
  cachedMemoryOverlay: memoryPromptBlock
6013
6340
  };
6014
6341
  }
6342
+ var ESC = "\x1B";
6343
+ var CTRL_C = "";
6015
6344
  function isApprovedConfirmationInput(answer) {
6016
6345
  const normalized = answer.trim().toLowerCase();
6017
6346
  return normalized === "" || normalized === "y" || normalized === "yes" || normalized === "\u786E\u8BA4";
6018
6347
  }
6019
- function waitForActionGate(rl, input, output, gate, spawnRunner) {
6348
+ function buildGateConfirmedResult(gate) {
6349
+ return {
6350
+ content: `action gate completed: ${gate.title}`,
6351
+ metadata: { actionGateResult: { gateId: gate.id, status: "completed", output: "confirmed" } }
6352
+ };
6353
+ }
6354
+ function buildGateCancelledResult(gate, reason) {
6355
+ return {
6356
+ content: `action gate cancelled: ${gate.title}`,
6357
+ metadata: {
6358
+ exitCode: 130,
6359
+ actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6360
+ }
6361
+ };
6362
+ }
6363
+ function buildGateFailedResult(gate, message) {
6364
+ return {
6365
+ content: `action gate failed: ${gate.title}`,
6366
+ metadata: {
6367
+ exitCode: 1,
6368
+ actionGateResult: { gateId: gate.id, status: "failed", output: message }
6369
+ }
6370
+ };
6371
+ }
6372
+ function deriveGateMeta(gate) {
6020
6373
  const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
6021
6374
  const isConfirmation = gate.kind === "confirm";
6022
6375
  const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
6023
6376
  const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
6024
6377
  const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
6025
6378
  const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
6379
+ return { commandPayload, isConfirmation, displayCommand, isInteractiveHandoff, title, body };
6380
+ }
6381
+ function writeGatePrompt(output, meta) {
6382
+ const { title, body, displayCommand, isConfirmation } = meta;
6026
6383
  output.write(`
6027
6384
  [nolo] ${t("actionGateNeeded")}
6028
6385
  `);
@@ -6034,29 +6391,22 @@ function waitForActionGate(rl, input, output, gate, spawnRunner) {
6034
6391
  `);
6035
6392
  output.write(`[nolo] ${isConfirmation ? t("actionGateConfirmHint") : t("actionGateEnterHint")}
6036
6393
  `);
6037
- return new Promise((resolve2) => {
6394
+ }
6395
+ function waitForActionGate(rl, input, output, gate, spawnRunner) {
6396
+ const meta = deriveGateMeta(gate);
6397
+ const { commandPayload, isConfirmation, displayCommand } = meta;
6398
+ writeGatePrompt(output, meta);
6399
+ return new Promise((resolve4) => {
6038
6400
  let settled = false;
6039
6401
  const finish = (result) => {
6040
6402
  if (settled) return;
6041
6403
  settled = true;
6042
6404
  rl.off("close", onClose);
6043
6405
  rl.off("SIGINT", onSigint);
6044
- resolve2(result);
6406
+ resolve4(result);
6045
6407
  };
6046
- const cancelResult = (reason) => ({
6047
- content: `action gate cancelled: ${gate.title}`,
6048
- metadata: {
6049
- exitCode: 130,
6050
- actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6051
- }
6052
- });
6053
- const failResult = (message) => ({
6054
- content: `action gate failed: ${gate.title}`,
6055
- metadata: {
6056
- exitCode: 1,
6057
- actionGateResult: { gateId: gate.id, status: "failed", output: message }
6058
- }
6059
- });
6408
+ const cancelResult = (reason) => buildGateCancelledResult(gate, reason);
6409
+ const failResult = (message) => buildGateFailedResult(gate, message);
6060
6410
  const onClose = () => finish(cancelResult("readline closed"));
6061
6411
  const onSigint = () => finish(cancelResult("interrupted"));
6062
6412
  rl.once("close", onClose);
@@ -6064,7 +6414,7 @@ function waitForActionGate(rl, input, output, gate, spawnRunner) {
6064
6414
  rl.question("", async (answer) => {
6065
6415
  if (settled) return;
6066
6416
  if (isConfirmation) {
6067
- finish(isApprovedConfirmationInput(answer) ? { content: `action gate completed: ${gate.title}`, metadata: { actionGateResult: { gateId: gate.id, status: "completed", output: "confirmed" } } } : cancelResult("confirmation declined"));
6417
+ finish(isApprovedConfirmationInput(answer) ? buildGateConfirmedResult(gate) : cancelResult("confirmation declined"));
6068
6418
  return;
6069
6419
  }
6070
6420
  if (!commandPayload) {
@@ -6112,24 +6462,10 @@ function isInteractiveInput(input) {
6112
6462
  return Boolean(candidate.isTTY) && typeof candidate.setRawMode === "function";
6113
6463
  }
6114
6464
  function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6115
- const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
6116
- const isConfirmation = gate.kind === "confirm";
6117
- const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
6118
- const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
6119
- const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
6120
- const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
6121
- output.write(`
6122
- [nolo] ${t("actionGateNeeded")}
6123
- `);
6124
- output.write(`[nolo] ${title}
6125
- `);
6126
- if (body) output.write(`[nolo] ${body}
6127
- `);
6128
- output.write(` ${displayCommand}
6129
- `);
6130
- output.write(`[nolo] ${isConfirmation ? t("actionGateConfirmHint") : t("actionGateEnterHint")}
6131
- `);
6132
- return new Promise((resolve2) => {
6465
+ const meta = deriveGateMeta(gate);
6466
+ const { commandPayload, isConfirmation, displayCommand } = meta;
6467
+ writeGatePrompt(output, meta);
6468
+ return new Promise((resolve4) => {
6133
6469
  const rawInput = input;
6134
6470
  let settled = false;
6135
6471
  let commandRunning = false;
@@ -6139,22 +6475,10 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6139
6475
  settled = true;
6140
6476
  hooks?.registerToken?.(null);
6141
6477
  input.off("data", onData);
6142
- resolve2(result);
6478
+ resolve4(result);
6143
6479
  };
6144
- const cancel = (reason) => finish({
6145
- content: `action gate cancelled: ${gate.title}`,
6146
- metadata: {
6147
- exitCode: 130,
6148
- actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6149
- }
6150
- });
6151
- const fail = (message) => finish({
6152
- content: `action gate failed: ${gate.title}`,
6153
- metadata: {
6154
- exitCode: 1,
6155
- actionGateResult: { gateId: gate.id, status: "failed", output: message }
6156
- }
6157
- });
6480
+ const cancel = (reason) => finish(buildGateCancelledResult(gate, reason));
6481
+ const fail = (message) => finish(buildGateFailedResult(gate, message));
6158
6482
  const runCommand = async () => {
6159
6483
  if (settled || commandRunning) return;
6160
6484
  commandRunning = true;
@@ -6197,7 +6521,7 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6197
6521
  };
6198
6522
  const handleToken = (text) => {
6199
6523
  if (settled || commandRunning) return;
6200
- if (text.includes("") || isConfirmation && text.includes("\x1B")) {
6524
+ if (text.includes(CTRL_C) || isConfirmation && text === ESC) {
6201
6525
  cancel("interrupted");
6202
6526
  return;
6203
6527
  }
@@ -6207,28 +6531,16 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6207
6531
  if (newline >= 0) {
6208
6532
  const normalized = confirmationLine.trim().toLowerCase();
6209
6533
  confirmationLine = "";
6210
- if (!isApprovedConfirmationInput(confirmationLine)) {
6534
+ if (!isApprovedConfirmationInput(normalized)) {
6211
6535
  cancel("confirmation declined");
6212
6536
  return;
6213
6537
  }
6214
- finish({
6215
- content: `action gate completed: ${gate.title}`,
6216
- metadata: { actionGateResult: { gateId: gate.id, status: "completed", output: "confirmed" } }
6217
- });
6538
+ finish(buildGateConfirmedResult(gate));
6218
6539
  }
6219
6540
  return;
6220
6541
  }
6221
6542
  if (text.includes("\r") || text.includes("\n")) {
6222
- if (isConfirmation) {
6223
- finish({
6224
- content: `action gate completed: ${gate.title}`,
6225
- metadata: {
6226
- actionGateResult: { gateId: gate.id, status: "completed", output: "confirmed" }
6227
- }
6228
- });
6229
- } else {
6230
- void runCommand();
6231
- }
6543
+ void runCommand();
6232
6544
  }
6233
6545
  };
6234
6546
  const onData = (chunk) => {
@@ -6242,6 +6554,40 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6242
6554
  }
6243
6555
  });
6244
6556
  }
6557
+ function asConfirmPermissionRequest(gate) {
6558
+ return gate;
6559
+ }
6560
+ function isAutoConfirmableFileWriteGate(gate) {
6561
+ return gate.kind === "confirm" && asConfirmPermissionRequest(gate).action === "file_write";
6562
+ }
6563
+ async function resolveActionGate(gate, deps) {
6564
+ if (gate.kind === "confirm") {
6565
+ const approved = await deps.dialogHost.run(
6566
+ (anchor) => runConfirmDialog({
6567
+ request: asConfirmPermissionRequest(gate),
6568
+ input: deps.input,
6569
+ output: deps.output,
6570
+ ...anchor
6571
+ })
6572
+ );
6573
+ return approved ? buildGateConfirmedResult(gate) : buildGateCancelledResult(gate, "confirmation declined");
6574
+ }
6575
+ return deps.dialogHost.withKeyboard(async () => {
6576
+ let subprocessRan = false;
6577
+ deps.pauseComposer();
6578
+ try {
6579
+ return await waitForRawActionGate(deps.input, deps.output, gate, deps.spawnRunner, {
6580
+ beforeSubprocess: () => {
6581
+ subprocessRan = true;
6582
+ },
6583
+ afterSubprocess: deps.resumeComposerFromSubprocess,
6584
+ registerToken: deps.registerToken
6585
+ });
6586
+ } finally {
6587
+ if (!subprocessRan) deps.resumeComposerFromDialog();
6588
+ }
6589
+ });
6590
+ }
6245
6591
  async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, confirmDestructiveAction) {
6246
6592
  const scheduleTitlePatchSync = (runResult) => {
6247
6593
  if (!runResult.titlePatchPromise || !runResult.dialogId) return;
@@ -6313,7 +6659,6 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6313
6659
  ctx.scheduleRender();
6314
6660
  }) : ctx.output;
6315
6661
  const requestUserChoice = isInteractiveInput(ctx.input) && ctx.dialogHost ? async (choiceReq) => {
6316
- ctx.modalOwnsKeyboard = true;
6317
6662
  try {
6318
6663
  return await ctx.dialogHost.run(
6319
6664
  (anchor) => runAskChoiceDialog({
@@ -6325,12 +6670,11 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6325
6670
  );
6326
6671
  } catch {
6327
6672
  return { kind: "cancelled" };
6328
- } finally {
6329
- ctx.composerDecoderDrain?.();
6330
- ctx.modalOwnsKeyboard = false;
6331
6673
  }
6332
6674
  } : void 0;
6333
6675
  ctx.runRegistryPoller.beginHold();
6676
+ const injectionInbox = createTurnInjectionInbox();
6677
+ ctx.turnInjectionInbox = injectionInbox;
6334
6678
  try {
6335
6679
  ctx.activeTurnAbort = new AbortController();
6336
6680
  ctx.activeTurnEpoch = myEpoch;
@@ -6347,6 +6691,7 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6347
6691
  ...confirmDestructiveAction ? { confirmDestructiveAction } : {},
6348
6692
  ...requestUserChoice ? { requestUserChoice } : {},
6349
6693
  abortSignal: ctx.activeTurnAbort.signal,
6694
+ drainInjections: () => injectionInbox.drain(),
6350
6695
  pastedTextStore: ctx.pasteStore,
6351
6696
  activityReporter: ctx.activityReporter,
6352
6697
  onAgentRunStatus: (snapshot) => {
@@ -6372,7 +6717,9 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6372
6717
  ...runResult.title ? { dialogTitle: runResult.title } : {}
6373
6718
  } : {},
6374
6719
  ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
6375
- ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
6720
+ ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {},
6721
+ // 本轮已发起 agent 调用(消息已注入),消费切换通知。
6722
+ pendingCwdNotice: void 0
6376
6723
  };
6377
6724
  if (runResult.dialogId && nextDialogKey) {
6378
6725
  ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
@@ -6420,7 +6767,11 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6420
6767
  // estimatedContextTokens:下一轮若 provider 不返回 usage,context
6421
6768
  // chip 仍显示真实累计占用而不是回退到启动时的静态估算。
6422
6769
  ...runResult.turnTokens && runResult.turnTokens.input > 0 ? { estimatedContextTokens: runResult.turnTokens.input } : {},
6423
- ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
6770
+ ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {},
6771
+ // 切换消息本轮已注入 agent 上下文(runAgentChat 已读),消费掉,
6772
+ // 避免下轮重复注入。仅当确实发起了 agent 调用(本轮是真实 turn、
6773
+ // 而非纯 child-run-completed 事件短路)时才清除。
6774
+ pendingCwdNotice: void 0
6424
6775
  };
6425
6776
  if (runResult.dialogId && nextDialogKey) {
6426
6777
  ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
@@ -6443,6 +6794,21 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6443
6794
  ctx.runRegistryPoller.endHold();
6444
6795
  ctx.activityIndicator.stop();
6445
6796
  ctx.activeTurnAbort = null;
6797
+ if (ctx.turnInjectionInbox === injectionInbox) ctx.turnInjectionInbox = null;
6798
+ const leftover = injectionInbox.close();
6799
+ if (leftover.length > 0) {
6800
+ const binding = ensureChatQueueBinding(
6801
+ ctx,
6802
+ actionGateHandler,
6803
+ confirmDestructiveAction
6804
+ );
6805
+ for (const entry of leftover) {
6806
+ try {
6807
+ binding.enqueue(entry.fallback);
6808
+ } catch {
6809
+ }
6810
+ }
6811
+ }
6446
6812
  }
6447
6813
  }
6448
6814
  function ensureChatQueueBinding(ctx, actionGateHandler, confirmDestructiveAction) {
@@ -7030,7 +7396,7 @@ function createFixedInput(output, config) {
7030
7396
  const renderInputArea = (buffer, cursorPos) => {
7031
7397
  const colorEnabled = resolveCliColorEnabled();
7032
7398
  const cols = Math.max(1, getColumns());
7033
- const completions = completeSlashCommand(buffer);
7399
+ const completions = completeSlashCommand(buffer, config.getCwd?.());
7034
7400
  const sections = [];
7035
7401
  if (completions.length > 0) {
7036
7402
  sections.push(fitAnsiLine(dimCliText(completions.join(" "), colorEnabled), cols));
@@ -7051,6 +7417,10 @@ function createFixedInput(output, config) {
7051
7417
  for (const line of queueLines) {
7052
7418
  sections.push(fitAnsiLine(line.replace(/\r?\n/g, " "), cols));
7053
7419
  }
7420
+ const attachmentLine = config.getAttachmentLine?.() ?? null;
7421
+ if (attachmentLine) {
7422
+ sections.push(fitAnsiLine(attachmentLine.replace(/\r?\n/g, " "), cols));
7423
+ }
7054
7424
  const promptRaw = t("promptLabel");
7055
7425
  const promptWidth = displayWidth(promptRaw);
7056
7426
  const prompt = colorEnabled ? `${themeColorSequence("accent")}${promptRaw}\x1B[39m` : promptRaw;
@@ -7100,7 +7470,7 @@ function createFixedInput(output, config) {
7100
7470
  }
7101
7471
  const text = sections.join("\n");
7102
7472
  const lines = sections.length;
7103
- const headerRows = (completions.length > 0 ? 1 : 0) + 2 + (titleLine ? 1 : 0) + activityLines.length + queueLines.length;
7473
+ const headerRows = (completions.length > 0 ? 1 : 0) + 2 + (titleLine ? 1 : 0) + activityLines.length + queueLines.length + (attachmentLine ? 1 : 0);
7104
7474
  return { text, lines, cursorCol, cursorRow: headerRows + cursorRow };
7105
7475
  };
7106
7476
  const repaintAt = (buffer, cursorPos, force = false) => {
@@ -7510,6 +7880,7 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7510
7880
  turnCtx,
7511
7881
  emitCommandOutput,
7512
7882
  renderHistoryToOutput,
7883
+ scheduleRender,
7513
7884
  refreshDialogTotalCredits,
7514
7885
  persistExplicitAgentSwitch,
7515
7886
  persistAgentSelection: persistAgentSelection2,
@@ -7683,6 +8054,18 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7683
8054
  emitCommandOutput(t("themeRefreshFailed"));
7684
8055
  }
7685
8056
  }
8057
+ if (result.action?.type === "cwd-refresh") {
8058
+ if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) !== "0") {
8059
+ const gitStatus = await detectGitStatusAsync(host.state.cwd);
8060
+ if (gitStatus !== void 0) {
8061
+ host.state = { ...host.state, gitStatus };
8062
+ }
8063
+ }
8064
+ if (result.action.switchMessage) {
8065
+ emitCommandOutput(result.action.switchMessage);
8066
+ }
8067
+ scheduleRender();
8068
+ }
7686
8069
  if (result.action?.type === "pick-agent") {
7687
8070
  try {
7688
8071
  const pickResult = await dialogHost.run(
@@ -8114,7 +8497,6 @@ async function runTuiWorkspace(options) {
8114
8497
  isTurnActive: () => activeTurnAbort !== null,
8115
8498
  fallbackLabel: () => `${state.agentName} -> working`,
8116
8499
  stoppingLabel: () => t("turnStopping"),
8117
- displayMode: () => state.toolDisplay,
8118
8500
  onRepaint: () => {
8119
8501
  if (fixedInput.active && !fixedInput.isPaused()) {
8120
8502
  output.write("\x1B[?2026h\x1B[?25l");
@@ -8158,7 +8540,14 @@ async function runTuiWorkspace(options) {
8158
8540
  getInputLines: () => fixedInput.getInputLines(),
8159
8541
  isPaused: () => fixedInput.isPaused()
8160
8542
  },
8161
- output
8543
+ output,
8544
+ // The decoder-drain hook is only bound once the interactive raw-mode
8545
+ // composer installs its decoder (inside the `isInteractiveInput` block
8546
+ // below); the non-raw readline path never binds it, so this stays a
8547
+ // no-op there — which is correct, that path has no raw decoder to drain.
8548
+ drainDecoder: () => {
8549
+ if (composerDecoderDrain) composerDecoderDrain();
8550
+ }
8162
8551
  });
8163
8552
  let sessionEnded = false;
8164
8553
  const selectionState = createSelectionState();
@@ -8187,7 +8576,7 @@ async function runTuiWorkspace(options) {
8187
8576
  const repaintBanner = () => {
8188
8577
  if (!output.isTTY) return;
8189
8578
  if (fixedInput.isPaused()) return;
8190
- if (modalOwnsKeyboard) return;
8579
+ if (dialogHost.isKeyboardClaimed()) return;
8191
8580
  const currentColumns = output.columns ?? 80;
8192
8581
  const welcome = renderWelcome(state, 0, 0, currentColumns);
8193
8582
  const lines = welcome.split("\n");
@@ -8236,7 +8625,6 @@ async function runTuiWorkspace(options) {
8236
8625
  selectionState.anchor = null;
8237
8626
  selectionState.head = null;
8238
8627
  };
8239
- let modalOwnsKeyboard = false;
8240
8628
  let rawActionGateTokenHandler = null;
8241
8629
  const refreshDialogTotalCredits = (dialogId, dialogKey) => {
8242
8630
  const env = options.env ?? process.env;
@@ -8272,6 +8660,7 @@ async function runTuiWorkspace(options) {
8272
8660
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
8273
8661
  };
8274
8662
  let chatQueueBinding = null;
8663
+ let turnInjectionInbox = null;
8275
8664
  const turnCtx = {
8276
8665
  get state() {
8277
8666
  return state;
@@ -8309,24 +8698,18 @@ async function runTuiWorkspace(options) {
8309
8698
  set activeTurnEpoch(v) {
8310
8699
  activeTurnEpoch = v;
8311
8700
  },
8312
- get modalOwnsKeyboard() {
8313
- return modalOwnsKeyboard;
8314
- },
8315
- set modalOwnsKeyboard(v) {
8316
- modalOwnsKeyboard = v;
8317
- },
8318
- get composerDecoderDrain() {
8319
- return composerDecoderDrain;
8320
- },
8321
- set composerDecoderDrain(v) {
8322
- composerDecoderDrain = v;
8323
- },
8324
8701
  get chatQueueBinding() {
8325
8702
  return chatQueueBinding;
8326
8703
  },
8327
8704
  set chatQueueBinding(v) {
8328
8705
  chatQueueBinding = v;
8329
8706
  },
8707
+ get turnInjectionInbox() {
8708
+ return turnInjectionInbox;
8709
+ },
8710
+ set turnInjectionInbox(v) {
8711
+ turnInjectionInbox = v;
8712
+ },
8330
8713
  get sessionEnded() {
8331
8714
  return sessionEnded;
8332
8715
  },
@@ -8392,6 +8775,7 @@ async function runTuiWorkspace(options) {
8392
8775
  turnCtx,
8393
8776
  emitCommandOutput,
8394
8777
  renderHistoryToOutput,
8778
+ scheduleRender,
8395
8779
  refreshDialogTotalCredits,
8396
8780
  persistExplicitAgentSwitch,
8397
8781
  persistAgentSelection,
@@ -8424,9 +8808,16 @@ async function runTuiWorkspace(options) {
8424
8808
  return dimCliText(` \u2937 ${i + 1}. ${oneLine}`, colorEnabled);
8425
8809
  });
8426
8810
  },
8811
+ getAttachmentLine: () => {
8812
+ const line = formatComposerAttachmentLine(state.attachedImages);
8813
+ return line ? dimCliText(line, resolveCliColorEnabled()) : null;
8814
+ },
8427
8815
  onInputLinesChange: () => {
8428
8816
  renderHistoryToOutput();
8429
- }
8817
+ },
8818
+ // /cd 路径补全候选行渲染需要当前 cwd(读 state 而非入参,保证
8819
+ // /cd 切换后立即以新 cwd 渲染,composer 不必重建)。
8820
+ getCwd: () => state.cwd
8430
8821
  });
8431
8822
  fixedInput = {
8432
8823
  ...baseFixedInput,
@@ -8500,39 +8891,33 @@ async function runTuiWorkspace(options) {
8500
8891
  input.setRawMode?.(false);
8501
8892
  };
8502
8893
  const buildInteractiveTurnHandlers = () => {
8503
- const releaseKeyboardToComposer = () => {
8504
- onData.destroy();
8505
- modalOwnsKeyboard = false;
8506
- };
8507
8894
  const actionGateHandler = async (gate) => {
8508
- modalOwnsKeyboard = true;
8509
- try {
8510
- return await waitForRawActionGate(input, output, gate, spawnRunner, {
8511
- beforeSubprocess: () => fixedInput.pause(),
8512
- afterSubprocess: () => fixedInput.resumeFromSubprocess(),
8513
- registerToken: (handler) => {
8514
- rawActionGateTokenHandler = handler;
8515
- }
8516
- });
8517
- } finally {
8518
- releaseKeyboardToComposer();
8895
+ if (state.autoConfirm && isAutoConfirmableFileWriteGate(gate)) {
8896
+ return buildGateConfirmedResult(gate);
8519
8897
  }
8898
+ return await resolveActionGate(gate, {
8899
+ dialogHost,
8900
+ input,
8901
+ output,
8902
+ spawnRunner,
8903
+ registerToken: (handler) => {
8904
+ rawActionGateTokenHandler = handler;
8905
+ },
8906
+ pauseComposer: () => fixedInput.pause(),
8907
+ resumeComposerFromSubprocess: () => fixedInput.resumeFromSubprocess(),
8908
+ resumeComposerFromDialog: () => fixedInput.resumeFromDialog()
8909
+ });
8520
8910
  };
8521
8911
  const confirmDestructiveAction = async (request) => {
8522
8912
  if (state.autoConfirm) return true;
8523
- modalOwnsKeyboard = true;
8524
- try {
8525
- return await dialogHost.run(
8526
- (anchor) => runConfirmDialog({
8527
- request,
8528
- input,
8529
- output,
8530
- ...anchor
8531
- })
8532
- );
8533
- } finally {
8534
- releaseKeyboardToComposer();
8535
- }
8913
+ return await dialogHost.run(
8914
+ (anchor) => runConfirmDialog({
8915
+ request,
8916
+ input,
8917
+ output,
8918
+ ...anchor
8919
+ })
8920
+ );
8536
8921
  };
8537
8922
  return { actionGateHandler, confirmDestructiveAction };
8538
8923
  };
@@ -8568,8 +8953,26 @@ ${err.message}` : ""}`
8568
8953
  runWakeHandler = (event) => {
8569
8954
  if (done) return;
8570
8955
  if (busy || fixedInput.isPaused()) {
8571
- const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
8572
- ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction).enqueue(event);
8956
+ const text = typeof event === "string" ? event : event.text;
8957
+ const injected = !!turnInjectionInbox && turnInjectionInbox.push({ text, fallback: event });
8958
+ if (injected) {
8959
+ const displayText = typeof event !== "string" && event.kind === "child-run-completed" ? event.displayText ?? event.text : text;
8960
+ const noticeLine = dimCliText(displayText, resolveCliColorEnabled());
8961
+ if (!isInteractiveInput(input)) {
8962
+ output.write(`${noticeLine}
8963
+ `);
8964
+ } else {
8965
+ history.followBottom = true;
8966
+ appendStreamSafeNotice(history, noticeLine, {
8967
+ appendLocalTurn,
8968
+ startTurn
8969
+ });
8970
+ renderHistoryToOutput();
8971
+ }
8972
+ } else {
8973
+ const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
8974
+ ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction).enqueue(event);
8975
+ }
8573
8976
  if (fixedInput.active && !fixedInput.isPaused()) {
8574
8977
  fixedInput.repaint(buffer, cursorPos);
8575
8978
  }
@@ -8710,6 +9113,19 @@ ${err.message}` : ""}`
8710
9113
  output.write(`${refreshMsg}
8711
9114
  `);
8712
9115
  }
9116
+ } else if (res.action?.type === "cwd-refresh") {
9117
+ state = res.nextState;
9118
+ if (res.output) {
9119
+ output.write(`${res.output}
9120
+ `);
9121
+ }
9122
+ if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) !== "0") {
9123
+ const gitStatus = await detectGitStatusAsync(state.cwd);
9124
+ if (gitStatus !== void 0) {
9125
+ state = { ...state, gitStatus };
9126
+ }
9127
+ }
9128
+ scheduleRender();
8713
9129
  } else if (res.action) {
8714
9130
  output.write(
8715
9131
  "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"
@@ -8734,7 +9150,7 @@ ${hint}` : hint;
8734
9150
  };
8735
9151
  const handleInputToken = async (sequence) => {
8736
9152
  if (done) return;
8737
- if (modalOwnsKeyboard) {
9153
+ if (dialogHost.isKeyboardClaimed()) {
8738
9154
  rawActionGateTokenHandler?.(sequence);
8739
9155
  return;
8740
9156
  }
@@ -8918,8 +9334,20 @@ ${merged}` : merged;
8918
9334
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
8919
9335
  return;
8920
9336
  }
9337
+ if (isBackspaceSequence(sequence, {}) && buffer === "" && state.attachedImages.length > 0) {
9338
+ const undo = popLastAttachedImage(state.attachedImages);
9339
+ if (undo.handled && undo.removed) {
9340
+ state = { ...state, attachedImages: undo.images };
9341
+ emitCommandOutput(
9342
+ `[nolo] ${t("attachmentRemovedHint", undo.removed.filename, String(undo.images.length))}`
9343
+ );
9344
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
9345
+ return;
9346
+ }
9347
+ }
8921
9348
  const result = applyTuiInputKey(buffer, sequence, {}, cursorPos, {
8922
- pasteStore
9349
+ pasteStore,
9350
+ cwd: state.cwd
8923
9351
  });
8924
9352
  if (result.redraw) {
8925
9353
  resetHistoryFrameDiffCache(output);
@@ -8942,7 +9370,7 @@ ${merged}` : merged;
8942
9370
  if (busyLock) {
8943
9371
  const trimmedText = submittedText.trim();
8944
9372
  const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
8945
- const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/switch" || busySlashCommand === "/theme" || busySlashCommand === "/density" || busySlashCommand === "/runtime" || busySlashCommand === "/tools" || busySlashCommand === "/thinking" || busySlashCommand === "/auto" || busySlashCommand === "/tasks" || busySlashCommand === "/jobs" || busySlashCommand === "/procs" || busySlashCommand === "/agents" || busySlashCommand === "/doc" || busySlashCommand === "/skill" || busySlashCommand === "/customize" || busySlashCommand === "/login" || busySlashCommand === "/profile" || busySlashCommand === "/version";
9373
+ const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/cd" || busySlashCommand === "/switch" || busySlashCommand === "/theme" || busySlashCommand === "/runtime" || busySlashCommand === "/tools" || busySlashCommand === "/thinking" || busySlashCommand === "/auto" || busySlashCommand === "/tasks" || busySlashCommand === "/jobs" || busySlashCommand === "/procs" || busySlashCommand === "/agents" || busySlashCommand === "/doc" || busySlashCommand === "/skill" || busySlashCommand === "/customize" || busySlashCommand === "/login" || busySlashCommand === "/profile" || busySlashCommand === "/version";
8946
9374
  if (isBusyLocalSlash) {
8947
9375
  await handleBusyLocalSlash(submittedText, busySlashCommand);
8948
9376
  return;
@@ -9012,9 +9440,9 @@ ${merged}` : merged;
9012
9440
  input.on("data", onData);
9013
9441
  fixedInput.repaint(buffer, cursorPos);
9014
9442
  refreshGitStatus();
9015
- await new Promise((resolve2) => {
9016
- resolveDone = resolve2;
9017
- if (done) resolve2();
9443
+ await new Promise((resolve4) => {
9444
+ resolveDone = resolve4;
9445
+ if (done) resolve4();
9018
9446
  });
9019
9447
  } finally {
9020
9448
  finish();
@@ -9028,7 +9456,11 @@ ${merged}` : merged;
9028
9456
  for await (const line of rl) {
9029
9457
  const shouldExit = await runSubmittedLine(
9030
9458
  line,
9031
- (gate) => waitForActionGate(rl, input, output, gate, spawnRunner),
9459
+ (gate) => (
9460
+ // /auto on 同款短路,见上方交互路径的 actionGateHandler 注释:
9461
+ // 只放行 file_write 的 confirm gate,handoff/input 类仍要求真人操作。
9462
+ state.autoConfirm && isAutoConfirmableFileWriteGate(gate) ? Promise.resolve(buildGateConfirmedResult(gate)) : waitForActionGate(rl, input, output, gate, spawnRunner)
9463
+ ),
9032
9464
  async (request) => {
9033
9465
  if (state.autoConfirm) return true;
9034
9466
  rl.pause();
@@ -9067,12 +9499,16 @@ ${renderStatusLine(state)}
9067
9499
  }
9068
9500
  async function startTuiWorkspace(options) {
9069
9501
  const themeOwner = ++latestWorkspaceThemeOwner;
9502
+ void sweepStaleClipboardFiles(getDefaultClipboardTempDir()).catch(() => {
9503
+ });
9070
9504
  try {
9071
9505
  return await runTuiWorkspace(options);
9072
9506
  } finally {
9073
9507
  if (themeOwner === latestWorkspaceThemeOwner) {
9074
9508
  resetWorkspaceThemeState();
9075
9509
  }
9510
+ void sweepStaleClipboardFiles(getDefaultClipboardTempDir()).catch(() => {
9511
+ });
9076
9512
  }
9077
9513
  }
9078
9514
  export {