@sema-agent/client-core 0.68.1 → 0.69.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.
@@ -0,0 +1,104 @@
1
+ /**
2
+ * mcpPanel.ts — `GET /v1/sessions/:id/mcp` 面板体(sdk `McpStatusPanel`)的**防御读视图**
3
+ * (CC-03,0.68.2;server 7.77.0 S-297 可选键 `lastLegMcp`;回 A-093 ③)。
4
+ *
5
+ * ── 为什么在包里(归层)────────────────────────────────────────────────────────────────────
6
+ * 面板体是三端都要渲的同一份读面:`servers[]` 是「这台部署此刻按需 materialize 出来的连接状态」,
7
+ * `lastLegMcp` 是「这个会话最近一条腿的账本里申报了哪些 MCP」。**两面合法可不同**(部署缺省场景
8
+ * 与按请求注入的名册本来就是两张表),契约 G.7 第 4 条禁止消费端拿它们对账后渲「不一致」。
9
+ * 这条纪律若各端自己写一遍,迟早有一端写反。⇒ 读法(哪些键、坏形怎么办、缺席说什么)只在这里
10
+ * 有一份;端只渲。
11
+ *
12
+ * ── 缺席语义(逐键)──────────────────────────────────────────────────────────────────────────
13
+ * · `lastLegMcp` **整键缺席** = server 的三形同形(新会话 / 最近腿的账本行在保留窗外 / 该腿没推
14
+ * `wiring_manifest`)**或**引擎早于 7.77.0;本视图同样不铸该键。「为什么缺席」这一位答不出来,
15
+ * 也不猜。
16
+ * · `lastLegMcp` **在场但形坏**(`runId` / `at` 不是非空串、`mcp` 读不出一行)⇒ 视图不铸该键,
17
+ * 改立 {@link McpPanelView.lastLegMcpUnreadable}(never false)。「引擎送了一份我读不懂的」与
18
+ * 「引擎没送」对运维是两句话,不折成一个 `undefined`。
19
+ * · `degraded`:只在 server 的超时臂铸(sdk openapi 顶注逐字)⇒ 本视图 never false:在场 = 取不到,
20
+ * 缺席 = 正常 materialize(`servers: []` 那时是「一台都没配」这句正面事实,不是「不知道」)。
21
+ * · `servers[]` 逐行:`name` + `status` 是必需两座(缺一丢该行,坏一行只丢那一行);**非空输入而零行
22
+ * 幸存 ⇒ 整个视图 `undefined`**(「我读不出来」不是「我知道是零」,与 `wiring_manifest.mcp`
23
+ * 同一条规矩)。`status` 按开集读(sdk 只声明 connected/failed 两词,core 未来加词照渲不丢行)。
24
+ * · `error`:server 已脱敏 + 200 字符封顶;本视图仍按 UNTRUSTED-for-display 再过一次
25
+ * {@link capForDisplay}(呈前消毒 + 封长),与本包其余远端文本铸点同律。
26
+ *
27
+ * ── `mcp[]` 条目与 `wiring_manifest` 第三段**同一只读器**─────────────────────────────────────
28
+ * `lastLegMcp.mcp[]` 是该腿 `wiring_manifest.mcp[]` 的逐字回放(server S-297),本视图直接调
29
+ * {@link projectMcpSection} —— 同一形、同一判据、同一缺席律;三端渲这两处用同一个
30
+ * {@link WiringManifestMcpEntry},不会出现「活体腿一种形、账本回放另一种形」。
31
+ */
32
+ import type { AgentClient } from '@sema-agent/sdk';
33
+ import { type WiringManifestMcpEntry } from './adapter/downstream/eventToSdkMessage.js';
34
+ /** 面板 `servers[]` 一行(sdk `McpServerStatus` 的窄读;开集键不透传,逐键挑)。 */
35
+ export interface McpPanelServerView {
36
+ /** 配置名(引擎产的标识,非用户内容)。 */
37
+ name: string;
38
+ /** `connected` / `failed`(sdk 声明的两词;**按开集读**,认不得的词照渲不丢行)。 */
39
+ status: string;
40
+ /** 服务器自报的名字与版本(仅 connected 时有)。缺席 = 没报。 */
41
+ serverInfo?: {
42
+ name: string;
43
+ version: string;
44
+ };
45
+ /** 挂上来的工具名(仅 connected 时有)。缺席 = 没报,**不是**空表。 */
46
+ toolNames?: string[];
47
+ /** 失败因由(仅 failed 时;server 已脱敏 + 封顶,本视图再消毒一次)。 */
48
+ error?: string;
49
+ }
50
+ /** `lastLegMcp{runId,at,mcp[]}`(server ≥7.77.0 S-297)的窄读。 */
51
+ export interface McpPanelLastLegView {
52
+ /** 该会话最近一条腿的 runId。 */
53
+ runId: string;
54
+ /** 写账本副本的钟(ISO;**不是** materialize 时刻 `asOf`,两者不比)。 */
55
+ at: string;
56
+ /** 该腿 `wiring_manifest.mcp[]` 逐字 —— 与活体腿的第三段同一只读器、同一形。 */
57
+ mcp: WiringManifestMcpEntry[];
58
+ }
59
+ /** `GET /v1/sessions/:id/mcp` 的读视图(缺席语义见文件顶注)。 */
60
+ export interface McpPanelView {
61
+ /** THIS materialize 时刻(ISO)。 */
62
+ asOf: string;
63
+ /** materialization-time 状态,不是 live 健康(壳渲「as of <asOf>」)。空表 = 一台都没配。 */
64
+ servers: McpPanelServerView[];
65
+ /** never false:在场 = materialize 超时/失败,`servers` 空但**不是**「没有 MCP」。 */
66
+ degraded?: true;
67
+ /** 最近一条腿的申报名册(server ≥7.77.0);缺席语义见顶注。🔴 禁与 `servers[]` 对账渲告警。 */
68
+ lastLegMcp?: McpPanelLastLegView;
69
+ /** never false:server 送了 `lastLegMcp` 但本视图读不出来(与整键缺席不是同一句话)。 */
70
+ lastLegMcpUnreadable?: true;
71
+ }
72
+ /**
73
+ * 面板体 → 读视图;**畸形一律 `undefined`**,绝不抛出(必填位 fail-closed,可选位只丢自己)。
74
+ *
75
+ * 🔴 `lastLegMcp` 只按「键在不在」判在场(`'lastLegMcp' in body`),不按真值判:server 的缺席形
76
+ * 是**键不出现**(LL-3),不是 `null`/`undefined` 在场 —— 后两者是坏形,走 `lastLegMcpUnreadable`。
77
+ * 🔴 本函数**不比** `servers[]` 与 `lastLegMcp.mcp[]`,视图上也没有任何「一致/不一致」位
78
+ * (契约 G.7 第 4 条:两面合法可不同)。
79
+ */
80
+ export declare function projectMcpPanel(body: unknown): McpPanelView | undefined;
81
+ /**
82
+ * 取面板体并投成读视图(CC-03 补件,0.69.0):`GET /v1/sessions/:id/mcp` 经 sdk `client.sessions.mcp`(自 sdk 0.0.75 在场,
83
+ * 与 `runs.subagentOutput` 同一条 transport,鉴权/principal 由 sdk 带 —— 本包**不**裸 fetch,不做第二套鉴权铸点)。
84
+ *
85
+ * 🔴 返回 `undefined` 的三形**同形**(与 {@link projectMcpPanel} 的缺席律一致):传输失败(网络 / 4xx / 5xx / abort)、
86
+ * 体读不出(必填位坏)、`sessionId` 空串(不发请求)。「为什么」不在返回值上 —— 传输失败留一行 `hostLog('debug')`,
87
+ * 端要分「未观测 vs 读不出」用 {@link mcpPanelLastLegDetail} 的 `reachable` 位(`view !== undefined` 即 reachable)。
88
+ * 🔴 `client` 只钉 `sessions` 一格(`Pick`):端传本包 `makeEngineWireClient` 造的实例即可,测试可喂假客户端。
89
+ */
90
+ export declare function fetchMcpPanel(client: Pick<AgentClient, 'sessions'>, sessionId: string, opts?: {
91
+ signal?: AbortSignal;
92
+ }): Promise<McpPanelView | undefined>;
93
+ /**
94
+ * `lastLegMcp` 那一行措辞的**唯一铸点**(三端共用;别在各端的行装配里另写一遍)。
95
+ *
96
+ * 🔴 四句刻意逐字互异(黑盒锚),且**没有一句**提到 `servers[]`:
97
+ * ① 在场 —— 名册 + runId + at;
98
+ * ② `opts.reachable === false` —— 「未观测」:这次进程没读到面板体;
99
+ * ③ 面板读到了但 `lastLegMcp` 整键缺席 —— 「不报」:三形同形 + 老引擎,**不武断咎为版本**;
100
+ * ④ 在场但读不懂 —— 「读不出」:与③是两句话。
101
+ */
102
+ export declare function mcpPanelLastLegDetail(view: McpPanelView | undefined, opts: {
103
+ reachable: boolean;
104
+ }): string;
@@ -0,0 +1,129 @@
1
+ import { hostLog } from './host.js';
2
+ import { projectMcpSection } from './adapter/downstream/eventToSdkMessage.js';
3
+ import { capForDisplay } from './fleetTaskDesc.js';
4
+ /** `error` 上屏前的封长(server 侧 `slice(200)`,同值)。 */
5
+ const MCP_PANEL_ERROR_MAX = 200;
6
+ /** 名字类词形的封长(与本包其余 detail 铸点同值同理由)。 */
7
+ const MCP_PANEL_WORD_MAX = 40;
8
+ /** `mcpPanelLastLegDetail` 里列出的名字上限(再多就是一行读不完的表,不是一句读数)。 */
9
+ const MCP_PANEL_NAMES_MAX = 8;
10
+ const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
11
+ const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
12
+ function projectServerRow(raw) {
13
+ if (!isRecord(raw))
14
+ return undefined;
15
+ if (!nonEmpty(raw.name) || !nonEmpty(raw.status))
16
+ return undefined;
17
+ const row = { name: raw.name, status: raw.status };
18
+ const si = raw.serverInfo;
19
+ if (isRecord(si) && typeof si.name === 'string' && typeof si.version === 'string') {
20
+ row.serverInfo = { name: si.name, version: si.version };
21
+ }
22
+ if (Array.isArray(raw.toolNames) && raw.toolNames.every((t) => typeof t === 'string')) {
23
+ row.toolNames = [...raw.toolNames];
24
+ }
25
+ if (typeof raw.error === 'string')
26
+ row.error = capForDisplay(raw.error, MCP_PANEL_ERROR_MAX);
27
+ return row;
28
+ }
29
+ function projectLastLeg(raw) {
30
+ if (!isRecord(raw))
31
+ return undefined;
32
+ if (!nonEmpty(raw.runId) || !nonEmpty(raw.at))
33
+ return undefined;
34
+ const mcp = projectMcpSection(raw.mcp);
35
+ if (mcp === undefined)
36
+ return undefined;
37
+ return { runId: raw.runId, at: raw.at, mcp };
38
+ }
39
+ /**
40
+ * 面板体 → 读视图;**畸形一律 `undefined`**,绝不抛出(必填位 fail-closed,可选位只丢自己)。
41
+ *
42
+ * 🔴 `lastLegMcp` 只按「键在不在」判在场(`'lastLegMcp' in body`),不按真值判:server 的缺席形
43
+ * 是**键不出现**(LL-3),不是 `null`/`undefined` 在场 —— 后两者是坏形,走 `lastLegMcpUnreadable`。
44
+ * 🔴 本函数**不比** `servers[]` 与 `lastLegMcp.mcp[]`,视图上也没有任何「一致/不一致」位
45
+ * (契约 G.7 第 4 条:两面合法可不同)。
46
+ */
47
+ export function projectMcpPanel(body) {
48
+ if (!isRecord(body))
49
+ return undefined;
50
+ if (!nonEmpty(body.asOf))
51
+ return undefined;
52
+ if (!Array.isArray(body.servers))
53
+ return undefined;
54
+ const servers = [];
55
+ for (const r of body.servers) {
56
+ const row = projectServerRow(r);
57
+ if (row !== undefined)
58
+ servers.push(row);
59
+ }
60
+ if (body.servers.length > 0 && servers.length === 0)
61
+ return undefined;
62
+ const view = { asOf: body.asOf, servers };
63
+ if (body.degraded === true)
64
+ view.degraded = true;
65
+ if ('lastLegMcp' in body) {
66
+ const leg = projectLastLeg(body.lastLegMcp);
67
+ if (leg !== undefined)
68
+ view.lastLegMcp = leg;
69
+ else
70
+ view.lastLegMcpUnreadable = true;
71
+ }
72
+ return view;
73
+ }
74
+ /**
75
+ * 取面板体并投成读视图(CC-03 补件,0.69.0):`GET /v1/sessions/:id/mcp` 经 sdk `client.sessions.mcp`(自 sdk 0.0.75 在场,
76
+ * 与 `runs.subagentOutput` 同一条 transport,鉴权/principal 由 sdk 带 —— 本包**不**裸 fetch,不做第二套鉴权铸点)。
77
+ *
78
+ * 🔴 返回 `undefined` 的三形**同形**(与 {@link projectMcpPanel} 的缺席律一致):传输失败(网络 / 4xx / 5xx / abort)、
79
+ * 体读不出(必填位坏)、`sessionId` 空串(不发请求)。「为什么」不在返回值上 —— 传输失败留一行 `hostLog('debug')`,
80
+ * 端要分「未观测 vs 读不出」用 {@link mcpPanelLastLegDetail} 的 `reachable` 位(`view !== undefined` 即 reachable)。
81
+ * 🔴 `client` 只钉 `sessions` 一格(`Pick`):端传本包 `makeEngineWireClient` 造的实例即可,测试可喂假客户端。
82
+ */
83
+ export async function fetchMcpPanel(client, sessionId, opts) {
84
+ if (typeof sessionId !== 'string' || sessionId.length === 0)
85
+ return undefined;
86
+ let body;
87
+ try {
88
+ body = await client.sessions.mcp(sessionId, opts?.signal ? { signal: opts.signal } : undefined);
89
+ }
90
+ catch (e) {
91
+ const status = e?.status;
92
+ hostLog('debug', `[mcp-panel] ${sessionId}: ${typeof status === 'number' ? status : String(e).slice(0, 160)}`);
93
+ return undefined;
94
+ }
95
+ return projectMcpPanel(body);
96
+ }
97
+ /**
98
+ * `lastLegMcp` 那一行措辞的**唯一铸点**(三端共用;别在各端的行装配里另写一遍)。
99
+ *
100
+ * 🔴 四句刻意逐字互异(黑盒锚),且**没有一句**提到 `servers[]`:
101
+ * ① 在场 —— 名册 + runId + at;
102
+ * ② `opts.reachable === false` —— 「未观测」:这次进程没读到面板体;
103
+ * ③ 面板读到了但 `lastLegMcp` 整键缺席 —— 「不报」:三形同形 + 老引擎,**不武断咎为版本**;
104
+ * ④ 在场但读不懂 —— 「读不出」:与③是两句话。
105
+ */
106
+ export function mcpPanelLastLegDetail(view, opts) {
107
+ if (!opts.reachable)
108
+ return "last leg mcp not observed (this end could not read the session's MCP panel)";
109
+ if (view === undefined)
110
+ return 'last leg mcp unreadable (the MCP panel body could not be read by this client)';
111
+ if (view.lastLegMcp !== undefined) {
112
+ const { runId, at, mcp } = view.lastLegMcp;
113
+ const names = mcp.slice(0, MCP_PANEL_NAMES_MAX).map((e) => capForDisplay(e.name, MCP_PANEL_WORD_MAX));
114
+ const more = mcp.length > MCP_PANEL_NAMES_MAX ? `, +${mcp.length - MCP_PANEL_NAMES_MAX} more` : '';
115
+ const roster = mcp.length === 0 ? 'none declared' : `${names.join(', ')}${more}`;
116
+ return `last leg mcp: ${roster} (run ${capForDisplay(runId, MCP_PANEL_WORD_MAX)}, at ${capForDisplay(at, MCP_PANEL_WORD_MAX)})`;
117
+ }
118
+ if (view.lastLegMcpUnreadable === true) {
119
+ return 'last leg mcp unreadable (the engine sent a shape this client cannot read)';
120
+ }
121
+ return 'last leg mcp not reported (no leg in the retention window, no manifest on the last leg, or the engine predates it)';
122
+ }
123
+ /**
124
+ * **编译期对账钉**(不出公面):sdk 的 `McpStatusPanel` 必须能赋给视图的必填半场 —— 名字在
125
+ * sdk ≥8.8.0 上存在(本包 peer 地板),`tsc` 在改名当天报「没有导出成员」。
126
+ * 反向刻意不钉(视图比 sdk 形窄:开集键不透传、`degraded` 收成 never-false)。
127
+ */
128
+ const _mcpPanelShapePin = (p) => p;
129
+ void _mcpPanelShapePin;
@@ -118,6 +118,60 @@ export interface TaskRequestInput {
118
118
  outputStyle?: string;
119
119
  };
