@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
|
@@ -1,3 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B7 批搬迁(2026-07-27,设计稿 §3 B7):cli `src/sema/liveToolApprovalWire.ts`(508 行)——
|
|
3
|
+
* **拆**(设计稿原文:「liveToolApprovalWire.ts | 509 | …… | **拆分**,B7;工具 schema 属
|
|
4
|
+
* agent-types 域」)。
|
|
5
|
+
*
|
|
6
|
+
* ── 拆缝在哪 ────────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
* **进包(本文件)**:gate 判定(`isFsApprovalGate` / `toolNameIsFsWrite`)· 结构等值
|
|
8
|
+
* (`structurallyEqual`,ctrl+g amend 的「真编辑过」判据)· 同步帧契约与判别式
|
|
9
|
+
* (`ToolApprovalFrame` / `isToolApprovalFrame` / `isFromSubagent`)· 徽章合成
|
|
10
|
+
* (`subagentBadgeFor`)· gate 文案兜 path(`pathFromGateMessage`)· **两条决断腿的编排**
|
|
11
|
+
* (`surfaceFsApprovalAndDecide` durable-park 腿 / `surfaceToolApprovalFrameAndRespond` 同步帧腿)。
|
|
12
|
+
* **留宿主**:**卡本身**(`surfaceApprovalCard`)—— 它要 leader `ToolUseConfirm` 队列、壳的
|
|
13
|
+
* vendored `Tool` 注册表(`FileWriteTool`/`FileEditTool`/`NotebookEditTool`/`EngineWorkflowTool`)、
|
|
14
|
+
* `applyPermissionUpdates` 与 `AppState.toolPermissionContext`。那是**端的渲染与权限态**,
|
|
15
|
+
* web/桌面各有各的形(浏览器根本没有 Ink 三选卡)。落点 = 下面的 `ApprovalCardPort`。
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **拆缝的失效形**([paired-mechanisms-must-share-premise]):卡口没装 ⇒ 两条腿都拿不到人的
|
|
18
|
+
* 决断。**这不是「静默不启用」可以接受的面** —— 同步帧腿上引擎正同步阻塞着,不 respond 就干等
|
|
19
|
+
* TTL;durable 腿上 run 卡在 suspended。所以本口:
|
|
20
|
+
* · **计 miss**(`approvalCardPortMisses()`,宿主自检恒应为 0);
|
|
21
|
+
* · 缺席时返回 `{kind:'failed', reason:'no approval card port installed …'}` —— 与壳原文
|
|
22
|
+
* 「no leader ToolUseConfirm queue registered(print/non-REPL mode)」**同一条 fail-soft 语义**
|
|
23
|
+
* (print/headless 本来就没有卡面),调用方照旧走 fail-closed deny / 诚实红。
|
|
24
|
+
*
|
|
25
|
+
* 🔴 搬迁差分(逐条,零行为变化):
|
|
26
|
+
* 1. `toolForName`(4 个 `require('../tools/…')`)整条留宿主 —— 工具注册表是壳资产。
|
|
27
|
+
* 2. `getLeaderToolUseConfirmQueue` / `getLeaderSetToolPermissionContext` / `applyPermissionUpdates` /
|
|
28
|
+
* `getAppStateStoreRef` / `syntheticAssistantMessage` / `stubToolUseContext` 全部随卡留宿主。
|
|
29
|
+
* 3. `logForDebugging` → `hostLog('debug'|'error', …)`(本包零 `utils/debug`,portability 门盯着)。
|
|
30
|
+
* 4. `require('./fleetClient.js').readEngineActiveBgTasks()` → 静态 import 包内
|
|
31
|
+
* `fleet/fleetLedger.js` 的同名读口(B6 已搬入;延迟 require 的理由「纯 wire 模块不背 fleet 图
|
|
32
|
+
* 冷启动」在包内不成立 —— 同一个包)。⚠️ try/catch 保留:台账不可用时兜 taskId 短形那一支
|
|
33
|
+
* 仍要成立。
|
|
34
|
+
*
|
|
35
|
+
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
36
|
+
*
|
|
37
|
+
* liveToolApprovalWire — 权限三选卡的壳侧半场([816] 壳侧承诺① / [819]② / [820] 四模式表,2026-07-14)。
|
|
38
|
+
*
|
|
39
|
+
* WIRE(server 1.191 已到货,[830]①):引擎对 fs 写工具(Write/Edit/NotebookEdit)出 ask ——
|
|
40
|
+
* 真腿=同步 `tool_approval` 帧(named SSE,redactDeep+16KiB 帽)→ 三选卡 → POST
|
|
41
|
+
* /v1/tool-approvals/:id/respond {allow|allow_session|deny}(本文件下半)。durable park 腿
|
|
42
|
+
* (checkpointGate → approvals.decide)🔴 **现势(REF-CC-023/031,2026-08-02)**:不是「旧引擎兜底」——
|
|
43
|
+
* [2150] S1 半场(83-87 行)证实产品默认 durable 投递下,引擎对 Bash 出的 durable park 就是**活路径**
|
|
44
|
+
* (r1 E5 fail-stuck 实例);synthApprovalProbe 合成单测仍在,但这条腿今天会被真用户真流量走到。
|
|
45
|
+
* 本模块把 gate 面翻成壳的 VENDORED CC 权限对话框(PermissionRequest → FileWritePermissionRequest,
|
|
46
|
+
* 三选文案逐字 CC 207:`Yes` / `Yes, allow all edits during this session (shift+tab)`[cwd 外变体带
|
|
47
|
+
* dirname] / `No`,题干 `Do you want to create <basename>?` verb=create/overwrite/write to)——零新 UI,
|
|
48
|
+
* 键行为(1/2/3、shift+tab 聚焦第 2 项、esc=reject)全部继承 vendor 组件。
|
|
49
|
+
*
|
|
50
|
+
* 决断腿:
|
|
51
|
+
* - Yes(accept-once)→ HitlBridge.decideTool({decision:'approve'})(D-1 绑定 verbatim,合 [819]②)。
|
|
52
|
+
* - Yes-allow-all(accept-session)→ 同 approve,且 permissionUpdates 里 vendor 产出的
|
|
53
|
+
* `{type:'setMode', mode:'acceptEdits', destination:'session'}` 经 leader setter 落壳 AppState →
|
|
54
|
+
* seamQuery 的 permissionModeField 下一 turn 起 stamp `permissionMode:"acceptEdits"`([816] 壳侧
|
|
55
|
+
* 承诺②:accept-session=升 intent,wire 已通)。cwd 外变体还带 addDirectories(session)——照 CC 收。
|
|
56
|
+
* - No(reject)→ decideTool({decision:'deny'});调用方(liveHitlAskWire)对该 call 的重放 tool_end
|
|
57
|
+
* stamp REJECT_MESSAGE,vendor 的 renderToolUseRejectedMessage 渲 `User rejected <op> to <path>`
|
|
58
|
+
* (subtle 色,CC 逐字)。
|
|
59
|
+
*
|
|
60
|
+
* 🔴 fail-soft 铁律(与 ask 桥同款):leader 队列未注册(print/非 REPL)、pending 蒸发、decide 409/404
|
|
61
|
+
* 都返回 failed —— 调用方回退「flush + 原样终帧」的诚实红,绝不更糟。
|
|
62
|
+
* 🔴 UNTRUSTED:gate args 为模型作文(service 已 redact),只渲染绝不回喂;决断只带 decision 枚举。
|
|
63
|
+
*/
|
|
1
64
|
import { type HitlClientLike } from './hitlBridge.js';
|
|
2
65
|
/** fs 写权限 gate 判定:未来的一等 kind(tool_approval)或按 toolName(server 桥首批=fs 写三件,
|
|
3
66
|
* [820] 表)。AskUserQuestion 永不进这里(ask 桥先判)。 */
|
|
@@ -19,14 +82,14 @@ export declare function isToolApprovalGate(gate: {
|
|
|
19
82
|
} | undefined | null): boolean;
|
|
20
83
|
export type FsApprovalOutcome = {
|
|
21
84
|
kind: 'decided';
|
|
22
|
-
gatedCallId?: string;
|
|
23
|
-
denied?: boolean;
|
|
85
|
+
gatedCallId?: string | undefined;
|
|
86
|
+
denied?: boolean | undefined;
|
|
24
87
|
} | {
|
|
25
88
|
kind: 'aborted';
|
|
26
|
-
gatedCallId?: string;
|
|
89
|
+
gatedCallId?: string | undefined;
|
|
27
90
|
} | {
|
|
28
91
|
kind: 'failed';
|
|
29
|
-
gatedCallId?: string;
|
|
92
|
+
gatedCallId?: string | undefined;
|
|
30
93
|
reason: string;
|
|
31
94
|
};
|
|
32
95
|
/** 本桥消费的 wire 面(liveHitlAskWire 的 AskGateWireDeps 同形切片,mock 可注入)。 */
|
|
@@ -1,7 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B7 批搬迁(2026-07-27,设计稿 §3 B7):cli `src/sema/liveToolApprovalWire.ts`(508 行)——
|
|
3
|
+
* **拆**(设计稿原文:「liveToolApprovalWire.ts | 509 | …… | **拆分**,B7;工具 schema 属
|
|
4
|
+
* agent-types 域」)。
|
|
5
|
+
*
|
|
6
|
+
* ── 拆缝在哪 ────────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
* **进包(本文件)**:gate 判定(`isFsApprovalGate` / `toolNameIsFsWrite`)· 结构等值
|
|
8
|
+
* (`structurallyEqual`,ctrl+g amend 的「真编辑过」判据)· 同步帧契约与判别式
|
|
9
|
+
* (`ToolApprovalFrame` / `isToolApprovalFrame` / `isFromSubagent`)· 徽章合成
|
|
10
|
+
* (`subagentBadgeFor`)· gate 文案兜 path(`pathFromGateMessage`)· **两条决断腿的编排**
|
|
11
|
+
* (`surfaceFsApprovalAndDecide` durable-park 腿 / `surfaceToolApprovalFrameAndRespond` 同步帧腿)。
|
|
12
|
+
* **留宿主**:**卡本身**(`surfaceApprovalCard`)—— 它要 leader `ToolUseConfirm` 队列、壳的
|
|
13
|
+
* vendored `Tool` 注册表(`FileWriteTool`/`FileEditTool`/`NotebookEditTool`/`EngineWorkflowTool`)、
|
|
14
|
+
* `applyPermissionUpdates` 与 `AppState.toolPermissionContext`。那是**端的渲染与权限态**,
|
|
15
|
+
* web/桌面各有各的形(浏览器根本没有 Ink 三选卡)。落点 = 下面的 `ApprovalCardPort`。
|
|
16
|
+
*
|
|
17
|
+
* 🔴 **拆缝的失效形**([paired-mechanisms-must-share-premise]):卡口没装 ⇒ 两条腿都拿不到人的
|
|
18
|
+
* 决断。**这不是「静默不启用」可以接受的面** —— 同步帧腿上引擎正同步阻塞着,不 respond 就干等
|
|
19
|
+
* TTL;durable 腿上 run 卡在 suspended。所以本口:
|
|
20
|
+
* · **计 miss**(`approvalCardPortMisses()`,宿主自检恒应为 0);
|
|
21
|
+
* · 缺席时返回 `{kind:'failed', reason:'no approval card port installed …'}` —— 与壳原文
|
|
22
|
+
* 「no leader ToolUseConfirm queue registered(print/non-REPL mode)」**同一条 fail-soft 语义**
|
|
23
|
+
* (print/headless 本来就没有卡面),调用方照旧走 fail-closed deny / 诚实红。
|
|
24
|
+
*
|
|
25
|
+
* 🔴 搬迁差分(逐条,零行为变化):
|
|
26
|
+
* 1. `toolForName`(4 个 `require('../tools/…')`)整条留宿主 —— 工具注册表是壳资产。
|
|
27
|
+
* 2. `getLeaderToolUseConfirmQueue` / `getLeaderSetToolPermissionContext` / `applyPermissionUpdates` /
|
|
28
|
+
* `getAppStateStoreRef` / `syntheticAssistantMessage` / `stubToolUseContext` 全部随卡留宿主。
|
|
29
|
+
* 3. `logForDebugging` → `hostLog('debug'|'error', …)`(本包零 `utils/debug`,portability 门盯着)。
|
|
30
|
+
* 4. `require('./fleetClient.js').readEngineActiveBgTasks()` → 静态 import 包内
|
|
31
|
+
* `fleet/fleetLedger.js` 的同名读口(B6 已搬入;延迟 require 的理由「纯 wire 模块不背 fleet 图
|
|
32
|
+
* 冷启动」在包内不成立 —— 同一个包)。⚠️ try/catch 保留:台账不可用时兜 taskId 短形那一支
|
|
33
|
+
* 仍要成立。
|
|
34
|
+
*
|
|
35
|
+
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
36
|
+
*
|
|
37
|
+
* liveToolApprovalWire — 权限三选卡的壳侧半场([816] 壳侧承诺① / [819]② / [820] 四模式表,2026-07-14)。
|
|
38
|
+
*
|
|
39
|
+
* WIRE(server 1.191 已到货,[830]①):引擎对 fs 写工具(Write/Edit/NotebookEdit)出 ask ——
|
|
40
|
+
* 真腿=同步 `tool_approval` 帧(named SSE,redactDeep+16KiB 帽)→ 三选卡 → POST
|
|
41
|
+
* /v1/tool-approvals/:id/respond {allow|allow_session|deny}(本文件下半)。durable park 腿
|
|
42
|
+
* (checkpointGate → approvals.decide)🔴 **现势(REF-CC-023/031,2026-08-02)**:不是「旧引擎兜底」——
|
|
43
|
+
* [2150] S1 半场(83-87 行)证实产品默认 durable 投递下,引擎对 Bash 出的 durable park 就是**活路径**
|
|
44
|
+
* (r1 E5 fail-stuck 实例);synthApprovalProbe 合成单测仍在,但这条腿今天会被真用户真流量走到。
|
|
45
|
+
* 本模块把 gate 面翻成壳的 VENDORED CC 权限对话框(PermissionRequest → FileWritePermissionRequest,
|
|
46
|
+
* 三选文案逐字 CC 207:`Yes` / `Yes, allow all edits during this session (shift+tab)`[cwd 外变体带
|
|
47
|
+
* dirname] / `No`,题干 `Do you want to create <basename>?` verb=create/overwrite/write to)——零新 UI,
|
|
48
|
+
* 键行为(1/2/3、shift+tab 聚焦第 2 项、esc=reject)全部继承 vendor 组件。
|
|
49
|
+
*
|
|
50
|
+
* 决断腿:
|
|
51
|
+
* - Yes(accept-once)→ HitlBridge.decideTool({decision:'approve'})(D-1 绑定 verbatim,合 [819]②)。
|
|
52
|
+
* - Yes-allow-all(accept-session)→ 同 approve,且 permissionUpdates 里 vendor 产出的
|
|
53
|
+
* `{type:'setMode', mode:'acceptEdits', destination:'session'}` 经 leader setter 落壳 AppState →
|
|
54
|
+
* seamQuery 的 permissionModeField 下一 turn 起 stamp `permissionMode:"acceptEdits"`([816] 壳侧
|
|
55
|
+
* 承诺②:accept-session=升 intent,wire 已通)。cwd 外变体还带 addDirectories(session)——照 CC 收。
|
|
56
|
+
* - No(reject)→ decideTool({decision:'deny'});调用方(liveHitlAskWire)对该 call 的重放 tool_end
|
|
57
|
+
* stamp REJECT_MESSAGE,vendor 的 renderToolUseRejectedMessage 渲 `User rejected <op> to <path>`
|
|
58
|
+
* (subtle 色,CC 逐字)。
|
|
59
|
+
*
|
|
60
|
+
* 🔴 fail-soft 铁律(与 ask 桥同款):leader 队列未注册(print/非 REPL)、pending 蒸发、decide 409/404
|
|
61
|
+
* 都返回 failed —— 调用方回退「flush + 原样终帧」的诚实红,绝不更糟。
|
|
62
|
+
* 🔴 UNTRUSTED:gate args 为模型作文(service 已 redact),只渲染绝不回喂;决断只带 decision 枚举。
|
|
63
|
+
*/
|
|
64
|
+
import { HitlBridge, findPendingForTask } from './hitlBridge.js';
|
|
2
65
|
import { hostLog } from '../host.js';
|
|
3
66
|
import { createSessionSlot, DEFAULT_SESSION_KEY } from '../sessionSlot.js';
|
|
4
67
|
import { readEngineActiveBgTasks } from '../fleet/fleetLedger.js';
|
|
68
|
+
import { observeCancelByDeny } from './hitlHostSurface.js';
|
|
5
69
|
/** fs 写权限 gate 判定:未来的一等 kind(tool_approval)或按 toolName(server 桥首批=fs 写三件,
|
|
6
70
|
* [820] 表)。AskUserQuestion 永不进这里(ask 桥先判)。 */
|
|
7
71
|
export function isFsApprovalGate(gate) {
|
|
@@ -105,13 +169,32 @@ export function surfaceApprovalCard(req) {
|
|
|
105
169
|
const cardPort = approvalCardPortFor(DEFAULT_SESSION_KEY);
|
|
106
170
|
if (cardPort === null) {
|
|
107
171
|
bumpCardPortMiss(DEFAULT_SESSION_KEY);
|
|
108
|
-
return Promise.resolve({
|
|
109
|
-
kind: 'failed',
|
|
110
|
-
reason: 'no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)',
|
|
111
|
-
});
|
|
172
|
+
return Promise.resolve({ kind: 'failed', reason: cardPortMissReason() });
|
|
112
173
|
}
|
|
113
174
|
return cardPort(req);
|
|
114
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* REF-CC-030(2026-08-02):三个决断入口(`surfaceApprovalCard` 本身 / `surfaceFsApprovalAndDecide` /
|
|
178
|
+
* `surfaceToolApprovalFrameAndRespond`,后两者都经 `surfaceApprovalCard`)今天全硬读
|
|
179
|
+
* DEFAULT_SESSION_KEY——keyed 卡口 API(`installApprovalCardPortFor` 等)建好了但没有任何调用点
|
|
180
|
+
* 带非默认键透传。多会话宿主如果照 W1 文档按会话键装口,会 100% miss 在默认键上,而它那边
|
|
181
|
+
* 「我装了啊」——原文案 `host forgot installApprovalCardPort` 会把这个键不匹配误导成没装。
|
|
182
|
+
* 这里在 miss 发生时顺手核一眼「是不是有别的键装了口」,点名两种不同的失效,而不需要先把三个
|
|
183
|
+
* 决断入口的签名都改成 `sessionKey?: string` 透传(那是更大的一次改动,候 W1 消费者真正到位)。
|
|
184
|
+
*/
|
|
185
|
+
function cardPortMissReason() {
|
|
186
|
+
// 🔴 判「装了没」必须读**值**,不能只读键:`installApprovalCardPortFor(key, null)` 是文档化的
|
|
187
|
+
// 卸口姿势(签名 `ApprovalCardPort | null`),而 SessionSlot.set 只对 `undefined` 删键 —— 卸完
|
|
188
|
+
// 键还在、值为 null。只看 `keys()` 会把「卸干净了、谁都没装」误诊成「装在别的键上」,恰好把这张
|
|
189
|
+
// 卡要分开说的两种失效说反。
|
|
190
|
+
const otherKeysInstalled = cardPortByKey
|
|
191
|
+
.keys()
|
|
192
|
+
.some((k) => k !== DEFAULT_SESSION_KEY && cardPortByKey.get(k) != null);
|
|
193
|
+
if (otherKeysInstalled) {
|
|
194
|
+
return 'no approval card port installed for the default session key, but a non-default sessionKey has one — likely a keyed-install/default-read mismatch (installApprovalCardPortFor vs the zero-arg default-key read), not a missing install';
|
|
195
|
+
}
|
|
196
|
+
return 'no approval card port installed (print/non-REPL mode, or host forgot installApprovalCardPort)';
|
|
197
|
+
}
|
|
115
198
|
/**
|
|
116
199
|
* 弹 CC 三选卡 → 等决断 → 经 HitlBridge decide(durable park 腿,server 桥前的 checkpointGate 面;
|
|
117
200
|
* 合成单测/旧引擎兜底保留)。decide 成功即引擎驱到下一状态(sync);调用方随后 attach runs.events
|
|
@@ -120,24 +203,20 @@ export function surfaceApprovalCard(req) {
|
|
|
120
203
|
* @param argsByCall tool_start.args(toolCallId → args)——题干/diff 的一手源;缺则 pending.input。
|
|
121
204
|
*/
|
|
122
205
|
export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signal) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// pending 行 = gate 的权威源 + D-1 绑定 surface(与 ask 桥同姿势)。
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}
|
|
138
|
-
if (!pending)
|
|
139
|
-
return { kind: 'failed', reason: 'no pending checkpoint for this run (resolved/expired?)' };
|
|
140
|
-
const gatedCallId = pending.toolCallId ?? pending.boundCallId ?? undefined;
|
|
206
|
+
// REF-CC-031(2026-08-02):卡口缺席判据不在此前置重复一遍 —— `surfaceApprovalCard`(下方调用)
|
|
207
|
+
// 已经计 miss + 出 typed `{kind:'failed', reason:...}`,card.kind==='failed' 走既有汇流分支
|
|
208
|
+
// (下面 switch 的 'failed' 臂);两处各写一份文案会在 SEMA_DEBUG 里出现两种措辞,取决于走的是
|
|
209
|
+
// 哪条腿。文案单源 = `surfaceApprovalCard` 那一份。
|
|
210
|
+
// pending 行 = gate 的权威源 + D-1 绑定 surface(与 ask 桥同姿势)。REF-CC-029(2026-08-02):
|
|
211
|
+
// 单源取件 —— 这里取到的行原样传进下面**四条决断腿的每一次** `bridge.decideTool(...)`
|
|
212
|
+
// (preResolvedPending 入参:aborted / allow+remember / allow 纯批 / deny),不再让 bridge 自己
|
|
213
|
+
// 二次 list(),消掉 hitl2-01/hitl2-08 的 TOCTOU 窗。
|
|
214
|
+
// 🔴 常驻判据不是「看得见 pending 这个实参」,是 pure 门那四条「approvals.list 全程恰好 1 次」——
|
|
215
|
+
// 漏传一条腿,形状断言全绿而计数当场翻红(回炉车前的实况:只有 aborted 臂传了)。
|
|
216
|
+
const found = await findPendingForTask(deps.client, taskId, (toolName) => toolName !== undefined && (toolNameIsFsWrite(toolName) || toolNameIsShellExec(toolName)), signal ? { signal } : undefined);
|
|
217
|
+
if (!found.ok)
|
|
218
|
+
return { kind: 'failed', reason: found.reason };
|
|
219
|
+
const { pending, gatedCallId } = found;
|
|
141
220
|
const toolName = typeof pending.toolName === 'string' ? pending.toolName : '';
|
|
142
221
|
// 工具入参:优先流上 tool_start.args(完整、零额外语义);缺则 pending.input(service 已 redact+限长
|
|
143
222
|
// ——大 Write 的 content 可能被截,对话框 diff 退化但仍可决断)。
|
|
@@ -159,8 +238,10 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
159
238
|
case 'failed':
|
|
160
239
|
return { kind: 'failed', gatedCallId, reason: card.reason };
|
|
161
240
|
case 'aborted':
|
|
162
|
-
// turn 被中断(Esc/Ctrl+C):cancel-by-deny(contract/04 §2.4)
|
|
163
|
-
|
|
241
|
+
// turn 被中断(Esc/Ctrl+C):cancel-by-deny(contract/04 §2.4)。REF-CC-023(2026-08-02):
|
|
242
|
+
// 有界观察(observeCancelByDeny)取代裸 `.catch(()=>{})` 全吞 —— 与 askGateWire.ts 同款
|
|
243
|
+
// reason 文案,同一次事故(件3,中断事故修复批 G,2026-07-15)的两条腿现在共用同一个观察器。
|
|
244
|
+
observeCancelByDeny(bridge.decideTool({ decision: 'deny', reason: 'Interrupted by user' }, gatedCallId, undefined, pending), taskId);
|
|
164
245
|
return { kind: 'aborted', gatedCallId };
|
|
165
246
|
case 'allow':
|
|
166
247
|
try {
|
|
@@ -171,14 +252,14 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
171
252
|
// 修复前行为,fail-soft 不更糟)。
|
|
172
253
|
if (card.allowSession) {
|
|
173
254
|
try {
|
|
174
|
-
await bridge.decideTool({ decision: 'approve', remember: 'session', ...(card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}) }, gatedCallId, signal ? { signal } : undefined);
|
|
255
|
+
await bridge.decideTool({ decision: 'approve', remember: 'session', ...(card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}) }, gatedCallId, signal ? { signal } : undefined, pending);
|
|
175
256
|
return { kind: 'decided', gatedCallId };
|
|
176
257
|
}
|
|
177
258
|
catch (e) {
|
|
178
259
|
hostLog('debug', `liveToolApprovalWire: decide(approve+remember) failed (${String(e)}) — falling back to plain approve`);
|
|
179
260
|
}
|
|
180
261
|
}
|
|
181
|
-
await bridge.decideTool({ decision: 'approve', ...(card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}) }, gatedCallId, signal ? { signal } : undefined);
|
|
262
|
+
await bridge.decideTool({ decision: 'approve', ...(card.updatedInput !== undefined ? { updatedInput: card.updatedInput } : {}) }, gatedCallId, signal ? { signal } : undefined, pending);
|
|
182
263
|
return { kind: 'decided', gatedCallId };
|
|
183
264
|
}
|
|
184
265
|
catch (e) {
|
|
@@ -186,7 +267,7 @@ export async function surfaceFsApprovalAndDecide(deps, taskId, argsByCall, signa
|
|
|
186
267
|
}
|
|
187
268
|
case 'deny':
|
|
188
269
|
try {
|
|
189
|
-
await bridge.decideTool({ decision: 'deny', reason: 'The user rejected this tool use' }, gatedCallId, signal ? { signal } : undefined);
|
|
270
|
+
await bridge.decideTool({ decision: 'deny', reason: 'The user rejected this tool use' }, gatedCallId, signal ? { signal } : undefined, pending);
|
|
190
271
|
return { kind: 'decided', gatedCallId, denied: true };
|
|
191
272
|
}
|
|
192
273
|
catch (e) {
|
|
@@ -229,11 +310,18 @@ export function isToolApprovalFrame(ev) {
|
|
|
229
310
|
typeof e.approvalId === 'string' &&
|
|
230
311
|
e.approvalId.length > 0);
|
|
231
312
|
}
|
|
313
|
+
/** core `fs-write-gate-policy` 的 ask 文案(逐字,具名常量便于对表)。 */
|
|
314
|
+
const FS_WRITE_GATE_ASK_PATTERN = /^approve write to "([^"]+)"\?$/;
|
|
232
315
|
/** 从 gate 的 ask 文案(core fs-write-gate-policy `approve write to "<path>"?`)兜出 path——argsOmitted
|
|
233
|
-
* 且流上无 tool_start.args 时的最后退化(卡仍可弹,diff 退化为空写)。
|
|
316
|
+
* 且流上无 tool_start.args 时的最后退化(卡仍可弹,diff 退化为空写)。
|
|
317
|
+
* REF-CC-035(2026-08-02):锚定 core 的精确文案前缀,不是「任意一对引号」—— 旧正则
|
|
318
|
+
* `/"([^"]+)"/` 对任何含引号的 gate 文案都会命中并**静默给错 path**(不是不命中,是给错值,
|
|
319
|
+
* 而 path 是审批卡展示给用户做安全决策的对象)。锚定失败即诚实缺席(返回 undefined,调用方
|
|
320
|
+
* 落回 `{}` 空写而不是猜一个;[honest-absence-not-fabricated-zero] 同族)。 */
|
|
234
321
|
function pathFromGateMessage(message) {
|
|
235
|
-
|
|
236
|
-
|
|
322
|
+
if (typeof message !== 'string')
|
|
323
|
+
return undefined;
|
|
324
|
+
return FS_WRITE_GATE_ASK_PATTERN.exec(message)?.[1];
|
|
237
325
|
}
|
|
238
326
|
/** 子代审批帧的归属徽章([1535] cli 半场):名一手源=帧上 sourceAgentName(core 1.378 RB-39②
|
|
239
327
|
* 显式展示身份,server redact 后透传,候 server 班车到货即亮);缺席退 fleet 在飞台账行名
|
package/dist/hooksWireCaps.d.ts
CHANGED
|
@@ -1,87 +1,6 @@
|
|
|
1
|
-
import { type EnvLike } from './hostEnv.js';
|
|
2
1
|
import type { WireHooksConfig } from './finalVerifyWire.js';
|
|
2
|
+
export { GOAL_STOP_HOOK_WIRE_ENV, CC_STOP_SEMANTICS_MIN_SERVER, ccStopSemanticsFromVersion, engineCcStopSemantics, isGoalStopHookWireArmed, setWireSessionStopHook, getWireSessionStopHook, getWireSessionStopHookCcSemantics, buildGoalStopHookPrompt, } from './goalStopHook.js';
|
|
3
3
|
export type { WireHooksConfig };
|
|
4
|
-
/** The env ESCAPE HATCH for the engine leg (see the block comment above). */
|
|
5
|
-
export declare const GOAL_STOP_HOOK_WIRE_ENV = "SEMA_GOAL_STOP_HOOK_WIRE";
|
|
6
|
-
export declare const CC_STOP_SEMANTICS_MIN_SERVER = "1.279.3";
|
|
7
|
-
/**
|
|
8
|
-
* 引擎自报版本是否达到 CC Stop 语义线({@link CC_STOP_SEMANTICS_MIN_SERVER})。
|
|
9
|
-
*
|
|
10
|
-
* 🔴 三条纪律:
|
|
11
|
-
* · **数值比较**,不是字典序(`1.9.0` < `1.279.0`,字符串比较会判反);
|
|
12
|
-
* · **预发版不算达标**(`1.279.0-rc.1` 里那套可能还在改)——正则只认三段纯数字,带 `-` 的直接落
|
|
13
|
-
* null ⇒ false;
|
|
14
|
-
* · 读不出/探不到 ⇒ **false**(降级方向恒安全:老形在新引擎上仍能拦,新形在老引擎上一次都拦不住)。
|
|
15
|
-
*/
|
|
16
|
-
export declare function ccStopSemanticsFromVersion(version: string | undefined | null): boolean;
|
|
17
|
-
/**
|
|
18
|
-
* 本会话连着的引擎是否具备 CC Stop 语义?判据 = live 车道的 `/v1/capabilities` 自报 `version`
|
|
19
|
-
* (engineCapsCache 的 boot-kick 缓存,与 `/health.version` 同源)。
|
|
20
|
-
*
|
|
21
|
-
* 🔴 「未判即降级」:caps 探测是构造期 fire-and-forget,`/goal` 真被敲下时通常早已落袋;万一没落袋
|
|
22
|
-
* 就按老引擎走(meta 也同步退档)——**宁可少承诺,不可多承诺**。
|
|
23
|
-
*/
|
|
24
|
-
export declare function engineCcStopSemantics(env?: EnvLike): boolean;
|
|
25
|
-
/**
|
|
26
|
-
* Is the `/goal` Stop-hook engine leg armed? **Default ON** since 2026-07-26 — both of the premises
|
|
27
|
-
* that kept it opt-in were disproved by server-side behavior evidence ([1721]) and re-verified against
|
|
28
|
-
* a real engine + real model (cli scripts/run-goal-stop-hook-live-test.mjs). Set
|
|
29
|
-
* `SEMA_GOAL_STOP_HOOK_WIRE=0|false|no|off` to disarm (an operator escape hatch, not a default).
|
|
30
|
-
*/
|
|
31
|
-
export declare function isGoalStopHookWireArmed(env?: EnvLike): boolean;
|
|
32
|
-
/**
|
|
33
|
-
* Register (or clear, with `null`) the session-scoped `/goal` Stop prompt hook that rides
|
|
34
|
-
* `settings.hooks.Stop` to the engine. Single-valued: a new goal replaces the previous one,
|
|
35
|
-
* exactly like CC 220's `Xdr` (remove-then-add). Projection is gated by
|
|
36
|
-
* {@link isGoalStopHookWireArmed} — storing is always safe, sending is not (yet).
|
|
37
|
-
*/
|
|
38
|
-
export declare function setWireSessionStopHook(prompt: string | null, opts?: {
|
|
39
|
-
/**
|
|
40
|
-
* 这条目标登记时,引擎是否已判定为 CC 语义档({@link engineCcStopSemantics})?
|
|
41
|
-
*
|
|
42
|
-
* 🔴 **为什么要在 set 那一刻锁存,而不是在 {@link hooksForWire} 里现读**:`/goal` 的 meta
|
|
43
|
-
* (对模型讲「会不会拦到条件成立为止」)是在 set 那一刻发出去的,而投影发生在随后每个 turn。
|
|
44
|
-
* 两处各读一次的话,caps 探测中途落袋就会造出「meta 承诺拦到底、投出去的却还是老门」的错配 ——
|
|
45
|
-
* 判据必须锚在**真正决定结果的那个量**上,而它只有一个:**这条目标登记时的档位**。
|
|
46
|
-
* 缺省 false ⇒ 不传就是老引擎档:翻档只能靠证据打开,不能靠默认值。
|
|
47
|
-
*/
|
|
48
|
-
ccSemantics?: boolean;
|
|
49
|
-
}): void;
|
|
50
|
-
/** The currently registered `/goal` Stop prompt, or null (registered ≠ projected — see the gate). */
|
|
51
|
-
export declare function getWireSessionStopHook(): string | null;
|
|
52
|
-
/** 当前这条目标登记时锁存的档位(真 = 投裸条件,假 = 投壳自造的门)。 */
|
|
53
|
-
export declare function getWireSessionStopHookCcSemantics(): boolean;
|
|
54
|
-
/**
|
|
55
|
-
* 把 `/goal` 的**原始条件**包成一条对引擎 prompt 载体**可执行**的 Stop 门 prompt。
|
|
56
|
-
*
|
|
57
|
-
* 🔴 适用面(2026-07-26 提货批后):**只用于未达标线的引擎档**(见 {@link CC_STOP_SEMANTICS_MIN_SERVER})。
|
|
58
|
-
* 达标档起引擎自己按 CC
|
|
59
|
-
* 220 逐字组装(系统提示 + 会话转录 + `wrapCondition`),那一档投的是**裸条件** —— 见
|
|
60
|
-
* {@link ccStopSemanticsFromVersion} 与 {@link hooksForWire} 的分道。本函数保留,是 tolerate-absent
|
|
61
|
-
* 的实体:老引擎上退回它,仍能拦一次;删掉它等于在老引擎上静默失效。
|
|
62
|
-
*
|
|
63
|
-
* 🔴 为什么必须包(2026-07-26 live 围栏实测,不是设计偏好):CC 单进程的 prompt 钩子执行器
|
|
64
|
-
* (`src/utils/hooks/execPromptHook.ts`)会给条件**套一层系统提示**(「You are evaluating a hook…
|
|
65
|
-
* 回 `{ok:true}` / `{ok:false,reason}`」)**并把整段会话前置**,所以裸条件在 CC 那边能被真评估。
|
|
66
|
-
* 引擎侧的载体两样都没有:
|
|
67
|
-
* · 无系统提示、无会话历史;
|
|
68
|
-
* · Stop payload 的 `transcript_path` 是**空串**(server basePayload),评估者对本会话是**瞎的**;
|
|
69
|
-
* · 判据 schema 也不同 —— core 只认 stdout JSON 的 `decision:"block"`。
|
|
70
|
-
* 实测(钉版 server 1.277.1 + core 1.416 + 真模型):裸条件 `the project contains a file named
|
|
71
|
-
* DONE.txt` 送过去,载体照样被调用,模型回的是**散文**(「I don't have direct access to your file
|
|
72
|
-
* system…」)⇒ `parseHookStdout` 拿不到 decision ⇒ **一次都不拦**。即:不包 = 每次停机白烧一次
|
|
73
|
-
* 模型调用、零效果。
|
|
74
|
-
*
|
|
75
|
-
* 所以这里把条件包成引擎契约里**能落地**的形状。诚实标注它**够不到 CC 的完整语义**:
|
|
76
|
-
* · 能做到:每个 turn 的**第一次**停机尝试被拦下,目标原文重新注入给模型,逼它自查后再收尾
|
|
77
|
-
* (`stop_hook_active` 为真时门自己收手 ⇒ 不会无限拦停);真正「条件是否成立」的判断交给
|
|
78
|
-
* **有会话、有工具的主模型**,而不是让瞎的评估者假装判断;
|
|
79
|
-
* · 做不到:CC 那种「拦到条件真成立为止」(需要引擎把 transcript 交给载体)、以及「条件满足后
|
|
80
|
-
* 自动清除目标」。这两条是**等件**,meta 文案因此不照抄 CC `Pwo`(见 cmd-goal.tsx)。
|
|
81
|
-
*
|
|
82
|
-
* 台账仍存**原始条件**({@link getWireSessionStopHook}),所以 `/goal clear` 读回的是用户写的那句。
|
|
83
|
-
*/
|
|
84
|
-
export declare function buildGoalStopHookPrompt(condition: string): string;
|
|
85
4
|
/**
|
|
86
5
|
* The merged, GOVERNANCE-GATED settings-file hooks for the wire, or undefined when none apply.
|
|
87
6
|
* Per-event arrays concatenate across sources (policy → user → project → local).
|