@sema-agent/settings-schema 1.10.0 → 2.0.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 +137 -3
- package/README.md +29 -2
- package/dist/config-fns.d.ts +72 -4
- package/dist/config-fns.js +204 -6
- package/dist/cross-domain.d.ts +8 -4
- package/dist/cross-domain.js +6 -2
- package/dist/fleet.d.ts +1 -1
- package/dist/fleet.js +1 -1
- package/dist/hooks.d.ts +367 -19
- package/dist/hooks.js +76 -8
- package/dist/remote-exec.d.ts +4 -4
- package/dist/remote-exec.js +1 -1
- package/dist/resolve-roster.d.ts +1 -1
- package/dist/scheduler-store.d.ts +13 -3
- package/dist/scheduler-store.js +13 -3
- package/dist/skills-manifest.js +1 -1
- package/dist/types.d.ts +663 -642
- package/dist/types.js +95 -46
- package/package.json +2 -1
package/dist/hooks.js
CHANGED
|
@@ -2,10 +2,15 @@
|
|
|
2
2
|
* `hooks` — the CC-parity hooks contract (incl. stop attribution).
|
|
3
3
|
*
|
|
4
4
|
* ONE definition of the FULL hook-event set — 27 CC 2.1.198 events (REF: the `jP` array in
|
|
5
|
-
* claude-cli-2.1.198.pretty.js) +
|
|
6
|
-
*
|
|
7
|
-
* from day one; the engine hook-runner (service) and the shell light events
|
|
8
|
-
* later is pure implementation, never a schema change.
|
|
5
|
+
* claude-cli-2.1.198.pretty.js) + 6 post-198 events (CwdChanged / FileChanged / MessageDisplay, DirectoryAdded
|
|
6
|
+
* from CC 2.1.219, and the model-switch pair PreModelSwitch / PostModelSwitch from CC 2.1.259). Per the product
|
|
7
|
+
* ruling: the CONTRACT is the full set from day one; the engine hook-runner (service) and the shell light events
|
|
8
|
+
* up in PHASES — adding an event later is pure implementation, never a schema change.
|
|
9
|
+
*
|
|
10
|
+
* DRIFT: this package OWNS the hooks contract, so it owns noticing that CC moved. `test/upstream-drift.test.ts`
|
|
11
|
+
* compares {@link HOOK_EVENTS} against a CC anchor snapshot that `scripts/extract-cc-hook-events.mjs` extracts
|
|
12
|
+
* from the CLI bundle — a word CC has and this set does not is a RED test naming it, instead of a consumer
|
|
13
|
+
* discovering it the hard way (the 1.3.0 / 1.6.0 / 1.8.0 / 1.11.0 pattern).
|
|
9
14
|
*
|
|
10
15
|
* CONSUMERS
|
|
11
16
|
* - service (the worker server): `TaskRequest.settings.hooks` wire face = `HooksConfig` below; the worker-side
|
|
@@ -56,12 +61,18 @@ export const CC_198_HOOK_EVENTS = [
|
|
|
56
61
|
"InstructionsLoaded",
|
|
57
62
|
];
|
|
58
63
|
/**
|
|
59
|
-
* Post-CC-2.1.198 additions
|
|
64
|
+
* Post-CC-2.1.198 additions (sema superset; CC added them after 198).
|
|
60
65
|
* APPEND-ONLY: new members go at the tail (1.3.0 `DirectoryAdded`, CC 2.1.219 — CC's own tuple places it
|
|
61
66
|
* between FileChanged and MessageDisplay; the declaration order here is not normative, the enum is a set).
|
|
67
|
+
* 1.11.0 appends the model-switch pair (`PreModelSwitch` / `PostModelSwitch`, CC 2.1.259 — absent from the
|
|
68
|
+
* 2.1.250 corpus, so 250→259 is when they landed upstream).
|
|
69
|
+
*
|
|
70
|
+
* 🔴 This list is no longer maintained by hand-reading a CLI bundle: `test/upstream-drift.test.ts` fails
|
|
71
|
+
* (naming the missing words) whenever the CC anchor snapshot in `test/fixtures/cc-hook-events.json` holds an
|
|
72
|
+
* event this set does not, and that snapshot is produced by `scripts/extract-cc-hook-events.mjs`, never typed.
|
|
62
73
|
*/
|
|
63
|
-
export const POST_198_HOOK_EVENTS = ["CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded"];
|
|
64
|
-
/** The full
|
|
74
|
+
export const POST_198_HOOK_EVENTS = ["CwdChanged", "FileChanged", "MessageDisplay", "DirectoryAdded", "PreModelSwitch", "PostModelSwitch"];
|
|
75
|
+
/** The full 33-event contract set = CC 198's 27 + the 6 post-198 events. */
|
|
65
76
|
export const HOOK_EVENTS = [...CC_198_HOOK_EVENTS, ...POST_198_HOOK_EVENTS];
|
|
66
77
|
export const HookEventName = z.enum(HOOK_EVENTS);
|
|
67
78
|
// ── ownership (owner-single) ─────────────────────────────────────────────────────────────────────
|
|
@@ -124,6 +135,10 @@ export const HOOK_EVENT_MATCHER_FIELD = {
|
|
|
124
135
|
FileChanged: "file_path", // matched against basename(file_path)
|
|
125
136
|
MessageDisplay: null,
|
|
126
137
|
DirectoryAdded: null, // the shell's getMatchingHooks switch has no arm for it → every entry matches (CwdChanged family)
|
|
138
|
+
// CC 2.1.259 matches both model-switch events against the RESOLVED target model id (`to_model`), so a
|
|
139
|
+
// matcher can scope a hook to "only when switching TO this model".
|
|
140
|
+
PreModelSwitch: "to_model",
|
|
141
|
+
PostModelSwitch: "to_model",
|
|
127
142
|
};
|
|
128
143
|
// ── shared enums (CC-verbatim) ─────────────────────────────────────────────────────────────────────────
|
|
129
144
|
export const PermissionBehavior = z.enum(["allow", "deny", "ask"]);
|
|
@@ -398,7 +413,49 @@ export const DirectoryAddedHookInput = BaseHookInput.extend({
|
|
|
398
413
|
directory: z.string(),
|
|
399
414
|
source: z.enum(DIRECTORY_ADDED_SOURCES),
|
|
400
415
|
});
|
|
401
|
-
/**
|
|
416
|
+
/**
|
|
417
|
+
* What triggered a model switch (CC 2.1.259). The two events carry DIFFERENT vocabularies and that is
|
|
418
|
+
* deliberate upstream: `PreModelSwitch` only fires for a REQUESTED switch (slash command / picker / headless
|
|
419
|
+
* set_model), while `PostModelSwitch` also reports changes nobody requested (`auto` = automatic fallback,
|
|
420
|
+
* `resume` = model restored while resuming a session) — those have no "before" to gate.
|
|
421
|
+
*/
|
|
422
|
+
export const MODEL_SWITCH_SOURCES = {
|
|
423
|
+
pre: ["command", "picker", "sdk"],
|
|
424
|
+
post: ["command", "picker", "sdk", "auto", "resume"],
|
|
425
|
+
};
|
|
426
|
+
/** The fields both model-switch events carry (CC-verbatim): the models, what was asked for, and the
|
|
427
|
+
* cache-forfeit estimate a hook needs to decide whether the switch is worth it. */
|
|
428
|
+
const ModelSwitchFields = {
|
|
429
|
+
/** Resolved model id the session was running before the switch. */
|
|
430
|
+
from_model: z.string(),
|
|
431
|
+
/** Resolved model id the session runs after the switch (also the `matcher` field for both events). */
|
|
432
|
+
to_model: z.string(),
|
|
433
|
+
/** What was asked for — an alias such as `"opus"`, a full id, or `null` for "default". NULLABLE, not optional. */
|
|
434
|
+
requested_model: z.string().nullable(),
|
|
435
|
+
/** Prompt tokens the next request re-sends (0 before the first response). */
|
|
436
|
+
context_tokens: z.number(),
|
|
437
|
+
/** Whether the current model's prompt cache is likely still warm (a switch then forfeits it). */
|
|
438
|
+
prompt_cache_warm: z.boolean(),
|
|
439
|
+
cache_ttl: z.enum(["5m", "1h"]),
|
|
440
|
+
/** Estimated cost of re-caching `context_tokens` on `to_model` at its cache-write rate (excludes the response). */
|
|
441
|
+
estimated_cache_write_usd: z.number(),
|
|
442
|
+
/** `configured` = managed modelPricing; `catalog` = list price; `default` = `to_model` unknown, default tier assumed. */
|
|
443
|
+
pricing: z.enum(["configured", "catalog", "default"]),
|
|
444
|
+
};
|
|
445
|
+
/** Fires BEFORE a requested model switch; its `hookSpecificOutput.permissionDecision` can deny/ask (same
|
|
446
|
+
* contract as PreToolUse — DESIGN POINT ① applies: the engine clamps `allow` to the policy floor). */
|
|
447
|
+
export const PreModelSwitchHookInput = BaseHookInput.extend({
|
|
448
|
+
hook_event_name: z.literal("PreModelSwitch"),
|
|
449
|
+
...ModelSwitchFields,
|
|
450
|
+
source: z.enum(MODEL_SWITCH_SOURCES.pre),
|
|
451
|
+
});
|
|
452
|
+
/** Fires AFTER the session model changed, whatever the cause (informational; may add context). */
|
|
453
|
+
export const PostModelSwitchHookInput = BaseHookInput.extend({
|
|
454
|
+
hook_event_name: z.literal("PostModelSwitch"),
|
|
455
|
+
...ModelSwitchFields,
|
|
456
|
+
source: z.enum(MODEL_SWITCH_SOURCES.post),
|
|
457
|
+
});
|
|
458
|
+
/** Any hook input — discriminated on `hook_event_name` (all 33 events). */
|
|
402
459
|
export const HookInput = z.discriminatedUnion("hook_event_name", [
|
|
403
460
|
PreToolUseHookInput,
|
|
404
461
|
PostToolUseHookInput,
|
|
@@ -431,6 +488,8 @@ export const HookInput = z.discriminatedUnion("hook_event_name", [
|
|
|
431
488
|
FileChangedHookInput,
|
|
432
489
|
MessageDisplayHookInput,
|
|
433
490
|
DirectoryAddedHookInput,
|
|
491
|
+
PreModelSwitchHookInput,
|
|
492
|
+
PostModelSwitchHookInput,
|
|
434
493
|
]);
|
|
435
494
|
// ── hook output / decision semantics (CC-verbatim, with the service-receipt design points pinned) ────────
|
|
436
495
|
//
|
|
@@ -502,6 +561,15 @@ export const HookSpecificOutput = z.union([
|
|
|
502
561
|
z.object({ hookEventName: z.literal("CwdChanged"), watchPaths: z.array(z.string()).optional() }),
|
|
503
562
|
z.object({ hookEventName: z.literal("FileChanged"), watchPaths: z.array(z.string()).optional() }),
|
|
504
563
|
z.object({ hookEventName: z.literal("WorktreeCreate"), worktreePath: z.string() }),
|
|
564
|
+
z.object({
|
|
565
|
+
hookEventName: z.literal("PreModelSwitch"),
|
|
566
|
+
/** CC: `allow` proceeds (skipping the interactive cache-miss confirm), `deny` cancels the switch, `ask`
|
|
567
|
+
* asks the user to confirm (a headless session refuses instead). Same wording as PreToolUse — DESIGN
|
|
568
|
+
* POINT ① applies: a hook `allow` never overrides a policy floor. */
|
|
569
|
+
permissionDecision: PermissionBehavior.optional(),
|
|
570
|
+
permissionDecisionReason: z.string().optional(),
|
|
571
|
+
}),
|
|
572
|
+
z.object({ hookEventName: z.literal("PostModelSwitch"), additionalContext: z.string().optional() }),
|
|
505
573
|
]);
|
|
506
574
|
/**
|
|
507
575
|
* Synchronous hook JSON output (stdout of a command hook, CC-verbatim). Exit-code semantics ride
|
package/dist/remote-exec.d.ts
CHANGED
|
@@ -61,7 +61,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
61
61
|
memory?: string | undefined;
|
|
62
62
|
workdir?: string | undefined;
|
|
63
63
|
cpus?: number | undefined;
|
|
64
|
-
network?: "
|
|
64
|
+
network?: "none" | "host" | "bridge" | undefined;
|
|
65
65
|
mountPath?: string | undefined;
|
|
66
66
|
}, {
|
|
67
67
|
provider: "local-docker";
|
|
@@ -69,7 +69,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
69
69
|
memory?: string | undefined;
|
|
70
70
|
workdir?: string | undefined;
|
|
71
71
|
cpus?: number | undefined;
|
|
72
|
-
network?: "
|
|
72
|
+
network?: "none" | "host" | "bridge" | undefined;
|
|
73
73
|
mountPath?: string | undefined;
|
|
74
74
|
}>, z.ZodObject<{
|
|
75
75
|
/** The remote docker endpoint — a DOCKER_HOST url: `ssh://user@host` or `tcp://host:2376`. A `user@` is OK
|
|
@@ -96,7 +96,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
96
96
|
memory?: string | undefined;
|
|
97
97
|
workdir?: string | undefined;
|
|
98
98
|
cpus?: number | undefined;
|
|
99
|
-
network?: "
|
|
99
|
+
network?: "none" | "host" | "bridge" | undefined;
|
|
100
100
|
mountPath?: string | undefined;
|
|
101
101
|
tlsCertEnv?: string | undefined;
|
|
102
102
|
tlsKeyEnv?: string | undefined;
|
|
@@ -109,7 +109,7 @@ export declare const RemoteExecSpec: z.ZodDiscriminatedUnion<"provider", [z.ZodO
|
|
|
109
109
|
memory?: string | undefined;
|
|
110
110
|
workdir?: string | undefined;
|
|
111
111
|
cpus?: number | undefined;
|
|
112
|
-
network?: "
|
|
112
|
+
network?: "none" | "host" | "bridge" | undefined;
|
|
113
113
|
mountPath?: string | undefined;
|
|
114
114
|
tlsCertEnv?: string | undefined;
|
|
115
115
|
tlsKeyEnv?: string | undefined;
|
package/dist/remote-exec.js
CHANGED
|
@@ -28,7 +28,7 @@ import { ENV_NAME, K8S_QUANTITY, NON_SECRET_URL, TCP_PORT, containsSecretToken,
|
|
|
28
28
|
* byte uniformly — a NUL validates here but makes Node throw at spawn/boot (fail-OPEN → runtime crash). R20. */
|
|
29
29
|
const execStr = z.string().min(1).refine((s) => !hasNul(s), "must not contain a NUL byte");
|
|
30
30
|
/** Container knobs shared by the two docker lanes (local-docker / remote-docker). `network` defaults to
|
|
31
|
-
* `none` at the adapter (the safe posture for untrusted code — see
|
|
31
|
+
* `none` at the adapter (the safe posture for untrusted code — see the dual-mode design note). */
|
|
32
32
|
const DOCKER_FIELDS = {
|
|
33
33
|
/** Sandbox image (required — a run must be reproducible). */
|
|
34
34
|
image: execStr,
|
package/dist/resolve-roster.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-worker roster resolution (
|
|
2
|
+
* Per-worker roster resolution (the multi-roster design note). Turns the GLOBAL effective config + a worker name
|
|
3
3
|
* into THAT worker's effective config: its `models` domain is the roster's view (model subset + role-map +
|
|
4
4
|
* @-allowlist override), everything else stays global. The `rosters` domain is control-plane-only and is
|
|
5
5
|
* always stripped from the wire payload (no /effective consumer reads it).
|
|
@@ -54,13 +54,23 @@ export interface SchedulerStore {
|
|
|
54
54
|
}
|
|
55
55
|
/** Structural validation of one record (required fields + a well-formed `when`). Unknown fields are allowed. */
|
|
56
56
|
export declare function isSchedulerRecord(v: unknown): v is SchedulerRecord;
|
|
57
|
-
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
/** Something {@link parseSchedulerStore} DID to the data it read, rather than reading it verbatim. `index` =
|
|
58
|
+
* the record's position in the parsed array (for correlating with the source file). Additive — passing
|
|
59
|
+
* `onWarning` is optional, and a new `kind` is an additive union member (a consumer with an exhaustive
|
|
60
|
+
* `switch` gains an arm to write).
|
|
61
|
+
* - `malformed-record` — the record failed {@link isSchedulerRecord} and was dropped; `record` = the
|
|
62
|
+
* raw (unvalidated) value, for diagnostics.
|
|
63
|
+
* - `durable-scope-rekeyed` — the durable-scope compat arm rewrote this row's `scope` (1.11.0; before that
|
|
64
|
+
* the rewrite happened silently every parse). */
|
|
60
65
|
export type SchedulerParseWarning = {
|
|
61
66
|
index: number;
|
|
62
67
|
kind: 'malformed-record';
|
|
63
68
|
record: unknown;
|
|
69
|
+
} | {
|
|
70
|
+
index: number;
|
|
71
|
+
kind: 'durable-scope-rekeyed';
|
|
72
|
+
from: string;
|
|
73
|
+
to: string;
|
|
64
74
|
};
|
|
65
75
|
export declare function parseSchedulerStore(text: string, onWarning?: (w: SchedulerParseWarning) => void): SchedulerRecord[];
|
|
66
76
|
/** Serialize the store to its canonical on-disk form (stable, pretty JSON). The WRITE half of the format contract. */
|
package/dist/scheduler-store.js
CHANGED
|
@@ -55,6 +55,11 @@ export function isSchedulerRecord(v) {
|
|
|
55
55
|
*
|
|
56
56
|
* 窄读判据:`lifetime` 不在本契约的显式键上(server 写侧作为 forward-compat 字段随行),按未知
|
|
57
57
|
* 字段窄读——`"session"` 行是会话链键,零触碰;其余(缺席=durable 默认)按 durable 归一。
|
|
58
|
+
*
|
|
59
|
+
* 落日(1.11.0):删除它 = **持久数据**面的破坏(仍按旧键存着的 durable 行会就此找不回),所以不能在
|
|
60
|
+
* minor 上一删了事 —— 归下一个 MAJOR,按持久数据的规矩办(响亮拒收 + 一次性迁移)。在那之前,本版把
|
|
61
|
+
* 这次重写从**静默**改成**出声**(`durable-scope-rekeyed`,带改写前后):关窗的前提是有人能证明它
|
|
62
|
+
* 已经不再触发,而不是有人记得它还在。
|
|
58
63
|
*/
|
|
59
64
|
function normalizeDurableScope(r) {
|
|
60
65
|
if (r.lifetime === 'session')
|
|
@@ -76,10 +81,15 @@ export function parseSchedulerStore(text, onWarning) {
|
|
|
76
81
|
return parsed.filter(isSchedulerRecord).map(normalizeDurableScope);
|
|
77
82
|
const out = [];
|
|
78
83
|
parsed.forEach((record, index) => {
|
|
79
|
-
if (isSchedulerRecord(record))
|
|
80
|
-
out.push(normalizeDurableScope(record));
|
|
81
|
-
else
|
|
84
|
+
if (!isSchedulerRecord(record)) {
|
|
82
85
|
onWarning({ index, kind: 'malformed-record', record });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const normalized = normalizeDurableScope(record);
|
|
89
|
+
// 兼容臂真改写了这一行才出声(未改写时 normalizeDurableScope 返回同一个引用)。
|
|
90
|
+
if (normalized !== record)
|
|
91
|
+
onWarning({ index, kind: 'durable-scope-rekeyed', from: record.scope, to: normalized.scope });
|
|
92
|
+
out.push(normalized);
|
|
83
93
|
});
|
|
84
94
|
return out;
|
|
85
95
|
}
|
package/dist/skills-manifest.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skills content-addressing (
|
|
2
|
+
* Skills content-addressing (the mcp/skills design note §3.1, slice 2). The /effective payload ships skills as a
|
|
3
3
|
* MANIFEST — each skill's markdown `content` replaced by a `contentHash` — so the frequent 60s refresh stays
|
|
4
4
|
* small; the service pulls each body ONCE (at boot) via GET /api/config/skills/content/<hash>, caches it by
|
|
5
5
|
* hash, and re-pulls only when the hash changes (skills rarely change → big win). Bodies are immutable +
|