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
|
@@ -2,6 +2,7 @@ import { recordAuditEvent, setAuditContext } from '../../../observability/audit-
|
|
|
2
2
|
import { getLogger } from '../../../observability/logger.js';
|
|
3
3
|
import { appendPlanNote } from '../../../plan/index.js';
|
|
4
4
|
import { EXECUTION_PHASES, } from '../../../types/runtime.js';
|
|
5
|
+
import { isRecord } from '../../../utils/serialize.js';
|
|
5
6
|
/**
|
|
6
7
|
* Typed Async Pipeline Container
|
|
7
8
|
*/
|
|
@@ -28,271 +29,179 @@ export class Pipeline {
|
|
|
28
29
|
* Add a step to the pipeline
|
|
29
30
|
*/
|
|
30
31
|
step(name, action) {
|
|
31
|
-
const nextPromise = this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const persistenceRoot = ctx?.workspace?.baseRepoPath || ctx?.workspace?.workPath;
|
|
41
|
-
const attempt = ctx?.attempt ?? 1;
|
|
42
|
-
const tryAppendPlanNote = async (note) => {
|
|
43
|
-
if (!planRuntime || !persistenceRoot)
|
|
44
|
-
return;
|
|
45
|
-
try {
|
|
46
|
-
await appendPlanNote({
|
|
47
|
-
persistenceRoot,
|
|
48
|
-
sessionId: planRuntime.sessionId,
|
|
49
|
-
note,
|
|
50
|
-
});
|
|
51
|
-
recordAuditEvent('plan.runtime.note.append', { note, ok: true }, { source: 'plan', severity: 'low', scope: 'session', phase: name });
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
catch (e) {
|
|
55
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
56
|
-
recordAuditEvent('plan.runtime.note.append.failed', { note, error: msg }, { source: 'plan', severity: 'low', scope: 'session', phase: name });
|
|
57
|
-
getLogger().debug(`[PlanRuntime] Failed to append note: ${msg}`);
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
};
|
|
61
|
-
try {
|
|
62
|
-
this.ctxRef.current = ctx;
|
|
63
|
-
const signal = ctx?.options?.signal;
|
|
64
|
-
const strategy = ctx?.workspace?.strategy ?? ctx?.options?.strategy;
|
|
65
|
-
if (signal?.aborted && strategy === 'worktree') {
|
|
66
|
-
throw new Error('Operation cancelled by user');
|
|
67
|
-
}
|
|
68
|
-
setAuditContext({ phase: name });
|
|
69
|
-
if (emit && isPhase(name)) {
|
|
70
|
-
emit({ type: 'phase.start', phase: name, timestamp: new Date() });
|
|
71
|
-
phaseStarted = true;
|
|
72
|
-
const ok = await tryAppendPlanNote(`Attempt ${attempt}: phase.start ${name}`);
|
|
73
|
-
if (planRuntime && ok !== undefined) {
|
|
74
|
-
emit({
|
|
75
|
-
type: 'plan.runtime.journal',
|
|
76
|
-
sessionId: planRuntime.sessionId,
|
|
77
|
-
phase: name,
|
|
78
|
-
kind: 'start',
|
|
79
|
-
attempt,
|
|
80
|
-
ok,
|
|
81
|
-
timestamp: new Date(),
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
result = await action(ctx);
|
|
86
|
-
this.ctxRef.current = result;
|
|
87
|
-
// APPLY_BACK failure is represented as a structured result (not an exception) to preserve context.
|
|
88
|
-
// The pipeline must still treat it as a phase failure for progress reporting and plan journaling.
|
|
89
|
-
if (name === 'APPLY_BACK' &&
|
|
90
|
-
result &&
|
|
91
|
-
typeof result === 'object' &&
|
|
92
|
-
result.applyBackResult &&
|
|
93
|
-
typeof result.applyBackResult === 'object') {
|
|
94
|
-
const applyBackResult = result.applyBackResult;
|
|
95
|
-
if (applyBackResult.success === false && !applyBackResult.skipped) {
|
|
96
|
-
errorStr = applyBackResult.safeMessage || applyBackResult.error || 'Apply-back failed';
|
|
97
|
-
errorMeta = {
|
|
32
|
+
const nextPromise = this.executeStep(name, action, async () => {
|
|
33
|
+
// No recovery for plain steps — check for APPLY_BACK structured failure
|
|
34
|
+
const result = this.ctxRef.current;
|
|
35
|
+
if (name === 'APPLY_BACK' && isRecord(result) && isRecord(result.applyBackResult)) {
|
|
36
|
+
const applyBackResult = result.applyBackResult;
|
|
37
|
+
if (applyBackResult.success === false && !applyBackResult.skipped) {
|
|
38
|
+
return {
|
|
39
|
+
errorStr: applyBackResult.safeMessage || applyBackResult.error || 'Apply-back failed',
|
|
40
|
+
errorMeta: {
|
|
98
41
|
name: 'ApplyBackFailure',
|
|
99
42
|
code: applyBackResult.errorCode || 'APPLY_BACK_FAILED',
|
|
100
|
-
}
|
|
101
|
-
}
|
|
43
|
+
},
|
|
44
|
+
};
|
|
102
45
|
}
|
|
103
|
-
return result;
|
|
104
46
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
47
|
+
return null;
|
|
48
|
+
});
|
|
49
|
+
return new Pipeline(nextPromise, this.startTime, name, this.traces, this.ctxRef);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Add a step with error recovery
|
|
53
|
+
*/
|
|
54
|
+
stepWithRecovery(name, action, recovery) {
|
|
55
|
+
const nextPromise = this.executeStep(name, action, async (ctx, errorStr) => {
|
|
56
|
+
// Only run recovery when the step actually failed
|
|
57
|
+
if (!errorStr)
|
|
58
|
+
return null;
|
|
59
|
+
const recStart = Date.now();
|
|
60
|
+
try {
|
|
61
|
+
await recovery(ctx);
|
|
62
|
+
this.traces.push({
|
|
63
|
+
name: `${name}:recovery`,
|
|
64
|
+
start: recStart,
|
|
65
|
+
end: Date.now(),
|
|
66
|
+
duration: Date.now() - recStart,
|
|
67
|
+
metadata: { success: true },
|
|
68
|
+
});
|
|
116
69
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
type: 'phase.end',
|
|
121
|
-
phase: name,
|
|
122
|
-
success: !errorStr,
|
|
123
|
-
timestamp: new Date(),
|
|
124
|
-
});
|
|
125
|
-
const ok = await tryAppendPlanNote(`Attempt ${attempt}: phase.end ${name} (success=${String(!errorStr)})`);
|
|
126
|
-
if (planRuntime && ok !== undefined) {
|
|
127
|
-
emit({
|
|
128
|
-
type: 'plan.runtime.journal',
|
|
129
|
-
sessionId: planRuntime.sessionId,
|
|
130
|
-
phase: name,
|
|
131
|
-
kind: 'end',
|
|
132
|
-
attempt,
|
|
133
|
-
ok,
|
|
134
|
-
timestamp: new Date(),
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
setAuditContext({ phase: undefined });
|
|
139
|
-
const end = Date.now();
|
|
70
|
+
catch (recError) {
|
|
71
|
+
const recEnd = Date.now();
|
|
72
|
+
const errorDetail = recError instanceof Error ? recError.message : String(recError);
|
|
140
73
|
this.traces.push({
|
|
141
|
-
name
|
|
142
|
-
start,
|
|
143
|
-
end,
|
|
144
|
-
duration:
|
|
145
|
-
error:
|
|
146
|
-
metadata:
|
|
74
|
+
name: `${name}:recovery`,
|
|
75
|
+
start: recStart,
|
|
76
|
+
end: recEnd,
|
|
77
|
+
duration: recEnd - recStart,
|
|
78
|
+
error: errorDetail,
|
|
79
|
+
metadata: { success: false, phase: 'RECOVERY_FAILURE' },
|
|
147
80
|
});
|
|
81
|
+
getLogger().audit('PIPELINE_RECOVERY_FAILED', { step: name, originalError: errorStr, recoveryError: errorDetail }, { source: 'system', severity: 'high', scope: 'session' });
|
|
148
82
|
}
|
|
83
|
+
return null; // Always re-throw original error
|
|
149
84
|
});
|
|
150
85
|
return new Pipeline(nextPromise, this.startTime, name, this.traces, this.ctxRef);
|
|
151
86
|
}
|
|
152
87
|
/**
|
|
153
|
-
*
|
|
88
|
+
* Core step execution shared by step() and stepWithRecovery().
|
|
89
|
+
* Handles: abort check, phase events, plan journaling, tracing.
|
|
90
|
+
* The onError callback runs recovery/post-processing; return non-null to inject error metadata.
|
|
154
91
|
*/
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
persistenceRoot,
|
|
174
|
-
sessionId: planRuntime.sessionId,
|
|
175
|
-
note,
|
|
176
|
-
});
|
|
177
|
-
recordAuditEvent('plan.runtime.note.append', { note, ok: true }, { source: 'plan', severity: 'low', scope: 'session', phase: name });
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
catch (e) {
|
|
181
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
182
|
-
recordAuditEvent('plan.runtime.note.append.failed', { note, error: msg }, { source: 'plan', severity: 'low', scope: 'session', phase: name });
|
|
183
|
-
getLogger().debug(`[PlanRuntime] Failed to append note: ${msg}`);
|
|
184
|
-
return false;
|
|
185
|
-
}
|
|
186
|
-
};
|
|
92
|
+
async executeStep(name, action, onError) {
|
|
93
|
+
const start = Date.now();
|
|
94
|
+
let phaseStarted = false;
|
|
95
|
+
let errorStr;
|
|
96
|
+
let errorMeta;
|
|
97
|
+
let result;
|
|
98
|
+
const ctx = await this.promise;
|
|
99
|
+
const emit = ctx.emit;
|
|
100
|
+
const isPhase = (value) => EXECUTION_PHASES.includes(value);
|
|
101
|
+
const ctxObj = isRecord(ctx) ? ctx : null;
|
|
102
|
+
const planRuntime = ctxObj?.planRuntime;
|
|
103
|
+
const workspace = isRecord(ctxObj?.workspace) ? ctxObj.workspace : null;
|
|
104
|
+
const persistenceRoot = (typeof workspace?.baseRepoPath === 'string' ? workspace.baseRepoPath : undefined) ||
|
|
105
|
+
(typeof workspace?.workPath === 'string' ? workspace.workPath : undefined);
|
|
106
|
+
const attempt = typeof ctxObj?.attempt === 'number' ? ctxObj.attempt : 1;
|
|
107
|
+
const tryAppendPlanNote = async (note) => {
|
|
108
|
+
if (!planRuntime || !persistenceRoot)
|
|
109
|
+
return;
|
|
187
110
|
try {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (signal?.aborted && strategy === 'worktree') {
|
|
192
|
-
abortedBeforeAction = true;
|
|
193
|
-
throw new Error('Operation cancelled by user');
|
|
194
|
-
}
|
|
195
|
-
setAuditContext({ phase: name });
|
|
196
|
-
if (emit && isPhase(name)) {
|
|
197
|
-
emit({ type: 'phase.start', phase: name, timestamp: new Date() });
|
|
198
|
-
phaseStarted = true;
|
|
199
|
-
const ok = await tryAppendPlanNote(`Attempt ${attempt}: phase.start ${name}`);
|
|
200
|
-
if (planRuntime && ok !== undefined) {
|
|
201
|
-
emit({
|
|
202
|
-
type: 'plan.runtime.journal',
|
|
203
|
-
sessionId: planRuntime.sessionId,
|
|
204
|
-
phase: name,
|
|
205
|
-
kind: 'start',
|
|
206
|
-
attempt,
|
|
207
|
-
ok,
|
|
208
|
-
timestamp: new Date(),
|
|
209
|
-
});
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
result = await action(ctx);
|
|
213
|
-
this.ctxRef.current = result;
|
|
214
|
-
return result;
|
|
111
|
+
await appendPlanNote({ persistenceRoot, sessionId: planRuntime.sessionId, note });
|
|
112
|
+
recordAuditEvent('plan.runtime.note.append', { note, ok: true }, { source: 'plan', severity: 'low', scope: 'session', phase: name });
|
|
113
|
+
return true;
|
|
215
114
|
}
|
|
216
|
-
catch (
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
115
|
+
catch (e) {
|
|
116
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
117
|
+
recordAuditEvent('plan.runtime.note.append.failed', { note, error: msg }, { source: 'plan', severity: 'low', scope: 'session', phase: name });
|
|
118
|
+
getLogger().debug(`[PlanRuntime] Failed to append note: ${msg}`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
try {
|
|
123
|
+
this.ctxRef.current = ctx;
|
|
124
|
+
const options = isRecord(ctxObj?.options) ? ctxObj.options : null;
|
|
125
|
+
const signal = options?.signal;
|
|
126
|
+
const strategy = (workspace?.strategy ?? options?.strategy);
|
|
127
|
+
if (signal?.aborted && strategy === 'worktree') {
|
|
128
|
+
throw new Error('Operation cancelled by user');
|
|
129
|
+
}
|
|
130
|
+
setAuditContext({ phase: name });
|
|
131
|
+
if (emit && isPhase(name)) {
|
|
132
|
+
emit({ type: 'phase.start', phase: name, timestamp: new Date() });
|
|
133
|
+
phaseStarted = true;
|
|
134
|
+
const ok = await tryAppendPlanNote(`Attempt ${attempt}: phase.start ${name}`);
|
|
135
|
+
if (planRuntime && ok !== undefined) {
|
|
136
|
+
emit({
|
|
137
|
+
type: 'plan.runtime.journal',
|
|
138
|
+
sessionId: planRuntime.sessionId,
|
|
139
|
+
phase: name,
|
|
140
|
+
kind: 'start',
|
|
141
|
+
attempt,
|
|
142
|
+
ok,
|
|
143
|
+
timestamp: new Date(),
|
|
239
144
|
});
|
|
240
145
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
146
|
+
}
|
|
147
|
+
result = await action(ctx);
|
|
148
|
+
this.ctxRef.current = result;
|
|
149
|
+
// Check for structured failures (e.g., APPLY_BACK)
|
|
150
|
+
const postResult = await onError(ctx, '');
|
|
151
|
+
if (postResult) {
|
|
152
|
+
errorStr = postResult.errorStr;
|
|
153
|
+
errorMeta = postResult.errorMeta;
|
|
154
|
+
}
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
errorStr = error instanceof Error ? error.message : String(error);
|
|
159
|
+
errorMeta =
|
|
160
|
+
typeof error === 'object' && error !== null
|
|
161
|
+
? {
|
|
162
|
+
name: error.name,
|
|
163
|
+
code: error.code,
|
|
164
|
+
llmCode: error.llmCode,
|
|
165
|
+
}
|
|
166
|
+
: undefined;
|
|
167
|
+
// Skip recovery on abort - the operation was cancelled, not failed
|
|
168
|
+
if (errorStr !== 'Operation cancelled by user') {
|
|
169
|
+
// Run recovery/post-processing
|
|
170
|
+
const postResult = await onError(ctx, errorStr);
|
|
171
|
+
if (postResult?.errorStr) {
|
|
172
|
+
errorStr = postResult.errorStr;
|
|
173
|
+
errorMeta = postResult.errorMeta;
|
|
259
174
|
}
|
|
260
|
-
throw error; // Propagate original error
|
|
261
175
|
}
|
|
262
|
-
|
|
263
|
-
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
if (emit && isPhase(name) && phaseStarted) {
|
|
180
|
+
emit({ type: 'phase.end', phase: name, success: !errorStr, timestamp: new Date() });
|
|
181
|
+
const ok = await tryAppendPlanNote(`Attempt ${attempt}: phase.end ${name} (success=${String(!errorStr)})`);
|
|
182
|
+
if (planRuntime && ok !== undefined) {
|
|
264
183
|
emit({
|
|
265
|
-
type: '
|
|
184
|
+
type: 'plan.runtime.journal',
|
|
185
|
+
sessionId: planRuntime.sessionId,
|
|
266
186
|
phase: name,
|
|
267
|
-
|
|
187
|
+
kind: 'end',
|
|
188
|
+
attempt,
|
|
189
|
+
ok,
|
|
268
190
|
timestamp: new Date(),
|
|
269
191
|
});
|
|
270
|
-
const ok = await tryAppendPlanNote(`Attempt ${attempt}: phase.end ${name} (success=${String(!errorStr)})`);
|
|
271
|
-
if (planRuntime && ok !== undefined) {
|
|
272
|
-
emit({
|
|
273
|
-
type: 'plan.runtime.journal',
|
|
274
|
-
sessionId: planRuntime.sessionId,
|
|
275
|
-
phase: name,
|
|
276
|
-
kind: 'end',
|
|
277
|
-
attempt,
|
|
278
|
-
ok,
|
|
279
|
-
timestamp: new Date(),
|
|
280
|
-
});
|
|
281
|
-
}
|
|
282
192
|
}
|
|
283
|
-
setAuditContext({ phase: undefined });
|
|
284
|
-
const end = Date.now();
|
|
285
|
-
this.traces.push({
|
|
286
|
-
name,
|
|
287
|
-
start,
|
|
288
|
-
end,
|
|
289
|
-
duration: end - start,
|
|
290
|
-
error: errorStr,
|
|
291
|
-
metadata: errorMeta,
|
|
292
|
-
});
|
|
293
193
|
}
|
|
294
|
-
|
|
295
|
-
|
|
194
|
+
setAuditContext({ phase: undefined });
|
|
195
|
+
const end = Date.now();
|
|
196
|
+
this.traces.push({
|
|
197
|
+
name,
|
|
198
|
+
start,
|
|
199
|
+
end,
|
|
200
|
+
duration: end - start,
|
|
201
|
+
error: errorStr,
|
|
202
|
+
metadata: errorMeta,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
296
205
|
}
|
|
297
206
|
/**
|
|
298
207
|
* Execute the pipeline and get the final result
|
|
@@ -5,7 +5,10 @@ import { mapErrorForDisplay } from '../../../observability/error-mapping.js';
|
|
|
5
5
|
import { resolveExecutionProfile } from '../../../runtime/execution-profile.js';
|
|
6
6
|
import { isRecoverableToolInputErrorCode } from '../../../tools/recoverable-tool-errors.js';
|
|
7
7
|
import { EXECUTION_PHASES } from '../../../types/runtime.js';
|
|
8
|
+
import { isRecord } from '../../../utils/serialize.js';
|
|
8
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.
|
|
9
12
|
const RETRYABLE_PHASES = new Set([
|
|
10
13
|
'CONTEXT',
|
|
11
14
|
'EXPLORE',
|
|
@@ -29,7 +32,7 @@ function inferFailurePhase(flowReport) {
|
|
|
29
32
|
}
|
|
30
33
|
return 'VERIFY';
|
|
31
34
|
}
|
|
32
|
-
function extractErrorCode(error) {
|
|
35
|
+
export function extractErrorCode(error) {
|
|
33
36
|
if (typeof error === 'object' && error !== null) {
|
|
34
37
|
const err = error;
|
|
35
38
|
// Prioritize code (from SalmonError) for better specificity
|
|
@@ -49,28 +52,52 @@ function sanitizeReason(value) {
|
|
|
49
52
|
return mapErrorForDisplay({ message: sanitized }).message;
|
|
50
53
|
}
|
|
51
54
|
function extractInputRequired(error) {
|
|
52
|
-
if (!error
|
|
55
|
+
if (!isRecord(error))
|
|
53
56
|
return undefined;
|
|
54
57
|
const value = 'inputRequired' in error ? error.inputRequired : error;
|
|
55
|
-
if (!value
|
|
58
|
+
if (!isRecord(value))
|
|
56
59
|
return undefined;
|
|
57
60
|
if (typeof value.prompt !== 'string' || typeof value.type !== 'string')
|
|
58
61
|
return undefined;
|
|
59
|
-
|
|
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;
|
|
60
75
|
}
|
|
61
76
|
function extractInterrupt(error) {
|
|
62
|
-
if (!error
|
|
77
|
+
if (!isRecord(error))
|
|
63
78
|
return undefined;
|
|
64
79
|
const value = error.interrupt;
|
|
65
|
-
if (!value
|
|
80
|
+
if (!isRecord(value))
|
|
66
81
|
return undefined;
|
|
67
82
|
if (typeof value.type !== 'string')
|
|
68
83
|
return undefined;
|
|
69
|
-
|
|
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;
|
|
70
94
|
}
|
|
71
95
|
export function resolveAttemptFailure(params) {
|
|
72
96
|
const { flowReport, context, flowMode } = params;
|
|
73
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;
|
|
74
101
|
const interrupt = extractInterrupt(flowReport.error);
|
|
75
102
|
const interruptCode = extractErrorCode(flowReport.error);
|
|
76
103
|
if (interruptCode === 'INTERRUPT_REQUIRED' && interrupt?.type === 'awaiting_input') {
|
|
@@ -92,7 +119,7 @@ export function resolveAttemptFailure(params) {
|
|
|
92
119
|
};
|
|
93
120
|
}
|
|
94
121
|
const autopilotCompletion = flowMode === 'autopilot' && context && 'completion' in context ? context.completion : undefined;
|
|
95
|
-
const verifyOk =
|
|
122
|
+
const verifyOk = effectiveVerifyPolicy === 'never' ? true : context?.verifyResult?.ok !== false;
|
|
96
123
|
const applyBackResult = context && 'applyBackResult' in context ? context.applyBackResult : undefined;
|
|
97
124
|
const applyBackFailed = profile.failurePolicy === 'rollback' &&
|
|
98
125
|
applyBackResult?.success === false &&
|
|
@@ -122,7 +149,7 @@ export function resolveAttemptFailure(params) {
|
|
|
122
149
|
return undefined;
|
|
123
150
|
}
|
|
124
151
|
const errorCode = extractErrorCode(flowReport.error) ?? extractErrorCodeFromTraces(flowReport);
|
|
125
|
-
if (
|
|
152
|
+
if (effectiveVerifyPolicy !== 'never' && context?.verifyResult?.ok === false) {
|
|
126
153
|
const verifyOutput = context.verifyResult.output || text.loop.loopExecutionFailed;
|
|
127
154
|
const errorType = classifyError(verifyOutput);
|
|
128
155
|
const fallbackReason = sanitizeReason(context.lastError || verifyOutput);
|
|
@@ -134,7 +161,7 @@ export function resolveAttemptFailure(params) {
|
|
|
134
161
|
environmentMode,
|
|
135
162
|
fallbackReason,
|
|
136
163
|
});
|
|
137
|
-
return {
|
|
164
|
+
return finalize({
|
|
138
165
|
reason: guidance.safeHint,
|
|
139
166
|
reasonCode: 'VERIFY_FAILED',
|
|
140
167
|
failurePhase: 'VERIFY',
|
|
@@ -143,7 +170,7 @@ export function resolveAttemptFailure(params) {
|
|
|
143
170
|
diagnosticCode: guidance.diagnosticCode,
|
|
144
171
|
safeHint: guidance.safeHint,
|
|
145
172
|
remediationSteps: guidance.remediationSteps,
|
|
146
|
-
};
|
|
173
|
+
});
|
|
147
174
|
}
|
|
148
175
|
if (flowMode === 'autopilot' && autopilotCompletion) {
|
|
149
176
|
if (autopilotCompletion.status === 'changed' ||
|
|
@@ -151,13 +178,11 @@ export function resolveAttemptFailure(params) {
|
|
|
151
178
|
if (flowReport.success && verifyOk)
|
|
152
179
|
return undefined;
|
|
153
180
|
}
|
|
154
|
-
const reasonCode = autopilotCompletion.status === '
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
: 'LOOP_FAILED';
|
|
160
|
-
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';
|
|
161
186
|
const fallbackReason = autopilotCompletion.reason ||
|
|
162
187
|
(autopilotCompletion.status === 'no_effect'
|
|
163
188
|
? 'Autopilot completed without changing files or producing an answer.'
|
|
@@ -169,7 +194,7 @@ export function resolveAttemptFailure(params) {
|
|
|
169
194
|
environmentMode,
|
|
170
195
|
fallbackReason,
|
|
171
196
|
});
|
|
172
|
-
return {
|
|
197
|
+
return finalize({
|
|
173
198
|
reason: guidance.safeHint,
|
|
174
199
|
reasonCode,
|
|
175
200
|
failurePhase,
|
|
@@ -178,7 +203,7 @@ export function resolveAttemptFailure(params) {
|
|
|
178
203
|
diagnosticCode: guidance.diagnosticCode,
|
|
179
204
|
safeHint: guidance.safeHint,
|
|
180
205
|
remediationSteps: guidance.remediationSteps,
|
|
181
|
-
};
|
|
206
|
+
});
|
|
182
207
|
}
|
|
183
208
|
if (errorCode === 'PREFLIGHT_NOT_GIT') {
|
|
184
209
|
const fallbackReason = sanitizeReason(flowReport.error);
|
|
@@ -271,7 +296,7 @@ export function resolveAttemptFailure(params) {
|
|
|
271
296
|
environmentMode,
|
|
272
297
|
fallbackReason,
|
|
273
298
|
});
|
|
274
|
-
return {
|
|
299
|
+
return finalize({
|
|
275
300
|
reason: guidance.safeHint,
|
|
276
301
|
reasonCode: 'TOOL_CORRECTION_REQUIRED',
|
|
277
302
|
failurePhase,
|
|
@@ -280,7 +305,7 @@ export function resolveAttemptFailure(params) {
|
|
|
280
305
|
diagnosticCode: guidance.diagnosticCode,
|
|
281
306
|
safeHint: guidance.safeHint,
|
|
282
307
|
remediationSteps: guidance.remediationSteps,
|
|
283
|
-
};
|
|
308
|
+
});
|
|
284
309
|
}
|
|
285
310
|
const guidance = buildFailureGuidance({
|
|
286
311
|
reasonCode: failurePhase === 'ROLLBACK' ? 'ROLLBACK_FAILED' : 'LOOP_FAILED',
|
|
@@ -303,7 +328,7 @@ export function resolveAttemptFailure(params) {
|
|
|
303
328
|
remediationSteps: guidance.remediationSteps,
|
|
304
329
|
};
|
|
305
330
|
}
|
|
306
|
-
return {
|
|
331
|
+
return finalize({
|
|
307
332
|
reason: guidance.safeHint,
|
|
308
333
|
reasonCode: 'LOOP_FAILED',
|
|
309
334
|
failurePhase,
|
|
@@ -312,6 +337,6 @@ export function resolveAttemptFailure(params) {
|
|
|
312
337
|
diagnosticCode: guidance.diagnosticCode,
|
|
313
338
|
safeHint: guidance.safeHint,
|
|
314
339
|
remediationSteps: guidance.remediationSteps,
|
|
315
|
-
};
|
|
340
|
+
});
|
|
316
341
|
}
|
|
317
342
|
//# sourceMappingURL=attempt-failure.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isRecord } from '../../../utils/serialize.js';
|
|
1
2
|
export function buildAuthorizationSummary(logs) {
|
|
2
3
|
if (!logs || logs.length === 0)
|
|
3
4
|
return null;
|
|
@@ -11,7 +12,7 @@ export function buildAuthorizationSummary(logs) {
|
|
|
11
12
|
};
|
|
12
13
|
let hasEntries = false;
|
|
13
14
|
for (const entry of logs) {
|
|
14
|
-
if (!entry || entry.eventType !== 'authorization')
|
|
15
|
+
if (!isRecord(entry) || entry.eventType !== 'authorization')
|
|
15
16
|
continue;
|
|
16
17
|
const source = entry.authSource;
|
|
17
18
|
if (source === 'auto') {
|