@sema-agent/client-core 0.51.0 → 0.53.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 +115 -0
- package/README.md +1 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +99 -2
- package/dist/adapter/activeRunSelfHeal.js +381 -104
- package/dist/classifierVerdictWire.d.ts +39 -0
- package/dist/classifierVerdictWire.js +111 -0
- package/dist/hitl/frameRouter.js +32 -3
- package/dist/hitl/gateLedger.d.ts +54 -0
- package/dist/hitl/gateLedger.js +54 -0
- package/dist/hitl/hitlHostSurface.d.ts +29 -2
- package/dist/hooksWireCaps.js +66 -2
- package/docs/INTEGRATION-CLIENTS.md +286 -8
- package/package.json +1 -1
|
@@ -15,15 +15,15 @@
|
|
|
15
15
|
|
|
16
16
|
## §0 版本锚与重扫纪律
|
|
17
17
|
|
|
18
|
-
### 0a. 版本锚(2026-09-
|
|
18
|
+
### 0a. 版本锚(2026-09-04)
|
|
19
19
|
|
|
20
20
|
| 项 | 值 | 真源 |
|
|
21
21
|
|---|---|---|
|
|
22
|
-
| 本包 | `@sema-agent/client-core` **0.
|
|
22
|
+
| 本包 | `@sema-agent/client-core` **0.53.0**(工作树**未发**;npm 最新 = **0.52.0**。L-93 那一批进 `CHANGELOG.md` 的 `## 0.53.0(未发布)` 段,冻结账已按两阶段协议插 `pending` 行) | `package.json` `version` |
|
|
23
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
|
-
| 公开导出面 | **
|
|
26
|
+
| 公开导出面 | **805** 个运行期符号(+ 41 个测试钩;= 工作树当下的值 —— 已发的 `0.51.0` 是 **803**,再加 L-69⑨ 两件未发 additive 导出;`0.49.0` 是 **795**,再加 S-81 五件未发 additive 导出;`0.48.0` 是 **794**,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
|
|
|
@@ -113,7 +113,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
113
113
|
|
|
114
114
|
## §2 公共导出面地图(按域)
|
|
115
115
|
|
|
116
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
116
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**805** 项)。
|
|
117
117
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
118
118
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
119
119
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -123,7 +123,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
123
123
|
|
|
124
124
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
125
125
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
126
|
-
实测:
|
|
126
|
+
实测:805 项 **100% 是运行期导出,零 type-only**。
|
|
127
127
|
|
|
128
128
|
**推论(端必须知道)**:
|
|
129
129
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -137,12 +137,12 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
137
137
|
`WorkflowsGateUnknownDenial` 四形**不在**基线里,`src/selfOrchestrationDenial.ts` 对基线贡献
|
|
138
138
|
**4** 项运行期导出(三个函数 + `SELF_ORCHESTRATION_RETRY_WITHOUT`)。
|
|
139
139
|
|
|
140
|
-
|
|
140
|
+
805 项的内部构成(帮助端估读表大小):**238** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
141
141
|
(矩阵、键集、env 名、锚串)而非可调用物;**5** 项是 PascalCase 运行期值
|
|
142
142
|
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
|
|
143
143
|
**41** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
144
144
|
|
|
145
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
145
|
+
### 2b. 域图(16 域,逐域计数之和 = 805)
|
|
146
146
|
|
|
147
147
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
148
148
|
|---|---|---|---|---|---|
|
|
@@ -161,7 +161,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
161
161
|
| 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 桌面壳里的符号只会零命中,而零命中在这里**既不证真也不证伪**。复核这些断言**不得**拿本仓语料当反证 |
|
|
162
162
|
| 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` |
|
|
163
163
|
| 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` |
|
|
164
|
-
| 16 | **引擎词汇表与包自检** |
|
|
164
|
+
| 16 | **引擎词汇表与包自检** | 51 | `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 铸点)· `CAPABILITY_SELF_ORCHESTRATION_REQUIRED`(S-81,server 7.57.0:提交面的 selfOrchestration 准入拒绝码。🔴 **复用码** —— 与其它 `capability.*` 501 同体形而处置不同,消费点必须按**恰等**判、绝不放宽成前缀判;判型与「去键重发一次」归 `src/selfOrchestrationDenial.ts`,详见 §13) | 三端分臂共用的**去字面化** `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 补三新码消费件三位);S-81 补 `capability.self_orchestration_required` 一位)、`src/classifierVerdictWire.ts`、`src/compensations.ts`、`src/clientSlice.ts` |
|
|
165
165
|
|
|
166
166
|
🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
|
|
167
167
|
回答的是「我认不认得这个码」,**绝不是**「合法码只有这些」。消费点 `switch` **必须留 `default`**,
|
|
@@ -1481,6 +1481,8 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
1481
1481
|
| **P-41** | low | 🆕 **decide 的 `currentPending` 指路键今天在标准 SDK 路径上恒缺席**(0.47.0 件②,如实登记的射程边界不是缺陷):server 的 409 `approval_stale` 臂**只在调用方回显 `checkpointToken` 时触发**(engine 7.52.1 `http/server.js` 的 `resumeCheckpoint`,`if (binding?.checkpointToken && …)` 真字节),而 sdk 的 `ApprovalDecision` 自 1.0.0 起**刻意删掉**了那一位 ⇒ 经 SDK client 的 decide 拿不到这枚 409。另:ask 腿(`GateOutcome.currentPending`)**包内无消费方** —— `GateOutcome` 是包内型、决断结局不出包;它在场的理由是**同形存量清剿**(两条 decide 失败腿一次改齐) | `src/hitl/hitlBridge.ts`(`readDecideCurrentPending`)、`src/hitl/toolApprovalWire.ts`、`src/hitl/parkResolver.ts` | 按 §4f 接:读得到就一跳重定位,**缺席时退回重拉 `GET /v1/approvals`**,绝不把缺席读成「没有别的 pending 了」。自己注入传输层(非 SDK client)的宿主今天就拿得到 |
|
|
1482
1482
|
|
|
1483
1483
|
| **P-43** | med(**存量、非本批引入** —— 0.42.0 基线上逐字相同) @cli @web @desktop | 🆕 **accept-session 回退臂的错误分类比它自己的注释宽**(#363 异源复审 [high] 的**未收窄那一半**,如实登记):`allowSession` 腿先发 `approve + remember:'session'`,失败时回退一发纯 approve;那条 catch 的注释写的是「**只**兜『老 server 不识别 remember ⇒ 400 未知键』这一形」,而实际形是 **catch-all 减去三条具名再抛**(`HitlSafetyError` / `DecideTransportRetryExhaustedError` / 0.47.0 新加的『拒体带 `currentPending`』)。⇒ 一个 **404 / 5xx / 宿主自抛的无 status 错误**今天仍会被当成「老 server 不识别 remember」并**自动重发**一次纯 approve。收窄成「只认 400」是**行为改动**,不属于 0.47.0 这个 additive 批的射程 | `src/hitl/toolApprovalWire.ts`(`case 'allow'` 的内层 catch) | 端今天不需要做什么(两发都是 approve,不构成跨门的 double-act);**属主批**:下一个愿意改老引擎兼容腿宽度的批把它收窄成精确的 legacy-400,并同批给回退臂补正控/负控 |
|
|
1484
|
+
| **P-44** | 已知缺口(0.53.0 登记;正位解在**引擎侧**)@cli | 🆕 **L-93 两选卡的 cancel 是「查了再做」,不是原子条件取消**(异源对抗复审 R2 [high] 如实登记):卡后那一发 `runs.get` 状态复证 + 开枪前那一发 `listOwnedPendingApprovals` 待决行复证,把窗口从「人看卡的任意长时间」压到「两发复证到一发 cancel」的毫秒级,**但没有关死** —— 待决行若恰在这两步之间恢复(store 恢复 / 会话重新附着),那一枪仍会落在一条**其实还能被决断**的 run 上,而 `runs.cancel` 会把它终态化。**客户端关不死它**:真正的关法是引擎侧的**条件取消**(带审批快照版本 / checkpoint 标识,条件变了回 409 且不取消),那是 wire 能力,不是壳能自造的语义 | `src/adapter/activeRunSelfHeal.ts`(`staleParkArm` 开枪前那一段的头注);§16b 四道防线 | 端照旧只在用户显式选「停掉它」时才走这条路,**卡面必须说清这是放弃这条 run**;要彻底关死,按跨仓宪法向引擎提**条件取消**(`If-Match` 形)。在那之前这条路的剩余风险是**在册**的,不是未知的 |
|
|
1485
|
+
| **P-45** | 在册局限(**存量族**,非本批引入)@cli @web @desktop | 🆕 **「run 读到终态」不等于「claim 已经落地释放」**(同上 R2 [medium]):`waitForClaimRelease` 的判据是 `runs.get` 读回 {@link CLAIM_RELEASED_STATES} 里的终态词,而本档 §12 与 `running-settled` 臂自己记着:409 报的 claim 与 poll 口径**可以短暂相左**。⇒ `running-cancelled`(0.37.0 起)与 🆕 `stale-park-cancelled`(0.53.0)两条 `resending` 结局,理论上都可能在 claim 尚未落地释放时重发一次,代价 = **再吃一个 409**(自愈树当拍再分诊)。**为什么不单修新那一条**:两条问的是同一个问题,给它们两把不同的尺 = 同一判据两份实现,必漂;正位解是一条 **session/claim 专用读面**(今天 `DurableRunVerbs` 里没有这个动词) | `src/adapter/activeRunSelfHeal.ts`(`CLAIM_RELEASED_STATES` 头注 / `waitForClaimRelease` / 两条 `resending` 结局) | 端**不要**把 `resending` 读成「会话一定空了」——它的成文含义是「引擎报那条 run 已终结」。重发撞回 409 是**已知可能**,自愈树会当拍再分诊(不是新 bug);要更强的保证,得等引擎给 claim 读面。🔴 **文案面同形存量**:0.53.0 的 `stale-park-cancelled` 行已按这条边界改写(只说「引擎报它已终结」+ 「可能要再发一次」),而 0.37.0 的 `running-cancelled` 行仍写着「The engine confirmed it is no longer holding this session」—— 同一把尺、两种说法。**属主批**:下一个愿意动那条既有产品文案的批同步收紧(本批不动,因为它是三端在跑的现产品串,改它属行为面/文案面变更,要单独走验收与端对表) |
|
|
1484
1486
|
|
|
1485
1487
|
### 7c. 多会话(sessionKey)面在册局限 —— 多会话端**接之前必读**
|
|
1486
1488
|
|
|
@@ -1519,6 +1521,8 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
1519
1521
|
|
|
1520
1522
|
| **P-42** | low @cli @web @desktop | 🆕 **Esc halt 只上收了判定,发射面仍在各端**(0.47.0 件③,刻意的分工不是半成品):`planInteractiveHalt` 给判据与升级码闭集,`POST /v1/runs/:id/interrupt` 的**发射**(以及 `?session=` 供给、超时窗、台账、留痕)仍归端。cli 侧那条「裸 fetch 直拨 interrupt」的网络面豁免,**退役条件就是端接上这个口子**(壳换装不在 0.47.0 批内) | `src/interactiveHalt.ts`;§10 | 按 §10b 的分支表接:判定用本包,发射用端自己的传输腿;🔴 halt 必须排在撕 SSE **之前**(§10c 第 1 条) |
|
|
1521
1523
|
|
|
1524
|
+
| **P-43** | med(治理面)@cli @web @desktop | 🆕 **`hooksForWire()` 只守住了本地执行器四条治理腿里的三条**(0.52.0 L-67④ 补了第三条,第四条**在册未修**):cli 仓那份本地执行器(`utils/hooks/hooksConfigSnapshot.ts` 的 `getHooksFromAllowedSources`,**cli 仓坐标不是本仓坐标**)的第四条腿 = `disableAllHooks` 出现在**非** managed 来源(user/project/local)时按 CC 语义降级成「只跑 managed hooks」。本包今天**只读** `policySettings.disableAllHooks` ⇒ 该形在引擎腿上**不成立**(用户把自己的 hooks 关了,引擎照投照跑)。**为什么不做单边近似**:cli 那条腿读的是**合并后**的标量(四源按 policy→user→project→local 后写覆盖前写),而本包 `SettingsPort` 只有 per-source 读口 —— 拿「任一来源为 true」去近似会在「user 写 `true`、local 写 `false`」这一形上**判反**(cli 那边是**不**限制)。忠实复刻要给 `SettingsPort` 加一个合并读口 = **公面改动**(端要跟车实现),属另一批 | `src/hooksWireCaps.ts`(头注「在册缺口」段);§15b/§15d | 端**不要**假定「用户 settings 里的 `disableAllHooks` 会挡住引擎腿的 hooks」——今天只有 **managed(policySettings)** 那一份挡得住。要它落地按 [C162] 令④ 回 C 板提(正位解在包侧:`SettingsPort` 补合并读口 + 本函数补第四条腿) |
|
|
1525
|
+
|
|
1522
1526
|
### 7e. 缺口的共同形状(值得单独说)
|
|
1523
1527
|
|
|
1524
1528
|
**P-1 / P-2 / P-3 / P-4 / P-5 / P-6 / P-7 是同一类**:上游(server / SDK)已经把材料铸到 wire 上了,
|
|
@@ -2314,3 +2318,277 @@ else {
|
|
|
2314
2318
|
最后一轮的 errorCode 带进终帧;为什么该副本工具不可达是引擎的事。plan_review 腿无「无卡直决」形(规则不决 plan),
|
|
2315
2319
|
`planReviewArm` 不加此臂。
|
|
2316
2320
|
|
|
2321
|
+
---
|
|
2322
|
+
|
|
2323
|
+
## §15 🆕 分类器拒绝行的现场 + hooks 的第三道 managed 治理门(0.52.0;L-69⑨ / L-67④)
|
|
2324
|
+
|
|
2325
|
+
### 15a. 件A:`surfaceClassifierDeny` 的 additive 第三参(端怎么接)
|
|
2326
|
+
|
|
2327
|
+
**修前**:引擎侧 auto-mode 分类器 block 掉一次工具调用时,`frameRouter` 的 `classifier-deny` 臂只把
|
|
2328
|
+
`toolName` 与裁决 verdict 交给宿主 ⇒ 端的「最近被拒」面板三行同名(三条被拒的 `Bash` 长一个样),
|
|
2329
|
+
重试也只能落到「整个 Bash 工具」这个粒度。根因在台账:`gateLedger` 此前**只在 gated `tool_start`**
|
|
2330
|
+
留 args,而分类器拒绝的工具通常**没被 gate**(引擎侧分类器直接 block,一张 `tool_approval` 帧都不出)。
|
|
2331
|
+
|
|
2332
|
+
**修后**(全部 additive):
|
|
2333
|
+
|
|
2334
|
+
```ts
|
|
2335
|
+
export interface ClassifierDenyContext {
|
|
2336
|
+
/** 这一只被拒调用的 wire `toolCallId`(retry 粒度的锚;帧上取,恒在场)。 */
|
|
2337
|
+
toolCallId: string
|
|
2338
|
+
/** 这一只调用的 `tool_start` 入参快照;**UNTRUSTED**,缺席 = 台账里没有。 */
|
|
2339
|
+
args?: unknown
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
interface HitlHostSurface {
|
|
2343
|
+
surfaceClassifierDeny(toolName: string, verdict: ClassifierDenyVerdict, ctx?: ClassifierDenyContext): void
|
|
2344
|
+
}
|
|
2345
|
+
```
|
|
2346
|
+
|
|
2347
|
+
端侧接法(两步):
|
|
2348
|
+
|
|
2349
|
+
```ts
|
|
2350
|
+
import { installHitlHostSurface, classifierDenyDisplay } from '@sema-agent/client-core'
|
|
2351
|
+
|
|
2352
|
+
installHitlHostSurface({
|
|
2353
|
+
showNotice, clearNoticeIfCurrent,
|
|
2354
|
+
surfaceClassifierDeny(toolName, verdict, ctx) {
|
|
2355
|
+
// ① 行文案:判定走库(三端同形),**展示消毒走端**。
|
|
2356
|
+
const display = classifierDenyDisplay(toolName, ctx?.args)
|
|
2357
|
+
recentDenials.push({
|
|
2358
|
+
tool: toolName,
|
|
2359
|
+
// 🔴 端的消毒单源(cli: cleanUntrustedForDisplay);库交出来的是**原文**。
|
|
2360
|
+
display: cleanUntrustedForDisplay(display),
|
|
2361
|
+
// ② retry 粒度:身份锚是 toolCallId,规则怎么铸由端定(见 §15d)。
|
|
2362
|
+
callId: ctx?.toolCallId,
|
|
2363
|
+
reason: verdict.reason,
|
|
2364
|
+
})
|
|
2365
|
+
},
|
|
2366
|
+
})
|
|
2367
|
+
```
|
|
2368
|
+
|
|
2369
|
+
`classifierDenyDisplay(toolName, args)` 的判据(纯函数、零 IO):
|
|
2370
|
+
|
|
2371
|
+
| 工具 | 读的入参键 | 说明 |
|
|
2372
|
+
|---|---|---|
|
|
2373
|
+
| `Bash` | `command` | |
|
|
2374
|
+
| `Read` / `Write` / `Edit` / `MultiEdit` | `file_path` | |
|
|
2375
|
+
| `NotebookEdit` | **`notebook_path`** | 🔴 **不是** `file_path` —— 那是这只工具的入参真形 |
|
|
2376
|
+
| 其余(开集) | —— | 回落工具名 |
|
|
2377
|
+
|
|
2378
|
+
- 取到的值必须是**非空串**(trim 后非空):非串 / 缺席 / 空串 / 纯空白一律回落工具名;
|
|
2379
|
+
- 工具名自身为空 ⇒ `'tool'`;
|
|
2380
|
+
- 工具名归一按「去空白/下划线/连字符 + 小写」(与 `toolNameIsFsWrite` 同姿势),`multi_edit` / `BASH` 都认;
|
|
2381
|
+
- 结果长度上界 `CLASSIFIER_DENY_DISPLAY_MAX`(= 200,导出常量,**别手抄那个数字**),超出截到上界、末位换 `…`。
|
|
2382
|
+
|
|
2383
|
+
### 15b. 件B:`hooksForWire()` 的第三道 managed 治理门(端不用改,但要知道)
|
|
2384
|
+
|
|
2385
|
+
`hooksForWire()` 此前过了两道 managed 治理门(`disableAllHooks` / `allowManagedHooksOnly`),**漏了**
|
|
2386
|
+
`strictPluginOnlyCustomization`。后果不是「少读一层配置」——管理侧把 hooks 面锁成 plugin-only 之后,
|
|
2387
|
+
端的**本地执行器**不再跑 user/project/local 的 hooks,而**引擎腿照投照跑**:同一条禁令只在一半的执行面上
|
|
2388
|
+
成立,而这一半恰好是工具真正执行的那一半。
|
|
2389
|
+
|
|
2390
|
+
修后判据(与 CC / cli 本地执行器 `isRestrictedToPluginOnly('hooks')` 逐条同形):
|
|
2391
|
+
|
|
2392
|
+
| `policySettings.strictPluginOnlyCustomization` | 结论 |
|
|
2393
|
+
|---|---|
|
|
2394
|
+
| `true` | **锁**(该值锁全部四个可定制面,hooks 在内) |
|
|
2395
|
+
| 数组且含 `"hooks"` | **锁** |
|
|
2396
|
+
| 数组不含 `"hooks"`(如 `["agents"]`) | 不锁(锁的是别的面) |
|
|
2397
|
+
| 缺席 / `false` / 串 / 对象 / 数字 / `[]` | **当未设**(不锁) |
|
|
2398
|
+
|
|
2399
|
+
「锁」的处置与 `allowManagedHooksOnly` **逐字相同**:`sources = ['policySettings']`,`/goal` 的用户态
|
|
2400
|
+
Stop overlay 一并不投,`hostLog('debug')` 留一行痕。四道门的**顺序**:信任门(最广)→ `disableAllHooks`
|
|
2401
|
+
(恒赢,连 policy 自己的也不投)→ 本门 / `allowManagedHooksOnly`(两者同场时结论同一个,无先后)。
|
|
2402
|
+
|
|
2403
|
+
🔴 **坏值形不 fail-closed 整条腿**:相邻两道门对 `SettingsPort` **抛出**选 fail-closed(治理策略读不出来 =
|
|
2404
|
+
未知态 ⇒ 当作有限制);本门守的是「读到了、但值是个坏形」—— 值在手里,方向由字段属主(cli `SettingsSchema`
|
|
2405
|
+
对该字段的 `.catch(undefined)`,原文口径是 *degrade to unlocked-for-this-field*)定。两件事别混。
|
|
2406
|
+
|
|
2407
|
+
### 15c. 端必读(三条)
|
|
2408
|
+
|
|
2409
|
+
1. **老宿主零行为差**(件A):`ctx` 是**可选**第三参。desktop / web 现有的两参
|
|
2410
|
+
`surfaceClassifierDeny(toolName, verdict)` 实现**不需要跟车** —— JS 里多传一个实参不影响两参函数,
|
|
2411
|
+
TS 里「参数少的函数可赋给参数多的签名」是语言规则。反过来**不成立**:端一旦读了 `ctx`,就必须按可选处理。
|
|
2412
|
+
2. **`ctx.args` 是 UNTRUSTED,库原样交出**:不渲、不截、不消毒。展示消毒(控制符 / 双向符 / 换行可见化)
|
|
2413
|
+
是**端的单源**(cli `cleanUntrustedForDisplay`)—— 库里消一遍、端再消一遍,两份字符集必然漂。
|
|
2414
|
+
🔴 **`CLASSIFIER_DENY_DISPLAY_MAX` 量的是「交出去的原文长度」,不是端渲出来那一行的最终宽度**:
|
|
2415
|
+
端的消毒会**变长**(200 个 ESC 在库里长度是 200,逐个转成 `\u001B` 之后是 1200)⇒ **端消毒之后
|
|
2416
|
+
必须再按自己的行宽兜一次底**,别把这个常量读成「拿到手就一定不超过 200 个显示格」。
|
|
2417
|
+
库那一侧保证的是两件、只有两件:原文不无限长;截断**不制造**畸形(边界跨 emoji 代理对时少切一个码元,
|
|
2418
|
+
绝不把一个代理对切成半只)。🔴 **输入里本来就有的**孤代理项(`JSON.parse('"\ud800"')` 完全合法 ⇒
|
|
2419
|
+
这种入参真实存在)**原样透出** —— 修好它属展示消毒,单源就是本包导出的 `escapeDisplayControlChars`
|
|
2420
|
+
(`collapseLabel` 的底座),其头注逐字点名这一族。端只要照第 2 条走自己的消毒,这一格就已经守住了。
|
|
2421
|
+
3. **`ctx.args` 会缺席,那是诚实缺席不是 bug**:台账对每张 `tool_start` 留快照,但那张表**按条目数有界**
|
|
2422
|
+
(`START_ARGS_MAX_ENTRIES = 256`,超容量丢最早)且 `tool_end` 收口即释放;durable re-attach 只消费
|
|
2423
|
+
`runs.events` 的一段,被拒 call 的 `tool_start` 完全可能落在本次连接之外。缺席时 `ctx` **只带
|
|
2424
|
+
`toolCallId`、`args` 键不铸** —— 端按「只有工具名」降级渲,**绝不**据此编一个空 args 出来。
|
|
2425
|
+
🔴 **「有界」是条目数,不是字节数**(成文的取舍):表里存的是 `ev.args` 的**引用**不是拷贝,而同一份
|
|
2426
|
+
入参在这一拍照常投影进转录面、本来就活着 ⇒ 表对驻留字节的增量是「一个指针 × 条目数」。单条载荷
|
|
2427
|
+
**没有**字节预算(一次 `Write` 的 `content` 有多大就跟着引用多大一份)——按字节记账要先定义降级形
|
|
2428
|
+
(截断 = 交出一份假入参,不许),属独立一件,在册未做。
|
|
2429
|
+
durable 重放会把 `tool_start` 再送一遍,那是**被淘汰项拿回快照**的机会(库把记账排在渲染去重闸之前),
|
|
2430
|
+
所以端不必自己缓存 args 去补这一格。
|
|
2431
|
+
|
|
2432
|
+
### 15d. 射程边界(别把本节读成比它更强)
|
|
2433
|
+
|
|
2434
|
+
- **retry 粒度不在包内**:库交出的是身份(`ctx.toolCallId`)与行文案判定(`classifierDenyDisplay`)。
|
|
2435
|
+
「重试这一只调用」具体怎么做 —— 铸一条 `Bash(git status:*)` 形的规则?按 callId 重放?只在面板上提示?——
|
|
2436
|
+
是**端侧规则面**的铸法,各端的规则存储与卡面都不同,库不替它们决定,也不提供「按 callId 重试」的动词。
|
|
2437
|
+
- **本包不改模型面**:`ctx` 只走宿主呈现/记账通道;喂回模型的那一份仍由引擎的 wire 承载,一个字节不碰。
|
|
2438
|
+
- **件B 只补第三道门**:cli 本地执行器还有**第四条**腿 —— `disableAllHooks` 出现在**非** managed 来源时
|
|
2439
|
+
降级成「只跑 managed hooks」。本包今天只读 `policySettings.disableAllHooks` ⇒ 该形在引擎腿上**不成立**,
|
|
2440
|
+
这是**在册缺口**(登记在 `src/hooksWireCaps.ts` 头注 + §7d)。不做单边近似的理由:cli 那条腿读的是
|
|
2441
|
+
**合并后**的标量(四源后写覆盖前写),而本包 `SettingsPort` 只有 per-source 读口,拿「任一来源为 true」
|
|
2442
|
+
去近似会在「user 写 true、local 写 false」这一形上判反(cli 那边是**不**限制)。忠实复刻需要给
|
|
2443
|
+
`SettingsPort` 加一个合并读口 = **公面改动**,属另一批。
|
|
2444
|
+
|
|
2445
|
+
**cli / web / desktop 认领**:cli 侧接点(Recent Denials 行 display + retry 粒度)在其下一批(表态制);
|
|
2446
|
+
web / desktop 无需动作(老宿主零行为差),件B 对三端都是治理面收紧、零签名改动。
|
|
2447
|
+
**实现锚**:`src/hitl/gateLedger.ts`(有界在飞表三动词)、`src/hitl/frameRouter.ts`(deny 臂 + 释放时序)、
|
|
2448
|
+
`src/hitl/hitlHostSurface.ts`(`ClassifierDenyContext`)、`src/classifierVerdictWire.ts`
|
|
2449
|
+
(`classifierDenyDisplay` / `CLASSIFIER_DENY_DISPLAY_MAX`)、`src/hooksWireCaps.ts`(第三道门)。
|
|
2450
|
+
**常驻门**:`scripts/run-client-core-pure-test.mjs`(B6 段 L-67④ 16 条 / B7 段 L-69⑨ 31 条)。
|
|
2451
|
+
|
|
2452
|
+
---
|
|
2453
|
+
|
|
2454
|
+
## §16 🆕 陈旧 park:「待决行已不在」的真出路(0.53.0;L-93)
|
|
2455
|
+
|
|
2456
|
+
### 16a. 修的是哪一形(现网 sema-bug7)
|
|
2457
|
+
|
|
2458
|
+
审批卡 park 住一条 run 之后过了很久(实测 27.5h),用户退出 TUI 再 `--resume`。重开链去引擎读
|
|
2459
|
+
待决行时,**读面答了**、而属主待决行**恰 0** —— 行确实没了(引擎重启后 `process_local` 会话店丢行 /
|
|
2460
|
+
审批被引擎 reap),可那条 run 仍是 `suspended`,**仍占着会话 claim**。修前本包对这一形只有一个结局:
|
|
2461
|
+
`ask-reopen-failed` ⇒ 端渲「Your message was NOT sent … 换个新会话」。用户手上一条真出路都没有 ——
|
|
2462
|
+
卡不可能重开(没有行了),run 不会自己结束(park 态不在时间型 reap 射程里),只剩丢掉整段上下文。
|
|
2463
|
+
|
|
2464
|
+
0.53.0 修的**只有这一形**:`reopened:false` **且** 宿主证出 `pendingRowGone: true`。其余全部形态
|
|
2465
|
+
(含宿主没证出这一位的一切情况)行为**逐字节不变**。
|
|
2466
|
+
|
|
2467
|
+
### 16b. 宿主契约:`pendingRowGone` 什么时候才许置
|
|
2468
|
+
|
|
2469
|
+
```ts
|
|
2470
|
+
export type ReopenCardVerdict =
|
|
2471
|
+
| { reopened: false; decidedWithoutCard?: true; pendingRowGone?: true }
|
|
2472
|
+
| { reopened: true; firstSight: boolean; presented?: boolean }
|
|
2473
|
+
```
|
|
2474
|
+
|
|
2475
|
+
🔴 **只在「读面答了 **且** 属主过滤后待决行数恰 0」时置 `true`**,别的一律不置(fail-closed)。
|
|
2476
|
+
这一位不是「我没找到卡」的同义词 —— 它是**正面证据**:引擎回答了,答案是「没有」。
|
|
2477
|
+
|
|
2478
|
+
| 宿主实际情形 | 置 `pendingRowGone`? |
|
|
2479
|
+
|---|---|
|
|
2480
|
+
| 有界窗内 `approvals.list` 答了,属主过滤后行数 = 0 | ✅ 置 |
|
|
2481
|
+
| 读口(`approvals.list` 端口)整个缺席 | ❌ 不置 |
|
|
2482
|
+
| 请求抛错 / 网络失败 / 5xx | ❌ 不置 |
|
|
2483
|
+
| 窗内没答完(超时)/ 调用方中止(Esc) | ❌ 不置 |
|
|
2484
|
+
| 有行,但归属证不出 / 问句缺席 / 行形坏(= 找不到**能用**的那一行) | ❌ 不置 |
|
|
2485
|
+
| 只读了一发就返回、没有等满出生窗 | ❌ 不置 |
|
|
2486
|
+
|
|
2487
|
+
**为什么这条边界是硬约束**:`pendingRowGone` 是本包**唯一**允许对一条 park 态 run 提供 cancel 的前提。
|
|
2488
|
+
park 态的 cancel 语义 = 替用户否掉那个待决项;而「待决项**仍在**」时那正是**已退役**的自动 cancel 臂的
|
|
2489
|
+
循环病根(cancel = deny ⇒ 模型重试 ⇒ 新 run 停在同一道门 ⇒ 再 park)。只有「确已没有任何待决项可被
|
|
2490
|
+
否掉」时,那条循环在结构上不成立。含糊值(`false` / `'true'` / `1` / 缺席)一律不算,判据只认严格 `true`。
|
|
2491
|
+
|
|
2492
|
+
包侧**不只信这一句话**,一共四道防线:
|
|
2493
|
+
|
|
2494
|
+
1. 进臂后用 `runs.get` **一手复核**真态,读回的必须是审批 park 词(`ASK_PARK_STATES` = `suspended`)
|
|
2495
|
+
才可能出卡(running / plan park / 终态 / 读不出的一律走别的臂或退回旧结局);
|
|
2496
|
+
2. 卡上必须**用户逐字选 `cancel`**;
|
|
2497
|
+
3. 🔴 **卡后复证 run 真态**(对抗复审 R3 [high] 采纳):第 1 道防线那一发打在**呈卡之前**,而卡是
|
|
2498
|
+
人在看、可以停留任意久 —— 这中间那条 run 完全可能**恢复运行**(审批在别处被决断 / 会话重新附着 /
|
|
2499
|
+
引擎自愈)。所以用户选 cancel 之后**再打一发** `runs.get`,状态仍是审批 park 词才继续;
|
|
2500
|
+
变成 running / 终态 / 换了门 / 读不出 ⇒ 一律 fail-closed 退回旧结局(**绝不**沿用旧卡的授权去停
|
|
2501
|
+
一条正在干活的 run),404 ⇒ `ask-run-not-found` 如实说;
|
|
2502
|
+
4. 🔴 **开枪前再证一次前提**(对抗复审 R1 [high] 采纳):在离 `runs.cancel` 最近的那一刻,用
|
|
2503
|
+
`deps.listOwnedPendingApprovals` 再问一次属主待决行数,**恰 0 才开枪**;非 0 / 抛错 / 窗尽 /
|
|
2504
|
+
中止 / 读不出一律不开枪(退回 `ask-reopen-failed`)。
|
|
2505
|
+
为什么这一步不可省:`pendingRowGone` 是宿主**读那一刻**的事实,而这张卡是人在看、可以看任意久
|
|
2506
|
+
—— 这中间待决行完全可能重新出现(store 恢复 / 会话重新附着),宿主也可能把「读失败 / 找不到
|
|
2507
|
+
能用的那一行」误报成 gone。而第 1/3 道防线**证不了这件事**:审批还在与审批已丢,`runs.get` 读回
|
|
2508
|
+
的都是 `suspended`。代价不对称 —— cancel 一条**其实还能被决断**的 run,等于用户在一句假前提下
|
|
2509
|
+
把整轮工作丢掉(`runs.cancel` 会把 suspended run 终态化,那一轮不会恢复)。
|
|
2510
|
+
|
|
2511
|
+
第 3、4 两道的调用序被常驻门钉住:**呈卡 → 卡后 get 复证 → 待决行复证 → cancel**,两发复证都紧跟在
|
|
2512
|
+
用户那句授权之后。
|
|
2513
|
+
|
|
2514
|
+
⚠️ 因此 `deps.listOwnedPendingApprovals`(既有键,假死锁防御那一只,带**归属过滤**)是这张卡的
|
|
2515
|
+
**供给前提**:宿主不注入它 ⇒ 两选卡整个不呈,行为退回今天的 `ask-reopen-failed`。这是刻意的
|
|
2516
|
+
fail-closed 形:与其先呈卡、等用户按下去才发现前提证不了,不如干脆不给这条路。
|
|
2517
|
+
|
|
2518
|
+
### 16c. 新宿主钩子:`offerStaleParkChoice`(两选卡)
|
|
2519
|
+
|
|
2520
|
+
```ts
|
|
2521
|
+
export interface StaleParkChoiceRequest {
|
|
2522
|
+
taskId: string
|
|
2523
|
+
/** 引擎报的状态(如实转述)。 */
|
|
2524
|
+
status: string
|
|
2525
|
+
/** 供给位:cancel 动词在场 **且** 有 `get` 能确认 claim 真的释放。 */
|
|
2526
|
+
canCancel: boolean
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2529
|
+
interface ActiveRunSelfHealDeps {
|
|
2530
|
+
offerStaleParkChoice?: (req: StaleParkChoiceRequest) => Promise<'cancel' | 'wait'>
|
|
2531
|
+
}
|
|
2532
|
+
```
|
|
2533
|
+
|
|
2534
|
+
- **缺席 ⇒ 整臂不走**(退回 `ask-reopen-failed`,逐字节零行为差)。老宿主(web / desktop)不传这个键
|
|
2535
|
+
= 今天的行为一字不变。
|
|
2536
|
+
- 卡上**没有 steer**:steer 的语义是「注入正在跑的那一轮」,对一条 park 住的 run 它落 `queued`
|
|
2537
|
+
(排在那个**已经不存在**的 checkpoint 上)—— 递一条注定注入不进去的路是假 affordance。
|
|
2538
|
+
- `canCancel` 的三条供给:`runs.cancel` 在场、`runs.get` 在场(确认释放用)、
|
|
2539
|
+
`deps.listOwnedPendingApprovals` 在场(开枪前复证用)。任一缺席 ⇒ 卡上只剩「什么都不做」= 纯噪音,
|
|
2540
|
+
**整卡不呈**,退回旧结局(所以卡真到宿主手上时 `canCancel` 恒 `true`)。
|
|
2541
|
+
- 🔴 **卡面文案是端的事,但语义由本节钉死**:这张卡上的「停掉它」= `POST /v1/runs/:id/cancel`,
|
|
2542
|
+
按 SDK `CancelAck` 契约 run 随后 settle 成 `failed` + `errorCode: "cancelled"` —— **那一轮的工作
|
|
2543
|
+
不会恢复**。卡面必须让用户看懂这是「放弃这条 run 换回会话」,不是「继续它」。
|
|
2544
|
+
- 🔴 返回值 **fail-closed**:只有**逐字** `'cancel'` 才武装那一枪;`Esc` / 空答 / 表外词 / 抛错一律
|
|
2545
|
+
当「什么都不做」(抛错那次还额外退回旧结局,并且**不登记**——用户没看到卡)。
|
|
2546
|
+
- 呈过一次卡且用户选了「什么都不做」⇒ 同 `(sessionKey, taskId)` 再撞不再整卡重弹,结局带
|
|
2547
|
+
`alreadyOffered: true` 让端降级渲一行;清口 = 既有的 `clearRunningChoiceOffer(taskId, sessionId)`
|
|
2548
|
+
(它**同时**清三选卡与两选卡的登记 —— 宿主「重新打开操作菜单」的语义是「让我重新表态」)。
|
|
2549
|
+
|
|
2550
|
+
### 16d. 新结局与处置表
|
|
2551
|
+
|
|
2552
|
+
| kind | 何时 | `selfHealSubmissionDisposition` | 端要做什么 |
|
|
2553
|
+
|---|---|---|---|
|
|
2554
|
+
| `ask-run-not-found` | 复核 `runs.get` 撞 **404**(幽灵 claim 的 park 半场) | `not-delivered` | 渲行;**不**自动重发(404 = 「不存在」∪「不是你的」不可分辨) |
|
|
2555
|
+
| `stale-park-cancelled` | 用户选 cancel,且引擎**确认**那条 run 不再占会话 | `resending` | 重发被拒的那条消息(与 `running-cancelled` 同一条腿) |
|
|
2556
|
+
| `stale-park-cancel-timeout` | cancel 发了,窗内没等到释放(或用户中止) | `not-delivered` | 渲行;不重发 |
|
|
2557
|
+
| `stale-park-cancel-failed` | 那一枪失败且确认腿也没看到释放(`delivery` 分 `rejected`/`unknown`) | `not-delivered` | 渲行;不重发 |
|
|
2558
|
+
| `stale-park-wait` | 用户选「什么都不做」(带 `alreadyOffered?` 降级位) | `not-delivered` | 渲行(或降级行);不重发 |
|
|
2559
|
+
|
|
2560
|
+
- 复核读回 `running` ⇒ 交给**既有** running 三选卡臂(不新铸分臂,也不打第二发 `runs.get`);
|
|
2561
|
+
- 复核读回 `needs_review`(plan park)⇒ 交给**既有** plan 重开臂 —— 对它 cancel 是替用户把整个 plan
|
|
2562
|
+
丢掉(禁区),而 plan 卡本身是能重开的;
|
|
2563
|
+
- 复核读不出 / 读回表外新词 / 用户中止 ⇒ 一律退回 `ask-reopen-failed`(不确定时不做破坏性动作)。
|
|
2564
|
+
- cancel 那条腿是**复用**的(`cancelAndConfirmRelease`):失败分类(`atMostOnceFailureClass`)与释放
|
|
2565
|
+
判据(`CLAIM_RELEASED_STATES`)与三选卡**同源**,不是第二份实现;开枪前那一发复证走的也是共享叶
|
|
2566
|
+
`readOwnedPendingCount`(与假死锁防御同一只,有界/可回收/中止不采信同律)。
|
|
2567
|
+
- `ask-run-not-found` 的文案**不**断言「那条 run 已经不存在」:该 404 分不出「不存在」与「这个会话
|
|
2568
|
+
读不到它」,行里如实把两种可能都说出来,只对「本层没有可动的东西」下结论。
|
|
2569
|
+
|
|
2570
|
+
### 16e. 射程边界(别把本节读成比它更强)
|
|
2571
|
+
|
|
2572
|
+
- **本批只到包为止**。`pendingRowGone` 由**宿主重开链**置位;cli 半场(`askParkReopen` 置位 + 两选卡
|
|
2573
|
+
UI + live 证据)是另一批,未发之前 cli 的行为与今天相同(键不传 ⇒ 老宿主臂)。
|
|
2574
|
+
- **cancel 一条 suspended run 的引擎侧后果不在本包射程**:run 落终态(SDK `CancelAck` 契约:受理回
|
|
2575
|
+
`cancelling`,随后 run settle 成 `failed` + `errorCode: "cancelled"`),那条 turn 的工作**不会**恢复。
|
|
2576
|
+
卡上必须让用户看懂这是「放弃这条 run 换回会话」,不是「继续它」。
|
|
2577
|
+
- **本包不做自动 cancel**:任何情况下都必须有一次用户的显式选择。没有钩子 = 没有卡 = 没有那一枪。
|
|
2578
|
+
- 🔴 **「零行为差」说的是运行期,不是类型面**(异源对抗复审 [medium] 采纳的措辞订正):
|
|
2579
|
+
`SelfHealOutcome` 是**开放增长**的判别联合,本批加了五个 kind。不传新键的宿主运行期结局与文案
|
|
2580
|
+
逐字节不变,但对 `SelfHealOutcome` 做**穷尽 switch + assertNever** 的消费方在**编译期**要补臂
|
|
2581
|
+
—— 这与 0.37.0(`running-*` 五个)、0.38.0 起历次加 kind 是同一形,不是本批新长出来的义务。
|
|
2582
|
+
端的既有 `default` 分支(本包自己的 `selfHealSubmissionDisposition` 就是这么写的)零改动。
|
|
2583
|
+
- **不承诺「重发一定成功」**:只有 `stale-park-cancelled`(引擎报那条 run 已终结)才判 `resending`;
|
|
2584
|
+
其余形一律 `not-delivered`,端不许自作主张重投。
|
|
2585
|
+
- 🔴 **两条在册边界(第二轮对抗复审登记,详见 §7b)**:**P-44** 开枪前复证是「查了再做」不是原子
|
|
2586
|
+
条件取消(残留毫秒级窗口,正位解在引擎侧条件取消);**P-45** 「读到终态」≠「claim 已落地释放」
|
|
2587
|
+
(存量族,与 `running-cancelled` 共用同一把尺,重发撞回 409 是已知可能)。
|
|
2588
|
+
|
|
2589
|
+
**cli / web / desktop 认领**:cli 侧接点(置位 + 两选卡)在其下一批(表态制);web / desktop **无需动作**
|
|
2590
|
+
(不传新键 ⇒ 逐字节零行为差)。
|
|
2591
|
+
**实现锚**:`src/adapter/activeRunSelfHeal.ts`(`ReopenCardVerdict.pendingRowGone` / `StaleParkChoiceRequest` /
|
|
2592
|
+
`offerStaleParkChoice` / `staleParkArm` / `cancelAndConfirmRelease` / 五个新 kind 的文案臂)。
|
|
2593
|
+
**常驻门**:`scripts/run-selfheal-reopen-test.mjs`(G11 段,含「该位缺席 ⇒ 逐字旧结局且零 cancel」负控)、
|
|
2594
|
+
`scripts/run-terminal-identity-copy-test.mjs`(G2 段:新 kind 的注入形与处置分类)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.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",
|