@sema-agent/server 7.1.0 → 7.3.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 +3 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +6 -1
- package/dist/approval-ask-machine.d.ts +39 -0
- package/dist/approval-ask-machine.js +101 -0
- package/dist/approval-card.d.ts +244 -0
- package/dist/approval-card.js +237 -0
- package/dist/approval-deny-reasons.d.ts +56 -0
- package/dist/approval-deny-reasons.js +54 -0
- package/dist/approval-reconciler.d.ts +167 -0
- package/dist/approval-reconciler.js +307 -0
- package/dist/boot/coordinators.d.ts +1 -0
- package/dist/boot/coordinators.js +36 -3
- package/dist/boot/lexical-path-env.d.ts +14 -0
- package/dist/boot/lexical-path-env.js +116 -0
- package/dist/boot/org-memory.d.ts +8 -5
- package/dist/boot/org-memory.js +23 -12
- package/dist/boot/reapers.d.ts +34 -0
- package/dist/boot/reapers.js +198 -23
- package/dist/boot/resolve-spec.d.ts +16 -1
- package/dist/boot/resolve-spec.js +104 -46
- package/dist/config-center/facade.d.ts +1 -1
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/http-client.d.ts +19 -0
- package/dist/config-center/http-client.js +81 -32
- package/dist/config-types.d.ts +76 -7
- package/dist/config.d.ts +1 -0
- package/dist/config.js +134 -4
- package/dist/elicitation.d.ts +4 -0
- package/dist/elicitation.js +2 -2
- package/dist/http/routes/capabilities.js +18 -2
- package/dist/http/routes/runs.d.ts +1 -0
- package/dist/http/routes/runs.js +548 -16
- package/dist/http/routes/tasks.js +190 -12
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +124 -8
- package/dist/http/sse-log.d.ts +51 -0
- package/dist/http/sse-log.js +64 -0
- package/dist/http/wire-types.d.ts +20 -5
- package/dist/leader/wire.js +4 -0
- package/dist/main.js +7 -4
- package/dist/org-memory-admission.d.ts +2 -1
- package/dist/org-memory-admission.js +10 -4
- package/dist/parked-decide.js +5 -2
- package/dist/plugins/approval-ask-store-memory.d.ts +38 -0
- package/dist/plugins/approval-ask-store-memory.js +299 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +341 -0
- package/dist/plugins/approval-ask-store-sql.js +705 -0
- package/dist/plugins/background-agent-store-sql.js +20 -1
- package/dist/plugins/checkpoint-store-sql.d.ts +84 -9
- package/dist/plugins/checkpoint-store-sql.js +297 -16
- package/dist/plugins/local-checkpoint-store.d.ts +6 -5
- package/dist/plugins/local-checkpoint-store.js +4 -0
- package/dist/plugins/pg-pool.js +11 -0
- package/dist/plugins/store-backend.d.ts +18 -0
- package/dist/plugins/store-backend.js +10 -0
- package/dist/plugins/tidb-pool.js +27 -4
- package/dist/question.d.ts +18 -3
- package/dist/question.js +20 -6
- package/dist/runs.d.ts +16 -1
- package/dist/runs.js +61 -3
- package/dist/runtime-caps-resolver.d.ts +7 -1
- package/dist/runtime-caps-resolver.js +65 -3
- package/dist/runtime-governance.d.ts +11 -4
- package/dist/runtime-governance.js +16 -5
- package/dist/spec-fields.d.ts +4 -0
- package/dist/spec-fields.js +6 -0
- package/dist/task-settings.d.ts +35 -15
- package/dist/task-settings.js +19 -5
- package/dist/tool-approval.d.ts +296 -3
- package/dist/tool-approval.js +1066 -50
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-sink.js +14 -1
- package/dist/trace/project.d.ts +55 -0
- package/dist/trace/project.js +135 -0
- package/package.json +4 -3
|
@@ -42,19 +42,26 @@ import { applyRuntimeGovernance, stripDelegationTools } from "../runtime-governa
|
|
|
42
42
|
import { HttpError, encodeCheckpointScope } from "../security.js";
|
|
43
43
|
import { memorySpecForRequest } from "../memory-scope.js";
|
|
44
44
|
import { mcpForScenario } from "../config-center/facade.js";
|
|
45
|
-
import { normalizeAttachments, normalizeResilience, normalizeResumeAtMode, normalizeSuggestNextPrompts, promptProfileFromBody, resolveTaskLimits, retainBackgroundProcessesFromBody, taskAgentsSpecFragment, toolNameListFromBody } from "../spec-fields.js";
|
|
45
|
+
import { normalizeAttachments, normalizeResilience, normalizeResumeAtMode, normalizeSuggestNextPrompts, promptProfileFromBody, resolveTaskLimits, retainBackgroundProcessesFromBody, taskAgentsSpecFragment, toolMaterializeStrategyFromBody, toolNameListFromBody } from "../spec-fields.js";
|
|
46
46
|
import { cwdHonored, effectiveHostWorkspace, inProcessSingleUserLane, isValidCwd, parseAdditionalDirectories, satisfiedByProcessCwd, shellEnvMismatchCount } from "../task-cwd.js";
|
|
47
47
|
import { resolveRequestMcp } from "../task-mcp.js";
|
|
48
48
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSettings, coercePermissionMode, effectiveThinking, hasConstitutionAnchors, parseTaskSettings, providerDropsAppend, withPermissionMode } from "../task-settings.js";
|
|
49
49
|
import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
|
|
50
50
|
import { redactSecrets } from "../trace/redact.js";
|
|
51
|
+
import { LexicalPathExecutionEnv } from "./lexical-path-env.js";
|
|
51
52
|
/** #152([2703] 案二):durable 部署上的 AskUserQuestion 门。活体面(QuestionCoordinator)缺席 ⇒ 原形
|
|
52
53
|
* `createDurableQuestionPolicy()`(恒 ask ⇒ 恒 durable park)。在场 ⇒ **判决时**按活流上下文分腿:
|
|
53
|
-
* 活流腿(bg/SSE,coordinator.runWithContext
|
|
54
|
-
* 的 coordinator,问正在 tail 流的活人;无活流腿(sync /v1/tasks、verify/cascade
|
|
55
|
-
* 原语义逐字保留(此时执行只会拿到 headless
|
|
56
|
-
*
|
|
57
|
-
|
|
54
|
+
* 活流腿(bg/SSE,coordinator.runWithContext 包裹且投递面此刻可达,ALS 判)allow——工具执行落到
|
|
55
|
+
* RunnerDeps.onQuestion 的 coordinator,问正在 tail 流的活人;无活流腿(sync /v1/tasks、verify/cascade、
|
|
56
|
+
* durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留(此时执行只会拿到 headless
|
|
57
|
+
* 空答,park 才是把问题送到人面前的那条腿)。
|
|
58
|
+
* 工具名字面量与 server.ts 的 pre-CAS 守卫同源("AskUserQuestion",core 未根导出常量)。
|
|
59
|
+
* ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
|
|
60
|
+
* 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
|
|
61
|
+
* 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
|
|
62
|
+
* 自折 core 原形——该腿无活体问答面可装且 leader 不 import boot 层(分层),core 原形+sentinel 即其完整
|
|
63
|
+
* 语义;豁免注在彼处互指,接活体面之日必须并回本工厂。 */
|
|
64
|
+
export function createDurableQuestionGate(live) {
|
|
58
65
|
if (live === undefined)
|
|
59
66
|
return createDurableQuestionPolicy();
|
|
60
67
|
return {
|
|
@@ -102,7 +109,11 @@ export function createResolveSpec(ctx) {
|
|
|
102
109
|
// allowedLanes ⇒ typed 403 `execution_lane_not_allowed`. Same caps car as the scenario ruling (zero extra
|
|
103
110
|
// RTT); fail-open + audit on blip. "in-process" = fleet's REMOTE_EXEC-unset core stub lane
|
|
104
111
|
// (named distinctly from the explicit `host` lane — same machine, different posture).
|
|
105
|
-
|
|
112
|
+
// #157 补痕交叉复审真发现(codex,2026-08-05):第三参不传就落 runtime-caps-resolver.ts 自己的私有
|
|
113
|
+
// `defaultLogger`(createLogger() 默认 minLevel="info")——与本部署真正按 config.logLevel 建的 `logger`
|
|
114
|
+
// 是两个独立实例,即便运维把 LOG_LEVEL 调到 debug 也打不到这条新增的一次性默认落痕行(补痕功能形同虚设)。
|
|
115
|
+
// 显式传本装配的真 logger,让该痕迹真的受部署日志级别管辖。
|
|
116
|
+
gateExecutionLane(await principalCaps?.executionRuling(auth?.principal), config.remoteExec?.provider ?? "in-process", logger);
|
|
106
117
|
// Resolve an unknown name to "default" so the WHOLE bundle stays coherent — tools/prompt AND
|
|
107
118
|
// skills/mcp fall back together. (selectScenario already falls back for tools/prompt; without
|
|
108
119
|
// resolving here, skills/mcp would still be filtered by the raw unknown name → any skill/MCP tagged
|
|
@@ -553,6 +564,10 @@ export function createResolveSpec(ctx) {
|
|
|
553
564
|
// [1144]/[1146] (core 1.328 R2): promptProfile 双形轴直通——纯呈现轴无租户门(deferTools 姿势);
|
|
554
565
|
// 缺省不挂键(引擎缺省 simple);非法值由 HTTP 门 400,resume 重放 defensive DROP。
|
|
555
566
|
...(() => { const v = promptProfileFromBody(body.promptProfile); return v ? { promptProfile: v } : {}; })(),
|
|
567
|
+
// [2856]②(core 5.14.0):deferred 工具激活后的 schema 供给策略,per-task 位。core 侧读法是
|
|
568
|
+
// `spec.toolMaterializeStrategy ?? env ?? "static"` ⇒ **缺省不挂键**才能让进程级 env 继续说话;
|
|
569
|
+
// 挂一个「缺省值」上去会把 env 车道悄悄夺权。非法值由 HTTP 门 400,resume 重放 defensive DROP。
|
|
570
|
+
...(() => { const v = toolMaterializeStrategyFromBody(body.toolMaterializeStrategy); return v ? { toolMaterializeStrategy: v } : {}; })(),
|
|
556
571
|
// [922]①/[915]② (core 1.296): interactiveTools 三态旋钮直通——boolean 上 spec(壳 -p 恒 stamp false=
|
|
557
572
|
// A3 类「headless 提问 park」根治腿;plan 显式 true 恒赢),undefined=core 自动判据(送达面探测)。
|
|
558
573
|
// per-run 工具面选择无跨租户面 ⇒ 无租户门;非 boolean 由 HTTP 门 400,resume 重放 typeof 检查天然 DROP。
|
|
@@ -736,7 +751,7 @@ export function createResolveSpec(ctx) {
|
|
|
736
751
|
// policy is LIVE-AWARE when the QuestionCoordinator is wired — a live-stream leg adjudicates `allow`
|
|
737
752
|
// (the live human answers over the stream) instead of unconditionally parking; see durableQuestionPolicy.
|
|
738
753
|
toolPolicy: durableEnabled
|
|
739
|
-
? combinePolicies(
|
|
754
|
+
? combinePolicies(createDurableQuestionGate(liveQuestionFace), createDurableAskPolicy({
|
|
740
755
|
requireApproval: config.approvalRequire, deny: config.approvalDeny, autoBudget: config.approvalAutoBudget, neverAuto: config.approvalNeverAuto,
|
|
741
756
|
// The probe key is the CONTINUED session (auth.sessionId — the same id that keys the
|
|
742
757
|
// durable checkpoint /decide route). A fresh session (no body.sessionId) has no exemptions by
|
|
@@ -864,12 +879,24 @@ export function createResolveSpec(ctx) {
|
|
|
864
879
|
const scratchpadDir = shellScratchpad ??
|
|
865
880
|
(hostSemanticsLane && auth?.sessionId ? await ensureScratchpadDir(config.localDataRoot ?? localRoot, auth.sessionId) : undefined);
|
|
866
881
|
if (effectiveSettings) {
|
|
867
|
-
// [816]/[820]① fs-write gate wiring
|
|
868
|
-
//
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
//
|
|
872
|
-
//
|
|
882
|
+
// [816]/[820]① fs-write gate wiring —— 两形,按 lane 分:
|
|
883
|
+
//
|
|
884
|
+
// ① HOST-semantics lane(REMOTE_EXEC 未设或 "host"):core 的 createFsWriteGatePolicy 把每一个
|
|
885
|
+
// target/dir 交给给定 env 的**真实 fs**(exists/canonicalPath/readLink,dist 亲读),所以 env 必须
|
|
886
|
+
// 就是 hand 工具真正写的那块盘 —— 这里给 worker 本机的 NodeExecutionEnv(构造是纯字段赋值,不 spawn)。
|
|
887
|
+
//
|
|
888
|
+
// ② 沙箱 lane(e2b/k8s/ssh/adb/local-docker,#156):沙箱 env 由 core 在 spec **之后**才铸
|
|
889
|
+
// (executionEnvFactory),spec 期把 worker 本机 env 递进去会拿**别人机器上的 fs 事实**裁决
|
|
890
|
+
// (symlink/exists 全答错),而错误的 allow 比没有门更糟 —— 这就是 [816] 当年在这些 lane 上诚实
|
|
891
|
+
// 不挂门的理由(#153 的 ③ 号注把它记成余款)。过渡还款不是把本机 fs 递进去,而是换一个**没有 fs**
|
|
892
|
+
// 的裁决 env:LexicalPathExecutionEnv 只对 POSIX 绝对路径做词法归一,其余(相对路径/`~`/Windows 形)
|
|
893
|
+
// 一律「解析不出」⇒ core 落 ask。于是沙箱 lane 拿到「绝对路径按词法判、其余 fail-closed」的写门,
|
|
894
|
+
// sensitivePatterns 的 deny 腿(按路径段词法匹配)与 isExempt 会话豁免探针(name-keyed,不做 fs
|
|
895
|
+
// 裁决)照常施加。**一个自动放行域都不铸**——两个候选域(cwd / scratchpad)各自的理由与代价写在
|
|
896
|
+
// 下面那条臂的注里,合起来的效果是 acceptEdits 在沙箱 lane 退化成与 default 同形(全 ask),与本段
|
|
897
|
+
// ① 里 host factory lane 的哨兵先例同一个 fail-safe 方向。
|
|
898
|
+
// 已知残余面(symlink 形看不见)与终局 seam(core [2751] 给 HookToolContext 加只读 env 能力窄面,
|
|
899
|
+
// 排 174/#22 窗;到货后本过渡形整体退役换正解)成文在 lexical-path-env.ts 的文件头。
|
|
873
900
|
//
|
|
874
901
|
// 修5(三路复审 W4,cwd 基准漂移):gate 的 cwd 基准=与 hands 真跑处同源,分三形:
|
|
875
902
|
// · session 注册过 cwd(cwdHonored 已在注册处把关)⇒ 用它——与 host factory 的 effectiveHostWorkspace
|
|
@@ -881,6 +908,32 @@ export function createResolveSpec(ctx) {
|
|
|
881
908
|
// (hand 工具的书面契约形)与 scratchpad exempt 均不受影响。
|
|
882
909
|
// · provider 未设(in-process host / run-local)⇒ hands 就在本进程 cwd 跑(无 factory),process.cwd()
|
|
883
910
|
// 正是真工作目录,保留。
|
|
911
|
+
// 两形**共有**的两件(都与 env/cwd 无关,故不随 lane 分叉——单一属主,两臂各抄一份正是漂移的成因):
|
|
912
|
+
const gateCommon = {
|
|
913
|
+
// ③ (core 1.295) sensitive-path write deny set:组合进 gate 腿的同一 combinePolicies 折叠
|
|
914
|
+
// (deny 恒赢 —— 豁免/acceptDirs 越不过;集合取舍 core 成文,server 只透传 config 旋钮:
|
|
915
|
+
// 缺省 = core RECOMMENDED_SENSITIVE_PATTERNS,SENSITIVE_WRITE_PATTERNS 显式替换/off 关闭)。
|
|
916
|
+
// 沙箱 lane 上它按**词法** key 的路径段匹配(#156)——glob 本就只看段名,不需要真 fs。
|
|
917
|
+
...(config.sensitiveWritePatterns.length > 0 ? { sensitivePatterns: config.sensitiveWritePatterns } : {}),
|
|
918
|
+
// [1557]§四 的 shellGate 旧家已随 #153 搬到 applyRuntimeGovernance(governance 层无条件施加,
|
|
919
|
+
// 见上方 governedBase)——wiring 不再承运该旋钮,FsWriteGateWiring.shellGate 字段一并拆除。
|
|
920
|
+
// [841]① / core 1.294 exemption seam:gate 产 ask 前查同一只 approval_exemption 店(与 decide
|
|
921
|
+
// remember="session" 的授予、ask 政策层探针同店同 canonical toolName 键空间)——「本会话不再询问」
|
|
922
|
+
// 对 fs-write 门同 turn 即时生效(父+继承子任务,ask 路径不再重入)。has() reject ⇒ core 按
|
|
923
|
+
// 未豁免处理(fail-closed 照 ask),与店契约的调用方纪律一致;命中打 info 审计(对齐
|
|
924
|
+
// approval_exempted 的短路留痕,不记路径,路径留在 core 的 allow message 里)。探针是 name-keyed
|
|
925
|
+
// 的(不做任何 fs 裁决),所以沙箱 lane 照接不误。
|
|
926
|
+
...(approvalExemptionStore && auth?.sessionId
|
|
927
|
+
? {
|
|
928
|
+
isExempt: async (toolName) => {
|
|
929
|
+
const hit = await approvalExemptionStore.has(auth.sessionId, toolName);
|
|
930
|
+
if (hit)
|
|
931
|
+
logger.info("fs_write_gate_exempted", { toolName, sessionId: auth.sessionId });
|
|
932
|
+
return hit;
|
|
933
|
+
},
|
|
934
|
+
}
|
|
935
|
+
: {}),
|
|
936
|
+
};
|
|
884
937
|
const fsWriteGate = hostSemanticsLane
|
|
885
938
|
? (() => {
|
|
886
939
|
const sessionCwd = auth?.sessionId ? effectiveHostWorkspace(perSessionCwd.get(auth.sessionId), {}) : undefined;
|
|
@@ -892,32 +945,37 @@ export function createResolveSpec(ctx) {
|
|
|
892
945
|
env: new NodeExecutionEnv({ cwd: gateCwd }),
|
|
893
946
|
cwd: gateCwd,
|
|
894
947
|
...(scratchpadDir ? { scratchpadDir } : {}),
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
948
|
+
...gateCommon,
|
|
949
|
+
};
|
|
950
|
+
})()
|
|
951
|
+
: // 沙箱 lane(#156):无 fs 的词法裁决 env,且**刻意不给** cwd 与 scratchpadDir —— 两者都是
|
|
952
|
+
// 「自动放行域」,而在纯词法规则下一个放行域就是本设计唯一可能产生**错误 allow** 的地方
|
|
953
|
+
// (词法看不见 symlink:域内一个指向域外的软链会被判成域内)。其余裁决只会是 ask 或 deny。
|
|
954
|
+
//
|
|
955
|
+
// ⚠️ 沙箱侧 scratchpad 的对应物**确实存在**(plugins/remote-scratchpad.ts:`/tmp/scratchpad/<sessionId>`,
|
|
956
|
+
// e2b/k8s/local-docker/ssh 四 lane,由 factory 装饰器 lazy mkdir、并由下面的 envFacts 同源广告;
|
|
957
|
+
// adb 无此约定)。这里**明知而不豁免**:host lane 的同一豁免是经真 fs canonicalize 过的(软链会被
|
|
958
|
+
// 解到真身),词法版给不出同等保证,所以按 fail-closed 偏置落 ask。代价有界且可见——沙箱里写
|
|
959
|
+
// scratchpad 会多一次询问,而不是任何功能坏掉;终局 seam(core [2751] 真 env 窄面)到货后应当把
|
|
960
|
+
// 这条豁免按 host 同形补回。
|
|
961
|
+
{
|
|
962
|
+
env: new LexicalPathExecutionEnv(),
|
|
963
|
+
...gateCommon,
|
|
964
|
+
// codex 复审 B(2026-08-05,dist 亲读+实测):core 的 canonicalizeTarget 把反斜杠 UNC 形在**入口**
|
|
965
|
+
// 短路成 ok:true/key=原样——不经本 env,canon 便「成功」,gate 随后就咨询豁免探针;name-keyed
|
|
966
|
+
// 命中即 allow,「非 POSIX 绝对形一律 ask」就被豁免会话越过(相对形/盘符形没这个洞:它们的
|
|
967
|
+
// canon 失败先于豁免咨询)。沙箱臂因此给探针加 canonical-key 守门:只有 `/` 开头(=真经过
|
|
968
|
+
// LexicalPathExecutionEnv 词法归一)的 key 才享豁免,其余落回 defaultWrite 的 ask。host 臂不加
|
|
969
|
+
// (真 fs canonicalize 的 key 可信,[841]① 原契约不动)。
|
|
970
|
+
...(gateCommon.isExempt
|
|
907
971
|
? {
|
|
908
|
-
isExempt:
|
|
909
|
-
const hit = await approvalExemptionStore.has(auth.sessionId, toolName);
|
|
910
|
-
if (hit)
|
|
911
|
-
logger.info("fs_write_gate_exempted", { toolName, sessionId: auth.sessionId });
|
|
912
|
-
return hit;
|
|
913
|
-
},
|
|
972
|
+
isExempt: (toolName, canonicalPath) => canonicalPath.startsWith("/") ? gateCommon.isExempt(toolName) : false,
|
|
914
973
|
}
|
|
915
974
|
: {}),
|
|
916
975
|
};
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
//
|
|
920
|
-
// the workflow gate is name-keyed, not fs-adjudicated). Same store + canonical toolName key space as the
|
|
976
|
+
// [1248]②/codex F2 — the workflow ask leg's session-exemption probe. Name-keyed and never fs-adjudicated,
|
|
977
|
+
// so it was on EVERY lane from day one (the fs gate has been too since #156, by a different route: a
|
|
978
|
+
// lexical env rather than no gate). Same store + canonical toolName key space as the
|
|
921
979
|
// fs-gate probe — one remember="session" grant serves both; without it the durable lane re-parks every
|
|
922
980
|
// later run_workflow call after an operator already granted "don't ask again this session".
|
|
923
981
|
const workflowGate = approvalExemptionStore && auth?.sessionId
|
|
@@ -937,16 +995,16 @@ export function createResolveSpec(ctx) {
|
|
|
937
995
|
throw new HttpError(422, `settings are tighten-only and cannot loosen the deployment policy: ${e.message}`);
|
|
938
996
|
}
|
|
939
997
|
}
|
|
940
|
-
// subagent-hands follow-on (adversarial-review finding):
|
|
941
|
-
//
|
|
942
|
-
//
|
|
943
|
-
//
|
|
944
|
-
//
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
//
|
|
998
|
+
// subagent-hands follow-on (adversarial-review finding): a read-only (permissionMode=plan) parent that mounts
|
|
999
|
+
// the delegation tool used to let the CHILD get WRITABLE hands — reachable once subRunner grew an
|
|
1000
|
+
// executionEnvFactory — which defeats plan mode's read-only contract. So a hands-read-only run mounts no
|
|
1001
|
+
// delegation tool at all.
|
|
1002
|
+
// 🔴 复审 2026-08-05 亲验安装包(core 5.13.x)后修口:当年那句「core 不把 handsReadOnly/shellGate 传给
|
|
1003
|
+
// 子任务」**已过期** —— 子 spec 现在两者都继承(subagent.js / prepare-task.js 的 inheritedGateForChildren,
|
|
1004
|
+
// 引文在 stripDelegationTools 的注释里)。本行因此是同向第二层(更严不更松),不是唯一防线;要拆得单独决定。
|
|
1005
|
+
// `stripDelegationTools`(runtime-governance.ts,过滤器本体在那里被测钉住)按 core 单源常量
|
|
1006
|
+
// `DEFAULT_SUBAGENT_TOOL_NAME`(="Agent")RAW 比对——5.0.0 RB-476 退役了折叠面,旧名 "Task"/"Fork" 在
|
|
1007
|
+
// roster 层就 miss、不再需要归一(常量引用本身即防当年 1.202 改名时裸字符串失配的那类 fail-open)。
|
|
950
1008
|
if (governed.handsReadOnly === true && Array.isArray(governed.tools)) {
|
|
951
1009
|
governed = { ...governed, tools: stripDelegationTools(governed.tools) };
|
|
952
1010
|
}
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* across a rename of this file's *contents*; a path rename (this move, 2026-07-29) does require the
|
|
40
40
|
* importers to update, which was done in the same commit.
|
|
41
41
|
*/
|
|
42
|
-
export { fetchEffective, fetchPrincipalCaps, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
42
|
+
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
43
43
|
export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, runtimeHasActiveGate, } from "./apply-effective.js";
|
|
44
44
|
export { restartReasons, planeHasActiveTiers, modelPlaneChanged, type RestartSlice, type RestartSliceCtx, type RestartSignal, } from "./restart-signal.js";
|
|
45
45
|
export { applyCenterSkills, resolveMcpServers, mcpForScenario } from "./skills-mcp.js";
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
* across a rename of this file's *contents*; a path rename (this move, 2026-07-29) does require the
|
|
40
40
|
* importers to update, which was done in the same commit.
|
|
41
41
|
*/
|
|
42
|
-
export { fetchEffective, fetchPrincipalCaps, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
42
|
+
export { fetchEffective, fetchPrincipalCaps, fetchPrincipalOrgMemory, ConfigCenterHttpError, fetchSkillContent, fetchPromptArtifact, fetchPromptBlob, } from "./http-client.js";
|
|
43
43
|
export { mutateInPlace, applyEffective, applyRuntimeGates, applyRuntimeHot, resolveDefaultModelName, logEffectiveDiff, runtimeHasActiveGate, } from "./apply-effective.js";
|
|
44
44
|
export { restartReasons, planeHasActiveTiers, modelPlaneChanged, } from "./restart-signal.js";
|
|
45
45
|
export { applyCenterSkills, resolveMcpServers, mcpForScenario } from "./skills-mcp.js";
|
|
@@ -41,6 +41,25 @@ export declare function fetchPrincipalCaps(baseUrl: string, token: string, princ
|
|
|
41
41
|
orgMemory?: unknown;
|
|
42
42
|
etag?: string;
|
|
43
43
|
} | null>;
|
|
44
|
+
/**
|
|
45
|
+
* design/170 件A —— 读 `?principal=` 响应里的 org 记忆解析段(org 目录腿的取数口)。
|
|
46
|
+
*
|
|
47
|
+
* 🔬 **为什么不复用 {@link fetchPrincipalCaps}**(复审车 D-2 修):C3「两张表失败域互不干扰」是在档
|
|
48
|
+
* 裁定,registry-core 的 `PrincipalCapsWire` 也正是为此把 org 段留成 `unknown`(「在本 schema 里校验
|
|
49
|
+
* 会把 org 段坏形连坐进 caps 腿」)。但隔离此前只做了**单向**:org 段坏不连坐 caps,caps 段坏却整体
|
|
50
|
+
* 连坐 org —— 治理面 `runtimeCaps`/`budget` 的一次值漂移会让整封 safeParse 失败、函数 throw,于是一个
|
|
51
|
+
* **形状完全正确**的 org 授权段读不出来,目录记 `fetch_failed`、准入铸瞬时拒。治理面的 fail-closed
|
|
52
|
+
* 极性是对的(它管的是「别静默放松治理」),但它不该决定 org 授权面的可用性。
|
|
53
|
+
*
|
|
54
|
+
* 所以这条腿用 {@link PrincipalOrgMemoryEnvelope} 只读它真正消费的两个字段:治理面漂移在这里是被
|
|
55
|
+
* `.passthrough()` 原样放过的未知键,不构成本腿的故障。段本身的形状校验仍在目录腿
|
|
56
|
+
* (`PrincipalOrgMemoryWire`),本腿保持薄透传不预判。
|
|
57
|
+
*
|
|
58
|
+
* 返回值三值语义:段键缺席 ⇒ `undefined`(旧 center 能力握手 ⇒ 目录判 `section_absent` 瞬时);
|
|
59
|
+
* 段在场 ⇒ 原值(空表 = 取数成功的负结果,与缺席不同格)。条件请求不适用(本腿恒不带 etag),
|
|
60
|
+
* 因此 304 在传输层就是 fail-loud。
|
|
61
|
+
*/
|
|
62
|
+
export declare function fetchPrincipalOrgMemory(baseUrl: string, token: string, principal: string, fetchImpl?: typeof fetch, worker?: string): Promise<unknown>;
|
|
44
63
|
/** An HTTP error from a config-center fetch that carries the response status so callers can branch on it
|
|
45
64
|
* (e.g. translate a 404 to "unknown hash → undefined") WITHOUT parsing the message string. */
|
|
46
65
|
export declare class ConfigCenterHttpError extends Error {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* config-center HTTP client — the transport calls the service makes against the config-center
|
|
3
|
-
* (`fetchEffective`/`fetchPrincipalCaps`/`fetchSkillContent`/`fetchPromptArtifact
|
|
4
|
-
* the shared HTTP-error type.
|
|
3
|
+
* (`fetchEffective`/`fetchPrincipalCaps`/`fetchPrincipalOrgMemory`/`fetchSkillContent`/`fetchPromptArtifact`/
|
|
4
|
+
* `fetchPromptBlob`) plus the shared HTTP-error type. 后两条 `?principal=` 腿共用 `requestPrincipalView`
|
|
5
|
+
* 传输半场与 `assertPrincipalEcho` 回声核,只在**解析投影**上分家(见 `fetchPrincipalOrgMemory` 头注)。 Split out of `facade.ts` (design/158 A13, internal-lossless — the
|
|
5
6
|
* facade re-exports every symbol below unchanged).
|
|
6
7
|
*/
|
|
7
8
|
import { createHash } from "node:crypto";
|
|
@@ -72,44 +73,19 @@ export async function fetchEffective(baseUrl, token, etag, fetchImpl = fetch, wo
|
|
|
72
73
|
* content-hashed (`p-<hash>`); an `if-none-match` poll is cheap. Mirrors `fetchEffective` (Bearer + 8s + scheme guard).
|
|
73
74
|
*/
|
|
74
75
|
export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchImpl = fetch, worker) {
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
77
|
-
throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
|
|
78
|
-
// `?principal=<id>[&worker=<self>]` — worker authorizes the wpt_<self> caps-only path; harmless on a full token.
|
|
79
|
-
const params = new URLSearchParams({ principal });
|
|
80
|
-
if (worker)
|
|
81
|
-
params.set("worker", worker);
|
|
82
|
-
const url = `${baseUrl.replace(/\/+$/, "")}/api/config/effective?${params.toString()}`;
|
|
83
|
-
const res = await fetchImpl(url, {
|
|
84
|
-
headers: { authorization: `Bearer ${token}`, ...(etag ? { "if-none-match": etag } : {}) },
|
|
85
|
-
signal: AbortSignal.timeout(8000),
|
|
86
|
-
});
|
|
87
|
-
if (res.status === 304) {
|
|
88
|
-
// A 304 is only valid as a reply to OUR conditional. If we sent no etag and still got 304, center is
|
|
89
|
-
// misbehaving — DON'T treat it as "unchanged" (that surfaces undefined caps = no restriction = fail OPEN).
|
|
90
|
-
// Throw so the resolver fail-closes (review B). With an etag, 304 = genuine "unchanged" → null.
|
|
91
|
-
if (!etag)
|
|
92
|
-
throw new Error("config-center returned 304 to a non-conditional principal-caps request");
|
|
76
|
+
const raw = await requestPrincipalView(baseUrl, token, principal, etag, fetchImpl, worker);
|
|
77
|
+
if (raw === null)
|
|
93
78
|
return null;
|
|
94
|
-
}
|
|
95
|
-
if (!res.ok)
|
|
96
|
-
throw new Error(`config-center principal-caps HTTP ${res.status}`);
|
|
97
79
|
// 边界读=schema(clay 宪法 [2704] §2,同源锚=registry-core PrincipalCapsWire):治理面
|
|
98
80
|
// runtimeCaps/budget 严格+passthrough(值漂移整体拒 ⇒ 本函数 throw ⇒ resolver fail-close,
|
|
99
81
|
// 「outage 绝不静默 GRANT」同向);[565]/[580] fail-open 面与 orgMemory(C3 失败域隔离)在
|
|
100
82
|
// schema 里保持宽读,归一/校验仍在下方与目录腿(极性不因 schema 化翻转)。
|
|
101
|
-
const parsedBody = PrincipalCapsWire.safeParse(
|
|
83
|
+
const parsedBody = PrincipalCapsWire.safeParse(raw.json);
|
|
102
84
|
if (!parsedBody.success) {
|
|
103
85
|
throw new Error(`config-center principal-caps body failed schema validation: ${parsedBody.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ").slice(0, 600)}`);
|
|
104
86
|
}
|
|
105
87
|
const body = parsedBody.data;
|
|
106
|
-
|
|
107
|
-
// 回带 principal——在场且与请求不符 = 中间层/LB 把别人的 caps 视图递了过来,弃用整响应(throw ⇒
|
|
108
|
-
// caps 腿 fail-close、org 目录腿记 unavailable)。键缺席 = 旧 center 形,按无证据容忍(回声核只拦
|
|
109
|
-
// 「有回声且对不上」的确定性错递,不把旧形升级成故障)。
|
|
110
|
-
if (body.principal !== undefined && body.principal !== principal) {
|
|
111
|
-
throw new Error(`config-center principal-caps response echoes principal ${JSON.stringify(body.principal)} but ${JSON.stringify(principal)} was requested — discarding the whole response (cross-tenant delivery defense)`);
|
|
112
|
-
}
|
|
88
|
+
assertPrincipalEcho(body.principal, principal);
|
|
113
89
|
return {
|
|
114
90
|
runtimeCaps: body.runtimeCaps ?? null,
|
|
115
91
|
configured: Boolean(body.configured),
|
|
@@ -166,9 +142,82 @@ export async function fetchPrincipalCaps(baseUrl, token, principal, etag, fetchI
|
|
|
166
142
|
// org 目录腿据此判 section_absent 瞬时;形状校验是 org-memory-admission 模块用 PrincipalOrgMemoryWire
|
|
167
143
|
// 的事,本腿保持薄透传不预判)。
|
|
168
144
|
...("orgMemory" in body ? { orgMemory: body.orgMemory } : {}),
|
|
169
|
-
etag:
|
|
145
|
+
etag: raw.etag, // 与提取共用件之前逐字等价:键恒在场,无 ETag 头时值为 undefined(消费端只读真值)
|
|
170
146
|
};
|
|
171
147
|
}
|
|
148
|
+
/** design/170 件A C8(响应 principal 回声核):center 两条 `?principal=` 腿的 body 都回带 principal——
|
|
149
|
+
* 在场且与请求不符 = 中间层/LB 把别的 principal 的视图递了过来,弃用整响应(throw ⇒ caps 腿
|
|
150
|
+
* fail-close、org 目录腿记 unavailable)。键缺席 = 旧 center 形,按无证据容忍(回声核只拦「有回声
|
|
151
|
+
* 且对不上」的确定性错递,不把旧形升级成故障)。两条腿共用同一道闸,不各写一遍。 */
|
|
152
|
+
function assertPrincipalEcho(echoed, principal) {
|
|
153
|
+
if (echoed !== undefined && echoed !== principal) {
|
|
154
|
+
throw new Error(`config-center principal-caps response echoes principal ${JSON.stringify(echoed)} but ${JSON.stringify(principal)} was requested — discarding the whole response`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** 两条 `?principal=` 腿共用的**传输**半场:scheme 闸 / `?principal=[&worker=]` / Bearer / 8s 超时 /
|
|
158
|
+
* 304 语义。逐字来自原 {@link fetchPrincipalCaps} 函数体,提成共用件是为了第二条腿(org 段读取)
|
|
159
|
+
* 不必抄一遍传输纪律——抄一遍就等于给同一条边界立两套会各自漂移的规矩。
|
|
160
|
+
* 返回 `null` = 合法的 304(仅当我们真的发了条件请求);否则是**解析前**的 body 原值。 */
|
|
161
|
+
async function requestPrincipalView(baseUrl, token, principal, etag, fetchImpl, worker) {
|
|
162
|
+
const scheme = new URL(baseUrl).protocol;
|
|
163
|
+
if (scheme !== "http:" && scheme !== "https:")
|
|
164
|
+
throw new Error(`SEMA_REGISTRY_URL must be http(s), got "${scheme}"`);
|
|
165
|
+
// `?principal=<id>[&worker=<self>]` — worker authorizes the wpt_<self> caps-only path; harmless on a full token.
|
|
166
|
+
const params = new URLSearchParams({ principal });
|
|
167
|
+
if (worker)
|
|
168
|
+
params.set("worker", worker);
|
|
169
|
+
const url = `${baseUrl.replace(/\/+$/, "")}/api/config/effective?${params.toString()}`;
|
|
170
|
+
const res = await fetchImpl(url, {
|
|
171
|
+
headers: { authorization: `Bearer ${token}`, ...(etag ? { "if-none-match": etag } : {}) },
|
|
172
|
+
signal: AbortSignal.timeout(8000),
|
|
173
|
+
});
|
|
174
|
+
if (res.status === 304) {
|
|
175
|
+
// A 304 is only valid as a reply to OUR conditional. If we sent no etag and still got 304, center is
|
|
176
|
+
// misbehaving — DON'T treat it as "unchanged" (that surfaces undefined caps = no restriction = fail OPEN).
|
|
177
|
+
// Throw so the resolver fail-closes (review B). With an etag, 304 = genuine "unchanged" → null.
|
|
178
|
+
if (!etag)
|
|
179
|
+
throw new Error("config-center returned 304 to a non-conditional principal-caps request");
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
if (!res.ok)
|
|
183
|
+
throw new Error(`config-center principal-caps HTTP ${res.status}`);
|
|
184
|
+
const responseEtag = res.headers.get("etag");
|
|
185
|
+
return { json: await res.json(), ...(responseEtag !== null ? { etag: responseEtag } : {}) };
|
|
186
|
+
}
|
|
187
|
+
/** 件A C3 失败域隔离的读取投影:org 目录腿只消费两个字段——`principal`(回声核)与 `orgMemory` 段
|
|
188
|
+
* 原值。用 `PrincipalCapsWire.pick()` 取子集(schema 属主仍是 registry-core,这里不复制形状定义,
|
|
189
|
+
* `.passthrough()` 随 `_def` 一并继承)。 */
|
|
190
|
+
const PrincipalOrgMemoryEnvelope = PrincipalCapsWire.pick({ principal: true, orgMemory: true });
|
|
191
|
+
/**
|
|
192
|
+
* design/170 件A —— 读 `?principal=` 响应里的 org 记忆解析段(org 目录腿的取数口)。
|
|
193
|
+
*
|
|
194
|
+
* 🔬 **为什么不复用 {@link fetchPrincipalCaps}**(复审车 D-2 修):C3「两张表失败域互不干扰」是在档
|
|
195
|
+
* 裁定,registry-core 的 `PrincipalCapsWire` 也正是为此把 org 段留成 `unknown`(「在本 schema 里校验
|
|
196
|
+
* 会把 org 段坏形连坐进 caps 腿」)。但隔离此前只做了**单向**:org 段坏不连坐 caps,caps 段坏却整体
|
|
197
|
+
* 连坐 org —— 治理面 `runtimeCaps`/`budget` 的一次值漂移会让整封 safeParse 失败、函数 throw,于是一个
|
|
198
|
+
* **形状完全正确**的 org 授权段读不出来,目录记 `fetch_failed`、准入铸瞬时拒。治理面的 fail-closed
|
|
199
|
+
* 极性是对的(它管的是「别静默放松治理」),但它不该决定 org 授权面的可用性。
|
|
200
|
+
*
|
|
201
|
+
* 所以这条腿用 {@link PrincipalOrgMemoryEnvelope} 只读它真正消费的两个字段:治理面漂移在这里是被
|
|
202
|
+
* `.passthrough()` 原样放过的未知键,不构成本腿的故障。段本身的形状校验仍在目录腿
|
|
203
|
+
* (`PrincipalOrgMemoryWire`),本腿保持薄透传不预判。
|
|
204
|
+
*
|
|
205
|
+
* 返回值三值语义:段键缺席 ⇒ `undefined`(旧 center 能力握手 ⇒ 目录判 `section_absent` 瞬时);
|
|
206
|
+
* 段在场 ⇒ 原值(空表 = 取数成功的负结果,与缺席不同格)。条件请求不适用(本腿恒不带 etag),
|
|
207
|
+
* 因此 304 在传输层就是 fail-loud。
|
|
208
|
+
*/
|
|
209
|
+
export async function fetchPrincipalOrgMemory(baseUrl, token, principal, fetchImpl = fetch, worker) {
|
|
210
|
+
const raw = await requestPrincipalView(baseUrl, token, principal, undefined, fetchImpl, worker);
|
|
211
|
+
// etag 恒缺席 ⇒ 304 已在传输层 throw;这一支只为类型收口(不可达)。
|
|
212
|
+
if (raw === null)
|
|
213
|
+
throw new Error("config-center returned 304 to a non-conditional org-directory fetch");
|
|
214
|
+
const parsed = PrincipalOrgMemoryEnvelope.safeParse(raw.json);
|
|
215
|
+
if (!parsed.success) {
|
|
216
|
+
throw new Error(`config-center org-memory section failed schema validation: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ").slice(0, 600)}`);
|
|
217
|
+
}
|
|
218
|
+
assertPrincipalEcho(parsed.data.principal, principal);
|
|
219
|
+
return parsed.data.orgMemory;
|
|
220
|
+
}
|
|
172
221
|
/** An HTTP error from a config-center fetch that carries the response status so callers can branch on it
|
|
173
222
|
* (e.g. translate a 404 to "unknown hash → undefined") WITHOUT parsing the message string. */
|
|
174
223
|
export class ConfigCenterHttpError extends Error {
|
package/dist/config-types.d.ts
CHANGED
|
@@ -21,6 +21,44 @@ export interface ScopedMcpServer {
|
|
|
21
21
|
* off (the control plane never mounts). The runner-facing facts (the runner principal, the build-host CACHE_BASE,
|
|
22
22
|
* the default base ref) live here so image-api injects only VETTED, server-fixed values into the build.sh argv —
|
|
23
23
|
* operator free-text never reaches the runner. */
|
|
24
|
+
/** #151 车3(design/172 流内审批协议)的配置面。总开关默认 **false**;四个从属旋钮只在开关为真时生效
|
|
25
|
+
* (语义与「谁需要 / 谁被伤 / 什么补偿」三问见 `ServiceConfigFlat.streamApproval` 的注)。 */
|
|
26
|
+
export interface StreamApprovalConfig {
|
|
27
|
+
/** 协议总开关。`STREAM_APPROVAL_ENABLED`,默认 **false**。关 ⇒ 全链逐字现行为。
|
|
28
|
+
* 翻真要等回决端点(车4)与对账收敛器(车5)到位——在此之前是个不完整协议(有卡无正规回决口、
|
|
29
|
+
* 有 PARKING 无收敛器、跨副本回决无唤醒路径)。 */
|
|
30
|
+
enabled: boolean;
|
|
31
|
+
/** 开关为真时作为 `ToolApprovalCoordinator` 的 `ttlMs`(design/172 §3.3 的「配置窗默认」)。
|
|
32
|
+
* `STREAM_ASK_WINDOW_MS`,默认 **60000**。`0` = 运维显式关窗 ⇒ 恒 park(§3.3 窗=0 语义)。
|
|
33
|
+
* 开关为假 ⇒ 本值不参与,窗保持既有 `DEFAULT_APPROVAL_TTL_MS`(5min)。 */
|
|
34
|
+
windowMs: number;
|
|
35
|
+
/** 开流重放(§5)每次最多投几张未决卡——**读面**的帽,超出只投最新的并记一次 warn,开流不失败。
|
|
36
|
+
* `STREAM_APPROVAL_REPLAY_MAX`,默认 **50**。 */
|
|
37
|
+
replayMax: number;
|
|
38
|
+
/** 写侧准入门(§0 X-2)的 per-task 未决 ask 上限。超限 ⇒ 在落 pending/建 timer/发帧**之前**返回
|
|
39
|
+
* `"unavailable"`(park 路由,**永不 deny**——过载 bypass 到 park 是 §3.3 的硬条款)。
|
|
40
|
+
* `STREAM_APPROVAL_ADMIT_MAX_PER_TASK`,默认 **32**。消费点在刀 3b。 */
|
|
41
|
+
admitMaxPerTask: number;
|
|
42
|
+
/** 同上,per-owner(租户)维。`STREAM_APPROVAL_ADMIT_MAX_PER_OWNER`,默认 **256**。 */
|
|
43
|
+
admitMaxPerOwner: number;
|
|
44
|
+
/** #151 车5 §5 治理旋钮(全部 `numEnvBounded` 有界解析,§9 C6)—— 对账收敛器每 tick **每段**
|
|
45
|
+
* (PARKING / 孤儿 STREAM_PENDING)最多处理的行数。超出的下轮接着扫(靠 `deferReconcile` 的队列
|
|
46
|
+
* 轮转保证「接着」是真的,不是永远同一批)。`STREAM_APPROVAL_RECONCILE_BATCH`,默认 **200**,
|
|
47
|
+
* 有界 [1, 10000]。 */
|
|
48
|
+
reconcileBatch: number;
|
|
49
|
+
/** 崩溃恢复扫描的**宽限**(§8 D-1.1):只有 `expiresAtMs + 本值 < now` 的孤儿 `STREAM_PENDING` 行才
|
|
50
|
+
* 被 reaper 代打 `expireAsk`。活属主进程里的窗到期竞争者在这个宽限内常态必胜 ⇒ reaper 退回纯崩溃
|
|
51
|
+
* 兜底,不与在场闭包抢(抢 = 制造挂死面)。`STREAM_APPROVAL_PENDING_GRACE_MS`,默认 **30000**,
|
|
52
|
+
* 有界 [0, 3600000]。 */
|
|
53
|
+
pendingGraceMs: number;
|
|
54
|
+
/** 判据 4(adhoc 腿)的窗后宽限。`STREAM_APPROVAL_ADHOC_GRACE_MS`,默认 **60000**,有界 [0, 86400000]。
|
|
55
|
+
* 🔴 跨旋钮不变量:必须 **< `orphanTtlMs`**(否则判据 5 的兜底会先于判据 4 触发,adhoc 腿拿到的
|
|
56
|
+
* 归因就成了 `orphan_ttl_exceeded`,归因诚实性失效)——boot 期校验,破坏形**拒启**。 */
|
|
57
|
+
adhocGraceMs: number;
|
|
58
|
+
/** 判据 5(遗孤最终可判)的上界,量 immutable 的 `createdAtMs`。`STREAM_APPROVAL_ORPHAN_TTL_MS`,
|
|
59
|
+
* 默认 **7d**,有界 [60000, 90d]。 */
|
|
60
|
+
orphanTtlMs: number;
|
|
61
|
+
}
|
|
24
62
|
export interface ImageBakeConfig {
|
|
25
63
|
enabled: boolean;
|
|
26
64
|
/** The dedicated operator principal the bake-runner authenticates as on the internal claim/ingest routes
|
|
@@ -717,14 +755,23 @@ export interface ServiceConfigFlat {
|
|
|
717
755
|
* recommended set (not a merge); `off` or an empty value = disabled (empty array); unset = core's recommended
|
|
718
756
|
* set. Deny composes deny-wins, so a session exemption / acceptEdits auto-allow never crosses it. */
|
|
719
757
|
sensitiveWritePatterns: string[];
|
|
720
|
-
/** [1557]§四 opt-in (cli[1555]② finding, core[1556] suggested mechanism): CC manual-family permission
|
|
721
|
-
* (default/auto/acceptEdits) gate every fs WRITE hand tool (Write/Edit/NotebookEdit) but never touched
|
|
722
|
-
* a model can `echo -n '…' > file` straight past the write-approval card. `MANUAL_MODE_SHELL_GATE`
|
|
723
|
-
* deployment into ALSO tightening `TaskSpec.shellGate`
|
|
724
|
-
*
|
|
758
|
+
/** [1557]§四 opt-in (cli[1555]② finding, core[1556] suggested mechanism): the CC manual-family permission
|
|
759
|
+
* modes (default/auto/acceptEdits) gate every fs WRITE hand tool (Write/Edit/NotebookEdit) but never touched
|
|
760
|
+
* Bash — a model can `echo -n '…' > file` straight past the write-approval card. `MANUAL_MODE_SHELL_GATE`
|
|
761
|
+
* opts a deployment into ALSO tightening `TaskSpec.shellGate` (core `bashReversibilityProbe` under
|
|
762
|
+
* `"classify"` asks on constructively-irreversible commands — shell redirects etc. — **plus** the boundary
|
|
763
|
+
* case in the 🔴 note below; it is not a "constructive-irreversibility only" filter).
|
|
764
|
+
* 🔴 **施加点 = governance 层,无条件(#153 / [2713] 分单②;搬家前的旧描述已作废)**:与 `AUTONOMY`
|
|
765
|
+
* 同拍在 `applyRuntimeGovernance`(runtime-governance.ts)里施加,**不看客户端 settings/permissionMode
|
|
766
|
+
* 的在场性、不看落在哪个模式臂、不看执行 lane**。旧家挂在 task-settings 的 default/auto/acceptEdits 三臂
|
|
767
|
+
* + host-lane fs-write wiring 上,等于让一个部署级旋钮的生死由客户端表态决定(print/headless 腿不 stamp
|
|
768
|
+
* settings ⇒ 整块跳过;bypassPermissions 臂不回 shellGate;非 host lane wiring 不建 ⇒ 沙箱 lane 全失效)。
|
|
769
|
+
* rank 合成:与 autonomy 派生值取大(`ask`→`"always"` 压过 `"classify"`),对 base 只升不降。
|
|
770
|
+
* 客户端 settings 折叠在其后、tighten-only,拿不掉这个值;SUP 路由姿态只会把它抬到 `"always"`。
|
|
725
771
|
* 🔴 **语义随 core 版本演进(2026-07-31 核)**:`"classify"` 下「纯读自动放行」这句自 core 2.7.0 起
|
|
726
772
|
* **不再无条件成立** —— RB-412 给 classifier 加了 root 边界扫描,core 内部自铸 boundary
|
|
727
|
-
* (`prepare-task.ts` 的 `shellReadBoundary`:roots=任务 root+additionalRoots,
|
|
773
|
+
* (`prepare-task.ts` 的 `shellReadBoundary`:roots=任务 root+additionalRoots+additionalReadRoots,
|
|
774
|
+
* cwd=活 handsCwd),
|
|
728
775
|
* **读到 roots 之外的路径同样会 ask**(server 侧零接线即得:我们只传 shellGate,边界是 core 自算的)。
|
|
729
776
|
* 旧 core(<2.7.0)下仍是名字级判定=越界读放行,两者都能工作、后者更严 ⇒ floor 不因此抬。
|
|
730
777
|
* Absent/unset = `undefined` = **zero behavior change** (this is a tighten-ONLY opt-in, never a
|
|
@@ -790,6 +837,28 @@ export interface ServiceConfigFlat {
|
|
|
790
837
|
* cap → posture-gated like askQuestion (single-user turnkey → ON; multi-tenant opt-in);
|
|
791
838
|
* `TOOL_APPROVAL_ENABLED=true/false` overrides. Absent ⇒ core's headless auto-deny stands ([819]⑤ fail-closed). */
|
|
792
839
|
toolApprovalEnabled: boolean;
|
|
840
|
+
/** #151 车2(design/172 §3.3 D3,窗长三元的安全余量):`ToolApprovalCoordinator` 的可选 `windowMarginMs`
|
|
841
|
+
* 构造项——有效窗 = `min(ttlMs, legRemainingMs − 本值)`,余量不足 ⇒ 不开窗直接走窗到期同路(park)。
|
|
842
|
+
* 仅在装配点把 `ToolApprovalRunContext.legDeadlineMonotonic` 传给协调器(车3 的活)时才实际生效——本
|
|
843
|
+
* 旋钮本身缺省对现行为零影响(D1)。`STREAM_ASK_WINDOW_MARGIN_MS`,默认 10000(10s)。ttl 概念沿用既有
|
|
844
|
+
* `approvalTimeoutSec` 族,本旋钮不新增第二个 TTL 概念。 */
|
|
845
|
+
streamAskWindowMarginMs: number;
|
|
846
|
+
/** #151 车3(design/172 流内审批协议)总开关 + 四个从属旋钮。**协议整体默认 OFF**——开关关闭时全链
|
|
847
|
+
* 逐字现行为:不发 `approval_request` 帧、不落 `approval_asks` 行、窗 = 既有 `DEFAULT_APPROVAL_TTL_MS`
|
|
848
|
+
* (5min)、`askStore` 不注入协调器。理由(三问):
|
|
849
|
+
* - 谁需要 60s 窗:开了协议的部署——approver 就盯着流、秒级应答,窗短 ⇒ 少占一条活腿、更快落到可恢复
|
|
850
|
+
* 的 park 面。这是协议的设计意图,不是随手取的数。
|
|
851
|
+
* - 谁被伤:①未 opt-in 的存量部署(把 5min 砍成 60s = 人离开工位 90 秒回来卡已经没了);②**park 设施
|
|
852
|
+
* 不在场**的部署——窗到期后 `"unavailable"` 没有降级目的地,core fail-closed deny,60s 会把
|
|
853
|
+
* 「人 90 秒后批准 = 放行」变成「拒绝」,是真行为回归。
|
|
854
|
+
* - 补偿:把 60s 绑在**新旋钮 + 新开关**上,一个字不动 `DEFAULT_APPROVAL_TTL_MS`;两边都不伤,也不给
|
|
855
|
+
* 既有腿新增第二个 TTL 概念。
|
|
856
|
+
*
|
|
857
|
+
* `STREAM_APPROVAL_ENABLED` / `STREAM_ASK_WINDOW_MS` / `STREAM_APPROVAL_REPLAY_MAX` /
|
|
858
|
+
* `STREAM_APPROVAL_ADMIT_MAX_PER_TASK` / `STREAM_APPROVAL_ADMIT_MAX_PER_OWNER` /
|
|
859
|
+
* `STREAM_APPROVAL_RECONCILE_BATCH` / `STREAM_APPROVAL_PENDING_GRACE_MS` /
|
|
860
|
+
* `STREAM_APPROVAL_ADHOC_GRACE_MS` / `STREAM_APPROVAL_ORPHAN_TTL_MS`(后四键 = 车5 收敛器)。 */
|
|
861
|
+
streamApproval: StreamApprovalConfig;
|
|
793
862
|
/** [824]① (clay A 案 = workflow 权限全面 CC parity): workflow 子 agent 的默认权限基线与主 LLM 同权(base `{}`)。
|
|
794
863
|
* 安全论证:主 LLM 与子 agent 同 root 同信任域,主 LLM 本就能写这棵树([816] ask 门照管),单独钳子 agent 的
|
|
795
864
|
* 安全增益≈0(只防绕路不防直路的门不是边界);实测产品代价=[814]A 死锁。`WORKFLOW_AGENTS_READONLY=true` =
|
|
@@ -906,7 +975,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
|
|
|
906
975
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "cascadeLadder" | "degrade">;
|
|
907
976
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|
|
908
977
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
909
|
-
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "toolApprovalEnabled" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
|
|
978
|
+
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "askQuestionEnabled" | "toolApprovalEnabled" | "streamAskWindowMarginMs" | "streamApproval" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate">;
|
|
910
979
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
911
980
|
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memorySync" | "memoryOrgAdmissionMode" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
912
981
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
package/dist/config.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ export declare function drainConfigNotices(): Array<{
|
|
|
60
60
|
* (run-local historically did exactly that), so the drain lives beside the collectors, not in one entrypoint. */
|
|
61
61
|
export declare function logConfigDiagnostics(logger: {
|
|
62
62
|
warn: (event: string, fields?: Record<string, unknown>) => void;
|
|
63
|
+
info?: (event: string, fields?: Record<string, unknown>) => void;
|
|
63
64
|
debug?: (event: string, fields?: Record<string, unknown>) => void;
|
|
64
65
|
}): void;
|
|
65
66
|
/** design/158 N8/N9 — the BOOLEAN member of this module's env-parser family (`env`/`env2`/`numEnv`/
|