@sema-agent/server 7.5.0 → 7.7.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/LICENSE +1 -1
- package/README.md +18 -3
- package/README.zh-CN.md +14 -3
- package/USAGE.md +100 -2
- package/dist/approval-reconciler.d.ts +1 -1
- package/dist/approval-reconciler.js +6 -5
- package/dist/boot/budget-tracing.js +1 -1
- package/dist/boot/config-center.js +17 -3
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/parked-revive-gate.d.ts +78 -0
- package/dist/boot/parked-revive-gate.js +114 -0
- package/dist/boot/resolve-spec.d.ts +8 -20
- package/dist/boot/resolve-spec.js +40 -125
- package/dist/budget.d.ts +1 -1
- package/dist/budget.js +1 -1
- package/dist/capabilities/hands-lane.d.ts +91 -0
- package/dist/capabilities/hands-lane.js +104 -0
- package/dist/capabilities/repo-tools.d.ts +1 -1
- package/dist/capabilities/repo-tools.js +8 -2
- package/dist/capabilities/scenarios.d.ts +49 -2
- package/dist/capabilities/scenarios.js +84 -16
- package/dist/config-provider.d.ts +1 -0
- package/dist/config-provider.js +23 -3
- package/dist/config-types.d.ts +5 -3
- package/dist/config.js +5 -4
- package/dist/deployment-governance.d.ts +168 -0
- package/dist/deployment-governance.js +206 -0
- package/dist/fleet/fleet-bus.d.ts +11 -1
- package/dist/fleet/fleet-bus.js +43 -0
- package/dist/fleet/fleet-terminal-window.d.ts +98 -0
- package/dist/fleet/fleet-terminal-window.js +319 -0
- package/dist/hooks/hook-runner.js +11 -0
- package/dist/http/route-ctx.d.ts +7 -1
- package/dist/http/routes/approvals-assistant.js +6 -7
- package/dist/http/routes/fleet.js +164 -14
- package/dist/http/routes/runs.js +7 -2
- package/dist/http/routes/tasks.js +5 -4
- package/dist/http/routes/trace-usage.js +3 -4
- package/dist/http/send.d.ts +23 -0
- package/dist/http/send.js +23 -0
- package/dist/http/server.d.ts +10 -0
- package/dist/http/server.js +28 -12
- package/dist/http/sse-log.js +3 -4
- package/dist/leader/diffout.d.ts +10 -0
- package/dist/leader/diffout.js +14 -2
- package/dist/leader/diffup.js +3 -2
- package/dist/leader/planner.js +7 -0
- package/dist/main.js +79 -32
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/tool-trace.d.ts +13 -0
- package/dist/observability/tool-trace.js +14 -0
- package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
- package/dist/orchestration/workflow-notify-journal.js +14 -34
- package/dist/plugins/approval-ask-store-sql.d.ts +33 -0
- package/dist/plugins/approval-ask-store-sql.js +66 -33
- package/dist/plugins/background-agent-store-sql.js +16 -16
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +5 -2
- package/dist/plugins/checkpoint-store-sql.js +5 -2
- package/dist/plugins/image-bake-store-sql.d.ts +1 -1
- package/dist/plugins/image-bake-store-sql.js +27 -27
- package/dist/plugins/image-index-sql.js +15 -15
- package/dist/plugins/mailbox-store-sql.js +3 -3
- package/dist/plugins/memory-engine-pg.js +9 -9
- package/dist/plugins/memory-engine-tidb.js +7 -7
- package/dist/plugins/memory-sync-store-pg.js +13 -13
- package/dist/plugins/memory-sync-store-tidb.js +5 -5
- package/dist/plugins/outcome-ledger-sql.js +7 -7
- package/dist/plugins/pg-cost-quota.js +3 -3
- package/dist/plugins/pg-pool.js +84 -75
- package/dist/plugins/pg-rate-limiter.js +3 -3
- package/dist/plugins/pg-session-storage.d.ts +1 -1
- package/dist/plugins/pg-session-storage.js +12 -13
- package/dist/plugins/remote-env-host.js +3 -1
- package/dist/plugins/remote-env-local-docker.js +6 -3
- package/dist/plugins/remote-env-ssh.d.ts +13 -1
- package/dist/plugins/roster-store-sql.js +8 -8
- package/dist/plugins/store-contracts.d.ts +19 -0
- package/dist/plugins/store-contracts.js +42 -0
- package/dist/plugins/task-attachment-store.js +5 -5
- package/dist/plugins/task-list-store-sql.js +1 -1
- package/dist/plugins/tidb-cost-quota.js +1 -1
- package/dist/plugins/tidb-pool.js +83 -60
- package/dist/plugins/tidb-rate-limiter.js +1 -1
- package/dist/plugins/tidb-session-store.js +2 -5
- package/dist/plugins/tool-result-store-sql.js +2 -2
- package/dist/plugins/usage-window-store-sql.js +13 -13
- package/dist/plugins/write-behind-counter.d.ts +10 -2
- package/dist/plugins/write-behind-counter.js +13 -3
- package/dist/resource-suspend.d.ts +3 -1
- package/dist/resource-suspend.js +3 -1
- package/dist/run-local.d.ts +73 -1
- package/dist/run-local.js +160 -9
- package/dist/runs.d.ts +3 -1
- package/dist/runs.js +3 -1
- package/dist/security.d.ts +12 -0
- package/dist/security.js +12 -0
- package/dist/session-sync-kernel.d.ts +13 -0
- package/dist/session-sync-kernel.js +13 -0
- package/dist/trace/core-keyset-guard.d.ts +1 -1
- package/dist/trace/project.d.ts +10 -1
- package/dist/trace/project.js +31 -0
- package/package.json +3 -3
|
@@ -18,15 +18,16 @@
|
|
|
18
18
|
* 描述的代码下移进阶段②。
|
|
19
19
|
*/
|
|
20
20
|
import { realpathSync } from "node:fs";
|
|
21
|
-
import { join
|
|
22
|
-
import {
|
|
23
|
-
import { createDurableAskPolicy } from "../approval.js";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
import { NodeExecutionEnv, QUESTION_AWAITS_RESUME, expandTiers, tightenTaskSpec } from "@sema-agent/core";
|
|
24
23
|
import { cappedCeiling } from "../budget.js";
|
|
25
24
|
import { centerPromptProvider, centerIdentityAssembled } from "../capabilities/center-prompts.js";
|
|
26
25
|
import { SessionEnvironmentSelection, selectEnvironmentTool } from "../capabilities/select-environment-tool.js";
|
|
27
26
|
import { sendUserFileTool } from "../capabilities/send-user-file-tool.js";
|
|
28
27
|
import { gateScenarioRequest, mergeUserSkills, selectScenario } from "../capabilities/scenarios.js";
|
|
28
|
+
import { toolPolicyForHands } from "../capabilities/hands-lane.js";
|
|
29
29
|
import { applyLongtailDefer } from "../capabilities/tool-defer.js";
|
|
30
|
+
import { assertGuardPatternsUsable, buildOnlySensitiveBaselineWarning, createApprovalBaselinePolicy, createDeploymentGovernanceInputs } from "../deployment-governance.js";
|
|
30
31
|
import { acceptShellScratchpadDir, buildEnvFacts, egressForRemoteExec, ensureScratchpadDir, resumeFactsForLane } from "../env-facts.js";
|
|
31
32
|
import { FleetEventBus } from "../fleet/fleet-bus.js";
|
|
32
33
|
import { composeHooks, createTaskHooks } from "../hooks/hook-runner.js";
|
|
@@ -49,105 +50,25 @@ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, acceptAppendSystemPrompt, applyTaskSet
|
|
|
49
50
|
import { enableForkFromBody, normalizeRetainSubagentSessions, selfOrchestrationFromBody } from "../task-workflow.js";
|
|
50
51
|
import { redactSecrets } from "../trace/redact.js";
|
|
51
52
|
import { DeferredSandboxPathEnv, isSandboxPathAdjudicationLane, sandboxPathEnvSlots } from "./deferred-sandbox-path-env.js";
|
|
52
|
-
/** #152([2703] 案二):durable 部署上的 AskUserQuestion 门。活体面(QuestionCoordinator)缺席 ⇒ 原形
|
|
53
|
-
* `createDurableQuestionPolicy()`(恒 ask ⇒ 恒 durable park)。在场 ⇒ **判决时**按活流上下文分腿:
|
|
54
|
-
* 活流腿(bg/SSE,coordinator.runWithContext 包裹且投递面此刻可达,ALS 判)allow——工具执行落到
|
|
55
|
-
* RunnerDeps.onQuestion 的 coordinator,问正在 tail 流的活人;无活流腿(sync /v1/tasks、verify/cascade、
|
|
56
|
-
* durable resume 驱动、断连后的 detach 腿)ask——durable park 原语义逐字保留(#166 后无活流腿放行执行
|
|
57
|
-
* 也不会产出空答:coordinator 无 ALS ctx ⇒ 冻结 `{kind:"unavailable"}`(src/question.ts),永不悬挂;
|
|
58
|
-
* park 仍是把问题送到人面前的唯一那条腿,正当性不变、只是反事实前提换了)。
|
|
59
|
-
* 工具名字面量与 server.ts 的 pre-CAS 守卫同源("AskUserQuestion",core 未根导出常量)。
|
|
60
|
-
* ⚠️ **单一属主**(复审 A2):AskUserQuestion 的 durable 判决只有这一处。任何需要「同参重建」这条判决的
|
|
61
|
-
* 地方(main.ts 的 parkedReviveInheritedGate 父约束链)必须调本工厂,不得自折 core 原形——两份拷贝里
|
|
62
|
-
* 只改一份正是本条 finding 的成因。**登记豁免一处**:leader worker 腿(src/leader/wire.ts provisionWorker)
|
|
63
|
-
* 自折 core 原形——该腿无活体问答面可装且 leader 不 import boot 层(分层),core 原形+sentinel 即其完整
|
|
64
|
-
* 语义;豁免注在彼处互指,接活体面之日必须并回本工厂。 */
|
|
65
|
-
export function createDurableQuestionGate(live) {
|
|
66
|
-
if (live === undefined)
|
|
67
|
-
return createDurableQuestionPolicy();
|
|
68
|
-
return {
|
|
69
|
-
check(req) {
|
|
70
|
-
if (req.toolName !== "AskUserQuestion")
|
|
71
|
-
return { action: "allow" };
|
|
72
|
-
return live.hasLiveContext()
|
|
73
|
-
? { action: "allow" }
|
|
74
|
-
: { action: "ask", message: "AskUserQuestion: awaiting a human answer (durable)" };
|
|
75
|
-
},
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
/**
|
|
79
|
-
* 沙箱 lane 上**相对形**写目标的守卫补层用 env(codex 对抗复审 round1 finding 1,红先复现)。
|
|
80
|
-
*
|
|
81
|
-
* 缺口:沙箱 lane 的守卫策略拿不到 `rootPath`(沙箱 cwd 不是 server 能猜的,#165 裁定 1),而
|
|
82
|
-
* `DeferredSandboxPathEnv.absolutePath` 对相对形一律报错。core 的 `canonicalizeTarget` 在
|
|
83
|
-
* **absolutePath 失败**这一支不置 `unresolvedSymlink`,于是 `createSensitivePathPolicy` 走的是
|
|
84
|
-
* 「判不了就弃权」的 `allow`(dist 亲读)。写门在场时这条腿被门的 `ask` 兜住;而
|
|
85
|
-
* `bypassPermissions` / settings 缺席这几形**根本没有门**,于是 `Write(file_path: ".env")` 一路放行——
|
|
86
|
-
* 而 core 的结构化写工具会把相对形按 engine 跟踪的 cwd 解析后真写下去(fs-write.js `resolveKey`)。
|
|
87
|
-
*
|
|
88
|
-
* 补法:**同一只** `createSensitivePathPolicy` 再铸一个实例,只把「路径→canonical key」这一步换成
|
|
89
|
-
* 纯词法基准(本 env)。判定与提取(哪个参数是写目标、NotebookEdit 的 notebook_path 优先、段匹配)
|
|
90
|
-
* 全部仍是 core 的,server 侧零复刻——复刻 core 的裁决逻辑正是「同源谎」那一类错误。
|
|
91
|
-
*
|
|
92
|
-
* 三条不可动的边界:
|
|
93
|
-
* · **绝对形一律弃权**(absolutePath 报错 ⇒ canon 失败且非 unresolvedSymlink ⇒ core 判 allow):
|
|
94
|
-
* 绝对形归真身裁决那一层,#165「真身胜过名字」的裁定(域内良性软链名叫 `.ssh` 只 ask)不受影响。
|
|
95
|
-
* · **只会 deny,不会放行**:相对形自身拼写里出现的段,解析成绝对路径后仍在,所以词法命中即真命中;
|
|
96
|
-
* 反过来一条名叫 `.env` 而真身良性的相对软链会被误 deny —— 方向是 fail-closed,与守卫集语义同向。
|
|
97
|
-
* · 🔴 **覆盖面到此为止:cwd 里的守卫段看不见**(codex 对抗复审 round2,验真)。本层把相对形挂在 `/`
|
|
98
|
-
* 上,而工具是挂在 engine **活** cwd 上的:`cd .git` 后 `Write("config")` 真写 `<root>/.git/config`
|
|
99
|
-
* (命中缺省模式 `.git/config`),本层只看得到 `/config` ⇒ 不命中 ⇒ 弃权。同形的还有 cwd 落在
|
|
100
|
-
* `.ssh`/`.aws` 里再写叶名。**这一格今天无人裁决**(沙箱 lane ∧ 守卫集开 ∧ 写门不在场的
|
|
101
|
-
* bypass/settings 缺席两形)。收口只有两条路,都超出本件自裁范围,已上交:①(正解)一条「按当前
|
|
102
|
-
* 工具调用的活 cwd/env 裁决」的引擎缝——与子代继承那条残余面同一条缝;②(代价形)守卫集开启时把
|
|
103
|
-
* 沙箱 lane 的相对写目标一律 deny——那是拿「相对写从 ask 变 deny」换覆盖,有真受损方且无对应旋钮,
|
|
104
|
-
* 须部署方拍板。特征化钉见 test/task-settings.test.ts(「cwd 里的守卫段」那条),别把它读成已保护。
|
|
105
|
-
*/
|
|
106
|
-
class RelativeTargetLexicalEnv extends StubExecutionEnv {
|
|
107
|
-
/** 相对形 → `/<词法归一>`;绝对形 / 空串 / 含 NUL 一律报错(= 弃权,见类注)。 */
|
|
108
|
-
absolutePath(path) {
|
|
109
|
-
if (path.length === 0 || path.startsWith("/") || path.includes("\u0000")) {
|
|
110
|
-
return Promise.resolve(err(new FileError("not_supported", "this guard layer only adjudicates RELATIVE write targets (absolute forms are adjudicated against the real sandbox filesystem)", path)));
|
|
111
|
-
}
|
|
112
|
-
return Promise.resolve(ok(posix.normalize(`/${path}`)));
|
|
113
|
-
}
|
|
114
|
-
/** 恒「不存在」⇒ core 的 `canonicalizeNewPath` 逐级回退,最终把词法归一形当 canonical key 交给段匹配。
|
|
115
|
-
* 这里绝不能报错:报错会被 core 读成 `unresolvedSymlink` 而对**每一个**相对目标 deny(含普通文件)。 */
|
|
116
|
-
exists(_path, _abortSignal) {
|
|
117
|
-
return Promise.resolve(ok(false));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
53
|
export function createResolveSpec(ctx) {
|
|
121
|
-
const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, } = ctx;
|
|
54
|
+
const { config, logger, metrics, localRoot, scenarios, principalCaps, centerRuntimeCapsResolver, handsLanes, getCenterPrompts, getKeyResolver, taskAttachmentStore, perSessionCwd, setSessionCwd, setSessionShellEnv, hookLlm, hookAgent, fleetBus, hookWakeBus, resumeAnchorStore, ownerAware, taskLimitCaps, taskTimeoutSec, selectEnvTool, sendUserFileToolSpec, memoryEngine, durableEnabled, approvalExemptionStore, singleUserAutoAcceptBaseline, checkpointStore, deploymentHooks, imageIndex, perTaskImage, sessionEnvSelection, liveQuestionFace, } = ctx;
|
|
122
55
|
// ── #177 的两件 boot 期收口(把守卫集搬到 governance 拍之后才成立的两条)────────────────────────
|
|
123
56
|
//
|
|
124
57
|
// ① 守卫集的**编译**从此每个请求都发生(旧家只在 default/auto/acceptEdits 三个模式臂里发生)。
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
// 非法旋钮值当场炸在启动上(与 config.ts 的 env fail-loud 同族),指名键与 core 的原因。
|
|
129
|
-
// env 只是编译期的占位(compilePatterns 不碰它),真裁决用的是每请求按 lane 铸的那一个。
|
|
130
|
-
if (config.sensitiveWritePatterns.length > 0) {
|
|
131
|
-
try {
|
|
132
|
-
createSensitivePathPolicy({ env: new StubExecutionEnv(), patterns: config.sensitiveWritePatterns });
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
throw new Error(`SENSITIVE_WRITE_PATTERNS is not a usable guard set: ${e instanceof Error ? e.message : String(e)}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
58
|
+
// 非法旋钮值必须当场炸在启动上而不是每任务一条 500 —— 判据与文案随 design/181 件一搬进构造口
|
|
59
|
+
// (`assertGuardPatternsUsable`),赎回腿/run-local 装配时调同一只,三腿同得。
|
|
60
|
+
assertGuardPatternsUsable(config);
|
|
138
61
|
// ② governance 拍的 DENY 基线让 `spec.toolPolicy` 在**任何**客户端表态下都在场,于是 core 的
|
|
139
62
|
// 「write-capable hand tools are present but UNGATED」那条 onError 不再触发(core 判据是
|
|
140
63
|
// `policyLayers.length > 0`,prepare-task dist 亲读)。它此前是「部署一个门都没接」这个 misconfig
|
|
141
64
|
// 的唯一信号,而守卫集只挡那二十来个路径段、其余写目标照旧无裁决 —— 信号不能因为本件静默消失。
|
|
142
65
|
// 条件 = 两条产 toolPolicy 的腿都不在场(durable 门关 ∧ 单用户 auto-accept 基线不适用),即修前
|
|
143
66
|
// `spec.toolPolicy` 恒缺席的那一形;三个量都是 boot 常量,所以这里说一次而不是每任务说一次。
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
});
|
|
150
|
-
}
|
|
67
|
+
// 判据与文案随 design/181 件三搬进构造口(`buildOnlySensitiveBaselineWarning`),run-local 腿调同
|
|
68
|
+
// 一只 —— 本处零行为变化(条件与字段逐字同前)。
|
|
69
|
+
const onlySensitiveBaseline = buildOnlySensitiveBaselineWarning(config, { durableEnabled, singleUserAutoAcceptBaseline });
|
|
70
|
+
if (onlySensitiveBaseline)
|
|
71
|
+
logger.warn(onlySensitiveBaseline.event, onlySensitiveBaseline.fields);
|
|
151
72
|
// Deployment-owned mapping. Tools/prompt/skills come from the selected scenario (assembled once,
|
|
152
73
|
// bound per request); identity/session/policy stay server-side. Never taken from the request body.
|
|
153
74
|
// design/158 A8:从 createHttpServer 实参里提出来(原地占该字面量 1,093 行中的 967 行)——
|
|
@@ -160,11 +81,15 @@ export function createResolveSpec(ctx) {
|
|
|
160
81
|
const anchors = await resolveHistoryAnchors(body, auth);
|
|
161
82
|
const spec = assembleSpecLiteral(body, auth, opts, { ...gated, ...lane, ...anchors });
|
|
162
83
|
const folded = await foldGovernanceAndSettings(body, auth, spec, gated);
|
|
163
|
-
|
|
84
|
+
const final = await applyImageFactsAndRouting(body, auth, folded.governed, {
|
|
164
85
|
scenarioName: gated.scenarioName,
|
|
165
86
|
scratchpadDir: folded.scratchpadDir,
|
|
166
87
|
hostSemanticsLane: folded.hostSemanticsLane,
|
|
167
88
|
});
|
|
89
|
+
// #196:登记本请求的 hands lane。**必须是最终对象**(阶段⑤/⑥ 会换新对象 —— 登记中间态 = 执行点
|
|
90
|
+
// 查不到、静默落回 full)。resume 腿也走同一条 resolveSpec,故续跑与首跑选同一只 Runner。
|
|
91
|
+
handsLanes.stamp(final, gated.hands);
|
|
92
|
+
return final;
|
|
168
93
|
};
|
|
169
94
|
/** 阶段①(场景/append 门):吃 请求体 + auth + `opts.leg`,吐 本请求的场景绑定(scenarioName/cap)、center-pack
|
|
170
95
|
* 的**单次**快照与 append 门结论(centerDecls/appendLessPack/acceptedAppend)、objective、解析后的 settings 戳,
|
|
@@ -274,7 +199,7 @@ export function createResolveSpec(ctx) {
|
|
|
274
199
|
}
|
|
275
200
|
}
|
|
276
201
|
}
|
|
277
|
-
return { scenarioName, cap, centerDecls, appendLessPack, acceptedAppend, objective, parsedSettings, attachmentNotice };
|
|
202
|
+
return { scenarioName, cap, hands: cap.hands, centerDecls, appendLessPack, acceptedAppend, objective, parsedSettings, attachmentNotice };
|
|
278
203
|
};
|
|
279
204
|
/** 阶段②(settings·cwd·env):吃 请求体 + auth + 阶段①的 objective/parsedSettings,吐 taskHooks、
|
|
280
205
|
* additionalDirectories、档位展开后的 wireCatalog 与选中的 picked;副作用=per-session cwd/shellEnv 注册与
|
|
@@ -485,7 +410,7 @@ export function createResolveSpec(ctx) {
|
|
|
485
410
|
/** 阶段④(spec 字面量):吃 请求体 + auth + `opts.leg` + 前三阶段的全部产出,吐**尚未过治理层**的 TaskSpec
|
|
486
411
|
* 字面量 —— 部署方拥有的 body→TaskSpec 映射本体(compactionModel 的 fresh 腿 400 也在这一段)。 */
|
|
487
412
|
const assembleSpecLiteral = (body, auth, opts, parts) => {
|
|
488
|
-
const { scenarioName, cap, centerDecls, acceptedAppend, parsedSettings, attachmentNotice, taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked, resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, } = parts;
|
|
413
|
+
const { scenarioName, cap, hands, centerDecls, acceptedAppend, parsedSettings, attachmentNotice, taskHooks, additionalDirectories, additionalReadDirectories, wireCatalog, picked, resumeAtEntryId, rewindFilesToEntryId, s4DefaultScopes, } = parts;
|
|
489
414
|
const spec = {
|
|
490
415
|
// D-1:附件告知随 objective 进 durable 流(只有名字/mime/尺寸——**内容永不进流**,这正是
|
|
491
416
|
// 「拼进 objective 是伪方案」的账要划清的线;文件名已消毒为安全字符集,无注入面)。
|
|
@@ -823,9 +748,15 @@ export function createResolveSpec(ctx) {
|
|
|
823
748
|
// the QuestionAnswer (server.ts `body.answer` → onQuestion closure). #152([2703] 案二): the question
|
|
824
749
|
// policy is LIVE-AWARE when the QuestionCoordinator is wired — a live-stream leg adjudicates `allow`
|
|
825
750
|
// (the live human answers over the stream) instead of unconditionally parking; see durableQuestionPolicy.
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
751
|
+
// design/181 件一:基线的**构造**归 `createApprovalBaselinePolicy`(三腿单一属主);本处只做部署形
|
|
752
|
+
// 判断——哪一形该有 durable 轴、哪一形铺 allow-all、哪一形留 `undefined`(见下两条注)。
|
|
753
|
+
// #196 纵深(设计小票 §2b):hands=none 的场景在 spec 上叠一份 band 名单 deny —— **不是**主保证
|
|
754
|
+
// (主保证=这类任务跑在不挂 executionEnvFactory 的 Runner 上,band 根本不 mount),而是「将来
|
|
755
|
+
// 有人把它误接回有手 Runner」时的第二层。合成走 tighten-only 的 combinePolicies(TRAP #1:
|
|
756
|
+
// 直接赋值会静默丢掉下方那条 durable 审批基线 / auto-accept 座),见 capabilities/hands-lane.ts。
|
|
757
|
+
toolPolicy: toolPolicyForHands(hands, durableEnabled
|
|
758
|
+
? createApprovalBaselinePolicy(config, {
|
|
759
|
+
question: liveQuestionFace,
|
|
829
760
|
// The probe key is the CONTINUED session (auth.sessionId — the same id that keys the
|
|
830
761
|
// durable checkpoint /decide route). A fresh session (no body.sessionId) has no exemptions by
|
|
831
762
|
// construction; fail-closed inside the policy on a store error.
|
|
@@ -835,21 +766,22 @@ export function createResolveSpec(ctx) {
|
|
|
835
766
|
onExempted: (toolName, rawToolName) => logger.info("approval_exempted", { sessionId: auth.sessionId, toolName, rawToolName }),
|
|
836
767
|
}
|
|
837
768
|
: {}),
|
|
838
|
-
})
|
|
769
|
+
})
|
|
839
770
|
: singleUserAutoAcceptBaseline
|
|
840
771
|
// 🔴 (live finding: single-user host-lane Bash ran UNGATED, no adjudication): a
|
|
841
772
|
// single-user turnkey worker with NO expressed gate intent previously left `toolPolicy` UNDEFINED →
|
|
842
773
|
// core's write-capable hand tools mount UNADJUDICATED (core error-logs it every task; host lane has NO
|
|
843
774
|
// sandbox to fall back on). CC's trust model is auto-accept, but the GATE MECHANISM must be PRESENT
|
|
844
775
|
// (core 原则: "机制留、默认可更宽 auto-accept"). So wire an adjudicated auto-accept BASELINE
|
|
845
|
-
// (`
|
|
846
|
-
// `hasEffectAwareGate`, restores observability + a hook/tighten point, and the
|
|
847
|
-
// irreversible ops via `AUTONOMY` / `commandPolicy` (layered TIGHTEN-ONLY by
|
|
776
|
+
// (the durable-less arm of `createApprovalBaselinePolicy` = a present, effect-aware policy that allows
|
|
777
|
+
// all): satisfies core's `hasEffectAwareGate`, restores observability + a hook/tighten point, and the
|
|
778
|
+
// operator STILL tightens irreversible ops via `AUTONOMY` / `commandPolicy` (layered TIGHTEN-ONLY by
|
|
779
|
+
// applyRuntimeGovernance).
|
|
848
780
|
// Interactive approval routed to the shell HITL = follow-on. 🔴 `singleUserAutoAcceptBaseline` requires
|
|
849
781
|
// ZERO gate intent — a single-user op who SET approval flags but wired no store falls to `undefined`
|
|
850
782
|
// (core warns = real misconfig, not masked); multi-tenant likewise stays `undefined` (approval required).
|
|
851
|
-
?
|
|
852
|
-
: undefined,
|
|
783
|
+
? createApprovalBaselinePolicy(config)
|
|
784
|
+
: undefined),
|
|
853
785
|
// Durable suspend needs both the store (here, per-task) and the opt-in scope key (multi-tenant =
|
|
854
786
|
// principal; "_" when auth is off). core suspends on a policy `ask` only when these are present.
|
|
855
787
|
...(durableEnabled
|
|
@@ -925,27 +857,10 @@ export function createResolveSpec(ctx) {
|
|
|
925
857
|
logger: { warn: (event, fields) => logger.warn(event, { ...(fields ?? {}), lane: config.remoteExec?.provider }) },
|
|
926
858
|
}),
|
|
927
859
|
};
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
patterns: config.sensitiveWritePatterns,
|
|
933
|
-
...(pathAdjudication.cwd !== undefined ? { rootPath: pathAdjudication.cwd } : {}),
|
|
934
|
-
});
|
|
935
|
-
// cwd 在场(host 形)⇒ 相对形已被 core 按 rootPath 解析进真身裁决,一层就够。
|
|
936
|
-
// cwd 缺席(沙箱形)⇒ 相对形在真身那一层是「判不了 ⇒ 弃权 allow」,而写门恰恰在
|
|
937
|
-
// bypass/settings 缺席这几形不在场 ⇒ 补一层纯词法的相对形守卫(见 RelativeTargetLexicalEnv)。
|
|
938
|
-
if (pathAdjudication.cwd !== undefined)
|
|
939
|
-
return realTarget;
|
|
940
|
-
return combinePolicies(realTarget, createSensitivePathPolicy({ env: new RelativeTargetLexicalEnv(), patterns: config.sensitiveWritePatterns }));
|
|
941
|
-
})()
|
|
942
|
-
: undefined;
|
|
943
|
-
const governedBase = applyRuntimeGovernance(spec, {
|
|
944
|
-
autonomy: config.autonomy,
|
|
945
|
-
commandPolicy: config.commandPolicy,
|
|
946
|
-
...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
|
|
947
|
-
...(sensitivePathPolicy ? { sensitivePathPolicy } : {}),
|
|
948
|
-
});
|
|
860
|
+
// design/181 件一:governance 实参(含守卫集预铸)归 `createDeploymentGovernanceInputs`(三腿单一
|
|
861
|
+
// 属主),**折叠属主不变**——仍是这一句 `applyRuntimeGovernance` → core `tightenTaskSpec`。每请求现调,
|
|
862
|
+
// 读的是活 config(autonomy/commandPolicy 热改;resume 腿按当前值重折,与上方审批基线同姿势)。
|
|
863
|
+
const governedBase = applyRuntimeGovernance(spec, createDeploymentGovernanceInputs(config, pathAdjudication));
|
|
949
864
|
// Fold the client's per-request settings stamp onto the governed spec, TIGHTEN-ONLY (deny-wins) — AFTER
|
|
950
865
|
// the approval baseline + operator governance, so the order is deployment ⊇ operator ⊇ client (each can only
|
|
951
866
|
// narrow). tightenTaskSpec THROWS if the client settings would LOOSEN a stricter baseline → a 422 client error
|
package/dist/budget.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cost / budget / trace wiring for the Runner (core
|
|
2
|
+
* Cost / budget / trace wiring for the Runner (core 5.x) — pure, testable helpers kept out of the
|
|
3
3
|
* auto-running main.ts:
|
|
4
4
|
* ③ buildPricing — Model.cost catalog → injected ModelPricing (authoritative costMicroUsd).
|
|
5
5
|
* ④ createTracer — core's content-free TraceEvents → /metrics (spend, first-token, tool calls).
|
package/dist/budget.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cost / budget / trace wiring for the Runner (core
|
|
2
|
+
* Cost / budget / trace wiring for the Runner (core 5.x) — pure, testable helpers kept out of the
|
|
3
3
|
* auto-running main.ts:
|
|
4
4
|
* ③ buildPricing — Model.cost catalog → injected ModelPricing (authoritative costMicroUsd).
|
|
5
5
|
* ④ createTracer — core's content-free TraceEvents → /metrics (spend, first-token, tool calls).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #196 场景 hands lane —— 「要手的场景」与「无手的场景」在装配层分家。
|
|
3
|
+
*
|
|
4
|
+
* 病根(设计小票 §1,案源 [3208]/[3209]/[3212]):core 的手带工具面是 **per-Runner 表态**
|
|
5
|
+
* (`prepare-task`:`handsEnabled = ownedEnv || deps.executionEnv`,ownedEnv 只要挂了
|
|
6
|
+
* `executionEnvFactory` 就每任务必铸)。server 此前全场景共享一只 runner + 一只 subRunner,
|
|
7
|
+
* 于是任何 REMOTE_EXEC 部署里,声明 clone-free 的 `scan`、只有 `run_council` 的评审团、
|
|
8
|
+
* 零工具人格的 team 成员,最终 roster 都被并集进全量可写手带(Bash/Edit/Write/…)。
|
|
9
|
+
*
|
|
10
|
+
* 本模块提供三件:
|
|
11
|
+
* ① {@link ScenarioHands} 判别位 + {@link pickHandsRunner} 的**穷举** switch —— 新场景不表态 = 编译红,
|
|
12
|
+
* 不是静默继承 full(词表闭集纪律)。
|
|
13
|
+
* ② {@link HANDS_EXCLUSIVE_TOOL_NAMES} + {@link handsDenyPolicy} —— 纵深:hands=none 的任务即便被误接到
|
|
14
|
+
* 有手 Runner 上,手独占名单逐一 deny。**不是**主保证(主保证是根本不挂 factory ⇒ 工具面不可见)。
|
|
15
|
+
* ③ {@link createHandsLaneRegistry} —— resolveSpec 产出 spec 与执行点选 Runner 之间的 lane 传递。
|
|
16
|
+
*/
|
|
17
|
+
import { type Runner, type TaskSpec, type ToolPolicy } from "@sema-agent/core";
|
|
18
|
+
/** 场景对手带工具面的表态(闭集)。`full` = core 手带 band 照常 mount;`none` = 不挂 executionEnvFactory 的 Runner。 */
|
|
19
|
+
export type ScenarioHands = "full" | "none";
|
|
20
|
+
/**
|
|
21
|
+
* 无手孪生 Runner 的 deps 构造:把 `executionEnvFactory` **摘键**(不是置 undefined)。
|
|
22
|
+
* 摘键让「这只 Runner 没有手」在 deps 字面量上自证;置 undefined 留下一个值为 undefined 的键,
|
|
23
|
+
* 与 core 现在的 `deps.executionEnvFactory ||` 判读结果相同,但把正确性寄托在真值判断上。
|
|
24
|
+
*/
|
|
25
|
+
export declare function withoutExecutionEnv<T extends {
|
|
26
|
+
executionEnvFactory?: unknown;
|
|
27
|
+
}>(deps: T): Omit<T, "executionEnvFactory">;
|
|
28
|
+
/** 一对 Runner:同 deps,唯一差别是 `handsless` 不挂 `executionEnvFactory`。 */
|
|
29
|
+
export interface HandsRunnerPair {
|
|
30
|
+
full: Runner;
|
|
31
|
+
handsless: Runner;
|
|
32
|
+
}
|
|
33
|
+
/** 按场景表态取 Runner。**穷举 switch**:`ScenarioHands` 加词而此处不改 = 编译错(无 default 臂)。 */
|
|
34
|
+
export declare function pickHandsRunner(hands: ScenarioHands, pair: HandsRunnerPair): Runner;
|
|
35
|
+
/**
|
|
36
|
+
* core 手带 band 的工具名**全集** —— 「挂 executionEnvFactory 才出现、不挂就消失」的那一批。
|
|
37
|
+
*
|
|
38
|
+
* 两段来源,都不是拍脑袋:
|
|
39
|
+
* · `HAND_TOOL_EFFECTS`(core 导出面,`tools/fs`)—— band 的 effect 表,11 名。
|
|
40
|
+
* · 下方钉死补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
|
|
41
|
+
* test/scenario-hands-lane.test.ts 的对表格:它用真 Runner 跑「有手 roster ∖ 无手 roster」,与本常量逐名对账,
|
|
42
|
+
* core 加/改 band 成员时红,防漂移)。
|
|
43
|
+
*
|
|
44
|
+
* ⚠️ 本全集是**观测口径**(用于防漂移对账),**不是** deny 名单 —— deny 用下面的
|
|
45
|
+
* {@link HANDS_EXCLUSIVE_TOOL_NAMES},理由见那里。
|
|
46
|
+
*/
|
|
47
|
+
export declare const HANDS_BAND_TOOL_NAMES: readonly string[];
|
|
48
|
+
/**
|
|
49
|
+
* band 里**只有手才可能出现**的那批(= deny 名单)。
|
|
50
|
+
*
|
|
51
|
+
* 🔴 codex 复审(2026-08-09 finding-3,亲验 core prepare-task):band 全集里有四件是**两用**的 ——
|
|
52
|
+
* `prepare-task` 的挂载条件是 `if (backgroundTaskToolsActive || workflowToolsActive)`,而
|
|
53
|
+
* `workflowToolsActive` 只要 `spec.selfOrchestration`(caller 可设,与场景正交)+ 部署的 workflow deps
|
|
54
|
+
* 就成立,**与手无关**。拿全集去 deny,会让「hands=none 场景 + selfOrchestration:true」的请求挂上
|
|
55
|
+
* `Workflow` 却读不到它的轮询/停止/消息/转录 —— 一个半瘫的工作流面,比不给还糟。
|
|
56
|
+
* 故 deny 名单 = 全集 ∖ 两用四件。Monitor 不在两用之列(core 的挂载条件是纯
|
|
57
|
+
* `if (backgroundTaskToolsActive)`,而它 = handsIncludeShell ∧ …,无手不成立)。
|
|
58
|
+
*/
|
|
59
|
+
export declare const HANDS_DUAL_USE_TOOL_NAMES: readonly string[];
|
|
60
|
+
export declare const HANDS_EXCLUSIVE_TOOL_NAMES: readonly string[];
|
|
61
|
+
/** 手独占名单的 deny 策略(纵深层)。名单是常量 ⇒ 每次调用产出等价策略,构造成本可忽略。 */
|
|
62
|
+
export declare function handsDenyPolicy(): ToolPolicy;
|
|
63
|
+
/**
|
|
64
|
+
* 把 band deny 叠到既有基线上 —— **tighten-only**。
|
|
65
|
+
*
|
|
66
|
+
* 🪤 TRAP #1(runtime-governance.ts 头注逐字):`TaskSpec.toolPolicy` 是**覆盖**而非合并
|
|
67
|
+
* (`spec.toolPolicy ?? deps.toolPolicy`),直接赋值会静默丢掉部署的 durable 审批基线 /
|
|
68
|
+
* 单用户 auto-accept 座。走 `combinePolicies(base, deny)`(deny 胜、ask 不被 allow 冲掉),
|
|
69
|
+
* 只会加 deny,永不放松。base 缺席时单挂 deny —— 该形下 band 本就不 mount,core 的
|
|
70
|
+
* "write-capable hand tools are present but UNGATED" 信号不受影响(它以 Write 在场为条件)。
|
|
71
|
+
*/
|
|
72
|
+
export declare function tightenWithHandsDeny(base: ToolPolicy | undefined): ToolPolicy;
|
|
73
|
+
/** 装配点的取用形:`full` 原样放行基线,`none` 叠 deny。**穷举 switch**,无缺省臂。 */
|
|
74
|
+
export declare function toolPolicyForHands(hands: ScenarioHands, base: ToolPolicy | undefined): ToolPolicy | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* 请求级 lane 登记簿 —— resolveSpec 在产出 TaskSpec 时登记本请求的场景表态,执行点凭 spec 取回选 Runner。
|
|
77
|
+
*
|
|
78
|
+
* 为什么不把 lane 写进 TaskSpec:那是 core 的类型,server 无自有位;而 `resolveSpec` 的返回形是
|
|
79
|
+
* ServiceDeps 的公开签名,80+ 测试文件在桩它 —— 改返回形的射程远大于本件。登记簿以 spec **对象身份**
|
|
80
|
+
* 为键(每请求一只新对象,并发无串台;WeakMap 弱引用,零清理欠账),由同一只 boot 装配同时交给
|
|
81
|
+
* resolveSpec 与 HTTP 执行点,配对是结构性的。
|
|
82
|
+
*
|
|
83
|
+
* 未登记 ⇒ `full`:唯一可达形是「这只 spec 不是本 resolver 产的」(测试桩)。生产链上 stamp 与 laneOf
|
|
84
|
+
* 同一实例,不存在半接;真误配时纵深 deny 仍在(见 {@link tightenWithHandsDeny})。
|
|
85
|
+
*/
|
|
86
|
+
export interface HandsLaneRegistry {
|
|
87
|
+
stamp(spec: TaskSpec, hands: ScenarioHands): void;
|
|
88
|
+
laneOf(spec: TaskSpec): ScenarioHands;
|
|
89
|
+
}
|
|
90
|
+
export declare function createHandsLaneRegistry(): HandsLaneRegistry;
|
|
91
|
+
//# sourceMappingURL=hands-lane.d.ts.map
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #196 场景 hands lane —— 「要手的场景」与「无手的场景」在装配层分家。
|
|
3
|
+
*
|
|
4
|
+
* 病根(设计小票 §1,案源 [3208]/[3209]/[3212]):core 的手带工具面是 **per-Runner 表态**
|
|
5
|
+
* (`prepare-task`:`handsEnabled = ownedEnv || deps.executionEnv`,ownedEnv 只要挂了
|
|
6
|
+
* `executionEnvFactory` 就每任务必铸)。server 此前全场景共享一只 runner + 一只 subRunner,
|
|
7
|
+
* 于是任何 REMOTE_EXEC 部署里,声明 clone-free 的 `scan`、只有 `run_council` 的评审团、
|
|
8
|
+
* 零工具人格的 team 成员,最终 roster 都被并集进全量可写手带(Bash/Edit/Write/…)。
|
|
9
|
+
*
|
|
10
|
+
* 本模块提供三件:
|
|
11
|
+
* ① {@link ScenarioHands} 判别位 + {@link pickHandsRunner} 的**穷举** switch —— 新场景不表态 = 编译红,
|
|
12
|
+
* 不是静默继承 full(词表闭集纪律)。
|
|
13
|
+
* ② {@link HANDS_EXCLUSIVE_TOOL_NAMES} + {@link handsDenyPolicy} —— 纵深:hands=none 的任务即便被误接到
|
|
14
|
+
* 有手 Runner 上,手独占名单逐一 deny。**不是**主保证(主保证是根本不挂 factory ⇒ 工具面不可见)。
|
|
15
|
+
* ③ {@link createHandsLaneRegistry} —— resolveSpec 产出 spec 与执行点选 Runner 之间的 lane 传递。
|
|
16
|
+
*/
|
|
17
|
+
import { HAND_TOOL_EFFECTS, combinePolicies, createAllowDenyPolicy } from "@sema-agent/core";
|
|
18
|
+
/**
|
|
19
|
+
* 无手孪生 Runner 的 deps 构造:把 `executionEnvFactory` **摘键**(不是置 undefined)。
|
|
20
|
+
* 摘键让「这只 Runner 没有手」在 deps 字面量上自证;置 undefined 留下一个值为 undefined 的键,
|
|
21
|
+
* 与 core 现在的 `deps.executionEnvFactory ||` 判读结果相同,但把正确性寄托在真值判断上。
|
|
22
|
+
*/
|
|
23
|
+
export function withoutExecutionEnv(deps) {
|
|
24
|
+
const { executionEnvFactory: _handsDropped, ...rest } = deps;
|
|
25
|
+
return rest;
|
|
26
|
+
}
|
|
27
|
+
/** 按场景表态取 Runner。**穷举 switch**:`ScenarioHands` 加词而此处不改 = 编译错(无 default 臂)。 */
|
|
28
|
+
export function pickHandsRunner(hands, pair) {
|
|
29
|
+
switch (hands) {
|
|
30
|
+
case "full":
|
|
31
|
+
return pair.full;
|
|
32
|
+
case "none":
|
|
33
|
+
return pair.handsless;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* core 手带 band 的工具名**全集** —— 「挂 executionEnvFactory 才出现、不挂就消失」的那一批。
|
|
38
|
+
*
|
|
39
|
+
* 两段来源,都不是拍脑袋:
|
|
40
|
+
* · `HAND_TOOL_EFFECTS`(core 导出面,`tools/fs`)—— band 的 effect 表,11 名。
|
|
41
|
+
* · 下方钉死补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
|
|
42
|
+
* test/scenario-hands-lane.test.ts 的对表格:它用真 Runner 跑「有手 roster ∖ 无手 roster」,与本常量逐名对账,
|
|
43
|
+
* core 加/改 band 成员时红,防漂移)。
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ 本全集是**观测口径**(用于防漂移对账),**不是** deny 名单 —— deny 用下面的
|
|
46
|
+
* {@link HANDS_EXCLUSIVE_TOOL_NAMES},理由见那里。
|
|
47
|
+
*/
|
|
48
|
+
export const HANDS_BAND_TOOL_NAMES = Object.freeze([
|
|
49
|
+
...Object.keys(HAND_TOOL_EFFECTS),
|
|
50
|
+
"SendMessage",
|
|
51
|
+
"AgentTranscript",
|
|
52
|
+
"Monitor",
|
|
53
|
+
"EnterWorktree",
|
|
54
|
+
"ExitWorktree",
|
|
55
|
+
]);
|
|
56
|
+
/**
|
|
57
|
+
* band 里**只有手才可能出现**的那批(= deny 名单)。
|
|
58
|
+
*
|
|
59
|
+
* 🔴 codex 复审(2026-08-09 finding-3,亲验 core prepare-task):band 全集里有四件是**两用**的 ——
|
|
60
|
+
* `prepare-task` 的挂载条件是 `if (backgroundTaskToolsActive || workflowToolsActive)`,而
|
|
61
|
+
* `workflowToolsActive` 只要 `spec.selfOrchestration`(caller 可设,与场景正交)+ 部署的 workflow deps
|
|
62
|
+
* 就成立,**与手无关**。拿全集去 deny,会让「hands=none 场景 + selfOrchestration:true」的请求挂上
|
|
63
|
+
* `Workflow` 却读不到它的轮询/停止/消息/转录 —— 一个半瘫的工作流面,比不给还糟。
|
|
64
|
+
* 故 deny 名单 = 全集 ∖ 两用四件。Monitor 不在两用之列(core 的挂载条件是纯
|
|
65
|
+
* `if (backgroundTaskToolsActive)`,而它 = handsIncludeShell ∧ …,无手不成立)。
|
|
66
|
+
*/
|
|
67
|
+
export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript"]);
|
|
68
|
+
export const HANDS_EXCLUSIVE_TOOL_NAMES = Object.freeze(HANDS_BAND_TOOL_NAMES.filter((n) => !HANDS_DUAL_USE_TOOL_NAMES.includes(n)));
|
|
69
|
+
/** 手独占名单的 deny 策略(纵深层)。名单是常量 ⇒ 每次调用产出等价策略,构造成本可忽略。 */
|
|
70
|
+
export function handsDenyPolicy() {
|
|
71
|
+
return createAllowDenyPolicy({ deny: [...HANDS_EXCLUSIVE_TOOL_NAMES] });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 把 band deny 叠到既有基线上 —— **tighten-only**。
|
|
75
|
+
*
|
|
76
|
+
* 🪤 TRAP #1(runtime-governance.ts 头注逐字):`TaskSpec.toolPolicy` 是**覆盖**而非合并
|
|
77
|
+
* (`spec.toolPolicy ?? deps.toolPolicy`),直接赋值会静默丢掉部署的 durable 审批基线 /
|
|
78
|
+
* 单用户 auto-accept 座。走 `combinePolicies(base, deny)`(deny 胜、ask 不被 allow 冲掉),
|
|
79
|
+
* 只会加 deny,永不放松。base 缺席时单挂 deny —— 该形下 band 本就不 mount,core 的
|
|
80
|
+
* "write-capable hand tools are present but UNGATED" 信号不受影响(它以 Write 在场为条件)。
|
|
81
|
+
*/
|
|
82
|
+
export function tightenWithHandsDeny(base) {
|
|
83
|
+
const deny = handsDenyPolicy();
|
|
84
|
+
return base ? combinePolicies(base, deny) : deny;
|
|
85
|
+
}
|
|
86
|
+
/** 装配点的取用形:`full` 原样放行基线,`none` 叠 deny。**穷举 switch**,无缺省臂。 */
|
|
87
|
+
export function toolPolicyForHands(hands, base) {
|
|
88
|
+
switch (hands) {
|
|
89
|
+
case "full":
|
|
90
|
+
return base;
|
|
91
|
+
case "none":
|
|
92
|
+
return tightenWithHandsDeny(base);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function createHandsLaneRegistry() {
|
|
96
|
+
const lanes = new WeakMap();
|
|
97
|
+
return {
|
|
98
|
+
stamp: (spec, hands) => {
|
|
99
|
+
lanes.set(spec, hands);
|
|
100
|
+
},
|
|
101
|
+
laneOf: (spec) => lanes.get(spec) ?? "full",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=hands-lane.js.map
|
|
@@ -11,7 +11,7 @@ export interface RepoCoords {
|
|
|
11
11
|
owner: string;
|
|
12
12
|
repo: string;
|
|
13
13
|
}
|
|
14
|
-
/** Parse `owner/name` or a repo URL (e.g.
|
|
14
|
+
/** Parse `owner/name` or a repo URL (e.g. https://git.example.com/some-org/some-repo.git). */
|
|
15
15
|
export declare function parseRepo(input: string): RepoCoords;
|
|
16
16
|
/** Thin read-only Gitea API client. Built once at startup; bound to a repo per task. */
|
|
17
17
|
export declare class GiteaClient {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
|
-
/** Parse `owner/name` or a repo URL (e.g.
|
|
2
|
+
/** Parse `owner/name` or a repo URL (e.g. https://git.example.com/some-org/some-repo.git). */
|
|
3
3
|
export function parseRepo(input) {
|
|
4
4
|
const s = input.trim().replace(/\.git$/, "");
|
|
5
5
|
const m = /^[a-z]+:\/\/[^/]+\/(.+)$/i.exec(s);
|
|
@@ -42,7 +42,13 @@ export class GiteaClient {
|
|
|
42
42
|
async tree(coords, ref) {
|
|
43
43
|
const { owner, repo } = coords;
|
|
44
44
|
const body = (await (await this.api(`/repos/${owner}/${repo}/git/trees/${encodeURIComponent(ref)}?recursive=true&per_page=${MAX_TREE_ENTRIES}`)).json());
|
|
45
|
-
|
|
45
|
+
const paths = (body.tree ?? []).filter((e) => e.type === "blob").map((e) => e.path);
|
|
46
|
+
// The upstream API caps the listing at `per_page` and reports the overflow via `truncated` — it does
|
|
47
|
+
// not report how many entries were left out. Surface that (same disclosure-marker family as
|
|
48
|
+
// `readFile`/`pullDiff` below) so a review lens never mistakes a capped tree for the whole repo.
|
|
49
|
+
return body.truncated
|
|
50
|
+
? [...paths, `…[truncated, tree has more than ${MAX_TREE_ENTRIES} entries]`]
|
|
51
|
+
: paths;
|
|
46
52
|
}
|
|
47
53
|
async readFile(coords, path, ref) {
|
|
48
54
|
const { owner, repo } = coords;
|
|
@@ -3,6 +3,7 @@ import type { Metrics } from "../observability/metrics.js";
|
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
4
|
import { GiteaClient } from "./repo-tools.js";
|
|
5
5
|
import { type LoadedSkill } from "./skills.js";
|
|
6
|
+
import { type ScenarioHands } from "./hands-lane.js";
|
|
6
7
|
/**
|
|
7
8
|
* Scenario routing — a deployment serves many scenarios from one image. Each request's `scenario`
|
|
8
9
|
* selects a pre-wired capability bundle: tools (incl. subagent roster) + a whole-harness prompt +
|
|
@@ -17,6 +18,14 @@ export interface ScenarioRequest {
|
|
|
17
18
|
export interface ScenarioBundle {
|
|
18
19
|
tools: ToolSpec[];
|
|
19
20
|
skills: SkillSpec[];
|
|
21
|
+
/**
|
|
22
|
+
* #196:本场景对 core 手带工具面(Bash/Edit/Write/Read/Grep/…)的表态 —— **必填闭集**。
|
|
23
|
+
* `full` = 任务跑在挂了 `executionEnvFactory` 的 Runner 上(core 把 band 并集进 roster);
|
|
24
|
+
* `none` = 跑在不挂 factory 的 Runner 上(band 根本不 mount,工具 schema 对模型不可见)。
|
|
25
|
+
* 必填是设计的一半:新场景不表态 = 编译红,而不是静默继承 full(那正是 [3208] 的病灶——
|
|
26
|
+
* `scan` 声明 clone-free 四件、实际拿到全量可写手)。
|
|
27
|
+
*/
|
|
28
|
+
hands: ScenarioHands;
|
|
20
29
|
promptProvider?: PromptProvider;
|
|
21
30
|
/** [849] 场景解释层定死的终验开关(core TaskSpec.finalVerification):bench 实测产品默认请求与
|
|
22
31
|
* bench 态差距三件之一。[2400] 起无场景定死(autonomous 退役)——纯 caller 显式旋钮;spec 组装处 OR 折入(场景只会
|
|
@@ -28,6 +37,13 @@ export interface ScenarioDeps {
|
|
|
28
37
|
runner: Runner;
|
|
29
38
|
/** In-memory runner for ephemeral sub-tasks (council lenses/arbiter) — keeps them out of TiDB. */
|
|
30
39
|
subRunner: Runner;
|
|
40
|
+
/**
|
|
41
|
+
* #196:`subRunner` 的无手孪生 —— 同 deps,唯独不挂 `executionEnvFactory`。hands=none 的场景把它交给
|
|
42
|
+
* 自己造的子任务工具(council 的 lens/arbiter、team 的成员/synthesizer):那些子任务只该读仓库/讨论,
|
|
43
|
+
* 拿全量可写手是 [3208] 病灶里最意外的一支(零工具人格照拿 Bash)。取用一律走 {@link pickHandsRunner},
|
|
44
|
+
* 不在场景体内手挑 —— 判别位是唯一开关。
|
|
45
|
+
*/
|
|
46
|
+
handslessSubRunner: Runner;
|
|
31
47
|
model: string;
|
|
32
48
|
skills: LoadedSkill[];
|
|
33
49
|
repoClient?: GiteaClient;
|
|
@@ -144,21 +160,52 @@ export interface ScenarioDetail {
|
|
|
144
160
|
tools: string[];
|
|
145
161
|
/** 内建=概览(不外泄提示词资产全文);center 条目=prompt 原文(本就 ≤4KB 明文在配置域)。 */
|
|
146
162
|
promptSummary: string;
|
|
163
|
+
/** **已声明**(内建恒 true;center 条目 `enabled !== false`)。⚠️ 不是「跑得动」——那是 {@link available}。 */
|
|
147
164
|
enabled: boolean;
|
|
165
|
+
/**
|
|
166
|
+
* [C132] **本部署现在真跑得动吗**:后端依赖(如 repo 型场景的 `GIT_API_BASEURL`)是否就位。
|
|
167
|
+
* 与请求面的 501 拒绝臂**单一属主**({@link scenarioAvailability});false ⟺ 良性请求真吃 501。
|
|
168
|
+
*/
|
|
169
|
+
available: boolean;
|
|
170
|
+
/** 机读原因键;仅 `available:false` 时在场(缺席=没有理由)。消费方按键分支,禁匹配英文文案。 */
|
|
171
|
+
unavailableReason?: ScenarioUnavailableReason;
|
|
148
172
|
}
|
|
149
173
|
/** 内建五场景详情。default/code/team 工厂对良性请求无副作用可真调(拿真实工具名单);code-review/scan 是
|
|
150
174
|
* fail-loud 语义(缺 GIT_API 配置/principal 即 throw)→ 探针失败落静态表兜底(表↔工厂一致性由测试锁:
|
|
151
175
|
* 测试喂 fake deps 真调工厂对账工具名)。enabled 对内建恒 true(约定②)。 */
|
|
152
|
-
export declare function builtinScenarioDetails(scenarios: Record<string, Scenario>, deps: Pick<ScenarioDeps, "brandIdentity">): Record<string, ScenarioDetail>;
|
|
176
|
+
export declare function builtinScenarioDetails(scenarios: Record<string, Scenario>, deps: Pick<ScenarioDeps, "brandIdentity" | "repoClient">): Record<string, ScenarioDetail>;
|
|
153
177
|
/** center 条目详情(有效性判定与 centerScenarios 完全同款:无效条目既不进 overlay 也不进详情——
|
|
154
178
|
* 保证约定①「详情显示的来源=运行实际用的定义」永不错位)。 */
|
|
155
|
-
export declare function centerScenarioDetails(specs: CenterScenarioSpec[] | undefined, builtinNames: string[]): Record<string, ScenarioDetail>;
|
|
179
|
+
export declare function centerScenarioDetails(specs: CenterScenarioSpec[] | undefined, builtinNames: string[], deps: Pick<ScenarioDeps, "repoClient">): Record<string, ScenarioDetail>;
|
|
156
180
|
export interface CenterScenarioSpec {
|
|
157
181
|
name: string;
|
|
158
182
|
toolset: string;
|
|
159
183
|
prompt?: string;
|
|
160
184
|
enabled?: boolean;
|
|
161
185
|
}
|
|
186
|
+
/** 场景不可用的**机读原因**(闭集)。新增成员必须在 {@link scenarioUnavailableMessage} 的穷举 switch
|
|
187
|
+
* 里表态——漏表态是编译错误,不是运行期 miss 臂。消费方按这个键分支,禁去正则匹配英文文案。 */
|
|
188
|
+
export type ScenarioUnavailableReason = "git_client_unconfigured";
|
|
189
|
+
/** 判别式:可用臂**不带**原因键(缺席=没有理由),不可用臂必带。 */
|
|
190
|
+
export type ScenarioAvailability = {
|
|
191
|
+
readonly available: true;
|
|
192
|
+
} | {
|
|
193
|
+
readonly available: false;
|
|
194
|
+
readonly reason: ScenarioUnavailableReason;
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* 🔴 场景可用性的**唯一属主**。列举面(`ScenarioDetail.available` / `unavailableReason`)与请求面
|
|
198
|
+
* ({@link requireRepoClient} 的 501 拒绝臂,三处调用点)都只从这里取值——两处各写一份就是本仓反复
|
|
199
|
+
* 吃过的「同一语义两个属主」病:判据一漂,列举面开始说谎而没人先红。一致性由 capabilities.test 的
|
|
200
|
+
* **对表格**逐名钉住(`available:false` ⟺ 良性请求真吃 501),而不是靠这段注释。
|
|
201
|
+
*
|
|
202
|
+
* 判据键 = **toolset**:内建详情与 center 条目都带这个字段,故两条产线天然共用同一份判据。
|
|
203
|
+
* `requiresRepo` 的 toolset 需要部署配好 git 后端(`GIT_API_BASEURL` ⇒ `deps.repoClient`)。
|
|
204
|
+
* 词表外的 toolset(内建的 `full-body`/`team`)不依赖后端 ⇒ 恒可用。
|
|
205
|
+
*/
|
|
206
|
+
export declare function scenarioAvailability(deps: Pick<ScenarioDeps, "repoClient">, toolset: string): ScenarioAvailability;
|
|
207
|
+
/** 拒绝文案的唯一属主:闭集穷举 switch(新增原因词不在这里表态即编译红)。 */
|
|
208
|
+
export declare function scenarioUnavailableMessage(reason: ScenarioUnavailableReason, scenarioLabel: string): string;
|
|
162
209
|
export declare const SCENARIO_NAME_RE: RegExp;
|
|
163
210
|
/**
|
|
164
211
|
* Build the center-declared scenario overlay. Invalid specs are SKIPPED with a warning (never fail
|