@sema-agent/core 5.60.0 → 5.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/dist/agents/subagent.d.ts +4 -2
- package/dist/agents/subagent.js +9 -9
- package/dist/core/checkpoint-store.d.ts +56 -6
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +2 -2
- package/dist/core/mcp.d.ts +4 -5
- package/dist/core/memory-engine/consolidation-driver.d.ts +19 -1
- package/dist/core/memory-engine/consolidation-driver.js +75 -3
- package/dist/core/memory-engine/consolidation.d.ts +52 -5
- package/dist/core/memory-engine/consolidation.js +3 -1
- package/dist/core/memory-engine/distiller.d.ts +89 -1
- package/dist/core/memory-engine/distiller.js +94 -5
- package/dist/core/memory-engine/engine.d.ts +13 -6
- package/dist/core/memory-engine/engine.js +51 -8
- package/dist/core/memory-engine/index.d.ts +1 -1
- package/dist/core/memory-engine/index.js +1 -1
- package/dist/core/runner/prepare-task.d.ts +6 -3
- package/dist/core/runner/prepare-task.js +1 -0
- package/dist/core/runner/runtask.js +113 -31
- package/dist/core/task-notification.d.ts +50 -23
- package/dist/core/task-notification.js +20 -4
- package/dist/core/tool-policy.d.ts +8 -2
- package/dist/core/types.d.ts +76 -19
- package/dist/engine/harness/agent-harness.d.ts +58 -2
- package/dist/engine/harness/agent-harness.js +115 -5
- package/dist/engine/loop/agent-loop.js +153 -15
- package/dist/engine/loop/types.d.ts +32 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/orchestration/run-workflow-tool.d.ts +7 -2
- package/dist/orchestration/run-workflow-tool.js +1 -1
- package/dist/tools/monitor.d.ts +3 -3
- package/dist/tools/monitor.js +1 -1
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +11 -1
|
@@ -265,6 +265,8 @@ function planShapeInvalid(raw, planId) {
|
|
|
265
265
|
if (freezeRefused !== undefined && (!Array.isArray(freezeRefused) || !freezeRefused.every((x) => typeof x === "string"))) {
|
|
266
266
|
return "malformed freezeRefusedInputIds";
|
|
267
267
|
}
|
|
268
|
+
if (p["mintExposure"] !== undefined && p["mintExposure"] !== "partitioned")
|
|
269
|
+
return "malformed mintExposure echo";
|
|
268
270
|
if ((p["v"] === 2) !== (freezeRefused !== undefined)) {
|
|
269
271
|
return p["v"] === 2 ? "v2 plan without its freezeRefusedInputIds member" : "freezeRefusedInputIds on a v1 plan";
|
|
270
272
|
}
|
|
@@ -360,4 +362,4 @@ export class ConsolidationRefusedError extends Error {
|
|
|
360
362
|
export function productAddPatch(product) {
|
|
361
363
|
return { op: "add", id: product.id, entry: product, guard: "absent" };
|
|
362
364
|
}
|
|
363
|
-
export const CONSOLIDATION_RUN_STOP_REASONS = ["converged", "fuse_residue", "max_cycles", "parked", "refused", "driver_failed"];
|
|
365
|
+
export const CONSOLIDATION_RUN_STOP_REASONS = ["converged", "fuse_residue", "max_cycles", "parked", "refused", "driver_failed", "mint_invalidated"];
|
|
@@ -64,6 +64,17 @@ export declare const LLM_DISTILLER_CONTRACTS: Record<string, LlmDistillerContrac
|
|
|
64
64
|
* archived). The shipping rule this alias exists to make auditable: a change to the product prompt
|
|
65
65
|
* is a NEW `version` (pd-2, …) and must produce rig readings BEFORE it ships — the contract-hash
|
|
66
66
|
* pin in the test suite reds on any silent byte drift under an unchanged version id.
|
|
67
|
+
*
|
|
68
|
+
* STANDING OBLIGATION on pd-2 (design/376-C1, ruled — mandatory, not optional): the next contract
|
|
69
|
+
* version MUST carry, as its FIRST A/B item, the root-elimination arm for the shared-grouping
|
|
70
|
+
* influence channel — the clean arm's writing pass without the grouping-authored `intent` (the
|
|
71
|
+
* template's own "(none stated)" arm) and without the `key` name fallback, which empties the one
|
|
72
|
+
* byte path an unattributed candidate has into a clean product and retires most of the D-1b guard
|
|
73
|
+
* machinery. The A/B must publish an EXPLICIT continuation verdict, one of three states — switch
|
|
74
|
+
* (root elimination ships) / hold (guard form stays, with readings) / redirect — it may not be
|
|
75
|
+
* left hanging. The exposure-partitioned topology itself deliberately did NOT bump this version
|
|
76
|
+
* (the candidate-set composition is topology, not wording; the wording axis stays single-variable
|
|
77
|
+
* for exactly this A/B).
|
|
67
78
|
*/
|
|
68
79
|
export declare const MEMORY_DISTILLER_CONTRACT_V1: LlmDistillerContract;
|
|
69
80
|
/**
|
|
@@ -223,6 +234,20 @@ export interface LlmConsolidationPlanProduct {
|
|
|
223
234
|
* alone, exactly as they always did (the archived-readings compatibility arm). */
|
|
224
235
|
inputRevs?: Record<string, string>;
|
|
225
236
|
}
|
|
237
|
+
/** design/376-C1 §2.2 — one arm's mint accounting inside an exposure-partitioned plan (usage
|
|
238
|
+
* "两臂合计+分列": the merged plan's top-level `minting` carries the totals, these rows carry the
|
|
239
|
+
* per-arm split PLUS each arm's verbatim grouping prompt — the clean arm's prompt bytes are the
|
|
240
|
+
* C1-G5 audit face: any reader can mechanically verify zero marked-entry bytes reached it). */
|
|
241
|
+
export interface LlmConsolidationPlanArm {
|
|
242
|
+
arm: "clean" | "marked";
|
|
243
|
+
candidateCount: number;
|
|
244
|
+
calls: number;
|
|
245
|
+
promptTokens: number;
|
|
246
|
+
completionTokens: number;
|
|
247
|
+
retries: number;
|
|
248
|
+
truncations: number;
|
|
249
|
+
groupingPrompt: string;
|
|
250
|
+
}
|
|
226
251
|
export interface LlmConsolidationPlan {
|
|
227
252
|
kind: "llm-consolidation-plan";
|
|
228
253
|
contractVersion: string;
|
|
@@ -233,6 +258,27 @@ export interface LlmConsolidationPlan {
|
|
|
233
258
|
maxInputsPerProduct: number;
|
|
234
259
|
candidateCount: number;
|
|
235
260
|
products: LlmConsolidationPlanProduct[];
|
|
261
|
+
/** design/376-C1 D-2 — present ⇔ this plan was minted by {@link mintExposurePartitionedPlan}
|
|
262
|
+
* (clean/marked candidates partitioned into physically separate call chains). The DRIVE threads
|
|
263
|
+
* this onto the committed proposal, where it is the engine's per-product-fold attestation.
|
|
264
|
+
* Absent on every plan minted before the field existed — those replay UN-attested (run-level
|
|
265
|
+
* fold, the safe default), which is exactly right for prompts composed over the whole library. */
|
|
266
|
+
mintExposure?: "partitioned";
|
|
267
|
+
/** design/376-C1 D-1b — the mint's served-world roster: every candidate actually RENDERED into
|
|
268
|
+
* some arm's grouping call, with the rev and marked flag it was served at. The driver's
|
|
269
|
+
* cross-cycle invalidation predicate compares each row against the fresh snapshot
|
|
270
|
+
* (offered ∧ same rev ∧ same marked, or departed-with-own-fold-evidence) — any other state
|
|
271
|
+
* means the world the prompts were composed over no longer stands. A single withheld marked
|
|
272
|
+
* row (markedCount = 1, no marked arm) is deliberately NOT in the roster: no call ever saw it. */
|
|
273
|
+
servedCandidates?: Array<{
|
|
274
|
+
id: string;
|
|
275
|
+
rev: string;
|
|
276
|
+
marked: boolean;
|
|
277
|
+
}>;
|
|
278
|
+
/** design/376-C1 A-5 disclosure — how many marked candidates were withheld from the CLEAN arm's
|
|
279
|
+
* prompts (= the marked candidate count when the partition engaged). Present ⇔ partitioned
|
|
280
|
+
* mint over a library with at least one marked row. */
|
|
281
|
+
withheldFromCleanArm?: number;
|
|
236
282
|
minting: {
|
|
237
283
|
calls: number;
|
|
238
284
|
groupingAttempts: number;
|
|
@@ -264,6 +310,10 @@ export interface LlmConsolidationPlan {
|
|
|
264
310
|
model: string | null;
|
|
265
311
|
answer: string;
|
|
266
312
|
}>;
|
|
313
|
+
/** Per-arm split of a partitioned mint (see {@link LlmConsolidationPlanArm}); absent on
|
|
314
|
+
* single-call-chain plans, INCLUDING the zero-marked degenerate of the partitioned mint
|
|
315
|
+
* (whose bytes must stay identical to the plain mint's, modulo the attestation echo). */
|
|
316
|
+
arms?: LlmConsolidationPlanArm[];
|
|
267
317
|
};
|
|
268
318
|
}
|
|
269
319
|
export type MintLlmConsolidationPlanResult = {
|
|
@@ -280,7 +330,7 @@ export type MintLlmConsolidationPlanResult = {
|
|
|
280
330
|
* it answered is returned inside the plan so the run can be re-read, re-scored and disputed from
|
|
281
331
|
* the archive alone.
|
|
282
332
|
*/
|
|
283
|
-
export declare function mintLlmConsolidationPlan({ candidates, chat, model, baseUrl, maxInputsPerProduct, contract, onProgress, }: {
|
|
333
|
+
export declare function mintLlmConsolidationPlan({ candidates, chat, model, baseUrl, maxInputsPerProduct, contract, onProgress, armLabel, }: {
|
|
284
334
|
candidates: readonly DistillerCandidate[];
|
|
285
335
|
chat: DistillerChatFn;
|
|
286
336
|
/** The requested model id, recorded into the plan (audit anchor). Alias spellings refuse. */
|
|
@@ -298,7 +348,44 @@ export declare function mintLlmConsolidationPlan({ candidates, chat, model, base
|
|
|
298
348
|
done: number;
|
|
299
349
|
of: number;
|
|
300
350
|
}) => void) | null;
|
|
351
|
+
/** design/376-C1 — the partitioned mint's TRANSCRIPT arm tag (`grouping.clean#1` /
|
|
352
|
+
* `writing.marked#1:key#1` label forms). Prompt bytes are untouched — this names calls in the
|
|
353
|
+
* archive only, and stays compatible with `planParseRepairs`' `startsWith("grouping")` phase
|
|
354
|
+
* split and its `#N` retry grammar. Absent ⇒ the historical label forms, byte-identical. */
|
|
355
|
+
armLabel?: "clean" | "marked";
|
|
301
356
|
}): Promise<MintLlmConsolidationPlanResult>;
|
|
357
|
+
/** ONE rev sentinel for every face that RECORDS or RE-CHECKS a candidate's rev (the mint roster
|
|
358
|
+
* below, the driver's cross-cycle roster re-check, the replay's offered map). The snapshot
|
|
359
|
+
* interface is structural — a host-supplied engine can hand entries whose rev is not a string at
|
|
360
|
+
* runtime — and the recording and re-checking sides MUST agree on the fallback, or a rev-less
|
|
361
|
+
* candidate compares unequal to its own roster row and self-invalidates the mint it was just
|
|
362
|
+
* served into (a paid mint discarded on its own cycle, re-minted and re-discarded forever). */
|
|
363
|
+
export declare const normalizedCandidateRev: (rev: unknown) => string;
|
|
364
|
+
/**
|
|
365
|
+
* design/376-C1 §2.2 — mint an EXPOSURE-PARTITIONED fold plan: candidates are bisected on the
|
|
366
|
+
* snapshot's per-candidate `marked` flag and each subset is minted through its own physically
|
|
367
|
+
* separate call chain, so no clean product's authoring chain (grouping intent included) is ever
|
|
368
|
+
* served a marked entry's bytes. This is what makes the engine's per-product fold SOUND — the
|
|
369
|
+
* attestation the plan carries (`mintExposure: "partitioned"`) is the freeze's gate, and this
|
|
370
|
+
* function is the single source the product driver and every bench adapter share.
|
|
371
|
+
*
|
|
372
|
+
* - CLEAN arm: `mintLlmConsolidationPlan` over the clean subset — contract, templates, retries,
|
|
373
|
+
* sampling all byte-identical to the plain mint (dl-3 恒等; only the candidate-set composition
|
|
374
|
+
* moves, which is topology, not wording — no prompt-contract version bump, D-10/A-10).
|
|
375
|
+
* - MARKED arm: the same contract over the marked subset, minted only at `markedCount ≥ 2`
|
|
376
|
+
* (design C1-B): a single marked row has no group to fold and simply stays where it is — an
|
|
377
|
+
* index handle row loses no discoverability by standing still. Withheld rows are disclosed
|
|
378
|
+
* (`withheldFromCleanArm`) and kept OUT of the served roster (no call saw them).
|
|
379
|
+
* - ZERO-marked degenerate: exactly ONE arm over exactly the full set with the HISTORICAL
|
|
380
|
+
* transcript labels — byte-identical to the plain mint modulo the attestation echo and the
|
|
381
|
+
* roster (C1-G1's identity claim).
|
|
382
|
+
* - Either arm failing fails the WHOLE mint (one mint, one verdict; both arms' calls ride the
|
|
383
|
+
* failure transcript). This widens nothing: under the run-level mint a hostile marked row
|
|
384
|
+
* already sat inside the single grouping call and could fail it whole.
|
|
385
|
+
* - Cross-arm group structure is impossible by construction: each arm's `sanitizeLlmGroups`
|
|
386
|
+
* valid map is built over that arm's own 1..N numbering.
|
|
387
|
+
*/
|
|
388
|
+
export declare function mintExposurePartitionedPlan(args: Parameters<typeof mintLlmConsolidationPlan>[0]): Promise<MintLlmConsolidationPlanResult>;
|
|
302
389
|
/** The distiller signature the drive consumes: `(candidates, snapshot, scope) -> proposal`.
|
|
303
390
|
* At runtime only `entry.id` is load-bearing for the replay filter — test/bench stubs that hand
|
|
304
391
|
* partial entry shapes are tolerated by the reads, the type states the engine's real snapshot.
|
|
@@ -321,6 +408,7 @@ export declare function llmPlanDistiller(plan: {
|
|
|
321
408
|
products?: ReadonlyArray<Pick<LlmConsolidationPlanProduct, "name" | "description" | "type" | "body" | "inputIds"> & {
|
|
322
409
|
inputRevs?: Record<string, string>;
|
|
323
410
|
}>;
|
|
411
|
+
mintExposure?: "partitioned";
|
|
324
412
|
} | undefined | null): ConsolidationDistillFn;
|
|
325
413
|
export interface FuseSchedule<P> {
|
|
326
414
|
schedule: "next-fit-decreasing";
|
|
@@ -252,12 +252,15 @@ export function sanitizeLlmGroups(groups, { candidateIds, maxInputsPerProduct =
|
|
|
252
252
|
}
|
|
253
253
|
return { groups: clean, repairs, claimedIds: [...claimed] };
|
|
254
254
|
}
|
|
255
|
-
|
|
255
|
+
function assertMintConfig(chat, model) {
|
|
256
256
|
if (typeof chat !== "function")
|
|
257
257
|
throw distillerConfigRefusal("mintLlmConsolidationPlan needs a chat function (the explicit model seat) — a distiller that silently skips would publish an empty plan as a model's answer.");
|
|
258
258
|
if (typeof model !== "string" || model.trim() === "" || isAliasModelId(model)) {
|
|
259
259
|
throw distillerConfigRefusal(`mintLlmConsolidationPlan needs an EXPLICIT model id, got ${JSON.stringify(model)} — an alias can be re-pointed upstream and would move the run with no trace in the plan.`);
|
|
260
260
|
}
|
|
261
|
+
}
|
|
262
|
+
export async function mintLlmConsolidationPlan({ candidates, chat, model, baseUrl, maxInputsPerProduct = DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, contract = MEMORY_DISTILLER_CONTRACT_V1, onProgress = null, armLabel, }) {
|
|
263
|
+
assertMintConfig(chat, model);
|
|
261
264
|
const rows = candidates.map((c, i) => ({ n: i + 1, id: c?.entry?.id, rev: c?.entry?.rev, text: renderCandidate(i + 1, c) }));
|
|
262
265
|
const missingId = rows.filter((r) => typeof r.id !== "string");
|
|
263
266
|
if (missingId.length > 0)
|
|
@@ -277,7 +280,7 @@ export async function mintLlmConsolidationPlan({ candidates, chat, model, baseUr
|
|
|
277
280
|
for (let attempt = 0; attempt <= contract.retries && groups === null; attempt++) {
|
|
278
281
|
groupingAttempts += 1;
|
|
279
282
|
const user = attempt === 0 ? groupingUser : `${groupingUser}\n\nYour previous answer could not be read: ${lastError}. Answer with one JSON object and nothing else.`;
|
|
280
|
-
const out = await record(`grouping#${attempt + 1}`, () => chat({ system: contract.grouping.system, user, maxTokens: contract.sampling.maxTokens.grouping, temperature: contract.sampling.temperature }));
|
|
283
|
+
const out = await record(`grouping${armLabel !== undefined ? `.${armLabel}` : ""}#${attempt + 1}`, () => chat({ system: contract.grouping.system, user, maxTokens: contract.sampling.maxTokens.grouping, temperature: contract.sampling.temperature }));
|
|
281
284
|
if (out.finishReason === "length") {
|
|
282
285
|
lastError = "the answer was cut off at the token limit";
|
|
283
286
|
continue;
|
|
@@ -312,7 +315,7 @@ export async function mintLlmConsolidationPlan({ candidates, chat, model, baseUr
|
|
|
312
315
|
let err = null;
|
|
313
316
|
for (let attempt = 0; attempt <= contract.retries && written === null; attempt++) {
|
|
314
317
|
const u = attempt === 0 ? user : `${user}\n\nYour previous answer could not be read: ${err}. Answer with one JSON object and nothing else.`;
|
|
315
|
-
const out = await record(`writing#${i + 1}:${g.key}#${attempt + 1}`, () => chat({ system: contract.writing.system, user: u, maxTokens: contract.sampling.maxTokens.writing, temperature: contract.sampling.temperature }));
|
|
318
|
+
const out = await record(`writing${armLabel !== undefined ? `.${armLabel}` : ""}#${i + 1}:${g.key}#${attempt + 1}`, () => chat({ system: contract.writing.system, user: u, maxTokens: contract.sampling.maxTokens.writing, temperature: contract.sampling.temperature }));
|
|
316
319
|
if (out.finishReason === "length") {
|
|
317
320
|
err = "the answer was cut off at the token limit";
|
|
318
321
|
continue;
|
|
@@ -372,17 +375,103 @@ export async function mintLlmConsolidationPlan({ candidates, chat, model, baseUr
|
|
|
372
375
|
},
|
|
373
376
|
};
|
|
374
377
|
}
|
|
378
|
+
export const normalizedCandidateRev = (rev) => (typeof rev === "string" ? rev : "");
|
|
379
|
+
const rosterOf = (list) => list.map((c) => ({ id: c.entry.id, rev: normalizedCandidateRev(c.entry.rev), marked: c.marked === true }));
|
|
380
|
+
const armUsageOf = (arm, plan, candidateCount) => ({
|
|
381
|
+
arm,
|
|
382
|
+
candidateCount,
|
|
383
|
+
calls: plan.minting.calls,
|
|
384
|
+
promptTokens: plan.minting.usage.promptTokens,
|
|
385
|
+
completionTokens: plan.minting.usage.completionTokens,
|
|
386
|
+
retries: plan.minting.parseRepairs.retries,
|
|
387
|
+
truncations: plan.minting.parseRepairs.lengthCapped.length,
|
|
388
|
+
groupingPrompt: plan.minting.prompts.grouping,
|
|
389
|
+
});
|
|
390
|
+
export async function mintExposurePartitionedPlan(args) {
|
|
391
|
+
assertMintConfig(args.chat, args.model);
|
|
392
|
+
const { candidates, ...rest } = args;
|
|
393
|
+
const clean = (candidates ?? []).filter((c) => c?.marked !== true);
|
|
394
|
+
const marked = (candidates ?? []).filter((c) => c?.marked === true);
|
|
395
|
+
if (marked.length === 0) {
|
|
396
|
+
const res = await mintLlmConsolidationPlan({ ...rest, candidates: clean });
|
|
397
|
+
if (!res.ok)
|
|
398
|
+
return res;
|
|
399
|
+
return { ok: true, plan: { ...res.plan, mintExposure: "partitioned", servedCandidates: rosterOf(clean) } };
|
|
400
|
+
}
|
|
401
|
+
const cleanRes = clean.length > 0 ? await mintLlmConsolidationPlan({ ...rest, candidates: clean, armLabel: "clean" }) : undefined;
|
|
402
|
+
if (cleanRes !== undefined && !cleanRes.ok) {
|
|
403
|
+
return { ok: false, reason: `clean arm: ${cleanRes.reason}`, calls: cleanRes.calls };
|
|
404
|
+
}
|
|
405
|
+
const markedRes = marked.length >= 2 ? await mintLlmConsolidationPlan({ ...rest, candidates: marked, armLabel: "marked" }) : undefined;
|
|
406
|
+
if (markedRes !== undefined && !markedRes.ok) {
|
|
407
|
+
return { ok: false, reason: `marked arm: ${markedRes.reason}`, calls: [...(cleanRes?.plan.minting.transcript ?? []), ...markedRes.calls] };
|
|
408
|
+
}
|
|
409
|
+
const arms = [
|
|
410
|
+
...(cleanRes !== undefined ? [{ res: cleanRes.plan, tag: "clean", count: clean.length }] : []),
|
|
411
|
+
...(markedRes !== undefined ? [{ res: markedRes.plan, tag: "marked", count: marked.length }] : []),
|
|
412
|
+
];
|
|
413
|
+
const served = [...(cleanRes !== undefined ? clean : []), ...(markedRes !== undefined ? marked : [])];
|
|
414
|
+
const transcript = arms.flatMap((a) => a.res.minting.transcript);
|
|
415
|
+
const sumRepairs = (k) => arms.reduce((n, a) => n + a.res.minting.repairs[k], 0);
|
|
416
|
+
const anchor = arms[0]?.res;
|
|
417
|
+
return {
|
|
418
|
+
ok: true,
|
|
419
|
+
plan: {
|
|
420
|
+
kind: "llm-consolidation-plan",
|
|
421
|
+
contractVersion: anchor?.contractVersion ?? (rest.contract ?? MEMORY_DISTILLER_CONTRACT_V1).version,
|
|
422
|
+
model: rest.model,
|
|
423
|
+
...(rest.baseUrl !== undefined ? { baseUrl: rest.baseUrl } : {}),
|
|
424
|
+
mintedAt: anchor?.mintedAt ?? new Date().toISOString(),
|
|
425
|
+
maxInputsPerProduct: anchor?.maxInputsPerProduct ?? rest.maxInputsPerProduct ?? DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT,
|
|
426
|
+
candidateCount: (candidates ?? []).length,
|
|
427
|
+
products: arms.flatMap((a) => a.res.products),
|
|
428
|
+
mintExposure: "partitioned",
|
|
429
|
+
servedCandidates: rosterOf(served),
|
|
430
|
+
withheldFromCleanArm: marked.length,
|
|
431
|
+
minting: {
|
|
432
|
+
calls: transcript.length,
|
|
433
|
+
groupingAttempts: arms.reduce((n, a) => n + a.res.minting.groupingAttempts, 0),
|
|
434
|
+
groupingParseRepairs: arms.reduce((n, a) => n + a.res.minting.groupingParseRepairs, 0),
|
|
435
|
+
parseRepairs: planParseRepairs({ minting: { transcript } }),
|
|
436
|
+
repairs: {
|
|
437
|
+
hallucinatedMembers: sumRepairs("hallucinatedMembers"),
|
|
438
|
+
duplicateMembers: sumRepairs("duplicateMembers"),
|
|
439
|
+
emptyGroups: sumRepairs("emptyGroups"),
|
|
440
|
+
oversizeGroups: arms.flatMap((a) => a.res.minting.repairs.oversizeGroups),
|
|
441
|
+
nonIntegerMembers: sumRepairs("nonIntegerMembers"),
|
|
442
|
+
},
|
|
443
|
+
writeFailures: arms.flatMap((a) => a.res.minting.writeFailures),
|
|
444
|
+
usage: {
|
|
445
|
+
promptTokens: arms.reduce((n, a) => n + a.res.minting.usage.promptTokens, 0),
|
|
446
|
+
completionTokens: arms.reduce((n, a) => n + a.res.minting.usage.completionTokens, 0),
|
|
447
|
+
},
|
|
448
|
+
prompts: {
|
|
449
|
+
grouping: anchor?.minting.prompts.grouping ?? "",
|
|
450
|
+
writingTemplate: (rest.contract ?? MEMORY_DISTILLER_CONTRACT_V1).writing.instructions,
|
|
451
|
+
systems: {
|
|
452
|
+
grouping: (rest.contract ?? MEMORY_DISTILLER_CONTRACT_V1).grouping.system,
|
|
453
|
+
writing: (rest.contract ?? MEMORY_DISTILLER_CONTRACT_V1).writing.system,
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
transcript,
|
|
457
|
+
...(arms.length > 0 ? { arms: arms.map((a) => armUsageOf(a.tag, a.res, a.count)) } : {}),
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
}
|
|
375
462
|
export function llmPlanDistiller(plan) {
|
|
376
463
|
const products = plan?.products ?? [];
|
|
464
|
+
const mintExposure = plan?.mintExposure === "partitioned" ? "partitioned" : undefined;
|
|
377
465
|
return (candidates, _snapshot, scope) => {
|
|
378
466
|
const offered = new Map();
|
|
379
467
|
for (const x of (candidates ?? [])) {
|
|
380
468
|
const id = x?.entry?.id ?? x?.id;
|
|
381
469
|
if (typeof id === "string")
|
|
382
|
-
offered.set(id,
|
|
470
|
+
offered.set(id, normalizedCandidateRev(x?.entry?.rev));
|
|
383
471
|
}
|
|
384
472
|
return {
|
|
385
473
|
scope,
|
|
474
|
+
...(mintExposure !== undefined ? { mintExposure } : {}),
|
|
386
475
|
products: products.map((p) => {
|
|
387
476
|
const ids = p.inputIds ?? [];
|
|
388
477
|
const live = ids.filter((id) => offered.has(id));
|
|
@@ -527,7 +616,7 @@ export async function driveConsolidationToFixpoint(engine, scope, opts) {
|
|
|
527
616
|
let receipt;
|
|
528
617
|
let commitError = null;
|
|
529
618
|
try {
|
|
530
|
-
receipt = await engine.commitConsolidationPlan(snapshot.cycleToken, { scope, products: plan.picked }, { requestId: `${requestIdPrefix}-c${cycle}` });
|
|
619
|
+
receipt = await engine.commitConsolidationPlan(snapshot.cycleToken, { scope, products: plan.picked, ...(proposal.mintExposure !== undefined ? { mintExposure: proposal.mintExposure } : {}) }, { requestId: `${requestIdPrefix}-c${cycle}` });
|
|
531
620
|
}
|
|
532
621
|
catch (err) {
|
|
533
622
|
commitError = err;
|
|
@@ -406,12 +406,11 @@ export declare function memorySessionPollutedNotice(input: {
|
|
|
406
406
|
* arithmetic would report a clean containment while a known failure sat in the report. Every
|
|
407
407
|
* not-moved file does have an escalation row, so `escalated` is the superset and the honest clause.
|
|
408
408
|
*
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
413
|
-
*
|
|
414
|
-
* The wording therefore says "entry file(s)" rather than implying the whole containment.
|
|
409
|
+
* The derived index is contained by a different path (`restorePollutedIndex` captures and restores
|
|
410
|
+
* `MEMORY.md`) that mints no rejection row; that gap was CLOSED by the structured containment signal
|
|
411
|
+
* on `HarvestReport.containment` (indexRolledBack — see the containment block below), so an
|
|
412
|
+
* index-only polluted session now signals and notices instead of hiding in warnings prose. The
|
|
413
|
+
* wording says "entry file(s)" because index containment reports through its own signal.
|
|
415
414
|
*
|
|
416
415
|
* `reason` is optional because the mark record is read separately from the report: a record that cannot
|
|
417
416
|
* be read must not turn a true "N files were withheld" into a lie about why.
|
|
@@ -476,6 +475,10 @@ export interface ConsolidationCommitReceipt {
|
|
|
476
475
|
settled: string[];
|
|
477
476
|
pending: string[];
|
|
478
477
|
};
|
|
478
|
+
/** True ⇔ the RUN-LEVEL single-value fold applied to this plan (design/376-C1 D-6: an
|
|
479
|
+
* un-attested freeze over a marked served set). An attested (exposure-partitioned) plan
|
|
480
|
+
* answers false here even when some of its products carry origin — per-product outcomes live
|
|
481
|
+
* in the plan summary's `markedProducts` and in each product's own committed bytes. */
|
|
479
482
|
foldedOrigin: boolean;
|
|
480
483
|
notices: EngineNotice[];
|
|
481
484
|
}
|
|
@@ -511,6 +514,10 @@ export interface ConsolidationPlanSummary {
|
|
|
511
514
|
state?: ConsolidationPlanFile["state"];
|
|
512
515
|
createdAt?: number;
|
|
513
516
|
products?: number;
|
|
517
|
+
/** design/376-C1 D-6 (additive) — how many of the plan's frozen products carry an origin marker
|
|
518
|
+
* in their own bytes. Counts BOTH fold forms truthfully: a run-level plan's single value rides
|
|
519
|
+
* every product, an attested plan marks exactly the products whose declared inputs were marked. */
|
|
520
|
+
markedProducts?: number;
|
|
514
521
|
directed?: number;
|
|
515
522
|
intents?: number;
|
|
516
523
|
}
|
|
@@ -2596,7 +2596,7 @@ export class MemoryEngine {
|
|
|
2596
2596
|
if (fresh.epoch !== epoch) {
|
|
2597
2597
|
throw new ConsolidationRefusedError("memory.consolidation_stale_snapshot", `memory consolidation refused: the ${JSON.stringify(scope)} watermark advanced during the snapshot read — retake the snapshot.`);
|
|
2598
2598
|
}
|
|
2599
|
-
fresh.snapshot = { token: cycleToken, at, epoch, candidates: candidateRevs, eligible: eligibleRevs, marked };
|
|
2599
|
+
fresh.snapshot = { token: cycleToken, at, epoch, candidates: candidateRevs, eligible: eligibleRevs, marked, markedIds: candidates.filter((c) => c.marked).map((c) => c.entry.id) };
|
|
2600
2600
|
let announce = false;
|
|
2601
2601
|
if (opts.force !== undefined && fresh.lastForcedRequestId !== opts.force.requestId) {
|
|
2602
2602
|
fresh.lastForcedRequestId = opts.force.requestId;
|
|
@@ -2656,6 +2656,9 @@ export class MemoryEngine {
|
|
|
2656
2656
|
else if (p.inputs.length > screened.maxInputsPerProduct)
|
|
2657
2657
|
reasons.push(`product #${i}: ${p.inputs.length} inputs > maxInputsPerProduct ${screened.maxInputsPerProduct}`);
|
|
2658
2658
|
}
|
|
2659
|
+
if (proposal.mintExposure !== undefined && proposal.mintExposure !== "partitioned") {
|
|
2660
|
+
reasons.push(`mintExposure ${JSON.stringify(proposal.mintExposure)} is not a member of the closed attestation set {"partitioned"} — omit it (run-level fold) or spell it exactly`);
|
|
2661
|
+
}
|
|
2659
2662
|
if (proposal.products.length === 0 && intents.length === 0) {
|
|
2660
2663
|
reasons.push("empty plan: zero products and zero directed intents — a plan with nothing to apply must not settle as a completed run (the vacuous completion would stamp the eligible set into the fingerprint and blind the incremental face)");
|
|
2661
2664
|
}
|
|
@@ -2741,19 +2744,43 @@ export class MemoryEngine {
|
|
|
2741
2744
|
}
|
|
2742
2745
|
const activeSetSize = headers.filter((h) => !superseded.has(h.id) && !exclusions.has(h.id)).length;
|
|
2743
2746
|
const ceiling = supersessionFuseCeiling(activeSetSize, screened);
|
|
2747
|
+
const attested = proposal.mintExposure === "partitioned";
|
|
2748
|
+
if (attested) {
|
|
2749
|
+
const staleAxes = [];
|
|
2750
|
+
const baseline = snapshot.markedIds !== undefined ? new Set(snapshot.markedIds) : undefined;
|
|
2751
|
+
for (const [id, rev] of Object.entries(snapshot.candidates)) {
|
|
2752
|
+
const h = headerById.get(id);
|
|
2753
|
+
if (h === undefined)
|
|
2754
|
+
staleAxes.push(`served candidate ${id} left the committed listing`);
|
|
2755
|
+
else if (h.rev !== rev)
|
|
2756
|
+
staleAxes.push(`served candidate ${id} moved rev since the snapshot`);
|
|
2757
|
+
else if (exclusions.has(id))
|
|
2758
|
+
staleAxes.push(`served candidate ${id} entered the challenge/latch exclusion set`);
|
|
2759
|
+
else if (h.exposure !== undefined && (baseline === undefined || !baseline.has(id))) {
|
|
2760
|
+
staleAxes.push(baseline === undefined
|
|
2761
|
+
? `served candidate ${id} is marked and the snapshot row predates the markedIds baseline — no proof the mark predates the mint (conservative arm)`
|
|
2762
|
+
: `served candidate ${id} turned marked since the snapshot`);
|
|
2763
|
+
}
|
|
2764
|
+
}
|
|
2765
|
+
if (staleAxes.length > 0) {
|
|
2766
|
+
throw new ConsolidationRefusedError("memory.consolidation_stale_snapshot", `memory consolidation refused: the attested (exposure-partitioned) mint's clean-arm premise is stale — the ${JSON.stringify(scope)} world moved between snapshot and freeze; retake the snapshot and re-mint`, staleAxes);
|
|
2767
|
+
}
|
|
2768
|
+
}
|
|
2744
2769
|
let visibleMarked = snapshot.marked;
|
|
2745
|
-
if (!visibleMarked) {
|
|
2770
|
+
if (!attested && !visibleMarked) {
|
|
2746
2771
|
const inputIds = [...new Set(proposal.products.flatMap((p) => p.inputs.map((i2) => i2.id)))];
|
|
2747
2772
|
const committedInputs = await face.getByIds(inputIds);
|
|
2748
2773
|
visibleMarked = committedInputs.some((e) => committedOriginOf(e.frontmatter) !== undefined);
|
|
2749
2774
|
}
|
|
2750
|
-
const foldedOrigin = visibleMarked ? { taint: "external", cause: "derived", at } : undefined;
|
|
2775
|
+
const foldedOrigin = !attested && visibleMarked ? { taint: "external", cause: "derived", at } : undefined;
|
|
2776
|
+
const productMarked = (p) => p.inputs.some((i2) => headerById.get(i2.id)?.exposure !== undefined);
|
|
2751
2777
|
for (let i = 0; i < proposal.products.length; i++) {
|
|
2752
2778
|
const p = proposal.products[i];
|
|
2753
2779
|
if (!isInstructionEntry({ ...(p.type !== undefined ? { type: p.type } : {}) }))
|
|
2754
2780
|
continue;
|
|
2755
|
-
|
|
2756
|
-
|
|
2781
|
+
const hardArm = attested ? productMarked(p) : visibleMarked;
|
|
2782
|
+
if (hardArm) {
|
|
2783
|
+
wholeReasons.push(`product #${i}: instruction-form product (type ${JSON.stringify(p.type)}) refused — ${attested ? "the product's declared inputs contain marked content" : "the run's visible set contains marked content"} (the laundering hard gate, G7/G26; no option opens this arm)`);
|
|
2757
2784
|
}
|
|
2758
2785
|
else if (!screened.allowInstructionProducts) {
|
|
2759
2786
|
wholeReasons.push(`product #${i}: instruction-form product (type ${JSON.stringify(p.type)}) refused — instruction-form consolidation products are refused unconditionally by default (a consolidation must not mint privileged entries out of ordinary notes); the host escape hatch is consolidation.allowInstructionProducts: true`);
|
|
@@ -2784,6 +2811,7 @@ export class MemoryEngine {
|
|
|
2784
2811
|
}
|
|
2785
2812
|
const id = uuidv7();
|
|
2786
2813
|
const slug = deriveProductSlug(p.name, `consolidated-${id.slice(0, 8)}`);
|
|
2814
|
+
const productOrigin = attested ? (productMarked(p) ? { taint: "external", cause: "derived", at } : undefined) : foldedOrigin;
|
|
2787
2815
|
const entry = {
|
|
2788
2816
|
id,
|
|
2789
2817
|
slug,
|
|
@@ -2792,7 +2820,7 @@ export class MemoryEngine {
|
|
|
2792
2820
|
name: p.name ?? slug,
|
|
2793
2821
|
...(p.description !== undefined ? { description: p.description } : {}),
|
|
2794
2822
|
...(p.type !== undefined ? { type: p.type } : {}),
|
|
2795
|
-
...(
|
|
2823
|
+
...(productOrigin !== undefined ? { origin: { ...productOrigin } } : {}),
|
|
2796
2824
|
distilled: {
|
|
2797
2825
|
planId,
|
|
2798
2826
|
at,
|
|
@@ -2917,6 +2945,7 @@ export class MemoryEngine {
|
|
|
2917
2945
|
epoch: snapshot.epoch,
|
|
2918
2946
|
visibleMarked,
|
|
2919
2947
|
...(foldedOrigin !== undefined ? { foldedOrigin } : {}),
|
|
2948
|
+
...(attested ? { mintExposure: "partitioned" } : {}),
|
|
2920
2949
|
products: assembled,
|
|
2921
2950
|
productStates: Object.fromEntries(assembled.map((e) => [e.id, "pending"])),
|
|
2922
2951
|
...(freezeRefusedInputIds.length > 0 ? { freezeRefusedInputIds } : {}),
|
|
@@ -2924,7 +2953,21 @@ export class MemoryEngine {
|
|
|
2924
2953
|
intents: intents.map((it) => ({ requestId: it.requestId, state: "pending" })),
|
|
2925
2954
|
state: "open",
|
|
2926
2955
|
audit: [
|
|
2927
|
-
{
|
|
2956
|
+
{
|
|
2957
|
+
at,
|
|
2958
|
+
event: "frozen",
|
|
2959
|
+
requestId: input.requestId,
|
|
2960
|
+
...((() => {
|
|
2961
|
+
const parts = [];
|
|
2962
|
+
if (attested) {
|
|
2963
|
+
const markedProducts = assembled.filter((e) => e.frontmatter.origin !== undefined).length;
|
|
2964
|
+
parts.push(`attested exposure-partitioned mint: ${markedProducts} marked / ${assembled.length - markedProducts} clean product(s)`);
|
|
2965
|
+
}
|
|
2966
|
+
if (refusedProducts.length > 0)
|
|
2967
|
+
parts.push(`${refusedProducts.length} product(s) refused at freeze: ${refusedProducts.map((r) => `#${r.index}: ${r.reason}`).join(" | ")}`);
|
|
2968
|
+
return parts.length > 0 ? { detail: parts.join("; ") } : {};
|
|
2969
|
+
})()),
|
|
2970
|
+
},
|
|
2928
2971
|
],
|
|
2929
2972
|
};
|
|
2930
2973
|
writeConsolidationPlan(this.controlDir, plan);
|
|
@@ -3492,7 +3535,7 @@ export class MemoryEngine {
|
|
|
3492
3535
|
if (read.state === "absent")
|
|
3493
3536
|
continue;
|
|
3494
3537
|
const p = read.plan;
|
|
3495
|
-
out.push({ planId, scope: p.scope, state: p.state, createdAt: p.createdAt, products: p.products.length, directed: p.directed.length, intents: p.intents.length });
|
|
3538
|
+
out.push({ planId, scope: p.scope, state: p.state, createdAt: p.createdAt, products: p.products.length, markedProducts: p.products.filter((e) => e.frontmatter?.origin !== undefined).length, directed: p.directed.length, intents: p.intents.length });
|
|
3496
3539
|
}
|
|
3497
3540
|
return out;
|
|
3498
3541
|
}
|
|
@@ -9,7 +9,7 @@ export { readV2HeaderHints, isInstructionEntry, type V2HeaderHints } from "./hea
|
|
|
9
9
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, committedOriginOf, originEquals, ambiguousOriginRepresentation, type ParsedEntryFile } from "./frontmatter.js";
|
|
10
10
|
export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
11
11
|
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, type ConsolidationGateRead, type ConsolidationGateRow, type ConsolidationIntent, type ConsolidationIntentCredentialRow, type ConsolidationLeaseSeat, type ConsolidationProductProposal, type ConsolidationProposal, type MemoryConsolidationOptions, CONSOLIDATION_RUN_STOP_REASONS, type ConsolidationRunStopReason, } from "./consolidation.js";
|
|
12
|
-
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, } from "./distiller.js";
|
|
12
|
+
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, type ConsolidationDistillFn, type ConsolidationDriveCycleRow, type ConsolidationDriveEngine, type ConsolidationDriveResult, type ConsolidationFoldState, type DistillerCandidate, type DistillerChatAnswer, type DistillerChatFn, type DistillerChatRequest, type FuseSchedule, type LlmConsolidationPlan, type LlmConsolidationPlanArm, type LlmConsolidationPlanProduct, type LlmDistillerContract, type MintLlmConsolidationPlanResult, type PlanParseRepairs, type SanitizedLlmGroups, } from "./distiller.js";
|
|
13
13
|
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, type ConsolidationDriverEngine, type ConsolidationDriverRunRow, type ConsolidationRunReceipt, type RunMemoryConsolidationOptions, } from "./consolidation-driver.js";
|
|
14
14
|
export type { OriginClearanceRow, OriginClearanceEvent } from "./origin-clearance.js";
|
|
15
15
|
export { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
@@ -9,7 +9,7 @@ export { readV2HeaderHints, isInstructionEntry } from "./header-hints.js";
|
|
|
9
9
|
export { parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, committedOriginOf, originEquals, ambiguousOriginRepresentation } from "./frontmatter.js";
|
|
10
10
|
export { committedDistilledOf, distilledEquals } from "./frontmatter.js";
|
|
11
11
|
export { CONSOLIDATION_DEFAULTS, ConsolidationRefusedError, MEMORY_SEARCH_SUPERSEDED_TAG, consolidationTypeEligible, deriveSupersededSet, memorySupersededNote, readIntentCredentials, supersessionFuseCeiling, CONSOLIDATION_RUN_STOP_REASONS, } from "./consolidation.js";
|
|
12
|
-
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, } from "./distiller.js";
|
|
12
|
+
export { DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT, LLM_DISTILLER_CONTRACT, LLM_DISTILLER_CONTRACT_DL2, LLM_DISTILLER_CONTRACT_DL3, LLM_DISTILLER_CONTRACTS, MEMORY_DISTILLER_CONTRACT_V1, contractGroupingDiff, driveConsolidationToFixpoint, isAliasModelId, llmPlanDistiller, mintExposurePartitionedPlan, mintLlmConsolidationPlan, openAiCompatChatSeat, parseJsonAnswer, planParseRepairs, sanitizeLlmGroups, scheduleUnderFuse, } from "./distiller.js";
|
|
13
13
|
export { CONSOLIDATION_DRIVER_PLANS_DIR, CONSOLIDATION_DRIVER_RUNS_FILE, archiveDistillerPlan, readConsolidationDriverRun, runMemoryConsolidationDriver, } from "./consolidation-driver.js";
|
|
14
14
|
export { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
15
15
|
export { memoryBackendContract, assertMemoryBackendSearchEquivalence, } from "./memory-backend-contract.js";
|
|
@@ -1490,9 +1490,12 @@ export interface RunInternals {
|
|
|
1490
1490
|
* `TaskStream.detach(toolCallId)`; the hands Bash tool threads `signalFor(toolCallId)` into env.exec. */
|
|
1491
1491
|
detachHub?: import("../tool-detach.js").ToolDetachHub;
|
|
1492
1492
|
onTaskNotification?: (notification: TaskNotificationPayload,
|
|
1493
|
-
/**
|
|
1494
|
-
*
|
|
1495
|
-
* "
|
|
1493
|
+
/** Injection tier (design/373 — the ladder is LIVE): "next" = the running turn's next boundary
|
|
1494
|
+
* (arrival order, consecutive frames batch); "later" = the run's would-otherwise-stop seat
|
|
1495
|
+
* (never folded into work in progress); "now" = class-head + earliest natural boundary on this
|
|
1496
|
+
* lane (interrupt authority belongs to the steer face, never to notifications). Internal
|
|
1497
|
+
* producers declare their tier explicitly (§3.7 census — completion-class lanes are "next");
|
|
1498
|
+
* the parameterless default "later" serves the external verb's omitting callers only. */
|
|
1496
1499
|
opts?: {
|
|
1497
1500
|
priority?: import("../task-notification.js").SystemInjectionPriority;
|
|
1498
1501
|
}) => void;
|
|
@@ -3965,6 +3965,7 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
3965
3965
|
: undefined,
|
|
3966
3966
|
resourceLedger: resourceLedgerOut,
|
|
3967
3967
|
...(spec.durableApproval !== undefined ? { durableApproval: { ...spec.durableApproval } } : {}),
|
|
3968
|
+
...(spec.principal ? { principal: spec.principal } : {}),
|
|
3968
3969
|
};
|
|
3969
3970
|
if (!(await commitSuspendSaga(token, cp, suspendableEnv, remoteHandle)).ok)
|
|
3970
3971
|
return false;
|