@xcompiler/cli 0.2.3 → 0.2.4
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.CN.md +137 -202
- package/README.md +138 -205
- package/config.example.yaml +64 -32
- package/debug-wiki/README.md +12 -0
- package/debug-wiki/wiki/agent/calibration-fixtures.md +33 -0
- package/debug-wiki/wiki/agent/calibration-network-api.md +36 -0
- package/debug-wiki/wiki/agent/calibration-python-imports.md +33 -0
- package/debug-wiki/wiki/system/debug-issue-flow.md +29 -0
- package/debug-wiki/wiki/system/no-poisoning.md +29 -0
- package/dist/acp/index.d.ts +17 -2
- package/dist/acp/index.js +2403 -432
- package/dist/acp/index.js.map +1 -1
- package/dist/cli/xcompiler.js +2507 -523
- package/dist/cli/xcompiler.js.map +1 -1
- package/dist/cli/xcompiler_build.js +730 -248
- package/dist/cli/xcompiler_build.js.map +1 -1
- package/dist/cli/xcompiler_run.js +2185 -359
- package/dist/cli/xcompiler_run.js.map +1 -1
- package/dist/plugins/index.d.ts +16 -3
- package/dist/plugins/index.js +27 -17
- package/dist/plugins/index.js.map +1 -1
- package/dist/runtime/runtime.d.ts +38 -13
- package/dist/runtime/runtime.js +2495 -515
- package/dist/runtime/runtime.js.map +1 -1
- package/docs/XCompiler_design.md +542 -0
- package/docs/acp.md +4 -4
- package/docs/assets/iterative-v-model-pipeline.svg +169 -0
- package/docs/assets/system-architecture.svg +134 -0
- package/docs/assets/xcompiler-icon.png +0 -0
- package/docs/deploy.md +381 -0
- package/docs/openrouter.md +10 -1
- package/package.json +5 -1
|
@@ -59,11 +59,11 @@ var init_types = __esm({
|
|
|
59
59
|
import { Command } from "commander";
|
|
60
60
|
|
|
61
61
|
// src/runtime/commands.ts
|
|
62
|
-
import
|
|
63
|
-
import { promises as
|
|
62
|
+
import path30 from "path";
|
|
63
|
+
import { promises as fs28 } from "fs";
|
|
64
64
|
|
|
65
65
|
// src/core/project_file.ts
|
|
66
|
-
import { promises as
|
|
66
|
+
import { promises as fs3 } from "fs";
|
|
67
67
|
import path4 from "path";
|
|
68
68
|
import { z as z3 } from "zod";
|
|
69
69
|
|
|
@@ -249,7 +249,7 @@ var PlanSchema = z.object({
|
|
|
249
249
|
}));
|
|
250
250
|
|
|
251
251
|
// src/core/storage.ts
|
|
252
|
-
import { promises as
|
|
252
|
+
import { promises as fs2 } from "fs";
|
|
253
253
|
import path3 from "path";
|
|
254
254
|
|
|
255
255
|
// src/core/docs.ts
|
|
@@ -337,6 +337,9 @@ function testPlanDocForIteration(testPhase, iterationId = "P1") {
|
|
|
337
337
|
return basename ? `docs/iterations/${iterationId}/tests/${basename}` : canonical;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
// src/core/language.ts
|
|
341
|
+
import { promises as fs } from "fs";
|
|
342
|
+
|
|
340
343
|
// src/core/entry_gate.ts
|
|
341
344
|
import path from "path";
|
|
342
345
|
|
|
@@ -438,6 +441,7 @@ var PYTHON_EXECUTOR_SYSTEM = `You are XCompiler's Step Executor. You may only in
|
|
|
438
441
|
Every round you must return strict JSON:
|
|
439
442
|
{
|
|
440
443
|
"thoughts": "<one sentence describing this round's intent>",
|
|
444
|
+
"issueResolutionPlan": "<required only in DEBUG issue mode: concise root cause, repair target, and validation plan>",
|
|
441
445
|
"actions": [ { "tool": "<tool name>", "args": { ... } }, ... ],
|
|
442
446
|
"done": true | false
|
|
443
447
|
}
|
|
@@ -522,6 +526,7 @@ var TYPESCRIPT_EXECUTOR_SYSTEM = `You are XCompiler's Step Executor. You may onl
|
|
|
522
526
|
Every round you must return strict JSON:
|
|
523
527
|
{
|
|
524
528
|
"thoughts": "<one sentence describing this round's intent>",
|
|
529
|
+
"issueResolutionPlan": "<required only in DEBUG issue mode: concise root cause, repair target, and validation plan>",
|
|
525
530
|
"actions": [ { "tool": "<tool name>", "args": { ... } }, ... ],
|
|
526
531
|
"done": true | false
|
|
527
532
|
}
|
|
@@ -545,7 +550,7 @@ Rules:
|
|
|
545
550
|
7. package.json is the dependency manifest. Use add_dependency for npm packages; never write requirements.txt.
|
|
546
551
|
8. run_program runs the project entry with \`npx tsx\`, run_tests runs Vitest via \`npm test\`, and the final delivery gate also verifies the direct Node entry command.`;
|
|
547
552
|
var PLANNER_CLARIFY_SYSTEM = `You are the requirements analyst for XCompiler's V-model. Do not restate the topic; expose unresolved decisions that would change functional design, acceptance, or architecture boundaries.
|
|
548
|
-
Return strict JSON only. Each question must be directly answerable by a product owner
|
|
553
|
+
Return strict JSON only. Each question must be directly answerable by a product owner and cover one decision. Avoid vague catch-all or implementation-stack questions, except for the single development-language question when the user prompt explicitly requires it.`;
|
|
549
554
|
function buildPlannerSystem(profile) {
|
|
550
555
|
return (profile.id === "typescript" ? TYPESCRIPT_PLANNER_SYSTEM : PYTHON_PLANNER_SYSTEM) + profile.plannerPromptOverride;
|
|
551
556
|
}
|
|
@@ -637,13 +642,13 @@ var messages = {
|
|
|
637
642
|
preflightOllamaUnreachable: (baseUrl, message) => `preflight: ollama ${baseUrl} unreachable: ${message}`,
|
|
638
643
|
preflightAutoAdded: (providers, roles) => `preflight: auto-added ${providers} provider(s) for roles [${roles}]`,
|
|
639
644
|
scoreFileHeader: "# XCompiler LLM provider score snapshot (maintained automatically by ScoreStore; do not edit)",
|
|
640
|
-
scoreFileSemantics: "# Scores: default 1.0; automatic range 0.1-1.0; providers tagged cluster default to 0.2-0.5 unless llm.cluster_score_min/max widens it; failure -0.5; success +0.1
|
|
645
|
+
scoreFileSemantics: "# Scores: dynamic snapshot; default 1.0; automatic range 0.1-1.0; providers tagged cluster default to 0.2-0.5 unless llm.cluster_score_min/max widens it; failure -0.5; success +0.1. Put user overrides in llm_scores_user.yaml; 0 disables a provider."
|
|
641
646
|
},
|
|
642
647
|
system: {
|
|
643
648
|
configEnvMissing: (names) => `[xcompiler] unset config environment variables were replaced with empty strings: ${names}`,
|
|
644
649
|
unhandledError: (message) => `Unhandled error: ${message}`,
|
|
645
650
|
unsupportedPypiOnlyNetwork: "network=pypi-only is rejected because Docker cannot enforce a PyPI-only allowlist by itself. Use network=off for isolation or network=download-only for explicitly unrestricted outbound downloads.",
|
|
646
|
-
dockerInsideContainerUnsupported: "XCompiler is running inside a container, so sandbox
|
|
651
|
+
dockerInsideContainerUnsupported: "XCompiler is running inside a container, so sandbox mode docker is unsupported because Docker-outside-of-Docker can mis-map bind mounts and docker.sock permissions. Use agent.sandboxes.<language>.mode=subprocess, run XCompiler on the host, or set XC_IN_CONTAINER=0 only in a controlled environment.",
|
|
647
652
|
firejailUnsupported: "sandbox=firejail is not implemented; use subprocess or docker.",
|
|
648
653
|
smokeHeader: (baseUrl) => `Smoke test against ${baseUrl} (streaming)`,
|
|
649
654
|
smokeOk: (model, totalMs, firstTokenMs, chunks, preview) => `[OK total=${totalMs}ms first-token=${firstTokenMs}ms chunks=${chunks}] ${model} -> ${preview}`,
|
|
@@ -660,8 +665,8 @@ var messages = {
|
|
|
660
665
|
loaded: (plugin, version) => `Plugin ${plugin}@${version} loaded.`,
|
|
661
666
|
extensionConflict: (plugin, kind, name) => `Plugin ${plugin} cannot replace existing ${kind} "${name}".`,
|
|
662
667
|
hookFailed: (plugin, stage, message) => `Plugin ${plugin} failed during ${stage}: ${message}`,
|
|
663
|
-
manifestReadFailed: (
|
|
664
|
-
moduleLoadFailed: (plugin,
|
|
668
|
+
manifestReadFailed: (path31, message) => `Cannot read plugin manifest ${path31}: ${message}`,
|
|
669
|
+
moduleLoadFailed: (plugin, path31, message) => `Cannot load plugin ${plugin} from ${path31}: ${message}`,
|
|
665
670
|
exportInvalid: (plugin, exportName) => `Plugin ${plugin} export ${exportName} is not a valid XCompiler plugin`,
|
|
666
671
|
manifestMismatch: (plugin) => `Plugin ${plugin} runtime manifest does not match its preflight manifest`
|
|
667
672
|
},
|
|
@@ -749,6 +754,7 @@ var messages = {
|
|
|
749
754
|
optIntent: "plan intent: greenfield | feature | refactor | self",
|
|
750
755
|
optBaselinePlan: "existing baseline phasePlan.json / plan.json path (default <workspace>/phasePlan.json)",
|
|
751
756
|
optProjectFile: "XXX.xc project file path (default <workspace>/<name>.xc)",
|
|
757
|
+
optDebugWikiPath: "debug wiki root directory path (default <XCompiler path>/.xcompiler/debug-wiki)",
|
|
752
758
|
argPlan: "phasePlan.json or legacy plan.json path (default = <workspace>/phasePlan.json)",
|
|
753
759
|
argProjectFile: "XXX.xc project file",
|
|
754
760
|
argStepId: "Step ID, e.g. S001",
|
|
@@ -895,7 +901,7 @@ var messages = {
|
|
|
895
901
|
stepHeader: (id, phase, title, status, retries, maxRetries) => `${id} ${phase} ${title} ${status} retries=${retries}/${maxRetries}`,
|
|
896
902
|
stepRoleTools: (role, tools) => `role=${role} tools=[${tools}]`,
|
|
897
903
|
stepDependsOn: (ids) => `dependsOn: ${ids}`,
|
|
898
|
-
outputStatus: (
|
|
904
|
+
outputStatus: (exists2, p) => `${exists2 ? "\u2713" : "\u2717"} ${p}`,
|
|
899
905
|
auditEntry: (ts, kind, message) => `${ts} ${kind} ${message}`
|
|
900
906
|
},
|
|
901
907
|
execute: {
|
|
@@ -963,7 +969,7 @@ var messages = {
|
|
|
963
969
|
deliveryGateReason: (command, exitCode, timedOut) => `FUNCTIONAL_TEST gate: \`${command}\` exit=${exitCode}${timedOut ? " (timeout)" : ""}`,
|
|
964
970
|
missingPythonEntrypoint: "missing Python entrypoint: expected src/main.py, src/<package>/__main__.py, or an explicit CLI file such as src/cli.py",
|
|
965
971
|
missingTypeScriptEntrypoint: "missing TypeScript entrypoint: expected package.json start/bin or one of src/main.ts, src/index.ts, src/main.tsx",
|
|
966
|
-
invalidPythonEntrypointSource: (
|
|
972
|
+
invalidPythonEntrypointSource: (path31) => `invalid Python entrypoint source in ${path31}: expected a real CLI entry structure such as def main(...), argparse.ArgumentParser, or if __name__ == "__main__"; placeholder/import-only files are not runnable applications.`,
|
|
967
973
|
entrypointHelpOutputMissing: (command) => `entrypoint probe \`${command}\` exited 0 but produced no meaningful help/usage text; implement --help instead of relying on an empty script exit.`,
|
|
968
974
|
reasonLine: (reason) => `reason: ${reason}`,
|
|
969
975
|
roundsLine: (rounds) => `rounds: ${rounds}`,
|
|
@@ -1032,12 +1038,13 @@ Question mix (functionality first):
|
|
|
1032
1038
|
- Label options sequentially from A through the last generated option, for example A-B, A-C, A-D, or A-E. A should be the recommended/default setting when one is apparent. Options must be concrete business/product settings, not vague placeholders.
|
|
1033
1039
|
- Do not include \u201COther\u201D, \u201CCustom\u201D, or \u201CLet the user decide\u201D as an option. The CLI already allows the user to reply with one of the shown labels or enter a custom free-form answer.
|
|
1034
1040
|
${opts.projectShapeAmbiguous ? "- Required for this topic: ask the API library vs runnable application vs mixed-deliverable boundary explicitly.\n" : ""}
|
|
1041
|
+
${opts.languageAmbiguous ? "- Required for this topic: include exactly one boundary question confirming the target development language. Options must be A. Python (default/recommended) and B. TypeScript / Node.js. The user may still answer with custom free-form text.\n" : ""}
|
|
1035
1042
|
|
|
1036
|
-
[Hard constraint] The implementation stack is already fixed by
|
|
1043
|
+
${opts.languageAmbiguous ? `[Stack decision] XCompiler could not infer the target language from the topic or baseline. Ask only the development-language question described above; do not ask package manager, test framework, or OS questions. If the user does not choose, Python is the default.` : `[Hard constraint] The implementation stack is already fixed by the user's topic or existing project baseline. Do not reopen language/runtime/package-manager decisions.
|
|
1037
1044
|
**Do NOT** ask questions of these forms:
|
|
1038
1045
|
- "Which programming language / framework / runtime should this use?"
|
|
1039
1046
|
- "Which test framework / build tool / package manager?"
|
|
1040
|
-
- "Which OS is the target platform?"
|
|
1047
|
+
- "Which OS is the target platform?"`}
|
|
1041
1048
|
${opts.intent && opts.intent !== "greenfield" ? `This is an incremental ${opts.intent} request against an existing project${opts.hasBaseline ? " with a separate baseline summary that will be provided during decomposition" : ""}. Ask ONLY delta questions; do not ask to rebuild the project from scratch.` : ""}The majority of questions must concern functional behaviour; performance, boundaries, and extensibility should eliminate ambiguities that affect this delivery.`,
|
|
1042
1049
|
plannerDecompose: (raw, qa, addenda, opts = {}) => `Original requirement:
|
|
1043
1050
|
"""
|
|
@@ -1139,7 +1146,7 @@ Return strict JSON StepPlan for the current phase only.`,
|
|
|
1139
1146
|
executorDebugBlock: (reason, suggestions) => `
|
|
1140
1147
|
|
|
1141
1148
|
You are now in DEBUG retry mode. Previous failure reason: ${reason}
|
|
1142
|
-
DEBUG may edit upstream source files and tests within the current allowedWrites. If the failure reveals a real implementation, contract, or downstream integration mismatch, fix that real defect; do not pass by weakening assertions, skipping tests, deleting failing cases, or merely accommodating an incorrect test. If this rollback is in a design/requirements step and the concrete code change belongs to a later V-model step outside the current allowedWrites, update the current contract, test plan, or diagnostic artifact and finish this step so the later CODE step can implement it; do not attempt denied writes. If the failure is a missing third-party dependency or wrong library choice, use add_dependency with the real package name or change the source back to the real library selected by HIGH_LEVEL_DESIGN; never add try/except ImportError fake modules, fake classes/functions, empty implementations, or fallback mocks in production src/ code to bypass the error. Begin with read_file / code_search to localise the issue, then make the smallest possible fix via apply_patch / replace_in_file / add_dependency, and finally run_tests to verify. A DEBUG retry cannot be marked complete from read-only inspection alone: it must produce a successful repair action or a successful verification command in this retry. If the previous failure reason mentions repeated read-only/probe actions, use the existing failure log as sufficient context and make the next action a patch/write/dependency change or a verification command. When a test executes and fails an assertion about returned behaviour, do not repeatedly rewrite fixtures or samples. Only edit fixtures when the evidence is missing-file, malformed-fixture, or parse-error in the fixture itself; otherwise patch the implementation, interface contract, dependency choice, or test expectation that is actually wrong. If the failure log shows a network/API failure, do not stop at probing endpoints: use at most two consecutive http_fetch probes, reject 2xx responses with empty or unusable bodies, then patch the real integration and verify with run_program plus run_tests. Do not set done=true while the entrypoint still reports a network/API failure.` + (suggestions ? `
|
|
1149
|
+
When this retry is handling an issue, every JSON response must include issueResolutionPlan before or while fixing it. The plan must be concise and actionable: root cause hypothesis, files/contracts to change, validation command or gate, and what would disprove the plan. DEBUG may edit upstream source files and tests within the current allowedWrites. If the failure reveals a real implementation, contract, or downstream integration mismatch, fix that real defect; do not pass by weakening assertions, skipping tests, deleting failing cases, or merely accommodating an incorrect test. If this rollback is in a design/requirements step and the concrete code change belongs to a later V-model step outside the current allowedWrites, update the current contract, test plan, or diagnostic artifact and finish this step so the later CODE step can implement it; do not attempt denied writes. If the failure is a missing third-party dependency or wrong library choice, use add_dependency with the real package name or change the source back to the real library selected by HIGH_LEVEL_DESIGN; never add try/except ImportError fake modules, fake classes/functions, empty implementations, or fallback mocks in production src/ code to bypass the error. Begin with read_file / code_search to localise the issue, then make the smallest possible fix via apply_patch / replace_in_file / add_dependency, and finally run_tests to verify. A DEBUG retry cannot be marked complete from read-only inspection alone: it must produce a successful repair action or a successful verification command in this retry. If the previous failure reason mentions repeated read-only/probe actions, use the existing failure log as sufficient context and make the next action a patch/write/dependency change or a verification command. When a test executes and fails an assertion about returned behaviour, do not repeatedly rewrite fixtures or samples. Only edit fixtures when the evidence is missing-file, malformed-fixture, or parse-error in the fixture itself; otherwise patch the implementation, interface contract, dependency choice, or test expectation that is actually wrong. If the failure log shows a network/API failure, do not stop at probing endpoints: use at most two consecutive http_fetch probes, reject 2xx responses with empty or unusable bodies, then patch the real integration and verify with run_program plus run_tests. Do not set done=true while the entrypoint still reports a network/API failure.` + (suggestions ? `
|
|
1143
1150
|
|
|
1144
1151
|
${suggestions}` : ""),
|
|
1145
1152
|
executorGlobalBlock: (globalPrompt) => `
|
|
@@ -1156,7 +1163,8 @@ ${sp}`,
|
|
|
1156
1163
|
executorFeedbackVerifyMissing: (paths) => `outputs still missing: ${paths}. Please continue.`,
|
|
1157
1164
|
executorFeedbackReadOnlyLoopWarning: (rounds, targets) => `Loop guard warning: the last ${rounds} round(s) used only read/probe tools` + (targets ? ` (${targets})` : "") + ". Next response must include a successful repair action (apply_patch / replace_in_file / write_file / add_dependency) or a concrete verification action (run_tests / run_program). Do not continue with only read_file, list_dir, code_search, or http_fetch.",
|
|
1158
1165
|
executorFeedbackReadOnlyRecoveryRequired: "Read-only recovery mode is active because the previous attempt already failed from probing. The next response must use existing failure evidence to patch/write/change dependency or run verification; one more read-only-only response will fail this retry.",
|
|
1159
|
-
executorFeedbackRepairEvidenceMissing: "Invalid DEBUG completion: this retry has not produced repair evidence yet. Before done=true, perform at least one successful repair action or successful verification run; otherwise stop only with a concrete blocker in thoughts."
|
|
1166
|
+
executorFeedbackRepairEvidenceMissing: "Invalid DEBUG completion: this retry has not produced repair evidence yet. Before done=true, perform at least one successful repair action or successful verification run; otherwise stop only with a concrete blocker in thoughts.",
|
|
1167
|
+
executorFeedbackIssueResolutionPlanMissing: "Invalid DEBUG issue completion: issueResolutionPlan is required before the issue can be resolved. Return JSON with a concise handling plan plus the needed repair or verification actions."
|
|
1160
1168
|
},
|
|
1161
1169
|
skills: {
|
|
1162
1170
|
patcher: "Use apply_patch / replace_in_file for small in-place edits to existing files; never overwrite a whole file.",
|
|
@@ -1177,7 +1185,7 @@ ${sp}`,
|
|
|
1177
1185
|
summaryOk: "all checks passed.",
|
|
1178
1186
|
summaryWarn: (n) => `passed with ${n} warning(s).`,
|
|
1179
1187
|
summaryFail: (n) => `${n} failure(s) detected.`,
|
|
1180
|
-
configLoadOk: (
|
|
1188
|
+
configLoadOk: (path31) => `config loaded: ${path31}`,
|
|
1181
1189
|
configLoadFail: (msg) => `failed to load config: ${msg}`,
|
|
1182
1190
|
configLocale: (locale) => `locale=${locale}`,
|
|
1183
1191
|
llmNoProviders: "no LLM providers defined in config.llm.providers",
|
|
@@ -1195,7 +1203,7 @@ ${sp}`,
|
|
|
1195
1203
|
roleOk: (role, provider) => `role "${role}" \u2192 ${provider}`,
|
|
1196
1204
|
sandboxKind: (kind) => `sandbox=${kind}`,
|
|
1197
1205
|
sandboxNetworkPolicy: (policy, ports) => `network=${policy}` + (ports.length ? ` (expose_ports=[${ports.join(", ")}])` : ""),
|
|
1198
|
-
sandboxFullNoPorts: "network=full but no expose_ports configured \u2014 host-side cannot reach container services. Add `agent.
|
|
1206
|
+
sandboxFullNoPorts: "network=full but no expose_ports configured \u2014 host-side cannot reach container services. Add `agent.sandboxes.<language>.<local|docker>.limits.expose_ports: [<port>]` in config.yaml.",
|
|
1199
1207
|
sandboxNodeMissing: "node not found on PATH (required by TypeScript subprocess sandbox)",
|
|
1200
1208
|
sandboxNodeOk: (version) => `node OK (${version})`,
|
|
1201
1209
|
sandboxNpmMissing: "npm not found on PATH (required by TypeScript subprocess sandbox)",
|
|
@@ -1314,6 +1322,7 @@ var PYTHON_EXECUTOR_SYSTEM2 = `\u4F60\u662F XCompiler \u7684 Step Executor\u3002
|
|
|
1314
1322
|
\u6BCF\u4E00\u8F6E\u4F60\u5FC5\u987B\u8FD4\u56DE\u4E25\u683C JSON\uFF1A
|
|
1315
1323
|
{
|
|
1316
1324
|
"thoughts": "<\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u672C\u8F6E\u610F\u56FE>",
|
|
1325
|
+
"issueResolutionPlan": "<\u4EC5 DEBUG issue \u6A21\u5F0F\u5FC5\u586B\uFF1A\u7B80\u660E\u8BF4\u660E\u6839\u56E0\u5047\u8BBE\u3001\u4FEE\u590D\u76EE\u6807\u548C\u9A8C\u8BC1\u65B9\u6848>",
|
|
1317
1326
|
"actions": [ { "tool": "<\u5DE5\u5177\u540D>", "args": { ... } }, ... ],
|
|
1318
1327
|
"done": true | false
|
|
1319
1328
|
}
|
|
@@ -1397,6 +1406,7 @@ var TYPESCRIPT_EXECUTOR_SYSTEM2 = `\u4F60\u662F XCompiler \u7684 Step Executor\u
|
|
|
1397
1406
|
\u6BCF\u4E00\u8F6E\u4F60\u5FC5\u987B\u8FD4\u56DE\u4E25\u683C JSON\uFF1A
|
|
1398
1407
|
{
|
|
1399
1408
|
"thoughts": "<\u7528\u4E00\u53E5\u8BDD\u8BF4\u660E\u672C\u8F6E\u610F\u56FE>",
|
|
1409
|
+
"issueResolutionPlan": "<\u4EC5 DEBUG issue \u6A21\u5F0F\u5FC5\u586B\uFF1A\u7B80\u660E\u8BF4\u660E\u6839\u56E0\u5047\u8BBE\u3001\u4FEE\u590D\u76EE\u6807\u548C\u9A8C\u8BC1\u65B9\u6848>",
|
|
1400
1410
|
"actions": [ { "tool": "<\u5DE5\u5177\u540D>", "args": { ... } }, ... ],
|
|
1401
1411
|
"done": true | false
|
|
1402
1412
|
}
|
|
@@ -1510,13 +1520,13 @@ var messages2 = {
|
|
|
1510
1520
|
preflightOllamaUnreachable: (baseUrl, message) => `\u9884\u68C0\uFF1AOllama ${baseUrl} \u4E0D\u53EF\u8FBE\uFF1A${message}`,
|
|
1511
1521
|
preflightAutoAdded: (providers, roles) => `\u9884\u68C0\uFF1A\u81EA\u52A8\u589E\u52A0 ${providers} \u4E2A provider\uFF0C\u8986\u76D6\u89D2\u8272 [${roles}]`,
|
|
1512
1522
|
scoreFileHeader: "# XCompiler LLM provider \u8BC4\u5206\u5FEB\u7167\uFF08\u7531 ScoreStore \u81EA\u52A8\u7EF4\u62A4\uFF0C\u8BF7\u52FF\u624B\u5DE5\u7F16\u8F91\uFF09",
|
|
1513
|
-
scoreFileSemantics: "# \u8BC4\u5206\u8BED\u4E49\uFF1A\u9ED8\u8BA4 1.0\uFF1B\u81EA\u52A8\u8BC4\u5206\u8303\u56F4 0.1\uFF5E1.0\uFF1Btags: [cluster] \u7684 provider \u9ED8\u8BA4 0.2\uFF5E0.5\uFF0C\u9664\u975E llm.cluster_score_min/max \u6269\u5BBD\uFF1B\u5931\u8D25 -0.5\uFF1B\u6210\u529F +0.1\
|
|
1523
|
+
scoreFileSemantics: "# \u8BC4\u5206\u8BED\u4E49\uFF1A\u8FD9\u662F\u52A8\u6001\u8BC4\u5206\u5FEB\u7167\uFF1B\u9ED8\u8BA4 1.0\uFF1B\u81EA\u52A8\u8BC4\u5206\u8303\u56F4 0.1\uFF5E1.0\uFF1Btags: [cluster] \u7684 provider \u9ED8\u8BA4 0.2\uFF5E0.5\uFF0C\u9664\u975E llm.cluster_score_min/max \u6269\u5BBD\uFF1B\u5931\u8D25 -0.5\uFF1B\u6210\u529F +0.1\u3002\u7528\u6237\u8986\u76D6\u8BF7\u5199 llm_scores_user.yaml\uFF0C0 \u8868\u793A\u7981\u7528\u3002"
|
|
1514
1524
|
},
|
|
1515
1525
|
system: {
|
|
1516
1526
|
configEnvMissing: (names) => `[xcompiler] \u914D\u7F6E\u4E2D\u7684\u73AF\u5883\u53D8\u91CF\u672A\u8BBE\u7F6E\uFF0C\u5DF2\u66FF\u6362\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF1A${names}`,
|
|
1517
1527
|
unhandledError: (message) => `\u672A\u5904\u7406\u9519\u8BEF\uFF1A${message}`,
|
|
1518
1528
|
unsupportedPypiOnlyNetwork: "\u62D2\u7EDD network=pypi-only\uFF1ADocker \u672C\u8EAB\u65E0\u6CD5\u53EF\u9760\u6267\u884C\u201C\u4EC5 PyPI\u201D\u57DF\u540D\u767D\u540D\u5355\u3002\u9700\u8981\u9694\u79BB\u8BF7\u4F7F\u7528 network=off\uFF1B\u660E\u786E\u5141\u8BB8\u4EFB\u610F\u51FA\u7AD9\u4E0B\u8F7D\u65F6\u4F7F\u7528 network=download-only\u3002",
|
|
1519
|
-
dockerInsideContainerUnsupported: "\u68C0\u6D4B\u5230 XCompiler \u8FD0\u884C\u5728\u5BB9\u5668\u5185\uFF0Csandbox=docker \u53EF\u80FD\u5BFC\u81F4 bind-mount \u8DEF\u5F84\u53CA docker.sock \u6743\u9650\u9519\u4F4D\uFF0C\u56E0\u6B64\u4E0D\u53D7\u652F\u6301\u3002\u8BF7\u4F7F\u7528 agent.
|
|
1529
|
+
dockerInsideContainerUnsupported: "\u68C0\u6D4B\u5230 XCompiler \u8FD0\u884C\u5728\u5BB9\u5668\u5185\uFF0Csandbox mode=docker \u53EF\u80FD\u5BFC\u81F4 bind-mount \u8DEF\u5F84\u53CA docker.sock \u6743\u9650\u9519\u4F4D\uFF0C\u56E0\u6B64\u4E0D\u53D7\u652F\u6301\u3002\u8BF7\u4F7F\u7528 agent.sandboxes.<language>.mode=subprocess\u3001\u6539\u5728\u5BBF\u4E3B\u673A\u8FD0\u884C\uFF0C\u6216\u4EC5\u5728\u53D7\u63A7\u73AF\u5883\u8BBE\u7F6E XC_IN_CONTAINER=0\u3002",
|
|
1520
1530
|
firejailUnsupported: "\u5C1A\u672A\u5B9E\u73B0 sandbox=firejail\uFF0C\u8BF7\u4F7F\u7528 subprocess \u6216 docker\u3002",
|
|
1521
1531
|
smokeHeader: (baseUrl) => `\u6B63\u5728\u5BF9 ${baseUrl} \u6267\u884C\u6D41\u5F0F\u5192\u70DF\u6D4B\u8BD5`,
|
|
1522
1532
|
smokeOk: (model, totalMs, firstTokenMs, chunks, preview) => `[\u6210\u529F \u603B\u8017\u65F6=${totalMs}ms \u9996Token=${firstTokenMs}ms \u5206\u5757=${chunks}] ${model} -> ${preview}`,
|
|
@@ -1533,8 +1543,8 @@ var messages2 = {
|
|
|
1533
1543
|
loaded: (plugin, version) => `\u63D2\u4EF6 ${plugin}@${version} \u5DF2\u52A0\u8F7D\u3002`,
|
|
1534
1544
|
extensionConflict: (plugin, kind, name) => `\u63D2\u4EF6 ${plugin} \u4E0D\u80FD\u8986\u76D6\u5DF2\u6709 ${kind} \u201C${name}\u201D\u3002`,
|
|
1535
1545
|
hookFailed: (plugin, stage, message) => `\u63D2\u4EF6 ${plugin} \u5728 ${stage} \u9636\u6BB5\u6267\u884C\u5931\u8D25\uFF1A${message}`,
|
|
1536
|
-
manifestReadFailed: (
|
|
1537
|
-
moduleLoadFailed: (plugin,
|
|
1546
|
+
manifestReadFailed: (path31, message) => `\u65E0\u6CD5\u8BFB\u53D6\u63D2\u4EF6\u6E05\u5355 ${path31}\uFF1A${message}`,
|
|
1547
|
+
moduleLoadFailed: (plugin, path31, message) => `\u65E0\u6CD5\u4ECE ${path31} \u52A0\u8F7D\u63D2\u4EF6 ${plugin}\uFF1A${message}`,
|
|
1538
1548
|
exportInvalid: (plugin, exportName) => `\u63D2\u4EF6 ${plugin} \u7684\u5BFC\u51FA ${exportName} \u4E0D\u662F\u6709\u6548 XCompiler \u63D2\u4EF6`,
|
|
1539
1549
|
manifestMismatch: (plugin) => `\u63D2\u4EF6 ${plugin} \u7684\u8FD0\u884C\u65F6\u6E05\u5355\u4E0E\u9884\u68C0\u6E05\u5355\u4E0D\u4E00\u81F4`
|
|
1540
1550
|
},
|
|
@@ -1622,6 +1632,7 @@ var messages2 = {
|
|
|
1622
1632
|
optIntent: "\u8BA1\u5212\u610F\u56FE\uFF1Agreenfield | feature | refactor | self",
|
|
1623
1633
|
optBaselinePlan: "\u5DF2\u6709\u57FA\u7EBF phasePlan.json / plan.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 <workspace>/phasePlan.json\uFF09",
|
|
1624
1634
|
optProjectFile: "XXX.xc \u5DE5\u7A0B\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 <workspace>/<name>.xc\uFF09",
|
|
1635
|
+
optDebugWikiPath: "debug wiki \u6839\u76EE\u5F55\u8DEF\u5F84\uFF08\u9ED8\u8BA4 <XCompiler path>/.xcompiler/debug-wiki\uFF09",
|
|
1625
1636
|
argPlan: "phasePlan.json \u6216\u5386\u53F2 plan.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 = <workspace>/phasePlan.json\uFF09",
|
|
1626
1637
|
argProjectFile: "XXX.xc \u5DE5\u7A0B\u6587\u4EF6",
|
|
1627
1638
|
argStepId: "Step ID\uFF0C\u5982 S001",
|
|
@@ -1768,7 +1779,7 @@ var messages2 = {
|
|
|
1768
1779
|
stepHeader: (id, phase, title, status, retries, maxRetries) => `${id} ${phase} ${title} ${status} \u91CD\u8BD5=${retries}/${maxRetries}`,
|
|
1769
1780
|
stepRoleTools: (role, tools) => `\u89D2\u8272=${role} \u5DE5\u5177=[${tools}]`,
|
|
1770
1781
|
stepDependsOn: (ids) => `\u4F9D\u8D56\uFF1A${ids}`,
|
|
1771
|
-
outputStatus: (
|
|
1782
|
+
outputStatus: (exists2, p) => `${exists2 ? "\u2713" : "\u2717"} ${p}`,
|
|
1772
1783
|
auditEntry: (ts, kind, message) => `${ts} ${kind} ${message}`
|
|
1773
1784
|
},
|
|
1774
1785
|
execute: {
|
|
@@ -1836,7 +1847,7 @@ var messages2 = {
|
|
|
1836
1847
|
deliveryGateReason: (command, exitCode, timedOut) => `FUNCTIONAL_TEST \u95E8\u7981\uFF1A\`${command}\` \u9000\u51FA\u7801=${exitCode}${timedOut ? "\uFF08\u8D85\u65F6\uFF09" : ""}`,
|
|
1837
1848
|
missingPythonEntrypoint: "\u7F3A\u5C11 Python \u5165\u53E3\uFF1A\u9700\u8981 src/main.py\u3001src/<package>/__main__.py\uFF0C\u6216 src/cli.py \u7B49\u663E\u5F0F CLI \u6587\u4EF6",
|
|
1838
1849
|
missingTypeScriptEntrypoint: "\u7F3A\u5C11 TypeScript \u5165\u53E3\uFF1A\u9700\u8981 package.json start/bin \u6216 src/main.ts\u3001src/index.ts\u3001src/main.tsx \u4E4B\u4E00",
|
|
1839
|
-
invalidPythonEntrypointSource: (
|
|
1850
|
+
invalidPythonEntrypointSource: (path31) => `Python \u5165\u53E3\u6E90\u7801\u65E0\u6548\uFF1A${path31} \u5FC5\u987B\u662F\u771F\u5B9E CLI \u5165\u53E3\uFF0C\u81F3\u5C11\u5305\u542B def main(...)\u3001argparse.ArgumentParser \u6216 if __name__ == "__main__" \u8FD9\u7C7B\u5165\u53E3\u7ED3\u6784\uFF1B\u4EC5 import/comment \u7684\u5360\u4F4D\u6587\u4EF6\u4E0D\u80FD\u7B97\u53EF\u8FD0\u884C\u5E94\u7528\u3002`,
|
|
1840
1851
|
entrypointHelpOutputMissing: (command) => `\u5165\u53E3\u63A2\u6D4B \`${command}\` \u867D\u7136\u9000\u51FA\u7801\u4E3A 0\uFF0C\u4F46\u6CA1\u6709\u8F93\u51FA\u6709\u610F\u4E49\u7684 help/usage \u6587\u672C\uFF1B\u5FC5\u987B\u5B9E\u73B0 --help\uFF0C\u4E0D\u80FD\u9760\u7A7A\u811A\u672C\u81EA\u7136\u9000\u51FA\u8FC7\u5173\u3002`,
|
|
1841
1852
|
reasonLine: (reason) => `\u539F\u56E0\uFF1A${reason}`,
|
|
1842
1853
|
roundsLine: (rounds) => `\u8F6E\u6B21\uFF1A${rounds}`,
|
|
@@ -1881,7 +1892,7 @@ var messages2 = {
|
|
|
1881
1892
|
- \u6BCF\u4E2A CODE/\u6D4B\u8BD5\u4EA7\u7269\u5FC5\u987B\u4E25\u683C\u9650\u5B9A\u5728\u672C\u6B21\u589E\u91CF\u8303\u56F4\uFF0C\u7981\u6B62\u6574\u4F53\u91CD\u5EFA\u6216\u66FF\u6362\u4ED3\u5E93\u3002
|
|
1882
1893
|
- \u5C06\u7A33\u5B9A\u5BBF\u4E3B\u89C6\u4E3A N \u4EE3\u3001\u9694\u79BB worktree \u4E2D\u7684\u5019\u9009\u7248\u672C\u89C6\u4E3A N+1 \u4EE3\uFF1B\u7981\u6B62\u8BBE\u8BA1\u8FDB\u7A0B\u5185\u70ED\u66FF\u6362\u3002`,
|
|
1883
1894
|
plannerClarifySystem: `\u4F60\u662F XCompiler V \u6A21\u578B\u7684\u9700\u6C42\u5206\u6790\u5E08\u3002\u4F60\u7684\u804C\u8D23\u4E0D\u662F\u590D\u8FF0 topic\uFF0C\u800C\u662F\u53D1\u73B0\u4F1A\u6539\u53D8\u529F\u80FD\u8BBE\u8BA1\u3001\u9A8C\u6536\u7ED3\u679C\u6216\u67B6\u6784\u8FB9\u754C\u7684\u672A\u51B3\u4E8B\u9879\u3002
|
|
1884
|
-
\u53EA\u8FD4\u56DE\u4E25\u683C JSON\u3002\u95EE\u9898\u5FC5\u987B\u53EF\u7531\u4E1A\u52A1\u65B9\u76F4\u63A5\u4F5C\u7B54\u3001\u4E00\u6B21\u53EA\u786E\u8BA4\u4E00\u4E2A\u51B3\u7B56\uFF0C\u907F\u514D\u7A7A\u6CDB\u7684\u201C\u8FD8\u6709\u4EC0\u4E48\u8981\u6C42\u201D\u6216\u6280\u672F\u6808\u9009\u578B\u95EE\u9898\u3002`,
|
|
1895
|
+
\u53EA\u8FD4\u56DE\u4E25\u683C JSON\u3002\u95EE\u9898\u5FC5\u987B\u53EF\u7531\u4E1A\u52A1\u65B9\u76F4\u63A5\u4F5C\u7B54\u3001\u4E00\u6B21\u53EA\u786E\u8BA4\u4E00\u4E2A\u51B3\u7B56\uFF0C\u907F\u514D\u7A7A\u6CDB\u7684\u201C\u8FD8\u6709\u4EC0\u4E48\u8981\u6C42\u201D\u6216\u6280\u672F\u6808\u9009\u578B\u95EE\u9898\uFF1B\u53EA\u6709\u7528\u6237\u63D0\u793A\u660E\u786E\u8981\u6C42\u65F6\uFF0C\u624D\u5141\u8BB8\u63D0\u51FA\u552F\u4E00\u4E00\u6761\u5F00\u53D1\u8BED\u8A00\u786E\u8BA4\u95EE\u9898\u3002`,
|
|
1885
1896
|
plannerClarify: (raw, opts = {}) => `\u7528\u6237\u7684\u539F\u59CB\u9700\u6C42\u5982\u4E0B\uFF1A
|
|
1886
1897
|
|
|
1887
1898
|
"""
|
|
@@ -1906,12 +1917,13 @@ ${raw}
|
|
|
1906
1917
|
- \u9009\u9879\u4ECE A \u5F00\u59CB\u8FDE\u7EED\u6807\u53F7\uFF0C\u5230\u5B9E\u9645\u6700\u540E\u4E00\u4E2A\u9009\u9879\u7ED3\u675F\uFF0C\u4F8B\u5982 A-B\u3001A-C\u3001A-D \u6216 A-E\uFF1B\u5982\u679C\u80FD\u5224\u65AD\u63A8\u8350/\u9ED8\u8BA4\u65B9\u6848\uFF0CA \u5E94\u662F\u6700\u9AD8\u4F18\u5148\u7EA7\u65B9\u6848\u3002\u9009\u9879\u5FC5\u987B\u662F\u5177\u4F53\u4E1A\u52A1/\u4EA7\u54C1\u8BBE\u5B9A\uFF0C\u4E0D\u8981\u5199\u6210\u7A7A\u6CDB\u5360\u4F4D\u3002
|
|
1907
1918
|
- \u4E0D\u8981\u628A\u201C\u5176\u4ED6 / \u81EA\u5B9A\u4E49 / \u7528\u6237\u51B3\u5B9A\u201D\u4F5C\u4E3A\u9009\u9879\uFF1BCLI \u5DF2\u652F\u6301\u7528\u6237\u8F93\u5165\u5DF2\u5C55\u793A\u7684\u9009\u9879\u5B57\u6BCD\u6216\u76F4\u63A5\u8F93\u5165\u81EA\u5B9A\u4E49\u56DE\u7B54\u5185\u5BB9\u3002
|
|
1908
1919
|
${opts.projectShapeAmbiguous ? "- \u672C topic \u5FC5\u95EE\uFF1A\u660E\u786E\u786E\u8BA4 API library / \u53EF\u8FD0\u884C\u5E94\u7528 / mixed \u4EA4\u4ED8\u8FB9\u754C\u3002\n" : ""}
|
|
1920
|
+
${opts.languageAmbiguous ? "- \u672C topic \u5FC5\u95EE\uFF1A\u5FC5\u987B\u5305\u542B\u4E14\u53EA\u5305\u542B 1 \u4E2A boundary \u95EE\u9898\u786E\u8BA4\u76EE\u6807\u5F00\u53D1\u8BED\u8A00\u3002\u9009\u9879\u5FC5\u987B\u662F A. Python\uFF08\u9ED8\u8BA4/\u63A8\u8350\uFF09 \u548C B. TypeScript / Node.js\uFF1B\u7528\u6237\u4ECD\u53EF\u8F93\u5165\u81EA\u5B9A\u4E49\u56DE\u7B54\u3002\n" : ""}
|
|
1909
1921
|
|
|
1910
|
-
\u3010\u786C\u7EA6\u675F\u3011\u5B9E\u73B0\u6280\u672F\u6808\u5DF2\u7ECF\u7531
|
|
1922
|
+
${opts.languageAmbiguous ? `\u3010\u6280\u672F\u6808\u786E\u8BA4\u3011XCompiler \u65E0\u6CD5\u4ECE topic \u6216\u57FA\u7EBF\u4E2D\u5224\u65AD\u76EE\u6807\u5F00\u53D1\u8BED\u8A00\u3002\u53EA\u8BE2\u95EE\u4E0A\u9762\u8981\u6C42\u7684\u5F00\u53D1\u8BED\u8A00\u95EE\u9898\uFF1B\u4E0D\u8981\u8BE2\u95EE\u5305\u7BA1\u7406\u5668\u3001\u6D4B\u8BD5\u6846\u67B6\u6216\u64CD\u4F5C\u7CFB\u7EDF\u3002\u82E5\u7528\u6237\u4E0D\u9009\u62E9\uFF0C\u9ED8\u8BA4 Python\u3002` : `\u3010\u786C\u7EA6\u675F\u3011\u5B9E\u73B0\u6280\u672F\u6808\u5DF2\u7ECF\u7531\u7528\u6237 topic / \u73B0\u6709\u5DE5\u7A0B\u57FA\u7EBF\u56FA\u5B9A\uFF0C\u4E0D\u8981\u91CD\u65B0\u8BE2\u95EE\u8BED\u8A00\u3001\u8FD0\u884C\u65F6\u3001\u5305\u7BA1\u7406\u5668\u8FD9\u7C7B\u95EE\u9898\u3002
|
|
1911
1923
|
**\u4E25\u7981**\u63D0\u51FA\u4EE5\u4E0B\u7C7B\u578B\u7684\u95EE\u9898\uFF1A
|
|
1912
1924
|
- "\u5E0C\u671B\u7528\u4EC0\u4E48\u7F16\u7A0B\u8BED\u8A00 / \u6846\u67B6 / \u8FD0\u884C\u65F6\u5B9E\u73B0\uFF1F"
|
|
1913
1925
|
- "\u9700\u8981\u54EA\u79CD\u6D4B\u8BD5\u6846\u67B6 / \u6784\u5EFA\u5DE5\u5177 / \u5305\u7BA1\u7406\u5668\uFF1F"
|
|
1914
|
-
- "\u76EE\u6807\u5E73\u53F0\u662F\u54EA\u79CD\u64CD\u4F5C\u7CFB\u7EDF\uFF1F"
|
|
1926
|
+
- "\u76EE\u6807\u5E73\u53F0\u662F\u54EA\u79CD\u64CD\u4F5C\u7CFB\u7EDF\uFF1F"`}
|
|
1915
1927
|
${opts.intent && opts.intent !== "greenfield" ? `\u8FD9\u662F\u4E00\u6761\u9488\u5BF9\u73B0\u6709\u5DE5\u7A0B\u7684\u589E\u91CF ${opts.intent} \u8BF7\u6C42${opts.hasBaseline ? "\uFF1B\u5206\u89E3\u9636\u6BB5\u8FD8\u4F1A\u63D0\u4F9B\u4E00\u4EFD\u57FA\u7EBF\u6458\u8981" : ""}\u3002\u8BF7\u53EA\u95EE\u201C\u53D8\u66F4\u589E\u91CF\u201D\u76F8\u5173\u95EE\u9898\uFF0C\u4E0D\u8981\u628A\u9879\u76EE\u5F53\u6210\u4ECE\u96F6\u5F00\u59CB\u91CD\u505A\u3002` : ""}\u95EE\u9898\u4E3B\u4F53\u5FC5\u987B\u805A\u7126\u529F\u80FD\u884C\u4E3A\uFF1B\u6027\u80FD\u3001\u8FB9\u754C\u548C\u6269\u5C55\u6027\u7528\u4E8E\u6D88\u9664\u4F1A\u5F71\u54CD\u672C\u671F\u8BBE\u8BA1\u7684\u5173\u952E\u6B67\u4E49\u3002`,
|
|
1916
1928
|
plannerDecompose: (raw, qa, addenda, opts = {}) => `\u539F\u59CB\u9700\u6C42\uFF1A
|
|
1917
1929
|
"""
|
|
@@ -2013,7 +2025,7 @@ ${opts.phasePlan}
|
|
|
2013
2025
|
executorDebugBlock: (reason, suggestions) => `
|
|
2014
2026
|
|
|
2015
2027
|
\u6B63\u5904\u4E8E DEBUG \u91CD\u8BD5\u6A21\u5F0F\u3002\u4E0A\u4E00\u8F6E\u5931\u8D25\u539F\u56E0: ${reason}
|
|
2016
|
-
DEBUG \u53EF\u4EE5\u4FEE\u6539\u5F53\u524D allowedWrites \u5185\u7684\u4E0A\u6E38\u6E90\u7801\u4E0E\u6D4B\u8BD5\u6587\u4EF6\uFF1B\u82E5\u5931\u8D25\u66B4\u9732\u7684\u662F\u5B9E\u73B0\u3001\u5951\u7EA6\u6216\u4E0B\u6E38\u8C03\u7528\u4E0D\u4E00\u81F4\uFF0C\u5FC5\u987B\u4FEE\u771F\u5B9E\u7F3A\u9677\uFF0C\u7981\u6B62\u901A\u8FC7\u524A\u5F31\u65AD\u8A00\u3001\u8DF3\u8FC7\u6D4B\u8BD5\u3001\u5220\u9664\u5931\u8D25\u7528\u4F8B\u6216\u53EA\u8FCE\u5408\u9519\u8BEF\u6D4B\u8BD5\u6765\u8FC7\u5173\u3002\u5982\u679C\u672C\u6B21\u56DE\u9000\u5904\u4E8E\u9700\u6C42/\u8BBE\u8BA1\u9636\u6BB5\uFF0C\u800C\u5177\u4F53\u6E90\u7801\u4FEE\u6539\u5C5E\u4E8E\u540E\u7EED V \u6A21\u578B Step \u4E14\u76EE\u6807\u6587\u4EF6\u4E0D\u5728\u5F53\u524D allowedWrites \u5185\uFF0C\u5E94\u66F4\u65B0\u5F53\u524D\u5951\u7EA6\u3001\u6D4B\u8BD5\u8BA1\u5212\u6216\u8BCA\u65AD\u4EA7\u7269\u5E76\u5B8C\u6210\u5F53\u524D Step\uFF0C\u8BA9\u540E\u7EED CODE Step \u5B9E\u73B0\uFF1B\u4E0D\u8981\u5C1D\u8BD5\u88AB\u62D2\u7EDD\u7684\u8D8A\u6743\u5199\u5165\u3002\u82E5\u5931\u8D25\u662F\u7B2C\u4E09\u65B9\u4F9D\u8D56\u7F3A\u5931\u6216\u5E93\u9009\u578B\u9519\u8BEF\uFF0C\u5FC5\u987B\u7528 add_dependency \u5199\u5165\u771F\u5B9E\u5305\u540D\uFF0C\u6216\u628A\u6E90\u7801\u6539\u56DE HIGH_LEVEL_DESIGN \u9009\u5B9A\u7684\u771F\u5B9E\u5E93\uFF1B\u4E25\u7981\u5728 src/ \u751F\u4EA7\u4EE3\u7801\u91CC try/except ImportError \u540E\u4F2A\u9020 module\u3001fake class/function\u3001\u7A7A\u5B9E\u73B0\u6216 fallback mock \u6765\u7ED5\u8FC7\u9519\u8BEF\u3002\u8BF7\u5305\u542B read_file/code_search \u5148\u5B9A\u4F4D\u95EE\u9898\uFF0C\u518D\u4EE5 apply_patch / replace_in_file / add_dependency \u4F5C\u6700\u5C0F\u4FEE\u6539\uFF0C\u6700\u540E run_tests \u9A8C\u8BC1\u3002DEBUG \u91CD\u8BD5\u4E0D\u80FD\u53EA\u9760\u53EA\u8BFB\u68C0\u67E5\u5C31\u6807\u8BB0\u5B8C\u6210\uFF1A\u672C\u6B21\u91CD\u8BD5\u5FC5\u987B\u4EA7\u751F\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\uFF0C\u6216\u4E00\u6B21\u6210\u529F\u7684\u9A8C\u8BC1\u547D\u4EE4\u3002\u5982\u679C\u4E0A\u4E00\u8F6E\u5931\u8D25\u539F\u56E0\u5305\u542B repeated read-only/probe actions\uFF0C\u8BF7\u628A\u5DF2\u6709 failure log \u89C6\u4E3A\u8DB3\u591F\u4E0A\u4E0B\u6587\uFF0C\u4E0B\u4E00\u6B65\u76F4\u63A5 patch/write/\u6539\u4F9D\u8D56\u6216\u6267\u884C\u9A8C\u8BC1\u547D\u4EE4\u3002\u5F53\u6D4B\u8BD5\u5DF2\u6B63\u5E38\u6267\u884C\u4F46\u5931\u8D25\u70B9\u662F\u8FD4\u56DE\u884C\u4E3A\u65AD\u8A00\u65F6\uFF0C\u7981\u6B62\u53CD\u590D\u91CD\u5199 fixture \u6216\u6837\u4F8B\u3002\u53EA\u6709\u8BC1\u636E\u660E\u786E\u662F\u6587\u4EF6\u7F3A\u5931\u3001fixture \u683C\u5F0F\u9519\u8BEF\u6216 fixture \u672C\u8EAB\u89E3\u6790\u5931\u8D25\u65F6\u624D\u4FEE\u6539 fixture\uFF1B\u5426\u5219\u5E94\u4FEE\u5B9E\u73B0\u3001\u63A5\u53E3\u5951\u7EA6\u3001\u4F9D\u8D56\u9009\u578B\u6216\u786E\u5B9E\u9519\u8BEF\u7684\u65AD\u8A00\u3002\u5982\u679C\u5931\u8D25\u65E5\u5FD7\u663E\u793A\u7F51\u7EDC/API \u8C03\u7528\u5931\u8D25\uFF0C\u4E0D\u5141\u8BB8\u53EA\u505C\u7559\u5728\u63A2\u6D4B\u63A5\u53E3\uFF1A\u6700\u591A\u8FDE\u7EED\u6267\u884C 2 \u6B21 http_fetch \u63A2\u6D4B\uFF1BHTTP 2xx \u4F46 body \u4E3A\u7A7A\u6216\u683C\u5F0F\u4E0D\u53EF\u7528\u4E0D\u7B97\u53EF\u7528\u63A5\u53E3\uFF1B\u968F\u540E\u5FC5\u987B patch \u771F\u5B9E\u96C6\u6210\u4EE3\u7801\uFF0C\u5E76\u7528 run_program \u548C run_tests \u9A8C\u8BC1\u3002\u5165\u53E3\u4ECD\u8F93\u51FA\u7F51\u7EDC/API \u5931\u8D25\u65F6\u4E0D\u5F97 done=true\u3002` + (suggestions ? `
|
|
2028
|
+
\u5F53\u672C\u6B21 DEBUG \u6B63\u5728\u5904\u7406 issue \u65F6\uFF0C\u6BCF\u4E00\u8F6E JSON \u90FD\u5FC5\u987B\u5728\u4FEE\u590D\u524D\u6216\u4FEE\u590D\u8FC7\u7A0B\u4E2D\u989D\u5916\u5305\u542B issueResolutionPlan\u3002\u8BE5\u65B9\u6848\u5FC5\u987B\u7B80\u6D01\u4E14\u53EF\u6267\u884C\uFF1A\u6839\u56E0\u5047\u8BBE\u3001\u8981\u4FEE\u6539\u7684\u6587\u4EF6/\u5951\u7EA6\u3001\u9A8C\u8BC1\u547D\u4EE4\u6216\u95E8\u7981\uFF0C\u4EE5\u53CA\u4EC0\u4E48\u8BC1\u636E\u4F1A\u63A8\u7FFB\u8BE5\u65B9\u6848\u3002DEBUG \u53EF\u4EE5\u4FEE\u6539\u5F53\u524D allowedWrites \u5185\u7684\u4E0A\u6E38\u6E90\u7801\u4E0E\u6D4B\u8BD5\u6587\u4EF6\uFF1B\u82E5\u5931\u8D25\u66B4\u9732\u7684\u662F\u5B9E\u73B0\u3001\u5951\u7EA6\u6216\u4E0B\u6E38\u8C03\u7528\u4E0D\u4E00\u81F4\uFF0C\u5FC5\u987B\u4FEE\u771F\u5B9E\u7F3A\u9677\uFF0C\u7981\u6B62\u901A\u8FC7\u524A\u5F31\u65AD\u8A00\u3001\u8DF3\u8FC7\u6D4B\u8BD5\u3001\u5220\u9664\u5931\u8D25\u7528\u4F8B\u6216\u53EA\u8FCE\u5408\u9519\u8BEF\u6D4B\u8BD5\u6765\u8FC7\u5173\u3002\u5982\u679C\u672C\u6B21\u56DE\u9000\u5904\u4E8E\u9700\u6C42/\u8BBE\u8BA1\u9636\u6BB5\uFF0C\u800C\u5177\u4F53\u6E90\u7801\u4FEE\u6539\u5C5E\u4E8E\u540E\u7EED V \u6A21\u578B Step \u4E14\u76EE\u6807\u6587\u4EF6\u4E0D\u5728\u5F53\u524D allowedWrites \u5185\uFF0C\u5E94\u66F4\u65B0\u5F53\u524D\u5951\u7EA6\u3001\u6D4B\u8BD5\u8BA1\u5212\u6216\u8BCA\u65AD\u4EA7\u7269\u5E76\u5B8C\u6210\u5F53\u524D Step\uFF0C\u8BA9\u540E\u7EED CODE Step \u5B9E\u73B0\uFF1B\u4E0D\u8981\u5C1D\u8BD5\u88AB\u62D2\u7EDD\u7684\u8D8A\u6743\u5199\u5165\u3002\u82E5\u5931\u8D25\u662F\u7B2C\u4E09\u65B9\u4F9D\u8D56\u7F3A\u5931\u6216\u5E93\u9009\u578B\u9519\u8BEF\uFF0C\u5FC5\u987B\u7528 add_dependency \u5199\u5165\u771F\u5B9E\u5305\u540D\uFF0C\u6216\u628A\u6E90\u7801\u6539\u56DE HIGH_LEVEL_DESIGN \u9009\u5B9A\u7684\u771F\u5B9E\u5E93\uFF1B\u4E25\u7981\u5728 src/ \u751F\u4EA7\u4EE3\u7801\u91CC try/except ImportError \u540E\u4F2A\u9020 module\u3001fake class/function\u3001\u7A7A\u5B9E\u73B0\u6216 fallback mock \u6765\u7ED5\u8FC7\u9519\u8BEF\u3002\u8BF7\u5305\u542B read_file/code_search \u5148\u5B9A\u4F4D\u95EE\u9898\uFF0C\u518D\u4EE5 apply_patch / replace_in_file / add_dependency \u4F5C\u6700\u5C0F\u4FEE\u6539\uFF0C\u6700\u540E run_tests \u9A8C\u8BC1\u3002DEBUG \u91CD\u8BD5\u4E0D\u80FD\u53EA\u9760\u53EA\u8BFB\u68C0\u67E5\u5C31\u6807\u8BB0\u5B8C\u6210\uFF1A\u672C\u6B21\u91CD\u8BD5\u5FC5\u987B\u4EA7\u751F\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\uFF0C\u6216\u4E00\u6B21\u6210\u529F\u7684\u9A8C\u8BC1\u547D\u4EE4\u3002\u5982\u679C\u4E0A\u4E00\u8F6E\u5931\u8D25\u539F\u56E0\u5305\u542B repeated read-only/probe actions\uFF0C\u8BF7\u628A\u5DF2\u6709 failure log \u89C6\u4E3A\u8DB3\u591F\u4E0A\u4E0B\u6587\uFF0C\u4E0B\u4E00\u6B65\u76F4\u63A5 patch/write/\u6539\u4F9D\u8D56\u6216\u6267\u884C\u9A8C\u8BC1\u547D\u4EE4\u3002\u5F53\u6D4B\u8BD5\u5DF2\u6B63\u5E38\u6267\u884C\u4F46\u5931\u8D25\u70B9\u662F\u8FD4\u56DE\u884C\u4E3A\u65AD\u8A00\u65F6\uFF0C\u7981\u6B62\u53CD\u590D\u91CD\u5199 fixture \u6216\u6837\u4F8B\u3002\u53EA\u6709\u8BC1\u636E\u660E\u786E\u662F\u6587\u4EF6\u7F3A\u5931\u3001fixture \u683C\u5F0F\u9519\u8BEF\u6216 fixture \u672C\u8EAB\u89E3\u6790\u5931\u8D25\u65F6\u624D\u4FEE\u6539 fixture\uFF1B\u5426\u5219\u5E94\u4FEE\u5B9E\u73B0\u3001\u63A5\u53E3\u5951\u7EA6\u3001\u4F9D\u8D56\u9009\u578B\u6216\u786E\u5B9E\u9519\u8BEF\u7684\u65AD\u8A00\u3002\u5982\u679C\u5931\u8D25\u65E5\u5FD7\u663E\u793A\u7F51\u7EDC/API \u8C03\u7528\u5931\u8D25\uFF0C\u4E0D\u5141\u8BB8\u53EA\u505C\u7559\u5728\u63A2\u6D4B\u63A5\u53E3\uFF1A\u6700\u591A\u8FDE\u7EED\u6267\u884C 2 \u6B21 http_fetch \u63A2\u6D4B\uFF1BHTTP 2xx \u4F46 body \u4E3A\u7A7A\u6216\u683C\u5F0F\u4E0D\u53EF\u7528\u4E0D\u7B97\u53EF\u7528\u63A5\u53E3\uFF1B\u968F\u540E\u5FC5\u987B patch \u771F\u5B9E\u96C6\u6210\u4EE3\u7801\uFF0C\u5E76\u7528 run_program \u548C run_tests \u9A8C\u8BC1\u3002\u5165\u53E3\u4ECD\u8F93\u51FA\u7F51\u7EDC/API \u5931\u8D25\u65F6\u4E0D\u5F97 done=true\u3002` + (suggestions ? `
|
|
2017
2029
|
|
|
2018
2030
|
${suggestions}` : ""),
|
|
2019
2031
|
executorGlobalBlock: (globalPrompt) => `
|
|
@@ -2030,7 +2042,8 @@ ${sp}`,
|
|
|
2030
2042
|
executorFeedbackVerifyMissing: (paths) => `outputs \u4ECD\u7F3A\u5931\uFF1A${paths}\u3002\u8BF7\u7EE7\u7EED\u3002`,
|
|
2031
2043
|
executorFeedbackReadOnlyLoopWarning: (rounds, targets) => `\u5FAA\u73AF\u95E8\u7981\u8B66\u544A\uFF1A\u6700\u8FD1 ${rounds} \u8F6E\u53EA\u8C03\u7528\u4E86\u8BFB\u53D6/\u63A2\u6D4B\u5DE5\u5177` + (targets ? `\uFF08${targets}\uFF09` : "") + "\u3002\u4E0B\u4E00\u8F6E\u5FC5\u987B\u5305\u542B\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\uFF08apply_patch / replace_in_file / write_file / add_dependency\uFF09\u6216\u660E\u786E\u7684\u9A8C\u8BC1\u52A8\u4F5C\uFF08run_tests / run_program\uFF09\u3002\u4E0D\u8981\u7EE7\u7EED\u53EA\u8C03\u7528 read_file\u3001list_dir\u3001code_search \u6216 http_fetch\u3002",
|
|
2032
2044
|
executorFeedbackReadOnlyRecoveryRequired: "\u53EA\u8BFB\u6062\u590D\u6A21\u5F0F\u5DF2\u542F\u7528\uFF1A\u4E0A\u4E00\u8F6E\u5DF2\u7ECF\u56E0\u4E3A\u6301\u7EED\u63A2\u6D4B\u5931\u8D25\u3002\u672C\u8F6E\u5FC5\u987B\u57FA\u4E8E\u5DF2\u6709 failure log \u76F4\u63A5 patch/write/\u6539\u4F9D\u8D56\u6216\u6267\u884C\u9A8C\u8BC1\uFF1B\u5982\u679C\u4E0B\u4E00\u8F6E\u4ECD\u7136\u53EA\u6709\u53EA\u8BFB/\u63A2\u6D4B\u52A8\u4F5C\uFF0C\u672C\u6B21\u91CD\u8BD5\u4F1A\u5931\u8D25\u5E76\u56DE\u9000\u3002",
|
|
2033
|
-
executorFeedbackRepairEvidenceMissing: "DEBUG \u5B8C\u6210\u65E0\u6548\uFF1A\u672C\u6B21\u91CD\u8BD5\u8FD8\u6CA1\u6709\u4EA7\u751F\u4FEE\u590D\u8BC1\u636E\u3002\u8BBE\u7F6E done=true \u524D\uFF0C\u5FC5\u987B\u81F3\u5C11\u5B8C\u6210\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\u6216\u6210\u529F\u7684\u9A8C\u8BC1\u8FD0\u884C\uFF1B\u5426\u5219\u53EA\u80FD\u5728 thoughts \u4E2D\u7ED9\u51FA\u5177\u4F53 blocker \u540E\u505C\u6B62\u3002"
|
|
2045
|
+
executorFeedbackRepairEvidenceMissing: "DEBUG \u5B8C\u6210\u65E0\u6548\uFF1A\u672C\u6B21\u91CD\u8BD5\u8FD8\u6CA1\u6709\u4EA7\u751F\u4FEE\u590D\u8BC1\u636E\u3002\u8BBE\u7F6E done=true \u524D\uFF0C\u5FC5\u987B\u81F3\u5C11\u5B8C\u6210\u4E00\u6B21\u6210\u529F\u7684\u4FEE\u590D\u52A8\u4F5C\u6216\u6210\u529F\u7684\u9A8C\u8BC1\u8FD0\u884C\uFF1B\u5426\u5219\u53EA\u80FD\u5728 thoughts \u4E2D\u7ED9\u51FA\u5177\u4F53 blocker \u540E\u505C\u6B62\u3002",
|
|
2046
|
+
executorFeedbackIssueResolutionPlanMissing: "DEBUG issue \u5B8C\u6210\u65E0\u6548\uFF1AissueResolutionPlan \u662F issue resolved \u7684\u5FC5\u8981\u5B57\u6BB5\u3002\u8BF7\u8FD4\u56DE\u5305\u542B\u7B80\u660E\u5904\u7406\u65B9\u6848\u7684 JSON\uFF0C\u5E76\u540C\u65F6\u7ED9\u51FA\u5FC5\u8981\u4FEE\u590D\u6216\u9A8C\u8BC1\u52A8\u4F5C\u3002"
|
|
2034
2047
|
},
|
|
2035
2048
|
skills: {
|
|
2036
2049
|
patcher: "\u901A\u8FC7 apply_patch / replace_in_file \u5BF9\u5DF2\u6709\u6587\u4EF6\u505A\u5C0F\u6539\u52A8\uFF0C\u7981\u6B62\u6574\u6587\u4EF6\u8986\u76D6\u3002",
|
|
@@ -2051,7 +2064,7 @@ ${sp}`,
|
|
|
2051
2064
|
summaryOk: "\u5168\u90E8\u68C0\u67E5\u901A\u8FC7\u3002",
|
|
2052
2065
|
summaryWarn: (n) => `\u901A\u8FC7\uFF0C\u4F46\u6709 ${n} \u6761 warning\u3002`,
|
|
2053
2066
|
summaryFail: (n) => `\u68C0\u6D4B\u5230 ${n} \u9879\u5931\u8D25\u3002`,
|
|
2054
|
-
configLoadOk: (
|
|
2067
|
+
configLoadOk: (path31) => `\u914D\u7F6E\u5DF2\u52A0\u8F7D\uFF1A${path31}`,
|
|
2055
2068
|
configLoadFail: (msg) => `\u914D\u7F6E\u52A0\u8F7D\u5931\u8D25\uFF1A${msg}`,
|
|
2056
2069
|
configLocale: (locale) => `locale=${locale}`,
|
|
2057
2070
|
llmNoProviders: "config.llm.providers \u4E3A\u7A7A\uFF0C\u672A\u58F0\u660E\u4EFB\u4F55 provider",
|
|
@@ -2069,7 +2082,7 @@ ${sp}`,
|
|
|
2069
2082
|
roleOk: (role, provider) => `\u89D2\u8272 "${role}" \u2192 ${provider}`,
|
|
2070
2083
|
sandboxKind: (kind) => `sandbox=${kind}`,
|
|
2071
2084
|
sandboxNetworkPolicy: (policy, ports) => `network=${policy}` + (ports.length ? `\uFF08expose_ports=[${ports.join(", ")}]\uFF09` : ""),
|
|
2072
|
-
sandboxFullNoPorts: "network=full \u4F46\u672A\u914D\u7F6E expose_ports\u2014\u5BBF\u4E3B\u4FA7\u65E0\u6CD5\u8BBF\u95EE\u5BB9\u5668\u5185\u670D\u52A1\u3002\u8BF7\u5728 config.yaml \u4E2D\u8BBE\u7F6E `agent.
|
|
2085
|
+
sandboxFullNoPorts: "network=full \u4F46\u672A\u914D\u7F6E expose_ports\u2014\u5BBF\u4E3B\u4FA7\u65E0\u6CD5\u8BBF\u95EE\u5BB9\u5668\u5185\u670D\u52A1\u3002\u8BF7\u5728 config.yaml \u4E2D\u8BBE\u7F6E `agent.sandboxes.<language>.<local|docker>.limits.expose_ports: [<port>]`\u3002",
|
|
2073
2086
|
sandboxNodeMissing: "PATH \u4E0A\u627E\u4E0D\u5230 node\uFF08TypeScript subprocess \u6C99\u76D2\u5FC5\u9700\uFF09",
|
|
2074
2087
|
sandboxNodeOk: (version) => `node OK\uFF08${version}\uFF09`,
|
|
2075
2088
|
sandboxNpmMissing: "PATH \u4E0A\u627E\u4E0D\u5230 npm\uFF08TypeScript subprocess \u6C99\u76D2\u5FC5\u9700\uFF09",
|
|
@@ -2131,8 +2144,10 @@ function detectNetworkApiFailure(text) {
|
|
|
2131
2144
|
function isTestAssertionDiagnosticLine(line) {
|
|
2132
2145
|
const text = line.trim();
|
|
2133
2146
|
if (!text) return false;
|
|
2147
|
+
if (/^\d+\|\s/u.test(text)) return true;
|
|
2134
2148
|
if (/^(?:[→>-]\s*)?expected\b/iu.test(text)) return true;
|
|
2135
2149
|
if (/\bAssertionError\b/iu.test(text) && /\bexpected\b/iu.test(text)) return true;
|
|
2150
|
+
if (/\bexpect\s*\(/u.test(text) && /\.(?:to|not)\w*\s*\(/u.test(text)) return true;
|
|
2136
2151
|
return /\bexpected\b[\s\S]{0,240}\b(?:got|received|to\s+(?:be|equal|throw|contain|have|match))\b/iu.test(text);
|
|
2137
2152
|
}
|
|
2138
2153
|
function isTestRunnerStatusLine(line) {
|
|
@@ -2179,8 +2194,8 @@ async function autoFixSrcImports(ws, audit) {
|
|
|
2179
2194
|
const candidates = [];
|
|
2180
2195
|
if (await ws.exists("src/main.py")) candidates.push("src/main.py");
|
|
2181
2196
|
try {
|
|
2182
|
-
const
|
|
2183
|
-
const entries = await
|
|
2197
|
+
const fs29 = await import("fs/promises");
|
|
2198
|
+
const entries = await fs29.readdir(ws.abs("src"), { withFileTypes: true });
|
|
2184
2199
|
for (const e of entries) {
|
|
2185
2200
|
if (!e.isDirectory()) continue;
|
|
2186
2201
|
const rel = `src/${e.name}/__main__.py`;
|
|
@@ -2331,8 +2346,8 @@ async function detectPythonEntrypoint(ws) {
|
|
|
2331
2346
|
if (await ws.exists("src/main.py")) return fileCandidate("src/main.py");
|
|
2332
2347
|
if (await ws.exists("src/__main__.py")) return fileCandidate("src/__main__.py");
|
|
2333
2348
|
try {
|
|
2334
|
-
const
|
|
2335
|
-
const entries = await
|
|
2349
|
+
const fs29 = await import("fs/promises");
|
|
2350
|
+
const entries = await fs29.readdir(ws.abs("src"), { withFileTypes: true });
|
|
2336
2351
|
for (const e of entries) {
|
|
2337
2352
|
if (!e.isDirectory()) continue;
|
|
2338
2353
|
const rel = `src/${e.name}/__main__.py`;
|
|
@@ -2391,7 +2406,7 @@ var typescriptProfile = {
|
|
|
2391
2406
|
manifestFile: "package.json",
|
|
2392
2407
|
codeExtensions: [".ts", ".tsx"],
|
|
2393
2408
|
seedManifestFromDeps: false,
|
|
2394
|
-
defaultDockerImage: "node:
|
|
2409
|
+
defaultDockerImage: "node:24-slim",
|
|
2395
2410
|
renderManifest(deps) {
|
|
2396
2411
|
const pkg = {
|
|
2397
2412
|
name: "app",
|
|
@@ -2420,6 +2435,9 @@ var typescriptProfile = {
|
|
|
2420
2435
|
},
|
|
2421
2436
|
plannerPromptOverride: "",
|
|
2422
2437
|
executorPromptOverride: "",
|
|
2438
|
+
async autoFixImports(ws, audit) {
|
|
2439
|
+
return autoFixTypeScriptTypeOnlyImports(ws, audit);
|
|
2440
|
+
},
|
|
2423
2441
|
async probeEntry(ws, sandbox) {
|
|
2424
2442
|
return probeTsEntrypoint(ws, sandbox);
|
|
2425
2443
|
}
|
|
@@ -2569,6 +2587,99 @@ async function readJsonFile(ws, rel) {
|
|
|
2569
2587
|
return null;
|
|
2570
2588
|
}
|
|
2571
2589
|
}
|
|
2590
|
+
var TYPE_ONLY_IMPORTS_BY_PACKAGE = {
|
|
2591
|
+
axios: /* @__PURE__ */ new Set([
|
|
2592
|
+
"AxiosAdapter",
|
|
2593
|
+
"AxiosBasicCredentials",
|
|
2594
|
+
"AxiosHeaderValue",
|
|
2595
|
+
"AxiosInstance",
|
|
2596
|
+
"AxiosInterceptorManager",
|
|
2597
|
+
"AxiosPromise",
|
|
2598
|
+
"AxiosProxyConfig",
|
|
2599
|
+
"AxiosRequestConfig",
|
|
2600
|
+
"AxiosRequestHeaders",
|
|
2601
|
+
"AxiosResponse",
|
|
2602
|
+
"AxiosResponseHeaders",
|
|
2603
|
+
"CreateAxiosDefaults",
|
|
2604
|
+
"InternalAxiosRequestConfig",
|
|
2605
|
+
"RawAxiosRequestHeaders"
|
|
2606
|
+
])
|
|
2607
|
+
};
|
|
2608
|
+
async function autoFixTypeScriptTypeOnlyImports(ws, audit) {
|
|
2609
|
+
const files = await listTypeScriptSourceFiles(ws, "src");
|
|
2610
|
+
const fixed = [];
|
|
2611
|
+
for (const rel of files) {
|
|
2612
|
+
const original = await ws.readFile(rel);
|
|
2613
|
+
const next = rewriteKnownTypeOnlyImports(original);
|
|
2614
|
+
if (next === original) continue;
|
|
2615
|
+
await ws.writeFile(rel, next);
|
|
2616
|
+
fixed.push(rel);
|
|
2617
|
+
await audit.event("note", `fixed type-only imports in ${rel}`, {
|
|
2618
|
+
messageId: "audit.typescript_imports_autofix",
|
|
2619
|
+
path: rel
|
|
2620
|
+
});
|
|
2621
|
+
}
|
|
2622
|
+
return fixed;
|
|
2623
|
+
}
|
|
2624
|
+
async function listTypeScriptSourceFiles(ws, dir) {
|
|
2625
|
+
const abs = ws.abs(dir);
|
|
2626
|
+
let entries;
|
|
2627
|
+
try {
|
|
2628
|
+
entries = await fs.readdir(abs, { withFileTypes: true });
|
|
2629
|
+
} catch {
|
|
2630
|
+
return [];
|
|
2631
|
+
}
|
|
2632
|
+
const files = [];
|
|
2633
|
+
for (const entry of entries) {
|
|
2634
|
+
const rel = `${dir}/${entry.name}`.replace(/\\/g, "/");
|
|
2635
|
+
if (entry.isDirectory()) {
|
|
2636
|
+
if (entry.name === "node_modules" || entry.name === ".git") continue;
|
|
2637
|
+
files.push(...await listTypeScriptSourceFiles(ws, rel));
|
|
2638
|
+
} else if (entry.isFile() && /\.tsx?$/.test(entry.name)) {
|
|
2639
|
+
files.push(rel);
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
return files.sort();
|
|
2643
|
+
}
|
|
2644
|
+
function rewriteKnownTypeOnlyImports(source) {
|
|
2645
|
+
const importRe = /^import\s+([^'"\n]+?)\s+from\s+(['"])([^'"]+)\2\s*;?$/gm;
|
|
2646
|
+
return source.replace(importRe, (full, clauseRaw, quote, specifier) => {
|
|
2647
|
+
const knownTypes = TYPE_ONLY_IMPORTS_BY_PACKAGE[specifier];
|
|
2648
|
+
if (!knownTypes) return full;
|
|
2649
|
+
if (clauseRaw.trim().startsWith("type ")) return full;
|
|
2650
|
+
const parsed = splitImportClause(clauseRaw);
|
|
2651
|
+
if (!parsed?.named.length) return full;
|
|
2652
|
+
const valueNamed = [];
|
|
2653
|
+
const typeNamed = [];
|
|
2654
|
+
for (const item of parsed.named) {
|
|
2655
|
+
const importedName = item.replace(/^type\s+/u, "").split(/\s+as\s+/iu)[0]?.trim() ?? "";
|
|
2656
|
+
if (knownTypes.has(importedName)) typeNamed.push(item.replace(/^type\s+/u, "").trim());
|
|
2657
|
+
else valueNamed.push(item);
|
|
2658
|
+
}
|
|
2659
|
+
if (typeNamed.length === 0) return full;
|
|
2660
|
+
const lines = [];
|
|
2661
|
+
if (parsed.defaultImport && valueNamed.length > 0) {
|
|
2662
|
+
lines.push(`import ${parsed.defaultImport}, { ${valueNamed.join(", ")} } from ${quote}${specifier}${quote};`);
|
|
2663
|
+
} else if (parsed.defaultImport) {
|
|
2664
|
+
lines.push(`import ${parsed.defaultImport} from ${quote}${specifier}${quote};`);
|
|
2665
|
+
} else if (valueNamed.length > 0) {
|
|
2666
|
+
lines.push(`import { ${valueNamed.join(", ")} } from ${quote}${specifier}${quote};`);
|
|
2667
|
+
}
|
|
2668
|
+
lines.push(`import type { ${typeNamed.join(", ")} } from ${quote}${specifier}${quote};`);
|
|
2669
|
+
return lines.join("\n");
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
function splitImportClause(clauseRaw) {
|
|
2673
|
+
const clause = clauseRaw.trim();
|
|
2674
|
+
const namedMatch = clause.match(/\{([\s\S]*)\}$/u);
|
|
2675
|
+
if (!namedMatch) return void 0;
|
|
2676
|
+
const beforeNamed = clause.slice(0, namedMatch.index).replace(/,\s*$/u, "").trim();
|
|
2677
|
+
const named = (namedMatch[1] ?? "").split(",").map((item) => item.trim()).filter(Boolean);
|
|
2678
|
+
return {
|
|
2679
|
+
defaultImport: beforeNamed || void 0,
|
|
2680
|
+
named
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2572
2683
|
|
|
2573
2684
|
// src/core/architecture.ts
|
|
2574
2685
|
var COMPLEXITY_SURFACES = [
|
|
@@ -2760,7 +2871,7 @@ function validateArchitectureContract(modules, steps, language, demand) {
|
|
|
2760
2871
|
}
|
|
2761
2872
|
allTestPaths.add(testPath);
|
|
2762
2873
|
}
|
|
2763
|
-
const owners = codeSteps.filter((step) => module.sourcePaths.every((
|
|
2874
|
+
const owners = codeSteps.filter((step) => module.sourcePaths.every((path31) => pathCoveredByOutputs(path31, step.outputs))).sort((a, b) => (stepIndex.get(a.id) ?? 0) - (stepIndex.get(b.id) ?? 0));
|
|
2764
2875
|
if (owners.length === 0) {
|
|
2765
2876
|
issues.push({
|
|
2766
2877
|
message: `${module.id} must map all sourcePaths to a CODE macro step; found 0.`
|
|
@@ -2790,7 +2901,7 @@ function validateArchitectureContract(modules, steps, language, demand) {
|
|
|
2790
2901
|
ownedModules.push(module);
|
|
2791
2902
|
modulesByCodeOwner.set(codeOwner.id, ownedModules);
|
|
2792
2903
|
const matchingTests = testSteps.filter(
|
|
2793
|
-
(step) => module.testPaths.some((
|
|
2904
|
+
(step) => module.testPaths.some((path31) => pathCoveredByOutputs(path31, step.outputs))
|
|
2794
2905
|
);
|
|
2795
2906
|
if (matchingTests.length === 0) {
|
|
2796
2907
|
issues.push({ message: `${module.id} testPaths are not produced by any MODULE_TEST step.` });
|
|
@@ -2847,8 +2958,8 @@ function flattenSubTasks(step) {
|
|
|
2847
2958
|
}
|
|
2848
2959
|
return out;
|
|
2849
2960
|
}
|
|
2850
|
-
function pathCoveredByOutputs(
|
|
2851
|
-
const normalizedPath = normalizePath(
|
|
2961
|
+
function pathCoveredByOutputs(path31, outputs) {
|
|
2962
|
+
const normalizedPath = normalizePath(path31);
|
|
2852
2963
|
return outputs.some((output) => {
|
|
2853
2964
|
const normalizedOutput = normalizePath(output);
|
|
2854
2965
|
return normalizedPath === normalizedOutput || normalizedPath.startsWith(`${normalizedOutput}/`);
|
|
@@ -2858,14 +2969,14 @@ function missingArchitectureDocumentTokens(content, modules) {
|
|
|
2858
2969
|
const required = modules.flatMap((module) => [module.id, ...module.sourcePaths, ...module.testPaths]);
|
|
2859
2970
|
return [...new Set(required)].filter((token) => !content.includes(token));
|
|
2860
2971
|
}
|
|
2861
|
-
function isSourcePath(
|
|
2862
|
-
return
|
|
2972
|
+
function isSourcePath(path31, extensions) {
|
|
2973
|
+
return path31.startsWith("src/") && extensions.some((extension) => path31.endsWith(extension));
|
|
2863
2974
|
}
|
|
2864
|
-
function isTestPath(
|
|
2865
|
-
return
|
|
2975
|
+
function isTestPath(path31, extensions) {
|
|
2976
|
+
return path31.startsWith("tests/") && extensions.some((extension) => path31.endsWith(extension));
|
|
2866
2977
|
}
|
|
2867
|
-
function normalizePath(
|
|
2868
|
-
return
|
|
2978
|
+
function normalizePath(path31) {
|
|
2979
|
+
return path31.replace(/\\/g, "/").replace(/^\.\//u, "").replace(/\/+$/u, "");
|
|
2869
2980
|
}
|
|
2870
2981
|
function transitivelyDependsOn(step, targetId, byId) {
|
|
2871
2982
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -3398,12 +3509,12 @@ function parseLoadedPlan(json) {
|
|
|
3398
3509
|
}
|
|
3399
3510
|
async function savePlan(planPath, plan) {
|
|
3400
3511
|
PlanSchema.parse(plan);
|
|
3401
|
-
await
|
|
3402
|
-
await
|
|
3512
|
+
await fs2.mkdir(path3.dirname(planPath), { recursive: true });
|
|
3513
|
+
await fs2.writeFile(planPath, JSON.stringify(plan, null, 2) + "\n", "utf8");
|
|
3403
3514
|
}
|
|
3404
3515
|
async function loadPlanTarget(inputPath) {
|
|
3405
3516
|
const requestedPath = path3.resolve(inputPath);
|
|
3406
|
-
const raw = await
|
|
3517
|
+
const raw = await fs2.readFile(requestedPath, "utf8");
|
|
3407
3518
|
const json = JSON.parse(raw);
|
|
3408
3519
|
const phasePlanResult = PhasePlanSchema.safeParse(json);
|
|
3409
3520
|
if (phasePlanResult.success) {
|
|
@@ -3413,7 +3524,7 @@ async function loadPlanTarget(inputPath) {
|
|
|
3413
3524
|
throw new Error(`phasePlan ${requestedPath} has no planPath for current phase ${phasePlan.currentPhaseId}`);
|
|
3414
3525
|
}
|
|
3415
3526
|
const planPath = path3.resolve(path3.dirname(requestedPath), phase.planPath);
|
|
3416
|
-
const loaded2 = parseLoadedPlan(JSON.parse(await
|
|
3527
|
+
const loaded2 = parseLoadedPlan(JSON.parse(await fs2.readFile(planPath, "utf8")));
|
|
3417
3528
|
return {
|
|
3418
3529
|
plan: loaded2.plan,
|
|
3419
3530
|
planPath,
|
|
@@ -3518,7 +3629,7 @@ async function findProjectFile(workspace) {
|
|
|
3518
3629
|
const ws = path4.resolve(workspace);
|
|
3519
3630
|
let entries;
|
|
3520
3631
|
try {
|
|
3521
|
-
entries = await
|
|
3632
|
+
entries = await fs3.readdir(ws, { withFileTypes: true });
|
|
3522
3633
|
} catch {
|
|
3523
3634
|
return void 0;
|
|
3524
3635
|
}
|
|
@@ -3568,8 +3679,8 @@ async function updateProjectFile(opts) {
|
|
|
3568
3679
|
history: nextHistory
|
|
3569
3680
|
};
|
|
3570
3681
|
XCompilerProjectFileSchema.parse(data);
|
|
3571
|
-
await
|
|
3572
|
-
await
|
|
3682
|
+
await fs3.mkdir(path4.dirname(filePath), { recursive: true });
|
|
3683
|
+
await fs3.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
3573
3684
|
return filePath;
|
|
3574
3685
|
}
|
|
3575
3686
|
function buildProjectProgress(plan) {
|
|
@@ -3612,7 +3723,7 @@ function buildProjectProgress(plan) {
|
|
|
3612
3723
|
}
|
|
3613
3724
|
async function readExistingProjectFile(filePath) {
|
|
3614
3725
|
try {
|
|
3615
|
-
const raw = await
|
|
3726
|
+
const raw = await fs3.readFile(filePath, "utf8");
|
|
3616
3727
|
return XCompilerProjectFileSchema.parse(JSON.parse(raw));
|
|
3617
3728
|
} catch {
|
|
3618
3729
|
return void 0;
|
|
@@ -3642,10 +3753,10 @@ function assertProjectFileExtension(filePath) {
|
|
|
3642
3753
|
|
|
3643
3754
|
// src/runtime/build.ts
|
|
3644
3755
|
import path13 from "path";
|
|
3645
|
-
import { promises as
|
|
3756
|
+
import { promises as fs12 } from "fs";
|
|
3646
3757
|
|
|
3647
3758
|
// src/config/config.ts
|
|
3648
|
-
import { promises as
|
|
3759
|
+
import { promises as fs4 } from "fs";
|
|
3649
3760
|
import path5 from "path";
|
|
3650
3761
|
import YAML from "yaml";
|
|
3651
3762
|
import { z as z4 } from "zod";
|
|
@@ -3711,6 +3822,73 @@ var ProviderSchema = z4.object({
|
|
|
3711
3822
|
think: z4.boolean().optional()
|
|
3712
3823
|
});
|
|
3713
3824
|
var LocaleSchema = z4.enum(["en", "zh"]);
|
|
3825
|
+
var TargetLanguageSchema = z4.enum(["python", "typescript"]);
|
|
3826
|
+
var SandboxModeSchema = z4.enum(["subprocess", "docker", "firejail"]);
|
|
3827
|
+
var SandboxLimitsSchema = z4.object({
|
|
3828
|
+
cpu: z4.number().positive().default(1),
|
|
3829
|
+
memory_mb: z4.number().int().positive().default(1024),
|
|
3830
|
+
wall_seconds: z4.number().int().positive().default(60),
|
|
3831
|
+
/**
|
|
3832
|
+
* Sandbox network policy.
|
|
3833
|
+
* - `off` — no network at all (`docker --network none`).
|
|
3834
|
+
* - `download-only` — outbound traffic allowed, no inbound port publishing.
|
|
3835
|
+
* - `pypi-only` — legacy value; rejected at sandbox creation rather than silently
|
|
3836
|
+
* allowing unrestricted outbound traffic.
|
|
3837
|
+
* - `full` — outbound + every port in `expose_ports` is published
|
|
3838
|
+
* to `127.0.0.1` so host-side tests can reach the app.
|
|
3839
|
+
*/
|
|
3840
|
+
network: z4.enum(["off", "pypi-only", "download-only", "full"]).default("download-only"),
|
|
3841
|
+
/** Container ports to publish to 127.0.0.1 when `network=full`. */
|
|
3842
|
+
expose_ports: z4.array(z4.number().int().min(1).max(65535)).default([])
|
|
3843
|
+
}).default({
|
|
3844
|
+
cpu: 1,
|
|
3845
|
+
memory_mb: 1024,
|
|
3846
|
+
wall_seconds: 60,
|
|
3847
|
+
network: "download-only",
|
|
3848
|
+
expose_ports: []
|
|
3849
|
+
});
|
|
3850
|
+
var LocalSandboxSchema = z4.object({
|
|
3851
|
+
sandbox_dir: z4.string().min(1).optional(),
|
|
3852
|
+
python_bin: z4.string().min(1).optional(),
|
|
3853
|
+
inherit_env: z4.boolean().optional(),
|
|
3854
|
+
limits: SandboxLimitsSchema
|
|
3855
|
+
}).default(() => ({ limits: defaultSandboxLimits() }));
|
|
3856
|
+
var DockerSandboxSchema = z4.object({
|
|
3857
|
+
image: z4.string().default("python:3.11-slim"),
|
|
3858
|
+
workdir: z4.string().default("/workspace"),
|
|
3859
|
+
pull: z4.boolean().default(false),
|
|
3860
|
+
docker_bin: z4.string().default("docker"),
|
|
3861
|
+
extra_run_args: z4.array(z4.string()).default([]),
|
|
3862
|
+
sandbox_dir: z4.string().min(1).optional(),
|
|
3863
|
+
limits: SandboxLimitsSchema
|
|
3864
|
+
}).default({
|
|
3865
|
+
image: "python:3.11-slim",
|
|
3866
|
+
workdir: "/workspace",
|
|
3867
|
+
pull: false,
|
|
3868
|
+
docker_bin: "docker",
|
|
3869
|
+
extra_run_args: [],
|
|
3870
|
+
limits: defaultSandboxLimits()
|
|
3871
|
+
});
|
|
3872
|
+
var LanguageSandboxSchema = z4.object({
|
|
3873
|
+
mode: SandboxModeSchema.default("subprocess"),
|
|
3874
|
+
local: LocalSandboxSchema,
|
|
3875
|
+
docker: DockerSandboxSchema
|
|
3876
|
+
}).default(() => ({
|
|
3877
|
+
mode: "subprocess",
|
|
3878
|
+
local: { limits: defaultSandboxLimits() },
|
|
3879
|
+
docker: {
|
|
3880
|
+
image: "python:3.11-slim",
|
|
3881
|
+
workdir: "/workspace",
|
|
3882
|
+
pull: false,
|
|
3883
|
+
docker_bin: "docker",
|
|
3884
|
+
extra_run_args: [],
|
|
3885
|
+
limits: defaultSandboxLimits()
|
|
3886
|
+
}
|
|
3887
|
+
}));
|
|
3888
|
+
var SandboxesSchema = z4.object({
|
|
3889
|
+
python: LanguageSandboxSchema.optional(),
|
|
3890
|
+
typescript: LanguageSandboxSchema.optional()
|
|
3891
|
+
}).default({});
|
|
3714
3892
|
var LlmSchema = z4.object({
|
|
3715
3893
|
default: z4.string(),
|
|
3716
3894
|
providers: z4.record(z4.string(), ProviderSchema),
|
|
@@ -3718,7 +3896,7 @@ var LlmSchema = z4.object({
|
|
|
3718
3896
|
* 角色 → provider 数组的映射。
|
|
3719
3897
|
* 兼容旧格式:单字符串 `Coder: ollama_code` 自动归一化为 `[ollama_code]`。
|
|
3720
3898
|
* 数组形式 `Coder: [ollama_code, openai]` 表示该角色的候选 LLM 池;
|
|
3721
|
-
* 实际选择顺序由
|
|
3899
|
+
* 实际选择顺序由 ScoreStore 有效评分降序决定;有效评分为用户覆盖优先,否则使用动态评分。
|
|
3722
3900
|
*/
|
|
3723
3901
|
roles: z4.record(z4.string(), z4.union([z4.string(), z4.array(z4.string())])).default({}).transform((obj) => {
|
|
3724
3902
|
const out = {};
|
|
@@ -3732,9 +3910,8 @@ var LlmSchema = z4.object({
|
|
|
3732
3910
|
/** 可选:按角色指定 fallback 链(覆盖全局) */
|
|
3733
3911
|
role_fallbacks: z4.record(z4.string(), z4.array(z4.string())).default({}),
|
|
3734
3912
|
/**
|
|
3735
|
-
* Provider
|
|
3736
|
-
*
|
|
3737
|
-
* 用户手工设置评分 = 0 表示禁用;运行时自动评分只在 0.1~1.0 范围内调整。
|
|
3913
|
+
* Provider 兼容初值。运行时动态评分由 ScoreStore 写入 llm_scores.yaml;
|
|
3914
|
+
* 用户手动覆盖应写入 llm_scores_user.yaml。旧配置里显式 0 仍表示用户禁用。
|
|
3738
3915
|
*/
|
|
3739
3916
|
scores: z4.record(z4.string(), z4.number().min(0)).default({}),
|
|
3740
3917
|
/**
|
|
@@ -3754,65 +3931,107 @@ var LlmSchema = z4.object({
|
|
|
3754
3931
|
});
|
|
3755
3932
|
}
|
|
3756
3933
|
});
|
|
3934
|
+
var AgentSchema = z4.object({
|
|
3935
|
+
/** @deprecated Target project language is inferred from topic/baseline. Kept for legacy configs only. */
|
|
3936
|
+
language: TargetLanguageSchema.optional(),
|
|
3937
|
+
max_steps: z4.number().int().positive().default(50),
|
|
3938
|
+
max_debug_retries: z4.number().int().positive().default(3),
|
|
3939
|
+
/** Debugger 滑动窗口的硬上限(默认 = max(max_debug_retries*4, 10))。 */
|
|
3940
|
+
max_debug_retries_cap: z4.number().int().positive().optional(),
|
|
3941
|
+
max_rounds_per_step: z4.number().int().positive().default(6),
|
|
3942
|
+
max_debug_rounds_per_step: z4.number().int().positive().optional(),
|
|
3943
|
+
max_edit_lines_per_step: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
3944
|
+
max_write_chunk_bytes: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
3945
|
+
/** @deprecated Use agent.sandboxes.<language>.mode. */
|
|
3946
|
+
sandbox: SandboxModeSchema.optional(),
|
|
3947
|
+
/** @deprecated Use agent.sandboxes.<language>.<local|docker>.limits. */
|
|
3948
|
+
sandbox_limits: SandboxLimitsSchema.optional(),
|
|
3949
|
+
/** @deprecated Use agent.sandboxes.<language>.docker. */
|
|
3950
|
+
sandbox_docker: DockerSandboxSchema.optional(),
|
|
3951
|
+
sandboxes: SandboxesSchema
|
|
3952
|
+
}).transform((agent) => {
|
|
3953
|
+
const legacyLanguage = agent.language ?? "python";
|
|
3954
|
+
const legacyMode = agent.sandbox ?? "subprocess";
|
|
3955
|
+
const legacyLimits = agent.sandbox_limits ?? defaultSandboxLimits();
|
|
3956
|
+
const defaults = {
|
|
3957
|
+
python: defaultLanguageSandbox("python", legacyMode, legacyLimits),
|
|
3958
|
+
typescript: defaultLanguageSandbox("typescript", legacyMode, legacyLimits)
|
|
3959
|
+
};
|
|
3960
|
+
const sandboxes = {
|
|
3961
|
+
python: mergeLanguageSandbox(
|
|
3962
|
+
defaults.python,
|
|
3963
|
+
agent.sandboxes.python,
|
|
3964
|
+
legacyLanguage === "python" ? agent.sandbox_docker : void 0
|
|
3965
|
+
),
|
|
3966
|
+
typescript: mergeLanguageSandbox(
|
|
3967
|
+
defaults.typescript,
|
|
3968
|
+
agent.sandboxes.typescript,
|
|
3969
|
+
legacyLanguage === "typescript" ? agent.sandbox_docker : void 0
|
|
3970
|
+
)
|
|
3971
|
+
};
|
|
3972
|
+
return {
|
|
3973
|
+
...agent,
|
|
3974
|
+
language: legacyLanguage,
|
|
3975
|
+
sandbox: sandboxes.python.mode,
|
|
3976
|
+
sandbox_limits: sandboxes.python.local.limits,
|
|
3977
|
+
sandbox_docker: sandboxes.python.docker,
|
|
3978
|
+
sandboxes
|
|
3979
|
+
};
|
|
3980
|
+
});
|
|
3757
3981
|
var ConfigSchema = z4.object({
|
|
3758
3982
|
/** CLI / prompt locale. Accepts 'en' (default) or 'zh'. */
|
|
3759
3983
|
locale: LocaleSchema.optional(),
|
|
3760
3984
|
/** @deprecated use `locale` instead. Kept as a backwards-compatible alias. */
|
|
3761
3985
|
ui_language: LocaleSchema.optional(),
|
|
3762
3986
|
llm: LlmSchema,
|
|
3763
|
-
agent:
|
|
3764
|
-
language: z4.enum(["python", "typescript"]).default("python"),
|
|
3765
|
-
max_steps: z4.number().int().positive().default(50),
|
|
3766
|
-
max_debug_retries: z4.number().int().positive().default(3),
|
|
3767
|
-
/** Debugger 滑动窗口的硬上限(默认 = max(max_debug_retries*4, 10))。 */
|
|
3768
|
-
max_debug_retries_cap: z4.number().int().positive().optional(),
|
|
3769
|
-
max_rounds_per_step: z4.number().int().positive().default(6),
|
|
3770
|
-
max_debug_rounds_per_step: z4.number().int().positive().optional(),
|
|
3771
|
-
max_edit_lines_per_step: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
3772
|
-
max_write_chunk_bytes: z4.union([z4.literal("auto"), z4.number().int().positive()]).default("auto"),
|
|
3773
|
-
sandbox: z4.enum(["subprocess", "docker", "firejail"]).default("subprocess"),
|
|
3774
|
-
sandbox_limits: z4.object({
|
|
3775
|
-
cpu: z4.number().positive().default(1),
|
|
3776
|
-
memory_mb: z4.number().int().positive().default(1024),
|
|
3777
|
-
wall_seconds: z4.number().int().positive().default(60),
|
|
3778
|
-
/**
|
|
3779
|
-
* Sandbox network policy.
|
|
3780
|
-
* - `off` — no network at all (`docker --network none`).
|
|
3781
|
-
* - `download-only` — outbound traffic allowed, no inbound port publishing
|
|
3782
|
-
* (default; lets python pip-install / fetch web pages).
|
|
3783
|
-
* - `pypi-only` — legacy value; rejected at sandbox creation rather than silently
|
|
3784
|
-
* allowing unrestricted outbound traffic.
|
|
3785
|
-
* - `full` — outbound + every port in `expose_ports` is published
|
|
3786
|
-
* to `127.0.0.1` so host-side tests can reach the app.
|
|
3787
|
-
*/
|
|
3788
|
-
network: z4.enum(["off", "pypi-only", "download-only", "full"]).default("download-only"),
|
|
3789
|
-
/** Container ports to publish to 127.0.0.1 when `network=full`. */
|
|
3790
|
-
expose_ports: z4.array(z4.number().int().min(1).max(65535)).default([])
|
|
3791
|
-
}).default({
|
|
3792
|
-
cpu: 1,
|
|
3793
|
-
memory_mb: 1024,
|
|
3794
|
-
wall_seconds: 60,
|
|
3795
|
-
network: "download-only",
|
|
3796
|
-
expose_ports: []
|
|
3797
|
-
}),
|
|
3798
|
-
sandbox_docker: z4.object({
|
|
3799
|
-
image: z4.string().default("python:3.11-slim"),
|
|
3800
|
-
workdir: z4.string().default("/workspace"),
|
|
3801
|
-
pull: z4.boolean().default(false),
|
|
3802
|
-
docker_bin: z4.string().default("docker"),
|
|
3803
|
-
extra_run_args: z4.array(z4.string()).default([])
|
|
3804
|
-
}).default({
|
|
3805
|
-
image: "python:3.11-slim",
|
|
3806
|
-
workdir: "/workspace",
|
|
3807
|
-
pull: false,
|
|
3808
|
-
docker_bin: "docker",
|
|
3809
|
-
extra_run_args: []
|
|
3810
|
-
})
|
|
3811
|
-
})
|
|
3987
|
+
agent: AgentSchema
|
|
3812
3988
|
}).transform(({ locale, ui_language, ...rest }) => ({
|
|
3813
3989
|
locale: locale ?? ui_language ?? "en",
|
|
3814
3990
|
...rest
|
|
3815
3991
|
}));
|
|
3992
|
+
function defaultSandboxLimits() {
|
|
3993
|
+
return {
|
|
3994
|
+
cpu: 1,
|
|
3995
|
+
memory_mb: 1024,
|
|
3996
|
+
wall_seconds: 60,
|
|
3997
|
+
network: "download-only",
|
|
3998
|
+
expose_ports: []
|
|
3999
|
+
};
|
|
4000
|
+
}
|
|
4001
|
+
function defaultLanguageSandbox(language, mode, limits) {
|
|
4002
|
+
return {
|
|
4003
|
+
mode,
|
|
4004
|
+
local: {
|
|
4005
|
+
sandbox_dir: `.sandbox/${language}`,
|
|
4006
|
+
limits: { ...limits, expose_ports: [...limits.expose_ports ?? []] }
|
|
4007
|
+
},
|
|
4008
|
+
docker: {
|
|
4009
|
+
image: language === "typescript" ? "node:24-slim" : "python:3.11-slim",
|
|
4010
|
+
workdir: "/workspace",
|
|
4011
|
+
pull: false,
|
|
4012
|
+
docker_bin: "docker",
|
|
4013
|
+
extra_run_args: [],
|
|
4014
|
+
sandbox_dir: `.sandbox/${language}`,
|
|
4015
|
+
limits: { ...limits, expose_ports: [...limits.expose_ports ?? []] }
|
|
4016
|
+
}
|
|
4017
|
+
};
|
|
4018
|
+
}
|
|
4019
|
+
function mergeLanguageSandbox(defaults, override, legacyDocker) {
|
|
4020
|
+
const dockerOverride = legacyDocker ?? override?.docker;
|
|
4021
|
+
return {
|
|
4022
|
+
mode: override?.mode ?? defaults.mode,
|
|
4023
|
+
local: {
|
|
4024
|
+
...defaults.local,
|
|
4025
|
+
...override?.local ?? {},
|
|
4026
|
+
limits: override?.local?.limits ?? defaults.local.limits
|
|
4027
|
+
},
|
|
4028
|
+
docker: {
|
|
4029
|
+
...defaults.docker,
|
|
4030
|
+
...dockerOverride ?? {},
|
|
4031
|
+
limits: dockerOverride?.limits ?? defaults.docker.limits
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
3816
4035
|
function getXCompilerPath() {
|
|
3817
4036
|
const env = xcEnv("PATH");
|
|
3818
4037
|
if (env && env.trim()) return path5.resolve(env.trim());
|
|
@@ -3834,7 +4053,7 @@ async function loadConfigWithPath(explicitPath) {
|
|
|
3834
4053
|
for (const abs of candidates) {
|
|
3835
4054
|
tried.push(abs);
|
|
3836
4055
|
try {
|
|
3837
|
-
const raw = await
|
|
4056
|
+
const raw = await fs4.readFile(abs, "utf8");
|
|
3838
4057
|
const expanded = expandEnv(raw);
|
|
3839
4058
|
const data = YAML.parse(expanded);
|
|
3840
4059
|
return { config: ConfigSchema.parse(data), path: abs };
|
|
@@ -3876,6 +4095,11 @@ var LOOP_REPEATS = 12;
|
|
|
3876
4095
|
var LOOP_MIN_LEN = 1500;
|
|
3877
4096
|
var LOOP_MIN_WINDOW = 96;
|
|
3878
4097
|
var LOOP_MAX_PERIOD = 256;
|
|
4098
|
+
var TEXT_LOOP_MIN_LEN = 6e3;
|
|
4099
|
+
var TEXT_LOOP_TAIL = 14e3;
|
|
4100
|
+
var TEXT_LOOP_SAMPLE_LEN = 48;
|
|
4101
|
+
var TEXT_LOOP_STRIDE = 8;
|
|
4102
|
+
var TEXT_LOOP_REPEATS = 5;
|
|
3879
4103
|
function detectCyclicTokenLoop(aggregate) {
|
|
3880
4104
|
if (aggregate.length < LOOP_MIN_LEN) return false;
|
|
3881
4105
|
const maxPeriod = Math.min(LOOP_MAX_PERIOD, Math.floor(aggregate.length / LOOP_REPEATS));
|
|
@@ -3897,6 +4121,35 @@ function detectCyclicTokenLoop(aggregate) {
|
|
|
3897
4121
|
}
|
|
3898
4122
|
return false;
|
|
3899
4123
|
}
|
|
4124
|
+
function detectRepeatedTextLoop(aggregate) {
|
|
4125
|
+
if (aggregate.length < TEXT_LOOP_MIN_LEN) return false;
|
|
4126
|
+
const tail = aggregate.slice(-TEXT_LOOP_TAIL).replace(/\s+/gu, " ").trim();
|
|
4127
|
+
if (tail.length < TEXT_LOOP_MIN_LEN) return false;
|
|
4128
|
+
const seen = /* @__PURE__ */ new Map();
|
|
4129
|
+
for (let i = 0; i + TEXT_LOOP_SAMPLE_LEN <= tail.length; i += TEXT_LOOP_STRIDE) {
|
|
4130
|
+
const sample = tail.slice(i, i + TEXT_LOOP_SAMPLE_LEN);
|
|
4131
|
+
if (!isHighSignalLoopSample(sample)) continue;
|
|
4132
|
+
const prior = seen.get(sample);
|
|
4133
|
+
if (!prior) {
|
|
4134
|
+
seen.set(sample, { count: 1, first: i, last: i });
|
|
4135
|
+
continue;
|
|
4136
|
+
}
|
|
4137
|
+
const next = { count: prior.count + 1, first: prior.first, last: i };
|
|
4138
|
+
seen.set(sample, next);
|
|
4139
|
+
if (next.count >= TEXT_LOOP_REPEATS && next.last - next.first >= TEXT_LOOP_SAMPLE_LEN * 3) {
|
|
4140
|
+
return true;
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
return false;
|
|
4144
|
+
}
|
|
4145
|
+
function isHighSignalLoopSample(sample) {
|
|
4146
|
+
const chars = [...sample];
|
|
4147
|
+
const signalChars = chars.filter((ch) => /[\p{L}\p{N}]/u.test(ch));
|
|
4148
|
+
if (signalChars.length < 20) return false;
|
|
4149
|
+
if (signalChars.length / Math.max(1, chars.length) < 0.4) return false;
|
|
4150
|
+
const unique = new Set(signalChars.map((ch) => ch.toLowerCase())).size;
|
|
4151
|
+
return unique >= 10;
|
|
4152
|
+
}
|
|
3900
4153
|
var RepeatTokenDetector = class {
|
|
3901
4154
|
constructor(threshold = 40) {
|
|
3902
4155
|
this.threshold = threshold;
|
|
@@ -4075,6 +4328,12 @@ function streamPostNdjson(url, body, watchdog, onLine, shouldStopWhen) {
|
|
|
4075
4328
|
);
|
|
4076
4329
|
return;
|
|
4077
4330
|
}
|
|
4331
|
+
if (detectRepeatedTextLoop(aggregate)) {
|
|
4332
|
+
fail(
|
|
4333
|
+
new Error("detected repeated text loop in stream (semantic tail repetition); aborting")
|
|
4334
|
+
);
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4078
4337
|
}
|
|
4079
4338
|
if (obj.error) {
|
|
4080
4339
|
fail(new Error(`Ollama error: ${obj.error}`));
|
|
@@ -4103,6 +4362,10 @@ function streamPostNdjson(url, body, watchdog, onLine, shouldStopWhen) {
|
|
|
4103
4362
|
fail(new Error("detected token loop in stream; aborting"));
|
|
4104
4363
|
return;
|
|
4105
4364
|
}
|
|
4365
|
+
if (detectRepeatedTextLoop(aggregate)) {
|
|
4366
|
+
fail(new Error("detected repeated text loop in stream; aborting"));
|
|
4367
|
+
return;
|
|
4368
|
+
}
|
|
4106
4369
|
}
|
|
4107
4370
|
});
|
|
4108
4371
|
res.on("end", () => {
|
|
@@ -4373,6 +4636,9 @@ var OpenAIClient = class {
|
|
|
4373
4636
|
if (detectCyclicTokenLoop(aggregate)) {
|
|
4374
4637
|
throw new Error("detected cyclic token loop in OpenAI stream (periodic tail); aborting");
|
|
4375
4638
|
}
|
|
4639
|
+
if (detectRepeatedTextLoop(aggregate)) {
|
|
4640
|
+
throw new Error("detected repeated text loop in OpenAI stream (semantic tail repetition); aborting");
|
|
4641
|
+
}
|
|
4376
4642
|
if (maxOutputChars > 0 && expectsJsonObject && aggregate.length > maxOutputChars && hasInvalidJsonPrefix(aggregate)) {
|
|
4377
4643
|
throw new Error(`OpenAI stream exceeded ${maxOutputChars} chars without a valid JSON prefix; aborting`);
|
|
4378
4644
|
}
|
|
@@ -4909,22 +5175,28 @@ async function reportRoleModelAdvice(router, audit, reporter = (message) => {
|
|
|
4909
5175
|
}
|
|
4910
5176
|
|
|
4911
5177
|
// src/llm/scores.ts
|
|
4912
|
-
import { promises as
|
|
5178
|
+
import { promises as fs5 } from "fs";
|
|
4913
5179
|
import path6 from "path";
|
|
4914
5180
|
import YAML2 from "yaml";
|
|
4915
5181
|
var CLUSTER_PROVIDER_TAG = "cluster";
|
|
5182
|
+
var LLM_DYNAMIC_SCORES_FILE = "llm_scores.yaml";
|
|
5183
|
+
var LLM_USER_SCORES_FILE = "llm_scores_user.yaml";
|
|
4916
5184
|
var ScoreStore = class _ScoreStore {
|
|
4917
5185
|
constructor(configPath, initial = {}, audit, opts = {}) {
|
|
4918
5186
|
this.audit = audit;
|
|
4919
|
-
|
|
5187
|
+
const configDir = path6.dirname(configPath);
|
|
5188
|
+
this.sidecarPath = path6.join(configDir, LLM_DYNAMIC_SCORES_FILE);
|
|
5189
|
+
this.userScoresPath = path6.join(configDir, LLM_USER_SCORES_FILE);
|
|
4920
5190
|
this.clusterProviders = new Set(opts.clusterProviderNames ?? []);
|
|
4921
5191
|
const bounds = normalizeClusterBounds(opts.clusterScoreMin, opts.clusterScoreMax);
|
|
4922
5192
|
this.clusterMin = bounds.min;
|
|
4923
5193
|
this.clusterMax = bounds.max;
|
|
4924
5194
|
for (const [k, v] of Object.entries(initial)) {
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
5195
|
+
if (v === _ScoreStore.DISABLED) {
|
|
5196
|
+
this.userScores.set(k, _ScoreStore.DISABLED);
|
|
5197
|
+
} else {
|
|
5198
|
+
this.dynamicScores.set(k, this.clampDynamic(k, v));
|
|
5199
|
+
}
|
|
4928
5200
|
}
|
|
4929
5201
|
}
|
|
4930
5202
|
audit;
|
|
@@ -4936,24 +5208,28 @@ var ScoreStore = class _ScoreStore {
|
|
|
4936
5208
|
static CLUSTER_MAX = 0.5;
|
|
4937
5209
|
static DECAY = 0.5;
|
|
4938
5210
|
static BOOST = 0.1;
|
|
4939
|
-
|
|
4940
|
-
|
|
5211
|
+
dynamicScores = /* @__PURE__ */ new Map();
|
|
5212
|
+
userScores = /* @__PURE__ */ new Map();
|
|
4941
5213
|
clusterProviders;
|
|
4942
5214
|
clusterMin;
|
|
4943
5215
|
clusterMax;
|
|
4944
5216
|
dirty = false;
|
|
4945
5217
|
writeQueue = Promise.resolve();
|
|
4946
5218
|
sidecarPath;
|
|
4947
|
-
|
|
5219
|
+
userScoresPath;
|
|
5220
|
+
/** 异步加载动态 sidecar 与用户覆盖文件;失败/不存在不抛错,使用 ctor 提供的初值。 */
|
|
4948
5221
|
async load() {
|
|
5222
|
+
await this.loadDynamicScores();
|
|
5223
|
+
await this.loadUserScores();
|
|
5224
|
+
}
|
|
5225
|
+
async loadDynamicScores() {
|
|
4949
5226
|
try {
|
|
4950
|
-
const raw = await
|
|
5227
|
+
const raw = await fs5.readFile(this.sidecarPath, "utf8");
|
|
4951
5228
|
const parsed = YAML2.parse(raw);
|
|
4952
5229
|
if (parsed && typeof parsed === "object") {
|
|
4953
5230
|
for (const [k, v] of Object.entries(parsed)) {
|
|
4954
5231
|
if (typeof v === "number" && Number.isFinite(v)) {
|
|
4955
|
-
|
|
4956
|
-
this.scores.set(k, this.clampDynamic(k, v));
|
|
5232
|
+
this.dynamicScores.set(k, this.clampPersistedDynamic(k, v));
|
|
4957
5233
|
}
|
|
4958
5234
|
}
|
|
4959
5235
|
}
|
|
@@ -4966,44 +5242,73 @@ var ScoreStore = class _ScoreStore {
|
|
|
4966
5242
|
}
|
|
4967
5243
|
}
|
|
4968
5244
|
}
|
|
5245
|
+
async loadUserScores() {
|
|
5246
|
+
try {
|
|
5247
|
+
const raw = await fs5.readFile(this.userScoresPath, "utf8");
|
|
5248
|
+
const parsed = YAML2.parse(raw);
|
|
5249
|
+
if (parsed && typeof parsed === "object") {
|
|
5250
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
5251
|
+
if (typeof v === "number" && Number.isFinite(v)) {
|
|
5252
|
+
this.userScores.set(k, this.clampUserScore(v));
|
|
5253
|
+
}
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
} catch (err) {
|
|
5257
|
+
if (err.code !== "ENOENT") {
|
|
5258
|
+
await this.audit?.event("llm.error", t().llm.scoreReadFailed(this.userScoresPath, err.message), {
|
|
5259
|
+
messageId: "llm.user_score_read_failed",
|
|
5260
|
+
sidecarPath: this.userScoresPath
|
|
5261
|
+
});
|
|
5262
|
+
}
|
|
5263
|
+
}
|
|
5264
|
+
}
|
|
4969
5265
|
get(name) {
|
|
4970
|
-
|
|
5266
|
+
if (this.userScores.has(name)) return this.userScores.get(name);
|
|
5267
|
+
return this.dynamicScores.has(name) ? this.dynamicScores.get(name) : this.defaultFor(name);
|
|
4971
5268
|
}
|
|
4972
|
-
/**
|
|
5269
|
+
/** 主动设置动态评分;用户覆盖只从配置或 llm_scores_user.yaml 读取。 */
|
|
4973
5270
|
set(name, value, reason) {
|
|
4974
|
-
if (this.userDisabled.has(name) && value !== _ScoreStore.DISABLED) return;
|
|
4975
5271
|
const v = value === _ScoreStore.DISABLED ? _ScoreStore.DISABLED : this.clampDynamic(name, value);
|
|
4976
|
-
const prev = this.
|
|
5272
|
+
const prev = this.dynamicScores.get(name);
|
|
4977
5273
|
if (prev === v) return;
|
|
4978
|
-
this.
|
|
5274
|
+
this.dynamicScores.set(name, v);
|
|
4979
5275
|
this.dirty = true;
|
|
4980
|
-
void this.audit?.event("llm.score", t().llm.scoreChanged(name, v.toFixed(2), (prev ??
|
|
5276
|
+
void this.audit?.event("llm.score", t().llm.scoreChanged(name, v.toFixed(2), (prev ?? this.defaultFor(name)).toFixed(2)), {
|
|
4981
5277
|
messageId: "llm.score_changed",
|
|
4982
5278
|
provider: name,
|
|
4983
5279
|
score: v,
|
|
4984
|
-
previous: prev ??
|
|
5280
|
+
previous: prev ?? this.defaultFor(name),
|
|
4985
5281
|
reason: reason ?? "set"
|
|
4986
5282
|
});
|
|
4987
5283
|
this.scheduleSave();
|
|
4988
5284
|
}
|
|
4989
5285
|
decay(name, reason) {
|
|
4990
|
-
if (this.
|
|
4991
|
-
|
|
5286
|
+
if (this.isUserDisabled(name)) return;
|
|
5287
|
+
const cur = this.dynamicScore(name);
|
|
5288
|
+
if (cur === _ScoreStore.DISABLED) return;
|
|
5289
|
+
this.set(name, Math.max(this.minFor(name), cur - _ScoreStore.DECAY), reason);
|
|
4992
5290
|
}
|
|
4993
5291
|
boost(name, reason) {
|
|
4994
|
-
|
|
5292
|
+
if (this.isUserDisabled(name)) return;
|
|
5293
|
+
const cur = this.dynamicScore(name);
|
|
4995
5294
|
if (cur === _ScoreStore.DISABLED) return;
|
|
4996
5295
|
if (cur >= this.maxFor(name)) return;
|
|
4997
5296
|
this.set(name, Math.min(this.maxFor(name), cur + _ScoreStore.BOOST), reason);
|
|
4998
5297
|
}
|
|
4999
|
-
/** True only for providers explicitly disabled by user config
|
|
5298
|
+
/** True only for providers explicitly disabled by user config or llm_scores_user.yaml. */
|
|
5000
5299
|
isUserDisabled(name) {
|
|
5001
|
-
return this.
|
|
5300
|
+
return this.userScores.get(name) === _ScoreStore.DISABLED;
|
|
5002
5301
|
}
|
|
5003
|
-
/**
|
|
5302
|
+
/** 动态评分快照(用于持久化、测试与 audit 输出;不包含用户覆盖)。 */
|
|
5004
5303
|
snapshot() {
|
|
5005
5304
|
const out = {};
|
|
5006
|
-
for (const [k, v] of this.
|
|
5305
|
+
for (const [k, v] of this.dynamicScores) out[k] = v;
|
|
5306
|
+
return out;
|
|
5307
|
+
}
|
|
5308
|
+
/** 用户覆盖快照(用于测试/诊断;运行时不会改写用户文件)。 */
|
|
5309
|
+
userSnapshot() {
|
|
5310
|
+
const out = {};
|
|
5311
|
+
for (const [k, v] of this.userScores) out[k] = v;
|
|
5007
5312
|
return out;
|
|
5008
5313
|
}
|
|
5009
5314
|
/** 等待待写入完成(CLI 退出前调用,确保评分已落盘)。 */
|
|
@@ -5026,8 +5331,8 @@ var ScoreStore = class _ScoreStore {
|
|
|
5026
5331
|
${t().llm.scoreFileSemantics}
|
|
5027
5332
|
` + YAML2.stringify(data);
|
|
5028
5333
|
const tmp = `${this.sidecarPath}.tmp`;
|
|
5029
|
-
await
|
|
5030
|
-
await
|
|
5334
|
+
await fs5.writeFile(tmp, yaml, "utf8");
|
|
5335
|
+
await fs5.rename(tmp, this.sidecarPath);
|
|
5031
5336
|
}
|
|
5032
5337
|
defaultFor(name) {
|
|
5033
5338
|
return this.isClusterProvider(name) ? this.clusterMax : _ScoreStore.DEFAULT;
|
|
@@ -5041,10 +5346,8 @@ ${t().llm.scoreFileSemantics}
|
|
|
5041
5346
|
isClusterProvider(name) {
|
|
5042
5347
|
return this.clusterProviders.has(name);
|
|
5043
5348
|
}
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
if (v === _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
|
|
5047
|
-
return this.clampDynamic(name, v);
|
|
5349
|
+
dynamicScore(name) {
|
|
5350
|
+
return this.dynamicScores.has(name) ? this.dynamicScores.get(name) : this.defaultFor(name);
|
|
5048
5351
|
}
|
|
5049
5352
|
clampDynamic(name, v) {
|
|
5050
5353
|
if (!Number.isFinite(v)) return this.defaultFor(name);
|
|
@@ -5053,6 +5356,18 @@ ${t().llm.scoreFileSemantics}
|
|
|
5053
5356
|
if (v > this.maxFor(name)) return this.maxFor(name);
|
|
5054
5357
|
return v;
|
|
5055
5358
|
}
|
|
5359
|
+
clampPersistedDynamic(name, v) {
|
|
5360
|
+
if (!Number.isFinite(v)) return this.defaultFor(name);
|
|
5361
|
+
if (v === _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
|
|
5362
|
+
return this.clampDynamic(name, v);
|
|
5363
|
+
}
|
|
5364
|
+
clampUserScore(v) {
|
|
5365
|
+
if (!Number.isFinite(v)) return _ScoreStore.DEFAULT;
|
|
5366
|
+
if (v <= _ScoreStore.DISABLED) return _ScoreStore.DISABLED;
|
|
5367
|
+
if (v < _ScoreStore.MIN) return _ScoreStore.MIN;
|
|
5368
|
+
if (v > _ScoreStore.MAX) return _ScoreStore.MAX;
|
|
5369
|
+
return v;
|
|
5370
|
+
}
|
|
5056
5371
|
};
|
|
5057
5372
|
function normalizeClusterBounds(rawMin, rawMax) {
|
|
5058
5373
|
const min = clampBound(rawMin ?? ScoreStore.CLUSTER_MIN);
|
|
@@ -5232,7 +5547,7 @@ async function defaultFetchTags(baseUrl, timeoutMs) {
|
|
|
5232
5547
|
}
|
|
5233
5548
|
|
|
5234
5549
|
// src/workspace/workspace.ts
|
|
5235
|
-
import { promises as
|
|
5550
|
+
import { promises as fs6 } from "fs";
|
|
5236
5551
|
import path7 from "path";
|
|
5237
5552
|
var Workspace = class {
|
|
5238
5553
|
constructor(root) {
|
|
@@ -5243,31 +5558,31 @@ var Workspace = class {
|
|
|
5243
5558
|
return path7.resolve(this.root, ...p);
|
|
5244
5559
|
}
|
|
5245
5560
|
async ensure(dir) {
|
|
5246
|
-
await
|
|
5561
|
+
await fs6.mkdir(this.abs(dir), { recursive: true });
|
|
5247
5562
|
}
|
|
5248
5563
|
async writeFile(rel, content) {
|
|
5249
5564
|
const full = this.abs(rel);
|
|
5250
|
-
await
|
|
5251
|
-
await
|
|
5565
|
+
await fs6.mkdir(path7.dirname(full), { recursive: true });
|
|
5566
|
+
await fs6.writeFile(full, content, "utf8");
|
|
5252
5567
|
}
|
|
5253
5568
|
async readFile(rel) {
|
|
5254
|
-
return
|
|
5569
|
+
return fs6.readFile(this.abs(rel), "utf8");
|
|
5255
5570
|
}
|
|
5256
5571
|
async exists(rel) {
|
|
5257
5572
|
try {
|
|
5258
|
-
await
|
|
5573
|
+
await fs6.stat(this.abs(rel));
|
|
5259
5574
|
return true;
|
|
5260
5575
|
} catch {
|
|
5261
5576
|
return false;
|
|
5262
5577
|
}
|
|
5263
5578
|
}
|
|
5264
5579
|
async remove(rel) {
|
|
5265
|
-
await
|
|
5580
|
+
await fs6.rm(this.abs(rel), { recursive: true, force: true });
|
|
5266
5581
|
}
|
|
5267
5582
|
};
|
|
5268
5583
|
|
|
5269
5584
|
// src/workspace/doc_archive.ts
|
|
5270
|
-
import { promises as
|
|
5585
|
+
import { promises as fs7 } from "fs";
|
|
5271
5586
|
import path8 from "path";
|
|
5272
5587
|
async function archiveIfExists(ws, rel, audit) {
|
|
5273
5588
|
const norm = rel.replaceAll("\\", "/");
|
|
@@ -5280,7 +5595,7 @@ async function archiveIfExists(ws, rel, audit) {
|
|
|
5280
5595
|
const target = `docs/history/${base}-${ts}${ext}`;
|
|
5281
5596
|
try {
|
|
5282
5597
|
await ws.ensure("docs/history");
|
|
5283
|
-
await
|
|
5598
|
+
await fs7.rename(ws.abs(norm), ws.abs(target));
|
|
5284
5599
|
await audit?.event("plan.persist", t().audit.documentArchived(norm, target), {
|
|
5285
5600
|
messageId: "audit.document_archived",
|
|
5286
5601
|
from: norm,
|
|
@@ -5482,6 +5797,18 @@ var DOC_PATH_ALIASES = {
|
|
|
5482
5797
|
"docs/integration-test.md": DOC_NAMES.integrationTest,
|
|
5483
5798
|
"docs/module-test.md": DOC_NAMES.moduleTest,
|
|
5484
5799
|
"docs/functional-test.md": DOC_NAMES.functionalTest,
|
|
5800
|
+
"docs/unit-test-plan.md": DOC_NAMES.unitTestPlan,
|
|
5801
|
+
"docs/unit_test_plan.md": DOC_NAMES.unitTestPlan,
|
|
5802
|
+
"docs/tests/unit_test_plan.md": DOC_NAMES.unitTestPlan,
|
|
5803
|
+
"docs/integration-test-plan.md": DOC_NAMES.integrationTestPlan,
|
|
5804
|
+
"docs/integration_test_plan.md": DOC_NAMES.integrationTestPlan,
|
|
5805
|
+
"docs/tests/integration_test_plan.md": DOC_NAMES.integrationTestPlan,
|
|
5806
|
+
"docs/module-test-plan.md": DOC_NAMES.moduleTestPlan,
|
|
5807
|
+
"docs/module_test_plan.md": DOC_NAMES.moduleTestPlan,
|
|
5808
|
+
"docs/tests/module_test_plan.md": DOC_NAMES.moduleTestPlan,
|
|
5809
|
+
"docs/functional-test-plan.md": DOC_NAMES.functionalTestPlan,
|
|
5810
|
+
"docs/functional_test_plan.md": DOC_NAMES.functionalTestPlan,
|
|
5811
|
+
"docs/tests/functional_test_plan.md": DOC_NAMES.functionalTestPlan,
|
|
5485
5812
|
"docs/quick-start.md": DOC_NAMES.quickstart,
|
|
5486
5813
|
"docs/quick_start.md": DOC_NAMES.quickstart,
|
|
5487
5814
|
"docs/quickstart.md": DOC_NAMES.quickstart,
|
|
@@ -5854,11 +6181,11 @@ function renderDebugSuggestions(sugs) {
|
|
|
5854
6181
|
var normalizePythonRequirements = calibratePythonRequirements;
|
|
5855
6182
|
|
|
5856
6183
|
// src/core/incremental.ts
|
|
5857
|
-
import { promises as
|
|
6184
|
+
import { promises as fs9 } from "fs";
|
|
5858
6185
|
import path10 from "path";
|
|
5859
6186
|
|
|
5860
6187
|
// src/core/project_memory.ts
|
|
5861
|
-
import { promises as
|
|
6188
|
+
import { promises as fs8 } from "fs";
|
|
5862
6189
|
import path9 from "path";
|
|
5863
6190
|
var PROJECT_MEMORY_PATH = ".xcompiler/project_memory.json";
|
|
5864
6191
|
async function refreshProjectMemory(ws, opts = {}) {
|
|
@@ -6016,7 +6343,7 @@ async function defaultPlanMetadataPath(root) {
|
|
|
6016
6343
|
}
|
|
6017
6344
|
async function fileExists(filePath) {
|
|
6018
6345
|
try {
|
|
6019
|
-
await
|
|
6346
|
+
await fs8.stat(filePath);
|
|
6020
6347
|
return true;
|
|
6021
6348
|
} catch {
|
|
6022
6349
|
return false;
|
|
@@ -6044,7 +6371,7 @@ async function readWorkspaceText(ws, rel, maxChars) {
|
|
|
6044
6371
|
}
|
|
6045
6372
|
async function readAbsoluteText(fullPath, maxChars) {
|
|
6046
6373
|
try {
|
|
6047
|
-
const raw = await
|
|
6374
|
+
const raw = await fs8.readFile(fullPath, "utf8");
|
|
6048
6375
|
return truncate(raw, maxChars);
|
|
6049
6376
|
} catch {
|
|
6050
6377
|
return "";
|
|
@@ -6066,7 +6393,7 @@ async function walk(abs, rel, out, depth) {
|
|
|
6066
6393
|
if (depth > 6 || out.length >= 160) return;
|
|
6067
6394
|
let entries;
|
|
6068
6395
|
try {
|
|
6069
|
-
entries = await
|
|
6396
|
+
entries = await fs8.readdir(abs, { withFileTypes: true });
|
|
6070
6397
|
} catch {
|
|
6071
6398
|
return;
|
|
6072
6399
|
}
|
|
@@ -6293,20 +6620,20 @@ function extractImportedStems(text) {
|
|
|
6293
6620
|
function normalizeImportStem(value) {
|
|
6294
6621
|
return value.replace(/^\.\//u, "").replace(/^\.\.\//u, "").replace(/\.(js|ts|tsx|py)$/u, "").replace(/\/index$/u, "").trim();
|
|
6295
6622
|
}
|
|
6296
|
-
function extractContractsFromDoc(
|
|
6623
|
+
function extractContractsFromDoc(path31, text) {
|
|
6297
6624
|
const contracts = [];
|
|
6298
6625
|
for (const rawLine of text.split("\n")) {
|
|
6299
6626
|
const line = rawLine.replace(/^[-*#\d.\s]+/u, "").trim();
|
|
6300
6627
|
if (line.length < 18) continue;
|
|
6301
6628
|
const lower = line.toLowerCase();
|
|
6302
6629
|
if (INVARIANT_RE.test(lower)) {
|
|
6303
|
-
contracts.push({ kind: "invariant", subject:
|
|
6630
|
+
contracts.push({ kind: "invariant", subject: path31, path: path31, detail: trimContractLine(line) });
|
|
6304
6631
|
} else if (LIMITATION_RE.test(lower)) {
|
|
6305
|
-
contracts.push({ kind: "limitation", subject:
|
|
6632
|
+
contracts.push({ kind: "limitation", subject: path31, path: path31, detail: trimContractLine(line) });
|
|
6306
6633
|
} else if (EXTENSION_RE.test(lower)) {
|
|
6307
|
-
contracts.push({ kind: "extension-point", subject:
|
|
6634
|
+
contracts.push({ kind: "extension-point", subject: path31, path: path31, detail: trimContractLine(line) });
|
|
6308
6635
|
} else if (INTEGRATION_RE.test(lower)) {
|
|
6309
|
-
contracts.push({ kind: "integration", subject:
|
|
6636
|
+
contracts.push({ kind: "integration", subject: path31, path: path31, detail: trimContractLine(line) });
|
|
6310
6637
|
}
|
|
6311
6638
|
}
|
|
6312
6639
|
return contracts;
|
|
@@ -6331,7 +6658,7 @@ function dedupContracts(contracts) {
|
|
|
6331
6658
|
}
|
|
6332
6659
|
|
|
6333
6660
|
// src/audit/audit.ts
|
|
6334
|
-
import { promises as
|
|
6661
|
+
import { promises as fs10, appendFileSync, mkdirSync, existsSync, writeFileSync } from "fs";
|
|
6335
6662
|
import { createHash } from "crypto";
|
|
6336
6663
|
import path11 from "path";
|
|
6337
6664
|
var AuditLogger = class {
|
|
@@ -6488,8 +6815,8 @@ var AuditLogger = class {
|
|
|
6488
6815
|
});
|
|
6489
6816
|
}
|
|
6490
6817
|
/**
|
|
6491
|
-
* 记录一轮 Executor
|
|
6492
|
-
* 写入 jsonl + markdown
|
|
6818
|
+
* 记录一轮 Executor 执行摘要:简短 intent、issue 处理方案、计划调用的 actions、是否完成。
|
|
6819
|
+
* 写入 jsonl + markdown 折叠块,交付时可追溯每轮决策和动作。
|
|
6493
6820
|
*/
|
|
6494
6821
|
async executorTurn(stepId, role, round, payload) {
|
|
6495
6822
|
const storedPayload = protectAuditContent(payload, this.contentMode);
|
|
@@ -6561,8 +6888,8 @@ ${t().audit.processLogPreamble}
|
|
|
6561
6888
|
}
|
|
6562
6889
|
async appendMd(text) {
|
|
6563
6890
|
this.mdQueue = this.mdQueue.then(
|
|
6564
|
-
() =>
|
|
6565
|
-
() =>
|
|
6891
|
+
() => fs10.appendFile(this.mdAbs, text, "utf8"),
|
|
6892
|
+
() => fs10.appendFile(this.mdAbs, text, "utf8")
|
|
6566
6893
|
).catch((err) => {
|
|
6567
6894
|
console.warn(t().audit.markdownAppendFailed(err.message));
|
|
6568
6895
|
});
|
|
@@ -6644,7 +6971,7 @@ function redactText(value) {
|
|
|
6644
6971
|
}
|
|
6645
6972
|
|
|
6646
6973
|
// src/core/lock.ts
|
|
6647
|
-
import { promises as
|
|
6974
|
+
import { promises as fs11, unlinkSync } from "fs";
|
|
6648
6975
|
import path12 from "path";
|
|
6649
6976
|
import os from "os";
|
|
6650
6977
|
var LockError = class extends Error {
|
|
@@ -6671,7 +6998,7 @@ function isAlive(pid) {
|
|
|
6671
6998
|
}
|
|
6672
6999
|
async function readLock(file) {
|
|
6673
7000
|
try {
|
|
6674
|
-
const raw = await
|
|
7001
|
+
const raw = await fs11.readFile(file, "utf8");
|
|
6675
7002
|
return JSON.parse(raw);
|
|
6676
7003
|
} catch {
|
|
6677
7004
|
return null;
|
|
@@ -6679,7 +7006,7 @@ async function readLock(file) {
|
|
|
6679
7006
|
}
|
|
6680
7007
|
async function acquireLock(workspace, command, options = {}) {
|
|
6681
7008
|
const file = lockPath(workspace);
|
|
6682
|
-
await
|
|
7009
|
+
await fs11.mkdir(path12.dirname(file), { recursive: true });
|
|
6683
7010
|
const info = {
|
|
6684
7011
|
pid: process.pid,
|
|
6685
7012
|
host: os.hostname(),
|
|
@@ -6689,7 +7016,7 @@ async function acquireLock(workspace, command, options = {}) {
|
|
|
6689
7016
|
const payload = JSON.stringify(info, null, 2);
|
|
6690
7017
|
const tryCreate = async () => {
|
|
6691
7018
|
try {
|
|
6692
|
-
const fh = await
|
|
7019
|
+
const fh = await fs11.open(file, "wx");
|
|
6693
7020
|
await fh.writeFile(payload, "utf8");
|
|
6694
7021
|
await fh.close();
|
|
6695
7022
|
return true;
|
|
@@ -6702,14 +7029,14 @@ async function acquireLock(workspace, command, options = {}) {
|
|
|
6702
7029
|
return makeReleaser(file);
|
|
6703
7030
|
}
|
|
6704
7031
|
if (options.force) {
|
|
6705
|
-
await
|
|
7032
|
+
await fs11.writeFile(file, payload, "utf8");
|
|
6706
7033
|
return makeReleaser(file);
|
|
6707
7034
|
}
|
|
6708
7035
|
const existing = await readLock(file);
|
|
6709
7036
|
if (existing) {
|
|
6710
7037
|
const sameHost = existing.host === info.host;
|
|
6711
7038
|
if (sameHost && !isAlive(existing.pid)) {
|
|
6712
|
-
await
|
|
7039
|
+
await fs11.writeFile(file, payload, "utf8");
|
|
6713
7040
|
return makeReleaser(file);
|
|
6714
7041
|
}
|
|
6715
7042
|
throw new LockError(
|
|
@@ -6718,7 +7045,7 @@ async function acquireLock(workspace, command, options = {}) {
|
|
|
6718
7045
|
existing
|
|
6719
7046
|
);
|
|
6720
7047
|
}
|
|
6721
|
-
await
|
|
7048
|
+
await fs11.writeFile(file, payload, "utf8");
|
|
6722
7049
|
return makeReleaser(file);
|
|
6723
7050
|
}
|
|
6724
7051
|
function makeReleaser(file) {
|
|
@@ -6727,7 +7054,7 @@ function makeReleaser(file) {
|
|
|
6727
7054
|
if (released) return;
|
|
6728
7055
|
released = true;
|
|
6729
7056
|
try {
|
|
6730
|
-
await
|
|
7057
|
+
await fs11.unlink(file);
|
|
6731
7058
|
} catch {
|
|
6732
7059
|
}
|
|
6733
7060
|
};
|
|
@@ -6752,7 +7079,7 @@ function makeReleaser(file) {
|
|
|
6752
7079
|
}
|
|
6753
7080
|
|
|
6754
7081
|
// src/version.ts
|
|
6755
|
-
var XCOMPILER_VERSION = "0.2.
|
|
7082
|
+
var XCOMPILER_VERSION = "0.2.4";
|
|
6756
7083
|
var XCOMPILER_PLUGIN_API_VERSION = 1;
|
|
6757
7084
|
|
|
6758
7085
|
// src/plugins/compatibility.ts
|
|
@@ -7054,10 +7381,10 @@ function runtimeResult(io, command, status, data) {
|
|
|
7054
7381
|
}
|
|
7055
7382
|
|
|
7056
7383
|
// src/runtime/run.ts
|
|
7057
|
-
import
|
|
7384
|
+
import path28 from "path";
|
|
7058
7385
|
|
|
7059
7386
|
// src/workspace/git.ts
|
|
7060
|
-
import { promises as
|
|
7387
|
+
import { promises as fs13 } from "fs";
|
|
7061
7388
|
import path14 from "path";
|
|
7062
7389
|
import { simpleGit } from "simple-git";
|
|
7063
7390
|
var RUNTIME_EXCLUDE_PATTERNS = [
|
|
@@ -7109,14 +7436,14 @@ var GitService = class {
|
|
|
7109
7436
|
const excludePath = this.ws.abs(".git/info/exclude");
|
|
7110
7437
|
let current2 = "";
|
|
7111
7438
|
try {
|
|
7112
|
-
current2 = await
|
|
7439
|
+
current2 = await fs13.readFile(excludePath, "utf8");
|
|
7113
7440
|
} catch {
|
|
7114
7441
|
return;
|
|
7115
7442
|
}
|
|
7116
7443
|
const missing = RUNTIME_EXCLUDE_PATTERNS.filter((pattern) => !current2.split(/\r?\n/u).includes(pattern));
|
|
7117
7444
|
if (missing.length === 0) return;
|
|
7118
7445
|
const prefix = current2.endsWith("\n") ? "\n" : "\n\n";
|
|
7119
|
-
await
|
|
7446
|
+
await fs13.appendFile(
|
|
7120
7447
|
excludePath,
|
|
7121
7448
|
`${prefix}# XCompiler runtime artifacts
|
|
7122
7449
|
${missing.join("\n")}
|
|
@@ -7161,7 +7488,7 @@ import { existsSync as existsSync2, readFileSync } from "fs";
|
|
|
7161
7488
|
|
|
7162
7489
|
// src/sandbox/subprocess.ts
|
|
7163
7490
|
import { spawn } from "child_process";
|
|
7164
|
-
import { promises as
|
|
7491
|
+
import { promises as fs14 } from "fs";
|
|
7165
7492
|
import path15 from "path";
|
|
7166
7493
|
import crypto from "crypto";
|
|
7167
7494
|
|
|
@@ -7215,6 +7542,9 @@ function formatCommand(cmd, argv) {
|
|
|
7215
7542
|
}
|
|
7216
7543
|
|
|
7217
7544
|
// src/sandbox/subprocess.ts
|
|
7545
|
+
var INSTALL_IDLE_TIMEOUT_FLOOR_MS = 15 * 6e4;
|
|
7546
|
+
var INSTALL_IDLE_TIMEOUT_MULTIPLIER = 10;
|
|
7547
|
+
var INSTALL_PROGRESS_CHECK_INTERVAL_MS = 15e3;
|
|
7218
7548
|
var SubprocessSandbox = class {
|
|
7219
7549
|
constructor(opts) {
|
|
7220
7550
|
this.opts = opts;
|
|
@@ -7268,12 +7598,13 @@ var SubprocessSandbox = class {
|
|
|
7268
7598
|
* Python → requirements.txt (venv + pip);TypeScript → package.json (npm install)。
|
|
7269
7599
|
*/
|
|
7270
7600
|
async build(manifestFile) {
|
|
7271
|
-
await
|
|
7601
|
+
await fs14.mkdir(this.sandboxAbs, { recursive: true });
|
|
7272
7602
|
if (this.opts.inheritEnv === false) {
|
|
7273
7603
|
await Promise.all([
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7604
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "home"), { recursive: true }),
|
|
7605
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "tmp"), { recursive: true }),
|
|
7606
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "npm-cache"), { recursive: true }),
|
|
7607
|
+
fs14.mkdir(path15.join(this.sandboxAbs, "pip-cache"), { recursive: true })
|
|
7277
7608
|
]);
|
|
7278
7609
|
}
|
|
7279
7610
|
if (this.language === "typescript") {
|
|
@@ -7283,10 +7614,10 @@ var SubprocessSandbox = class {
|
|
|
7283
7614
|
}
|
|
7284
7615
|
async buildPython(requirementsTxt) {
|
|
7285
7616
|
const reqAbs = this.opts.ws.abs(requirementsTxt);
|
|
7286
|
-
const reqContent = await
|
|
7617
|
+
const reqContent = await fs14.readFile(reqAbs, "utf8").catch(() => "");
|
|
7287
7618
|
const sig = crypto.createHash("sha256").update(reqContent).digest("hex");
|
|
7288
|
-
const cached = await
|
|
7289
|
-
const venvExists = await
|
|
7619
|
+
const cached = await fs14.readFile(this.cacheFile, "utf8").catch(() => "");
|
|
7620
|
+
const venvExists = await fs14.stat(this.pythonInVenv).then(() => true).catch(() => false);
|
|
7290
7621
|
if (venvExists && cached === sig) {
|
|
7291
7622
|
return { rebuilt: false, reason: "cache hit" };
|
|
7292
7623
|
}
|
|
@@ -7303,7 +7634,7 @@ var SubprocessSandbox = class {
|
|
|
7303
7634
|
}
|
|
7304
7635
|
}
|
|
7305
7636
|
const pyVenv = this.pythonInVenv;
|
|
7306
|
-
const pyVenvExists = await
|
|
7637
|
+
const pyVenvExists = await fs14.stat(pyVenv).then(() => true).catch(() => false);
|
|
7307
7638
|
if (!pyVenvExists) {
|
|
7308
7639
|
throw new Error(`venv python missing after creation: ${pyVenv} (install system package python3-venv / python3-virtualenv)`);
|
|
7309
7640
|
}
|
|
@@ -7320,17 +7651,26 @@ Install on the host: apt-get install python3-venv python3-pip or yum install
|
|
|
7320
7651
|
}
|
|
7321
7652
|
}
|
|
7322
7653
|
if (reqContent.trim().length > 0) {
|
|
7654
|
+
const progressWatch = createInstallProgressWatch(
|
|
7655
|
+
[this.venvAbs, path15.join(this.sandboxAbs, "pip-cache")],
|
|
7656
|
+
this.opts.limits,
|
|
7657
|
+
"pip install"
|
|
7658
|
+
);
|
|
7659
|
+
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.command("subprocess", `pip install -r ${reqAbs}`), {
|
|
7660
|
+
messageId: "sandbox.command",
|
|
7661
|
+
cwd: this.opts.ws.root,
|
|
7662
|
+
progressIdleTimeoutMs: progressWatch.idleTimeoutMs,
|
|
7663
|
+
progressPaths: progressWatch.paths
|
|
7664
|
+
});
|
|
7323
7665
|
const r = await execRaw(pyVenv, ["-m", "pip", "install", "-r", reqAbs, "--quiet", "--disable-pip-version-check"], {
|
|
7324
|
-
|
|
7666
|
+
env: this.baseEnvironment(),
|
|
7667
|
+
progressWatch
|
|
7325
7668
|
});
|
|
7326
7669
|
if (r.exitCode !== 0) {
|
|
7327
|
-
throw new Error(
|
|
7328
|
-
`pip install failed (venv=${this.venvAbs}, requirements=${reqAbs}):
|
|
7329
|
-
${r.stderr || r.stdout}`
|
|
7330
|
-
);
|
|
7670
|
+
throw new Error(formatExecFailure(`pip install failed (venv=${this.venvAbs}, requirements=${reqAbs})`, r));
|
|
7331
7671
|
}
|
|
7332
7672
|
}
|
|
7333
|
-
await
|
|
7673
|
+
await fs14.writeFile(this.cacheFile, sig, "utf8");
|
|
7334
7674
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.subprocessBuilt(!!reqContent), {
|
|
7335
7675
|
messageId: "sandbox.subprocess_built"
|
|
7336
7676
|
});
|
|
@@ -7338,29 +7678,39 @@ ${r.stderr || r.stdout}`
|
|
|
7338
7678
|
}
|
|
7339
7679
|
async buildNode(manifestFile) {
|
|
7340
7680
|
const pkgAbs = this.opts.ws.abs(manifestFile);
|
|
7341
|
-
const pkgContent = await
|
|
7681
|
+
const pkgContent = await fs14.readFile(pkgAbs, "utf8").catch(() => "");
|
|
7342
7682
|
if (!pkgContent) {
|
|
7343
7683
|
return { rebuilt: false, reason: "no package.json yet" };
|
|
7344
7684
|
}
|
|
7345
7685
|
const lockAbs = this.opts.ws.abs("package-lock.json");
|
|
7346
|
-
const lockContent = await
|
|
7686
|
+
const lockContent = await fs14.readFile(lockAbs, "utf8").catch(() => "");
|
|
7347
7687
|
const sig = crypto.createHash("sha256").update(pkgContent + "\n" + lockContent).digest("hex");
|
|
7348
|
-
const cached = await
|
|
7349
|
-
const modulesExist = await
|
|
7688
|
+
const cached = await fs14.readFile(this.cacheFile, "utf8").catch(() => "");
|
|
7689
|
+
const modulesExist = await fs14.stat(this.opts.ws.abs("node_modules")).then(() => true).catch(() => false);
|
|
7350
7690
|
if (modulesExist && cached === sig) {
|
|
7351
7691
|
return { rebuilt: false, reason: "cache hit" };
|
|
7352
7692
|
}
|
|
7353
7693
|
const installArgs = lockContent.trim() ? ["ci", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--ignore-scripts", "--no-audit", "--no-fund"];
|
|
7694
|
+
const progressWatch = createInstallProgressWatch(
|
|
7695
|
+
[this.opts.ws.abs("node_modules"), path15.join(this.sandboxAbs, "npm-cache")],
|
|
7696
|
+
this.opts.limits,
|
|
7697
|
+
"npm install"
|
|
7698
|
+
);
|
|
7699
|
+
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.command("subprocess", `npm ${installArgs.join(" ")}`), {
|
|
7700
|
+
messageId: "sandbox.command",
|
|
7701
|
+
cwd: this.opts.ws.root,
|
|
7702
|
+
progressIdleTimeoutMs: progressWatch.idleTimeoutMs,
|
|
7703
|
+
progressPaths: progressWatch.paths
|
|
7704
|
+
});
|
|
7354
7705
|
const r = await execRaw("npm", installArgs, {
|
|
7355
7706
|
cwd: this.opts.ws.root,
|
|
7356
7707
|
env: this.baseEnvironment(),
|
|
7357
|
-
|
|
7708
|
+
progressWatch
|
|
7358
7709
|
});
|
|
7359
7710
|
if (r.exitCode !== 0) {
|
|
7360
|
-
throw new Error(`npm dependency install failed (cwd=${this.opts.ws.root})
|
|
7361
|
-
${r.stderr || r.stdout}`);
|
|
7711
|
+
throw new Error(formatExecFailure(`npm dependency install failed (cwd=${this.opts.ws.root})`, r));
|
|
7362
7712
|
}
|
|
7363
|
-
await
|
|
7713
|
+
await fs14.writeFile(this.cacheFile, sig, "utf8");
|
|
7364
7714
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.subprocessNodeBuilt, {
|
|
7365
7715
|
messageId: "sandbox.subprocess_node_built"
|
|
7366
7716
|
});
|
|
@@ -7384,9 +7734,11 @@ ${r.stderr || r.stdout}`);
|
|
|
7384
7734
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.command("subprocess", `${cmd} ${argv.join(" ")}`), {
|
|
7385
7735
|
messageId: "sandbox.command",
|
|
7386
7736
|
cwd,
|
|
7387
|
-
timeoutMs
|
|
7737
|
+
timeoutMs,
|
|
7738
|
+
progressIdleTimeoutMs: extra?.progressWatch?.idleTimeoutMs,
|
|
7739
|
+
progressPaths: extra?.progressWatch?.paths
|
|
7388
7740
|
});
|
|
7389
|
-
const r = await execRaw(cmd, argv, { cwd, env, timeoutMs });
|
|
7741
|
+
const r = await execRaw(cmd, argv, { cwd, env, timeoutMs, progressWatch: extra?.progressWatch });
|
|
7390
7742
|
return r;
|
|
7391
7743
|
}
|
|
7392
7744
|
baseEnvironment() {
|
|
@@ -7420,16 +7772,55 @@ ${r.stderr || r.stdout}`);
|
|
|
7420
7772
|
}
|
|
7421
7773
|
/** 安装额外依赖(不会写入依赖清单,需要由调用方自行回写)。 */
|
|
7422
7774
|
async installDeps(packages) {
|
|
7775
|
+
const progressWatch = createInstallProgressWatch(
|
|
7776
|
+
[
|
|
7777
|
+
this.language === "typescript" ? this.opts.ws.abs("node_modules") : this.venvAbs,
|
|
7778
|
+
path15.join(this.sandboxAbs, this.language === "typescript" ? "npm-cache" : "pip-cache")
|
|
7779
|
+
],
|
|
7780
|
+
this.opts.limits,
|
|
7781
|
+
this.language === "typescript" ? "npm install" : "pip install"
|
|
7782
|
+
);
|
|
7423
7783
|
if (this.language === "typescript") {
|
|
7424
|
-
return this.exec("npm", ["install", "--no-audit", "--no-fund", ...packages]
|
|
7784
|
+
return this.exec("npm", ["install", "--no-audit", "--no-fund", ...packages], {
|
|
7785
|
+
timeoutMs: 0,
|
|
7786
|
+
progressWatch
|
|
7787
|
+
});
|
|
7425
7788
|
}
|
|
7426
|
-
return this.exec(this.pythonInVenv, ["-m", "pip", "install", ...packages, "--quiet", "--disable-pip-version-check"]
|
|
7789
|
+
return this.exec(this.pythonInVenv, ["-m", "pip", "install", ...packages, "--quiet", "--disable-pip-version-check"], {
|
|
7790
|
+
timeoutMs: 0,
|
|
7791
|
+
progressWatch
|
|
7792
|
+
});
|
|
7427
7793
|
}
|
|
7428
7794
|
};
|
|
7429
7795
|
function sanitizeVenvName(name) {
|
|
7430
7796
|
const cleaned = name.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
7431
7797
|
return cleaned.length > 0 ? cleaned : "venv";
|
|
7432
7798
|
}
|
|
7799
|
+
function createInstallProgressWatch(paths, limits, label = "dependency install") {
|
|
7800
|
+
const idleTimeoutMs = Math.max(
|
|
7801
|
+
limits.wall_seconds * 1e3 * INSTALL_IDLE_TIMEOUT_MULTIPLIER,
|
|
7802
|
+
INSTALL_IDLE_TIMEOUT_FLOOR_MS
|
|
7803
|
+
);
|
|
7804
|
+
return {
|
|
7805
|
+
paths: dedupPaths(paths),
|
|
7806
|
+
idleTimeoutMs,
|
|
7807
|
+
checkIntervalMs: INSTALL_PROGRESS_CHECK_INTERVAL_MS,
|
|
7808
|
+
label
|
|
7809
|
+
};
|
|
7810
|
+
}
|
|
7811
|
+
function formatExecFailure(label, r) {
|
|
7812
|
+
const parts = [
|
|
7813
|
+
`${label} (exit=${r.exitCode}, timedOut=${r.timedOut ? "true" : "false"}, durationMs=${r.durationMs}${r.timeoutReason ? `, reason=${r.timeoutReason}` : ""}):`
|
|
7814
|
+
];
|
|
7815
|
+
const stdout = clipExecOutput(r.stdout);
|
|
7816
|
+
const stderr = clipExecOutput(r.stderr);
|
|
7817
|
+
if (stdout) parts.push(`stdout:
|
|
7818
|
+
${stdout}`);
|
|
7819
|
+
if (stderr) parts.push(`stderr:
|
|
7820
|
+
${stderr}`);
|
|
7821
|
+
if (!stdout && !stderr) parts.push("(no stdout/stderr captured)");
|
|
7822
|
+
return parts.join("\n");
|
|
7823
|
+
}
|
|
7433
7824
|
async function execRaw(cmd, argv, opts = {}) {
|
|
7434
7825
|
const start = Date.now();
|
|
7435
7826
|
return new Promise((resolve) => {
|
|
@@ -7441,10 +7832,46 @@ async function execRaw(cmd, argv, opts = {}) {
|
|
|
7441
7832
|
let stdout = "";
|
|
7442
7833
|
let stderr = "";
|
|
7443
7834
|
let timedOut = false;
|
|
7835
|
+
let timeoutReason;
|
|
7836
|
+
let settled = false;
|
|
7837
|
+
let progressTimer = null;
|
|
7838
|
+
let progressStopped = false;
|
|
7444
7839
|
const t2 = opts.timeoutMs ? setTimeout(() => {
|
|
7445
7840
|
timedOut = true;
|
|
7841
|
+
timeoutReason = `wall-clock timeout after ${opts.timeoutMs}ms`;
|
|
7446
7842
|
child.kill("SIGKILL");
|
|
7447
7843
|
}, opts.timeoutMs) : null;
|
|
7844
|
+
const finish = (result) => {
|
|
7845
|
+
if (settled) return;
|
|
7846
|
+
settled = true;
|
|
7847
|
+
progressStopped = true;
|
|
7848
|
+
if (t2) clearTimeout(t2);
|
|
7849
|
+
if (progressTimer) clearTimeout(progressTimer);
|
|
7850
|
+
resolve(result);
|
|
7851
|
+
};
|
|
7852
|
+
if (opts.progressWatch?.paths.length) {
|
|
7853
|
+
const watch = opts.progressWatch;
|
|
7854
|
+
let lastProgressAt = start;
|
|
7855
|
+
let lastObservedSize = null;
|
|
7856
|
+
const checkProgress = async () => {
|
|
7857
|
+
if (progressStopped) return;
|
|
7858
|
+
const observedAt = Date.now();
|
|
7859
|
+
const currentSize = await directoryTreeSize(watch.paths);
|
|
7860
|
+
if (progressStopped) return;
|
|
7861
|
+
if (lastObservedSize !== null && currentSize > lastObservedSize) {
|
|
7862
|
+
lastProgressAt = observedAt;
|
|
7863
|
+
}
|
|
7864
|
+
lastObservedSize = currentSize;
|
|
7865
|
+
if (observedAt - lastProgressAt >= watch.idleTimeoutMs) {
|
|
7866
|
+
timedOut = true;
|
|
7867
|
+
timeoutReason = `${watch.label ?? "process"} progress idle for ${watch.idleTimeoutMs}ms; watched paths did not grow: ${watch.paths.join(", ")}`;
|
|
7868
|
+
child.kill("SIGKILL");
|
|
7869
|
+
return;
|
|
7870
|
+
}
|
|
7871
|
+
progressTimer = setTimeout(checkProgress, watch.checkIntervalMs ?? INSTALL_PROGRESS_CHECK_INTERVAL_MS);
|
|
7872
|
+
};
|
|
7873
|
+
progressTimer = setTimeout(checkProgress, watch.checkIntervalMs ?? INSTALL_PROGRESS_CHECK_INTERVAL_MS);
|
|
7874
|
+
}
|
|
7448
7875
|
child.stdout.on("data", (b) => {
|
|
7449
7876
|
stdout += b.toString();
|
|
7450
7877
|
});
|
|
@@ -7452,31 +7879,69 @@ async function execRaw(cmd, argv, opts = {}) {
|
|
|
7452
7879
|
stderr += b.toString();
|
|
7453
7880
|
});
|
|
7454
7881
|
child.on("error", (err) => {
|
|
7455
|
-
|
|
7456
|
-
resolve({
|
|
7882
|
+
finish({
|
|
7457
7883
|
exitCode: -1,
|
|
7458
7884
|
stdout,
|
|
7459
7885
|
stderr: stderr + `
|
|
7460
7886
|
[spawn error] ${err.message}`,
|
|
7461
7887
|
timedOut,
|
|
7888
|
+
...timeoutReason ? { timeoutReason } : {},
|
|
7462
7889
|
durationMs: Date.now() - start
|
|
7463
7890
|
});
|
|
7464
7891
|
});
|
|
7465
7892
|
child.on("close", (code) => {
|
|
7466
|
-
|
|
7467
|
-
resolve({
|
|
7893
|
+
finish({
|
|
7468
7894
|
exitCode: code ?? -1,
|
|
7469
7895
|
stdout,
|
|
7470
7896
|
stderr,
|
|
7471
7897
|
timedOut,
|
|
7898
|
+
...timeoutReason ? { timeoutReason } : {},
|
|
7472
7899
|
durationMs: Date.now() - start
|
|
7473
7900
|
});
|
|
7474
7901
|
});
|
|
7475
7902
|
});
|
|
7476
7903
|
}
|
|
7904
|
+
async function directoryTreeSize(paths) {
|
|
7905
|
+
let total = 0;
|
|
7906
|
+
for (const p of dedupPaths(paths)) {
|
|
7907
|
+
total += await pathTreeSize(p);
|
|
7908
|
+
}
|
|
7909
|
+
return total;
|
|
7910
|
+
}
|
|
7911
|
+
async function pathTreeSize(abs) {
|
|
7912
|
+
let stat;
|
|
7913
|
+
try {
|
|
7914
|
+
stat = await fs14.lstat(abs);
|
|
7915
|
+
} catch {
|
|
7916
|
+
return 0;
|
|
7917
|
+
}
|
|
7918
|
+
if (!stat.isDirectory()) {
|
|
7919
|
+
return stat.isFile() ? stat.size : 0;
|
|
7920
|
+
}
|
|
7921
|
+
let total = stat.size;
|
|
7922
|
+
let entries;
|
|
7923
|
+
try {
|
|
7924
|
+
entries = await fs14.readdir(abs, { withFileTypes: true });
|
|
7925
|
+
} catch {
|
|
7926
|
+
return total;
|
|
7927
|
+
}
|
|
7928
|
+
for (const entry of entries) {
|
|
7929
|
+
total += await pathTreeSize(path15.join(abs, entry.name));
|
|
7930
|
+
}
|
|
7931
|
+
return total;
|
|
7932
|
+
}
|
|
7933
|
+
function dedupPaths(paths) {
|
|
7934
|
+
return Array.from(new Set(paths.map((p) => path15.resolve(p)).filter(Boolean)));
|
|
7935
|
+
}
|
|
7936
|
+
function clipExecOutput(text, max = 6e3) {
|
|
7937
|
+
const normalized = String(text ?? "").trim();
|
|
7938
|
+
if (normalized.length <= max) return normalized;
|
|
7939
|
+
return `${normalized.slice(0, max)}
|
|
7940
|
+
...[truncated ${normalized.length - max} chars]`;
|
|
7941
|
+
}
|
|
7477
7942
|
|
|
7478
7943
|
// src/sandbox/docker.ts
|
|
7479
|
-
import { promises as
|
|
7944
|
+
import { promises as fs15 } from "fs";
|
|
7480
7945
|
import path16 from "path";
|
|
7481
7946
|
import crypto2 from "crypto";
|
|
7482
7947
|
var DockerSandbox = class {
|
|
@@ -7486,7 +7951,7 @@ var DockerSandbox = class {
|
|
|
7486
7951
|
throw new Error(t().system.unsupportedPypiOnlyNetwork);
|
|
7487
7952
|
}
|
|
7488
7953
|
this.language = opts.language ?? "python";
|
|
7489
|
-
this.image = opts.image ?? (this.language === "typescript" ? "node:
|
|
7954
|
+
this.image = opts.image ?? (this.language === "typescript" ? "node:24-slim" : "python:3.11-slim");
|
|
7490
7955
|
this.workdir = opts.workdir ?? "/workspace";
|
|
7491
7956
|
this.sandboxRel = (opts.sandboxDir ?? ".sandbox").replaceAll("\\", "/");
|
|
7492
7957
|
this.cacheRel = `${this.sandboxRel}/${this.language === "typescript" ? "package.sha256" : "requirements.sha256"}`;
|
|
@@ -7535,7 +8000,7 @@ var DockerSandbox = class {
|
|
|
7535
8000
|
async build(manifestFile) {
|
|
7536
8001
|
await this.assertDocker();
|
|
7537
8002
|
const sandboxAbs = this.opts.ws.abs(this.sandboxRel);
|
|
7538
|
-
await
|
|
8003
|
+
await fs15.mkdir(sandboxAbs, { recursive: true });
|
|
7539
8004
|
if (this.language === "typescript") {
|
|
7540
8005
|
return this.buildNode(manifestFile ?? "package.json");
|
|
7541
8006
|
}
|
|
@@ -7544,11 +8009,11 @@ var DockerSandbox = class {
|
|
|
7544
8009
|
async buildPython(requirementsTxt) {
|
|
7545
8010
|
const sandboxAbs = this.opts.ws.abs(this.sandboxRel);
|
|
7546
8011
|
const reqAbs = this.opts.ws.abs(requirementsTxt);
|
|
7547
|
-
const reqContent = await
|
|
8012
|
+
const reqContent = await fs15.readFile(reqAbs, "utf8").catch(() => "");
|
|
7548
8013
|
const sig = crypto2.createHash("sha256").update(this.image + "\n" + reqContent).digest("hex");
|
|
7549
8014
|
const cacheAbs = this.opts.ws.abs(this.cacheRel);
|
|
7550
|
-
const cached = await
|
|
7551
|
-
const venvExists = await
|
|
8015
|
+
const cached = await fs15.readFile(cacheAbs, "utf8").catch(() => "");
|
|
8016
|
+
const venvExists = await fs15.stat(path16.join(sandboxAbs, this.venvName, "bin", "python")).then(() => true).catch(() => false);
|
|
7552
8017
|
if (venvExists && cached === sig) {
|
|
7553
8018
|
return { rebuilt: false, reason: "cache hit" };
|
|
7554
8019
|
}
|
|
@@ -7560,6 +8025,11 @@ var DockerSandbox = class {
|
|
|
7560
8025
|
}
|
|
7561
8026
|
const reqInContainer = `${this.workdir}/${requirementsTxt}`;
|
|
7562
8027
|
const installCmd = reqContent.trim().length > 0 ? `python -m venv ${this.venvInContainer} && ${this.pipInContainer} install --quiet -r ${reqInContainer}` : `python -m venv ${this.venvInContainer}`;
|
|
8028
|
+
const progressWatch = createInstallProgressWatch(
|
|
8029
|
+
[path16.join(sandboxAbs, this.venvName)],
|
|
8030
|
+
this.opts.limits,
|
|
8031
|
+
"docker pip install"
|
|
8032
|
+
);
|
|
7563
8033
|
const r = await execRaw(
|
|
7564
8034
|
this.dockerBin,
|
|
7565
8035
|
[
|
|
@@ -7575,15 +8045,12 @@ var DockerSandbox = class {
|
|
|
7575
8045
|
"-lc",
|
|
7576
8046
|
installCmd
|
|
7577
8047
|
],
|
|
7578
|
-
{
|
|
8048
|
+
{ progressWatch }
|
|
7579
8049
|
);
|
|
7580
8050
|
if (r.exitCode !== 0) {
|
|
7581
|
-
throw new Error(
|
|
7582
|
-
`docker sandbox build failed (exit=${r.exitCode}):
|
|
7583
|
-
${r.stderr || r.stdout}`
|
|
7584
|
-
);
|
|
8051
|
+
throw new Error(formatExecFailure(`docker sandbox build failed (image=${this.image}, cwd=${this.opts.ws.root})`, r));
|
|
7585
8052
|
}
|
|
7586
|
-
await
|
|
8053
|
+
await fs15.writeFile(cacheAbs, sig, "utf8");
|
|
7587
8054
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.dockerBuilt(!!reqContent), {
|
|
7588
8055
|
messageId: "sandbox.docker_built",
|
|
7589
8056
|
image: this.image
|
|
@@ -7592,15 +8059,15 @@ ${r.stderr || r.stdout}`
|
|
|
7592
8059
|
}
|
|
7593
8060
|
async buildNode(manifestFile) {
|
|
7594
8061
|
const pkgAbs = this.opts.ws.abs(manifestFile);
|
|
7595
|
-
const pkgContent = await
|
|
8062
|
+
const pkgContent = await fs15.readFile(pkgAbs, "utf8").catch(() => "");
|
|
7596
8063
|
if (!pkgContent) {
|
|
7597
8064
|
return { rebuilt: false, reason: "no package.json yet" };
|
|
7598
8065
|
}
|
|
7599
|
-
const lockContent = await
|
|
8066
|
+
const lockContent = await fs15.readFile(this.opts.ws.abs("package-lock.json"), "utf8").catch(() => "");
|
|
7600
8067
|
const sig = crypto2.createHash("sha256").update(this.image + "\n" + pkgContent + "\n" + lockContent).digest("hex");
|
|
7601
8068
|
const cacheAbs = this.opts.ws.abs(this.cacheRel);
|
|
7602
|
-
const cached = await
|
|
7603
|
-
const modulesExist = await
|
|
8069
|
+
const cached = await fs15.readFile(cacheAbs, "utf8").catch(() => "");
|
|
8070
|
+
const modulesExist = await fs15.stat(this.opts.ws.abs("node_modules")).then(() => true).catch(() => false);
|
|
7604
8071
|
if (modulesExist && cached === sig) {
|
|
7605
8072
|
return { rebuilt: false, reason: "cache hit" };
|
|
7606
8073
|
}
|
|
@@ -7611,6 +8078,11 @@ ${r.stderr || r.stdout}`
|
|
|
7611
8078
|
}
|
|
7612
8079
|
}
|
|
7613
8080
|
const installCommand = lockContent.trim() ? "npm ci --ignore-scripts --no-audit --no-fund" : "npm install --ignore-scripts --no-audit --no-fund";
|
|
8081
|
+
const progressWatch = createInstallProgressWatch(
|
|
8082
|
+
[this.opts.ws.abs("node_modules")],
|
|
8083
|
+
this.opts.limits,
|
|
8084
|
+
"docker npm install"
|
|
8085
|
+
);
|
|
7614
8086
|
const r = await execRaw(
|
|
7615
8087
|
this.dockerBin,
|
|
7616
8088
|
[
|
|
@@ -7626,13 +8098,12 @@ ${r.stderr || r.stdout}`
|
|
|
7626
8098
|
"-lc",
|
|
7627
8099
|
installCommand
|
|
7628
8100
|
],
|
|
7629
|
-
{
|
|
8101
|
+
{ progressWatch }
|
|
7630
8102
|
);
|
|
7631
8103
|
if (r.exitCode !== 0) {
|
|
7632
|
-
throw new Error(`docker sandbox build failed (
|
|
7633
|
-
${r.stderr || r.stdout}`);
|
|
8104
|
+
throw new Error(formatExecFailure(`docker sandbox build failed (image=${this.image}, cwd=${this.opts.ws.root})`, r));
|
|
7634
8105
|
}
|
|
7635
|
-
await
|
|
8106
|
+
await fs15.writeFile(cacheAbs, sig, "utf8");
|
|
7636
8107
|
await this.opts.audit?.event("sandbox.exec", t().sandboxLog.dockerNodeBuilt, {
|
|
7637
8108
|
messageId: "sandbox.docker_node_built",
|
|
7638
8109
|
image: this.image
|
|
@@ -7685,7 +8156,7 @@ ${r.stderr || r.stdout}`);
|
|
|
7685
8156
|
image: this.image,
|
|
7686
8157
|
network: this.opts.limits.network
|
|
7687
8158
|
});
|
|
7688
|
-
return execRaw(this.dockerBin, dockerArgs, { timeoutMs });
|
|
8159
|
+
return execRaw(this.dockerBin, dockerArgs, { timeoutMs, progressWatch: extra?.progressWatch });
|
|
7689
8160
|
}
|
|
7690
8161
|
async runProgram(args, extra) {
|
|
7691
8162
|
if (this.language === "typescript") {
|
|
@@ -7703,10 +8174,23 @@ ${r.stderr || r.stdout}`);
|
|
|
7703
8174
|
return this.exec(this.pythonInContainer, ["-m", "pytest", ...args], extra);
|
|
7704
8175
|
}
|
|
7705
8176
|
async installDeps(packages) {
|
|
8177
|
+
const progressWatch = createInstallProgressWatch(
|
|
8178
|
+
[
|
|
8179
|
+
this.language === "typescript" ? this.opts.ws.abs("node_modules") : this.opts.ws.abs(`${this.sandboxRel}/${this.venvName}`)
|
|
8180
|
+
],
|
|
8181
|
+
this.opts.limits,
|
|
8182
|
+
this.language === "typescript" ? "docker npm install" : "docker pip install"
|
|
8183
|
+
);
|
|
7706
8184
|
if (this.language === "typescript") {
|
|
7707
|
-
return this.exec("npm", ["install", "--no-audit", "--no-fund", ...packages]
|
|
8185
|
+
return this.exec("npm", ["install", "--no-audit", "--no-fund", ...packages], {
|
|
8186
|
+
timeoutMs: 0,
|
|
8187
|
+
progressWatch
|
|
8188
|
+
});
|
|
7708
8189
|
}
|
|
7709
|
-
return this.exec(this.pipInContainer, ["install", "--quiet", ...packages]
|
|
8190
|
+
return this.exec(this.pipInContainer, ["install", "--quiet", ...packages], {
|
|
8191
|
+
timeoutMs: 0,
|
|
8192
|
+
progressWatch
|
|
8193
|
+
});
|
|
7710
8194
|
}
|
|
7711
8195
|
/** 将宿主机绝对路径映射回容器路径;相对路径或 undefined 原样返回(相对则拼到 workdir)。 */
|
|
7712
8196
|
toContainerPath(p) {
|
|
@@ -7737,12 +8221,11 @@ function isRunningInContainer() {
|
|
|
7737
8221
|
}
|
|
7738
8222
|
return false;
|
|
7739
8223
|
}
|
|
7740
|
-
function createSandbox(cfg, ws, audit, language =
|
|
7741
|
-
const
|
|
7742
|
-
const
|
|
7743
|
-
const
|
|
7744
|
-
|
|
7745
|
-
if (cfg.agent.sandbox_limits.network === "pypi-only") {
|
|
8224
|
+
function createSandbox(cfg, ws, audit, language = "python") {
|
|
8225
|
+
const languageSandbox = cfg.agent.sandboxes?.[language] ?? legacyLanguageSandbox(cfg, language);
|
|
8226
|
+
const kind = languageSandbox.mode;
|
|
8227
|
+
const activeLimits = kind === "docker" ? languageSandbox.docker.limits : languageSandbox.local.limits;
|
|
8228
|
+
if (activeLimits.network === "pypi-only") {
|
|
7746
8229
|
throw new Error(t().system.unsupportedPypiOnlyNetwork);
|
|
7747
8230
|
}
|
|
7748
8231
|
if (kind === "docker") {
|
|
@@ -7751,24 +8234,60 @@ function createSandbox(cfg, ws, audit, language = cfg.agent.language) {
|
|
|
7751
8234
|
}
|
|
7752
8235
|
return new DockerSandbox({
|
|
7753
8236
|
ws,
|
|
7754
|
-
limits:
|
|
8237
|
+
limits: languageSandbox.docker.limits,
|
|
7755
8238
|
audit,
|
|
7756
8239
|
language,
|
|
7757
|
-
image,
|
|
7758
|
-
workdir:
|
|
7759
|
-
pull:
|
|
7760
|
-
dockerBin:
|
|
7761
|
-
extraRunArgs:
|
|
8240
|
+
image: languageSandbox.docker.image ?? getLanguageProfile(language).defaultDockerImage,
|
|
8241
|
+
workdir: languageSandbox.docker.workdir,
|
|
8242
|
+
pull: languageSandbox.docker.pull,
|
|
8243
|
+
dockerBin: languageSandbox.docker.docker_bin,
|
|
8244
|
+
extraRunArgs: languageSandbox.docker.extra_run_args,
|
|
8245
|
+
sandboxDir: languageSandbox.docker.sandbox_dir
|
|
7762
8246
|
});
|
|
7763
8247
|
}
|
|
7764
8248
|
if (kind === "firejail") {
|
|
7765
8249
|
throw new Error(t().system.firejailUnsupported);
|
|
7766
8250
|
}
|
|
7767
|
-
return new SubprocessSandbox({
|
|
8251
|
+
return new SubprocessSandbox({
|
|
8252
|
+
ws,
|
|
8253
|
+
limits: languageSandbox.local.limits,
|
|
8254
|
+
audit,
|
|
8255
|
+
language,
|
|
8256
|
+
sandboxDir: languageSandbox.local.sandbox_dir,
|
|
8257
|
+
pythonBin: languageSandbox.local.python_bin,
|
|
8258
|
+
inheritEnv: languageSandbox.local.inherit_env
|
|
8259
|
+
});
|
|
8260
|
+
}
|
|
8261
|
+
function legacyLanguageSandbox(cfg, language) {
|
|
8262
|
+
const legacyAgent = cfg.agent;
|
|
8263
|
+
const limits = legacyAgent.sandbox_limits ?? {
|
|
8264
|
+
cpu: 1,
|
|
8265
|
+
memory_mb: 1024,
|
|
8266
|
+
wall_seconds: 60,
|
|
8267
|
+
network: "download-only",
|
|
8268
|
+
expose_ports: []
|
|
8269
|
+
};
|
|
8270
|
+
const useLegacyDocker = legacyAgent.language === language ? legacyAgent.sandbox_docker : void 0;
|
|
8271
|
+
return {
|
|
8272
|
+
mode: legacyAgent.sandbox ?? "subprocess",
|
|
8273
|
+
local: {
|
|
8274
|
+
sandbox_dir: `.sandbox/${language}`,
|
|
8275
|
+
limits
|
|
8276
|
+
},
|
|
8277
|
+
docker: {
|
|
8278
|
+
image: useLegacyDocker?.image ?? getLanguageProfile(language).defaultDockerImage,
|
|
8279
|
+
workdir: useLegacyDocker?.workdir ?? "/workspace",
|
|
8280
|
+
pull: useLegacyDocker?.pull ?? false,
|
|
8281
|
+
docker_bin: useLegacyDocker?.docker_bin ?? "docker",
|
|
8282
|
+
extra_run_args: useLegacyDocker?.extra_run_args ?? [],
|
|
8283
|
+
sandbox_dir: useLegacyDocker?.sandbox_dir ?? `.sandbox/${language}`,
|
|
8284
|
+
limits: useLegacyDocker?.limits ?? limits
|
|
8285
|
+
}
|
|
8286
|
+
};
|
|
7768
8287
|
}
|
|
7769
8288
|
|
|
7770
8289
|
// src/core/engine.ts
|
|
7771
|
-
import
|
|
8290
|
+
import path27 from "path";
|
|
7772
8291
|
import chalk2 from "chalk";
|
|
7773
8292
|
|
|
7774
8293
|
// src/util/spinner.ts
|
|
@@ -7854,10 +8373,10 @@ init_types();
|
|
|
7854
8373
|
// src/tools/fs.ts
|
|
7855
8374
|
init_types();
|
|
7856
8375
|
import path18 from "path";
|
|
7857
|
-
import { promises as
|
|
8376
|
+
import { promises as fs17 } from "fs";
|
|
7858
8377
|
|
|
7859
8378
|
// src/tools/path_guard.ts
|
|
7860
|
-
import { promises as
|
|
8379
|
+
import { promises as fs16 } from "fs";
|
|
7861
8380
|
import path17 from "path";
|
|
7862
8381
|
async function resolveWorkspacePath(ws, rawPath, operation, opts = {}) {
|
|
7863
8382
|
const raw = rawPath && rawPath.trim() ? rawPath : ".";
|
|
@@ -7866,19 +8385,19 @@ async function resolveWorkspacePath(ws, rawPath, operation, opts = {}) {
|
|
|
7866
8385
|
if (!isInside(root, abs)) return deny(operation, raw);
|
|
7867
8386
|
if (opts.mustExist) {
|
|
7868
8387
|
try {
|
|
7869
|
-
const real = await
|
|
8388
|
+
const real = await fs16.realpath(abs);
|
|
7870
8389
|
if (!isInside(root, real)) return deny(operation, raw);
|
|
7871
8390
|
} catch (err) {
|
|
7872
8391
|
return { ok: false, error: `${operation} failed: ${err.message}` };
|
|
7873
8392
|
}
|
|
7874
8393
|
} else if (opts.forWrite) {
|
|
7875
|
-
const existingTarget = await
|
|
8394
|
+
const existingTarget = await fs16.realpath(abs).catch(() => void 0);
|
|
7876
8395
|
if (existingTarget && !isInside(root, existingTarget)) return deny(operation, raw);
|
|
7877
8396
|
const parent = await nearestExistingParent(path17.dirname(abs));
|
|
7878
|
-
const realParent = await
|
|
8397
|
+
const realParent = await fs16.realpath(parent).catch(() => parent);
|
|
7879
8398
|
if (!isInside(root, realParent)) return deny(operation, raw);
|
|
7880
8399
|
} else {
|
|
7881
|
-
const existing = await
|
|
8400
|
+
const existing = await fs16.realpath(abs).catch(() => void 0);
|
|
7882
8401
|
if (existing && !isInside(root, existing)) return deny(operation, raw);
|
|
7883
8402
|
}
|
|
7884
8403
|
return { ok: true, abs };
|
|
@@ -7888,13 +8407,13 @@ function isInside(root, candidate) {
|
|
|
7888
8407
|
return rel === "" || !!rel && !rel.startsWith("..") && !path17.isAbsolute(rel);
|
|
7889
8408
|
}
|
|
7890
8409
|
async function realpathOrResolve(p) {
|
|
7891
|
-
return
|
|
8410
|
+
return fs16.realpath(p).catch(() => path17.resolve(p));
|
|
7892
8411
|
}
|
|
7893
8412
|
async function nearestExistingParent(start) {
|
|
7894
8413
|
let current2 = path17.resolve(start);
|
|
7895
8414
|
while (true) {
|
|
7896
8415
|
try {
|
|
7897
|
-
const stat = await
|
|
8416
|
+
const stat = await fs16.stat(current2);
|
|
7898
8417
|
if (stat.isDirectory()) return current2;
|
|
7899
8418
|
} catch {
|
|
7900
8419
|
}
|
|
@@ -7920,9 +8439,9 @@ var readFileTool = {
|
|
|
7920
8439
|
const resolved = await resolveWorkspacePath(ctx.ws, args.path, "read_file", { mustExist: true });
|
|
7921
8440
|
if (!resolved.ok) return { ok: false, error: resolved.error };
|
|
7922
8441
|
const abs = resolved.abs;
|
|
7923
|
-
const stat = await
|
|
8442
|
+
const stat = await fs17.stat(abs);
|
|
7924
8443
|
if (!stat.isFile()) return { ok: false, error: "not a file" };
|
|
7925
|
-
const buf = await
|
|
8444
|
+
const buf = await fs17.readFile(abs);
|
|
7926
8445
|
const limit = args.maxBytes ?? 2e5;
|
|
7927
8446
|
const content = buf.byteLength > limit ? buf.subarray(0, limit).toString("utf8") + `
|
|
7928
8447
|
... [truncated ${buf.byteLength - limit} bytes]` : buf.toString("utf8");
|
|
@@ -7983,8 +8502,8 @@ var writeFileTool = {
|
|
|
7983
8502
|
}
|
|
7984
8503
|
try {
|
|
7985
8504
|
const abs = resolved.abs;
|
|
7986
|
-
await
|
|
7987
|
-
await
|
|
8505
|
+
await fs17.mkdir(path18.dirname(abs), { recursive: true });
|
|
8506
|
+
await fs17.writeFile(abs, args.content, "utf8");
|
|
7988
8507
|
return {
|
|
7989
8508
|
ok: true,
|
|
7990
8509
|
data: { bytes: size },
|
|
@@ -8020,11 +8539,11 @@ var appendFileTool = {
|
|
|
8020
8539
|
}
|
|
8021
8540
|
try {
|
|
8022
8541
|
const abs = resolved.abs;
|
|
8023
|
-
await
|
|
8024
|
-
await
|
|
8542
|
+
await fs17.mkdir(path18.dirname(abs), { recursive: true });
|
|
8543
|
+
await fs17.appendFile(abs, args.content, "utf8");
|
|
8025
8544
|
let total = size;
|
|
8026
8545
|
try {
|
|
8027
|
-
total = (await
|
|
8546
|
+
total = (await fs17.stat(abs)).size;
|
|
8028
8547
|
} catch {
|
|
8029
8548
|
}
|
|
8030
8549
|
return {
|
|
@@ -8057,7 +8576,7 @@ var listDirTool = {
|
|
|
8057
8576
|
const resolved = await resolveWorkspacePath(ctx.ws, args.path ?? ".", "list_dir", { mustExist: true });
|
|
8058
8577
|
if (!resolved.ok) return { ok: false, error: resolved.error };
|
|
8059
8578
|
const abs = resolved.abs;
|
|
8060
|
-
const entries = await
|
|
8579
|
+
const entries = await fs17.readdir(abs, { withFileTypes: true });
|
|
8061
8580
|
return {
|
|
8062
8581
|
ok: true,
|
|
8063
8582
|
data: { entries: entries.map((e) => e.isDirectory() ? e.name + "/" : e.name) },
|
|
@@ -8072,7 +8591,7 @@ var listDirTool = {
|
|
|
8072
8591
|
// src/tools/patch.ts
|
|
8073
8592
|
init_types();
|
|
8074
8593
|
import path19 from "path";
|
|
8075
|
-
import { promises as
|
|
8594
|
+
import { promises as fs18 } from "fs";
|
|
8076
8595
|
var applyPatchTool = {
|
|
8077
8596
|
name: "apply_patch",
|
|
8078
8597
|
description: "\u5E94\u7528 unified diff \u8865\u4E01\uFF1B\u76EE\u6807\u6587\u4EF6\u5FC5\u987B\u5728\u5F53\u524D Step writable allowlist \u5185\u3002",
|
|
@@ -8090,14 +8609,14 @@ var applyPatchTool = {
|
|
|
8090
8609
|
const abs = resolved.abs;
|
|
8091
8610
|
let original = "";
|
|
8092
8611
|
try {
|
|
8093
|
-
original = await
|
|
8612
|
+
original = await fs18.readFile(abs, "utf8");
|
|
8094
8613
|
} catch {
|
|
8095
8614
|
if (!fd.isNewFile) return { ok: false, error: `target file missing: ${fd.target}` };
|
|
8096
8615
|
}
|
|
8097
8616
|
const next = applyHunks(original, fd.hunks);
|
|
8098
8617
|
if (next.error) return { ok: false, error: `${fd.target}: ${next.error}` };
|
|
8099
|
-
await
|
|
8100
|
-
await
|
|
8618
|
+
await fs18.mkdir(path19.dirname(abs), { recursive: true });
|
|
8619
|
+
await fs18.writeFile(abs, next.content, "utf8");
|
|
8101
8620
|
changed.push(fd.target);
|
|
8102
8621
|
}
|
|
8103
8622
|
return { ok: true, data: { changedFiles: changed }, summary: `patched ${changed.join(", ")}` };
|
|
@@ -8313,7 +8832,7 @@ var pipInstallTool = {
|
|
|
8313
8832
|
// src/tools/edit.ts
|
|
8314
8833
|
init_types();
|
|
8315
8834
|
import path20 from "path";
|
|
8316
|
-
import { promises as
|
|
8835
|
+
import { promises as fs19 } from "fs";
|
|
8317
8836
|
var replaceInFileTool = {
|
|
8318
8837
|
name: "replace_in_file",
|
|
8319
8838
|
description: "\u628A\u5F53\u524D Step writable allowlist \u5185\u76EE\u6807\u6587\u4EF6\u7684 find \u5B57\u7B26\u4E32\u7CBE\u786E\u66FF\u6362\u4E3A replace\uFF08\u9ED8\u8BA4\u8981\u6C42\u51FA\u73B0 1 \u6B21\uFF09\u3002",
|
|
@@ -8334,7 +8853,7 @@ var replaceInFileTool = {
|
|
|
8334
8853
|
const abs = resolved.abs;
|
|
8335
8854
|
let original;
|
|
8336
8855
|
try {
|
|
8337
|
-
original = await
|
|
8856
|
+
original = await fs19.readFile(abs, "utf8");
|
|
8338
8857
|
} catch {
|
|
8339
8858
|
return { ok: false, error: `file not found: ${args.path}` };
|
|
8340
8859
|
}
|
|
@@ -8366,8 +8885,8 @@ var replaceInFileTool = {
|
|
|
8366
8885
|
return { ok: false, error: hint.join("\n") };
|
|
8367
8886
|
}
|
|
8368
8887
|
const next = parts.join(args.replace);
|
|
8369
|
-
await
|
|
8370
|
-
await
|
|
8888
|
+
await fs19.mkdir(path20.dirname(abs), { recursive: true });
|
|
8889
|
+
await fs19.writeFile(abs, next, "utf8");
|
|
8371
8890
|
return {
|
|
8372
8891
|
ok: true,
|
|
8373
8892
|
data: { occurrences },
|
|
@@ -8384,7 +8903,7 @@ var codeSearchTool = {
|
|
|
8384
8903
|
const resolved = await resolveWorkspacePath(ctx.ws, args.root ?? ".", "code_search", { mustExist: true });
|
|
8385
8904
|
if (!resolved.ok) return { ok: false, error: resolved.error };
|
|
8386
8905
|
const root = resolved.abs;
|
|
8387
|
-
const workspaceRoot = await
|
|
8906
|
+
const workspaceRoot = await fs19.realpath(ctx.ws.root).catch(() => ctx.ws.root);
|
|
8388
8907
|
const max = args.maxResults ?? 50;
|
|
8389
8908
|
const exts = args.ext && args.ext.length > 0 ? new Set(args.ext.map((e) => e.startsWith(".") ? e : "." + e)) : null;
|
|
8390
8909
|
const matches = [];
|
|
@@ -8393,9 +8912,9 @@ var codeSearchTool = {
|
|
|
8393
8912
|
if (/(?:^|\/)(node_modules|\.git|\.sandbox|\.xcompiler|dist|__pycache__)\//.test(abs)) return;
|
|
8394
8913
|
let content;
|
|
8395
8914
|
try {
|
|
8396
|
-
const stat = await
|
|
8915
|
+
const stat = await fs19.stat(abs);
|
|
8397
8916
|
if (stat.size > 512e3) return;
|
|
8398
|
-
content = await
|
|
8917
|
+
content = await fs19.readFile(abs, "utf8");
|
|
8399
8918
|
} catch {
|
|
8400
8919
|
return;
|
|
8401
8920
|
}
|
|
@@ -8418,7 +8937,7 @@ var codeSearchTool = {
|
|
|
8418
8937
|
async function walk2(dir, onFile) {
|
|
8419
8938
|
let entries;
|
|
8420
8939
|
try {
|
|
8421
|
-
entries = await
|
|
8940
|
+
entries = await fs19.readdir(dir, { withFileTypes: true });
|
|
8422
8941
|
} catch {
|
|
8423
8942
|
return;
|
|
8424
8943
|
}
|
|
@@ -8472,7 +8991,7 @@ var analyzeErrorTool = {
|
|
|
8472
8991
|
};
|
|
8473
8992
|
|
|
8474
8993
|
// src/tools/deps.ts
|
|
8475
|
-
import { promises as
|
|
8994
|
+
import { promises as fs20 } from "fs";
|
|
8476
8995
|
var addDependencyTool = {
|
|
8477
8996
|
name: "add_dependency",
|
|
8478
8997
|
description: "\u5411\u4F9D\u8D56\u6E05\u5355\u8FFD\u52A0\u4F9D\u8D56\uFF08python: requirements.txt\uFF1Btypescript: package.json\uFF09\u5E76\u91CD\u5EFA\u6C99\u76D2\u3002",
|
|
@@ -8492,7 +9011,7 @@ var addDependencyTool = {
|
|
|
8492
9011
|
const added = [];
|
|
8493
9012
|
let final;
|
|
8494
9013
|
if (ctx.language === "typescript") {
|
|
8495
|
-
const pkg = await
|
|
9014
|
+
const pkg = await fs20.readFile(abs, "utf8").then((text) => JSON.parse(text)).catch(() => ({}));
|
|
8496
9015
|
const existingDeps = pkg.dependencies && typeof pkg.dependencies === "object" && !Array.isArray(pkg.dependencies) ? { ...pkg.dependencies } : {};
|
|
8497
9016
|
const before = new Set(Object.keys(existingDeps));
|
|
8498
9017
|
for (const name of normalized) {
|
|
@@ -8501,11 +9020,11 @@ var addDependencyTool = {
|
|
|
8501
9020
|
}
|
|
8502
9021
|
final = Object.keys(existingDeps).sort();
|
|
8503
9022
|
pkg.dependencies = Object.fromEntries(final.map((name) => [name, existingDeps[name] ?? "*"]));
|
|
8504
|
-
await
|
|
9023
|
+
await fs20.writeFile(abs, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
8505
9024
|
} else {
|
|
8506
9025
|
let existing = "";
|
|
8507
9026
|
try {
|
|
8508
|
-
existing = await
|
|
9027
|
+
existing = await fs20.readFile(abs, "utf8");
|
|
8509
9028
|
} catch {
|
|
8510
9029
|
}
|
|
8511
9030
|
const set = /* @__PURE__ */ new Set();
|
|
@@ -8519,7 +9038,7 @@ var addDependencyTool = {
|
|
|
8519
9038
|
set.add(p);
|
|
8520
9039
|
}
|
|
8521
9040
|
final = [...set].sort();
|
|
8522
|
-
await
|
|
9041
|
+
await fs20.writeFile(abs, final.join("\n") + "\n", "utf8");
|
|
8523
9042
|
}
|
|
8524
9043
|
try {
|
|
8525
9044
|
await ctx.sandbox.build(manifestPath);
|
|
@@ -8538,7 +9057,7 @@ var addDependencyTool = {
|
|
|
8538
9057
|
};
|
|
8539
9058
|
|
|
8540
9059
|
// src/tools/net.ts
|
|
8541
|
-
import { promises as
|
|
9060
|
+
import { promises as fs21 } from "fs";
|
|
8542
9061
|
import path21 from "path";
|
|
8543
9062
|
var DEFAULT_TIMEOUT_MS = 15e3;
|
|
8544
9063
|
var DEFAULT_MAX_BYTES = 256 * 1024;
|
|
@@ -8600,8 +9119,8 @@ var httpFetchTool = {
|
|
|
8600
9119
|
if (timer) clearTimeout(timer);
|
|
8601
9120
|
const trimmed = buf.length > maxBytes ? buf.subarray(0, maxBytes) : buf;
|
|
8602
9121
|
const abs = saveAsAbs;
|
|
8603
|
-
await
|
|
8604
|
-
await
|
|
9122
|
+
await fs21.mkdir(path21.dirname(abs), { recursive: true });
|
|
9123
|
+
await fs21.writeFile(abs, trimmed);
|
|
8605
9124
|
await ctx.audit?.event("tool.call", t().audit.httpFetchSaved(method, args.url, args.saveAs, trimmed.length), {
|
|
8606
9125
|
messageId: "audit.http_fetch_saved",
|
|
8607
9126
|
stepId: ctx.stepId,
|
|
@@ -8659,7 +9178,7 @@ var httpFetchTool = {
|
|
|
8659
9178
|
init_types();
|
|
8660
9179
|
|
|
8661
9180
|
// src/tools/guard.ts
|
|
8662
|
-
import { promises as
|
|
9181
|
+
import { promises as fs22 } from "fs";
|
|
8663
9182
|
import path22 from "path";
|
|
8664
9183
|
var WRITE_TOOLS = /* @__PURE__ */ new Set(["write_file", "append_file", "apply_patch", "replace_in_file", "add_dependency"]);
|
|
8665
9184
|
var DEFAULT_EDIT_LINES_PER_STEP = 400;
|
|
@@ -8742,8 +9261,8 @@ var EditGuard = class {
|
|
|
8742
9261
|
async record(partial) {
|
|
8743
9262
|
const rec = { ts: (/* @__PURE__ */ new Date()).toISOString(), stepId: this.opts.stepId, ...partial };
|
|
8744
9263
|
try {
|
|
8745
|
-
await
|
|
8746
|
-
await
|
|
9264
|
+
await fs22.mkdir(path22.dirname(this.logAbs), { recursive: true });
|
|
9265
|
+
await fs22.appendFile(this.logAbs, JSON.stringify(rec) + "\n", "utf8");
|
|
8747
9266
|
} catch {
|
|
8748
9267
|
}
|
|
8749
9268
|
}
|
|
@@ -8799,8 +9318,9 @@ function buildDefaultRegistry() {
|
|
|
8799
9318
|
|
|
8800
9319
|
// src/agents/executor.ts
|
|
8801
9320
|
import path23 from "path";
|
|
8802
|
-
import { promises as
|
|
9321
|
+
import { promises as fs23 } from "fs";
|
|
8803
9322
|
import { jsonrepair } from "jsonrepair";
|
|
9323
|
+
var MISSING_OUTPUT_STALL_ROUND_LIMIT = 3;
|
|
8804
9324
|
var StepExecutor = class {
|
|
8805
9325
|
constructor(opts) {
|
|
8806
9326
|
this.opts = opts;
|
|
@@ -8824,8 +9344,12 @@ var StepExecutor = class {
|
|
|
8824
9344
|
];
|
|
8825
9345
|
const calls = [];
|
|
8826
9346
|
let finalThought;
|
|
8827
|
-
|
|
9347
|
+
let issueResolutionPlan;
|
|
9348
|
+
const initialVerify = await verifyOutputs(inp);
|
|
9349
|
+
const initialMissing = initialVerify.missing.length;
|
|
8828
9350
|
const hardRoundLimit = Math.max(maxRounds, maxRounds + Math.min(12, Math.max(4, initialMissing * 2)));
|
|
9351
|
+
let lastMissingCount = initialMissing;
|
|
9352
|
+
let missingOutputStallRounds = 0;
|
|
8829
9353
|
let parseFailures = 0;
|
|
8830
9354
|
let repeatedTurns = 0;
|
|
8831
9355
|
let lastActionsKey = null;
|
|
@@ -8874,11 +9398,12 @@ var StepExecutor = class {
|
|
|
8874
9398
|
} catch (err) {
|
|
8875
9399
|
rep.done("failed");
|
|
8876
9400
|
const errMsg = err.message;
|
|
9401
|
+
actualRounds = round;
|
|
8877
9402
|
const dumpRel = `.xcompiler/llm-stream/${inp.step.id}-${role}-r${round}.txt`;
|
|
8878
9403
|
try {
|
|
8879
9404
|
const dumpAbs = inp.ctx.ws.abs(dumpRel);
|
|
8880
|
-
await
|
|
8881
|
-
await
|
|
9405
|
+
await fs23.mkdir(path23.dirname(dumpAbs), { recursive: true });
|
|
9406
|
+
await fs23.writeFile(
|
|
8882
9407
|
dumpAbs,
|
|
8883
9408
|
`${t().audit.partialFailureHeader(errMsg)}
|
|
8884
9409
|
${t().audit.streamLength(rawAggregate.length)}
|
|
@@ -8907,11 +9432,33 @@ ${rawAggregate}`,
|
|
|
8907
9432
|
partialBytes: rawAggregate.length
|
|
8908
9433
|
}
|
|
8909
9434
|
);
|
|
9435
|
+
if (isLowQualityLLMResponseError(errMsg)) {
|
|
9436
|
+
repeatedTurns++;
|
|
9437
|
+
const verify2 = await verifyOutputs(inp);
|
|
9438
|
+
const metrics2 = computeMetrics({
|
|
9439
|
+
rounds: actualRounds,
|
|
9440
|
+
parseFailures,
|
|
9441
|
+
repeatedTurns,
|
|
9442
|
+
calls,
|
|
9443
|
+
initialMissing,
|
|
9444
|
+
currentMissing: verify2.missing.length
|
|
9445
|
+
});
|
|
9446
|
+
return {
|
|
9447
|
+
success: false,
|
|
9448
|
+
rounds: round,
|
|
9449
|
+
toolCalls: calls,
|
|
9450
|
+
finalThought,
|
|
9451
|
+
issueResolutionPlan,
|
|
9452
|
+
error: errMsg,
|
|
9453
|
+
metrics: metrics2
|
|
9454
|
+
};
|
|
9455
|
+
}
|
|
8910
9456
|
throw err;
|
|
8911
9457
|
}
|
|
8912
9458
|
rep.done();
|
|
8913
9459
|
const turn = parseTurn(text);
|
|
8914
9460
|
finalThought = turn.thoughts;
|
|
9461
|
+
issueResolutionPlan = extractIssueResolutionPlan(turn) ?? issueResolutionPlan;
|
|
8915
9462
|
const normalizedActions = normalizeActions(turn.actions);
|
|
8916
9463
|
const actions = normalizedActions.actions;
|
|
8917
9464
|
if (normalizedActions.invalid.length > 0) {
|
|
@@ -8961,6 +9508,7 @@ ${rawAggregate}`,
|
|
|
8961
9508
|
}
|
|
8962
9509
|
await inp.ctx.audit?.executorTurn(inp.step.id, role, round, {
|
|
8963
9510
|
thoughts: turn.thoughts,
|
|
9511
|
+
issueResolutionPlan,
|
|
8964
9512
|
actions,
|
|
8965
9513
|
done: turn.done === true,
|
|
8966
9514
|
raw: text,
|
|
@@ -9069,12 +9617,25 @@ ${rawAggregate}`,
|
|
|
9069
9617
|
turnResults.push({ ...r, tool: a.tool });
|
|
9070
9618
|
}
|
|
9071
9619
|
const verify = await verifyOutputs(inp);
|
|
9620
|
+
const mutationSucceededThisRound = turnResults.some((r) => r.ok && isRepairEvidenceTool(r.tool));
|
|
9621
|
+
if (verify.missing.length < lastMissingCount) {
|
|
9622
|
+
lastMissingCount = verify.missing.length;
|
|
9623
|
+
missingOutputStallRounds = 0;
|
|
9624
|
+
} else if (!verify.ok && initialMissing > 0 && mutationSucceededThisRound && !readOnlyRound && unresolvedToolFailures.size === 0) {
|
|
9625
|
+
missingOutputStallRounds++;
|
|
9626
|
+
}
|
|
9072
9627
|
if (turnResults.some((r) => r.tool === "run_tests" && !r.ok) && !advisoryFailureTools.has("run_tests")) {
|
|
9073
9628
|
failedTestRunRounds++;
|
|
9074
9629
|
}
|
|
9075
9630
|
const repairGateOk = !repairRequired || repairEvidence || canAcceptOutputCompletionRecovery(inp, initialMissing);
|
|
9631
|
+
const issueResolutionPlanRequired = role === "Debugger" && !!inp.debugContext?.issueId;
|
|
9632
|
+
const issueResolutionPlanOk = !issueResolutionPlanRequired || !!issueResolutionPlan?.trim();
|
|
9076
9633
|
const verifiedCompletion = !turn.done && hasSuccessfulCompletionVerification(calls);
|
|
9077
|
-
|
|
9634
|
+
const outputCompletionRecovery = verify.ok && canAcceptOutputCompletionRecovery(inp, initialMissing);
|
|
9635
|
+
const supersededContractFailures = repairEvidence && hasSuccessfulCompletionVerification(calls) && hasOnlySupersededToolContractFailures(unresolvedToolFailures);
|
|
9636
|
+
const unresolvedFailuresOk = unresolvedToolFailures.size === 0 || outputCompletionRecovery && hasOnlyUntargetedToolContractFailures(unresolvedToolFailures) || supersededContractFailures;
|
|
9637
|
+
const completionSignal = turn.done || verifiedCompletion || outputCompletionRecovery;
|
|
9638
|
+
if (completionSignal && verify.ok && unresolvedFailuresOk && repairGateOk && issueResolutionPlanOk) {
|
|
9078
9639
|
const metrics2 = computeMetrics({
|
|
9079
9640
|
rounds: actualRounds,
|
|
9080
9641
|
parseFailures,
|
|
@@ -9083,7 +9644,7 @@ ${rawAggregate}`,
|
|
|
9083
9644
|
initialMissing,
|
|
9084
9645
|
currentMissing: verify.missing.length
|
|
9085
9646
|
});
|
|
9086
|
-
return { success: true, rounds: round, toolCalls: calls, finalThought, metrics: metrics2 };
|
|
9647
|
+
return { success: true, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, metrics: metrics2 };
|
|
9087
9648
|
}
|
|
9088
9649
|
if (this.opts.maxFailedTestRuns && failedTestRunRounds >= this.opts.maxFailedTestRuns) {
|
|
9089
9650
|
const metrics2 = computeMetrics({
|
|
@@ -9102,7 +9663,27 @@ ${rawAggregate}`,
|
|
|
9102
9663
|
failedTestRunRounds,
|
|
9103
9664
|
maxFailedTestRuns: this.opts.maxFailedTestRuns
|
|
9104
9665
|
});
|
|
9105
|
-
return { success: false, rounds: round, toolCalls: calls, finalThought, error, metrics: metrics2 };
|
|
9666
|
+
return { success: false, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, error, metrics: metrics2 };
|
|
9667
|
+
}
|
|
9668
|
+
if (missingOutputStallRounds >= MISSING_OUTPUT_STALL_ROUND_LIMIT) {
|
|
9669
|
+
repeatedTurns++;
|
|
9670
|
+
const metrics2 = computeMetrics({
|
|
9671
|
+
rounds: actualRounds,
|
|
9672
|
+
parseFailures,
|
|
9673
|
+
repeatedTurns,
|
|
9674
|
+
calls,
|
|
9675
|
+
initialMissing,
|
|
9676
|
+
currentMissing: verify.missing.length
|
|
9677
|
+
});
|
|
9678
|
+
const error = `write/progress actions did not reduce missing outputs for ${missingOutputStallRounds} rounds; missing outputs: ${verify.missing.join(", ")}. Next attempt must create those exact outputs before rewriting already-existing files.`;
|
|
9679
|
+
await inp.ctx.audit?.event("note", error, {
|
|
9680
|
+
messageId: "audit.executor_missing_output_stall",
|
|
9681
|
+
stepId: inp.step.id,
|
|
9682
|
+
round,
|
|
9683
|
+
missingOutputStallRounds,
|
|
9684
|
+
missingOutputs: verify.missing
|
|
9685
|
+
});
|
|
9686
|
+
return { success: false, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, error, metrics: metrics2 };
|
|
9106
9687
|
}
|
|
9107
9688
|
const readOnlyRecoveryViolation = readOnlyRecoveryMode && readOnlyRecoveryRounds >= 2;
|
|
9108
9689
|
const readOnlyRoundLimit = directRepairMode ? 2 : 3;
|
|
@@ -9125,7 +9706,7 @@ ${rawAggregate}`,
|
|
|
9125
9706
|
consecutiveReadOnlyRounds,
|
|
9126
9707
|
actions
|
|
9127
9708
|
});
|
|
9128
|
-
return { success: false, rounds: round, toolCalls: calls, finalThought, error, metrics: metrics2 };
|
|
9709
|
+
return { success: false, rounds: round, toolCalls: calls, finalThought, issueResolutionPlan, error, metrics: metrics2 };
|
|
9129
9710
|
}
|
|
9130
9711
|
if (round >= roundLimit && roundLimit < hardRoundLimit && shouldExtendProductiveRun({
|
|
9131
9712
|
parseFailures,
|
|
@@ -9159,8 +9740,13 @@ ${rawAggregate}`,
|
|
|
9159
9740
|
rounds: consecutiveReadOnlyRounds,
|
|
9160
9741
|
targets: actions.flatMap((action) => actionTargetPaths(action.tool, action.args)).join(", ")
|
|
9161
9742
|
} : void 0,
|
|
9743
|
+
missingOutputStallWarning: missingOutputStallRounds >= MISSING_OUTPUT_STALL_ROUND_LIMIT - 1 && !verify.ok ? {
|
|
9744
|
+
rounds: missingOutputStallRounds,
|
|
9745
|
+
missing: verify.missing.join(", ")
|
|
9746
|
+
} : void 0,
|
|
9162
9747
|
readOnlyRecoveryWarning: (readOnlyRecoveryMode || directRepairMode) && readOnlyRound,
|
|
9163
|
-
repairEvidenceMissing: repairRequired && turn.done === true && verify.ok && unresolvedToolFailures.size === 0 && !repairEvidence
|
|
9748
|
+
repairEvidenceMissing: repairRequired && turn.done === true && verify.ok && unresolvedToolFailures.size === 0 && !repairEvidence,
|
|
9749
|
+
issueResolutionPlanMissing: issueResolutionPlanRequired && turn.done === true && verify.ok && unresolvedToolFailures.size === 0 && !issueResolutionPlanOk
|
|
9164
9750
|
})
|
|
9165
9751
|
});
|
|
9166
9752
|
}
|
|
@@ -9178,7 +9764,8 @@ ${rawAggregate}`,
|
|
|
9178
9764
|
rounds: actualRounds || roundLimit,
|
|
9179
9765
|
toolCalls: calls,
|
|
9180
9766
|
finalThought,
|
|
9181
|
-
|
|
9767
|
+
issueResolutionPlan,
|
|
9768
|
+
error: role === "Debugger" && !!inp.debugContext?.issueId && !issueResolutionPlan?.trim() ? "DEBUG issue completion missing issueResolutionPlan; provide a concrete handling plan before marking the issue resolved." : repairRequired && finalVerify.ok && unresolvedToolFailures.size === 0 && !repairEvidence && !canAcceptOutputCompletionRecovery(inp, initialMissing) ? "DEBUG retry ended without repair evidence; run a successful patch/write/dependency change or verification command before done=true." : finalVerify.ok && unresolvedToolFailures.size > 0 ? `unresolved tool failures remain: ${[...unresolvedToolFailures.values()].join("; ")}` : "max rounds exceeded without satisfying outputs",
|
|
9182
9769
|
metrics
|
|
9183
9770
|
};
|
|
9184
9771
|
}
|
|
@@ -9191,6 +9778,9 @@ function isReadOnlyOrProbeAction(action) {
|
|
|
9191
9778
|
function isReadOnlyLoopFailure(reason) {
|
|
9192
9779
|
return /repeated read-only\/probe actions without progress/i.test(reason) || /read-only recovery mode repeated probe actions/i.test(reason);
|
|
9193
9780
|
}
|
|
9781
|
+
function isLowQualityLLMResponseError(message) {
|
|
9782
|
+
return /low-quality (?:debugger )?response/i.test(message) || /read-only\/probe actions in read-only recovery mode/i.test(message);
|
|
9783
|
+
}
|
|
9194
9784
|
function hasActionableDebuggerFailure(debugContext) {
|
|
9195
9785
|
if (!debugContext) return false;
|
|
9196
9786
|
const text = [
|
|
@@ -9206,6 +9796,18 @@ function canAcceptOutputCompletionRecovery(inp, initialMissing) {
|
|
|
9206
9796
|
if (initialMissing !== 0) return false;
|
|
9207
9797
|
return isOutputCompletionFailure(inp.debugContext.reason, inp.debugContext.failureLog);
|
|
9208
9798
|
}
|
|
9799
|
+
function hasOnlyUntargetedToolContractFailures(unresolved) {
|
|
9800
|
+
if (unresolved.size === 0) return true;
|
|
9801
|
+
return [...unresolved.entries()].every(
|
|
9802
|
+
([key, detail]) => key.startsWith("tool:") && /invalid (?:write_file|append_file|replace_in_file|read_file) args: path must be a non-empty string/i.test(detail)
|
|
9803
|
+
);
|
|
9804
|
+
}
|
|
9805
|
+
function hasOnlySupersededToolContractFailures(unresolved) {
|
|
9806
|
+
if (unresolved.size === 0) return true;
|
|
9807
|
+
return [...unresolved.values()].every(
|
|
9808
|
+
(detail) => /invalid (?:write_file|append_file|replace_in_file|apply_patch|read_file) args/i.test(detail) || /(?:read_file|write_file|append_file|replace_in_file|apply_patch) denied: path "\." is outside the project directory/i.test(detail) || /(?:read_file|write_file|append_file|replace_in_file) failed:.*path must be a non-empty string/i.test(detail) || /path must be a non-empty string/i.test(detail)
|
|
9809
|
+
);
|
|
9810
|
+
}
|
|
9209
9811
|
function isOutputCompletionFailure(reason = "", failureLog = "") {
|
|
9210
9812
|
const text = `${reason}
|
|
9211
9813
|
${failureLog}`;
|
|
@@ -9280,6 +9882,7 @@ function compactTurnForHistory(turn) {
|
|
|
9280
9882
|
const normalized = normalizeActions(turn.actions);
|
|
9281
9883
|
return JSON.stringify({
|
|
9282
9884
|
thoughts: truncate2(turn.thoughts ?? "", 500),
|
|
9885
|
+
issueResolutionPlan: truncate2(extractIssueResolutionPlan(turn) ?? "", 900),
|
|
9283
9886
|
actions: normalized.actions.map((action) => ({
|
|
9284
9887
|
tool: action.tool,
|
|
9285
9888
|
args: compactActionArgs(action.tool, action.args)
|
|
@@ -9618,8 +10221,8 @@ async function verifyOutputs(inp) {
|
|
|
9618
10221
|
const missing = [];
|
|
9619
10222
|
for (const out of inp.step.outputs) {
|
|
9620
10223
|
if (out.endsWith("/")) continue;
|
|
9621
|
-
const
|
|
9622
|
-
if (!
|
|
10224
|
+
const exists2 = await inp.ctx.ws.exists(out);
|
|
10225
|
+
if (!exists2) missing.push(out);
|
|
9623
10226
|
}
|
|
9624
10227
|
return { ok: missing.length === 0, missing };
|
|
9625
10228
|
}
|
|
@@ -9635,13 +10238,21 @@ function renderUserPrompt(inp, toolDocs) {
|
|
|
9635
10238
|
${truncate2(s.content, s.path === ".xcompiler/architecture-contract.json" ? architectureLimit : snippetLimit)}
|
|
9636
10239
|
\`\`\``
|
|
9637
10240
|
).join("\n\n");
|
|
9638
|
-
const dbg = inp.debugContext ?
|
|
10241
|
+
const dbg = inp.debugContext ? [
|
|
10242
|
+
inp.debugContext.issueId ? `## issue
|
|
10243
|
+
id: ${inp.debugContext.issueId}
|
|
10244
|
+
` : "",
|
|
10245
|
+
inp.debugContext.debugBrief ? `${inp.debugContext.debugBrief}
|
|
10246
|
+
` : "",
|
|
10247
|
+
`## compact failure evidence
|
|
9639
10248
|
\`\`\`
|
|
9640
10249
|
${truncate2(inp.debugContext.failureLog, failureLogLimit)}
|
|
9641
10250
|
\`\`\`
|
|
9642
|
-
|
|
10251
|
+
`,
|
|
10252
|
+
inp.debugContext.suggestions ? `
|
|
9643
10253
|
${inp.debugContext.suggestions}
|
|
9644
|
-
` : ""
|
|
10254
|
+
` : ""
|
|
10255
|
+
].join("\n") : "";
|
|
9645
10256
|
return [
|
|
9646
10257
|
`# Step ${inp.step.id} \u2014 ${inp.step.title}`,
|
|
9647
10258
|
`phase: ${inp.step.phase}`,
|
|
@@ -9729,6 +10340,11 @@ function renderFeedback(results, verify, turn) {
|
|
|
9729
10340
|
)
|
|
9730
10341
|
);
|
|
9731
10342
|
}
|
|
10343
|
+
if (turn.missingOutputStallWarning) {
|
|
10344
|
+
lines.push(
|
|
10345
|
+
`Output progress warning: required outputs have not decreased for ${turn.missingOutputStallWarning.rounds} write/progress rounds. Create these exact missing outputs next: ${turn.missingOutputStallWarning.missing}. Do not keep rewriting files that already satisfy declared outputs.`
|
|
10346
|
+
);
|
|
10347
|
+
}
|
|
9732
10348
|
if (turn.readOnlyRecoveryWarning) {
|
|
9733
10349
|
lines.push(M.executorFeedbackReadOnlyRecoveryRequired);
|
|
9734
10350
|
if (!verify.ok && verify.missing.length > 0) {
|
|
@@ -9737,6 +10353,9 @@ function renderFeedback(results, verify, turn) {
|
|
|
9737
10353
|
);
|
|
9738
10354
|
}
|
|
9739
10355
|
}
|
|
10356
|
+
if (turn.issueResolutionPlanMissing) {
|
|
10357
|
+
lines.push(M.executorFeedbackIssueResolutionPlanMissing);
|
|
10358
|
+
}
|
|
9740
10359
|
if (turn.unresolvedFailures && turn.unresolvedFailures.length > 0) {
|
|
9741
10360
|
lines.push(
|
|
9742
10361
|
`Unresolved tool failures remain: ${turn.unresolvedFailures.map((failure) => truncate2(failure, 1200)).join("; ")}`
|
|
@@ -9947,6 +10566,21 @@ function isTurnObject(value) {
|
|
|
9947
10566
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
9948
10567
|
return value;
|
|
9949
10568
|
}
|
|
10569
|
+
function extractIssueResolutionPlan(turn) {
|
|
10570
|
+
const candidates = [
|
|
10571
|
+
turn.issueResolutionPlan,
|
|
10572
|
+
turn.issue_resolution_plan,
|
|
10573
|
+
turn.resolutionPlan,
|
|
10574
|
+
turn.handlingPlan,
|
|
10575
|
+
turn.fixPlan
|
|
10576
|
+
];
|
|
10577
|
+
for (const value of candidates) {
|
|
10578
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
10579
|
+
return truncate2(value.trim(), 2400);
|
|
10580
|
+
}
|
|
10581
|
+
}
|
|
10582
|
+
return void 0;
|
|
10583
|
+
}
|
|
9950
10584
|
function normalizeJsonLikeStrings(text) {
|
|
9951
10585
|
let out = "";
|
|
9952
10586
|
let inStr = false;
|
|
@@ -10084,8 +10718,301 @@ function buildDefaultSkills() {
|
|
|
10084
10718
|
}
|
|
10085
10719
|
|
|
10086
10720
|
// src/core/debug_cache.ts
|
|
10087
|
-
import { promises as
|
|
10721
|
+
import { promises as fs24 } from "fs";
|
|
10088
10722
|
import path24 from "path";
|
|
10723
|
+
|
|
10724
|
+
// src/core/debug_brief.ts
|
|
10725
|
+
var MAX_EVIDENCE = 8;
|
|
10726
|
+
var MAX_EVIDENCE_LINE = 260;
|
|
10727
|
+
var MAX_TOOL_FAILURES = 6;
|
|
10728
|
+
var MAX_FAILED_TESTS = 8;
|
|
10729
|
+
var MAX_FILES = 10;
|
|
10730
|
+
function buildDebugBrief(input2) {
|
|
10731
|
+
const reason = oneLine(input2.reason ?? "");
|
|
10732
|
+
const raw = `${reason}
|
|
10733
|
+
${input2.failureLog ?? ""}`.trim();
|
|
10734
|
+
const sections = splitFailureSections(raw);
|
|
10735
|
+
const rootSignals = extractSignals(sections.root || raw);
|
|
10736
|
+
const latestSignals = sections.latest ? extractSignals(sections.latest) : void 0;
|
|
10737
|
+
const chosen = choosePrimarySignals(rootSignals, latestSignals);
|
|
10738
|
+
const category = chosen.category;
|
|
10739
|
+
const primaryError = chosen.primaryError || reason || "Unknown failure";
|
|
10740
|
+
const failedTests = dedup3([...rootSignals.failedTests ?? [], ...latestSignals?.failedTests ?? []]).slice(0, MAX_FAILED_TESTS);
|
|
10741
|
+
const files = dedup3([...rootSignals.files ?? [], ...latestSignals?.files ?? []]).slice(0, MAX_FILES);
|
|
10742
|
+
const toolFailures = dedup3([...rootSignals.toolFailures ?? [], ...latestSignals?.toolFailures ?? []]).slice(0, MAX_TOOL_FAILURES);
|
|
10743
|
+
const statusCodes = dedup3([...rootSignals.statusCodes ?? [], ...latestSignals?.statusCodes ?? []]).slice(0, 6);
|
|
10744
|
+
const evidence = selectEvidenceLines(raw, category, primaryError, failedTests, files, toolFailures);
|
|
10745
|
+
return {
|
|
10746
|
+
version: 1,
|
|
10747
|
+
category,
|
|
10748
|
+
summary: buildSummary({ category, reason, primaryError, failedTests, files, phase: input2.phase, targetPhase: input2.targetPhase }),
|
|
10749
|
+
primaryError,
|
|
10750
|
+
debugDemand: buildDebugDemand(category, input2.targetPhase ?? input2.phase, statusCodes),
|
|
10751
|
+
failedTests,
|
|
10752
|
+
files,
|
|
10753
|
+
toolFailures,
|
|
10754
|
+
statusCodes,
|
|
10755
|
+
evidence: evidence.lines,
|
|
10756
|
+
omittedEvidenceLines: evidence.omitted
|
|
10757
|
+
};
|
|
10758
|
+
}
|
|
10759
|
+
function renderDebugBriefForPrompt(brief) {
|
|
10760
|
+
const lines = [
|
|
10761
|
+
"## debug brief",
|
|
10762
|
+
`- category: ${brief.category}`,
|
|
10763
|
+
`- summary: ${brief.summary}`,
|
|
10764
|
+
`- primaryError: ${brief.primaryError}`,
|
|
10765
|
+
`- debugDemand: ${brief.debugDemand}`
|
|
10766
|
+
];
|
|
10767
|
+
if (brief.failedTests.length > 0) lines.push(`- failedTests: ${brief.failedTests.join(", ")}`);
|
|
10768
|
+
if (brief.files.length > 0) lines.push(`- likelyFiles: ${brief.files.join(", ")}`);
|
|
10769
|
+
if (brief.toolFailures.length > 0) lines.push(`- toolFailures: ${brief.toolFailures.join(" | ")}`);
|
|
10770
|
+
if (brief.statusCodes.length > 0) lines.push(`- httpStatus: ${brief.statusCodes.join(", ")}`);
|
|
10771
|
+
if (brief.evidence.length > 0) {
|
|
10772
|
+
lines.push("- keyEvidence:");
|
|
10773
|
+
for (const line of brief.evidence) lines.push(` - ${line}`);
|
|
10774
|
+
}
|
|
10775
|
+
if (brief.omittedEvidenceLines > 0) {
|
|
10776
|
+
lines.push(`- omittedEvidenceLines: ${brief.omittedEvidenceLines}`);
|
|
10777
|
+
}
|
|
10778
|
+
return lines.join("\n");
|
|
10779
|
+
}
|
|
10780
|
+
function compactFailureEvidence(input2) {
|
|
10781
|
+
const maxChars = input2.maxChars ?? 2400;
|
|
10782
|
+
const maxLines = input2.maxLines ?? 50;
|
|
10783
|
+
const reason = shouldSuppressReasonInEvidence(input2.reason, input2.failureLog) ? "" : input2.reason ?? "";
|
|
10784
|
+
const raw = `${reason}
|
|
10785
|
+
${input2.failureLog ?? ""}`.trim();
|
|
10786
|
+
if (!raw) return "";
|
|
10787
|
+
const brief = buildDebugBrief({ ...input2, reason });
|
|
10788
|
+
const important = selectEvidenceLines(
|
|
10789
|
+
raw,
|
|
10790
|
+
brief.category,
|
|
10791
|
+
brief.primaryError,
|
|
10792
|
+
brief.failedTests,
|
|
10793
|
+
brief.files,
|
|
10794
|
+
brief.toolFailures,
|
|
10795
|
+
Math.min(MAX_EVIDENCE + 4, 12)
|
|
10796
|
+
).lines;
|
|
10797
|
+
const tail = raw.split(/\r?\n/u).map((line) => line.trimEnd()).filter((line) => line.trim().length > 0).slice(-Math.max(10, Math.floor(maxLines / 2))).map((line) => truncateLine(line, 320));
|
|
10798
|
+
const lines = dedup3([...important, ...tail]).slice(-maxLines);
|
|
10799
|
+
const joined = lines.join("\n");
|
|
10800
|
+
if (joined.length <= maxChars) return joined;
|
|
10801
|
+
const head = joined.slice(0, Math.floor(maxChars * 0.45));
|
|
10802
|
+
const tailText2 = joined.slice(-Math.floor(maxChars * 0.45));
|
|
10803
|
+
return `${head}
|
|
10804
|
+
... [debug evidence truncated ${joined.length - head.length - tailText2.length} chars]
|
|
10805
|
+
${tailText2}`;
|
|
10806
|
+
}
|
|
10807
|
+
function splitFailureSections(text) {
|
|
10808
|
+
const marker = /\n##\s+latest Debugger attempt failure\b/u.exec(text);
|
|
10809
|
+
if (!marker) return { root: text };
|
|
10810
|
+
return {
|
|
10811
|
+
root: text.slice(0, marker.index).trim(),
|
|
10812
|
+
latest: text.slice(marker.index + 1).trim()
|
|
10813
|
+
};
|
|
10814
|
+
}
|
|
10815
|
+
function choosePrimarySignals(root, latest) {
|
|
10816
|
+
if (!latest) return root;
|
|
10817
|
+
if (isProcessNoise(latest.category) && !isProcessNoise(root.category)) return root;
|
|
10818
|
+
if (latest.category !== "unknown") return latest;
|
|
10819
|
+
return root.category !== "unknown" ? root : latest;
|
|
10820
|
+
}
|
|
10821
|
+
function extractSignals(text) {
|
|
10822
|
+
const lines = normalizedLines(text);
|
|
10823
|
+
const failedTests = extractFailedTests(text);
|
|
10824
|
+
const files = extractFiles(text);
|
|
10825
|
+
const toolFailures = extractToolFailures(lines);
|
|
10826
|
+
const statusCodes = extractStatusCodes(text);
|
|
10827
|
+
const category = classify(text, lines, failedTests, toolFailures);
|
|
10828
|
+
return {
|
|
10829
|
+
category,
|
|
10830
|
+
primaryError: findPrimaryError(text, lines, category, failedTests, toolFailures),
|
|
10831
|
+
failedTests,
|
|
10832
|
+
files,
|
|
10833
|
+
toolFailures,
|
|
10834
|
+
statusCodes
|
|
10835
|
+
};
|
|
10836
|
+
}
|
|
10837
|
+
function classify(text, lines, failedTests, toolFailures) {
|
|
10838
|
+
const lower = text.toLowerCase();
|
|
10839
|
+
if (/openai|ollama|openrouter|llm provider|provider_call_failed|all llm providers failed|prefill_memory_exceeded|context window|token limit|prompt too long/u.test(lower)) {
|
|
10840
|
+
return "llm_provider";
|
|
10841
|
+
}
|
|
10842
|
+
if (/repeated read-only\/probe actions|read-only recovery mode|low-quality debugger response/u.test(lower)) {
|
|
10843
|
+
return "tool_loop";
|
|
10844
|
+
}
|
|
10845
|
+
if (/permission denied/u.test(lower)) return "permission_denied";
|
|
10846
|
+
if (/network api failure detected|http_fetch|https?:\/\/|http\s+(?:401|403|404|408|409|410|422|429|5\d\d)|timed out|timeout/u.test(lower)) {
|
|
10847
|
+
if (!/openai|ollama|openrouter|llm provider/u.test(lower)) return "network_api_failure";
|
|
10848
|
+
}
|
|
10849
|
+
if (/modulenotfounderror|importerror/u.test(lower)) return "import_error";
|
|
10850
|
+
if (/could not find a version|no matching distribution|pip install|add_dependency/u.test(lower)) return "dependency_error";
|
|
10851
|
+
if (/syntaxerror|indentationerror|taberror/u.test(lower)) return "syntax_error";
|
|
10852
|
+
if (/outputs? (?:still )?missing|missing required outputs?|outputs? \S*缺失|仍缺失/u.test(lower)) return "missing_output";
|
|
10853
|
+
if (failedTests.length > 0 || /pytest exit=\s*[1-9]|tests? exit=\s*[1-9]|test gate|测试门禁|vitest|assertionerror|failed tests?|test failures?|(?:unit|integration|module|functional|gate) regression failed/u.test(lower)) {
|
|
10854
|
+
return "test_failure";
|
|
10855
|
+
}
|
|
10856
|
+
if (toolFailures.length > 0) return "exception";
|
|
10857
|
+
if (lines.some((line) => /error|exception|traceback|failed/i.test(line))) return "exception";
|
|
10858
|
+
return "unknown";
|
|
10859
|
+
}
|
|
10860
|
+
function findPrimaryError(text, lines, category, failedTests, toolFailures) {
|
|
10861
|
+
if (failedTests.length > 0 && category === "test_failure") return `failed test: ${failedTests[0]}`;
|
|
10862
|
+
if (toolFailures.length > 0 && (category === "tool_loop" || category === "exception")) return toolFailures[0];
|
|
10863
|
+
const patterns = [
|
|
10864
|
+
/(?:SyntaxError|IndentationError|TabError|ModuleNotFoundError|ImportError|AssertionError|TypeError|ValueError|FileNotFoundError|AttributeError|RuntimeError):[^\n]+/u,
|
|
10865
|
+
/\bFAILED\s+[^\n]+/u,
|
|
10866
|
+
/\b(?:pytest|vitest)[^\n]*(?:exit|failed|FAIL)[^\n]*/iu,
|
|
10867
|
+
/\bHTTP\s+(?:401|403|404|408|409|410|422|429|5\d\d)[^\n]*/iu,
|
|
10868
|
+
/Network API failure detected[^\n]*/iu,
|
|
10869
|
+
/outputs?[^\n]*(?:missing|缺失|仍缺失)[^\n]*/iu,
|
|
10870
|
+
/repeated read-only\/probe actions[^\n]*/iu
|
|
10871
|
+
];
|
|
10872
|
+
for (const pattern of patterns) {
|
|
10873
|
+
const match = text.match(pattern)?.[0];
|
|
10874
|
+
if (match) return oneLine(match);
|
|
10875
|
+
}
|
|
10876
|
+
const lastMeaningful = [...lines].reverse().find((line) => /error|exception|failed|exit=\s*[1-9]|缺失/i.test(line));
|
|
10877
|
+
return oneLine(lastMeaningful ?? lines.at(-1) ?? "Unknown failure");
|
|
10878
|
+
}
|
|
10879
|
+
function buildSummary(args) {
|
|
10880
|
+
const scope = args.targetPhase || args.phase ? ` in ${args.targetPhase ?? args.phase}` : "";
|
|
10881
|
+
if (args.failedTests.length > 0) return `${args.category}${scope}: ${args.failedTests[0]} failed`;
|
|
10882
|
+
if (args.files.length > 0) return `${args.category}${scope}: ${args.primaryError} (${args.files[0]})`;
|
|
10883
|
+
return `${args.category}${scope}: ${args.primaryError || args.reason || "failure"}`;
|
|
10884
|
+
}
|
|
10885
|
+
function buildDebugDemand(category, phase, statusCodes = []) {
|
|
10886
|
+
const phaseHint = phase ? ` for ${phase}` : "";
|
|
10887
|
+
switch (category) {
|
|
10888
|
+
case "test_failure":
|
|
10889
|
+
return `Fix the root implementation/contract defect${phaseHint}, then run the smallest relevant test command before done=true. Do not rewrite fixtures unless evidence says the fixture is missing or malformed.`;
|
|
10890
|
+
case "syntax_error":
|
|
10891
|
+
return `Read the referenced file, patch the syntax/indentation at the failing location, then run tests.`;
|
|
10892
|
+
case "import_error":
|
|
10893
|
+
return `Resolve the real import/module path or dependency. Do not add fake fallback modules or swallow ImportError in production code.`;
|
|
10894
|
+
case "dependency_error":
|
|
10895
|
+
return `Replace hallucinated dependency names with real package names and update the manifest via add_dependency.`;
|
|
10896
|
+
case "network_api_failure":
|
|
10897
|
+
return networkDemand(statusCodes);
|
|
10898
|
+
case "missing_output":
|
|
10899
|
+
return `Create or repair the declared output files. Do not mark done=true until verify outputs passes.`;
|
|
10900
|
+
case "tool_loop":
|
|
10901
|
+
return `Stop repeating read-only/probe actions. Use the current evidence to make a patch/write/dependency change or run a concrete verification command.`;
|
|
10902
|
+
case "permission_denied":
|
|
10903
|
+
return `Treat the denied operation as a real blocker unless an allowed alternative exists; do not bypass the permission gate.`;
|
|
10904
|
+
case "llm_provider":
|
|
10905
|
+
return `This is provider/context infrastructure, not a project code bug. Reduce prompt/debug context or fix provider config before retrying.`;
|
|
10906
|
+
case "exception":
|
|
10907
|
+
return `Localize the exception to a file or tool call, make the smallest allowed repair, then verify.`;
|
|
10908
|
+
case "unknown":
|
|
10909
|
+
return `Read the most relevant files and produce a concrete diagnosis before making a minimal allowed repair.`;
|
|
10910
|
+
}
|
|
10911
|
+
}
|
|
10912
|
+
function networkDemand(statusCodes) {
|
|
10913
|
+
if (statusCodes.some((code) => code === "401" || code === "403")) {
|
|
10914
|
+
return "The API is unauthorized/forbidden. If no user key/token is available, switch to a public no-key API and verify the real integration.";
|
|
10915
|
+
}
|
|
10916
|
+
if (statusCodes.some((code) => code === "404" || code === "410")) {
|
|
10917
|
+
return "The API URL/resource is unavailable. Stop retrying the same URL; switch to a maintained endpoint and verify response shape.";
|
|
10918
|
+
}
|
|
10919
|
+
if (statusCodes.includes("429")) {
|
|
10920
|
+
return "The API is rate-limited. Switch to a suitable fallback API or implement explicit retry/cache behaviour and tests.";
|
|
10921
|
+
}
|
|
10922
|
+
if (statusCodes.some((code) => /^5/u.test(code))) {
|
|
10923
|
+
return "The API server failed. Use a stable fallback endpoint or fail closed with a clear user-visible error path.";
|
|
10924
|
+
}
|
|
10925
|
+
return "Locate the failing URL/status/body, patch the real API integration, and verify with run_program plus tests. Do not hide the API failure.";
|
|
10926
|
+
}
|
|
10927
|
+
function isProcessNoise(category) {
|
|
10928
|
+
return ["tool_loop", "llm_provider", "permission_denied", "exception"].includes(category);
|
|
10929
|
+
}
|
|
10930
|
+
function shouldSuppressReasonInEvidence(reason, failureLog) {
|
|
10931
|
+
if (!reason || !failureLog?.trim()) return false;
|
|
10932
|
+
const lowerReason = reason.toLowerCase();
|
|
10933
|
+
const lowerLog = failureLog.toLowerCase();
|
|
10934
|
+
const hasRootSignal = /pytest exit=\s*[1-9]|tests? exit=\s*[1-9]|test gate|测试门禁|failed\s+(?:tests?|src)\/|assertionerror|syntaxerror|modulenotfounderror|importerror|network api failure|http\s+(?:401|403|404|408|409|410|422|429|5\d\d)|outputs?.*(?:missing|缺失|仍缺失)/u.test(lowerLog);
|
|
10935
|
+
if (!hasRootSignal) return false;
|
|
10936
|
+
return /script exhausted|completed phase debug finished without|repeated read-only\/probe actions|read-only recovery mode|low-quality debugger response|openai http (?:400|401|403|408|409|429|5\d\d)|rate limit exceeded|free-models-per-day|stream (?:wall-clock|idle)|request timed out|provider_call_failed|all llm providers failed/u.test(lowerReason);
|
|
10937
|
+
}
|
|
10938
|
+
function extractFailedTests(text) {
|
|
10939
|
+
const patterns = [
|
|
10940
|
+
/\bFAILED\s+([^\s]+(?:\.py|\.ts|\.tsx|\.js|\.jsx)(?:::[^\s]+)?)/gu,
|
|
10941
|
+
/([^\s]+(?:\.py|\.ts|\.tsx|\.js|\.jsx)::[A-Za-z0-9_:[\].-]+)/gu,
|
|
10942
|
+
/[×x]\s+([^\n]+?>\s+[^\n]+)/gu
|
|
10943
|
+
];
|
|
10944
|
+
const out = [];
|
|
10945
|
+
for (const pattern of patterns) {
|
|
10946
|
+
for (const match of text.matchAll(pattern)) {
|
|
10947
|
+
const value = oneLine(match[1] ?? "");
|
|
10948
|
+
if (value) out.push(value);
|
|
10949
|
+
}
|
|
10950
|
+
}
|
|
10951
|
+
return dedup3(out);
|
|
10952
|
+
}
|
|
10953
|
+
function extractFiles(text) {
|
|
10954
|
+
const out = [];
|
|
10955
|
+
const patterns = [
|
|
10956
|
+
/\b((?:src|tests?|docs)\/[A-Za-z0-9_./-]+\.(?:py|ts|tsx|js|jsx|json|md|dbc|csv|xlsx?))/gu,
|
|
10957
|
+
/File\s+["']([^"']+)["']/gu
|
|
10958
|
+
];
|
|
10959
|
+
for (const pattern of patterns) {
|
|
10960
|
+
for (const match of text.matchAll(pattern)) {
|
|
10961
|
+
const file = normalizePath2(match[1] ?? "");
|
|
10962
|
+
if (file && !file.includes("node_modules/")) out.push(file);
|
|
10963
|
+
}
|
|
10964
|
+
}
|
|
10965
|
+
return dedup3(out);
|
|
10966
|
+
}
|
|
10967
|
+
function extractToolFailures(lines) {
|
|
10968
|
+
return lines.filter((line) => /(?:\b(?:FAIL|failed|denied|exit=[1-9]|Error:)|失败)/iu.test(line)).filter((line) => /\b(?:run_tests|run_program|write_file|replace_in_file|apply_patch|append_file|http_fetch|add_dependency|read_file)\b/u.test(line)).map((line) => oneLine(line)).slice(0, MAX_TOOL_FAILURES);
|
|
10969
|
+
}
|
|
10970
|
+
function extractStatusCodes(text) {
|
|
10971
|
+
const out = [];
|
|
10972
|
+
for (const match of text.matchAll(/\b(?:HTTP\s*)?(401|403|404|408|409|410|422|429|5\d\d)\b/giu)) {
|
|
10973
|
+
out.push(match[1]);
|
|
10974
|
+
}
|
|
10975
|
+
return dedup3(out);
|
|
10976
|
+
}
|
|
10977
|
+
function selectEvidenceLines(text, category, primaryError, failedTests, files, toolFailures, maxEvidence = MAX_EVIDENCE) {
|
|
10978
|
+
const lines = normalizedLines(text);
|
|
10979
|
+
const needles = [
|
|
10980
|
+
primaryError,
|
|
10981
|
+
...failedTests,
|
|
10982
|
+
...files,
|
|
10983
|
+
...toolFailures,
|
|
10984
|
+
category === "network_api_failure" ? "http" : "",
|
|
10985
|
+
category === "missing_output" ? "missing" : "",
|
|
10986
|
+
category === "tool_loop" ? "read-only" : ""
|
|
10987
|
+
].map((item) => item.toLowerCase()).filter(Boolean);
|
|
10988
|
+
const selected = [];
|
|
10989
|
+
for (const line of lines) {
|
|
10990
|
+
const lower = line.toLowerCase();
|
|
10991
|
+
if (needles.some((needle) => lower.includes(needle.slice(0, 80))) || /\b(?:FAILED|Traceback|Error|Exception|AssertionError|SyntaxError|ModuleNotFoundError|pytest exit=|HTTP\s*[45]\d\d|outputs?.*missing)\b/iu.test(line) || /失败/u.test(line)) {
|
|
10992
|
+
selected.push(truncateLine(line, MAX_EVIDENCE_LINE));
|
|
10993
|
+
}
|
|
10994
|
+
}
|
|
10995
|
+
const compact = dedup3(selected).slice(0, maxEvidence);
|
|
10996
|
+
return { lines: compact, omitted: Math.max(0, selected.length - compact.length) };
|
|
10997
|
+
}
|
|
10998
|
+
function normalizedLines(text) {
|
|
10999
|
+
return text.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0).filter((line) => !/^##\s+历史\s+DEBUG/u.test(line)).filter((line) => !/^##\s+修复建议/u.test(line)).filter((line) => !/^prior suggestions:/iu.test(line));
|
|
11000
|
+
}
|
|
11001
|
+
function oneLine(text) {
|
|
11002
|
+
return truncateLine(text.replace(/\s+/gu, " ").trim(), MAX_EVIDENCE_LINE);
|
|
11003
|
+
}
|
|
11004
|
+
function truncateLine(text, max) {
|
|
11005
|
+
if (text.length <= max) return text;
|
|
11006
|
+
return `${text.slice(0, Math.max(0, max - 24))}... [truncated ${text.length - max + 24} chars]`;
|
|
11007
|
+
}
|
|
11008
|
+
function normalizePath2(file) {
|
|
11009
|
+
return file.replace(/\\/g, "/").replace(/^.*?((?:src|tests?|docs)\/)/u, "$1");
|
|
11010
|
+
}
|
|
11011
|
+
function dedup3(items) {
|
|
11012
|
+
return [...new Set(items.filter((item) => String(item ?? "").length > 0))];
|
|
11013
|
+
}
|
|
11014
|
+
|
|
11015
|
+
// src/core/debug_cache.ts
|
|
10089
11016
|
var EMPTY = { version: 1, steps: {} };
|
|
10090
11017
|
function sanitizeDebugFailureLogForPrompt(log) {
|
|
10091
11018
|
const lines = log.split(/\r?\n/u);
|
|
@@ -10097,19 +11024,23 @@ function sanitizeDebugFailureLogForPrompt(log) {
|
|
|
10097
11024
|
mode = "skip-history";
|
|
10098
11025
|
continue;
|
|
10099
11026
|
}
|
|
11027
|
+
if (/^##\s+(?:debug brief|root issue brief|current retry brief)\b/u.test(line)) {
|
|
11028
|
+
mode = "skip-brief";
|
|
11029
|
+
continue;
|
|
11030
|
+
}
|
|
10100
11031
|
if (/^##\s+修复建议/u.test(line)) {
|
|
10101
11032
|
mode = "skip-suggestions";
|
|
10102
11033
|
continue;
|
|
10103
11034
|
}
|
|
10104
11035
|
if (mode === "skip-history") {
|
|
10105
|
-
if (/^(原因:|Reason:|##\s+debug failure log\b)/u.test(line)) {
|
|
11036
|
+
if (/^(原因:|Reason:|##\s+(?:debug failure log|compact failure evidence)\b)/u.test(line)) {
|
|
10106
11037
|
mode = "keep";
|
|
10107
11038
|
} else {
|
|
10108
11039
|
continue;
|
|
10109
11040
|
}
|
|
10110
11041
|
}
|
|
10111
11042
|
if (mode === "skip-suggestions") {
|
|
10112
|
-
if (/^(原因:|Reason:|轮次:|工具调用:|---\s+|##\s+历史\s+DEBUG\s+尝试|##\s+debug failure log\b)/u.test(line)) {
|
|
11043
|
+
if (/^(原因:|Reason:|轮次:|工具调用:|---\s+|##\s+历史\s+DEBUG\s+尝试|##\s+(?:debug failure log|compact failure evidence)\b)/u.test(line)) {
|
|
10113
11044
|
mode = "keep";
|
|
10114
11045
|
if (/^##\s+历史\s+DEBUG\s+尝试/u.test(line)) {
|
|
10115
11046
|
mode = "skip-history";
|
|
@@ -10119,6 +11050,13 @@ function sanitizeDebugFailureLogForPrompt(log) {
|
|
|
10119
11050
|
continue;
|
|
10120
11051
|
}
|
|
10121
11052
|
}
|
|
11053
|
+
if (mode === "skip-brief") {
|
|
11054
|
+
if (/^(原因:|Reason:|轮次:|工具调用:|---\s+|##\s+修复建议|##\s+历史\s+DEBUG\s+尝试|##\s+(?:debug failure log|compact failure evidence)\b)/u.test(line)) {
|
|
11055
|
+
mode = "keep";
|
|
11056
|
+
} else {
|
|
11057
|
+
continue;
|
|
11058
|
+
}
|
|
11059
|
+
}
|
|
10122
11060
|
if (/^\s*suggestions:\s/u.test(line)) continue;
|
|
10123
11061
|
if (/^(原因:|Reason:)/u.test(line)) {
|
|
10124
11062
|
const normalized = line.trim();
|
|
@@ -10156,7 +11094,7 @@ var DebugCache = class {
|
|
|
10156
11094
|
if (this.loaded) return;
|
|
10157
11095
|
this.loaded = true;
|
|
10158
11096
|
try {
|
|
10159
|
-
const raw = await
|
|
11097
|
+
const raw = await fs24.readFile(this.file, "utf8");
|
|
10160
11098
|
const parsed = JSON.parse(raw);
|
|
10161
11099
|
if (parsed && parsed.version === 1 && parsed.steps && typeof parsed.steps === "object") {
|
|
10162
11100
|
this.data = { version: 1, steps: parsed.steps };
|
|
@@ -10184,11 +11122,24 @@ var DebugCache = class {
|
|
|
10184
11122
|
const cleanedFailureLog = stripNestedLatestDebuggerFailures(
|
|
10185
11123
|
sanitizeDebugFailureLogForPrompt(entry.failureLogTail ?? "")
|
|
10186
11124
|
);
|
|
11125
|
+
const debugBrief = buildDebugBrief({
|
|
11126
|
+
reason: entry.reason,
|
|
11127
|
+
failureLog: cleanedFailureLog
|
|
11128
|
+
});
|
|
11129
|
+
const compactFailureLog = compactFailureEvidence({
|
|
11130
|
+
reason: entry.reason,
|
|
11131
|
+
failureLog: cleanedFailureLog,
|
|
11132
|
+
maxChars: 3600,
|
|
11133
|
+
maxLines: this.maxLogLines
|
|
11134
|
+
});
|
|
10187
11135
|
const e = {
|
|
10188
11136
|
attempt: entry.attempt,
|
|
10189
11137
|
ts: entry.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
10190
11138
|
reason: (entry.reason ?? "").slice(0, 500),
|
|
10191
|
-
failureLogTail: tail(
|
|
11139
|
+
failureLogTail: tail(compactFailureLog),
|
|
11140
|
+
debugBrief,
|
|
11141
|
+
contextMode: entry.contextMode,
|
|
11142
|
+
testScopeArgs: entry.testScopeArgs,
|
|
10192
11143
|
suggestions: entry.suggestions,
|
|
10193
11144
|
snapshotSha: entry.snapshotSha,
|
|
10194
11145
|
metrics: entry.metrics
|
|
@@ -10253,8 +11204,10 @@ var DebugCache = class {
|
|
|
10253
11204
|
const m = e.metrics ? ` [health=${e.metrics.healthScore.toFixed(2)} parseFail=${e.metrics.parseFailures} repeat=${e.metrics.repeatedTurns}]` : "";
|
|
10254
11205
|
const sugg = e.suggestions && e.suggestions.length > 0 ? `
|
|
10255
11206
|
prior suggestions: omitted (${e.suggestions.length}) to avoid stale guidance; use the current failure log first.` : "";
|
|
11207
|
+
const brief = e.debugBrief ? `
|
|
11208
|
+
brief: ${e.debugBrief.category} \u2014 ${e.debugBrief.summary}` : "";
|
|
10256
11209
|
return `- attempt #${e.attempt} @ ${e.ts}${m}
|
|
10257
|
-
reason: ${e.reason}${sugg}`;
|
|
11210
|
+
reason: ${e.reason}${brief}${sugg}`;
|
|
10258
11211
|
});
|
|
10259
11212
|
const omitted = [
|
|
10260
11213
|
noisyCount > 0 ? `- omitted ${noisyCount} noisy provider/read-only/recovery attempt(s); keep focus on the current actionable failure.` : "",
|
|
@@ -10270,18 +11223,609 @@ var DebugCache = class {
|
|
|
10270
11223
|
].join("\n");
|
|
10271
11224
|
}
|
|
10272
11225
|
async save() {
|
|
10273
|
-
await
|
|
10274
|
-
await
|
|
11226
|
+
await fs24.mkdir(path24.dirname(this.file), { recursive: true });
|
|
11227
|
+
await fs24.writeFile(this.file, JSON.stringify(this.data, null, 2), "utf8");
|
|
10275
11228
|
}
|
|
10276
11229
|
};
|
|
10277
11230
|
function isNoisyDebugReason(reason) {
|
|
10278
11231
|
const text = reason.toLowerCase();
|
|
10279
|
-
return /repeated read-only\/probe actions without progress/u.test(text) || /read-only recovery mode repeated probe actions/u.test(text) || /low-quality debugger response/u.test(text) || /without repair evidence/u.test(text) || /had an unresolved failure from a previous run.*rolling back/u.test(text) || /tool verification failed.*rolling back/u.test(text) || /openai http (?:400|401|403|408|409|429|5\d\d)/u.test(text) || /rate limit exceeded|free-models-per-day|retry_after_seconds|retry-after/u.test(text) || /response_format|json_object|json_schema|invalid_request_body/u.test(text) || /stream (?:wall-clock|idle)|request timed out|fetch failed/u.test(text);
|
|
11232
|
+
return /repeated read-only\/probe actions without progress/u.test(text) || /read-only recovery mode repeated probe actions/u.test(text) || /low-quality debugger response/u.test(text) || /script exhausted/u.test(text) || /without repair evidence/u.test(text) || /without a successful repair mutation or verification tool call/u.test(text) || /had an unresolved failure from a previous run.*rolling back/u.test(text) || /tool verification failed.*rolling back/u.test(text) || /openai http (?:400|401|403|408|409|429|5\d\d)/u.test(text) || /rate limit exceeded|free-models-per-day|retry_after_seconds|retry-after/u.test(text) || /response_format|json_object|json_schema|invalid_request_body/u.test(text) || /stream (?:wall-clock|idle)|request timed out|fetch failed/u.test(text);
|
|
10280
11233
|
}
|
|
10281
11234
|
|
|
10282
|
-
// src/core/
|
|
10283
|
-
import { promises as
|
|
11235
|
+
// src/core/debug_wiki.ts
|
|
11236
|
+
import { promises as fs25 } from "fs";
|
|
10284
11237
|
import path25 from "path";
|
|
11238
|
+
import { fileURLToPath } from "url";
|
|
11239
|
+
import YAML3 from "yaml";
|
|
11240
|
+
var DEFAULT_DEBUG_WIKI_REL_PATH = ".xcompiler/debug-wiki";
|
|
11241
|
+
var BUNDLED_DEBUG_WIKI_REL_PATH = "debug-wiki";
|
|
11242
|
+
var DEBUG_WIKI_VERSION = 1;
|
|
11243
|
+
var LAYERS = ["system", "agent", "external"];
|
|
11244
|
+
var EMPTY_STATS = { uses: 0, successes: 0, failures: 0 };
|
|
11245
|
+
function defaultDebugWikiPath() {
|
|
11246
|
+
const configured = xcEnv("PATH")?.trim();
|
|
11247
|
+
const base = configured ? path25.resolve(configured) : path25.resolve(path25.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
11248
|
+
return path25.join(base, DEFAULT_DEBUG_WIKI_REL_PATH);
|
|
11249
|
+
}
|
|
11250
|
+
function bundledDebugWikiPath() {
|
|
11251
|
+
return path25.join(path25.resolve(path25.dirname(fileURLToPath(import.meta.url)), "..", ".."), BUNDLED_DEBUG_WIKI_REL_PATH);
|
|
11252
|
+
}
|
|
11253
|
+
var DebugWiki = class {
|
|
11254
|
+
loaded = false;
|
|
11255
|
+
entries = [];
|
|
11256
|
+
rootPath;
|
|
11257
|
+
filePath;
|
|
11258
|
+
bundledPath;
|
|
11259
|
+
constructor(rootPath, opts = {}) {
|
|
11260
|
+
this.rootPath = path25.resolve(rootPath);
|
|
11261
|
+
this.filePath = this.rootPath;
|
|
11262
|
+
this.bundledPath = opts.bundledPath ?? bundledDebugWikiPath();
|
|
11263
|
+
}
|
|
11264
|
+
async load() {
|
|
11265
|
+
if (this.loaded) return;
|
|
11266
|
+
this.loaded = true;
|
|
11267
|
+
await this.ensureLayout();
|
|
11268
|
+
await this.ensureRootReadme();
|
|
11269
|
+
await this.ensureOperationLog();
|
|
11270
|
+
await this.copyBundledLayers();
|
|
11271
|
+
this.entries = [];
|
|
11272
|
+
for (const layer of LAYERS) {
|
|
11273
|
+
this.entries.push(...await this.readLayer(layer));
|
|
11274
|
+
}
|
|
11275
|
+
await this.applyFeedbackLog();
|
|
11276
|
+
await this.writeIndex();
|
|
11277
|
+
}
|
|
11278
|
+
async search(brief, opts = {}) {
|
|
11279
|
+
await this.load();
|
|
11280
|
+
const limit = opts.limit ?? 3;
|
|
11281
|
+
return this.rank(brief, opts.language).filter((match) => match.score >= 4).slice(0, limit);
|
|
11282
|
+
}
|
|
11283
|
+
async recordUse(entryIds, input2) {
|
|
11284
|
+
await this.load();
|
|
11285
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11286
|
+
const feedback = this.feedbackFrom(entryIds, input2, now, "used");
|
|
11287
|
+
if (feedback.length === 0) return;
|
|
11288
|
+
for (const item of feedback) {
|
|
11289
|
+
const entry = this.byId(item.entryId);
|
|
11290
|
+
if (!entry) continue;
|
|
11291
|
+
entry.stats.uses += 1;
|
|
11292
|
+
entry.lastUsedAt = now;
|
|
11293
|
+
entry.updatedAt = now;
|
|
11294
|
+
pushFeedback(entry, item);
|
|
11295
|
+
}
|
|
11296
|
+
await this.appendLayerFeedback(feedback);
|
|
11297
|
+
await this.persistExternalEntries();
|
|
11298
|
+
await this.writeIndex(now);
|
|
11299
|
+
await this.appendOperationLog({
|
|
11300
|
+
at: now,
|
|
11301
|
+
action: "use",
|
|
11302
|
+
entryIds: feedback.map((item) => item.entryId).filter(Boolean),
|
|
11303
|
+
issueId: input2.issueId,
|
|
11304
|
+
stepId: input2.stepId,
|
|
11305
|
+
phase: input2.phase,
|
|
11306
|
+
summary: input2.brief.summary
|
|
11307
|
+
});
|
|
11308
|
+
}
|
|
11309
|
+
async recordFailure(entryIds, input2) {
|
|
11310
|
+
await this.load();
|
|
11311
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11312
|
+
const feedback = this.feedbackFrom(entryIds, input2, now, "failure", input2.reason);
|
|
11313
|
+
if (feedback.length === 0) return;
|
|
11314
|
+
for (const item of feedback) {
|
|
11315
|
+
const entry = this.byId(item.entryId);
|
|
11316
|
+
if (!entry) continue;
|
|
11317
|
+
entry.stats.failures += 1;
|
|
11318
|
+
entry.status = "needs_review";
|
|
11319
|
+
entry.updatedAt = now;
|
|
11320
|
+
pushFeedback(entry, item);
|
|
11321
|
+
}
|
|
11322
|
+
await this.appendLayerFeedback(feedback);
|
|
11323
|
+
await this.persistExternalEntries();
|
|
11324
|
+
await this.writeIndex(now);
|
|
11325
|
+
await this.appendOperationLog({
|
|
11326
|
+
at: now,
|
|
11327
|
+
action: "failure",
|
|
11328
|
+
entryIds: feedback.map((item) => item.entryId).filter(Boolean),
|
|
11329
|
+
issueId: input2.issueId,
|
|
11330
|
+
stepId: input2.stepId,
|
|
11331
|
+
phase: input2.phase,
|
|
11332
|
+
summary: input2.brief.summary,
|
|
11333
|
+
reason: input2.reason
|
|
11334
|
+
});
|
|
11335
|
+
}
|
|
11336
|
+
async recordResolution(input2) {
|
|
11337
|
+
await this.load();
|
|
11338
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11339
|
+
const used = this.byIds(input2.usedEntryIds ?? []);
|
|
11340
|
+
const externalTargets = used.filter((entry) => entry.layer === "external");
|
|
11341
|
+
const target = externalTargets.length > 0 ? externalTargets : this.rank(input2.brief, input2.language).filter((match) => match.entry.layer === "external" && match.score >= 8).slice(0, 1).map((m) => m.entry);
|
|
11342
|
+
const updated = [];
|
|
11343
|
+
let createdId;
|
|
11344
|
+
for (const entry of target) {
|
|
11345
|
+
this.applyResolution(entry, input2, now, entry.stats.failures > 0 ? "corrected" : "success");
|
|
11346
|
+
updated.push(entry.id);
|
|
11347
|
+
}
|
|
11348
|
+
if (updated.length === 0) {
|
|
11349
|
+
const created = createEntry(input2, now, this.nextExternalId(now), "external");
|
|
11350
|
+
created.supersedes = used.length > 0 ? used.map((entry) => entry.id) : void 0;
|
|
11351
|
+
this.entries.push(created);
|
|
11352
|
+
createdId = created.id;
|
|
11353
|
+
}
|
|
11354
|
+
const correctedFeedback = this.feedbackFrom(
|
|
11355
|
+
used.filter((entry) => entry.layer !== "external").map((entry) => entry.id),
|
|
11356
|
+
input2,
|
|
11357
|
+
now,
|
|
11358
|
+
"corrected"
|
|
11359
|
+
);
|
|
11360
|
+
for (const item of correctedFeedback) {
|
|
11361
|
+
const entry = this.byId(item.entryId);
|
|
11362
|
+
if (!entry) continue;
|
|
11363
|
+
entry.stats.successes += 1;
|
|
11364
|
+
entry.status = "active";
|
|
11365
|
+
entry.updatedAt = now;
|
|
11366
|
+
pushFeedback(entry, item);
|
|
11367
|
+
}
|
|
11368
|
+
await this.appendLayerFeedback(correctedFeedback);
|
|
11369
|
+
await this.persistExternalEntries();
|
|
11370
|
+
await this.writeIndex(now);
|
|
11371
|
+
await this.appendOperationLog({
|
|
11372
|
+
at: now,
|
|
11373
|
+
action: createdId ? "resolution_created" : "resolution_updated",
|
|
11374
|
+
entryIds: createdId ? [createdId] : updated,
|
|
11375
|
+
issueId: input2.issueId,
|
|
11376
|
+
stepId: input2.stepId,
|
|
11377
|
+
phase: input2.phase,
|
|
11378
|
+
summary: input2.brief.summary
|
|
11379
|
+
});
|
|
11380
|
+
return createdId ? { created: createdId, updated: [] } : { updated };
|
|
11381
|
+
}
|
|
11382
|
+
async ensureLayout() {
|
|
11383
|
+
for (const layer of LAYERS) {
|
|
11384
|
+
await fs25.mkdir(this.layerDir(layer), { recursive: true });
|
|
11385
|
+
}
|
|
11386
|
+
}
|
|
11387
|
+
async ensureRootReadme() {
|
|
11388
|
+
const to = path25.join(this.rootPath, "README.md");
|
|
11389
|
+
if (await exists(to)) return;
|
|
11390
|
+
const from = path25.join(this.bundledPath, "README.md");
|
|
11391
|
+
const fallback = defaultDebugWikiReadme();
|
|
11392
|
+
const text = await fs25.readFile(from, "utf8").catch(() => fallback);
|
|
11393
|
+
await fs25.writeFile(to, text.endsWith("\n") ? text : `${text}
|
|
11394
|
+
`, "utf8");
|
|
11395
|
+
}
|
|
11396
|
+
async ensureOperationLog() {
|
|
11397
|
+
const file = this.operationLogPath();
|
|
11398
|
+
if (await exists(file)) return;
|
|
11399
|
+
await fs25.writeFile(file, "# XCompiler Debug Wiki Log\n\nAppend-only operational notes for retrieval, failed reuse, and confirmed repairs.\n", "utf8");
|
|
11400
|
+
}
|
|
11401
|
+
async copyBundledLayers() {
|
|
11402
|
+
if (path25.resolve(this.bundledPath) === this.rootPath) return;
|
|
11403
|
+
for (const layer of ["system", "agent"]) {
|
|
11404
|
+
const from = path25.join(this.bundledPath, "wiki", layer);
|
|
11405
|
+
const to = this.layerDir(layer);
|
|
11406
|
+
if (!await exists(from)) continue;
|
|
11407
|
+
await fs25.cp(from, to, { recursive: true, force: true });
|
|
11408
|
+
}
|
|
11409
|
+
}
|
|
11410
|
+
async readLayer(layer) {
|
|
11411
|
+
const dir = this.layerDir(layer);
|
|
11412
|
+
const files = (await fs25.readdir(dir).catch(() => [])).filter((file) => file.endsWith(".md")).sort();
|
|
11413
|
+
const entries = [];
|
|
11414
|
+
for (const file of files) {
|
|
11415
|
+
const abs = path25.join(dir, file);
|
|
11416
|
+
const page = parseWikiPage(await fs25.readFile(abs, "utf8"));
|
|
11417
|
+
const entry = normalizeEntry({ ...page.data, layer, solution: page.data.solution ?? page.body }, layer);
|
|
11418
|
+
entry.sourcePath = path25.relative(this.rootPath, abs).replace(/\\/g, "/");
|
|
11419
|
+
entries.push(entry);
|
|
11420
|
+
}
|
|
11421
|
+
return entries;
|
|
11422
|
+
}
|
|
11423
|
+
async applyFeedbackLog() {
|
|
11424
|
+
const log = await fs25.readFile(this.feedbackPath(), "utf8").catch(() => "");
|
|
11425
|
+
for (const line of log.split(/\r?\n/u)) {
|
|
11426
|
+
if (!line.trim()) continue;
|
|
11427
|
+
const item = JSON.parse(line);
|
|
11428
|
+
const entry = this.byId(item.entryId);
|
|
11429
|
+
if (!entry) continue;
|
|
11430
|
+
if (item.kind === "used") entry.stats.uses += 1;
|
|
11431
|
+
if (item.kind === "failure") {
|
|
11432
|
+
entry.stats.failures += 1;
|
|
11433
|
+
entry.status = "needs_review";
|
|
11434
|
+
}
|
|
11435
|
+
if (item.kind === "success" || item.kind === "corrected") {
|
|
11436
|
+
entry.stats.successes += 1;
|
|
11437
|
+
if (item.kind === "corrected") entry.status = "active";
|
|
11438
|
+
}
|
|
11439
|
+
entry.updatedAt = item.at;
|
|
11440
|
+
pushFeedback(entry, item);
|
|
11441
|
+
}
|
|
11442
|
+
}
|
|
11443
|
+
applyResolution(entry, input2, now, kind) {
|
|
11444
|
+
entry.status = "active";
|
|
11445
|
+
entry.updatedAt = now;
|
|
11446
|
+
entry.summary = input2.brief.summary;
|
|
11447
|
+
entry.primaryError = input2.brief.primaryError;
|
|
11448
|
+
entry.debugDemand = input2.brief.debugDemand;
|
|
11449
|
+
entry.fingerprints = dedup4([...entry.fingerprints, ...fingerprints(input2.brief)]);
|
|
11450
|
+
entry.symptoms = dedup4([...input2.brief.evidence, ...entry.symptoms]).slice(0, 12);
|
|
11451
|
+
entry.evidence = dedup4([...input2.evidence ?? [], ...entry.evidence]).slice(0, 12);
|
|
11452
|
+
if (input2.resolutionPlan?.trim()) entry.resolutionPlan = input2.resolutionPlan.trim();
|
|
11453
|
+
entry.solution = mergeSolution(entry.solution, input2.solution);
|
|
11454
|
+
entry.repairFiles = dedup4([...input2.repairFiles ?? [], ...entry.repairFiles ?? []]).slice(0, 12);
|
|
11455
|
+
entry.stats.successes += 1;
|
|
11456
|
+
pushFeedback(entry, {
|
|
11457
|
+
at: now,
|
|
11458
|
+
kind,
|
|
11459
|
+
entryId: entry.id,
|
|
11460
|
+
issueId: input2.issueId,
|
|
11461
|
+
stepId: input2.stepId,
|
|
11462
|
+
phase: input2.phase,
|
|
11463
|
+
summary: input2.brief.summary
|
|
11464
|
+
});
|
|
11465
|
+
}
|
|
11466
|
+
async persistExternalEntries() {
|
|
11467
|
+
for (const entry of this.entries.filter((item) => item.layer === "external")) {
|
|
11468
|
+
const abs = path25.join(this.rootPath, entry.sourcePath ?? this.externalEntryPath(entry));
|
|
11469
|
+
entry.sourcePath = path25.relative(this.rootPath, abs).replace(/\\/g, "/");
|
|
11470
|
+
await fs25.mkdir(path25.dirname(abs), { recursive: true });
|
|
11471
|
+
await fs25.writeFile(abs, renderWikiPage(entry), "utf8");
|
|
11472
|
+
}
|
|
11473
|
+
}
|
|
11474
|
+
async appendFeedback(feedback) {
|
|
11475
|
+
if (feedback.length === 0) return;
|
|
11476
|
+
await fs25.mkdir(path25.dirname(this.feedbackPath()), { recursive: true });
|
|
11477
|
+
await fs25.appendFile(this.feedbackPath(), feedback.map((item) => JSON.stringify(item)).join("\n") + "\n", "utf8");
|
|
11478
|
+
}
|
|
11479
|
+
async appendLayerFeedback(feedback) {
|
|
11480
|
+
await this.appendFeedback(feedback.filter((item) => this.byId(item.entryId)?.layer !== "external"));
|
|
11481
|
+
}
|
|
11482
|
+
async appendOperationLog(entry) {
|
|
11483
|
+
await fs25.mkdir(path25.dirname(this.operationLogPath()), { recursive: true });
|
|
11484
|
+
await fs25.appendFile(this.operationLogPath(), renderOperationLogEntry(entry), "utf8");
|
|
11485
|
+
}
|
|
11486
|
+
async writeIndex(now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
11487
|
+
const layerCounts = Object.fromEntries(LAYERS.map((layer) => [
|
|
11488
|
+
layer,
|
|
11489
|
+
{ entries: this.entries.filter((entry) => entry.layer === layer).length, writable: layer === "external" }
|
|
11490
|
+
]));
|
|
11491
|
+
const index = {
|
|
11492
|
+
version: DEBUG_WIKI_VERSION,
|
|
11493
|
+
updatedAt: now,
|
|
11494
|
+
root: this.rootPath,
|
|
11495
|
+
layers: layerCounts,
|
|
11496
|
+
entries: this.entries.map((entry) => ({
|
|
11497
|
+
id: entry.id,
|
|
11498
|
+
layer: entry.layer,
|
|
11499
|
+
status: entry.status,
|
|
11500
|
+
category: entry.category,
|
|
11501
|
+
summary: entry.summary,
|
|
11502
|
+
updatedAt: entry.updatedAt,
|
|
11503
|
+
sourcePath: entry.sourcePath
|
|
11504
|
+
}))
|
|
11505
|
+
};
|
|
11506
|
+
await fs25.writeFile(path25.join(this.rootPath, "index.json"), `${JSON.stringify(index, null, 2)}
|
|
11507
|
+
`, "utf8");
|
|
11508
|
+
await fs25.writeFile(path25.join(this.rootPath, "index.md"), renderReadableIndex(index, this.entries), "utf8");
|
|
11509
|
+
}
|
|
11510
|
+
feedbackFrom(ids, input2, now, kind, reason) {
|
|
11511
|
+
return dedup4(ids).filter((id) => this.byId(id)).map((id) => ({
|
|
11512
|
+
at: now,
|
|
11513
|
+
kind,
|
|
11514
|
+
entryId: id,
|
|
11515
|
+
issueId: input2.issueId,
|
|
11516
|
+
stepId: input2.stepId,
|
|
11517
|
+
phase: input2.phase,
|
|
11518
|
+
summary: input2.brief.summary,
|
|
11519
|
+
reason
|
|
11520
|
+
}));
|
|
11521
|
+
}
|
|
11522
|
+
rank(brief, language) {
|
|
11523
|
+
const queryTokens = new Set(tokensForBrief(brief));
|
|
11524
|
+
const queryFingerprints = fingerprints(brief);
|
|
11525
|
+
return this.entries.filter((entry) => entry.status !== "superseded").map((entry) => {
|
|
11526
|
+
const reasons = [];
|
|
11527
|
+
let score = entry.layer === "agent" ? 1 : entry.layer === "system" ? 0.5 : 0;
|
|
11528
|
+
if (entry.category === brief.category) {
|
|
11529
|
+
score += 4;
|
|
11530
|
+
reasons.push(`category:${entry.category}`);
|
|
11531
|
+
}
|
|
11532
|
+
if (language && entry.language === language) score += 1;
|
|
11533
|
+
const exact = entry.fingerprints.filter((fp) => queryFingerprints.includes(fp));
|
|
11534
|
+
if (exact.length > 0) {
|
|
11535
|
+
score += exact.length * 3;
|
|
11536
|
+
reasons.push(`fingerprint:${exact.length}`);
|
|
11537
|
+
}
|
|
11538
|
+
const entryTokens = new Set(tokensForEntry(entry));
|
|
11539
|
+
let overlap = 0;
|
|
11540
|
+
for (const token of queryTokens) if (entryTokens.has(token)) overlap++;
|
|
11541
|
+
score += Math.min(6, overlap);
|
|
11542
|
+
if (overlap > 0) reasons.push(`tokens:${overlap}`);
|
|
11543
|
+
const confidence = confidenceFor(entry);
|
|
11544
|
+
return { entry, score: score * confidence, confidence, reasons };
|
|
11545
|
+
}).sort((a, b) => b.score - a.score);
|
|
11546
|
+
}
|
|
11547
|
+
byId(id) {
|
|
11548
|
+
return this.entries.find((entry) => entry.id === id);
|
|
11549
|
+
}
|
|
11550
|
+
byIds(ids) {
|
|
11551
|
+
const wanted = new Set(ids);
|
|
11552
|
+
return this.entries.filter((entry) => wanted.has(entry.id));
|
|
11553
|
+
}
|
|
11554
|
+
layerDir(layer) {
|
|
11555
|
+
return path25.join(this.rootPath, "wiki", layer);
|
|
11556
|
+
}
|
|
11557
|
+
feedbackPath() {
|
|
11558
|
+
return path25.join(this.rootPath, "wiki", "external", "feedback.jsonl");
|
|
11559
|
+
}
|
|
11560
|
+
operationLogPath() {
|
|
11561
|
+
return path25.join(this.rootPath, "log.md");
|
|
11562
|
+
}
|
|
11563
|
+
nextExternalId(now) {
|
|
11564
|
+
const stamp = now.replace(/[-:.TZ]/g, "").slice(0, 14);
|
|
11565
|
+
const count = this.entries.filter((entry) => entry.layer === "external").length + 1;
|
|
11566
|
+
return `external.${stamp}.${String(count).padStart(4, "0")}`;
|
|
11567
|
+
}
|
|
11568
|
+
externalEntryPath(entry) {
|
|
11569
|
+
return path25.join("wiki", "external", `${slugify(entry.id)}.md`);
|
|
11570
|
+
}
|
|
11571
|
+
};
|
|
11572
|
+
function renderDebugWikiMatchesForPrompt(matches) {
|
|
11573
|
+
if (matches.length === 0) return "";
|
|
11574
|
+
const lines = [
|
|
11575
|
+
"## debug wiki matches",
|
|
11576
|
+
"LLM-wiki layered retrieval. Treat entries as hypotheses, verify against current files/tests, and stop using any entry that current evidence disproves."
|
|
11577
|
+
];
|
|
11578
|
+
for (const match of matches) {
|
|
11579
|
+
const entry = match.entry;
|
|
11580
|
+
lines.push(
|
|
11581
|
+
`- ${entry.id} layer=${entry.layer} score=${match.score.toFixed(2)} confidence=${match.confidence.toFixed(2)} status=${entry.status}`,
|
|
11582
|
+
` problem: [${entry.category}] ${entry.summary}`,
|
|
11583
|
+
` symptoms: ${entry.symptoms.slice(0, 4).join(" | ") || entry.primaryError}`,
|
|
11584
|
+
entry.resolutionPlan ? ` priorPlan: ${entry.resolutionPlan}` : "",
|
|
11585
|
+
` confirmedSolution: ${entry.solution}`,
|
|
11586
|
+
` feedback: uses=${entry.stats.uses} successes=${entry.stats.successes} failures=${entry.stats.failures}`
|
|
11587
|
+
);
|
|
11588
|
+
if (entry.repairFiles?.length) lines.push(` repairFiles: ${entry.repairFiles.join(", ")}`);
|
|
11589
|
+
if (entry.supersedes?.length) lines.push(` supersedes: ${entry.supersedes.join(", ")}`);
|
|
11590
|
+
if (match.reasons.length) lines.push(` matchedBy: ${match.reasons.join(", ")}`);
|
|
11591
|
+
}
|
|
11592
|
+
return lines.filter(Boolean).join("\n");
|
|
11593
|
+
}
|
|
11594
|
+
function createEntry(input2, now, id, layer) {
|
|
11595
|
+
return normalizeEntry({
|
|
11596
|
+
id,
|
|
11597
|
+
layer,
|
|
11598
|
+
createdAt: now,
|
|
11599
|
+
updatedAt: now,
|
|
11600
|
+
status: "active",
|
|
11601
|
+
category: input2.brief.category,
|
|
11602
|
+
summary: input2.brief.summary,
|
|
11603
|
+
primaryError: input2.brief.primaryError,
|
|
11604
|
+
debugDemand: input2.brief.debugDemand,
|
|
11605
|
+
fingerprints: fingerprints(input2.brief),
|
|
11606
|
+
symptoms: input2.brief.evidence.slice(0, 12),
|
|
11607
|
+
resolutionPlan: input2.resolutionPlan?.trim(),
|
|
11608
|
+
solution: input2.solution,
|
|
11609
|
+
evidence: (input2.evidence ?? input2.brief.evidence).slice(0, 12),
|
|
11610
|
+
sourceIssueId: input2.issueId,
|
|
11611
|
+
sourceStepId: input2.stepId,
|
|
11612
|
+
sourcePhase: input2.phase,
|
|
11613
|
+
targetPhase: input2.targetPhase,
|
|
11614
|
+
language: input2.language,
|
|
11615
|
+
repairFiles: input2.repairFiles?.slice(0, 12),
|
|
11616
|
+
stats: { uses: 0, successes: 1, failures: 0 },
|
|
11617
|
+
feedback: [{ at: now, kind: "success", entryId: id, issueId: input2.issueId, stepId: input2.stepId, phase: input2.phase, summary: input2.brief.summary }]
|
|
11618
|
+
}, layer);
|
|
11619
|
+
}
|
|
11620
|
+
function normalizeEntry(raw, layer) {
|
|
11621
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
11622
|
+
return {
|
|
11623
|
+
id: String(raw.id ?? `${layer}.${slugify(raw.summary ?? raw.primaryError ?? "entry")}`),
|
|
11624
|
+
layer: raw.layer ?? layer,
|
|
11625
|
+
createdAt: raw.createdAt ?? now,
|
|
11626
|
+
updatedAt: raw.updatedAt ?? raw.createdAt ?? now,
|
|
11627
|
+
status: raw.status ?? "active",
|
|
11628
|
+
category: raw.category ?? "unknown",
|
|
11629
|
+
summary: raw.summary ?? raw.primaryError ?? "Debug wiki entry",
|
|
11630
|
+
primaryError: raw.primaryError ?? raw.summary ?? "",
|
|
11631
|
+
debugDemand: raw.debugDemand ?? "",
|
|
11632
|
+
fingerprints: raw.fingerprints ?? [],
|
|
11633
|
+
symptoms: raw.symptoms ?? [],
|
|
11634
|
+
resolutionPlan: raw.resolutionPlan,
|
|
11635
|
+
solution: raw.solution ?? "",
|
|
11636
|
+
evidence: raw.evidence ?? [],
|
|
11637
|
+
sourceIssueId: raw.sourceIssueId,
|
|
11638
|
+
sourceStepId: raw.sourceStepId,
|
|
11639
|
+
sourcePhase: raw.sourcePhase,
|
|
11640
|
+
targetPhase: raw.targetPhase,
|
|
11641
|
+
language: raw.language,
|
|
11642
|
+
repairFiles: raw.repairFiles ?? [],
|
|
11643
|
+
supersedes: raw.supersedes ?? [],
|
|
11644
|
+
stats: { ...EMPTY_STATS, ...raw.stats ?? {} },
|
|
11645
|
+
lastUsedAt: raw.lastUsedAt,
|
|
11646
|
+
feedback: (raw.feedback ?? []).slice(-20),
|
|
11647
|
+
sourcePath: raw.sourcePath
|
|
11648
|
+
};
|
|
11649
|
+
}
|
|
11650
|
+
function renderWikiPage(entry) {
|
|
11651
|
+
const frontmatter = { ...entry, sourcePath: void 0 };
|
|
11652
|
+
return [
|
|
11653
|
+
"---",
|
|
11654
|
+
YAML3.stringify(frontmatter).trim(),
|
|
11655
|
+
"---",
|
|
11656
|
+
"",
|
|
11657
|
+
`# ${entry.summary}`,
|
|
11658
|
+
"",
|
|
11659
|
+
"## Problem",
|
|
11660
|
+
"",
|
|
11661
|
+
`- category: ${entry.category}`,
|
|
11662
|
+
`- status: ${entry.status}`,
|
|
11663
|
+
`- primaryError: ${entry.primaryError || "n/a"}`,
|
|
11664
|
+
`- debugDemand: ${entry.debugDemand || "n/a"}`,
|
|
11665
|
+
"",
|
|
11666
|
+
"## Resolution Plan",
|
|
11667
|
+
"",
|
|
11668
|
+
entry.resolutionPlan?.trim() || "No explicit plan recorded.",
|
|
11669
|
+
"",
|
|
11670
|
+
"## Confirmed Solution",
|
|
11671
|
+
"",
|
|
11672
|
+
entry.solution.trim() || "No confirmed solution recorded.",
|
|
11673
|
+
"",
|
|
11674
|
+
"## Evidence",
|
|
11675
|
+
"",
|
|
11676
|
+
renderMarkdownList(entry.evidence),
|
|
11677
|
+
"",
|
|
11678
|
+
"## Retrieval",
|
|
11679
|
+
"",
|
|
11680
|
+
`- fingerprints: ${entry.fingerprints.join(", ") || "n/a"}`,
|
|
11681
|
+
`- repairFiles: ${(entry.repairFiles ?? []).join(", ") || "n/a"}`,
|
|
11682
|
+
`- stats: uses=${entry.stats.uses} successes=${entry.stats.successes} failures=${entry.stats.failures}`,
|
|
11683
|
+
"",
|
|
11684
|
+
"## Feedback",
|
|
11685
|
+
"",
|
|
11686
|
+
renderMarkdownList(entry.feedback.slice(-8).map((item) => `${item.at} ${item.kind}: ${item.summary}`)),
|
|
11687
|
+
""
|
|
11688
|
+
].join("\n");
|
|
11689
|
+
}
|
|
11690
|
+
function parseWikiPage(text) {
|
|
11691
|
+
const match = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/u);
|
|
11692
|
+
if (!match) return { data: {}, body: text.trim() };
|
|
11693
|
+
return { data: YAML3.parse(match[1] ?? "") ?? {}, body: (match[2] ?? "").trim() };
|
|
11694
|
+
}
|
|
11695
|
+
function fingerprints(brief) {
|
|
11696
|
+
return dedup4([
|
|
11697
|
+
`cat:${brief.category}`,
|
|
11698
|
+
brief.primaryError ? `err:${normalize(brief.primaryError)}` : "",
|
|
11699
|
+
...brief.failedTests.map((test) => `test:${normalize(test)}`),
|
|
11700
|
+
...brief.files.map((file) => `file:${normalize(file)}`),
|
|
11701
|
+
...brief.statusCodes.map((code) => `http:${code}`)
|
|
11702
|
+
]);
|
|
11703
|
+
}
|
|
11704
|
+
function tokensForBrief(brief) {
|
|
11705
|
+
return tokenize2([brief.summary, brief.primaryError, brief.debugDemand, ...brief.failedTests, ...brief.files, ...brief.evidence, ...brief.statusCodes].join(" "));
|
|
11706
|
+
}
|
|
11707
|
+
function tokensForEntry(entry) {
|
|
11708
|
+
return tokenize2([entry.id, entry.summary, entry.primaryError, entry.debugDemand, entry.resolutionPlan ?? "", entry.solution, ...entry.symptoms, ...entry.evidence, ...entry.repairFiles ?? []].join(" "));
|
|
11709
|
+
}
|
|
11710
|
+
function tokenize2(text) {
|
|
11711
|
+
return dedup4(text.toLowerCase().split(/[^a-z0-9_./:-]+/u).filter((token) => token.length >= 3));
|
|
11712
|
+
}
|
|
11713
|
+
function normalize(text) {
|
|
11714
|
+
return tokenize2(text).slice(0, 24).join(" ");
|
|
11715
|
+
}
|
|
11716
|
+
function renderReadableIndex(index, entries) {
|
|
11717
|
+
const lines = [
|
|
11718
|
+
"# XCompiler Debug Wiki Index",
|
|
11719
|
+
"",
|
|
11720
|
+
`Updated: ${index.updatedAt}`,
|
|
11721
|
+
"",
|
|
11722
|
+
"This file is regenerated from wiki pages and feedback overlays. Edit knowledge pages under `wiki/`, not this index.",
|
|
11723
|
+
"",
|
|
11724
|
+
"## Layers",
|
|
11725
|
+
"",
|
|
11726
|
+
"| Layer | Entries | Writable | Purpose |",
|
|
11727
|
+
"| --- | ---: | --- | --- |"
|
|
11728
|
+
];
|
|
11729
|
+
for (const layer of LAYERS) {
|
|
11730
|
+
const info = index.layers[layer];
|
|
11731
|
+
lines.push(`| ${layer} | ${info.entries} | ${info.writable ? "yes" : "no"} | ${layerPurpose(layer)} |`);
|
|
11732
|
+
}
|
|
11733
|
+
for (const layer of LAYERS) {
|
|
11734
|
+
const layerEntries = entries.filter((entry) => entry.layer === layer);
|
|
11735
|
+
lines.push("", `## ${layer}`, "");
|
|
11736
|
+
if (layerEntries.length === 0) {
|
|
11737
|
+
lines.push("No entries.");
|
|
11738
|
+
continue;
|
|
11739
|
+
}
|
|
11740
|
+
lines.push("| ID | Status | Category | Summary | Source |", "| --- | --- | --- | --- | --- |");
|
|
11741
|
+
for (const entry of layerEntries) {
|
|
11742
|
+
const source = entry.sourcePath ? `[${entry.sourcePath}](${entry.sourcePath})` : "";
|
|
11743
|
+
lines.push(`| ${escapeTable(entry.id)} | ${entry.status} | ${entry.category} | ${escapeTable(entry.summary)} | ${source} |`);
|
|
11744
|
+
}
|
|
11745
|
+
}
|
|
11746
|
+
return `${lines.join("\n")}
|
|
11747
|
+
`;
|
|
11748
|
+
}
|
|
11749
|
+
function renderOperationLogEntry(entry) {
|
|
11750
|
+
const lines = [
|
|
11751
|
+
"",
|
|
11752
|
+
`- ${entry.at} ${entry.action}: ${entry.entryIds.join(", ") || "none"}`,
|
|
11753
|
+
` - issue: ${entry.issueId ?? "n/a"}; step: ${entry.stepId ?? "n/a"}; phase: ${entry.phase ?? "n/a"}`,
|
|
11754
|
+
` - summary: ${entry.summary}`
|
|
11755
|
+
];
|
|
11756
|
+
if (entry.reason) lines.push(` - reason: ${entry.reason}`);
|
|
11757
|
+
return `${lines.join("\n")}
|
|
11758
|
+
`;
|
|
11759
|
+
}
|
|
11760
|
+
function defaultDebugWikiReadme() {
|
|
11761
|
+
return [
|
|
11762
|
+
"# XCompiler Debug Wiki",
|
|
11763
|
+
"",
|
|
11764
|
+
"This directory is an LLM-wiki style knowledge base for Debugger repair.",
|
|
11765
|
+
"",
|
|
11766
|
+
"- `wiki/system/` contains system-level debug policies and safety rules.",
|
|
11767
|
+
"- `wiki/agent/` contains agent-level calibration knowledge derived from recurring LLM failure patterns.",
|
|
11768
|
+
"- `wiki/external/` stores real project issue resolutions and feedback.",
|
|
11769
|
+
"- `index.md` is a human-readable regenerated catalog.",
|
|
11770
|
+
"- `index.json` is the machine-readable retrieval cache.",
|
|
11771
|
+
"- `log.md` is an append-only operational log.",
|
|
11772
|
+
""
|
|
11773
|
+
].join("\n");
|
|
11774
|
+
}
|
|
11775
|
+
function layerPurpose(layer) {
|
|
11776
|
+
switch (layer) {
|
|
11777
|
+
case "system":
|
|
11778
|
+
return "bundled system debug policies";
|
|
11779
|
+
case "agent":
|
|
11780
|
+
return "bundled agent calibration knowledge";
|
|
11781
|
+
case "external":
|
|
11782
|
+
return "local project issue resolutions";
|
|
11783
|
+
}
|
|
11784
|
+
}
|
|
11785
|
+
function renderMarkdownList(items) {
|
|
11786
|
+
const compact = items.map((item) => item.trim()).filter(Boolean);
|
|
11787
|
+
if (compact.length === 0) return "- n/a";
|
|
11788
|
+
return compact.map((item) => `- ${item}`).join("\n");
|
|
11789
|
+
}
|
|
11790
|
+
function escapeTable(text) {
|
|
11791
|
+
return text.replace(/\|/gu, "\\|").replace(/\r?\n/gu, " ");
|
|
11792
|
+
}
|
|
11793
|
+
function confidenceFor(entry) {
|
|
11794
|
+
const total = entry.stats.uses + entry.stats.successes + entry.stats.failures;
|
|
11795
|
+
const base = (entry.stats.successes + 1) / Math.max(2, total + 2);
|
|
11796
|
+
const statusFactor = entry.status === "needs_review" ? 0.45 : 1;
|
|
11797
|
+
const layerFactor = entry.layer === "system" ? 0.9 : 1;
|
|
11798
|
+
return Math.max(0.1, Math.min(1, base * statusFactor * layerFactor));
|
|
11799
|
+
}
|
|
11800
|
+
function mergeSolution(previous, next) {
|
|
11801
|
+
const trimmed = next.trim();
|
|
11802
|
+
if (!trimmed || previous.includes(trimmed)) return previous;
|
|
11803
|
+
if (!previous.trim()) return trimmed;
|
|
11804
|
+
return `${previous.trim()}
|
|
11805
|
+
Corrected/confirmed resolution: ${trimmed}`;
|
|
11806
|
+
}
|
|
11807
|
+
function pushFeedback(entry, feedback) {
|
|
11808
|
+
entry.feedback.push(feedback);
|
|
11809
|
+
if (entry.feedback.length > 20) entry.feedback.splice(0, entry.feedback.length - 20);
|
|
11810
|
+
}
|
|
11811
|
+
function slugify(text) {
|
|
11812
|
+
return text.toLowerCase().replace(/[^a-z0-9._-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 96) || "entry";
|
|
11813
|
+
}
|
|
11814
|
+
async function exists(filePath) {
|
|
11815
|
+
try {
|
|
11816
|
+
await fs25.stat(filePath);
|
|
11817
|
+
return true;
|
|
11818
|
+
} catch {
|
|
11819
|
+
return false;
|
|
11820
|
+
}
|
|
11821
|
+
}
|
|
11822
|
+
function dedup4(items) {
|
|
11823
|
+
return [...new Set(items.filter((item) => String(item ?? "").length > 0))];
|
|
11824
|
+
}
|
|
11825
|
+
|
|
11826
|
+
// src/core/project_audit.ts
|
|
11827
|
+
import { promises as fs26 } from "fs";
|
|
11828
|
+
import path26 from "path";
|
|
10285
11829
|
function renderProjectAuditFailureLog(result) {
|
|
10286
11830
|
const failed = result.checks.filter((check) => !check.ok);
|
|
10287
11831
|
const interesting = failed.length > 0 ? failed : result.checks;
|
|
@@ -10330,12 +11874,12 @@ async function checkDocumentationBundle(ws, projectType, iterationId) {
|
|
|
10330
11874
|
const docs = iterationId ? deliveryDocsForIteration(projectType, iterationId) : deliveryDocsForProjectType(projectType);
|
|
10331
11875
|
const checks = [];
|
|
10332
11876
|
for (const doc of docs) {
|
|
10333
|
-
const
|
|
11877
|
+
const exists2 = await ws.exists(doc);
|
|
10334
11878
|
checks.push({
|
|
10335
11879
|
name: docCheckName(doc),
|
|
10336
|
-
severity:
|
|
10337
|
-
ok:
|
|
10338
|
-
summary:
|
|
11880
|
+
severity: exists2 ? "info" : "error",
|
|
11881
|
+
ok: exists2,
|
|
11882
|
+
summary: exists2 ? t().execute.auditDocPresent(doc) : t().execute.auditDocMissing(doc)
|
|
10339
11883
|
});
|
|
10340
11884
|
}
|
|
10341
11885
|
return checks;
|
|
@@ -10456,14 +12000,14 @@ async function listFiles(ws, dir) {
|
|
|
10456
12000
|
async function walk3(abs, rel, out) {
|
|
10457
12001
|
let entries;
|
|
10458
12002
|
try {
|
|
10459
|
-
entries = await
|
|
12003
|
+
entries = await fs26.readdir(abs, { withFileTypes: true });
|
|
10460
12004
|
} catch {
|
|
10461
12005
|
return;
|
|
10462
12006
|
}
|
|
10463
12007
|
for (const entry of entries) {
|
|
10464
12008
|
if (entry.name.startsWith(".")) continue;
|
|
10465
12009
|
const childRel = `${rel}/${entry.name}`;
|
|
10466
|
-
const childAbs =
|
|
12010
|
+
const childAbs = path26.join(abs, entry.name);
|
|
10467
12011
|
if (entry.isDirectory()) {
|
|
10468
12012
|
await walk3(childAbs, childRel, out);
|
|
10469
12013
|
} else {
|
|
@@ -10483,6 +12027,7 @@ var PhaseEngine = class {
|
|
|
10483
12027
|
this.skills = opts.skills ?? buildDefaultSkills();
|
|
10484
12028
|
this.plugins = opts.plugins ?? new PluginHost();
|
|
10485
12029
|
this.debugCache = new DebugCache(opts.ws.abs(".xcompiler/debug_cache.json"));
|
|
12030
|
+
this.debugWiki = new DebugWiki(opts.debugWikiPath ?? defaultDebugWikiPath());
|
|
10486
12031
|
}
|
|
10487
12032
|
opts;
|
|
10488
12033
|
registry;
|
|
@@ -10491,6 +12036,8 @@ var PhaseEngine = class {
|
|
|
10491
12036
|
pluginExtensionsApplied = false;
|
|
10492
12037
|
/** 跨 xcompiler run 持久化的 debug 历史(`<workspace>/.xcompiler/debug_cache.json`)。 */
|
|
10493
12038
|
debugCache;
|
|
12039
|
+
/** 跨项目 debug 知识库,记录错误摘要、解决方案和正/负反馈。 */
|
|
12040
|
+
debugWiki;
|
|
10494
12041
|
/** 当前 Plan 的语言 profile(在 run() 起始处按 plan.language 解析)。 */
|
|
10495
12042
|
profile = getLanguageProfile("python");
|
|
10496
12043
|
/** 当前 workspace 的项目记忆,用于给执行阶段注入更稳定的跨轮上下文。 */
|
|
@@ -10510,6 +12057,21 @@ var PhaseEngine = class {
|
|
|
10510
12057
|
spin(text, options) {
|
|
10511
12058
|
return this.terminalOutput ? spinner(text, options).start() : null;
|
|
10512
12059
|
}
|
|
12060
|
+
async withDebugWiki(operation, action, fallback) {
|
|
12061
|
+
try {
|
|
12062
|
+
return await action();
|
|
12063
|
+
} catch (err) {
|
|
12064
|
+
const message = err.message;
|
|
12065
|
+
await this.opts.audit.event("note", `debug wiki ${operation} failed: ${message}`, {
|
|
12066
|
+
messageId: "engine.debug_wiki_failed",
|
|
12067
|
+
operation,
|
|
12068
|
+
path: this.debugWiki.filePath,
|
|
12069
|
+
error: message
|
|
12070
|
+
});
|
|
12071
|
+
if (this.opts.debugWikiStrict) throw err;
|
|
12072
|
+
return fallback;
|
|
12073
|
+
}
|
|
12074
|
+
}
|
|
10513
12075
|
async requestEnginePermission(request) {
|
|
10514
12076
|
if (!this.opts.requestPermission) return { approved: true };
|
|
10515
12077
|
const decision = await this.opts.requestPermission(request);
|
|
@@ -10534,6 +12096,7 @@ var PhaseEngine = class {
|
|
|
10534
12096
|
}
|
|
10535
12097
|
async run(plan) {
|
|
10536
12098
|
await this.plugins.initialize();
|
|
12099
|
+
await this.withDebugWiki("load", () => this.debugWiki.load(), void 0);
|
|
10537
12100
|
if (!this.pluginExtensionsApplied) {
|
|
10538
12101
|
this.plugins.applyExtensions({ tools: this.registry, skills: this.skills });
|
|
10539
12102
|
this.pluginExtensionsApplied = true;
|
|
@@ -10741,7 +12304,23 @@ var PhaseEngine = class {
|
|
|
10741
12304
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
10742
12305
|
this.issueSeq += 1;
|
|
10743
12306
|
const id = `ISSUE-${now.replace(/[-:.TZ]/g, "").slice(0, 14)}-${String(this.issueSeq).padStart(3, "0")}`;
|
|
10744
|
-
const
|
|
12307
|
+
const rawFailureLog = input2.failureLog ?? "";
|
|
12308
|
+
const cleanedFailureLog = cleanFailureLogForDebugContext(rawFailureLog);
|
|
12309
|
+
const debugBrief = buildDebugBrief({
|
|
12310
|
+
reason: input2.reason,
|
|
12311
|
+
failureLog: cleanedFailureLog,
|
|
12312
|
+
phase: step?.phase
|
|
12313
|
+
});
|
|
12314
|
+
const failureLog = compactFailureEvidence({
|
|
12315
|
+
reason: input2.reason,
|
|
12316
|
+
failureLog: cleanedFailureLog,
|
|
12317
|
+
phase: step?.phase,
|
|
12318
|
+
maxChars: 6e3,
|
|
12319
|
+
maxLines: 90
|
|
12320
|
+
});
|
|
12321
|
+
const rawFailureLogPath = `.xcompiler/issues/${id}/failure.raw.log`;
|
|
12322
|
+
await this.opts.ws.writeFile(rawFailureLogPath, rawFailureLog.endsWith("\n") ? rawFailureLog : `${rawFailureLog}
|
|
12323
|
+
`);
|
|
10745
12324
|
const issue = {
|
|
10746
12325
|
id,
|
|
10747
12326
|
createdAt: now,
|
|
@@ -10759,6 +12338,9 @@ var PhaseEngine = class {
|
|
|
10759
12338
|
title: step?.title,
|
|
10760
12339
|
reason: input2.reason,
|
|
10761
12340
|
failureLog,
|
|
12341
|
+
failureLogBytes: Buffer.byteLength(rawFailureLog, "utf8"),
|
|
12342
|
+
rawFailureLogPath,
|
|
12343
|
+
debugBrief,
|
|
10762
12344
|
metrics: input2.metrics,
|
|
10763
12345
|
evidence: input2.evidence
|
|
10764
12346
|
};
|
|
@@ -10776,6 +12358,12 @@ var PhaseEngine = class {
|
|
|
10776
12358
|
issue.status = "routed";
|
|
10777
12359
|
issue.targetStepId = target.id;
|
|
10778
12360
|
issue.targetPhase = target.phase;
|
|
12361
|
+
issue.debugBrief = buildDebugBrief({
|
|
12362
|
+
reason: issue.reason,
|
|
12363
|
+
failureLog: issue.failureLog,
|
|
12364
|
+
phase: issue.phase,
|
|
12365
|
+
targetPhase: target.phase
|
|
12366
|
+
});
|
|
10779
12367
|
issue.routedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10780
12368
|
issue.updatedAt = issue.routedAt;
|
|
10781
12369
|
await this.persistIssue(issue, "routed", { routingReason: reason });
|
|
@@ -10794,13 +12382,27 @@ var PhaseEngine = class {
|
|
|
10794
12382
|
issue.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10795
12383
|
await this.persistIssue(issue, "unresolved", { reason });
|
|
10796
12384
|
}
|
|
10797
|
-
async markIssueResolved(issueId, step, repair) {
|
|
12385
|
+
async markIssueResolved(issueId, step, repair, issueResolutionPlan) {
|
|
10798
12386
|
const issue = issueId ? this.findIssue(issueId) : void 0;
|
|
10799
12387
|
if (!issue) return;
|
|
10800
12388
|
issue.status = "resolved";
|
|
10801
12389
|
issue.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10802
12390
|
issue.updatedAt = issue.resolvedAt;
|
|
10803
12391
|
if (repair) issue.repair = repair;
|
|
12392
|
+
if (issueResolutionPlan?.trim()) {
|
|
12393
|
+
issue.issueResolutionPlan = issueResolutionPlan.trim();
|
|
12394
|
+
issue.resolutionPlanHistory = [
|
|
12395
|
+
...issue.resolutionPlanHistory ?? [],
|
|
12396
|
+
{
|
|
12397
|
+
at: issue.resolvedAt,
|
|
12398
|
+
stepId: step.id,
|
|
12399
|
+
phase: step.phase,
|
|
12400
|
+
plan: issue.issueResolutionPlan,
|
|
12401
|
+
outcome: "accepted"
|
|
12402
|
+
}
|
|
12403
|
+
].slice(-8);
|
|
12404
|
+
}
|
|
12405
|
+
await this.recordDebugWikiResolution(issue, step, repair);
|
|
10804
12406
|
await this.persistIssue(issue, "resolved");
|
|
10805
12407
|
await this.opts.audit.event("issue.resolve", `${issue.id} resolved by ${step.id} ${step.phase}`, {
|
|
10806
12408
|
messageId: "engine.issue_resolved",
|
|
@@ -10810,6 +12412,82 @@ var PhaseEngine = class {
|
|
|
10810
12412
|
repair
|
|
10811
12413
|
});
|
|
10812
12414
|
}
|
|
12415
|
+
async recordDebugWikiFailure(step, debug, outcome) {
|
|
12416
|
+
const entryIds = dedup5(debug.debugWikiEntryIds ?? []);
|
|
12417
|
+
if (entryIds.length === 0) return;
|
|
12418
|
+
const issue = debug.issueId ? this.findIssue(debug.issueId) : void 0;
|
|
12419
|
+
const brief = buildDebugBrief({
|
|
12420
|
+
reason: outcome.reason ?? debug.reason,
|
|
12421
|
+
failureLog: cleanFailureLogForDebugContext(outcome.failureLog),
|
|
12422
|
+
phase: issue?.phase ?? step.phase,
|
|
12423
|
+
targetPhase: issue?.targetPhase ?? step.phase
|
|
12424
|
+
});
|
|
12425
|
+
await this.withDebugWiki(
|
|
12426
|
+
"record-failure",
|
|
12427
|
+
() => this.debugWiki.recordFailure(entryIds, {
|
|
12428
|
+
brief,
|
|
12429
|
+
issueId: issue?.id,
|
|
12430
|
+
stepId: step.id,
|
|
12431
|
+
phase: step.phase,
|
|
12432
|
+
targetPhase: issue?.targetPhase,
|
|
12433
|
+
language: this.profile.id,
|
|
12434
|
+
solution: "retrieved wiki solution did not resolve this attempt",
|
|
12435
|
+
reason: outcome.reason ?? debug.reason
|
|
12436
|
+
}),
|
|
12437
|
+
void 0
|
|
12438
|
+
);
|
|
12439
|
+
await this.opts.audit.event("note", `debug wiki marked ${entryIds.join(", ")} for review`, {
|
|
12440
|
+
messageId: "engine.debug_wiki_feedback",
|
|
12441
|
+
kind: "failure",
|
|
12442
|
+
entryIds,
|
|
12443
|
+
issueId: issue?.id,
|
|
12444
|
+
stepId: step.id,
|
|
12445
|
+
reason: outcome.reason ?? debug.reason
|
|
12446
|
+
});
|
|
12447
|
+
}
|
|
12448
|
+
async recordDebugWikiResolution(issue, step, repair) {
|
|
12449
|
+
const brief = issue.debugBrief ?? buildDebugBrief({
|
|
12450
|
+
reason: issue.reason,
|
|
12451
|
+
failureLog: issue.failureLog,
|
|
12452
|
+
phase: issue.phase,
|
|
12453
|
+
targetPhase: issue.targetPhase
|
|
12454
|
+
});
|
|
12455
|
+
const repairFiles = repair?.changedFiles?.length ? repair.changedFiles : step.outputs;
|
|
12456
|
+
const evidenceSummary = [
|
|
12457
|
+
`Resolved ${issue.kind} by Debugger in ${step.id}/${step.phase}.`,
|
|
12458
|
+
`Mode: ${repair?.mode ?? "verification"}.`,
|
|
12459
|
+
repairFiles.length > 0 ? `Changed/verified files: ${repairFiles.join(", ")}.` : "",
|
|
12460
|
+
repair?.patchPath ? `Patch: ${repair.patchPath}.` : "",
|
|
12461
|
+
`Demand: ${brief.debugDemand}`
|
|
12462
|
+
].filter(Boolean).join(" ");
|
|
12463
|
+
const solution = issue.issueResolutionPlan ? `${issue.issueResolutionPlan}
|
|
12464
|
+
Resolution evidence: ${evidenceSummary}` : evidenceSummary;
|
|
12465
|
+
const result = await this.withDebugWiki(
|
|
12466
|
+
"record-resolution",
|
|
12467
|
+
() => this.debugWiki.recordResolution({
|
|
12468
|
+
brief,
|
|
12469
|
+
issueId: issue.id,
|
|
12470
|
+
stepId: step.id,
|
|
12471
|
+
phase: step.phase,
|
|
12472
|
+
targetPhase: issue.targetPhase,
|
|
12473
|
+
language: this.profile.id,
|
|
12474
|
+
resolutionPlan: issue.issueResolutionPlan,
|
|
12475
|
+
solution,
|
|
12476
|
+
evidence: brief.evidence,
|
|
12477
|
+
repairFiles,
|
|
12478
|
+
usedEntryIds: issue.debugWikiEntryIds
|
|
12479
|
+
}),
|
|
12480
|
+
{ updated: [] }
|
|
12481
|
+
);
|
|
12482
|
+
if (result.created || result.updated.length > 0) {
|
|
12483
|
+
await this.opts.audit.event("note", `debug wiki updated after ${issue.id}`, {
|
|
12484
|
+
messageId: "engine.debug_wiki_updated",
|
|
12485
|
+
issueId: issue.id,
|
|
12486
|
+
created: result.created,
|
|
12487
|
+
updated: result.updated
|
|
12488
|
+
});
|
|
12489
|
+
}
|
|
12490
|
+
}
|
|
10813
12491
|
findIssue(issueId) {
|
|
10814
12492
|
return this.issues.find((issue) => issue.id === issueId);
|
|
10815
12493
|
}
|
|
@@ -10829,6 +12507,10 @@ var PhaseEngine = class {
|
|
|
10829
12507
|
targetStepId: issue.targetStepId,
|
|
10830
12508
|
targetPhase: issue.targetPhase,
|
|
10831
12509
|
reason: issue.reason,
|
|
12510
|
+
debugSummary: issue.debugBrief?.summary,
|
|
12511
|
+
debugDemand: issue.debugBrief?.debugDemand,
|
|
12512
|
+
debugWikiEntryIds: issue.debugWikiEntryIds,
|
|
12513
|
+
issueResolutionPlan: issue.issueResolutionPlan,
|
|
10832
12514
|
...extra
|
|
10833
12515
|
});
|
|
10834
12516
|
await this.opts.ws.writeFile(eventPath, `${existing}${line}
|
|
@@ -10848,6 +12530,7 @@ var PhaseEngine = class {
|
|
|
10848
12530
|
const diff = await this.opts.git.raw().diff([beforeRef, "--"]).catch((err) => `# git diff failed: ${err.message}
|
|
10849
12531
|
`);
|
|
10850
12532
|
const mode = inferRepairMode(toolCalls);
|
|
12533
|
+
const changedFiles = parsePatchChangedFiles(diff);
|
|
10851
12534
|
await this.opts.ws.writeFile(patchPath, diff || "# No textual diff captured.\n");
|
|
10852
12535
|
await this.opts.ws.writeFile(
|
|
10853
12536
|
summaryPath,
|
|
@@ -10871,7 +12554,8 @@ var PhaseEngine = class {
|
|
|
10871
12554
|
completedBeforeDebug,
|
|
10872
12555
|
mode,
|
|
10873
12556
|
patchPath,
|
|
10874
|
-
summaryPath
|
|
12557
|
+
summaryPath,
|
|
12558
|
+
changedFiles
|
|
10875
12559
|
};
|
|
10876
12560
|
}
|
|
10877
12561
|
async completedPhaseRepairViolation(toolCalls) {
|
|
@@ -10888,13 +12572,13 @@ var PhaseEngine = class {
|
|
|
10888
12572
|
return void 0;
|
|
10889
12573
|
}
|
|
10890
12574
|
async repairChangedFiles() {
|
|
10891
|
-
const planPath = normalizeGitPath(
|
|
12575
|
+
const planPath = normalizeGitPath(path27.relative(this.opts.ws.root, path27.resolve(this.opts.planPath)));
|
|
10892
12576
|
const status = await this.opts.git.raw().status();
|
|
10893
12577
|
return status.files.map((file) => normalizeGitPath(file.path)).filter((file) => file.length > 0).filter((file) => !isRuntimeOnlyChange(file, planPath));
|
|
10894
12578
|
}
|
|
10895
12579
|
testGateArgsForStep(step) {
|
|
10896
12580
|
if (step.phase === "FUNCTIONAL_TEST") return [];
|
|
10897
|
-
return
|
|
12581
|
+
return dedup5(
|
|
10898
12582
|
step.outputs.filter((out) => typeof out === "string" && !out.endsWith("/")).map((out) => normalizeGitPath(out)).filter(isTestFilePath)
|
|
10899
12583
|
);
|
|
10900
12584
|
}
|
|
@@ -11062,7 +12746,7 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11062
12746
|
initialDebug: {
|
|
11063
12747
|
reason,
|
|
11064
12748
|
failureLog: debugFailureLog,
|
|
11065
|
-
contextPaths:
|
|
12749
|
+
contextPaths: dedup5([
|
|
11066
12750
|
...sourceStep.inputs,
|
|
11067
12751
|
...sourceStep.outputs,
|
|
11068
12752
|
...routedTest.inputs,
|
|
@@ -11070,7 +12754,7 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11070
12754
|
...failedTest.inputs,
|
|
11071
12755
|
...failedTest.outputs
|
|
11072
12756
|
]),
|
|
11073
|
-
extraAllowedWrites:
|
|
12757
|
+
extraAllowedWrites: dedup5([...routedTest.outputs]),
|
|
11074
12758
|
contextMode: "test-rollback",
|
|
11075
12759
|
testScopeArgs: this.testGateArgsForStep(routedTest),
|
|
11076
12760
|
issueId: issue?.id,
|
|
@@ -11211,6 +12895,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11211
12895
|
await this.debugCache.load();
|
|
11212
12896
|
const initialDebug = opts.initialDebug;
|
|
11213
12897
|
const inheritedExtraAllowedWrites = initialDebug?.extraAllowedWrites;
|
|
12898
|
+
const inheritedContextMode = initialDebug?.contextMode;
|
|
12899
|
+
const inheritedTestScopeArgs = initialDebug?.testScopeArgs;
|
|
11214
12900
|
const completedBeforeDebug = initialDebug?.completedBeforeDebug ?? step.status === "DONE";
|
|
11215
12901
|
const rootDebugFailureLog = initialDebug ? cleanFailureLogForDebugContext(initialDebug.failureLog) : void 0;
|
|
11216
12902
|
const includeRootDebugFailureLog = (failureLog, reason) => rootDebugFailureLog ? composeDebugRetryFailureLog(rootDebugFailureLog, failureLog, reason) : failureLog;
|
|
@@ -11244,7 +12930,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11244
12930
|
priorAttemptsPrompt: priorPrompt,
|
|
11245
12931
|
contextPaths: initialDebug.contextPaths,
|
|
11246
12932
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
11247
|
-
contextMode:
|
|
12933
|
+
contextMode: inheritedContextMode,
|
|
12934
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
11248
12935
|
issueId: activeIssueId,
|
|
11249
12936
|
completedBeforeDebug
|
|
11250
12937
|
});
|
|
@@ -11252,6 +12939,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11252
12939
|
const attempts = this.debugCache.attempts(step.id);
|
|
11253
12940
|
const last = attempts.slice(-1)[0];
|
|
11254
12941
|
const resume = latestActionableDebugAttempt(attempts) ?? last;
|
|
12942
|
+
const cachedTestScopeArgs = inferCachedTestScopeArgs(resume);
|
|
12943
|
+
const cachedContextMode = resume.contextMode ?? (cachedTestScopeArgs.length > 0 ? "test-rollback" : void 0);
|
|
11255
12944
|
this.log(
|
|
11256
12945
|
chalk2.yellow(
|
|
11257
12946
|
t().engine.debugResumeNotice(step.id, attempts.length)
|
|
@@ -11279,6 +12968,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11279
12968
|
reason: resume.reason,
|
|
11280
12969
|
priorAttemptsPrompt: priorPrompt,
|
|
11281
12970
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
12971
|
+
contextMode: cachedContextMode,
|
|
12972
|
+
testScopeArgs: cachedTestScopeArgs,
|
|
11282
12973
|
issueId: activeIssueId,
|
|
11283
12974
|
completedBeforeDebug
|
|
11284
12975
|
});
|
|
@@ -11307,6 +12998,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11307
12998
|
reason: resume.reason,
|
|
11308
12999
|
priorAttemptsPrompt: priorPrompt,
|
|
11309
13000
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
13001
|
+
contextMode: cachedContextMode,
|
|
13002
|
+
testScopeArgs: cachedTestScopeArgs,
|
|
11310
13003
|
issueId: activeIssueId,
|
|
11311
13004
|
completedBeforeDebug
|
|
11312
13005
|
});
|
|
@@ -11357,6 +13050,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11357
13050
|
suggestions: calibrateDebugSuggestions(cleanFailureLogForDebugContext(initialFailureLogForRecord), initialReason).map(
|
|
11358
13051
|
(s) => `[${s.code}] ${s.hint}`
|
|
11359
13052
|
),
|
|
13053
|
+
contextMode: inheritedContextMode,
|
|
13054
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
11360
13055
|
metrics: initial.metrics ? {
|
|
11361
13056
|
healthScore: initial.metrics.healthScore,
|
|
11362
13057
|
parseFailures: initial.metrics.parseFailures,
|
|
@@ -11428,6 +13123,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11428
13123
|
reason: lastReason,
|
|
11429
13124
|
priorAttemptsPrompt: priorPrompt,
|
|
11430
13125
|
extraAllowedWrites: inheritedExtraAllowedWrites,
|
|
13126
|
+
contextMode: inheritedContextMode,
|
|
13127
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
11431
13128
|
issueId: activeIssueId,
|
|
11432
13129
|
completedBeforeDebug
|
|
11433
13130
|
});
|
|
@@ -11483,6 +13180,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11483
13180
|
suggestions: calibrateDebugSuggestions(cleanFailureLogForDebugContext(recordedFailureLog2), rollbackReason).map(
|
|
11484
13181
|
(s) => `[${s.code}] ${s.hint}`
|
|
11485
13182
|
),
|
|
13183
|
+
contextMode: inheritedContextMode,
|
|
13184
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
11486
13185
|
metrics: r.metrics ? {
|
|
11487
13186
|
healthScore: r.metrics.healthScore,
|
|
11488
13187
|
parseFailures: r.metrics.parseFailures,
|
|
@@ -11502,9 +13201,11 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11502
13201
|
}
|
|
11503
13202
|
const m = r.metrics;
|
|
11504
13203
|
const readOnlyProbeLoop = isReadOnlyProbeLoopFailure(r.reason);
|
|
13204
|
+
const missingOutputStall = isMissingOutputStallFailure(r.reason);
|
|
13205
|
+
const lowQualityDebuggerResponse = isLowQualityDebuggerResponseFailure(r.reason);
|
|
11505
13206
|
const repairEvidenceMissing = isRepairEvidenceMissingFailure(r.reason);
|
|
11506
|
-
const healthy = !readOnlyProbeLoop && !repairEvidenceMissing && !!m && m.parseFailures === 0 && m.repeatedTurns <= 1 && m.healthScore >= 0.6;
|
|
11507
|
-
const bad = readOnlyProbeLoop || repairEvidenceMissing || !!m && (m.healthScore < 0.3 || m.parseFailures + m.repeatedTurns >= Math.max(2, Math.ceil(m.rounds / 2)));
|
|
13207
|
+
const healthy = !readOnlyProbeLoop && !missingOutputStall && !lowQualityDebuggerResponse && !repairEvidenceMissing && !!m && m.parseFailures === 0 && m.repeatedTurns <= 1 && m.healthScore >= 0.6;
|
|
13208
|
+
const bad = readOnlyProbeLoop || missingOutputStall || lowQualityDebuggerResponse || repairEvidenceMissing || !!m && (m.healthScore < 0.3 || m.parseFailures + m.repeatedTurns >= Math.max(2, Math.ceil(m.rounds / 2)));
|
|
11508
13209
|
const tag = m ? `health=${m.healthScore.toFixed(2)} parseFail=${m.parseFailures} repeat=${m.repeatedTurns} progress=${m.progressRatio.toFixed(2)}` : "";
|
|
11509
13210
|
if (healthy) {
|
|
11510
13211
|
const before = budget;
|
|
@@ -11555,6 +13256,8 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11555
13256
|
suggestions: calibrateDebugSuggestions(cleanFailureLogForDebugContext(recordedFailureLog), r.reason ?? "").map(
|
|
11556
13257
|
(s) => `[${s.code}] ${s.hint}`
|
|
11557
13258
|
),
|
|
13259
|
+
contextMode: inheritedContextMode,
|
|
13260
|
+
testScopeArgs: inheritedTestScopeArgs,
|
|
11558
13261
|
metrics: m ? {
|
|
11559
13262
|
healthScore: m.healthScore,
|
|
11560
13263
|
parseFailures: m.parseFailures,
|
|
@@ -11674,9 +13377,75 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11674
13377
|
`${iterationPrefix}/api-guide.md`
|
|
11675
13378
|
] : []
|
|
11676
13379
|
];
|
|
11677
|
-
if (failedNames.has("entrypoint")) return
|
|
11678
|
-
if (failedNames.has("tests") || failedNames.has("test-files")) return
|
|
11679
|
-
return
|
|
13380
|
+
if (failedNames.has("entrypoint")) return dedup5([...codeAndTests, ...docs]);
|
|
13381
|
+
if (failedNames.has("tests") || failedNames.has("test-files")) return dedup5([...codeAndTests, ...docs]);
|
|
13382
|
+
return dedup5([...codeAndTests, ...step.inputs, ...docs]);
|
|
13383
|
+
}
|
|
13384
|
+
async buildDebugPromptPayload(step, debug, failureLog) {
|
|
13385
|
+
const issue = debug.issueId ? this.findIssue(debug.issueId) : void 0;
|
|
13386
|
+
const currentBrief = buildDebugBrief({
|
|
13387
|
+
reason: debug.reason,
|
|
13388
|
+
failureLog,
|
|
13389
|
+
phase: issue?.phase ?? step.phase,
|
|
13390
|
+
targetPhase: issue?.targetPhase ?? step.phase
|
|
13391
|
+
});
|
|
13392
|
+
const rootBrief = issue?.debugBrief;
|
|
13393
|
+
const briefBlocks = rootBrief && rootBrief.summary !== currentBrief.summary ? [
|
|
13394
|
+
"## root issue brief",
|
|
13395
|
+
renderDebugBriefForPrompt(rootBrief),
|
|
13396
|
+
"",
|
|
13397
|
+
"## current retry brief",
|
|
13398
|
+
renderDebugBriefForPrompt(currentBrief)
|
|
13399
|
+
] : [renderDebugBriefForPrompt(currentBrief)];
|
|
13400
|
+
const evidence = compactFailureEvidence({
|
|
13401
|
+
reason: debug.reason,
|
|
13402
|
+
failureLog,
|
|
13403
|
+
phase: issue?.phase ?? step.phase,
|
|
13404
|
+
targetPhase: issue?.targetPhase ?? step.phase,
|
|
13405
|
+
maxChars: 2600,
|
|
13406
|
+
maxLines: 50
|
|
13407
|
+
});
|
|
13408
|
+
const suggestions = renderDebugSuggestions(
|
|
13409
|
+
calibrateDebugSuggestions(failureLog, debug.reason)
|
|
13410
|
+
);
|
|
13411
|
+
const wikiMatches = await this.withDebugWiki(
|
|
13412
|
+
"search",
|
|
13413
|
+
() => this.debugWiki.search(currentBrief, { language: this.profile.id, limit: 3 }),
|
|
13414
|
+
[]
|
|
13415
|
+
);
|
|
13416
|
+
const debugWikiEntryIds = wikiMatches.map((match) => match.entry.id);
|
|
13417
|
+
if (debugWikiEntryIds.length > 0) {
|
|
13418
|
+
debug.debugWikiEntryIds = dedup5([...debug.debugWikiEntryIds ?? [], ...debugWikiEntryIds]);
|
|
13419
|
+
if (issue) {
|
|
13420
|
+
issue.debugWikiEntryIds = dedup5([...issue.debugWikiEntryIds ?? [], ...debugWikiEntryIds]);
|
|
13421
|
+
}
|
|
13422
|
+
await this.withDebugWiki(
|
|
13423
|
+
"record-use",
|
|
13424
|
+
() => this.debugWiki.recordUse(debugWikiEntryIds, {
|
|
13425
|
+
brief: currentBrief,
|
|
13426
|
+
issueId: issue?.id,
|
|
13427
|
+
stepId: step.id,
|
|
13428
|
+
phase: step.phase,
|
|
13429
|
+
targetPhase: issue?.targetPhase,
|
|
13430
|
+
language: this.profile.id,
|
|
13431
|
+
solution: "retrieved for Debugger prompt"
|
|
13432
|
+
}),
|
|
13433
|
+
void 0
|
|
13434
|
+
);
|
|
13435
|
+
await this.opts.audit.event("note", `debug wiki matched ${debugWikiEntryIds.join(", ")}`, {
|
|
13436
|
+
messageId: "engine.debug_wiki_matched",
|
|
13437
|
+
entryIds: debugWikiEntryIds,
|
|
13438
|
+
stepId: step.id,
|
|
13439
|
+
phase: step.phase
|
|
13440
|
+
});
|
|
13441
|
+
}
|
|
13442
|
+
const wikiPrompt = renderDebugWikiMatchesForPrompt(wikiMatches);
|
|
13443
|
+
return {
|
|
13444
|
+
debugBrief: [briefBlocks.join("\n"), wikiPrompt].filter(Boolean).join("\n\n"),
|
|
13445
|
+
failureLog: evidence,
|
|
13446
|
+
suggestions,
|
|
13447
|
+
debugWikiEntryIds
|
|
13448
|
+
};
|
|
11680
13449
|
}
|
|
11681
13450
|
/** 一次执行尝试:可选 debug 模式(使用 Debugger 角色 + 注入失败日志)。 */
|
|
11682
13451
|
async runOneAttempt(plan, step, debug) {
|
|
@@ -11690,6 +13459,9 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11690
13459
|
});
|
|
11691
13460
|
try {
|
|
11692
13461
|
const outcome = await this.runOneAttemptCore(plan, step, debug);
|
|
13462
|
+
if (debug && !outcome.ok) {
|
|
13463
|
+
await this.recordDebugWikiFailure(step, debug, outcome);
|
|
13464
|
+
}
|
|
11693
13465
|
await this.plugins.emit("step.attempt.after", {
|
|
11694
13466
|
plan,
|
|
11695
13467
|
step,
|
|
@@ -11705,6 +13477,9 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11705
13477
|
failureLog: error instanceof Error ? error.stack ?? error.message : String(error),
|
|
11706
13478
|
reason: error instanceof Error ? error.message : String(error)
|
|
11707
13479
|
};
|
|
13480
|
+
if (debug) {
|
|
13481
|
+
await this.recordDebugWikiFailure(step, debug, outcome);
|
|
13482
|
+
}
|
|
11708
13483
|
await this.plugins.emit("step.attempt.after", {
|
|
11709
13484
|
plan,
|
|
11710
13485
|
step,
|
|
@@ -11728,10 +13503,10 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11728
13503
|
hints.push(`[debugger] ${dbg.prompt}`);
|
|
11729
13504
|
}
|
|
11730
13505
|
}
|
|
11731
|
-
const allNames =
|
|
13506
|
+
const allNames = dedup5([...resolvedToolNames, ...extraNames]);
|
|
11732
13507
|
const baseTools = this.registry.pick(allNames);
|
|
11733
|
-
const allowedWrites = debug ?
|
|
11734
|
-
const augmentedWrites = this.isVModelTestPhase(step.phase) || step.phase === "DEBUG" || debug ?
|
|
13508
|
+
const allowedWrites = debug ? dedup5([...this.computeDebugAllowedWrites(plan, step), ...debug.extraAllowedWrites ?? []]) : this.computeStepAllowedWrites(plan, step);
|
|
13509
|
+
const augmentedWrites = this.isVModelTestPhase(step.phase) || step.phase === "DEBUG" || debug ? dedup5([...allowedWrites, "tests/fixtures"]) : allowedWrites;
|
|
11735
13510
|
const budgetContext = {
|
|
11736
13511
|
phase: step.phase,
|
|
11737
13512
|
role,
|
|
@@ -11818,6 +13593,7 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11818
13593
|
{ animate: false }
|
|
11819
13594
|
);
|
|
11820
13595
|
const debugFailureLog = debug ? cleanFailureLogForDebugContext(debug.failureLog) : void 0;
|
|
13596
|
+
const debugPayload = debug ? await this.buildDebugPromptPayload(step, debug, debugFailureLog ?? debug.failureLog) : void 0;
|
|
11821
13597
|
try {
|
|
11822
13598
|
const r = await executor.run({
|
|
11823
13599
|
step,
|
|
@@ -11827,14 +13603,15 @@ ${cleanFailureLogForDebugContext(sourceFailureLog)}` : ""
|
|
|
11827
13603
|
contextSnippets: ctxSnippets,
|
|
11828
13604
|
skillHints: hints,
|
|
11829
13605
|
debugContext: debug ? {
|
|
13606
|
+
issueId: debug.issueId,
|
|
11830
13607
|
reason: debug.reason,
|
|
11831
|
-
failureLog: debugFailureLog ?? debug.failureLog,
|
|
13608
|
+
failureLog: debugPayload?.failureLog ?? debugFailureLog ?? debug.failureLog,
|
|
13609
|
+
debugBrief: debugPayload?.debugBrief,
|
|
11832
13610
|
repairRequired: !debug.completedBeforeDebug,
|
|
11833
|
-
suggestions:
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
${debug.priorAttemptsPrompt}` : "")
|
|
13611
|
+
suggestions: [
|
|
13612
|
+
debugPayload?.suggestions,
|
|
13613
|
+
debug.priorAttemptsPrompt
|
|
13614
|
+
].filter(Boolean).join("\n\n")
|
|
11838
13615
|
} : void 0,
|
|
11839
13616
|
globalPrompt: plan.globalPrompt,
|
|
11840
13617
|
languageProfile: this.profile
|
|
@@ -12087,7 +13864,7 @@ ${pt.stderr}`);
|
|
|
12087
13864
|
r.toolCalls
|
|
12088
13865
|
) : void 0;
|
|
12089
13866
|
if (debug?.issueId) {
|
|
12090
|
-
await this.markIssueResolved(debug.issueId, step, repair);
|
|
13867
|
+
await this.markIssueResolved(debug.issueId, step, repair, r.issueResolutionPlan);
|
|
12091
13868
|
}
|
|
12092
13869
|
await this.opts.git.snapshot(step.id, step.retries, debug ? "debug done" : "done");
|
|
12093
13870
|
spin?.succeed(t().engine.phaseDone(step.id, r.rounds));
|
|
@@ -12096,9 +13873,12 @@ ${pt.stderr}`);
|
|
|
12096
13873
|
rounds: r.rounds,
|
|
12097
13874
|
retry: step.retries
|
|
12098
13875
|
});
|
|
12099
|
-
return { ok: true, failureLog: "" };
|
|
13876
|
+
return { ok: true, failureLog: "", issueResolutionPlan: r.issueResolutionPlan };
|
|
13877
|
+
}
|
|
13878
|
+
let reason = r.error ?? t().engine.outputsMissing(verify.missing.join(", "));
|
|
13879
|
+
if (debug?.completedBeforeDebug && !hasSuccessfulRepairMutation(r.toolCalls) && hasFailedVerificationEvidence(r.toolCalls)) {
|
|
13880
|
+
reason = "completed phase debug finished with failed verification but without a successful repair mutation";
|
|
12100
13881
|
}
|
|
12101
|
-
const reason = r.error ?? t().engine.outputsMissing(verify.missing.join(", "));
|
|
12102
13882
|
const m = r.metrics;
|
|
12103
13883
|
const metricsLine = m ? t().engine.metricsLine(m.healthScore.toFixed(2), m.parseFailures, m.repeatedTurns, m.toolFailRatio.toFixed(2), m.progressRatio.toFixed(2)) : t().engine.metricsUnavailable;
|
|
12104
13884
|
const failureLog = [
|
|
@@ -12132,7 +13912,7 @@ ${pt.stderr}`);
|
|
|
12132
13912
|
} else {
|
|
12133
13913
|
await this.opts.git.revertTo(sha);
|
|
12134
13914
|
}
|
|
12135
|
-
return { ok: false, failureLog, reason, metrics: m, issueKind: "phase" };
|
|
13915
|
+
return { ok: false, failureLog, reason, metrics: m, issueKind: "phase", issueResolutionPlan: r.issueResolutionPlan };
|
|
12136
13916
|
} catch (err) {
|
|
12137
13917
|
const msg = err.message;
|
|
12138
13918
|
const stack = err.stack ?? msg;
|
|
@@ -12146,7 +13926,7 @@ ${pt.stderr}`);
|
|
|
12146
13926
|
});
|
|
12147
13927
|
return { ok: false, failureLog: stack, reason: msg, issueKind: "exception" };
|
|
12148
13928
|
} finally {
|
|
12149
|
-
void
|
|
13929
|
+
void path27;
|
|
12150
13930
|
}
|
|
12151
13931
|
}
|
|
12152
13932
|
async buildContextSnippets(plan, step, debug) {
|
|
@@ -12203,7 +13983,7 @@ ${pt.stderr}`);
|
|
|
12203
13983
|
if (downstream) {
|
|
12204
13984
|
out.set(`.xcompiler/downstream/${step.id}.md`, downstream);
|
|
12205
13985
|
}
|
|
12206
|
-
return [...out.entries()].map(([
|
|
13986
|
+
return [...out.entries()].map(([path31, content]) => ({ path: path31, content }));
|
|
12207
13987
|
}
|
|
12208
13988
|
findOwningTestStepForFailure(plan, currentStep, failureText) {
|
|
12209
13989
|
const failedPaths = extractFailedTestPaths(failureText);
|
|
@@ -12326,7 +14106,7 @@ ${pt.stderr}`);
|
|
|
12326
14106
|
].join("\n").length;
|
|
12327
14107
|
}
|
|
12328
14108
|
};
|
|
12329
|
-
function
|
|
14109
|
+
function dedup5(arr) {
|
|
12330
14110
|
return [...new Set(arr)];
|
|
12331
14111
|
}
|
|
12332
14112
|
function isDesignSourcePhase(phase) {
|
|
@@ -12358,6 +14138,9 @@ var REPAIR_VERIFICATION_TOOLS = /* @__PURE__ */ new Set([
|
|
|
12358
14138
|
function hasSuccessfulVerificationEvidence(toolCalls) {
|
|
12359
14139
|
return toolCalls.some((call) => call.ok && REPAIR_VERIFICATION_TOOLS.has(call.tool));
|
|
12360
14140
|
}
|
|
14141
|
+
function hasFailedVerificationEvidence(toolCalls) {
|
|
14142
|
+
return toolCalls.some((call) => !call.ok && REPAIR_VERIFICATION_TOOLS.has(call.tool));
|
|
14143
|
+
}
|
|
12361
14144
|
function shouldRollbackTestPhaseFromToolFailures(step, toolCalls) {
|
|
12362
14145
|
let unresolvedTestFailure = false;
|
|
12363
14146
|
for (const call of toolCalls) {
|
|
@@ -12477,32 +14260,65 @@ function extractFailedTestPaths(text) {
|
|
|
12477
14260
|
if (file && isTestFilePath(file)) found.push(file);
|
|
12478
14261
|
}
|
|
12479
14262
|
}
|
|
12480
|
-
return
|
|
14263
|
+
return dedup5(found);
|
|
12481
14264
|
}
|
|
12482
14265
|
function isNonDebuggableInfrastructureFailure(reason, failureLog) {
|
|
12483
14266
|
const text = `${reason ?? ""}
|
|
12484
14267
|
${failureLog ?? ""}`.toLowerCase();
|
|
12485
|
-
|
|
14268
|
+
if (/low-quality debugger response/u.test(text)) return false;
|
|
14269
|
+
return /typeerror:\s*fetch failed/u.test(text) || /(?:openai|ollama) http (?:401|403|408|409|429|5\d\d)\b/u.test(text) || /rate limit exceeded|free-models-per-day|retry_after_seconds|retry-after/u.test(text) || /(?:openai|ollama|llm|provider)[^\n]{0,180}(?:rate[- ]?limit|rate limited|rate-limited|retry-after|retry_after_seconds)/u.test(text) || /(?:response_format|json_object|json_schema)[^\n]{0,220}(?:not support|unsupported|invalid_request_body|supported formats)/u.test(text) || /openai stream (?:wall-clock|idle)/u.test(text) || /ollama stream (?:wall-clock|idle)/u.test(text) || /request timed out after \d+ms/u.test(text) || /prefill_memory_exceeded|prefill memory guard|dynamic ceiling/u.test(text) || /context (?:length|window)|token limit|too many tokens|prompt too long|max(?:imum)? context/u.test(text) || /input[^\n]{0,80}tokens[^\n]{0,120}(?:exceed|limit|ceiling)/u.test(text) || /llm provider|provider_call_failed|all llm providers failed/u.test(text);
|
|
12486
14270
|
}
|
|
12487
14271
|
function isReadOnlyProbeLoopFailure(reason) {
|
|
12488
14272
|
return /repeated read-only\/probe actions without progress/u.test(reason ?? "") || /read-only recovery mode repeated probe actions/u.test(reason ?? "");
|
|
12489
14273
|
}
|
|
14274
|
+
function isMissingOutputStallFailure(reason) {
|
|
14275
|
+
return /write\/progress actions did not reduce missing outputs/u.test(reason ?? "");
|
|
14276
|
+
}
|
|
14277
|
+
function isLowQualityDebuggerResponseFailure(reason) {
|
|
14278
|
+
return /low-quality debugger response/u.test(reason ?? "");
|
|
14279
|
+
}
|
|
12490
14280
|
function isRepairEvidenceMissingFailure(reason) {
|
|
12491
|
-
return /without repair evidence/u.test(reason ?? "") || /without a successful repair mutation or verification tool call/u.test(reason ?? "");
|
|
14281
|
+
return /without repair evidence/u.test(reason ?? "") || /without a successful repair mutation/u.test(reason ?? "") || /without a successful repair mutation or verification tool call/u.test(reason ?? "");
|
|
12492
14282
|
}
|
|
12493
14283
|
function shouldRollbackTestPhaseFailure(reason, failureLog) {
|
|
12494
14284
|
const text = `${reason ?? ""}
|
|
12495
14285
|
${failureLog ?? ""}`.toLowerCase();
|
|
12496
14286
|
if (isReadOnlyProbeLoopFailure(reason) || isRepairEvidenceMissingFailure(reason)) return false;
|
|
14287
|
+
if (/tool verification failed.*rolling back to paired/u.test(text)) return true;
|
|
12497
14288
|
if (isCachedTestArtifactDiscoveryFailure(text)) return false;
|
|
12498
14289
|
if (/missing outputs|outputs? 校验|verify outputs|declared outputs|产物/u.test(text)) return false;
|
|
12499
14290
|
if (/invalid action|args must be an object|invalid json|parse failure/u.test(text)) return false;
|
|
12500
|
-
if (/
|
|
12501
|
-
if (/test gate|functional gate|functional entry probe|entry probe|delivery gate/u.test(text)) return true;
|
|
14291
|
+
if (/test gate|functional gate|functional entry probe|entry probe|delivery gate|测试门禁|功能门禁|功能入口探测|入口探测|交付门禁/u.test(text)) return true;
|
|
12502
14292
|
if (/pytest exit=\s*[1-9]\d*|exit code\s*[1-9]\d*|failed tests?|test failures?/u.test(text)) return true;
|
|
12503
14293
|
if (/assertionerror|failed:\s+did not|traceback \(most recent call last\)/u.test(text)) return true;
|
|
12504
14294
|
return false;
|
|
12505
14295
|
}
|
|
14296
|
+
function inferCachedTestScopeArgs(entry) {
|
|
14297
|
+
const explicit = (entry.testScopeArgs ?? []).map(normalizeGitPath).filter(isTestFilePath);
|
|
14298
|
+
if (explicit.length > 0) return dedup5(explicit);
|
|
14299
|
+
const text = [
|
|
14300
|
+
entry.failureLogTail,
|
|
14301
|
+
entry.debugBrief?.toolFailures?.join("\n") ?? "",
|
|
14302
|
+
entry.debugBrief?.evidence?.join("\n") ?? ""
|
|
14303
|
+
].filter(Boolean).join("\n");
|
|
14304
|
+
const fromRunTestsArgs = extractRunTestsArgs(text);
|
|
14305
|
+
if (fromRunTestsArgs.length > 0) return fromRunTestsArgs;
|
|
14306
|
+
const failedPaths = extractFailedTestPaths(text);
|
|
14307
|
+
if (failedPaths.length > 0) return failedPaths;
|
|
14308
|
+
return dedup5((entry.debugBrief?.files ?? []).map(normalizeGitPath).filter(isTestFilePath));
|
|
14309
|
+
}
|
|
14310
|
+
function extractRunTestsArgs(text) {
|
|
14311
|
+
const out = [];
|
|
14312
|
+
for (const match of text.matchAll(/\brun_tests[^\n]*\bargs=([^\n]+)/giu)) {
|
|
14313
|
+
const raw = match[1] ?? "";
|
|
14314
|
+
for (const token of raw.split(/\s+/u)) {
|
|
14315
|
+
const cleaned = token.replace(/^["'`]+|["'`,;]+$/gu, "");
|
|
14316
|
+
const normalized = normalizeGitPath(cleaned);
|
|
14317
|
+
if (isTestFilePath(normalized)) out.push(normalized);
|
|
14318
|
+
}
|
|
14319
|
+
}
|
|
14320
|
+
return dedup5(out);
|
|
14321
|
+
}
|
|
12506
14322
|
function isCachedTestArtifactDiscoveryFailure(text) {
|
|
12507
14323
|
return /no test files? found|no tests? found/u.test(text) || /filter:\s+tests?\//u.test(text) || /(?:enoent|no such file or directory|not a file)[^\n]{0,240}tests?\//u.test(text);
|
|
12508
14324
|
}
|
|
@@ -12558,6 +14374,13 @@ function inferRepairMode(toolCalls) {
|
|
|
12558
14374
|
if (usedRewrite) return "rewrite";
|
|
12559
14375
|
return "verification";
|
|
12560
14376
|
}
|
|
14377
|
+
function parsePatchChangedFiles(diff) {
|
|
14378
|
+
const files = [];
|
|
14379
|
+
for (const match of diff.matchAll(/^diff --git a\/(.+?) b\/(.+)$/gmu)) {
|
|
14380
|
+
files.push(normalizeGitPath(match[2] ?? match[1] ?? ""));
|
|
14381
|
+
}
|
|
14382
|
+
return dedup5(files.filter(Boolean));
|
|
14383
|
+
}
|
|
12561
14384
|
|
|
12562
14385
|
// src/runtime/run.ts
|
|
12563
14386
|
async function runExecute(opts) {
|
|
@@ -12566,7 +14389,7 @@ async function runExecute(opts) {
|
|
|
12566
14389
|
io.interaction?.pauseStdin?.();
|
|
12567
14390
|
} catch {
|
|
12568
14391
|
}
|
|
12569
|
-
const ws = new Workspace(
|
|
14392
|
+
const ws = new Workspace(path28.resolve(opts.workspace));
|
|
12570
14393
|
const { config: cfg, path: cfgPath } = await loadConfigWithPath(opts.configPath);
|
|
12571
14394
|
if (!hasXcEnv("LANG")) setLocale(cfg.locale);
|
|
12572
14395
|
let lock;
|
|
@@ -12736,6 +14559,8 @@ async function runExecute(opts) {
|
|
|
12736
14559
|
maxEditLinesPerStep: cfg.agent.max_edit_lines_per_step,
|
|
12737
14560
|
maxWriteChunkBytes: cfg.agent.max_write_chunk_bytes,
|
|
12738
14561
|
terminalOutput: opts.terminalOutput ?? true,
|
|
14562
|
+
debugWikiPath: opts.debugWikiPath ? path28.resolve(opts.debugWikiPath) : void 0,
|
|
14563
|
+
debugWikiStrict: !!opts.debugWikiPath,
|
|
12739
14564
|
requestPermission: io.requestPermission ? async (request) => {
|
|
12740
14565
|
await io.emit({ type: "permission", status: "requested", request });
|
|
12741
14566
|
const decision = await io.requestPermission(request);
|
|
@@ -12980,15 +14805,15 @@ async function emitProjectAudit(io, result) {
|
|
|
12980
14805
|
}
|
|
12981
14806
|
|
|
12982
14807
|
// src/runtime/workspace.ts
|
|
12983
|
-
import
|
|
12984
|
-
import { promises as
|
|
14808
|
+
import path29 from "path";
|
|
14809
|
+
import { promises as fs27 } from "fs";
|
|
12985
14810
|
|
|
12986
14811
|
// src/runtime/commands.ts
|
|
12987
14812
|
async function runRunCommand(opts) {
|
|
12988
14813
|
const cwd = opts.cwd ?? process.cwd();
|
|
12989
14814
|
const explicit = opts.output ?? opts.workspace;
|
|
12990
|
-
const workspace = explicit ?
|
|
12991
|
-
const planPath = opts.planArg ?
|
|
14815
|
+
const workspace = explicit ? path30.resolve(explicit) : opts.planArg ? path30.dirname(path30.resolve(opts.planArg)) : cwd;
|
|
14816
|
+
const planPath = opts.planArg ? path30.resolve(opts.planArg) : await defaultRunnablePlanPath(workspace);
|
|
12992
14817
|
return runExecute({
|
|
12993
14818
|
...opts,
|
|
12994
14819
|
workspace,
|
|
@@ -12997,15 +14822,15 @@ async function runRunCommand(opts) {
|
|
|
12997
14822
|
});
|
|
12998
14823
|
}
|
|
12999
14824
|
async function defaultRunnablePlanPath(workspace) {
|
|
13000
|
-
const phasePlanPath =
|
|
14825
|
+
const phasePlanPath = path30.join(workspace, DEFAULT_PHASE_PLAN_FILE);
|
|
13001
14826
|
if (await fileExists2(phasePlanPath)) return phasePlanPath;
|
|
13002
|
-
const legacyPlanPath =
|
|
14827
|
+
const legacyPlanPath = path30.join(workspace, DEFAULT_PLAN_FILE);
|
|
13003
14828
|
if (await fileExists2(legacyPlanPath)) return legacyPlanPath;
|
|
13004
14829
|
return phasePlanPath;
|
|
13005
14830
|
}
|
|
13006
14831
|
async function fileExists2(filePath) {
|
|
13007
14832
|
try {
|
|
13008
|
-
await
|
|
14833
|
+
await fs28.stat(filePath);
|
|
13009
14834
|
return true;
|
|
13010
14835
|
} catch {
|
|
13011
14836
|
return false;
|
|
@@ -13184,7 +15009,7 @@ configureLocalizedHelp(program);
|
|
|
13184
15009
|
program.name("xcompiler_run").description(t().cli.runDescription).version(XCOMPILER_VERSION, "-V, --version", t().cli.versionOption).option("--lang <code>", t().cli.optLang, parseLocale).hook("preAction", (cmd) => {
|
|
13185
15010
|
const l = cmd.opts().lang;
|
|
13186
15011
|
if (l) setLocale(l);
|
|
13187
|
-
}).argument("[plan]", t().cli.argPlan).option("-o, --output <dir>", t().cli.optOutput).option("-w, --workspace <dir>", t().cli.optWorkspace).option("-c, --config <file>", t().cli.optConfig).option("--dry-run", t().cli.optDryRun, false).option("--from <stepId>", t().cli.optFrom, parseStepId).option("--phase <phase>", t().cli.optPhase, parsePhase).option("--reset", t().cli.optReset, false).option("--force", t().cli.optForce, false).option("--project-file <file>", t().cli.optProjectFile).action(async (planArg, opts) => {
|
|
15012
|
+
}).argument("[plan]", t().cli.argPlan).option("-o, --output <dir>", t().cli.optOutput).option("-w, --workspace <dir>", t().cli.optWorkspace).option("-c, --config <file>", t().cli.optConfig).option("--dry-run", t().cli.optDryRun, false).option("--from <stepId>", t().cli.optFrom, parseStepId).option("--phase <phase>", t().cli.optPhase, parsePhase).option("--reset", t().cli.optReset, false).option("--force", t().cli.optForce, false).option("--project-file <file>", t().cli.optProjectFile).option("--debug-wiki-path <dir>", t().cli.optDebugWikiPath).action(async (planArg, opts) => {
|
|
13188
15013
|
const result = await runRunCommand({
|
|
13189
15014
|
planArg,
|
|
13190
15015
|
output: opts.output,
|
|
@@ -13196,6 +15021,7 @@ program.name("xcompiler_run").description(t().cli.runDescription).version(XCOMPI
|
|
|
13196
15021
|
resetStatus: !!opts.reset,
|
|
13197
15022
|
force: !!opts.force,
|
|
13198
15023
|
projectFilePath: opts.projectFile,
|
|
15024
|
+
debugWikiPath: opts.debugWikiPath,
|
|
13199
15025
|
cwd: process.cwd(),
|
|
13200
15026
|
io: createCliRuntimeIO()
|
|
13201
15027
|
});
|