@carboncode/cli 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -24
- package/README.zh-CN.md +13 -11
- package/dist/cli/{acp-35C4ME6Y.js → acp-E6QAGSG4.js} +17 -16
- package/dist/cli/acp-E6QAGSG4.js.map +1 -0
- package/dist/cli/{chat-A6UJDPGV.js → chat-6MBLB7MU.js} +21 -21
- package/dist/cli/{chunk-J5BYPUB5.js → chunk-2EKLWOE3.js} +794 -554
- package/dist/cli/chunk-2EKLWOE3.js.map +1 -0
- package/dist/cli/{chunk-UI66BH6D.js → chunk-2UXHZAXP.js} +2 -2
- package/dist/cli/{chunk-IX6XI2RG.js → chunk-3PNIUDTA.js} +2 -2
- package/dist/cli/{chunk-JKGYMRX5.js → chunk-676EW5HH.js} +2 -2
- package/dist/cli/{chunk-BSINVTTL.js → chunk-7R3PKZXB.js} +7 -7
- package/dist/cli/{chunk-3T6VBZCL.js → chunk-B2RA3NMS.js} +2 -2
- package/dist/cli/{chunk-CPKCNHRR.js → chunk-BYBOY5UY.js} +5 -5
- package/dist/cli/{chunk-3OAR6NVL.js → chunk-GG2V37EH.js} +2 -2
- package/dist/cli/{chunk-4IBIPQVB.js → chunk-J6UWUIT2.js} +3 -3
- package/dist/cli/{chunk-4TVNJWMA.js → chunk-LRXTF6NZ.js} +178 -18
- package/dist/cli/chunk-LRXTF6NZ.js.map +1 -0
- package/dist/cli/{chunk-T5TQ4NDT.js → chunk-MDQWQYBS.js} +3 -3
- package/dist/cli/{chunk-TH756VLN.js → chunk-NCMC7AMN.js} +240 -191
- package/dist/cli/chunk-NCMC7AMN.js.map +1 -0
- package/dist/cli/{chunk-ILJOIQ5W.js → chunk-NTF4IE7G.js} +2 -2
- package/dist/cli/{chunk-XJ5SRLKK.js → chunk-OYAIE6C7.js} +2 -2
- package/dist/cli/{chunk-BSGCXZQN.js → chunk-PT4UDK7Z.js} +2 -2
- package/dist/cli/{chunk-QJG7OF27.js → chunk-QLPHVU3W.js} +27 -10
- package/dist/cli/chunk-QLPHVU3W.js.map +1 -0
- package/dist/cli/{chunk-WRN65TRD.js → chunk-RLQQOIBS.js} +2 -2
- package/dist/cli/{chunk-QVC75MR3.js → chunk-U7RHC7D6.js} +2 -2
- package/dist/cli/{chunk-IAUOP25G.js → chunk-V6A26HU5.js} +34 -20
- package/dist/cli/chunk-V6A26HU5.js.map +1 -0
- package/dist/cli/{chunk-D5NFKRGO.js → chunk-VVQTSZWX.js} +2 -2
- package/dist/cli/{chunk-7L2WTRNU.js → chunk-X3IHKOYW.js} +2 -2
- package/dist/cli/{chunk-S2KIUQKQ.js → chunk-XJIF545V.js} +7 -6
- package/dist/cli/{chunk-S2KIUQKQ.js.map → chunk-XJIF545V.js.map} +1 -1
- package/dist/cli/{chunk-4MQ3VURH.js → chunk-ZADUQPQP.js} +24 -24
- package/dist/cli/chunk-ZADUQPQP.js.map +1 -0
- package/dist/cli/{code-4TUTAGO5.js → code-LBRSX6ZI.js} +24 -33
- package/dist/cli/code-LBRSX6ZI.js.map +1 -0
- package/dist/cli/{commands-KMOZEYCF.js → commands-PCHFC3CL.js} +4 -4
- package/dist/cli/{commit-DTFA56VQ.js → commit-HDN6VJBA.js} +3 -3
- package/dist/cli/{desktop-7N3MHNBD.js → desktop-RHWSCBHO.js} +17 -17
- package/dist/cli/{diff-E5OWTF4C.js → diff-MV5JNUH4.js} +8 -8
- package/dist/cli/{doctor-IEJQRJMN.js → doctor-QLO4V4DD.js} +8 -8
- package/dist/cli/index.js +32 -32
- package/dist/cli/{mcp-PDI2PDLG.js → mcp-JSHFAINM.js} +2 -2
- package/dist/cli/{mcp-browse-OSPXOFPZ.js → mcp-browse-ESMKKKYH.js} +2 -2
- package/dist/cli/{mcp-inspect-QRFVTHMF.js → mcp-inspect-WSUN36FM.js} +2 -2
- package/dist/cli/{prompt-3CDII3UO.js → prompt-5LMDCF4M.js} +3 -3
- package/dist/cli/{replay-HYOSRQIV.js → replay-D3ILR2YO.js} +8 -8
- package/dist/cli/{run-2ZHADOUP.js → run-6NN3P5JM.js} +13 -13
- package/dist/cli/{server-X75PAZG5.js → server-HE7LFAHH.js} +10 -10
- package/dist/cli/{sessions-POOZA5CQ.js → sessions-HXDBQM3V.js} +12 -12
- package/dist/cli/{setup-YLPFI3OH.js → setup-EP3UPG3F.js} +5 -5
- package/dist/cli/{stats-NXJ3TO2D.js → stats-5RC6P5TN.js} +6 -6
- package/dist/cli/{version-NXXWE3WN.js → version-AIR25TRN.js} +12 -12
- package/dist/index.d.ts +15 -2
- package/dist/index.js +328 -89
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/dist/cli/acp-35C4ME6Y.js.map +0 -1
- package/dist/cli/chunk-4MQ3VURH.js.map +0 -1
- package/dist/cli/chunk-4TVNJWMA.js.map +0 -1
- package/dist/cli/chunk-IAUOP25G.js.map +0 -1
- package/dist/cli/chunk-J5BYPUB5.js.map +0 -1
- package/dist/cli/chunk-QJG7OF27.js.map +0 -1
- package/dist/cli/chunk-TH756VLN.js.map +0 -1
- package/dist/cli/code-4TUTAGO5.js.map +0 -1
- /package/dist/cli/{chat-A6UJDPGV.js.map → chat-6MBLB7MU.js.map} +0 -0
- /package/dist/cli/{chunk-UI66BH6D.js.map → chunk-2UXHZAXP.js.map} +0 -0
- /package/dist/cli/{chunk-IX6XI2RG.js.map → chunk-3PNIUDTA.js.map} +0 -0
- /package/dist/cli/{chunk-JKGYMRX5.js.map → chunk-676EW5HH.js.map} +0 -0
- /package/dist/cli/{chunk-BSINVTTL.js.map → chunk-7R3PKZXB.js.map} +0 -0
- /package/dist/cli/{chunk-3T6VBZCL.js.map → chunk-B2RA3NMS.js.map} +0 -0
- /package/dist/cli/{chunk-CPKCNHRR.js.map → chunk-BYBOY5UY.js.map} +0 -0
- /package/dist/cli/{chunk-3OAR6NVL.js.map → chunk-GG2V37EH.js.map} +0 -0
- /package/dist/cli/{chunk-4IBIPQVB.js.map → chunk-J6UWUIT2.js.map} +0 -0
- /package/dist/cli/{chunk-T5TQ4NDT.js.map → chunk-MDQWQYBS.js.map} +0 -0
- /package/dist/cli/{chunk-ILJOIQ5W.js.map → chunk-NTF4IE7G.js.map} +0 -0
- /package/dist/cli/{chunk-XJ5SRLKK.js.map → chunk-OYAIE6C7.js.map} +0 -0
- /package/dist/cli/{chunk-BSGCXZQN.js.map → chunk-PT4UDK7Z.js.map} +0 -0
- /package/dist/cli/{chunk-WRN65TRD.js.map → chunk-RLQQOIBS.js.map} +0 -0
- /package/dist/cli/{chunk-QVC75MR3.js.map → chunk-U7RHC7D6.js.map} +0 -0
- /package/dist/cli/{chunk-D5NFKRGO.js.map → chunk-VVQTSZWX.js.map} +0 -0
- /package/dist/cli/{chunk-7L2WTRNU.js.map → chunk-X3IHKOYW.js.map} +0 -0
- /package/dist/cli/{commands-KMOZEYCF.js.map → commands-PCHFC3CL.js.map} +0 -0
- /package/dist/cli/{commit-DTFA56VQ.js.map → commit-HDN6VJBA.js.map} +0 -0
- /package/dist/cli/{desktop-7N3MHNBD.js.map → desktop-RHWSCBHO.js.map} +0 -0
- /package/dist/cli/{diff-E5OWTF4C.js.map → diff-MV5JNUH4.js.map} +0 -0
- /package/dist/cli/{doctor-IEJQRJMN.js.map → doctor-QLO4V4DD.js.map} +0 -0
- /package/dist/cli/{mcp-PDI2PDLG.js.map → mcp-JSHFAINM.js.map} +0 -0
- /package/dist/cli/{mcp-browse-OSPXOFPZ.js.map → mcp-browse-ESMKKKYH.js.map} +0 -0
- /package/dist/cli/{mcp-inspect-QRFVTHMF.js.map → mcp-inspect-WSUN36FM.js.map} +0 -0
- /package/dist/cli/{prompt-3CDII3UO.js.map → prompt-5LMDCF4M.js.map} +0 -0
- /package/dist/cli/{replay-HYOSRQIV.js.map → replay-D3ILR2YO.js.map} +0 -0
- /package/dist/cli/{run-2ZHADOUP.js.map → run-6NN3P5JM.js.map} +0 -0
- /package/dist/cli/{server-X75PAZG5.js.map → server-HE7LFAHH.js.map} +0 -0
- /package/dist/cli/{sessions-POOZA5CQ.js.map → sessions-HXDBQM3V.js.map} +0 -0
- /package/dist/cli/{setup-YLPFI3OH.js.map → setup-EP3UPG3F.js.map} +0 -0
- /package/dist/cli/{stats-NXJ3TO2D.js.map → stats-5RC6P5TN.js.map} +0 -0
- /package/dist/cli/{version-NXXWE3WN.js.map → version-AIR25TRN.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: "#
|
|
37
|
-
body: "#
|
|
38
|
-
sub: "#
|
|
39
|
-
meta: "#
|
|
40
|
-
faint: "#
|
|
36
|
+
strong: "#f8fafc",
|
|
37
|
+
body: "#e5e7eb",
|
|
38
|
+
sub: "#a1a1aa",
|
|
39
|
+
meta: "#71717a",
|
|
40
|
+
faint: "#52525b"
|
|
41
41
|
},
|
|
42
42
|
tone: {
|
|
43
|
-
brand: "#
|
|
44
|
-
accent: "#
|
|
45
|
-
violet: "#
|
|
46
|
-
ok: "#
|
|
47
|
-
warn: "#
|
|
48
|
-
err: "#
|
|
49
|
-
info: "#
|
|
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: "#
|
|
53
|
-
accent: "#
|
|
54
|
-
violet: "#
|
|
55
|
-
ok: "#
|
|
56
|
-
warn: "#
|
|
57
|
-
err: "#
|
|
58
|
-
info: "#
|
|
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: "#
|
|
62
|
-
bgInput: "#
|
|
63
|
-
bgCode: "#
|
|
64
|
-
bgElev: "#
|
|
61
|
+
bg: "#09090b",
|
|
62
|
+
bgInput: "#111113",
|
|
63
|
+
bgCode: "#050507",
|
|
64
|
+
bgElev: "#18181b"
|
|
65
65
|
}
|
|
66
66
|
});
|
|
67
67
|
var dark = defineTheme({
|
|
@@ -1139,13 +1139,16 @@ var EN = {
|
|
|
1139
1139
|
},
|
|
1140
1140
|
ui: {
|
|
1141
1141
|
welcome: "Run `carboncode` any time to start chatting \u2014 your settings are remembered.",
|
|
1142
|
-
taglineChat: "
|
|
1143
|
-
taglineCode: "
|
|
1144
|
-
taglineSub: "
|
|
1145
|
-
startSessionHint: "type a
|
|
1142
|
+
taglineChat: "terminal coding agent",
|
|
1143
|
+
taglineCode: "terminal coding agent",
|
|
1144
|
+
taglineSub: "read the repo, edit files, run validation",
|
|
1145
|
+
startSessionHint: "type a task to start",
|
|
1146
1146
|
inputPlaceholder: "Ask anything... (type / for commands, @ for files)",
|
|
1147
1147
|
busy: "Thinking...",
|
|
1148
1148
|
thinking: "\u25B8 thinking...",
|
|
1149
|
+
activityWaitingForModel: "waiting for model\u2026",
|
|
1150
|
+
activityThinking: "thinking\u2026",
|
|
1151
|
+
activityProcessing: "processing\u2026",
|
|
1149
1152
|
undo: "Undo",
|
|
1150
1153
|
undoHint: "press u within 5s to undo",
|
|
1151
1154
|
applied: "applied",
|
|
@@ -1623,6 +1626,7 @@ var EN = {
|
|
|
1623
1626
|
app: {
|
|
1624
1627
|
walkCancelledRemaining: "\u25B8 walk cancelled \u2014 {count} block(s) still pending.",
|
|
1625
1628
|
walkCancelled: "\u25B8 walk cancelled.",
|
|
1629
|
+
turnInterrupted: "\u25B8 turn interrupted \u2014 type a follow-up to continue.",
|
|
1626
1630
|
editModeYolo: "\u25B8 edit mode: YOLO \u2014 edits AND shell commands auto-run. /undo still rolls back edits. Use carefully.",
|
|
1627
1631
|
editModeAuto: "\u25B8 edit mode: AUTO \u2014 edits apply immediately; press u within 5s to undo (space pauses the timer). Shell commands still ask.",
|
|
1628
1632
|
editModeReview: "\u25B8 edit mode: review \u2014 edits queue for /apply (or y) / /discard (or n)",
|
|
@@ -2103,10 +2107,13 @@ var EN = {
|
|
|
2103
2107
|
queuedApplyDiscard: "{count} queued \xB7 y apply \xB7 n discard",
|
|
2104
2108
|
editsQueued: "edits queued \xB7 y apply \xB7 n discard",
|
|
2105
2109
|
shiftTabFlip: " {mid} \xB7 Shift+Tab to flip",
|
|
2106
|
-
queuedDots: "queued\u2026"
|
|
2110
|
+
queuedDots: "queued\u2026",
|
|
2111
|
+
undoApplied: "applied {ok}/{total}",
|
|
2112
|
+
undoHint: "u undo \xB7 Space pause",
|
|
2113
|
+
undoPausedHint: "u undo \xB7 Space resume"
|
|
2107
2114
|
},
|
|
2108
2115
|
composer: {
|
|
2109
|
-
placeholder: "
|
|
2116
|
+
placeholder: "Ask for a code change",
|
|
2110
2117
|
waitingForResponse: "\u2026waiting for response\u2026",
|
|
2111
2118
|
hintSend: "send",
|
|
2112
2119
|
hintNewline: "newline",
|
|
@@ -2130,7 +2137,7 @@ var EN = {
|
|
|
2130
2137
|
denyTitle: "Deny \u2014 provide context",
|
|
2131
2138
|
optional: "optional",
|
|
2132
2139
|
denyFooter: "type context \xB7 \u23CE submit with reason \xB7 esc skip (deny without reason)",
|
|
2133
|
-
pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7
|
|
2140
|
+
pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 esc cancel",
|
|
2134
2141
|
allowOnce: "allow once",
|
|
2135
2142
|
allowOnceDesc: "permit this access; remember the directory for the rest of this session",
|
|
2136
2143
|
allowAlways: "allow always",
|
|
@@ -2150,7 +2157,7 @@ var EN = {
|
|
|
2150
2157
|
optional: "optional",
|
|
2151
2158
|
denyFooter: "type context \xB7 \u23CE submit with reason \xB7 esc skip (deny without reason)",
|
|
2152
2159
|
awaiting: "awaiting",
|
|
2153
|
-
pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7
|
|
2160
|
+
pickFooter: "\u2191\u2193 pick \xB7 \u23CE confirm \xB7 esc cancel",
|
|
2154
2161
|
allowOnce: "allow once",
|
|
2155
2162
|
allowOnceDesc: "run this command, ask again next time",
|
|
2156
2163
|
allowAlways: "allow always",
|
|
@@ -2164,7 +2171,7 @@ var EN = {
|
|
|
2164
2171
|
previewMorePlural: "\u2026 {n} more lines hidden \u2014 press esc, ask the model to split it"
|
|
2165
2172
|
},
|
|
2166
2173
|
editConfirm: {
|
|
2167
|
-
footer: "
|
|
2174
|
+
footer: "Enter apply \xB7 n reject \xB7 \u2191\u2193 scroll \xB7 Esc cancel",
|
|
2168
2175
|
newTag: "NEW",
|
|
2169
2176
|
editTag: "EDIT",
|
|
2170
2177
|
linesCount: "-{removed} +{added} lines",
|
|
@@ -2583,13 +2590,16 @@ var zhCN = {
|
|
|
2583
2590
|
},
|
|
2584
2591
|
ui: {
|
|
2585
2592
|
welcome: "\u968F\u65F6\u8FD0\u884C `carboncode` \u5F00\u59CB\u804A\u5929 \u2014 \u60A8\u7684\u8BBE\u7F6E\u5C06\u88AB\u8BB0\u4F4F\u3002",
|
|
2586
|
-
taglineChat: "
|
|
2587
|
-
taglineCode: "
|
|
2588
|
-
taglineSub: "\
|
|
2589
|
-
startSessionHint: "\u8F93\u5165\
|
|
2593
|
+
taglineChat: "\u7EC8\u7AEF\u4EE3\u7801\u667A\u80FD\u4F53",
|
|
2594
|
+
taglineCode: "\u7EC8\u7AEF\u4EE3\u7801\u667A\u80FD\u4F53",
|
|
2595
|
+
taglineSub: "\u8BFB\u53D6\u4ED3\u5E93\u3001\u7F16\u8F91\u6587\u4EF6\u3001\u8FD0\u884C\u9A8C\u8BC1",
|
|
2596
|
+
startSessionHint: "\u8F93\u5165\u4EFB\u52A1\u5F00\u59CB",
|
|
2590
2597
|
inputPlaceholder: "\u8F93\u5165\u4EFB\u4F55\u5185\u5BB9... (\u8F93\u5165 / \u4F7F\u7528\u547D\u4EE4, @ \u5F15\u7528\u6587\u4EF6)",
|
|
2591
2598
|
busy: "\u601D\u8003\u4E2D...",
|
|
2592
2599
|
thinking: "\u25B8 \u601D\u8003\u4E2D...",
|
|
2600
|
+
activityWaitingForModel: "\u7B49\u5F85\u6A21\u578B\u2026",
|
|
2601
|
+
activityThinking: "\u601D\u8003\u4E2D\u2026",
|
|
2602
|
+
activityProcessing: "\u5904\u7406\u4E2D\u2026",
|
|
2593
2603
|
undo: "\u64A4\u6D88",
|
|
2594
2604
|
undoHint: "\u5728 5 \u79D2\u5185\u6309 u \u64A4\u6D88",
|
|
2595
2605
|
applied: "\u5DF2\u5E94\u7528",
|
|
@@ -3068,6 +3078,7 @@ var zhCN = {
|
|
|
3068
3078
|
app: {
|
|
3069
3079
|
walkCancelledRemaining: "\u25B8 \u6D4F\u89C8\u5DF2\u53D6\u6D88 \u2014 \u8FD8\u6709 {count} \u4E2A\u5F85\u5904\u7406\u7F16\u8F91\u5757\u3002",
|
|
3070
3080
|
walkCancelled: "\u25B8 \u6D4F\u89C8\u5DF2\u53D6\u6D88\u3002",
|
|
3081
|
+
turnInterrupted: "\u25B8 \u672C\u8F6E\u5DF2\u4E2D\u65AD \u2014 \u8F93\u5165\u540E\u7EED\u5185\u5BB9\u53EF\u7EE7\u7EED\u3002",
|
|
3071
3082
|
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
3083
|
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
3084
|
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 +3559,13 @@ var zhCN = {
|
|
|
3548
3559
|
queuedApplyDiscard: "{count} \u4E2A\u5F85\u5904\u7406 \xB7 y \u5E94\u7528 \xB7 n \u4E22\u5F03",
|
|
3549
3560
|
editsQueued: "\u7F16\u8F91\u5DF2\u6392\u961F \xB7 y \u5E94\u7528 \xB7 n \u4E22\u5F03",
|
|
3550
3561
|
shiftTabFlip: " {mid} \xB7 Shift+Tab \u5207\u6362",
|
|
3551
|
-
queuedDots: "\u6392\u961F\u4E2D\u2026"
|
|
3562
|
+
queuedDots: "\u6392\u961F\u4E2D\u2026",
|
|
3563
|
+
undoApplied: "\u5DF2\u5E94\u7528 {ok}/{total}",
|
|
3564
|
+
undoHint: "u \u64A4\u6D88 \xB7 Space \u6682\u505C",
|
|
3565
|
+
undoPausedHint: "u \u64A4\u6D88 \xB7 Space \u7EE7\u7EED"
|
|
3552
3566
|
},
|
|
3553
3567
|
composer: {
|
|
3554
|
-
placeholder: "\u8F93\u5165\u4EFB\
|
|
3568
|
+
placeholder: "\u8F93\u5165\u4EFB\u52A1",
|
|
3555
3569
|
waitingForResponse: "\u2026\u7B49\u5F85\u54CD\u5E94\u2026",
|
|
3556
3570
|
hintSend: "\u53D1\u9001",
|
|
3557
3571
|
hintNewline: "\u6362\u884C",
|
|
@@ -3575,7 +3589,7 @@ var zhCN = {
|
|
|
3575
3589
|
denyTitle: "\u62D2\u7EDD \u2014 \u63D0\u4F9B\u539F\u56E0",
|
|
3576
3590
|
optional: "\u53EF\u9009",
|
|
3577
3591
|
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
|
|
3592
|
+
pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Esc \u53D6\u6D88",
|
|
3579
3593
|
allowOnce: "\u5141\u8BB8\u4E00\u6B21",
|
|
3580
3594
|
allowOnceDesc: "\u672C\u6B21\u5141\u8BB8\uFF0C\u672C\u4F1A\u8BDD\u5185\u6B64\u76EE\u5F55\u4E0D\u518D\u8BE2\u95EE",
|
|
3581
3595
|
allowAlways: "\u59CB\u7EC8\u5141\u8BB8",
|
|
@@ -3595,7 +3609,7 @@ var zhCN = {
|
|
|
3595
3609
|
optional: "\u53EF\u9009",
|
|
3596
3610
|
denyFooter: "\u8F93\u5165\u539F\u56E0 \xB7 \u23CE \u63D0\u4EA4 \xB7 Esc \u8DF3\u8FC7\uFF08\u76F4\u63A5\u62D2\u7EDD\uFF09",
|
|
3597
3611
|
awaiting: "\u7B49\u5F85\u4E2D",
|
|
3598
|
-
pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7
|
|
3612
|
+
pickFooter: "\u2191\u2193 \u9009\u62E9 \xB7 \u23CE \u786E\u8BA4 \xB7 Esc \u53D6\u6D88",
|
|
3599
3613
|
allowOnce: "\u5141\u8BB8\u4E00\u6B21",
|
|
3600
3614
|
allowOnceDesc: "\u6267\u884C\u6B64\u547D\u4EE4\uFF0C\u4E0B\u6B21\u518D\u95EE",
|
|
3601
3615
|
allowAlways: "\u59CB\u7EC8\u5141\u8BB8",
|
|
@@ -3609,7 +3623,7 @@ var zhCN = {
|
|
|
3609
3623
|
previewMorePlural: "\u2026 \u8FD8\u6709 {n} \u884C\u672A\u663E\u793A \u2014 \u6309 esc \u53D6\u6D88\uFF0C\u8BA9\u6A21\u578B\u62C6\u5206\u540E\u518D\u8BD5"
|
|
3610
3624
|
},
|
|
3611
3625
|
editConfirm: {
|
|
3612
|
-
footer: "
|
|
3626
|
+
footer: "Enter \u5E94\u7528 \xB7 n \u62D2\u7EDD \xB7 \u2191\u2193 \u6EDA\u52A8 \xB7 Esc \u53D6\u6D88",
|
|
3613
3627
|
newTag: "\u65B0\u589E",
|
|
3614
3628
|
editTag: "\u7F16\u8F91",
|
|
3615
3629
|
linesCount: "-{removed} +{added} \u884C",
|
|
@@ -4785,9 +4799,14 @@ var ToolRegistry = class {
|
|
|
4785
4799
|
rejectedReason: "plan-mode"
|
|
4786
4800
|
});
|
|
4787
4801
|
}
|
|
4802
|
+
const dispatchCtx = {
|
|
4803
|
+
signal: opts.signal,
|
|
4804
|
+
confirmationGate: opts.confirmationGate,
|
|
4805
|
+
onInteractiveWait: opts.onInteractiveWait
|
|
4806
|
+
};
|
|
4788
4807
|
if (this._interceptor) {
|
|
4789
4808
|
try {
|
|
4790
|
-
const short = await this._interceptor(name, args);
|
|
4809
|
+
const short = await this._interceptor(name, args, dispatchCtx);
|
|
4791
4810
|
if (typeof short === "string") return short;
|
|
4792
4811
|
} catch (err) {
|
|
4793
4812
|
return JSON.stringify({
|
|
@@ -4801,10 +4820,7 @@ var ToolRegistry = class {
|
|
|
4801
4820
|
this._auditListener?.({ name, args });
|
|
4802
4821
|
} catch {
|
|
4803
4822
|
}
|
|
4804
|
-
const result = await tool.fn(args,
|
|
4805
|
-
signal: opts.signal,
|
|
4806
|
-
confirmationGate: opts.confirmationGate
|
|
4807
|
-
});
|
|
4823
|
+
const result = await tool.fn(args, dispatchCtx);
|
|
4808
4824
|
const str = typeof result === "string" ? result : JSON.stringify(result);
|
|
4809
4825
|
let clipped = str;
|
|
4810
4826
|
if (opts.maxResultTokens !== void 0) {
|
|
@@ -6834,6 +6850,8 @@ var CacheFirstLoop = class {
|
|
|
6834
6850
|
const name = call.function?.name ?? "";
|
|
6835
6851
|
const args = call.function?.arguments ?? "{}";
|
|
6836
6852
|
const parsedArgs = safeParseToolArgs(args);
|
|
6853
|
+
const startedAt = Date.now();
|
|
6854
|
+
let interactiveWaitMs = 0;
|
|
6837
6855
|
this._inflight.add(this.inflightIdFor(call));
|
|
6838
6856
|
try {
|
|
6839
6857
|
const preReport = await runHooks({
|
|
@@ -6853,13 +6871,17 @@ var CacheFirstLoop = class {
|
|
|
6853
6871
|
preWarnings,
|
|
6854
6872
|
postWarnings: [],
|
|
6855
6873
|
result: `[hook block] ${blocking?.hook.command ?? "<unknown>"}
|
|
6856
|
-
${reason}
|
|
6874
|
+
${reason}`,
|
|
6875
|
+
elapsedMs: Date.now() - startedAt
|
|
6857
6876
|
};
|
|
6858
6877
|
}
|
|
6859
6878
|
const result = await this.tools.dispatch(name, args, {
|
|
6860
6879
|
signal,
|
|
6861
6880
|
maxResultTokens: DEFAULT_MAX_RESULT_TOKENS,
|
|
6862
|
-
confirmationGate: this.confirmationGate
|
|
6881
|
+
confirmationGate: this.confirmationGate,
|
|
6882
|
+
onInteractiveWait: (elapsedMs) => {
|
|
6883
|
+
interactiveWaitMs += Math.max(0, elapsedMs);
|
|
6884
|
+
}
|
|
6863
6885
|
});
|
|
6864
6886
|
const postReport = await runHooks({
|
|
6865
6887
|
hooks: this.hooks,
|
|
@@ -6872,7 +6894,12 @@ ${reason}`
|
|
|
6872
6894
|
}
|
|
6873
6895
|
});
|
|
6874
6896
|
const postWarnings = [...hookWarnings(postReport.outcomes, this._turn)];
|
|
6875
|
-
return {
|
|
6897
|
+
return {
|
|
6898
|
+
preWarnings,
|
|
6899
|
+
postWarnings,
|
|
6900
|
+
result,
|
|
6901
|
+
elapsedMs: Math.max(0, Date.now() - startedAt - interactiveWaitMs)
|
|
6902
|
+
};
|
|
6876
6903
|
} finally {
|
|
6877
6904
|
this._inflight.delete(this.inflightIdFor(call));
|
|
6878
6905
|
}
|
|
@@ -7357,12 +7384,14 @@ ${reason}`
|
|
|
7357
7384
|
const args = call.function?.arguments ?? "{}";
|
|
7358
7385
|
const s = settled[k];
|
|
7359
7386
|
let result;
|
|
7387
|
+
let elapsedMs = 0;
|
|
7360
7388
|
let preWarnings = [];
|
|
7361
7389
|
let postWarnings = [];
|
|
7362
7390
|
if (s.status === "fulfilled") {
|
|
7363
7391
|
preWarnings = s.value.preWarnings;
|
|
7364
7392
|
postWarnings = s.value.postWarnings;
|
|
7365
7393
|
result = s.value.result;
|
|
7394
|
+
elapsedMs = s.value.elapsedMs;
|
|
7366
7395
|
} else {
|
|
7367
7396
|
const err = s.reason instanceof Error ? s.reason : new Error(String(s.reason));
|
|
7368
7397
|
result = JSON.stringify({ error: `${err.name}: ${err.message}` });
|
|
@@ -7381,7 +7410,8 @@ ${reason}`
|
|
|
7381
7410
|
content: result,
|
|
7382
7411
|
toolName: name,
|
|
7383
7412
|
toolArgs: args,
|
|
7384
|
-
callId: this.inflightIdFor(call)
|
|
7413
|
+
callId: this.inflightIdFor(call),
|
|
7414
|
+
elapsedMs
|
|
7385
7415
|
};
|
|
7386
7416
|
}
|
|
7387
7417
|
}
|
|
@@ -8931,27 +8961,51 @@ async function applyMultiEdit(rootDir, edits) {
|
|
|
8931
8961
|
);
|
|
8932
8962
|
}
|
|
8933
8963
|
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
8964
|
let state = filesByPath.get(e.abs);
|
|
8940
8965
|
if (!state) {
|
|
8941
8966
|
let before;
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
|
|
8946
|
-
|
|
8947
|
-
|
|
8967
|
+
if (e.search.length === 0) {
|
|
8968
|
+
try {
|
|
8969
|
+
await fs.readFile(e.abs, "utf8");
|
|
8970
|
+
throw new Error("empty SEARCH only creates new files \u2014 this file already exists");
|
|
8971
|
+
} catch (err) {
|
|
8972
|
+
const code = err.code;
|
|
8973
|
+
if (code !== "ENOENT") {
|
|
8974
|
+
throw new Error(
|
|
8975
|
+
`multi_edit: edit #${i + 1} cannot create ${rel}: ${err.message} (no edits applied)`
|
|
8976
|
+
);
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
state = { buf: "", le: "\n", hunks: [], deltaChars: 0, touched: 0, created: true };
|
|
8980
|
+
filesByPath.set(e.abs, state);
|
|
8981
|
+
} else {
|
|
8982
|
+
try {
|
|
8983
|
+
before = await fs.readFile(e.abs, "utf8");
|
|
8984
|
+
} catch (err) {
|
|
8985
|
+
throw new Error(
|
|
8986
|
+
`multi_edit: edit #${i + 1} cannot read ${rel}: ${err.message} (no edits applied)`
|
|
8987
|
+
);
|
|
8988
|
+
}
|
|
8989
|
+
const le = before.includes("\r\n") ? "\r\n" : "\n";
|
|
8990
|
+
state = { buf: before, le, hunks: [], deltaChars: 0, touched: 0, created: false };
|
|
8991
|
+
filesByPath.set(e.abs, state);
|
|
8948
8992
|
}
|
|
8949
|
-
|
|
8950
|
-
|
|
8951
|
-
|
|
8993
|
+
}
|
|
8994
|
+
if (e.search.length === 0 && (!state.created || state.touched > 0)) {
|
|
8995
|
+
throw new Error(
|
|
8996
|
+
`multi_edit: edit #${i + 1} (${rel}) empty search only creates new files (no edits applied)`
|
|
8997
|
+
);
|
|
8952
8998
|
}
|
|
8953
8999
|
const adaptedSearch = e.search.replace(/\r?\n/g, state.le);
|
|
8954
9000
|
const adaptedReplace = e.replace.replace(/\r?\n/g, state.le);
|
|
9001
|
+
if (adaptedSearch.length === 0) {
|
|
9002
|
+
state.buf = adaptedReplace;
|
|
9003
|
+
state.hunks.push(`# ${rel}
|
|
9004
|
+
${renderCreateDiff(adaptedReplace)}`);
|
|
9005
|
+
state.deltaChars += adaptedReplace.length;
|
|
9006
|
+
state.touched++;
|
|
9007
|
+
continue;
|
|
9008
|
+
}
|
|
8955
9009
|
const firstIdx = state.buf.indexOf(adaptedSearch);
|
|
8956
9010
|
if (firstIdx < 0) {
|
|
8957
9011
|
throw new Error(
|
|
@@ -8972,6 +9026,7 @@ ${renderEditDiff(adaptedSearch, adaptedReplace, startLine)}`);
|
|
|
8972
9026
|
state.touched++;
|
|
8973
9027
|
}
|
|
8974
9028
|
for (const [abs, state] of filesByPath) {
|
|
9029
|
+
if (state.created) await fs.mkdir(pathMod.dirname(abs), { recursive: true });
|
|
8975
9030
|
await fs.writeFile(abs, state.buf, "utf8");
|
|
8976
9031
|
}
|
|
8977
9032
|
const fileCount = filesByPath.size;
|
|
@@ -8998,6 +9053,13 @@ function renderEditDiff(search, replace, startLine) {
|
|
|
8998
9053
|
return `${hunk}
|
|
8999
9054
|
${body}`;
|
|
9000
9055
|
}
|
|
9056
|
+
function renderCreateDiff(replace) {
|
|
9057
|
+
const lines = replace.length === 0 ? [] : replace.split(/\r?\n/);
|
|
9058
|
+
const hunk = `@@ -1,0 +1,${lines.length} @@`;
|
|
9059
|
+
const body = lines.map((line) => `+ ${line}`).join("\n");
|
|
9060
|
+
return body ? `${hunk}
|
|
9061
|
+
${body}` : hunk;
|
|
9062
|
+
}
|
|
9001
9063
|
function lineDiff(a, b) {
|
|
9002
9064
|
const n = a.length;
|
|
9003
9065
|
const m = b.length;
|
|
@@ -9281,6 +9343,110 @@ function formatOutline(entries) {
|
|
|
9281
9343
|
].join("\n");
|
|
9282
9344
|
}
|
|
9283
9345
|
|
|
9346
|
+
// src/tools/fs/patch.ts
|
|
9347
|
+
function parseUnifiedPatch(patch) {
|
|
9348
|
+
if (typeof patch !== "string" || patch.trim().length === 0) {
|
|
9349
|
+
throw new Error("apply_patch: patch must be a non-empty string");
|
|
9350
|
+
}
|
|
9351
|
+
const lines = patch.split(/\n/);
|
|
9352
|
+
const files = [];
|
|
9353
|
+
let current = null;
|
|
9354
|
+
const ensureCurrent = () => {
|
|
9355
|
+
if (!current) {
|
|
9356
|
+
current = { oldPath: null, newPath: null, blocks: [] };
|
|
9357
|
+
files.push(current);
|
|
9358
|
+
}
|
|
9359
|
+
return current;
|
|
9360
|
+
};
|
|
9361
|
+
let i = 0;
|
|
9362
|
+
while (i < lines.length) {
|
|
9363
|
+
const line = lines[i];
|
|
9364
|
+
if (line.startsWith("diff --git ")) {
|
|
9365
|
+
current = { oldPath: null, newPath: null, blocks: [] };
|
|
9366
|
+
files.push(current);
|
|
9367
|
+
i++;
|
|
9368
|
+
continue;
|
|
9369
|
+
}
|
|
9370
|
+
if (line.startsWith("--- ")) {
|
|
9371
|
+
ensureCurrent().oldPath = parsePatchPath(line.slice(4));
|
|
9372
|
+
i++;
|
|
9373
|
+
continue;
|
|
9374
|
+
}
|
|
9375
|
+
if (line.startsWith("+++ ")) {
|
|
9376
|
+
ensureCurrent().newPath = parsePatchPath(line.slice(4));
|
|
9377
|
+
i++;
|
|
9378
|
+
continue;
|
|
9379
|
+
}
|
|
9380
|
+
if (line.startsWith("@@")) {
|
|
9381
|
+
const file = ensureCurrent();
|
|
9382
|
+
const path2 = file.newPath ?? file.oldPath;
|
|
9383
|
+
if (!path2) throw new Error("apply_patch: hunk is missing a file path");
|
|
9384
|
+
const searchLines = [];
|
|
9385
|
+
const replaceLines = [];
|
|
9386
|
+
i++;
|
|
9387
|
+
while (i < lines.length) {
|
|
9388
|
+
const hunkLine = lines[i];
|
|
9389
|
+
if (hunkLine.startsWith("diff --git ") || hunkLine.startsWith("@@") || hunkLine.startsWith("--- ")) {
|
|
9390
|
+
break;
|
|
9391
|
+
}
|
|
9392
|
+
if (hunkLine === "" && i === lines.length - 1) {
|
|
9393
|
+
break;
|
|
9394
|
+
}
|
|
9395
|
+
if (hunkLine.startsWith("\")) {
|
|
9396
|
+
i++;
|
|
9397
|
+
continue;
|
|
9398
|
+
}
|
|
9399
|
+
const marker = hunkLine[0];
|
|
9400
|
+
const body = hunkLine.slice(1);
|
|
9401
|
+
if (marker === " ") {
|
|
9402
|
+
searchLines.push(body);
|
|
9403
|
+
replaceLines.push(body);
|
|
9404
|
+
} else if (marker === "-") {
|
|
9405
|
+
searchLines.push(body);
|
|
9406
|
+
} else if (marker === "+") {
|
|
9407
|
+
replaceLines.push(body);
|
|
9408
|
+
} else if (hunkLine.trim().length === 0) {
|
|
9409
|
+
} else {
|
|
9410
|
+
throw new Error(`apply_patch: invalid hunk line: ${hunkLine}`);
|
|
9411
|
+
}
|
|
9412
|
+
i++;
|
|
9413
|
+
}
|
|
9414
|
+
file.blocks.push({
|
|
9415
|
+
path: path2,
|
|
9416
|
+
search: file.oldPath === null ? "" : linesToText(searchLines),
|
|
9417
|
+
replace: linesToText(replaceLines),
|
|
9418
|
+
offset: 0
|
|
9419
|
+
});
|
|
9420
|
+
continue;
|
|
9421
|
+
}
|
|
9422
|
+
i++;
|
|
9423
|
+
}
|
|
9424
|
+
const blocks = files.flatMap((file) => file.blocks);
|
|
9425
|
+
if (blocks.length === 0) throw new Error("apply_patch: patch did not contain any hunks");
|
|
9426
|
+
return blocks;
|
|
9427
|
+
}
|
|
9428
|
+
function linesToText(lines) {
|
|
9429
|
+
if (lines.length === 0) return "";
|
|
9430
|
+
return `${lines.join("\n")}
|
|
9431
|
+
`;
|
|
9432
|
+
}
|
|
9433
|
+
function parsePatchPath(raw) {
|
|
9434
|
+
const cleaned = unquotePath(raw.trim().split(/\t/, 1)[0] ?? "");
|
|
9435
|
+
if (cleaned === "/dev/null") return null;
|
|
9436
|
+
const withoutPrefix = cleaned.replace(/^[ab]\//, "");
|
|
9437
|
+
const normalized = withoutPrefix.replace(/^[/\\]+/, "");
|
|
9438
|
+
if (!normalized) throw new Error(`apply_patch: invalid patch path: ${raw}`);
|
|
9439
|
+
return normalized.replaceAll("\\", "/");
|
|
9440
|
+
}
|
|
9441
|
+
function unquotePath(path2) {
|
|
9442
|
+
if (path2.length < 2 || !path2.startsWith('"') || !path2.endsWith('"')) return path2;
|
|
9443
|
+
try {
|
|
9444
|
+
return JSON.parse(path2);
|
|
9445
|
+
} catch {
|
|
9446
|
+
return path2.slice(1, -1);
|
|
9447
|
+
}
|
|
9448
|
+
}
|
|
9449
|
+
|
|
9284
9450
|
// src/tools/fs/search.ts
|
|
9285
9451
|
import { promises as fs3 } from "fs";
|
|
9286
9452
|
import * as pathMod4 from "path";
|
|
@@ -9578,10 +9744,14 @@ ${body}`;
|
|
|
9578
9744
|
let pending = inflightGate.get(allowPrefix);
|
|
9579
9745
|
if (!pending) {
|
|
9580
9746
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
9747
|
+
const waitStartedAt = Date.now();
|
|
9581
9748
|
pending = gate.ask({
|
|
9582
9749
|
kind: "path_access",
|
|
9583
9750
|
payload: { path: abs, intent, toolName, sandboxRoot: normRoot, allowPrefix }
|
|
9584
9751
|
});
|
|
9752
|
+
pending = pending.finally(() => {
|
|
9753
|
+
ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
|
|
9754
|
+
});
|
|
9585
9755
|
inflightGate.set(allowPrefix, pending);
|
|
9586
9756
|
void pending.finally(() => inflightGate.delete(allowPrefix));
|
|
9587
9757
|
}
|
|
@@ -10001,7 +10171,7 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
10001
10171
|
});
|
|
10002
10172
|
registry.register({
|
|
10003
10173
|
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,
|
|
10174
|
+
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
10175
|
parameters: {
|
|
10006
10176
|
type: "object",
|
|
10007
10177
|
properties: {
|
|
@@ -10038,6 +10208,41 @@ Prefer \`list_directory\` for a single-level view, \`search_files\` to find spec
|
|
|
10038
10208
|
return applyMultiEdit(rootDir, resolved);
|
|
10039
10209
|
}
|
|
10040
10210
|
});
|
|
10211
|
+
registry.register({
|
|
10212
|
+
name: "apply_patch",
|
|
10213
|
+
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.",
|
|
10214
|
+
parameters: {
|
|
10215
|
+
type: "object",
|
|
10216
|
+
properties: {
|
|
10217
|
+
patch: {
|
|
10218
|
+
type: "string",
|
|
10219
|
+
description: "Unified diff text with diff --git / --- / +++ headers and @@ hunks. Paths should be project-relative or a/ b/-prefixed."
|
|
10220
|
+
}
|
|
10221
|
+
},
|
|
10222
|
+
required: ["patch"]
|
|
10223
|
+
},
|
|
10224
|
+
fn: async (args, ctx) => {
|
|
10225
|
+
try {
|
|
10226
|
+
const blocks = parseUnifiedPatch(args.patch);
|
|
10227
|
+
const resolved = await Promise.all(
|
|
10228
|
+
blocks.map(async (block) => ({
|
|
10229
|
+
abs: await safePath(block.path, "apply_patch", ctx, "write"),
|
|
10230
|
+
search: block.search,
|
|
10231
|
+
replace: block.replace,
|
|
10232
|
+
rel: block.path,
|
|
10233
|
+
created: block.search.length === 0
|
|
10234
|
+
}))
|
|
10235
|
+
);
|
|
10236
|
+
const output = await applyMultiEdit(rootDir, resolved);
|
|
10237
|
+
const fileCount = new Set(resolved.map((edit) => edit.rel)).size;
|
|
10238
|
+
const fileNoun = fileCount === 1 ? "file" : "files";
|
|
10239
|
+
const created = resolved.filter((edit) => edit.created).map((edit) => `created ${edit.rel}`);
|
|
10240
|
+
return [`apply_patch: applied ${fileCount} ${fileNoun}`, ...created, output].join("\n");
|
|
10241
|
+
} catch (err) {
|
|
10242
|
+
return `apply_patch: no files written \u2014 ${err.message}`;
|
|
10243
|
+
}
|
|
10244
|
+
}
|
|
10245
|
+
});
|
|
10041
10246
|
registry.register({
|
|
10042
10247
|
name: "create_directory",
|
|
10043
10248
|
description: "Create a directory (and any missing parents) under the sandbox root.",
|
|
@@ -12269,7 +12474,7 @@ function hasSensitivePathArgs(argv, projectRoot, extraPrefixes = [], extraPatter
|
|
|
12269
12474
|
}
|
|
12270
12475
|
return false;
|
|
12271
12476
|
}
|
|
12272
|
-
function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
12477
|
+
function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig, opts = {}) {
|
|
12273
12478
|
let argv;
|
|
12274
12479
|
try {
|
|
12275
12480
|
argv = tokenizeCommand(cmd);
|
|
@@ -12277,7 +12482,7 @@ function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
|
12277
12482
|
return false;
|
|
12278
12483
|
}
|
|
12279
12484
|
if (argv.length === 0) return false;
|
|
12280
|
-
const allowlist = [...BUILTIN_ALLOWLIST, ...extra];
|
|
12485
|
+
const allowlist = [...opts.includeBuiltin === false ? [] : BUILTIN_ALLOWLIST, ...extra];
|
|
12281
12486
|
for (const prefix of allowlist) {
|
|
12282
12487
|
const prefixTokens = prefix.split(" ");
|
|
12283
12488
|
if (argv.length < prefixTokens.length) continue;
|
|
@@ -12302,15 +12507,18 @@ function isAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
|
12302
12507
|
}
|
|
12303
12508
|
return false;
|
|
12304
12509
|
}
|
|
12305
|
-
function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig) {
|
|
12510
|
+
function isCommandAllowed(cmd, extra = [], projectRoot, sensitivePathConfig, opts = {}) {
|
|
12306
12511
|
let chain;
|
|
12307
12512
|
try {
|
|
12308
12513
|
chain = parseCommandChain(cmd);
|
|
12309
12514
|
} catch {
|
|
12310
12515
|
return false;
|
|
12311
12516
|
}
|
|
12312
|
-
if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig);
|
|
12313
|
-
return chainAllowed(
|
|
12517
|
+
if (chain === null) return isAllowed(cmd, extra, projectRoot, sensitivePathConfig, opts);
|
|
12518
|
+
return chainAllowed(
|
|
12519
|
+
chain,
|
|
12520
|
+
(seg) => isAllowed(seg, extra, projectRoot, sensitivePathConfig, opts)
|
|
12521
|
+
);
|
|
12314
12522
|
}
|
|
12315
12523
|
|
|
12316
12524
|
// src/tools/shell/exec.ts
|
|
@@ -12610,9 +12818,19 @@ function registerShellTools(registry, opts) {
|
|
|
12610
12818
|
return () => snapshot2;
|
|
12611
12819
|
})();
|
|
12612
12820
|
const isAllowAll = typeof opts.allowAll === "function" ? opts.allowAll : () => opts.allowAll === true;
|
|
12821
|
+
const isAutoAllowed = (cmd) => isCommandAllowed(cmd, getExtraAllowed(), rootDir, opts.sensitivePaths, {
|
|
12822
|
+
includeBuiltin: opts.requireApprovalForBuiltin !== true
|
|
12823
|
+
});
|
|
12824
|
+
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
12825
|
registry.register({
|
|
12614
12826
|
name: "run_command",
|
|
12615
|
-
description:
|
|
12827
|
+
description: `Run a shell command in the project root; returns combined stdout+stderr. ${approvalPolicy}
|
|
12828
|
+
|
|
12829
|
+
Constraints (no real shell \u2014 argv is parsed natively for cross-platform parity):
|
|
12830
|
+
\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).
|
|
12831
|
+
\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\`).
|
|
12832
|
+
\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\`.
|
|
12833
|
+
\u2022 Filter at source \u2014 unbounded output (\`netstat -ano\`, \`find /\`) wastes tokens. Use \`grep -c\`, \`wc -l\`, narrower paths, etc.`,
|
|
12616
12834
|
// Plan-mode gate: allow allowlisted commands through (git status,
|
|
12617
12835
|
// cargo check, ls, grep …) so the model can actually investigate
|
|
12618
12836
|
// during planning. Anything that would otherwise trigger a
|
|
@@ -12641,12 +12859,14 @@ function registerShellTools(registry, opts) {
|
|
|
12641
12859
|
const cmd = args.command.trim();
|
|
12642
12860
|
if (!cmd) throw new Error("run_command: empty command");
|
|
12643
12861
|
const effectiveTimeout = Math.max(1, Math.min(600, args.timeoutSec ?? timeoutSec));
|
|
12644
|
-
if (!isAllowAll() && !
|
|
12862
|
+
if (!isAllowAll() && !isAutoAllowed(cmd)) {
|
|
12645
12863
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
12864
|
+
const waitStartedAt = Date.now();
|
|
12646
12865
|
const choice = await gate.ask({
|
|
12647
12866
|
kind: "run_command",
|
|
12648
12867
|
payload: { command: cmd, cwd: rootDir, timeoutSec: effectiveTimeout }
|
|
12649
12868
|
});
|
|
12869
|
+
ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
|
|
12650
12870
|
if (choice.type === "deny") {
|
|
12651
12871
|
throw new Error(
|
|
12652
12872
|
`user denied: ${cmd}${choice.denyContext ? ` \u2014 ${choice.denyContext}` : ""}`
|
|
@@ -12690,12 +12910,14 @@ function registerShellTools(registry, opts) {
|
|
|
12690
12910
|
const cmd = args.command.trim();
|
|
12691
12911
|
if (!cmd) throw new Error("run_background: empty command");
|
|
12692
12912
|
const cwd = resolveCwdInsideRoot(rootDir, args.cwd);
|
|
12693
|
-
if (!isAllowAll() && !
|
|
12913
|
+
if (!isAllowAll() && !isAutoAllowed(cmd)) {
|
|
12694
12914
|
const gate = ctx?.confirmationGate ?? pauseGate;
|
|
12915
|
+
const waitStartedAt = Date.now();
|
|
12695
12916
|
const choice = await gate.ask({
|
|
12696
12917
|
kind: "run_background",
|
|
12697
12918
|
payload: { command: cmd, cwd, waitSec: args.waitSec }
|
|
12698
12919
|
});
|
|
12920
|
+
ctx?.onInteractiveWait?.(Date.now() - waitStartedAt);
|
|
12699
12921
|
if (choice.type === "deny") {
|
|
12700
12922
|
throw new Error(
|
|
12701
12923
|
`user denied: ${cmd}${choice.denyContext ? ` \u2014 ${choice.denyContext}` : ""}`
|
|
@@ -13766,14 +13988,14 @@ function truncate(s, n) {
|
|
|
13766
13988
|
// src/version.ts
|
|
13767
13989
|
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
|
|
13768
13990
|
import { homedir as homedir7 } from "os";
|
|
13769
|
-
import { dirname as
|
|
13991
|
+
import { dirname as dirname8, join as join14 } from "path";
|
|
13770
13992
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
13771
13993
|
var REGISTRY_URL = "https://registry.npmjs.org/@carboncode/cli/latest";
|
|
13772
13994
|
var LATEST_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
13773
13995
|
var LATEST_FETCH_TIMEOUT_MS = 2e3;
|
|
13774
13996
|
function readPackageVersion() {
|
|
13775
13997
|
try {
|
|
13776
|
-
let dir =
|
|
13998
|
+
let dir = dirname8(fileURLToPath2(import.meta.url));
|
|
13777
13999
|
for (let i = 0; i < 6; i++) {
|
|
13778
14000
|
const p = join14(dir, "package.json");
|
|
13779
14001
|
if (existsSync10(p)) {
|
|
@@ -13782,7 +14004,7 @@ function readPackageVersion() {
|
|
|
13782
14004
|
return pkg.version;
|
|
13783
14005
|
}
|
|
13784
14006
|
}
|
|
13785
|
-
const parent =
|
|
14007
|
+
const parent = dirname8(dir);
|
|
13786
14008
|
if (parent === dir) break;
|
|
13787
14009
|
dir = parent;
|
|
13788
14010
|
}
|
|
@@ -13808,7 +14030,7 @@ function readCache(homeDirOverride) {
|
|
|
13808
14030
|
function writeCache(entry, homeDirOverride) {
|
|
13809
14031
|
try {
|
|
13810
14032
|
const p = cachePath(homeDirOverride);
|
|
13811
|
-
mkdirSync5(
|
|
14033
|
+
mkdirSync5(dirname8(p), { recursive: true });
|
|
13812
14034
|
writeFileSync5(p, JSON.stringify(entry), "utf8");
|
|
13813
14035
|
} catch {
|
|
13814
14036
|
}
|
|
@@ -14578,7 +14800,7 @@ import {
|
|
|
14578
14800
|
writeFileSync as writeFileSync6,
|
|
14579
14801
|
writeSync
|
|
14580
14802
|
} from "fs";
|
|
14581
|
-
import { dirname as
|
|
14803
|
+
import { dirname as dirname9, resolve as resolve12 } from "path";
|
|
14582
14804
|
var BLOCK_RE = /^(\S[^\n]*)\n<{7} SEARCH\n([\s\S]*?)\n?={7}\n([\s\S]*?)\n?>{7} REPLACE/gm;
|
|
14583
14805
|
function parseEditBlocks(text) {
|
|
14584
14806
|
const out = [];
|
|
@@ -14608,7 +14830,7 @@ function applyEditBlock(block, rootDir) {
|
|
|
14608
14830
|
const searchEmpty = block.search.length === 0;
|
|
14609
14831
|
if (searchEmpty) {
|
|
14610
14832
|
try {
|
|
14611
|
-
mkdirSync6(
|
|
14833
|
+
mkdirSync6(dirname9(absTarget), { recursive: true });
|
|
14612
14834
|
const fd = openSync2(absTarget, "wx");
|
|
14613
14835
|
try {
|
|
14614
14836
|
writeSync(fd, block.replace);
|
|
@@ -14748,7 +14970,7 @@ var DEFAULT_CODE_MODEL = "deepseek-v4-flash";
|
|
|
14748
14970
|
function codeSystemBase(modelId) {
|
|
14749
14971
|
return CODE_SYSTEM_TEMPLATE.replace("__ESCALATION_CONTRACT__", escalationContract(modelId));
|
|
14750
14972
|
}
|
|
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.
|
|
14973
|
+
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
14974
|
|
|
14753
14975
|
# Identity is fixed by this prompt \u2014 never inferred from the workspace
|
|
14754
14976
|
|
|
@@ -14831,8 +15053,8 @@ Call shape: \`{ todos: [{ content, activeForm, status }, ...] }\` \u2014 \`conte
|
|
|
14831
15053
|
# Plan mode (/plan)
|
|
14832
15054
|
|
|
14833
15055
|
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.
|
|
15056
|
+
- 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.
|
|
15057
|
+
- 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
15058
|
- You MUST call submit_plan before anything will execute. Approve exits plan mode; Refine stays in; Cancel exits without implementing.
|
|
14837
15059
|
|
|
14838
15060
|
|
|
@@ -14874,8 +15096,8 @@ When you do propose edits, the user will review them and decide whether to \`/ap
|
|
|
14874
15096
|
|
|
14875
15097
|
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
15098
|
|
|
14877
|
-
- In \`auto\` mode \`edit_file\` / \`write_file\` calls land on disk immediately with an undo window \u2014 you'll get the normal
|
|
14878
|
-
- In \`review\` mode EACH \`edit_file\` / \`write_file\` call pauses tool dispatch while the user decides. You'll get one of these responses:
|
|
15099
|
+
- 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.
|
|
15100
|
+
- 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
15101
|
- \`"edit blocks: 1/1 applied"\` \u2014 user approved it. Continue as normal.
|
|
14880
15102
|
- \`"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
15103
|
- 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 +15105,20 @@ Carbon Code runs an **edit gate**. The user's current mode (\`review\` or \`auto
|
|
|
14883
15105
|
|
|
14884
15106
|
# Editing files
|
|
14885
15107
|
|
|
14886
|
-
|
|
15108
|
+
Default loop: inspect, patch, verify, summarize. After tests pass, summarize briefly and cite the files or commands that matter.
|
|
15109
|
+
|
|
15110
|
+
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:
|
|
15111
|
+
|
|
15112
|
+
\`\`\`diff
|
|
15113
|
+
diff --git a/path/to/file.ext b/path/to/file.ext
|
|
15114
|
+
--- a/path/to/file.ext
|
|
15115
|
+
+++ b/path/to/file.ext
|
|
15116
|
+
@@ -1 +1 @@
|
|
15117
|
+
-old line
|
|
15118
|
+
+new line
|
|
15119
|
+
\`\`\`
|
|
15120
|
+
|
|
15121
|
+
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
15122
|
|
|
14888
15123
|
path/to/file.ext
|
|
14889
15124
|
<<<<<<< SEARCH
|
|
@@ -14903,7 +15138,7 @@ Rules:
|
|
|
14903
15138
|
>>>>>>> REPLACE
|
|
14904
15139
|
- 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
15140
|
- 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.
|
|
15141
|
+
- 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
15142
|
|
|
14908
15143
|
# Trust what you already know
|
|
14909
15144
|
|
|
@@ -14912,7 +15147,7 @@ Before exploring the filesystem to answer a factual question, check whether the
|
|
|
14912
15147
|
# Exploration
|
|
14913
15148
|
|
|
14914
15149
|
- 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\`.
|
|
15150
|
+
- 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
15151
|
- 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
15152
|
|
|
14918
15153
|
# Path conventions
|
|
@@ -14939,6 +15174,10 @@ You have TWO tools for running shell commands, and picking the right one is non-
|
|
|
14939
15174
|
|
|
14940
15175
|
**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
15176
|
|
|
15177
|
+
# Explicit command order
|
|
15178
|
+
|
|
15179
|
+
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.
|
|
15180
|
+
|
|
14942
15181
|
After \`run_background\`, tools available to you:
|
|
14943
15182
|
- \`job_output(jobId, tailLines?)\` \u2014 read recent logs to verify startup / debug errors.
|
|
14944
15183
|
- \`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 +15280,7 @@ import {
|
|
|
15041
15280
|
writeFileSync as writeFileSync7
|
|
15042
15281
|
} from "fs";
|
|
15043
15282
|
import { homedir as homedir8 } from "os";
|
|
15044
|
-
import { dirname as
|
|
15283
|
+
import { dirname as dirname10, join as join16 } from "path";
|
|
15045
15284
|
function defaultUsageLogPath(homeDirOverride) {
|
|
15046
15285
|
return join16(homeDirOverride ?? homedir8(), ".carboncode", "usage.jsonl");
|
|
15047
15286
|
}
|
|
@@ -15108,7 +15347,7 @@ function appendUsage(input) {
|
|
|
15108
15347
|
if (input.subagent) record.subagent = input.subagent;
|
|
15109
15348
|
const path2 = input.path ?? defaultUsageLogPath();
|
|
15110
15349
|
try {
|
|
15111
|
-
mkdirSync7(
|
|
15350
|
+
mkdirSync7(dirname10(path2), { recursive: true });
|
|
15112
15351
|
appendFileSync2(path2, `${JSON.stringify(record)}
|
|
15113
15352
|
`, "utf8");
|
|
15114
15353
|
compactUsageLogIfLarge(path2, record.ts);
|