@sema-agent/core 7.56.0 → 7.57.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 +541 -0
- package/LICENSE +1 -1
- package/dist/agents/org-delivery-gate.js +3 -4
- package/dist/agents/peer-held-queue.d.ts +69 -49
- package/dist/agents/peer-held-queue.js +255 -106
- package/dist/agents/peer-session-drain.js +63 -43
- package/dist/agents/send-message-tool.js +3 -2
- package/dist/agents/subagent.js +8 -8
- package/dist/brain/anthropic.js +16 -8
- package/dist/brain/open-responses.js +10 -8
- package/dist/brain/openai.js +11 -9
- package/dist/brain/provider-dialect.d.ts +3 -2
- package/dist/brain/provider-dialect.js +2 -2
- package/dist/brain/request-params.d.ts +6 -0
- package/dist/brain/request-params.js +14 -1
- package/dist/brain/stream-engine.d.ts +9 -0
- package/dist/brain/stream-engine.js +24 -10
- package/dist/brain/stream-shared.d.ts +5 -4
- package/dist/brain/stream-shared.js +4 -4
- package/dist/core/allow-entry.d.ts +3 -10
- package/dist/core/allow-entry.js +17 -25
- package/dist/core/approval-object.d.ts +21 -5
- package/dist/core/approval-object.js +44 -17
- package/dist/core/ask-origin.d.ts +2 -2
- package/dist/core/auto-compaction.d.ts +71 -6
- package/dist/core/auto-compaction.js +42 -26
- package/dist/core/background-agent-store.d.ts +6 -2
- package/dist/core/background-agent-store.js +1 -1
- package/dist/core/checkpoint-requirements.d.ts +7 -1
- package/dist/core/checkpoint-requirements.js +2 -0
- package/dist/core/checkpoint-store.d.ts +17 -29
- package/dist/core/checkpoint-store.js +19 -11
- package/dist/core/cross-boundary-taint.js +4 -2
- package/dist/core/engine-notice.d.ts +3 -2
- package/dist/core/engine-notice.js +6 -0
- package/dist/core/file-history-store.d.ts +14 -8
- package/dist/core/file-history-store.js +14 -4
- package/dist/core/gate-lanes.js +2 -3
- package/dist/core/hooks.d.ts +2 -2
- package/dist/core/mailbox-store.d.ts +62 -1
- package/dist/core/mailbox-store.js +59 -0
- package/dist/core/mcp-name-judge.d.ts +46 -0
- package/dist/core/mcp-name-judge.js +61 -0
- package/dist/core/mcp-probe.d.ts +1 -1
- package/dist/core/mcp-probe.js +2 -5
- package/dist/core/mcp.js +10 -30
- package/dist/core/memory-engine/consolidation-driver.js +4 -3
- package/dist/core/memory-engine/distiller.js +26 -17
- package/dist/core/memory-engine/layout.js +5 -2
- package/dist/core/park-selfcheck.js +2 -0
- package/dist/core/permission-rule-consent.d.ts +5 -3
- package/dist/core/permission-rule-consent.js +20 -15
- package/dist/core/permission-rule-model.d.ts +43 -2
- package/dist/core/permission-rule-model.js +52 -8
- package/dist/core/permission-rule-org.js +1 -1
- package/dist/core/protocol-http-auth.d.ts +16 -2
- package/dist/core/protocol-http-auth.js +42 -11
- package/dist/core/protocol-naming.d.ts +20 -0
- package/dist/core/protocol-naming.js +20 -3
- package/dist/core/read-only-shell.d.ts +20 -5
- package/dist/core/read-only-shell.js +29 -14
- package/dist/core/remote-env.d.ts +37 -1
- package/dist/core/remote-env.js +11 -0
- package/dist/core/resume-startup-cause.d.ts +24 -2
- package/dist/core/resume-startup-cause.js +20 -1
- package/dist/core/roles.js +50 -7
- package/dist/core/runner/compact-session.d.ts +10 -0
- package/dist/core/runner/compact-session.js +202 -0
- package/dist/core/runner/compaction-call-options.d.ts +2 -2
- package/dist/core/runner/compaction-seams.d.ts +21 -1
- package/dist/core/runner/compaction-seams.js +11 -8
- package/dist/core/runner/contracts.d.ts +23 -16
- package/dist/core/runner/derived-route-fallback.d.ts +13 -0
- package/dist/core/runner/derived-route-fallback.js +6 -0
- package/dist/core/runner/env-capability-census.d.ts +3 -2
- package/dist/core/runner/env-capability-census.js +9 -3
- package/dist/core/runner/permission-rule-lanes.d.ts +7 -7
- package/dist/core/runner/permission-rule-lanes.js +17 -11
- package/dist/core/runner/prepare-acquire-reconcile.d.ts +3 -0
- package/dist/core/runner/prepare-acquire-reconcile.js +2 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +4 -3
- package/dist/core/runner/prepare-hands-readface.d.ts +3 -1
- package/dist/core/runner/prepare-hands-readface.js +2 -0
- package/dist/core/runner/prepare-park-ask.d.ts +2 -0
- package/dist/core/runner/prepare-park-ask.js +2 -2
- package/dist/core/runner/prepare-policy-chain.js +2 -4
- package/dist/core/runner/prepare-task.d.ts +4 -1
- package/dist/core/runner/prepare-task.js +23 -27
- package/dist/core/runner/prepare-tool-disclosure-mount.d.ts +13 -0
- package/dist/core/runner/prepare-tool-disclosure-mount.js +13 -7
- package/dist/core/runner/rewind-files.d.ts +2 -5
- package/dist/core/runner/rewind-files.js +9 -51
- package/dist/core/runner/run-leg.js +10 -21
- package/dist/core/runner/run-notification-lane.d.ts +2 -0
- package/dist/core/runner/run-notification-lane.js +2 -2
- package/dist/core/runner/run-recovery-lanes.js +3 -4
- package/dist/core/runner/run-settle-and-teardown.js +5 -11
- package/dist/core/runner/run-terminal-adoption.js +3 -4
- package/dist/core/runner/run-turn-boundary.js +3 -5
- package/dist/core/runner/runtask.d.ts +8 -1
- package/dist/core/runner/runtask.js +28 -34
- package/dist/core/runner/send-message-continuation.js +1 -1
- package/dist/core/runner/session-verb-doors.d.ts +28 -0
- package/dist/core/runner/session-verb-doors.js +48 -0
- package/dist/core/runner/session-write-ledger.d.ts +30 -0
- package/dist/core/runner/session-write-ledger.js +101 -0
- package/dist/core/runner/stream-settle-backstop.js +11 -14
- package/dist/core/runner/sub-agent-deny-lists.d.ts +25 -3
- package/dist/core/runner/sub-agent-deny-lists.js +21 -10
- package/dist/core/runner/teardown-bounded.d.ts +1 -0
- package/dist/core/runner/teardown-bounded.js +1 -0
- package/dist/core/runner-deps.d.ts +2 -2
- package/dist/core/secret-env.d.ts +4 -0
- package/dist/core/secret-env.js +3 -0
- package/dist/core/session-policy-store.d.ts +7 -2
- package/dist/core/session-policy-store.js +1 -1
- package/dist/core/shell-wrapper-projection.d.ts +1 -1
- package/dist/core/store-contracts/background-agent-store-contract.js +1 -0
- package/dist/core/store-contracts/checkpoint-store-contract.js +2 -1
- package/dist/core/store-contracts/file-history-store-contract.js +9 -0
- package/dist/core/store-contracts/mailbox-store-contract.js +49 -1
- package/dist/core/task-registry-agent.d.ts +8 -4
- package/dist/core/task-registry-agent.js +14 -8
- package/dist/core/task-registry-shared.d.ts +35 -6
- package/dist/core/task-registry-shared.js +11 -0
- package/dist/core/task-registry.d.ts +1 -1
- package/dist/core/task-registry.js +1 -1
- package/dist/core/tool-catalog-entries.d.ts +6 -0
- package/dist/core/tool-catalog-entries.js +1 -0
- package/dist/core/tool-name-aliases.d.ts +32 -2
- package/dist/core/tool-name-aliases.js +23 -0
- package/dist/core/tool-name-readings.d.ts +4 -0
- package/dist/core/tool-name-readings.js +1 -0
- package/dist/core/tool-policy.d.ts +10 -6
- package/dist/core/tool-policy.js +4 -7
- package/dist/core/trace.d.ts +4 -1
- package/dist/core/trace.js +2 -1
- package/dist/core/untrusted-egress.d.ts +20 -0
- package/dist/core/untrusted-egress.js +77 -3
- package/dist/engine/harness/agent-harness.d.ts +7 -5
- package/dist/engine/harness/agent-harness.js +11 -2
- package/dist/engine/loop/agent-loop.d.ts +1 -4
- package/dist/engine/loop/agent-loop.js +26 -4
- package/dist/index.d.ts +12 -7
- package/dist/index.js +7 -4
- package/dist/internal/harness.d.ts +2 -1
- package/dist/internal/harness.js +1 -0
- package/dist/primitives/credential-ref.d.ts +89 -0
- package/dist/primitives/credential-ref.js +125 -0
- package/dist/primitives/index.d.ts +2 -0
- package/dist/primitives/index.js +1 -0
- package/dist/stores/file/checkpoint-store.d.ts +0 -4
- package/dist/stores/file/checkpoint-store.js +0 -1
- package/dist/stores/file/file-history-store.js +12 -4
- package/dist/stores/file/mailbox-store.d.ts +2 -1
- package/dist/stores/file/mailbox-store.js +26 -1
- package/dist/tools/fs/bash-readonly-classifier.d.ts +15 -19
- package/dist/tools/fs/bash-readonly-classifier.js +5 -10
- package/dist/tools/fs/fs-bash.js +10 -3
- package/dist/tools/fs/pattern-kill-guard.d.ts +3 -2
- package/dist/tools/fs/pattern-kill-guard.js +68 -36
- package/dist/tools/fs/shell-read-check.d.ts +10 -0
- package/dist/tools/fs/shell-read-check.js +7 -1
- package/dist/tools/fs/shell-read-identity.d.ts +12 -9
- package/dist/tools/fs/shell-read-identity.js +9 -4
- package/dist/tools/web.js +9 -7
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +79 -3
- package/wire-changes/core-stores-sql@7.57.0.json +29 -0
- package/wire-changes/core@7.57.0.json +1487 -0
- package/wire-changes/mods-kernel@7.57.0.json +29 -0
- package/wire-changes/primitives@7.57.0.json +245 -0
|
@@ -18,7 +18,56 @@ export interface MailboxMessage {
|
|
|
18
18
|
* ABSENT = a pre-385 or foreign record (same semantics as an absent `hopChain`): the drain treats it
|
|
19
19
|
* as foreign, never retro-admits it. Validated at `append` ({@link readMailboxPeerMeta}). */
|
|
20
20
|
peerMeta?: MailboxPeerMeta;
|
|
21
|
+
/** The record's HOLD DECISION ({@link MailboxDecision}): absent = never held. Never appended — it
|
|
22
|
+
* starts absent and moves only through {@link MailboxStore.moveDecision}; every lease snapshot
|
|
23
|
+
* carries it, so every consumer of the box reads the same decision. */
|
|
24
|
+
decision?: MailboxDecision;
|
|
21
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* A parked record's hold decision — the durable answer about a record a recipient's setting put on
|
|
28
|
+
* hold, kept ON the record so every consumer of the box (another terminal, another process, the session's
|
|
29
|
+
* next run) reads the same answer. `held` — waiting for a person; `approved` — a person approved it, not yet
|
|
30
|
+
* handed over; `delivered` — a consumer holding the lease took the move and handed it over (a later
|
|
31
|
+
* answer about it learns that instead of claiming a decision); `denied` / `expired` / `refused` /
|
|
32
|
+
* `dropped` — final: the record is never delivered and only waits for the ack that removes it.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MAILBOX_DECISIONS: readonly ["held", "approved", "delivered", "denied", "expired", "refused", "dropped"];
|
|
35
|
+
export type MailboxDecision = (typeof MAILBOX_DECISIONS)[number];
|
|
36
|
+
/** A stored decision word as a reader takes it: a word this engine does not know (a newer engine's) reads
|
|
37
|
+
* as `held` — never delivered on a guess, and left for a person (or that newer engine) to decide. */
|
|
38
|
+
export declare function readStoredMailboxDecision(raw: unknown): MailboxDecision | undefined;
|
|
39
|
+
/** The refusal code of a malformed {@link MailboxStore.moveDecision} call (a word outside the
|
|
40
|
+
* closed set, a non-positive seq, or an edge the decision law does not allow). */
|
|
41
|
+
export declare const MAILBOX_INVALID_DECISION_MOVE_CODE = "mailbox.invalid_decision_move";
|
|
42
|
+
export declare function isMailboxDecision(value: unknown): value is MailboxDecision;
|
|
43
|
+
/** Final states: the record is never delivered again, by any consumer. */
|
|
44
|
+
export declare function isMailboxDecisionFinal(state: MailboxDecision | undefined): boolean;
|
|
45
|
+
/** Deliverable states: a consumer holding the lease delivers the record (a setting that now refuses still wins). */
|
|
46
|
+
export declare function isMailboxDecisionDeliverable(state: MailboxDecision | undefined): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* THE decision law — a decision only moves forward, and every hand-over of a held record is itself a move:
|
|
49
|
+
* absent → `held`; `held` → `approved` / `delivered` / `denied` / `expired` / `refused`; `approved` →
|
|
50
|
+
* `delivered` or a final state; `delivered` → `dropped` (the handed-over frame was refused by policy inside a lease-holding pass) only;
|
|
51
|
+
* a final state never moves.
|
|
52
|
+
*/
|
|
53
|
+
export declare function mailboxDecisionEdgeAllowed(from: MailboxDecision | undefined, to: MailboxDecision): boolean;
|
|
54
|
+
/** What {@link MailboxStore.moveDecision} answers: the move happened, or the record's actual state
|
|
55
|
+
* (`unset` = never held, `gone` = no such record any more — acked, reaped or dropped). */
|
|
56
|
+
export type MailboxDecisionOutcome = {
|
|
57
|
+
readonly ok: true;
|
|
58
|
+
} | {
|
|
59
|
+
readonly ok: false;
|
|
60
|
+
readonly current: MailboxDecision | "unset" | "gone";
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* THE caller-side reading of {@link MailboxStore.moveDecision}: a fault can arrive AFTER the move
|
|
64
|
+
* committed (a lock release that failed, a reply lost on the wire), so a call that throws is asked once
|
|
65
|
+
* more, and a store that already holds `next` answers the first call's success. A second fault throws.
|
|
66
|
+
* Every engine-side decision move goes through this one function.
|
|
67
|
+
*/
|
|
68
|
+
export declare function moveMailboxDecision(store: Pick<MailboxStore, "moveDecision">, scope: string, handle: string, seq: number, expect: MailboxDecision | undefined, next: MailboxDecision): Promise<MailboxDecisionOutcome>;
|
|
69
|
+
/** The one validator every backend runs BEFORE touching storage: a malformed call has zero side effects. */
|
|
70
|
+
export declare function assertMailboxDecisionMove(seq: unknown, expect: unknown, next: unknown): void;
|
|
22
71
|
/** design/385 — the typed peer-metadata record on a parked message. ONE optional object, one-time
|
|
23
72
|
* contract extension; every field optional. Bundled backends persist it verbatim and hand back a
|
|
24
73
|
* detached copy. */
|
|
@@ -132,7 +181,7 @@ export declare const MAILBOX_TOMBSTONED_RECIPIENT_CODE = "mailbox.recipient_tomb
|
|
|
132
181
|
* A backend may raise the same code with a plain `Error` carrying `.code`; consumers branch on the
|
|
133
182
|
* string, not on this class (a cross-process/out-of-repo store cannot hand back an instance).
|
|
134
183
|
*/
|
|
135
|
-
export type MailboxStoreErrorCode = typeof MAILBOX_TOMBSTONED_RECIPIENT_CODE | typeof MAILBOX_INVALID_PEER_META_CODE | typeof MAILBOX_CROSS_PROCESS_UNSAFE_CODE;
|
|
184
|
+
export type MailboxStoreErrorCode = typeof MAILBOX_TOMBSTONED_RECIPIENT_CODE | typeof MAILBOX_INVALID_PEER_META_CODE | typeof MAILBOX_CROSS_PROCESS_UNSAFE_CODE | typeof MAILBOX_INVALID_DECISION_MOVE_CODE;
|
|
136
185
|
export declare class MailboxStoreError extends Error {
|
|
137
186
|
readonly code: MailboxStoreErrorCode;
|
|
138
187
|
constructor(code: MailboxStoreErrorCode, message: string);
|
|
@@ -241,6 +290,17 @@ export interface MailboxStore {
|
|
|
241
290
|
* Implementations report `{acked:true}` only when they actually consumed the prefix.
|
|
242
291
|
*/
|
|
243
292
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
|
|
293
|
+
/**
|
|
294
|
+
* Move one record's hold decision from `expect` (absent = never held) to `next`, atomically
|
|
295
|
+
* against every other transition and against `ack` — the compare-and-set every decision about a held
|
|
296
|
+
* record goes through. NOT fenced by the lease: a person answers from whichever process shows the
|
|
297
|
+
* record, whether or not it holds the box. A state that is not `expect` answers `{ ok: false, current }`
|
|
298
|
+
* and changes nothing; a record no longer in the box answers `current: "gone"`. A malformed call (see
|
|
299
|
+
* {@link assertMailboxDecisionMove}) is refused with {@link MAILBOX_INVALID_DECISION_MOVE_CODE}
|
|
300
|
+
* before any side effect. The state is durable like the record, and `ack` / `reap` / `drop` remove it
|
|
301
|
+
* with the record.
|
|
302
|
+
*/
|
|
303
|
+
moveDecision(scope: string, handle: string, seq: number, expect: MailboxDecision | undefined, next: MailboxDecision): Promise<MailboxDecisionOutcome>;
|
|
244
304
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
245
305
|
/** Observation face (the "N pending" receipt figure) — count of currently-visible messages. */
|
|
246
306
|
peekCount(scope: string, handle: string): Promise<number>;
|
|
@@ -291,6 +351,7 @@ export declare class InMemoryMailboxStore implements MailboxStore {
|
|
|
291
351
|
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
292
352
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
293
353
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
|
|
354
|
+
moveDecision(scope: string, handle: string, seq: number, expect: MailboxDecision | undefined, next: MailboxDecision): Promise<MailboxDecisionOutcome>;
|
|
294
355
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
295
356
|
peekCount(scope: string, handle: string): Promise<number>;
|
|
296
357
|
drop(scope: string, handle: string): Promise<void>;
|
|
@@ -1,4 +1,53 @@
|
|
|
1
1
|
import { assertRetentionPolicy } from "./retention-policy.js";
|
|
2
|
+
export const MAILBOX_DECISIONS = Object.freeze(["held", "approved", "delivered", "denied", "expired", "refused", "dropped"]);
|
|
3
|
+
export function readStoredMailboxDecision(raw) {
|
|
4
|
+
if (raw === undefined)
|
|
5
|
+
return undefined;
|
|
6
|
+
return isMailboxDecision(raw) ? raw : "held";
|
|
7
|
+
}
|
|
8
|
+
export const MAILBOX_INVALID_DECISION_MOVE_CODE = "mailbox.invalid_decision_move";
|
|
9
|
+
export function isMailboxDecision(value) {
|
|
10
|
+
return typeof value === "string" && MAILBOX_DECISIONS.includes(value);
|
|
11
|
+
}
|
|
12
|
+
export function isMailboxDecisionFinal(state) {
|
|
13
|
+
return state === "denied" || state === "expired" || state === "refused" || state === "dropped";
|
|
14
|
+
}
|
|
15
|
+
export function isMailboxDecisionDeliverable(state) {
|
|
16
|
+
return state === "approved" || state === "delivered";
|
|
17
|
+
}
|
|
18
|
+
export function mailboxDecisionEdgeAllowed(from, to) {
|
|
19
|
+
if (from === undefined)
|
|
20
|
+
return to === "held";
|
|
21
|
+
if (from === "held")
|
|
22
|
+
return to === "approved" || to === "delivered" || to === "denied" || to === "expired" || to === "refused";
|
|
23
|
+
if (from === "approved")
|
|
24
|
+
return to === "delivered" || isMailboxDecisionFinal(to);
|
|
25
|
+
if (from === "delivered")
|
|
26
|
+
return to === "dropped";
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
export async function moveMailboxDecision(store, scope, handle, seq, expect, next) {
|
|
30
|
+
try {
|
|
31
|
+
return await store.moveDecision(scope, handle, seq, expect, next);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
const again = await store.moveDecision(scope, handle, seq, expect, next);
|
|
35
|
+
return !again.ok && again.current === next ? { ok: true } : again;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export function assertMailboxDecisionMove(seq, expect, next) {
|
|
39
|
+
const refuse = (why) => {
|
|
40
|
+
throw new MailboxStoreError(MAILBOX_INVALID_DECISION_MOVE_CODE, `MailboxStore.moveDecision: ${why}`);
|
|
41
|
+
};
|
|
42
|
+
if (typeof seq !== "number" || !Number.isInteger(seq) || seq < 1)
|
|
43
|
+
refuse(`seq must be a positive integer (got ${String(seq)})`);
|
|
44
|
+
if (expect !== undefined && !isMailboxDecision(expect))
|
|
45
|
+
refuse(`expect must be absent or one of ${MAILBOX_DECISIONS.join("|")}`);
|
|
46
|
+
if (!isMailboxDecision(next))
|
|
47
|
+
refuse(`next must be one of ${MAILBOX_DECISIONS.join("|")}`);
|
|
48
|
+
if (!mailboxDecisionEdgeAllowed(expect, next))
|
|
49
|
+
refuse(`${String(expect ?? "unset")} → ${String(next)} is not a decision move (a decision only moves forward)`);
|
|
50
|
+
}
|
|
2
51
|
export const MAILBOX_PEER_FROM_MODES = Object.freeze(["bypass", "prompting"]);
|
|
3
52
|
export const MAILBOX_PEER_RECORD_KINDS = Object.freeze(["peer_message", "idle_notice", "delivery_notice", "idle_subscription", "engine_frame"]);
|
|
4
53
|
export const MAILBOX_PEER_NOTICE_STATES = Object.freeze(["held", "denied", "expired", "delivered", "refused", "dropped", "idle", "exited", "unavailable"]);
|
|
@@ -184,6 +233,16 @@ export class InMemoryMailboxStore {
|
|
|
184
233
|
b.lease = undefined;
|
|
185
234
|
return { acked: true };
|
|
186
235
|
}
|
|
236
|
+
async moveDecision(scope, handle, seq, expect, next) {
|
|
237
|
+
assertMailboxDecisionMove(seq, expect, next);
|
|
238
|
+
const m = this.boxes.get(this.key(scope, handle))?.messages.find((x) => x.seq === seq);
|
|
239
|
+
if (m === undefined)
|
|
240
|
+
return { ok: false, current: "gone" };
|
|
241
|
+
if (m.decision !== expect)
|
|
242
|
+
return { ok: false, current: m.decision ?? "unset" };
|
|
243
|
+
m.decision = next;
|
|
244
|
+
return { ok: true };
|
|
245
|
+
}
|
|
187
246
|
async releaseLease(scope, handle, owner) {
|
|
188
247
|
const b = this.boxes.get(this.key(scope, handle));
|
|
189
248
|
if (b?.lease?.owner === owner)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One declared server, as far as its names go: `McpServerSpec` fits as it is. `tools` are the server's REMOTE tool names the asker
|
|
3
|
+
* already knows (a listing, a host's reading of one — never the minted `mcp__…` names); absent means none are known.
|
|
4
|
+
*/
|
|
5
|
+
export interface McpNameSetServer {
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly tools?: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* One contention: `names` are the two sources, the earlier one first (server names for `server_name`; the remote tool names for
|
|
11
|
+
* `tool_name`, where `server` says whose); `shared` is the name they mint — the prefix for `server_name`, the wire name for `tool_name`.
|
|
12
|
+
*/
|
|
13
|
+
export type McpNameConflict = {
|
|
14
|
+
readonly kind: "server_name";
|
|
15
|
+
readonly names: readonly [string, string];
|
|
16
|
+
readonly shared: string;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "tool_name";
|
|
19
|
+
readonly server: string;
|
|
20
|
+
readonly names: readonly [string, string];
|
|
21
|
+
readonly shared: string;
|
|
22
|
+
};
|
|
23
|
+
/** The judge's answer: `ok`, or EVERY contention in the set — server rows first, then tool rows, each in declaration order; never empty. */
|
|
24
|
+
export type McpNameVerdict = {
|
|
25
|
+
readonly ok: true;
|
|
26
|
+
} | {
|
|
27
|
+
readonly ok: false;
|
|
28
|
+
readonly conflicts: readonly [McpNameConflict, ...McpNameConflict[]];
|
|
29
|
+
};
|
|
30
|
+
/** Judge a declared set. Pure: reads nothing but the names it is handed; a malformed set is refused (`config.invalid_argument`), never read as clean. */
|
|
31
|
+
export declare function judgeMcpNames(servers: readonly McpNameSetServer[]): McpNameVerdict;
|
|
32
|
+
/**
|
|
33
|
+
* The sentence that says why a tool of one server is not mounted under its own name: `heldBy` is the raw name that took the wire
|
|
34
|
+
* name `minted` first, `tool` the raw name that came after. TWO FACTS, TWO SENTENCES (a fixed label must not head more than one
|
|
35
|
+
* cause): the same raw name listed again, where namespacing folded nothing, and two different raw names that fold onto one name.
|
|
36
|
+
* It carries no raw name — the caller's own field says which tool.
|
|
37
|
+
*/
|
|
38
|
+
export declare function mcpToolDropReason(minted: string, heldBy: string, tool: string): string;
|
|
39
|
+
/** One row as a sentence — the one the engine refuses a run with (`server_name`) or discloses a drop with (`tool_name`). Names are quoted single-line and bounded. */
|
|
40
|
+
export declare function describeMcpNameConflict(conflict: McpNameConflict): string;
|
|
41
|
+
/**
|
|
42
|
+
* The refusal of a declared set whose server names do not each own a namespace domain — thrown by the mount and by the no-run probe,
|
|
43
|
+
* so one set is refused with one code and one sentence wherever it is asked. It takes the NAMES only (a run's refusal reads no
|
|
44
|
+
* tool list); the FIRST `server_name` row speaks, naming both raw spellings.
|
|
45
|
+
*/
|
|
46
|
+
export declare function refuseCollidingMcpServerNames(serverNames: readonly string[]): void;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
2
|
+
import { createToolNameClaims, findNamespacePrefixCollisions } from "./protocol-naming.js";
|
|
3
|
+
import { inlineUntrusted } from "./untrusted-text.js";
|
|
4
|
+
function refuseArgument(problem) {
|
|
5
|
+
throw Object.assign(new Error(problem), { code: "config.invalid_argument" });
|
|
6
|
+
}
|
|
7
|
+
function readNameSet(servers) {
|
|
8
|
+
if (!Array.isArray(servers))
|
|
9
|
+
refuseArgument("judgeMcpNames takes a list of declared servers; what was passed is not a list.");
|
|
10
|
+
return Array.from(servers, (server, index) => {
|
|
11
|
+
if (typeof server !== "object" || server === null)
|
|
12
|
+
refuseArgument(`judgeMcpNames: entry ${index} is not a server declaration (an object with a "name").`);
|
|
13
|
+
const { name, tools } = server;
|
|
14
|
+
if (typeof name !== "string")
|
|
15
|
+
refuseArgument(`judgeMcpNames: entry ${index} has no string "name".`);
|
|
16
|
+
if (tools === undefined)
|
|
17
|
+
return { name };
|
|
18
|
+
const listed = Array.isArray(tools) ? Array.from(tools) : [undefined];
|
|
19
|
+
if (listed.some((tool) => typeof tool !== "string")) {
|
|
20
|
+
refuseArgument(`judgeMcpNames: entry ${index} ("${inlineUntrusted(name, 120)}") names its tools as something other than a list of strings.`);
|
|
21
|
+
}
|
|
22
|
+
return { name, tools: listed };
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function judgeMcpNames(servers) {
|
|
26
|
+
const set = readNameSet(servers);
|
|
27
|
+
const conflicts = findNamespacePrefixCollisions(MCP_NAMESPACE, set.map((s) => s.name)).map((c) => ({ kind: "server_name", names: c.peers, shared: c.prefix }));
|
|
28
|
+
for (const server of set) {
|
|
29
|
+
const claim = createToolNameClaims(MCP_NAMESPACE, server.name);
|
|
30
|
+
for (const tool of server.tools ?? []) {
|
|
31
|
+
const { minted, heldBy } = claim(tool);
|
|
32
|
+
if (heldBy !== undefined)
|
|
33
|
+
conflicts.push({ kind: "tool_name", server: server.name, names: [heldBy, tool], shared: minted });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const [first, ...rest] = conflicts;
|
|
37
|
+
return first === undefined ? { ok: true } : { ok: false, conflicts: [first, ...rest] };
|
|
38
|
+
}
|
|
39
|
+
export function mcpToolDropReason(minted, heldBy, tool) {
|
|
40
|
+
return heldBy === tool
|
|
41
|
+
? `this server listed this tool name more than once in one listing; the first entry is mounted as "${minted}" and the repeat is dropped (one name cannot denote two tools)`
|
|
42
|
+
: `name collides with an earlier tool of this server: both mount as "${minted}" after namespacing (only [a-zA-Z0-9_-] survives), and one name cannot denote two tools`;
|
|
43
|
+
}
|
|
44
|
+
export function describeMcpNameConflict(conflict) {
|
|
45
|
+
const [a, b] = conflict.names;
|
|
46
|
+
if (conflict.kind === "tool_name") {
|
|
47
|
+
return `MCP server "${inlineUntrusted(conflict.server, 120)}", tool "${inlineUntrusted(b, 120)}": ${mcpToolDropReason(conflict.shared, a, b)}`;
|
|
48
|
+
}
|
|
49
|
+
return a === b
|
|
50
|
+
? `MCP server "${inlineUntrusted(a, 120)}" is declared twice — each server needs its own name (both mount under "${conflict.shared}", so their tools would shadow each other and a refresh of one would unmount the other's).`
|
|
51
|
+
: `MCP server names "${inlineUntrusted(a, 120)}" and "${inlineUntrusted(b, 120)}" both mount under "${conflict.shared}" — the namespaced tool name keeps only [a-zA-Z0-9_-], so they are the same domain to this engine (their tools would shadow each other and a refresh of one would unmount the other's). Rename one.`;
|
|
52
|
+
}
|
|
53
|
+
export function refuseCollidingMcpServerNames(serverNames) {
|
|
54
|
+
const verdict = judgeMcpNames(serverNames.map((name) => ({ name })));
|
|
55
|
+
if (verdict.ok)
|
|
56
|
+
return;
|
|
57
|
+
const row = verdict.conflicts.find((c) => c.kind === "server_name");
|
|
58
|
+
if (row === undefined)
|
|
59
|
+
return;
|
|
60
|
+
throw Object.assign(new Error(describeMcpNameConflict(row)), { code: "config.mcp_server_name_collision" });
|
|
61
|
+
}
|
package/dist/core/mcp-probe.d.ts
CHANGED
|
@@ -93,7 +93,7 @@ export interface McpProbeOptions {
|
|
|
93
93
|
* FAILURE IS PER SERVER, REFUSAL IS NOT. A server that cannot be dialed, that answers a status, or that
|
|
94
94
|
* outlives `timeoutMs` takes its verdict in its own row and the walk continues. What DOES reject is the
|
|
95
95
|
* declaration set the materialization itself refuses — two names that mount under one namespace prefix
|
|
96
|
-
* are refused before any transport is touched, by
|
|
96
|
+
* are refused before any transport is touched, by the judge the mount asks, so the probe answers a colliding
|
|
97
97
|
* configuration exactly as a task would rather than showing two healthy-looking rows for a pair the
|
|
98
98
|
* engine would never mount.
|
|
99
99
|
*
|
package/dist/core/mcp-probe.js
CHANGED
|
@@ -2,8 +2,7 @@ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
|
|
|
2
2
|
import { snapshotMcpEntries } from "./locked-config.js";
|
|
3
3
|
import { classifyMcpFailure, livenessOf } from "./mcp-failure.js";
|
|
4
4
|
import { MCP_ENV_MS_MAX, materializeMcpTools } from "./mcp.js";
|
|
5
|
-
import {
|
|
6
|
-
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
5
|
+
import { refuseCollidingMcpServerNames } from "./mcp-name-judge.js";
|
|
7
6
|
import { mcpManifestEntries } from "./runner/prepare-wiring-manifest.js";
|
|
8
7
|
const DECLINE_EVERY_ELICITATION = async () => ({ action: "decline" });
|
|
9
8
|
function wholeInRange(name, value, min, max) {
|
|
@@ -66,9 +65,7 @@ export async function probeMcpServers(specs, opts = {}) {
|
|
|
66
65
|
const concurrency = wholeInRange("concurrency", opts.concurrency, 1, Number.MAX_SAFE_INTEGER) ?? 1;
|
|
67
66
|
const onElicit = opts.onElicit ?? DECLINE_EVERY_ELICITATION;
|
|
68
67
|
const signal = opts.signal;
|
|
69
|
-
|
|
70
|
-
await materializeMcpTools(declared, opts.principal, onElicit);
|
|
71
|
-
}
|
|
68
|
+
refuseCollidingMcpServerNames(declared.map((e) => e.name));
|
|
72
69
|
const statuses = [];
|
|
73
70
|
const servers = new Array(declared.length);
|
|
74
71
|
const teardowns = [];
|
package/dist/core/mcp.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { egressText } from "./untrusted-egress.js";
|
|
2
1
|
import { __resetEnvKnobAnnouncements } from "./env-knob-announce.js";
|
|
3
2
|
import { resolveEnvBudget } from "./env-budget.js";
|
|
4
3
|
import { MCP_NAMESPACE } from "./protocol-table.js";
|
|
5
|
-
import { declaredHeaderValues,
|
|
4
|
+
import { declaredHeaderValues, remoteWords, resolveProtocolHttpHeaders } from "./protocol-http-auth.js";
|
|
6
5
|
import { disposeSectionEntry, sectionAnswerWarning } from "./prompt-section-seat.js";
|
|
7
|
-
import {
|
|
6
|
+
import { createToolNameClaims, mintNamespacePrefix, normalizeNameSegment } from "./protocol-naming.js";
|
|
7
|
+
import { mcpToolDropReason, refuseCollidingMcpServerNames } from "./mcp-name-judge.js";
|
|
8
8
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
9
9
|
import { McpConnectionPool } from "./mcp-connection-pool.js";
|
|
10
10
|
import { SessionEndingTransport, requestSite } from "./mcp-session-end.js";
|
|
@@ -165,7 +165,7 @@ export function collapseMcpErrorPrefix(message) {
|
|
|
165
165
|
function sanitizeMcpErrorTextInPlace(err, declared) {
|
|
166
166
|
if (!(err instanceof Error))
|
|
167
167
|
return;
|
|
168
|
-
const sanitized =
|
|
168
|
+
const sanitized = remoteWords(err instanceof McpError ? collapseMcpErrorPrefix(err.message) : err.message, declared);
|
|
169
169
|
if (sanitized !== err.message)
|
|
170
170
|
err.message = sanitized;
|
|
171
171
|
}
|
|
@@ -176,10 +176,7 @@ function namedMcpFailureText(err, declared) {
|
|
|
176
176
|
}
|
|
177
177
|
function remoteErrorText(err, declared, opts) {
|
|
178
178
|
const raw = thrownText(err);
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
function mcpRemoteText(text, declared) {
|
|
182
|
-
return egressText("same-principal", scrubRequestEcho(text, declared));
|
|
179
|
+
return remoteWords(opts?.collapse === true ? collapseMcpErrorPrefix(raw) : raw, declared);
|
|
183
180
|
}
|
|
184
181
|
function writeEffectWarning(writeEffect) {
|
|
185
182
|
return writeEffect
|
|
@@ -655,17 +652,7 @@ export async function materializeMcpTools(specs, principal, onElicit, imageResiz
|
|
|
655
652
|
axesRefused.set(spec, judged.error);
|
|
656
653
|
return spec;
|
|
657
654
|
});
|
|
658
|
-
|
|
659
|
-
const collision = findNamespacePrefixCollision(MCP_NAMESPACE, entries.map((s) => s.name));
|
|
660
|
-
if (collision) {
|
|
661
|
-
const [a, b] = collision.peers;
|
|
662
|
-
const e = new Error(a === b
|
|
663
|
-
? `MCP server "${inlineUntrusted(a, 120)}" is declared twice — each server needs its own name (both mount under "${collision.prefix}", so their tools would shadow each other and a refresh of one would unmount the other's).`
|
|
664
|
-
: `MCP server names "${inlineUntrusted(a, 120)}" and "${inlineUntrusted(b, 120)}" both mount under "${collision.prefix}" — the namespaced tool name keeps only [a-zA-Z0-9_-], so they are the same domain to this engine (their tools would shadow each other and a refresh of one would unmount the other's). Rename one.`);
|
|
665
|
-
e.code = "config.mcp_server_name_collision";
|
|
666
|
-
throw e;
|
|
667
|
-
}
|
|
668
|
-
}
|
|
655
|
+
refuseCollidingMcpServerNames(entries.map((s) => s.name));
|
|
669
656
|
let revocationProbeFailed = false;
|
|
670
657
|
const isServerRevoked = (serverName) => {
|
|
671
658
|
if (mcpRevocations === undefined)
|
|
@@ -1664,7 +1651,7 @@ function intakeListedTools(listed, spec, link, declared, imageResizer, reminderD
|
|
|
1664
1651
|
const dropped = [];
|
|
1665
1652
|
const accepted = [];
|
|
1666
1653
|
const advisories = [];
|
|
1667
|
-
const
|
|
1654
|
+
const claimToolName = createToolNameClaims(MCP_NAMESPACE, spec.name);
|
|
1668
1655
|
for (const t of listed.tools) {
|
|
1669
1656
|
if (spec.allowTools && !spec.allowTools.includes(t.name)) {
|
|
1670
1657
|
continue;
|
|
@@ -1704,18 +1691,11 @@ function intakeListedTools(listed, spec, link, declared, imageResizer, reminderD
|
|
|
1704
1691
|
}
|
|
1705
1692
|
const remoteName = t.name;
|
|
1706
1693
|
const taskRequired = t.execution !== null && typeof t.execution === "object" && t.execution.taskSupport === "required";
|
|
1707
|
-
const namespacedName =
|
|
1708
|
-
const mintedBy = mintedNames.get(namespacedName);
|
|
1694
|
+
const { minted: namespacedName, heldBy: mintedBy } = claimToolName(remoteName);
|
|
1709
1695
|
if (mintedBy !== undefined) {
|
|
1710
|
-
dropped.push({
|
|
1711
|
-
tool: inlineUntrusted(t.name),
|
|
1712
|
-
reason: mintedBy === t.name
|
|
1713
|
-
? `this server listed this tool name more than once in one listing; the first entry is mounted as "${namespacedName}" and the repeat is dropped (one name cannot denote two tools)`
|
|
1714
|
-
: `name collides with an earlier tool of this server: both mount as "${namespacedName}" after namespacing (only [a-zA-Z0-9_-] survives), and one name cannot denote two tools`,
|
|
1715
|
-
});
|
|
1696
|
+
dropped.push({ tool: inlineUntrusted(t.name), reason: mcpToolDropReason(namespacedName, mintedBy, t.name) });
|
|
1716
1697
|
continue;
|
|
1717
1698
|
}
|
|
1718
|
-
mintedNames.set(namespacedName, t.name);
|
|
1719
1699
|
const hintAxis = mcpAxisFor(namespacedName, t.annotations);
|
|
1720
1700
|
const axis = applyCallerAxisOverride(namespacedName, hintAxis, spec.toolAxes?.[remoteName]);
|
|
1721
1701
|
if (axis)
|
|
@@ -1819,7 +1799,7 @@ function intakeListedTools(listed, spec, link, declared, imageResizer, reminderD
|
|
|
1819
1799
|
const errScLine = structuredContentErrorLine(res.structuredContent, parts.join("\n"));
|
|
1820
1800
|
if (errScLine !== undefined)
|
|
1821
1801
|
parts.push(errScLine);
|
|
1822
|
-
const body = truncateMcpErrorText(
|
|
1802
|
+
const body = truncateMcpErrorText(remoteWords(parts.join("\n").trim(), declared));
|
|
1823
1803
|
const msg = body
|
|
1824
1804
|
? `MCP tool ${inlineUntrusted(remoteName)} reported an error. The server's error content follows as external/untrusted data:\n${delimitUntrusted(`${spec.name} tool error`, body)}`
|
|
1825
1805
|
: `MCP tool ${inlineUntrusted(remoteName)} reported an error`;
|
|
@@ -2,6 +2,7 @@ import { join } from "node:path";
|
|
|
2
2
|
import { uuidv7 } from "../../internal/harness.js";
|
|
3
3
|
import { atomicWriteFileSync, ensureDirExists, lockedStrictUpdate, readStrictSidecar, enqueueMemoryAnnouncement, ControlPlaneCorruptError } from "./layout.js";
|
|
4
4
|
import { inlineUntrusted } from "../untrusted-text.js";
|
|
5
|
+
import { extensionWords } from "../untrusted-egress.js";
|
|
5
6
|
import { deliverEngineNotice } from "../engine-notice.js";
|
|
6
7
|
import { CONSOLIDATION_FORCE_THROTTLE_FLOOR_MS, screenConsolidationOptions, supersessionFuseCeiling, updateConsolidationGate, recommendationUnconsumed, CONSOLIDATION_WRITE_REFUSED_CODE, ConsolidationRefusedError, } from "./consolidation.js";
|
|
7
8
|
import { memoryConsolidationIncompleteNotice, memoryConsolidationWithheldNotice } from "./engine.js";
|
|
@@ -510,15 +511,15 @@ export async function runMemoryConsolidationDriver(engine, scope, opts) {
|
|
|
510
511
|
}
|
|
511
512
|
catch (err) {
|
|
512
513
|
if (err instanceof DriverMintFailure) {
|
|
513
|
-
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} FAILED before any write: ${
|
|
514
|
+
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} FAILED before any write: ${extensionWords(err.detail, 300)}`]);
|
|
514
515
|
return settle("driver_failed", undefined, err.detail);
|
|
515
516
|
}
|
|
516
517
|
if (err instanceof DriverPurityViolation) {
|
|
517
|
-
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} REFUSED on the clean-arm purity gate (no writes): ${
|
|
518
|
+
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} REFUSED on the clean-arm purity gate (no writes): ${extensionWords(err.detail, 300)}`]);
|
|
518
519
|
return settle("purity_violation", undefined, err.detail);
|
|
519
520
|
}
|
|
520
521
|
if (err instanceof MintInvalidated) {
|
|
521
|
-
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} MINT INVALIDATED: ${
|
|
522
|
+
announce([`memory consolidation driver run ${run.runId} for scope ${inlineUntrusted(scope, 80)} MINT INVALIDATED: ${extensionWords(err.detail, 300)}`]);
|
|
522
523
|
if (run.residue === undefined && err.residue.length > 0)
|
|
523
524
|
run.residue = err.residue;
|
|
524
525
|
const partial = {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { requireWriteGate } from "./consolidation-write-gate.js";
|
|
2
2
|
import { readMember, thrownText } from "../thrown-value.js";
|
|
3
3
|
import { assertServableRequestRoot, joinRequestUrl } from "../../brain/route-adjudicator.js";
|
|
4
|
-
import { holdOwnCredentialValues } from "../secret-env.js";
|
|
4
|
+
import { holdOwnCredentialValues, namesASecret } from "../secret-env.js";
|
|
5
|
+
import { remoteWords } from "../protocol-http-auth.js";
|
|
5
6
|
export const DISTILLER_DEFAULT_MAX_INPUTS_PER_PRODUCT = 32;
|
|
6
7
|
const SIZE_CLAUSE = {
|
|
7
8
|
"dl-2": "- the index should be as small as it can be made without losing either of the above.",
|
|
@@ -171,29 +172,37 @@ export function openAiCompatChatSeat(seat) {
|
|
|
171
172
|
catch (e) {
|
|
172
173
|
throw distillerConfigRefusal(thrownText(e), e);
|
|
173
174
|
}
|
|
175
|
+
const declared = namesASecret(apiKey) ? [apiKey] : [];
|
|
174
176
|
return async (req) => {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
177
|
+
let res;
|
|
178
|
+
let text;
|
|
179
|
+
try {
|
|
180
|
+
res = await fetch(joinRequestUrl(requestRoot, "/chat/completions"), {
|
|
181
|
+
method: "POST",
|
|
182
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
183
|
+
body: JSON.stringify({
|
|
184
|
+
model: seat.model,
|
|
185
|
+
messages: [
|
|
186
|
+
{ role: "system", content: req.system },
|
|
187
|
+
{ role: "user", content: req.user },
|
|
188
|
+
],
|
|
189
|
+
temperature: req.temperature,
|
|
190
|
+
max_tokens: req.maxTokens,
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
text = await res.text();
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
throw new Error(`model call failed: ${remoteWords(thrownText(e), declared)}`);
|
|
197
|
+
}
|
|
189
198
|
if (!res.ok)
|
|
190
|
-
throw new Error(`model call failed ${res.status}: ${text.slice(0, 400)}`);
|
|
199
|
+
throw new Error(`model call failed ${res.status}: ${remoteWords(text, declared).slice(0, 400)}`);
|
|
191
200
|
let body;
|
|
192
201
|
try {
|
|
193
202
|
body = JSON.parse(text);
|
|
194
203
|
}
|
|
195
204
|
catch {
|
|
196
|
-
throw new Error(`model returned non-JSON envelope: ${text.slice(0, 400)}`);
|
|
205
|
+
throw new Error(`model returned non-JSON envelope: ${remoteWords(text, declared).slice(0, 400)}`);
|
|
197
206
|
}
|
|
198
207
|
return {
|
|
199
208
|
content: body.choices?.[0]?.message?.content ?? "",
|
|
@@ -6,6 +6,7 @@ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
|
6
6
|
import { MEMORY_ORIGIN_CAUSES } from "./types.js";
|
|
7
7
|
import { physicalPathOf, physicalPathOfExisting, priorCallerCasedPathOf } from "../physical-path.js";
|
|
8
8
|
import { thrownLabel, thrownText } from "../thrown-value.js";
|
|
9
|
+
import { egressText } from "../untrusted-egress.js";
|
|
9
10
|
const SCOPES_FILE = "scopes.json";
|
|
10
11
|
export const CURSORS_FILE = "cursors.json";
|
|
11
12
|
export const CONTROL_PLANE_DIR = ".engine";
|
|
@@ -1467,6 +1468,7 @@ function readSidecarJson(controlDir, fileName) {
|
|
|
1467
1468
|
}
|
|
1468
1469
|
return undefined;
|
|
1469
1470
|
}
|
|
1471
|
+
const mintedItems = (items) => items.map((item) => egressText("same-principal", item));
|
|
1470
1472
|
function coerceAnnouncements(raw) {
|
|
1471
1473
|
const rec = { folded: 0, queue: [] };
|
|
1472
1474
|
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
@@ -1488,7 +1490,7 @@ function coerceAnnouncements(raw) {
|
|
|
1488
1490
|
at: a.at,
|
|
1489
1491
|
...(typeof a.sessionId === "string" ? { sessionId: a.sessionId } : {}),
|
|
1490
1492
|
...(typeof a.reportId === "string" ? { reportId: a.reportId } : {}),
|
|
1491
|
-
items: a.items,
|
|
1493
|
+
items: mintedItems(a.items),
|
|
1492
1494
|
});
|
|
1493
1495
|
}
|
|
1494
1496
|
}
|
|
@@ -1496,7 +1498,8 @@ function coerceAnnouncements(raw) {
|
|
|
1496
1498
|
return rec;
|
|
1497
1499
|
}
|
|
1498
1500
|
export function enqueueMemoryAnnouncement(controlDir, ann, opts) {
|
|
1499
|
-
|
|
1501
|
+
ann = { ...ann, items: mintedItems(ann.items) };
|
|
1502
|
+
const family = opts?.supersedesPrefix === undefined ? undefined : egressText("same-principal", opts.supersedesPrefix);
|
|
1500
1503
|
if (ann.items.length === 0 && family === undefined)
|
|
1501
1504
|
return;
|
|
1502
1505
|
lockedJournaledUpdate(controlDir, ANNOUNCEMENTS_FILE, (current) => {
|
|
@@ -81,6 +81,8 @@ function syntheticCheckpoint(scope, fidelity) {
|
|
|
81
81
|
askClass: "denial_limit_fallback",
|
|
82
82
|
classes: ["denial_limit_fallback"],
|
|
83
83
|
engineMounted: true,
|
|
84
|
+
targets: { protective: "/park-selfcheck/target", declared: null },
|
|
85
|
+
targetReads: { ["0".repeat(64)]: "f".repeat(64) },
|
|
84
86
|
batchToolCallIds: [`${scope}:call`],
|
|
85
87
|
completedCallIds: [],
|
|
86
88
|
},
|
|
@@ -317,11 +317,13 @@ export declare function prepareCardApproval(opts: {
|
|
|
317
317
|
principal?: string;
|
|
318
318
|
/** design/182 §4.5 (additive): the structural owner — only the local-owner path needs it. */
|
|
319
319
|
owner?: RuleOwner;
|
|
320
|
-
/** The tool whose ask this card belongs to
|
|
321
|
-
* card)
|
|
320
|
+
/** The tool whose ask this card belongs to: a tool whose card the offer factory mints a rule on (`cardRuleFormOf`) — the shell
|
|
321
|
+
* tool, a name tool (the skill-activation card), a host tool (the fetch card), a tool with no rule grammar (the whole-tool card).
|
|
322
|
+
* Any other tool (a path tool, a shell-face sibling) draws no card. */
|
|
322
323
|
toolName: string;
|
|
323
324
|
/** The command as adjudicated — the same bytes the ask carried; for a name tool (`Skill`), the name its call carried (`args.skill`);
|
|
324
|
-
* for a host tool (`WebFetch`), the host its call reaches (the offer's `command`)
|
|
325
|
+
* for a host tool (`WebFetch`), the host its call reaches (the offer's `command`); for a tool with no rule grammar, `""` (the
|
|
326
|
+
* offer's `command`: the whole tool binds no value). */
|
|
325
327
|
command: string;
|
|
326
328
|
/** The ask's call id and argument digest, recorded for reconciliation. */
|
|
327
329
|
toolCallId?: string;
|