nolo-cli 0.50.0-alpha.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-4BPV4B2U.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-KZSETRE3.js → agentDeleteCommand-SVXKS5SC.js} +30 -28
  6. package/{agentEmailCommands-5TEW64UO.js → agentEmailCommands-WN7QBTP3.js} +57 -52
  7. package/{agentListCommands-KVLURCS7.js → agentListCommands-2BEBOQIG.js} +42 -33
  8. package/{agentMachineCommands-4LQF5YB5.js → agentMachineCommands-WALP3ONU.js} +30 -28
  9. package/{agentRecordCommands-OXC6JSH6.js → agentRecordCommands-E652SW57.js} +55 -50
  10. package/{agentRunCommand-4AXTA7MR.js → agentRunCommand-6VXWXKCS.js} +60 -53
  11. package/{agentRunControl-4GHHSXFZ.js → agentRunControl-LJVFDJGU.js} +3 -1
  12. package/{agentRuntimeLocal-SIQYQGSK.js → agentRuntimeLocal-SBG7BDFQ.js} +30 -26
  13. package/{agentSlice-VD3BAJAU.js → agentSlice-3WYXIGEO.js} +8 -8
  14. package/{agentSuperviseCommands-VB6BUS5H.js → agentSuperviseCommands-F3RFAL7K.js} +2 -2
  15. package/{appDeleteCommands-LSIIQFYN.js → appDeleteCommands-NTUNMEXY.js} +31 -29
  16. package/{appDeployCommands-VYGKN7HU.js → appDeployCommands-DISWXRTL.js} +31 -29
  17. package/{appDeployStatusCommands-ODFHFUR7.js → appDeployStatusCommands-IKRA6B6F.js} +31 -29
  18. package/{appGetCommands-DPQY67UZ.js → appGetCommands-UMOP2DFV.js} +31 -29
  19. package/{appListCommands-EFXENXWX.js → appListCommands-ZKFD2AKS.js} +31 -29
  20. package/{authCommand-WLWOEVYI.js → authCommand-BS3CQC6I.js} +11 -11
  21. package/{chatQueueReduxAdapter-N5432EP2.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-WWXZYFWM.js → chunk-2TLYVDTN.js} +464 -1135
  26. package/{chunk-6BYAXNH5.js → chunk-3QOCS5IF.js} +11 -2
  27. package/chunk-6IGD47IC.js +19 -0
  28. package/{chunk-QDNFRWAX.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-OZ4JNDIL.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-JBL24PK6.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-HMMOAG2I.js → chunk-DXOELY7S.js} +67 -69
  40. package/{chunk-OFZPJ3NT.js → chunk-E4MARS3Q.js} +1 -1
  41. package/{chunk-27MWS6HD.js → chunk-EAYBPWHX.js} +179 -86
  42. package/{chunk-DQLYEGP7.js → chunk-EGVY3GMT.js} +1 -1
  43. package/{chunk-2JYSSXLY.js → chunk-EJFW7TRP.js} +5 -26
  44. package/{chunk-4YAMAMNA.js → chunk-FGRCSLPV.js} +36 -46
  45. package/{chunk-VFN256RI.js → chunk-G4WSLTQF.js} +56 -97
  46. package/{chunk-HR5XDXYM.js → chunk-G5SQVIFZ.js} +3 -3
  47. package/{chunk-J3UALFHL.js → chunk-GHE5XMVR.js} +1 -1
  48. package/{chunk-7M72YPM2.js → chunk-GKB4AZKV.js} +440 -2981
  49. package/{chunk-S3HB4CQY.js → chunk-GKNGRBE6.js} +256 -40
  50. package/{chunk-Q24S4NDF.js → chunk-HQTK2SFT.js} +1 -1
  51. package/{chunk-XB7ACJFH.js → chunk-IPTZ5TVU.js} +7 -6
  52. package/{chunk-4RXXP6WD.js → chunk-J7KDNHUO.js} +5 -64
  53. package/{chunk-BO3YFOZR.js → chunk-KNQ5LYVZ.js} +57 -2
  54. package/{chunk-AV4Z57LV.js → chunk-KVVNC5V3.js} +2 -2
  55. package/{chunk-VKSBVRGP.js → chunk-LVCAWHI5.js} +4 -4
  56. package/{chunk-5JXRVJAC.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-72COHDZR.js → chunk-OSUZKICS.js} +1600 -190
  60. package/{chunk-HIDPTO4B.js → chunk-Q2QN2RHG.js} +7 -7
  61. package/{chunk-L3JWI7WX.js → chunk-QOKAKDPE.js} +15 -11
  62. package/{chunk-4ZL2KBI7.js → chunk-RFYXK6KI.js} +1 -1
  63. package/chunk-RQSN75DW.js +1766 -0
  64. package/{chunk-RPO6WAZV.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-FTSPVLKJ.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-F22JQTSX.js → chunk-UEV5WVQV.js} +3 -3
  72. package/{chunk-MCX2LX27.js → chunk-UMCMW5R3.js} +51 -34
  73. package/{chunk-6L6UPZNH.js → chunk-VFDRZHPM.js} +16 -16
  74. package/{chunk-C2WF2OY7.js → chunk-W22UTILW.js} +3 -3
  75. package/{chunk-W3RZLXMA.js → chunk-WERPP2S7.js} +2 -2
  76. package/{chunk-AZ6DT2UD.js → chunk-WSUHBABG.js} +1 -1
  77. package/{chunk-3YVCM5PE.js → chunk-X3OGKR5Q.js} +4 -4
  78. package/{chunk-IERBSHMR.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-Y7TGWSUN.js → chunk-YWY25N4V.js} +1 -1
  86. package/{chunk-Z7A54NFZ.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-IHKV53QC.js → createAgentAutomationAction-HTMTGX6Q.js} +25 -21
  92. package/{createDialogAction-4ARDCKPP.js → createDialogAction-WYQBSN6A.js} +27 -23
  93. package/{createPageAction-GQOCPICU.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-YOTCP26N.js → dialogCommands-FRSIAPKY.js} +31 -29
  97. package/{dialogSlice-NX26W623.js → dialogSlice-5KPVIAWH.js} +24 -21
  98. package/{docCreateCommands-JVAMHES4.js → docCreateCommands-DBRJMPDX.js} +30 -28
  99. package/{docDeleteCommands-LW5DWICT.js → docDeleteCommands-BCYPLWXN.js} +31 -29
  100. package/{docReadCommands-AQOZS3I5.js → docReadCommands-L5JX4Q7G.js} +30 -28
  101. package/{docStore-YAE7GYHZ.js → docStore-34NXLCEO.js} +8 -33
  102. package/{docUpdateCommands-FFGGX5RG.js → docUpdateCommands-RELG7XJD.js} +30 -28
  103. package/fetchMessages-KZQVBXKY.js +1 -1
  104. package/{getPricing-JNOCWYDH.js → getPricing-FM7TJZFV.js} +4 -4
  105. package/{handleSendMessageAction-7YKH4UBI.js → handleSendMessageAction-MEYK7RPD.js} +25 -22
  106. package/index.js +64 -64
  107. package/{machineCommands-FMMFBMCQ.js → machineCommands-YSXTOWHQ.js} +59 -54
  108. package/{machineWsRunDispatch-UODRJY63.js → machineWsRunDispatch-DNAYOPQT.js} +54 -49
  109. package/memberThunks-KVPBG4V7.js +638 -0
  110. package/{models-N4NXQ44D.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-GVDEA7RH.js → readlineWorkspace-KZ3LPQG2.js} +866 -377
  115. package/{removeDialogAgentAction-RMPISD42.js → removeDialogAgentAction-JGSCPML3.js} +12 -11
  116. package/{runAgentClientLoop-6NCW5AS6.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-7MJVL4XR.js → spaceCommands-OI7RIB2T.js} +30 -28
  122. package/{streamAgentChatTurn-UAKCHWFN.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-ROUITGAR.js → tools-6ZPQ75XA.js} +37 -32
  127. package/{tuiSummaryLlmCaller-U4EGD2SX.js → tuiSummaryLlmCaller-YKYQB4ZL.js} +17 -15
  128. package/{updateDialogTitleAction-R4M6ZXCX.js → updateDialogTitleAction-LRJ7NSOS.js} +26 -22
  129. package/chunk-EUG45QJR.js +0 -6104
  130. package/spaceSlice-JVVDCGA3.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-WWXZYFWM.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-C2WF2OY7.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-27MWS6HD.js";
