engineering-behavior-observatory 0.2.1

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 (178) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +151 -0
  3. package/contracts/codex-app-server-0.150.1/manifest.json +25 -0
  4. package/contracts/codex-app-server-0.150.1/schema/InitializeParams.json +84 -0
  5. package/contracts/codex-app-server-0.150.1/schema/JSONRPCMessage.json +137 -0
  6. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadReadParams.json +17 -0
  7. package/contracts/codex-app-server-0.150.1/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  8. package/contracts/codex-app-server-0.150.1/schema/v2/TurnInterruptParams.json +17 -0
  9. package/contracts/codex-app-server-0.150.1/types/AskForApproval.ts +5 -0
  10. package/contracts/codex-app-server-0.150.1/types/ClientNotification.ts +5 -0
  11. package/contracts/codex-app-server-0.150.1/types/SandboxMode.ts +5 -0
  12. package/contracts/codex-app-server-0.150.1/types/ThreadReadParams.ts +9 -0
  13. package/contracts/codex-app-server-0.150.1/types/TokenUsageBreakdown.ts +5 -0
  14. package/contracts/codex-app-server-0.150.1/types/TurnInterruptParams.ts +5 -0
  15. package/contracts/codex-app-server-0.153.4/manifest.json +27 -0
  16. package/contracts/codex-app-server-0.153.4/schema/JSONRPCMessage.json +137 -0
  17. package/contracts/codex-app-server-0.153.4/schema/v1/InitializeParams.json +84 -0
  18. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadReadParams.json +17 -0
  19. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadStartParams.json +515 -0
  20. package/contracts/codex-app-server-0.153.4/schema/v2/ThreadTokenUsageUpdatedNotification.json +82 -0
  21. package/contracts/codex-app-server-0.153.4/schema/v2/TurnInterruptParams.json +17 -0
  22. package/contracts/codex-app-server-0.153.4/schema/v2/TurnStartParams.json +921 -0
  23. package/contracts/codex-app-server-0.153.4/types/AskForApproval.ts +5 -0
  24. package/contracts/codex-app-server-0.153.4/types/ClientNotification.ts +5 -0
  25. package/contracts/codex-app-server-0.153.4/types/SandboxMode.ts +5 -0
  26. package/contracts/codex-app-server-0.153.4/types/ThreadReadParams.ts +12 -0
  27. package/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.ts +5 -0
  28. package/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.ts +5 -0
  29. package/contracts/openhands-agent-server-v1.44.1.json +65 -0
  30. package/contracts/openhands-agent-server-v1.46.0.json +46 -0
  31. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.d.ts +9 -0
  32. package/dist/contracts/codex-app-server-0.153.4/types/AskForApproval.js +2 -0
  33. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.d.ts +3 -0
  34. package/dist/contracts/codex-app-server-0.153.4/types/ClientNotification.js +2 -0
  35. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.d.ts +1 -0
  36. package/dist/contracts/codex-app-server-0.153.4/types/SandboxMode.js +2 -0
  37. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.d.ts +10 -0
  38. package/dist/contracts/codex-app-server-0.153.4/types/ThreadReadParams.js +2 -0
  39. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.d.ts +8 -0
  40. package/dist/contracts/codex-app-server-0.153.4/types/TokenUsageBreakdown.js +2 -0
  41. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.d.ts +4 -0
  42. package/dist/contracts/codex-app-server-0.153.4/types/TurnInterruptParams.js +2 -0
  43. package/dist/src/agent-sdk-normalizer.d.ts +22 -0
  44. package/dist/src/agent-sdk-normalizer.js +995 -0
  45. package/dist/src/agent-sdk-run.d.ts +31 -0
  46. package/dist/src/agent-sdk-run.js +242 -0
  47. package/dist/src/agent-sdk-runner.d.ts +96 -0
  48. package/dist/src/agent-sdk-runner.js +407 -0
  49. package/dist/src/agent-sdk.d.ts +294 -0
  50. package/dist/src/agent-sdk.js +644 -0
  51. package/dist/src/aggregation.d.ts +183 -0
  52. package/dist/src/aggregation.js +513 -0
  53. package/dist/src/artifacts.d.ts +36 -0
  54. package/dist/src/artifacts.js +1372 -0
  55. package/dist/src/atlas-grafana.d.ts +377 -0
  56. package/dist/src/atlas-grafana.js +47 -0
  57. package/dist/src/atlas-html.d.ts +2 -0
  58. package/dist/src/atlas-html.js +34 -0
  59. package/dist/src/atlas.d.ts +130 -0
  60. package/dist/src/atlas.js +235 -0
  61. package/dist/src/behavior-assertions.d.ts +95 -0
  62. package/dist/src/behavior-assertions.js +154 -0
  63. package/dist/src/cli.d.ts +5 -0
  64. package/dist/src/cli.js +957 -0
  65. package/dist/src/codex-judge.d.ts +44 -0
  66. package/dist/src/codex-judge.js +214 -0
  67. package/dist/src/codex-run.d.ts +114 -0
  68. package/dist/src/codex-run.js +519 -0
  69. package/dist/src/codex.d.ts +187 -0
  70. package/dist/src/codex.js +1028 -0
  71. package/dist/src/contracts.d.ts +155 -0
  72. package/dist/src/contracts.js +866 -0
  73. package/dist/src/corpus.d.ts +67 -0
  74. package/dist/src/corpus.js +540 -0
  75. package/dist/src/cursor-sdk-runner.d.ts +81 -0
  76. package/dist/src/cursor-sdk-runner.js +362 -0
  77. package/dist/src/cursor-sdk.d.ts +81 -0
  78. package/dist/src/cursor-sdk.js +1078 -0
  79. package/dist/src/deepseek-adapter.d.ts +152 -0
  80. package/dist/src/deepseek-adapter.js +777 -0
  81. package/dist/src/exports.d.ts +69 -0
  82. package/dist/src/exports.js +966 -0
  83. package/dist/src/human-calibration.d.ts +167 -0
  84. package/dist/src/human-calibration.js +618 -0
  85. package/dist/src/index.d.ts +71 -0
  86. package/dist/src/index.js +38 -0
  87. package/dist/src/lifecycle.d.ts +196 -0
  88. package/dist/src/lifecycle.js +1889 -0
  89. package/dist/src/normalization-integrity.d.ts +128 -0
  90. package/dist/src/normalization-integrity.js +429 -0
  91. package/dist/src/openhands-run.d.ts +36 -0
  92. package/dist/src/openhands-run.js +415 -0
  93. package/dist/src/openhands.d.ts +130 -0
  94. package/dist/src/openhands.js +859 -0
  95. package/dist/src/pi.d.ts +203 -0
  96. package/dist/src/pi.js +1345 -0
  97. package/dist/src/process-protocol.d.ts +245 -0
  98. package/dist/src/process-protocol.js +1261 -0
  99. package/dist/src/retained-evidence.d.ts +12 -0
  100. package/dist/src/retained-evidence.js +201 -0
  101. package/dist/src/run-bundles.d.ts +193 -0
  102. package/dist/src/run-bundles.js +993 -0
  103. package/dist/src/scheduler.d.ts +109 -0
  104. package/dist/src/scheduler.js +1080 -0
  105. package/dist/src/semantic-judge.d.ts +194 -0
  106. package/dist/src/semantic-judge.js +875 -0
  107. package/dist/src/structural-observations.d.ts +124 -0
  108. package/dist/src/structural-observations.js +671 -0
  109. package/dist/src/task-packets.d.ts +115 -0
  110. package/dist/src/task-packets.js +683 -0
  111. package/dist/src/uniform-events.d.ts +120 -0
  112. package/dist/src/uniform-events.js +158 -0
  113. package/dist/src/verifiers.d.ts +116 -0
  114. package/dist/src/verifiers.js +819 -0
  115. package/dist/src/workspaces.d.ts +56 -0
  116. package/dist/src/workspaces.js +1314 -0
  117. package/docs/README.md +60 -0
  118. package/docs/development/README.md +71 -0
  119. package/docs/development/documentation-sync.md +58 -0
  120. package/docs/development/extension-contracts.md +307 -0
  121. package/docs/evaluation/README.md +48 -0
  122. package/docs/evaluation/aggregation.md +112 -0
  123. package/docs/evaluation/behavior-assertions.md +45 -0
  124. package/docs/evaluation/human-calibration.md +170 -0
  125. package/docs/evaluation/normalization-integrity.md +80 -0
  126. package/docs/evaluation/semantic-judge.md +188 -0
  127. package/docs/evaluation/structural-observations.md +84 -0
  128. package/docs/evaluation/uniform-events.md +131 -0
  129. package/docs/guides/atlas.md +144 -0
  130. package/docs/guides/evidence-and-sharing.md +100 -0
  131. package/docs/guides/operator-guide.md +424 -0
  132. package/docs/guides/quickstart.md +152 -0
  133. package/docs/guides/telemetry.md +66 -0
  134. package/docs/harnesses/README.md +31 -0
  135. package/docs/harnesses/claude-agent-sdk.md +61 -0
  136. package/docs/harnesses/codex-harness.md +174 -0
  137. package/docs/harnesses/cursor-sdk.md +149 -0
  138. package/docs/harnesses/deepseek-harness.md +134 -0
  139. package/docs/harnesses/openhands-agent-server.md +153 -0
  140. package/docs/harnesses/pi-sdk.md +66 -0
  141. package/docs/reference/README.md +19 -0
  142. package/docs/reference/agent-sdk-operational-runner.md +118 -0
  143. package/docs/reference/cli.md +114 -0
  144. package/docs/reference/contracts.md +222 -0
  145. package/docs/reference/run-bundle-contract.md +354 -0
  146. package/docs/reference/run-lifecycle.md +54 -0
  147. package/examples/cursor-sdk/README.md +11 -0
  148. package/examples/cursor-sdk/capture-profile.json +1 -0
  149. package/examples/cursor-sdk/harness.json +1 -0
  150. package/examples/cursor-sdk/model.json +1 -0
  151. package/examples/cursor-sdk/native-limits.json +1 -0
  152. package/examples/cursor-sdk/native-tool-policy.json +1 -0
  153. package/ontology/behavior-categories.v1.json +46 -0
  154. package/package.json +66 -0
  155. package/release/0.1.0/KNOWN_LIMITATIONS.md +35 -0
  156. package/release/0.1.0/README.md +46 -0
  157. package/release/0.1.0/reproducibility.json +61 -0
  158. package/release/0.2.0/KNOWN_LIMITATIONS.md +40 -0
  159. package/release/0.2.0/README.md +42 -0
  160. package/release/0.2.0/reproducibility.json +73 -0
  161. package/release/0.2.1/KNOWN_LIMITATIONS.md +18 -0
  162. package/release/0.2.1/README.md +63 -0
  163. package/release/0.2.1/reproducibility.json +73 -0
  164. package/release/README.md +13 -0
  165. package/schemas/aggregation.v1.json +293 -0
  166. package/schemas/behavior-assertions.v1.json +220 -0
  167. package/schemas/deepseek-runtime-composition.v1.schema.json +122 -0
  168. package/schemas/experiment.v1.schema.json +188 -0
  169. package/schemas/human-calibration.v1.json +336 -0
  170. package/schemas/normalization-integrity.v1.json +302 -0
  171. package/schemas/run-bundles/v1.json +1040 -0
  172. package/schemas/run-queue.v1.schema.json +206 -0
  173. package/schemas/semantic-judge.v1.json +380 -0
  174. package/schemas/structural-observations.v1.json +131 -0
  175. package/schemas/task-packet-freeze.v1.schema.json +106 -0
  176. package/schemas/task-packet.v1.schema.json +234 -0
  177. package/schemas/uniform-events/v1.json +289 -0
  178. package/scripts/atlas-grafana.sh +18 -0
