@sema-agent/client-core 0.18.0 → 0.20.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/README.md +1 -1
- package/dist/adapt/arms.js +53 -11
- package/dist/adapt/ids.d.ts +1 -1
- package/dist/adapt/ids.js +11 -2
- package/dist/adapt/textStream.js +2 -2
- package/dist/adapt/toolCards.js +1 -1
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +2 -0
- package/dist/adapter/downstream/eventToSdkMessage.js +58 -0
- package/dist/attachmentsWireCaps.d.ts +73 -10
- package/dist/attachmentsWireCaps.js +188 -14
- package/dist/controlRouter.d.ts +45 -8
- package/dist/controlRouter.js +53 -21
- package/dist/hitl/askGateWire.d.ts +1 -1
- package/dist/hitl/askGateWire.js +1 -1
- package/dist/hitl/frameRouter.js +3 -1
- package/dist/hitl/hitlBridge.d.ts +19 -2
- package/dist/hitl/hitlBridge.js +37 -0
- package/dist/hitl/hitlHostSurface.d.ts +27 -0
- package/dist/hitl/hitlHostSurface.js +50 -10
- package/dist/hitl/parkResolver.d.ts +8 -1
- package/dist/hitl/parkResolver.js +26 -1
- package/dist/hitl/toolApprovalWire.d.ts +41 -4
- package/dist/hitl/toolApprovalWire.js +67 -5
- package/dist/seam.d.ts +44 -1
- package/dist/seam.js +6 -0
- package/dist/seatContract.d.ts +68 -8
- package/dist/seatContract.js +57 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
23
23
|
|
|
24
24
|
## Scope
|
|
25
25
|
|
|
26
|
-
**Version:** 0.
|
|
26
|
+
**Version:** 0.20.0
|
|
27
27
|
|
|
28
28
|
- **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
|
|
29
29
|
B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
|
package/dist/adapt/arms.js
CHANGED
|
@@ -63,7 +63,7 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
63
63
|
uuid: idOf(m),
|
|
64
64
|
session_id: ctx.sessionId,
|
|
65
65
|
parent_tool_use_id: null,
|
|
66
|
-
});
|
|
66
|
+
}, ctx.now());
|
|
67
67
|
if (decision === 'reject' || decision === 'cancel') {
|
|
68
68
|
// S1:一个结果关闭该 model response。**yield 之前**复位 —— 矩阵 §2.2 定性为
|
|
69
69
|
// 「形不一致的技术债、不是缺陷」(L1 turn 级、下一句就 return、早退时无读者),
|
|
@@ -84,7 +84,7 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
84
84
|
},
|
|
85
85
|
uuid: idOf(m, decision),
|
|
86
86
|
session_id: ctx.sessionId,
|
|
87
|
-
});
|
|
87
|
+
}, ctx.now());
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
if (decision === 'error') {
|
|
@@ -104,7 +104,7 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
104
104
|
},
|
|
105
105
|
uuid: idOf(m, 'error'),
|
|
106
106
|
session_id: ctx.sessionId,
|
|
107
|
-
});
|
|
107
|
+
}, ctx.now());
|
|
108
108
|
return;
|
|
109
109
|
}
|
|
110
110
|
// ⟨帧序耦合 1/7 + 3/7⟩ 开卡:后到的 task_progress 才猜得到父、后到的 tool_end 才关得掉卡。
|
|
@@ -130,7 +130,7 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
130
130
|
uuid: idOf(m),
|
|
131
131
|
session_id: ctx.sessionId,
|
|
132
132
|
parent_tool_use_id: null,
|
|
133
|
-
});
|
|
133
|
+
}, ctx.now());
|
|
134
134
|
text.markEmittedText();
|
|
135
135
|
}
|
|
136
136
|
const thinkingBlock = blocks.find(b => b?.type === 'thinking');
|
|
@@ -141,7 +141,7 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
141
141
|
uuid: idOf(m, 'durable-thinking'),
|
|
142
142
|
session_id: ctx.sessionId,
|
|
143
143
|
parent_tool_use_id: null,
|
|
144
|
-
});
|
|
144
|
+
}, ctx.now());
|
|
145
145
|
}
|
|
146
146
|
};
|
|
147
147
|
// ══ M5 纯投影臂(零适配器状态,类型即证明)══════════════════════════════════════════════════════
|
|
@@ -149,10 +149,10 @@ const assistantArm = function* (m, { ctx, idOf, text, cards, inst }) {
|
|
|
149
149
|
const userArm = function* (m, { ctx, idOf }) {
|
|
150
150
|
if (m.message === undefined)
|
|
151
151
|
return;
|
|
152
|
-
yield transcript({ ...m, uuid: idOf(m), session_id: ctx.sessionId });
|
|
152
|
+
yield transcript({ ...m, uuid: idOf(m), session_id: ctx.sessionId }, ctx.now());
|
|
153
153
|
};
|
|
154
|
-
const systemArm = function* (m, { idOf }) {
|
|
155
|
-
yield transcript(m);
|
|
154
|
+
const systemArm = function* (m, { ctx, idOf }) {
|
|
155
|
+
yield transcript(m, ctx.now());
|
|
156
156
|
// core 1.219 attachWorkingFiles:compact_boundary 可携 attachedFiles → CC 的
|
|
157
157
|
// "Referenced file {path}" 行(post-compact attachment,零新 UI)。
|
|
158
158
|
const attachedFiles = m.attachedFiles;
|
|
@@ -212,6 +212,47 @@ const workspaceChangedArm = function* (m, { idOf }) {
|
|
|
212
212
|
attachment: { type: 'workspace_changed', cwd },
|
|
213
213
|
});
|
|
214
214
|
};
|
|
215
|
+
/**
|
|
216
|
+
* FIX⑦(2026-08-07,黑板 [3017]/[3020])—— `human_input` 生命周期账本帧。
|
|
217
|
+
*
|
|
218
|
+
* 姿势抄同族的 `steering_injected` / `workspace_changed`(都是「引擎侧发生了一件事,壳记一笔」的
|
|
219
|
+
* 非持久帧,归 chrome),但**不走 `attachment` 臂**:那三条 attachment 是要在转录流里渲出一行给
|
|
220
|
+
* 用户看的东西,而本帧**不带正文** —— 没有可渲的内容。硬造一行「有人输入了什么」的空 attachment
|
|
221
|
+
* 就是在编内容。所以它有自己的 chrome 臂,宿主义务是「可选地记进审计/时间线」。
|
|
222
|
+
* 子流帧(带 parentToolCallId)同族让位:不上 leader 面。
|
|
223
|
+
*/
|
|
224
|
+
const humanInputArm = function* (m) {
|
|
225
|
+
if (typeof m.parentToolCallId === 'string')
|
|
226
|
+
return;
|
|
227
|
+
const carrier = typeof m.carrier === 'string' ? m.carrier : '';
|
|
228
|
+
if (carrier.length === 0)
|
|
229
|
+
return; // 无载体的账本行渲出去只是噪声(投影层已判 malformed,这里是第二道)
|
|
230
|
+
const str = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
|
|
231
|
+
const rawActor = typeof m.actor === 'object' && m.actor !== null && !Array.isArray(m.actor)
|
|
232
|
+
? m.actor
|
|
233
|
+
: undefined;
|
|
234
|
+
const actor = rawActor === undefined
|
|
235
|
+
? undefined
|
|
236
|
+
: {
|
|
237
|
+
...(str(rawActor.id) !== undefined ? { id: str(rawActor.id) } : {}),
|
|
238
|
+
...(typeof rawActor.hostAsserted === 'boolean' ? { hostAsserted: rawActor.hostAsserted } : {}),
|
|
239
|
+
...(str(rawActor.issuer) !== undefined ? { issuer: str(rawActor.issuer) } : {}),
|
|
240
|
+
};
|
|
241
|
+
yield chrome({
|
|
242
|
+
kind: 'human_input',
|
|
243
|
+
laneProof: MAIN,
|
|
244
|
+
carrier,
|
|
245
|
+
...(str(m.inputId) !== undefined ? { inputId: str(m.inputId) } : {}),
|
|
246
|
+
...(typeof m.sessionSeq === 'number' && Number.isFinite(m.sessionSeq) ? { sessionSeq: m.sessionSeq } : {}),
|
|
247
|
+
...(str(m.source) !== undefined ? { source: str(m.source) } : {}),
|
|
248
|
+
...(str(m.delivery) !== undefined ? { delivery: str(m.delivery) } : {}),
|
|
249
|
+
...(str(m.issuer) !== undefined ? { issuer: str(m.issuer) } : {}),
|
|
250
|
+
...(actor !== undefined && Object.keys(actor).length > 0 ? { actor } : {}),
|
|
251
|
+
...(str(m.entryId) !== undefined ? { entryId: str(m.entryId) } : {}),
|
|
252
|
+
// 宿主幂等落账的键(codex 复审二轮):重放同一条账本帧必须得到同一个 eventId。
|
|
253
|
+
...(str(m.eventId) !== undefined ? { eventId: str(m.eventId) } : {}),
|
|
254
|
+
});
|
|
255
|
+
};
|
|
215
256
|
const promptSuggestionsArm = function* (m) {
|
|
216
257
|
// T57 第五处断闸(#47 矩阵 #5 同族):子流(parentToolCallId 标)的建议绝不骑主 composer。
|
|
217
258
|
if (typeof m.parentToolCallId === 'string')
|
|
@@ -271,7 +312,7 @@ const toolEndResultArm = function* (m, { ctx, idOf, cards, panel }) {
|
|
|
271
312
|
timestamp: new Date(ctx.now()).toISOString(),
|
|
272
313
|
uuid: idOf(m, 'memory-saved'),
|
|
273
314
|
isMeta: false,
|
|
274
|
-
});
|
|
315
|
+
}, ctx.now());
|
|
275
316
|
}
|
|
276
317
|
}
|
|
277
318
|
// ── E 层 ②:core 1.217 task 族 structured —— 权威全量态精确同步 ctrl+t 面板(引擎真实 id)。
|
|
@@ -467,7 +508,7 @@ const resultArm = function* (m, { ctx, idOf, text, cards, panel, flags }) {
|
|
|
467
508
|
uuid: idOf(m, 'terminal-text'),
|
|
468
509
|
session_id: ctx.sessionId,
|
|
469
510
|
parent_tool_use_id: null,
|
|
470
|
-
});
|
|
511
|
+
}, ctx.now());
|
|
471
512
|
text.markEmittedText();
|
|
472
513
|
}
|
|
473
514
|
}
|
|
@@ -556,7 +597,7 @@ const taskNotificationArm = function* (m, { ctx, idOf, panel, inst }) {
|
|
|
556
597
|
message: { role: 'user', content: [{ type: 'text', text: renderTaskNotificationXml(fields) }] },
|
|
557
598
|
uuid: idOf(m),
|
|
558
599
|
session_id: ctx.sessionId,
|
|
559
|
-
});
|
|
600
|
+
}, ctx.now());
|
|
560
601
|
};
|
|
561
602
|
/**
|
|
562
603
|
* ⚠️ 本臂声明成 `ProjectionArmFn`(碰不到五个适配器状态模块),但它**有副作用** ——
|
|
@@ -632,6 +673,7 @@ export const ARMS = new Map([
|
|
|
632
673
|
['diagnostics', diagnosticsArm],
|
|
633
674
|
['steering_injected', steeringInjectedArm],
|
|
634
675
|
['workspace_changed', workspaceChangedArm],
|
|
676
|
+
['human_input', humanInputArm],
|
|
635
677
|
['prompt_suggestions', promptSuggestionsArm],
|
|
636
678
|
['retry_status', retryStatusArm],
|
|
637
679
|
]);
|
package/dist/adapt/ids.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export declare const chrome: (event: ChromeEvent) => AdapterOutput;
|
|
|
13
13
|
* 到 `SDKMessage` 的那一步仍是 cast:CC 词汇的字面形与 SDK 类型有已登记的分歧(DIVERGENCE-2/9),
|
|
14
14
|
* 这条 cast 是**有意**的边界,不是偷懒。
|
|
15
15
|
*/
|
|
16
|
-
export declare const transcript: (message: Frame) => AdapterOutput;
|
|
16
|
+
export declare const transcript: (message: Frame, nowMs: number) => AdapterOutput;
|
|
17
17
|
/**
|
|
18
18
|
* [1653]:稳定键优先序 = 帧 id > seq > toolCallId,全缺才 ctx.uuid()。现役 wire 的稳定键落在
|
|
19
19
|
* `uuid` 字段(§E2 core 铸的 eventId / stamp 铸的帧 id),故映到 `id` 位喂进去(见 SEAM-GAP-1)。
|
package/dist/adapt/ids.js
CHANGED
|
@@ -9,9 +9,18 @@ export const chrome = (event) => ({ plane: 'chrome', event });
|
|
|
9
9
|
* 到 `SDKMessage` 的那一步仍是 cast:CC 词汇的字面形与 SDK 类型有已登记的分歧(DIVERGENCE-2/9),
|
|
10
10
|
* 这条 cast 是**有意**的边界,不是偷懒。
|
|
11
11
|
*/
|
|
12
|
-
export const transcript = (message) => ({
|
|
12
|
+
export const transcript = (message, nowMs) => ({
|
|
13
13
|
plane: 'transcript',
|
|
14
|
-
|
|
14
|
+
// 到达时戳补齐(#185-A1 正位收编,2026-08-08;此前是壳 upstreamBridge.stampTranscriptArrival
|
|
15
|
+
// 的消费端补偿,注释明记「长线正位在 client-core 的 adapt 臂」):transcript 消息不变式=恒带
|
|
16
|
+
// timestamp——CC 在 content_block_stop 取 wall-clock,而 transcript 帧正是块收尾时到达,到达
|
|
17
|
+
// 时刻本身就是同一个量。只补缺席不覆盖(wire 哪天带了就用 wire 的);壳侧补偿臂保留为双保险
|
|
18
|
+
// (its ③:不覆盖已有值 ⇒ 本补齐生效后壳臂自然零触发)。
|
|
19
|
+
// [C114] 请托(web jsdom-adapt-parity 门 INCONCLUSIVE 实抓):时钟**必经 ctx.now()**(调用方
|
|
20
|
+
// 注入 nowMs)——裸 `new Date()` 违 DIVERGENCE-7 可重放纪律,确定性双跑逐字节对拍当场失判别力。
|
|
21
|
+
message: (typeof message.timestamp === 'string'
|
|
22
|
+
? message
|
|
23
|
+
: { ...message, timestamp: new Date(nowMs).toISOString() }),
|
|
15
24
|
});
|
|
16
25
|
/**
|
|
17
26
|
* [1653]:稳定键优先序 = 帧 id > seq > toolCallId,全缺才 ctx.uuid()。现役 wire 的稳定键落在
|
package/dist/adapt/textStream.js
CHANGED
|
@@ -35,7 +35,7 @@ export function createTextStream(ctx, idOf) {
|
|
|
35
35
|
thinkingBlockOpen = false;
|
|
36
36
|
// P2d:活体 "∴ Thinking…" 行在 committed 块落地的**同一刻**收掉(先收行再上消息,cli 同序)
|
|
37
37
|
yield chrome({ kind: 'thinking_activity', laneProof: MAIN, active: false });
|
|
38
|
-
yield transcript(msg);
|
|
38
|
+
yield transcript(msg, ctx.now());
|
|
39
39
|
}
|
|
40
40
|
/** 提交累积的文本段(cli takeAnswerSegment:#27 段序铁律——工具卡前/turn 末各 flush 一次)。 */
|
|
41
41
|
function* takeAnswerSegment() {
|
|
@@ -53,7 +53,7 @@ export function createTextStream(ctx, idOf) {
|
|
|
53
53
|
segmentAnchor = null;
|
|
54
54
|
emittedAssistantText = true;
|
|
55
55
|
textBlockOpen = false;
|
|
56
|
-
yield transcript(msg);
|
|
56
|
+
yield transcript(msg, ctx.now());
|
|
57
57
|
}
|
|
58
58
|
/** 合并后的活体增量下泄(cli drainLive:思考先于文本 = 持久臂序)。 */
|
|
59
59
|
function* drainLive() {
|
package/dist/adapt/toolCards.js
CHANGED
|
@@ -68,7 +68,7 @@ export function createToolCardLedger(ctx, idOf) {
|
|
|
68
68
|
toolUseResult: rich !== null ? rich.toolUseResult : (body.toolUseResult ?? body.content),
|
|
69
69
|
// T7:wire 没带正文的诚实标记 —— 在场 = 这张卡没有真实执行结果(UI 显示决策留端)。
|
|
70
70
|
...(body.degraded !== undefined ? { _sema_degraded: body.degraded } : {}),
|
|
71
|
-
});
|
|
71
|
+
}, ctx.now());
|
|
72
72
|
}
|
|
73
73
|
/** 关一张卡的**唯一**实现:出栈 + S1 复位 + 铸卡,前两步恒在第一个 yield 之前。 */
|
|
74
74
|
function* closeCard(p, end) {
|
package/dist/adapt.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export interface AdapterLedgerState {
|
|
|
50
50
|
/** 本批覆盖清单——差分守卫按它区分「已覆盖臂必须逐字段等价」与「已声明未覆盖臂」。 */
|
|
51
51
|
export declare const ADAPTER_COVERAGE: {
|
|
52
52
|
/** 已落码的帧臂。 */
|
|
53
|
-
readonly frames: readonly ["assistant", "user", "stream_event", "turn_usage", "result", "system", "task_notification", "workflow_complete", "diagnostics", "steering_injected", "workspace_changed", "prompt_suggestions", "retry_status", "task_progress", "tool_end_result(label 补位 + 关卡 settle + response-id 复位 + 开卡台账出栈 + 经 cards.close 铸 tool_result)"];
|
|
53
|
+
readonly frames: readonly ["assistant", "user", "stream_event", "turn_usage", "result", "system", "task_notification", "workflow_complete", "diagnostics", "steering_injected", "workspace_changed", "human_input", "prompt_suggestions", "retry_status", "task_progress", "tool_end_result(label 补位 + 关卡 settle + response-id 复位 + 开卡台账出栈 + 经 cards.close 铸 tool_result)"];
|
|
54
54
|
/** 已落码臂产出的 transcript 消息类目(差分守卫的比对域)。 */
|
|
55
55
|
readonly transcriptKinds: readonly ["assistant_text", "assistant_thinking", "assistant_tool_use", "user_tool_result_decision", "user_task_notification", "system_passthrough"];
|
|
56
56
|
/** B3(0.5.0)新落码的臂/半场 —— 从 todo 移过来的,别再在 todo 里留同名条目。 */
|
package/dist/adapt.js
CHANGED
|
@@ -31,6 +31,8 @@ export const ADAPTER_COVERAGE = {
|
|
|
31
31
|
'diagnostics',
|
|
32
32
|
'steering_injected',
|
|
33
33
|
'workspace_changed',
|
|
34
|
+
// FIX⑦(2026-08-07):`human_input` 账本帧 —— 投 chrome 记账,**不铸 transcript 行**(不带正文)。
|
|
35
|
+
'human_input',
|
|
34
36
|
'prompt_suggestions',
|
|
35
37
|
'retry_status',
|
|
36
38
|
'task_progress',
|
|
@@ -63,6 +63,17 @@ function assertNeverArm(_ev) {
|
|
|
63
63
|
* 旧写法 `if (msg)` 在新返回型上恒真(对象永远 truthy),所以这是**必须点名**的一类改动。
|
|
64
64
|
*/
|
|
65
65
|
export function eventToSdkMessage(ev, ctx) {
|
|
66
|
+
// ── raw 预分派 —— `human_input`(core 5.14.0 design/171 / server 7.4.0 SSE,黑板 [3017]/[3020])──
|
|
67
|
+
// 🔴 为什么必须在 switch **之前**:这条臂**还没进** SDK 6.3.0 的 `AgentEvent` union
|
|
68
|
+
// (`dist/events.d.ts` 实扫:33 条臂里没有它),`case 'human_input'` 在 `ev.type` 上是编译错。
|
|
69
|
+
// 在 union 里的那天就把它搬进 switch —— 与 `workflow_complete` 当年那条 raw 预分派臂同样的
|
|
70
|
+
// 退役条件(那一条已于 sdk 6.2.0 兑现退役,先例在本文件 `case 'workflow_complete'` 头注)。
|
|
71
|
+
// 处置:server 侧对本帧的投影明写「**本帧不带正文**(core 的臂里就没有 text 字段),所以没有
|
|
72
|
+
// 内容脱敏面」。因此它是**生命周期账本帧**,不是转录物 —— 投成中性内部臂交给 chrome 面记账,
|
|
73
|
+
// 绝不铸 transcript 行(铸一行就是替引擎编一条它没说过的话)。
|
|
74
|
+
if (ev.type === 'human_input') {
|
|
75
|
+
return humanInputProjection(ev, ctx);
|
|
76
|
+
}
|
|
66
77
|
switch (ev.type) {
|
|
67
78
|
// service 1.75 — `workflow_complete`: an out-of-band background-workflow completion push, ridden onto
|
|
68
79
|
// the session's next stream open (BEFORE the run's own events). Projects to a NEUTRAL internal arm;
|
|
@@ -404,6 +415,53 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
404
415
|
return dropped('unknown_arm', String(ev.type ?? 'unknown'));
|
|
405
416
|
}
|
|
406
417
|
}
|
|
418
|
+
/**
|
|
419
|
+
* `human_input` → 中性内部账本臂(见 `eventToSdkMessage` 顶部的 raw 预分派注释)。
|
|
420
|
+
*
|
|
421
|
+
* 白名单逐键挑(禁 spread),与 server 侧 `humanInputEventData` 的投影面同源:
|
|
422
|
+
* · `inputId` / `sessionSeq` / `carrier` / `source` / `delivery` / `entryId` = 引擎铸的标识与枚举
|
|
423
|
+
* 值,非用户内容 ⇒ verbatim;
|
|
424
|
+
* · `issuer` / `actor.id` / `actor.issuer` = 宿主从入口凭据派生的**身份串**(自由文本形)——
|
|
425
|
+
* server 已 redact 过再上帧,本层只透传,不再二次加工(壳侧 UNTRUSTED-for-display);
|
|
426
|
+
* · `actor.hostAsserted` = 消费端**唯一**能判「这个署名可信吗」的位(core 的 `[from …]` 渲染靠它
|
|
427
|
+
* 决定加不加 `(unverified)`)—— 剥掉它比剥掉 id 更坏:剩下一个看不出真假的名字。
|
|
428
|
+
* · `principal` = **刻意不投影**(与 ToolApprovalFrame 的 principal 同裁:帧本就经 owner-scoped
|
|
429
|
+
* 流投递,租户身份不必由帧自述)。
|
|
430
|
+
* `carrier` 缺席 ⇒ `malformed`:一条既没有载体也没有正文的账本行,渲出去只是一行「有人输入了什么」
|
|
431
|
+
* 的噪声,而 dropped 至少会留痕。
|
|
432
|
+
*/
|
|
433
|
+
function humanInputProjection(ev, ctx) {
|
|
434
|
+
const str = (v) => (typeof v === 'string' && v.length > 0 ? v : undefined);
|
|
435
|
+
const carrier = str(ev.carrier);
|
|
436
|
+
if (carrier === undefined)
|
|
437
|
+
return dropped('malformed', 'human_input');
|
|
438
|
+
const rawActor = typeof ev.actor === 'object' && ev.actor !== null && !Array.isArray(ev.actor)
|
|
439
|
+
? ev.actor
|
|
440
|
+
: undefined;
|
|
441
|
+
const actor = rawActor === undefined
|
|
442
|
+
? undefined
|
|
443
|
+
: {
|
|
444
|
+
...(str(rawActor.id) !== undefined ? { id: str(rawActor.id) } : {}),
|
|
445
|
+
...(typeof rawActor.hostAsserted === 'boolean' ? { hostAsserted: rawActor.hostAsserted } : {}),
|
|
446
|
+
...(str(rawActor.issuer) !== undefined ? { issuer: str(rawActor.issuer) } : {}),
|
|
447
|
+
};
|
|
448
|
+
return projected(stamp(ctx, armBody({
|
|
449
|
+
type: 'human_input',
|
|
450
|
+
carrier,
|
|
451
|
+
...(str(ev.inputId) !== undefined ? { inputId: str(ev.inputId) } : {}),
|
|
452
|
+
...(typeof ev.sessionSeq === 'number' && Number.isFinite(ev.sessionSeq) ? { sessionSeq: ev.sessionSeq } : {}),
|
|
453
|
+
...(str(ev.source) !== undefined ? { source: str(ev.source) } : {}),
|
|
454
|
+
...(str(ev.delivery) !== undefined ? { delivery: str(ev.delivery) } : {}),
|
|
455
|
+
...(str(ev.issuer) !== undefined ? { issuer: str(ev.issuer) } : {}),
|
|
456
|
+
...(actor !== undefined && Object.keys(actor).length > 0 ? { actor } : {}),
|
|
457
|
+
...(str(ev.entryId) !== undefined ? { entryId: str(ev.entryId) } : {}),
|
|
458
|
+
// 🔴 `eventId` 必须带到底(codex 复审二轮):durable 腿按 `Last-Event-ID` 续读会**重放**同一条
|
|
459
|
+
// 账本帧,而本地行 id 是现铸的 —— 审计/时间线消费者若没有引擎铸的稳定键,一次人工注入会被
|
|
460
|
+
// 记成多次。`inputId`/`entryId` 在公开形上都是可选的,`eventId` 是那个恒在的身份位。
|
|
461
|
+
...(str(ev.eventId) !== undefined ? { eventId: str(ev.eventId) } : {}),
|
|
462
|
+
...(str(ev.parentToolCallId) !== undefined ? { parentToolCallId: str(ev.parentToolCallId) } : {}),
|
|
463
|
+
})));
|
|
464
|
+
}
|
|
407
465
|
/**
|
|
408
466
|
* CS-7 §2.7 — turn_end usage → CC `ModelUsage` (pinned name mapping;
|
|
409
467
|
* costMicroUsd/1e6 → costUSD). Surfaced separately because the slice has no
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* src/sema/attachmentsWireCaps.ts — design/133 + G1 turn-boundary attachments WIRE pure-projection:
|
|
6
6
|
* the shell's intent → the top-level `TaskRequest.attachments` object the service normalizes
|
|
7
|
-
* (spec-fields.ts normalizeAttachments
|
|
8
|
-
* (all producers opt-in engine-side; core 1.253).
|
|
7
|
+
* (spec-fields.ts normalizeAttachments) → core `TaskSpec.attachments`.
|
|
9
8
|
*
|
|
10
9
|
* DEFAULT ON for the TOC live path ([487]② — CC parity): CC's own /compact announces the still
|
|
11
10
|
* pending/running background tasks after compaction and notices newly-available deferred tools at
|
|
@@ -19,30 +18,94 @@
|
|
|
19
18
|
* only; interactive deployments stay byte-identical) is respected until a behavior leg says
|
|
20
19
|
* otherwise.
|
|
21
20
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* ══ 🔴 FIX⑤(2026-08-07)—— 这个字段下面其实躺着**两族契约**,此前只实现了一族 ═══════════════
|
|
22
|
+
*
|
|
23
|
+
* 契约真源 = `@sema-agent/sdk` `types.d.ts` 的 `TaskRequest.attachments`(6.3.0 起 10 键)。
|
|
24
|
+
* 两族的「关」是两种**不同**的动作,混为一谈就必然有一族关不掉:
|
|
25
|
+
*
|
|
26
|
+
* ① **literal-true 族**(7 员:todoReminder / changedFiles / planModeReminder / budgetUsd /
|
|
27
|
+
* backgroundTasks / toolsDelta / mcpInstructions,外加取值型的 todoReminderMode):
|
|
28
|
+
* 引擎侧默认**关**,键在场即开。板 [479] ask-2 的原话是「off = DELETE the key, never send
|
|
29
|
+
* false」—— 对这一族,发 `false` 是违约(核心根本没有「显式关」这个语义位),所以本模块对
|
|
30
|
+
* 它们**永不产出 false**:OFF ⇒ 键不出现。
|
|
31
|
+
*
|
|
32
|
+
* ② **explicit-false 族**(2 员:agentListing / skillsListing):
|
|
33
|
+
* 它们在 core 里是 **DEFAULT-ON**,SDK 头注写死「`agentListing`/`skillsListing` 是 core
|
|
34
|
+
* DEFAULT-ON —— **explicit false 才关**(1.254 起 false 真透传;更老 server 静默丢)」。
|
|
35
|
+
* 于是「删键」在这一族上的含义是**保持开着**,而不是关掉。旧的 `AttachmentsSpec` 把全部键
|
|
36
|
+
* 钉成 `?: true`,literal 类型层面就表达不出 `false` ⇒ 这两个 DEFAULT-ON 的附件**没有任何
|
|
37
|
+
* 路径可以关闭**(不是「难关」,是类型层封死)。本批把这两键的类型放宽成 `boolean` 并给出
|
|
38
|
+
* 产出 `false` 的路径。
|
|
39
|
+
*
|
|
40
|
+
* ── env 面(`SEMA_ATTACHMENTS`)的取值语法,以及为什么选这一种 ────────────────────────────────
|
|
41
|
+
* 目标是「**默认行为逐字节不变** + 有路可关 DEFAULT-ON 两键」,所以选了**在既有值上加逗号 token
|
|
42
|
+
* 列表**,而不是新开一个 env:
|
|
43
|
+
* · 新开一个 env(如 `SEMA_ATTACHMENTS_OFF`)会让「附件面怎么配」有两个入口,而两个入口之间的
|
|
44
|
+
* 优先级又要再定义一次 —— 这类第二真源正是本仓反复登记的病族;
|
|
45
|
+
* · 逗号 token 列表对**存量取值零影响**:`unset` / `full` / `all` / off 拼法四种老写法解析结果
|
|
46
|
+
* 逐字节不变(FIX7 段 ⑤ 有正面钉),新语法只在用户真写了逗号 token 时才被触发。
|
|
47
|
+
* 语法(逗号分隔,大小写不敏感,允许空白):
|
|
48
|
+
* · `off|0|false|no|none`(整值) ⇒ **真·全关**:literal-true 族一个不发(那一族的 off 就是
|
|
49
|
+
* 删键)+ DEFAULT-ON 两键发 `false`。
|
|
50
|
+
* 🔴 codex 复审 [medium](2026-08-07)判的真病:这一支此前直接 `return undefined`(不发字段),
|
|
51
|
+
* 而「不发字段」对 DEFAULT-ON 两键的含义**恰恰是保持开着** —— 一个叫 off 的总开关关不掉十件里
|
|
52
|
+
* 的两件,且用户没有任何办法察觉。旧行为在 <1.254 的 server 上与新行为**逐字节等价**(那些
|
|
53
|
+
* server 静默丢 false),在 ≥1.254 上新行为才第一次兑现这个旋钮的字面承诺。
|
|
54
|
+
* ⚠️ **能力边界说清楚**(codex 复审二轮):本模块是**纯 env→spec 投影**,手里没有 server
|
|
55
|
+
* 版本/capability 这个量,所以对 <1.254 的部署它兑现不了「全关」—— 那两键仍会默认开着,而
|
|
56
|
+
* 壳侧无从察觉。补这个洞的正位在**请求装配层**(按已探到的 capability 判,与 limitsWire 的
|
|
57
|
+
* 两道地板同族),已记进 docs/refactor/README.md 的宿主/上游工单表。本层只保证两件:
|
|
58
|
+
* ① 新行为在任何 server 代际上都**不比旧行为差**(旧行为在所有代际上都关不掉那两键);
|
|
59
|
+
* ② 不在自己的能力之外说大话 —— 所以上面那句「真·全关」的适用范围就是 ≥1.254。
|
|
60
|
+
* · `full` / `all` ⇒ literal-true 族 7 员全开(**不含** DEFAULT-ON 两键 ——
|
|
61
|
+
* 不发 = 保持 core 默认开,壳不替 core 做那个决定)
|
|
62
|
+
* · `default` ⇒ CC-parity 那一对(显式写出「默认」这个意思)
|
|
63
|
+
* · `<key>` / `+<key>` ⇒ 单点打开一个 literal-true 族成员
|
|
64
|
+
* · `-agentListing` / `-skillsListing` ⇒ 产出 **`false`**(explicit-false 族唯一的关法)
|
|
65
|
+
* · `todoReminderMode=baseline|off` ⇒ 取值型键
|
|
66
|
+
* 🔴 未登记 token / 对 literal-true 族用 `-` / todoReminderMode 取闭集外的值 ⇒ **fail-loud 抛错**。
|
|
67
|
+
* 静默忽略是这条链上最坏的失效形:用户以为自己关掉了 agentListing,而请求照发、附件照进上下文,
|
|
68
|
+
* 并且没有任何一处会说出来。
|
|
24
69
|
*
|
|
25
70
|
* PURE — no env/file IO of its own (callers pass `env`), the verified wireCaps pattern. The CALLER
|
|
26
71
|
* (seamQuery / the print-mode ask) gates the read to LIVE mode so the mock request shape never
|
|
27
72
|
* changes.
|
|
28
73
|
*/
|
|
29
74
|
import { type EnvLike } from './hostEnv.js';
|
|
30
|
-
/** The env key the shell reads.
|
|
31
|
-
* `0`/`false`/`no`/`off`/`none`)⇒ no stamp; `full|all` ⇒ all five producers; unset/anything else
|
|
32
|
-
* ⇒ the CC-parity default pair. */
|
|
75
|
+
/** The env key the shell reads. 取值语法见文件头注 §env。 */
|
|
33
76
|
export declare const ATTACHMENTS_ENV: "SEMA_ATTACHMENTS";
|
|
34
|
-
/**
|
|
77
|
+
/**
|
|
78
|
+
* Mirror of `@sema-agent/sdk` `TaskRequest.attachments`(6.3.0,`types.d.ts`)—— **10 键全镜像**。
|
|
79
|
+
* 两族的类型形不同,而这个不同**就是**契约本身(见文件头注):literal-true 族只能是 `true`
|
|
80
|
+
* (发 `false` 违约),DEFAULT-ON 两族是 `boolean`(`false` 才是它们的关)。
|
|
81
|
+
*/
|
|
35
82
|
export interface AttachmentsSpec {
|
|
36
83
|
todoReminder?: true;
|
|
84
|
+
/** 取值型:`"baseline"` | `"off"`(缺席 = 引擎默认)。 */
|
|
85
|
+
todoReminderMode?: 'baseline' | 'off';
|
|
37
86
|
changedFiles?: true | {
|
|
38
87
|
maxFiles?: number;
|
|
39
88
|
};
|
|
40
89
|
planModeReminder?: true;
|
|
90
|
+
budgetUsd?: true;
|
|
41
91
|
backgroundTasks?: true;
|
|
42
92
|
toolsDelta?: true;
|
|
93
|
+
mcpInstructions?: true;
|
|
94
|
+
agentListing?: boolean;
|
|
95
|
+
skillsListing?: boolean;
|
|
43
96
|
}
|
|
97
|
+
/** 键面台账(机读位):加键必须同批登记,否则下方 `Covers` 双向钉编译红。 */
|
|
98
|
+
export declare const ATTACHMENTS_SPEC_KEYS: readonly ["todoReminder", "todoReminderMode", "changedFiles", "planModeReminder", "budgetUsd", "backgroundTasks", "toolsDelta", "mcpInstructions", "agentListing", "skillsListing"];
|
|
99
|
+
/** ② 族(core DEFAULT-ON,explicit false 才关)。 */
|
|
100
|
+
export declare const ATTACHMENTS_DEFAULT_ON_KEYS: readonly ["agentListing", "skillsListing"];
|
|
44
101
|
/**
|
|
45
|
-
* ENV source → the `TaskRequest.attachments` stamp.
|
|
46
|
-
*
|
|
102
|
+
* ENV source → the `TaskRequest.attachments` stamp.
|
|
103
|
+
*
|
|
104
|
+
* 恒返回一个对象:literal-true 族永远只出现 `true`(core 契约:off = 删键),而
|
|
105
|
+
* `agentListing`/`skillsListing` 只在被显式关掉时出现,值恒为 `false`(它们的 core 默认是开,
|
|
106
|
+
* 不发 = 不动那个默认)。返回型保留 `| undefined` 是给未来留位,今天没有分支产出它。
|
|
107
|
+
*
|
|
108
|
+
* @throws 未登记 token / 对 literal-true 族用 `-` / todoReminderMode 取闭集外的值(fail-loud:
|
|
109
|
+
* 静默忽略会让「我明明关了」与「它其实还开着」同时为真)。
|
|
47
110
|
*/
|
|
48
111
|
export declare function attachmentsForRequest(env?: EnvLike): AttachmentsSpec | undefined;
|