@sema-agent/client-core 0.33.0 → 0.34.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
@@ -16,6 +16,36 @@
16
16
  > 🔴 **互链**(web [C166]⑦):各版「已知局限」段只记**该版新增**;接入面已知局限的完整台账在
17
17
  > `docs/INTEGRATION-CLIENTS.md` §6e/§7 —— **只读其一会漏**,两处都过。
18
18
 
19
+ ## 0.34.0(2026-08-17)
20
+
21
+ **#292 P1(cli 黑板 [4285]/[4309] 定谳)——`REQUEST_FIELD_MATRIX` 的 `settings.<resolved>` 行补
22
+ `print` 车道、撤 `gap:true`。行为面**一条**:print 车道的 `buildTaskRequest` 从此会把端给的
23
+ `input.settings.resolved` 摊开上 wire(端不给值 ⇒ 仍零 stamp,不会凭空出现)。**
24
+
25
+ - **修的是什么**:这一行自 B4 起就自标 `gap:true`(表内定义逐字 = 「这条差异**没有正当理由,
26
+ 是漏的**」),而它的缺席是**权限方向**的 —— cli [4208] 实测:`-p` 车道上用户 settings 的
27
+ `permissions.deny/ask` 规则**整体不被引擎求值**(deny 拦不住),交互 REPL 同一份文件照拦。
28
+ 同一份配置在两条车道上给出相反的安全结论,不是「车道本来就该不同」。
29
+ - 🔴 **配对纪律(这条修为什么等了这么久)**:包内 stamp 门只是**一半**。另一半在端 ——
30
+ 端必须把 resolver 快照放进 `input.settings.resolved`;cli 的 print 车道修前把 boot 期算好的
31
+ stamp 写进一个只有交互构造器读的模块级 store(死写)。**任一半单独改都无效**,故本条与 cli
32
+ 同批发([4309] 定谳)。
33
+ - **端影响面**:`interactive` 车道逐字节零变化(web / desktop 不经 print 车道 ⇒ 零扰动);
34
+ print 车道的宿主(今天只有 cli)按上一条给值才生效。
35
+ - ⚠️ **可执行判据的口径变化(要知道代价)**:`unregisteredRequestKeys(req,'print')` 对 `settings` 走
36
+ **开放集**口径 —— 只放行**表外**动态子键(resolver 快照自己的 permissions/env/model/…,不可枚举),
37
+ **表内但不属于本车道**的子键(今天 = print 的 `settings.ultracode`)**仍被点名**;顶层键的点名一字
38
+ 未动。构造器侧同一道纪律:`resolvedForLane` 在摊开快照前剥掉本车道不拥有的具名子键,并丢掉
39
+ **函数值**(自有 `toJSON` 能在序列化那一刻整只改写 `settings` 的字节 = 绕过一切按对象查键的判据)。
40
+ 脏快照(null / 非对象 / 数组)一律降空对象 —— 旧式 `?? {}` 的 null 容忍不许因本批变成 TypeError。
41
+ 判据面同款纪律:`settings` 上(自有 / 非枚举 / 原型链)只要有 callable `toJSON`,`unregisteredRequestKeys`
42
+ 一律点名 `settings.toJSON` —— 那种对象「看起来一份、序列化成另一份」,判据不许被它骗过。
43
+ ⚠️ 本层**不做**整份请求体的递归 JSON 净化(函数/访问器/循环/自定义原型):端是同进程受信调用方,
44
+ 而同一条论证对 `agents`/`hooks`/`attachments` 等一切对象位一样成立 ⇒ 归「包级 wire 载荷 JSON-safe
45
+ 规范化」独立工单,不在本批射程。
46
+ - 文档:`docs/INTEGRATION-CLIENTS.md` P-15 的 gap 台账 6 → 5(结清 `settings.<resolved>`,并写明
47
+ print 车道现在**会**摊开快照 + 开放集口径)。
48
+
19
49
  ## 0.33.0(2026-08-17)
20
50
 
21
51
  **#289 批面②③(dogfood 案1②/案5,[4199] T2/T5 RED 修)——additive,零 BREAKING。**
@@ -12,6 +12,8 @@
12
12
  * · **print 独有** `finalVerification` / `limits` / `interactiveTools`;
13
13
  * · **TUI 独有** `ultracode` / `systemPrompt` / `reasoningEffort` / `images` / `model` /
14
14
  * rewind 三件 / `clientContext` / `scratchpadDir` / 已解析 settings。
