@sema-agent/client-core 0.68.0 → 0.68.1
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 +137 -0
- package/README.md +2 -1
- package/dist/adapt/arms.js +40 -7
- package/dist/adapt/textStream.d.ts +102 -1
- package/dist/adapt/textStream.js +169 -6
- package/dist/adapt.js +4 -1
- package/dist/adapter/downstream/eventToSdkMessage.js +17 -7
- package/dist/controlRouter.d.ts +16 -0
- package/dist/controlRouter.js +6 -0
- package/dist/request/taskRequest.d.ts +6 -6
- package/dist/request/taskRequest.js +45 -0
- package/dist/seam.d.ts +78 -8
- package/dist/seam.js +16 -2
- package/dist/toolRoster.d.ts +34 -2
- package/dist/toolRoster.js +16 -2
- package/docs/INTEGRATION-CLIENTS.md +267 -1
- package/package.json +1 -1
package/dist/adapt.js
CHANGED
|
@@ -38,7 +38,10 @@ export const ADAPTER_COVERAGE = {
|
|
|
38
38
|
'message_committed',
|
|
39
39
|
// #318 件①(2026-08-21):`engine_notice` 引擎结构化通告 —— 投 chrome(开集,一个码都不判)。
|
|
40
40
|
'engine_notice',
|
|
41
|
-
// #323 / core #447:assistant 散文段边界 →
|
|
41
|
+
// #323 / core #447 + L-310(0.68.1 改口):assistant 散文段边界 → **整段替换文本缓冲** +
|
|
42
|
+
// chrome text_segment_end(子流断闸在替换之前)。修前这里写「本批**不**动文本缓冲」——
|
|
43
|
+
// server 7.75.3 起 `text_end.content` 经脱敏器而 `text_delta` 仍逐字,不动缓冲 = 未脱敏字节
|
|
44
|
+
// committed 进本地转录;替换语义见 `adapt/textStream.ts` 的 `replaceAnswerSegment` 头注。
|
|
42
45
|
'text_end',
|
|
43
46
|
'prompt_suggestions',
|
|
44
47
|
'retry_status',
|
|
@@ -877,8 +877,14 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
877
877
|
*
|
|
878
878
|
* ── 这是什么 ────────────────────────────────────────────────────────────────────────────────
|
|
879
879
|
* 「assistant 的这一段散文写完了」—— 模型关掉了那个 text content block,而它的字节刚刚以
|
|
880
|
-
* `text_delta` 流过。core 的臂注逐字:`content` = **该段的权威全文**(
|
|
881
|
-
*
|
|
880
|
+
* `text_delta` 流过。core 的臂注逐字:`content` = **该段的权威全文**(取自 brain 自己的累加),
|
|
881
|
+
* 消费方**据它提交这一段**,而不是信自己的 delta 缝合。
|
|
882
|
+
*
|
|
883
|
+
* 🔴 **「与 delta 拼接逐字节相等」这句自 server 7.75.3 起不再成立**(L-310,0.68.1 订正;此前本注
|
|
884
|
+
* 照抄 core 臂注的那半句):`text_end.content` 与 `result`/账本走**同一只脱敏器**,而 `text_delta`
|
|
885
|
+
* 仍逐字(跨 chunk 的凭据无法就地判,流式脱敏是独立设计件)⇒ 含凭据形的段上两者**字节不同**。
|
|
886
|
+
* server `ASSISTANT-WIRE-CONTRACT` §5.1 live 面逐字要求消费端「在 `text_end` 到达时以它**整段
|
|
887
|
+
* 替换**已攒的 delta,而不是只当段界信号」。
|
|
882
888
|
*
|
|
883
889
|
* 🔴 **它的存在意义 = 让消费方撤掉 idle-flush 启发式**(core 臂注点名的那件事)。本包的
|
|
884
890
|
* `adapt/textStream.ts` 至今用「静默 1.5s + 句末/段末边界 + 每段一刀」猜段边界(#323 症状① 的止血
|
|
@@ -893,11 +899,15 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
893
899
|
* 信号(段边界 + 权威全文),静默丢 = 把一个真实能力缺口做成 fail-open,正是本文件对
|
|
894
900
|
* `approval_request` 那段头注点名的病形。⇒ 投中性内部臂,宿主(壳 REPL 桥 / web / desktop 座位层)
|
|
895
901
|
* 在臂上读它、决定何时提交段。
|
|
896
|
-
* ·
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
900
|
-
*
|
|
902
|
+
* · **包内接线**(0.68.1 / L-310 起):`adapt/arms.ts` 的 `textSegmentEndArm` 拿这条内部臂调
|
|
903
|
+
* `adapt/textStream.ts` 的 `replaceAnswerSegment(content, frame)` —— **整段替换**段缓冲与活体尾巴,
|
|
904
|
+
* 再把 `diverged` / `committedPrefixDiverged` / `committedPrefixLen` 三个 never-false 键挂到
|
|
905
|
+
* chrome `text_segment_end` 上交给宿主。
|
|
906
|
+
* · 📋 **仍然留白的那一件**:让 `textStream` 真的**撤掉** idle-flush 启发式。core 臂注明写这是
|
|
907
|
+
* 「诚实缺席」的位(只有会报块结束的 brain 才发它,自定义 brain 可能整条流一帧都没有),所以
|
|
908
|
+
* 消费方必须按**每条流**判「这条流带不带边界帧」再决定退不退启发式 —— 那是一条带状态的策略,
|
|
909
|
+
* 属行为面改动,按宪法三问单独走。权威替换**兼容**启发式(半段已被 flush 那一形由两个前缀键
|
|
910
|
+
* 如实交代),不是它的继任。
|
|
901
911
|
*
|
|
902
912
|
* ── 畸形与空段(fail-closed 方向 + 对位 core 的「空段无帧」)────────────────────────────────
|
|
903
913
|
* · `content` **非串** ⇒ `malformed`:承重位读不动(wire 是 JSON,SDK 只 JSON.parse 不校型)。
|
package/dist/controlRouter.d.ts
CHANGED
|
@@ -81,6 +81,10 @@ export interface ControlClientLike {
|
|
|
81
81
|
* 可达其中 4 个:`not_running`(409)/ `invalid_content`(422)/ `queue_full`(409,core 5.14.0 队列
|
|
82
82
|
* 化后新出)/ `duplicate_input_id`(409,调用方带 `Idempotency-Key` 时可达);另外 3 个来自子代
|
|
83
83
|
* steer/resume 面与 workflow agent steer 面(`ambiguous_target` / `ambiguous_label` / `still_running`)。
|
|
84
|
+
* 🔴 **[7226] 包侧缺口 ①(0.68.1)**:码表补**第 8 码** `steering.blocked_by_hook`(422,部署
|
|
85
|
+
* `userPromptSubmit` 门拦下;live 腿可达)。它此前落开集兜底位 `steering_other` —— 兜底位的判词
|
|
86
|
+
* 逐字是「别按成员猜它的意思」,而这一码的处置恰恰是**明确的**(输入未受理、改内容自由重试)⇒
|
|
87
|
+
* 兜底在这一位上不是「安全降级」,是把一条能自救的拒绝渲成一条不知道怎么办的拒绝。
|
|
84
88
|
* SDK 6.3.0 侧已把整族改成**前缀分派**(`SteeringError` 基类),所以「没认全」的后果不是崩溃,
|
|
85
89
|
* 而是那些码原样裸抛给壳 —— 壳只 `catch (e instanceof ControlSafetyError)` 就漏在外面。
|
|
86
90
|
*
|
|
@@ -101,6 +105,18 @@ export type ControlSafetyCode =
|
|
|
101
105
|
| 'ambiguous_target'
|
|
102
106
|
/** 对一个**还在飞**的子代调了 resume;处置=改调 steer,或等它 settle。`not_running` 的反面。 */
|
|
103
107
|
| 'still_running'
|
|
108
|
+
/**
|
|
109
|
+
* 部署的 `userPromptSubmit` 门拦下了这条输入([7226] 包侧缺口 ①,0.68.1;server 契约 (2) 表
|
|
110
|
+
* 第 8 行;core 5.62 design/373 §4.3)。block / 超时 / 崩溃**同码 fail-closed**,成因由 message
|
|
111
|
+
* 判别(携 hook 自己的 bounded reason)。
|
|
112
|
+
* 🔴 **输入未被受理**:没有 `human_input` 帧、`inputId` 不入账 ⇒ 处置 = **改内容自由重试**。
|
|
113
|
+
* 🔴 它与 `invalid_content` / `steering_other` 都**不许合并**:前者是「正文违规,SURFACE 别
|
|
114
|
+
* strip-and-retry」,后者的判词逐字是「别按成员猜它的意思」—— 把一条**能自救**的拒绝塌进这两位
|
|
115
|
+
* 任何一位,UI 都会把「改一句话再发」说成「你没救了」。
|
|
116
|
+
* ⚠️ `/steer` 的 **park 腿结构性不可达本码**(hook 对 parked 转向的拦截发生在 resume 再投递时刻,
|
|
117
|
+
* 走 `steering.parked_input_blocked` 通告);本路由器调的是 live 腿,所以这一位在这里可达。
|
|
118
|
+
*/
|
|
119
|
+
| 'blocked_by_hook'
|
|
104
120
|
/** 开集兜底:`steering.` 前缀但本表不认得的**未来**码(SDK 前缀分派同款姿势)。
|
|
105
121
|
* 处置=按「这条 steer 没落地」呈现,并把 `cause.errorCode` 原样打进日志,别按成员猜语义。 */
|
|
106
122
|
| 'steering_other'
|
package/dist/controlRouter.js
CHANGED
|
@@ -243,6 +243,11 @@ const STEERING_CODE_TO_SAFETY = new Map([
|
|
|
243
243
|
['steering.ambiguous_target', 'ambiguous_target'],
|
|
244
244
|
['steering.ambiguous_label', 'ambiguous_target'],
|
|
245
245
|
['steering.still_running', 'still_running'],
|
|
246
|
+
// [7226] 包侧缺口 ①(0.68.1):server 契约 (2) 表第 8 码(422)。SDK 8.8.0 **没有**专属子类
|
|
247
|
+
// (`errors.d.ts` 的 `Steering*Error` 只有六只)⇒ 只能按 `errorCode` 认;认不出时它会落开集位
|
|
248
|
+
// `steering_other`,而那一位的判词是「别猜它的意思」—— 恰好把一条「改内容重试即可」的拒绝
|
|
249
|
+
// 说成没救。所以码表必须点名它,不能靠前缀兜底。
|
|
250
|
+
['steering.blocked_by_hook', 'blocked_by_hook'],
|
|
246
251
|
]);
|
|
247
252
|
/** SDK 的 typed 子类名兜底(错误对象被传输层剥掉 `errorCode` 时仍认得族;`SteeringError` 基类本身
|
|
248
253
|
* = 「是 steering 族但没有专属子类」⇒ 落开集臂)。 */
|
|
@@ -261,6 +266,7 @@ const STEERING_ADVICE = new Map([
|
|
|
261
266
|
['duplicate_input_id', 'this Idempotency-Key is already parked with DIFFERENT steering content — reissue with a fresh key, resending verbatim will never succeed'],
|
|
262
267
|
['ambiguous_target', 'more than one live target matches — address it uniquely (sub-agent: parentToolCallId; workflow: a more specific label); retrying verbatim yields the same result'],
|
|
263
268
|
['still_running', 'the target is STILL RUNNING — steer it instead of resuming, or wait for it to settle (this is the exact opposite of not_running: never collapse the two)'],
|
|
269
|
+
['blocked_by_hook', "this deployment's userPromptSubmit gate refused the input (block, timeout and crash all report this one code, fail-closed) — the input was NOT accepted: no human_input frame, the inputId is not on the ledger, so editing the text and sending again is a normal retry, not a duplicate"],
|
|
264
270
|
['steering_other', 'an unrecognized steering.* refusal (open set) — the steer did NOT land; log the errorCode verbatim, do not guess its meaning'],
|
|
265
271
|
]);
|
|
266
272
|
/**
|
|
@@ -90,6 +90,12 @@ export interface TaskRequestInput {
|
|
|
90
90
|
interactiveTools?: false;
|
|
91
91
|
/** 一次性提交声明(`true|undefined` 型:opt-out=不传,绝不发 false——wire 上与缺席同义,发小的那个)。 */
|
|
92
92
|
oneShot?: true;
|
|
93
|
+
/**
|
|
94
|
+
* 「本会话永远不进长期记忆」的**声明形**([7226] ⑤;server §12.4)。
|
|
95
|
+
* 🔴 型面就写成 `'off'`:上游是**单成员闭集**(没有 `"on"`),给它一个宽串型等于邀请调用方发
|
|
96
|
+
* 一个 400 回来。JS 调用方(本包是发出去的 npm 公开面)绕过型面时由构造器再判一次值。
|
|
97
|
+
*/
|
|
98
|
+
memoryCapture?: 'off';
|
|
93
99
|
/** rewind 三件(E18);端自己判空。 */
|
|
94
100
|
rewind?: Record<string, unknown>;
|
|
95
101
|
/** 仓库坐标串(scan/code-review 场景;今日唯一供给方 web 的 Repository 输入=url/`owner/name` 串。
|
|
@@ -112,12 +118,6 @@ export interface TaskRequestInput {
|
|
|
112
118
|
outputStyle?: string;
|
|
113
119
|
};
|
|
114
120
|
}
|
|
115
|
-
/**
|
|
116
|
-
* 合一后的请求构造器 —— **按车道出两形**,字段集差异全部由 `REQUEST_FIELD_MATRIX` 决定。
|
|
117
|
-
*
|
|
118
|
-
* 🔴 行为纪律:本函数**不做任何统一**。print 没有 `ultracode` 就是没有(表里 `gap:true` 记着账),
|
|
119
|
-
* 补齐要另立项 —— 在这里顺手加一行,就是把「合一」偷换成「行为改动」。
|
|
120
|
-
*/
|
|
121
121
|
export declare function buildTaskRequest(input: TaskRequestInput, lane: RequestLane): TaskRequestLike;
|
|
122
122
|
/** `applyLiveRequestDefaults` 的宿主输入(端解析好的值,同样零取值方式)。 */
|
|
123
123
|
export interface LiveDefaultsInput {
|
|
@@ -42,6 +42,15 @@ export const REQUEST_FIELD_MATRIX = [
|
|
|
42
42
|
{ field: 'debate', lanes: ['interactive', 'print'], live: true, why: 'council 的 debate 变体开关(server 读 `=== true`)' },
|
|
43
43
|
{ field: 'rounds', lanes: ['interactive', 'print'], live: true, why: 'debate 轮数(server clamp [1,3],非有限数被 server 归 undefined——本层原样透传不预 clamp)' },
|
|
44
44
|
{ field: 'lenses', lanes: ['interactive', 'print'], live: true, why: 'code-review 镜头集(passthrough 同 repo;形状由 server 场景定义,本层不闭集)' },
|
|
45
|
+
// ── memoryCapture([7226] 包侧缺口 ⑤,0.68.1)──────────────────────────────────────────────
|
|
46
|
+
// 🔴 **单成员闭集**:server 契约 §12.4 逐字「`memoryCapture?: "off"`。单成员闭集(**没有**
|
|
47
|
+
// `"on"`)」—— 所以本层只在值**恰是** `'off'` 时 stamp;`'on'` / 坏拼写一律不上 wire
|
|
48
|
+
// (坏拼写 server 判 400 `request.field_invalid`,本层不预铸第二判官,但也不替它买路)。
|
|
49
|
+
// 🔴 与**中途翻转 verb** `POST /v1/runs/:id/memory/capture-optout`(§12.5,能力位
|
|
50
|
+
// `runMemoryCaptureOptOut`)是**同一条一次性单向记录的两个口**,不是两个轴:verb 管这一条
|
|
51
|
+
// 在飞的 run,本键管「本会话的下一次新提交」。非 live 的 verb(parked / 终态 / 他副本)回
|
|
52
|
+
// 409 `steering.not_running` 并**指路本键** —— 端没有本键就接不住那条指路。
|
|
53
|
+
{ field: 'memoryCapture', lanes: ['interactive', 'print'], live: true, why: '本会话永远不进长期记忆的**声明形**(server §12.4 body 键,单成员闭集 "off");两条车道都是提交面 ⇒ 都能声明。与 memoryWrite:false 不是一个轴(那是「本 run 只读平面」)。⚠️ parked 续跑腿结构性拿不到声明(决策/续跑端点从持久 body 重建 spec),那是引擎侧事实,不是本层的漏' },
|
|
45
54
|
// ── print 独有(§8-5 认定的**有理由**的差异)──────────────────────────────────────────────
|
|
46
55
|
{ field: 'finalVerification', lanes: ['print'], live: true, why: 'P1-1 终验:无人值守车道才需要引擎自证;交互 REPL 由人当场看结果。#106 裁 B(让位+告知)后交互面默认关' },
|
|
47
56
|
{ field: 'limits', lanes: ['print'], live: true, why: 'P2-3-b:`-p` 的预算护栏(--max-* flag 族),交互 REPL 由人随时 Esc' },
|
|
@@ -390,8 +399,40 @@ const resolvedSnapshotForWire = (resolved) => {
|
|
|
390
399
|
* 🔴 行为纪律:本函数**不做任何统一**。print 没有 `ultracode` 就是没有(表里 `gap:true` 记着账),
|
|
391
400
|
* 补齐要另立项 —— 在这里顺手加一行,就是把「合一」偷换成「行为改动」。
|
|
392
401
|
*/
|
|
402
|
+
/**
|
|
403
|
+
* ⑤ 的**响亮拒**([7226] 包侧缺口 ⑤;异源对抗复审轮五 finding① 采纳)。
|
|
404
|
+
*
|
|
405
|
+
* 🔴 修前这里是「不是 `'off'` 就整键不 stamp」——**静默删键**,而删掉的恰是一条**隐私声明**:
|
|
406
|
+
* 一个把 `/memory-capture off` 打成 `OFF` 的会话,请求照发、引擎照常采集,**没有任何人会知道**。
|
|
407
|
+
* 🔴 上游把这条写死了(装机 core `dist/core/memory.d.ts` 的 `capture?: "off"` 头注**逐字**):
|
|
408
|
+
* 「Any other value — `"on"`, `"OFF"`, booleans, garbage — is REFUSED loudly
|
|
409
|
+
* (`config.memory_capture_spelling`), never read as either state (**a privacy request must not be
|
|
410
|
+
* dropped by a typo**, and capture must not be switched off by one either)」;server 契约 §12.4
|
|
411
|
+
* 同样写明坏拼写 400 `request.field_invalid`,并点名「把一条隐私请求按打字错误静默丢掉恰是**禁的方向**」。
|
|
412
|
+
* ⇒ 提前删键 = 把上游那道响亮门**绕过去**,方向正好反了。
|
|
413
|
+
* 🔴 与本文件 `resolvedSnapshotForWire` 对畸形权限快照的处置**同一条纪律**(那里也是抛,理由逐字
|
|
414
|
+
* 是「降空 = 让请求带着被剥掉的权限面发出去」)——两处都是**声明方向**的位:丢了没人看得见。
|
|
415
|
+
* ⚠️ `undefined` / `null` = **合法缺席**(端没有这个入口 / 没有声明),照旧不 stamp,不拒。
|
|
416
|
+
* ⚠️ **不分车道、不受 live 门**:拒绝不是「stamp 一个键」,不改请求形状;而一条打错字的隐私声明
|
|
417
|
+
* 在哪条车道上都不该被默默放行。
|
|
418
|
+
* (本包是发出去的 npm 公开面:JS 调用方与版本偏斜的宿主都到得了这里,型面拦不住。)
|
|
419
|
+
*/
|
|
420
|
+
const refuseBadMemoryCapture = (v) => {
|
|
421
|
+
if (v === undefined || v === null)
|
|
422
|
+
return;
|
|
423
|
+
if (v === 'off')
|
|
424
|
+
return;
|
|
425
|
+
throw new TypeError('buildTaskRequest: memoryCapture 只接受字面量 `\'off\'` 或缺席(undefined/null)—— ' +
|
|
426
|
+
`收到 ${shapeTag(v)}。这一位是**会话级隐私声明**(「本会话永远不进长期记忆」),` +
|
|
427
|
+
'上游是**单成员闭集**(没有 `"on"`)且对坏拼写**响亮拒**(core `config.memory_capture_spelling` / ' +
|
|
428
|
+
'server 400 `request.field_invalid`)。本层若把坏值静默删键,请求会照发、引擎会照常采集,' +
|
|
429
|
+
'而声明人不会知道自己的 opt-out 丢了 —— 把一条隐私请求按打字错误静默丢掉是**禁的方向**。' +
|
|
430
|
+
'错误文本只报形状,不回显值。');
|
|
431
|
+
};
|
|
393
432
|
export function buildTaskRequest(input, lane) {
|
|
394
433
|
const live = input.live;
|
|
434
|
+
// ⑤ 响亮拒排在最前面:一条打错字的隐私声明绝不许被后面任何一条「不 stamp」路径吞掉。
|
|
435
|
+
refuseBadMemoryCapture(input.memoryCapture);
|
|
395
436
|
/** stamp 门:字段在本车道的表里 ∧(非 live-gated ∨ 本次是 live 车道)∧ 值非空。 */
|
|
396
437
|
const on = (field, value) => {
|
|
397
438
|
if (value === undefined || value === null)
|
|
@@ -469,6 +510,10 @@ export function buildTaskRequest(input, lane) {
|
|
|
469
510
|
? { interactiveTools: false }
|
|
470
511
|
: {}),
|
|
471
512
|
...(on('oneShot', input.oneShot) && input.oneShot === true ? { oneShot: true } : {}),
|
|
513
|
+
// ⑤ 单成员闭集:值**恰是** `'off'` 才 stamp。表外拼写在上面已经**响亮拒**(见 `refuseBadMemoryCapture`)。
|
|
514
|
+
...(on('memoryCapture', input.memoryCapture) && input.memoryCapture === 'off'
|
|
515
|
+
? { memoryCapture: 'off' }
|
|
516
|
+
: {}),
|
|
472
517
|
...(Object.keys(settingsOut).length > 0 ? { settings: settingsOut } : {}),
|
|
473
518
|
};
|
|
474
519
|
return out;
|
package/dist/seam.d.ts
CHANGED
|
@@ -600,11 +600,53 @@ export interface WiringManifestChromeEvent {
|
|
|
600
600
|
* text content block。CC 对位:CC 在 provider 的 `content_block_stop` 上把每个写完的块当作一条
|
|
601
601
|
* 独立 assistant 消息 —— 块结束**就是**分段信号,本臂把同一个边界搬到了 wire 上。
|
|
602
602
|
*
|
|
603
|
-
* 🔴 **宿主消费义务**(
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
603
|
+
* 🔴 **宿主消费义务**(② ③ ④ 可选、fail-soft;① 自 0.68.1 起是**安全面义务**,不实现 = 未脱敏
|
|
604
|
+
* 字节留在那个宿主的本地转录里):
|
|
605
|
+
* ① 🔴 **它是信号 + 权威内容**(0.68.1 改口;server ≥7.75.3)。`content` = 那一段的**权威全文**,
|
|
606
|
+
* 并且与活体增量的拼接**可以不相等** —— server 7.75.3 起它与 `result`/账本走**同一只脱敏器**,
|
|
607
|
+
* 而 `text_delta` 仍逐字(跨 chunk 的凭据无法就地判)。修前这里写的是「与增量拼接逐字节相等…
|
|
608
|
+
* 别拿它再渲一行」,那句话在 7.75.3 起**不再成立**;按它不渲 = 屏上与转录里留的是**未脱敏**
|
|
609
|
+
* 的那一份。
|
|
610
|
+
* · **`diverged` 在场**(never false,缺席 = 逐字节相同)⇒ 你按增量拼出来的那一段是**过期**的:
|
|
611
|
+
* 必须用 `content` **重渲该段**,并以它为该段的 committed 文本,**不许保留增量拼文**。
|
|
612
|
+
* · 🔴 **`committedPrefixDiverged` 在场**(never false)⇒ 这一段此前已经 committed 上屏的那一截
|
|
613
|
+
* **自己**就是过期的(凭据落在它里面),而它**撤不回**(那条 transcript 消息早已交给你)。
|
|
614
|
+
* 这一形下本包**一个字节都不再交**给 transcript 平面 —— 该段唯一算数的那一份就是本帧的
|
|
615
|
+
* `content`:宿主必须把 `committedPrefixLen` 指的那一截(见下)换成 `content`。
|
|
616
|
+
* 🔴 **不实现这一条 = 这一段在你的转录里只剩未脱敏的那半截**(而且缺了后半段)。
|
|
617
|
+
* 它**缺席**时(含压根没有已提交前缀)本包交的是**尾段** —— 前缀 + 尾段拼起来逐字节 =
|
|
618
|
+
* `content`,宿主**什么都不用丢**,照常把新到的 transcript 消息接在后面。
|
|
619
|
+
* · **`committedPrefixLen` 在场**(never 0)= 这一段已经 committed 上屏的**长度**,是上一条的
|
|
620
|
+
* **定位量**,照抄这一行就对:
|
|
621
|
+
* `已committed正文.slice(0, 已committed正文.length - committedPrefixLen) + content`。
|
|
622
|
+
* 🔴 **单位 = JS 字符串长度(UTF-16 代码单元),不是 UTF-8 字节** —— 按 UTF-8 字节去截会在
|
|
623
|
+
* 任何非 ASCII 正文上截错位置(实测 `sk-…` + `中文`×30 + 换行:UTF-16 长度 82 / UTF-8 字节 202,
|
|
624
|
+
* 按 202 截会把凭据**原样留在屏上**还附带乱码)。
|
|
625
|
+
* 🔴 **也不是「几条消息」** —— 本包在 idle-flush 那一形下会把「上一段的定稿 + 这一段的
|
|
626
|
+
* 半截」合并进**同一条** assistant 消息(分段行为零改动的代价),按**消息**去丢会把已经
|
|
627
|
+
* 定稿的上一段一起删掉,而本包不会再补发它。
|
|
628
|
+
* 🔴 它按**增量拼文**计长,**不是** `content` 上的偏移量 —— 前缀自己也可能被脱敏改过字节,
|
|
629
|
+
* 拿它去切 `content` 在 `committedPrefixDiverged` 那一形上会切出半截乱码。
|
|
630
|
+
* ⚠️ 🔴 **一条总不变量 + 一条如实留白**:
|
|
631
|
+
* **不变量** —— 本包只在「整段都在自己手里(**同一条** committed 消息内)」时改自己交的字节;
|
|
632
|
+
* 段一旦跨过包侧边界(工具卡 / 消息划界),就**只发这三个键、不动任何既有行为**:转录逐条、
|
|
633
|
+
* 终帧补差走向都与 0.68.0 **逐字相同**(常驻门按「补差臂走向」这个真正决定结果的量对照)。
|
|
634
|
+
* **留白** —— 因此跨消息那几形里,`committedPrefixLen` 指的那一截可能落在别条消息里、甚至
|
|
635
|
+
* 中间**夹着一张工具卡**,而终帧补差的基线也不会跟着换(它本来就不跟,与本批无关)。
|
|
636
|
+
* 两者的根因是同一个:**包按自己的边界切 committed 消息,引擎按 content block 切段**。
|
|
637
|
+
* 根治 = 「每个引擎段各自一条 committed 消息」(= CC 在 `content_block_stop` 上的原生做法),
|
|
638
|
+
* 那是**分段行为改动**、与撤 idle-flush 启发式同一件事,按宪法三问单独走;本批不做,
|
|
639
|
+
* 把边界如实写在这里而不是假装没有。
|
|
640
|
+
* · 三键**都缺席**(= 最常见的那一形)⇒ 一切照旧:`content` 只是**定界**信号,这些字节已经
|
|
641
|
+
* 以 `stream_delta` 流过、并由 transcript 平面给出 committed 形,**拿它再渲一行 = 同一段
|
|
642
|
+
* 文字上屏两遍**。
|
|
643
|
+
* ⚠️ 分工:`diverged` 那一条只有**自己拼 delta 上屏**的宿主要做(只渲 transcript 平面的宿主
|
|
644
|
+
* 自动正确 —— 本包已经把权威全文换进段缓冲);而 `committedPrefixDiverged` 那一条
|
|
645
|
+
* **所有宿主都要做**(包在那一形上交不出东西,只有你能修)。
|
|
646
|
+
* ⚠️ 🔴 **迟到的段边界**(引擎在工具执行之后才报这一段的边界 —— openai 车道的实测时序)同样
|
|
647
|
+
* 会带着这三个键到达:那一段的正文可能已经落在**上一条** assistant 消息里,`committedPrefixLen`
|
|
648
|
+
* 指的就是那一条。宿主按同一条规矩处置(丢掉 + 用 `content` 重渲),不要因为「这一段看起来
|
|
649
|
+
* 已经结束很久了」就跳过。
|
|
608
650
|
* ② **诚实缺席,不可反推**(core 臂注逐字):只有会报块结束的 Brain 才发它 —— 三个一方 brain 都发,
|
|
609
651
|
* 自定义 brain 可能整条流一帧都没有。⇒ 缺席 = 「**没报**」,**永远不等于**「这一段没结束」。
|
|
610
652
|
* 消费方按**每条流**判「这条流带不带边界帧」,只在整条流一帧都没有时才回落自己的启发式;
|
|
@@ -618,14 +660,42 @@ export interface WiringManifestChromeEvent {
|
|
|
618
660
|
* 要消费子流边界的宿主读 SDKMessage 平面的 `text_end` 内部臂,那一层原样带 `parentToolCallId`。
|
|
619
661
|
*
|
|
620
662
|
* ⚠️ **UNTRUSTED、仅展示**:`content` 是模型输出,契约与活体增量同 —— 渲染,绝不回喂模型。
|
|
621
|
-
* 📋 如实留白:本包的 `takeAnswerSegmentOnIdle`
|
|
622
|
-
* 的状态化策略,属行为面改动,按宪法三问单独走)
|
|
663
|
+
* 📋 如实留白:本包的 `takeAnswerSegmentOnIdle` 启发式**仍未撤**(撤它要按 ② 做一条 per-stream
|
|
664
|
+
* 的状态化策略,属行为面改动,按宪法三问单独走)。0.68.1 的权威段替换是**兼容**它的 ——
|
|
665
|
+
* 「半段已被 idle-flush 提交」那一形由 `committedPrefixLen` / `committedPrefixDiverged` 如实
|
|
666
|
+
* 交代,不是把启发式悄悄换掉了。
|
|
623
667
|
*/
|
|
624
668
|
export interface TextSegmentEndChromeEvent {
|
|
625
669
|
kind: 'text_segment_end';
|
|
626
670
|
laneProof: LaneProof;
|
|
627
|
-
/**
|
|
671
|
+
/**
|
|
672
|
+
* 该段的**权威全文**(server ≥7.75.3 起经脱敏器;**可以**与活体增量的拼接**不相等**)。
|
|
673
|
+
* 🔴 读法全在上面义务 ①:`diverged` 缺席时它只是对账/定界量(别拿它再渲一行);在场时它是
|
|
674
|
+
* 该段唯一算数的那一份。
|
|
675
|
+
*/
|
|
628
676
|
content: string;
|
|
677
|
+
/**
|
|
678
|
+
* **活体面**那份过期了(never false —— 缺席 = 与增量拼接逐字节相同)。
|
|
679
|
+
* 在场 ⇒ 按 {@link TextSegmentEndChromeEvent.content} 重渲该段,别保留增量拼文。
|
|
680
|
+
*/
|
|
681
|
+
diverged?: true;
|
|
682
|
+
/**
|
|
683
|
+
* **转录面**已经 committed 的那一截**自己**也过期了(never false)。
|
|
684
|
+
* 🔴 在场 ⇒ 本包**一个字节都不再交**给转录面(撤不回的那截拼不出 `content`,再补一条就是同一段话
|
|
685
|
+
* 上屏两遍)⇒ 该段唯一算数的那一份就是 {@link TextSegmentEndChromeEvent.content},宿主把
|
|
686
|
+
* {@link TextSegmentEndChromeEvent.committedPrefixLen} 指的那一截换成它;
|
|
687
|
+
* 缺席 ⇒ 本包交的是**尾段**,前缀 + 尾段 = `content`,什么都不用丢。
|
|
688
|
+
*/
|
|
689
|
+
committedPrefixDiverged?: true;
|
|
690
|
+
/**
|
|
691
|
+
* 这一段已经 committed 上屏的**长度**(never 0)——「要换的是哪一段」的定位量:
|
|
692
|
+
* `已committed正文.slice(0, 已committed正文.length - committedPrefixLen) + content`。
|
|
693
|
+
* 🔴 **单位 = JS 字符串长度(UTF-16 代码单元),不是 UTF-8 字节**(按字节截会在非 ASCII 正文上
|
|
694
|
+
* 截错位置,凭据会原样留在屏上);🔴 **也不是「几条消息」**(本包会把上一段的定稿与这一段的
|
|
695
|
+
* 半截合并进同一条消息);
|
|
696
|
+
* 🔴 按**增量拼文**计长,不是 `content` 上的偏移量(见义务 ①)。
|
|
697
|
+
*/
|
|
698
|
+
committedPrefixLen?: number;
|
|
629
699
|
/** core 铸的事件身份(uuidv7 形);wire 未必带 ⇒ 缺席时本键不在场。 */
|
|
630
700
|
eventId?: string;
|
|
631
701
|
}
|
package/dist/seam.js
CHANGED
|
@@ -110,8 +110,22 @@ const CHROME_ARM_TABLE = {
|
|
|
110
110
|
'(撤帧可能整帧丢失)③已 DECIDED 的兄弟不在 askIds 里;真撤到本地卡时给一行归因(reason 消毒后呈现),零命中不要多说那一行',
|
|
111
111
|
},
|
|
112
112
|
text_segment_end: {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
// 🔴 0.68.1 / L-310:`false` → `true`。改的理由不是「这一面更重要了」,是**后果类目变了**:
|
|
114
|
+
// 本表的 `false` 语义逐字是「不接 = 这条披露看不见,不属『已发生的行为丢失』」,而 server
|
|
115
|
+
// 7.75.3 起不接的后果是**未脱敏字节留在本地转录里**(b2 那一形上,那条明文消息**已经渲过**
|
|
116
|
+
// ⇒ 就是「已发生的行为」)。留 `false` = 把一条安全面义务标成可选,正是本仓点名要治的
|
|
117
|
+
// 「假 affordance / 悄悄的谎」。⚠️ 这一位翻面会让按本表自检覆盖率的端**当场显形**——那正是目的。
|
|
118
|
+
required: true,
|
|
119
|
+
duty: '🔴 引擎明报的 assistant 散文段边界 + **该段权威全文**(#323/core #447;server ≥7.75.3 起 content 经脱敏器、' +
|
|
120
|
+
'text_delta 仍逐字 ⇒ 两者**可以不相等**)。①`diverged` 在场(never false)⇒ 你按增量拼出来的那一段是过期的,' +
|
|
121
|
+
'必须用 content **重渲该段**并以它为该段 committed 文本,不许保留增量拼文;②`committedPrefixDiverged` 在场' +
|
|
122
|
+
'(never false)⇒ 这一段此前已 committed 上屏的正文**也**过期了,🔴 本包在这一形上**一个字节都不再交**' +
|
|
123
|
+
'(撤不回的那截拼不出 content)⇒ 把 committedPrefixLen 指的那一截换成 content,这一条**所有宿主都要做**;' +
|
|
124
|
+
'缺席 ⇒ 本包交的是尾段,前缀+尾段=content,什么都不用丢;③`committedPrefixLen`(never 0)= 定位量,' +
|
|
125
|
+
'🔴 **单位 = JS 字符串长度(UTF-16 代码单元),不是 UTF-8 字节、也不是几条消息**' +
|
|
126
|
+
'(按 UTF-8 字节截会在非 ASCII 正文上截错位置把凭据留在屏上;按消息丢会连合并在同一条消息里的上一段定稿一起删掉),' +
|
|
127
|
+
'照抄 `已committed正文.slice(0, 长度 - committedPrefixLen) + content`;按**增量拼文**计长、不是 content 上的偏移;④三键全缺席 ⇒ 照旧只当对账/定界,拿 content 再渲一行 = 同一段上屏两遍。' +
|
|
128
|
+
'🔴 缺席只表示「没报」,绝不等于「段没结束」——要退回自家启发式必须按整条流判、不按单帧判',
|
|
115
129
|
},
|
|
116
130
|
};
|
|
117
131
|
/**
|
package/dist/toolRoster.d.ts
CHANGED
|
@@ -28,8 +28,16 @@
|
|
|
28
28
|
* 而且它把「位置从名册来、永远不从 summary 来」这条契约反过来用了。
|
|
29
29
|
*
|
|
30
30
|
* -- 开集读 -----------------------------------------------------------------------------------
|
|
31
|
-
* `source`(七词)/ `effect` / `family` / `access`
|
|
32
|
-
*
|
|
31
|
+
* `source`(七词)/ `effect` / `family` / `access` / 三个 `*Provenance` 等词表的属主都是引擎,一律
|
|
32
|
+
* **原样透传**,不窄读成枚举 —— 那会在引擎加词当天把一份真名册判没。
|
|
33
|
+
*
|
|
34
|
+
* -- 🔴 逐键窄读器 vs 上游的逐字透传(L-290 的病形,0.68.1)-----------------------------------
|
|
35
|
+
* server(`src/trace/project.ts` 的 `tools` 段)对名册的姿势是**整只判形 + 逐字透传**,理由逐字:
|
|
36
|
+
* 成员表由 core 以 typebox schema 单点持有,手抄一张挑键表 = 立刻多一份会漂的镜像。本读器**确实**
|
|
37
|
+
* 是那份镜像(本包要给三端一个窄化过的视图,不能把 30 余键的开集原样甩出去)—— 所以镜像必须有账:
|
|
38
|
+
* `scripts/run-tool-roster-projection-test.mjs` **G 段**拿**实装 core 的 schema 成员表**与本读器
|
|
39
|
+
* 真挑出来的键集逐键对账,core 的每一个成员要么被挑、要么在账上写明「为什么不挑」;上游加一个
|
|
40
|
+
* 成员而账上没有 ⇒ 当天红。没有这本账,「挑漏一个键 = 那个键在包边界上不存在」就会一直无声发生。
|
|
33
41
|
*/
|
|
34
42
|
/** 一只工具的**路径目标**(引擎的 `pathTarget`;`base`/`absent`/`patternParam` 是 core 7.9.1 #635 加的)。 */
|
|
35
43
|
export interface ToolRosterPathTargetView {
|
|
@@ -78,10 +86,34 @@ export interface ToolRosterEntryView {
|
|
|
78
86
|
capabilityId?: string;
|
|
79
87
|
/** `read` / `write` / `idempotent`;开集读。 */
|
|
80
88
|
effect?: string;
|
|
89
|
+
/**
|
|
90
|
+
* `effect` 这根轴**是谁说的**(`declared` 定义自报 / `caller` 调用方声明 / `synthetic` 引擎合成 /
|
|
91
|
+
* `default` 谁都没说,落默认);**开集读**。见下面 {@link ToolRosterEntryView.contentOriginProvenance}
|
|
92
|
+
* 的头注 —— 三个出身键同生同灭。
|
|
93
|
+
*/
|
|
94
|
+
effectProvenance?: string;
|
|
81
95
|
egress?: boolean;
|
|
96
|
+
/** `egress` 这根轴的出身(`declared` / `caller` / `default`);**开集读**,读法同上。 */
|
|
97
|
+
egressProvenance?: string;
|
|
82
98
|
/** `never` / `maybe` / `always`;开集读。 */
|
|
83
99
|
irreversibility?: string;
|
|
84
100
|
contentOrigin?: string;
|
|
101
|
+
/**
|
|
102
|
+
* `contentOrigin` 这根轴的出身(`declared` 定义自报 / `server` mcp·a2a 行由服务端定 /
|
|
103
|
+
* `exempted` 在受信工具豁免名单里 / `default` 谁都没说);**开集读**(L-290,0.68.1)。
|
|
104
|
+
*
|
|
105
|
+
* 🔴 **为什么要有这一位**:轴的**值**说的是「这只工具的产出算不算外来内容」,而一个
|
|
106
|
+
* `contentOrigin: "local"` 到底是**工具自己声明**的、还是**因为它在豁免名单里**才这样 ——
|
|
107
|
+
* 在信任面上是两件事。诊断行(壳 `/doctor` 的「Tools (engine leg)」)读的正是后者。
|
|
108
|
+
* 🔴 **族扫(同形存量清剿)**:server `src/trace/project.ts` 的 `tools` 段是**整只判形 + 逐字
|
|
109
|
+
* 透传**,而本读器是**逐键挑** —— 挑漏的键在包边界上等于不存在。三根轴各有一个出身键,漏的形
|
|
110
|
+
* 一模一样,所以三个一起挑,不是只补被点名的那一个;核过之后 core `ToolRosterEntry` 的**每一个**
|
|
111
|
+
* 成员在 `scripts/run-tool-roster-projection-test.mjs` G 段都有一行处置(挑 / 不挑 + 理由),
|
|
112
|
+
* 上游再加成员当天红。
|
|
113
|
+
* ⚠️ 出身键是**诊断面**,刻意**不进** {@link ToolShim}(那是渲染面:人话名 / 卡型 / 三根轴的
|
|
114
|
+
* **值**)—— 端渲一只工具时要的是「它是什么」,不是「这句话是谁说的」。
|
|
115
|
+
*/
|
|
116
|
+
contentOriginProvenance?: string;
|
|
85
117
|
family?: string;
|
|
86
118
|
pathTarget?: ToolRosterPathTargetView;
|
|
87
119
|
renderHints?: ToolRosterRenderHintsView;
|
package/dist/toolRoster.js
CHANGED
|
@@ -28,8 +28,16 @@
|
|
|
28
28
|
* 而且它把「位置从名册来、永远不从 summary 来」这条契约反过来用了。
|
|
29
29
|
*
|
|
30
30
|
* -- 开集读 -----------------------------------------------------------------------------------
|
|
31
|
-
* `source`(七词)/ `effect` / `family` / `access`
|
|
32
|
-
*
|
|
31
|
+
* `source`(七词)/ `effect` / `family` / `access` / 三个 `*Provenance` 等词表的属主都是引擎,一律
|
|
32
|
+
* **原样透传**,不窄读成枚举 —— 那会在引擎加词当天把一份真名册判没。
|
|
33
|
+
*
|
|
34
|
+
* -- 🔴 逐键窄读器 vs 上游的逐字透传(L-290 的病形,0.68.1)-----------------------------------
|
|
35
|
+
* server(`src/trace/project.ts` 的 `tools` 段)对名册的姿势是**整只判形 + 逐字透传**,理由逐字:
|
|
36
|
+
* 成员表由 core 以 typebox schema 单点持有,手抄一张挑键表 = 立刻多一份会漂的镜像。本读器**确实**
|
|
37
|
+
* 是那份镜像(本包要给三端一个窄化过的视图,不能把 30 余键的开集原样甩出去)—— 所以镜像必须有账:
|
|
38
|
+
* `scripts/run-tool-roster-projection-test.mjs` **G 段**拿**实装 core 的 schema 成员表**与本读器
|
|
39
|
+
* 真挑出来的键集逐键对账,core 的每一个成员要么被挑、要么在账上写明「为什么不挑」;上游加一个
|
|
40
|
+
* 成员而账上没有 ⇒ 当天红。没有这本账,「挑漏一个键 = 那个键在包边界上不存在」就会一直无声发生。
|
|
33
41
|
*/
|
|
34
42
|
const str = (v) => typeof v === 'string' && v.length > 0 ? v : undefined;
|
|
35
43
|
const strArr = (v) => Array.isArray(v) ? v.filter((x) => typeof x === 'string') : undefined;
|
|
@@ -91,9 +99,15 @@ function readEntry(raw) {
|
|
|
91
99
|
...(str(o.cardId) !== undefined ? { cardId: str(o.cardId) } : {}),
|
|
92
100
|
...(str(o.capabilityId) !== undefined ? { capabilityId: str(o.capabilityId) } : {}),
|
|
93
101
|
...(str(o.effect) !== undefined ? { effect: str(o.effect) } : {}),
|
|
102
|
+
// L-290 族扫:三根轴各自的**出身**键(开集读,`str()` 同律;坏形只丢这一格,行还在 —— 出身不是身份)。
|
|
103
|
+
...(str(o.effectProvenance) !== undefined ? { effectProvenance: str(o.effectProvenance) } : {}),
|
|
94
104
|
...(typeof o.egress === 'boolean' ? { egress: o.egress } : {}),
|
|
105
|
+
...(str(o.egressProvenance) !== undefined ? { egressProvenance: str(o.egressProvenance) } : {}),
|
|
95
106
|
...(str(o.irreversibility) !== undefined ? { irreversibility: str(o.irreversibility) } : {}),
|
|
96
107
|
...(str(o.contentOrigin) !== undefined ? { contentOrigin: str(o.contentOrigin) } : {}),
|
|
108
|
+
...(str(o.contentOriginProvenance) !== undefined
|
|
109
|
+
? { contentOriginProvenance: str(o.contentOriginProvenance) }
|
|
110
|
+
: {}),
|
|
97
111
|
...(str(o.family) !== undefined ? { family: str(o.family) } : {}),
|
|
98
112
|
...(readPathTarget(o.pathTarget) !== undefined
|
|
99
113
|
? { pathTarget: readPathTarget(o.pathTarget) }
|