@sema-agent/core 5.26.0 → 5.28.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.
Files changed (58) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/dist/agents/agent-transcript-tool.d.ts +5 -2
  3. package/dist/agents/agent-transcript-tool.js +2 -1
  4. package/dist/agents/send-message-tool.d.ts +4 -1
  5. package/dist/agents/subagent.d.ts +5 -2
  6. package/dist/core/checkpoint-store.d.ts +7 -2
  7. package/dist/core/hooks.d.ts +61 -4
  8. package/dist/core/hooks.js +37 -15
  9. package/dist/core/memory-engine/engine.d.ts +8 -5
  10. package/dist/core/memory-engine/engine.js +18 -6
  11. package/dist/core/memory-engine/file-backend.d.ts +144 -4
  12. package/dist/core/memory-engine/file-backend.js +304 -36
  13. package/dist/core/memory-engine/layout.d.ts +31 -2
  14. package/dist/core/memory-engine/layout.js +132 -8
  15. package/dist/core/memory-engine/types.d.ts +9 -1
  16. package/dist/core/memory-vector.d.ts +6 -1
  17. package/dist/core/memory-vector.js +14 -4
  18. package/dist/core/memory.js +1 -6
  19. package/dist/core/permission-rule-consent.d.ts +82 -8
  20. package/dist/core/permission-rule-consent.js +92 -1
  21. package/dist/core/permission-rule-model.d.ts +87 -6
  22. package/dist/core/permission-rule-model.js +79 -0
  23. package/dist/core/permission-rule-org.d.ts +22 -3
  24. package/dist/core/permission-rule-org.js +67 -20
  25. package/dist/core/permission-rule-store.js +2 -2
  26. package/dist/core/permission-rule-sync.d.ts +15 -1
  27. package/dist/core/permission-rule-sync.js +89 -47
  28. package/dist/core/runner/prepare-memory.js +14 -9
  29. package/dist/core/runner/prepare-task.d.ts +9 -3
  30. package/dist/core/runner/prepare-task.js +37 -11
  31. package/dist/core/runner/runtask.d.ts +8 -1
  32. package/dist/core/runner/runtask.js +8 -1
  33. package/dist/core/task-registry-agent.d.ts +13 -3
  34. package/dist/core/task-registry-agent.js +51 -21
  35. package/dist/core/task-registry-monitor.js +1 -1
  36. package/dist/core/task-registry-shared.d.ts +9 -0
  37. package/dist/core/task-registry.d.ts +6 -3
  38. package/dist/core/tool-policy.d.ts +44 -4
  39. package/dist/core/tool-policy.js +37 -3
  40. package/dist/core/tool-result-store.d.ts +108 -7
  41. package/dist/core/tool-result-store.js +95 -15
  42. package/dist/core/types.d.ts +115 -17
  43. package/dist/core/types.js +30 -1
  44. package/dist/engine/loop/types.d.ts +10 -3
  45. package/dist/index.d.ts +2 -2
  46. package/dist/index.js +1 -1
  47. package/dist/orchestration/run-workflow-tool.d.ts +5 -3
  48. package/dist/orchestration/workflow.d.ts +9 -6
  49. package/dist/stores/file/checkpoint-store.d.ts +2 -1
  50. package/dist/stores/file/index.d.ts +1 -1
  51. package/dist/stores/file/tool-result-store.d.ts +41 -1
  52. package/dist/stores/file/tool-result-store.js +107 -19
  53. package/dist/tools/fs/fs-bash.d.ts +7 -0
  54. package/dist/tools/fs/fs-shared.d.ts +5 -0
  55. package/dist/tools/fs/fs-shared.js +11 -7
  56. package/dist/tools/fs/index.d.ts +6 -0
  57. package/dist/tools/fs/index.js +2 -0
  58. package/package.json +1 -1
