@sema-agent/client-core 0.12.2 → 0.14.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 +67 -0
- package/dist/adapt/arms.js +623 -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 +59 -0
- package/dist/adapt/panelTasks.js +193 -0
- package/dist/adapt/textStream.d.ts +63 -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 +37 -0
- package/dist/adapt/turnFlags.js +55 -0
- package/dist/adapt/wireShapes.d.ts +96 -0
- package/dist/adapt/wireShapes.js +167 -0
- package/dist/adapt.d.ts +33 -60
- package/dist/adapt.js +84 -1207
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +57 -13
- package/dist/adapter/downstream/eventToSdkMessage.js +174 -106
- package/dist/adapter/downstream/terminalToSdkResult.js +151 -160
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +36 -0
- package/dist/adapter/downstream/turnUsageToModelUsage.js +34 -6
- package/dist/adapter/runStream.d.ts +36 -4
- package/dist/adapter/runStream.js +195 -13
- package/dist/adapter/types.d.ts +28 -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.d.ts +22 -1
- package/dist/hitl/approvalsFeed.js +103 -9
- package/dist/hitl/askGateWire.d.ts +27 -96
- package/dist/hitl/askGateWire.js +69 -618
- package/dist/hitl/frameRouter.d.ts +86 -0
- package/dist/hitl/frameRouter.js +383 -0
- package/dist/hitl/gateLedger.d.ts +119 -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 +250 -0
- package/dist/hitl/planReviewWire.d.ts +60 -2
- package/dist/hitl/planReviewWire.js +197 -91
- package/dist/hitl/toolApprovalWire.d.ts +67 -4
- package/dist/hitl/toolApprovalWire.js +129 -31
- package/dist/hooksWireCaps.d.ts +1 -82
- package/dist/hooksWireCaps.js +44 -237
- 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 +18 -6
- package/dist/liveQuestionStore.js +15 -0
- package/dist/model/providerPresets.js +11 -1
- package/dist/notifications.d.ts +62 -2
- package/dist/notifications.js +306 -49
- package/dist/printToolResultFrame.d.ts +12 -4
- package/dist/printToolResultFrame.js +11 -21
- 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 +30 -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
package/dist/hooksWireCaps.js
CHANGED
|
@@ -21,8 +21,13 @@
|
|
|
21
21
|
*
|
|
22
22
|
* 🔴 模块级状态:`wireSessionStopHookPrompt` / `wireSessionStopHookCcSemantics`(`/goal` 的会话级
|
|
23
23
|
* Stop 钩子锁存)——写口 `setWireSessionStopHook`(命令层)、读口 `hooksForWire`(每 turn 请求
|
|
24
|
-
* 构造期)与 `getWireSessionStopHook`(`/goal clear` 回显)
|
|
25
|
-
*
|
|
24
|
+
* 构造期)与 `getWireSessionStopHook`(`/goal clear` 回显)。两份实例 = 设了目标却投不出去
|
|
25
|
+
* (而两边代码看着都对)。单实例清单登记项。
|
|
26
|
+
* ⇄ REF-CC-156(midband-04,split,2026-08-02):这份状态 + 版本判档 + prompt 组装已拆到
|
|
27
|
+
* `./goalStopHook.ts`(原文件那一半占体量 57%、是独立关注点——`/goal` 命令专属,与本文件剩下
|
|
28
|
+
* 的「通用 settings-file hooks 投影 + 治理门」不是同一件事)。本文件只在 `hooksForWire()` 尾部
|
|
29
|
+
* 调 `goalStopHookMatcher()` 合流一次;所有对外名字原样从这里 re-export,调用方 import 路径
|
|
30
|
+
* 零改动。
|
|
26
31
|
*
|
|
27
32
|
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
28
33
|
*
|
|
@@ -58,218 +63,29 @@
|
|
|
58
63
|
* shell to scrub here (commands are the user's own text).
|
|
59
64
|
*/
|
|
60
65
|
import { hostLog, hostSettings } from './host.js';
|
|
61
|
-
|
|
62
|
-
|
|
66
|
+
// REF-CC-156(split):`/goal` 版本判档 + prompt 组装 + 模块状态搬到 goalStopHook.ts。
|
|
67
|
+
// `goalStopHookMatcher`/`__resetGoalStopHookForTests` 是两文件间的私有接口(不 re-export);
|
|
68
|
+
// 其余七个名字原样 re-export,外部 import 路径(index 的 `export *`)零改动。
|
|
69
|
+
import { goalStopHookMatcher, __resetGoalStopHookForTests } from './goalStopHook.js';
|
|
70
|
+
export { GOAL_STOP_HOOK_WIRE_ENV, CC_STOP_SEMANTICS_MIN_SERVER, ccStopSemanticsFromVersion, engineCcStopSemantics, isGoalStopHookWireArmed, setWireSessionStopHook, getWireSessionStopHook, getWireSessionStopHookCcSemantics, buildGoalStopHookPrompt, } from './goalStopHook.js';
|
|
63
71
|
const EDITABLE_HOOK_SOURCES = ['userSettings', 'projectSettings', 'localSettings'];
|
|
72
|
+
/**
|
|
73
|
+
* REF-CC-155(midband-03,fix-outright):此前这里是静默 `catch { return null }` —— 零 hostLog、零
|
|
74
|
+
* 注释说明「为什么这次失败可以死」。settings 读取失败(格式损坏 / 端内部错误)会被悄悄当成
|
|
75
|
+
* 「该来源没配 hooks」处理,与真实的「没配」在结果上不可分辨,排查只能靠猜。补一行 debug 记录被吞的
|
|
76
|
+
* 异常(与本文件其余分支的记账习惯一致);这一处仍按「无 hooks」降级 —— 单来源读失败不是安全门
|
|
77
|
+
* (信任门/policy 门另有专门处置,见 hooksForWire),不必 fail-closed 整条腿。
|
|
78
|
+
*/
|
|
64
79
|
function hooksOfSource(source) {
|
|
65
80
|
try {
|
|
66
81
|
const s = hostSettings()?.getSettingsForSource(source);
|
|
67
82
|
return s?.hooks && typeof s.hooks === 'object' && !Array.isArray(s.hooks) ? s.hooks : null;
|
|
68
83
|
}
|
|
69
|
-
catch {
|
|
84
|
+
catch (err) {
|
|
85
|
+
hostLog('debug', `hooksWireCaps: ${source} settings read threw — treated as no hooks from this source: ${String(err)}`);
|
|
70
86
|
return null;
|
|
71
87
|
}
|
|
72
88
|
}
|
|
73
|
-
// ── SESSION-SCOPED Stop hook overlay(`/goal`,任务 #75 · C-7,2026-07-26)───────────────────────
|
|
74
|
-
// CC 220 的 `/goal <condition>` 会往 `sessionHooksRegistry` 注册一条会话级 Stop×prompt 钩子
|
|
75
|
-
// (`t.sessionHooksRegistry.add(sessionId,"Stop","",{type:"prompt",prompt})`),那才是「干到条件满足
|
|
76
|
-
// 再停」的机制半边。CC 是单进程,注册进壳内 registry 就够。
|
|
77
|
-
//
|
|
78
|
-
// 🔴 **sema 是双进程壳,壳侧那条钩子在 live 模式 fire 不到**(实证,不是推测):壳的 Stop 钩子只有
|
|
79
|
-
// `src/query/stopHooks.ts handleStopHooks` 一个执行点,调用方是 `src/query.ts` —— 壳自己的 query
|
|
80
|
-
// 循环;live 模式 turn 循环在引擎里跑,壳的 query.ts 不参与。upstreamBridge 只 fire SubagentStop 且
|
|
81
|
-
// observe-only。所以引擎腿必须走 `settings.hooks` 投影,这个 overlay 就是那条腿。
|
|
82
|
-
//
|
|
83
|
-
// 🟢 **本 overlay 默认开**(2026-07-26 翻默认;逃生口 `SEMA_GOAL_STOP_HOOK_WIRE=0/false/no/off`)。
|
|
84
|
-
//
|
|
85
|
-
// 修前默认关,理由是两条**前提**——两条都已被 server 侧行为取证**证伪**(黑板 [1721],6 条行为
|
|
86
|
-
// 用例已进 server 仓;cli 仓另有 scripts/run-goal-stop-hook-live-test.mjs 的真引擎+真模型复验):
|
|
87
|
-
// ① 旧论断「SDK 类型 DEFERRED ⇒ 服务端会 400」**是把类型面当成了运行时校验面**,对 server
|
|
88
|
-
// 不成立:`parseHooksConfig({Stop:[{hooks:[{type:"prompt",prompt}]}]})` 无 error、真解析;
|
|
89
|
-
// prompt 形在 server 的条目白名单里与 command/http/agent **并列**
|
|
90
|
-
// (@sema-agent/server hooks/hook-runner `entry.type !== "command" && … !== "prompt" && …`
|
|
91
|
-
// 那道 type_not_supported 闸口把 prompt 放行,1.277.1 dist 对应 hook-runner.js:390)。
|
|
92
|
-
// 卡着的只是 **SDK / registry-core 的类型**(见下方 cast),与 server 运行时接受与否两件事。
|
|
93
|
-
// ② 旧论断「引擎侧 Stop×prompt 是否真 fire 未取证」已取证:**真 fire**(Stop 触发时 prompt
|
|
94
|
-
// 载体调用一次,`$ARGUMENTS` 已替换为 payload JSON)、**能拦停**(模型输出
|
|
95
|
-
// `{"decision":"block","reason":…}` → `{block: reason}`,core `runner/runtask.js:1663`
|
|
96
|
-
// `(spec.hooks ?? deps.hooks)?.stop` 起 stop gate,整条链通)、非决策 JSON 不拦(与 command
|
|
97
|
-
// 形 exit-0 非 JSON 同语义)、`stop_hook_active:true` 如实进 payload(模型可自己收手;core
|
|
98
|
-
// 另有 STOP_HOOK_BLOCK_CAP=8 连续拦停硬上限兜底,不会无限拦停)。
|
|
99
|
-
// ③ 最坏情况**不是** 400,而是**钩子不生效**:部署没组装 prompt 载体(`ctx.hookLlm` 缺席)⇒
|
|
100
|
-
// server 记账 `hook_entries_skipped{reason:"llm_carrier_unavailable"}` 并跳过,**非阻断**。
|
|
101
|
-
// ⇒ 默认开。等件(不阻断本腿):registry-core hooks 契约建模 prompt 形([1717]① 在飞)、SDK
|
|
102
|
-
// `SettingsHookCommand` 放开 prompt 形 —— 到位后把下方 cast 换成直类型即可。
|
|
103
|
-
//
|
|
104
|
-
// 纪律:
|
|
105
|
-
// - 只放 goal 这一条(单值,不是通用会话钩子通道)——通用会话/插件钩子仍按模块头所述不投影;
|
|
106
|
-
// - 走 hooksForWire() 的**同一套治理门**(trust / disableAllHooks / allowManagedHooksOnly):
|
|
107
|
-
// overlay 在门之后才合并,策略禁钩子时一并禁掉;
|
|
108
|
-
// - `/goal clear` 传 null 收回。
|
|
109
|
-
let wireSessionStopHookPrompt = null;
|
|
110
|
-
/**
|
|
111
|
-
* CC 语义档的**锁存位**(见 {@link setWireSessionStopHook})。投影形按它分道:
|
|
112
|
-
* 真 ⇒ 裸条件(引擎自己包 CC 形);假 ⇒ 壳自造的门 prompt(老引擎唯一能落地的形)。
|
|
113
|
-
*/
|
|
114
|
-
let wireSessionStopHookCcSemantics = false;
|
|
115
|
-
/** The env ESCAPE HATCH for the engine leg (see the block comment above). */
|
|
116
|
-
export const GOAL_STOP_HOOK_WIRE_ENV = 'SEMA_GOAL_STOP_HOOK_WIRE';
|
|
117
|
-
// ── 引擎档位:CC 220 Stop×prompt 完整语义(达标线 = CC_STOP_SEMANTICS_MIN_SERVER,见下)────────
|
|
118
|
-
//
|
|
119
|
-
// server 1.279.0 把 CC 220 的 prompt-hook 半场搬进了引擎(dist/hooks/cc-stop-prompt.js +
|
|
120
|
-
// branch-transcript.js,源码直读):
|
|
121
|
-
// ① Stop×prompt 触发时先取 `sctx.getBranch()`(core 1.419+ 就在 stop gate ctx 里传了它)渲染成
|
|
122
|
-
// 会话转录;**证据面为空则整条 prompt 条目被过滤掉、评估者根本不被调用**
|
|
123
|
-
// (`hook_entries_skipped{reason:"stop_prompt_no_evidence:<why>"}`)——这正是老引擎那条
|
|
124
|
-
// 「白烧一次模型调用、零效果」的反面;
|
|
125
|
-
// ② 调用时带 CC 220 **逐字**的系统提示(`ccPromptSystemFor("Stop")` = CC 的 full 档)+ CC 逐字的
|
|
126
|
-
// 条件包装(`wrapCondition`);
|
|
127
|
-
// ③ 判词 `{ok,reason,impossible}` 在**引擎翻译层**映射:ok ⇒ 放行、impossible ⇒ 记 warn 后**放行**
|
|
128
|
-
// (真 fail-open,既不 block 也不产 additionalContext)、其余 ⇒ block(reason 喂回模型)。
|
|
129
|
-
//
|
|
130
|
-
// ⇒ 到这一档,壳侧唯一正确的动作是把 CC `Xdr` 的**原形(裸条件)**投出去 —— 引擎会自己包。继续投
|
|
131
|
-
// 壳自造的门 prompt 只会被再包一层、并与 CC 系统提示要求的判词 schema 打架。
|
|
132
|
-
// 🔴 但老引擎**没有这套**(1.278.0 的 hooks/ 目录里既无 cc-stop-prompt 也无 branch-transcript,
|
|
133
|
-
// hook-llm 连 `system` 形参都没有):裸条件送过去评估者对本会话是瞎的,回散文 ⇒ 一次都不拦。
|
|
134
|
-
// 所以判档失败/探不到时**必须**退回老形,绝不「因为代码里写了 enforced 就真拦」。
|
|
135
|
-
//
|
|
136
|
-
// 🔴 **为什么达标线钉 1.279.3 而不是 CC 面首次出现的 1.279.0** —— 两道闸,缺一道都不许翻档:
|
|
137
|
-
//
|
|
138
|
-
// ① **core 下限**(1.279.1 才补上):CC Stop 语义在 1.279.0 就在了,但 1.279.1 才把
|
|
139
|
-
// `@sema-agent/core` 的下限提到 `^1.422.0`。core <1.422 上,一条只带 `additionalContext` 的
|
|
140
|
-
// Stop 反馈**不结束 + 不计数 + 清零计数** ⇒ block 与 additionalContext 交替时计数呈锯齿
|
|
141
|
-
// (`[0,1,0,1,0]`)、`STOP_HOOK_BLOCK_CAP` 永远到不了 ⇒ 无界循环;1.422 起它计入上限且不再清零。
|
|
142
|
-
// (注:goal 这条路本身产不出 additionalContext —— CC 判词的 ok/impossible 两支在引擎翻译层都
|
|
143
|
-
// 直接 `continue`;但那条通道对 command 形钩子是开着的,而**上限修好的是最坏值,不是这条通道的
|
|
144
|
-
// 性质**:它仍然会让那一轮不结束。所以措辞面一步都不许放松,见 cmd-goal 的 meta 四档。)
|
|
145
|
-
//
|
|
146
|
-
// ② **判词额度**(1.279.3 才补上):1.279.1/1.279.2 的引擎侧 hook 载体把 `max_tokens` 钳死在
|
|
147
|
-
// `Math.min(…, 1024)`(dist/hooks/hook-llm.js)。而 CC 的评估者系统提示要求「quote evidence
|
|
148
|
-
// from the transcript」,推理档模型的 thinking 还与输出**共用**这份额度 ⇒ 判词被截断(JSON 不
|
|
149
|
-
// 闭合)或干脆零 text 块 ⇒ `parseCcVerdict` 拿不到 ⇒ 引擎**丢弃整条判词并 fail-open 放行**。
|
|
150
|
-
// 🔴 这条缺口的形状与 ① 不同,但危害更隐蔽:它让 enforced 档对模型讲的那两句承诺在**守卫失效
|
|
151
|
-
// 的那些轮里变成空话,而且没有任何人知道**(引擎侧只留一行 `hook_llm_unparsed_verdict` /
|
|
152
|
-
// `hook_llm_failed{returned no content}` 记账,壳侧、用户侧、模型侧全都看不到)。
|
|
153
|
-
// 1.279.3 起 `maxOutputTokens` 由调用方声明,CC 评估者路声明 `CC_EVALUATOR_MAX_OUTPUT_TOKENS`
|
|
154
|
-
// = 4096,并对「无内容」重试一次(**不**重试「有内容但读不懂」)。
|
|
155
|
-
//
|
|
156
|
-
// ⇒ 达标线钉的是「**承诺能兑现**的那个组合」,不是「能力面在场」的那一版。差一档就翻,等于把用户
|
|
157
|
-
// 放到一个「守卫会静默失效」的搭配上,还对着模型把话说满。
|
|
158
|
-
export const CC_STOP_SEMANTICS_MIN_SERVER = '1.279.3';
|
|
159
|
-
/** `x.y.z` → 数值三元组;非法形 ⇒ null(预发标记按「未达标」处理,见下)。 */
|
|
160
|
-
function parseSemverCore(v) {
|
|
161
|
-
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v.trim());
|
|
162
|
-
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* 引擎自报版本是否达到 CC Stop 语义线({@link CC_STOP_SEMANTICS_MIN_SERVER})。
|
|
166
|
-
*
|
|
167
|
-
* 🔴 三条纪律:
|
|
168
|
-
* · **数值比较**,不是字典序(`1.9.0` < `1.279.0`,字符串比较会判反);
|
|
169
|
-
* · **预发版不算达标**(`1.279.0-rc.1` 里那套可能还在改)——正则只认三段纯数字,带 `-` 的直接落
|
|
170
|
-
* null ⇒ false;
|
|
171
|
-
* · 读不出/探不到 ⇒ **false**(降级方向恒安全:老形在新引擎上仍能拦,新形在老引擎上一次都拦不住)。
|
|
172
|
-
*/
|
|
173
|
-
export function ccStopSemanticsFromVersion(version) {
|
|
174
|
-
if (typeof version !== 'string' || version.length === 0)
|
|
175
|
-
return false;
|
|
176
|
-
const got = parseSemverCore(version);
|
|
177
|
-
const min = parseSemverCore(CC_STOP_SEMANTICS_MIN_SERVER);
|
|
178
|
-
if (got === null || min === null)
|
|
179
|
-
return false;
|
|
180
|
-
for (let i = 0; i < 3; i++) {
|
|
181
|
-
if (got[i] !== min[i])
|
|
182
|
-
return got[i] > min[i];
|
|
183
|
-
}
|
|
184
|
-
return true;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* 本会话连着的引擎是否具备 CC Stop 语义?判据 = live 车道的 `/v1/capabilities` 自报 `version`
|
|
188
|
-
* (engineCapsCache 的 boot-kick 缓存,与 `/health.version` 同源)。
|
|
189
|
-
*
|
|
190
|
-
* 🔴 「未判即降级」:caps 探测是构造期 fire-and-forget,`/goal` 真被敲下时通常早已落袋;万一没落袋
|
|
191
|
-
* 就按老引擎走(meta 也同步退档)——**宁可少承诺,不可多承诺**。
|
|
192
|
-
*/
|
|
193
|
-
export function engineCcStopSemantics(env = hostEnv()) {
|
|
194
|
-
const base = env.SEMA_LIVE_BASEURL;
|
|
195
|
-
if (!base)
|
|
196
|
-
return false;
|
|
197
|
-
return ccStopSemanticsFromVersion(engineCapString(base, 'version'));
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Is the `/goal` Stop-hook engine leg armed? **Default ON** since 2026-07-26 — both of the premises
|
|
201
|
-
* that kept it opt-in were disproved by server-side behavior evidence ([1721]) and re-verified against
|
|
202
|
-
* a real engine + real model (cli scripts/run-goal-stop-hook-live-test.mjs). Set
|
|
203
|
-
* `SEMA_GOAL_STOP_HOOK_WIRE=0|false|no|off` to disarm (an operator escape hatch, not a default).
|
|
204
|
-
*/
|
|
205
|
-
export function isGoalStopHookWireArmed(env = hostEnv()) {
|
|
206
|
-
const v = env[GOAL_STOP_HOOK_WIRE_ENV]?.trim().toLowerCase();
|
|
207
|
-
if (v === undefined || v === '')
|
|
208
|
-
return true;
|
|
209
|
-
return !(v === '0' || v === 'false' || v === 'no' || v === 'off');
|
|
210
|
-
}
|
|
211
|
-
/**
|
|
212
|
-
* Register (or clear, with `null`) the session-scoped `/goal` Stop prompt hook that rides
|
|
213
|
-
* `settings.hooks.Stop` to the engine. Single-valued: a new goal replaces the previous one,
|
|
214
|
-
* exactly like CC 220's `Xdr` (remove-then-add). Projection is gated by
|
|
215
|
-
* {@link isGoalStopHookWireArmed} — storing is always safe, sending is not (yet).
|
|
216
|
-
*/
|
|
217
|
-
export function setWireSessionStopHook(prompt, opts) {
|
|
218
|
-
wireSessionStopHookPrompt = prompt && prompt.length > 0 ? prompt : null;
|
|
219
|
-
wireSessionStopHookCcSemantics = wireSessionStopHookPrompt === null ? false : opts?.ccSemantics === true;
|
|
220
|
-
}
|
|
221
|
-
/** The currently registered `/goal` Stop prompt, or null (registered ≠ projected — see the gate). */
|
|
222
|
-
export function getWireSessionStopHook() {
|
|
223
|
-
return wireSessionStopHookPrompt;
|
|
224
|
-
}
|
|
225
|
-
/** 当前这条目标登记时锁存的档位(真 = 投裸条件,假 = 投壳自造的门)。 */
|
|
226
|
-
export function getWireSessionStopHookCcSemantics() {
|
|
227
|
-
return wireSessionStopHookCcSemantics;
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* 把 `/goal` 的**原始条件**包成一条对引擎 prompt 载体**可执行**的 Stop 门 prompt。
|
|
231
|
-
*
|
|
232
|
-
* 🔴 适用面(2026-07-26 提货批后):**只用于未达标线的引擎档**(见 {@link CC_STOP_SEMANTICS_MIN_SERVER})。
|
|
233
|
-
* 达标档起引擎自己按 CC
|
|
234
|
-
* 220 逐字组装(系统提示 + 会话转录 + `wrapCondition`),那一档投的是**裸条件** —— 见
|
|
235
|
-
* {@link ccStopSemanticsFromVersion} 与 {@link hooksForWire} 的分道。本函数保留,是 tolerate-absent
|
|
236
|
-
* 的实体:老引擎上退回它,仍能拦一次;删掉它等于在老引擎上静默失效。
|
|
237
|
-
*
|
|
238
|
-
* 🔴 为什么必须包(2026-07-26 live 围栏实测,不是设计偏好):CC 单进程的 prompt 钩子执行器
|
|
239
|
-
* (`src/utils/hooks/execPromptHook.ts`)会给条件**套一层系统提示**(「You are evaluating a hook…
|
|
240
|
-
* 回 `{ok:true}` / `{ok:false,reason}`」)**并把整段会话前置**,所以裸条件在 CC 那边能被真评估。
|
|
241
|
-
* 引擎侧的载体两样都没有:
|
|
242
|
-
* · 无系统提示、无会话历史;
|
|
243
|
-
* · Stop payload 的 `transcript_path` 是**空串**(server basePayload),评估者对本会话是**瞎的**;
|
|
244
|
-
* · 判据 schema 也不同 —— core 只认 stdout JSON 的 `decision:"block"`。
|
|
245
|
-
* 实测(钉版 server 1.277.1 + core 1.416 + 真模型):裸条件 `the project contains a file named
|
|
246
|
-
* DONE.txt` 送过去,载体照样被调用,模型回的是**散文**(「I don't have direct access to your file
|
|
247
|
-
* system…」)⇒ `parseHookStdout` 拿不到 decision ⇒ **一次都不拦**。即:不包 = 每次停机白烧一次
|
|
248
|
-
* 模型调用、零效果。
|
|
249
|
-
*
|
|
250
|
-
* 所以这里把条件包成引擎契约里**能落地**的形状。诚实标注它**够不到 CC 的完整语义**:
|
|
251
|
-
* · 能做到:每个 turn 的**第一次**停机尝试被拦下,目标原文重新注入给模型,逼它自查后再收尾
|
|
252
|
-
* (`stop_hook_active` 为真时门自己收手 ⇒ 不会无限拦停);真正「条件是否成立」的判断交给
|
|
253
|
-
* **有会话、有工具的主模型**,而不是让瞎的评估者假装判断;
|
|
254
|
-
* · 做不到:CC 那种「拦到条件真成立为止」(需要引擎把 transcript 交给载体)、以及「条件满足后
|
|
255
|
-
* 自动清除目标」。这两条是**等件**,meta 文案因此不照抄 CC `Pwo`(见 cmd-goal.tsx)。
|
|
256
|
-
*
|
|
257
|
-
* 台账仍存**原始条件**({@link getWireSessionStopHook}),所以 `/goal clear` 读回的是用户写的那句。
|
|
258
|
-
*/
|
|
259
|
-
export function buildGoalStopHookPrompt(condition) {
|
|
260
|
-
const reason = `[session goal] Before you stop: verify that this goal actually holds — ${condition}. ` +
|
|
261
|
-
`If it does hold, say so plainly and finish. If it does not, keep working toward it and do not ` +
|
|
262
|
-
`pause to ask the user what to do.`;
|
|
263
|
-
return ('You are the Stop gate for an autonomous coding agent session. The user set this session goal, ' +
|
|
264
|
-
'which the agent is supposed to satisfy before it stops:\n' +
|
|
265
|
-
`<goal>\n${condition}\n</goal>\n` +
|
|
266
|
-
'Stop-hook input JSON: $ARGUMENTS\n' +
|
|
267
|
-
'Reply with RAW JSON only — no prose, no explanation, no code fences.\n' +
|
|
268
|
-
'If the input JSON has "stop_hook_active" set to true, this gate already fired once for this turn: ' +
|
|
269
|
-
'reply with exactly {"continue":true} so the agent may stop.\n' +
|
|
270
|
-
'Otherwise reply with exactly:\n' +
|
|
271
|
-
JSON.stringify({ decision: 'block', reason }));
|
|
272
|
-
}
|
|
273
89
|
/**
|
|
274
90
|
* The merged, GOVERNANCE-GATED settings-file hooks for the wire, or undefined when none apply.
|
|
275
91
|
* Per-event arrays concatenate across sources (policy → user → project → local).
|
|
@@ -282,21 +98,36 @@ export function hooksForWire() {
|
|
|
282
98
|
return undefined;
|
|
283
99
|
// trust gate first (cheapest + broadest): untrusted interactive workspace ⇒ no hooks anywhere,
|
|
284
100
|
// the engine leg included — same invariant the local executor enforces per-fire.
|
|
101
|
+
// [2393] F-2(fix-outright):此前这条 catch 是 **fail-OPEN**(fall through),理由句写「端侧本地
|
|
102
|
+
// 执行器仍 per-fire 把门」—— 那句在本包语境下是**假话**,本文件头第三段自己写明:壳里那条
|
|
103
|
+
// catch 的前提是单进程本地执行器,**库里没有那个兜底**。后果是「未受信工作区的 hooks 绝不上
|
|
104
|
+
// wire」这条被文件头称作安全门的不变量,在 SettingsPort 抛错时整个失效(用户/项目 settings 里的
|
|
105
|
+
// `command` 型 hook 照投给引擎真执行)。而同一函数下方对**同类失败**(port 抛)的 policy 读已选
|
|
106
|
+
// fail-closed 并写明「治理策略读不出来时更安全的默认应是当作有限制」—— 两道门相邻、同类输入,
|
|
107
|
+
// 结论必须一致,更敏感的那道不许留在宽的一侧。改成 fail-closed:信任查不出来 = 当作未受信。
|
|
285
108
|
try {
|
|
286
109
|
if (settings.shouldSkipHookDueToTrust()) {
|
|
287
110
|
hostLog('debug', 'hooksWireCaps: workspace not trusted — no hooks projected to the engine');
|
|
288
111
|
return undefined;
|
|
289
112
|
}
|
|
290
113
|
}
|
|
291
|
-
catch {
|
|
292
|
-
|
|
114
|
+
catch (err) {
|
|
115
|
+
hostLog('debug', `hooksWireCaps: workspace trust check threw — fail-closed (no hooks projected to the engine): ${String(err)}`);
|
|
116
|
+
return undefined;
|
|
293
117
|
}
|
|
294
118
|
let policy = null;
|
|
119
|
+
// REF-CC-155(midband-03,fix-outright,行为面已裁 GO — 安全敏感面 [2374] 背书):policy 是
|
|
120
|
+
// `disableAllHooks`/`allowManagedHooksOnly` 两道治理门的唯一读口。此前读取失败(格式损坏 / 端内部
|
|
121
|
+
// 错误)与「本来就没配 policy」在这里被静默折成同一个 `policy = null` ⇒ 等价于「无限制」——而这条
|
|
122
|
+
// 恰恰是安全敏感面:治理策略读不出来时,更安全的默认应是「当作有限制」而不是「当作无限制」。改成
|
|
123
|
+
// fail-closed:读取真的**抛出**(不是「值合法地缺席」)⇒ 整条腿按 disableAllHooks 同款处置,不再
|
|
124
|
+
// 往下算 managedOnly / 拼装钩子。
|
|
295
125
|
try {
|
|
296
126
|
policy = settings.getSettingsForSource('policySettings');
|
|
297
127
|
}
|
|
298
|
-
catch {
|
|
299
|
-
|
|
128
|
+
catch (err) {
|
|
129
|
+
hostLog('debug', `hooksWireCaps: policySettings read threw — fail-closed (no hooks projected to the engine): ${String(err)}`);
|
|
130
|
+
return undefined;
|
|
300
131
|
}
|
|
301
132
|
if (policy?.disableAllHooks === true) {
|
|
302
133
|
hostLog('debug', 'hooksWireCaps: disableAllHooks (managed) — no hooks projected to the engine');
|
|
@@ -322,38 +153,14 @@ export function hooksForWire() {
|
|
|
322
153
|
// `/goal` 的会话级 Stop×prompt 钩子(CC 220 Xdr)——在治理门之后合并,所以 trust /
|
|
323
154
|
// disableAllHooks / allowManagedHooksOnly 任一命中时它同样不投影(managedOnly 下用户态的
|
|
324
155
|
// goal 钩子也一并禁掉,与 EDITABLE_HOOK_SOURCES 被排除同理)。默认开,见上方 overlay 注释。
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// [1721] 6 条行为用例 + cli 仓 live 围栏双证);
|
|
329
|
-
// · 卡着的是 **SDK 类型**:`SettingsHookCommand.type` 仍是字面量 `"command"`
|
|
330
|
-
// (@sema-agent/sdk dist/settings.d.ts,prompt 形 v1 标 DEFERRED);
|
|
331
|
-
// · **registry-core 建模 prompt 形在 [1717]① 等件中**,SDK 随后放开。
|
|
332
|
-
// 到位后这里换成直类型、去掉 cast(唯一改动点,调用面不动)。
|
|
333
|
-
const goalMatcher = {
|
|
334
|
-
matcher: '',
|
|
335
|
-
// 🔴 投影形**按引擎档位分道**(锁存于 set 那一刻,见 setWireSessionStopHook):
|
|
336
|
-
// · CC 语义档(server ≥ CC_STOP_SEMANTICS_MIN_SERVER)⇒ **裸条件** —— 与 CC `Xdr` 注册的
|
|
337
|
-
// `{type:"prompt",prompt:condition}` 逐字同形;引擎自己套 CC 系统提示 + 会话转录 +
|
|
338
|
-
// `wrapCondition`,再包一层只会打架;
|
|
339
|
-
// · 老引擎档 ⇒ 包装后的可执行门 prompt(裸条件在老引擎上一次都拦不住 —— 见
|
|
340
|
-
// buildGoalStopHookPrompt 的实测说明)。
|
|
341
|
-
// 台账两档都存**原始条件**,供 `/goal clear` 读回。
|
|
342
|
-
hooks: [
|
|
343
|
-
{
|
|
344
|
-
type: 'prompt',
|
|
345
|
-
prompt: wireSessionStopHookCcSemantics
|
|
346
|
-
? wireSessionStopHookPrompt
|
|
347
|
-
: buildGoalStopHookPrompt(wireSessionStopHookPrompt),
|
|
348
|
-
},
|
|
349
|
-
],
|
|
350
|
-
};
|
|
156
|
+
// REF-CC-156(split):判档/锁存态/包装全归 goalStopHook.ts,本文件只调尾部合流点。
|
|
157
|
+
const goalMatcher = goalStopHookMatcher(managedOnly);
|
|
158
|
+
if (goalMatcher !== null) {
|
|
351
159
|
merged['Stop'] = [...(merged['Stop'] ?? []), goalMatcher];
|
|
352
160
|
}
|
|
353
161
|
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
354
162
|
}
|
|
355
163
|
/** 测试钩:清 `/goal` 锁存(壳里靠进程边界隔离;包内同进程多组断言必须能清)。 */
|
|
356
164
|
export function __resetHooksWireCapsForTests() {
|
|
357
|
-
|
|
358
|
-
wireSessionStopHookCcSemantics = false;
|
|
165
|
+
__resetGoalStopHookForTests();
|
|
359
166
|
}
|
package/dist/host.d.ts
CHANGED
|
@@ -19,8 +19,15 @@
|
|
|
19
19
|
* miss 计数(`hostPortMisses()`),宿主自检断言它恒为 0 —— 与 `notificationQueuePortMisses()`
|
|
20
20
|
* 同一套姿势([probe-must-prove-it-speaks]:不能让「什么都没有」看起来像「一切正常」)。
|
|
21
21
|
*/
|
|
22
|
-
import type { AdapterLogLevel } from './seam.js';
|
|
22
|
+
import type { AdapterLogLevel, TimerHandle } from './seam.js';
|
|
23
23
|
import { type NotificationQueuePort } from './notifications.js';
|
|
24
|
+
/**
|
|
25
|
+
* settings 文件的顶层文档形状(TYPESHAPE-11)。开集是**有理由的**:settings 文件本身是用户
|
|
26
|
+
* 自由格式(壳三层合并后的 JSON、web center 下发、桌面主进程各自的形状都不同),库不认识
|
|
27
|
+
* 具体键——窄化点在各消费者(如 hooksWireCaps)按各自需要的子形状 cast。缺席该来源的
|
|
28
|
+
* settings ⇒ `null`(与本包既有 mock/消费点已经在用的口径一致,不是新增语义)。
|
|
29
|
+
*/
|
|
30
|
+
export type SettingsDocument = Record<string, unknown>;
|
|
24
31
|
/**
|
|
25
32
|
* settings 口 —— 用户 settings 文件的读取面(壳=三层合并后的文件读;web=center 下发;桌面=主进程)。
|
|
26
33
|
*
|
|
@@ -29,7 +36,7 @@ import { type NotificationQueuePort } from './notifications.js';
|
|
|
29
36
|
*/
|
|
30
37
|
export interface SettingsPort {
|
|
31
38
|
/** 按来源取 settings(cli `utils/settings/settings.ts` 的 `getSettingsForSource`)。 */
|
|
32
|
-
getSettingsForSource(source: string):
|
|
39
|
+
getSettingsForSource(source: string): SettingsDocument | null;
|
|
33
40
|
/**
|
|
34
41
|
* 信任门:hook 是否因工作区未受信而跳过。**返回 true = 跳过**。
|
|
35
42
|
* 🔴 B6 更正:壳侧真身 `utils/hooks.shouldSkipHookDueToTrust()` 是**零参**的(门是工作区级、
|
|
@@ -49,10 +56,14 @@ export interface FsPort {
|
|
|
49
56
|
/** 🔴 壳侧真身是 **async**(mkdir 0o700);端可返 void 或 Promise,库两种都吞(fire-and-forget)。 */
|
|
50
57
|
ensureScratchpadDir(): void | Promise<void>;
|
|
51
58
|
}
|
|
52
|
-
/**
|
|
59
|
+
/**
|
|
60
|
+
* 定时器口(进程级默认;`ctx.setTimer` 仍可按 turn 覆盖)。
|
|
61
|
+
* 🔴 TYPESHAPE-10:句柄用 seam.ts 已有的命名不透明别名 `TimerHandle`(不透明本身不变 —— Node 是
|
|
62
|
+
* Timeout 对象、浏览器是 number,库侧只负责回传;只是把裸 `unknown` 收进一个有名字的口)。
|
|
63
|
+
*/
|
|
53
64
|
export interface TimersPort {
|
|
54
|
-
setTimer(ms: number, fn: () => void):
|
|
55
|
-
clearTimer(handle:
|
|
65
|
+
setTimer(ms: number, fn: () => void): TimerHandle;
|
|
66
|
+
clearTimer(handle: TimerHandle): void;
|
|
56
67
|
}
|
|
57
68
|
/**
|
|
58
69
|
* session 口(**B6 引入**)—— 「现在是哪个会话 / 哪个 run 在飞」。
|
package/dist/index.d.ts
CHANGED
|
@@ -150,6 +150,7 @@ export * from './engineToolLabelStore.js';
|
|
|
150
150
|
export * from './fleetTaskDesc.js';
|
|
151
151
|
export type * from './types/engineState.js';
|
|
152
152
|
export * from './hostEnv.js';
|
|
153
|
+
export * from './unrefTimer.js';
|
|
153
154
|
export * from './controlRouter.js';
|
|
154
155
|
export * from './sseIdleTriage.js';
|
|
155
156
|
export * from './engineWireSdk.js';
|
|
@@ -211,6 +212,7 @@ export * from './hooksWireCaps.js';
|
|
|
211
212
|
export * from './liveInitToolFace.js';
|
|
212
213
|
export * from './model/providerPresets.js';
|
|
213
214
|
export * from './hitl/hitlBridge.js';
|
|
215
|
+
export * from './hitl/hitlHostSurface.js';
|
|
214
216
|
export * from './hitl/toolApprovalWire.js';
|
|
215
217
|
export * from './hitl/askGateWire.js';
|
|
216
218
|
export * from './hitl/planReviewWire.js';
|
package/dist/index.js
CHANGED
|
@@ -153,6 +153,10 @@ export * from './fleetTaskDesc.js';
|
|
|
153
153
|
// 通知族(engineTaskNotification + taskNotificationErrorSupplement + hookNoticeStore 判定半场)
|
|
154
154
|
// 已并入 notifications.js,不需要新增导出行。
|
|
155
155
|
export * from './hostEnv.js';
|
|
156
|
+
export * from './unrefTimer.js';
|
|
157
|
+
// REF-CC-141 dup-02(wave1 卡B,2026-08-01):envFlag.ts 是十处 SEMA_* 布尔 env 旋钮拼写集
|
|
158
|
+
// 单源叶模块,但只被十个已在闭包里的模块值级 import 消费,不需要经 barrel 发到 npm 公面
|
|
159
|
+
// (wave1 回炉 advisory:摘掉此行,envFlagOff/envFlagOn 保持包内部,不进 public-export-baseline)。
|
|
156
160
|
export * from './controlRouter.js';
|
|
157
161
|
export * from './sseIdleTriage.js';
|
|
158
162
|
export * from './engineWireSdk.js';
|
|
@@ -197,6 +201,12 @@ export * from './clientSlice.js';
|
|
|
197
201
|
// 包级宿主装配(per-turn 的 AdapterContext 之外的那一层:settings / fs / queue / timers)。
|
|
198
202
|
// 🔴 `installNotificationQueuePort` 仍是队列的真源出口,`installHost({queue})` 只是直通它。
|
|
199
203
|
export * from './host.js';
|
|
204
|
+
// REF-CC-147/REF-CC-149(P2「I族」裁定):D 族循环收编只抽的两个低层原语之一——「怎么安全地睡
|
|
205
|
+
// 一觉」,workflowClient.ts 与 agentSession/backgroundView.ts 的轮询退避共用。wave1 回炉复审
|
|
206
|
+
// (opus advisory)撤回:这是包内实现叶,不出 barrel(与下面 `createSessionSlot` 同一先例)——
|
|
207
|
+
// 出了 barrel 就是 public-export-baseline.json 自述的**不可撤回承诺**,而没有任何门要求它公开;
|
|
208
|
+
// 内部两处消费点都是相对路径 `import { abortableSleep } from './abortableSleep.js'`,门也改吃
|
|
209
|
+
// `dist/abortableSleep.js`(见 scripts/run-abortable-sleep-test.mjs)。
|
|
200
210
|
// W1(design/161):五个模块级单槽位的 per-session 注册表键。零参旧 API 全部等价于用这个键调
|
|
201
211
|
// `*For(sessionKey, …)` 变体;多会话宿主(desktop)每会话一键。`createSessionSlot` 是包内实现件,
|
|
202
212
|
// 刻意不出 barrel(公面只承诺键与各槽位的 *For API)。
|
|
@@ -264,9 +274,13 @@ export * from './model/providerPresets.js';
|
|
|
264
274
|
// 🔴 两个新拆缝口(缺席都计 miss,宿主自检恒应为 0):
|
|
265
275
|
// · `installApprovalCardPort` —— 三选卡本体(vendored CC `PermissionRequest` / 桌面模态);
|
|
266
276
|
// · `installHitlHostSurface` —— footer 通知与 /permissions Recent Denials 记账(端状态形状)。
|
|
267
|
-
// 🔴 单实例:`toolApprovalWire` 的卡口槽 + `
|
|
277
|
+
// 🔴 单实例:`toolApprovalWire` 的卡口槽 + `hitlHostSurface` 的宿主面槽是 module 级变量;两份实例 ⇒
|
|
268
278
|
// 装在一份、读另一份 = 每张 gate 都走 fail-closed deny(卡根本不弹)。
|
|
279
|
+
// 🆕 REF-CC-023(2026-08-02,P3 wave2 卡C-bridge):`hitlHostSurface.ts` 从 `askGateWire.ts` 里提出的
|
|
280
|
+
// 共享宿主通知面 + cancel-by-deny 有界观察 —— `askGateWire.ts` 已 import `toolApprovalWire.ts`,
|
|
281
|
+
// 反向 import 会成环,所以两条腿共用的那一组挪进一个两边都能到达的独立模块。
|
|
269
282
|
export * from './hitl/hitlBridge.js';
|
|
283
|
+
export * from './hitl/hitlHostSurface.js';
|
|
270
284
|
export * from './hitl/toolApprovalWire.js';
|
|
271
285
|
export * from './hitl/askGateWire.js';
|
|
272
286
|
export * from './hitl/planReviewWire.js';
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* · 显式交互意图恒赢:CLI 无 --interactive-tools 类旗(gap-check 2026-07-16:上游 CC 2.1.207
|
|
17
17
|
* 与本壳 argv 面均无,不发明);既有旗里唯一表达「我要交互门」的是 `--permission-mode plan`
|
|
18
18
|
* (plan 流程本身要 plan-review 交互)——该旗在场时不 stamp(维持引擎缺省判据);
|
|
19
|
-
* · SEMA_HEADLESS_INTERACTIVE_TOOLS 部署旋钮(settings env
|
|
20
|
-
*
|
|
19
|
+
* · SEMA_HEADLESS_INTERACTIVE_TOOLS 部署旋钮(settings env 车道,拼写集 = envFlagOn/envFlagOff、
|
|
20
|
+
* REF-CC-141 dup-02 单源):真值拼写 = 不 stamp(回引擎缺省判据),假值拼写 = 显式 stamp false
|
|
21
|
+
* (与缺省同效,显式垫);非法值一次性 warn + 回缺省;
|
|
21
22
|
* · 缺省 = stamp false(无人值守正解);
|
|
22
23
|
* · 旧 server 白名单 fail-soft:server resolveSpec 逐字段白名单拼 TaskSpec,未知 body 字段静默
|
|
23
24
|
* 忽略(limits wire 同款勘察结论)——本字段发了白发不炸。gap-check 实拆 server 1.210.0 dist:
|
|
@@ -36,8 +37,18 @@ export type InteractiveToolsResolution = {
|
|
|
36
37
|
/** 非法 env 值的告警文案(调用层一次性打 stderr)。 */
|
|
37
38
|
warning?: string;
|
|
38
39
|
};
|
|
39
|
-
/**
|
|
40
|
-
*
|
|
40
|
+
/**
|
|
41
|
+
* argv 里是否显式要了 plan 交互意图(`--permission-mode plan` / `--permission-mode=plan`;
|
|
42
|
+
* 后出现者赢,commander 同语义;`--` 之后不是 flag)。
|
|
43
|
+
*
|
|
44
|
+
* REF-CC-143(dup-10)收编,2026-08-01:这个函数原本自带一份与 headlessPermissionModeWire.
|
|
45
|
+
* permissionModeFromArgv 逐字重复的 argv 扫描循环(两个 headless 旋钮读的是同一个 `--permission-mode`
|
|
46
|
+
* flag,必须是同一份读法)。dup-10 证明过等价性:`permissionModeFromArgv` 的
|
|
47
|
+
* `--dangerously-skip-permissions` 分支只在没有显式 `--permission-mode` 时才返回
|
|
48
|
+
* `'bypassPermissions'`,那个值永远不等于 `'plan'`,所以对任意 argv 恒有
|
|
49
|
+
* `planModeExplicitlyRequested(argv) === (permissionModeFromArgv(argv) === 'plan')`——折成薄壳,
|
|
50
|
+
* 循环整个消失,而不只是「提取逻辑」被共享。
|
|
51
|
+
*/
|
|
41
52
|
export declare function planModeExplicitlyRequested(argv: readonly string[]): boolean;
|
|
42
53
|
/**
|
|
43
54
|
* 解析 headless `-p` 的 interactiveTools stamp。纯函数除告警 latch。
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
* · 显式交互意图恒赢:CLI 无 --interactive-tools 类旗(gap-check 2026-07-16:上游 CC 2.1.207
|
|
17
17
|
* 与本壳 argv 面均无,不发明);既有旗里唯一表达「我要交互门」的是 `--permission-mode plan`
|
|
18
18
|
* (plan 流程本身要 plan-review 交互)——该旗在场时不 stamp(维持引擎缺省判据);
|
|
19
|
-
* · SEMA_HEADLESS_INTERACTIVE_TOOLS 部署旋钮(settings env
|
|
20
|
-
*
|
|
19
|
+
* · SEMA_HEADLESS_INTERACTIVE_TOOLS 部署旋钮(settings env 车道,拼写集 = envFlagOn/envFlagOff、
|
|
20
|
+
* REF-CC-141 dup-02 单源):真值拼写 = 不 stamp(回引擎缺省判据),假值拼写 = 显式 stamp false
|
|
21
|
+
* (与缺省同效,显式垫);非法值一次性 warn + 回缺省;
|
|
21
22
|
* · 缺省 = stamp false(无人值守正解);
|
|
22
23
|
* · 旧 server 白名单 fail-soft:server resolveSpec 逐字段白名单拼 TaskSpec,未知 body 字段静默
|
|
23
24
|
* 忽略(limits wire 同款勘察结论)——本字段发了白发不炸。gap-check 实拆 server 1.210.0 dist:
|
|
@@ -25,29 +26,24 @@
|
|
|
25
26
|
* 壳先备(黑板记账)。
|
|
26
27
|
*/
|
|
27
28
|
import { hostEnv } from './hostEnv.js';
|
|
29
|
+
import { envFlagOff, envFlagOn } from './envFlag.js';
|
|
30
|
+
import { permissionModeFromArgv } from './headlessPermissionModeWire.js';
|
|
28
31
|
/** 部署旋钮 env 键名(settings.json env 块 → 1a-envseed → process.env 同车道)。 */
|
|
29
32
|
export const HEADLESS_INTERACTIVE_TOOLS_ENV = 'SEMA_HEADLESS_INTERACTIVE_TOOLS';
|
|
30
|
-
/**
|
|
31
|
-
*
|
|
33
|
+
/**
|
|
34
|
+
* argv 里是否显式要了 plan 交互意图(`--permission-mode plan` / `--permission-mode=plan`;
|
|
35
|
+
* 后出现者赢,commander 同语义;`--` 之后不是 flag)。
|
|
36
|
+
*
|
|
37
|
+
* REF-CC-143(dup-10)收编,2026-08-01:这个函数原本自带一份与 headlessPermissionModeWire.
|
|
38
|
+
* permissionModeFromArgv 逐字重复的 argv 扫描循环(两个 headless 旋钮读的是同一个 `--permission-mode`
|
|
39
|
+
* flag,必须是同一份读法)。dup-10 证明过等价性:`permissionModeFromArgv` 的
|
|
40
|
+
* `--dangerously-skip-permissions` 分支只在没有显式 `--permission-mode` 时才返回
|
|
41
|
+
* `'bypassPermissions'`,那个值永远不等于 `'plan'`,所以对任意 argv 恒有
|
|
42
|
+
* `planModeExplicitlyRequested(argv) === (permissionModeFromArgv(argv) === 'plan')`——折成薄壳,
|
|
43
|
+
* 循环整个消失,而不只是「提取逻辑」被共享。
|
|
44
|
+
*/
|
|
32
45
|
export function planModeExplicitlyRequested(argv) {
|
|
33
|
-
|
|
34
|
-
for (let i = 0; i < argv.length; i++) {
|
|
35
|
-
const a = argv[i];
|
|
36
|
-
if (a === '--permission-mode') {
|
|
37
|
-
const v = argv[i + 1];
|
|
38
|
-
if (typeof v === 'string' && !v.startsWith('-')) {
|
|
39
|
-
mode = v;
|
|
40
|
-
i++;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
else if (a?.startsWith('--permission-mode=')) {
|
|
44
|
-
mode = a.slice('--permission-mode='.length);
|
|
45
|
-
}
|
|
46
|
-
else if (a === '--') {
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return mode === 'plan';
|
|
46
|
+
return permissionModeFromArgv(argv) === 'plan';
|
|
51
47
|
}
|
|
52
48
|
// 一次性告警 latch(-p 是 one-shot 进程;测试钩子可重置)
|
|
53
49
|
let warnedInvalidEnv = false;
|
|
@@ -60,12 +56,14 @@ export function resolveHeadlessInteractiveTools(argv, env = hostEnv()) {
|
|
|
60
56
|
if (planModeExplicitlyRequested(argv)) {
|
|
61
57
|
return { fields: {}, source: 'plan-flag' };
|
|
62
58
|
}
|
|
63
|
-
// 2)
|
|
59
|
+
// 2) 部署旋钮。拼写集 = envFlagOn/envFlagOff(REF-CC-141 dup-02 单源)—— 修前本处是四份重复
|
|
60
|
+
// 实现里唯一不 lowercase 的一份(`raw==='true'||raw==='1'`),`SEMA_HEADLESS_INTERACTIVE_
|
|
61
|
+
// TOOLS=False` 会落进下面的「不是布尔值」warn 分支,现改为大小写不敏感 + 拼写集与其它九处同源。
|
|
64
62
|
const raw = env[HEADLESS_INTERACTIVE_TOOLS_ENV]?.trim();
|
|
65
63
|
if (raw !== undefined && raw !== '') {
|
|
66
|
-
if (raw
|
|
64
|
+
if (envFlagOn(raw))
|
|
67
65
|
return { fields: {}, source: 'env' }; // 回引擎缺省判据
|
|
68
|
-
if (raw
|
|
66
|
+
if (envFlagOff(raw))
|
|
69
67
|
return { fields: { interactiveTools: false }, source: 'env' };
|
|
70
68
|
const resolution = {
|
|
71
69
|
fields: { interactiveTools: false },
|
|
@@ -73,7 +71,7 @@ export function resolveHeadlessInteractiveTools(argv, env = hostEnv()) {
|
|
|
73
71
|
};
|
|
74
72
|
if (!warnedInvalidEnv) {
|
|
75
73
|
warnedInvalidEnv = true;
|
|
76
|
-
resolution.warning = `sema: ${HEADLESS_INTERACTIVE_TOOLS_ENV}="${raw}" is not a boolean (expected true/1/false/0) — ignoring it, headless -p keeps interactive tools OFF by default`;
|
|
74
|
+
resolution.warning = `sema: ${HEADLESS_INTERACTIVE_TOOLS_ENV}="${raw}" is not a recognized boolean spelling (expected true/1/yes/on or false/0/no/off/none) — ignoring it, headless -p keeps interactive tools OFF by default`;
|
|
77
75
|
}
|
|
78
76
|
return resolution;
|
|
79
77
|
}
|
package/dist/limitsWire.js
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* Unreachable /health or an unparsable version stays silent (can't confirm staleness — fail-soft).
|
|
34
34
|
*/
|
|
35
35
|
import { hostEnv } from './hostEnv.js';
|
|
36
|
+
import { lastFlagValue } from './argvFlagValue.js';
|
|
36
37
|
/** Settings-lane env knobs (settings.json `env` block → 1a-envseed → process.env). */
|
|
37
38
|
export const HEADLESS_DEADLINE_ENV = 'SEMA_HEADLESS_DEADLINE_SEC';
|
|
38
39
|
export const HEADLESS_MAX_TURNS_ENV = 'SEMA_HEADLESS_MAX_TURNS';
|
|
@@ -53,46 +54,12 @@ const MAX_TURNS_USAGE = `sema: --max-turns expects a whole number of turns betwe
|
|
|
53
54
|
' (no flag) no turn cap — today\'s behaviour';
|
|
54
55
|
/** STRICT integer shape for explicit flag values: ASCII digits only (no sign/decimal/exponent/space). */
|
|
55
56
|
const INT_RE = /^\d+$/;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*/
|
|
63
|
-
function lastFlagValue(argv, name) {
|
|
64
|
-
const eq = `${name}=`;
|
|
65
|
-
let present = false;
|
|
66
|
-
let raw;
|
|
67
|
-
let valueMissing = false;
|
|
68
|
-
for (let i = 0; i < argv.length; i++) {
|
|
69
|
-
const a = argv[i];
|
|
70
|
-
if (a === '--')
|
|
71
|
-
break; // positionals — never flags
|
|
72
|
-
if (a === name) {
|
|
73
|
-
present = true;
|
|
74
|
-
const nxt = argv[i + 1];
|
|
75
|
-
if (nxt === undefined || nxt.startsWith('-')) {
|
|
76
|
-
// note: a NEGATIVE number would look flag-shaped too — the domains are all-positive, so the
|
|
77
|
-
// fail-loud usage hint is the right answer for `--deadline -5` as well.
|
|
78
|
-
valueMissing = true;
|
|
79
|
-
raw = undefined;
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
valueMissing = false;
|
|
83
|
-
raw = nxt;
|
|
84
|
-
i++; // consume the value token
|
|
85
|
-
}
|
|
86
|
-
else if (a.startsWith(eq)) {
|
|
87
|
-
present = true;
|
|
88
|
-
valueMissing = false;
|
|
89
|
-
raw = a.slice(eq.length);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (present && (valueMissing || raw === undefined))
|
|
93
|
-
return { present: true };
|
|
94
|
-
return present ? { present: true, raw } : { present: false };
|
|
95
|
-
}
|
|
57
|
+
// `lastFlagValue` (the `--name <v>`/`--name=<v>`/repeated-flag-last-wins scan) now lives in
|
|
58
|
+
// argvFlagValue.ts (REF-CC-143/dup-07, P3 wave1 卡C-argv, 2026-08-01) — this was the fullest of five
|
|
59
|
+
// hand-rolled copies (present/raw/valueMissing three-state) and is the one the shared leaf was cribbed
|
|
60
|
+
// from verbatim; see that file's header for the shared 缺值 contract. `--deadline`/`--max-turns` are
|
|
61
|
+
// both all-positive domains, so a NEGATIVE number (which looks flag-shaped to the scanner) correctly
|
|
62
|
+
// falls into the same fail-loud usage-hint path as a genuinely missing value below.
|
|
96
63
|
/** Validate a RAW flag value against an inclusive integer domain. Returns the number or null. */
|
|
97
64
|
function parseIntInDomain(raw, min, max) {
|
|
98
65
|
const v = raw.trim();
|