@namzu/sdk 52.4.0 → 52.5.0
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/CHANGELOG.md +27 -0
- package/dist/authorization/gate.d.ts +11 -0
- package/dist/authorization/gate.d.ts.map +1 -1
- package/dist/authorization/gate.js +35 -0
- package/dist/authorization/gate.js.map +1 -1
- package/dist/authorization/removal.js +11 -6
- package/dist/authorization/removal.js.map +1 -1
- package/dist/authorization/rules.d.ts.map +1 -1
- package/dist/authorization/rules.js +30 -2
- package/dist/authorization/rules.js.map +1 -1
- package/dist/authorization/shell-paths.d.ts +20 -0
- package/dist/authorization/shell-paths.d.ts.map +1 -1
- package/dist/authorization/shell-paths.js +254 -52
- package/dist/authorization/shell-paths.js.map +1 -1
- package/dist/bridge/acp/server.d.ts +8 -0
- package/dist/bridge/acp/server.d.ts.map +1 -1
- package/dist/bridge/acp/server.js +7 -0
- package/dist/bridge/acp/server.js.map +1 -1
- package/dist/manager/resident/outbox.d.ts +4 -4
- package/dist/public-runtime.d.ts +2 -1
- package/dist/public-runtime.d.ts.map +1 -1
- package/dist/public-runtime.js +4 -1
- package/dist/public-runtime.js.map +1 -1
- package/dist/public-types.d.ts +1 -0
- package/dist/public-types.d.ts.map +1 -1
- package/dist/runtime/query/auto-review/classifier.d.ts +130 -0
- package/dist/runtime/query/auto-review/classifier.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/classifier.js +244 -0
- package/dist/runtime/query/auto-review/classifier.js.map +1 -0
- package/dist/runtime/query/auto-review/index.d.ts +7 -0
- package/dist/runtime/query/auto-review/index.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/index.js +4 -0
- package/dist/runtime/query/auto-review/index.js.map +1 -0
- package/dist/runtime/query/auto-review/prompt.d.ts +20 -0
- package/dist/runtime/query/auto-review/prompt.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/prompt.js +89 -0
- package/dist/runtime/query/auto-review/prompt.js.map +1 -0
- package/dist/runtime/query/auto-review/rules.d.ts +49 -0
- package/dist/runtime/query/auto-review/rules.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/rules.js +81 -0
- package/dist/runtime/query/auto-review/rules.js.map +1 -0
- package/dist/runtime/query/auto-review/transcript.d.ts +59 -0
- package/dist/runtime/query/auto-review/transcript.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/transcript.js +116 -0
- package/dist/runtime/query/auto-review/transcript.js.map +1 -0
- package/dist/runtime/query/auto-review/verdict.d.ts +21 -0
- package/dist/runtime/query/auto-review/verdict.d.ts.map +1 -0
- package/dist/runtime/query/auto-review/verdict.js +45 -0
- package/dist/runtime/query/auto-review/verdict.js.map +1 -0
- package/dist/runtime/query/executor.d.ts +7 -0
- package/dist/runtime/query/executor.d.ts.map +1 -1
- package/dist/runtime/query/executor.js +26 -2
- package/dist/runtime/query/executor.js.map +1 -1
- package/dist/runtime/query/index.js +2 -2
- package/dist/runtime/query/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/context.d.ts +0 -9
- package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/context.js +11 -2
- package/dist/runtime/query/iteration/phases/context.js.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/tool-review.js +39 -0
- package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
- package/dist/runtime/query/path-escalation.d.ts.map +1 -1
- package/dist/runtime/query/path-escalation.js +43 -10
- package/dist/runtime/query/path-escalation.js.map +1 -1
- package/dist/runtime/query/resume-pending.d.ts.map +1 -1
- package/dist/runtime/query/resume-pending.js +14 -0
- package/dist/runtime/query/resume-pending.js.map +1 -1
- package/dist/runtime/query/review-policy.d.ts +88 -1
- package/dist/runtime/query/review-policy.d.ts.map +1 -1
- package/dist/runtime/query/review-policy.js +504 -11
- package/dist/runtime/query/review-policy.js.map +1 -1
- package/dist/tools/builtins/bash.d.ts.map +1 -1
- package/dist/tools/builtins/bash.js +2 -0
- package/dist/tools/builtins/bash.js.map +1 -1
- package/dist/tools/builtins/classifier-input.d.ts +9 -0
- package/dist/tools/builtins/classifier-input.d.ts.map +1 -0
- package/dist/tools/builtins/classifier-input.js +20 -0
- package/dist/tools/builtins/classifier-input.js.map +1 -0
- package/dist/tools/builtins/code-analysis.d.ts +2 -0
- package/dist/tools/builtins/code-analysis.d.ts.map +1 -1
- package/dist/tools/builtins/code-analysis.js +196 -119
- package/dist/tools/builtins/code-analysis.js.map +1 -1
- package/dist/tools/builtins/computer-use.d.ts +10 -10
- package/dist/tools/builtins/edit.d.ts.map +1 -1
- package/dist/tools/builtins/edit.js +12 -0
- package/dist/tools/builtins/edit.js.map +1 -1
- package/dist/tools/builtins/monitor.d.ts.map +1 -1
- package/dist/tools/builtins/monitor.js +3 -0
- package/dist/tools/builtins/monitor.js.map +1 -1
- package/dist/tools/builtins/web.d.ts.map +1 -1
- package/dist/tools/builtins/web.js +3 -0
- package/dist/tools/builtins/web.js.map +1 -1
- package/dist/tools/builtins/write-file.d.ts.map +1 -1
- package/dist/tools/builtins/write-file.js +6 -0
- package/dist/tools/builtins/write-file.js.map +1 -1
- package/dist/tools/defineTool.d.ts +2 -0
- package/dist/tools/defineTool.d.ts.map +1 -1
- package/dist/tools/defineTool.js +1 -0
- package/dist/tools/defineTool.js.map +1 -1
- package/dist/types/acp/index.d.ts +7 -0
- package/dist/types/acp/index.d.ts.map +1 -1
- package/dist/types/code-analysis/index.d.ts +54 -3
- package/dist/types/code-analysis/index.d.ts.map +1 -1
- package/dist/types/hitl/index.d.ts +45 -1
- package/dist/types/hitl/index.d.ts.map +1 -1
- package/dist/types/hitl/index.js.map +1 -1
- package/dist/types/tool/index.d.ts +16 -0
- package/dist/types/tool/index.d.ts.map +1 -1
- package/dist/types/tool/index.js.map +1 -1
- package/package.json +1 -1
- package/src/authorization/gate.ts +35 -0
- package/src/authorization/removal.ts +12 -6
- package/src/authorization/rules.ts +27 -2
- package/src/authorization/shell-paths.ts +264 -51
- package/src/bridge/acp/server.ts +20 -0
- package/src/public-runtime.ts +18 -0
- package/src/public-types.ts +11 -0
- package/src/runtime/query/auto-review/classifier.ts +404 -0
- package/src/runtime/query/auto-review/index.ts +23 -0
- package/src/runtime/query/auto-review/prompt.ts +97 -0
- package/src/runtime/query/auto-review/rules.ts +106 -0
- package/src/runtime/query/auto-review/transcript.ts +159 -0
- package/src/runtime/query/auto-review/verdict.ts +50 -0
- package/src/runtime/query/executor.ts +29 -2
- package/src/runtime/query/index.ts +2 -2
- package/src/runtime/query/iteration/phases/context.ts +17 -3
- package/src/runtime/query/iteration/phases/tool-review.ts +41 -0
- package/src/runtime/query/path-escalation.ts +44 -9
- package/src/runtime/query/resume-pending.ts +14 -0
- package/src/runtime/query/review-policy.ts +591 -14
- package/src/tools/builtins/bash.ts +3 -0
- package/src/tools/builtins/classifier-input.ts +19 -0
- package/src/tools/builtins/code-analysis.ts +211 -119
- package/src/tools/builtins/edit.ts +13 -0
- package/src/tools/builtins/monitor.ts +4 -0
- package/src/tools/builtins/web.ts +3 -0
- package/src/tools/builtins/write-file.ts +6 -0
- package/src/tools/defineTool.ts +3 -0
- package/src/types/acp/index.ts +7 -0
- package/src/types/code-analysis/index.ts +51 -3
- package/src/types/hitl/index.ts +49 -1
- package/src/types/tool/index.ts +17 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for `ToolDefinition.classifierInput`: what the auto-mode
|
|
3
|
+
* classifier is shown of a call to a built-in tool.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Cuts `text` to `limit` characters, saying how much was left out. */
|
|
7
|
+
export function clip(text: string, limit: number): string {
|
|
8
|
+
if (text.length <= limit) return text
|
|
9
|
+
return `${text.slice(0, limit)}... [${text.length - limit} more characters]`
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** The characters a change adds and removes, in a form short enough to read at a glance. */
|
|
13
|
+
export function describeChange(removed: string | undefined, added: string | undefined): string {
|
|
14
|
+
const parts: string[] = []
|
|
15
|
+
if (removed !== undefined && removed !== '')
|
|
16
|
+
parts.push(`removes ${JSON.stringify(clip(removed, 200))}`)
|
|
17
|
+
if (added !== undefined && added !== '') parts.push(`adds ${JSON.stringify(clip(added, 300))}`)
|
|
18
|
+
return parts.length > 0 ? parts.join(', ') : 'no text change'
|
|
19
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { realpath, stat } from 'node:fs/promises'
|
|
2
|
+
import { isAbsolute, relative, sep } from 'node:path'
|
|
2
3
|
import { z } from 'zod'
|
|
3
4
|
|
|
4
5
|
import type {
|
|
@@ -112,7 +113,14 @@ const functionSourceOp = z.object({
|
|
|
112
113
|
),
|
|
113
114
|
function_id: z.number().int().min(0).optional(),
|
|
114
115
|
path: z.string().optional(),
|
|
115
|
-
name: z.string().min(1).optional(),
|
|
116
|
+
name: z.string().min(1).optional().describe('`name` or `Class.method`.'),
|
|
117
|
+
line: z
|
|
118
|
+
.number()
|
|
119
|
+
.int()
|
|
120
|
+
.min(1)
|
|
121
|
+
.optional()
|
|
122
|
+
.describe('With `column`: cut a long function around this position, not at its end.'),
|
|
123
|
+
column: z.number().int().min(1).optional(),
|
|
116
124
|
max_chars: maxChars,
|
|
117
125
|
...paging,
|
|
118
126
|
})
|
|
@@ -123,7 +131,10 @@ const callersOp = z.object({
|
|
|
123
131
|
.describe(
|
|
124
132
|
'Call sites of a callee TEXT (not scope-aware), each with its caller. Per file by default.',
|
|
125
133
|
),
|
|
126
|
-
name: z
|
|
134
|
+
name: z
|
|
135
|
+
.string()
|
|
136
|
+
.min(1)
|
|
137
|
+
.describe('Callee text: `foo`, `obj.foo`, `this.foo`; or a method as `Class.method`.'),
|
|
127
138
|
scope: z
|
|
128
139
|
.enum(['file', 'workspace'])
|
|
129
140
|
.optional()
|
|
@@ -207,122 +218,176 @@ export const CodeAnalysisTool = defineTool({
|
|
|
207
218
|
}
|
|
208
219
|
const root = owner.root
|
|
209
220
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
221
|
+
const outcome = await (async (): Promise<ToolResult> => {
|
|
222
|
+
switch (input.operation) {
|
|
223
|
+
case 'index': {
|
|
224
|
+
const result = await provider.index({
|
|
225
|
+
root,
|
|
226
|
+
// A narrower path indexes just that part of the root, which is
|
|
227
|
+
// how a root too big to index whole is covered in steps.
|
|
228
|
+
...(target !== root ? { scope: target } : {}),
|
|
229
|
+
...(input.force ? { force: true } : {}),
|
|
230
|
+
...signalOf(context),
|
|
231
|
+
})
|
|
232
|
+
return render(result, (value) => {
|
|
233
|
+
const f = value.files
|
|
234
|
+
const lines = [
|
|
235
|
+
`Indexed ${value.root}: ${f.indexed} files (${f.parsed} parsed, ${f.unchanged} unchanged, ${f.removed} removed, ${f.skipped} skipped, ${f.withParseErrors} with parse errors) in ${value.durationMs} ms.`,
|
|
236
|
+
`${value.functions} functions, ${value.strings} string literals, ${value.calls} calls. ${value.persisted ? 'Cached on disk.' : 'Held in memory only; it is gone when this process exits.'}`,
|
|
237
|
+
]
|
|
238
|
+
if (value.truncated) lines.push(value.truncated)
|
|
239
|
+
const reasons = Object.entries(value.skippedReasons)
|
|
240
|
+
if (reasons.length > 0) {
|
|
241
|
+
lines.push(`Skipped: ${reasons.map(([why, n]) => `${n} ${why}`).join(', ')}.`)
|
|
242
|
+
}
|
|
243
|
+
return lines
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
case 'find_literal': {
|
|
247
|
+
const result = await provider.findLiteral({
|
|
248
|
+
root,
|
|
249
|
+
text: input.text,
|
|
250
|
+
path: target,
|
|
251
|
+
...(input.case_sensitive === false ? { caseSensitive: false } : {}),
|
|
252
|
+
...pageArgs(input),
|
|
253
|
+
...signalOf(context),
|
|
254
|
+
})
|
|
255
|
+
return renderPage(
|
|
256
|
+
result,
|
|
257
|
+
(hit) => {
|
|
258
|
+
const shown = JSON.stringify(hit.value) + (hit.valueTruncated ? ' (cut)' : '')
|
|
259
|
+
const kind = hit.kind === 'string' ? '' : ` (${hit.kind})`
|
|
260
|
+
const inside = hit.function ? ` in ${shortFunction(hit.function)}` : ' at top level'
|
|
261
|
+
return `${hit.line}:${hit.column} ${shown}${kind}${inside}${staleMark(hit.stale)}`
|
|
262
|
+
},
|
|
263
|
+
(hit) => relativeTo(root, hit.path),
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
case 'enclosing_function': {
|
|
267
|
+
const result = await provider.enclosingFunction({
|
|
268
|
+
root,
|
|
269
|
+
path: target,
|
|
270
|
+
line: input.line,
|
|
271
|
+
...(input.column !== undefined ? { column: input.column } : {}),
|
|
272
|
+
maxChars: input.max_chars ?? DEFAULT_MAX_CHARS,
|
|
273
|
+
...signalOf(context),
|
|
274
|
+
})
|
|
275
|
+
return render(result, (value) => {
|
|
276
|
+
if (!value.function) {
|
|
277
|
+
const place = `${relativeTo(root, target)}:${input.line}:${input.column ?? 1}`
|
|
278
|
+
return [
|
|
279
|
+
value.inClass !== undefined
|
|
280
|
+
? `${place} is in the body of class ${value.inClass}, outside every method; not inside any function.`
|
|
281
|
+
: `${place} is not inside any function.`,
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
const lines = [describeFunction(value.function, root)]
|
|
285
|
+
if (value.parents.length > 0) {
|
|
286
|
+
lines.push(`parents: ${value.parents.map(shortFunction).join(' < ')}`)
|
|
287
|
+
}
|
|
288
|
+
if (value.source) lines.push(...sourceLines(value.source))
|
|
289
|
+
return lines
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
case 'function_source': {
|
|
293
|
+
if (input.function_id === undefined && !(input.path !== undefined && input.name)) {
|
|
294
|
+
return fail('function_source needs `function_id`, or `path` together with `name`.')
|
|
262
295
|
}
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
296
|
+
const result = await provider.functionSource({
|
|
297
|
+
root,
|
|
298
|
+
...(input.function_id !== undefined ? { id: input.function_id } : {}),
|
|
299
|
+
...(input.name ? { name: input.name, path: target } : {}),
|
|
300
|
+
...(input.line !== undefined
|
|
301
|
+
? {
|
|
302
|
+
around: {
|
|
303
|
+
line: input.line,
|
|
304
|
+
...(input.column !== undefined ? { column: input.column } : {}),
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
: {}),
|
|
308
|
+
maxChars: input.max_chars ?? DEFAULT_MAX_CHARS,
|
|
309
|
+
...pageArgs(input),
|
|
310
|
+
...signalOf(context),
|
|
311
|
+
})
|
|
312
|
+
return renderPage(result, (item) =>
|
|
313
|
+
[
|
|
314
|
+
describeFunction(item.function, root),
|
|
315
|
+
item.parents.length > 0
|
|
316
|
+
? `parents: ${item.parents.map(shortFunction).join(' < ')}`
|
|
317
|
+
: undefined,
|
|
318
|
+
...sourceLines(item.source),
|
|
319
|
+
]
|
|
320
|
+
.filter((l): l is string => l !== undefined)
|
|
321
|
+
.join('\n'),
|
|
322
|
+
)
|
|
323
|
+
}
|
|
324
|
+
case 'callers_of': {
|
|
325
|
+
const scope = input.scope ?? 'file'
|
|
326
|
+
if (scope === 'file' && input.path === undefined) {
|
|
327
|
+
return fail(
|
|
328
|
+
'callers_of looks in one file by default, so it needs `path`. Pass `scope: "workspace"` to search the whole index; it matches by name only and will mix unrelated functions that share one.',
|
|
329
|
+
)
|
|
266
330
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
331
|
+
const result = await provider.callersOf({
|
|
332
|
+
root,
|
|
333
|
+
name: input.name,
|
|
334
|
+
scope,
|
|
335
|
+
...(input.path !== undefined ? { path: target } : {}),
|
|
336
|
+
...(input.match ? { match: input.match } : {}),
|
|
337
|
+
...pageArgs(input),
|
|
338
|
+
...signalOf(context),
|
|
339
|
+
})
|
|
340
|
+
return renderPage(
|
|
341
|
+
result,
|
|
342
|
+
(site) => {
|
|
343
|
+
const inside = site.caller ? ` in ${shortFunction(site.caller)}` : ' at top level'
|
|
344
|
+
const how = site.kind === 'new' ? 'new ' : ''
|
|
345
|
+
const tag =
|
|
346
|
+
site.kind === 'call-via'
|
|
347
|
+
? ' [call/apply/bind]'
|
|
348
|
+
: site.kind === 'tagged'
|
|
349
|
+
? ' [tagged template]'
|
|
350
|
+
: ''
|
|
351
|
+
const label = site.resolution === 'text-only' ? ' [text-only]' : ''
|
|
352
|
+
return `${site.line}:${site.column} ${how}${site.callee}(${site.argumentCount} arg${site.argumentCount === 1 ? '' : 's'})${tag}${label}${inside}${staleMark(site.stale)}`
|
|
353
|
+
},
|
|
354
|
+
(site) => relativeTo(root, site.path),
|
|
355
|
+
)
|
|
274
356
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
357
|
+
case 'imports': {
|
|
358
|
+
const result = await provider.imports({
|
|
359
|
+
root,
|
|
360
|
+
path: target,
|
|
361
|
+
...pageArgs(input),
|
|
362
|
+
...signalOf(context),
|
|
363
|
+
})
|
|
364
|
+
return render(
|
|
365
|
+
result,
|
|
366
|
+
(page) =>
|
|
367
|
+
page.items.length === 0
|
|
368
|
+
? [page.total === 0 ? 'No results.' : 'No results on this page.']
|
|
369
|
+
: [
|
|
370
|
+
relativeTo(root, target),
|
|
371
|
+
...page.items.map((edge) => ` ${describeEdge(edge)}`),
|
|
372
|
+
page.nextCursor !== undefined
|
|
373
|
+
? `-- ${page.items.length} of ${page.total} shown. next cursor: ${page.nextCursor}`
|
|
374
|
+
: `-- ${page.items.length} of ${page.total} shown. last page.`,
|
|
375
|
+
],
|
|
376
|
+
(page) => ({
|
|
377
|
+
total: page.total,
|
|
378
|
+
count: page.items.length,
|
|
379
|
+
items: page.items,
|
|
380
|
+
...(page.nextCursor !== undefined ? { nextCursor: page.nextCursor } : {}),
|
|
381
|
+
}),
|
|
300
382
|
)
|
|
301
383
|
}
|
|
302
|
-
const result = await provider.callersOf({
|
|
303
|
-
root,
|
|
304
|
-
name: input.name,
|
|
305
|
-
scope,
|
|
306
|
-
...(input.path !== undefined ? { path: target } : {}),
|
|
307
|
-
...(input.match ? { match: input.match } : {}),
|
|
308
|
-
...pageArgs(input),
|
|
309
|
-
...signalOf(context),
|
|
310
|
-
})
|
|
311
|
-
return renderPage(result, (site) => {
|
|
312
|
-
const inside = site.caller ? ` in ${describeFunction(site.caller)}` : ' at top level'
|
|
313
|
-
return `${site.path}:${site.line}:${site.column} ${site.kind === 'new' ? 'new ' : ''}${site.callee}(${site.argumentCount} arg${site.argumentCount === 1 ? '' : 's'})${inside}${staleMark(site.stale)}`
|
|
314
|
-
})
|
|
315
|
-
}
|
|
316
|
-
case 'imports': {
|
|
317
|
-
const result = await provider.imports({
|
|
318
|
-
root,
|
|
319
|
-
path: target,
|
|
320
|
-
...pageArgs(input),
|
|
321
|
-
...signalOf(context),
|
|
322
|
-
})
|
|
323
|
-
return renderPage(result, (edge) => describeEdge(edge, target))
|
|
324
384
|
}
|
|
325
|
-
}
|
|
385
|
+
})()
|
|
386
|
+
// With one root, a relative path is unambiguous. With several, the same
|
|
387
|
+
// `src/a.ts` could be in any of them, so the reader is told which this is.
|
|
388
|
+
return roots.length > 1 && outcome.success
|
|
389
|
+
? { ...outcome, output: `paths are relative to ${root}\n${outcome.output}` }
|
|
390
|
+
: outcome
|
|
326
391
|
},
|
|
327
392
|
})
|
|
328
393
|
|
|
@@ -381,13 +446,27 @@ async function owningRoot(roots: readonly string[], target: string): Promise<Own
|
|
|
381
446
|
return sawFile ? { kind: 'file' } : { kind: 'none' }
|
|
382
447
|
}
|
|
383
448
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
449
|
+
/** `path` relative to the root (POSIX separators), or `path` itself when it is not under it. */
|
|
450
|
+
function relativeTo(root: string, path: string): string {
|
|
451
|
+
const rel = relative(root, path)
|
|
452
|
+
// `..` as a whole segment is outside the root; `..x` is a directory named so.
|
|
453
|
+
if (rel === '' || rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel)) return path
|
|
454
|
+
return sep === '/' ? rel : rel.split(sep).join('/')
|
|
387
455
|
}
|
|
388
456
|
|
|
389
|
-
|
|
390
|
-
|
|
457
|
+
/** `Svc.init#12 L10-40`: enough to cite and to ask for by id, no repeated path or kind. */
|
|
458
|
+
function shortFunction(fn: AnalysisFunction): string {
|
|
459
|
+
const lines = fn.endLine === fn.line ? `L${fn.line}` : `L${fn.line}-${fn.endLine}`
|
|
460
|
+
return `${fn.qualifiedName ?? fn.name ?? '(anonymous)'}#${fn.id} ${lines}`
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** The full line a single function gets: kind and place included. */
|
|
464
|
+
function describeFunction(fn: AnalysisFunction, root: string): string {
|
|
465
|
+
return `${shortFunction(fn)} [${fn.kind}] ${relativeTo(root, fn.path)}:${fn.line}:${fn.column}`
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function describeEdge(edge: AnalysisModuleEdge): string {
|
|
469
|
+
const where = `${edge.line}:${edge.column}`
|
|
391
470
|
if (edge.direction === 'import') {
|
|
392
471
|
const names = edge.names.length > 0 ? ` { ${edge.names.join(', ')} }` : ''
|
|
393
472
|
return `${where} import (${edge.kind})${names} from ${JSON.stringify(edge.source)}`
|
|
@@ -400,7 +479,9 @@ function sourceLines(slice: AnalysisSourceSlice): string[] {
|
|
|
400
479
|
const lines = ['--- source ---', slice.text]
|
|
401
480
|
if (slice.truncated) {
|
|
402
481
|
lines.push(
|
|
403
|
-
|
|
482
|
+
slice.window
|
|
483
|
+
? `--- cut: showing ${slice.text.length} of ${slice.chars} characters, the function's first ${slice.window.headerChars} and the part around the position; raise max_chars to see more ---`
|
|
484
|
+
: `--- cut: showing ${slice.text.length} of ${slice.chars} characters; raise max_chars to see more ---`,
|
|
404
485
|
)
|
|
405
486
|
}
|
|
406
487
|
return lines
|
|
@@ -449,6 +530,8 @@ function render<T>(
|
|
|
449
530
|
function renderPage<T>(
|
|
450
531
|
result: CodeAnalysisResult<AnalysisPage<T>>,
|
|
451
532
|
line: (item: T) => string,
|
|
533
|
+
/** Groups consecutive items of one file under a single heading. */
|
|
534
|
+
fileOf?: (item: T) => string,
|
|
452
535
|
): ToolResult {
|
|
453
536
|
return render(
|
|
454
537
|
result,
|
|
@@ -456,7 +539,16 @@ function renderPage<T>(
|
|
|
456
539
|
if (page.items.length === 0) {
|
|
457
540
|
return [page.total === 0 ? 'No results.' : 'No results on this page.']
|
|
458
541
|
}
|
|
459
|
-
const lines =
|
|
542
|
+
const lines: string[] = []
|
|
543
|
+
let current: string | undefined
|
|
544
|
+
for (const item of page.items) {
|
|
545
|
+
const file = fileOf?.(item)
|
|
546
|
+
if (file !== undefined && file !== current) {
|
|
547
|
+
lines.push(file)
|
|
548
|
+
current = file
|
|
549
|
+
}
|
|
550
|
+
lines.push(file === undefined ? line(item) : ` ${line(item)}`)
|
|
551
|
+
}
|
|
460
552
|
lines.push(
|
|
461
553
|
page.nextCursor !== undefined
|
|
462
554
|
? `-- ${page.items.length} of ${page.total} shown. next cursor: ${page.nextCursor}`
|
|
@@ -5,6 +5,7 @@ import type { ToolContext, ToolResult } from '../../types/tool/index.js'
|
|
|
5
5
|
import { defineTool } from '../defineTool.js'
|
|
6
6
|
import { resolveWithinAnyReal, toolRoots } from '../paths.js'
|
|
7
7
|
import { atomicWriteFile } from './atomic-write-file.js'
|
|
8
|
+
import { clip, describeChange } from './classifier-input.js'
|
|
8
9
|
import { fingerprintContent, staleFileError, unreadFileError } from './content-fingerprint.js'
|
|
9
10
|
import { type NormalizedEditInput, applyEdit, normalizeEditInput } from './edit-apply.js'
|
|
10
11
|
import { withFileMutationLock } from './file-mutation-lock.js'
|
|
@@ -393,6 +394,18 @@ export const EditTool = defineTool({
|
|
|
393
394
|
// Declared so a path outside the turn's roots can be reviewed before the
|
|
394
395
|
// call runs, rather than refused after. See `ToolDefinition.pathArgument`.
|
|
395
396
|
pathArgument: 'path',
|
|
397
|
+
// The classifier judges which file changes and in what way, in a line.
|
|
398
|
+
classifierInput: (input: EditInput) => {
|
|
399
|
+
if (input.edits !== undefined) {
|
|
400
|
+
const shown = input.edits.slice(0, 5).map((e) => describeChange(e.old_string, e.new_string))
|
|
401
|
+
const more =
|
|
402
|
+
input.edits.length > shown.length ? `; ${input.edits.length - shown.length} more` : ''
|
|
403
|
+
return clip(`${input.path}: ${input.edits.length} edits: ${shown.join('; ')}${more}`, 1500)
|
|
404
|
+
}
|
|
405
|
+
const removed = input.old_string ?? input.oldStr
|
|
406
|
+
const added = input.new_string ?? input.newStr
|
|
407
|
+
return clip(`${input.path}: ${describeChange(removed, added)}`, 1500)
|
|
408
|
+
},
|
|
396
409
|
permissions: ['file_write'],
|
|
397
410
|
readOnly: false,
|
|
398
411
|
destructive: false,
|
|
@@ -178,6 +178,10 @@ export function createMonitorTool(options: MonitorToolOptions = {}) {
|
|
|
178
178
|
// has no `command`, so a rule written for commands has nothing to match
|
|
179
179
|
// and cannot allow it; `requiresApproval` below is what asks.
|
|
180
180
|
commandArgument: 'command',
|
|
181
|
+
classifierInput: (input: MonitorInput) =>
|
|
182
|
+
input.command !== undefined
|
|
183
|
+
? `[long-running watch] ${input.command}`
|
|
184
|
+
: `[long-running WebSocket watch] ${JSON.stringify(input.ws)}`,
|
|
181
185
|
commandDialect: bashToolDialect,
|
|
182
186
|
// `network_access` too: a WebSocket call reaches the network. The gate
|
|
183
187
|
// reads a tool's `category`, not this list, to route a call, so declaring
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { WebFetchRefusedError } from '../../connector/web/types.js'
|
|
12
12
|
import type { ToolContext } from '../../types/tool/index.js'
|
|
13
13
|
import { defineTool } from '../defineTool.js'
|
|
14
|
+
import { clip } from './classifier-input.js'
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* The two web tools, and the reason they are one file.
|
|
@@ -151,6 +152,8 @@ export const WebFetchTool = defineTool({
|
|
|
151
152
|
// preset — handing outbound reach to runs whose sandbox was never asked
|
|
152
153
|
// to confine the network.
|
|
153
154
|
category: 'network',
|
|
155
|
+
// The classifier judges the address and what is asked of it.
|
|
156
|
+
classifierInput: (input) => `${clip(input.url, 1000)}: ${clip(input.prompt, 300)}`,
|
|
154
157
|
permissions: ['network_access'],
|
|
155
158
|
// Reads a page and changes nothing HERE. That is not the same as
|
|
156
159
|
// harmless — a GET can still act on a remote system — which is why the
|
|
@@ -6,6 +6,7 @@ import type { ToolFacts } from '../../types/tool/presentation.js'
|
|
|
6
6
|
import { defineTool } from '../defineTool.js'
|
|
7
7
|
import { resolveWithinAnyReal, toolRoots } from '../paths.js'
|
|
8
8
|
import { atomicWriteFile } from './atomic-write-file.js'
|
|
9
|
+
import { clip } from './classifier-input.js'
|
|
9
10
|
import { fingerprintContent, staleFileError } from './content-fingerprint.js'
|
|
10
11
|
import { withFileMutationLock } from './file-mutation-lock.js'
|
|
11
12
|
import { fileWriteResult } from './file-write-result.js'
|
|
@@ -93,6 +94,11 @@ export const WriteFileTool = defineTool({
|
|
|
93
94
|
// Declared so a path outside the turn's roots can be reviewed before the
|
|
94
95
|
// call runs, rather than refused after. See `ToolDefinition.pathArgument`.
|
|
95
96
|
pathArgument: 'path',
|
|
97
|
+
// The classifier judges where a file is written and how much, not the body.
|
|
98
|
+
classifierInput: (input: WriteInput) => {
|
|
99
|
+
const body = input.content ?? input.newStr ?? ''
|
|
100
|
+
return `${input.path}: writes ${body.length} characters, starting ${JSON.stringify(clip(body, 300))}`
|
|
101
|
+
},
|
|
96
102
|
permissions: ['file_write'],
|
|
97
103
|
readOnly: false,
|
|
98
104
|
destructive: true,
|
package/src/tools/defineTool.ts
CHANGED
|
@@ -33,6 +33,8 @@ export interface DefineToolOptions<S extends z.ZodType> {
|
|
|
33
33
|
capturesScreen?: boolean | ((input: z.infer<S>) => boolean)
|
|
34
34
|
/** Whether this exact call always needs a person's approval; see {@link ToolDefinition.requiresApproval}. */
|
|
35
35
|
requiresApproval?: boolean | ((input: z.infer<S>) => boolean)
|
|
36
|
+
/** What the auto-mode classifier is shown of this call; see {@link ToolDefinition.classifierInput}. */
|
|
37
|
+
classifierInput?: (input: z.infer<S>) => string
|
|
36
38
|
/** Free-form filtering data, never sent to the model; see {@link ToolDefinition.metadata}. */
|
|
37
39
|
metadata?: Readonly<Record<string, unknown>>
|
|
38
40
|
/** Batch ordering boundary; see {@link ToolDefinition.executionBarrier}. */
|
|
@@ -186,6 +188,7 @@ export function defineTool<S extends z.ZodType>(
|
|
|
186
188
|
: () => options.requiresApproval as boolean,
|
|
187
189
|
}
|
|
188
190
|
: {}),
|
|
191
|
+
...(options.classifierInput !== undefined ? { classifierInput: options.classifierInput } : {}),
|
|
189
192
|
...(options.metadata !== undefined ? { metadata: options.metadata } : {}),
|
|
190
193
|
|
|
191
194
|
async execute(input: TInput, context: ToolContext): Promise<ToolResult> {
|
package/src/types/acp/index.ts
CHANGED
|
@@ -43,6 +43,13 @@ export interface AcpInitializeResult {
|
|
|
43
43
|
readonly promptAttachments?: boolean
|
|
44
44
|
/** True only when this host applies captured effort and review settings per prompt. */
|
|
45
45
|
readonly promptOptions?: boolean
|
|
46
|
+
/**
|
|
47
|
+
* The review modes beyond the original five (`prompt`, `plan`,
|
|
48
|
+
* `accept-edits`, `auto`, `strict`) that this host applies per prompt:
|
|
49
|
+
* `bypass` and `classify`, when it supports them. Absent: it accepts only
|
|
50
|
+
* the original five, and refuses the others before a turn starts.
|
|
51
|
+
*/
|
|
52
|
+
readonly promptPermissionModes?: readonly ('bypass' | 'classify')[]
|
|
46
53
|
/** Optional, explicitly installed Namzu host methods; core ACP peers can ignore them. */
|
|
47
54
|
readonly extensions?: readonly string[]
|
|
48
55
|
readonly protocolVersion: number
|
|
@@ -119,6 +119,14 @@ export interface AnalysisFunction {
|
|
|
119
119
|
readonly id: number
|
|
120
120
|
/** `null` for an anonymous function with nothing to infer a name from. */
|
|
121
121
|
readonly name: string | null
|
|
122
|
+
/**
|
|
123
|
+
* `name` qualified by what holds it, when it is a member: `Svc.init` for a
|
|
124
|
+
* method of the class `Svc` (a class expression assigned to a variable takes
|
|
125
|
+
* the variable's name), `obj.run` for a method of an object literal assigned
|
|
126
|
+
* to `obj`. Absent for a function that is not a member. `functionSource`
|
|
127
|
+
* accepts either spelling.
|
|
128
|
+
*/
|
|
129
|
+
readonly qualifiedName?: string
|
|
122
130
|
readonly kind: AnalysisFunctionKind
|
|
123
131
|
/** Absolute path. */
|
|
124
132
|
readonly path: string
|
|
@@ -136,6 +144,17 @@ export interface AnalysisSourceSlice {
|
|
|
136
144
|
readonly chars: number
|
|
137
145
|
/** `true` when `text` is shorter than `chars` because of the size cap. */
|
|
138
146
|
readonly truncated: boolean
|
|
147
|
+
/**
|
|
148
|
+
* Present when the cut was made around a position rather than at the end:
|
|
149
|
+
* `text` is the function's first `headerChars` characters, a marker for
|
|
150
|
+
* what was left out, then the file's own characters `from` to `to`, which
|
|
151
|
+
* contain the position asked about. `from` and `to` are offsets into the file.
|
|
152
|
+
*/
|
|
153
|
+
readonly window?: {
|
|
154
|
+
readonly headerChars: number
|
|
155
|
+
readonly from: number
|
|
156
|
+
readonly to: number
|
|
157
|
+
}
|
|
139
158
|
}
|
|
140
159
|
|
|
141
160
|
export interface IndexRequest extends AnalysisCallOptions {
|
|
@@ -222,6 +241,11 @@ export interface EnclosingFunctionRequest extends AnalysisCallOptions {
|
|
|
222
241
|
export interface AnalysisEnclosingFunction {
|
|
223
242
|
/** `null` when the position is not inside any function — a real answer. */
|
|
224
243
|
readonly function: AnalysisFunction | null
|
|
244
|
+
/**
|
|
245
|
+
* When `function` is `null` because the position is in a class body outside
|
|
246
|
+
* every method (a field initializer, a `static {}` block): that class.
|
|
247
|
+
*/
|
|
248
|
+
readonly inClass?: string
|
|
225
249
|
/** The function's parents, innermost first. */
|
|
226
250
|
readonly parents: readonly AnalysisFunction[]
|
|
227
251
|
readonly source?: AnalysisSourceSlice
|
|
@@ -235,6 +259,11 @@ export interface FunctionSourceRequest extends AnalysisPaging {
|
|
|
235
259
|
readonly path?: string
|
|
236
260
|
readonly name?: string
|
|
237
261
|
readonly maxChars?: number
|
|
262
|
+
/**
|
|
263
|
+
* A position in the function. When the source is longer than `maxChars`, the
|
|
264
|
+
* slice is cut around it instead of at the end, and always contains it.
|
|
265
|
+
*/
|
|
266
|
+
readonly around?: { readonly line: number; readonly column?: number }
|
|
238
267
|
}
|
|
239
268
|
|
|
240
269
|
export interface AnalysisFunctionSource {
|
|
@@ -245,7 +274,11 @@ export interface AnalysisFunctionSource {
|
|
|
245
274
|
|
|
246
275
|
export interface CallersOfRequest extends AnalysisPaging {
|
|
247
276
|
readonly root: string
|
|
248
|
-
/**
|
|
277
|
+
/**
|
|
278
|
+
* The callee text: `foo`, `obj.foo`, `this.foo`. A method's qualified name
|
|
279
|
+
* (`Svc.init`, as `functionSource` shows it) finds `this.init()` inside
|
|
280
|
+
* `Svc` as a resolved call, and every other `<expr>.init()` as `text-only`.
|
|
281
|
+
*/
|
|
249
282
|
readonly name: string
|
|
250
283
|
/**
|
|
251
284
|
* `file` looks in `path` only and is the default, because a callee matched
|
|
@@ -266,8 +299,19 @@ export interface AnalysisCallSite {
|
|
|
266
299
|
readonly line: number
|
|
267
300
|
readonly column: number
|
|
268
301
|
readonly callee: string
|
|
269
|
-
|
|
302
|
+
/**
|
|
303
|
+
* `call-via` is `f.call(…)`, `f.apply(…)` or `f.bind(…)` of the callee (a
|
|
304
|
+
* use, not necessarily a call); `tagged` is a tagged template.
|
|
305
|
+
*/
|
|
306
|
+
readonly kind: 'call' | 'new' | 'call-via' | 'tagged'
|
|
270
307
|
readonly argumentCount: number
|
|
308
|
+
/**
|
|
309
|
+
* Only when the request named a method by its qualified name: `resolved`
|
|
310
|
+
* for `this.m()` inside the class that declares it and for the qualified
|
|
311
|
+
* text itself, `text-only` for any other receiver, which may be an
|
|
312
|
+
* unrelated method of the same name.
|
|
313
|
+
*/
|
|
314
|
+
readonly resolution?: 'resolved' | 'text-only'
|
|
271
315
|
/** The function the call sits in; absent at the top level of a file. */
|
|
272
316
|
readonly caller?: AnalysisFunction
|
|
273
317
|
/** See {@link AnalysisLiteralHit.stale}. */
|
|
@@ -290,7 +334,11 @@ export type AnalysisModuleEdge =
|
|
|
290
334
|
}
|
|
291
335
|
| {
|
|
292
336
|
readonly direction: 'export'
|
|
293
|
-
|
|
337
|
+
/**
|
|
338
|
+
* `type` is a type-only export, `commonjs` is `module.exports` or
|
|
339
|
+
* `exports.x`, `assign` is `export =`.
|
|
340
|
+
*/
|
|
341
|
+
readonly kind: 'named' | 'default' | 'reexport' | 'star' | 'type' | 'commonjs' | 'assign'
|
|
294
342
|
readonly name: string
|
|
295
343
|
readonly source?: string
|
|
296
344
|
readonly line: number
|