nolo-cli 0.54.0-alpha.1 → 0.54.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 (146) hide show
  1. package/{_executeModel-IVEOJ7L6.js → _executeModel-OPTKYHJA.js} +53 -48
  2. package/{action-IU6ON7ZB.js → action-ORXWW2ZG.js} +9 -9
  3. package/{addContentAction-5V5CCI3I.js → addContentAction-53BZ34AE.js} +9 -8
  4. package/{addDialogAgentAction-KF3KZZNZ.js → addDialogAgentAction-JRYUS54Y.js} +11 -10
  5. package/{agentDeleteCommand-D7AFLKQN.js → agentDeleteCommand-FJIWU2BS.js} +31 -29
  6. package/{agentEmailCommands-MXFZGF54.js → agentEmailCommands-CE32L43B.js} +62 -58
  7. package/{agentListCommands-MP732Q23.js → agentListCommands-VJNKULIK.js} +43 -34
  8. package/{agentMachineCommands-352JFJX4.js → agentMachineCommands-HCJ3WQXL.js} +31 -29
  9. package/agentRecordCommands-QFWERED4.js +119 -0
  10. package/{agentRunCommand-UUWJMH7J.js → agentRunCommand-G3GQMUNN.js} +70 -59
  11. package/{agentRunControl-4GHHSXFZ.js → agentRunControl-INKFDXDA.js} +3 -1
  12. package/{agentRuntimeLocal-KWBPJ53H.js → agentRuntimeLocal-K327ELSL.js} +31 -27
  13. package/{agentSlice-V742SNKC.js → agentSlice-ODKF4J6M.js} +9 -9
  14. package/{agentSuperviseCommands-VB6BUS5H.js → agentSuperviseCommands-5AR3DKNN.js} +2 -2
  15. package/{appDeleteCommands-GV4TF52A.js → appDeleteCommands-XUXMFDI4.js} +32 -30
  16. package/{appDeployCommands-EZMUPNSW.js → appDeployCommands-37F4GPCU.js} +32 -30
  17. package/{appDeployStatusCommands-AAGELKAN.js → appDeployStatusCommands-WSPU7TUK.js} +32 -30
  18. package/{appGetCommands-W37KDAON.js → appGetCommands-YLOBA3EM.js} +32 -30
  19. package/{appListCommands-IFDWBBNV.js → appListCommands-UD62R5ME.js} +32 -30
  20. package/{authCommand-5HM57KPL.js → authCommand-OK6HLHGS.js} +13 -13
  21. package/{authCommands-SBQVTD6V.js → authCommands-2DUIUEMT.js} +2 -2
  22. package/{chatQueueReduxAdapter-ZXXP6E4O.js → chatQueueReduxAdapter-TV2TUOQS.js} +28 -25
  23. package/{chunk-7CH6U3FL.js → chunk-2GRGFCF6.js} +2 -4
  24. package/{chunk-T27IEBNQ.js → chunk-2QDDKFG5.js} +74 -1
  25. package/{chunk-2JYSSXLY.js → chunk-2WBFJHQA.js} +5 -27
  26. package/{chunk-FZMM2KZC.js → chunk-3LKZRP7E.js} +2 -2
  27. package/{chunk-WUMYQ2FE.js → chunk-3Y2K5O2R.js} +64 -6
  28. package/{chunk-5XCW6RQG.js → chunk-3ZTR7ZO4.js} +1 -1
  29. package/{chunk-W6N3GLUU.js → chunk-5FCIPEKT.js} +327 -149
  30. package/{chunk-B54WOZ77.js → chunk-5UAYZEXO.js} +2 -2
  31. package/{chunk-5SXS7T5Q.js → chunk-6COJEAYH.js} +36 -105
  32. package/{chunk-PEMGYJNS.js → chunk-6HDYSFLO.js} +4 -6
  33. package/{chunk-6XIE5ZGI.js → chunk-6HIGYUBQ.js} +1 -1
  34. package/chunk-6IGD47IC.js +19 -0
  35. package/chunk-6YTOJY35.js +352 -0
  36. package/{chunk-6FGXGFAH.js → chunk-75MSWWUE.js} +1 -1
  37. package/{chunk-ALMVQQPG.js → chunk-7FAS3NEF.js} +17 -17
  38. package/{chunk-76UA7376.js → chunk-7NYDJAQ7.js} +20 -15
  39. package/{chunk-2T6XO4PK.js → chunk-A65UV3M5.js} +4 -4
  40. package/{chunk-V2IPBG5U.js → chunk-AA564GBL.js} +2 -2
  41. package/{chunk-SQTVMYMU.js → chunk-AUVFJGPC.js} +4 -4
  42. package/{chunk-HR5XDXYM.js → chunk-BBTN4NQD.js} +4 -4
  43. package/{chunk-CULR4XB4.js → chunk-BG7J6Y5X.js} +1 -1
  44. package/chunk-BY2FF2C4.js +526 -0
  45. package/{chunk-FA4SY4F5.js → chunk-BYEJ7K2Z.js} +1 -1
  46. package/chunk-BYXBJQAS.js +0 -0
  47. package/{chunk-3AOLVADY.js → chunk-DVINR3PL.js} +1 -1
  48. package/{chunk-DIQCV4FR.js → chunk-EA5OYZVD.js} +1 -1
  49. package/{chunk-RO2IUWZE.js → chunk-EICIFHVS.js} +1 -1
  50. package/chunk-EJE33XNR.js +19059 -0
  51. package/{chunk-M2CGG6VJ.js → chunk-EVBFYPDC.js} +1 -1
  52. package/{chunk-KHCLMKZZ.js → chunk-EY7V7REW.js} +280 -6
  53. package/{chunk-MC66ASS6.js → chunk-FJHMLRDQ.js} +12 -12
  54. package/{chunk-YFHUQLJF.js → chunk-GRFK2XP4.js} +2 -2
  55. package/{chunk-J3UALFHL.js → chunk-HJTAGT3W.js} +1 -1
  56. package/{chunk-J7KVJAUQ.js → chunk-HPW5MNDQ.js} +38 -99
  57. package/{chunk-H2G6JXTR.js → chunk-HQUORJTM.js} +1 -1
  58. package/{chunk-OYNDYVSE.js → chunk-JON2PHYT.js} +1 -1
  59. package/{chunk-RBJD33HM.js → chunk-JXIP6ZWH.js} +7 -7
  60. package/{chunk-PTHLNXGY.js → chunk-K4NISKOR.js} +46 -8
  61. package/{chunk-5Y5G76BJ.js → chunk-KLIBF3BY.js} +2 -2
  62. package/chunk-LRGKOI5Y.js +297 -0
  63. package/{chunk-OU7NGN5Y.js → chunk-MLEZPE3U.js} +2 -2
  64. package/{chunk-4RXXP6WD.js → chunk-MYMBBB6Q.js} +5 -64
  65. package/{chunk-ET7BXWAW.js → chunk-N4JKMCY3.js} +2 -2
  66. package/{chunk-45E65JER.js → chunk-NFLA6GSU.js} +4 -4
  67. package/{chunk-SVEUG45B.js → chunk-NHRQCXIQ.js} +0 -24
  68. package/{chunk-WJV3G7BM.js → chunk-NJUQBYFJ.js} +3 -1
  69. package/{chunk-LGUQHYAL.js → chunk-O5FLGN2L.js} +3 -2
  70. package/{chunk-Q24S4NDF.js → chunk-Q4XZVZPN.js} +1 -1
  71. package/{chunk-L3JWI7WX.js → chunk-QOKAKDPE.js} +15 -11
  72. package/{chunk-VM7K5EJL.js → chunk-RHMDMTDK.js} +2 -2
  73. package/{chunk-YZ2WE6CW.js → chunk-SAJSBPI2.js} +2 -2
  74. package/{chunk-WHVQA7PJ.js → chunk-SSXTSPSG.js} +27 -6
  75. package/chunk-SUEIAK5N.js +2574 -0
  76. package/{chunk-X7S7OFUX.js → chunk-SVC3GGUA.js} +6 -6
  77. package/{chunk-7UFH6THF.js → chunk-SVDMVX4G.js} +118 -2
  78. package/{chunk-ZMV5PCRS.js → chunk-SWJGJMTW.js} +1 -1
  79. package/{chunk-DQLYEGP7.js → chunk-SYYUU7EV.js} +2 -2
  80. package/{chunk-K7CNV7GI.js → chunk-TC6P4NDR.js} +7 -7
  81. package/{chunk-SUFDWBLV.js → chunk-TR4DXUYX.js} +1988 -452
  82. package/{chunk-BANYWEYY.js → chunk-TY3ADTX5.js} +1 -1
  83. package/chunk-UAD2WT54.js +64 -0
  84. package/{chunk-HCBQVVFZ.js → chunk-UGBHDMHI.js} +4 -4
  85. package/{chunk-R63FNYSI.js → chunk-UOWXD4VN.js} +851 -970
  86. package/{chunk-GBXR3CSM.js → chunk-UR66XPVF.js} +3 -3
  87. package/{chunk-6BYAXNH5.js → chunk-VZMUOPYG.js} +12 -2
  88. package/{chunk-RVHPY5PC.js → chunk-WQHGJHAL.js} +441 -2982
  89. package/chunk-XIJZOCSP.js +1766 -0
  90. package/{chunk-HHBXCRJS.js → chunk-XOJRZUPF.js} +4 -4
  91. package/{chunk-BKPUPZ6W.js → chunk-YBFJQC2M.js} +95 -778
  92. package/{chunk-MRWGHF56.js → chunk-YDH3FDFQ.js} +12 -356
  93. package/{chunk-SG65C33A.js → chunk-YLCUU3WM.js} +4 -4
  94. package/{chunk-XXZULHJ7.js → chunk-YQBXOHE6.js} +1 -1
  95. package/chunk-ZJ7UHBJS.js +89 -0
  96. package/{chunk-TNPCQNGQ.js → chunk-ZVOHVJI3.js} +4 -9
  97. package/{codingSkills-BYQI6EPT.js → codingSkills-BCQXRZOI.js} +3 -4
  98. package/contentThunks-KNI6VXLT.js +71 -0
  99. package/{createAgentAutomationAction-LE3ZYDT6.js → createAgentAutomationAction-OK4L33KF.js} +29 -25
  100. package/createDialogAction-JDSMY4AY.js +62 -0
  101. package/{createPageAction-X4C7K3OD.js → createPageAction-T7RA3HCG.js} +32 -28
  102. package/{dbSlice-FWF5F4L7.js → dbSlice-TVX6SN2T.js} +7 -7
  103. package/{deleteFile-NPZ64DFN.js → deleteFile-JYKRT7C3.js} +4 -4
  104. package/{dialogCommands-CP2U44A7.js → dialogCommands-UQZYWOOV.js} +33 -31
  105. package/{dialogSlice-2SH5636X.js → dialogSlice-TYW2G7IL.js} +28 -25
  106. package/{docCreateCommands-A2KIJSF7.js → docCreateCommands-OA6HZBWO.js} +37 -36
  107. package/{docDeleteCommands-NZ7XO4Z4.js → docDeleteCommands-KBB3YQI2.js} +34 -33
  108. package/{docReadCommands-VWVK4ZAR.js → docReadCommands-ZLMFVC67.js} +33 -32
  109. package/{docStore-J6GQYKHS.js → docStore-2URPBVCS.js} +9 -34
  110. package/{docUpdateCommands-YTUEC6PN.js → docUpdateCommands-HMH5KUON.js} +37 -36
  111. package/{fetchMessages-KZQVBXKY.js → fetchMessages-DRABNDPZ.js} +3 -3
  112. package/{getPricing-I53UFOPH.js → getPricing-RFOPUWHY.js} +4 -4
  113. package/{handleSendMessageAction-MD325XZS.js → handleSendMessageAction-GO53TY7U.js} +30 -27
  114. package/index.js +67 -67
  115. package/{localDialogRead-NQ33HBSI.js → localDialogRead-PUQKFI73.js} +1 -1
  116. package/{machineCommands-TCNZV2DC.js → machineCommands-RK6L7ZWL.js} +64 -60
  117. package/{machineWsRunDispatch-QSX5IWPH.js → machineWsRunDispatch-3X64DW2Z.js} +59 -55
  118. package/memberThunks-THZYZFSN.js +638 -0
  119. package/{models-E2CYP4IB.js → models-JVKUEER5.js} +2 -2
  120. package/package.json +2 -3
  121. package/{read-J7RVVCHH.js → read-K4JU4MQC.js} +4 -4
  122. package/{readDocTool-G2WBM4IH.js → readDocTool-LQGR6FUN.js} +1 -1
  123. package/{readlineWorkspace-N3RGX7EK.js → readlineWorkspace-XUFJV3RS.js} +1005 -437
  124. package/{removeDialogAgentAction-RMPISD42.js → removeDialogAgentAction-ECHCFB7N.js} +13 -12
  125. package/{runAgentClientLoop-R3NTOERA.js → runAgentClientLoop-24GQ2AW6.js} +51 -46
  126. package/{setDialogExtraReferencesAction-7BUTHDAL.js → setDialogExtraReferencesAction-KCVJNGMB.js} +10 -9
  127. package/{setPrimaryDialogAgentAction-WCYIBGZ4.js → setPrimaryDialogAgentAction-VDOBOMKC.js} +13 -12
  128. package/{settingSlice-7NN2MI6Y.js → settingSlice-VR2IJSXC.js} +8 -8
  129. package/{skillDiscovery-COX56OBO.js → skillDiscovery-2QTZINON.js} +3 -4
  130. package/{spaceCommands-RC7XMYQ7.js → spaceCommands-S5YJJXL7.js} +31 -29
  131. package/{streamAgentChatTurn-WLWEBWSF.js → streamAgentChatTurn-QHKP3ABO.js} +82 -70
  132. package/{switchDialogAgentAction-WGMMQHDI.js → switchDialogAgentAction-5L4UP7VI.js} +11 -10
  133. package/{tableCommands-P2TXJ3CF.js → tableCommands-R6WFRDYF.js} +1 -1
  134. package/{toolApiClient-B2MFXMEG.js → toolApiClient-53U4UDFF.js} +3 -2
  135. package/{tools-LSLDSUWU.js → tools-SSXSTC3O.js} +42 -38
  136. package/{tuiSummaryLlmCaller-TPBL4BV2.js → tuiSummaryLlmCaller-URFJHFC5.js} +19 -17
  137. package/{updateDialogTitleAction-4HJEARM7.js → updateDialogTitleAction-YFYAEW3T.js} +30 -26
  138. package/agentRecordCommands-G6EFHU3G.js +0 -115
  139. package/chunk-AETZ7AD6.js +0 -274
  140. package/chunk-DMCFF4KE.js +0 -435
  141. package/chunk-EUG45QJR.js +0 -6104
  142. package/chunk-J4Z6UI4C.js +0 -7076
  143. package/chunk-OFZPJ3NT.js +0 -114
  144. package/createDialogAction-2C6SR62D.js +0 -59
  145. package/spaceSlice-KBVKKO3M.js +0 -127
  146. 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-XIJZOCSP.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,
@@ -24,13 +25,15 @@ import {
24
25
  findCollapsedPasteSpanAt,
25
26
  fitAnsiLine,
26
27
  formatAssistantDisplay,
28
+ formatCreditsChip,
27
29
  formatTokenCount,
28
- getActiveDensity,
29
30
  getActiveThemeMode,
30
31
  getActiveThemeName,
31
32
  isBalanceExhaustedError,
32
- normalizeToolDisplayMode,
33
33
  padOrTruncateToWidth,
34
+ polishBreathInsertsBlankBetween,
35
+ readAgentsMdLayerFromDisk,
36
+ redactSecrets,
34
37
  releaseCollapsedPaste,
35
38
  releaseCollapsedPasteReferences,
36
39
  renderSurfaceLine,
@@ -46,7 +49,6 @@ import {
46
49
  resolveTuiThemeMode,
47
50
  runAgentTurn,
48
51
  setActiveBrightness,
49
- setActiveDensity,
50
52
  setActiveThemeMode,
51
53
  setActiveThemeName,
52
54
  setMathRenderingEnabled,
@@ -64,30 +66,32 @@ import {
64
66
  wrapTextToLines,
65
67
  wrapTranscriptLine,
66
68
  wrapTranscriptLineWithLayout
67
- } from "./chunk-R63FNYSI.js";
69
+ } from "./chunk-UOWXD4VN.js";
70
+ import "./chunk-S7Z2HMCN.js";
71
+ import "./chunk-LYGNQHUW.js";
72
+ import "./chunk-ESHYE7WU.js";
68
73
  import {
69
74
  listFavoriteAgentIdsAcrossServers,
70
75
  listLocalCachedAgents,
71
76
  listRemoteAgents,
72
77
  listRemoteAgentsAcrossServers,
73
78
  normalizeListedAgent
74
- } from "./chunk-GBXR3CSM.js";
79
+ } from "./chunk-UR66XPVF.js";
75
80
  import "./chunk-ID4G2MAR.js";
76
81
  import {
77
82
  listUserRecordsFromServers,
78
83
  readLiveDbRecordAfterTombstoneMerge
79
- } from "./chunk-Q24S4NDF.js";
80
- import "./chunk-J3UALFHL.js";
81
- import "./chunk-LYGNQHUW.js";
84
+ } from "./chunk-Q4XZVZPN.js";
82
85
  import {
86
+ RUN_WAKE_CHANNEL_ENV,
83
87
  createCliLocalRuntimeAdapter,
84
88
  initCliLocale,
85
89
  parseCliLocale,
86
90
  readTimestamp,
87
91
  setCliLocale,
88
92
  t
89
- } from "./chunk-W6N3GLUU.js";
90
- import "./chunk-45E65JER.js";
93
+ } from "./chunk-5FCIPEKT.js";
94
+ import "./chunk-NFLA6GSU.js";
91
95
  import "./chunk-EADNWBAZ.js";
92
96
  import "./chunk-NI2BS6UD.js";
93
97
  import {
@@ -97,37 +101,46 @@ import {
97
101
  deleteDbRecord,
98
102
  queryUserRecords,
99
103
  readDbRecord
100
- } from "./chunk-DQLYEGP7.js";
101
- import "./chunk-OYNDYVSE.js";
104
+ } from "./chunk-SYYUU7EV.js";
105
+ import "./chunk-JON2PHYT.js";
102
106
  import {
103
107
  NOLO_DEFAULT_AGENT_KEY,
104
108
  resolveCliAgentKeyInput
105
109
  } from "./chunk-EKLRRZIN.js";
