@sema-agent/settings-schema 1.5.0 → 1.6.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 CHANGED
@@ -6,6 +6,31 @@
6
6
  > `@sema-agent/registry-core`(更早还曾是 `@sema-ai/registry-core`)。本档 `0.19.0` 以下各节里的
7
7
  > 包名是**历史记录**,如实保留不追改。
8
8
 
9
+ ## 1.6.0 — 2026-09-05 — `settings.json` 跨会话两键 `crossSessionInbound` / `dialogExpiry`(新模块/新导出=minor)
10
+
11
+ 成案:跨终端会话设计稿(design/385 §4.4/§8)把这两个 settings 键的属主定在 worker 服务侧,而键的**格式契约**
12
+ 按本包定位落这里(与 `hooks` 同姿势:本包出 zod 形,引擎出判定,宿主出装配)。此前 worker 服务的 settings 键闭集门
13
+ 不认这两个键 ⇒ 一份合法的 CC `settings.json` 带上它们会被整请求 400;本节是那条链的上游一格。
14
+
15
+ **新模块 `src/cross-session.ts`(主 barrel 导出,无新 subpath ⇒ `exports` 表 21 项不变)**:
16
+
17
+ - `crossSessionInbound` —— 显式闭集 `CROSS_SESSION_INBOUND_VALUES = ["accept","hold","refuse"]`,**顺序即收紧秩**
18
+ (accept < hold < refuse:层次解析只许沿此序收紧);UI 第四拼写 `CROSS_SESSION_INBOUND_UNSET = "default"`
19
+ = **未设**(不是第四种策略),未设 ⇒ mode-parity。CC describe 逐字锚:「Inbound cross-session peer messages
20
+ (SendMessage from your other sessions)」。
21
+ - `dialogExpiry` —— `DIALOG_EXPIRY_VALUES = ["60s","5m","10m","never"]` + `DIALOG_EXPIRY_DEFAULT = "5m"`。
22
+ CC describe 明写**两个**消费面(远端停靠的审批对话框 / 停审的跨会话消息),本包只声明键形,宿主自陈消费哪一面。
23
+ - zod 受理面 `CrossSessionInboundSetting` / `DialogExpirySetting`;片段 schema `CrossSessionSettings`
24
+ (两键可选 + `.passthrough()` —— 本片段**不是**整份 settings 的闭集门)。
25
+ - 层读取器 `readCrossSessionInbound` / `readDialogExpiry` → `SettingLayerRead<T>` 三态(未设 / 显式值 / 词表外)。
26
+ **坏值方向 = 响亮**:词表外恒 `ok:false` 交调用方拒,与 CC 文件层的 `.catch(undefined)` 静默吞成未设有意分岔
27
+ (一个大小写拼错静默读成「未设」会把用户以为已关的入站车道悄悄打开)。
28
+
29
+ **锁步义务(写进模块头注)**:引擎侧另有一份同词表(判定纯函数的输入域),两份必须逐字相等;等值由同时装了本包
30
+ 与引擎的消费方用一格锁步钉住——本包保持纯度不变量,不 import 引擎。
31
+
32
+ 红先:`test/cross-session-1.6.test.ts` 20 格,落 `src/cross-session.ts` 之前首跑红(模块不存在)。
33
+
9
34
  ## 1.5.0 — 2026-09-04 — 扩展命名空间 + 剥键告警截断标志(sema-server S-118 源头修;新键/新导出=minor)
10
35
 
