@piagent/platform 1.5.4 → 1.6.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/CHANGELOG.md +136 -0
- package/README.md +9 -8
- package/README.vi.md +9 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +68 -0
- package/benchmarks/production-v1/suite.json +42 -3
- package/catalog/capabilities.json +6 -6
- package/docs/command-reference-vietnamese.md +6 -6
- package/docs/context-engine.md +7 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +1 -0
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +13 -11
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +9 -0
- package/docs/quality-benchmark.md +271 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v4.json +6 -5
- package/evals/fs5-pilot-protocol.v5.json +7 -6
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/package.json +3 -1
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +148 -145
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
- package/packages/piagent-core/extensions/criterion-graph.js +78 -8
- package/packages/piagent-core/extensions/guard-types.ts +3 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +250 -43
- package/packages/piagent-core/extensions/policy-core.js +1 -1
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
- package/packages/piagent-core/extensions/task-state.js +40 -134
- package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +3 -0
- package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
- package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +9 -3
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +29 -5
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
- package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
- package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
- package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- package/packages/piagent-core/subagents/piagent-worker.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -120
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +22 -4
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- package/schemas/task-contract.schema.json +12 -1
- package/scripts/benchmark-runner-core.mjs +355 -359
- package/scripts/benchmark-runner-finalization.mjs +309 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/helper-evaluation.mjs +1 -1
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/piagent-dashboard.mjs +13 -1
- package/scripts/setup.sh +2 -2
- package/scripts/verify-local.sh +5 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
renderBenchmarkHtml,
|
|
6
|
+
renderBenchmarkText,
|
|
7
|
+
summarizeBenchmark
|
|
8
|
+
} from "../packages/piagent-core/benchmark/benchmark-core.js";
|
|
9
|
+
import { codexModelName } from "../packages/piagent-core/benchmark/benchmark-codex.js";
|
|
10
|
+
import { benchmarkTrustChecklist } from "../packages/piagent-core/benchmark/benchmark-matrix.js";
|
|
11
|
+
import { applyBenchmarkClaimRestrictions } from "../packages/piagent-core/benchmark/benchmark-claim-restrictions.js";
|
|
12
|
+
import { piagentTreatment } from "../packages/piagent-core/benchmark/benchmark-runtime.js";
|
|
13
|
+
import { cleanupBenchmarkPiRuntimeHome } from "../packages/piagent-core/benchmark/benchmark-pi-home.js";
|
|
14
|
+
import { benchmarkSuiteCoverage } from "../packages/piagent-core/benchmark/benchmark-runner-policy.js";
|
|
15
|
+
import {
|
|
16
|
+
benchmarkHostReadinessPolicyDigest,
|
|
17
|
+
summarizeBenchmarkHostReadinessHistory
|
|
18
|
+
} from "../packages/piagent-core/benchmark/benchmark-host-readiness.js";
|
|
19
|
+
import {
|
|
20
|
+
cleanupUnretainedWorkspaces,
|
|
21
|
+
writeBenchmarkAbort,
|
|
22
|
+
writeBenchmarkRunManifest,
|
|
23
|
+
writePrivate,
|
|
24
|
+
writePrivateAtomic
|
|
25
|
+
} from "../packages/piagent-core/benchmark/benchmark-forensics.js";
|
|
26
|
+
import {
|
|
27
|
+
buildBenchmarkStageDiagnostic,
|
|
28
|
+
pendProductionStageControl
|
|
29
|
+
} from "../packages/piagent-core/benchmark/benchmark-stage-diagnostic.js";
|
|
30
|
+
import {
|
|
31
|
+
assertBenchmarkLedgerBinding,
|
|
32
|
+
inspectBenchmarkLedger,
|
|
33
|
+
validateBenchmarkLedgerPrefix
|
|
34
|
+
} from "../packages/piagent-core/benchmark/benchmark-ledger.js";
|
|
35
|
+
import {
|
|
36
|
+
completedBenchmarkRecord,
|
|
37
|
+
expectedBenchmarkRecord
|
|
38
|
+
} from "../packages/piagent-core/benchmark/benchmark-record-validation.js";
|
|
39
|
+
import { recoverOrphanedBenchmarkAttempts } from "../packages/piagent-core/benchmark/benchmark-resume-recovery.js";
|
|
40
|
+
import { benchmarkResumeCommand, benchmarkRunKey } from "./benchmark-runner-support.mjs";
|
|
41
|
+
|
|
42
|
+
export function finalizeBenchmarkRun(context) {
|
|
43
|
+
const {
|
|
44
|
+
assuranceEvidence,
|
|
45
|
+
bootstrapMetadata,
|
|
46
|
+
candidateGuard,
|
|
47
|
+
canonicalProductionSuite,
|
|
48
|
+
codexCredentialBridge,
|
|
49
|
+
comparison,
|
|
50
|
+
configurationDigest,
|
|
51
|
+
declaredScenarioCount,
|
|
52
|
+
detachPiRuntimeHome,
|
|
53
|
+
environmentPolicy,
|
|
54
|
+
executionGuard,
|
|
55
|
+
fatalExecutionReceipt,
|
|
56
|
+
fatalRunError,
|
|
57
|
+
finalizationReceipt,
|
|
58
|
+
fullOrder,
|
|
59
|
+
interruptedSignal,
|
|
60
|
+
ledgerBinding,
|
|
61
|
+
ledgerPath,
|
|
62
|
+
lifecycles,
|
|
63
|
+
manifest,
|
|
64
|
+
options,
|
|
65
|
+
packageVersion,
|
|
66
|
+
pauseReason,
|
|
67
|
+
piRuntimeHome,
|
|
68
|
+
preservePiRuntime,
|
|
69
|
+
productionAllStageBoundaries,
|
|
70
|
+
productionHostReadinessPolicy,
|
|
71
|
+
productionSpendControlled,
|
|
72
|
+
rootSeed,
|
|
73
|
+
rootSeedDigest,
|
|
74
|
+
runId,
|
|
75
|
+
runRoot,
|
|
76
|
+
runs,
|
|
77
|
+
runtime,
|
|
78
|
+
runtimeCommands,
|
|
79
|
+
source,
|
|
80
|
+
startedAt,
|
|
81
|
+
suite,
|
|
82
|
+
suiteDigest,
|
|
83
|
+
suiteIdentity,
|
|
84
|
+
terminalStop
|
|
85
|
+
} = context;
|
|
86
|
+
|
|
87
|
+
if (interruptedSignal) {
|
|
88
|
+
const provenanceStamp = executionGuard.stamp("interrupted", [piRuntimeHome]);
|
|
89
|
+
detachPiRuntimeHome();
|
|
90
|
+
recoverOrphanedBenchmarkAttempts({ runRoot, manifest, fullOrder, completedKeys: new Set(runs.map(benchmarkRunKey)) });
|
|
91
|
+
cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
|
|
92
|
+
const interruptedCompletedRuns = runs.filter(completedBenchmarkRecord).length;
|
|
93
|
+
const resumeCommand = benchmarkResumeCommand({
|
|
94
|
+
runRoot,
|
|
95
|
+
productionSpendControlled,
|
|
96
|
+
completedRuns: interruptedCompletedRuns,
|
|
97
|
+
stageControl: manifest.stageControl,
|
|
98
|
+
stageBoundaries: productionAllStageBoundaries
|
|
99
|
+
});
|
|
100
|
+
writePrivateAtomic(path.join(runRoot, "interrupted.json"), `${JSON.stringify({ schemaVersion: 1, runId, signal: interruptedSignal, completedRuns: interruptedCompletedRuns, expectedRuns: fullOrder.length, interruptedAt: new Date().toISOString(), resumeCommand, ledger: ledgerBinding, provenanceStamp }, null, 2)}\n`);
|
|
101
|
+
process.stderr.write(`Benchmark interrupted by ${interruptedSignal}. Partial ledger: ${ledgerPath}\n`);
|
|
102
|
+
process.exitCode = interruptedSignal === "SIGINT" ? 130 : interruptedSignal === "SIGHUP" ? 129 : 143;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (fatalRunError) {
|
|
106
|
+
const provenanceStamp = finalizationReceipt?.stamp ?? fatalExecutionReceipt?.stamp ?? executionGuard.stamp("fatal", piRuntimeHome ? [piRuntimeHome] : []);
|
|
107
|
+
if (!preservePiRuntime) cleanupBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome, piRuntimeHome);
|
|
108
|
+
detachPiRuntimeHome();
|
|
109
|
+
recoverOrphanedBenchmarkAttempts({ runRoot, manifest, fullOrder, completedKeys: new Set(runs.map(benchmarkRunKey)) });
|
|
110
|
+
cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
|
|
111
|
+
const provenanceFailure = writeBenchmarkAbort(runRoot, { runId, completedRuns: runs.filter(completedBenchmarkRecord).length, expectedRuns: fullOrder.length }, fatalRunError, {
|
|
112
|
+
ledger: ledgerBinding,
|
|
113
|
+
provenanceStamp
|
|
114
|
+
});
|
|
115
|
+
process.stderr.write(provenanceFailure
|
|
116
|
+
? `Benchmark aborted because candidate provenance changed. Partial ledger: ${ledgerPath}\n`
|
|
117
|
+
: `Benchmark aborted after an infrastructure error. Partial ledger: ${ledgerPath}\n`);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (terminalStop) {
|
|
122
|
+
cleanupBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome, piRuntimeHome);
|
|
123
|
+
detachPiRuntimeHome();
|
|
124
|
+
cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
|
|
125
|
+
for (const marker of ["paused.json", "stage-diagnostic.json", "interrupted.json", "aborted.json"]) fs.rmSync(path.join(runRoot, marker), { force: true });
|
|
126
|
+
writePrivateAtomic(path.join(runRoot, "stopped.json"), `${JSON.stringify({ ...terminalStop, runId, completedRuns: runs.filter(completedBenchmarkRecord).length, expectedRuns: fullOrder.length, stoppedAt: new Date().toISOString(), resumeAllowed: false, ledger: ledgerBinding, provenanceStamp: finalizationReceipt.stamp }, null, 2)}\n`);
|
|
127
|
+
process.stderr.write(`Benchmark terminal-stopped after paired outcome-floor failure. Partial ledger: ${ledgerPath}\n`);
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const completedRuns = runs.filter(completedBenchmarkRecord);
|
|
133
|
+
const summarizedHostReadinessHistory = productionHostReadinessPolicy
|
|
134
|
+
? summarizeBenchmarkHostReadinessHistory({
|
|
135
|
+
policy: productionHostReadinessPolicy,
|
|
136
|
+
receipts: manifest.hostReadinessReceipts,
|
|
137
|
+
completedRuns: completedRuns.length,
|
|
138
|
+
authorizedThroughRuns: manifest.stageControl?.authorizedThroughRuns,
|
|
139
|
+
runId: manifest.runId,
|
|
140
|
+
configurationDigest: manifest.configurationDigest,
|
|
141
|
+
stageBoundaries: productionAllStageBoundaries
|
|
142
|
+
})
|
|
143
|
+
: null;
|
|
144
|
+
const hostReadinessPolicyBindingValid = !productionHostReadinessPolicy
|
|
145
|
+
|| manifest.hostReadinessPolicyDigest === benchmarkHostReadinessPolicyDigest(productionHostReadinessPolicy);
|
|
146
|
+
const hostReadinessHistory = summarizedHostReadinessHistory && !hostReadinessPolicyBindingValid
|
|
147
|
+
? {
|
|
148
|
+
...summarizedHostReadinessHistory,
|
|
149
|
+
valid: false,
|
|
150
|
+
ready: false,
|
|
151
|
+
errors: [...summarizedHostReadinessHistory.errors, "host-readiness-manifest-policy-digest-mismatch"]
|
|
152
|
+
}
|
|
153
|
+
: summarizedHostReadinessHistory;
|
|
154
|
+
if (completedRuns.length < fullOrder.length) {
|
|
155
|
+
detachPiRuntimeHome();
|
|
156
|
+
cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
|
|
157
|
+
const resumeCommand = benchmarkResumeCommand({
|
|
158
|
+
runRoot,
|
|
159
|
+
productionSpendControlled,
|
|
160
|
+
completedRuns: completedRuns.length,
|
|
161
|
+
stageControl: manifest.stageControl,
|
|
162
|
+
stageBoundaries: productionAllStageBoundaries
|
|
163
|
+
});
|
|
164
|
+
const paused = {
|
|
165
|
+
schemaVersion: 1,
|
|
166
|
+
runId,
|
|
167
|
+
reason: pauseReason ?? "partial-run",
|
|
168
|
+
completedRuns: completedRuns.length,
|
|
169
|
+
expectedRuns: fullOrder.length,
|
|
170
|
+
remainingRuns: fullOrder.length - completedRuns.length,
|
|
171
|
+
pausedAt: new Date().toISOString(),
|
|
172
|
+
resumeCommand,
|
|
173
|
+
ledger: ledgerBinding,
|
|
174
|
+
provenanceStamp: executionGuard.stamp("paused")
|
|
175
|
+
};
|
|
176
|
+
const stageDiagnostic = buildBenchmarkStageDiagnostic({
|
|
177
|
+
runId,
|
|
178
|
+
reason: paused.reason,
|
|
179
|
+
runs: completedRuns,
|
|
180
|
+
fullOrder,
|
|
181
|
+
candidateSurface: comparison.candidateSurface,
|
|
182
|
+
baselineSurface: comparison.baselineSurface,
|
|
183
|
+
requestedModel: options.model,
|
|
184
|
+
requestedThinking: options.thinking,
|
|
185
|
+
suite,
|
|
186
|
+
manifest,
|
|
187
|
+
hostReadinessPolicy: productionHostReadinessPolicy,
|
|
188
|
+
hostReadinessStageBoundaries: productionAllStageBoundaries
|
|
189
|
+
});
|
|
190
|
+
if (productionSpendControlled) {
|
|
191
|
+
manifest.stageControl = pendProductionStageControl(manifest.stageControl, {
|
|
192
|
+
reason: paused.reason,
|
|
193
|
+
completedRuns: completedRuns.length,
|
|
194
|
+
ledger: ledgerBinding
|
|
195
|
+
});
|
|
196
|
+
manifest.ledger = ledgerBinding;
|
|
197
|
+
writeBenchmarkRunManifest(runRoot, manifest);
|
|
198
|
+
for (const marker of ["interrupted.json", "aborted.json"]) fs.rmSync(path.join(runRoot, marker), { force: true });
|
|
199
|
+
}
|
|
200
|
+
paused.stageDiagnostic = {
|
|
201
|
+
path: "stage-diagnostic.json",
|
|
202
|
+
diagnosticOnly: true,
|
|
203
|
+
claimEligible: false,
|
|
204
|
+
stageAdvanceAllowed: stageDiagnostic.stageAdvanceAllowed
|
|
205
|
+
};
|
|
206
|
+
writePrivateAtomic(path.join(runRoot, "stage-diagnostic.json"), `${JSON.stringify(stageDiagnostic, null, 2)}\n`);
|
|
207
|
+
writePrivateAtomic(path.join(runRoot, "paused.json"), `${JSON.stringify(paused, null, 2)}\n`);
|
|
208
|
+
process.stdout.write(`Benchmark paused after ${completedRuns.length}/${fullOrder.length} completed sessions (${paused.reason}).\nStage advance: ${stageDiagnostic.stageAdvanceAllowed ? "allowed by diagnostic" : `blocked (${stageDiagnostic.blockingReasons.join(", ")})`}\nResume: ${paused.resumeCommand}\nStage diagnostic: ${path.join(runRoot, "stage-diagnostic.json")}\nPartial ledger: ${ledgerPath}\n`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
cleanupBenchmarkPiRuntimeHome(bootstrapMetadata.piAgentHome, piRuntimeHome);
|
|
213
|
+
detachPiRuntimeHome();
|
|
214
|
+
const report = summarizeBenchmark({
|
|
215
|
+
suite,
|
|
216
|
+
canonicalProductionSuite,
|
|
217
|
+
runId,
|
|
218
|
+
startedAt,
|
|
219
|
+
completedAt: new Date().toISOString(),
|
|
220
|
+
repeats: options.repeats,
|
|
221
|
+
environment: {
|
|
222
|
+
platformVersion: packageVersion,
|
|
223
|
+
suiteDigest,
|
|
224
|
+
variantRootSeed: suite.scenarios.some((scenario) => scenario.variantGenerator) ? rootSeed : null,
|
|
225
|
+
variantRootSeedDigest: suite.scenarios.some((scenario) => scenario.variantGenerator) ? rootSeedDigest : null,
|
|
226
|
+
executionOrder: suite.schemaVersion === 2 ? "seeded-paired-block-randomized" : "paired-alternating",
|
|
227
|
+
replaySource: options.replaySource ?? null,
|
|
228
|
+
profile: suite.profile,
|
|
229
|
+
requestedModel: options.model ?? null,
|
|
230
|
+
requestedThinking: options.thinking ?? null,
|
|
231
|
+
piagentTreatment: piagentTreatment(options.piagentTreatment),
|
|
232
|
+
treatmentBaseline: lifecycles.length === 1 && lifecycles[0] === "steady-state"
|
|
233
|
+
? options.surfaces.includes("codex-cli")
|
|
234
|
+
? "piagent-initialized-and-onboarded; codex-clean-fixture"
|
|
235
|
+
: "initialized-and-onboarded"
|
|
236
|
+
: "scenario-defined-mixed-lifecycle",
|
|
237
|
+
timeoutSeconds: options.timeoutSeconds,
|
|
238
|
+
nodeVersion: process.version,
|
|
239
|
+
piVersion: runtime.piVersion,
|
|
240
|
+
codexVersion: runtime.codexVersion ?? null,
|
|
241
|
+
codexMode: options.surfaces.includes("codex-cli") ? options.codexMode : null,
|
|
242
|
+
codexAuth: runtime.codexAuth ?? null,
|
|
243
|
+
codexIsolation: options.surfaces.includes("codex-cli")
|
|
244
|
+
? options.codexMode === "controlled" ? "per-session-temporary-home" : "operator-home"
|
|
245
|
+
: null,
|
|
246
|
+
codexCredentialBridge: options.surfaces.includes("codex-cli") ? codexCredentialBridge : null,
|
|
247
|
+
codexGlobalInstructions: options.surfaces.includes("codex-cli")
|
|
248
|
+
? options.codexMode === "controlled" ? "excluded" : "operator-home"
|
|
249
|
+
: null,
|
|
250
|
+
piGlobalInstructions: "excluded",
|
|
251
|
+
piAgentHome: manifest.piAgentHome,
|
|
252
|
+
usageIntegrity: manifest.tokenClaimsUnavailableReason ?? "measured",
|
|
253
|
+
codexDisabledFeatures: runtime.codexDisabledFeatures,
|
|
254
|
+
surfaces: options.surfaces,
|
|
255
|
+
scenarioSelection: options.scenarioIds ?? null,
|
|
256
|
+
suiteCoverage: benchmarkSuiteCoverage(declaredScenarioCount, suite.scenarios.length),
|
|
257
|
+
surfaceModels: options.surfaces.includes("codex-cli") ? {
|
|
258
|
+
piagent: options.model,
|
|
259
|
+
"codex-cli": codexModelName(options.model)
|
|
260
|
+
} : { "raw-pi": options.model ?? null, piagent: options.model ?? null },
|
|
261
|
+
modelParityEvidence: options.surfaces.includes("codex-cli") ? "command-line-pinned" : "session-reported",
|
|
262
|
+
gitVersion: runtime.gitVersion,
|
|
263
|
+
source,
|
|
264
|
+
candidateProvenance: candidateGuard.report(),
|
|
265
|
+
suiteIdentity,
|
|
266
|
+
runtimeCommands,
|
|
267
|
+
configurationDigest,
|
|
268
|
+
hostReadinessHistory,
|
|
269
|
+
environmentPolicy,
|
|
270
|
+
runtimeDependencies: bootstrapMetadata?.runtimeDependencies ?? null,
|
|
271
|
+
assuranceEvidence
|
|
272
|
+
},
|
|
273
|
+
runs: completedRuns,
|
|
274
|
+
...comparison
|
|
275
|
+
});
|
|
276
|
+
report.ledger = ledgerBinding;
|
|
277
|
+
applyBenchmarkClaimRestrictions(report, { tokenReason: manifest.tokenClaimsUnavailableReason, replaySource: options.replaySource, codexMode: options.codexMode, surfaces: options.surfaces });
|
|
278
|
+
report.trustChecklist = benchmarkTrustChecklist(report);
|
|
279
|
+
const text = renderBenchmarkText(report);
|
|
280
|
+
const reportLedger = inspectBenchmarkLedger(ledgerPath);
|
|
281
|
+
assertBenchmarkLedgerBinding(ledgerBinding, reportLedger.binding, "benchmark report ledger");
|
|
282
|
+
validateBenchmarkLedgerPrefix(reportLedger.records, fullOrder, (record, index, expected) => expectedBenchmarkRecord(record, index, expected, runId, suite, configurationDigest));
|
|
283
|
+
writePrivate(path.join(runRoot, "report.html"), renderBenchmarkHtml(report));
|
|
284
|
+
writePrivate(path.join(runRoot, "summary.txt"), text);
|
|
285
|
+
cleanupUnretainedWorkspaces(runRoot, options.keepWorkspaces);
|
|
286
|
+
const prepublishReceipt = executionGuard.receipt("prepublish");
|
|
287
|
+
const prepublishError = prepublishReceipt.error;
|
|
288
|
+
if (prepublishError) {
|
|
289
|
+
writeBenchmarkAbort(runRoot, { runId, completedRuns: completedRuns.length, expectedRuns: fullOrder.length }, prepublishError, { ledger: ledgerBinding, provenanceStamp: prepublishReceipt.stamp });
|
|
290
|
+
throw prepublishError;
|
|
291
|
+
}
|
|
292
|
+
for (const marker of ["paused.json", "stage-diagnostic.json", "interrupted.json", "aborted.json", "stopped.json"]) fs.rmSync(path.join(runRoot, marker), { force: true });
|
|
293
|
+
writePrivateAtomic(path.join(runRoot, "report.json"), `${JSON.stringify(report, null, 2)}\n`);
|
|
294
|
+
process.stdout.write(options.json ? `${JSON.stringify(report, null, 2)}\n` : text);
|
|
295
|
+
process.stdout.write(`Reports: ${runRoot}\n`);
|
|
296
|
+
const productionTokenClaimFailed = canonicalProductionSuite
|
|
297
|
+
&& suite.releaseGate?.requireEfficiencyClaim === true
|
|
298
|
+
&& report.comparison.tokenClaimAllowed !== true;
|
|
299
|
+
if (
|
|
300
|
+
report.comparison.qualityGate === false
|
|
301
|
+
|| report.comparison.safetyGate === false
|
|
302
|
+
|| report.comparison.reliabilityGate === false
|
|
303
|
+
|| report.comparison.qualityNonInferior === false
|
|
304
|
+
|| report.comparison.workflowGate === false
|
|
305
|
+
|| report.comparison.categoryGate === false
|
|
306
|
+
|| report.comparison.suiteGate?.passed === false
|
|
307
|
+
|| productionTokenClaimFailed
|
|
308
|
+
) process.exitCode = 1;
|
|
309
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
benchmarkHostReadinessPolicyDigest,
|
|
3
|
+
benchmarkHostReadinessStartValidationErrors,
|
|
4
|
+
collectBenchmarkHostReadinessReceipt,
|
|
5
|
+
summarizeBenchmarkHostReadinessHistory
|
|
6
|
+
} from "../packages/piagent-core/benchmark/benchmark-host-readiness.js";
|
|
7
|
+
|
|
8
|
+
export { benchmarkHostReadinessPolicyDigest };
|
|
9
|
+
|
|
10
|
+
function readinessReasons(summary) {
|
|
11
|
+
return [...summary.errors, ...summary.blockingReasons].join(", ")
|
|
12
|
+
|| "invalid-host-readiness-history";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function benchmarkHostError(message, code) {
|
|
16
|
+
const error = new Error(message);
|
|
17
|
+
error.code = code;
|
|
18
|
+
error.exitCode = 1;
|
|
19
|
+
return error;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function assertExistingBenchmarkHostReadiness(input) {
|
|
23
|
+
const summary = summarizeBenchmarkHostReadinessHistory(input);
|
|
24
|
+
if (!summary.valid || !summary.ready) {
|
|
25
|
+
throw benchmarkHostError(
|
|
26
|
+
`Cannot resume production benchmark: host-readiness history is missing or invalid (${readinessReasons(summary)}). Start a new staged run.`,
|
|
27
|
+
"BENCHMARK_HOST_READINESS_HISTORY_INVALID"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return summary;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function collectReadyBenchmarkHostReadiness({
|
|
34
|
+
policy,
|
|
35
|
+
completedRuns,
|
|
36
|
+
authorizedThroughRuns,
|
|
37
|
+
runId,
|
|
38
|
+
configurationDigest,
|
|
39
|
+
stageBoundaries,
|
|
40
|
+
existingReceipts = [],
|
|
41
|
+
collectionDependencies
|
|
42
|
+
}) {
|
|
43
|
+
const receipt = await collectBenchmarkHostReadinessReceipt({
|
|
44
|
+
policy,
|
|
45
|
+
completedRuns,
|
|
46
|
+
authorizedThroughRuns,
|
|
47
|
+
runId,
|
|
48
|
+
configurationDigest
|
|
49
|
+
}, collectionDependencies);
|
|
50
|
+
if (receipt.status !== "ready") {
|
|
51
|
+
const sample = receipt.samples.at(-1);
|
|
52
|
+
throw benchmarkHostError(
|
|
53
|
+
`Production benchmark host is not ready (${receipt.failureReasons.join(", ") || "incomplete-host-readiness-evidence"}; normalized load ${sample?.normalizedLoad1 ?? "n/a"}, CPU idle ${sample?.cpuIdlePercent ?? "n/a"}%). No auth, tool, or provider preflight was started.`,
|
|
54
|
+
"BENCHMARK_HOST_NOT_READY"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
const prospective = summarizeBenchmarkHostReadinessHistory({
|
|
58
|
+
policy,
|
|
59
|
+
receipts: [...existingReceipts, receipt],
|
|
60
|
+
completedRuns,
|
|
61
|
+
authorizedThroughRuns,
|
|
62
|
+
runId,
|
|
63
|
+
configurationDigest,
|
|
64
|
+
stageBoundaries
|
|
65
|
+
});
|
|
66
|
+
if (!prospective.valid || !prospective.ready) {
|
|
67
|
+
throw benchmarkHostError(
|
|
68
|
+
`Production benchmark host-readiness history rejected the new receipt (${readinessReasons(prospective)}). No auth, tool, or provider preflight was started.`,
|
|
69
|
+
"BENCHMARK_HOST_READINESS_HISTORY_INVALID"
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return receipt;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function assertBenchmarkHostReadinessStartReady(receipt, {
|
|
76
|
+
policy,
|
|
77
|
+
runId,
|
|
78
|
+
configurationDigest,
|
|
79
|
+
nowMilliseconds = Date.now()
|
|
80
|
+
}) {
|
|
81
|
+
if (!receipt) return;
|
|
82
|
+
const errors = benchmarkHostReadinessStartValidationErrors(receipt, {
|
|
83
|
+
policy,
|
|
84
|
+
runId,
|
|
85
|
+
configurationDigest,
|
|
86
|
+
nowMilliseconds
|
|
87
|
+
});
|
|
88
|
+
if (errors.length > 0) {
|
|
89
|
+
throw benchmarkHostError(
|
|
90
|
+
`Production benchmark host-readiness receipt became stale or invalid before execution (${errors.join(", ")}). No provider session was started.`,
|
|
91
|
+
"BENCHMARK_HOST_READINESS_STALE"
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "node:readline/promises";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
|
|
8
|
+
import { productionStageResumeWindow } from "../packages/piagent-core/benchmark/benchmark-stage-diagnostic.js";
|
|
9
|
+
import { benchmarkLedgerCheckpoint, inspectBenchmarkLedger } from "../packages/piagent-core/benchmark/benchmark-ledger.js";
|
|
10
|
+
import { acquireBenchmarkRunLock } from "../packages/piagent-core/benchmark/benchmark-run-lock.js";
|
|
11
|
+
|
|
12
|
+
export function fail(message, code = 2) {
|
|
13
|
+
const error = new Error(message);
|
|
14
|
+
error.exitCode = code;
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function frozenRuntimeCommandsForFinalization(manifest, surfaces) {
|
|
19
|
+
const commands = manifest?.runtimeCommands;
|
|
20
|
+
const validIdentity = (identity) => identity?.schemaVersion === 1
|
|
21
|
+
&& identity.kind === "regular"
|
|
22
|
+
&& typeof identity.resolvedPath === "string"
|
|
23
|
+
&& /^[a-f0-9]{64}$/.test(String(identity.contentDigest ?? ""));
|
|
24
|
+
const required = ["pi", "node", "git", "bash", ...(surfaces.includes("codex-cli") ? ["codex"] : [])];
|
|
25
|
+
if (!commands || typeof commands !== "object" || required.some((label) => !validIdentity(commands[label]))) {
|
|
26
|
+
fail("Cannot finalize production benchmark provider-free: frozen runtime command identities are missing or invalid", 1);
|
|
27
|
+
}
|
|
28
|
+
if (!surfaces.includes("codex-cli") && commands.codex !== null) {
|
|
29
|
+
fail("Cannot finalize production benchmark provider-free: frozen Codex command identity does not match the surface list", 1);
|
|
30
|
+
}
|
|
31
|
+
return commands;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function isLegacyInvocation(argv) {
|
|
35
|
+
return argv.includes("--record") || argv.includes("--init");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function privateDirectory(target) {
|
|
39
|
+
fs.mkdirSync(target, { recursive: true, mode: 0o700 });
|
|
40
|
+
try { fs.chmodSync(target, 0o700); } catch { /* Non-POSIX filesystem. */ }
|
|
41
|
+
return target;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function defaultOutputRoot(bootstrapMetadata) {
|
|
45
|
+
if (bootstrapMetadata?.defaultOutputRoot) return bootstrapMetadata.defaultOutputRoot;
|
|
46
|
+
const agentRoot = process.env.PI_CODING_AGENT_DIR || path.join(os.homedir(), ".pi", "agent");
|
|
47
|
+
return path.join(agentRoot, "benchmarks", "piagent");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createRunId(suiteId) {
|
|
51
|
+
const timestamp = new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
52
|
+
return `${suiteId}-${timestamp}-${crypto.randomBytes(3).toString("hex")}`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function ensureEmptyOutput(target) {
|
|
56
|
+
if (fs.existsSync(target) && fs.readdirSync(target).length > 0) fail(`Output directory is not empty: ${target}`, 1);
|
|
57
|
+
return privateDirectory(target);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function benchmarkRunKey(value) {
|
|
61
|
+
return `${value.scenarioId ?? value.scenario?.id}\0${value.surface}\0${value.repeat}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function samePairedBlock(left, right) {
|
|
65
|
+
return left?.scenario?.id === right?.scenario?.id && left?.repeat === right?.repeat;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function pairedChunk(order, maximum) {
|
|
69
|
+
if (maximum === undefined || order.length <= maximum) return order;
|
|
70
|
+
let length = maximum;
|
|
71
|
+
while (length > 0 && samePairedBlock(order[length - 1], order[length])) length -= 1;
|
|
72
|
+
if (length === 0) fail(`--max-sessions ${maximum} would split the first paired benchmark block; increase the chunk size`, 1);
|
|
73
|
+
return order.slice(0, length);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function shellCommandArgument(value) {
|
|
77
|
+
const text = String(value);
|
|
78
|
+
if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(text)) return text;
|
|
79
|
+
return `'${text.replaceAll("'", `'"'"'`)}'`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function benchmarkResumeCommand({ runRoot, productionSpendControlled, completedRuns, stageControl, stageBoundaries }) {
|
|
83
|
+
const base = `piagent-benchmark --resume ${shellCommandArgument(runRoot)}`;
|
|
84
|
+
if (!productionSpendControlled) return `${base} --yes`;
|
|
85
|
+
const window = productionStageResumeWindow(stageControl, { completedRuns, stageBoundaries });
|
|
86
|
+
return window?.remainingSessions > 0
|
|
87
|
+
? `${base} --max-sessions ${window.remainingSessions} --yes`
|
|
88
|
+
: `${base} --yes`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function readJsonFile(file, label) {
|
|
92
|
+
try {
|
|
93
|
+
return JSON.parse(fs.readFileSync(file, "utf8"));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
fail(`Cannot read ${label} ${file}: ${error.message}`, 1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function resolveResumeRunRoot(input) {
|
|
100
|
+
const target = path.resolve(input);
|
|
101
|
+
try {
|
|
102
|
+
const stat = fs.statSync(target);
|
|
103
|
+
if (stat.isDirectory()) return target;
|
|
104
|
+
if (stat.isFile()) return path.dirname(target);
|
|
105
|
+
} catch {
|
|
106
|
+
fail(`Cannot resume benchmark; path does not exist: ${target}`, 1);
|
|
107
|
+
}
|
|
108
|
+
fail(`Cannot resume benchmark; path is not a file or directory: ${target}`, 1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function loadResumeState(input) {
|
|
112
|
+
const runRoot = resolveResumeRunRoot(input);
|
|
113
|
+
const releaseRunLock = acquireBenchmarkRunLock(runRoot, "resume-pending");
|
|
114
|
+
try {
|
|
115
|
+
const manifestPath = path.join(runRoot, "run-manifest.json");
|
|
116
|
+
if (!fs.existsSync(manifestPath)) {
|
|
117
|
+
fail(`Cannot resume ${runRoot}: missing run-manifest.json. This run was created before resume metadata was written, so its root seed cannot be recovered safely. Start a new run with --max-sessions or --max-runtime-minutes to make it resumable.`, 1);
|
|
118
|
+
}
|
|
119
|
+
const manifest = readJsonFile(manifestPath, "benchmark resume manifest");
|
|
120
|
+
if (fs.existsSync(path.join(runRoot, "stopped.json"))) fail(`Cannot resume ${runRoot}: the paired release stop is terminal`, 1);
|
|
121
|
+
if (manifest?.schemaVersion !== 1 || typeof manifest.runId !== "string") {
|
|
122
|
+
fail(`Cannot resume ${runRoot}: run-manifest.json has an unsupported shape`, 1);
|
|
123
|
+
}
|
|
124
|
+
const ledger = benchmarkLedgerCheckpoint(
|
|
125
|
+
manifest.ledger,
|
|
126
|
+
inspectBenchmarkLedger(path.join(runRoot, "runs.jsonl")),
|
|
127
|
+
"benchmark resume ledger"
|
|
128
|
+
);
|
|
129
|
+
const pendingPath = path.join(runRoot, "pending-record.json");
|
|
130
|
+
const pendingRecord = fs.existsSync(pendingPath) ? readJsonFile(pendingPath, "benchmark pending record") : null;
|
|
131
|
+
const measuredPath = path.join(runRoot, "measured-record-ready.json");
|
|
132
|
+
const measuredReady = fs.existsSync(measuredPath) ? readJsonFile(measuredPath, "measured benchmark record") : null;
|
|
133
|
+
return {
|
|
134
|
+
runRoot,
|
|
135
|
+
manifest,
|
|
136
|
+
completedRuns: ledger.records,
|
|
137
|
+
ledgerBinding: ledger.binding,
|
|
138
|
+
recoveredLedgerSuffix: ledger.recovered,
|
|
139
|
+
pendingRecord,
|
|
140
|
+
measuredReady,
|
|
141
|
+
releaseRunLock
|
|
142
|
+
};
|
|
143
|
+
} catch (error) {
|
|
144
|
+
releaseRunLock();
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function sameStringList(left, right) {
|
|
150
|
+
return Array.isArray(left)
|
|
151
|
+
&& Array.isArray(right)
|
|
152
|
+
&& left.length === right.length
|
|
153
|
+
&& left.every((value, index) => value === right[index]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function formatDuration(ms) {
|
|
157
|
+
const totalSeconds = Math.max(0, Math.round(ms / 1_000));
|
|
158
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
159
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
160
|
+
const seconds = totalSeconds % 60;
|
|
161
|
+
if (hours > 0) return `${hours}h${String(minutes).padStart(2, "0")}m`;
|
|
162
|
+
if (minutes > 0) return `${minutes}m${String(seconds).padStart(2, "0")}s`;
|
|
163
|
+
return `${seconds}s`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function executionOrder(suite, repeats, surfaces, rootSeed) {
|
|
167
|
+
const order = [];
|
|
168
|
+
for (let repeat = 1; repeat <= repeats; repeat += 1) {
|
|
169
|
+
const scenarios = suite.schemaVersion === 2
|
|
170
|
+
? [...suite.scenarios].sort((left, right) => {
|
|
171
|
+
const rank = (scenario) => crypto.createHmac("sha256", rootSeed).update(`order\0${repeat}\0${scenario.id}`).digest("hex");
|
|
172
|
+
return rank(left).localeCompare(rank(right));
|
|
173
|
+
})
|
|
174
|
+
: suite.scenarios;
|
|
175
|
+
for (const [index, scenario] of scenarios.entries()) {
|
|
176
|
+
const reverse = suite.schemaVersion === 2
|
|
177
|
+
? (crypto.createHmac("sha256", rootSeed).update(`surface\0${repeat}\0${scenario.id}`).digest()[0] & 1) === 1
|
|
178
|
+
: (repeat + index) % 2 !== 0;
|
|
179
|
+
const ordered = reverse ? [...surfaces].reverse() : surfaces;
|
|
180
|
+
for (const surface of ordered) order.push({ scenario, surface, repeat });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return order;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function confirmPlan(message) {
|
|
187
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) fail("Refusing to start billed model runs without --yes in a non-interactive terminal", 1);
|
|
188
|
+
const terminal = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
189
|
+
try {
|
|
190
|
+
const answer = await terminal.question(`${message}\nContinue? [y/N] `);
|
|
191
|
+
return /^(?:y|yes)$/i.test(answer.trim());
|
|
192
|
+
} finally {
|
|
193
|
+
terminal.close();
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function invokedAsEntrypoint(metaUrl, argvEntry) {
|
|
198
|
+
try { return fs.realpathSync(fileURLToPath(metaUrl)) === fs.realpathSync(argvEntry || ""); }
|
|
199
|
+
catch { return metaUrl === pathToFileURL(argvEntry || "").href; }
|
|
200
|
+
}
|