@sema-agent/client-core 0.49.0 → 0.51.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 +167 -0
- package/README.md +3 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +17 -1
- package/dist/adapter/activeRunSelfHeal.js +45 -7
- package/dist/engineErrorCodes.d.ts +16 -0
- package/dist/engineErrorCodes.js +17 -0
- package/dist/hitl/askGateWire.d.ts +3 -2
- package/dist/hitl/askGateWire.js +82 -6
- package/dist/hitl/frameRouter.d.ts +13 -0
- package/dist/hitl/parkResolver.d.ts +56 -1
- package/dist/hitl/parkResolver.js +167 -50
- package/dist/hitl/toolApprovalWire.d.ts +3 -0
- package/dist/hitl/toolApprovalWire.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -0
- package/dist/selfOrchestrationDenial.d.ts +210 -0
- package/dist/selfOrchestrationDenial.js +255 -0
- package/docs/INTEGRATION-CLIENTS.md +333 -15
- package/package.json +1 -1
|
@@ -15,25 +15,29 @@
|
|
|
15
15
|
|
|
16
16
|
## §0 版本锚与重扫纪律
|
|
17
17
|
|
|
18
|
-
### 0a. 版本锚(2026-09-
|
|
18
|
+
### 0a. 版本锚(2026-09-03)
|
|
19
19
|
|
|
20
20
|
| 项 | 值 | 真源 |
|
|
21
21
|
|---|---|---|
|
|
22
|
-
| 本包 | `@sema-agent/client-core` **0.
|
|
22
|
+
| 本包 | `@sema-agent/client-core` **0.49.0**(工作树 = npm 最新;S-81 那一批未发,进 `CHANGELOG.md` 的 `## 0.50.0(未发布)` 段) | `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
|
+
| 公开导出面 | **803** 个运行期符号(+ 41 个测试钩;= 工作树当下的值 —— 已发的 `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
|
|
|
30
|
-
⚠️
|
|
31
|
-
`pending` 行)。装 ≤0.47.0 的端注意:0.48.0 新增的 **4 个 additive 导出**
|
|
30
|
+
⚠️ 装 ≤`0.47.0` 的端注意:`0.48.0` 新增的 **4 个 additive 导出**
|
|
32
31
|
(`readSessionMemoryStatus` / `classifyMemoryStatusFailure` / `readCaptureOptOut` / `readLastCapture`)
|
|
33
32
|
在旧版上按名 import 会**在 ESM 实例化当场炸**(具名导出不存在)—— 提货前先抬依赖。
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
装 ≤`0.48.0` 的端同理:`0.49.0` 的 `projectCrashConverged`(L-38,§12)在旧版上不存在。
|
|
34
|
+
⚠️ **工作树里还有一批比 `0.49.0` 更晚的未发 additive 导出**(S-81,见 §13):
|
|
35
|
+
`classifySelfOrchestrationRefusal` / `stripSelfOrchestrationIntent` / `projectWorkflowsGate` /
|
|
36
|
+
`SELF_ORCHESTRATION_RETRY_WITHOUT` / `CAPABILITY_SELF_ORCHESTRATION_REQUIRED`
|
|
37
|
+
(+ **四个** type-only 形 `SelfOrchestrationRefusal` / `SelfOrchestrationDenialReason` /
|
|
38
|
+
`WorkflowsGateProjection` / `WorkflowsGateUnknownDenial`)。它们随下一个版本段发出;
|
|
39
|
+
在此之前按名 import 会在 ESM 实例化当场炸 —— 提货前先抬依赖。
|
|
40
|
+
(L-38 的 `projectCrashConverged` 已随 `0.49.0` 发出,不再是未发件。)
|
|
37
41
|
🔴 **0.48.0 还抬了 peer 地板**(`@sema-agent/sdk >=7.4.0`),这是本版**唯一**的非 additive 面:
|
|
38
42
|
端装 <7.4.0 的 SDK 会看到 peer 警告(运行期不因此变化)。同一条对 0.47.0 那 **3 个 additive 导出**
|
|
39
43
|
成立(`planInteractiveHalt` / `RUN_LEVEL_STOP_ERROR_CODES` / `readDecideCurrentPending`)。同一条对 0.38.0 那 11 个
|
|
@@ -109,7 +113,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
109
113
|
|
|
110
114
|
## §2 公共导出面地图(按域)
|
|
111
115
|
|
|
112
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
116
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**803** 项)。
|
|
113
117
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
114
118
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
115
119
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -119,7 +123,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
119
123
|
|
|
120
124
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
121
125
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
122
|
-
实测:
|
|
126
|
+
实测:803 项 **100% 是运行期导出,零 type-only**。
|
|
123
127
|
|
|
124
128
|
**推论(端必须知道)**:
|
|
125
129
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -129,32 +133,35 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
129
133
|
- `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
|
|
130
134
|
- 同理:L-38 的 `CrashConvergedRow` / `ApprovalsListEnvelope` / `CrashConvergedProjection` 三个形也
|
|
131
135
|
**不在**基线里(纯类型),`src/hitl/crashConverged.ts` 对基线只贡献 `projectCrashConverged` 一项。
|
|
136
|
+
- S-81 同款:`SelfOrchestrationRefusal` / `SelfOrchestrationDenialReason` / `WorkflowsGateProjection` /
|
|
137
|
+
`WorkflowsGateUnknownDenial` 四形**不在**基线里,`src/selfOrchestrationDenial.ts` 对基线贡献
|
|
138
|
+
**4** 项运行期导出(三个函数 + `SELF_ORCHESTRATION_RETRY_WITHOUT`)。
|
|
132
139
|
|
|
133
|
-
|
|
140
|
+
803 项的内部构成(帮助端估读表大小):**237** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
134
141
|
(矩阵、键集、env 名、锚串)而非可调用物;**5** 项是 PascalCase 运行期值
|
|
135
142
|
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError` / `DecideTransportRetryExhaustedError`);
|
|
136
143
|
**41** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
137
144
|
|
|
138
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
145
|
+
### 2b. 域图(16 域,逐域计数之和 = 803)
|
|
139
146
|
|
|
140
147
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
141
148
|
|---|---|---|---|---|---|
|
|
142
149
|
| 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` |
|
|
143
150
|
| 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
|
|
144
|
-
| 3 | **HITL 决断卡链**(§4/§5 主战场) |
|
|
151
|
+
| 3 | **HITL 决断卡链**(§4/§5 主战场) | 136 | `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 崩溃收敛读面) |
|
|
145
152
|
| 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` |
|
|
146
153
|
| 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` |
|
|
147
154
|
| 6 | **请求装配(上行唯一构造口)** | 8 | `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
|
|
148
155
|
| 7 | **通知与 outstanding 台账** | 41 | `installNotificationQueuePort` · `normalizeTaskNotification` · `taskNotificationDedupKeyFromWire` · `registerOutstandingBgTask` / `registerOutstandingWorkflowRun` · `notificationQueuePortMisses` · `subscribeOutstandingWorkflows` · `outstandingDeliverableWorkflowCount` | `task_notification` 归一 + 去重 + 投递进宿主命令队列的**一把闸**;`outstandingDeliverableWorkflowCount()` 是 headless `-p` 的**退出门** | `src/notifications.ts`(11 个 module 台账) |
|
|
149
156
|
| 8 | **工具结果卡** | 25 | `structuredToToolUseResult` · `readAsyncLaunchedAgentReceipt` · `wireOutputToBody` · `parseModelFacingBash` · `getPatchFromContents` · `toolEndResultToUserFrame` · `flattenToolOutput` | 铸端要渲的 `tool_result` 卡体,含客户端 diff hunk(唯一 runtime dep 的用处) | `src/toolResult.ts`、`src/printToolResultFrame.ts`、`src/diff/patch.ts` |
|
|
150
|
-
| 9 | **能力/旋钮 wire 门族** |
|
|
157
|
+
| 9 | **能力/旋钮 wire 门族** | 91 | `projectAgentsForWire` / `awaitTaskAgentsWire` · `hooksForWire` · `mcpConfigsToSpecs` · `ultracodeForRequest` · `resolveWebSearch` + `buildWebSearchEnv` · `attachmentsForRequest` · `projectEffectiveBody` · `classifySelfOrchestrationRefusal` / `stripSelfOrchestrationIntent` / `projectWorkflowsGate` / `SELF_ORCHESTRATION_RETRY_WITHOUT`(S-81,server 7.57.0:上面两条 stamp 腿的**背面** —— 半配置多租户形态下 server 把带 `selfOrchestration` / `settings.ultracode` 的提交 501 拒掉,判型/去键/caps 闸三处都是判定不是文案,详见 §13) | 逐功能的「引擎有没有这个能力 + 这个字段怎么上 wire」投影器,由 `buildTaskRequest` 组装。🔴 `hooksForWire()` 是 **fail-closed**:无 `SettingsPort` ⇒ 返回 `undefined`(信任门,不是取值) | 17 个 `src/*WireCaps.ts` + `src/scratchpadWireCaps.ts`、`src/hooksWireCaps.ts`、`src/goalStopHook.ts`、`src/websearch/searchProviderPresets.ts`、`src/selfOrchestrationDenial.ts`(S-81 拒绝判定层) |
|
|
151
158
|
| 10 | **headless / 部署旋钮 wire** | 61 | `parseSandboxArgv` / `sandboxRequestFields` · `parseLimitsArgv` / `limitsForPrint` · `resolveHeadlessFinalVerify` · `resolveHeadlessPermissionMode` · `resolveHeadlessInteractiveTools` · `armDetachCancel` + `detachCancelArm` + `isDetachArmed` · `withHeadlessR1Reconnect` | `-p`/headless 车道的 env+argv 旋钮。🔴 `detachWire` 是**拆**的补偿:判定与 cancel-arm 台账在库,信号路径的裸 fetch 留宿主(`detachCancelArm()` 是取件口) | `src/sandboxWire.ts`、`scenarioWire.ts`、`finalVerifyWire.ts`、`limitsWire.ts`、`interactiveToolsWire.ts`、`headlessPermissionModeWire.ts`、`headlessReconnectWire.ts`、`detachWire.ts` |
|
|
152
159
|
| 11 | **模型目录与预算** | 67 | `resolveModelCatalog` · `loadCatalogWithSources` · `PROVIDER_PRESETS` / `MODEL_FAMILIES` · `defaultMaxTokensFor` · `getLiveModelCatalog` / `setLiveModelCatalogRefresher` · `providerAuthMethods` / `beginDeviceCodeAuth` · `providerCatalogRows` / `providerCatalogRowDetail` / `providerPresetById`(#244 F4 族D A-028.17:46 家表的规范折表层 —— 全表不重排、诚实缺席「model id typed in」,cli 目录/web 向导同一份折表)· `TIER_ORDER` / `CC_TIER_ALIASES` / `isTier` / `resolveTierBinding`(A-028.18:档位词表+校验+fail-open 降档派生单源;settings 存储归宿主)· `resolveEntryVision` / `computeDeleteBlockers` / `computeDeleteWarnings`(0.38.0 #318 件③ 上收:Model Hub 供给面的三端公共判定 —— vision 生效值+来源三态、删除断链核(拒删+指路)、删除降级后果(照删但必说)。**零 IO**,读盘那半场留各端;`doc === null` 的两义在调用方分流) | 三层 provider 目录解析(线上 URL → 包内预设 → 用户覆盖)+ per-model `maxTokens` 封顶。线上腿需注入 `CatalogFetchJson`,缺席 ⇒ 整条不启用(`online.reason='no-fetch-port'`);缓存落盘经 `CatalogCachePort` | `src/model/{catalog,catalogLoader,providerAuth,providerPresets,providerCatalog,tierVocabulary}.ts`、`src/liveModelCatalog.ts`、`src/modelBudgetRule.ts`、`src/sessionModelLatch.ts`、`src/effortWire.ts` |
|
|
153
160
|
| 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`) |
|
|
154
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 桌面壳里的符号只会零命中,而零命中在这里**既不证真也不证伪**。复核这些断言**不得**拿本仓语料当反证 |
|
|
155
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` |
|
|
156
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` |
|
|
157
|
-
| 16 | **引擎词汇表与包自检** |
|
|
164
|
+
| 16 | **引擎词汇表与包自检** | 49 | `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` |
|
|
158
165
|
|
|
159
166
|
🔴 **`engineErrorCodes` 的开集纪律**(该文件头注逐字):这些 `ReadonlySet` / 前缀谓词一律是**识别表**,
|
|
160
167
|
回答的是「我认不认得这个码」,**绝不是**「合法码只有这些」。消费点 `switch` **必须留 `default`**,
|
|
@@ -1996,3 +2003,314 @@ if (orphans === undefined) {
|
|
|
1996
2003
|
**cli 认领**:壳侧接点(`--resume` 后的一行披露)在下一批。
|
|
1997
2004
|
**实现锚**:`src/hitl/crashConverged.ts`(信封形放宽在 `src/hitl/hitlBridge.ts` 的 `ApprovalsResourceLike`)。
|
|
1998
2005
|
**常驻门**:`scripts/run-crash-converged-projection-test.mjs`。
|
|
2006
|
+
|
|
2007
|
+
---
|
|
2008
|
+
|
|
2009
|
+
## §13 🆕 S-81 selfOrchestration 拒绝的三端同形(server ≥7.57.0;工作树未发)
|
|
2010
|
+
|
|
2011
|
+
server 7.57.0 在**半配置的多租户形态**上收窄了三条。判据只在这一种形态上成立,先把它说准:
|
|
2012
|
+
`REQUIRE_PRINCIPAL=true`(要求 principal)+ `SELF_ORCHESTRATION_ENABLED=true`(引擎侧开着)
|
|
2013
|
+
+ **没有中心侧的准入解析器** ⇒ server 无法为这位 principal 判「能不能用 workflow」,fail-closed。
|
|
2014
|
+
单用户 worker / 接了解析器的多租户部署都**不**走这条路,行为逐字不变。
|
|
2015
|
+
|
|
2016
|
+
| 面 | 7.56 及以前 | 7.57.0 起 |
|
|
2017
|
+
|---|---|---|
|
|
2018
|
+
| `GET /v1/capabilities` 的 `workflows` | `true` | **`false`**(它现在把「本 principal 能不能真用上」也算进去了) |
|
|
2019
|
+
| 同一份 caps | — | 新 additive 键 `workflowsGate: { engineCan: boolean, denial: "entitlement_resolver_absent" \| null }` |
|
|
2020
|
+
| `POST /v1/tasks`(及同闸的 stream 提交)带 `selfOrchestration:true` **或** `settings.ultracode:true` | 受理 | **501 `capability.self_orchestration_required`**(体形与其它 `capability.*` 501 同);去掉这两个键 ⇒ **照常受理** |
|
|
2021
|
+
| 非布尔的 `selfOrchestration` | — | **400**(值不对 ≠ 不给用,**不是**本码) |
|
|
2022
|
+
|
|
2023
|
+
### 13a. 为什么这三处在库里(三端各写一遍必然各错一遍)
|
|
2024
|
+
|
|
2025
|
+
1. **「要不要去键重发」是判定不是文案**。判据是**合取**:`status === 501` **∧** `errorCode`
|
|
2026
|
+
**恰等** `capability.self_orchestration_required`。🔴 这个码是**复用码** —— 按 `capability.`
|
|
2027
|
+
**前缀**分诊会把「别的能力位没接线」的 501 也拖进重发臂,而那些请求去掉这两个键**也不会**
|
|
2028
|
+
变成可受理:白发一次请求 + 给用户一个错误的原因。**无码的 501 判不出**(落 `null`,如实说),
|
|
2029
|
+
码对但状态码不是 501 同样 `null`。
|
|
2030
|
+
2. **「去掉哪两个键」是结构操作不是措辞**。`selfOrchestration` 在**顶层**,`ultracode` 在
|
|
2031
|
+
`settings` 下 —— 两条不同的 stamp 腿(`selfOrchestrationWireCaps.ts` / `ultracodeWireCaps.ts`),
|
|
2032
|
+
端各自手写 `delete` 必然有人漏掉第二处,而漏掉的后果是「重发一次、又被拒一次」:
|
|
2033
|
+
用户看到的是同一个功能坏了两遍。
|
|
2034
|
+
3. **caps 的「缺席」与「关着」是两件事**。老 server 压根没有 `workflowsGate` 这个键;把缺席读成
|
|
2035
|
+
「引擎说不行」是替一台**什么都没说**的 server 下断言([honest-absence-not-fabricated-zero])。
|
|
2036
|
+
反方向的同一种病:把一个**认不得的** `denial` 折成 `null` ⇒ 端渲出「没有任何拒绝」,
|
|
2037
|
+
而真相是「拒了,只是我不认得原因」。
|
|
2038
|
+
|
|
2039
|
+
### 13b. 端怎么接(提交失败 → 去键重发**一次**)
|
|
2040
|
+
|
|
2041
|
+
🔴 **两条提交腿的接法不同,别只抄第一段**(异源对抗复审 R2 采纳)。`sema` 的两条腿在**什么时候
|
|
2042
|
+
把 501 抛出来**这一点上不一样,而判定层要能跑到才有用:
|
|
2043
|
+
|
|
2044
|
+
| 腿 | SDK 形 | 501 什么时候抛 |
|
|
2045
|
+
|---|---|---|
|
|
2046
|
+
| `tasks.submit(req)` / `tasks.streamRaw(req)` | `async` 方法 | **调用即在飞**,在 `await` 处抛 |
|
|
2047
|
+
| `tasks.stream(req, {transientOk:true})` | **async generator**(`async *stream`) | 调用**不发请求**;POST 在 `streamRaw` 里,要等**第一次迭代**(`next()` / `for await` 的第一拍)才跑,501 也在那时才抛 |
|
|
2048
|
+
|
|
2049
|
+
**① 同步腿(`submit` / `streamRaw`)**
|
|
2050
|
+
|
|
2051
|
+
```ts
|
|
2052
|
+
import type { TaskRequest } from '@sema-agent/sdk'
|
|
2053
|
+
import {
|
|
2054
|
+
buildTaskRequest,
|
|
2055
|
+
classifySelfOrchestrationRefusal,
|
|
2056
|
+
stripSelfOrchestrationIntent,
|
|
2057
|
+
} from '@sema-agent/client-core'
|
|
2058
|
+
|
|
2059
|
+
let req: TaskRequest = buildTaskRequest(input, 'interactive') as TaskRequest
|
|
2060
|
+
// 🔴 `...(signal ? { signal } : {})` 而不是 `{ signal }`:SDK 声明的是 `signal?: AbortSignal`,
|
|
2061
|
+
// 本仓(以及任何开了 `exactOptionalPropertyTypes` 的端)显式写入一个可能是 undefined 的值会 TS2379。
|
|
2062
|
+
const opts = (): { signal?: AbortSignal } => ({ ...(signal ? { signal } : {}) })
|
|
2063
|
+
try {
|
|
2064
|
+
return await client.tasks.submit(req, opts())
|
|
2065
|
+
} catch (e) {
|
|
2066
|
+
const denied = classifySelfOrchestrationRefusal(e)
|
|
2067
|
+
if (denied === null) throw e // 🔴 认不得就按普通失败呈现,绝不猜
|
|
2068
|
+
signal?.throwIfAborted() // 🔴 人已经喊停了就别再发第二发(见下)
|
|
2069
|
+
req = stripSelfOrchestrationIntent(req) // 两个键**同时**去掉(单源,端不要自己 delete)
|
|
2070
|
+
transcript.note(disclose(denied.retryWithout)) // 🔴 措辞归端;必须留一行诚实披露(见 13d)
|
|
2071
|
+
return await client.tasks.submit(req, opts()) // 🔴 **只重发一次**;它再 501 就直接外溢
|
|
2072
|
+
}
|
|
2073
|
+
```
|
|
2074
|
+
|
|
2075
|
+
`stripSelfOrchestrationIntent` 有**两个重载**:进一份 SDK `TaskRequest` ⇒ 出来仍是 `TaskRequest`
|
|
2076
|
+
(本函数删的两位在那个型里都是**可选位**,所以「删完仍是一份合法 `TaskRequest`」是类型面成立的事实),
|
|
2077
|
+
所以上面那句「去键之后直接重发」**不需要任何 `as` 强转**;进本包 `buildTaskRequest` 的宽形产物则
|
|
2078
|
+
走宽重载,读法不变。常驻门 §G6 用真 tsc 把这两条路各编一遍(带已知会红的负控)。
|
|
2079
|
+
|
|
2080
|
+
**② 流腿(`tasks.stream`)—— 分类必须放在驱动迭代的 try 里,重发要建**新的** generator**
|
|
2081
|
+
|
|
2082
|
+
```ts
|
|
2083
|
+
// 🔴 两段范式**各自自足**:端只抄第二段也必须编得过(所以 import 在这里再写一遍,不是省略)。
|
|
2084
|
+
import type { TaskRequest } from '@sema-agent/sdk'
|
|
2085
|
+
import {
|
|
2086
|
+
buildTaskRequest,
|
|
2087
|
+
classifySelfOrchestrationRefusal,
|
|
2088
|
+
stripSelfOrchestrationIntent,
|
|
2089
|
+
} from '@sema-agent/client-core'
|
|
2090
|
+
|
|
2091
|
+
async function runOnce(req: TaskRequest, signal?: AbortSignal): Promise<void> {
|
|
2092
|
+
// 🔴 `for await` 的第一拍才真正 POST —— 把 try 套在**迭代**上,不是套在 `stream(...)` 调用上。
|
|
2093
|
+
// 🔴 `signal` 必须**贯穿到每一条** generator,否则外部 AbortController 既停不掉读取、
|
|
2094
|
+
// 也取消不了服务端那条 run(而这是一条会真的跑工具、真的烧 token 的 live task)。
|
|
2095
|
+
// 写法是**条件展开**而不是 `{ signal }` —— 见上一段那条 `exactOptionalPropertyTypes` 注。
|
|
2096
|
+
let sawTerminal = false
|
|
2097
|
+
for await (const ev of client.tasks.stream(req, { transientOk: true, ...(signal ? { signal } : {}) })) {
|
|
2098
|
+
handle(ev)
|
|
2099
|
+
if (ev.type === 'done' || ev.type === 'failed') { sawTerminal = true; break }
|
|
2100
|
+
// 🔴 **每一个非终帧之后立刻查取消**:SSE 的一次读取会缓冲**多帧**,SDK 会连着把它们 yield 出来
|
|
2101
|
+
// (真实时序,不是畸形载荷)。只在整个 `for await` 结束后才查 signal 的话,人已经喊停了却还会
|
|
2102
|
+
// 继续消费同一 chunk 里的后续帧 —— 实测:同一 chunk 是 `turn_start` → `done`,在
|
|
2103
|
+
// `handle(turn_start)` 里 abort,仍会吃掉 `done` 并**报成功**;若后一帧是坏 JSON,取消还会被
|
|
2104
|
+
// 一条 `SyntaxError` 盖掉。终帧那一支先 `break`,让「终帧优先」在这里也成立。
|
|
2105
|
+
signal?.throwIfAborted()
|
|
2106
|
+
}
|
|
2107
|
+
// 🔴 **终帧优先于取消,取消又优先于截断** —— 三者的顺序都是有代价的:
|
|
2108
|
+
// · 已经见到终帧 ⇒ 这条 run **已经有结局了**(可能已经落了副作用)。此时哪怕 signal 也已经
|
|
2109
|
+
// aborted(取消恰好发生在终帧交付之后),报「取消」就是把一个真实终局盖掉,而人多半会再跑
|
|
2110
|
+
// 一遍 ⇒ 重复执行。所以下面两条判都**只在 `!sawTerminal` 时**才轮得到。
|
|
2111
|
+
// · 没见到终帧而 signal 已 aborted ⇒ 这是**用户主动取消**:`readSseFrames` 见到
|
|
2112
|
+
// `signal.aborted` 会**正常** return,于是 `for await` 也正常结束。少了这一判,取消会被改写
|
|
2113
|
+
// 成一条普通流故障 —— 靠 `AbortError` 抑制报错 / 决定要不要重试的端会把取消渲成故障,
|
|
2114
|
+
// 甚至照着「故障」再提交一次。取消的原因必须原样保真。
|
|
2115
|
+
// · 都不是 ⇒ 才是真的**被腰斩**:SDK 的 `stream()` 只在 `done` / `failed` 处 `return`,
|
|
2116
|
+
// 流被中途干净截断(或一帧都没产出)时 `for await` 同样正常结束。少了这一判,一次被腰斩的
|
|
2117
|
+
// live task 会被端静默当成功 —— 这条腿上最难发现的假绿。
|
|
2118
|
+
if (!sawTerminal) {
|
|
2119
|
+
if (signal?.aborted) signal.throwIfAborted()
|
|
2120
|
+
throw new Error('stream ended without a terminal frame (done/failed)')
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
let req: TaskRequest = buildTaskRequest(input, 'interactive') as TaskRequest
|
|
2125
|
+
try {
|
|
2126
|
+
return await runOnce(req, signal)
|
|
2127
|
+
} catch (e) {
|
|
2128
|
+
const denied = classifySelfOrchestrationRefusal(e)
|
|
2129
|
+
if (denied === null) throw e
|
|
2130
|
+
signal?.throwIfAborted() // 🔴 中断闸,见下
|
|
2131
|
+
// 🔴 generator 用过就不能重来:去键之后必须 `stream(...)` **建一条新的**(旧的那只已经出局)。
|
|
2132
|
+
req = stripSelfOrchestrationIntent(req)
|
|
2133
|
+
transcript.note(disclose(denied.retryWithout))
|
|
2134
|
+
return await runOnce(req, signal) // 🔴 仍然**只重发一次**
|
|
2135
|
+
}
|
|
2136
|
+
```
|
|
2137
|
+
|
|
2138
|
+
🔴 **中断闸不是可选的**(异源对抗复审 R3 采纳):首发 501 与去键重发之间隔着一次分类 + 一次
|
|
2139
|
+
写披露,人完全可能就在这个窗口里按下停止。少了 `signal?.throwIfAborted()`,端会在用户已经喊停
|
|
2140
|
+
之后**再发一条会跑工具、会烧 token 的 run**,并且还写一行「已按不带编排的方式继续」——
|
|
2141
|
+
那是实打实的取消语义回归。同理 `signal` 要透传进**两次**调用(第二条 generator 也要受它管)。
|
|
2142
|
+
|
|
2143
|
+
🔴 **把 try 套在 `client.tasks.stream(...)` 这一行上是无效的**:`await` 一个 async generator 对象
|
|
2144
|
+
不会执行函数体,那一行永远不抛 ⇒ 分类器根本不运行 ⇒ 去键重发整条失效,用户直接看到 501。
|
|
2145
|
+
实装 SDK 坐标:`node_modules/@sema-agent/sdk/dist/resources/tasks.js` 的 `async *stream`
|
|
2146
|
+
(它 `await this.streamRaw(req, opts)`,而 POST 在 `streamRaw` 里)。
|
|
2147
|
+
本包常驻门 §G5 用**真 `TasksResource`** + 一只假传输层离线钉住这两条腿的差异。
|
|
2148
|
+
|
|
2149
|
+
🔴 **只重发一次,不做重试环**:第二发若仍是 501,按**普通失败**呈现(不再分类、不再去键)。
|
|
2150
|
+
本包**不提供**重试原语也不代端计数 —— 「重发几次」是端的编排,库只给判据。
|
|
2151
|
+
🔴 **重发的是同一条用户意图,但不是同一个请求**:body 少了两个键 ⇒ 若端在用**幂等键**
|
|
2152
|
+
(server 7.56.0 的 idempotency 语义),这一发必须换一个新键,否则 server 会把它当成前一发的重放
|
|
2153
|
+
而回放那条 501。库不碰这一位(它不在 `TaskRequestLike` 的判定面上),端自己在 `submit` 里给。
|
|
2154
|
+
🔴 **不改 `deferTools`**:`stripSelfOrchestrationIntent` 只做减法、且只减那两处。把 `Workflow`
|
|
2155
|
+
从 `deferTools` 里拿掉或塞回去都是**行为改动**,不是「去掉意图」。
|
|
2156
|
+
|
|
2157
|
+
`stripSelfOrchestrationIntent` 的精确语义(常驻门逐条钉):
|
|
2158
|
+
|
|
2159
|
+
| 输入 | 输出 |
|
|
2160
|
+
|---|---|
|
|
2161
|
+
| 顶层 `selfOrchestration` 在场 | 键被删(不是置 `undefined`) |
|
|
2162
|
+
| `settings.ultracode` 在场,`settings` 还有别的键 | 只删 `ultracode`,**其余子键与它们的值逐字保留** |
|
|
2163
|
+
| `settings` 里只有 `ultracode` | **整个 `settings` 键删掉**(与 `buildTaskRequest`「全缺则整键不出现」同语义) |
|
|
2164
|
+
| `settings` 里没有 `ultracode` | `settings` **原样带出**(同一只对象;本来就空的也不删) |
|
|
2165
|
+
| `settings` 不是对象(串/数组/`null`) | **一律不动**(读不懂的东西不去动它) |
|
|
2166
|
+
| 只挂在**原型**上的 `ultracode` | 不算在场(自有键判) |
|
|
2167
|
+
| 任意 additive 未知键(顶层与 `settings` 两层) | **全保**(「上游已发的键不许被静默剥掉」) |
|
|
2168
|
+
| 再调一次 | **幂等**,同形结果 |
|
|
2169
|
+
| 入参本身 | **原样不动**(返回的是新对象;端还能拿原体做诊断/日志) |
|
|
2170
|
+
|
|
2171
|
+
### 13c. caps 侧:`projectWorkflowsGate` 的返回形
|
|
2172
|
+
|
|
2173
|
+
```ts
|
|
2174
|
+
import { projectWorkflowsGate } from '@sema-agent/client-core'
|
|
2175
|
+
|
|
2176
|
+
const gate = projectWorkflowsGate(caps) // caps = GET /v1/capabilities 的回体
|
|
2177
|
+
if (gate === undefined) { /* 🔴 零渲染:本部署没告诉我这件事 */ }
|
|
2178
|
+
else {
|
|
2179
|
+
// gate.workflows —— **唯一**该拿来决定「给不给 /workflows、ultracode 入口」的位
|
|
2180
|
+
// gate.engineCan —— boolean | undefined(undefined = 这台 server 没有这个闸 ⇒ 两根轴都别渲)
|
|
2181
|
+
// gate.denial —— null | 'entitlement_resolver_absent' | { unknown: string }
|
|
2182
|
+
}
|
|
2183
|
+
```
|
|
2184
|
+
|
|
2185
|
+
| caps 形 | 返回 |
|
|
2186
|
+
|---|---|
|
|
2187
|
+
| 非对象 / `null` / `workflows` 不是布尔 / `workflows` 只在原型上 | `undefined`(**诚实缺席**,端零渲染) |
|
|
2188
|
+
| 有 `workflows`、无 `workflowsGate`(老 server) | `{ workflows, engineCan: undefined, denial: null }` |
|
|
2189
|
+
| `workflowsGate` 在场却读不出(非对象 / accessor) | 同上一档(`engineCan === undefined` 就是端判这一档的那一位) |
|
|
2190
|
+
| `workflowsGate: { engineCan: true, denial: 'entitlement_resolver_absent' }` | `denial` 按**字面量**带出(端可以据此说人话) |
|
|
2191
|
+
| `denial` 缺席 / `null` | `null`(闸明说没有拒绝) |
|
|
2192
|
+
| `denial` 是**别的串** | `{ unknown: <原串> }` —— 🔴 **绝不** `null` |
|
|
2193
|
+
| `denial` 非串 / 是 accessor | `{ unknown: '' }` —— 仍然是拒绝,只是连记号都带不出来 |
|
|
2194
|
+
|
|
2195
|
+
🔴 四处不可信读取(`caps.workflows` / `caps.workflowsGate` / `gate.engineCan` / `gate.denial`)
|
|
2196
|
+
只认**自有数据描述符**,accessor 与只挂在原型上的位**一次都不执行**
|
|
2197
|
+
(`catch` 接得住「抛」,接不住「不返回」——一只死循环的 getter 会把这条读面所在的线程永久钉住)。
|
|
2198
|
+
🔴 **但「不执行」之后落哪一档,四处并不相同**(异源对抗复审 R2 采纳的订正 —— 上一版这里写成
|
|
2199
|
+
一句「一律当缺席」,与上面那张表自相矛盾,端照概括实现就会把「读不出的拒绝」折成「没有拒绝」,
|
|
2200
|
+
正好违反本节的核心不变量):
|
|
2201
|
+
- `caps.workflows` / `caps.workflowsGate` / `gate.engineCan` 三处:accessor **按缺席处理**
|
|
2202
|
+
(依次 ⇒ 整只 `undefined` / 闸缺席档 / `engineCan: undefined`);
|
|
2203
|
+
- `gate.denial` **一处例外**:accessor ⇒ `{ unknown: '' }`,**不是** `null`。键**不在场**才是
|
|
2204
|
+
「闸明说没有拒绝」;在场却执行不得是「拒了,但读不出」——两者处置相反,所以本包的读口刻意是
|
|
2205
|
+
**三态**(缺席 / accessor / 数据)而不是两态。
|
|
2206
|
+
- 只挂在**原型**上的位(不是自有位)四处**都**按缺席处理 —— 那不是这份供给自己说的话。
|
|
2207
|
+
|
|
2208
|
+
真供给来自 `JSON.parse`,每一位都是自有数据位 ⇒ 以上对真 caps 零影响;accessor 载体属 §12e
|
|
2209
|
+
明确排除的「非 JSON 合成载荷」族,这里成文只是为了让公开契约与实现**逐字**对得上。
|
|
2210
|
+
🔴 `{ unknown: … }` 里的串是 **server 的内部词,不是给人看的话**:端可以记进日志/诊断面,
|
|
2211
|
+
**不要**当文案直接上屏。
|
|
2212
|
+
|
|
2213
|
+
### 13d. 端必读的五条(含负控)
|
|
2214
|
+
|
|
2215
|
+
1. 🔴 **重发必须留一行诚实披露**。用户按的是「用 ultracode 跑」,而实际跑的是**没有** workflow
|
|
2216
|
+
编排的那一发 —— 不说 = 让人以为自己要的东西生效了。措辞归端(库零文案),内容至少要说清
|
|
2217
|
+
「这台部署不提供 workflow 编排,已按不带编排的方式继续」+ 去掉了哪两个意图
|
|
2218
|
+
(`SELF_ORCHESTRATION_RETRY_WITHOUT` 就是那份清单)。
|
|
2219
|
+
⚠️ 别把 `reason` 直接翻给用户:今天 server 的 501 体形**不带**机器可读的 `denial` 位 ⇒
|
|
2220
|
+
`reason` 在真 wire 上恒是 `'unknown'`(闭集那一臂是**防御臂**,server 补这一位的当天自动点亮)。
|
|
2221
|
+
要给具体原因,材料在 **caps 侧**的 `projectWorkflowsGate(...).denial`,不在这条错误上。
|
|
2222
|
+
2. 🔴 **负控一:老 server 零渲染**。`projectWorkflowsGate` 返回 `undefined` 时,`/workflows`
|
|
2223
|
+
与 ultracode 入口**照旧**(别渲「不可用」、也别渲「可用」)—— 那台 server 什么都没说。
|
|
2224
|
+
3. 🔴 **负控二:非 501 不重发**。`classifySelfOrchestrationRefusal` 返回 `null` 的一切情形
|
|
2225
|
+
(别的 501 码、无码 501、500/400/503、传输错、非对象抛出物)一律按普通失败呈现。
|
|
2226
|
+
多发一次请求的代价不只是延迟:它会让一个**已经落过副作用**的失败被重放。
|
|
2227
|
+
4. 🔴 **负控三:未知 `denial` 值不崩、也不静默**。`{ unknown: … }` 是一个端必须有分支的形;
|
|
2228
|
+
把它 `?? null` 掉就回到了本节要根除的那句假断言。渲染上的保守做法 = 与闭集成员同档处理
|
|
2229
|
+
(「本部署不提供 workflow 编排」),只是不说具体原因。
|
|
2230
|
+
5. 🔴 **流腿:干净 EOF 不等于跑完**。SDK 的 `tasks.stream()` 只在见到 `done` / `failed` 时 `return`;
|
|
2231
|
+
流被中途干净截断、或一帧都没产出时,`for await` **同样正常结束**。所以 §13b ② 的 `runOnce`
|
|
2232
|
+
带一个 `sawTerminal` 判 —— 少了它,一次**被腰斩**的 live task 会被端静默当成功,而这条腿上
|
|
2233
|
+
恰恰跑着会真的执行工具、真的烧 token 的任务。这一条与本节的去键重发**正交**(它对每一条流都
|
|
2234
|
+
成立),写在这里是因为 §13b 的范式是端照抄的那一份。
|
|
2235
|
+
|
|
2236
|
+
### 13e. 射程边界
|
|
2237
|
+
|
|
2238
|
+
与 §12e 同款:真供给 = server JSON → SDK `JSON.parse` → 端,每一位都是自有数据属性。
|
|
2239
|
+
**被中间层合成的非 JSON 载荷 / 敌意 `Proxy` / 原型注射**这一族**不在射程内** —— 本件对它们只承诺
|
|
2240
|
+
**不抛**、**绝不把一个说不清的 `denial` 折成「没有拒绝」**;**不承诺**还原出「真实内容到底是什么」。
|
|
2241
|
+
后续复审若再命中这一族,处置是**照 §12e 引用、不再迭代**。
|
|
2242
|
+
|
|
2243
|
+
🔴 **但「getter 零执行」这一条只对 `projectWorkflowsGate` 成立,对
|
|
2244
|
+
`classifySelfOrchestrationRefusal` 不成立**(异源对抗复审采纳的订正 —— 一句做不到的承诺比没有承诺更坏):
|
|
2245
|
+
|
|
2246
|
+
| 读的是什么 | 读法 | 承诺 |
|
|
2247
|
+
|---|---|---|
|
|
2248
|
+
| `projectWorkflowsGate(caps)` —— **wire JSON** | 只认**自有数据描述符**(四处) | accessor 与原型位一次都不执行(门钉调用数恒 0) |
|
|
2249
|
+
| `classifySelfOrchestrationRefusal(e)` —— **抛出物** | **普通属性读取**(沿原型、会执行 getter) | 只承诺**不抛** |
|
|
2250
|
+
|
|
2251
|
+
抛出物按设计可能是 SDK 的 `APIError` **类实例**,`status` / `errorCode` 完全可能坐在**原型**上、
|
|
2252
|
+
甚至是原型上的 getter(传输层的写法本包不拥有)。只认自有数据描述符会把一个**读得懂**的错判成
|
|
2253
|
+
读不懂 —— 那正是它必须走普通读取的原因(与 `classifyMemoryStatusFailure` / `classifyRulesFailure`
|
|
2254
|
+
逐字同款)。一只**挂死**的 getter 长在抛出物上时本包挡不住:那与「宿主注入了一个会撒谎的传输层」
|
|
2255
|
+
是同一件事。**能收窄的那一半已经收窄**:`denial` 只在 `501 ∧ 恰码` 两条判据都通过之后才读,
|
|
2256
|
+
所以一个根本不匹配的错误**不会**被跑一次它的 getter(门钉调用数恒 0)。
|
|
2257
|
+
|
|
2258
|
+
🔴 **`SELF_ORCHESTRATION_RETRY_WITHOUT` 运行期是冻结的**(`Object.freeze`,不只是 `as const`)。
|
|
2259
|
+
判决的 `retryWithout` 与它是**同一只引用**(单源的代价),所以它必须真冻:否则任意一个 JS 消费者
|
|
2260
|
+
`splice` 它一下,此后同进程内每一次判决都带着被改写的清单,端照它去键就会删掉别的字段。
|
|
2261
|
+
端**不要**尝试写它(严格模式下当场抛)。
|
|
2262
|
+
|
|
2263
|
+
**cli / web / desktop 认领**:壳侧与两端的接点在各自下一批(表态制)。
|
|
2264
|
+
**实现锚**:`src/selfOrchestrationDenial.ts`(码常量单源在 `src/engineErrorCodes.ts`;
|
|
2265
|
+
两条 stamp 腿仍在 `src/selfOrchestrationWireCaps.ts` / `src/ultracodeWireCaps.ts`)。
|
|
2266
|
+
**常驻门**:`scripts/run-self-orchestration-denial-test.mjs`。
|
|
2267
|
+
|
|
2268
|
+
## §14 🆕 park 再附着的 hop 预算 = 连续非进展轮(0.51.0;L-80)
|
|
2269
|
+
|
|
2270
|
+
### 14a. 语义(端不用改也要知道)
|
|
2271
|
+
|
|
2272
|
+
`bridgeAskUserQuestionGates` 对每一次 park 都会 decide/呈卡后**再附着** `runs.events`。修前每次 park 吃一格预算
|
|
2273
|
+
(24 格),模型在一个 turn 里问 25 次就撞 `gate hop limit (24) exceeded`——与真因无关。现在预算只数**连续非进展轮**:
|
|
2274
|
+
一轮再附着之后那段流里有没有 host 推进帧(`text_delta` / `reasoning_delta` / 非孙代 `tool_start`)。有 ⇒ 清零;
|
|
2275
|
+
没有 ⇒ +1;超过 `MAX_GATE_HOPS`(=2)⇒ 收场。「决了但引擎原样 park 回来」(坐标失配、`resume.tool_unavailable`
|
|
2276
|
+
这类 reopen 再 park)因此天然判非进展;「模型重试同一失败调用、每次真被门」每轮都有推进帧 ⇒ 不限次(CC 同款)。
|
|
2277
|
+
|
|
2278
|
+
### 14b. 端怎么接(两件,additive)
|
|
2279
|
+
|
|
2280
|
+
1. **`deps.onParkReattach?: (e: ParkReattachNotice) => void`** —— 每一轮非进展 reattach 调一次
|
|
2281
|
+
`{ attempt, max, reason }`。渲成瞬态状态行(cli:Spinner 行「re-attaching to the parked run (attempt n/m: <reason>)」),
|
|
2282
|
+
同 reason 覆盖不叠行,turn 结束清。缺席 = 只留 debug(修前形)。**不要**据它做任何处置。
|
|
2283
|
+
2. **`ReopenCardVerdict.decidedWithoutCard`** —— 端的 `reopenAskPark` 若链**没呈卡但已成功决断**(sync-allow /
|
|
2284
|
+
规则直决),返 `{ reopened: false, decidedWithoutCard: true }`。库**不**据此直接重发:它只证明「决断受理」,
|
|
2285
|
+
库先用 `runs.get` 有界等 claim 释放(与 cancel 释放窗同源,10s):释放 ⇒ `SelfHealOutcome{kind:'ask-decided-without-card', released:true}`,
|
|
2286
|
+
`selfHealSubmissionDisposition` = `resending`;没释放但 run 在跑 ⇒ 走既有 running 臂(三选卡或「等它跑完」);
|
|
2287
|
+
仍 parked / 读不出 / 端没给 `runs.get` ⇒ `ask-decided-release-unknown`(不重发、不谎报失败:「should be resuming … wait a moment and send it again」)。**决断成功之后绝不再落 `ask-reopen-failed`**。
|
|
2288
|
+
`ask-decided-release-unknown` 的 `selfHealSubmissionDisposition` = `not-delivered`(闭集**不加值**):用户消息按文案
|
|
2289
|
+
手动重发;系统注入件走既有 not-delivered 处置(端:归因上屏 + 问一次待决队列,不回灌不盲发)。库**不**承诺
|
|
2290
|
+
「稍后自动投递」—— 没有释放驱动的重投机制之前,承诺就是谎报(对抗复审 r4)。端的自动重发若按 kind 等值判
|
|
2291
|
+
(cli `running-cancelled` 那条),需同批把这个 kind 列进去,否则文案说「re-sending」而消息没发 = 谎报。
|
|
2292
|
+
裸 `{reopened:false}` 一字不变。
|
|
2293
|
+
|
|
2294
|
+
### 14c. 端必读的四条
|
|
2295
|
+
|
|
2296
|
+
- 终帧两码:`hitl_unanswered`(这一轮没决断落地:读空 / 传输败 / 无卡)文案带真因与出路(「… decide it on the card
|
|
2297
|
+
when it is shown again, or cancel the run」);**`hitl_stalled`**(新)= 这一轮 decide 已成功但引擎连续 N 次把同一只 call
|
|
2298
|
+
原样 park 回来 —— 文案说「decision was accepted … sema did not observe the run move on」,**不**说 could not be answered /
|
|
2299
|
+
still parked。两码都不再含 `gate hop limit`;端若曾 grep 这句做判别,改锚 `errorCode`。
|
|
2300
|
+
- 触顶时若上一轮**没**呈过卡,库会现读队列再呈一次收场卡(走同一个 `ApprovalCardPort`);上一轮呈过就**不**重复问。
|
|
2301
|
+
- 已解决臂同因限次仍是 1(第二次同因即收场);`retryExhausted`(decide 出站瞬断耗尽)臂同因连续 2 次即收场,
|
|
2302
|
+
两臂都经 `onParkReattach` 告知。
|
|
2303
|
+
- 进展判决分两半:帧半场(resolve 前,只复位同因账;有推进帧时**不在解析前触顶**)+ 身份半场(resolve 后,权威:
|
|
2304
|
+
这一轮真解析到的 pending 行;同一只 call 原样回来哪怕夹文本帧也不算进展;没解析到行的轮次一票否决(例外:上一轮在**新 call** 上真决断落地过 —— 那时的「首读空」是取件失败不是空转,推进帧照算进展;
|
|
2305
|
+
同一只 call 原样回来再决一次不算);帧上身份陈旧/缺席都不参与,也不回填)。提交后超上限 ⇒ 立即收场(该轮已呈过卡,不再呈)。tool-less park
|
|
2306
|
+
无身份只看帧,另有 `MAX_TOTAL_PARKS=64` 硬兜底 —— **只数解析不出身份的 park**(每轮都解析到新 call 的门不吃这格,CC 同款问 N 次答 N 次;命中终帧「N parks … resolved to no identifiable approval」)。
|
|
2307
|
+
- `MAX_GATE_HOPS` / `MAX_TOTAL_PARKS` / `nextHopBudget` / `HopBudget` / `HopRound` / `ParkReattachNotice` /
|
|
2308
|
+
在公面上;`nextHopBudget` 是纯函数,
|
|
2309
|
+
端可用它在自己的诊断面复算。
|
|
2310
|
+
|
|
2311
|
+
### 14d. 射程边界
|
|
2312
|
+
|
|
2313
|
+
引擎侧 reopen 类 re-park(`/decide` 200 `{retriable:true}` + 账本 `suspended`,server [6217])本库只判「非进展」并把
|
|
2314
|
+
最后一轮的 errorCode 带进终帧;为什么该副本工具不可达是引擎的事。plan_review 腿无「无卡直决」形(规则不决 plan),
|
|
2315
|
+
`planReviewArm` 不加此臂。
|
|
2316
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.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",
|