@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,853 @@
1
+ import { saveRunRecovery, restoreRunRecovery } from '../compute/coordinator-recovery.mjs';
2
+ import { stageReferenceMaterials } from "../research/reference-materials.mjs";
3
+ import { mergeInventoryPreparation } from "./inventory-handoff.mjs";
4
+ import { continuationRepairTargets } from "./continuation-selection.mjs";
5
+ import { preparePaperMarkdown } from "./paper-markdown.mjs";
6
+ import { createHash } from "node:crypto";
7
+ import { cp, mkdir, writeFile } from "node:fs/promises";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ import { isDeepStrictEqual } from "node:util";
11
+
12
+ import {
13
+ captureFinalWorkspace,
14
+ copyPaper,
15
+ prepareRepository,
16
+ snapshotDigest,
17
+ snapshotDirectory,
18
+ updateRunState,
19
+ } from "../job.mjs";
20
+ import { previewEgressProxy, startEgressProxy } from "../network/egress-proxy.mjs";
21
+ import { previewProviderSession, startProviderSession } from "../provider/relay.mjs";
22
+ import { resolveAgentRuntime, publicAgentRuntime } from "../runtime/config.mjs";
23
+ import { buildAgentInvocation } from "../runtime/index.mjs";
24
+ import { assertLocalRunStopped, buildDockerRun, inspectDockerImage, requireRuntimeCredentials, runDockerInvocation, writeInvocation } from "../sandbox/docker.mjs";
25
+ import { readAgentUsage } from "../telemetry/usage.mjs";
26
+ import { CiteArkError, createRunId, createSessionId, isRecord, pathExists, readJson, sha256File, sha256Value, writeJson } from "../util.mjs";
27
+ import { listEvidenceParsers } from "../evidence/registry.mjs";
28
+ import { assetSourceGroundingIssues } from "../assets/source-grounding.mjs";
29
+ import { RECONSTRUCTION_REPOSITORY } from "../repository-origin.mjs";
30
+ import {
31
+ blockingCompilerReconstructionIssues,
32
+ compilerReconstructionRetryIssues,
33
+ normalizeCompilerResearchDraft,
34
+ normalizeResearchStructure,
35
+ } from "./structure.mjs";
36
+ import { reviewCompilerCoverage } from "./compiler-coverage.mjs";
37
+ import { reviewCompilerSources } from "./source-review.mjs";
38
+ import { scientificPlanningPolicy } from "./planning-policy.mjs";
39
+ import { sharedWorkloads, workloadMinutes } from "../compute/shared-workloads.mjs";
40
+ import { researchCompilationFailure } from "./compiler-failure.mjs";
41
+
42
+ const SOURCE_ROOT = path.dirname(fileURLToPath(import.meta.url));
43
+ const PACKAGE_ROOT = path.resolve(SOURCE_ROOT, "../..");
44
+
45
+ export async function runResearchCompiler({ taskPath, runsDirectory, runId = createRunId(), dryRun = false, imageOverride, agentRuntime, sourceReviewer = reviewCompilerSources, validateResearch = async () => [] }) {
46
+ const absoluteTaskPath = path.resolve(taskPath);
47
+ const taskDirectory = path.dirname(absoluteTaskPath);
48
+ const task = normalizeCompilationTask(await readJson(absoluteTaskPath));
49
+ const runtimeAgent = resolveAgentRuntime({
50
+ taskAgent: task.agent,
51
+ ...agentRuntime,
52
+ requireApi: !dryRun,
53
+ });
54
+ if (imageOverride) task.environment.image = imageOverride;
55
+ const bundle = await restoreRunRecovery({ runsDirectory, runId }) ?? await prepareCompilationBundle({ task, taskDirectory, runsDirectory, runId });
56
+ if (sha256Value(bundle.task) !== sha256Value(task)) throw Error('The saved research input changed. Start a new workspace.');
57
+ bundle.runtimeAgent = runtimeAgent;
58
+ await updateRunState(bundle, {
59
+ runtime: runtimeAgent.runtime,
60
+ agent: publicAgentRuntime(runtimeAgent),
61
+ });
62
+ const invocations = [];
63
+ const promptsDirectory = path.join(bundle.directories.execution, "prompts");
64
+ await mkdir(promptsDirectory, { recursive: true });
65
+ let validationIssues = ["research.json 尚未生成"];
66
+ let blockingFailure = null;
67
+ let normalizedResearch = null;
68
+ let normalizationWarnings = [];
69
+ const maxAttempts = 1 + runtimeAgent.validationRetries;
70
+ const firstAttempt = bundle.restored && bundle.state.status !== "prepared" ? Math.max(1, ...bundle.attempts.map(a => a.attempt)) + 1 : 1;
71
+ if (!dryRun) { await assertLocalRunStopped(bundle); requireRuntimeCredentials(runtimeAgent); }
72
+ const providerSession = dryRun
73
+ ? previewProviderSession(runtimeAgent)
74
+ : await startProviderSession(runtimeAgent, {
75
+ correlation: { runId: bundle.runId, workload: "research_compilation" },
76
+ });
77
+ let egressSession = previewEgressProxy();
78
+
79
+ try {
80
+ if (!dryRun) egressSession = await startEgressProxy();
81
+ const instructions = await readFileText(
82
+ path.join(bundle.directories.input, "agent-instructions.md"),
83
+ );
84
+ const retainedDraft = path.join(bundle.directories.output, 'research.json');
85
+ if (bundle.restored && !dryRun && await pathExists(retainedDraft)) {
86
+ try {
87
+ const accepted = await acceptCompilerDraft({ bundle, draft: await readJson(retainedDraft), validateResearch, sourceReviewer });
88
+ normalizedResearch = accepted.research; normalizationWarnings = accepted.normalizationWarnings; validationIssues = [];
89
+ } catch (error) {
90
+ if (error.failureCode === 'compiler.source_review_unavailable') throw error;
91
+ validationIssues = [error.message];
92
+ }
93
+ }
94
+ for (let attempt = firstAttempt; !normalizedResearch && attempt < firstAttempt + maxAttempts; attempt += 1) {
95
+ const runtime = await buildAgentInvocation({
96
+ bundle,
97
+ attempt,
98
+ prompt: compilerPrompt(attempt, validationIssues, {
99
+ continuation: bundle.runtimeTask?.continuation,
100
+ repairBaseline: bundle.repairBaselineResearch,
101
+ }),
102
+ instructions,
103
+ providerSession,
104
+ });
105
+ const docker = {
106
+ ...buildDockerRun({
107
+ bundle,
108
+ runtimeCommand: runtime.command,
109
+ runtimeArgs: runtime.args,
110
+ attempt,
111
+ providerSession,
112
+ egressSession,
113
+ }),
114
+ attempt,
115
+ };
116
+ invocations.push(docker);
117
+ await writeInvocation(bundle, invocations);
118
+ await writeFile(path.join(promptsDirectory, `attempt-${attempt}.txt`), `${runtime.prompt}\n`, "utf8");
119
+ if (dryRun) {
120
+ await updateRunState(bundle, { status: "prepared" });
121
+ return { bundle, invocation: docker, dryRun: true };
122
+ }
123
+ if (attempt === firstAttempt) {
124
+ await updateRunState(bundle, { status: "running", startedAt: new Date().toISOString() });
125
+ }
126
+ const processResult = await runDockerInvocation(docker, {
127
+ bundle,
128
+ attempt,
129
+ timeoutMs: task.environment.timeoutMinutes * 60_000,
130
+ });
131
+ bundle.attempts.push({
132
+ attempt,
133
+ startedAt: bundle.state.startedAt,
134
+ finishedAt: new Date().toISOString(),
135
+ exitCode: processResult.code,
136
+ signal: processResult.signal,
137
+ timedOut: processResult.timedOut,
138
+ });
139
+ const researchPath = path.join(bundle.directories.output, "research.json");
140
+ try {
141
+ if (processResult.code !== 0 || processResult.timedOut) throw new CiteArkError("Compiler process did not finish successfully; retained JSON is a draft");
142
+ if (!(await pathExists(researchPath))) throw new CiteArkError("缺少 /job/output/research.json");
143
+ const accepted = await acceptCompilerDraft({ bundle, draft: await readJson(researchPath), validateResearch, sourceReviewer });
144
+ normalizationWarnings = accepted.normalizationWarnings;
145
+ normalizedResearch = accepted.research;
146
+ validationIssues = [];
147
+ break;
148
+ } catch (error) {
149
+ validationIssues = [error.message];
150
+ if (error.failureCode === "compiler.source_review_unavailable") { blockingFailure = { code: error.failureCode, retryable: false }; break; }
151
+ }
152
+ }
153
+
154
+ bundle.sandboxImageIdentity = await inspectDockerImage(task.environment.image);
155
+ await captureFinalWorkspace(bundle);
156
+ const usage = await readAgentUsage(path.join(bundle.directories.execution, "trace.jsonl"), {
157
+ runtimeHome: bundle.directories.runtimeHome,
158
+ model: bundle.runtimeAgent.model,
159
+ });
160
+ const status = validationIssues.length ? "invalid_result" : "completed";
161
+ await updateRunState(bundle, {
162
+ status,
163
+ finishedAt: new Date().toISOString(),
164
+ attempts: bundle.attempts,
165
+ validationIssues,
166
+ normalizationWarnings,
167
+ output: normalizedResearch ? "output/research.normalized.json" : null,
168
+ sandboxImageIdentity: bundle.sandboxImageIdentity,
169
+ usage,
170
+ });
171
+ if (validationIssues.length) throw researchCompilationFailure(validationIssues, blockingFailure);
172
+ return {
173
+ bundle,
174
+ research: normalizedResearch,
175
+ normalizationWarnings,
176
+ dryRun: false,
177
+ };
178
+ } finally {
179
+ await saveRunRecovery(bundle, async () => {});
180
+ await Promise.all([providerSession.close(), egressSession.close()]);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Inventory acceptance checks source coverage and structure only. Reproduction
186
+ * preparation applies scheduling and complete execution dispositions later;
187
+ * runtime still owns concrete commands, assets and evidence. Legacy fixed
188
+ * entrypoints retain their operational gate.
189
+ */
190
+ export async function reviewCompilerCandidate({
191
+ candidate,
192
+ draftContext,
193
+ reconstructionIssues = [],
194
+ structureIssues = [],
195
+ validateResearch = async () => [],
196
+ datasetRegistry,
197
+ previousCoverageInventory = [],
198
+ }) {
199
+ const coverage = reviewCompilerCoverage(candidate, draftContext, previousCoverageInventory);
200
+ const experiments = Array.isArray(candidate?.experiments) ? candidate.experiments : [];
201
+ const agentPlanned = experiments.every((experiment) =>
202
+ experiment?.protocol?.executionMode === "agent_planned",
203
+ );
204
+ const reconstructionBlocking = blockingCompilerReconstructionIssues(
205
+ reconstructionIssues,
206
+ { agentPlanned },
207
+ );
208
+ const blockingSet = new Set(reconstructionBlocking);
209
+ const schedulingIssues = candidate.compilationStage === "inventory" ? [] : await validateResearch(candidate);
210
+ const assetGroundingIssues = candidate.compilationStage === "inventory" ? [] : await assetSourceGroundingIssues(candidate, {
211
+ ...draftContext,
212
+ datasetRegistry,
213
+ });
214
+ const sharedWorkIssues = [];
215
+ if (draftContext?.compilationStage === "execution_plan") {
216
+ // New plans must expose work identity and a usable quote. A bounded repair
217
+ // cannot rewrite unrelated historical experiment objects.
218
+ const repairTargets = continuationRepairTargets(draftContext.continuation);
219
+ const required = draftContext.sharedWorkloadsRequired === false ? [] : draftContext.repairBaselineResearch
220
+ ? experiments.filter(e => repairTargets.some(t => t.experimentId === e.id)) : experiments;
221
+ for (const experiment of required) {
222
+ const workloads = experiment.compute?.workloads;
223
+ if (!workloads?.length) {
224
+ sharedWorkIssues.push(`Shared-work planning retry: ${experiment.id} must declare compute.workloads, including reusable training/setup and separate incremental evaluations. Reuse identical IDs across consumers and give every item a finite positive duration estimate. Do not change scientific scope.`);
225
+ continue;
226
+ }
227
+ const compute = experiment.compute ?? {};
228
+ const accelerators = compute.accelerator === "none" ? ["cpu"]
229
+ : compute.accelerator === "required" || compute.cpuFallbackAllowed === false
230
+ ? ["gpu"] : ["cpu", "gpu"];
231
+ for (const workload of workloads) for (const accelerator of accelerators) {
232
+ const minutes = workloadMinutes(workload, accelerator);
233
+ if (typeof minutes !== "number" || !Number.isFinite(minutes) || minutes <= 0) {
234
+ sharedWorkIssues.push(`Shared-work estimate retry: ${experiment.id}/${workload?.id ?? "unnamed-workload"} must provide a finite positive ${accelerator} duration. Use an explicit conservative rate assumption when no benchmark exists; null or omission is not a quote.`);
235
+ }
236
+ }
237
+ }
238
+ try { sharedWorkloads(experiments); } catch (error) { sharedWorkIssues.push(error.message); }
239
+ }
240
+ return {
241
+ agentPlanned,
242
+ coverage,
243
+ blockingIssues: [
244
+ ...reconstructionBlocking,
245
+ ...structureIssues.filter((issue) => !blockingSet.has(issue)),
246
+ ...schedulingIssues,
247
+ ...sharedWorkIssues,
248
+ ...(agentPlanned ? [] : assetGroundingIssues),
249
+ ],
250
+ advisoryIssues: [
251
+ ...coverage.issues.map((issue) =>
252
+ `Initial catalog coverage remains partial: ${issue} Preserve the visible gap for user review; this warning does not establish scientific support or authorize extra resources.`),
253
+ ...reconstructionIssues.filter((issue) => !blockingSet.has(issue)
254
+ && !/^(?:Scientific coverage|Direct-evidence) retry:/.test(issue)),
255
+ ...(agentPlanned ? assetGroundingIssues : []),
256
+ ],
257
+ };
258
+ }
259
+
260
+ /** All transports and completed-output recovery use this acceptance boundary.
261
+ * Executors own transport/retries only; none may downgrade scientific scope.
262
+ */
263
+ export async function acceptCompilerDraft({ bundle, draft, validateResearch = async () => [], sourceReviewer = reviewCompilerSources }) {
264
+ const draftContext = compilerDraftContext(bundle);
265
+ const preparedDraft = bundle.sourceInventory ? mergeInventoryPreparation(bundle.sourceInventory, draft) : draft;
266
+ const normalized = normalizeCompilerResearchDraft(preparedDraft, draftContext, { collectIssues: true });
267
+ const candidateResearch = applyCompilationLicensePolicy(
268
+ pinResearchRepository(normalized.research, bundle.repositoryIdentity), bundle.task.licensePolicy,
269
+ );
270
+ if (candidateResearch.compilationStage === "inventory") candidateResearch.provenance.sourceIdentity = {
271
+ paperDigest: bundle.runtimeTask?.paper?.digest,
272
+ repository: structuredClone(bundle.repositoryIdentity),
273
+ };
274
+ const repair = applyContinuationRepairBoundary(candidateResearch, draftContext);
275
+ const candidate = repair.research;
276
+ const coveragePath = path.join(bundle.directories.execution, "compiler-coverage.json");
277
+ const previousCoverage = await pathExists(coveragePath) ? await readJson(coveragePath) : null;
278
+ const review = await reviewCompilerCandidate({
279
+ candidate, draftContext,
280
+ reconstructionIssues: [...repair.issues, ...compilerReconstructionRetryIssues(candidate, draftContext)],
281
+ structureIssues: normalized.structureIssues,
282
+ previousCoverageInventory: previousCoverage?.inventory ?? [],
283
+ validateResearch,
284
+ datasetRegistry: await readJson(path.join(bundle.directories.input, "dataset-source-registry.json")),
285
+ });
286
+ await writeJson(coveragePath, review.coverage);
287
+ if (review.blockingIssues.length) throw new CiteArkError(review.blockingIssues.join("\n"));
288
+ const normalizationWarnings = attachCompilerAdvisories(candidate, normalized.warnings, review.advisoryIssues);
289
+ if (candidate.provenance) delete candidate.provenance.compilerSourceReview;
290
+ if (bundle.sourceInventory) {
291
+ candidate.provenance.reproductionPreparation = { sourceInventoryDigest: candidate.sourceInventory.digest,
292
+ status: "prepared", sourceContent: candidate.sourceInventory.revisions?.length ? "revised_with_sources" : "preserved", scientificSupport: "not_assessed" };
293
+ } else if (candidate.compilationStage === "inventory") {
294
+ candidate.provenance.compilerSourceReview = {
295
+ accepted: false, status: "deferred", mode: "agentic_reading",
296
+ reason: "Initial reading is a visible, revisable catalog rather than a release gate. Reproduction may reread and correct it; independent assessment judges source fidelity and observed evidence.",
297
+ };
298
+ } else {
299
+ const sourceReview = await sourceReviewer({ bundle, candidate, paperPath: draftContext.paperPath }).catch((error) => {
300
+ // Provider failures cannot be repaired by regenerating the scientific plan.
301
+ // Use the existing terminal review boundary in every compiler transport.
302
+ if (error.failureCategory === "provider") throw new CiteArkError(error.message, {
303
+ cause: error, failureCode: "compiler.source_review_unavailable", retryable: false,
304
+ failureCategory: "provider", recoveryAction: error.recoveryAction,
305
+ });
306
+ throw error;
307
+ });
308
+ if (sourceReview?.accepted !== true) throw new CiteArkError("Independent source review did not accept candidate", { failureCode: "compiler.source_review_unavailable", retryable: false });
309
+ candidate.provenance = { ...(candidate.provenance ?? {}), compilerSourceReview: sourceReview };
310
+ }
311
+ await writeJson(path.join(bundle.directories.output, "research.normalized.json"), candidate);
312
+ return { research: candidate, normalizationWarnings, coverage: review.coverage };
313
+ }
314
+
315
+ export function attachCompilerAdvisories(research, existingWarnings, advisoryIssues) {
316
+ const warnings = [...new Set([
317
+ ...(Array.isArray(existingWarnings) ? existingWarnings : []),
318
+ ...(Array.isArray(advisoryIssues) ? advisoryIssues : [])
319
+ .filter((issue) => typeof issue === "string" && issue.trim())
320
+ .map((issue) => `Execution-agent advisory: ${issue.trim()}`),
321
+ ])];
322
+ const bounded = warnings.length > 100
323
+ ? [
324
+ ...warnings.slice(0, 99),
325
+ `${warnings.length - 99} additional compiler advisories were omitted from this record.`,
326
+ ]
327
+ : warnings;
328
+ research.provenance = {
329
+ ...(research.provenance ?? {}),
330
+ ...(bounded.length ? { normalizationWarnings: bounded } : {}),
331
+ };
332
+ return bounded;
333
+ }
334
+
335
+ export function compilerDraftContext(bundle) {
336
+ const paper = bundle.runtimeTask?.paper ?? {};
337
+ return {
338
+ taskName: bundle.task?.name,
339
+ compilationStage: bundle.task?.compilationStage,
340
+ // Completed/ongoing legacy checkpoints must remain recoverable without
341
+ // paying to recompile their already-fixed scientific plan.
342
+ sharedWorkloadsRequired: !bundle.restored,
343
+ reproductionScope: bundle.runtimeTask?.reproductionScope ?? bundle.task?.reproductionScope,
344
+ computeContext: bundle.runtimeTask?.computeContext ?? bundle.task?.computeContext,
345
+ paperTitle: paper.title,
346
+ paperUri: paper.sourceUrl ?? paper.resolvedUrl,
347
+ paperDigest: paper.digest,
348
+ paperPath: compilerPaperPath(bundle, paper.path),
349
+ repositoryRoot: bundle.directories.repository,
350
+ repositoryIdentity: bundle.repositoryIdentity,
351
+ repositoryFiles: (bundle.beforeSnapshot?.entries ?? [])
352
+ .filter((entry) => entry?.type === "file" && typeof entry.path === "string")
353
+ .map((entry) => entry.path),
354
+ licensePolicy: bundle.task?.licensePolicy,
355
+ continuation: bundle.runtimeTask?.continuation ?? null,
356
+ repairBaselineResearch: bundle.repairBaselineResearch ?? null,
357
+ };
358
+ }
359
+
360
+ function compilerPaperPath(bundle, runtimePath) {
361
+ const prefix = "/job/input/";
362
+ if (typeof runtimePath !== "string" || !runtimePath.startsWith(prefix)) return undefined;
363
+ const relative = runtimePath.slice(prefix.length);
364
+ if (!relative || relative.split("/").includes("..")) return undefined;
365
+ return path.join(bundle.directories.input, ...relative.split("/"));
366
+ }
367
+
368
+ export function normalizeCompilationTask(input) {
369
+ const issues = [];
370
+ if (!isRecord(input)) throw new CiteArkError("编译任务必须是对象");
371
+ if (input.schemaVersion !== "0.1") issues.push("schemaVersion 必须是 0.1");
372
+ required(input.name, "name", issues);
373
+ validateSource(input.paper, "paper", issues);
374
+ if (input.repository !== undefined) {
375
+ validateSource(input.repository, "repository", issues);
376
+ }
377
+ if (input.repairBaseline !== undefined) {
378
+ if (!isRecord(input.repairBaseline)) {
379
+ issues.push("repairBaseline 必须是对象");
380
+ } else if (typeof input.repairBaseline.path !== "string" || !input.repairBaseline.path.trim()) {
381
+ issues.push("repairBaseline.path 必须是非空字符串");
382
+ }
383
+ }
384
+ if (
385
+ input.agent?.runtime != null &&
386
+ !new Set(["claude-code", "codex", "opencode"]).has(input.agent.runtime)
387
+ ) {
388
+ issues.push("agent.runtime 无效");
389
+ }
390
+ if (
391
+ input.agent?.reasoningMode !== undefined &&
392
+ !new Set(["standard", "pro"]).has(input.agent.reasoningMode)
393
+ ) {
394
+ issues.push("agent.reasoningMode 无效");
395
+ }
396
+ if (input.agent?.reasoningMode === "pro" && input.agent?.runtime === "claude-code") {
397
+ issues.push("agent.reasoningMode=pro 仅支持 Codex 和 OpenCode");
398
+ }
399
+ if (input.reproductionScope !== undefined && !["low", "medium", "high"].includes(input.reproductionScope)) issues.push("reproductionScope 无效");
400
+ if (input.compilationStage !== undefined && !["inventory", "execution_plan"].includes(input.compilationStage)) issues.push("compilationStage 无效");
401
+ if (input.sourceReviewMode !== undefined && !["deferred", "required"].includes(input.sourceReviewMode)) issues.push("sourceReviewMode 无效");
402
+ if (input.researchGoal !== undefined && (typeof input.researchGoal !== 'string' || input.researchGoal.length > 32768)) issues.push('researchGoal 必须是最多 32768 字符的文本');
403
+ if (input.reproductionGuide !== undefined && (typeof input.reproductionGuide !== "string" || input.reproductionGuide.length > 262_144)) issues.push("reproductionGuide 必须是最多 262144 字符的文本");
404
+ if (issues.length) throw new CiteArkError(`研究编译任务校验失败:\n- ${issues.join("\n- ")}`);
405
+ return {
406
+ ...input,
407
+ compilationStage: input.compilationStage ?? (input.repairBaseline ? "execution_plan" : "inventory"),
408
+ agent: {
409
+ runtime: input.agent?.runtime ?? null,
410
+ model: input.agent?.model ?? null,
411
+ subagentModel: input.agent?.subagentModel ?? null,
412
+ autoCompactWindow: input.agent?.autoCompactWindow ?? 786432,
413
+ effort: input.agent?.effort ?? "high",
414
+ reasoningMode: input.agent?.reasoningMode ?? "standard",
415
+ maxTurns: input.agent?.maxTurns ?? 100,
416
+ maxBudgetUsd: input.agent?.maxBudgetUsd ?? 5,
417
+ validationRetries: input.agent?.validationRetries ?? 1,
418
+ capacityRetryDelaySeconds: input.agent?.capacityRetryDelaySeconds ?? 10,
419
+ capacityMaxRetries: input.agent?.capacityMaxRetries ?? 30,
420
+ },
421
+ environment: {
422
+ image: input.environment?.image ?? "citeark-agent/runtime:0.2.0",
423
+ timeoutMinutes: input.environment?.timeoutMinutes ?? 45,
424
+ cpus: input.environment?.cpus ?? 4,
425
+ memoryGb: input.environment?.memoryGb ?? 8,
426
+ shmGb: input.environment?.shmGb ?? 1,
427
+ gpu: input.environment?.gpu ?? "none",
428
+ networkAccess: true,
429
+ },
430
+ };
431
+ }
432
+
433
+ export async function prepareCompilationBundle({ task, taskDirectory, runsDirectory, runId, prepareMarkdown = preparePaperMarkdown }) {
434
+ const runDirectory = path.resolve(runsDirectory, runId);
435
+ if (await pathExists(runDirectory)) throw new CiteArkError(`运行目录已经存在:${runDirectory}`);
436
+ const directories = {
437
+ run: runDirectory,
438
+ input: path.join(runDirectory, "input"),
439
+ workspace: path.join(runDirectory, "workspace"),
440
+ repository: path.join(runDirectory, "workspace", "repository"),
441
+ repositorySnapshot: path.join(runDirectory, "snapshot", "repository"),
442
+ output: path.join(runDirectory, "output"),
443
+ runtimeHome: path.join(runDirectory, "runtime-home"),
444
+ execution: path.join(runDirectory, "execution"),
445
+ artifact: path.join(runDirectory, "artifact"),
446
+ };
447
+ await Promise.all(
448
+ Object.values(directories)
449
+ .filter(
450
+ (item) =>
451
+ item !== directories.run &&
452
+ item !== directories.repository &&
453
+ item !== directories.repositorySnapshot,
454
+ )
455
+ .map((item) => mkdir(item, { recursive: true })),
456
+ );
457
+ await stageReferenceMaterials(task.referenceMaterialsPath, directories.input);
458
+ const sourceInventory = task.sourceInventory?.path
459
+ ? normalizeResearchStructure(await readJson(path.resolve(taskDirectory, task.sourceInventory.path))) : null;
460
+ if (sourceInventory && (task.compilationStage !== "execution_plan" || sourceInventory.compilationStage !== "inventory"
461
+ || task.sourceInventory.digest !== `sha256:${sha256Value(sourceInventory)}`))
462
+ throw new CiteArkError("Reproduction preparation requires the original inventory digest");
463
+ const runtimeTask = structuredClone(task);
464
+ if (task.referenceMaterialsPath) runtimeTask.referenceMaterialsPath = "/job/input/reference-materials.json";
465
+ if (sourceInventory) {
466
+ await writeJson(path.join(directories.input, "source-inventory.json"), sourceInventory);
467
+ runtimeTask.sourceInventory = { ...task.sourceInventory, path: "/job/input/source-inventory.json" };
468
+ }
469
+ await writeJson(path.join(directories.input, "submitted-compile-task.json"), task);
470
+ const repairBaselineDraft = task.repairBaseline?.path
471
+ ? normalizeResearchStructure(
472
+ await readJson(path.resolve(taskDirectory, task.repairBaseline.path)),
473
+ )
474
+ : null;
475
+ await copyPaper(runtimeTask, taskDirectory, directories.input);
476
+ await snapshotRemotePaper(runtimeTask, directories.input);
477
+ const fixedPaperPath = compilerPaperPath({ directories }, runtimeTask.paper?.path);
478
+ if (fixedPaperPath) {
479
+ const paperDigest = `sha256:${await sha256File(fixedPaperPath)}`;
480
+ if (runtimeTask.paper.digest && runtimeTask.paper.digest !== paperDigest) throw new CiteArkError("Fixed paper digest changed before preparation");
481
+ if (sourceInventory && sourceInventory.provenance.sourceIdentity?.paperDigest !== paperDigest) throw new CiteArkError("Reproduction paper differs from the reviewed inventory");
482
+ runtimeTask.paper.digest = paperDigest;
483
+ const stagedReading = { ...task.paper.preparedReading };
484
+ delete stagedReading.directory;
485
+ const reading = runtimeTask.paper.markdownPath && stagedReading.status === "ready" ? stagedReading : await prepareMarkdown({ paperPath: fixedPaperPath,
486
+ outputDirectory: path.join(directories.input, "paper-reading"),
487
+ cacheDirectory: process.env.CITEARK_PAPER_MARKDOWN_CACHE ?? path.join(path.resolve(runsDirectory), ".paper-markdown-cache") });
488
+ runtimeTask.paper.reading = reading;
489
+ if (reading.status === "ready") {
490
+ runtimeTask.paper.markdownPath = "/job/input/paper-reading/paper.md";
491
+ runtimeTask.paper.sourceMapPath = "/job/input/paper-reading/source-map.json";
492
+ }
493
+ }
494
+ const repositoryProvenance = task.repository
495
+ ? await prepareRepository(runtimeTask, taskDirectory, directories.repository)
496
+ : await prepareMissingRepository(directories.repository);
497
+ const beforeSnapshot = await snapshotDirectory(directories.repository);
498
+ const repositorySnapshotDigest = snapshotDigest(beforeSnapshot);
499
+ let repositoryIdentity = task.repository
500
+ ? {
501
+ available: true,
502
+ url:
503
+ repositoryProvenance.sourceType === "url"
504
+ ? task.repository.url
505
+ : `citeark://snapshot/sha256/${repositorySnapshotDigest.slice("sha256:".length)}`,
506
+ commit:
507
+ repositoryProvenance.sourceType === "url"
508
+ ? repositoryProvenance.commit
509
+ : repositorySnapshotDigest,
510
+ snapshotDigest: repositorySnapshotDigest,
511
+ }
512
+ : {
513
+ available: false,
514
+ reason: "The fixed paper did not declare a unique verified code repository.",
515
+ };
516
+ if (sourceInventory) {
517
+ const originalIdentity = sourceInventory.provenance.sourceIdentity?.repository;
518
+ if (!originalIdentity || originalIdentity.available !== repositoryIdentity.available
519
+ || (originalIdentity.available && originalIdentity.snapshotDigest !== repositoryIdentity.snapshotDigest))
520
+ throw new CiteArkError("Reproduction repository differs from the reviewed inventory");
521
+ repositoryIdentity = structuredClone(originalIdentity);
522
+ }
523
+ if (task.repository && !repositoryIdentity.commit) {
524
+ throw new CiteArkError("Research Compiler 无法固定仓库版本");
525
+ }
526
+ const repairBaselineResearch = repairBaselineDraft
527
+ ? applyCompilationLicensePolicy(
528
+ pinResearchRepository(repairBaselineDraft, repositoryIdentity),
529
+ task.licensePolicy,
530
+ )
531
+ : null;
532
+ await mkdir(path.dirname(directories.repositorySnapshot), { recursive: true });
533
+ await cp(directories.repository, directories.repositorySnapshot, {
534
+ recursive: true,
535
+ force: true,
536
+ verbatimSymlinks: true,
537
+ });
538
+ runtimeTask.run = { id: runId, outputPath: "/job/output", repositoryPath: "/job/workspace/repository" };
539
+ runtimeTask.repository = task.repository
540
+ ? { path: "/job/workspace/repository", commit: repositoryProvenance.commit }
541
+ : {
542
+ available: false,
543
+ path: "/job/workspace/repository",
544
+ reason: repositoryIdentity.reason,
545
+ };
546
+ if (repairBaselineResearch) {
547
+ await writeJson(
548
+ path.join(directories.input, "repair-baseline.json"),
549
+ repairBaselineResearch,
550
+ );
551
+ await writeJson(
552
+ path.join(directories.output, "research.json"),
553
+ repairBaselineResearch,
554
+ );
555
+ runtimeTask.repairBaseline = {
556
+ ...task.repairBaseline,
557
+ path: "/job/input/repair-baseline.json",
558
+ };
559
+ }
560
+ runtimeTask.scientificPlanningPolicy = scientificPlanningPolicy(runtimeTask.reproductionScope, task.compilationStage);
561
+ await writeJson(path.join(directories.input, "compile-task.json"), runtimeTask);
562
+ await writeJson(
563
+ path.join(directories.input, "repository-identity.json"),
564
+ repositoryIdentity,
565
+ );
566
+ await writeJson(path.join(directories.input, "parser-catalog.json"), listEvidenceParsers());
567
+ if (sourceInventory) {
568
+ const schema = await readJson(path.join(PACKAGE_ROOT, "schemas/research.schema.json"));
569
+ await writeJson(path.join(directories.input, "research.schema.json"), {
570
+ $schema: schema.$schema,
571
+ title: "Reproduction preparation draft from an accepted inventory",
572
+ type: "object", additionalProperties: false, required: ["experiments", "dispositions"],
573
+ properties: {
574
+ inventoryRevisions: { type: "array", items: { type: "object", additionalProperties: false,
575
+ required: ["claimId", "claim", "reason", "sources"], properties: {
576
+ claimId: { type: "string", minLength: 1 }, claim: schema.properties.claims.items,
577
+ reason: { type: "string", minLength: 1 },
578
+ sources: { type: "array", minItems: 1, items: { type: "object", required: ["sourceId", "locator"],
579
+ properties: { sourceId: { type: "string" }, locator: { type: "string", minLength: 1 } } } },
580
+ } } },
581
+ researchObjects: schema.properties.researchObjects,
582
+ experiments: schema.properties.experiments,
583
+ experimentSelection: schema.properties.experimentSelection,
584
+ dispositions: { type: "array", items: { type: "object", additionalProperties: false,
585
+ required: ["claimId", "reproduction"], properties: {
586
+ claimId: { type: "string", minLength: 1 },
587
+ reproduction: schema.properties.claims.items.properties.reproduction,
588
+ } } },
589
+ },
590
+ $defs: schema.$defs,
591
+ });
592
+ } else {
593
+ await cp(path.join(PACKAGE_ROOT, "schemas", task.compilationStage === "inventory" ? "research-inventory-draft.schema.json" : "research.schema.json"), path.join(directories.input, "research.schema.json"));
594
+ }
595
+ await cp(
596
+ path.join(
597
+ PACKAGE_ROOT,
598
+ "prompts",
599
+ repairBaselineResearch ? "repair-research.md" : task.compilationStage === "execution_plan" ? "prepare-reproduction.md" : "compile-research.md",
600
+ ),
601
+ path.join(directories.input, "agent-instructions.md"),
602
+ );
603
+ await cp(
604
+ path.join(PACKAGE_ROOT, "data", "dataset-source-registry.v1.json"),
605
+ path.join(directories.input, "dataset-source-registry.json"),
606
+ );
607
+ await writeJson(path.join(directories.execution, "workspace-before.json"), beforeSnapshot);
608
+ const sessionId = createSessionId();
609
+ const state = {
610
+ schemaVersion: "0.1",
611
+ runId,
612
+ sessionId,
613
+ phase: "research_compilation",
614
+ status: "prepared",
615
+ createdAt: new Date().toISOString(),
616
+ updatedAt: new Date().toISOString(),
617
+ taskName: task.name,
618
+ runtime: task.agent.runtime,
619
+ sandbox: "docker",
620
+ image: task.environment.image,
621
+ repository: { ...repositoryProvenance, identity: repositoryIdentity },
622
+ };
623
+ await writeJson(path.join(directories.run, "run.json"), state);
624
+ const bundle = {
625
+ runId,
626
+ sessionId,
627
+ task,
628
+ runtimeTask,
629
+ directories,
630
+ repositoryProvenance,
631
+ repositoryIdentity,
632
+ repairBaselineResearch,
633
+ sourceInventory,
634
+ beforeSnapshot,
635
+ state,
636
+ attempts: [],
637
+ };
638
+ await saveRunRecovery(bundle, async () => {});
639
+ return bundle;
640
+ }
641
+
642
+ export function compilerPrompt(attempt, validationIssues, options = {}) {
643
+ const failedTargets = continuationRepairTargets(options.continuation);
644
+ if (options.repairBaseline && failedTargets.length) {
645
+ const targetList = failedTargets
646
+ .map((target) => `${target.claimId}/${target.experimentId}`)
647
+ .join(", ");
648
+ if (attempt === 1) {
649
+ return `Repair the staged Research Plan in /job/output/research.json for only these failed continuation targets: ${targetList}. The immutable baseline is also available at /job/input/repair-baseline.json. Read the requestedTargets re-execution reasons or failedTargets diagnoses in /job/input/compile-task.json, inspect the fixed repository, and make the smallest executable correction to those experiment objects. Claims, reported values, unselected completed targets, unrelated experiments, experiment ids, claim bindings, and reportedMeasurementId bindings are locked by the host and cannot be changed. Do not redesign the whole plan. Only write /job/output/research.json; do not create a new official helper file that is absent from the fixed repository snapshot.`;
650
+ }
651
+ return `The bounded continuation repair still failed authoritative validation:\n${validationIssues.map((issue) => `- ${issue}`).join("\n")}\nInspect the current /job/output/research.json and repair only these failed targets: ${targetList}. Preserve every part that is already valid. Claims, reported values, unselected completed targets, unrelated experiments, ids, claim bindings, and reportedMeasurementId bindings are host-locked. Make the minimum grounded change, parse the complete JSON, and stop after writing /job/output/research.json. Do not replace the plan, rename targets, remove measurements, or create an official helper file outside the fixed repository snapshot.`;
652
+ }
653
+ if (attempt === 1) return "Read /job/input/agent-instructions.md and /job/input/compile-task.json. Follow the task's compilationStage: inventory records the fixed paper faithfully; execution_plan prepares reproduction from /job/input/source-inventory.json. Write the completed submission to /job/output/research.json and stop. Do not perform experiments during either preparation stage.";
654
+ return `The draft has these authoritative defects:\n${validationIssues.map((issue) => `- ${issue}`).join("\n")}\nRead the existing research.json and correct these defects while preserving grounded content. Keep the original claim scope and reported values. Do not delete discovered measurements or relabel empirical scope as context to pass validation. Preserve measurement ids when regrouping independent results into separate claims. Operational commands, parser refinements and dependency work belong in the execution workspace. Produce readable JSON, parse it, and stop.`;
655
+ }
656
+
657
+ /**
658
+ * Treat a continuation repair as a transaction over an immutable Research
659
+ * Plan. The model may propose a complete document, but only failed experiment
660
+ * objects can be committed. This prevents a later retry from regressing
661
+ * completed targets or rewriting paper claims and reported values.
662
+ */
663
+ export function applyContinuationRepairBoundary(candidate, context = {}) {
664
+ const baseline = context.repairBaselineResearch;
665
+ const failedTargets = continuationRepairTargets(context.continuation);
666
+ if (!baseline || !failedTargets.length) {
667
+ return { research: candidate, issues: [] };
668
+ }
669
+
670
+ const issues = [];
671
+ const merged = structuredClone(baseline);
672
+ const baselineExperiments = Array.isArray(merged.experiments) ? merged.experiments : [];
673
+ const candidateExperiments = Array.isArray(candidate?.experiments) ? candidate.experiments : [];
674
+ const baselineById = new Map(baselineExperiments.map((experiment) => [experiment?.id, experiment]));
675
+ const candidateById = new Map(candidateExperiments.map((experiment) => [experiment?.id, experiment]));
676
+ const failedByExperiment = new Map();
677
+ for (const target of failedTargets) {
678
+ const targets = failedByExperiment.get(target.experimentId) ?? [];
679
+ targets.push(target);
680
+ failedByExperiment.set(target.experimentId, targets);
681
+ }
682
+
683
+ for (const [experimentId, targets] of failedByExperiment) {
684
+ const baselineExperiment = baselineById.get(experimentId);
685
+ const candidateExperiment = candidateById.get(experimentId);
686
+ if (!baselineExperiment || !candidateExperiment) {
687
+ issues.push(
688
+ `Continuation-repair boundary retry: failed target ${experimentId} must remain present under its original experiment id.`,
689
+ );
690
+ continue;
691
+ }
692
+ const baselineClaimIds = sortedStrings(baselineExperiment.claimIds);
693
+ const candidateClaimIds = sortedStrings(candidateExperiment.claimIds);
694
+ const requiredClaimIds = sortedStrings(targets.map((target) => target.claimId));
695
+ if (
696
+ !isDeepStrictEqual(candidateClaimIds, baselineClaimIds)
697
+ || requiredClaimIds.some((claimId) => !candidateClaimIds.includes(claimId))
698
+ ) {
699
+ issues.push(
700
+ `Continuation-repair boundary retry: ${experimentId} changed its immutable claim binding; preserve ${baselineClaimIds.join(", ")}.`,
701
+ );
702
+ continue;
703
+ }
704
+ const baselineMeasurementIds = reportedMeasurementBindings(baselineExperiment);
705
+ const candidateMeasurementIds = reportedMeasurementBindings(candidateExperiment);
706
+ if (!isDeepStrictEqual(candidateMeasurementIds, baselineMeasurementIds)) {
707
+ issues.push(
708
+ `Continuation-repair boundary retry: ${experimentId} changed its immutable reportedMeasurementId binding; preserve ${baselineMeasurementIds.join(", ")}.`,
709
+ );
710
+ continue;
711
+ }
712
+ const index = baselineExperiments.findIndex((experiment) => experiment?.id === experimentId);
713
+ baselineExperiments[index] = structuredClone(candidateExperiment);
714
+ }
715
+
716
+ return {
717
+ research: normalizeResearchStructure(merged),
718
+ issues,
719
+ };
720
+ }
721
+
722
+
723
+ function reportedMeasurementBindings(experiment) {
724
+ return sortedStrings(
725
+ (Array.isArray(experiment?.measurements) ? experiment.measurements : [])
726
+ .map((measurement) => measurement?.reportedMeasurementId),
727
+ );
728
+ }
729
+
730
+ function sortedStrings(values) {
731
+ return [...new Set((Array.isArray(values) ? values : [])
732
+ .filter((value) => typeof value === "string" && value.trim())
733
+ .map((value) => value.trim()))]
734
+ .sort();
735
+ }
736
+
737
+ async function snapshotRemotePaper(runtimeTask, inputDirectory) {
738
+ if (!runtimeTask.paper?.url) return;
739
+ const sourceUrl = new URL(runtimeTask.paper.url);
740
+ if (!new Set(["http:", "https:"]).has(sourceUrl.protocol)) {
741
+ throw new CiteArkError(`论文 URL 只支持 HTTP(S):${runtimeTask.paper.url}`);
742
+ }
743
+ let response;
744
+ try {
745
+ response = await fetch(sourceUrl, {
746
+ redirect: "follow",
747
+ signal: AbortSignal.timeout(60_000),
748
+ headers: { "user-agent": "CiteArk-Agent/0.2 (+https://citeark.com)" },
749
+ });
750
+ } catch (error) {
751
+ throw new CiteArkError(`无法固定论文快照:${sourceUrl}`, { cause: error });
752
+ }
753
+ if (!response.ok) {
754
+ throw new CiteArkError(`无法固定论文快照:${sourceUrl} 返回 HTTP ${response.status}`);
755
+ }
756
+ const body = Buffer.from(await response.arrayBuffer());
757
+ const maximumBytes = 10 * 1024 * 1024;
758
+ if (!body.length || body.length > maximumBytes) {
759
+ throw new CiteArkError(`论文快照大小无效:${body.length} bytes(上限 ${maximumBytes})`);
760
+ }
761
+ const contentType = response.headers.get("content-type")?.split(";")[0].trim().toLowerCase();
762
+ const extension = contentType === "application/pdf" ? ".pdf" : contentType === "text/plain" ? ".txt" : ".html";
763
+ const paperDirectory = path.join(inputDirectory, "paper");
764
+ const filename = `paper${extension}`;
765
+ await mkdir(paperDirectory, { recursive: true });
766
+ await writeFile(path.join(paperDirectory, filename), body);
767
+ runtimeTask.paper = {
768
+ title: runtimeTask.paper.title,
769
+ path: `/job/input/paper/${filename}`,
770
+ sourceUrl: sourceUrl.toString(),
771
+ resolvedUrl: response.url,
772
+ digest: `sha256:${createHash("sha256").update(body).digest("hex")}`,
773
+ mediaType: contentType ?? "application/octet-stream",
774
+ size: body.length,
775
+ };
776
+ }
777
+
778
+ async function readFileText(target) {
779
+ const { readFile } = await import("node:fs/promises");
780
+ return readFile(target, "utf8");
781
+ }
782
+
783
+ export function pinResearchRepository(research, repositoryIdentity) {
784
+ const pinned = structuredClone(research);
785
+ if (
786
+ !repositoryIdentity?.available ||
787
+ !repositoryIdentity.url ||
788
+ !repositoryIdentity.commit
789
+ ) {
790
+ pinned.sources = pinned.sources.filter((source) => source.kind !== "repository");
791
+ pinned.experiments = pinned.experiments
792
+ .filter((experiment) => experiment.implementationOrigin === "citeark_reconstruction")
793
+ .map((experiment) => ({
794
+ ...experiment,
795
+ implementationOrigin: "citeark_reconstruction",
796
+ repository: { ...RECONSTRUCTION_REPOSITORY },
797
+ }));
798
+ return normalizeResearchStructure(pinned);
799
+ }
800
+ for (const source of pinned.sources) {
801
+ if (source.kind !== "repository") continue;
802
+ source.uri = repositoryIdentity.url;
803
+ source.version = repositoryIdentity.commit;
804
+ source.digest = repositoryIdentity.snapshotDigest;
805
+ }
806
+ for (const experiment of pinned.experiments) {
807
+ if (experiment.implementationOrigin === "citeark_reconstruction") {
808
+ experiment.repository = { ...RECONSTRUCTION_REPOSITORY };
809
+ } else {
810
+ experiment.implementationOrigin = "official";
811
+ experiment.repository = {
812
+ implementationOrigin: "official",
813
+ url: repositoryIdentity.url,
814
+ commit: repositoryIdentity.commit,
815
+ };
816
+ }
817
+ }
818
+ return normalizeResearchStructure(pinned);
819
+ }
820
+
821
+ async function prepareMissingRepository(destination) {
822
+ await mkdir(destination, { recursive: true });
823
+ return {
824
+ sourceType: "none",
825
+ submittedSource: null,
826
+ commit: null,
827
+ dirty: null,
828
+ };
829
+ }
830
+
831
+ export function applyCompilationLicensePolicy(research, licensePolicy) {
832
+ const normalized = structuredClone(research);
833
+ normalized.license = {
834
+ ...normalized.license,
835
+ executionAllowed: licensePolicy?.executable !== false,
836
+ redistribution:
837
+ normalized.license.redistribution ??
838
+ (licensePolicy?.redistributable
839
+ ? "Source redistribution permitted by the recorded license assessment"
840
+ : "Source materials remain link-only; the Artifact contains derived execution records"),
841
+ };
842
+ return normalizeResearchStructure(normalized);
843
+ }
844
+
845
+ function validateSource(value, label, issues) {
846
+ if (!isRecord(value)) return issues.push(`${label} 必须是对象`);
847
+ const count = Number(Boolean(value.path)) + Number(Boolean(value.url));
848
+ if (count !== 1) issues.push(`${label} 必须且只能提供 path 或 url`);
849
+ }
850
+
851
+ function required(value, label, issues) {
852
+ if (typeof value !== "string" || !value.trim()) issues.push(`${label} 必须是非空字符串`);
853
+ }