@carboncode/cli 0.1.0 → 0.1.2

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 (99) hide show
  1. package/README.md +15 -24
  2. package/README.zh-CN.md +13 -11
  3. package/dist/cli/{acp-35C4ME6Y.js → acp-6J54TVVC.js} +17 -16
  4. package/dist/cli/acp-6J54TVVC.js.map +1 -0
  5. package/dist/cli/{chat-A6UJDPGV.js → chat-636MFZ7W.js} +21 -21
  6. package/dist/cli/{chunk-JKGYMRX5.js → chunk-3N7FTZVE.js} +2 -2
  7. package/dist/cli/{chunk-4TVNJWMA.js → chunk-ACHQFKW2.js} +178 -18
  8. package/dist/cli/chunk-ACHQFKW2.js.map +1 -0
  9. package/dist/cli/{chunk-7L2WTRNU.js → chunk-ANVEA3RU.js} +2 -2
  10. package/dist/cli/{chunk-QVC75MR3.js → chunk-BXMMGFAL.js} +2 -2
  11. package/dist/cli/{chunk-UI66BH6D.js → chunk-COTWTQQZ.js} +2 -2
  12. package/dist/cli/{chunk-J5BYPUB5.js → chunk-CZCPIK5K.js} +1508 -1176
  13. package/dist/cli/chunk-CZCPIK5K.js.map +1 -0
  14. package/dist/cli/{chunk-XJ5SRLKK.js → chunk-D3ACJ6D5.js} +2 -2
  15. package/dist/cli/{chunk-WRN65TRD.js → chunk-DSQNSP7F.js} +2 -2
  16. package/dist/cli/{chunk-QJG7OF27.js → chunk-FKSYTVWZ.js} +27 -10
  17. package/dist/cli/chunk-FKSYTVWZ.js.map +1 -0
  18. package/dist/cli/{chunk-BSINVTTL.js → chunk-FXG7CSGY.js} +7 -7
  19. package/dist/cli/{chunk-4MQ3VURH.js → chunk-K43DXH3G.js} +52 -83
  20. package/dist/cli/chunk-K43DXH3G.js.map +1 -0
  21. package/dist/cli/{chunk-BSGCXZQN.js → chunk-LNU3CR7X.js} +2 -2
  22. package/dist/cli/{chunk-TH756VLN.js → chunk-MXUSER5C.js} +240 -191
  23. package/dist/cli/chunk-MXUSER5C.js.map +1 -0
  24. package/dist/cli/{chunk-3T6VBZCL.js → chunk-NQJYZKEU.js} +2 -2
  25. package/dist/cli/{chunk-IX6XI2RG.js → chunk-OB5XR5HG.js} +2 -2
  26. package/dist/cli/{chunk-ILJOIQ5W.js → chunk-OY5GGU6D.js} +2 -2
  27. package/dist/cli/{chunk-IAUOP25G.js → chunk-R677DIFU.js} +38 -22
  28. package/dist/cli/chunk-R677DIFU.js.map +1 -0
  29. package/dist/cli/{chunk-CPKCNHRR.js → chunk-RSQMO6CF.js} +5 -5
  30. package/dist/cli/{chunk-3OAR6NVL.js → chunk-RUPXIRNL.js} +2 -2
  31. package/dist/cli/{chunk-S2KIUQKQ.js → chunk-S4YD3N3X.js} +7 -6
  32. package/dist/cli/{chunk-S2KIUQKQ.js.map → chunk-S4YD3N3X.js.map} +1 -1
  33. package/dist/cli/{chunk-4IBIPQVB.js → chunk-T6SBUSG2.js} +3 -3
  34. package/dist/cli/{chunk-D5NFKRGO.js → chunk-UGPC4LPM.js} +2 -2
  35. package/dist/cli/{chunk-T5TQ4NDT.js → chunk-X4UJ6Q6M.js} +3 -3
  36. package/dist/cli/{code-4TUTAGO5.js → code-TBC3K5AZ.js} +24 -33
  37. package/dist/cli/code-TBC3K5AZ.js.map +1 -0
  38. package/dist/cli/{commands-KMOZEYCF.js → commands-HMQPRVNT.js} +4 -4
  39. package/dist/cli/{commit-DTFA56VQ.js → commit-WIY4B3X4.js} +3 -3
  40. package/dist/cli/{desktop-7N3MHNBD.js → desktop-MGOG3AWV.js} +17 -17
  41. package/dist/cli/{diff-E5OWTF4C.js → diff-57LRKCB7.js} +8 -8
  42. package/dist/cli/{doctor-IEJQRJMN.js → doctor-5FDRBIXE.js} +8 -8
  43. package/dist/cli/index.js +32 -32
  44. package/dist/cli/{mcp-PDI2PDLG.js → mcp-HJHTNRZF.js} +2 -2
  45. package/dist/cli/{mcp-browse-OSPXOFPZ.js → mcp-browse-C2PJRQBO.js} +2 -2
  46. package/dist/cli/{mcp-inspect-QRFVTHMF.js → mcp-inspect-JBFXV2II.js} +2 -2
  47. package/dist/cli/{prompt-3CDII3UO.js → prompt-U62OVZNY.js} +3 -3
  48. package/dist/cli/{replay-HYOSRQIV.js → replay-M3YKBVAM.js} +8 -8
  49. package/dist/cli/{run-2ZHADOUP.js → run-V6X5GXCR.js} +13 -13
  50. package/dist/cli/{server-X75PAZG5.js → server-5WVJQUOR.js} +10 -10
  51. package/dist/cli/{sessions-POOZA5CQ.js → sessions-B266WVM3.js} +12 -12
  52. package/dist/cli/{setup-YLPFI3OH.js → setup-SWX5E3W2.js} +5 -5
  53. package/dist/cli/{stats-NXJ3TO2D.js → stats-VPPKS6UF.js} +6 -6
  54. package/dist/cli/{version-NXXWE3WN.js → version-TVHAEHWY.js} +12 -12
  55. package/dist/index.d.ts +17 -2
  56. package/dist/index.js +360 -150
  57. package/dist/index.js.map +1 -1
  58. package/package.json +2 -2
  59. package/dist/cli/acp-35C4ME6Y.js.map +0 -1
  60. package/dist/cli/chunk-4MQ3VURH.js.map +0 -1
  61. package/dist/cli/chunk-4TVNJWMA.js.map +0 -1
  62. package/dist/cli/chunk-IAUOP25G.js.map +0 -1
  63. package/dist/cli/chunk-J5BYPUB5.js.map +0 -1
  64. package/dist/cli/chunk-QJG7OF27.js.map +0 -1
  65. package/dist/cli/chunk-TH756VLN.js.map +0 -1
  66. package/dist/cli/code-4TUTAGO5.js.map +0 -1
  67. /package/dist/cli/{chat-A6UJDPGV.js.map → chat-636MFZ7W.js.map} +0 -0
  68. /package/dist/cli/{chunk-JKGYMRX5.js.map → chunk-3N7FTZVE.js.map} +0 -0
  69. /package/dist/cli/{chunk-7L2WTRNU.js.map → chunk-ANVEA3RU.js.map} +0 -0
  70. /package/dist/cli/{chunk-QVC75MR3.js.map → chunk-BXMMGFAL.js.map} +0 -0
  71. /package/dist/cli/{chunk-UI66BH6D.js.map → chunk-COTWTQQZ.js.map} +0 -0
  72. /package/dist/cli/{chunk-XJ5SRLKK.js.map → chunk-D3ACJ6D5.js.map} +0 -0
  73. /package/dist/cli/{chunk-WRN65TRD.js.map → chunk-DSQNSP7F.js.map} +0 -0
  74. /package/dist/cli/{chunk-BSINVTTL.js.map → chunk-FXG7CSGY.js.map} +0 -0
  75. /package/dist/cli/{chunk-BSGCXZQN.js.map → chunk-LNU3CR7X.js.map} +0 -0
  76. /package/dist/cli/{chunk-3T6VBZCL.js.map → chunk-NQJYZKEU.js.map} +0 -0
  77. /package/dist/cli/{chunk-IX6XI2RG.js.map → chunk-OB5XR5HG.js.map} +0 -0
  78. /package/dist/cli/{chunk-ILJOIQ5W.js.map → chunk-OY5GGU6D.js.map} +0 -0
  79. /package/dist/cli/{chunk-CPKCNHRR.js.map → chunk-RSQMO6CF.js.map} +0 -0
  80. /package/dist/cli/{chunk-3OAR6NVL.js.map → chunk-RUPXIRNL.js.map} +0 -0
  81. /package/dist/cli/{chunk-4IBIPQVB.js.map → chunk-T6SBUSG2.js.map} +0 -0
  82. /package/dist/cli/{chunk-D5NFKRGO.js.map → chunk-UGPC4LPM.js.map} +0 -0
  83. /package/dist/cli/{chunk-T5TQ4NDT.js.map → chunk-X4UJ6Q6M.js.map} +0 -0
  84. /package/dist/cli/{commands-KMOZEYCF.js.map → commands-HMQPRVNT.js.map} +0 -0
  85. /package/dist/cli/{commit-DTFA56VQ.js.map → commit-WIY4B3X4.js.map} +0 -0
  86. /package/dist/cli/{desktop-7N3MHNBD.js.map → desktop-MGOG3AWV.js.map} +0 -0
  87. /package/dist/cli/{diff-E5OWTF4C.js.map → diff-57LRKCB7.js.map} +0 -0
  88. /package/dist/cli/{doctor-IEJQRJMN.js.map → doctor-5FDRBIXE.js.map} +0 -0
  89. /package/dist/cli/{mcp-PDI2PDLG.js.map → mcp-HJHTNRZF.js.map} +0 -0
  90. /package/dist/cli/{mcp-browse-OSPXOFPZ.js.map → mcp-browse-C2PJRQBO.js.map} +0 -0
  91. /package/dist/cli/{mcp-inspect-QRFVTHMF.js.map → mcp-inspect-JBFXV2II.js.map} +0 -0
  92. /package/dist/cli/{prompt-3CDII3UO.js.map → prompt-U62OVZNY.js.map} +0 -0
  93. /package/dist/cli/{replay-HYOSRQIV.js.map → replay-M3YKBVAM.js.map} +0 -0
  94. /package/dist/cli/{run-2ZHADOUP.js.map → run-V6X5GXCR.js.map} +0 -0
  95. /package/dist/cli/{server-X75PAZG5.js.map → server-5WVJQUOR.js.map} +0 -0
  96. /package/dist/cli/{sessions-POOZA5CQ.js.map → sessions-B266WVM3.js.map} +0 -0
  97. /package/dist/cli/{setup-YLPFI3OH.js.map → setup-SWX5E3W2.js.map} +0 -0
  98. /package/dist/cli/{stats-NXJ3TO2D.js.map → stats-VPPKS6UF.js.map} +0 -0
  99. /package/dist/cli/{version-NXXWE3WN.js.map → version-TVHAEHWY.js.map} +0 -0
