@sema-agent/client-core 0.12.1 → 0.13.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 +15 -2
- package/dist/abortableSleep.d.ts +29 -0
- package/dist/abortableSleep.js +43 -0
- package/dist/adapt/arms.d.ts +58 -0
- package/dist/adapt/arms.js +613 -0
- package/dist/adapt/ids.d.ts +22 -0
- package/dist/adapt/ids.js +34 -0
- package/dist/adapt/instanceLedger.d.ts +36 -0
- package/dist/adapt/instanceLedger.js +50 -0
- package/dist/adapt/panelTasks.d.ts +54 -0
- package/dist/adapt/panelTasks.js +193 -0
- package/dist/adapt/textStream.d.ts +49 -0
- package/dist/adapt/textStream.js +141 -0
- package/dist/adapt/toolCards.d.ts +65 -0
- package/dist/adapt/toolCards.js +100 -0
- package/dist/adapt/turnFlags.d.ts +33 -0
- package/dist/adapt/turnFlags.js +55 -0
- package/dist/adapt/wireShapes.d.ts +93 -0
- package/dist/adapt/wireShapes.js +167 -0
- package/dist/adapt.d.ts +32 -59
- package/dist/adapt.js +78 -1206
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +55 -13
- package/dist/adapter/downstream/eventToSdkMessage.js +166 -106
- package/dist/adapter/downstream/terminalToSdkResult.js +149 -160
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +36 -0
- package/dist/adapter/downstream/turnUsageToModelUsage.js +34 -6
- package/dist/adapter/runStream.d.ts +29 -4
- package/dist/adapter/runStream.js +129 -13
- package/dist/adapter/types.d.ts +2 -2
- package/dist/agentSession/backgroundView.js +4 -15
- package/dist/agentsWireCaps.d.ts +10 -6
- package/dist/agentsWireCaps.js +21 -7
- package/dist/argvFlagValue.d.ts +41 -0
- package/dist/argvFlagValue.js +69 -0
- package/dist/attachmentsWireCaps.d.ts +3 -2
- package/dist/attachmentsWireCaps.js +5 -3
- package/dist/classifierVerdictWire.d.ts +8 -0
- package/dist/classifierVerdictWire.js +8 -0
- package/dist/cloudConfigWireCaps.d.ts +28 -1
- package/dist/cloudConfigWireCaps.js +51 -11
- package/dist/controlRouter.d.ts +14 -8
- package/dist/controlRouter.js +15 -21
- package/dist/detachWire.d.ts +15 -5
- package/dist/detachWire.js +17 -7
- package/dist/effortWire.d.ts +0 -21
- package/dist/effortWire.js +6 -20
- package/dist/engineInlineTaskStats.d.ts +12 -6
- package/dist/engineWireSdk.d.ts +12 -0
- package/dist/env/localeGeo.js +2 -1
- package/dist/envFlag.d.ts +39 -0
- package/dist/envFlag.js +51 -0
- package/dist/finalVerifyWire.d.ts +7 -5
- package/dist/finalVerifyWire.js +6 -5
- package/dist/fleet/fleetLedger.d.ts +32 -9
- package/dist/fleet/fleetLedger.js +119 -34
- package/dist/fleet/fleetProjection.d.ts +44 -6
- package/dist/fleet/fleetProjection.js +52 -10
- package/dist/fleetAgentPanelProjection.d.ts +18 -1
- package/dist/fleetAgentPanelProjection.js +61 -14
- package/dist/forkWireCaps.d.ts +2 -1
- package/dist/forkWireCaps.js +5 -11
- package/dist/goalStopHook.d.ts +142 -0
- package/dist/goalStopHook.js +258 -0
- package/dist/headlessPermissionModeWire.d.ts +10 -0
- package/dist/headlessPermissionModeWire.js +24 -21
- package/dist/headlessReconnectWire.d.ts +7 -1
- package/dist/headlessReconnectWire.js +20 -2
- package/dist/hitl/approvalsFeed.js +31 -8
- package/dist/hitl/askGateWire.d.ts +27 -96
- package/dist/hitl/askGateWire.js +69 -546
- package/dist/hitl/frameRouter.d.ts +86 -0
- package/dist/hitl/frameRouter.js +342 -0
- package/dist/hitl/gateLedger.d.ts +107 -0
- package/dist/hitl/gateLedger.js +113 -0
- package/dist/hitl/hitlBridge.d.ts +75 -15
- package/dist/hitl/hitlBridge.js +94 -22
- package/dist/hitl/hitlHostSurface.d.ts +49 -0
- package/dist/hitl/hitlHostSurface.js +155 -0
- package/dist/hitl/parkResolver.d.ts +74 -0
- package/dist/hitl/parkResolver.js +241 -0
- package/dist/hitl/planReviewWire.d.ts +60 -2
- package/dist/hitl/planReviewWire.js +152 -74
- package/dist/hitl/toolApprovalWire.d.ts +67 -4
- package/dist/hitl/toolApprovalWire.js +119 -31
- package/dist/hooksWireCaps.d.ts +1 -82
- package/dist/hooksWireCaps.js +34 -235
- package/dist/host.d.ts +16 -5
- package/dist/index.d.ts +2 -0
- package/dist/index.js +15 -1
- package/dist/interactiveToolsWire.d.ts +15 -4
- package/dist/interactiveToolsWire.js +24 -26
- package/dist/limitsWire.js +7 -40
- package/dist/liveInitToolFace.d.ts +51 -6
- package/dist/liveQuestionStore.d.ts +5 -6
- package/dist/model/providerPresets.js +11 -1
- package/dist/notifications.d.ts +48 -2
- package/dist/notifications.js +223 -46
- package/dist/retainBackgroundWireCaps.d.ts +3 -2
- package/dist/retainBackgroundWireCaps.js +5 -9
- package/dist/sandboxWire.d.ts +9 -31
- package/dist/sandboxWire.js +51 -50
- package/dist/scenarioWire.d.ts +1 -1
- package/dist/scenarioWire.js +25 -36
- package/dist/seam.d.ts +23 -6
- package/dist/seam.js +40 -30
- package/dist/seatContract.d.ts +369 -83
- package/dist/seatContract.js +585 -198
- package/dist/selfOrchestrationWireCaps.d.ts +6 -5
- package/dist/selfOrchestrationWireCaps.js +8 -12
- package/dist/sessionSlot.d.ts +8 -0
- package/dist/sessionSlot.js +1 -0
- package/dist/steering.js +2 -2
- package/dist/subagent/engineTaskHandleWire.d.ts +3 -0
- package/dist/subagent/engineTaskHandleWire.js +17 -2
- package/dist/subagentContentStore.d.ts +5 -5
- package/dist/toolResult.d.ts +89 -8
- package/dist/toolResult.js +99 -30
- package/dist/typePins.d.ts +17 -0
- package/dist/typePins.js +1 -0
- package/dist/ultracodeWireCaps.js +5 -6
- package/dist/unrefTimer.d.ts +19 -0
- package/dist/unrefTimer.js +5 -0
- package/dist/workflow.d.ts +3 -2
- package/dist/workflow.js +3 -2
- package/dist/workflowClient.d.ts +7 -0
- package/dist/workflowClient.js +47 -12
- package/package.json +3 -3
|
@@ -28,11 +28,19 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import { permissionModeField } from './permissionWireCaps.js';
|
|
30
30
|
import { hostEnv } from './hostEnv.js';
|
|
31
|
+
import { lastFlagValue } from './argvFlagValue.js';
|
|
31
32
|
/** 部署旋钮 env 键名(settings.json env 块 → 1a-envseed → process.env 同车道)。 */
|
|
32
33
|
export const HEADLESS_PERMISSION_MODE_ENV = 'SEMA_HEADLESS_PERMISSION_MODE';
|
|
33
34
|
/** wire 词表(permissionWireCaps.PermissionMode 的运行时镜像;大小写敏感——与 CC flag choices 一致)。
|
|
34
35
|
* 'auto'(分类器批3,[907]):server ≥1.209 五模式表认;老 server coerce 未知词 → "default" 只紧不松。 */
|
|
35
36
|
const HEADLESS_PERMISSION_WIRE_MODE_TUPLE = ['default', 'plan', 'acceptEdits', 'bypassPermissions', 'auto'];
|
|
37
|
+
/**
|
|
38
|
+
* 🔴 [2006] B-CC-2(2026-07-29):`readonly PermissionMode[]` 的注解只保证「元素合法」,**从不保证
|
|
39
|
+
* 穷举** —— server 五模式表加第六个词、`PermissionMode` 跟了而这张运行时镜像没跟 ⇒ 编译期全绿,
|
|
40
|
+
* 而那个词从 env 旋钮走进来会被判非法、一次性 warn 后**静默回落**(部署以为设了、实际没设)。
|
|
41
|
+
* 补「类型 → 常量」那半边;之前的看守只有 `length > 0 && includes('plan')`(对这个方向零判别力)。
|
|
42
|
+
* `Covers`(REF-CC-145/dup-12,P3 wave1 卡C-argv)改吃共用叶 typePins.ts——四份逐字重复钉之一。
|
|
43
|
+
*/
|
|
36
44
|
const _headlessPermissionModePins = [true, true];
|
|
37
45
|
void _headlessPermissionModePins;
|
|
38
46
|
export const HEADLESS_PERMISSION_WIRE_MODES = HEADLESS_PERMISSION_WIRE_MODE_TUPLE;
|
|
@@ -40,33 +48,28 @@ export const HEADLESS_PERMISSION_WIRE_MODES = HEADLESS_PERMISSION_WIRE_MODE_TUPL
|
|
|
40
48
|
* argv 里的 `--permission-mode <v>` / `--permission-mode=<v>`(后者/后出现者赢,commander 同语义)与
|
|
41
49
|
* `--dangerously-skip-permissions`(= bypassPermissions;`--permission-mode` 在场时让位)。
|
|
42
50
|
* 返回 undefined = flag 车道未显式表态。
|
|
51
|
+
*
|
|
52
|
+
* REF-CC-143(dup-07)收编:`--permission-mode` 的取值改吃共用 lastFlagValue(argvFlagValue.ts,
|
|
53
|
+
* `onMissingValue:'latch-previous'`)——旧手写循环对「某一次 occurrence 缺值」的处置是跳过这一次、
|
|
54
|
+
* 锁存(latch)住之前已经取到的合法值,不是把整个旗塌缩成「从没写过」(`--permission-mode plan
|
|
55
|
+
* --permission-mode` 的旧行为是 `mode==='plan'`,不是 `undefined`)。共用叶的缺省策略
|
|
56
|
+
* (`'invalidate'`,sandboxWire/scenarioWire 用)会把这个情形塌缩成「有旗无值」,与本函数原有语义
|
|
57
|
+
* 不符,所以本调用点显式传 `'latch-previous'` 保住旧行为——这才是任务书说的「各调用方保持自己现有
|
|
58
|
+
* 对缺值的处置」(wave1 回炉修复,blocker:此前误用了共用叶的 invalidate 缺省,详见 commit 记账)。
|
|
59
|
+
* `--dangerously-skip-permissions` 的存在性判别不属于「取某旗最后值」这个不变量,单独一个小循环即
|
|
60
|
+
* 可,零重复。
|
|
43
61
|
*/
|
|
44
62
|
export function permissionModeFromArgv(argv) {
|
|
45
|
-
|
|
46
|
-
|
|
63
|
+
const flag = lastFlagValue(argv, '--permission-mode', 'latch-previous');
|
|
64
|
+
if (flag.present && flag.raw !== undefined)
|
|
65
|
+
return flag.raw;
|
|
47
66
|
for (let i = 0; i < argv.length; i++) {
|
|
48
67
|
const a = argv[i];
|
|
49
|
-
if (a === '--
|
|
50
|
-
const v = argv[i + 1];
|
|
51
|
-
if (typeof v === 'string' && !v.startsWith('-')) {
|
|
52
|
-
mode = v;
|
|
53
|
-
i++;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
else if (a?.startsWith('--permission-mode=')) {
|
|
57
|
-
mode = a.slice('--permission-mode='.length);
|
|
58
|
-
}
|
|
59
|
-
else if (a === '--dangerously-skip-permissions') {
|
|
60
|
-
dsp = true;
|
|
61
|
-
}
|
|
62
|
-
else if (a === '--') {
|
|
68
|
+
if (a === '--')
|
|
63
69
|
break; // POSIX 约定:-- 之后不是 flag
|
|
64
|
-
|
|
70
|
+
if (a === '--dangerously-skip-permissions')
|
|
71
|
+
return 'bypassPermissions';
|
|
65
72
|
}
|
|
66
|
-
if (mode !== undefined)
|
|
67
|
-
return mode;
|
|
68
|
-
if (dsp)
|
|
69
|
-
return 'bypassPermissions';
|
|
70
73
|
return undefined;
|
|
71
74
|
}
|
|
72
75
|
// 一次性告警 latch(-p 是 one-shot 进程;测试钩子可重置)
|
|
@@ -56,7 +56,13 @@
|
|
|
56
56
|
* 消费面兜住。
|
|
57
57
|
*/
|
|
58
58
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
59
|
-
/** 每 run 壳级重连预算(design 铁律 5)。cap/416 的 SDK 内部续传不计入(同一次 openEvents)。
|
|
59
|
+
/** 每 run 壳级重连预算(design 铁律 5)。cap/416 的 SDK 内部续传不计入(同一次 openEvents)。
|
|
60
|
+
* 选值:design/HEADLESS-RECONNECT-DESIGN.md §2 铁律 5 只钉了「有界」与这个数,未留更细的推导
|
|
61
|
+
* —— 诚实记账,不倒推一个当时没写下来的公式。可核实的取值依据是本环自身的形状(REF-CC-148
|
|
62
|
+
* 普查记账):每次尝试是两次真网络往返(重试环里的 `probe(deps.baseUrl)` + `deps.openEvents(…)`)
|
|
63
|
+
* 且两次尝试之间**零退避**,预算越大,在一个真死引擎上耗尽这个环、落到诚实判死文案之前的墙钟代价就越高
|
|
64
|
+
* ——2 是「给瞬时抖动一次重试余地」与「死引擎上不过度拖延判死」之间的保守取值,不是精算结果;
|
|
65
|
+
* 若上游把撕裂形态改成更常见的可重连态,这个数可能需要上调(域词表-19 已留此账)。 */
|
|
60
66
|
export declare const HEADLESS_RECONNECT_MAX = 2;
|
|
61
67
|
/** GET /v1/runs/:id 的 run row 终局子集(SDK RunRecord;字段防御性读)。 */
|
|
62
68
|
export interface RunRowLike {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { isSseIdleError, probeEngineAlive } from './sseIdleTriage.js';
|
|
2
2
|
import { hostEnv } from './hostEnv.js';
|
|
3
|
-
/** 每 run 壳级重连预算(design 铁律 5)。cap/416 的 SDK 内部续传不计入(同一次 openEvents)。
|
|
3
|
+
/** 每 run 壳级重连预算(design 铁律 5)。cap/416 的 SDK 内部续传不计入(同一次 openEvents)。
|
|
4
|
+
* 选值:design/HEADLESS-RECONNECT-DESIGN.md §2 铁律 5 只钉了「有界」与这个数,未留更细的推导
|
|
5
|
+
* —— 诚实记账,不倒推一个当时没写下来的公式。可核实的取值依据是本环自身的形状(REF-CC-148
|
|
6
|
+
* 普查记账):每次尝试是两次真网络往返(重试环里的 `probe(deps.baseUrl)` + `deps.openEvents(…)`)
|
|
7
|
+
* 且两次尝试之间**零退避**,预算越大,在一个真死引擎上耗尽这个环、落到诚实判死文案之前的墙钟代价就越高
|
|
8
|
+
* ——2 是「给瞬时抖动一次重试余地」与「死引擎上不过度拖延判死」之间的保守取值,不是精算结果;
|
|
9
|
+
* 若上游把撕裂形态改成更常见的可重连态,这个数可能需要上调(域词表-19 已留此账)。 */
|
|
4
10
|
export const HEADLESS_RECONNECT_MAX = 2;
|
|
5
11
|
/** SEMA_DEBUG 探针(headless `-p` 车道 Ink 未挂载,stderr 可用;绝不带凭证/帧内容)。 */
|
|
6
12
|
function debugLog(msg) {
|
|
@@ -72,10 +78,22 @@ export async function* withHeadlessR1Reconnect(live, deps) {
|
|
|
72
78
|
let attempts = 0;
|
|
73
79
|
let lastSeenSeq; // R1 去重锚(runs.events SSE id = 账本 seq;见模块头选型)
|
|
74
80
|
const probe = deps.probeAlive ?? probeEngineAlive;
|
|
81
|
+
// REF-CC-148(域词表-19):originalTearErr 在环内从不被重写(它是进入本重试环前唯一捕获的
|
|
82
|
+
// 错误对象),所以这个判型是**环不变式**——算一次即可,不必逐轮重算。
|
|
83
|
+
// wave1 回炉复审(codex blocker):求值时机必须排在铁律 3(用户中止恒先行)**之后**才对 main
|
|
84
|
+
// 逐字——main 是「每轮入口先查 aborted,再判型」,若把判型提到循环顶(环外、抢在第一轮的
|
|
85
|
+
// aborted 检查之前),当 originalTearErr 是一个 `.name` getter 会抛的畸形错误对象、且入环时
|
|
86
|
+
// signal 已经 aborted 时,新顺序会先让 isSseIdleError 抛出**那个畸形对象的二次异常**,盖过
|
|
87
|
+
// 本该抛出的 originalTearErr,违反铁律 3。改法:惰性 memo——用 `undefined` 起始态占位「尚未算
|
|
88
|
+
// 过」(合法返回值只有 true/false),真正求值仍只发生一次,但**排在每轮的 aborted 检查之后**,
|
|
89
|
+
// 与 main 的求值顺序逐字一致,「只判一次」的不变量原样保留。
|
|
90
|
+
let isRecoverableTear;
|
|
75
91
|
for (;;) {
|
|
76
92
|
if (deps.signal?.aborted)
|
|
77
93
|
throw originalTearErr; // 铁律 3:用户中止恒先行,绝不进重连分支
|
|
78
|
-
if (
|
|
94
|
+
if (isRecoverableTear === undefined)
|
|
95
|
+
isRecoverableTear = isSseIdleError(originalTearErr);
|
|
96
|
+
if (!isRecoverableTear)
|
|
79
97
|
throw originalTearErr; // 只救 engine-alive-idle 的静默撕裂形
|
|
80
98
|
const taskId = deps.getTaskId();
|
|
81
99
|
if (!taskId)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hostLog } from '../host.js';
|
|
2
|
+
import { unrefTimer } from '../unrefTimer.js';
|
|
2
3
|
/**
|
|
3
4
|
* pending 列表的稳定摘要 —— 只在**内容变了**的时候发快照。
|
|
4
5
|
* 键里带 `boundInputHash`:同一条 pending 的绑定被服务端换掉(TOCTOU 场景)也算变化,
|
|
@@ -36,10 +37,9 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
|
|
|
36
37
|
const stats = {
|
|
37
38
|
pushEvents: 0, heartbeats: 0, polls: 0, listErrors: 0, streamFailures: 0, snapshots: 0,
|
|
38
39
|
};
|
|
40
|
+
/** 域词表-14 收编:「怎么调 unref」的单一实现见 unrefTimer.ts,本函数只是就地起个短名。 */
|
|
39
41
|
function arm(t) {
|
|
40
|
-
;
|
|
41
|
-
t.unref?.();
|
|
42
|
-
return t;
|
|
42
|
+
return unrefTimer(t);
|
|
43
43
|
}
|
|
44
44
|
async function take(via) {
|
|
45
45
|
if (stopped)
|
|
@@ -91,25 +91,44 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
|
|
|
91
91
|
await take('poll');
|
|
92
92
|
if (mode === 'poll')
|
|
93
93
|
schedulePoll();
|
|
94
|
-
})()
|
|
94
|
+
})().catch(e => {
|
|
95
|
+
// REF-CC-024 ①:take() 理论上自己兜底(见 take() 内的 try/catch),但这是它逃逸时的最后一道
|
|
96
|
+
// 接盘 —— 缺了它,轮询定时器的回调体抛出会变成 unhandled rejection(Node 默认致命)。
|
|
97
|
+
hostLog('debug', `approvalsFeed: poll leg threw unexpectedly (take() should have self-caught): ${String(e)}`);
|
|
98
|
+
});
|
|
95
99
|
}, pollIntervalMs));
|
|
96
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* 与 schedulePoll 对称(域词表-18):**幂等卫兵**(retryTimer 已在飞则不再排一个)+ **回调首行
|
|
103
|
+
* 自清**(否则 retryTimer 触发后仍持有已死句柄,degradeToPoll 若被再次调用会直接覆盖它而不清掉
|
|
104
|
+
* 旧的——旧的照样触发一次 `void runStream()`,两条 push 腿并行 = 双倍取件)。
|
|
105
|
+
*/
|
|
97
106
|
function scheduleStreamRetry() {
|
|
98
107
|
if (stopped || ac.signal.aborted || streamRetryMs <= 0)
|
|
99
108
|
return;
|
|
100
109
|
if (!client.approvals.stream)
|
|
101
110
|
return;
|
|
111
|
+
if (retryTimer !== undefined)
|
|
112
|
+
return;
|
|
102
113
|
retryTimer = arm(setTimeout(() => {
|
|
114
|
+
retryTimer = undefined;
|
|
103
115
|
if (stopped || ac.signal.aborted)
|
|
104
116
|
return;
|
|
105
117
|
hostLog('debug', 'approvalsFeed: retrying push leg (降级是暂时的,服务端升级后不该还要重启客户端)');
|
|
106
118
|
void runStream();
|
|
107
119
|
}, streamRetryMs));
|
|
108
120
|
}
|
|
109
|
-
|
|
121
|
+
/**
|
|
122
|
+
* REF-CC-024 ③:`real`(缺省 true)只计**真正的** stream 故障(抛出 / 空转结束)进
|
|
123
|
+
* `stats.streamFailures`。「SDK 根本没有 `approvals.stream` 这个动词」不是失败,是能力缺席
|
|
124
|
+
* (老 SDK / mock)——此前无条件 `++` 会让这类宿主在 t=0 就报 streamFailures=1,而文件头
|
|
125
|
+
* 38-42 正是拿这个数卖「区分『连着但没事发生』与『断了』」,能力缺席被计成故障就说反了。
|
|
126
|
+
*/
|
|
127
|
+
function degradeToPoll(why, real = true) {
|
|
110
128
|
if (stopped)
|
|
111
129
|
return;
|
|
112
|
-
|
|
130
|
+
if (real)
|
|
131
|
+
stats.streamFailures++;
|
|
113
132
|
if (mode !== 'poll') {
|
|
114
133
|
hostLog('debug', `approvalsFeed: push leg unavailable (${why}) — falling back to polling every ${pollIntervalMs}ms`);
|
|
115
134
|
}
|
|
@@ -120,7 +139,7 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
|
|
|
120
139
|
async function runStream() {
|
|
121
140
|
const stream = client.approvals.stream;
|
|
122
141
|
if (!stream) {
|
|
123
|
-
degradeToPoll('SDK has no approvals.stream (old SDK / mock)');
|
|
142
|
+
degradeToPoll('SDK has no approvals.stream (old SDK / mock)', false);
|
|
124
143
|
return;
|
|
125
144
|
}
|
|
126
145
|
try {
|
|
@@ -162,7 +181,11 @@ export function startApprovalsFeed(client, onSnapshot, opts) {
|
|
|
162
181
|
if (client.approvals.stream)
|
|
163
182
|
void runStream();
|
|
164
183
|
else
|
|
165
|
-
degradeToPoll('SDK has no approvals.stream (old SDK / mock)');
|
|
184
|
+
degradeToPoll('SDK has no approvals.stream (old SDK / mock)', false);
|
|
185
|
+
}).catch(e => {
|
|
186
|
+
// REF-CC-024 ①:同款接盘(见 schedulePoll 里那份同款注释)——首帧取件是唯一没被 try/catch
|
|
187
|
+
// 包住的调用点之一,这里补上不让它成为 unhandled rejection。
|
|
188
|
+
hostLog('debug', `approvalsFeed: initial take() threw unexpectedly (take() should have self-caught): ${String(e)}`);
|
|
166
189
|
});
|
|
167
190
|
return {
|
|
168
191
|
stop() {
|
|
@@ -22,6 +22,21 @@
|
|
|
22
22
|
* 同时进 bundle ⇒ overlay responder 注册在一份、`publishQuestionFrame` 发到另一份 = 对话框
|
|
23
23
|
* 弹出来但没人收答(静默挂死)。
|
|
24
24
|
*
|
|
25
|
+
* ── REF-CC-032(2026-08-02,P3 A 族压轴卡 askgate-split;设计输入 = P1 lens-giant-split SPLIT-12)──
|
|
26
|
+
*
|
|
27
|
+
* 本文件从 369 行的单函数塌成**驱动 + 公开面**。三刀落在同目录三个新文件,分工与各自头注一致:
|
|
28
|
+
* ① `gateLedger.ts` —— 8 个集合 + 3 个记账标量收成一个台账对象并给出动词。拆分的真约束就是
|
|
29
|
+
* 「同一个容器有内环、外环两个写者」(`heldAskEnds` / `deniedCalls` / `resolvedAnswers` /
|
|
30
|
+
* `endedCalls` 四个各自都有两处写点),收口后容器只剩一个写者。
|
|
31
|
+
* ② `frameRouter.ts` —— 内环帧路由:一帧进、一个 `FrameAction` 出。tool_end 的 5 条分支是
|
|
32
|
+
* **有序**的(分类器 deny 的结构性签名必须先于 fs HOLD/REJECT 判),注册表化后顺序写成数据
|
|
33
|
+
* 并配一条加载期断言钉死。共享词汇(`AskGateWireDeps` / `isAskTool` / 两个 CC 逐字常量 /
|
|
34
|
+
* park 形状谓词)也落在那里 —— 它是内外环都够得着的最低模块。
|
|
35
|
+
* ③ `parkResolver.ts` —— 外环 park 决断:`reattach` 还是 `failsoft`,两条 hostLog 与 hop 预算
|
|
36
|
+
* 全在那里。
|
|
37
|
+
* 本文件只剩:双层驱动循环(内环读流吐帧、外环决断后换流)+ 对外 re-export。**公开面一个名字
|
|
38
|
+
* 都没动**(`export *` 经本文件出去的四个值 + 四个类型全部原位可达)。
|
|
39
|
+
*
|
|
25
40
|
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
26
41
|
*
|
|
27
42
|
* liveHitlAskWire — §4④ AskUserQuestion 的 suspended↔overlay 桥(engine 1.163 HITL 语义,2026-07-12)。
|
|
@@ -54,107 +69,23 @@
|
|
|
54
69
|
* (`selected ⊆ options`,off-list 进 note)。
|
|
55
70
|
*/
|
|
56
71
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
57
|
-
import { type
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
export
|
|
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
|
-
/** W1 带 key 变体:装/卸某会话的 HITL 宿主面(多会话宿主每会话一键,互不顶盖)。 */
|
|
81
|
-
export declare function installHitlHostSurfaceFor(sessionKey: string, surface: HitlHostSurface | null): () => void;
|
|
82
|
-
/** 🔴 宿主自检:恒应为 0。非 0 = 有 HITL 副作用发生时口不在,那一行 warn / 那条记账丢了。 */
|
|
83
|
-
export declare function hitlHostSurfaceMisses(): number;
|
|
84
|
-
/** W1 带 key 变体(miss 计数逐键)。 */
|
|
85
|
-
export declare function hitlHostSurfaceMissesFor(sessionKey: string): number;
|
|
86
|
-
/** W1 读端:某会话的 HITL 宿主面(未装 = null;**不计 miss** —— miss 只在副作用真发生时计)。 */
|
|
87
|
-
export declare function hitlHostSurfaceFor(sessionKey: string): HitlHostSurface | null;
|
|
88
|
-
/** 测试钩:卸口 + 清计数。 */
|
|
89
|
-
export declare function _resetHitlHostSurfaceForTest(): void;
|
|
90
|
-
/**
|
|
91
|
-
* CC `utils/messages.ts` 的 `REJECT_MESSAGE` **逐字**(搬迁差分 2)。deny 后重放 tool_end 的
|
|
92
|
-
* render 面 stamp 用 —— vendored `renderToolUseRejectedMessage` 渲 `User rejected <op> to <path>`。
|
|
93
|
-
* 🔴 pure 门 B7 段:包内冻结字面量(无条件)+ 壳树全树扫描「每一处声明都逐字节相同」(壳树缺席=跳过)。
|
|
94
|
-
*/
|
|
95
|
-
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.";
|
|
96
|
-
/** 本桥消费的 wire 面(@sema-ai/sdk AgentClient 的结构切片,mock 可注入)。 */
|
|
97
|
-
export interface AskGateWireDeps {
|
|
98
|
-
/** approvals.list/decide + assistant(HitlBridge 的 client 切片)。 */
|
|
99
|
-
client: HitlClientLike;
|
|
100
|
-
/** GET /v1/runs/:id/events — decide 后的续流 attach(lastEventId 续传)。 */
|
|
101
|
-
runsEvents: (taskId: string, opts?: {
|
|
102
|
-
signal?: AbortSignal;
|
|
103
|
-
lastEventId?: string;
|
|
104
|
-
}) => AsyncGenerator<AgentEvent>;
|
|
105
|
-
/** POST /v1/tool-approvals/:id/respond(server 1.191 同步帧腿,[830]①)。缺省=不消费
|
|
106
|
-
* tool_approval 帧(帧被吞、引擎按自身 fail-closed TTL 自决)——mock/旧引擎路径零影响。 */
|
|
107
|
-
respondToolApproval?: RespondToolApprovalFn;
|
|
108
|
-
}
|
|
109
|
-
/** core 对被 gate/连坐 abort 的 call 铸的 tool_end 载体(逐字;desktop session-host 真引擎实测
|
|
110
|
-
* 同款)——HOLD 谓词锚它做**精确等值**,普通工具错的输出是各自错误文案,永不进 HOLD。 */
|
|
111
|
-
export declare const ENGINE_ABORT_TOOL_RESULT = "Operation aborted";
|
|
112
|
-
/** CC AskUserQuestion outputSchema 形状(答过的问题卡):toolResult 的 `ask-user-question` arm
|
|
113
|
-
* 消费它渲真实答案卡(否则「诚实缺席」路径会把这张卡渲成结果不可用)。 */
|
|
114
|
-
export interface AskAnsweredOutput {
|
|
115
|
-
type: 'ask-user-question';
|
|
116
|
-
questions: unknown[];
|
|
117
|
-
answers: Record<string, string>;
|
|
118
|
-
annotations?: Record<string, {
|
|
119
|
-
notes?: string;
|
|
120
|
-
}>;
|
|
121
|
-
}
|
|
122
|
-
export type GateOutcome = {
|
|
123
|
-
kind: 'decided';
|
|
124
|
-
gatedCallId?: string;
|
|
125
|
-
answered?: AskAnsweredOutput;
|
|
126
|
-
} | {
|
|
127
|
-
kind: 'aborted';
|
|
128
|
-
gatedCallId?: string;
|
|
129
|
-
} | {
|
|
130
|
-
kind: 'failed';
|
|
131
|
-
gatedCallId?: string;
|
|
132
|
-
reason: string;
|
|
133
|
-
};
|
|
134
|
-
/** 把 wire 答案({answers:[{header,selected,note?}]})折回 CC 卡片的 Record<question,string> 形状
|
|
135
|
-
* (multiSelect 与对话框同款 ", " lossy join;note → annotations.notes)。 */
|
|
136
|
-
export declare function toAnsweredOutput(questions: unknown[], answer: QuestionAnswer): AskAnsweredOutput;
|
|
137
|
-
/** cancel-by-deny 的后台 settle 预算。decide 是 SYNC 驱动的(引擎跑到下一 park/终态才返,实测
|
|
138
|
-
* 4-5s+),但 DENY-abort 语义上引擎收到即终结 run;2s 内连收都没收到 ⇒ 按丢失警示(晚到成功
|
|
139
|
-
* 只是多一行良性 warn,比锁死无线索诚实)。 */
|
|
140
|
-
export declare const CANCEL_DENY_BUDGET_MS = 2000;
|
|
141
|
-
/** warn 行文案(测试锁字面)。 */
|
|
142
|
-
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";
|
|
143
|
-
/**
|
|
144
|
-
* 中断 deny 的有界观察(壳侧单测 `hitlCancelDeny.test.ts` 的被测面)。
|
|
145
|
-
* 铁律:不 await 进 abort 返回路径(用户立即拿回控制);这里只管后台 settle 的«观察»:
|
|
146
|
-
* - 2s 内 settle 成功 ⇒ 零上屏(SEMA_DEBUG 记成功);
|
|
147
|
-
* - 失败/超时 ⇒ 上屏一行 warn + SEMA_DEBUG 记原因(deny 丢失 = run 卡 suspended,下一条消息
|
|
148
|
-
* 撞 409;配合件2b 的专属文案,用户知道现场 + 出路);
|
|
149
|
-
* - HitlSafetyError code==='no_pending' ⇒ 良性静默(pending 已被别处消解/过期 —— run 没锁;
|
|
150
|
-
* hitlBridge decideTool 的同款语义,那里的静默维持不动)。
|
|
151
|
-
*/
|
|
152
|
-
export declare function observeCancelByDeny(settle: Promise<unknown>, taskId: string): void;
|
|
72
|
+
import { type AskGateWireDeps } from './frameRouter.js';
|
|
73
|
+
export { HITL_REJECT_MESSAGE, ENGINE_ABORT_TOOL_RESULT } from './frameRouter.js';
|
|
74
|
+
export type { AskGateWireDeps } from './frameRouter.js';
|
|
75
|
+
export type { AskAnsweredOutput } from './gateLedger.js';
|
|
76
|
+
export { toAnsweredOutput, isAlreadyResolvedGateReason } from './parkResolver.js';
|
|
77
|
+
export type { GateOutcome, GateFailureCode } from './parkResolver.js';
|
|
153
78
|
/**
|
|
154
79
|
* 包一层 AgentEvent 流:把 AskUserQuestion 的 suspended park 变成「对话框 → decide → 续流」闭环。
|
|
155
80
|
* 其它事件原样透传;非 AskUserQuestion 的 gate 保持现状。fail-soft:任何桥内失败回退为
|
|
156
81
|
* 「flush 毒化 tool_end + 原样终帧」(= 修复前行为)。
|
|
157
82
|
*
|
|
83
|
+
* 双层循环(REF-CC-032 后只剩驱动骨架):
|
|
84
|
+
* · 内环 `for await` 读流 —— 每帧先记 seq、再判扣留帧放行、再交 `routeFrame` 定处置;
|
|
85
|
+
* `park` 动作 break 出内环。
|
|
86
|
+
* · 外环 `while` —— `resolvePark` 决断:`reattach` 就换一条 `runs.events` 流接着读
|
|
87
|
+
* (hop 预算在 resolver 内按 `hops` 判),`failsoft` 就吐完 events 收场。
|
|
88
|
+
*
|
|
158
89
|
* @param source 上游 AgentEvent 流(tasks.stream 或 runs.events,已过 demuxQuestionFrames)。
|
|
159
90
|
* @param opts.taskId runs.events 消费时已知的 run handle(sync leg 从 suspended done 捕获)。
|
|
160
91
|
*/
|