@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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +2227 -1253
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6237 -130
- package/dist/index.js.map +1 -1
- package/dist/rolldown-runtime-CiIaOW0V.js +13 -0
- package/dist/run-crew-DDznbc3G.js +888 -0
- package/dist/run-crew-DDznbc3G.js.map +1 -0
- package/dist/{runtime-CfZ-sGGk.js → runtime-BTi8lr_O.js} +258 -228
- package/dist/runtime-BTi8lr_O.js.map +1 -0
- package/package.json +9 -9
- package/dist/runtime-CfZ-sGGk.js.map +0 -1
|
@@ -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
|