@unbrained/pm-cli 2026.9.2 → 2026.9.4

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 (165) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/CHANGELOG.md +44 -0
  3. package/dist/cli/after-command-context-usage.js +16 -3
  4. package/dist/cli-bundle/bundle-manifest.json +151 -151
  5. package/dist/cli-bundle/chunks/chunk-6XSKPNCF.js +230 -0
  6. package/dist/cli-bundle/chunks/chunk-A24WAVE6.js +8 -0
  7. package/dist/cli-bundle/chunks/{chunk-TAG4VC22.js → chunk-HI6BHPCU.js} +27 -27
  8. package/dist/cli-bundle/chunks/{chunk-Y7VOMSZT.js → chunk-I2AS367G.js} +2 -2
  9. package/dist/cli-bundle/chunks/{chunk-X4RCGLTX.js → chunk-OGBVU67G.js} +5 -5
  10. package/dist/cli-bundle/chunks/{chunk-DTGWEL23.js → chunk-RI4XNOYS.js} +2 -2
  11. package/dist/cli-bundle/chunks/chunk-TXW6VL33.js +38 -0
  12. package/dist/cli-bundle/chunks/{chunk-FZ4Z2J4F.js → chunk-V72UV7QS.js} +76 -79
  13. package/dist/cli-bundle/chunks/chunk-VGLHECAP.js +9 -0
  14. package/dist/cli-bundle/chunks/{chunk-T6H5BZCP.js → chunk-X6CMGV6L.js} +2 -2
  15. package/dist/cli-bundle/chunks/{chunk-3WICXLVT.js → chunk-YW4QK2G7.js} +2 -2
  16. package/dist/cli-bundle/chunks/{register-list-query-XYNX4ASQ.js → register-list-query-Z5YYSJJG.js} +2 -2
  17. package/dist/cli-bundle/chunks/{register-mutation-WFQ7MJTX.js → register-mutation-ELSP6I4I.js} +2 -2
  18. package/dist/cli-bundle/chunks/{register-operations-JVTI7JPP.js → register-operations-OH7R3ZT4.js} +2 -2
  19. package/dist/cli-bundle/chunks/{register-setup-KVNC5KPL.js → register-setup-E2AE72QG.js} +2 -2
  20. package/dist/cli-bundle/focused-chunks/{chunk-MEASX544.js → chunk-25UJBH4M.js} +2 -2
  21. package/dist/cli-bundle/focused-chunks/{chunk-SVXJYD6G.js → chunk-3646BZ4W.js} +2 -2
  22. package/dist/cli-bundle/focused-chunks/chunk-4YRAVI43.js +12 -0
  23. package/dist/cli-bundle/focused-chunks/chunk-53IHPICX.js +3 -0
  24. package/dist/cli-bundle/focused-chunks/chunk-5P4HVOWG.js +176 -0
  25. package/dist/cli-bundle/focused-chunks/chunk-636VVQ7F.js +54 -0
  26. package/dist/cli-bundle/focused-chunks/{chunk-4O4CJAR6.js → chunk-6PWPQKDN.js} +2 -2
  27. package/dist/cli-bundle/focused-chunks/chunk-7N2TV7WZ.js +8 -0
  28. package/dist/cli-bundle/focused-chunks/chunk-AJR3OP2E.js +3 -0
  29. package/dist/cli-bundle/focused-chunks/chunk-B3A6MJYE.js +2 -0
  30. package/dist/cli-bundle/focused-chunks/chunk-BFDDJ2T2.js +2 -0
  31. package/dist/cli-bundle/focused-chunks/chunk-BHUIRZWQ.js +9 -0
  32. package/dist/cli-bundle/focused-chunks/chunk-JADHHPY6.js +6 -0
  33. package/dist/cli-bundle/focused-chunks/{chunk-KB5OFSFD.js → chunk-MHKUIPJW.js} +2 -2
  34. package/dist/cli-bundle/focused-chunks/chunk-NBMJD6MK.js +2 -0
  35. package/dist/cli-bundle/focused-chunks/chunk-NSKBLHVI.js +20 -0
  36. package/dist/cli-bundle/focused-chunks/{chunk-BEVHCEWG.js → chunk-P2GOAOQN.js} +2 -2
  37. package/dist/cli-bundle/focused-chunks/chunk-YARWJM2B.js +2 -0
  38. package/dist/cli-bundle/main.js +15 -15
  39. package/dist/cli-bundle/sdk-authoring.js +1 -1
  40. package/dist/cli-bundle/sdk-contracts.js +1 -1
  41. package/dist/cli-bundle/sdk-core.js +28 -31
  42. package/dist/cli-bundle/sdk-governance.js +1 -1
  43. package/dist/cli-bundle/sdk-graph.js +1 -1
  44. package/dist/cli-bundle/sdk-merge.js +43 -36
  45. package/dist/cli-bundle/sdk-query.js +1 -1
  46. package/dist/cli-bundle/sdk-runtime.js +1 -1
  47. package/dist/cli-bundle/sdk-testing.js +1 -1
  48. package/dist/cli-bundle/sdk.js +10 -10
  49. package/dist/core/diagnostics/remediation.js +4 -4
  50. package/dist/core/history/event-classification.d.ts +21 -0
  51. package/dist/core/history/event-classification.js +109 -0
  52. package/dist/core/history/event-index.d.ts +14 -3
  53. package/dist/core/history/event-index.js +543 -88
  54. package/dist/core/history/history.d.ts +37 -1
  55. package/dist/core/history/history.js +320 -71
  56. package/dist/core/history/replay.js +68 -34
  57. package/dist/core/history/workspace-history.js +7 -6
  58. package/dist/core/lock/lock.d.ts +32 -2
  59. package/dist/core/lock/lock.js +161 -20
  60. package/dist/core/output/output.js +31 -7
  61. package/dist/core/shared/author.d.ts +3 -1
  62. package/dist/core/shared/author.js +111 -23
  63. package/dist/core/shared/time.d.ts +2 -0
  64. package/dist/core/shared/time.js +8 -2
  65. package/dist/core/store/item-metadata-query-index.js +5 -5
  66. package/dist/sdk/context/recency.d.ts +9 -0
  67. package/dist/sdk/context/recency.js +34 -0
  68. package/dist/sdk/context/usage-egress.d.ts +9 -0
  69. package/dist/sdk/context/usage-egress.js +30 -0
  70. package/dist/sdk/context-intent-contracts.js +7 -3
  71. package/dist/sdk/context-relevance.d.ts +23 -0
  72. package/dist/sdk/context-relevance.js +94 -23
  73. package/dist/sdk/context-signal-store.d.ts +7 -3
  74. package/dist/sdk/context-signal-store.js +155 -42
  75. package/dist/sdk/context-usage.d.ts +54 -0
  76. package/dist/sdk/context-usage.js +245 -16
  77. package/dist/sdk/environment/agent-environment.d.ts +296 -0
  78. package/dist/sdk/environment/agent-environment.js +467 -0
  79. package/dist/sdk/environment/project-runtime-compatibility.d.ts +19 -1
  80. package/dist/sdk/environment/project-runtime-compatibility.js +118 -21
  81. package/dist/sdk/generated/generated-error-code-catalog-part-1.js +14 -2
  82. package/dist/sdk/generated/generated-error-code-catalog-part-2.js +14 -2
  83. package/dist/sdk/governance/health.js +13 -44
  84. package/dist/sdk/governance/merge-receipt-history.d.ts +62 -0
  85. package/dist/sdk/governance/merge-receipt-history.js +207 -0
  86. package/dist/sdk/governance/provenance-health.js +9 -3
  87. package/dist/sdk/history-compact.js +14 -5
  88. package/dist/sdk/history-redact.js +58 -23
  89. package/dist/sdk/history-repair.js +16 -6
  90. package/dist/sdk/index.d.ts +4 -0
  91. package/dist/sdk/index.js +6 -2
  92. package/dist/sdk/init.d.ts +2 -0
  93. package/dist/sdk/init.js +18 -9
  94. package/dist/sdk/merge/driver.d.ts +3 -1
  95. package/dist/sdk/merge/driver.js +9 -9
  96. package/dist/sdk/merge/index.d.ts +1 -1
  97. package/dist/sdk/merge/index.js +3 -3
  98. package/dist/sdk/merge/receipts.d.ts +17 -6
  99. package/dist/sdk/merge/receipts.js +246 -75
  100. package/dist/sdk/merge/reconcile.d.ts +9 -7
  101. package/dist/sdk/merge/reconcile.js +175 -12
  102. package/dist/sdk/merge/three-way.d.ts +22 -2
  103. package/dist/sdk/merge/three-way.js +92 -25
  104. package/dist/sdk/query/context.d.ts +3 -1
  105. package/dist/sdk/query/context.js +21 -9
  106. package/dist/sdk/query/next.js +18 -9
  107. package/dist/sdk/runtime-input.d.ts +2 -0
  108. package/dist/sdk/runtime-input.js +25 -2
  109. package/dist/sdk/runtime-primitives.d.ts +2 -2
  110. package/dist/sdk/runtime-primitives.js +4 -4
  111. package/dist/sdk/runtime.d.ts +1 -0
  112. package/dist/sdk/runtime.js +17 -26
  113. package/dist/types.d.ts +27 -0
  114. package/dist/types.js +2 -2
  115. package/docs/AGENT_ENVIRONMENTS.md +47 -0
  116. package/docs/AGENT_PROVENANCE_ADR.md +17 -2
  117. package/docs/CONTEXT_INTEGRITY_CONTRACTS.md +38 -1
  118. package/docs/CONTEXT_RELEVANCE.md +44 -1
  119. package/docs/MERGE_SAFETY.md +119 -23
  120. package/docs/PERFORMANCE.md +6 -3
  121. package/docs/README.md +1 -0
  122. package/docs/READ_OUTPUT_CONTRACTS.md +9 -1
  123. package/docs/SDK.md +11 -8
  124. package/docs/SDK_CONTEXT.md +7 -0
  125. package/docs/SDK_CONTEXT_EVIDENCE_CONTRACTS.md +24 -2
  126. package/docs/SDK_RUNTIME_BOUNDARIES.md +17 -1
  127. package/docs/examples/sdk-agent-environment/README.md +14 -0
  128. package/docs/examples/sdk-agent-environment/package.json +8 -0
  129. package/docs/examples/sdk-agent-environment/run-agent-environment.mjs +154 -0
  130. package/docs/generated/REFUSAL_CLOSURE_CENSUS.md +5 -3
  131. package/marketplace.json +2 -2
  132. package/package.json +5 -5
  133. package/packages/pm-beads/package.json +1 -1
  134. package/packages/pm-calendar/package.json +1 -1
  135. package/packages/pm-command-kit/package.json +1 -1
  136. package/packages/pm-digital-twin/package.json +1 -1
  137. package/packages/pm-governance-audit/package.json +1 -1
  138. package/packages/pm-guide-shell/package.json +1 -1
  139. package/packages/pm-kanban/package.json +1 -1
  140. package/packages/pm-lifecycle-hooks/package.json +1 -1
  141. package/packages/pm-linked-test-adapters/package.json +1 -1
  142. package/packages/pm-search-advanced/package.json +1 -1
  143. package/packages/pm-templates/package.json +1 -1
  144. package/packages/pm-todos/package.json +1 -1
  145. package/packages/pm-vcs/package.json +1 -1
  146. package/plugins/pm-claude/.claude-plugin/plugin.json +1 -1
  147. package/plugins/pm-codex/.codex-plugin/plugin.json +1 -1
  148. package/sdk/public-surface.json +773 -46
  149. package/dist/cli-bundle/chunks/chunk-GOACULJW.js +0 -13
  150. package/dist/cli-bundle/chunks/chunk-NOPL37X4.js +0 -205
  151. package/dist/cli-bundle/chunks/chunk-UDW64J2W.js +0 -8
  152. package/dist/cli-bundle/chunks/chunk-ZLJIFSOE.js +0 -35
  153. package/dist/cli-bundle/focused-chunks/chunk-242AXYB2.js +0 -2
  154. package/dist/cli-bundle/focused-chunks/chunk-2FEZ5SKM.js +0 -3
  155. package/dist/cli-bundle/focused-chunks/chunk-6GCRSLPG.js +0 -2
  156. package/dist/cli-bundle/focused-chunks/chunk-7VATS5QY.js +0 -16
  157. package/dist/cli-bundle/focused-chunks/chunk-JEFKLWMM.js +0 -2
  158. package/dist/cli-bundle/focused-chunks/chunk-MKHG3T76.js +0 -20
  159. package/dist/cli-bundle/focused-chunks/chunk-O67VDLOT.js +0 -51
  160. package/dist/cli-bundle/focused-chunks/chunk-PF3E6Q36.js +0 -8
  161. package/dist/cli-bundle/focused-chunks/chunk-QDFJIRKN.js +0 -4
  162. package/dist/cli-bundle/focused-chunks/chunk-RMRCQPWU.js +0 -2
  163. package/dist/cli-bundle/focused-chunks/chunk-S4U5CN5Q.js +0 -2
  164. package/dist/cli-bundle/focused-chunks/chunk-T2LXXSOD.js +0 -158
  165. package/dist/cli-bundle/focused-chunks/chunk-Z2USIBR2.js +0 -5
