@sema-agent/client-core 0.47.0 → 0.49.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 +155 -0
- package/README.md +4 -2
- package/dist/adapt/arms.js +10 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +62 -28
- package/dist/hitl/crashConverged.d.ts +176 -0
- package/dist/hitl/crashConverged.js +323 -0
- package/dist/hitl/hitlBridge.d.ts +38 -24
- package/dist/hitl/hitlBridge.js +16 -2
- package/dist/hitl/toolApprovalWire.d.ts +20 -0
- package/dist/hitl/toolApprovalWire.js +7 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +14 -0
- package/dist/retryStatus.d.ts +51 -3
- package/dist/retryStatus.js +31 -7
- package/dist/sessionMemoryStatus.d.ts +138 -0
- package/dist/sessionMemoryStatus.js +181 -0
- package/dist/typePins.d.ts +40 -0
- package/docs/INTEGRATION-CLIENTS.md +412 -22
- package/package.json +4 -4
|
@@ -15,22 +15,28 @@
|
|
|
15
15
|
|
|
16
16
|
## §0 版本锚与重扫纪律
|
|
17
17
|
|
|
18
|
-
### 0a. 版本锚(2026-
|
|
18
|
+
### 0a. 版本锚(2026-09-02)
|
|
19
19
|
|
|
20
20
|
| 项 | 值 | 真源 |
|
|
21
21
|
|---|---|---|
|
|
22
|
-
| 本包 | `@sema-agent/client-core` **0.
|
|
23
|
-
| peer:wire 契约 | `@sema-agent/sdk` **>=7.
|
|
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
|
-
| 公开导出面 | **
|
|
26
|
+
| 公开导出面 | **795** 个运行期符号(+ 41 个测试钩;= 工作树当下的值 —— 0.48.0 的 **794** 再加 L-38 一件未发 additive 导出,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.
|
|
31
|
-
`pending` 行)。装 ≤0.
|
|
32
|
-
(`
|
|
33
|
-
import 会**在 ESM 实例化当场炸**(具名导出不存在)——
|
|
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 更晚的未发 additive 导出**(L-38,见 §12):`projectCrashConverged`
|
|
35
|
+
(+ 三个 type-only 形 `CrashConvergedRow` / `ApprovalsListEnvelope` / `CrashConvergedProjection`)。
|
|
36
|
+
它随下一个版本段发出;在此之前按名 import 它同样会在 ESM 实例化当场炸。
|
|
37
|
+
🔴 **0.48.0 还抬了 peer 地板**(`@sema-agent/sdk >=7.4.0`),这是本版**唯一**的非 additive 面:
|
|
38
|
+
端装 <7.4.0 的 SDK 会看到 peer 警告(运行期不因此变化)。同一条对 0.47.0 那 **3 个 additive 导出**
|
|
39
|
+
成立(`planInteractiveHalt` / `RUN_LEVEL_STOP_ERROR_CODES` / `readDecideCurrentPending`)。同一条对 0.38.0 那 11 个
|
|
34
40
|
additive 导出成立(`engineCapState` + `EngineCapState`、`resolveEntryVision` /
|
|
35
41
|
`computeDeleteBlockers` / `computeDeleteWarnings`、`CONFIG_DELEGATION_ENTRY_CAPS` /
|
|
36
42
|
`DELEGATION_CONCURRENCY_CAP` / `DELEGATION_SESSION_CAP` / `DELEGATION_CAP_CODES` /
|
|
@@ -103,7 +109,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
103
109
|
|
|
104
110
|
## §2 公共导出面地图(按域)
|
|
105
111
|
|
|
106
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
112
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**795** 项)。
|
|
107
113
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
108
114
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
109
115
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -113,7 +119,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
113
119
|
|
|
114
120
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
115
121
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
116
|
-
实测:
|
|
122
|
+
实测:795 项 **100% 是运行期导出,零 type-only**。
|
|
117
123
|
|
|
118
124
|
**推论(端必须知道)**:
|
|
119
125
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -121,19 +127,21 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
121
127
|
`LocalSessionEvent` / `SeatMethodName` / `ModelCatalog` 全在公面上、全**不在**基线里。
|
|
122
128
|
端依赖这些类型是合法的,但**不要**拿基线 diff 当"类型面没变"的证据。
|
|
123
129
|
- `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
|
|
130
|
+
- 同理:L-38 的 `CrashConvergedRow` / `ApprovalsListEnvelope` / `CrashConvergedProjection` 三个形也
|
|
131
|
+
**不在**基线里(纯类型),`src/hitl/crashConverged.ts` 对基线只贡献 `projectCrashConverged` 一项。
|
|
124
132
|
|
|
125
|
-
|
|
133
|
+
795 项的内部构成(帮助端估读表大小):**233** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
126
134
|
(矩阵、键集、env 名、锚串)而非可调用物;**5** 项是 PascalCase 运行期值
|
|
127
135
|
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
|
|
128
136
|
**41** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
129
137
|
|
|
130
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
138
|
+
### 2b. 域图(16 域,逐域计数之和 = 795)
|
|
131
139
|
|
|
132
140
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
133
141
|
|---|---|---|---|---|---|
|
|
134
142
|
| 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` |
|
|
135
143
|
| 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
|
|
136
|
-
| 3 | **HITL 决断卡链**(§4/§5 主战场) |
|
|
144
|
+
| 3 | **HITL 决断卡链**(§4/§5 主战场) | 133 | `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` 同族纪律:决断照送、只是规则没存,静默丢掉用户明确意图 = 让人以为功能坏了)· `projectCrashConverged`(L-38,0.49.0:`/v1/approvals` additive 键 `crashConverged` 的分桶投影 —— 崩溃收敛的孤儿审批读面,**缺席 ≠ 空数组**、分桶恰一个合取、坏行丢弃并计数,详见 §12;同批把 `ApprovalsResourceLike.list()` 的返回位 additive 放宽成 `ApprovalsListEnvelope`,老形 `{pending}` 仍可赋值)· `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 规则侧)、`crashConverged.ts`(L-38 崩溃收敛读面) |
|
|
137
145
|
| 4 | **子代 wire + 面板侧信道台账** | 84 | `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` |
|
|
138
146
|
| 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` |
|
|
139
147
|
| 6 | **请求装配(上行唯一构造口)** | 8 | `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
|
|
@@ -145,7 +153,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
145
153
|
| 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
154
|
| 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
155
|
| 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 | **控制面与传输** |
|
|
156
|
+
| 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
157
|
| 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
158
|
|
|
151
159
|
🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
|
|
@@ -251,14 +259,21 @@ taskId · name · usage · model · currentAction · workflowRunId · workflowAg
|
|
|
251
259
|
`prepared.model.id`;server `trace/project.js` 的 `taskProgressEventData` 里条件 spread:
|
|
252
260
|
`string` 且非空才发)。本层同条件透传 —— **空串/缺席一律整键不铸**(空串既不是模型 id 也不是
|
|
253
261
|
「不知道」)。补它之前是 `workflowRunId` 的**同形第二例**:上游真发、白名单剥掉、两边代码看着都对。
|
|
254
|
-
|
|
255
|
-
|
|
262
|
+
✅ **SDK 锚已追平,cast 已退(0.48.0)**:0.47.0 这里写的是「sdk 7.3.0 的 `task_progress` 臂尚未
|
|
263
|
+
声明这一位,故本层是**结构视图读**;SDK 补上当天那处 cast 可整条删」—— sdk **7.4.0 已声明**
|
|
264
|
+
`model?: string`,peer 地板同批抬到 `>=7.4.0` ⇒ 按那条退役条件兑现,改类型面直读。
|
|
265
|
+
🔴 **退役的是 cast,不是运行期判**:`typeof` 门保留(旧 server 缺席 ⇒ 键不 stamp;wire 是 JSON,
|
|
266
|
+
类型声明是上游承诺、不是本层前提)。端侧**行为逐字节不变**。
|
|
256
267
|
|
|
257
268
|
**仍然被本层剥掉的五键**(`seq` / `taskType` / `parentTaskId` / `status` / `eventId`):
|
|
258
269
|
- `taskType` / `status` / `parentTaskId` —— 在册的 §7 缺口 **P-1**;lane 归属今天改用 id 形状 /
|
|
259
270
|
`workflowRunId` 启发式判(`src/adapt/arms.ts`)。
|
|
260
271
|
- `seq` —— core #258 的 stop-cycle 代际号(复活即 +1)。fleet 面已有同轴的 `wireCycleSeq`(0.38.0),
|
|
261
|
-
**tick
|
|
272
|
+
**tick 这条腿今天没有消费方** ⇒ 照旧剥。
|
|
273
|
+
⚠️ **就地订正(0.48.0)**:本条 0.47.0 的原文还写着「SDK 7.3.0 连声明都没有」—— sdk **7.4.0 已
|
|
274
|
+
声明** `seq?: number`(与 `model` 同批)。**剥它的理由换了一条,但仍然剥**:准入条件从来是
|
|
275
|
+
「说得出谁读它、读来干什么」,SDK 有没有声明只是当时顺带成立的第二个事实。
|
|
276
|
+
🔴 **声明到货不是透传的理由** —— 否则这张白名单会随上游类型面自动变宽,准入条件形同虚设。
|
|
262
277
|
- `eventId` —— EventIdentity 的另一半;本臂只补了 `parentToolCallId`(lane 判据要它),`eventId` 至今无消费方。
|
|
263
278
|
|
|
264
279
|
🔴 **透一位的前置条件**(键账的维护规矩,不是修辞):说得出**谁读它、读来干什么**,并同批更新
|
|
@@ -338,17 +353,90 @@ in-process ask 拒绝时**在场。
|
|
|
338
353
|
5. 🔴 **缺席不可反推** —— server 对非白名单码 / 缺 `sessionId` 的通告**如实不投**(宁缺席不串台),
|
|
339
354
|
全族那一份只在 server 的运维日志里。「没收到通告」**不等于**「没发生」。
|
|
340
355
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
356
|
+
#### 3f-2. 🆕 server 7.54.0 两个新码(0.48.0;`task.halt_unconsumed` / `task.late_approval`)
|
|
357
|
+
|
|
358
|
+
server 7.54.0 的 `ENGINE_NOTICE_WIRE_CODES` 增至 **14 码**,新增两个 **task 域**的码
|
|
359
|
+
([5899] / [5906];真源 = server `dist/trace/engine-notice-wire.js` 的常量数组直证):
|
|
360
|
+
|
|
361
|
+
| code | 语义(server 侧铸文;本包不改写一个字) |
|
|
362
|
+
|---|---|
|
|
363
|
+
| `task.halt_unconsumed` | 一次 halt 请求没有被消费掉(停止动词落在了「没有在飞的那一轮」那一格) |
|
|
364
|
+
| `task.late_approval` | 一次审批**迟到**了 —— 决断到达时它要结算的那只 ask 已经不在等了 |
|
|
365
|
+
|
|
366
|
+
🔴 **本包的施工量 = 零,而这不是偷懒**:本臂按**开集**消费(消费纪律第 2 条:一个码都不硬编),
|
|
367
|
+
所以这两个码**按构造**就到得了端 —— 不需要、也**不应该**为它们加任何识别分支。加一张码白名单
|
|
368
|
+
才是这条腿唯一会坏的方式(新码当天静默蒸发,而两边代码看着都对)。
|
|
369
|
+
常驻门 `scripts/run-additive-key-passthrough-test.mjs` **G3 段**把这条构造钉住:两个真码 **+ 一个
|
|
370
|
+
编造的码**同时过投影,三者都必须原样出臂 —— 编造码那一条是**判别力的来源**(真码可能因为被加进
|
|
371
|
+
某张白名单而仍然绿,编造码必红)。
|
|
372
|
+
|
|
373
|
+
🔴 **端的消费点应当落在哪(本节写死,免得三端各找各的)**:
|
|
374
|
+
- 两码都是**会话级披露**,不是转录物 ⇒ 落**通知面 / 状态行**,**不要**合成 transcript 行
|
|
375
|
+
(与 §3f 主段同一条纪律)。
|
|
376
|
+
- `task.late_approval` 的呈现要与**审批卡面**联动而不是并列:它说的是「你刚才那一决断没落到东西
|
|
377
|
+
上」,端若已经把卡收掉,应当据此把那张卡的终态从「已决断」订正为「未结算」,否则用户看到的是
|
|
378
|
+
一次并不存在的成功。🔴 **它不是错误**,不要渲成失败态 —— 迟到是时序事实。
|
|
379
|
+
⚠️ **cli 消费批打回条目(2026-09-01,自维护上游候件,不在 0.48.0 修)**:上两句在 **TUI** 上互斥 ——
|
|
380
|
+
「订正已收掉的卡的终态」与「不合成 transcript 行」不可兼得:一张已收掉的卡,其终态在 TUI 端**只活在
|
|
381
|
+
转录里**(卡已退出队列与台账;观察事件账明写不参与终态判定,往那里写=inert 台账);两次壳侧自行加固
|
|
382
|
+
(加长通知窗 / 按 toolCallId 撤卡)均被异源复审证伪(独占共享 footer 饿死高优先级取件行 / id 复用与
|
|
383
|
+
重放时撤掉另一张有效卡)。⇒ 壳 0.48.0 消费批交付的是本条款允许载体上**能说的最强的话**(点名工具+
|
|
384
|
+
「工具没跑 / 什么都没结算」两句+显式免责),**不发明第三条路**。正位解=本包给一个 TUI 可用的**持久卡态
|
|
385
|
+
订正面**(或明许本码破例合成一条转录订正行),登记 DEBTS-cli L-44,届时本条款改写。
|
|
386
|
+
- `task.halt_unconsumed` 对应壳侧 Esc/停止腿(§10 `planInteractiveHalt` 的同一条语义轴):
|
|
387
|
+
端据它把「已请求停止」的乐观态**收回**,而不是让那一行一直挂着。
|
|
388
|
+
⚠️ 与 §10 的判定**不互替**:那一条是**发起前**的判定(该发 halt 还是 cancel),本码是**发起后**
|
|
389
|
+
引擎回报的事实。两者都要,缺任一端都会在某一格谎报。
|
|
390
|
+
- 两码都遵守 §3f 的重放幂等序(`eventId` > `eventSeq` > `code+ts`)。
|
|
391
|
+
- **cli 认领**:壳侧接点在下一批(本批只保证「到得了端」+ 门 + 本节指引)。
|
|
392
|
+
|
|
393
|
+
⚠️ **一条如实登记的订正(0.48.0)**:
|
|
394
|
+
- ✅ **SDK 类型面已到货,raw 预分派已退役**:0.47.0 这里登记的是「`engine_notice` 尚未进已发布
|
|
395
|
+
SDK 的 `AgentEvent` union(npm 7.2.0 真 tarball 全树零命中),故走 **raw 预分派** + 自退休钉」。
|
|
396
|
+
sdk **7.4.0 已声明该臂**(五键全必填、无 `& EventIdentity`)⇒ 那颗自退休钉**本批真的响了**:
|
|
397
|
+
devDep 抬到 7.4.0 的当拍 `tsc` 就报 `assertNeverArm` 收不下这条臂,逼着把它搬进 `case`。
|
|
398
|
+
已按原定条款搬迁,**行为一字不改**(投影函数原样复用)。
|
|
399
|
+
🔴 **投影仍走 raw `Record` 视图,刻意不改吃 SDK 收窄形**:SDK 把五键记成**全必填**,而本层对
|
|
400
|
+
每一键都做诚实缺席处理(`code` 空 ⇒ malformed;`message`/`detail` 坏 ⇒ 降级但不丢帧;
|
|
401
|
+
`sessionId`/`ts` 非法 ⇒ 不 stamp)—— 这些分支在收窄形上会被类型面判成死码而**静默失效**。
|
|
402
|
+
必填是 server 的承诺,不是本层的前提。且 `eventId` / `eventSeq` 两个重放身份键**根本不在**
|
|
403
|
+
SDK 臂声明里,收窄形上读它们是编译错。
|
|
346
404
|
- **附录 D.3 的白名单表已失真**:档里仍写「起步白名单(server 7.36 三码)」,而 server main 的
|
|
347
405
|
`ENGINE_NOTICE_WIRE_CODES` 已是**六码**(core 5.47/5.48 的 `NOTICE_AUDIENCE` 到货后
|
|
348
406
|
`memory.hold_opened` / `hold_released` / `hold_disposed` 入册;v7.37.0 tag 上仍是三码 ⇒ 六码随
|
|
349
407
|
7.38 到)。**对本包与端零影响** —— 正因为消费面按开集写,白名单是 server 的投递判定,不是消费判据。
|
|
350
408
|
已按接入文档宪法回报 server。
|
|
351
409
|
|
|
410
|
+
### 3g. 🆕 `status`(BrainStatus)臂的两个新键(0.48.0;core 7.0.x #506 ㋑ / server ≥7.53)
|
|
411
|
+
|
|
412
|
+
`RetryStatus` 上新增两个 additive 位。**病形与 §3d 的 `model` 逐字同族**:上游真发、本层闭形白名单
|
|
413
|
+
剥掉、两边代码看着都对。⚠️ 这条腿上有**两层**白名单(`eventToSdkMessage` 的 `case 'status'` +
|
|
414
|
+
`adapt/arms.ts` 的 `retryStatusArm`),**两层同批修** —— 只修其中一层键仍到不了宿主。
|
|
415
|
+
|
|
416
|
+
| 键 | 语义 | 缺席读法 |
|
|
417
|
+
|---|---|---|
|
|
418
|
+
| `retryAtMs` | 本次退避**预计结束的墙钟时刻**(epoch ms),= 发帧那一刻的 `Date.now() + retryInMs`,**由产生者铸** | 不宣告等待的帧上必缺席(`recovered` / `gave_up` / output-cap 立即重发) |
|
|
419
|
+
| `errorStatus` | **刚刚失败那次尝试**的 HTTP 状态码;CC `system/api_retry.error_status` 是同一个数 | 缺席面**封闭**:传输层失败 / 流中断 / `circuit_open` / 终态帧上恒缺席 —— **禁**渲成 `0` 或「未知错误码」 |
|
|
420
|
+
|
|
421
|
+
🔴 **`retryAtMs` 在场时端应当拿它渲倒计时,而不是拿 `deadline`**:`deadline` 是**本包**按
|
|
422
|
+
`nowMs + 剩余量`现算的,跨进程跳(core → server → 本包 → 端)的传输耗时已经把它推后了;而 core 对
|
|
423
|
+
>30s 的等待会每 30s **重播一帧并递减**,于是「自己算」的倒计时在每个重播片上**重新起跳**而不是收敛。
|
|
424
|
+
产生者是唯一说得出那个时刻的人,所以它才铸这一位。
|
|
425
|
+
🔴 **`deadline` 的语义与字节本批一字未改**(0.29.0 起已发布的行为面):两位**并存**,端自己选
|
|
426
|
+
(在场优先)。换算法 = 一次静默的行为改动,本包不做。常驻门 G4c 段是这条方向钉的反钉。
|
|
427
|
+
🔴 **时钟域**:`retryAtMs` 是**墙钟**(`Date.now()`),不是单调钟。端不得拿它与自己的单调计时器比;
|
|
428
|
+
跨机器 / 跨授时校正时按**近似值**处理 —— 权威的**相对**量始终是 `retryInMs`。
|
|
429
|
+
🔴 **`errorStatus` 不许用来推断该不该重试**:该不该等由 `phase` / `errClass` 两个中性桶说了算;
|
|
430
|
+
本位是给操作者看的**点名**(「谁失败了」),渲进人话行即可。
|
|
431
|
+
|
|
432
|
+
**端的消费点**:重试覆盖层那一行(`RetryStatus`)。⚠️ **CC 取证订正(cli 消费批 2026-09-01,语料直证)**:CC 2.1.223 该行非终态 headline 逐字是小写 `API error` 且**不渲状态码**(`cc-decoded/pretty223.js:744413`,`Yii = !Pmf ? "API error" : …`);此前本档写的「API Error 529 · Retrying in Ns」是转述不是取证。端的正位形 = 保留 CC 的 `API error` 措辞,把状态码作为**超集**追加(`API error 529`),缺席时与 CC 逐字节相同。
|
|
433
|
+
**cli 认领**:壳侧渲染在下一批。
|
|
434
|
+
**实现锚**:`src/retryStatus.ts`(`BrainStatusPayload` / `RetryStatus` / `mapBrainStatusToRetry`)+
|
|
435
|
+
`src/adapter/downstream/eventToSdkMessage.ts` 的 `case 'status'` + `src/adapt/arms.ts` 的 `retryStatusArm`。
|
|
436
|
+
**常驻门**:`scripts/run-additive-key-passthrough-test.mjs` **G4 段**(驱**两段合成**的全链,
|
|
437
|
+
只驱一层会在另一层漏修时假绿)+ `scripts/run-engine-vocab-floor-test.mjs` **G2-c**(键集镜像对
|
|
438
|
+
**实装 core** 逐键等值 —— 引擎再加键,那边先红)。
|
|
439
|
+
|
|
352
440
|
### 3z. 🆕 `_sema_collateral_abort` —— 连坐 abort 机读位(0.40.0,#324 / [4907])
|
|
353
441
|
|
|
354
442
|
**病形**:同 turn 多个 call 在飞 + 一个撞 gate ⇒ 引擎对**整批**在飞 call 铸同一串
|
|
@@ -876,6 +964,41 @@ decide 今天拿不到这枚 409**。本位是给「注入自有传输层 / 读
|
|
|
876
964
|
|
|
877
965
|
---
|
|
878
966
|
|
|
967
|
+
### 4g. 🆕 durable park 行的 bidi 披露位 `hasBidiControls`(0.48.0;S-30①,server ≥7.53 / core 5.60.0 #438)
|
|
968
|
+
|
|
969
|
+
durable 审批行 `PendingCheckpoint.hasBidiControls` 随卡透传到 `ApprovalCardRequest.hasBidiControls`。
|
|
970
|
+
语义:这条 park 行的**执行载荷**里含至少一个 DIRECTIONAL 格式控制符(Trojan Source —— 人眼读到的
|
|
971
|
+
顺序 ≠ 真正执行的字节顺序)。
|
|
972
|
+
|
|
973
|
+
🔴 **与活卡腿的 `inputHasBidi` 刻意分键不合流**(上游把两个名字取得不同,正是为了不让人合并):
|
|
974
|
+
|
|
975
|
+
| | 活卡帧腿 | durable park 行腿 |
|
|
976
|
+
|---|---|---|
|
|
977
|
+
| 卡上的键 | `inputHasBidi`(0.43.0 起) | `hasBidiControls`(**本批**) |
|
|
978
|
+
| 谁算的 | server 对**帧自身序列化后的 args** 现算(E-14) | **core** 在 park mint 时算(`PendingAction.hasBidiControls`),反范式成 durable 列;server `listPending` 读列 `=== 1` 才铸,**不重算** |
|
|
979
|
+
|
|
980
|
+
⇒ 同一只 ask 的两条腿**在场性可以不一致**,这是设计不是缺陷。端要渲一个徽标的话,读**两位的并**
|
|
981
|
+
是允许的(那是端的呈现决定),但两位在本层必须**各自到货**。合成一位 = 拿一个量冒充另一个。
|
|
982
|
+
|
|
983
|
+
🔴 **缺席绝不折成 `false`**(类型是 `true`,与 `governanceForced` / `inputHasBidi` 同族):
|
|
984
|
+
缺席 = **没检出**(干净 / core 有界扫描没够着 / 列诞生前 park 的老行),端**禁**读成「已确认干净」
|
|
985
|
+
—— 那是对用户下一个证不出的断言。
|
|
986
|
+
🔴 **披露位,不是清洗位;本包字节零改**:清洗会改掉即将被执行的那串字节(卡上显示的与真跑的不是
|
|
987
|
+
同一个东西),比不披露更坏。显形(转义 / 高亮 / 加标记)归端。
|
|
988
|
+
🔴 **永不参与 resume / gate / CAS**(上游同款纪律):展示与分诊用。
|
|
989
|
+
|
|
990
|
+
**上游三条读面与本包的覆盖**(如实记账):
|
|
991
|
+
| 上游读面 | 本包 |
|
|
992
|
+
|---|---|
|
|
993
|
+
| `GET /v1/approvals` 行(`PendingCheckpoint`) | ✅ **原样透传**(`startApprovalsFeed` 把 `list()` 的行原样交给宿主,零重铸)+ 本批补上「行 → 卡」重铸处 |
|
|
994
|
+
| `/v1/approvals/stream` 的 `pending` 帧(`ApprovalStreamEvent`) | ⭕ **零施工(如实记)** —— 本包的 feed 只把 stream 事件当「变了」信号,权威列表**恒来自 `list()`**(见 `approvalsFeed.ts` 头注),所以该帧的键不经本包任何投影 |
|
|
995
|
+
| inbox 行(`InboxRow`) | ⭕ **零施工(如实记)** —— 本包**没有** inbox 投影面(全仓零 `InboxRow` 引用);端若自接 inbox,直接读 SDK 形 |
|
|
996
|
+
|
|
997
|
+
**端的消费点**:审批卡的 bidi 提示 / 徽标。**cli 认领**:壳侧审批卡在下一批接。
|
|
998
|
+
**实现锚**:`src/hitl/toolApprovalWire.ts`(`ApprovalCardRequest.hasBidiControls` + `surfaceFsApprovalAndDecide` 的卡入参)。
|
|
999
|
+
**常驻门**:`scripts/run-durable-card-display-keys-test.mjs` **⑪ 段**(正控 + 缺席 + 非 true 四形负控 +
|
|
1000
|
+
两腿分键反钉)与 **⑦ 段富行键集普查**(上游 additive 加展示键当天红 —— 本批正是被它抓出来的)。
|
|
1001
|
+
|
|
879
1002
|
## §5 能力位 gate 义务与端口缺席语义
|
|
880
1003
|
|
|
881
1004
|
### 5a. 端口(`install*`):缺席语义与"漏装会静默坏掉什么"
|
|
@@ -1606,3 +1729,270 @@ planInteractiveHalt({ parked, interruptOutcome })
|
|
|
1606
1729
|
人话文案归端(别把它当展示串)。
|
|
1607
1730
|
|
|
1608
1731
|
**实现锚**:`src/interactiveHalt.ts`;常驻门 `scripts/run-esc-halt-plan-test.mjs`。
|
|
1732
|
+
|
|
1733
|
+
## §11 🆕 会话记忆姿态读面(S-53,0.48.0;server ≥7.53 / core 7.0.2 #511 件1)
|
|
1734
|
+
|
|
1735
|
+
`GET /v1/sessions/:id/memory-status` 的**三端公共读面**。本包提供**纯判定 + 薄封装**,
|
|
1736
|
+
IO 归宿主注入(`MemoryStatusClientLike`,与 `HitlClientLike` 同款 duck-type);
|
|
1737
|
+
本件**一句面向用户的话都不铸** —— 措辞与是否上屏归端。
|
|
1738
|
+
|
|
1739
|
+
### 11a. 为什么这件在库里(两处判定,三端各写一遍必然各错一遍)
|
|
1740
|
+
|
|
1741
|
+
**① 同 status 不同码。** 本路由的 **404 有两个互不相干的含义**:
|
|
1742
|
+
|
|
1743
|
+
| errorCode | 含义 | 端的处置 |
|
|
1744
|
+
|---|---|---|
|
|
1745
|
+
| `not_found.session` | 会话未知**或非本 principal 所有**(server 反枚举:两者同码同串,判不出更细的,**别猜**) | 会话面报「找不到这条会话」 |
|
|
1746
|
+
| `not_found.route` | 支持区间内 **<7.53 的老 server 没有这条路由**,答的是通用回退 | 按「**面不存在**」降级(与 501 同处置) |
|
|
1747
|
+
|
|
1748
|
+
🔴 按 **status** 分诊必然把「你的部署没这个面」说成「你这个会话不存在」——
|
|
1749
|
+
判据只能锚 `errorCode`([anchor-on-the-deciding-quantity])。
|
|
1750
|
+
🔴 **无码的 404 落 `failed`(如实说判不出),绝不挑一个猜**:两个码的处置相反,猜错任一向都是
|
|
1751
|
+
一句用户会照着去排错的假话。**501 才允许无码兜底**(本路由两条 501 臂都是「面不在/没开」,无歧义)。
|
|
1752
|
+
🔴 `capability.*`(换部署形态)与 `feature.*`(叫管理员开开关)**分列不合流** ——
|
|
1753
|
+
SDK 顶注逐字:同为 501 而**处置相反**。
|
|
1754
|
+
|
|
1755
|
+
**② 五键缺席语义逐键不同。** `optOutSource` / `lastCaptureAt` 在**健康会话**上就合法缺席;
|
|
1756
|
+
零历史会话的真形 = `{captureOptedOut:false, committedCount:0, foldedCount:0}`,**没有任何降级**。
|
|
1757
|
+
⇒ 把缺席一律读成「没有 / 关着 / 0」就是对用户下一个证不出的断言。
|
|
1758
|
+
|
|
1759
|
+
### 11b. 端怎么接
|
|
1760
|
+
|
|
1761
|
+
```ts
|
|
1762
|
+
import { readSessionMemoryStatus, readCaptureOptOut, readLastCapture } from '@sema-agent/client-core'
|
|
1763
|
+
|
|
1764
|
+
const v = await readSessionMemoryStatus(client, engineCapturedSessionId, { signal })
|
|
1765
|
+
switch (v.kind) {
|
|
1766
|
+
case 'ok': /* readCaptureOptOut(v.facts) / readLastCapture(v.facts) */ break
|
|
1767
|
+
case 'unsupported': /* 🔴 别提供这个入口(不是报错,是诚实的能力缺席);reason 分 capability/route/feature */ break
|
|
1768
|
+
case 'not_found': /* 会话未知或非属主 */ break
|
|
1769
|
+
case 'failed': /* 分类不明,如实说;v.error 是原始抛出物 */ break
|
|
1770
|
+
}
|
|
1771
|
+
```
|
|
1772
|
+
|
|
1773
|
+
两个**合读器**(缺席语义就藏在这两格里,端**不要**自己读裸键):
|
|
1774
|
+
|
|
1775
|
+
| 读法 | 三态 | 判别材料 |
|
|
1776
|
+
|---|---|---|
|
|
1777
|
+
| `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` 是反向的同一种谎 |
|
|
1778
|
+
| `readLastCapture` | `known` / `none` / `indeterminate` | 🔴 判别材料是**另一键** `committedCount`,不是 `lastCaptureAt` 本身:本键缺席**同时**覆盖「台账不可读」与「真的没有贡献」两形 ⇒ **单读它判不出任何东西**。`committedCount === 0`(台账可读、真零)+ 本键缺席 ⇒ `none`;`committedCount` 缺席 ⇒ `indeterminate`。🔴 `known` 的条件是**合取**(时刻在场 ∧ 台账可读 ∧ `committedCount > 0`):两者本是**同一次台账读**,`{committedCount:0, lastCaptureAt:X}` 这种对不上的形是矛盾 ⇒ `indeterminate`,不产出确定时间 |
|
|
1779
|
+
|
|
1780
|
+
### 11c. 端必读的三条
|
|
1781
|
+
|
|
1782
|
+
1. 🔴 **`sessionId` 必须取引擎捕获值**(壳从 wire 上拿到的那个 id),不是宿主自铸/自选的串。
|
|
1783
|
+
server 侧记录与台账按**裸 sessionId** 键控且**活过会话** ⇒ 喂一个**被回收**的 id 会读到
|
|
1784
|
+
**上一代**的计数 / opt-out(元数据,无内容字节;server 侧成文的跨代注意)。
|
|
1785
|
+
本包对空串**直接落 `failed` 且不发请求** —— 那一发必然是对某个不属于本会话的东西提问。
|
|
1786
|
+
2. 🔴 **没有能力位**([5785]/[5786] 未定位名):直接调用,**501 就是本部署无此面的诚实答案**。
|
|
1787
|
+
别为它去探一个不存在的 caps 位。
|
|
1788
|
+
3. 🔴 **畸形键在本层降缺席、不采信**:降键的后果是两个合读器答 `indeterminate`(「不知道」),
|
|
1789
|
+
采信坏形的后果是拿它当真值渲。两害相权,如实不知道。
|
|
1790
|
+
⚠️ 但 **200 体整体非对象 ⇒ `failed`**,不洗成「全键缺席」的假 `ok` —— 那会把一次装配缺陷
|
|
1791
|
+
渲成一个看起来很诚实的 `indeterminate`。
|
|
1792
|
+
4. 🔴 **身份绑定:回声的 `sessionId` 必须与请求值逐字相等,否则整只落 `failed`**
|
|
1793
|
+
(`SessionMemoryStatusResponse` 契约上它是必填回显 ⇒ 缺席/非串同样是坏形)。
|
|
1794
|
+
收下一个不相等的回声 = 把**另一条会话**的记忆元数据(计数 / opt-out 姿态)呈现在当前会话面板上
|
|
1795
|
+
—— 缓存错配、中间层串台、依赖故障都造得出。上游对同一件事的纪律是**宁缺席不串台**,本层照办。
|
|
1796
|
+
端**不需要**自己再核一遍这一位。
|
|
1797
|
+
5. 🔴 **「永不抛」对畸形宿主也成立,成功路与失败路都算**:本函数吃的是 duck-typed 注入 client,
|
|
1798
|
+
它可以回一个带**抛错 getter** 的对象或敌意 `Proxy`,也可以**用**这种对象作拒因。
|
|
1799
|
+
两条路都设了防:归一化整段在 `try` 内;`classifyMemoryStatusFailure` **自己**取属性时也带保护
|
|
1800
|
+
(它是在 `catch` **块内**被调用的 —— `catch` 里抛出的异常不会再被同一个 `try` 接住,分类器一抛
|
|
1801
|
+
就会击穿这句承诺)。⇒ 调用点**不需要**给它套 `try`,顶注那句承诺是可依赖的。
|
|
1802
|
+
⚠️ `classifyMemoryStatusFailure` 单独调用时同样永不抛,且**原抛出物原样带出**(端要看得到真因)。
|
|
1803
|
+
|
|
1804
|
+
**端的消费点**:cli [5902] 研判推荐挂 **`/status` 面**(会话级披露,不是转录物)。
|
|
1805
|
+
**cli 认领**:壳侧接点在下一批。
|
|
1806
|
+
**实现锚**:`src/sessionMemoryStatus.ts`。
|
|
1807
|
+
**常驻门**:`scripts/run-session-memory-status-test.mjs`。
|
|
1808
|
+
|
|
1809
|
+
## §12 🆕 崩溃收敛的孤儿审批读面(L-38,0.49.0;server ≥7.55.0)
|
|
1810
|
+
|
|
1811
|
+
`GET /v1/approvals` 回体上的 **additive 键 `crashConverged`**:local 引擎在**人还挂在审批门上**的
|
|
1812
|
+
时候崩了,那些孤儿 ask 被 server 重启后**收敛成 DENIED 同码**;这一键把「上一条命留下了什么」
|
|
1813
|
+
交到端手上。本包提供**纯投影**(零 IO、零 module 级可变态、零文案),取件仍走既有的权威
|
|
1814
|
+
`client.approvals.list()`。
|
|
1815
|
+
|
|
1816
|
+
### 12a. 信封形与 additive 放宽(现有消费点零改动)
|
|
1817
|
+
|
|
1818
|
+
```ts
|
|
1819
|
+
// 放宽后的 approvals.list() 回体形(hitlBridge.ts 的 ApprovalsResourceLike.list)
|
|
1820
|
+
interface ApprovalsListEnvelope {
|
|
1821
|
+
pending: PendingCheckpoint[] // 老形的全部
|
|
1822
|
+
livePending?: unknown[] // additive;本包**不消费**,形属主是 server
|
|
1823
|
+
crashConverged?: CrashConvergedRow[] // additive;本节的主角
|
|
1824
|
+
}
|
|
1825
|
+
```
|
|
1826
|
+
|
|
1827
|
+
🔴 **放宽是 additive**:老形 `{ pending }` 的 mock 与实现仍然可赋值,`hitlBridge.pendingForDecide` /
|
|
1828
|
+
`approvalsFeed` 那两处 `.pending` 消费点**一个字节不动**(常驻门真跑一次 tsc 求值,附一份已知会红
|
|
1829
|
+
的负控语料证明那台检查器会说话)。同一段门还编译**真 `AgentClient`** 的两条路 ——
|
|
1830
|
+
`projectCrashConverged(await client.approvals.list())` 与 `client.approvals` 赋值到本切片 ——
|
|
1831
|
+
因为只拿本包自己的信封形去测**发现不了**弱类型陷阱:`projectCrashConverged` 的入参若写成
|
|
1832
|
+
「带一个可选 `crashConverged` 的对象」,SDK 7.4.0 声明的 `{ pending; livePending? }` 与它一个共同
|
|
1833
|
+
属性都没有 ⇒ TS2559。⇒ 入参收 **`unknown`**,窄化全在运行期。
|
|
1834
|
+
🔴 `livePending` 留 `unknown[]` 还有第二条实证理由:**SDK barrel 没有导出 `LivePendingRow`**
|
|
1835
|
+
(7.4.0 `dist/index.d.ts` 的 `export type { … } from "./types.js"` 名单里没有它)⇒ 想用单一真源也
|
|
1836
|
+
拿不到。方向是安全的:真回体的 `LivePendingRow[]` 可赋值到 `unknown[]`。
|
|
1837
|
+
🔴 `livePending` 在本包留 `unknown[]`:它的形今天由 server 独占、本包零消费点,手抄一份结构 =
|
|
1838
|
+
手抄一份会漂的上游形(与 `ToolApprovalFrame.probeCause` 同一条理由)。要读它的端自己窄读。
|
|
1839
|
+
|
|
1840
|
+
### 12b. `CrashConvergedRow` 逐位(server 7.55.0 fixture 直证)
|
|
1841
|
+
|
|
1842
|
+
| 位 | 形 | 说明 |
|
|
1843
|
+
|---|---|---|
|
|
1844
|
+
| `approvalId` | `string`(非空) | 被收敛的那条 ask 的审批 id;端的**行身份** |
|
|
1845
|
+
| `toolName` | `string`(非空) | 崩溃时挂在门上的工具名。**UNTRUSTED-for-display**,端自己消毒控制字符 |
|
|
1846
|
+
| `taskId` | `string`(非空) | 归属 task |
|
|
1847
|
+
| `sessionId` | `string?` | 归属会话(server 在场才发) |
|
|
1848
|
+
| `ts` / `expiresAtMs` | `number` | 原 ask 的铸出 / 到期时刻(epoch ms) |
|
|
1849
|
+
| `decision` | `'denied'` | **闭集判别式**:收敛出来的行按定义就是被判 denied 的那一种 |
|
|
1850
|
+
| `cause` | `'crashed_before_park'` | **闭集判别式**:别族的 denied 行不该混进本面 |
|
|
1851
|
+
| `orphanState` | `'pending' \| 'decided'` | 分桶**主判据**:`pending` = 工具零执行;`decided` = 人当时已批 approve |
|
|
1852
|
+
| `originalDecision` | `'approve'?` | `decided` 臂上人按下的那一下(在场才发) |
|
|
1853
|
+
| `decidedAtMs` | `number?` | 人按下那一下的时刻(在场才发) |
|
|
1854
|
+
| `convergedAtMs` | `number` | 收敛动作自己发生的时刻 |
|
|
1855
|
+
| `resumeSafe` | `boolean` | 🔴 **缺省值,不是铁证** —— 见 12d caveat;它只是分桶四项合取里的一项 |
|
|
1856
|
+
|
|
1857
|
+
### 12c. 端怎么接
|
|
1858
|
+
|
|
1859
|
+
```ts
|
|
1860
|
+
import { projectCrashConverged } from '@sema-agent/client-core'
|
|
1861
|
+
|
|
1862
|
+
// 壳启动 / --resume 之后**读一次**(不需要轮询:这是上一条命的残留,不是流内协议)
|
|
1863
|
+
const env = await client.approvals.list({ signal })
|
|
1864
|
+
const orphans = projectCrashConverged(env) // 入参是 unknown ⇒ 真 AgentClient 的回体直接喂即可
|
|
1865
|
+
|
|
1866
|
+
if (orphans === undefined) {
|
|
1867
|
+
// 🔴 本部署没告诉我这件事(老 server / deps 不在场 / 载体读不出)⇒ **零渲染**。
|
|
1868
|
+
// 绝不渲「0 个」「本次无崩溃遗留」—— 那是替 server 下一个它没说过的断言。
|
|
1869
|
+
} else if (orphans.total === 0 && orphans.dropped === 0) {
|
|
1870
|
+
// server 明说「一条都没有」⇒ 这一档端**可以**渲「没有崩溃遗留」。
|
|
1871
|
+
} else {
|
|
1872
|
+
// orphans.resumeSafe[] / orphans.needsHuman[] / orphans.dropped
|
|
1873
|
+
}
|
|
1874
|
+
```
|
|
1875
|
+
|
|
1876
|
+
| 产出位 | 语义 |
|
|
1877
|
+
|---|---|
|
|
1878
|
+
| `total` | **读得出来的**行数(恒 = 两桶长度之和)。供给总行数 = `total + dropped` |
|
|
1879
|
+
| `resumeSafe[]` | 进这个桶的合取有**五项**:`orphanState==='pending'` ∧ `resumeSafe===true` ∧ `originalDecision` 缺席 ∧ `decidedAtMs` 缺席 ∧ 这一行**不带 accessor**。中间两项是**矛盾闸** —— `pending` 说「一步都没执行」,而那两位是「人已按下 approve」的证据,同时在场就是自相矛盾的载荷(版本斜差 / 畸形体 / 中间层改写都造得出)。最后一项是**顺序重入闸**:带 getter 的行能在展开过程中把靠后的证据位删掉(实测:修前该行落进 resumeSafe,修后落 needsHuman)。三者都只挡「安全」这一侧,**不丢** |
|
|
1880
|
+
| `needsHuman[]` | 其余**一切**:`decided` 臂(人已批,可能落了半截副作用)、`resumeSafe===false`、跨位矛盾形、以及任何说不清的组合。🔴 矛盾形**落这个桶而不是被丢** —— 它是一条真孤儿,而且恰恰是最该给人看的那一条 |
|
|
1881
|
+
| `dropped` | 供给里**类型面读不出来**的行数(缺必填位 / 位的类型不对 / 闭集判别式对不上 / 取属性就抛)。🔴 尺子是**类型面**,不是「数据好不好看」:`ts: NaN`、`toolName: ''` 这类**退化但合型**的值只是难看,本层**不丢**(为一个装饰位吞掉一条真孤儿是更坏的方向);唯一一处越过类型面的严格是 `approvalId` 必须非空(行身份:空 id 去不了重也引用不了)。🔴 `dropped > 0` 本身是一条**要显形的事实**:上游形漂了或载体坏了,端不许静默吃掉(至少留一条 debug 痕) |
|
|
1882
|
+
|
|
1883
|
+
🔴 **缺席 vs 空数组是两件事,两档返回形就不同**(端拿 `=== undefined` 一刀分开,不必读计数):
|
|
1884
|
+
|
|
1885
|
+
| 供给 | 产出 | 端的动作 |
|
|
1886
|
+
|---|---|---|
|
|
1887
|
+
| 键缺席(老 server / deps 不在场) | `undefined` | **零渲染** |
|
|
1888
|
+
| 键在场但载体不是数组(形漂了 / 中间层改写) | `undefined` | **零渲染**(读不出 ≠ 一条都没有) |
|
|
1889
|
+
| 载体**读不出**(已撤销 `Proxy` / `length` 不是非负整数 / 下标取值抛) | `undefined` | **零渲染**;刻意不交还半程结果 —— 一个自己都知道不全的计数,拿去渲「影响了 N 件」比不说话更坏 |
|
|
1890
|
+
| 载体自报行数 **> 100000**(硬上限) | `undefined` | **零渲染**。判在遍历**之前** —— 只判「非负整数」挡不住一个谎报十亿的 `length` trap,而那会让本函数在启动 / `--resume` 路上同步空转(实测:两千万行 = 18.3 秒、两千万次下标读;十亿是「回不来」)。真实队列远在这个量级之下 |
|
|
1891
|
+
| 键在场、空数组 | `{ total: 0, resumeSafe: [], needsHuman: [], dropped: 0 }` | 可以渲「没有崩溃遗留」 |
|
|
1892
|
+
|
|
1893
|
+
⚠️ 两档「零渲染」合流的代价只是**都不说话**;而把「读不出」折成 `{total:0}` 的代价是一句
|
|
1894
|
+
用户会照着去操作的谎 —— 在一条给人判断「能不能闭眼重跑」的面上,那是最坏方向。
|
|
1895
|
+
|
|
1896
|
+
### 12d. 端必读的四条
|
|
1897
|
+
|
|
1898
|
+
1. 🔴 **caveat:`resumeSafe` 是以账本完整为前提算出来的缺省值,不是铁证**(DEBTS L-38 收执逐字)。
|
|
1899
|
+
崩溃现场本来就是账本最可能缺页的时刻。⇒ 文案只许写「**按记录看**可以重跑」,
|
|
1900
|
+
**绝不**写「已确认没有副作用」。
|
|
1901
|
+
2. 🔴 **键在场 ≠ 流内协议上场**。`crashConverged` 只是这一次 `list()` 回体上的一个 additive 键:
|
|
1902
|
+
它既不宣示引擎具备什么能力,也不代表会有一条推送通道再告诉端第二次。**别拿它当能力位读**,
|
|
1903
|
+
也别为它去探一个不存在的 caps 位。
|
|
1904
|
+
3. 🔴 **分桶只有一个合取**,保守侧是设计而不是偷懒:判**不**安全的代价是多问人一句(可恢复);
|
|
1905
|
+
判**错**安全的代价是让人闭眼重跑一件已经落过副作用的事(不可恢复)。凡证不出来一律落
|
|
1906
|
+
`needsHuman`。端**不要**自己按 `orphanState` 或 `resumeSafe` 单读一遍 —— 单读任一键在 2×2
|
|
1907
|
+
真值表上都会至少错一格。
|
|
1908
|
+
4. 🔴 **行是一次性快照,零文案**:每个自有可枚举键**恰读一次**落成数据属性,校验、分桶与交还
|
|
1909
|
+
全部只看这一份快照 —— 所以端读到的每一位都与分桶判据**同源**。additive 键**不剥**(拷的是全部
|
|
1910
|
+
自有可枚举键,上游加键时端照样收得到);代价是交还的**不是入参那一只**(原型链与不可枚举位
|
|
1911
|
+
不随行,对 JSON 回体无差别)。本包不排序、不折叠、不铸任何面向用户的串 —— 措辞、排序、
|
|
1912
|
+
是否上屏全归端。
|
|
1913
|
+
⚠️ 为什么必须快照:此前是「读一遍校验 → 把原对象交出去」,而分桶随后还要再读一遍同样几位。
|
|
1914
|
+
一只**非幂等** getter(不抛,只是两次读返回不同值)就能在这两读之间把已批证据抹掉 ——
|
|
1915
|
+
实测修前 `resumeSafe=1 / needsHuman=0`,交还的行上 `originalDecision` 读出来是缺席,
|
|
1916
|
+
而它在校验那一读里明明是 `"approve"`。跨位矛盾闸只有在「判据与交付物出自同一次读」时才成立。
|
|
1917
|
+
⚠️ 反过来说:**类型面读不出的行会被丢掉**(产物声明成 `CrashConvergedRow`,放一行缺必填位、
|
|
1918
|
+
或位的类型不对的东西过去,就是在类型面撒谎)。丢弃是**计数**的,不是静默的 —— 见上表 `dropped`。
|
|
1919
|
+
🔴 但「丢」的尺子**只到类型面为止**:退化但合型的值(`ts: NaN` / `toolName: ''` / `sessionId: ''`)
|
|
1920
|
+
一律**不丢**,时刻与标签怎么渲归端。为一个装饰位吞掉一条真孤儿,比渲一个丑行坏得多。
|
|
1921
|
+
5. 🔴 **三处不可信读取只认自有数据位**:信封的 `crashConverged`、载体的 `length`、载体的每个
|
|
1922
|
+
数字下标 —— 都走「读自有属性描述符、只取数据描述符的 `value`」。accessor / 缺席 / 只挂在原型上
|
|
1923
|
+
的东西一律**当缺席**,一次别人的代码都不执行。理由与第 7 条同源:`catch` 接得住「抛」,
|
|
1924
|
+
接不住「不返回」。真供给是 `JSON.parse` 的产物,每一位都是自有数据位 ⇒ 对真行零影响。
|
|
1925
|
+
6. 🔴 **本包对畸形宿主也不抛**:触碰载体的**每一处**都在保护内 —— 信封取属性、`Array.isArray()`
|
|
1926
|
+
自己(对一只**已撤销**的 `Proxy` 调用它直接抛 `TypeError`)、`length` 与逐个下标取值、逐行读属性。
|
|
1927
|
+
⇒ 调用点**不需要**给它套 `try`。
|
|
1928
|
+
7. 🔴 **遍历按数字下标,不用载体自己的迭代协议**:`for…of` 会把「这个数组里到底有哪些行」的
|
|
1929
|
+
解释权交给载体自己。一个自带 `Symbol.iterator` 覆盖的数组(中间层改写 / 反序列化器产物都造得出)
|
|
1930
|
+
可以**一条都不产出** —— 本包就会答 `{total:0}`,而端把它读成「server 明说一条都没有」,
|
|
1931
|
+
一条真孤儿就此蒸发;它也可以把一条 `decided` 的危险行**换成**一条 `pending/resumeSafe` 的安全行,
|
|
1932
|
+
直接误导重跑。改按下标读之后这两种伪造都不成立(实测:空迭代器载体修前 `total=0`、修后 `total=1`;
|
|
1933
|
+
换行载体修前产出 `fake-safe`、修后产出 `real-danger`)。
|
|
1934
|
+
⚠️ **边界说清楚**:一只代理仍然能在 `length` / 下标 trap 上撒谎 —— 那与「宿主注入了一个会撒谎的
|
|
1935
|
+
传输层」是同一件事,本包挡不住,也**不假装**挡得住。本条守的是**协议面**:不把「有哪些行」的
|
|
1936
|
+
解释权交给载体的迭代器。
|
|
1937
|
+
8. 🔴 **accessor 位一律不执行、当缺席;带 accessor 的行永远拿不到「安全」判决**:取一个 accessor
|
|
1938
|
+
的值意味着**同步执行别人的代码**,而 `catch` 接得住「抛」、接不住「不返回」—— 一只死循环的
|
|
1939
|
+
getter 能把启动 / `--resume` 路的线程永久钉住(行数上限对这一形无效)。⇒ 那一位当缺席:
|
|
1940
|
+
缺的是**必填位** ⇒ 整行计入 `dropped`(响亮,不是静默);缺的是**可选 / additive 位** ⇒ 行照留,
|
|
1941
|
+
只是那一位在交还的行上缺席。JSON 回体的每一位都是纯数据属性,所以真供给
|
|
1942
|
+
永远撞不上这一条;会撞上它的只有**被中间层合成过**的载荷。判据前移到**描述符**
|
|
1943
|
+
(`Object.getOwnPropertyDescriptors`,这一下不触发任何 getter)—— 因为展开是**按属性顺序执行**
|
|
1944
|
+
getter 的,靠前那一位完全可以在证据位被读到之前把它 `delete` 掉,而单读快照看到的就是
|
|
1945
|
+
「证据不在」。这样的行落 needsHuman,**不丢**。
|
|
1946
|
+
🔴 判据与快照出自**同一次**枚举:描述符判一遍、再展开取一遍值 = 对同一行做了两次独立观察,
|
|
1947
|
+
而一只**不抛**的 `Proxy` 能让两次 `ownKeys` 给出不同答案(第一次亮出 `originalDecision:'approve'`
|
|
1948
|
+
⇒ 判成纯数据行;第二次省掉这个可配置位 ⇒ 快照里证据消失)⇒ 危险行落进 resumeSafe
|
|
1949
|
+
(实测修前 `resumeSafe=['danger']`、`ownKeys` 被调 2 次;修后 `needsHuman=['danger']`、恰 1 次)。
|
|
1950
|
+
⇒ 快照直接由那**同一份**描述符构造 —— 一次观察就没有「另一次」可以与它矛盾。
|
|
1951
|
+
🔴 落键走 `Object.defineProperty`,**绝不**用普通赋值:`'__proto__'` 是一个合法的自有可枚举键
|
|
1952
|
+
(JSON 里就出得来),而 `o['__proto__'] = X` **不是存值** —— 它调用 `Object.prototype.__proto__`
|
|
1953
|
+
的 setter,把 X 装成快照的**原型**。于是一行「自有位全是纯数据」的载荷(accessor 闸天然看不见)
|
|
1954
|
+
能把一只带 `sessionId` getter 的对象注射成快照原型,校验读可选位时那只 getter 就 `delete` 掉
|
|
1955
|
+
快照里的已批证据 ⇒ 危险行落进 resumeSafe(实测修前如此)。`defineProperty` 不触发任何 setter,
|
|
1956
|
+
`__proto__` 因此老老实实成为一个自有数据位(additive 键照样保全),快照原型仍是 `Object.prototype`。
|
|
1957
|
+
🔴 **源行自带原型**也一样只挡「安全」这一侧:快照只枚举**自有**位,挂在原型上的
|
|
1958
|
+
`originalDecision` / `decidedAtMs` 因此进不了快照,跨位矛盾闸就看不见证据 —— 一个普通、
|
|
1959
|
+
无代理、无 accessor、观察完全稳定的 `Object.assign(Object.create({originalDecision:'approve'}), row)`
|
|
1960
|
+
在修前会落进 resumeSafe。⇒ 原型不是 `Object.prototype` / `null` 的行一律落 needsHuman(不丢)。
|
|
1961
|
+
真供给来自 `JSON.parse`,原型恒是 `Object.prototype`,所以这条对真行零影响。
|
|
1962
|
+
🔴 **校验用的字典是 null 原型,分桶判据也不回头再读交付物**:普通 `{}` 上的每一次属性查找都会
|
|
1963
|
+
落到 `Object.prototype`;那份原型一旦被污染(例如 `Object.prototype.sessionId` 被装成一只
|
|
1964
|
+
`delete this.originalDecision` 的 getter),「校验可选位」这一步就会抹掉快照里的已批证据 ⇒
|
|
1965
|
+
危险行落进 resumeSafe(实测修前如此)。null 原型没有上一层可查找,这条路径按构造消失;
|
|
1966
|
+
`orphanState` / `resumeSafe` / 「已批证据在不在」三个判据在校验那一刻就定下来并原样带到分桶,
|
|
1967
|
+
回头再读交付物等于开第二次观察窗口。**交还给端的行仍是普通原型对象**(`hasOwnProperty` 照常用),
|
|
1968
|
+
只是它的每一位都逐字来自那份 null 原型快照。
|
|
1969
|
+
9. 🔴 **坏行不连坐**:一行读不出(缺必填位 / 类型不对 / 已撤销 `Proxy` / 抛错 getter)只让 `dropped`
|
|
1970
|
+
加一,同批的好行照收、`total` 照数。⇒ `undefined` 与「有几行读不出」是**两件事**,端别把它们
|
|
1971
|
+
合流:前者说「本部署没提供这个面」,后者说「面在,只是有 N 行读不出」。
|
|
1972
|
+
|
|
1973
|
+
### 12e. 🔴 非目标与已知边界(对手模型成文,别把它读成缺陷)
|
|
1974
|
+
|
|
1975
|
+
本件的**真供给**是:server 的 JSON 回体 → SDK `JSON.parse` → 端。那条路上的每一位都是**自有数据
|
|
1976
|
+
属性**,没有代理、没有 accessor、原型恒是 `Object.prototype`。本节把「不在射程内」的东西写死,
|
|
1977
|
+
免得下一棒把边界当缺口反复施工。
|
|
1978
|
+
|
|
1979
|
+
**在射程内(本包负责,常驻门逐条钉)**:缺席 vs 空数组不合流 / 分桶五项合取 / 坏行按类型面丢弃
|
|
1980
|
+
并计数且不连坐 / 类型放宽不破坏既有 mock 与真 `AgentClient` / 永不抛 / 不同步空转。
|
|
1981
|
+
|
|
1982
|
+
**不在射程内(明确不做,也不假装做得到)**:**被中间层合成的非 JSON 载荷**、**敌意 `Proxy`**、
|
|
1983
|
+
**原型注射/污染**这一族。本包对它们只承诺三件——**不抛**、**不同步阻塞**、**不产出「可安全重跑」
|
|
1984
|
+
这个判决**(一律落 `needsHuman` 或 `dropped`);但**不承诺**能还原出「真实内容到底是什么」:
|
|
1985
|
+
一只代理在它唯一那次被观察时就可以给出假答案,而那与「宿主自己注入了一个会撒谎的传输层」是同一件
|
|
1986
|
+
事——那种情况下进程里每一个对象都不可信,本包不是能修好它的那一层。
|
|
1987
|
+
|
|
1988
|
+
> 末轮对抗复审判词原文:「可发。未发现真供给可达的实质行为差:server JSON 经 SDK 解析后,
|
|
1989
|
+
> 缺席与空数组保持分离,pending/decided 分桶正确,坏行逐行计数且不连坐,实际 7.55.0 行形不会被
|
|
1990
|
+
> 误丢,既有 mock 类型仍兼容。剩余风险仅属于已成文排除的非 JSON 合成载荷、敌意 Proxy 或原型注射
|
|
1991
|
+
> 族。」
|
|
1992
|
+
|
|
1993
|
+
⇒ 后续复审若再命中这一族,处置是**照此段引用、不再迭代**;要动它必须先动这一段(说明为什么边界
|
|
1994
|
+
变了),而不是直接加一层防御——每加一层都在真供给上零收益,却让这条读面更难读。
|
|
1995
|
+
|
|
1996
|
+
**cli 认领**:壳侧接点(`--resume` 后的一行披露)在下一批。
|
|
1997
|
+
**实现锚**:`src/hitl/crashConverged.ts`(信封形放宽在 `src/hitl/hitlBridge.ts` 的 `ApprovalsResourceLike`)。
|
|
1998
|
+
**常驻门**:`scripts/run-crash-converged-projection-test.mjs`。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.49.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.
|
|
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": "^
|
|
39
|
-
"@sema-agent/sdk": "^7.
|
|
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
|
}
|