@sema-agent/core 7.52.0 → 7.53.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 +194 -0
- package/README.md +3 -1
- package/dist/agents/cross-session-envelope.d.ts +8 -0
- package/dist/agents/cross-session-envelope.js +6 -0
- package/dist/agents/cross-session-judge.d.ts +21 -1
- package/dist/agents/cross-session-judge.js +8 -0
- package/dist/agents/cross-session-ref.d.ts +5 -0
- package/dist/agents/cross-session-ref.js +1 -0
- package/dist/agents/org-delivery-gate.d.ts +122 -0
- package/dist/agents/org-delivery-gate.js +69 -0
- package/dist/agents/peer-held-queue.d.ts +9 -4
- package/dist/agents/peer-held-queue.js +6 -5
- package/dist/agents/peer-session-drain.d.ts +35 -5
- package/dist/agents/peer-session-drain.js +271 -180
- package/dist/agents/read-inbox-tool.d.ts +7 -0
- package/dist/agents/read-inbox-tool.js +5 -1
- package/dist/agents/subagent.d.ts +3 -0
- package/dist/agents/subagent.js +99 -72
- package/dist/agents/tool-filter.d.ts +18 -0
- package/dist/agents/tool-filter.js +6 -0
- package/dist/brain/anthropic.d.ts +6 -1
- package/dist/brain/anthropic.js +4 -4
- package/dist/brain/model-presets.js +2 -1
- package/dist/brain/route-adjudicator.d.ts +6 -0
- package/dist/brain/routing.d.ts +24 -1
- package/dist/brain/routing.js +54 -19
- package/dist/brain/timeout.js +5 -4
- package/dist/core/a2a.d.ts +5 -2
- package/dist/core/a2a.js +30 -13
- package/dist/core/allow-entry.d.ts +2 -1
- package/dist/core/allow-entry.js +2 -2
- package/dist/core/auto-compaction.d.ts +57 -0
- package/dist/core/auto-compaction.js +28 -2
- package/dist/core/checkpoint-store.d.ts +29 -20
- package/dist/core/checkpoint-store.js +3 -3
- package/dist/core/compliance.js +3 -1
- package/dist/core/effective-path-target.d.ts +25 -6
- package/dist/core/effective-path-target.js +24 -2
- package/dist/core/engine-notice.d.ts +13 -2
- package/dist/core/gate-fold.js +3 -2
- package/dist/core/gate-lanes.js +11 -13
- package/dist/core/gate-outcome.d.ts +11 -6
- package/dist/core/governance-codes.d.ts +3 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/hands-band-screen.d.ts +7 -0
- package/dist/core/hands-band-screen.js +32 -0
- package/dist/core/hooks.d.ts +7 -5
- package/dist/core/hooks.js +5 -3
- package/dist/core/locked-config.d.ts +84 -32
- package/dist/core/locked-config.js +128 -42
- package/dist/core/mcp-probe.js +1 -1
- package/dist/core/mcp-server-spec.d.ts +88 -10
- package/dist/core/mcp-server-spec.js +183 -33
- package/dist/core/mcp.d.ts +21 -14
- package/dist/core/mcp.js +55 -51
- package/dist/core/memory-engine/engine.d.ts +14 -19
- package/dist/core/memory-engine/engine.js +7 -5
- package/dist/core/mode-policy.d.ts +3 -18
- package/dist/core/mode-policy.js +2 -16
- package/dist/core/mods/kernel/dispatch.js +13 -15
- package/dist/core/mods/kernel/events.d.ts +9 -6
- package/dist/core/mods/kernel/events.js +2 -2
- package/dist/core/mods/kernel/receive.d.ts +39 -10
- package/dist/core/mods/kernel/receive.js +119 -54
- package/dist/core/mods/kernel/site-rules.d.ts +80 -15
- package/dist/core/mods/kernel/site-rules.js +232 -40
- package/dist/core/mods/stations.d.ts +0 -6
- package/dist/core/mods/stations.js +0 -7
- package/dist/core/mounted-mcp.d.ts +57 -0
- package/dist/core/mounted-mcp.js +71 -2
- package/dist/core/own-property-receipt.d.ts +13 -0
- package/dist/core/own-property-receipt.js +3 -1
- package/dist/core/parent-spec-seat.d.ts +2 -2
- package/dist/core/permission-rule-model.d.ts +58 -9
- package/dist/core/permission-rule-model.js +81 -7
- package/dist/core/permission-rule-org.d.ts +163 -33
- package/dist/core/permission-rule-org.js +205 -71
- package/dist/core/permission-rules.d.ts +7 -1
- package/dist/core/permission-rules.js +37 -8
- package/dist/core/retention.js +7 -2
- package/dist/core/retired-keys.d.ts +8 -2
- package/dist/core/retired-keys.js +12 -1
- package/dist/core/roles.js +60 -10
- package/dist/core/rule-lane-reads.js +1 -1
- package/dist/core/runner/contracts.d.ts +1 -0
- package/dist/core/runner/gate-exit.js +4 -3
- package/dist/core/runner/mcp-redial.d.ts +52 -10
- package/dist/core/runner/mcp-redial.js +38 -9
- package/dist/core/runner/permission-rule-lanes.d.ts +5 -2
- package/dist/core/runner/permission-rule-lanes.js +22 -10
- package/dist/core/runner/person-path-rows.d.ts +3 -1
- package/dist/core/runner/person-path-rows.js +2 -2
- package/dist/core/runner/prepare-config-doors.d.ts +48 -2
- package/dist/core/runner/prepare-config-doors.js +161 -145
- package/dist/core/runner/prepare-defer-classify.js +4 -7
- package/dist/core/runner/prepare-delegation-surface.d.ts +2 -2
- package/dist/core/runner/prepare-delegation-surface.js +7 -5
- package/dist/core/runner/prepare-hands-readface.d.ts +1 -1
- package/dist/core/runner/prepare-hands-readface.js +2 -1
- package/dist/core/runner/prepare-inherited-gate.js +2 -2
- package/dist/core/runner/prepare-listings.d.ts +1 -1
- package/dist/core/runner/prepare-listings.js +4 -2
- package/dist/core/runner/prepare-memory-engine-session.d.ts +0 -6
- package/dist/core/runner/prepare-memory-engine-session.js +19 -6
- package/dist/core/runner/prepare-memory.d.ts +3 -2
- package/dist/core/runner/prepare-memory.js +6 -2
- package/dist/core/runner/prepare-offload-wrappers.d.ts +4 -4
- package/dist/core/runner/prepare-offload-wrappers.js +2 -1
- package/dist/core/runner/prepare-policy-chain.d.ts +1 -1
- package/dist/core/runner/prepare-policy-chain.js +12 -12
- package/dist/core/runner/prepare-project-context.d.ts +1 -1
- package/dist/core/runner/prepare-project-context.js +6 -4
- package/dist/core/runner/prepare-prompt-assembly.d.ts +1 -1
- package/dist/core/runner/prepare-prompt-assembly.js +20 -16
- package/dist/core/runner/prepare-prompt-inputs.d.ts +12 -6
- package/dist/core/runner/prepare-prompt-inputs.js +6 -3
- package/dist/core/runner/prepare-protocol-tools.d.ts +7 -5
- package/dist/core/runner/prepare-protocol-tools.js +19 -12
- package/dist/core/runner/prepare-question-face.d.ts +4 -2
- package/dist/core/runner/prepare-question-face.js +4 -3
- package/dist/core/runner/prepare-task.js +10 -10
- package/dist/core/runner/prepare-wiring-manifest.d.ts +8 -3
- package/dist/core/runner/prepare-wiring-manifest.js +8 -5
- package/dist/core/runner/resume-admission.d.ts +4 -1
- package/dist/core/runner/resume-admission.js +7 -5
- package/dist/core/runner/resume-internals-and-config.d.ts +3 -3
- package/dist/core/runner/resume-internals-and-config.js +3 -8
- package/dist/core/runner/retired-runner-deps.d.ts +5 -6
- package/dist/core/runner/retired-runner-deps.js +1 -4
- package/dist/core/runner/run-leg.js +42 -12
- package/dist/core/runner/run-recovery-lanes.js +5 -3
- package/dist/core/runner/run-terminal-adoption.js +6 -5
- package/dist/core/runner/run-turn-boundary.js +9 -3
- package/dist/core/runner/runtask.js +1 -1
- package/dist/core/runner/skills-seat.d.ts +15 -0
- package/dist/core/runner/skills-seat.js +57 -0
- package/dist/core/runner/sub-agent-deny-lists.d.ts +13 -11
- package/dist/core/runner/sub-agent-deny-lists.js +7 -5
- package/dist/core/runner/synthetic-tools.d.ts +5 -2
- package/dist/core/runner/synthetic-tools.js +2 -12
- package/dist/core/runner-deps.d.ts +29 -14
- package/dist/core/session-policy-store.d.ts +19 -0
- package/dist/core/session-policy-store.js +22 -2
- package/dist/core/store-contracts/mailbox-store-contract.js +13 -0
- package/dist/core/task-event.d.ts +38 -2
- package/dist/core/task-notification.js +2 -2
- package/dist/core/task-spec.d.ts +1 -1
- package/dist/core/tool-policy.d.ts +3 -0
- package/dist/core/tool-policy.js +10 -4
- package/dist/core/types.d.ts +2 -2
- package/dist/core/types.js +1 -1
- package/dist/core/untrusted-text.d.ts +16 -0
- package/dist/core/untrusted-text.js +7 -2
- package/dist/core/user-attachment.d.ts +13 -2
- package/dist/core/user-attachment.js +47 -41
- package/dist/core/wiring-manifest.d.ts +4 -2
- package/dist/engine/harness/agent-harness.js +2 -1
- package/dist/engine/harness/types.d.ts +3 -0
- package/dist/engine/loop/agent-loop.d.ts +12 -1
- package/dist/engine/loop/agent-loop.js +17 -5
- package/dist/engine/loop/types.d.ts +7 -1
- package/dist/index.d.ts +15 -9
- package/dist/index.js +12 -6
- package/dist/internal/harness.d.ts +1 -0
- package/dist/internal/harness.js +1 -0
- package/dist/orchestration/goal.d.ts +24 -1
- package/dist/orchestration/goal.js +3 -2
- package/dist/prompt-assembly/artifact-store.d.ts +44 -9
- package/dist/prompt-assembly/artifact-store.js +38 -12
- package/dist/prompt-assembly/artifact.d.ts +12 -11
- package/dist/prompt-assembly/artifact.js +52 -21
- package/dist/prompt-assembly/assemble.d.ts +2 -0
- package/dist/prompt-assembly/assemble.js +2 -1
- package/dist/prompt-assembly/catalog/index.js +2 -1
- package/dist/prompt-assembly/catalog/memory.d.ts +13 -0
- package/dist/prompt-assembly/catalog/memory.js +6 -0
- package/dist/prompt-assembly/catalog/schema.d.ts +16 -2
- package/dist/prompt-assembly/composer.d.ts +2 -0
- package/dist/prompt-assembly/composer.js +1 -1
- package/dist/prompt-assembly/overlay.d.ts +21 -11
- package/dist/prompt-assembly/overlay.js +55 -17
- package/dist/prompt-assembly/select.d.ts +21 -6
- package/dist/prompt-assembly/select.js +39 -2
- package/dist/prompts/default.d.ts +0 -11
- package/dist/prompts/default.js +0 -3
- package/dist/stores/file/permission-rule-store.d.ts +1 -1
- package/dist/stores/file/permission-rule-store.js +2 -1
- package/dist/tools/artifact/artifact-tool.js +4 -3
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +65 -5
- package/wire-changes/core@7.53.0.json +1946 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const NO_PERSISTENT_STORE = `# Memory
|
|
2
|
+
|
|
3
|
+
You have no persistent memory store: what is said in this conversation is not automatically retained for future sessions. If the user asks you to remember something for later, say plainly that you cannot store it — do not claim to have noted or saved it. If you have file tools, you may offer to write it into a project file instead (files persist; conversation memory does not).`;
|
|
4
|
+
export const MEMORY_MESSAGES = [
|
|
5
|
+
{ id: "core/memory.no-persistent-store", variants: [{ id: "default", when: {}, text: NO_PERSISTENT_STORE }], attribution: { form: "sema-owned" } },
|
|
6
|
+
];
|
|
@@ -69,15 +69,29 @@ export interface Attribution {
|
|
|
69
69
|
export interface Variant {
|
|
70
70
|
/** Reported in the manifest — one of the answers to "why this sentence". */
|
|
71
71
|
readonly id: string;
|
|
72
|
-
readonly when:
|
|
72
|
+
readonly when: CatalogVariantWhen;
|
|
73
73
|
readonly text: string;
|
|
74
74
|
}
|
|
75
|
-
/**
|
|
75
|
+
/** A condition on the run's binding (`TaskSpec.promptBinding`, a deployment's scenario name): exactly one list. A run
|
|
76
|
+
* with no binding is outside every `anyOf` and inside every `noneOf`. */
|
|
77
|
+
export type BindingCondition = {
|
|
78
|
+
readonly anyOf: readonly string[];
|
|
79
|
+
readonly noneOf?: never;
|
|
80
|
+
} | {
|
|
81
|
+
readonly noneOf: readonly string[];
|
|
82
|
+
readonly anyOf?: never;
|
|
83
|
+
};
|
|
84
|
+
/** The selector condition a variant (or an overlay) is chosen on: a conjunction over traits, host, facts and binding. */
|
|
76
85
|
export interface VariantWhen {
|
|
77
86
|
readonly traits?: TraitCondition;
|
|
78
87
|
readonly host?: HostCondition;
|
|
79
88
|
readonly facts?: FactConjunction;
|
|
89
|
+
readonly binding?: BindingCondition;
|
|
80
90
|
}
|
|
91
|
+
/** The condition a BUNDLED catalog variant is chosen on: a {@link VariantWhen} without the binding. A binding is a
|
|
92
|
+
* deployment's own vocabulary (scenario names the engine cannot know), so no catalog variant can name one — the type
|
|
93
|
+
* refuses it where the catalog data is written. */
|
|
94
|
+
export type CatalogVariantWhen = Omit<VariantWhen, "binding">;
|
|
81
95
|
export interface CatalogMessage {
|
|
82
96
|
/** `core/<area>.<name>`. */
|
|
83
97
|
readonly id: string;
|
|
@@ -9,6 +9,8 @@ export interface CompositionPlan {
|
|
|
9
9
|
readonly role?: HostRole;
|
|
10
10
|
readonly form?: HostForm;
|
|
11
11
|
};
|
|
12
|
+
/** The run's binding (`TaskSpec.promptBinding`; absent ⇒ none) — what every overlay's `when.binding` reads. */
|
|
13
|
+
readonly binding?: string;
|
|
12
14
|
/** Validated overlays, already in application order (`bundled < task < center < deploy`, then declaration order). */
|
|
13
15
|
readonly overlays?: readonly SourcedOverlay[];
|
|
14
16
|
}
|
|
@@ -91,7 +91,7 @@ function renderAdded(e, fold) {
|
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
93
|
export function composeCatalog(plan, inputs) {
|
|
94
|
-
const s = selectorsOf(inputs.facts, plan.host);
|
|
94
|
+
const s = selectorsOf(inputs.facts, plan.host, plan.binding);
|
|
95
95
|
const bases = basesOf(plan.catalog, inputs, s);
|
|
96
96
|
const selectedText = selectedTextOf(plan.catalog, bases);
|
|
97
97
|
const overlayText = (o) => {
|
|
@@ -10,11 +10,8 @@ export interface PromptOverlay {
|
|
|
10
10
|
readonly text?: string;
|
|
11
11
|
/** A catalog message whose selected variant is the overlay's text; exclusive with `text`. */
|
|
12
12
|
readonly message?: string;
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
readonly binding?: string;
|
|
16
|
-
};
|
|
17
|
-
/** The overlay applies to this run only when this holds (the variant selector's own closed vocabulary). */
|
|
13
|
+
/** The overlay applies to this run only when this holds (the variant selector's own vocabulary, the binding
|
|
14
|
+
* included) — the one applicability condition of every source class. */
|
|
18
15
|
readonly when?: VariantWhen;
|
|
19
16
|
}
|
|
20
17
|
/** The source classes in application order (later wins). `bundled` is the catalog itself and writes no overlay. */
|
|
@@ -59,10 +56,23 @@ export declare function receivePromptData(raw: unknown): {
|
|
|
59
56
|
} | {
|
|
60
57
|
readonly notData: string;
|
|
61
58
|
};
|
|
59
|
+
/** The longest binding a task may name (`TaskSpec.promptBinding`), counted in Unicode code points (`[...s].length`) — not
|
|
60
|
+
* in UTF-16 code units; every binding a `when.binding` list names is held to the same bound. Exported so a host screens
|
|
61
|
+
* with the engine's number and the engine's unit. Code points are the one unit of every character bound of the prompt
|
|
62
|
+
* artifact verifier ({@link codePointsOver}: a binding, an overlay id, `sourceRevision`, `evidence.intent`, a
|
|
63
|
+
* reference), so a character outside the BMP (an emoji, a rare CJK ideograph) counts one, not two; the overlay text
|
|
64
|
+
* budgets are UTF-8 bytes and are named so. */
|
|
65
|
+
export declare const PROMPT_BINDING_MAX_CHARS = 128;
|
|
66
|
+
/** The most bindings one `when.binding` list names — it bounds what a publisher can make the conflict check walk. */
|
|
67
|
+
export declare const PROMPT_BINDING_LIST_MAX = 64;
|
|
68
|
+
/** Whether `s` holds more than `max` Unicode code points — the one length unit of the prompt artifact verifier's
|
|
69
|
+
* character bounds. A string of at most `max` code units fits (a code point is one or two of them); one of more than
|
|
70
|
+
* twice `max` does not; only a string in between is counted — counted, never copied whole. */
|
|
71
|
+
export declare function codePointsOver(s: string, max: number): boolean;
|
|
62
72
|
/**
|
|
63
|
-
* The run's
|
|
64
|
-
* Its one writer is the task (a deployment names its scenario); absent ⇒ undefined. A value outside the
|
|
65
|
-
* refused by name (`config.prompt_selector_invalid`), never read as absent.
|
|
73
|
+
* The run's binding (`TaskSpec.promptBinding`): the scenario a `when.binding` condition reads, on an overlay of any
|
|
74
|
+
* source class. Its one writer is the task (a deployment names its scenario); absent ⇒ undefined. A value outside the
|
|
75
|
+
* grammar is refused by name (`config.prompt_selector_invalid`), never read as absent.
|
|
66
76
|
*/
|
|
67
77
|
export declare function resolvePromptBinding(task: {
|
|
68
78
|
readonly promptBinding?: unknown;
|
|
@@ -79,9 +89,9 @@ export interface OverlayValidationContext {
|
|
|
79
89
|
readonly lowerAdds?: ReadonlyMap<string, BandId>;
|
|
80
90
|
}
|
|
81
91
|
/**
|
|
82
|
-
* Validate one overlay for one source class. Returns the overlay (a frozen copy of its known members
|
|
83
|
-
* a violation: shape, namespace, op, text/message, `when`,
|
|
84
|
-
*
|
|
92
|
+
* Validate one overlay for one source class. Returns the overlay (a frozen copy of its known members, its `when` in
|
|
93
|
+
* normal form — {@link canonicalWhen}) or why it is a violation: shape, namespace, op, text/message, `when`, target existence, band authorization (and the band's mods
|
|
94
|
+
* write port), and the locked-text guard.
|
|
85
95
|
*/
|
|
86
96
|
export declare function validateOverlay(raw: unknown, ctx: OverlayValidationContext): {
|
|
87
97
|
readonly overlay: PromptOverlay;
|
|
@@ -5,6 +5,7 @@ import { bandOf, isBandId } from "./bands.js";
|
|
|
5
5
|
import { SEMA_DEFAULT_CATALOG, catalogMessage } from "./catalog/index.js";
|
|
6
6
|
import { HOST_FORMS, HOST_ROLES, OVERLAY_OPS, PROMPT_FACT_NAMES, PROMPT_TRAITS, isWordOf, } from "./catalog/schema.js";
|
|
7
7
|
import { RENDERERS } from "./renderers.js";
|
|
8
|
+
import { canonicalWhen } from "./select.js";
|
|
8
9
|
export const OVERLAY_SOURCE_ORDER = Object.freeze(["bundled", "task", "center", "deploy"]);
|
|
9
10
|
export const ABSENT_THIS_RUN = "(absent this run)";
|
|
10
11
|
const SECTION_BY_ID = new Map(SEMA_DEFAULT_CATALOG.sections.map((s) => [s.id, s]));
|
|
@@ -40,16 +41,49 @@ export function assembledRoleRefused(field, hits) {
|
|
|
40
41
|
return Object.assign(new Error(`prompt.assembled_role_refused: ${field} contains, verbatim, the locked text of ${named.slice(0, 4).join(", ")}${named.length > 4 ? ` and ${named.length - 4} more` : ""} — the engine composes the locked sections itself; supply only the role layer (an already-assembled prompt is refused, never passed through)`), { code: "prompt.assembled_role_refused" });
|
|
41
42
|
}
|
|
42
43
|
const ID_GRAMMAR = /^[a-z0-9][a-z0-9/._-]*$/;
|
|
43
|
-
const OVERLAY_KEYS = new Set(["id", "op", "target", "text", "message", "
|
|
44
|
+
const OVERLAY_KEYS = new Set(["id", "op", "target", "text", "message", "when"]);
|
|
44
45
|
const OPS = new Set(OVERLAY_OPS);
|
|
45
46
|
const PROMPT_DATA = Object.freeze({ accessors: "refuse", depth: 4, others: "refuse", freeze: true, isProxy: (v) => types.isProxy(v) });
|
|
46
47
|
export function receivePromptData(raw) {
|
|
47
48
|
const r = receive(raw, PROMPT_DATA);
|
|
48
49
|
return r.ok ? { value: r.value } : { notData: describeFault(r.fault) };
|
|
49
50
|
}
|
|
50
|
-
const
|
|
51
|
+
export const PROMPT_BINDING_MAX_CHARS = 128;
|
|
52
|
+
export const PROMPT_BINDING_LIST_MAX = 64;
|
|
53
|
+
export function codePointsOver(s, max) {
|
|
54
|
+
if (s.length <= max)
|
|
55
|
+
return false;
|
|
56
|
+
if (s.length > 2 * max)
|
|
57
|
+
return true;
|
|
58
|
+
let n = 0;
|
|
59
|
+
for (const _ of s)
|
|
60
|
+
if (++n > max)
|
|
61
|
+
return true;
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
51
64
|
function bindingProblem(v) {
|
|
52
|
-
return typeof v !== "string" || v.trim() === "" || v
|
|
65
|
+
return typeof v !== "string" || v.trim() === "" || codePointsOver(v, PROMPT_BINDING_MAX_CHARS) ? `${str(v)} is not a non-blank string of at most ${PROMPT_BINDING_MAX_CHARS} characters (code points)` : undefined;
|
|
66
|
+
}
|
|
67
|
+
function bindingConditionProblem(b) {
|
|
68
|
+
if (!isPlainRecord(b))
|
|
69
|
+
return `"when.binding" is ${Array.isArray(b) ? "an array" : str(b)}, not { anyOf: [...] } or { noneOf: [...] }`;
|
|
70
|
+
const keys = Object.keys(b);
|
|
71
|
+
const k = keys[0];
|
|
72
|
+
if (keys.length !== 1 || (k !== "anyOf" && k !== "noneOf"))
|
|
73
|
+
return `"when.binding" carries ${keys.length === 0 ? "no list" : keys.map((x) => str(x)).join(", ")} (exactly one of anyOf, noneOf)`;
|
|
74
|
+
const list = b[k];
|
|
75
|
+
if (!Array.isArray(list) || list.length === 0)
|
|
76
|
+
return `"when.binding.${k}" is ${Array.isArray(list) ? "empty" : str(list)}, not a list naming at least one binding`;
|
|
77
|
+
if (list.length > PROMPT_BINDING_LIST_MAX)
|
|
78
|
+
return `"when.binding.${k}" names ${list.length} bindings (at most ${PROMPT_BINDING_LIST_MAX})`;
|
|
79
|
+
for (const name of list) {
|
|
80
|
+
const why = bindingProblem(name);
|
|
81
|
+
if (why !== undefined)
|
|
82
|
+
return `"when.binding.${k}": ${why}`;
|
|
83
|
+
}
|
|
84
|
+
if (new Set(list).size !== list.length)
|
|
85
|
+
return `"when.binding.${k}" names a binding twice`;
|
|
86
|
+
return undefined;
|
|
53
87
|
}
|
|
54
88
|
export function resolvePromptBinding(task) {
|
|
55
89
|
if (task.promptBinding === undefined)
|
|
@@ -69,8 +103,8 @@ function whenViolation(when) {
|
|
|
69
103
|
if (typeof when !== "object" || when === null || Array.isArray(when))
|
|
70
104
|
return `"when" is ${str(when)}, not an object`;
|
|
71
105
|
for (const k of Object.keys(when))
|
|
72
|
-
if (k !== "traits" && k !== "host" && k !== "facts")
|
|
73
|
-
return `"when" carries ${str(k)} (traits, host, facts only)`;
|
|
106
|
+
if (k !== "traits" && k !== "host" && k !== "facts" && k !== "binding")
|
|
107
|
+
return `"when" carries ${str(k)} (traits, host, facts, binding only)`;
|
|
74
108
|
const w = when;
|
|
75
109
|
if (w["traits"] !== undefined) {
|
|
76
110
|
const t = w["traits"];
|
|
@@ -107,6 +141,8 @@ function whenViolation(when) {
|
|
|
107
141
|
return `"when.facts.${k}" is ${str(v)}, not a boolean`;
|
|
108
142
|
}
|
|
109
143
|
}
|
|
144
|
+
if (w["binding"] !== undefined)
|
|
145
|
+
return bindingConditionProblem(w["binding"]);
|
|
110
146
|
return undefined;
|
|
111
147
|
}
|
|
112
148
|
function textsOf(o) {
|
|
@@ -124,11 +160,14 @@ export function validateOverlay(raw, ctx) {
|
|
|
124
160
|
const r = received.value;
|
|
125
161
|
if (!isPlainRecord(r))
|
|
126
162
|
return fail(`an overlay is an object, got ${Array.isArray(r) ? "an array" : str(r)}`);
|
|
127
|
-
for (const k of Object.keys(r))
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
163
|
+
for (const k of Object.keys(r)) {
|
|
164
|
+
if (OVERLAY_KEYS.has(k))
|
|
165
|
+
continue;
|
|
166
|
+
const rewrite = k === "scope" ? `; an overlay names its binding as "when": { "binding": { "anyOf": [<binding>] } }` : "";
|
|
167
|
+
return fail(`an overlay carries ${str(k)} (id, op, target, text, message, when only)${rewrite}`);
|
|
168
|
+
}
|
|
169
|
+
const { id, op, target, text, message, when } = r;
|
|
170
|
+
if (typeof id !== "string" || id.length === 0 || codePointsOver(id, 128) || !ID_GRAMMAR.test(id))
|
|
132
171
|
return fail(`overlay id ${str(id)} is not ^[a-z0-9][a-z0-9/._-]*$ of at most 128 characters`);
|
|
133
172
|
if (id.startsWith("core/") && ctx.sugar !== true)
|
|
134
173
|
return fail(`overlay id ${str(id)}: the core/ namespace is the engine's own`);
|
|
@@ -150,11 +189,6 @@ export function validateOverlay(raw, ctx) {
|
|
|
150
189
|
if (why !== undefined)
|
|
151
190
|
return fail(`overlay "${id}": ${why}`);
|
|
152
191
|
}
|
|
153
|
-
if (scope !== undefined && ctx.source !== "center")
|
|
154
|
-
return fail(`overlay "${id}": "scope" selects among a center artifact's overlays; a ${ctx.source} overlay answers for this run`);
|
|
155
|
-
if (scope !== undefined && (!isPlainRecord(scope) || Object.entries(scope).some(([k, v]) => k !== "binding" || bindingProblem(v) !== undefined))) {
|
|
156
|
-
return fail(`overlay "${id}": "scope" is { binding?: a non-blank string of at most ${BINDING_MAX} characters }`);
|
|
157
|
-
}
|
|
158
192
|
let band;
|
|
159
193
|
let where;
|
|
160
194
|
if (op === "add") {
|
|
@@ -191,14 +225,14 @@ export function validateOverlay(raw, ctx) {
|
|
|
191
225
|
}
|
|
192
226
|
if (ctx.event !== undefined && row.modsEvent !== ctx.event)
|
|
193
227
|
return fail(`overlay "${id}": ${where} is written through ${row.modsEvent ?? "no mods event"}, not ${ctx.event}`);
|
|
228
|
+
const normalWhen = canonicalWhen(when);
|
|
194
229
|
const overlay = Object.freeze({
|
|
195
230
|
id,
|
|
196
231
|
op: op,
|
|
197
232
|
target,
|
|
198
233
|
...(text !== undefined ? { text: text } : {}),
|
|
199
234
|
...(message !== undefined ? { message: message } : {}),
|
|
200
|
-
...(
|
|
201
|
-
...(when !== undefined ? { when: when } : {}),
|
|
235
|
+
...(normalWhen !== undefined ? { when: normalWhen } : {}),
|
|
202
236
|
});
|
|
203
237
|
for (const t of textsOf(overlay)) {
|
|
204
238
|
const hits = lockedTextIn(t);
|
|
@@ -255,6 +289,10 @@ export function foldOverlays(ordered, selectedText, applies, overlayText) {
|
|
|
255
289
|
addedBy.delete(o.target);
|
|
256
290
|
continue;
|
|
257
291
|
}
|
|
292
|
+
if (!SECTION_BY_ID.has(o.target)) {
|
|
293
|
+
supersede(me, o.target, ABSENT_THIS_RUN);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
258
296
|
for (const m of sectionMessages(o.target)) {
|
|
259
297
|
for (const w of writers.get(m) ?? [])
|
|
260
298
|
supersede(w, m, o.id);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The selector: the one reading that picks, for a catalog message, the variant a run receives — and decides
|
|
3
|
-
* whether an overlay's `when` applies to this run.
|
|
4
|
-
* ever needs a precedence order:
|
|
3
|
+
* whether an overlay's `when` applies to this run (every source class alike). Four dimensions, each with exactly one
|
|
4
|
+
* writer, so no value ever needs a precedence order:
|
|
5
5
|
*
|
|
6
6
|
* - `traits` — derived from the run's two prompt-shape axes by {@link promptTraitsOf} (the prompt profile and the
|
|
7
7
|
* fable-family model); catalog data reads traits, never the axes.
|
|
@@ -9,10 +9,12 @@
|
|
|
9
9
|
* `form` (`TaskSpec.promptForm`, which every engine-built child receives through the parent spec seat; absent ⇒
|
|
10
10
|
* `standard`).
|
|
11
11
|
* - `facts` — the runtime facts prepare produces (one derived: `executionEnvironmentDeclared`).
|
|
12
|
+
* - `binding` — the deployment's scenario name (`TaskSpec.promptBinding`, an open vocabulary; absent ⇒ none).
|
|
12
13
|
*
|
|
13
|
-
* A
|
|
14
|
-
* equal,
|
|
14
|
+
* A `when` is a conjunction: every listed trait present, every `noneOf` trait absent, every host key equal, every
|
|
15
|
+
* fact equal, the binding inside its `anyOf` (or outside its `noneOf`). No disjunction across dimensions.
|
|
15
16
|
*/
|
|
17
|
+
import type { PromptManifestSelectors } from "../core/trace.js";
|
|
16
18
|
import { type CatalogMessage, type FactConjunction, type HostForm, type HostRole, type PromptFactName, type PromptTrait, type Variant, type VariantWhen } from "./catalog/schema.js";
|
|
17
19
|
import type { PromptRuntimeFacts } from "./types.js";
|
|
18
20
|
/** The run's selector values, resolved once per assembly. */
|
|
@@ -23,6 +25,7 @@ export interface PromptSelectors {
|
|
|
23
25
|
readonly form: HostForm;
|
|
24
26
|
};
|
|
25
27
|
readonly facts: Readonly<Record<PromptFactName, boolean>>;
|
|
28
|
+
readonly binding?: string;
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* The run's prompt traits, derived from the two prompt-shape axes the engine resolves today: the simple
|
|
@@ -44,13 +47,25 @@ export declare function resolveHost(task: {
|
|
|
44
47
|
readonly role?: HostRole;
|
|
45
48
|
readonly form?: HostForm;
|
|
46
49
|
};
|
|
47
|
-
/** The run's selectors: traits and facts from the runtime facts, the host from its two writers' values
|
|
50
|
+
/** The run's selectors — their one mint: traits and facts from the runtime facts, the host from its two writers' values,
|
|
51
|
+
* the binding as the task's door validated it. */
|
|
48
52
|
export declare function selectorsOf(facts: PromptRuntimeFacts, host?: {
|
|
49
53
|
readonly role?: HostRole;
|
|
50
54
|
readonly form?: HostForm;
|
|
51
|
-
}): PromptSelectors;
|
|
55
|
+
}, binding?: string): PromptSelectors;
|
|
56
|
+
/** The selectors' plain form (traits sorted): what the manifest shows, the identity's `binding` element digests and the
|
|
57
|
+
* `prompt.compose` event carries — one record. */
|
|
58
|
+
export declare function selectorRecordOf(s: PromptSelectors): PromptManifestSelectors;
|
|
52
59
|
export declare function factsHold(conj: FactConjunction | undefined, facts: Readonly<Record<PromptFactName, boolean>>): boolean;
|
|
53
60
|
/** Whether a `when` holds under the run's selectors. */
|
|
54
61
|
export declare function whenHolds(when: VariantWhen | undefined, s: PromptSelectors): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* A `when` in its normal form — the one spelling of what it asks: every list sorted and without repeats, a conjunct that
|
|
64
|
+
* asks nothing (an empty `traits`, `host` or `facts`, an empty list) left out, and a condition left with no conjunct
|
|
65
|
+
* (`{}`) is no condition at all (`undefined`). Two conditions that hold in the same runs by construction are equal as
|
|
66
|
+
* values here, so the overlay door (`validateOverlay`) hands every reader — the artifact digest, the publish-side
|
|
67
|
+
* conflict check — the same condition. Idempotent; it never changes which runs a condition holds for.
|
|
68
|
+
*/
|
|
69
|
+
export declare function canonicalWhen(when: VariantWhen | undefined): VariantWhen | undefined;
|
|
55
70
|
/** The variant this run receives, or undefined when no variant's `when` holds (the message is absent). */
|
|
56
71
|
export declare function selectVariant(message: CatalogMessage, s: PromptSelectors): Variant | undefined;
|
|
@@ -36,13 +36,22 @@ export function resolveHost(task) {
|
|
|
36
36
|
const form = task.promptForm === undefined ? undefined : task.promptForm === "minimal" ? "minimal" : refuse("promptForm", task.promptForm, ["minimal"]);
|
|
37
37
|
return Object.freeze({ ...(role !== undefined ? { role } : {}), ...(form !== undefined ? { form } : {}) });
|
|
38
38
|
}
|
|
39
|
-
export function selectorsOf(facts, host = {}) {
|
|
39
|
+
export function selectorsOf(facts, host = {}, binding) {
|
|
40
40
|
return Object.freeze({
|
|
41
41
|
traits: promptTraitsOf(facts),
|
|
42
42
|
host: Object.freeze({ role: host.role ?? "general", form: host.form ?? "standard" }),
|
|
43
43
|
facts: Object.freeze(catalogFactsOf(facts)),
|
|
44
|
+
...(binding !== undefined ? { binding } : {}),
|
|
44
45
|
});
|
|
45
46
|
}
|
|
47
|
+
export function selectorRecordOf(s) {
|
|
48
|
+
return {
|
|
49
|
+
...(s.binding !== undefined ? { binding: s.binding } : {}),
|
|
50
|
+
traits: [...s.traits].sort(),
|
|
51
|
+
host: { role: s.host.role, form: s.host.form },
|
|
52
|
+
facts: { ...s.facts },
|
|
53
|
+
};
|
|
54
|
+
}
|
|
46
55
|
export function factsHold(conj, facts) {
|
|
47
56
|
if (conj === undefined)
|
|
48
57
|
return true;
|
|
@@ -61,8 +70,36 @@ function hostHolds(cond, host) {
|
|
|
61
70
|
return true;
|
|
62
71
|
return (cond.role === undefined || cond.role === host.role) && (cond.form === undefined || cond.form === host.form);
|
|
63
72
|
}
|
|
73
|
+
function bindingHolds(cond, binding) {
|
|
74
|
+
if (cond === undefined)
|
|
75
|
+
return true;
|
|
76
|
+
if (cond.anyOf !== undefined)
|
|
77
|
+
return binding !== undefined && cond.anyOf.includes(binding);
|
|
78
|
+
return binding === undefined || !cond.noneOf.includes(binding);
|
|
79
|
+
}
|
|
64
80
|
export function whenHolds(when, s) {
|
|
65
|
-
return when === undefined || (traitsHold(when.traits, s.traits) && hostHolds(when.host, s.host) && factsHold(when.facts, s.facts));
|
|
81
|
+
return when === undefined || (traitsHold(when.traits, s.traits) && hostHolds(when.host, s.host) && factsHold(when.facts, s.facts) && bindingHolds(when.binding, s.binding));
|
|
82
|
+
}
|
|
83
|
+
const byText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
84
|
+
const sortedSet = (xs) => Object.freeze([...new Set(xs ?? [])].sort(byText));
|
|
85
|
+
export function canonicalWhen(when) {
|
|
86
|
+
if (when === undefined)
|
|
87
|
+
return undefined;
|
|
88
|
+
const allOf = sortedSet(when.traits?.allOf);
|
|
89
|
+
const noneOf = sortedSet(when.traits?.noneOf);
|
|
90
|
+
const hostKeys = Object.keys(when.host ?? {});
|
|
91
|
+
const factNames = Object.keys(when.facts ?? {}).sort(byText);
|
|
92
|
+
const binding = when.binding;
|
|
93
|
+
const out = {};
|
|
94
|
+
if (allOf.length > 0 || noneOf.length > 0)
|
|
95
|
+
out.traits = Object.freeze({ ...(allOf.length > 0 ? { allOf } : {}), ...(noneOf.length > 0 ? { noneOf } : {}) });
|
|
96
|
+
if (hostKeys.length > 0)
|
|
97
|
+
out.host = Object.freeze({ ...when.host });
|
|
98
|
+
if (factNames.length > 0)
|
|
99
|
+
out.facts = Object.freeze(Object.fromEntries(factNames.map((n) => [n, when.facts[n]])));
|
|
100
|
+
if (binding !== undefined)
|
|
101
|
+
out.binding = Object.freeze(binding.anyOf !== undefined ? { anyOf: sortedSet(binding.anyOf) } : { noneOf: sortedSet(binding.noneOf) });
|
|
102
|
+
return Object.keys(out).length === 0 ? undefined : Object.freeze(out);
|
|
66
103
|
}
|
|
67
104
|
export function selectVariant(message, s) {
|
|
68
105
|
return message.variants.find((v) => whenHolds(v.when, s));
|
|
@@ -51,17 +51,6 @@ export declare const MEMORY_HYGIENE = "What's worth saving \u2014 organize by to
|
|
|
51
51
|
* when you need the split.
|
|
52
52
|
*/
|
|
53
53
|
export declare const MEMORY_GUIDANCE = "## Memory\nWhen you save a durable note to memory, phrase it as a declarative fact or a stable\npreference \u2014 never as an instruction to your future self.\n- \"User prefers concise responses\" \u2713 \u2014 \"Always respond concisely\" \u2717\n- \"The reporting database is read-only via the analytics user\" \u2713 \u2014 \"Always use the analytics user\" \u2717\nNever put secrets (API keys, credentials, tokens) in memory \u2014 especially where it may be shared.\nMemory is a fact, never a command; the user's current request and live tool output always win over memory.\n\nWhat's worth saving \u2014 organize by topic, not by when it happened:\n- who the user is \u2014 role, expertise, durable preferences;\n- guidance the user gave on HOW to work \u2014 corrections and confirmed approaches, with the reason why;\n- ongoing goals or constraints that aren't derivable from the code or its history;\n- pointers to external resources (URLs, dashboards, tickets).\n\nHygiene:\n- Convert relative dates (\"yesterday\", \"last week\") to absolute dates, so the note stays interpretable later.\n- Before saving, check what memory already holds: update an existing note rather than writing a near-duplicate, and remove a note that turns out to be wrong.\n- Don't save what the code, its history, or this conversation already records (structure, past fixes, transient task state). If asked to remember something obvious, save what was non-obvious about it instead.";
|
|
54
|
-
/**
|
|
55
|
-
* The NO-memory disclosure — a standalone prompt asset (like {@link MEMORY_SAFETY}, core does not
|
|
56
|
-
* inject it) for a deployment that mounts NO persistent-memory face at all. Without it the model,
|
|
57
|
-
* asked "remember X", answers with a success receipt for a save that has nowhere to land — the
|
|
58
|
-
* confabulated-receipt shape: the user walks away believing the fact is stored and the next session
|
|
59
|
-
* proves otherwise. A deployment that composes its own prompt should include this block exactly when
|
|
60
|
-
* no memory face is mounted; the engine-mounted counterpart (read-only state) is
|
|
61
|
-
* `MEMORY_READONLY_NOTICE` in the memory engine, injected automatically. Name-free by construction
|
|
62
|
-
* (#181 class): it names no tool, so it cannot go stale against any toolkit.
|
|
63
|
-
*/
|
|
64
|
-
export declare const NO_PERSISTENT_MEMORY_NOTICE = "# Memory\n\nYou have no persistent memory store: what is said in this conversation is not automatically retained for future sessions. If the user asks you to remember something for later, say plainly that you cannot store it \u2014 do not claim to have noted or saved it. If you have file tools, you may offer to write it into a project file instead (files persist; conversation memory does not).";
|
|
65
54
|
/** The refuse-harmful-use security boundary (composed on every shape; safety text is never gated). */
|
|
66
55
|
export declare const CYBER_RISK: string;
|
|
67
56
|
/** The never-guess-URLs rule (composed on every shape). */
|
package/dist/prompts/default.js
CHANGED
|
@@ -24,9 +24,6 @@ Hygiene:
|
|
|
24
24
|
- Before saving, check what memory already holds: update an existing note rather than writing a near-duplicate, and remove a note that turns out to be wrong.
|
|
25
25
|
- Don't save what the code, its history, or this conversation already records (structure, past fixes, transient task state). If asked to remember something obvious, save what was non-obvious about it instead.`;
|
|
26
26
|
export const MEMORY_GUIDANCE = `${MEMORY_SAFETY}\n\n${MEMORY_HYGIENE}`;
|
|
27
|
-
export const NO_PERSISTENT_MEMORY_NOTICE = `# Memory
|
|
28
|
-
|
|
29
|
-
You have no persistent memory store: what is said in this conversation is not automatically retained for future sessions. If the user asks you to remember something for later, say plainly that you cannot store it — do not claim to have noted or saved it. If you have file tools, you may offer to write it into a project file instead (files persist; conversation memory does not).`;
|
|
30
27
|
export const CYBER_RISK = catalogText("core/security.cyber-risk");
|
|
31
28
|
export const URL_SAFETY = catalogText("core/security.url-safety");
|
|
32
29
|
export const SUMMARIZE_TOOL_RESULTS = catalogText("core/harness.tool-result-retention");
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
import type { PersistedRule, RuleTombstone } from "../../core/permission-rule-model.js";
|
|
53
53
|
import type { DurableRulePartition, DurableRulePartitionProvider, PermissionRuleWriter, QuarantinedRuleAdd, RuleOwner, RuleSyncFrontier, StoredRules, WritableDurableRulePartition } from "../../core/permission-rule-store.js";
|
|
54
54
|
import { PERMISSION_RULE_WRITER } from "../../core/permission-rule-store.js";
|
|
55
|
-
import type
|
|
55
|
+
import { type OrgRuleStatePersistence, type PersistedOrgRuleState } from "../../core/permission-rule-org.js";
|
|
56
56
|
import type { StoreDurability, StoreFidelity } from "../../core/checkpoint-store.js";
|
|
57
57
|
/**
|
|
58
58
|
* The on-disk shape of one bucket. `schemaVersion` is the only field a future reader may rely on before
|
|
@@ -4,6 +4,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
4
4
|
import { isRuleBehavior } from "../../core/permission-rule-model.js";
|
|
5
5
|
import { posixModeBitsMeaningful } from "../../core/posix-mode-face.js";
|
|
6
6
|
import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, foldDelta, unknownRuleWriteDeltaError, } from "../../core/permission-rule-store.js";
|
|
7
|
+
import { sameOrgPolicyContent } from "../../core/permission-rule-org.js";
|
|
7
8
|
import { canonicalize } from "../../core/canonical-json.js";
|
|
8
9
|
import { BootLock, FileStoreLockError } from "./fs-atomic.js";
|
|
9
10
|
import { assertAdoptionBootGate } from "./adoption/marker.js";
|
|
@@ -549,7 +550,7 @@ class FileOrgRuleStatePersistence {
|
|
|
549
550
|
if (cur !== undefined && state.revisionHighWater < cur.revisionHighWater) {
|
|
550
551
|
throw new OrgRevisionRefusal(`refusing to lower the org revision high-water mark (${cur.revisionHighWater} → ${state.revisionHighWater}) — an interleaved install must re-read and re-compare`);
|
|
551
552
|
}
|
|
552
|
-
if (cur !== undefined && state.snapshot.revision === cur.snapshot.revision &&
|
|
553
|
+
if (cur !== undefined && state.snapshot.revision === cur.snapshot.revision && !sameOrgPolicyContent(state.snapshot, cur.snapshot)) {
|
|
553
554
|
throw new OrgRevisionRefusal(`refusing to replace org revision ${state.snapshot.revision} with DIFFERENT policy content under the same revision — content changes must bump the revision`);
|
|
554
555
|
}
|
|
555
556
|
if (cur !== undefined && state.snapshot.revision === cur.snapshot.revision && state.snapshot.fetchedAtMs <= cur.snapshot.fetchedAtMs)
|
|
@@ -64,6 +64,7 @@ function renderFile(f) {
|
|
|
64
64
|
return isTextMedia(f.mediaType) ? `${head}\n${Buffer.from(f.content).toString("utf8")}` : `${head}\n[binary content, not shown]`;
|
|
65
65
|
}
|
|
66
66
|
const isRecord = (x) => x !== null && typeof x === "object";
|
|
67
|
+
const withDefaultAction = (args) => (isRecord(args) && args["action"] === undefined ? { ...args, action: "publish" } : args);
|
|
67
68
|
const everyItem = (x, pred) => Array.isArray(x) && x.every(pred);
|
|
68
69
|
const optString = (v) => v === undefined || typeof v === "string";
|
|
69
70
|
const isRef = (x) => isRecord(x) && typeof x["url"] === "string" && typeof x["slug"] === "string" && typeof x["version"] === "string" && optString(x["title"]);
|
|
@@ -445,11 +446,11 @@ export function createArtifactTool(opts) {
|
|
|
445
446
|
contentOrigin: ARTIFACT_PLANE_CONTENT_ORIGIN[caps.plane],
|
|
446
447
|
description: artifactDescription(caps.plane, caps.actions),
|
|
447
448
|
parameters: buildSchema(caps),
|
|
448
|
-
prepareArguments:
|
|
449
|
+
prepareArguments: withDefaultAction,
|
|
449
450
|
isConcurrencySafe: (args) => isRecord(args) && typeof args["action"] === "string" && ARTIFACT_ACTION_CONCURRENCY_SAFE[args["action"]] === true,
|
|
450
451
|
execute: async (args, ctx) => {
|
|
451
|
-
const a = args;
|
|
452
|
-
const action = a.action
|
|
452
|
+
const a = withDefaultAction(args);
|
|
453
|
+
const action = a.action;
|
|
453
454
|
const target = typeof a.url === "string" ? a.url : undefined;
|
|
454
455
|
if (target !== undefined)
|
|
455
456
|
activeTargets.set(target, (activeTargets.get(target) ?? 0) + 1);
|