salmon-loop 0.3.2 → 0.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/dist/cli/authorization/non-interactive.js +9 -13
- package/dist/cli/authorization/provider.js +2 -10
- package/dist/cli/chat.js +12 -6
- package/dist/cli/commands/allowlist.js +1 -1
- package/dist/cli/commands/chat.js +13 -13
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/parallel.js +1 -1
- package/dist/cli/commands/run/handler.js +9 -4
- package/dist/cli/commands/run/loop-params.js +2 -0
- package/dist/cli/commands/run/parse-options.js +14 -26
- package/dist/cli/commands/run/runtime-llm.js +15 -12
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/headless/openai-responses-canonical-applier.js +1 -7
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +12 -3
- package/dist/cli/reporters/stream-json.js +2 -1
- package/dist/cli/slash/runtime.js +2 -2
- package/dist/cli/ui/hooks/useLoopEvents.js +1 -1
- package/dist/cli/ui/hooks/useLoopState.js +1 -1
- package/dist/core/adapters/fs/file-adapter.js +3 -1
- package/dist/core/adapters/git/git-adapter.js +6 -3
- package/dist/core/adapters/git/git-runner.js +5 -2
- package/dist/core/adapters/git/lock-manager.js +7 -4
- package/dist/core/ast/parser.js +18 -9
- package/dist/core/checkpoint-domain/manifest-store.js +21 -13
- package/dist/core/checkpoint-domain/service.js +3 -1
- package/dist/core/config/limits.js +1 -1
- package/dist/core/config/model-pricing.js +61 -0
- package/dist/core/config/schema.js +738 -0
- package/dist/core/config/validate.js +11 -922
- package/dist/core/context/ast/skeleton-extractor.js +225 -0
- package/dist/core/context/ast/source-outline.js +24 -1
- package/dist/core/context/budget/dynamic-adjuster.js +20 -5
- package/dist/core/context/builder.js +7 -3
- package/dist/core/context/cache/store-factory.js +3 -1
- package/dist/core/context/dependencies.js +2 -1
- package/dist/core/context/effectiveness/persistence.js +50 -0
- package/dist/core/context/effectiveness/tracker.js +24 -0
- package/dist/core/context/gatherers/architecture-gatherer.js +2 -1
- package/dist/core/context/gatherers/artifact-gatherer.js +7 -4
- package/dist/core/context/gatherers/ast-gatherer.js +34 -40
- package/dist/core/context/gatherers/ghost-dependency-gatherer.js +0 -1
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +21 -2
- package/dist/core/context/gatherers/metadata-gatherer.js +12 -7
- package/dist/core/context/gatherers/ripgrep-gatherer.js +6 -3
- package/dist/core/context/service.js +12 -2
- package/dist/core/context/steps/context-gather.js +14 -3
- package/dist/core/context/steps/context-targets.js +1 -0
- package/dist/core/context/targeting/target-resolver.js +29 -11
- package/dist/core/context/token/cache.js +5 -2
- package/dist/core/context/token/encoding-registry.js +7 -6
- package/dist/core/context/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/index.js +48 -3
- package/dist/core/extensions/load.js +3 -2
- package/dist/core/extensions/merge.js +5 -1
- package/dist/core/extensions/paths.js +8 -2
- package/dist/core/extensions/schemas.js +21 -0
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/facades/cli-command-chat.js +2 -0
- package/dist/core/facades/cli-run-handler.js +1 -0
- package/dist/core/facades/cli-utils-serialize.js +2 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +4 -3
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +30 -13
- package/dist/core/grizzco/engine/pipeline/pipeline.js +149 -240
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +49 -24
- package/dist/core/grizzco/engine/transaction/authorization-summary.js +2 -1
- package/dist/core/grizzco/engine/transaction/transaction-runner.js +40 -34
- package/dist/core/grizzco/execution/RejectionManager.js +7 -5
- package/dist/core/grizzco/runtime/apply-back-runtime.js +5 -2
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/services/registry.js +18 -0
- package/dist/core/grizzco/steps/audit.js +20 -10
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/display-report.js +4 -11
- package/dist/core/grizzco/steps/explore.js +14 -4
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/patch/prompt-input.js +4 -1
- package/dist/core/grizzco/steps/patch.js +1 -0
- package/dist/core/grizzco/steps/plan.js +58 -49
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/tool-runtime.js +3 -0
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -1
- package/dist/core/grizzco/workers/strata-sync-worker.js +2 -1
- package/dist/core/history/input-history.js +3 -1
- package/dist/core/intent/chat-intent.js +3 -1
- package/dist/core/llm/ai-sdk/message-mapper.js +37 -26
- package/dist/core/llm/ai-sdk/request-params.js +2 -6
- package/dist/core/llm/ai-sdk/result-mapper.js +14 -8
- package/dist/core/llm/ai-sdk/retry-classifier.js +17 -7
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/contracts/repair.js +16 -8
- package/dist/core/llm/errors.js +18 -14
- package/dist/core/llm/output-policy.js +8 -0
- package/dist/core/llm/redact.js +1 -3
- package/dist/core/llm/retry-utils.js +8 -2
- package/dist/core/llm/stream-utils.js +5 -3
- package/dist/core/llm/sub-agent-factory.js +51 -0
- package/dist/core/llm/tool-calling-stub.js +48 -0
- package/dist/core/llm/utils.js +17 -6
- package/dist/core/mcp/bridge/prompt-command-provider.js +4 -3
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/bridge/tool-bridge.js +5 -14
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +7 -4
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/mcp/host/sampling-provider.js +1 -1
- package/dist/core/mcp/schema/json-schema-to-zod.js +2 -1
- package/dist/core/memory/relevant-retrieval.js +6 -4
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -1
- package/dist/core/observability/authorization-decisions.js +13 -12
- package/dist/core/observability/error-mapping.js +2 -1
- package/dist/core/observability/logger.js +2 -1
- package/dist/core/observability/monitor.js +24 -0
- package/dist/core/observability/run-outcome-reporter.js +1 -0
- package/dist/core/observability/token-usage.js +5 -4
- package/dist/core/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +8 -5
- package/dist/core/prompts/registry.js +12 -30
- package/dist/core/prompts/runtime.js +3 -1
- package/dist/core/prompts/templates/system/autopilot_system.hbs +28 -4
- package/dist/core/protocols/a2a/sdk/executor.js +3 -1
- package/dist/core/protocols/a2a/sdk/server.js +5 -4
- package/dist/core/protocols/acp/acp-command-runner.js +7 -6
- package/dist/core/protocols/acp/acp-session-persistence.js +13 -10
- package/dist/core/protocols/acp/formal-agent.js +13 -6
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/protocols/acp/stdio-server.js +6 -6
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/agent-server-runtime.js +3 -2
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +71 -6
- package/dist/core/runtime/loop-finalize.js +3 -0
- package/dist/core/runtime/loop-session-runner.js +5 -0
- package/dist/core/runtime/loop.js +4 -0
- package/dist/core/runtime/paths.js +9 -6
- package/dist/core/runtime/spawn-interactive.js +5 -4
- package/dist/core/security/redaction.js +3 -2
- package/dist/core/session/compaction/index.js +4 -3
- package/dist/core/session/compression.js +3 -1
- package/dist/core/session/manager.js +26 -38
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +27 -9
- package/dist/core/skills/parser.js +3 -2
- package/dist/core/skills/permissions.js +2 -2
- package/dist/core/skills/runtime/MicroTaskRunner.js +1 -1
- package/dist/core/skills/runtime/SkillRunner.js +5 -2
- package/dist/core/slash/steps/slash-execute.js +7 -5
- package/dist/core/slash/strategy.js +1 -1
- package/dist/core/strata/checkpoint/manager.js +16 -10
- package/dist/core/strata/checkpoint/snapshot-create.js +5 -4
- package/dist/core/strata/checkpoint/snapshot-write-tree.js +7 -3
- package/dist/core/strata/engine/shadow-merge-engine.js +4 -2
- package/dist/core/strata/interaction/file-system-provider.js +2 -1
- package/dist/core/strata/layers/file-state-resolver.js +9 -7
- package/dist/core/strata/layers/immutable-git-layer.js +3 -1
- package/dist/core/strata/layers/shadow-driver/readonly-lock.js +8 -6
- package/dist/core/strata/layers/shadow-driver/shadow-driver.js +2 -1
- package/dist/core/strata/layers/worktree.js +9 -10
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +28 -26
- package/dist/core/streaming/canonical/parts-from-llm-stream-chunk.js +1 -11
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/structured-output/json-schema-validator.js +1 -13
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/context-snapshot.js +12 -6
- package/dist/core/sub-agent/controller.js +70 -1
- package/dist/core/sub-agent/core/loop.js +25 -3
- package/dist/core/sub-agent/core/manager.js +343 -117
- package/dist/core/sub-agent/registry-defaults.js +12 -0
- package/dist/core/sub-agent/registry.js +8 -0
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/team.js +98 -0
- package/dist/core/sub-agent/tools/task-await.js +109 -0
- package/dist/core/sub-agent/tools/task-spawn.js +52 -7
- package/dist/core/sub-agent/tools/team.js +92 -0
- package/dist/core/sub-agent/types.js +11 -2
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +7 -12
- package/dist/core/tools/builtin/ast.js +144 -0
- package/dist/core/tools/builtin/code-search/backends/powershell.js +3 -1
- package/dist/core/tools/builtin/code-search/backends/rg.js +3 -1
- package/dist/core/tools/builtin/code-search/executor.js +46 -43
- package/dist/core/tools/builtin/code-search/parse/plain-grep.js +3 -1
- package/dist/core/tools/builtin/code-search/parse/rg-json.js +3 -1
- package/dist/core/tools/builtin/fs.js +90 -7
- package/dist/core/tools/builtin/git.js +242 -0
- package/dist/core/tools/builtin/glob.js +79 -0
- package/dist/core/tools/builtin/index.js +53 -111
- package/dist/core/tools/builtin/interaction.js +13 -15
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +14 -3
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/capability/executor.js +5 -5
- package/dist/core/tools/headless-payload.js +1 -3
- package/dist/core/tools/mapper.js +8 -42
- package/dist/core/tools/parallel/persistence.js +17 -5
- package/dist/core/tools/parallel/scheduler.js +23 -21
- package/dist/core/tools/permissions/permission-rules.js +69 -115
- package/dist/core/tools/plugins/loader.js +4 -3
- package/dist/core/tools/router.js +112 -58
- package/dist/core/tools/session.js +64 -102
- package/dist/core/tools/streaming/ToolCallAccumulator.js +1 -3
- package/dist/core/tools/tool-visibility.js +2 -1
- package/dist/core/tools/types.js +10 -0
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/error.js +79 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +66 -0
- package/dist/core/utils/zod.js +29 -0
- package/dist/core/verification/detect-runner.js +86 -0
- package/dist/core/verification/runner.js +76 -0
- package/dist/core/version.js +3 -1
- package/dist/core/workspace/capabilities.js +3 -2
- package/dist/integrations/langfuse/litellm-langfuse-outcome-reporter.js +9 -8
- package/dist/languages/python/index.js +154 -0
- package/dist/locales/en.js +8 -1
- package/package.json +2 -1
|
@@ -3,8 +3,13 @@ import { z } from 'zod';
|
|
|
3
3
|
import { text } from '../../../locales/index.js';
|
|
4
4
|
import { readFile } from '../../adapters/fs/node-fs.js';
|
|
5
5
|
import { AstParser } from '../../ast/parser.js';
|
|
6
|
+
import { extractImportSpecifiers } from '../../context/ast/import-extractor.js';
|
|
7
|
+
import { resolveImportCandidates } from '../../context/ast/module-resolver.js';
|
|
8
|
+
import { getLogger } from '../../observability/logger.js';
|
|
6
9
|
import { tryGetPluginRegistry } from '../../plugin/registry.js';
|
|
10
|
+
import { spawnCommand } from '../../runtime/process-runner.js';
|
|
7
11
|
import { Phase } from '../../types/runtime.js';
|
|
12
|
+
import { normalizePath } from '../../utils/path.js';
|
|
8
13
|
import { pathPrefixResource } from '../parallel/resource-helpers.js';
|
|
9
14
|
export const astDefsRefsSpec = {
|
|
10
15
|
name: 'code.ast',
|
|
@@ -59,4 +64,143 @@ export async function executeAstDefsRefs(input, ctx) {
|
|
|
59
64
|
// Tree deletion is handled by AstParser's cache cleanup logic or explicit delete if needed
|
|
60
65
|
}
|
|
61
66
|
}
|
|
67
|
+
// ── code.find_references ──────────────────────────────────────────────
|
|
68
|
+
const MAX_SCAN_FILES = 30;
|
|
69
|
+
const RG_TIMEOUT_MS = 10_000;
|
|
70
|
+
export const codeFindReferencesSpec = {
|
|
71
|
+
name: 'code.find_references',
|
|
72
|
+
source: 'builtin',
|
|
73
|
+
intent: 'SEARCH',
|
|
74
|
+
description: text.tools.codeFindReferencesDescription,
|
|
75
|
+
riskLevel: 'low',
|
|
76
|
+
sideEffects: ['fs_read'],
|
|
77
|
+
concurrency: 'parallel_ok',
|
|
78
|
+
computeResources: (input, ctx) => [pathPrefixResource(ctx, input.file)],
|
|
79
|
+
inputSchema: z.object({
|
|
80
|
+
file: z.string().describe('Relative path to the file where the symbol is defined'),
|
|
81
|
+
symbol: z.string().describe('The symbol name to find references for'),
|
|
82
|
+
}),
|
|
83
|
+
outputSchema: z.object({
|
|
84
|
+
definition: z
|
|
85
|
+
.object({
|
|
86
|
+
file: z.string(),
|
|
87
|
+
name: z.string(),
|
|
88
|
+
location: z.any(),
|
|
89
|
+
})
|
|
90
|
+
.nullable(),
|
|
91
|
+
references: z.array(z.object({
|
|
92
|
+
file: z.string(),
|
|
93
|
+
name: z.string(),
|
|
94
|
+
location: z.any(),
|
|
95
|
+
})),
|
|
96
|
+
filesScanned: z.number(),
|
|
97
|
+
}),
|
|
98
|
+
allowedPhases: [Phase.CONTEXT, Phase.EXPLORE, Phase.PLAN, Phase.AUTOPILOT],
|
|
99
|
+
};
|
|
100
|
+
export async function executeCodeFindReferences(input, ctx) {
|
|
101
|
+
const repoRoot = ctx.worktreeRoot || ctx.repoRoot;
|
|
102
|
+
const registry = ctx.languagePlugins ?? tryGetPluginRegistry();
|
|
103
|
+
// 1. Parse the target file to find the definition
|
|
104
|
+
const targetPath = join(repoRoot, input.file);
|
|
105
|
+
const targetCode = await readFile(targetPath, 'utf-8');
|
|
106
|
+
const targetLang = registry?.getByExtension(input.file)?.meta.id;
|
|
107
|
+
let definition = null;
|
|
108
|
+
if (targetLang) {
|
|
109
|
+
const tree = await AstParser.parse(targetCode, targetLang);
|
|
110
|
+
const defs = await AstParser.identifyDefinitions(tree, targetLang);
|
|
111
|
+
const match = defs.find((d) => d.name === input.symbol);
|
|
112
|
+
if (match) {
|
|
113
|
+
definition = { file: input.file, name: match.name, location: match.location };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// 2. Collect candidate files via ripgrep (fast pre-filter)
|
|
117
|
+
const candidates = await rgFindCandidates(repoRoot, input.symbol, input.file);
|
|
118
|
+
// 3. Also collect import neighbors of the target file
|
|
119
|
+
const importNeighbors = await resolveImportNeighbors(input.file, targetCode, repoRoot);
|
|
120
|
+
for (const neighbor of importNeighbors) {
|
|
121
|
+
if (!candidates.includes(neighbor) && neighbor !== input.file) {
|
|
122
|
+
candidates.push(neighbor);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// 4. Cap the number of files to scan
|
|
126
|
+
const toScan = candidates.slice(0, MAX_SCAN_FILES);
|
|
127
|
+
// 5. Parse each candidate and find references
|
|
128
|
+
const references = [];
|
|
129
|
+
for (const file of toScan) {
|
|
130
|
+
try {
|
|
131
|
+
const lang = registry?.getByExtension(file)?.meta.id;
|
|
132
|
+
if (!lang)
|
|
133
|
+
continue;
|
|
134
|
+
const fullPath = join(repoRoot, file);
|
|
135
|
+
const code = await readFile(fullPath, 'utf-8');
|
|
136
|
+
const tree = await AstParser.parse(code, lang);
|
|
137
|
+
const refs = await AstParser.identifyReferences(tree, lang);
|
|
138
|
+
for (const ref of refs) {
|
|
139
|
+
if (ref.name === input.symbol) {
|
|
140
|
+
references.push({ file, name: ref.name, location: ref.location });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
// Skip files that can't be parsed
|
|
146
|
+
getLogger().debug(`[CodeAst] Failed to parse file ${file}: ${error instanceof Error ? error.message : String(error)}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
definition,
|
|
151
|
+
references,
|
|
152
|
+
filesScanned: toScan.length,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Use ripgrep to find files that mention the symbol (fast pre-filter).
|
|
157
|
+
* Returns repo-relative paths, excluding the target file itself.
|
|
158
|
+
*/
|
|
159
|
+
async function rgFindCandidates(repoRoot, symbol, excludeFile) {
|
|
160
|
+
let stdout = '';
|
|
161
|
+
try {
|
|
162
|
+
const result = await spawnCommand({
|
|
163
|
+
command: 'rg',
|
|
164
|
+
args: ['--files-with-matches', '--fixed-strings', '--max-count', '1', symbol, '.'],
|
|
165
|
+
cwd: repoRoot,
|
|
166
|
+
env: process.env,
|
|
167
|
+
timeoutMs: RG_TIMEOUT_MS,
|
|
168
|
+
onStdoutChunk: (chunk) => {
|
|
169
|
+
stdout += Buffer.from(chunk).toString();
|
|
170
|
+
},
|
|
171
|
+
onStderrChunk: () => { },
|
|
172
|
+
});
|
|
173
|
+
if (result.error || result.timedOut || (result.code !== 0 && result.code !== 1)) {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
const normalizedExclude = normalizePath(excludeFile).replace(/^(\.\/|\/)+/, '');
|
|
177
|
+
return stdout
|
|
178
|
+
.split('\n')
|
|
179
|
+
.map((line) => normalizePath(line.trim()).replace(/^(\.\/|\/)+/, ''))
|
|
180
|
+
.filter((f) => f && f !== normalizedExclude);
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
getLogger().debug(`[CodeAst] rg candidate search failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
184
|
+
return [];
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Resolve import neighbors of a file — files that the target imports.
|
|
189
|
+
*/
|
|
190
|
+
async function resolveImportNeighbors(targetFile, targetCode, _repoRoot) {
|
|
191
|
+
const specifiers = extractImportSpecifiers(targetCode);
|
|
192
|
+
const neighbors = [];
|
|
193
|
+
for (const spec of specifiers) {
|
|
194
|
+
if (!spec.startsWith('.'))
|
|
195
|
+
continue;
|
|
196
|
+
const candidates = resolveImportCandidates({ currentFile: targetFile, specifier: spec });
|
|
197
|
+
for (const candidate of candidates) {
|
|
198
|
+
const normalized = normalizePath(candidate).replace(/^(\.\/|\/)+/, '');
|
|
199
|
+
if (normalized && !neighbors.includes(normalized)) {
|
|
200
|
+
neighbors.push(normalized);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return neighbors;
|
|
205
|
+
}
|
|
62
206
|
//# sourceMappingURL=ast.js.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LIMITS } from '../../../../config/limits.js';
|
|
2
|
+
import { getLogger } from '../../../../observability/logger.js';
|
|
2
3
|
import { parsePlainMatches } from '../parse/plain-grep.js';
|
|
3
4
|
export const psBackend = {
|
|
4
5
|
id: 'powershell',
|
|
@@ -12,7 +13,8 @@ export const psBackend = {
|
|
|
12
13
|
});
|
|
13
14
|
return res.exitCode === 0;
|
|
14
15
|
}
|
|
15
|
-
catch {
|
|
16
|
+
catch (error) {
|
|
17
|
+
getLogger().debug(`[CodeSearch] PowerShell compatibility check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
16
18
|
return false;
|
|
17
19
|
}
|
|
18
20
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { resolve } from 'path';
|
|
2
2
|
import { LIMITS } from '../../../../config/limits.js';
|
|
3
|
+
import { getLogger } from '../../../../observability/logger.js';
|
|
3
4
|
import { parseRgJson } from '../parse/rg-json.js';
|
|
4
5
|
export const rgBackend = {
|
|
5
6
|
id: 'rg',
|
|
@@ -9,7 +10,8 @@ export const rgBackend = {
|
|
|
9
10
|
const res = await ctx.runner.execFile('rg', ['--version'], { timeoutMs: 1500 });
|
|
10
11
|
return res.exitCode === 0;
|
|
11
12
|
}
|
|
12
|
-
catch {
|
|
13
|
+
catch (error) {
|
|
14
|
+
getLogger().debug(`[CodeSearch] rg compatibility check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
13
15
|
return false;
|
|
14
16
|
}
|
|
15
17
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { LIMITS } from '../../../config/limits.js';
|
|
2
2
|
import { getLogger } from '../../../observability/logger.js';
|
|
3
3
|
import { spawnCommand } from '../../../runtime/process-runner.js';
|
|
4
|
+
import { isRecord } from '../../../utils/serialize.js';
|
|
4
5
|
import { runWithFallback } from '../../capability/executor.js';
|
|
5
6
|
import { psBackend } from './backends/powershell.js';
|
|
6
7
|
import { rgBackend } from './backends/rg.js';
|
|
@@ -24,53 +25,55 @@ export async function codeSearchExecutor(input, ctx) {
|
|
|
24
25
|
attemptId: ctx.attemptId,
|
|
25
26
|
dryRun: ctx.dryRun,
|
|
26
27
|
// Allow tests (and callers) to override platform; default to host platform.
|
|
27
|
-
platform: ctx.platform
|
|
28
|
-
runner: ctx.runner
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
28
|
+
platform: isRecord(ctx) && typeof ctx.platform === 'string' ? ctx.platform : process.platform,
|
|
29
|
+
runner: isRecord(ctx) && typeof ctx.runner === 'object' && ctx.runner !== null
|
|
30
|
+
? ctx.runner
|
|
31
|
+
: {
|
|
32
|
+
execFile: async (file, args, opts) => {
|
|
33
|
+
const maxStdoutBytes = opts?.maxStdoutBytes ?? Number.POSITIVE_INFINITY;
|
|
34
|
+
let stdout = '';
|
|
35
|
+
let stderr = '';
|
|
36
|
+
let stdoutBytes = 0;
|
|
37
|
+
const result = await spawnCommand({
|
|
38
|
+
command: file,
|
|
39
|
+
args,
|
|
40
|
+
cwd: opts?.cwd ?? ctx.repoRoot,
|
|
41
|
+
timeoutMs: opts?.timeoutMs,
|
|
42
|
+
signal: ctx.signal,
|
|
43
|
+
env: { ...process.env, ...ctx.env, ...opts?.env },
|
|
44
|
+
onStdoutChunk: (chunk) => {
|
|
45
|
+
if (stdoutBytes >= maxStdoutBytes)
|
|
46
|
+
return;
|
|
47
|
+
const buffer = Buffer.from(chunk);
|
|
48
|
+
const remaining = maxStdoutBytes - stdoutBytes;
|
|
49
|
+
if (buffer.length <= remaining) {
|
|
50
|
+
stdout += buffer.toString();
|
|
51
|
+
stdoutBytes += buffer.length;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
stdout += buffer.subarray(0, remaining).toString();
|
|
55
|
+
stdoutBytes += remaining;
|
|
56
|
+
},
|
|
57
|
+
onStderrChunk: (chunk) => {
|
|
58
|
+
stderr += Buffer.from(chunk).toString();
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
if (result.error) {
|
|
62
|
+
return {
|
|
63
|
+
stdout,
|
|
64
|
+
stderr: stderr || result.error.message,
|
|
65
|
+
exitCode: 1,
|
|
66
|
+
timedOut: false,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
59
69
|
return {
|
|
60
70
|
stdout,
|
|
61
|
-
stderr
|
|
62
|
-
exitCode: 1,
|
|
63
|
-
timedOut:
|
|
71
|
+
stderr,
|
|
72
|
+
exitCode: result.code ?? 1,
|
|
73
|
+
timedOut: result.timedOut,
|
|
64
74
|
};
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
stdout,
|
|
68
|
-
stderr,
|
|
69
|
-
exitCode: result.code ?? 1,
|
|
70
|
-
timedOut: result.timedOut,
|
|
71
|
-
};
|
|
75
|
+
},
|
|
72
76
|
},
|
|
73
|
-
},
|
|
74
77
|
limits: {
|
|
75
78
|
timeoutMs: LIMITS.defaultToolTimeoutMs,
|
|
76
79
|
maxOutputBytes: LIMITS.maxToolOutputBytes,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../../../../observability/logger.js';
|
|
1
2
|
/**
|
|
2
3
|
* A versatile parser for non-JSON-native search tools.
|
|
3
4
|
* Supports PowerShell JSON objects and traditional line-based formats.
|
|
@@ -29,8 +30,9 @@ function parsePsJson(stdout, maxMatches) {
|
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
catch {
|
|
33
|
+
catch (error) {
|
|
33
34
|
// If JSON parsing fails, fallback to empty
|
|
35
|
+
getLogger().debug(`[CodeSearch] Failed to parse PowerShell JSON output: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
36
|
}
|
|
35
37
|
return { matches, truncated };
|
|
36
38
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../../../../observability/logger.js';
|
|
1
2
|
/**
|
|
2
3
|
* Parses the newline-delimited JSON output from ripgrep (--json).
|
|
3
4
|
*/
|
|
@@ -22,8 +23,9 @@ export function parseRgJson(stdout, opts) {
|
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
|
-
catch {
|
|
26
|
+
catch (error) {
|
|
26
27
|
// Ignore malformed JSON lines
|
|
28
|
+
getLogger().debug(`[CodeSearch] Failed to parse rg JSON line: ${error instanceof Error ? error.message : String(error)}`);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
31
|
return { matches, truncated };
|
|
@@ -5,8 +5,10 @@ import { z } from 'zod';
|
|
|
5
5
|
import { text } from '../../../locales/index.js';
|
|
6
6
|
import { AtomicFileWriter } from '../../adapters/fs/atomic-file-writer.js';
|
|
7
7
|
import { mkdir, readFile, readdir, stat } from '../../adapters/fs/node-fs.js';
|
|
8
|
+
import { getLogger } from '../../observability/logger.js';
|
|
8
9
|
import { Phase } from '../../types/runtime.js';
|
|
9
10
|
import { normalizeRepoRelativePath } from '../../utils/path.js';
|
|
11
|
+
import { isRecord } from '../../utils/serialize.js';
|
|
10
12
|
import { pathPrefixResource } from '../parallel/resource-helpers.js';
|
|
11
13
|
const FsListEntryType = z.enum(['file', 'dir', 'symlink', 'other']);
|
|
12
14
|
const fsListInputSchema = z.preprocess((raw) => {
|
|
@@ -218,7 +220,8 @@ function shouldIncludeListedEntry(dir, entryName, includeHidden) {
|
|
|
218
220
|
assertNotReservedRepoPrefix(childPath);
|
|
219
221
|
return true;
|
|
220
222
|
}
|
|
221
|
-
catch {
|
|
223
|
+
catch (error) {
|
|
224
|
+
getLogger().debug(`[Fs] Reserved path check failed for "${childPath}": ${error instanceof Error ? error.message : String(error)}`);
|
|
222
225
|
return false;
|
|
223
226
|
}
|
|
224
227
|
}
|
|
@@ -338,12 +341,13 @@ export const fsWriteFileSpec = {
|
|
|
338
341
|
bytesWritten: z.number().int().nonnegative(),
|
|
339
342
|
}),
|
|
340
343
|
summarizeArgsForAuthorization: async (args) => {
|
|
341
|
-
const
|
|
342
|
-
const
|
|
344
|
+
const a = isRecord(args) ? args : {};
|
|
345
|
+
const encoding = typeof a.encoding === 'string' ? a.encoding : 'utf-8';
|
|
346
|
+
const content = String(a.content ?? '');
|
|
343
347
|
const bytes = Buffer.byteLength(content, 'utf8');
|
|
344
348
|
const sha256 = createHash('sha256').update(content, 'utf8').digest('hex');
|
|
345
349
|
return JSON.stringify({
|
|
346
|
-
file:
|
|
350
|
+
file: typeof a.file === 'string' ? a.file : undefined,
|
|
347
351
|
encoding,
|
|
348
352
|
bytes,
|
|
349
353
|
sha256,
|
|
@@ -364,6 +368,79 @@ export async function executeFsWriteFile(input, ctx) {
|
|
|
364
368
|
bytesWritten: contentBytes.length,
|
|
365
369
|
};
|
|
366
370
|
}
|
|
371
|
+
// ── fs.edit_file ──────────────────────────────────────────────────────
|
|
372
|
+
const fsEditFileInputSchema = z.preprocess((raw) => {
|
|
373
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
374
|
+
return raw;
|
|
375
|
+
const input = raw;
|
|
376
|
+
if (typeof input.file === 'string')
|
|
377
|
+
return input;
|
|
378
|
+
const alias = input.path ?? input.file_path ?? input.filePath;
|
|
379
|
+
if (typeof alias !== 'string')
|
|
380
|
+
return input;
|
|
381
|
+
return {
|
|
382
|
+
...input,
|
|
383
|
+
file: alias,
|
|
384
|
+
};
|
|
385
|
+
}, z.object({
|
|
386
|
+
file: z.string().describe('Relative path to the file from the repository root'),
|
|
387
|
+
old_string: z.string().min(1).describe('The exact text to find in the file'),
|
|
388
|
+
new_string: z.string().describe('The replacement text'),
|
|
389
|
+
replace_all: z
|
|
390
|
+
.boolean()
|
|
391
|
+
.optional()
|
|
392
|
+
.describe('Replace all occurrences instead of just the first one'),
|
|
393
|
+
}));
|
|
394
|
+
export const fsEditFileSpec = {
|
|
395
|
+
name: 'fs.edit_file',
|
|
396
|
+
source: 'builtin',
|
|
397
|
+
intent: 'WRITE',
|
|
398
|
+
description: text.tools.fsEditFileDescription,
|
|
399
|
+
riskLevel: 'high',
|
|
400
|
+
sideEffects: ['fs_write'],
|
|
401
|
+
concurrency: 'serial_only',
|
|
402
|
+
computeResources: (input, ctx) => [pathPrefixResource(ctx, input.file)],
|
|
403
|
+
allowedPhases: [Phase.SLASH, Phase.AUTOPILOT],
|
|
404
|
+
inputSchema: fsEditFileInputSchema,
|
|
405
|
+
outputSchema: z.object({
|
|
406
|
+
ok: z.boolean(),
|
|
407
|
+
path: z.string(),
|
|
408
|
+
replacements: z.number().int().nonnegative(),
|
|
409
|
+
}),
|
|
410
|
+
summarizeArgsForAuthorization: async (args) => {
|
|
411
|
+
const a = isRecord(args) ? args : {};
|
|
412
|
+
return JSON.stringify({
|
|
413
|
+
file: typeof a.file === 'string' ? a.file : undefined,
|
|
414
|
+
oldString: String(a.old_string ?? '').slice(0, 80),
|
|
415
|
+
newString: String(a.new_string ?? '').slice(0, 80),
|
|
416
|
+
replaceAll: Boolean(a.replace_all),
|
|
417
|
+
});
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
export async function executeFsEditFile(input, ctx) {
|
|
421
|
+
if (ctx.dryRun) {
|
|
422
|
+
return { ok: true, path: input.file, replacements: 0 };
|
|
423
|
+
}
|
|
424
|
+
const { absolutePath } = resolveRepoRelativePath(ctx.repoRoot, input.file);
|
|
425
|
+
const content = await readFile(absolutePath, 'utf-8');
|
|
426
|
+
const count = input.old_string ? content.split(input.old_string).length - 1 : 0;
|
|
427
|
+
if (count === 0) {
|
|
428
|
+
throw new Error(`old_string not found in file "${input.file}". Ensure the string matches exactly, including whitespace and indentation.`);
|
|
429
|
+
}
|
|
430
|
+
if (count > 1 && !input.replace_all) {
|
|
431
|
+
throw new Error(`old_string found ${count} times in "${input.file}", expected exactly 1. Use replace_all: true to replace all occurrences, or provide more surrounding context to uniquely identify the location.`);
|
|
432
|
+
}
|
|
433
|
+
const updated = input.replace_all
|
|
434
|
+
? content.replaceAll(input.old_string, input.new_string)
|
|
435
|
+
: content.replace(input.old_string, input.new_string);
|
|
436
|
+
const writer = new AtomicFileWriter();
|
|
437
|
+
await writer.writeAtomic(absolutePath, Buffer.from(updated, 'utf8'));
|
|
438
|
+
return {
|
|
439
|
+
ok: true,
|
|
440
|
+
path: input.file,
|
|
441
|
+
replacements: count,
|
|
442
|
+
};
|
|
443
|
+
}
|
|
367
444
|
const fsCreateDirectoryInputSchema = z.preprocess((raw) => {
|
|
368
445
|
if (typeof raw === 'string')
|
|
369
446
|
return { path: raw };
|
|
@@ -390,7 +467,10 @@ export const fsCreateDirectorySpec = {
|
|
|
390
467
|
ok: z.boolean(),
|
|
391
468
|
path: z.string(),
|
|
392
469
|
}),
|
|
393
|
-
summarizeArgsForAuthorization: async (args) =>
|
|
470
|
+
summarizeArgsForAuthorization: async (args) => {
|
|
471
|
+
const a = isRecord(args) ? args : {};
|
|
472
|
+
return JSON.stringify({ path: a.path, recursive: a.recursive });
|
|
473
|
+
},
|
|
394
474
|
};
|
|
395
475
|
export async function executeFsCreateDirectory(input, ctx) {
|
|
396
476
|
if (ctx.dryRun) {
|
|
@@ -427,7 +507,10 @@ export const fsDeleteFileSpec = {
|
|
|
427
507
|
path: z.string(),
|
|
428
508
|
deleted: z.boolean(),
|
|
429
509
|
}),
|
|
430
|
-
summarizeArgsForAuthorization: async (args) =>
|
|
510
|
+
summarizeArgsForAuthorization: async (args) => {
|
|
511
|
+
const a = isRecord(args) ? args : {};
|
|
512
|
+
return JSON.stringify({ file: a.file, missingOk: a.missingOk });
|
|
513
|
+
},
|
|
431
514
|
};
|
|
432
515
|
export async function executeFsDeleteFile(input, ctx) {
|
|
433
516
|
if (ctx.dryRun) {
|
|
@@ -439,7 +522,7 @@ export async function executeFsDeleteFile(input, ctx) {
|
|
|
439
522
|
await stat(absolutePath);
|
|
440
523
|
}
|
|
441
524
|
catch (e) {
|
|
442
|
-
const code = e && typeof e === '
|
|
525
|
+
const code = isRecord(e) && typeof e.code === 'string' ? e.code : undefined;
|
|
443
526
|
if (code === 'ENOENT')
|
|
444
527
|
exists = false;
|
|
445
528
|
else
|