@@ -0,0 +1,467 @@
1
+ /**
2
+ * @module sdk/agent-environment
3
+ *
4
+ * Declares reproducible, budgeted agent observations, total verdicts, and
5
+ * isolated project-management episodes over adapter-provided recorded state.
6
+ */
7
+
8
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="38cb0b88-262f-5061-b95a-f4532bc52613")}catch(e){}}();
9
+ import { sha256Hex, stableStringify } from "../../core/shared/serialization.js";
10
+ import { runWithAgentSessionContext } from "../agent-session-context.js";
11
+ import { defineWorkspaceRecipe, } from "../workspace-recipe.js";
12
+ /** Schema identifier for a portable observation contract. */
13
+ export const PM_OBSERVATION_SCHEMA = "pm.agent-observation/v1";
14
+ /** Schema identifier for a deterministic verdict contract. */
15
+ export const PM_VERDICT_SCHEMA = "pm.agent-verdict/v1";
16
+ /** Schema identifier for a bounded agent-environment episode. */
17
+ export const PM_EPISODE_SCHEMA = "pm.agent-episode/v1";
18
+ function assertPositiveInteger(value, label) {
19
+ if (!Number.isSafeInteger(value) || value <= 0) {
20
+ throw new Error(`${label} must be a positive safe integer.`);
21
+ }
22
+ }
23
+ /** Reject blank user-facing identifiers at the declaration boundary. */
24
+ function assertNonEmpty(value, label) {
25
+ if (value.trim().length === 0)
26
+ throw new Error(`${label} must not be empty.`);
27
+ }
28
+ /** Recursively freeze a structured clone without mutating caller input. */
29
+ function frozenCopy(value) {
30
+ if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
31
+ Object.freeze(value);
32
+ for (const nested of Object.values(value))
33
+ frozenCopy(nested);
34
+ }
35
+ return value;
36
+ }
37
+ /** Validate bounded read declarations and reject arithmetic overflow. */
38
+ function validateObservationReads(reads) {
39
+ if (reads.length === 0) {
40
+ throw new Error("observation.reads must declare at least one bounded read.");
41
+ }
42
+ let readCeiling = 0;
43
+ for (const read of reads) {
44
+ assertNonEmpty(read.command, "observation.reads.command");
45
+ assertPositiveInteger(read.budget_tokens, "observation.reads.budget_tokens");
46
+ readCeiling += read.budget_tokens;
47
+ if (!Number.isSafeInteger(readCeiling)) {
48
+ throw new Error("observation read ceilings exceed safe integer range.");
49
+ }
50
+ }
51
+ }
52
+ /** Validate that emitted-byte expectations are supported by bounded samples. */
53
+ function validateObservationCalibration(calibration) {
54
+ if (calibration.basis !== "emitted_bytes") {
55
+ throw new Error("observation calibration must use emitted_bytes.");
56
+ }
57
+ for (const sample of calibration.samples) {
58
+ if (!Number.isSafeInteger(sample) || sample < 0) {
59
+ throw new Error("observation calibration samples must be non-negative safe integers.");
60
+ }
61
+ }
62
+ if (calibration.samples.length === 0) {
63
+ throw new Error("observation calibration must include at least one sample.");
64
+ }
65
+ assertPositiveInteger(calibration.expected_emitted_bytes, "observation.calibration.expected_emitted_bytes");
66
+ if (!Number.isSafeInteger(calibration.tolerance_bytes) ||
67
+ calibration.tolerance_bytes < 0) {
68
+ throw new Error("observation.calibration.tolerance_bytes must be a non-negative safe integer.");
69
+ }
70
+ const sampleMean = calibration.samples.reduce((total, sample) => total + sample, 0) /
71
+ calibration.samples.length;
72
+ if (Math.abs(sampleMean - calibration.expected_emitted_bytes) >
73
+ calibration.tolerance_bytes) {
74
+ throw new Error("observation expected_emitted_bytes is outside the calibrated sample tolerance.");
75
+ }
76
+ }
77
+ /** Validate the ordered disclosure ladder and every projected field path. */
78
+ function validateObservationTiers(tiers) {
79
+ if (tiers.length === 0) {
80
+ throw new Error("observation.tiers must declare a degradation ladder.");
81
+ }
82
+ const tierIds = new Set();
83
+ let previousTierBudget = Number.POSITIVE_INFINITY;
84
+ for (const tier of tiers) {
85
+ assertNonEmpty(tier.id, "observation.tiers.id");
86
+ assertPositiveInteger(tier.max_tokens, "observation.tiers.max_tokens");
87
+ if (tierIds.has(tier.id))
88
+ throw new Error(`Duplicate observation tier: ${tier.id}.`);
89
+ tierIds.add(tier.id);
90
+ if (tier.max_tokens > previousTierBudget) {
91
+ throw new Error("observation tiers must be ordered from largest to smallest max_tokens.");
92
+ }
93
+ previousTierBudget = tier.max_tokens;
94
+ const fields = new Set();
95
+ for (const field of tier.fields) {
96
+ parseFieldPath(field, "observation.tiers.fields");
97
+ if (fields.has(field)) {
98
+ throw new Error(`Duplicate observation tier field: ${field}.`);
99
+ }
100
+ fields.add(field);
101
+ }
102
+ }
103
+ }
104
+ /** Validate, clone, and freeze a portable observation declaration. */
105
+ export function defineObservationContract(declaration) {
106
+ if (declaration.schema !== PM_OBSERVATION_SCHEMA) {
107
+ throw new Error(`observation.schema must be ${PM_OBSERVATION_SCHEMA}.`);
108
+ }
109
+ assertNonEmpty(declaration.id, "observation.id");
110
+ assertPositiveInteger(declaration.version, "observation.version");
111
+ validateObservationReads(declaration.reads);
112
+ validateObservationCalibration(declaration.calibration);
113
+ if (declaration.corpus_dependence.kind === "bounded") {
114
+ assertNonEmpty(declaration.corpus_dependence.dimension, "observation.corpus_dependence.dimension");
115
+ assertPositiveInteger(declaration.corpus_dependence.maximum, "observation.corpus_dependence.maximum");
116
+ }
117
+ validateObservationTiers(declaration.tiers);
118
+ return frozenCopy(structuredClone(declaration));
119
+ }
120
+ /** Describe guaranteed and calibrated observation cost before collection. */
121
+ export function describeObservationCost(contract) {
122
+ return {
123
+ contract_id: contract.id,
124
+ contract_version: contract.version,
125
+ guaranteed_ceiling_tokens: contract.reads.reduce((total, read) => total + read.budget_tokens, 0),
126
+ expected_emitted_bytes: contract.calibration.expected_emitted_bytes,
127
+ expected_tokens: Math.ceil(contract.calibration.expected_emitted_bytes / 4),
128
+ tolerance_bytes: contract.calibration.tolerance_bytes,
129
+ calibration_basis: "emitted_bytes",
130
+ corpus_dependence: contract.corpus_dependence,
131
+ };
132
+ }
133
+ /** Serve the richest declared tier that fits both caller and tier ceilings. */
134
+ export async function serveDeclaredObservation(contract, collect, request) {
135
+ assertPositiveInteger(request.budget_tokens, "observation request budget_tokens");
136
+ const attempted = [];
137
+ for (const [index, tier] of contract.tiers.entries()) {
138
+ attempted.push(tier.id);
139
+ const payload = await collect(tier);
140
+ const emittedBytes = Buffer.byteLength(stableStringify(payload), "utf8");
141
+ const estimatedTokens = Math.ceil(emittedBytes / 4);
142
+ if (estimatedTokens <= Math.min(request.budget_tokens, tier.max_tokens)) {
143
+ return {
144
+ status: "served",
145
+ payload,
146
+ receipt: {
147
+ contract_id: contract.id,
148
+ contract_version: contract.version,
149
+ served_tier: tier.id,
150
+ degraded: index > 0,
151
+ attempted_tiers: attempted,
152
+ emitted_bytes: emittedBytes,
153
+ estimated_tokens: estimatedTokens,
154
+ calibration_basis: "emitted_bytes",
155
+ },
156
+ };
157
+ }
158
+ }
159
+ return {
160
+ status: "refused",
161
+ reason: "no_declared_tier_fits_budget",
162
+ receipt: {
163
+ contract_id: contract.id,
164
+ contract_version: contract.version,
165
+ served_tier: null,
166
+ degraded: contract.tiers.length > 1,
167
+ attempted_tiers: attempted,
168
+ emitted_bytes: null,
169
+ estimated_tokens: null,
170
+ calibration_basis: "emitted_bytes",
171
+ },
172
+ };
173
+ }
174
+ /** Parse a safe dot path and reject prototype-mutating or ambiguous segments. */
175
+ function parseFieldPath(path, label) {
176
+ const segments = path.split(".");
177
+ if (segments.some((segment) => segment.length === 0 ||
178
+ segment === "__proto__" ||
179
+ segment === "prototype" ||
180
+ segment === "constructor")) {
181
+ throw new Error(`${label} must be a safe, non-empty dot-separated path.`);
182
+ }
183
+ return segments;
184
+ }
185
+ /** Read a declared field without interpreting inherited properties. */
186
+ function valueAtPath(root, path) {
187
+ let value = root;
188
+ for (const segment of parseFieldPath(path, "recorded field path")) {
189
+ if (value === null ||
190
+ typeof value !== "object" ||
191
+ !Object.hasOwn(value, segment)) {
192
+ return undefined;
193
+ }
194
+ value = value[segment];
195
+ }
196
+ return value;
197
+ }
198
+ /** Convert a safe dot path into an RFC 6901 evidence pointer. */
199
+ function jsonPointer(path) {
200
+ return `/${parseFieldPath(path, "verdict predicate path")
201
+ .map((segment) => segment.replaceAll("~", "~0").replaceAll("/", "~1"))
202
+ .join("/")}`;
203
+ }
204
+ /** Reject equality operands that cannot survive portable JSON storage. */
205
+ function assertPortableExpectedValue(predicate) {
206
+ if (predicate.kind !== "field_equals")
207
+ return;
208
+ let serializedExpected;
209
+ try {
210
+ serializedExpected = JSON.stringify(predicate.expected);
211
+ }
212
+ catch {
213
+ throw new Error(`Expected value for verdict predicate ${predicate.id} must be JSON serializable.`);
214
+ }
215
+ if (serializedExpected === undefined) {
216
+ throw new Error(`Expected value for verdict predicate ${predicate.id} must be JSON serializable.`);
217
+ }
218
+ }
219
+ /** Validate, clone, and freeze a portable total-verdict declaration. */
220
+ export function defineVerdictContract(declaration) {
221
+ if (declaration.schema !== PM_VERDICT_SCHEMA) {
222
+ throw new Error(`verdict.schema must be ${PM_VERDICT_SCHEMA}.`);
223
+ }
224
+ assertNonEmpty(declaration.id, "verdict.id");
225
+ assertPositiveInteger(declaration.version, "verdict.version");
226
+ const predicateIds = new Set();
227
+ for (const predicate of declaration.predicates) {
228
+ assertNonEmpty(predicate.id, "verdict.predicates.id");
229
+ parseFieldPath(predicate.path, "verdict.predicates.path");
230
+ const runtimeKind = predicate.kind;
231
+ if (runtimeKind !== "field_equals" &&
232
+ runtimeKind !== "field_present" &&
233
+ runtimeKind !== "field_absent") {
234
+ throw new Error(`Unsupported verdict predicate kind for ${predicate.id}.`);
235
+ }
236
+ if (predicateIds.has(predicate.id))
237
+ throw new Error(`Duplicate verdict predicate: ${predicate.id}.`);
238
+ assertPortableExpectedValue(predicate);
239
+ predicateIds.add(predicate.id);
240
+ }
241
+ if (declaration.composition.operator !== "all" &&
242
+ declaration.composition.operator !== "any") {
243
+ throw new Error("verdict composition operator must be all or any.");
244
+ }
245
+ if (declaration.composition.predicate_ids.length !==
246
+ declaration.predicates.length) {
247
+ throw new Error("verdict composition must reference every predicate exactly once.");
248
+ }
249
+ const compositionIds = new Set(declaration.composition.predicate_ids);
250
+ if (compositionIds.size !== declaration.composition.predicate_ids.length ||
251
+ [...compositionIds].some((id) => !predicateIds.has(id))) {
252
+ throw new Error("verdict composition contains duplicate or unknown predicate ids.");
253
+ }
254
+ return frozenCopy(structuredClone(declaration));
255
+ }
256
+ /** Evaluate a total verdict exclusively from one immutable recorded snapshot. */
257
+ export function evaluateVerdictContract(contract, recorded) {
258
+ if (contract.predicates.length === 0) {
259
+ return {
260
+ contract_id: contract.id,
261
+ contract_version: contract.version,
262
+ snapshot_id: recorded.snapshot_id,
263
+ outcome: "not_applicable",
264
+ score: null,
265
+ policy: "verdict_only_no_reward",
266
+ reason: "no_predicates_declared",
267
+ predicates: [],
268
+ };
269
+ }
270
+ const byId = new Map(contract.predicates.map((predicate) => [predicate.id, predicate]));
271
+ const predicates = contract.composition.predicate_ids.map((id) => {
272
+ const predicate = byId.get(id);
273
+ const observed = valueAtPath(recorded.state, predicate.path);
274
+ const satisfied = predicate.kind === "field_present"
275
+ ? observed !== undefined
276
+ : predicate.kind === "field_absent"
277
+ ? observed === undefined
278
+ : stableStringify(observed) === stableStringify(predicate.expected);
279
+ return {
280
+ id,
281
+ outcome: satisfied ? "satisfied" : "violated",
282
+ evidence_pointer: `${recorded.snapshot_id}#${jsonPointer(predicate.path)}`,
283
+ evidence_digest: sha256Hex(stableStringify(observed)),
284
+ };
285
+ });
286
+ const satisfiedCount = predicates.filter((predicate) => predicate.outcome === "satisfied").length;
287
+ const satisfied = contract.composition.operator === "all"
288
+ ? satisfiedCount === predicates.length
289
+ : satisfiedCount > 0;
290
+ return {
291
+ contract_id: contract.id,
292
+ contract_version: contract.version,
293
+ snapshot_id: recorded.snapshot_id,
294
+ outcome: satisfied ? "satisfied" : "violated",
295
+ score: satisfiedCount / predicates.length,
296
+ policy: "verdict_only_no_reward",
297
+ predicates,
298
+ };
299
+ }
300
+ /** Validate, clone, and freeze a portable episode specification. */
301
+ export function defineEpisodeSpecification(declaration) {
302
+ if (declaration.schema !== PM_EPISODE_SCHEMA) {
303
+ throw new Error(`episode.schema must be ${PM_EPISODE_SCHEMA}.`);
304
+ }
305
+ assertNonEmpty(declaration.id, "episode.id");
306
+ assertPositiveInteger(declaration.version, "episode.version");
307
+ if (!["benchmark", "evaluation", "training", "scale_transfer"].includes(declaration.mode)) {
308
+ throw new Error("episode.mode is not supported.");
309
+ }
310
+ if (declaration.task_item_ids.length === 0) {
311
+ throw new Error("episode.task_item_ids must declare at least one pm item.");
312
+ }
313
+ for (const itemId of declaration.task_item_ids) {
314
+ assertNonEmpty(itemId, "episode.task_item_ids");
315
+ }
316
+ assertPositiveInteger(declaration.limits.max_actions, "episode.limits.max_actions");
317
+ assertPositiveInteger(declaration.limits.max_observations, "episode.limits.max_observations");
318
+ assertPositiveInteger(declaration.limits.max_tokens_per_observation, "episode.limits.max_tokens_per_observation");
319
+ for (const field of declaration.observable_fields) {
320
+ parseFieldPath(field, "episode.observable_fields");
321
+ }
322
+ for (const field of declaration.withheld_fields) {
323
+ parseFieldPath(field, "episode.withheld_fields");
324
+ }
325
+ for (const observable of declaration.observable_fields) {
326
+ const overlap = declaration.withheld_fields.find((withheld) => observable === withheld ||
327
+ observable.startsWith(`${withheld}.`) ||
328
+ withheld.startsWith(`${observable}.`));
329
+ if (overlap !== undefined) {
330
+ throw new Error(`Observable field ${observable} overlaps withheld field ${overlap}.`);
331
+ }
332
+ }
333
+ for (const tier of declaration.observation.tiers) {
334
+ for (const field of tier.fields) {
335
+ if (!declaration.observable_fields.includes(field)) {
336
+ throw new Error(`Observation tier field ${field} is not declared observable.`);
337
+ }
338
+ }
339
+ }
340
+ defineObservationContract(declaration.observation);
341
+ defineVerdictContract(declaration.verdict);
342
+ defineWorkspaceRecipe(declaration.recipe);
343
+ return frozenCopy(structuredClone(declaration));
344
+ }
345
+ /** Copy one selected field into a null-prototype-safe projected object. */
346
+ function assignProjectedPath(target, path, value) {
347
+ const segments = parseFieldPath(path, "observation projection field");
348
+ let cursor = target;
349
+ for (const [index, segment] of segments.entries()) {
350
+ if (index === segments.length - 1) {
351
+ if (value !== undefined)
352
+ cursor[segment] = structuredClone(value);
353
+ return;
354
+ }
355
+ const nested = cursor[segment];
356
+ if (nested === null ||
357
+ typeof nested !== "object" ||
358
+ Array.isArray(nested)) {
359
+ cursor[segment] = {};
360
+ }
361
+ cursor = cursor[segment];
362
+ }
363
+ }
364
+ /** Project only declared tier fields from recorded workspace state. */
365
+ function projectedState(state, fields) {
366
+ const result = {};
367
+ for (const field of fields)
368
+ assignProjectedPath(result, field, valueAtPath(state, field));
369
+ return result;
370
+ }
371
+ /** Open one isolated, deterministic episode around an adapter's ordinary actions. */
372
+ export async function openPmEpisode(specification, adapter) {
373
+ const trajectory = [];
374
+ let actions = 0;
375
+ let observations = 0;
376
+ let closed = false;
377
+ let closing;
378
+ const context = {
379
+ episode: {
380
+ id: specification.id,
381
+ label: `pm agent environment v${specification.version}`,
382
+ },
383
+ provenance: { role: specification.mode, topic: "agent-environment" },
384
+ };
385
+ const reset = await runWithAgentSessionContext(context, () => adapter.reset(specification.recipe));
386
+ trajectory.push({ sequence: 1, kind: "reset", state_id: reset.state_id });
387
+ const readObservation = async () => {
388
+ if (closed)
389
+ throw new Error("episode is already closed.");
390
+ if (observations >= specification.limits.max_observations) {
391
+ throw new Error("episode exceeded limits.max_observations.");
392
+ }
393
+ observations += 1;
394
+ const recorded = await adapter.readRecordedState();
395
+ const result = await serveDeclaredObservation(specification.observation, async (tier) => projectedState(recorded.state, tier.fields), { budget_tokens: specification.limits.max_tokens_per_observation });
396
+ trajectory.push({
397
+ sequence: trajectory.length + 1,
398
+ kind: "observation",
399
+ state_id: result.receipt.served_tier ?? recorded.snapshot_id,
400
+ });
401
+ return result;
402
+ };
403
+ const evaluate = async (reservedForClose = false) => {
404
+ if (closed && !reservedForClose)
405
+ throw new Error("episode is already closed.");
406
+ if (observations >= specification.limits.max_observations) {
407
+ throw new Error("episode exceeded limits.max_observations.");
408
+ }
409
+ observations += 1;
410
+ const recorded = await adapter.readRecordedState();
411
+ const result = evaluateVerdictContract(specification.verdict, recorded);
412
+ trajectory.push({
413
+ sequence: trajectory.length + 1,
414
+ kind: "verdict",
415
+ state_id: recorded.snapshot_id,
416
+ });
417
+ return result;
418
+ };
419
+ return {
420
+ observe: () => runWithAgentSessionContext(context, readObservation),
421
+ step: async (action) => {
422
+ if (closed)
423
+ throw new Error("episode is already closed.");
424
+ if (actions >= specification.limits.max_actions) {
425
+ throw new Error("episode exceeded limits.max_actions.");
426
+ }
427
+ actions += 1;
428
+ const result = await runWithAgentSessionContext(context, () => adapter.execute(action));
429
+ trajectory.push({
430
+ sequence: trajectory.length + 1,
431
+ kind: "action",
432
+ state_id: result.state_id,
433
+ });
434
+ return result;
435
+ },
436
+ score: () => runWithAgentSessionContext(context, evaluate),
437
+ close: () => {
438
+ if (closing !== undefined)
439
+ return closing;
440
+ if (closed)
441
+ return Promise.reject(new Error("episode is already closed."));
442
+ closed = true;
443
+ const operation = runWithAgentSessionContext(context, () => evaluate(true))
444
+ .then((verdict) => {
445
+ trajectory.push({
446
+ sequence: trajectory.length + 1,
447
+ kind: "close",
448
+ state_id: verdict.snapshot_id,
449
+ });
450
+ return {
451
+ verdict,
452
+ trajectory: frozenCopy(structuredClone(trajectory)),
453
+ };
454
+ })
455
+ .catch((error) => {
456
+ closed = false;
457
+ throw error;
458
+ });
459
+ closing = operation.finally(() => {
460
+ closing = undefined;
461
+ });
462
+ return closing;
463
+ },
464
+ };
465
+ }
466
+ //# sourceMappingURL=agent-environment.js.map
467
+ //# debugId=38cb0b88-262f-5061-b95a-f4532bc52613
@@ -1,11 +1,19 @@
1
1
  import { PmCliError } from "../../core/shared/errors.js";
