@sema-agent/client-core 0.72.7 → 0.72.9
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 +25 -0
- package/README.md +2 -1
- package/dist/adapter/activeRunSelfHeal.d.ts +8 -0
- package/dist/adapter/activeRunSelfHeal.js +43 -15
- package/dist/adapter/runStream.d.ts +18 -0
- package/dist/adapter/runStream.js +32 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -0
- package/dist/mcpReconnect.d.ts +131 -0
- package/dist/mcpReconnect.js +235 -0
- package/dist/request/taskRequest.d.ts +30 -0
- package/dist/request/taskRequest.js +79 -3
- package/dist/resumeRefusalCopy.d.ts +2 -2
- package/dist/resumeRefusalCopy.js +10 -4
- package/dist/sessionMemoryStatus.d.ts +15 -1
- package/dist/sessionMemoryStatus.js +18 -0
- package/docs/INTEGRATION-CLIENTS.md +73 -8
- package/package.json +1 -1
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcpReconnect — 会话内 MCP re-dial 的**三端共用消费口**(0.72.9 CC-30;server ≥7.85.0 S-381 / core 7.22.0 #857;
|
|
3
|
+
* 契约 §G.7c / 附录 B.1 `mcpReconnect`;sdk 9.6.0 尚未声明端点与体,读口 duck-typed 注入,与 `sessionMemoryStatus` 同款)。
|
|
4
|
+
*
|
|
5
|
+
* ── 这一口答的是哪件事 ────────────────────────────────────────────────────────────────────
|
|
6
|
+
* 传输断了的 stdio MCP server 在不结束任务的前提下**重拨一台具名 server**:`POST /v1/sessions/:id/mcp/reconnect { server }`。
|
|
7
|
+
* 壳只有「重连这台」按钮与结果渲染;判定(三态分臂 / 名册在场性 / 失败分诊 / 交易文案)三端各写一遍必各错一遍 ⇒ 归包。
|
|
8
|
+
*
|
|
9
|
+
* ── 🔴 五条契约读法(§G.7c 逐字)──────────────────────────────────────────────────────────
|
|
10
|
+
* ① `outcome` 是**唯一判别键**,三态全部走 200:`accepted`(core reconnected)/ `refused`(core refused **或** not_declared,
|
|
11
|
+
* wire 上刻意不可区分,子理由骑 `reason`)/ `unsupported`(server 自己的词:这条 run 一台 MCP 都没声明;不是 409、不是 refused)。
|
|
12
|
+
* ② `unsupported` **恒五键**(taskId / sessionId / server / outcome / reason):那条臂没有 core 结果,`prefix` / `toolCount` / `added` /
|
|
13
|
+
* `removed` 编成 `0` / `[]` = 替 core 说一句它没说过的话 ⇒ 本读器**按 outcome 分支再读**;accepted / refused 四座缺一 ⇒ 整只畸形。
|
|
14
|
+
* ③ **名册按 `toolNames` 的在场性**:在场非空 = 对该 `prefix` 域断言的名册(**超集**:core 挂载前还滤一道);在场 `[]` = 拨号失败、
|
|
15
|
+
* 这台的工具被撤走(空数组是一句话);整键缺席 = 这条臂从未碰过连接,挂载名册原样不动。真挂上了什么读 `wiring_manifest.tools`。
|
|
16
|
+
* ④ **下一步动作读 `status.errorCode` / `httpStatus`,不解析 `reason`**:`errorCode` 是 core 十词闭集但 server 不枚举 ⇒ 本读器按**开集**
|
|
17
|
+
* 透传(消费端 switch 必带 default);`delivered` 三词 / `transportClosed` 在场即 true(缺席 = 没观察到关闭,**不是**还活着);
|
|
18
|
+
* `error` / `serverInfo` / `reason` 是远端自由文本 —— UNTRUSTED-for-display,呈前消毒封长。
|
|
19
|
+
* ⑤ **交易,不是刷新**(core 原话,契约 🔴 必进 UI):旧连接在拨新号**之前**就被关 ⇒ 一次失败的重拨花掉一条能用的连接。
|
|
20
|
+
* 壳不许只写「重连」—— 措辞单源 {@link MCP_RECONNECT_TRANSACTION_NOTICE}。
|
|
21
|
+
*
|
|
22
|
+
* ── 失败分诊按 `errorCode` 不按 status(与 `classifyMemoryStatusFailure` 同律)────────────────
|
|
23
|
+
* 422 `steering.invalid_content` ⇒ invalid · 409 `steering.not_running` ⇒ not_running(parked / 终态 / 他副本 / 无活流四臂一码)·
|
|
24
|
+
* 501 `capability.run_store_required` ⇒ unsupported/capability · 404 `not_found.route` ⇒ unsupported/route(老 server 无此路由)·
|
|
25
|
+
* 404 `not_found.session` ⇒ not_found(未知 / 非属主同码同串)· 无码 ⇒ failed(**不猜**)。
|
|
26
|
+
*/
|
|
27
|
+
import { capForDisplay } from './fleetTaskDesc.js';
|
|
28
|
+
/** `outcome` 三词(契约 §G.7c;`unsupported` 是 server 的词,core 没有它)。 */
|
|
29
|
+
export const MCP_RECONNECT_OUTCOMES = Object.freeze(['accepted', 'refused', 'unsupported']);
|
|
30
|
+
/**
|
|
31
|
+
* 「重连 = 一次交易」的**唯一措辞铸点**(三端共用;契约 🔴 这句必须进 UI,壳不许只写「重连」)。
|
|
32
|
+
* 零内部车道词;只说用户能据以决策的事实:旧连接先关、失败则工具撤走、直到再拨成功。
|
|
33
|
+
*/
|
|
34
|
+
export const MCP_RECONNECT_TRANSACTION_NOTICE = 'Reconnecting closes the current connection before dialing again. On a healthy server this is a transaction, ' +
|
|
35
|
+
'not a refresh: a failed re-dial leaves the server down and withdraws its tools from the model until a later re-dial succeeds.';
|
|
36
|
+
const RECONNECT_TEXT_MAX = 200;
|
|
37
|
+
const RECONNECT_WORD_MAX = 40;
|
|
38
|
+
/** 契约 §G.7c:`server` 非空且 ≤ 190 字符(逐字进 core 运维通告 `detail.server`)。 */
|
|
39
|
+
const SERVER_NAME_MAX = 190;
|
|
40
|
+
const isRecord = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
41
|
+
const nonEmpty = (v) => typeof v === 'string' && v.length > 0;
|
|
42
|
+
const stringList = (v) => Array.isArray(v) && v.every((t) => typeof t === 'string') ? [...v] : undefined;
|
|
43
|
+
function projectStatus(raw) {
|
|
44
|
+
if (!isRecord(raw) || !nonEmpty(raw.name) || !nonEmpty(raw.status))
|
|
45
|
+
return undefined;
|
|
46
|
+
// `name` 是**身份键**(core `statuses[name]` 的键,与顶层 `server` 同一事实),不是远端自由文本 ⇒ 原样,不封长(opus 替补复审 [low]);
|
|
47
|
+
// 消毒封长只给 serverInfo / error 两座远端文本。
|
|
48
|
+
const view = { name: raw.name, status: raw.status };
|
|
49
|
+
if (nonEmpty(raw.errorCode))
|
|
50
|
+
view.errorCode = raw.errorCode;
|
|
51
|
+
if (typeof raw.httpStatus === 'number' && Number.isInteger(raw.httpStatus))
|
|
52
|
+
view.httpStatus = raw.httpStatus;
|
|
53
|
+
if (nonEmpty(raw.delivered))
|
|
54
|
+
view.delivered = raw.delivered;
|
|
55
|
+
const si = raw.serverInfo;
|
|
56
|
+
if (isRecord(si) && typeof si.name === 'string' && typeof si.version === 'string') {
|
|
57
|
+
view.serverInfo = { name: capForDisplay(si.name, RECONNECT_WORD_MAX), version: capForDisplay(si.version, RECONNECT_WORD_MAX) };
|
|
58
|
+
}
|
|
59
|
+
if (typeof raw.error === 'string')
|
|
60
|
+
view.error = capForDisplay(raw.error, RECONNECT_TEXT_MAX);
|
|
61
|
+
if (raw.transportClosed === true)
|
|
62
|
+
view.transportClosed = true;
|
|
63
|
+
return view;
|
|
64
|
+
}
|
|
65
|
+
function projectListingIncomplete(raw) {
|
|
66
|
+
// `reason` / `pages` 必填(server 侧无条件投 pages):缺一 = 读不懂这一格,由调用方判整只畸形(在场但坏形 ≠ 缺席)。
|
|
67
|
+
if (!isRecord(raw) || !nonEmpty(raw.reason))
|
|
68
|
+
return undefined;
|
|
69
|
+
if (typeof raw.pages !== 'number' || !Number.isInteger(raw.pages) || raw.pages < 0)
|
|
70
|
+
return undefined;
|
|
71
|
+
const view = { reason: raw.reason, pages: raw.pages };
|
|
72
|
+
if (typeof raw.budgetMs === 'number' && Number.isFinite(raw.budgetMs))
|
|
73
|
+
view.budgetMs = raw.budgetMs;
|
|
74
|
+
if (typeof raw.error === 'string')
|
|
75
|
+
view.error = capForDisplay(raw.error, RECONNECT_TEXT_MAX);
|
|
76
|
+
return view;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 200 体 → 读视图;**畸形一律 `undefined`**(不编、不猜)。按 `outcome` 分形读键(文件头 ①②③④)。
|
|
80
|
+
*/
|
|
81
|
+
export function projectMcpReconnectResult(body) {
|
|
82
|
+
if (!isRecord(body))
|
|
83
|
+
return undefined;
|
|
84
|
+
if (!nonEmpty(body.taskId) || !nonEmpty(body.sessionId) || !nonEmpty(body.server))
|
|
85
|
+
return undefined;
|
|
86
|
+
const outcome = body.outcome;
|
|
87
|
+
if (typeof outcome !== 'string' || !MCP_RECONNECT_OUTCOMES.includes(outcome))
|
|
88
|
+
return undefined;
|
|
89
|
+
const base = { taskId: body.taskId, sessionId: body.sessionId, server: body.server };
|
|
90
|
+
if (outcome === 'unsupported') {
|
|
91
|
+
// 恒五键:其余键真出现了也不过境。`reason` 是人读文本(server 侧可为空)—— `outcome` 才是唯一判别键,
|
|
92
|
+
// 一句人读文本不许把判别键整只报废(opus 替补复审 [low]):缺席 / 空串 ⇒ 视图无 reason 键。
|
|
93
|
+
const u = { outcome: 'unsupported', ...base };
|
|
94
|
+
if (nonEmpty(body.reason))
|
|
95
|
+
u.reason = capForDisplay(body.reason, RECONNECT_TEXT_MAX);
|
|
96
|
+
return u;
|
|
97
|
+
}
|
|
98
|
+
// accepted / refused:四座必填(core 型上必填,server 原样投),缺一整只畸形。
|
|
99
|
+
const added = stringList(body.added);
|
|
100
|
+
const removed = stringList(body.removed);
|
|
101
|
+
if (!nonEmpty(body.prefix) || typeof body.toolCount !== 'number' || !Number.isInteger(body.toolCount) || body.toolCount < 0)
|
|
102
|
+
return undefined;
|
|
103
|
+
if (added === undefined || removed === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
const view = {
|
|
106
|
+
outcome: outcome,
|
|
107
|
+
...base,
|
|
108
|
+
prefix: body.prefix,
|
|
109
|
+
toolCount: body.toolCount,
|
|
110
|
+
added,
|
|
111
|
+
removed,
|
|
112
|
+
};
|
|
113
|
+
// 🔴 「在场但坏形」≠「缺席」(opus 替补复审 [medium]×2):`toolNames` 缺席 = 名册别动、`listingIncomplete` 缺席 = 名册走完了,
|
|
114
|
+
// 两者在契约上都是**正面指令**;把读不懂的那一格折成缺席 = 替 server 下一条它没下的指令 ⇒ 在场但坏形一律整只畸形。
|
|
115
|
+
if ('toolNames' in body && body.toolNames !== undefined) {
|
|
116
|
+
const toolNames = stringList(body.toolNames);
|
|
117
|
+
if (toolNames === undefined)
|
|
118
|
+
return undefined;
|
|
119
|
+
view.toolNames = toolNames;
|
|
120
|
+
}
|
|
121
|
+
if (nonEmpty(body.reason))
|
|
122
|
+
view.reason = capForDisplay(body.reason, RECONNECT_TEXT_MAX);
|
|
123
|
+
const status = projectStatus(body.status);
|
|
124
|
+
if (status !== undefined)
|
|
125
|
+
view.status = status;
|
|
126
|
+
if ('listingIncomplete' in body && body.listingIncomplete !== undefined) {
|
|
127
|
+
const li = projectListingIncomplete(body.listingIncomplete);
|
|
128
|
+
if (li === undefined)
|
|
129
|
+
return undefined;
|
|
130
|
+
view.listingIncomplete = li;
|
|
131
|
+
}
|
|
132
|
+
return view;
|
|
133
|
+
}
|
|
134
|
+
/** 抛出物 → 判决(按 `errorCode`;与 `classifyMemoryStatusFailure` 同律:无码不猜)。 */
|
|
135
|
+
export function classifyMcpReconnectFailure(e) {
|
|
136
|
+
// 🔴 读 `errorCode` 本身包在 try 里(异源对抗复审 [medium]):拒因可以是 Proxy / 带抛 getter 的对象,读一个属性就抛 ⇒
|
|
137
|
+
// 分诊器抛穿「永不抛」。读不出 = 判不出码 ⇒ failed,原拒因原样带出。
|
|
138
|
+
let code;
|
|
139
|
+
try {
|
|
140
|
+
code = typeof e === 'object' && e !== null ? e.errorCode : undefined;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return { kind: 'failed', error: e };
|
|
144
|
+
}
|
|
145
|
+
if (code === 'steering.invalid_content')
|
|
146
|
+
return { kind: 'invalid' };
|
|
147
|
+
if (code === 'steering.not_running')
|
|
148
|
+
return { kind: 'not_running' };
|
|
149
|
+
// 501 两码并列(附录 B.1:缺 sessionStorage.ownerOf ⇒ `capability.session_ownership_required`,缺 run 账面 ⇒ `capability.run_store_required`)
|
|
150
|
+
// ⇒ 前缀族开集(opus 替补复审 [medium]);`feature.*` = 面在但开关关着,与 capability 分列(与 memory-status 分诊同律)。
|
|
151
|
+
if (typeof code === 'string' && code.startsWith('capability.'))
|
|
152
|
+
return { kind: 'unsupported', reason: 'capability' };
|
|
153
|
+
if (typeof code === 'string' && code.startsWith('feature.'))
|
|
154
|
+
return { kind: 'unsupported', reason: 'feature' };
|
|
155
|
+
if (code === 'not_found.route')
|
|
156
|
+
return { kind: 'unsupported', reason: 'route' };
|
|
157
|
+
if (code === 'not_found.session')
|
|
158
|
+
return { kind: 'not_found' };
|
|
159
|
+
return { kind: 'failed', error: e };
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* 「重连这台 MCP」的消费口:**永不抛**。本地先按契约把体门(`server` 非空 ≤ 190)判掉,不发一条注定 400 的请求;
|
|
163
|
+
* 回体经 {@link projectMcpReconnectResult} 投影;**回声身份**(`sessionId` / `server`)与请求逐字相等,否则 `failed`(宁缺席不串台,
|
|
164
|
+
* 与 `readSessionMemoryStatus` 同律)。注入口没有方法 ⇒ `unsupported/route`(壳没接线 = 面不存在)。
|
|
165
|
+
*/
|
|
166
|
+
export async function reconnectMcpServer(client, sessionId, server, opts) {
|
|
167
|
+
if (!nonEmpty(sessionId))
|
|
168
|
+
return { kind: 'invalid' };
|
|
169
|
+
if (!nonEmpty(server) || server.length > SERVER_NAME_MAX)
|
|
170
|
+
return { kind: 'invalid' };
|
|
171
|
+
try {
|
|
172
|
+
const verb = client?.sessions?.mcpReconnect;
|
|
173
|
+
if (typeof verb !== 'function')
|
|
174
|
+
return { kind: 'unsupported', reason: 'route' };
|
|
175
|
+
// 🔴 本地取消闸(异源复审第二轮 [medium]):signal 只转交给注入口不够 —— 壳的传输若忽略它,用户已撤销的操作仍会关掉一条
|
|
176
|
+
// 健康连接并重拨(失败则工具撤走)。破坏性动词在「用户说停」之后一发都不许出。
|
|
177
|
+
if (opts?.signal?.aborted === true) {
|
|
178
|
+
return { kind: 'failed', error: opts.signal.reason ?? new Error('mcpReconnect: aborted before the verb was called') };
|
|
179
|
+
}
|
|
180
|
+
// 方法形调用保接收者(真 SDK 资源方法体走 this.t.request)。
|
|
181
|
+
const raw = await client.sessions.mcpReconnect(sessionId, { server }, opts?.signal ? { signal: opts.signal } : undefined);
|
|
182
|
+
const view = projectMcpReconnectResult(raw);
|
|
183
|
+
if (view === undefined)
|
|
184
|
+
return { kind: 'failed', error: new Error('mcpReconnect: malformed body') };
|
|
185
|
+
if (view.sessionId !== sessionId || view.server !== server)
|
|
186
|
+
return { kind: 'failed', error: new Error('mcpReconnect: response identity mismatch') };
|
|
187
|
+
return { kind: 'ok', result: view };
|
|
188
|
+
}
|
|
189
|
+
catch (e) {
|
|
190
|
+
return classifyMcpReconnectFailure(e);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* 能力位 `GET /v1/capabilities.mcpReconnect`(设施依赖 = runStore ∧ sessionStorage.ownerOf)的三态读:
|
|
195
|
+
* `available` / `unavailable` / `not_reported`(键缺席 = 老 server <7.85.0 **判不了**,不折 unavailable);坏形 ⇒ `undefined`。
|
|
196
|
+
* 纯投影(caps 体由宿主交,本包不另开 tee:这一位只在按钮亮不亮时读一次)。
|
|
197
|
+
*/
|
|
198
|
+
export function projectMcpReconnectCapability(caps) {
|
|
199
|
+
if (!isRecord(caps))
|
|
200
|
+
return undefined;
|
|
201
|
+
if (!('mcpReconnect' in caps) || caps.mcpReconnect === undefined)
|
|
202
|
+
return 'not_reported';
|
|
203
|
+
if (caps.mcpReconnect === true)
|
|
204
|
+
return 'available';
|
|
205
|
+
if (caps.mcpReconnect === false)
|
|
206
|
+
return 'unavailable';
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* 三态 → 一句结果(**唯一措辞铸点**;三端共用;零内部车道词)。`accepted` 带工具计数;`refused` 说清工具已撤走(交易的代价)并带 reason;
|
|
211
|
+
* `unsupported` 说清是这条 run 的装配(未声明 MCP),不是 refused。
|
|
212
|
+
*/
|
|
213
|
+
export function mcpReconnectOutcomeDetail(view) {
|
|
214
|
+
const name = capForDisplay(view.server, RECONNECT_WORD_MAX);
|
|
215
|
+
switch (view.outcome) {
|
|
216
|
+
case 'accepted':
|
|
217
|
+
return `MCP server ${name} was re-dialed: ${view.toolCount} tool${view.toolCount === 1 ? '' : 's'} mounted (${view.added.length} added, ${view.removed.length} removed)`;
|
|
218
|
+
case 'refused': {
|
|
219
|
+
// 🔴 名册按 toolNames 在场性分三句(异源对抗复审 [medium]:此前无条件说「工具已撤走」,把未知名 / 重拨在飞 / 已撤销这些
|
|
220
|
+
// **没碰连接**的拒绝也说成撤了工具,误导用户再来一次破坏性重拨):
|
|
221
|
+
// 缺席 = 这条臂从未碰过连接,名册未动;[] = 拨号失败,工具撤走;非空 = 拒了但对该 prefix 域做了名册断言,如实报数。
|
|
222
|
+
const why = view.reason !== undefined ? ` — ${view.reason}` : '';
|
|
223
|
+
if (view.toolNames === undefined) {
|
|
224
|
+
return `MCP server ${name} was NOT re-dialed; its connection and tools were left untouched${why}`;
|
|
225
|
+
}
|
|
226
|
+
if (view.toolNames.length === 0) {
|
|
227
|
+
return `MCP server ${name} was NOT re-dialed and its tools were withdrawn from the model until a later re-dial succeeds${why}`;
|
|
228
|
+
}
|
|
229
|
+
return `MCP server ${name} was NOT re-dialed; the engine reports ${view.toolNames.length} tool${view.toolNames.length === 1 ? '' : 's'} for it after this attempt${why}`;
|
|
230
|
+
}
|
|
231
|
+
case 'unsupported':
|
|
232
|
+
return (`This run declared no MCP servers, so there is nothing to re-dial (${name} is not part of this run)` +
|
|
233
|
+
(view.reason !== undefined ? ` — ${view.reason}` : ''));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -96,6 +96,11 @@ export interface TaskRequestInput {
|
|
|
96
96
|
* 一个 400 回来。JS 调用方(本包是发出去的 npm 公开面)绕过型面时由构造器再判一次值。
|
|
97
97
|
*/
|
|
98
98
|
memoryCapture?: 'off';
|
|
99
|
+
/**
|
|
100
|
+
* 「这一次别给我记忆」的**请求级整面关**(CC-39③;server §12.7 姊妹键)。单成员闭集 `'off'`,缺席 = 记忆照常挂。
|
|
101
|
+
* 型面同 {@link TaskRequestInput.memoryCapture}:给它一个宽串型等于邀请调用方发一个 400 回来;JS 调用方由构造器再判一次值。
|
|
102
|
+
*/
|
|
103
|
+
memory?: 'off';
|
|
99
104
|
/** rewind 三件(E18);端自己判空。 */
|
|
100
105
|
rewind?: Record<string, unknown>;
|
|
101
106
|
/** 仓库坐标串(scan/code-review 场景;今日唯一供给方 web 的 Repository 输入=url/`owner/name` 串。
|
|
@@ -172,6 +177,31 @@ export declare const MEMORY_CAPTURE_OFF: "off";
|
|
|
172
177
|
export declare function memoryCaptureDeclarationField(declared: boolean): {
|
|
173
178
|
memoryCapture: 'off';
|
|
174
179
|
} | Record<string, never>;
|
|
180
|
+
/** `memory: "off"` 的唯一字面(server §12.7;core #863 W3)。 */
|
|
181
|
+
export declare const MEMORY_OFF: "off";
|
|
182
|
+
/**
|
|
183
|
+
* 「这一次别给我记忆」的**意图位** → 提交腿的 spread-ready 片段(CC-39③,0.72.8;与 {@link memoryCaptureDeclarationField} 同律)。
|
|
184
|
+
* 🔴 `false` ⇒ 整键缺席(= 记忆照常挂的唯一写法),不是 `{ memory: undefined }`、更不是某个「on」值。
|
|
185
|
+
* 🔴 本层只答「下一条提交带不带这一键」,不答「这次真的没挂记忆面」:后者只有引擎知道(老 server 400 / 更老 worker 静默忽略);
|
|
186
|
+
* 终局 memory 状态词 `disabled`(不是 `no-spec`)才是引擎的答案,端别拿这一位去渲「已关」。
|
|
187
|
+
* 🔴 两键同发(`memoryCapture:"off"` + `memory:"off"`)时 capture 的单向记录**不落**(core 只在挂引擎时记)—— 契约 §12.7 写真话,
|
|
188
|
+
* 不许拿 `memory:"off"` 当隐私声明;要「别记住我」用 memoryCapture 那一键。
|
|
189
|
+
*/
|
|
190
|
+
export declare function memoryOffDeclarationField(declared: boolean): {
|
|
191
|
+
memory: 'off';
|
|
192
|
+
} | Record<string, never>;
|
|
193
|
+
/**
|
|
194
|
+
* 端的两个入口 → 「这一次别给我记忆」的**意图位**(cli [7510]:发口投影归包,三端同判、壳零自判)。
|
|
195
|
+
* · `flag`:CLI 旗 `--memory <word>` 的原词 —— **只认恰 `'off'`**(单成员闭集;`'on'` / 大小写 / 带空格一律不认,
|
|
196
|
+
* 那些拼法上 wire 会被 server 400,而本层不替它买路也不替它编);
|
|
197
|
+
* · `settingsMemory`:settings 的 CC 形 `memory` 段 —— **只认严格 `{ enabled: false }`**(`"false"` / `0` / 缺席 = 沉默不是关,
|
|
198
|
+
* 与 core `ParentSpecSeat.memoryOff` 只从显式 `enabled: false` 铸同律)。
|
|
199
|
+
* 任一成立 ⇒ `true`;其余 / 坏形 / 非对象入参 ⇒ `false`(= 键缺席,记忆照常挂)。喂给 {@link memoryOffDeclarationField}。
|
|
200
|
+
*/
|
|
201
|
+
export declare function memoryOffDeclared(input: {
|
|
202
|
+
flag?: unknown;
|
|
203
|
+
settingsMemory?: unknown;
|
|
204
|
+
} | null | undefined): boolean;
|
|
175
205
|
export declare function buildTaskRequest(input: TaskRequestInput, lane: RequestLane): TaskRequestLike;
|
|
176
206
|
/** `applyLiveRequestDefaults` 的宿主输入(端解析好的值,同样零取值方式)。 */
|
|
177
207
|
export interface LiveDefaultsInput {
|
|
@@ -51,6 +51,12 @@ export const REQUEST_FIELD_MATRIX = [
|
|
|
51
51
|
// 在飞的 run,本键管「本会话的下一次新提交」。非 live 的 verb(parked / 终态 / 他副本)回
|
|
52
52
|
// 409 `steering.not_running` 并**指路本键** —— 端没有本键就接不住那条指路。
|
|
53
53
|
{ field: 'memoryCapture', lanes: ['interactive', 'print'], live: true, why: '本会话永远不进长期记忆的**声明形**(server §12.4 body 键,单成员闭集 "off");两条车道都是提交面 ⇒ 都能声明。与 memoryWrite:false 不是一个轴(那是「本 run 只读平面」)。⚠️ parked 续跑腿结构性拿不到声明(决策/续跑端点从持久 body 重建 spec),那是引擎侧事实,不是本层的漏' },
|
|
54
|
+
// ── memory:"off"(CC-39③,0.72.8;server 7.84.0 S-405 / core #863 W3)────────────────────────
|
|
55
|
+
// 🔴 契约 §12.7 姊妹键:`memoryCapture` = 「记忆照常用,但别记住我」(会话级采集面关,一次性);`memory` =
|
|
56
|
+
// 「这一次别给我记忆」(请求级整面关:不挂 # Memory 段 / 索引 / 三只记忆工具 / 写准入;零记录,可逐请求开关)。
|
|
57
|
+
// 同形、不同轴、可并存;两键共用同一只拼写判据(400 `request.field_invalid` 两腿同句)。缺席 = 记忆照常挂。
|
|
58
|
+
// 无能力位(7.78.1–7.83.x 只能 trial-by-400)⇒ 本层不预判 server 版本,照发。子代地板(ParentSpecSeat.memoryOff)归 core。
|
|
59
|
+
{ field: 'memory', lanes: ['interactive', 'print'], live: true, why: '这一次运行不挂记忆面的**请求级**开关(server §12.7 body 键,单成员闭集 "off");两条车道都是提交面 ⇒ 都能声明。与 memoryCapture(采集面)不是一个轴、可并存;与 memoryWrite:false(只读平面)也不是一个轴' },
|
|
54
60
|
// ── print 独有(§8-5 认定的**有理由**的差异)──────────────────────────────────────────────
|
|
55
61
|
{ field: 'finalVerification', lanes: ['print'], live: true, why: 'P1-1 终验:无人值守车道才需要引擎自证;交互 REPL 由人当场看结果。#106 裁 B(让位+告知)后交互面默认关' },
|
|
56
62
|
{ field: 'limits', lanes: ['print'], live: true, why: 'P2-3-b:`-p` 的预算护栏(--max-* flag 族),交互 REPL 由人随时 Esc' },
|
|
@@ -122,6 +128,13 @@ const PERMISSION_FACE_KEY = 'permissions';
|
|
|
122
128
|
* `toString` 与 `constructor` 都抛错,而**构造错误文本时抛错**会把「拒」这个判决换成一个面目全非的
|
|
123
129
|
* 异常。判决是拒,描述只是线索,取不到就报 unreadable。
|
|
124
130
|
*/
|
|
131
|
+
/**
|
|
132
|
+
* 拒绝文本用的**粗分类**(0.72.8 异源复审第二轮 [medium] 真修):只用 `typeof` / null / array 三个固定词,**不**读
|
|
133
|
+
* `Symbol.toStringTag`、也不读 prototype 的 constructor 名 —— 那两样都是**调用方可控**的串,对象可以借它们把敏感内容
|
|
134
|
+
* 带进错误展示 / 日志。memory 一族(隐私 / 记忆面声明)的拒绝路径一律走它;`shapeTag` 留给 settings.resolved 那类
|
|
135
|
+
* 载体形诊断(那里报的是构造器名这一线索,与本族的「只报形状不回显值」纪律不同)。
|
|
136
|
+
*/
|
|
137
|
+
const coarseShape = (v) => v === null ? 'typeof=object / null' : Array.isArray(v) ? 'typeof=object / array' : `typeof=${typeof v}`;
|
|
125
138
|
const shapeTag = (v) => {
|
|
126
139
|
try {
|
|
127
140
|
const proto = Object.getPrototypeOf(v);
|
|
@@ -447,13 +460,69 @@ export const MEMORY_CAPTURE_OFF = 'off';
|
|
|
447
460
|
export function memoryCaptureDeclarationField(declared) {
|
|
448
461
|
return declared ? { memoryCapture: MEMORY_CAPTURE_OFF } : {};
|
|
449
462
|
}
|
|
463
|
+
/** `memory: "off"` 的唯一字面(server §12.7;core #863 W3)。 */
|
|
464
|
+
export const MEMORY_OFF = 'off';
|
|
465
|
+
/**
|
|
466
|
+
* 「这一次别给我记忆」的**意图位** → 提交腿的 spread-ready 片段(CC-39③,0.72.8;与 {@link memoryCaptureDeclarationField} 同律)。
|
|
467
|
+
* 🔴 `false` ⇒ 整键缺席(= 记忆照常挂的唯一写法),不是 `{ memory: undefined }`、更不是某个「on」值。
|
|
468
|
+
* 🔴 本层只答「下一条提交带不带这一键」,不答「这次真的没挂记忆面」:后者只有引擎知道(老 server 400 / 更老 worker 静默忽略);
|
|
469
|
+
* 终局 memory 状态词 `disabled`(不是 `no-spec`)才是引擎的答案,端别拿这一位去渲「已关」。
|
|
470
|
+
* 🔴 两键同发(`memoryCapture:"off"` + `memory:"off"`)时 capture 的单向记录**不落**(core 只在挂引擎时记)—— 契约 §12.7 写真话,
|
|
471
|
+
* 不许拿 `memory:"off"` 当隐私声明;要「别记住我」用 memoryCapture 那一键。
|
|
472
|
+
*/
|
|
473
|
+
export function memoryOffDeclarationField(declared) {
|
|
474
|
+
return declared ? { memory: MEMORY_OFF } : {};
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* 端的两个入口 → 「这一次别给我记忆」的**意图位**(cli [7510]:发口投影归包,三端同判、壳零自判)。
|
|
478
|
+
* · `flag`:CLI 旗 `--memory <word>` 的原词 —— **只认恰 `'off'`**(单成员闭集;`'on'` / 大小写 / 带空格一律不认,
|
|
479
|
+
* 那些拼法上 wire 会被 server 400,而本层不替它买路也不替它编);
|
|
480
|
+
* · `settingsMemory`:settings 的 CC 形 `memory` 段 —— **只认严格 `{ enabled: false }`**(`"false"` / `0` / 缺席 = 沉默不是关,
|
|
481
|
+
* 与 core `ParentSpecSeat.memoryOff` 只从显式 `enabled: false` 铸同律)。
|
|
482
|
+
* 任一成立 ⇒ `true`;其余 / 坏形 / 非对象入参 ⇒ `false`(= 键缺席,记忆照常挂)。喂给 {@link memoryOffDeclarationField}。
|
|
483
|
+
*/
|
|
484
|
+
export function memoryOffDeclared(input) {
|
|
485
|
+
if (typeof input !== 'object' || input === null)
|
|
486
|
+
return false;
|
|
487
|
+
// 🔴 异源对抗复审 [medium] 真修:入口投影**不许**把打错字的收紧声明降级成「缺席」—— 那样片段函数删键、`refuseBadMemoryOff`
|
|
488
|
+
// 无从校验,用户以为关了、这次运行照常挂上记忆面。旗在场但不是 'off' ⇒ 响亮拒(与 buildTaskRequest 同律,不回显值)。
|
|
489
|
+
const flag = input.flag;
|
|
490
|
+
if (flag !== undefined && flag !== null && flag !== MEMORY_OFF) {
|
|
491
|
+
throw new TypeError(`memoryOffDeclared: --memory 旗只接受字面量 \'off\' 或缺席 —— 收到 ${coarseShape(flag)}。上游是单成员闭集(没有 "on"),` +
|
|
492
|
+
'打错字的关闭声明若被折成缺席,这一次运行会照常挂上记忆面而声明人不会知道。错误文本只报形状,不回显值。');
|
|
493
|
+
}
|
|
494
|
+
let fromSettings = false;
|
|
495
|
+
const sm = input.settingsMemory;
|
|
496
|
+
if (sm !== undefined && sm !== null) {
|
|
497
|
+
if (typeof sm !== 'object' || Array.isArray(sm)) {
|
|
498
|
+
throw new TypeError(`memoryOffDeclared: settings 的 memory 段必须是对象 —— 收到 ${coarseShape(sm)}。`);
|
|
499
|
+
}
|
|
500
|
+
const enabled = sm.enabled;
|
|
501
|
+
// CC 形 `memory: { enabled }`:缺席 = 沉默(不是关);在场必须是布尔 —— "false" / 0 是打错字的收紧声明,同样响亮拒。
|
|
502
|
+
if (enabled !== undefined && typeof enabled !== 'boolean') {
|
|
503
|
+
throw new TypeError(`memoryOffDeclared: settings memory.enabled 在场时必须是布尔 —— 收到 ${coarseShape(enabled)}。`);
|
|
504
|
+
}
|
|
505
|
+
fromSettings = enabled === false;
|
|
506
|
+
}
|
|
507
|
+
return flag === MEMORY_OFF || fromSettings;
|
|
508
|
+
}
|
|
509
|
+
const refuseBadMemoryOff = (v) => {
|
|
510
|
+
if (v === undefined || v === null)
|
|
511
|
+
return;
|
|
512
|
+
if (v === 'off')
|
|
513
|
+
return;
|
|
514
|
+
throw new TypeError('buildTaskRequest: memory 只接受字面量 `\'off\'` 或缺席(undefined/null)—— ' +
|
|
515
|
+
`收到 ${coarseShape(v)}。这一位是**请求级整面开关**(「这一次别给我记忆」),上游是**单成员闭集**(没有 ` +
|
|
516
|
+
'`"on"`,`{ enabled: false }` 对象形也不是 wire 拼法)且对坏拼写**响亮拒**(server 400 `request.field_invalid`)。' +
|
|
517
|
+
'本层若静默删键,这一次运行会照常挂上记忆面而声明人不会知道 —— 与 memoryCapture 同一条纪律。错误文本只报形状,不回显值。');
|
|
518
|
+
};
|
|
450
519
|
const refuseBadMemoryCapture = (v) => {
|
|
451
520
|
if (v === undefined || v === null)
|
|
452
521
|
return;
|
|
453
522
|
if (v === 'off')
|
|
454
523
|
return;
|
|
455
524
|
throw new TypeError('buildTaskRequest: memoryCapture 只接受字面量 `\'off\'` 或缺席(undefined/null)—— ' +
|
|
456
|
-
`收到 ${
|
|
525
|
+
`收到 ${coarseShape(v)}。这一位是**会话级隐私声明**(「本会话永远不进长期记忆」),` +
|
|
457
526
|
'上游是**单成员闭集**(没有 `"on"`)且对坏拼写**响亮拒**(core `config.memory_capture_spelling` / ' +
|
|
458
527
|
'server 400 `request.field_invalid`)。本层若把坏值静默删键,请求会照发、引擎会照常采集,' +
|
|
459
528
|
'而声明人不会知道自己的 opt-out 丢了 —— 把一条隐私请求按打字错误静默丢掉是**禁的方向**。' +
|
|
@@ -462,7 +531,12 @@ const refuseBadMemoryCapture = (v) => {
|
|
|
462
531
|
export function buildTaskRequest(input, lane) {
|
|
463
532
|
const live = input.live;
|
|
464
533
|
// ⑤ 响亮拒排在最前面:一条打错字的隐私声明绝不许被后面任何一条「不 stamp」路径吞掉。
|
|
465
|
-
|
|
534
|
+
// 🔴 只读一次(异源复审第二轮 [medium] 真修):校验与 stamp 共用**同一次读取**。带 getter / proxy 的入参在两次读取之间可以
|
|
535
|
+
// 变值 —— 第一次 'off' 过了校验、第二次 'OFF' 在 stamp 处被折成缺席,一条收紧声明就这样静默丢了。
|
|
536
|
+
const memoryCaptureIn = input.memoryCapture;
|
|
537
|
+
const memoryIn = input.memory;
|
|
538
|
+
refuseBadMemoryCapture(memoryCaptureIn);
|
|
539
|
+
refuseBadMemoryOff(memoryIn);
|
|
466
540
|
/** stamp 门:字段在本车道的表里 ∧(非 live-gated ∨ 本次是 live 车道)∧ 值非空。 */
|
|
467
541
|
const on = (field, value) => {
|
|
468
542
|
if (value === undefined || value === null)
|
|
@@ -541,9 +615,11 @@ export function buildTaskRequest(input, lane) {
|
|
|
541
615
|
: {}),
|
|
542
616
|
...(on('oneShot', input.oneShot) && input.oneShot === true ? { oneShot: true } : {}),
|
|
543
617
|
// ⑤ 单成员闭集:值**恰是** `'off'` 才 stamp。表外拼写在上面已经**响亮拒**(见 `refuseBadMemoryCapture`)。
|
|
544
|
-
...(on('memoryCapture',
|
|
618
|
+
...(on('memoryCapture', memoryCaptureIn) && memoryCaptureIn === 'off'
|
|
545
619
|
? { memoryCapture: 'off' }
|
|
546
620
|
: {}),
|
|
621
|
+
// CC-39③:姊妹键同律(值恰是 'off' 才 stamp;坏拼写已在上面响亮拒)。
|
|
622
|
+
...(on('memory', memoryIn) && memoryIn === 'off' ? { memory: 'off' } : {}),
|
|
547
623
|
...(Object.keys(settingsOut).length > 0 ? { settings: settingsOut } : {}),
|
|
548
624
|
};
|
|
549
625
|
return out;
|
|
@@ -153,7 +153,7 @@ export declare function resumeContextUnavailableFromError(err: unknown): ResumeC
|
|
|
153
153
|
* 🔴 **只凭码不推断原因**(异源对抗复审实抓):server 在「上下文被回收」与「上下文读取失败(如存储故障)」两种情况下
|
|
154
154
|
* 都返回本码,wire 上没有判别位 ⇒ 第一句只说「当前没有可用的上下文来继续它」,不说「等得太久 / 已被回收」。
|
|
155
155
|
* 🔴 **调大保留时长救不回这一次**(上下文已删就是删了),它只能避免以后再发生 ⇒ 第三句按 `runId` 在场与否给恢复指引,
|
|
156
|
-
*
|
|
157
|
-
*
|
|
156
|
+
* 保留时长只作预防句。零内部车道词;{@link ResumeContextUnavailableDetail.staleAfterSec} 在场时第三句**带原数**
|
|
157
|
+
* (0.72.9 CC-45:此前留给端渲而端没渲 ⇒ 有数必渲),缺席就不渲、不编。
|
|
158
158
|
*/
|
|
159
159
|
export declare function resumeContextUnavailableContent(d: ResumeContextUnavailableDetail): string;
|
|
@@ -185,13 +185,19 @@ export function resumeContextUnavailableFromError(err) {
|
|
|
185
185
|
* 🔴 **只凭码不推断原因**(异源对抗复审实抓):server 在「上下文被回收」与「上下文读取失败(如存储故障)」两种情况下
|
|
186
186
|
* 都返回本码,wire 上没有判别位 ⇒ 第一句只说「当前没有可用的上下文来继续它」,不说「等得太久 / 已被回收」。
|
|
187
187
|
* 🔴 **调大保留时长救不回这一次**(上下文已删就是删了),它只能避免以后再发生 ⇒ 第三句按 `runId` 在场与否给恢复指引,
|
|
188
|
-
*
|
|
189
|
-
*
|
|
188
|
+
* 保留时长只作预防句。零内部车道词;{@link ResumeContextUnavailableDetail.staleAfterSec} 在场时第三句**带原数**
|
|
189
|
+
* (0.72.9 CC-45:此前留给端渲而端没渲 ⇒ 有数必渲),缺席就不渲、不编。
|
|
190
190
|
*/
|
|
191
191
|
export function resumeContextUnavailableContent(d) {
|
|
192
|
-
|
|
192
|
+
// 0.72.9 CC-45(cli L-383 / test [7516] N-G10):**有数必渲、无数不编**。0.72.1 曾把秒数留给端「单独渲」而端没渲 ⇒ 补充句永无
|
|
193
|
+
// 保留秒数;归层改为本句直接带原数(整数秒,读口已窄读),缺席时本句逐字同 0.72.1。
|
|
194
|
+
// 🔴 措辞铸点是最后一关(opus 替补复审 [low]):公面函数入参可被手工构造,`0` / 非整数 / 巨数与读口同一判据(整数 ∧ ≥1)才渲,
|
|
195
|
+
// 否则不渲 —— 绝不渲「0 秒」这种看起来像答案的空壳。
|
|
196
|
+
const sec = d.staleAfterSec;
|
|
197
|
+
const retention = typeof sec === 'number' && Number.isSafeInteger(sec) && sec >= 1 ? `(本部署的会话保留时长为 ${sec} 秒)` : '';
|
|
198
|
+
const third = (d.runId !== undefined
|
|
193
199
|
? '错误里带了这条运行的 id,请用它自己恢复这条运行再决;调大会话保留时长只能避免以后再发生,救不回这一次'
|
|
194
|
-
: '错误里没有带运行 id,请回到运行列表找到这条运行自己恢复它再决;调大会话保留时长只能避免以后再发生,救不回这一次';
|
|
200
|
+
: '错误里没有带运行 id,请回到运行列表找到这条运行自己恢复它再决;调大会话保留时长只能避免以后再发生,救不回这一次') + retention;
|
|
195
201
|
return [
|
|
196
202
|
'这个批准无处投递:发起这次运行的会话当前没有可用的上下文来继续它',
|
|
197
203
|
'什么都没有被决定,你的决定没有被消费,这张卡仍在等待',
|
|
@@ -29,12 +29,20 @@ export interface MemoryStatusClientLike {
|
|
|
29
29
|
} & SessionMemoryStatus>;
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* 读数体 = sdk 五键 + server 7.85.0 S-403 新键 `autoConsolidationArmed`(0.72.9 CC-39②;core #863 W1 唯一读法
|
|
34
|
+
* `autoRunOnRecommendationEffective` 组,与 manifest `autoConsolidation` 段在场性同源)。sdk 9.6.0 尚未声明 ⇒ 本包按结构读;
|
|
35
|
+
* 缺席 = 老 server(<7.85.0)**判不了**,不折 false —— 读法见 {@link readAutoConsolidationArmed}。
|
|
36
|
+
*/
|
|
37
|
+
export type MemoryStatusFacts = SessionMemoryStatus & {
|
|
38
|
+
readonly autoConsolidationArmed?: boolean;
|
|
39
|
+
};
|
|
32
40
|
export type MemoryStatusVerdict =
|
|
33
41
|
/** 200:读到了。`facts` 的**每一键**都按 wire 原样(合形才在场),缺席语义见 {@link readCaptureOptOut} 等。 */
|
|
34
42
|
{
|
|
35
43
|
readonly kind: 'ok';
|
|
36
44
|
readonly sessionId: string;
|
|
37
|
-
readonly facts:
|
|
45
|
+
readonly facts: MemoryStatusFacts;
|
|
38
46
|
}
|
|
39
47
|
/** 本部署**没有这个面** —— 端应当**别提供这个入口**(不是报错,是诚实的能力缺席)。 */
|
|
40
48
|
| {
|
|
@@ -136,3 +144,9 @@ export declare function readLastCapture(s: SessionMemoryStatus): LastCaptureRead
|
|
|
136
144
|
export declare function readSessionMemoryStatus(client: MemoryStatusClientLike, sessionId: string, opts?: {
|
|
137
145
|
signal?: AbortSignal;
|
|
138
146
|
}): Promise<MemoryStatusVerdict>;
|
|
147
|
+
/**
|
|
148
|
+
* 「这台部署的记忆自动整理武装了没有」:`armed` / `unarmed` / `indeterminate`。
|
|
149
|
+
* 🔴 缺席 = 老 server(<7.85.0)**判不了**,不折 `unarmed`;坏形同样 `indeterminate`(不抛)。三个词对运维是三条不同的下一步
|
|
150
|
+
* (与 manifest `autoConsolidation` 段「缺席 = indeterminate」同律;server 用 core 唯一读法组这一位,端别按席自判)。
|
|
151
|
+
*/
|
|
152
|
+
export declare function readAutoConsolidationArmed(facts: MemoryStatusFacts | null | undefined): 'armed' | 'unarmed' | 'indeterminate';
|
|
@@ -172,6 +172,8 @@ export async function readSessionMemoryStatus(client, sessionId, opts) {
|
|
|
172
172
|
? { optOutSource: o.optOutSource }
|
|
173
173
|
: {}),
|
|
174
174
|
...(typeof o.lastCaptureAt === 'number' ? { lastCaptureAt: o.lastCaptureAt } : {}),
|
|
175
|
+
// 0.72.9 CC-39②(server 7.85.0 S-403):布尔恒在场;非布尔 / 老 server 缺席 ⇒ 键不在场(不铸 false)。
|
|
176
|
+
...(typeof o.autoConsolidationArmed === 'boolean' ? { autoConsolidationArmed: o.autoConsolidationArmed } : {}),
|
|
175
177
|
},
|
|
176
178
|
};
|
|
177
179
|
}
|
|
@@ -179,3 +181,19 @@ export async function readSessionMemoryStatus(client, sessionId, opts) {
|
|
|
179
181
|
return classifyMemoryStatusFailure(e);
|
|
180
182
|
}
|
|
181
183
|
}
|
|
184
|
+
// ── ④ autoConsolidationArmed 三态(0.72.9 CC-39②)──────────────────────────────────────────
|
|
185
|
+
/**
|
|
186
|
+
* 「这台部署的记忆自动整理武装了没有」:`armed` / `unarmed` / `indeterminate`。
|
|
187
|
+
* 🔴 缺席 = 老 server(<7.85.0)**判不了**,不折 `unarmed`;坏形同样 `indeterminate`(不抛)。三个词对运维是三条不同的下一步
|
|
188
|
+
* (与 manifest `autoConsolidation` 段「缺席 = indeterminate」同律;server 用 core 唯一读法组这一位,端别按席自判)。
|
|
189
|
+
*/
|
|
190
|
+
export function readAutoConsolidationArmed(facts) {
|
|
191
|
+
if (typeof facts !== 'object' || facts === null)
|
|
192
|
+
return 'indeterminate';
|
|
193
|
+
const v = facts.autoConsolidationArmed;
|
|
194
|
+
if (v === true)
|
|
195
|
+
return 'armed';
|
|
196
|
+
if (v === false)
|
|
197
|
+
return 'unarmed';
|
|
198
|
+
return 'indeterminate';
|
|
199
|
+
}
|