@mcoda/mswarm 0.1.57 → 0.1.60
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/README.md +19 -0
- package/dist/codali-executor.d.ts +266 -0
- package/dist/codali-executor.d.ts.map +1 -0
- package/dist/codali-executor.js +227 -0
- package/dist/codali-executor.js.map +1 -0
- package/dist/runtime.d.ts +36 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +219 -30
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +54 -0
- package/dist/server.js.map +1 -1
- package/dist/vendor/codali/agents/AgentProtocol.d.ts +287 -0
- package/dist/vendor/codali/agents/AgentProtocol.d.ts.map +1 -0
- package/dist/vendor/codali/agents/AgentProtocol.js +365 -0
- package/dist/vendor/codali/agents/AgentResolver.d.ts +23 -0
- package/dist/vendor/codali/agents/AgentResolver.d.ts.map +1 -0
- package/dist/vendor/codali/agents/AgentResolver.js +77 -0
- package/dist/vendor/codali/agents/PhaseAgentSelector.d.ts +23 -0
- package/dist/vendor/codali/agents/PhaseAgentSelector.d.ts.map +1 -0
- package/dist/vendor/codali/agents/PhaseAgentSelector.js +287 -0
- package/dist/vendor/codali/cli/EvalCommand.d.ts +37 -0
- package/dist/vendor/codali/cli/EvalCommand.d.ts.map +1 -0
- package/dist/vendor/codali/cli/EvalCommand.js +333 -0
- package/dist/vendor/codali/cli/FeedbackCommand.d.ts +22 -0
- package/dist/vendor/codali/cli/FeedbackCommand.d.ts.map +1 -0
- package/dist/vendor/codali/cli/FeedbackCommand.js +163 -0
- package/dist/vendor/codali/cli/RunCommand.d.ts +78 -0
- package/dist/vendor/codali/cli/RunCommand.d.ts.map +1 -0
- package/dist/vendor/codali/cli/RunCommand.js +2261 -0
- package/dist/vendor/codali/cli.d.ts +3 -0
- package/dist/vendor/codali/cli.d.ts.map +1 -0
- package/dist/vendor/codali/cli.js +109 -0
- package/dist/vendor/codali/cognitive/ArchitectPlanner.d.ts +107 -0
- package/dist/vendor/codali/cognitive/ArchitectPlanner.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ArchitectPlanner.js +1726 -0
- package/dist/vendor/codali/cognitive/BuilderOutputParser.d.ts +25 -0
- package/dist/vendor/codali/cognitive/BuilderOutputParser.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/BuilderOutputParser.js +164 -0
- package/dist/vendor/codali/cognitive/BuilderRunner.d.ts +76 -0
- package/dist/vendor/codali/cognitive/BuilderRunner.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/BuilderRunner.js +1159 -0
- package/dist/vendor/codali/cognitive/ContextAssembler.d.ts +91 -0
- package/dist/vendor/codali/cognitive/ContextAssembler.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextAssembler.js +4547 -0
- package/dist/vendor/codali/cognitive/ContextBudget.d.ts +19 -0
- package/dist/vendor/codali/cognitive/ContextBudget.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextBudget.js +35 -0
- package/dist/vendor/codali/cognitive/ContextFileLoader.d.ts +30 -0
- package/dist/vendor/codali/cognitive/ContextFileLoader.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextFileLoader.js +307 -0
- package/dist/vendor/codali/cognitive/ContextManager.d.ts +47 -0
- package/dist/vendor/codali/cognitive/ContextManager.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextManager.js +272 -0
- package/dist/vendor/codali/cognitive/ContextRedactor.d.ts +18 -0
- package/dist/vendor/codali/cognitive/ContextRedactor.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextRedactor.js +53 -0
- package/dist/vendor/codali/cognitive/ContextSelector.d.ts +22 -0
- package/dist/vendor/codali/cognitive/ContextSelector.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextSelector.js +431 -0
- package/dist/vendor/codali/cognitive/ContextSerializer.d.ts +8 -0
- package/dist/vendor/codali/cognitive/ContextSerializer.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextSerializer.js +882 -0
- package/dist/vendor/codali/cognitive/ContextStore.d.ts +27 -0
- package/dist/vendor/codali/cognitive/ContextStore.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextStore.js +79 -0
- package/dist/vendor/codali/cognitive/ContextSummarizer.d.ts +16 -0
- package/dist/vendor/codali/cognitive/ContextSummarizer.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ContextSummarizer.js +45 -0
- package/dist/vendor/codali/cognitive/CostEstimator.d.ts +31 -0
- package/dist/vendor/codali/cognitive/CostEstimator.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/CostEstimator.js +66 -0
- package/dist/vendor/codali/cognitive/CriticEvaluator.d.ts +32 -0
- package/dist/vendor/codali/cognitive/CriticEvaluator.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/CriticEvaluator.js +297 -0
- package/dist/vendor/codali/cognitive/EvidenceGate.d.ts +9 -0
- package/dist/vendor/codali/cognitive/EvidenceGate.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/EvidenceGate.js +75 -0
- package/dist/vendor/codali/cognitive/GoldenExampleIndexer.d.ts +12 -0
- package/dist/vendor/codali/cognitive/GoldenExampleIndexer.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/GoldenExampleIndexer.js +34 -0
- package/dist/vendor/codali/cognitive/GoldenSetStore.d.ts +33 -0
- package/dist/vendor/codali/cognitive/GoldenSetStore.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/GoldenSetStore.js +159 -0
- package/dist/vendor/codali/cognitive/IntentSignals.d.ts +7 -0
- package/dist/vendor/codali/cognitive/IntentSignals.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/IntentSignals.js +285 -0
- package/dist/vendor/codali/cognitive/LearningGovernance.d.ts +100 -0
- package/dist/vendor/codali/cognitive/LearningGovernance.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/LearningGovernance.js +276 -0
- package/dist/vendor/codali/cognitive/MemoryWriteback.d.ts +64 -0
- package/dist/vendor/codali/cognitive/MemoryWriteback.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/MemoryWriteback.js +287 -0
- package/dist/vendor/codali/cognitive/PatchApplier.d.ts +49 -0
- package/dist/vendor/codali/cognitive/PatchApplier.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/PatchApplier.js +199 -0
- package/dist/vendor/codali/cognitive/PatchInterpreter.d.ts +35 -0
- package/dist/vendor/codali/cognitive/PatchInterpreter.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/PatchInterpreter.js +100 -0
- package/dist/vendor/codali/cognitive/PatchOutputNormalizer.d.ts +7 -0
- package/dist/vendor/codali/cognitive/PatchOutputNormalizer.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/PatchOutputNormalizer.js +59 -0
- package/dist/vendor/codali/cognitive/PostMortemAnalyzer.d.ts +17 -0
- package/dist/vendor/codali/cognitive/PostMortemAnalyzer.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/PostMortemAnalyzer.js +131 -0
- package/dist/vendor/codali/cognitive/PreferenceExtraction.d.ts +3 -0
- package/dist/vendor/codali/cognitive/PreferenceExtraction.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/PreferenceExtraction.js +85 -0
- package/dist/vendor/codali/cognitive/Prompts.d.ts +15 -0
- package/dist/vendor/codali/cognitive/Prompts.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/Prompts.js +326 -0
- package/dist/vendor/codali/cognitive/ProviderRouting.d.ts +16 -0
- package/dist/vendor/codali/cognitive/ProviderRouting.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ProviderRouting.js +24 -0
- package/dist/vendor/codali/cognitive/QueryExtraction.d.ts +12 -0
- package/dist/vendor/codali/cognitive/QueryExtraction.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/QueryExtraction.js +262 -0
- package/dist/vendor/codali/cognitive/RunHistoryIndexer.d.ts +13 -0
- package/dist/vendor/codali/cognitive/RunHistoryIndexer.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/RunHistoryIndexer.js +125 -0
- package/dist/vendor/codali/cognitive/SmartPipeline.d.ts +92 -0
- package/dist/vendor/codali/cognitive/SmartPipeline.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/SmartPipeline.js +4804 -0
- package/dist/vendor/codali/cognitive/Types.d.ts +474 -0
- package/dist/vendor/codali/cognitive/Types.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/Types.js +7 -0
- package/dist/vendor/codali/cognitive/ValidationRunner.d.ts +57 -0
- package/dist/vendor/codali/cognitive/ValidationRunner.d.ts.map +1 -0
- package/dist/vendor/codali/cognitive/ValidationRunner.js +515 -0
- package/dist/vendor/codali/config/Config.d.ts +249 -0
- package/dist/vendor/codali/config/Config.d.ts.map +1 -0
- package/dist/vendor/codali/config/Config.js +200 -0
- package/dist/vendor/codali/config/ConfigLoader.d.ts +56 -0
- package/dist/vendor/codali/config/ConfigLoader.d.ts.map +1 -0
- package/dist/vendor/codali/config/ConfigLoader.js +1246 -0
- package/dist/vendor/codali/docdex/DocdexClient.d.ts +113 -0
- package/dist/vendor/codali/docdex/DocdexClient.d.ts.map +1 -0
- package/dist/vendor/codali/docdex/DocdexClient.js +524 -0
- package/dist/vendor/codali/eval/EvalRunner.d.ts +35 -0
- package/dist/vendor/codali/eval/EvalRunner.d.ts.map +1 -0
- package/dist/vendor/codali/eval/EvalRunner.js +38 -0
- package/dist/vendor/codali/eval/EvalTaskExecutor.d.ts +81 -0
- package/dist/vendor/codali/eval/EvalTaskExecutor.d.ts.map +1 -0
- package/dist/vendor/codali/eval/EvalTaskExecutor.js +371 -0
- package/dist/vendor/codali/eval/GateEvaluator.d.ts +31 -0
- package/dist/vendor/codali/eval/GateEvaluator.d.ts.map +1 -0
- package/dist/vendor/codali/eval/GateEvaluator.js +134 -0
- package/dist/vendor/codali/eval/MetricTypes.d.ts +28 -0
- package/dist/vendor/codali/eval/MetricTypes.d.ts.map +1 -0
- package/dist/vendor/codali/eval/MetricTypes.js +1 -0
- package/dist/vendor/codali/eval/MetricsAggregator.d.ts +4 -0
- package/dist/vendor/codali/eval/MetricsAggregator.d.ts.map +1 -0
- package/dist/vendor/codali/eval/MetricsAggregator.js +97 -0
- package/dist/vendor/codali/eval/RegressionComparator.d.ts +29 -0
- package/dist/vendor/codali/eval/RegressionComparator.d.ts.map +1 -0
- package/dist/vendor/codali/eval/RegressionComparator.js +155 -0
- package/dist/vendor/codali/eval/ReportInputAdapter.d.ts +52 -0
- package/dist/vendor/codali/eval/ReportInputAdapter.d.ts.map +1 -0
- package/dist/vendor/codali/eval/ReportInputAdapter.js +229 -0
- package/dist/vendor/codali/eval/ReportSerializer.d.ts +32 -0
- package/dist/vendor/codali/eval/ReportSerializer.d.ts.map +1 -0
- package/dist/vendor/codali/eval/ReportSerializer.js +33 -0
- package/dist/vendor/codali/eval/ReportStore.d.ts +18 -0
- package/dist/vendor/codali/eval/ReportStore.d.ts.map +1 -0
- package/dist/vendor/codali/eval/ReportStore.js +96 -0
- package/dist/vendor/codali/eval/SuiteLoader.d.ts +12 -0
- package/dist/vendor/codali/eval/SuiteLoader.d.ts.map +1 -0
- package/dist/vendor/codali/eval/SuiteLoader.js +51 -0
- package/dist/vendor/codali/eval/SuiteSchema.d.ts +56 -0
- package/dist/vendor/codali/eval/SuiteSchema.d.ts.map +1 -0
- package/dist/vendor/codali/eval/SuiteSchema.js +357 -0
- package/dist/vendor/codali/index.d.ts +11 -0
- package/dist/vendor/codali/index.d.ts.map +1 -0
- package/dist/vendor/codali/index.js +5 -0
- package/dist/vendor/codali/providers/CodexCliProvider.d.ts +8 -0
- package/dist/vendor/codali/providers/CodexCliProvider.d.ts.map +1 -0
- package/dist/vendor/codali/providers/CodexCliProvider.js +282 -0
- package/dist/vendor/codali/providers/OllamaRemoteProvider.d.ts +8 -0
- package/dist/vendor/codali/providers/OllamaRemoteProvider.d.ts.map +1 -0
- package/dist/vendor/codali/providers/OllamaRemoteProvider.js +300 -0
- package/dist/vendor/codali/providers/OpenAiCompatibleProvider.d.ts +8 -0
- package/dist/vendor/codali/providers/OpenAiCompatibleProvider.d.ts.map +1 -0
- package/dist/vendor/codali/providers/OpenAiCompatibleProvider.js +192 -0
- package/dist/vendor/codali/providers/ProviderRegistry.d.ts +12 -0
- package/dist/vendor/codali/providers/ProviderRegistry.d.ts.map +1 -0
- package/dist/vendor/codali/providers/ProviderRegistry.js +28 -0
- package/dist/vendor/codali/providers/ProviderTypes.d.ts +81 -0
- package/dist/vendor/codali/providers/ProviderTypes.d.ts.map +1 -0
- package/dist/vendor/codali/providers/ProviderTypes.js +1 -0
- package/dist/vendor/codali/runtime/CodaliRuntime.d.ts +183 -0
- package/dist/vendor/codali/runtime/CodaliRuntime.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/CodaliRuntime.js +1363 -0
- package/dist/vendor/codali/runtime/DeepInvestigationErrors.d.ts +39 -0
- package/dist/vendor/codali/runtime/DeepInvestigationErrors.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/DeepInvestigationErrors.js +57 -0
- package/dist/vendor/codali/runtime/RunContext.d.ts +27 -0
- package/dist/vendor/codali/runtime/RunContext.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/RunContext.js +51 -0
- package/dist/vendor/codali/runtime/RunLogQuery.d.ts +48 -0
- package/dist/vendor/codali/runtime/RunLogQuery.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/RunLogQuery.js +36 -0
- package/dist/vendor/codali/runtime/RunLogReader.d.ts +19 -0
- package/dist/vendor/codali/runtime/RunLogReader.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/RunLogReader.js +361 -0
- package/dist/vendor/codali/runtime/RunLogger.d.ts +71 -0
- package/dist/vendor/codali/runtime/RunLogger.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/RunLogger.js +100 -0
- package/dist/vendor/codali/runtime/RunTelemetryTypes.d.ts +117 -0
- package/dist/vendor/codali/runtime/RunTelemetryTypes.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/RunTelemetryTypes.js +299 -0
- package/dist/vendor/codali/runtime/Runner.d.ts +66 -0
- package/dist/vendor/codali/runtime/Runner.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/Runner.js +215 -0
- package/dist/vendor/codali/runtime/StoragePaths.d.ts +3 -0
- package/dist/vendor/codali/runtime/StoragePaths.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/StoragePaths.js +19 -0
- package/dist/vendor/codali/runtime/WorkspaceLock.d.ts +30 -0
- package/dist/vendor/codali/runtime/WorkspaceLock.d.ts.map +1 -0
- package/dist/vendor/codali/runtime/WorkspaceLock.js +141 -0
- package/dist/vendor/codali/session/InstructionLoader.d.ts +14 -0
- package/dist/vendor/codali/session/InstructionLoader.d.ts.map +1 -0
- package/dist/vendor/codali/session/InstructionLoader.js +107 -0
- package/dist/vendor/codali/session/SessionStore.d.ts +81 -0
- package/dist/vendor/codali/session/SessionStore.d.ts.map +1 -0
- package/dist/vendor/codali/session/SessionStore.js +244 -0
- package/dist/vendor/codali/subagents/SubagentOrchestrator.d.ts +68 -0
- package/dist/vendor/codali/subagents/SubagentOrchestrator.d.ts.map +1 -0
- package/dist/vendor/codali/subagents/SubagentOrchestrator.js +150 -0
- package/dist/vendor/codali/tools/ToolRegistry.d.ts +9 -0
- package/dist/vendor/codali/tools/ToolRegistry.d.ts.map +1 -0
- package/dist/vendor/codali/tools/ToolRegistry.js +263 -0
- package/dist/vendor/codali/tools/ToolTypes.d.ts +66 -0
- package/dist/vendor/codali/tools/ToolTypes.d.ts.map +1 -0
- package/dist/vendor/codali/tools/ToolTypes.js +32 -0
- package/dist/vendor/codali/tools/diff/DiffTool.d.ts +3 -0
- package/dist/vendor/codali/tools/diff/DiffTool.d.ts.map +1 -0
- package/dist/vendor/codali/tools/diff/DiffTool.js +34 -0
- package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts +4 -0
- package/dist/vendor/codali/tools/docdex/DocdexTools.d.ts.map +1 -0
- package/dist/vendor/codali/tools/docdex/DocdexTools.js +453 -0
- package/dist/vendor/codali/tools/filesystem/FileTools.d.ts +3 -0
- package/dist/vendor/codali/tools/filesystem/FileTools.d.ts.map +1 -0
- package/dist/vendor/codali/tools/filesystem/FileTools.js +141 -0
- package/dist/vendor/codali/tools/search/SearchTool.d.ts +3 -0
- package/dist/vendor/codali/tools/search/SearchTool.d.ts.map +1 -0
- package/dist/vendor/codali/tools/search/SearchTool.js +46 -0
- package/dist/vendor/codali/tools/shell/ShellTool.d.ts +3 -0
- package/dist/vendor/codali/tools/shell/ShellTool.d.ts.map +1 -0
- package/dist/vendor/codali/tools/shell/ShellTool.js +104 -0
- package/package.json +5 -3
|
@@ -0,0 +1,1159 @@
|
|
|
1
|
+
import { Runner } from "../runtime/Runner.js";
|
|
2
|
+
import { buildBuilderPrompt, BUILDER_PATCH_GBNF_FILE_WRITES, BUILDER_PATCH_GBNF_SEARCH_REPLACE, } from "./Prompts.js";
|
|
3
|
+
import { serializeContext } from "./ContextSerializer.js";
|
|
4
|
+
import { parsePatchOutput, FILES_ARRAY_EMPTY_ERROR, FILES_ARRAY_MISSING_ERROR, FILES_ARRAY_TYPE_ERROR, PATCHES_ARRAY_EMPTY_ERROR, PATCHES_ARRAY_MISSING_ERROR, PATCHES_ARRAY_TYPE_ERROR, } from "./BuilderOutputParser.js";
|
|
5
|
+
import { PatchPolicyError } from "./PatchApplier.js";
|
|
6
|
+
export class PatchApplyError extends Error {
|
|
7
|
+
constructor(details) {
|
|
8
|
+
super(`Patch apply failed (${details.source}): ${details.error}`);
|
|
9
|
+
this.details = details;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const uniqueStrings = (values) => Array.from(new Set(values));
|
|
13
|
+
const PROSE_PATCH_INTENT_PATTERN = /\b(add|update|modify|replace|insert|create|remove|delete|change|refactor|rename)\b/i;
|
|
14
|
+
const looksLikeTargetedProsePatchIntent = (content, plan) => {
|
|
15
|
+
const trimmed = content.trim();
|
|
16
|
+
if (!trimmed || trimmed.length < 40)
|
|
17
|
+
return false;
|
|
18
|
+
if (looksLikeJsonPatchCandidate(trimmed))
|
|
19
|
+
return false;
|
|
20
|
+
if (!PROSE_PATCH_INTENT_PATTERN.test(trimmed))
|
|
21
|
+
return false;
|
|
22
|
+
const normalized = trimmed.replace(/\s+/g, " ").toLowerCase();
|
|
23
|
+
const targetPaths = uniqueStrings([...(plan.target_files ?? []), ...(plan.create_files ?? [])]
|
|
24
|
+
.map((entry) => entry.trim().toLowerCase())
|
|
25
|
+
.filter((entry) => entry.length >= 3));
|
|
26
|
+
if (targetPaths.length === 0)
|
|
27
|
+
return false;
|
|
28
|
+
return targetPaths.some((target) => normalized.includes(target));
|
|
29
|
+
};
|
|
30
|
+
const looksLikeJsonPatchCandidate = (content) => {
|
|
31
|
+
const trimmed = content.trim();
|
|
32
|
+
if (!trimmed)
|
|
33
|
+
return false;
|
|
34
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("["))
|
|
35
|
+
return true;
|
|
36
|
+
if (!/"patches"\s*:|"files"\s*:/.test(trimmed))
|
|
37
|
+
return false;
|
|
38
|
+
if (/```/i.test(trimmed)) {
|
|
39
|
+
const embedded = parseEmbeddedJsonObject(trimmed);
|
|
40
|
+
if (!embedded)
|
|
41
|
+
return false;
|
|
42
|
+
return (Object.prototype.hasOwnProperty.call(embedded, "patches")
|
|
43
|
+
|| Object.prototype.hasOwnProperty.call(embedded, "files"));
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
// Keep parse non-recoverable list explicit. Empty arrays are now retryable once via schema repair.
|
|
48
|
+
const NON_RECOVERABLE_PATCH_PARSE_PATTERNS = [];
|
|
49
|
+
const isNonRecoverablePatchParseError = (message) => {
|
|
50
|
+
const normalized = message.toLowerCase();
|
|
51
|
+
return NON_RECOVERABLE_PATCH_PARSE_PATTERNS.some((pattern) => normalized.includes(pattern));
|
|
52
|
+
};
|
|
53
|
+
const isSchemaDefinitionError = (message) => {
|
|
54
|
+
const normalized = message.toLowerCase();
|
|
55
|
+
return (normalized.includes(PATCHES_ARRAY_MISSING_ERROR.toLowerCase()) ||
|
|
56
|
+
normalized.includes(PATCHES_ARRAY_TYPE_ERROR.toLowerCase()) ||
|
|
57
|
+
normalized.includes(FILES_ARRAY_MISSING_ERROR.toLowerCase()) ||
|
|
58
|
+
normalized.includes(FILES_ARRAY_TYPE_ERROR.toLowerCase()) ||
|
|
59
|
+
normalized.includes("patch payload must include patches array") ||
|
|
60
|
+
normalized.includes("patch payload must include files array"));
|
|
61
|
+
};
|
|
62
|
+
const isInterpreterParseRetryCandidate = (message) => {
|
|
63
|
+
if (isNonRecoverablePatchParseError(message))
|
|
64
|
+
return false;
|
|
65
|
+
const normalized = message.toLowerCase();
|
|
66
|
+
if (isSchemaDefinitionError(message))
|
|
67
|
+
return false;
|
|
68
|
+
if (normalized.includes("patch entry must be an object"))
|
|
69
|
+
return false;
|
|
70
|
+
if (normalized.includes("patch file entry must be an object"))
|
|
71
|
+
return false;
|
|
72
|
+
if (normalized.includes("patch action must be"))
|
|
73
|
+
return false;
|
|
74
|
+
if (normalized.includes("patch field"))
|
|
75
|
+
return false;
|
|
76
|
+
if (normalized.includes("patch output is empty"))
|
|
77
|
+
return false;
|
|
78
|
+
if (normalized.includes("placeholder"))
|
|
79
|
+
return false;
|
|
80
|
+
if (normalized.includes("delete action without delete intent"))
|
|
81
|
+
return false;
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
const shouldRetrySchemaRepair = (message) => {
|
|
85
|
+
if (isNonRecoverablePatchParseError(message))
|
|
86
|
+
return false;
|
|
87
|
+
const normalized = message.toLowerCase();
|
|
88
|
+
if (normalized.includes("enoent")
|
|
89
|
+
|| normalized.includes("no such file or directory")
|
|
90
|
+
|| normalized.includes("disallowed files")
|
|
91
|
+
|| normalized.includes("outside allowed scope")
|
|
92
|
+
|| normalized.includes("outside architect plan targets")
|
|
93
|
+
|| normalized.includes("search block not found")
|
|
94
|
+
|| normalized.includes("read-only")
|
|
95
|
+
|| normalized.includes("destructive-operation policy")) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
const RETRYABLE_PATCH_APPLY_PATTERNS = [
|
|
101
|
+
"patch output used placeholder file paths",
|
|
102
|
+
"patch contains placeholder replace blocks",
|
|
103
|
+
"patch contains placeholder create content",
|
|
104
|
+
PATCHES_ARRAY_EMPTY_ERROR.toLowerCase(),
|
|
105
|
+
FILES_ARRAY_EMPTY_ERROR.toLowerCase(),
|
|
106
|
+
];
|
|
107
|
+
const NON_RETRYABLE_PATCH_APPLY_PATTERNS = [
|
|
108
|
+
"enoent",
|
|
109
|
+
"no such file or directory",
|
|
110
|
+
"disallowed files",
|
|
111
|
+
"outside allowed scope",
|
|
112
|
+
"delete action without delete intent",
|
|
113
|
+
"read-only",
|
|
114
|
+
"outside architect plan targets",
|
|
115
|
+
"destructive_operation_blocked",
|
|
116
|
+
"writes_disabled_by_profile",
|
|
117
|
+
"patch_outside_allowed_scope",
|
|
118
|
+
"patch_read_only_path",
|
|
119
|
+
"patch_outside_workspace",
|
|
120
|
+
"destructive-operation policy",
|
|
121
|
+
"write actions are disabled for the active workflow profile",
|
|
122
|
+
"search block not found",
|
|
123
|
+
];
|
|
124
|
+
const isSchemaLikePatchError = (message) => {
|
|
125
|
+
const normalized = message.toLowerCase();
|
|
126
|
+
return (isSchemaDefinitionError(message)
|
|
127
|
+
|| normalized.includes("patch output is not valid json")
|
|
128
|
+
|| normalized.includes("patch output is empty")
|
|
129
|
+
|| normalized.includes("patch action must be")
|
|
130
|
+
|| normalized.includes("patch field")
|
|
131
|
+
|| normalized.includes("patch entry must be an object")
|
|
132
|
+
|| normalized.includes("patch file entry must be an object"));
|
|
133
|
+
};
|
|
134
|
+
const buildPatchFailureClassification = (options) => {
|
|
135
|
+
if (options.rollback.attempted && !options.rollback.ok) {
|
|
136
|
+
return {
|
|
137
|
+
failure_class: "rollback",
|
|
138
|
+
failure_code: "patch_rollback_failed",
|
|
139
|
+
remediation_key: "manual_workspace_reconciliation",
|
|
140
|
+
retryable: false,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
switch (options.reasonCode) {
|
|
144
|
+
case "patch_outside_workspace":
|
|
145
|
+
return {
|
|
146
|
+
failure_class: "scope",
|
|
147
|
+
failure_code: "patch_outside_workspace",
|
|
148
|
+
remediation_key: "restrict_patch_to_workspace",
|
|
149
|
+
retryable: false,
|
|
150
|
+
};
|
|
151
|
+
case "patch_outside_allowed_scope":
|
|
152
|
+
return {
|
|
153
|
+
failure_class: "scope",
|
|
154
|
+
failure_code: "patch_outside_allowed_scope",
|
|
155
|
+
remediation_key: "restrict_patch_to_allowed_paths",
|
|
156
|
+
retryable: false,
|
|
157
|
+
};
|
|
158
|
+
case "patch_read_only_path":
|
|
159
|
+
return {
|
|
160
|
+
failure_class: "guardrail",
|
|
161
|
+
failure_code: "patch_read_only_path",
|
|
162
|
+
remediation_key: "exclude_read_only_paths",
|
|
163
|
+
retryable: false,
|
|
164
|
+
};
|
|
165
|
+
case "destructive_operation_blocked":
|
|
166
|
+
return {
|
|
167
|
+
failure_class: "guardrail",
|
|
168
|
+
failure_code: "destructive_operation_blocked",
|
|
169
|
+
remediation_key: "remove_delete_or_enable_destructive_policy",
|
|
170
|
+
retryable: false,
|
|
171
|
+
};
|
|
172
|
+
case "writes_disabled_by_profile":
|
|
173
|
+
return {
|
|
174
|
+
failure_class: "guardrail",
|
|
175
|
+
failure_code: "writes_disabled_by_profile",
|
|
176
|
+
remediation_key: "switch_workflow_profile_or_remove_writes",
|
|
177
|
+
retryable: false,
|
|
178
|
+
};
|
|
179
|
+
default:
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
const normalized = `${options.error} ${options.source}`.toLowerCase();
|
|
183
|
+
if (normalized.includes("delete action without delete intent")) {
|
|
184
|
+
return {
|
|
185
|
+
failure_class: "guardrail",
|
|
186
|
+
failure_code: "patch_delete_without_plan_intent",
|
|
187
|
+
remediation_key: "declare_delete_intent_or_remove_delete_actions",
|
|
188
|
+
retryable: false,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
if (normalized.includes("search block not found")) {
|
|
192
|
+
return {
|
|
193
|
+
failure_class: "search_match",
|
|
194
|
+
failure_code: "patch_search_block_not_found",
|
|
195
|
+
remediation_key: "refresh_file_context_and_retry_exact_match",
|
|
196
|
+
retryable: true,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (normalized.includes("ambiguous search block")) {
|
|
200
|
+
return {
|
|
201
|
+
failure_class: "search_match",
|
|
202
|
+
failure_code: "patch_search_block_ambiguous",
|
|
203
|
+
remediation_key: "narrow_search_block_context",
|
|
204
|
+
retryable: true,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (normalized.includes("placeholder")) {
|
|
208
|
+
return {
|
|
209
|
+
failure_class: "schema",
|
|
210
|
+
failure_code: "patch_placeholder_payload",
|
|
211
|
+
remediation_key: "replace_placeholders_with_concrete_payload",
|
|
212
|
+
retryable: true,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (normalized.includes("enoent") || normalized.includes("no such file or directory")) {
|
|
216
|
+
return {
|
|
217
|
+
failure_class: "filesystem",
|
|
218
|
+
failure_code: "patch_file_not_found",
|
|
219
|
+
remediation_key: "use_existing_path_or_plan_create_file",
|
|
220
|
+
retryable: false,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (normalized.includes("outside allowed scope")
|
|
224
|
+
|| normalized.includes("disallowed files")
|
|
225
|
+
|| normalized.includes("outside architect plan targets")) {
|
|
226
|
+
return {
|
|
227
|
+
failure_class: "scope",
|
|
228
|
+
failure_code: "patch_scope_violation",
|
|
229
|
+
remediation_key: "restrict_patch_to_plan_targets",
|
|
230
|
+
retryable: false,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
if (normalized.includes("read-only")
|
|
234
|
+
|| normalized.includes("destructive-operation policy")
|
|
235
|
+
|| normalized.includes("write actions are disabled")) {
|
|
236
|
+
return {
|
|
237
|
+
failure_class: "guardrail",
|
|
238
|
+
failure_code: "patch_guardrail_violation",
|
|
239
|
+
remediation_key: "respect_patch_policies",
|
|
240
|
+
retryable: false,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (isSchemaLikePatchError(options.error) || normalized.includes("patch parsing failed")) {
|
|
244
|
+
return {
|
|
245
|
+
failure_class: "schema",
|
|
246
|
+
failure_code: "patch_schema_invalid",
|
|
247
|
+
remediation_key: "emit_schema_valid_patch_payload",
|
|
248
|
+
retryable: true,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
failure_class: "filesystem",
|
|
253
|
+
failure_code: "patch_apply_failed",
|
|
254
|
+
remediation_key: "rebuild_patch_against_current_workspace",
|
|
255
|
+
retryable: false,
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
const shouldRetrySchemaRepairFromPatchApplyError = (error) => {
|
|
259
|
+
const classification = error.details.classification
|
|
260
|
+
?? buildPatchFailureClassification({
|
|
261
|
+
source: error.details.source,
|
|
262
|
+
error: error.details.error ?? error.message,
|
|
263
|
+
reasonCode: error.details.reasonCode,
|
|
264
|
+
rollback: error.details.rollback,
|
|
265
|
+
});
|
|
266
|
+
if (classification.failure_class === "search_match")
|
|
267
|
+
return false;
|
|
268
|
+
if (!classification.retryable)
|
|
269
|
+
return false;
|
|
270
|
+
if (classification.failure_class === "schema")
|
|
271
|
+
return true;
|
|
272
|
+
if (error.details.reasonCode
|
|
273
|
+
&& NON_RETRYABLE_PATCH_APPLY_PATTERNS.includes(error.details.reasonCode)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const message = (error.details.error ?? error.message ?? "").toLowerCase();
|
|
277
|
+
if (!message)
|
|
278
|
+
return false;
|
|
279
|
+
if (NON_RETRYABLE_PATCH_APPLY_PATTERNS.some((pattern) => message.includes(pattern)))
|
|
280
|
+
return false;
|
|
281
|
+
if (isSchemaDefinitionError(message))
|
|
282
|
+
return true;
|
|
283
|
+
return RETRYABLE_PATCH_APPLY_PATTERNS.some((pattern) => message.includes(pattern));
|
|
284
|
+
};
|
|
285
|
+
const isSearchBlockNotFoundError = (message) => message.toLowerCase().includes("search block not found");
|
|
286
|
+
const PATCH_PLACEHOLDER_PATTERN = /^(?:\.\.\.|<[^>]+>|todo|tbd|fixme|placeholder)$/i;
|
|
287
|
+
const hasMeaningfulPatchText = (value) => {
|
|
288
|
+
const trimmed = value.trim();
|
|
289
|
+
if (!trimmed)
|
|
290
|
+
return false;
|
|
291
|
+
if (PATCH_PLACEHOLDER_PATTERN.test(trimmed))
|
|
292
|
+
return false;
|
|
293
|
+
if (trimmed.length < 3)
|
|
294
|
+
return false;
|
|
295
|
+
return true;
|
|
296
|
+
};
|
|
297
|
+
const planAllowsDeleteAction = (plan) => {
|
|
298
|
+
const corpus = [
|
|
299
|
+
...(plan.steps ?? []),
|
|
300
|
+
plan.risk_assessment ?? "",
|
|
301
|
+
...(plan.verification ?? []),
|
|
302
|
+
].join(" ");
|
|
303
|
+
return /\b(delete|remove|drop|cleanup|clean up|retire|deprecat|prune)\b/i.test(corpus);
|
|
304
|
+
};
|
|
305
|
+
const validatePatchPayloadQuality = (patches, options) => {
|
|
306
|
+
for (const patch of patches) {
|
|
307
|
+
if (patch.action === "replace") {
|
|
308
|
+
if (!hasMeaningfulPatchText(patch.search_block) || !hasMeaningfulPatchText(patch.replace_block)) {
|
|
309
|
+
return "Patch contains placeholder replace blocks";
|
|
310
|
+
}
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (patch.action === "create") {
|
|
314
|
+
if (!hasMeaningfulPatchText(patch.content)) {
|
|
315
|
+
return "Patch contains placeholder create content";
|
|
316
|
+
}
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (patch.action === "delete" && !options.allowDeleteIntent) {
|
|
320
|
+
return "Patch contains delete action without delete intent in plan";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return undefined;
|
|
324
|
+
};
|
|
325
|
+
const parseEmbeddedJsonObject = (content) => {
|
|
326
|
+
const start = content.indexOf("{");
|
|
327
|
+
const end = content.lastIndexOf("}");
|
|
328
|
+
if (start < 0 || end <= start)
|
|
329
|
+
return undefined;
|
|
330
|
+
const candidate = content.slice(start, end + 1).trim();
|
|
331
|
+
if (!candidate)
|
|
332
|
+
return undefined;
|
|
333
|
+
try {
|
|
334
|
+
const parsed = JSON.parse(candidate);
|
|
335
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed))
|
|
336
|
+
return parsed;
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
// ignore invalid embedded JSON
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
};
|
|
343
|
+
const parseContextRequestRecord = (parsed) => {
|
|
344
|
+
const needsContext = parsed.needs_context === true ||
|
|
345
|
+
parsed.request_context === true ||
|
|
346
|
+
parsed.context_request === true ||
|
|
347
|
+
parsed.type === "needs_context";
|
|
348
|
+
if (!needsContext)
|
|
349
|
+
return undefined;
|
|
350
|
+
const queries = Array.isArray(parsed.queries)
|
|
351
|
+
? parsed.queries.map((entry) => String(entry)).filter(Boolean)
|
|
352
|
+
: undefined;
|
|
353
|
+
const files = Array.isArray(parsed.files)
|
|
354
|
+
? parsed.files.map((entry) => String(entry)).filter(Boolean)
|
|
355
|
+
: undefined;
|
|
356
|
+
const reason = typeof parsed.reason === "string" ? parsed.reason : undefined;
|
|
357
|
+
return { reason, queries, files };
|
|
358
|
+
};
|
|
359
|
+
const parseContextRequest = (content, mode) => {
|
|
360
|
+
const trimmed = content.trim();
|
|
361
|
+
if (!trimmed)
|
|
362
|
+
return undefined;
|
|
363
|
+
try {
|
|
364
|
+
const parsed = JSON.parse(trimmed);
|
|
365
|
+
return parseContextRequestRecord(parsed);
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// ignore invalid JSON
|
|
369
|
+
}
|
|
370
|
+
// In strict patch-json mode, only pure JSON context requests are accepted.
|
|
371
|
+
// Mixed prose + JSON should fail patch parsing and trigger deterministic recovery.
|
|
372
|
+
if (mode === "patch_json") {
|
|
373
|
+
if (!trimmed.startsWith("```"))
|
|
374
|
+
return undefined;
|
|
375
|
+
const embedded = parseEmbeddedJsonObject(trimmed);
|
|
376
|
+
if (!embedded)
|
|
377
|
+
return undefined;
|
|
378
|
+
return parseContextRequestRecord(embedded);
|
|
379
|
+
}
|
|
380
|
+
const embedded = parseEmbeddedJsonObject(trimmed);
|
|
381
|
+
if (embedded) {
|
|
382
|
+
const fromEmbedded = parseContextRequestRecord(embedded);
|
|
383
|
+
if (fromEmbedded)
|
|
384
|
+
return fromEmbedded;
|
|
385
|
+
}
|
|
386
|
+
if (/^needs_context$/i.test(trimmed)) {
|
|
387
|
+
return { reason: "needs_context" };
|
|
388
|
+
}
|
|
389
|
+
return undefined;
|
|
390
|
+
};
|
|
391
|
+
export class BuilderRunner {
|
|
392
|
+
constructor(options) {
|
|
393
|
+
this.options = options;
|
|
394
|
+
}
|
|
395
|
+
setProvider(provider, options = {}) {
|
|
396
|
+
this.options = {
|
|
397
|
+
...this.options,
|
|
398
|
+
provider,
|
|
399
|
+
model: options.model ?? this.options.model,
|
|
400
|
+
temperature: options.temperature ?? this.options.temperature,
|
|
401
|
+
responseFormat: options.responseFormat ?? this.options.responseFormat,
|
|
402
|
+
mode: options.mode ?? this.options.mode,
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
isToolSupportError(message) {
|
|
406
|
+
const normalized = message.toLowerCase();
|
|
407
|
+
return (normalized.includes("does not support tools") ||
|
|
408
|
+
normalized.includes("tools are not supported") ||
|
|
409
|
+
normalized.includes("tool is not supported") ||
|
|
410
|
+
(normalized.includes("tool") && normalized.includes("not supported")));
|
|
411
|
+
}
|
|
412
|
+
async run(plan, contextBundle, options = {}) {
|
|
413
|
+
let mode = this.options.mode ?? "tool_calls";
|
|
414
|
+
const patchFormat = this.options.patchFormat ?? "search_replace";
|
|
415
|
+
const emitPatchStatus = (message) => {
|
|
416
|
+
this.options.onEvent?.({ type: "status", phase: "patching", message });
|
|
417
|
+
};
|
|
418
|
+
const contextContent = contextBundle.serialized?.mode === "bundle_text" &&
|
|
419
|
+
contextBundle.serialized.audience === "builder"
|
|
420
|
+
? contextBundle.serialized.content
|
|
421
|
+
: serializeContext(contextBundle, { mode: "bundle_text", audience: "builder" }).content;
|
|
422
|
+
const buildSystemMessage = (modeOverride) => ({
|
|
423
|
+
role: "system",
|
|
424
|
+
content: buildBuilderPrompt(modeOverride, patchFormat),
|
|
425
|
+
});
|
|
426
|
+
const planTargets = Array.isArray(plan.target_files) ? plan.target_files.filter(Boolean) : [];
|
|
427
|
+
const planCreateTargets = Array.isArray(plan.create_files) ? plan.create_files.filter(Boolean) : [];
|
|
428
|
+
const bundlePaths = (contextBundle.files ?? []).map((entry) => entry.path).filter(Boolean);
|
|
429
|
+
const normalizePath = (value) => value.replace(/\\/g, "/").replace(/^\.?\//, "");
|
|
430
|
+
const bundleReadOnly = (contextBundle.read_only_paths ?? []).map(normalizePath).filter(Boolean);
|
|
431
|
+
const readOnlyPaths = bundleReadOnly;
|
|
432
|
+
const looksLikePlaceholderPath = (value) => value.startsWith("path/to/") || value.includes("<") || value.includes("...") || value.startsWith("your/");
|
|
433
|
+
const isReadOnlyPath = (value) => {
|
|
434
|
+
const normalized = normalizePath(value);
|
|
435
|
+
return readOnlyPaths.some((entry) => normalized === entry || normalized.startsWith(`${entry}/`));
|
|
436
|
+
};
|
|
437
|
+
const bundleAllow = (contextBundle.allow_write_paths ?? [])
|
|
438
|
+
.map(normalizePath)
|
|
439
|
+
.filter(Boolean)
|
|
440
|
+
.filter((path) => path !== "unknown")
|
|
441
|
+
.filter((path) => !isReadOnlyPath(path));
|
|
442
|
+
const normalizedPlanTargets = uniqueStrings([...planTargets, ...planCreateTargets]
|
|
443
|
+
.map(normalizePath)
|
|
444
|
+
.filter(Boolean)
|
|
445
|
+
.filter((path) => path !== "unknown")
|
|
446
|
+
.filter((path) => !isReadOnlyPath(path))
|
|
447
|
+
.filter((path) => !looksLikePlaceholderPath(path)));
|
|
448
|
+
const fallbackAllow = [...normalizedPlanTargets, ...bundlePaths]
|
|
449
|
+
.map(normalizePath)
|
|
450
|
+
.filter(Boolean)
|
|
451
|
+
.filter((path) => path !== "unknown")
|
|
452
|
+
.filter((path) => !isReadOnlyPath(path));
|
|
453
|
+
const preferredPaths = uniqueStrings([...normalizedPlanTargets, ...bundlePaths]
|
|
454
|
+
.map(normalizePath)
|
|
455
|
+
.filter(Boolean)
|
|
456
|
+
.filter((path) => path !== "unknown")
|
|
457
|
+
.filter((path) => !isReadOnlyPath(path))
|
|
458
|
+
.filter((path) => !looksLikePlaceholderPath(path)));
|
|
459
|
+
// Prefer architect-declared targets (including explicit create targets).
|
|
460
|
+
// Fall back to librarian-provided allow list only when plan targets are absent.
|
|
461
|
+
const allowedPathValues = normalizedPlanTargets.length > 0
|
|
462
|
+
? normalizedPlanTargets
|
|
463
|
+
: bundleAllow.length > 0
|
|
464
|
+
? uniqueStrings([...bundleAllow, ...fallbackAllow])
|
|
465
|
+
: [];
|
|
466
|
+
const allowedPaths = new Set(allowedPathValues);
|
|
467
|
+
const preferredRetryPaths = preferredPaths.filter((path) => allowedPaths.size === 0 || allowedPaths.has(path));
|
|
468
|
+
const concreteRetryPaths = preferredRetryPaths.length > 0
|
|
469
|
+
? preferredRetryPaths
|
|
470
|
+
: (allowedPaths.size > 0 ? Array.from(allowedPaths) : preferredPaths);
|
|
471
|
+
const validatePatchTargets = (paths) => {
|
|
472
|
+
const invalid = paths.filter((path) => {
|
|
473
|
+
if (!path.trim())
|
|
474
|
+
return true;
|
|
475
|
+
if (looksLikePlaceholderPath(path))
|
|
476
|
+
return true;
|
|
477
|
+
return false;
|
|
478
|
+
});
|
|
479
|
+
if (invalid.length) {
|
|
480
|
+
throw new Error(`Patch references invalid files: ${invalid.join(", ")}`);
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const allowDeleteIntent = planAllowsDeleteAction(plan);
|
|
484
|
+
const patchPolicy = {
|
|
485
|
+
allowWritePaths: allowedPathValues,
|
|
486
|
+
readOnlyPaths,
|
|
487
|
+
allowDestructiveOperations: this.options.allowDestructiveOperations === true,
|
|
488
|
+
};
|
|
489
|
+
const touchedFiles = new Set();
|
|
490
|
+
const logPatchPayload = async (payload, source) => {
|
|
491
|
+
if (!this.options.logger)
|
|
492
|
+
return;
|
|
493
|
+
const path = await this.options.logger.writePhaseArtifact("builder", "patch", payload);
|
|
494
|
+
await this.options.logger.log("builder_patch", {
|
|
495
|
+
patches: payload.patches.length,
|
|
496
|
+
source,
|
|
497
|
+
path,
|
|
498
|
+
});
|
|
499
|
+
};
|
|
500
|
+
const applyPayload = async (payload, source, rawOutput) => {
|
|
501
|
+
const patchApplier = this.options.patchApplier;
|
|
502
|
+
if (!patchApplier) {
|
|
503
|
+
throw new Error("PatchApplier is required to apply patches");
|
|
504
|
+
}
|
|
505
|
+
const patchQualityError = validatePatchPayloadQuality(payload.patches, {
|
|
506
|
+
allowDeleteIntent,
|
|
507
|
+
});
|
|
508
|
+
if (patchQualityError) {
|
|
509
|
+
const classification = buildPatchFailureClassification({
|
|
510
|
+
source,
|
|
511
|
+
error: patchQualityError,
|
|
512
|
+
rollback: { attempted: false, ok: true },
|
|
513
|
+
});
|
|
514
|
+
throw new PatchApplyError({
|
|
515
|
+
source,
|
|
516
|
+
error: patchQualityError,
|
|
517
|
+
classification,
|
|
518
|
+
patches: payload.patches,
|
|
519
|
+
rollback: { attempted: false, ok: true },
|
|
520
|
+
rawOutput,
|
|
521
|
+
});
|
|
522
|
+
}
|
|
523
|
+
validatePatchTargets(payload.patches.map((patch) => patch.file));
|
|
524
|
+
emitPatchStatus("applying patches");
|
|
525
|
+
let rollbackPlan;
|
|
526
|
+
try {
|
|
527
|
+
rollbackPlan = await patchApplier.createRollback(payload.patches, patchPolicy);
|
|
528
|
+
const applyResult = await patchApplier.apply(payload.patches, patchPolicy);
|
|
529
|
+
if (this.options.logger) {
|
|
530
|
+
await this.options.logger.log("patch_applied", {
|
|
531
|
+
patches: payload.patches.length,
|
|
532
|
+
touchedFiles: applyResult.touched,
|
|
533
|
+
source,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
for (const file of applyResult.touched)
|
|
537
|
+
touchedFiles.add(file);
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
let rollbackOk = false;
|
|
541
|
+
let rollbackError;
|
|
542
|
+
if (rollbackPlan) {
|
|
543
|
+
try {
|
|
544
|
+
await patchApplier.rollback(rollbackPlan);
|
|
545
|
+
rollbackOk = true;
|
|
546
|
+
}
|
|
547
|
+
catch (rollbackErr) {
|
|
548
|
+
rollbackError =
|
|
549
|
+
rollbackErr instanceof Error ? rollbackErr.message : String(rollbackErr);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (this.options.logger) {
|
|
553
|
+
await this.options.logger.log("patch_rollback", {
|
|
554
|
+
source,
|
|
555
|
+
ok: rollbackOk,
|
|
556
|
+
error: rollbackError ?? null,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
560
|
+
const reasonCode = error instanceof PatchPolicyError ? error.metadata.reason_code : undefined;
|
|
561
|
+
const diagnostics = error instanceof PatchPolicyError
|
|
562
|
+
? error.metadata
|
|
563
|
+
: undefined;
|
|
564
|
+
const rollback = { attempted: true, ok: rollbackOk, error: rollbackError };
|
|
565
|
+
const classification = buildPatchFailureClassification({
|
|
566
|
+
source,
|
|
567
|
+
error: message,
|
|
568
|
+
reasonCode,
|
|
569
|
+
rollback,
|
|
570
|
+
});
|
|
571
|
+
if (this.options.logger?.logSafetyEvent) {
|
|
572
|
+
await this.options.logger.logSafetyEvent({
|
|
573
|
+
phase: "act",
|
|
574
|
+
category: "patch",
|
|
575
|
+
code: classification.failure_code,
|
|
576
|
+
disposition: classification.retryable ? "retryable" : "non_retryable",
|
|
577
|
+
source,
|
|
578
|
+
message,
|
|
579
|
+
details: {
|
|
580
|
+
reason_code: reasonCode,
|
|
581
|
+
...(diagnostics ?? {}),
|
|
582
|
+
patch_failure: classification,
|
|
583
|
+
},
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
throw new PatchApplyError({
|
|
587
|
+
source,
|
|
588
|
+
error: message,
|
|
589
|
+
reasonCode,
|
|
590
|
+
diagnostics,
|
|
591
|
+
classification,
|
|
592
|
+
patches: payload.patches,
|
|
593
|
+
rollback,
|
|
594
|
+
rawOutput,
|
|
595
|
+
});
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
const buildUserContent = (note) => [
|
|
599
|
+
note ? `RETRY NOTE: ${note}` : null,
|
|
600
|
+
"PLAN (read-only):",
|
|
601
|
+
JSON.stringify(plan, null, 2),
|
|
602
|
+
"",
|
|
603
|
+
"CONTEXT BUNDLE (read-only; do not output):",
|
|
604
|
+
contextContent,
|
|
605
|
+
]
|
|
606
|
+
.filter(Boolean)
|
|
607
|
+
.join("\n");
|
|
608
|
+
const buildSchemaOnlyContent = (note) => {
|
|
609
|
+
const allowList = allowedPaths.size > 0
|
|
610
|
+
? Array.from(allowedPaths).join(", ")
|
|
611
|
+
: "all (except read-only)";
|
|
612
|
+
const readOnlyList = readOnlyPaths.length ? readOnlyPaths.join(", ") : "none";
|
|
613
|
+
const preferredList = concreteRetryPaths.length ? concreteRetryPaths.join(", ") : "none";
|
|
614
|
+
return [
|
|
615
|
+
note,
|
|
616
|
+
"Return ONLY the JSON payload that matches the schema in the system prompt.",
|
|
617
|
+
"Do not include any prose, markdown, or extra keys.",
|
|
618
|
+
"Do NOT include plan or context.",
|
|
619
|
+
"Never include changes for read-only paths.",
|
|
620
|
+
"Do NOT repeat prior rejected payloads or schema examples.",
|
|
621
|
+
`Allowed paths: ${allowList}`,
|
|
622
|
+
`Read-only paths: ${readOnlyList}`,
|
|
623
|
+
`Preferred concrete paths: ${preferredList}`,
|
|
624
|
+
concreteRetryPaths.length > 0
|
|
625
|
+
? "Use these repo-relative paths instead of placeholders like path/to/file.ts."
|
|
626
|
+
: null,
|
|
627
|
+
].join("\n");
|
|
628
|
+
};
|
|
629
|
+
const parseDisallowed = (message) => {
|
|
630
|
+
const match = message.match(/disallowed files:\\s*(.+)$/i);
|
|
631
|
+
if (match)
|
|
632
|
+
return match[1]?.trim();
|
|
633
|
+
const outsideAllowed = message.match(/outside allowed scope:\s*(.+)$/i);
|
|
634
|
+
if (outsideAllowed)
|
|
635
|
+
return outsideAllowed[1]?.trim();
|
|
636
|
+
const readOnly = message.match(/read-only:\s*(.+)$/i);
|
|
637
|
+
return readOnly ? readOnly[1]?.trim() : undefined;
|
|
638
|
+
};
|
|
639
|
+
const isDisallowedError = (message) => /disallowed files|outside allowed scope|read-only|patch_outside_allowed_scope|patch_read_only_path/i
|
|
640
|
+
.test(message);
|
|
641
|
+
const buildDisallowedNote = (message) => {
|
|
642
|
+
const disallowed = parseDisallowed(message) ?? "unknown";
|
|
643
|
+
const preferred = concreteRetryPaths.length > 0
|
|
644
|
+
? ` Preferred concrete paths: ${concreteRetryPaths.join(", ")}.`
|
|
645
|
+
: "";
|
|
646
|
+
return `Your patch included disallowed files: ${disallowed}. Remove any changes to read-only paths and return JSON for allowed paths only.${preferred}`;
|
|
647
|
+
};
|
|
648
|
+
const buildUserMessage = () => ({
|
|
649
|
+
role: "user",
|
|
650
|
+
content: buildUserContent(options.note),
|
|
651
|
+
});
|
|
652
|
+
const finalizeResult = (base, overrides = {}) => ({
|
|
653
|
+
...base,
|
|
654
|
+
...overrides,
|
|
655
|
+
...(touchedFiles.size > 0
|
|
656
|
+
? { touchedFiles: Array.from(touchedFiles).sort() }
|
|
657
|
+
: {}),
|
|
658
|
+
});
|
|
659
|
+
const contextManager = options.contextManager ?? this.options.contextManager;
|
|
660
|
+
const laneId = options.laneId ?? this.options.laneId;
|
|
661
|
+
const model = options.model ?? this.options.model;
|
|
662
|
+
const buildMessages = async (modeOverride) => {
|
|
663
|
+
const systemMessage = buildSystemMessage(modeOverride);
|
|
664
|
+
const userMessage = buildUserMessage();
|
|
665
|
+
const history = contextManager && laneId
|
|
666
|
+
? await contextManager.prepare(laneId, {
|
|
667
|
+
systemPrompt: systemMessage.content,
|
|
668
|
+
bundle: userMessage.content,
|
|
669
|
+
model,
|
|
670
|
+
})
|
|
671
|
+
: [];
|
|
672
|
+
const historyForMode = modeOverride === "patch_json"
|
|
673
|
+
? history.filter((message) => message.role !== "system")
|
|
674
|
+
: history;
|
|
675
|
+
return {
|
|
676
|
+
systemMessage,
|
|
677
|
+
userMessage,
|
|
678
|
+
history: historyForMode,
|
|
679
|
+
messages: [systemMessage, ...historyForMode, userMessage],
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
let messageState = await buildMessages(mode);
|
|
683
|
+
const resolveResponseFormat = (modeOverride) => modeOverride === "patch_json" && this.options.provider.name === "ollama-remote"
|
|
684
|
+
? {
|
|
685
|
+
type: "gbnf",
|
|
686
|
+
grammar: patchFormat === "file_writes"
|
|
687
|
+
? BUILDER_PATCH_GBNF_FILE_WRITES
|
|
688
|
+
: BUILDER_PATCH_GBNF_SEARCH_REPLACE,
|
|
689
|
+
}
|
|
690
|
+
: this.options.responseFormat;
|
|
691
|
+
const buildRunner = (modeOverride = mode, responseFormat = resolveResponseFormat(modeOverride)) => new Runner({
|
|
692
|
+
provider: this.options.provider,
|
|
693
|
+
tools: this.options.tools,
|
|
694
|
+
context: this.options.context,
|
|
695
|
+
maxSteps: this.options.maxSteps,
|
|
696
|
+
maxToolCalls: this.options.maxToolCalls,
|
|
697
|
+
maxTokens: this.options.maxTokens,
|
|
698
|
+
timeoutMs: this.options.timeoutMs,
|
|
699
|
+
temperature: this.options.temperature,
|
|
700
|
+
responseFormat,
|
|
701
|
+
toolChoice: modeOverride === "patch_json" || modeOverride === "freeform" ? "none" : "auto",
|
|
702
|
+
stream: this.options.stream,
|
|
703
|
+
onEvent: this.options.onEvent,
|
|
704
|
+
onToken: this.options.onToken,
|
|
705
|
+
streamFlushMs: this.options.streamFlushMs,
|
|
706
|
+
logger: this.options.logger,
|
|
707
|
+
});
|
|
708
|
+
const mergeUsage = (a, b) => {
|
|
709
|
+
if (!a)
|
|
710
|
+
return b;
|
|
711
|
+
if (!b)
|
|
712
|
+
return a;
|
|
713
|
+
return {
|
|
714
|
+
inputTokens: (a.inputTokens ?? 0) + (b.inputTokens ?? 0),
|
|
715
|
+
outputTokens: (a.outputTokens ?? 0) + (b.outputTokens ?? 0),
|
|
716
|
+
totalTokens: (a.totalTokens ?? 0) + (b.totalTokens ?? 0),
|
|
717
|
+
};
|
|
718
|
+
};
|
|
719
|
+
let result;
|
|
720
|
+
try {
|
|
721
|
+
result = await buildRunner(mode).run(messageState.messages);
|
|
722
|
+
}
|
|
723
|
+
catch (error) {
|
|
724
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
725
|
+
if (mode === "tool_calls" && this.isToolSupportError(message)) {
|
|
726
|
+
this.options.onEvent?.({
|
|
727
|
+
type: "status",
|
|
728
|
+
phase: "executing",
|
|
729
|
+
message: "builder: tools unsupported, retrying with patch_json",
|
|
730
|
+
});
|
|
731
|
+
if (this.options.logger) {
|
|
732
|
+
await this.options.logger.log("builder_tool_fallback", {
|
|
733
|
+
from: "tool_calls",
|
|
734
|
+
to: "patch_json",
|
|
735
|
+
error: message,
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
mode = "patch_json";
|
|
739
|
+
messageState = await buildMessages(mode);
|
|
740
|
+
result = await buildRunner(mode).run(messageState.messages);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const initialOutput = result.finalMessage.content;
|
|
747
|
+
if (contextManager && laneId) {
|
|
748
|
+
await contextManager.append(laneId, messageState.userMessage, {
|
|
749
|
+
role: "builder",
|
|
750
|
+
model,
|
|
751
|
+
});
|
|
752
|
+
await contextManager.append(laneId, result.finalMessage, {
|
|
753
|
+
role: "builder",
|
|
754
|
+
model,
|
|
755
|
+
tokens: result.usage?.totalTokens,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
const logContextRequest = async (contextRequest) => {
|
|
759
|
+
if (!this.options.logger)
|
|
760
|
+
return;
|
|
761
|
+
await this.options.logger.log("context_request", {
|
|
762
|
+
reason: contextRequest.reason,
|
|
763
|
+
queries: contextRequest.queries,
|
|
764
|
+
files: contextRequest.files,
|
|
765
|
+
});
|
|
766
|
+
};
|
|
767
|
+
const contextRequest = parseContextRequest(result.finalMessage.content, mode);
|
|
768
|
+
if (contextRequest) {
|
|
769
|
+
await logContextRequest(contextRequest);
|
|
770
|
+
return finalizeResult(result, { contextRequest });
|
|
771
|
+
}
|
|
772
|
+
if (mode === "tool_calls" && result.toolCallsExecuted === 0) {
|
|
773
|
+
const tryApplyDirectPatch = async () => {
|
|
774
|
+
const formats = patchFormat === "file_writes"
|
|
775
|
+
? ["file_writes", "search_replace"]
|
|
776
|
+
: ["search_replace", "file_writes"];
|
|
777
|
+
for (const format of formats) {
|
|
778
|
+
try {
|
|
779
|
+
const payload = parsePatchOutput(result.finalMessage.content, format);
|
|
780
|
+
if (this.options.logger) {
|
|
781
|
+
await this.options.logger.log("patch_direct_parse", {
|
|
782
|
+
length: result.finalMessage.content.length,
|
|
783
|
+
format,
|
|
784
|
+
source: "tool_calls_direct",
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
await logPatchPayload(payload, `tool_calls_direct_${format}`);
|
|
788
|
+
await applyPayload(payload, `tool_calls_direct_${format}`, result.finalMessage.content);
|
|
789
|
+
return true;
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
// try next format
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
return false;
|
|
796
|
+
};
|
|
797
|
+
const applied = await tryApplyDirectPatch();
|
|
798
|
+
if (applied) {
|
|
799
|
+
return finalizeResult(result);
|
|
800
|
+
}
|
|
801
|
+
if (this.options.logger) {
|
|
802
|
+
await this.options.logger.log("builder_tool_calls_no_actions", {
|
|
803
|
+
length: result.finalMessage.content.length,
|
|
804
|
+
preview: result.finalMessage.content.slice(0, 200),
|
|
805
|
+
action: "retry_patch_json",
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
this.options.onEvent?.({
|
|
809
|
+
type: "status",
|
|
810
|
+
phase: "executing",
|
|
811
|
+
message: "builder: no tool actions produced, retrying with patch_json",
|
|
812
|
+
});
|
|
813
|
+
mode = "patch_json";
|
|
814
|
+
messageState = await buildMessages(mode);
|
|
815
|
+
result = await buildRunner(mode).run(messageState.messages);
|
|
816
|
+
const retryContextRequest = parseContextRequest(result.finalMessage.content, mode);
|
|
817
|
+
if (retryContextRequest) {
|
|
818
|
+
await logContextRequest(retryContextRequest);
|
|
819
|
+
return finalizeResult(result, { contextRequest: retryContextRequest });
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
if (mode === "freeform") {
|
|
823
|
+
const patchApplier = this.options.patchApplier;
|
|
824
|
+
const interpreter = this.options.interpreter;
|
|
825
|
+
if (!patchApplier) {
|
|
826
|
+
throw new Error("PatchApplier is required for freeform mode");
|
|
827
|
+
}
|
|
828
|
+
if (!interpreter) {
|
|
829
|
+
throw new Error("PatchInterpreter is required for freeform mode");
|
|
830
|
+
}
|
|
831
|
+
if (this.options.logger) {
|
|
832
|
+
await this.options.logger.log("builder_freeform_output", {
|
|
833
|
+
length: result.finalMessage.content.length,
|
|
834
|
+
preview: result.finalMessage.content.slice(0, 200),
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
const payload = await interpreter.interpret(result.finalMessage.content);
|
|
838
|
+
await logPatchPayload(payload, "interpreter_freeform");
|
|
839
|
+
await applyPayload(payload, "interpreter_freeform", result.finalMessage.content);
|
|
840
|
+
return finalizeResult(result);
|
|
841
|
+
}
|
|
842
|
+
if (mode === "patch_json") {
|
|
843
|
+
const interpreter = this.options.interpreter;
|
|
844
|
+
const fallbackToInterpreter = this.options.fallbackToInterpreter === true;
|
|
845
|
+
const attemptHistory = [];
|
|
846
|
+
const recordAttempt = (stage, message) => {
|
|
847
|
+
const normalized = message.trim();
|
|
848
|
+
if (!normalized)
|
|
849
|
+
return;
|
|
850
|
+
attemptHistory.push(`${stage}:${normalized}`);
|
|
851
|
+
};
|
|
852
|
+
const parseAndApply = async (content, format, source) => {
|
|
853
|
+
const contextRequest = parseContextRequest(content, mode);
|
|
854
|
+
if (contextRequest)
|
|
855
|
+
return contextRequest;
|
|
856
|
+
const normalized = content.toLowerCase();
|
|
857
|
+
if (normalized.includes("path/to/file.")
|
|
858
|
+
|| normalized.includes("path/to/new.")
|
|
859
|
+
|| normalized.includes("path/to/old.")) {
|
|
860
|
+
const classification = buildPatchFailureClassification({
|
|
861
|
+
source,
|
|
862
|
+
error: "Patch output used placeholder file paths",
|
|
863
|
+
rollback: { attempted: false, ok: true },
|
|
864
|
+
});
|
|
865
|
+
throw new PatchApplyError({
|
|
866
|
+
source,
|
|
867
|
+
error: "Patch output used placeholder file paths",
|
|
868
|
+
classification,
|
|
869
|
+
patches: [],
|
|
870
|
+
rollback: { attempted: false, ok: true },
|
|
871
|
+
rawOutput: content,
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
try {
|
|
875
|
+
const payload = parsePatchOutput(content, format);
|
|
876
|
+
if (this.options.logger) {
|
|
877
|
+
await this.options.logger.log("patch_direct_parse", {
|
|
878
|
+
length: content.length,
|
|
879
|
+
format,
|
|
880
|
+
source,
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
await logPatchPayload(payload, source);
|
|
884
|
+
await applyPayload(payload, source, content);
|
|
885
|
+
return undefined;
|
|
886
|
+
}
|
|
887
|
+
catch (parseError) {
|
|
888
|
+
if (parseError instanceof PatchApplyError) {
|
|
889
|
+
throw parseError;
|
|
890
|
+
}
|
|
891
|
+
const parseErrorMessage = parseError instanceof Error ? parseError.message : String(parseError);
|
|
892
|
+
const hasJsonCandidate = looksLikeJsonPatchCandidate(content);
|
|
893
|
+
const hasTargetedProseIntent = looksLikeTargetedProsePatchIntent(content, plan);
|
|
894
|
+
const canUseInterpreter = fallbackToInterpreter
|
|
895
|
+
&& Boolean(interpreter)
|
|
896
|
+
&& (hasJsonCandidate || hasTargetedProseIntent)
|
|
897
|
+
&& isInterpreterParseRetryCandidate(parseErrorMessage);
|
|
898
|
+
if (!canUseInterpreter) {
|
|
899
|
+
throw parseError;
|
|
900
|
+
}
|
|
901
|
+
if (this.options.logger) {
|
|
902
|
+
await this.options.logger.log("patch_interpreter_precheck", {
|
|
903
|
+
length: content.length,
|
|
904
|
+
format,
|
|
905
|
+
source,
|
|
906
|
+
reason: hasJsonCandidate ? "json_candidate" : "prose_targeted_intent",
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
const payload = await interpreter.interpret(content, format);
|
|
910
|
+
await logPatchPayload(payload, source);
|
|
911
|
+
await applyPayload(payload, source, content);
|
|
912
|
+
return undefined;
|
|
913
|
+
}
|
|
914
|
+
};
|
|
915
|
+
let processingError;
|
|
916
|
+
const attemptFileWritesRecovery = async (reason, source) => {
|
|
917
|
+
const recoverySystem = {
|
|
918
|
+
role: "system",
|
|
919
|
+
content: buildBuilderPrompt(mode, "file_writes"),
|
|
920
|
+
};
|
|
921
|
+
const recoveryUser = {
|
|
922
|
+
role: "user",
|
|
923
|
+
content: buildSchemaOnlyContent(`${reason} Return JSON with a top-level "files" array and full updated content for each changed file.`),
|
|
924
|
+
};
|
|
925
|
+
if (this.options.logger) {
|
|
926
|
+
await this.options.logger.log("patch_search_block_recovery", {
|
|
927
|
+
format: "file_writes",
|
|
928
|
+
reason,
|
|
929
|
+
source,
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
const recoveryResult = await buildRunner(mode, {
|
|
933
|
+
type: "gbnf",
|
|
934
|
+
grammar: BUILDER_PATCH_GBNF_FILE_WRITES,
|
|
935
|
+
}).run([recoverySystem, ...messageState.history, messageState.userMessage, recoveryUser]);
|
|
936
|
+
const merged = {
|
|
937
|
+
...recoveryResult,
|
|
938
|
+
usage: mergeUsage(result.usage, recoveryResult.usage),
|
|
939
|
+
};
|
|
940
|
+
result = merged;
|
|
941
|
+
const contextRequestFromRecovery = await parseAndApply(merged.finalMessage.content, "file_writes", source);
|
|
942
|
+
if (contextRequestFromRecovery) {
|
|
943
|
+
await logContextRequest(contextRequestFromRecovery);
|
|
944
|
+
return finalizeResult(merged, { contextRequest: contextRequestFromRecovery });
|
|
945
|
+
}
|
|
946
|
+
return finalizeResult(merged);
|
|
947
|
+
};
|
|
948
|
+
try {
|
|
949
|
+
const contextRequestFromPrimary = await parseAndApply(result.finalMessage.content, patchFormat, "interpreter_primary");
|
|
950
|
+
if (contextRequestFromPrimary) {
|
|
951
|
+
await logContextRequest(contextRequestFromPrimary);
|
|
952
|
+
return finalizeResult(result, { contextRequest: contextRequestFromPrimary });
|
|
953
|
+
}
|
|
954
|
+
return finalizeResult(result);
|
|
955
|
+
}
|
|
956
|
+
catch (error) {
|
|
957
|
+
if (error instanceof PatchApplyError) {
|
|
958
|
+
recordAttempt("primary", error.details.error);
|
|
959
|
+
if (!shouldRetrySchemaRepairFromPatchApplyError(error)) {
|
|
960
|
+
if (patchFormat === "search_replace" && isSearchBlockNotFoundError(error.details.error)) {
|
|
961
|
+
return attemptFileWritesRecovery(`Search-replace patch failed: ${error.details.error}`, "interpreter_search_block_recovery");
|
|
962
|
+
}
|
|
963
|
+
throw error;
|
|
964
|
+
}
|
|
965
|
+
processingError = new Error(error.details.error);
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
recordAttempt("primary", error instanceof Error ? error.message : String(error));
|
|
969
|
+
processingError = error instanceof Error ? error : new Error(String(error));
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (processingError && this.options.logger) {
|
|
973
|
+
await this.options.logger.log("patch_parse_failed", {
|
|
974
|
+
format: patchFormat,
|
|
975
|
+
error: processingError.message,
|
|
976
|
+
preview: result.finalMessage.content.slice(0, 200),
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
const schemaRetryAllowed = processingError !== undefined ? shouldRetrySchemaRepair(processingError.message) : false;
|
|
980
|
+
if (processingError && !schemaRetryAllowed && this.options.logger) {
|
|
981
|
+
await this.options.logger.log("patch_retry_skipped", {
|
|
982
|
+
format: patchFormat,
|
|
983
|
+
reason: "non_recoverable_schema_error",
|
|
984
|
+
error: processingError.message,
|
|
985
|
+
});
|
|
986
|
+
}
|
|
987
|
+
if (processingError && schemaRetryAllowed && patchFormat === "file_writes") {
|
|
988
|
+
const retryNote = isDisallowedError(processingError.message)
|
|
989
|
+
? buildDisallowedNote(processingError.message)
|
|
990
|
+
: 'Your output did not match schema. Respond ONLY with JSON and include a top-level "files" array.';
|
|
991
|
+
const retrySystem = {
|
|
992
|
+
role: "system",
|
|
993
|
+
content: buildBuilderPrompt(mode, "file_writes"),
|
|
994
|
+
};
|
|
995
|
+
const retryUser = {
|
|
996
|
+
role: "user",
|
|
997
|
+
content: buildSchemaOnlyContent(retryNote),
|
|
998
|
+
};
|
|
999
|
+
if (this.options.logger) {
|
|
1000
|
+
await this.options.logger.log("patch_retry", { format: "file_writes", error: processingError.message });
|
|
1001
|
+
}
|
|
1002
|
+
const retryResult = await buildRunner(mode, {
|
|
1003
|
+
type: "gbnf",
|
|
1004
|
+
grammar: BUILDER_PATCH_GBNF_FILE_WRITES,
|
|
1005
|
+
}).run([retrySystem, ...messageState.history, messageState.userMessage, retryUser]);
|
|
1006
|
+
result = { ...retryResult, usage: mergeUsage(result.usage, retryResult.usage) };
|
|
1007
|
+
try {
|
|
1008
|
+
const contextRequestFromRetry = await parseAndApply(result.finalMessage.content, "file_writes", "interpreter_retry");
|
|
1009
|
+
if (contextRequestFromRetry) {
|
|
1010
|
+
await logContextRequest(contextRequestFromRetry);
|
|
1011
|
+
return finalizeResult(result, { contextRequest: contextRequestFromRetry });
|
|
1012
|
+
}
|
|
1013
|
+
return finalizeResult(result);
|
|
1014
|
+
}
|
|
1015
|
+
catch (retryError) {
|
|
1016
|
+
if (retryError instanceof PatchApplyError) {
|
|
1017
|
+
recordAttempt("retry", retryError.details.error);
|
|
1018
|
+
throw retryError;
|
|
1019
|
+
}
|
|
1020
|
+
const retryMessage = retryError instanceof Error ? retryError.message : String(retryError);
|
|
1021
|
+
recordAttempt("retry", retryMessage);
|
|
1022
|
+
if (this.options.logger) {
|
|
1023
|
+
await this.options.logger.log("patch_retry_failed", { format: "file_writes", error: retryMessage });
|
|
1024
|
+
}
|
|
1025
|
+
const fallbackNote = isDisallowedError(retryMessage)
|
|
1026
|
+
? buildDisallowedNote(retryMessage)
|
|
1027
|
+
: 'Fallback required. Respond ONLY with JSON patch schema using a top-level "patches" array.';
|
|
1028
|
+
const fallbackSystem = {
|
|
1029
|
+
role: "system",
|
|
1030
|
+
content: buildBuilderPrompt(mode, "search_replace"),
|
|
1031
|
+
};
|
|
1032
|
+
const fallbackUser = {
|
|
1033
|
+
role: "user",
|
|
1034
|
+
content: buildSchemaOnlyContent(fallbackNote),
|
|
1035
|
+
};
|
|
1036
|
+
if (this.options.logger) {
|
|
1037
|
+
await this.options.logger.log("patch_fallback", { format: "search_replace" });
|
|
1038
|
+
}
|
|
1039
|
+
const fallbackResult = await buildRunner(mode, {
|
|
1040
|
+
type: "gbnf",
|
|
1041
|
+
grammar: BUILDER_PATCH_GBNF_SEARCH_REPLACE,
|
|
1042
|
+
}).run([fallbackSystem, ...messageState.history, messageState.userMessage, fallbackUser]);
|
|
1043
|
+
result = { ...fallbackResult, usage: mergeUsage(result.usage, fallbackResult.usage) };
|
|
1044
|
+
try {
|
|
1045
|
+
const contextRequestFromFallback = await parseAndApply(result.finalMessage.content, "search_replace", "interpreter_fallback");
|
|
1046
|
+
if (contextRequestFromFallback) {
|
|
1047
|
+
await logContextRequest(contextRequestFromFallback);
|
|
1048
|
+
return finalizeResult(result, { contextRequest: contextRequestFromFallback });
|
|
1049
|
+
}
|
|
1050
|
+
return finalizeResult(result);
|
|
1051
|
+
}
|
|
1052
|
+
catch (fallbackError) {
|
|
1053
|
+
if (fallbackError instanceof PatchApplyError) {
|
|
1054
|
+
recordAttempt("fallback", fallbackError.details.error);
|
|
1055
|
+
throw fallbackError;
|
|
1056
|
+
}
|
|
1057
|
+
const fallbackMessage = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
|
|
1058
|
+
recordAttempt("fallback", fallbackMessage);
|
|
1059
|
+
if (isDisallowedError(fallbackMessage)) {
|
|
1060
|
+
const guardSystem = {
|
|
1061
|
+
role: "system",
|
|
1062
|
+
content: buildBuilderPrompt(mode, "search_replace"),
|
|
1063
|
+
};
|
|
1064
|
+
const guardUser = {
|
|
1065
|
+
role: "user",
|
|
1066
|
+
content: buildSchemaOnlyContent(buildDisallowedNote(fallbackMessage)),
|
|
1067
|
+
};
|
|
1068
|
+
const guardResult = await buildRunner(mode, {
|
|
1069
|
+
type: "gbnf",
|
|
1070
|
+
grammar: BUILDER_PATCH_GBNF_SEARCH_REPLACE,
|
|
1071
|
+
}).run([guardSystem, ...messageState.history, messageState.userMessage, guardUser]);
|
|
1072
|
+
result = { ...guardResult, usage: mergeUsage(result.usage, guardResult.usage) };
|
|
1073
|
+
try {
|
|
1074
|
+
await parseAndApply(result.finalMessage.content, "search_replace", "interpreter_guard");
|
|
1075
|
+
return finalizeResult(result);
|
|
1076
|
+
}
|
|
1077
|
+
catch (guardError) {
|
|
1078
|
+
if (guardError instanceof PatchApplyError) {
|
|
1079
|
+
recordAttempt("guard", guardError.details.error);
|
|
1080
|
+
throw guardError;
|
|
1081
|
+
}
|
|
1082
|
+
const guardMessage = guardError instanceof Error ? guardError.message : String(guardError);
|
|
1083
|
+
recordAttempt("guard", guardMessage);
|
|
1084
|
+
processingError = new Error(`Patch parsing failed. initial=${processingError.message}; retry=${retryMessage}; fallback=${fallbackMessage}; guard=${guardMessage}`);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
processingError = new Error(`Patch parsing failed. initial=${processingError.message}; retry=${retryMessage}; fallback=${fallbackMessage}`);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
if (processingError && schemaRetryAllowed && patchFormat === "search_replace") {
|
|
1094
|
+
const initialMessage = processingError.message;
|
|
1095
|
+
const retryNote = isDisallowedError(initialMessage)
|
|
1096
|
+
? buildDisallowedNote(initialMessage)
|
|
1097
|
+
: 'Your output did not match schema. Respond ONLY with JSON and include a top-level "patches" array.';
|
|
1098
|
+
const retrySystem = {
|
|
1099
|
+
role: "system",
|
|
1100
|
+
content: buildBuilderPrompt(mode, "search_replace"),
|
|
1101
|
+
};
|
|
1102
|
+
const retryUser = {
|
|
1103
|
+
role: "user",
|
|
1104
|
+
content: buildSchemaOnlyContent(retryNote),
|
|
1105
|
+
};
|
|
1106
|
+
if (this.options.logger) {
|
|
1107
|
+
await this.options.logger.log("patch_retry", {
|
|
1108
|
+
format: "search_replace",
|
|
1109
|
+
error: initialMessage,
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
const retryResult = await buildRunner(mode, {
|
|
1113
|
+
type: "gbnf",
|
|
1114
|
+
grammar: BUILDER_PATCH_GBNF_SEARCH_REPLACE,
|
|
1115
|
+
}).run([retrySystem, ...messageState.history, messageState.userMessage, retryUser]);
|
|
1116
|
+
result = { ...retryResult, usage: mergeUsage(result.usage, retryResult.usage) };
|
|
1117
|
+
try {
|
|
1118
|
+
await parseAndApply(result.finalMessage.content, "search_replace", "interpreter_retry");
|
|
1119
|
+
return finalizeResult(result);
|
|
1120
|
+
}
|
|
1121
|
+
catch (retryError) {
|
|
1122
|
+
if (retryError instanceof PatchApplyError) {
|
|
1123
|
+
recordAttempt("retry", retryError.details.error);
|
|
1124
|
+
if (isSearchBlockNotFoundError(retryError.details.error)) {
|
|
1125
|
+
return attemptFileWritesRecovery(`Search-replace retry failed: ${retryError.details.error}`, "interpreter_search_block_retry_recovery");
|
|
1126
|
+
}
|
|
1127
|
+
throw retryError;
|
|
1128
|
+
}
|
|
1129
|
+
const retryMessage = retryError instanceof Error ? retryError.message : String(retryError);
|
|
1130
|
+
recordAttempt("retry", retryMessage);
|
|
1131
|
+
if (this.options.logger) {
|
|
1132
|
+
await this.options.logger.log("patch_retry_failed", {
|
|
1133
|
+
format: "search_replace",
|
|
1134
|
+
error: retryMessage,
|
|
1135
|
+
});
|
|
1136
|
+
}
|
|
1137
|
+
processingError = new Error(`Patch parsing failed. initial=${initialMessage}; retry=${retryMessage}`);
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
if (processingError) {
|
|
1141
|
+
const classification = buildPatchFailureClassification({
|
|
1142
|
+
source: "builder_patch_processing",
|
|
1143
|
+
error: processingError.message,
|
|
1144
|
+
rollback: { attempted: false, ok: true },
|
|
1145
|
+
});
|
|
1146
|
+
throw new PatchApplyError({
|
|
1147
|
+
source: "builder_patch_processing",
|
|
1148
|
+
error: processingError.message,
|
|
1149
|
+
classification,
|
|
1150
|
+
attemptHistory: attemptHistory.length > 0 ? [...attemptHistory] : undefined,
|
|
1151
|
+
patches: [],
|
|
1152
|
+
rollback: { attempted: false, ok: true },
|
|
1153
|
+
rawOutput: result.finalMessage.content,
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
return finalizeResult(result);
|
|
1158
|
+
}
|
|
1159
|
+
}
|