@citeark/agent 0.3.20

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.
Files changed (347) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +128 -0
  3. package/data/dataset-source-registry.v1.json +300 -0
  4. package/dist/arkgraph/boot.js +6 -0
  5. package/dist/arkgraph/index.html +1 -0
  6. package/dist/arkgraph/viewer.css +1 -0
  7. package/dist/arkgraph/viewer.en.css +1 -0
  8. package/dist/arkgraph/viewer.en.js +49 -0
  9. package/dist/arkgraph/viewer.en.js.LEGAL.txt +56 -0
  10. package/dist/arkgraph/viewer.js +49 -0
  11. package/dist/arkgraph/viewer.js.LEGAL.txt +56 -0
  12. package/docker/claude-code/Dockerfile +97 -0
  13. package/docker/claude-code/codex-pro-relay.mjs +466 -0
  14. package/docker/claude-code/runtime-contract-check.mjs +79 -0
  15. package/docs/arkgraph-reading.md +79 -0
  16. package/docs/configuration.md +100 -0
  17. package/docs/integration.md +92 -0
  18. package/docs/maturity-plan.md +27 -0
  19. package/docs/npm-release.md +44 -0
  20. package/docs/paper-reading.md +40 -0
  21. package/docs/research-plan-granularity.md +27 -0
  22. package/docs/terminal.md +49 -0
  23. package/examples/toy-evaluation/compile-task.json +27 -0
  24. package/examples/toy-evaluation/paper.md +5 -0
  25. package/examples/toy-evaluation/repository/README.md +9 -0
  26. package/examples/toy-evaluation/repository/checkpoint.json +4 -0
  27. package/examples/toy-evaluation/repository/evaluate.py +17 -0
  28. package/examples/toy-evaluation/task.json +81 -0
  29. package/package.json +59 -0
  30. package/prompts/compile-research.md +58 -0
  31. package/prompts/execute-contract.md +72 -0
  32. package/prompts/execute-workspace-simple.md +51 -0
  33. package/prompts/execute-workspace.md +34 -0
  34. package/prompts/prepare-reproduction.md +82 -0
  35. package/prompts/repair-research.md +45 -0
  36. package/protocol/CAP.md +129 -0
  37. package/protocol/LICENSE +12 -0
  38. package/protocol/MAPPINGS.md +72 -0
  39. package/protocol/README.md +38 -0
  40. package/protocol/conformance-v2.0-alpha.1.json +36 -0
  41. package/protocol/examples/arkgraph/checkpoint-evaluation.json +309 -0
  42. package/protocol/examples/arkgraph/fixtures.mjs +49 -0
  43. package/protocol/examples/arkgraph/paper-free.json +291 -0
  44. package/protocol/examples/arkgraph/partial-failure.json +344 -0
  45. package/protocol/examples/arkgraph/training-evaluation.json +443 -0
  46. package/protocol/profiles/agent-trace.md +16 -0
  47. package/protocol/profiles/computational-run.md +16 -0
  48. package/protocol/profiles/core.md +15 -0
  49. package/protocol/profiles/public-bundle.md +18 -0
  50. package/protocol/profiles/reproduction.md +29 -0
  51. package/protocol/profiles/research-compilation.md +44 -0
  52. package/protocol/profiles/research-plan.md +39 -0
  53. package/protocol/profiles/restricted-evidence.md +15 -0
  54. package/runtime/bootstrap-autodl-runtime.sh +314 -0
  55. package/runtime/create-runtime-venv.sh +41 -0
  56. package/runtime/install-local-cpu-runtime.sh +23 -0
  57. package/runtime/install-scientific-runtime.sh +153 -0
  58. package/runtime/mineru/parse.py +62 -0
  59. package/runtime/mineru/requirements.txt +4 -0
  60. package/runtime/requirements-baseline.txt +38 -0
  61. package/schemas/cap/v2/activity.schema.json +47 -0
  62. package/schemas/cap/v2/agent.schema.json +32 -0
  63. package/schemas/cap/v2/assertion.schema.json +110 -0
  64. package/schemas/cap/v2/descriptor.schema.json +243 -0
  65. package/schemas/cap/v2/entity.schema.json +64 -0
  66. package/schemas/cap/v2/manifest.schema.json +67 -0
  67. package/schemas/cap/v2/relation.schema.json +82 -0
  68. package/schemas/compute-catalog.schema.json +63 -0
  69. package/schemas/compute-decision.schema.json +27 -0
  70. package/schemas/execution-contract.schema.json +1024 -0
  71. package/schemas/research-card.schema.json +30 -0
  72. package/schemas/research-inventory-draft.schema.json +366 -0
  73. package/schemas/research.schema.json +1044 -0
  74. package/schemas/result.schema.json +173 -0
  75. package/schemas/verification-policy.schema.json +47 -0
  76. package/schemas/verified-conclusion.schema.json +58 -0
  77. package/schemas/workspace-summary.schema.json +24 -0
  78. package/scripts/build-arkgraph-view.mjs +12 -0
  79. package/scripts/check-execution-feasibility.mjs +24 -0
  80. package/scripts/check-syntax.mjs +15 -0
  81. package/scripts/deterministic-asset-preparation.py +438 -0
  82. package/scripts/package-cap.mjs +23 -0
  83. package/scripts/package-local-agent.mjs +23 -0
  84. package/scripts/preview-arkgraph.mjs +25 -0
  85. package/scripts/replay-research-compiler-candidate.mjs +134 -0
  86. package/scripts/review-compiler-sources.mjs +44 -0
  87. package/scripts/run-asset-preparation.sh +17 -0
  88. package/scripts/run-research-plan.mjs +98 -0
  89. package/scripts/validate-asset-preparation.py +290 -0
  90. package/scripts/verify-local-runtime.mjs +57 -0
  91. package/scripts/verify-npm-package.mjs +57 -0
  92. package/src/adapters/paper2agent.mjs +107 -0
  93. package/src/assets/cache.mjs +159 -0
  94. package/src/assets/compute.mjs +98 -0
  95. package/src/assets/executor.mjs +145 -0
  96. package/src/assets/lifecycle.mjs +213 -0
  97. package/src/assets/manifest.mjs +242 -0
  98. package/src/assets/opportunistic-preparation.mjs +81 -0
  99. package/src/assets/plan.mjs +411 -0
  100. package/src/assets/prompts.mjs +29 -0
  101. package/src/assets/public-asset-probe.mjs +525 -0
  102. package/src/assets/qualification.mjs +119 -0
  103. package/src/assets/readiness.mjs +130 -0
  104. package/src/assets/reproduction-admission.mjs +355 -0
  105. package/src/assets/requirements.mjs +152 -0
  106. package/src/assets/source-grounding.mjs +341 -0
  107. package/src/assets/source-policy.mjs +118 -0
  108. package/src/autodl/client.mjs +260 -0
  109. package/src/autodl/ssh.mjs +380 -0
  110. package/src/autodl/tools.mjs +129 -0
  111. package/src/cap/redaction.mjs +38 -0
  112. package/src/cap/v2/archive.mjs +152 -0
  113. package/src/cap/v2/attestation.mjs +204 -0
  114. package/src/cap/v2/canonical-json.mjs +114 -0
  115. package/src/cap/v2/compilation-artifact.mjs +240 -0
  116. package/src/cap/v2/core.mjs +282 -0
  117. package/src/cap/v2/measurement-assessment-records.mjs +23 -0
  118. package/src/cap/v2/pipeline-artifact.mjs +922 -0
  119. package/src/cap/v2/read.mjs +41 -0
  120. package/src/cap/v2/reassessment-artifact.mjs +383 -0
  121. package/src/cap/v2/research-artifact.mjs +231 -0
  122. package/src/cap/v2/research-map-records.mjs +46 -0
  123. package/src/cap/v2/research-object-records.mjs +163 -0
  124. package/src/cap/v2/research-records.mjs +187 -0
  125. package/src/cap/v2/verify.mjs +642 -0
  126. package/src/cli.mjs +1146 -0
  127. package/src/compute/autodl-pro-compiler.mjs +347 -0
  128. package/src/compute/autodl-pro-executor.mjs +459 -0
  129. package/src/compute/autodl-pro-job.mjs +843 -0
  130. package/src/compute/autodl-pro-network.mjs +295 -0
  131. package/src/compute/autodl-pro-remote.mjs +810 -0
  132. package/src/compute/autodl-pro-staging.mjs +117 -0
  133. package/src/compute/campaign.mjs +110 -0
  134. package/src/compute/catalog.mjs +123 -0
  135. package/src/compute/checkpoint-protocol.mjs +154 -0
  136. package/src/compute/codex-account-lock.mjs +111 -0
  137. package/src/compute/codex-account-session.mjs +107 -0
  138. package/src/compute/compiler-profile.mjs +38 -0
  139. package/src/compute/compiler-router.mjs +23 -0
  140. package/src/compute/coordinator-recovery.mjs +210 -0
  141. package/src/compute/executor-router.mjs +29 -0
  142. package/src/compute/gcp-batch-compiler.mjs +685 -0
  143. package/src/compute/gcp-batch-executor.mjs +1215 -0
  144. package/src/compute/gcp-batch-failure.mjs +92 -0
  145. package/src/compute/gcp-batch-job.mjs +527 -0
  146. package/src/compute/gcp-batch-lifecycle.mjs +81 -0
  147. package/src/compute/gcp-checkpoint-worker.mjs +1633 -0
  148. package/src/compute/local-codex-compiler.mjs +52 -0
  149. package/src/compute/measurement-hardware.mjs +128 -0
  150. package/src/compute/remote-attempt.mjs +226 -0
  151. package/src/compute/requirements.mjs +124 -0
  152. package/src/compute/research-phases.mjs +48 -0
  153. package/src/compute/scheduler.mjs +452 -0
  154. package/src/compute/shared-workloads.mjs +26 -0
  155. package/src/compute/stage-archive.mjs +79 -0
  156. package/src/contracts/campaign-contract.mjs +52 -0
  157. package/src/contracts/execution-contract.mjs +819 -0
  158. package/src/contracts/execution-mode.mjs +19 -0
  159. package/src/contracts/execution-timeouts.mjs +45 -0
  160. package/src/contracts/execution-workload.mjs +68 -0
  161. package/src/contracts/preflight-schema.mjs +25 -0
  162. package/src/contracts/public-contract.mjs +63 -0
  163. package/src/contracts/subject-tags.mjs +31 -0
  164. package/src/dashboard/data.mjs +898 -0
  165. package/src/dashboard/server.mjs +79 -0
  166. package/src/dashboard/static/dashboard.css +366 -0
  167. package/src/dashboard/static/dashboard.js +560 -0
  168. package/src/dashboard/static/index.html +85 -0
  169. package/src/deployment/community-policy.mjs +9 -0
  170. package/src/deployment/environment.mjs +112 -0
  171. package/src/deployment/guided.mjs +98 -0
  172. package/src/deployment/handoff.mjs +102 -0
  173. package/src/deployment/local-contract.mjs +31 -0
  174. package/src/deployment/local.mjs +100 -0
  175. package/src/deployment/prepare.mjs +46 -0
  176. package/src/deployment/recipe.mjs +108 -0
  177. package/src/deployment/supplement.mjs +51 -0
  178. package/src/deployment/terminal.mjs +43 -0
  179. package/src/diagnosis/renderer.mjs +75 -0
  180. package/src/diagnosis/target-failure.mjs +46 -0
  181. package/src/evidence/parser-registry.mjs +54 -0
  182. package/src/evidence/parsers/fasttext-classification.mjs +82 -0
  183. package/src/evidence/parsers/json-scalar.mjs +96 -0
  184. package/src/evidence/parsers/simcse-senteval.mjs +104 -0
  185. package/src/evidence/parsers/starspace-classification.mjs +78 -0
  186. package/src/evidence/registry.mjs +147 -0
  187. package/src/execution/runner-audit.mjs +473 -0
  188. package/src/gcp/auth.mjs +106 -0
  189. package/src/gcp/batch-client.mjs +120 -0
  190. package/src/gcp/resource-discovery.mjs +177 -0
  191. package/src/gcp/rest.mjs +82 -0
  192. package/src/gcp/secret-manager.mjs +34 -0
  193. package/src/gcp/signed-url.mjs +133 -0
  194. package/src/gcp/storage.mjs +220 -0
  195. package/src/graph/command.mjs +41 -0
  196. package/src/graph/execution.mjs +97 -0
  197. package/src/graph/model.mjs +37 -0
  198. package/src/graph/presentation.mjs +110 -0
  199. package/src/graph/query.mjs +159 -0
  200. package/src/graph/research-relations.mjs +69 -0
  201. package/src/graph/source-page.mjs +12 -0
  202. package/src/graph/source-preview.mjs +34 -0
  203. package/src/graph/validate.mjs +76 -0
  204. package/src/job.mjs +496 -0
  205. package/src/network/autodl-routing-proxy.mjs +462 -0
  206. package/src/network/egress-proxy.mjs +158 -0
  207. package/src/observability/event-contract.mjs +230 -0
  208. package/src/observability/pipeline-monitor.mjs +166 -0
  209. package/src/pipeline/orchestrator.mjs +1281 -0
  210. package/src/pipeline/recovery-error.mjs +11 -0
  211. package/src/pipeline/replay.mjs +304 -0
  212. package/src/pipeline/shared-execution.mjs +115 -0
  213. package/src/pipeline/stage-checkpoint.mjs +86 -0
  214. package/src/pipeline/stage-recovery.mjs +101 -0
  215. package/src/pipeline/targets.mjs +110 -0
  216. package/src/process.mjs +143 -0
  217. package/src/protocol.mjs +312 -0
  218. package/src/provider/codex-account.mjs +44 -0
  219. package/src/provider/codex-completion.mjs +49 -0
  220. package/src/provider/completion.mjs +292 -0
  221. package/src/provider/model-client.mjs +44 -0
  222. package/src/provider/model-route.mjs +29 -0
  223. package/src/provider/openrouter-readiness.mjs +189 -0
  224. package/src/provider/reader-bridge.mjs +35 -0
  225. package/src/provider/relay.mjs +263 -0
  226. package/src/provider/runtime-auth.mjs +40 -0
  227. package/src/public/cap.d.mts +90 -0
  228. package/src/public/cap.mjs +12 -0
  229. package/src/public/contracts.d.mts +2 -0
  230. package/src/public/host.mjs +171 -0
  231. package/src/public/operations.d.mts +11 -0
  232. package/src/public/presentation.d.mts +4 -0
  233. package/src/records/views.mjs +26 -0
  234. package/src/remote/command.mjs +178 -0
  235. package/src/remote/ssh.mjs +59 -0
  236. package/src/repository-origin.mjs +81 -0
  237. package/src/reproduction/evidence-feedback.mjs +96 -0
  238. package/src/reproduction/incomplete-initialization.mjs +25 -0
  239. package/src/reproduction/lifecycle.mjs +253 -0
  240. package/src/reproduction/plan.mjs +132 -0
  241. package/src/reproduction/prompts.mjs +70 -0
  242. package/src/reproduction/runner.mjs +188 -0
  243. package/src/reproduction/summary.mjs +130 -0
  244. package/src/reproduction/workspace-mode.mjs +7 -0
  245. package/src/research/automatic-admission.mjs +156 -0
  246. package/src/research/compiler-coverage.mjs +85 -0
  247. package/src/research/compiler-failure.mjs +24 -0
  248. package/src/research/compiler-normalization-guards.mjs +112 -0
  249. package/src/research/compiler-repair.mjs +3 -0
  250. package/src/research/compiler.mjs +853 -0
  251. package/src/research/continuation-selection.mjs +26 -0
  252. package/src/research/execution-graph-context.mjs +43 -0
  253. package/src/research/experiment-importance.mjs +15 -0
  254. package/src/research/inventory-handoff.mjs +104 -0
  255. package/src/research/inventory-revisions.mjs +32 -0
  256. package/src/research/mineru-local.mjs +73 -0
  257. package/src/research/paper-command.mjs +19 -0
  258. package/src/research/paper-markdown.mjs +180 -0
  259. package/src/research/paper-source-map.mjs +69 -0
  260. package/src/research/planning-policy.mjs +88 -0
  261. package/src/research/reference-materials.mjs +11 -0
  262. package/src/research/reproduction-scope.mjs +30 -0
  263. package/src/research/research-map.mjs +94 -0
  264. package/src/research/research-objects.mjs +88 -0
  265. package/src/research/source-discovery.mjs +646 -0
  266. package/src/research/source-observations.mjs +75 -0
  267. package/src/research/source-review-cli-mcp.mjs +26 -0
  268. package/src/research/source-review-input.mjs +209 -0
  269. package/src/research/source-review-local-codex.mjs +36 -0
  270. package/src/research/source-review-model.mjs +70 -0
  271. package/src/research/source-review.mjs +173 -0
  272. package/src/research/structure.mjs +3163 -0
  273. package/src/research-card/renderer.mjs +277 -0
  274. package/src/research-card/verified-conclusion.mjs +143 -0
  275. package/src/results/output-registry.mjs +183 -0
  276. package/src/runtime/claude-code.mjs +52 -0
  277. package/src/runtime/codex-capacity-retry.mjs +87 -0
  278. package/src/runtime/codex.mjs +64 -0
  279. package/src/runtime/config.mjs +157 -0
  280. package/src/runtime/final-output.mjs +40 -0
  281. package/src/runtime/index.mjs +21 -0
  282. package/src/runtime/local-codex.mjs +74 -0
  283. package/src/runtime/opencode.mjs +95 -0
  284. package/src/runtime/prompt.mjs +13 -0
  285. package/src/sandbox/docker.mjs +363 -0
  286. package/src/settings/command.mjs +297 -0
  287. package/src/settings/store.mjs +119 -0
  288. package/src/telemetry/pricing.mjs +68 -0
  289. package/src/telemetry/usage.mjs +265 -0
  290. package/src/terminal/events.mjs +97 -0
  291. package/src/terminal/input.mjs +40 -0
  292. package/src/terminal/plain.mjs +40 -0
  293. package/src/terminal/remote-stream.mjs +22 -0
  294. package/src/terminal/screen.mjs +214 -0
  295. package/src/terminal/transcript.mjs +69 -0
  296. package/src/util.mjs +107 -0
  297. package/src/verification/ai-assessor.mjs +534 -0
  298. package/src/verification/claim-evaluator.mjs +242 -0
  299. package/src/verification/evidence-context.mjs +165 -0
  300. package/src/verification/evidence-reader.mjs +95 -0
  301. package/src/verification/integrity.mjs +570 -0
  302. package/src/verification/tolerance.mjs +32 -0
  303. package/src/workloads/cpu-research-preparation.mjs +56 -0
  304. package/src/workloads/definition.mjs +74 -0
  305. package/src/workloads/phase-aware-reproduction.mjs +46 -0
  306. package/src/workloads/reproduction.mjs +85 -0
  307. package/src/workspace/command.mjs +242 -0
  308. package/src/workspace/control.mjs +49 -0
  309. package/src/workspace/entry.mjs +28 -0
  310. package/src/workspace/input.mjs +93 -0
  311. package/src/workspace/interactive.mjs +94 -0
  312. package/src/workspace/jobs.mjs +418 -0
  313. package/src/workspace/session.mjs +97 -0
  314. package/src/workspace/worker.mjs +137 -0
  315. package/ui/arkgraph/ambient-motion.mjs +10 -0
  316. package/ui/arkgraph/app.jsx +153 -0
  317. package/ui/arkgraph/boot.js +6 -0
  318. package/ui/arkgraph/camera-motion.mjs +20 -0
  319. package/ui/arkgraph/context-reveal.mjs +39 -0
  320. package/ui/arkgraph/details.css +3 -0
  321. package/ui/arkgraph/entry.jsx +28 -0
  322. package/ui/arkgraph/experiment-curves.mjs +17 -0
  323. package/ui/arkgraph/experiment-selection.mjs +15 -0
  324. package/ui/arkgraph/experiment-style.css +26 -0
  325. package/ui/arkgraph/experiment-ui.jsx +32 -0
  326. package/ui/arkgraph/frame.html +1 -0
  327. package/ui/arkgraph/graph-gestures.mjs +62 -0
  328. package/ui/arkgraph/label-layout.mjs +57 -0
  329. package/ui/arkgraph/locales/en.json +229 -0
  330. package/ui/arkgraph/locales/source-types.json +15 -0
  331. package/ui/arkgraph/localization-build.mjs +27 -0
  332. package/ui/arkgraph/material-build.mjs +23 -0
  333. package/ui/arkgraph/material-colors.mjs +39 -0
  334. package/ui/arkgraph/material-style.css +15 -0
  335. package/ui/arkgraph/open-graph.jsx +326 -0
  336. package/ui/arkgraph/outline.jsx +49 -0
  337. package/ui/arkgraph/package-lock.json +888 -0
  338. package/ui/arkgraph/package.json +17 -0
  339. package/ui/arkgraph/reading-layout.mjs +130 -0
  340. package/ui/arkgraph/reading-presentation.mjs +73 -0
  341. package/ui/arkgraph/record-detail.css +51 -0
  342. package/ui/arkgraph/record-details.jsx +29 -0
  343. package/ui/arkgraph/research-types.mjs +31 -0
  344. package/ui/arkgraph/selection-mark.jsx +6 -0
  345. package/ui/arkgraph/soft-spine.mjs +26 -0
  346. package/ui/arkgraph/steering-style.css +187 -0
  347. package/ui/arkgraph/style.css +272 -0
