howtofixcli 0.7.8 → 0.7.9
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/dist/chunk-27TRKD65.js +2 -0
- package/dist/chunk-3FYCFLNK.js +1 -0
- package/dist/chunk-56SEAGAQ.js +1 -0
- package/dist/chunk-63ZCQDXH.js +6 -0
- package/dist/chunk-6FUWMLWO.js +1 -0
- package/dist/chunk-7CHDNTH6.js +1 -0
- package/dist/chunk-ACOMNDUL.js +185 -0
- package/dist/chunk-AEVPET7X.js +2 -0
- package/dist/chunk-AGGIYXN7.js +2 -0
- package/dist/chunk-BHVBO4SC.js +1 -0
- package/dist/chunk-FPXVDP32.js +8 -0
- package/dist/chunk-GIUYIKH3.js +109 -0
- package/dist/chunk-GQCAJ56I.js +5 -0
- package/dist/chunk-GXB7RZHH.js +1 -0
- package/dist/chunk-IGEY3AWF.js +1 -0
- package/dist/chunk-ILDDDLRC.js +1 -0
- package/dist/chunk-JFYVRWJI.js +2 -0
- package/dist/chunk-MVLCGU3G.js +1 -0
- package/dist/chunk-N345KWWK.js +1 -0
- package/dist/chunk-NBQFPCRT.js +1 -0
- package/dist/chunk-OIPSLIZE.js +5 -0
- package/dist/chunk-ORJ2QZBG.js +3 -0
- package/dist/chunk-PCALBZOO.js +2 -0
- package/dist/chunk-PDNBGUYP.js +2 -0
- package/dist/chunk-S656RE4A.js +1 -0
- package/dist/chunk-TSEKPGK4.js +1 -0
- package/dist/chunk-U6CI6KXN.js +34 -0
- package/dist/chunk-UTYFLLSM.js +1 -0
- package/dist/chunk-V3R4FYTJ.js +1 -0
- package/dist/chunk-V656XVXV.js +8 -0
- package/dist/chunk-WKUJ7CFO.js +1 -0
- package/dist/chunk-WOT6VMZA.js +1 -0
- package/dist/chunk-WUPU6LTD.js +4 -0
- package/dist/chunk-WX3V5P7U.js +5 -0
- package/dist/chunk-ZKZ6WCI3.js +2 -0
- package/dist/main.js +94 -536
- package/package.json +15 -62
- package/dist/account-credential.js +0 -136
- package/dist/account-credential.test.js +0 -38
- package/dist/activity.js +0 -124
- package/dist/agent-defs.js +0 -237
- package/dist/agent-limiter.js +0 -68
- package/dist/agents.js +0 -554
- package/dist/anim.js +0 -361
- package/dist/args.js +0 -228
- package/dist/at-file.js +0 -28
- package/dist/brand-extension.js +0 -87
- package/dist/brand.js +0 -76
- package/dist/browser.js +0 -58
- package/dist/cache-report.js +0 -139
- package/dist/checkpoint.js +0 -437
- package/dist/claims.js +0 -167
- package/dist/cli-auth.js +0 -142
- package/dist/cli-checkin.js +0 -120
- package/dist/cli-credentials.js +0 -254
- package/dist/clipboard.js +0 -149
- package/dist/collapse.js +0 -129
- package/dist/commands.js +0 -301
- package/dist/compaction-guard.js +0 -81
- package/dist/context-extension.js +0 -17
- package/dist/dcp/commands.js +0 -172
- package/dist/dcp/compress-state.js +0 -139
- package/dist/dcp/compress.js +0 -329
- package/dist/dcp/dcp-extension.js +0 -288
- package/dist/dcp/dedup.js +0 -93
- package/dist/dcp/ids.js +0 -189
- package/dist/dcp/index.js +0 -25
- package/dist/dcp/prompts.js +0 -68
- package/dist/dcp/protected-content.js +0 -221
- package/dist/dcp/protected.js +0 -161
- package/dist/dcp/prune.js +0 -418
- package/dist/dcp/state.js +0 -176
- package/dist/device-id.js +0 -90
- package/dist/device-id.test.js +0 -23
- package/dist/errors.js +0 -63
- package/dist/extensions.js +0 -48
- package/dist/fatal.js +0 -35
- package/dist/feed.js +0 -498
- package/dist/fs-atomic.js +0 -54
- package/dist/gateway-errors.js +0 -90
- package/dist/git.js +0 -230
- package/dist/goal-extension.js +0 -88
- package/dist/goal.js +0 -222
- package/dist/hooks-extension.js +0 -176
- package/dist/hooks.js +0 -443
- package/dist/howtofix-account.js +0 -715
- package/dist/hud-line.js +0 -153
- package/dist/hwid.js +0 -130
- package/dist/interrupt.js +0 -60
- package/dist/json-events.js +0 -50
- package/dist/level-style.js +0 -112
- package/dist/loop-guard.js +0 -95
- package/dist/lsp-tools.js +0 -32
- package/dist/lsp.js +0 -347
- package/dist/mcp-config.js +0 -85
- package/dist/mcp-lifecycle.js +0 -147
- package/dist/model-catalog.js +0 -217
- package/dist/model-resolve.js +0 -65
- package/dist/notes.js +0 -78
- package/dist/oauth-login.js +0 -568
- package/dist/paths.js +0 -61
- package/dist/permissions.js +0 -298
- package/dist/plan-mode.js +0 -363
- package/dist/preset-command.js +0 -227
- package/dist/preset-extension.js +0 -35
- package/dist/preset-mcp.js +0 -292
- package/dist/presets.js +0 -727
- package/dist/prompt-files.js +0 -103
- package/dist/prompt-text.js +0 -41
- package/dist/proxy.js +0 -137
- package/dist/quality/ast.js +0 -196
- package/dist/quality/cognitive.js +0 -83
- package/dist/quality/cyclomatic.js +0 -30
- package/dist/quality/halstead.js +0 -83
- package/dist/quality/index.js +0 -18
- package/dist/quality/length.js +0 -3
- package/dist/quality/maintainability.js +0 -15
- package/dist/quality/nesting.js +0 -57
- package/dist/quality/report.js +0 -803
- package/dist/quality/smells.js +0 -67
- package/dist/quality-tool.js +0 -63
- package/dist/read-only-commands.js +0 -597
- package/dist/repo-map.js +0 -292
- package/dist/rtk.js +0 -193
- package/dist/sandbox.js +0 -105
- package/dist/schema.js +0 -214
- package/dist/session-lifecycle.js +0 -19
- package/dist/session-retention.js +0 -111
- package/dist/session.js +0 -446
- package/dist/sessions.js +0 -100
- package/dist/settings.js +0 -148
- package/dist/skills.js +0 -35
- package/dist/slop-tool.js +0 -96
- package/dist/spend-cap.js +0 -43
- package/dist/subagent.js +0 -171
- package/dist/tar.js +0 -146
- package/dist/theme.js +0 -79
- package/dist/thinking.js +0 -45
- package/dist/todo.js +0 -114
- package/dist/tool-loadout.js +0 -97
- package/dist/tool-render.js +0 -769
- package/dist/tools.js +0 -176
- package/dist/ui.js +0 -4757
- package/dist/update.js +0 -533
- package/dist/usage-segment.js +0 -69
- package/dist/web-tools.js +0 -46
- package/dist/web.js +0 -265
- package/dist/welcome.js +0 -179
- package/dist/workflow-engine.js +0 -246
- package/dist/workflow-progress.js +0 -446
- package/dist/workflow-view.js +0 -195
- package/dist/workflow.js +0 -349
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import { refIndicesOf } from "./ids.js";
|
|
2
|
-
import { activeCompressedRanges, estimateSummaryTokens } from "./compress-state.js";
|
|
3
|
-
import { HARD_EXCEPTION_TOOLS, isToolProtected } from "./protected.js";
|
|
4
|
-
/**
|
|
5
|
-
* The shared predicate, once: selection members in transcript order, minus
|
|
6
|
-
* the ones an active block already covers, resolved to array positions.
|
|
7
|
-
*
|
|
8
|
-
* `exceptBlockId` is the block this append is FOR. At render time that block is
|
|
9
|
-
* already active and covers exactly the window being appended, so leaving it in
|
|
10
|
-
* the skip set makes every append a no-op — silently, with a stored summary that
|
|
11
|
-
* looks correct and has lost the user's own words. The caller that renders a
|
|
12
|
-
* block's summary passes its id; a caller doing anything else omits it.
|
|
13
|
-
*/
|
|
14
|
-
function unprotectedPositions(selection, source, state, exceptBlockId) {
|
|
15
|
-
// No ref state (a transcript that was never tagged): nothing resolves, so
|
|
16
|
-
// every append is a no-op rather than an error — the compaction case.
|
|
17
|
-
const posByRef = new Map();
|
|
18
|
-
if (source.idState) {
|
|
19
|
-
const refs = refIndicesOf(source.idState, source.messages);
|
|
20
|
-
for (let position = 0; position < refs.length; position++) {
|
|
21
|
-
const ref = refs[position];
|
|
22
|
-
if (ref !== null)
|
|
23
|
-
posByRef.set(ref, position);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
const covered = new Set();
|
|
27
|
-
for (const range of activeCompressedRanges(state)) {
|
|
28
|
-
if (exceptBlockId !== undefined && range.blockId === exceptBlockId)
|
|
29
|
-
continue;
|
|
30
|
-
for (const index of range.coveredIndices) {
|
|
31
|
-
covered.add(index);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
const positions = [];
|
|
35
|
-
if (source.idState) {
|
|
36
|
-
for (const ref of selection.indices) {
|
|
37
|
-
if (covered.has(ref))
|
|
38
|
-
continue;
|
|
39
|
-
const position = posByRef.get(ref);
|
|
40
|
-
if (position === undefined)
|
|
41
|
-
continue;
|
|
42
|
-
positions.push(position);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
return positions;
|
|
46
|
-
}
|
|
47
|
-
/** Every non-empty text of a user message; the content may be a bare string. */
|
|
48
|
-
function userTextParts(message) {
|
|
49
|
-
if (typeof message.content === "string") {
|
|
50
|
-
return message.content.trim() ? [message.content] : [];
|
|
51
|
-
}
|
|
52
|
-
const texts = [];
|
|
53
|
-
for (const part of message.content) {
|
|
54
|
-
if (part.type === "text" && part.text.trim()) {
|
|
55
|
-
texts.push(part.text);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return texts;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Append the first non-empty text of every selected user message that is not
|
|
62
|
-
* already covered by an active block. Byte-identical no-op when `enabled` is
|
|
63
|
-
* false or nothing qualifies — a heading with no body would be worse than
|
|
64
|
-
* nothing, it reads as a bug to the model.
|
|
65
|
-
*/
|
|
66
|
-
export function appendProtectedUserMessages(summary, selection, source, state, enabled, forBlockId) {
|
|
67
|
-
if (!enabled)
|
|
68
|
-
return summary;
|
|
69
|
-
const userTexts = [];
|
|
70
|
-
for (const position of unprotectedPositions(selection, source, state, forBlockId)) {
|
|
71
|
-
const message = source.messages[position];
|
|
72
|
-
if (!message || message.role !== "user")
|
|
73
|
-
continue;
|
|
74
|
-
const first = userTextParts(message)[0];
|
|
75
|
-
if (first !== undefined) {
|
|
76
|
-
userTexts.push(first);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (userTexts.length === 0)
|
|
80
|
-
return summary;
|
|
81
|
-
const heading = "\n\nThe following user messages were sent in this conversation verbatim:";
|
|
82
|
-
const body = userTexts.map((text) => `\n${text}`).join("");
|
|
83
|
-
return summary + heading + body;
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Append every `<protect>...</protect>` capture from the selected user
|
|
87
|
-
* messages that is not already covered by an active block. Same no-op rule
|
|
88
|
-
* as the user-message path.
|
|
89
|
-
*/
|
|
90
|
-
export function appendProtectedPromptInfo(summary, selection, source, state, enabled, forBlockId) {
|
|
91
|
-
if (!enabled)
|
|
92
|
-
return summary;
|
|
93
|
-
const protectedTexts = [];
|
|
94
|
-
for (const position of unprotectedPositions(selection, source, state, forBlockId)) {
|
|
95
|
-
const message = source.messages[position];
|
|
96
|
-
if (!message || message.role !== "user")
|
|
97
|
-
continue;
|
|
98
|
-
for (const text of userTextParts(message)) {
|
|
99
|
-
protectedTexts.push(...extractProtectedPromptInfo(text));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
if (protectedTexts.length === 0)
|
|
103
|
-
return summary;
|
|
104
|
-
const heading = "\n\nThe following protected prompt information was included in this conversation verbatim:";
|
|
105
|
-
const body = protectedTexts.map((text) => `\n${text}`).join("");
|
|
106
|
-
return summary + heading + body;
|
|
107
|
-
}
|
|
108
|
-
/** Every `<protect>...</protect>` capture in `text`, trimmed, empties dropped. */
|
|
109
|
-
export function extractProtectedPromptInfo(text) {
|
|
110
|
-
const protectedTexts = [];
|
|
111
|
-
const protectTagRegex = /<protect>([\s\S]*?)<\/protect>/gi;
|
|
112
|
-
for (const match of text.matchAll(protectTagRegex)) {
|
|
113
|
-
const captured = match[1]?.trim();
|
|
114
|
-
if (captured) {
|
|
115
|
-
protectedTexts.push(captured);
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return protectedTexts;
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Append the outputs of protected tools from the selection window. A tool is
|
|
122
|
-
* protected when its name matches the hard exceptions, the configured list,
|
|
123
|
-
* or a configured file pattern matches a path in its arguments.
|
|
124
|
-
*
|
|
125
|
-
* The window is also the pairing scope: an output whose call sits outside the
|
|
126
|
-
* window has no arguments to inspect, so it falls back to name-based
|
|
127
|
-
* protection (`isToolProtected` with an undefined input).
|
|
128
|
-
*/
|
|
129
|
-
export function appendProtectedTools(summary, selection, source, state, protectedTools = [], protectedFilePatterns = [], forBlockId) {
|
|
130
|
-
const argsByCallId = new Map();
|
|
131
|
-
const outputs = [];
|
|
132
|
-
for (const position of unprotectedPositions(selection, source, state, forBlockId)) {
|
|
133
|
-
const message = source.messages[position];
|
|
134
|
-
if (!message)
|
|
135
|
-
continue;
|
|
136
|
-
if (message.role === "assistant") {
|
|
137
|
-
for (const part of message.content) {
|
|
138
|
-
if (part.type === "toolCall") {
|
|
139
|
-
argsByCallId.set(part.id, part.arguments);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
continue;
|
|
143
|
-
}
|
|
144
|
-
if (message.role === "toolResult") {
|
|
145
|
-
// Error outputs are prune candidates, not keepsakes; the reference only
|
|
146
|
-
// ever preserved completed output.
|
|
147
|
-
if (message.isError)
|
|
148
|
-
continue;
|
|
149
|
-
const text = message.content
|
|
150
|
-
.filter((part) => part.type === "text")
|
|
151
|
-
.map((part) => part.text)
|
|
152
|
-
.join("");
|
|
153
|
-
if (text.trim()) {
|
|
154
|
-
outputs.push({ toolCallId: message.toolCallId, toolName: message.toolName, text });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (outputs.length === 0)
|
|
159
|
-
return summary;
|
|
160
|
-
const toolNames = [...new Set([...HARD_EXCEPTION_TOOLS, ...protectedTools])];
|
|
161
|
-
const filePatterns = [...protectedFilePatterns];
|
|
162
|
-
const blocks = [];
|
|
163
|
-
for (const output of outputs) {
|
|
164
|
-
const input = argsByCallId.get(output.toolCallId);
|
|
165
|
-
if (!isToolProtected(output.toolName, input, toolNames, filePatterns))
|
|
166
|
-
continue;
|
|
167
|
-
blocks.push(`\n### Tool: ${output.toolName}\n${output.text}`);
|
|
168
|
-
}
|
|
169
|
-
if (blocks.length === 0)
|
|
170
|
-
return summary;
|
|
171
|
-
const heading = "\n\nThe following protected tools were used in this conversation as well:";
|
|
172
|
-
return summary + heading + blocks.join("");
|
|
173
|
-
}
|
|
174
|
-
/**
|
|
175
|
-
* Store-side enrichment: layer a block's window's protected content onto its
|
|
176
|
-
* stored summary in one mutation.
|
|
177
|
-
*
|
|
178
|
-
* This is the only path that carries the text, and the handler calls it once
|
|
179
|
-
* per active block on every request. It runs here rather than in the compress
|
|
180
|
-
* tool because the tool's `messages()` is a ref snapshot, not the transcript —
|
|
181
|
-
* the appends resolve refs to messages, so only a caller holding both can do
|
|
182
|
-
* this at all.
|
|
183
|
-
*
|
|
184
|
-
* Contract rules, each observable in `check-dcp.mjs`:
|
|
185
|
-
*
|
|
186
|
-
* 1. The selection is the block's own coverage — min/max bound it, the
|
|
187
|
-
* indices enumerate it.
|
|
188
|
-
* 2. `forBlockId` is passed on every append: the block is active and covers
|
|
189
|
-
* its window, so without the self-exclusion everything no-ops silently.
|
|
190
|
-
* 3. The result is written back with `summaryTokens` recomputed through the
|
|
191
|
-
* same estimator creation uses — stale tokens would lie about the savings.
|
|
192
|
-
* 4. Idempotent via `protectedEnriched`: a re-run on a resumed session never
|
|
193
|
-
* duplicates the user's words. The flag lives on the block, not in the
|
|
194
|
-
* text, so a nested block whose body inherits an enriched summary still
|
|
195
|
-
* enriches its own window.
|
|
196
|
-
* 5. A transcript that no longer holds the covered refs simply resolves to
|
|
197
|
-
* no messages: nothing is appended, nothing throws.
|
|
198
|
-
*
|
|
199
|
-
* `state` must carry the ref assignment (`DcpState.messageIds`); typed as an
|
|
200
|
-
* intersection so this module does not depend on the store that depends on
|
|
201
|
-
* `compress-state.ts`.
|
|
202
|
-
*/
|
|
203
|
-
export function enrichBlockSummary(state, block, messages) {
|
|
204
|
-
if (block.protectedEnriched)
|
|
205
|
-
return;
|
|
206
|
-
const covered = block.coveredIndices;
|
|
207
|
-
if (covered.length > 0) {
|
|
208
|
-
const selection = {
|
|
209
|
-
startIdx: Math.min(...covered),
|
|
210
|
-
endIdx: Math.max(...covered),
|
|
211
|
-
indices: [...covered],
|
|
212
|
-
requiredBlockIds: [],
|
|
213
|
-
tokenByIndex: new Map(),
|
|
214
|
-
};
|
|
215
|
-
const source = { messages, idState: state.messageIds };
|
|
216
|
-
const enriched = appendProtectedTools(appendProtectedPromptInfo(appendProtectedUserMessages(block.summary, selection, source, state, true, block.blockId), selection, source, state, true, block.blockId), selection, source, state, [], [], block.blockId);
|
|
217
|
-
block.summary = enriched;
|
|
218
|
-
block.summaryTokens = estimateSummaryTokens(enriched);
|
|
219
|
-
}
|
|
220
|
-
block.protectedEnriched = true;
|
|
221
|
-
}
|
package/dist/dcp/protected.js
DELETED
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Static protection layer: which tools and which file paths are never pruned.
|
|
3
|
-
*
|
|
4
|
-
* Ported from Opencode-DCP `lib/protected-patterns.ts`. `normalizePath`,
|
|
5
|
-
* `matchesGlob` (`*`, `?`, `**`), `getFilePathsFromParameters` (`path`,
|
|
6
|
-
* `filePath`, `patchText`, `multiedit`), `isFilePathProtected`,
|
|
7
|
-
* `isToolNameProtected`, and `isToolProtected` keep DCP's behaviour; the only
|
|
8
|
-
* divergence is typed access — the port uses `unknown` narrowing instead of the
|
|
9
|
-
* original's `any`.
|
|
10
|
-
*
|
|
11
|
-
* The keep-set (goal/plan/notes aware) is the other, dynamic protection layer
|
|
12
|
-
* and lives in `prune.ts`; this module is pure pattern matching.
|
|
13
|
-
*/
|
|
14
|
-
/** Collapse Windows separators so paths compare across platforms. */
|
|
15
|
-
export function normalizePath(input) {
|
|
16
|
-
// A single backslash. In source, "\\" is the one-character string; the
|
|
17
|
-
// previous "\\\\" was a *two*-character string, so it only ever matched a
|
|
18
|
-
// doubled separator -- which a real Windows path does not contain. The
|
|
19
|
-
// normalisation was therefore a no-op on the only platform that needs it.
|
|
20
|
-
return input.replaceAll("\\", "/");
|
|
21
|
-
}
|
|
22
|
-
function escapeRegExpChar(ch) {
|
|
23
|
-
return /[\\.^$+{}()|\[\]]/.test(ch) ? `\\${ch}` : ch;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Glob match supporting `*` (no `/`), `?` (one non-`/`), and `**`
|
|
27
|
-
* (`**` before a slash = zero or more directories, bare `**` = anything).
|
|
28
|
-
*/
|
|
29
|
-
export function matchesGlob(inputPath, pattern) {
|
|
30
|
-
if (!pattern)
|
|
31
|
-
return false;
|
|
32
|
-
const input = normalizePath(inputPath);
|
|
33
|
-
const pat = normalizePath(pattern);
|
|
34
|
-
let regex = "^";
|
|
35
|
-
for (let i = 0; i < pat.length; i++) {
|
|
36
|
-
const ch = pat[i];
|
|
37
|
-
if (ch === "*") {
|
|
38
|
-
const next = pat[i + 1];
|
|
39
|
-
if (next === "*") {
|
|
40
|
-
const after = pat[i + 2];
|
|
41
|
-
if (after === "/") {
|
|
42
|
-
// A doubled star before a slash: zero or more directories
|
|
43
|
-
regex += "(?:.*/)?";
|
|
44
|
-
i += 2;
|
|
45
|
-
continue;
|
|
46
|
-
}
|
|
47
|
-
// **
|
|
48
|
-
regex += ".*";
|
|
49
|
-
i++;
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
// *
|
|
53
|
-
regex += "[^/]*";
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (ch === "?") {
|
|
57
|
-
regex += "[^/]";
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
if (ch === "/") {
|
|
61
|
-
regex += "/";
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
regex += escapeRegExpChar(ch);
|
|
65
|
-
}
|
|
66
|
-
regex += "$";
|
|
67
|
-
return new RegExp(regex).test(input);
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Extract file paths from a tool call's parameters:
|
|
71
|
-
* - `apply_patch`/`patch` → paths embedded in `patchText`
|
|
72
|
-
* - `multiedit` → top-level `filePath` plus each edit's `filePath`
|
|
73
|
-
* - any tool → a string `filePath`
|
|
74
|
-
* - `read`/`write`/`edit` → their V2 `path` parameter
|
|
75
|
-
* Returns unique non-empty paths.
|
|
76
|
-
*/
|
|
77
|
-
export function getFilePathsFromParameters(tool, parameters) {
|
|
78
|
-
if (typeof parameters !== "object" || parameters === null) {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
const paths = [];
|
|
82
|
-
const params = parameters;
|
|
83
|
-
// 1. apply_patch uses patchText with embedded paths
|
|
84
|
-
if ((tool === "apply_patch" || tool === "patch") && typeof params.patchText === "string") {
|
|
85
|
-
const pathRegex = /\*\*\* (?:Add|Delete|Update) File: ([^\n\r]+)/g;
|
|
86
|
-
let match;
|
|
87
|
-
while ((match = pathRegex.exec(params.patchText)) !== null) {
|
|
88
|
-
paths.push(match[1].trim());
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
// 2. multiedit uses top-level filePath and nested edits array
|
|
92
|
-
if (tool === "multiedit") {
|
|
93
|
-
if (typeof params.filePath === "string") {
|
|
94
|
-
paths.push(params.filePath);
|
|
95
|
-
}
|
|
96
|
-
if (Array.isArray(params.edits)) {
|
|
97
|
-
for (const edit of params.edits) {
|
|
98
|
-
if (edit && typeof edit === "object" && typeof edit.filePath === "string") {
|
|
99
|
-
paths.push(edit.filePath);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
// 3. Default check for common filePath parameter (read, write, edit, etc)
|
|
105
|
-
if (typeof params.filePath === "string") {
|
|
106
|
-
paths.push(params.filePath);
|
|
107
|
-
}
|
|
108
|
-
// V2 file tools use path rather than filePath.
|
|
109
|
-
if (["read", "write", "edit"].includes(tool) && typeof params.path === "string") {
|
|
110
|
-
paths.push(params.path);
|
|
111
|
-
}
|
|
112
|
-
// Unique, non-empty
|
|
113
|
-
return [...new Set(paths)].filter((p) => p.length > 0);
|
|
114
|
-
}
|
|
115
|
-
export function isFilePathProtected(filePaths, patterns) {
|
|
116
|
-
if (filePaths.length === 0)
|
|
117
|
-
return false;
|
|
118
|
-
if (patterns.length === 0)
|
|
119
|
-
return false;
|
|
120
|
-
return filePaths.some((path) => patterns.some((pattern) => matchesGlob(path, pattern)));
|
|
121
|
-
}
|
|
122
|
-
const GLOB_CHARS = /[*?]/;
|
|
123
|
-
/**
|
|
124
|
-
* Tools that are never pruned, deduped, or compressed — their results carry
|
|
125
|
-
* state the session replays from (todo/plan), or steer the run (goal/context/task).
|
|
126
|
-
* Spec §3.4 hard exceptions; the default protection list for dedup and prune.
|
|
127
|
-
*/
|
|
128
|
-
export const HARD_EXCEPTION_TOOLS = ["todo", "goal", "context", "task"];
|
|
129
|
-
export function isToolNameProtected(toolName, patterns) {
|
|
130
|
-
if (!toolName || patterns.length === 0)
|
|
131
|
-
return false;
|
|
132
|
-
const exactPatterns = new Set();
|
|
133
|
-
const globPatterns = [];
|
|
134
|
-
for (const pattern of patterns) {
|
|
135
|
-
if (GLOB_CHARS.test(pattern)) {
|
|
136
|
-
globPatterns.push(pattern);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
exactPatterns.add(pattern);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
if (exactPatterns.has(toolName))
|
|
143
|
-
return true;
|
|
144
|
-
return globPatterns.some((pattern) => matchesGlob(toolName, pattern));
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* True when the tool name matches `tools` or any of its file paths match
|
|
148
|
-
* `files`. `metadata.toolCalls` (V2 code-mode nested calls) is checked too:
|
|
149
|
-
* one combined output is protected when any nested call matches.
|
|
150
|
-
*/
|
|
151
|
-
export function isToolProtected(tool, input, tools, files, metadata) {
|
|
152
|
-
const calls = [{ tool, input }];
|
|
153
|
-
if (tool === "execute" && Array.isArray(metadata?.toolCalls)) {
|
|
154
|
-
for (const call of metadata.toolCalls) {
|
|
155
|
-
if (call && typeof call === "object" && typeof call.tool === "string") {
|
|
156
|
-
calls.push({ tool: call.tool, input: call.input });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
return calls.some((call) => isToolNameProtected(call.tool, tools) || isFilePathProtected(getFilePathsFromParameters(call.tool, call.input), files));
|
|
161
|
-
}
|