110
+ import "./chunk-HJTAGT3W.js";
111
+ import {
112
+ createChatQueueRuntime,
113
+ createTurnRequest,
114
+ normalizeRunCompletionShape
115
+ } from "./chunk-YPREESF4.js";
106
116
  import "./chunk-JDSUTNYY.js";
117
+ import "./chunk-JBTJAPUF.js";
107
118
  import {
119
+ backfillRunRecordParentDialog,
108
120
  checkStaleRun,
109
121
  isRunRecordClaimed,
110
122
  listRunRecords,
111
123
  readRunRecord
112
- } from "./chunk-WHVQA7PJ.js";
113
- import "./chunk-ESHYE7WU.js";
114
- import "./chunk-JBTJAPUF.js";
124
+ } from "./chunk-SSXTSPSG.js";
115
125
  import "./chunk-LTRXGFMS.js";
116
- import "./chunk-RO2IUWZE.js";
126
+ import "./chunk-EICIFHVS.js";
117
127
  import "./chunk-ATNTHIZI.js";
118
128
  import "./chunk-JEXVIMYZ.js";
119
129
  import {
120
130
  prepareTools
121
- } from "./chunk-VM7K5EJL.js";
131
+ } from "./chunk-RHMDMTDK.js";
132
+ import {
133
+ buildResponseGuidelines
134
+ } from "./chunk-6IGD47IC.js";
122
135
  import "./chunk-HS7MLR74.js";
123
- import "./chunk-CULR4XB4.js";
124
- import "./chunk-SUFDWBLV.js";
136
+ import "./chunk-BG7J6Y5X.js";
137
+ import "./chunk-TR4DXUYX.js";
125
138
  import "./chunk-5XTOKQLW.js";
126
139
  import "./chunk-NF64D32W.js";
127
140
  import "./chunk-3L2WKC3W.js";
128
- import "./chunk-YFHUQLJF.js";
141
+ import "./chunk-GRFK2XP4.js";
129
142
  import "./chunk-KQHGZWSV.js";
130
- import "./chunk-5XCW6RQG.js";
143
+ import "./chunk-3ZTR7ZO4.js";
131
144
  import {
132
145
  clipText,
133
146
  formatDuration,
@@ -139,67 +152,65 @@ import {
139
152
  runShowsLogTail,
140
153
  shortRunId
141
154
  } from "./chunk-KXKQO4Q6.js";
155
+ import "./chunk-LRGKOI5Y.js";
156
+ import "./chunk-TY3ADTX5.js";
157
+ import "./chunk-UGBHDMHI.js";
142
158
  import "./chunk-INZZJSQF.js";
143
- import "./chunk-PEMGYJNS.js";
144
- import "./chunk-BANYWEYY.js";
145
- import "./chunk-HCBQVVFZ.js";
146
159
  import "./chunk-MEYIRKDN.js";
147
160
  import "./chunk-63W5N6P3.js";
148
161
  import "./chunk-VF4K2QE6.js";
162
+ import "./chunk-6HDYSFLO.js";
163
+ import "./chunk-FJHMLRDQ.js";
164
+ import "./chunk-WQHGJHAL.js";
165
+ import "./chunk-ZJ7UHBJS.js";
149
166
  import {
150
- createChatQueueRuntime,
151
- createTurnRequest,
152
- normalizeRunCompletionShape
153
- } from "./chunk-YPREESF4.js";
154
- import "./chunk-MC66ASS6.js";
155
- import "./chunk-RVHPY5PC.js";
156
- import {
157
- extractReferenceKeysFromMessage
158
- } from "./chunk-BKPUPZ6W.js";
159
- import "./chunk-M2CGG6VJ.js";
160
- import "./chunk-H2G6JXTR.js";
167
+ extractReferenceKeysFromMessage,
168
+ normalizeUsage
169
+ } from "./chunk-YBFJQC2M.js";
170
+ import "./chunk-EVBFYPDC.js";
171
+ import "./chunk-HQUORJTM.js";
161
172
  import "./chunk-YYLHWIW6.js";
162
- import "./chunk-K7CNV7GI.js";
163
173
  import "./chunk-AGGIDPCZ.js";
164
- import "./chunk-SG65C33A.js";
174
+ import "./chunk-TC6P4NDR.js";
175
+ import "./chunk-YLCUU3WM.js";
165
176
  import "./chunk-5RGU3J7P.js";
177
+ import "./chunk-6YTOJY35.js";
166
178
  import "./chunk-M76ECQRD.js";
167
- import "./chunk-MRWGHF56.js";
179
+ import "./chunk-YDH3FDFQ.js";
168
180
  import "./chunk-URMS4XY2.js";
169
181
  import "./chunk-IA3A5373.js";
170
182
  import "./chunk-GLKOYSRU.js";
171
183
  import {
172
184
  buildSkillDiscoveryContextBlock,
173
185
  buildSkillDiscoveryContextLayer
174
- } from "./chunk-OFZPJ3NT.js";
175
- import "./chunk-V2IPBG5U.js";
186
+ } from "./chunk-SUEIAK5N.js";
187
+ import "./chunk-AA564GBL.js";
176
188
  import {
177
189
  expandEnabledPacks
178
- } from "./chunk-LGUQHYAL.js";
179
- import "./chunk-AETZ7AD6.js";
180
- import "./chunk-KHCLMKZZ.js";
190
+ } from "./chunk-O5FLGN2L.js";
191
+ import "./chunk-EY7V7REW.js";
181
192
  import {
182
193
  buildLocalWorkspaceOpenAiTools,
183
194
  buildLocalWorkspaceToolset,
184
195
  getProcessRegistry,
185
196
  readCommandActionGatePayload
186
- } from "./chunk-J4Z6UI4C.js";
187
- import "./chunk-WUMYQ2FE.js";
197
+ } from "./chunk-EJE33XNR.js";
188
198
  import "./chunk-ZNFJWEN4.js";
189
- import "./chunk-2T6XO4PK.js";
190
- import "./chunk-T27IEBNQ.js";
199
+ import "./chunk-3Y2K5O2R.js";
200
+ import "./chunk-A65UV3M5.js";
201
+ import "./chunk-2QDDKFG5.js";
191
202
  import "./chunk-N7J63Q6C.js";
192
203
  import {
193
204
  buildCurrentTimeBlock,
194
205
  buildIdentityBlock,
195
206
  buildRuntimeGuidanceBlocks,
196
207
  resolveToolGuidedSections
197
- } from "./chunk-DMCFF4KE.js";
198
- import "./chunk-6BYAXNH5.js";
208
+ } from "./chunk-BY2FF2C4.js";
209
+ import "./chunk-VZMUOPYG.js";
199
210
  import {
200
211
  resolvePlatformAuthToken
201
- } from "./chunk-5Y5G76BJ.js";
202
- import "./chunk-TNPCQNGQ.js";
212
+ } from "./chunk-KLIBF3BY.js";
213
+ import "./chunk-ZVOHVJI3.js";
203
214
  import {
204
215
  isQuotaExhaustedError
205
216
  } from "./chunk-IWUMSAQF.js";
@@ -207,7 +218,7 @@ import "./chunk-2MBTWINN.js";
207
218
  import {
208
219
  DEFAULT_CONTEXT_WINDOW,
209
220
  getModelContextWindow
210
- } from "./chunk-PTHLNXGY.js";
221
+ } from "./chunk-K4NISKOR.js";
211
222
  import {
212
223
  buildCompactionMetricsFromPlan,
213
224
  buildCompactionUserContent,
@@ -218,32 +229,32 @@ import {
218
229
  formatMessagesForSummaryWithTruncation,
219
230
  planCompression,
220
231
  serializeMessageContent
221
- } from "./chunk-HR5XDXYM.js";
222
- import "./chunk-ET7BXWAW.js";
223
- import "./chunk-NQ4N4IGO.js";
224
- import "./chunk-FD36SCCD.js";
225
- import "./chunk-7UFH6THF.js";
232
+ } from "./chunk-BBTN4NQD.js";
233
+ import "./chunk-N4JKMCY3.js";
234
+ import "./chunk-BYXBJQAS.js";
235
+ import "./chunk-SVDMVX4G.js";
226
236
  import {
227
237
  compactWhitespace
228
238
  } from "./chunk-DWLEOOGR.js";
239
+ import "./chunk-CS6TCJ7E.js";
240
+ import "./chunk-FD36SCCD.js";
229
241
  import "./chunk-ZHXJ6M7T.js";
230
242
  import {
231
243
  toErrorMessage
232
244
  } from "./chunk-HNMXYYJR.js";
233
245
  import {
234
- buildAgentsMdLayer,
235
246
  buildMemoryOverlayLayer,
236
247
  buildMemoryUseGuidanceLayer,
237
248
  partitionScopedBlocks,
238
249
  renderTurnContextBlocksWithScope
239
- } from "./chunk-3AOLVADY.js";
250
+ } from "./chunk-DVINR3PL.js";
240
251
  import {
241
252
  buildUserResponseLanguageContext
242
- } from "./chunk-7CH6U3FL.js";
253
+ } from "./chunk-2GRGFCF6.js";
243
254
  import {
244
255
  canonicalizeToolNames
245
256
  } from "./chunk-O6OSWFZQ.js";