@@ -0,0 +1,52 @@
1
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { prepareCompilationBundle, normalizeCompilationTask, compilerPrompt, acceptCompilerDraft } from "../research/compiler.mjs";
4
+ import { reviewCompilerSources } from "../research/source-review.mjs";
5
+ import { createLocalSourceReviewCompletion } from "../research/source-review-local-codex.mjs";
6
+ import { composeAgentPrompt } from "../runtime/prompt.mjs";
7
+ import { runLocalCodex } from "../runtime/local-codex.mjs";
8
+ import { captureFinalWorkspace, updateRunState } from "../job.mjs";
9
+ import { readJson, writeJson, createRunId } from "../util.mjs";
10
+
11
+ // Only the execution transport and /job path mount change. The prepared task,
12
+ // compiler prompt, normalization, coverage and source-review gates are shared.
13
+ export const localizeCompilerPaths = (value, directory) => typeof value === "string"
14
+ ? value.replaceAll("/job/", `${directory}/`) : Array.isArray(value)
15
+ ? value.map((item) => localizeCompilerPaths(item, directory)) : value && typeof value === "object"
16
+ ? Object.fromEntries(Object.entries(value).map(([key, item]) => [key, localizeCompilerPaths(item, directory)])) : value;
17
+
18
+ export async function runLocalCodexCompiler({ taskPath, runsDirectory, runId = createRunId(), prepareMarkdown,
19
+ validateResearch = async () => [], progress = () => {}, runCli = runLocalCodex }) {
20
+ const task = normalizeCompilationTask(await readJson(taskPath));
21
+ if (task.agent.runtime !== "codex" || !task.agent.model || task.agent.validationRetries !== 0)
22
+ throw new Error("Local CLI single-run compiler requires codex, an explicit model and validationRetries=0");
23
+ const bundle = await prepareCompilationBundle({ task, taskDirectory: path.dirname(path.resolve(taskPath)), runsDirectory, runId, prepareMarkdown });
24
+ bundle.runtimeAgent = { ...task.agent, authentication: "chatgpt_login" };
25
+ const cliDirectory = path.join(bundle.directories.execution, "local-codex");
26
+ await mkdir(cliDirectory, { recursive: true });
27
+ const instructions = await readFile(path.join(bundle.directories.input, "agent-instructions.md"), "utf8");
28
+ const prompt = localizeCompilerPaths(composeAgentPrompt(instructions, compilerPrompt(1, [])), bundle.directories.run);
29
+ await writeFile(path.join(cliDirectory, "prompt.txt"), prompt);
30
+ await writeJson(path.join(bundle.directories.input, "compile-task.json"), localizeCompilerPaths(bundle.runtimeTask, bundle.directories.run));
31
+ await updateRunState(bundle, { status: "running", sandbox: "local-codex-workspace-write", image: null,
32
+ startedAt: new Date().toISOString(), authentication: "chatgpt_login", model: task.agent.model });
33
+ try {
34
+ progress(task.compilationStage === "inventory" ? "本机 Codex CLI 开始模块二研究清单整理" : "本机 Codex CLI 开始复现准备");
35
+ const execution = await runCli({ prompt, cwd: bundle.directories.repository, directory: cliDirectory,
36
+ model: task.agent.model, effort: task.agent.effort, writableDirectories: [bundle.directories.output],
37
+ timeoutMs: task.environment.timeoutMinutes * 60_000, progress });
38
+ await writeJson(path.join(cliDirectory, "execution.json"), execution);
39
+ if (execution.code !== 0 || execution.timedOut) throw new Error("Local Codex compiler did not finish successfully; see retained execution logs");
40
+ progress(task.compilationStage === "inventory" ? "研究清单已返回,执行内容校验和独立来源复核" : "复现准备已返回,核对原始清单、实验覆盖与调度约束");
41
+ const complete = createLocalSourceReviewCompletion({ directory: path.join(bundle.directories.execution, "local-source-review"),
42
+ model: task.agent.model, effort: task.agent.effort, progress, runCli });
43
+ const accepted = await acceptCompilerDraft({ bundle, draft: await readJson(path.join(bundle.directories.output, "research.json")),
44
+ validateResearch, sourceReviewer: (options) => reviewCompilerSources({ ...options, complete }) });
45
+ await updateRunState(bundle, { status: "completed", finishedAt: new Date().toISOString(), output: "output/research.normalized.json",
46
+ normalizationWarnings: accepted.normalizationWarnings, usage: execution.usage });
47
+ return { bundle, ...accepted };
48
+ } catch (error) {
49
+ await updateRunState(bundle, { status: "invalid_result", finishedAt: new Date().toISOString(), validationIssues: [error.message] });
50
+ throw error;
51
+ } finally { await captureFinalWorkspace(bundle); }
52
+ }
@@ -0,0 +1,128 @@
1
+ /** Scientific device constraints are applied before operational cost ranking. */
2
+ // Metric spelling is only a hint; explicit scientific semantics own the gate.
3
+ export const HARDWARE_SENSITIVE_METRIC = /(?:^|[_ -])(time|latency|throughput|memory|energy|runtime|duration|speed(?:up)?|joules?|watts?)(?:$|[_ -])/i;
4
+ export function measurementRequiresHardwareProtocol(measurement) {
5
+ return measurement?.quantityKind === "device_performance";
6
+ }
7
+
8
+ export function measurementHardwareIssues(experiment, { requireDeclaration = false } = {}) {
9
+ const benchmark = experiment?.protocol?.benchmark;
10
+ const compute = experiment?.compute;
11
+ const measurements = Array.isArray(experiment?.measurements) ? experiment.measurements : [];
12
+ const sensitive = measurements.some(measurementRequiresHardwareProtocol);
13
+ const issues = [];
14
+ for (const measurement of measurements) {
15
+ if (requireDeclaration && measurement?.quantityKind === undefined
16
+ && HARDWARE_SENSITIVE_METRIC.test(String(measurement?.metric ?? ""))) {
17
+ issues.push(`Hardware-admission retry: classify measurement ${measurement?.reportedMeasurementId ?? measurement?.metric ?? "unknown"} with quantityKind before deciding whether hardware constraints apply.`);
18
+ }
19
+ if (measurement?.quantityKind !== undefined && !["device_performance", "operation_count", "numerical_result"].includes(measurement.quantityKind)) {
20
+ issues.push("measurement.quantityKind must be device_performance, operation_count or numerical_result");
21
+ }
22
+ }
23
+ if (requireDeclaration && sensitive && !benchmark?.device) {
24
+ issues.push("Hardware-boundary retry: declare protocol.benchmark.device as cpu or gpu from the scientific measurement, not from the cheapest available machine. Separate device-independent counts from device performance when necessary.");
25
+ }
26
+ if (requireDeclaration && sensitive && !benchmark?.hardwareBinding) {
27
+ issues.push("Hardware-admission retry: declare protocol.benchmark.hardwareBinding as required, not_required or unknown from the paper's measurement conditions. Do not infer portability from the currently available machine.");
28
+ }
29
+ if (requireDeclaration && sensitive && !benchmark?.hardwareBindingRationale) {
30
+ issues.push("Hardware-admission retry: explain the paper-grounded hardware-binding decision in protocol.benchmark.hardwareBindingRationale.");
31
+ }
32
+ if (requireDeclaration && sensitive && !benchmark?.hardwareBindingSources?.length) {
33
+ issues.push("Hardware-admission retry: cite the paper or repository locations establishing the hardware-binding decision in protocol.benchmark.hardwareBindingSources.");
34
+ }
35
+ if (benchmark?.device !== undefined && !["cpu", "gpu"].includes(benchmark.device)) {
36
+ issues.push("protocol.benchmark.device must be cpu or gpu");
37
+ }
38
+ if (benchmark?.device === "gpu" && (experiment?.compute?.accelerator !== "required" || experiment?.compute?.cpuFallbackAllowed !== false)) {
39
+ issues.push("Hardware-boundary retry: a GPU benchmark requires compute.accelerator=required and cpuFallbackAllowed=false. CPU timing or process RSS cannot replace GPU latency or device memory.");
40
+ }
41
+ if (benchmark?.requiredGpuTypes !== undefined && (!Array.isArray(benchmark.requiredGpuTypes)
42
+ || benchmark.requiredGpuTypes.some((value) => typeof value !== "string" || !value.trim()))) {
43
+ issues.push("protocol.benchmark.requiredGpuTypes must be an array of GPU type identifiers");
44
+ }
45
+ if (benchmark?.requiredGpuTypes?.length && benchmark.device !== "gpu") {
46
+ issues.push("protocol.benchmark.requiredGpuTypes requires device=gpu");
47
+ }
48
+ if (benchmark?.requiredGpuCount !== undefined && (!Number.isInteger(benchmark.requiredGpuCount) || benchmark.requiredGpuCount < 1)) {
49
+ issues.push("protocol.benchmark.requiredGpuCount must be a positive integer");
50
+ }
51
+ if (benchmark?.requiredVramGb !== undefined && (!(Number.isFinite(benchmark.requiredVramGb)) || benchmark.requiredVramGb <= 0)) {
52
+ issues.push("protocol.benchmark.requiredVramGb must be a positive finite number");
53
+ }
54
+ if (benchmark?.hardwareBinding !== undefined && !["required", "not_required", "unknown"].includes(benchmark.hardwareBinding)) {
55
+ issues.push("protocol.benchmark.hardwareBinding must be required, not_required or unknown");
56
+ }
57
+ if (benchmark?.hardwareBindingRationale !== undefined && (typeof benchmark.hardwareBindingRationale !== "string" || !benchmark.hardwareBindingRationale.trim())) {
58
+ issues.push("protocol.benchmark.hardwareBindingRationale must explain the source-grounded binding decision");
59
+ }
60
+ if (benchmark?.hardwareBindingSources !== undefined && (!Array.isArray(benchmark.hardwareBindingSources)
61
+ || benchmark.hardwareBindingSources.length === 0
62
+ || benchmark.hardwareBindingSources.some((source) =>
63
+ !source || typeof source.sourceId !== "string" || !source.sourceId.trim()
64
+ || typeof source.locator !== "string" || !source.locator.trim()))) {
65
+ issues.push("protocol.benchmark.hardwareBindingSources must contain sourceId and locator records");
66
+ }
67
+ if (benchmark?.comparisonIntent !== undefined && !["reference_conditions", "cross_hardware"].includes(benchmark.comparisonIntent)) {
68
+ issues.push("protocol.benchmark.comparisonIntent must be reference_conditions or cross_hardware");
69
+ }
70
+ if (benchmark?.comparisonRationale !== undefined && (typeof benchmark.comparisonRationale !== "string" || !benchmark.comparisonRationale.trim())) {
71
+ issues.push("protocol.benchmark.comparisonRationale must explain the intended comparison");
72
+ }
73
+ if (benchmark?.hardwareBinding === "required") {
74
+ if (benchmark.comparisonIntent !== "reference_conditions") {
75
+ issues.push("Hardware-admission retry: a paper-bound hardware measurement must use comparisonIntent=reference_conditions. A cross-hardware run is a separate exploratory question and cannot verify the reported value.");
76
+ }
77
+ if (typeof benchmark.referenceHardware !== "string" || !benchmark.referenceHardware.trim()) {
78
+ issues.push("protocol.benchmark.referenceHardware is required when hardwareBinding=required");
79
+ }
80
+ if (benchmark.device === "gpu" && !benchmark.requiredGpuTypes?.length) {
81
+ issues.push("Hardware-admission retry: a GPU-bound reported measurement must declare exact catalog GPU types in protocol.benchmark.requiredGpuTypes.");
82
+ }
83
+ if (benchmark.device === "gpu" && (!Number.isInteger(benchmark.requiredGpuCount) || benchmark.requiredGpuCount < 1)) {
84
+ issues.push("Hardware-admission retry: a GPU-bound reported measurement must declare protocol.benchmark.requiredGpuCount from the paper conditions.");
85
+ }
86
+ if (Number.isInteger(benchmark.requiredGpuCount) && Number(compute?.gpuCount ?? 0) < benchmark.requiredGpuCount) {
87
+ issues.push(`Hardware-admission retry: compute.gpuCount=${compute?.gpuCount ?? 0} is below the paper-bound requirement ${benchmark.requiredGpuCount}.`);
88
+ }
89
+ if (Number.isFinite(benchmark.requiredVramGb) && Number(compute?.minVramGb ?? 0) < benchmark.requiredVramGb) {
90
+ issues.push(`Hardware-admission retry: compute.minVramGb=${compute?.minVramGb ?? 0} is below the paper-bound per-GPU requirement ${benchmark.requiredVramGb} GiB.`);
91
+ }
92
+ }
93
+ return issues;
94
+ }
95
+
96
+ export function measurementHardwareConstraint(experiment) {
97
+ const benchmark = experiment?.protocol?.benchmark;
98
+ return {
99
+ device: benchmark?.device ?? null,
100
+ requiredGpuTypes: benchmark?.requiredGpuTypes ?? [],
101
+ requiredGpuCount: benchmark?.requiredGpuCount ?? null,
102
+ requiredVramGb: benchmark?.requiredVramGb ?? null,
103
+ referenceHardware: benchmark?.referenceHardware ?? null,
104
+ hardwareBinding: benchmark?.hardwareBinding ?? null,
105
+ hardwareBindingRationale: benchmark?.hardwareBindingRationale ?? null,
106
+ hardwareBindingSources: benchmark?.hardwareBindingSources ?? [],
107
+ comparisonIntent: benchmark?.comparisonIntent ?? null,
108
+ comparisonRationale: benchmark?.comparisonRationale ?? null,
109
+ };
110
+ }
111
+
112
+ export function measurementHardwareRejections(profile, constraint) {
113
+ const reasons = [];
114
+ if (constraint.device && profile.accelerator !== constraint.device) {
115
+ reasons.push(`科研测量要求 ${constraint.device} 设备,不能以 ${profile.accelerator} 替代`);
116
+ }
117
+ if (constraint.requiredGpuTypes.length && !constraint.requiredGpuTypes.some((type) =>
118
+ type.trim().toLowerCase() === String(profile.gpu?.type ?? "").trim().toLowerCase())) {
119
+ reasons.push(`科研测量限定 GPU 型号:${constraint.requiredGpuTypes.join("、")}`);
120
+ }
121
+ if (constraint.requiredGpuCount !== null && (profile.gpu?.count ?? 0) < constraint.requiredGpuCount) {
122
+ reasons.push(`科研测量要求至少 ${constraint.requiredGpuCount} 张 GPU`);
123
+ }
124
+ if (constraint.requiredVramGb !== null && (profile.gpu?.vramGb ?? 0) < constraint.requiredVramGb) {
125
+ reasons.push(`科研测量要求每张 GPU 至少 ${constraint.requiredVramGb} GiB 显存`);
126
+ }
127
+ return reasons;
128
+ }
@@ -0,0 +1,226 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ import { redactString } from "../cap/redaction.mjs";
5
+ import { pathExists, readJson } from "../util.mjs";
6
+
7
+ const ERROR_TAIL_CHARACTERS = 4_000;
8
+ const TRACE_TAIL_LINES = 240;
9
+
10
+ export async function readRemoteAttempt(bundle, attempt) {
11
+ const target = path.join(
12
+ bundle.directories.execution,
13
+ `remote-attempt-${attempt}.json`,
14
+ );
15
+ return await pathExists(target) ? await readJson(target) : null;
16
+ }
17
+
18
+ export async function describeRemoteAgentFailure(bundle, attempt) {
19
+ const budgetFailurePath = path.join(bundle.directories.execution, "paper-budget-exhausted.json");
20
+ if (await pathExists(budgetFailurePath)) {
21
+ const recorded = await readJson(budgetFailurePath);
22
+ // A preserved process failure is evidence about that attempt, not a new
23
+ // spending decision. Reconcile it with the submitted result like any exit.
24
+ return {
25
+ attempt, exitCode: 1, failureCode: "paper.budget_exhausted",
26
+ failureCategory: "budget", failureStage: "execution", retryable: false,
27
+ message: redactString(`该次执行因预算停止${recorded.at ? `(${recorded.at})` : ""}:${recorded.reason || "预算不足或价格无法确认"}`),
28
+ };
29
+ }
30
+ const remoteAttempt = await readRemoteAttempt(bundle, attempt);
31
+ const reportedExitCode = Number(remoteAttempt?.exitCode);
32
+ const agentExitCode = Number(remoteAttempt?.agentExitCode);
33
+ const agentFailed = Number.isInteger(agentExitCode) && agentExitCode !== 0;
34
+ const exitCode = agentFailed ? agentExitCode : reportedExitCode;
35
+ if (!Number.isInteger(exitCode) || exitCode === 0) return null;
36
+
37
+ const agentStalled = remoteAttempt?.agentStalled === true
38
+ || remoteAttempt?.agentStalled === 1;
39
+ const timedOut = remoteAttempt?.timedOut === true || remoteAttempt?.timedOut === 1;
40
+ const reportedCategory = typeof remoteAttempt?.failureCategory === "string"
41
+ ? remoteAttempt.failureCategory
42
+ : "agent";
43
+ const failureCategory = timedOut || agentStalled ? "timeout" : agentFailed ? "agent" : reportedCategory;
44
+ const reportedStage = typeof remoteAttempt?.failureStage === "string"
45
+ ? remoteAttempt.failureStage
46
+ : "execution";
47
+ const failureStage = agentStalled
48
+ ? reportedStage
49
+ : timedOut ? "execution" : agentFailed ? "execution" : reportedStage;
50
+ const retryable = timedOut || agentStalled ? false : agentFailed || remoteAttempt?.retryable !== false;
51
+ const diagnostics = failureCategory === "publication" || failureStage.includes("publication") || failureStage === "storage-headroom"
52
+ ? [await readDiagnosticFile(bundle, "publication-stderr.log")]
53
+ : await agentFailureDiagnostics(bundle);
54
+ const tail = redactString(diagnostics.filter(Boolean).join("\n").slice(-ERROR_TAIL_CHARACTERS))
55
+ .replaceAll(/\s+/g, " ")
56
+ .trim();
57
+ const accountFailure = bundle.runtimeAgent?.codexAccountId ? classifyProviderActionRequired(tail) : null;
58
+ if (accountFailure) return { attempt, exitCode, failureCategory: "provider", failureStage: "provider", failureCode: accountFailure.code, retryable: false, message: accountFailure.message, recoveryAction: accountFailure.recoveryAction };
59
+ const runtime = bundle.runtimeAgent?.runtime ?? "Agent";
60
+ if (diagnostics.some(value => value.startsWith("citeark.codex_capacity_retries_exhausted:"))) return { attempt, exitCode,
61
+ failureCategory: "provider", failureStage: "provider", failureCode: "codex.capacity_retry_exhausted", retryable: false,
62
+ message: "模型容量不足;同一环境内的等待恢复已达到上限,已停止并保留编译会话",
63
+ recoveryAction: "等待模型容量恢复后显式续跑" };
64
+ const prefix = agentStalled
65
+ ? `${runtime} 长时间没有有效进展,已由监督器终止并保留现场`
66
+ : timedOut
67
+ ? `${runtime} 已耗尽不可变执行时限`
68
+ : failureCategory === "publication"
69
+ ? `结果发布在 ${failureStage} 阶段失败`
70
+ : `${runtime} 第 ${attempt} 次执行失败`;
71
+ const publicationSuffix = agentFailed && reportedCategory === "publication"
72
+ ? `;随后结果发布也在 ${reportedStage} 阶段失败`
73
+ : "";
74
+ return {
75
+ attempt,
76
+ exitCode,
77
+ failureCategory,
78
+ failureStage,
79
+ failureCode: timedOut
80
+ ? remoteAttempt?.failureCode ?? "execution.runtime_budget_exhausted"
81
+ : remoteAttempt?.failureCode,
82
+ agentStalled,
83
+ timedOut,
84
+ retryable,
85
+ message: `${prefix}(退出码 ${exitCode})${
86
+ tail ? `:${tail}` : ",但没有留下 stderr"
87
+ }${publicationSuffix}`,
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Provider account/configuration failures cannot be repaired by rerunning the
93
+ * same compiler input. Convert raw runtime diagnostics into a stable, safe
94
+ * operator-facing reason and let the processing worker end the attempt.
95
+ */
96
+ export function classifyProviderActionRequired(value) {
97
+ const diagnostic = value instanceof Error ? value.message : String(value ?? "");
98
+ if (/model.{0,160}is not supported when using Codex with a ChatGPT account/i.test(diagnostic)) {
99
+ return { code: "codex.subscription_model_unavailable", failureCategory: "provider", retryable: false,
100
+ message: "当前 Codex 订阅执行环境无法使用所选模型,已停止自动重试并保留诊断",
101
+ recoveryAction: "核对科学容器中的 Codex 版本及该账号的模型可用性,修复后显式续跑" };
102
+ }
103
+ if (/usage_limit_reached|you(?:'|’)ve hit your usage limit|your usage limit has been reached/i.test(diagnostic)) {
104
+ return { code: "codex.subscription_quota_exhausted", failureCategory: "provider", retryable: false,
105
+ message: "Codex 订阅额度已用尽;已停止本次处理并保留现场",
106
+ recoveryAction: "等待此账号额度恢复后再续跑" };
107
+ }
108
+ if (
109
+ (/\b402\b/.test(diagnostic) &&
110
+ /(?:api|provider|payment|credit|balance|openrouter|openai|anthropic)/i.test(diagnostic)) ||
111
+ /(?:insufficient|not enough|more)\s+(?:credit|credits|balance)/i.test(diagnostic) ||
112
+ /(?:credit|balance).{0,40}(?:exhausted|depleted|too low)/i.test(diagnostic)
113
+ ) {
114
+ return {
115
+ code: "provider_credit_exhausted",
116
+ failureCategory: "provider",
117
+ retryable: false,
118
+
119
+ message: "模型服务账户余额不足,本次处理已结束;充值或切换可用模型后可创建续跑",
120
+ recoveryAction: "充值模型服务账户,或切换到可用的模型配置后重试",
121
+ };
122
+ }
123
+ if (
124
+ /\b(?:401|403)\b/.test(diagnostic) &&
125
+ /(?:provider|api|auth|credential|token|key|unauthor|forbidden|openrouter|openai|anthropic)/i.test(diagnostic)
126
+ ) {
127
+ return {
128
+ code: "provider_credentials_invalid",
129
+ failureCategory: "provider",
130
+ retryable: false,
131
+
132
+ message: "模型服务凭证无效或无权访问,本次处理已结束;更新凭证后可创建续跑",
133
+ recoveryAction: "检查模型服务凭证与模型访问权限后重试",
134
+ };
135
+ }
136
+ return null;
137
+ }
138
+
139
+ /**
140
+ * The remote Agent process and the scientific result envelope are separate
141
+ * trust boundaries. A non-zero wrapper exit is useful attempt diagnostics, but
142
+ * it must not invalidate a result that already passed deterministic protocol
143
+ * validation. Only merge the remote failure into validation when the submitted
144
+ * envelope is itself missing or invalid.
145
+ */
146
+ export function reconcileRemoteAgentOutcome({ validation, remoteFailure }) {
147
+ if (!remoteFailure) {
148
+ return { validation, blockingFailure: null };
149
+ }
150
+ const executionSucceeded = validation?.result?.execution?.status === "succeeded";
151
+ const preservedWorkspaceOutcome = validation?.normalization?.source === "platform-workspace-summary";
152
+ const requiresCompletedEvidence = ["timeout", "budget", "provider"].includes(remoteFailure.failureCategory);
153
+ if (validation.issues.length === 0 && (!requiresCompletedEvidence || executionSucceeded || preservedWorkspaceOutcome)) {
154
+ return { validation, blockingFailure: null };
155
+ }
156
+ return {
157
+ validation: {
158
+ ...validation,
159
+ issues: [...new Set([remoteFailure.message, ...validation.issues])],
160
+ },
161
+ blockingFailure: remoteFailure.retryable === false ? remoteFailure : null,
162
+ };
163
+ }
164
+
165
+ async function agentFailureDiagnostics(bundle) {
166
+ const [stderr, trace, relay] = await Promise.all([
167
+ readDiagnosticFile(bundle, "claude-stderr.log"),
168
+ readDiagnosticFile(bundle, "trace.jsonl"),
169
+ readDiagnosticFile(bundle, "provider-pro-relay.log"),
170
+ ]);
171
+ const traceError = structuredTraceError(trace);
172
+ const relayError = relay
173
+ .split(/\r?\n/)
174
+ .filter((line) => line.trim() && !/codex_pro_relay\.ready/.test(line))
175
+ .join("\n");
176
+ return [stderr, traceError, relayError];
177
+ }
178
+
179
+ async function readDiagnosticFile(bundle, name) {
180
+ return await readFile(
181
+ path.join(bundle.directories.execution, name),
182
+ "utf8",
183
+ ).catch(() => "");
184
+ }
185
+
186
+ function structuredTraceError(trace) {
187
+ const lines = trace.split(/\r?\n/).filter(Boolean).slice(-TRACE_TAIL_LINES).reverse();
188
+ for (const line of lines) {
189
+ let event;
190
+ try {
191
+ event = JSON.parse(line);
192
+ } catch {
193
+ continue;
194
+ }
195
+ if (!event || typeof event !== "object") continue;
196
+ const kind = [event.type, event.event, event.status, event.error?.name]
197
+ .filter((value) => typeof value === "string")
198
+ .join(" ");
199
+ if (!event.error && !/(?:error|fail|abort|cancel)/i.test(kind)) continue;
200
+ const message = firstDiagnosticString([
201
+ event.error?.data?.message,
202
+ event.error?.message,
203
+ event.data?.message,
204
+ event.message,
205
+ event.reason,
206
+ event.error,
207
+ ]);
208
+ if (message) return `${kind || "runtime error"}: ${message}`;
209
+ }
210
+ return "";
211
+ }
212
+
213
+ function firstDiagnosticString(values) {
214
+ for (const value of values) {
215
+ if (typeof value === "string" && value.trim()) return value.trim();
216
+ if (value && typeof value === "object") {
217
+ try {
218
+ const serialized = JSON.stringify(value);
219
+ if (serialized && serialized !== "{}") return serialized;
220
+ } catch {
221
+ // Ignore non-serializable diagnostic payloads.
222
+ }
223
+ }
224
+ }
225
+ return "";
226
+ }
@@ -0,0 +1,124 @@
1
+ import { isRecord } from "../util.mjs";
2
+
3
+ export const ACCELERATOR_REQUIREMENTS = new Set(["none", "optional", "required"]);
4
+
5
+ export function validateDeclaredComputeRequirement(value, label = "compute") {
6
+ const issues = [];
7
+ if (!isRecord(value)) return [`${label} 必须是对象`];
8
+ if (!ACCELERATOR_REQUIREMENTS.has(value.accelerator)) {
9
+ issues.push(`${label}.accelerator 必须是 none、optional 或 required`);
10
+ }
11
+ for (const field of ["cpuCores", "memoryGb"]) {
12
+ if (!isFinitePositive(value[field])) {
13
+ issues.push(`${label}.${field} 必须是大于 0 的有限数值`);
14
+ }
15
+ }
16
+ if (!isFiniteNonNegative(value.gpuCount)) {
17
+ issues.push(`${label}.gpuCount 必须是非负有限数值`);
18
+ } else if (value.accelerator !== "none" && value.gpuCount <= 0) {
19
+ issues.push(`${label}.gpuCount 在需要或可选 GPU 时必须大于 0`);
20
+ }
21
+ if (value.minVramGb !== undefined && value.minVramGb !== null && !isFinitePositive(value.minVramGb)) {
22
+ issues.push(`${label}.minVramGb 必须是大于 0 的有限数值或 null`);
23
+ }
24
+ if (typeof value.cpuFallbackAllowed !== "boolean") {
25
+ issues.push(`${label}.cpuFallbackAllowed 必须是布尔值`);
26
+ }
27
+ if (value.preemptionSafe !== undefined && typeof value.preemptionSafe !== "boolean") {
28
+ issues.push(`${label}.preemptionSafe 必须是布尔值`);
29
+ }
30
+ if (value.preferredGpuTypes !== undefined) {
31
+ if (!Array.isArray(value.preferredGpuTypes) || value.preferredGpuTypes.some((item) => typeof item !== "string" || !item.trim())) {
32
+ issues.push(`${label}.preferredGpuTypes 必须是非空字符串数组`);
33
+ }
34
+ }
35
+ if (value.estimatedDurationMinutes !== undefined) {
36
+ if (!isRecord(value.estimatedDurationMinutes)) {
37
+ issues.push(`${label}.estimatedDurationMinutes 必须是对象`);
38
+ } else {
39
+ for (const field of ["cpu", "gpu"]) {
40
+ if (value.estimatedDurationMinutes[field] !== undefined && value.estimatedDurationMinutes[field] !== null && !isFinitePositive(value.estimatedDurationMinutes[field])) {
41
+ issues.push(`${label}.estimatedDurationMinutes.${field} 必须是大于 0 的有限数值或 null`);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ if (typeof value.rationale !== "string" || !value.rationale.trim()) {
47
+ issues.push(`${label}.rationale 必须是非空字符串`);
48
+ }
49
+ if (value.workloads !== undefined) {
50
+ if (!Array.isArray(value.workloads) || !value.workloads.length) issues.push(`${label}.workloads 必须是非空数组`);
51
+ else {
52
+ const ids = new Set();
53
+ for (const item of value.workloads) {
54
+ if (!isRecord(item) || typeof item.id !== "string" || !item.id.trim()
55
+ || typeof item.description !== "string" || !item.description.trim()) {
56
+ issues.push(`${label}.workloads 每项必须有计算身份和说明`); continue;
57
+ }
58
+ if (ids.has(item.id)) issues.push(`${label}.workloads 计算身份重复:${item.id}`);
59
+ ids.add(item.id);
60
+ for (const accelerator of ["cpu", "gpu"]) {
61
+ const duration = item.estimatedDurationMinutes?.[accelerator];
62
+ if (duration !== undefined && duration !== null && !isFinitePositive(duration)) issues.push(`${label}.workloads 时间必须是正数或未知`);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ return issues;
68
+ }
69
+
70
+ export function resolveComputeRequirement(experiment) {
71
+ const declared = experiment?.compute;
72
+ if (declared) {
73
+ const issues = validateDeclaredComputeRequirement(declared);
74
+ if (issues.length) throw new TypeError(issues.join(";"));
75
+ }
76
+
77
+ const inferredAccelerator = experiment?.environment?.gpu === "all"
78
+ ? "required"
79
+ : experiment?.reproductionLevel === "official-checkpoint"
80
+ ? "optional"
81
+ : "none";
82
+ const accelerator = declared?.accelerator ?? inferredAccelerator;
83
+ const cpuFallbackAllowed = declared?.cpuFallbackAllowed ?? accelerator !== "required";
84
+ return {
85
+ schemaVersion: "0.1",
86
+ source: declared ? "research-declared" : "research-derived",
87
+ classification: classificationFor(accelerator),
88
+ accelerator,
89
+ cpuCores: declared?.cpuCores ?? experiment?.environment?.cpus ?? 4,
90
+ memoryGb: declared?.memoryGb ?? experiment?.environment?.memoryGb ?? 16,
91
+ gpuCount: declared?.gpuCount ?? (accelerator === "none" ? 0 : 1),
92
+ minVramGb: declared?.minVramGb ?? null,
93
+ preferredGpuTypes: [...(declared?.preferredGpuTypes ?? [])],
94
+ cpuFallbackAllowed,
95
+ preemptionSafe: declared?.preemptionSafe === true,
96
+ estimatedDurationMinutes: {
97
+ cpu: declared?.estimatedDurationMinutes?.cpu ?? null,
98
+ gpu: declared?.estimatedDurationMinutes?.gpu ?? null,
99
+ },
100
+ rationale: declared?.rationale ?? inferredRationale(experiment?.reproductionLevel, accelerator),
101
+ };
102
+ }
103
+
104
+ function classificationFor(accelerator) {
105
+ if (accelerator === "required") return "gpu-required";
106
+ if (accelerator === "optional") return "gpu-optional";
107
+ return "cpu-only";
108
+ }
109
+
110
+ function inferredRationale(reproductionLevel, accelerator) {
111
+ if (accelerator === "required") return "The fixed Research Plan requires a GPU execution environment.";
112
+ if (accelerator === "optional") return "Official checkpoint evaluation can usually run on CPU, but a GPU may shorten runtime.";
113
+ return reproductionLevel === "full-training"
114
+ ? "Full training does not by itself imply a GPU dependency; without an explicit GPU requirement, the experiment is scheduled as a CPU task."
115
+ : "This experiment declares no GPU dependency and is scheduled as a CPU task.";
116
+ }
117
+
118
+ function isFinitePositive(value) {
119
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
120
+ }
121
+
122
+ function isFiniteNonNegative(value) {
123
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
124
+ }
@@ -0,0 +1,48 @@
1
+ import { CiteArkError } from "../util.mjs";
2
+ import { createCpuResearchPreparationWorkload } from "../workloads/cpu-research-preparation.mjs";
3
+ import { createPhaseAwareReproductionWorkload } from "../workloads/phase-aware-reproduction.mjs";
4
+ import { executeGcpBatchReproduction } from "./gcp-batch-executor.mjs";
5
+
6
+ /** Machine transitions, not prescribed scientific steps. Deterministic run IDs
7
+ * reuse saved phase receipts on coordinator recovery instead of paying twice.
8
+ */
9
+ export async function executeResearchPhases(options, execute = executeGcpBatchReproduction) {
10
+ const gcp = options.executorOptions?.gcpBatch ?? {};
11
+ const cpuDecision = gcp.cpuResearchComputeDecision;
12
+ if (!cpuDecision || options.workloadAdapter || options.computeDecision?.environment?.gpu !== "all") return execute(options);
13
+ // Every phase keeps the same cumulative paper budget and cancellation path.
14
+ // A phase count is not evidence that a scientific task has stopped progressing.
15
+ let checkpoint = null;
16
+ for (let cycle = 0; ; cycle++) {
17
+ const execution = await execute({ ...options,
18
+ runId: cycle ? `${options.runId}-gpu-${cycle}` : options.runId,
19
+ workloadAdapter: createPhaseAwareReproductionWorkload(),
20
+ executorOptions: { ...options.executorOptions, gcpBatch: { ...gcp,
21
+ ...(checkpoint ? { restoreCheckpointPrefix: checkpoint.checkpointPrefix,
22
+ restoreCheckpointBucket: checkpoint.bucket, reuseCompletedResultOnRestore: false } : {}),
23
+ } },
24
+ });
25
+ if (execution.dryRun || !execution.phaseHandoff) return execution;
26
+ checkpoint = execution.checkpoint;
27
+ options.progress?.("GPU 已结束本阶段,使用完整检查点回到 CPU 继续准备;预算与历史结果保持累计。");
28
+ const preparation = await execute({ ...options,
29
+ runId: `${options.runId}-cpu-${cycle + 1}`,
30
+ computeDecision: cpuDecision,
31
+ workloadAdapter: createCpuResearchPreparationWorkload({ runtimeBudgetMinutes: options.computeDecision.environment.timeoutMinutes }),
32
+ executorOptions: { ...options.executorOptions, gcpBatch: { ...gcp,
33
+ clients: gcp.assetPreparationClients ?? gcp.clients,
34
+ restoreCheckpointPrefix: checkpoint.checkpointPrefix,
35
+ restoreCheckpointBucket: checkpoint.bucket,
36
+ reuseCompletedResultOnRestore: false, keepStaging: true,
37
+ } },
38
+ });
39
+ if (preparation.preparation?.status !== "ready") {
40
+ const error = new CiteArkError(`CPU 继续准备未找到可执行路线:${preparation.preparation?.summary ?? "缺少交接结果"}`, {
41
+ failureCode: "research_preparation.no_viable_execution", retryable: false,
42
+ });
43
+ error.recoveryCheckpoint = preparation.checkpoint;
44
+ throw error;
45
+ }
46
+ checkpoint = preparation.checkpoint;
47
+ }
48
+ }