salmon-loop 0.4.1 → 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/provider.js +2 -10
- package/dist/cli/commands/config.js +2 -2
- package/dist/cli/commands/mode.js +2 -2
- package/dist/cli/commands/run/handler.js +3 -1
- package/dist/cli/commands/run/loop-params.js +1 -0
- package/dist/cli/commands/run/runtime-options.js +3 -1
- package/dist/cli/config.js +0 -8
- package/dist/cli/locales/en.js +2 -2
- package/dist/cli/reporters/standard.js +10 -0
- 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/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/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 +30 -28
- package/dist/core/context/gatherers/git-history-gatherer.js +3 -1
- package/dist/core/context/gatherers/knowledge-gatherer.js +18 -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 +4 -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/truncation/strategies/json.js +5 -2
- package/dist/core/context/truncation/type-detector.js +3 -1
- package/dist/core/extensions/paths.js +2 -2
- package/dist/core/facades/cli-authorization-provider.js +1 -0
- package/dist/core/feedback/parsers.js +290 -1
- package/dist/core/grizzco/dsl/llm-strategy.js +1 -1
- package/dist/core/grizzco/engine/observability/loop-telemetry.js +5 -2
- package/dist/core/grizzco/engine/outcome/loop-result-mapper.js +15 -3
- package/dist/core/grizzco/engine/transaction/attempt-failure.js +44 -20
- 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 +3 -1
- package/dist/core/grizzco/services/implementations/default/GitConfigService.js +2 -1
- package/dist/core/grizzco/steps/autopilot.js +21 -32
- package/dist/core/grizzco/steps/explore.js +5 -2
- package/dist/core/grizzco/steps/generateReview.js +3 -1
- package/dist/core/grizzco/steps/research.js +3 -1
- package/dist/core/grizzco/steps/verify.js +7 -1
- package/dist/core/grizzco/validation/AstValidationService.js +3 -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 +13 -8
- package/dist/core/llm/ai-sdk/request-params.js +1 -3
- package/dist/core/llm/ai-sdk/retry-classifier.js +12 -4
- package/dist/core/llm/ai-sdk/retry-executor.js +1 -1
- package/dist/core/llm/errors.js +5 -4
- 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 +3 -0
- package/dist/core/mcp/bridge/resource-context-provider.js +3 -1
- package/dist/core/mcp/catalog/discovery.js +3 -1
- package/dist/core/mcp/client/connection-manager.js +4 -2
- package/dist/core/mcp/client/transport-factory.js +7 -3
- package/dist/core/observability/audit-file.js +2 -1
- package/dist/core/observability/audit-trail.js +3 -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/permission-gate/default-gate.js +5 -8
- package/dist/core/plan/storage.js +7 -4
- package/dist/core/plugin/loader.js +3 -1
- package/dist/core/prompts/registry.js +1 -1
- 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 +3 -1
- 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 +3 -2
- package/dist/core/protocols/acp/permission-provider.js +3 -2
- package/dist/core/reflection/engine.js +114 -14
- package/dist/core/runtime/batch-runner.js +81 -0
- package/dist/core/runtime/initialize.js +2 -1
- 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/compression.js +3 -1
- package/dist/core/session/manager.js +2 -1
- package/dist/core/session/pruning-strategy.js +2 -1
- package/dist/core/session/token-tracker.js +11 -4
- package/dist/core/skills/permissions.js +2 -2
- 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 +2 -1
- package/dist/core/strata/runtime/environment.js +2 -1
- package/dist/core/strata/runtime/synchronizer.js +18 -17
- package/dist/core/structured-output/json-extract.js +3 -1
- package/dist/core/sub-agent/artifacts/store.js +2 -1
- package/dist/core/sub-agent/core/manager.js +24 -1
- package/dist/core/sub-agent/registry-defaults.js +2 -2
- package/dist/core/sub-agent/summary.js +96 -0
- package/dist/core/sub-agent/tools/task-spawn.js +7 -4
- package/dist/core/target-runtime/profile.js +3 -1
- package/dist/core/tools/audit.js +3 -2
- package/dist/core/tools/budget.js +3 -1
- 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/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 +76 -1
- 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 +12 -4
- package/dist/core/tools/builtin/knowledge.js +146 -4
- package/dist/core/tools/builtin/proposal.js +3 -1
- package/dist/core/tools/builtin/verify.js +35 -3
- package/dist/core/tools/permissions/permission-rules.js +3 -1
- package/dist/core/tools/router.js +88 -5
- package/dist/core/tools/session.js +10 -5
- package/dist/core/types/batch.js +2 -0
- package/dist/core/utils/sanitizer.js +5 -2
- package/dist/core/utils/serialize.js +5 -2
- 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/languages/python/index.js +154 -0
- package/dist/locales/en.js +6 -0
- package/package.json +2 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getLogger } from '../observability/logger.js';
|
|
1
2
|
export function parseTscOutput(output) {
|
|
2
3
|
const diagnostics = [];
|
|
3
4
|
const lines = output.split('\n');
|
|
@@ -46,8 +47,296 @@ export function parsePythonError(output) {
|
|
|
46
47
|
}
|
|
47
48
|
return diagnostics;
|
|
48
49
|
}
|
|
50
|
+
export function parsePytestOutput(output) {
|
|
51
|
+
const diagnostics = [];
|
|
52
|
+
const lines = output.split('\n');
|
|
53
|
+
// Pattern 1: FAILED lines (--tb=short -q format)
|
|
54
|
+
// e.g. FAILED tests/test_foo.py::test_bar - AssertionError: expected 5, got 3
|
|
55
|
+
// e.g. FAILED tests/test_foo.py::TestClass::test_method - ValueError: bad value
|
|
56
|
+
const failedRegex = /^FAILED\s+(\S+?)\s+-\s+(.+)$/;
|
|
57
|
+
// Pattern 2: Assertion detail lines (E prefix)
|
|
58
|
+
// e.g. E AssertionError: expected 5, got 3
|
|
59
|
+
const assertionRegex = /^\s*E\s{2,}(\w+(?:Error|Exception)?):\s*(.+)$/;
|
|
60
|
+
// Pattern 3: File:line reference in traceback
|
|
61
|
+
// e.g. tests/test_module.py:42: in test_function
|
|
62
|
+
const fileLineRegex = /^\s*(\S+\.py):(\d+): in /;
|
|
63
|
+
// Pattern 4: One-line format (--tb=line)
|
|
64
|
+
// e.g. tests/test_module.py:42: AssertionError
|
|
65
|
+
const lineFormatRegex = /^(\S+\.py):(\d+):\s+(\w+(?:Error|Exception)?)$/;
|
|
66
|
+
// Track current file context for enhancing FAILED diagnostics
|
|
67
|
+
let currentFile = null;
|
|
68
|
+
let currentLine = null;
|
|
69
|
+
// Map test_id -> { file, line, assertion } for merging
|
|
70
|
+
const assertionMap = new Map();
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i];
|
|
73
|
+
// Collect file:line context from tracebacks
|
|
74
|
+
const fileMatch = line.match(fileLineRegex);
|
|
75
|
+
if (fileMatch) {
|
|
76
|
+
currentFile = fileMatch[1];
|
|
77
|
+
currentLine = parseInt(fileMatch[2]);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// Collect assertion details
|
|
81
|
+
const assertionMatch = line.match(assertionRegex);
|
|
82
|
+
if (assertionMatch && currentFile) {
|
|
83
|
+
// Associate with the most recent file context
|
|
84
|
+
const key = `${currentFile}:${currentLine}`;
|
|
85
|
+
if (!assertionMap.has(key)) {
|
|
86
|
+
assertionMap.set(key, {
|
|
87
|
+
file: currentFile,
|
|
88
|
+
line: currentLine ?? undefined,
|
|
89
|
+
assertion: `${assertionMatch[1]}: ${assertionMatch[2]}`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
// Parse FAILED lines — the primary signal
|
|
95
|
+
const failedMatch = line.match(failedRegex);
|
|
96
|
+
if (failedMatch) {
|
|
97
|
+
const testId = failedMatch[1]; // e.g. tests/test_foo.py::test_bar
|
|
98
|
+
const errorMessage = failedMatch[2];
|
|
99
|
+
// Extract file from test_id (everything before ::)
|
|
100
|
+
const testFile = testId.split('::')[0];
|
|
101
|
+
diagnostics.push({
|
|
102
|
+
file: testFile,
|
|
103
|
+
severity: 'error',
|
|
104
|
+
message: errorMessage,
|
|
105
|
+
source: 'pytest',
|
|
106
|
+
});
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Parse one-line format
|
|
110
|
+
const lineFormatMatch = line.match(lineFormatRegex);
|
|
111
|
+
if (lineFormatMatch) {
|
|
112
|
+
diagnostics.push({
|
|
113
|
+
file: lineFormatMatch[1],
|
|
114
|
+
line: parseInt(lineFormatMatch[2]),
|
|
115
|
+
severity: 'error',
|
|
116
|
+
message: lineFormatMatch[3],
|
|
117
|
+
source: 'pytest',
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Enhance FAILED diagnostics with file:line info from tracebacks
|
|
122
|
+
if (diagnostics.length > 0 && assertionMap.size > 0) {
|
|
123
|
+
for (const diag of diagnostics) {
|
|
124
|
+
if (diag.source === 'pytest' && !diag.line) {
|
|
125
|
+
// Find matching assertion detail by file
|
|
126
|
+
for (const [, info] of assertionMap) {
|
|
127
|
+
if (info.file === diag.file && info.line) {
|
|
128
|
+
diag.line = info.line;
|
|
129
|
+
if (info.assertion && diag.message.length < info.assertion.length) {
|
|
130
|
+
diag.message = info.assertion;
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return diagnostics;
|
|
139
|
+
}
|
|
140
|
+
export function parseJestOutput(output) {
|
|
141
|
+
const diagnostics = [];
|
|
142
|
+
const lines = output.split('\n');
|
|
143
|
+
// Pattern 1: FAIL src/foo.test.ts
|
|
144
|
+
const suiteFailRegex = /^(?:FAIL|✗|✘)\s+(\S+)$/;
|
|
145
|
+
// Pattern 2: ● Test suite failed to run / ● test_name
|
|
146
|
+
const bulletRegex = /^\s*●\s+(.+)$/;
|
|
147
|
+
// Pattern 3: inline error: src/foo.ts:10:5 - error TS2322: ...
|
|
148
|
+
const inlineRegex = /^(\S+\.tsx?):(\d+):(\d+)\s+-\s+(error|warning)\s+(.+)$/;
|
|
149
|
+
let currentSuite = null;
|
|
150
|
+
for (const line of lines) {
|
|
151
|
+
const suiteMatch = line.match(suiteFailRegex);
|
|
152
|
+
if (suiteMatch) {
|
|
153
|
+
currentSuite = suiteMatch[1];
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const inlineMatch = line.match(inlineRegex);
|
|
157
|
+
if (inlineMatch) {
|
|
158
|
+
diagnostics.push({
|
|
159
|
+
file: inlineMatch[1],
|
|
160
|
+
line: parseInt(inlineMatch[2]),
|
|
161
|
+
column: parseInt(inlineMatch[3]),
|
|
162
|
+
severity: inlineMatch[4],
|
|
163
|
+
message: inlineMatch[5],
|
|
164
|
+
source: 'jest',
|
|
165
|
+
});
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const bulletMatch = line.match(bulletRegex);
|
|
169
|
+
if (bulletMatch && currentSuite) {
|
|
170
|
+
diagnostics.push({
|
|
171
|
+
file: currentSuite,
|
|
172
|
+
severity: 'error',
|
|
173
|
+
message: bulletMatch[1],
|
|
174
|
+
source: 'jest',
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return diagnostics;
|
|
179
|
+
}
|
|
180
|
+
// ── Structured JSON parsers ──────────────────────────────────────────────────
|
|
181
|
+
/** Parse jest --json output (single JSON object). */
|
|
182
|
+
export function parseJestJson(output) {
|
|
183
|
+
const diagnostics = [];
|
|
184
|
+
let parsed;
|
|
185
|
+
try {
|
|
186
|
+
parsed = JSON.parse(output);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
getLogger().debug(`[Parsers] Failed to parse jest JSON output: ${error instanceof Error ? error.message : String(error)}`);
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
192
|
+
for (const suite of parsed.testResults ?? []) {
|
|
193
|
+
for (const assertion of suite.assertionResults ?? []) {
|
|
194
|
+
if (assertion.status === 'failed') {
|
|
195
|
+
diagnostics.push({
|
|
196
|
+
file: suite.name,
|
|
197
|
+
severity: 'error',
|
|
198
|
+
message: (assertion.failureMessages ?? []).join('\n') ||
|
|
199
|
+
`Test failed: ${(assertion.ancestorTitles ?? []).join(' > ')} > ${assertion.fullName ?? assertion.title}`,
|
|
200
|
+
source: 'jest',
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return diagnostics;
|
|
206
|
+
}
|
|
207
|
+
/** Extract test summary from jest --json output. */
|
|
208
|
+
export function parseJestJsonSummary(output) {
|
|
209
|
+
try {
|
|
210
|
+
const parsed = JSON.parse(output);
|
|
211
|
+
if (typeof parsed.numTotalTests === 'number') {
|
|
212
|
+
const passed = parsed.numPassedTests ?? 0;
|
|
213
|
+
const failed = parsed.numFailedTests ?? 0;
|
|
214
|
+
const total = parsed.numTotalTests;
|
|
215
|
+
return { total, passed, failed, skipped: total - passed - failed };
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
/* not JSON */
|
|
220
|
+
getLogger().debug(`[Parsers] Failed to parse jest JSON summary: ${error instanceof Error ? error.message : String(error)}`);
|
|
221
|
+
}
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
/** Parse eslint --format json output (JSON array). */
|
|
225
|
+
export function parseEslintJson(output) {
|
|
226
|
+
const diagnostics = [];
|
|
227
|
+
let parsed;
|
|
228
|
+
try {
|
|
229
|
+
parsed = JSON.parse(output);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
getLogger().debug(`[Parsers] Failed to parse eslint JSON output: ${error instanceof Error ? error.message : String(error)}`);
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
if (!Array.isArray(parsed))
|
|
236
|
+
return [];
|
|
237
|
+
for (const file of parsed) {
|
|
238
|
+
for (const msg of file.messages ?? []) {
|
|
239
|
+
diagnostics.push({
|
|
240
|
+
file: file.filePath,
|
|
241
|
+
line: msg.line,
|
|
242
|
+
column: msg.column,
|
|
243
|
+
severity: msg.severity === 2 ? 'error' : 'warning',
|
|
244
|
+
message: msg.message + (msg.ruleId ? ` (${msg.ruleId})` : ''),
|
|
245
|
+
source: 'eslint',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
return diagnostics;
|
|
250
|
+
}
|
|
251
|
+
/** Parse bun test --json NDJSON output. */
|
|
252
|
+
export function parseBunTestNdjson(output) {
|
|
253
|
+
const diagnostics = [];
|
|
254
|
+
for (const line of output.split('\n')) {
|
|
255
|
+
const trimmed = line.trim();
|
|
256
|
+
if (!trimmed.startsWith('{'))
|
|
257
|
+
continue;
|
|
258
|
+
try {
|
|
259
|
+
const evt = JSON.parse(trimmed);
|
|
260
|
+
if (evt.type === 'test-fail' || evt.data?.status === 'fail') {
|
|
261
|
+
const data = evt.data ?? {};
|
|
262
|
+
diagnostics.push({
|
|
263
|
+
file: data.file ?? data.sourceFile ?? 'unknown',
|
|
264
|
+
severity: 'error',
|
|
265
|
+
message: data.error?.message ?? data.message ?? 'Test failed',
|
|
266
|
+
source: 'bun',
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch (error) {
|
|
271
|
+
/* skip non-JSON lines */
|
|
272
|
+
getLogger().debug(`[Parsers] Failed to parse bun test NDJSON line: ${error instanceof Error ? error.message : String(error)}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return diagnostics;
|
|
276
|
+
}
|
|
277
|
+
/** Parse go test -json NDJSON output. */
|
|
278
|
+
export function parseGoTestNdjson(output) {
|
|
279
|
+
const diagnostics = [];
|
|
280
|
+
for (const line of output.split('\n')) {
|
|
281
|
+
const trimmed = line.trim();
|
|
282
|
+
if (!trimmed.startsWith('{'))
|
|
283
|
+
continue;
|
|
284
|
+
try {
|
|
285
|
+
const evt = JSON.parse(trimmed);
|
|
286
|
+
if (evt.Action === 'fail' && evt.Test) {
|
|
287
|
+
diagnostics.push({
|
|
288
|
+
file: evt.Package ?? 'unknown',
|
|
289
|
+
severity: 'error',
|
|
290
|
+
message: `Test failed: ${evt.Test}`,
|
|
291
|
+
source: 'go',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
/* skip non-JSON lines */
|
|
297
|
+
getLogger().debug(`[Parsers] Failed to parse go test NDJSON line: ${error instanceof Error ? error.message : String(error)}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return diagnostics;
|
|
301
|
+
}
|
|
302
|
+
/** Dispatch to the correct structured parser based on runner type. */
|
|
303
|
+
export function parseRunnerOutput(output, runner) {
|
|
304
|
+
switch (runner) {
|
|
305
|
+
case 'jest':
|
|
306
|
+
case 'vitest':
|
|
307
|
+
return parseJestJson(output);
|
|
308
|
+
case 'eslint':
|
|
309
|
+
return parseEslintJson(output);
|
|
310
|
+
case 'bun':
|
|
311
|
+
return parseBunTestNdjson(output);
|
|
312
|
+
case 'go':
|
|
313
|
+
return parseGoTestNdjson(output);
|
|
314
|
+
case 'pytest':
|
|
315
|
+
case 'tsc':
|
|
316
|
+
case 'unknown':
|
|
317
|
+
default:
|
|
318
|
+
return parseGenericOutput(output);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** Extract test summary from structured JSON output when available. */
|
|
322
|
+
export function parseStructuredSummary(output, runner) {
|
|
323
|
+
switch (runner) {
|
|
324
|
+
case 'jest':
|
|
325
|
+
case 'vitest':
|
|
326
|
+
return parseJestJsonSummary(output);
|
|
327
|
+
default:
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
// ── Text-based heuristic dispatcher (fallback) ──────────────────────────────
|
|
49
332
|
export function parseGenericOutput(output) {
|
|
50
|
-
//
|
|
333
|
+
// Try pytest first (SWE-bench's primary test runner)
|
|
334
|
+
const pytest = parsePytestOutput(output);
|
|
335
|
+
if (pytest.length > 0)
|
|
336
|
+
return pytest;
|
|
337
|
+
const jest = parseJestOutput(output);
|
|
338
|
+
if (jest.length > 0)
|
|
339
|
+
return jest;
|
|
51
340
|
const tsc = parseTscOutput(output);
|
|
52
341
|
if (tsc.length > 0)
|
|
53
342
|
return tsc;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LIMITS } from '../../../config/limits.js';
|
|
2
|
+
import { getLogger } from '../../../observability/logger.js';
|
|
2
3
|
export class LoopTelemetry {
|
|
3
4
|
now;
|
|
4
5
|
logs = [];
|
|
@@ -16,11 +17,13 @@ export class LoopTelemetry {
|
|
|
16
17
|
try {
|
|
17
18
|
return JSON.stringify(output);
|
|
18
19
|
}
|
|
19
|
-
catch {
|
|
20
|
+
catch (error) {
|
|
21
|
+
getLogger().debug(`[LoopTelemetry] JSON.stringify failed, falling back to String(): ${error instanceof Error ? error.message : String(error)}`);
|
|
20
22
|
try {
|
|
21
23
|
return String(output);
|
|
22
24
|
}
|
|
23
|
-
catch {
|
|
25
|
+
catch (stringError) {
|
|
26
|
+
getLogger().debug(`[LoopTelemetry] String() conversion also failed: ${stringError instanceof Error ? stringError.message : String(stringError)}`);
|
|
24
27
|
return '[Unserializable]';
|
|
25
28
|
}
|
|
26
29
|
}
|
|
@@ -23,6 +23,12 @@ function toRootCauseCode(code) {
|
|
|
23
23
|
? code
|
|
24
24
|
: undefined;
|
|
25
25
|
}
|
|
26
|
+
function applyPassthroughFields(result, options) {
|
|
27
|
+
if (options.tags)
|
|
28
|
+
result.tags = options.tags;
|
|
29
|
+
if (options.providerMeta)
|
|
30
|
+
result.providerMeta = options.providerMeta;
|
|
31
|
+
}
|
|
26
32
|
export function buildLoopResultFromTransaction({ executionReport, flowMode, options, telemetry, auditPath, }) {
|
|
27
33
|
const profile = resolveExecutionProfile(flowMode);
|
|
28
34
|
const rootCause = toRootCauseCode(executionReport.lastErrorCode);
|
|
@@ -93,7 +99,7 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
|
|
|
93
99
|
const usage = getTokenUsageFromAuditTrail() ?? undefined;
|
|
94
100
|
const budgetSummary = getBudgetRunSummary() ?? undefined;
|
|
95
101
|
if (options.dryRun || profile.readOnly) {
|
|
96
|
-
|
|
102
|
+
const result = {
|
|
97
103
|
success: true,
|
|
98
104
|
reason: text.loop.operationCompleted,
|
|
99
105
|
reasonCode: options.dryRun ? 'DRY_RUN' : 'SUCCESS',
|
|
@@ -115,8 +121,10 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
|
|
|
115
121
|
fsMode: executionReport.flowReport.fsMode ?? flowMode,
|
|
116
122
|
budgetSummary,
|
|
117
123
|
};
|
|
124
|
+
applyPassthroughFields(result, options);
|
|
125
|
+
return result;
|
|
118
126
|
}
|
|
119
|
-
|
|
127
|
+
const result = {
|
|
120
128
|
success: true,
|
|
121
129
|
reason: text.loop.operationCompleted,
|
|
122
130
|
reasonCode: 'SUCCESS',
|
|
@@ -138,6 +146,8 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
|
|
|
138
146
|
fsMode: executionReport.flowReport.fsMode ?? flowMode,
|
|
139
147
|
budgetSummary,
|
|
140
148
|
};
|
|
149
|
+
applyPassthroughFields(result, options);
|
|
150
|
+
return result;
|
|
141
151
|
}
|
|
142
152
|
const retryFailureReason = executionReport.history.at(-1)?.error ?? text.loop.loopExecutionFailed;
|
|
143
153
|
const failureReason = executionReport.retryExhausted
|
|
@@ -164,7 +174,7 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
|
|
|
164
174
|
remediationSteps,
|
|
165
175
|
fallbackMessage: failureReason,
|
|
166
176
|
});
|
|
167
|
-
|
|
177
|
+
const result = {
|
|
168
178
|
success: false,
|
|
169
179
|
reason: resultReason,
|
|
170
180
|
reasonCode,
|
|
@@ -195,6 +205,8 @@ export function buildLoopResultFromTransaction({ executionReport, flowMode, opti
|
|
|
195
205
|
? executionReport.terminalInputRequired
|
|
196
206
|
: undefined,
|
|
197
207
|
};
|
|
208
|
+
applyPassthroughFields(result, options);
|
|
209
|
+
return result;
|
|
198
210
|
}
|
|
199
211
|
export function buildLoopFailureResult({ message, flowMode, telemetry, auditPath, reasonCode, failurePhase, errorCode, }) {
|
|
200
212
|
const usage = getTokenUsageFromAuditTrail() ?? undefined;
|
|
@@ -7,6 +7,8 @@ import { isRecoverableToolInputErrorCode } from '../../../tools/recoverable-tool
|
|
|
7
7
|
import { EXECUTION_PHASES } from '../../../types/runtime.js';
|
|
8
8
|
import { isRecord } from '../../../utils/serialize.js';
|
|
9
9
|
import { classifyError, isRetryable } from '../../../verification/runner.js';
|
|
10
|
+
// Used by non-autopilot modes to determine retry eligibility.
|
|
11
|
+
// Autopilot overrides via finalize() — this set has no effect there.
|
|
10
12
|
const RETRYABLE_PHASES = new Set([
|
|
11
13
|
'CONTEXT',
|
|
12
14
|
'EXPLORE',
|
|
@@ -30,7 +32,7 @@ function inferFailurePhase(flowReport) {
|
|
|
30
32
|
}
|
|
31
33
|
return 'VERIFY';
|
|
32
34
|
}
|
|
33
|
-
function extractErrorCode(error) {
|
|
35
|
+
export function extractErrorCode(error) {
|
|
34
36
|
if (typeof error === 'object' && error !== null) {
|
|
35
37
|
const err = error;
|
|
36
38
|
// Prioritize code (from SalmonError) for better specificity
|
|
@@ -57,7 +59,19 @@ function extractInputRequired(error) {
|
|
|
57
59
|
return undefined;
|
|
58
60
|
if (typeof value.prompt !== 'string' || typeof value.type !== 'string')
|
|
59
61
|
return undefined;
|
|
60
|
-
|
|
62
|
+
const result = { type: value.type, prompt: value.prompt };
|
|
63
|
+
if (value.reason === 'approval' ||
|
|
64
|
+
value.reason === 'clarification' ||
|
|
65
|
+
value.reason === 'reopen') {
|
|
66
|
+
result.reason = value.reason;
|
|
67
|
+
}
|
|
68
|
+
if (Array.isArray(value.questions)) {
|
|
69
|
+
result.questions = value.questions;
|
|
70
|
+
}
|
|
71
|
+
if (typeof value.responseFormat === 'string') {
|
|
72
|
+
result.responseFormat = value.responseFormat;
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
61
75
|
}
|
|
62
76
|
function extractInterrupt(error) {
|
|
63
77
|
if (!isRecord(error))
|
|
@@ -67,11 +81,23 @@ function extractInterrupt(error) {
|
|
|
67
81
|
return undefined;
|
|
68
82
|
if (typeof value.type !== 'string')
|
|
69
83
|
return undefined;
|
|
70
|
-
|
|
84
|
+
const result = {
|
|
85
|
+
type: value.type,
|
|
86
|
+
};
|
|
87
|
+
if (typeof value.reason === 'string')
|
|
88
|
+
result.reason = value.reason;
|
|
89
|
+
if (typeof value.prompt === 'string')
|
|
90
|
+
result.prompt = value.prompt;
|
|
91
|
+
if (isRecord(value.data))
|
|
92
|
+
result.data = value.data;
|
|
93
|
+
return result;
|
|
71
94
|
}
|
|
72
95
|
export function resolveAttemptFailure(params) {
|
|
73
96
|
const { flowReport, context, flowMode } = params;
|
|
74
97
|
const profile = resolveExecutionProfile(flowMode);
|
|
98
|
+
// Autopilot is one-shot: never retry at the transaction-runner level.
|
|
99
|
+
const finalize = (failure) => flowMode === 'autopilot' ? { ...failure, retryable: false } : failure;
|
|
100
|
+
const effectiveVerifyPolicy = params.verifyPolicy ?? profile.verifyPolicy;
|
|
75
101
|
const interrupt = extractInterrupt(flowReport.error);
|
|
76
102
|
const interruptCode = extractErrorCode(flowReport.error);
|
|
77
103
|
if (interruptCode === 'INTERRUPT_REQUIRED' && interrupt?.type === 'awaiting_input') {
|
|
@@ -93,7 +119,7 @@ export function resolveAttemptFailure(params) {
|
|
|
93
119
|
};
|
|
94
120
|
}
|
|
95
121
|
const autopilotCompletion = flowMode === 'autopilot' && context && 'completion' in context ? context.completion : undefined;
|
|
96
|
-
const verifyOk =
|
|
122
|
+
const verifyOk = effectiveVerifyPolicy === 'never' ? true : context?.verifyResult?.ok !== false;
|
|
97
123
|
const applyBackResult = context && 'applyBackResult' in context ? context.applyBackResult : undefined;
|
|
98
124
|
const applyBackFailed = profile.failurePolicy === 'rollback' &&
|
|
99
125
|
applyBackResult?.success === false &&
|
|
@@ -123,7 +149,7 @@ export function resolveAttemptFailure(params) {
|
|
|
123
149
|
return undefined;
|
|
124
150
|
}
|
|
125
151
|
const errorCode = extractErrorCode(flowReport.error) ?? extractErrorCodeFromTraces(flowReport);
|
|
126
|
-
if (
|
|
152
|
+
if (effectiveVerifyPolicy !== 'never' && context?.verifyResult?.ok === false) {
|
|
127
153
|
const verifyOutput = context.verifyResult.output || text.loop.loopExecutionFailed;
|
|
128
154
|
const errorType = classifyError(verifyOutput);
|
|
129
155
|
const fallbackReason = sanitizeReason(context.lastError || verifyOutput);
|
|
@@ -135,7 +161,7 @@ export function resolveAttemptFailure(params) {
|
|
|
135
161
|
environmentMode,
|
|
136
162
|
fallbackReason,
|
|
137
163
|
});
|
|
138
|
-
return {
|
|
164
|
+
return finalize({
|
|
139
165
|
reason: guidance.safeHint,
|
|
140
166
|
reasonCode: 'VERIFY_FAILED',
|
|
141
167
|
failurePhase: 'VERIFY',
|
|
@@ -144,7 +170,7 @@ export function resolveAttemptFailure(params) {
|
|
|
144
170
|
diagnosticCode: guidance.diagnosticCode,
|
|
145
171
|
safeHint: guidance.safeHint,
|
|
146
172
|
remediationSteps: guidance.remediationSteps,
|
|
147
|
-
};
|
|
173
|
+
});
|
|
148
174
|
}
|
|
149
175
|
if (flowMode === 'autopilot' && autopilotCompletion) {
|
|
150
176
|
if (autopilotCompletion.status === 'changed' ||
|
|
@@ -152,13 +178,11 @@ export function resolveAttemptFailure(params) {
|
|
|
152
178
|
if (flowReport.success && verifyOk)
|
|
153
179
|
return undefined;
|
|
154
180
|
}
|
|
155
|
-
const reasonCode = autopilotCompletion.status === '
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
: 'LOOP_FAILED';
|
|
161
|
-
const failurePhase = autopilotCompletion.status === 'verification_missing' ? 'VERIFY' : 'AUTOPILOT';
|
|
181
|
+
const reasonCode = autopilotCompletion.status === 'tool_failure' &&
|
|
182
|
+
isRecoverableToolInputErrorCode(autopilotCompletion.errorCode)
|
|
183
|
+
? 'TOOL_CORRECTION_REQUIRED'
|
|
184
|
+
: 'LOOP_FAILED';
|
|
185
|
+
const failurePhase = 'AUTOPILOT';
|
|
162
186
|
const fallbackReason = autopilotCompletion.reason ||
|
|
163
187
|
(autopilotCompletion.status === 'no_effect'
|
|
164
188
|
? 'Autopilot completed without changing files or producing an answer.'
|
|
@@ -170,7 +194,7 @@ export function resolveAttemptFailure(params) {
|
|
|
170
194
|
environmentMode,
|
|
171
195
|
fallbackReason,
|
|
172
196
|
});
|
|
173
|
-
return {
|
|
197
|
+
return finalize({
|
|
174
198
|
reason: guidance.safeHint,
|
|
175
199
|
reasonCode,
|
|
176
200
|
failurePhase,
|
|
@@ -179,7 +203,7 @@ export function resolveAttemptFailure(params) {
|
|
|
179
203
|
diagnosticCode: guidance.diagnosticCode,
|
|
180
204
|
safeHint: guidance.safeHint,
|
|
181
205
|
remediationSteps: guidance.remediationSteps,
|
|
182
|
-
};
|
|
206
|
+
});
|
|
183
207
|
}
|
|
184
208
|
if (errorCode === 'PREFLIGHT_NOT_GIT') {
|
|
185
209
|
const fallbackReason = sanitizeReason(flowReport.error);
|
|
@@ -272,7 +296,7 @@ export function resolveAttemptFailure(params) {
|
|
|
272
296
|
environmentMode,
|
|
273
297
|
fallbackReason,
|
|
274
298
|
});
|
|
275
|
-
return {
|
|
299
|
+
return finalize({
|
|
276
300
|
reason: guidance.safeHint,
|
|
277
301
|
reasonCode: 'TOOL_CORRECTION_REQUIRED',
|
|
278
302
|
failurePhase,
|
|
@@ -281,7 +305,7 @@ export function resolveAttemptFailure(params) {
|
|
|
281
305
|
diagnosticCode: guidance.diagnosticCode,
|
|
282
306
|
safeHint: guidance.safeHint,
|
|
283
307
|
remediationSteps: guidance.remediationSteps,
|
|
284
|
-
};
|
|
308
|
+
});
|
|
285
309
|
}
|
|
286
310
|
const guidance = buildFailureGuidance({
|
|
287
311
|
reasonCode: failurePhase === 'ROLLBACK' ? 'ROLLBACK_FAILED' : 'LOOP_FAILED',
|
|
@@ -304,7 +328,7 @@ export function resolveAttemptFailure(params) {
|
|
|
304
328
|
remediationSteps: guidance.remediationSteps,
|
|
305
329
|
};
|
|
306
330
|
}
|
|
307
|
-
return {
|
|
331
|
+
return finalize({
|
|
308
332
|
reason: guidance.safeHint,
|
|
309
333
|
reasonCode: 'LOOP_FAILED',
|
|
310
334
|
failurePhase,
|
|
@@ -313,6 +337,6 @@ export function resolveAttemptFailure(params) {
|
|
|
313
337
|
diagnosticCode: guidance.diagnosticCode,
|
|
314
338
|
safeHint: guidance.safeHint,
|
|
315
339
|
remediationSteps: guidance.remediationSteps,
|
|
316
|
-
};
|
|
340
|
+
});
|
|
317
341
|
}
|
|
318
342
|
//# sourceMappingURL=attempt-failure.js.map
|