246
- import "./chunk-FA4SY4F5.js";
257
+ import "./chunk-BYEJ7K2Z.js";
247
258
  import {
248
259
  checkForCliUpdate,
249
260
  runSelfUpdateDetailed
@@ -268,19 +279,20 @@ import {
268
279
  import {
269
280
  DEFAULT_NOLO_SERVER_URL
270
281
  } from "./chunk-OLZBQWZU.js";
271
- import "./chunk-WJV3G7BM.js";
272
- import "./chunk-J7KVJAUQ.js";
273
- import "./chunk-SQTVMYMU.js";
274
- import "./chunk-X7S7OFUX.js";
282
+ import "./chunk-NJUQBYFJ.js";
283
+ import "./chunk-HPW5MNDQ.js";
284
+ import "./chunk-AUVFJGPC.js";
285
+ import "./chunk-SVC3GGUA.js";
275
286
  import "./chunk-Y5GCAMGP.js";
276
287
  import "./chunk-UZ6XXCVR.js";
277
288
  import "./chunk-KX6YBIHG.js";
278
- import "./chunk-4RXXP6WD.js";
279
- import "./chunk-76UA7376.js";
280
- import "./chunk-SVEUG45B.js";
289
+ import "./chunk-MYMBBB6Q.js";
290
+ import "./chunk-UAD2WT54.js";
291
+ import "./chunk-7NYDJAQ7.js";
292
+ import "./chunk-NHRQCXIQ.js";
281
293
  import "./chunk-WWC7I6TA.js";
282
294
  import "./chunk-I42VC5KR.js";
283
- import "./chunk-YZ2WE6CW.js";
295
+ import "./chunk-SAJSBPI2.js";
284
296
  import {
285
297
  asTrimmedLowercaseString
286
298
  } from "./chunk-QSJ3HFEF.js";
@@ -289,18 +301,18 @@ import {
289
301
  BUILTIN_NOLO_AGENT_MODEL,
290
302
  BUILTIN_NOLO_AGENT_NAME,
291
303
  builtinAgentCatalogEntryById
292
- } from "./chunk-2JYSSXLY.js";
304
+ } from "./chunk-2WBFJHQA.js";
293
305
  import {
294
306
  extractCustomId,
295
307
  parsePublicAgentId
296
308
  } from "./chunk-BJIRLHJR.js";
297
- import "./chunk-HHBXCRJS.js";
298
- import "./chunk-L3JWI7WX.js";
309
+ import "./chunk-XOJRZUPF.js";
310
+ import "./chunk-QOKAKDPE.js";
299
311
  import "./chunk-OW765XBU.js";
300
312
  import "./chunk-GBRG75BQ.js";
301
- import "./chunk-KUG52IAP.js";
302
313
  import "./chunk-3EUW3WEO.js";
303
314
  import "./chunk-U6VC4MU4.js";
315
+ import "./chunk-KUG52IAP.js";
304
316
  import "./chunk-LCBAMY2M.js";
305
317
  import "./chunk-4SN6J65J.js";
306
318
  import "./chunk-CF7PE763.js";
@@ -624,9 +636,9 @@ function createRawKeyReader(input) {
624
636
  };
625
637
  const resolveWaiter = (value) => {
626
638
  if (!waiter) return;
627
- const resolve2 = waiter;
639
+ const resolve4 = waiter;
628
640
  waiter = null;
629
- resolve2(value);
641
+ resolve4(value);
630
642
  };
631
643
  const tryDeliver = () => {
632
644
  if (!waiter) return;
@@ -683,12 +695,12 @@ function createRawKeyReader(input) {
683
695
  input.on("close", onClose);
684
696
  input.on("end", onClose);
685
697
  input.resume?.();
686
- const reader = () => new Promise((resolve2) => {
698
+ const reader = () => new Promise((resolve4) => {
687
699
  if (closed) {
688
- resolve2(drainClosed());
700
+ resolve4(drainClosed());
689
701
  return;
690
702
  }
691
- waiter = resolve2;
703
+ waiter = resolve4;
692
704
  tryDeliver();
693
705
  });
694
706
  reader.dispose = () => {
@@ -1225,7 +1237,81 @@ function appendLocalTurn(history, command, output) {
1225
1237
  trimHistoryToBudget(history);
1226
1238
  resetStreamingTurnCache();
1227
1239
  }
1240
+ var TOOL_SUCCESS_LINE_RE = /^(▸ .*?)(?: ×(\d+))?( ✓)$/;
1241
+ function applyTreeRewrite(text, chunk) {
1242
+ if (!chunk.startsWith(TUI_TREE_MARKER)) return null;
1243
+ const payload = chunk.slice(TUI_TREE_MARKER.length).replace(/\u0002\n?$/, "");
1244
+ const tree = `\u2022 ${payload}`;
1245
+ const hadTrailingNewline = text.endsWith("\n");
1246
+ const body = hadTrailingNewline ? text.slice(0, -1) : text;
1247
+ const bodyLines = body.split("\n");
1248
+ const plainLines = bodyLines.map((line) => stripAnsi(line));
1249
+ const tail = plainLines.at(-1) ?? "";
1250
+ let prefix;
1251
+ if (tail.trimStart().startsWith("\u25B8 ")) {
1252
+ prefix = bodyLines.length < 2 ? "" : `${bodyLines.slice(0, -1).join("\n")}
1253
+ `;
1254
+ } else if (tail.trimStart().startsWith("\u2514\u2500\u2500 ")) {
1255
+ const headerIndex = plainLines.findLastIndex((line) => line.trimStart().startsWith("\u2022 "));
1256
+ if (headerIndex < 0) return null;
1257
+ prefix = `${bodyLines.slice(0, headerIndex).join("\n")}${headerIndex > 0 ? "\n" : ""}`;
1258
+ } else return null;
1259
+ return `${prefix}${tree}
1260
+ `;
1261
+ }
1262
+ function foldToolSuccessLine(text, chunk) {
1263
+ const plain = stripAnsi(chunk).replace(/\n+$/, "");
1264
+ if (!plain || plain.includes("\n") || !TOOL_SUCCESS_LINE_RE.test(plain)) return null;
1265
+ const hadTrailingNewline = text.endsWith("\n");
1266
+ const body = hadTrailingNewline ? text.slice(0, -1) : text;
1267
+ const newlineAt = body.lastIndexOf("\n");
1268
+ const tail = newlineAt === -1 ? body : body.slice(newlineAt + 1);
1269
+ const match = TOOL_SUCCESS_LINE_RE.exec(stripAnsi(tail));
1270
+ if (!match || `${match[1]}${match[3]}` !== plain) return null;
1271
+ const count = (match[2] ? Number(match[2]) : 1) + 1;
1272
+ const anchor = tail.lastIndexOf(" ");
1273
+ if (anchor < 0) return null;
1274
+ const head = tail.slice(0, anchor).replace(/\s×\d+$/, "");
1275
+ const merged = `${head} \xD7${count}${tail.slice(anchor)}`;
1276
+ const prefix = newlineAt === -1 ? "" : body.slice(0, newlineAt + 1);
1277
+ return `${prefix}${merged}${hadTrailingNewline ? "\n" : ""}`;
1278
+ }
1228
1279
  function applyOutputChunkToCurrentTurn(history, chunk, kind = "assistant") {
1280
+ if (kind === "tool") {
1281
+ if (chunk.startsWith(TUI_TREE_MARKER)) {
1282
+ const rewrittenTree = applyTreeRewrite(history.currentContent, chunk);
1283
+ if (rewrittenTree === null) {
1284
+ const safe = chunk.slice(TUI_TREE_MARKER.length).replace(/\u0002/g, "");
1285
+ const leaves = safe.split("\n").filter((line) => /^(?:├── |└── )/.test(line));
1286
+ const newest = leaves.at(-1)?.replace(/^(?:├── |└── )/, "\u25B8 ") ?? "";
1287
+ if (!newest) return false;
1288
+ const flat = `${newest}
1289
+ `;
1290
+ history.currentContent = applyTerminalOutputToText(history.currentContent, flat);
1291
+ appendCurrentBlock(history, kind, flat);
1292
+ return false;
1293
+ }
1294
+ const treeContent = rewrittenTree;
1295
+ history.currentContent = treeContent;
1296
+ const lastBlock = history.currentBlocks.at(-1);
1297
+ const treeLines = treeContent.split("\n");
1298
+ const treeHeaderIndex = treeLines.findIndex((line) => line.trimStart().startsWith("\u2022 "));
1299
+ const treeBlock = treeHeaderIndex >= 0 ? treeLines.slice(treeHeaderIndex).join("\n") : treeContent;
1300
+ if (lastBlock?.kind === "tool") lastBlock.content = lastBlock.content.replace(/[^\n]*$/, "") + treeBlock;
1301
+ else history.currentBlocks.push({ kind: "tool", content: treeBlock });
1302
+ return true;
1303
+ }
1304
+ const foldedContent = foldToolSuccessLine(history.currentContent, chunk);
1305
+ if (foldedContent !== null) {
1306
+ history.currentContent = foldedContent;
1307
+ const lastBlock = history.currentBlocks.at(-1);
1308
+ if (lastBlock?.kind === "tool") {
1309
+ const foldedBlock = foldToolSuccessLine(lastBlock.content, chunk);
1310
+ if (foldedBlock !== null) lastBlock.content = foldedBlock;
1311
+ }
1312
+ return true;
1313
+ }
1314
+ }
1229
1315
  const next = applyTerminalOutputToText(history.currentContent, chunk);
1230
1316
  if (next === history.currentContent) return false;
1231
1317
  history.currentContent = next;
@@ -1385,9 +1471,9 @@ function layoutTurnRows(role, content, contentWidth, colorEnabled, command) {
1385
1471
  }
1386
1472
  return rows.length > 0 ? rows : [{ rendered: "", sourceStart: 0, sourceEnd: 0, prefixWidth: 0 }];
1387
1473
  }
1388
- function getTurnLayoutRows(turn, contentWidth, colorEnabled, density, themeFingerprint) {
1474
+ function getTurnLayoutRows(turn, contentWidth, colorEnabled, themeFingerprint) {
1389
1475
  const cached = getCachedTurnLine(turn);
1390
- if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.themeFingerprint === themeFingerprint) {
1476
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.themeFingerprint === themeFingerprint) {
1391
1477
  return cached.layoutRows;
1392
1478
  }
1393
1479
  const layoutRows = layoutTurnRows(turn.role, turn.content, contentWidth, colorEnabled, turn.command);
@@ -1395,7 +1481,6 @@ function getTurnLayoutRows(turn, contentWidth, colorEnabled, density, themeFinge
1395
1481
  setCachedTurnLine(turn, {
1396
1482
  width: contentWidth,
1397
1483
  color: colorEnabled,
1398
- density,
1399
1484
  themeFingerprint,
1400
1485
  lines,
1401
1486
  layoutRows
@@ -1409,6 +1494,40 @@ var streamingTurnCache = null;
1409
1494
  function resetStreamingTurnCache() {
1410
1495
  streamingTurnCache = null;
1411
1496
  }
1497
+ function countFenceFlips(text) {
1498
+ const matches = text.match(/^[ \t]*```/gm);
1499
+ return matches ? matches.length : 0;
1500
+ }
1501
+ var HEADING_LIKE_RE = /^#{1,3} /;
1502
+ function isPipeRowLike(line) {
1503
+ const trimmed = line.trim();
1504
+ return trimmed.length > 0 && trimmed.startsWith("|");
1505
+ }
1506
+ function isSuspiciousAppend(newTail, fenceParityEven) {
1507
+ if (!fenceParityEven) return true;
1508
+ if (newTail.includes("\r")) return true;
1509
+ if (newTail.includes("```")) return true;
1510
+ if (newTail.includes("$$") || newTail.includes("\\[") || newTail.includes("\\]")) {
1511
+ return true;
1512
+ }
1513
+ if (/\n{4,}/.test(newTail)) return true;
1514
+ if (newTail.includes("\0")) return true;
1515
+ return hasPipeRowLine(newTail);
1516
+ }
1517
+ function hasPipeRowLine(text) {
1518
+ const lines = text.split("\n");
1519
+ return lines.some((line) => isPipeRowLike(line));
1520
+ }
1521
+ function isSuspiciousBoundary(committedLastLine, newTail) {
1522
+ const newlineIdx = newTail.indexOf("\n");
1523
+ const firstLine = newlineIdx === -1 ? newTail : newTail.slice(0, newlineIdx);
1524
+ if (HEADING_LIKE_RE.test(firstLine) && committedLastLine !== "") return true;
1525
+ if (HEADING_LIKE_RE.test(committedLastLine) && firstLine !== "") return true;
1526
+ if (isPipeRowLike(firstLine) || isPipeRowLike(committedLastLine)) {
1527
+ return true;
1528
+ }
1529
+ return polishBreathInsertsBlankBetween(committedLastLine, firstLine);
1530
+ }
1412
1531
  function renderPrefixTurnBlock(role, content, contentWidth, colorEnabled) {
1413
1532
  content = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
1414
1533
  if (role === "user") {
@@ -1450,75 +1569,105 @@ function renderTailTurnBlock(role, content, contentWidth, colorEnabled) {
1450
1569
  return lines;
1451
1570
  }
1452
1571
  }
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);
1572
+ function getStreamingTurnLines(role, content, contentWidth, colorEnabled, themeFingerprint) {
1573
+ const forceValidate = process.env.NOLO_TUI_RENDER_VALIDATE === "1";
1574
+ let cache = streamingTurnCache;
1575
+ const geometryChanged = !cache || cache.role !== role || cache.contentWidth !== contentWidth || cache.colorEnabled !== colorEnabled || cache.themeFingerprint !== themeFingerprint;
1576
+ if (geometryChanged || !content.startsWith(cache.fullContent)) {
1577
+ return rebuildStreamingCache(content, role, contentWidth, colorEnabled, themeFingerprint);
1578
+ }
1579
+ if (content === cache.fullContent && cache.lastResult) {
1580
+ return cache.lastResult;
1581
+ }
1582
+ const suffix = content.slice(cache.fullContent.length);
1583
+ if (suffix === "") {
1584
+ return cache.lastResult ?? cache.committedLines;
1585
+ }
1586
+ const renderFull = () => {
1587
+ const lines = renderTurnBlock(role, content, contentWidth, colorEnabled);
1588
+ cache.fullContent = content;
1589
+ cache.fenceFlips = countFenceFlips(content);
1590
+ const lastNewline = content.lastIndexOf("\n");
1591
+ cache.completeContentLength = lastNewline + 1;
1592
+ cache.committedLines = lastNewline === -1 ? [] : renderTurnBlock(role, content.slice(0, lastNewline), contentWidth, colorEnabled);
1593
+ cache.pendingTail = content.slice(lastNewline + 1);
1594
+ cache.committedLastLine = lastNewline === -1 ? "" : lastLogicalLineOf(content.slice(0, lastNewline + 1));
1595
+ cache.lastResult = lines;
1596
+ return lines;
1597
+ };
1598
+ const newTail = cache.pendingTail + suffix;
1599
+ if (!cache.fastPathEnabled || isSuspiciousAppend(newTail, cache.fenceFlips % 2 === 0) || isSuspiciousBoundary(cache.committedLastLine, newTail)) {
1600
+ return renderFull();
1601
+ }
1602
+ const tailRenderer = cache.completeContentLength === 0 ? renderPrefixTurnBlock : renderTailTurnBlock;
1603
+ const tailLines = tailRenderer(role, newTail, contentWidth, colorEnabled);
1604
+ const composed = cache.completeContentLength === 0 ? tailLines : [...cache.committedLines, ...tailLines];
1605
+ if (!cache.spliceValidated || forceValidate) {
1606
+ const full = renderTurnBlock(role, content, contentWidth, colorEnabled);
1607
+ cache.spliceValidated = true;
1608
+ if (composed.length === full.length && composed.every((line, index) => line === full[index])) {
1609
+ commitCompletedTail(cache, content, newTail, role, contentWidth, colorEnabled);
1610
+ cache.lastResult = composed;
1611
+ return composed;
1612
+ }
1613
+ cache.fastPathEnabled = false;
1614
+ return renderFull();
1615
+ }
1616
+ commitCompletedTail(cache, content, newTail, role, contentWidth, colorEnabled);
1617
+ cache.lastResult = composed;
1618
+ return composed;
1619
+ }
1620
+ function lastLogicalLineOf(content) {
1621
+ const trimmed = content.replace(/\n$/, "");
1622
+ const idx = trimmed.lastIndexOf("\n");
1623
+ return idx === -1 ? trimmed : trimmed.slice(idx + 1);
1624
+ }
1625
+ function rebuildStreamingCache(content, role, contentWidth, colorEnabled, themeFingerprint) {
1626
+ const lines = renderTurnBlock(role, content, contentWidth, colorEnabled);
1627
+ const lastNewline = content.lastIndexOf("\n");
1628
+ streamingTurnCache = {
1629
+ role,
1630
+ contentWidth,
1631
+ colorEnabled,
1632
+ themeFingerprint,
1633
+ fullContent: content,
1634
+ // committed 渲染源剥掉段尾 \n(幽灵空行修正,见 renderFull 注释)。
1635
+ committedLines: lastNewline === -1 ? [] : renderTurnBlock(role, content.slice(0, lastNewline), contentWidth, colorEnabled),
1636
+ completeContentLength: lastNewline + 1,
1637
+ pendingTail: content.slice(lastNewline + 1),
1638
+ fenceFlips: countFenceFlips(content),
1639
+ committedLastLine: lastNewline === -1 ? "" : lastLogicalLineOf(content.slice(0, lastNewline + 1)),
1640
+ fastPathEnabled: true,
1641
+ spliceValidated: false,
1642
+ lastResult: lines
1643
+ };
1644
+ return lines;
1645
+ }
1646
+ function commitCompletedTail(cache, content, newTail, role, contentWidth, colorEnabled) {
1647
+ const lastNewline = newTail.lastIndexOf("\n");
1648
+ if (lastNewline !== -1) {
1649
+ const completePart = newTail.slice(0, lastNewline + 1);
1650
+ const renderSource = completePart.slice(0, -1);
1651
+ const renderer = cache.completeContentLength === 0 ? renderPrefixTurnBlock : renderTailTurnBlock;
1652
+ cache.committedLines.push(
1653
+ ...renderer(role, renderSource, contentWidth, colorEnabled)
1654
+ );
1655
+ cache.completeContentLength += completePart.length;
1656
+ cache.fenceFlips += countFenceFlips(completePart);
1657
+ cache.committedLastLine = lastLogicalLineOf(completePart);
1658
+ cache.pendingTail = newTail.slice(lastNewline + 1);
1507
1659
  }
1508
- const tailContent = content.slice(streamingTurnCache.prefixLength);
1509
- const tailLines = renderTailTurnBlock(role, tailContent, contentWidth, colorEnabled);
1510
- return [...streamingTurnCache.prefixLines, ...tailLines];
1660
+ cache.fullContent = content;
1511
1661
  }
1512
1662
  function countTurnLines(role, content, contentWidth, command) {
1513
1663
  return layoutTurnRows(role, content, contentWidth, false, command).length;
1514
1664
  }
1515
1665
  function buildTurnOffsets(history, contentWidth) {
1516
- const density = getActiveDensity();
1517
1666
  const entries = [];
1518
1667
  let offset = 0;
1519
1668
  for (let i = 0; i < history.turns.length; i++) {
1520
1669
  const turn = history.turns[i];
1521
- const separatorAbove = density === "spacious" && (i > 0 || turn.role === "user") ? 1 : 0;
1670
+ const separatorAbove = i > 0 && turn.role === "user" ? 1 : 0;
1522
1671
  offset += separatorAbove;
1523
1672
  const cacheMap = turnLineCountCache.get(turn);
1524
1673
  let lineCount;
@@ -1547,7 +1696,6 @@ function getAllTurnEntries(history, contentWidth) {
1547
1696
  const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
1548
1697
  const turns = [...history.turns];
1549
1698
  if (history.currentRole !== null && history.currentContent) {
1550
- const density = getActiveDensity();
1551
1699
  const colorEnabled = resolveCliColorEnabled();
1552
1700
  const themeFingerprint = tuiRenderThemeFingerprint(colorEnabled);
1553
1701
  const streamingLines = getStreamingTurnLines(
@@ -1555,10 +1703,9 @@ function getAllTurnEntries(history, contentWidth) {
1555
1703
  history.currentContent,
1556
1704
  contentWidth,
1557
1705
  colorEnabled,
1558
- density,
1559
1706
  themeFingerprint
1560
1707
  );
1561
- const sep = currentTurnSeparator(history, density);
1708
+ const sep = currentTurnSeparator(history);
1562
1709
  const startRow = finalizedLines + sep;
1563
1710
  entries.push({
1564
1711
  startRow,
@@ -1573,12 +1720,11 @@ function getAllTurnEntries(history, contentWidth) {
1573
1720
  }
1574
1721
  return { entries, totalLines: finalizedLines, turns };
1575
1722
  }
1576
- function currentTurnSeparator(history, density) {
1577
- return density === "spacious" && (history.turns.length > 0 || history.currentRole === "user") ? 1 : 0;
1723
+ function currentTurnSeparator(history) {
1724
+ return history.turns.length > 0 && history.currentRole === "user" ? 1 : 0;
1578
1725
  }
1579
1726
  function buildHistoryLayoutRows(history, contentWidth) {
1580
1727
  const colorEnabled = resolveCliColorEnabled();
1581
- const density = getActiveDensity();
1582
1728
  const themeFingerprint = tuiRenderThemeFingerprint(colorEnabled);
1583
1729
  const rows = [];
1584
1730
  const separatorRow = () => ({
@@ -1589,20 +1735,19 @@ function buildHistoryLayoutRows(history, contentWidth) {
1589
1735
  });
1590
1736
  for (let i = 0; i < history.turns.length; i++) {
1591
1737
  const turn = history.turns[i];
1592
- if (density === "spacious" && (i > 0 || turn.role === "user")) {
1738
+ if (i > 0 && turn.role === "user") {
1593
1739
  rows.push(separatorRow());
1594
1740
  }
1595
1741
  rows.push(...getTurnLayoutRows(
1596
1742
  turn,
1597
1743
  contentWidth,
1598
1744
  colorEnabled,
1599
- density,
1600
1745
  themeFingerprint
1601
1746
  ));
1602
1747
  }
1603
1748
  if (history.currentRole !== null && history.currentContent) {
1604
1749
  const i = history.turns.length;
1605
- if (density === "spacious" && (i > 0 || history.currentRole === "user")) {
1750
+ if (i > 0 && history.currentRole === "user") {
1606
1751
  rows.push(separatorRow());
1607
1752
  }
1608
1753
  rows.push(
@@ -1625,7 +1770,6 @@ function renderHistory(output, history, inputLines, selection) {
1625
1770
  const visibleHeight = Math.max(1, rows - inputLines);
1626
1771
  const contentWidth = Math.max(1, columns - 1);
1627
1772
  const colorEnabled = resolveCliColorEnabled();
1628
- const density = getActiveDensity();
1629
1773
  const themeFingerprint = tuiRenderThemeFingerprint(colorEnabled);
1630
1774
  const { entries, totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
1631
1775
  let currentLines = [];
@@ -1636,10 +1780,9 @@ function renderHistory(output, history, inputLines, selection) {
1636
1780
  history.currentContent,
1637
1781
  contentWidth,
1638
1782
  colorEnabled,
1639
- density,
1640
1783
  themeFingerprint
1641
1784
  );
1642
- const sep = currentTurnSeparator(history, density);
1785
+ const sep = currentTurnSeparator(history);
1643
1786
  currentStart = finalizedLines + sep;
1644
1787
  }
1645
1788
  const totalLines = currentStart >= 0 ? currentStart + currentLines.length : finalizedLines;
@@ -1670,7 +1813,7 @@ function renderHistory(output, history, inputLines, selection) {
1670
1813
  const turn = history.turns[i];
1671
1814
  const cached = getCachedTurnLine(turn);
1672
1815
  let layoutRows;
1673
- if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.density === density && cached.themeFingerprint === themeFingerprint) {
1816
+ if (cached && cached.width === contentWidth && cached.color === colorEnabled && cached.themeFingerprint === themeFingerprint) {
1674
1817
  layoutRows = cached.layoutRows;
1675
1818
  } else {
1676
1819
  renderCacheMissCount += 1;
@@ -1678,7 +1821,6 @@ function renderHistory(output, history, inputLines, selection) {
1678
1821
  turn,
1679
1822
  contentWidth,
1680
1823
  colorEnabled,
1681
- density,
1682
1824
  themeFingerprint
1683
1825
  );
1684
1826
  }
@@ -1690,7 +1832,7 @@ function renderHistory(output, history, inputLines, selection) {
1690
1832
  }
1691
1833
  }
1692
1834
  if (currentStart >= 0) {
1693
- const separatorAbove = currentTurnSeparator(history, density);
1835
+ const separatorAbove = currentTurnSeparator(history);
1694
1836
  if (separatorAbove > 0) {
1695
1837
  const sepRow = currentStart - 1;
1696
1838
  if (sepRow >= winStart && sepRow < winEnd) {
@@ -1750,6 +1892,7 @@ function createHistoryOutputStream(history, onUpdate) {
1750
1892
  const stream = {
1751
1893
  isTTY: true,
1752
1894
  assistantLabelManaged: true,
1895
+ tuiTrees: true,
1753
1896
  write(chunk) {
1754
1897
  const text = typeof chunk === "string" ? chunk : chunk.toString();
1755
1898
  if (applyOutputChunkToCurrentTurn(history, text)) {
@@ -1758,9 +1901,9 @@ function createHistoryOutputStream(history, onUpdate) {
1758
1901
  return true;
1759
1902
  },
1760
1903
  writeToolBlock(chunk) {
1761
- if (applyOutputChunkToCurrentTurn(history, chunk, "tool")) {
1762
- onUpdate();
1763
- }
1904
+ const applied = applyOutputChunkToCurrentTurn(history, chunk, "tool");
1905
+ if (applied) onUpdate();
1906
+ return applied;
1764
1907
  }
1765
1908
  };
1766
1909
  return stream;
@@ -1774,7 +1917,7 @@ function applyScrollAction(history, action, output, inputLines) {
1774
1917
  const { totalLines: finalizedLines } = buildTurnOffsets(history, contentWidth);
1775
1918
  let totalLines = finalizedLines;
1776
1919
  if (history.currentRole !== null && history.currentContent) {
1777
- totalLines += currentTurnSeparator(history, getActiveDensity()) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
1920
+ totalLines += currentTurnSeparator(history) + countTurnLines(history.currentRole, history.currentContent, contentWidth);
1778
1921
  }
1779
1922
  const maxScrollTop = Math.max(0, totalLines - visibleHeight);
1780
1923
  history.followBottom = false;
@@ -1823,6 +1966,14 @@ function resolveDialogBottomRow(args) {
1823
1966
  return Math.max(1, resolveTtyRows(args.output) - args.inputLines);
1824
1967
  }
1825
1968
  function createDialogHost(args) {
1969
+ let keyboardClaimed = false;
1970
+ const claimKeyboard = () => {
1971
+ keyboardClaimed = true;
1972
+ };
1973
+ const releaseKeyboard = () => {
1974
+ args.drainDecoder?.();
1975
+ keyboardClaimed = false;
1976
+ };
1826
1977
  return {
1827
1978
  async run(body) {
1828
1979
  const anchor = {
@@ -1832,14 +1983,25 @@ function createDialogHost(args) {
1832
1983
  inputLines: args.composer.getInputLines()
1833
1984
  })
1834
1985
  };
1986
+ claimKeyboard();
1835
1987
  args.composer.pause();
1836
1988
  try {
1837
1989
  return await body(anchor);
1838
1990
  } finally {
1839
1991
  resetHistoryFrameDiffCache(args.output);
1840
1992
  args.composer.resumeFromDialog();
1993
+ releaseKeyboard();
1841
1994
  }
1842
- }
1995
+ },
1996
+ async withKeyboard(body) {
1997
+ claimKeyboard();
1998
+ try {
1999
+ return await body();
2000
+ } finally {
2001
+ releaseKeyboard();
2002
+ }
2003
+ },
2004
+ isKeyboardClaimed: () => keyboardClaimed
1843
2005
  };
1844
2006
  }
1845
2007
 
@@ -1865,13 +2027,14 @@ function formatUnassignedFact(snapshot) {
1865
2027
  }
1866
2028
  function sanitizeRunSnapshotForNormal(snapshot) {
1867
2029
  const clean = (value) => stripAnsi(value).replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim();
2030
+ const errorMessage = snapshot.errorMessage?.trim() ? redactSecrets(clean(snapshot.errorMessage)) : void 0;
1868
2031
  return {
1869
2032
  ...snapshot,
1870
2033
  agentName: snapshot.agentName ? clean(snapshot.agentName) : snapshot.agentName,
1871
2034
  taskPreview: snapshot.taskPreview ? clean(snapshot.taskPreview) : snapshot.taskPreview,
1872
2035
  lastAssistantText: snapshot.lastAssistantText ? clean(snapshot.lastAssistantText) : snapshot.lastAssistantText,
1873
2036
  lastToolNames: snapshot.lastToolNames?.map(clean).filter(Boolean),
1874
- errorMessage: void 0,
2037
+ errorMessage,
1875
2038
  logLines: void 0
1876
2039
  };
1877
2040
  }
@@ -1912,9 +2075,10 @@ function formatAgentRunPanelLines(snapshot, colorEnabled, now = Date.now()) {
1912
2075
  const progressPart = formatPanelProgress(snapshot, now);
1913
2076
  const age = formatTerminalRunAge(snapshot, now);
1914
2077
  const agePart = age ? ` \xB7 ${age}` : "";
2078
+ const creditsPart = typeof snapshot.credits === "number" && Number.isFinite(snapshot.credits) ? ` \xB7 ${formatCreditsChip(snapshot.credits)}` : "";
1915
2079
  if (!colorEnabled) {
1916
2080
  lines.push(
1917
- `\u{1F916} Sub-Agent: ${name}${runId} \xB7 ${statusSymbol} ${status}${unassignedPart}${agePart}${progressPart}${errPart}`
2081
+ `\u{1F916} Sub-Agent: ${name}${runId} \xB7 ${statusSymbol} ${status}${unassignedPart}${agePart}${progressPart}${creditsPart}${errPart}`
1918
2082
  );
1919
2083
  } else {
1920
2084
  const botIcon = themeText("\u{1F916}", "accent", true);
@@ -1924,8 +2088,9 @@ function formatAgentRunPanelLines(snapshot, colorEnabled, now = Date.now()) {
1924
2088
  const unassignedText = unassignedPart ? themeText(unassignedPart, "muted") : "";
1925
2089
  const ageText = agePart ? themeText(agePart, "chrome") : "";
1926
2090
  const progressText = progressPart ? themeText(progressPart, "chrome") : "";
2091
+ const creditsText = creditsPart ? themeText(creditsPart, "muted") : "";
1927
2092
  const errorText = errPart ? themeText(errPart, "danger") : "";
1928
- lines.push(`${botIcon}${labelText}${nameText}${statusText}${unassignedText}${ageText}${progressText}${errorText}`);
2093
+ lines.push(`${botIcon}${labelText}${nameText}${statusText}${unassignedText}${ageText}${progressText}${creditsText}${errorText}`);
1929
2094
  }
1930
2095
  const detail = formatPanelDetail(snapshot);
1931
2096
  if (detail) {
@@ -2049,8 +2214,7 @@ function createRunDock(deps) {
2049
2214
  const ordered = orderedEntries();
2050
2215
  if (ordered.length === 0) return [];
2051
2216
  const at = now();
2052
- const mode = typeof deps.displayMode === "function" ? deps.displayMode() : deps.displayMode;
2053
- const displaySnapshots = mode === "normal" || mode === "hide" ? ordered.map((entry) => sanitizeRunSnapshotForNormal(entry.snapshot)) : ordered.map((entry) => entry.snapshot);
2217
+ const displaySnapshots = ordered.map((entry) => sanitizeRunSnapshotForNormal(entry.snapshot));
2054
2218
  if (displaySnapshots.length === 1) {
2055
2219
  return formatAgentRunPanelLines(displaySnapshots[0], colorEnabled, at);
2056
2220
  }
@@ -2122,6 +2286,9 @@ function formatRunDockRow(snapshot, colorEnabled, now) {
2122
2286
  if (typeof snapshot.toolCallCount === "number" && Number.isFinite(snapshot.toolCallCount)) {
2123
2287
  facts.push(`${snapshot.toolCallCount} tools`);
2124
2288
  }
2289
+ if (typeof snapshot.credits === "number" && Number.isFinite(snapshot.credits)) {
2290
+ facts.push(formatCreditsChip(snapshot.credits, { compact: true }));
2291
+ }
2125
2292
  const inFlight = activeInFlight(snapshot);
2126
2293
  if (inFlight) {
2127
2294
  facts.push(formatInFlightFact(inFlight, now));
@@ -2224,8 +2391,7 @@ function createActivityIndicator(deps) {
2224
2391
  onRepaint: deps.onRepaint,
2225
2392
  now,
2226
2393
  setIntervalFn,
2227
- clearIntervalFn,
2228
- displayMode: deps.displayMode
2394
+ clearIntervalFn
2229
2395
  });
2230
2396
  const updateAgentRun = (snapshot) => {
2231
2397
  runDock.update(snapshot);
@@ -2309,6 +2475,8 @@ function snapshotFromRunRecord(record, now) {
2309
2475
  ...startedAt !== void 0 ? { startedAt } : {},
2310
2476
  ...endedAt !== void 0 ? { finishedAt: endedAt } : {},
2311
2477
  ...typeof counters?.toolCalls === "number" ? { toolCallCount: counters.toolCalls } : {},
2478
+ // 收尾自报的平台积分 → dock 行「⚡ x.xx 积分」。
2479
+ ...typeof record.credits === "number" && Number.isFinite(record.credits) ? { credits: record.credits } : {},
2312
2480
  // note 在终态时是死因("orphaned: process gone…"),非终态时只是备注。
2313
2481
  ...record.note && isAgentRunTerminalStatus(record.status) ? { errorMessage: record.note } : {},
2314
2482
  inFlight,
@@ -2323,6 +2491,7 @@ function fingerprint(snapshot) {
2323
2491
  snapshot.toolCallCount ?? -1,
2324
2492
  snapshot.errorMessage ?? "",
2325
2493
  snapshot.finishedAt ?? 0,
2494
+ snapshot.credits ?? -1,
2326
2495
  snapshot.inFlight ? [snapshot.inFlight.kind, snapshot.inFlight.name, snapshot.inFlight.startedAt] : null
2327
2496
  ]);
2328
2497
  }
@@ -3327,6 +3496,20 @@ function summarizeAttachment(img) {
3327
3496
  ].join("\n");
3328
3497
  return themeText(text, "chrome", colorEnabled);
3329
3498
  }
3499
+ function formatComposerAttachmentLine(images) {
3500
+ if (images.length === 0) return null;
3501
+ const head = images.slice(0, 2).map((img) => `\u{1F4CE} ${img.filename} (${formatBytes(img.sizeBytes)})`);
3502
+ const extra = images.length - head.length;
3503
+ return extra > 0 ? `${head.join(", ")} +${extra}` : head.join(", ");
3504
+ }
3505
+ function popLastAttachedImage(images) {
3506
+ if (images.length === 0) return { handled: false, images };
3507
+ return {
3508
+ handled: true,
3509
+ images: images.slice(0, -1),
3510
+ removed: images[images.length - 1]
3511
+ };
3512
+ }
3330
3513
  function mergeAttachedImages(existing, incoming) {
3331
3514
  if (incoming.length === 0) return existing;
3332
3515
  if (existing.length === 0) return [...incoming];
@@ -3348,12 +3531,12 @@ function mergeAttachedImages(existing, incoming) {
3348
3531
  return out;
3349
3532
  }
3350
3533
  async function readImagePaths(paths, options = {}) {
3351
- const resolve2 = options.resolve ?? ((p) => p);
3534
+ const resolve4 = options.resolve ?? ((p) => p);
3352
3535
  const reportedFailures = /* @__PURE__ */ new Set();
3353
3536
  const images = [];
3354
3537
  const failures = [];
3355
3538
  for (const raw of paths) {
3356
- const absolute = resolve2(raw);
3539
+ const absolute = resolve4(raw);
3357
3540
  try {
3358
3541
  const img = await readImageAsDataUrl(absolute, {
3359
3542
  ...options.maxBytes !== void 0 ? { maxBytes: options.maxBytes } : {}
@@ -3393,7 +3576,7 @@ async function resolveAttachmentImageUrls({
3393
3576
  // packages/cli/tui/clipboardImage.ts
3394
3577
  import { createHash } from "node:crypto";
3395
3578
  import { mkdirSync } from "node:fs";
3396
- import { writeFile } from "node:fs/promises";
3579
+ import { readdir, stat as stat2, unlink, writeFile } from "node:fs/promises";
3397
3580
  import { tmpdir } from "node:os";
3398
3581
  import { join } from "node:path";
3399
3582
  var ClipboardImageError = class extends Error {
@@ -3405,6 +3588,34 @@ var ClipboardImageError = class extends Error {
3405
3588
  }
3406
3589
  };
3407
3590
  var DEFAULT_CLIPBOARD_TIMEOUT_MS = 3e4;
3591
+ var CLIPBOARD_TEMP_DIR_NAME = "nolo-clipboard";
3592
+ function getDefaultClipboardTempDir() {
3593
+ return join(tmpdir(), CLIPBOARD_TEMP_DIR_NAME);
3594
+ }
3595
+ var CLIPBOARD_SWEEP_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
3596
+ var CLIPBOARD_SWEEP_FILE_RE = /^clip-[0-9a-f]{8,}\.(png|jpg|jpeg|gif|webp)$/;
3597
+ async function sweepStaleClipboardFiles(tempDir, opts = {}) {
3598
+ try {
3599
+ const maxAgeMs = opts.maxAgeMs ?? CLIPBOARD_SWEEP_MAX_AGE_MS;
3600
+ const now = opts.now ?? Date.now();
3601
+ const entries = await readdir(tempDir);
3602
+ let removed = 0;
3603
+ for (const name of entries) {
3604
+ if (!CLIPBOARD_SWEEP_FILE_RE.test(name)) continue;
3605
+ try {
3606
+ const fullPath = join(tempDir, name);
3607
+ const stats = await stat2(fullPath);
3608
+ if (stats.mtimeMs >= now - maxAgeMs) continue;
3609
+ await unlink(fullPath);
3610
+ removed += 1;
3611
+ } catch {
3612
+ }
3613
+ }
3614
+ return removed;
3615
+ } catch {
3616
+ return 0;
3617
+ }
3618
+ }
3408
3619
  function isRemoteSession(env = process.env) {
3409
3620
  return Boolean(env.SSH_CONNECTION || env.SSH_TTY || env.SSH_CLIENT);
3410
3621
  }
@@ -3420,13 +3631,20 @@ function getClipboardImageCommand(deps = {}) {
3420
3631
  }
3421
3632
  if (platform === "darwin") {
3422
3633
  const pngpaste = which("pngpaste");
3423
- if (!pngpaste) {
3634
+ if (pngpaste) {
3635
+ return { command: [pngpaste, "-"], outputFormat: "raw" };
3636
+ }
3637
+ const osascript = which("osascript");
3638
+ if (!osascript) {
3424
3639
  throw new ClipboardImageError(
3425
3640
  "binary-missing",
3426
- "\u672A\u627E\u5230 pngpaste\uFF0C\u8BF7\u8FD0\u884C brew install pngpaste \u5B89\u88C5\uFF0C\u6216\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6/\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"
3641
+ "\u672A\u627E\u5230 pngpaste\uFF0C\u4E5F\u672A\u627E\u5230\u7CFB\u7EDF\u81EA\u5E26\u7684 osascript\uFF0C\u65E0\u6CD5\u8BFB\u53D6\u526A\u8D34\u677F\u56FE\u50CF\u3002\u8BF7\u8FD0\u884C brew install pngpaste \u5B89\u88C5\uFF0C\u6216\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6/\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"
3427
3642
  );
3428
3643
  }
3429
- return { command: [pngpaste, "-"] };
3644
+ return {
3645
+ command: [osascript, "-e", "the clipboard as \xABclass PNGf\xBB"],
3646
+ outputFormat: "osascript-hex"
3647
+ };
3430
3648
  }
3431
3649
  if (platform === "win32") {
3432
3650
  const ps = which("powershell.exe") || which("powershell") || which("pwsh.exe") || which("pwsh");
@@ -3443,7 +3661,8 @@ function getClipboardImageCommand(deps = {}) {
3443
3661
  "-NonInteractive",
3444
3662
  "-Command",
3445
3663
  "$img = Get-Clipboard -Format Image; if ($img) { $ms = New-Object System.IO.MemoryStream; $img.Save($ms, [System.Drawing.Imaging.ImageFormat]::Png); [Console]::OpenStandardOutput().Write($ms.ToArray(), 0, $ms.Length) }"
3446
- ]
3664
+ ],
3665
+ outputFormat: "raw"
3447
3666
  };
3448
3667
  }
3449
3668
  if (platform === "linux") {
@@ -3456,17 +3675,18 @@ function getClipboardImageCommand(deps = {}) {
3456
3675
  "\u672A\u627E\u5230 wl-paste\uFF0C\u8BF7\u5B89\u88C5 wl-clipboard (\u4F8B\u5982 sudo apt install wl-clipboard)\uFF0C\u6216\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6/\u8F93\u5165\u56FE\u7247\u8DEF\u5F84"
3457
3676
  );
3458
3677
  }
3459
- return { command: [wlPaste2, "--type", "image/png"] };
3678
+ return { command: [wlPaste2, "--type", "image/png"], outputFormat: "raw" };
3460
3679
  }
3461
3680
  const xclip = which("xclip");
3462
3681
  if (xclip) {
3463
3682
  return {
3464
- command: [xclip, "-selection", "clipboard", "-t", "image/png", "-o"]
3683
+ command: [xclip, "-selection", "clipboard", "-t", "image/png", "-o"],
3684
+ outputFormat: "raw"
3465
3685
  };
3466
3686
  }
3467
3687
  const wlPaste = which("wl-paste");
3468
3688
  if (wlPaste) {
3469
- return { command: [wlPaste, "--type", "image/png"] };
3689
+ return { command: [wlPaste, "--type", "image/png"], outputFormat: "raw" };
3470
3690
  }
3471
3691
  throw new ClipboardImageError(
3472
3692
  "binary-missing",
@@ -3478,6 +3698,21 @@ function getClipboardImageCommand(deps = {}) {
3478
3698
  `\u5F53\u524D\u64CD\u4F5C\u7CFB\u7EDF\u6682\u4E0D\u652F\u6301\u526A\u8D34\u677F\u56FE\u50CF\u8BFB\u53D6: ${platform}`
3479
3699
  );
3480
3700
  }
3701
+ 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";
3702
+ function decodeOsascriptPngHex(text) {
3703
+ const match = /PNGf\s*([0-9A-Fa-f]*)/.exec(text);
3704
+ if (!match) return { kind: "empty" };
3705
+ const hex = match[1] ?? "";
3706
+ const tail = text.slice(match.index + match[0].length).replace(/[\s»]*$/, "");
3707
+ if (tail.length > 0) return { kind: "invalid" };
3708
+ if (hex.length === 0) return { kind: "empty" };
3709
+ if (hex.length % 2 !== 0) return { kind: "invalid" };
3710
+ const bytes = new Uint8Array(hex.length / 2);
3711
+ for (let i = 0; i < bytes.length; i += 1) {
3712
+ bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
3713
+ }
3714
+ return { kind: "bytes", bytes };
3715
+ }
3481
3716
  async function readClipboardImage(deps = {}) {
3482
3717
  const env = deps.env ?? process.env;
3483
3718
  const platform = deps.platform ?? process.platform;
@@ -3485,8 +3720,8 @@ async function readClipboardImage(deps = {}) {
3485
3720
  const spawn = deps.spawn ?? Bun.spawn;
3486
3721
  const maxBytes = deps.maxBytes ?? DEFAULT_MAX_IMAGE_BYTES;
3487
3722
  const timeoutMs = deps.timeoutMs ?? DEFAULT_CLIPBOARD_TIMEOUT_MS;
3488
- const tempDir = deps.tempDir ?? join(tmpdir(), "nolo-clipboard");
3489
- const { command } = getClipboardImageCommand({
3723
+ const tempDir = deps.tempDir ?? getDefaultClipboardTempDir();
3724
+ const { command, outputFormat } = getClipboardImageCommand({
3490
3725
  env,
3491
3726
  platform,
3492
3727
  which
@@ -3520,6 +3755,12 @@ async function readClipboardImage(deps = {}) {
3520
3755
  timeoutPromise
3521
3756
  ]);
3522
3757
  if (exitCode !== 0 || stdoutArrayBuffer.byteLength === 0) {
3758
+ if (outputFormat === "osascript-hex") {
3759
+ throw new ClipboardImageError(
3760
+ "empty-clipboard",
3761
+ OSASCRIPT_EMPTY_CLIPBOARD_MESSAGE + (stderrText.trim() ? `: ${stderrText.trim()}` : "")
3762
+ );
3763
+ }
3523
3764
  throw new ClipboardImageError(
3524
3765
  "empty-clipboard",
3525
3766
  "\u526A\u8D34\u677F\u4E2D\u6CA1\u6709\u56FE\u50CF\u6570\u636E\uFF0C\u8BF7\u76F4\u63A5\u62D6\u5165\u56FE\u7247\u6587\u4EF6\u6216\u8F93\u5165\u56FE\u7247\u8DEF\u5F84" + (stderrText.trim() ? `: ${stderrText.trim()}` : "")
@@ -3538,6 +3779,23 @@ async function readClipboardImage(deps = {}) {
3538
3779
  `\u8BFB\u53D6\u526A\u8D34\u677F\u56FE\u50CF\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`
3539
3780
  );
3540
3781
  }
3782
+ if (outputFormat === "osascript-hex") {
3783
+ const text = Buffer.from(stdoutBuf).toString("utf8");
3784
+ const decoded = decodeOsascriptPngHex(text);
3785
+ if (decoded.kind === "empty") {
3786
+ throw new ClipboardImageError(
3787
+ "empty-clipboard",
3788
+ OSASCRIPT_EMPTY_CLIPBOARD_MESSAGE
3789
+ );
3790
+ }
3791
+ if (decoded.kind === "invalid") {
3792
+ throw new ClipboardImageError(
3793
+ "read-failed",
3794
+ "\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"
3795
+ );
3796
+ }
3797
+ stdoutBuf = decoded.bytes;
3798
+ }
3541
3799
  const compressResult = await compressImage(stdoutBuf, "image/png");
3542
3800
  const finalBuf = compressResult.buffer;
3543
3801
  const finalMime = compressResult.mime;
@@ -3598,7 +3856,7 @@ function parseStatusOutput(branch, status) {
3598
3856
  return { branch, modified, untracked };
3599
3857
  }
3600
3858
  function execFileUtf8(file, args, cwd) {
3601
- return new Promise((resolve2, reject) => {
3859
+ return new Promise((resolve4, reject) => {
3602
3860
  execFile(
3603
3861
  file,
3604
3862
  args,
@@ -3608,7 +3866,7 @@ function execFileUtf8(file, args, cwd) {
3608
3866
  reject(err);
3609
3867
  return;
3610
3868
  }
3611
- resolve2(typeof stdout === "string" ? stdout : String(stdout ?? ""));
3869
+ resolve4(typeof stdout === "string" ? stdout : String(stdout ?? ""));
3612
3870
  }
3613
3871
  );
3614
3872
  });
@@ -3650,6 +3908,12 @@ function renderComposerTokenChip(tokens, fallbackContextWindow, estimatedContext
3650
3908
  const pctText = pct > 0 && pct < 10 ? pct.toFixed(1) : Math.round(pct).toString();
3651
3909
  return `context: ${pctText}% (${formatTokenCount(used)}/${formatTokenCount(cw)})`;
3652
3910
  }
3911
+ function resolveStatusLineCredits(state) {
3912
+ const base = state.dialogCreditsBase;
3913
+ const session = state.sessionCredits;
3914
+ if (base === void 0 && session === void 0) return void 0;
3915
+ return (base ?? 0) + (session ?? 0);
3916
+ }
3653
3917
  function renderStatusLine(state, maxWidth) {
3654
3918
  const colorEnabled = resolveCliColorEnabled();
3655
3919
  const sep = themeText(" \xB7 ", "chrome", colorEnabled);
@@ -3666,18 +3930,19 @@ function renderStatusLine(state, maxWidth) {
3666
3930
  const untrackedText = untracked > 0 ? ` ${themeText(`?${untracked}`, "muted", colorEnabled)}` : "";
3667
3931
  parts.push(`${branchText}${modifiedText}${untrackedText}`);
3668
3932
  }
3669
- const credits = state.apiSource === "platform" ? state.dialogTotalCredits ?? state.turnTokens?.credits : void 0;
3670
- const creditsSuffix = credits !== void 0 && credits > 0 ? ` \xB7 \u26A1 ${credits.toFixed(2)} \u79EF\u5206` : "";
3933
+ const credits = resolveStatusLineCredits(state);
3671
3934
  const tokenSegment = themeText(
3672
3935
  renderComposerTokenChip(
3673
3936
  state.turnTokens,
3674
3937
  state.contextWindow,
3675
3938
  state.estimatedContextTokens
3676
- ) + creditsSuffix,
3939
+ ),
3677
3940
  "muted",
3678
3941
  colorEnabled
3679
3942
  );
3943
+ const creditsSegment = credits !== void 0 && credits > 0 ? themeText(formatCreditsChip(credits), "muted", colorEnabled) : "";
3680
3944
  parts.push(tokenSegment);
3945
+ if (creditsSegment) parts.push(creditsSegment);
3681
3946
  const runningTaskCount = getProcessRegistry().listBackground().filter((p) => p.status === "running").length;
3682
3947
  if (runningTaskCount > 0) {
3683
3948
  parts.push(themeText(`\u2699 ${runningTaskCount} running`, "info", colorEnabled));
@@ -3688,7 +3953,8 @@ function renderStatusLine(state, maxWidth) {
3688
3953
  let visibleParts = parts;
3689
3954
  if (maxWidth && maxWidth > 0) {
3690
3955
  const widthOf = (segments) => displayWidth(stripAnsi(segments.join(" \xB7 "))) + 2;
3691
- for (const optional of [tokenSegment, cwdSegment, agentSegment]) {
3956
+ for (const optional of [cwdSegment, agentSegment, tokenSegment, creditsSegment]) {
3957
+ if (!optional) continue;
3692
3958
  if (widthOf(visibleParts) <= maxWidth) break;
3693
3959
  visibleParts = visibleParts.filter((part) => part !== optional);
3694
3960
  }
@@ -3721,6 +3987,9 @@ function renderStatusLine(state, maxWidth) {
3721
3987
  colorEnabled
3722
3988
  ));
3723
3989
  }
3990
+ if (creditsSegment) {
3991
+ emergency.push(themeText(formatCreditsChip(credits ?? 0, { compact: true }), "muted", colorEnabled));
3992
+ }
3724
3993
  visibleParts = [emergency.join(" ")];
3725
3994
  }
3726
3995
  }
@@ -3851,6 +4120,20 @@ function renderTuiHelp(colorEnabled = resolveCliColorEnabled()) {
3851
4120
  return themeText(line, "muted", colorEnabled);
3852
4121
  }).join("\n");
3853
4122
  }
4123
+ function renderCreditsDebug(state, colorEnabled = resolveCliColorEnabled()) {
4124
+ const credits = resolveStatusLineCredits(state);
4125
+ const lines = [
4126
+ "[credits debug] \u79EF\u5206\u94FE\u8DEF\u771F\u503C",
4127
+ `\u2022 dialogCreditsBase : ${state.dialogCreditsBase?.toFixed(4) ?? "undefined\uFF08\u670D\u52A1\u7AEF\u57FA\u6570\u672A seed\u2014\u2014/pick \u540E\u9996\u6B21\u8BFB\u53D6\u5931\u8D25\u6216\u670D\u52A1\u7AEF\u672A\u5199 totalCost\uFF09"}`,
4128
+ `\u2022 sessionCredits : ${state.sessionCredits?.toFixed(4) ?? "undefined\uFF08\u672C\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u5E73\u53F0\u8BA1\u8D39\u8F6E\uFF09"}`,
4129
+ `\u2022 \u72B6\u6001\u884C\u663E\u793A : ${credits !== void 0 && credits > 0 ? formatCreditsChip(credits) : "\uFF08\u65E0 \u26A1\u2014\u2014\u4E24\u6BB5\u5408\u8BA1\u4E3A 0/undefined\uFF09"}`,
4130
+ `\u2022 turnTokens.credits: ${state.turnTokens?.credits?.toFixed(4) ?? "undefined"}\uFF08\u672C\u8F6E\u6700\u540E\u4E00\u6B21\u8C03\u7528\u7684\u6298\u7B97\u503C\uFF0C\u4EC5\u4F9B\u53C2\u8003\uFF0C\u4E0D\u8FDB\u72B6\u6001\u884C\uFF09`,
4131
+ `\u2022 contextWindow : ${state.contextWindow ?? "unknown"}`,
4132
+ '\u2022 \u5224\u5B9A\u53E3\u5F84\uFF1A\u53EA\u6709 billing_unit === "credits" \u7684\u5E73\u53F0\u8BA1\u8D39\u5E27\u624D\u7D2F\u8BA1\uFF1B\u81EA\u6709 API / \u8BA2\u9605\u5236\u6052\u4E3A undefined\u3002',
4133
+ "\u2022 \u82E5\u521A\u8DD1\u5B8C\u5E73\u53F0\u8BA1\u8D39\u8F6E\u4ECD\u65E0 \u26A1\uFF1A\u68C0\u67E5 runAgentTurn \u8FD4\u56DE\u662F\u5426\u5E26 turnCredits\uFF08foldLocalResultForTui \u767D\u540D\u5355\uFF09\u3002"
4134
+ ];
4135
+ return lines.map((line) => colorEnabled ? themeText(line, "chrome") : line).join("\n");
4136
+ }
3854
4137
  function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
3855
4138
  const docs = state.attachedDocs.length ? state.attachedDocs.join(", ") : "none";
3856
4139
  const skills = state.attachedSkills.length ? state.attachedSkills.join(", ") : "none";
@@ -3862,7 +4145,6 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
3862
4145
  t("contextFieldSkills"),
3863
4146
  t("contextFieldProfile"),
3864
4147
  t("contextFieldRuntime"),
3865
- t("contextFieldTools"),
3866
4148
  t("contextFieldServer")
3867
4149
  ];
3868
4150
  let maxW = 0;
@@ -3892,8 +4174,7 @@ function renderContextPanel(state, colorEnabled = resolveCliColorEnabled()) {
3892
4174
  field(labels[4], skills),
3893
4175
  field(labels[5], state.profileName),
3894
4176
  field(labels[6], state.runtimeMode),
3895
- field(labels[7], state.toolDisplay),
3896
- field(labels[8], state.serverUrl),
4177
+ field(labels[7], state.serverUrl),
3897
4178
  "",
3898
4179
  themeText(t("contextNext"), "chrome", colorEnabled),
3899
4180
  next("/agents ", ` ${t("contextNextAgents")}`),
@@ -3923,6 +4204,9 @@ function renderKnownAgents(colorEnabled = resolveCliColorEnabled()) {
3923
4204
  }
3924
4205
 
3925
4206
  // packages/cli/tui/sessionInput.ts
4207
+ import { readdirSync, statSync } from "node:fs";
4208
+ import { homedir as homedir2 } from "node:os";
4209
+ import { resolve as resolve2 } from "node:path";
3926
4210
  var PASTE_TOKEN_PREFIX = "\0PASTE\0";
3927
4211
  function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
3928
4212
  const seq = sequence ?? "";
@@ -4027,6 +4311,16 @@ function applyTuiInputKey(buffer, sequence, key = {}, cursorPos, options) {
4027
4311
  return { buffer, cursorPos: curPos };
4028
4312
  }
4029
4313
  if (seq === " " || key.name === "tab") {
4314
+ if (options?.cwd) {
4315
+ const pathCandidates = completeSlashCommand(buffer, options.cwd);
4316
+ if (pathCandidates.length === 1) {
4317
+ const [only] = pathCandidates;
4318
+ return { buffer: only, cursorPos: only.length };
4319
+ }
4320
+ if (pathCandidates.length > 1) {
4321
+ return { buffer, cursorPos: curPos };
4322
+ }
4323
+ }
4030
4324
  const completed = completeSlashPrefix(buffer) ?? buffer;
4031
4325
  return { buffer: completed, cursorPos: completed.length };
4032
4326
  }
@@ -4079,9 +4373,9 @@ function deleteBufferRange(buffer, start, end, pasteStore) {
4079
4373
  function releasePastesInRange(buffer, start, end, pasteStore) {
4080
4374
  if (!pasteStore || start >= end) return;
4081
4375
  const slice = buffer.slice(start, end);
4082
- const re = /\[paste #(\d+) · (\d+) lines\]/g;
4376
+ COLLAPSED_PASTE_PLACEHOLDER_RE.lastIndex = 0;
4083
4377
  let match;
4084
- while ((match = re.exec(slice)) !== null) {
4378
+ while ((match = COLLAPSED_PASTE_PLACEHOLDER_RE.exec(slice)) !== null) {
4085
4379
  releaseCollapsedPaste(pasteStore, Number(match[1]));
4086
4380
  }
4087
4381
  }
@@ -4135,12 +4429,11 @@ var SLASH_COMMANDS = [
4135
4429
  "/clear",
4136
4430
  "/compact",
4137
4431
  "/theme",
4138
- "/density",
4139
4432
  "/context",
4140
4433
  "/ctx",
4434
+ "/credits",
4435
+ "/cd",
4141
4436
  "/runtime",
4142
- "/tools",
4143
- "/thinking",
4144
4437
  "/auto",
4145
4438
  "/switch",
4146
4439
  "/agent",
@@ -4178,12 +4471,49 @@ function completeSlashPrefix(buffer) {
4178
4471
  }
4179
4472
  return prefix.length > buffer.length ? prefix : null;
4180
4473
  }
4181
- function completeSlashCommand(buffer) {
4474
+ function completeSlashCommand(buffer, cwd) {
4182
4475
  if (!buffer.startsWith("/")) return [];
4183
4476
  const trimmed = buffer.trim();
4477
+ if (cwd && buffer.startsWith("/cd ")) {
4478
+ const prefix = buffer.slice("/cd ".length);
4479
+ return completeCdPathCandidates(prefix, cwd);
4480
+ }
4184
4481
  if (trimmed.includes(" ")) return [];
4185
4482
  return SLASH_COMMANDS.filter((cmd) => cmd.startsWith(trimmed) && cmd !== trimmed);
4186
4483
  }
4484
+ function completeCdPathCandidates(prefix, cwd) {
4485
+ const lastSlash = prefix.lastIndexOf("/");
4486
+ const head = lastSlash >= 0 ? prefix.slice(0, lastSlash + 1) : "";
4487
+ const baseName = lastSlash >= 0 ? prefix.slice(lastSlash + 1) : prefix;
4488
+ let dirPath;
4489
+ if (head === "~" || head === "~/" || head === "~\\") {
4490
+ dirPath = homedir2();
4491
+ } else if (head.startsWith("~/") || head.startsWith("~\\")) {
4492
+ dirPath = resolve2(homedir2(), head.slice(2));
4493
+ } else {
4494
+ dirPath = head.startsWith("/") ? head : resolve2(cwd, head);
4495
+ }
4496
+ let entries;
4497
+ try {
4498
+ entries = readdirSync(dirPath, { withFileTypes: true });
4499
+ } catch {
4500
+ return [];
4501
+ }
4502
+ const hiddenRequested = baseName.startsWith(".");
4503
+ const names = entries.filter((e) => {
4504
+ if (e.isDirectory()) return true;
4505
+ if (!e.isSymbolicLink()) return false;
4506
+ try {
4507
+ return statSync(resolve2(dirPath, e.name)).isDirectory();
4508
+ } catch {
4509
+ return false;
4510
+ }
4511
+ }).map((e) => e.name).filter((name) => {
4512
+ if (!name.startsWith(baseName)) return false;
4513
+ return hiddenRequested || !name.startsWith(".");
4514
+ }).sort();
4515
+ return names.map((name) => `/cd ${head}${name}/`);
4516
+ }
4187
4517
  function isLikelySlashCommand(input) {
4188
4518
  const trimmed = input.trim();
4189
4519
  if (!trimmed.startsWith("/")) return false;
@@ -4202,6 +4532,11 @@ function stripImageTokens(input, hints) {
4202
4532
  return compactWhitespace(out);
4203
4533
  }
4204
4534
 
4535
+ // packages/cli/tui/sessionDispatch.ts
4536
+ import { statSync as statSync2 } from "node:fs";
4537
+ import { homedir as homedir3 } from "node:os";
4538
+ import { resolve as resolve3 } from "node:path";
4539
+
4205
4540
  // packages/cli/tui/agentPicker.ts
4206
4541
  function toAgentPickerItems(entries) {
4207
4542
  return entries.map((entry) => ({
@@ -4266,7 +4601,6 @@ async function runAgentPicker(args) {
4266
4601
  name: selected.name,
4267
4602
  key: selected.key,
4268
4603
  model: selected.model,
4269
- apiSource: selected.apiSource ?? (selected.kind === "platform" ? "platform" : void 0),
4270
4604
  entries
4271
4605
  };
4272
4606
  }
@@ -4278,10 +4612,7 @@ function resolveAgentSwitchTarget(rawTarget, catalogEntries = []) {
4278
4612
  return {
4279
4613
  name: aliasEntry?.name ?? asTrimmedLowercaseString(target),
4280
4614
  key: resolvedKey,
4281
- ...aliasEntry?.model ? { model: aliasEntry.model } : {},
4282
- ...aliasEntry ? {
4283
- ...aliasEntry.apiSource ? { apiSource: aliasEntry.apiSource } : aliasEntry.kind === "platform" ? { apiSource: "platform" } : {}
4284
- } : {}
4615
+ ...aliasEntry?.model ? { model: aliasEntry.model } : {}
4285
4616
  };
4286
4617
  }
4287
4618
  return findAgentCatalogEntry(catalogEntries, rawTarget);
@@ -4363,6 +4694,10 @@ function estimateDefaultCliContextTokens(opts = {}) {
4363
4694
 
4364
4695
  // packages/cli/tui/sessionDispatch.ts
4365
4696
  var DEFAULT_TUI_SERVER_URL = DEFAULT_NOLO_SERVER_URL;
4697
+ function resolveThinkingDisplayMode(env = process.env) {
4698
+ const raw = (env.NOLO_CLI_THINKING ?? "").trim().toLowerCase();
4699
+ return raw === "hide" || raw === "false" || raw === "0" ? "hide" : "show";
4700
+ }
4366
4701
  function createInitialTuiState(env = process.env) {
4367
4702
  const agentKey = asOptionalTrimmedString(env.NOLO_AGENT) ?? DEFAULT_TUI_AGENT_KEY;
4368
4703
  const agentName = asOptionalTrimmedString(env.NOLO_AGENT_NAME) ?? "nolo";
@@ -4395,16 +4730,14 @@ function createInitialTuiState(env = process.env) {
4395
4730
  modeLabel: asOptionalTrimmedString(env.NOLO_CLI_STATUS_MODE) ?? runtimeMode,
4396
4731
  userLanguage: asOptionalTrimmedString(env.NOLO_LANG),
4397
4732
  gitStatus: void 0,
4398
- toolDisplay: normalizeToolDisplayMode(env.NOLO_CLI_TOOLS ?? env.NOLO_TOOLS, "normal"),
4399
- thinkingDisplay: "show",
4733
+ thinkingDisplay: resolveThinkingDisplayMode(env),
4400
4734
  contextWindow: resolveAgentContextWindow({ agentKey, agentName }),
4401
4735
  estimatedContextTokens: estimateDefaultCliContextTokens({
4402
4736
  cwd,
4403
4737
  agentKey,
4404
4738
  userLanguage: asOptionalTrimmedString(env.NOLO_LANG),
4405
4739
  ...resolveAgentModelIdentity({ agentKey, agentName })
4406
- }),
4407
- apiSource: PLATFORM_AGENTS.some((entry) => entry.key === agentKey) ? "platform" : void 0
4740
+ })
4408
4741
  };
4409
4742
  }
4410
4743
  function applyAgentSwitch(state, target) {
@@ -4425,8 +4758,7 @@ function applyAgentSwitch(state, target) {
4425
4758
  agentName: target.name,
4426
4759
  model: target.model,
4427
4760
  userLanguage: state.userLanguage
4428
- }),
4429
- apiSource: target.apiSource
4761
+ })
4430
4762
  },
4431
4763
  output: `Switched to ${target.name}. ${state.dialogId ? `Dialog kept: ${state.dialogId}` : "Dialog kept: new"}`
4432
4764
  };
@@ -4520,31 +4852,58 @@ function handleTuiInput(input, state) {
4520
4852
  };
4521
4853
  }
4522
4854
  }
4523
- case "/density": {
4524
- const parts = argText.split(/\s+/);
4525
- const sub = parts[0]?.trim();
4526
- if (!sub) {
4855
+ case "/cd": {
4856
+ if (!argText) {
4527
4857
  return {
4528
4858
  nextState: state,
4529
- output: t("densityCurrent", getActiveDensity())
4859
+ output: `${t("cdCurrent", state.cwd)}
4860
+ ${t("cdUsage")}`
4530
4861
  };
4531
4862
  }
4532
- if (sub === "cozy" || sub === "spacious") {
4533
- setActiveDensity(sub);
4534
- return {
4535
- nextState: state,
4536
- output: t("densitySwitched", sub)
4537
- };
4538
- } else {
4863
+ if (argText === "-") {
4864
+ if (!state.prevCwd) {
4865
+ return { nextState: state, output: t("cdNoPrevious") };
4866
+ }
4867
+ const previous = state.prevCwd;
4868
+ const switchMessage2 = t("cdSwitchedMessage", state.cwd, previous);
4539
4869
  return {
4540
- nextState: state,
4541
- output: t("densityUnknown", sub)
4870
+ nextState: { ...state, cwd: previous, prevCwd: void 0, pendingCwdNotice: switchMessage2 },
4871
+ output: t("cdSwitched", previous),
4872
+ action: { type: "cwd-refresh", switchMessage: switchMessage2 }
4542
4873
  };
4543
4874
  }
4875
+ const expanded = argText === "~" ? homedir3() : argText.startsWith("~/") || argText.startsWith("~\\") ? resolve3(homedir3(), argText.slice(2)) : argText;
4876
+ const target = resolve3(state.cwd, expanded);
4877
+ try {
4878
+ if (!statSync2(target).isDirectory()) {
4879
+ return { nextState: state, output: t("cdNotDir", target) };
4880
+ }
4881
+ } catch {
4882
+ return { nextState: state, output: t("cdNotFound", target) };
4883
+ }
4884
+ const normalized = target === "/" ? "/" : target.replace(/\/+$/, "");
4885
+ if (normalized === state.cwd) {
4886
+ return { nextState: state, output: t("cdCurrent", state.cwd) };
4887
+ }
4888
+ const switchMessage = t("cdSwitchedMessage", state.cwd, normalized);
4889
+ return {
4890
+ nextState: { ...state, cwd: normalized, prevCwd: state.cwd, pendingCwdNotice: switchMessage },
4891
+ output: t("cdSwitched", normalized),
4892
+ action: { type: "cwd-refresh", switchMessage }
4893
+ };
4544
4894
  }
4895
+ case "/density":
4896
+ case "/tools":
4897
+ case "/thinking":
4898
+ return {
4899
+ nextState: state,
4900
+ output: t("displayFixedHint")
4901
+ };
4545
4902
  case "/context":
4546
4903
  case "/ctx":
4547
4904
  return { nextState: state, output: renderContextPanel(state) };
4905
+ case "/credits":
4906
+ return { nextState: state, output: renderCreditsDebug(state) };
4548
4907
  case "/runtime": {
4549
4908
  if (argText !== "auto" && argText !== "local" && argText !== "server") {
4550
4909
  return {
@@ -4557,45 +4916,6 @@ function handleTuiInput(input, state) {
4557
4916
  output: t("runtimeSet", argText)
4558
4917
  };
4559
4918
  }
4560
- case "/tools": {
4561
- if (!argText) {
4562
- return {
4563
- nextState: state,
4564
- output: t("toolsCurrent", state.toolDisplay)
4565
- };
4566
- }
4567
- const normalizedArg = asTrimmedLowercaseString(argText);
4568
- if (!["normal", "pro", "hide", "compact", "verbose", "on", "off"].includes(normalizedArg)) {
4569
- return {
4570
- nextState: state,
4571
- output: t("toolsUsage")
4572
- };
4573
- }
4574
- const nextMode = normalizeToolDisplayMode(normalizedArg, state.toolDisplay);
4575
- return {
4576
- nextState: { ...state, toolDisplay: nextMode },
4577
- output: t("toolsSet", nextMode)
4578
- };
4579
- }
4580
- case "/thinking": {
4581
- const normalizedArg = asTrimmedLowercaseString(argText);
4582
- if (!normalizedArg) {
4583
- return {
4584
- nextState: state,
4585
- output: t("thinkingCurrent", state.thinkingDisplay)
4586
- };
4587
- }
4588
- if (normalizedArg !== "show" && normalizedArg !== "hide") {
4589
- return {
4590
- nextState: state,
4591
- output: t("thinkingUsage")
4592
- };
4593
- }
4594
- return {
4595
- nextState: { ...state, thinkingDisplay: normalizedArg },
4596
- output: t("thinkingSet", normalizedArg)
4597
- };
4598
- }
4599
4919
  case "/auto": {
4600
4920
  const normalizedArg = asTrimmedLowercaseString(argText);
4601
4921
  if (!normalizedArg) {
@@ -5004,7 +5324,7 @@ async function probeTerminalBackground(args = {}) {
5004
5324
  if (typeof stdin.setRawMode !== "function") return null;
5005
5325
  if (!isTerminal(stdin)) return null;
5006
5326
  const wasRaw = Boolean(stdin.isRaw);
5007
- return await new Promise((resolve2) => {
5327
+ return await new Promise((resolve4) => {
5008
5328
  let buffer = "";
5009
5329
  let settled = false;
5010
5330
  let timer;
@@ -5017,7 +5337,7 @@ async function probeTerminalBackground(args = {}) {
5017
5337
  if (!wasRaw) stdin.setRawMode?.(false);
5018
5338
  } catch {
5019
5339
  }
5020
- resolve2(result);
5340
+ resolve4(result);
5021
5341
  };
5022
5342
  const onData = (chunk) => {
5023
5343
  buffer += chunk.toString("latin1");
@@ -5080,9 +5400,106 @@ function detectSystemBrightness(env = process.env, options = {}) {
5080
5400
  return null;
5081
5401
  }
5082
5402
 
5083
- // packages/cli/tui/tuiTurnRunner.ts
5084
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
5403
+ // packages/cli/tui/turnInjectionInbox.ts
5404
+ function appendStreamSafeNotice(history, text, deps) {
5405
+ const wasStreamingAssistant = history.currentRole === "assistant";
5406
+ if (wasStreamingAssistant && !history.currentContent) {
5407
+ history.currentRole = null;
5408
+ history.currentBlocks = [];
5409
+ }
5410
+ deps.appendLocalTurn(history, "", text);
5411
+ if (wasStreamingAssistant) deps.startTurn(history, "assistant");
5412
+ }
5413
+ function createTurnInjectionInbox() {
5414
+ let pending = [];
5415
+ let closed = false;
5416
+ return {
5417
+ push(entry) {
5418
+ if (closed) return false;
5419
+ if (!entry || typeof entry.text !== "string" || !entry.text.trim()) return false;
5420
+ pending.push(entry);
5421
+ return true;
5422
+ },
5423
+ drain() {
5424
+ if (pending.length === 0) return [];
5425
+ const taken = pending;
5426
+ pending = [];
5427
+ return taken.map((entry) => entry.text);
5428
+ },
5429
+ close() {
5430
+ closed = true;
5431
+ const remaining = pending;
5432
+ pending = [];
5433
+ return remaining;
5434
+ },
5435
+ get size() {
5436
+ return pending.length;
5437
+ },
5438
+ get closed() {
5439
+ return closed;
5440
+ }
5441
+ };
5442
+ }
5443
+
5444
+ // packages/cli/client/turnBillingAudit.ts
5445
+ import { appendFileSync, mkdirSync as mkdirSync2 } from "node:fs";
5446
+ import { homedir as homedir4 } from "node:os";
5085
5447
  import { join as join3 } from "node:path";
5448
+ function projectUsageForAudit(usage) {
5449
+ if (!usage || typeof usage !== "object") return null;
5450
+ const normalized = normalizeUsage(usage);
5451
+ const projected = {
5452
+ input_tokens: normalized.input_tokens,
5453
+ output_tokens: normalized.output_tokens,
5454
+ cache_read_input_tokens: normalized.cache_read_input_tokens,
5455
+ cache_creation_input_tokens: normalized.cache_creation_input_tokens
5456
+ };
5457
+ if (typeof usage.cost === "number" && Number.isFinite(usage.cost)) {
5458
+ projected.cost = usage.cost;
5459
+ }
5460
+ if (typeof usage.billing_unit === "string" && usage.billing_unit.trim()) {
5461
+ projected.billing_unit = usage.billing_unit;
5462
+ }
5463
+ if (typeof usage.provider_call_id === "string" && usage.provider_call_id.trim()) {
5464
+ projected.provider_call_id = usage.provider_call_id.trim();
5465
+ }
5466
+ return projected;
5467
+ }
5468
+ function resolveTurnBillingAuditLogPath(env = process.env) {
5469
+ const noloHome = env.NOLO_HOME?.trim() || join3(homedir4(), ".nolo");
5470
+ return join3(noloHome, "logs", "turn-billing.jsonl");
5471
+ }
5472
+ function appendTurnBillingAudit(input) {
5473
+ try {
5474
+ if (process.env.NOLO_TURN_BILLING_AUDIT === "0") return;
5475
+ const calls = (input.usageRecords ?? []).map((record) => {
5476
+ const usage = projectUsageForAudit(record?.usage);
5477
+ if (!usage) return null;
5478
+ return {
5479
+ ...record?.callId ? { callId: record.callId } : {},
5480
+ ...record?.model ? { model: record.model } : {},
5481
+ ...record?.provider ? { provider: record.provider } : {},
5482
+ ...usage
5483
+ };
5484
+ }).filter((call) => call !== null);
5485
+ if (calls.length === 0 && (input.turnCredits === void 0 || input.turnCredits === null)) {
5486
+ return;
5487
+ }
5488
+ const row = {
5489
+ ts: Date.now(),
5490
+ ...input.dialogId ? { dialogId: input.dialogId } : {},
5491
+ ...input.agentKey ? { agentKey: input.agentKey } : {},
5492
+ ...input.turnCredits !== void 0 && input.turnCredits !== null ? { turnCredits: input.turnCredits } : {},
5493
+ ...input.aborted ? { aborted: true } : {},
5494
+ calls
5495
+ };
5496
+ const logPath = resolveTurnBillingAuditLogPath();
5497
+ mkdirSync2(join3(logPath, ".."), { recursive: true });
5498
+ appendFileSync(logPath, `${JSON.stringify(row)}
5499
+ `);
5500
+ } catch {
5501
+ }
5502
+ }
5086
5503
 
5087
5504
  // packages/ai/tools/askChoiceState.ts
5088
5505
  function normalizeAskChoiceArgs(args) {
@@ -5861,24 +6278,6 @@ function emitTerminalBell(output) {
5861
6278
  }
5862
6279
 
5863
6280
  // packages/cli/tui/tuiTurnRunner.ts
5864
- var AGENTS_MD_MAX_BYTES = 8192;
5865
- function readAgentsMdLayer(cwd) {
5866
- for (const name of ["AGENTS.md", "CLAUDE.md"]) {
5867
- const filePath = join3(cwd, name);
5868
- if (existsSync3(filePath)) {
5869
- try {
5870
- let content = readFileSync3(filePath, "utf8").trim();
5871
- if (!content) continue;
5872
- if (Buffer.byteLength(content, "utf8") > AGENTS_MD_MAX_BYTES) {
5873
- content = Buffer.from(content, "utf8").subarray(0, AGENTS_MD_MAX_BYTES).toString("utf8") + "\n\n<!-- AGENTS.md truncated -->";
5874
- }
5875
- return buildAgentsMdLayer(content, name);
5876
- } catch {
5877
- }
5878
- }
5879
- }
5880
- return null;
5881
- }
5882
6281
  async function runAgentChat(scriptDir, state, message, env, output, agentRunner = runAgentTurn, options = {}) {
5883
6282
  const effectiveAgentKey = state.agentKey;
5884
6283
  const effectiveAgentName = state.agentName;
@@ -5923,12 +6322,16 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5923
6322
  }
5924
6323
  }
5925
6324
  const layers = [
5926
- readAgentsMdLayer(state.cwd),
6325
+ readAgentsMdLayerFromDisk(state.cwd),
5927
6326
  // Skill discovery: scan conventional skill dirs for SKILL.md and inject an
5928
6327
  // index layer so the model knows what skills exist and can readFile them
5929
6328
  // on-demand. Mirrors agentRunCommand.ts and desktopAgentRuntimeTurnService.
5930
6329
  buildSkillDiscoveryContextLayer(state.cwd)
5931
6330
  ];
6331
+ const responseGuidelinesBlock = {
6332
+ content: buildResponseGuidelines(true),
6333
+ cacheScope: "session"
6334
+ };
5932
6335
  let memoryPromptBlock = state.cachedMemoryOverlay;
5933
6336
  if (memoryPromptBlock === void 0) {
5934
6337
  memoryPromptBlock = await resolveCliMemory({
@@ -5941,15 +6344,19 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5941
6344
  }
5942
6345
  const memoryOverlayLayer = buildMemoryOverlayLayer({ promptBlock: memoryPromptBlock });
5943
6346
  const memoryUseGuidanceLayer = buildMemoryUseGuidanceLayer({ promptBlock: memoryPromptBlock });
6347
+ const cwdNoticeBlocks = state.pendingCwdNotice ? [{ content: state.pendingCwdNotice, cacheScope: "turn" }] : [];
5944
6348
  const contextBlockScopes = partitionScopedBlocks([
5945
6349
  ...renderTurnContextBlocksWithScope(layers),
6350
+ responseGuidelinesBlock,
5946
6351
  ...renderTurnContextBlocksWithScope([memoryUseGuidanceLayer]),
5947
6352
  // Attached skill bodies are already self-contained sections built by
5948
6353
  // buildSkillContextBlocks; they stay turn-scope because the user can
5949
6354
  // attach/detach skills between turns.
5950
6355
  ...(skillContextBlocks ?? []).map((content) => content.trim()).filter((content) => content.length > 0).map((content) => ({ content, cacheScope: "turn" })),
5951
- ...renderTurnContextBlocksWithScope([memoryOverlayLayer])
6356
+ ...renderTurnContextBlocksWithScope([memoryOverlayLayer]),
6357
+ ...cwdNoticeBlocks
5952
6358
  ]);
6359
+ const spawnedRunsMissingParent = [];
5953
6360
  const result = await agentRunner({
5954
6361
  agentName: effectiveAgentName,
5955
6362
  agentKey: effectiveAgentKey,
@@ -5967,10 +6374,12 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5967
6374
  scriptDir,
5968
6375
  env: {
5969
6376
  ...env,
5970
- NOLO_LANG: state.userLanguage,
5971
- NOLO_CLI_TOOLS: state.toolDisplay
6377
+ NOLO_LANG: state.userLanguage
6378
+ },
6379
+ output: {
6380
+ ...output,
6381
+ tuiTrees: output.tuiTrees === true && typeof output.writeToolBlock === "function"
5972
6382
  },
5973
- output,
5974
6383
  showThinking: state.thinkingDisplay === "show",
5975
6384
  ...options.imageUrls && options.imageUrls.length > 0 ? { imageUrls: options.imageUrls } : {},
5976
6385
  ...options.actionGateHandler ? { actionGateHandler: options.actionGateHandler } : {},
@@ -5994,15 +6403,28 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
5994
6403
  // run-completion watcher can attribute terminal-state wakes to this
5995
6404
  // conversation.
5996
6405
  ...state.dialogId ? { parentDialogId: state.dialogId } : {},
6406
+ // 首轮无 dialogId 时的兜底:收集失归属的 spawn,turn 结束回填。
6407
+ onBackgroundRunSpawned: (runId) => {
6408
+ spawnedRunsMissingParent.push(runId);
6409
+ },
5997
6410
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {}
5998
6411
  }),
5999
6412
  ...options.activityReporter ? { activityReporter: options.activityReporter } : {},
6000
6413
  // 转发 dock 订阅:runAgentTurn 的输出层靠它判断「有面板」从而抑制
6001
6414
  // transcript 的进展卡片;dock 本身也靠它接收模型轮询带回来的快照。
6002
6415
  ...options.onAgentRunStatus ? { onAgentRunStatus: options.onAgentRunStatus } : {},
6416
+ ...options.drainInjections ? { drainInjections: options.drainInjections } : {},
6003
6417
  ...skillAllowedTools !== void 0 ? { allowedToolNames: skillAllowedTools } : {},
6004
6418
  ...contextBlockScopes.length > 0 ? { contextBlockScopes } : {}
6005
6419
  });
6420
+ if (result.dialogId && spawnedRunsMissingParent.length > 0) {
6421
+ for (const runId of spawnedRunsMissingParent) {
6422
+ try {
6423
+ backfillRunRecordParentDialog(runId, result.dialogId, { env });
6424
+ } catch {
6425
+ }
6426
+ }
6427
+ }
6006
6428
  return {
6007
6429
  ...result,
6008
6430
  contextWindow: resolveAgentContextWindow({
@@ -6012,13 +6434,47 @@ async function runAgentChat(scriptDir, state, message, env, output, agentRunner
6012
6434
  cachedMemoryOverlay: memoryPromptBlock
6013
6435
  };
6014
6436
  }
6015
- function waitForActionGate(rl, input, output, gate, spawnRunner) {
6437
+ var ESC = "\x1B";
6438
+ var CTRL_C = "";
6439
+ function isApprovedConfirmationInput(answer) {
6440
+ const normalized = answer.trim().toLowerCase();
6441
+ return normalized === "" || normalized === "y" || normalized === "yes" || normalized === "\u786E\u8BA4";
6442
+ }
6443
+ function buildGateConfirmedResult(gate) {
6444
+ return {
6445
+ content: `action gate completed: ${gate.title}`,
6446
+ metadata: { actionGateResult: { gateId: gate.id, status: "completed", output: "confirmed" } }
6447
+ };
6448
+ }
6449
+ function buildGateCancelledResult(gate, reason) {
6450
+ return {
6451
+ content: `action gate cancelled: ${gate.title}`,
6452
+ metadata: {
6453
+ exitCode: 130,
6454
+ actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6455
+ }
6456
+ };
6457
+ }
6458
+ function buildGateFailedResult(gate, message) {
6459
+ return {
6460
+ content: `action gate failed: ${gate.title}`,
6461
+ metadata: {
6462
+ exitCode: 1,
6463
+ actionGateResult: { gateId: gate.id, status: "failed", output: message }
6464
+ }
6465
+ };
6466
+ }
6467
+ function deriveGateMeta(gate) {
6016
6468
  const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
6017
6469
  const isConfirmation = gate.kind === "confirm";
6018
6470
  const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
6019
6471
  const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
6020
6472
  const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
6021
6473
  const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
6474
+ return { commandPayload, isConfirmation, displayCommand, isInteractiveHandoff, title, body };
6475
+ }
6476
+ function writeGatePrompt(output, meta) {
6477
+ const { title, body, displayCommand, isConfirmation } = meta;
6022
6478
  output.write(`
6023
6479
  [nolo] ${t("actionGateNeeded")}
6024
6480
  `);
@@ -6028,44 +6484,32 @@ function waitForActionGate(rl, input, output, gate, spawnRunner) {
6028
6484
  `);
6029
6485
  output.write(` ${displayCommand}
6030
6486
  `);
6031
- output.write(`[nolo] ${t("actionGateEnterHint")}
6487
+ output.write(`[nolo] ${isConfirmation ? t("actionGateConfirmHint") : t("actionGateEnterHint")}
6032
6488
  `);
6033
- return new Promise((resolve2) => {
6489
+ }
6490
+ function waitForActionGate(rl, input, output, gate, spawnRunner) {
6491
+ const meta = deriveGateMeta(gate);
6492
+ const { commandPayload, isConfirmation, displayCommand } = meta;
6493
+ writeGatePrompt(output, meta);
6494
+ return new Promise((resolve4) => {
6034
6495
  let settled = false;
6035
6496
  const finish = (result) => {
6036
6497
  if (settled) return;
6037
6498
  settled = true;
6038
6499
  rl.off("close", onClose);
6039
6500
  rl.off("SIGINT", onSigint);
6040
- resolve2(result);
6501
+ resolve4(result);
6041
6502
  };
6042
- const cancelResult = (reason) => ({
6043
- content: `action gate cancelled: ${gate.title}`,
6044
- metadata: {
6045
- exitCode: 130,
6046
- actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6047
- }
6048
- });
6049
- const failResult = (message) => ({
6050
- content: `action gate failed: ${gate.title}`,
6051
- metadata: {
6052
- exitCode: 1,
6053
- actionGateResult: { gateId: gate.id, status: "failed", output: message }
6054
- }
6055
- });
6503
+ const cancelResult = (reason) => buildGateCancelledResult(gate, reason);
6504
+ const failResult = (message) => buildGateFailedResult(gate, message);
6056
6505
  const onClose = () => finish(cancelResult("readline closed"));
6057
6506
  const onSigint = () => finish(cancelResult("interrupted"));
6058
6507
  rl.once("close", onClose);
6059
6508
  rl.once("SIGINT", onSigint);
6060
- rl.question("", async () => {
6509
+ rl.question("", async (answer) => {
6061
6510
  if (settled) return;
6062
6511
  if (isConfirmation) {
6063
- finish({
6064
- content: `action gate completed: ${gate.title}`,
6065
- metadata: {
6066
- actionGateResult: { gateId: gate.id, status: "completed", output: "confirmed" }
6067
- }
6068
- });
6512
+ finish(isApprovedConfirmationInput(answer) ? buildGateConfirmedResult(gate) : cancelResult("confirmation declined"));
6069
6513
  return;
6070
6514
  }
6071
6515
  if (!commandPayload) {
@@ -6113,48 +6557,23 @@ function isInteractiveInput(input) {
6113
6557
  return Boolean(candidate.isTTY) && typeof candidate.setRawMode === "function";
6114
6558
  }
6115
6559
  function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6116
- const commandPayload = gate.kind === "handoff" ? readCommandActionGatePayload(gate.payload) : null;
6117
- const isConfirmation = gate.kind === "confirm";
6118
- const displayCommand = commandPayload?.displayCommand ?? commandPayload?.command.join(" ") ?? gate.title;
6119
- const isInteractiveHandoff = gate.kind === "handoff" && gate.title === "This command requires an interactive terminal.";
6120
- const title = isInteractiveHandoff ? t("actionGateInteractiveTitle") : gate.title;
6121
- const body = isInteractiveHandoff ? t("actionGateInteractiveBody") : gate.body;
6122
- output.write(`
6123
- [nolo] ${t("actionGateNeeded")}
6124
- `);
6125
- output.write(`[nolo] ${title}
6126
- `);
6127
- if (body) output.write(`[nolo] ${body}
6128
- `);
6129
- output.write(` ${displayCommand}
6130
- `);
6131
- output.write(`[nolo] ${t("actionGateEnterHint")}
6132
- `);
6133
- return new Promise((resolve2) => {
6560
+ const meta = deriveGateMeta(gate);
6561
+ const { commandPayload, isConfirmation, displayCommand } = meta;
6562
+ writeGatePrompt(output, meta);
6563
+ return new Promise((resolve4) => {
6134
6564
  const rawInput = input;
6135
6565
  let settled = false;
6136
6566
  let commandRunning = false;
6567
+ let confirmationLine = "";
6137
6568
  const finish = (result) => {
6138
6569
  if (settled) return;
6139
6570
  settled = true;
6140
6571
  hooks?.registerToken?.(null);
6141
6572
  input.off("data", onData);
6142
- resolve2(result);
6573
+ resolve4(result);
6143
6574
  };
6144
- const cancel = (reason) => finish({
6145
- content: `action gate cancelled: ${gate.title}`,
6146
- metadata: {
6147
- exitCode: 130,
6148
- actionGateResult: { gateId: gate.id, status: "cancelled", output: reason }
6149
- }
6150
- });
6151
- const fail = (message) => finish({
6152
- content: `action gate failed: ${gate.title}`,
6153
- metadata: {
6154
- exitCode: 1,
6155
- actionGateResult: { gateId: gate.id, status: "failed", output: message }
6156
- }
6157
- });
6575
+ const cancel = (reason) => finish(buildGateCancelledResult(gate, reason));
6576
+ const fail = (message) => finish(buildGateFailedResult(gate, message));
6158
6577
  const runCommand = async () => {
6159
6578
  if (settled || commandRunning) return;
6160
6579
  commandRunning = true;
@@ -6197,21 +6616,26 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6197
6616
  };
6198
6617
  const handleToken = (text) => {
6199
6618
  if (settled || commandRunning) return;
6200
- if (text.includes("") || isConfirmation && text.includes("\x1B")) {
6619
+ if (text.includes(CTRL_C) || isConfirmation && text === ESC) {
6201
6620
  cancel("interrupted");
6202
6621
  return;
6203
6622
  }
6204
- if (text.includes("\r") || text.includes("\n")) {
6205
- if (isConfirmation) {
6206
- finish({
6207
- content: `action gate completed: ${gate.title}`,
6208
- metadata: {
6209
- actionGateResult: { gateId: gate.id, status: "completed", output: "confirmed" }
6210
- }
6211
- });
6212
- } else {
6213
- void runCommand();
6623
+ if (isConfirmation) {
6624
+ const newline = text.search(/[\r\n]/);
6625
+ confirmationLine += newline >= 0 ? text.slice(0, newline) : text;
6626
+ if (newline >= 0) {
6627
+ const normalized = confirmationLine.trim().toLowerCase();
6628
+ confirmationLine = "";
6629
+ if (!isApprovedConfirmationInput(normalized)) {
6630
+ cancel("confirmation declined");
6631
+ return;
6632
+ }
6633
+ finish(buildGateConfirmedResult(gate));
6214
6634
  }
6635
+ return;
6636
+ }
6637
+ if (text.includes("\r") || text.includes("\n")) {
6638
+ void runCommand();
6215
6639
  }
6216
6640
  };
6217
6641
  const onData = (chunk) => {
@@ -6225,6 +6649,40 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
6225
6649
  }
6226
6650
  });
6227
6651
  }
6652
+ function asConfirmPermissionRequest(gate) {
6653
+ return gate;
6654
+ }
6655
+ function isAutoConfirmableFileWriteGate(gate) {
6656
+ return gate.kind === "confirm" && asConfirmPermissionRequest(gate).action === "file_write";
6657
+ }
6658
+ async function resolveActionGate(gate, deps) {
6659
+ if (gate.kind === "confirm") {
6660
+ const approved = await deps.dialogHost.run(
6661
+ (anchor) => runConfirmDialog({
6662
+ request: asConfirmPermissionRequest(gate),
6663
+ input: deps.input,
6664
+ output: deps.output,
6665
+ ...anchor
6666
+ })
6667
+ );
6668
+ return approved ? buildGateConfirmedResult(gate) : buildGateCancelledResult(gate, "confirmation declined");
6669
+ }
6670
+ return deps.dialogHost.withKeyboard(async () => {
6671
+ let subprocessRan = false;
6672
+ deps.pauseComposer();
6673
+ try {
6674
+ return await waitForRawActionGate(deps.input, deps.output, gate, deps.spawnRunner, {
6675
+ beforeSubprocess: () => {
6676
+ subprocessRan = true;
6677
+ },
6678
+ afterSubprocess: deps.resumeComposerFromSubprocess,
6679
+ registerToken: deps.registerToken
6680
+ });
6681
+ } finally {
6682
+ if (!subprocessRan) deps.resumeComposerFromDialog();
6683
+ }
6684
+ });
6685
+ }
6228
6686
  async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, confirmDestructiveAction) {
6229
6687
  const scheduleTitlePatchSync = (runResult) => {
6230
6688
  if (!runResult.titlePatchPromise || !runResult.dialogId) return;
@@ -6296,7 +6754,6 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6296
6754
  ctx.scheduleRender();
6297
6755
  }) : ctx.output;
6298
6756
  const requestUserChoice = isInteractiveInput(ctx.input) && ctx.dialogHost ? async (choiceReq) => {
6299
- ctx.modalOwnsKeyboard = true;
6300
6757
  try {
6301
6758
  return await ctx.dialogHost.run(
6302
6759
  (anchor) => runAskChoiceDialog({
@@ -6308,12 +6765,11 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6308
6765
  );
6309
6766
  } catch {
6310
6767
  return { kind: "cancelled" };
6311
- } finally {
6312
- ctx.composerDecoderDrain?.();
6313
- ctx.modalOwnsKeyboard = false;
6314
6768
  }
6315
6769
  } : void 0;
6316
6770
  ctx.runRegistryPoller.beginHold();
6771
+ const injectionInbox = createTurnInjectionInbox();
6772
+ ctx.turnInjectionInbox = injectionInbox;
6317
6773
  try {
6318
6774
  ctx.activeTurnAbort = new AbortController();
6319
6775
  ctx.activeTurnEpoch = myEpoch;
@@ -6330,6 +6786,7 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6330
6786
  ...confirmDestructiveAction ? { confirmDestructiveAction } : {},
6331
6787
  ...requestUserChoice ? { requestUserChoice } : {},
6332
6788
  abortSignal: ctx.activeTurnAbort.signal,
6789
+ drainInjections: () => injectionInbox.drain(),
6333
6790
  pastedTextStore: ctx.pasteStore,
6334
6791
  activityReporter: ctx.activityReporter,
6335
6792
  onAgentRunStatus: (snapshot) => {
@@ -6355,12 +6812,18 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6355
6812
  ...runResult.title ? { dialogTitle: runResult.title } : {}
6356
6813
  } : {},
6357
6814
  ...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
6358
- ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
6815
+ ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {},
6816
+ // 本轮已发起 agent 调用(消息已注入),消费切换通知。
6817
+ pendingCwdNotice: void 0
6359
6818
  };
6360
- if (runResult.dialogId && nextDialogKey) {
6361
- ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
6362
- }
6363
6819
  }
6820
+ ctx.accumulateSessionCredits(runResult.turnCredits);
6821
+ appendTurnBillingAudit({
6822
+ dialogId: runResult.dialogId ?? null,
6823
+ turnCredits: runResult.turnCredits ?? null,
6824
+ usageRecords: runResult.usageRecords,
6825
+ aborted: true
6826
+ });
6364
6827
  scheduleTitlePatchSync(runResult);
6365
6828
  return { ok: false, aborted: true };
6366
6829
  }
@@ -6403,12 +6866,22 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6403
6866
  // estimatedContextTokens:下一轮若 provider 不返回 usage,context
6404
6867
  // chip 仍显示真实累计占用而不是回退到启动时的静态估算。
6405
6868
  ...runResult.turnTokens && runResult.turnTokens.input > 0 ? { estimatedContextTokens: runResult.turnTokens.input } : {},
6406
- ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
6869
+ ...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {},
6870
+ // 切换消息本轮已注入 agent 上下文(runAgentChat 已读),消费掉,
6871
+ // 避免下轮重复注入。仅当确实发起了 agent 调用(本轮是真实 turn、
6872
+ // 而非纯 child-run-completed 事件短路)时才清除。
6873
+ pendingCwdNotice: void 0
6407
6874
  };
6408
- if (runResult.dialogId && nextDialogKey) {
6409
- ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
6410
- }
6411
6875
  }
6876
+ ctx.accumulateSessionCredits(runResult.turnCredits);
6877
+ appendTurnBillingAudit({
6878
+ dialogId: runResult.dialogId ?? null,
6879
+ turnCredits: runResult.turnCredits ?? null,
6880
+ usageRecords: runResult.usageRecords,
6881
+ // 普通中断(Esc)同样属于「账照记、轮未正常收尾」,审计行标注出来,
6882
+ // 与 1028 行 forceStop 路径及正常完成轮区分开。
6883
+ ...wasAborted || runResult.streamInterrupted ? { aborted: true } : {}
6884
+ });
6412
6885
  scheduleTitlePatchSync(runResult);
6413
6886
  if (!wasAborted && runResult.exitCode !== 0) {
6414
6887
  if (isBalanceExhaustedError(runResult.localError) && runResult.dialogId) {
@@ -6426,6 +6899,21 @@ async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, conf
6426
6899
  ctx.runRegistryPoller.endHold();
6427
6900
  ctx.activityIndicator.stop();
6428
6901
  ctx.activeTurnAbort = null;
6902
+ if (ctx.turnInjectionInbox === injectionInbox) ctx.turnInjectionInbox = null;
6903
+ const leftover = injectionInbox.close();
6904
+ if (leftover.length > 0) {
6905
+ const binding = ensureChatQueueBinding(
6906
+ ctx,
6907
+ actionGateHandler,
6908
+ confirmDestructiveAction
6909
+ );
6910
+ for (const entry of leftover) {
6911
+ try {
6912
+ binding.enqueue(entry.fallback);
6913
+ } catch {
6914
+ }
6915
+ }
6916
+ }
6429
6917
  }
6430
6918
  }
6431
6919
  function ensureChatQueueBinding(ctx, actionGateHandler, confirmDestructiveAction) {
@@ -7013,7 +7501,7 @@ function createFixedInput(output, config) {
7013
7501
  const renderInputArea = (buffer, cursorPos) => {
7014
7502
  const colorEnabled = resolveCliColorEnabled();
7015
7503
  const cols = Math.max(1, getColumns());
7016
- const completions = completeSlashCommand(buffer);
7504
+ const completions = completeSlashCommand(buffer, config.getCwd?.());
7017
7505
  const sections = [];
7018
7506
  if (completions.length > 0) {
7019
7507
  sections.push(fitAnsiLine(dimCliText(completions.join(" "), colorEnabled), cols));
@@ -7034,6 +7522,10 @@ function createFixedInput(output, config) {
7034
7522
  for (const line of queueLines) {
7035
7523
  sections.push(fitAnsiLine(line.replace(/\r?\n/g, " "), cols));
7036
7524
  }
7525
+ const attachmentLine = config.getAttachmentLine?.() ?? null;
7526
+ if (attachmentLine) {
7527
+ sections.push(fitAnsiLine(attachmentLine.replace(/\r?\n/g, " "), cols));
7528
+ }
7037
7529
  const promptRaw = t("promptLabel");
7038
7530
  const promptWidth = displayWidth(promptRaw);
7039
7531
  const prompt = colorEnabled ? `${themeColorSequence("accent")}${promptRaw}\x1B[39m` : promptRaw;
@@ -7083,7 +7575,7 @@ function createFixedInput(output, config) {
7083
7575
  }
7084
7576
  const text = sections.join("\n");
7085
7577
  const lines = sections.length;
7086
- const headerRows = (completions.length > 0 ? 1 : 0) + 2 + (titleLine ? 1 : 0) + activityLines.length + queueLines.length;
7578
+ const headerRows = (completions.length > 0 ? 1 : 0) + 2 + (titleLine ? 1 : 0) + activityLines.length + queueLines.length + (attachmentLine ? 1 : 0);
7087
7579
  return { text, lines, cursorCol, cursorRow: headerRows + cursorRow };
7088
7580
  };
7089
7581
  const repaintAt = (buffer, cursorPos, force = false) => {
@@ -7493,7 +7985,8 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7493
7985
  turnCtx,
7494
7986
  emitCommandOutput,
7495
7987
  renderHistoryToOutput,
7496
- refreshDialogTotalCredits,
7988
+ scheduleRender,
7989
+ seedDialogCreditsBase,
7497
7990
  persistExplicitAgentSwitch,
7498
7991
  persistAgentSelection: persistAgentSelection2,
7499
7992
  writeClipboard: writeClipboard2,
@@ -7541,8 +8034,10 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7541
8034
  host.state = {
7542
8035
  ...host.state,
7543
8036
  turnTokens: void 0,
7544
- // 新对话累计积分从 0 开始,清掉旧对话残留的累计值,等首轮读取后再回填。
7545
- dialogTotalCredits: void 0,
8037
+ // 新对话的积分累计从 0 重新开始:既清掉旧对话的本会话累加,也清掉旧对话
8038
+ // 的历史基数(新对话没有历史)。
8039
+ sessionCredits: void 0,
8040
+ dialogCreditsBase: void 0,
7546
8041
  cachedMemoryOverlay: void 0,
7547
8042
  // 新对话重新加载记忆
7548
8043
  estimatedContextTokens: estimateDefaultCliContextTokens({
@@ -7592,9 +8087,10 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7592
8087
  // composer chip falls back to the default CLI surface estimate and
7593
8088
  // the context percentage visibly drops. Mirrors the /clear reset.
7594
8089
  turnTokens: void 0,
7595
- // compact fork 出新的官方 dialog,其全量计费投影从新记录累计。
7596
- // 清掉旧累计值,等首轮读取后再回填新 dialog 的 totalCost。
7597
- dialogTotalCredits: void 0,
8090
+ // compact fork 出新的官方 dialog,计费从新记录开始累计;新 dialog 没有
8091
+ // 历史基数,本会话累加也随之归零。
8092
+ sessionCredits: void 0,
8093
+ dialogCreditsBase: void 0,
7598
8094
  cachedMemoryOverlay: void 0,
7599
8095
  // compact 创建新 dialog,重新加载记忆
7600
8096
  estimatedContextTokens: estimateDefaultCliContextTokens({
@@ -7666,6 +8162,18 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7666
8162
  emitCommandOutput(t("themeRefreshFailed"));
7667
8163
  }
7668
8164
  }
8165
+ if (result.action?.type === "cwd-refresh") {
8166
+ if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) !== "0") {
8167
+ const gitStatus = await detectGitStatusAsync(host.state.cwd);
8168
+ if (gitStatus !== void 0) {
8169
+ host.state = { ...host.state, gitStatus };
8170
+ }
8171
+ }
8172
+ if (result.action.switchMessage) {
8173
+ emitCommandOutput(result.action.switchMessage);
8174
+ }
8175
+ scheduleRender();
8176
+ }
7669
8177
  if (result.action?.type === "pick-agent") {
7670
8178
  try {
7671
8179
  const pickResult = await dialogHost.run(
@@ -7697,7 +8205,6 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7697
8205
  model: pickResult.model,
7698
8206
  userLanguage: host.state.userLanguage
7699
8207
  }),
7700
- ...pickResult.apiSource ? { apiSource: pickResult.apiSource } : {},
7701
8208
  cachedMemoryOverlay: void 0
7702
8209
  // 切换 agent 后重新加载记忆
7703
8210
  };
@@ -7814,13 +8321,14 @@ async function runSubmittedSlashLine(line, host, actionGateHandler, confirmDestr
7814
8321
  dialogLabel: pickResult.dialog.title || pickResult.dialog.id,
7815
8322
  dialogTitle: pickResult.dialog.title,
7816
8323
  turnTokens: void 0,
7817
- // 切换对话先清空上一个对话的累计积分,等下面异步读取成功后回填;
8324
+ // 切换对话先清空上一个对话的积分,等下面异步 seed 成功后回填历史基数;
7818
8325
  // 若读取失败也不至于把旧对话的累计值残留显示在状态行。
7819
- dialogTotalCredits: void 0,
8326
+ sessionCredits: void 0,
8327
+ dialogCreditsBase: void 0,
7820
8328
  cachedMemoryOverlay: void 0
7821
8329
  // 切换对话后重新加载记忆
7822
8330
  };
7823
- refreshDialogTotalCredits(pickResult.dialog.id, pickResult.dialog.dbKey);
8331
+ seedDialogCreditsBase(pickResult.dialog.id, pickResult.dialog.dbKey);
7824
8332
  clearCollapsedPasteStore(pasteStore);
7825
8333
  emitCommandOutput(
7826
8334
  `${t("resumedDialogPrefix")}: ${pickResult.dialog.title} (${pickResult.dialog.id})`
@@ -8097,7 +8605,6 @@ async function runTuiWorkspace(options) {
8097
8605
  isTurnActive: () => activeTurnAbort !== null,
8098
8606
  fallbackLabel: () => `${state.agentName} -> working`,
8099
8607
  stoppingLabel: () => t("turnStopping"),
8100
- displayMode: () => state.toolDisplay,
8101
8608
  onRepaint: () => {
8102
8609
  if (fixedInput.active && !fixedInput.isPaused()) {
8103
8610
  output.write("\x1B[?2026h\x1B[?25l");
@@ -8141,7 +8648,14 @@ async function runTuiWorkspace(options) {
8141
8648
  getInputLines: () => fixedInput.getInputLines(),
8142
8649
  isPaused: () => fixedInput.isPaused()
8143
8650
  },
8144
- output
8651
+ output,
8652
+ // The decoder-drain hook is only bound once the interactive raw-mode
8653
+ // composer installs its decoder (inside the `isInteractiveInput` block
8654
+ // below); the non-raw readline path never binds it, so this stays a
8655
+ // no-op there — which is correct, that path has no raw decoder to drain.
8656
+ drainDecoder: () => {
8657
+ if (composerDecoderDrain) composerDecoderDrain();
8658
+ }
8145
8659
  });
8146
8660
  let sessionEnded = false;
8147
8661
  const selectionState = createSelectionState();
@@ -8170,7 +8684,7 @@ async function runTuiWorkspace(options) {
8170
8684
  const repaintBanner = () => {
8171
8685
  if (!output.isTTY) return;
8172
8686
  if (fixedInput.isPaused()) return;
8173
- if (modalOwnsKeyboard) return;
8687
+ if (dialogHost.isKeyboardClaimed()) return;
8174
8688
  const currentColumns = output.columns ?? 80;
8175
8689
  const welcome = renderWelcome(state, 0, 0, currentColumns);
8176
8690
  const lines = welcome.split("\n");
@@ -8219,9 +8733,8 @@ async function runTuiWorkspace(options) {
8219
8733
  selectionState.anchor = null;
8220
8734
  selectionState.head = null;
8221
8735
  };
8222
- let modalOwnsKeyboard = false;
8223
8736
  let rawActionGateTokenHandler = null;
8224
- const refreshDialogTotalCredits = (dialogId, dialogKey) => {
8737
+ const seedDialogCreditsBase = (dialogId, dialogKey) => {
8225
8738
  const env = options.env ?? process.env;
8226
8739
  const authToken = resolvePlatformAuthToken(env);
8227
8740
  if (!authToken) return;
@@ -8233,14 +8746,19 @@ async function runTuiWorkspace(options) {
8233
8746
  }).then((record) => {
8234
8747
  if (sessionEnded) return;
8235
8748
  if (state.dialogId !== dialogId) return;
8749
+ if (state.sessionCredits !== void 0) return;
8236
8750
  const recordObj = record && typeof record === "object" ? record : null;
8237
8751
  const totalCost = recordObj ? Number(recordObj?.totalCost) : NaN;
8238
8752
  if (Number.isFinite(totalCost) && totalCost >= 0) {
8239
- state = { ...state, dialogTotalCredits: totalCost };
8753
+ state = { ...state, dialogCreditsBase: totalCost };
8240
8754
  }
8241
8755
  }).catch(() => {
8242
8756
  });
8243
8757
  };
8758
+ const accumulateSessionCredits = (credits) => {
8759
+ if (credits === void 0 || !Number.isFinite(credits)) return;
8760
+ state = { ...state, sessionCredits: (state.sessionCredits ?? 0) + credits };
8761
+ };
8244
8762
  let composerDecoderDrain = null;
8245
8763
  const emitCommandOutput = (text, command = "") => {
8246
8764
  if (!text) return;
@@ -8255,6 +8773,7 @@ async function runTuiWorkspace(options) {
8255
8773
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
8256
8774
  };
8257
8775
  let chatQueueBinding = null;
8776
+ let turnInjectionInbox = null;
8258
8777
  const turnCtx = {
8259
8778
  get state() {
8260
8779
  return state;
@@ -8292,24 +8811,18 @@ async function runTuiWorkspace(options) {
8292
8811
  set activeTurnEpoch(v) {
8293
8812
  activeTurnEpoch = v;
8294
8813
  },
8295
- get modalOwnsKeyboard() {
8296
- return modalOwnsKeyboard;
8297
- },
8298
- set modalOwnsKeyboard(v) {
8299
- modalOwnsKeyboard = v;
8300
- },
8301
- get composerDecoderDrain() {
8302
- return composerDecoderDrain;
8303
- },
8304
- set composerDecoderDrain(v) {
8305
- composerDecoderDrain = v;
8306
- },
8307
8814
  get chatQueueBinding() {
8308
8815
  return chatQueueBinding;
8309
8816
  },
8310
8817
  set chatQueueBinding(v) {
8311
8818
  chatQueueBinding = v;
8312
8819
  },
8820
+ get turnInjectionInbox() {
8821
+ return turnInjectionInbox;
8822
+ },
8823
+ set turnInjectionInbox(v) {
8824
+ turnInjectionInbox = v;
8825
+ },
8313
8826
  get sessionEnded() {
8314
8827
  return sessionEnded;
8315
8828
  },
@@ -8337,7 +8850,8 @@ async function runTuiWorkspace(options) {
8337
8850
  renderHistoryToOutput,
8338
8851
  scheduleRender,
8339
8852
  flushPendingRender,
8340
- refreshDialogTotalCredits,
8853
+ seedDialogCreditsBase,
8854
+ accumulateSessionCredits,
8341
8855
  emitCommandOutput
8342
8856
  };
8343
8857
  const persistExplicitAgentSwitch = (previousAgentKey) => {
@@ -8375,7 +8889,8 @@ async function runTuiWorkspace(options) {
8375
8889
  turnCtx,
8376
8890
  emitCommandOutput,
8377
8891
  renderHistoryToOutput,
8378
- refreshDialogTotalCredits,
8892
+ scheduleRender,
8893
+ seedDialogCreditsBase,
8379
8894
  persistExplicitAgentSwitch,
8380
8895
  persistAgentSelection,
8381
8896
  writeClipboard: writeClipboard2,
@@ -8407,9 +8922,16 @@ async function runTuiWorkspace(options) {
8407
8922
  return dimCliText(` \u2937 ${i + 1}. ${oneLine}`, colorEnabled);
8408
8923
  });
8409
8924
  },
8925
+ getAttachmentLine: () => {
8926
+ const line = formatComposerAttachmentLine(state.attachedImages);
8927
+ return line ? dimCliText(line, resolveCliColorEnabled()) : null;
8928
+ },
8410
8929
  onInputLinesChange: () => {
8411
8930
  renderHistoryToOutput();
8412
- }
8931
+ },
8932
+ // /cd 路径补全候选行渲染需要当前 cwd(读 state 而非入参,保证
8933
+ // /cd 切换后立即以新 cwd 渲染,composer 不必重建)。
8934
+ getCwd: () => state.cwd
8413
8935
  });
8414
8936
  fixedInput = {
8415
8937
  ...baseFixedInput,
@@ -8483,39 +9005,33 @@ async function runTuiWorkspace(options) {
8483
9005
  input.setRawMode?.(false);
8484
9006
  };
8485
9007
  const buildInteractiveTurnHandlers = () => {
8486
- const releaseKeyboardToComposer = () => {
8487
- onData.destroy();
8488
- modalOwnsKeyboard = false;
8489
- };
8490
9008
  const actionGateHandler = async (gate) => {
8491
- modalOwnsKeyboard = true;
8492
- try {
8493
- return await waitForRawActionGate(input, output, gate, spawnRunner, {
8494
- beforeSubprocess: () => fixedInput.pause(),
8495
- afterSubprocess: () => fixedInput.resumeFromSubprocess(),
8496
- registerToken: (handler) => {
8497
- rawActionGateTokenHandler = handler;
8498
- }
8499
- });
8500
- } finally {
8501
- releaseKeyboardToComposer();
9009
+ if (state.autoConfirm && isAutoConfirmableFileWriteGate(gate)) {
9010
+ return buildGateConfirmedResult(gate);
8502
9011
  }
9012
+ return await resolveActionGate(gate, {
9013
+ dialogHost,
9014
+ input,
9015
+ output,
9016
+ spawnRunner,
9017
+ registerToken: (handler) => {
9018
+ rawActionGateTokenHandler = handler;
9019
+ },
9020
+ pauseComposer: () => fixedInput.pause(),
9021
+ resumeComposerFromSubprocess: () => fixedInput.resumeFromSubprocess(),
9022
+ resumeComposerFromDialog: () => fixedInput.resumeFromDialog()
9023
+ });
8503
9024
  };
8504
9025
  const confirmDestructiveAction = async (request) => {
8505
9026
  if (state.autoConfirm) return true;
8506
- modalOwnsKeyboard = true;
8507
- try {
8508
- return await dialogHost.run(
8509
- (anchor) => runConfirmDialog({
8510
- request,
8511
- input,
8512
- output,
8513
- ...anchor
8514
- })
8515
- );
8516
- } finally {
8517
- releaseKeyboardToComposer();
8518
- }
9027
+ return await dialogHost.run(
9028
+ (anchor) => runConfirmDialog({
9029
+ request,
9030
+ input,
9031
+ output,
9032
+ ...anchor
9033
+ })
9034
+ );
8519
9035
  };
8520
9036
  return { actionGateHandler, confirmDestructiveAction };
8521
9037
  };
@@ -8548,11 +9064,30 @@ ${err.message}` : ""}`
8548
9064
  flushPendingRender();
8549
9065
  fixedInput.exitOutputMode(buffer, cursorPos);
8550
9066
  };
9067
+ (options.env ?? process.env)[RUN_WAKE_CHANNEL_ENV] = "1";
8551
9068
  runWakeHandler = (event) => {
8552
9069
  if (done) return;
8553
9070
  if (busy || fixedInput.isPaused()) {
8554
- const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
8555
- ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction).enqueue(event);
9071
+ const text = typeof event === "string" ? event : event.text;
9072
+ const injected = !!turnInjectionInbox && turnInjectionInbox.push({ text, fallback: event });
9073
+ if (injected) {
9074
+ const displayText = typeof event !== "string" && event.kind === "child-run-completed" ? event.displayText ?? event.text : text;
9075
+ const noticeLine = dimCliText(displayText, resolveCliColorEnabled());
9076
+ if (!isInteractiveInput(input)) {
9077
+ output.write(`${noticeLine}
9078
+ `);
9079
+ } else {
9080
+ history.followBottom = true;
9081
+ appendStreamSafeNotice(history, noticeLine, {
9082
+ appendLocalTurn,
9083
+ startTurn
9084
+ });
9085
+ renderHistoryToOutput();
9086
+ }
9087
+ } else {
9088
+ const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
9089
+ ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction).enqueue(event);
9090
+ }
8556
9091
  if (fixedInput.active && !fixedInput.isPaused()) {
8557
9092
  fixedInput.repaint(buffer, cursorPos);
8558
9093
  }
@@ -8693,6 +9228,19 @@ ${err.message}` : ""}`
8693
9228
  output.write(`${refreshMsg}
8694
9229
  `);
8695
9230
  }
9231
+ } else if (res.action?.type === "cwd-refresh") {
9232
+ state = res.nextState;
9233
+ if (res.output) {
9234
+ output.write(`${res.output}
9235
+ `);
9236
+ }
9237
+ if ((options.env?.NOLO_CLI_GIT_STATUS ?? process.env.NOLO_CLI_GIT_STATUS) !== "0") {
9238
+ const gitStatus = await detectGitStatusAsync(state.cwd);
9239
+ if (gitStatus !== void 0) {
9240
+ state = { ...state, gitStatus };
9241
+ }
9242
+ }
9243
+ scheduleRender();
8696
9244
  } else if (res.action) {
8697
9245
  output.write(
8698
9246
  "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"
@@ -8717,7 +9265,7 @@ ${hint}` : hint;
8717
9265
  };
8718
9266
  const handleInputToken = async (sequence) => {
8719
9267
  if (done) return;
8720
- if (modalOwnsKeyboard) {
9268
+ if (dialogHost.isKeyboardClaimed()) {
8721
9269
  rawActionGateTokenHandler?.(sequence);
8722
9270
  return;
8723
9271
  }
@@ -8901,8 +9449,20 @@ ${merged}` : merged;
8901
9449
  if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
8902
9450
  return;
8903
9451
  }
9452
+ if (isBackspaceSequence(sequence, {}) && buffer === "" && state.attachedImages.length > 0) {
9453
+ const undo = popLastAttachedImage(state.attachedImages);
9454
+ if (undo.handled && undo.removed) {
9455
+ state = { ...state, attachedImages: undo.images };
9456
+ emitCommandOutput(
9457
+ `[nolo] ${t("attachmentRemovedHint", undo.removed.filename, String(undo.images.length))}`
9458
+ );
9459
+ if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
9460
+ return;
9461
+ }
9462
+ }
8904
9463
  const result = applyTuiInputKey(buffer, sequence, {}, cursorPos, {
8905
- pasteStore
9464
+ pasteStore,
9465
+ cwd: state.cwd
8906
9466
  });
8907
9467
  if (result.redraw) {
8908
9468
  resetHistoryFrameDiffCache(output);
@@ -8925,7 +9485,7 @@ ${merged}` : merged;
8925
9485
  if (busyLock) {
8926
9486
  const trimmedText = submittedText.trim();
8927
9487
  const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
8928
- 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";
9488
+ 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";
8929
9489
  if (isBusyLocalSlash) {
8930
9490
  await handleBusyLocalSlash(submittedText, busySlashCommand);
8931
9491
  return;
@@ -8995,9 +9555,9 @@ ${merged}` : merged;
8995
9555
  input.on("data", onData);
8996
9556
  fixedInput.repaint(buffer, cursorPos);
8997
9557
  refreshGitStatus();
8998
- await new Promise((resolve2) => {
8999
- resolveDone = resolve2;
9000
- if (done) resolve2();
9558
+ await new Promise((resolve4) => {
9559
+ resolveDone = resolve4;
9560
+ if (done) resolve4();
9001
9561
  });
9002
9562
  } finally {
9003
9563
  finish();
@@ -9011,7 +9571,11 @@ ${merged}` : merged;
9011
9571
  for await (const line of rl) {
9012
9572
  const shouldExit = await runSubmittedLine(
9013
9573
  line,
9014
- (gate) => waitForActionGate(rl, input, output, gate, spawnRunner),
9574
+ (gate) => (
9575
+ // /auto on 同款短路,见上方交互路径的 actionGateHandler 注释:
9576
+ // 只放行 file_write 的 confirm gate,handoff/input 类仍要求真人操作。
9577
+ state.autoConfirm && isAutoConfirmableFileWriteGate(gate) ? Promise.resolve(buildGateConfirmedResult(gate)) : waitForActionGate(rl, input, output, gate, spawnRunner)
9578
+ ),
9015
9579
  async (request) => {
9016
9580
  if (state.autoConfirm) return true;
9017
9581
  rl.pause();
@@ -9050,12 +9614,16 @@ ${renderStatusLine(state)}
9050
9614
  }
9051
9615
  async function startTuiWorkspace(options) {
9052
9616
  const themeOwner = ++latestWorkspaceThemeOwner;
9617
+ void sweepStaleClipboardFiles(getDefaultClipboardTempDir()).catch(() => {
9618
+ });
9053
9619
  try {
9054
9620
  return await runTuiWorkspace(options);
9055
9621
  } finally {
9056
9622
  if (themeOwner === latestWorkspaceThemeOwner) {
9057
9623
  resetWorkspaceThemeState();
9058
9624
  }
9625
+ void sweepStaleClipboardFiles(getDefaultClipboardTempDir()).catch(() => {
9626
+ });
9059
9627
  }
9060
9628
  }
9061
9629
  export {