@x-code-cli/core 0.1.11 → 0.2.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/dist/agent/api-errors.d.ts.map +1 -1
- package/dist/agent/api-errors.js +18 -0
- package/dist/agent/api-errors.js.map +1 -1
- package/dist/agent/diff.d.ts +35 -0
- package/dist/agent/diff.d.ts.map +1 -0
- package/dist/agent/diff.js +83 -0
- package/dist/agent/diff.js.map +1 -0
- package/dist/agent/loop-state.d.ts +45 -3
- package/dist/agent/loop-state.d.ts.map +1 -1
- package/dist/agent/loop-state.js +24 -3
- package/dist/agent/loop-state.js.map +1 -1
- package/dist/agent/loop.d.ts +10 -6
- package/dist/agent/loop.d.ts.map +1 -1
- package/dist/agent/loop.js +212 -30
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/plan-storage.d.ts +55 -0
- package/dist/agent/plan-storage.d.ts.map +1 -0
- package/dist/agent/plan-storage.js +156 -0
- package/dist/agent/plan-storage.js.map +1 -0
- package/dist/agent/session-store.d.ts +114 -0
- package/dist/agent/session-store.d.ts.map +1 -0
- package/dist/agent/session-store.js +415 -0
- package/dist/agent/session-store.js.map +1 -0
- package/dist/agent/sub-agents/built-in.d.ts +3 -0
- package/dist/agent/sub-agents/built-in.d.ts.map +1 -0
- package/dist/agent/sub-agents/built-in.js +98 -0
- package/dist/agent/sub-agents/built-in.js.map +1 -0
- package/dist/agent/sub-agents/index.d.ts +7 -0
- package/dist/agent/sub-agents/index.d.ts.map +1 -0
- package/dist/agent/sub-agents/index.js +5 -0
- package/dist/agent/sub-agents/index.js.map +1 -0
- package/dist/agent/sub-agents/loader.d.ts +5 -0
- package/dist/agent/sub-agents/loader.d.ts.map +1 -0
- package/dist/agent/sub-agents/loader.js +117 -0
- package/dist/agent/sub-agents/loader.js.map +1 -0
- package/dist/agent/sub-agents/registry.d.ts +14 -0
- package/dist/agent/sub-agents/registry.d.ts.map +1 -0
- package/dist/agent/sub-agents/registry.js +37 -0
- package/dist/agent/sub-agents/registry.js.map +1 -0
- package/dist/agent/sub-agents/runner.d.ts +26 -0
- package/dist/agent/sub-agents/runner.d.ts.map +1 -0
- package/dist/agent/sub-agents/runner.js +287 -0
- package/dist/agent/sub-agents/runner.js.map +1 -0
- package/dist/agent/sub-agents/types.d.ts +63 -0
- package/dist/agent/sub-agents/types.d.ts.map +1 -0
- package/dist/agent/sub-agents/types.js +2 -0
- package/dist/agent/sub-agents/types.js.map +1 -0
- package/dist/agent/system-prompt.d.ts +15 -0
- package/dist/agent/system-prompt.d.ts.map +1 -1
- package/dist/agent/system-prompt.js +161 -0
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/tool-execution.d.ts +4 -3
- package/dist/agent/tool-execution.d.ts.map +1 -1
- package/dist/agent/tool-execution.js +324 -14
- package/dist/agent/tool-execution.js.map +1 -1
- package/dist/agent/tool-result-sanitize.d.ts +12 -0
- package/dist/agent/tool-result-sanitize.d.ts.map +1 -1
- package/dist/agent/tool-result-sanitize.js +70 -0
- package/dist/agent/tool-result-sanitize.js.map +1 -1
- package/dist/config/index.d.ts +6 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -3
- package/dist/index.js.map +1 -1
- package/dist/knowledge/session.d.ts +4 -7
- package/dist/knowledge/session.d.ts.map +1 -1
- package/dist/knowledge/session.js +20 -55
- package/dist/knowledge/session.js.map +1 -1
- package/dist/permissions/index.d.ts +21 -4
- package/dist/permissions/index.d.ts.map +1 -1
- package/dist/permissions/index.js +37 -3
- package/dist/permissions/index.js.map +1 -1
- package/dist/permissions/session-store.d.ts +60 -0
- package/dist/permissions/session-store.d.ts.map +1 -0
- package/dist/permissions/session-store.js +233 -0
- package/dist/permissions/session-store.js.map +1 -0
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +8 -6
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/enter-plan-mode.d.ts +25 -0
- package/dist/tools/enter-plan-mode.d.ts.map +1 -0
- package/dist/tools/enter-plan-mode.js +120 -0
- package/dist/tools/enter-plan-mode.js.map +1 -0
- package/dist/tools/exit-plan-mode.d.ts +13 -0
- package/dist/tools/exit-plan-mode.d.ts.map +1 -0
- package/dist/tools/exit-plan-mode.js +22 -0
- package/dist/tools/exit-plan-mode.js.map +1 -0
- package/dist/tools/grep.d.ts +1 -1
- package/dist/tools/index.d.ts +20 -4
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +7 -1
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/save-knowledge.d.ts +2 -2
- package/dist/tools/shell-provider.d.ts +4 -0
- package/dist/tools/shell-provider.d.ts.map +1 -1
- package/dist/tools/shell-provider.js +2 -0
- package/dist/tools/shell-provider.js.map +1 -1
- package/dist/tools/task.d.ts +14 -0
- package/dist/tools/task.d.ts.map +1 -0
- package/dist/tools/task.js +95 -0
- package/dist/tools/task.js.map +1 -0
- package/dist/tools/todo-write.d.ts +21 -0
- package/dist/tools/todo-write.d.ts.map +1 -0
- package/dist/tools/todo-write.js +117 -0
- package/dist/tools/todo-write.js.map +1 -0
- package/dist/types/index.d.ts +104 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/knowledge/session-usage.d.ts +0 -24
- package/dist/knowledge/session-usage.d.ts.map +0 -1
- package/dist/knowledge/session-usage.js +0 -86
- package/dist/knowledge/session-usage.js.map +0 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export interface AllowRule {
|
|
2
|
+
tool: string;
|
|
3
|
+
pattern: string;
|
|
4
|
+
type: 'exact' | 'prefix' | 'tool';
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Extract a command prefix suitable for prefix-match rules.
|
|
8
|
+
* Returns `null` when no meaningful prefix can be derived.
|
|
9
|
+
*
|
|
10
|
+
* 'git commit -m "fix"' → 'git commit'
|
|
11
|
+
* 'pnpm run build' → 'pnpm run'
|
|
12
|
+
* 'npm install lodash' → 'npm install'
|
|
13
|
+
* 'NODE_ENV=prod npm run dev' → 'npm run'
|
|
14
|
+
* 'powershell -Command "Get-CimInstance ..."' → 'Get-CimInstance'
|
|
15
|
+
* 'powershell -Command "git status"' → 'git'
|
|
16
|
+
* 'ls -la' → null
|
|
17
|
+
* '' → null
|
|
18
|
+
*/
|
|
19
|
+
export declare function extractCommandPrefix(command: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Generate the display label for the "don't ask again" option.
|
|
22
|
+
* Returns `null` when no meaningful rule can be suggested — the UI
|
|
23
|
+
* should hide the "don't ask again" option entirely in that case.
|
|
24
|
+
*
|
|
25
|
+
* Shell with prefix: `git commit:*`
|
|
26
|
+
* Shell without prefix: null (no safe rule to suggest)
|
|
27
|
+
* Write tools (writeFile, edit): `all edits this session` (session-only)
|
|
28
|
+
*/
|
|
29
|
+
export declare function suggestRuleLabel(toolName: string, input: Record<string, unknown>): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Build the AllowRule for a "don't ask again" approval.
|
|
32
|
+
* Returns `null` when no meaningful rule can be built (shell command
|
|
33
|
+
* without derivable prefix) — caller should not save a rule.
|
|
34
|
+
*
|
|
35
|
+
* - Shell with derivable prefix (e.g. `git commit`) → prefix rule
|
|
36
|
+
* - Shell without prefix → null (UI should not offer this option)
|
|
37
|
+
* - writeFile / edit → tool-wide allow (session-only, not persisted)
|
|
38
|
+
*
|
|
39
|
+
* `persist` indicates whether the rule should be saved to disk.
|
|
40
|
+
* Write tools return persist=false (session-only, matching Claude Code).
|
|
41
|
+
*/
|
|
42
|
+
export declare function buildAllowRule(toolName: string, input: Record<string, unknown>): {
|
|
43
|
+
rule: AllowRule;
|
|
44
|
+
persist: boolean;
|
|
45
|
+
} | null;
|
|
46
|
+
export declare function addSessionAllowRule(rule: AllowRule): void;
|
|
47
|
+
export declare function sessionRulesMatch(toolName: string, input: Record<string, unknown>): boolean;
|
|
48
|
+
export declare function clearSessionRules(): void;
|
|
49
|
+
/**
|
|
50
|
+
* Load persisted permission rules from `.x-code/local/permissions.json`
|
|
51
|
+
* into the in-memory store. Safe to call multiple times (deduplicates).
|
|
52
|
+
* Silently no-ops if the file doesn't exist or is malformed.
|
|
53
|
+
*/
|
|
54
|
+
export declare function loadPersistedRules(cwd: string): void;
|
|
55
|
+
/**
|
|
56
|
+
* Persist a new rule to `.x-code/local/permissions.json`.
|
|
57
|
+
* Creates the file if it doesn't exist. Appends without duplicating.
|
|
58
|
+
*/
|
|
59
|
+
export declare function persistRule(cwd: string, rule: AllowRule): void;
|
|
60
|
+
//# sourceMappingURL=session-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.d.ts","sourceRoot":"","sources":["../../src/permissions/session-store.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAA;CAClC;AAYD;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAwBnE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAQhG;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAU7H;AA6ED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAEzD;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAE3F;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAoBpD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,IAAI,CAsB9D"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// @x-code-cli/core — Permission memory with disk persistence.
|
|
2
|
+
//
|
|
3
|
+
// When a user approves a tool call with "don't ask again", the decision
|
|
4
|
+
// is stored as an AllowRule both in-memory AND on disk at
|
|
5
|
+
// `.x-code/local/permissions.json`. On next startup the persisted rules
|
|
6
|
+
// are loaded so approvals survive across sessions.
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import { XCODE_DIR } from '../utils.js';
|
|
10
|
+
// Env-var assignment prefix: VAR=value (unquoted, safe chars only).
|
|
11
|
+
const ENV_VAR_RE = /^[A-Za-z_]\w*=[A-Za-z0-9_./:@-]*\s+/;
|
|
12
|
+
// Matches `powershell -Command "..."` or `powershell -c "..."` (case-insensitive).
|
|
13
|
+
const POWERSHELL_CMD_RE = /^powershell(?:\.exe)?\s+(?:-(?:Command|c)\s+)?["']/i;
|
|
14
|
+
// Extracts the first cmdlet or command name from inside quoted PowerShell.
|
|
15
|
+
// Handles Verb-Noun cmdlets (Get-Process) and plain commands (git, npm).
|
|
16
|
+
const PS_INNER_CMD_RE = /["']?\s*(?:&\s*)?([A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)+|[a-z][a-z0-9._-]*)/;
|
|
17
|
+
/**
|
|
18
|
+
* Extract a command prefix suitable for prefix-match rules.
|
|
19
|
+
* Returns `null` when no meaningful prefix can be derived.
|
|
20
|
+
*
|
|
21
|
+
* 'git commit -m "fix"' → 'git commit'
|
|
22
|
+
* 'pnpm run build' → 'pnpm run'
|
|
23
|
+
* 'npm install lodash' → 'npm install'
|
|
24
|
+
* 'NODE_ENV=prod npm run dev' → 'npm run'
|
|
25
|
+
* 'powershell -Command "Get-CimInstance ..."' → 'Get-CimInstance'
|
|
26
|
+
* 'powershell -Command "git status"' → 'git'
|
|
27
|
+
* 'ls -la' → null
|
|
28
|
+
* '' → null
|
|
29
|
+
*/
|
|
30
|
+
export function extractCommandPrefix(command) {
|
|
31
|
+
let cmd = command.trim();
|
|
32
|
+
while (ENV_VAR_RE.test(cmd)) {
|
|
33
|
+
cmd = cmd.replace(ENV_VAR_RE, '');
|
|
34
|
+
}
|
|
35
|
+
// Handle `powershell -Command "..."`: extract the inner cmdlet/command.
|
|
36
|
+
if (POWERSHELL_CMD_RE.test(cmd)) {
|
|
37
|
+
const quoteStart = cmd.indexOf('"') !== -1 ? cmd.indexOf('"') : cmd.indexOf("'");
|
|
38
|
+
if (quoteStart !== -1) {
|
|
39
|
+
const inner = cmd.slice(quoteStart);
|
|
40
|
+
const m = PS_INNER_CMD_RE.exec(inner);
|
|
41
|
+
if (m?.[1])
|
|
42
|
+
return m[1];
|
|
43
|
+
}
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
const tokens = cmd.split(/\s+/).filter(Boolean);
|
|
47
|
+
if (tokens.length < 2)
|
|
48
|
+
return null;
|
|
49
|
+
const second = tokens[1];
|
|
50
|
+
if (/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(second)) {
|
|
51
|
+
return `${tokens[0]} ${second}`;
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Generate the display label for the "don't ask again" option.
|
|
57
|
+
* Returns `null` when no meaningful rule can be suggested — the UI
|
|
58
|
+
* should hide the "don't ask again" option entirely in that case.
|
|
59
|
+
*
|
|
60
|
+
* Shell with prefix: `git commit:*`
|
|
61
|
+
* Shell without prefix: null (no safe rule to suggest)
|
|
62
|
+
* Write tools (writeFile, edit): `all edits this session` (session-only)
|
|
63
|
+
*/
|
|
64
|
+
export function suggestRuleLabel(toolName, input) {
|
|
65
|
+
if (toolName === 'shell') {
|
|
66
|
+
const cmd = input.command ?? '';
|
|
67
|
+
const prefix = extractCommandPrefix(cmd);
|
|
68
|
+
if (prefix)
|
|
69
|
+
return `${prefix}:*`;
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
return 'all edits this session';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Build the AllowRule for a "don't ask again" approval.
|
|
76
|
+
* Returns `null` when no meaningful rule can be built (shell command
|
|
77
|
+
* without derivable prefix) — caller should not save a rule.
|
|
78
|
+
*
|
|
79
|
+
* - Shell with derivable prefix (e.g. `git commit`) → prefix rule
|
|
80
|
+
* - Shell without prefix → null (UI should not offer this option)
|
|
81
|
+
* - writeFile / edit → tool-wide allow (session-only, not persisted)
|
|
82
|
+
*
|
|
83
|
+
* `persist` indicates whether the rule should be saved to disk.
|
|
84
|
+
* Write tools return persist=false (session-only, matching Claude Code).
|
|
85
|
+
*/
|
|
86
|
+
export function buildAllowRule(toolName, input) {
|
|
87
|
+
if (toolName === 'shell') {
|
|
88
|
+
const cmd = input.command ?? '';
|
|
89
|
+
const prefix = extractCommandPrefix(cmd);
|
|
90
|
+
if (prefix) {
|
|
91
|
+
return { rule: { tool: toolName, pattern: prefix, type: 'prefix' }, persist: true };
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return { rule: { tool: toolName, pattern: '*', type: 'tool' }, persist: false };
|
|
96
|
+
}
|
|
97
|
+
function stripEnvVars(command) {
|
|
98
|
+
let cmd = command.trim();
|
|
99
|
+
while (ENV_VAR_RE.test(cmd)) {
|
|
100
|
+
cmd = cmd.replace(ENV_VAR_RE, '');
|
|
101
|
+
}
|
|
102
|
+
return cmd.trim();
|
|
103
|
+
}
|
|
104
|
+
// ─── Serialization helpers ───
|
|
105
|
+
function ruleToString(rule) {
|
|
106
|
+
if (rule.type === 'tool')
|
|
107
|
+
return `${rule.tool}:*`;
|
|
108
|
+
if (rule.type === 'prefix')
|
|
109
|
+
return `${rule.tool}:${rule.pattern}:*`;
|
|
110
|
+
return `${rule.tool}:=${rule.pattern}`;
|
|
111
|
+
}
|
|
112
|
+
function parseRuleString(s) {
|
|
113
|
+
// tool:* → tool-wide
|
|
114
|
+
const toolWide = s.match(/^([^:]+):\*$/);
|
|
115
|
+
if (toolWide)
|
|
116
|
+
return { tool: toolWide[1], pattern: '*', type: 'tool' };
|
|
117
|
+
// tool:prefix:* → prefix match
|
|
118
|
+
const prefix = s.match(/^([^:]+):(.+):\*$/);
|
|
119
|
+
if (prefix)
|
|
120
|
+
return { tool: prefix[1], pattern: prefix[2], type: 'prefix' };
|
|
121
|
+
// tool:=exact → exact match
|
|
122
|
+
const exact = s.match(/^([^:]+):=(.+)$/);
|
|
123
|
+
if (exact)
|
|
124
|
+
return { tool: exact[1], pattern: exact[2], type: 'exact' };
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
function getPermissionsPath(cwd) {
|
|
128
|
+
return path.join(cwd, XCODE_DIR, 'local', 'permissions.json');
|
|
129
|
+
}
|
|
130
|
+
// ─── Store ───
|
|
131
|
+
class SessionPermissionStore {
|
|
132
|
+
rules = [];
|
|
133
|
+
addRule(rule) {
|
|
134
|
+
const exists = this.rules.some((r) => r.tool === rule.tool && r.pattern === rule.pattern && r.type === rule.type);
|
|
135
|
+
if (!exists)
|
|
136
|
+
this.rules.push(rule);
|
|
137
|
+
}
|
|
138
|
+
matches(toolName, input) {
|
|
139
|
+
for (const rule of this.rules) {
|
|
140
|
+
if (rule.tool !== toolName)
|
|
141
|
+
continue;
|
|
142
|
+
if (rule.type === 'tool')
|
|
143
|
+
return true;
|
|
144
|
+
if (toolName === 'shell') {
|
|
145
|
+
const cmd = input.command ?? '';
|
|
146
|
+
const prefix = extractCommandPrefix(cmd);
|
|
147
|
+
if (rule.type === 'exact' && stripEnvVars(cmd) === rule.pattern)
|
|
148
|
+
return true;
|
|
149
|
+
if (rule.type === 'prefix' && prefix) {
|
|
150
|
+
if (prefix === rule.pattern)
|
|
151
|
+
return true;
|
|
152
|
+
if (prefix.startsWith(rule.pattern + ' '))
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
clear() {
|
|
160
|
+
this.rules = [];
|
|
161
|
+
}
|
|
162
|
+
get size() {
|
|
163
|
+
return this.rules.length;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const store = new SessionPermissionStore();
|
|
167
|
+
export function addSessionAllowRule(rule) {
|
|
168
|
+
store.addRule(rule);
|
|
169
|
+
}
|
|
170
|
+
export function sessionRulesMatch(toolName, input) {
|
|
171
|
+
return store.matches(toolName, input);
|
|
172
|
+
}
|
|
173
|
+
export function clearSessionRules() {
|
|
174
|
+
store.clear();
|
|
175
|
+
}
|
|
176
|
+
// ─── Disk persistence ───
|
|
177
|
+
/**
|
|
178
|
+
* Load persisted permission rules from `.x-code/local/permissions.json`
|
|
179
|
+
* into the in-memory store. Safe to call multiple times (deduplicates).
|
|
180
|
+
* Silently no-ops if the file doesn't exist or is malformed.
|
|
181
|
+
*/
|
|
182
|
+
export function loadPersistedRules(cwd) {
|
|
183
|
+
const filePath = getPermissionsPath(cwd);
|
|
184
|
+
let raw;
|
|
185
|
+
try {
|
|
186
|
+
raw = fs.readFileSync(filePath, 'utf-8');
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
let data;
|
|
192
|
+
try {
|
|
193
|
+
data = JSON.parse(raw);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (!Array.isArray(data.allow))
|
|
199
|
+
return;
|
|
200
|
+
for (const entry of data.allow) {
|
|
201
|
+
if (typeof entry !== 'string')
|
|
202
|
+
continue;
|
|
203
|
+
const rule = parseRuleString(entry);
|
|
204
|
+
if (rule)
|
|
205
|
+
store.addRule(rule);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Persist a new rule to `.x-code/local/permissions.json`.
|
|
210
|
+
* Creates the file if it doesn't exist. Appends without duplicating.
|
|
211
|
+
*/
|
|
212
|
+
export function persistRule(cwd, rule) {
|
|
213
|
+
const filePath = getPermissionsPath(cwd);
|
|
214
|
+
const ruleStr = ruleToString(rule);
|
|
215
|
+
let data = { allow: [] };
|
|
216
|
+
try {
|
|
217
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
218
|
+
const parsed = JSON.parse(raw);
|
|
219
|
+
if (Array.isArray(parsed.allow)) {
|
|
220
|
+
data.allow = parsed.allow.filter((s) => typeof s === 'string');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// File doesn't exist or is malformed — start fresh.
|
|
225
|
+
}
|
|
226
|
+
if (data.allow.includes(ruleStr))
|
|
227
|
+
return;
|
|
228
|
+
data.allow.push(ruleStr);
|
|
229
|
+
const dir = path.dirname(filePath);
|
|
230
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
231
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/permissions/session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-user.d.ts","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO;;;;;;
|
|
1
|
+
{"version":3,"file":"ask-user.d.ts","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO;;;;;;SAqBlB,CAAA"}
|
package/dist/tools/ask-user.js
CHANGED
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
import { tool } from 'ai';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
export const askUser = tool({
|
|
5
|
-
description: 'Ask the user a clarifying question with multiple-choice options. Use when you need user input to decide between approaches.',
|
|
5
|
+
description: 'Ask the user a clarifying question with multiple-choice options. Use when you need user input to decide between approaches. In **plan mode** this is also the primary "interview" tool — call it after every meaningful analysis or exploration to hand decision points back to the user with concrete next-step choices.',
|
|
6
6
|
inputSchema: z.object({
|
|
7
|
-
question: z.string().describe('The question to ask'),
|
|
7
|
+
question: z.string().describe('The question to ask. Markdown is rendered.'),
|
|
8
8
|
options: z
|
|
9
9
|
.array(z.object({
|
|
10
|
-
label: z.string().describe('Option label (1-
|
|
11
|
-
description: z
|
|
10
|
+
label: z.string().describe('Option label (1-8 words). Shown as the choice itself.'),
|
|
11
|
+
description: z
|
|
12
|
+
.string()
|
|
13
|
+
.describe('One-line tradeoff or scope hint shown beneath the label.'),
|
|
12
14
|
}))
|
|
13
15
|
.min(2)
|
|
14
|
-
.max(
|
|
15
|
-
.describe('Choices
|
|
16
|
+
.max(6)
|
|
17
|
+
.describe('Choices. DO NOT include an "Other"/freeform/custom-input option — the UI auto-appends one as the last row, so adding your own creates a duplicate. 2-6 entries — for plan-mode interview menus 4-6 entries with both action options ("plan high-priority items") and meta options ("just discuss further") work best.'),
|
|
16
18
|
}),
|
|
17
19
|
// No execute — through callback to trigger UI rendering
|
|
18
20
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ask-user.js","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC;IAC1B,WAAW,EACT,
|
|
1
|
+
{"version":3,"file":"ask-user.js","sourceRoot":"","sources":["../../src/tools/ask-user.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC;IAC1B,WAAW,EACT,2TAA2T;IAC7T,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,4CAA4C,CAAC;QAC3E,OAAO,EAAE,CAAC;aACP,KAAK,CACJ,CAAC,CAAC,MAAM,CAAC;YACP,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,uDAAuD,CAAC;YACnF,WAAW,EAAE,CAAC;iBACX,MAAM,EAAE;iBACR,QAAQ,CAAC,0DAA0D,CAAC;SACxE,CAAC,CACH;aACA,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,CAAC,CAAC;aACN,QAAQ,CACP,uTAAuT,CACxT;KACJ,CAAC;IACF,wDAAwD;CACzD,CAAC,CAAA"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The tool description below is the heart of plan-mode auto-trigger
|
|
2
|
+
* behavior — it's what the model reads in the tool list and uses to
|
|
3
|
+
* decide whether to recommend plan mode. Ported (with naming
|
|
4
|
+
* adjustments for our tool surface) from Claude Code's
|
|
5
|
+
* `EnterPlanModeTool/prompt.ts` external-user prompt
|
|
6
|
+
* (`/d/res/claude-code/src/tools/EnterPlanModeTool/prompt.ts:16-99`).
|
|
7
|
+
*
|
|
8
|
+
* WHY THIS IS LONG: a one-line description ("use for complex tasks")
|
|
9
|
+
* produces a model that almost never calls the tool — it has no
|
|
10
|
+
* concrete trigger pattern to match against the user's request. CC's
|
|
11
|
+
* prompt deliberately includes 7 numbered criteria, multiple worked
|
|
12
|
+
* examples per criterion, and an explicit "PREFER plan mode unless
|
|
13
|
+
* simple" anchor — that's what gets the model to actually recommend
|
|
14
|
+
* plan mode for refactors, new features, architectural decisions,
|
|
15
|
+
* etc. The token cost (~600 tokens in tool list each turn) is what
|
|
16
|
+
* buys the auto-trigger behavior; without it plan mode is dead UX.
|
|
17
|
+
*
|
|
18
|
+
* No `execute` field — the side-effect (asking the user to confirm,
|
|
19
|
+
* mutating LoopState.permissionMode, invalidating the system-prompt
|
|
20
|
+
* cache) is handled manually in `processToolCalls`. Same pattern as
|
|
21
|
+
* `askUser`. */
|
|
22
|
+
export declare const enterPlanMode: import("ai").Tool<{
|
|
23
|
+
topic?: string | undefined;
|
|
24
|
+
}, never>;
|
|
25
|
+
//# sourceMappingURL=enter-plan-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enter-plan-mode.d.ts","sourceRoot":"","sources":["../../src/tools/enter-plan-mode.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;iBAoBiB;AACjB,eAAO,MAAM,aAAa;;SAgGxB,CAAA"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// @x-code-cli/core — enterPlanMode tool (mode switch, no execute — handled in agent loop)
|
|
2
|
+
import { tool } from 'ai';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/** The tool description below is the heart of plan-mode auto-trigger
|
|
5
|
+
* behavior — it's what the model reads in the tool list and uses to
|
|
6
|
+
* decide whether to recommend plan mode. Ported (with naming
|
|
7
|
+
* adjustments for our tool surface) from Claude Code's
|
|
8
|
+
* `EnterPlanModeTool/prompt.ts` external-user prompt
|
|
9
|
+
* (`/d/res/claude-code/src/tools/EnterPlanModeTool/prompt.ts:16-99`).
|
|
10
|
+
*
|
|
11
|
+
* WHY THIS IS LONG: a one-line description ("use for complex tasks")
|
|
12
|
+
* produces a model that almost never calls the tool — it has no
|
|
13
|
+
* concrete trigger pattern to match against the user's request. CC's
|
|
14
|
+
* prompt deliberately includes 7 numbered criteria, multiple worked
|
|
15
|
+
* examples per criterion, and an explicit "PREFER plan mode unless
|
|
16
|
+
* simple" anchor — that's what gets the model to actually recommend
|
|
17
|
+
* plan mode for refactors, new features, architectural decisions,
|
|
18
|
+
* etc. The token cost (~600 tokens in tool list each turn) is what
|
|
19
|
+
* buys the auto-trigger behavior; without it plan mode is dead UX.
|
|
20
|
+
*
|
|
21
|
+
* No `execute` field — the side-effect (asking the user to confirm,
|
|
22
|
+
* mutating LoopState.permissionMode, invalidating the system-prompt
|
|
23
|
+
* cache) is handled manually in `processToolCalls`. Same pattern as
|
|
24
|
+
* `askUser`. */
|
|
25
|
+
export const enterPlanMode = tool({
|
|
26
|
+
description: `Use this tool proactively when you're about to start a non-trivial implementation task. Getting user sign-off on your approach before writing code prevents wasted effort and ensures alignment. This tool transitions you into plan mode where you can explore the codebase and design an implementation approach for user approval.
|
|
27
|
+
|
|
28
|
+
## When to Use This Tool
|
|
29
|
+
|
|
30
|
+
**Prefer using enterPlanMode** for implementation tasks unless they're simple. Use it when ANY of these conditions apply:
|
|
31
|
+
|
|
32
|
+
1. **New Feature Implementation**: Adding meaningful new functionality
|
|
33
|
+
- Example: "Add a logout button" - where should it go? What should happen on click?
|
|
34
|
+
- Example: "Add form validation" - what rules? What error messages?
|
|
35
|
+
|
|
36
|
+
2. **Multiple Valid Approaches**: The task can be solved in several different ways
|
|
37
|
+
- Example: "Add caching to the API" - could use Redis, in-memory, file-based, etc.
|
|
38
|
+
- Example: "Improve performance" - many optimization strategies possible
|
|
39
|
+
|
|
40
|
+
3. **Code Modifications**: Changes that affect existing behavior or structure
|
|
41
|
+
- Example: "Update the login flow" - what exactly should change?
|
|
42
|
+
- Example: "Refactor this component" - what's the target architecture?
|
|
43
|
+
|
|
44
|
+
4. **Architectural Decisions**: The task requires choosing between patterns or technologies
|
|
45
|
+
- Example: "Add real-time updates" - WebSockets vs SSE vs polling
|
|
46
|
+
- Example: "Implement state management" - Redux vs Context vs custom solution
|
|
47
|
+
|
|
48
|
+
5. **Multi-File Changes**: The task will likely touch more than 2-3 files
|
|
49
|
+
- Example: "Refactor the authentication system"
|
|
50
|
+
- Example: "Add a new API endpoint with tests"
|
|
51
|
+
|
|
52
|
+
6. **Unclear Requirements**: You need to explore before understanding the full scope
|
|
53
|
+
- Example: "Make the app faster" - need to profile and identify bottlenecks
|
|
54
|
+
- Example: "Fix the bug in checkout" - need to investigate root cause
|
|
55
|
+
|
|
56
|
+
7. **User Preferences Matter**: The implementation could reasonably go multiple ways
|
|
57
|
+
- If you would use askUser to clarify the approach, use enterPlanMode instead
|
|
58
|
+
- Plan mode lets you explore first, then present options with context
|
|
59
|
+
|
|
60
|
+
## When NOT to Use This Tool
|
|
61
|
+
|
|
62
|
+
Only skip enterPlanMode for simple tasks:
|
|
63
|
+
- Single-line or few-line fixes (typos, obvious bugs, small tweaks)
|
|
64
|
+
- Adding a single function with clear requirements
|
|
65
|
+
- Tasks where the user has given very specific, detailed instructions
|
|
66
|
+
- Pure research / "what does X do" questions — just answer them directly
|
|
67
|
+
|
|
68
|
+
## What Happens in Plan Mode
|
|
69
|
+
|
|
70
|
+
In plan mode, you'll:
|
|
71
|
+
1. Thoroughly explore the codebase using readFile, glob, grep, and listDir
|
|
72
|
+
2. Understand existing patterns and architecture
|
|
73
|
+
3. Design an implementation approach
|
|
74
|
+
4. Use askUser to clarify approaches with the user when needed
|
|
75
|
+
5. Write the plan incrementally to a session-scoped plan file
|
|
76
|
+
6. Exit plan mode with exitPlanMode when ready to implement
|
|
77
|
+
|
|
78
|
+
## Examples
|
|
79
|
+
|
|
80
|
+
### GOOD - Use enterPlanMode:
|
|
81
|
+
User: "Add user authentication to the app"
|
|
82
|
+
- Requires architectural decisions (session vs JWT, where to store tokens, middleware structure)
|
|
83
|
+
|
|
84
|
+
User: "Optimize the database queries"
|
|
85
|
+
- Multiple approaches possible, need to profile first, significant impact
|
|
86
|
+
|
|
87
|
+
User: "Implement dark mode"
|
|
88
|
+
- Architectural decision on theme system, affects many components
|
|
89
|
+
|
|
90
|
+
User: "Add a delete button to the user profile"
|
|
91
|
+
- Seems simple but involves: where to place it, confirmation dialog, API call, error handling, state updates
|
|
92
|
+
|
|
93
|
+
User: "Update the error handling in the API"
|
|
94
|
+
- Affects multiple files, user should approve the approach
|
|
95
|
+
|
|
96
|
+
### BAD - Don't use enterPlanMode:
|
|
97
|
+
User: "Fix the typo in the README"
|
|
98
|
+
- Straightforward, no planning needed
|
|
99
|
+
|
|
100
|
+
User: "Add a console.log to debug this function"
|
|
101
|
+
- Simple, obvious implementation
|
|
102
|
+
|
|
103
|
+
User: "What files handle routing?"
|
|
104
|
+
- Research / Q&A task, not implementation — just answer
|
|
105
|
+
|
|
106
|
+
## Important Notes
|
|
107
|
+
|
|
108
|
+
- This tool REQUIRES user approval — they must consent to entering plan mode (an approval dialog appears).
|
|
109
|
+
- If unsure whether to use it, err on the side of planning — it's better to get alignment upfront than to redo work.
|
|
110
|
+
- Do not call enterPlanMode if you are already in plan mode (check the system prompt; if you see plan-mode instructions you are already in it).`,
|
|
111
|
+
inputSchema: z.object({
|
|
112
|
+
topic: z
|
|
113
|
+
.string()
|
|
114
|
+
.min(1)
|
|
115
|
+
.max(60)
|
|
116
|
+
.optional()
|
|
117
|
+
.describe('STRONGLY RECOMMENDED. A 3-5 word English filename slug summarizing the user\'s task. Lowercase, hyphen-separated, no spaces or special chars. The plan file is named `<topic>-<YYYYMMDD-HHMMSS>.md` so this makes the file identifiable in `ls .x-code/plans/`. Translate non-English requests into English keywords (e.g. user asks "重构这个项目" → topic: "refactor-x-code-cli"; user asks "加 OAuth 登录" → topic: "add-oauth-login"). Omit only when you genuinely cannot summarize — the file then falls back to timestamp-only naming.'),
|
|
118
|
+
}),
|
|
119
|
+
});
|
|
120
|
+
//# sourceMappingURL=enter-plan-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enter-plan-mode.js","sourceRoot":"","sources":["../../src/tools/enter-plan-mode.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAC1F,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;;;;;;;;;;;;;iBAoBiB;AACjB,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC;IAChC,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;gJAoFiI;IAC9I,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,KAAK,EAAE,CAAC;aACL,MAAM,EAAE;aACR,GAAG,CAAC,CAAC,CAAC;aACN,GAAG,CAAC,EAAE,CAAC;aACP,QAAQ,EAAE;aACV,QAAQ,CACP,sgBAAsgB,CACvgB;KACJ,CAAC;CACH,CAAC,CAAA"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Present the plan to the user for approval. The user sees a Yes/No
|
|
2
|
+
* dialog with the plan body inline; on Yes, the session leaves plan mode
|
|
3
|
+
* and the next turn can write code; on No, the session stays in plan
|
|
4
|
+
* mode and the model is told to revise.
|
|
5
|
+
*
|
|
6
|
+
* No `execute` field — the dispatch path in `processToolCalls` reads the
|
|
7
|
+
* plan file, calls `callbacks.onPlanApprovalRequest(planText)`, and
|
|
8
|
+
* feeds the verdict back as a synthetic tool result so the model knows
|
|
9
|
+
* whether to proceed or iterate. */
|
|
10
|
+
export declare const exitPlanMode: import("ai").Tool<{
|
|
11
|
+
plan?: string | undefined;
|
|
12
|
+
}, never>;
|
|
13
|
+
//# sourceMappingURL=exit-plan-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-plan-mode.d.ts","sourceRoot":"","sources":["../../src/tools/exit-plan-mode.ts"],"names":[],"mappings":"AAKA;;;;;;;;qCAQqC;AACrC,eAAO,MAAM,YAAY;;SAWvB,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// @x-code-cli/core — exitPlanMode tool (user-approval gate, no execute — handled in agent loop)
|
|
2
|
+
import { tool } from 'ai';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
/** Present the plan to the user for approval. The user sees a Yes/No
|
|
5
|
+
* dialog with the plan body inline; on Yes, the session leaves plan mode
|
|
6
|
+
* and the next turn can write code; on No, the session stays in plan
|
|
7
|
+
* mode and the model is told to revise.
|
|
8
|
+
*
|
|
9
|
+
* No `execute` field — the dispatch path in `processToolCalls` reads the
|
|
10
|
+
* plan file, calls `callbacks.onPlanApprovalRequest(planText)`, and
|
|
11
|
+
* feeds the verdict back as a synthetic tool result so the model knows
|
|
12
|
+
* whether to proceed or iterate. */
|
|
13
|
+
export const exitPlanMode = tool({
|
|
14
|
+
description: 'Use this tool when you are in plan mode and have finished writing your plan to the plan file and are ready for user approval. This tool reads the plan from the file you wrote during planning — pass an optional `plan` parameter only if you want to override what is in the file. The user sees the plan content in an approval dialog and chooses Yes/No. The model cannot leave plan mode without user approval; if rejected, revise the plan file (using edit) and call this again. Do NOT use this for research / Q&A — only when the user has asked you to implement something and you have a complete plan written to the plan file. Do NOT use askUser to ask "is this plan okay?" — exitPlanMode is the only correct way to request plan approval.',
|
|
15
|
+
inputSchema: z.object({
|
|
16
|
+
plan: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('Optional override for the plan body. By default the plan body comes from the plan file you wrote during planning — only pass this argument if you want to use different content (rare).'),
|
|
20
|
+
}),
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=exit-plan-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exit-plan-mode.js","sourceRoot":"","sources":["../../src/tools/exit-plan-mode.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAA;AAEzB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;;qCAQqC;AACrC,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC;IAC/B,WAAW,EACT,+tBAA+tB;IACjuB,WAAW,EAAE,CAAC,CAAC,MAAM,CAAC;QACpB,IAAI,EAAE,CAAC;aACJ,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,yLAAyL,CAC1L;KACJ,CAAC;CACH,CAAC,CAAA"}
|
package/dist/tools/grep.d.ts
CHANGED
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { askUser } from './ask-user.js';
|
|
2
2
|
import { edit } from './edit.js';
|
|
3
|
+
import { enterPlanMode } from './enter-plan-mode.js';
|
|
4
|
+
import { exitPlanMode } from './exit-plan-mode.js';
|
|
3
5
|
import { glob } from './glob.js';
|
|
4
6
|
import { grep } from './grep.js';
|
|
5
7
|
import { listDir } from './list-dir.js';
|
|
6
8
|
import { readFile } from './read-file.js';
|
|
7
9
|
import { saveKnowledge } from './save-knowledge.js';
|
|
8
10
|
import { shell } from './shell.js';
|
|
11
|
+
import { todoWrite } from './todo-write.js';
|
|
9
12
|
import { webFetch } from './web-fetch.js';
|
|
10
13
|
import { webSearch } from './web-search.js';
|
|
11
14
|
import { writeFile } from './write-file.js';
|
|
@@ -63,8 +66,8 @@ export declare const toolRegistry: {
|
|
|
63
66
|
}, string>;
|
|
64
67
|
grep: import("ai").Tool<{
|
|
65
68
|
pattern: string;
|
|
66
|
-
path?: string | undefined;
|
|
67
69
|
glob?: string | undefined;
|
|
70
|
+
path?: string | undefined;
|
|
68
71
|
maxResults?: number | undefined;
|
|
69
72
|
}, string>;
|
|
70
73
|
listDir: import("ai").Tool<{
|
|
@@ -86,14 +89,27 @@ export declare const toolRegistry: {
|
|
|
86
89
|
question: string;
|
|
87
90
|
}, never>;
|
|
88
91
|
saveKnowledge: import("ai").Tool<{
|
|
89
|
-
category: "
|
|
92
|
+
category: "project" | "user" | "feedback" | "reference";
|
|
90
93
|
action: "add" | "delete";
|
|
91
94
|
key: string;
|
|
92
95
|
fact: string;
|
|
93
|
-
scope: "
|
|
96
|
+
scope: "global" | "project";
|
|
94
97
|
}, string>;
|
|
98
|
+
enterPlanMode: import("ai").Tool<{
|
|
99
|
+
topic?: string | undefined;
|
|
100
|
+
}, never>;
|
|
101
|
+
exitPlanMode: import("ai").Tool<{
|
|
102
|
+
plan?: string | undefined;
|
|
103
|
+
}, never>;
|
|
104
|
+
todoWrite: import("ai").Tool<{
|
|
105
|
+
todos: {
|
|
106
|
+
content?: string | undefined;
|
|
107
|
+
status?: "pending" | "in_progress" | "completed" | undefined;
|
|
108
|
+
activeForm?: string | undefined;
|
|
109
|
+
}[];
|
|
110
|
+
}, never>;
|
|
95
111
|
};
|
|
96
|
-
export { readFile, writeFile, edit, shell, glob, grep, listDir, webSearch, webFetch, askUser, saveKnowledge, };
|
|
112
|
+
export { readFile, writeFile, edit, shell, glob, grep, listDir, webSearch, webFetch, askUser, saveKnowledge, enterPlanMode, exitPlanMode, todoWrite, };
|
|
97
113
|
export { MAX_TOOL_RESULT_LINES, MAX_TOOL_RESULT_BYTES, MAX_AGGREGATE_TOOL_RESULT_BYTES, truncateToolResult, } from './truncate.js';
|
|
98
114
|
export type { TruncateOptions } from './truncate.js';
|
|
99
115
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAE3C,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAexB,CAAA;AAED,OAAO,EACL,QAAQ,EACR,SAAS,EACT,IAAI,EACJ,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,OAAO,EACP,SAAS,EACT,QAAQ,EACR,OAAO,EACP,aAAa,EACb,aAAa,EACb,YAAY,EACZ,SAAS,GACV,CAAA;AAED,OAAO,EACL,qBAAqB,EACrB,qBAAqB,EACrB,+BAA+B,EAC/B,kBAAkB,GACnB,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA"}
|