@hassangameryt/freeflow 0.8.0 → 0.9.0
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/.agents/plugins/marketplace.json +2 -2
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -2
- package/.codex-plugin/plugin.json +3 -3
- package/.cursor-plugin/plugin.json +1 -1
- package/README.md +96 -149
- package/capabilities/cognitive-routing/SKILL.md +21 -129
- package/capabilities/cognitive-routing/references/coordinator.md +163 -0
- package/capabilities/cognitive-routing/references/worker.md +38 -0
- package/capabilities/compaction/SKILL.md +3 -3
- package/capabilities/compaction/references/summary-format.md +26 -15
- package/capabilities/messaging/SKILL.md +49 -0
- package/capabilities/task-memory/SKILL.md +87 -0
- package/command-surface.json +6 -54
- package/gemini-extension.json +1 -1
- package/hooks/claude/hooks.json +13 -3
- package/hooks/codex/hooks.json +2 -1
- package/hooks/shared/runtime-context.mjs +69 -77
- package/package.json +25 -8
- package/pi-extension/dist/ask-user/ask.js +108 -0
- package/pi-extension/dist/ask-user/form.js +250 -0
- package/pi-extension/dist/ask-user/questions.js +66 -0
- package/pi-extension/dist/ask-user/tool.js +124 -0
- package/pi-extension/dist/cognitive-routing/assembler.js +35 -11
- package/pi-extension/dist/cognitive-routing/bootstrap.js +90 -0
- package/pi-extension/dist/cognitive-routing/config.js +3 -4
- package/pi-extension/dist/cognitive-routing/event-store.js +4 -1
- package/pi-extension/dist/cognitive-routing/gate.js +20 -5
- package/pi-extension/dist/cognitive-routing/handlers.js +15 -13
- package/pi-extension/dist/cognitive-routing/model-control.js +17 -8
- package/pi-extension/dist/cognitive-routing/projection.js +23 -6
- package/pi-extension/dist/cognitive-routing/provenance.js +3 -1
- package/pi-extension/dist/cognitive-routing/render.js +15 -15
- package/pi-extension/dist/cognitive-routing/runtime.js +4 -2
- package/pi-extension/dist/cognitive-routing/schemas.js +4 -4
- package/pi-extension/dist/cognitive-routing/session.js +3 -1
- package/pi-extension/dist/cognitive-routing/sources.js +29 -4
- package/pi-extension/dist/cognitive-routing/state.js +18 -8
- package/pi-extension/dist/cognitive-routing/status.js +4 -3
- package/pi-extension/dist/cognitive-routing/tools.js +29 -15
- package/pi-extension/dist/cognitive-routing/types.js +10 -1
- package/pi-extension/dist/compaction/carry.js +36 -22
- package/pi-extension/dist/compaction/config.js +4 -2
- package/pi-extension/dist/compaction/controller.js +52 -29
- package/pi-extension/dist/compaction/harness.js +19 -3
- package/pi-extension/dist/compaction/results.js +14 -13
- package/pi-extension/dist/compaction/tool.js +6 -6
- package/pi-extension/dist/host/catalog.js +20 -35
- package/pi-extension/dist/host/config-validation.js +233 -0
- package/pi-extension/dist/host/config.js +78 -91
- package/pi-extension/dist/host/inbound.js +142 -0
- package/pi-extension/dist/host/projection-tags.js +1 -1
- package/pi-extension/dist/host/prompts.js +24 -13
- package/pi-extension/dist/host/read-only-session.js +6 -1
- package/pi-extension/dist/host/request-history.js +7 -3
- package/pi-extension/dist/host/rows.js +25 -0
- package/pi-extension/dist/host/runtime-state.js +17 -2
- package/pi-extension/dist/host/section-keeper.js +85 -0
- package/pi-extension/dist/host/settings/freeflow-command.js +846 -93
- package/pi-extension/dist/host/settings/settings-view.js +27 -8
- package/pi-extension/dist/host/status.js +19 -5
- package/pi-extension/dist/index.js +638 -72
- package/pi-extension/dist/messaging/config.js +27 -0
- package/pi-extension/dist/messaging/messaging.js +398 -0
- package/pi-extension/dist/messaging/registry.js +57 -0
- package/pi-extension/dist/messaging/render.js +90 -0
- package/pi-extension/dist/messaging/tools.js +109 -0
- package/pi-extension/dist/messaging/transport.js +101 -0
- package/pi-extension/dist/provider-support/cache/keepalive.js +16 -4
- package/pi-extension/dist/task-memory/config.js +49 -0
- package/pi-extension/dist/task-memory/registry.js +119 -0
- package/pi-extension/dist/task-memory/render.js +107 -0
- package/pi-extension/dist/task-memory/runtime.js +994 -0
- package/pi-extension/dist/task-memory/tool.js +233 -0
- package/pi-extension/dist/tool-execution/apply-patch/tool.js +1 -1
- package/pi-extension/dist/tool-execution/background.js +220 -28
- package/pi-extension/dist/tool-execution/config.js +14 -3
- package/pi-extension/dist/tool-execution/file-tracking.js +24 -4
- package/pi-extension/dist/tool-execution/guidance-notices.js +124 -0
- package/pi-extension/dist/tool-execution/skill-reads.js +143 -0
- package/pi-extension/dist/tool-execution/tools.js +2 -2
- package/runtime/prompts/cognitive-routing.md +4 -4
- package/runtime/prompts/core.md +24 -14
- package/runtime/prompts/interaction-contract.md +7 -4
- package/runtime/prompts/tool-execution.md +3 -16
- package/runtime/prompts/working-method.md +16 -99
- package/skills/deliver-work/SKILL.md +76 -0
- package/skills/deliver-work/references/branch-closeout.md +134 -0
- package/skills/deliver-work/references/commit.md +83 -0
- package/skills/deliver-work/references/launch.md +94 -0
- package/skills/deliver-work/references/release.md +85 -0
- package/skills/deliver-work/scripts/commit-state.mjs +139 -0
- package/skills/deliver-work/scripts/delivery-state.mjs +107 -0
- package/skills/deliver-work/scripts/lib/git-state.mjs +178 -0
- package/skills/deliver-work/scripts/push-route.mjs +122 -0
- package/skills/deliver-work/scripts/release-preflight.mjs +177 -0
- package/skills/discuss-work/SKILL.md +148 -0
- package/skills/{discuss → discuss-work}/references/discussion-continuity.md +10 -27
- package/skills/discuss-work/references/learning-action.md +36 -0
- package/skills/discuss-work/scripts/choices.mjs +101 -0
- package/skills/discuss-work/scripts/lib/choices.mjs +159 -0
- package/skills/evaluate-behavior/SKILL.md +107 -0
- package/skills/evaluate-behavior/references/authoring.md +381 -0
- package/skills/evaluate-behavior/references/coding-tasks.md +145 -0
- package/skills/evaluate-behavior/references/conversations-pipelines-judges.md +177 -0
- package/skills/evaluate-behavior/references/design-and-measurement.md +273 -0
- package/skills/evaluate-behavior/references/running-and-evidence.md +245 -0
- package/skills/evaluate-behavior/scripts/claude-guard.mjs +76 -0
- package/skills/evaluate-behavior/scripts/guard-rules.mjs +309 -0
- package/skills/evaluate-behavior/scripts/lib/agreement.mjs +90 -0
- package/skills/evaluate-behavior/scripts/lib/arms.mjs +55 -0
- package/skills/evaluate-behavior/scripts/lib/budget.mjs +87 -0
- package/skills/evaluate-behavior/scripts/lib/build-source.mjs +287 -0
- package/skills/evaluate-behavior/scripts/lib/clear.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/container.mjs +395 -0
- package/skills/evaluate-behavior/scripts/lib/decision.mjs +418 -0
- package/skills/evaluate-behavior/scripts/lib/definitions.mjs +1072 -0
- package/skills/evaluate-behavior/scripts/lib/freeflow.mjs +103 -0
- package/skills/evaluate-behavior/scripts/lib/grade-command.mjs +591 -0
- package/skills/evaluate-behavior/scripts/lib/grade-comparison.mjs +90 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-interaction.mjs +19 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-support.mjs +32 -5
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/grade-workspace.mjs +28 -12
- package/skills/evaluate-behavior/scripts/lib/grade.mjs +101 -0
- package/skills/evaluate-behavior/scripts/lib/hooks.mjs +98 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/confinement.mjs +196 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/constraints.mjs +111 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/index.mjs +48 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/isolation.mjs +74 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/launch.mjs +123 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/model-call.mjs +73 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/recorder.mjs +178 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/session.mjs +494 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/claude-code/skills.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/index.mjs +31 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/names.mjs +3 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/confinement.mjs +173 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/host.mjs +148 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/index.mjs +70 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/isolation.mjs +81 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/model-call.mjs +79 -0
- package/skills/evaluate-behavior/scripts/lib/hosts/pi/process.mjs +241 -0
- package/skills/{evaluate-skill/scripts/lib → evaluate-behavior/scripts/lib/hosts/pi}/rpc.mjs +144 -26
- package/skills/evaluate-behavior/scripts/lib/identity.mjs +82 -0
- package/skills/evaluate-behavior/scripts/lib/judge.mjs +688 -0
- package/skills/evaluate-behavior/scripts/lib/metrics.mjs +332 -0
- package/skills/evaluate-behavior/scripts/lib/model-call.mjs +17 -0
- package/skills/evaluate-behavior/scripts/lib/participants.mjs +616 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/process.mjs +22 -0
- package/skills/evaluate-behavior/scripts/lib/queue.mjs +54 -0
- package/skills/evaluate-behavior/scripts/lib/reference.mjs +343 -0
- package/skills/evaluate-behavior/scripts/lib/reuse.mjs +277 -0
- package/skills/evaluate-behavior/scripts/lib/review.mjs +933 -0
- package/skills/evaluate-behavior/scripts/lib/runner.mjs +732 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/sandbox.mjs +119 -14
- package/skills/evaluate-behavior/scripts/lib/scan-shell.mjs +457 -0
- package/skills/evaluate-behavior/scripts/lib/scan.mjs +479 -0
- package/skills/evaluate-behavior/scripts/lib/seal.mjs +91 -0
- package/skills/evaluate-behavior/scripts/lib/shell.mjs +560 -0
- package/skills/evaluate-behavior/scripts/lib/source-override.mjs +64 -0
- package/skills/evaluate-behavior/scripts/lib/subject-file.mjs +88 -0
- package/skills/{evaluate-skill/scripts/lib/pi.mjs → evaluate-behavior/scripts/lib/subject.mjs} +235 -265
- package/skills/evaluate-behavior/scripts/lib/task-copy.mjs +124 -0
- package/skills/evaluate-behavior/scripts/lib/trials.mjs +524 -0
- package/skills/evaluate-behavior/scripts/lib/validity.mjs +68 -0
- package/skills/evaluate-behavior/scripts/lib/view-html.mjs +454 -0
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/view.mjs +159 -18
- package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-command-tool.mjs +21 -6
- package/skills/evaluate-behavior/scripts/pi-container-route.mjs +23 -0
- package/skills/evaluate-behavior/scripts/pi-guard.mjs +77 -0
- package/skills/evaluate-behavior/scripts/pi-login-refresh.mjs +31 -0
- package/skills/evaluate-behavior/scripts/skill-eval.mjs +586 -0
- package/skills/execute-work/SKILL.md +96 -104
- package/skills/execute-work/references/code-practices.md +27 -51
- package/skills/execute-work/references/design.md +271 -0
- package/skills/execute-work/references/diagnose.md +117 -0
- package/skills/execute-work/references/migration.md +108 -0
- package/skills/execute-work/references/simplify.md +65 -0
- package/skills/execute-work/scripts/self-review.mjs +422 -0
- package/skills/execute-work/scripts/touch-map.mjs +290 -0
- package/skills/review-work/SKILL.md +89 -65
- package/skills/review-work/references/adjudication.md +94 -0
- package/skills/review-work/references/artifact.md +198 -0
- package/skills/review-work/references/evals-and-judges.md +66 -0
- package/skills/review-work/references/work.md +177 -0
- package/skills/review-work/scripts/check-brief.mjs +128 -0
- package/skills/review-work/scripts/review-packet.mjs +233 -0
- package/skills/setup-freeflow/SKILL.md +68 -17
- package/skills/track-work/SKILL.md +52 -20
- package/skills/track-work/references/handoff.md +190 -0
- package/skills/track-work/references/projects.md +51 -0
- package/skills/track-work/references/working-record-format.md +36 -18
- package/skills/track-work/scripts/lib/document.mjs +17 -13
- package/skills/track-work/scripts/lib/format.mjs +2 -0
- package/skills/track-work/scripts/lib/recover.mjs +254 -0
- package/skills/track-work/scripts/lib/registry.mjs +656 -0
- package/skills/track-work/scripts/lib/search.mjs +40 -0
- package/skills/track-work/scripts/lib/store.mjs +46 -15
- package/skills/track-work/scripts/lib/views.mjs +50 -1
- package/skills/track-work/scripts/working-record.mjs +232 -13
- package/skills/verify-work/SKILL.md +45 -37
- package/skills/verify-work/references/feature-map.md +120 -0
- package/skills/verify-work/references/runtime-evidence.md +203 -0
- package/skills/verify-work/references/test-design.md +25 -6
- package/skills/verify-work/scripts/test-changes.mjs +246 -0
- package/skills/write-docs/SKILL.md +113 -25
- package/skills/write-docs/references/documentation-shapes.md +68 -3
- package/skills/write-docs/references/spec-and-plan-shapes.md +308 -0
- package/skills/write-docs/scripts/artifact-check.mjs +148 -0
- package/skills/write-docs/scripts/docs-check.mjs +96 -6
- package/skills/write-docs/scripts/plan-lint.mjs +123 -0
- package/skills/write-skill/SKILL.md +92 -52
- package/skills/write-skill/references/activation-and-wording.md +143 -0
- package/skills/write-skill/references/placement-and-dependencies.md +154 -0
- package/skills/write-skill/scripts/lib/compare.mjs +701 -0
- package/skills/write-skill/scripts/lib/skill-author.mjs +197 -3
- package/skills/write-skill/scripts/skill-author.mjs +51 -8
- package/capabilities/cognitive-routing/references/both-mode.md +0 -36
- package/capabilities/cognitive-routing/references/executor-mode.md +0 -48
- package/capabilities/cognitive-routing/references/helper-mode.md +0 -47
- package/plugin.json +0 -33
- package/skills/action-selection/SKILL.md +0 -92
- package/skills/action-selection/references/trajectory-stalls.md +0 -73
- package/skills/bypass/SKILL.md +0 -153
- package/skills/commit-work/SKILL.md +0 -93
- package/skills/commit-work/references/git-state-edges.md +0 -94
- package/skills/commit-work/references/simple-push.md +0 -94
- package/skills/commit-work/references/staging-decisions.md +0 -106
- package/skills/decision-gate/SKILL.md +0 -87
- package/skills/design-for-depth/SKILL.md +0 -108
- package/skills/design-for-depth/references/design-pressure-signals.md +0 -84
- package/skills/design-for-depth/references/implementation-decisions.md +0 -76
- package/skills/design-for-depth/references/interface-design-loop.md +0 -105
- package/skills/design-for-depth/references/module-and-dependency-design.md +0 -117
- package/skills/design-for-depth/references/state-and-failure-boundaries.md +0 -125
- package/skills/diagnose-failure/SKILL.md +0 -140
- package/skills/diagnose-failure/references/diagnostic-loop-catalog.md +0 -50
- package/skills/diagnose-failure/references/flaky-and-performance-diagnosis.md +0 -51
- package/skills/discuss/SKILL.md +0 -140
- package/skills/evaluate-skill/SKILL.md +0 -188
- package/skills/evaluate-skill/references/evaluation-definition-schema.md +0 -415
- package/skills/evaluate-skill/references/evaluation-design.md +0 -112
- package/skills/evaluate-skill/references/execution-and-evidence.md +0 -111
- package/skills/evaluate-skill/references/review-and-revision.md +0 -94
- package/skills/evaluate-skill/scripts/lib/definitions.mjs +0 -448
- package/skills/evaluate-skill/scripts/lib/grade-comparison.mjs +0 -60
- package/skills/evaluate-skill/scripts/lib/grade.mjs +0 -64
- package/skills/evaluate-skill/scripts/lib/review.mjs +0 -196
- package/skills/evaluate-skill/scripts/lib/runner.mjs +0 -405
- package/skills/evaluate-skill/scripts/lib/trials.mjs +0 -103
- package/skills/evaluate-skill/scripts/pi-guard.mjs +0 -181
- package/skills/evaluate-skill/scripts/skill-eval.mjs +0 -182
- package/skills/execute-work/references/execute-work-edges.md +0 -78
- package/skills/finish-branch/SKILL.md +0 -117
- package/skills/finish-branch/references/integration-options.md +0 -80
- package/skills/handoff/SKILL.md +0 -82
- package/skills/handoff/references/handoff-templates.md +0 -150
- package/skills/launch-work/SKILL.md +0 -116
- package/skills/launch-work/references/launch-readiness.md +0 -85
- package/skills/migration-work/SKILL.md +0 -115
- package/skills/migration-work/references/migration-lifecycle.md +0 -82
- package/skills/release-work/SKILL.md +0 -118
- package/skills/release-work/references/release-evidence.md +0 -78
- package/skills/review-artifact/SKILL.md +0 -143
- package/skills/review-artifact/references/adjudicate-artifact-review.md +0 -101
- package/skills/review-artifact/references/independent-artifact-reviewer-contract.md +0 -159
- package/skills/review-work/references/adjudicate-work-review.md +0 -97
- package/skills/review-work/references/independent-work-reviewer-contract.md +0 -139
- package/skills/review-work/references/security-risk-lens.md +0 -67
- package/skills/setup-freeflow/references/activation-contract.md +0 -103
- package/skills/setup-freeflow/references/host-setup.md +0 -67
- package/skills/simplify-code/SKILL.md +0 -85
- package/skills/simplify-code/references/simplification-patterns.md +0 -70
- package/skills/verify-work/references/browser-runtime-evidence.md +0 -51
- package/skills/verify-work/references/integration-evidence.md +0 -49
- package/skills/verify-work/references/performance-evidence.md +0 -59
- package/skills/workflow/SKILL.md +0 -171
- package/skills/workflow/references/domain-skill-composition.md +0 -45
- package/skills/write-plan/SKILL.md +0 -92
- package/skills/write-plan/references/plan-shapes.md +0 -156
- package/skills/write-skill/references/activation-boundaries.md +0 -106
- package/skills/write-skill/references/agent-first-instructions.md +0 -133
- package/skills/write-skill/references/progressive-disclosure.md +0 -129
- package/skills/write-skill/references/skill-dependency-graphs.md +0 -192
- package/skills/write-spec/SKILL.md +0 -85
- package/skills/write-spec/references/artifact-standards.md +0 -86
- package/skills/write-spec/references/decision-records.md +0 -80
- package/skills/write-spec/references/spec-shapes.md +0 -143
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/lib/evidence.mjs +0 -0
- /package/skills/{evaluate-skill → evaluate-behavior}/scripts/pi-observer.mjs +0 -0
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
import { mkdir, realpath } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { armNames } from "./arms.mjs";
|
|
5
|
+
import { budgetEvidence, charge, costEvidence, measuredCost, reserve, runBudgetEvidence } from "./budget.mjs";
|
|
6
|
+
import { COMMAND_TOOL, loadDefinition, selectDefinition } from "./definitions.mjs";
|
|
7
|
+
import { createInvocationId, fileIdentity, writeJson, writeText } from "./evidence.mjs";
|
|
8
|
+
import { gradeDeterministic } from "./grade.mjs";
|
|
9
|
+
import { prepareFreeflow } from "./freeflow.mjs";
|
|
10
|
+
import { postRunRecords } from "./hooks.mjs";
|
|
11
|
+
import { hostAdapter } from "./hosts/index.mjs";
|
|
12
|
+
import { identityRefusal, inputIdentity } from "./identity.mjs";
|
|
13
|
+
import { prepareSourceExtensions } from "./build-source.mjs";
|
|
14
|
+
import { armParticipants, runParticipants } from "./participants.mjs";
|
|
15
|
+
import { prepareShell } from "./shell.mjs";
|
|
16
|
+
import { enforceReferenceChecks } from "./reference.mjs";
|
|
17
|
+
import { createLimiter, withCpuSlot } from "./queue.mjs";
|
|
18
|
+
import { adoptRun, appendIndex, createReuse } from "./reuse.mjs";
|
|
19
|
+
import { prepareFixture, prepareRuntime, verifyDescriptionOnlyRevision } from "./sandbox.mjs";
|
|
20
|
+
import { applySourceOverrides } from "./source-override.mjs";
|
|
21
|
+
import { runBody, runDescription, VariantSetupError } from "./subject.mjs";
|
|
22
|
+
import { unclearedFlags } from "./scan.mjs";
|
|
23
|
+
import { armSealing } from "./seal.mjs";
|
|
24
|
+
import { applyValidityGate } from "./validity.mjs";
|
|
25
|
+
|
|
26
|
+
const DESCRIPTION_TOOLS = new Set(["read"]);
|
|
27
|
+
const BODY_TOOLS = new Set(["read", "write", "edit", "ls", "find", "grep"]);
|
|
28
|
+
// Native tools must be separated from extension tools before CLI tool selection is built.
|
|
29
|
+
const NATIVE_TOOLS = new Set(["read", "bash", "powershell", "edit", "write", "grep", "find", "ls"]);
|
|
30
|
+
const BACKGROUND_TOOLS = new Set(["bash_background", "monitor"]);
|
|
31
|
+
const DEFAULT_RUNTIME = {
|
|
32
|
+
session: false,
|
|
33
|
+
prompt: "isolated",
|
|
34
|
+
extensions: [],
|
|
35
|
+
environment: { literal: {}, inherit: [] },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** A group's host: its configuration's `host.name`, which every arm shares. Pi for groups resolved before it was recorded. */
|
|
39
|
+
export function groupHost(group) {
|
|
40
|
+
return group.host ?? group.runtime?.host ?? "pi";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Where results are stored: `--results` when given, else `<root>/.skill-eval/runs`. */
|
|
44
|
+
export function resultsDirectory(root, results = null) {
|
|
45
|
+
return results === null || results === undefined
|
|
46
|
+
? path.join(root, ".skill-eval", "runs")
|
|
47
|
+
: path.resolve(root, results);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The reuse state for one run or aggregate (see reuse.mjs); `enabled` is `--reuse`. */
|
|
51
|
+
export function createRunReuse(runsDirectory, enabled) {
|
|
52
|
+
return createReuse({
|
|
53
|
+
resultsDirectory: runsDirectory,
|
|
54
|
+
enabled,
|
|
55
|
+
hostVersion: (host) => hostAdapter(host).version(),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Runs the selection once. Subjects run through `subjects` (a limiter shared by every invocation of one
|
|
61
|
+
* aggregate; `jobs` at once); with one job, groups and arms run in order exactly as before. Cancellation
|
|
62
|
+
* reaches running subjects; budget exhaustion only stops new ones from starting.
|
|
63
|
+
*
|
|
64
|
+
* @param {string} definitionFile
|
|
65
|
+
* @param {any} selectors
|
|
66
|
+
* @param {{root?: string, signal?: AbortSignal, budget?: any, dryRun?: boolean, jobs?: number, subjects?: ReturnType<typeof createLimiter>, results?: string | null, reuse?: any, groupRange?: [number, number] | null, loginsRefreshed?: boolean}} options
|
|
67
|
+
*/
|
|
68
|
+
export async function runEvaluation(definitionFile, selectors = {}, options = {}) {
|
|
69
|
+
const { root = process.cwd(), signal, budget = null, dryRun = false, groupRange = null } = options;
|
|
70
|
+
const { skipReferenceCheck = false } = options;
|
|
71
|
+
const subjects = options.subjects ?? createLimiter(options.jobs ?? 1);
|
|
72
|
+
const runsDirectory = resultsDirectory(root, options.results);
|
|
73
|
+
const reuse = options.reuse ?? createRunReuse(runsDirectory, false);
|
|
74
|
+
const definitionPath = await realpath(path.resolve(root, definitionFile));
|
|
75
|
+
const definition = await loadDefinition(definitionPath, { root });
|
|
76
|
+
const selection = selectDefinition(definition, selectors);
|
|
77
|
+
// A pilot runs the first groups of the selection, and the batch after it the rest.
|
|
78
|
+
if (groupRange !== null) selection.groups = selection.groups.slice(groupRange[0], groupRange[1]);
|
|
79
|
+
// --source: an arm's Git source refs replaced for this run only, before anything reads them.
|
|
80
|
+
const sources = options.sources && Object.keys(options.sources).length > 0 ? options.sources : null;
|
|
81
|
+
applySourceOverrides(selection, sources);
|
|
82
|
+
assertSupportedSelection(selection);
|
|
83
|
+
|
|
84
|
+
// Canonical evidence needs every working-tree input recoverable from one commit; anything else is a dry run.
|
|
85
|
+
const identity = await inputIdentity(root, workingTreeInputs(selection, definitionPath, root));
|
|
86
|
+
const refusal = identityRefusal(identity);
|
|
87
|
+
if (refusal !== null && !dryRun) throw new Error(refusal);
|
|
88
|
+
const hostVersion = await checkHostVersion(selection);
|
|
89
|
+
const hostExtensions = await resolveSelectedExtensions(selection);
|
|
90
|
+
if (!options.loginsRefreshed) await refreshSelectedLogins(selection);
|
|
91
|
+
const invocationId = createInvocationId();
|
|
92
|
+
const resultDirectory = path.join(runsDirectory, invocationId);
|
|
93
|
+
await mkdir(runsDirectory, { recursive: true });
|
|
94
|
+
await mkdir(resultDirectory);
|
|
95
|
+
await writeJson(path.join(resultDirectory, "invocation.json"), {
|
|
96
|
+
schema_version: 1,
|
|
97
|
+
id: invocationId,
|
|
98
|
+
definition: definitionPath,
|
|
99
|
+
results: runsDirectory,
|
|
100
|
+
jobs: subjects.limit,
|
|
101
|
+
reuse: reuse.enabled,
|
|
102
|
+
...(sources === null ? {} : { sources }),
|
|
103
|
+
selection: {
|
|
104
|
+
group: selectors.group ?? null,
|
|
105
|
+
variant: selectors.variant ?? null,
|
|
106
|
+
groups: selection.groups.map((entry) => ({
|
|
107
|
+
id: entry.group.id,
|
|
108
|
+
position: entry.position,
|
|
109
|
+
variants: entry.variants,
|
|
110
|
+
})),
|
|
111
|
+
},
|
|
112
|
+
startedAt: new Date().toISOString(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// A declared reference must solve its task before any subject starts (SPEC-002 A9).
|
|
116
|
+
await enforceReferenceChecks(selection, { root, resultDirectory, skip: skipReferenceCheck, signal });
|
|
117
|
+
|
|
118
|
+
// Budget exhaustion stops only subjects not yet started; a subject already running completes and is charged.
|
|
119
|
+
const context = {
|
|
120
|
+
root,
|
|
121
|
+
invocationId,
|
|
122
|
+
resultDirectory,
|
|
123
|
+
signal,
|
|
124
|
+
stopped: () => Boolean(signal?.aborted || budget?.exhausted),
|
|
125
|
+
budget,
|
|
126
|
+
subjects,
|
|
127
|
+
// A clean working tree is identified by its commit, recorded on each working-tree source.
|
|
128
|
+
sourceCommit: refusal === null ? identity.commit : null,
|
|
129
|
+
reuse,
|
|
130
|
+
dryRun,
|
|
131
|
+
indexEntries: [],
|
|
132
|
+
sources,
|
|
133
|
+
// This invocation's own spend, apart from the run's shared budget, which every trial charges.
|
|
134
|
+
cost: { spent: 0, subjects: 0, unmeasured: 0 },
|
|
135
|
+
};
|
|
136
|
+
const runOne = (selected) =>
|
|
137
|
+
runGroup({ ...context, selected, hostExtensions: hostExtensions.get(selected.group.id) });
|
|
138
|
+
const groups =
|
|
139
|
+
subjects.limit === 1 ? await sequential(selection.groups, runOne) : await Promise.all(selection.groups.map(runOne));
|
|
140
|
+
// Canonical complete runs become reusable, with or without --reuse; an index that cannot be written never fails
|
|
141
|
+
// the run.
|
|
142
|
+
if (!dryRun) await appendIndex(runsDirectory, context.indexEntries).catch(() => {});
|
|
143
|
+
const state = batchState(groups);
|
|
144
|
+
const summary = {
|
|
145
|
+
schema_version: 1,
|
|
146
|
+
id: invocationId,
|
|
147
|
+
state,
|
|
148
|
+
definitionKind: selection.definitionKind,
|
|
149
|
+
dryRun,
|
|
150
|
+
inputs: identity.repository
|
|
151
|
+
? { commit: identity.commit, uncommitted: identity.uncommitted }
|
|
152
|
+
: { commit: null, uncommitted: [], repository: false },
|
|
153
|
+
groups,
|
|
154
|
+
completedAt: new Date().toISOString(),
|
|
155
|
+
};
|
|
156
|
+
if (hostVersion !== null) summary.hostVersion = hostVersion;
|
|
157
|
+
summary.cost = costEvidence(context.cost);
|
|
158
|
+
// The budget is the whole run's, shared by every trial: its total is what the run had spent when this one ended.
|
|
159
|
+
if (budget !== null) summary.budget = runBudgetEvidence(budget);
|
|
160
|
+
await writeJson(path.join(resultDirectory, "summary.json"), summary);
|
|
161
|
+
return { id: invocationId, path: resultDirectory, state, summary };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Every path a run reads from the working tree: definition files, fixtures, runtime bundles, working-tree subjects. */
|
|
165
|
+
function workingTreeInputs(selection, definitionPath, root) {
|
|
166
|
+
const inputs = new Set([definitionPath]);
|
|
167
|
+
for (const { group, variants } of selection.groups) {
|
|
168
|
+
for (const file of group.files ?? []) inputs.add(file);
|
|
169
|
+
if (group.fixture !== null) inputs.add(path.resolve(root, group.fixture));
|
|
170
|
+
for (const bundle of group.runtime?.extensions ?? []) {
|
|
171
|
+
for (const resource of bundle.resources) inputs.add(path.resolve(root, resource));
|
|
172
|
+
}
|
|
173
|
+
for (const [arm, state] of Object.entries(variants)) {
|
|
174
|
+
const variant = group.variants[arm];
|
|
175
|
+
if (state !== "selected" || variant.source.kind !== "working-tree") continue;
|
|
176
|
+
for (const entry of [...variant.skills, ...variant.context]) inputs.add(path.resolve(root, entry));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return [...inputs];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Each selected arm's declared host extensions, found among the host's installed packages before anything starts. */
|
|
183
|
+
async function resolveSelectedExtensions(selection) {
|
|
184
|
+
const resolved = new Map();
|
|
185
|
+
for (const { group, variants } of selection.groups) {
|
|
186
|
+
const arms = Object.fromEntries(armNames(group).map((arm) => [arm, []]));
|
|
187
|
+
for (const [arm, state] of Object.entries(variants)) {
|
|
188
|
+
if (state === "selected") {
|
|
189
|
+
arms[arm] = await hostAdapter(groupHost(group)).resolveExtensions(group.variants[arm].hostExtensions ?? [], {
|
|
190
|
+
deferSources: true,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
resolved.set(group.id, arms);
|
|
195
|
+
}
|
|
196
|
+
return resolved;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function sequential(items, work) {
|
|
200
|
+
const results = [];
|
|
201
|
+
for (const item of items) results.push(await work(item));
|
|
202
|
+
return results;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Before a batch, makes every OAuth login the selected arms use valid for the target window, refreshing it
|
|
207
|
+
* once, centrally, so no subject has to refresh its own copy. Trials call it once for all their invocations.
|
|
208
|
+
*/
|
|
209
|
+
export async function refreshSelectedLogins(selection) {
|
|
210
|
+
const providers = new Map();
|
|
211
|
+
for (const { group, variants } of selection.groups) {
|
|
212
|
+
const host = groupHost(group);
|
|
213
|
+
if (!providers.has(host)) providers.set(host, new Set());
|
|
214
|
+
for (const [arm, state] of Object.entries(variants)) {
|
|
215
|
+
const model = group.variants[arm].model?.model;
|
|
216
|
+
if (state === "selected" && typeof model === "string" && model.includes("/")) {
|
|
217
|
+
providers.get(host).add(model.split("/")[0]);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
for (const [host, names] of providers) await hostAdapter(host).refreshLogins([...names]);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* A declared host version must match the installed host before anything starts. Returns the installed version of
|
|
226
|
+
* the one host whose version was declared, or null when none was.
|
|
227
|
+
*/
|
|
228
|
+
async function checkHostVersion(selection) {
|
|
229
|
+
const declared = new Map();
|
|
230
|
+
for (const { group, variants } of selection.groups) {
|
|
231
|
+
const host = groupHost(group);
|
|
232
|
+
for (const [arm, state] of Object.entries(variants)) {
|
|
233
|
+
const version = group.configurations?.[arm]?.host?.version;
|
|
234
|
+
if (state !== "selected" || version === undefined) continue;
|
|
235
|
+
if (!declared.has(host)) declared.set(host, new Set());
|
|
236
|
+
declared.get(host).add(version);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
if (declared.size === 0) return null;
|
|
240
|
+
const installed = {};
|
|
241
|
+
for (const [host, versions] of declared) {
|
|
242
|
+
installed[host] = await hostAdapter(host).version();
|
|
243
|
+
for (const version of versions) {
|
|
244
|
+
if (version !== installed[host]) {
|
|
245
|
+
throw new Error(`the definition declares ${host} ${version}, but the installed ${host} is ${installed[host]}`);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// One host's version is a string, as it always was; a selection declaring versions for several hosts records each.
|
|
250
|
+
const hosts = Object.keys(installed);
|
|
251
|
+
return hosts.length === 1 ? installed[hosts[0]] : installed;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function assertSupportedSelection(selection) {
|
|
255
|
+
for (const selected of selection.groups) {
|
|
256
|
+
const { group } = selected;
|
|
257
|
+
if (group.type !== "description" && group.type !== "body") {
|
|
258
|
+
throw new Error("skill-eval run currently supports description and body groups only");
|
|
259
|
+
}
|
|
260
|
+
const supportedTools = new Set(group.type === "body" ? BODY_TOOLS : DESCRIPTION_TOOLS);
|
|
261
|
+
// bash only through a declared, verified shell isolation (definitions.mjs and shell.mjs).
|
|
262
|
+
if (group.type === "body" && Object.values(group.variants).every((variant) => variant.shell)) {
|
|
263
|
+
supportedTools.add("bash");
|
|
264
|
+
}
|
|
265
|
+
const unsupportedNativeTools = group.tools.filter((tool) => NATIVE_TOOLS.has(tool) && !supportedTools.has(tool));
|
|
266
|
+
if (unsupportedNativeTools.length > 0) {
|
|
267
|
+
const hint = unsupportedNativeTools.includes("bash")
|
|
268
|
+
? "; bash runs only in a body group whose every arm declares a shell isolation (configuration.shell)"
|
|
269
|
+
: "";
|
|
270
|
+
throw new Error(
|
|
271
|
+
`${group.type} execution does not support native tools: ${unsupportedNativeTools.join(", ")}${hint}`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
// The evaluator supplies run_command itself when the definition declares commands.
|
|
275
|
+
const customTools = group.tools.filter(
|
|
276
|
+
(tool) => !NATIVE_TOOLS.has(tool) && !supportedTools.has(tool) && tool !== COMMAND_TOOL,
|
|
277
|
+
);
|
|
278
|
+
if (group.type === "description" && customTools.length > 0) {
|
|
279
|
+
throw new Error(`${group.type} execution does not support custom tools: ${customTools.join(", ")}`);
|
|
280
|
+
}
|
|
281
|
+
// Freeflow's background commands outlive the call that starts them, so they run only in a container, which the
|
|
282
|
+
// evaluator removes when the subject ends (guard-rules.mjs routes them there).
|
|
283
|
+
const background = group.tools.filter((tool) => BACKGROUND_TOOLS.has(tool));
|
|
284
|
+
if (background.length > 0 && !Object.values(group.variants).every((variant) => variant.shell?.container)) {
|
|
285
|
+
throw new Error(
|
|
286
|
+
`${background.join(" and ")} run only in a container environment: every arm needs configuration.environment`,
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
// Freeflow registers its own tools, so a Freeflow configuration also allows custom tool names.
|
|
290
|
+
const freeflowLoaded = Object.values(group.variants).some((variant) => variant.freeflow);
|
|
291
|
+
if (customTools.length > 0 && (group.runtime?.extensions?.length ?? 0) === 0 && !freeflowLoaded) {
|
|
292
|
+
throw new Error(
|
|
293
|
+
`${group.type} execution requires declared extensions for custom tools: ${customTools.join(", ")}`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export { budgetEvidence };
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* One subject in a slot: it waits for a free slot, starts only if nothing stopped the run and the budget can
|
|
303
|
+
* reserve for it, and is charged what it cost, to the run's budget and to this invocation's own `cost`. Returns
|
|
304
|
+
* `{ run, executed }`.
|
|
305
|
+
*/
|
|
306
|
+
async function runSubject({ group, variant, stopped, budget, cost, subjects, execute }) {
|
|
307
|
+
return subjects.run(async () => {
|
|
308
|
+
if (stopped()) return { run: cancelledRun(group, variant), executed: false };
|
|
309
|
+
const reservation = reserve(budget);
|
|
310
|
+
if (reservation === null) return { run: cancelledRun(group, variant), executed: false };
|
|
311
|
+
const run = await execute();
|
|
312
|
+
const spent = measuredCost(run.usage);
|
|
313
|
+
charge(budget, reservation, spent ?? 0);
|
|
314
|
+
cost.subjects += 1;
|
|
315
|
+
if (spent === null) cost.unmeasured += 1;
|
|
316
|
+
else cost.spent += spent;
|
|
317
|
+
return { run, executed: true };
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function runGroup({
|
|
322
|
+
selected,
|
|
323
|
+
root,
|
|
324
|
+
invocationId,
|
|
325
|
+
resultDirectory,
|
|
326
|
+
signal,
|
|
327
|
+
stopped,
|
|
328
|
+
budget,
|
|
329
|
+
subjects,
|
|
330
|
+
sourceCommit = null,
|
|
331
|
+
hostExtensions = {},
|
|
332
|
+
reuse,
|
|
333
|
+
dryRun,
|
|
334
|
+
indexEntries,
|
|
335
|
+
sources = null,
|
|
336
|
+
cost,
|
|
337
|
+
}) {
|
|
338
|
+
const { group } = selected;
|
|
339
|
+
const arms = armNames(group);
|
|
340
|
+
const groupDirectory = path.join(resultDirectory, "groups", group.id);
|
|
341
|
+
await mkdir(groupDirectory, { recursive: true });
|
|
342
|
+
await writeJson(path.join(groupDirectory, "definition.json"), group);
|
|
343
|
+
|
|
344
|
+
let fixture = null;
|
|
345
|
+
let runtime = null;
|
|
346
|
+
let freeflow = Object.fromEntries(arms.map((arm) => [arm, null]));
|
|
347
|
+
let resourceError = null;
|
|
348
|
+
let resourceDetails = null;
|
|
349
|
+
let confinement = {};
|
|
350
|
+
if (!stopped()) {
|
|
351
|
+
try {
|
|
352
|
+
fixture = await prepareFixture({ declaredPath: group.fixture, root, groupDirectory });
|
|
353
|
+
runtime = await prepareRuntime({
|
|
354
|
+
runtime: group.runtime ?? { host: groupHost(group), ...DEFAULT_RUNTIME },
|
|
355
|
+
root,
|
|
356
|
+
groupDirectory,
|
|
357
|
+
});
|
|
358
|
+
freeflow = await withCpuSlot(() => prepareFreeflow({ variants: group.variants, root, groupDirectory }));
|
|
359
|
+
// Building from source and probing the shell are local CPU work, so they wait for a --cpu-jobs slot.
|
|
360
|
+
const extensions = hostExtensions;
|
|
361
|
+
hostExtensions = await withCpuSlot(() => prepareSourceExtensions({ extensions, root, groupDirectory, signal }));
|
|
362
|
+
const built = hostExtensions;
|
|
363
|
+
confinement = await withCpuSlot(() =>
|
|
364
|
+
prepareShell({ group, variants: selected.variants, hostExtensions: built, groupDirectory, signal }),
|
|
365
|
+
);
|
|
366
|
+
if (group.revision === "description-only") {
|
|
367
|
+
await verifyDescriptionOnlyRevision({
|
|
368
|
+
variants: group.variants,
|
|
369
|
+
root,
|
|
370
|
+
destinationRoot: path.join(groupDirectory, "resources", "description-only-check"),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
} catch (error) {
|
|
374
|
+
resourceError = error instanceof Error ? error.message : String(error);
|
|
375
|
+
resourceDetails = error?.details ?? null;
|
|
376
|
+
if (resourceDetails?.confinement) confinement = resourceDetails.confinement;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const runs = {};
|
|
381
|
+
const runFiles = {};
|
|
382
|
+
const keys = {};
|
|
383
|
+
const reusedFrom = {};
|
|
384
|
+
const runArm = async (variant) => {
|
|
385
|
+
const variantDirectory = path.join(groupDirectory, variant);
|
|
386
|
+
await mkdir(variantDirectory, { recursive: true });
|
|
387
|
+
if (selected.variants[variant] === "not-selected") {
|
|
388
|
+
runs[variant] = notSelectedRun(group, variant);
|
|
389
|
+
} else if (stopped()) {
|
|
390
|
+
runs[variant] = cancelledRun(group, variant);
|
|
391
|
+
} else if (resourceError === null) {
|
|
392
|
+
// A canonical run has a reuse key, and a new complete run is indexed under it with or without --reuse, so a
|
|
393
|
+
// run made without it (a first run, a pilot on its own) can stand in later. Only --reuse claims an indexed run.
|
|
394
|
+
keys[variant] = dryRun ? null : await reuse.keyFor(group, variant, root).catch(() => null);
|
|
395
|
+
const claimed = await reuse.claim(keys[variant]);
|
|
396
|
+
const adopted = claimed === null ? null : await adoptRun(claimed, variantDirectory).catch(() => null);
|
|
397
|
+
if (adopted !== null) {
|
|
398
|
+
runs[variant] = adopted;
|
|
399
|
+
reusedFrom[variant] = adopted.reused;
|
|
400
|
+
} else {
|
|
401
|
+
const { run, executed } = await runSubject({
|
|
402
|
+
group,
|
|
403
|
+
variant,
|
|
404
|
+
stopped,
|
|
405
|
+
budget,
|
|
406
|
+
cost,
|
|
407
|
+
subjects,
|
|
408
|
+
execute: () =>
|
|
409
|
+
executeVariant({
|
|
410
|
+
group,
|
|
411
|
+
variant,
|
|
412
|
+
root,
|
|
413
|
+
variantDirectory,
|
|
414
|
+
fixture,
|
|
415
|
+
runtime,
|
|
416
|
+
freeflow: freeflow[variant],
|
|
417
|
+
hostExtensions: hostExtensions[variant] ?? [],
|
|
418
|
+
signal,
|
|
419
|
+
}),
|
|
420
|
+
});
|
|
421
|
+
runs[variant] = run;
|
|
422
|
+
if (executed) {
|
|
423
|
+
if (confinement[variant] !== undefined) run.confinement = confinement[variant];
|
|
424
|
+
// What the subject's own code could reach: the network policy and where each path ran.
|
|
425
|
+
run.sealing = armSealing(group, variant, confinement[variant]);
|
|
426
|
+
if (sources?.[variant] !== undefined) run.sourceOverride = sources[variant];
|
|
427
|
+
if (sourceCommit !== null && run.resources?.source?.kind === "working-tree") {
|
|
428
|
+
run.resources.source.commit = sourceCommit;
|
|
429
|
+
}
|
|
430
|
+
// A run that shows no model at work is invalid before anything grades it; its graders then do not start.
|
|
431
|
+
applyValidityGate(run);
|
|
432
|
+
// Post-run hooks see the finished run before it is persisted; their records land in run.json.
|
|
433
|
+
const records = await postRunRecords({ group, arm: variant, run, root, directory: variantDirectory, signal });
|
|
434
|
+
if (records !== null) run.postRun = records;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
} else {
|
|
438
|
+
runs[variant] = invalidRun(group, variant, resourceError);
|
|
439
|
+
if (resourceDetails !== null) runs[variant].error.details = resourceDetails;
|
|
440
|
+
if (confinement[variant] !== undefined) runs[variant].confinement = confinement[variant];
|
|
441
|
+
}
|
|
442
|
+
const persisted = await persistRunOrFailure(variantDirectory, runs[variant]);
|
|
443
|
+
runs[variant] = persisted.run;
|
|
444
|
+
runFiles[variant] = persisted.file;
|
|
445
|
+
};
|
|
446
|
+
if (subjects.limit === 1) await sequential(arms, runArm);
|
|
447
|
+
else await Promise.all(arms.map(runArm));
|
|
448
|
+
|
|
449
|
+
const evidence = {};
|
|
450
|
+
for (const arm of arms) evidence[arm] = await runFileIdentity(runFiles[arm]);
|
|
451
|
+
const grade = await createGrade(group, runs, evidence);
|
|
452
|
+
const persistedGrade = await persistGrade(groupDirectory, grade);
|
|
453
|
+
const selectedRuns = arms
|
|
454
|
+
.filter((variant) => selected.variants[variant] === "selected")
|
|
455
|
+
.map((variant) => runs[variant]);
|
|
456
|
+
const selectedState = selectedGroupState(selectedRuns, persistedGrade.grade);
|
|
457
|
+
let groupState = selectedState;
|
|
458
|
+
if (selectedState !== "cancelled" && resourceError !== null) groupState = "invalid";
|
|
459
|
+
const groupResult = {
|
|
460
|
+
id: group.id,
|
|
461
|
+
position: selected.position,
|
|
462
|
+
state: groupState,
|
|
463
|
+
variants: Object.fromEntries(arms.map((variant) => [variant, runs[variant].state])),
|
|
464
|
+
grade: persistedGrade.grade.state,
|
|
465
|
+
artifacts: { grade: persistedGrade.artifact, group: "group.json" },
|
|
466
|
+
errors: persistedGrade.errors,
|
|
467
|
+
};
|
|
468
|
+
if (Object.keys(reusedFrom).length > 0) groupResult.reused = reusedFrom;
|
|
469
|
+
// Why each invalid arm is invalid, so a pilot or a view can say it without opening the run.
|
|
470
|
+
const invalid = Object.fromEntries(
|
|
471
|
+
arms.filter((arm) => runs[arm].state === "invalid").map((arm) => [arm, runs[arm].error?.message ?? "invalid"]),
|
|
472
|
+
);
|
|
473
|
+
if (Object.keys(invalid).length > 0) groupResult.invalid = invalid;
|
|
474
|
+
// Each arm's transcript-scan flags no person has cleared yet; such a run is held out of every claim.
|
|
475
|
+
const flagged = {};
|
|
476
|
+
for (const arm of arms) {
|
|
477
|
+
const uncleared = await unclearedFlags(runs[arm], path.join(groupDirectory, arm));
|
|
478
|
+
if (uncleared.length > 0) flagged[arm] = uncleared;
|
|
479
|
+
}
|
|
480
|
+
if (Object.keys(flagged).length > 0) groupResult.flagged = flagged;
|
|
481
|
+
// A complete run with a readable grade is indexed for reuse; a reused run is not, so it is reused once.
|
|
482
|
+
const gradeReadable =
|
|
483
|
+
persistedGrade.artifact === "deterministic-grade.json" && persistedGrade.grade.state !== "grade-error";
|
|
484
|
+
for (const arm of arms) {
|
|
485
|
+
if (!gradeReadable || typeof keys[arm] !== "string" || runs[arm].state !== "complete" || runs[arm].reused) continue;
|
|
486
|
+
indexEntries.push({
|
|
487
|
+
key: keys[arm],
|
|
488
|
+
result: invocationId,
|
|
489
|
+
group: group.id,
|
|
490
|
+
arm,
|
|
491
|
+
indexedAt: new Date().toISOString(),
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
return persistGroupResult(groupDirectory, groupResult);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function batchState(groups) {
|
|
498
|
+
if (groups.some((group) => group.state === "cancelled")) return "cancelled";
|
|
499
|
+
if (groups.every((group) => group.state === "complete")) return "complete";
|
|
500
|
+
return "partially-complete";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function selectedGroupState(runs, grade) {
|
|
504
|
+
if (runs.some((run) => run.state === "cancelled")) return "cancelled";
|
|
505
|
+
// A grade whose every selected check is unavailable observed nothing, so the group is not complete.
|
|
506
|
+
if (runs.every((run) => run.state === "complete") && grade.state === "complete") return "complete";
|
|
507
|
+
return "partially-complete";
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async function createGrade(group, runs, evidence) {
|
|
511
|
+
try {
|
|
512
|
+
return await gradeDeterministic(group, runs, evidence);
|
|
513
|
+
} catch (error) {
|
|
514
|
+
return gradeErrorReport(evidence, `deterministic grading failed: ${errorMessage(error)}`);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function persistGrade(groupDirectory, grade) {
|
|
519
|
+
const artifact = "deterministic-grade.json";
|
|
520
|
+
try {
|
|
521
|
+
await writeJson(path.join(groupDirectory, artifact), grade);
|
|
522
|
+
return { grade, artifact, errors: [] };
|
|
523
|
+
} catch (error) {
|
|
524
|
+
const reason = `failed to persist ${artifact}: ${errorMessage(error)}`;
|
|
525
|
+
const failed = gradeErrorReport(grade.evidence, reason, grade);
|
|
526
|
+
const fallback = "deterministic-grade-error.json";
|
|
527
|
+
await writeJson(path.join(groupDirectory, fallback), failed);
|
|
528
|
+
return {
|
|
529
|
+
grade: failed,
|
|
530
|
+
artifact: fallback,
|
|
531
|
+
errors: [{ kind: "persistence", artifact, message: reason }],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async function persistGroupResult(groupDirectory, groupResult) {
|
|
537
|
+
const artifact = "group.json";
|
|
538
|
+
try {
|
|
539
|
+
await writeJson(path.join(groupDirectory, artifact), groupResult);
|
|
540
|
+
return groupResult;
|
|
541
|
+
} catch (error) {
|
|
542
|
+
const message = `failed to persist ${artifact}: ${errorMessage(error)}`;
|
|
543
|
+
const fallback = "group-error.json";
|
|
544
|
+
const failed = {
|
|
545
|
+
...groupResult,
|
|
546
|
+
state: groupResult.state === "complete" ? "partially-complete" : groupResult.state,
|
|
547
|
+
artifacts: { ...groupResult.artifacts, group: fallback },
|
|
548
|
+
errors: [...groupResult.errors, { kind: "persistence", artifact, message }],
|
|
549
|
+
};
|
|
550
|
+
await writeJson(path.join(groupDirectory, fallback), failed);
|
|
551
|
+
return failed;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function gradeErrorReport(evidence, reason, grade = null) {
|
|
556
|
+
return {
|
|
557
|
+
schema_version: 1,
|
|
558
|
+
state: "grade-error",
|
|
559
|
+
evidence,
|
|
560
|
+
checks: grade?.checks ?? [],
|
|
561
|
+
comparisons: grade?.comparisons ?? [],
|
|
562
|
+
errors: [...(grade?.errors ?? []), { id: null, reason }],
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
function errorMessage(error) {
|
|
567
|
+
return error instanceof Error ? error.message : String(error);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function executeVariant({
|
|
571
|
+
group,
|
|
572
|
+
variant,
|
|
573
|
+
root,
|
|
574
|
+
variantDirectory,
|
|
575
|
+
fixture,
|
|
576
|
+
runtime,
|
|
577
|
+
freeflow,
|
|
578
|
+
hostExtensions,
|
|
579
|
+
signal,
|
|
580
|
+
}) {
|
|
581
|
+
try {
|
|
582
|
+
const run =
|
|
583
|
+
armParticipants(group, variant) !== null ? runParticipants : group.type === "body" ? runBody : runDescription;
|
|
584
|
+
return await run({ group, variant, root, variantDirectory, fixture, runtime, freeflow, hostExtensions, signal });
|
|
585
|
+
} catch (error) {
|
|
586
|
+
if (error instanceof VariantSetupError) return invalidRun(group, variant, error.message);
|
|
587
|
+
return infrastructureFailedRun(group, variant, error);
|
|
588
|
+
} finally {
|
|
589
|
+
await hostAdapter(groupHost(group)).removeCredentials(variantDirectory);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
async function persistRunOrFailure(variantDirectory, run) {
|
|
594
|
+
try {
|
|
595
|
+
return { run, file: await persistRun(variantDirectory, run) };
|
|
596
|
+
} catch (error) {
|
|
597
|
+
const failed = persistenceFailedRun(run, error);
|
|
598
|
+
const runFile = path.join(variantDirectory, "run.json");
|
|
599
|
+
await writeJson(runFile, {
|
|
600
|
+
...failed,
|
|
601
|
+
transcript: failed.transcript ?? [],
|
|
602
|
+
artifacts: {
|
|
603
|
+
events: failed.process === undefined ? null : "events.jsonl",
|
|
604
|
+
final: null,
|
|
605
|
+
stderr: failed.process === undefined ? null : "stderr.log",
|
|
606
|
+
},
|
|
607
|
+
});
|
|
608
|
+
return { run: failed, file: runFile };
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function persistRun(variantDirectory, run) {
|
|
613
|
+
const transcript = run.transcript ?? [];
|
|
614
|
+
const response = run.response ?? "";
|
|
615
|
+
await writeJson(path.join(variantDirectory, "transcript.json"), transcript);
|
|
616
|
+
await writeText(path.join(variantDirectory, "final.md"), response === "" ? "" : `${response}\n`);
|
|
617
|
+
const runFile = path.join(variantDirectory, "run.json");
|
|
618
|
+
const hasProcessEvidence = run.process !== undefined;
|
|
619
|
+
const persisted = {
|
|
620
|
+
...run,
|
|
621
|
+
transcript: "transcript.json",
|
|
622
|
+
artifacts: {
|
|
623
|
+
events: hasProcessEvidence ? "events.jsonl" : null,
|
|
624
|
+
final: "final.md",
|
|
625
|
+
stderr: hasProcessEvidence ? "stderr.log" : null,
|
|
626
|
+
context: run.contextObservationArtifact ? "context-observations.jsonl" : null,
|
|
627
|
+
},
|
|
628
|
+
};
|
|
629
|
+
if (Array.isArray(run.turns)) persisted.turns = externalizeTurnTranscripts(run.turns);
|
|
630
|
+
await writeJson(runFile, persisted);
|
|
631
|
+
return runFile;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
function externalizeTurnTranscripts(turns) {
|
|
635
|
+
let offset = 0;
|
|
636
|
+
return turns.map((turn) => {
|
|
637
|
+
const length = Array.isArray(turn.transcript) ? turn.transcript.length : 0;
|
|
638
|
+
const transcript = { file: "transcript.json", start: offset, end: offset + length };
|
|
639
|
+
offset += length;
|
|
640
|
+
return { ...turn, transcript };
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
function notSelectedRun(group, variant) {
|
|
645
|
+
return {
|
|
646
|
+
schema_version: 1,
|
|
647
|
+
variant,
|
|
648
|
+
state: "not-selected",
|
|
649
|
+
evaluationType: group.type,
|
|
650
|
+
prompt: group.input.prompt ?? null,
|
|
651
|
+
prompts: group.input.turns ?? null,
|
|
652
|
+
activation: unavailableActivation(),
|
|
653
|
+
response: "",
|
|
654
|
+
transcript: [],
|
|
655
|
+
error: null,
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function cancelledRun(group, variant) {
|
|
660
|
+
return {
|
|
661
|
+
schema_version: 1,
|
|
662
|
+
variant,
|
|
663
|
+
state: "cancelled",
|
|
664
|
+
evaluationType: group.type,
|
|
665
|
+
prompt: group.input.prompt ?? null,
|
|
666
|
+
prompts: group.input.turns ?? null,
|
|
667
|
+
activation: unavailableActivation(),
|
|
668
|
+
response: "",
|
|
669
|
+
transcript: [],
|
|
670
|
+
error: { kind: "cancellation", message: "Cancelled before subject execution" },
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
function invalidRun(group, variant, message) {
|
|
675
|
+
return {
|
|
676
|
+
schema_version: 1,
|
|
677
|
+
variant,
|
|
678
|
+
state: "invalid",
|
|
679
|
+
evaluationType: group.type,
|
|
680
|
+
prompt: group.input.prompt ?? null,
|
|
681
|
+
prompts: group.input.turns ?? null,
|
|
682
|
+
activation: unavailableActivation(),
|
|
683
|
+
response: "",
|
|
684
|
+
transcript: [],
|
|
685
|
+
error: { kind: "setup", message },
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function persistenceFailedRun(run, error) {
|
|
690
|
+
return {
|
|
691
|
+
...run,
|
|
692
|
+
state: "infrastructure-failed",
|
|
693
|
+
error: {
|
|
694
|
+
kind: "persistence",
|
|
695
|
+
message: error instanceof Error ? error.message : String(error),
|
|
696
|
+
cause: run.error ?? null,
|
|
697
|
+
},
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function infrastructureFailedRun(group, variant, error) {
|
|
702
|
+
return {
|
|
703
|
+
schema_version: 1,
|
|
704
|
+
variant,
|
|
705
|
+
state: "infrastructure-failed",
|
|
706
|
+
evaluationType: group.type,
|
|
707
|
+
prompt: group.input.prompt ?? null,
|
|
708
|
+
prompts: group.input.turns ?? null,
|
|
709
|
+
activation: unavailableActivation(),
|
|
710
|
+
response: "",
|
|
711
|
+
transcript: [],
|
|
712
|
+
error: { kind: "infrastructure", message: error instanceof Error ? error.message : String(error) },
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function unavailableActivation() {
|
|
717
|
+
return {
|
|
718
|
+
targetRead: null,
|
|
719
|
+
firstReadTurn: null,
|
|
720
|
+
readTurns: [],
|
|
721
|
+
successfulReadPaths: [],
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// An unreadable run file leaves its identity unavailable instead of escaping the whole invocation.
|
|
726
|
+
async function runFileIdentity(file) {
|
|
727
|
+
try {
|
|
728
|
+
return await fileIdentity(file);
|
|
729
|
+
} catch (error) {
|
|
730
|
+
return { path: file, sha256: null, error: errorMessage(error) };
|
|
731
|
+
}
|
|
732
|
+
}
|