90
- import "./chunk-3YVCM5PE.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-RPO6WAZV.js";
127
+ } from "./chunk-S3JNURSO.js";
128
+ import {
129
+ buildResponseGuidelines
130
+ } from "./chunk-6IGD47IC.js";
122
131
  import "./chunk-HS7MLR74.js";
123
- import "./chunk-4ZL2KBI7.js";
124
- import "./chunk-72COHDZR.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-Z7A54NFZ.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-QDNFRWAX.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-IERBSHMR.js";
155
- import "./chunk-7M72YPM2.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-MCX2LX27.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-5JXRVJAC.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-S3HB4CQY.js";
187
- import "./chunk-XB7ACJFH.js";
193
+ } from "./chunk-GKNGRBE6.js";
188
194
  import "./chunk-ZNFJWEN4.js";
189
- import "./chunk-VKSBVRGP.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-HMMOAG2I.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-JBL24PK6.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-F22JQTSX.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-AV4Z57LV.js";
223
- import "./chunk-NQ4N4IGO.js";
224
- import "./chunk-FD36SCCD.js";
225
- import "./chunk-BO3YFOZR.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-Y7TGWSUN.js";
272
- import "./chunk-VFN256RI.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
 
@@ -1863,6 +2021,18 @@ function formatTerminalRunAge(snapshot, now) {
1863
2021
  function formatUnassignedFact(snapshot) {
1864
2022
  return snapshot.unassigned ? "parent: ?" : null;
1865
2023
  }
2024
+ function sanitizeRunSnapshotForNormal(snapshot) {
2025
+ const clean = (value) => stripAnsi(value).replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
2026
+ return {
2027
+ ...snapshot,
2028
+ agentName: snapshot.agentName ? clean(snapshot.agentName) : snapshot.agentName,
2029
+ taskPreview: snapshot.taskPreview ? clean(snapshot.taskPreview) : snapshot.taskPreview,
2030
+ lastAssistantText: snapshot.lastAssistantText ? clean(snapshot.lastAssistantText) : snapshot.lastAssistantText,
2031
+ lastToolNames: snapshot.lastToolNames?.map(clean).filter(Boolean),
2032
+ errorMessage: void 0,
2033
+ logLines: void 0
2034
+ };
2035
+ }
1866
2036
 
1867
2037
  // packages/cli/tui/agentRunPanelLines.ts
1868
2038
  function formatPanelProgress(snapshot, now) {
@@ -2037,11 +2207,12 @@ function createRunDock(deps) {
2037
2207
  const ordered = orderedEntries();
2038
2208
  if (ordered.length === 0) return [];
2039
2209
  const at = now();
2040
- if (ordered.length === 1) {
2041
- return formatAgentRunPanelLines(ordered[0].snapshot, colorEnabled, at);
2210
+ const displaySnapshots = ordered.map((entry) => sanitizeRunSnapshotForNormal(entry.snapshot));
2211
+ if (displaySnapshots.length === 1) {
2212
+ return formatAgentRunPanelLines(displaySnapshots[0], colorEnabled, at);
2042
2213
  }
2043
2214
  return formatRunDockLines(
2044
- ordered.map((entry) => entry.snapshot),
2215
+ displaySnapshots,
2045
2216
  colorEnabled,
2046
2217
  at,
2047
2218
  maxRows
@@ -3312,6 +3483,20 @@ function summarizeAttachment(img) {
3312
3483
  ].join("\n");
3313
3484
  return themeText(text, "chrome", colorEnabled);
3314
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
+ }
3315
3500
  function mergeAttachedImages(existing, incoming) {
3316
3501
  if (incoming.length === 0) return existing;
3317
3502
  if (existing.length === 0) return [...incoming];
@@ -3333,12 +3518,12 @@ function mergeAttachedImages(existing, incoming) {
3333
3518
  return out;
3334
3519
  }
3335
3520
  async function readImagePaths(paths, options = {}) {
3336
- const resolve2 = options.resolve ?? ((p) => p);
3521
+ const resolve4 = options.resolve ?? ((p) => p);
3337
3522
  const reportedFailures = /* @__PURE__ */ new Set();
3338
3523
  const images = [];
3339
3524
  const failures = [];
3340
3525
  for (const raw of paths) {
3341
- const absolute = resolve2(raw);
3526
+ const absolute = resolve4(raw);
3342
3527
  try {
3343
3528
  const img = await readImageAsDataUrl(absolute, {
3344
3529
  ...options.maxBytes !== void 0 ? { maxBytes: options.maxBytes } : {}
@@ -3378,7 +3563,7 @@ async function resolveAttachmentImageUrls({
3378
3563
  // packages/cli/tui/clipboardImage.ts
3379
3564
  import { createHash } from "node:crypto";
3380
3565
  import { mkdirSync } from "node:fs";
3381
- import { writeFile } from "node:fs/promises";
3566
+ import { readdir, stat as stat2, unlink, writeFile } from "node:fs/promises";
3382
3567
  import { tmpdir } from "node:os";
3383
3568
  import { join } from "node:path";
3384
3569
  var ClipboardImageError = class extends Error {
@@ -3390,6 +3575,34 @@ var ClipboardImageError = class extends Error {
3390
3575
  }
3391
3576
  };
3392
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
+ }
3393
3606
  function isRemoteSession(env = process.env) {
3394
3607
  return Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
3395
3608
  }
@@ -3405,13 +3618,20 @@ function getClipboardImageCommand(deps = {}) {
3405
3618
  }
3406
3619
  if (platform === "darwin") {
3407
3620
  const pngpaste = which("pngpaste");
3408
- if (!pngpaste) {
3621
+ if (pngpaste) {
3622
+ return { command: [pngpaste, "-"], outputFormat: "raw" };
3623
+ }
3624
+ const osascript = which("osascript");
3625
+ if (!osascript) {
3409
3626
  throw new ClipboardImageError(
3410
3627
  "binary-missing",
3411
- "\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"
3412
3629
  );
3413
3630
  }
3414
- return { command: [pngpaste, "-"] };
3631
+ return {
3632
+ command: [osascript, "-e", "the clipboard as \xABclass PNGf\xBB"],
3633
+ outputFormat: "osascript-hex"
3634
+ };
3415
3635
  }
3416
3636
  if (platform === "win32") {
3417
3637
  const ps = which("powershell.exe") || which("powershell") || which("pwsh.exe") || which("pwsh");
@@ -3428,7 +3648,8 @@ function getClipboardImageCommand(deps = {}) {
3428
3648
  "-NonInteractive",
3429
3649
  "-Command",
3430
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) }"
3431
- ]
3651
+ ],
3652
+ outputFormat: "raw"
3432
3653
  };
3433
3654
  }
3434
3655
  if (platform === "linux") {
@@ -3441,17 +3662,18 @@ function getClipboardImageCommand(deps = {}) {
3441
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"
3442
3663
  );
3443
3664
  }
3444
- return { command: [wlPaste2, "--type", "image/png"] };
3665
+ return { command: [wlPaste2, "--type", "image/png"], outputFormat: "raw" };
3445
3666
  }
3446
3667
  const xclip = which("xclip");
3447
3668
  if (xclip) {
3448
3669
  return {
3449
- command: [xclip, "-selection", "clipboard", "-t", "image/png", "-o"]
3670
+ command: [xclip, "-selection", "clipboard", "-t", "image/png", "-o"],
3671
+ outputFormat: "raw"
3450
3672
  };
3451
3673
  }
3452
3674
  const wlPaste = which("wl-paste");
3453
3675
  if (wlPaste) {
3454
- return { command: [wlPaste, "--type", "image/png"] };
3676
+ return { command: [wlPaste, "--type", "image/png"], outputFormat: "raw" };
3455
3677
  }
3456
3678
  throw new ClipboardImageError(
3457
3679
  "binary-missing",
@@ -3463,6 +3685,21 @@ function getClipboardImageCommand(deps = {}) {
3463
3685
  `\u5F53\u524D\u64CD\u4F5C\u7CFB\u7EDF\u6682\u4E0D\u652F\u6301\u526A\u8D34\u677F\u56FE\u50CF\u8BFB\u53D6: ${platform}`
3464
3686
  );
3465
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
+ }
3466
3703
  async function readClipboardImage(deps = {}) {
3467
3704
  const env = deps.env ?? process.env;
3468
3705
  const platform = deps.platform ?? process.platform;
@@ -3470,8 +3707,8 @@ async function readClipboardImage(deps = {}) {
3470
3707
  const spawn = deps.spawn ?? Bun.spawn;
3471
3708
  const maxBytes = deps.maxBytes ?? DEFAULT_MAX_IMAGE_BYTES;
3472
3709
  const timeoutMs = deps.timeoutMs ?? DEFAULT_CLIPBOARD_TIMEOUT_MS;
3473
- const tempDir = deps.tempDir ?? join(tmpdir(), "nolo-clipboard");
3474
- const { command } = getClipboardImageCommand({
3710
+ const tempDir = deps.tempDir ?? getDefaultClipboardTempDir();
3711
+ const { command, outputFormat } = getClipboardImageCommand({
3475
3712
  env,
3476
3713
  platform,
3477
3714
  which
@@ -3505,6 +3742,12 @@ async function readClipboardImage(deps = {}) {
3505
3742
  timeoutPromise
3506
3743
  ]);
3507
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
+ }
3508
3751
  throw new ClipboardImageError(
3509
3752
  "empty-clipboard",
3510
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()}` : "")
@@ -3523,6 +3766,23 @@ async function readClipboardImage(deps = {}) {
3523
3766
  `\u8BFB\u53D6\u526A\u8D34\u677F\u56FE\u50CF\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
3524
3767
  );
3525
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
+ }
3526
3786
  const compressResult = await compressImage(stdoutBuf, "image/png");
3527
3787
  const finalBuf = compressResult.buffer;
3528
3788
  const finalMime = compressResult.mime;
@@ -3583,7 +3843,7 @@ function parseStatusOutput(branch, status) {
3583
3843
  return { branch, modified, untracked };
3584
3844
  }
3585
3845
  function execFileUtf8(file, args, cwd) {
3586
- return new Promise((resolve2, reject) => {
3846
+ return new Promise((resolve4, reject) => {
3587
3847
  execFile(
3588
3848
  file,
3589
3849
  args,
@@ -3593,7 +3853,7 @@ function execFileUtf8(file, args, cwd) {
3593
3853
  reject(err);
3594
3854
  return;
3595
3855
  }
3596
- resolve2(typeof stdout === "string" ? stdout : String(stdout ?? ""));
3856
+ resolve4(typeof stdout === "string" ? stdout : String(stdout ?? ""));
3597
3857
  }
3598
3858
  );
3599
3859
  });
@@ -3694,7 +3954,7 @@ function renderStatusLine(state, maxWidth) {
3694
3954
  if (state.autoConfirm === true) {
3695
3955
  emergency.push(themeText("\u23F5", "warning", colorEnabled));
3696
3956
  }
3697
- const runningTotal = runningTaskCount + runningAgentCount;
3957
+ const runningTotal = runningTaskCount;
3698
3958
  if (runningTotal > 0) {
3699
3959
  emergency.push(themeText(`\u2699${runningTotal}`, "info", colorEnabled));
3700
3960
  }
@@ -3847,7 +4107,6 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
3847
4107
  t("contextFieldSkills"),
3848
4108
  t("contextFieldProfile"),
3849
4109
  t("contextFieldRuntime"),
3850
- t("contextFieldTools"),
3851
4110
  t("contextFieldServer")
3852
4111
  ];
3853
4112
  let maxW = 0;
@@ -3877,8 +4136,7 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
3877
4136
  field(labels[4], skills),
3878
4137
  field(labels[5], state.profileName),
3879
4138
  field(labels[6], state.runtimeMode),
3880
- field(labels[7], state.toolDisplay),
3881
- field(labels[8], state.serverUrl),
4139
+ field(labels[7], state.serverUrl),
3882
4140
  "",
3883
4141
  themeText(t("contextNext"), "chrome", colorEnabled),
3884
4142
  next("/agents ", ` ${t("contextNextAgents")}`),
@@ -3908,6 +4166,9 @@ function renderKnownAgents(colorEnabled = resolveCliColorEnabled()) {
3908
4166
  }
3909
4167
 
3910
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";
3911
4172
  var PASTE_TOKEN_PREFIX = "\0PASTE\0";
3912
4173
  function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
3913
4174
  const seq = sequence ?? "";
@@ -4012,6 +4273,16 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
4012
4273
  return { buffer, cursorPos: curPos };
4013
4274
  }
4014
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
+ }
4015
4286
  const completed = completeSlashPrefix(buffer) ?? buffer;
4016
4287
  return { buffer: completed, cursorPos: completed.length };
4017
4288
  }
@@ -4064,9 +4335,9 @@ function deleteBufferRange(buffer, start, end, pasteStore) {
4064
4335
  function releasePastesInRange(buffer, start, end, pasteStore) {
4065
4336
  if (!pasteStore || start >= end) return;
4066
4337
  const slice = buffer.slice(start, end);
4067
- const re = /\[paste #(\d+) · (\d+) lines\]/g;
4338
+ COLLAPSED_PASTE_PLACEHOLDER_RE.lastIndex = 0;
4068
4339
  let match;
4069
- while ((match = re.exec(slice)) !== null) {
4340
+ while ((match = COLLAPSED_PASTE_PLACEHOLDER_RE.exec(slice)) !== null) {
4070
4341
  releaseCollapsedPaste(pasteStore, Number(match[1]));
4071
4342
  }
4072
4343
  }
@@ -4120,12 +4391,10 @@ var SLASH_COMMANDS = [
4120
4391
  "/clear",
4121
4392
  "/compact",
4122
4393
  "/theme",
4123
- "/density",
4124
4394
  "/context",
4125
4395
  "/ctx",
4396
+ "/cd",
4126
4397
  "/runtime",
4127
- "/tools",
4128
- "/thinking",
4129
4398
  "/auto",
4130
4399
  "/switch",
4131
4400
  "/agent",
@@ -4163,12 +4432,49 @@ function completeSlashPrefix(buffer) {
4163
4432
  }
4164
4433
  return prefix.length > buffer.length ? prefix : null;
4165
4434
  }
4166
- function completeSlashCommand(buffer) {
4435
+ function completeSlashCommand(buffer, cwd) {
4167
4436
  if (!buffer.startsWith("/")) return [];
4168
4437
  const trimmed = buffer.trim();
4438
+ if (cwd && buffer.startsWith("/cd ")) {
4439
+ const prefix = buffer.slice("/cd ".length);
4440
+ return completeCdPathCandidates(prefix, cwd);
4441
+ }
4169
4442
  if (trimmed.includes(" ")) return [];
4170
4443
  return SLASH_COMMANDS.filter((cmd) => cmd.startsWith(trimmed) && cmd !== trimmed);
4171
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
+ }
4172
4478
  function isLikelySlashCommand(input) {
4173
4479
  const trimmed = input.trim();
4174
4480
  if (!trimmed.startsWith("/")) return false;
@@ -4187,6 +4493,11 @@ function stripImageTokens(input, hints) {
4187
4493
  return compactWhitespace(out);
4188
4494
  }
4189
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
+
4190
4501
  // packages/cli/tui/agentPicker.ts
4191
4502
  function toAgentPickerItems(entries) {
4192
4503
  return entries.map((entry) => ({
@@ -4348,6 +4659,10 @@ function estimateDefaultCliContextTokens(opts = {}) {
4348
4659
 
4349
4660
  // packages/cli/tui/sessionDispatch.ts
4350
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
+ }
4351
4666
  function createInitialTuiState(env = process.env) {
4352
4667
  const agentKey = asOptionalTrimmedString(env.NOLO_AGENT) ?? DEFAULT_TUI_AGENT_KEY;
4353
4668
  const agentName = asOptionalTrimmedString(env.NOLO_AGENT_NAME) ?? "nolo";
@@ -4380,8 +4695,7 @@ function createInitialTuiState(env = process.env) {
4380
4695
  modeLabel: asOptionalTrimmedString(env.NOLO_CLI_STATUS_MODE) ?? runtimeMode,
4381
4696
  userLanguage: asOptionalTrimmedString(env.NOLO_LANG),
4382
4697
  gitStatus: void 0,
4383
- toolDisplay: normalizeToolDisplayMode(env.NOLO_CLI_TOOLS ?? env.NOLO_TOOLS, "normal"),
4384
- thinkingDisplay: "show",
4698
+ thinkingDisplay: resolveThinkingDisplayMode(env),
4385
4699
  contextWindow: resolveAgentContextWindow({ agentKey, agentName }),
4386
4700
  estimatedContextTokens: estimateDefaultCliContextTokens({
4387
4701
  cwd,
@@ -4505,28 +4819,53 @@ function handleTuiInput(input, state) {
4505
4819
  };
4506
4820
  }
4507
4821
  }
4508
- case "/density": {
4509
- const parts = argText.split(/\s+/);
4510
- const sub = parts[0]?.trim();
4511
- if (!sub) {
4822
+ case "/cd": {
4823
+ if (!argText) {
4512
4824
  return {
4513
4825
  nextState: state,
4514
- output: t("densityCurrent", getActiveDensity())
4826
+ output: `${t("cdCurrent", state.cwd)}
4827
+ ${t("cdUsage")}`
4515
4828
  };
4516
4829
  }
4517
- if (sub === "cozy" || sub === "spacious") {
4518
- setActiveDensity(sub);
4519
- return {
4520
- nextState: state,
4521
- output: t("densitySwitched", sub)
4522
- };
4523
- } 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);
4524
4836
  return {
4525
- nextState: state,
4526
- 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 }
4527
4840
  };
4528
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
+ };
4529
4861
  }
4862
+ case "/density":
4863
+ case "/tools":
4864
+ case "/thinking":
4865
+ return {
4866
+ nextState: state,
4867
+ output: t("displayFixedHint")
4868
+ };
4530
4869
  case "/context":
4531
4870
  case "/ctx":
4532
4871
  return { nextState: state, output: renderContextPanel(state) };
@@ -4542,45 +4881,6 @@ function handleTuiInput(input, state) {
4542
4881
  output: t("runtimeSet", argText)
4543
4882
  };
4544
4883
  }
4545
- case "/tools": {
4546
- if (!argText) {
4547
- return {
4548
- nextState: state,
4549
- output: t("toolsCurrent", state.toolDisplay)
4550
- };
4551
- }
4552
- const normalizedArg = asTrimmedLowercaseString(argText);
4553
- if (!["normal", "pro", "hide", "compact", "verbose", "on", "off"].includes(normalizedArg)) {
4554
- return {
4555
- nextState: state,
4556
- output: t("toolsUsage")
4557
- };
4558
- }
4559
- const nextMode = normalizeToolDisplayMode(normalizedArg, state.toolDisplay);
4560
- return {
4561
- nextState: { ...state, toolDisplay: nextMode },
4562
- output: t("toolsSet", nextMode)
4563
- };
4564
- }
4565
- case "/thinking": {
4566
- const normalizedArg = asTrimmedLowercaseString(argText);
4567
- if (!normalizedArg) {
4568
- return {
4569
- nextState: state,
4570
- output: t("thinkingCurrent", state.thinkingDisplay)
4571
- };
4572
- }
4573
- if (normalizedArg !== "show" && normalizedArg !== "hide") {
4574
- return {
4575
- nextState: state,
4576
- output: t("thinkingUsage")
4577
- };
4578
- }
4579
- return {
4580
- nextState: { ...state, thinkingDisplay: normalizedArg },
4581
- output: t("thinkingSet", normalizedArg)
4582
- };
4583
- }
4584
4884
  case "/auto": {
4585
4885
  const normalizedArg = asTrimmedLowercaseString(argText);
4586
4886
  if (!normalizedArg) {
@@ -4989,7 +5289,7 @@ async function probeTerminalBackground(args = {}) {
4989
5289
  if (typeof stdin.setRawMode !== "function") return null;
4990
5290
  if (!isTerminal(stdin)) return null;
4991
5291
  const wasRaw = Boolean(stdin.isRaw);
4992
- return await new Promise((resolve2) => {
5292
+ return await new Promise((resolve4) => {
4993
5293
  let buffer = "";
4994
5294
  let settled = false;
4995
5295
  let timer;
@@ -5002,7 +5302,7 @@ async function probeTerminalBackground(args = {}) {
5002
5302
  if (!wasRaw) stdin.setRawMode?.(false);
5003
5303
  } catch {
5004
5304
  }
5005
- resolve2(result);
5305
+ resolve4(result);
5006
5306
  };
5007
5307
  const onData = (chunk) => {
5008
5308
  buffer += chunk.toString("latin1");
@@ -5065,9 +5365,46 @@ function detectSystemBrightness(env = process.env, options = {}) {
5065
5365
  return null;
5066
5366
  }
5067
5367
 
5068
- // packages/cli/tui/tuiTurnRunner.ts
5069
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
5070
- 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
+ }
5071
5408
 
5072
5409
  // packages/ai/tools/askChoiceState.ts
5073
5410
  function normalizeAskChoiceArgs(args) {
@@ -5846,24 +6183,6 @@ function emitTerminalBell(output) {
5846
6183
  }
5847
6184
 
5848
6185
  // packages/cli/tui/tuiTurnRunner.ts
5849
- var AGENTS_MD_MAX_BYTES = 8192;
5850
- function readAgentsMdLayer(cwd) {
5851
- for (const name of ["AGENTS.md", "CLAUDE.md"]) {
5852
- const filePath = join3(cwd, name);
5853
- if (existsSync3(filePath)) {
5854
- try {
5855
- let content = readFileSync3(filePath, "utf8").trim();
5856
- if (!content) continue;
5857
- if (Buffer.byteLength(content, "utf8") > AGENTS_MD_MAX_BYTES) {
5858
- content = Buffer.from(content, "utf8").subarray(0, AGENTS_MD_MAX_BYTES).toString("utf8") + "\n\n<!-- AGENTS.md truncated -->";
5859
- }
5860
- return buildAgentsMdLayer(content, name);
5861
- } catch {
5862
- }
5863
- }
5864
- }
5865
- return null;
5866
- }
5867
6186
  async function runAgentChat(scriptDir, state, message, env, output, agentRunner = runAgentTurn, options = {}) {
5868
6187
  const effectiveAgentKey = state.agentKey;
5869
6188
  const effectiveAgentName = state.agentName;
@@ -5908,12 +6227,16 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5908
6227
  }
5909
6228
  }
5910
6229
  const layers = [
5911
- readAgentsMdLayer(state.cwd),
6230
+ readAgentsMdLayerFromDisk(state.cwd),
5912
6231
  // Skill discovery: scan conventional skill dirs for SKILL.md and inject an
5913
6232
  // index layer so the model knows what skills exist and can readFile them
5914
6233
  // on-demand. Mirrors agentRunCommand.ts and desktopAgentRuntimeTurnService.
5915
6234
  buildSkillDiscoveryContextLayer(state.cwd)
5916
6235
  ];
6236
+ const responseGuidelinesBlock = {
6237
+ content: buildResponseGuidelines(true),
6238
+ cacheScope: "session"
6239
+ };
5917
6240
  let memoryPromptBlock = state.cachedMemoryOverlay;
5918
6241
  if (memoryPromptBlock === void 0) {
5919
6242
  memoryPromptBlock = await resolveCliMemory({
@@ -5926,15 +6249,19 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5926
6249
  }
5927
6250
  const memoryOverlayLayer = buildMemoryOverlayLayer({ promptBlock: memoryPromptBlock });
5928
6251
  const memoryUseGuidanceLayer = buildMemoryUseGuidanceLayer({ promptBlock: memoryPromptBlock });
6252
+ const cwdNoticeBlocks = state.pendingCwdNotice ? [{ content: state.pendingCwdNotice, cacheScope: "turn" }] : [];
5929
6253
  const contextBlockScopes = partitionScopedBlocks([
5930
6254
  ...renderTurnContextBlocksWithScope(layers),
6255
+ responseGuidelinesBlock,
5931
6256
  ...renderTurnContextBlocksWithScope([memoryUseGuidanceLayer]),
5932
6257
  // Attached skill bodies are already self-contained sections built by
5933
6258
  // buildSkillContextBlocks; they stay turn-scope because the user can
5934
6259
  // attach/detach skills between turns.
5935
6260
  ...(skillContextBlocks ?? []).map((content) => content.trim()).filter((content) => content.length > 0).map((content) => ({ content, cacheScope: "turn" })),
5936
- ...renderTurnContextBlocksWithScope([memoryOverlayLayer])
6261
+ ...renderTurnContextBlocksWithScope([memoryOverlayLayer]),
6262
+ ...cwdNoticeBlocks
5937
6263
  ]);
6264
+ const spawnedRunsMissingParent = [];
5938
6265
  const result = await agentRunner({
5939
6266
  agentName: effectiveAgentName,
5940
6267
  agentKey: effectiveAgentKey,
@@ -5952,10 +6279,12 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5952
6279
  scriptDir,
5953
6280
  env: {
5954
6281
  ...env,
5955
- NOLO_LANG: state.userLanguage,
5956
- NOLO_CLI_TOOLS: state.toolDisplay
6282
+ NOLO_LANG: state.userLanguage
6283
+ },
6284
+ output: {
6285
+ ...output,
6286
+ tuiTrees: output.tuiTrees === true && typeof output.writeToolBlock === "function"
5957
6287
  },
5958
- output,
5959
6288
  showThinking: state.thinkingDisplay === "show",
5960
6289
  ...options.imageUrls && options.imageUrls.length > 0 ? { imageUrls: options.imageUrls } : {},
5961
6290
  ...options.actionGateHandler ? { actionGateHandler: options.actionGateHandler } : {},
@@ -5979,15 +6308,28 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5979
6308
  // run-completion watcher can attribute terminal-state wakes to this
5980
6309
  // conversation.
5981
6310
  ...state.dialogId ? { parentDialogId: state.dialogId } : {},
6311
+ // 首轮无 dialogId 时的兜底:收集失归属的 spawn,turn 结束回填。
6312
+ onBackgroundRunSpawned: (runId) => {
6313
+ spawnedRunsMissingParent.push(runId);
6314
+ },
5982
6315
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {}
5983
6316
  }),
5984
6317
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {},
5985
6318
  // 转发 dock 订阅:runAgentTurn 的输出层靠它判断「有面板」从而抑制
5986
6319
  // transcript 的进展卡片;dock 本身也靠它接收模型轮询带回来的快照。
5987
6320
  ...options.onAgentRunStatus ? { onAgentRunStatus: options.onAgentRunStatus } : {},
6321
+ ...options.drainInjections ? { drainInjections: options.drainInjections } : {},
5988
6322
  ...skillAllowedTools !== void 0 ? { allowedToolNames: skillAllowedTools } : {},
5989
6323
  ...contextBlockScopes.length > 0 ? { contextBlockScopes } : {}
5990
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
+ }
5991
6333
  return {
5992
6334
  ...result,
5993
6335
  contextWindow: resolveAgentContextWindow({
@@ -5997,12 +6339,47 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5997
6339
  cachedMemoryOverlay: memoryPromptBlock
5998
6340
  };
5999
6341
  }
6000
- function waitForActionGate(rl, input, output, gate, spawnRunner) {
6342
+ var ESC = "\x1B";
6343
+ var CTRL_C = "";
6344
+ function isApprovedConfirmationInput(answer) {
6345
+ const normalized = answer.trim().toLowerCase();
6346
+ return normalized === "" || normalized === "y" || normalized === "yes" || normalized === "\u786E\u8BA4";
6347
+ }
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) {
6001
6373
  const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
6374
+ const isConfirmation = gate.kind === "confirm";
6002
6375
  const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
6003
6376
  const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
6004
6377
  const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
6005
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;
6006
6383
  output.write(`
6007
6384
  [nolo] ${t("actionGateNeeded")}
6008
6385
  `);
@@ -6012,37 +6389,34 @@ function waitForActionGate(rl, input, output, gate, spawnRunner) {
6012
6389
  `);
6013
6390
  output.write(` ${displayCommand}
6014
6391
  `);
6015
- output.write(`[nolo] ${t("actionGateEnterHint")}
6392
+ output.write(`[nolo] ${isConfirmation ? t("actionGateConfirmHint") : t("actionGateEnterHint")}
6016
6393
  `);
6017
- 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) => {
6018
6400
  let settled = false;
6019
6401
  const finish = (result) => {
6020
6402
  if (settled) return;
6021
6403
  settled = true;
6022
6404
  rl.off("close", onClose);
6023
6405
  rl.off("SIGINT", onSigint);
6024
- resolve2(result);
6406
+ resolve4(result);
6025
6407
  };
6026
- const cancelResult = (reason) => ({
6027
- content: `action gate cancelled: ${gate.title}`,
6028
- metadata: {
6029
- exitCode: 130,
6030
- actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6031
- }
6032
- });
6033
- const failResult = (message) => ({
6034
- content: `action gate failed: ${gate.title}`,
6035
- metadata: {
6036
- exitCode: 1,
6037
- actionGateResult: { gateId: gate.id, status: "failed", output: message }
6038
- }
6039
- });
6408
+ const cancelResult = (reason) => buildGateCancelledResult(gate, reason);
6409
+ const failResult = (message) => buildGateFailedResult(gate, message);
6040
6410
  const onClose = () => finish(cancelResult("readline closed"));
6041
6411
  const onSigint = () => finish(cancelResult("interrupted"));
6042
6412
  rl.once("close", onClose);
6043
6413
  rl.once("SIGINT", onSigint);
6044
- rl.question("", async () => {
6414
+ rl.question("", async (answer) => {
6045
6415
  if (settled) return;
6416
+ if (isConfirmation) {
6417
+ finish(isApprovedConfirmationInput(answer) ? buildGateConfirmedResult(gate) : cancelResult("confirmation declined"));
6418
+ return;
6419
+ }
6046
6420
  if (!commandPayload) {
6047
6421
  finish(failResult("unsupported gate payload"));
6048
6422
  return;
@@ -6088,47 +6462,23 @@ function isInteractiveInput(input) {
6088
6462
  return Boolean(candidate.isTTY) && typeof candidate.setRawMode === "function";
6089
6463
  }
6090
6464
  function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6091
- const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
6092
- const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
6093
- const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
6094
- const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
6095
- const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
6096
- output.write(`
6097
- [nolo] ${t("actionGateNeeded")}
6098
- `);
6099
- output.write(`[nolo] ${title}
6100
- `);
6101
- if (body) output.write(`[nolo] ${body}
6102
- `);
6103
- output.write(` ${displayCommand}
6104
- `);
6105
- output.write(`[nolo] ${t("actionGateEnterHint")}
6106
- `);
6107
- return new Promise((resolve2) => {
6465
+ const meta = deriveGateMeta(gate);
6466
+ const { commandPayload, isConfirmation, displayCommand } = meta;
6467
+ writeGatePrompt(output, meta);
6468
+ return new Promise((resolve4) => {
6108
6469
  const rawInput = input;
6109
6470
  let settled = false;
6110
6471
  let commandRunning = false;
6472
+ let confirmationLine = "";
6111
6473
  const finish = (result) => {
6112
6474
  if (settled) return;
6113
6475
  settled = true;
6114
6476
  hooks?.registerToken?.(null);
6115
6477
  input.off("data", onData);
6116
- resolve2(result);
6478
+ resolve4(result);
6117
6479
  };
6118
- const cancel = (reason) => finish({
6119
- content: `action gate cancelled: ${gate.title}`,
6120
- metadata: {
6121
- exitCode: 130,
6122
- actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6123
- }
6124
- });
6125
- const fail = (message) => finish({
6126
- content: `action gate failed: ${gate.title}`,
6127
- metadata: {
6128
- exitCode: 1,
6129
- actionGateResult: { gateId: gate.id, status: "failed", output: message }
6130
- }
6131
- });
6480
+ const cancel = (reason) => finish(buildGateCancelledResult(gate, reason));
6481
+ const fail = (message) => finish(buildGateFailedResult(gate, message));
6132
6482
  const runCommand = async () => {
6133
6483
  if (settled || commandRunning) return;
6134
6484
  commandRunning = true;
@@ -6171,10 +6521,24 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6171
6521
  };
6172
6522
  const handleToken = (text) => {
6173
6523
  if (settled || commandRunning) return;
6174
- if (text.includes("")) {
6524
+ if (text.includes(CTRL_C) || isConfirmation && text === ESC) {
6175
6525
  cancel("interrupted");
6176
6526
  return;
6177
6527
  }
6528
+ if (isConfirmation) {
6529
+ const newline = text.search(/[\r\n]/);
6530
+ confirmationLine += newline >= 0 ? text.slice(0, newline) : text;
6531
+ if (newline >= 0) {
6532
+ const normalized = confirmationLine.trim().toLowerCase();
6533
+ confirmationLine = "";
6534
+ if (!isApprovedConfirmationInput(normalized)) {
6535
+ cancel("confirmation declined");
6536
+ return;
6537
+ }
6538
+ finish(buildGateConfirmedResult(gate));
6539
+ }
6540
+ return;
6541
+ }
6178
6542
  if (text.includes("\r") || text.includes("\n")) {
6179
6543
  void runCommand();
6180
6544
  }
@@ -6190,6 +6554,40 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6190
6554
  }
6191
6555
  });
6192
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
+ }
6193
6591
  async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, confirmDestructiveAction) {
6194
6592
  const scheduleTitlePatchSync = (runResult) => {
6195
6593
  if (!runResult.titlePatchPromise || !runResult.dialogId) return;
@@ -6261,7 +6659,6 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6261
6659
  ctx.scheduleRender();
6262
6660
  }) : ctx.output;
6263
6661
  const requestUserChoice = isInteractiveInput(ctx.input) && ctx.dialogHost ? async (choiceReq) => {
6264
- ctx.modalOwnsKeyboard = true;
6265
6662
  try {
6266
6663
  return await ctx.dialogHost.run(
6267
6664
  (anchor) => runAskChoiceDialog({
@@ -6273,12 +6670,11 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6273
6670
  );
6274
6671
  } catch {
6275
6672
  return { kind: "cancelled" };
6276
- } finally {
6277
- ctx.composerDecoderDrain?.();
6278
- ctx.modalOwnsKeyboard = false;
6279
6673
  }
6280
6674
  } : void 0;
6281
6675
  ctx.runRegistryPoller.beginHold();
6676
+ const injectionInbox = createTurnInjectionInbox();
6677
+ ctx.turnInjectionInbox = injectionInbox;
6282
6678
  try {
6283
6679
  ctx.activeTurnAbort = new AbortController();
6284
6680
  ctx.activeTurnEpoch = myEpoch;
@@ -6295,6 +6691,7 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6295
6691
  ...confirmDestructiveAction ? { confirmDestructiveAction } : {},
6296
6692
  ...requestUserChoice ? { requestUserChoice } : {},
6297
6693
  abortSignal: ctx.activeTurnAbort.signal,
6694
+ drainInjections: () => injectionInbox.drain(),
6298
6695
  pastedTextStore: ctx.pasteStore,
6299
6696
  activityReporter: ctx.activityReporter,
6300
6697
  onAgentRunStatus: (snapshot) => {
@@ -6320,7 +6717,9 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6320
6717
  ...runResult.title ? { dialogTitle: runResult.title } : {}
6321
6718
  } : {},
6322
6719
  ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
6323
- ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
6720
+ ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {},
6721
+ // 本轮已发起 agent 调用(消息已注入),消费切换通知。
6722
+ pendingCwdNotice: void 0
6324
6723
  };
6325
6724
  if (runResult.dialogId && nextDialogKey) {
6326
6725
  ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
@@ -6368,7 +6767,11 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6368
6767
  // estimatedContextTokens:下一轮若 provider 不返回 usage,context
6369
6768
  // chip 仍显示真实累计占用而不是回退到启动时的静态估算。
6370
6769
  ...runResult.turnTokens && runResult.turnTokens.input > 0 ? { estimatedContextTokens: runResult.turnTokens.input } : {},
6371
- ...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
6372
6775
  };
6373
6776
  if (runResult.dialogId && nextDialogKey) {
6374
6777
  ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
@@ -6391,6 +6794,21 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6391
6794
  ctx.runRegistryPoller.endHold();
6392
6795
  ctx.activityIndicator.stop();
6393
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
+ }
6394
6812
  }
6395
6813
  }
6396
6814
  function ensureChatQueueBinding(ctx, actionGateHandler, confirmDestructiveAction) {
@@ -6978,7 +7396,7 @@ function createFixedInput(output, config) {
6978
7396
  const renderInputArea = (buffer, cursorPos) => {
6979
7397
  const colorEnabled = resolveCliColorEnabled();
6980
7398
  const cols = Math.max(1, getColumns());
6981
- const completions = completeSlashCommand(buffer);
7399
+ const completions = completeSlashCommand(buffer, config.getCwd?.());
6982
7400
  const sections = [];
6983
7401
  if (completions.length > 0) {
6984
7402
  sections.push(fitAnsiLine(dimCliText(completions.join(" "), colorEnabled), cols));
@@ -6999,6 +7417,10 @@ function createFixedInput(output, config) {
6999
7417
  for (const line of queueLines) {
7000
7418
  sections.push(fitAnsiLine(line.replace(/\r?\n/g, " "), cols));
7001
7419
  }
7420
+ const attachmentLine = config.getAttachmentLine?.() ?? null;
7421
+ if (attachmentLine) {
7422
+ sections.push(fitAnsiLine(attachmentLine.replace(/\r?\n/g, " "), cols));
7423
+ }
7002
7424
  const promptRaw = t("promptLabel");
7003
7425
  const promptWidth = displayWidth(promptRaw);
7004
7426
  const prompt = colorEnabled ? `${themeColorSequence("accent")}${promptRaw}\x1B[39m` : promptRaw;
@@ -7048,7 +7470,7 @@ function createFixedInput(output, config) {
7048
7470
  }
7049
7471
  const text = sections.join("\n");
7050
7472
  const lines = sections.length;
7051
- 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);
7052
7474
  return { text, lines, cursorCol, cursorRow: headerRows + cursorRow };
7053
7475
  };
7054
7476
  const repaintAt = (buffer, cursorPos, force = false) => {
@@ -7458,6 +7880,7 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7458
7880
  turnCtx,
7459
7881
  emitCommandOutput,
7460
7882
  renderHistoryToOutput,
7883
+ scheduleRender,
7461
7884
  refreshDialogTotalCredits,
7462
7885
  persistExplicitAgentSwitch,
7463
7886
  persistAgentSelection: persistAgentSelection2,
@@ -7631,6 +8054,18 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7631
8054
  emitCommandOutput(t("themeRefreshFailed"));
7632
8055
  }
7633
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
+ }
7634
8069
  if (result.action?.type === "pick-agent") {
7635
8070
  try {
7636
8071
  const pickResult = await dialogHost.run(
@@ -8105,7 +8540,14 @@ async function runTuiWorkspace(options) {
8105
8540
  getInputLines: () => fixedInput.getInputLines(),
8106
8541
  isPaused: () => fixedInput.isPaused()
8107
8542
  },
8108
- 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
+ }
8109
8551
  });
8110
8552
  let sessionEnded = false;
8111
8553
  const selectionState = createSelectionState();
@@ -8134,7 +8576,7 @@ async function runTuiWorkspace(options) {
8134
8576
  const repaintBanner = () => {
8135
8577
  if (!output.isTTY) return;
8136
8578
  if (fixedInput.isPaused()) return;
8137
- if (modalOwnsKeyboard) return;
8579
+ if (dialogHost.isKeyboardClaimed()) return;
8138
8580
  const currentColumns = output.columns ?? 80;
8139
8581
  const welcome = renderWelcome(state, 0, 0, currentColumns);
8140
8582
  const lines = welcome.split("\n");
@@ -8183,7 +8625,6 @@ async function runTuiWorkspace(options) {
8183
8625
  selectionState.anchor = null;
8184
8626
  selectionState.head = null;
8185
8627
  };
8186
- let modalOwnsKeyboard = false;
8187
8628
  let rawActionGateTokenHandler = null;
8188
8629
  const refreshDialogTotalCredits = (dialogId, dialogKey) => {
8189
8630
  const env = options.env ?? process.env;
@@ -8219,6 +8660,7 @@ async function runTuiWorkspace(options) {
8219
8660
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
8220
8661
  };
8221
8662
  let chatQueueBinding = null;
8663
+ let turnInjectionInbox = null;
8222
8664
  const turnCtx = {
8223
8665
  get state() {
8224
8666
  return state;
@@ -8256,24 +8698,18 @@ async function runTuiWorkspace(options) {
8256
8698
  set activeTurnEpoch(v) {
8257
8699
  activeTurnEpoch = v;
8258
8700
  },
8259
- get modalOwnsKeyboard() {
8260
- return modalOwnsKeyboard;
8261
- },
8262
- set modalOwnsKeyboard(v) {
8263
- modalOwnsKeyboard = v;
8264
- },
8265
- get composerDecoderDrain() {
8266
- return composerDecoderDrain;
8267
- },
8268
- set composerDecoderDrain(v) {
8269
- composerDecoderDrain = v;
8270
- },
8271
8701
  get chatQueueBinding() {
8272
8702
  return chatQueueBinding;
8273
8703
  },
8274
8704
  set chatQueueBinding(v) {
8275
8705
  chatQueueBinding = v;
8276
8706
  },
8707
+ get turnInjectionInbox() {
8708
+ return turnInjectionInbox;
8709
+ },
8710
+ set turnInjectionInbox(v) {
8711
+ turnInjectionInbox = v;
8712
+ },
8277
8713
  get sessionEnded() {
8278
8714
  return sessionEnded;
8279
8715
  },
@@ -8339,6 +8775,7 @@ async function runTuiWorkspace(options) {
8339
8775
  turnCtx,
8340
8776
  emitCommandOutput,
8341
8777
  renderHistoryToOutput,
8778
+ scheduleRender,
8342
8779
  refreshDialogTotalCredits,
8343
8780
  persistExplicitAgentSwitch,
8344
8781
  persistAgentSelection,
@@ -8371,9 +8808,16 @@ async function runTuiWorkspace(options) {
8371
8808
  return dimCliText(` \u2937 ${i + 1}. ${oneLine}`, colorEnabled);
8372
8809
  });
8373
8810
  },
8811
+ getAttachmentLine: () => {
8812
+ const line = formatComposerAttachmentLine(state.attachedImages);
8813
+ return line ? dimCliText(line, resolveCliColorEnabled()) : null;
8814
+ },
8374
8815
  onInputLinesChange: () => {
8375
8816
  renderHistoryToOutput();
8376
- }
8817
+ },
8818
+ // /cd 路径补全候选行渲染需要当前 cwd(读 state 而非入参,保证
8819
+ // /cd 切换后立即以新 cwd 渲染,composer 不必重建)。
8820
+ getCwd: () => state.cwd
8377
8821
  });
8378
8822
  fixedInput = {
8379
8823
  ...baseFixedInput,
@@ -8447,39 +8891,33 @@ async function runTuiWorkspace(options) {
8447
8891
  input.setRawMode?.(false);
8448
8892
  };
8449
8893
  const buildInteractiveTurnHandlers = () => {
8450
- const releaseKeyboardToComposer = () => {
8451
- onData.destroy();
8452
- modalOwnsKeyboard = false;
8453
- };
8454
8894
  const actionGateHandler = async (gate) => {
8455
- modalOwnsKeyboard = true;
8456
- try {
8457
- return await waitForRawActionGate(input, output, gate, spawnRunner, {
8458
- beforeSubprocess: () => fixedInput.pause(),
8459
- afterSubprocess: () => fixedInput.resumeFromSubprocess(),
8460
- registerToken: (handler) => {
8461
- rawActionGateTokenHandler = handler;
8462
- }
8463
- });
8464
- } finally {
8465
- releaseKeyboardToComposer();
8895
+ if (state.autoConfirm && isAutoConfirmableFileWriteGate(gate)) {
8896
+ return buildGateConfirmedResult(gate);
8466
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
+ });
8467
8910
  };
8468
8911
  const confirmDestructiveAction = async (request) => {
8469
8912
  if (state.autoConfirm) return true;
8470
- modalOwnsKeyboard = true;
8471
- try {
8472
- return await dialogHost.run(
8473
- (anchor) => runConfirmDialog({
8474
- request,
8475
- input,
8476
- output,
8477
- ...anchor
8478
- })
8479
- );
8480
- } finally {
8481
- releaseKeyboardToComposer();
8482
- }
8913
+ return await dialogHost.run(
8914
+ (anchor) => runConfirmDialog({
8915
+ request,
8916
+ input,
8917
+ output,
8918
+ ...anchor
8919
+ })
8920
+ );
8483
8921
  };
8484
8922
  return { actionGateHandler, confirmDestructiveAction };
8485
8923
  };
@@ -8515,8 +8953,26 @@ ${err.message}` : ""}`
8515
8953
  runWakeHandler = (event) => {
8516
8954
  if (done) return;
8517
8955
  if (busy || fixedInput.isPaused()) {
8518
- const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
8519
- 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
+ }
8520
8976
  if (fixedInput.active && !fixedInput.isPaused()) {
8521
8977
  fixedInput.repaint(buffer, cursorPos);
8522
8978
  }
@@ -8657,6 +9113,19 @@ ${err.message}` : ""}`
8657
9113
  output.write(`${refreshMsg}
8658
9114
  `);
8659
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();
8660
9129
  } else if (res.action) {
8661
9130
  output.write(
8662
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"
@@ -8681,7 +9150,7 @@ ${hint}` : hint;
8681
9150
  };
8682
9151
  const handleInputToken = async (sequence) => {
8683
9152
  if (done) return;
8684
- if (modalOwnsKeyboard) {
9153
+ if (dialogHost.isKeyboardClaimed()) {
8685
9154
  rawActionGateTokenHandler?.(sequence);
8686
9155
  return;
8687
9156
  }
@@ -8865,8 +9334,20 @@ ${merged}` : merged;
8865
9334
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
8866
9335
  return;
8867
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
+ }
8868
9348
  const result = applyTuiInputKey(buffer, sequence, {}, cursorPos, {
8869
- pasteStore
9349
+ pasteStore,
9350
+ cwd: state.cwd
8870
9351
  });
8871
9352
  if (result.redraw) {
8872
9353
  resetHistoryFrameDiffCache(output);
@@ -8889,7 +9370,7 @@ ${merged}` : merged;
8889
9370
  if (busyLock) {
8890
9371
  const trimmedText = submittedText.trim();
8891
9372
  const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
8892
- 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";
8893
9374
  if (isBusyLocalSlash) {
8894
9375
  await handleBusyLocalSlash(submittedText, busySlashCommand);
8895
9376
  return;
@@ -8959,9 +9440,9 @@ ${merged}` : merged;
8959
9440
  input.on("data", onData);
8960
9441
  fixedInput.repaint(buffer, cursorPos);
8961
9442
  refreshGitStatus();
8962
- await new Promise((resolve2) => {
8963
- resolveDone = resolve2;
8964
- if (done) resolve2();
9443
+ await new Promise((resolve4) => {
9444
+ resolveDone = resolve4;
9445
+ if (done) resolve4();
8965
9446
  });
8966
9447
  } finally {
8967
9448
  finish();
@@ -8975,7 +9456,11 @@ ${merged}` : merged;
8975
9456
  for await (const line of rl) {
8976
9457
  const shouldExit = await runSubmittedLine(
8977
9458
  line,
8978
- (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
+ ),
8979
9464
  async (request) => {
8980
9465
  if (state.autoConfirm) return true;
8981
9466
  rl.pause();
@@ -9014,12 +9499,16 @@ ${renderStatusLine(state)}
9014
9499
  }
9015
9500
  async function startTuiWorkspace(options) {
9016
9501
  const themeOwner = ++latestWorkspaceThemeOwner;
9502
+ void sweepStaleClipboardFiles(getDefaultClipboardTempDir()).catch(() => {
9503
+ });
9017
9504
  try {
9018
9505
  return await runTuiWorkspace(options);
9019
9506
  } finally {
9020
9507
  if (themeOwner === latestWorkspaceThemeOwner) {
9021
9508
  resetWorkspaceThemeState();
9022
9509
  }
9510
+ void sweepStaleClipboardFiles(getDefaultClipboardTempDir()).catch(() => {
9511
+ });
9023
9512
  }
9024
9513
  }
9025
9514
  export {