@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.
Files changed (230) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/README.md +9 -8
  3. package/README.vi.md +9 -5
  4. package/adapters/web-frontend/profile.json +2 -4
  5. package/architecture/layers.json +18 -3
  6. package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
  7. package/benchmarks/deep-logic-v1/grade.mjs +272 -10
  8. package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
  9. package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
  10. package/benchmarks/deep-logic-v1/project/package.json +1 -1
  11. package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
  12. package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
  13. package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
  14. package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
  15. package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
  16. package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
  17. package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
  18. package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
  19. package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
  20. package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
  21. package/benchmarks/deep-logic-v1/suite.json +51 -3
  22. package/benchmarks/deep-logic-v1/variant.mjs +22 -1
  23. package/benchmarks/production-v1/spend-control.v1.json +68 -0
  24. package/benchmarks/production-v1/suite.json +42 -3
  25. package/catalog/capabilities.json +6 -6
  26. package/docs/command-reference-vietnamese.md +6 -6
  27. package/docs/context-engine.md +7 -1
  28. package/docs/distribution-standard.md +7 -7
  29. package/docs/en/architecture.md +1 -0
  30. package/docs/languages.json +4 -4
  31. package/docs/model-options.md +8 -5
  32. package/docs/operator-manual-vietnamese.md +13 -11
  33. package/docs/project-adapters.md +2 -2
  34. package/docs/publishing-for-teams.md +9 -0
  35. package/docs/quality-benchmark.md +271 -62
  36. package/docs/release-install-policy.md +10 -9
  37. package/docs/runtime-policy-design.md +4 -4
  38. package/docs/runtime-quality-baseline.md +13 -6
  39. package/docs/security-threat-model.md +2 -2
  40. package/docs/task-lifecycle-vietnamese.md +2 -2
  41. package/docs/team-onboarding.md +2 -2
  42. package/docs/usage-observability.md +7 -2
  43. package/docs/vi/architecture.md +1 -0
  44. package/docs-site/architecture.html +1 -1
  45. package/docs-site/benchmark.html +1 -1
  46. package/docs-site/commands.html +1 -1
  47. package/docs-site/context-engine.html +1 -1
  48. package/docs-site/en/architecture.html +1 -1
  49. package/docs-site/en/benchmark.html +1 -1
  50. package/docs-site/en/commands.html +1 -1
  51. package/docs-site/en/context-engine.html +1 -1
  52. package/docs-site/en/index.html +1 -1
  53. package/docs-site/en/mcp-approval.html +1 -1
  54. package/docs-site/en/mcp-auth.html +1 -1
  55. package/docs-site/en/mcp-servers.html +1 -1
  56. package/docs-site/en/mcp.html +1 -1
  57. package/docs-site/en/permissions.html +1 -1
  58. package/docs-site/en/profiles.html +1 -1
  59. package/docs-site/en/quickstart.html +1 -1
  60. package/docs-site/en/runtime.html +1 -1
  61. package/docs-site/en/scope.html +1 -1
  62. package/docs-site/en/security.html +1 -1
  63. package/docs-site/en/team.html +1 -1
  64. package/docs-site/en/whats-new.html +1 -1
  65. package/docs-site/en/workflows.html +1 -1
  66. package/docs-site/index.html +1 -1
  67. package/docs-site/mcp-approval.html +1 -1
  68. package/docs-site/mcp-auth.html +1 -1
  69. package/docs-site/mcp-servers.html +1 -1
  70. package/docs-site/mcp.html +1 -1
  71. package/docs-site/permissions.html +1 -1
  72. package/docs-site/profiles.html +1 -1
  73. package/docs-site/quickstart.html +1 -1
  74. package/docs-site/runtime.html +1 -1
  75. package/docs-site/scope.html +1 -1
  76. package/docs-site/security.html +1 -1
  77. package/docs-site/team.html +1 -1
  78. package/docs-site/whats-new.html +1 -1
  79. package/docs-site/workflows.html +1 -1
  80. package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
  81. package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
  82. package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
  83. package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
  84. package/evals/fs4-readiness-matrix.v1.json +2 -2
  85. package/evals/fs5-causal-arm.v1.json +2 -2
  86. package/evals/fs5-pilot-protocol.v1.json +2 -2
  87. package/evals/fs5-pilot-protocol.v4.json +6 -5
  88. package/evals/fs5-pilot-protocol.v5.json +7 -6
  89. package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
  90. package/package.json +3 -1
  91. package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
  92. package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
  93. package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
  94. package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
  95. package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
  96. package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
  97. package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
  98. package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
  99. package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
  100. package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
  101. package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
  102. package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
  103. package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
  104. package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
  105. package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
  106. package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
  107. package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
  108. package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
  109. package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
  110. package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
  111. package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
  112. package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
  113. package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
  114. package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
  115. package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
  116. package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
  117. package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
  118. package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
  119. package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
  120. package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
  121. package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
  122. package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
  123. package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
  124. package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
  125. package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
  126. package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
  127. package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
  128. package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
  129. package/packages/piagent-core/extensions/context-engine.js +148 -145
  130. package/packages/piagent-core/extensions/context-evidence.js +48 -0
  131. package/packages/piagent-core/extensions/context-import-links.js +296 -0
  132. package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
  133. package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
  134. package/packages/piagent-core/extensions/criterion-graph.js +78 -8
  135. package/packages/piagent-core/extensions/guard-types.ts +3 -0
  136. package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
  137. package/packages/piagent-core/extensions/piagent-guard.ts +250 -43
  138. package/packages/piagent-core/extensions/policy-core.js +1 -1
  139. package/packages/piagent-core/extensions/task-contract-view.js +17 -3
  140. package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
  141. package/packages/piagent-core/extensions/task-state.js +40 -134
  142. package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
  143. package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
  144. package/packages/piagent-core/package.json +1 -1
  145. package/packages/piagent-core/policies/base-policy.json +3 -0
  146. package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
  147. package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
  148. package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
  149. package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
  150. package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
  151. package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
  152. package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
  153. package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
  154. package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
  155. package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
  156. package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
  157. package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
  158. package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
  159. package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
  160. package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +9 -3
  161. package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
  162. package/packages/piagent-core/runtime/inspection/approval-broker.ts +29 -5
  163. package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
  164. package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
  165. package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
  166. package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
  167. package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
  168. package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
  169. package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
  170. package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
  171. package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
  172. package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
  173. package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
  174. package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
  175. package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
  176. package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
  177. package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
  178. package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
  179. package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
  180. package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
  181. package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
  182. package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
  183. package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
  184. package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
  185. package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
  186. package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
  187. package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
  188. package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
  189. package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
  190. package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
  191. package/packages/piagent-core/security/sensitive-data.js +13 -218
  192. package/packages/piagent-core/security/sensitive-project-file.js +42 -0
  193. package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
  194. package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
  195. package/packages/piagent-core/security/sensitive-text.js +234 -0
  196. package/packages/piagent-core/subagents/piagent-worker.md +1 -1
  197. package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
  198. package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
  199. package/packages/piagent-webui/dist/client/index.html +1 -1
  200. package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
  201. package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
  202. package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
  203. package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
  204. package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
  205. package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
  206. package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
  207. package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
  208. package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
  209. package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -120
  210. package/packages/piagent-webui/server/loopback-server.ts +8 -0
  211. package/packages/piagent-webui/server/transcript-projection.ts +22 -4
  212. package/packages/piagent-webui/shared/text-visibility.ts +10 -0
  213. package/schemas/piagent-webui/catalog-v1.json +9 -0
  214. package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
  215. package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
  216. package/schemas/task-contract.schema.json +12 -1
  217. package/scripts/benchmark-runner-core.mjs +355 -359
  218. package/scripts/benchmark-runner-finalization.mjs +309 -0
  219. package/scripts/benchmark-runner-host-readiness.mjs +94 -0
  220. package/scripts/benchmark-runner-support.mjs +200 -0
  221. package/scripts/benchmark-session.mjs +95 -41
  222. package/scripts/configure-model-scope.sh +4 -3
  223. package/scripts/helper-evaluation.mjs +1 -1
  224. package/scripts/install-global.sh +2 -2
  225. package/scripts/pi-usage-history.mjs +37 -10
  226. package/scripts/piagent-dashboard.mjs +13 -1
  227. package/scripts/setup.sh +2 -2
  228. package/scripts/verify-local.sh +5 -1
  229. package/templates/global/settings.json +2 -1
  230. 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
+ }