koneck 2.25.36 → 2.25.38
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/engine.d.ts.map +1 -1
- package/dist/engine.js +34 -2
- package/dist/engine.js.map +1 -1
- package/dist/ink-chat.d.ts.map +1 -1
- package/dist/ink-chat.js +23 -2
- package/dist/ink-chat.js.map +1 -1
- package/dist/permissions.d.ts +8 -0
- package/dist/permissions.d.ts.map +1 -1
- package/dist/permissions.js +69 -1
- package/dist/permissions.js.map +1 -1
- package/dist/plan.d.ts +48 -0
- package/dist/plan.d.ts.map +1 -0
- package/dist/plan.js +108 -0
- package/dist/plan.js.map +1 -0
- package/dist/tools.d.ts +16 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +169 -12
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/permissions.d.ts
CHANGED
|
@@ -43,6 +43,14 @@ export interface AllowRule {
|
|
|
43
43
|
export declare function commandPrefixSuggestion(command: string): string;
|
|
44
44
|
/** Whether a granted rule covers this call. */
|
|
45
45
|
export declare function ruleMatches(rule: AllowRule, tool: string, argsJson: string): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Whether a command only reads.
|
|
48
|
+
*
|
|
49
|
+
* A chained command is never trusted, however it starts: `ls | rm -rf .` begins with `ls`. The
|
|
50
|
+
* same reasoning that keeps a granted prefix from covering a chain applies here, and for the same
|
|
51
|
+
* reason — the check looks at the front of the line and the danger is at the back.
|
|
52
|
+
*/
|
|
53
|
+
export declare function isReadOnlyCommand(command: string): boolean;
|
|
46
54
|
/**
|
|
47
55
|
* What should happen to one tool call: run it, ask, or refuse.
|
|
48
56
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAEhD,wFAAwF;AACxF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACnC,4FAA4F;IAC5F,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,KAAK,EAAE,SAAS,QAAQ,EAkCpC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAE/C;AAcD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAM/C;AAED,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ/D;AAED,+CAA+C;AAC/C,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAUpF;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,QAAQ,
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAEH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,MAAM,CAAC;AAEhD,wFAAwF;AACxF,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEjD,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAE7D,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,QAAQ,CAAC;IACf,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACnC,4FAA4F;IAC5F,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,KAAK,EAAE,SAAS,QAAQ,EAkCpC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAE/C;AAcD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,CAM/C;AAED,2FAA2F;AAC3F,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAQ/D;AAED,+CAA+C;AAC/C,wBAAgB,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAUpF;AAiCD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAsB1D;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,SAAS,SAAS,EAAE,GAC1B,QAAQ,CAaV;AAMD;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,CAAC,CAUjE;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvF;AAED,yDAAyD;AACzD,wBAAgB,OAAO,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,GAAG,SAAS,EAAE,CAIjF;AAED,kEAAkE;AAClE,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,CAEpD"}
|
package/dist/permissions.js
CHANGED
|
@@ -40,7 +40,7 @@ export function modeSpec(name) {
|
|
|
40
40
|
/** Tools that only observe. Never gated: an agent reads constantly, and prompting would be noise. */
|
|
41
41
|
const READ_TOOLS = new Set([
|
|
42
42
|
'read_files', 'list_dir', 'glob_search', 'grep_search', 'fetch_url', 'web_search',
|
|
43
|
-
'remember', 'git_status', 'git_diff', 'git_log',
|
|
43
|
+
'remember', 'plan', 'git_status', 'git_diff', 'git_log',
|
|
44
44
|
]);
|
|
45
45
|
/** Tools that change the working tree. */
|
|
46
46
|
const WRITE_TOOLS = new Set([
|
|
@@ -103,6 +103,63 @@ export function ruleMatches(rule, tool, argsJson) {
|
|
|
103
103
|
return false;
|
|
104
104
|
return text.startsWith(rule.prefix);
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Commands that only look, and are run without asking in any mode that would otherwise ask.
|
|
108
|
+
*
|
|
109
|
+
* Without this, careful mode asks about `ls`. A prompt for something that cannot change anything
|
|
110
|
+
* is pure friction, and friction is what teaches people to approve without reading — at which
|
|
111
|
+
* point the prompt that matters gets waved through too. Codex draws the same line and calls the
|
|
112
|
+
* policy "untrusted": trusted commands run, everything else escalates.
|
|
113
|
+
*
|
|
114
|
+
* Membership is decided by what a command can do, not by how harmless it sounds. Anything that
|
|
115
|
+
* writes, installs, connects out, or evaluates code is absent on purpose.
|
|
116
|
+
*/
|
|
117
|
+
const READ_ONLY_COMMANDS = new Set([
|
|
118
|
+
'ls', 'dir', 'pwd', 'cd', 'echo', 'cat', 'head', 'tail', 'wc', 'file', 'stat',
|
|
119
|
+
'find', 'grep', 'rg', 'ag', 'tree', 'which', 'type', 'whoami', 'id', 'hostname',
|
|
120
|
+
'date', 'uptime', 'df', 'du', 'ps', 'env', 'printenv', 'uname', 'basename', 'dirname',
|
|
121
|
+
'sort', 'uniq', 'cut', 'diff', 'md5sum', 'sha256sum', 'true', 'false',
|
|
122
|
+
]);
|
|
123
|
+
/** Subcommands of tools that are safe only in part: `git log` yes, `git push` emphatically not. */
|
|
124
|
+
const READ_ONLY_SUBCOMMANDS = {
|
|
125
|
+
git: new Set(['status', 'log', 'diff', 'show', 'branch', 'remote', 'blame', 'describe',
|
|
126
|
+
'rev-parse', 'ls-files', 'shortlog', 'tag', 'config']),
|
|
127
|
+
npm: new Set(['ls', 'list', 'view', 'outdated', 'why', 'root', 'prefix', 'config']),
|
|
128
|
+
yarn: new Set(['list', 'why', 'info']),
|
|
129
|
+
pnpm: new Set(['list', 'why', 'root']),
|
|
130
|
+
cargo: new Set(['tree', 'metadata']),
|
|
131
|
+
docker: new Set(['ps', 'images', 'version', 'info']),
|
|
132
|
+
kubectl: new Set(['get', 'describe', 'version']),
|
|
133
|
+
};
|
|
134
|
+
/**
|
|
135
|
+
* Whether a command only reads.
|
|
136
|
+
*
|
|
137
|
+
* A chained command is never trusted, however it starts: `ls | rm -rf .` begins with `ls`. The
|
|
138
|
+
* same reasoning that keeps a granted prefix from covering a chain applies here, and for the same
|
|
139
|
+
* reason — the check looks at the front of the line and the danger is at the back.
|
|
140
|
+
*/
|
|
141
|
+
export function isReadOnlyCommand(command) {
|
|
142
|
+
const text = command.trim();
|
|
143
|
+
if (!text || SHELL_CHAINING.test(text))
|
|
144
|
+
return false;
|
|
145
|
+
const words = text.split(/\s+/).filter(Boolean);
|
|
146
|
+
const head = (words[0] ?? '').toLowerCase();
|
|
147
|
+
// Redirection writes files even when the command itself would not.
|
|
148
|
+
if (words.some(w => w === '>' || w === '>>'))
|
|
149
|
+
return false;
|
|
150
|
+
const subcommands = READ_ONLY_SUBCOMMANDS[head];
|
|
151
|
+
if (subcommands) {
|
|
152
|
+
const sub = (words[1] ?? '').toLowerCase();
|
|
153
|
+
return subcommands.has(sub);
|
|
154
|
+
}
|
|
155
|
+
if (!READ_ONLY_COMMANDS.has(head))
|
|
156
|
+
return false;
|
|
157
|
+
// `find … -delete` and `find … -exec` are not reads at all.
|
|
158
|
+
if (head === 'find' && words.some(w => w === '-delete' || w === '-exec' || w === '-execdir')) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
106
163
|
/**
|
|
107
164
|
* What should happen to one tool call: run it, ask, or refuse.
|
|
108
165
|
*
|
|
@@ -113,6 +170,17 @@ export function decide(mode, tool, argsJson, rules) {
|
|
|
113
170
|
const base = modeSpec(mode).policy[toolRisk(tool)];
|
|
114
171
|
if (base !== 'ask')
|
|
115
172
|
return base;
|
|
173
|
+
// A command that cannot change anything is not worth a question. Note this is reached only
|
|
174
|
+
// when the mode says "ask" — plan mode's "deny" is decided above and stays denied.
|
|
175
|
+
if (tool === 'execute_command') {
|
|
176
|
+
let command = '';
|
|
177
|
+
try {
|
|
178
|
+
command = String(JSON.parse(argsJson).command ?? '');
|
|
179
|
+
}
|
|
180
|
+
catch { /* ask */ }
|
|
181
|
+
if (command && isReadOnlyCommand(command))
|
|
182
|
+
return 'allow';
|
|
183
|
+
}
|
|
116
184
|
return rules.some(r => ruleMatches(r, tool, argsJson)) ? 'allow' : 'ask';
|
|
117
185
|
}
|
|
118
186
|
function rulesFile(cwd) {
|
package/dist/permissions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,aAAa,CAAC;AAC7B,OAAO,IAAI,MAAM,MAAM,CAAC;AAiCxB,MAAM,CAAC,MAAM,KAAK,GAAwB;IACxC;QACE,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,0DAA0D;QACjE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE;QACxD,MAAM,EAAE,EAAE;KACX;IACD;QACE,IAAI,EAAE,OAAO;QACb,KAAK,EAAE,cAAc;QACrB,KAAK,EAAE,kDAAkD;QACzD,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE;QACtD,MAAM,EAAE,EAAE;KACX;IACD;QACE,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,KAAK,EAAE,mDAAmD;QAC1D,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE;QACpD,MAAM,EAAE,EAAE;KACX;IACD;QACE,IAAI,EAAE,MAAM;QACZ,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,kDAAkD;QACzD,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE;QACtD,uFAAuF;QACvF,oDAAoD;QACpD,MAAM,EACJ,qFAAqF;YACrF,uFAAuF;YACvF,+DAA+D;KAClE;CACF,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,KAAK,CAAC,CAAC,CAAE,CAAC;AACvD,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY;IACjF,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,SAAS;
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../src/permissions.ts"],"names":[],"mappings":"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"}
|
package/dist/plan.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list of steps a run is working through, kept where the model can see it every turn.
|
|
3
|
+
*
|
|
4
|
+
* The failure this exists for: asked to build a Stokvel platform, a run listed directories, read
|
|
5
|
+
* a file, narrated what it would do next, listed the same directories again, and eventually
|
|
6
|
+
* halted having built nothing. Nothing was holding the shape of the job. Each turn the model saw
|
|
7
|
+
* a transcript and had to reconstruct what it was doing from it, and by the time the transcript
|
|
8
|
+
* was long it reconstructed something different every time.
|
|
9
|
+
*
|
|
10
|
+
* A plan makes that state explicit rather than inferred. It is written by the model, restated to
|
|
11
|
+
* it on every turn, and shown to the person watching — who can then see the agent forget, which
|
|
12
|
+
* is the difference between "it is stuck" and "it says step 4 of 7 and is on step 4".
|
|
13
|
+
*/
|
|
14
|
+
export type StepState = 'todo' | 'doing' | 'done' | 'blocked';
|
|
15
|
+
export interface PlanStep {
|
|
16
|
+
text: string;
|
|
17
|
+
state: StepState;
|
|
18
|
+
/** Why it is blocked, when it is. */
|
|
19
|
+
note?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface Plan {
|
|
22
|
+
steps: PlanStep[];
|
|
23
|
+
/** When the plan was last written, for showing staleness in a long session. */
|
|
24
|
+
updatedAt: number;
|
|
25
|
+
}
|
|
26
|
+
export declare const EMPTY_PLAN: Plan;
|
|
27
|
+
/** At most this many steps. A plan nobody can hold in their head is a transcript with bullets. */
|
|
28
|
+
export declare const MAX_STEPS = 20;
|
|
29
|
+
/**
|
|
30
|
+
* A plan from whatever shape the model sent.
|
|
31
|
+
*
|
|
32
|
+
* Deliberately forgiving. A model that writes `{"steps": ["a", "b"]}` has expressed a perfectly
|
|
33
|
+
* good plan and should not be argued with about the schema; rejecting it teaches it to stop using
|
|
34
|
+
* the tool, and the tool only helps if it is used.
|
|
35
|
+
*/
|
|
36
|
+
export declare function parsePlan(input: unknown, now: number): Plan | string;
|
|
37
|
+
/**
|
|
38
|
+
* The plan as the model is reminded of it, at the top of every turn.
|
|
39
|
+
*
|
|
40
|
+
* Written as instruction rather than decoration: the point is that the next action follows from
|
|
41
|
+
* the plan, and that a step which is finished gets marked so rather than silently redone.
|
|
42
|
+
*/
|
|
43
|
+
export declare function planReminder(plan: Plan): string;
|
|
44
|
+
/** One line for a status bar: how far along, and what is being done now. */
|
|
45
|
+
export declare function planSummary(plan: Plan): string;
|
|
46
|
+
/** What the model is told after it writes a plan, so the next step is unambiguous. */
|
|
47
|
+
export declare function planAck(plan: Plan): string;
|
|
48
|
+
//# sourceMappingURL=plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9D,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,SAAS,CAAC;IACjB,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,IAAI;IACnB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,+EAA+E;IAC/E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,UAAU,EAAE,IAAkC,CAAC;AAE5D,kGAAkG;AAClG,eAAO,MAAM,SAAS,KAAK,CAAC;AAc5B;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CA0BpE;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAe/C;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAO9C;AAED,sFAAsF;AACtF,wBAAgB,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAO1C"}
|
package/dist/plan.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The list of steps a run is working through, kept where the model can see it every turn.
|
|
3
|
+
*
|
|
4
|
+
* The failure this exists for: asked to build a Stokvel platform, a run listed directories, read
|
|
5
|
+
* a file, narrated what it would do next, listed the same directories again, and eventually
|
|
6
|
+
* halted having built nothing. Nothing was holding the shape of the job. Each turn the model saw
|
|
7
|
+
* a transcript and had to reconstruct what it was doing from it, and by the time the transcript
|
|
8
|
+
* was long it reconstructed something different every time.
|
|
9
|
+
*
|
|
10
|
+
* A plan makes that state explicit rather than inferred. It is written by the model, restated to
|
|
11
|
+
* it on every turn, and shown to the person watching — who can then see the agent forget, which
|
|
12
|
+
* is the difference between "it is stuck" and "it says step 4 of 7 and is on step 4".
|
|
13
|
+
*/
|
|
14
|
+
export const EMPTY_PLAN = { steps: [], updatedAt: 0 };
|
|
15
|
+
/** At most this many steps. A plan nobody can hold in their head is a transcript with bullets. */
|
|
16
|
+
export const MAX_STEPS = 20;
|
|
17
|
+
const STATES = ['todo', 'doing', 'done', 'blocked'];
|
|
18
|
+
function coerceState(value) {
|
|
19
|
+
const text = String(value ?? '').toLowerCase();
|
|
20
|
+
if (STATES.includes(text))
|
|
21
|
+
return text;
|
|
22
|
+
// in_progress, in-progress, active, wip — all mean the same thing to whoever wrote them.
|
|
23
|
+
if (/progress|active|current|wip|working/.test(text))
|
|
24
|
+
return 'doing';
|
|
25
|
+
if (/complete|finish|done/.test(text))
|
|
26
|
+
return 'done';
|
|
27
|
+
if (/block|stuck|wait/.test(text))
|
|
28
|
+
return 'blocked';
|
|
29
|
+
return 'todo';
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A plan from whatever shape the model sent.
|
|
33
|
+
*
|
|
34
|
+
* Deliberately forgiving. A model that writes `{"steps": ["a", "b"]}` has expressed a perfectly
|
|
35
|
+
* good plan and should not be argued with about the schema; rejecting it teaches it to stop using
|
|
36
|
+
* the tool, and the tool only helps if it is used.
|
|
37
|
+
*/
|
|
38
|
+
export function parsePlan(input, now) {
|
|
39
|
+
const raw = input?.steps ?? input;
|
|
40
|
+
if (!Array.isArray(raw)) {
|
|
41
|
+
return 'ERROR: plan needs a "steps" array, e.g. ' +
|
|
42
|
+
'{"steps":[{"text":"design the ledger schema","state":"doing"}]}.';
|
|
43
|
+
}
|
|
44
|
+
if (raw.length === 0)
|
|
45
|
+
return 'ERROR: a plan with no steps says nothing. Give at least one step.';
|
|
46
|
+
const steps = [];
|
|
47
|
+
for (const entry of raw.slice(0, MAX_STEPS)) {
|
|
48
|
+
if (typeof entry === 'string') {
|
|
49
|
+
if (entry.trim())
|
|
50
|
+
steps.push({ text: entry.trim(), state: 'todo' });
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const obj = entry;
|
|
54
|
+
const text = String(obj.text ?? obj.step ?? obj.title ?? '').trim();
|
|
55
|
+
if (!text)
|
|
56
|
+
continue;
|
|
57
|
+
const note = obj.note === undefined ? undefined : String(obj.note).trim();
|
|
58
|
+
steps.push({
|
|
59
|
+
text: text.slice(0, 200),
|
|
60
|
+
state: coerceState(obj.state ?? obj.status),
|
|
61
|
+
...(note ? { note: note.slice(0, 200) } : {}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (steps.length === 0)
|
|
65
|
+
return 'ERROR: none of those steps had any text in them.';
|
|
66
|
+
return { steps, updatedAt: now };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The plan as the model is reminded of it, at the top of every turn.
|
|
70
|
+
*
|
|
71
|
+
* Written as instruction rather than decoration: the point is that the next action follows from
|
|
72
|
+
* the plan, and that a step which is finished gets marked so rather than silently redone.
|
|
73
|
+
*/
|
|
74
|
+
export function planReminder(plan) {
|
|
75
|
+
if (plan.steps.length === 0)
|
|
76
|
+
return '';
|
|
77
|
+
const done = plan.steps.filter(s => s.state === 'done').length;
|
|
78
|
+
const current = plan.steps.find(s => s.state === 'doing') ?? plan.steps.find(s => s.state === 'todo');
|
|
79
|
+
const lines = plan.steps.map((s, i) => {
|
|
80
|
+
const mark = s.state === 'done' ? 'x' : s.state === 'doing' ? '>' : s.state === 'blocked' ? '!' : ' ';
|
|
81
|
+
return ` [${mark}] ${i + 1}. ${s.text}${s.note ? ` — ${s.note}` : ''}`;
|
|
82
|
+
});
|
|
83
|
+
return `\n## Your plan (${done}/${plan.steps.length} done)\n` +
|
|
84
|
+
lines.join('\n') +
|
|
85
|
+
(current ? `\n\nCurrent step: ${current.text}` : '\n\nEvery step is done or blocked.') +
|
|
86
|
+
`\nWork the current step. Call \`plan\` again to mark it done and move on, or to change the ` +
|
|
87
|
+
`plan if what you have learned makes it wrong. Do not re-do a step already marked done.\n`;
|
|
88
|
+
}
|
|
89
|
+
/** One line for a status bar: how far along, and what is being done now. */
|
|
90
|
+
export function planSummary(plan) {
|
|
91
|
+
if (plan.steps.length === 0)
|
|
92
|
+
return '';
|
|
93
|
+
const done = plan.steps.filter(s => s.state === 'done').length;
|
|
94
|
+
const current = plan.steps.find(s => s.state === 'doing');
|
|
95
|
+
const blocked = plan.steps.filter(s => s.state === 'blocked').length;
|
|
96
|
+
const suffix = blocked > 0 ? ` ${blocked} blocked` : '';
|
|
97
|
+
return `${done}/${plan.steps.length}${current ? ` ${current.text.slice(0, 48)}` : ''}${suffix}`;
|
|
98
|
+
}
|
|
99
|
+
/** What the model is told after it writes a plan, so the next step is unambiguous. */
|
|
100
|
+
export function planAck(plan) {
|
|
101
|
+
const done = plan.steps.filter(s => s.state === 'done').length;
|
|
102
|
+
const current = plan.steps.find(s => s.state === 'doing') ?? plan.steps.find(s => s.state === 'todo');
|
|
103
|
+
return current
|
|
104
|
+
? `Plan recorded: ${plan.steps.length} steps, ${done} done. Now do this step: ${current.text}`
|
|
105
|
+
: `Plan recorded: all ${plan.steps.length} steps are done or blocked. ` +
|
|
106
|
+
`If the task is finished, say so and stop.`;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=plan.js.map
|
package/dist/plan.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plan.js","sourceRoot":"","sources":["../src/plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiBH,MAAM,CAAC,MAAM,UAAU,GAAS,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;AAE5D,kGAAkG;AAClG,MAAM,CAAC,MAAM,SAAS,GAAG,EAAE,CAAC;AAE5B,MAAM,MAAM,GAAyB,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC;AAE1E,SAAS,WAAW,CAAC,KAAc;IACjC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/C,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAiB,CAAC;QAAE,OAAO,IAAiB,CAAC;IACjE,yFAAyF;IACzF,IAAI,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,OAAO,CAAC;IACrE,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAC;IACrD,IAAI,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,KAAc,EAAE,GAAW;IACnD,MAAM,GAAG,GAAI,KAA6B,EAAE,KAAK,IAAI,KAAK,CAAC;IAC3D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACxB,OAAO,0CAA0C;YAC/C,kEAAkE,CAAC;IACvE,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,mEAAmE,CAAC;IAEjG,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,EAAE,CAAC;QAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,KAAK,CAAC,IAAI,EAAE;gBAAE,KAAK,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;YACpE,SAAS;QACX,CAAC;QACD,MAAM,GAAG,GAAG,KAA+G,CAAC;QAC5H,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACpE,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1E,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;YACxB,KAAK,EAAE,WAAW,CAAC,GAAG,CAAC,KAAK,IAAI,GAAG,CAAC,MAAM,CAAC;YAC3C,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,kDAAkD,CAAC;IAClF,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;AACnC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAU;IACrC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;IAEtG,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACpC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QACtG,OAAO,MAAM,IAAI,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,OAAO,mBAAmB,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,UAAU;QAC3D,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;QAChB,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,oCAAoC,CAAC;QACtF,6FAA6F;QAC7F,0FAA0F,CAAC;AAC/F,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,IAAU;IACpC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,CAAC;IAC1D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,MAAM,CAAC;IACrE,MAAM,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,OAAO,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,OAAO,GAAG,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC;AACnG,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,OAAO,CAAC,IAAU;IAChC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAC/D,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC;IACtG,OAAO,OAAO;QACZ,CAAC,CAAC,kBAAkB,IAAI,CAAC,KAAK,CAAC,MAAM,WAAW,IAAI,4BAA4B,OAAO,CAAC,IAAI,EAAE;QAC9F,CAAC,CAAC,sBAAsB,IAAI,CAAC,KAAK,CAAC,MAAM,8BAA8B;YACrE,2CAA2C,CAAC;AAClD,CAAC"}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { ToolDefinition, ReadFilesArgs, WriteFileArgs, ApplyDiffArgs, ExecuteCommandArgs, ListDirArgs, GlobSearchArgs, GrepSearchArgs, MoveFileArgs, DeleteFileArgs, SearchReplaceArgs, FetchUrlArgs, RememberArgs, WebSearchArgs } from './types.js';
|
|
2
2
|
export declare const TOOL_DEFINITIONS: ToolDefinition[];
|
|
3
|
+
/**
|
|
4
|
+
* Lines returned for one file unless a window is asked for.
|
|
5
|
+
*
|
|
6
|
+
* There was no cap at all: a five thousand line file went whole into the context window and
|
|
7
|
+
* crowded out everything the run had learned before it. What follows is a model that has read a
|
|
8
|
+
* great deal and remembers almost none of it — which is most of what looks like an agent losing
|
|
9
|
+
* the thread.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_READ_LINES = 800;
|
|
3
12
|
export declare function readFiles(args: ReadFilesArgs, cwd: string): Promise<string>;
|
|
4
13
|
export declare function writeFile(args: WriteFileArgs, cwd: string, requireApproval: boolean): Promise<string>;
|
|
5
14
|
export declare function applyDiff(args: ApplyDiffArgs, cwd: string, requireApproval: boolean): Promise<string>;
|
|
@@ -95,5 +104,12 @@ export declare function aliasNotice(called: string, actual: string): string;
|
|
|
95
104
|
* guessing when the loop detector stopped it. Naming the tools ends that in one turn.
|
|
96
105
|
*/
|
|
97
106
|
export declare function unknownToolError(name: string, available: readonly string[]): string;
|
|
107
|
+
/**
|
|
108
|
+
* The arguments as the tool that will run expects them.
|
|
109
|
+
*
|
|
110
|
+
* Applied to every builtin call, not only aliased ones: a model that writes `cmd` instead of
|
|
111
|
+
* `command` has made the same mistake whatever name it used for the tool.
|
|
112
|
+
*/
|
|
113
|
+
export declare function normalizeToolArgs(tool: string, argsJson: string): string;
|
|
98
114
|
export declare function dispatchTool(name: string, argsJson: string, cwd: string, requireApproval: boolean, onOutput?: (lastLine: string) => void): Promise<string>;
|
|
99
115
|
//# sourceMappingURL=tools.d.ts.map
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,WAAW,EACX,cAAc,EACd,cAAc,EACd,YAAY,EACZ,cAAc,EACd,iBAAiB,EACjB,YAAY,EACZ,YAAY,EACZ,aAAa,EACd,MAAM,YAAY,CAAC;AAGpB,eAAO,MAAM,gBAAgB,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
|
package/dist/tools.js
CHANGED
|
@@ -10,7 +10,9 @@ import { GIT_TOOL_DEFINITIONS, dispatchGitTool } from './git-tools.js';
|
|
|
10
10
|
export const TOOL_DEFINITIONS = [
|
|
11
11
|
{
|
|
12
12
|
name: 'read_files',
|
|
13
|
-
description: 'Read one or more files concurrently. Returns content with line numbers.
|
|
13
|
+
description: 'Read one or more files concurrently. Returns content with line numbers. ' +
|
|
14
|
+
'Long files come back as the first 800 lines with the rest offered by offset — page only ' +
|
|
15
|
+
'if you must; grep_search is usually the better way to find something in a large file.',
|
|
14
16
|
parameters: {
|
|
15
17
|
type: 'object',
|
|
16
18
|
properties: {
|
|
@@ -19,6 +21,14 @@ export const TOOL_DEFINITIONS = [
|
|
|
19
21
|
items: { type: 'string' },
|
|
20
22
|
description: 'File paths to read (absolute or relative to cwd)',
|
|
21
23
|
},
|
|
24
|
+
offset: {
|
|
25
|
+
type: 'number',
|
|
26
|
+
description: 'First line to return, zero-based. For paging through a long file.',
|
|
27
|
+
},
|
|
28
|
+
limit: {
|
|
29
|
+
type: 'number',
|
|
30
|
+
description: 'How many lines to return from offset. Defaults to 800.',
|
|
31
|
+
},
|
|
22
32
|
},
|
|
23
33
|
required: ['paths'],
|
|
24
34
|
},
|
|
@@ -198,6 +208,36 @@ export const TOOL_DEFINITIONS = [
|
|
|
198
208
|
required: ['url'],
|
|
199
209
|
},
|
|
200
210
|
},
|
|
211
|
+
{
|
|
212
|
+
name: 'plan',
|
|
213
|
+
description: 'Record or update the list of steps you are working through. Call it once at the start of ' +
|
|
214
|
+
'any task with more than one step, and again whenever a step is finished, blocked, or the ' +
|
|
215
|
+
'plan turns out to be wrong. The plan is restated to you every turn, so it is how you keep ' +
|
|
216
|
+
'track of a long job instead of re-deriving it from the transcript.',
|
|
217
|
+
parameters: {
|
|
218
|
+
type: 'object',
|
|
219
|
+
properties: {
|
|
220
|
+
steps: {
|
|
221
|
+
type: 'array',
|
|
222
|
+
description: 'The full list of steps, in order. Send the whole list every time, not a delta.',
|
|
223
|
+
items: {
|
|
224
|
+
type: 'object',
|
|
225
|
+
properties: {
|
|
226
|
+
text: { type: 'string', description: 'What the step is' },
|
|
227
|
+
state: {
|
|
228
|
+
type: 'string',
|
|
229
|
+
enum: ['todo', 'doing', 'done', 'blocked'],
|
|
230
|
+
description: 'Exactly one step should be "doing" at a time',
|
|
231
|
+
},
|
|
232
|
+
note: { type: 'string', description: 'Why it is blocked, if it is' },
|
|
233
|
+
},
|
|
234
|
+
required: ['text'],
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
required: ['steps'],
|
|
239
|
+
},
|
|
240
|
+
},
|
|
201
241
|
{
|
|
202
242
|
name: 'remember',
|
|
203
243
|
description: 'Persist a durable fact about this project to .koneck/MEMORY.md, where every future session ' +
|
|
@@ -266,15 +306,53 @@ export const TOOL_DEFINITIONS = [
|
|
|
266
306
|
...GIT_TOOL_DEFINITIONS,
|
|
267
307
|
];
|
|
268
308
|
// ─── Tool handlers ────────────────────────────────────────────────────────────
|
|
309
|
+
/**
|
|
310
|
+
* Lines returned for one file unless a window is asked for.
|
|
311
|
+
*
|
|
312
|
+
* There was no cap at all: a five thousand line file went whole into the context window and
|
|
313
|
+
* crowded out everything the run had learned before it. What follows is a model that has read a
|
|
314
|
+
* great deal and remembers almost none of it — which is most of what looks like an agent losing
|
|
315
|
+
* the thread.
|
|
316
|
+
*/
|
|
317
|
+
export const MAX_READ_LINES = 800;
|
|
318
|
+
/** One file's contents, as a window with its position said out loud. */
|
|
319
|
+
function renderWindow(filePath, content, offset, limit) {
|
|
320
|
+
const lines = content.split('\n');
|
|
321
|
+
const total = lines.length;
|
|
322
|
+
const from = Math.max(0, Math.min(offset, Math.max(0, total - 1)));
|
|
323
|
+
const to = Math.min(total, from + limit);
|
|
324
|
+
const body = lines
|
|
325
|
+
.slice(from, to)
|
|
326
|
+
.map((line, i) => `${String(from + i + 1).padStart(5)} │ ${line}`)
|
|
327
|
+
.join('\n');
|
|
328
|
+
const shownAll = from === 0 && to >= total;
|
|
329
|
+
const header = shownAll
|
|
330
|
+
? `=== ${filePath} (${total} lines) ===`
|
|
331
|
+
: `=== ${filePath} (lines ${from + 1}-${to} of ${total}) ===`;
|
|
332
|
+
// Truncation is stated, never silent. A model that believes it has seen a whole file decides
|
|
333
|
+
// things about the part it never read.
|
|
334
|
+
const footer = shownAll
|
|
335
|
+
? ''
|
|
336
|
+
: `\n… ${total - to} lines below. Read them with ` +
|
|
337
|
+
`{"paths":["${filePath}"],"offset":${to},"limit":${limit}} — or use grep_search to find ` +
|
|
338
|
+
`what you need instead of paging through it.`;
|
|
339
|
+
return header + '\n' + body + footer;
|
|
340
|
+
}
|
|
269
341
|
export async function readFiles(args, cwd) {
|
|
342
|
+
// Said plainly rather than thrown. A missing field used to surface as "Cannot read properties
|
|
343
|
+
// of undefined (reading 'map')", which tells the model nothing it can act on.
|
|
344
|
+
if (!Array.isArray(args.paths) || args.paths.length === 0) {
|
|
345
|
+
return 'ERROR: read_files needs a "paths" array, e.g. {"paths": ["src/App.jsx"]}.';
|
|
346
|
+
}
|
|
347
|
+
const offset = Math.max(0, Math.floor(Number(args.offset ?? 0)) || 0);
|
|
348
|
+
const requested = Math.floor(Number(args.limit ?? MAX_READ_LINES));
|
|
349
|
+
const limit = Number.isFinite(requested) && requested > 0
|
|
350
|
+
? Math.min(requested, MAX_READ_LINES * 4) // an explicit ask can go further, not forever
|
|
351
|
+
: MAX_READ_LINES;
|
|
270
352
|
const results = await Promise.allSettled(args.paths.map(async (filePath) => {
|
|
271
353
|
const abs = path.resolve(cwd, filePath);
|
|
272
354
|
const content = await fs.readFile(abs, 'utf-8');
|
|
273
|
-
|
|
274
|
-
.split('\n')
|
|
275
|
-
.map((line, i) => `${String(i + 1).padStart(5)} │ ${line}`)
|
|
276
|
-
.join('\n');
|
|
277
|
-
return `=== ${filePath} (${content.split('\n').length} lines) ===\n${numbered}`;
|
|
355
|
+
return renderWindow(filePath, content, offset, limit);
|
|
278
356
|
}));
|
|
279
357
|
return results
|
|
280
358
|
.map((r, i) => r.status === 'fulfilled'
|
|
@@ -512,6 +590,22 @@ function killTree(pid) {
|
|
|
512
590
|
signal('SIGTERM');
|
|
513
591
|
setTimeout(() => signal('SIGKILL'), 2_000).unref();
|
|
514
592
|
}
|
|
593
|
+
/**
|
|
594
|
+
* Directories never worth listing, whether or not a .gitignore says so.
|
|
595
|
+
*
|
|
596
|
+
* A recursive listing of a project root returned the whole of node_modules. The model was handed
|
|
597
|
+
* thousands of paths to Babel and Rollup internals, decided the user had "pasted a list of file
|
|
598
|
+
* paths", and spent the rest of the turn explaining what @babel/types is for. `gitignore: true`
|
|
599
|
+
* covered this only in repositories that have a .gitignore — the folder in question was not even
|
|
600
|
+
* a git repository.
|
|
601
|
+
*/
|
|
602
|
+
const NEVER_LIST = [
|
|
603
|
+
'**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**', '**/.next/**',
|
|
604
|
+
'**/coverage/**', '**/vendor/**', '**/target/**', '**/__pycache__/**', '**/.venv/**',
|
|
605
|
+
'**/venv/**', '**/.cache/**', '**/.turbo/**',
|
|
606
|
+
];
|
|
607
|
+
/** Past this a listing stops being information. */
|
|
608
|
+
const MAX_LISTED_ENTRIES = 400;
|
|
515
609
|
export async function listDir(args, cwd) {
|
|
516
610
|
const abs = path.resolve(cwd, args.path);
|
|
517
611
|
try {
|
|
@@ -520,12 +614,19 @@ export async function listDir(args, cwd) {
|
|
|
520
614
|
cwd: abs,
|
|
521
615
|
dot: true,
|
|
522
616
|
gitignore: true,
|
|
617
|
+
ignore: NEVER_LIST,
|
|
523
618
|
onlyFiles: false,
|
|
524
619
|
markDirectories: true,
|
|
525
620
|
});
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
621
|
+
if (entries.length === 0)
|
|
622
|
+
return '(empty directory)';
|
|
623
|
+
// Truncated out loud. A silently cut listing reads as a complete one, and a decision made
|
|
624
|
+
// on it — "there is no test directory" — is then wrong for a reason nobody can see.
|
|
625
|
+
return entries.length > MAX_LISTED_ENTRIES
|
|
626
|
+
? entries.slice(0, MAX_LISTED_ENTRIES).join('\n') +
|
|
627
|
+
`\n… and ${entries.length - MAX_LISTED_ENTRIES} more entries not shown. ` +
|
|
628
|
+
`Narrow the path or use glob_search for what you are looking for.`
|
|
629
|
+
: entries.join('\n');
|
|
529
630
|
}
|
|
530
631
|
const entries = await fs.readdir(abs, { withFileTypes: true });
|
|
531
632
|
if (entries.length === 0)
|
|
@@ -896,9 +997,7 @@ export const TOOL_ALIASES = {
|
|
|
896
997
|
dir: 'list_dir',
|
|
897
998
|
list_files: 'list_dir',
|
|
898
999
|
grep: 'grep_search',
|
|
899
|
-
search: 'grep_search',
|
|
900
1000
|
glob: 'glob_search',
|
|
901
|
-
find: 'glob_search',
|
|
902
1001
|
rm: 'delete_file',
|
|
903
1002
|
remove_file: 'delete_file',
|
|
904
1003
|
mv: 'move_file',
|
|
@@ -942,11 +1041,69 @@ export function unknownToolError(name, available) {
|
|
|
942
1041
|
`The tools available in this session are: ${available.join(', ')}.\n` +
|
|
943
1042
|
`Use one of those exactly as written — the list is complete, and nothing else can be loaded.`;
|
|
944
1043
|
}
|
|
945
|
-
|
|
1044
|
+
/**
|
|
1045
|
+
* Argument names models use for our tools, mapped to the ones we declare.
|
|
1046
|
+
*
|
|
1047
|
+
* Two problems meet here. Aliased calls arrive shaped for the tool that was asked for rather than
|
|
1048
|
+
* the one that runs — `read_file {path}` became `read_files`, whose paths field was then
|
|
1049
|
+
* undefined, and the run died with "Cannot read properties of undefined (reading 'map')" twice in
|
|
1050
|
+
* a row. And models get field names wrong on their own account, `cmd` for `command` and so on,
|
|
1051
|
+
* which produced the same class of failure with no alias involved.
|
|
1052
|
+
*
|
|
1053
|
+
* Normalising costs nothing and turns a crash into a call that works.
|
|
1054
|
+
*/
|
|
1055
|
+
const ARG_SYNONYMS = {
|
|
1056
|
+
read_files: { path: 'paths', file: 'paths', filename: 'paths', files: 'paths' },
|
|
1057
|
+
write_file: { file_path: 'path', filename: 'path', file: 'path', text: 'content', contents: 'content' },
|
|
1058
|
+
execute_command: { cmd: 'command', script: 'command', command_line: 'command', shell_command: 'command' },
|
|
1059
|
+
list_dir: { dir: 'path', directory: 'path', folder: 'path' },
|
|
1060
|
+
grep_search: { query: 'pattern', search: 'pattern', text: 'pattern', regex: 'pattern' },
|
|
1061
|
+
glob_search: { glob: 'pattern', patterns: 'pattern', path: 'cwd' },
|
|
1062
|
+
search_replace: { old: 'search', old_string: 'search', find: 'search',
|
|
1063
|
+
new: 'replace', new_string: 'replace', replacement: 'replace' },
|
|
1064
|
+
delete_file: { file: 'path', target: 'path' },
|
|
1065
|
+
move_file: { src: 'source', from: 'source', dest: 'destination', to: 'destination' },
|
|
1066
|
+
apply_diff: { file: 'path', patch: 'diff' },
|
|
1067
|
+
};
|
|
1068
|
+
/** Fields that must be a list, whatever a single value arrived as. */
|
|
1069
|
+
const LIST_FIELDS = { read_files: 'paths' };
|
|
1070
|
+
/**
|
|
1071
|
+
* The arguments as the tool that will run expects them.
|
|
1072
|
+
*
|
|
1073
|
+
* Applied to every builtin call, not only aliased ones: a model that writes `cmd` instead of
|
|
1074
|
+
* `command` has made the same mistake whatever name it used for the tool.
|
|
1075
|
+
*/
|
|
1076
|
+
export function normalizeToolArgs(tool, argsJson) {
|
|
1077
|
+
const synonyms = ARG_SYNONYMS[tool];
|
|
1078
|
+
const listField = LIST_FIELDS[tool];
|
|
1079
|
+
if (!synonyms && !listField)
|
|
1080
|
+
return argsJson;
|
|
946
1081
|
let parsed;
|
|
947
1082
|
try {
|
|
948
1083
|
parsed = JSON.parse(argsJson);
|
|
949
1084
|
}
|
|
1085
|
+
catch {
|
|
1086
|
+
return argsJson;
|
|
1087
|
+
}
|
|
1088
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
|
|
1089
|
+
return argsJson;
|
|
1090
|
+
const out = {};
|
|
1091
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
1092
|
+
const target = synonyms?.[key] ?? key;
|
|
1093
|
+
// A real field already present wins: renaming over it would discard what the model meant.
|
|
1094
|
+
if (out[target] === undefined)
|
|
1095
|
+
out[target] = value;
|
|
1096
|
+
}
|
|
1097
|
+
if (listField && out[listField] !== undefined && !Array.isArray(out[listField])) {
|
|
1098
|
+
out[listField] = [out[listField]];
|
|
1099
|
+
}
|
|
1100
|
+
return JSON.stringify(out);
|
|
1101
|
+
}
|
|
1102
|
+
export async function dispatchTool(name, argsJson, cwd, requireApproval, onOutput) {
|
|
1103
|
+
let parsed;
|
|
1104
|
+
try {
|
|
1105
|
+
parsed = JSON.parse(normalizeToolArgs(name, argsJson));
|
|
1106
|
+
}
|
|
950
1107
|
catch {
|
|
951
1108
|
return `ERROR: Malformed JSON arguments for tool "${name}": ${argsJson.slice(0, 200)}`;
|
|
952
1109
|
}
|