package/dist/src/pi.js ADDED
@@ -0,0 +1,1345 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { renameSync } from "node:fs";
3
+ import { cp, lstat, mkdir, mkdtemp, rm, stat } from "node:fs/promises";
4
+ import { tmpdir } from "node:os";
5
+ import { isAbsolute, join, relative, resolve, sep } from "node:path";
6
+ import { createAgentSession, createBashToolDefinition, DefaultResourceLoader, ModelRuntime, SessionManager, SettingsManager, VERSION as PI_SDK_VERSION, } from "@earendil-works/pi-coding-agent";
7
+ import { assertNoDuplicateJsonKeys, digestMetadata, validateArtifact, validateRunManifestEvidence } from "./artifacts.js";
8
+ import { isSafeArtifactRelativePath, resolveBundleConfiguration } from "./contracts.js";
9
+ import { createAttemptIdentity, createRunIdentity, executeRunAttempt, } from "./lifecycle.js";
10
+ import { createCapturedNativeEvidenceResolver, describeNormalizedDataset, validateNormalizedDataset, } from "./normalization-integrity.js";
11
+ import { JsonlEvidenceWriter } from "./process-protocol.js";
12
+ import { createRunBundleAssembler, qualifyRunBundle, } from "./run-bundles.js";
13
+ import { readBoundedFile, readRunQueue } from "./scheduler.js";
14
+ import { assertTaskPacketAdmitted, formatErrors } from "./task-packets.js";
15
+ import { executeVerifier } from "./verifiers.js";
16
+ import {} from "./uniform-events.js";
17
+ import { cleanupWorkspace, materializeWorkspace } from "./workspaces.js";
18
+ export const PI_HARNESS = "pi-sdk";
19
+ export const PI_CONFIG_SCHEMA_VERSION = "ebo.pi-config/v1";
20
+ export const PI_ADAPTER_VERSION = "1.0.0";
21
+ export const PINNED_PI_SDK_VERSION = "0.85.1";
22
+ const ATTEMPT_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
23
+ const ENVIRONMENT_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
24
+ const PI_APIS = ["anthropic-messages", "openai-completions", "openai-responses", "google-generative-ai"];
25
+ const PI_THINKING_LEVELS = ["off", "minimal", "low", "medium", "high", "xhigh", "max"];
26
+ const PI_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls"];
27
+ export const piCapabilityProfile = {
28
+ schemaVersion: "ebo.adapter-capability-profile/v1",
29
+ adapterId: `${PI_HARNESS}/v1`,
30
+ harness: PI_HARNESS,
31
+ nativeTypes: [
32
+ "session", "history:message", "history:model_change", "history:thinking_level_change", "history:compaction", "history:branch_summary",
33
+ "stream:agent_start", "stream:agent_end", "stream:agent_settled", "stream:turn_start", "stream:turn_end",
34
+ "stream:message_start", "stream:message_update", "stream:message_end", "stream:tool_execution_start",
35
+ "stream:tool_execution_update", "stream:tool_execution_end", "stream:compaction_start", "stream:compaction_end",
36
+ "stream:auto_retry_start", "stream:auto_retry_end", "observer:session_start", "observer:session_shutdown",
37
+ "observer:context", "observer:before_provider_request", "observer:before_provider_headers",
38
+ "observer:after_provider_response", "observer:before_agent_start", "observer:tool_call", "observer:tool_result",
39
+ "observer:session_before_compact", "observer:session_compact", "observer:session_compact_failed",
40
+ "adapter:session_created", "adapter:capture_error", "adapter:cleanup",
41
+ ],
42
+ families: {
43
+ message: { status: "available" },
44
+ "model-request": { status: "partial", detail: "Available when the caller enables the passive observer." },
45
+ tool: { status: "available" },
46
+ context: { status: "available" },
47
+ permission: { status: "unsupported", detail: "Pi's selected public SDK events do not expose a distinct permission decision record." },
48
+ delegation: { status: "unsupported", detail: "Child-session/delegation histories are outside this one-session runner." },
49
+ artifact: { status: "partial", detail: "Workspace outcome is authoritative; only tool events explicitly identify mutations." },
50
+ validation: { status: "partial", detail: "Validation is identified only when the native tool name or command is explicit." },
51
+ runtime: { status: "available" },
52
+ outcome: { status: "available" },
53
+ },
54
+ evidence: {
55
+ nativeOrder: { status: "partial", detail: "Pi history and subscription/observer records retain separate source-local order domains." },
56
+ nativeTime: { status: "partial", detail: "History timestamps are native; adapter receipt timestamps are labeled and not promoted to native time." },
57
+ parentage: { status: "partial", detail: "Pi session entry IDs and parent IDs remain authoritative in native history." },
58
+ content: { status: "partial", detail: "Native content remains restricted and is referenced rather than copied into uniform events." },
59
+ },
60
+ };
61
+ class PiEvidenceRecorder {
62
+ writer;
63
+ sequence = 0;
64
+ queue = Promise.resolve();
65
+ error;
66
+ constructor(path) {
67
+ this.writer = new JsonlEvidenceWriter(path, { exclusive: true });
68
+ }
69
+ record(input) {
70
+ let snapshot;
71
+ try {
72
+ snapshot = jsonSnapshot(input);
73
+ }
74
+ catch (error) {
75
+ this.error ??= error;
76
+ return Promise.resolve();
77
+ }
78
+ const record = {
79
+ schemaVersion: "ebo.pi-native-record/v1",
80
+ sequence: ++this.sequence,
81
+ receivedAt: new Date().toISOString(),
82
+ ...snapshot,
83
+ };
84
+ this.queue = this.queue.then(() => this.writer.append(record)).catch((error) => {
85
+ this.error ??= error;
86
+ });
87
+ return this.queue;
88
+ }
89
+ async flush() {
90
+ await this.queue;
91
+ if (this.error !== undefined)
92
+ throw this.error;
93
+ await this.writer.flush();
94
+ }
95
+ async close() {
96
+ await this.queue;
97
+ await this.writer.close();
98
+ }
99
+ }
100
+ /** Execute one direct Pi TypeScript SDK session and retain native history, transient events, and passive observations. */
101
+ export async function capturePiSdkRun(options) {
102
+ if (PI_SDK_VERSION !== PINNED_PI_SDK_VERSION)
103
+ throw new Error(`Pi SDK ${PINNED_PI_SDK_VERSION} is required; installed ${PI_SDK_VERSION}.`);
104
+ if (options.definition.run.harness.id !== PI_HARNESS)
105
+ throw new Error(`Pi run harness must be ${PI_HARNESS}.`);
106
+ if (options.definition.run.model.id !== options.model.model)
107
+ throw new Error("Declared model does not match Pi configuration.");
108
+ if (options.toolPolicy.environmentAllowlist.includes(options.model.apiKeyEnv)) {
109
+ throw new Error("Pi tool environment allowlist must not expose the provider credential variable.");
110
+ }
111
+ const assembler = await createRunBundleAssembler(withPinnedRuntime(options.definition));
112
+ const stream = new PiEvidenceRecorder(join(assembler.bundleRoot, "pi-events.jsonl"));
113
+ const observer = new PiEvidenceRecorder(join(assembler.bundleRoot, "pi-observer.jsonl"));
114
+ let workspace;
115
+ let workspaceOutcome;
116
+ let workspaceOutcomePromise;
117
+ let verifierResult;
118
+ let sessionId;
119
+ let sessionExported = false;
120
+ let sessionExportFailed = false;
121
+ let terminalEvidenceObserved = false;
122
+ let agentSettledObserved = false;
123
+ let adapterFinalized = true;
124
+ let adapterFinalizationTimedOut = false;
125
+ let adapterFinalizationFailure;
126
+ let recorderCloseFailure;
127
+ const captureWorkspace = async (context) => {
128
+ if (workspaceOutcome !== undefined)
129
+ return workspaceOutcome;
130
+ if (workspace?.status !== "ready" || workspace.path === undefined || workspace.artifactId === undefined) {
131
+ throw new Error("Pi capture requires a ready retained workspace before outcome packaging.");
132
+ }
133
+ workspaceOutcomePromise ??= assembler.captureWorkspaceOutcome({
134
+ startPath: options.startingWorkspacePath,
135
+ finalPath: workspace.path,
136
+ id: workspace.artifactId,
137
+ source: "pi-sdk-workspace-outcome",
138
+ ...(options.workspaceOutcomeExcludedDirectoryNames === undefined ? {} : { excludeDirectoryNames: options.workspaceOutcomeExcludedDirectoryNames }),
139
+ ...(options.workspaceOutcomeRespectsGitignore === undefined ? {} : { respectGitignore: options.workspaceOutcomeRespectsGitignore }),
140
+ ...(options.workspaceOutcomeOmitsEmptyDirectories === undefined ? {} : { omitEmptyDirectories: options.workspaceOutcomeOmitsEmptyDirectories }),
141
+ }, context === undefined || options.verifier === undefined ? undefined : async (path, outcome) => {
142
+ verifierResult = await options.verifier(context, outcome, path);
143
+ });
144
+ workspaceOutcome = await workspaceOutcomePromise;
145
+ return workspaceOutcome;
146
+ };
147
+ const coordinatedWorkspace = {
148
+ setup: async (context) => {
149
+ workspace = await options.workspace.setup(context);
150
+ return workspace;
151
+ },
152
+ cleanup: async (context) => {
153
+ if (workspace?.status === "ready")
154
+ await captureWorkspace();
155
+ await options.workspace.cleanup?.(context);
156
+ },
157
+ };
158
+ const run = createRunIdentity({
159
+ id: options.definition.run.id,
160
+ taskId: options.definition.run.task.id,
161
+ modelId: options.definition.run.model.id,
162
+ harnessId: PI_HARNESS,
163
+ });
164
+ const attemptIdentity = createAttemptIdentity(options.definition.run.id, options.definition.attempt.number, options.definition.attempt.id, options.definition.attempt.retryOf);
165
+ const createSession = options.createSession ?? createProductionPiSession;
166
+ let attempt;
167
+ try {
168
+ attempt = await executeRunAttempt({
169
+ run,
170
+ assessmentMode: options.definition.run.assessmentMode,
171
+ attempt: attemptIdentity,
172
+ workspace: coordinatedWorkspace,
173
+ evidence: { flush: () => Promise.all([stream.flush(), observer.flush()]).then(() => undefined) },
174
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
175
+ ...(options.maxWallClockMs === undefined ? {} : { maxWallClockMs: options.maxWallClockMs }),
176
+ ...(options.limits.shutdownGraceMs === undefined ? {} : { shutdownGraceMs: options.limits.shutdownGraceMs }),
177
+ harness: async ({ registerShutdown, signal }) => {
178
+ if (workspace?.status !== "ready" || workspace.path === undefined)
179
+ throw new Error("Pi run requires a ready workspace.");
180
+ let session;
181
+ let unsubscribe;
182
+ let failure;
183
+ let resolveAdapterFinalized;
184
+ const adapterFinalizationPromise = new Promise((resolvePromise) => { resolveAdapterFinalized = resolvePromise; });
185
+ adapterFinalized = false;
186
+ let signalAbort;
187
+ const abortSession = () => {
188
+ if (session === undefined || signalAbort !== undefined)
189
+ return;
190
+ signalAbort = session.abort();
191
+ void signalAbort.catch(() => undefined);
192
+ };
193
+ registerShutdown(async () => {
194
+ abortSession();
195
+ const outcomes = await Promise.allSettled([signalAbort ?? Promise.resolve(), session?.waitForIdle() ?? Promise.resolve(), adapterFinalizationPromise]);
196
+ const rejected = outcomes.find((outcome) => outcome.status === "rejected");
197
+ if (rejected !== undefined)
198
+ throw rejected.reason;
199
+ });
200
+ try {
201
+ session = await createSession({
202
+ workspacePath: workspace.path,
203
+ sessionDirectory: join(assembler.bundleRoot, ".pi-native"),
204
+ model: options.model,
205
+ harness: options.harness,
206
+ limits: options.limits,
207
+ toolPolicy: options.toolPolicy,
208
+ captureProfile: options.captureProfile,
209
+ bundleRoot: resolve(options.configurationRoot),
210
+ observer,
211
+ signal,
212
+ });
213
+ sessionId = session.sessionId;
214
+ if (signal.aborted)
215
+ await session.abort();
216
+ else
217
+ signal.addEventListener("abort", abortSession, { once: true });
218
+ await stream.record({
219
+ channel: "adapter",
220
+ nativeType: "session_created",
221
+ sessionId,
222
+ payload: piComposition(options, session.getActiveToolNames()),
223
+ });
224
+ unsubscribe = session.subscribe((event) => {
225
+ if (event.type === "agent_settled")
226
+ agentSettledObserved = true;
227
+ void stream.record({ channel: "stream", nativeType: event.type, sessionId, payload: compactPiStreamEvent(event) });
228
+ });
229
+ if (signal.aborted)
230
+ return { status: "interrupted", reason: "Pi session was aborted before prompting.", evidence: piEvidence(sessionId, session) };
231
+ await session.prompt(options.prompt, { expandPromptTemplates: false, source: "extension" });
232
+ await session.waitForIdle();
233
+ const lastAssistant = [...session.messages].reverse().find((message) => isRecord(message) && message.role === "assistant");
234
+ if (lastAssistant === undefined) {
235
+ failure = new Error("Pi prompt settled without an assistant terminal message.");
236
+ return { status: "failed", failureClass: "infrastructure", reason: errorMessage(failure), evidence: piEvidence(sessionId, session) };
237
+ }
238
+ if (lastAssistant.stopReason === "aborted") {
239
+ terminalEvidenceObserved = true;
240
+ return { status: "interrupted", reason: "Pi session was aborted.", evidence: piEvidence(sessionId, session) };
241
+ }
242
+ if (lastAssistant.stopReason === "error") {
243
+ terminalEvidenceObserved = true;
244
+ failure = new Error(text(lastAssistant.errorMessage) ?? "Pi provider returned an error terminal.");
245
+ return {
246
+ status: "failed",
247
+ failureClass: "infrastructure",
248
+ reason: errorMessage(failure),
249
+ evidence: piEvidence(sessionId, session),
250
+ };
251
+ }
252
+ if (!["stop", "length"].includes(String(lastAssistant.stopReason)) || !agentSettledObserved) {
253
+ failure = new Error("Pi prompt settled without a recognized assistant terminal and agent_settled evidence.");
254
+ return { status: "failed", failureClass: "infrastructure", reason: errorMessage(failure), evidence: piEvidence(sessionId, session) };
255
+ }
256
+ terminalEvidenceObserved = true;
257
+ return {
258
+ status: "completed",
259
+ completionEvidence: { sessionId, stopReason: lastAssistant.stopReason },
260
+ evidence: piEvidence(sessionId, session),
261
+ };
262
+ }
263
+ catch (error) {
264
+ failure = error;
265
+ await stream.record({ channel: "adapter", nativeType: "capture_error", ...(sessionId === undefined ? {} : { sessionId }), payload: { message: errorMessage(error) } });
266
+ return { status: "failed", failureClass: "infrastructure", reason: errorMessage(error), evidence: piEvidence(sessionId, session) };
267
+ }
268
+ finally {
269
+ try {
270
+ if (session !== undefined) {
271
+ await session.waitForIdle().catch(() => undefined);
272
+ let exportFailure;
273
+ if (!adapterFinalizationTimedOut && session.extensionRunner !== undefined) {
274
+ try {
275
+ await session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
276
+ }
277
+ catch (error) {
278
+ exportFailure = error;
279
+ sessionExportFailed = true;
280
+ await observer.record({
281
+ channel: "observer",
282
+ nativeType: "extension_error",
283
+ hook: "session_shutdown",
284
+ stage: "extension-error",
285
+ payload: { event: "session_shutdown", error: errorMessage(error) },
286
+ });
287
+ }
288
+ }
289
+ unsubscribe?.();
290
+ const nativeDirectory = join(assembler.bundleRoot, ".pi-native");
291
+ if (!adapterFinalizationTimedOut) {
292
+ const pendingSessionPath = join(nativeDirectory, "retained-session.pending.jsonl");
293
+ try {
294
+ await mkdir(nativeDirectory, { recursive: true });
295
+ const nativeSessionPath = session.sessionFile;
296
+ if (nativeSessionPath !== undefined && await nonempty(nativeSessionPath)) {
297
+ await cp(nativeSessionPath, pendingSessionPath, { force: true });
298
+ }
299
+ else {
300
+ session.exportToJsonl(pendingSessionPath);
301
+ if (options.createSession === undefined)
302
+ sessionExportFailed = true;
303
+ }
304
+ if (!adapterFinalizationTimedOut) {
305
+ renameSync(pendingSessionPath, join(assembler.bundleRoot, "pi-session.jsonl"));
306
+ sessionExported = true;
307
+ }
308
+ }
309
+ catch (error) {
310
+ exportFailure ??= error;
311
+ sessionExportFailed = true;
312
+ }
313
+ }
314
+ session.dispose();
315
+ if (!adapterFinalizationTimedOut) {
316
+ await stream.record({ channel: "adapter", nativeType: "cleanup", sessionId, payload: {
317
+ disposed: true, promptFailed: failure !== undefined, sessionExported,
318
+ ...(exportFailure === undefined ? {} : { exportError: errorMessage(exportFailure) }),
319
+ } });
320
+ if (sessionExported)
321
+ await rm(nativeDirectory, { recursive: true, force: true });
322
+ if (exportFailure !== undefined)
323
+ throw exportFailure;
324
+ }
325
+ }
326
+ }
327
+ finally {
328
+ signal.removeEventListener("abort", abortSession);
329
+ adapterFinalized = true;
330
+ resolveAdapterFinalized();
331
+ }
332
+ }
333
+ },
334
+ ...(options.verifier === undefined ? {} : {
335
+ verifier: async (context) => {
336
+ await captureWorkspace(context);
337
+ if (verifierResult === undefined)
338
+ throw new Error("Pi verifier did not return a result.");
339
+ await assembler.writeJsonArtifact({
340
+ id: "verifier", source: "ebo-verifier", kind: "verifier", mediaType: "application/json",
341
+ sharingClass: "restricted", relativePath: "verifier.json",
342
+ }, verifierResult);
343
+ return { status: verifierResult.status, ...(verifierResult.error === undefined ? {} : { error: verifierResult.error }), evidence: verifierResult };
344
+ },
345
+ }),
346
+ });
347
+ }
348
+ finally {
349
+ if (!adapterFinalized) {
350
+ adapterFinalizationTimedOut = true;
351
+ adapterFinalizationFailure = "Pi adapter finalization did not settle within the lifecycle shutdown grace period.";
352
+ await stream.record({
353
+ channel: "adapter",
354
+ nativeType: "capture_error",
355
+ ...(sessionId === undefined ? {} : { sessionId }),
356
+ payload: { message: adapterFinalizationFailure },
357
+ });
358
+ }
359
+ const closeResults = await Promise.allSettled([stream.close(), observer.close()]);
360
+ const failures = closeResults.flatMap((result) => result.status === "rejected" ? [errorMessage(result.reason)] : []);
361
+ if (failures.length > 0)
362
+ recorderCloseFailure = `Pi recorder close failed: ${failures.join("; ")}`;
363
+ }
364
+ if (sessionExported && await nonempty(join(assembler.bundleRoot, "pi-session.jsonl"))) {
365
+ await assembler.registerArtifact({
366
+ id: "pi-session", source: PI_HARNESS, kind: "session", mediaType: "application/x-ndjson",
367
+ sharingClass: "restricted", relativePath: "pi-session.jsonl",
368
+ ...(sessionId === undefined ? {} : { nativeReference: { type: "session", id: sessionId } }),
369
+ });
370
+ }
371
+ if (await nonempty(stream.writer.path)) {
372
+ await assembler.registerArtifact({
373
+ id: "pi-events", source: "pi-sdk-subscription", kind: "session", mediaType: "application/x-ndjson",
374
+ sharingClass: "restricted", relativePath: "pi-events.jsonl",
375
+ });
376
+ }
377
+ if (await nonempty(observer.writer.path)) {
378
+ await assembler.registerArtifact({
379
+ id: "pi-observer", source: "ebo-pi-passive-extension", kind: "session", mediaType: "application/x-ndjson",
380
+ sharingClass: "restricted", relativePath: "pi-observer.jsonl",
381
+ });
382
+ }
383
+ const missingEvidence = [{
384
+ kind: "telemetry", reason: "unsupported", affects: ["timing-resource"],
385
+ detail: "Pi 0.85.1 has no verified native OTLP receipt surface in this integration.",
386
+ }, ...(attempt.terminal.state === "completed" && terminalEvidenceObserved ? [] : [{
387
+ kind: "session", reason: "not-emitted", affects: ["semantic"],
388
+ detail: "Pi did not retain a completed assistant terminal after the owned prompt.",
389
+ }]), ...(sessionExportFailed ? [{
390
+ kind: "session", reason: "not-collected", affects: ["semantic"],
391
+ detail: "Pi session export failed; a persisted native-session fallback was retained when available.",
392
+ }] : []), ...(attempt.record.capture?.status === "incomplete" ? [{
393
+ kind: "session", reason: "not-collected", affects: ["semantic"],
394
+ detail: attempt.record.capture.error ?? "Pi evidence recorder did not drain cleanly.",
395
+ }] : []), ...(recorderCloseFailure === undefined ? [] : [{
396
+ kind: "session", reason: "not-collected", affects: ["semantic"],
397
+ detail: recorderCloseFailure,
398
+ }]), ...(adapterFinalizationFailure === undefined ? [] : [{
399
+ kind: "session", reason: "not-collected", affects: ["semantic"],
400
+ detail: adapterFinalizationFailure,
401
+ }])];
402
+ const qualificationOptions = {
403
+ startingWorkspacePath: options.startingWorkspacePath,
404
+ semanticEvidenceKinds: ["session"],
405
+ ...(options.workspaceOutcomeExcludedDirectoryNames === undefined ? {} : { workspaceOutcomeExcludedDirectoryNames: options.workspaceOutcomeExcludedDirectoryNames }),
406
+ ...(options.workspaceOutcomeRespectsGitignore === undefined ? {} : { workspaceOutcomeRespectsGitignore: options.workspaceOutcomeRespectsGitignore }),
407
+ ...(options.workspaceOutcomeOmitsEmptyDirectories === undefined ? {} : { workspaceOutcomeOmitsEmptyDirectories: options.workspaceOutcomeOmitsEmptyDirectories }),
408
+ };
409
+ const terminal = structuredClone(attempt.terminal);
410
+ if (workspaceOutcome === undefined)
411
+ delete terminal.workspaceArtifactId;
412
+ try {
413
+ const native = await readPiNativeArtifacts(assembler.bundleRoot, options.definition.run.id, options.definition.attempt.id);
414
+ qualifyRetainedPiCapture(native, sessionId, terminal.state === "completed" || terminal.failureClass === "task");
415
+ await describeAndValidatePiDataset(native);
416
+ }
417
+ catch (error) {
418
+ missingEvidence.push({
419
+ kind: "session",
420
+ reason: "not-collected",
421
+ affects: ["semantic"],
422
+ detail: `Pi source-specific validation failed: ${errorMessage(error)}`,
423
+ });
424
+ }
425
+ const manifest = await assembler.finalize({ terminal, missingEvidence, qualification: qualificationOptions });
426
+ const qualification = await qualifyRunBundle(assembler.bundleRoot, qualificationOptions);
427
+ return { attempt, manifest, qualification };
428
+ }
429
+ async function createProductionPiSession(input) {
430
+ input.signal.throwIfAborted();
431
+ const modelRuntime = await ModelRuntime.create({
432
+ authPath: join(input.sessionDirectory, "auth.json"),
433
+ modelsPath: null,
434
+ refreshOnCreate: false,
435
+ allowModelNetwork: false,
436
+ signal: input.signal,
437
+ });
438
+ input.signal.throwIfAborted();
439
+ const credentialReference = `$${input.model.apiKeyEnv}`;
440
+ const provider = {
441
+ name: input.model.provider,
442
+ baseUrl: input.model.baseUrl,
443
+ apiKey: credentialReference,
444
+ api: input.model.api,
445
+ models: [{
446
+ id: input.model.model,
447
+ name: input.model.model,
448
+ api: input.model.api,
449
+ reasoning: input.model.reasoning,
450
+ ...(input.model.thinkingLevelMap === undefined ? {} : { thinkingLevelMap: { ...input.model.thinkingLevelMap } }),
451
+ input: [...input.model.input],
452
+ cost: { ...input.model.cost },
453
+ contextWindow: input.model.contextWindow,
454
+ maxTokens: input.model.maxTokens,
455
+ }],
456
+ };
457
+ modelRuntime.registerProvider(input.model.provider, provider);
458
+ const model = modelRuntime.getModel(input.model.provider, input.model.model);
459
+ if (model === undefined)
460
+ throw new Error(`Pi model ${input.model.provider}/${input.model.model} is unavailable after explicit registration.`);
461
+ const settingsManager = SettingsManager.inMemory({
462
+ enableInstallTelemetry: false,
463
+ retry: {
464
+ enabled: (input.limits.maxRetries ?? 0) > 0,
465
+ maxRetries: input.limits.maxRetries ?? 0,
466
+ baseDelayMs: input.limits.retryBaseDelayMs ?? 2_000,
467
+ provider: {
468
+ maxRetries: 0,
469
+ ...(input.limits.providerTimeoutMs === undefined ? {} : { timeoutMs: input.limits.providerTimeoutMs }),
470
+ },
471
+ },
472
+ }, { projectTrusted: false });
473
+ const resources = resolvePiResources(input.bundleRoot, input.harness);
474
+ const resourceLoader = new DefaultResourceLoader({
475
+ cwd: input.workspacePath,
476
+ agentDir: input.sessionDirectory,
477
+ settingsManager,
478
+ additionalExtensionPaths: resources.extensions,
479
+ additionalSkillPaths: resources.skills,
480
+ additionalPromptTemplatePaths: resources.prompts,
481
+ noExtensions: true,
482
+ noSkills: true,
483
+ noPromptTemplates: true,
484
+ noThemes: true,
485
+ noContextFiles: true,
486
+ ...(resources.systemPrompt === undefined ? {} : { systemPrompt: resources.systemPrompt }),
487
+ agentsFilesOverride: () => ({ agentsFiles: resources.contextFiles }),
488
+ extensionFactories: input.captureProfile.passiveObserver === false
489
+ ? []
490
+ : [createPiPassiveObserver(input.observer, resources.extensions.length, input.captureProfile.includeProviderPayloads === true)],
491
+ });
492
+ await resourceLoader.reload();
493
+ input.signal.throwIfAborted();
494
+ const resourceErrors = resourceLoader.getExtensions().errors;
495
+ if (resourceErrors.length > 0)
496
+ throw new Error(`Pi extension loading failed: ${resourceErrors.map(({ path, error }) => `${path}: ${error}`).join("; ")}`);
497
+ const skillResult = resourceLoader.getSkills();
498
+ const promptResult = resourceLoader.getPrompts();
499
+ const resourceDiagnostics = [...skillResult.diagnostics, ...promptResult.diagnostics];
500
+ if (resourceDiagnostics.length > 0)
501
+ throw new Error(`Pi resource loading failed: ${resourceDiagnostics.map(({ path, message }) => `${path ?? "resource"}: ${message}`).join("; ")}`);
502
+ const loadedExtensionPaths = new Set(resourceLoader.getExtensions().extensions.flatMap(({ resolvedPath }) => resolvedPath.startsWith("<inline:") ? [] : [resolve(resolvedPath)]));
503
+ const loadedSkillPaths = new Set(skillResult.skills.map(({ filePath }) => resolve(filePath)));
504
+ const loadedPromptPaths = new Set(promptResult.prompts.map(({ filePath }) => resolve(filePath)));
505
+ if (resources.extensions.some((path) => !loadedExtensionPaths.has(resolve(path)))
506
+ || resources.skills.some((path) => !loadedSkillPaths.has(resolve(path)))
507
+ || resources.prompts.some((path) => !loadedPromptPaths.has(resolve(path)))) {
508
+ throw new Error("Pi did not load every digest-pinned extension, skill, and prompt resource.");
509
+ }
510
+ if (resources.systemPrompt !== undefined && resourceLoader.getSystemPrompt() !== resources.systemPrompt) {
511
+ throw new Error("Pi did not load the digest-pinned system prompt.");
512
+ }
513
+ if (JSON.stringify(resourceLoader.getAgentsFiles().agentsFiles) !== JSON.stringify(resources.contextFiles)) {
514
+ throw new Error("Pi did not load the digest-pinned context files.");
515
+ }
516
+ const sessionManager = SessionManager.create(input.workspacePath, input.sessionDirectory);
517
+ const allowedEnvironment = new Set(input.toolPolicy.environmentAllowlist);
518
+ const extensionToolNames = resourceLoader.getExtensions().extensions.flatMap((extension) => [...extension.tools.keys()]);
519
+ const bashTool = createBashToolDefinition(input.workspacePath, {
520
+ exposeSessionEnvironment: false,
521
+ spawnHook: ({ command, cwd, env }) => ({
522
+ command,
523
+ cwd,
524
+ env: filterPiToolEnvironment(env, allowedEnvironment, input.model.apiKeyEnv),
525
+ }),
526
+ });
527
+ const { session, extensionsResult } = await createAgentSession({
528
+ cwd: input.workspacePath,
529
+ agentDir: input.sessionDirectory,
530
+ modelRuntime,
531
+ model,
532
+ thinkingLevel: input.model.thinkingLevel,
533
+ sessionManager,
534
+ settingsManager,
535
+ resourceLoader,
536
+ tools: [...new Set([...input.toolPolicy.tools, ...extensionToolNames])],
537
+ excludeTools: [...(input.toolPolicy.excludeTools ?? [])],
538
+ customTools: input.toolPolicy.tools.includes("bash") ? [bashTool] : [],
539
+ });
540
+ try {
541
+ input.signal.throwIfAborted();
542
+ if (extensionsResult.errors.length > 0) {
543
+ throw new Error(`Pi extension binding failed: ${extensionsResult.errors.map(({ error }) => error).join("; ")}`);
544
+ }
545
+ await session.bindExtensions({
546
+ onError: (error) => {
547
+ void input.observer.record({
548
+ channel: "observer",
549
+ nativeType: "extension_error",
550
+ hook: error.event,
551
+ stage: "extension-error",
552
+ payload: { extensionPath: error.extensionPath, event: error.event, error: error.error },
553
+ });
554
+ },
555
+ });
556
+ const expectedTools = input.toolPolicy.tools.filter((tool) => !(input.toolPolicy.excludeTools ?? []).includes(tool)).sort();
557
+ const activeBuiltins = session.getActiveToolNames().filter((tool) => PI_TOOLS.includes(tool)).sort();
558
+ if (JSON.stringify(activeBuiltins) !== JSON.stringify(expectedTools)) {
559
+ throw new Error("Pi active tools differ from the digest-pinned tool policy.");
560
+ }
561
+ if (session.model?.provider !== input.model.provider || session.model.id !== input.model.model
562
+ || session.model.api !== input.model.api || session.model.baseUrl !== input.model.baseUrl) {
563
+ throw new Error("Pi effective model/provider route differs from the digest-pinned model configuration.");
564
+ }
565
+ if (session.thinkingLevel !== input.model.thinkingLevel) {
566
+ throw new Error("Pi effective thinking level differs from the digest-pinned model configuration.");
567
+ }
568
+ return session;
569
+ }
570
+ catch (error) {
571
+ await session.abort().catch(() => undefined);
572
+ session.dispose();
573
+ throw error;
574
+ }
575
+ }
576
+ export function filterPiToolEnvironment(environment, allowlist, apiKeyEnv) {
577
+ if (allowlist.has(apiKeyEnv))
578
+ throw new Error("Pi tool environment allowlist must not expose the provider credential variable.");
579
+ return Object.fromEntries(Object.entries(environment).filter(([name]) => allowlist.has(name)));
580
+ }
581
+ export function createPiPassiveObserver(recorder, extensionOrder, includeProviderPayloads) {
582
+ const factory = (pi) => {
583
+ const record = (hook, stage, payload) => recorder.record({
584
+ channel: "observer", nativeType: hook, hook, stage, extensionOrder, payload,
585
+ });
586
+ pi.on("session_start", (event, ctx) => record(event.type, "session-lifecycle", { event, sessionId: ctx.sessionManager.getSessionId() }));
587
+ pi.on("session_shutdown", (event, ctx) => record(event.type, "session-lifecycle", { event, sessionId: ctx.sessionManager.getSessionId() }));
588
+ pi.on("context", (event, ctx) => record(event.type, "pre-provider-context", {
589
+ sessionId: ctx.sessionManager.getSessionId(), messageCount: event.messages.length,
590
+ messageTimestamps: event.messages.map((message) => isRecord(message) && typeof message.timestamp === "number" ? message.timestamp : null),
591
+ }));
592
+ pi.on("before_agent_start", (event, ctx) => record(event.type, "pre-agent", {
593
+ sessionId: ctx.sessionManager.getSessionId(), promptLength: event.prompt.length,
594
+ systemPromptLength: event.systemPrompt.length, systemPromptOptionKeys: Object.keys(event.systemPromptOptions).sort(),
595
+ }));
596
+ pi.on("before_provider_request", (event, ctx) => record(event.type, "pre-provider-request", {
597
+ sessionId: ctx.sessionManager.getSessionId(),
598
+ ...(includeProviderPayloads ? { payload: event.payload } : { payloadShape: valueShape(event.payload) }),
599
+ }));
600
+ pi.on("before_provider_headers", (event, ctx) => record(event.type, "pre-provider-headers", {
601
+ sessionId: ctx.sessionManager.getSessionId(), headerNames: Object.keys(event.headers).sort(),
602
+ }));
603
+ pi.on("after_provider_response", (event, ctx) => record(event.type, "post-provider-response", {
604
+ sessionId: ctx.sessionManager.getSessionId(), status: event.status, headerNames: Object.keys(event.headers).sort(),
605
+ }));
606
+ pi.on("tool_call", (event, ctx) => record(event.type, "pre-tool", { sessionId: ctx.sessionManager.getSessionId(), event }));
607
+ pi.on("tool_result", (event, ctx) => record(event.type, "post-tool", { sessionId: ctx.sessionManager.getSessionId(), event }));
608
+ pi.on("session_before_compact", (event, ctx) => record(event.type, "pre-compaction", {
609
+ sessionId: ctx.sessionManager.getSessionId(), reason: event.reason, willRetry: event.willRetry,
610
+ branchEntryIds: event.branchEntries.map(({ id }) => id),
611
+ }));
612
+ pi.on("session_compact", (event, ctx) => record(event.type, "post-compaction", { sessionId: ctx.sessionManager.getSessionId(), event }));
613
+ pi.on("session_compact_failed", (event, ctx) => record(event.type, "post-compaction", { sessionId: ctx.sessionManager.getSessionId(), event }));
614
+ };
615
+ return { name: "ebo-passive-observer", factory, hidden: true };
616
+ }
617
+ function resolvePiResources(bundleRoot, configuration) {
618
+ const paths = (references) => (references ?? []).map((reference) => {
619
+ resolveBundleConfiguration(bundleRoot, reference);
620
+ return resolve(bundleRoot, reference.locator);
621
+ });
622
+ const extensions = (configuration.extensions ?? []).map((reference) => {
623
+ const source = new TextDecoder("utf-8", { fatal: true }).decode(resolveBundleConfiguration(bundleRoot, reference));
624
+ assertSelfContainedPiExtension(source, reference);
625
+ return resolve(bundleRoot, reference.locator);
626
+ });
627
+ const contextFiles = (configuration.contextFiles ?? []).map((reference) => ({
628
+ path: resolve(bundleRoot, reference.locator),
629
+ content: new TextDecoder("utf-8", { fatal: true }).decode(resolveBundleConfiguration(bundleRoot, reference)),
630
+ }));
631
+ const systemPrompt = configuration.systemPrompt === undefined ? undefined
632
+ : new TextDecoder("utf-8", { fatal: true }).decode(resolveBundleConfiguration(bundleRoot, configuration.systemPrompt));
633
+ return {
634
+ extensions,
635
+ skills: paths(configuration.skills),
636
+ prompts: paths(configuration.prompts),
637
+ contextFiles,
638
+ ...(systemPrompt === undefined ? {} : { systemPrompt }),
639
+ };
640
+ }
641
+ function assertSelfContainedPiExtension(source, reference) {
642
+ const importSyntax = /\bimport\b/u;
643
+ const commonJs = /\brequire\s*\(/u;
644
+ const exportSyntax = /\bexport\b/u;
645
+ const fromSpecifier = /\bfrom\s*["']/u;
646
+ if (importSyntax.test(source) || commonJs.test(source) || exportSyntax.test(source) && fromSpecifier.test(source)) {
647
+ throw piConfigError(reference, "must be self-contained and cannot import or require an unpinned dependency graph");
648
+ }
649
+ }
650
+ export async function normalizePiCapture(input) {
651
+ const events = [];
652
+ const unmapped = [];
653
+ const retainedSessionId = input.records.flatMap(({ record }) => record.type === "session" ? text(record.id) ?? [] : []).at(0)
654
+ ?? input.records.flatMap(({ record }) => text(record.sessionId) ?? []).at(0);
655
+ for (const captured of input.records) {
656
+ const mapped = mapPiRecord(input, captured, retainedSessionId);
657
+ events.push(...mapped);
658
+ if (mapped.length === 0)
659
+ unmapped.push({ reference: captured.reference, reason: piUnmappedReason(captured.record) });
660
+ }
661
+ return { events, unmapped };
662
+ }
663
+ export async function describeAndValidatePiDataset(capture) {
664
+ const normalization = await normalizePiCapture(capture);
665
+ const resolver = createCapturedNativeEvidenceResolver(capture);
666
+ const dataset = describeNormalizedDataset({
667
+ capture,
668
+ normalization,
669
+ capabilityProfile: piCapabilityProfile,
670
+ adapterVersion: PI_ADAPTER_VERSION,
671
+ nativeType: piNativeType,
672
+ });
673
+ const coverage = await validateNormalizedDataset(dataset, resolver);
674
+ return { dataset, coverage, resolver };
675
+ }
676
+ async function readPiNativeArtifacts(bundleRoot, runId, attemptId) {
677
+ const records = [];
678
+ for (const [artifactId, relativePath] of [
679
+ ["pi-session", "pi-session.jsonl"],
680
+ ["pi-events", "pi-events.jsonl"],
681
+ ["pi-observer", "pi-observer.jsonl"],
682
+ ]) {
683
+ const path = join(bundleRoot, relativePath);
684
+ if (!await nonempty(path))
685
+ continue;
686
+ const source = new TextDecoder("utf-8", { fatal: true }).decode(readBoundedFile(path, `Pi ${artifactId} evidence`));
687
+ for (const [index, line] of source.split(/\r?\n/u).filter((candidate) => candidate.trim() !== "").entries()) {
688
+ assertNoDuplicateJsonKeys(line);
689
+ const record = JSON.parse(line);
690
+ assertPiNativeRecord(record, index + 1);
691
+ records.push({ reference: { artifactId, recordLocator: `line:${index + 1}` }, record });
692
+ }
693
+ }
694
+ return { runId, attemptId, qualification: "qualified", records };
695
+ }
696
+ function mapPiRecord(input, captured, retainedSessionId) {
697
+ const record = captured.record;
698
+ const nativeType = piNativeType(record);
699
+ const envelope = record.schemaVersion === "ebo.pi-native-record/v1";
700
+ const channel = envelope ? text(record.channel) : undefined;
701
+ const payload = envelope && isRecord(record.payload) ? record.payload : record;
702
+ const sourceId = text(record.sessionId) ?? retainedSessionId ?? text(payload.id) ?? input.attemptId;
703
+ const order = envelope ? number(record.sequence) : lineNumber(captured.reference.recordLocator);
704
+ const payloadMessage = isRecord(payload.message) ? payload.message : isRecord(payload.error) ? payload.error : undefined;
705
+ const timestamp = envelope ? millisTimestamp(payloadMessage?.timestamp) : text(record.timestamp) ?? millisTimestamp(payload.timestamp);
706
+ const common = {
707
+ schemaVersion: "ebo.uniform-event/v1",
708
+ runId: input.runId,
709
+ attemptId: input.attemptId,
710
+ source: { harness: PI_HARNESS, nativeType, nativeReference: structuredClone(captured.reference) },
711
+ nativeOrder: order === undefined
712
+ ? { status: "unknown", reason: "Pi native order is unavailable" }
713
+ : { status: "known", value: order, domain: envelope ? `pi-${channel ?? "adapter"}` : "pi-session" },
714
+ nativeTime: timestamp === undefined
715
+ ? { status: "unknown", reason: envelope ? "Adapter receipt time is not native time" : "Pi history timestamp is unavailable" }
716
+ : { status: "known", value: timestamp },
717
+ relations: { parent: { status: "unknown", reason: "Native parent IDs remain in Pi session history" }, known: [] },
718
+ };
719
+ const event = (suffix, family, phase, actor, scope, attributes, content = true) => ({
720
+ ...common,
721
+ id: `pi-${captured.reference.artifactId}-${String(order ?? 0)}-${suffix}`,
722
+ family,
723
+ phase,
724
+ actor,
725
+ scope,
726
+ attributes: { sessionId: sourceId, ...attributes },
727
+ content: content ? { status: "known", value: [{ nativeReference: structuredClone(captured.reference) }] }
728
+ : { status: "unknown", reason: "No content is associated with this lifecycle record" },
729
+ });
730
+ if (!envelope) {
731
+ if (record.type === "session")
732
+ return [event("session", "runtime", "instant", { kind: "harness" }, { kind: "session", id: sourceId }, { lifecycle: "session-start" }, false)];
733
+ if (record.type === "message" && isRecord(record.message)) {
734
+ const message = record.message;
735
+ const role = text(message.role);
736
+ const actor = role === "assistant" ? { kind: "model", id: text(message.model) }
737
+ : role === "toolResult" || role === "bashExecution" ? { kind: "tool", id: text(message.toolName) }
738
+ : role === "user" ? { kind: "user" }
739
+ : ["custom", "branchSummary", "compactionSummary"].includes(String(role)) ? { kind: "harness" }
740
+ : undefined;
741
+ if (actor === undefined)
742
+ return [];
743
+ const result = [event(`message-${text(record.id) ?? "entry"}`, "message", "after", actor, { kind: "session", id: sourceId }, {
744
+ role: role ?? "unknown", entryId: text(record.id) ?? "unknown",
745
+ })];
746
+ if ((role === "assistant" || role === "toolResult") && isRecord(message.usage)) {
747
+ result.push(event(`usage-${text(record.id) ?? "entry"}`, "runtime", "after", { kind: "harness" }, { kind: "session", id: sourceId }, usageAttributes(message.usage, role), false));
748
+ }
749
+ return result;
750
+ }
751
+ if (record.type === "compaction" || record.type === "branch_summary") {
752
+ const result = [event(`${String(record.type)}-${text(record.id) ?? "entry"}`, "context", "after", { kind: "harness" }, { kind: "session", id: sourceId }, {
753
+ boundary: String(record.type),
754
+ ...(record.type === "compaction" ? { subtype: "compact_boundary" } : {}),
755
+ })];
756
+ if (isRecord(record.usage))
757
+ result.push(event(`usage-${text(record.id) ?? "entry"}`, "runtime", "after", { kind: "harness" }, { kind: "session", id: sourceId }, usageAttributes(record.usage, String(record.type)), false));
758
+ return result;
759
+ }
760
+ if (record.type === "model_change" || record.type === "thinking_level_change") {
761
+ return [event(`${String(record.type)}-${text(record.id) ?? "entry"}`, "runtime", "instant", { kind: "harness" }, { kind: "session", id: sourceId }, { change: String(record.type) })];
762
+ }
763
+ return [];
764
+ }
765
+ const kind = text(record.nativeType) ?? "unknown";
766
+ if (channel === "stream") {
767
+ if (["message_start", "message_end"].includes(kind)) {
768
+ const message = isRecord(payload.message) ? payload.message : undefined;
769
+ const role = text(message?.role) ?? "unknown";
770
+ const actor = role === "assistant" ? { kind: "model" }
771
+ : role === "toolResult" || role === "bashExecution" ? { kind: "tool" }
772
+ : role === "user" ? { kind: "user" }
773
+ : role === "custom" ? { kind: "harness" }
774
+ : undefined;
775
+ return actor === undefined ? [] : [event(kind, "message", kind.endsWith("start") ? "before" : "after", actor, { kind: "session", id: sourceId }, { role })];
776
+ }
777
+ if (["tool_execution_start", "tool_execution_update", "tool_execution_end"].includes(kind)) {
778
+ const toolCallId = text(payload.toolCallId);
779
+ const args = payload.args;
780
+ return [event(kind, "tool", kind.endsWith("start") ? "before" : kind.endsWith("end") ? "after" : "during", { kind: "tool", id: text(payload.toolName) }, { kind: "operation", ...(toolCallId === undefined ? {} : { id: toolCallId }) }, compactAttributes({
781
+ toolCallId, toolName: text(payload.toolName), inputDigest: args === undefined ? undefined : `sha256:${digestMetadata(args).value}`,
782
+ isError: typeof payload.isError === "boolean" ? payload.isError : undefined,
783
+ }))];
784
+ }
785
+ if (["compaction_start", "compaction_end"].includes(kind))
786
+ return [event(kind, "context", kind.endsWith("start") ? "before" : "after", { kind: "harness" }, { kind: "session", id: sourceId }, { boundary: "compaction", subtype: "compact_boundary" })];
787
+ if (["agent_start", "agent_end", "agent_settled", "turn_start", "turn_end", "auto_retry_start", "auto_retry_end"].includes(kind)) {
788
+ const turnIndex = number(payload.turnIndex);
789
+ const scope = kind.startsWith("turn")
790
+ ? { kind: "turn", ...(turnIndex === undefined ? {} : { id: `${sourceId}:turn:${String(turnIndex)}` }) }
791
+ : { kind: "session", id: sourceId };
792
+ return [event(kind, kind.startsWith("auto_retry") ? "context" : "runtime", kind.endsWith("start") ? "before" : "after", { kind: "harness" }, scope, compactAttributes({ lifecycle: kind, turnIndex, receiptAt: text(record.receivedAt) }))];
793
+ }
794
+ return [];
795
+ }
796
+ if (channel === "observer") {
797
+ if (kind === "before_provider_request")
798
+ return [event(kind, "model-request", "before", { kind: "harness" }, { kind: "operation", id: `provider-${String(order ?? 0)}` }, { requestId: `provider-${String(order ?? 0)}`, observationStage: text(record.stage) ?? "pre-provider-request" })];
799
+ if (["context", "before_agent_start"].includes(kind))
800
+ return [event(kind, "context", "before", { kind: "harness" }, { kind: "session", id: sourceId }, { observationStage: text(record.stage) ?? kind })];
801
+ if (["session_before_compact", "session_compact", "session_compact_failed"].includes(kind)) {
802
+ return [event(kind, "context", kind === "session_before_compact" ? "before" : "after", { kind: "harness" }, { kind: "session", id: sourceId }, {
803
+ observationStage: text(record.stage) ?? kind,
804
+ subtype: "compact_boundary",
805
+ })];
806
+ }
807
+ if (["session_start", "session_shutdown", "before_provider_headers", "after_provider_response"].includes(kind))
808
+ return [event(kind, "runtime", kind.endsWith("start") || kind.startsWith("before") ? "before" : "after", { kind: "harness" }, { kind: "session", id: sourceId }, { observationStage: text(record.stage) ?? kind })];
809
+ return [];
810
+ }
811
+ if (channel === "adapter")
812
+ return [event(kind, kind === "capture_error" ? "outcome" : "runtime", "instant", { kind: "harness" }, { kind: "session", id: sourceId }, { lifecycle: kind })];
813
+ return [];
814
+ }
815
+ export function piNativeType(record) {
816
+ if (record.schemaVersion === "ebo.pi-native-record/v1")
817
+ return `${String(record.channel)}:${String(record.nativeType)}`;
818
+ if (record.type === "session")
819
+ return "session";
820
+ return `history:${String(record.type)}`;
821
+ }
822
+ export function assertPiNativeRecord(record, line) {
823
+ if (!isRecord(record) || !Number.isSafeInteger(line) || line < 1)
824
+ throw new Error(`Invalid retained Pi native record at line ${line}.`);
825
+ if (record.schemaVersion === "ebo.pi-native-record/v1") {
826
+ if (!Number.isSafeInteger(record.sequence) || record.sequence !== line
827
+ || !["stream", "observer", "adapter"].includes(String(record.channel))
828
+ || text(record.nativeType) === undefined || text(record.receivedAt) === undefined || record.payload === undefined) {
829
+ throw new Error(`Invalid retained Pi native envelope at line ${line}.`);
830
+ }
831
+ return;
832
+ }
833
+ if (text(record.type) === undefined || (record.type === "session"
834
+ ? line !== 1 || record.version !== 3 || text(record.id) === undefined || text(record.cwd) === undefined
835
+ : line === 1 || text(record.id) === undefined || !(record.parentId === null || text(record.parentId) !== undefined) || text(record.timestamp) === undefined)) {
836
+ throw new Error(`Invalid retained Pi session entry at line ${line}.`);
837
+ }
838
+ }
839
+ export function qualifyRetainedPiCapture(capture, sessionId, expectsCompletion) {
840
+ for (const { reference, record } of capture.records)
841
+ assertPiNativeRecord(record, lineNumber(reference.recordLocator) ?? 0);
842
+ const history = capture.records.filter(({ reference }) => reference.artifactId === "pi-session").map(({ record }) => record);
843
+ const headers = history.filter(({ type }) => type === "session");
844
+ if (sessionId === undefined || headers.length !== 1 || headers[0].id !== sessionId) {
845
+ throw new Error("Retained Pi session identity differs from the run manifest.");
846
+ }
847
+ const entryIds = new Set();
848
+ for (const record of history.filter(({ type }) => type !== "session")) {
849
+ const id = text(record.id);
850
+ if (entryIds.has(id) || record.parentId !== null && !entryIds.has(String(record.parentId))) {
851
+ throw new Error("Retained Pi session history has duplicate or unresolved parent identities.");
852
+ }
853
+ entryIds.add(id);
854
+ }
855
+ const envelopeSessionIds = capture.records.flatMap(({ record }) => {
856
+ const ids = [text(record.sessionId)];
857
+ if (record.channel === "observer" && isRecord(record.payload))
858
+ ids.push(text(record.payload.sessionId));
859
+ return ids.filter((id) => id !== undefined);
860
+ });
861
+ if (envelopeSessionIds.some((id) => id !== sessionId))
862
+ throw new Error("Retained Pi stream identity differs from the native session.");
863
+ if (capture.records.some(({ record }) => record.channel === "observer" && record.nativeType === "extension_error")) {
864
+ throw new Error("Retained Pi capture contains a selected extension hook failure.");
865
+ }
866
+ if (expectsCompletion) {
867
+ const settled = capture.records.some(({ record }) => record.channel === "stream" && record.nativeType === "agent_settled");
868
+ const lastAssistant = history.filter(({ type, message }) => type === "message" && isRecord(message) && message.role === "assistant").at(-1)?.message;
869
+ if (!settled || !isRecord(lastAssistant) || !["stop", "length"].includes(String(lastAssistant.stopReason))) {
870
+ throw new Error("Completed retained Pi capture lacks matching native settled terminal evidence.");
871
+ }
872
+ const historyCalls = new Set(history.flatMap(({ type, message }) => {
873
+ if (type !== "message" || !isRecord(message) || message.role !== "assistant" || !Array.isArray(message.content))
874
+ return [];
875
+ return message.content.flatMap((block) => isRecord(block) && block.type === "toolCall" ? text(block.id) ?? [] : []);
876
+ }));
877
+ const historyResults = new Set(history.flatMap(({ type, message }) => type === "message" && isRecord(message) && message.role === "toolResult" ? text(message.toolCallId) ?? [] : []));
878
+ const streamIds = (nativeType) => new Set(capture.records.flatMap(({ record }) => record.channel === "stream" && record.nativeType === nativeType && isRecord(record.payload)
879
+ ? text(record.payload.toolCallId) ?? [] : []));
880
+ if (!sameTextSet(historyCalls, historyResults)
881
+ || !sameTextSet(historyCalls, streamIds("tool_execution_start"))
882
+ || !sameTextSet(historyCalls, streamIds("tool_execution_end"))) {
883
+ throw new Error("Completed retained Pi capture has mismatched native history and streamed tool identities.");
884
+ }
885
+ }
886
+ return capture;
887
+ }
888
+ function piUnmappedReason(record) {
889
+ const type = piNativeType(record);
890
+ if (type === "stream:message_update")
891
+ return "Transient message delta retained natively; final history/message records own counted semantics";
892
+ if (type === "stream:entry_appended")
893
+ return "Persistence notification overlaps authoritative native session history";
894
+ if (type === "observer:tool_call" || type === "observer:tool_result")
895
+ return "Passive observer duplicate; subscription tool lifecycle owns normalized operation semantics";
896
+ return `Pi native type ${type} is retained but has no uniform projection`;
897
+ }
898
+ /** Execute exactly one frozen queue entry through the pinned direct Pi SDK integration. */
899
+ export async function runPiQueueEntry(options) {
900
+ const bundleRoot = resolve(options.bundleRoot);
901
+ const queue = readRunQueue(options.queuePath, undefined, { bundleRoot });
902
+ const matches = queue.entries.filter((entry) => entry.runId === options.runId);
903
+ if (matches.length !== 1)
904
+ throw new Error(matches.length === 0 ? `Run "${options.runId}" is not in the queue.` : `Run "${options.runId}" matches more than one queue entry.`);
905
+ const entry = matches[0];
906
+ if (entry.harness.id !== PI_HARNESS)
907
+ throw new Error(`Queue harness must be ${PI_HARNESS}.`);
908
+ const model = resolvePiConfigurationRecord(bundleRoot, entry.configuration.model, "model");
909
+ const harness = resolvePiConfigurationRecord(bundleRoot, entry.configuration.harness, "harness");
910
+ const limits = resolvePiConfigurationRecord(bundleRoot, entry.configuration.nativeLimits, "native-limits");
911
+ const toolPolicy = resolvePiConfigurationRecord(bundleRoot, entry.configuration.nativeToolPolicy, "native-tool-policy");
912
+ const captureProfile = resolvePiConfigurationRecord(bundleRoot, queue.captureProfile, "capture-profile");
913
+ if (toolPolicy.environmentAllowlist.includes(model.apiKeyEnv))
914
+ throw new Error("Pi tool environment allowlist must not expose the provider credential variable.");
915
+ if (model.queueModelId !== entry.model.id)
916
+ throw new Error(`Pi model condition "${model.queueModelId}" does not match queue model "${entry.model.id}".`);
917
+ resolvePiResources(bundleRoot, harness);
918
+ const inspection = assertTaskPacketAdmitted(bundleRoot, entry.task.packetRef.locator);
919
+ const packet = inspection.packet;
920
+ if (inspection.packetDigest === null || inspection.packetDigest.value !== entry.task.packetRef.digest.value
921
+ || inspection.packetDigest.algorithm !== entry.task.packetRef.digest.algorithm) {
922
+ throw new Error(`Task packet "${entry.task.packetRef.locator}" changed from its frozen queue reference.`);
923
+ }
924
+ const attemptId = options.attemptId ?? randomUUID();
925
+ if (!ATTEMPT_ID_PATTERN.test(attemptId))
926
+ throw new Error("Attempt ID must be one safe path component.");
927
+ const outputRoot = resolve(options.outputRoot);
928
+ const destination = join(outputRoot, entry.runId, attemptId);
929
+ const fromOutput = relative(outputRoot, destination);
930
+ if (fromOutput === "" || isAbsolute(fromOutput) || fromOutput === ".." || fromOutput.startsWith(`..${sep}`))
931
+ throw new Error("Attempt destination escapes selected output root.");
932
+ if (await exists(destination))
933
+ throw new Error(`Attempt destination "${destination}" already exists and is never replaced.`);
934
+ const workspace = await materializeWorkspace({
935
+ bundleRoot,
936
+ packetLocator: entry.task.packetRef.locator,
937
+ freezeLocator: entry.task.freezeLocator,
938
+ ...(options.workspaceRoot === undefined ? {} : { workspaceParent: options.workspaceRoot }),
939
+ });
940
+ if (workspace.status !== "ready")
941
+ throw new Error(`Workspace materialization failed: ${workspace.error ?? "unknown failure"}`);
942
+ let baselineRoot;
943
+ let captureStarted = false;
944
+ try {
945
+ baselineRoot = await mkdtemp(join(tmpdir(), "ebo-pi-baseline-"));
946
+ const startingWorkspacePath = join(baselineRoot, "workspace");
947
+ await cp(workspace.path, startingWorkspacePath, { recursive: true, preserveTimestamps: true, force: false });
948
+ const verifierReference = packet.assessmentMode === "verified" ? packet.restricted.verifier : undefined;
949
+ const verifierFormat = verifierReference?.locator.toLowerCase().endsWith(".mjs") ? "module" : "commonjs";
950
+ const definition = buildPiDefinition(queue.captureProfile, entry, packet, destination, attemptId, model, harness, verifierFormat);
951
+ const result = await capturePiSdkRun({
952
+ definition,
953
+ configurationRoot: bundleRoot,
954
+ startingWorkspacePath,
955
+ workspace: {
956
+ setup: () => {
957
+ captureStarted = true;
958
+ return { status: "ready", path: workspace.path, artifactId: "workspace", retained: true };
959
+ },
960
+ cleanup: () => cleanupWorkspace(workspace),
961
+ },
962
+ model,
963
+ harness,
964
+ limits,
965
+ toolPolicy,
966
+ captureProfile,
967
+ prompt: packet.agentInput.prompt,
968
+ ...(verifierReference === undefined ? {} : {
969
+ verifier: (context, outcome, projectedPath) => executeVerifier({
970
+ bundleId: definition.bundleId,
971
+ verifierRoot: bundleRoot,
972
+ verifier: verifierReference,
973
+ workspacePath: projectedPath,
974
+ workspaceFingerprint: outcome.fingerprint,
975
+ workspace: { artifactId: outcome.descriptor.id, digest: outcome.descriptor.digest, fingerprint: outcome.fingerprint },
976
+ artifactRoot: destination,
977
+ moduleFormat: verifierFormat,
978
+ signal: context.signal,
979
+ }),
980
+ }),
981
+ maxWallClockMs: queue.coordinatorBudget.maxWallClockMs,
982
+ ...(options.signal === undefined ? {} : { signal: options.signal }),
983
+ ...(options.createSession === undefined ? {} : { createSession: options.createSession }),
984
+ ...(captureProfile.workspaceOutcome === undefined ? {} : {
985
+ workspaceOutcomeExcludedDirectoryNames: captureProfile.workspaceOutcome.excludeDirectoryNames,
986
+ ...(captureProfile.workspaceOutcome.respectGitignore === undefined ? {} : { workspaceOutcomeRespectsGitignore: captureProfile.workspaceOutcome.respectGitignore }),
987
+ ...(captureProfile.workspaceOutcome.omitEmptyDirectories === undefined ? {} : { workspaceOutcomeOmitsEmptyDirectories: captureProfile.workspaceOutcome.omitEmptyDirectories }),
988
+ }),
989
+ });
990
+ const manifest = reopenManifest(destination);
991
+ return {
992
+ runId: entry.runId,
993
+ attemptId,
994
+ bundlePath: destination,
995
+ terminal: structuredClone(manifest.terminal),
996
+ classification: result.attempt.classification.kind,
997
+ captureQualification: result.qualification.status,
998
+ assessmentMode: packet.assessmentMode,
999
+ ...(manifest.run.native?.sessionId === undefined ? {} : { sessionId: manifest.run.native.sessionId }),
1000
+ ...(workspace.state === "ready" ? { retainedWorkspacePath: workspace.path } : {}),
1001
+ };
1002
+ }
1003
+ finally {
1004
+ if (baselineRoot !== undefined)
1005
+ await rm(baselineRoot, { recursive: true, force: true });
1006
+ if (!captureStarted && workspace.state === "ready")
1007
+ await cleanupWorkspace(workspace).catch(() => undefined);
1008
+ }
1009
+ }
1010
+ export function resolvePiConfigurationRecord(bundleRoot, reference, kind) {
1011
+ const textValue = new TextDecoder("utf-8", { fatal: true }).decode(resolveBundleConfiguration(bundleRoot, reference));
1012
+ assertNoDuplicateJsonKeys(textValue);
1013
+ const value = JSON.parse(textValue);
1014
+ if (!isRecord(value) || value.schemaVersion !== PI_CONFIG_SCHEMA_VERSION || value.kind !== kind)
1015
+ throw piConfigError(reference, `must declare ${PI_CONFIG_SCHEMA_VERSION} kind ${kind}`);
1016
+ validatePiConfiguration(value, kind, reference);
1017
+ return value;
1018
+ }
1019
+ function validatePiConfiguration(record, kind, reference) {
1020
+ if (kind === "model") {
1021
+ keys(record, ["schemaVersion", "kind", "provider", "queueModelId", "model", "api", "baseUrl", "apiKeyEnv", "thinkingLevel", "thinkingLevelMap", "reasoning", "input", "contextWindow", "maxTokens", "cost"], ["provider", "queueModelId", "model", "api", "baseUrl", "apiKeyEnv", "thinkingLevel", "reasoning", "input", "contextWindow", "maxTokens", "cost"], reference);
1022
+ requiredText(record.provider, "provider", reference);
1023
+ requiredText(record.model, "model", reference);
1024
+ if (typeof record.queueModelId !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(record.queueModelId))
1025
+ throw piConfigError(reference, "queueModelId is invalid");
1026
+ if (!PI_APIS.includes(record.api))
1027
+ throw piConfigError(reference, "api is unsupported");
1028
+ assertHttpUrl(record.baseUrl, reference);
1029
+ if (typeof record.apiKeyEnv !== "string" || !ENVIRONMENT_NAME_PATTERN.test(record.apiKeyEnv))
1030
+ throw piConfigError(reference, "apiKeyEnv is invalid");
1031
+ if (!PI_THINKING_LEVELS.includes(record.thinkingLevel))
1032
+ throw piConfigError(reference, "thinkingLevel is invalid");
1033
+ if (record.thinkingLevelMap !== undefined) {
1034
+ if (!isRecord(record.thinkingLevelMap) || Object.keys(record.thinkingLevelMap).some((level) => !PI_THINKING_LEVELS.includes(level))
1035
+ || Object.values(record.thinkingLevelMap).some((value) => value !== null && (typeof value !== "string" || value.trim() === ""))) {
1036
+ throw piConfigError(reference, "thinkingLevelMap must map supported levels to nonempty provider values or null");
1037
+ }
1038
+ }
1039
+ if (["xhigh", "max"].includes(String(record.thinkingLevel))
1040
+ && (!isRecord(record.thinkingLevelMap) || typeof record.thinkingLevelMap[String(record.thinkingLevel)] !== "string")) {
1041
+ throw piConfigError(reference, "xhigh and max require an explicit non-null thinkingLevelMap entry");
1042
+ }
1043
+ if (typeof record.reasoning !== "boolean")
1044
+ throw piConfigError(reference, "reasoning must be boolean");
1045
+ if (record.reasoning === false && record.thinkingLevel !== "off")
1046
+ throw piConfigError(reference, "non-reasoning models require thinkingLevel off");
1047
+ if (!Array.isArray(record.input) || record.input.length === 0 || record.input.some((item) => item !== "text" && item !== "image") || new Set(record.input).size !== record.input.length)
1048
+ throw piConfigError(reference, "input must be a unique text/image list");
1049
+ positive(record.contextWindow, "contextWindow", reference);
1050
+ positive(record.maxTokens, "maxTokens", reference);
1051
+ if (!isRecord(record.cost))
1052
+ throw piConfigError(reference, "cost must be an object");
1053
+ keys(record.cost, ["input", "output", "cacheRead", "cacheWrite"], ["input", "output", "cacheRead", "cacheWrite"], reference);
1054
+ for (const [name, value] of Object.entries(record.cost))
1055
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
1056
+ throw piConfigError(reference, `cost.${name} must be nonnegative`);
1057
+ }
1058
+ else if (kind === "harness") {
1059
+ keys(record, ["schemaVersion", "kind", "adapter", "version", "extensions", "skills", "prompts", "contextFiles", "systemPrompt"], ["adapter", "version"], reference);
1060
+ if (record.adapter !== PI_HARNESS || record.version !== PINNED_PI_SDK_VERSION)
1061
+ throw piConfigError(reference, "adapter or version is not pinned");
1062
+ for (const field of ["extensions", "skills", "prompts", "contextFiles"])
1063
+ if (record[field] !== undefined)
1064
+ assertReferences(record[field], field, reference);
1065
+ if (record.systemPrompt !== undefined)
1066
+ assertReference(record.systemPrompt, "systemPrompt", reference);
1067
+ }
1068
+ else if (kind === "native-limits") {
1069
+ keys(record, ["schemaVersion", "kind", "shutdownGraceMs", "maxRetries", "retryBaseDelayMs", "providerTimeoutMs"], [], reference);
1070
+ for (const field of ["shutdownGraceMs", "retryBaseDelayMs", "providerTimeoutMs"])
1071
+ if (record[field] !== undefined)
1072
+ positive(record[field], field, reference);
1073
+ if (record.maxRetries !== undefined && (!Number.isSafeInteger(record.maxRetries) || record.maxRetries < 0))
1074
+ throw piConfigError(reference, "maxRetries must be a nonnegative safe integer");
1075
+ }
1076
+ else if (kind === "native-tool-policy") {
1077
+ keys(record, ["schemaVersion", "kind", "tools", "excludeTools", "environmentAllowlist"], ["tools", "environmentAllowlist"], reference);
1078
+ assertStringList(record.tools, "tools", reference, PI_TOOLS);
1079
+ if (record.excludeTools !== undefined)
1080
+ assertStringList(record.excludeTools, "excludeTools", reference, PI_TOOLS);
1081
+ assertStringList(record.environmentAllowlist, "environmentAllowlist", reference);
1082
+ if (record.environmentAllowlist.some((name) => !ENVIRONMENT_NAME_PATTERN.test(name)))
1083
+ throw piConfigError(reference, "environmentAllowlist contains an invalid variable name");
1084
+ }
1085
+ else {
1086
+ keys(record, ["schemaVersion", "kind", "passiveObserver", "includeProviderPayloads", "workspaceOutcome"], [], reference);
1087
+ if (record.passiveObserver !== undefined && typeof record.passiveObserver !== "boolean")
1088
+ throw piConfigError(reference, "passiveObserver must be boolean");
1089
+ if (record.includeProviderPayloads !== undefined && typeof record.includeProviderPayloads !== "boolean")
1090
+ throw piConfigError(reference, "includeProviderPayloads must be boolean");
1091
+ if (record.workspaceOutcome !== undefined)
1092
+ validateWorkspaceOutcome(record.workspaceOutcome, reference);
1093
+ }
1094
+ }
1095
+ function buildPiDefinition(captureProfile, entry, packet, bundleRoot, attemptId, model, harness, verifierFormat) {
1096
+ return {
1097
+ bundleRoot,
1098
+ bundleId: `bundle-${attemptId}`,
1099
+ run: {
1100
+ id: entry.runId,
1101
+ trial: { index: entry.trial.index },
1102
+ assessmentMode: packet.assessmentMode,
1103
+ task: { id: entry.task.id, digest: `sha256:${entry.task.packetRef.digest.value}` },
1104
+ fixture: { id: packet.agentInput.fixture.source.locator, digest: `sha256:${packet.agentInput.fixture.source.digest.value}` },
1105
+ model: { provider: model.provider, id: model.model, configurationDigest: `sha256:${entry.configuration.model.digest.value}` },
1106
+ harness: { id: PI_HARNESS, version: harness.version, configurationDigest: `sha256:${entry.configuration.harness.digest.value}` },
1107
+ runtime: [
1108
+ { source: "earendil-works", name: "pi-coding-agent", version: PINNED_PI_SDK_VERSION },
1109
+ { source: "EBO", name: PI_HARNESS, version: PINNED_PI_SDK_VERSION },
1110
+ { source: "EBO", name: "pi-sdk-adapter", version: PI_ADAPTER_VERSION },
1111
+ ],
1112
+ ...(packet.assessmentMode === "verified" ? { verifier: { locator: packet.restricted.verifier.locator, digest: `sha256:${packet.restricted.verifier.digest.value}`, format: verifierFormat } } : {}),
1113
+ },
1114
+ attempt: { id: attemptId, number: 1 },
1115
+ configuration: {
1116
+ digest: `sha256:${digestMetadata({ model: entry.configuration.model, harness: entry.configuration.harness, captureProfile }).value}`,
1117
+ captureProfileDigest: `sha256:${captureProfile.digest.value}`,
1118
+ budgetDigest: `sha256:${entry.configuration.nativeLimits.digest.value}`,
1119
+ toolPolicyDigest: `sha256:${entry.configuration.nativeToolPolicy.digest.value}`,
1120
+ },
1121
+ };
1122
+ }
1123
+ function withPinnedRuntime(definition) {
1124
+ return {
1125
+ ...structuredClone(definition),
1126
+ run: {
1127
+ ...structuredClone(definition.run),
1128
+ harness: { ...structuredClone(definition.run.harness), id: PI_HARNESS, version: PINNED_PI_SDK_VERSION },
1129
+ runtime: definition.run.runtime.filter(({ name }) => !["pi-coding-agent", PI_HARNESS, "pi-sdk-adapter"].includes(name)).concat([
1130
+ { source: "earendil-works", name: "pi-coding-agent", version: PINNED_PI_SDK_VERSION },
1131
+ { source: "EBO", name: PI_HARNESS, version: PINNED_PI_SDK_VERSION },
1132
+ { source: "EBO", name: "pi-sdk-adapter", version: PI_ADAPTER_VERSION },
1133
+ ]),
1134
+ },
1135
+ };
1136
+ }
1137
+ function piEvidence(sessionId, session) {
1138
+ return {
1139
+ sdkVersion: PINNED_PI_SDK_VERSION,
1140
+ adapterVersion: PI_ADAPTER_VERSION,
1141
+ ...(sessionId === undefined ? {} : { sessionId }),
1142
+ ...(session === undefined ? {} : { activeTools: session.getActiveToolNames() }),
1143
+ };
1144
+ }
1145
+ function piComposition(options, activeTools) {
1146
+ return {
1147
+ schemaVersion: "ebo.pi-composition/v1",
1148
+ sdk: { package: "@earendil-works/pi-coding-agent", version: PINNED_PI_SDK_VERSION },
1149
+ adapter: { id: `${PI_HARNESS}/v1`, version: PI_ADAPTER_VERSION },
1150
+ configurationDigest: options.definition.configuration.digest,
1151
+ model: {
1152
+ provider: options.model.provider,
1153
+ queueModelId: options.model.queueModelId,
1154
+ model: options.model.model,
1155
+ api: options.model.api,
1156
+ baseUrl: options.model.baseUrl,
1157
+ credentialEnvironment: options.model.apiKeyEnv,
1158
+ thinkingLevel: options.model.thinkingLevel,
1159
+ ...(options.model.thinkingLevelMap === undefined ? {} : { thinkingLevelMap: { ...options.model.thinkingLevelMap } }),
1160
+ reasoning: options.model.reasoning,
1161
+ },
1162
+ requestEnvironment: {
1163
+ piCacheRetention: process.env.PI_CACHE_RETENTION === "long" ? "long" : "short",
1164
+ },
1165
+ tools: {
1166
+ active: [...activeTools],
1167
+ excluded: [...(options.toolPolicy.excludeTools ?? [])],
1168
+ environmentAllowlist: [...options.toolPolicy.environmentAllowlist],
1169
+ },
1170
+ resources: {
1171
+ extensions: structuredClone(options.harness.extensions ?? []),
1172
+ skills: structuredClone(options.harness.skills ?? []),
1173
+ prompts: structuredClone(options.harness.prompts ?? []),
1174
+ contextFiles: structuredClone(options.harness.contextFiles ?? []),
1175
+ ...(options.harness.systemPrompt === undefined ? {} : { systemPrompt: structuredClone(options.harness.systemPrompt) }),
1176
+ },
1177
+ capture: {
1178
+ passiveObserver: options.captureProfile.passiveObserver !== false,
1179
+ includeProviderPayloads: options.captureProfile.includeProviderPayloads === true,
1180
+ },
1181
+ };
1182
+ }
1183
+ function reopenManifest(bundleRoot) {
1184
+ const textValue = new TextDecoder("utf-8", { fatal: true }).decode(readBoundedFile(join(bundleRoot, "manifest.json"), "Run manifest"));
1185
+ assertNoDuplicateJsonKeys(textValue);
1186
+ const value = JSON.parse(textValue);
1187
+ const errors = [...validateArtifact("manifest.json", value), ...validateRunManifestEvidence("manifest.json", value, bundleRoot)];
1188
+ if (errors.length > 0)
1189
+ throw new Error(`Retained run manifest failed validation:\n${formatErrors(errors)}`);
1190
+ return value;
1191
+ }
1192
+ function validateWorkspaceOutcome(value, reference) {
1193
+ if (!isRecord(value))
1194
+ throw piConfigError(reference, "workspaceOutcome must be an object");
1195
+ keys(value, ["excludeDirectoryNames", "respectGitignore", "omitEmptyDirectories"], ["excludeDirectoryNames"], reference);
1196
+ assertStringList(value.excludeDirectoryNames, "excludeDirectoryNames", reference);
1197
+ if (value.excludeDirectoryNames.some((name) => name.includes("/") || !isSafeArtifactRelativePath(name)))
1198
+ throw piConfigError(reference, "workspace exclusions must be safe path segments");
1199
+ for (const field of ["respectGitignore", "omitEmptyDirectories"])
1200
+ if (value[field] !== undefined && typeof value[field] !== "boolean")
1201
+ throw piConfigError(reference, `${field} must be boolean`);
1202
+ }
1203
+ function assertReferences(value, field, reference) {
1204
+ if (!Array.isArray(value) || new Set(value.map((entry) => JSON.stringify(entry))).size !== value.length)
1205
+ throw piConfigError(reference, `${field} must be a unique reference list`);
1206
+ for (const entry of value)
1207
+ assertReference(entry, field, reference);
1208
+ }
1209
+ function assertReference(value, field, reference) {
1210
+ if (!isRecord(value) || Object.keys(value).some((key) => !["locator", "digest"].includes(key)) || text(value.locator) === undefined
1211
+ || !isSafeArtifactRelativePath(String(value.locator)) || !isRecord(value.digest) || value.digest.algorithm !== "sha256"
1212
+ || typeof value.digest.value !== "string" || !/^[a-f0-9]{64}$/u.test(value.digest.value))
1213
+ throw piConfigError(reference, `${field} contains an invalid artifact reference`);
1214
+ }
1215
+ function keys(record, allowed, required, reference) {
1216
+ const allowedSet = new Set(allowed);
1217
+ const unexpected = Object.keys(record).find((key) => !allowedSet.has(key));
1218
+ if (unexpected !== undefined)
1219
+ throw piConfigError(reference, `contains unknown field "${unexpected}"`);
1220
+ const missing = required.find((key) => record[key] === undefined);
1221
+ if (missing !== undefined)
1222
+ throw piConfigError(reference, `is missing "${missing}"`);
1223
+ }
1224
+ function assertStringList(value, field, reference, allowed) {
1225
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string" || item.trim() === "") || new Set(value).size !== value.length
1226
+ || allowed !== undefined && value.some((item) => !allowed.includes(item)))
1227
+ throw piConfigError(reference, `${field} must be a unique supported string list`);
1228
+ }
1229
+ function assertHttpUrl(value, reference) {
1230
+ requiredText(value, "baseUrl", reference);
1231
+ let url;
1232
+ try {
1233
+ url = new URL(value);
1234
+ }
1235
+ catch {
1236
+ throw piConfigError(reference, "baseUrl must be an absolute URL");
1237
+ }
1238
+ if (!/^https?:$/u.test(url.protocol) || url.username !== "" || url.password !== "" || url.search !== "" || url.hash !== "")
1239
+ throw piConfigError(reference, "baseUrl must be an HTTP(S) URL without credentials, query, or fragment");
1240
+ }
1241
+ function requiredText(value, field, reference) {
1242
+ if (typeof value !== "string" || value.trim() === "")
1243
+ throw piConfigError(reference, `${field} must be nonempty`);
1244
+ }
1245
+ function positive(value, field, reference) {
1246
+ if (!Number.isSafeInteger(value) || value < 1)
1247
+ throw piConfigError(reference, `${field} must be a positive safe integer`);
1248
+ }
1249
+ function piConfigError(reference, detail) {
1250
+ return new Error(`Pi configuration "${reference.locator}" ${detail}.`);
1251
+ }
1252
+ function compactAttributes(input) {
1253
+ return Object.fromEntries(Object.entries(input).filter((entry) => entry[1] !== undefined));
1254
+ }
1255
+ function sameTextSet(left, right) {
1256
+ return left.size === right.size && [...left].every((value) => right.has(value));
1257
+ }
1258
+ function usageAttributes(usage, usageScope) {
1259
+ const cost = isRecord(usage.cost) ? usage.cost : undefined;
1260
+ return compactAttributes({
1261
+ inputTokens: number(usage.input),
1262
+ outputTokens: number(usage.output),
1263
+ cacheReadInputTokens: number(usage.cacheRead),
1264
+ cacheCreationInputTokens: number(usage.cacheWrite),
1265
+ reasoningOutputTokens: number(usage.reasoning),
1266
+ totalTokens: number(usage.totalTokens),
1267
+ totalCostUsd: number(cost?.total),
1268
+ resourceSemantics: "increment",
1269
+ usageScope,
1270
+ });
1271
+ }
1272
+ function jsonSnapshot(value) {
1273
+ try {
1274
+ return JSON.parse(JSON.stringify(value));
1275
+ }
1276
+ catch {
1277
+ throw new Error("Pi callback value is not JSON-safe.");
1278
+ }
1279
+ }
1280
+ function compactPiStreamEvent(event) {
1281
+ if (event.type !== "message_update")
1282
+ return event;
1283
+ const update = event.assistantMessageEvent;
1284
+ const { partial: _partial, ...increment } = update;
1285
+ if (update.type === "toolcall_start" && Number.isSafeInteger(update.contentIndex) && isRecord(update.partial)
1286
+ && Array.isArray(update.partial.content)) {
1287
+ const toolCall = update.partial.content[Number(update.contentIndex)];
1288
+ if (isRecord(toolCall) && toolCall.type === "toolCall") {
1289
+ increment.id = toolCall.id;
1290
+ increment.toolName = toolCall.name;
1291
+ }
1292
+ }
1293
+ const message = event.message;
1294
+ return { type: event.type, ...(isRecord(message.usage) ? { usage: message.usage } : {}), assistantMessageEvent: increment };
1295
+ }
1296
+ function valueShape(value) {
1297
+ if (Array.isArray(value))
1298
+ return { type: "array", length: value.length };
1299
+ if (isRecord(value))
1300
+ return { type: "object", keys: Object.keys(value).sort() };
1301
+ return { type: typeof value };
1302
+ }
1303
+ function lineNumber(locator) {
1304
+ const match = locator.match(/^line:(\d+)$/u);
1305
+ return match === null ? undefined : Number(match[1]);
1306
+ }
1307
+ function millisTimestamp(value) {
1308
+ if (typeof value !== "number" || !Number.isFinite(value))
1309
+ return undefined;
1310
+ const timestamp = new Date(value).toISOString();
1311
+ return Number.isNaN(Date.parse(timestamp)) ? undefined : timestamp;
1312
+ }
1313
+ function text(value) {
1314
+ return typeof value === "string" && value.trim() !== "" ? value : undefined;
1315
+ }
1316
+ function number(value) {
1317
+ return typeof value === "number" && Number.isFinite(value) ? value : undefined;
1318
+ }
1319
+ function isRecord(value) {
1320
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1321
+ }
1322
+ function errorMessage(error) {
1323
+ return error instanceof Error ? error.message : String(error);
1324
+ }
1325
+ async function nonempty(path) {
1326
+ try {
1327
+ return (await stat(path)).size > 0;
1328
+ }
1329
+ catch (error) {
1330
+ if (error.code === "ENOENT")
1331
+ return false;
1332
+ throw error;
1333
+ }
1334
+ }
1335
+ async function exists(path) {
1336
+ try {
1337
+ await lstat(path);
1338
+ return true;
1339
+ }
1340
+ catch (error) {
1341
+ if (error.code === "ENOENT")
1342
+ return false;
1343
+ throw error;
1344
+ }
1345
+ }