@sema-agent/client-core 0.8.0 → 0.9.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 +18 -6
- package/dist/adapt.js +12 -48
- package/dist/compensations.d.ts +2 -1
- package/dist/compensations.js +71 -1
- package/dist/hitl/approvalsFeed.d.ts +102 -0
- package/dist/hitl/approvalsFeed.js +185 -0
- package/dist/hitl/askGateWire.d.ts +155 -0
- package/dist/hitl/askGateWire.js +530 -0
- package/dist/hitl/hitlBridge.d.ts +245 -0
- package/dist/hitl/hitlBridge.js +267 -0
- package/dist/hitl/planReviewWire.d.ts +17 -0
- package/dist/hitl/planReviewWire.js +142 -0
- package/dist/hitl/toolApprovalWire.d.ts +126 -0
- package/dist/hitl/toolApprovalWire.js +263 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +36 -0
- package/dist/notifications.d.ts +67 -0
- package/dist/notifications.js +94 -0
- package/dist/subagent/engineTaskHandleWire.d.ts +0 -1
- package/dist/subagent/engineTaskHandleWire.js +4 -3
- package/package.json +2 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B7 批搬迁(2026-07-27,设计稿 §3 B7):cli `src/sema/liveHitlAskWire.ts`(658 行)**整条进包**
|
|
3
|
+
* (设计稿表 §2.5:`S/liveHitlAskWire.ts | 659 | utils/debug · utils/messages(dyn) | B7`)。
|
|
4
|
+
*
|
|
5
|
+
* 🔴 搬迁差分(逐条,零行为变化;每条都在 pure 门 B7 段有断言):
|
|
6
|
+
* 1. `logForDebugging(x)` / `logForDebugging(x,{level:'error'})` → `hostLog('debug'|'error', x)`
|
|
7
|
+
* —— 本包零 `utils/debug`(portability 门盯着)。
|
|
8
|
+
* 2. `require('../utils/messages.js').REJECT_MESSAGE` 的 lazy require + 内联字面量兜底
|
|
9
|
+
* → 直接用常量 `HITL_REJECT_MESSAGE`(**逐字**同一串)。lazy require 存在的理由是「wire 模块
|
|
10
|
+
* 别拉 React/ink 图」,在包里没有那张图 ⇒ 理由消失,只留常量。
|
|
11
|
+
* 🔴 常量漂移锁:pure 门 B7 段拿壳树 `src/utils/messages.ts` 的 `REJECT_MESSAGE` 做
|
|
12
|
+
* byte-identity 对拍(壳树缺席 ⇒ DEGRADED 打印,不假绿)。
|
|
13
|
+
* 3. `getAppStateStoreRef()` 直写 `AppState.notifications` → `HitlHostSurface` 口
|
|
14
|
+
* (`showNotice` / `clearNoticeIfCurrent`)—— `AppState` 是**端的状态形状**。
|
|
15
|
+
* 同理 `surfaceClassifierDeny`(footer 通知 + /permissions Recent Denials 记账,两者都是壳资产)
|
|
16
|
+
* 也走这个口。判定半场(`classifierDenyFromToolEnd`)B2 就已在包内。
|
|
17
|
+
* 4. `liveToolApprovalWire` 的引用 → 包内 `./toolApprovalWire.js`(同批拆搬)。
|
|
18
|
+
* 5. `liveQuestionStore` → 包内同名模块(B1 已搬,**同一个 module 台账**)。
|
|
19
|
+
*
|
|
20
|
+
* 🔴 单实例纪律:本文件自身无 module 级台账,但它**读**两个包内台账
|
|
21
|
+
* (`liveQuestionStore` 的 responder 表 / `toolApprovalWire` 的卡口)。壳侧本地副本与 npm 包
|
|
22
|
+
* 同时进 bundle ⇒ overlay responder 注册在一份、`publishQuestionFrame` 发到另一份 = 对话框
|
|
23
|
+
* 弹出来但没人收答(静默挂死)。
|
|
24
|
+
*
|
|
25
|
+
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* liveHitlAskWire — §4④ AskUserQuestion 的 suspended↔overlay 桥(engine 1.163 HITL 语义,2026-07-12)。
|
|
28
|
+
*
|
|
29
|
+
* WIRE 全链(engine 1.163.0 × SDK 0.0.44,askq-wire-probe 逐字实证 2026-07-12):
|
|
30
|
+
* 1. 模型调 AskUserQuestion → 引擎 DURABLE_APPROVAL park:sync leg(POST /v1/tasks/stream)上先发
|
|
31
|
+
* `tool_start`(args 带完整 questions payload)、再发被 park 毒化的 `tool_end isError:true
|
|
32
|
+
* output:"Operation aborted"`(同 turn 其它并发工具也一并 abort,模型 resume 后自己重发),
|
|
33
|
+
* 终帧 `done` `status:"suspended"` + `checkpointGate:{kind:"human",toolName:"AskUserQuestion"}`,流结束。
|
|
34
|
+
* 🔴 1.61 时代的 `event: question` 帧已蒸发(SDK 0.0.44 连 questions 资源都没了)——
|
|
35
|
+
* demuxQuestionFrames→overlay 旧链收不到任何东西,turn 尾裸渲 `⎿ Error: Operation aborted`。
|
|
36
|
+
* 2. 本模块(liveClient 流管道内层)拦下这个 park:HOLD 毒化 tool_end(不渲)、吞 suspended done、
|
|
37
|
+
* 从 `GET /v1/approvals` 拿 pending 行(input=questions + boundCallId/boundInputHash 绑定)、
|
|
38
|
+
* 合成 QuestionFrame 借既有 AskUserQuestion overlay(planReviewWire 同款 local-responder 姿势,
|
|
39
|
+
* CC 原生对话框,零新 UI)、等用户作答。
|
|
40
|
+
* 3. 决断走 T23 HitlBridge(答案 ride `ApprovalDecision.answer`,D-1 绑定 verbatim 回显;拒答=
|
|
41
|
+
* cancel-by-deny,contract/04 §2.4)。decide 是 SYNC 驱动的:引擎跑到下一个 park 或终态才返
|
|
42
|
+
* (实测 4-5s+),返回体 `{status}` 即下一状态。
|
|
43
|
+
* 4. 续流:attach `GET /v1/runs/:taskId/events`(durable leg;实证 durable log 从 park 点才开始,
|
|
44
|
+
* 无挂起前重放)。重放的 tool_start/tool_end 按 toolCallId 去重;被 gate 的 call 在重放里带来
|
|
45
|
+
* `tool_end isError:false` = 卡片正常收口(HOLD 的毒化帧被丢弃)。模型再次提问时 durable 流发
|
|
46
|
+
* `suspended` arm(带 gate)后流结束 → 同一循环再走一遍,`lastEventId` 续传防重放。
|
|
47
|
+
*
|
|
48
|
+
* 🔴 fail-soft 铁律:桥内任何一步失败(overlay 未挂载/print 模式、pending 蒸发、decide 409/404、
|
|
49
|
+
* turn 中断)都回退到「flush HOLD 的毒化帧 + 原样吐 suspended 终帧」= 今天的诚实红,绝不更糟。
|
|
50
|
+
* 🔴 路由面([816] 壳侧承诺① 放宽,2026-07-14):AskUserQuestion gate 走问答 overlay(原路);
|
|
51
|
+
* fs 写权限 gate(Write/Edit/NotebookEdit,或一等 kind==='tool_approval')走 liveToolApprovalWire
|
|
52
|
+
* 的 CC 三选卡(vendored PermissionRequest);其余 human/irreversible gate 保持现状透传。
|
|
53
|
+
* 🔴 UNTRUSTED:questions 为模型作文(service 已 redact),只渲染绝不回喂;答案由 core 围栏
|
|
54
|
+
* (`selected ⊆ options`,off-list 进 note)。
|
|
55
|
+
*/
|
|
56
|
+
import type { AgentEvent } from '@sema-agent/sdk';
|
|
57
|
+
import { type HitlClientLike } from './hitlBridge.js';
|
|
58
|
+
import { type QuestionAnswer } from '../liveQuestionStore.js';
|
|
59
|
+
import { type RespondToolApprovalFn } from './toolApprovalWire.js';
|
|
60
|
+
import { type ClassifierDenyVerdict } from '../classifierVerdictWire.js';
|
|
61
|
+
/** footer 通知(壳 `AppState.notifications.current` 的形)。 */
|
|
62
|
+
export interface HitlNotice {
|
|
63
|
+
key: string;
|
|
64
|
+
text: string;
|
|
65
|
+
color: 'warning';
|
|
66
|
+
priority: 'immediate';
|
|
67
|
+
timeoutMs: number;
|
|
68
|
+
}
|
|
69
|
+
/** HITL 的宿主副作用面 —— 通知上屏 + 分类器 deny 的端侧记账。 */
|
|
70
|
+
export interface HitlHostSurface {
|
|
71
|
+
/** 立即顶到 current(壳:`notifications.current = notice`,queue 不动)。 */
|
|
72
|
+
showNotice(notice: HitlNotice): void;
|
|
73
|
+
/** **仅当** current 仍是这个 key 时清掉(壳原文的 if-still-mine 语义 —— 否则会误清别人的通知)。 */
|
|
74
|
+
clearNoticeIfCurrent(key: string): void;
|
|
75
|
+
/** 分类器 deny 裁决的宿主副作用:footer 通知 + /permissions Recent Denials 记账(壳资产)。 */
|
|
76
|
+
surfaceClassifierDeny(toolName: string, verdict: ClassifierDenyVerdict): void;
|
|
77
|
+
}
|
|
78
|
+
/** 装 HITL 宿主面(传 null 卸)。返回还原函数。 */
|
|
79
|
+
export declare function installHitlHostSurface(surface: HitlHostSurface | null): () => void;
|
|
80
|
+
/** 🔴 宿主自检:恒应为 0。非 0 = 有 HITL 副作用发生时口不在,那一行 warn / 那条记账丢了。 */
|
|
81
|
+
export declare function hitlHostSurfaceMisses(): number;
|
|
82
|
+
/** 测试钩:卸口 + 清计数。 */
|
|
83
|
+
export declare function _resetHitlHostSurfaceForTest(): void;
|
|
84
|
+
/**
|
|
85
|
+
* CC `utils/messages.ts` 的 `REJECT_MESSAGE` **逐字**(搬迁差分 2)。deny 后重放 tool_end 的
|
|
86
|
+
* render 面 stamp 用 —— vendored `renderToolUseRejectedMessage` 渲 `User rejected <op> to <path>`。
|
|
87
|
+
* 🔴 与壳树那份的 byte-identity 由 pure 门 B7 段锁住(壳树缺席 ⇒ DEGRADED,不假绿)。
|
|
88
|
+
*/
|
|
89
|
+
export declare const HITL_REJECT_MESSAGE = "The user doesn't want to proceed with this tool use. The tool use was rejected (eg. if it was a file edit, the new_string was NOT written to the file). STOP what you are doing and wait for the user to tell you how to proceed.";
|
|
90
|
+
/** 本桥消费的 wire 面(@sema-ai/sdk AgentClient 的结构切片,mock 可注入)。 */
|
|
91
|
+
export interface AskGateWireDeps {
|
|
92
|
+
/** approvals.list/decide + assistant(HitlBridge 的 client 切片)。 */
|
|
93
|
+
client: HitlClientLike;
|
|
94
|
+
/** GET /v1/runs/:id/events — decide 后的续流 attach(lastEventId 续传)。 */
|
|
95
|
+
runsEvents: (taskId: string, opts?: {
|
|
96
|
+
signal?: AbortSignal;
|
|
97
|
+
lastEventId?: string;
|
|
98
|
+
}) => AsyncGenerator<AgentEvent>;
|
|
99
|
+
/** POST /v1/tool-approvals/:id/respond(server 1.191 同步帧腿,[830]①)。缺省=不消费
|
|
100
|
+
* tool_approval 帧(帧被吞、引擎按自身 fail-closed TTL 自决)——mock/旧引擎路径零影响。 */
|
|
101
|
+
respondToolApproval?: RespondToolApprovalFn;
|
|
102
|
+
}
|
|
103
|
+
/** CC AskUserQuestion outputSchema 形状(答过的问题卡):toolResult 的 `ask-user-question` arm
|
|
104
|
+
* 消费它渲真实答案卡(否则「诚实缺席」路径会把这张卡渲成结果不可用)。 */
|
|
105
|
+
export interface AskAnsweredOutput {
|
|
106
|
+
type: 'ask-user-question';
|
|
107
|
+
questions: unknown[];
|
|
108
|
+
answers: Record<string, string>;
|
|
109
|
+
annotations?: Record<string, {
|
|
110
|
+
notes?: string;
|
|
111
|
+
}>;
|
|
112
|
+
}
|
|
113
|
+
export type GateOutcome = {
|
|
114
|
+
kind: 'decided';
|
|
115
|
+
gatedCallId?: string;
|
|
116
|
+
answered?: AskAnsweredOutput;
|
|
117
|
+
} | {
|
|
118
|
+
kind: 'aborted';
|
|
119
|
+
gatedCallId?: string;
|
|
120
|
+
} | {
|
|
121
|
+
kind: 'failed';
|
|
122
|
+
gatedCallId?: string;
|
|
123
|
+
reason: string;
|
|
124
|
+
};
|
|
125
|
+
/** 把 wire 答案({answers:[{header,selected,note?}]})折回 CC 卡片的 Record<question,string> 形状
|
|
126
|
+
* (multiSelect 与对话框同款 ", " lossy join;note → annotations.notes)。 */
|
|
127
|
+
export declare function toAnsweredOutput(questions: unknown[], answer: QuestionAnswer): AskAnsweredOutput;
|
|
128
|
+
/** cancel-by-deny 的后台 settle 预算。decide 是 SYNC 驱动的(引擎跑到下一 park/终态才返,实测
|
|
129
|
+
* 4-5s+),但 DENY-abort 语义上引擎收到即终结 run;2s 内连收都没收到 ⇒ 按丢失警示(晚到成功
|
|
130
|
+
* 只是多一行良性 warn,比锁死无线索诚实)。 */
|
|
131
|
+
export declare const CANCEL_DENY_BUDGET_MS = 2000;
|
|
132
|
+
/** warn 行文案(测试锁字面)。 */
|
|
133
|
+
export declare const CANCEL_DENY_WARN_TEXT = "could not cancel the pending question \u2014 the session may stay locked; the run may need engine-side recovery";
|
|
134
|
+
/**
|
|
135
|
+
* 中断 deny 的有界观察(壳侧单测 `hitlCancelDeny.test.ts` 的被测面)。
|
|
136
|
+
* 铁律:不 await 进 abort 返回路径(用户立即拿回控制);这里只管后台 settle 的«观察»:
|
|
137
|
+
* - 2s 内 settle 成功 ⇒ 零上屏(SEMA_DEBUG 记成功);
|
|
138
|
+
* - 失败/超时 ⇒ 上屏一行 warn + SEMA_DEBUG 记原因(deny 丢失 = run 卡 suspended,下一条消息
|
|
139
|
+
* 撞 409;配合件2b 的专属文案,用户知道现场 + 出路);
|
|
140
|
+
* - HitlSafetyError code==='no_pending' ⇒ 良性静默(pending 已被别处消解/过期 —— run 没锁;
|
|
141
|
+
* hitlBridge decideTool 的同款语义,那里的静默维持不动)。
|
|
142
|
+
*/
|
|
143
|
+
export declare function observeCancelByDeny(settle: Promise<unknown>, taskId: string): void;
|
|
144
|
+
/**
|
|
145
|
+
* 包一层 AgentEvent 流:把 AskUserQuestion 的 suspended park 变成「对话框 → decide → 续流」闭环。
|
|
146
|
+
* 其它事件原样透传;非 AskUserQuestion 的 gate 保持现状。fail-soft:任何桥内失败回退为
|
|
147
|
+
* 「flush 毒化 tool_end + 原样终帧」(= 修复前行为)。
|
|
148
|
+
*
|
|
149
|
+
* @param source 上游 AgentEvent 流(tasks.stream 或 runs.events,已过 demuxQuestionFrames)。
|
|
150
|
+
* @param opts.taskId runs.events 消费时已知的 run handle(sync leg 从 suspended done 捕获)。
|
|
151
|
+
*/
|
|
152
|
+
export declare function bridgeAskUserQuestionGates(source: AsyncGenerator<AgentEvent>, deps: AskGateWireDeps, opts?: {
|
|
153
|
+
signal?: AbortSignal;
|
|
154
|
+
taskId?: string;
|
|
155
|
+
}): AsyncGenerator<AgentEvent>;
|