@@ -48,8 +48,10 @@ export interface ToolResultStore {
48
48
  * (session, tool call) is legitimately written by two different tasks of one session — the
49
49
  * Runner shares one store across its tasks on purpose, and a BYOM brain may mint the same
50
50
  * tool-call id in both — so stamping a `taskId` there would turn a designed sharing case into a
51
- * refusal. Write sites whose ref carries the task (the background-agent and monitor spills key
52
- * on the handle id) state it; the offload/budget/projection sites state the session only;
51
+ * refusal. Every engine write site therefore states the session only (#167) including the
52
+ * background-agent and monitor spills, whose ref DOES carry the registry handle id: that id is
53
+ * an addressing segment of the ref, not an ownership coordinate a read-face caller can present
54
+ * (see {@link ToolResultProvenance.taskId} for the namespace rule);
53
55
  * - an entry stored **without** provenance (written by an older engine, or by a call site that has
54
56
  * none) stays UNOWNED: `put` never back-fills an owner onto it (that would be adoption by a party
55
57
  * that cannot prove it wrote it) and {@link ownerOf} keeps answering `undefined`, which a read face
@@ -72,12 +74,77 @@ export interface ToolResultStore {
72
74
  * that cannot answer this cannot back a host read face at all.
73
75
  */
74
76
  ownerOf?(ref: string): Promise<ToolResultProvenance | undefined> | ToolResultProvenance | undefined;
77
+ /**
78
+ * Erase every entry this store recorded as belonging to `sessionId` — the DELETE half of the same
79
+ * ownership coordinate {@link ownerOf} answers on. Without it a durable backend keeps a deleted
80
+ * session's offloaded and spilled results on its media indefinitely: `put`/`get` alone give a
81
+ * deployment no way to complete a session deletion, and the engine ships no scheduler that would
82
+ * eventually collect them.
83
+ *
84
+ * Rules every implementation follows:
85
+ * - **selection is by RECORDED PROVENANCE, never by parsing the ref.** A ref is an opaque handle:
86
+ * its session segment may be folded ({@link buildToolResultRef}), refs minted before the
87
+ * injective form decompose two ways (`tr_team_blue_x` is both ("team","blue_x") and
88
+ * ("team_blue","x")), and a caller may mint its own. Prefix-matching a ref therefore both
89
+ * misses rows and reaches rows of a NEIGHBOURING session, and over-deletion here destroys a
90
+ * live session's readable bytes. The owner recorded at the winning write is the one coordinate
91
+ * that means exactly one thing, and it is the same one a read face authorizes against;
92
+ * - **`taskId` is ignored in the match.** A stored `{sessionId, taskId}` is a NARROWING of the
93
+ * same session, not a different owner — write sites stamped one before the namespace rule
94
+ * settled the shape (#167), and such rows still belong to the session being deleted;
95
+ * - **an UNOWNED entry is never deleted.** A row whose write stated no provenance (an older
96
+ * engine, a call site that has none, or a two-object backend interrupted between publishing
97
+ * content and owner) belongs to no session this sweep can name, and it is still READABLE via
98
+ * `get` — deleting it on a guess would destroy another session's output. It is counted instead
99
+ * ({@link ToolResultDeletionReport.unattributable}) so the caller learns the deletion was
100
+ * incomplete rather than being told a clean "done";
101
+ * - **idempotent**: deleting a session with no entries is a no-op, and a repeat call after a
102
+ * partial (loudly failed) sweep resumes it;
103
+ * - **concurrency-tolerant**: an entry that disappears between enumeration and removal is honest
104
+ * absence, not an error.
105
+ *
106
+ * Typed OPTIONAL, and deliberately NOT part of the published contract kit
107
+ * (`toolResultStoreContract`): unlike `ownerOf`, a backend that cannot enumerate by owner is still
108
+ * a usable offload store, so its absence must stay a checkable fact rather than a contract breach.
109
+ * Present ⇒ the store can complete a session deletion; absent ⇒ the deployment owns that gap.
110
+ *
111
+ * Implementing it does NOT make a store `retention: "managed"` — that declaration promises the whole
112
+ * {@link import("./retention.js").ManagedRetentionCapability} (domain enumeration, tombstones,
113
+ * audit receipts, scheduled execution), of which this is one caller-driven erase.
114
+ */
115
+ deleteBySession?(sessionId: string): Promise<ToolResultDeletionReport> | ToolResultDeletionReport;
116
+ }
117
+ /**
118
+ * What a {@link ToolResultStore.deleteBySession} sweep actually did. A count, not a `void`, because the
119
+ * sweep has a documented INCOMPLETE outcome (unowned entries it must not delete) and a caller running a
120
+ * session-deletion face has to be able to tell "the session's results are gone" from "some rows survive
121
+ * that no session can clear".
122
+ */
123
+ export interface ToolResultDeletionReport {
124
+ /** Entries removed by THIS call. A retry of an already-swept session reports `0`, not an error. */
125
+ readonly deleted: number;
126
+ /**
127
+ * Entries the sweep could not attribute to ANY session and therefore left in place: stored without
128
+ * provenance, or with an owner record that is present but unreadable. Store-wide, not
129
+ * session-specific — an unowned row may or may not have been this session's, which is precisely why
130
+ * it cannot be deleted — so the same figure recurs on every session's sweep until a migration or an
131
+ * operator clears those rows.
132
+ */
133
+ readonly unattributable: number;
75
134
  }
76
135
  /**
77
136
  * Backlog #119 — WHO a stored tool result belongs to. Structured (not a single opaque string) because
78
137
  * the read face authorizes by COMPARING fields it already knows; `taskId` is present when the write site
79
138
  * has one (it is a narrowing of the session, never a substitute for it).
80
139
  *
140
+ * #167 (namespace rule): a stamped `taskId` must name a coordinate the read face's CALLER presents —
141
+ * the host's own task/run id — never an engine-internal registry handle id. A handle id lives in a
142
+ * namespace only the engine's in-memory registry can resolve, so an owner stamped with one compares
143
+ * unequal to every caller coordinate forever: the entry becomes readable by NOBODY across the wire
144
+ * (fail-closed, but pointlessly — the disclosure text tells the caller to read it back). The engine's
145
+ * own write sites all state the session only; the handle id, where a ref needs it, is an ADDRESSING
146
+ * segment of the ref, not part of the owner.
147
+ *
81
148
  * Deliberately NOT part of it: `toolCallId`. It is a retention/addressing coordinate, not an ownership
82
149
  * one — two different tool calls in one session are the same owner.
83
150
  */
@@ -262,6 +329,12 @@ export declare class InMemoryToolResultStore implements ToolResultStore {
262
329
  /** #119 — the owner recorded at the winning write; `undefined` for unknown AND for unowned entries,
263
330
  * which a read face treats identically (fail-closed). */
264
331
  ownerOf(ref: string): ToolResultProvenance | undefined;
332
+ /** Erase this session's entries, selected on the owner recorded at the winning write (the interface
333
+ * states the rules). Implemented here as well as on the durable backends so the bundled pair does
334
+ * not answer the same operation two ways: the entry holds content and owner as ONE value, so the
335
+ * same provenance match applies with none of the file backend's two-object bookkeeping. Deleting
336
+ * from a Map while iterating it is defined (a removed key is simply not revisited). */
337
+ deleteBySession(sessionId: string): ToolResultDeletionReport;
265
338
  /** design/80 D-2: true when NOTHING has been offloaded — a durable suspend can then proceed safely even on
266
339
  * this in-memory store, because a cross-replica resume has no offloaded result to deref to null. */
267
340
  isEmpty(): boolean;
@@ -316,12 +389,35 @@ export declare function isVolatileOffloadStore(store: ToolResultStore): boolean;
316
389
  export declare const OFFLOAD_TOOL_NAME = "ReadToolResult";
317
390
  /**
318
391
  * RB-469-d — the runner's clear-with-offload persist, as ONE construction point (the closure used to
319
- * live inline in prepare-task). Fire-and-forget by design (the caller returns the ref synchronously),
320
- * but a failed put no longer strands the model with a ref pointing at nothing: a tiny lost-marker is
321
- * written under the same ref so the page-back face reports the true cause (C6 "lost at write" and
322
- * "expired" are different answers). If the marker write fails too, the generic miss remains.
392
+ * live inline in prepare-task). Fire-and-forget by design (the caller returns the ref synchronously).
393
+ *
394
+ * #167 (A-025.17) a failed put REPORTS the loss, it never writes under the ref. The previous arm
395
+ * wrote a lost-marker row under the SAME ref "so the page-back face reports the true cause", and that
396
+ * one write carried two defects:
397
+ * - **revival**: the put and a session purge can race. When the put loses, the marker write lands
398
+ * AFTER the purge — re-inserting a row into a session the deployment just deleted, alive until the
399
+ * backend's own TTL. A failure arm must not be able to out-write a deletion;
400
+ * - **retry poisoning**: the per-request re-clear legitimately re-invokes this persist with the same
401
+ * (toolCallId, fullText) — the write-once retry path. With a marker occupying the ref, that
402
+ * retry's put of the REAL bytes hit the write-once no-op (same owner ⇒ keep what is there), so a
403
+ * transient first failure served the marker forever even though the full text was still in hand.
404
+ * Now the failure arm writes NOTHING (a later persist call retries the real bytes against an empty
405
+ * ref) and announces the failed write loudly instead: through the deployment's structured notice sink
406
+ * when one is wired, else `console.warn` (the loud-bad-value announcement dialect — a silent catch
407
+ * here would be a silent data loss). The announcement claims only what this arm can know: THIS
408
+ * attempt stored nothing — the same (toolCallId, fullText) persist recurs per request (write-once
409
+ * idempotent re-put), so an EARLIER attempt may already have landed the row and a rejected re-put is
410
+ * then no loss at all; declaring "the ref is empty" here would fabricate a data-loss incident on a
411
+ * transient outage. Page-back of a ref no attempt ever landed serves the generic miss, which is
412
+ * honest: nothing is stored under it.
323
413
  */
324
- export declare function createOffloadPersist(store: ToolResultStore, sessionId: string): (toolCallId: string, fullText: string) => string;
414
+ export declare function createOffloadPersist(store: ToolResultStore, sessionId: string,
415
+ /** Structured sink for the loss announcement, delivered through the shared guarded form
416
+ * (`deliverEngineNotice`, #170): a wired FUNCTION seat REPLACES the console line, swallow-guarded
417
+ * (a throwing/rejecting sink never turns an announcement into a failure); a present NON-function
418
+ * seat falls back to `console.warn` plus the once-per-process seat-defect announcement; absent ⇒
419
+ * `console.warn`. */
420
+ onNotice?: (notice: import("./types.js").EngineNotice) => void): (toolCallId: string, fullText: string) => string;
325
421
  /** Backlog #119 — build the provenance a write site records, from the two coordinates every write site
326
422
  * already has in hand. `taskId` is omitted (not `undefined`-valued) when the run declares none, so the
327
423
  * stored shape compares equal across a durable round-trip. */
@@ -388,6 +484,11 @@ export declare function buildPreview(full: string, ref: string, sizes?: {
388
484
  * wake needs a durable store (the default in-memory store loses the full text — the preview still stands).
389
485
  * Image blocks are left untouched; only text is offloaded. Forwards the `onUpdate` progress callback so a
390
486
  * wrapped (e.g. MCP) streaming tool isn't broken.
487
+ *
488
+ * Backlog #175 — a failing store NEVER turns the wrapped tool's success into a failure: the content face
489
+ * falls back to the original (un-offloaded) result, and the `details` walk to a ref-free notice per lost
490
+ * member (see the two arms for why the degrades differ). The wrapper adds a storage step to a tool that
491
+ * has already run; it must not be able to invalidate that run.
391
492
  */
392
493
  export declare function withToolResultOffload(tool: AgentTool, store: ToolResultStore, thresholdChars: number, sessionId: string,
393
494
  /** RB-374① — LIVE accessor for the session's currently CALLABLE tool set (evaluated per offload,
@@ -1,6 +1,7 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { Type } from "typebox";
3
3
  import { defineTool, errorResult } from "./tools.js";
4
+ import { deliverEngineNotice } from "./types.js";
4
5
  import { TOOL_SEARCH_NAME } from "./runner/tool-disclosure.js";
5
6
  export const TOOL_RESULT_REF_CONFLICT_CODE = "tool_result.ref_conflict";
6
7
  export class ToolResultRefConflictError extends Error {
@@ -94,6 +95,22 @@ export class InMemoryToolResultStore {
94
95
  ownerOf(ref) {
95
96
  return this.map.get(ref)?.provenance;
96
97
  }
98
+ deleteBySession(sessionId) {
99
+ let deleted = 0;
100
+ let unattributable = 0;
101
+ for (const [ref, entry] of this.map) {
102
+ if (entry.provenance === undefined) {
103
+ unattributable++;
104
+ continue;
105
+ }
106
+ if (entry.provenance.sessionId !== sessionId)
107
+ continue;
108
+ this.map.delete(ref);
109
+ this.totalChars -= entry.content.length;
110
+ deleted++;
111
+ }
112
+ return { deleted, unattributable };
113
+ }
97
114
  isEmpty() {
98
115
  return this.map.size === 0;
99
116
  }
@@ -152,14 +169,23 @@ export function isVolatileOffloadStore(store) {
152
169
  return store instanceof InMemoryToolResultStore;
153
170
  }
154
171
  export const OFFLOAD_TOOL_NAME = "ReadToolResult";
155
- export function createOffloadPersist(store, sessionId) {
172
+ export function createOffloadPersist(store, sessionId, onNotice) {
156
173
  const provenance = toolResultProvenanceOf(sessionId);
157
174
  return (toolCallId, fullText) => {
158
175
  const ref = buildToolResultRef(sessionId, toolCallId, toolResultContentSegment(fullText));
159
- void Promise.resolve(store.put(ref, fullText, provenance)).catch((err) => {
176
+ const reportFailedWrite = (err) => {
160
177
  const cause = err instanceof Error ? err.message : String(err);
161
- void Promise.resolve(store.put(ref, `[offload LOST at write time: ${cause} — the inline preview is all that survived]`, provenance)).catch(() => undefined);
162
- });
178
+ const message = `tool-result offload: persisting ref "${ref}" failed (${cause})this write attempt stored nothing; ` +
179
+ `unless an earlier attempt already stored this ref, the inline preview is all that survived. ` +
180
+ `A later re-clear of the same result retries the write.`;
181
+ deliverEngineNotice(onNotice, { code: "tool_result.offload_put_failed", message, detail: { ref, sessionId, cause } });
182
+ };
183
+ try {
184
+ void Promise.resolve(store.put(ref, fullText, provenance)).catch(reportFailedWrite);
185
+ }
186
+ catch (err) {
187
+ reportFailedWrite(err);
188
+ }
163
189
  return ref;
164
190
  };
165
191
  }
@@ -236,7 +262,12 @@ export function withToolResultOffload(tool, store, thresholdChars, sessionId, re
236
262
  if (full.length <= PREVIEW_HEAD_CHARS + PREVIEW_TAIL_CHARS)
237
263
  return withDetails(res);
238
264
  const ref = buildToolResultRef(sessionId, toolCallId, toolResultContentSegment(full));
239
- await store.put(ref, full, provenance);
265
+ try {
266
+ await store.put(ref, full, provenance);
267
+ }
268
+ catch {
269
+ return withDetails(res);
270
+ }
240
271
  const images = res.content.filter((b) => b.type !== "text");
241
272
  return withDetails({ ...res, content: [{ type: "text", text: buildPreview(full, ref, undefined, reachableTools?.()) }, ...images] });
242
273
  };
@@ -249,19 +280,34 @@ export function isOffloadedDetailReplacement(s) {
249
280
  return s.includes(`\n${OFFLOADED_DETAIL_NOTICE_PREFIX}`);
250
281
  }
251
282
  async function offloadOversizedDetailStrings(details, store, thresholdChars, sessionId, toolCallId, provenance) {
252
- const puts = [];
253
- const onStack = new Set();
254
283
  const isPlainObject = (v) => {
255
284
  if (typeof v !== "object" || v === null)
256
285
  return false;
257
286
  const p = Object.getPrototypeOf(v);
258
287
  return p === Object.prototype || p === null;
259
288
  };
289
+ const onStack = new Set();
290
+ const pending = [];
291
+ const rebuilt = new Set();
260
292
  const replace = (full, path) => {
261
293
  const detailRef = buildToolResultRef(sessionId, toolCallId, path, toolResultContentSegment(full));
262
- puts.push(Promise.resolve(store.put(detailRef, full, provenance)));
263
- return (`${full.slice(0, OFFLOADED_DETAIL_HEAD_CHARS)}\n` +
264
- `${OFFLOADED_DETAIL_NOTICE_PREFIX}${full.length} chars total; ref "${detailRef}"; the remainder is retained in this run's tool-result store and reads back through the deployment's tool-results face]`);
294
+ let done;
295
+ try {
296
+ done = Promise.resolve(store.put(detailRef, full, provenance));
297
+ }
298
+ catch (err) {
299
+ done = Promise.reject(err);
300
+ }
301
+ const marker = {};
302
+ pending.push({
303
+ marker,
304
+ done,
305
+ render: (stored) => `${full.slice(0, OFFLOADED_DETAIL_HEAD_CHARS)}\n${OFFLOADED_DETAIL_NOTICE_PREFIX}${full.length} chars total; ` +
306
+ (stored
307
+ ? `ref "${detailRef}"; the remainder is retained in this run's tool-result store and reads back through the deployment's tool-results face]`
308
+ : `persisting the remainder to this run's tool-result store failed or went unconfirmed, so no ref is offered — the text above is all this field carries]`),
309
+ });
310
+ return marker;
265
311
  };
266
312
  const memo = new Map();
267
313
  const walk = (v, segs) => {
@@ -275,14 +321,21 @@ async function offloadOversizedDetailStrings(details, store, thresholdChars, ses
275
321
  return done;
276
322
  onStack.add(v);
277
323
  let changed = false;
278
- const next = v.map((item, i) => {
324
+ const len = v.length;
325
+ const next = new Array(len);
326
+ for (let i = 0; i < len; i++) {
327
+ if (!(i in v))
328
+ continue;
329
+ const item = v[i];
279
330
  const w = walk(item, [...segs, i]);
280
331
  if (w !== item)
281
332
  changed = true;
282
- return w;
283
- });
333
+ Object.defineProperty(next, i, { value: w, enumerable: true, writable: true, configurable: true });
334
+ }
284
335
  onStack.delete(v);
285
336
  const result = changed ? next : v;
337
+ if (changed)
338
+ rebuilt.add(next);
286
339
  memo.set(v, result);
287
340
  return result;
288
341
  }
@@ -303,14 +356,41 @@ async function offloadOversizedDetailStrings(details, store, thresholdChars, ses
303
356
  }
304
357
  onStack.delete(v);
305
358
  const result = changed ? next : v;
359
+ if (changed)
360
+ rebuilt.add(next);
306
361
  memo.set(v, result);
307
362
  return result;
308
363
  }
309
364
  return v;
310
365
  };
311
366
  const value = walk(details, []);
312
- await Promise.all(puts);
313
- return { value };
367
+ const settled = await Promise.allSettled(pending.map((p) => p.done));
368
+ const notices = new Map();
369
+ pending.forEach((p, i) => void notices.set(p.marker, p.render(settled[i].status === "fulfilled")));
370
+ const seen = new Set();
371
+ const finalize = (node) => {
372
+ if (typeof node !== "object" || node === null)
373
+ return node;
374
+ const notice = notices.get(node);
375
+ if (notice !== undefined)
376
+ return notice;
377
+ if (!rebuilt.has(node) || seen.has(node))
378
+ return node;
379
+ seen.add(node);
380
+ if (Array.isArray(node)) {
381
+ for (let i = 0; i < node.length; i++) {
382
+ if (!Object.hasOwn(node, i))
383
+ continue;
384
+ Object.defineProperty(node, i, { value: finalize(node[i]), enumerable: true, writable: true, configurable: true });
385
+ }
386
+ return node;
387
+ }
388
+ const obj = node;
389
+ for (const k of Object.keys(obj))
390
+ Object.defineProperty(obj, k, { value: finalize(obj[k]), enumerable: true, writable: true, configurable: true });
391
+ return node;
392
+ };
393
+ return { value: finalize(value) };
314
394
  }
315
395
  export function createReadToolResultTool(store) {
316
396
  return defineTool({
@@ -855,8 +855,11 @@ export interface ToolExecuteContext {
855
855
  * `RunInternals.onForwardEvent`) to receive a SUBAGENT's live `task_progress` ticks that otherwise stay in the
856
856
  * child's ISOLATED stream. A delegation tool threads it to the child so nested progress bubbles to one sink. This
857
857
  * is a DISPLAY channel ONLY — the child stream is NEVER merged into the parent's MODEL context, and nothing
858
- * security-relevant consumes a forwarded event. Present ONLY when the deployment opted in; the Runner filters it
859
- * to `task_progress` (a tool cannot push other event types). ⚠️ Forwarded ticks are UNTRUSTED display hints any
858
+ * security-relevant consumes a forwarded event. Present ONLY when the deployment opted in. The tool-ctx wrapper
859
+ * passes `task_progress` unconditionally and when the deployment sets `forwardSubagentEvents: true`the
860
+ * transcript classes too (text_delta/reasoning_delta/tool_start/tool_end); other event types never cross it.
861
+ * The delegation lane's OWN tap is trusted and forwards the child's FULL event stream (bg frames tagged
862
+ * with bgAgentId). ⚠️ Forwarded ticks are UNTRUSTED display hints — any
860
863
  * tool holding this ctx could forge one, so a consumer validates `parentTaskId` against its known runs.
861
864
  */
862
865
  forwardEvent?: (event: TaskEvent) => void;
@@ -1731,6 +1734,11 @@ export interface TaskSpec {
1731
1734
  * surfaced to the operator as a config-phase warning. Protocol tools (MCP/A2A) can never collide
1732
1735
  * here: their wire names are namespaced (`mcp__<server>__…`) and a caller name containing `__`
1733
1736
  * is rejected at prepare.
1737
+ *
1738
+ * Mount point: the roster (with `defer`/`excludeTools`/`deferTools`) lives on this PER-TASK spec —
1739
+ * the Runner constructor's deps carry no tool roster. Plain-JS callers beware: an unrecognized key
1740
+ * passed to the constructor is dropped by ordinary object semantics (TypeScript callers get an
1741
+ * excess-property error), so a roster placed there never mounts and its `defer` flags never apply.
1734
1742
  */
1735
1743
  tools?: ToolSpec[];
1736
1744
  /**
@@ -1871,7 +1879,9 @@ export interface TaskSpec {
1871
1879
  * write-capable fs tools (CC's same gate); zero cost/behavior change otherwise. `false` opts out.
1872
1880
  */
1873
1881
  lspDiagnostics?: boolean;
1874
- /** In-process hooks (PreToolUse/PostToolUse/UserPromptSubmit) for this task. Overrides `RunnerDeps.hooks`. */
1882
+ /** In-process hooks for this task the full `Hooks` lifecycle seam (tool pre/post/failure/batch
1883
+ * interception, prompt gating, stop pushback, compaction taps, permission-denied observation; each
1884
+ * member's contract is on the interface). Overrides `RunnerDeps.hooks`. */
1875
1885
  hooks?: import("./hooks.js").Hooks;
1876
1886
  /**
1877
1887
  * design/45 **durable suspend-on-approval** (F4). When set AND a `CheckpointStore` is wired
@@ -2745,9 +2755,11 @@ export interface TaskResult {
2745
2755
  * How long (milliseconds) until the deployment usage window that stopped this task frees up — the wait
2746
2756
  * hint a scheduler needs to decide WHEN to re-submit, rather than polling.
2747
2757
  *
2748
- * **In-presence condition:** set on exactly one terminal — `errorCode === "usage.window_exhausted"`
2749
- * (either moment: the entry refusal that ran nothing, or the running terminal that could not suspend).
2750
- * Absent everywhere else, INCLUDING when a usage window did stop the run but a higher-ranked terminal
2758
+ * **In-presence condition:** set on exactly two terminals — `errorCode === "usage.window_exhausted"`
2759
+ * (either moment: the entry refusal that ran nothing, or the running terminal that could not suspend)
2760
+ * and `errorCode === "memory.admission_required"` (the prepare-throw path sets it there too see
2761
+ * assemble-result's accepted-code pair). Absent everywhere else, INCLUDING when a usage window did
2762
+ * stop the run but a higher-ranked terminal
2751
2763
  * named the result (a budget ceiling crossed on the same turn): the reported cause is then that other
2752
2764
  * code, and a wait filed under it would describe something the code does not name.
2753
2765
  *
@@ -3260,13 +3272,16 @@ export type TaskEvent = ({
3260
3272
  */
3261
3273
  structured?: unknown;
3262
3274
  /**
3263
- * Present iff {@link isError} is true AND the harness result's `details` carried a string `code` —
3264
- * a machine-readable discriminator for error frames, lifted so a consumer never has to parse the
3265
- * (contract-stable) result text. Engine-minted vocabulary today: `"tool.not_found"` (unknown tool
3266
- * name) and `"gate.parked"` (an abort short-circuit poisoned this call because a durable gate
3267
- * parked the batch the "Operation aborted" family). A tool's own error `details.code` (string)
3268
- * rides the same lift. Additive: absent on every error frame minted before this field existed,
3269
- * and on error results whose details carry no string code.
3275
+ * Present iff {@link isError} is true AND the harness result's `details` carried a string
3276
+ * discriminator read as `details.code` first, falling back to `details.errorKind` (each must be
3277
+ * a string; `code` wins when both are present, as the deliberate tool-chosen spelling). The
3278
+ * `errorKind` leg is what makes a LOOP-THROWN error's frame classifiable: the loop's thrown-error
3279
+ * fold and the resume legs write the discriminator under that name. Lifted so a consumer never
3280
+ * has to parse the (contract-stable) result text. Engine-minted vocabulary today includes
3281
+ * `"tool.not_found"` (unknown tool name) and `"gate.parked"` (an abort short-circuit poisoned
3282
+ * this call because a durable gate parked the batch — the "Operation aborted" family). Additive:
3283
+ * absent on error frames minted before this field existed, and on error results whose details
3284
+ * carry no string discriminator under either name.
3270
3285
  */
3271
3286
  errorCode?: string;
3272
3287
  /**
@@ -4156,6 +4171,51 @@ export interface ProjectMemoryLoad {
4156
4171
  contentHash: string | null;
4157
4172
  }>;
4158
4173
  }
4174
+ /**
4175
+ * A structured operator-facing notice ({@link RunnerDeps.onNotice}) — a fact the engine announces that
4176
+ * is neither an error nor part of the task result: today, a configured value that was discarded in
4177
+ * favor of another (the loud-bad-value discipline's announcement dialect). Structured so a host can
4178
+ * FORWARD it to its own user surface instead of losing it in process stderr.
4179
+ */
4180
+ export interface EngineNotice {
4181
+ /** Stable machine-readable family, dot-namespaced. Current families:
4182
+ * - `"config.env_timeout_discarded"` — a Bash timeout knob (option or env) held a value that is not
4183
+ * the value in force; `detail: { knob, raw, usedMs }`.
4184
+ * - `"config.materialize_env_discarded"` — `SEMA_TOOL_MATERIALIZE_STRATEGY` held a value outside the
4185
+ * closed set in a seat where it is not in force; `detail: { raw, specStrategy? }`.
4186
+ * - `"tool_result.offload_put_failed"` (#167) — a clear-with-offload persist's fire-and-forget put
4187
+ * failed; THIS attempt stored nothing (the failure arm reports, it never re-inserts under the
4188
+ * ref) — an earlier attempt of the same idempotent re-put may already have stored the row, so
4189
+ * the notice claims a failed write, not an empty ref; `detail: { ref, sessionId, cause }`.
4190
+ * Per-occurrence, not per-process-deduplicated: each failed write is a distinct fact. */
4191
+ code: string;
4192
+ /** The exact human-readable line the unwired build prints via `console.warn` — same words, one text. */
4193
+ message: string;
4194
+ /** Machine-readable facts of the notice (knob names, arriving values, values in force). */
4195
+ detail?: Record<string, unknown>;
4196
+ }
4197
+ /** Test seam (mirrors `__resetBashTimeoutAnnouncements`): never called by production code. */
4198
+ export declare function __resetMalformedNoticeSeatAnnouncement(): void;
4199
+ /**
4200
+ * The ONE delivery form behind every {@link RunnerDeps.onNotice} emission point (#170: the form was
4201
+ * triplicated across the emission stations, and every copy judged the seat with `!== undefined` — so a
4202
+ * PRESENT non-function seat (null, a config typo, an untyped host's JSON wiring) entered the wired arm,
4203
+ * threw `TypeError` on the call, and the swallow guard silenced BOTH channels at once). Contract:
4204
+ * · a FUNCTION seat REPLACES the console line (a host forwarding notices to its own surface must not
4205
+ * show every fact twice), swallow-guarded against both failure shapes the void-typed seat admits —
4206
+ * a synchronous throw and an async sink's rejected promise (unhandled, that rejection is a
4207
+ * process-level fault): a notice sink must never turn an announcement into a failure. Deliberately
4208
+ * NO console fallback on sink failure: loudness ownership transfers with the wiring, and a console
4209
+ * echo of a transient sink failure would double-send the fact. Corollary kept as-is: stations that
4210
+ * de-duplicate ledger the line BEFORE this call, so a true-function sink that throws can in
4211
+ * principle lose a ledgered line for the process lifetime — un-ledgering after the fact would race
4212
+ * an async sink's late rejection and re-announce (double-send) on transient failures.
4213
+ * · a PRESENT NON-function seat is a bad deployment value, and #123 (loud-bad-value law) forbids
4214
+ * folding it to silence: the notice itself falls back to `console.warn` (no line is lost), and the
4215
+ * seat defect — the one fact every fallback would otherwise repeat — is announced once per process.
4216
+ * · an ABSENT seat prints the historic `console.warn` line verbatim (byte-compat loudness).
4217
+ */
4218
+ export declare function deliverEngineNotice(onNotice: ((notice: EngineNotice) => void) | undefined, notice: EngineNotice): void;
4159
4219
  /** Runtime dependencies shared across tasks. */
4160
4220
  export interface RunnerDeps {
4161
4221
  brain: Brain;
@@ -4729,10 +4789,15 @@ export interface RunnerDeps {
4729
4789
  * task's own `lspManager` overrides this. Unset ⇒ no `lsp` tool. */
4730
4790
  lspManager?: import("./lsp.js").LspServerManager;
4731
4791
  /**
4732
- * Default in-process hooks for all tasks (design/37): PreToolUse (rewrite/restrict args + inject
4733
- * context), PostToolUse (rewrite output + inject context), UserPromptSubmit (block/inject before the
4734
- * objective becomes a message). A task's own `hooks` overrides this. A PreToolUse hook's `allow`
4735
- * never bypasses `toolPolicy` the policy is always the final say.
4792
+ * Default in-process hooks for all tasks (design/37) the FULL lifecycle seam of the `Hooks`
4793
+ * interface, not just the tool-call trio: `preToolUse` (rewrite/restrict args + inject context),
4794
+ * `postToolUse` (rewrite output + inject context), `userPromptSubmit` (block/inject before the
4795
+ * objective becomes a message), plus `stop` (push back when the run would otherwise end and continue
4796
+ * it), `postToolUseFailure` / `postToolBatch` / `permissionDenied` (failure, batch-boundary and
4797
+ * deny observers), `preCompact` / `postCompact` (compaction gate + observer), `stopFailure`
4798
+ * (API-error terminal observer) and the `preToolUseObservational` declaration flag — each member's
4799
+ * contract is documented on the `Hooks` interface itself. A task's own `hooks` overrides this. A
4800
+ * PreToolUse hook's `allow` never bypasses `toolPolicy` — the policy is always the final say.
4736
4801
  */
4737
4802
  hooks?: import("./hooks.js").Hooks;
4738
4803
  /**
@@ -4750,12 +4815,30 @@ export interface RunnerDeps {
4750
4815
  * - `"prompt-constitution"` — a `stableSystem` provider returned an ALREADY-assembled prompt
4751
4816
  * (constitution anchor found); core passed it through un-doubled. Upgrade the provider to return
4752
4817
  * only the role base (or set `replaceAll: true` to own the whole base).
4818
+ * - `"degraded"` — the task kept running with a capability quietly reduced: a question auto-answered
4819
+ * with no human present, a skipped env git snapshot, a lost checkpoint-put confirmation, an
4820
+ * unroutable descendant notification, and similar best-effort arms. The run proceeds; the reduced
4821
+ * arm is what is being disclosed (`classification` names it).
4822
+ * - `"config"` — a deployment-wiring problem detected while preparing or tearing down a task: tool
4823
+ * mount conflicts, refused/ignored knob values, gate-off notices, store/env teardown legs. The
4824
+ * broadest class by call-site count — most misconfigurations announce here rather than failing
4825
+ * the task.
4753
4826
  * - `"memory"` — a post-task memory-consolidation pass failed or skipped a malformed reconcile
4754
4827
  * decision (design/41). Best-effort: the notes the model saved are kept; the task still succeeds.
4755
4828
  * - `"mcp"` — an MCP server failed to connect / list its tools and was skipped (fail-open, design/29);
4756
4829
  * the task runs with the remaining servers' tools. One call per failed server.
4757
4830
  * - `"a2a"` — the A2A sibling of `"mcp"`: a declared peer's agent card could not be fetched/read, or it
4758
4831
  * advertises no transport this client speaks, so the peer was skipped. One call per skipped peer.
4832
+ * - `"interrupt-reconcile"` — repairing an interrupted session's transcript on re-entry (synthesizing
4833
+ * tool results for orphaned calls, flushing held session writes) failed; the run proceeds on the
4834
+ * unrepaired transcript.
4835
+ * - `"suggestions"` — the best-effort follow-up-suggestions pass failed or timed out; the result
4836
+ * simply carries no suggestions.
4837
+ * - `"rewind"` — the per-turn working-tree snapshot backing file rewind failed or was skipped (e.g. a
4838
+ * too-large root inside its cooldown window); turns without a snapshot cannot be rewound to.
4839
+ * - `"hook"` — a deployment hook misbehaved: a callback threw, or returned a verdict it was not
4840
+ * allowed to (e.g. a declared-observational hook). The engine applies the hook's own documented
4841
+ * fallback (swallow, or fail-closed deny, per its contract) and reports the fact here.
4759
4842
  */
4760
4843
  onError?: (err: unknown, context: {
4761
4844
  phase: "compaction" | "prompt-cache" | "prompt-constitution" | "degraded" | "config" | "memory" | "mcp" | "a2a" | "interrupt-reconcile" | "suggestions" | "rewind" | "hook";
@@ -4769,6 +4852,21 @@ export interface RunnerDeps {
4769
4852
  */
4770
4853
  classification?: string;
4771
4854
  }) => void;
4855
+ /**
4856
+ * Structured sink for operator-facing NOTICES ({@link EngineNotice}) — announcements that are not
4857
+ * errors and do not affect the run, which the engine otherwise prints via `console.warn` (e.g. a
4858
+ * configured timeout/env value discarded in favor of another). When wired, a notice goes HERE
4859
+ * INSTEAD of `console.warn` (structured replaces the console line — a host forwarding notices to
4860
+ * its own surface must not show every fact twice); when absent, the historic `console.warn` line
4861
+ * is printed verbatim, so an unwired build keeps its exact loudness. Swallow-guarded at every
4862
+ * emission point (`onError`/`tracer` posture): neither a throwing sink nor an async sink's
4863
+ * rejected promise ever affects the run. A PRESENT NON-function value (an untyped host wiring
4864
+ * null/junk) is a bad deployment value, not a wired sink: every notice then falls back to the
4865
+ * `console.warn` line and the seat defect itself is announced once per process (#170 — a bad seat
4866
+ * must not silence both channels). Per-process announcement de-duplication is unchanged and
4867
+ * sits BEFORE the sink branch — a deduplicated repeat reaches neither channel.
4868
+ */
4869
+ onNotice?: (notice: EngineNotice) => void;
4772
4870
  /** Best-effort fire-and-forget trace sink (task/turn/brain/tool); `TaskSpec.tracer` overrides per task. */
4773
4871
  tracer?: import("./trace.js").TracerHook;
4774
4872
  /**
@@ -1 +1,30 @@
1
- export {};
1
+ let malformedNoticeSeatAnnounced = false;
2
+ export function __resetMalformedNoticeSeatAnnouncement() {
3
+ malformedNoticeSeatAnnounced = false;
4
+ }
5
+ export function deliverEngineNotice(onNotice, notice) {
6
+ if (typeof onNotice === "function") {
7
+ try {
8
+ const r = onNotice(notice);
9
+ if (typeof r?.then === "function") {
10
+ r.then(undefined, () => {
11
+ });
12
+ }
13
+ }
14
+ catch {
15
+ }
16
+ return;
17
+ }
18
+ if (onNotice !== undefined) {
19
+ if (!malformedNoticeSeatAnnounced) {
20
+ malformedNoticeSeatAnnounced = true;
21
+ try {
22
+ console.warn(`The structured notice sink (RunnerDeps.onNotice) holds ${onNotice === null ? "null" : typeof onNotice} — not a ` +
23
+ `function. Notices fall back to console.warn until the wiring is fixed (omit the key, or wire a function).`);
24
+ }
25
+ catch {
26
+ }
27
+ }
28
+ }
29
+ console.warn(notice.message);
30
+ }
@@ -23,7 +23,11 @@ export type ToolExecutionMode = "sequential" | "parallel";
23
23
  * Controls how many queued user messages are injected when the agent loop reaches a queue drain point.
24
24
  *
25
25
  * - "all": drain and inject every queued message at that point.
26
- * - "one-at-a-time": drain and inject only the oldest queued message, leaving the rest queued for later drain points.
26
+ * - "one-at-a-time": drain and inject only the oldest queued message, leaving the rest queued for later
27
+ * drain points. Exception: when the oldest frame is an engine note (a task-notification sidecar
28
+ * payload), every consecutive engine-note frame behind it drains with it as ONE batch, stopping at
29
+ * the first non-note frame (e.g. a real user steer) — N buffered completion notices are one
30
+ * boundary's worth of frames, not N sequential turns.
27
31
  */
28
32
  export type QueueMode = "all" | "one-at-a-time";
29
33
  /** A single tool call content block emitted by an assistant message. */
@@ -394,8 +398,11 @@ export interface AgentLoopConfig extends SimpleStreamOptions {
394
398
  }
395
399
  /**
396
400
  * Thinking/reasoning level for models that support it.
397
- * Note: "xhigh" is only supported by selected model families. Use model thinking-level metadata
398
- * from openclaw/plugin-sdk/llm to detect support for a concrete model.
401
+ * Note: "xhigh" is only supported by selected model families. Per-model/per-endpoint support is
402
+ * declared in this package's model metadata (`src/engine/llm/types.ts`): the compat blocks'
403
+ * accepted-tier sets (`reasoningEffortLevels` on the OpenAI-family compats, `effortLevels` on the
404
+ * Anthropic Messages compat — a requested tier above the declared set clamps down rather than
405
+ * erroring), and `Model.thinkingLevelMap`, where `null` marks a level as unsupported.
399
406
  */
400
407
  export type ThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
401
408
  export interface BashExecutionMessage {
package/dist/index.d.ts CHANGED
@@ -87,7 +87,7 @@ export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, Ch
87
87
  export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
88
88
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
89
89
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
90
- export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, } from "./core/tool-result-store.js";
90
+ export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
91
91
  export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
92
92
  export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
93
93
  export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
@@ -250,7 +250,7 @@ export { retryBackoffMs, parseRetryAfter } from "./brain/retry.js";
250
250
  export { type BrainTimeoutConfig } from "./brain/timeout.js";
251
251
  export { createAssistantMessageEventStream } from "./internal/llm.js";
252
252
  export type { AssistantMessage, AssistantMessageEvent, CompleteSimpleFn, Context, DocumentContent, ImageContent, Message, StopReason, StreamFn, TextContent, ThinkingContent, ToolCall, ToolResultMessage, Usage, UserMessage, } from "./internal/llm.js";
253
- export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
253
+ export type { AgentDefinition, BeforeWriteHook, BeforeWriteRequest, BeforeWriteResult, HandsBandOptions, Brain, BrainStatus, BrainStatusPhase, BrainRetryErrClass, ImageInput, McpElicitRequest, McpElicitResponse, McpServerSpec, A2aServerSpec, OnElicit, Model, ModelRef, ProjectMemoryLoad, RunnerDeps, EngineNotice, RuntimeCaps, BackgroundChildEvent, SkillManifest, SkillSpec, TaskEvent, TaskEventIdentity, ToolActivity, TaskLimits, StaleToolResultOffloadOptions, TaskResult, RemoteEnvFailureNote, TaskSpec, TaskStatus, TaskStream, CompactOutcome, ThinkingLevel, ToolExecuteContext, ToolReturn, ToolSpec, ToolEffect, ToolContentOrigin, WorkflowGovernanceBaseline, DelegationTaskType, } from "./core/types.js";
254
254
  export { Type } from "typebox";
255
255
  export type { TSchema, Static } from "typebox";
256
256
  export { explainPromptAssembly, describeDefaultPack, type DefaultPackDescription, type ExplainInput } from "./prompt-assembly/explain.js";