@sema-agent/core 7.28.1 → 7.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +473 -1
- package/dist/agents/child-model-seat.d.ts +59 -7
- package/dist/agents/child-model-seat.js +33 -7
- package/dist/agents/cross-session-judge.d.ts +27 -5
- package/dist/agents/cross-session-judge.js +23 -12
- package/dist/agents/list-agents-tool.d.ts +31 -6
- package/dist/agents/list-agents-tool.js +66 -14
- package/dist/agents/peer-admission.d.ts +20 -8
- package/dist/agents/peer-admission.js +21 -12
- package/dist/agents/peer-directory.d.ts +29 -0
- package/dist/agents/peer-directory.js +20 -0
- package/dist/agents/peer-held-queue.d.ts +31 -4
- package/dist/agents/peer-held-queue.js +32 -1
- package/dist/agents/peer-session-drain.d.ts +91 -13
- package/dist/agents/peer-session-drain.js +409 -102
- package/dist/agents/read-inbox-tool.d.ts +22 -0
- package/dist/agents/read-inbox-tool.js +39 -0
- package/dist/agents/roster-store.d.ts +2 -4
- package/dist/agents/roster-store.js +1 -1
- package/dist/agents/send-message-tool.d.ts +4 -4
- package/dist/agents/send-message-tool.js +22 -2
- package/dist/agents/subagent.js +62 -44
- package/dist/agents/team-role.d.ts +34 -0
- package/dist/agents/team-role.js +17 -0
- package/dist/agents/verify.js +2 -2
- package/dist/brain/anthropic.js +4 -2
- package/dist/brain/open-responses.js +4 -3
- package/dist/brain/openai.js +2 -1
- package/dist/brain/stream-engine.js +6 -0
- package/dist/brain/stream-shared.d.ts +8 -0
- package/dist/brain/stream-shared.js +8 -0
- package/dist/core/auto-compaction.d.ts +42 -0
- package/dist/core/auto-compaction.js +35 -2
- package/dist/core/child-clamp-seats.d.ts +11 -0
- package/dist/core/child-clamp-seats.js +3 -0
- package/dist/core/context-edit.d.ts +3 -2
- package/dist/core/context-edit.js +6 -5
- package/dist/core/context-guard.d.ts +2 -1
- package/dist/core/context-guard.js +3 -2
- package/dist/core/context-settings.d.ts +103 -0
- package/dist/core/context-settings.js +60 -0
- package/dist/core/engine-notice.d.ts +18 -1
- package/dist/core/frame-settlement.d.ts +97 -0
- package/dist/core/frame-settlement.js +36 -0
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +4 -0
- package/dist/core/hooks.d.ts +10 -10
- package/dist/core/mailbox-store.d.ts +41 -7
- package/dist/core/mailbox-store.js +9 -3
- package/dist/core/message-utils.d.ts +13 -0
- package/dist/core/message-utils.js +6 -0
- package/dist/core/parent-spec-seat.d.ts +14 -1
- package/dist/core/parent-spec-seat.js +6 -0
- package/dist/core/permission-rule-model.js +1 -1
- package/dist/core/roles.d.ts +20 -0
- package/dist/core/roles.js +35 -16
- package/dist/core/runner/assemble-result.d.ts +2 -0
- package/dist/core/runner/assemble-result.js +3 -1
- package/dist/core/runner/compaction-call-options.d.ts +26 -2
- package/dist/core/runner/compaction-call-options.js +9 -0
- package/dist/core/runner/compaction-seams.d.ts +4 -3
- package/dist/core/runner/compaction-seams.js +13 -6
- package/dist/core/runner/contracts.d.ts +61 -7
- package/dist/core/runner/decide-continuation.d.ts +2 -1
- package/dist/core/runner/decide-continuation.js +11 -10
- package/dist/core/runner/initial-run-state.js +2 -2
- package/dist/core/runner/prepare-caps-and-workflow.js +9 -1
- package/dist/core/runner/prepare-config-doors.js +4 -1
- package/dist/core/runner/prepare-context-lane.d.ts +9 -2
- package/dist/core/runner/prepare-context-lane.js +15 -10
- package/dist/core/runner/prepare-delegation-surface.d.ts +3 -2
- package/dist/core/runner/prepare-delegation-surface.js +9 -3
- package/dist/core/runner/prepare-file-history.js +10 -8
- package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
- package/dist/core/runner/prepare-gate-stations.js +3 -1
- package/dist/core/runner/prepare-inherited-gate.js +1 -0
- package/dist/core/runner/prepare-prompt-inputs.d.ts +6 -0
- package/dist/core/runner/prepare-prompt-inputs.js +2 -1
- package/dist/core/runner/prepare-task.js +22 -9
- package/dist/core/runner/prepare-wiring-manifest.d.ts +6 -1
- package/dist/core/runner/prepare-wiring-manifest.js +6 -1
- package/dist/core/runner/response-model-seat.d.ts +16 -0
- package/dist/core/runner/response-model-seat.js +27 -0
- package/dist/core/runner/retired-runner-deps.d.ts +3 -1
- package/dist/core/runner/retired-runner-deps.js +1 -0
- package/dist/core/runner/run-compaction-machinery.d.ts +4 -2
- package/dist/core/runner/run-compaction-machinery.js +5 -3
- package/dist/core/runner/run-harness-handlers.js +9 -2
- package/dist/core/runner/run-identity-wiring.js +19 -1
- package/dist/core/runner/run-leg.js +1 -1
- package/dist/core/runner/run-notification-lane.js +8 -2
- package/dist/core/runner/run-recovery-lanes.js +9 -5
- package/dist/core/runner/run-settle-and-teardown.d.ts +4 -22
- package/dist/core/runner/run-settle-and-teardown.js +6 -10
- package/dist/core/runner/run-stop-and-final-verify.js +1 -0
- package/dist/core/runner/run-terminal-adoption.js +5 -5
- package/dist/core/runner/run-turn-boundary.js +32 -5
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +33 -2
- package/dist/core/runner-deps.d.ts +44 -13
- package/dist/core/side-query.d.ts +6 -5
- package/dist/core/side-query.js +3 -1
- package/dist/core/swappable-deps.d.ts +20 -4
- package/dist/core/swappable-deps.js +6 -1
- package/dist/core/task-event.d.ts +13 -0
- package/dist/core/task-notification.d.ts +144 -2
- package/dist/core/task-notification.js +154 -5
- package/dist/core/task-result.d.ts +27 -3
- package/dist/core/task-spec.d.ts +7 -0
- package/dist/core/tool-catalog-entries.js +2 -1
- package/dist/core/tool-conformance.js +1 -0
- package/dist/core/tool-policy.d.ts +9 -4
- package/dist/core/tool-spec.d.ts +14 -0
- package/dist/core/wiring-manifest.d.ts +12 -0
- package/dist/core/wiring-manifest.js +5 -1
- package/dist/engine/harness/agent-harness.d.ts +14 -1
- package/dist/engine/harness/agent-harness.js +28 -3
- package/dist/engine/harness/types.d.ts +3 -1
- package/dist/engine/llm/types.d.ts +8 -7
- package/dist/engine/llm/types.js +1 -1
- package/dist/engine/session/import-validate.js +4 -0
- package/dist/index.d.ts +10 -7
- package/dist/index.js +7 -5
- package/dist/orchestration/run-workflow-tool.d.ts +4 -4
- package/dist/orchestration/workflow.d.ts +1 -1
- package/dist/orchestration/workflow.js +8 -5
- package/dist/shell/lex.d.ts +30 -51
- package/dist/shell/lex.js +237 -284
- package/dist/stores/cc/mailbox-store.js +3 -2
- package/dist/stores/file/corrupt-read-sink.d.ts +28 -2
- package/dist/stores/file/file-history-store.d.ts +2 -4
- package/dist/stores/file/file-history-store.js +1 -1
- package/dist/stores/file/fs-atomic.d.ts +16 -11
- package/dist/stores/file/fs-atomic.js +17 -6
- package/dist/stores/file/index.d.ts +5 -6
- package/dist/stores/file/index.js +1 -1
- package/dist/stores/file/mailbox-store.d.ts +4 -9
- package/dist/stores/file/mailbox-store.js +29 -30
- package/dist/stores/file/session-policy-store.d.ts +2 -0
- package/dist/stores/file/session-policy-store.js +1 -1
- package/dist/stores/file/session-store.d.ts +11 -7
- package/dist/stores/file/session-store.js +23 -16
- package/dist/stores/file/shared-ledger.js +3 -14
- package/dist/stores/file/tool-result-store.d.ts +2 -0
- package/dist/stores/file/tool-result-store.js +5 -2
- package/dist/stores/file/workflow-journal-store.js +14 -9
- package/dist/tools/fs/bash-program-position.d.ts +15 -24
- package/dist/tools/fs/bash-program-position.js +11 -29
- package/dist/tools/fs/bash-readonly-classifier.d.ts +125 -117
- package/dist/tools/fs/bash-readonly-classifier.js +400 -272
- package/dist/tools/fs/shell-read-check.js +3 -3
- package/dist/tools/fs/shell-read-establish.js +1 -1
- package/dist/tools/fs/shell-read-plan.d.ts +11 -6
- package/dist/tools/fs/shell-read-plan.js +167 -183
- package/dist/tools/fs/shell-read-word.d.ts +50 -0
- package/dist/tools/fs/shell-read-word.js +59 -0
- package/dist/tools/fs/shell-reentry-text.d.ts +10 -0
- package/dist/tools/fs/shell-reentry-text.js +73 -0
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +58 -8
- package/wire-changes/core@7.29.0.json +185 -0
- package/wire-changes/core@7.30.0.json +1821 -0
|
@@ -107,6 +107,54 @@ export interface TaskNotificationPayload {
|
|
|
107
107
|
* semantics as the poll face's `partial_result` flag (one meaning across both surfaces). Absent on
|
|
108
108
|
* every other notification (a completed result is never flagged). */
|
|
109
109
|
partial?: boolean;
|
|
110
|
+
/** Delegation lanes, terminal frames: the child's EXPLICIT `Agent({name})` spawn key — the same word
|
|
111
|
+
* its `task_progress.spawnName`, roster row and hook identity carry, rendered as `<agent-name>` so
|
|
112
|
+
* the parent can `SendMessage({to})` it by name. Minted from the identity the frame's producer holds
|
|
113
|
+
* (never from the description); ABSENT = an unnamed delegation. */
|
|
114
|
+
spawnName?: string;
|
|
115
|
+
/** Delegation lanes, terminal frames — how the child's report reached this frame under the
|
|
116
|
+
* hand-back contract: `send` = a report was delivered; `flagged` = delivered under the hand-back
|
|
117
|
+
* screening's SECURITY WARNING; `withheld` = nothing was delivered (no result text, no partial text).
|
|
118
|
+
* Absent on non-terminal frames and on the non-delegation lanes. The model face renders a
|
|
119
|
+
* `<handback>` line only for `flagged` / `withheld` — the parent is told when a report did not
|
|
120
|
+
* arrive plainly, and nothing for the ordinary case. */
|
|
121
|
+
handback?: HandbackState;
|
|
122
|
+
/** Delegation lanes, terminal frames, present iff the child's leg compacted its context at least once
|
|
123
|
+
* or opened its compaction breaker: `passes` = landed compactions, `breakerOpen` = compaction was
|
|
124
|
+
* disabled for the rest of the leg (later context was trimmed, not summarized), `kind` = the last
|
|
125
|
+
* landed pass's trigger. The parent otherwise cannot tell that a child forgot. */
|
|
126
|
+
compaction?: {
|
|
127
|
+
passes: number;
|
|
128
|
+
breakerOpen: boolean;
|
|
129
|
+
kind?: CompactionKind;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* The INBOX NOTICE carrier: one drain pass of this session's box stopped with records still waiting
|
|
133
|
+
* (the pass budget, an admission stop, a lane refusal). `pending` = records still leased and not yet
|
|
134
|
+
* injected when the pass ended; `delivered` = records injected this pass; `urgent` = how many of the
|
|
135
|
+
* waiting ones their sender marked urgent (absent = 0); `box` = the session box. Its PRESENCE is the
|
|
136
|
+
* render discriminator: the model reads ONE engine line (`[Inbox notice] …`), never a shell. Minted
|
|
137
|
+
* ONLY by the engine's session-box drain, keyed `task_id: "inbox:<box>"` with the pass counter as
|
|
138
|
+
* `seq` so it never collides with a message frame's key; absent when the pass emptied the box.
|
|
139
|
+
*/
|
|
140
|
+
inboxNotice?: {
|
|
141
|
+
pending: number;
|
|
142
|
+
delivered: number;
|
|
143
|
+
urgent?: number;
|
|
144
|
+
resupplied?: number;
|
|
145
|
+
box: string;
|
|
146
|
+
};
|
|
147
|
+
/** #1036 — THIS occurrence is a RE-SUPPLY of a record the model was already shown once and
|
|
148
|
+
* whose frame a compaction then summarized away. Same record, same `task_id`, same `seq`, a new
|
|
149
|
+
* occurrence: `attempt` says which one. ABSENT = not a re-supply (a first delivery); there is no
|
|
150
|
+
* `false` form. Only the session-box drain mints it. */
|
|
151
|
+
resupplied?: true;
|
|
152
|
+
/** #1036 — which DELIVERY ATTEMPT of this record's frame this occurrence is: 1 = the first
|
|
153
|
+
* delivery, 2 = the one re-supply a summarized record gets. ABSENT = 1 (there is no `0`, and a
|
|
154
|
+
* consumer must not read absence as "unknown"). It folds into the injection queue's dedup key
|
|
155
|
+
* ({@link taskNotificationDedupKey}), which is what keeps a re-supply from collapsing into the
|
|
156
|
+
* first occurrence's entry. Only the session-box drain mints it. */
|
|
157
|
+
attempt?: number;
|
|
110
158
|
output_file?: string;
|
|
111
159
|
/** Whatever the settling lane knows about its own spend, JSON-rendered verbatim into one `<usage>`
|
|
112
160
|
* tag. Deliberately untyped — the lanes report different quantities and nothing here should force
|
|
@@ -223,7 +271,7 @@ export interface SemaProvenance {
|
|
|
223
271
|
/** `agent_message` = the same-process uplink (§1.4 d1); `cross_session_message` = the session-box
|
|
224
272
|
* drain (§4.1) — `from` is then the sender's ADDRESS, `taskId` the recipient's own box handle,
|
|
225
273
|
* `seq` the box seq, and the typed peer record rides in `peerMeta`. */
|
|
226
|
-
kind: "agent_message" | "cross_session_message" | "cross_session_notice";
|
|
274
|
+
kind: "agent_message" | "cross_session_message" | "cross_session_notice" | "inbox_notice";
|
|
227
275
|
from: string;
|
|
228
276
|
taskId: string;
|
|
229
277
|
seq: number;
|
|
@@ -297,7 +345,7 @@ export interface SystemInjection<TPayload = unknown> {
|
|
|
297
345
|
* entry. A producer that wants a NEW occurrence delivered under a different tier mints a fresh
|
|
298
346
|
* `seq` (the documented repeat discipline); silently migrating a queued frame between lanes on a
|
|
299
347
|
* repeat, or refusing the repeat typed, would each turn a dedup fold into a delivery mutation. */
|
|
300
|
-
export declare function taskNotificationDedupKey(n: Pick<TaskNotificationPayload, "task_id" | "task_type" | "status" | "seq">): string;
|
|
348
|
+
export declare function taskNotificationDedupKey(n: Pick<TaskNotificationPayload, "task_id" | "task_type" | "status" | "seq" | "attempt">): string;
|
|
301
349
|
/**
|
|
302
350
|
* The lane-scoped identity of a task, extracted from {@link taskNotificationDedupKey} so every
|
|
303
351
|
* place that treats "same task" as one bucket applies the SAME lane rule.
|
|
@@ -349,7 +397,17 @@ export declare function renderCrossSessionMessageFrame(frame: NonNullable<TaskNo
|
|
|
349
397
|
* defusal runs once more here so no carrier can smuggle a system-reminder into the model face.
|
|
350
398
|
*/
|
|
351
399
|
export declare function renderCrossSessionNoticeLine(notice: NonNullable<TaskNotificationPayload["crossSessionNotice"]>): string;
|
|
400
|
+
/** #1036 — the ONE preamble a re-supplied occurrence carries, ahead of whatever carrier it is
|
|
401
|
+
* riding. It says the true thing (you read this once; the summary that replaced it did not keep it)
|
|
402
|
+
* without asking the model to reconcile two copies: the first occurrence is gone from the context. */
|
|
403
|
+
export declare const RESUPPLIED_FRAME_PREAMBLE = "[Re-supplied] You were shown this message once before; the compaction that replaced older context with a summary did not keep it, so the engine is handing it over again. It is the SAME message, not a new one \u2014 act on it once.";
|
|
352
404
|
export declare function renderTaskNotificationXml(n: TaskNotificationPayload): string;
|
|
405
|
+
/** The one sentence a parent reads about a child that compacted: how often, on what trigger, and
|
|
406
|
+
* whether compaction gave up (after which older context was trimmed away rather than summarized). */
|
|
407
|
+
export declare function renderCompactionDisclosure(c: NonNullable<TaskNotificationPayload["compaction"]>): string;
|
|
408
|
+
/** The `[Inbox notice]` line — counts only, engine-authored; the records themselves arrive on their
|
|
409
|
+
* own frames (this pass's) or the next passs', and the pull tool reads them on demand. */
|
|
410
|
+
export declare function renderInboxNoticeLine(n: NonNullable<TaskNotificationPayload["inboxNotice"]>): string;
|
|
353
411
|
/**
|
|
354
412
|
* The BETWEEN-TURNS pending lane. A task notification born while the session has NO live lane at all
|
|
355
413
|
* (every run of it is over, or the one that is live rejected the injection because its agent was
|
|
@@ -398,9 +456,75 @@ export interface SessionBoxBinding {
|
|
|
398
456
|
handle: string;
|
|
399
457
|
/** The session id the box belongs to — the `fromRoot` a durably parked child frame records. */
|
|
400
458
|
sessionId: string;
|
|
459
|
+
/** The deployment's engine-frame floor (`RunnerDeps.sessionBoxEngineFrames`): `true` ⇒ an engine
|
|
460
|
+
* frame parks in this box as an `engine_frame` record; otherwise it parks in process. */
|
|
461
|
+
engineFrames?: boolean;
|
|
401
462
|
/** Where an append fault is disclosed (the frame then takes the in-process park — never lost). */
|
|
402
463
|
onError?: (e: unknown) => void;
|
|
403
464
|
}
|
|
465
|
+
/** The hand-back contract's three words (see {@link TaskNotificationPayload.handback}). */
|
|
466
|
+
export declare const HANDBACK_STATES: readonly ["send", "flagged", "withheld"];
|
|
467
|
+
export type HandbackState = (typeof HANDBACK_STATES)[number];
|
|
468
|
+
/** THE hand-back projection, one rule every terminal frame producer calls: a warning ⇒ `flagged`; any
|
|
469
|
+
* report or partial text ⇒ `send`; neither ⇒ `withheld`. */
|
|
470
|
+
export declare function handbackStateOf(facts: {
|
|
471
|
+
result?: string | undefined;
|
|
472
|
+
partialText?: string | undefined;
|
|
473
|
+
warning?: string | undefined;
|
|
474
|
+
}): HandbackState;
|
|
475
|
+
/** The three-way compaction trigger word a terminal frame carries (the wire's `forced` is `reactive` here). */
|
|
476
|
+
export declare const COMPACTION_KINDS: readonly ["manual", "auto", "reactive"];
|
|
477
|
+
export type CompactionKind = (typeof COMPACTION_KINDS)[number];
|
|
478
|
+
/** The wire trigger word → the frame's kind word (`forced` = a reactive pass the run could not skip). */
|
|
479
|
+
export declare function compactionKindOf(trigger: "auto" | "manual" | "forced"): CompactionKind;
|
|
480
|
+
/**
|
|
481
|
+
* THE projection every delegation terminal frame calls for its identity + hand-back + context facets:
|
|
482
|
+
* the spawn key from the identity the producer holds (never the description), the hand-back state from
|
|
483
|
+
* the delivered text and the screening's warning, and the child's compaction record verbatim. Absent
|
|
484
|
+
* inputs stay absent; `handback` is always minted (a terminal always says how the report reached it).
|
|
485
|
+
*/
|
|
486
|
+
export declare function terminalFrameFacets(facts: {
|
|
487
|
+
spawnName?: string | undefined;
|
|
488
|
+
result?: string | undefined;
|
|
489
|
+
partialText?: string | undefined;
|
|
490
|
+
warning?: string | undefined;
|
|
491
|
+
compaction?: TaskNotificationPayload["compaction"] | undefined;
|
|
492
|
+
}): Pick<TaskNotificationPayload, "spawnName" | "handback" | "compaction">;
|
|
493
|
+
/** The codec version this engine writes into an `engine_frame` record and the highest it reads. */
|
|
494
|
+
export declare const ENGINE_FRAME_CODEC_VERSION = 1;
|
|
495
|
+
/** The engine's own lanes — the only frames the durable park may encode (an `external` event never is). */
|
|
496
|
+
export declare const ENGINE_FRAME_TASK_TYPES: readonly ["background_agent", "monitor", "workflow"];
|
|
497
|
+
/** May this frame take the durable engine-frame park? Its lane must be one of the engine's own and it
|
|
498
|
+
* must wear no shell-less carrier. */
|
|
499
|
+
export declare function isEngineFrameParkable(payload: TaskNotificationPayload): boolean;
|
|
500
|
+
/** The record text of a durably parked engine frame: the codec version, the injection tier it was
|
|
501
|
+
* submitted at, and the frame itself (its own `seq` is the lane's cycle count — a different domain
|
|
502
|
+
* from the box seq, carried inside untouched). */
|
|
503
|
+
export declare function encodeEngineFrame(payload: TaskNotificationPayload, priority: SystemInjectionPriority): string;
|
|
504
|
+
export type EngineFrameDecode = {
|
|
505
|
+
ok: true;
|
|
506
|
+
payload: TaskNotificationPayload;
|
|
507
|
+
priority: SystemInjectionPriority;
|
|
508
|
+
}
|
|
509
|
+
/** A version this engine does not read: the record is a NEWER engine's; leave it for one. */
|
|
510
|
+
| {
|
|
511
|
+
ok: false;
|
|
512
|
+
reason: "newer";
|
|
513
|
+
codecVersion: number;
|
|
514
|
+
}
|
|
515
|
+
/** Not this codec at all (or a frame outside the engine's lanes): settle it without delivery. */
|
|
516
|
+
| {
|
|
517
|
+
ok: false;
|
|
518
|
+
reason: "malformed";
|
|
519
|
+
detail: string;
|
|
520
|
+
};
|
|
521
|
+
/** Read an `engine_frame` record back. Every field the lane reads is re-typed here; a carrier the
|
|
522
|
+
* writer must never have encoded is dropped rather than trusted. */
|
|
523
|
+
export declare function decodeEngineFrame(content: string): EngineFrameDecode;
|
|
524
|
+
/** The durable form of an engine frame: the codec text under `kind: "engine_frame"`, stamped with
|
|
525
|
+
* the box owner as `fromRoot` (the park seat's own fact, as for a peer-words record); no hop chain —
|
|
526
|
+
* it is not a peer message and is never admitted as one. */
|
|
527
|
+
export declare function engineFrameRecordOf(payload: TaskNotificationPayload, priority: SystemInjectionPriority, box: Pick<SessionBoxBinding, "sessionId">): MailboxAppendMessage;
|
|
404
528
|
/**
|
|
405
529
|
* RB-375 A2 (ruled 2026-08-03) — is this frame the TERMINAL of a delegated background agent?
|
|
406
530
|
*
|
|
@@ -616,6 +740,24 @@ export declare class PendingSessionNotifications {
|
|
|
616
740
|
markDelivered(sessionId: string, dedupKey: string): void;
|
|
617
741
|
get size(): number;
|
|
618
742
|
}
|
|
743
|
+
/**
|
|
744
|
+
* The durable box appends ONE run leg caused — its obligations. Attribution is by execution: an append
|
|
745
|
+
* belongs to the leg whose execution (its tools, its producers, its teardown reap, a child terminal it
|
|
746
|
+
* hands up the delegation chain) reached the park seat, whichever lane's routing carried it there. A
|
|
747
|
+
* leg's teardown awaits its own obligations and nobody else's — another session's pending append is
|
|
748
|
+
* never a condition of this leg's completion. A park reached outside any leg is nobody's obligation.
|
|
749
|
+
*/
|
|
750
|
+
export declare class DurableAppendObligations {
|
|
751
|
+
private static readonly scope;
|
|
752
|
+
private readonly inFlight;
|
|
753
|
+
/** The obligations of the leg whose execution is running now; undefined outside every leg. */
|
|
754
|
+
static current(): DurableAppendObligations | undefined;
|
|
755
|
+
/** Run `body` as this leg (synchronous: the body's own return value, no added tick). */
|
|
756
|
+
run<R>(body: () => R): R;
|
|
757
|
+
track(p: Promise<unknown>): void;
|
|
758
|
+
/** Resolves when every append this leg caused has settled — the fault arm already re-parked in process. */
|
|
759
|
+
settle(): Promise<void>;
|
|
760
|
+
}
|
|
619
761
|
/**
|
|
620
762
|
* WHERE A FRAME ADDRESSED TO ONE SESSION GOES — the single decision, so the two addresses that ask it
|
|
621
763
|
* (a run's own session, and the delegation tree's root anchor) cannot drift apart. Three outcomes:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
1
2
|
import { escapeEnvelopeTag, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
2
3
|
import { PEER_MESSAGE_NOTICE } from "../agents/peer-admission.js";
|
|
3
4
|
import { buildCrossSessionEnvelope, CROSS_SESSION_MESSAGE_NOTICE, neutralizePeerBody } from "../agents/cross-session-envelope.js";
|
|
@@ -24,6 +25,9 @@ const TASK_NOTIFICATION_TAGS = [
|
|
|
24
25
|
"recent-steps",
|
|
25
26
|
"edited-files",
|
|
26
27
|
"resumable",
|
|
28
|
+
"agent-name",
|
|
29
|
+
"handback",
|
|
30
|
+
"context",
|
|
27
31
|
];
|
|
28
32
|
function renderResidual(n) {
|
|
29
33
|
const lines = [];
|
|
@@ -36,14 +40,22 @@ function renderResidual(n) {
|
|
|
36
40
|
return lines.length > 0 ? lines.join("\n") : undefined;
|
|
37
41
|
}
|
|
38
42
|
function tag(name, value) {
|
|
39
|
-
|
|
43
|
+
let shown;
|
|
44
|
+
try {
|
|
45
|
+
shown = typeof value === "string" ? value : String(value);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
shown = `[unrenderable ${typeof value}]`;
|
|
49
|
+
}
|
|
50
|
+
const text = sanitizeUntrustedText(shown, TASK_NOTIFICATION_TAGS)
|
|
40
51
|
.replace(/&/g, "&")
|
|
41
52
|
.replace(/</g, "<")
|
|
42
53
|
.replace(/>/g, ">");
|
|
43
54
|
return `<${name}>${text}</${name}>`;
|
|
44
55
|
}
|
|
45
56
|
export function taskNotificationDedupKey(n) {
|
|
46
|
-
|
|
57
|
+
const attempt = n.attempt !== undefined && Number.isInteger(n.attempt) && n.attempt > 1 ? `:a${n.attempt}` : "";
|
|
58
|
+
return `task_notification:${taskNotificationLaneKey(n)}:${n.status}${n.seq !== undefined ? `:${n.seq}` : ""}${attempt}`;
|
|
47
59
|
}
|
|
48
60
|
export function taskNotificationLaneKey(n) {
|
|
49
61
|
return n.task_type === "external" ? `external:${n.task_id}` : n.task_id;
|
|
@@ -64,13 +76,20 @@ export function renderCrossSessionMessageFrame(frame) {
|
|
|
64
76
|
export function renderCrossSessionNoticeLine(notice) {
|
|
65
77
|
return sanitizeUntrustedText(notice.text);
|
|
66
78
|
}
|
|
79
|
+
export const RESUPPLIED_FRAME_PREAMBLE = "[Re-supplied] You were shown this message once before; the compaction that replaced older context with a summary did not keep it, so the engine is handing it over again. It is the SAME message, not a new one \u2014 act on it once.";
|
|
67
80
|
export function renderTaskNotificationXml(n) {
|
|
81
|
+
const body = renderTaskNotificationCarrier(n);
|
|
82
|
+
return n.resupplied === true ? `${RESUPPLIED_FRAME_PREAMBLE}\n\n${body}` : body;
|
|
83
|
+
}
|
|
84
|
+
function renderTaskNotificationCarrier(n) {
|
|
68
85
|
if (n.agentMessage !== undefined)
|
|
69
86
|
return renderAgentMessageFrame(n.agentMessage);
|
|
70
87
|
if (n.crossSessionMessage !== undefined)
|
|
71
88
|
return renderCrossSessionMessageFrame(n.crossSessionMessage);
|
|
72
89
|
if (n.crossSessionNotice !== undefined)
|
|
73
90
|
return renderCrossSessionNoticeLine(n.crossSessionNotice);
|
|
91
|
+
if (n.inboxNotice !== undefined)
|
|
92
|
+
return renderInboxNoticeLine(n.inboxNotice);
|
|
74
93
|
const usage = n.usage === undefined
|
|
75
94
|
? undefined
|
|
76
95
|
: (() => {
|
|
@@ -92,6 +111,8 @@ export function renderTaskNotificationXml(n) {
|
|
|
92
111
|
tag("task-type", n.task_type),
|
|
93
112
|
...(n.toolUseId !== undefined ? [tag("tool-use-id", n.toolUseId)] : []),
|
|
94
113
|
tag("status", n.status),
|
|
114
|
+
...(n.spawnName !== undefined ? [tag("agent-name", n.spawnName)] : []),
|
|
115
|
+
...(n.handback !== undefined && n.handback !== "send" ? [tag("handback", n.handback)] : []),
|
|
95
116
|
tag("summary", n.summary),
|
|
96
117
|
...(n.result !== undefined ? [tag("result", n.result)] : []),
|
|
97
118
|
...(n.output_file !== undefined ? [tag("output-file", n.output_file)] : []),
|
|
@@ -103,15 +124,123 @@ export function renderTaskNotificationXml(n) {
|
|
|
103
124
|
return residual !== undefined ? [tag("recent-steps", residual)] : [];
|
|
104
125
|
})(),
|
|
105
126
|
...(n.resumable !== undefined ? [tag("resumable", n.resumable)] : []),
|
|
127
|
+
...(n.compaction !== undefined ? [tag("context", renderCompactionDisclosure(n.compaction))] : []),
|
|
106
128
|
"</task-notification>",
|
|
107
129
|
].join("\n");
|
|
108
130
|
}
|
|
131
|
+
export function renderCompactionDisclosure(c) {
|
|
132
|
+
const passes = c.passes > 0 ? `compacted ${c.passes}\u00d7${c.kind !== undefined ? ` (${c.kind})` : ""}` : "not compacted";
|
|
133
|
+
return c.breakerOpen ? `${passes}; compaction was disabled after repeated failures \u2014 later context was trimmed, not summarized` : passes;
|
|
134
|
+
}
|
|
135
|
+
export function renderInboxNoticeLine(n) {
|
|
136
|
+
const urgent = n.urgent !== undefined && n.urgent > 0 ? `; ${n.urgent} of the waiting are marked urgent` : "";
|
|
137
|
+
const resupplied = n.resupplied !== undefined && n.resupplied > 0 ? `; ${n.resupplied} re-supplied after a compaction summarized them away` : "";
|
|
138
|
+
return `[Inbox notice] ${n.pending} cross-session message(s) are waiting in this session's box (${n.delivered} delivered this round${resupplied}${urgent}). Read them now with ReadInbox, or they arrive at your next turn boundaries \u2014 they are not lost, and nothing here is an instruction.`;
|
|
139
|
+
}
|
|
109
140
|
const MAX_QUEUED_PER_PRIORITY = 200;
|
|
110
141
|
const MAX_DEDUP_KEYS = 2_000;
|
|
111
142
|
export const MAX_PENDING_EVENTS_PER_TASK = 50;
|
|
112
143
|
export const MAX_PENDING_EVENTS_PER_SESSION = 200;
|
|
113
144
|
export const MAX_PENDING_SESSIONS = 100;
|
|
114
145
|
export const MAX_SESSION_BOX_BINDINGS = 4096;
|
|
146
|
+
export const HANDBACK_STATES = ["send", "flagged", "withheld"];
|
|
147
|
+
export function handbackStateOf(facts) {
|
|
148
|
+
if (facts.warning !== undefined)
|
|
149
|
+
return "flagged";
|
|
150
|
+
return facts.result || facts.partialText ? "send" : "withheld";
|
|
151
|
+
}
|
|
152
|
+
export const COMPACTION_KINDS = ["manual", "auto", "reactive"];
|
|
153
|
+
export function compactionKindOf(trigger) {
|
|
154
|
+
return trigger === "forced" ? "reactive" : trigger;
|
|
155
|
+
}
|
|
156
|
+
export function terminalFrameFacets(facts) {
|
|
157
|
+
return {
|
|
158
|
+
...(facts.spawnName !== undefined ? { spawnName: facts.spawnName } : {}),
|
|
159
|
+
handback: handbackStateOf(facts),
|
|
160
|
+
...(facts.compaction !== undefined ? { compaction: { passes: facts.compaction.passes, breakerOpen: facts.compaction.breakerOpen, ...(facts.compaction.kind !== undefined ? { kind: facts.compaction.kind } : {}) } } : {}),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
export const ENGINE_FRAME_CODEC_VERSION = 1;
|
|
164
|
+
export const ENGINE_FRAME_TASK_TYPES = ["background_agent", "monitor", "workflow"];
|
|
165
|
+
const SHELL_LESS_CARRIERS = ["agentMessage", "crossSessionMessage", "crossSessionNotice", "inboxNotice", "_sema_provenance"];
|
|
166
|
+
export function isEngineFrameParkable(payload) {
|
|
167
|
+
return ENGINE_FRAME_TASK_TYPES.includes(payload.task_type) && SHELL_LESS_CARRIERS.every((k) => payload[k] === undefined);
|
|
168
|
+
}
|
|
169
|
+
export function encodeEngineFrame(payload, priority) {
|
|
170
|
+
return JSON.stringify({ codecVersion: ENGINE_FRAME_CODEC_VERSION, priority, payload });
|
|
171
|
+
}
|
|
172
|
+
export function decodeEngineFrame(content) {
|
|
173
|
+
let raw;
|
|
174
|
+
try {
|
|
175
|
+
raw = JSON.parse(content);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return { ok: false, reason: "malformed", detail: "not JSON" };
|
|
179
|
+
}
|
|
180
|
+
if (raw === null || typeof raw !== "object" || Array.isArray(raw))
|
|
181
|
+
return { ok: false, reason: "malformed", detail: "not an object" };
|
|
182
|
+
const r = raw;
|
|
183
|
+
const version = r["codecVersion"];
|
|
184
|
+
if (typeof version !== "number" || !Number.isInteger(version) || version < 1)
|
|
185
|
+
return { ok: false, reason: "malformed", detail: "codecVersion missing" };
|
|
186
|
+
if (version > ENGINE_FRAME_CODEC_VERSION)
|
|
187
|
+
return { ok: false, reason: "newer", codecVersion: version };
|
|
188
|
+
const priority = r["priority"];
|
|
189
|
+
if (!isSystemInjectionPriority(priority))
|
|
190
|
+
return { ok: false, reason: "malformed", detail: "priority outside the ladder" };
|
|
191
|
+
const p = r["payload"];
|
|
192
|
+
if (p === null || typeof p !== "object" || Array.isArray(p))
|
|
193
|
+
return { ok: false, reason: "malformed", detail: "payload missing" };
|
|
194
|
+
const f = p;
|
|
195
|
+
if (typeof f["task_id"] !== "string" || f["task_id"] === "")
|
|
196
|
+
return { ok: false, reason: "malformed", detail: "task_id missing" };
|
|
197
|
+
if (!ENGINE_FRAME_TASK_TYPES.includes(f["task_type"]))
|
|
198
|
+
return { ok: false, reason: "malformed", detail: "task_type outside the engine's lanes" };
|
|
199
|
+
if (typeof f["status"] !== "string" || typeof f["summary"] !== "string")
|
|
200
|
+
return { ok: false, reason: "malformed", detail: "status/summary missing" };
|
|
201
|
+
const facet = malformedFacet(f);
|
|
202
|
+
if (facet !== undefined)
|
|
203
|
+
return { ok: false, reason: "malformed", detail: facet };
|
|
204
|
+
const payload = { ...p };
|
|
205
|
+
for (const k of SHELL_LESS_CARRIERS)
|
|
206
|
+
delete payload[k];
|
|
207
|
+
return { ok: true, payload, priority };
|
|
208
|
+
}
|
|
209
|
+
const SPAWN_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
210
|
+
function malformedFacet(f) {
|
|
211
|
+
if (f["spawnName"] !== undefined && (typeof f["spawnName"] !== "string" || !SPAWN_NAME_RE.test(f["spawnName"])))
|
|
212
|
+
return "spawnName outside the spawn-name grammar";
|
|
213
|
+
if (f["handback"] !== undefined && !(typeof f["handback"] === "string" && HANDBACK_STATES.includes(f["handback"])))
|
|
214
|
+
return "handback outside its closed set";
|
|
215
|
+
const steps = f["recentSteps"];
|
|
216
|
+
if (steps !== undefined && !(Array.isArray(steps) && steps.every((s) => s !== null && typeof s === "object" && typeof s.tool === "string" && typeof s.target === "string" && typeof s.outcome === "string")))
|
|
217
|
+
return "recentSteps is not a list of steps";
|
|
218
|
+
const edits = f["editedFiles"];
|
|
219
|
+
if (edits !== undefined && !(Array.isArray(edits) && edits.every((e) => e !== null && typeof e === "object" && typeof e.path === "string" && typeof e.edits === "number")))
|
|
220
|
+
return "editedFiles is not a list of files";
|
|
221
|
+
if (f["lines"] !== undefined && !(Array.isArray(f["lines"]) && f["lines"].every((l) => typeof l === "string")))
|
|
222
|
+
return "lines is not a list of strings";
|
|
223
|
+
const c = f["compaction"];
|
|
224
|
+
if (c !== undefined) {
|
|
225
|
+
if (c === null || typeof c !== "object" || Array.isArray(c))
|
|
226
|
+
return "compaction is not an object";
|
|
227
|
+
const { passes, breakerOpen, kind } = c;
|
|
228
|
+
if (typeof passes !== "number" || !Number.isInteger(passes) || passes < 0)
|
|
229
|
+
return "compaction.passes is not a count";
|
|
230
|
+
if (typeof breakerOpen !== "boolean")
|
|
231
|
+
return "compaction.breakerOpen is not a boolean";
|
|
232
|
+
if (kind !== undefined && !(typeof kind === "string" && COMPACTION_KINDS.includes(kind)))
|
|
233
|
+
return "compaction.kind outside its closed set";
|
|
234
|
+
}
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
export function engineFrameRecordOf(payload, priority, box) {
|
|
238
|
+
return {
|
|
239
|
+
content: encodeEngineFrame(payload, priority),
|
|
240
|
+
sentAt: Date.now(),
|
|
241
|
+
peerMeta: { fromRoot: box.sessionId, kind: "engine_frame" },
|
|
242
|
+
};
|
|
243
|
+
}
|
|
115
244
|
const MAX_TOMBSTONES = 5_000;
|
|
116
245
|
const TERMINAL_STATUSES = new Set(["completed", "failed", "killed", "cancelled"]);
|
|
117
246
|
export function isDelegatedAgentTerminal(n) {
|
|
@@ -327,6 +456,24 @@ export class PendingSessionNotifications {
|
|
|
327
456
|
return this.sessions.size;
|
|
328
457
|
}
|
|
329
458
|
}
|
|
459
|
+
export class DurableAppendObligations {
|
|
460
|
+
static scope = new AsyncLocalStorage();
|
|
461
|
+
inFlight = new Set();
|
|
462
|
+
static current() {
|
|
463
|
+
return DurableAppendObligations.scope.getStore();
|
|
464
|
+
}
|
|
465
|
+
run(body) {
|
|
466
|
+
return DurableAppendObligations.scope.run(this, body);
|
|
467
|
+
}
|
|
468
|
+
track(p) {
|
|
469
|
+
this.inFlight.add(p);
|
|
470
|
+
void p.finally(() => this.inFlight.delete(p));
|
|
471
|
+
}
|
|
472
|
+
async settle() {
|
|
473
|
+
while (this.inFlight.size > 0)
|
|
474
|
+
await Promise.allSettled([...this.inFlight]);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
330
477
|
export function routeOrParkNotification(store, sessionId, payload, priority, onDisposition) {
|
|
331
478
|
const dedupKey = taskNotificationDedupKey(payload);
|
|
332
479
|
if (store.holdsKey(sessionId, dedupKey)) {
|
|
@@ -337,9 +484,10 @@ export function routeOrParkNotification(store, sessionId, payload, priority, onD
|
|
|
337
484
|
if (routed === "unavailable") {
|
|
338
485
|
const box = store.sessionBoxFor(sessionId);
|
|
339
486
|
const carrier = payload.agentMessage;
|
|
340
|
-
|
|
487
|
+
const durable = box !== undefined && (carrier !== undefined || (box.engineFrames === true && isEngineFrameParkable(payload)));
|
|
488
|
+
if (box !== undefined && durable) {
|
|
341
489
|
store.noteDurablePark(sessionId, dedupKey);
|
|
342
|
-
const record = sessionBoxRecordOf({ ...payload, agentMessage: carrier }, box);
|
|
490
|
+
const record = carrier !== undefined ? sessionBoxRecordOf({ ...payload, agentMessage: carrier }, box) : engineFrameRecordOf(payload, priority, box);
|
|
343
491
|
let appended;
|
|
344
492
|
try {
|
|
345
493
|
appended = Promise.resolve(box.mailbox.append(box.scope, box.handle, record));
|
|
@@ -347,7 +495,7 @@ export function routeOrParkNotification(store, sessionId, payload, priority, onD
|
|
|
347
495
|
catch (e) {
|
|
348
496
|
appended = Promise.reject(e);
|
|
349
497
|
}
|
|
350
|
-
appended.then(() => onDisposition?.("parked_durably"), (e) => {
|
|
498
|
+
const settled = appended.then(() => onDisposition?.("parked_durably"), (e) => {
|
|
351
499
|
store.forgetDurablePark(sessionId, dedupKey);
|
|
352
500
|
try {
|
|
353
501
|
box.onError?.(e);
|
|
@@ -357,6 +505,7 @@ export function routeOrParkNotification(store, sessionId, payload, priority, onD
|
|
|
357
505
|
store.pend(sessionId, payload, priority);
|
|
358
506
|
onDisposition?.("parked");
|
|
359
507
|
});
|
|
508
|
+
DurableAppendObligations.current()?.track(settled);
|
|
360
509
|
return;
|
|
361
510
|
}
|
|
362
511
|
store.pend(sessionId, payload, priority);
|
|
@@ -198,10 +198,20 @@ export interface TaskResult {
|
|
|
198
198
|
* {@link import("./runner/terminal-projection.js").terminalProjection} — the one cause→word derivation.
|
|
199
199
|
*/
|
|
200
200
|
terminal: TerminalCause;
|
|
201
|
-
/** design/99 MF-25: the EFFECTIVE model id
|
|
202
|
-
* `TaskSpec.model` ref (
|
|
203
|
-
*
|
|
201
|
+
/** design/99 MF-25: the EFFECTIVE model id this task ran on — the ENGINE-SELECTED, RESOLVED `Model.id`, not
|
|
202
|
+
* the requested `TaskSpec.model` ref (a role / name / `@mention`). A mid-run degradation is observed
|
|
203
|
+
* separately (the degraded-model fields); what the PROVIDER called it rides {@link responseModel}. */
|
|
204
204
|
model?: string;
|
|
205
|
+
/** The PROVIDER'S OWN name for the model that answered this run's FINAL turn, when it DIFFERS from the
|
|
206
|
+
* id the request was sent with — the result-face twin of `turn_end.responseModel`. {@link model} above
|
|
207
|
+
* stays the ENGINE-SELECTED id a resume re-seeds and a catalog resolves; a UI echoing "served by X"
|
|
208
|
+
* prints this one when it is here and {@link model} when it is not. **In-presence condition**: the final
|
|
209
|
+
* assistant message carried one. ABSENT ⇒ the provider named the requested id or named nothing (one
|
|
210
|
+
* reading, not two), there was no final assistant message, or the final turn AGREED while an earlier one
|
|
211
|
+
* did not — the per-turn frames are the complete record, this is the terminal one. Never present on the
|
|
212
|
+
* engine's synthetic messages, which carry no provider report; a mismatch is announced either way as
|
|
213
|
+
* `route.response_model_mismatch`. */
|
|
214
|
+
responseModel?: string;
|
|
205
215
|
/**
|
|
206
216
|
* The leg's effective REASONING resolution: the result-face twin of {@link model} for the thinking
|
|
207
217
|
* knob. How the requested tier resolved against the serving model's real capability
|
|
@@ -264,6 +274,20 @@ export interface TaskResult {
|
|
|
264
274
|
* Undefined on every other terminal and when there was nothing to salvage.
|
|
265
275
|
*/
|
|
266
276
|
salvagedOutput?: string;
|
|
277
|
+
/**
|
|
278
|
+
* Present iff this run compacted its context at least once at a turn boundary or a prompt-too-long
|
|
279
|
+
* recovery, or opened its compaction breaker: `passes` = landed compactions, `kind` = the last landed
|
|
280
|
+
* pass's trigger (`reactive` is the wire's `forced`), `breakerOpen` = compaction was disabled for the
|
|
281
|
+
* rest of the run (later context was trimmed away, not summarized). The end-of-task settle pass is
|
|
282
|
+
* NOT counted (ruled): it summarizes AFTER the work, for the next run, and says nothing about what this
|
|
283
|
+
* run's model saw. Projected onto a delegated child's terminal frame so
|
|
284
|
+
* the parent can tell that a child forgot.
|
|
285
|
+
*/
|
|
286
|
+
compaction?: {
|
|
287
|
+
passes: number;
|
|
288
|
+
breakerOpen: boolean;
|
|
289
|
+
kind?: import("./task-notification.js").CompactionKind;
|
|
290
|
+
};
|
|
267
291
|
/**
|
|
268
292
|
* The remote-workspace lifecycle failures this run hit, as DATA. The seam declares eleven
|
|
269
293
|
* distinct `RemoteExecutionErrorCode`s, but every one of them used to reach the caller as the same
|
package/dist/core/task-spec.d.ts
CHANGED
|
@@ -554,6 +554,13 @@ export interface TaskSpec {
|
|
|
554
554
|
* interception, prompt gating, stop pushback, compaction taps, permission-denied observation; each
|
|
555
555
|
* member's contract is on the interface). Overrides `RunnerDeps.hooks`. */
|
|
556
556
|
hooks?: import("./hooks.js").Hooks;
|
|
557
|
+
/**
|
|
558
|
+
* The session's SELF-REPORTED presence for the peer directory (`agent` = what it is, `intent` = what
|
|
559
|
+
* it is doing; each ≤ 120 chars). The engine never writes a directory row: the same object goes out
|
|
560
|
+
* on the leg's `wiring_manifest` so a host copies it into the row it writes, and `ListAgents` renders
|
|
561
|
+
* it on other sessions' listings prefixed `self:`. Labels only — never read by any judgment.
|
|
562
|
+
*/
|
|
563
|
+
peerPresence?: import("../agents/peer-directory.js").PeerPresence;
|
|
557
564
|
/**
|
|
558
565
|
* design/45 **durable suspend-on-approval** (F4). When set AND a `CheckpointStore` is wired
|
|
559
566
|
* (`checkpointStore` here or on `RunnerDeps`), a tool-policy `ask` **can suspend durably**: when no LIVE
|
|
@@ -111,7 +111,8 @@ export const TOOL_CATALOG_ENTRIES = [
|
|
|
111
111
|
{ id: "monitor", name: "Monitor", source: "builtin", definedIn: "src/tools/monitor.ts", mountedBy: ["backgroundTasks"], cards: ["monitor-start", "readonly_out_of_root"], face: { family: "other", effect: "write", contract: c("core.monitor@1"), offloadThresholdChars: 10_000 } },
|
|
112
112
|
{ id: "send-message", name: "SendMessage", source: "builtin", definedIn: "src/agents/send-message-tool.ts", mountedBy: ["delegation", "hostInternals", "peerLane"], cards: ["send-message"], face: { family: "protocol", effect: "write", contract: c("core.send_message@1") } },
|
|
113
113
|
{ id: "agent-transcript", name: "AgentTranscript", source: "builtin", definedIn: "src/agents/agent-transcript-tool.ts", mountedBy: ["delegation"], cards: ["agent-transcript"], face: { family: "delegate", effect: "read", contract: c("core.agent_transcript@1") } },
|
|
114
|
-
{ id: "list-agents", name: "ListAgents", source: "builtin", definedIn: "src/agents/list-agents-tool.ts", mountedBy: ["peerLane"], cards: ["list-agents"], face: { family: "protocol", effect: "read", aliases: ["ListPeers"], contract: c("core.list_agents@1") } },
|
|
114
|
+
{ id: "list-agents", name: "ListAgents", source: "builtin", definedIn: "src/agents/list-agents-tool.ts", mountedBy: ["delegation", "peerLane"], cards: ["list-agents"], face: { family: "protocol", effect: "read", aliases: ["ListPeers"], contract: c("core.list_agents@1") } },
|
|
115
|
+
{ id: "read-inbox", name: "ReadInbox", source: "builtin", definedIn: "src/agents/read-inbox-tool.ts", mountedBy: ["peerLane"], cards: ["read-inbox"], face: { family: "protocol", effect: "read", contract: c("core.read_inbox@1"), offload: false, budgetExempt: true } },
|
|
115
116
|
{ id: "workflow", name: "Workflow", source: "builtin", definedIn: "src/orchestration/run-workflow-tool.ts", mountedBy: ["workflow"], cards: ["workflow-run"], face: { family: "delegate", effect: "write", contract: c("core.workflow@1") } },
|
|
116
117
|
{ id: "report-blocked", name: "ReportBlocked", source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["always"], cards: [], face: { family: "scaffold", effect: "read", contract: c("core.report_blocked@1") } },
|
|
117
118
|
{ id: "report-findings", name: "ReportFindings", source: "builtin", definedIn: "src/core/runner/synthetic-tools.ts", mountedBy: ["always"], cards: ["report-findings"], face: { family: "scaffold", effect: "read", contract: c("core.report_findings@1") } },
|
|
@@ -13,6 +13,7 @@ export const TOOL_CONFORMANCE_VECTORS = [
|
|
|
13
13
|
vector("hands-full", ["always", "hands", "backgroundTasks", "delegation"], [
|
|
14
14
|
{ name: "SendMessage", why: "needs the runner self seat (a direct prepare has none)" },
|
|
15
15
|
{ name: "AgentTranscript", why: "needs the runner self seat (a direct prepare has none)" },
|
|
16
|
+
{ name: "ListAgents", why: "mounts with the delegation face only under a runner self seat (a direct prepare has none)" },
|
|
16
17
|
], [OFFLOAD_READER]),
|
|
17
18
|
vector("hands-readonly", ["always", "hands"], [
|
|
18
19
|
{ name: "Edit", why: "the read-only band mounts the read half only" },
|
|
@@ -321,8 +321,12 @@ export type PermissionResult = {
|
|
|
321
321
|
* in the one direction the stamp cannot reach (a caller composing its own ask) the claim is
|
|
322
322
|
* tightening-only: it can make an ask outlive a rule, never turn one into an allow. */
|
|
323
323
|
probeMandated?: true;
|
|
324
|
-
/** This ask is MANDATED — no persisted allow rule and no
|
|
325
|
-
*
|
|
324
|
+
/** This ask is MANDATED — no persisted allow rule and no STANDING ANSWER (a release remembered from an
|
|
325
|
+
* earlier call, at any scope, a session included) may retire it; only an answer produced for THIS call
|
|
326
|
+
* clears it — a person, a hook, a deployment's blanket `onAsk: "allow"` seat, or the live deciders the
|
|
327
|
+
* mandate tables admit ({@link import("./ask-origin.js").classifierMayAnswerMandate} /
|
|
328
|
+
* `sandboxMayAdmitMandate`); {@link requiresRealApproval} is the stronger demand that also refuses the
|
|
329
|
+
* blanket seat. ENGINE-STAMPED at the gate's single origin stamp from the ONE derivation
|
|
326
330
|
* the approval card's rule-offer word also reads ({@link import("./ask-origin.js").ruleClearanceOf},
|
|
327
331
|
* which lists the doors). TIGHTENING-ONLY: a layer below the mint may not clear one a layer above
|
|
328
332
|
* set, and a policy self-declaring it only opts its own ask OUT of automatic answering. Carried
|
|
@@ -1259,8 +1263,9 @@ export interface AskRequest {
|
|
|
1259
1263
|
* caller/worker-settable field. */
|
|
1260
1264
|
readonly requiresRealApproval?: boolean;
|
|
1261
1265
|
/**
|
|
1262
|
-
* PRESENCE ONLY, and a FACT ABOUT THIS ASK: no persisted allow rule and no
|
|
1263
|
-
*
|
|
1266
|
+
* PRESENCE ONLY, and a FACT ABOUT THIS ASK: no persisted allow rule and no STANDING ANSWER — a release
|
|
1267
|
+
* remembered from an earlier call, at any scope, a session included — can retire this question; it is
|
|
1268
|
+
* answered only for THIS call, by an answerer (see the ask-arm member of the same name for who may). ABSENT is not the inverse claim: a person's own explicit `ask` rule and a
|
|
1264
1269
|
* matching allow rule the lane already refused both leave it absent and still block rule clearance
|
|
1265
1270
|
* ({@link ruleOffersAbsence} words those `shadowed`). The wire twin of the
|
|
1266
1271
|
* {@link PermissionResult} ask-arm member of the same name, minted once by the engine at the gate's
|
package/dist/core/tool-spec.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { AgentDefinition } from "./agent-definition.js";
|
|
|
14
14
|
import type { TaskEvent } from "./task-event.js";
|
|
15
15
|
import type { TaskSpec } from "./task-spec.js";
|
|
16
16
|
import type { BackgroundChildEvent } from "./delegation-frames.js";
|
|
17
|
+
import type { ModelRef } from "./model-seat.js";
|
|
17
18
|
import type { ToolFamily, ToolPathTarget, ToolRenderHints, ToolRuleFace } from "./tool-face.js";
|
|
18
19
|
/**
|
|
19
20
|
* Side-effect class of a tool. Used by wake/resume reconciliation: when a call was interrupted
|
|
@@ -819,6 +820,19 @@ export interface ToolExecuteContext {
|
|
|
819
820
|
* the tool runs outside a Runner task.
|
|
820
821
|
*/
|
|
821
822
|
model?: Model;
|
|
823
|
+
/**
|
|
824
|
+
* WHICH REF chose {@link model} — the catalog word the parent leg is running under, or the `Model`
|
|
825
|
+
* object itself when no word exists (an object-pinned spec, a `select`-criteria role). Snapshotted at
|
|
826
|
+
* the same call-time instant as {@link model}, and its companion in the same sense {@link thinkingLevel}
|
|
827
|
+
* is: one read, one moment.
|
|
828
|
+
*
|
|
829
|
+
* A delegation tool inheriting the caller's model reads THIS, not {@link model}: a word resolves against
|
|
830
|
+
* the catalog generation in force at the CHILD's own spawn, so a child spawned after a catalog hot-swap
|
|
831
|
+
* lands on the current generation instead of on the object its parent has been holding since its own
|
|
832
|
+
* prepare (and a word the swap retired refuses loudly, exactly as a caller-written retired word does).
|
|
833
|
+
* Undefined when the tool runs outside a Runner task — the same absence {@link model} has there.
|
|
834
|
+
*/
|
|
835
|
+
modelRef?: ModelRef;
|
|
822
836
|
/**
|
|
823
837
|
* The PARENT task's current thinking level, snapshotted at call time — the {@link model} companion.
|
|
824
838
|
* A delegation tool threads it into the child so a sub-agent inherits the caller's thinking depth by
|
|
@@ -158,6 +158,14 @@ export interface WiringManifest {
|
|
|
158
158
|
leg?: {
|
|
159
159
|
kind: WiringLegKind;
|
|
160
160
|
};
|
|
161
|
+
/** Effective half only — the leg's team role ({@link import("../agents/team-role.js").teamRoleOf}):
|
|
162
|
+
* `lead` for a top-level leg, `teammate` for a named delegation, `none` for an unnamed one. Leg-local
|
|
163
|
+
* (excluded from the fingerprint). */
|
|
164
|
+
teamRole?: import("../agents/team-role.js").TeamRole;
|
|
165
|
+
/** Effective half only, present iff the spec reported one — the session's self-reported presence,
|
|
166
|
+
* the very object `TaskSpec.peerPresence` carried, for a host to copy into the directory row it
|
|
167
|
+
* writes without parsing the spec. Leg-local (excluded from the fingerprint). */
|
|
168
|
+
peerPresence?: import("../agents/peer-directory.js").PeerPresence;
|
|
161
169
|
ask: {
|
|
162
170
|
form: AskSeamForm;
|
|
163
171
|
/** Absent iff `form === "absent"` (no seat supplied a value — a fabricated provenance would be
|
|
@@ -413,6 +421,10 @@ export interface WiringFacts {
|
|
|
413
421
|
half: "static" | "effective";
|
|
414
422
|
/** Required on the effective half; must be absent on the static half. */
|
|
415
423
|
leg?: WiringLegKind;
|
|
424
|
+
/** Effective half only — see {@link WiringManifest.teamRole}. */
|
|
425
|
+
teamRole?: import("../agents/team-role.js").TeamRole;
|
|
426
|
+
/** Effective half only — see {@link WiringManifest.peerPresence}; copied, never aliased. */
|
|
427
|
+
peerPresence?: import("../agents/peer-directory.js").PeerPresence;
|
|
416
428
|
askForm: AskSeamForm;
|
|
417
429
|
askProvenance?: SeamProvenance;
|
|
418
430
|
questionWired: boolean;
|
|
@@ -146,9 +146,13 @@ export function deriveWiringManifest(facts) {
|
|
|
146
146
|
? { readDeny: { builtinTiers: [...facts.readDenyBuiltinTiersActive] } }
|
|
147
147
|
: {}),
|
|
148
148
|
...(facts.half === "effective" && facts.autoConsolidationOnRecommendation === true ? { autoConsolidation: { onRecommendation: true } } : {}),
|
|
149
|
+
...(facts.half === "effective" && facts.teamRole !== undefined ? { teamRole: facts.teamRole } : {}),
|
|
150
|
+
...(facts.half === "effective" && facts.peerPresence !== undefined && (facts.peerPresence.agent !== undefined || facts.peerPresence.intent !== undefined)
|
|
151
|
+
? { peerPresence: { ...(facts.peerPresence.agent !== undefined ? { agent: facts.peerPresence.agent } : {}), ...(facts.peerPresence.intent !== undefined ? { intent: facts.peerPresence.intent } : {}) } }
|
|
152
|
+
: {}),
|
|
149
153
|
};
|
|
150
154
|
if (facts.half === "effective") {
|
|
151
|
-
const { leg: _leg, mcp: _mcp, tools: _tools, lsp: _lsp, hooks: _hooks, ...assembly } = manifest;
|
|
155
|
+
const { leg: _leg, mcp: _mcp, tools: _tools, lsp: _lsp, hooks: _hooks, teamRole: _teamRole, peerPresence: _peerPresence, ...assembly } = manifest;
|
|
152
156
|
const { provenance: _askSeat, ...askForHash } = assembly.ask;
|
|
153
157
|
const { provenance: _questionSeat, ...questionForHash } = assembly.question;
|
|
154
158
|
manifest.configFingerprint = createHash("sha256")
|