@full-self-browsing/lattice 0.0.0-bootstrap.0 → 1.3.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.
@@ -0,0 +1,888 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-CiIaOW0V.js";
2
+ import { a as createNoopAgentHost, f as validateSchemaOutput, n as runAgentInternal, p as formatToolsForProvider, u as createNoopSurvivabilityAdapter, y as createReceipt } from "./runtime-BTi8lr_O.js";
3
+ //#region src/agent/infra/cost-tracker.ts
4
+ const WARNING_THRESHOLD = .8;
5
+ function createCostTracker() {
6
+ let promptTokens = 0;
7
+ let completionTokens = 0;
8
+ let costUsd = null;
9
+ return {
10
+ kind: "cost-tracker",
11
+ recordIteration(usage) {
12
+ promptTokens += usage.promptTokens;
13
+ completionTokens += usage.completionTokens;
14
+ if (usage.costUsd !== null) costUsd = (costUsd ?? 0) + usage.costUsd;
15
+ },
16
+ total() {
17
+ return {
18
+ promptTokens,
19
+ completionTokens,
20
+ costUsd
21
+ };
22
+ },
23
+ budgetStatus(budget) {
24
+ const max = budget?.maxCostUsd;
25
+ if (max === void 0 || costUsd === null) return "ok";
26
+ if (costUsd >= max) return "exceeded";
27
+ if (costUsd >= max * WARNING_THRESHOLD) return "warning";
28
+ return "ok";
29
+ }
30
+ };
31
+ }
32
+ //#endregion
33
+ //#region src/receipts/cid.ts
34
+ /**
35
+ * Derive the content-addressed CID of a receipt envelope.
36
+ *
37
+ * Returns `sha256:<hex>` where `<hex>` is the 64-char lowercase SHA-256
38
+ * digest of the decoded DSSE payload bytes. No KeySet, signer, or other
39
+ * key material is required — callers chaining receipts (parentReceiptCid)
40
+ * compute this from the parent envelope alone.
41
+ */
42
+ async function receiptCid(envelope) {
43
+ const bytes = Uint8Array.from(atob(envelope.payload), (c) => c.charCodeAt(0));
44
+ const copy = new Uint8Array(bytes.byteLength);
45
+ copy.set(bytes);
46
+ const digest = await crypto.subtle.digest("SHA-256", copy.buffer);
47
+ return `sha256:${Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("")}`;
48
+ }
49
+ //#endregion
50
+ //#region src/agent/infra/action-history.ts
51
+ /**
52
+ * ActionHistory — Phase 21 (v1.2).
53
+ *
54
+ * Detects stuck patterns in the agent loop's tool-call sequence:
55
+ * - "consecutive-identical-tool-call" — same (toolName, argsHash) N+ times in a row
56
+ * - "ping-pong" — last 4 records alternate between 2 distinct (toolName, argsHash) pairs
57
+ * - "no-progress" — reserved for callers wiring goal-progress feedback here
58
+ *
59
+ * Standalone (no dependency on the agent runtime); callers register the
60
+ * primitive externally and pump `recordAction` from their loop or hook.
61
+ */
62
+ const STUCK_REASONS = [
63
+ "consecutive-identical-tool-call",
64
+ "no-progress",
65
+ "ping-pong"
66
+ ];
67
+ function actionKey(record) {
68
+ return `${record.toolName}::${record.argsHash}`;
69
+ }
70
+ function createActionHistory(options = {}) {
71
+ const consecutiveLimit = options.consecutiveLimit ?? 3;
72
+ const records = [];
73
+ return {
74
+ kind: "action-history",
75
+ recordAction(action) {
76
+ records.push(action);
77
+ if (records.length >= consecutiveLimit) {
78
+ const tail = records.slice(-consecutiveLimit);
79
+ const firstKey = actionKey(tail[0]);
80
+ if (tail.every((r) => actionKey(r) === firstKey)) return "consecutive-identical-tool-call";
81
+ }
82
+ if (records.length >= 4) {
83
+ const keys = records.slice(-4).map(actionKey);
84
+ if (Array.from(new Set(keys)).length === 2 && keys[0] === keys[2] && keys[1] === keys[3] && keys[0] !== keys[1]) return "ping-pong";
85
+ }
86
+ return null;
87
+ },
88
+ history() {
89
+ return Object.freeze([...records]);
90
+ }
91
+ };
92
+ }
93
+ //#endregion
94
+ //#region src/agent/crew/dispatcher.ts
95
+ /**
96
+ * Create the crew dispatch chokepoint for one agent spec.
97
+ *
98
+ * `spec` is the agent whose loop this dispatcher serves (its `childAgents`
99
+ * are the dispatchable targets); `ctx` carries every crew-level concern.
100
+ */
101
+ function createCrewDispatcher(spec, ctx) {
102
+ return createDispatcherNode(spec, ctx, {
103
+ exhausted: false,
104
+ runId: `lattice-crew-${crypto.randomUUID()}`
105
+ });
106
+ }
107
+ function createDispatcherNode(spec, ctx, shared) {
108
+ const children = spec.childAgents ?? [];
109
+ const terminalBlock = /* @__PURE__ */ new Map();
110
+ const childHost = ctx.sharedPrefix.length > 0 ? {
111
+ ...ctx.childHost,
112
+ transport: withCachePrefixHoist(ctx.sharedPrefix, ctx.childHost.transport)
113
+ } : ctx.childHost;
114
+ async function dispatchToolUse(req, _loopCtx) {
115
+ const childSpec = children.find((child) => child.id === req.name);
116
+ if (childSpec === void 0) return;
117
+ const blocked = terminalBlock.get(childSpec.id);
118
+ if (blocked !== void 0) return { content: blocked };
119
+ if (req.name === spec.id || ctx.ancestry.includes(req.name)) return errorResult({
120
+ kind: "crew-cycle-rejected",
121
+ reason: `Dispatch of "${req.name}" rejected: the id already appears in the crew ancestry chain (D-05 cycle prevention).`,
122
+ terminal: false
123
+ });
124
+ if (ctx.ancestry.length >= ctx.policy.maxDepth) return errorResult({
125
+ kind: "crew-depth-exceeded",
126
+ reason: `Dispatch of "${req.name}" rejected: crew maxDepth ${ctx.policy.maxDepth} reached (ancestry depth ${ctx.ancestry.length}).`,
127
+ terminal: false
128
+ });
129
+ const pool = ctx.remainingBudget();
130
+ if (isPoolExhausted(pool)) {
131
+ shared.exhausted = true;
132
+ return errorResult({
133
+ kind: "crew-budget-exceeded",
134
+ reason: "Crew budget pool exhausted — the crew run must end (D-10).",
135
+ terminal: true
136
+ });
137
+ }
138
+ const task = extractTaskArg(req.args);
139
+ if (task === null) return errorResult({
140
+ kind: "invalid-dispatch-args",
141
+ reason: `Dispatch args for child agent "${childSpec.id}" must be { "task": string }.`,
142
+ terminal: false
143
+ });
144
+ const effectiveBudget = deriveChildBudget(childSpec.contract?.budget, pool, ctx.policy.maxIterationsPerAgent);
145
+ const childAncestry = [...ctx.ancestry, spec.id];
146
+ const childNode = childSpec.childAgents !== void 0 && childSpec.childAgents.length > 0 ? createDispatcherNode(childSpec, {
147
+ ...ctx,
148
+ ancestry: childAncestry
149
+ }, shared) : void 0;
150
+ const childResult = await runAgentInternal({
151
+ task,
152
+ tools: childNode !== void 0 ? [...childSpec.tools, ...childNode.childToolDeclarations] : childSpec.tools,
153
+ host: childHost,
154
+ survivabilityAdapter: withAncestrySnapshot(createNoopSurvivabilityAdapter(), [...childAncestry, childSpec.id]),
155
+ ...effectiveBudget !== void 0 ? { contract: {
156
+ kind: "capability-contract",
157
+ budget: effectiveBudget
158
+ } } : {},
159
+ ...ctx.signer !== void 0 ? { signer: ctx.signer } : {},
160
+ ...ctx.tracer !== void 0 ? { tracer: ctx.tracer } : {},
161
+ ...ctx.pipeline !== void 0 ? { pipeline: ctx.pipeline } : {}
162
+ }, ctx.config, childNode !== void 0 ? { dispatchToolUse: childNode.dispatchToolUse } : {});
163
+ ctx.recordUsage(childSpec.id, childResult.usage);
164
+ ctx.recordAgentResult?.(childSpec.id, childResult);
165
+ if (childResult.kind !== "success") {
166
+ const classified = classifyChildFailure(childSpec.id, childResult);
167
+ const result = errorResult(classified);
168
+ if (classified.terminal) {
169
+ terminalBlock.set(childSpec.id, result.content);
170
+ if (classified.kind === "crew-budget-exceeded") shared.exhausted = true;
171
+ }
172
+ return result;
173
+ }
174
+ const receipts = [];
175
+ if (ctx.signer !== void 0) try {
176
+ const envelope = await createReceipt({
177
+ runId: shared.runId,
178
+ model: {
179
+ requested: "lattice-crew/agent-completion",
180
+ observed: null
181
+ },
182
+ route: {
183
+ providerId: "lattice-crew",
184
+ capabilityId: "lattice-crew/agent-completion",
185
+ attemptNumber: 1
186
+ },
187
+ ...ctx.crewRootCid !== void 0 ? { parentReceiptCid: ctx.crewRootCid } : {},
188
+ usage: childResult.usage,
189
+ contractVerdict: "success",
190
+ contractHash: null,
191
+ inputHashes: [],
192
+ outputHash: null,
193
+ stepName: `crew-agent-completion:${childSpec.id}`
194
+ }, ctx.signer);
195
+ ctx.mintedReceipts(envelope);
196
+ receipts.push(await receiptCid(envelope));
197
+ } catch {}
198
+ const envelope = {
199
+ summary: extractSummary(childResult.output),
200
+ artifacts: extractArtifacts(childResult),
201
+ receipts
202
+ };
203
+ const validation = await validateSchemaOutput(childSpec.id, childSpec.summaryReturnSchema, envelope);
204
+ if (!validation.ok) return errorResult({
205
+ kind: "summary-validation-failed",
206
+ reason: validation.issue.issues.map((issue) => issue.message).join("; "),
207
+ terminal: false
208
+ });
209
+ return { content: JSON.stringify(envelope) };
210
+ }
211
+ return {
212
+ dispatchToolUse,
213
+ childToolDeclarations: synthesizeChildDeclarations(children),
214
+ crewBudgetExhausted: () => shared.exhausted
215
+ };
216
+ }
217
+ /**
218
+ * Per-dimension `min(spec.contract?.budget, remaining crew pool)` with the
219
+ * iteration dimension additionally capped by `maxIterationsPerAgent`.
220
+ *
221
+ * Cost-dimension min applies ONLY when both sides are numbers — a pool
222
+ * derived from null-cost (unmeasured) usage omits `maxCostUsd`, and even a
223
+ * literal `null` never poisons the arithmetic (Pitfall 4).
224
+ */
225
+ function deriveChildBudget(specBudget, pool, maxIterationsPerAgent) {
226
+ const maxIterations = minDefined$1(minDefined$1(specBudget?.maxIterations, pool?.maxIterations), maxIterationsPerAgent);
227
+ const maxWallTimeMs = minDefined$1(specBudget?.maxWallTimeMs, pool?.maxWallTimeMs);
228
+ const maxCostUsd = minDefined$1(specBudget?.maxCostUsd, pool?.maxCostUsd);
229
+ if (maxIterations === void 0 && maxWallTimeMs === void 0 && maxCostUsd === void 0) return;
230
+ return {
231
+ ...maxIterations !== void 0 ? { maxIterations } : {},
232
+ ...maxWallTimeMs !== void 0 ? { maxWallTimeMs } : {},
233
+ ...maxCostUsd !== void 0 ? { maxCostUsd } : {}
234
+ };
235
+ }
236
+ /** min() that only applies when BOTH sides are real numbers (Pitfall 4). */
237
+ function minDefined$1(a, b) {
238
+ const aNum = typeof a === "number" && Number.isFinite(a) ? a : void 0;
239
+ const bNum = typeof b === "number" && Number.isFinite(b) ? b : void 0;
240
+ if (aNum !== void 0 && bNum !== void 0) return Math.min(aNum, bNum);
241
+ return aNum ?? bNum;
242
+ }
243
+ /**
244
+ * Crew-ceiling predicate (D-10): the pool is exhausted when ANY bounded
245
+ * dimension is at/below zero. Null/absent dimensions (unmeasured cost)
246
+ * never count as exhausted (Pitfall 4).
247
+ */
248
+ function isPoolExhausted(pool) {
249
+ if (pool === void 0) return false;
250
+ return typeof pool.maxIterations === "number" && pool.maxIterations <= 0 || typeof pool.maxWallTimeMs === "number" && pool.maxWallTimeMs <= 0 || typeof pool.maxCostUsd === "number" && pool.maxCostUsd <= 0;
251
+ }
252
+ /**
253
+ * Compose the byte-stable crew cache prefix for one tool surface: the
254
+ * `describeForSystem()` block (tool descriptions + envelope instructions),
255
+ * derived from deterministic, version-pinned inputs ONLY — no timestamps,
256
+ * random ids, or unsorted keys (Phase 35 scaffold discipline; any
257
+ * non-byte-stable fragment silently zeroes the cache-hit rate).
258
+ *
259
+ * The 39-06 orchestrator composes this ONCE per crew at crew start and
260
+ * threads it as `CrewDispatchContext.sharedPrefix`. All members sharing a
261
+ * tool surface share byte-identical prefix bytes across dispatches.
262
+ */
263
+ function composeCrewCachePrefix(tools) {
264
+ return formatToolsForProvider("lattice-crew", tools).describeForSystem();
265
+ }
266
+ /**
267
+ * Transport wrapper implementing the quirks-gated prefix hoist:
268
+ *
269
+ * - Adapter discloses `quirks.promptCachingSupported === true` (Anthropic
270
+ * block-granular caching) AND the outgoing task starts with the shared
271
+ * prefix → the prefix is hoisted to `cacheSystemPrefix` and `task`
272
+ * carries ONLY the conversation body. The 39-03 byte-equality invariant
273
+ * (`describeForSystem() + "\n" + buildTaskBody(conv) === buildTask(conv)`)
274
+ * guarantees the stripped remainder IS the body-only rendering — the
275
+ * prefix is never duplicated.
276
+ * - Any other adapter → the request passes through UNTOUCHED: no
277
+ * `cacheSystemPrefix` own-property is ever created (Pitfall 6) and the
278
+ * prefix stays at the head of `task` (OpenAI automatic token-prefix path).
279
+ *
280
+ * Composes over an existing `AgentTransport` (FSB offscreen bridge etc.);
281
+ * when `inner` is absent it dispatches `provider.execute()` directly,
282
+ * matching the runtime's default transport behavior.
283
+ */
284
+ function withCachePrefixHoist(sharedPrefix, inner) {
285
+ const marker = `${sharedPrefix}\n`;
286
+ return { async call(provider, request) {
287
+ const outbound = sharedPrefix.length > 0 && supportsPromptCaching(provider) && request.task.startsWith(marker) ? {
288
+ ...request,
289
+ task: request.task.slice(marker.length),
290
+ cacheSystemPrefix: sharedPrefix
291
+ } : request;
292
+ if (inner !== void 0) return inner.call(provider, outbound);
293
+ if (provider.execute === void 0) throw new Error(`CrewDispatcher: provider "${provider.id}" has no execute() method.`);
294
+ return provider.execute(outbound);
295
+ } };
296
+ }
297
+ /** Quirks gate: only adapters that disclose block-granular prompt caching. */
298
+ function supportsPromptCaching(provider) {
299
+ return provider.quirks?.promptCachingSupported === true;
300
+ }
301
+ /**
302
+ * Map a child `AgentFailure` to the structured tool-result error body.
303
+ *
304
+ * Terminal (D-10): tripwire violations and contract no-match (the
305
+ * `isTerminal()` kinds in results/errors.ts — the agent loop reuses
306
+ * `no-contract-match` for child cost-budget exhaustion), crew-ceiling
307
+ * breaches, and non-stuck SAFETY-band denials (AgentDeniedError aligns
308
+ * with TripwireViolationError terminal semantics). Recoverable (D-09):
309
+ * iteration/wall-time exhaustion and STUCK_REASONS stalls — the parent
310
+ * MAY re-dispatch.
311
+ */
312
+ function classifyChildFailure(childId, failure) {
313
+ return {
314
+ kind: failure.kind,
315
+ reason: failure.reason ?? `Child agent "${childId}" failed with kind "${failure.kind}".`,
316
+ terminal: isTerminalChildFailure(failure)
317
+ };
318
+ }
319
+ function isTerminalChildFailure(failure) {
320
+ if (failure.kind === "tripwire-violated" || failure.kind === "no-contract-match" || failure.kind === "crew-budget-exceeded") return true;
321
+ if (failure.kind === "agent-iteration-denied") {
322
+ const reason = failure.reason ?? "";
323
+ return !STUCK_REASONS.some((stuck) => reason.includes(stuck));
324
+ }
325
+ return false;
326
+ }
327
+ function errorResult(body) {
328
+ return { content: JSON.stringify({ error: {
329
+ kind: body.kind,
330
+ reason: body.reason,
331
+ terminal: body.terminal
332
+ } }) };
333
+ }
334
+ function extractTaskArg(args) {
335
+ if (typeof args !== "object" || args === null) return null;
336
+ const task = args["task"];
337
+ return typeof task === "string" ? task : null;
338
+ }
339
+ function extractSummary(output) {
340
+ if (typeof output === "object" && output !== null) {
341
+ const answer = output["answer"];
342
+ if (typeof answer === "string") return answer;
343
+ }
344
+ if (typeof output === "string") return output;
345
+ try {
346
+ return JSON.stringify(output);
347
+ } catch {
348
+ return String(output);
349
+ }
350
+ }
351
+ function extractArtifacts(result) {
352
+ const artifacts = result.artifacts;
353
+ return Array.isArray(artifacts) ? [...artifacts] : [];
354
+ }
355
+ /**
356
+ * Wrap a survivability adapter so serialized child snapshots carry the
357
+ * root-first ancestry chain (D-05; AgentSnapshot.ancestry from 39-03).
358
+ * The `agent-snapshot/v1` version literal is unchanged — the field is
359
+ * additive-optional (Pitfall 8).
360
+ */
361
+ function withAncestrySnapshot(base, ancestry) {
362
+ return {
363
+ ...base,
364
+ serialize: (state) => base.serialize({
365
+ ...state,
366
+ ancestry
367
+ })
368
+ };
369
+ }
370
+ function synthesizeChildDeclarations(children) {
371
+ return children.map((child) => ({
372
+ kind: "tool",
373
+ name: child.id,
374
+ description: `Delegate a task to the "${child.id}" child agent. Agent intent: ${child.intent} Returns a JSON summary envelope { "summary": string, "artifacts": array, "receipts": array } validated against the agent's summaryReturnSchema.`,
375
+ inputSchema: makeDispatchArgsSchema(child.id),
376
+ execute: () => {
377
+ throw new Error(`Child agent "${child.id}" must be dispatched through the CrewDispatcher (kind:"agent" branch) — direct tool execution is forbidden (D-01/D-02).`);
378
+ }
379
+ }));
380
+ }
381
+ /**
382
+ * Deterministic `~standard` schema for `{ task: string }` dispatch args.
383
+ * Carries a fixed `toJSONSchema()` so `formatToolsForProvider` renders a
384
+ * meaningful args_schema (byte-stable — no timestamps/random ids; the
385
+ * declarations feed the shared cache prefix).
386
+ */
387
+ function makeDispatchArgsSchema(childId) {
388
+ return {
389
+ "~standard": {
390
+ version: 1,
391
+ vendor: "lattice-crew",
392
+ validate: (value) => {
393
+ if (typeof value === "object" && value !== null && typeof value["task"] === "string") return { value };
394
+ return { issues: [{ message: `Dispatch args for child agent "${childId}" must be { "task": string }.` }] };
395
+ }
396
+ },
397
+ toJSONSchema: () => ({
398
+ type: "object",
399
+ properties: { task: {
400
+ type: "string",
401
+ description: "The task to delegate to this child agent."
402
+ } },
403
+ required: ["task"],
404
+ additionalProperties: false
405
+ })
406
+ };
407
+ }
408
+ //#endregion
409
+ //#region src/agent/crew/crew-policy.ts
410
+ /**
411
+ * Validates and normalizes a `CrewPolicy`.
412
+ *
413
+ * - Applies defaults: `maxDepth: 1`, `maxConcurrentChildren: 1`,
414
+ * `coordination: "managed"`.
415
+ * - Throws `TypeError` when `maxConcurrentChildren > 1` (serial-only v1.3
416
+ * limit, D-11) or when any structural cap is a non-integer or < 1.
417
+ * - Returns a frozen normalized policy; the input is never mutated.
418
+ */
419
+ function validateCrewPolicy(policy = {}) {
420
+ assertStructuralCap("maxConcurrentChildren", policy.maxConcurrentChildren);
421
+ if (policy.maxConcurrentChildren !== void 0 && policy.maxConcurrentChildren > 1) throw new TypeError("CrewPolicy.maxConcurrentChildren > 1 is not supported in v1.3 — children execute serially (D-11).");
422
+ assertStructuralCap("maxDepth", policy.maxDepth);
423
+ assertStructuralCap("maxTotalIterations", policy.maxTotalIterations);
424
+ assertStructuralCap("maxIterationsPerAgent", policy.maxIterationsPerAgent);
425
+ return Object.freeze({
426
+ ...policy.budget !== void 0 ? { budget: Object.freeze({ ...policy.budget }) } : {},
427
+ ...policy.maxTotalIterations !== void 0 ? { maxTotalIterations: policy.maxTotalIterations } : {},
428
+ ...policy.maxIterationsPerAgent !== void 0 ? { maxIterationsPerAgent: policy.maxIterationsPerAgent } : {},
429
+ maxConcurrentChildren: policy.maxConcurrentChildren ?? 1,
430
+ maxDepth: policy.maxDepth ?? 1,
431
+ ...policy.limits !== void 0 ? { limits: Object.freeze(Object.fromEntries(Object.entries(policy.limits).map(([adapterId, override]) => [adapterId, Object.freeze({ ...override })]))) } : {},
432
+ coordination: policy.coordination ?? "managed"
433
+ });
434
+ }
435
+ function assertStructuralCap(field, value) {
436
+ if (value === void 0) return;
437
+ if (!Number.isInteger(value) || value < 1) throw new TypeError(`CrewPolicy.${field} must be a positive integer (>= 1); received ${String(value)}.`);
438
+ }
439
+ //#endregion
440
+ //#region src/agent/infra/rate-limit-group.ts
441
+ /** Anthropic Tier 1 requests/minute (fetched 2026-06-10). */
442
+ const DEFAULT_REQUESTS_PER_MINUTE = 50;
443
+ /** Anthropic Tier 1 input tokens/minute, Sonnet-class (fetched 2026-06-10). */
444
+ const DEFAULT_TOKENS_PER_MINUTE = 3e4;
445
+ const MS_PER_MINUTE = 6e4;
446
+ /**
447
+ * Absorbs floating-point drift in two places: bucket-level comparisons
448
+ * (`available >= need`) and deficit-wait rounding. 1e-6 of a token/request
449
+ * (or millisecond) is far above accumulated IEEE-754 error at these
450
+ * magnitudes and far below anything rate-limit-relevant.
451
+ */
452
+ const FLOAT_EPSILON = 1e-6;
453
+ function createBucket(perMinute, label) {
454
+ if (!Number.isFinite(perMinute) || perMinute <= 0) throw new TypeError(`createRateLimitGroup: ${label} must be a positive finite number, got ${perMinute}.`);
455
+ return {
456
+ available: perMinute,
457
+ capacity: perMinute,
458
+ perMsRate: perMinute / MS_PER_MINUTE
459
+ };
460
+ }
461
+ function createRateLimitGroup(options = {}) {
462
+ const now = options.now ?? Date.now;
463
+ const requests = createBucket(options.requestsPerMinute ?? DEFAULT_REQUESTS_PER_MINUTE, "requestsPerMinute");
464
+ const tokens = createBucket(options.tokensPerMinute ?? DEFAULT_TOKENS_PER_MINUTE, "tokensPerMinute");
465
+ let lastRefillAt = now();
466
+ const waiters = [];
467
+ let timer = null;
468
+ /** Lazy continuous refill from the clock delta — never a recurring timer. */
469
+ function refill() {
470
+ const t = now();
471
+ const elapsed = t - lastRefillAt;
472
+ if (elapsed <= 0) return;
473
+ lastRefillAt = t;
474
+ requests.available = Math.min(requests.capacity, requests.available + elapsed * requests.perMsRate);
475
+ tokens.available = Math.min(tokens.capacity, tokens.available + elapsed * tokens.perMsRate);
476
+ }
477
+ /**
478
+ * Token requirement for availability checks, clamped to capacity so an
479
+ * oversized estimate (> bucket capacity) proceeds at full bucket instead
480
+ * of deadlocking; the full amount is still debited (the bucket goes into
481
+ * debt and recovers via drain).
482
+ */
483
+ function tokenNeed(inputTokens) {
484
+ return Math.min(inputTokens, tokens.capacity);
485
+ }
486
+ function tryDebit(inputTokens) {
487
+ if (requests.available + FLOAT_EPSILON >= 1 && tokens.available + FLOAT_EPSILON >= tokenNeed(inputTokens)) {
488
+ requests.available -= 1;
489
+ tokens.available -= inputTokens;
490
+ return true;
491
+ }
492
+ return false;
493
+ }
494
+ /** Exact wait (ms) until both dimensions can cover the head waiter. */
495
+ function deficitWaitMs(inputTokens) {
496
+ const requestDeficit = Math.max(0, 1 - requests.available);
497
+ const tokenDeficit = Math.max(0, tokenNeed(inputTokens) - tokens.available);
498
+ const requestWait = requestDeficit / requests.perMsRate;
499
+ const tokenWait = tokenDeficit / tokens.perMsRate;
500
+ return Math.max(requestWait, tokenWait);
501
+ }
502
+ function scheduleNext() {
503
+ if (timer !== null) return;
504
+ const head = waiters[0];
505
+ if (head === void 0) return;
506
+ const waitMs = Math.max(1, Math.ceil(deficitWaitMs(head.inputTokens) - FLOAT_EPSILON));
507
+ timer = setTimeout(() => {
508
+ timer = null;
509
+ pump();
510
+ }, waitMs);
511
+ }
512
+ /** Refill, resolve as many FIFO waiters as capacity allows, reschedule. */
513
+ function pump() {
514
+ if (timer !== null) {
515
+ clearTimeout(timer);
516
+ timer = null;
517
+ }
518
+ refill();
519
+ while (waiters.length > 0) {
520
+ const head = waiters[0];
521
+ if (head === void 0 || !tryDebit(head.inputTokens)) break;
522
+ waiters.shift();
523
+ head.resolve(createLease(head.inputTokens));
524
+ }
525
+ scheduleNext();
526
+ }
527
+ function createLease(estimateTokens) {
528
+ let released = false;
529
+ return { release(actual) {
530
+ if (released) return;
531
+ released = true;
532
+ refill();
533
+ const actualTokens = typeof actual.promptTokens === "number" && Number.isFinite(actual.promptTokens) ? actual.promptTokens : estimateTokens;
534
+ tokens.available = Math.min(tokens.capacity, tokens.available + (estimateTokens - actualTokens));
535
+ pump();
536
+ } };
537
+ }
538
+ return {
539
+ kind: "rate-limit-group",
540
+ async acquire(estimate) {
541
+ refill();
542
+ if (waiters.length === 0 && tryDebit(estimate.inputTokens)) return createLease(estimate.inputTokens);
543
+ return new Promise((resolve) => {
544
+ waiters.push({
545
+ inputTokens: estimate.inputTokens,
546
+ resolve
547
+ });
548
+ scheduleNext();
549
+ });
550
+ }
551
+ };
552
+ }
553
+ /**
554
+ * chars/4 heuristic for lease reservation (matches the transcript-store
555
+ * default `TokenEstimator`). Persistent estimation error is benign: `release`
556
+ * reconciles every lease against the actual `Usage.promptTokens` (A2).
557
+ */
558
+ function estimateInputTokens(task) {
559
+ return Math.ceil(task.length / 4);
560
+ }
561
+ /**
562
+ * Wrap an `AgentTransport` so every provider call is gated through `group`.
563
+ *
564
+ * Every transport wrapped with the SAME group instance shares one bucket —
565
+ * `runAgentCrew` (39-06) wraps parent + child hosts with one shared group per
566
+ * adapter instance, structurally guaranteeing crew-wide coordination (D-13).
567
+ * `ProviderAdapter` is never modified (INV-03 parity invariant intact).
568
+ *
569
+ * - `inner` provided → dispatch nests through `inner.call(provider, request)`,
570
+ * composing with consumer transports (e.g. cross-process bridges).
571
+ * - `inner` undefined → falls through to `provider.execute(request)`, guarded
572
+ * the same way as `createNoopAgentHost` (error names the provider id only).
573
+ *
574
+ * Release policy:
575
+ * - Success → release with `normalizedUsage.promptTokens`; when usage is
576
+ * missing or non-finite, fall back to the estimate (no NaN arithmetic —
577
+ * the `costUsd: null` "unmeasured" discipline analog).
578
+ * - Throw → release with the ORIGINAL estimate (burn — no refund; the
579
+ * provider may have consumed quota despite the error) and rethrow the
580
+ * same error unchanged. Request objects and headers are never serialized
581
+ * into error paths.
582
+ */
583
+ function withRateLimit(group, inner) {
584
+ return { async call(provider, request) {
585
+ const estimate = estimateInputTokens(request.task);
586
+ const lease = await group.acquire({ inputTokens: estimate });
587
+ try {
588
+ let response;
589
+ if (inner !== void 0) response = await inner.call(provider, request);
590
+ else {
591
+ if (provider.execute === void 0) throw new Error(`AgentTransport: provider ${provider.id} has no execute() method.`);
592
+ response = await provider.execute(request);
593
+ }
594
+ const actual = response.normalizedUsage?.promptTokens;
595
+ lease.release({ promptTokens: typeof actual === "number" && Number.isFinite(actual) ? actual : estimate });
596
+ return response;
597
+ } catch (error) {
598
+ lease.release({ promptTokens: estimate });
599
+ throw error;
600
+ }
601
+ } };
602
+ }
603
+ //#endregion
604
+ //#region src/agent/crew/run-crew.ts
605
+ var run_crew_exports = /* @__PURE__ */ __exportAll({ runAgentCrew: () => runAgentCrew });
606
+ const ZERO_USAGE = {
607
+ promptTokens: 0,
608
+ completionTokens: 0,
609
+ costUsd: null
610
+ };
611
+ /**
612
+ * Execute a crew rooted at `options.root`.
613
+ */
614
+ async function runAgentCrew(options, config = {}) {
615
+ const policy = validateCrewPolicy(options.policy);
616
+ const runId = `runAgentCrew-${Date.now()}-${Math.random().toString(16).slice(2)}`;
617
+ const startedAt = Date.now();
618
+ const accounting = /* @__PURE__ */ new Map();
619
+ const receipts = [];
620
+ const receiptCidsByAgent = /* @__PURE__ */ new Map();
621
+ const crewRoot = await maybeMintCrewRoot({
622
+ runId,
623
+ root: options.root,
624
+ ...options.signer !== void 0 ? { signer: options.signer } : {}
625
+ });
626
+ if (crewRoot !== void 0) receipts.push(crewRoot.envelope);
627
+ const groupForProvider = createRateLimitGroupResolver(policy);
628
+ const childHost = wrapHostWithRateLimits(options.hosts.childHost, groupForProvider);
629
+ function recordUsage(agentId, usage) {
630
+ entryFor(accounting, agentId).tracker.recordIteration(usage);
631
+ }
632
+ function recordAgentResult(agentId, result) {
633
+ const entry = entryFor(accounting, agentId);
634
+ entry.iterations += result.iterations.length;
635
+ }
636
+ function remainingBudget() {
637
+ return deriveRemainingBudget({
638
+ policy,
639
+ usage: totalUsage(accounting),
640
+ iterations: totalIterations(accounting),
641
+ startedAt
642
+ });
643
+ }
644
+ const dispatcher = createCrewDispatcher(options.root, {
645
+ policy,
646
+ childHost,
647
+ ancestry: [],
648
+ recordUsage,
649
+ recordAgentResult,
650
+ remainingBudget,
651
+ sharedPrefix: composeSharedPrefix(options.root),
652
+ mintedReceipts(envelope) {
653
+ receipts.push(envelope);
654
+ },
655
+ config,
656
+ ...crewRoot !== void 0 ? { crewRootCid: crewRoot.cid } : {},
657
+ ...options.signer !== void 0 ? { signer: options.signer } : {},
658
+ ...options.tracer !== void 0 ? { tracer: options.tracer } : {},
659
+ ...options.pipeline !== void 0 ? { pipeline: options.pipeline } : {}
660
+ });
661
+ const parentResult = await runAgentInternal({
662
+ task: options.root.intent,
663
+ tools: [...options.root.tools, ...dispatcher.childToolDeclarations],
664
+ host: wrapHostWithRateLimits(createNoopAgentHost(), groupForProvider),
665
+ ...options.root.contract !== void 0 ? { contract: options.root.contract } : {},
666
+ ...options.signer !== void 0 ? { signer: options.signer } : {},
667
+ ...options.tracer !== void 0 ? { tracer: options.tracer } : {},
668
+ ...options.pipeline !== void 0 ? { pipeline: options.pipeline } : {}
669
+ }, config, { dispatchToolUse: dispatcher.dispatchToolUse });
670
+ recordUsage(options.root.id, parentResult.usage);
671
+ recordAgentResult(options.root.id, parentResult);
672
+ if (options.signer !== void 0 && crewRoot !== void 0) {
673
+ const parentEnvelope = await createAgentCompletionReceipt({
674
+ runId,
675
+ agentId: options.root.id,
676
+ usage: parentResult.usage,
677
+ signer: options.signer,
678
+ parentReceiptCid: crewRoot.cid,
679
+ success: parentResult.kind === "success"
680
+ });
681
+ receipts.push(parentEnvelope);
682
+ }
683
+ await populateReceiptCidIndex(receipts, receiptCidsByAgent);
684
+ return freezeCrewResult({
685
+ result: dispatcher.crewBudgetExhausted() || crewBudgetViolated({
686
+ policy,
687
+ usage: totalUsage(accounting),
688
+ iterations: totalIterations(accounting),
689
+ startedAt
690
+ }) ? buildCrewBudgetFailure(parentResult) : parentResult,
691
+ perAgent: buildPerAgent(accounting, receiptCidsByAgent),
692
+ usage: totalUsage(accounting),
693
+ totalIterations: totalIterations(accounting),
694
+ receipts,
695
+ ...crewRoot !== void 0 ? { crewRootCid: crewRoot.cid } : {}
696
+ });
697
+ }
698
+ function entryFor(accounting, agentId) {
699
+ let entry = accounting.get(agentId);
700
+ if (entry === void 0) {
701
+ entry = {
702
+ tracker: createCostTracker(),
703
+ iterations: 0
704
+ };
705
+ accounting.set(agentId, entry);
706
+ }
707
+ return entry;
708
+ }
709
+ function totalUsage(accounting) {
710
+ const total = {
711
+ promptTokens: 0,
712
+ completionTokens: 0,
713
+ costUsd: null
714
+ };
715
+ for (const entry of accounting.values()) accumulate(total, entry.tracker.total());
716
+ return snapshot(total);
717
+ }
718
+ function totalIterations(accounting) {
719
+ let total = 0;
720
+ for (const entry of accounting.values()) total += entry.iterations;
721
+ return total;
722
+ }
723
+ function accumulate(total, usage) {
724
+ total.promptTokens += usage.promptTokens;
725
+ total.completionTokens += usage.completionTokens;
726
+ if (usage.costUsd !== null) total.costUsd = (total.costUsd ?? 0) + usage.costUsd;
727
+ }
728
+ function snapshot(usage) {
729
+ return {
730
+ promptTokens: usage.promptTokens,
731
+ completionTokens: usage.completionTokens,
732
+ costUsd: usage.costUsd
733
+ };
734
+ }
735
+ function deriveRemainingBudget(input) {
736
+ const remaining = {};
737
+ const budget = input.policy.budget;
738
+ const iterationCeiling = minDefined(budget?.maxIterations, input.policy.maxTotalIterations);
739
+ if (iterationCeiling !== void 0) remaining.maxIterations = iterationCeiling - input.iterations;
740
+ if (budget?.maxWallTimeMs !== void 0) remaining.maxWallTimeMs = budget.maxWallTimeMs - (Date.now() - input.startedAt);
741
+ if (budget?.maxCostUsd !== void 0 && input.usage.costUsd !== null) remaining.maxCostUsd = budget.maxCostUsd - input.usage.costUsd;
742
+ else if (budget?.maxCostUsd !== void 0 && input.usage.costUsd === null) remaining.maxCostUsd = budget.maxCostUsd;
743
+ if (budget?.p95LatencyMs !== void 0) remaining.p95LatencyMs = budget.p95LatencyMs;
744
+ return Object.keys(remaining).length > 0 ? remaining : void 0;
745
+ }
746
+ function crewBudgetViolated(input) {
747
+ const iterationCeiling = minDefined(input.policy.budget?.maxIterations, input.policy.maxTotalIterations);
748
+ if (iterationCeiling !== void 0 && input.iterations > iterationCeiling) return true;
749
+ const maxWallTimeMs = input.policy.budget?.maxWallTimeMs;
750
+ if (maxWallTimeMs !== void 0 && Date.now() - input.startedAt > maxWallTimeMs) return true;
751
+ const maxCostUsd = input.policy.budget?.maxCostUsd;
752
+ return maxCostUsd !== void 0 && input.usage.costUsd !== null && input.usage.costUsd > maxCostUsd;
753
+ }
754
+ function minDefined(a, b) {
755
+ if (a !== void 0 && b !== void 0) return Math.min(a, b);
756
+ return a ?? b;
757
+ }
758
+ function createRateLimitGroupResolver(policy) {
759
+ if (policy.coordination === "unmanaged") return () => void 0;
760
+ const groups = /* @__PURE__ */ new Map();
761
+ return (provider) => {
762
+ let group = groups.get(provider);
763
+ if (group === void 0) {
764
+ group = createRateLimitGroup(rateLimitOptionsFor(policy, provider));
765
+ groups.set(provider, group);
766
+ }
767
+ return group;
768
+ };
769
+ }
770
+ function rateLimitOptionsFor(policy, provider) {
771
+ const override = policy.limits?.[provider.id];
772
+ return {
773
+ ...override?.requestsPerMinute !== void 0 ? { requestsPerMinute: override.requestsPerMinute } : {},
774
+ ...override?.tokensPerMinute !== void 0 ? { tokensPerMinute: override.tokensPerMinute } : {}
775
+ };
776
+ }
777
+ function wrapHostWithRateLimits(host, groupForProvider) {
778
+ return {
779
+ ...host,
780
+ transport: wrapTransport(host.transport, groupForProvider)
781
+ };
782
+ }
783
+ function wrapTransport(inner, groupForProvider) {
784
+ return { call(provider, request) {
785
+ const group = groupForProvider(provider);
786
+ if (group !== void 0) return withRateLimit(group, inner).call(provider, request);
787
+ if (inner !== void 0) return inner.call(provider, request);
788
+ if (provider.execute === void 0) throw new Error(`AgentTransport: provider ${provider.id} has no execute() method.`);
789
+ return provider.execute(request);
790
+ } };
791
+ }
792
+ function composeSharedPrefix(root) {
793
+ const firstChild = root.childAgents?.[0];
794
+ return composeCrewCachePrefix(firstChild?.tools ?? root.tools);
795
+ }
796
+ async function maybeMintCrewRoot(input) {
797
+ if (input.signer === void 0) return void 0;
798
+ const envelope = await createReceipt({
799
+ runId: input.runId,
800
+ model: {
801
+ requested: "lattice-crew/root",
802
+ observed: null
803
+ },
804
+ route: {
805
+ providerId: "lattice-crew",
806
+ capabilityId: "lattice-crew/run",
807
+ attemptNumber: 1
808
+ },
809
+ usage: ZERO_USAGE,
810
+ contractVerdict: "success",
811
+ contractHash: null,
812
+ inputHashes: [],
813
+ outputHash: null,
814
+ stepName: `crew-start:${input.root.id}`
815
+ }, input.signer);
816
+ return {
817
+ envelope,
818
+ cid: await receiptCid(envelope)
819
+ };
820
+ }
821
+ async function createAgentCompletionReceipt(input) {
822
+ return createReceipt({
823
+ runId: input.runId,
824
+ model: {
825
+ requested: "lattice-crew/agent-completion",
826
+ observed: null
827
+ },
828
+ route: {
829
+ providerId: "lattice-crew",
830
+ capabilityId: "lattice-crew/agent-completion",
831
+ attemptNumber: 1
832
+ },
833
+ parentReceiptCid: input.parentReceiptCid,
834
+ usage: input.usage,
835
+ contractVerdict: input.success ? "success" : "execution-failed",
836
+ contractHash: null,
837
+ inputHashes: [],
838
+ outputHash: null,
839
+ stepName: `crew-agent-completion:${input.agentId}`
840
+ }, input.signer);
841
+ }
842
+ async function populateReceiptCidIndex(receipts, byAgent) {
843
+ for (const envelope of receipts) {
844
+ const agentId = parseCompletionAgentId(decodeReceiptBody(envelope).stepName);
845
+ if (agentId === null) continue;
846
+ const cids = byAgent.get(agentId) ?? [];
847
+ cids.push(await receiptCid(envelope));
848
+ byAgent.set(agentId, cids);
849
+ }
850
+ }
851
+ function decodeReceiptBody(envelope) {
852
+ return JSON.parse(atob(envelope.payload));
853
+ }
854
+ function parseCompletionAgentId(stepName) {
855
+ if (stepName?.startsWith("crew-agent-completion:") !== true) return null;
856
+ return stepName.slice(22);
857
+ }
858
+ function buildPerAgent(accounting, receiptCidsByAgent) {
859
+ return Object.freeze(Array.from(accounting.entries(), ([id, entry]) => Object.freeze({
860
+ id,
861
+ usage: Object.freeze(entry.tracker.total()),
862
+ iterations: entry.iterations,
863
+ receiptCids: Object.freeze([...receiptCidsByAgent.get(id) ?? []])
864
+ })));
865
+ }
866
+ function freezeCrewResult(result) {
867
+ return Object.freeze({
868
+ result: result.result,
869
+ perAgent: Object.freeze([...result.perAgent]),
870
+ usage: Object.freeze({ ...result.usage }),
871
+ totalIterations: result.totalIterations,
872
+ receipts: Object.freeze([...result.receipts]),
873
+ ...result.crewRootCid !== void 0 ? { crewRootCid: result.crewRootCid } : {}
874
+ });
875
+ }
876
+ function buildCrewBudgetFailure(parentResult) {
877
+ return {
878
+ kind: "crew-budget-exceeded",
879
+ reason: "Crew budget pool exhausted — the crew run ended with terminal semantics.",
880
+ usage: parentResult.usage,
881
+ iterations: Object.freeze([...parentResult.iterations]),
882
+ ...parentResult.receipt !== void 0 ? { receipt: parentResult.receipt } : {}
883
+ };
884
+ }
885
+ //#endregion
886
+ export { STUCK_REASONS as a, createCostTracker as c, withRateLimit as i, run_crew_exports as n, createActionHistory as o, createRateLimitGroup as r, receiptCid as s, runAgentCrew as t };
887
+
888
+ //# sourceMappingURL=run-crew-DDznbc3G.js.map