@sema-agent/client-core 0.11.23 → 0.12.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/README.md +3 -1
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +15 -1
- package/dist/adapter/runStream.d.ts +7 -0
- package/dist/adapter/runStream.js +5 -1
- package/dist/fleet/fleetProjection.js +4 -0
- package/dist/hitl/planReviewWire.js +42 -1
- package/dist/seam.d.ts +9 -0
- package/dist/subagent/engineDelegatedPrompt.js +25 -3
- package/package.json +3 -3
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.12.1
|
|
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
|
|
@@ -216,6 +216,8 @@ itself (FAILED names + skipped names + arithmetic reconciliation). All-SKIP repo
|
|
|
216
216
|
| `scripts/run-streamjson-timing-honesty-test.mjs` | Stream timing & terminal honesty ([2084]): held errored fs-write results release on model progress; wall-clock timeout maps to `error_during_execution` with a truthful salvage note; the synthetic API-error assistant row carries the `<synthetic>` in-message sentinel |
|
|
217
217
|
| `scripts/run-background-view-test.mjs` | `createBackgroundView` lifecycle: polling/notify pairing, per-source degrade (`501 → not-configured` vs `unavailable`), the capabilities `scheduler` probe, and dispose really aborting the in-flight fleet snapshot (pure projection lives in the pure suite's W-A segment) |
|
|
218
218
|
| `scripts/run-fleet-view-keys-test.mjs` | The fleet projection views, **both directions**: `FleetTaskView`/`FleetWorkflowView` ⇄ their key lists (compile-pinned) ⇄ what a maximal/minimal row really projects, plus a wire-key coverage ledger (every `FleetTaskRow` key is either projected or carries a written reason why not) and a drift ledger against the shell's render contract. A one-directional assignability check is blind to optional keys — which is how `startedAt` was silently dropped |
|
|
219
|
+
| `scripts/run-usage-verbatim-channel-test.mjs` | The two complementary usage disciplines (core 3.0.0 metering semantics): the CC `ModelUsage` mirror stays pure (five pinned keys, `totalInputTokens` has no seat), while the sema-owned channel forwards the engine `turn_end.usage` object **verbatim** (six keys, incl. `totalInputTokens`) via `last_turn_usage.engineUsage` / `handle.latestEngineUsage` — honest absence on pre-3.0.0 engines, no fabricated zeros |
|
|
220
|
+
| `scripts/run-plan-review-decide-verify-test.mjs` | `decidePlanReview`'s post-decide honesty ([2315]/[2316], engine RB-471 family): a 2xx from the decide endpoint is **not** a terminal — the wire re-pulls the task status and words the outcome by the real shape (still-locked / legal new gate / genuinely left park / unverified), never claiming success it hasn't earned. Driven against a real fake-engine HTTP server through the shipped dist |
|
|
219
221
|
| `scripts/run-public-surface-test.mjs` | The outward promises: the npm export surface baseline, the peer floor witness, and this README's claims |
|
|
220
222
|
|
|
221
223
|
Each suite carries a floor that only moves up — a refactor that stops executing a group of
|
|
@@ -16,11 +16,25 @@
|
|
|
16
16
|
* `maxOutputTokens` — the missing `contextWindow` denominator is mock-fill from
|
|
17
17
|
* a static per-model table (contract 02 §2.7 VERIFY). Defaulted to 0 to satisfy
|
|
18
18
|
* the CC `ModelUsage` shape without inventing a number.
|
|
19
|
+
*
|
|
20
|
+
* core 3.0.0 语义翻转注(2026-08-02,[2318] 提货):wire 的 `inputTokens` 从「含 cache 总量」
|
|
21
|
+
* 翻为「归一化未命中分量(cache-MISS)」。本映射**一个字节没动**,但语义上恰好从错变对 ——
|
|
22
|
+
* CC `ModelUsage.inputTokens` 的本义就是不含 cache 的 input_tokens(Anthropic API 同义),
|
|
23
|
+
* 旧协议下这里灌总量其实是跨层同名不同义病([C7] 家族)。总量(含 cache)自 3.0.0 起走
|
|
24
|
+
* `totalInputTokens`,在本文件的 CC 镜像里**没有座位**(CC 形状不承载非 CC 语义,[2295]),
|
|
25
|
+
* 消费方要总量请走逐字通道 {@link EngineTurnUsage}(runStream `engineUsage` 键)。
|
|
19
26
|
*/
|
|
20
27
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
21
28
|
import type { ModelUsage } from '../types.js';
|
|
22
|
-
|
|
29
|
+
/**
|
|
30
|
+
* 引擎 `turn_end.usage` 的**逐字原形**(SDK 3.0.0 六键全必填,含 `totalInputTokens`)。
|
|
31
|
+
* [2295] 裁 ②(2026-08-02):sema 自有 usage 通道逐字透传这个对象,不做再策展的子集映射 ——
|
|
32
|
+
* 这次断链的根因就是「中间层五键定型丢多余键」。导出类型 = 通道的契约面(runStream 折叠点
|
|
33
|
+
* 的 `engineUsage` 键与宿主消费端同用这一个名字)。
|
|
34
|
+
*/
|
|
35
|
+
export type EngineTurnUsage = NonNullable<Extract<AgentEvent, {
|
|
23
36
|
type: 'turn_end';
|
|
24
37
|
}>['usage']>;
|
|
38
|
+
type TurnUsage = EngineTurnUsage;
|
|
25
39
|
export declare function turnUsageToModelUsage(usage: TurnUsage): ModelUsage;
|
|
26
40
|
export {};
|
|
@@ -30,9 +30,16 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
32
32
|
import { type SDKMessage, type EmitContext, type ModelUsage } from './types.js';
|
|
33
|
+
import type { EngineTurnUsage } from './downstream/turnUsageToModelUsage.js';
|
|
33
34
|
export interface RunStreamHandle {
|
|
34
35
|
/** The latest folded turn usage (footer counters); updated on each turn_end. */
|
|
35
36
|
latestUsage?: ModelUsage;
|
|
37
|
+
/**
|
|
38
|
+
* [2295] 裁 ② 逐字通道:与 latestUsage 同拍更新的引擎 `turn_end.usage` **原形**(六键含
|
|
39
|
+
* `totalInputTokens`)。镜像键求和≠总量(仅 cache 族一致时相等),总量消费面吃这份。
|
|
40
|
+
* 旧引擎(core <3.0.0)wire 缺形时为 undefined —— 诚实缺席,不造零值。
|
|
41
|
+
*/
|
|
42
|
+
latestEngineUsage?: EngineTurnUsage;
|
|
36
43
|
}
|
|
37
44
|
export declare function isRunStreamActive(): boolean;
|
|
38
45
|
export declare function runStream(events: AsyncIterable<AgentEvent>, ctx: EmitContext, handle?: RunStreamHandle): AsyncGenerator<SDKMessage>;
|
|
@@ -81,6 +81,8 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
81
81
|
const isSubFlow = ev.parentToolCallId !== undefined;
|
|
82
82
|
if (usage) {
|
|
83
83
|
handle.latestUsage = usage;
|
|
84
|
+
// [2295] 裁 ② 逐字通道:与镜像同拍存一份引擎原形(六键含 totalInputTokens)。
|
|
85
|
+
handle.latestEngineUsage = ev.usage;
|
|
84
86
|
// plugins 专项 G1(2026-07-21):同一折叠点多发一份给 lastTurnUsageStore——StatusLine
|
|
85
87
|
// 的 statusline 命令 stdin(context_window.current_usage)在消息面无 usage(seam 合成
|
|
86
88
|
// 消息不带)时回落到这里,claude-hud 类插件的 Context 条才有真值。sub-flow 的 turn_end
|
|
@@ -91,7 +93,9 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
91
93
|
// 与原式同样**不 await**(fire-and-forget),sink 抛错不影响流(fail-soft 原语义)。
|
|
92
94
|
if (!isSubFlow && ctx.emitChrome) {
|
|
93
95
|
try {
|
|
94
|
-
|
|
96
|
+
// engineUsage = [2295] 裁 ② 逐字原形(additive 键,契约见 seam.ts 本臂 doc):
|
|
97
|
+
// 总量/命中率消费面吃它,镜像 usage 保纯。
|
|
98
|
+
void ctx.emitChrome({ kind: 'last_turn_usage', laneProof: MAIN, usage, engineUsage: ev.usage });
|
|
95
99
|
}
|
|
96
100
|
catch { /* fail-soft — statusline 退回 null,原语义 */ }
|
|
97
101
|
}
|
|
@@ -320,6 +320,7 @@ export const FLEET_WORKFLOW_VIEW_KEYS = FLEET_WORKFLOW_VIEW_KEY_TUPLE;
|
|
|
320
320
|
const FLEET_TASK_ROW_WIRE_KEY_TUPLE = [
|
|
321
321
|
'id',
|
|
322
322
|
'name',
|
|
323
|
+
'sourceLane',
|
|
323
324
|
'description',
|
|
324
325
|
'agentType',
|
|
325
326
|
'agentName',
|
|
@@ -367,6 +368,9 @@ export const FLEET_WORKFLOW_ROW_WIRE_KEYS = FLEET_WORKFLOW_ROW_WIRE_KEY_TUPLE;
|
|
|
367
368
|
* 腿上当场红,不给「悄悄没人管」留位置。
|
|
368
369
|
*/
|
|
369
370
|
export const FLEET_TASK_ROW_KEYS_NOT_PROJECTED = {
|
|
371
|
+
sourceLane: 'SDK 3.0.0 新键([2070]① 让位半场:前台 delegation 复合 id 行带 "run-leg" 标)。两仓现零消费面 ——' +
|
|
372
|
+
'#95 纪律:没人读的键不盲投。已知潜在消费方=壳 wf-panel 的双生行过滤(现走 taskId 台账补偿),' +
|
|
373
|
+
'若迁到本键判别则届时投影并删本条(那是行为改动,单独批)。',
|
|
370
374
|
agentType: '折进 `name`(deriveAgentLabel:agentName ?? agentType,都缺才退 objective 派生)——渲染契约只有一个身份列,187 的 tjl 同形。',
|
|
371
375
|
agentName: '同 agentType:两位在本层合成一个身份 label,分开带出去等于把 187 的 tjl 规则推给每个消费端各写一遍。',
|
|
372
376
|
workflowRunId: 'workflow 归属**不走行字段**:权威判定在 adapt 的 task_progress 臂(recordWorkflowAgentTaskId,判据④),端按 taskId 尾段查台账过滤双生行。行上再带一份 = 第二真源。',
|
|
@@ -119,7 +119,48 @@ export async function decidePlanReview(taskId, decision) {
|
|
|
119
119
|
try {
|
|
120
120
|
const body = await client.assistant.planReview(taskId, { decision });
|
|
121
121
|
hostLog('debug', `planReviewWire: ${decision} → ok ${JSON.stringify(body).slice(0, 200)}`);
|
|
122
|
-
|
|
122
|
+
// [2315]/[2316](#109,2026-08-02):decide 的 **2xx 不当终态** —— test 黑盒实测 reject 9/9
|
|
123
|
+
// 返回 200 而会话仍锁在同一 gate(core RB-471:重开兜底对 reject 腿恒真误触发)。这里回拉
|
|
124
|
+
// 一次任务状态,按真形分三路措辞;根因归 core/server,本腿是「对外动作回读验证」在产品面的
|
|
125
|
+
// 同款,上游修后仍有价值(approve 合法推进新 gate 的形也靠它说真话)。
|
|
126
|
+
// 回拉用**短超时**独立 client(15s):verify 腿挂住不该占用 decide 的 6h 宽预算。
|
|
127
|
+
let postStatus;
|
|
128
|
+
try {
|
|
129
|
+
const probe = makeEngineWireClient({
|
|
130
|
+
baseUrl: cfg.baseUrl,
|
|
131
|
+
...(cfg.token ? { token: cfg.token } : {}),
|
|
132
|
+
principal: cfg.principal,
|
|
133
|
+
timeoutMs: 15_000,
|
|
134
|
+
});
|
|
135
|
+
const post = probe ? (await probe.runs.get(taskId)) : null;
|
|
136
|
+
postStatus = typeof post?.status === 'string' ? post.status : undefined;
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
hostLog('debug', `planReviewWire: post-decide status re-pull failed (fail-soft): ${String(e)}`);
|
|
140
|
+
}
|
|
141
|
+
// 回拉失败时回落 decide 200 体自带的 status(次级来源;两者都缺=unverified,措辞如实降级)。
|
|
142
|
+
const effective = postStatus ?? (typeof body?.status === 'string' ? body.status : undefined);
|
|
143
|
+
if (effective === 'needs_review') {
|
|
144
|
+
// 决定没生效,仍锁原 gate。不渲「已处理」;真出路只有 approve 或 cancel(下一次提交撞 409
|
|
145
|
+
// 时 activeRunSelfHeal 会把审批卡重开——这里不自动重弹卡,避免「刚拒绝又弹卡」的突袭感,
|
|
146
|
+
// 决定权经卡的重开路径还给用户)。
|
|
147
|
+
outcome =
|
|
148
|
+
`The plan_review ${decision} returned HTTP 200 but the session is STILL locked on the same review gate ` +
|
|
149
|
+
`(post-decide status: needs_review). The decision did NOT take effect (known engine issue, RB-471 family). ` +
|
|
150
|
+
`Tell the user plainly that the ${decision} did not go through; the reliable exits today are approving the plan or cancelling the task.`;
|
|
151
|
+
}
|
|
152
|
+
else if (effective === 'suspended') {
|
|
153
|
+
// 合法推进到新 gate(test [2315] ②形):不是「完成」,如实说下一张审批卡会跟上。
|
|
154
|
+
outcome =
|
|
155
|
+
`The plan was ${decision === 'approve' ? 'approved' : 'rejected'} and the task advanced to a NEW approval gate ` +
|
|
156
|
+
`(post-decide status: suspended) — the next approval card will surface it; this is not a completion yet.`;
|
|
157
|
+
}
|
|
158
|
+
else if (effective !== undefined) {
|
|
159
|
+
outcome = `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: ${effective} (post-decide re-checked: task left the review gate).`;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
outcome = `The plan was ${decision === 'approve' ? 'approved and the parked task resumed to completion' : 'rejected (plan discarded)'} — final status: unknown (post-decide verification unavailable; treat as unconfirmed).`;
|
|
163
|
+
}
|
|
123
164
|
}
|
|
124
165
|
catch (e) {
|
|
125
166
|
// APIError 判型走 status duck-check(sseIdleTriage 同款纪律:双包时 instanceof 会分叉)。
|
package/dist/seam.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* 往返守卫不变量:同流重放 ⇒ 同 id 序列。
|
|
8
8
|
*/
|
|
9
9
|
import type { ModelUsage, SDKMessage } from '@sema-agent/agent-types';
|
|
10
|
+
import type { EngineTurnUsage } from './adapter/downstream/turnUsageToModelUsage.js';
|
|
10
11
|
/**
|
|
11
12
|
* `AbortSignal` 的结构型(B3 扩容,SEAM-GAP-4)。
|
|
12
13
|
*
|
|
@@ -327,11 +328,19 @@ export type ChromeEvent = {
|
|
|
327
328
|
* 宿主消费义务:落「最近一次 turn 的真 usage」槽,供 statusline 类消费面在消息面取不到 usage
|
|
328
329
|
* 时回落(不落 = 插件的 Context 条恒 0%,原病复发)。子流 turn_end **不发本臂**(判据同 §E2:
|
|
329
330
|
* statusline 显示的是主会话窗口,子代 usage 会把 Context% 打成孩子的窗口占用)。
|
|
331
|
+
*
|
|
332
|
+
* `engineUsage`([2295] 裁 ② 逐字通道,2026-08-02 加,additive):引擎 `turn_end.usage`
|
|
333
|
+
* **整对象原形**(SDK 3.0.0 六键必填,含 `totalInputTokens`=归一化总输入含 cache)。
|
|
334
|
+
* `usage`(CC ModelUsage 镜像)保持纯净不加键 —— CC 形状不承载非 CC 语义;要总量/命中率的
|
|
335
|
+
* 消费面(context% 分子、cacheHitRate 分母)一律吃这份原形,别把镜像键求和当总量
|
|
336
|
+
* (求和等式仅 cache 族一致时成立,唯一硬保证=弱式 ≥,[2318])。optional:旧引擎
|
|
337
|
+
* (core <3.0.0)wire 无此形时缺席,消费方按诚实缺席处理,不造零值。
|
|
330
338
|
*/
|
|
331
339
|
| {
|
|
332
340
|
kind: 'last_turn_usage';
|
|
333
341
|
laneProof: LaneProof;
|
|
334
342
|
usage: ModelUsage;
|
|
343
|
+
engineUsage?: EngineTurnUsage;
|
|
335
344
|
}
|
|
336
345
|
/**
|
|
337
346
|
* B3 新臂 ③(runStream 外向边切除,设计稿 §5.2 头号污染源)—— `done{status:"needs_review"}` +
|
|
@@ -104,8 +104,10 @@ function contentOf(entry) {
|
|
|
104
104
|
* prompt(这条判别就是「别把子代自己的消息当委派 prompt」的落点)。
|
|
105
105
|
*/
|
|
106
106
|
export function delegatedPromptText(content) {
|
|
107
|
+
// 字符串形也要过信封剥离 —— 这条早退曾经绕过它(自家红测抓到:剥的是数组形,而真转录里
|
|
108
|
+
// 两种形都出现)。任何「返回 prompt 正文」的出口都必须经同一道剥离,否则就是半修。
|
|
107
109
|
if (typeof content === 'string')
|
|
108
|
-
return content
|
|
110
|
+
return stripEnvelopes(content);
|
|
109
111
|
if (!Array.isArray(content))
|
|
110
112
|
return undefined;
|
|
111
113
|
const parts = [];
|
|
@@ -122,8 +124,28 @@ export function delegatedPromptText(content) {
|
|
|
122
124
|
if (b.type === 'text' && typeof b.text === 'string')
|
|
123
125
|
parts.push(b.text);
|
|
124
126
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
+
return stripEnvelopes(parts.join('\n'));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 剥掉委派 prompt 外面那两层**运行时信封**,再判空。
|
|
131
|
+
*
|
|
132
|
+
* 🔴 为什么必须剥(2026-08-01 真机实测):引擎会话里那条 user 消息的前 ~1330 字符是
|
|
133
|
+
* `<system-reminder>…技能不可用清单…</system-reminder>` 与 `<fork-boilerplate>…worker fork 交代…
|
|
134
|
+
* </fork-boilerplate>` 两层信封,真正的委派 prompt 排在后面。而详情页的 `displayPrompt` 只渲**前 300
|
|
135
|
+
* 字符** ⇒ 用户在「Prompt」标题下看到的是一份技能清单,不是他派下去的那句话。
|
|
136
|
+
*
|
|
137
|
+
* 判空也必须在剥完之后做:只有信封、没有正文的消息应当算**没有委派 prompt**(让取件腿如实留白),
|
|
138
|
+
* 而不是把信封当成 prompt 渲出去 —— 后者是「渲了个东西」冒充「渲对了东西」。
|
|
139
|
+
*
|
|
140
|
+
* 信封是**成对标签**,所以用配对正则整段剥;不配对(被截断等)时保守不动,宁可多渲也不吃掉正文。
|
|
141
|
+
*/
|
|
142
|
+
const PROMPT_ENVELOPE_TAGS = ['system-reminder', 'fork-boilerplate'];
|
|
143
|
+
function stripEnvelopes(raw) {
|
|
144
|
+
let text = raw;
|
|
145
|
+
for (const tag of PROMPT_ENVELOPE_TAGS) {
|
|
146
|
+
text = text.replace(new RegExp(`<${tag}>[\\s\\S]*?</${tag}>`, 'g'), '');
|
|
147
|
+
}
|
|
148
|
+
return text.trim() || undefined;
|
|
127
149
|
}
|
|
128
150
|
/**
|
|
129
151
|
* 从转录里挑出委派 prompt = **最后一条非 tool_result 的 user 消息**(倒扫)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@sema-agent/agent-types": ">=0.2.0",
|
|
31
|
-
"@sema-agent/sdk": ">=
|
|
31
|
+
"@sema-agent/sdk": ">=3.0.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@sema-agent/agent-types": "^0.2.0",
|
|
35
|
-
"@sema-agent/sdk": "^
|
|
35
|
+
"@sema-agent/sdk": "^3.0.0",
|
|
36
36
|
"esbuild": "^0.27.4",
|
|
37
37
|
"typescript": "^6.0.2"
|
|
38
38
|
}
|