@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,94 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { stat } from "node:fs/promises";
|
|
3
|
+
import { readJson, writeJson } from "../util.mjs";
|
|
4
|
+
import { launchJob, attachJob } from "./jobs.mjs";
|
|
5
|
+
|
|
6
|
+
// The terminal observes the same durable worker used by headless servers.
|
|
7
|
+
export async function interactiveResearch({
|
|
8
|
+
root,
|
|
9
|
+
options,
|
|
10
|
+
screen,
|
|
11
|
+
runtime,
|
|
12
|
+
assessmentRuntime,
|
|
13
|
+
signingKeyPath,
|
|
14
|
+
}) {
|
|
15
|
+
const env = { ...process.env };
|
|
16
|
+
const sessionPath = path.join(root, "workspace.json");
|
|
17
|
+
const session = await readJson(sessionPath);
|
|
18
|
+
for (const [agent, field, fallback] of [
|
|
19
|
+
[runtime, "modelConfiguration", "CITEARK_SESSION_MODEL_KEY"],
|
|
20
|
+
[
|
|
21
|
+
assessmentRuntime,
|
|
22
|
+
"assessmentConfiguration",
|
|
23
|
+
"CITEARK_SESSION_ASSESSMENT_KEY",
|
|
24
|
+
],
|
|
25
|
+
]) {
|
|
26
|
+
if (!agent.apiKey || !session[field]) continue;
|
|
27
|
+
const key = session[field].apiKeyEnv ?? fallback;
|
|
28
|
+
env[key] = agent.apiKey;
|
|
29
|
+
session[field].apiKeyEnv = key;
|
|
30
|
+
}
|
|
31
|
+
await writeJson(sessionPath, session);
|
|
32
|
+
await screen.stopRecording();
|
|
33
|
+
let cursor = (await stat(path.join(root, "session.events.jsonl"))).size;
|
|
34
|
+
const retained = Object.entries(options).filter(
|
|
35
|
+
([key]) =>
|
|
36
|
+
key.startsWith("--gcp-") ||
|
|
37
|
+
key.startsWith("--autodl-") ||
|
|
38
|
+
key === "--allow-remote-model-key",
|
|
39
|
+
);
|
|
40
|
+
const args = [
|
|
41
|
+
"--resume",
|
|
42
|
+
"--work-dir",
|
|
43
|
+
root,
|
|
44
|
+
"--signing-key",
|
|
45
|
+
signingKeyPath,
|
|
46
|
+
...retained.flatMap(([key, value]) =>
|
|
47
|
+
value === undefined || value === false
|
|
48
|
+
? []
|
|
49
|
+
: value === true
|
|
50
|
+
? [key]
|
|
51
|
+
: [key, String(value)],
|
|
52
|
+
),
|
|
53
|
+
];
|
|
54
|
+
let experiments = options["--experiments"];
|
|
55
|
+
while (true) {
|
|
56
|
+
await launchJob(
|
|
57
|
+
[...args, ...(experiments ? ["--experiments", experiments] : [])],
|
|
58
|
+
{ env },
|
|
59
|
+
);
|
|
60
|
+
const state = await attachJob(root, { output: screen, cursor });
|
|
61
|
+
cursor = state.cursor;
|
|
62
|
+
if (state.detached) {
|
|
63
|
+
await screen.close();
|
|
64
|
+
console.log(`Reconnect: citeark session attach ${JSON.stringify(root)}`);
|
|
65
|
+
return { phase: "detached" };
|
|
66
|
+
}
|
|
67
|
+
if (
|
|
68
|
+
state.status === "failed" ||
|
|
69
|
+
state.status === "cancelled" ||
|
|
70
|
+
state.status === "interrupted"
|
|
71
|
+
)
|
|
72
|
+
throw Error(state.error ?? `Session ${state.status}`);
|
|
73
|
+
if (state.status !== "awaiting_selection" || options["--plan-only"])
|
|
74
|
+
return readJson(sessionPath);
|
|
75
|
+
for (const [i, experiment] of state.experiments.entries())
|
|
76
|
+
screen.event({
|
|
77
|
+
kind: "status",
|
|
78
|
+
text: `${i + 1}. ${experiment.id} — ${experiment.title}`,
|
|
79
|
+
});
|
|
80
|
+
const answer = await screen.ask(
|
|
81
|
+
"Select experiment numbers (comma-separated; leave empty to save the plan)",
|
|
82
|
+
);
|
|
83
|
+
if (!answer) return readJson(sessionPath);
|
|
84
|
+
experiments = answer
|
|
85
|
+
.split(",")
|
|
86
|
+
.map(
|
|
87
|
+
(value) =>
|
|
88
|
+
state.experiments[Number(value.trim()) - 1]?.id ?? value.trim(),
|
|
89
|
+
)
|
|
90
|
+
.join(",");
|
|
91
|
+
if (!(await screen.confirm("Run the selected experiments?")))
|
|
92
|
+
return readJson(sessionPath);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import {
|
|
3
|
+
open,
|
|
4
|
+
mkdir,
|
|
5
|
+
readFile,
|
|
6
|
+
writeFile,
|
|
7
|
+
rename,
|
|
8
|
+
readdir,
|
|
9
|
+
rm,
|
|
10
|
+
appendFile,
|
|
11
|
+
} from "node:fs/promises";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { settingsPath } from "../settings/store.mjs";
|
|
15
|
+
|
|
16
|
+
export const jobDirectory = (root) =>
|
|
17
|
+
path.join(path.resolve(root), ".citeark-job");
|
|
18
|
+
export async function jobJson(root, name, fallback = null) {
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(
|
|
21
|
+
await readFile(path.join(jobDirectory(root), name), "utf8"),
|
|
22
|
+
);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
if (error.code === "ENOENT") return fallback;
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export async function saveJobJson(root, name, value) {
|
|
29
|
+
const file = path.join(jobDirectory(root), name);
|
|
30
|
+
await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
31
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
32
|
+
await writeFile(temp, JSON.stringify(value) + "\n", { mode: 0o600 });
|
|
33
|
+
await rename(temp, file);
|
|
34
|
+
}
|
|
35
|
+
export function alive(pid) {
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
return true;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return e.code === "EPERM";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const terminal = new Set([
|
|
44
|
+
"completed",
|
|
45
|
+
"failed",
|
|
46
|
+
"cancelled",
|
|
47
|
+
"awaiting_selection",
|
|
48
|
+
"prepared",
|
|
49
|
+
]);
|
|
50
|
+
export async function inspectJob(root, cursor = 0) {
|
|
51
|
+
let state = await jobJson(root, "state.json");
|
|
52
|
+
if (!state) throw Error(`No managed research session at ${root}`);
|
|
53
|
+
if (!terminal.has(state.status) && !alive(state.pid))
|
|
54
|
+
state = { ...state, status: "interrupted" };
|
|
55
|
+
const file = path.join(root, "session.events.jsonl");
|
|
56
|
+
let events = [],
|
|
57
|
+
nextCursor = cursor;
|
|
58
|
+
try {
|
|
59
|
+
const handle = await open(file, "r");
|
|
60
|
+
try {
|
|
61
|
+
const chunks = [];
|
|
62
|
+
let bytes = 0,
|
|
63
|
+
complete = -1;
|
|
64
|
+
while (complete < 0) {
|
|
65
|
+
const chunk = Buffer.alloc(512 * 1024);
|
|
66
|
+
const { bytesRead } = await handle.read(
|
|
67
|
+
chunk,
|
|
68
|
+
0,
|
|
69
|
+
chunk.length,
|
|
70
|
+
cursor + bytes,
|
|
71
|
+
);
|
|
72
|
+
if (!bytesRead) break;
|
|
73
|
+
chunks.push(chunk.subarray(0, bytesRead));
|
|
74
|
+
const newline = chunk.subarray(0, bytesRead).lastIndexOf(10);
|
|
75
|
+
if (newline >= 0) complete = bytes + newline;
|
|
76
|
+
bytes += bytesRead;
|
|
77
|
+
if (bytesRead < chunk.length) break;
|
|
78
|
+
}
|
|
79
|
+
const buffer = Buffer.concat(chunks);
|
|
80
|
+
if (complete >= 0) {
|
|
81
|
+
events = buffer
|
|
82
|
+
.subarray(0, complete)
|
|
83
|
+
.toString("utf8")
|
|
84
|
+
.split("\n")
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.map(JSON.parse);
|
|
87
|
+
nextCursor += complete + 1;
|
|
88
|
+
}
|
|
89
|
+
} finally {
|
|
90
|
+
await handle.close();
|
|
91
|
+
}
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (error.code !== "ENOENT") throw error;
|
|
94
|
+
}
|
|
95
|
+
let workspace;
|
|
96
|
+
try {
|
|
97
|
+
workspace = JSON.parse(
|
|
98
|
+
await readFile(path.join(root, "workspace.json"), "utf8"),
|
|
99
|
+
);
|
|
100
|
+
} catch (error) {
|
|
101
|
+
if (error.code !== "ENOENT") throw error;
|
|
102
|
+
}
|
|
103
|
+
let experiments;
|
|
104
|
+
if (state.status === "awaiting_selection" && workspace?.researchPath)
|
|
105
|
+
experiments = JSON.parse(
|
|
106
|
+
await readFile(workspace.researchPath, "utf8"),
|
|
107
|
+
).experiments.map(({ id, title }) => ({ id, title }));
|
|
108
|
+
return {
|
|
109
|
+
...state,
|
|
110
|
+
directory: root,
|
|
111
|
+
cursor: nextCursor,
|
|
112
|
+
events,
|
|
113
|
+
experiments,
|
|
114
|
+
artifacts: workspace?.artifacts ?? [],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function launchJob(
|
|
119
|
+
argv,
|
|
120
|
+
{ cwd = process.cwd(), env = process.env } = {},
|
|
121
|
+
) {
|
|
122
|
+
if (argv.includes("--api-key"))
|
|
123
|
+
throw Error(
|
|
124
|
+
"Background research uses a model profile or --api-key-env. Credentials must not appear in command arguments.",
|
|
125
|
+
);
|
|
126
|
+
const args = argv.filter((a) => a !== "--background");
|
|
127
|
+
const pathOptions = [
|
|
128
|
+
"--paper",
|
|
129
|
+
"--cap",
|
|
130
|
+
"--repository",
|
|
131
|
+
"--guide",
|
|
132
|
+
"--compute-catalog",
|
|
133
|
+
"--compute-policy",
|
|
134
|
+
"--signing-key",
|
|
135
|
+
"--work-dir",
|
|
136
|
+
];
|
|
137
|
+
for (const option of pathOptions) {
|
|
138
|
+
const i = args.indexOf(option);
|
|
139
|
+
if (i >= 0 && args[i + 1] && !/^https?:\/\//.test(args[i + 1]))
|
|
140
|
+
args[i + 1] = path.resolve(cwd, args[i + 1]);
|
|
141
|
+
}
|
|
142
|
+
const i = args.indexOf("--work-dir");
|
|
143
|
+
const root =
|
|
144
|
+
i >= 0 ? args[i + 1] : path.resolve(cwd, `citeark-research-${Date.now()}`);
|
|
145
|
+
if (i < 0) args.push("--work-dir", root);
|
|
146
|
+
const previous = await jobJson(root, "state.json");
|
|
147
|
+
if (previous && !terminal.has(previous.status) && alive(previous.pid))
|
|
148
|
+
throw Error(
|
|
149
|
+
`Session is ${previous.status}. Use citeark session attach ${JSON.stringify(root)}.`,
|
|
150
|
+
);
|
|
151
|
+
if (!args.includes("--resume")) {
|
|
152
|
+
try {
|
|
153
|
+
await readFile(path.join(root, "workspace.json"));
|
|
154
|
+
throw Error("Workspace exists. Use --resume to continue it.");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error.code !== "ENOENT") throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
await mkdir(jobDirectory(root), { recursive: true, mode: 0o700 });
|
|
160
|
+
const lock = path.join(jobDirectory(root), "starting");
|
|
161
|
+
const owner = await jobJson(root, "owner.json");
|
|
162
|
+
if (owner && !alive(owner.pid))
|
|
163
|
+
await rm(lock, { recursive: true, force: true });
|
|
164
|
+
let spawned = false;
|
|
165
|
+
try {
|
|
166
|
+
await mkdir(lock);
|
|
167
|
+
} catch (error) {
|
|
168
|
+
if (error.code === "EEXIST")
|
|
169
|
+
throw Error("Another process is starting this session.");
|
|
170
|
+
throw error;
|
|
171
|
+
}
|
|
172
|
+
try {
|
|
173
|
+
if (!args.includes("--non-interactive")) args.push("--non-interactive");
|
|
174
|
+
if (!args.includes("--plain")) args.push("--plain");
|
|
175
|
+
await saveJobJson(root, "request.json", { argv: args, cwd });
|
|
176
|
+
await saveJobJson(root, "control.json", { action: "run" });
|
|
177
|
+
const log = await open(
|
|
178
|
+
path.join(jobDirectory(root), "worker.log"),
|
|
179
|
+
"a",
|
|
180
|
+
0o600,
|
|
181
|
+
);
|
|
182
|
+
let child;
|
|
183
|
+
try {
|
|
184
|
+
child = spawn(
|
|
185
|
+
process.execPath,
|
|
186
|
+
[fileURLToPath(new URL("./worker.mjs", import.meta.url)), root],
|
|
187
|
+
{
|
|
188
|
+
cwd,
|
|
189
|
+
env: { ...env, CITEARK_JOB_WORKER: "1" },
|
|
190
|
+
detached: true,
|
|
191
|
+
stdio: ["ignore", log.fd, log.fd],
|
|
192
|
+
},
|
|
193
|
+
);
|
|
194
|
+
await new Promise((resolve, reject) => {
|
|
195
|
+
child.once("spawn", resolve);
|
|
196
|
+
child.once("error", reject);
|
|
197
|
+
});
|
|
198
|
+
} finally {
|
|
199
|
+
await log.close();
|
|
200
|
+
}
|
|
201
|
+
await saveJobJson(root, "owner.json", { pid: child.pid });
|
|
202
|
+
spawned = true;
|
|
203
|
+
child.unref();
|
|
204
|
+
for (let attempt = 0; attempt < 50; attempt++) {
|
|
205
|
+
if ((await jobJson(root, "state.json"))?.pid === child.pid) break;
|
|
206
|
+
if (!alive(child.pid))
|
|
207
|
+
throw Error(
|
|
208
|
+
"Research worker failed to start. See .citeark-job/worker.log.",
|
|
209
|
+
);
|
|
210
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
211
|
+
}
|
|
212
|
+
const index = path.join(path.dirname(settingsPath(env)), "sessions");
|
|
213
|
+
await mkdir(index, { recursive: true, mode: 0o700 });
|
|
214
|
+
const { createHash } = await import("node:crypto");
|
|
215
|
+
await writeFile(
|
|
216
|
+
path.join(
|
|
217
|
+
index,
|
|
218
|
+
`${createHash("sha256").update(root).digest("hex")}.json`,
|
|
219
|
+
),
|
|
220
|
+
JSON.stringify({ directory: root }),
|
|
221
|
+
{ mode: 0o600 },
|
|
222
|
+
);
|
|
223
|
+
return { pid: child.pid, directory: root };
|
|
224
|
+
} finally {
|
|
225
|
+
if (!spawned) await rm(lock, { recursive: true, force: true });
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function sessionCommand(argv, parseOptions) {
|
|
230
|
+
const { takeOption, remoteSession } = await import("../remote/command.mjs");
|
|
231
|
+
const compute = takeOption(argv, "--compute");
|
|
232
|
+
if (compute) return remoteSession(argv, compute);
|
|
233
|
+
const [action = "list", directory, ...rest] = argv;
|
|
234
|
+
if (action === "list") {
|
|
235
|
+
let files;
|
|
236
|
+
const index = path.join(path.dirname(settingsPath()), "sessions");
|
|
237
|
+
try {
|
|
238
|
+
files = await readdir(index);
|
|
239
|
+
} catch (error) {
|
|
240
|
+
if (error.code !== "ENOENT") throw error;
|
|
241
|
+
files = [];
|
|
242
|
+
}
|
|
243
|
+
for (const file of files.filter((f) => f.endsWith(".json"))) {
|
|
244
|
+
const entry = JSON.parse(await readFile(path.join(index, file), "utf8"));
|
|
245
|
+
try {
|
|
246
|
+
const s = await inspectJob(entry.directory);
|
|
247
|
+
console.log(`${s.status} ${entry.directory}`);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (error.code !== "ENOENT")
|
|
250
|
+
console.log(`unavailable ${entry.directory}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (!directory)
|
|
256
|
+
throw Error(
|
|
257
|
+
"Usage: citeark session status|attach|pause|resume|cancel <workspace>",
|
|
258
|
+
);
|
|
259
|
+
const root = path.resolve(directory);
|
|
260
|
+
if (action === "status" || action === "inspect") {
|
|
261
|
+
const options = parseOptions(rest, {
|
|
262
|
+
"--cursor": { type: "string", default: "0" },
|
|
263
|
+
});
|
|
264
|
+
const cursor = Number(options["--cursor"]);
|
|
265
|
+
if (!Number.isSafeInteger(cursor) || cursor < 0)
|
|
266
|
+
throw Error("Invalid transcript cursor.");
|
|
267
|
+
const value = await inspectJob(root, cursor);
|
|
268
|
+
if (action === "status") {
|
|
269
|
+
delete value.events;
|
|
270
|
+
delete value.cursor;
|
|
271
|
+
}
|
|
272
|
+
console.log(JSON.stringify(value));
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
if (action === "attach") return attachJob(root);
|
|
276
|
+
if (action === "note") {
|
|
277
|
+
const options = parseOptions(rest, {
|
|
278
|
+
"--message": { type: "string", required: true },
|
|
279
|
+
});
|
|
280
|
+
await controlJob(root, options["--message"]);
|
|
281
|
+
console.log("Instruction queued for the next Agent invocation.");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const state = await inspectJob(root);
|
|
285
|
+
if (!["cancel", "pause", "resume"].includes(action))
|
|
286
|
+
throw Error(`Unknown session operation: ${action}`);
|
|
287
|
+
if (terminal.has(state.status) || state.status === "interrupted")
|
|
288
|
+
throw Error(
|
|
289
|
+
`Session is ${state.status}. Use citeark start --resume --work-dir ${JSON.stringify(root)} to continue.`,
|
|
290
|
+
);
|
|
291
|
+
await saveJobJson(root, "control.json", {
|
|
292
|
+
action: action === "resume" ? "run" : action,
|
|
293
|
+
requestedAt: new Date().toISOString(),
|
|
294
|
+
});
|
|
295
|
+
console.log(
|
|
296
|
+
action === "pause"
|
|
297
|
+
? "Pause requested; it takes effect at the next stage boundary."
|
|
298
|
+
: `${action === "cancel" ? "Cancellation" : "Resume"} requested. Use session status to confirm.`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export async function controlJob(root, text) {
|
|
303
|
+
const state = await inspectJob(root);
|
|
304
|
+
if (terminal.has(state.status) || state.status === "interrupted")
|
|
305
|
+
throw Error(
|
|
306
|
+
`Session is ${state.status}; resume it before sending instructions.`,
|
|
307
|
+
);
|
|
308
|
+
if (["/pause", "/resume", "/cancel"].includes(text)) {
|
|
309
|
+
await saveJobJson(root, "control.json", {
|
|
310
|
+
action: text === "/resume" ? "run" : text.slice(1),
|
|
311
|
+
requestedAt: new Date().toISOString(),
|
|
312
|
+
});
|
|
313
|
+
} else {
|
|
314
|
+
if (!text.trim()) return;
|
|
315
|
+
await appendFile(
|
|
316
|
+
path.join(jobDirectory(root), "notes.jsonl"),
|
|
317
|
+
JSON.stringify({ at: new Date().toISOString(), text }) + "\n",
|
|
318
|
+
{ mode: 0o600 },
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function attachJob(
|
|
324
|
+
root,
|
|
325
|
+
{
|
|
326
|
+
inspect = inspectJob,
|
|
327
|
+
control = controlJob,
|
|
328
|
+
output,
|
|
329
|
+
cursor: initialCursor = 0,
|
|
330
|
+
} = {},
|
|
331
|
+
) {
|
|
332
|
+
const { PlainSession } = await import("../terminal/plain.mjs");
|
|
333
|
+
const { TerminalScreen } = await import("../terminal/screen.mjs");
|
|
334
|
+
const interactive =
|
|
335
|
+
!output &&
|
|
336
|
+
process.stdin.isTTY &&
|
|
337
|
+
process.stdout.isTTY &&
|
|
338
|
+
process.env.TERM !== "dumb";
|
|
339
|
+
let detached = false;
|
|
340
|
+
const sink =
|
|
341
|
+
output ??
|
|
342
|
+
(interactive
|
|
343
|
+
? new TerminalScreen({
|
|
344
|
+
onExit: () => {
|
|
345
|
+
detached = true;
|
|
346
|
+
},
|
|
347
|
+
onInput: async (text) => {
|
|
348
|
+
await control(root, text);
|
|
349
|
+
sink.event({
|
|
350
|
+
kind: "status",
|
|
351
|
+
text: text.startsWith("/")
|
|
352
|
+
? "Control request sent."
|
|
353
|
+
: "Instruction queued for the next Agent invocation.",
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
})
|
|
357
|
+
: new PlainSession());
|
|
358
|
+
const previousExit = sink.onExit,
|
|
359
|
+
previousInput = sink.onInput;
|
|
360
|
+
if (output) {
|
|
361
|
+
sink.onExit = () => {
|
|
362
|
+
detached = true;
|
|
363
|
+
};
|
|
364
|
+
sink.onInput = async (text) => {
|
|
365
|
+
await control(root, text);
|
|
366
|
+
sink.event({
|
|
367
|
+
kind: "status",
|
|
368
|
+
text: text.startsWith("/")
|
|
369
|
+
? "Control request sent."
|
|
370
|
+
: "Instruction queued for the next Agent invocation.",
|
|
371
|
+
});
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
if (interactive) {
|
|
375
|
+
sink.context = root;
|
|
376
|
+
sink.start();
|
|
377
|
+
}
|
|
378
|
+
const disconnect = () => {
|
|
379
|
+
detached = true;
|
|
380
|
+
};
|
|
381
|
+
if (!interactive) process.once("SIGINT", disconnect);
|
|
382
|
+
let cursor = initialCursor,
|
|
383
|
+
state;
|
|
384
|
+
try {
|
|
385
|
+
while (!detached) {
|
|
386
|
+
state = await inspect(root, cursor);
|
|
387
|
+
for (const event of state.events) sink.event(event);
|
|
388
|
+
const progressed = state.cursor !== cursor;
|
|
389
|
+
cursor = state.cursor;
|
|
390
|
+
if (
|
|
391
|
+
(terminal.has(state.status) || state.status === "interrupted") &&
|
|
392
|
+
!progressed
|
|
393
|
+
)
|
|
394
|
+
break;
|
|
395
|
+
if (!progressed)
|
|
396
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
397
|
+
}
|
|
398
|
+
if (!detached && interactive) await sink.finish(`Session ${state.status}`);
|
|
399
|
+
} finally {
|
|
400
|
+
if (!interactive) process.removeListener("SIGINT", disconnect);
|
|
401
|
+
if (!output) await sink.close();
|
|
402
|
+
else {
|
|
403
|
+
sink.onExit = previousExit;
|
|
404
|
+
sink.onInput = previousInput;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (state?.resourceCleanup?.some((item) => item.status === "failed"))
|
|
408
|
+
console.log(
|
|
409
|
+
"Resource cleanup needs attention. Use session status to inspect the resource IDs before resuming.",
|
|
410
|
+
);
|
|
411
|
+
console.log(
|
|
412
|
+
`Session ${detached ? "detached; research continues" : state.status}: ${root}`,
|
|
413
|
+
);
|
|
414
|
+
if (state?.experiments)
|
|
415
|
+
for (const { id, title } of state.experiments)
|
|
416
|
+
console.log(`${id} ${title}`);
|
|
417
|
+
return { ...state, detached, cursor };
|
|
418
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { reproductionCampaign } from '../compute/campaign.mjs';
|
|
2
|
+
import { generateKeyPairSync } from 'node:crypto';
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { executeFullPipeline } from '../pipeline/orchestrator.mjs';
|
|
7
|
+
import { projectSharedExecutionClaim } from '../pipeline/shared-execution.mjs';
|
|
8
|
+
import { resolveReproductionTargets } from '../pipeline/targets.mjs';
|
|
9
|
+
import { createScientificAssessor } from '../verification/ai-assessor.mjs';
|
|
10
|
+
import { readJson, writeJson, pathExists } from '../util.mjs';
|
|
11
|
+
|
|
12
|
+
export async function ensureSigningKey(filename = path.join(process.env.CITEARK_CONFIG_DIR ?? path.join(homedir(), '.citeark'), 'signing-key.pem')) {
|
|
13
|
+
if (!await pathExists(filename)) {
|
|
14
|
+
await mkdir(path.dirname(filename), { recursive: true, mode: 0o700 });
|
|
15
|
+
const privateKey = generateKeyPairSync('ed25519').privateKey.export({ type: 'pkcs8', format: 'pem' });
|
|
16
|
+
await writeFile(filename, privateKey, { flag: 'wx', mode: 0o600 });
|
|
17
|
+
}
|
|
18
|
+
return filename;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function selectResearchTargets(research, experimentIds) {
|
|
22
|
+
if (!experimentIds?.length) throw Error('Select experiments to run, or use --plan-only to save the plan.');
|
|
23
|
+
const selected = [...new Set(experimentIds)];
|
|
24
|
+
const targets = selected.flatMap(experimentId => {
|
|
25
|
+
const experiment = research.experiments.find(item => item.id === experimentId);
|
|
26
|
+
if (!experiment) throw Error(`Unknown experiment: ${experimentId}`);
|
|
27
|
+
const matches = resolveReproductionTargets(research, { experimentId });
|
|
28
|
+
if (!matches.length) throw Error(`Experiment ${experimentId} has no verifiable measurement targets.`);
|
|
29
|
+
return matches;
|
|
30
|
+
});
|
|
31
|
+
return targets;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function planWorkspace({ directory, input, agentRuntime, computeCatalogPath, computePolicyPath,
|
|
35
|
+
computeProfile, executorOptions, signingKeyPath, dryRun = false, progress = message => console.log(message) },
|
|
36
|
+
{ pipeline = executeFullPipeline } = {}) {
|
|
37
|
+
executorOptions = { ...executorOptions, gcpBatch: { onCheckpoint: async () => {}, ...executorOptions?.gcpBatch }, autoDl: { onCheckpoint: async () => {}, ...executorOptions?.autoDl } };
|
|
38
|
+
const root = path.resolve(directory);
|
|
39
|
+
const sessionPath = path.join(root, 'workspace.json');
|
|
40
|
+
const session = await pathExists(sessionPath) ? await readJson(sessionPath) : {
|
|
41
|
+
schemaVersion: '1.0', input, computeCatalogPath, computePolicyPath, computeProfile,
|
|
42
|
+
phase: 'planning', artifacts: [] };
|
|
43
|
+
await writeJson(sessionPath, session);
|
|
44
|
+
if (session.researchPath && !dryRun) return { session, research: await readJson(session.researchPath) };
|
|
45
|
+
const planningDirectory = path.join(root, 'planning');
|
|
46
|
+
const plan = await pipeline({ compileTaskPath: session.input.compileTaskPath, workDirectory: planningDirectory,
|
|
47
|
+
stopAfter: 'research_plan', agentRuntime, computeCatalogPath, computePolicyPath, computeProfile, executorOptions,
|
|
48
|
+
signingKeyPath, sourceArtifactDigests: session.input.sourceArtifactDigests,
|
|
49
|
+
sourceCompilationDigests: session.input.sourceCompilationDigests,
|
|
50
|
+
dryRun, progress, resume: await pathExists(path.join(planningDirectory, 'pipeline.json')) });
|
|
51
|
+
if (dryRun) return { dryRun: true, session, plan };
|
|
52
|
+
session.researchPath = path.join(planningDirectory, 'research.normalized.json');
|
|
53
|
+
session.planningArtifact = { path: plan.researchPlanArchive.path, digest: plan.researchPlan.artifactDigest };
|
|
54
|
+
session.phase = 'awaiting_selection';
|
|
55
|
+
session.artifacts = plan.researchPlanArchive ? [plan.researchPlanArchive.path] : [];
|
|
56
|
+
await writeJson(sessionPath, session);
|
|
57
|
+
return { session, research: plan.research };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function executeWorkspace({ directory, experimentIds, agentRuntime, assessmentRuntime = agentRuntime, executorOptions, signingKeyPath,
|
|
61
|
+
progress = message => console.log(message) }, { pipeline = executeFullPipeline, project = projectSharedExecutionClaim } = {}) {
|
|
62
|
+
executorOptions = { ...executorOptions, gcpBatch: { onCheckpoint: async () => {}, ...executorOptions?.gcpBatch }, autoDl: { onCheckpoint: async () => {}, ...executorOptions?.autoDl } };
|
|
63
|
+
const root = path.resolve(directory);
|
|
64
|
+
const sessionPath = path.join(root, 'workspace.json');
|
|
65
|
+
const session = await readJson(sessionPath);
|
|
66
|
+
const research = await readJson(session.researchPath);
|
|
67
|
+
if (session.phase === 'completed') return session;
|
|
68
|
+
const targets = reproductionCampaign(research, session.targets ?? selectResearchTargets(research, experimentIds)).targets;
|
|
69
|
+
if (session.targets && experimentIds?.length &&
|
|
70
|
+
JSON.stringify([...new Set(session.targets.map(t => t.experimentId))].sort()) !== JSON.stringify([...new Set(experimentIds)].sort())) {
|
|
71
|
+
throw Error('Resuming preserves the selected experiments. Create a new workspace for a different selection.');
|
|
72
|
+
}
|
|
73
|
+
session.targets = targets;
|
|
74
|
+
session.phase = 'executing';
|
|
75
|
+
await writeJson(sessionPath, session);
|
|
76
|
+
const executionDirectory = path.join(root, 'execution');
|
|
77
|
+
const assessor = createScientificAssessor({ runtimeAgent: assessmentRuntime, providerSecret: executorOptions?.gcpBatch?.providerSecret });
|
|
78
|
+
const first = await pipeline({ researchPath: session.researchPath, workDirectory: executionDirectory,
|
|
79
|
+
executionTargets: targets, claimId: targets[0].claimId, experimentId: targets[0].experimentId,
|
|
80
|
+
agentRuntime, executorOptions, signingKeyPath, scientificAssessor: assessor,
|
|
81
|
+
computeCatalogPath: session.computeCatalogPath, computePolicyPath: session.computePolicyPath,
|
|
82
|
+
computeProfile: session.computeProfile, referenceMaterialsPath: session.input.referenceMaterialsPath,
|
|
83
|
+
sourceArtifactDigests: [...new Set([...(session.input.sourceArtifactDigests ?? []), session.planningArtifact.digest])],
|
|
84
|
+
sourceCompilationDigests: session.input.sourceCompilationDigests,
|
|
85
|
+
resume: await pathExists(path.join(executionDirectory, 'pipeline.json')), progress });
|
|
86
|
+
const artifacts = [session.planningArtifact.path, first.researchPlanArchive.path, first.archive.path];
|
|
87
|
+
for (const [index, target] of targets.slice(1).entries()) {
|
|
88
|
+
const result = await project({ sourcePipeline: first, researchPath: session.researchPath,
|
|
89
|
+
...target, workDirectory: path.join(root, `result-${index + 2}`), signingKeyPath,
|
|
90
|
+
researchArtifactDigest: first.researchPlan.artifactDigest, scientificAssessor: assessor });
|
|
91
|
+
artifacts.push(result.archive.path);
|
|
92
|
+
}
|
|
93
|
+
session.phase = 'completed';
|
|
94
|
+
session.artifacts = artifacts;
|
|
95
|
+
await writeJson(sessionPath, session);
|
|
96
|
+
return session;
|
|
97
|
+
}
|