@sema-agent/client-core 0.68.1 → 0.69.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.
@@ -0,0 +1,161 @@
1
+ /**
2
+ * coreValuePorts.ts — 引擎(`@sema-agent/core`)**值级**面的端口注入口(CC-07,0.69.0;cli L-249 到期桶 core-value 清零)。
3
+ *
4
+ * ## 这一格要解决的问题
5
+ * 壳(cli)有五组逻辑必须与引擎**跑同一个函数体**才不分歧:自主循环提示词的组装、权限规则「放宽」判据、
6
+ * 工具策略的组合、工具名的协议/退役/语法判别与规则编译校验、讨论工作流的名字。它们此前在壳里**值级直连**
7
+ * `@sema-agent/core`(cli sdk-isolation 门的 `core-value` 桶,retireBy 已过期),而 web / desktop 座位层
8
+ * 结构上装不了引擎 —— 同一句判据在三端里只有一端有。
9
+ *
10
+ * ## 🔴 为什么是端口注入而不是 `export … from '@sema-agent/core'`
11
+ * 与 `hitl/editedRuleTextPrecheck.ts` 逐字同一条硬约束:本包的可移植门是外部包**等值集**(`{diff, @sema-agent/sdk}`)
12
+ * + `--platform=browser` 真打包,而 core 的 barrel 值级拉 `node:crypto` / `node:fs` / `node:path` ⇒ 一条值级
13
+ * re-export 会把整台引擎焊进每一个装本包的端。⇒ **包声明端口 + 诚实缺席读口;Node 宿主在装配根把 core 的真实现
14
+ * 原样装进来(不许包一层判断);浏览器 lane 留缺席走各自的往返/降级形**。
15
+ *
16
+ * ## 形(逐组,签名与 core 7.18.0 `dist/**.d.ts` 逐字同,只把 core 私有型换成**结构等价的镜像**)
17
+ * · `resolveAutonomousLoopPrompt(prompt, opts?)` → `string | null`(core `tools/loop-tick.d.ts`);
18
+ * · `loosenReasons(prior, next)` → `string[]`(core `core/session-permission-rules`;两参用 sdk 的
19
+ * `SessionPermissionRules`,与 core 同名同形);
20
+ * · `combinePolicies(...policies)` / `createAllowDenyPolicy(opts)` → 工具策略(core `core/tool-policy.d.ts`;
21
+ * 策略对本包是**不透明句柄** {@link CoreToolPolicyHandle}:本包不调 `check`,只把它交回引擎/请求装配);
22
+ * · `MCP_NAMESPACE` / `RETIRED_TOOL_NAMES` / `protocolOf(name)` / `ruleToolGrammarOf(toolName)` /
23
+ * `validatePermissionRules(rules, opts?)`(core `core/protocol-table.d.ts` / `core/permission-rules.d.ts`);
24
+ * · `DISCUSSION_WORKFLOW_NAME`(core 常量 `"discussion"`)。
25
+ *
26
+ * ## 🔴 读口纪律(与 `precheckEditedRuleText` 同律)
27
+ * · 没装 ⇒ 返 `undefined`(**诚实缺席**),绝不返一个编出来的值(空数组 / 默认策略 / `"discussion"` 字面量都算编);
28
+ * · 装的必须是 core 的**同一只**实现,原样装 —— 包一层就把「同一函数体」这条唯一的抗漂移保证拆掉;
29
+ * · core 对调用方 bug 的响亮抛(如 `createAllowDenyPolicy` 的 `config.invalid_tool_name_set`)**原样上抛**,本层不吞;
30
+ * · 装口返回**还原函数**(还原到装之前那一只,不是无脑清空 —— 嵌套装载时无脑清空会抹掉外层)。
31
+ *
32
+ * ## 缺席语义(三端)
33
+ * · cli / desktop 主进程(Node,引擎在场):装配根一处 `installCoreValuePorts({...core 那十件...})`,缺席 = 宿主漏装 = bug;
34
+ * · web / desktop 渲染层:结构上装不了 ⇒ 恒缺席;消费点按 `undefined` 走各自的降级形(不判定、交引擎端裁)。
35
+ * 本口的缺席因此**不是**「宿主忘了装」的同义词,别拿它判部署形态(`hasCoreValuePorts()` 只答「装没装」)。
36
+ */
37
+ import type { SessionPermissionRules } from '@sema-agent/sdk';
38
+ /** core `AutonomousLoopPromptOptions` 的逐形镜像(三键全可选)。 */
39
+ export interface AutonomousLoopPromptOptionsView {
40
+ firstTick?: boolean;
41
+ persistentVariant?: boolean;
42
+ pushNotification?: boolean;
43
+ }
44
+ /**
45
+ * core `ToolPolicy` 的**不透明句柄**:本包不调 `check`(那是引擎在 run 里做的事),只把策略原样交回。
46
+ * 结构上只钉「有一只 `check`」—— core 的 `ToolPolicy` / `NamedToolPolicy` 都可赋给它;反向刻意不钉。
47
+ */
48
+ export interface CoreToolPolicyHandle {
49
+ readonly check: (req: never, signal?: AbortSignal) => CorePermissionResultView | Promise<CorePermissionResultView>;
50
+ readonly projection?: object;
51
+ }
52
+ /** core `PermissionResult` 的最小镜像(本包只钉判别位 `action`;其余位在引擎侧)。 */
53
+ export interface CorePermissionResultView {
54
+ readonly action: 'allow' | 'ask' | 'deny' | (string & {});
55
+ }
56
+ /** core `createAllowDenyPolicy` 的入参镜像。 */
57
+ export interface AllowDenyPolicyOptionsView {
58
+ allow?: string[];
59
+ deny?: string[];
60
+ /** 非裸工具名的 entry(规则内容形)怎么办:`throw`(缺省)在构造时拒;`skip` 丢掉并经 `onInvalidNameIssue` 报。 */
61
+ onInvalidName?: 'throw' | 'skip';
62
+ onInvalidNameIssue?: (issue: AllowDenyInvalidNameIssueView) => void;
63
+ }
64
+ /** `onInvalidName: 'skip'` 时逐条报出的那一件。 */
65
+ export interface AllowDenyInvalidNameIssueView {
66
+ entry: string;
67
+ list: 'allow' | 'deny';
68
+ message: string;
69
+ }
70
+ /** core `ProtocolNamespace` 的镜像(`id` 按开集读:core 今天 `mcp` / `a2a` 两词)。 */
71
+ export interface CoreProtocolNamespaceView {
72
+ readonly id: string;
73
+ readonly prefix: string;
74
+ makeName(peer: string, tool: string): string;
75
+ /** 解不出 ⇒ `undefined`(core `ProtocolPeerTool` 对本包不透明,只钉「解出了一个对象」)。 */
76
+ parse(name: string): object | undefined;
77
+ displayGroupKey(name: string): string;
78
+ }
79
+ /** core `PermissionRule` 的逐形镜像。 */
80
+ export interface PermissionRuleView {
81
+ rule: string;
82
+ behavior: 'deny' | 'ask' | 'allow';
83
+ source?: string;
84
+ }
85
+ /**
86
+ * core `PermissionRuleIssue` 的镜像。🔴 `code` 按**开集串**读:core 那张码表(`invalid.*` / `unsupported.*`)属主在引擎,
87
+ * 镜像枚举 = 引擎加员当天把一个合法码判没(#157 词表纪律)。
88
+ */
89
+ export interface PermissionRuleIssueView {
90
+ rule: string;
91
+ code: string;
92
+ message: string;
93
+ }
94
+ /** core `validatePermissionRules` 第二参的镜像(`Pick<PermissionRulePolicyOptions, …>` 四键)。 */
95
+ export interface ValidatePermissionRulesOptionsView {
96
+ primaryFieldGeneric?: 'reject' | 'allow-lexical';
97
+ /** core `Partial<PermissionRuleCaps>`;本包不钉各帽名(属主在引擎),按对象透传。 */
98
+ caps?: object;
99
+ ruleFaces?: (toolName: string) => object | undefined;
100
+ /** core `PathRuleBases`:`cwd` / `root` / `home` 三基各可选(按开集键读)。 */
101
+ pathBases?: {
102
+ readonly [base: string]: string | undefined;
103
+ };
104
+ }
105
+ /** 十件端口的**形**(每一件的唯一合法实参 = core 导出的同名那一只,原样装)。 */
106
+ export interface CoreValuePorts {
107
+ resolveAutonomousLoopPrompt: (prompt: string, opts?: AutonomousLoopPromptOptionsView) => string | null;
108
+ loosenReasons: (prior: SessionPermissionRules, next: SessionPermissionRules) => string[];
109
+ combinePolicies: (...policies: CoreToolPolicyHandle[]) => CoreToolPolicyHandle;
110
+ createAllowDenyPolicy: (opts: AllowDenyPolicyOptionsView) => CoreToolPolicyHandle;
111
+ MCP_NAMESPACE: CoreProtocolNamespaceView;
112
+ RETIRED_TOOL_NAMES: ReadonlyMap<string, string>;
113
+ protocolOf: (name: string) => CoreProtocolNamespaceView | undefined;
114
+ ruleToolGrammarOf: (toolName: string) => 'command' | 'path' | undefined;
115
+ validatePermissionRules: (rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView) => PermissionRuleIssueView[];
116
+ DISCUSSION_WORKFLOW_NAME: string;
117
+ }
118
+ /** 端口名的闭集(读口逐名一一对应;`hasCoreValuePort(name)` 的入参域)。 */
119
+ export declare const CORE_VALUE_PORT_NAMES: readonly ["resolveAutonomousLoopPrompt", "loosenReasons", "combinePolicies", "createAllowDenyPolicy", "MCP_NAMESPACE", "RETIRED_TOOL_NAMES", "protocolOf", "ruleToolGrammarOf", "validatePermissionRules", "DISCUSSION_WORKFLOW_NAME"];
120
+ export type CoreValuePortName = (typeof CORE_VALUE_PORT_NAMES)[number];
121
+ /**
122
+ * 装/卸端口(可以只装一部分:没装的那几件读口照旧缺席)。返回**还原函数**(还原到装之前那一袋)。
123
+ *
124
+ * Node 宿主的装法(**逐字**,别加工):
125
+ * ```ts
126
+ * import { resolveAutonomousLoopPrompt, loosenReasons, combinePolicies, createAllowDenyPolicy,
127
+ * MCP_NAMESPACE, RETIRED_TOOL_NAMES, protocolOf, ruleToolGrammarOf, validatePermissionRules,
128
+ * DISCUSSION_WORKFLOW_NAME } from '@sema-agent/core'
129
+ * installCoreValuePorts({ resolveAutonomousLoopPrompt, loosenReasons, combinePolicies, createAllowDenyPolicy,
130
+ * MCP_NAMESPACE, RETIRED_TOOL_NAMES, protocolOf, ruleToolGrammarOf, validatePermissionRules,
131
+ * DISCUSSION_WORKFLOW_NAME })
132
+ * ```
133
+ * 🔴 传 `null` = 卸;传一袋 = **整袋替换**(不与上一袋合并 —— 合并会让「卸掉一件」这件事无法表达)。
134
+ */
135
+ export declare function installCoreValuePorts(ports: Partial<CoreValuePorts> | null): () => void;
136
+ /** 装没装(整袋)。`false` 时所有读口缺席。 */
137
+ export declare function hasCoreValuePorts(): boolean;
138
+ /** 某一件装没装(存在性读口哨兵:`boolean` 形)。 */
139
+ export declare function hasCoreValuePort(name: CoreValuePortName): boolean;
140
+ /** 十件里还没装的那几件的名字(宿主自检 / doctor 行用;全装 ⇒ `[]`,整袋没装 ⇒ 十件全列)。 */
141
+ export declare function coreValuePortMisses(): readonly CoreValuePortName[];
142
+ /** 自主循环提示词组装(core `resolveAutonomousLoopPrompt`)。没装 ⇒ `undefined`;装了但 core 判「不组」⇒ `null`(两态不折)。 */
143
+ export declare function resolveAutonomousLoopPrompt(prompt: string, opts?: AutonomousLoopPromptOptionsView): string | null | undefined;
144
+ /** 「`next` 相对 `prior` 放宽了哪些」(core `loosenReasons`;空数组 = 没放宽)。没装 ⇒ `undefined`(不是 `[]`)。 */
145
+ export declare function loosenReasons(prior: SessionPermissionRules, next: SessionPermissionRules): string[] | undefined;
146
+ /** 组合工具策略(core `combinePolicies`,deny-wins)。没装 ⇒ `undefined`。 */
147
+ export declare function combinePolicies(...policies: CoreToolPolicyHandle[]): CoreToolPolicyHandle | undefined;
148
+ /** 白/黑名单策略(core `createAllowDenyPolicy`;`onInvalidName` 缺省 `throw`,core 响亮抛原样上抛)。没装 ⇒ `undefined`。 */
149
+ export declare function createAllowDenyPolicy(opts: AllowDenyPolicyOptionsView): CoreToolPolicyHandle | undefined;
150
+ /** MCP 协议命名空间(core `MCP_NAMESPACE`;`prefix` 今天 `mcp__`)。没装 ⇒ `undefined`。 */
151
+ export declare function mcpNamespace(): CoreProtocolNamespaceView | undefined;
152
+ /** 退役工具名 → 继任名(core `RETIRED_TOOL_NAMES`)。没装 ⇒ `undefined`(不是空 Map)。 */
153
+ export declare function retiredToolNames(): ReadonlyMap<string, string> | undefined;
154
+ /** 名字属于哪个协议命名空间(core `protocolOf`)。没装 ⇒ `undefined`;装了但不属任何协议 ⇒ 也是 `undefined`(两态在这一格上**不可分**,要分先问 `hasCoreValuePort('protocolOf')`)。 */
155
+ export declare function protocolOf(name: string): CoreProtocolNamespaceView | undefined;
156
+ /** 规则内容语法族(core `ruleToolGrammarOf`;`command` / `path`)。没装 ⇒ `undefined`(与「该工具无语法族」同形,要分先问 `hasCoreValuePort`)。 */
157
+ export declare function ruleToolGrammarOf(toolName: string): 'command' | 'path' | undefined;
158
+ /** 规则编译校验(core `validatePermissionRules`;空数组 = 全部编译得了)。没装 ⇒ `undefined`(不是 `[]` —— 那会把「没校」说成「全过」)。 */
159
+ export declare function validatePermissionRules(rules: PermissionRuleView[], opts?: ValidatePermissionRulesOptionsView): PermissionRuleIssueView[] | undefined;
160
+ /** 讨论工作流的名字(core `DISCUSSION_WORKFLOW_NAME`,今天 `"discussion"`)。没装 ⇒ `undefined`(不铸字面量)。 */
161
+ export declare function discussionWorkflowName(): string | undefined;
@@ -0,0 +1,98 @@
1
+ /** 端口名的闭集(读口逐名一一对应;`hasCoreValuePort(name)` 的入参域)。 */
2
+ export const CORE_VALUE_PORT_NAMES = Object.freeze([
3
+ 'resolveAutonomousLoopPrompt',
4
+ 'loosenReasons',
5
+ 'combinePolicies',
6
+ 'createAllowDenyPolicy',
7
+ 'MCP_NAMESPACE',
8
+ 'RETIRED_TOOL_NAMES',
9
+ 'protocolOf',
10
+ 'ruleToolGrammarOf',
11
+ 'validatePermissionRules',
12
+ 'DISCUSSION_WORKFLOW_NAME',
13
+ ]);
14
+ let installed = null;
15
+ /**
16
+ * 装/卸端口(可以只装一部分:没装的那几件读口照旧缺席)。返回**还原函数**(还原到装之前那一袋)。
17
+ *
18
+ * Node 宿主的装法(**逐字**,别加工):
19
+ * ```ts
20
+ * import { resolveAutonomousLoopPrompt, loosenReasons, combinePolicies, createAllowDenyPolicy,
21
+ * MCP_NAMESPACE, RETIRED_TOOL_NAMES, protocolOf, ruleToolGrammarOf, validatePermissionRules,
22
+ * DISCUSSION_WORKFLOW_NAME } from '@sema-agent/core'
23
+ * installCoreValuePorts({ resolveAutonomousLoopPrompt, loosenReasons, combinePolicies, createAllowDenyPolicy,
24
+ * MCP_NAMESPACE, RETIRED_TOOL_NAMES, protocolOf, ruleToolGrammarOf, validatePermissionRules,
25
+ * DISCUSSION_WORKFLOW_NAME })
26
+ * ```
27
+ * 🔴 传 `null` = 卸;传一袋 = **整袋替换**(不与上一袋合并 —— 合并会让「卸掉一件」这件事无法表达)。
28
+ */
29
+ export function installCoreValuePorts(ports) {
30
+ const prev = installed;
31
+ installed = ports === null ? null : { ...ports };
32
+ return () => {
33
+ installed = prev;
34
+ };
35
+ }
36
+ /** 装没装(整袋)。`false` 时所有读口缺席。 */
37
+ export function hasCoreValuePorts() {
38
+ return installed !== null;
39
+ }
40
+ /** 某一件装没装(存在性读口哨兵:`boolean` 形)。 */
41
+ export function hasCoreValuePort(name) {
42
+ return installed !== null && installed[name] !== undefined;
43
+ }
44
+ /** 十件里还没装的那几件的名字(宿主自检 / doctor 行用;全装 ⇒ `[]`,整袋没装 ⇒ 十件全列)。 */
45
+ export function coreValuePortMisses() {
46
+ return CORE_VALUE_PORT_NAMES.filter((n) => installed === null || installed[n] === undefined);
47
+ }
48
+ function port(name) {
49
+ return installed === null ? undefined : installed[name];
50
+ }
51
+ // ── 读口(逐件;没装 ⇒ undefined,绝不编值;core 的响亮抛原样上抛)──────────────────────────────
52
+ /** 自主循环提示词组装(core `resolveAutonomousLoopPrompt`)。没装 ⇒ `undefined`;装了但 core 判「不组」⇒ `null`(两态不折)。 */
53
+ export function resolveAutonomousLoopPrompt(prompt, opts) {
54
+ const fn = port('resolveAutonomousLoopPrompt');
55
+ return fn === undefined ? undefined : fn(prompt, opts);
56
+ }
57
+ /** 「`next` 相对 `prior` 放宽了哪些」(core `loosenReasons`;空数组 = 没放宽)。没装 ⇒ `undefined`(不是 `[]`)。 */
58
+ export function loosenReasons(prior, next) {
59
+ const fn = port('loosenReasons');
60
+ return fn === undefined ? undefined : fn(prior, next);
61
+ }
62
+ /** 组合工具策略(core `combinePolicies`,deny-wins)。没装 ⇒ `undefined`。 */
63
+ export function combinePolicies(...policies) {
64
+ const fn = port('combinePolicies');
65
+ return fn === undefined ? undefined : fn(...policies);
66
+ }
67
+ /** 白/黑名单策略(core `createAllowDenyPolicy`;`onInvalidName` 缺省 `throw`,core 响亮抛原样上抛)。没装 ⇒ `undefined`。 */
68
+ export function createAllowDenyPolicy(opts) {
69
+ const fn = port('createAllowDenyPolicy');
70
+ return fn === undefined ? undefined : fn(opts);
71
+ }
72
+ /** MCP 协议命名空间(core `MCP_NAMESPACE`;`prefix` 今天 `mcp__`)。没装 ⇒ `undefined`。 */
73
+ export function mcpNamespace() {
74
+ return port('MCP_NAMESPACE');
75
+ }
76
+ /** 退役工具名 → 继任名(core `RETIRED_TOOL_NAMES`)。没装 ⇒ `undefined`(不是空 Map)。 */
77
+ export function retiredToolNames() {
78
+ return port('RETIRED_TOOL_NAMES');
79
+ }
80
+ /** 名字属于哪个协议命名空间(core `protocolOf`)。没装 ⇒ `undefined`;装了但不属任何协议 ⇒ 也是 `undefined`(两态在这一格上**不可分**,要分先问 `hasCoreValuePort('protocolOf')`)。 */
81
+ export function protocolOf(name) {
82
+ const fn = port('protocolOf');
83
+ return fn === undefined ? undefined : fn(name);
84
+ }
85
+ /** 规则内容语法族(core `ruleToolGrammarOf`;`command` / `path`)。没装 ⇒ `undefined`(与「该工具无语法族」同形,要分先问 `hasCoreValuePort`)。 */
86
+ export function ruleToolGrammarOf(toolName) {
87
+ const fn = port('ruleToolGrammarOf');
88
+ return fn === undefined ? undefined : fn(toolName);
89
+ }
90
+ /** 规则编译校验(core `validatePermissionRules`;空数组 = 全部编译得了)。没装 ⇒ `undefined`(不是 `[]` —— 那会把「没校」说成「全过」)。 */
91
+ export function validatePermissionRules(rules, opts) {
92
+ const fn = port('validatePermissionRules');
93
+ return fn === undefined ? undefined : fn(rules, opts);
94
+ }
95
+ /** 讨论工作流的名字(core `DISCUSSION_WORKFLOW_NAME`,今天 `"discussion"`)。没装 ⇒ `undefined`(不铸字面量)。 */
96
+ export function discussionWorkflowName() {
97
+ return port('DISCUSSION_WORKFLOW_NAME');
98
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * src/engineHttpTools.ts — SDK **纯工具面**的转口口(L-61 / L-318 件④,0.68.2)。
3
+ *
4
+ * ── 病形(为什么要有这一只)──────────────────────────────────────────────────────────────────
5
+ * 端对引擎 wire 的一切消费必须经本包(sdk-isolation 门公约),而 SDK 里有两件**纯工具**至今没有
6
+ * 经本包的路:
7
+ * · `engineUrl(baseUrl, path)` —— 「URL 去尾斜杠 + 拼路径」的语义单真源(端自己拼字符串就会
8
+ * 在 `//v1/...` 这类形上各错各的);
9
+ * · `probeHealth(baseUrl, timeoutMs?, fetchImpl?)` —— 引擎存活探针的单真源。
10
+ * 于是 cli 有 5 个产品文件**直连** `@sema-agent/sdk` 只为取这两件(sdk-isolation 存量册
11
+ * `sdk-value` 桶里那几条,`_retirement` 段逐字:「多数只取 engineUrl builder 一件」),web /
12
+ * desktop 接同一条腿时会再各直连一次。
13
+ *
14
+ * ── 为什么这一只可以做**值级**转口(与 core 判官那一只的分界)──────────────────────────────
15
+ * 可移植门(`run-client-core-portability-test.mjs`)对包总入口做两件事:外部包**等值集**
16
+ * `EXPECTED_PACKAGES_INDEX` + esbuild `--platform=browser` 真打一次包。
17
+ * · `@sema-agent/sdk` **本来就在**那个等值集里(本包已有 5 处值级 SDK import)⇒ 加这一只
18
+ * 不改集合,只让闭包文件数 +1(棘轮按既有姿势逐件记账,见该门 `MAX_CLOSURE_FILES_INDEX` 头注);
19
+ * · SDK 的 `dist/health.js` **零 import**(实测:整文件一条 `import`/`require` 都没有,传输走
20
+ * 全局 `fetch` = web 标准,不是 Node 内建)⇒ 浏览器打包面零风险。
21
+ * 🔴 对照:`@sema-agent/core` 的同类件**不能**这么做(`hitl/editedRuleTextPrecheck.ts` 头注逐字:
22
+ * core 的 barrel 值级拉 `node:crypto`/`node:fs`/`node:path`,一条这样的边会把整台引擎焊进
23
+ * web/desktop 的产物)—— 那一族走**端口注入**形。两者的分界线是「这个外部包在不在等值集里」,
24
+ * 不是「它是不是纯函数」。
25
+ *
26
+ * 🔴 **原样转口,一个字节都不加工**:本模块不包装、不改签名、不补默认值 —— 包一层就是把「同一
27
+ * 函数体」这条唯一的抗漂移保证亲手拆掉(与 `editedRuleTextPrecheck` 的装口纪律同一条)。
28
+ * 要在这两件之上加判定的那天,新开一个具名模块,别往转口口里塞。
29
+ */
30
+ export { engineUrl, probeHealth } from '@sema-agent/sdk';
31
+ export type { ProbeHealthResult } from '@sema-agent/sdk';
@@ -0,0 +1,30 @@
1
+ /**
2
+ * src/engineHttpTools.ts — SDK **纯工具面**的转口口(L-61 / L-318 件④,0.68.2)。
3
+ *
4
+ * ── 病形(为什么要有这一只)──────────────────────────────────────────────────────────────────
5
+ * 端对引擎 wire 的一切消费必须经本包(sdk-isolation 门公约),而 SDK 里有两件**纯工具**至今没有
6
+ * 经本包的路:
7
+ * · `engineUrl(baseUrl, path)` —— 「URL 去尾斜杠 + 拼路径」的语义单真源(端自己拼字符串就会
8
+ * 在 `//v1/...` 这类形上各错各的);
9
+ * · `probeHealth(baseUrl, timeoutMs?, fetchImpl?)` —— 引擎存活探针的单真源。
10
+ * 于是 cli 有 5 个产品文件**直连** `@sema-agent/sdk` 只为取这两件(sdk-isolation 存量册
11
+ * `sdk-value` 桶里那几条,`_retirement` 段逐字:「多数只取 engineUrl builder 一件」),web /
12
+ * desktop 接同一条腿时会再各直连一次。
13
+ *
14
+ * ── 为什么这一只可以做**值级**转口(与 core 判官那一只的分界)──────────────────────────────
15
+ * 可移植门(`run-client-core-portability-test.mjs`)对包总入口做两件事:外部包**等值集**
16
+ * `EXPECTED_PACKAGES_INDEX` + esbuild `--platform=browser` 真打一次包。
17
+ * · `@sema-agent/sdk` **本来就在**那个等值集里(本包已有 5 处值级 SDK import)⇒ 加这一只
18
+ * 不改集合,只让闭包文件数 +1(棘轮按既有姿势逐件记账,见该门 `MAX_CLOSURE_FILES_INDEX` 头注);
19
+ * · SDK 的 `dist/health.js` **零 import**(实测:整文件一条 `import`/`require` 都没有,传输走
20
+ * 全局 `fetch` = web 标准,不是 Node 内建)⇒ 浏览器打包面零风险。
21
+ * 🔴 对照:`@sema-agent/core` 的同类件**不能**这么做(`hitl/editedRuleTextPrecheck.ts` 头注逐字:
22
+ * core 的 barrel 值级拉 `node:crypto`/`node:fs`/`node:path`,一条这样的边会把整台引擎焊进
23
+ * web/desktop 的产物)—— 那一族走**端口注入**形。两者的分界线是「这个外部包在不在等值集里」,
24
+ * 不是「它是不是纯函数」。
25
+ *
26
+ * 🔴 **原样转口,一个字节都不加工**:本模块不包装、不改签名、不补默认值 —— 包一层就是把「同一
27
+ * 函数体」这条唯一的抗漂移保证亲手拆掉(与 `editedRuleTextPrecheck` 的装口纪律同一条)。
28
+ * 要在这两件之上加判定的那天,新开一个具名模块,别往转口口里塞。
29
+ */
30
+ export { engineUrl, probeHealth } from '@sema-agent/sdk';
@@ -54,7 +54,7 @@
54
54
  /** 一条通告是**说给谁听**的。两值,刻意不设第三值(见模块顶注)。 */
55
55
  export type NoticeAudience = 'user' | 'operator';
56
56
  /**
57
- * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**7.17.x = 五十二码**;顺序同源)。
57
+ * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**7.18.0 = 五十六码**(7.17.x 五十二 + #748 两枚 writeFace + #742 两枚 classifier,均 operator);顺序同源)。
58
58
  * 🔴 这是一份**抄件**,不是本包的意见 —— 改它必须同 commit 附 core 坐标,且门会先红。
59
59
  * ⚠️ `EngineNotice.code` 的型面在上游**故意留 `string`**(宿主把自己的通告转发进同一个水槽是
60
60
  * 被支持的形),所以本表是**判据**不是型 —— 别拿它去窄化那个字段。
@@ -52,7 +52,7 @@
52
52
  * 出现在本包源码或产物里 => 当场红。
53
53
  */
54
54
  /**
55
- * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**7.17.x = 五十二码**;顺序同源)。
55
+ * 引擎**成文码册**的逐字镜像(core `ENGINE_NOTICE_CODES`,**7.18.0 = 五十六码**(7.17.x 五十二 + #748 两枚 writeFace + #742 两枚 classifier,均 operator);顺序同源)。
56
56
  * 🔴 这是一份**抄件**,不是本包的意见 —— 改它必须同 commit 附 core 坐标,且门会先红。
57
57
  * ⚠️ `EngineNotice.code` 的型面在上游**故意留 `string`**(宿主把自己的通告转发进同一个水槽是
58
58
  * 被支持的形),所以本表是**判据**不是型 —— 别拿它去窄化那个字段。
@@ -67,6 +67,9 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
67
67
  'config.models_swapped',
68
68
  'config.read_face_swapped',
69
69
  'config.read_face_deployment_clamped',
70
+ // core 7.18.0 #748(0.68.3 提货批 CC-05):writeFace 席的两枚运维通告;位置 = governance-codes.d.ts 元组 @6/@7 真字节。
71
+ 'config.write_face_swapped',
72
+ 'config.write_face_deployment_clamped',
70
73
  'config.tool_model_gate_removed',
71
74
  'config.tool_model_gate_unknown_class',
72
75
  'config.tool_model_gate_env_invalid',
@@ -83,6 +86,8 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
83
86
  'peer.held_settled',
84
87
  'peer.idle_subscription',
85
88
  'classifier.denial_limit',
89
+ // core 7.18.0 #742(CC-05):分类器席坏配置 ⇒ 主模型代判的一条运维通告;元组 @22。
90
+ 'classifier.fallback',
86
91
  'checkpoint.execution_outcome_unrecorded',
87
92
  'delegation.transcript_integrity',
88
93
  'delegation.ask_unresolvable',
@@ -104,6 +109,8 @@ export const ENGINE_NOTICE_CODES = Object.freeze([
104
109
  'memory.consolidation_refused',
105
110
  'memory.consolidation_withheld',
106
111
  'route.fallback_to_primary',
112
+ // core 7.18.0 #742(CC-05):分类器候选梯子无可路由席;元组 @44。
113
+ 'route.classifier_unroutable',
107
114
  'route.base_url_changed_key_unchanged',
108
115
  'task.user_steer_undrained',
109
116
  'task.user_followup_undrained',
@@ -137,6 +144,8 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
137
144
  'config.models_swapped': 'operator',
138
145
  'config.read_face_swapped': 'operator',
139
146
  'config.read_face_deployment_clamped': 'operator',
147
+ 'config.write_face_swapped': 'operator',
148
+ 'config.write_face_deployment_clamped': 'operator',
140
149
  'config.tool_model_gate_removed': 'operator',
141
150
  'config.tool_model_gate_unknown_class': 'operator',
142
151
  'config.tool_model_gate_env_invalid': 'operator',
@@ -152,6 +161,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
152
161
  'peer.held_settled': 'user',
153
162
  'peer.idle_subscription': 'user',
154
163
  'classifier.denial_limit': 'user',
164
+ 'classifier.fallback': 'operator',
155
165
  'checkpoint.execution_outcome_unrecorded': 'operator',
156
166
  'delegation.transcript_integrity': 'operator',
157
167
  'delegation.ask_unresolvable': 'user',
@@ -175,6 +185,7 @@ export const ENGINE_NOTICE_AUDIENCE = Object.freeze({
175
185
  // 理由 = 这条通告没有 sessionId,一次固化跑在任何会话之外)。
176
186
  'memory.consolidation_withheld': 'operator',
177
187
  'route.fallback_to_primary': 'operator',
188
+ 'route.classifier_unroutable': 'operator',
178
189
  'route.base_url_changed_key_unchanged': 'operator',
179
190
  'task.user_steer_undrained': 'user',
180
191
  'task.user_followup_undrained': 'user',
@@ -51,8 +51,12 @@ import type { DeniedBy, Settlement } from '@sema-agent/sdk';
51
51
  */
52
52
  export type GateDispositionView = {
53
53
  kind: 'allowed';
54
+ /** 0.68.3(core 7.18.0 #742):分类器轮记录;缺席 = 未知(见 {@link GateClassifierRoundView})。 */
55
+ classifier?: GateClassifierRoundView;
54
56
  } | {
55
57
  kind: 'denied';
58
+ /** 同上 —— 两臂都可带(core `GateDisposition` 两臂各自声明,本层逐臂逐键挑)。 */
59
+ classifier?: GateClassifierRoundView;
56
60
  deniedBy: DeniedBy | (string & {});
57
61
  /**
58
62
  * 0.67.0(core 7.14.0 #688)—— 这次 deny 是**分类器那一轮没能裁决**的哪一形
@@ -69,6 +73,29 @@ export type GateDispositionView = {
69
73
  */
70
74
  cause?: string;
71
75
  };
76
+ /**
77
+ * 0.68.3(core 7.18.0 #742)—— **分类器那一轮到底问了谁**(`AutoModeClassifierRound` 的防御读视图)。
78
+ *
79
+ * 🔴 **缺席 = 未知 ≠ 席自答**(core 提货单 [7281] 逐字):缺席同时覆盖「这不是分类器轮」「老引擎」
80
+ * 「分类器席没配」三形,端**禁**读成「主模型自己判的」。
81
+ * · `modelRequested` / `modelUsed`:请求的席与实际答题的模型(两者不同 ⇒ 走了候选梯子)。
82
+ * · `fallback`:从哪一级退到哪一级、为什么(`cause` = core `AUTO_MODE_FALLBACK_CAUSES` 三词
83
+ * `error` / `timeout` / `parse_error`,**按开集读**;本包不持该表)。缺席 = 没退。
84
+ * ⚠️ core 顶注:prompt 都没能组出来的那一形也记成 `{from: seat, to: main, cause: "error"}`——
85
+ * 「没问过任何模型」与「问了但坏了」在这一位上**分不开**,端别据它渲「分类器席坏了」。
86
+ * 🔴 两个必在位缺一 ⇒ 整只 `classifier` 缺席(半只视图比没有更坏);`fallback` 三键缺一 ⇒ 只丢 `fallback`。
87
+ */
88
+ export interface GateClassifierRoundView {
89
+ modelRequested: string;
90
+ modelUsed: string;
91
+ fallback?: GateClassifierFallbackView;
92
+ }
93
+ /** 分类器候选梯子的一次退级:从哪一级到哪一级、为什么(`cause` 按开集读)。 */
94
+ export interface GateClassifierFallbackView {
95
+ from: string;
96
+ to: string;
97
+ cause: string;
98
+ }
72
99
  /** 结算方的身份座(形由 {@link SettlementView.kind} 决定,本读器不重铸那张配对表)。 */
73
100
  export interface SettlementWhoView {
74
101
  /** `person` / `engine` / `host` / `none`(开集读)。 */
@@ -96,19 +123,6 @@ export interface GateOutcomeView {
96
123
  /** 谁**问**的(core `ASK_ORIGINS`,7.6.0 起十词;**按开集读**)。与 {@link settlement} 同在同缺。 */
97
124
  origin?: string;
98
125
  }
99
- /**
100
- * `tool_end` 帧(或本包投影出的 `tool_end_result` 内部臂、耐久行的 resolved outcome)→ 门记录。
101
- *
102
- * 入参是**帧**而不是 `frame.gate`,刻意如此:端不必知道这条记录挂在哪个键上(那正是下一次形变
103
- * 会动的东西),也不必自己判「这一帧带不带门」。
104
- *
105
- * 🔴 `disposition` 是**唯一的承重座**:它读不出来 ⇒ 整只 `undefined`。一条只有 `settlement` 而
106
- * 说不出「放行还是拒了」的记录,消费端能从它得到的只有误导。
107
- * 🔴 **不校 I1–I4 那四条不变量**(`settlement` ⇔ `origin` 同在、`ask_resolution` ⇒ 有拒绝类结算……):
108
- * 筛子的属主是引擎,server 对过不了筛的记录**整条不投**。包再校一遍,只会在上游合法演进的当天
109
- * 把一条真记录判没,而本层给不出比「原样交给端」更好的降级动作(`wiring_manifest.autoMode`
110
- * 那一段的反向先例是**同一条帧上的段内自洽**,不是跨系统的不变量,两者刻意不同处置)。
111
- */
112
126
  export declare function gateOutcomeOf(frame: unknown): GateOutcomeView | undefined;
113
127
  /**
114
128
  * 这次调用是**被门 park 了**吗 —— 「没在这里结算」的**第二形**(engine 7.64.0 真机黑盒直证)。
@@ -56,6 +56,26 @@ function readSettlement(v) {
56
56
  * 把一条真记录判没,而本层给不出比「原样交给端」更好的降级动作(`wiring_manifest.autoMode`
57
57
  * 那一段的反向先例是**同一条帧上的段内自洽**,不是跨系统的不变量,两者刻意不同处置)。
58
58
  */
59
+ function readClassifierRound(raw) {
60
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
61
+ return undefined;
62
+ const r = raw;
63
+ const modelRequested = str(r.modelRequested);
64
+ const modelUsed = str(r.modelUsed);
65
+ if (modelRequested === undefined || modelUsed === undefined)
66
+ return undefined;
67
+ const view = { modelRequested, modelUsed };
68
+ const fb = r.fallback;
69
+ if (typeof fb === 'object' && fb !== null && !Array.isArray(fb)) {
70
+ const f = fb;
71
+ const from = str(f.from);
72
+ const to = str(f.to);
73
+ const cause = str(f.cause);
74
+ if (from !== undefined && to !== undefined && cause !== undefined)
75
+ view.fallback = { from, to, cause };
76
+ }
77
+ return view;
78
+ }
59
79
  export function gateOutcomeOf(frame) {
60
80
  if (typeof frame !== 'object' || frame === null || Array.isArray(frame))
61
81
  return undefined;
@@ -69,8 +89,10 @@ export function gateOutcomeOf(frame) {
69
89
  const dr = d;
70
90
  const dk = str(dr.kind);
71
91
  let disposition;
92
+ // 0.68.3(core 7.18.0 #742):两臂都可带 `classifier`;逐键挑、缺席不铸键。
93
+ const classifier = readClassifierRound(dr.classifier);
72
94
  if (dk === 'allowed') {
73
- disposition = { kind: 'allowed' };
95
+ disposition = { kind: 'allowed', ...(classifier !== undefined ? { classifier } : {}) };
74
96
  }
75
97
  else if (dk === 'denied') {
76
98
  const deniedBy = str(dr.deniedBy);
@@ -79,7 +101,12 @@ export function gateOutcomeOf(frame) {
79
101
  return undefined;
80
102
  // 0.67.0:分类器 deny 的成因位(开集透传、缺席不铸键;判成员在 `classifierDenyCauseOf`)。
81
103
  const cause = str(dr.cause);
82
- disposition = { kind: 'denied', deniedBy, ...(cause !== undefined ? { cause } : {}) };
104
+ disposition = {
105
+ kind: 'denied',
106
+ deniedBy,
107
+ ...(cause !== undefined ? { cause } : {}),
108
+ ...(classifier !== undefined ? { classifier } : {}),
109
+ };
83
110
  }
84
111
  else {
85
112
  // 处置词本身不认识 ⇒ 整只不成形:`allowed` / `denied` 是这条记录的**判别位**,猜不得。
package/dist/index.d.ts CHANGED
@@ -141,6 +141,10 @@ export * from './diagnostics.js';
141
141
  export * from './retryStatus.js';
142
142
  export * from './sessionMemoryStatus.js';
143
143
  export * from './adapt.js';
144
+ export * from './adapt/textSegmentAuthority.js';
145
+ export * from './engineHttpTools.js';
146
+ export * from './sdkWireTransit.js';
147
+ export { SEMA_SEGMENT_ID_KEY } from './adapt/textStream.js';
144
148
  export * from './subagentContentStore.js';
145
149
  export * from './engineAgentPanelStore.js';
146
150
  export * from './fleetAgentPanelProjection.js';
@@ -152,6 +156,7 @@ export * from './sqlEngineCapability.js';
152
156
  export * from './writeProtectionCapability.js';
153
157
  export * from './runTerminal.js';
154
158
  export * from './readFacePosture.js';
159
+ export * from './mcpPanel.js';
155
160
  export * from './gateOutcome.js';
156
161
  export * from './postureKnob.js';
157
162
  export * from './engineIdentity.js';
@@ -244,6 +249,7 @@ export { isAskTool } from './hitl/frameRouter.js';
244
249
  export * from './hitl/hitlHostSurface.js';
245
250
  export * from './hitl/toolApprovalWire.js';
246
251
  export * from './hitl/editedRuleTextPrecheck.js';
252
+ export * from './coreValuePorts.js';
247
253
  export * from './hitl/askGateWire.js';
248
254
  export * from './hitl/planReviewWire.js';
249
255
  export * from './hitl/gateIdentity.js';
package/dist/index.js CHANGED
@@ -152,6 +152,24 @@ export * from './retryStatus.js';
152
152
  // 合法缺席,一律读成「没有/关着/0」就是对用户下一个证不出的断言。
153
153
  export * from './sessionMemoryStatus.js';
154
154
  export * from './adapt.js';
155
+ // ── L-318(0.68.2):`text_end` 权威段替换的**判决单源** + print 形车道的段账状态机 ──────────
156
+ // 修前这套六形判据有两份实现:本包 `adapt/textStream.replaceAnswerSegment`(交互车道)与 cli
157
+ // 1.0.114 在壳里热修的 `PrintStreamProjector.onTextEnd`(`-p` 车道,B-122)。两条车道的**账本**
158
+ // 形状不同(消息划界 vs 帧划界,差别有理由、刻意不归一),但**判决**是同一件事 ⇒ 判决进包,
159
+ // 两条车道同吃;`-p` 车道的段账(已出门前缀累加 / 轮收口清账 / 被扣段判重)也归包,端只留
160
+ // 「把尾段写进哪一个 CC content block」这一步装配(§8-5:stream-json 帧序/SSE 重铸属端)。
161
+ export * from './adapt/textSegmentAuthority.js';
162
+ // ── L-61 / L-318 件④(0.68.2):SDK **纯工具面**转口口(engineUrl / probeHealth)。端直连 SDK
163
+ // 只为取这两件的存量(cli sdk-isolation `sdk-value` 桶)从此有「经包」的路;值级转口在可移植门
164
+ // 上零风险的理由(SDK 已在外部包等值集里 + `dist/health.js` 零 import)写在模块头注。
165
+ export * from './engineHttpTools.js';
166
+ // ── L-61 存量清零(clay 令 C-R43「所有欠账绝不延期,宁可红」):SDK **wire 面**转口口 ────────
167
+ // 客户端类 + 流内审批帧谓词 + 端上存量实际用到的那一小撮 wire 型面。**原样转口零包装**;
168
+ // 「端该不该直接 new AgentClient」是另一个(仍然欠着的)设计问题,不该继续挡着归层 —— 理由与
169
+ // 射程边界全在模块头注。新码一律走本包自有的 `makeEngineWireClient`,本转口口只给存量用。
170
+ export * from './sdkWireTransit.js';
171
+ // CC-01(0.68.2):段身份键 —— committed assistant 文本行顶层 `_sema_segment_id` 的唯一字面量出处(壳只读此常量)。
172
+ export { SEMA_SEGMENT_ID_KEY } from './adapt/textStream.js';
155
173
  // ── B1 批:纯函数 / 侧信道台账 / 投影闸(2026-07-27)──────────────────────────────────────────
156
174
  export * from './subagentContentStore.js';
157
175
  export * from './engineAgentPanelStore.js';
@@ -173,6 +191,8 @@ export * from './runTerminal.js';
173
191
  // (防御读 / 唯一措辞铸点 / UNTRUSTED-for-display);与租户面 `capabilities.readFace` 刻意不合流,
174
192
  // 只带一个纯比较函数,渲染归端。
175
193
  export * from './readFacePosture.js';
194
+ // CC-03(0.68.2):`GET /v1/sessions/:id/mcp` 面板体的防御读视图 + lastLegMcp 一行措辞铸点(三端共用)。
195
+ export * from './mcpPanel.js';
176
196
  export * from './gateOutcome.js';
177
197
  // 0.63.0(sdk 8.8.0 / engine ≥7.67.0 / S-178):`serverGates` 三根 posture 旋钮的读数窄读器
178
198
  // (值 + 谁定的 + 指路句)。四词来源表在本包只有这一份,`readFacePosture` 与它共用。
@@ -413,6 +433,8 @@ export * from './hitl/toolApprovalWire.js';
413
433
  // `--platform=browser` 真打包,一条这样的边会把整台引擎焊进 web/desktop 的产物。
414
434
  // ⇒ 转出口取「类型面 + 注入口 + 诚实缺席读口」三件(逐条理由见模块头注)。
415
435
  export * from './hitl/editedRuleTextPrecheck.js';
436
+ // CC-07(0.69.0):core **值级**面十件的端口注入口(cli L-249 core-value 桶清零;同 editedRuleTextPrecheck 形:包声明端口 + 诚实缺席读口)。
437
+ export * from './coreValuePorts.js';
416
438
  export * from './hitl/askGateWire.js';
417
439
  export * from './hitl/planReviewWire.js';
418
440
  // ── A-028.2/.3(#244 族A,2026-08-12):决断卡链的键词汇/呈现台账/归属判据三件 ————————————