@sema-agent/client-core 0.42.0 → 0.43.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 +102 -0
- package/README.md +1 -1
- package/dist/adapt/textStream.d.ts +11 -1
- package/dist/adapt/textStream.js +68 -2
- package/dist/adapt/wireShapes.d.ts +8 -0
- package/dist/adapt/wireShapes.js +8 -0
- package/dist/adapt.js +13 -2
- package/dist/hitl/askGateWire.d.ts +1 -0
- package/dist/hitl/askGateWire.js +15 -4
- package/dist/hitl/frameRouter.d.ts +59 -1
- package/dist/hitl/frameRouter.js +107 -3
- package/dist/hitl/gateLedger.d.ts +9 -0
- package/dist/hitl/gateLedger.js +3 -0
- package/dist/hitl/hitlHostSurface.d.ts +50 -0
- package/dist/hitl/hitlHostSurface.js +59 -0
- package/dist/hitl/parkResolver.js +14 -2
- package/dist/hitl/toolApprovalWire.d.ts +320 -25
- package/dist/hitl/toolApprovalWire.js +307 -21
- package/docs/INTEGRATION-CLIENTS.md +216 -16
- package/package.json +1 -1
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| peer:wire 契约 | `@sema-agent/sdk` **>=7.2.0**(value-level,非 type-only) | `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
|
+
| 公开导出面 | **776** 个运行期符号(+ 40 个测试钩;= 未发 `0.43.0` 的值,npm `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
|
|
|
@@ -101,7 +101,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
101
101
|
|
|
102
102
|
## §2 公共导出面地图(按域)
|
|
103
103
|
|
|
104
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
104
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**776** 项)。
|
|
105
105
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
106
106
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
107
107
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -111,7 +111,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
111
111
|
|
|
112
112
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
113
113
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
114
|
-
实测:
|
|
114
|
+
实测:776 项 **100% 是运行期导出,零 type-only**。
|
|
115
115
|
|
|
116
116
|
**推论(端必须知道)**:
|
|
117
117
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -120,18 +120,18 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
120
120
|
端依赖这些类型是合法的,但**不要**拿基线 diff 当"类型面没变"的证据。
|
|
121
121
|
- `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
|
|
122
122
|
|
|
123
|
-
|
|
123
|
+
776 项的内部构成(帮助端估读表大小):**230** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
124
124
|
(矩阵、键集、env 名、锚串)而非可调用物;**5** 项是 PascalCase 运行期值
|
|
125
125
|
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
|
|
126
126
|
**39** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
127
127
|
|
|
128
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
128
|
+
### 2b. 域图(16 域,逐域计数之和 = 776)
|
|
129
129
|
|
|
130
130
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
131
131
|
|---|---|---|---|---|---|
|
|
132
132
|
| 1 | **适配内核(下行主链)** | 33 | `adapt` · `createWireToCcAdapter` · `runStream` · `eventToSdkMessage` · `terminalToSdkResult` · `turnUsageToModelUsage` · `isRunStreamActive` · `ADAPTER_DIVERGENCES` | 引擎 SSE `AgentEvent` → 端要渲的**双面输出**:transcript(`SDKMessage`)+ chrome(瞬态 `ChromeEvent`)。**本包存在的理由** | `src/adapt.ts`、`src/adapt/{arms,wireShapes,panelTasks}.ts`(经 `adapt.ts` 再导出)、`src/adapter/runStream.ts`、`src/adapter/downstream/*`、`src/adapter/types.ts` |
|
|
133
133
|
| 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
|
|
134
|
-
| 3 | **HITL 决断卡链**(§4/§5 主战场) |
|
|
134
|
+
| 3 | **HITL 决断卡链**(§4/§5 主战场) | 130 | `makeHitlCanUseTool` · `HitlBridge` · `findPendingForTask` · `HitlSafetyError` · `bridgeAskUserQuestionGates` · `surfaceToolApprovalFrameAndRespond` / `surfaceFsApprovalAndDecide` · `readToolApprovalRespondAck` · `installApprovalCardPort(For)` · `installHitlHostSurface(For)` · `armPlanReviewApproval` · `reopenPlanReviewCard` · `decidePlanReview` · `startApprovalsFeed` · `pendingRowIsOwnedByThisSession` · `approvalCallKey`/`liveFrameCallKey`/`planReviewQuestionId` · `registerArmedGateFor`/`wasGateArmedFor`/`clearArmedGateFor` · `waitForGateArmed(For)`/`onGateArmed(For)`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive(For)`(A-024.4 plan 呈现分代) · `toolEndOutputText` · `isAskTool` · `waitForParkRowBirth` · `classifyAskParkRows` / `askParkRowArm` / `classifyAskParkChainFailure` · `readDecisionNoteAudit` / `decisionNoteAuditLine` · `resumeRunningOptions` / `resumeChoiceFromLabels` · `persistedRulesLaneAvailable`/`persistedRulesGovernanceAvailable` · `classifyRulesFailure` · `listAllPersistedRules` · `classifySkippedReason` · `readRulePersistOutcome`(#244 F2:persist-ack 读口与 `readToolApprovalRespondAck` 合成一处) · `parseLocalAllowRule`(durable 腿本地落规则窄化骨架,谓词经 `LocalAllowRuleDeps` 注入) · `readToolApprovalRespondRefusal`(#225 件5,0.42.0:respond 抛错的结构化原文读口 —— 三位各自防御读、各自缺席不铸、**三位皆缺席时整只返 `undefined`**;原样交还零加工,UNTRUSTED-for-display)· `installEditedRuleTextPrechecker` / `hasEditedRuleTextPrechecker` / `precheckEditedRuleText`([5076] 转出口,0.42.0:core 5.57.0 `precheckEditedRuleText` 的**端口注入形** —— 类型面 + 注入口 + 诚实缺席读口。🔴 **不是** value 级 re-export,理由见 §7 缺口 **P-34**;未装 ⇒ 返 `undefined`,绝不编一个 `{ok:true}`)· `surfaceRuleArmNotSent` / `RULE_NOT_SENT_WARN_TEXT` + `surfaceRuleArmRejected` / `RULE_NOT_SENT_REJECTED_WARN_TEXT`(#334,0.43.0:人在卡上按下的「不再询问」被整条丢弃时的诚实告知——**两条刻意分开**:前者=**引擎能力位未确认**(换台引擎/等探测就好),后者=**这次选择没过包内表核/互斥核**(表外文本/坏下标/两臂同场,换引擎也不会变) —— 编辑臂 `respondFreeFormRules` 与批臂 `respondBatchRuleOffers` 两条同形存量共用一条,与 `surfaceRememberNotApplied` 同族纪律:决断照送、只是规则没存,静默丢掉用户明确意图 = 让人以为功能坏了)· `DecideTransportRetryExhaustedError`(Inkglow-1085 P0a:decide 出站瞬断重试耗尽的 typed 判别 —— HitlBridge 内建单次退避重试,耗尽走重呈臂不判死 turn;端一般只消费行为,不需要 instanceof) | suspended→decide→resume 环。🔴 **D-1 两元组 verbatim 回显**是字节级断言的安全不变量,端**不许重实现它的任何一段**。🔴 键空间边界(web [C1] d3 拦截):`gateIdentity` 四常量两函数只覆盖 HITL questionId/callKey 空间;seat 的 `TOOL_PERMISSION_REQUEST_ID_DOMAINS`(`plan:` 等)是另一键空间,**两者绝不合并**(合并=座位校验器静默拒全部 plan-review 卡) | `src/hitl/hitlBridge.ts`、`toolApprovalWire.ts`、`askGateWire.ts`、`planReviewWire.ts`、`hitlHostSurface.ts`、`gateIdentity.ts`、`armedGateRegistry.ts`、`parkOwnership.ts`、`parkResolver.ts`、`approvalsFeed.ts`、`frameRouter.ts`(**只挑名导出** `toolEndOutputText`/`ENGINE_ABORT_TOOL_RESULT`/`isAskTool`/`HITL_REJECT_MESSAGE`/`HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE`)、`parkRowBirthWait.ts`、`approvalDecisionNoteAudit.ts`、`askParkRowRouting.ts`、`resumeRunningCard.ts`(#265 上收的判定层)、`persistedRulesWire.ts`、`localAllowRule.ts`(#244 F2 规则侧) |
|
|
135
135
|
| 4 | **子代 wire + 面板侧信道台账** | 82 | `tailEngineSubagent` · `installSubagentActivitySink` · `installSubagentTailMetaSink`(#280 件2:tail meta 帧发布口,`contentFrames` 判别位载体)· `stopEngineTask` + `classifyTaskStopConflict` · `fetchEngineSubagentReport` · `steerEngineSubagent`(0.32.0 未发布 #280 件A:additive 第三参 `childTaskId` —— 端有行上下文时**应当**传,传了就走「台账优先 / 缺席即诚实缺席 + `noteBgOwnerAbsence` 留痕」的 Q3 口径,与 tail·taskOutput·subagentOutput 三腿同姿势、与孪生 resume 腿共用同一个 `resolveOwnerRunId` 判据;**不传**则逐字维持旧行为=回落在飞 run)· `resumeSettledSubagent` + `resolveSubagentResumeContext` + `resolveOwnerRunId` + `classifySubagentResumeFailure` + `subagentResumeAvailable`(#242 批 2 A-028.7:resume 判定半场上收,与 steer 孪生同居;取址三态 = 台账有行用行值 / 指名了行但台账缺席则**诚实缺席绝不回落在飞 run** / 没指名行才回落。出路文案归端)· `recordSubagentOwnerFromProgress` + `getBgParentRunOwner`(A-028.6:「子代 → 宿主 run」**单表**,宿主 run 必须由持 stream-local 值的调用方显式传入,包内绝不从 `activeEngineRunId()` 推断)· `noteBgOwnerAbsence`(#242 批 3 [4000] Q3=B:tail/taskOutput·taskStop/subagentOutput 三腿台账缺席即诚实缺席**绝不回落在飞 run**,缺席 warn 留痕每 (腿,taskId) 一条)· `clearBgTerminalFacts`(#242 批 3 扫码修:复活=新周期,旧周期终态事实作废——fleetLedger 复活两腿按尾段清账,factsAccepted 方向核不再拿上周期终态当先例)· `auditRetainWithoutWake`([4000] Q5:引擎宣示 `subagentResume` + 本端在付 `retainSubagentSessions` + 端未实现 `wakeSubagent` ⇒ 响亮一条;`CLIENT_VERBS.wakeSubagent` 维持 fail-soft)· `subscribeSubagentContent` · `subscribeEngineAgentPanel` · `publishQuestionFrame` / `respondToQuestion` | 驱动与观测委派子代;经 module 级台账喂活体 agent/task 面板。全部**能力位 gate**(§5b) | `src/subagent/*.ts`、`src/subagentContentStore.ts`、`src/engineAgentPanelStore.ts`、`src/engineInlineTaskStats.ts`、`src/engineToolLabelStore.ts`、`src/liveQuestionStore.ts` |
|
|
136
136
|
| 5 | **fleet 投影** | 46 | `createFleetLedger` · `projectTasks` · `projectWorkflows` · `projectFleetAgentRows` · `readEngineActiveBgTasks` · `FLEET_TASK_VIEW_KEYS` · `escapeDisplayControlChars`(不可见字符可见化,行标签/描述消毒的共享底座)· `wireCycleSeq` / `wireRetiredBy`(0.38.0 提货补投的 #261 §2 两位:代际号 = SendMessage 复活即 +1,**缺席 ≠ 第一代**;`retiredBy` 在场 = 这条终态是对账腿从 durable run 行投影出来的、**不是**发布方亲报 —— 幽灵行与正常收尾唯一的 wire 判据。两位都只在场才落键) | 老 `fleetClient` 那一刀的成品:**帧体归库、连接归端** —— 端持 SSE 连接,库做行投影 + 保留台账 | `src/fleet/fleetProjection.ts`、`src/fleet/fleetLedger.ts`、`src/fleetAgentPanelProjection.ts`、`src/fleetTaskDesc.ts` |
|
|
137
137
|
| 6 | **请求装配(上行唯一构造口)** | 8 | `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
|
|
@@ -467,6 +467,8 @@ pure 门 062⑥/⑦):
|
|
|
467
467
|
| `rulePersisted?: boolean`(sdk 6.14.0,#225) | **缺席 ≠ false**(未带 `persistRule` 的回决 / 旧 server 省略);非布尔降缺席 | 决定「规则存没存上」的诚实告知;透传坏形会说反话 |
|
|
468
468
|
| `ruleRefusal?: string`(sdk 6.14.0) | 非串/空串 ⇒ 降缺席 | 规则被拒的归因(如 `rule_not_offered`)原样呈现,不改写 |
|
|
469
469
|
| `noteRecorded?: boolean`(sdk 6.16.0,#229) | **缺席 ≠ false**(未带 `note` 的回决 / 老 server 省略);非布尔降缺席 | 发了 `note` 而 `!== true` ⇒ **debug 留痕即可,不惊动用户**:决断没丢,丢的只是理由的持久档(纯活卡无行可落 / 店抖但裁决照常生效 / 并发同决议先落行) |
|
|
470
|
+
| 🆕 `persistedRule?: string`(server ≥7.44,#340;**sdk 7.2.0 锚尚无** ⇒ 本包 `ToolApprovalRespondAckView` 超集位) | 非串/空串 ⇒ 降缺席;候选臂/批臂/兑付失败/老 server ⇒ 字段省略。🔴 **三道包内相关性闸**(过不了就降缺席 + 响亮留痕):①与 `persistedRules` **同场**(互斥臂的产物)⇒ 两位一起丢;②`rulePersisted !== true` ⇒ 丢(「没存上」却报「存成了什么」自相矛盾);③**本次不是编辑臂**(`persistRuleEdited !== true`)⇒ 丢 —— server `editedArmEcho` 只在编辑臂铸,候选臂上的这一位是一次没发生过的授权 | **编辑臂**兑付成功时规则店里**真正落盘**的规范文本。🔴 **界面要回显的是这一份,不是输入框里那一份** —— core 会规范化拼写(`Bash(adb *)` → `Bash(adb:*)`),回显输入框那份等于告诉人「你存的是 X」而店里是 Y |
|
|
471
|
+
| 🆕 `persistedRules?: readonly string[]`(server ≥7.46.0,#334 design/377;同为超集位) | **整只判形**:非数组 / 空数组 / 含非串或空串成员 ⇒ 整只降缺席(半份清单比没有清单更坏);非批臂/失败/老 server ⇒ 省略。🔴 **同样过三道包内闸**:①与 `persistedRule` 同场 ⇒ 两位一起丢;②`rulePersisted !== true` ⇒ 丢;③**基数相关性** —— 条数必须等于本次所选 batch offer 的成员数(server 契约逐字「全体成员落地 + 展示序 = 成员序」),多报 = 声称存了用户在这只批里**没看见**的规则(授权范围被放大)、少报 = 这份清单不是它自称的「全体」;⚠️ **只核基数不复判文本**(规范化是引擎的活,在这里比对文本就是装第二个判官);⚠️ **在册残余 P-39**:基数抓不到「条数对但内容是另一批规则」—— 端渲成「**引擎报告**落盘的规则」而不是「你刚才选的那批」,正位解候上游补 additive 锚 | **批臂**兑付成功时落地的**全体**成员规范文本,**展示序**(= batch offer 的成员序)。「落地」含 persisted 与 deduped 两种(等价规则已在店 = 同意已生效)。🔴 **复数是契约**:合取批一次授权多条,折成一条或只报第一条会让人以为自己只批了一条 —— 端应逐条回显 |
|
|
470
472
|
|
|
471
473
|
**实现锚**:`src/hitl/toolApprovalWire.ts`(`readToolApprovalRespondAck` / `ToolApprovalFrameOutcome` /
|
|
472
474
|
`surfaceToolApprovalFrameAndRespond` 的相关性门与三处 ack 消费分支)、
|
|
@@ -523,7 +525,9 @@ persistRule !== undefined
|
|
|
523
525
|
|
|
524
526
|
- `=== true` ⇒ 编辑臂两位一起上 wire;
|
|
525
527
|
- 缺席 / `false` ⇒ **`persistRule` 整条不发**(fail-closed 到「不发」侧),决断本身照常送达,
|
|
526
|
-
|
|
528
|
+
行为逐字节 = 0.41.0(⚠️ **0.43.0 措辞订正**:此处原写「老引擎**零受迫**」—— 那是**能力位读数正确**
|
|
529
|
+
这个前提下才成立的话。能力位是 per-baseUrl 缓存、不与收 respond 的那个副本绑定,滚动升级下一个
|
|
530
|
+
陈旧的 `true` 仍可能把新形送到老副本上,见 §7 **P-40**);
|
|
527
531
|
- **候选臂不受本位影响**(它是 0.25.0 起就有的既有通道)。
|
|
528
532
|
|
|
529
533
|
#### (c) 运行期候选表核的**让位**判据(端最容易读错的一格)
|
|
@@ -573,6 +577,175 @@ persistRule !== undefined
|
|
|
573
577
|
**常驻门**:`scripts/run-hitl-gate-honesty-test.mjs` 的 **F12** 三腿(能力位缺席不发 / 表核让位负控 /
|
|
574
578
|
400 原文可达),各带负控。
|
|
575
579
|
|
|
580
|
+
### 4b-3. 🆕 `#334` **BREAKING**:`ruleSuggestions` → `ruleOffers` 判别联合(0.43.0;server ≥7.46.0)
|
|
581
|
+
|
|
582
|
+
上游形见 server `[5223]` / core 5.58.0 design/375。**这是一次换键换形,不是 additive** ——
|
|
583
|
+
`tool_approval` 活卡帧 / `card_json` 呈卡帧 / durable `PendingCheckpoint` **三腿同换**;7.46 引擎上
|
|
584
|
+
旧键 `ruleSuggestions` 在这三腿上**一个都不发**(engine 7.46.0 fixture 直证:同文件 0 命中)。
|
|
585
|
+
|
|
586
|
+
#### (a) 包内**单一形出口**:端只读一个键,不感知上游换代
|
|
587
|
+
|
|
588
|
+
| 面 | 0.42.0 及更早 | 0.43.0 起 |
|
|
589
|
+
|---|---|---|
|
|
590
|
+
| 卡入参(活卡帧腿) | `ApprovalCardRequest.ruleSuggestions?: RuleSuggestion[]` | **`ApprovalCardRequest.ruleOffers?: RuleOffer[]`** |
|
|
591
|
+
| 卡入参(durable 行腿,只读) | `ApprovalCardRequest.ruleSuggestionsReadOnly?: RuleSuggestion[]` | **`ApprovalCardRequest.ruleOffersReadOnly?: RuleOffer[]`** |
|
|
592
|
+
| 卡决断(兑付) | `persistRule` / `persistRuleEdited` | 两位**字节不变** + 新增 **`persistRuleBatchOfferIndex?: number`** |
|
|
593
|
+
|
|
594
|
+
两代 wire 键(新 `ruleOffers` / 旧 `ruleSuggestions`)经**同一把**窄读器归一后落同一个出口:
|
|
595
|
+
**新键优先;新键在场即定局,绝不回落旧键**(一台 7.46 引擎不会同时按两代形铸候选,拿旧键顶上去
|
|
596
|
+
等于把一份异源素材冒充成这次 ask 的候选)。回落只在新键**整个缺席**(= ≤7.45 引擎)时发生,
|
|
597
|
+
旧形逐条归一成 `kind:'single'`。⇒ **混舰队部署上端不需要任何版本分支**。
|
|
598
|
+
|
|
599
|
+
🔴 **`null` 与 `undefined` 同视为「新键缺席」**(明示裁定,不是漏判):7.46 的三腿上旧键一个字都不铸
|
|
600
|
+
⇒ 「新键 null + 旧键有值」这个组合**在真引擎上不可能出现**,回落读到的只会是缺席、没有异源素材可混;
|
|
601
|
+
反过来,把 `null` 判成坏形会让任何「把缺席序列化成 `null`」的中转层(JSON 规范化包装器 / 某些 JSONB
|
|
602
|
+
读面 / mock)**整段打掉所有 ≤7.45 引擎**的「不再询问」档 —— 那正是本批要修的病本身换了个触发条件。
|
|
603
|
+
⚠️ 但「新键**有载体**而读不出」(空数组 / 非数组 / 全条坏形)**不回落** —— 那是「新引擎给了坏素材」,
|
|
604
|
+
拿旧键顶上去才是混编。
|
|
605
|
+
|
|
606
|
+
#### (b) `RuleOffer` 判别联合(端渲染必须分臂)
|
|
607
|
+
|
|
608
|
+
```ts
|
|
609
|
+
// 入参面(帧/durable 行)—— 与上游 core `RuleOffer` 逐字同形,**没有** offerIndex
|
|
610
|
+
type WireRuleOffer =
|
|
611
|
+
| { kind: 'single'; rule: string; match: 'exact'|'prefix'; command: string }
|
|
612
|
+
| { kind: 'batch'; rules: readonly RuleOfferBatchMember[]; uncoveredSegments: number }
|
|
613
|
+
// 出参面(卡入参)—— 归一后,带本包铸的 offerIndex
|
|
614
|
+
type RuleOffer =
|
|
615
|
+
| { kind: 'single'; offerIndex: number; rule: string; match: 'exact'|'prefix'; command: string }
|
|
616
|
+
| { kind: 'batch'; offerIndex: number; rules: RuleOfferBatchMember[]; uncoveredSegments: number }
|
|
617
|
+
// RuleOfferBatchMember = { rule, match, command, segment }(两代/两面共用,成员上没有下标这回事)
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
🔴 **入参型与出参型刻意分开**(异源对抗复审 [medium] 采纳):`ToolApprovalFrame.ruleOffers` 收的是
|
|
621
|
+
`readonly WireRuleOffer[]` —— 把带 `offerIndex` 的归一形写成入参型,等于要求宿主为一个上游根本
|
|
622
|
+
不发的字段负责(真 7.46.0 帧会类型不合,只能强转或伪造一个本该由窄读器按**当前这条腿**算的下标)。
|
|
623
|
+
|
|
624
|
+
- `single` = **整条命令**一条规则(精确拼写,或简单命令的更宽 reviewed 前缀形);
|
|
625
|
+
- `batch` = 复合命令的**合取批**:勾它 = 对 `rules`(1..5 条,段序,按规则文本去重)**一次全说是**,
|
|
626
|
+
**没有成员级选中**(要更窄的答案就选 single,或只批这一次不落规则);
|
|
627
|
+
`uncoveredSegments` = 铸卡时刻的诚实余量披露(兑完这批仍未被任何规则放行的段数;0 = 全覆盖)。
|
|
628
|
+
🔴 它是**对铸卡快照的陈述**,不是长期保证 —— 并发删规则会让它过时,文案别写成「今后再也不会问」。
|
|
629
|
+
- **序即契约**:whole-string exact 的 `single` 在场时恒 index 0、`batch` 至多一条且恒末位;
|
|
630
|
+
core 契约基数 ≤2,server 执法帽 4 ⇒ **端按 ≤4 布局**。
|
|
631
|
+
- `rule` / `command` / `rules[].segment` 全部 **UNTRUSTED-for-display**(`segment` 是**渲染座,永不参与
|
|
632
|
+
裁决**,且是原始 post-rewrite 命令字节 —— 用 core 的 `renderUntrustedCommandText` 展示基线渲)。
|
|
633
|
+
|
|
634
|
+
🔴 **`offerIndex` 是本包铸的座(上游形上没有这一位)**,因为 batch 臂的兑付键就是**原始 wire 下标**:
|
|
635
|
+
本包窄读器**逐条丢坏形**(一条坏 offer 不该让另一条真 offer 消失),压紧就会让「人点的第 k 个」与
|
|
636
|
+
「服务端兑的第 k 个」指向两条不同规则。core 的契约原话:做不到保住原始下标的消费端
|
|
637
|
+
「must suppress its persistence actions entirely (fail toward asking)」。
|
|
638
|
+
🔴 **两条腿的下标语义不同**:
|
|
639
|
+
- **活卡帧腿**(`ruleOffers`):server 同步腿是纯前缀截、零逐条丢弃 ⇒ `offerIndex` **恒等于** core 的
|
|
640
|
+
offer index,**是**合法选择键;
|
|
641
|
+
- **durable 行腿**(`ruleOffersReadOnly`):server `boundedRuleOffers` 已逐条丢弃并压紧过一次 ⇒ 行上的
|
|
642
|
+
下标本就不是 core 的 offer index;这条腿**根本没有兑付口**(`/decide` 体无规则位)⇒ 那一位只是
|
|
643
|
+
展示/对账座,**禁**当选择键。
|
|
644
|
+
|
|
645
|
+
#### (c) 批臂的兑付:`persistRuleBatchOfferIndex` 与**三臂互斥**
|
|
646
|
+
|
|
647
|
+
| 位 | 型 | 语义 |
|
|
648
|
+
|---|---|---|
|
|
649
|
+
| `ApprovalCardAllowDecision.persistRule` | `string`(**字节不变**) | 候选臂:`kind:'single'` offer 的 `rule` **原文** |
|
|
650
|
+
| `ApprovalCardAllowDecision.persistRuleEdited` | `true?`(**字节不变**) | 编辑臂:上面那段是人手打的自由文本(§4b-2) |
|
|
651
|
+
| 🆕 `ApprovalCardAllowDecision.persistRuleBatchOfferIndex` | `number?` | 批臂:选中 batch offer 的 **`offerIndex`**(合取批没有单条文本可抄) |
|
|
652
|
+
| `RespondToolApprovalOpts.persistRuleBatchOfferIndex` | 同上 | 编排层已做表核与互斥剥除后交给注入面的形 |
|
|
653
|
+
|
|
654
|
+
🔴 **注入面的映射义务**(本包只到扁平兄弟位这一格,wire 体由宿主/SDK 铸):
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
persistRuleBatchOfferIndex !== undefined
|
|
658
|
+
? { batchOfferIndex: persistRuleBatchOfferIndex }
|
|
659
|
+
: persistRule !== undefined
|
|
660
|
+
? { rule: persistRule, ...(persistRuleEdited === true ? { edited: true } : {}) }
|
|
661
|
+
: undefined
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
**三位绝不同时上体** —— server 对同场组合响亮 400
|
|
665
|
+
(`persistRule.batchOfferIndex is mutually exclusive with persistRule.rule / persistRule.edited`)
|
|
666
|
+
且**连决断一起拒**。注入面只按上面的优先序取一个,**不要**自己再补 fallback。
|
|
667
|
+
|
|
668
|
+
**包边界已做的两道剥除**(端可以依赖,但仍应自己别乱回):
|
|
669
|
+
|
|
670
|
+
| 情形 | 包的处置 | 理由 |
|
|
671
|
+
|---|---|---|
|
|
672
|
+
| 文本臂与批臂同场 | **整条持久臂丢弃** + 响亮留痕,决断照送 | 两臂说的是两次不同的授权,替人挑一个 = 替人改主意;而让 400 把决断打掉 = 把附带愿望的失败升级成主动作的失败 |
|
|
673
|
+
| 下标越界 / 指向 single / 负数 / 非整数 | **丢批臂键** + 响亮留痕,决断照送 | server 对坏下标响亮 400;一个被 `\|0` 折过的下标指向的是**另一条** offer |
|
|
674
|
+
| deny 决断带任何持久位 | 恒不上 wire | 一次拒绝上不留「像是记住了」的错觉(与 `persistRule` 同纪律) |
|
|
675
|
+
| 文本臂报了 batch **成员**的文本 | 表核不认 ⇒ 丢键 | 合取批只能整只勾,成员文本不是可选中的候选 |
|
|
676
|
+
|
|
677
|
+
🔴 **批臂有独立的能力位闸**(`ToolApprovalFrameLaneOpts.respondBatchRuleOffersCapable`,与
|
|
678
|
+
`note`/自由文本臂**同一条**「位缺席就别发」纪律;宿主从 `capabilities.respondBatchRuleOffers` 填):
|
|
679
|
+
|
|
680
|
+
> ⚠️ **本段是订正**(异源对抗复审二轮 [high]):初稿写的是「批臂**没有**独立能力位闸,供给自闸即可」——
|
|
681
|
+
> 施工时实证推翻:server 7.46.0 的 `capabilities.respondBatchRuleOffers` **确实存在**(engine fixture
|
|
682
|
+
> `dist/http/routes/capabilities.js` 直证)。而且「供给自闸」这个论证本身站不住:**帧上有 batch offer
|
|
683
|
+
> 只证明出帧的那一端认识新形,不证明收 respond 的那个端点认识它** —— 滚动升级 / 多副本 / 代理后面
|
|
684
|
+
> 站着一台 ≤7.45 实例时,旧解析器对 `persistRule.batchOfferIndex` 走的是「`persistRule.rule` 必须是
|
|
685
|
+
> 非空串」那条,**响亮 400 且连决断一起拒**,于是人按下的那次 allow 落不了地、退化成 unresolved/TTL。
|
|
686
|
+
|
|
687
|
+
- `=== true` ⇒ 批臂上 wire;
|
|
688
|
+
- 缺席 / `false` ⇒ **`persistRuleBatchOfferIndex` 整条不发**(fail-closed 到「不发」侧),决断照送;
|
|
689
|
+
- 🔴 **只闸兑付,不闸展示**:batch offer 照旧上卡(`ApprovalCardRequest.ruleOffers` 字节不变),
|
|
690
|
+
端可以渲、可以走**本地落规则**那条路(与 `ruleOffersReadOnly` 同姿势),只是这条 wire 兑付通道不开;
|
|
691
|
+
- 🆕 🔴 **被丢掉的用户意图会经 `HitlHostSurface` 如实上屏** —— **两条,按原因分**:
|
|
692
|
+
`surfaceRuleArmNotSent()` / `RULE_NOT_SENT_WARN_TEXT`(**能力位未确认**)与
|
|
693
|
+
`surfaceRuleArmRejected()` / `RULE_NOT_SENT_REJECTED_WARN_TEXT`(这次选择**没过包内表核/互斥核**:
|
|
694
|
+
表外文本 / 坏下标 / 两臂同场 —— 成因通常是卡口实现的 bug,`hostLog('error')` 那条留痕照旧保留,
|
|
695
|
+
两个受众两条通道)。🔴 **刻意不折成一条**:对用户的下一步建议不同(换台引擎/等探测 vs 换引擎
|
|
696
|
+
也不会变)。以下纪律两条共用。前一条—— 与 `surfaceRememberNotApplied` 同族:人按下的是一个明确
|
|
697
|
+
意图,静默丢掉会让他以为功能坏了。**编辑臂与批臂共用这一条**(同形存量一并清 —— 编辑臂此前也
|
|
698
|
+
只写 debug)。端的义务 = **装 `HitlHostSurface` 口**(§5a),没装 ⇒ 这条告知静默丢失。
|
|
699
|
+
两条口径纪律写在文案里,端改写文案时别丢:
|
|
700
|
+
· **不说「这台引擎不支持」** —— 能力位缺席有三种同形成因(宿主没接这个字段 / 探测还没回来 /
|
|
701
|
+
探测失败),断言引擎的能力是没有证据的话,所以用 `could not be confirmed` 的未知口径;
|
|
702
|
+
· **「审批本身过了」只在 respond 真成功之后才成立** ⇒ 包内这两条通知的发出时机被钉在
|
|
703
|
+
`await respond(...)` **成功之后**(respond 抛错那条路上 `decision:'unresolved'`,决断其实没落定,
|
|
704
|
+
一个字都不发)。
|
|
705
|
+
|
|
706
|
+
🔴 **同一次 respond 的通知顺序契约**(端实现 `showNotice` 时必读):`HitlHostSurface.showNotice` 的
|
|
707
|
+
语义是**直接顶替 current、不排队** ⇒ **最后发的那条才是用户真看得到的那条**。一次 allow 可以同时
|
|
708
|
+
触发三条,包内按**严重度升序**发、最重的压轴:
|
|
709
|
+
|
|
710
|
+
| 顺序 | 通知 | 说的是 |
|
|
711
|
+
|---|---|---|
|
|
712
|
+
| 1(先) | `surfaceRuleArmNotSent` / `surfaceRuleArmRejected` | 这次「不再询问」没存上 —— 下次还会问,**不影响这次跑的是什么** |
|
|
713
|
+
| 2 | `surfaceRememberNotApplied` | `allow_session` 没记住 —— 同族,范围大一点 |
|
|
714
|
+
| 3(压轴) | `surfaceEditNotForwarded` | **工具正在用原始入参跑**(批的那份 ≠ 跑的那份)—— 唯一一条 [high] |
|
|
715
|
+
|
|
716
|
+
⚠️ 端若自己重排/合并这些通知,**必须保住这条不变量**:别用一条「规则没保存」把「你批的和正在跑的
|
|
717
|
+
不是同一个东西」顶掉。端要是实现成**队列**(不顶替),三条都能看见,那更好。
|
|
718
|
+
|
|
719
|
+
#### (d) 🆕 `inputHasBidi`:bidi 控制符**披露位**(#341/[5214]③;server ≥7.46.0)
|
|
720
|
+
|
|
721
|
+
`ToolApprovalFrame.inputHasBidi?: true` → `ApprovalCardRequest.inputHasBidi?: true`(条件 stamp,
|
|
722
|
+
**只认严格 true**)。在场 = 这只 ask 的**工具输入**里含 bidi 控制符(LRM/RLM、嵌入/覆写、隔离符)。
|
|
723
|
+
|
|
724
|
+
- 🔴 **缺席绝不折成 `false`**:缺席同时覆盖「真的没有」与「args 序列化不了(循环引用 / 抛错的
|
|
725
|
+
`toJSON` / BigInt)所以扫不了」两形,读成「已确认干净」= 对用户下一个证不出的断言;
|
|
726
|
+
- 🔴 **披露位不是清洗位;本包字节零改**:审批卡是唯一一处把模型写的命令交给**人眼**判断的面,
|
|
727
|
+
bidi 覆写让人眼读到的顺序与真正执行的字节顺序不同(**人批的是 A、跑的是 B**)。清洗会改掉即将
|
|
728
|
+
被执行的那串字节 —— 卡上显示的与真跑的不是同一个东西,**比不披露更坏**;
|
|
729
|
+
- **端的义务** = 显形(转义 / 高亮 / 加标记,端自己定;壳侧基线 = core 的
|
|
730
|
+
`renderUntrustedCommandText`),**不是**替换字节;
|
|
731
|
+
- **单腿**:活卡帧腿独有,durable 行腿今天无对偶(server 7.46.0 的 `PendingCheckpoint` /
|
|
732
|
+
`riskDescriptor` 都没有这一位)⇒ 与 §7 **P-32** 同族,别按「两面能对上」写码。
|
|
733
|
+
|
|
734
|
+
**实现锚**:`src/hitl/toolApprovalWire.ts`(`RuleOffer` / `RuleOfferBatchMember` /
|
|
735
|
+
`ApprovalCardRequest.ruleOffers`·`ruleOffersReadOnly`·`inputHasBidi` /
|
|
736
|
+
`ApprovalCardAllowDecision.persistRuleBatchOfferIndex` / `RespondToolApprovalOpts.persistRuleBatchOfferIndex` /
|
|
737
|
+
`ToolApprovalRespondAckView` / `readRuleOffers`·`readLegacyRuleSuggestions`·`readOfferSupply` /
|
|
738
|
+
`surfaceToolApprovalFrameAndRespond` 的兑付段 / `surfaceFsApprovalAndDecide` 的行→卡重铸段)。
|
|
739
|
+
**常驻门**:`scripts/run-client-core-pure-test.mjs`(B7 `ruleOffers` ①-⑩ 与 `inputHasBidi` ①-④)、
|
|
740
|
+
`scripts/run-durable-card-display-keys-test.mjs`(③/④/④b/⑤ 只读腿)、
|
|
741
|
+
`scripts/run-approval-frame-keys-test.mjs`(键镜像 24 项 + `AHEAD_OF_ANCHOR` 三条新登记 +
|
|
742
|
+
🆕 **server fixture 腿**)。
|
|
743
|
+
🔴 **fixture 腿要显式开**:`SEMA_CC_SERVER_FIXTURE=<装了 @sema-agent/server 的目录>`。它直接解析
|
|
744
|
+
engine fixture 的 `dist/tool-approval.d.ts`,把 server 真发的帧键集与本仓镜像做**机器对表** ——
|
|
745
|
+
SDK 运行期锚**按构造滞后于 server**,只对 SDK 锚比对的门对「server 已发、SDK 与本仓同时没有」的键
|
|
746
|
+
是**盲的**,而那正是 [5223] 这次静默换键的成因形。缺 env ⇒ 门打印一行**明说没跑**(不是静默略过,
|
|
747
|
+
也不是整门 SKIP:SDK 锚腿仍是真判据)。**提货/抬 pin 批必须带上这个 env 跑一次**。
|
|
748
|
+
|
|
576
749
|
### 4c. `ReopenCardVerdict` 的 `presented` 位契约
|
|
577
750
|
|
|
578
751
|
```ts
|
|
@@ -723,6 +896,8 @@ durable park 腿走 `HitlBridge.decideTool(outcome, toolUseID, opts, preResolved
|
|
|
723
896
|
| `subagentStream` | 子代 live tail | 同上纪律 | tail 整条 `return`(不 fallback 到轮询) | `src/subagent/engineSubagentTail.ts` |
|
|
724
897
|
| `manualCompact` | `runs.compact` 腿 | 包内自探,`probeManualCompactCapability()` 在首个 turn bind 时预热 | `=== false` ⇒ **verb 抑制**(TOC-local runStore-less 引擎)+ 留痕;此前端不消费该门,verb 打过去 501 被 fire-and-forget 吞掉 | `src/subagent/engineCompactWire.ts` |
|
|
725
898
|
| **`approvalDecisionNote`** | live 帧腿的 `note` 上 wire | 🔴 **宿主供给**(不是包自探):`ToolApprovalFrameLaneOpts.approvalDecisionNoteCapable`,端从自己的 caps 缓存填 | 缺席/false ⇒ **fail-closed 到「不发」侧**(决断照常送达,现状字节不变) | `src/hitl/toolApprovalWire.ts`(`ToolApprovalFrameLaneOpts`) |
|
|
899
|
+
| **`respondFreeFormRules`**(server ≥7.44) | live 帧腿的**编辑臂** `persistRule` + `persistRuleEdited` 上 wire | 同上:`ToolApprovalFrameLaneOpts.respondFreeFormRulesCapable` | 缺席/false ⇒ **编辑臂整条不发**(候选臂不受影响;决断照送) | 同上 |
|
|
900
|
+
| 🆕 **`respondBatchRuleOffers`**(server ≥7.46.0) | live 帧腿的**批臂** `persistRuleBatchOfferIndex` 上 wire | 同上:`ToolApprovalFrameLaneOpts.respondBatchRuleOffersCapable`(直证 = engine 7.46.0 fixture `dist/http/routes/capabilities.js` 的 `respondBatchRuleOffers: true`) | 缺席/false ⇒ **批臂整条不发**;🔴 **只闸兑付不闸展示** —— batch offer 照旧上卡,端可渲、可走本地落规则那条路 | 同上 |
|
|
726
901
|
|
|
727
902
|
#### 🔴 `approvalLane` 的供给链(端接 durable/live 审批帧腿时**必接**)
|
|
728
903
|
|
|
@@ -731,15 +906,35 @@ durable park 腿走 `HitlBridge.decideTool(outcome, toolUseID, opts, preResolved
|
|
|
731
906
|
→ AskGateWireDeps.approvalLane: ToolApprovalFrameLaneOpts (src/hitl/frameRouter.ts)
|
|
732
907
|
→ routeFrame 的 tool_approval 臂透传 deps.approvalLane
|
|
733
908
|
→ surfaceToolApprovalFrameAndRespond(…, lane) (src/hitl/toolApprovalWire.ts)
|
|
734
|
-
→ lane.approvalDecisionNoteCapable
|
|
909
|
+
→ lane.approvalDecisionNoteCapable === true 才发 note
|
|
910
|
+
→ lane.respondFreeFormRulesCapable === true 才发**编辑臂** persistRule(+ persistRuleEdited)
|
|
911
|
+
→ lane.respondBatchRuleOffersCapable === true 才发**批臂** persistRuleBatchOfferIndex
|
|
912
|
+
→ lane.windowIsCurrent === true 才把**窗三键**过境到卡(倒计时)
|
|
735
913
|
```
|
|
736
914
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
915
|
+
⚠️ **本节 0.43.0 起是四位,不再是 note-only**(异源对抗复审六轮 [medium] 订正:旧版这段只画了
|
|
916
|
+
`approvalDecisionNoteCapable` 一位,并把缺席后果写成「note 恒不发」—— 宿主照这段接线,会以为
|
|
917
|
+
不填只损失一条备注)。**逐位缺席后果**:
|
|
918
|
+
|
|
919
|
+
| 位 | 缺席 / `false` 的后果 | 用户看得见什么 |
|
|
920
|
+
|---|---|---|
|
|
921
|
+
| `approvalDecisionNoteCapable` | 回决 `note` **整条不发** | 审计面没有拒因;屏上无变化 |
|
|
922
|
+
| `respondFreeFormRulesCapable` | **编辑臂** `persistRule` 整条不发(候选臂不受影响) | 人手改的规则没存上 ⇒ 包发 `surfaceRuleArmNotSent()` 通知 |
|
|
923
|
+
| `respondBatchRuleOffersCapable` | **批臂** `persistRuleBatchOfferIndex` 整条不发(offer 仍上卡) | 勾的合取批没存上 ⇒ 同一条通知 |
|
|
924
|
+
| `windowIsCurrent` | 窗三键**不过境**到卡 | 卡上**没有倒计时**([4845]「卡挂着像活的」) |
|
|
925
|
+
|
|
926
|
+
**缺席一律 fail-closed 到「不发」侧**,决断本身照常送达。0.29.0 发包扫描门修的正是这条链:此前包内
|
|
927
|
+
**唯一生产调用点**(`routeFrame` 的 `tool_approval` 臂)**没有这个位**,于是 `note` 恒不发 ——
|
|
928
|
+
位建好了、腿接好了,生产路径上一个字节都没上 wire。
|
|
929
|
+
|
|
930
|
+
**端的义务**:接审批帧腿时**必须**在 `AskGateWireDeps` 里填 `approvalLane`,且**四位都要填**;
|
|
931
|
+
不填不是「少个功能」,是审计归因整条断掉、两条持久臂静默失效、倒计时不渲,而且**不报错**。
|
|
740
932
|
|
|
741
|
-
|
|
742
|
-
|
|
933
|
+
🔴 **三个能力位的新鲜度纪律**(在册局限 **P-40**,与 §5c 的读法纪律成对):它们是**per-baseUrl**
|
|
934
|
+
的布尔缓存,**不与出这条帧的那个副本、也不与收 respond 的那个副本绑定**。引擎温切 / 滚动升级 /
|
|
935
|
+
多副本代理下,一个 `true` 可能来自 7.46 实例而 respond 落到 ≤7.45 实例。端的义务 = 引擎
|
|
936
|
+
respawn/restart 后调 `invalidateEngineCaps(baseUrl, probe?)`(**推荐两参形**,见 §2b 域 15),
|
|
937
|
+
不要让上一代引擎的能力读数活到下一代。
|
|
743
938
|
|
|
744
939
|
### 5c. caps 缓存的读法纪律
|
|
745
940
|
|
|
@@ -956,7 +1151,7 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
956
1151
|
| **P-4** | med | **`approval_revoke` 在 SDK union 里连成员都没有**(SDK 顶注:known asymmetry,`Registering it is an open item for the next batch`)⇒ 本包不可能有 case ⇒ 运行期落 `dropped('unknown_arm')`;审批链也看不见它(`isToolApprovalFrame` 只认两帧)。全仓 `grep -rn "revoke"` = **0** | `src/adapter/downstream/eventToSdkMessage.ts` 的 `default` 臂;`src/hitl/toolApprovalWire.ts` 的 `isToolApprovalFrame` | 🔴 **引擎撤卡时本包不会替你撤那张卡** —— 被撤的 ask 会一直留在屏上,直到它自己的 5 分钟 TTL / deny 路径触发。要 revoke 语义的端只能自己接 raw named-SSE 腿并撤自己的卡(`unknown_arm` 的 drop 至少留了一行痕) |
|
|
957
1152
|
| **P-5** | 真缺口(未定级) | **durable 重放腿上,一条被重放的 `question` 今天不会再打开覆盖层** —— 覆盖层的入口是 `liveQuestionStore` 的 **live demux 写口**,不是投影函数。交互 REPL 无损,「断线后按 `Last-Event-ID` 续读」场景下是真缺口。补它属**行为面**改动(先要答「重放一条已过 5min TTL 的问题该不该弹窗」),按宪法三问单独走 | `src/adapter/downstream/eventToSdkMessage.ts` 的 `question`/`question_complete`/`elicitation`/`elicitation_complete` 臂(缺口逐字记在该处);`src/liveQuestionStore.ts` 头注(`LIVE-ONLY + SAME-REPLICA … No durable resume anchor`) | `respondToQuestion` 当 best-effort 用(404 = 「已经放掉了」,dismiss,**绝不重试**);重连后的恢复走 **409 自愈树 + 自己的 `/v1/approvals` 列举**,**不要**指望重放的 `question` 帧能弹出覆盖层 |
|
|
958
1153
|
| **P-6** | low | `compaction_outcome`(压缩**非成功结局**:mooted/failed)在本切片 `not_in_slice`;「压缩失败让用户看见」是 chrome/HUD 面的活,**今天两端都还没接**(adapt 臂表同样无此臂) | `src/adapter/downstream/eventToSdkMessage.ts` 的 `case 'compaction_outcome'` | 压缩失败对用户**不可见**。🔴 **不许**由投影切片顺手编一个假 transcript 形来假装接上了 |
|
|
959
|
-
| **P-32** | 在册局限(0.36.0 两 wire 键过境后的**辖域**,非缺陷) @cli @web @desktop | **两键各只有一条腿,别按「两面能对上」写码**。① **`requiresRealApproval`**(#283)
|
|
1154
|
+
| **P-32** | 在册局限(0.36.0 两 wire 键过境后的**辖域**,非缺陷) @cli @web @desktop | **两键各只有一条腿,别按「两面能对上」写码**。① **`requiresRealApproval`**(#283)与 🆕 **`inputHasBidi`**(#341,0.43.0)**同族**:两位都只在**活卡帧**腿(`ToolApprovalFrame` → `ApprovalCardRequest`);**耐久腿零 stamp** —— sdk 7.1.0 / **server 7.46.0 fixture 重验**的`PendingCheckpoint` / `riskDescriptor` 都没有这两位(server 那份在**卡内**:`risk.requiresRealApproval` 是**恒在布尔**、`inputHasBidi` 是卡上的在场位,都不是行上的键),包侧刻意不猜载体名。🔴 **「帧上没有」与「卡上是 false」是两条不同的陈述,别互相推导**(server `ApprovalCardSchema` 顶注逐字)。② **`checkpointId`**(#285 件2):只在 **409 `conflict.session_active_run` 体 + 其 SSE done 帧**的 `pendingGate`;另外三条 park 读面(`/v1/assistant/inbox`、`GET /v1/approvals`、`/v1/approvals/stream`)**都没有**这一格(后两条要给 checkpoint 表反范式一列 = SQL 面双库门,属另一批) | `src/hitl/toolApprovalWire.ts`(帧腿 stamp / durable 腿刻意零 stamp,两处 JSDoc 逐条写明理由)· `src/adapter/runStream.ts`(`ActiveRunPendingGate.checkpointId` + `pendingGateIsProvablyDifferent`)· 行为钉:`run-durable-card-display-keys-test.mjs` ⑨ 段(四种最像的载体名摆在行上,卡入参一个都不许长出来)· `run-selfheal-reopen-test.mjs` G1/G1b | ① **`requiresRealApproval` 在场 ⇒ 一切自动放行让位**(记住的规则 / `allow_session` / bypass 姿态),**缺席绝不读成 `false`**(缺席 = 不是安全类 ask **或** 老 server,两者同形不猜);durable 卡上今天**恒缺席**,别据此认为「durable 门都不是安全类」。② **`checkpointId` 只做两件事**:落日志/排障关联,以及经 `pendingGateIsProvablyDifferent(prev, next)` 做**单向**判断「id 变了 ⇒ 不是刚才那一行」。🔴 **`false` 是「证不出」不是「同一道门」**,🔴 **绝不**当跨调用去重键(server 选行是**无序 `LIMIT 1`**、同 session 可并存多条 pending 行 ⇒ 同一情形连续两次 409 可能报不同 id;也可能 id 不变而 `activeTaskId` 已换),🔴 **绝不**拿它去 join durable 队列行(必然落空,且落空与 legacy 行同形)。缺席是**三成因合流**(legacy 行 / 值没过 server 信任边界校验 / 整只材料读取失败),读作「不知道这道门叫什么」 |
|
|
960
1155
|
|
|
961
1156
|
### 7b. 决断链缺口
|
|
962
1157
|
|
|
@@ -968,6 +1163,11 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
968
1163
|
|
|
969
1164
|
| **P-33** | low | **`CANCEL_DENY_BUDGET_MS` 的论证前提已作废,数值未动**(0.42.0 §2.4 撤稿件的如实残余):那 2s 预算的原理由是「DENY-abort 语义上引擎收到即终结 run」,而 server `ASSISTANT-WIRE-CONTRACT.md` §4a 逐字反对(**DENY 是 TOOL 级应答,永远不是 run kill**)。于是「decide 慢」与「deny 丢了」这两件事在 2s 这个刻度上**不可分**,晚到的成功也不会撤回那行 warn(只有 10s 自清) | `src/hitl/hitlHostSurface.ts`(`CANCEL_DENY_BUDGET_MS` 头注的重审段 + `observeCancelByDeny`) | 端**不要**把 `CANCEL_DENY_WARN_TEXT` 那行当成「会话一定锁死了」的判据——它今天只证明「2s 内没收到 settle」。根治要做成两档(软档只记 debug、硬档才上屏),那是**跨仓一批**(包侧改时序 + 壳侧同批换判据与用例),本批**刻意不做单边改动** |
|
|
970
1165
|
| **P-35** | low | **`WIRE_NETWORK_ERROR_PATTERN` 与 web 的 `NETWORK_PATTERNS` 存在真实的**大小写敏感度分叉**(0.42.0 [C195] 行为对拍腿实测):本包整条基表带 `i` flag,而 web 的 errno 类 token(`ECONNREFUSED` / `ECONNRESET` / `ETIMEDOUT` / `EAI_AGAIN` / `ENOTFOUND` / `EHOSTUNREACH` / `ENETUNREACH` / `EPIPE` / `UND_ERR`)**逐条无 `i`** ⇒ 同一条小写 errno 文本,本包判 `transport`、web 判 `unknown` | `src/wireErrorTriage.ts`(`WIRE_NETWORK_ERROR_PATTERN`);对拍腿 = `scripts/run-client-core-pure-test.mjs` 的 `F3E-C195-web-parity` 条件腿 | 分叉**已登记并被门钉住**(登记表在对拍腿里,分叉消失即红、未登记的新分歧也红)。端今天照现状读即可;要收敛得两侧同批改 flags —— 这属跨仓一批,已按表态制上 C 板 |
|
|
1166
|
+
| **P-40** | med(能力位模型;**系统性、非本批引入**——`approvalDecisionNote`/`respondFreeFormRules` 自 0.29.0/0.42.0 起同形) @cli @web @desktop | 🆕 **能力位是 per-baseUrl 的布尔缓存,不与「出这条帧的副本」或「收 respond 的副本」绑定**(异源对抗复审六轮 [medium] 登记)。`engineCapTrue(baseUrl, key)` 答的是「这个地址上次探到什么」——引擎温切 / 滚动升级 / 多副本代理下,一个 `true` 可以来自 7.46 实例而 respond 落到 ≤7.45 实例。后果按位不同:`note` 被静默忽略(无害);**编辑臂/批臂**会被老解析器**响亮 400 且连决断一起拒** ⇒ 人按下的 allow 落不了地,退化成 `unresolved`/TTL 自决。⚠️ ⇒ 「能力位闸让老引擎**零受迫**」这句话的正确形是「**在能力读数与实际 responder 一致的前提下**零受迫」,本档 §4b-2/§4b-3 的措辞已按此订正 | `src/engineCapsCache.ts`(per-baseUrl 缓存 + `invalidateEngineCaps(baseUrl, probe?)`)· `src/hitl/toolApprovalWire.ts`(`ToolApprovalFrameLaneOpts` 三个能力位的闸) | 端的现有缓解:①引擎 respawn/restart 后**必须**调 `invalidateEngineCaps(baseUrl, probe?)`(推荐两参形:推进代际 + 注册新探测在同一同步块内完成);②滚动升级窗口内宁可把两个规则能力位按 `false` 供给(丢持久臂 + 一条诚实通知,远好于把决断打掉)。🔴 正位解需要**上游给出 per-responder 的能力证明**(回执/帧上带副本代际或集群最低能力),或让 server 保证这类 400 对决断**零副作用**(那样客户端可以去掉批臂重送原决断)—— 按接入文档宪法向 server 点名,属跨仓设计件 |
|
|
1167
|
+
| **P-39** | med(呈现口径;**候上游补锚**) @cli @web @desktop @server | 🆕 **批臂回执 `persistedRules` 的逐条归属今天 wire 上证不出**(0.43.0 #334;异源对抗复审三轮 [medium] 登记)。本包做到的相关性 = `approvalId` + `decision` + **基数**(条数须等于所选 batch offer 的成员数)。基数抓得到「少报/多报」,**抓不到**「条数对但内容是另一批规则」—— 回执上没有任何锚能把这 N 条与那只 offer 对上:①不回 `batchOfferIndex`;②成员无稳定 id;③**文本等式在这一位上不成立**(落盘的是**规范化后**的文本,与 offer 上的文本按设计可以不同 —— 拿文本去比就是装第二个判官,`Bash(adb *)`→`Bash(adb:*)` 这类形会被当场误判) | `src/hitl/toolApprovalWire.ts`(`ToolApprovalRespondAckView.persistedRules` 顶注 + `surfaceToolApprovalFrameAndRespond` 的臂相关性门)· 行为钉:`run-client-core-pure-test.mjs` B7 `ruleOffers`⑬(少报/多报两向负控) | 端把它渲成「**引擎报告**落盘的规则」,**不是**「你刚才选的那批规则」——两句话在正常情形下指同一件事,但只有前一句是本包能背书的。🔴 **@server 请托**(接入文档宪法/表态制):兑付回执上补一个 **additive 锚** —— 回显 `batchOfferIndex`,或给 batch 成员一个稳定标识/服务端算的 offer 摘要,让客户端能做**逐条**相关性而不只是基数。补上当天本包按 `probeCause` 先例跟批收紧 |
|
|
1168
|
+
| **P-38** | med(集成面;**存量、非本批引入** —— 0.42.0 基线 `4c364c1` 上逐字相同) @cli @web @desktop | 🆕 **标准帧路由丢弃 `ToolApprovalFrameOutcome` 的诊断/回显面**:`routeToolApprovalFrame`(`src/hitl/frameRouter.ts`)对 `surfaceToolApprovalFrameAndRespond` 的产物**只解构 `decision`**,`ack` 与 `respondRefusal` 整段丢掉;`AskGateWireDeps` 上也没有 outcome sink。⇒ 走**标准集成面**(`runStream` → frameRouter)的端读不到:① 0.42.0 #225 件5 的 `respondRefusal`(server 响亮 400 / `edit-rejected` 拒句)② 0.43.0 #334 的 `persistedRule` / `persistedRules` 规范文本回显。⚠️ 两个**更重**的安全位不在此列 —— `rememberApplied:false` / `updatedInputForwarded:false` 由 `toolApprovalWire` 就地经 `HitlHostSurface` 投递(`surfaceRememberNotApplied` / `surfaceEditNotForwarded`),**不经**本路由,所以那两条告知照常到达。⚠️ 端若**自己直调** `surfaceToolApprovalFrameAndRespond`(cli 的 `approvalStreamWire` 卡腿即是)则拿得到完整 outcome,本条只约束走 frameRouter 那条路 | `src/hitl/frameRouter.ts`(`const { decision } = await surfaceToolApprovalFrameAndRespond(...)`)· `src/hitl/toolApprovalWire.ts`(`ToolApprovalFrameOutcome` 的三位产物) | 端**不要**假定「包返回了这一位 ⇒ 标准路由上就能读到」:走 frameRouter 的端今天拿不到规则回显与拒句,渲「你的规则已存为 X」/「引擎说:…」必须先确认自己走的是哪条调用点。🔴 正位解在包侧(给 frameRouter / `AskGateWireDeps` 补一个 outcome 回执端口,或把规则回显与拒句经 `HitlHostSurface` 就地投递,与 `rememberApplied` 同姿势),属**行为面 + 三端接线**的独立设计件,按 [C162] 令④ 回 C 板提 |
|
|
1169
|
+
| **P-37** | low(镜像面) @cli @web @desktop | 🆕 **`tool_approval_complete.parked` 已进键镜像,但本包零消费**(0.43.0 同形族扫的产物,#329 server 7.44.0 及更早就在场)。它是 `outcome:"expired"` 一词三义(park / 当场 deny / 无设施 deny)里 **park 那一义的显式判别位**:在场 ⇔ 这条 ask 按 park 路由收尾且墓碑已落(同一把 `approvalId` 仍可走迟到受理)。本包今天只把它镜像进 `TOOL_APPROVAL_FRAME_KEYS_MIRROR` + 类型,**没有**任何消费面 —— 因为它只长在 `tool_approval_complete` 上,而那种帧根本不进卡口(`surfaceToolApprovalFrameAndRespond` 消费的是 `tool_approval`) | `src/hitl/toolApprovalWire.ts`(`ToolApprovalFrame.parked` 声明 + 镜像项,两处 JSDoc 写明理由)· `scripts/run-approval-frame-keys-test.mjs` 的 `AHEAD_OF_ANCHOR` 登记 | 端今天**不要**指望包把「卡失效」改渲成「已转后台候批」——那条投影还没有。🔴 **缺席禁读作「真 deny」**:无店部署 / deny 政策 / 被连坐 VOID 的兄弟都发不出这个键,缺席只是「无 park 证据」。要把它变成一条真投影是**行为面**改动(消费者 = 端的完成帧处理面),按 [C162] 令④ 回 C 板提 |
|
|
1170
|
+
| **P-36** | low | **中断文案归一只覆盖 `Operation aborted` 这一串**(#323 症状②,0.43.0;clay 裁定的**明确边界**,不是漏做):同一次用户中断里,**执行前被连坐**的旁观者拿的是 core 的另一串 `operation aborted before execution`,`interrupted_never_started` 族又是第三种;这两族**刻意不并入**中断改写臂 —— core 显式拒绝合并两串向([4973]),两串各承真语义(`Operation aborted` = 执行中被中止 / 该串 = 从未执行),而且它们**各有自己的文案与折叠腿**(端侧的 interrupted-batch 折叠 + `TOOL_END_INTERRUPTED_CODES` 词表)。⇒ 纯取消批里,那两族的 tool_end 今天仍按各自原文呈现 | `src/hitl/frameRouter.ts`(`isUserInterruptRewritable` 的判据①头注 + `isEngineAbortToolEnd` 的两串族说明)· 负控 = `scripts/run-hitl-gate-honesty-test.mjs` F13-d | 端**不要**假定「用户中断 ⇒ 这一批 tool_end 文案全是 CC 中断串」;两族按各自既有腿归因(机读码优先,文案兜底)。要不要并成一形是**语义裁定**不是实现细节,需 clay 先裁 |
|
|
971
1171
|
| **P-34** | low | **编辑臂预检判官在浏览器 lane 结构上装不了**(#225 / [5076],0.42.0):`precheckEditedRuleText` 的唯一合法实参是 core 5.57.0 那只纯函数,而 `@sema-agent/core` 的 barrel 值级拉 `node:crypto`/`node:fs`/`node:path` —— 本包**不能**做 value 级 re-export(portability 门 `EXPECTED_PACKAGES_INDEX` 等值门 + esbuild 浏览器腿双重否决,施工时实打验证) | `src/hitl/editedRuleTextPrecheck.ts`(模块头注的「为什么是端口注入」段) | Node 宿主(TUI / desktop 主进程)装上即得内联即时校验;**浏览器 lane 留缺席走「提交后才知道」的往返形**,这是设计不是漏装。🔴 缺席**不可**据以判断部署形态 |
|
|
972
1172
|
|
|
973
1173
|
### 7c. 多会话(sessionKey)面在册局限 —— 多会话端**接之前必读**
|
|
@@ -1003,7 +1203,7 @@ CHANGELOG 0.29.0「已知局限」段与相应 JSDoc 都有成文。**别在读
|
|
|
1003
1203
|
| **P-27** | 立票设计件(web [C1] 疑点③,族A 二段票同批) | `ToolPermissionDecision` **无 note 席位**且座位宿主无 caps 缓存读口 ⇒ #229 回决备注在两座位端(web/desktop)**结构性无法供给**。修形二选一未裁:decision 形补 `note?` + 能力位随 `ToolPermissionRequest` 下发,或宿主侧统一判 | `src/seatContract.ts`(`ToolPermissionDecision`,682 行域) | 座位端今天**不要**渲 note 输入位(渲了也送不出去=假 affordance);候本条落地随提货单换 |
|
|
1004
1204
|
| **P-28** | 🔴 med(浏览器面) | ⚠️ 2026-08-14 补记:除下面那 10 处外,**第二个未放宽的入参面** `EngineProbeOpts.authToken`(`src/engineWireSdk.ts`)另喂 2 处探针(`agentsWireCaps.ts` 的 `engineSupportsTaskAgents` ⇒ `undefined` / `liveInitToolFace.ts` 的 `probeScenarioTools` ⇒ `null`,均属 C 档静默),合计 **12** 处 —— 见 §5d 末尾那段。 **`same-origin-relay` 只放宽了 2 个入参面,装配入口没跟**:`EngineWireClientConfig.token` 与 `LiveWorkflowConfig.authToken` 收了 `\| { mode:'same-origin-relay' }`,而 `EngineWireTarget.token` 仍是 `string` —— `installEngineWireTarget()` 恰恰是**非 Node 宿主唯一**的装配入口。经 `engineWireTarget()` 取址再构造 client 的 **10 处**(`hitl/planReviewWire.ts` ×2 · `subagent/engineSubagentTail.ts` · `engineSubagentSteer.ts` · `engineSubagentOutput.ts` · `engineCompactWire.ts` ×2 · `engineTaskHandleWire.ts` ×2 · `engineDelegatedPrompt.ts`)在同源反代部署下**没有合法凭证形可传**。⚠️ 失效形**逐点不同**(§5d 末两表:A 响亮 + **有条件**用户可见 = `decidePlanReview`,可见性取决于通知队列口装没装 / B 结构化 reason = steer 与 taskStop / C 无条件静默 = 其余七处),但**构造失败一律吞成 null、从不抛异常** | `src/engineWireTarget.ts`(`EngineWireTarget.token`)· `src/engineWireSdk.ts`(`makeEngineWireClient` 的 catch 臂)· §5d 的两表 | 浏览器同源宿主今天**只能**走 §5d 上表那两条自带入参面的路径(直调 `makeEngineWireClient` / `createLiveWorkflowSource`);走 `engineWireTarget()` 的子代与 plan-review 动词**别指望在 relay 部署下发得出去**,也**不要**把「动词没反应」读成「引擎没这个能力」(⚠️ plan-review 那条**只在通知队列口装上时**才到达用户,见 §5d 末表 —— 队列口没装就退回零用户通道,端的兜底告知别急着撤)。🔴 正位解在包侧(放宽 `EngineWireTarget.token` + 10 处透传),**不许端侧侧路补救**(跨仓缺陷源头修复);要它落地按 [C162] 令④ 回 C 板 |
|
|
1005
1205
|
| **P-29** | low(自检面) | **通知队列口没有存在性读口**:审批卡口有 `hasApprovalCardPort(For)`、HITL 面有 `hitlHostSurfaceFor`、宿主端口族有 `hostSettings()` 等无副作用读口(见 §5a 的 (a) 表),**唯独 `installNotificationQueuePort()` 没有对偶谓词**。而它的 `notificationQueuePortMisses()` 与同族几个 miss 计数一样**初值为 0**,只有真发生过一次「用到了但没装」才递增 ⇒ 「完全没装 + 还没有任何投递」照样是 0。拿它做**启动装配自检**必然假绿 —— §5a 此前正是这么写的(#252 复审 R3/R4 命中,已按端口拆成「存在性读口」与「回归探针」两类) | `src/notifications.ts`(`installNotificationQueuePort` 无对偶读口;`queuePortMisses` 初值与 `port()` 的 null 分支) | 队列口:按 §8-B 真调 `installNotificationQueuePort()`,miss 计数只当**跑过真流量之后**的回归探针用;其余端口按 §5a (a) 表用各自的存在性读口做启动校验。要队列口的读口按 [C162] 令④ 回 C 板提(正位解在包侧:补一个 `hasNotificationQueuePort()` 谓词) |
|
|
1006
|
-
| **P-30** | med(HITL 路由面) | **durable 审批腿不按 `gateKind` 路由,且取行有「同 taskId 任意行」回落** (0.30.0 发包扫描 对抗复审 finding① 坐实,**非本窗引入**):`findPendingForTask` 在工具名谓词无命中时走 `?? rows.find(r => r.taskId === taskId)`,而 `surfaceFsApprovalAndDecide` 拿到行之后**不校 `gateKind`** ⇒ 同一 task 上同时停着 `plan_review` / `resource_limit` 行时,会弹出一张 `toolName` 为空串的**工具审批卡**。⚠️ **不会误批**(server 侧 fail-closed):本腿打的是 `POST /v1/approvals/:sessionId/decide`,非工具门在该端点上回 **409 `gate_not_tool_approval`**(SDK `dist/errors.d.ts`;⚠️ **不是** `gate_not_resumable` / `gate_not_plan_review` —— 那两个分别是 `/resume` 与 plan-review 端点的守卫,2026-08-14 对抗复审 R2 订正本条初稿的错码)。🔴 **但后果不止「一次失败的决断」**:decide 抛错 ⇒ `surfaceFsApprovalAndDecide` 折成 `{kind:'failed'}` ⇒ `parkResolver` 走 fail-soft 结束**本次客户端 turn**;而 server 侧 checkpoint 因为 fail-closed **没被消费**,run/session 仍 suspended、仍持 claim ⇒ 重试还会再撞一次。⚠️ **终帧按入口分两形,排障别只等一个码**(2026-08-14 对抗复审 R3 订正本条初稿的单一描述):① **初始 park 入口**(`done{…park…}` 经 `frameRouter.routeDone` 进来,`park.pendingDone` **在场**)⇒ 先 `led.flushHeld()` 吐出 park 期被 HOLD 的**毒化帧**(`tool_end{isError:true, output:'Operation aborted'}`,`frameRouter.ENGINE_ABORT_TOOL_RESULT`),再原样回吐那条 `done` —— **没有**合成 `failed` 终帧、**没有** `hitl_unanswered` 错误码,可观察到的失败信号只有那条 isError 的 `tool_end`。⚠️ **它与「用户真按了拒绝」可以分辨,按 `output` 分**(2026-08-14 对抗复审 R5
|
|
1206
|
+
| **P-30** | med(HITL 路由面) | **durable 审批腿不按 `gateKind` 路由,且取行有「同 taskId 任意行」回落** (0.30.0 发包扫描 对抗复审 finding① 坐实,**非本窗引入**):`findPendingForTask` 在工具名谓词无命中时走 `?? rows.find(r => r.taskId === taskId)`,而 `surfaceFsApprovalAndDecide` 拿到行之后**不校 `gateKind`** ⇒ 同一 task 上同时停着 `plan_review` / `resource_limit` 行时,会弹出一张 `toolName` 为空串的**工具审批卡**。⚠️ **不会误批**(server 侧 fail-closed):本腿打的是 `POST /v1/approvals/:sessionId/decide`,非工具门在该端点上回 **409 `gate_not_tool_approval`**(SDK `dist/errors.d.ts`;⚠️ **不是** `gate_not_resumable` / `gate_not_plan_review` —— 那两个分别是 `/resume` 与 plan-review 端点的守卫,2026-08-14 对抗复审 R2 订正本条初稿的错码)。🔴 **但后果不止「一次失败的决断」**:decide 抛错 ⇒ `surfaceFsApprovalAndDecide` 折成 `{kind:'failed'}` ⇒ `parkResolver` 走 fail-soft 结束**本次客户端 turn**;而 server 侧 checkpoint 因为 fail-closed **没被消费**,run/session 仍 suspended、仍持 claim ⇒ 重试还会再撞一次。⚠️ **终帧按入口分两形,排障别只等一个码**(2026-08-14 对抗复审 R3 订正本条初稿的单一描述):① **初始 park 入口**(`done{…park…}` 经 `frameRouter.routeDone` 进来,`park.pendingDone` **在场**)⇒ 先 `led.flushHeld()` 吐出 park 期被 HOLD 的**毒化帧**(`tool_end{isError:true, output:'Operation aborted'}`,`frameRouter.ENGINE_ABORT_TOOL_RESULT`),再原样回吐那条 `done` —— **没有**合成 `failed` 终帧、**没有** `hitl_unanswered` 错误码,可观察到的失败信号只有那条 isError 的 `tool_end`。⚠️ **它与「用户真按了拒绝」可以分辨,按 `output` 分**(2026-08-14 对抗复审 R5 订正本条初稿的「同形不可分」;**0.43.0 起是三分不是两分**,见下):fail-soft 这条是 `flushHeld()` 吐出的**毒化帧**,`output` 逐字是 `ENGINE_ABORT_TOOL_RESULT`(`'Operation aborted'`);真 deny 走 `frameRouter` 的 `denied-call` / `deny-stamp-next` 臂,`output` 被改写成 `HITL_REJECT_MESSAGE`(CC `REJECT_MESSAGE` 逐字)。🆕 **0.43.0 新增第三形(#323 症状②)**:fail-soft 的原因若是**用户在门卡上中断**(`GateOutcome.kind === 'aborted'`,= 用户按 Esc/Ctrl+C),同一批毒化帧的 `output` 被归一成 `HITL_INTERRUPT_MESSAGE_FOR_TOOL_USE`(CC `[Request interrupted by user for tool use]` 逐字,公面导出)——`isError` / `errorCode`(含 `gate.parked`)/ `_sema_collateral_abort` 等机读位**一个都不改**。⇒ 端做归因的 `output` 三分:`'Operation aborted'` = 非中断原因的 fail-soft(卡面不可用 / no_pending / 传输失败)· CC 中断串 = 用户中断 · CC REJECT 串 = 用户真拒绝。端**不要**再假定「fail-soft ⇒ 必是引擎原文」;② **续流 / durable re-attach 入口**(`suspended` 进来,无 `pendingDone`)⇒ 才合成 `failed{errorCode:'hitl_unanswered'}`。⇒ 端做告警/埋点时**不要**只锚 `hitl_unanswered`,①那条路径上它根本不出现。⚠️ 定性要分清:这条 fail-soft 链是 durable 腿**通用**的失败路径(设计如此 —— 替代方案是谎报成功,更坏),**不是**本缺口独有;本缺口的**增量**是「弹了一张 `toolName` 为空的卡 + 发了一次注定 409 的 decide + 把用户的一次表态浪费掉」 | `src/hitl/hitlBridge.ts`(`findPendingForTask` 的第二条 `rows.find`)· `src/hitl/toolApprovalWire.ts`(`surfaceFsApprovalAndDecide` 全程零 `gateKind` 读)· 常驻登记见 `scripts/run-durable-card-display-keys-test.mjs` ⑦ 段 `gateKind` 那条未投影理由 | 端**不要**把「durable 卡弹出来了」读成「这一定是个工具门」;拿到 `toolName` 为空串的卡按异常处置、别渲成可决断卡。🔴 正位解在包侧(本腿按 `gateKind` 严格路由 + 回落收窄),要同批想好 pre-`gate_kind` 历史行 `gateKind` 缺席时的降级 —— 属独立设计件,按 [C162] 令④ 回 C 板提 |
|
|
1007
1207
|
|
|
1008
1208
|
### 7e. 缺口的共同形状(值得单独说)
|
|
1009
1209
|
|
|
@@ -1101,7 +1301,7 @@ reason 里写明「枚举器盲区形」。已知两形:
|
|
|
1101
1301
|
|
|
1102
1302
|
**E. 上行与回执(§4,最容易漏)**
|
|
1103
1303
|
- [ ] 请求一律经 `buildTaskRequest(input, lane)`,**不自拼字面量**;上 wire 前过 `unregisteredRequestKeys(req, lane)`
|
|
1104
|
-
- [ ] 接审批帧腿时**填 `AskGateWireDeps.approvalLane
|
|
1304
|
+
- [ ] 接审批帧腿时**填 `AskGateWireDeps.approvalLane` 的四位**(0.43.0 起不再是 note-only):`approvalDecisionNoteCapable`(缺席 = `note` 恒不发)· `respondFreeFormRulesCapable`(缺席 = **编辑臂**整条不发)· `respondBatchRuleOffersCapable`(缺席 = **批臂**整条不发)· `windowIsCurrent`(缺席 = 卡上**无倒计时**)。**全部 fail-closed 且不报错**;两条规则臂被丢时包会发 `surfaceRuleArmNotSent()`(要看得见得先装 `HitlHostSurface` 口)。逐位后果表见 §5b 的供给链段,新鲜度纪律见 §7 **P-40**
|
|
1105
1305
|
- [ ] ack 五位按 §4a 三列表消费:**缺席一律当未知**,`rememberApplied === false` 与 `updatedInputForwarded === false` 必须响亮告知
|
|
1106
1306
|
- [ ] durable 腿:`decideTool` 前把**呈卡用的那一行** pending 经 `preResolvedPending` 传进去(TOCTOU)
|
|
1107
1307
|
- [ ] durable 腿:**按意图**选动词 —— 否掉这一道门用 **deny**(run 继续),终结整条 run 用 **`runs.cancel`**(server [868] 起对 suspended/needs_review 就地取消;409 只剩 CAS race)。⚠️ 0.36.0 修:原行写的「取消 suspended 必须用 deny、绝不 cancel」已被 SDK 7.1.0 标 stale,详见 §4 「cancel vs deny」行
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.43.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",
|