salmon-loop 0.2.13 → 0.3.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/dist/cli/argv/headless-detection.js +27 -0
- package/dist/cli/chat-flow.js +11 -0
- package/dist/cli/chat.js +160 -24
- package/dist/cli/commands/chat.js +14 -7
- package/dist/cli/commands/flow-mode.js +63 -0
- package/dist/cli/commands/registry.js +2 -0
- package/dist/cli/commands/run/benchmark-artifacts.js +41 -0
- package/dist/cli/commands/run/early-errors.js +23 -0
- package/dist/cli/commands/run/handler.js +115 -27
- package/dist/cli/commands/run/headless-error-writer.js +8 -0
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/mode.js +2 -5
- package/dist/cli/commands/run/parse-options.js +16 -0
- package/dist/cli/commands/run/persist-session.js +10 -1
- package/dist/cli/commands/run/preflight.js +10 -0
- package/dist/cli/commands/run/reporter-factory.js +4 -0
- package/dist/cli/commands/run/runtime-llm.js +38 -11
- package/dist/cli/commands/run/runtime-options.js +2 -2
- package/dist/cli/commands/serve.js +97 -77
- package/dist/cli/commands/tool-names.js +78 -78
- package/dist/cli/headless/anthropic-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/json-protocol.js +37 -0
- package/dist/cli/headless/native-stream-normalized-encoder.js +6 -1
- package/dist/cli/headless/protocol-metadata.js +22 -0
- package/dist/cli/headless/stream-json-protocol.js +34 -1
- package/dist/cli/index.js +6 -4
- package/dist/cli/locales/en.js +30 -6
- package/dist/cli/program-bootstrap.js +10 -5
- package/dist/cli/program-commands.js +5 -1
- package/dist/cli/reporters/anthropic-stream.js +7 -1
- package/dist/cli/reporters/json.js +4 -0
- package/dist/cli/reporters/stream-json.js +17 -2
- package/dist/cli/run-cli.js +5 -3
- package/dist/cli/slash/runtime.js +27 -12
- package/dist/cli/ui/components/CommandInput.js +7 -3
- package/dist/cli/ui/components/CommandSuggestionList.js +1 -1
- package/dist/cli/utils/command-option-source.js +13 -0
- package/dist/cli/utils/verify-resolver.js +8 -4
- package/dist/cli/utils/worktree-prepare-resolver.js +7 -3
- package/dist/core/adapters/fs/file-adapter.js +6 -0
- package/dist/core/adapters/fs/filesystem.js +2 -1
- package/dist/core/adapters/git/git-adapter.js +78 -1
- package/dist/core/backends/salmon-loop/task-executor.js +1 -0
- package/dist/core/benchmark/patch-artifact.js +124 -0
- package/dist/core/benchmark/swe-bench.js +25 -0
- package/dist/core/config/load.js +18 -11
- package/dist/core/config/resolve-llm.js +12 -0
- package/dist/core/config/resolvers/server.js +0 -6
- package/dist/core/config/validate.js +73 -21
- package/dist/core/context/gatherers/metadata-gatherer.js +1 -0
- package/dist/core/context/gatherers/ripgrep-gatherer.js +84 -2
- package/dist/core/context/keywords.js +18 -4
- package/dist/core/context/service-deps.js +2 -2
- package/dist/core/context/service.js +8 -0
- package/dist/core/context/steps/context-gather.js +38 -0
- package/dist/core/context/summarization/summarizer.js +55 -12
- package/dist/core/context/targeting/target-resolver.js +4 -4
- package/dist/core/extensions/index.js +23 -5
- package/dist/core/extensions/merge.js +14 -0
- package/dist/core/extensions/paths.js +31 -0
- package/dist/core/extensions/schemas.js +8 -5
- package/dist/core/facades/cli-chat.js +6 -2
- package/dist/core/facades/cli-command-chat.js +1 -0
- package/dist/core/facades/cli-command-tool-names.js +2 -0
- package/dist/core/facades/cli-observability.js +1 -1
- package/dist/core/facades/cli-program-bootstrap.js +1 -0
- package/dist/core/facades/cli-run-handler.js +4 -2
- package/dist/core/facades/cli-run-persist-session.js +1 -0
- package/dist/core/facades/cli-serve.js +4 -4
- package/dist/core/facades/cli-utils-worktree.js +1 -1
- package/dist/core/failure/diagnostics.js +53 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -1
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +67 -9
- package/dist/core/grizzco/engine/pipeline/pipeline.js +6 -2
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +90 -15
- package/dist/core/grizzco/engine/transaction/report-mapper.js +17 -3
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +165 -7
- package/dist/core/grizzco/flows/AutopilotFlow.js +18 -0
- package/dist/core/grizzco/flows/flow-dispatch.js +11 -0
- package/dist/core/grizzco/steps/answer.js +13 -14
- package/dist/core/grizzco/steps/autopilot.js +396 -0
- package/dist/core/grizzco/steps/cache-sharing.js +29 -0
- package/dist/core/grizzco/steps/explore.js +37 -21
- package/dist/core/grizzco/steps/generateReview.js +2 -5
- package/dist/core/grizzco/steps/patch/apply-check.js +10 -0
- package/dist/core/grizzco/steps/patch/diff-normalization.js +70 -0
- package/dist/core/grizzco/steps/patch/diff-salvage.js +46 -0
- package/dist/core/grizzco/steps/patch/prompt-input.js +42 -0
- package/dist/core/grizzco/steps/patch.js +105 -146
- package/dist/core/grizzco/steps/plan.js +101 -25
- package/dist/core/grizzco/steps/preflight.js +5 -6
- package/dist/core/grizzco/steps/request-assembly.js +78 -0
- package/dist/core/grizzco/steps/research.js +39 -36
- package/dist/core/grizzco/steps/tool-runtime.js +47 -0
- package/dist/core/grizzco/steps/verify-shared.js +23 -0
- package/dist/core/grizzco/steps/verify.js +13 -21
- package/dist/core/interaction/orchestration/facade.js +1 -1
- package/dist/core/llm/ai-sdk/chat-executor.js +2 -0
- package/dist/core/llm/ai-sdk/high-level-phase-specs.js +63 -0
- package/dist/core/llm/ai-sdk/message-mapper.js +40 -10
- package/dist/core/llm/ai-sdk/provider-factory.js +14 -0
- package/dist/core/llm/ai-sdk/request-params.js +113 -1
- package/dist/core/llm/ai-sdk/result-mapper.js +16 -0
- package/dist/core/llm/ai-sdk.js +112 -27
- package/dist/core/llm/capabilities.js +12 -0
- package/dist/core/llm/contracts/repair.js +36 -30
- package/dist/core/llm/errors.js +83 -2
- package/dist/core/llm/message-composition.js +7 -22
- package/dist/core/llm/phase-router.js +29 -10
- package/dist/core/llm/redact.js +28 -3
- package/dist/core/llm/registry.js +2 -0
- package/dist/core/llm/request-augmentation.js +55 -0
- package/dist/core/llm/request-envelope.js +334 -0
- package/dist/core/llm/shared-request-assembly.js +35 -0
- package/dist/core/llm/stream-utils.js +13 -4
- package/dist/core/llm/utils.js +18 -29
- package/dist/core/memory/relevant-retrieval.js +144 -0
- package/dist/core/observability/logger.js +11 -2
- package/dist/core/patch/diff.js +1 -0
- package/dist/core/prompts/registry.js +39 -2
- package/dist/core/prompts/runtime.js +50 -12
- package/dist/core/prompts/templates/phases/patch_user.hbs +2 -5
- package/dist/core/prompts/templates/phases/research_user.hbs +11 -0
- package/dist/core/prompts/templates/phases/review_user.hbs +3 -0
- package/dist/core/prompts/templates/system/answer_system.hbs +5 -0
- package/dist/core/prompts/templates/system/autopilot_system.hbs +11 -0
- package/dist/core/prompts/templates/system/explore_system.hbs +14 -23
- package/dist/core/prompts/templates/system/main_system.hbs +4 -16
- package/dist/core/prompts/templates/system/patch_system.hbs +39 -8
- package/dist/core/prompts/templates/system/plan_system.hbs +86 -1
- package/dist/core/prompts/templates/system/research_system.hbs +2 -0
- package/dist/core/protocols/a2a/agent-card.js +5 -3
- package/dist/core/protocols/a2a/sdk/executor.js +2 -1
- package/dist/core/protocols/a2a/sdk/server.js +0 -1
- package/dist/core/protocols/acp/formal-agent.js +300 -58
- package/dist/core/protocols/acp/handlers.js +5 -1
- package/dist/core/protocols/acp/permission-provider.js +1 -1
- package/dist/core/protocols/shared/flow-mode-mapping.js +23 -0
- package/dist/core/public-capabilities/flow-mode-metadata.js +39 -0
- package/dist/core/public-capabilities/projections.js +29 -0
- package/dist/core/public-capabilities/registry.js +26 -0
- package/dist/core/public-capabilities/types.js +2 -0
- package/dist/core/runtime/agent-server-runtime.js +47 -43
- package/dist/core/runtime/execution-profile.js +67 -0
- package/dist/core/session/artifact-state.js +160 -0
- package/dist/core/session/compaction/index.js +183 -0
- package/dist/core/session/compaction/microcompact.js +78 -0
- package/dist/core/session/compaction/tracking.js +48 -0
- package/dist/core/session/compaction/types.js +11 -0
- package/dist/core/session/compression.js +8 -0
- package/dist/core/session/manager.js +244 -8
- package/dist/core/session/pruning-strategy.js +55 -9
- package/dist/core/session/replacement-preview-provider.js +24 -0
- package/dist/core/session/replacement-state.js +131 -0
- package/dist/core/session/resume-repair/pipeline.js +79 -0
- package/dist/core/session/resume-repair/stages/load-raw-archive-state.js +40 -0
- package/dist/core/session/resume-repair/stages/reattach-runtime-state.js +8 -0
- package/dist/core/session/resume-repair/stages/recover-orphaned-branches.js +10 -0
- package/dist/core/session/resume-repair/stages/relink-boundary-and-tail.js +36 -0
- package/dist/core/session/resume-repair/stages/replay-startup-hooks.js +23 -0
- package/dist/core/session/resume-repair/stages/rescue-stale-metadata.js +17 -0
- package/dist/core/session/resume-repair/types.js +2 -0
- package/dist/core/session/summary-sync.js +164 -13
- package/dist/core/session/token-tracker.js +6 -0
- package/dist/core/skills/audit.js +34 -0
- package/dist/core/skills/bridge.js +84 -7
- package/dist/core/skills/discovery.js +94 -0
- package/dist/core/skills/feature-flags.js +52 -0
- package/dist/core/skills/index.js +1 -1
- package/dist/core/skills/loader.js +195 -20
- package/dist/core/skills/parser.js +296 -24
- package/dist/core/skills/permissions.js +117 -0
- package/dist/core/skills/runtime/MicroTaskRunner.js +10 -4
- package/dist/core/skills/runtime/SkillRunner.js +240 -61
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +37 -7
- package/dist/core/strata/layers/worktree.js +67 -10
- package/dist/core/strata/runtime/synchronizer.js +29 -2
- package/dist/core/streaming/stream-assembler.js +75 -31
- package/dist/core/sub-agent/context-snapshot.js +156 -0
- package/dist/core/sub-agent/core/loop.js +1 -1
- package/dist/core/sub-agent/core/manager.js +119 -20
- package/dist/core/sub-agent/dispatch-policy.js +29 -0
- package/dist/core/sub-agent/prefix-consistency.js +48 -0
- package/dist/core/sub-agent/registry-defaults.js +4 -0
- package/dist/core/sub-agent/tools/task-spawn.js +79 -2
- package/dist/core/sub-agent/types.js +134 -5
- package/dist/core/tools/audit.js +13 -4
- package/dist/core/tools/builtin/ast-grep.js +1 -1
- package/dist/core/tools/builtin/ast.js +1 -1
- package/dist/core/tools/builtin/benchmark.js +360 -0
- package/dist/core/tools/builtin/code-search/backends/rg.js +2 -1
- package/dist/core/tools/builtin/code-search/executor.js +6 -1
- package/dist/core/tools/builtin/code-search/spec.js +26 -2
- package/dist/core/tools/builtin/fs.js +256 -23
- package/dist/core/tools/builtin/git.js +2 -2
- package/dist/core/tools/builtin/index.js +51 -2
- package/dist/core/tools/builtin/interaction.js +8 -1
- package/dist/core/tools/builtin/plan.js +37 -15
- package/dist/core/tools/builtin/shell.js +1 -1
- package/dist/core/tools/loader.js +39 -16
- package/dist/core/tools/mapper.js +17 -3
- package/dist/core/tools/mcp/client.js +2 -1
- package/dist/core/tools/parallel/scheduler.js +35 -4
- package/dist/core/tools/permissions/permission-rules.js +5 -10
- package/dist/core/tools/policy.js +6 -1
- package/dist/core/tools/recoverable-tool-errors.js +10 -0
- package/dist/core/tools/router.js +24 -6
- package/dist/core/tools/session.js +458 -48
- package/dist/core/tools/tool-visibility.js +62 -0
- package/dist/core/tools/types.js +9 -1
- package/dist/core/types/execution.js +4 -0
- package/dist/core/types/flow-mode.js +8 -0
- package/dist/core/utils/path.js +52 -0
- package/dist/core/verification/runner.js +4 -1
- package/dist/core/version.js +17 -0
- package/dist/languages/typescript/index.js +4 -1
- package/dist/locales/en.js +35 -2
- package/dist/utils/eol.js +1 -1
- package/package.json +14 -7
- package/scripts/fix-es-abstract-compat.js +77 -0
- package/dist/core/runtime/fastify-server-bundle.js +0 -26
- package/dist/core/runtime/sidecar-fastify-plugin.js +0 -35
- package/dist/core/runtime/sidecar-paths.js +0 -47
- package/dist/core/runtime/sidecar-route-catalog.js +0 -103
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import { createHash } from 'crypto';
|
|
2
|
+
import { Buffer } from 'node:buffer';
|
|
1
3
|
import { isAbsolute, relative, resolve } from 'path';
|
|
2
4
|
import { z } from 'zod';
|
|
3
5
|
import { text } from '../../../locales/index.js';
|
|
4
|
-
import {
|
|
6
|
+
import { AtomicFileWriter } from '../../adapters/fs/atomic-file-writer.js';
|
|
7
|
+
import { mkdir, readFile, readdir, stat } from '../../adapters/fs/node-fs.js';
|
|
5
8
|
import { Phase } from '../../types/runtime.js';
|
|
9
|
+
import { normalizeRepoRelativePath } from '../../utils/path.js';
|
|
6
10
|
import { pathPrefixResource } from '../parallel/resource-helpers.js';
|
|
7
11
|
const FsListEntryType = z.enum(['file', 'dir', 'symlink', 'other']);
|
|
8
12
|
const fsListInputSchema = z.preprocess((raw) => {
|
|
@@ -94,6 +98,7 @@ export const fsReadFileSpec = {
|
|
|
94
98
|
Phase.CONTEXT,
|
|
95
99
|
Phase.EXPLORE,
|
|
96
100
|
Phase.PLAN,
|
|
101
|
+
Phase.AUTOPILOT,
|
|
97
102
|
Phase.PATCH,
|
|
98
103
|
Phase.VERIFY,
|
|
99
104
|
Phase.SHRINK,
|
|
@@ -154,11 +159,28 @@ export const fsListSpec = {
|
|
|
154
159
|
Phase.CONTEXT,
|
|
155
160
|
Phase.EXPLORE,
|
|
156
161
|
Phase.PLAN,
|
|
162
|
+
Phase.AUTOPILOT,
|
|
157
163
|
Phase.PATCH,
|
|
158
164
|
Phase.VERIFY,
|
|
159
165
|
Phase.SHRINK,
|
|
160
166
|
],
|
|
161
167
|
};
|
|
168
|
+
/**
|
|
169
|
+
* Spec for the fs.list_directory tool.
|
|
170
|
+
*/
|
|
171
|
+
export const fsListDirectorySpec = {
|
|
172
|
+
...fsListSpec,
|
|
173
|
+
name: 'fs.list_directory',
|
|
174
|
+
description: text.tools.fsListDirectoryDescription,
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Spec for the fs.list_files tool.
|
|
178
|
+
*/
|
|
179
|
+
export const fsListFilesSpec = {
|
|
180
|
+
...fsListSpec,
|
|
181
|
+
name: 'fs.list_files',
|
|
182
|
+
description: text.tools.fsListFilesDescription,
|
|
183
|
+
};
|
|
162
184
|
function toRepoRelativeChildPath(dir, name) {
|
|
163
185
|
const normalizedDir = String(dir || '.')
|
|
164
186
|
.replace(/\\/g, '/')
|
|
@@ -166,23 +188,49 @@ function toRepoRelativeChildPath(dir, name) {
|
|
|
166
188
|
const base = normalizedDir === '.' ? '' : normalizedDir.replace(/^\.\/+/, '').replace(/\/+$/, '');
|
|
167
189
|
return base ? `${base}/${name}` : name;
|
|
168
190
|
}
|
|
191
|
+
function assertNotReservedRepoPrefix(relPath) {
|
|
192
|
+
const normalized = normalizeRepoRelativePath(relPath);
|
|
193
|
+
if (normalized === '.git' || normalized.startsWith('.git/')) {
|
|
194
|
+
throw new Error(text.errors.reservedPathPrefix('.git/'));
|
|
195
|
+
}
|
|
196
|
+
if (normalized === '.salmonloop' || normalized.startsWith('.salmonloop/')) {
|
|
197
|
+
throw new Error(text.errors.reservedPathPrefix('.salmonloop/'));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function resolveRepoRelativePath(repoRoot, relPath) {
|
|
201
|
+
if (isAbsolute(relPath)) {
|
|
202
|
+
throw new Error(text.errors.pathOutsideRepo);
|
|
203
|
+
}
|
|
204
|
+
assertNotReservedRepoPrefix(relPath);
|
|
205
|
+
const absoluteRoot = resolve(repoRoot);
|
|
206
|
+
const absolutePath = resolve(absoluteRoot, relPath);
|
|
207
|
+
const computedRelPath = relative(absoluteRoot, absolutePath);
|
|
208
|
+
if (computedRelPath.startsWith('..') || isAbsolute(computedRelPath)) {
|
|
209
|
+
throw new Error(text.errors.pathOutsideRepo);
|
|
210
|
+
}
|
|
211
|
+
return { absolutePath };
|
|
212
|
+
}
|
|
213
|
+
function shouldIncludeListedEntry(dir, entryName, includeHidden) {
|
|
214
|
+
if (!includeHidden && entryName.startsWith('.'))
|
|
215
|
+
return false;
|
|
216
|
+
const childPath = toRepoRelativeChildPath(dir, entryName);
|
|
217
|
+
try {
|
|
218
|
+
assertNotReservedRepoPrefix(childPath);
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
169
225
|
/**
|
|
170
226
|
* Implementation of the fs.list tool.
|
|
171
227
|
*/
|
|
172
228
|
export async function executeFsList(input, ctx) {
|
|
173
229
|
const { path: dir, includeHidden, maxEntries } = input;
|
|
174
|
-
|
|
175
|
-
throw new Error(text.errors.pathOutsideRepo);
|
|
176
|
-
}
|
|
177
|
-
const absoluteRoot = resolve(ctx.repoRoot);
|
|
178
|
-
const absolutePath = resolve(absoluteRoot, dir);
|
|
179
|
-
const relPath = relative(absoluteRoot, absolutePath);
|
|
180
|
-
if (relPath.startsWith('..') || isAbsolute(relPath)) {
|
|
181
|
-
throw new Error(text.errors.pathOutsideRepo);
|
|
182
|
-
}
|
|
230
|
+
const { absolutePath } = resolveRepoRelativePath(ctx.repoRoot, dir);
|
|
183
231
|
try {
|
|
184
232
|
const dirents = await readdir(absolutePath, { withFileTypes: true });
|
|
185
|
-
const visible =
|
|
233
|
+
const visible = dirents.filter((d) => shouldIncludeListedEntry(dir, d.name, includeHidden));
|
|
186
234
|
const entries = visible
|
|
187
235
|
.map((d) => {
|
|
188
236
|
const type = d.isDirectory()
|
|
@@ -211,23 +259,208 @@ export async function executeFsList(input, ctx) {
|
|
|
211
259
|
throw new Error(`Failed to list directory ${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
|
212
260
|
}
|
|
213
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Implementation of the fs.list_directory tool.
|
|
264
|
+
*/
|
|
265
|
+
export async function executeFsListDirectory(input, ctx) {
|
|
266
|
+
return executeFsList(input, ctx);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Implementation of the fs.list_files tool.
|
|
270
|
+
*/
|
|
271
|
+
export async function executeFsListFiles(input, ctx) {
|
|
272
|
+
const { path: dir, includeHidden, maxEntries } = input;
|
|
273
|
+
const { absolutePath } = resolveRepoRelativePath(ctx.repoRoot, dir);
|
|
274
|
+
try {
|
|
275
|
+
const dirents = await readdir(absolutePath, { withFileTypes: true });
|
|
276
|
+
const visible = dirents.filter((d) => shouldIncludeListedEntry(dir, d.name, includeHidden));
|
|
277
|
+
const fileEntries = visible
|
|
278
|
+
.filter((d) => d.isFile())
|
|
279
|
+
.map((d) => ({
|
|
280
|
+
name: d.name,
|
|
281
|
+
path: toRepoRelativeChildPath(dir, d.name),
|
|
282
|
+
type: 'file',
|
|
283
|
+
}))
|
|
284
|
+
.sort((a, b) => a.path.localeCompare(b.path));
|
|
285
|
+
const totalEntries = fileEntries.length;
|
|
286
|
+
const sliced = fileEntries.slice(0, maxEntries);
|
|
287
|
+
return {
|
|
288
|
+
entries: sliced,
|
|
289
|
+
truncated: sliced.length < totalEntries,
|
|
290
|
+
totalEntries,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
catch (e) {
|
|
294
|
+
throw new Error(`Failed to list files in directory ${dir}: ${e instanceof Error ? e.message : String(e)}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
const fsWriteFileInputSchema = z.preprocess((raw) => {
|
|
298
|
+
if (typeof raw === 'string') {
|
|
299
|
+
return { file: raw };
|
|
300
|
+
}
|
|
301
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
302
|
+
return raw;
|
|
303
|
+
const input = raw;
|
|
304
|
+
if (typeof input.file === 'string')
|
|
305
|
+
return input;
|
|
306
|
+
const alias = input.path ?? input.file_path ?? input.filePath;
|
|
307
|
+
if (typeof alias !== 'string')
|
|
308
|
+
return input;
|
|
309
|
+
return {
|
|
310
|
+
...input,
|
|
311
|
+
file: alias,
|
|
312
|
+
};
|
|
313
|
+
}, z.object({
|
|
314
|
+
file: z.string().describe('Relative path to the file from the repository root'),
|
|
315
|
+
content: z.string().describe('UTF-8 text content to write'),
|
|
316
|
+
encoding: z
|
|
317
|
+
.enum(['utf-8'])
|
|
318
|
+
.optional()
|
|
319
|
+
.describe('Text encoding (only utf-8 is supported; default: utf-8)'),
|
|
320
|
+
}));
|
|
321
|
+
/**
|
|
322
|
+
* Spec for the fs.write_file tool.
|
|
323
|
+
*/
|
|
324
|
+
export const fsWriteFileSpec = {
|
|
325
|
+
name: 'fs.write_file',
|
|
326
|
+
source: 'builtin',
|
|
327
|
+
intent: 'WRITE',
|
|
328
|
+
description: text.tools.fsWriteFileDescription,
|
|
329
|
+
riskLevel: 'high',
|
|
330
|
+
sideEffects: ['fs_write'],
|
|
331
|
+
concurrency: 'serial_only',
|
|
332
|
+
computeResources: (input, ctx) => [pathPrefixResource(ctx, input.file)],
|
|
333
|
+
allowedPhases: [Phase.SLASH, Phase.AUTOPILOT],
|
|
334
|
+
inputSchema: fsWriteFileInputSchema,
|
|
335
|
+
outputSchema: z.object({
|
|
336
|
+
ok: z.boolean(),
|
|
337
|
+
path: z.string(),
|
|
338
|
+
bytesWritten: z.number().int().nonnegative(),
|
|
339
|
+
}),
|
|
340
|
+
summarizeArgsForAuthorization: async (args) => {
|
|
341
|
+
const encoding = args?.encoding || 'utf-8';
|
|
342
|
+
const content = String(args?.content ?? '');
|
|
343
|
+
const bytes = Buffer.byteLength(content, 'utf8');
|
|
344
|
+
const sha256 = createHash('sha256').update(content, 'utf8').digest('hex');
|
|
345
|
+
return JSON.stringify({
|
|
346
|
+
file: args?.file,
|
|
347
|
+
encoding,
|
|
348
|
+
bytes,
|
|
349
|
+
sha256,
|
|
350
|
+
});
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
export async function executeFsWriteFile(input, ctx) {
|
|
354
|
+
if (ctx.dryRun) {
|
|
355
|
+
return { ok: true, path: input.file, bytesWritten: 0 };
|
|
356
|
+
}
|
|
357
|
+
const { absolutePath } = resolveRepoRelativePath(ctx.repoRoot, input.file);
|
|
358
|
+
const writer = new AtomicFileWriter();
|
|
359
|
+
const contentBytes = Buffer.from(input.content, 'utf8');
|
|
360
|
+
await writer.writeAtomic(absolutePath, contentBytes);
|
|
361
|
+
return {
|
|
362
|
+
ok: true,
|
|
363
|
+
path: input.file,
|
|
364
|
+
bytesWritten: contentBytes.length,
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const fsCreateDirectoryInputSchema = z.preprocess((raw) => {
|
|
368
|
+
if (typeof raw === 'string')
|
|
369
|
+
return { path: raw };
|
|
370
|
+
return raw;
|
|
371
|
+
}, z.object({
|
|
372
|
+
path: z.string().describe('Relative directory path to create from the repository root'),
|
|
373
|
+
recursive: z.boolean().default(true).describe('Whether to create parent directories'),
|
|
374
|
+
}));
|
|
375
|
+
/**
|
|
376
|
+
* Spec for the fs.create_directory tool.
|
|
377
|
+
*/
|
|
378
|
+
export const fsCreateDirectorySpec = {
|
|
379
|
+
name: 'fs.create_directory',
|
|
380
|
+
source: 'builtin',
|
|
381
|
+
intent: 'WRITE',
|
|
382
|
+
description: text.tools.fsCreateDirectoryDescription,
|
|
383
|
+
riskLevel: 'high',
|
|
384
|
+
sideEffects: ['fs_write'],
|
|
385
|
+
concurrency: 'serial_only',
|
|
386
|
+
computeResources: (input, ctx) => [pathPrefixResource(ctx, input.path)],
|
|
387
|
+
allowedPhases: [Phase.SLASH, Phase.AUTOPILOT],
|
|
388
|
+
inputSchema: fsCreateDirectoryInputSchema,
|
|
389
|
+
outputSchema: z.object({
|
|
390
|
+
ok: z.boolean(),
|
|
391
|
+
path: z.string(),
|
|
392
|
+
}),
|
|
393
|
+
summarizeArgsForAuthorization: async (args) => JSON.stringify({ path: args?.path, recursive: args?.recursive }),
|
|
394
|
+
};
|
|
395
|
+
export async function executeFsCreateDirectory(input, ctx) {
|
|
396
|
+
if (ctx.dryRun) {
|
|
397
|
+
return { ok: true, path: input.path };
|
|
398
|
+
}
|
|
399
|
+
const { absolutePath } = resolveRepoRelativePath(ctx.repoRoot, input.path);
|
|
400
|
+
await mkdir(absolutePath, { recursive: input.recursive });
|
|
401
|
+
return { ok: true, path: input.path };
|
|
402
|
+
}
|
|
403
|
+
const fsDeleteFileInputSchema = z.preprocess((raw) => {
|
|
404
|
+
if (typeof raw === 'string')
|
|
405
|
+
return { file: raw };
|
|
406
|
+
return raw;
|
|
407
|
+
}, z.object({
|
|
408
|
+
file: z.string().describe('Relative path to the file from the repository root'),
|
|
409
|
+
missingOk: z.boolean().default(true).describe('Whether missing files are treated as success'),
|
|
410
|
+
}));
|
|
411
|
+
/**
|
|
412
|
+
* Spec for the fs.delete_file tool.
|
|
413
|
+
*/
|
|
414
|
+
export const fsDeleteFileSpec = {
|
|
415
|
+
name: 'fs.delete_file',
|
|
416
|
+
source: 'builtin',
|
|
417
|
+
intent: 'WRITE',
|
|
418
|
+
description: text.tools.fsDeleteFileDescription,
|
|
419
|
+
riskLevel: 'high',
|
|
420
|
+
sideEffects: ['fs_write'],
|
|
421
|
+
concurrency: 'serial_only',
|
|
422
|
+
computeResources: (input, ctx) => [pathPrefixResource(ctx, input.file)],
|
|
423
|
+
allowedPhases: [Phase.SLASH, Phase.AUTOPILOT],
|
|
424
|
+
inputSchema: fsDeleteFileInputSchema,
|
|
425
|
+
outputSchema: z.object({
|
|
426
|
+
ok: z.boolean(),
|
|
427
|
+
path: z.string(),
|
|
428
|
+
deleted: z.boolean(),
|
|
429
|
+
}),
|
|
430
|
+
summarizeArgsForAuthorization: async (args) => JSON.stringify({ file: args?.file, missingOk: args?.missingOk }),
|
|
431
|
+
};
|
|
432
|
+
export async function executeFsDeleteFile(input, ctx) {
|
|
433
|
+
if (ctx.dryRun) {
|
|
434
|
+
return { ok: true, path: input.file, deleted: false };
|
|
435
|
+
}
|
|
436
|
+
const { absolutePath } = resolveRepoRelativePath(ctx.repoRoot, input.file);
|
|
437
|
+
let exists = true;
|
|
438
|
+
try {
|
|
439
|
+
await stat(absolutePath);
|
|
440
|
+
}
|
|
441
|
+
catch (e) {
|
|
442
|
+
const code = e && typeof e === 'object' && 'code' in e ? e.code : undefined;
|
|
443
|
+
if (code === 'ENOENT')
|
|
444
|
+
exists = false;
|
|
445
|
+
else
|
|
446
|
+
throw e;
|
|
447
|
+
}
|
|
448
|
+
if (!exists) {
|
|
449
|
+
if (input.missingOk) {
|
|
450
|
+
return { ok: true, path: input.file, deleted: false };
|
|
451
|
+
}
|
|
452
|
+
throw new Error(text.errors.pathNotFound(input.file));
|
|
453
|
+
}
|
|
454
|
+
const writer = new AtomicFileWriter();
|
|
455
|
+
await writer.deleteAtomic(absolutePath);
|
|
456
|
+
return { ok: true, path: input.file, deleted: true };
|
|
457
|
+
}
|
|
214
458
|
/**
|
|
215
459
|
* Implementation of the fs.read tool.
|
|
216
460
|
*/
|
|
217
461
|
export async function executeFsReadFile(input, ctx) {
|
|
218
462
|
const { file } = input;
|
|
219
|
-
|
|
220
|
-
throw new Error(text.errors.pathOutsideRepo);
|
|
221
|
-
}
|
|
222
|
-
// CRITICAL SAFETY: Path traversal check using relative path resolution
|
|
223
|
-
// We resolve to absolute paths to handle '.' and '..' correctly
|
|
224
|
-
const absoluteRoot = resolve(ctx.repoRoot);
|
|
225
|
-
// use resolve instead of join to handle absolute paths in input correctly
|
|
226
|
-
const absolutePath = resolve(absoluteRoot, file);
|
|
227
|
-
const relPath = relative(absoluteRoot, absolutePath);
|
|
228
|
-
if (relPath.startsWith('..') || isAbsolute(relPath)) {
|
|
229
|
-
throw new Error(text.errors.pathOutsideRepo);
|
|
230
|
-
}
|
|
463
|
+
const { absolutePath } = resolveRepoRelativePath(ctx.repoRoot, file);
|
|
231
464
|
try {
|
|
232
465
|
const fileStat = await stat(absolutePath);
|
|
233
466
|
const content = await readFile(absolutePath, 'utf-8');
|
|
@@ -22,7 +22,7 @@ export const gitCatSpec = {
|
|
|
22
22
|
file: z.string(),
|
|
23
23
|
ref: z.string(),
|
|
24
24
|
}),
|
|
25
|
-
allowedPhases: [Phase.SLASH, Phase.CONTEXT],
|
|
25
|
+
allowedPhases: [Phase.SLASH, Phase.CONTEXT, Phase.AUTOPILOT],
|
|
26
26
|
examples: [
|
|
27
27
|
{
|
|
28
28
|
description: 'Read a file from HEAD revision',
|
|
@@ -86,7 +86,7 @@ export const gitStatusSpec = {
|
|
|
86
86
|
outputSchema: z.object({
|
|
87
87
|
status: z.string(),
|
|
88
88
|
}),
|
|
89
|
-
allowedPhases: [Phase.SLASH, Phase.CONTEXT, Phase.PLAN, Phase.VERIFY],
|
|
89
|
+
allowedPhases: [Phase.SLASH, Phase.CONTEXT, Phase.PLAN, Phase.AUTOPILOT, Phase.VERIFY],
|
|
90
90
|
};
|
|
91
91
|
/**
|
|
92
92
|
* Builtin tool to check git status
|
|
@@ -2,9 +2,10 @@ import { subAgentTaskSpec } from '../../sub-agent/tools/task-spawn.js';
|
|
|
2
2
|
import { artifactReadSpec, executeArtifactRead } from './artifact.js';
|
|
3
3
|
import { astGrepSpec, executeAstGrep } from './ast-grep.js';
|
|
4
4
|
import { astDefsRefsSpec, executeAstDefsRefs } from './ast.js';
|
|
5
|
+
import { benchmarkReportSpec, executeBenchmarkReport, executeGitApplyCheck, executeGitDiffCheck, executeSweBenchGetReport, executeSweBenchLoadInstance, executeSweBenchSubmitPredictions, executeSweBenchWritePrediction, gitApplyCheckSpec, gitDiffCheckSpec, sweBenchGetReportSpec, sweBenchLoadInstanceSpec, sweBenchSubmitPredictionsSpec, sweBenchWritePredictionSpec, } from './benchmark.js';
|
|
5
6
|
import { codeSearchExecutor } from './code-search/executor.js';
|
|
6
7
|
import { CodeSearchSpec } from './code-search/spec.js';
|
|
7
|
-
import { codeReadSpec, executeFsList, executeFsReadFile, fsListSpec, fsReadFileSpec, } from './fs.js';
|
|
8
|
+
import { codeReadSpec, executeFsCreateDirectory, executeFsList, executeFsListDirectory, executeFsListFiles, executeFsReadFile, executeFsDeleteFile, executeFsWriteFile, fsCreateDirectorySpec, fsDeleteFileSpec, fsListSpec, fsListDirectorySpec, fsListFilesSpec, fsReadFileSpec, fsWriteFileSpec, } from './fs.js';
|
|
8
9
|
import { gitCatSpec, executeGitCat, gitStatusSpec, executeGitStatus } from './git.js';
|
|
9
10
|
import { askUserSpec } from './interaction.js';
|
|
10
11
|
import { updateKnowledgeSpec, executeUpdateKnowledge } from './knowledge.js';
|
|
@@ -47,6 +48,34 @@ export function registerAllBuiltins(registry) {
|
|
|
47
48
|
...gitStatusSpec,
|
|
48
49
|
executor: executeGitStatus,
|
|
49
50
|
});
|
|
51
|
+
registry.register({
|
|
52
|
+
...gitDiffCheckSpec,
|
|
53
|
+
executor: executeGitDiffCheck,
|
|
54
|
+
});
|
|
55
|
+
registry.register({
|
|
56
|
+
...gitApplyCheckSpec,
|
|
57
|
+
executor: executeGitApplyCheck,
|
|
58
|
+
});
|
|
59
|
+
registry.register({
|
|
60
|
+
...benchmarkReportSpec,
|
|
61
|
+
executor: executeBenchmarkReport,
|
|
62
|
+
});
|
|
63
|
+
registry.register({
|
|
64
|
+
...sweBenchLoadInstanceSpec,
|
|
65
|
+
executor: executeSweBenchLoadInstance,
|
|
66
|
+
});
|
|
67
|
+
registry.register({
|
|
68
|
+
...sweBenchWritePredictionSpec,
|
|
69
|
+
executor: executeSweBenchWritePrediction,
|
|
70
|
+
});
|
|
71
|
+
registry.register({
|
|
72
|
+
...sweBenchSubmitPredictionsSpec,
|
|
73
|
+
executor: executeSweBenchSubmitPredictions,
|
|
74
|
+
});
|
|
75
|
+
registry.register({
|
|
76
|
+
...sweBenchGetReportSpec,
|
|
77
|
+
executor: executeSweBenchGetReport,
|
|
78
|
+
});
|
|
50
79
|
registry.register({
|
|
51
80
|
...fsReadFileSpec,
|
|
52
81
|
executor: executeFsReadFile,
|
|
@@ -59,6 +88,14 @@ export function registerAllBuiltins(registry) {
|
|
|
59
88
|
...fsListSpec,
|
|
60
89
|
executor: executeFsList,
|
|
61
90
|
});
|
|
91
|
+
registry.register({
|
|
92
|
+
...fsListDirectorySpec,
|
|
93
|
+
executor: executeFsListDirectory,
|
|
94
|
+
});
|
|
95
|
+
registry.register({
|
|
96
|
+
...fsListFilesSpec,
|
|
97
|
+
executor: executeFsListFiles,
|
|
98
|
+
});
|
|
62
99
|
registry.register({
|
|
63
100
|
...astGrepSpec,
|
|
64
101
|
executor: executeAstGrep,
|
|
@@ -71,10 +108,22 @@ export function registerAllBuiltins(registry) {
|
|
|
71
108
|
...shellExecSpec,
|
|
72
109
|
executor: executeShellExec,
|
|
73
110
|
});
|
|
111
|
+
registry.register({
|
|
112
|
+
...fsWriteFileSpec,
|
|
113
|
+
executor: executeFsWriteFile,
|
|
114
|
+
});
|
|
115
|
+
registry.register({
|
|
116
|
+
...fsCreateDirectorySpec,
|
|
117
|
+
executor: executeFsCreateDirectory,
|
|
118
|
+
});
|
|
119
|
+
registry.register({
|
|
120
|
+
...fsDeleteFileSpec,
|
|
121
|
+
executor: executeFsDeleteFile,
|
|
122
|
+
});
|
|
74
123
|
registry.register(planInitSpec);
|
|
75
124
|
registry.register(planReadSpec);
|
|
76
125
|
registry.register(planUpdateSpec);
|
|
77
126
|
registry.register(askUserSpec);
|
|
78
127
|
}
|
|
79
|
-
export { CodeSearchSpec, codeSearchExecutor, astDefsRefsSpec as codeAstSpec, executeAstDefsRefs as executeCodeAst, gitCatSpec, executeGitCat, gitStatusSpec, executeGitStatus, codeReadSpec, fsListSpec, executeFsList, fsReadFileSpec as fsReadSpec, executeFsReadFile as executeFsRead, updateKnowledgeSpec, executeUpdateKnowledge, astGrepSpec as codeSearchAstSpec, executeAstGrep as executeCodeSearchAst, verifyRunSpec as testRunSpec, executeVerifyRun as executeTestRun, };
|
|
128
|
+
export { CodeSearchSpec, codeSearchExecutor, astDefsRefsSpec as codeAstSpec, executeAstDefsRefs as executeCodeAst, gitCatSpec, executeGitCat, gitStatusSpec, executeGitStatus, codeReadSpec, fsListSpec, executeFsList, fsReadFileSpec as fsReadSpec, executeFsReadFile as executeFsRead, updateKnowledgeSpec, executeUpdateKnowledge, astGrepSpec as codeSearchAstSpec, executeAstGrep as executeCodeSearchAst, verifyRunSpec as testRunSpec, executeVerifyRun as executeTestRun, gitDiffCheckSpec, executeGitDiffCheck, gitApplyCheckSpec, executeGitApplyCheck, benchmarkReportSpec, executeBenchmarkReport, sweBenchLoadInstanceSpec, executeSweBenchLoadInstance, sweBenchWritePredictionSpec, executeSweBenchWritePrediction, sweBenchSubmitPredictionsSpec, executeSweBenchSubmitPredictions, sweBenchGetReportSpec, executeSweBenchGetReport, };
|
|
80
129
|
//# sourceMappingURL=index.js.map
|
|
@@ -63,7 +63,14 @@ export const askUserSpec = {
|
|
|
63
63
|
riskLevel: 'low',
|
|
64
64
|
sideEffects: ['none'],
|
|
65
65
|
concurrency: 'serial_only',
|
|
66
|
-
allowedPhases: [
|
|
66
|
+
allowedPhases: [
|
|
67
|
+
Phase.EXPLORE,
|
|
68
|
+
Phase.PLAN,
|
|
69
|
+
Phase.AUTOPILOT,
|
|
70
|
+
Phase.PATCH,
|
|
71
|
+
Phase.VALIDATE,
|
|
72
|
+
Phase.AST_VALIDATE,
|
|
73
|
+
],
|
|
67
74
|
inputSchema: askUserInputSchema,
|
|
68
75
|
outputSchema: askUserOutputSchema,
|
|
69
76
|
executor: async (input, ctx) => {
|
|
@@ -14,6 +14,17 @@ const stepIdSchema = z
|
|
|
14
14
|
.max(128)
|
|
15
15
|
.regex(/^[a-zA-Z0-9_.:-]+$/)
|
|
16
16
|
.describe('Stable step ID (from <!-- sl:id=... -->).');
|
|
17
|
+
const planUpdatePatchSchema = z
|
|
18
|
+
.object({
|
|
19
|
+
status: z.enum(['todo', 'active', 'done', 'failed', 'skipped', 'conflict']).optional(),
|
|
20
|
+
checkbox: z.enum(['checked', 'unchecked']).optional(),
|
|
21
|
+
appendSubtasks: z.array(z.string().min(1)).optional(),
|
|
22
|
+
note: z.string().optional(),
|
|
23
|
+
})
|
|
24
|
+
.describe('Patch object for plan.update. Provide a JSON object (not a JSON string). Optional keys: status, checkbox, appendSubtasks, note.');
|
|
25
|
+
const planUpdateConflictCodeSchema = z
|
|
26
|
+
.enum(['BASE_HASH_MISMATCH', 'STEP_NOT_FOUND', 'MALFORMED_METADATA', 'WRITE_DENIED'])
|
|
27
|
+
.describe('Conflict code returned when ok=false.');
|
|
17
28
|
function planResource(ctx, sessionId) {
|
|
18
29
|
const repoId = ctx.persistenceRoot ?? ctx.repoRoot;
|
|
19
30
|
if (!sessionId)
|
|
@@ -29,7 +40,14 @@ export const planInitSpec = {
|
|
|
29
40
|
sideEffects: ['runtime_write'],
|
|
30
41
|
concurrency: 'mutex_by_resource',
|
|
31
42
|
computeResources: (_args, ctx) => planResource(ctx),
|
|
32
|
-
allowedPhases: [
|
|
43
|
+
allowedPhases: [
|
|
44
|
+
Phase.EXPLORE,
|
|
45
|
+
Phase.PLAN,
|
|
46
|
+
Phase.AUTOPILOT,
|
|
47
|
+
Phase.PATCH,
|
|
48
|
+
Phase.VERIFY,
|
|
49
|
+
Phase.SHRINK,
|
|
50
|
+
],
|
|
33
51
|
inputSchema: z.object({
|
|
34
52
|
mission: z.string().min(1),
|
|
35
53
|
objective: z.string().min(1),
|
|
@@ -59,7 +77,14 @@ export const planReadSpec = {
|
|
|
59
77
|
sideEffects: ['fs_read'],
|
|
60
78
|
concurrency: 'parallel_ok',
|
|
61
79
|
computeResources: (args, ctx) => planResource(ctx, args.sessionId),
|
|
62
|
-
allowedPhases: [
|
|
80
|
+
allowedPhases: [
|
|
81
|
+
Phase.EXPLORE,
|
|
82
|
+
Phase.PLAN,
|
|
83
|
+
Phase.AUTOPILOT,
|
|
84
|
+
Phase.PATCH,
|
|
85
|
+
Phase.VERIFY,
|
|
86
|
+
Phase.SHRINK,
|
|
87
|
+
],
|
|
63
88
|
inputSchema: z.object({
|
|
64
89
|
sessionId: sessionIdSchema,
|
|
65
90
|
}),
|
|
@@ -100,17 +125,19 @@ export const planUpdateSpec = {
|
|
|
100
125
|
sideEffects: ['runtime_write'],
|
|
101
126
|
concurrency: 'mutex_by_resource',
|
|
102
127
|
computeResources: (args, ctx) => planResource(ctx, args.sessionId),
|
|
103
|
-
allowedPhases: [
|
|
128
|
+
allowedPhases: [
|
|
129
|
+
Phase.EXPLORE,
|
|
130
|
+
Phase.PLAN,
|
|
131
|
+
Phase.AUTOPILOT,
|
|
132
|
+
Phase.PATCH,
|
|
133
|
+
Phase.VERIFY,
|
|
134
|
+
Phase.SHRINK,
|
|
135
|
+
],
|
|
104
136
|
inputSchema: z.object({
|
|
105
137
|
sessionId: sessionIdSchema,
|
|
106
138
|
baseHash: z.string().min(8),
|
|
107
139
|
stepId: stepIdSchema,
|
|
108
|
-
patch:
|
|
109
|
-
status: z.enum(['todo', 'active', 'done', 'failed', 'skipped', 'conflict']).optional(),
|
|
110
|
-
checkbox: z.enum(['checked', 'unchecked']).optional(),
|
|
111
|
-
appendSubtasks: z.array(z.string().min(1)).optional(),
|
|
112
|
-
note: z.string().optional(),
|
|
113
|
-
}),
|
|
140
|
+
patch: planUpdatePatchSchema,
|
|
114
141
|
}),
|
|
115
142
|
outputSchema: z.union([
|
|
116
143
|
z.object({
|
|
@@ -124,12 +151,7 @@ export const planUpdateSpec = {
|
|
|
124
151
|
sessionId: z.string(),
|
|
125
152
|
baseHash: z.string(),
|
|
126
153
|
conflict: z.object({
|
|
127
|
-
code:
|
|
128
|
-
'BASE_HASH_MISMATCH',
|
|
129
|
-
'STEP_NOT_FOUND',
|
|
130
|
-
'MALFORMED_METADATA',
|
|
131
|
-
'WRITE_DENIED',
|
|
132
|
-
]),
|
|
154
|
+
code: planUpdateConflictCodeSchema,
|
|
133
155
|
message: z.string(),
|
|
134
156
|
}),
|
|
135
157
|
}),
|
|
@@ -13,7 +13,7 @@ export const shellExecSpec = {
|
|
|
13
13
|
sideEffects: ['process'],
|
|
14
14
|
concurrency: 'isolated',
|
|
15
15
|
computeResources: (_input, ctx) => [repoResource(ctx), processResource(ctx)],
|
|
16
|
-
allowedPhases: [Phase.SLASH],
|
|
16
|
+
allowedPhases: [Phase.SLASH, Phase.AUTOPILOT],
|
|
17
17
|
inputSchema: z.object({
|
|
18
18
|
command: z.string().min(1).describe('Shell command to execute'),
|
|
19
19
|
}),
|
|
@@ -28,21 +28,6 @@ export async function createStandardToolstack(options) {
|
|
|
28
28
|
const sanitize = new ToolSanitizer();
|
|
29
29
|
// 2. Register all builtin tools (rg, git, ast, ast-grep)
|
|
30
30
|
registerAllBuiltins(registry);
|
|
31
|
-
// 3. Load and register Skills as tools
|
|
32
|
-
const extensions = options.extensions;
|
|
33
|
-
const skillLoader = new SkillLoader({
|
|
34
|
-
repoRoot: options.repoRoot,
|
|
35
|
-
useDefaults: extensions?.skillDiscovery.useDefaults,
|
|
36
|
-
extraPaths: extensions?.skillDiscovery.paths,
|
|
37
|
-
});
|
|
38
|
-
const skills = await skillLoader.initialize();
|
|
39
|
-
for (const skill of skills) {
|
|
40
|
-
registry.register(skillToToolSpec(skill));
|
|
41
|
-
}
|
|
42
|
-
if (extensions) {
|
|
43
|
-
await registerMcpTools(registry, extensions.mcpServers);
|
|
44
|
-
await registerPluginTools(registry, extensions.toolPlugins);
|
|
45
|
-
}
|
|
46
31
|
const compiledPermissionRules = (() => {
|
|
47
32
|
if (!options.permissionRules)
|
|
48
33
|
return undefined;
|
|
@@ -53,6 +38,42 @@ export async function createStandardToolstack(options) {
|
|
|
53
38
|
}
|
|
54
39
|
return compiled.compiled;
|
|
55
40
|
})();
|
|
41
|
+
// 3. Register ALL tools (builtins already done above) into the registry,
|
|
42
|
+
// then apply allowlist/permission filtering, then create the router.
|
|
43
|
+
//
|
|
44
|
+
// The challenge: skill executors need a ToolRouter reference (to call
|
|
45
|
+
// shell.exec through governance), but the router needs the final filtered
|
|
46
|
+
// registry. We break this cycle with a RouterBox — a mutable container
|
|
47
|
+
// whose .router field is filled after the router is constructed.
|
|
48
|
+
//
|
|
49
|
+
// Order:
|
|
50
|
+
// a. Create RouterBox (null initially)
|
|
51
|
+
// b. Load skills → register with RouterBox (executor reads lazily)
|
|
52
|
+
// c. Register MCP + plugin tools
|
|
53
|
+
// d. Apply allowlist/permission filtering → new filtered registry
|
|
54
|
+
// e. Create ToolRouter with filtered registry
|
|
55
|
+
// f. Fill RouterBox.router — skill executors now have a valid reference
|
|
56
|
+
const routerBox = { router: null };
|
|
57
|
+
// 3a. Load skill catalog (Tier 1: lightweight metadata only) and register
|
|
58
|
+
// bridge tool specs with lazy activation. Full skill content is loaded
|
|
59
|
+
// on demand via SkillLoader.activateSkill() (Tier 2) when the executor
|
|
60
|
+
// is actually invoked.
|
|
61
|
+
// @see https://agentskills.io/specification — Progressive disclosure
|
|
62
|
+
const extensions = options.extensions;
|
|
63
|
+
const skillLoader = new SkillLoader({
|
|
64
|
+
repoRoot: options.repoRoot,
|
|
65
|
+
extraPaths: extensions?.skillDiscovery.paths,
|
|
66
|
+
});
|
|
67
|
+
const catalog = await skillLoader.loadCatalog();
|
|
68
|
+
for (const entry of catalog) {
|
|
69
|
+
registry.register(skillToToolSpec({ entry, loader: skillLoader }, routerBox));
|
|
70
|
+
}
|
|
71
|
+
// 3b. Register MCP + plugin tools
|
|
72
|
+
if (extensions) {
|
|
73
|
+
await registerMcpTools(registry, extensions.mcpServers);
|
|
74
|
+
await registerPluginTools(registry, extensions.toolPlugins);
|
|
75
|
+
}
|
|
76
|
+
// 3c. Apply allowlist / permission-rule filtering — skills are now included
|
|
56
77
|
const allowSets = [];
|
|
57
78
|
if (Array.isArray(options.allowedToolNames) && options.allowedToolNames.length > 0) {
|
|
58
79
|
allowSets.push(new Set(options.allowedToolNames));
|
|
@@ -76,8 +97,10 @@ export async function createStandardToolstack(options) {
|
|
|
76
97
|
}
|
|
77
98
|
registry = filtered;
|
|
78
99
|
}
|
|
79
|
-
//
|
|
100
|
+
// 3d. Create Router with the FINAL (filtered) registry — skills included
|
|
80
101
|
const router = new ToolRouter(registry, policy, budget, audit, sanitize, options.authorizationProvider, { authorizationMode: options.authorizationMode, permissionRules: compiledPermissionRules });
|
|
102
|
+
// 3e. Fill the RouterBox — skill executors can now resolve the router
|
|
103
|
+
routerBox.router = router;
|
|
81
104
|
// 4. Create Dispatcher (The high-level coordinator for LLM text)
|
|
82
105
|
const dispatcher = new ToolDispatcher(router, {
|
|
83
106
|
repoRoot: options.repoRoot,
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
function formatToolExamplesForDescription(spec) {
|
|
3
|
+
if (!Array.isArray(spec.examples) || spec.examples.length === 0)
|
|
4
|
+
return '';
|
|
5
|
+
const examples = spec.examples
|
|
6
|
+
.map((example) => {
|
|
7
|
+
const input = JSON.stringify(example.input);
|
|
8
|
+
return input ? `- ${example.description}: ${input}` : undefined;
|
|
9
|
+
})
|
|
10
|
+
.filter((line) => Boolean(line));
|
|
11
|
+
return examples.length > 0 ? `\n\nExamples:\n${examples.join('\n')}` : '';
|
|
12
|
+
}
|
|
13
|
+
function toolDescriptionForModel(spec) {
|
|
14
|
+
return `${spec.description}${formatToolExamplesForDescription(spec)}`;
|
|
15
|
+
}
|
|
2
16
|
function unwrapForSchemaGeneration(schema) {
|
|
3
17
|
let current = schema;
|
|
4
18
|
for (let depth = 0; depth < 20; depth++) {
|
|
@@ -113,7 +127,7 @@ export function toolToOpenAI(spec) {
|
|
|
113
127
|
type: 'function',
|
|
114
128
|
function: {
|
|
115
129
|
name: spec.name,
|
|
116
|
-
description: spec
|
|
130
|
+
description: toolDescriptionForModel(spec),
|
|
117
131
|
parameters: zodToOpenApi3(spec.inputSchema),
|
|
118
132
|
},
|
|
119
133
|
};
|
|
@@ -124,7 +138,7 @@ export function toolToOpenAI(spec) {
|
|
|
124
138
|
export function toolToAnthropic(spec) {
|
|
125
139
|
return {
|
|
126
140
|
name: spec.name,
|
|
127
|
-
description: spec
|
|
141
|
+
description: toolDescriptionForModel(spec),
|
|
128
142
|
input_schema: zodToOpenApi3(spec.inputSchema),
|
|
129
143
|
};
|
|
130
144
|
}
|
|
@@ -135,7 +149,7 @@ export function formatToolsForPrompt(specs) {
|
|
|
135
149
|
return specs
|
|
136
150
|
.map((spec) => {
|
|
137
151
|
const schema = zodToOpenApi3(spec.inputSchema);
|
|
138
|
-
return `Tool: ${spec.name}\nDescription: ${spec
|
|
152
|
+
return `Tool: ${spec.name}\nDescription: ${toolDescriptionForModel(spec)}\nSchema: ${JSON.stringify(schema, null, 2)}`;
|
|
139
153
|
})
|
|
140
154
|
.join('\n\n---\n\n');
|
|
141
155
|
}
|