120
120
  }
121
+ /**
122
+ * 合一后的请求构造器 —— **按车道出两形**,字段集差异全部由 `REQUEST_FIELD_MATRIX` 决定。
123
+ *
124
+ * 🔴 行为纪律:本函数**不做任何统一**。print 没有 `ultracode` 就是没有(表里 `gap:true` 记着账),
125
+ * 补齐要另立项 —— 在这里顺手加一行,就是把「合一」偷换成「行为改动」。
126
+ */
127
+ /**
128
+ * ⑤ 的**响亮拒**([7226] 包侧缺口 ⑤;异源对抗复审轮五 finding① 采纳)。
129
+ *
130
+ * 🔴 修前这里是「不是 `'off'` 就整键不 stamp」——**静默删键**,而删掉的恰是一条**隐私声明**:
131
+ * 一个把 `/memory-capture off` 打成 `OFF` 的会话,请求照发、引擎照常采集,**没有任何人会知道**。
132
+ * 🔴 上游把这条写死了(装机 core `dist/core/memory.d.ts` 的 `capture?: "off"` 头注**逐字**):
133
+ * 「Any other value — `"on"`, `"OFF"`, booleans, garbage — is REFUSED loudly
134
+ * (`config.memory_capture_spelling`), never read as either state (**a privacy request must not be
135
+ * dropped by a typo**, and capture must not be switched off by one either)」;server 契约 §12.4
136
+ * 同样写明坏拼写 400 `request.field_invalid`,并点名「把一条隐私请求按打字错误静默丢掉恰是**禁的方向**」。
137
+ * ⇒ 提前删键 = 把上游那道响亮门**绕过去**,方向正好反了。
138
+ * 🔴 与本文件 `resolvedSnapshotForWire` 对畸形权限快照的处置**同一条纪律**(那里也是抛,理由逐字
139
+ * 是「降空 = 让请求带着被剥掉的权限面发出去」)——两处都是**声明方向**的位:丢了没人看得见。
140
+ * ⚠️ `undefined` / `null` = **合法缺席**(端没有这个入口 / 没有声明),照旧不 stamp,不拒。
141
+ * ⚠️ **不分车道、不受 live 门**:拒绝不是「stamp 一个键」,不改请求形状;而一条打错字的隐私声明
142
+ * 在哪条车道上都不该被默默放行。
143
+ * (本包是发出去的 npm 公开面:JS 调用方与版本偏斜的宿主都到得了这里,型面拦不住。)
144
+ */
145
+ /**
146
+ * wire 上那个**单成员闭集**的唯一字面(server §12.4)。导出它是为了让端的断言/门有一个机读锚,
147
+ * 🔴 **不是**为了让端拿它去自己拼请求 —— 拼请求走 {@link memoryCaptureDeclarationField}。
148
+ */
149
+ export declare const MEMORY_CAPTURE_OFF: "off";
150
+ /**
151
+ * 「本会话声明过 opt-out 没有」这个**意图位** → 提交腿的 spread-ready 片段(L-316,0.68.2)。
152
+ *
153
+ * ── 为什么这一只在包里(归层,不是搬家)────────────────────────────────────────────────────
154
+ * 端手里只有一个**布尔**:「这条会话敲过 `/memory-capture off` 没有」。而 wire 上那个值是
155
+ * **单成员闭集**的一个字面串 —— 谁铸这个串,谁就得同时承担「拼错了会被 400 响亮拒、而且这是
156
+ * 一条**隐私**声明、拼错即静默失效」这件事。cli 1.0.x 起这个串在壳里另有一处铸点
157
+ * (`memoryCaptureOptOut.memoryCaptureRequestField`),web / desktop 接这条腿时会各铸第三、第四处
158
+ * —— 三端各写一个字面量,正是本层存在的理由(与 `taskNotificationToPrintFrame` 同一条先例)。
159
+ * ⇒ 端交**意图位**,值由本层唯一铸出;拼写门与 {@link buildTaskRequest} 的响亮拒共用同一个字面。
160
+ *
161
+ * 🔴 **`false` ⇒ 整键缺席**,不是 `{ memoryCapture: undefined }`、更不是某个「on」值:wire 上
162
+ * 压根没有那个值(单成员闭集),**缺席就是「照常采集」的唯一写法**。所以未启用时本片段对
163
+ * wire 字节零影响。
164
+ * 🔴 **本层只答「下一条提交带不带这一键」,不答「采集关没关」**:后者只有引擎知道(老 worker
165
+ * 静默忽略本键 / 部署策略 403 / 控制面写失败),任何端都不许拿这一位去渲一句「已经关了」。
166
+ *
167
+ * 用法(端逐字照抄,别在外面再包一层字面量):
168
+ * ```ts
169
+ * buildTaskRequest({ …, ...memoryCaptureDeclarationField(declaredForThisSession) }, 'interactive')
170
+ * ```
171
+ */
172
+ export declare function memoryCaptureDeclarationField(declared: boolean): {
173
+ memoryCapture: 'off';
174
+ } | Record<string, never>;
121
175
  export declare function buildTaskRequest(input: TaskRequestInput, lane: RequestLane): TaskRequestLike;