11
36
  成案:sema-server 修 cli 用户 P1「`/model` 热加载被装饰键整拒」时在下游连打四层补丁(按 kind 放宽→按域→按键路径闭集→
@@ -0,0 +1,78 @@
1
+ /**
2
+ * `settings.json` 的**跨会话两键** —— CC-parity 契约(`crossSessionInbound` / `dialogExpiry`)。
3
+ *
4
+ * 与 `hooks.ts` 同姿势:本包只出**格式契约**(纯 zod、零运行时依赖、浏览器安全),不做任何解释/判定。
5
+ * 判定(层次收紧、mode-parity、hold 因由)归引擎;写行、渲染、下发归各宿主。
6
+ *
7
+ * ## 两键的 CC 锚(逐字核过的语料读数,非转述)
8
+ *
9
+ * - `crossSessionInbound: enum(["accept","hold","refuse"])` —— describe:「Inbound cross-session peer
10
+ * messages (SendMessage from your other sessions)」:`accept` 投递、`hold` 停在收件方的待审队列
11
+ * (模型看不见、不能据此行动)、`refuse` 让本会话整体退出这条车道。**显式值恒赢**;未设 =
12
+ * mode-parity(发送方与收件方的权限档位同类才自动投递,不同类停审)。
13
+ * - `dialogExpiry: enum(["60s","5m","10m","never"])`,缺省 `"5m"` —— describe:「Max time a
14
+ * permission/user dialog forwarded to a remote client stays parked awaiting an answer, and how long a
15
+ * HELD cross-session message awaits approval」。**注意它有两个消费面**:远端停靠的审批对话框,与
16
+ * 停审的跨会话消息;本模块只声明键形,哪个宿主消费哪一面由宿主自陈。
17
+ *
18
+ * ## `"default"` 这第四个拼写
19
+ *
20
+ * 设置 UI 的选择器需要一个「不设」的可写值,CC 用字面量 `"default"`。它**不是第四种策略**:读取器
21
+ * (`readCrossSessionInbound` / `readDialogExpiry`)把它与 `null`/缺席一同折成 `undefined`。所以
22
+ * `CrossSessionInboundSetting` 的受理面是四拼写,而**值域闭集**(`CROSS_SESSION_INBOUND_VALUES`)只有三个。
23
+ *
24
+ * ## 坏值方向 = 响亮,不静默
25
+ *
26
+ * 词表外的值一律 `ok:false`,由调用方拒(HTTP 门 400 / 文件层告警)。CC 在文件层用 `.catch(undefined)`
27
+ * 静默吞成未设;本栈的坏值响亮律与之有意分岔——一个 `crossSessionInbound: "Hold"`(大小写拼错)静默
28
+ * 读成「未设」会把用户以为已经关掉的入站车道悄悄打开,那正是保护型配置最不能有的沉默。
29
+ *
30
+ * ## 与引擎词表的锁步
31
+ *
32
+ * 引擎另有一份同词表(判定纯函数的输入域)。两份**必须逐字相等**;等值由消费方(同时装了本包与引擎
33
+ * 的那一侧)用一格锁步钉住 —— 任一侧加词/改序,那一格先红。本包不 import 引擎(纯度不变量)。
34
+ */
35
+ import { z } from "zod";
36
+ /** 显式三值,**顺序即收紧秩**(accept < hold < refuse):层次解析只许沿此序收紧,永不放宽。 */
37
+ export declare const CROSS_SESSION_INBOUND_VALUES: readonly ["accept", "hold", "refuse"];
38
+ export type CrossSessionInboundValue = (typeof CROSS_SESSION_INBOUND_VALUES)[number];
39
+ /** 设置 UI 的「不设」拼写(见头注)——两键共用同一个字面量。 */
40
+ export declare const CROSS_SESSION_INBOUND_UNSET: "default";
41
+ /** 文件层受理面:三个显式值 + `"default"`(未设)。 */
42
+ export declare const CrossSessionInboundSetting: z.ZodEnum<["accept", "hold", "refuse", "default"]>;
43
+ export type CrossSessionInboundSettingValue = z.infer<typeof CrossSessionInboundSetting>;
44
+ /** 四值闭集(CC 逐字)。`"never"` = 不设期限。 */
45
+ export declare const DIALOG_EXPIRY_VALUES: readonly ["60s", "5m", "10m", "never"];
46
+ export type DialogExpiryValue = (typeof DIALOG_EXPIRY_VALUES)[number];
47
+ /** 未设时生效的值(CC 缺省)。 */
48
+ export declare const DIALOG_EXPIRY_DEFAULT: DialogExpiryValue;
49
+ /** 文件层受理面:四个值 + `"default"`(未设 ⇒ 落 {@link DIALOG_EXPIRY_DEFAULT})。 */
50
+ export declare const DialogExpirySetting: z.ZodEnum<["60s", "5m", "10m", "never", "default"]>;
51
+ export type DialogExpirySettingValue = z.infer<typeof DialogExpirySetting>;
52
+ /**
53
+ * 整份 `settings.json` 里**本片段负责的两键**。刻意 `passthrough`:本片段不是整份 settings 的闭集门
54
+ * (那道门归各宿主的受理面——宿主消费哪些键、拒哪些键,是宿主的事)。它只保证:**这两个键若在场,
55
+ * 值必合法**。
56
+ */
57
+ export declare const CrossSessionSettings: z.ZodObject<{
58
+ crossSessionInbound: z.ZodOptional<z.ZodEnum<["accept", "hold", "refuse", "default"]>>;
59
+ dialogExpiry: z.ZodOptional<z.ZodEnum<["60s", "5m", "10m", "never", "default"]>>;
60
+ }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
61
+ crossSessionInbound: z.ZodOptional<z.ZodEnum<["accept", "hold", "refuse", "default"]>>;
62
+ dialogExpiry: z.ZodOptional<z.ZodEnum<["60s", "5m", "10m", "never", "default"]>>;
63
+ }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
64
+ crossSessionInbound: z.ZodOptional<z.ZodEnum<["accept", "hold", "refuse", "default"]>>;
65
+ dialogExpiry: z.ZodOptional<z.ZodEnum<["60s", "5m", "10m", "never", "default"]>>;
66
+ }, z.ZodTypeAny, "passthrough">>;
67
+ export type CrossSessionSettings = z.infer<typeof CrossSessionSettings>;
68
+ /** 一层读数:`ok:false` = 词表外(调用方响亮拒);`ok:true` 无 `value` = 未设;有 `value` = 显式值。 */
69
+ export type SettingLayerRead<T> = {
70
+ ok: true;
71
+ value?: T;
72
+ } | {
73
+ ok: false;
74
+ };
75
+ /** 读一层的 `crossSessionInbound` 原始值。 */
76
+ export declare function readCrossSessionInbound(raw: unknown): SettingLayerRead<CrossSessionInboundValue>;
77
+ /** 读一层的 `dialogExpiry` 原始值(未设 ⇒ 由消费方落 {@link DIALOG_EXPIRY_DEFAULT},本函数不替它落)。 */
78
+ export declare function readDialogExpiry(raw: unknown): SettingLayerRead<DialogExpiryValue>;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * `settings.json` 的**跨会话两键** —— CC-parity 契约(`crossSessionInbound` / `dialogExpiry`)。
3
+ *
4
+ * 与 `hooks.ts` 同姿势:本包只出**格式契约**(纯 zod、零运行时依赖、浏览器安全),不做任何解释/判定。
5
+ * 判定(层次收紧、mode-parity、hold 因由)归引擎;写行、渲染、下发归各宿主。
6
+ *
7
+ * ## 两键的 CC 锚(逐字核过的语料读数,非转述)
8
+ *
9
+ * - `crossSessionInbound: enum(["accept","hold","refuse"])` —— describe:「Inbound cross-session peer
10
+ * messages (SendMessage from your other sessions)」:`accept` 投递、`hold` 停在收件方的待审队列
11
+ * (模型看不见、不能据此行动)、`refuse` 让本会话整体退出这条车道。**显式值恒赢**;未设 =
12
+ * mode-parity(发送方与收件方的权限档位同类才自动投递,不同类停审)。
13
+ * - `dialogExpiry: enum(["60s","5m","10m","never"])`,缺省 `"5m"` —— describe:「Max time a
14
+ * permission/user dialog forwarded to a remote client stays parked awaiting an answer, and how long a
15
+ * HELD cross-session message awaits approval」。**注意它有两个消费面**:远端停靠的审批对话框,与
16
+ * 停审的跨会话消息;本模块只声明键形,哪个宿主消费哪一面由宿主自陈。
17
+ *
18
+ * ## `"default"` 这第四个拼写
19
+ *
20
+ * 设置 UI 的选择器需要一个「不设」的可写值,CC 用字面量 `"default"`。它**不是第四种策略**:读取器
21
+ * (`readCrossSessionInbound` / `readDialogExpiry`)把它与 `null`/缺席一同折成 `undefined`。所以
22
+ * `CrossSessionInboundSetting` 的受理面是四拼写,而**值域闭集**(`CROSS_SESSION_INBOUND_VALUES`)只有三个。
23
+ *
24
+ * ## 坏值方向 = 响亮,不静默
25
+ *
26
+ * 词表外的值一律 `ok:false`,由调用方拒(HTTP 门 400 / 文件层告警)。CC 在文件层用 `.catch(undefined)`
27
+ * 静默吞成未设;本栈的坏值响亮律与之有意分岔——一个 `crossSessionInbound: "Hold"`(大小写拼错)静默
28
+ * 读成「未设」会把用户以为已经关掉的入站车道悄悄打开,那正是保护型配置最不能有的沉默。
29
+ *
30
+ * ## 与引擎词表的锁步
31
+ *
32
+ * 引擎另有一份同词表(判定纯函数的输入域)。两份**必须逐字相等**;等值由消费方(同时装了本包与引擎
33
+ * 的那一侧)用一格锁步钉住 —— 任一侧加词/改序,那一格先红。本包不 import 引擎(纯度不变量)。
34
+ */
35
+ import { z } from "zod";
36
+ // ── crossSessionInbound ────────────────────────────────────────────────────────────────────────────
37
+ /** 显式三值,**顺序即收紧秩**(accept < hold < refuse):层次解析只许沿此序收紧,永不放宽。 */
38
+ export const CROSS_SESSION_INBOUND_VALUES = ["accept", "hold", "refuse"];
39
+ /** 设置 UI 的「不设」拼写(见头注)——两键共用同一个字面量。 */
40
+ export const CROSS_SESSION_INBOUND_UNSET = "default";
41
+ /** 文件层受理面:三个显式值 + `"default"`(未设)。 */
42
+ export const CrossSessionInboundSetting = z.enum([...CROSS_SESSION_INBOUND_VALUES, CROSS_SESSION_INBOUND_UNSET]);
43
+ // ── dialogExpiry ───────────────────────────────────────────────────────────────────────────────────
44
+ /** 四值闭集(CC 逐字)。`"never"` = 不设期限。 */
45
+ export const DIALOG_EXPIRY_VALUES = ["60s", "5m", "10m", "never"];
46
+ /** 未设时生效的值(CC 缺省)。 */
47
+ export const DIALOG_EXPIRY_DEFAULT = "5m";
48
+ /** 文件层受理面:四个值 + `"default"`(未设 ⇒ 落 {@link DIALOG_EXPIRY_DEFAULT})。 */
49
+ export const DialogExpirySetting = z.enum([...DIALOG_EXPIRY_VALUES, CROSS_SESSION_INBOUND_UNSET]);
50
+ // ── settings 片段 ──────────────────────────────────────────────────────────────────────────────────
51
+ /**
52
+ * 整份 `settings.json` 里**本片段负责的两键**。刻意 `passthrough`:本片段不是整份 settings 的闭集门
53
+ * (那道门归各宿主的受理面——宿主消费哪些键、拒哪些键,是宿主的事)。它只保证:**这两个键若在场,
54
+ * 值必合法**。
55
+ */
56
+ export const CrossSessionSettings = z
57
+ .object({
58
+ crossSessionInbound: CrossSessionInboundSetting.optional(),
59
+ dialogExpiry: DialogExpirySetting.optional(),
60
+ })
61
+ .passthrough();
62
+ function readEnumLayer(raw, values) {
63
+ if (raw === undefined || raw === null || raw === CROSS_SESSION_INBOUND_UNSET)
64
+ return { ok: true };
65
+ return typeof raw === "string" && values.includes(raw) ? { ok: true, value: raw } : { ok: false };
66
+ }
67
+ /** 读一层的 `crossSessionInbound` 原始值。 */
68
+ export function readCrossSessionInbound(raw) {
69
+ return readEnumLayer(raw, CROSS_SESSION_INBOUND_VALUES);
70
+ }
71
+ /** 读一层的 `dialogExpiry` 原始值(未设 ⇒ 由消费方落 {@link DIALOG_EXPIRY_DEFAULT},本函数不替它落)。 */
72
+ export function readDialogExpiry(raw) {
73
+ return readEnumLayer(raw, DIALOG_EXPIRY_VALUES);
74
+ }
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export * from "./cross-domain.js";
14
14
  export * from "./remote-exec.js";