package/dist/index.js CHANGED
@@ -33,35 +33,35 @@ function defineTheme(base) {
33
33
  }
34
34
  var githubDark = defineTheme({
35
35
  fg: {
36
- strong: "#e6edf3",
37
- body: "#c9d1d9",
38
- sub: "#8b949e",
39
- meta: "#6e7681",
40
- faint: "#484f58"
36
+ strong: "#f8fafc",
37
+ body: "#e5e7eb",
38
+ sub: "#a1a1aa",
39
+ meta: "#71717a",
40
+ faint: "#52525b"
41
41
  },
42
42
  tone: {
43
- brand: "#79c0ff",
44
- accent: "#d2a8ff",
45
- violet: "#b395f5",
46
- ok: "#7ee787",
47
- warn: "#f0b07d",
48
- err: "#ff8b81",
49
- info: "#79c0ff"
43
+ brand: "#f8fafc",
44
+ accent: "#a78bfa",
45
+ violet: "#c084fc",
46
+ ok: "#22c55e",
47
+ warn: "#eab308",
48
+ err: "#ef4444",
49
+ info: "#60a5fa"
50
50
  },
51
51
  toneActive: {
52
- brand: "#a5d6ff",
53
- accent: "#e2c5ff",
54
- violet: "#c8aaff",
55
- ok: "#a8f5ad",
56
- warn: "#ffc99e",
57
- err: "#ffaba3",
58
- info: "#a5d6ff"
52
+ brand: "#ffffff",
53
+ accent: "#c4b5fd",
54
+ violet: "#d8b4fe",
55
+ ok: "#4ade80",
56
+ warn: "#facc15",
57
+ err: "#f87171",
58
+ info: "#93c5fd"
59
59
  },
60
60
  surface: {
61
- bg: "#0a0c10",
62
- bgInput: "#0d1015",
63
- bgCode: "#06080c",
64
- bgElev: "#11141a"
61
+ bg: "#09090b",
62
+ bgInput: "#111113",
63
+ bgCode: "#050507",
64
+ bgElev: "#18181b"
65
65
  }
66
66
  });
