@tangle-network/agent-runtime 0.190.0 → 0.191.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/dist/{activation-BgRWFKZi.js → activation-BpTEBhQR.js} +2 -2
- package/dist/{activation-BgRWFKZi.js.map → activation-BpTEBhQR.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +2 -2
- package/dist/{authoring-dQQ2z1fT.js → authoring-DV0fkyxx.js} +7 -3
- package/dist/{authoring-dQQ2z1fT.js.map → authoring-DV0fkyxx.js.map} +1 -1
- package/dist/conversation.d.ts +1 -1
- package/dist/{coordination-driver-Da1IGdTP.js → coordination-driver-Bw0Prnim.js} +668 -35
- package/dist/coordination-driver-Bw0Prnim.js.map +1 -0
- package/dist/durable.d.ts +2 -2
- package/dist/durable.js +1 -1
- package/dist/{environment-provider-Bf-hgR6p.d.ts → environment-provider-CJ43FJfJ.d.ts} +2 -2
- package/dist/{environment-provider-DcelIawX.js → environment-provider-DEwsolvi.js} +43 -6
- package/dist/environment-provider-DEwsolvi.js.map +1 -0
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/{graph-Bd1elhdL.js → graph-BzsMtBYp.js} +3 -2
- package/dist/graph-BzsMtBYp.js.map +1 -0
- package/dist/graph.d.ts +3 -3
- package/dist/graph.js +3 -3
- package/dist/{redact-Cv1GfOFO.d.ts → improve-Cbbt3jE6.d.ts} +4 -36
- package/dist/{improvement-cycle-BanlFMyL.js → improvement-cycle-jC89uaRW.js} +3 -3
- package/dist/{improvement-cycle-BanlFMyL.js.map → improvement-cycle-jC89uaRW.js.map} +1 -1
- package/dist/{index-DpNil9jh.d.ts → index-B2rVjcPT.d.ts} +311 -13
- package/dist/{index-Clb7_qML.d.ts → index-OSRVFARy.d.ts} +2 -2
- package/dist/{index-C-gBk5M9.d.ts → index-Y14IrBuB.d.ts} +4 -4
- package/dist/index.d.ts +9 -8
- package/dist/index.js +7 -8
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +5 -4
- package/dist/intelligence.js +4 -5
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +6 -6
- package/dist/kernel.js +9 -9
- package/dist/{knowledge-BXdXlv48.js → knowledge-BuXP5ynd.js} +3 -3
- package/dist/{knowledge-BXdXlv48.js.map → knowledge-BuXP5ynd.js.map} +1 -1
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{loop-runner-bin-CGQzzWAc.js → loop-runner-bin-B_Xh4jLi.js} +3 -3
- package/dist/{loop-runner-bin-CGQzzWAc.js.map → loop-runner-bin-B_Xh4jLi.js.map} +1 -1
- package/dist/{loop-runner-bin-Cy4f9M6p.d.ts → loop-runner-bin-C2Yw-RBc.d.ts} +3 -3
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +3 -3
- package/dist/mcp/index.d.ts +4 -4
- package/dist/mcp/index.js +6 -6
- package/dist/{openai-tools-BSDk_umV.js → openai-tools-Dqg8IhKd.js} +2 -2
- package/dist/{openai-tools-BSDk_umV.js.map → openai-tools-Dqg8IhKd.js.map} +1 -1
- package/dist/primeintellect/index.d.ts +1 -1
- package/dist/{provision-supervisor-bwpzajiz.js → provision-supervisor-DAcTZDQG.js} +4 -4
- package/dist/{provision-supervisor-bwpzajiz.js.map → provision-supervisor-DAcTZDQG.js.map} +1 -1
- package/dist/{supervisor-CqJW4qap.js → redact-BP8Xn2A3.js} +83 -4
- package/dist/redact-BP8Xn2A3.js.map +1 -0
- package/dist/{runtime-BMWmSu4A.d.ts → runtime-DI4mJNXI.d.ts} +3 -3
- package/dist/{runtime-Ddh7PyCy.js → runtime-Dm63BrGY.js} +63 -19
- package/dist/{runtime-Ddh7PyCy.js.map → runtime-Dm63BrGY.js.map} +1 -1
- package/dist/{stream-agent-turn-uqzf9ZEh.d.ts → stream-agent-turn-_GgUW49y.d.ts} +35 -3
- package/dist/{structural-rollout-0NKvKuFm.js → structural-rollout-ChpGSR0q.js} +2 -2
- package/dist/{structural-rollout-0NKvKuFm.js.map → structural-rollout-ChpGSR0q.js.map} +1 -1
- package/dist/{supervise-yDiZAqlN.js → supervise-B89x7rgF.js} +21 -6
- package/dist/supervise-B89x7rgF.js.map +1 -0
- package/dist/testing.d.ts +2 -2
- package/dist/testing.js +12 -12
- package/dist/tui/index.d.ts +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{types-02hXqnSP.d.ts → types-DKA_PDv0.d.ts} +27 -2
- package/package.json +1 -1
- package/dist/coordination-driver-Da1IGdTP.js.map +0 -1
- package/dist/environment-provider-DcelIawX.js.map +0 -1
- package/dist/graph-Bd1elhdL.js.map +0 -1
- package/dist/redact-D-u-rrcn.js +0 -82
- package/dist/redact-D-u-rrcn.js.map +0 -1
- package/dist/supervise-yDiZAqlN.js.map +0 -1
- package/dist/supervisor-CqJW4qap.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { t as detachedFrozen } from "./snapshot-CTAf4uuA.js";
|
|
|
4
4
|
import { b as workerTraceAnalysisStore, i as InMemorySpawnJournal, n as FileSpawnJournal, r as InMemoryResultBlobStore, t as FileResultBlobStore, y as parseWorkerToolTraceArtifact } from "./spawn-journal-B9eTmVAn.js";
|
|
5
5
|
import { r as chargedTokens, u as promptCacheTokenClasses, v as unmeteredSpend } from "./util-mCNUeboW.js";
|
|
6
6
|
import { i as writeAllBytes, n as parseCommittedJsonLines, r as prepareJsonlAppend, t as isNoEntError } from "./jsonl-file-CDfsCI5s.js";
|
|
7
|
-
import {
|
|
7
|
+
import { At as createPeerMailbox, Ft as isTerminalNodeStatus, K as freeSlots, Pt as isLiveNodeStatus, Tt as createInbox, Yt as runBrainLoop, Z as assertValidBudget, d as pickBestDelivered, f as runFinalizer, h as withDriverExecutor, l as bestDelivered, lt as createExecutorRegistry, p as runTree, r as resolveRedactor, y as meterRuntimeOwnedProviderAttempt } from "./redact-BP8Xn2A3.js";
|
|
8
8
|
import { A as writeWorkerCancellation, M as writeWorkerSteerAcknowledgement, c as readWorkerCancelRequests, d as readWorkerSteerRequests, k as writeRunCancellation, l as readWorkerCancellation, o as readRunCancelRequest, r as claimWorkerSteerDelivery, s as readRunCancellation, u as readWorkerSteerAcknowledgement } from "./run-layout-B8I_LXN-.js";
|
|
9
9
|
import { agentProfileSchema, canonicalCandidateDigest } from "@tangle-network/agent-interface";
|
|
10
10
|
import { argHash, errorStreakDetector, observeAll, repeatedActionDetector } from "@tangle-network/agent-eval";
|
|
@@ -139,6 +139,50 @@ function createEventBus(now = Date.now) {
|
|
|
139
139
|
* nothing else, so the decision type states them and nothing else. */
|
|
140
140
|
const questionEscalationTargets = ["parent", "user"];
|
|
141
141
|
const isQuestionEscalationTarget = (value) => questionEscalationTargets.includes(value);
|
|
142
|
+
/**
|
|
143
|
+
* The trace-tool sets a DEFINED analyst may ask for — the exact group names agent-eval's
|
|
144
|
+
* `buildTraceToolsForGroup` accepts, restated here so the `define_analyst` JSON Schema can
|
|
145
|
+
* enumerate them for the model that writes one. Named, not free-form, because the group is the
|
|
146
|
+
* lens's cost ceiling: `all` grants seven trace tools, `discovery` grants three and no deep reads.
|
|
147
|
+
* A name eval does not know throws at registration, which is where the two lists are held together.
|
|
148
|
+
*/
|
|
149
|
+
const analystToolGroupNames = [
|
|
150
|
+
"all",
|
|
151
|
+
"discovery",
|
|
152
|
+
"discoveryAndRead",
|
|
153
|
+
"discoveryAndSearch",
|
|
154
|
+
"targeted",
|
|
155
|
+
"singleTrace"
|
|
156
|
+
];
|
|
157
|
+
/** Every bound `define_analyst` enforces before a definition reaches a registry.
|
|
158
|
+
*
|
|
159
|
+
* MOTIVE, stated as numbers, because a fence whose motive cannot be stated is over-engineering:
|
|
160
|
+
* a defined lens spends real model calls from the run's own account on every settle it is routed
|
|
161
|
+
* over, and its `instructions` are re-sent on every one of them. So the two fields that multiply
|
|
162
|
+
* — `instructions` bytes and `maxLlmCalls` — are the ones with hard ceilings, and the ceilings are
|
|
163
|
+
* twice agent-eval's own defaults (`maxIterations` 12, `maxLlmCalls` 8, `maxToolCalls` 48,
|
|
164
|
+
* `maxOutputChars` 10_000): enough headroom for a deeper question than the shipped lenses ask,
|
|
165
|
+
* not enough for one definition to become the dominant cost of a run. */
|
|
166
|
+
const ANALYST_DEFINITION_BOUNDS = Object.freeze({
|
|
167
|
+
idPattern: /^[a-z0-9][a-z0-9-]{1,63}$/,
|
|
168
|
+
maxDescriptionChars: 300,
|
|
169
|
+
maxAreaChars: 64,
|
|
170
|
+
maxQuestionChars: 1e3,
|
|
171
|
+
maxInstructionsChars: 2e4,
|
|
172
|
+
maxModelChars: 128,
|
|
173
|
+
maxIterations: 24,
|
|
174
|
+
maxLlmCalls: 16,
|
|
175
|
+
maxToolCalls: 96,
|
|
176
|
+
maxOutputChars: 2e4,
|
|
177
|
+
maxEvidenceCitations: 10,
|
|
178
|
+
/** Lenses ONE manager may define, counting those it defined in a PRIOR process of a durable run.
|
|
179
|
+
* This bounds MENU GROWTH, not spend: a definition costs nothing until `run_analyst` is called,
|
|
180
|
+
* so the failure it stops is a manager that keeps re-authoring a lens instead of running one —
|
|
181
|
+
* each definition adds a line every later `list_analysts` re-reads. Eight is more than the five
|
|
182
|
+
* calibrated lenses agent-eval ships. Per-run analyst SPEND is bounded by the conserved pool the
|
|
183
|
+
* engine draws from, not here. */
|
|
184
|
+
maxDefinitionsPerManager: 8
|
|
185
|
+
});
|
|
142
186
|
/** Producer-side cleanliness for the `finding` event. The findings payload is arbitrary analyst
|
|
143
187
|
* output, the digest a subscriber computes (RFC 8785) throws on ANY `undefined` value — nested
|
|
144
188
|
* included — and a throwing subscriber leaves the event invisible to EVERY subscriber. The
|
|
@@ -164,10 +208,163 @@ function canonicalFindingEvent(finding) {
|
|
|
164
208
|
};
|
|
165
209
|
}
|
|
166
210
|
}
|
|
211
|
+
/**
|
|
212
|
+
* Validate and BOUND one `define_analyst` argument.
|
|
213
|
+
*
|
|
214
|
+
* Every reason is collected, never short-circuited: a manager re-authoring from a partial list of
|
|
215
|
+
* complaints spends a turn per complaint. Numeric limits are CLAMPED rather than rejected — asking
|
|
216
|
+
* for 200 tool calls is a mis-estimate, not a malformed definition, and the accepted value is
|
|
217
|
+
* returned so the manager reads the ceiling it actually got.
|
|
218
|
+
*/
|
|
219
|
+
function parseAuthoredAnalystDefinition(raw) {
|
|
220
|
+
const issues = [];
|
|
221
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return { issues: [{
|
|
222
|
+
path: "definition",
|
|
223
|
+
message: "must be an object"
|
|
224
|
+
}] };
|
|
225
|
+
const o = raw;
|
|
226
|
+
const bounds = ANALYST_DEFINITION_BOUNDS;
|
|
227
|
+
const text = (field, max) => {
|
|
228
|
+
const value = o[field];
|
|
229
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
230
|
+
issues.push({
|
|
231
|
+
path: field,
|
|
232
|
+
message: "is required and must be a non-empty string"
|
|
233
|
+
});
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
const trimmed = value.trim();
|
|
237
|
+
if (trimmed.length > max) {
|
|
238
|
+
issues.push({
|
|
239
|
+
path: field,
|
|
240
|
+
message: `must be at most ${max} characters (received ${trimmed.length})`
|
|
241
|
+
});
|
|
242
|
+
return "";
|
|
243
|
+
}
|
|
244
|
+
return trimmed;
|
|
245
|
+
};
|
|
246
|
+
const id = text("id", 64);
|
|
247
|
+
if (id.length > 0 && !bounds.idPattern.test(id)) issues.push({
|
|
248
|
+
path: "id",
|
|
249
|
+
message: "must be 2-64 characters of lowercase letters, digits and hyphens, starting with a letter or digit"
|
|
250
|
+
});
|
|
251
|
+
const description = text("description", bounds.maxDescriptionChars);
|
|
252
|
+
const area = text("area", bounds.maxAreaChars);
|
|
253
|
+
const question = text("question", bounds.maxQuestionChars);
|
|
254
|
+
const instructions = text("instructions", bounds.maxInstructionsChars);
|
|
255
|
+
const toolGroup = o.toolGroup;
|
|
256
|
+
if (!analystToolGroupNames.includes(toolGroup)) issues.push({
|
|
257
|
+
path: "toolGroup",
|
|
258
|
+
message: `must be one of ${analystToolGroupNames.join(", ")}`
|
|
259
|
+
});
|
|
260
|
+
let model;
|
|
261
|
+
if (o.model !== void 0) if (typeof o.model !== "string" || o.model.trim().length === 0) issues.push({
|
|
262
|
+
path: "model",
|
|
263
|
+
message: "must be a non-empty string when present"
|
|
264
|
+
});
|
|
265
|
+
else if (o.model.length > bounds.maxModelChars) issues.push({
|
|
266
|
+
path: "model",
|
|
267
|
+
message: `must be at most ${bounds.maxModelChars} characters`
|
|
268
|
+
});
|
|
269
|
+
else model = o.model.trim();
|
|
270
|
+
const clampedLimit = (field, max, source) => {
|
|
271
|
+
const value = source[field];
|
|
272
|
+
if (value === void 0) return void 0;
|
|
273
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
274
|
+
issues.push({
|
|
275
|
+
path: `limits.${field}`,
|
|
276
|
+
message: "must be a positive integer"
|
|
277
|
+
});
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
return Math.min(value, max);
|
|
281
|
+
};
|
|
282
|
+
let limits;
|
|
283
|
+
if (o.limits !== void 0) if (!o.limits || typeof o.limits !== "object" || Array.isArray(o.limits)) issues.push({
|
|
284
|
+
path: "limits",
|
|
285
|
+
message: "must be an object"
|
|
286
|
+
});
|
|
287
|
+
else {
|
|
288
|
+
const source = o.limits;
|
|
289
|
+
const knownLimits = /* @__PURE__ */ new Set([
|
|
290
|
+
"maxIterations",
|
|
291
|
+
"maxLlmCalls",
|
|
292
|
+
"maxToolCalls",
|
|
293
|
+
"maxOutputChars"
|
|
294
|
+
]);
|
|
295
|
+
for (const key of Object.keys(source)) if (!knownLimits.has(key)) issues.push({
|
|
296
|
+
path: `limits.${key}`,
|
|
297
|
+
message: `is not an investigation limit (accepted: ${[...knownLimits].join(", ")})`
|
|
298
|
+
});
|
|
299
|
+
const maxIterations = clampedLimit("maxIterations", bounds.maxIterations, source);
|
|
300
|
+
const maxLlmCalls = clampedLimit("maxLlmCalls", bounds.maxLlmCalls, source);
|
|
301
|
+
const maxToolCalls = clampedLimit("maxToolCalls", bounds.maxToolCalls, source);
|
|
302
|
+
const maxOutputChars = clampedLimit("maxOutputChars", bounds.maxOutputChars, source);
|
|
303
|
+
const present = {
|
|
304
|
+
...maxIterations === void 0 ? {} : { maxIterations },
|
|
305
|
+
...maxLlmCalls === void 0 ? {} : { maxLlmCalls },
|
|
306
|
+
...maxToolCalls === void 0 ? {} : { maxToolCalls },
|
|
307
|
+
...maxOutputChars === void 0 ? {} : { maxOutputChars }
|
|
308
|
+
};
|
|
309
|
+
if (Object.keys(present).length > 0) limits = present;
|
|
310
|
+
}
|
|
311
|
+
let minimumEvidenceCitations;
|
|
312
|
+
if (o.minimumEvidenceCitations !== void 0) {
|
|
313
|
+
const value = o.minimumEvidenceCitations;
|
|
314
|
+
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) issues.push({
|
|
315
|
+
path: "minimumEvidenceCitations",
|
|
316
|
+
message: "must be a positive integer"
|
|
317
|
+
});
|
|
318
|
+
else minimumEvidenceCitations = Math.min(value, bounds.maxEvidenceCitations);
|
|
319
|
+
}
|
|
320
|
+
const known = /* @__PURE__ */ new Set([
|
|
321
|
+
"id",
|
|
322
|
+
"description",
|
|
323
|
+
"area",
|
|
324
|
+
"question",
|
|
325
|
+
"instructions",
|
|
326
|
+
"toolGroup",
|
|
327
|
+
"model",
|
|
328
|
+
"limits",
|
|
329
|
+
"minimumEvidenceCitations"
|
|
330
|
+
]);
|
|
331
|
+
for (const key of Object.keys(o)) if (!known.has(key)) issues.push({
|
|
332
|
+
path: key,
|
|
333
|
+
message: `is not part of an authored analyst definition (accepted: ${[...known].join(", ")})`
|
|
334
|
+
});
|
|
335
|
+
if (issues.length > 0) return { issues };
|
|
336
|
+
return { definition: Object.freeze({
|
|
337
|
+
id,
|
|
338
|
+
description,
|
|
339
|
+
area,
|
|
340
|
+
question,
|
|
341
|
+
instructions,
|
|
342
|
+
toolGroup,
|
|
343
|
+
...model === void 0 ? {} : { model },
|
|
344
|
+
...limits === void 0 ? {} : { limits: Object.freeze(limits) },
|
|
345
|
+
...minimumEvidenceCitations === void 0 ? {} : { minimumEvidenceCitations }
|
|
346
|
+
}) };
|
|
347
|
+
}
|
|
167
348
|
/** Normalize the two spellings of an analyst-on-settle entry to the route form. */
|
|
168
349
|
function normalizeAnalyzeOnSettle(entry) {
|
|
169
350
|
return typeof entry === "string" ? { kind: entry } : entry;
|
|
170
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* Why one parent→child delivery did not land, in the words the MANAGER needs.
|
|
354
|
+
*
|
|
355
|
+
* `DownMessageDeliveryOutcome` is the machine code; this is the sentence beside it. A code alone
|
|
356
|
+
* left the manager to guess whether to retry, spawn, or give up — `already-settled` and
|
|
357
|
+
* `scope-stopped` look alike and need opposite moves. Every refusal a coordination verb returns
|
|
358
|
+
* carries a sentence like these, because the manager cannot see the state that produced it.
|
|
359
|
+
*/
|
|
360
|
+
const downMessageRefusalReasons = Object.freeze({
|
|
361
|
+
delivered: "the message reached the worker",
|
|
362
|
+
"unknown-worker": "no worker of this manager has that id — check observe_agent for the live ids",
|
|
363
|
+
"already-settled": "that worker already settled, so nothing can reach it — spawn a fresh worker for the follow-up work",
|
|
364
|
+
"runtime-has-no-inbox": "that worker runs on an executor with no inbox, so it can never receive a message — re-spawn it on a steerable executor if it must be corrected mid-run",
|
|
365
|
+
"scope-stopped": "this manager’s scope has stopped, so no further message may be sent from it",
|
|
366
|
+
"runtime-error": "the executor threw while accepting the message; the worker may or may not have received it"
|
|
367
|
+
});
|
|
171
368
|
/** Every cause at zero — a pre-flight publishes its whole ledger from the first read. */
|
|
172
369
|
function emptyPreflightCounts() {
|
|
173
370
|
return {
|
|
@@ -180,6 +377,29 @@ function emptyPreflightCounts() {
|
|
|
180
377
|
* MCP client request timeout so the call returns a `pending` liveness snapshot instead of erroring;
|
|
181
378
|
* the supervisor re-polls until the worker settles. */
|
|
182
379
|
const DEFAULT_AWAIT_EVENT_TIMEOUT_MS = 15e3;
|
|
380
|
+
/** Default and maximum bounds for one {@link CoordinationTools} journal read. The defaults are
|
|
381
|
+
* sized for a driver turn (a page a model can read and still have room to act on); the maxima
|
|
382
|
+
* bound what a single tool result may cost even when the caller asks for everything. */
|
|
383
|
+
const JOURNAL_READ_BOUNDS = Object.freeze({
|
|
384
|
+
defaultLimit: 50,
|
|
385
|
+
maxLimit: 500,
|
|
386
|
+
defaultMaxBytes: 16384,
|
|
387
|
+
maxMaxBytes: 262144
|
|
388
|
+
});
|
|
389
|
+
/** Every `CoordinationEvent` kind `read_journal` can return — the tool advertises this list in its
|
|
390
|
+
* JSON Schema AND filters on it, so the schema cannot promise a kind the filter drops. */
|
|
391
|
+
const journalEventKinds = [
|
|
392
|
+
"question",
|
|
393
|
+
"settled",
|
|
394
|
+
"finding",
|
|
395
|
+
"steer",
|
|
396
|
+
"answer",
|
|
397
|
+
"instruction",
|
|
398
|
+
"delivery-attempt",
|
|
399
|
+
"mail",
|
|
400
|
+
"escalation",
|
|
401
|
+
"analyst-defined"
|
|
402
|
+
];
|
|
183
403
|
/** The reserved coordination verb names — the complete set `createCoordinationTools` can emit
|
|
184
404
|
* (the analyst pair is conditional but still reserved). A driver's extra WORK tools must not
|
|
185
405
|
* collide with any of these, or it could no longer coordinate; callers validate eagerly against
|
|
@@ -202,8 +422,10 @@ const coordinationVerbNames = [
|
|
|
202
422
|
"ask_parent",
|
|
203
423
|
"submit_result",
|
|
204
424
|
"stop",
|
|
425
|
+
"read_journal",
|
|
205
426
|
"list_analysts",
|
|
206
|
-
"run_analyst"
|
|
427
|
+
"run_analyst",
|
|
428
|
+
"define_analyst"
|
|
207
429
|
];
|
|
208
430
|
/**
|
|
209
431
|
* The `CoordinationEvent` kinds a driver may name in `await_event`. The pull queue carries the
|
|
@@ -969,6 +1191,14 @@ function createCoordinationTools(opts) {
|
|
|
969
1191
|
type: "mail",
|
|
970
1192
|
...ev.mail
|
|
971
1193
|
};
|
|
1194
|
+
if (ev.type === "escalation") return {
|
|
1195
|
+
type: "escalation",
|
|
1196
|
+
...ev.escalation
|
|
1197
|
+
};
|
|
1198
|
+
if (ev.type === "analyst-defined") return {
|
|
1199
|
+
type: "analyst-defined",
|
|
1200
|
+
...ev.analyst
|
|
1201
|
+
};
|
|
972
1202
|
return {
|
|
973
1203
|
type: ev.type,
|
|
974
1204
|
...ev.down
|
|
@@ -1032,6 +1262,13 @@ function createCoordinationTools(opts) {
|
|
|
1032
1262
|
}, { priority: urgencyPriority(record.question.urgency) });
|
|
1033
1263
|
return record.question;
|
|
1034
1264
|
};
|
|
1265
|
+
/** The refusal every `answer_question` path returns for an id this manager has no question for.
|
|
1266
|
+
* A THROW here reaches the manager as a transport error with no vocabulary it can act on, and
|
|
1267
|
+
* this is the verb the `no-parent` guidance tells it to use — so it names the condition. */
|
|
1268
|
+
const unknownQuestion = (questionId) => ({
|
|
1269
|
+
error: "unknown-question",
|
|
1270
|
+
reason: `this manager has no question with the id ${JSON.stringify(questionId)}; call list_questions for the ids you may decide`
|
|
1271
|
+
});
|
|
1035
1272
|
const decideQuestion = (questionId, decision) => {
|
|
1036
1273
|
const idx = questions.findIndex((q) => q.id === questionId);
|
|
1037
1274
|
if (idx < 0) throw new Error(`unknown questionId ${JSON.stringify(questionId)}`);
|
|
@@ -1045,6 +1282,36 @@ function createCoordinationTools(opts) {
|
|
|
1045
1282
|
questions[idx] = next;
|
|
1046
1283
|
return next;
|
|
1047
1284
|
};
|
|
1285
|
+
const escalations = [...opts.priorEscalations ?? []];
|
|
1286
|
+
const escalate = async (question) => {
|
|
1287
|
+
let outcome;
|
|
1288
|
+
if (opts.escalateQuestion === void 0) outcome = {
|
|
1289
|
+
delivered: false,
|
|
1290
|
+
reason: "this manager has no parent inbox in this run, so nothing above it can receive the question"
|
|
1291
|
+
};
|
|
1292
|
+
else try {
|
|
1293
|
+
outcome = await opts.escalateQuestion(question);
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
outcome = {
|
|
1296
|
+
delivered: false,
|
|
1297
|
+
reason: `the parent channel refused the question: ${error instanceof Error ? error.message : String(error)}`
|
|
1298
|
+
};
|
|
1299
|
+
}
|
|
1300
|
+
const record = Object.freeze({
|
|
1301
|
+
questionId: question.id,
|
|
1302
|
+
from: question.from,
|
|
1303
|
+
urgency: question.urgency,
|
|
1304
|
+
delivered: outcome.delivered,
|
|
1305
|
+
...outcome.delivered ? { to: outcome.to } : { reason: outcome.reason },
|
|
1306
|
+
at: Date.now()
|
|
1307
|
+
});
|
|
1308
|
+
escalations.push(record);
|
|
1309
|
+
await bus.publish({
|
|
1310
|
+
type: "escalation",
|
|
1311
|
+
escalation: record
|
|
1312
|
+
}, { queue: false });
|
|
1313
|
+
return record;
|
|
1314
|
+
};
|
|
1048
1315
|
const blockingQuestionsForStop = () => {
|
|
1049
1316
|
if (questionPolicy === "auto" || questionPolicy === "bubble") return [];
|
|
1050
1317
|
return questions.filter((q) => {
|
|
@@ -1053,6 +1320,165 @@ function createCoordinationTools(opts) {
|
|
|
1053
1320
|
return q.status !== "answered" && q.status !== "deferred";
|
|
1054
1321
|
});
|
|
1055
1322
|
};
|
|
1323
|
+
const redactJournalEvent = resolveRedactor(opts.redactJournal);
|
|
1324
|
+
const clampBound = (raw, field, fallback, max) => {
|
|
1325
|
+
if (raw === void 0) return fallback;
|
|
1326
|
+
if (typeof raw !== "number" || !Number.isInteger(raw)) throw new Error(`coordination tools: "${field}" must be an integer`);
|
|
1327
|
+
return Math.min(Math.max(raw, 1), max);
|
|
1328
|
+
};
|
|
1329
|
+
const journalKinds = (raw) => {
|
|
1330
|
+
if (raw === void 0) return void 0;
|
|
1331
|
+
if (!Array.isArray(raw)) throw new Error("coordination tools: \"kinds\" must be an array of event kinds");
|
|
1332
|
+
for (const kind of raw) if (!journalEventKinds.includes(kind)) throw new Error(`coordination tools: "kinds" accepts ${journalEventKinds.join(", ")}; received ${JSON.stringify(kind)}`);
|
|
1333
|
+
return new Set(raw);
|
|
1334
|
+
};
|
|
1335
|
+
const redactedRow = (record) => {
|
|
1336
|
+
try {
|
|
1337
|
+
return redactJournalEvent(detachedFrozen(record.event));
|
|
1338
|
+
} catch (error) {
|
|
1339
|
+
return {
|
|
1340
|
+
type: record.event.type,
|
|
1341
|
+
redactionFailed: error instanceof Error ? error.message : String(error)
|
|
1342
|
+
};
|
|
1343
|
+
}
|
|
1344
|
+
};
|
|
1345
|
+
const priorJournal = opts.priorJournal ?? [];
|
|
1346
|
+
const journalRows = () => {
|
|
1347
|
+
const rows = [];
|
|
1348
|
+
let row = 0;
|
|
1349
|
+
for (const record of priorJournal) rows.push({
|
|
1350
|
+
row: row++,
|
|
1351
|
+
seq: record.seq,
|
|
1352
|
+
at: record.at,
|
|
1353
|
+
priority: record.priority,
|
|
1354
|
+
prior: true,
|
|
1355
|
+
event: void 0,
|
|
1356
|
+
record
|
|
1357
|
+
});
|
|
1358
|
+
for (const record of bus.history()) rows.push({
|
|
1359
|
+
row: row++,
|
|
1360
|
+
seq: record.seq,
|
|
1361
|
+
at: record.at,
|
|
1362
|
+
priority: record.priority,
|
|
1363
|
+
event: void 0,
|
|
1364
|
+
record
|
|
1365
|
+
});
|
|
1366
|
+
return rows;
|
|
1367
|
+
};
|
|
1368
|
+
const readJournal = (raw) => {
|
|
1369
|
+
const a = raw === void 0 ? {} : obj(raw);
|
|
1370
|
+
const sinceRow = a.sinceRow === void 0 ? 0 : typeof a.sinceRow === "number" && Number.isInteger(a.sinceRow) && a.sinceRow >= 0 ? a.sinceRow : (() => {
|
|
1371
|
+
throw new Error("coordination tools: \"sinceRow\" must be a non-negative integer");
|
|
1372
|
+
})();
|
|
1373
|
+
const limit = clampBound(a.limit, "limit", JOURNAL_READ_BOUNDS.defaultLimit, JOURNAL_READ_BOUNDS.maxLimit);
|
|
1374
|
+
const maxBytes = clampBound(a.maxBytes, "maxBytes", JOURNAL_READ_BOUNDS.defaultMaxBytes, JOURNAL_READ_BOUNDS.maxMaxBytes);
|
|
1375
|
+
const kinds = journalKinds(a.kinds);
|
|
1376
|
+
const matching = journalRows().filter((candidate) => candidate.row >= sinceRow && (!kinds || kinds.has(candidate.record.event.type)));
|
|
1377
|
+
const entries = [];
|
|
1378
|
+
let usedBytes = 2;
|
|
1379
|
+
let index = 0;
|
|
1380
|
+
let bounded = false;
|
|
1381
|
+
for (; index < matching.length; index++) {
|
|
1382
|
+
if (entries.length >= limit) {
|
|
1383
|
+
bounded = true;
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
const { record, event: _placeholder, ...stamp } = matching[index];
|
|
1387
|
+
const row = {
|
|
1388
|
+
...stamp,
|
|
1389
|
+
event: redactedRow(record)
|
|
1390
|
+
};
|
|
1391
|
+
const bytes = Buffer.byteLength(safeJsonText(row), "utf8") + (entries.length === 0 ? 0 : 1);
|
|
1392
|
+
if (usedBytes + bytes > maxBytes) {
|
|
1393
|
+
bounded = true;
|
|
1394
|
+
if (entries.length > 0) break;
|
|
1395
|
+
const marker = {
|
|
1396
|
+
...stamp,
|
|
1397
|
+
event: { type: record.event.type },
|
|
1398
|
+
oversize: {
|
|
1399
|
+
type: record.event.type,
|
|
1400
|
+
bytes
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
entries.push(marker);
|
|
1404
|
+
usedBytes += Buffer.byteLength(safeJsonText(marker), "utf8");
|
|
1405
|
+
index++;
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
entries.push(row);
|
|
1409
|
+
usedBytes += bytes;
|
|
1410
|
+
}
|
|
1411
|
+
const last = entries[entries.length - 1];
|
|
1412
|
+
return {
|
|
1413
|
+
entries,
|
|
1414
|
+
nextRow: last === void 0 ? sinceRow : last.row + 1,
|
|
1415
|
+
remaining: matching.length - index,
|
|
1416
|
+
truncated: bounded,
|
|
1417
|
+
bounds: {
|
|
1418
|
+
sinceRow,
|
|
1419
|
+
limit,
|
|
1420
|
+
maxBytes,
|
|
1421
|
+
usedBytes
|
|
1422
|
+
},
|
|
1423
|
+
priorRows: priorJournal.length
|
|
1424
|
+
};
|
|
1425
|
+
};
|
|
1426
|
+
const definedAnalysts = [...opts.priorAnalystDefinitions ?? []];
|
|
1427
|
+
const analystMenu = () => [...opts.analysts?.kinds ?? [], ...definedAnalysts.map((record) => record.kind)];
|
|
1428
|
+
const maxDefinedAnalysts = opts.maxDefinedAnalysts ?? ANALYST_DEFINITION_BOUNDS.maxDefinitionsPerManager;
|
|
1429
|
+
const defineAnalyst = async (raw) => {
|
|
1430
|
+
const register = opts.analysts?.register;
|
|
1431
|
+
if (register === void 0) return {
|
|
1432
|
+
error: "authoring-unavailable",
|
|
1433
|
+
reason: "this run's analyst registry admits no authored lens"
|
|
1434
|
+
};
|
|
1435
|
+
if (maxDefinedAnalysts > 0 && definedAnalysts.length >= maxDefinedAnalysts) return {
|
|
1436
|
+
error: "max-defined-analysts",
|
|
1437
|
+
reason: `you have defined ${definedAnalysts.length} lenses, the cap for this manager; run the ones you have instead of defining more`,
|
|
1438
|
+
defined: definedAnalysts.map((record) => record.kind.id)
|
|
1439
|
+
};
|
|
1440
|
+
const parsed = parseAuthoredAnalystDefinition(raw);
|
|
1441
|
+
if ("issues" in parsed) return {
|
|
1442
|
+
error: "invalid-definition",
|
|
1443
|
+
reason: parsed.issues.map((issue) => `${issue.path} ${issue.message}`).join("; "),
|
|
1444
|
+
issues: parsed.issues
|
|
1445
|
+
};
|
|
1446
|
+
const { definition } = parsed;
|
|
1447
|
+
const taken = analystMenu().find((kind) => kind.id === definition.id);
|
|
1448
|
+
if (taken !== void 0) return {
|
|
1449
|
+
error: "duplicate-analyst",
|
|
1450
|
+
reason: `the lens id ${JSON.stringify(definition.id)} is already on the menu (${taken.description}); choose a different id or run the existing lens`
|
|
1451
|
+
};
|
|
1452
|
+
let kind;
|
|
1453
|
+
try {
|
|
1454
|
+
kind = await register(definition);
|
|
1455
|
+
} catch (error) {
|
|
1456
|
+
return {
|
|
1457
|
+
error: "register-refused",
|
|
1458
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
const record = Object.freeze({
|
|
1462
|
+
definition,
|
|
1463
|
+
kind: Object.freeze({
|
|
1464
|
+
id: kind.id,
|
|
1465
|
+
description: kind.description,
|
|
1466
|
+
area: kind.area
|
|
1467
|
+
}),
|
|
1468
|
+
digest: canonicalCandidateDigest(definition),
|
|
1469
|
+
definedAt: Date.now()
|
|
1470
|
+
});
|
|
1471
|
+
definedAnalysts.push(record);
|
|
1472
|
+
await bus.publish({
|
|
1473
|
+
type: "analyst-defined",
|
|
1474
|
+
analyst: record
|
|
1475
|
+
}, { queue: false });
|
|
1476
|
+
return {
|
|
1477
|
+
analyst: record.kind,
|
|
1478
|
+
digest: record.digest,
|
|
1479
|
+
defined: definedAnalysts.length
|
|
1480
|
+
};
|
|
1481
|
+
};
|
|
1056
1482
|
const maxLiveWorkers = opts.maxLiveWorkers;
|
|
1057
1483
|
const localLiveWorkerCount = () => opts.scope.view.nodes.filter((n) => isLiveNodeStatus(n.status)).length;
|
|
1058
1484
|
const sharedWorkerCapacity = () => {
|
|
@@ -1207,14 +1633,16 @@ function createCoordinationTools(opts) {
|
|
|
1207
1633
|
handler: async (raw) => {
|
|
1208
1634
|
const a = obj(raw);
|
|
1209
1635
|
const key = a.key === void 0 ? void 0 : str(a.key, "key");
|
|
1210
|
-
if (!(key !== void 0 && completedKeys.has(key)) && !usesTreeWideLimit() && maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers) return Promise.resolve({
|
|
1636
|
+
if (!(key !== void 0 && completedKeys.has(key)) && !usesTreeWideLimit() && maxLiveWorkers !== void 0 && maxLiveWorkers > 0 && liveWorkerCount() >= maxLiveWorkers) return Promise.resolve(((live) => ({
|
|
1211
1637
|
error: "max-live-workers",
|
|
1212
|
-
live:
|
|
1638
|
+
reason: `${live} worker${live === 1 ? " is" : "s are"} already live, which is this manager's ceiling — await_event until one settles, then spawn`,
|
|
1639
|
+
live,
|
|
1213
1640
|
freeSlots: freeWorkerSlots()
|
|
1214
|
-
});
|
|
1641
|
+
}))(liveWorkerCount()));
|
|
1215
1642
|
const parsedProfile = agentProfileSchema.safeParse(a.profile);
|
|
1216
1643
|
if (!parsedProfile.success) return Promise.resolve({
|
|
1217
1644
|
error: "invalid-profile",
|
|
1645
|
+
reason: `the profile you wrote is not a valid AgentProfile: ${parsedProfile.error.issues.map((issue) => `${issue.path.join(".") || "profile"} ${issue.message}`).join("; ")}`,
|
|
1218
1646
|
issues: parsedProfile.error.issues.map((issue) => ({
|
|
1219
1647
|
path: issue.path.join("."),
|
|
1220
1648
|
message: issue.message
|
|
@@ -1224,6 +1652,7 @@ function createCoordinationTools(opts) {
|
|
|
1224
1652
|
const continuity = resolveContinuity(parseContinuity(a.continuity), profile.name, key);
|
|
1225
1653
|
if ("error" in continuity) return Promise.resolve({
|
|
1226
1654
|
error: continuity.error,
|
|
1655
|
+
reason: continuity.hint,
|
|
1227
1656
|
hint: continuity.hint,
|
|
1228
1657
|
live: liveWorkerCount(),
|
|
1229
1658
|
freeSlots: freeWorkerSlots()
|
|
@@ -1241,6 +1670,7 @@ function createCoordinationTools(opts) {
|
|
|
1241
1670
|
preflightCounts[refusal.cause] += 1;
|
|
1242
1671
|
return {
|
|
1243
1672
|
error: "preflight-refused",
|
|
1673
|
+
reason: `the spawn pre-flight refused this profile (${refusal.cause}): ${refusal.detail}`,
|
|
1244
1674
|
cause: refusal.cause,
|
|
1245
1675
|
detail: refusal.detail,
|
|
1246
1676
|
live: liveWorkerCount(),
|
|
@@ -1306,6 +1736,7 @@ function createCoordinationTools(opts) {
|
|
|
1306
1736
|
...priorHistory
|
|
1307
1737
|
} : {
|
|
1308
1738
|
error: res.reason,
|
|
1739
|
+
reason: res.reason === "usd-unbudgeted" ? "this run's root budget declares no maxUsd, so a child budget naming maxUsd can never be admitted at any amount — spawn with a budget that omits maxUsd" : `the conserved pool refused this spawn (${String(res.reason)}); the run has no allocation left to give this worker`,
|
|
1309
1740
|
...res.reason === "usd-unbudgeted" ? { hint: "This run's root budget declares no maxUsd, so a child budget naming maxUsd can never be admitted — at any amount. Retrying with a smaller maxUsd will fail identically. Spawn with a budget that omits maxUsd, or ask the caller to give the run a root maxUsd." } : {},
|
|
1310
1741
|
live: liveWorkerCount(),
|
|
1311
1742
|
freeSlots: freeWorkerSlots()
|
|
@@ -1325,7 +1756,10 @@ function createCoordinationTools(opts) {
|
|
|
1325
1756
|
const node = opts.scope.view.nodes.find((n) => n.id === id);
|
|
1326
1757
|
if (!node) {
|
|
1327
1758
|
const resumed = opts.scope.resume?.view.nodes.find((n) => n.id === id);
|
|
1328
|
-
if (!resumed) return {
|
|
1759
|
+
if (!resumed) return {
|
|
1760
|
+
error: "unknown-worker",
|
|
1761
|
+
reason: `no worker of this manager has the id ${JSON.stringify(id)}; spawn_worker returns the ids you may observe`
|
|
1762
|
+
};
|
|
1329
1763
|
const output = resumed.outRef ? await opts.blobs.get(resumed.outRef) : void 0;
|
|
1330
1764
|
return {
|
|
1331
1765
|
...projectNodeEvidence(resumed, true),
|
|
@@ -1373,7 +1807,8 @@ function createCoordinationTools(opts) {
|
|
|
1373
1807
|
};
|
|
1374
1808
|
return {
|
|
1375
1809
|
delivered: false,
|
|
1376
|
-
|
|
1810
|
+
outcome: delivery.outcome,
|
|
1811
|
+
reason: downMessageRefusalReasons[delivery.outcome],
|
|
1377
1812
|
progress: readProgress(workerId) ?? null
|
|
1378
1813
|
};
|
|
1379
1814
|
}
|
|
@@ -1453,7 +1888,7 @@ function createCoordinationTools(opts) {
|
|
|
1453
1888
|
if (typeof a.answer === "string" && a.answer.length > 0) {
|
|
1454
1889
|
const answer = a.answer;
|
|
1455
1890
|
const pendingQuestion = questions.find((question) => question.id === questionId);
|
|
1456
|
-
if (pendingQuestion === void 0)
|
|
1891
|
+
if (pendingQuestion === void 0) return unknownQuestion(questionId);
|
|
1457
1892
|
const interrupt = pendingQuestion.urgency === "blocks-run" || pendingQuestion.urgency === "blocks-step";
|
|
1458
1893
|
const authorized = authorizeInstruction("answer", pendingQuestion.from, answer, interrupt, questionId);
|
|
1459
1894
|
await recordInstruction(authorized);
|
|
@@ -1469,23 +1904,44 @@ function createCoordinationTools(opts) {
|
|
|
1469
1904
|
by: typeof a.by === "string" && a.by.length > 0 ? a.by : "user"
|
|
1470
1905
|
}) : pendingQuestion,
|
|
1471
1906
|
delivered: delivery.delivered,
|
|
1472
|
-
...delivery.delivered ? {} : {
|
|
1907
|
+
...delivery.delivered ? {} : {
|
|
1908
|
+
outcome: delivery.outcome,
|
|
1909
|
+
reason: downMessageRefusalReasons[delivery.outcome]
|
|
1910
|
+
}
|
|
1473
1911
|
};
|
|
1474
1912
|
}
|
|
1475
|
-
if (typeof a.deferReason === "string" && a.deferReason.length > 0)
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1913
|
+
if (typeof a.deferReason === "string" && a.deferReason.length > 0) {
|
|
1914
|
+
if (!questions.some((question) => question.id === questionId)) return Promise.resolve(unknownQuestion(questionId));
|
|
1915
|
+
return Promise.resolve({ question: decideQuestion(questionId, {
|
|
1916
|
+
kind: "defer",
|
|
1917
|
+
reason: a.deferReason
|
|
1918
|
+
}) });
|
|
1919
|
+
}
|
|
1479
1920
|
if (typeof a.escalateTo === "string" && a.escalateTo.length > 0) {
|
|
1480
|
-
if (!isQuestionEscalationTarget(a.escalateTo))
|
|
1921
|
+
if (!isQuestionEscalationTarget(a.escalateTo)) return Promise.resolve({
|
|
1922
|
+
error: "invalid-escalation-target",
|
|
1923
|
+
reason: `escalateTo must be one of ${questionEscalationTargets.join(", ")}; received ${JSON.stringify(a.escalateTo)}`
|
|
1924
|
+
});
|
|
1925
|
+
if (!questions.some((question) => question.id === questionId)) return Promise.resolve(unknownQuestion(questionId));
|
|
1481
1926
|
const escalateReason = typeof a.escalateReason === "string" && a.escalateReason.length > 0 ? a.escalateReason : "driver escalated";
|
|
1482
|
-
|
|
1927
|
+
const decided = decideQuestion(questionId, {
|
|
1483
1928
|
kind: "escalate",
|
|
1484
1929
|
to: a.escalateTo,
|
|
1485
1930
|
reason: escalateReason
|
|
1486
|
-
})
|
|
1931
|
+
});
|
|
1932
|
+
if (a.escalateTo !== "parent") return Promise.resolve({ question: decided });
|
|
1933
|
+
return escalate(decided).then((escalation) => ({
|
|
1934
|
+
question: decided,
|
|
1935
|
+
escalated: escalation.delivered,
|
|
1936
|
+
outcome: escalation.delivered ? "queued-for-parent" : "no-parent",
|
|
1937
|
+
reason: escalation.delivered ? `the question is held by ${escalation.to}` : escalation.reason,
|
|
1938
|
+
...escalation.delivered ? {} : { guidance: "No inbox above this manager is configured to receive it, so nothing will route an answer back to you. The question stays on the run record for anyone watching. Do not BLOCK on it: answer_question it yourself, or answer_question with deferReason to record that it stays open." }
|
|
1939
|
+
}));
|
|
1487
1940
|
}
|
|
1488
|
-
|
|
1941
|
+
return Promise.resolve({
|
|
1942
|
+
error: "no-decision",
|
|
1943
|
+
reason: "a decision is required: pass `answer` to answer the question, `deferReason` to record that it stays open, or `escalateTo` to hand it further up"
|
|
1944
|
+
});
|
|
1489
1945
|
}
|
|
1490
1946
|
},
|
|
1491
1947
|
{
|
|
@@ -1525,7 +1981,7 @@ function createCoordinationTools(opts) {
|
|
|
1525
1981
|
handler: async (raw) => {
|
|
1526
1982
|
const a = obj(raw);
|
|
1527
1983
|
const from = str(a.from, "from");
|
|
1528
|
-
|
|
1984
|
+
const q = await emitNewQuestion(addQuestion({
|
|
1529
1985
|
from,
|
|
1530
1986
|
level: level(a.level),
|
|
1531
1987
|
question: str(a.question, "question"),
|
|
@@ -1535,7 +1991,15 @@ function createCoordinationTools(opts) {
|
|
|
1535
1991
|
kind: "escalate",
|
|
1536
1992
|
to: "parent",
|
|
1537
1993
|
reason: "asked parent"
|
|
1538
|
-
}))
|
|
1994
|
+
}));
|
|
1995
|
+
const escalation = await escalate(q);
|
|
1996
|
+
return {
|
|
1997
|
+
question: q,
|
|
1998
|
+
escalated: escalation.delivered,
|
|
1999
|
+
outcome: escalation.delivered ? "queued-for-parent" : "no-parent",
|
|
2000
|
+
reason: escalation.delivered ? `the question is held by ${escalation.to}` : escalation.reason,
|
|
2001
|
+
...escalation.delivered ? {} : { guidance: "Nobody above this manager received the question. It is journaled for the operator, and YOU are the last decider: answer_question it yourself, or answer_question with deferReason to record that it stays open. Do not wait for an answer." }
|
|
2002
|
+
};
|
|
1539
2003
|
}
|
|
1540
2004
|
},
|
|
1541
2005
|
...deliverable ? [{
|
|
@@ -1561,14 +2025,17 @@ function createCoordinationTools(opts) {
|
|
|
1561
2025
|
if (!Object.hasOwn(a, "result")) throw new Error("submit_result: \"result\" is required");
|
|
1562
2026
|
const result = structuredClone(a.result);
|
|
1563
2027
|
let accepted = false;
|
|
2028
|
+
let thrown;
|
|
1564
2029
|
try {
|
|
1565
2030
|
accepted = await deliverable.check(result) === true;
|
|
1566
|
-
} catch {
|
|
2031
|
+
} catch (error) {
|
|
1567
2032
|
accepted = false;
|
|
2033
|
+
thrown = error instanceof Error ? error.message : String(error);
|
|
1568
2034
|
}
|
|
1569
2035
|
if (!accepted) return {
|
|
1570
2036
|
accepted: false,
|
|
1571
|
-
stop: false
|
|
2037
|
+
stop: false,
|
|
2038
|
+
reason: thrown === void 0 ? `the independent check did not pass on this result${deliverable.describe ? `. Expected: ${deliverable.describe}` : ""}` : `the independent check THREW, so nothing was accepted: ${thrown}. This is a fault in the check, not necessarily in your result — report it rather than resubmitting unchanged`
|
|
1572
2039
|
};
|
|
1573
2040
|
if (submitted) return {
|
|
1574
2041
|
accepted: true,
|
|
@@ -1598,28 +2065,73 @@ function createCoordinationTools(opts) {
|
|
|
1598
2065
|
},
|
|
1599
2066
|
handler: (raw) => {
|
|
1600
2067
|
const blocking = blockingQuestionsForStop();
|
|
1601
|
-
if (blocking.length)
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
2068
|
+
if (blocking.length) {
|
|
2069
|
+
const unheard = blocking.filter((question) => escalations.some((record) => record.questionId === question.id && record.delivered === false));
|
|
2070
|
+
return Promise.resolve({
|
|
2071
|
+
stopped: false,
|
|
2072
|
+
error: "unresolved-blocking-questions",
|
|
2073
|
+
reason: `${blocking.length} blocking question${blocking.length === 1 ? "" : "s"} ${blocking.length === 1 ? "is" : "are"} still undecided` + (unheard.length > 0 ? `, and ${unheard.length} of them reached no parent — nothing above this manager will answer ${unheard.length === 1 ? "it" : "them"}. Decide ${unheard.length === 1 ? "it" : "them"} with answer_question (answer, or deferReason to record that it stays open) and stop again.` : ". Decide each with answer_question (answer, deferReason, or escalateTo) and stop again."),
|
|
2074
|
+
questions: blocking,
|
|
2075
|
+
...unheard.length > 0 ? { unheardQuestionIds: unheard.map((q) => q.id) } : {}
|
|
2076
|
+
});
|
|
2077
|
+
}
|
|
1606
2078
|
stopped = true;
|
|
1607
2079
|
const r = obj(raw).reason;
|
|
1608
2080
|
reason = typeof r === "string" ? r : void 0;
|
|
1609
2081
|
notifyStop();
|
|
1610
2082
|
return Promise.resolve({ stopped: true });
|
|
1611
2083
|
}
|
|
2084
|
+
},
|
|
2085
|
+
{
|
|
2086
|
+
name: "read_journal",
|
|
2087
|
+
description: [
|
|
2088
|
+
"Re-read YOUR OWN coordination journal: every spawn you made, every worker that settled,",
|
|
2089
|
+
"every question raised or decided, every steer you authorized, and every analyst finding —",
|
|
2090
|
+
"oldest first, on this node only, including what you did before a restart. Read it before",
|
|
2091
|
+
"deciding what to do next, so you can see what you already tried. Bounded: page with the",
|
|
2092
|
+
"returned `nextRow`, and check `truncated` before concluding you have read everything."
|
|
2093
|
+
].join(" "),
|
|
2094
|
+
inputSchema: {
|
|
2095
|
+
type: "object",
|
|
2096
|
+
properties: {
|
|
2097
|
+
sinceRow: {
|
|
2098
|
+
type: "integer",
|
|
2099
|
+
minimum: 0,
|
|
2100
|
+
description: "First journal row to return (inclusive, 0 = the first record of the run). Pass the previous call’s `nextRow` to continue."
|
|
2101
|
+
},
|
|
2102
|
+
kinds: {
|
|
2103
|
+
type: "array",
|
|
2104
|
+
items: {
|
|
2105
|
+
type: "string",
|
|
2106
|
+
enum: [...journalEventKinds]
|
|
2107
|
+
},
|
|
2108
|
+
description: "Return only these event kinds. Omit for every kind."
|
|
2109
|
+
},
|
|
2110
|
+
limit: {
|
|
2111
|
+
type: "integer",
|
|
2112
|
+
minimum: 1,
|
|
2113
|
+
description: `Maximum rows to return. Default ${JOURNAL_READ_BOUNDS.defaultLimit}; anything above ${JOURNAL_READ_BOUNDS.maxLimit} is clamped to it.`
|
|
2114
|
+
},
|
|
2115
|
+
maxBytes: {
|
|
2116
|
+
type: "integer",
|
|
2117
|
+
minimum: 1,
|
|
2118
|
+
description: `Byte budget for the returned rows. Default ${JOURNAL_READ_BOUNDS.defaultMaxBytes}; anything above ${JOURNAL_READ_BOUNDS.maxMaxBytes} is clamped to it.`
|
|
2119
|
+
}
|
|
2120
|
+
},
|
|
2121
|
+
additionalProperties: false
|
|
2122
|
+
},
|
|
2123
|
+
handler: async (raw) => readJournal(raw)
|
|
1612
2124
|
}
|
|
1613
2125
|
];
|
|
1614
2126
|
if (opts.analysts) {
|
|
1615
2127
|
tools.push({
|
|
1616
2128
|
name: "list_analysts",
|
|
1617
|
-
description: "List trace-analyst lenses available to run over a settled worker.",
|
|
2129
|
+
description: "List trace-analyst lenses available to run over a settled worker — the ones this run was given plus any you defined with define_analyst.",
|
|
1618
2130
|
inputSchema: {
|
|
1619
2131
|
type: "object",
|
|
1620
2132
|
properties: {}
|
|
1621
2133
|
},
|
|
1622
|
-
handler: () => Promise.resolve({ analysts:
|
|
2134
|
+
handler: () => Promise.resolve({ analysts: analystMenu() })
|
|
1623
2135
|
});
|
|
1624
2136
|
tools.push({
|
|
1625
2137
|
name: "run_analyst",
|
|
@@ -1639,8 +2151,14 @@ function createCoordinationTools(opts) {
|
|
|
1639
2151
|
const a = obj(raw);
|
|
1640
2152
|
const id = str(a.workerId, "workerId");
|
|
1641
2153
|
const node = nodeForWorker(id);
|
|
1642
|
-
if (!node) return {
|
|
1643
|
-
|
|
2154
|
+
if (!node) return {
|
|
2155
|
+
error: "unknown-worker",
|
|
2156
|
+
reason: `no worker of this manager has the id ${JSON.stringify(id)}; spawn_worker returns the ids you may analyze`
|
|
2157
|
+
};
|
|
2158
|
+
if (isLiveNodeStatus(node.status)) return {
|
|
2159
|
+
error: "worker-not-settled",
|
|
2160
|
+
reason: `worker ${JSON.stringify(id)} has not settled, so it has no trace to analyze yet — await_event until it settles, then run the lens`
|
|
2161
|
+
};
|
|
1644
2162
|
const trace = ledger.find((worker) => worker.id === id)?.trace ?? node.trace ?? {
|
|
1645
2163
|
status: "unavailable",
|
|
1646
2164
|
reason: "legacy-settlement-without-trace-evidence"
|
|
@@ -1650,13 +2168,105 @@ function createCoordinationTools(opts) {
|
|
|
1650
2168
|
store = await workerTraceAnalysisStore(trace, opts.blobs);
|
|
1651
2169
|
} catch (error) {
|
|
1652
2170
|
return {
|
|
1653
|
-
error:
|
|
2171
|
+
error: "trace-unreadable",
|
|
2172
|
+
reason: `the settled worker's trace evidence could not be read: ${error instanceof Error ? error.message : String(error)}`,
|
|
1654
2173
|
trace
|
|
1655
2174
|
};
|
|
1656
2175
|
}
|
|
1657
|
-
|
|
2176
|
+
const kind = str(a.kind, "kind");
|
|
2177
|
+
if (!analystMenu().some((entry) => entry.id === kind)) return {
|
|
2178
|
+
error: "unknown-analyst",
|
|
2179
|
+
reason: `no lens named ${JSON.stringify(kind)} is on this manager's menu; call list_analysts to see what you may run`
|
|
2180
|
+
};
|
|
2181
|
+
return { findings: await opts.analysts?.run(kind, store) };
|
|
1658
2182
|
}
|
|
1659
2183
|
});
|
|
2184
|
+
if (opts.analysts.register) tools.push({
|
|
2185
|
+
name: "define_analyst",
|
|
2186
|
+
description: [
|
|
2187
|
+
"Define a NEW trace-analyst lens for this run, then run it with run_analyst like any",
|
|
2188
|
+
"other. You write the research question, the policy for answering it, which trace tools",
|
|
2189
|
+
"it may use, and the model seat — data only, never code. The definition is journaled, so",
|
|
2190
|
+
"the lens you invented is reproducible and attributed to you."
|
|
2191
|
+
].join(" "),
|
|
2192
|
+
inputSchema: {
|
|
2193
|
+
type: "object",
|
|
2194
|
+
properties: {
|
|
2195
|
+
id: {
|
|
2196
|
+
type: "string",
|
|
2197
|
+
description: "Stable lens id: lowercase letters, digits and hyphens, 2-64 characters. It is the `kind` you pass to run_analyst and the analyst_id on every finding."
|
|
2198
|
+
},
|
|
2199
|
+
description: {
|
|
2200
|
+
type: "string",
|
|
2201
|
+
description: `One line naming what this lens looks for. At most ${ANALYST_DEFINITION_BOUNDS.maxDescriptionChars} characters.`
|
|
2202
|
+
},
|
|
2203
|
+
area: {
|
|
2204
|
+
type: "string",
|
|
2205
|
+
description: "The finding area this lens reports under, e.g. coordination, tool-use, cost."
|
|
2206
|
+
},
|
|
2207
|
+
question: {
|
|
2208
|
+
type: "string",
|
|
2209
|
+
description: `The research question, in your own words. At most ${ANALYST_DEFINITION_BOUNDS.maxQuestionChars} characters.`
|
|
2210
|
+
},
|
|
2211
|
+
instructions: {
|
|
2212
|
+
type: "string",
|
|
2213
|
+
description: `How to answer it: evidence rules, what counts as a finding, what to refuse to infer. At most ${ANALYST_DEFINITION_BOUNDS.maxInstructionsChars} characters.`
|
|
2214
|
+
},
|
|
2215
|
+
toolGroup: {
|
|
2216
|
+
type: "string",
|
|
2217
|
+
enum: [...analystToolGroupNames],
|
|
2218
|
+
description: "Smallest trace-tool set that can answer the question. discovery = overview/query/count only; discoveryAndRead adds deep reads; discoveryAndSearch adds regex; targeted and singleTrace narrow further; all grants every trace tool."
|
|
2219
|
+
},
|
|
2220
|
+
model: {
|
|
2221
|
+
type: "string",
|
|
2222
|
+
description: "The model seat this lens should run on. Omit for the run default. It is a request: registration may refuse a seat this run cannot serve."
|
|
2223
|
+
},
|
|
2224
|
+
limits: {
|
|
2225
|
+
type: "object",
|
|
2226
|
+
properties: {
|
|
2227
|
+
maxIterations: {
|
|
2228
|
+
type: "integer",
|
|
2229
|
+
minimum: 1,
|
|
2230
|
+
maximum: ANALYST_DEFINITION_BOUNDS.maxIterations
|
|
2231
|
+
},
|
|
2232
|
+
maxLlmCalls: {
|
|
2233
|
+
type: "integer",
|
|
2234
|
+
minimum: 1,
|
|
2235
|
+
maximum: ANALYST_DEFINITION_BOUNDS.maxLlmCalls
|
|
2236
|
+
},
|
|
2237
|
+
maxToolCalls: {
|
|
2238
|
+
type: "integer",
|
|
2239
|
+
minimum: 1,
|
|
2240
|
+
maximum: ANALYST_DEFINITION_BOUNDS.maxToolCalls
|
|
2241
|
+
},
|
|
2242
|
+
maxOutputChars: {
|
|
2243
|
+
type: "integer",
|
|
2244
|
+
minimum: 1,
|
|
2245
|
+
maximum: ANALYST_DEFINITION_BOUNDS.maxOutputChars
|
|
2246
|
+
}
|
|
2247
|
+
},
|
|
2248
|
+
additionalProperties: false,
|
|
2249
|
+
description: "Investigation ceilings. Values above the maximum are clamped, not refused."
|
|
2250
|
+
},
|
|
2251
|
+
minimumEvidenceCitations: {
|
|
2252
|
+
type: "integer",
|
|
2253
|
+
minimum: 1,
|
|
2254
|
+
maximum: ANALYST_DEFINITION_BOUNDS.maxEvidenceCitations,
|
|
2255
|
+
description: "Distinct evidence citations required per finding. Default 1."
|
|
2256
|
+
}
|
|
2257
|
+
},
|
|
2258
|
+
required: [
|
|
2259
|
+
"id",
|
|
2260
|
+
"description",
|
|
2261
|
+
"area",
|
|
2262
|
+
"question",
|
|
2263
|
+
"instructions",
|
|
2264
|
+
"toolGroup"
|
|
2265
|
+
],
|
|
2266
|
+
additionalProperties: false
|
|
2267
|
+
},
|
|
2268
|
+
handler: async (raw) => defineAnalyst(raw)
|
|
2269
|
+
});
|
|
1660
2270
|
}
|
|
1661
2271
|
const abortWorker = (ref, reason) => {
|
|
1662
2272
|
const live = opts.scope.view.nodes.filter((node) => isLiveNodeStatus(node.status));
|
|
@@ -1688,6 +2298,8 @@ function createCoordinationTools(opts) {
|
|
|
1688
2298
|
submittedResult: () => submitted,
|
|
1689
2299
|
settled: () => ledger,
|
|
1690
2300
|
questions: () => questions,
|
|
2301
|
+
escalations: () => escalations,
|
|
2302
|
+
definedAnalysts: () => definedAnalysts,
|
|
1691
2303
|
drainResolved,
|
|
1692
2304
|
abortWorker,
|
|
1693
2305
|
...peerMail ? { peerMail } : {}
|
|
@@ -1785,6 +2397,8 @@ var FileCoordinationLog = class {
|
|
|
1785
2397
|
}
|
|
1786
2398
|
const byId = /* @__PURE__ */ new Map();
|
|
1787
2399
|
const findings = [];
|
|
2400
|
+
const escalations = [];
|
|
2401
|
+
const analystDefinitions = [];
|
|
1788
2402
|
const continuations = [];
|
|
1789
2403
|
const deliveryEvidence = [];
|
|
1790
2404
|
const mail = [];
|
|
@@ -1822,11 +2436,15 @@ var FileCoordinationLog = class {
|
|
|
1822
2436
|
});
|
|
1823
2437
|
} else if (ev.type === "instruction") continuations.push(ev.instruction);
|
|
1824
2438
|
else if (ev.type === "mail") mail.push(ev.mail);
|
|
2439
|
+
else if (ev.type === "escalation") escalations.push(ev.escalation);
|
|
2440
|
+
else if (ev.type === "analyst-defined") analystDefinitions.push(ev.analyst);
|
|
1825
2441
|
}
|
|
1826
2442
|
return {
|
|
1827
2443
|
...ownerId !== void 0 ? { ownerId } : {},
|
|
1828
2444
|
questions: [...byId.values()],
|
|
1829
2445
|
findings,
|
|
2446
|
+
escalations,
|
|
2447
|
+
analystDefinitions,
|
|
1830
2448
|
continuations,
|
|
1831
2449
|
deliveryEvidence,
|
|
1832
2450
|
mail,
|
|
@@ -1839,6 +2457,8 @@ function emptyPriorCoordination(ownerId) {
|
|
|
1839
2457
|
...ownerId !== void 0 ? { ownerId } : {},
|
|
1840
2458
|
questions: [],
|
|
1841
2459
|
findings: [],
|
|
2460
|
+
escalations: [],
|
|
2461
|
+
analystDefinitions: [],
|
|
1842
2462
|
continuations: [],
|
|
1843
2463
|
deliveryEvidence: [],
|
|
1844
2464
|
mail: [],
|
|
@@ -2447,6 +3067,15 @@ function createSteerAcknowledger(deps) {
|
|
|
2447
3067
|
}
|
|
2448
3068
|
} };
|
|
2449
3069
|
}
|
|
3070
|
+
/**
|
|
3071
|
+
* The OPERATOR-facing sentence for a steer outcome, written into the durable acknowledgement file.
|
|
3072
|
+
*
|
|
3073
|
+
* Deliberately separate from `downMessageRefusalReasons`, which is the MANAGER-facing sentence: one
|
|
3074
|
+
* says what happened for someone reading the run afterwards, the other tells a model what to do
|
|
3075
|
+
* next, and merging them would make one audience read the other's copy. They must nonetheless cover
|
|
3076
|
+
* the SAME outcomes, so `tests/kernel/refusal-reasons.test.ts` holds both maps total over
|
|
3077
|
+
* `DownMessageDeliveryOutcome` — a new code cannot land in only one.
|
|
3078
|
+
*/
|
|
2450
3079
|
function steerAcknowledgementDetail(outcome) {
|
|
2451
3080
|
switch (outcome.outcome) {
|
|
2452
3081
|
case "delivered": return "the owning manager delivered the steer to the exact live worker";
|
|
@@ -2753,9 +3382,13 @@ function driverAgent(opts) {
|
|
|
2753
3382
|
...opts.continuityByProfile ? { continuityByProfile: opts.continuityByProfile } : {},
|
|
2754
3383
|
...opts.preflightSpawn ? { preflightSpawn: opts.preflightSpawn } : {},
|
|
2755
3384
|
...opts.resolveSpawnProfile ? { resolveSpawnProfile: opts.resolveSpawnProfile } : {},
|
|
3385
|
+
...opts.escalateQuestion ? { escalateQuestion: opts.escalateQuestion } : {},
|
|
2756
3386
|
...opts.onEvent ? { onEvent: opts.onEvent } : {},
|
|
2757
3387
|
...opts.replaySettlements ? { replaySettlements: true } : {},
|
|
2758
|
-
...opts.priorCoordination?.questions.length ? { priorQuestions: opts.priorCoordination.questions } : {}
|
|
3388
|
+
...opts.priorCoordination?.questions.length ? { priorQuestions: opts.priorCoordination.questions } : {},
|
|
3389
|
+
...opts.priorCoordination?.escalations?.length ? { priorEscalations: opts.priorCoordination.escalations } : {},
|
|
3390
|
+
...opts.priorCoordination?.records.length ? { priorJournal: opts.priorCoordination.records } : {},
|
|
3391
|
+
...opts.priorCoordination?.analystDefinitions?.length ? { priorAnalystDefinitions: opts.priorCoordination.analystDefinitions } : {}
|
|
2759
3392
|
});
|
|
2760
3393
|
await coord.ready();
|
|
2761
3394
|
opts.onCoordinationTools?.(coord.tools);
|
|
@@ -3066,6 +3699,6 @@ function errMessage(e) {
|
|
|
3066
3699
|
return e instanceof Error ? e.message : String(e);
|
|
3067
3700
|
}
|
|
3068
3701
|
//#endregion
|
|
3069
|
-
export {
|
|
3702
|
+
export { questionEscalationTargets as A, analystToolGroupNames as C, downMessageRefusalReasons as D, createCoordinationTools as E, defaultToolDetectors as M, watchTrace as N, normalizeAnalyzeOnSettle as O, DEFAULT_AWAIT_EVENT_TIMEOUT_MS as S, coordinationVerbNames as T, renderAnytimeTable as _, allOf as a, FileCoordinationLog as b, createProgressTracker as c, progressStop as d, sampleFromSettled as f, plateauLength as g, bestSoFar as h, finalizeBestDelivered as i, createEventBus as j, parseAuthoredAnalystDefinition as k, noProgressFor as l, areaUnderCurve as m, createSteerAcknowledger as n, allWorkersStalled as o, anytimeReport as p, driverAgent as r, anyOf as s, createCancelAcknowledger as t, plateau as u, createFileRunContext as v, canonicalFindingEvent as w, ANALYST_DEFINITION_BOUNDS as x, createInMemoryRunContext as y };
|
|
3070
3703
|
|
|
3071
|
-
//# sourceMappingURL=coordination-driver-
|
|
3704
|
+
//# sourceMappingURL=coordination-driver-Bw0Prnim.js.map
|