15
+ * (最后一项 2026-08-17 已补齐两车道 —— #292 P1,理由见矩阵里那一行的行注:它的缺席是
16
+ * **权限方向**的,不是排版问题。)
15
17
  * 这不是「两条车道本来就该不同」——里面**一部分是有理由的,一部分纯粹是漏了**。合一的目的不是
16
18
  * 把它们抹平(那是行为改动),而是把「谁有谁没有、为什么」变成**一张表**:再漏就会在 diff 里显形。
17
19
  *
@@ -164,8 +166,16 @@ export type BuiltTaskRequest = TaskRequest;
164
166
  *
165
167
  * 判据口径(三类不点名):
166
168
  * ① 车道表里的字段(`lanes` 含本车道);② `LIVE_DEFAULT_FIELDS` —— live 兜底层追加、两条车道
167
- * 都经过,故本就不进车道表;③ `settings` 的子键按表认(`settings.<sub>`),但**交互车道**的
168
- * `settings.<resolved>` resolver effective 快照 = **开放集**,子键不可枚举 ⇒ 整体放行。
169
+ * 都经过,故本就不进车道表;③ `settings` 的子键按表认(`settings.<sub>`)—— 登记了
170
+ * `settings.<resolved>` 的车道(#292 P1 = **两条**)那份 resolver effective 快照是**开放集**,
171
+ * 所以**表外**子键(permissions/env/model/… 不可枚举)放行。
172
+ *
173
+ * 🔴 开放集**不是**整只 `settings` 免检(#292 P1 codex 复审第二轮 [medium] 采纳):表里点名了、
174
+ * 而 lanes 不含本车道的子键(如 print 车道的 `settings.ultracode`)照旧**点名** —— 那些键是
175
+ * **可枚举的已知量**,放过它们等于让「ultracode 仅 interactive」这条登记在真 wire 面失效。
176
+ * `buildTaskRequest` 内的 `resolvedForLane` 只管住构造器自己那一份;端在构造之后仍能往请求体上
177
+ * 塞键(print lane 修前正是「自己另拼一份」),而本函数的整个存在理由就是替**真上 wire 的那份**
178
+ * 过表。两道门缺一不可。
169
179
  *
170
180
  * 🔴 它**不判**「该不该有」——只判「有没有登记」。补齐一条真实差异要改表(带理由),
171
181
  * 而不是绕过本函数:改表这个动作本身就会在 diff 里显形,这正是本判据存在的意义。
@@ -24,6 +24,14 @@ export const REQUEST_FIELD_MATRIX = [
24
24
  { field: 'mcpServers', lanes: ['interactive', 'print'], live: true, why: '本地 .mcp.json 投影;service 单用户门决定是否兑现' },
25
25
  { field: 'settings.webSearch', lanes: ['interactive', 'print'], live: true, why: 'SEMA_WEBSEARCH_* / settings.json;per-request 配置赢过部署 env' },
26
26
  { field: 'settings.hooks', lanes: ['interactive', 'print'], live: true, why: '[495]① 用户 settings 文件 hooks 逐字上 wire' },
27
+ // ⚠️ 这一行 2026-08-17 之前是 `lanes:['interactive'] gap:true`(表内自标「这条差异没有正当理由,
28
+ // 是漏的」),#292 P1 把它补齐并搬进本节。补齐的理由不是对称美观,是 [4208] 实测的**权限方向**
29
+ // 后果:`-p` 车道上用户 settings 的 `permissions.deny/ask` 整体不被引擎求值(deny 拦不住),
30
+ // 而交互 REPL 同一份文件照拦 —— 两条车道对同一份配置给出相反的安全结论。
31
+ // 🔴 配对纪律:这一行只管**包内 stamp 门**;端还得把值放进 `input.settings.resolved`
32
+ // (壳 print 车道 = `-p` 前置步装一个晚绑定供给器,`seamQueryEngine` 在拼装请求那一刻求值)。
33
+ // 任一半单独改都无效 —— 这正是本条修前长期停在 gap 的原因([4285]/[4309] 定谳)。
34
+ { field: 'settings.<resolved>', lanes: ['interactive', 'print'], live: true, why: 'settings resolver 的 effective 快照(hooks/env/model + 已解析权限投影)。两条车道同源:交互面 replEntry / print 面 printModeEngine 各跑一遍同一个 resolver,产物经同一个 buildWireSettingsStamp 投影。开放集(子键不可枚举)⇒ unregisteredRequestKeys 放行本车道 settings 的**表外**子键,但表内异车道子键仍点名' },
27
35
  // ── repo 场景五键([C135] 请托,2026-08-08):SDK TaskRequest 成文 passthrough 键(types doc
28
36
  // 「scenario-specific fields like code-review's repo/council/debate/rounds ride here」),server
29
37
  // resolve-spec 顶层直读(council/debate 按 `=== true`,rounds 进 clamp [1,3])。lanes 与
@@ -54,7 +62,6 @@ export const REQUEST_FIELD_MATRIX = [
54
62
  { field: 'resumeAt/rewindFiles/rewindFilesTo', lanes: ['interactive'], live: false, why: 'E18 `/rewind` 是交互命令' },
55
63
  { field: 'clientContext', lanes: ['interactive'], live: false, why: 'IANA 时区 + 可选邮箱 → core 本地化 `# Environment` 的 today;print 同样跑在用户机器上,缺席让引擎误标 (UTC)', gap: true },
56
64
  { field: 'scratchpadDir', lanes: ['interactive'], live: true, why: '[816]③ per-session 暂存目录;print 也有 session,缺席让 `-p` 的工具写不进 exemptDir', gap: true },
57
- { field: 'settings.<resolved>', lanes: ['interactive'], live: true, why: 'settings resolver 的 effective 快照(hooks/env/model + 已解析权限);print 只带 webSearch+hooks 两键', gap: true },
58
65
  ];
59
66
  /** live 兜底层(`toLiveRequest`)追加的字段 —— 两条车道**都**经过,故不进上表。 */
60
67
  export const LIVE_DEFAULT_FIELDS = [
@@ -70,6 +77,55 @@ export const LIVE_DEFAULT_FIELDS = [
70
77
  'sessionId(三态解析)',
71
78
  ];
72
79
  const laneHas = (field, lane) => REQUEST_FIELD_MATRIX.find(f => f.field === field)?.lanes.includes(lane) ?? false;
80
+ /**
81
+ * 本车道**不拥有**的具名 settings 子键(= 表里有 `settings.<sub>` 行、而该行的 lanes 不含本车道)。
82
+ *
83
+ * 为什么要有(#292 P1 codex 对抗复审 [medium] 采纳):`settings.<resolved>` 是**开放集 spread**
84
+ * —— 快照里的子键原样就是 wire 键。所以一旦某条车道能摊开快照,它就能借快照绕过那条车道
85
+ * **专属**子键的 stamp 门:`resolved:{ultracode:true}` 在 print 车道会真的生成
86
+ * `settings.ultracode:true`,而表里那一行写的是 interactive 独有。更坏的是它**不会被点名** ——
87
+ * 修前更坏的是它**不会被点名**(开放集口径曾对整只 `settings` 放行);现在 `unregisteredRequestKeys`
88
+ * 对「表内异车道子键」照旧点名,两道门配对成立 —— 但构造器这一道仍不可省:端在构造之后还能塞键,
89
+ * 而构造器这一道管的是「本包自己产的那份不许含异车道键」。
90
+ * 「表说了算,不是调用方说了算」要成立,就必须对**两条通道**都成立:具名通道过 `on()`,
91
+ * 开放集通道过本函数。今天的端(cli `toWireSettings` 是白名单投影)不产 ultracode,所以这是
92
+ * **潜在**旁路而不是现网缺陷 —— 但下一条车道专属子键落表时,它就是现网缺陷。
93
+ *
94
+ * 🔴 只剥「表里点名了、且不属于本车道」的键:表外子键(快照自己的 permissions/env/model/…)
95
+ * 原样摊开(那是开放集的全部意义),`outputStyle` 无车道行(live 兜底层的位)故不受影响。
96
+ */
97
+ const foreignSettingsSubKeys = (lane) => REQUEST_FIELD_MATRIX.filter(f => f.field.startsWith('settings.') && f.field !== 'settings.<resolved>' && !f.lanes.includes(lane)).map(f => f.field.slice('settings.'.length));
98
+ /**
99
+ * 快照 → 摊开前先剥掉本车道不拥有的具名子键(见 {@link foreignSettingsSubKeys})。
100
+ *
101
+ * 🔴 非对象一律降空对象(#292 P1 codex 复审第二轮 [medium] 采纳):型面写的是
102
+ * `Record<string, unknown> | undefined`,但本包是**发出去的 npm 公开面** —— JS 调用方、版本偏斜的
103
+ * 宿主、以及「端把 null 当空快照传」的老写法都到得了这里。修前那一版只判 `undefined`,`null` 会
104
+ * 走到 `Object.entries(null)` 当场 TypeError ⇒ headless 请求在发出**之前**就崩,而交互车道不崩 =
105
+ * 新造一条两车道差异。旧式 `?? {}` 对 null 是容忍的,这里把容忍面扩到「一切非对象」(降空 =
106
+ * 不 stamp = wire 上最小的诚实形;绝不把一个字符串 spread 成 `{0:'a',1:'b'}` 那种垃圾键)。
107
+ */
108
+ const resolvedForLane = (resolved, lane) => {
109
+ if (resolved === null || resolved === undefined || typeof resolved !== 'object')
110
+ return {};
111
+ if (Array.isArray(resolved))
112
+ return {}; // 数组也是 object,但 spread 出来是索引键,同属垃圾形
113
+ const foreign = foreignSettingsSubKeys(lane);
114
+ const out = {};
115
+ for (const [k, v] of Object.entries(resolved)) {
116
+ if (foreign.includes(k))
117
+ continue;
118
+ // 🔴 函数值一律不进(#292 P1 codex 复审第三轮 [medium] 采纳):JSON wire 上没有函数,而一个
119
+ // **自有 `toJSON`** 会在序列化那一刻整只改写 `settings` 的字节 —— 实测
120
+ // `resolved={permissions:…, toJSON(){return {ultracode:true}}}` 能让 `JSON.stringify(req)` 出
121
+ // `settings:{ultracode:true}`,把上面那道车道剥离(以及一切按对象查的判据)整体绕过。
122
+ // 「真上 wire 的那份」= 序列化后的字节,所以过滤必须发生在**值**这一层,而不是只看键名。
123
+ if (typeof v === 'function')
124
+ continue;
125
+ out[k] = v;
126
+ }
127
+ return out;
128
+ };
73
129
  /**
74
130
  * 合一后的请求构造器 —— **按车道出两形**,字段集差异全部由 `REQUEST_FIELD_MATRIX` 决定。
75
131
  *
@@ -93,9 +149,13 @@ export function buildTaskRequest(input, lane) {
93
149
  };
94
150
  // settings 四源合并(cli 原式:任一存在则 stamp,全缺则整个 `settings` 键都不出现)。
95
151
  // ultracode 只在交互车道进表 ⇒ print 传了也不 stamp(表说了算,不是调用方说了算)。
152
+ // resolved 两车道都进表(#292 P1)——它是**开放集 spread**(子键即 wire 键),所以走
153
+ // `laneHas + live` 而不是 `on()`:`on()` 判的是「这个键值非空」,而这里要判的是「这份快照要不要
154
+ // 摊开」。合并序恒是 resolved 先、具名子键后 —— 具名通道(webSearch/hooks/outputStyle)各有自己
155
+ // 的门,同名时必须赢过快照里那份(见 cli settingsRulesWire 的 hooks 剥离注)。
96
156
  const s = input.settings ?? {};
97
157
  const settingsOut = {
98
- ...(laneHas('settings.<resolved>', lane) && live ? (s.resolved ?? {}) : {}),
158
+ ...(laneHas('settings.<resolved>', lane) && live ? resolvedForLane(s.resolved, lane) : {}),
99
159
  ...(on('settings.webSearch', s.webSearch) ? { webSearch: s.webSearch } : {}),
100
160
  ...(on('settings.ultracode', s.ultracode) ? { ultracode: s.ultracode } : {}),
101
161
  ...(on('settings.hooks', s.hooks) ? { hooks: s.hooks } : {}),
@@ -226,8 +286,16 @@ export function applyLiveRequestDefaults(req, host) {
226
286
  *
227
287
  * 判据口径(三类不点名):
228
288
  * ① 车道表里的字段(`lanes` 含本车道);② `LIVE_DEFAULT_FIELDS` —— live 兜底层追加、两条车道
229
- * 都经过,故本就不进车道表;③ `settings` 的子键按表认(`settings.<sub>`),但**交互车道**的
230
- * `settings.<resolved>` resolver effective 快照 = **开放集**,子键不可枚举 ⇒ 整体放行。
289
+ * 都经过,故本就不进车道表;③ `settings` 的子键按表认(`settings.<sub>`)—— 登记了
290
+ * `settings.<resolved>` 的车道(#292 P1 = **两条**)那份 resolver effective 快照是**开放集**,
291
+ * 所以**表外**子键(permissions/env/model/… 不可枚举)放行。
292
+ *
293
+ * 🔴 开放集**不是**整只 `settings` 免检(#292 P1 codex 复审第二轮 [medium] 采纳):表里点名了、
294
+ * 而 lanes 不含本车道的子键(如 print 车道的 `settings.ultracode`)照旧**点名** —— 那些键是
295
+ * **可枚举的已知量**,放过它们等于让「ultracode 仅 interactive」这条登记在真 wire 面失效。
296
+ * `buildTaskRequest` 内的 `resolvedForLane` 只管住构造器自己那一份;端在构造之后仍能往请求体上
297
+ * 塞键(print lane 修前正是「自己另拼一份」),而本函数的整个存在理由就是替**真上 wire 的那份**
298
+ * 过表。两道门缺一不可。
231
299
  *
232
300
  * 🔴 它**不判**「该不该有」——只判「有没有登记」。补齐一条真实差异要改表(带理由),
233
301
  * 而不是绕过本函数:改表这个动作本身就会在 diff 里显形,这正是本判据存在的意义。
@@ -248,17 +316,38 @@ export function unregisteredRequestKeys(req, lane) {
248
316
  .filter(f => f.field.startsWith('settings.') && f.field !== 'settings.<resolved>')
249
317
  .map(f => f.field.slice('settings.'.length)));
250
318
  const settingsIsOpenSet = laneRows.some(f => f.field === 'settings.<resolved>');
319
+ /** 表里点名了、但 lanes 不含本车道的具名子键 —— 开放集车道也照旧点名(见头注第二条红线)。 */
320
+ const foreignSettingsKeys = new Set(foreignSettingsSubKeys(lane));
251
321
  const out = [];
252
322
  for (const key of Object.keys(req)) {
253
323
  if (key === 'settings') {
254
- if (settingsIsOpenSet)
255
- continue;
256
324
  const settings = req.settings;
257
325
  if (settings === null || typeof settings !== 'object')
258
326
  continue;
259
- for (const sub of Object.keys(settings)) {
327
+ // 🔴 自报会改写自己字节的 `settings` 一律点名(#292 P1 复审第四轮的可采部分):`JSON.stringify`
328
+ // 找 `toJSON` 走的是**属性查找**(原型链上的、非枚举的都算),而下面的逐键遍历只看**可枚举自有**
329
+ // 键 ⇒ 一个挂在原型上的 `toJSON` 能让本函数看到一份对象、wire 上却是另一份字节。本函数的承诺
330
+ // 是「替真上 wire 的那份过表」,所以这种「对象与字节不一致」的形必须先被点名,而不是默默通过。
331
+ // (⚠️ 递归深净化整份请求体不在本层的职责里 —— 那是包级 wire 载荷 JSON-safe 规范化的设计题,
332
+ // 今天对 agents/hooks/attachments 等一切对象位一样成立,已登记为独立工单;端是同进程受信调用方,
333
+ // 这里做的是「判据不许被骗」,不是「防御受信宿主」。)
334
+ if (typeof settings.toJSON === 'function')
335
+ out.push('settings.toJSON');
336
+ for (const [sub, value] of Object.entries(settings)) {
337
+ if (sub === 'toJSON')
338
+ continue; // 上面那条属性查找已点过名(可枚举自有形不重复报)
339
+ // 🔴 函数值恒点名(复审第三轮 [medium]):wire 上没有函数,而自有 `toJSON` 会在序列化时
340
+ // 整只改写 `settings` 的字节 ⇒ 一切「按对象查键」的判据(含本函数)都会被它骗过。所以它
341
+ // 先于任何登记判断被点名 —— 包括登记在册的子键名上挂了函数值的形。
342
+ if (typeof value === 'function') {
343
+ out.push(`settings.${sub}`);
344
+ continue;
345
+ }
260
346
  if (laneSettingsKeys.has(sub) || liveDefaults.has(`settings.${sub}`))
261
347
  continue;
348
+ // 开放集车道:表外子键放行(快照的键不可枚举),表内异车道子键仍点名。
349
+ if (settingsIsOpenSet && !foreignSettingsKeys.has(sub))
350
+ continue;
262
351
  out.push(`settings.${sub}`);
263
352
  }
264
353
  continue;
@@ -724,7 +724,7 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
724
724
 
725
725
  | ID | 级别 | 一句话现状 | 实现锚 | 端**今天**必须怎么办 |
726
726
  |---|---|---|---|---|
727
- | **P-15** | med | `REQUEST_FIELD_MATRIX` 有 **6 个字段登记为 `gap: true`**(表内 `gap:true` 的定义逐字 = 「这条差异**没有正当理由,是漏的**」),全部是 **print/headless 车道缺席**:`settings.ultracode` · `reasoningEffort` · `model` · `clientContext` · `scratchpadDir` · `settings.<resolved>`。表内点名的后果:`clientContext` 缺席 ⇒ **引擎误标 (UTC)**;`scratchpadDir` 缺席 ⇒ **`-p` 的工具写不进 exemptDir** | `src/request/taskRequest.ts`(`REQUEST_FIELD_MATRIX` 的 `gap` 列) | headless 车道上这六项**确实不上 wire**。端不要在 print 车道假设它们在场;补齐是**行为改动**,要单独一条测试,不许端侧偷加 |
727
+ | **P-15** | med(6 → **5**,#292 P1 结清一条) | `REQUEST_FIELD_MATRIX` 有 **5 个字段登记为 `gap: true`**(表内 `gap:true` 的定义逐字 = 「这条差异**没有正当理由,是漏的**」),全部是 **print/headless 车道缺席**:`settings.ultracode` · `reasoningEffort` · `model` · `clientContext` · `scratchpadDir`。表内点名的后果:`clientContext` 缺席 ⇒ **引擎误标 (UTC)**;`scratchpadDir` 缺席 ⇒ **`-p` 的工具写不进 exemptDir**。**已结清**:`settings.<resolved>`(0.34.0 / #292 P1 —— 版本号与 CHANGELOG 段**同一个**,codex 复审 [low] 抓的正是两处不一致)—— 它的缺席是**权限方向**的(`-p` 上用户 settings 的 `permissions.deny/ask` 整体不被引擎求值,cli [4208] 实测),现两车道都 stamp | `src/request/taskRequest.ts`(`REQUEST_FIELD_MATRIX` 的 `gap` 列) | headless 车道上这五项**确实不上 wire**。端不要在 print 车道假设它们在场;补齐是**行为改动**,要单独一条测试,不许端侧偷加。`settings.<resolved>` 反过来:print 车道现在**会**摊开 resolver 快照 ⇒ 端必须把值放进 `input.settings.resolved`(端不给值仍是零 stamp,不会凭空出现),且该车道 `settings` 子键走**开放集**口径:`unregisteredRequestKeys` 只放行**表外**动态子键(快照自己的 permissions/env/model/… 不可枚举),**表内但不属于本车道**的子键(今天 = print 的 `settings.ultracode`)仍会被点名 —— 端不许把它白名单化,那条红是真的;另:`settings` 子键值为**函数**(如自有 `toJSON`,能在序列化时整只改写字节)恒被点名且构造器不 stamp |
728
728
  | **P-15b** | 🔴 权限方向 | `REQUEST_FIELD_MATRIX` 的 stamp 门对**未登记键静默丢弃** —— 表里点名的真实危险形逐字:**「用户显式排除的工具被静默放回」(权限方向回归,类型层不报)**。`excludeTools` 是真 wire 键、早在 seatContract 的 `START_SESSION_OPTION_KEYS` 里,却曾长期在矩阵外;**今天只有 desktop 在发它** | `src/request/taskRequest.ts`(`excludeTools` 行)、`src/seatContract.ts`(`START_SESSION_OPTION_KEYS`) | 端自拼 taskReq 的键**必须**先进矩阵;上 wire 前跑 `unregisteredRequestKeys(req, lane)` 并**当红对待**,别当 lint |
729
729
  | **P-16** | 提货单 S 组(**BREAKING**,P2 裁决**未填**) | 三条签名级 BREAKING 在册未决,**约束三端的 port 实现**:REF-CC-133(`SettingsPort.getSettingsForSource` 返回裸 `unknown`)· REF-CC-134(`ModelFacingTaskOutput`)· REF-CC-135(`errCodes`,**且是行为改动**:脏形不再原样透传) | `docs/REFACTOR-LEDGER.md` REF-CC-133/134/135 | 端实现 `SettingsPort` 时不要依赖当前的宽返回型;`errCodes` 的脏形透传行为**会变** |
730
730
  | **P-17** | 在册 | 两条 wave2 残余:REF-CC-059(`NeutralDelta` 命名已落,**泄漏门没建**)· REF-CC-063(自锚已修,`gate:line-anchor` 口未做) | `docs/refactor/WAVE2-RESIDUALS.md` | 端无直接动作;别把这两条当已闭合 |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
5
5
  "license": "MIT",
6
6
  "type": "module",