@sema-agent/client-core 0.47.0 → 0.48.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.
@@ -15,22 +15,25 @@
15
15
 
16
16
  ## §0 版本锚与重扫纪律
17
17
 
18
- ### 0a. 版本锚(2026-08-31)
18
+ ### 0a. 版本锚(2026-09-01)
19
19
 
20
20
  | 项 | 值 | 真源 |
21
21
  |---|---|---|
22
- | 本包 | `@sema-agent/client-core` **0.47.0**(工作树;发布前 npm 最新是 `0.46.0`) | `package.json` `version` |
23
- | peer:wire 契约 | `@sema-agent/sdk` **>=7.2.0**(value-level,非 type-only) | `package.json` `peerDependencies` |
22
+ | 本包 | `@sema-agent/client-core` **0.48.0**(工作树;发布前 npm 最新是 `0.47.0`) | `package.json` `version` |
23
+ | peer:wire 契约 | `@sema-agent/sdk` **>=7.4.0**(value-level,非 type-only;0.48.0 抬版,四条硬理由见 `CHANGELOG.md` 0.48.0 段末的地板影响面账) | `package.json` `peerDependencies` |
24
24
  | peer:会话词汇表 | `@sema-agent/agent-types` **>=0.2.0**(type-only,零运行时) | 同上 |
25
25
  | runtime dep | `diff` ^9.0.0(**唯一**一条;portability 门按**等值**钉死) | `package.json` `dependencies` |
26
- | 公开导出面 | **790** 个运行期符号(+ 41 个测试钩;= 未发 0.47.0 的值,npm `0.46.0` 是 **787**,`0.44.0` 是 **783**,`0.43.1`/`0.43.0` 是 **776**,`0.42.0` 是 **771**,`0.41.0` 是 **767**,`0.39.0` 是 **766**,`0.38.0` 是 **764**;`0.37.0` 是 **753**,见 `CHANGELOG.md`) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
26
+ | 公开导出面 | **794** 个运行期符号(+ 41 个测试钩;= 未发 0.48.0 的值,npm `0.47.0` 是 **790**,`0.46.0` 是 **787**,`0.44.0` 是 **783**,`0.43.1`/`0.43.0` 是 **776**,`0.42.0` 是 **771**,`0.41.0` 是 **767**,`0.39.0` 是 **766**,`0.38.0` 是 **764**;`0.37.0` 是 **753**,见 `CHANGELOG.md`) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
27
27
  | 常驻门 | 以 `scripts/gates-manifest.json` 的 `suites` 长度为准(**本档不抄这个数**) | `scripts/gates-manifest.json`;`npm test` 的名单等值门与它逐名对账 |
28
28
  | 沿革档 | 0.29.0 起建 `CHANGELOG.md`;更早批次记账在 `src/index.ts` 文件头 + `docs/REFACTOR-LEDGER.md` | — |
29
29
 
30
- ⚠️ **工作树 ≠ npm**:本表记的是**工作树**的 0.47.0,npm 上最新仍是 0.46.0(冻结账里 0.47.0 是
31
- `pending` 行)。装 ≤0.46.0 的端注意:0.47.0 新增的 **3 个 additive 导出**
32
- (`planInteractiveHalt` / `RUN_LEVEL_STOP_ERROR_CODES` / `readDecideCurrentPending`)在旧版上按名
33
- import 会**在 ESM 实例化当场炸**(具名导出不存在)—— 提货前先抬依赖。同一条对 0.38.0 那 11 个
30
+ ⚠️ **工作树 ≠ npm**:本表记的是**工作树**的 0.48.0,npm 上最新仍是 0.47.0(冻结账里 0.48.0 是
31
+ `pending` 行)。装 ≤0.47.0 的端注意:0.48.0 新增的 **4 个 additive 导出**
32
+ (`readSessionMemoryStatus` / `classifyMemoryStatusFailure` / `readCaptureOptOut` / `readLastCapture`)
33
+ 在旧版上按名 import 会**在 ESM 实例化当场炸**(具名导出不存在)—— 提货前先抬依赖。
34
+ 🔴 **0.48.0 还抬了 peer 地板**(`@sema-agent/sdk >=7.4.0`),这是本版**唯一**的非 additive 面:
35
+ 端装 <7.4.0 的 SDK 会看到 peer 警告(运行期不因此变化)。同一条对 0.47.0 那 **3 个 additive 导出**
36
+ 成立(`planInteractiveHalt` / `RUN_LEVEL_STOP_ERROR_CODES` / `readDecideCurrentPending`)。同一条对 0.38.0 那 11 个
34
37
  additive 导出成立(`engineCapState` + `EngineCapState`、`resolveEntryVision` /
35
38
  `computeDeleteBlockers` / `computeDeleteWarnings`、`CONFIG_DELEGATION_ENTRY_CAPS` /
36
39
  `DELEGATION_CONCURRENCY_CAP` / `DELEGATION_SESSION_CAP` / `DELEGATION_CAP_CODES` /
@@ -103,7 +106,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
103
106
 
104
107
  ## §2 公共导出面地图(按域)
105
108
 
106
- > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**790** 项)。
109
+ > 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**794** 项)。
107
110
  > 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
108
111
  > **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
109
112
  > 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
@@ -113,7 +116,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
113
116
 
114
117
  `public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
115
118
  `scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
116
- 实测:790 项 **100% 是运行期导出,零 type-only**。
119
+ 实测:794 项 **100% 是运行期导出,零 type-only**。
117
120
 
118
121
  **推论(端必须知道)**:
119
122
  - barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
@@ -122,12 +125,12 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
122
125
  端依赖这些类型是合法的,但**不要**拿基线 diff 当"类型面没变"的证据。