2
2
  export { PmCliError };
3
+ /** First pm release whose history writer and reader support item-hash epoch 3. */
4
+ export declare const HISTORY_ITEM_HASH_VERSION_3_INTRODUCED_IN = "2026.8.31";
3
5
  /** One discovered project pin that can prove the active executable is stale. */
4
6
  export interface ProjectRuntimeVersionPin {
5
- /** Exact or minimum date-version extracted from the project declaration. */
7
+ /** Representative date-version extracted from the project declaration. */
6
8
  version: string;
7
9
  /** Redaction-safe origin of the pin. */
8
10
  source: "package.json" | "installed-package" | "package-lock.json" | "pnpm-lock.yaml" | "yarn.lock";
11
+ /** Whether the declaration selects one runtime, a minimum, or a bounded range. */
12
+ constraint: "exact" | "minimum" | "range";
13
+ /** Original package declaration when it conveys more than the representative version. */
14
+ declaration?: string;
15
+ /** Whether the declaration permits a reader that understands history item-hash epoch 3. */
16
+ history_epoch_compatible?: boolean;
9
17
  }
10
18
  /** Structured result returned by the read-only compatibility probe. */
11
19
  export interface ProjectRuntimeCompatibilityResult {
@@ -23,6 +31,14 @@ export interface ProjectRuntimeCompatibilityResult {
23
31
  mutating: boolean;
24
32
  /** Whether the active executable is older than the strongest project pin. */
25
33
  stale?: boolean;
34
+ /** Whether this mutation would write a history epoch a project pin cannot read. */
35
+ history_epoch_incompatible?: boolean;
36
+ /** Item-hash epoch the executing runtime writes. */
37
+ writer_item_hash_version?: 2 | 3;
38
+ /** Project runtime declaration that cannot read the writer epoch. */
39
+ incompatible_project_version?: string;
40
+ /** Origin of the incompatible project runtime declaration. */
41
+ incompatible_source?: ProjectRuntimeVersionPin["source"];
26
42
  /** Non-blocking diagnostic attached to stale read-only invocations. */
27
43
  warning?: ProjectRuntimeCompatibilityWarning;
28
44
  /** Whether the explicit one-invocation recovery override was accepted. */
@@ -45,6 +61,8 @@ export interface ProjectRuntimeCompatibilityWarning {
45
61
  }
46
62
  /** Compare pm date versions without accepting arbitrary semver-like prose. */
47
63
  export declare function comparePmDateVersions(left: string, right: string): number | null;
64
+ /** Resolve the item-hash epoch written by a known pm runtime release. */
65
+ export declare function historyItemHashVersionForRuntime(version: string): 2 | 3;
48
66
  /** Discover declared, installed, and lock-resolved versions without network I/O. */
49
67
  export declare function discoverProjectRuntimeVersionPins(projectRoot: string): ProjectRuntimeVersionPin[];
50
68
  /** Classify mutation-capable invocations while preserving explicit read modes. */