67
67
  var dark = defineTheme({
@@ -97,39 +97,40 @@ var dark = defineTheme({
97
97
  bgElev: "#151d2f"
98
98
  }
99
99
  });
100
- var light = defineTheme({
100
+ var codexLightBase = {
101
101
  fg: {
102
- strong: "#111827",
103
- body: "#1f2937",
104
- sub: "#4b5563",
105
- meta: "#6b7280",
106
- faint: "#9ca3af"
102
+ strong: "#0d0d0d",
103
+ body: "#111111",
104
+ sub: "#666666",
105
+ meta: "#9a9a9a",
106
+ faint: "#c7c7c7"
107
107
  },
108
108
  tone: {
109
- brand: "#2563eb",
110
- accent: "#7c3aed",
111
- violet: "#6d28d9",
112
- ok: "#15803d",
113
- warn: "#b45309",
114
- err: "#dc2626",
115
- info: "#0369a1"
109
+ brand: "#0096a6",
110
+ accent: "#c000c0",
111
+ violet: "#7a5cff",
112
+ ok: "#238636",
113
+ warn: "#8a7a00",
114
+ err: "#c62828",
115
+ info: "#0096a6"
116
116
  },
117
117
  toneActive: {
118
- brand: "#1d4ed8",
119
- accent: "#6d28d9",
120
- violet: "#5b21b6",
121
- ok: "#166534",
122
- warn: "#92400e",
123
- err: "#b91c1c",
124
- info: "#075985"
118
+ brand: "#00a3b5",
119
+ accent: "#d000d0",
120
+ violet: "#8a6dff",
121
+ ok: "#2ea043",
122
+ warn: "#9a8700",
123
+ err: "#d32f2f",
124
+ info: "#00a3b5"
125
125
  },
126
126
  surface: {
127
127
  bg: "#ffffff",
128
- bgInput: "#f8fafc",
129
- bgCode: "#f3f4f6",
130
- bgElev: "#eef2f7"
128
+ bgInput: "#f2f2f2",
129
+ bgCode: "#f5f5f5",
130
+ bgElev: "#e7e7e7"
131
131
  }
132
- });
132
+ };
133
+ var light = defineTheme(codexLightBase);
133
134
  var tokyoNight = defineTheme({
134
135
  fg: {
135
136
  strong: "#c0caf5",
@@ -163,39 +164,7 @@ var tokyoNight = defineTheme({
163
164
  bgElev: "#24283b"
164
165
  }
165
166
  });
166
- var githubLight = defineTheme({
167
- fg: {
168
- strong: "#1f2328",
169
- body: "#24292f",
170
- sub: "#57606a",
171
- meta: "#6e7781",
172
- faint: "#8c959f"
173
- },
174
- tone: {
175
- brand: "#0969da",
176
- accent: "#8250df",
177
- violet: "#6639ba",
178
- ok: "#1a7f37",
179
- warn: "#9a6700",
180
- err: "#cf222e",
181
- info: "#0969da"
182
- },
183
- toneActive: {
184
- brand: "#0550ae",
185
- accent: "#6639ba",
186
- violet: "#512a97",
187
- ok: "#116329",
188
- warn: "#7d4e00",
189
- err: "#a40e26",
190
- info: "#0550ae"
191
- },
192
- surface: {
193
- bg: "#ffffff",
194
- bgInput: "#f6f8fa",
195
- bgCode: "#f6f8fa",
196
- bgElev: "#eaeef2"
197
- }
198
- });
167
+ var githubLight = defineTheme(codexLightBase);
199
168
  var highContrast = defineTheme({
200
169
  fg: {
201
170
  strong: "#ffffff",
@@ -230,7 +199,7 @@ var highContrast = defineTheme({
230
199
  }
231
200
  });
232
201
  var THEMES = {
233
- default: githubDark,
202
+ default: githubLight,
234
203
  dark,
235
204
  light,
236
205
  "tokyo-night": tokyoNight,
@@ -238,7 +207,7 @@ var THEMES = {
238
207
  "github-light": githubLight,
239
208
  "high-contrast": highContrast
240
209
  };
241
- var DEFAULT_THEME_NAME = "default";
210
+ var DEFAULT_THEME_NAME = "github-light";
242
211
  var DEFAULT_THEME = THEMES[DEFAULT_THEME_NAME];
243
212
  var activeTheme = DEFAULT_THEME;
244
213
  function proxyTokens(select) {
@@ -1139,13 +1108,16 @@ var EN = {
1139
1108
  },
1140
1109
  ui: {
1141
1110
  welcome: "Run `carboncode` any time to start chatting \u2014 your settings are remembered.",
1142
- taglineChat: "DeepSeek-native agent",
1143
- taglineCode: "DeepSeek-native coding agent",
1144
- taglineSub: "cache-first \xB7 flash-first",
1145
- startSessionHint: "type a message to start your session",
1111
+ taglineChat: "terminal coding agent",
1112
+ taglineCode: "terminal coding agent",
1113
+ taglineSub: "read the repo, edit files, run validation",
1114
+ startSessionHint: "type a task to start",
1146
1115
  inputPlaceholder: "Ask anything... (type / for commands, @ for files)",
1147
1116
  busy: "Thinking...",
1148
1117
  thinking: "\u25B8 thinking...",
1118
+ activityWaitingForModel: "waiting for model\u2026",
1119
+ activityThinking: "thinking\u2026",
1120
+ activityProcessing: "processing\u2026",
1149
1121
  undo: "Undo",
1150
1122
  undoHint: "press u within 5s to undo",
1151
1123
  applied: "applied",
@@ -1623,6 +1595,7 @@ var EN = {
1623
1595
  app: {
1624
1596
  walkCancelledRemaining: "\u25B8 walk cancelled \u2014 {count} block(s) still pending.",
1625
1597
  walkCancelled: "\u25B8 walk cancelled.",
1598
+ turnInterrupted: "\u25B8 turn interrupted \u2014 type a follow-up to continue.",
1626
1599
  editModeYolo: "\u25B8 edit mode: YOLO \u2014 edits AND shell commands auto-run. /undo still rolls back edits. Use carefully.",
1627
1600
  editModeAuto: "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo (space pauses the timer). Shell commands still ask.",
1628
1601
  editModeReview: "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)",
@@ -2103,10 +2076,13 @@ var EN = {
2103
2076
  queuedApplyDiscard: "{count} queued \xB7 y apply \xB7 n discard",
2104
2077
  editsQueued: "edits queued \xB7 y apply \xB7 n discard",
2105
2078
  shiftTabFlip: " {mid} \xB7 Shift+Tab to flip",
2106
- queuedDots: "queued\u2026"
2079
+ queuedDots: "queued\u2026",
2080
+ undoApplied: "applied {ok}/{total}",
2081
+ undoHint: "u undo \xB7 Space pause",
2082
+ undoPausedHint: "u undo \xB7 Space resume"
2107
2083
  },
2108
2084
  composer: {
2109
- placeholder: "ask anything \xB7 slash for commands \xB7 at-sign for files",
2085
+ placeholder: "Ask for a code change",
2110
2086
  waitingForResponse: "\u2026waiting for response\u2026",
2111
2087
  hintSend: "send",
2112
2088
  hintNewline: "newline",
@@ -2130,7 +2106,7 @@ var EN = {
2130
2106
  denyTitle: "Deny \u2014 provide context",
2131
2107
  optional: "optional",
2132
2108
  denyFooter: "type context \xB7 \u23CE submit with reason \xB7 esc skip (deny without reason)",
2133
- pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 Tab add context \xB7 esc cancel",
2109
+ pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 esc cancel",
2134
2110
  allowOnce: "allow once",
2135
2111
  allowOnceDesc: "permit this access; remember the directory for the rest of this session",
2136
2112
  allowAlways: "allow always",
@@ -2150,7 +2126,7 @@ var EN = {
2150
2126
  optional: "optional",
2151
2127
  denyFooter: "type context \xB7 \u23CE submit with reason \xB7 esc skip (deny without reason)",
2152
2128
  awaiting: "awaiting",
2153
- pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 Tab add context \xB7 esc cancel",
2129
+ pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 esc cancel",
2154
2130
  allowOnce: "allow once",
2155
2131
  allowOnceDesc: "run this command, ask again next time",
2156
2132
  allowAlways: "allow always",
@@ -2164,7 +2140,7 @@ var EN = {
2164
2140
  previewMorePlural: "\u2026 {n} more lines hidden \u2014 press esc, ask the model to split it"
2165
2141
  },
2166
2142
  editConfirm: {
2167
- footer: "[y/Enter] apply \xB7 [n] reject with reason \xB7 [a] apply rest \xB7 [A] flip AUTO \xB7 [\u2191\u2193/Space] scroll \xB7 [Esc] abort",
2143
+ footer: "Enter apply \xB7 n reject \xB7 \u2191\u2193 scroll \xB7 Esc cancel",
2168
2144
  newTag: "NEW",
2169
2145
  editTag: "EDIT",
2170
2146
  linesCount: "-{removed} +{added} lines",
@@ -2286,7 +2262,8 @@ var EN = {
2286
2262
  startup: {
2287
2263
  codeRooted: '\u25B8 carboncode code: rooted at {rootDir}, session "{session}" \xB7 {tools} native tool(s){semantic}',
2288
2264
  ephemeral: "(ephemeral)",
2289
- semanticOn: " \xB7 semantic_search on"
2265
+ semanticOn: " \xB7 semantic_search on",
2266
+ updateAvailable: "\u25B8 Update available: {current} -> {latest}\n Run `npm install -g @carboncode/cli` to update."
2290
2267
  },
2291
2268
  doctorErrors: {
2292
2269
  unreadable: "{path} unreadable \u2014 {message}",
@@ -2583,13 +2560,16 @@ var zhCN = {
2583
2560
  },
2584
2561
  ui: {
2585
2562
  welcome: "\u968F\u65F6\u8FD0\u884C `carboncode` \u5F00\u59CB\u804A\u5929 \u2014 \u60A8\u7684\u8BBE\u7F6E\u5C06\u88AB\u8BB0\u4F4F\u3002",
2586
- taglineChat: "DeepSeek \u539F\u751F\u667A\u80FD\u4F53",
2587
- taglineCode: "DeepSeek \u539F\u751F\u4EE3\u7801\u667A\u80FD\u4F53",
2588
- taglineSub: "\u7F13\u5B58\u4F18\u5148 \xB7 Flash \u4F18\u5148",
2589
- startSessionHint: "\u8F93\u5165\u6D88\u606F\u4EE5\u5F00\u59CB\u60A8\u7684\u4F1A\u8BDD",
2563
+ taglineChat: "\u7EC8\u7AEF\u4EE3\u7801\u667A\u80FD\u4F53",
2564
+ taglineCode: "\u7EC8\u7AEF\u4EE3\u7801\u667A\u80FD\u4F53",
2565
+ taglineSub: "\u8BFB\u53D6\u4ED3\u5E93\u3001\u7F16\u8F91\u6587\u4EF6\u3001\u8FD0\u884C\u9A8C\u8BC1",
2566
+ startSessionHint: "\u8F93\u5165\u4EFB\u52A1\u5F00\u59CB",
2590
2567
  inputPlaceholder: "\u8F93\u5165\u4EFB\u4F55\u5185\u5BB9... (\u8F93\u5165 / \u4F7F\u7528\u547D\u4EE4, @ \u5F15\u7528\u6587\u4EF6)",
2591
2568
  busy: "\u601D\u8003\u4E2D...",
2592
2569
  thinking: "\u25B8 \u601D\u8003\u4E2D...",
2570
+ activityWaitingForModel: "\u7B49\u5F85\u6A21\u578B\u2026",
2571
+ activityThinking: "\u601D\u8003\u4E2D\u2026",
2572
+ activityProcessing: "\u5904\u7406\u4E2D\u2026",
2593
2573
  undo: "\u64A4\u6D88",
2594
2574
  undoHint: "\u5728 5 \u79D2\u5185\u6309 u \u64A4\u6D88",
2595
2575
  applied: "\u5DF2\u5E94\u7528",
@@ -3068,6 +3048,7 @@ var zhCN = {
3068
3048
  app: {
3069
3049
  walkCancelledRemaining: "\u25B8 \u6D4F\u89C8\u5DF2\u53D6\u6D88 \u2014 \u8FD8\u6709 {count} \u4E2A\u5F85\u5904\u7406\u7F16\u8F91\u5757\u3002",
3070
3050
  walkCancelled: "\u25B8 \u6D4F\u89C8\u5DF2\u53D6\u6D88\u3002",
3051
+ turnInterrupted: "\u25B8 \u672C\u8F6E\u5DF2\u4E2D\u65AD \u2014 \u8F93\u5165\u540E\u7EED\u5185\u5BB9\u53EF\u7EE7\u7EED\u3002",
3071
3052
  editModeYolo: "\u25B8 \u7F16\u8F91\u6A21\u5F0F\uFF1AYOLO \u2014 \u7F16\u8F91\u548C shell \u547D\u4EE4\u90FD\u81EA\u52A8\u6267\u884C\u3002/undo \u4ECD\u53EF\u64A4\u9500\u7F16\u8F91\u3002\u8BF7\u8C28\u614E\u4F7F\u7528\u3002",
3072
3053
  editModeAuto: "\u25B8 \u7F16\u8F91\u6A21\u5F0F\uFF1AAUTO \u2014 \u7F16\u8F91\u7ACB\u5373\u5E94\u7528\uFF1B5 \u79D2\u5185\u6309 u \u64A4\u9500\uFF08\u7A7A\u683C\u6682\u505C\u8BA1\u65F6\uFF09\u3002shell \u547D\u4EE4\u4ECD\u4F1A\u8BE2\u95EE\u3002",
3073
3054
  editModeReview: "\u25B8 \u7F16\u8F91\u6A21\u5F0F\uFF1Areview \u2014 \u7F16\u8F91\u5165\u961F\u5F85 /apply\uFF08\u6216 y\uFF09/ /discard\uFF08\u6216 n\uFF09",
@@ -3548,10 +3529,13 @@ var zhCN = {
3548
3529
  queuedApplyDiscard: "{count} \u4E2A\u5F85\u5904\u7406 \xB7 y \u5E94\u7528 \xB7 n \u4E22\u5F03",
3549
3530
  editsQueued: "\u7F16\u8F91\u5DF2\u6392\u961F \xB7 y \u5E94\u7528 \xB7 n \u4E22\u5F03",
3550
3531
  shiftTabFlip: " {mid} \xB7 Shift+Tab \u5207\u6362",
3551
- queuedDots: "\u6392\u961F\u4E2D\u2026"
3532
+ queuedDots: "\u6392\u961F\u4E2D\u2026",
3533
+ undoApplied: "\u5DF2\u5E94\u7528 {ok}/{total}",
3534
+ undoHint: "u \u64A4\u6D88 \xB7 Space \u6682\u505C",
3535
+ undoPausedHint: "u \u64A4\u6D88 \xB7 Space \u7EE7\u7EED"
3552
3536
  },
3553
3537
  composer: {
3554
- placeholder: "\u8F93\u5165\u4EFB\u4F55\u5185\u5BB9 \xB7 / \u4F7F\u7528\u547D\u4EE4 \xB7 @ \u5F15\u7528\u6587\u4EF6",
3538
+ placeholder: "\u8F93\u5165\u4EFB\u52A1",
3555
3539
  waitingForResponse: "\u2026\u7B49\u5F85\u54CD\u5E94\u2026",
3556
3540
  hintSend: "\u53D1\u9001",
3557
3541
  hintNewline: "\u6362\u884C",
@@ -3575,7 +3559,7 @@ var zhCN = {
3575
3559
  denyTitle: "\u62D2\u7EDD \u2014 \u63D0\u4F9B\u539F\u56E0",
3576
3560
  optional: "\u53EF\u9009",
3577
3561
  denyFooter: "\u8F93\u5165\u539F\u56E0 \xB7 \u23CE \u63D0\u4EA4 \xB7 Esc \u8DF3\u8FC7\uFF08\u76F4\u63A5\u62D2\u7EDD\uFF09",
3578
- pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Tab \u6DFB\u52A0\u8BF4\u660E \xB7 Esc \u53D6\u6D88",
3562
+ pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Esc \u53D6\u6D88",
3579
3563
  allowOnce: "\u5141\u8BB8\u4E00\u6B21",
3580
3564
  allowOnceDesc: "\u672C\u6B21\u5141\u8BB8\uFF0C\u672C\u4F1A\u8BDD\u5185\u6B64\u76EE\u5F55\u4E0D\u518D\u8BE2\u95EE",
3581
3565
  allowAlways: "\u59CB\u7EC8\u5141\u8BB8",
@@ -3595,7 +3579,7 @@ var zhCN = {
3595
3579
  optional: "\u53EF\u9009",
3596
3580
  denyFooter: "\u8F93\u5165\u539F\u56E0 \xB7 \u23CE \u63D0\u4EA4 \xB7 Esc \u8DF3\u8FC7\uFF08\u76F4\u63A5\u62D2\u7EDD\uFF09",
3597
3581
  awaiting: "\u7B49\u5F85\u4E2D",
3598
- pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Tab \u6DFB\u52A0\u8BF4\u660E \xB7 Esc \u53D6\u6D88",
3582
+ pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Esc \u53D6\u6D88",
3599
3583
  allowOnce: "\u5141\u8BB8\u4E00\u6B21",
3600
3584
  allowOnceDesc: "\u6267\u884C\u6B64\u547D\u4EE4\uFF0C\u4E0B\u6B21\u518D\u95EE",
3601
3585
  allowAlways: "\u59CB\u7EC8\u5141\u8BB8",
@@ -3609,7 +3593,7 @@ var zhCN = {
3609
3593
  previewMorePlural: "\u2026 \u8FD8\u6709 {n} \u884C\u672A\u663E\u793A \u2014 \u6309 esc \u53D6\u6D88\uFF0C\u8BA9\u6A21\u578B\u62C6\u5206\u540E\u518D\u8BD5"
3610
3594
  },
3611
3595
  editConfirm: {
3612
- footer: "[y/Enter] \u5E94\u7528 \xB7 [n] \u62D2\u7EDD\u5E76\u8BF4\u660E \xB7 [a] \u5E94\u7528\u5269\u4F59 \xB7 [A] \u5207\u6362 AUTO \xB7 [\u2191\u2193/Space] \u6EDA\u52A8 \xB7 [Esc] \u4E2D\u6B62",
3596
+ footer: "Enter \u5E94\u7528 \xB7 n \u62D2\u7EDD \xB7 \u2191\u2193 \u6EDA\u52A8 \xB7 Esc \u53D6\u6D88",
3613
3597
  newTag: "\u65B0\u589E",
3614
3598
  editTag: "\u7F16\u8F91",
3615
3599
  linesCount: "-{removed} +{added} \u884C",
@@ -3731,7 +3715,8 @@ var zhCN = {
3731
3715
  startup: {
3732
3716
  codeRooted: '\u25B8 carboncode code\uFF1A\u6839\u76EE\u5F55 {rootDir}\uFF0C\u4F1A\u8BDD "{session}" \xB7 {tools} \u4E2A\u539F\u751F\u5DE5\u5177{semantic}',
3733
3717
  ephemeral: "\uFF08\u4E34\u65F6\uFF09",
3734
- semanticOn: " \xB7 \u8BED\u4E49\u641C\u7D22\u5DF2\u5F00\u542F"
3718
+ semanticOn: " \xB7 \u8BED\u4E49\u641C\u7D22\u5DF2\u5F00\u542F",
3719
+ updateAvailable: "\u25B8 \u6709\u65B0\u7248\u672C\u53EF\u7528\uFF1A{current} -> {latest}\n \u8FD0\u884C `npm install -g @carboncode/cli` \u66F4\u65B0\u3002"
3735
3720
  },
3736
3721
  doctorErrors: {
3737
3722
  unreadable: "{path} \u65E0\u6CD5\u8BFB\u53D6 \u2014 {message}",
@@ -4785,9 +4770,14 @@ var ToolRegistry = class {
4785
4770
  rejectedReason: "plan-mode"
4786
4771
  });
4787
4772
  }
4773
+ const dispatchCtx = {
4774
+ signal: opts.signal,
4775
+ confirmationGate: opts.confirmationGate,
4776
+ onInteractiveWait: opts.onInteractiveWait
4777
+ };
4788
4778
  if (this._interceptor) {
4789
4779
  try {
4790
- const short = await this._interceptor(name, args);
4780
+ const short = await this._interceptor(name, args, dispatchCtx);
4791
4781
  if (typeof short === "string") return short;
4792
4782
  } catch (err) {
4793
4783
  return JSON.stringify({
@@ -4801,10 +4791,7 @@ var ToolRegistry = class {
4801
4791
  this._auditListener?.({ name, args });
4802
4792
  } catch {
4803
4793
  }
4804
- const result = await tool.fn(args, {
4805
- signal: opts.signal,
4806
- confirmationGate: opts.confirmationGate
4807
- });
4794
+ const result = await tool.fn(args, dispatchCtx);
4808
4795
  const str = typeof result === "string" ? result : JSON.stringify(result);
4809
4796
  let clipped = str;
4810
4797
  if (opts.maxResultTokens !== void 0) {
@@ -6834,6 +6821,8 @@ var CacheFirstLoop = class {
6834
6821
  const name = call.function?.name ?? "";
6835
6822
  const args = call.function?.arguments ?? "{}";
6836
6823
  const parsedArgs = safeParseToolArgs(args);
6824
+ const startedAt = Date.now();
6825
+ let interactiveWaitMs = 0;
6837
6826
  this._inflight.add(this.inflightIdFor(call));
6838
6827
  try {
6839
6828
  const preReport = await runHooks({
@@ -6853,13 +6842,17 @@ var CacheFirstLoop = class {
6853
6842
  preWarnings,
6854
6843
  postWarnings: [],
6855
6844
  result: `[hook block] ${blocking?.hook.command ?? "<unknown>"}
6856
- ${reason}`
6845
+ ${reason}`,
6846
+ elapsedMs: Date.now() - startedAt
6857
6847
  };
6858
6848
  }
6859
6849
  const result = await this.tools.dispatch(name, args, {
6860
6850
  signal,
6861
6851
  maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
6862
- confirmationGate: this.confirmationGate
6852
+ confirmationGate: this.confirmationGate,
6853
+ onInteractiveWait: (elapsedMs) => {
6854
+ interactiveWaitMs += Math.max(0, elapsedMs);
6855
+ }
6863
6856
  });
6864
6857
  const postReport = await runHooks({
6865
6858
  hooks: this.hooks,
@@ -6872,7 +6865,12 @@ ${reason}`
6872
6865
  }
6873
6866
  });
6874
6867
  const postWarnings = [...hookWarnings(postReport.outcomes, this._turn)];
6875
- return { preWarnings, postWarnings, result };
6868
+ return {
6869
+ preWarnings,
6870
+ postWarnings,
6871
+ result,
6872
+ elapsedMs: Math.max(0, Date.now() - startedAt - interactiveWaitMs)
6873
+ };
6876
6874
  } finally {
6877
6875
  this._inflight.delete(this.inflightIdFor(call));
6878
6876
  }
@@ -7357,12 +7355,14 @@ ${reason}`
7357
7355
  const args = call.function?.arguments ?? "{}";
7358
7356
  const s = settled[k];
7359
7357
  let result;
7358
+ let elapsedMs = 0;
7360
7359
  let preWarnings = [];
7361
7360
  let postWarnings = [];
7362
7361
  if (s.status === "fulfilled") {
7363
7362
  preWarnings = s.value.preWarnings;
7364
7363
  postWarnings = s.value.postWarnings;
7365
7364
  result = s.value.result;
7365
+ elapsedMs = s.value.elapsedMs;
7366
7366
  } else {
7367
7367
  const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
7368
7368
  result = JSON.stringify({ error: `${err.name}: ${err.message}` });
@@ -7381,7 +7381,8 @@ ${reason}`
7381
7381
  content: result,
7382
7382
  toolName: name,
7383
7383
  toolArgs: args,
7384
- callId: this.inflightIdFor(call)
7384
+ callId: this.inflightIdFor(call),
7385
+ elapsedMs
7385
7386
  };
7386
7387
  }
7387
7388
  }
@@ -8931,27 +8932,51 @@ async function applyMultiEdit(rootDir, edits) {
8931
8932
  );
8932
8933
  }
8933
8934
  const rel = displayRel(rootDir, e.abs);
8934
- if (e.search.length === 0) {
8935
- throw new Error(
8936
- `multi_edit: edit #${i + 1} (${rel}) search cannot be empty (no edits applied)`
8937
- );
8938
- }
8939
8935
  let state = filesByPath.get(e.abs);
8940
8936
  if (!state) {
8941
8937
  let before;
8942
- try {
8943
- before = await fs.readFile(e.abs, "utf8");
8944
- } catch (err) {
8945
- throw new Error(
8946
- `multi_edit: edit #${i + 1} cannot read ${rel}: ${err.message} (no edits applied)`
8947
- );
8938
+ if (e.search.length === 0) {
8939
+ try {
8940
+ await fs.readFile(e.abs, "utf8");
8941
+ throw new Error("empty SEARCH only creates new files \u2014 this file already exists");
8942
+ } catch (err) {
8943
+ const code = err.code;
8944
+ if (code !== "ENOENT") {
8945
+ throw new Error(
8946
+ `multi_edit: edit #${i + 1} cannot create ${rel}: ${err.message} (no edits applied)`
8947
+ );
8948
+ }
8949
+ }
8950
+ state = { buf: "", le: "\n", hunks: [], deltaChars: 0, touched: 0, created: true };
8951
+ filesByPath.set(e.abs, state);
8952
+ } else {
8953
+ try {
8954
+ before = await fs.readFile(e.abs, "utf8");
8955
+ } catch (err) {
8956
+ throw new Error(
8957
+ `multi_edit: edit #${i + 1} cannot read ${rel}: ${err.message} (no edits applied)`
8958
+ );
8959
+ }
8960
+ const le = before.includes("\r\n") ? "\r\n" : "\n";
8961
+ state = { buf: before, le, hunks: [], deltaChars: 0, touched: 0, created: false };
8962
+ filesByPath.set(e.abs, state);
8948
8963
  }
8949
- const le = before.includes("\r\n") ? "\r\n" : "\n";
8950
- state = { buf: before, le, hunks: [], deltaChars: 0, touched: 0 };
8951
- filesByPath.set(e.abs, state);
8964
+ }
8965
+ if (e.search.length === 0 && (!state.created || state.touched > 0)) {
8966
+ throw new Error(
8967
+ `multi_edit: edit #${i + 1} (${rel}) empty search only creates new files (no edits applied)`
8968
+ );
8952
8969
  }
8953
8970
  const adaptedSearch = e.search.replace(/\r?\n/g, state.le);
8954
8971
  const adaptedReplace = e.replace.replace(/\r?\n/g, state.le);
8972
+ if (adaptedSearch.length === 0) {
8973
+ state.buf = adaptedReplace;
8974
+ state.hunks.push(`# ${rel}
8975
+ ${renderCreateDiff(adaptedReplace)}`);
8976
+ state.deltaChars += adaptedReplace.length;
8977
+ state.touched++;
8978
+ continue;
8979
+ }
8955
8980
  const firstIdx = state.buf.indexOf(adaptedSearch);
8956
8981
  if (firstIdx < 0) {
8957
8982
  throw new Error(
@@ -8972,6 +8997,7 @@ ${renderEditDiff(adaptedSearch, adaptedReplace, startLine)}`);
8972
8997
  state.touched++;
8973
8998
  }
8974
8999
  for (const [abs, state] of filesByPath) {
9000
+ if (state.created) await fs.mkdir(pathMod.dirname(abs), { recursive: true });
8975
9001
  await fs.writeFile(abs, state.buf, "utf8");
8976
9002
  }
8977
9003
  const fileCount = filesByPath.size;
@@ -8998,6 +9024,13 @@ function renderEditDiff(search, replace, startLine) {
8998
9024
  return `${hunk}
8999
9025
  ${body}`;
9000
9026
  }
9027
+ function renderCreateDiff(replace) {
9028
+ const lines = replace.length === 0 ? [] : replace.split(/\r?\n/);
9029
+ const hunk = `@@ -1,0 +1,${lines.length} @@`;
9030
+ const body = lines.map((line) => `+ ${line}`).join("\n");
9031
+ return body ? `${hunk}
9032
+ ${body}` : hunk;
9033
+ }
9001
9034
  function lineDiff(a, b) {
9002
9035
  const n = a.length;
9003
9036
  const m = b.length;
@@ -9281,6 +9314,110 @@ function formatOutline(entries) {
9281
9314
  ].join("\n");
9282
9315
  }
9283
9316
 
9317
+ // src/tools/fs/patch.ts
9318
+ function parseUnifiedPatch(patch) {
9319
+ if (typeof patch !== "string" || patch.trim().length === 0) {
9320
+ throw new Error("apply_patch: patch must be a non-empty string");
9321
+ }
9322
+ const lines = patch.split(/\n/);
9323
+ const files = [];
9324
+ let current = null;
9325
+ const ensureCurrent = () => {
9326
+ if (!current) {
9327
+ current = { oldPath: null, newPath: null, blocks: [] };
9328
+ files.push(current);
9329
+ }
9330
+ return current;
9331
+ };
9332
+ let i = 0;
9333
+ while (i < lines.length) {
9334
+ const line = lines[i];
9335
+ if (line.startsWith("diff --git ")) {
9336
+ current = { oldPath: null, newPath: null, blocks: [] };
9337
+ files.push(current);
9338
+ i++;
9339
+ continue;
9340
+ }
9341
+ if (line.startsWith("--- ")) {
9342
+ ensureCurrent().oldPath = parsePatchPath(line.slice(4));
9343
+ i++;
9344
+ continue;
9345
+ }
9346
+ if (line.startsWith("+++ ")) {
9347
+ ensureCurrent().newPath = parsePatchPath(line.slice(4));
9348
+ i++;
9349
+ continue;
9350
+ }
9351
+ if (line.startsWith("@@")) {
9352
+ const file = ensureCurrent();
9353
+ const path2 = file.newPath ?? file.oldPath;
9354
+ if (!path2) throw new Error("apply_patch: hunk is missing a file path");
9355
+ const searchLines = [];
9356
+ const replaceLines = [];
9357
+ i++;
9358
+ while (i < lines.length) {
9359
+ const hunkLine = lines[i];
9360
+ if (hunkLine.startsWith("diff --git ") || hunkLine.startsWith("@@") || hunkLine.startsWith("--- ")) {
9361
+ break;
9362
+ }
9363
+ if (hunkLine === "" && i === lines.length - 1) {
9364
+ break;
9365
+ }
9366
+ if (hunkLine.startsWith("\")) {
9367
+ i++;
9368
+ continue;
9369
+ }
9370
+ const marker = hunkLine[0];
9371
+ const body = hunkLine.slice(1);
9372
+ if (marker === " ") {
9373
+ searchLines.push(body);
9374
+ replaceLines.push(body);
9375
+ } else if (marker === "-") {
9376
+ searchLines.push(body);
9377
+ } else if (marker === "+") {
9378
+ replaceLines.push(body);
9379
+ } else if (hunkLine.trim().length === 0) {
9380
+ } else {
9381
+ throw new Error(`apply_patch: invalid hunk line: ${hunkLine}`);
9382
+ }
9383
+ i++;
9384
+ }
9385
+ file.blocks.push({
9386
+ path: path2,
9387
+ search: file.oldPath === null ? "" : linesToText(searchLines),
9388
+ replace: linesToText(replaceLines),
9389
+ offset: 0
9390
+ });
9391
+ continue;
9392
+ }
9393
+ i++;
9394
+ }
9395
+ const blocks = files.flatMap((file) => file.blocks);
9396
+ if (blocks.length === 0) throw new Error("apply_patch: patch did not contain any hunks");
9397
+ return blocks;
9398
+ }
9399
+ function linesToText(lines) {
9400
+ if (lines.length === 0) return "";
9401
+ return `${lines.join("\n")}
9402
+ `;
9403
+ }
9404
+ function parsePatchPath(raw) {
9405
+ const cleaned = unquotePath(raw.trim().split(/\t/, 1)[0] ?? "");
9406
+ if (cleaned === "/dev/null") return null;
9407
+ const withoutPrefix = cleaned.replace(/^[ab]\//, "");
9408
+ const normalized = withoutPrefix.replace(/^[/\\]+/, "");
9409
+ if (!normalized) throw new Error(`apply_patch: invalid patch path: ${raw}`);
9410
+ return normalized.replaceAll("\\", "/");
9411
+ }
9412
+ function unquotePath(path2) {
9413
+ if (path2.length < 2 || !path2.startsWith('"') || !path2.endsWith('"')) return path2;
9414
+ try {
9415
+ return JSON.parse(path2);
9416
+ } catch {
9417
+ return path2.slice(1, -1);
9418
+ }
9419
+ }
9420
+
9284
9421
  // src/tools/fs/search.ts
9285
9422
  import { promises as fs3 } from "fs";
9286
9423
  import * as pathMod4 from "path";
@@ -9578,10 +9715,14 @@ ${body}`;
9578
9715
  let pending = inflightGate.get(allowPrefix);
9579
9716
  if (!pending) {
9580
9717
  const gate = ctx?.confirmationGate ?? pauseGate;
9718
+ const waitStartedAt = Date.now();
9581
9719
  pending = gate.ask({
9582
9720
  kind: "path_access",
9583
9721
  payload: { path: abs, intent, toolName, sandboxRoot: normRoot, allowPrefix }
9584
9722
  });
9723
+ pending = pending.finally(() => {
9724
+ ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
9725
+ });
9585
9726
  inflightGate.set(allowPrefix, pending);
9586
9727
  void pending.finally(() => inflightGate.delete(allowPrefix));
9587
9728
  }
@@ -10001,7 +10142,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
10001
10142
  });
10002
10143
  registry.register({
10003
10144
  name: "multi_edit",
10004
- description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in a single atomic call. Edits run sequentially in array order; for edits that touch the same file, a later edit can match text inserted by an earlier one. If ANY edit fails (search not found, ambiguous match, empty search, file unreadable), NO files are written \u2014 atomic at the validation layer. Same per-edit rules as edit_file: `search` is exact text (whitespace sensitive, no regex) and must be unique in its target file at the moment that edit applies. Use this for renames spanning multiple files, cross-file refactors, or any batch where you'd otherwise loop edit_file.",
10145
+ description: "Apply N SEARCH/REPLACE edits across ONE OR MORE files in a single atomic call. Edits run sequentially in array order; for edits that touch the same file, a later edit can match text inserted by an earlier one. If ANY edit fails (search not found, ambiguous match, file unreadable, invalid create), NO files are written \u2014 atomic at the validation layer. Same per-edit rules as edit_file: `search` is exact text (whitespace sensitive, no regex) and must be unique in its target file at the moment that edit applies. To create a new file, use an empty `search`; empty `search` is refused for existing files. Use this for renames spanning multiple files, cross-file refactors, or any batch where you'd otherwise loop edit_file.",
10005
10146
  parameters: {
10006
10147
  type: "object",
10007
10148
  properties: {
@@ -10038,6 +10179,41 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
10038
10179
  return applyMultiEdit(rootDir, resolved);
10039
10180
  }
10040
10181
  });
10182
+ registry.register({
10183
+ name: "apply_patch",
10184
+ description: "Apply a unified git-style patch atomically under the sandbox root. Use this for non-trivial code edits because it lets the UI review the whole patch as one batch. If any hunk fails, no files are written.",
10185
+ parameters: {
10186
+ type: "object",
10187
+ properties: {
10188
+ patch: {
10189
+ type: "string",
10190
+ description: "Unified diff text with diff --git / --- / +++ headers and @@ hunks. Paths should be project-relative or a/ b/-prefixed."
10191
+ }
10192
+ },
10193
+ required: ["patch"]
10194
+ },
10195
+ fn: async (args, ctx) => {
10196
+ try {
10197
+ const blocks = parseUnifiedPatch(args.patch);
10198
+ const resolved = await Promise.all(
10199
+ blocks.map(async (block) => ({
10200
+ abs: await safePath(block.path, "apply_patch", ctx, "write"),
10201
+ search: block.search,
10202
+ replace: block.replace,
10203
+ rel: block.path,
10204
+ created: block.search.length === 0
10205
+ }))
10206
+ );
10207
+ const output = await applyMultiEdit(rootDir, resolved);
10208
+ const fileCount = new Set(resolved.map((edit) => edit.rel)).size;
10209
+ const fileNoun = fileCount === 1 ? "file" : "files";
10210
+ const created = resolved.filter((edit) => edit.created).map((edit) => `created ${edit.rel}`);
10211
+ return [`apply_patch: applied ${fileCount} ${fileNoun}`, ...created, output].join("\n");
10212
+ } catch (err) {
10213
+ return `apply_patch: no files written \u2014 ${err.message}`;
10214
+ }
10215
+ }
10216
+ });
10041
10217
  registry.register({
10042
10218
  name: "create_directory",
10043
10219
  description: "Create a directory (and any missing parents) under the sandbox root.",
@@ -12269,7 +12445,7 @@ function hasSensitivePathArgs(argv, projectRoot, extraPrefixes = [], extraPatter
12269
12445
  }
12270
12446
  return false;
12271
12447
  }
12272
- function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
12448
+ function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig, opts = {}) {
12273
12449
  let argv;
12274
12450
  try {
12275
12451
  argv = tokenizeCommand(cmd);
@@ -12277,7 +12453,7 @@ function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
12277
12453
  return false;
12278
12454
  }
12279
12455
  if (argv.length === 0) return false;
12280
- const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
12456
+ const allowlist = [...opts.includeBuiltin === false ? [] : BUILTIN_ALLOWLIST, ...extra];
12281
12457
  for (const prefix of allowlist) {
12282
12458
  const prefixTokens = prefix.split(" ");
12283
12459
  if (argv.length < prefixTokens.length) continue;
@@ -12302,15 +12478,18 @@ function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
12302
12478
  }
12303
12479
  return false;
12304
12480
  }
12305
- function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
12481
+ function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig, opts = {}) {
12306
12482
  let chain;
12307
12483
  try {
12308
12484
  chain = parseCommandChain(cmd);
12309
12485
  } catch {
12310
12486
  return false;
12311
12487
  }
12312
- if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig);
12313
- return chainAllowed(chain, (seg) => isAllowed(seg, extra, projectRoot, sensitivePathConfig));
12488
+ if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig, opts);
12489
+ return chainAllowed(
12490
+ chain,
12491
+ (seg) => isAllowed(seg, extra, projectRoot, sensitivePathConfig, opts)
12492
+ );
12314
12493
  }
12315
12494
 
12316
12495
  // src/tools/shell/exec.ts
@@ -12610,9 +12789,19 @@ function registerShellTools(registry, opts) {
12610
12789
  return () => snapshot2;
12611
12790
  })();
12612
12791
  const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
12792
+ const isAutoAllowed = (cmd) => isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths, {
12793
+ includeBuiltin: opts.requireApprovalForBuiltin !== true
12794
+ });
12795
+ const approvalPolicy = opts.requireApprovalForBuiltin ? "Model-requested shell commands ask the user before they run unless the user has explicitly always-allowed a project prefix or yolo mode is active. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify." : "Allowlisted read-only / test / lint / typecheck commands run immediately; anything that could mutate state, install deps, or touch the network is gated by user confirmation. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.";
12613
12796
  registry.register({
12614
12797
  name: "run_command",
12615
- description: "Run a shell command in the project root; returns combined stdout+stderr. Allowlisted read-only / test / lint / typecheck commands run immediately; anything that could mutate state, install deps, or touch the network is gated by user confirmation. Prefer this over asking the user to run a command manually \u2014 after edits, run the project's tests to verify.\n\nConstraints (no real shell \u2014 argv is parsed natively for cross-platform parity):\n\u2022 Supported: chain ops `|` / `||` / `&&` / `;` (each segment allowlist-checked individually), file redirects `>` / `>>` / `<` / `2>` / `2>>` / `2>&1` / `&>` (target paths resolve relative to project root, max one redirect per fd per segment).\n\u2022 NOT supported: background `&`, heredoc `<<`, command substitution `$(\u2026)`, subshells `(\u2026)`, process substitution `<(\u2026)`, `$VAR` env expansion, glob expansion. To pass an operator char as literal arg, quote it (`grep \"a|b\" file`).\n\u2022 `cd` does NOT persist \u2014 between calls OR within a chain like `cd dir && cmd`. Use the binary's own cwd flag: `npm --prefix <dir>`, `git -C <dir>`, `cargo -C <dir>`, `pytest <dir>/tests`.\n\u2022 Filter at source \u2014 unbounded output (`netstat -ano`, `find /`) wastes tokens. Use `grep -c`, `wc -l`, narrower paths, etc.",
12798
+ description: `Run a shell command in the project root; returns combined stdout+stderr. ${approvalPolicy}
12799
+
12800
+ Constraints (no real shell \u2014 argv is parsed natively for cross-platform parity):
12801
+ \u2022 Supported: chain ops \`|\` / \`||\` / \`&&\` / \`;\` (each segment allowlist-checked individually), file redirects \`>\` / \`>>\` / \`<\` / \`2>\` / \`2>>\` / \`2>&1\` / \`&>\` (target paths resolve relative to project root, max one redirect per fd per segment).
12802
+ \u2022 NOT supported: background \`&\`, heredoc \`<<\`, command substitution \`$(\u2026)\`, subshells \`(\u2026)\`, process substitution \`<(\u2026)\`, \`$VAR\` env expansion, glob expansion. To pass an operator char as literal arg, quote it (\`grep "a|b" file\`).
12803
+ \u2022 \`cd\` does NOT persist \u2014 between calls OR within a chain like \`cd dir && cmd\`. Use the binary's own cwd flag: \`npm --prefix <dir>\`, \`git -C <dir>\`, \`cargo -C <dir>\`, \`pytest <dir>/tests\`.
12804
+ \u2022 Filter at source \u2014 unbounded output (\`netstat -ano\`, \`find /\`) wastes tokens. Use \`grep -c\`, \`wc -l\`, narrower paths, etc.`,
12616
12805
  // Plan-mode gate: allow allowlisted commands through (git status,
12617
12806
  // cargo check, ls, grep …) so the model can actually investigate
12618
12807
  // during planning. Anything that would otherwise trigger a
@@ -12641,12 +12830,14 @@ function registerShellTools(registry, opts) {
12641
12830
  const cmd = args.command.trim();
12642
12831
  if (!cmd) throw new Error("run_command: empty command");
12643
12832
  const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
12644
- if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths)) {
12833
+ if (!isAllowAll() && !isAutoAllowed(cmd)) {
12645
12834
  const gate = ctx?.confirmationGate ?? pauseGate;
12835
+ const waitStartedAt = Date.now();
12646
12836
  const choice = await gate.ask({
12647
12837
  kind: "run_command",
12648
12838
  payload: { command: cmd, cwd: rootDir, timeoutSec: effectiveTimeout }
12649
12839
  });
12840
+ ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
12650
12841
  if (choice.type === "deny") {
12651
12842
  throw new Error(
12652
12843
  `user denied: ${cmd}${choice.denyContext ? ` \u2014 ${choice.denyContext}` : ""}`
@@ -12690,12 +12881,14 @@ function registerShellTools(registry, opts) {
12690
12881
  const cmd = args.command.trim();
12691
12882
  if (!cmd) throw new Error("run_background: empty command");
12692
12883
  const cwd = resolveCwdInsideRoot(rootDir, args.cwd);
12693
- if (!isAllowAll() && !isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths)) {
12884
+ if (!isAllowAll() && !isAutoAllowed(cmd)) {
12694
12885
  const gate = ctx?.confirmationGate ?? pauseGate;
12886
+ const waitStartedAt = Date.now();
12695
12887
  const choice = await gate.ask({
12696
12888
  kind: "run_background",
12697
12889
  payload: { command: cmd, cwd, waitSec: args.waitSec }
12698
12890
  });
12891
+ ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
12699
12892
  if (choice.type === "deny") {
12700
12893
  throw new Error(
12701
12894
  `user denied: ${cmd}${choice.denyContext ? ` \u2014 ${choice.denyContext}` : ""}`
@@ -13766,14 +13959,14 @@ function truncate(s, n) {
13766
13959
  // src/version.ts
13767
13960
  import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
13768
13961
  import { homedir as homedir7 } from "os";
13769
- import { dirname as dirname7, join as join14 } from "path";
13962
+ import { dirname as dirname8, join as join14 } from "path";
13770
13963
  import { fileURLToPath as fileURLToPath2 } from "url";
13771
13964
  var REGISTRY_URL = "https://registry.npmjs.org/@carboncode/cli/latest";
13772
13965
  var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
13773
13966
  var LATEST_FETCH_TIMEOUT_MS = 2e3;
13774
13967
  function readPackageVersion() {
13775
13968
  try {
13776
- let dir = dirname7(fileURLToPath2(import.meta.url));
13969
+ let dir = dirname8(fileURLToPath2(import.meta.url));
13777
13970
  for (let i = 0; i < 6; i++) {
13778
13971
  const p = join14(dir, "package.json");
13779
13972
  if (existsSync10(p)) {
@@ -13782,7 +13975,7 @@ function readPackageVersion() {
13782
13975
  return pkg.version;
13783
13976
  }
13784
13977
  }
13785
- const parent = dirname7(dir);
13978
+ const parent = dirname8(dir);
13786
13979
  if (parent === dir) break;
13787
13980
  dir = parent;
13788
13981
  }
@@ -13808,7 +14001,7 @@ function readCache(homeDirOverride) {
13808
14001
  function writeCache(entry, homeDirOverride) {
13809
14002
  try {
13810
14003
  const p = cachePath(homeDirOverride);
13811
- mkdirSync5(dirname7(p), { recursive: true });
14004
+ mkdirSync5(dirname8(p), { recursive: true });
13812
14005
  writeFileSync5(p, JSON.stringify(entry), "utf8");
13813
14006
  } catch {
13814
14007
  }
@@ -14578,7 +14771,7 @@ import {
14578
14771
  writeFileSync as writeFileSync6,
14579
14772
  writeSync
14580
14773
  } from "fs";
14581
- import { dirname as dirname8, resolve as resolve12 } from "path";
14774
+ import { dirname as dirname9, resolve as resolve12 } from "path";
14582
14775
  var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
14583
14776
  function parseEditBlocks(text) {
14584
14777
  const out = [];
@@ -14608,7 +14801,7 @@ function applyEditBlock(block, rootDir) {
14608
14801
  const searchEmpty = block.search.length === 0;
14609
14802
  if (searchEmpty) {
14610
14803
  try {
14611
- mkdirSync6(dirname8(absTarget), { recursive: true });
14804
+ mkdirSync6(dirname9(absTarget), { recursive: true });
14612
14805
  const fd = openSync2(absTarget, "wx");
14613
14806
  try {
14614
14807
  writeSync(fd, block.replace);
@@ -14748,7 +14941,7 @@ var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
14748
14941
  function codeSystemBase(modelId) {
14749
14942
  return CODE_SYSTEM_TEMPLATE.replace("__ESCALATION_CONTRACT__", escalationContract(modelId));
14750
14943
  }
14751
- var CODE_SYSTEM_TEMPLATE = `You are Carbon Code, a coding assistant. You have filesystem tools (read_file, write_file, edit_file, multi_edit, list_directory, directory_tree, search_files, search_content, glob, get_file_info) rooted at the user's working directory, plus run_command / run_background for shell, plus \`todo_write\` for in-session multi-step tracking.
14944
+ var CODE_SYSTEM_TEMPLATE = `You are Carbon Code, a coding assistant. You have filesystem tools (read_file, write_file, edit_file, multi_edit, apply_patch, list_directory, directory_tree, search_files, search_content, glob, get_file_info) rooted at the user's working directory, plus run_command / run_background for shell, plus \`todo_write\` for in-session multi-step tracking.
14752
14945
 
14753
14946
  # Identity is fixed by this prompt \u2014 never inferred from the workspace
14754
14947
 
@@ -14831,8 +15024,8 @@ Call shape: \`{ todos: [{ content, activeForm, status }, ...] }\` \u2014 \`conte
14831
15024
  # Plan mode (/plan)
14832
15025
 
14833
15026
  The user can ALSO enter "plan mode" via /plan, which is a stronger, explicit constraint:
14834
- - Write tools (edit_file, multi_edit, write_file, create_directory, move_file, copy_file, delete_file, delete_directory) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like "unavailable in plan mode". Don't retry them.
14835
- - Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted read-only / test shell commands still work \u2014 use them to investigate.
15027
+ - Write tools (edit_file, multi_edit, apply_patch, write_file, create_directory, move_file, copy_file, delete_file, delete_directory) and non-allowlisted run_command calls are BOUNCED at dispatch \u2014 you'll get a tool result like "unavailable in plan mode". Don't retry them.
15028
+ - Read tools (read_file, list_directory, search_files, directory_tree, get_file_info) and allowlisted read-only / test shell commands still work after user approval \u2014 use them to investigate.
14836
15029
  - You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.
14837
15030
 
14838
15031
 
@@ -14874,8 +15067,8 @@ When you do propose edits, the user will review them and decide whether to \`/ap
14874
15067
 
14875
15068
  Carbon Code runs an **edit gate**. The user's current mode (\`review\` or \`auto\`) decides what happens to your writes; you DO NOT see which mode is active, and you SHOULD NOT ask. Write the same way in both cases.
14876
15069
 
14877
- - In \`auto\` mode \`edit_file\` / \`write_file\` calls land on disk immediately with an undo window \u2014 you'll get the normal "edit blocks: 1/1 applied" style response.
14878
- - In \`review\` mode EACH \`edit_file\` / \`write_file\` call pauses tool dispatch while the user decides. You'll get one of these responses:
15070
+ - In \`auto\` mode \`edit_file\` / \`write_file\` / \`multi_edit\` / \`apply_patch\` calls land on disk immediately with an undo window \u2014 you'll get the normal applied-edit response.
15071
+ - In \`review\` mode EACH \`edit_file\` / \`write_file\` / \`multi_edit\` / \`apply_patch\` call pauses tool dispatch while the user decides. You'll get one of these responses:
14879
15072
  - \`"edit blocks: 1/1 applied"\` \u2014 user approved it. Continue as normal.
14880
15073
  - \`"User rejected this edit to <path>. Don't retry the same SEARCH/REPLACE\u2026"\` \u2014 user said no to THIS specific edit. Do NOT re-emit the same block, do NOT switch tools to sneak it past the gate (write_file \u2192 edit_file, or text-form SEARCH/REPLACE). Either take a clearly different approach or stop and ask the user what they want instead.
14881
15074
  - Text-form SEARCH/REPLACE blocks in your assistant reply queue for end-of-turn /apply \u2014 same "don't retry on rejection" rule.
@@ -14883,7 +15076,20 @@ Carbon Code runs an **edit gate**. The user's current mode (\`review\` or \`auto
14883
15076
 
14884
15077
  # Editing files
14885
15078
 
14886
- When you've been asked to change a file, output one or more SEARCH/REPLACE blocks in this exact format:
15079
+ Default loop: inspect, patch, verify, summarize. After tests pass, summarize briefly and cite the files or commands that matter.
15080
+
15081
+ When you've been asked to change a file, prefer \`apply_patch\` for non-trivial edits. It accepts a unified git-style patch and lets the user review the whole batch at once:
15082
+
15083
+ \`\`\`diff
15084
+ diff --git a/path/to/file.ext b/path/to/file.ext
15085
+ --- a/path/to/file.ext
15086
+ +++ b/path/to/file.ext
15087
+ @@ -1 +1 @@
15088
+ -old line
15089
+ +new line
15090
+ \`\`\`
15091
+
15092
+ For tiny exact replacements, \`edit_file\` is fine. For generated programmatic batches, \`multi_edit\` is also fine. If you are writing text-form edits in your final answer instead of calling tools, output one or more SEARCH/REPLACE blocks in this exact format:
14887
15093
 
14888
15094
  path/to/file.ext
14889
15095
  <<<<<<< SEARCH
@@ -14903,7 +15109,7 @@ Rules:
14903
15109
  >>>>>>> REPLACE
14904
15110
  - Do NOT use write_file to change existing files \u2014 the user reviews your edits as SEARCH/REPLACE. write_file is only for files you explicitly want to overwrite wholesale (rare).
14905
15111
  - Paths are relative to the working directory. Don't use absolute paths.
14906
- - For multi-site changes \u2014 same file or across files \u2014 prefer \`multi_edit\` over N \`edit_file\` calls. Shape: \`{ edits: [{ path, search, replace }, ...] }\`. All edits validate before any file is written; any failure \u2192 ALL files untouched. Per-file edits run in array order, so a later edit can match text inserted by an earlier one.
15112
+ - For multi-site changes \u2014 same file or across files \u2014 prefer \`apply_patch\` or \`multi_edit\` over N \`edit_file\` calls. \`multi_edit\` shape: \`{ edits: [{ path, search, replace }, ...] }\`. Both batch tools validate before any file is written; any failure \u2192 ALL files untouched. Per-file edits run in array order, so a later edit can match text inserted by an earlier one. To create a new file inside a \`multi_edit\`, use an empty \`search\` for that file.
14907
15113
 
14908
15114
  # Trust what you already know
14909
15115
 
@@ -14912,7 +15118,7 @@ Before exploring the filesystem to answer a factual question, check whether the
14912
15118
  # Exploration
14913
15119
 
14914
15120
  - Skip dependency, build, and VCS directories unless the user explicitly asks. The pinned .gitignore block (if any, below) is your authoritative denylist.
14915
- - Prefer \`search_files\` over \`list_directory\` when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees. Note: \`search_files\` matches file NAMES; for searching file CONTENTS use \`search_content\`.
15121
+ - Prefer \`search_files\` over \`list_directory\` when you know roughly what you're looking for \u2014 it saves context and avoids enumerating huge trees. Use \`search_files\` for a filename query. Use \`glob\` for wildcard file patterns. Note: \`search_files\` matches file NAMES; for searching file CONTENTS use \`search_content\`.
14916
15122
  - Available exploration tools: \`read_file\`, \`list_directory\`, \`directory_tree\`, \`search_files\` (filename match), \`glob\` (mtime-sorted glob \u2014 use for "what changed lately", "all *.ts under src/"), \`search_content\` (content grep \u2014 use for "where is X called", "find all references to Y"; pass \`context:N\` for grep -C N around hits), \`get_file_info\`. Don't call \`grep\` or other tools that aren't in this list \u2014 they don't exist as functions.
14917
15123
 
14918
15124
  # Path conventions
@@ -14939,6 +15145,10 @@ You have TWO tools for running shell commands, and picking the right one is non-
14939
15145
 
14940
15146
  **Never use run_command for a dev server or a download likely to exceed a minute.** It will block, time out, and the user will see a frozen tool call while the work was actually running fine. Always \`run_background\` + \`wait_for_job\` / \`job_output\`.
14941
15147
 
15148
+ # Explicit command order
15149
+
15150
+ If the user explicitly asks you to run a command first (for example "\u5148\u8FD0\u884C\u6D4B\u8BD5", "run tests first", or "\u5148\u770B npm test \u5931\u8D25"), do that command before reading files or proposing edits. Do not substitute file reading, test-file inspection, or inference for the requested command execution. If the command is denied, say so and continue only within the user's constraint.
15151
+
14942
15152
  After \`run_background\`, tools available to you:
14943
15153
  - \`job_output(jobId, tailLines?)\` \u2014 read recent logs to verify startup / debug errors.
14944
15154
  - \`wait_for_job(jobId, timeoutMs?, waitFor?)\` \u2014 block server-side until the job finishes (or, with \`waitFor: 'output-or-exit'\`, until it writes a new line). ONE tool call per wait regardless of duration. \`timeoutMs\` clamps at 300_000. For downloads / installs / builds: leave \`waitFor\` at the default \`'exit'\` and set \`timeoutMs\` to the slowest reasonable end-to-end. For tailing a dev server and reacting to a specific log line: pass \`waitFor: 'output-or-exit'\` with a short \`timeoutMs\`.
@@ -15041,7 +15251,7 @@ import {
15041
15251
  writeFileSync as writeFileSync7
15042
15252
  } from "fs";
15043
15253
  import { homedir as homedir8 } from "os";
15044
- import { dirname as dirname9, join as join16 } from "path";
15254
+ import { dirname as dirname10, join as join16 } from "path";
15045
15255
  function defaultUsageLogPath(homeDirOverride) {
15046
15256
  return join16(homeDirOverride ?? homedir8(), ".carboncode", "usage.jsonl");
15047
15257
  }
@@ -15108,7 +15318,7 @@ function appendUsage(input) {
15108
15318
  if (input.subagent) record.subagent = input.subagent;
15109
15319
  const path2 = input.path ?? defaultUsageLogPath();
15110
15320
  try {
15111
- mkdirSync7(dirname9(path2), { recursive: true });
15321
+ mkdirSync7(dirname10(path2), { recursive: true });
15112
15322
  appendFileSync2(path2, `${JSON.stringify(record)}
15113
15323
  `, "utf8");
15114
15324
  compactUsageLogIfLarge(path2, record.ts);