123
126
  - `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
124
127
 
125
- 790 项的内部构成(帮助端估读表大小):**233** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
128
+ 794 项的内部构成(帮助端估读表大小):**233** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
126
129
  (矩阵、键集、env 名、锚串)而非可调用物;**5** 项是 PascalCase 运行期值
127
130
  (`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
128
131
  **41** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
129
132
 
130
- ### 2b. 域图(16 域,逐域计数之和 = 790)
133
+ ### 2b. 域图(16 域,逐域计数之和 = 794)
131
134
 
132
135
  | # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
133
136
  |---|---|---|---|---|---|
@@ -145,7 +148,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
145
148
  | 12 | **workflow 与后台工作视图** | 19 | `projectWorkflowRun` · `createLiveWorkflowSource` · `ensureWorkflowActivityLedger` · `readWorkflowActivityLedger` · `stopWorkflowActivityLedger` · `resetWorkflowActivityLedgers` · `createBackgroundView` · `projectBackgroundView` · `recordWorkflowAgentTaskId` · `agentDisplayStatus` | 活过一个 turn 的长任务读面:workflow run + 跨 session 后台任务归一表(`assistant.tasks` 与 fleet SSE **两源独立降级**) | `src/workflow.ts`、`src/workflowClient.ts`、`src/workflowMonitor.ts`、`src/agentSession/backgroundView.ts`(+ 纯类型 `src/agentSession/contract.ts`) |
146
149
  | 13 | **座位 IPC 契约** | 33 | `LOCAL_SESSIONS_SPEC` · `SEAT_METHOD_NAMES` · `SEAT_EVENT_TYPES` · `isLocalSessionEvent` · `isToolPermissionRequest` · `toolPermissionRequestId` · `SEAT_VALIDATOR_KEY_COVERAGE` | desktop↔web 座位 IPC 契约的**单一真源**(此前两边各一份、名字零重合 ⇒ 编译器永远不会告诉你它们漂了)。🔴 加 verb 忘了加 `LOCAL_SESSIONS_SPEC` **不报错**:preload 不注册 channel、渲染端读到 `undefined` | `src/seatContract.ts`(**零 import**,纯类型 + 常量 + 纯谓词)。🔴 **证据等级标注(0.42.0,test [5087] 的「语料**种类**缺口」/ cli [5088] 认领件)**:该文件里所有以「CC 如何如何」为形的断言(`212 methods` / `854-channel census` / 方法名逐字保留 / `fQe` 逐字段对照 / 一切 `.vite/build/index.chunk-*.js` 坐标)**证据等级 = 桌面 unpack,本地语料库不可复验** —— 本仓手边可复验的参照语料**只覆盖终端 CLI 形态**的静态产物,拿它去 grep 桌面壳里的符号只会零命中,而零命中在这里**既不证真也不证伪**。复核这些断言**不得**拿本仓语料当反证 |
