@sema-agent/client-core 0.25.0 → 0.26.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
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.26.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
|
|
@@ -53,7 +53,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
53
53
|
`SseIdleError`, `probeHealth`, `APIError` and `TaskStopConflictError` are imported as values in
|
|
54
54
|
five modules, and the browser bundle really bundles the SDK through (the portability guard would
|
|
55
55
|
exit 3 rather than quietly mark it external).
|
|
56
|
-
- The declared floor is `>=6.
|
|
56
|
+
- The declared floor is `>=6.14.0`, and it is *witnessed*: the guard checks that an actually
|
|
57
57
|
installed SDK at that line still exports every value-level symbol this package imports and still
|
|
58
58
|
declares `TaskStats.costMicroUsd` (the key `costOrNull` reads). A floor nobody ever ran is a
|
|
59
59
|
promise, not a contract.
|
|
@@ -17,6 +17,13 @@
|
|
|
17
17
|
*/
|
|
18
18
|
/** 构造期 kick(async 幂等);probe = client.capabilities 薄闭包。 */
|
|
19
19
|
export declare function kickEngineCapsProbe(baseUrl: string, probe: () => Promise<unknown>): void;
|
|
20
|
+
/**
|
|
21
|
+
* 0.26.0(#225 件1 配套,消费请托自领):等**当前这一次**探测落地(成功或失败都算落地)。
|
|
22
|
+
* 无 in-flight ⇒ 立即 resolve —— 判据永远是缓存里的位({@link engineCapTrue}),不是本 promise;
|
|
23
|
+
* 本函数只解决「kick 完只能盲猜轮询窗」的时序问题(慢响应被猜短的窗渲成「没有这条车道」)。
|
|
24
|
+
* 绝不 reject(探测失败=位维持未判,调用方按缺席降级)。
|
|
25
|
+
*/
|
|
26
|
+
export declare function engineCapsSettled(baseUrl: string | undefined): Promise<void>;
|
|
20
27
|
/** 同步读口:该 base 的 caps 里 key 是否字面 true(未判/缺键 = false)。 */
|
|
21
28
|
export declare function engineCapTrue(baseUrl: string | undefined, key: string): boolean;
|
|
22
29
|
/**
|
package/dist/engineCapsCache.js
CHANGED
|
@@ -17,12 +17,14 @@
|
|
|
17
17
|
*/
|
|
18
18
|
const capsByBase = new Map();
|
|
19
19
|
const inFlight = new Set();
|
|
20
|
+
/** in-flight 探测的 settle 载体(finally 清;engineCapsSettled 消费)。 */
|
|
21
|
+
const settleByBase = new Map();
|
|
20
22
|
/** 构造期 kick(async 幂等);probe = client.capabilities 薄闭包。 */
|
|
21
23
|
export function kickEngineCapsProbe(baseUrl, probe) {
|
|
22
24
|
if (!baseUrl || capsByBase.has(baseUrl) || inFlight.has(baseUrl))
|
|
23
25
|
return;
|
|
24
26
|
inFlight.add(baseUrl);
|
|
25
|
-
|
|
27
|
+
const run = (async () => {
|
|
26
28
|
try {
|
|
27
29
|
const caps = await probe();
|
|
28
30
|
if (caps && typeof caps === 'object') {
|
|
@@ -34,8 +36,22 @@ export function kickEngineCapsProbe(baseUrl, probe) {
|
|
|
34
36
|
}
|
|
35
37
|
finally {
|
|
36
38
|
inFlight.delete(baseUrl);
|
|
39
|
+
settleByBase.delete(baseUrl);
|
|
37
40
|
}
|
|
38
41
|
})();
|
|
42
|
+
settleByBase.set(baseUrl, run);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* 0.26.0(#225 件1 配套,消费请托自领):等**当前这一次**探测落地(成功或失败都算落地)。
|
|
46
|
+
* 无 in-flight ⇒ 立即 resolve —— 判据永远是缓存里的位({@link engineCapTrue}),不是本 promise;
|
|
47
|
+
* 本函数只解决「kick 完只能盲猜轮询窗」的时序问题(慢响应被猜短的窗渲成「没有这条车道」)。
|
|
48
|
+
* 绝不 reject(探测失败=位维持未判,调用方按缺席降级)。
|
|
49
|
+
*/
|
|
50
|
+
export function engineCapsSettled(baseUrl) {
|
|
51
|
+
if (!baseUrl)
|
|
52
|
+
return Promise.resolve();
|
|
53
|
+
const p = settleByBase.get(baseUrl);
|
|
54
|
+
return p ? p.then(() => undefined, () => undefined) : Promise.resolve();
|
|
39
55
|
}
|
|
40
56
|
/** 同步读口:该 base 的 caps 里 key 是否字面 true(未判/缺键 = false)。 */
|
|
41
57
|
export function engineCapTrue(baseUrl, key) {
|
|
@@ -61,4 +77,5 @@ export function engineCapString(baseUrl, key) {
|
|
|
61
77
|
export function __resetEngineCapsCacheForTests() {
|
|
62
78
|
capsByBase.clear();
|
|
63
79
|
inFlight.clear();
|
|
80
|
+
settleByBase.clear();
|
|
64
81
|
}
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
* 🔴 UNTRUSTED:gate args 为模型作文(service 已 redact),只渲染绝不回喂;决断只带 decision 枚举。
|
|
63
63
|
*/
|
|
64
64
|
import { type HitlClientLike } from './hitlBridge.js';
|
|
65
|
-
import type { ToolApprovalRespondAck } from '@sema-agent/sdk';
|
|
65
|
+
import type { RuleSuggestion, ToolApprovalRespondAck } from '@sema-agent/sdk';
|
|
66
66
|
/** fs 写权限 gate 判定:未来的一等 kind(tool_approval)或按 toolName(server 桥首批=fs 写三件,
|
|
67
67
|
* [820] 表)。AskUserQuestion 永不进这里(ask 桥先判)。 */
|
|
68
68
|
export declare function isFsApprovalGate(gate: {
|
|
@@ -102,11 +102,16 @@ export interface FsApprovalWireDeps {
|
|
|
102
102
|
export declare function structurallyEqual(a: unknown, b: unknown): boolean;
|
|
103
103
|
/** 三选卡的原始决断(vendor 卡的三个选项 + abort + 表面失败)。allow_session=第 2 项
|
|
104
104
|
* (vendor 产出非空 permissionUpdates:setMode acceptEdits / cwd 外 addDirectories)。 */
|
|
105
|
-
|
|
105
|
+
/** allow 决断臂(命名形,typeshape B4 口径;0.26.0 因 persistRule 位达 4 成员抽名)。 */
|
|
106
|
+
export interface ApprovalCardAllowDecision {
|
|
106
107
|
kind: 'allow';
|
|
107
108
|
allowSession: boolean;
|
|
108
109
|
updatedInput?: unknown;
|
|
109
|
-
|
|
110
|
+
/** #225 件1:卡上选中的持久规则候选**原文**({@link ApprovalCardRequest.ruleSuggestions} 之一);
|
|
111
|
+
* 缺席/空串 = 本次不兑付。表外文本会在编排层被丢键留痕(server 亦拒 rule_not_offered)。 */
|
|
112
|
+
persistRule?: string;
|
|
113
|
+
}
|
|
114
|
+
export type ApprovalCardDecision = ApprovalCardAllowDecision | {
|
|
110
115
|
kind: 'deny';
|
|
111
116
|
} | {
|
|
112
117
|
kind: 'aborted';
|
|
@@ -138,6 +143,13 @@ export interface ApprovalCardRequest {
|
|
|
138
143
|
/** 委派出处链(core 5.9.0 W1)——原样来自 {@link ToolApprovalFrame.delegation};只读展示增强,
|
|
139
144
|
* `agentName` UNTRUSTED-for-display。子代 ask 才在场。 */
|
|
140
145
|
delegation?: ToolApprovalDelegation;
|
|
146
|
+
/**
|
|
147
|
+
* 持久规则候选(#225 件1,原样来自 {@link ToolApprovalFrame.ruleSuggestions} 的合形项)——
|
|
148
|
+
* 卡据此渲「不再询问」档;缺席/空 = 卡形与 0.25.0 字节不变(不渲该档)。
|
|
149
|
+
* 🔴 卡只许把其中一条的 `rule` **原样**放进决断的 `persistRule`(不拼、不改写);
|
|
150
|
+
* 出身裁剪(MANDATED ask 不提供本档)候 core 5.25.0 出身键 wire 过境,见 #144/[3438]/[3442]。
|
|
151
|
+
*/
|
|
152
|
+
ruleSuggestions?: RuleSuggestion[];
|
|
141
153
|
}
|
|
142
154
|
/**
|
|
143
155
|
* 🔴 **拆缝口** —— 弹「三选卡」并等人的决断。壳 = vendored CC `PermissionRequest`;
|
|
@@ -219,6 +231,14 @@ export interface ToolApprovalFrame {
|
|
|
219
231
|
* 一层浮上来的。`agentName` 与 {@link sourceAgentName} 同属 UNTRUSTED-for-display。
|
|
220
232
|
*/
|
|
221
233
|
delegation?: ToolApprovalDelegation;
|
|
234
|
+
/**
|
|
235
|
+
* server ≥7.12.0(#154 车二 / design/179,ADDITIVE)——**引擎铸的**持久规则候选。只在
|
|
236
|
+
* `capabilities.permissionRules` 谓词为真且引擎对本次裁决真铸出候选时在场;缺席 = 这张卡
|
|
237
|
+
* 不提供「不再询问」档(老 server / 规则店未接 / 引擎判无候选,三者同形,不猜)。
|
|
238
|
+
* 🔴 文本由 ENGINE 铸,不由客户端拼:回决时只能把其中一条**原样**报回
|
|
239
|
+
* (`respond` 体 `persistRule.rule`),报表外文本 = server 拒 `rule_not_offered`。
|
|
240
|
+
*/
|
|
241
|
+
ruleSuggestions?: RuleSuggestion[];
|
|
222
242
|
}
|
|
223
243
|
/** {@link ToolApprovalFrame.delegation} 的形(命名形,不用内联匿名 —— typeshape 门 B4 棘轮口径)。 */
|
|
224
244
|
export interface ToolApprovalDelegation {
|
|
@@ -234,7 +254,7 @@ export interface ToolApprovalDelegation {
|
|
|
234
254
|
* `TOOL_APPROVAL_FRAME_KEYS` 比对——SDK additive 增键时对账当天红,不再人肉追平。
|
|
235
255
|
* 下面两个类型钉保证镜像与 interface 本身不可能漂移(少键/多键都是编译错)。
|
|
236
256
|
*/
|
|
237
|
-
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "delegation", "outcome"];
|
|
257
|
+
export declare const TOOL_APPROVAL_FRAME_KEYS_MIRROR: readonly ["type", "approvalId", "toolCallId", "toolName", "sourceTaskId", "fromSubagent", "sourceAgentName", "message", "args", "argsOmitted", "governanceForced", "ruleSuggestions", "delegation", "outcome"];
|
|
238
258
|
/** 子代帧判别:显式键 fromSubagent(core 1.378 RB-39②)优先;缺席退 sourceTaskId 在场性权宜式
|
|
239
259
|
* (server 1.258 [1549]①3,旧代际兼容)。 */
|
|
240
260
|
export declare function isFromSubagent(frame: ToolApprovalFrame): boolean;
|
|
@@ -254,10 +274,14 @@ export type ToolApprovalRespondDecision = 'allow' | 'allow_session' | 'deny';
|
|
|
254
274
|
* 返回型放宽成 `ack | void` 而不是硬钉 ack:**注入面的实现方**(旧 liveClient / mock / 测试桩)
|
|
255
275
|
* 回 void 仍然合法(assignable),消费端按 {@link readToolApprovalRespondAck} 结构化读,缺席=未知。
|
|
256
276
|
*/
|
|
257
|
-
|
|
277
|
+
/** respond 的可选参(命名形,typeshape B4 口径;0.26.0 因 persistRule 位达 3 成员抽名)。 */
|
|
278
|
+
export interface RespondToolApprovalOpts {
|
|
258
279
|
signal?: AbortSignal;
|
|
259
280
|
updatedInput?: unknown;
|
|
260
|
-
|
|
281
|
+
/** #225 件1:兑付键 —— 帧候选之一的**原文**(编排层已做表内核对与 deny 剥除)。 */
|
|
282
|
+
persistRule?: string;
|
|
283
|
+
}
|
|
284
|
+
export type RespondToolApprovalFn = (approvalId: string, decision: ToolApprovalRespondDecision, opts?: RespondToolApprovalOpts) => Promise<ToolApprovalRespondAck | void>;
|
|
261
285
|
/**
|
|
262
286
|
* respond 回执的**结构化读口**(wire 是 JSON:注入面可能是旧 liveClient 的 raw fetch,也可能是
|
|
263
287
|
* 比本包新一版的 SDK)。坏形一律降 `undefined` —— 与 `controlRouter.errCodes` 同族纪律:
|
|
@@ -306,6 +306,9 @@ export const TOOL_APPROVAL_FRAME_KEYS_MIRROR = [
|
|
|
306
306
|
// [1947] 那次 `toolCallId` 滞后的同款形)。本门(run-approval-frame-keys-test.mjs)对 SDK 运行期
|
|
307
307
|
// 锚逐元素相等,所以这次滞后是被门抓到的,不是人肉发现的。
|
|
308
308
|
'governanceForced',
|
|
309
|
+
// sdk 6.14.0 追平(#225 件1,client-core 0.26.0):server 7.12.0 起真发规则候选 —— 门
|
|
310
|
+
// (run-approval-frame-keys-test.mjs)在 bump SDK 当天恰红抓获本键缺席,本次补齐。
|
|
311
|
+
'ruleSuggestions',
|
|
309
312
|
'delegation',
|
|
310
313
|
'outcome',
|
|
311
314
|
];
|
|
@@ -375,6 +378,12 @@ export function readToolApprovalRespondAck(v) {
|
|
|
375
378
|
decision: o.decision,
|
|
376
379
|
...(typeof o.rememberApplied === 'boolean' ? { rememberApplied: o.rememberApplied } : {}),
|
|
377
380
|
...(typeof o.updatedInputForwarded === 'boolean' ? { updatedInputForwarded: o.updatedInputForwarded } : {}),
|
|
381
|
+
// #225 件1(sdk 6.14.0):兑付回执两位。缺席 ≠ false(未带 persistRule 的回决/旧 server 字段
|
|
382
|
+
// 省略);非布尔/非串一律降缺席 —— 「规则存没存上」直接决定壳的诚实告知,透传坏形会说反话。
|
|
383
|
+
...(typeof o.rulePersisted === 'boolean' ? { rulePersisted: o.rulePersisted } : {}),
|
|
384
|
+
...(typeof o.ruleRefusal === 'string' && o.ruleRefusal !== ''
|
|
385
|
+
? { ruleRefusal: o.ruleRefusal }
|
|
386
|
+
: {}),
|
|
378
387
|
};
|
|
379
388
|
}
|
|
380
389
|
/** 结构性识别流上的 tool_approval 帧(named SSE frame,payload.type === 帧名)。
|
|
@@ -401,6 +410,35 @@ function pathFromGateMessage(message) {
|
|
|
401
410
|
return undefined;
|
|
402
411
|
return FS_WRITE_GATE_ASK_PATTERN.exec(message)?.[1];
|
|
403
412
|
}
|
|
413
|
+
/**
|
|
414
|
+
* `ruleSuggestions` 的结构读(wire 是 JSON:注入面/坏 mock/异代 server 都可能给别的形)。
|
|
415
|
+
* 逐项窄化:`rule` 非空串 + `match` 二词闭集 合形才保留(`command` 非串时单独降缺席);
|
|
416
|
+
* 全部不合形/非数组 ⇒ 整体缺席 —— 卡形退 0.25.0 字节(不渲「不再询问」档),绝不拿半条候选
|
|
417
|
+
* 渲一个按下去必被 server 拒的选项。上限 8 条(server 端候选本就 ≤2,超长=坏形,截不留痕会
|
|
418
|
+
* 掩盖注入,整体降缺席)。
|
|
419
|
+
*/
|
|
420
|
+
function readRuleSuggestions(v) {
|
|
421
|
+
if (!Array.isArray(v))
|
|
422
|
+
return undefined;
|
|
423
|
+
if (v.length === 0 || v.length > 8)
|
|
424
|
+
return undefined;
|
|
425
|
+
const out = [];
|
|
426
|
+
for (const item of v) {
|
|
427
|
+
if (item === null || typeof item !== 'object')
|
|
428
|
+
continue;
|
|
429
|
+
const o = item;
|
|
430
|
+
if (typeof o.rule !== 'string' || o.rule === '')
|
|
431
|
+
continue;
|
|
432
|
+
if (o.match !== 'exact' && o.match !== 'prefix')
|
|
433
|
+
continue;
|
|
434
|
+
// SDK 形上 `command` 必填(渲染用的命令模式)——缺席/非串 ⇒ 整项不合形丢弃(半条候选
|
|
435
|
+
// 渲出来是一格没有正文的选项)。
|
|
436
|
+
if (typeof o.command !== 'string' || o.command === '')
|
|
437
|
+
continue;
|
|
438
|
+
out.push({ rule: o.rule, match: o.match, command: o.command });
|
|
439
|
+
}
|
|
440
|
+
return out.length > 0 ? out : undefined;
|
|
441
|
+
}
|
|
404
442
|
/** 子代审批帧的归属徽章([1535] cli 半场):名一手源=帧上 sourceAgentName(core 1.378 RB-39②
|
|
405
443
|
* 显式展示身份,server redact 后透传,候 server 班车到货即亮);缺席退 fleet 在飞台账行名
|
|
406
444
|
* (`${agentType}: ${description}` 合成形;⚠️ sourceTaskId 实测=子代 sessionId 非 a… handle,
|
|
@@ -461,6 +499,7 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
461
499
|
// 🔴 治理位与委派链透传(2026-08-08):两键在帧上**只在为真/在场时**出现,所以这里也按在场性
|
|
462
500
|
// 条件 stamp —— 绝不 `governanceForced: frame.governanceForced === true`(那会把「缺席」折成
|
|
463
501
|
// 显式 `false`,而缺席的语义是「没有治理来源的证据」,不是「这门可以被表态掀掉」)。
|
|
502
|
+
const ruleSuggestions = readRuleSuggestions(frame.ruleSuggestions);
|
|
464
503
|
const card = await surfaceApprovalCard({
|
|
465
504
|
toolName,
|
|
466
505
|
args: args,
|
|
@@ -477,6 +516,8 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
477
516
|
...(isFromSubagent(frame) && isToolApprovalDelegation(frame.delegation)
|
|
478
517
|
? { delegation: frame.delegation }
|
|
479
518
|
: {}),
|
|
519
|
+
// #225 件1:规则候选透传(合形项;缺席/坏形 ⇒ 键不 stamp,卡形与 0.25.0 字节不变)。
|
|
520
|
+
...(ruleSuggestions !== undefined ? { ruleSuggestions } : {}),
|
|
480
521
|
});
|
|
481
522
|
const decision = card.kind === 'allow' ? (card.allowSession ? 'allow_session' : 'allow') : 'deny';
|
|
482
523
|
if (card.kind === 'failed') {
|
|
@@ -484,9 +525,23 @@ export async function surfaceToolApprovalFrameAndRespond(frame, respond, streamA
|
|
|
484
525
|
}
|
|
485
526
|
try {
|
|
486
527
|
// abort 后的 deny 仍要送达(引擎侧同款 cancel-by-deny)——不带已 aborted 的 signal。
|
|
528
|
+
// #225 件1:卡决断携带的 persistRule 只在「确是帧候选之一的原文」时上 wire —— server 本就
|
|
529
|
+
// 按表拒(rule_not_offered),这道核对把坏卡口实现挡在包边界并留痕(丢键不丢决断:人的
|
|
530
|
+
// allow/deny 已经落定,一条对不上的规则文本不该反过来污染整次回决)。deny 恒不带(server
|
|
531
|
+
// 宽收后忽略,发了只留「像是记住了」的错觉)。
|
|
532
|
+
let persistRule;
|
|
533
|
+
if (card.kind === 'allow' && typeof card.persistRule === 'string' && card.persistRule !== '' && decision !== 'deny') {
|
|
534
|
+
if (ruleSuggestions?.some(sug => sug.rule === card.persistRule) === true) {
|
|
535
|
+
persistRule = card.persistRule;
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
hostLog('error', `liveToolApprovalWire: DROPPING persistRule for ${frame.approvalId} — the card returned a rule that is not among the frame's suggestions (never sending un-offered text to the rule store)`);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
487
541
|
const raw = await respond(frame.approvalId, decision, {
|
|
488
542
|
...(signal && !signal.aborted ? { signal } : {}),
|
|
489
543
|
...(card.kind === 'allow' && card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}),
|
|
544
|
+
...(persistRule !== undefined ? { persistRule } : {}),
|
|
490
545
|
});
|
|
491
546
|
// 🔴 相关性门(对抗复审二轮):ack 必须是**这一次**审批的回执 —— id 与决断词都要对上。
|
|
492
547
|
// 对不上的 ack(注入面串了别人的响应 / 坏 mock / raw-fetch 包装器复用了连接)如果照样被
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
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,12 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@sema-agent/agent-types": ">=0.2.0",
|
|
31
|
-
"@sema-agent/sdk": ">=6.
|
|
31
|
+
"@sema-agent/sdk": ">=6.14.0"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@sema-agent/agent-types": "^0.2.0",
|
|
35
35
|
"@sema-agent/core": "^5.20.0",
|
|
36
|
-
"@sema-agent/sdk": "^6.
|
|
36
|
+
"@sema-agent/sdk": "^6.14.0",
|
|
37
37
|
"esbuild": "^0.27.4",
|
|
38
38
|
"typescript": "^6.0.2"
|
|
39
39
|
}
|