122
176
  /** `applyLiveRequestDefaults` 的宿主输入(端解析好的值,同样零取值方式)。 */
123
177
  export interface LiveDefaultsInput {
@@ -417,6 +417,36 @@ const resolvedSnapshotForWire = (resolved) => {
417
417
  * 在哪条车道上都不该被默默放行。
418
418
  * (本包是发出去的 npm 公开面:JS 调用方与版本偏斜的宿主都到得了这里,型面拦不住。)
419
419
  */
420
+ /**
421
+ * wire 上那个**单成员闭集**的唯一字面(server §12.4)。导出它是为了让端的断言/门有一个机读锚,
422
+ * 🔴 **不是**为了让端拿它去自己拼请求 —— 拼请求走 {@link memoryCaptureDeclarationField}。
423
+ */
424
+ export const MEMORY_CAPTURE_OFF = 'off';
425
+ /**
426
+ * 「本会话声明过 opt-out 没有」这个**意图位** → 提交腿的 spread-ready 片段(L-316,0.68.2)。
427
+ *
428
+ * ── 为什么这一只在包里(归层,不是搬家)────────────────────────────────────────────────────
429
+ * 端手里只有一个**布尔**:「这条会话敲过 `/memory-capture off` 没有」。而 wire 上那个值是
430
+ * **单成员闭集**的一个字面串 —— 谁铸这个串,谁就得同时承担「拼错了会被 400 响亮拒、而且这是
431
+ * 一条**隐私**声明、拼错即静默失效」这件事。cli 1.0.x 起这个串在壳里另有一处铸点
432
+ * (`memoryCaptureOptOut.memoryCaptureRequestField`),web / desktop 接这条腿时会各铸第三、第四处
433
+ * —— 三端各写一个字面量,正是本层存在的理由(与 `taskNotificationToPrintFrame` 同一条先例)。
434
+ * ⇒ 端交**意图位**,值由本层唯一铸出;拼写门与 {@link buildTaskRequest} 的响亮拒共用同一个字面。
435
+ *
436
+ * 🔴 **`false` ⇒ 整键缺席**,不是 `{ memoryCapture: undefined }`、更不是某个「on」值:wire 上
437
+ * 压根没有那个值(单成员闭集),**缺席就是「照常采集」的唯一写法**。所以未启用时本片段对
438
+ * wire 字节零影响。
439
+ * 🔴 **本层只答「下一条提交带不带这一键」,不答「采集关没关」**:后者只有引擎知道(老 worker
440
+ * 静默忽略本键 / 部署策略 403 / 控制面写失败),任何端都不许拿这一位去渲一句「已经关了」。
441
+ *
442
+ * 用法(端逐字照抄,别在外面再包一层字面量):
443
+ * ```ts
444
+ * buildTaskRequest({ …, ...memoryCaptureDeclarationField(declaredForThisSession) }, 'interactive')
445
+ * ```
446
+ */
447
+ export function memoryCaptureDeclarationField(declared) {
448
+ return declared ? { memoryCapture: MEMORY_CAPTURE_OFF } : {};
449
+ }
420
450
  const refuseBadMemoryCapture = (v) => {
421
451
  if (v === undefined || v === null)
422
452
  return;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * src/sdkWireTransit.ts — SDK **wire 面**的转口口(L-61 存量清零;clay 令 C-R43「所有欠账绝不延期」)。
3
+ *
4
+ * ── 为什么开这一只(与 `engineHttpTools.ts` 的分界)──────────────────────────────────────────
5
+ * `engineHttpTools.ts` 转的是两件**纯工具**(URL builder / 存活探针)。本文件转的是 wire 面的
6
+ * **客户端类**、**帧谓词**与**型面** —— 端至今只能直连 `@sema-agent/sdk` 去取它们
7
+ * (cli sdk-isolation 存量册 `sdk-value` 桶的剩余条目),而「壳对引擎 wire 的一切消费必须经本包」
8
+ * 这条公约对它们一直不成立。
9
+ *
10
+ * 🔴 **为什么 0.68.2 之前把 `AgentClient` 记成 declined,现在改成转口**(改口要给理由,不能只改结论):
11
+ * 此前的理由是「转口一个客户端类 = 本包为它的连接姿势/鉴权头/重试语义背书」。这条**顾虑仍然
12
+ * 成立**,但它指向的是「端该不该直接 new 一个 AgentClient」这个**设计问题**,而不是「这条 import
13
+ * 该不该经本包」这个**归层问题**——把两者绑在一起的代价是:归层这件事被一个未排期的设计件
14
+ * 无限期挡住(存量窗已因此过期两代)。⇒ 分开处理:
15
+ * · **归层现在做**(本文件,原样转口,零包装);
16
+ * · **设计件照旧欠着**:本包已有的自有路 `makeEngineWireClient` 才是推荐姿势,端上新码一律走
17
+ * 那一条;本转口口只给**存量**用,不是新码的入口。这一条写在这里,不靠人记得。
18
+ *
19
+ * 🔴 **原样转口,一个字节都不加工**(与 `engineHttpTools.ts` 同一条纪律):不包装、不改签名、
20
+ * 不补默认值 —— 包一层就是把「同一实现」这条唯一的抗漂移保证亲手拆掉。
21
+ *
22
+ * ── 可移植门为什么不受影响(实测,不是推断)────────────────────────────────────────────────
23
+ * · `@sema-agent/sdk` **本来就在** `EXPECTED_PACKAGES_INDEX` 等值集里(本包已有值级 SDK import)
24
+ * ⇒ 集合一个字节不动;
25
+ * · SDK 的 `dist/` 全树 **零 `node:` 内建**(实测:`grep -rl "node:" dist/*.js` 空),传输走全局
26
+ * `fetch`/`AbortController` = web 标准 ⇒ ③ 段的 `--platform=browser` 真打包面零风险;
27
+ * · 型面转口是 `export type`,编译后整段消失 ⇒ 闭包扫描的「剥 type-only」那一步本来就不看它。
28
+ */
29
+ export { AgentClient } from '@sema-agent/sdk';
30
+ export { isApprovalRequestFrameV1, isApprovalRevokeFrameV1 } from '@sema-agent/sdk';
31
+ /**
32
+ * ── ③ wire **型面**(零运行期,`export type`)────────────────────────────────────────────────
33
+ *
34
+ * 🔴 **射程写明**(免得下一棒把这里当成「SDK 型面的全量镜像」):本表**只**收端上存量直连实际用到
35
+ * 的那些名字,一个不多。全量镜像是另一件事(会把本包的 `.d.ts` 与 SDK 的每一次型面改动绑死),
36
+ * 与 `@sema-agent/agent-types` 的**CC 形**全量镜像不是一回事 —— 那边镜的是 CC SDK 的会话词汇,
37
+ * 这边是引擎 wire 的请求/事件/回执形。
38
+ * 🔴 **谁是权威**:权威恒是 `@sema-agent/sdk` 本身。本文件是**别名**,上游改名 ⇒ 本文件当场编译红,
39
+ * 端跟着红 —— 这正是型面转口相对「端各自直连」唯一多出来的那点好处(红在一处,不是散在九处)。
40
+ */
41
+ export type { TaskRequest, SemaSettings, SkillSpec, AgentEvent, RunReceipt, CancelAck, RunRecord, ToolApprovalRespondAck, FleetTaskRow, FleetWorkflowRow, ApprovalCard, ApprovalRequestFrame, ApprovalRiskAxes, AskDecisionAck, AskDecisionBody, } from '@sema-agent/sdk';
@@ -0,0 +1,32 @@
1
+ /**
2
+ * src/sdkWireTransit.ts — SDK **wire 面**的转口口(L-61 存量清零;clay 令 C-R43「所有欠账绝不延期」)。
3
+ *
4
+ * ── 为什么开这一只(与 `engineHttpTools.ts` 的分界)──────────────────────────────────────────
5
+ * `engineHttpTools.ts` 转的是两件**纯工具**(URL builder / 存活探针)。本文件转的是 wire 面的
6
+ * **客户端类**、**帧谓词**与**型面** —— 端至今只能直连 `@sema-agent/sdk` 去取它们
7
+ * (cli sdk-isolation 存量册 `sdk-value` 桶的剩余条目),而「壳对引擎 wire 的一切消费必须经本包」
8
+ * 这条公约对它们一直不成立。
9
+ *
10
+ * 🔴 **为什么 0.68.2 之前把 `AgentClient` 记成 declined,现在改成转口**(改口要给理由,不能只改结论):
11
+ * 此前的理由是「转口一个客户端类 = 本包为它的连接姿势/鉴权头/重试语义背书」。这条**顾虑仍然
12
+ * 成立**,但它指向的是「端该不该直接 new 一个 AgentClient」这个**设计问题**,而不是「这条 import
13
+ * 该不该经本包」这个**归层问题**——把两者绑在一起的代价是:归层这件事被一个未排期的设计件
14
+ * 无限期挡住(存量窗已因此过期两代)。⇒ 分开处理:
15
+ * · **归层现在做**(本文件,原样转口,零包装);
16
+ * · **设计件照旧欠着**:本包已有的自有路 `makeEngineWireClient` 才是推荐姿势,端上新码一律走
17
+ * 那一条;本转口口只给**存量**用,不是新码的入口。这一条写在这里,不靠人记得。
18
+ *
19
+ * 🔴 **原样转口,一个字节都不加工**(与 `engineHttpTools.ts` 同一条纪律):不包装、不改签名、
20
+ * 不补默认值 —— 包一层就是把「同一实现」这条唯一的抗漂移保证亲手拆掉。
21
+ *
22
+ * ── 可移植门为什么不受影响(实测,不是推断)────────────────────────────────────────────────
23
+ * · `@sema-agent/sdk` **本来就在** `EXPECTED_PACKAGES_INDEX` 等值集里(本包已有值级 SDK import)
24
+ * ⇒ 集合一个字节不动;
25
+ * · SDK 的 `dist/` 全树 **零 `node:` 内建**(实测:`grep -rl "node:" dist/*.js` 空),传输走全局
26
+ * `fetch`/`AbortController` = web 标准 ⇒ ③ 段的 `--platform=browser` 真打包面零风险;
27
+ * · 型面转口是 `export type`,编译后整段消失 ⇒ 闭包扫描的「剥 type-only」那一步本来就不看它。
28
+ */
29
+ // ── ① 客户端类(存量转口;新码走 `makeEngineWireClient`)────────────────────────────────────
30
+ export { AgentClient } from '@sema-agent/sdk';
31
+ // ── ② 流内审批帧的**信封谓词**(v1 窄化判据;端拿它判「这一帧是不是我认得的那一代」)──────────
32
+ export { isApprovalRequestFrameV1, isApprovalRevokeFrameV1 } from '@sema-agent/sdk';
package/dist/seam.d.ts CHANGED
@@ -533,7 +533,7 @@ export type ChromeEvent = {
533
533
  * ⚠️ **只有 live 腿有本帧**(server 7.69.0 亲读:durable/bg 腿把它喂给 rewind 锚而不入账本)
534
534
  * ⇒ replay/resume 上映射表是空的;那时正解是**不画分割线**,不是回退成「画在最后一条」。
535
535
  */
536
- | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent
536
+ | MessageCommittedChromeEvent | EngineNoticeChromeEvent | TextSegmentEndChromeEvent | ThinkingSegmentEndChromeEvent | WiringManifestChromeEvent | ResultTextDivergedChromeEvent
537
537
  /**
538
538
  * B-078 / L-208(0.65.0):design/172 流内审批协议的**两条帧**。修前它们在标准管线上落
539
539
  * `dropped('unsupported_arm')`,壳自己另接一份 ⇒ 只走包管线的宿主(desktop/web)拿不到
@@ -696,6 +696,54 @@ export interface TextSegmentEndChromeEvent {
696
696
  * 🔴 按**增量拼文**计长,不是 `content` 上的偏移量(见义务 ①)。
697
697
  */
698
698
  committedPrefixLen?: number;
699
+ /**
700
+ * **段身份**(CC-01,0.68.2)—— 与本段已过境的 committed assistant 文本行顶层 `_sema_segment_id`
701
+ * **同值**(本包在 `adapt()` 出口盖;语义与铸法见 `SEMA_SEGMENT_ID_KEY` 头注)。
702
+ * 🔴 宿主按 {@link TextSegmentEndChromeEvent.committedPrefixLen} 换掉已提交那一截时,认行的钥匙是
703
+ * **两把**:行 `uuid` ∧ 行上的段身份 === 本键(按字节相等找行会被同后缀的独立行冒充)。
704
+ * 恒在场(本包每条 leader 段边界都带);它**不是** wire 键 —— `eventId` 才是引擎铸的事件身份。
705
+ */
706
+ segmentId: string;
707
+ /** core 铸的事件身份(uuidv7 形);wire 未必带 ⇒ 缺席时本键不在场。 */
708
+ eventId?: string;
709
+ }
710
+ /**
711
+ * {@link ChromeEvent} 的 `thinking_segment_end` 臂(CC-02,0.68.3;server ≥7.77.0 S-310 `reasoning_end`,
712
+ * sdk ≥9.4.0 `SegmentEndFields` 与 `text_end` 单源)——「**引擎明报:一段推理到此为止,这是它的权威全文**」。
713
+ *
714
+ * ── 与 `text_segment_end` 同一句消费律(sdk 顶注逐字:一个 wire 概念一份形,读法只学一次)────────
715
+ * `reasoning_delta` 逐 chunk 脱敏对**跨 chunk** 的凭据形无能为力,只有整段看得见 ⇒ `content` 是经与
716
+ * `text_end` / `result` **同一只**脱敏器的段权威全文,**可以**与活体增量的拼接不相等。本包在臂上做
717
+ * **整段替换**(思考缓冲 + 活体尾巴),三个 additive 键与 `text_segment_end` **同名同律**(never-false)。
718
+ *
719
+ * ── 与散文段的一处结构差(读这一条再接)──────────────────────────────────────────────────────
720
+ * 思考块**没有 idle-flush**:本包只在三处正常边界提交它(思考→回答边界 / 工具卡前 / turn 收口),
721
+ * 提交是**整块**的 ⇒ 「已提交前缀」在思考面上是**全有或全无**:`committedPrefixDiverged` 在场时,
722
+ * `committedPrefixLen` 恒 = 那条已 committed 思考行的全文长度,而且本臂**多带一把钥匙**
723
+ * {@link ThinkingSegmentEndChromeEvent.committedUuid}(那条行的 `uuid`,本包铸的)—— 思考行**不盖**
724
+ * `_sema_segment_id`(CC-01 只盖文本行,刻意),宿主认行用这把 uuid 就够了(整块换,不切片)。
725
+ *
726
+ * ── 🔴 宿主消费义务(与 `text_segment_end` 逐条同形)─────────────────────────────────────────
727
+ * ① `diverged` 在场 ⇒ **自己拼 `stream_delta{channel:'thinking'}` 上屏**的端按 `content` 重渲该块;
728
+ * 只渲 transcript 平面的端零改动(包已把权威全文换进思考缓冲,下一次提交交的就是它)。
729
+ * ② `committedPrefixDiverged` 在场 ⇒ **所有端都要做**:那条已 committed 的思考行(`uuid === committedUuid`)
730
+ * 是过期的,整块换成 `content`;本包在这一形上**一个字节都不再交**(再交一条 = 同一段推理上屏两遍)。
731
+ * ③ 三键全缺席 ⇒ 照旧只当对账/定界,拿 `content` 再渲一行 = 同一段上屏两遍。
732
+ * ④ 缺席 ≠ 段没结束:只有会报块结束的引擎才发它;按整条流判有没有边界帧。
733
+ */
734
+ export interface ThinkingSegmentEndChromeEvent {
735
+ kind: 'thinking_segment_end';
736
+ laneProof: LaneProof;
737
+ /** 该段推理的**权威全文**(server ≥7.77.0 经脱敏器;可以与活体增量拼接不相等)。读法见义务 ①③。 */
738
+ content: string;
739
+ /** 活体面那份过期了(never false)。 */
740
+ diverged?: true;
741
+ /** 已 committed 的那条思考行过期了(never false)⇒ 整块换,本包不再交字节。 */
742
+ committedPrefixDiverged?: true;
743
+ /** 已 committed 思考行的全文长度(UTF-16 代码单元;never 0)—— 思考面上恒 = 整块。 */
744
+ committedPrefixLen?: number;
745
+ /** `committedPrefixDiverged` 在场时恒在场:要换掉的那条思考行的 `uuid`(本包铸)。 */
746
+ committedUuid?: string;
699
747
  /** core 铸的事件身份(uuidv7 形);wire 未必带 ⇒ 缺席时本键不在场。 */
700
748
  eventId?: string;
701
749
  }
package/dist/seam.js CHANGED
@@ -78,6 +78,14 @@ const CHROME_ARM_TABLE = {
78
78
  required: false,
79
79
  duty: '可选:渲引擎通告(按 code+detail,message 仅 fallback;未知 code 也必须渲不许丢;durable 重放按 eventId 幂等;harvest 的 moved/escalated 并列呈现绝不相减)',
80
80
  },
81
+ thinking_segment_end: {
82
+ // CC-02(0.68.3):与 text_segment_end 同一类目的后果 —— server 7.77.0 起不接 = 未脱敏的推理字节留在
83
+ // 本地转录里(b2 形上那条明文思考行已经渲过 = 已发生的行为)⇒ `required: true`,理由同上臂。
84
+ required: true,
85
+ duty: '整段替换:①`diverged`(never false)在场 ⇒ 自己拼 thinking stream_delta 上屏的端按 content 重渲该块;' +
86
+ '②`committedPrefixDiverged`(never false)在场 ⇒ 所有端把 `uuid === committedUuid` 的那条已 committed 思考行**整块**换成 content(思考面无 idle-flush,前缀全有或全无,`committedPrefixLen` 恒 = 该行全文长度,单位 UTF-16 代码单元);' +
87
+ '本包在这一形上一个字节都不再交;③三键全缺席 ⇒ 照旧只当对账/定界;④缺席 ≠ 段没结束,按整条流判。',
88
+ },
81
89
  wiring_manifest: {
82
90
  required: false,
83
91
  duty: '可选:渲引擎接线自述里的三段用户面事实(modelGate = 本 run 被模型门卸掉的工具 + 逐字恢复办法;' +
@@ -124,7 +132,7 @@ const CHROME_ARM_TABLE = {
124
132
  '缺席 ⇒ 本包交的是尾段,前缀+尾段=content,什么都不用丢;③`committedPrefixLen`(never 0)= 定位量,' +
125
133
  '🔴 **单位 = JS 字符串长度(UTF-16 代码单元),不是 UTF-8 字节、也不是几条消息**' +
126
134
  '(按 UTF-8 字节截会在非 ASCII 正文上截错位置把凭据留在屏上;按消息丢会连合并在同一条消息里的上一段定稿一起删掉),' +
127
- '照抄 `已committed正文.slice(0, 长度 - committedPrefixLen) + content`;按**增量拼文**计长、不是 content 上的偏移;④三键全缺席 ⇒ 照旧只当对账/定界,拿 content 再渲一行 = 同一段上屏两遍。' +
135
+ '照抄 `已committed正文.slice(0, 长度 - committedPrefixLen) + content`;按**增量拼文**计长、不是 content 上的偏移;④三键全缺席 ⇒ 照旧只当对账/定界,拿 content 再渲一行 = 同一段上屏两遍;⑤`segmentId`(恒在场)= 段身份,与本段已过境的 committed 文本行顶层 `_sema_segment_id` 同值 —— 换掉已提交那截时认行的钥匙是**两把**:行 uuid ∧ 段身份(按字节相等找行会被同后缀的独立行冒充)。' +
128
136
  '🔴 缺席只表示「没报」,绝不等于「段没结束」——要退回自家启发式必须按整条流判、不按单帧判',
129
137
  },
130
138
  };
@@ -71,6 +71,13 @@ export interface WorkflowParkRowView {
71
71
  sessionId: string;
72
72
  /** 最初跑出这只 park 的那条 run。 */
73
73
  originRunId: string;
74
+ /**
75
+ * core 7.18.0 #755(0.68.3 CC-05):这一行**在记录上**,但还没有任何 journal 确认它属于谁 ——
76
+ * `originRunId` 此时说的是「材料**属于**哪条 run」,不是「在哪条 run 上见过」,消费端**禁**当已确认读。
77
+ * never-false:确认那一刻上游**删键**(绝不写 `false`)⇒ 缺席 = 已确认(与 7.18.0 之前写的行同读)。
78
+ * 本包逐字投影(core 顶注:same projection obligation as `parks` —— 丢了就把一条未确认行读成已确认)。
79
+ */
80
+ originUnconfirmed?: true;
74
81
  }
75
82
  /**
76
83
  * `GET /v1/workflows/:id` 的 `parks[]` → 三键行;**整键缺席 ⇒ `undefined`**(与空数组逐字可分)。
@@ -85,6 +92,19 @@ export interface WorkflowParkRowView {
85
92
  * 🔴 纯函数、零副作用、**绝不抛**。
86
93
  */
87
94
  export declare function readWorkflowParks(run: unknown): readonly WorkflowParkRowView[] | undefined;
95
+ /**
96
+ * `GET /v1/workflows/:id` 的 `resumeAdmissionIncomplete`(core 7.18.0 #755,0.68.3 CC-05)——
97
+ * 「这条 resume run 的准入**没有完成**」:记录从创建起带 `true`,准入完成那一刻**删键**。
98
+ *
99
+ * 🔴 **在场 = 不是 resume 基**(引擎自己会拒 `workflow.park_truth_unreadable` / `admission_incomplete`,
100
+ * 且不从它派生候选);缺席 = 每条非 resume run 与每次完成了的准入的常态。⇒ 读者只认在场,
101
+ * 缺席不是断言。never-false:严格 `true` 才算在场,其余一律缺席。
102
+ * 🔴 与 {@link readWorkflowParks} **同一条投影义务**(core 顶注逐字:dropping it turns a refused record
103
+ * back into an admissible one)—— 端渲 resume 候选 / 面板行时**必须**带上这一位;本包不把它折进
104
+ * `parks` 的缺席(那一形有自己的两义,见 `readWorkflowParks` 顶注)。
105
+ * 🔴 纯函数、零副作用、绝不抛;坏载体 ⇒ `false`(不是「读不出」:这一位没有第三态)。
106
+ */
107
+ export declare function readWorkflowResumeAdmissionIncomplete(run: unknown): boolean;
88
108
  /** 台账里的一个 agent(读口产物,只读快照;缺席 = 不知道,绝不渲成 0/空)。 */
89
109
  export interface WorkflowActivityAgentView {
90
110
  /** `ctx.agent` 调用的稳定身份(SSE 主键);老引擎/无 callKey 的帧上缺席。 */
@@ -411,13 +411,31 @@ export function readWorkflowParks(run) {
411
411
  continue;
412
412
  // 🔴 **逐键铸**,不 spread:上游在这张行上多放一个键(尤其是凭据形)时,它在结构上到不了
413
413
  // 本包的产物。这不是风格选择 —— 它是本段 ① 那条红线的实现。
414
- rows.push({ callKey, sessionId, originRunId });
414
+ // 7.18.0:第四键 never-false,严格 `true` 才铸(`false`/`"true"`/`1` 都是坏值 = 缺席 = 已确认)。
415
+ rows.push({ callKey, sessionId, originRunId, ...(o.originUnconfirmed === true ? { originUnconfirmed: true } : {}) });
415
416
  }
416
417
  // 非空输入而一行都没活下来 ⇒ 读不出,不是「零 park」(绝不铸 `[]` 冒充一句正面事实)。
417
418
  if (raw.length > 0 && rows.length === 0)
418
419
  return undefined;
419
420
  return rows;
420
421
  }
422
+ /**
423
+ * `GET /v1/workflows/:id` 的 `resumeAdmissionIncomplete`(core 7.18.0 #755,0.68.3 CC-05)——
424
+ * 「这条 resume run 的准入**没有完成**」:记录从创建起带 `true`,准入完成那一刻**删键**。
425
+ *
426
+ * 🔴 **在场 = 不是 resume 基**(引擎自己会拒 `workflow.park_truth_unreadable` / `admission_incomplete`,
427
+ * 且不从它派生候选);缺席 = 每条非 resume run 与每次完成了的准入的常态。⇒ 读者只认在场,
428
+ * 缺席不是断言。never-false:严格 `true` 才算在场,其余一律缺席。
429
+ * 🔴 与 {@link readWorkflowParks} **同一条投影义务**(core 顶注逐字:dropping it turns a refused record
430
+ * back into an admissible one)—— 端渲 resume 候选 / 面板行时**必须**带上这一位;本包不把它折进
431
+ * `parks` 的缺席(那一形有自己的两义,见 `readWorkflowParks` 顶注)。
432
+ * 🔴 纯函数、零副作用、绝不抛;坏载体 ⇒ `false`(不是「读不出」:这一位没有第三态)。
433
+ */
434
+ export function readWorkflowResumeAdmissionIncomplete(run) {
435
+ if (typeof run !== 'object' || run === null || Array.isArray(run))
436
+ return false;
437
+ return run.resumeAdmissionIncomplete === true;
438
+ }
421
439
  // ── 轮询/退避节拍(REF-CC-131,域词表-15):三个 3000ms 常量语义各不相同,拆开命名——合并成一个
422
440
  // 会把三条独立的退避策略绑死在一起(其中任一策略调优都会误伤另外两条)。──────────────────────
423
441
  /** owner 名下暂无任何 run(`resolveRunId` 回 null)时的重试间隔:不是错误,只是「还没有可监的