147
150
  | 14 | **宿主端口与会话槽** | 26 | `installHost` · `installHostFor` · `hostPortMisses(For)` · `DEFAULT_SESSION_KEY` · `hostEnv` · `unrefTimer` · `parseLocaleTag` / `pickUiLanguage`(#244 F4 族D A-028.20:locale tag 手术单源 + UI 语言判定;与 `resolveRegionHint` 双出口成文 —— 语言偏好域 en/zh ≠ 地址可达域 cn/intl/unknown,`zh-Hant` 前者 zh 后者 intl 是设计)· `engineNamespaceKeyFor` / `mergeSessionMapRecord` / `mergeEngineEntry`(A-028.12:会话 id 映射单一键形 + merge 判定;存储经 `SessionMapStorePort` 归端 —— cli 文件锁/原子写,web localStorage)| 进程/端级装配层(settings/fs/queue/timers/session/log/probe),与 per-turn 的 `AdapterContext` **分层**。头注的判定规则:**这个能力每 turn 都会变吗?** 会 ⇒ `ctx`;不会 ⇒ `installHost` | `src/host.ts`、`src/hostEnv.ts`、`src/sessionSlot.ts`、`src/unrefTimer.ts`、`src/env/{localeGeo,localeTag,uiLanguage}.ts`、`src/sessionMap.ts` |
148
- | 15 | **控制面与传输** | 78 | `ControlRouter`(+ `ControlSafetyError`)· `makeEngineWireClient` + `resolveWireAuth` · `installEngineWireTarget` · `diagnoseSseIdleTear` / `isSseIdleError` · `attemptActiveRunSelfHeal` + `activeRunBusySignal` + `activeRunSelfHealRow` / `activeRunBusyHeadlessRow` · `kickEngineCapsProbe` / `engineCapTrue` / `invalidateEngineCaps(baseUrl, probe?)`(#307 S25:引擎温切后的 caps 生产失效口 —— kick 自带幂等闸,同 baseUrl 重启后不显式失效就永远读到旧引擎那一版能力位;调用方 = 壳的 respawn/restartEngine。🔴 **推荐两参形**:第二参给替代探测则「推进代际 + 注册新探测」在同一同步块内完成,失效与下一次 kick 之间那个「等待者读到未判」的窗按构造不存在;单参形保留给「只丢缓存、这一刻没有替代探测」的调用方,那种情形下读到未判是诚实结局) · `mapBrainStatusToRetry` · `waitForClaimRelease` + `CLAIM_RELEASED_STATES` / `CLAIM_HELD_STATES` · `atMostOnceFailureClass` / `readSteerDelivery` · `clearRunningChoiceOffer`(Inkglow-1085 P0b①:「Do nothing」登记的清口 —— 端的「重新打开操作菜单」入口;登记在场时 attemptActiveRunSelfHeal 不整卡重弹,not-parked 结局带 `alreadyOffered: true` 判别位,端据此降级渲一行)· `INTERACTIVE_WAY_OUT`(默认出路串单源)· `engineSessionParamFor`(design/285 批 0:`?session=` 派生的 **per-key** 形 —— `hostSessionFor(sessionKey)?.currentSessionId()` + [1501] 空串归一;零参 `engineSessionParam()` = 默认槽兼容层,取值链逐字等价)· `normalizeWirePrincipal`(A-028.10:principal 在场性 trim 原语 —— 全空白=缺席不发头,engineWireTarget 两臂/makeEngineWireClient/壳 livePrincipal 同尺)· `classifyTurnWireError` / `isWireTransportError` / `isPreStreamDrainingReject` / `isResumeAtRejection` / `drainingRetryDelayMs` / `scenarioDenyFromError` + `WIRE_NETWORK_ERROR_PATTERN`(A-028.11/.13:turn 错误分型判定半场,人话文案与渲染归端) | 上行通道的**监管**半场(submit / steer / kill / 队列命令定序)+ 传输构造、caps 探测、SSE 断流分诊、**409 active-run 自愈** | `src/controlRouter.ts`、`steering.ts`、`sseIdleTriage.ts`、`retryStatus.ts`、`diagnostics.ts`、`engineWireSdk.ts`、`engineWireTarget.ts`、`src/principalWire.ts`、`src/wireErrorTriage.ts`、`engineSessionParam.ts`、`engineCapsCache.ts`、`liveInitToolFace.ts`、`adapter/activeRunSelfHeal.ts` |
151
+ | 15 | **控制面与传输** | 82 | `ControlRouter`(+ `ControlSafetyError`)· `makeEngineWireClient` + `resolveWireAuth` · `installEngineWireTarget` · `diagnoseSseIdleTear` / `isSseIdleError` · `attemptActiveRunSelfHeal` + `activeRunBusySignal` + `activeRunSelfHealRow` / `activeRunBusyHeadlessRow` · `kickEngineCapsProbe` / `engineCapTrue` / `invalidateEngineCaps(baseUrl, probe?)`(#307 S25:引擎温切后的 caps 生产失效口 —— kick 自带幂等闸,同 baseUrl 重启后不显式失效就永远读到旧引擎那一版能力位;调用方 = 壳的 respawn/restartEngine。🔴 **推荐两参形**:第二参给替代探测则「推进代际 + 注册新探测」在同一同步块内完成,失效与下一次 kick 之间那个「等待者读到未判」的窗按构造不存在;单参形保留给「只丢缓存、这一刻没有替代探测」的调用方,那种情形下读到未判是诚实结局) · `mapBrainStatusToRetry` · `waitForClaimRelease` + `CLAIM_RELEASED_STATES` / `CLAIM_HELD_STATES` · `atMostOnceFailureClass` / `readSteerDelivery` · `clearRunningChoiceOffer`(Inkglow-1085 P0b①:「Do nothing」登记的清口 —— 端的「重新打开操作菜单」入口;登记在场时 attemptActiveRunSelfHeal 不整卡重弹,not-parked 结局带 `alreadyOffered: true` 判别位,端据此降级渲一行)· `INTERACTIVE_WAY_OUT`(默认出路串单源)· `engineSessionParamFor`(design/285 批 0:`?session=` 派生的 **per-key** 形 —— `hostSessionFor(sessionKey)?.currentSessionId()` + [1501] 空串归一;零参 `engineSessionParam()` = 默认槽兼容层,取值链逐字等价)· `normalizeWirePrincipal`(A-028.10:principal 在场性 trim 原语 —— 全空白=缺席不发头,engineWireTarget 两臂/makeEngineWireClient/壳 livePrincipal 同尺)· `readSessionMemoryStatus` / `classifyMemoryStatusFailure` / `readCaptureOptOut` / `readLastCapture`(S-53 会话记忆姿态读面,0.48.0:失败分诊**码优先**——两个 404 分道 `not_found.session` / `not_found.route`,无码 404 不猜落 failed;五键逐键缺席语义两个合读器,`lastCapture` 三态的判别材料是 `committedCount` 不是本键;IO 归宿主注入 `MemoryStatusClientLike`,详见 §11) · `classifyTurnWireError` / `isWireTransportError` / `isPreStreamDrainingReject` / `isResumeAtRejection` / `drainingRetryDelayMs` / `scenarioDenyFromError` + `WIRE_NETWORK_ERROR_PATTERN`(A-028.11/.13:turn 错误分型判定半场,人话文案与渲染归端) | 上行通道的**监管**半场(submit / steer / kill / 队列命令定序)+ 传输构造、caps 探测、SSE 断流分诊、**409 active-run 自愈** | `src/controlRouter.ts`、`steering.ts`、`sseIdleTriage.ts`、`retryStatus.ts`、`diagnostics.ts`、`engineWireSdk.ts`、`engineWireTarget.ts`、`src/principalWire.ts`、`src/wireErrorTriage.ts`、`engineSessionParam.ts`、`engineCapsCache.ts`、`liveInitToolFace.ts`、`adapter/activeRunSelfHeal.ts`、`src/sessionMemoryStatus.ts` |
149
152
  | 16 | **引擎词汇表与包自检** | 48 | `CONFIG_REFUSAL_CODES` / `isConfigRefusalCode` · `STOP_CONFLICT_CODES` · `isInterruptedToolEndCode` · `isRewindFamilyCode` · `CLIENT_VERBS` · `compensationSplitViolations` · `DELEGATION_CAP_CODES` / `isDelegationCapCode` / `DELEGATION_CONCURRENCY_CAP` / `DELEGATION_SESSION_CAP`(0.38.0 #318 件④:core 5.48.0 design/323 委派席位到限**两码,处置不对称禁合并** —— 并发帽=**可等**(兄弟结束即有位)/ 会话累计帽=**等也没用**(这条会话的配额用尽))· `CONFIG_DELEGATION_ENTRY_CAPS`(同批入 `CONFIG_REFUSAL_CODES` 识别表)· `delegationCapDispositionOf` / `MCP_SERVER_REVOKED`(0.39.0 载体到货消费件:core 5.50.0 补 `{ error: code, code }` 孪生拼法后两码真上 `tool_end.errorCode`,0.38.0「先立词不落消费分支」的已知局限自此解除;处置轴 `wait-for-slot` / `reuse-existing-or-await-reap` 机器可读(累计帽=retained-window 帐,行回收配额即回,处置=SendMessage 复用,**非**「换会话/永久耗尽」——0.38.0 段该句系勘误),未知 `delegation.*` 码 ⇒ `undefined`;`mcp.server_revoked` = 操作员 mid-session 吊销 server 后的工具面本地闸(被吊销的 server **名**今天不过 wire 境:detail.server 是进程内位,抬升腿只 lift code——归因渲染候 core 补 typed detail,已点名);载体门 = engine-vocab G3 腿锚 core dist 铸点) | 三端分臂共用的**去字面化** `errorCode` 词表(病根正是三端各抄一份字面);编译期 verb 闭合门;搬迁补偿登记表 | `src/engineErrorCodes.ts`(计数以 `scripts/public-export-baseline.json` 为准,别手抄;A-028.11/.13 补 `DRAINING_ERROR_CODE`/`SCENARIO_NOT_ALLOWED_ERROR_CODE`/`RESUME_AT_ERROR_CODE_PREFIX`;#318 件④ 补 `delegation.*` 族四位 + `config.delegation_entry_caps`;0.39.0 补三新码消费件三位)、`src/classifierVerdictWire.ts`、`src/compensations.ts`、`src/clientSlice.ts` |
150
153
 
151
154
  🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
@@ -251,14 +254,21 @@ taskId · name · usage · model · currentAction · workflowRunId · workflowAg
251
254
  `prepared.model.id`;server `trace/project.js` 的 `taskProgressEventData` 里条件 spread:
252
255
  `string` 且非空才发)。本层同条件透传 —— **空串/缺席一律整键不铸**(空串既不是模型 id 也不是
253
256
  「不知道」)。补它之前是 `workflowRunId` 的**同形第二例**:上游真发、白名单剥掉、两边代码看着都对。
254
- ⚠️ sdk **7.3.0** 的 `task_progress` 臂**尚未声明**这一位(与 `requiresRealApproval`/`ruleOffers`
255
- 同形:server 已真发、SDK 锚未跟),故本层是**结构视图读**;SDK 补上当天那处 cast 可整条删。
257
+ **SDK 锚已追平,cast 已退(0.48.0)**:0.47.0 这里写的是「sdk 7.3.0 的 `task_progress` 臂尚未
258
+ 声明这一位,故本层是**结构视图读**;SDK 补上当天那处 cast 可整条删」—— sdk **7.4.0 已声明**
259
+ `model?: string`,peer 地板同批抬到 `>=7.4.0` ⇒ 按那条退役条件兑现,改类型面直读。
260
+ 🔴 **退役的是 cast,不是运行期判**:`typeof` 门保留(旧 server 缺席 ⇒ 键不 stamp;wire 是 JSON,
261
+ 类型声明是上游承诺、不是本层前提)。端侧**行为逐字节不变**。
256
262
 
257
263
  **仍然被本层剥掉的五键**(`seq` / `taskType` / `parentTaskId` / `status` / `eventId`):
258
264
  - `taskType` / `status` / `parentTaskId` —— 在册的 §7 缺口 **P-1**;lane 归属今天改用 id 形状 /
259
265
  `workflowRunId` 启发式判(`src/adapt/arms.ts`)。
260
266
  - `seq` —— core #258 的 stop-cycle 代际号(复活即 +1)。fleet 面已有同轴的 `wireCycleSeq`(0.38.0),
261
- **tick 这条腿今天没有消费方**;SDK 7.3.0 连声明都没有(core/server 两侧都有)。
267
+ **tick 这条腿今天没有消费方** 照旧剥。
268
+ ⚠️ **就地订正(0.48.0)**:本条 0.47.0 的原文还写着「SDK 7.3.0 连声明都没有」—— sdk **7.4.0 已
269
+ 声明** `seq?: number`(与 `model` 同批)。**剥它的理由换了一条,但仍然剥**:准入条件从来是
270
+ 「说得出谁读它、读来干什么」,SDK 有没有声明只是当时顺带成立的第二个事实。
271
+ 🔴 **声明到货不是透传的理由** —— 否则这张白名单会随上游类型面自动变宽,准入条件形同虚设。
262
272
  - `eventId` —— EventIdentity 的另一半;本臂只补了 `parentToolCallId`(lane 判据要它),`eventId` 至今无消费方。
263
273
 
264
274
  🔴 **透一位的前置条件**(键账的维护规矩,不是修辞):说得出**谁读它、读来干什么**,并同批更新
@@ -338,17 +348,83 @@ in-process ask 拒绝时**在场。
338
348
  5. 🔴 **缺席不可反推** —— server 对非白名单码 / 缺 `sessionId` 的通告**如实不投**(宁缺席不串台),
339
349
  全族那一份只在 server 的运维日志里。「没收到通告」**不等于**「没发生」。
340
350
 
341
- ⚠️ **两条如实登记(2026-08-21 亲验)**:
342
- - **SDK 类型面还没到货**:`engine_notice` 尚未进已发布 SDK 的 `AgentEvent` union
343
- (sdk 仓 `3d6aebc` 已写,但 npm `@sema-agent/sdk@7.2.0` 的真 tarball `dist/` 全树零命中)。
344
- 故本包按 `workflow_complete` / `human_input` 当年的先例走 **raw 预分派**,并留了自退休钉:
345
- 臂一进 union,`assertNeverArm` 就编译期真红,逼下一棒把它搬进 switch。
351
+ #### 3f-2. 🆕 server 7.54.0 两个新码(0.48.0;`task.halt_unconsumed` / `task.late_approval`)
352
+
353
+ server 7.54.0`ENGINE_NOTICE_WIRE_CODES` 增至 **14 码**,新增两个 **task 域**的码
354
+ ([5899] / [5906];真源 = server `dist/trace/engine-notice-wire.js` 的常量数组直证):
355
+
356
+ | code | 语义(server 侧铸文;本包不改写一个字) |
357
+ |---|---|
358
+ | `task.halt_unconsumed` | 一次 halt 请求没有被消费掉(停止动词落在了「没有在飞的那一轮」那一格) |
359
+ | `task.late_approval` | 一次审批**迟到**了 —— 决断到达时它要结算的那只 ask 已经不在等了 |
360
+
361
+ 🔴 **本包的施工量 = 零,而这不是偷懒**:本臂按**开集**消费(消费纪律第 2 条:一个码都不硬编),
362
+ 所以这两个码**按构造**就到得了端 —— 不需要、也**不应该**为它们加任何识别分支。加一张码白名单
363
+ 才是这条腿唯一会坏的方式(新码当天静默蒸发,而两边代码看着都对)。
364
+ 常驻门 `scripts/run-additive-key-passthrough-test.mjs` **G3 段**把这条构造钉住:两个真码 **+ 一个
365
+ 编造的码**同时过投影,三者都必须原样出臂 —— 编造码那一条是**判别力的来源**(真码可能因为被加进
366
+ 某张白名单而仍然绿,编造码必红)。
367
+
368
+ 🔴 **端的消费点应当落在哪(本节写死,免得三端各找各的)**:
369
+ - 两码都是**会话级披露**,不是转录物 ⇒ 落**通知面 / 状态行**,**不要**合成 transcript 行
370
+ (与 §3f 主段同一条纪律)。
371
+ - `task.late_approval` 的呈现要与**审批卡面**联动而不是并列:它说的是「你刚才那一决断没落到东西
372
+ 上」,端若已经把卡收掉,应当据此把那张卡的终态从「已决断」订正为「未结算」,否则用户看到的是
373
+ 一次并不存在的成功。🔴 **它不是错误**,不要渲成失败态 —— 迟到是时序事实。
374
+ - `task.halt_unconsumed` 对应壳侧 Esc/停止腿(§10 `planInteractiveHalt` 的同一条语义轴):
375
+ 端据它把「已请求停止」的乐观态**收回**,而不是让那一行一直挂着。
376
+ ⚠️ 与 §10 的判定**不互替**:那一条是**发起前**的判定(该发 halt 还是 cancel),本码是**发起后**
377
+ 引擎回报的事实。两者都要,缺任一端都会在某一格谎报。
378
+ - 两码都遵守 §3f 的重放幂等序(`eventId` > `eventSeq` > `code+ts`)。
379
+ - **cli 认领**:壳侧接点在下一批(本批只保证「到得了端」+ 门 + 本节指引)。
380
+
381
+ ⚠️ **一条如实登记的订正(0.48.0)**:
382
+ - ✅ **SDK 类型面已到货,raw 预分派已退役**:0.47.0 这里登记的是「`engine_notice` 尚未进已发布
383
+ SDK 的 `AgentEvent` union(npm 7.2.0 真 tarball 全树零命中),故走 **raw 预分派** + 自退休钉」。
384
+ sdk **7.4.0 已声明该臂**(五键全必填、无 `& EventIdentity`)⇒ 那颗自退休钉**本批真的响了**:
385
+ devDep 抬到 7.4.0 的当拍 `tsc` 就报 `assertNeverArm` 收不下这条臂,逼着把它搬进 `case`。
386
+ 已按原定条款搬迁,**行为一字不改**(投影函数原样复用)。
387
+ 🔴 **投影仍走 raw `Record` 视图,刻意不改吃 SDK 收窄形**:SDK 把五键记成**全必填**,而本层对
388
+ 每一键都做诚实缺席处理(`code` 空 ⇒ malformed;`message`/`detail` 坏 ⇒ 降级但不丢帧;
389
+ `sessionId`/`ts` 非法 ⇒ 不 stamp)—— 这些分支在收窄形上会被类型面判成死码而**静默失效**。
390
+ 必填是 server 的承诺,不是本层的前提。且 `eventId` / `eventSeq` 两个重放身份键**根本不在**
391
+ SDK 臂声明里,收窄形上读它们是编译错。
346
392
  - **附录 D.3 的白名单表已失真**:档里仍写「起步白名单(server 7.36 三码)」,而 server main 的
347
393
  `ENGINE_NOTICE_WIRE_CODES` 已是**六码**(core 5.47/5.48 的 `NOTICE_AUDIENCE` 到货后
348
394
  `memory.hold_opened` / `hold_released` / `hold_disposed` 入册;v7.37.0 tag 上仍是三码 ⇒ 六码随
349
395
  7.38 到)。**对本包与端零影响** —— 正因为消费面按开集写,白名单是 server 的投递判定,不是消费判据。
350
396
  已按接入文档宪法回报 server。
351
397
 
398
+ ### 3g. 🆕 `status`(BrainStatus)臂的两个新键(0.48.0;core 7.0.x #506 ㋑ / server ≥7.53)
399
+
400
+ `RetryStatus` 上新增两个 additive 位。**病形与 §3d 的 `model` 逐字同族**:上游真发、本层闭形白名单
401
+ 剥掉、两边代码看着都对。⚠️ 这条腿上有**两层**白名单(`eventToSdkMessage` 的 `case 'status'` +
402
+ `adapt/arms.ts` 的 `retryStatusArm`),**两层同批修** —— 只修其中一层键仍到不了宿主。
403
+
404
+ | 键 | 语义 | 缺席读法 |
405
+ |---|---|---|
406
+ | `retryAtMs` | 本次退避**预计结束的墙钟时刻**(epoch ms),= 发帧那一刻的 `Date.now() + retryInMs`,**由产生者铸** | 不宣告等待的帧上必缺席(`recovered` / `gave_up` / output-cap 立即重发) |
407
+ | `errorStatus` | **刚刚失败那次尝试**的 HTTP 状态码;CC `system/api_retry.error_status` 是同一个数 | 缺席面**封闭**:传输层失败 / 流中断 / `circuit_open` / 终态帧上恒缺席 —— **禁**渲成 `0` 或「未知错误码」 |
408
+
409
+ 🔴 **`retryAtMs` 在场时端应当拿它渲倒计时,而不是拿 `deadline`**:`deadline` 是**本包**按
410
+ `nowMs + 剩余量`现算的,跨进程跳(core → server → 本包 → 端)的传输耗时已经把它推后了;而 core 对
411
+ >30s 的等待会每 30s **重播一帧并递减**,于是「自己算」的倒计时在每个重播片上**重新起跳**而不是收敛。
412
+ 产生者是唯一说得出那个时刻的人,所以它才铸这一位。
413
+ 🔴 **`deadline` 的语义与字节本批一字未改**(0.29.0 起已发布的行为面):两位**并存**,端自己选
414
+ (在场优先)。换算法 = 一次静默的行为改动,本包不做。常驻门 G4c 段是这条方向钉的反钉。
415
+ 🔴 **时钟域**:`retryAtMs` 是**墙钟**(`Date.now()`),不是单调钟。端不得拿它与自己的单调计时器比;
416
+ 跨机器 / 跨授时校正时按**近似值**处理 —— 权威的**相对**量始终是 `retryInMs`。
417
+ 🔴 **`errorStatus` 不许用来推断该不该重试**:该不该等由 `phase` / `errClass` 两个中性桶说了算;
418
+ 本位是给操作者看的**点名**(「谁失败了」),渲进人话行即可。
419
+
420
+ **端的消费点**:重试覆盖层那一行(`RetryStatus`)。CC parity 形 = 「API Error 529 · Retrying in Ns」。
421
+ **cli 认领**:壳侧渲染在下一批。
422
+ **实现锚**:`src/retryStatus.ts`(`BrainStatusPayload` / `RetryStatus` / `mapBrainStatusToRetry`)+
423
+ `src/adapter/downstream/eventToSdkMessage.ts` 的 `case 'status'` + `src/adapt/arms.ts` 的 `retryStatusArm`。
424
+ **常驻门**:`scripts/run-additive-key-passthrough-test.mjs` **G4 段**(驱**两段合成**的全链,
425
+ 只驱一层会在另一层漏修时假绿)+ `scripts/run-engine-vocab-floor-test.mjs` **G2-c**(键集镜像对
426
+ **实装 core** 逐键等值 —— 引擎再加键,那边先红)。
427
+
352
428
  ### 3z. 🆕 `_sema_collateral_abort` —— 连坐 abort 机读位(0.40.0,#324 / [4907])
353
429
 
354
430
  **病形**:同 turn 多个 call 在飞 + 一个撞 gate ⇒ 引擎对**整批**在飞 call 铸同一串
@@ -876,6 +952,41 @@ decide 今天拿不到这枚 409**。本位是给「注入自有传输层 / 读
876
952
 
877
953
  ---
878
954
 
955
+ ### 4g. 🆕 durable park 行的 bidi 披露位 `hasBidiControls`(0.48.0;S-30①,server ≥7.53 / core 5.60.0 #438)
956
+
957
+ durable 审批行 `PendingCheckpoint.hasBidiControls` 随卡透传到 `ApprovalCardRequest.hasBidiControls`。
958
+ 语义:这条 park 行的**执行载荷**里含至少一个 DIRECTIONAL 格式控制符(Trojan Source —— 人眼读到的
959
+ 顺序 ≠ 真正执行的字节顺序)。
960
+
961
+ 🔴 **与活卡腿的 `inputHasBidi` 刻意分键不合流**(上游把两个名字取得不同,正是为了不让人合并):
962
+
963
+ | | 活卡帧腿 | durable park 行腿 |
964
+ |---|---|---|
965
+ | 卡上的键 | `inputHasBidi`(0.43.0 起) | `hasBidiControls`(**本批**) |
966
+ | 谁算的 | server 对**帧自身序列化后的 args** 现算(E-14) | **core** 在 park mint 时算(`PendingAction.hasBidiControls`),反范式成 durable 列;server `listPending` 读列 `=== 1` 才铸,**不重算** |
967
+
968
+ ⇒ 同一只 ask 的两条腿**在场性可以不一致**,这是设计不是缺陷。端要渲一个徽标的话,读**两位的并**
969
+ 是允许的(那是端的呈现决定),但两位在本层必须**各自到货**。合成一位 = 拿一个量冒充另一个。
970
+
971
+ 🔴 **缺席绝不折成 `false`**(类型是 `true`,与 `governanceForced` / `inputHasBidi` 同族):
972
+ 缺席 = **没检出**(干净 / core 有界扫描没够着 / 列诞生前 park 的老行),端**禁**读成「已确认干净」
973
+ —— 那是对用户下一个证不出的断言。
974
+ 🔴 **披露位,不是清洗位;本包字节零改**:清洗会改掉即将被执行的那串字节(卡上显示的与真跑的不是
975
+ 同一个东西),比不披露更坏。显形(转义 / 高亮 / 加标记)归端。
976
+ 🔴 **永不参与 resume / gate / CAS**(上游同款纪律):展示与分诊用。
977
+
978
+ **上游三条读面与本包的覆盖**(如实记账):
979
+ | 上游读面 | 本包 |
980
+ |---|---|
981
+ | `GET /v1/approvals` 行(`PendingCheckpoint`) | ✅ **原样透传**(`startApprovalsFeed` 把 `list()` 的行原样交给宿主,零重铸)+ 本批补上「行 → 卡」重铸处 |
982
+ | `/v1/approvals/stream` 的 `pending` 帧(`ApprovalStreamEvent`) | ⭕ **零施工(如实记)** —— 本包的 feed 只把 stream 事件当「变了」信号,权威列表**恒来自 `list()`**(见 `approvalsFeed.ts` 头注),所以该帧的键不经本包任何投影 |
983
+ | inbox 行(`InboxRow`) | ⭕ **零施工(如实记)** —— 本包**没有** inbox 投影面(全仓零 `InboxRow` 引用);端若自接 inbox,直接读 SDK 形 |
984
+
985
+ **端的消费点**:审批卡的 bidi 提示 / 徽标。**cli 认领**:壳侧审批卡在下一批接。
986
+ **实现锚**:`src/hitl/toolApprovalWire.ts`(`ApprovalCardRequest.hasBidiControls` + `surfaceFsApprovalAndDecide` 的卡入参)。
987
+ **常驻门**:`scripts/run-durable-card-display-keys-test.mjs` **⑪ 段**(正控 + 缺席 + 非 true 四形负控 +
988
+ 两腿分键反钉)与 **⑦ 段富行键集普查**(上游 additive 加展示键当天红 —— 本批正是被它抓出来的)。
989
+
879
990
  ## §5 能力位 gate 义务与端口缺席语义
880
991
 
881
992
  ### 5a. 端口(`install*`):缺席语义与"漏装会静默坏掉什么"
@@ -1606,3 +1717,79 @@ planInteractiveHalt({ parked, interruptOutcome })
1606
1717
  人话文案归端(别把它当展示串)。
1607
1718
 
1608
1719
  **实现锚**:`src/interactiveHalt.ts`;常驻门 `scripts/run-esc-halt-plan-test.mjs`。
1720
+
1721
+ ## §11 🆕 会话记忆姿态读面(S-53,0.48.0;server ≥7.53 / core 7.0.2 #511 件1)
1722
+
1723
+ `GET /v1/sessions/:id/memory-status` 的**三端公共读面**。本包提供**纯判定 + 薄封装**,
1724
+ IO 归宿主注入(`MemoryStatusClientLike`,与 `HitlClientLike` 同款 duck-type);
1725
+ 本件**一句面向用户的话都不铸** —— 措辞与是否上屏归端。
1726
+
1727
+ ### 11a. 为什么这件在库里(两处判定,三端各写一遍必然各错一遍)
1728
+
1729
+ **① 同 status 不同码。** 本路由的 **404 有两个互不相干的含义**:
1730
+
1731
+ | errorCode | 含义 | 端的处置 |
1732
+ |---|---|---|
1733
+ | `not_found.session` | 会话未知**或非本 principal 所有**(server 反枚举:两者同码同串,判不出更细的,**别猜**) | 会话面报「找不到这条会话」 |
1734
+ | `not_found.route` | 支持区间内 **<7.53 的老 server 没有这条路由**,答的是通用回退 | 按「**面不存在**」降级(与 501 同处置) |
1735
+
1736
+ 🔴 按 **status** 分诊必然把「你的部署没这个面」说成「你这个会话不存在」——
1737
+ 判据只能锚 `errorCode`([anchor-on-the-deciding-quantity])。
1738
+ 🔴 **无码的 404 落 `failed`(如实说判不出),绝不挑一个猜**:两个码的处置相反,猜错任一向都是
1739
+ 一句用户会照着去排错的假话。**501 才允许无码兜底**(本路由两条 501 臂都是「面不在/没开」,无歧义)。
1740
+ 🔴 `capability.*`(换部署形态)与 `feature.*`(叫管理员开开关)**分列不合流** ——
1741
+ SDK 顶注逐字:同为 501 而**处置相反**。
1742
+
1743
+ **② 五键缺席语义逐键不同。** `optOutSource` / `lastCaptureAt` 在**健康会话**上就合法缺席;
1744
+ 零历史会话的真形 = `{captureOptedOut:false, committedCount:0, foldedCount:0}`,**没有任何降级**。
1745
+ ⇒ 把缺席一律读成「没有 / 关着 / 0」就是对用户下一个证不出的断言。
1746
+
1747
+ ### 11b. 端怎么接
1748
+
1749
+ ```ts
1750
+ import { readSessionMemoryStatus, readCaptureOptOut, readLastCapture } from '@sema-agent/client-core'
1751
+
1752
+ const v = await readSessionMemoryStatus(client, engineCapturedSessionId, { signal })
1753
+ switch (v.kind) {
1754
+ case 'ok': /* readCaptureOptOut(v.facts) / readLastCapture(v.facts) */ break
1755
+ case 'unsupported': /* 🔴 别提供这个入口(不是报错,是诚实的能力缺席);reason 分 capability/route/feature */ break
1756
+ case 'not_found': /* 会话未知或非属主 */ break
1757
+ case 'failed': /* 分类不明,如实说;v.error 是原始抛出物 */ break
1758
+ }
1759
+ ```
1760
+
1761
+ 两个**合读器**(缺席语义就藏在这两格里,端**不要**自己读裸键):
1762
+
1763
+ | 读法 | 三态 | 判别材料 |
1764
+ |---|---|---|
1765
+ | `readCaptureOptOut` | `opted_out` / `active` / `indeterminate` | `captureOptedOut` × `optOutSource` **完整真值表**(不是「看布尔位 + 特判 fault」)。契约把两键**成对**定死,只有两个组合有定义:`true`×`"record"` ⇒ `opted_out`;`false`× **缺席** ⇒ `active`;缺席×`"fault"`(记录店失败)⇒ `indeterminate`。🔴 **其余组合在契约上不存在**(`false`×`record` / `true`× 缺席 / `true`×`fault` …),只可能来自版本斜差、畸形 200 体或中间层改写 ⇒ 一律 `indeterminate`。这是**隐私姿态**断言,两个方向都危险:读成 `active` 是向用户断言「你的对话正在被记忆」,读成 `opted_out` 是反向的同一种谎 |
1766
+ | `readLastCapture` | `known` / `none` / `indeterminate` | 🔴 判别材料是**另一键** `committedCount`,不是 `lastCaptureAt` 本身:本键缺席**同时**覆盖「台账不可读」与「真的没有贡献」两形 ⇒ **单读它判不出任何东西**。`committedCount === 0`(台账可读、真零)+ 本键缺席 ⇒ `none`;`committedCount` 缺席 ⇒ `indeterminate`。🔴 `known` 的条件是**合取**(时刻在场 ∧ 台账可读 ∧ `committedCount > 0`):两者本是**同一次台账读**,`{committedCount:0, lastCaptureAt:X}` 这种对不上的形是矛盾 ⇒ `indeterminate`,不产出确定时间 |
1767
+
1768
+ ### 11c. 端必读的三条
1769
+
1770
+ 1. 🔴 **`sessionId` 必须取引擎捕获值**(壳从 wire 上拿到的那个 id),不是宿主自铸/自选的串。
1771
+ server 侧记录与台账按**裸 sessionId** 键控且**活过会话** ⇒ 喂一个**被回收**的 id 会读到
1772
+ **上一代**的计数 / opt-out(元数据,无内容字节;server 侧成文的跨代注意)。
1773
+ 本包对空串**直接落 `failed` 且不发请求** —— 那一发必然是对某个不属于本会话的东西提问。
1774
+ 2. 🔴 **没有能力位**([5785]/[5786] 未定位名):直接调用,**501 就是本部署无此面的诚实答案**。
1775
+ 别为它去探一个不存在的 caps 位。
1776
+ 3. 🔴 **畸形键在本层降缺席、不采信**:降键的后果是两个合读器答 `indeterminate`(「不知道」),
1777
+ 采信坏形的后果是拿它当真值渲。两害相权,如实不知道。
1778
+ ⚠️ 但 **200 体整体非对象 ⇒ `failed`**,不洗成「全键缺席」的假 `ok` —— 那会把一次装配缺陷
1779
+ 渲成一个看起来很诚实的 `indeterminate`。
1780
+ 4. 🔴 **身份绑定:回声的 `sessionId` 必须与请求值逐字相等,否则整只落 `failed`**
1781
+ (`SessionMemoryStatusResponse` 契约上它是必填回显 ⇒ 缺席/非串同样是坏形)。
1782
+ 收下一个不相等的回声 = 把**另一条会话**的记忆元数据(计数 / opt-out 姿态)呈现在当前会话面板上
1783
+ —— 缓存错配、中间层串台、依赖故障都造得出。上游对同一件事的纪律是**宁缺席不串台**,本层照办。
1784
+ 端**不需要**自己再核一遍这一位。
1785
+ 5. 🔴 **「永不抛」对畸形宿主也成立,成功路与失败路都算**:本函数吃的是 duck-typed 注入 client,
1786
+ 它可以回一个带**抛错 getter** 的对象或敌意 `Proxy`,也可以**用**这种对象作拒因。
1787
+ 两条路都设了防:归一化整段在 `try` 内;`classifyMemoryStatusFailure` **自己**取属性时也带保护
1788
+ (它是在 `catch` **块内**被调用的 —— `catch` 里抛出的异常不会再被同一个 `try` 接住,分类器一抛
1789
+ 就会击穿这句承诺)。⇒ 调用点**不需要**给它套 `try`,顶注那句承诺是可依赖的。
1790
+ ⚠️ `classifyMemoryStatusFailure` 单独调用时同样永不抛,且**原抛出物原样带出**(端要看得到真因)。
1791
+
1792
+ **端的消费点**:cli [5902] 研判推荐挂 **`/status` 面**(会话级披露,不是转录物)。
1793
+ **cli 认领**:壳侧接点在下一批。
1794
+ **实现锚**:`src/sessionMemoryStatus.ts`。
1795
+ **常驻门**:`scripts/run-session-memory-status-test.mjs`。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/client-core",
3
- "version": "0.47.0",
3
+ "version": "0.48.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. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "peerDependencies": {
33
33
  "@sema-agent/agent-types": ">=0.2.0",
34
- "@sema-agent/sdk": ">=7.2.0"
34
+ "@sema-agent/sdk": ">=7.4.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@sema-agent/agent-types": "^0.2.0",
38
- "@sema-agent/core": "^5.57.0",
39
- "@sema-agent/sdk": "^7.2.0",
38
+ "@sema-agent/core": "^7.1.0",
39
+ "@sema-agent/sdk": "^7.4.0",
40
40
  "esbuild": "^0.27.4",
41
41
  "typescript": "^6.0.2"
42
42
  }