@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.
- package/LICENSE +202 -0
- package/README.md +128 -0
- package/data/dataset-source-registry.v1.json +300 -0
- package/dist/arkgraph/boot.js +6 -0
- package/dist/arkgraph/index.html +1 -0
- package/dist/arkgraph/viewer.css +1 -0
- package/dist/arkgraph/viewer.en.css +1 -0
- package/dist/arkgraph/viewer.en.js +49 -0
- package/dist/arkgraph/viewer.en.js.LEGAL.txt +56 -0
- package/dist/arkgraph/viewer.js +49 -0
- package/dist/arkgraph/viewer.js.LEGAL.txt +56 -0
- package/docker/claude-code/Dockerfile +97 -0
- package/docker/claude-code/codex-pro-relay.mjs +466 -0
- package/docker/claude-code/runtime-contract-check.mjs +79 -0
- package/docs/arkgraph-reading.md +79 -0
- package/docs/configuration.md +100 -0
- package/docs/integration.md +92 -0
- package/docs/maturity-plan.md +27 -0
- package/docs/npm-release.md +44 -0
- package/docs/paper-reading.md +40 -0
- package/docs/research-plan-granularity.md +27 -0
- package/docs/terminal.md +49 -0
- package/examples/toy-evaluation/compile-task.json +27 -0
- package/examples/toy-evaluation/paper.md +5 -0
- package/examples/toy-evaluation/repository/README.md +9 -0
- package/examples/toy-evaluation/repository/checkpoint.json +4 -0
- package/examples/toy-evaluation/repository/evaluate.py +17 -0
- package/examples/toy-evaluation/task.json +81 -0
- package/package.json +59 -0
- package/prompts/compile-research.md +58 -0
- package/prompts/execute-contract.md +72 -0
- package/prompts/execute-workspace-simple.md +51 -0
- package/prompts/execute-workspace.md +34 -0
- package/prompts/prepare-reproduction.md +82 -0
- package/prompts/repair-research.md +45 -0
- package/protocol/CAP.md +129 -0
- package/protocol/LICENSE +12 -0
- package/protocol/MAPPINGS.md +72 -0
- package/protocol/README.md +38 -0
- package/protocol/conformance-v2.0-alpha.1.json +36 -0
- package/protocol/examples/arkgraph/checkpoint-evaluation.json +309 -0
- package/protocol/examples/arkgraph/fixtures.mjs +49 -0
- package/protocol/examples/arkgraph/paper-free.json +291 -0
- package/protocol/examples/arkgraph/partial-failure.json +344 -0
- package/protocol/examples/arkgraph/training-evaluation.json +443 -0
- package/protocol/profiles/agent-trace.md +16 -0
- package/protocol/profiles/computational-run.md +16 -0
- package/protocol/profiles/core.md +15 -0
- package/protocol/profiles/public-bundle.md +18 -0
- package/protocol/profiles/reproduction.md +29 -0
- package/protocol/profiles/research-compilation.md +44 -0
- package/protocol/profiles/research-plan.md +39 -0
- package/protocol/profiles/restricted-evidence.md +15 -0
- package/runtime/bootstrap-autodl-runtime.sh +314 -0
- package/runtime/create-runtime-venv.sh +41 -0
- package/runtime/install-local-cpu-runtime.sh +23 -0
- package/runtime/install-scientific-runtime.sh +153 -0
- package/runtime/mineru/parse.py +62 -0
- package/runtime/mineru/requirements.txt +4 -0
- package/runtime/requirements-baseline.txt +38 -0
- package/schemas/cap/v2/activity.schema.json +47 -0
- package/schemas/cap/v2/agent.schema.json +32 -0
- package/schemas/cap/v2/assertion.schema.json +110 -0
- package/schemas/cap/v2/descriptor.schema.json +243 -0
- package/schemas/cap/v2/entity.schema.json +64 -0
- package/schemas/cap/v2/manifest.schema.json +67 -0
- package/schemas/cap/v2/relation.schema.json +82 -0
- package/schemas/compute-catalog.schema.json +63 -0
- package/schemas/compute-decision.schema.json +27 -0
- package/schemas/execution-contract.schema.json +1024 -0
- package/schemas/research-card.schema.json +30 -0
- package/schemas/research-inventory-draft.schema.json +366 -0
- package/schemas/research.schema.json +1044 -0
- package/schemas/result.schema.json +173 -0
- package/schemas/verification-policy.schema.json +47 -0
- package/schemas/verified-conclusion.schema.json +58 -0
- package/schemas/workspace-summary.schema.json +24 -0
- package/scripts/build-arkgraph-view.mjs +12 -0
- package/scripts/check-execution-feasibility.mjs +24 -0
- package/scripts/check-syntax.mjs +15 -0
- package/scripts/deterministic-asset-preparation.py +438 -0
- package/scripts/package-cap.mjs +23 -0
- package/scripts/package-local-agent.mjs +23 -0
- package/scripts/preview-arkgraph.mjs +25 -0
- package/scripts/replay-research-compiler-candidate.mjs +134 -0
- package/scripts/review-compiler-sources.mjs +44 -0
- package/scripts/run-asset-preparation.sh +17 -0
- package/scripts/run-research-plan.mjs +98 -0
- package/scripts/validate-asset-preparation.py +290 -0
- package/scripts/verify-local-runtime.mjs +57 -0
- package/scripts/verify-npm-package.mjs +57 -0
- package/src/adapters/paper2agent.mjs +107 -0
- package/src/assets/cache.mjs +159 -0
- package/src/assets/compute.mjs +98 -0
- package/src/assets/executor.mjs +145 -0
- package/src/assets/lifecycle.mjs +213 -0
- package/src/assets/manifest.mjs +242 -0
- package/src/assets/opportunistic-preparation.mjs +81 -0
- package/src/assets/plan.mjs +411 -0
- package/src/assets/prompts.mjs +29 -0
- package/src/assets/public-asset-probe.mjs +525 -0
- package/src/assets/qualification.mjs +119 -0
- package/src/assets/readiness.mjs +130 -0
- package/src/assets/reproduction-admission.mjs +355 -0
- package/src/assets/requirements.mjs +152 -0
- package/src/assets/source-grounding.mjs +341 -0
- package/src/assets/source-policy.mjs +118 -0
- package/src/autodl/client.mjs +260 -0
- package/src/autodl/ssh.mjs +380 -0
- package/src/autodl/tools.mjs +129 -0
- package/src/cap/redaction.mjs +38 -0
- package/src/cap/v2/archive.mjs +152 -0
- package/src/cap/v2/attestation.mjs +204 -0
- package/src/cap/v2/canonical-json.mjs +114 -0
- package/src/cap/v2/compilation-artifact.mjs +240 -0
- package/src/cap/v2/core.mjs +282 -0
- package/src/cap/v2/measurement-assessment-records.mjs +23 -0
- package/src/cap/v2/pipeline-artifact.mjs +922 -0
- package/src/cap/v2/read.mjs +41 -0
- package/src/cap/v2/reassessment-artifact.mjs +383 -0
- package/src/cap/v2/research-artifact.mjs +231 -0
- package/src/cap/v2/research-map-records.mjs +46 -0
- package/src/cap/v2/research-object-records.mjs +163 -0
- package/src/cap/v2/research-records.mjs +187 -0
- package/src/cap/v2/verify.mjs +642 -0
- package/src/cli.mjs +1146 -0
- package/src/compute/autodl-pro-compiler.mjs +347 -0
- package/src/compute/autodl-pro-executor.mjs +459 -0
- package/src/compute/autodl-pro-job.mjs +843 -0
- package/src/compute/autodl-pro-network.mjs +295 -0
- package/src/compute/autodl-pro-remote.mjs +810 -0
- package/src/compute/autodl-pro-staging.mjs +117 -0
- package/src/compute/campaign.mjs +110 -0
- package/src/compute/catalog.mjs +123 -0
- package/src/compute/checkpoint-protocol.mjs +154 -0
- package/src/compute/codex-account-lock.mjs +111 -0
- package/src/compute/codex-account-session.mjs +107 -0
- package/src/compute/compiler-profile.mjs +38 -0
- package/src/compute/compiler-router.mjs +23 -0
- package/src/compute/coordinator-recovery.mjs +210 -0
- package/src/compute/executor-router.mjs +29 -0
- package/src/compute/gcp-batch-compiler.mjs +685 -0
- package/src/compute/gcp-batch-executor.mjs +1215 -0
- package/src/compute/gcp-batch-failure.mjs +92 -0
- package/src/compute/gcp-batch-job.mjs +527 -0
- package/src/compute/gcp-batch-lifecycle.mjs +81 -0
- package/src/compute/gcp-checkpoint-worker.mjs +1633 -0
- package/src/compute/local-codex-compiler.mjs +52 -0
- package/src/compute/measurement-hardware.mjs +128 -0
- package/src/compute/remote-attempt.mjs +226 -0
- package/src/compute/requirements.mjs +124 -0
- package/src/compute/research-phases.mjs +48 -0
- package/src/compute/scheduler.mjs +452 -0
- package/src/compute/shared-workloads.mjs +26 -0
- package/src/compute/stage-archive.mjs +79 -0
- package/src/contracts/campaign-contract.mjs +52 -0
- package/src/contracts/execution-contract.mjs +819 -0
- package/src/contracts/execution-mode.mjs +19 -0
- package/src/contracts/execution-timeouts.mjs +45 -0
- package/src/contracts/execution-workload.mjs +68 -0
- package/src/contracts/preflight-schema.mjs +25 -0
- package/src/contracts/public-contract.mjs +63 -0
- package/src/contracts/subject-tags.mjs +31 -0
- package/src/dashboard/data.mjs +898 -0
- package/src/dashboard/server.mjs +79 -0
- package/src/dashboard/static/dashboard.css +366 -0
- package/src/dashboard/static/dashboard.js +560 -0
- package/src/dashboard/static/index.html +85 -0
- package/src/deployment/community-policy.mjs +9 -0
- package/src/deployment/environment.mjs +112 -0
- package/src/deployment/guided.mjs +98 -0
- package/src/deployment/handoff.mjs +102 -0
- package/src/deployment/local-contract.mjs +31 -0
- package/src/deployment/local.mjs +100 -0
- package/src/deployment/prepare.mjs +46 -0
- package/src/deployment/recipe.mjs +108 -0
- package/src/deployment/supplement.mjs +51 -0
- package/src/deployment/terminal.mjs +43 -0
- package/src/diagnosis/renderer.mjs +75 -0
- package/src/diagnosis/target-failure.mjs +46 -0
- package/src/evidence/parser-registry.mjs +54 -0
- package/src/evidence/parsers/fasttext-classification.mjs +82 -0
- package/src/evidence/parsers/json-scalar.mjs +96 -0
- package/src/evidence/parsers/simcse-senteval.mjs +104 -0
- package/src/evidence/parsers/starspace-classification.mjs +78 -0
- package/src/evidence/registry.mjs +147 -0
- package/src/execution/runner-audit.mjs +473 -0
- package/src/gcp/auth.mjs +106 -0
- package/src/gcp/batch-client.mjs +120 -0
- package/src/gcp/resource-discovery.mjs +177 -0
- package/src/gcp/rest.mjs +82 -0
- package/src/gcp/secret-manager.mjs +34 -0
- package/src/gcp/signed-url.mjs +133 -0
- package/src/gcp/storage.mjs +220 -0
- package/src/graph/command.mjs +41 -0
- package/src/graph/execution.mjs +97 -0
- package/src/graph/model.mjs +37 -0
- package/src/graph/presentation.mjs +110 -0
- package/src/graph/query.mjs +159 -0
- package/src/graph/research-relations.mjs +69 -0
- package/src/graph/source-page.mjs +12 -0
- package/src/graph/source-preview.mjs +34 -0
- package/src/graph/validate.mjs +76 -0
- package/src/job.mjs +496 -0
- package/src/network/autodl-routing-proxy.mjs +462 -0
- package/src/network/egress-proxy.mjs +158 -0
- package/src/observability/event-contract.mjs +230 -0
- package/src/observability/pipeline-monitor.mjs +166 -0
- package/src/pipeline/orchestrator.mjs +1281 -0
- package/src/pipeline/recovery-error.mjs +11 -0
- package/src/pipeline/replay.mjs +304 -0
- package/src/pipeline/shared-execution.mjs +115 -0
- package/src/pipeline/stage-checkpoint.mjs +86 -0
- package/src/pipeline/stage-recovery.mjs +101 -0
- package/src/pipeline/targets.mjs +110 -0
- package/src/process.mjs +143 -0
- package/src/protocol.mjs +312 -0
- package/src/provider/codex-account.mjs +44 -0
- package/src/provider/codex-completion.mjs +49 -0
- package/src/provider/completion.mjs +292 -0
- package/src/provider/model-client.mjs +44 -0
- package/src/provider/model-route.mjs +29 -0
- package/src/provider/openrouter-readiness.mjs +189 -0
- package/src/provider/reader-bridge.mjs +35 -0
- package/src/provider/relay.mjs +263 -0
- package/src/provider/runtime-auth.mjs +40 -0
- package/src/public/cap.d.mts +90 -0
- package/src/public/cap.mjs +12 -0
- package/src/public/contracts.d.mts +2 -0
- package/src/public/host.mjs +171 -0
- package/src/public/operations.d.mts +11 -0
- package/src/public/presentation.d.mts +4 -0
- package/src/records/views.mjs +26 -0
- package/src/remote/command.mjs +178 -0
- package/src/remote/ssh.mjs +59 -0
- package/src/repository-origin.mjs +81 -0
- package/src/reproduction/evidence-feedback.mjs +96 -0
- package/src/reproduction/incomplete-initialization.mjs +25 -0
- package/src/reproduction/lifecycle.mjs +253 -0
- package/src/reproduction/plan.mjs +132 -0
- package/src/reproduction/prompts.mjs +70 -0
- package/src/reproduction/runner.mjs +188 -0
- package/src/reproduction/summary.mjs +130 -0
- package/src/reproduction/workspace-mode.mjs +7 -0
- package/src/research/automatic-admission.mjs +156 -0
- package/src/research/compiler-coverage.mjs +85 -0
- package/src/research/compiler-failure.mjs +24 -0
- package/src/research/compiler-normalization-guards.mjs +112 -0
- package/src/research/compiler-repair.mjs +3 -0
- package/src/research/compiler.mjs +853 -0
- package/src/research/continuation-selection.mjs +26 -0
- package/src/research/execution-graph-context.mjs +43 -0
- package/src/research/experiment-importance.mjs +15 -0
- package/src/research/inventory-handoff.mjs +104 -0
- package/src/research/inventory-revisions.mjs +32 -0
- package/src/research/mineru-local.mjs +73 -0
- package/src/research/paper-command.mjs +19 -0
- package/src/research/paper-markdown.mjs +180 -0
- package/src/research/paper-source-map.mjs +69 -0
- package/src/research/planning-policy.mjs +88 -0
- package/src/research/reference-materials.mjs +11 -0
- package/src/research/reproduction-scope.mjs +30 -0
- package/src/research/research-map.mjs +94 -0
- package/src/research/research-objects.mjs +88 -0
- package/src/research/source-discovery.mjs +646 -0
- package/src/research/source-observations.mjs +75 -0
- package/src/research/source-review-cli-mcp.mjs +26 -0
- package/src/research/source-review-input.mjs +209 -0
- package/src/research/source-review-local-codex.mjs +36 -0
- package/src/research/source-review-model.mjs +70 -0
- package/src/research/source-review.mjs +173 -0
- package/src/research/structure.mjs +3163 -0
- package/src/research-card/renderer.mjs +277 -0
- package/src/research-card/verified-conclusion.mjs +143 -0
- package/src/results/output-registry.mjs +183 -0
- package/src/runtime/claude-code.mjs +52 -0
- package/src/runtime/codex-capacity-retry.mjs +87 -0
- package/src/runtime/codex.mjs +64 -0
- package/src/runtime/config.mjs +157 -0
- package/src/runtime/final-output.mjs +40 -0
- package/src/runtime/index.mjs +21 -0
- package/src/runtime/local-codex.mjs +74 -0
- package/src/runtime/opencode.mjs +95 -0
- package/src/runtime/prompt.mjs +13 -0
- package/src/sandbox/docker.mjs +363 -0
- package/src/settings/command.mjs +297 -0
- package/src/settings/store.mjs +119 -0
- package/src/telemetry/pricing.mjs +68 -0
- package/src/telemetry/usage.mjs +265 -0
- package/src/terminal/events.mjs +97 -0
- package/src/terminal/input.mjs +40 -0
- package/src/terminal/plain.mjs +40 -0
- package/src/terminal/remote-stream.mjs +22 -0
- package/src/terminal/screen.mjs +214 -0
- package/src/terminal/transcript.mjs +69 -0
- package/src/util.mjs +107 -0
- package/src/verification/ai-assessor.mjs +534 -0
- package/src/verification/claim-evaluator.mjs +242 -0
- package/src/verification/evidence-context.mjs +165 -0
- package/src/verification/evidence-reader.mjs +95 -0
- package/src/verification/integrity.mjs +570 -0
- package/src/verification/tolerance.mjs +32 -0
- package/src/workloads/cpu-research-preparation.mjs +56 -0
- package/src/workloads/definition.mjs +74 -0
- package/src/workloads/phase-aware-reproduction.mjs +46 -0
- package/src/workloads/reproduction.mjs +85 -0
- package/src/workspace/command.mjs +242 -0
- package/src/workspace/control.mjs +49 -0
- package/src/workspace/entry.mjs +28 -0
- package/src/workspace/input.mjs +93 -0
- package/src/workspace/interactive.mjs +94 -0
- package/src/workspace/jobs.mjs +418 -0
- package/src/workspace/session.mjs +97 -0
- package/src/workspace/worker.mjs +137 -0
- package/ui/arkgraph/ambient-motion.mjs +10 -0
- package/ui/arkgraph/app.jsx +153 -0
- package/ui/arkgraph/boot.js +6 -0
- package/ui/arkgraph/camera-motion.mjs +20 -0
- package/ui/arkgraph/context-reveal.mjs +39 -0
- package/ui/arkgraph/details.css +3 -0
- package/ui/arkgraph/entry.jsx +28 -0
- package/ui/arkgraph/experiment-curves.mjs +17 -0
- package/ui/arkgraph/experiment-selection.mjs +15 -0
- package/ui/arkgraph/experiment-style.css +26 -0
- package/ui/arkgraph/experiment-ui.jsx +32 -0
- package/ui/arkgraph/frame.html +1 -0
- package/ui/arkgraph/graph-gestures.mjs +62 -0
- package/ui/arkgraph/label-layout.mjs +57 -0
- package/ui/arkgraph/locales/en.json +229 -0
- package/ui/arkgraph/locales/source-types.json +15 -0
- package/ui/arkgraph/localization-build.mjs +27 -0
- package/ui/arkgraph/material-build.mjs +23 -0
- package/ui/arkgraph/material-colors.mjs +39 -0
- package/ui/arkgraph/material-style.css +15 -0
- package/ui/arkgraph/open-graph.jsx +326 -0
- package/ui/arkgraph/outline.jsx +49 -0
- package/ui/arkgraph/package-lock.json +888 -0
- package/ui/arkgraph/package.json +17 -0
- package/ui/arkgraph/reading-layout.mjs +130 -0
- package/ui/arkgraph/reading-presentation.mjs +73 -0
- package/ui/arkgraph/record-detail.css +51 -0
- package/ui/arkgraph/record-details.jsx +29 -0
- package/ui/arkgraph/research-types.mjs +31 -0
- package/ui/arkgraph/selection-mark.jsx +6 -0
- package/ui/arkgraph/soft-spine.mjs +26 -0
- package/ui/arkgraph/steering-style.css +187 -0
- package/ui/arkgraph/style.css +272 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import { GoogleAccessTokenProvider } from "../gcp/auth.mjs";
|
|
4
|
+
import { GcpIamCredentialsSigner, createGcsV4SignedReadUrl } from "../gcp/signed-url.mjs";
|
|
5
|
+
import { GcsStagingClient } from "../gcp/storage.mjs";
|
|
6
|
+
|
|
7
|
+
export function createAutoDlGcsStageTransfer({ config, clients = {} } = {}) {
|
|
8
|
+
if (!config?.stagingBucket || !config?.stageSigningServiceAccount) return null;
|
|
9
|
+
if (clients.stageTransfer) return clients.stageTransfer;
|
|
10
|
+
const auth = clients.stageAuth ?? new GoogleAccessTokenProvider(clients.stageAuthOptions);
|
|
11
|
+
return new AutoDlGcsStageTransfer({
|
|
12
|
+
bucket: config.stagingBucket,
|
|
13
|
+
serviceAccount: config.stageSigningServiceAccount,
|
|
14
|
+
expiresSeconds: config.stageSignedUrlExpiresSeconds,
|
|
15
|
+
storage: clients.stageStorage ?? new GcsStagingClient({
|
|
16
|
+
auth,
|
|
17
|
+
...clients.stageStorageOptions,
|
|
18
|
+
}),
|
|
19
|
+
signer: clients.stageSigner ?? new GcpIamCredentialsSigner({
|
|
20
|
+
auth,
|
|
21
|
+
...clients.stageSignerOptions,
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class AutoDlGcsStageTransfer {
|
|
27
|
+
constructor({
|
|
28
|
+
bucket,
|
|
29
|
+
serviceAccount,
|
|
30
|
+
expiresSeconds = 30 * 60,
|
|
31
|
+
storage,
|
|
32
|
+
signer,
|
|
33
|
+
now = () => new Date(),
|
|
34
|
+
} = {}) {
|
|
35
|
+
this.bucket = required(bucket, "bucket");
|
|
36
|
+
this.serviceAccount = required(serviceAccount, "serviceAccount");
|
|
37
|
+
this.expiresSeconds = positiveInteger(expiresSeconds, "expiresSeconds");
|
|
38
|
+
this.storage = requiredClient(storage, "storage", ["uploadFile", "deleteObject"]);
|
|
39
|
+
this.signer = requiredClient(signer, "signer", ["signBlob"]);
|
|
40
|
+
this.now = now;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async publish({ runId, attempt, filePath, digest, byteSize }) {
|
|
44
|
+
const normalizedDigest = String(digest ?? "").toLowerCase();
|
|
45
|
+
if (!/^[a-f0-9]{64}$/.test(normalizedDigest)) {
|
|
46
|
+
throw new TypeError("AutoDL GCS 暂存包 SHA-256 无效");
|
|
47
|
+
}
|
|
48
|
+
const size = positiveInteger(byteSize, "byteSize");
|
|
49
|
+
const object = [
|
|
50
|
+
"autodl-stage",
|
|
51
|
+
safeIdentifier(runId),
|
|
52
|
+
`attempt-${positiveInteger(attempt, "attempt")}`,
|
|
53
|
+
`${normalizedDigest}.tar.gz`,
|
|
54
|
+
].join("/");
|
|
55
|
+
await this.storage.uploadFile({
|
|
56
|
+
bucket: this.bucket,
|
|
57
|
+
object,
|
|
58
|
+
filePath: path.resolve(filePath),
|
|
59
|
+
contentType: "application/gzip",
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
const url = await createGcsV4SignedReadUrl({
|
|
63
|
+
bucket: this.bucket,
|
|
64
|
+
object,
|
|
65
|
+
serviceAccount: this.serviceAccount,
|
|
66
|
+
signer: this.signer,
|
|
67
|
+
expiresSeconds: this.expiresSeconds,
|
|
68
|
+
now: this.now(),
|
|
69
|
+
});
|
|
70
|
+
return {
|
|
71
|
+
mode: "gcs-signed-url",
|
|
72
|
+
bucket: this.bucket,
|
|
73
|
+
object,
|
|
74
|
+
url,
|
|
75
|
+
digest: normalizedDigest,
|
|
76
|
+
byteSize: size,
|
|
77
|
+
remove: async () => await this.storage.deleteObject({
|
|
78
|
+
bucket: this.bucket,
|
|
79
|
+
object,
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
} catch (error) {
|
|
83
|
+
await this.storage.deleteObject({ bucket: this.bucket, object }).catch(() => undefined);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function safeIdentifier(value) {
|
|
90
|
+
return String(value ?? "run")
|
|
91
|
+
.toLowerCase()
|
|
92
|
+
.replaceAll(/[^a-z0-9-]/g, "-")
|
|
93
|
+
.replaceAll(/^-+|-+$/g, "")
|
|
94
|
+
.slice(0, 80) || "run";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function required(value, field) {
|
|
98
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
99
|
+
throw new TypeError(`AutoDL GCS 暂存缺少 ${field}`);
|
|
100
|
+
}
|
|
101
|
+
return value.trim();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function requiredClient(value, field, methods) {
|
|
105
|
+
if (!value || methods.some((method) => typeof value[method] !== "function")) {
|
|
106
|
+
throw new TypeError(`AutoDL GCS 暂存 ${field} 实现无效`);
|
|
107
|
+
}
|
|
108
|
+
return value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function positiveInteger(value, field) {
|
|
112
|
+
const numeric = Number(value);
|
|
113
|
+
if (!Number.isInteger(numeric) || numeric <= 0) {
|
|
114
|
+
throw new TypeError(`AutoDL GCS 暂存 ${field} 必须是正整数`);
|
|
115
|
+
}
|
|
116
|
+
return numeric;
|
|
117
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { resolveComputeRequirement } from "./requirements.mjs";
|
|
2
|
+
import { measurementHardwareConstraint } from "./measurement-hardware.mjs";
|
|
3
|
+
import { executionTimeoutCapMinutes } from "../contracts/execution-timeouts.mjs";
|
|
4
|
+
import { CiteArkError, sha256Value } from "../util.mjs";
|
|
5
|
+
import { sharedWorkloads, workloadMinutes } from "./shared-workloads.mjs";
|
|
6
|
+
|
|
7
|
+
// Keep an existing campaign intact across publication retries. When migrating
|
|
8
|
+
// an older per-experiment job, completed targets must not train again.
|
|
9
|
+
export function campaignExecutionTargets(targets, previousUnits = [], completedTargets = []) {
|
|
10
|
+
const key = t => `${t.experimentId}:${t.claimId}`;
|
|
11
|
+
const previous = previousUnits.find(unit => unit.id?.startsWith("campaign:"));
|
|
12
|
+
if (previous) {
|
|
13
|
+
const admitted = new Set(targets.map(key));
|
|
14
|
+
const removed = previous.targets.filter(t => !admitted.has(key(t)));
|
|
15
|
+
// Workers before the target-binding fix persisted contextual
|
|
16
|
+
// claim/experiment associations without any verifiable measurement as
|
|
17
|
+
// campaign members. They never had a valid contract to execute. Permit a
|
|
18
|
+
// one-way migration that only drops those empty legacy members; a real
|
|
19
|
+
// measured target changing still invalidates the immutable campaign.
|
|
20
|
+
if (removed.some(t => Array.isArray(t.measurementIds) && t.measurementIds.length)) {
|
|
21
|
+
throw new CiteArkError("恢复任务的整组实验范围已改变");
|
|
22
|
+
}
|
|
23
|
+
const retainedTargets = previous.targets.filter(t => admitted.has(key(t)));
|
|
24
|
+
const retained = new Set(retainedTargets.map(key));
|
|
25
|
+
const done = new Set(completedTargets.map(key));
|
|
26
|
+
if (targets.some(t => !retained.has(key(t)) && !done.has(key(t)))) throw new CiteArkError("不能在恢复运行中加入新的实验");
|
|
27
|
+
return retainedTargets;
|
|
28
|
+
}
|
|
29
|
+
const done = new Set(completedTargets.map(key));
|
|
30
|
+
return targets.filter(t => !done.has(key(t)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** One selected set is one persistent execution, with claim-specific outputs. */
|
|
34
|
+
export function reproductionCampaign(research, targets) {
|
|
35
|
+
if (!targets.length) return null;
|
|
36
|
+
const unique = [...new Map(targets.map(t => [`${t.experimentId}:${t.claimId}`, t])).values()]
|
|
37
|
+
.sort((a, b) => `${a.experimentId}:${a.claimId}`.localeCompare(`${b.experimentId}:${b.claimId}`));
|
|
38
|
+
const ids = [...new Set(unique.map(t => t.experimentId))];
|
|
39
|
+
const experiments = ids.map(id => {
|
|
40
|
+
const experiment = research.experiments.find(e => e.id === id);
|
|
41
|
+
if (!experiment) throw new CiteArkError(`找不到所选实验:${id}`);
|
|
42
|
+
return experiment;
|
|
43
|
+
});
|
|
44
|
+
for (const target of unique) {
|
|
45
|
+
if (!experiments.find(e => e.id === target.experimentId).claimIds.includes(target.claimId)) {
|
|
46
|
+
throw new CiteArkError(`实验 ${target.experimentId} 未绑定结论 ${target.claimId}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// A run has one immutable source snapshot. Do not quietly substitute another
|
|
50
|
+
// repository or split the user's selection into separately billed machines.
|
|
51
|
+
if (new Set(experiments.map(e => sha256Value(e.repository))).size !== 1) {
|
|
52
|
+
throw new CiteArkError("所选实验的固定源码不同,需先统一工作区源码计划,不能静默拆成多台机器执行");
|
|
53
|
+
}
|
|
54
|
+
const requirements = experiments.map(resolveComputeRequirement);
|
|
55
|
+
const gpu = requirements.some(r => r.accelerator === "required" || r.accelerator === "optional");
|
|
56
|
+
const hardware = experiments.map(measurementHardwareConstraint);
|
|
57
|
+
const typeSets = hardware.map(h => h.requiredGpuTypes).filter(types => types.length);
|
|
58
|
+
const requiredGpuTypes = typeSets.length ? typeSets[0].filter(type => typeSets.every(set =>
|
|
59
|
+
set.some(t => t.toLowerCase() === type.toLowerCase()))) : [];
|
|
60
|
+
if (typeSets.length && !requiredGpuTypes.length) {
|
|
61
|
+
throw new CiteArkError("所选实验要求不同的固定 GPU 型号,单台机器无法满足全部科学条件");
|
|
62
|
+
}
|
|
63
|
+
const accelerator = gpu ? "gpu" : "cpu";
|
|
64
|
+
const estimate = campaignWorkEstimate(experiments, accelerator);
|
|
65
|
+
const maximum = key => Math.max(...requirements.map(r => r[key] ?? 0));
|
|
66
|
+
const timeoutMinutes = Math.min(executionTimeoutCapMinutes("faithful"), experiments.reduce(
|
|
67
|
+
(sum, e) => sum + (e.environment?.timeoutMinutes ?? 720), 0));
|
|
68
|
+
const first = experiments[0];
|
|
69
|
+
const compute = {
|
|
70
|
+
accelerator: gpu ? "required" : "none", cpuCores: maximum("cpuCores"),
|
|
71
|
+
memoryGb: maximum("memoryGb"), gpuCount: gpu ? Math.max(1, maximum("gpuCount"),
|
|
72
|
+
...hardware.map(h => h.requiredGpuCount ?? 0)) : 0,
|
|
73
|
+
...(gpu ? { minVramGb: Math.max(maximum("minVramGb"), ...hardware.map(h => h.requiredVramGb ?? 0)) || undefined } : {}),
|
|
74
|
+
cpuFallbackAllowed: !gpu, preemptionSafe: requirements.every(r => r.preemptionSafe),
|
|
75
|
+
estimatedDurationMinutes: { [accelerator]: estimate.durationMinutes },
|
|
76
|
+
rationale: estimate.basis === "shared_workloads"
|
|
77
|
+
? "One machine; identical declared workloads counted once, without assuming parallel speedup."
|
|
78
|
+
: "Joint runtime is unknown until the Agent plans shared work; independent experiment durations are not added into a campaign quote.",
|
|
79
|
+
};
|
|
80
|
+
const experiment = {
|
|
81
|
+
...first, compute,
|
|
82
|
+
environment: { ...first.environment, timeoutMinutes },
|
|
83
|
+
protocol: { executionMode: "agent_planned", objective: "Complete all selected experiments in one persistent workspace.",
|
|
84
|
+
entrypoint: "Agent organizes the selected scientific work, shared training and measurement.",
|
|
85
|
+
requiredCommandFragments: [],
|
|
86
|
+
...(gpu ? { benchmark: { device: "gpu", ...(requiredGpuTypes.length ? { requiredGpuTypes } : {}),
|
|
87
|
+
...(compute.gpuCount ? { requiredGpuCount: compute.gpuCount } : {}),
|
|
88
|
+
...(compute.minVramGb ? { requiredVramGb: compute.minVramGb } : {}) } } : {}),
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
return { id: `campaign:${sha256Value(unique.map(t => [t.claimId, t.experimentId])).slice(0, 20)}`,
|
|
92
|
+
experimentId: first.id, experimentIds: ids, claimIds: [...new Set(unique.map(t => t.claimId))],
|
|
93
|
+
measurementIds: [...new Set(unique.flatMap(t => t.measurementIds ?? []))], targets: unique,
|
|
94
|
+
sharedAcrossClaims: new Set(unique.map(t => t.claimId)).size > 1, experiments, experiment,
|
|
95
|
+
estimate, timeoutMinutes };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Optional planner-declared work items describe *computation*, not new scientific
|
|
99
|
+
// experiments or a DAG. Same ID is reusable only with the exact same definition.
|
|
100
|
+
export function campaignWorkEstimate(experiments, accelerator) {
|
|
101
|
+
if (experiments.length === 1 && !experiments[0].compute?.workloads?.length) {
|
|
102
|
+
const r = resolveComputeRequirement(experiments[0]);
|
|
103
|
+
return { basis: "single_experiment", durationMinutes: r.estimatedDurationMinutes[accelerator] ?? null };
|
|
104
|
+
}
|
|
105
|
+
if (!experiments.every(e => e.compute?.workloads?.length)) return { basis: "unknown", durationMinutes: null };
|
|
106
|
+
const work = sharedWorkloads(experiments);
|
|
107
|
+
const durations = work.map(item => workloadMinutes(item, accelerator));
|
|
108
|
+
return { basis: "shared_workloads", workloadCount: work.length,
|
|
109
|
+
durationMinutes: durations.every(n => Number.isFinite(n) && n >= 0) ? durations.reduce((a, b) => a + b, 0) : null };
|
|
110
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { readJson } from "../util.mjs";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_COMPUTE_CATALOG = Object.freeze({
|
|
4
|
+
schemaVersion: "0.1",
|
|
5
|
+
profiles: [
|
|
6
|
+
{
|
|
7
|
+
id: "local-docker-cpu",
|
|
8
|
+
label: "Local Docker CPU",
|
|
9
|
+
executor: "local-docker",
|
|
10
|
+
accelerator: "cpu",
|
|
11
|
+
cpuCores: 8,
|
|
12
|
+
memoryGb: 32,
|
|
13
|
+
hourlyUsd: 0,
|
|
14
|
+
available: true,
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_COMPUTE_POLICY = Object.freeze({
|
|
20
|
+
schemaVersion: "0.1",
|
|
21
|
+
allowCpuFallback: true,
|
|
22
|
+
maxEstimatedComputeUsd: null,
|
|
23
|
+
maxNonResumableSpotDurationMinutes: 30,
|
|
24
|
+
allowedExecutors: null,
|
|
25
|
+
allowedProfileIds: null,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export async function loadComputeCatalog(target) {
|
|
29
|
+
const catalog = target ? await readJson(target) : structuredClone(DEFAULT_COMPUTE_CATALOG);
|
|
30
|
+
validateComputeCatalog(catalog);
|
|
31
|
+
return catalog;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function loadComputePolicy(target) {
|
|
35
|
+
const policy = target ? await readJson(target) : structuredClone(DEFAULT_COMPUTE_POLICY);
|
|
36
|
+
validateComputePolicy(policy);
|
|
37
|
+
return { ...structuredClone(DEFAULT_COMPUTE_POLICY), ...policy };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function validateComputeCatalog(catalog) {
|
|
41
|
+
const issues = [];
|
|
42
|
+
if (catalog?.schemaVersion !== "0.1") issues.push("catalog.schemaVersion 必须是 0.1");
|
|
43
|
+
if (!Array.isArray(catalog?.profiles) || catalog.profiles.length === 0) issues.push("catalog.profiles 必须是非空数组");
|
|
44
|
+
const ids = new Set();
|
|
45
|
+
for (const [index, profile] of (catalog?.profiles ?? []).entries()) {
|
|
46
|
+
const label = `catalog.profiles[${index}]`;
|
|
47
|
+
if (typeof profile?.id !== "string" || !profile.id) issues.push(`${label}.id 必须是非空字符串`);
|
|
48
|
+
if (ids.has(profile?.id)) issues.push(`计算资源 profile id 重复:${profile.id}`);
|
|
49
|
+
ids.add(profile?.id);
|
|
50
|
+
if (typeof profile?.executor !== "string" || !profile.executor) issues.push(`${label}.executor 必须是非空字符串`);
|
|
51
|
+
if (profile?.executorConfig !== undefined && (!profile.executorConfig || typeof profile.executorConfig !== "object" || Array.isArray(profile.executorConfig))) {
|
|
52
|
+
issues.push(`${label}.executorConfig 必须是对象`);
|
|
53
|
+
}
|
|
54
|
+
if (
|
|
55
|
+
profile?.executor === "gcp-batch"
|
|
56
|
+
&& !nonEmptyString(profile?.executorConfig?.machineType)
|
|
57
|
+
&& !nonEmptyString(profile?.executorConfig?.instanceTemplate)
|
|
58
|
+
) {
|
|
59
|
+
issues.push(`${label}.executorConfig 至少需要 machineType 或 instanceTemplate`);
|
|
60
|
+
}
|
|
61
|
+
if (profile?.executor === "autodl-pro" && (typeof profile?.executorConfig?.gpuSpecUuid !== "string" || !profile.executorConfig.gpuSpecUuid)) {
|
|
62
|
+
issues.push(`${label}.executorConfig.gpuSpecUuid 是 autodl-pro 的必需字段`);
|
|
63
|
+
}
|
|
64
|
+
if (profile?.executorConfig?.provisioningModel && !new Set(["STANDARD", "SPOT", "FLEX_START"]).has(profile.executorConfig.provisioningModel)) {
|
|
65
|
+
issues.push(`${label}.executorConfig.provisioningModel 必须是 STANDARD、SPOT 或 FLEX_START`);
|
|
66
|
+
}
|
|
67
|
+
if (!new Set(["cpu", "gpu"]).has(profile?.accelerator)) issues.push(`${label}.accelerator 必须是 cpu 或 gpu`);
|
|
68
|
+
for (const field of ["cpuCores", "memoryGb"]) {
|
|
69
|
+
if (!isFinitePositive(profile?.[field])) issues.push(`${label}.${field} 必须大于 0`);
|
|
70
|
+
}
|
|
71
|
+
if (profile?.hourlyUsd !== null && profile?.hourlyUsd !== undefined && !isFiniteNonNegative(profile.hourlyUsd)) {
|
|
72
|
+
issues.push(`${label}.hourlyUsd 必须是非负数或 null`);
|
|
73
|
+
}
|
|
74
|
+
if (profile?.hourlyCny !== null && profile?.hourlyCny !== undefined && !isFiniteNonNegative(profile.hourlyCny)) {
|
|
75
|
+
issues.push(`${label}.hourlyCny 必须是非负数或 null`);
|
|
76
|
+
}
|
|
77
|
+
if (profile?.accelerator === "gpu") {
|
|
78
|
+
if (!isFinitePositive(profile?.gpu?.count)) issues.push(`${label}.gpu.count 必须大于 0`);
|
|
79
|
+
if (!isFinitePositive(profile?.gpu?.vramGb)) issues.push(`${label}.gpu.vramGb 必须大于 0`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (issues.length) throw new TypeError(`计算资源目录校验失败:\n- ${issues.join("\n- ")}`);
|
|
83
|
+
return catalog;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function nonEmptyString(value) {
|
|
87
|
+
return typeof value === "string" && value.length > 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function validateComputePolicy(policy) {
|
|
91
|
+
const issues = [];
|
|
92
|
+
if (policy?.schemaVersion !== "0.1") issues.push("policy.schemaVersion 必须是 0.1");
|
|
93
|
+
if (policy?.allowCpuFallback !== undefined && typeof policy.allowCpuFallback !== "boolean") issues.push("policy.allowCpuFallback 必须是布尔值");
|
|
94
|
+
if (policy?.maxEstimatedComputeUsd !== null && policy?.maxEstimatedComputeUsd !== undefined && !isFiniteNonNegative(policy.maxEstimatedComputeUsd)) {
|
|
95
|
+
issues.push("policy.maxEstimatedComputeUsd 必须是非负数或 null");
|
|
96
|
+
}
|
|
97
|
+
if (
|
|
98
|
+
policy?.maxNonResumableSpotDurationMinutes !== null
|
|
99
|
+
&& policy?.maxNonResumableSpotDurationMinutes !== undefined
|
|
100
|
+
&& !isFinitePositive(policy.maxNonResumableSpotDurationMinutes)
|
|
101
|
+
) {
|
|
102
|
+
issues.push("policy.maxNonResumableSpotDurationMinutes 必须是正数或 null");
|
|
103
|
+
}
|
|
104
|
+
for (const field of ["allowedExecutors", "allowedProfileIds"]) {
|
|
105
|
+
if (policy?.[field] !== null && policy?.[field] !== undefined && (!Array.isArray(policy[field]) || policy[field].some((item) => typeof item !== "string" || !item))) {
|
|
106
|
+
issues.push(`policy.${field} 必须是字符串数组或 null`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (policy.allowedGpuTypes !== undefined && (!Array.isArray(policy.allowedGpuTypes)
|
|
110
|
+
|| policy.allowedGpuTypes.some((type) => typeof type !== "string" || !type))) issues.push("allowedGpuTypes 必须是型号列表");
|
|
111
|
+
if (policy.maximumGpuCount !== undefined && (!Number.isInteger(policy.maximumGpuCount)
|
|
112
|
+
|| policy.maximumGpuCount < 1)) issues.push("maximumGpuCount 必须是正整数");
|
|
113
|
+
if (issues.length) throw new TypeError(`计算策略校验失败:\n- ${issues.join("\n- ")}`);
|
|
114
|
+
return policy;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isFinitePositive(value) {
|
|
118
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function isFiniteNonNegative(value) {
|
|
122
|
+
return typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
123
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
const DEFAULT_CRITICAL_INTERVAL_SECONDS = 20;
|
|
2
|
+
const DEFAULT_FULL_INTERVAL_SECONDS = 300;
|
|
3
|
+
const DEFAULT_RETENTION = 3;
|
|
4
|
+
|
|
5
|
+
export const GCP_BATCH_RETRYABLE_INFRASTRUCTURE_EXIT_CODES = Object.freeze([
|
|
6
|
+
50001, // Spot VM preemption
|
|
7
|
+
50002, // VM reporting timeout
|
|
8
|
+
50003, // VM rebooted during execution
|
|
9
|
+
50004, // VM and task became unresponsive
|
|
10
|
+
50006, // VM recreated during execution
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
const GCP_BATCH_RUNTIME_TIMEOUT_EXIT_CODE = 50005;
|
|
14
|
+
|
|
15
|
+
export function resolveCheckpointPolicy({ options = {}, profile = {} } = {}) {
|
|
16
|
+
const enabled = booleanValue(
|
|
17
|
+
options.checkpointEnabled ?? profile.checkpointEnabled,
|
|
18
|
+
true,
|
|
19
|
+
);
|
|
20
|
+
const criticalIntervalSeconds = positiveInteger(
|
|
21
|
+
options.criticalCheckpointIntervalSeconds ?? profile.criticalCheckpointIntervalSeconds,
|
|
22
|
+
DEFAULT_CRITICAL_INTERVAL_SECONDS,
|
|
23
|
+
);
|
|
24
|
+
const fullIntervalSeconds = positiveInteger(
|
|
25
|
+
options.fullCheckpointIntervalSeconds ?? profile.fullCheckpointIntervalSeconds,
|
|
26
|
+
DEFAULT_FULL_INTERVAL_SECONDS,
|
|
27
|
+
);
|
|
28
|
+
if (fullIntervalSeconds < criticalIntervalSeconds) {
|
|
29
|
+
throw new TypeError("GCP 完整检查点间隔不能短于关键检查点间隔");
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
schemaVersion: "0.1",
|
|
33
|
+
enabled,
|
|
34
|
+
criticalIntervalSeconds,
|
|
35
|
+
fullIntervalSeconds,
|
|
36
|
+
retention: positiveInteger(
|
|
37
|
+
options.checkpointRetention ?? profile.checkpointRetention,
|
|
38
|
+
DEFAULT_RETENTION,
|
|
39
|
+
),
|
|
40
|
+
restoreOnInfrastructureRetry: true,
|
|
41
|
+
reuseCompletedResult: true,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function checkpointObjectPrefix(stagingPrefix, attempt) {
|
|
46
|
+
const normalized = String(stagingPrefix ?? "citeark-run")
|
|
47
|
+
.replaceAll(/[^a-zA-Z0-9_.-]/g, "-");
|
|
48
|
+
return `${normalized}-checkpoint-a${Number(attempt)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function checkpointStatusObject(prefix) {
|
|
52
|
+
return `${prefix}-status.json`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function infrastructureResumePrompt(bundle) {
|
|
56
|
+
return [
|
|
57
|
+
`Run ID: ${bundle.runId}`,
|
|
58
|
+
"The previous cloud worker was interrupted by infrastructure, not by a scientific or protocol decision.",
|
|
59
|
+
"CiteArk restored the most recent durable checkpoint, including the Agent session, execution trace, workspace, and any evidence already produced.",
|
|
60
|
+
"Continue the same task from that checkpoint. Inspect existing files first and do not repeat completed downloads, installations, experiments, or repairs unless their outputs are missing or corrupt.",
|
|
61
|
+
"When task.json enables workspacePlan, restore the current research-plan.json and progress.md, not the initial interpretation. Check prior process identity, live state, logs and outputs before starting any replacement. Continue within the runner's remaining cumulative budget.",
|
|
62
|
+
"If /job/output/result.json and all evidence it references are already complete, verify them and finish immediately without rerunning the experiment.",
|
|
63
|
+
...coordinatorRecoveryGuidance(bundle),
|
|
64
|
+
].join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function targetRecoveryPrompt(bundle) {
|
|
68
|
+
return [
|
|
69
|
+
`Run ID: ${bundle.runId}`,
|
|
70
|
+
"This is a new operational attempt for the same immutable reproduction target.",
|
|
71
|
+
"CiteArk restored the previous Agent workspace, task environment, acquired assets, caches, audit trace, and evidence so the repair can continue instead of restarting.",
|
|
72
|
+
"Inspect the preserved failure, acquisition manifest, installed dependency state, and existing files before acting. Keep valid completed work and apply a materially different repair for the recorded blocker.",
|
|
73
|
+
"Do not reuse a failed result.json as success. When workspacePlan is enabled, continue the preserved editable plan with source-grounded revisions; otherwise preserve the original contract. Replace the result only when actual evidence justifies it, within the remaining runner budget.",
|
|
74
|
+
...coordinatorRecoveryGuidance(bundle),
|
|
75
|
+
].join("\n");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function coordinatorRecoveryGuidance(bundle) {
|
|
79
|
+
const notes = bundle.state?.recoveryGuidance;
|
|
80
|
+
if (!Array.isArray(notes)) return [];
|
|
81
|
+
const lines = notes.filter((note) => typeof note === "string" && note.trim());
|
|
82
|
+
return lines.length
|
|
83
|
+
? ["Coordinator observations from the preserved failure (verify against the restored files; the scientific contract remains unchanged):", ...lines]
|
|
84
|
+
: [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function summarizeInfrastructureAttempt(job) {
|
|
88
|
+
const starts = [];
|
|
89
|
+
const seen = new Set();
|
|
90
|
+
const failures = [];
|
|
91
|
+
for (const [index, event] of (job?.status?.statusEvents ?? []).entries()) {
|
|
92
|
+
const description = String(event?.description ?? "");
|
|
93
|
+
const isStart = /\bcontainer\b.*\bstarted\b/i.test(description)
|
|
94
|
+
|| (event?.type === "RUNNING_EVENT" && description.length === 0);
|
|
95
|
+
const eventTime = String(event?.eventTime ?? "");
|
|
96
|
+
const identity = eventTime
|
|
97
|
+
? `${eventTime}|${description}`
|
|
98
|
+
: `without-time-${index}`;
|
|
99
|
+
if (seen.has(identity)) continue;
|
|
100
|
+
seen.add(identity);
|
|
101
|
+
if (isStart) {
|
|
102
|
+
starts.push(event);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const exitCode = batchExitCode(description);
|
|
106
|
+
if (exitCode !== null) {
|
|
107
|
+
failures.push({
|
|
108
|
+
exitCode,
|
|
109
|
+
eventTime: eventTime || null,
|
|
110
|
+
description: description || null,
|
|
111
|
+
category: exitCode === GCP_BATCH_RUNTIME_TIMEOUT_EXIT_CODE
|
|
112
|
+
? "timeout"
|
|
113
|
+
: GCP_BATCH_RETRYABLE_INFRASTRUCTURE_EXIT_CODES.includes(exitCode)
|
|
114
|
+
? "infrastructure"
|
|
115
|
+
: "execution",
|
|
116
|
+
retryable: GCP_BATCH_RETRYABLE_INFRASTRUCTURE_EXIT_CODES.includes(exitCode),
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const executionAttempt = Math.max(1, starts.length || 1);
|
|
121
|
+
const resumedInfrastructureAttempts = Math.min(
|
|
122
|
+
Math.max(0, executionAttempt - 1),
|
|
123
|
+
failures.filter((failure) => failure.retryable).length,
|
|
124
|
+
);
|
|
125
|
+
const infrastructureAttempt = 1 + resumedInfrastructureAttempts;
|
|
126
|
+
return {
|
|
127
|
+
executionAttempt,
|
|
128
|
+
infrastructureAttempt,
|
|
129
|
+
resumeCount: resumedInfrastructureAttempts,
|
|
130
|
+
interruptionCount: resumedInfrastructureAttempts,
|
|
131
|
+
latestFailure: failures.at(-1) ?? null,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function batchExitCode(description) {
|
|
136
|
+
const matched = String(description ?? "").match(/\bexit code\s+(-?\d+)\b/i);
|
|
137
|
+
if (!matched) return null;
|
|
138
|
+
const exitCode = Number(matched[1]);
|
|
139
|
+
return Number.isInteger(exitCode) ? exitCode : null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function booleanValue(value, fallback) {
|
|
143
|
+
if (value === undefined || value === null) return fallback;
|
|
144
|
+
if (typeof value !== "boolean") throw new TypeError("GCP checkpointEnabled 必须是布尔值");
|
|
145
|
+
return value;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function positiveInteger(value, fallback) {
|
|
149
|
+
const numeric = value === undefined || value === null ? fallback : Number(value);
|
|
150
|
+
if (!Number.isInteger(numeric) || numeric <= 0) {
|
|
151
|
+
throw new TypeError("GCP 检查点参数必须是正整数");
|
|
152
|
+
}
|
|
153
|
+
return numeric;
|
|
154
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { CiteArkError } from "../util.mjs";
|
|
2
|
+
|
|
3
|
+
const TERMINAL_BATCH_STATES = new Set(["SUCCEEDED", "FAILED", "CANCELLED"]);
|
|
4
|
+
|
|
5
|
+
export function codexAccountLockObject(accountId) {
|
|
6
|
+
if (!/^[a-z0-9][a-z0-9-]{0,47}$/.test(accountId ?? "")) {
|
|
7
|
+
throw new TypeError("Codex 专用账号编号无效");
|
|
8
|
+
}
|
|
9
|
+
return `codex-accounts/${accountId}/active.json`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function codexAccountLockReference({ bucket, accountId, owner, jobName }) {
|
|
13
|
+
if (!accountId) return null;
|
|
14
|
+
return {
|
|
15
|
+
bucket,
|
|
16
|
+
object: codexAccountLockObject(accountId),
|
|
17
|
+
owner,
|
|
18
|
+
jobName,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function acquireCodexAccountLock({
|
|
23
|
+
storage,
|
|
24
|
+
batch,
|
|
25
|
+
bucket,
|
|
26
|
+
accountId,
|
|
27
|
+
owner,
|
|
28
|
+
jobName,
|
|
29
|
+
now = () => new Date(),
|
|
30
|
+
}) {
|
|
31
|
+
if (!accountId) return null;
|
|
32
|
+
const object = codexAccountLockObject(accountId);
|
|
33
|
+
const value = {
|
|
34
|
+
schemaVersion: "0.2",
|
|
35
|
+
accountId,
|
|
36
|
+
owner,
|
|
37
|
+
jobName,
|
|
38
|
+
createdAt: now().toISOString(),
|
|
39
|
+
};
|
|
40
|
+
let created = await storage.createJsonObjectIfAbsent({ bucket, object, value });
|
|
41
|
+
if (created) return lockReference(value, bucket, object, created.generation);
|
|
42
|
+
|
|
43
|
+
let current = await storage.readJsonObjectVersion({ bucket, object, missing: null });
|
|
44
|
+
if (!current) {
|
|
45
|
+
created = await storage.createJsonObjectIfAbsent({ bucket, object, value });
|
|
46
|
+
if (created) return lockReference(value, bucket, object, created.generation);
|
|
47
|
+
current = await storage.readJsonObjectVersion({ bucket, object, missing: null });
|
|
48
|
+
}
|
|
49
|
+
if (current?.value?.owner === owner && current.value?.jobName === jobName) {
|
|
50
|
+
return lockReference(value, bucket, object, current.generation);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const incumbentJobName = typeof current?.value?.jobName === "string"
|
|
54
|
+
? current.value.jobName
|
|
55
|
+
: null;
|
|
56
|
+
if (incumbentJobName && typeof batch?.findJob === "function") {
|
|
57
|
+
const incumbent = await batch.findJob(incumbentJobName).catch(() => undefined);
|
|
58
|
+
if (incumbent === null || TERMINAL_BATCH_STATES.has(incumbent?.status?.state)) {
|
|
59
|
+
await storage.deleteObject({
|
|
60
|
+
bucket,
|
|
61
|
+
object,
|
|
62
|
+
ifGenerationMatch: current.generation,
|
|
63
|
+
});
|
|
64
|
+
const replacement = await storage.createJsonObjectIfAbsent({ bucket, object, value });
|
|
65
|
+
if (replacement) {
|
|
66
|
+
return lockReference(value, bucket, object, replacement.generation);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw new CiteArkError("Codex 订阅账号当前正被另一项活动任务使用,请等待该任务结束", {
|
|
72
|
+
retryable: true,
|
|
73
|
+
failureCode: "codex.account_busy",
|
|
74
|
+
failureCategory: "provider",
|
|
75
|
+
retryDisposition: "wait_for_account",
|
|
76
|
+
failureOwner: "platform",
|
|
77
|
+
recoveryAction: "等待同一订阅账号的活动 Batch 结束;平台不会为账号占用启动新的计算实例",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function releaseCodexAccountLock({ storage, lock }) {
|
|
82
|
+
if (!lock) return { released: false, reason: "not-configured" };
|
|
83
|
+
const current = await storage.readJsonObjectVersion({
|
|
84
|
+
bucket: lock.bucket,
|
|
85
|
+
object: lock.object,
|
|
86
|
+
missing: null,
|
|
87
|
+
});
|
|
88
|
+
if (!current) return { released: false, reason: "absent" };
|
|
89
|
+
if (
|
|
90
|
+
current.value?.owner !== lock.owner
|
|
91
|
+
|| (lock.jobName && current.value?.jobName !== lock.jobName)
|
|
92
|
+
) {
|
|
93
|
+
return { released: false, reason: "owner-mismatch" };
|
|
94
|
+
}
|
|
95
|
+
await storage.deleteObject({
|
|
96
|
+
bucket: lock.bucket,
|
|
97
|
+
object: lock.object,
|
|
98
|
+
ifGenerationMatch: current.generation,
|
|
99
|
+
});
|
|
100
|
+
return { released: true, reason: "released" };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function lockReference(value, bucket, object, generation) {
|
|
104
|
+
return {
|
|
105
|
+
bucket,
|
|
106
|
+
object,
|
|
107
|
+
owner: value.owner,
|
|
108
|
+
jobName: value.jobName,
|
|
109
|
+
generation: String(generation),
|
|
110
|
+
};
|
|
111
|
+
}
|