15
15
  export * from "./safety-merge-spec.js";
16
16
  export * from "./scheduler-store.js";
17
+ export * from "./cross-session.js";
17
18
  export * from "./hooks.js";
18
19
  export * from "./fleet.js";
19
20
  export * from "./migrate.js";
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ export * from "./cross-domain.js"; // pure half only (no fleet placement)
14
14
  export * from "./remote-exec.js";
15
15
  export * from "./safety-merge-spec.js"; // #14 per-category safety merge-spec (settings resolver authority)
16
16
  export * from "./scheduler-store.js"; // R7 self-wake scheduler store CONTRACT (pure: type + parse/serialize + SchedulerStore interface; fs binding in ./node)
17
+ export * from "./cross-session.js"; // CC-parity `settings.json` cross-session keys (crossSessionInbound / dialogExpiry) — pure/browser-safe
17
18
  export * from "./hooks.js"; // CC-parity hooks contract (31-event set + owner map + per-event input/output shapes + HooksConfig) & stop attribution (stoppedBy) — pure/browser-safe
18
19
  export * from "./fleet.js"; // scheduling/fleet contract (drain protocol · instance discovery · dispatch policy · quota lease · usage report · quota reset event) — CONTROL-PLANE-SCHEDULING-DESIGN, pure/browser-safe
19
20
  export * from "./migrate.js"; // 0.5→0.6 migration tools (hosts grandfathering + worker.model→catalog migration PLAN — report/draft only, E1; EXPERT-REDESIGN §2-2/§3), pure/browser-safe
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/settings-schema",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Sema settings schema — pure config contract (zod domains + effective-config + roster resolution + ref-integrity + remoteExec) shared by sema-registry, sema-server, and the TOC desktop/CLI. One source of truth. (Renamed at 1.0.0 from @sema-agent/registry-core — name and docs only, no behavior change; note the range moves 0.19.0 -> 1.0.0, so a dependency must become ^1.0.0. The old names @sema-agent/registry-core <=0.19.0 and @sema-ai/registry-core remain published, frozen, for migration.)",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",