thincoder 0.12.2 → 0.12.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -6
- package/package.json +3 -3
- package/src/advisor/history.mjs +112 -0
- package/src/advisor/messages.mjs +182 -0
- package/src/advisor/repos.mjs +133 -0
- package/src/advisor/run.mjs +346 -0
- package/src/advisor.mjs +109 -509
- package/src/agent/completion.mjs +134 -0
- package/src/agent/dispatch.mjs +54 -7
- package/src/agent/post-turn.mjs +70 -0
- package/src/agent/setup.mjs +95 -6
- package/src/agent-tools/advisor.mjs +159 -12
- package/src/agent-tools/eng.mjs +64 -0
- package/src/agent-tools/subagent.mjs +73 -3
- package/src/agent-tools/task.mjs +45 -6
- package/src/agent-tools/verify.mjs +18 -0
- package/src/agent-tools.mjs +1 -0
- package/src/agent.mjs +152 -161
- package/src/cli/make-agent.mjs +1 -0
- package/src/cli/setup-wizard.mjs +1 -0
- package/src/config.mjs +34 -4
- package/src/context.mjs +47 -13
- package/src/generate-title.mjs +44 -0
- package/src/prompts/advisor-design.md +43 -0
- package/src/prompts/advisor-round1.md +11 -4
- package/src/prompts/advisor-round2.md +12 -7
- package/src/prompts/advisor-round3.md +11 -6
- package/src/prompts/coder.md +9 -3
- package/src/prompts/discipline.md +12 -96
- package/src/prompts/eng-coder.md +34 -0
- package/src/prompts/engineering-sub.md +12 -0
- package/src/prompts/engineering.md +96 -0
- package/src/prompts/main.md +1 -1
- package/src/prompts/methodology-template.md +39 -0
- package/src/prompts/plan.md +2 -2
- package/src/prompts/system.md +43 -61
- package/src/provider/core.mjs +58 -2
- package/src/session.mjs +291 -94
- package/src/skills.mjs +48 -15
- package/src/tools/apply_patch.md +1 -1
- package/src/tools/checklist.mjs +4 -3
- package/src/tools/codemode.mjs +23 -11
- package/src/tools/delete.md +1 -0
- package/src/tools/edit.md +1 -1
- package/src/tools/execute.md +5 -0
- package/src/tools/file.mjs +4 -0
- package/src/tools/git.md +15 -0
- package/src/tools/git.mjs +1 -6
- package/src/tools/lint.md +8 -0
- package/src/tools/linter.mjs +1 -5
- package/src/tools/lsp.md +7 -0
- package/src/tools/lsp.mjs +8 -9
- package/src/tools/patch.mjs +1 -29
- package/src/tools/read_image.md +5 -1
- package/src/tools/system.mjs +1 -1
- package/src/tools/web.mjs +3 -3
- package/src/tui/agent-turn.mjs +184 -66
- package/src/tui/ansi.mjs +4 -0
- package/src/tui/clipboard.mjs +9 -0
- package/src/tui/cmd-config.mjs +14 -26
- package/src/tui/cmd-eng.mjs +44 -0
- package/src/tui/cmd-exit.mjs +1 -1
- package/src/tui/cmd-fold.mjs +3 -4
- package/src/tui/cmd-model.mjs +11 -6
- package/src/tui/cmd-new.mjs +5 -5
- package/src/tui/cmd-session.mjs +21 -11
- package/src/tui/cmd-think.mjs +1 -0
- package/src/tui/index.mjs +20 -9
- package/src/tui/key-handler.mjs +177 -9
- package/src/tui/layout.mjs +5 -5
- package/src/tui/markdown.mjs +52 -0
- package/src/tui/pickers.mjs +190 -45
- package/src/tui/render-conversation.mjs +54 -13
- package/src/tui/render-frame.mjs +39 -12
- package/src/tui/render-loop.mjs +2 -1
- package/src/tui/render.mjs +13 -7
- package/src/tui/slash-commands.mjs +11 -7
- package/src/tui/startup.mjs +4 -3
- package/src/tui/wizard.mjs +3 -0
- package/src/tools/checkpoint.md +0 -15
- package/src/tools/git_diff.md +0 -11
- package/src/tools/git_log.md +0 -10
- package/src/tools/git_status.md +0 -8
- package/src/tools/linter.md +0 -13
- package/src/tools/syntax_check.md +0 -10
package/src/tools/codemode.mjs
CHANGED
|
@@ -24,14 +24,17 @@
|
|
|
24
24
|
import { Script, createContext } from "node:vm"
|
|
25
25
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, statSync, readdirSync } from "node:fs"
|
|
26
26
|
import { join, dirname, relative, resolve } from "node:path"
|
|
27
|
-
import { globToRegex, normalizeEOL, isPrivateHost } from "./shared.mjs"
|
|
27
|
+
import { DESC, globToRegex, normalizeEOL, isPrivateHost } from "./shared.mjs"
|
|
28
28
|
|
|
29
29
|
const MAX_OUTPUT = 50_000
|
|
30
30
|
const MAX_SCRIPT = 50_000
|
|
31
31
|
const DEFAULT_TIMEOUT = 30_000
|
|
32
32
|
|
|
33
|
-
/** SSRF-safe fetch: only http/https, private IP rejection, 10s timeout.
|
|
34
|
-
|
|
33
|
+
/** SSRF-safe fetch: only http/https, private IP rejection, 10s timeout.
|
|
34
|
+
* Validation throws SYNCHRONOUSLY so the vm sandbox's try/catch can catch it —
|
|
35
|
+
* an async throw here would become an unhandled rejection and crash the host process,
|
|
36
|
+
* and the rejection message would never reach the model. */
|
|
37
|
+
function sandboxFetch(url) {
|
|
35
38
|
const parsed = new URL(url)
|
|
36
39
|
if (!["http:", "https:"].includes(parsed.protocol)) {
|
|
37
40
|
throw new Error(`CodeMode fetch: protocol not allowed: ${parsed.protocol}`)
|
|
@@ -40,6 +43,10 @@ async function sandboxFetch(url) {
|
|
|
40
43
|
if (isPrivateHost(parsed.hostname)) {
|
|
41
44
|
throw new Error(`CodeMode fetch: private/internal host not allowed: ${parsed.hostname}`)
|
|
42
45
|
}
|
|
46
|
+
return doFetch(url)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function doFetch(url) {
|
|
43
50
|
const ctrl = new AbortController()
|
|
44
51
|
const timer = setTimeout(() => ctrl.abort(), 10_000)
|
|
45
52
|
try {
|
|
@@ -53,9 +60,7 @@ async function sandboxFetch(url) {
|
|
|
53
60
|
|
|
54
61
|
export const codeModeTool = {
|
|
55
62
|
name: "execute",
|
|
56
|
-
description:
|
|
57
|
-
"Execute sandboxed JavaScript code. Use this to compose multiple file operations into one call — " +
|
|
58
|
-
"read, write, glob, grep, and log results. No network or system access. Max 30s timeout, 50KB output.",
|
|
63
|
+
description: DESC("execute"),
|
|
59
64
|
parameters: {
|
|
60
65
|
type: "object",
|
|
61
66
|
properties: {
|
|
@@ -96,6 +101,11 @@ export const codeModeTool = {
|
|
|
96
101
|
return abs
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
// Block dynamic imports: check for import() syntax before execution
|
|
105
|
+
if (/\bimport\s*\(/.test(code)) {
|
|
106
|
+
return "Error: dynamic import() is not allowed in CodeMode sandbox. Use the provided readFile/writeFile/glob/grep/fetch functions instead."
|
|
107
|
+
}
|
|
108
|
+
|
|
99
109
|
const sandbox = createContext({
|
|
100
110
|
readFile: (p) => {
|
|
101
111
|
const abs = safePath(p)
|
|
@@ -149,14 +159,16 @@ export const codeModeTool = {
|
|
|
149
159
|
}
|
|
150
160
|
},
|
|
151
161
|
fetch: sandboxFetch,
|
|
162
|
+
// Block process and require access
|
|
163
|
+
require: () => { throw new Error("require() is not available in CodeMode sandbox") },
|
|
164
|
+
process: undefined,
|
|
152
165
|
})
|
|
153
166
|
|
|
154
167
|
try {
|
|
155
|
-
const script = new Script(code, {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
})
|
|
159
|
-
script.runInContext(sandbox)
|
|
168
|
+
const script = new Script(code, { filename: "codemode.js" })
|
|
169
|
+
// timeout belongs on runInContext — the Script constructor ignores it,
|
|
170
|
+
// so passing it there let runaway scripts (while(true)) hang the process forever.
|
|
171
|
+
script.runInContext(sandbox, { timeout: timeoutMs })
|
|
160
172
|
return output.join("\n") || "(no output)"
|
|
161
173
|
} catch (err) {
|
|
162
174
|
const out = output.join("\n")
|
package/src/tools/delete.md
CHANGED
|
@@ -2,6 +2,7 @@ Delete a file. Use when the agent created a temporary or junk file that should b
|
|
|
2
2
|
|
|
3
3
|
Parameters:
|
|
4
4
|
- path (required): File path, relative to cwd or absolute
|
|
5
|
+
- force: Allow deleting git-tracked files (default false)
|
|
5
6
|
|
|
6
7
|
Notes:
|
|
7
8
|
- Untracked or non-git files are deleted immediately
|
package/src/tools/edit.md
CHANGED
|
@@ -15,6 +15,6 @@ Parameters:
|
|
|
15
15
|
- replace_all: Replace all occurrences instead of just one (default false)
|
|
16
16
|
|
|
17
17
|
Notes:
|
|
18
|
-
- Prefer this over write for targeted edits — it's safer and keeps
|
|
18
|
+
- Prefer this over write for targeted edits — it's safer and keeps changes targeted
|
|
19
19
|
- If old_string matches zero times: error. If it matches multiple times without replace_all: error — add more surrounding context to make it unique
|
|
20
20
|
- Never fabricate the old_string — copy it verbatim from the actual file using read first
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Execute sandboxed JavaScript code. Use this to compose multiple file operations into one call — read, write, glob, grep, and log results. No network or system access. Max 30s timeout, 50KB output.
|
|
2
|
+
|
|
3
|
+
Parameters:
|
|
4
|
+
- code (required): JavaScript code to execute in the sandbox. Use provided functions: readFile(path), writeFile(path, content), glob(pattern), grep(pattern, file), log(...args).
|
|
5
|
+
- timeoutMs: Timeout in milliseconds (default 30000, max 60000)
|
package/src/tools/file.mjs
CHANGED
|
@@ -122,6 +122,7 @@ export const writeTool = {
|
|
|
122
122
|
required: ["path", "content"],
|
|
123
123
|
},
|
|
124
124
|
readonly: false,
|
|
125
|
+
touchedPaths(args) { return args.path ? [args.path] : [] },
|
|
125
126
|
async execute(args, ctx) {
|
|
126
127
|
const abs = resolveInCwd(ctx, args.path)
|
|
127
128
|
await mkdir(dirname(abs), { recursive: true })
|
|
@@ -149,6 +150,7 @@ export const editTool = {
|
|
|
149
150
|
required: ["path", "old_string", "new_string"],
|
|
150
151
|
},
|
|
151
152
|
readonly: false,
|
|
153
|
+
touchedPaths(args) { return args.path ? [args.path] : [] },
|
|
152
154
|
async execute(args, ctx) {
|
|
153
155
|
const abs = resolveInCwd(ctx, args.path)
|
|
154
156
|
if (!args.old_string) {
|
|
@@ -194,6 +196,7 @@ export const insertAfterTool = {
|
|
|
194
196
|
required: ["path", "content"],
|
|
195
197
|
},
|
|
196
198
|
readonly: false,
|
|
199
|
+
touchedPaths(args) { return args.path ? [args.path] : [] },
|
|
197
200
|
async execute(args, ctx) {
|
|
198
201
|
const abs = resolveInCwd(ctx, args.path)
|
|
199
202
|
const text = normalizeEOL(await readFile(abs, "utf8"))
|
|
@@ -257,6 +260,7 @@ export const hashlineEditTool = {
|
|
|
257
260
|
required: ["path", "old_hashes", "new_content"],
|
|
258
261
|
},
|
|
259
262
|
readonly: false,
|
|
263
|
+
touchedPaths(args) { return args.path ? [args.path] : [] },
|
|
260
264
|
async execute(args, ctx) {
|
|
261
265
|
const abs = resolveInCwd(ctx, args.path)
|
|
262
266
|
if (!args.old_hashes?.length) throw new Error("old_hashes must not be empty — read the file with hashes=true to get line hashes")
|
package/src/tools/git.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Run a git command. Use this to see uncommitted changes, staged changes, diff against a ref, recent commits, or manage checkpoints. Only works inside a git repository.
|
|
2
|
+
- action='diff': Show unified diff — what changed since last commit. Set staged=true for staged-only diff, ref=<ref> to compare against a specific commit/branch, path=<dir> to scope to a file or directory.
|
|
3
|
+
- action='status': Show working tree state — staged, unstaged, untracked files, and conflicts. Returns categorized lists.
|
|
4
|
+
- action='log': Show recent commit history. Set count to limit, oneline=true for compact format, path=<file> to see history of one file.
|
|
5
|
+
- action='checkpoint': Manage git-based snapshots. Use checkpointAction to choose: list (overview), create (snapshot now), rewind (restore snapshot by id), cat (read a file from a snapshot).
|
|
6
|
+
|
|
7
|
+
Parameters:
|
|
8
|
+
- action (required): diff / status / log / checkpoint
|
|
9
|
+
- staged: (diff) Show staged changes instead of working tree
|
|
10
|
+
- path: (diff/log/checkpoint:cat/checkpoint:rewind) File or directory to scope to
|
|
11
|
+
- ref: (diff) Compare against this ref (default HEAD)
|
|
12
|
+
- count: (log) Number of commits (default 10)
|
|
13
|
+
- oneline: (log) One-line-per-commit format
|
|
14
|
+
- checkpointAction: (checkpoint) list snapshots / create one / restore by id / read file from snapshot
|
|
15
|
+
- checkpointId: (checkpoint) Snapshot id — required for rewind and cat; optional for list (shows file tree)
|
package/src/tools/git.mjs
CHANGED
|
@@ -9,12 +9,7 @@ import { join } from "node:path";
|
|
|
9
9
|
|
|
10
10
|
export const gitTool = {
|
|
11
11
|
name: "git",
|
|
12
|
-
description:
|
|
13
|
-
"Run a git command. Use this to see uncommitted changes, staged changes, diff against a ref, recent commits, or manage checkpoints. Only works inside a git repository.\n" +
|
|
14
|
-
"- action='diff': Show unified diff — what changed since last commit. Set staged=true for staged-only diff, ref=<ref> to compare against a specific commit/branch, path=<dir> to scope to a file or directory.\n" +
|
|
15
|
-
"- action='status': Show working tree state — staged, unstaged, untracked files, and conflicts. Returns categorized lists.\n" +
|
|
16
|
-
"- action='log': Show recent commit history. Set count to limit, oneline=true for compact format, path=<file> to see history of one file.\n" +
|
|
17
|
-
"- action='checkpoint': Manage git-based snapshots. Use checkpointAction to choose: list (overview), create (snapshot now), rewind (restore snapshot by id), cat (read a file from a snapshot).",
|
|
12
|
+
description: DESC("git"),
|
|
18
13
|
parameters: {
|
|
19
14
|
type: "object",
|
|
20
15
|
properties: {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
Run the appropriate linter/checker for a file. Auto-detects based on file extension and project config.
|
|
2
|
+
Without 'full', runs a fast node --check (JS/TS syntax only, catches parse errors in milliseconds).
|
|
3
|
+
With 'full', runs the language-aware cascade: eslint → tsc –noEmit → node --check (JS/TS/TSX); ruff (Python); cargo check (Rust); go vet (Go).
|
|
4
|
+
Use the fast default after every write/edit; use 'full' before declaring a task complete.
|
|
5
|
+
|
|
6
|
+
Parameters:
|
|
7
|
+
- path: File to check (default: most recently modified file)
|
|
8
|
+
- full: Run the full language-aware cascade instead of just node --check (default false)
|
package/src/tools/linter.mjs
CHANGED
|
@@ -5,11 +5,7 @@ import { join, relative } from "node:path"
|
|
|
5
5
|
|
|
6
6
|
export const lintTool = {
|
|
7
7
|
name: "lint",
|
|
8
|
-
description:
|
|
9
|
-
"Run the appropriate linter/checker for a file. Auto-detects based on file extension and project config.\n" +
|
|
10
|
-
"Without 'full', runs a fast node --check (JS/TS syntax only, catches parse errors in milliseconds).\n" +
|
|
11
|
-
"With 'full', runs the language-aware cascade: eslint → tsc –noEmit → node --check (JS/TS/TSX); ruff (Python); cargo check (Rust); go vet (Go).\n" +
|
|
12
|
-
"Use the fast default after every write/edit; use 'full' before declaring a task complete.",
|
|
8
|
+
description: DESC("lint"),
|
|
13
9
|
parameters: {
|
|
14
10
|
type: "object",
|
|
15
11
|
properties: {
|
package/src/tools/lsp.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
LSP code intelligence: go to definition, find references, hover info, document symbols, diagnostics. Use this to understand code structure without grep-guessing function locations or type shapes.
|
|
2
|
+
|
|
3
|
+
Parameters:
|
|
4
|
+
- subcommand (required): LSP operation — "definition" | "references" | "hover" | "symbols" | "diagnostics"
|
|
5
|
+
- uri (required): Target file path (relative to project root)
|
|
6
|
+
- line: 1-based line number (for definition/references/hover)
|
|
7
|
+
- character: 1-based character offset (for definition/references/hover)
|
package/src/tools/lsp.mjs
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import { spawn } from "node:child_process"
|
|
18
18
|
import { existsSync, readFileSync } from "node:fs"
|
|
19
19
|
import { join, extname } from "node:path"
|
|
20
|
+
import { DESC } from "./shared.mjs"
|
|
20
21
|
|
|
21
22
|
// ---- JSON-RPC transport over stdio ----
|
|
22
23
|
|
|
@@ -184,9 +185,7 @@ async function ensureOpen(proc, uri, ext) {
|
|
|
184
185
|
|
|
185
186
|
export const lspTool = {
|
|
186
187
|
name: "lsp",
|
|
187
|
-
description:
|
|
188
|
-
"LSP code intelligence: go to definition, find references, hover info, document symbols, diagnostics. " +
|
|
189
|
-
"Use this to understand code structure without grep-guessing function locations or type shapes.",
|
|
188
|
+
description: DESC("lsp"),
|
|
190
189
|
parameters: {
|
|
191
190
|
type: "object",
|
|
192
191
|
properties: {
|
|
@@ -240,7 +239,7 @@ export const lspTool = {
|
|
|
240
239
|
textDocument: { uri },
|
|
241
240
|
position: { line: args.line - 1, character: args.character - 1 },
|
|
242
241
|
}, 10)
|
|
243
|
-
if (!res?.result) return "
|
|
242
|
+
if (!res?.result) return "(no definition found)"
|
|
244
243
|
const locs = Array.isArray(res.result) ? res.result : [res.result]
|
|
245
244
|
return locs.map((l) => {
|
|
246
245
|
const path = l.uri.replace(/^file:\/\/\//, "").replace(/%3A/, ":")
|
|
@@ -255,7 +254,7 @@ export const lspTool = {
|
|
|
255
254
|
position: { line: args.line - 1, character: args.character - 1 },
|
|
256
255
|
context: { includeDeclaration: false },
|
|
257
256
|
}, 10)
|
|
258
|
-
if (!res?.result?.length) return "
|
|
257
|
+
if (!res?.result?.length) return "(no references found)"
|
|
259
258
|
return res.result.slice(0, 50).map((l) => {
|
|
260
259
|
const path = l.uri.replace(/^file:\/\/\//, "").replace(/%3A/, ":")
|
|
261
260
|
return `${path}:${l.range.start.line + 1}:${l.range.start.character + 1}`
|
|
@@ -268,7 +267,7 @@ export const lspTool = {
|
|
|
268
267
|
textDocument: { uri },
|
|
269
268
|
position: { line: args.line - 1, character: args.character - 1 },
|
|
270
269
|
}, 10)
|
|
271
|
-
if (!res?.result?.contents) return "
|
|
270
|
+
if (!res?.result?.contents) return "(no hover info)"
|
|
272
271
|
const contents = res.result.contents
|
|
273
272
|
if (typeof contents === "string") return contents
|
|
274
273
|
if (Array.isArray(contents)) return contents.map((c) => typeof c === "string" ? c : c.value).join("\n")
|
|
@@ -280,7 +279,7 @@ export const lspTool = {
|
|
|
280
279
|
const res = await request(proc, "textDocument/documentSymbol", {
|
|
281
280
|
textDocument: { uri },
|
|
282
281
|
}, 10)
|
|
283
|
-
if (!res?.result?.length) return "
|
|
282
|
+
if (!res?.result?.length) return "(no symbols found)"
|
|
284
283
|
function render(nodes, depth) {
|
|
285
284
|
const lines = []
|
|
286
285
|
for (const n of nodes) {
|
|
@@ -295,7 +294,7 @@ export const lspTool = {
|
|
|
295
294
|
|
|
296
295
|
case "diagnostics": {
|
|
297
296
|
const diags = proc._diagnostics?.[uri]
|
|
298
|
-
if (!diags?.length) return "
|
|
297
|
+
if (!diags?.length) return "(no diagnostics)"
|
|
299
298
|
return diags.slice(0, 30).map((d) => {
|
|
300
299
|
const sev = { 1: "ERROR", 2: "WARN", 3: "INFO", 4: "HINT" }[d.severity] || "?"
|
|
301
300
|
return `L${d.range.start.line + 1}: ${sev}: ${d.message}${d.code ? ` [${d.code}]` : ""}`
|
|
@@ -306,7 +305,7 @@ export const lspTool = {
|
|
|
306
305
|
return `Unknown subcommand: ${args.subcommand}`
|
|
307
306
|
}
|
|
308
307
|
} catch (err) {
|
|
309
|
-
return `
|
|
308
|
+
return `lsp error: ${err.message}`
|
|
310
309
|
}
|
|
311
310
|
},
|
|
312
311
|
}
|
package/src/tools/patch.mjs
CHANGED
|
@@ -159,36 +159,7 @@ export const applyPatchTool = {
|
|
|
159
159
|
},
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
// ---------------------------------------------------------------- syntax_check
|
|
163
162
|
|
|
164
|
-
export const syntaxCheckTool = {
|
|
165
|
-
name: "syntax_check",
|
|
166
|
-
description: DESC("syntax_check"),
|
|
167
|
-
parameters: {
|
|
168
|
-
type: "object",
|
|
169
|
-
properties: {
|
|
170
|
-
path: { type: "string", description: "File path (.js/.mjs/.cjs only)" },
|
|
171
|
-
},
|
|
172
|
-
required: ["path"],
|
|
173
|
-
},
|
|
174
|
-
readonly: true,
|
|
175
|
-
execute(args, ctx) {
|
|
176
|
-
const abs = resolveInCwd(ctx, args.path)
|
|
177
|
-
if (!/\.(?:[mc]?js)$/.test(abs)) {
|
|
178
|
-
return `syntax_check only supports .js/.mjs/.cjs files; ${args.path} skipped.`
|
|
179
|
-
}
|
|
180
|
-
try {
|
|
181
|
-
execFileSync(process.execPath, ["--check", abs], {
|
|
182
|
-
cwd: ctx.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
|
|
183
|
-
})
|
|
184
|
-
return `Syntax OK: ${args.path}`
|
|
185
|
-
} catch (e) {
|
|
186
|
-
// node --check writes errors to stderr
|
|
187
|
-
const msg = (e.stderr || e.stdout || e.message || "").trim()
|
|
188
|
-
return `Syntax error in ${args.path}:\n${msg || "(unknown)"}`
|
|
189
|
-
}
|
|
190
|
-
},
|
|
191
|
-
}
|
|
192
163
|
|
|
193
164
|
// ---------------------------------------------------------------- bash
|
|
194
165
|
|
|
@@ -204,6 +175,7 @@ export const deleteTool = {
|
|
|
204
175
|
required: ["path"],
|
|
205
176
|
},
|
|
206
177
|
readonly: false,
|
|
178
|
+
touchedPaths(args) { return args.path ? [args.path] : [] },
|
|
207
179
|
async execute(args, ctx) {
|
|
208
180
|
const abs = resolveInCwd(ctx, args.path)
|
|
209
181
|
let s
|
package/src/tools/read_image.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
Read an image file and return it as multimodal content visible to the model. Use this to view screenshots, UI mockups, diagrams, or any visual content. The model only sees images through this tool — it cannot "see" files directly. Supports png, jpg, gif, webp, bmp, svg. The image is base64-encoded and included in the response. Large images (>20MB) are rejected.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Parameters:
|
|
4
|
+
- path (required): Path to image file (relative to cwd or absolute). Supports png, jpg, gif, webp, bmp, svg.
|
|
5
|
+
|
|
6
|
+
Notes:
|
|
7
|
+
- This tool only works with models that support vision/image input (Kimi K3, Qwen3.7, MiniMax M3). Pure text models (DeepSeek V4, GLM-5) will receive an error.
|
package/src/tools/system.mjs
CHANGED
|
@@ -378,7 +378,7 @@ export const lsTool = {
|
|
|
378
378
|
}),
|
|
379
379
|
)
|
|
380
380
|
rows.sort((a, b) => (a.dir === b.dir ? a.name.localeCompare(b.name) : a.dir ? -1 : 1))
|
|
381
|
-
if (rows.length === 0) return "(
|
|
381
|
+
if (rows.length === 0) return "(no entries)"
|
|
382
382
|
const out = rows.map((r) => `${r.dir ? "d" : "-"} ${r.name.padEnd(40)} ${String(r.size).padStart(10)} ${r.mtime}`)
|
|
383
383
|
return truncate(out.join("\n"))
|
|
384
384
|
},
|
package/src/tools/web.mjs
CHANGED
|
@@ -77,12 +77,12 @@ export const websearchTool = {
|
|
|
77
77
|
const engine = ENGINES.find(e => e.name === args.engine)
|
|
78
78
|
if (!engine) return `Unknown engine '${args.engine}'. Available: ${ENGINE_NAMES.join(", ")}`
|
|
79
79
|
const fetched = await fetchEngine(engine, args.query, page, ctx)
|
|
80
|
-
if (!fetched || fetched.results.length === 0) return
|
|
81
|
-
return truncate(fetched.results.slice(0, limit).map((r, i) => `${i + 1}. ${r.title}\n ${r.href}\n ${r.snippet}`).join("\n\n"))
|
|
80
|
+
if (!fetched || fetched.results.length === 0) return "(no results)"
|
|
81
|
+
return truncate(fetched.results.slice(0, limit).map((r, i) => `${i + 1}. [${engine.label}] ${r.title}\n ${r.href}\n ${r.snippet}`).join("\n\n"))
|
|
82
82
|
}
|
|
83
83
|
const promises = ENGINES.map(e => fetchEngine(e, args.query, 1, ctx))
|
|
84
84
|
const fetched = (await Promise.all(promises)).filter(Boolean)
|
|
85
|
-
if (fetched.length === 0) return "(no results
|
|
85
|
+
if (fetched.length === 0) return "(no results)"
|
|
86
86
|
const merged = [], indexes = fetched.map(() => 0)
|
|
87
87
|
let done = false
|
|
88
88
|
while (!done && merged.length < limit) {
|