@sema-agent/server 7.2.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 +2 -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/reapers.d.ts +34 -0
- package/dist/boot/reapers.js +198 -23
- package/dist/boot/resolve-spec.js +82 -30
- package/dist/config-types.d.ts +61 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.js +130 -2
- package/dist/elicitation.d.ts +4 -0
- package/dist/elicitation.js +2 -2
- package/dist/http/routes/capabilities.js +14 -0
- package/dist/http/routes/runs.d.ts +1 -0
- package/dist/http/routes/runs.js +548 -16
- package/dist/http/routes/tasks.js +151 -9
- package/dist/http/server.d.ts +1 -1
- package/dist/http/server.js +114 -4
- 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/main.js +1 -1
- 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 +3 -0
- package/dist/question.js +2 -2
- 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/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
package/dist/config.js
CHANGED
|
@@ -188,6 +188,30 @@ const CONFIG_NOTICES = [];
|
|
|
188
188
|
export function drainConfigNotices() {
|
|
189
189
|
return CONFIG_NOTICES.splice(0, CONFIG_NOTICES.length);
|
|
190
190
|
}
|
|
191
|
+
/** #157([2762]§五,F 类观测,零行为变化)—— 本次 boot 的执行车道落点 + 它的来源。
|
|
192
|
+
* 为什么单开一格:REMOTE_EXEC 未设/空串时的落点由 CONFIG_PROVIDER 隐式决定(local ⇒ host 真机,其余 ⇒
|
|
193
|
+
* 进程内 stub),而这个落点此前在日志里零留痕 —— 运维拿到一台 worker,无从确认「它跑的是哪条执行通道、
|
|
194
|
+
* 是显式配的还是默认落的」。与 CONFIG_WARNINGS / CONFIG_NOTICES 同为 pre-logger 收集(config 解析早于
|
|
195
|
+
* logger 建立),但级别是 **info**:这是每次 boot 都成立的常态事实,不是异常,而 CONFIG_NOTICES 那一格
|
|
196
|
+
* 固定按 warn 排空。
|
|
197
|
+
* 单槽(不是队列):一次 boot 只有一个落点;重复 loadConfig(测试)覆写即可,不会堆积。
|
|
198
|
+
* ⚠️ 写入时机=装配尾(见 loadConfig),不是解析途中:写早了,任何**后置**拒启(limits 域数值、跨域
|
|
199
|
+
* 不变量)都会留下一条「其实没启起来」的落点;而 loadConfig 开头的清槽保证失败的一轮不会把**上一轮**
|
|
200
|
+
* 的落点串给下一次排空(交叉复审 medium,2026-08-06)。
|
|
201
|
+
* ⚠️ 边界:记的是 **ENV 解析出的**落点。local 形的 `config.d/remote-exec.json` 覆写发生在 main.ts 的
|
|
202
|
+
* applyLocalRemoteExec(晚于本行排空),它自己另记 `remote_exec_from_file` —— 两行合读才是最终 lane。 */
|
|
203
|
+
let EXEC_LANE_NOTICE;
|
|
204
|
+
/** 从**已装配好的** remoteExec 派生落点一行(而不是在 REMOTE_EXEC 那条三元链的各臂里分别记账):
|
|
205
|
+
* 分臂记账要靠「校验过了 ⇒ 必落对应那一臂」这条人肉维护的同源假设,链子哪天多一臂就悄悄失真;
|
|
206
|
+
* 取装配结果则天然跟随。`in-process` = REMOTE_EXEC 未设的进程内 stub 姿态,沿用既有词表
|
|
207
|
+
* (config-center/types.ts 的 provider 词表、run-local 的 `?? "in-process"` 同名)。 */
|
|
208
|
+
function buildExecLaneNotice(remoteExec) {
|
|
209
|
+
const explicit = process.env.REMOTE_EXEC !== undefined && process.env.REMOTE_EXEC !== "";
|
|
210
|
+
return {
|
|
211
|
+
lane: remoteExec?.provider ?? "in-process",
|
|
212
|
+
source: explicit ? "explicit-env" : process.env.CONFIG_PROVIDER === "local" ? "default-local-provider" : "default-unset",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
191
215
|
/** F10 (codex audit): drain + log BOTH pre-logger diagnostic collectors (S20 soft-knob warnings + [792]④ boot
|
|
192
216
|
* notices) in one place. EVERY entrypoint that calls loadConfig() then builds a logger must call this right
|
|
193
217
|
* after (main.ts, run-local.ts) — a new entry that forgets it would silently drop operator-facing diagnostics
|
|
@@ -197,6 +221,13 @@ export function logConfigDiagnostics(logger) {
|
|
|
197
221
|
logger.warn("config_env_invalid_using_default", { env: w.env, raw: w.raw });
|
|
198
222
|
for (const n of drainConfigNotices())
|
|
199
223
|
logger.warn(n.event, n.fields);
|
|
224
|
+
// #157 F 类观测:执行车道落点一行(见 EXEC_LANE_NOTICE 头注)。无条件发 —— boot 期本来就只跑一次,
|
|
225
|
+
// 不需要 once-per-hit 抑制。先取后清:排空语义与上面两格一致(重复调用不重放同一条)。`info` 与
|
|
226
|
+
// `debug` 同为可选,理由同上 —— 老 embedder 传进来的 warn-only logger 仍能编译、仍拿得到警告。
|
|
227
|
+
const laneNotice = EXEC_LANE_NOTICE;
|
|
228
|
+
EXEC_LANE_NOTICE = undefined;
|
|
229
|
+
if (laneNotice && logger.info)
|
|
230
|
+
logger.info("exec_lane_selected", { lane: laneNotice.lane, source: laneNotice.source });
|
|
200
231
|
// design/158 N9 (polarity self-report): a `*_ENABLED` suffix does NOT carry its default — the repo grew four
|
|
201
232
|
// dialects for it (opt-in / opt-out / posture tri-state / negative name). Rather than force operators to read
|
|
202
233
|
// config.ts to learn what a knob does when unset, boolEnv-parsed knobs register themselves and we print one
|
|
@@ -874,6 +905,45 @@ export function formatUnmatchableToolNames(source, bad) {
|
|
|
874
905
|
return (`${source} names tool(s) that can never match a live tool (core 5.0.0 RB-476 — the alias/auto-prefix folds are gone): ` +
|
|
875
906
|
bad.map((b) => `${b.name} → ${b.guidance}`).join("; "));
|
|
876
907
|
}
|
|
908
|
+
/**
|
|
909
|
+
* #151 流内审批协议(design/172)的九键段(车3 刀 3a 五键 + 车5 收敛器四键)。
|
|
910
|
+
*
|
|
911
|
+
* **总开关默认 false** ⇒ 全链逐字现行为(不发 approval_request、不落 ask 行、不起收敛器腿、窗仍是
|
|
912
|
+
* `DEFAULT_APPROVAL_TTL_MS`)。窗/帽四键沿用 `numEnv` fail-loud 形(坏形启动期炸,不静默折 NaN);
|
|
913
|
+
* 车5 的四键一律 **`numEnvBounded`**(§9 C6:batch 整数 ≥1 有上帽,grace/TTL 非负有界)——它们直接
|
|
914
|
+
* 决定 reaper 每 tick 的库压与「多久算遗孤」,一个手滑的 `0` 或 `1e12` 都是运维事故面。
|
|
915
|
+
*
|
|
916
|
+
* 🔴 跨旋钮不变量(§9 C6,`#157` 三分类 P 族姿势):`ADHOC_GRACE < ORPHAN_TTL`。破坏形**拒启** ——
|
|
917
|
+
* 若 adhoc 宽限 ≥ 遗孤 TTL,判据 5 会先于判据 4 触发,一条 adhoc 腿拿到的归因就成了
|
|
918
|
+
* `orphan_ttl_exceeded`(「遗孤兜底」)而不是 `adhoc_leg_no_durable_domain`(「结构上无对账域」),
|
|
919
|
+
* 审计面从此读不出真实成因。默认值(60s vs 7d)自然满足;只有显式改坏才会撞上。
|
|
920
|
+
*/
|
|
921
|
+
function streamApprovalConfig() {
|
|
922
|
+
const cfg = {
|
|
923
|
+
enabled: boolEnv("STREAM_APPROVAL_ENABLED", false),
|
|
924
|
+
windowMs: numEnv("STREAM_ASK_WINDOW_MS", "60000"),
|
|
925
|
+
replayMax: numEnv("STREAM_APPROVAL_REPLAY_MAX", "50"),
|
|
926
|
+
admitMaxPerTask: numEnv("STREAM_APPROVAL_ADMIT_MAX_PER_TASK", "32"),
|
|
927
|
+
admitMaxPerOwner: numEnv("STREAM_APPROVAL_ADMIT_MAX_PER_OWNER", "256"),
|
|
928
|
+
reconcileBatch: numEnvBounded("STREAM_APPROVAL_RECONCILE_BATCH", "200", 1, 10_000),
|
|
929
|
+
pendingGraceMs: numEnvBounded("STREAM_APPROVAL_PENDING_GRACE_MS", "30000", 0, 3_600_000),
|
|
930
|
+
adhocGraceMs: numEnvBounded("STREAM_APPROVAL_ADHOC_GRACE_MS", "60000", 0, 86_400_000),
|
|
931
|
+
orphanTtlMs: numEnvBounded("STREAM_APPROVAL_ORPHAN_TTL_MS", String(7 * 24 * 60 * 60 * 1000), 60_000, 90 * 24 * 60 * 60 * 1000),
|
|
932
|
+
};
|
|
933
|
+
// 🔴 不变量的**正确形**(codex 交叉复审 C7,2026-08-06 验真):两条臂量的不是同一个起点 ——
|
|
934
|
+
// 判据 4 在 `expiresAtMs + ADHOC_GRACE` 触发,而 `expiresAtMs ≈ createdAtMs + WINDOW`;判据 5 在
|
|
935
|
+
// `createdAtMs + ORPHAN_TTL` 触发。只比 `ADHOC_GRACE < ORPHAN_TTL` 会漏掉窗:例如 WINDOW 与
|
|
936
|
+
// ORPHAN_TTL 等长时校验放行,但遗孤兜底必定先到,adhoc 腿的归因被写成 `orphan_ttl_exceeded`。
|
|
937
|
+
// 正确判据 = `WINDOW + ADHOC_GRACE < ORPHAN_TTL`(两边都是有界有限值,不会溢出)。
|
|
938
|
+
if (cfg.windowMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
|
|
939
|
+
throw new Error(`env STREAM_ASK_WINDOW_MS=${cfg.windowMs} + STREAM_APPROVAL_ADHOC_GRACE_MS=${cfg.adhocGraceMs} must be STRICTLY LESS than STREAM_APPROVAL_ORPHAN_TTL_MS=${cfg.orphanTtlMs} — the adhoc arm fires at (createdAt + window + grace) while the orphan backstop fires at (createdAt + ttl); if the backstop wins, every adhoc-leg row is attributed "orphan_ttl_exceeded" instead of "adhoc_leg_no_durable_domain" (attribution honesty, design/172 §6.1)`);
|
|
940
|
+
}
|
|
941
|
+
// 整数门(batch 是行数,不是时长):`numEnvBounded` 只管区间,`200.5` 会一路走到 SQL `LIMIT` 上。
|
|
942
|
+
if (!Number.isInteger(cfg.reconcileBatch)) {
|
|
943
|
+
throw new Error(`env STREAM_APPROVAL_RECONCILE_BATCH=${cfg.reconcileBatch} must be an INTEGER row count`);
|
|
944
|
+
}
|
|
945
|
+
return cfg;
|
|
946
|
+
}
|
|
877
947
|
function parseApprovalDomain(ctx) {
|
|
878
948
|
const { postureOn } = ctx; // 跨域入参②:posture 三态(single-user turnkey ⇒ HITL 面默认 ON)
|
|
879
949
|
// design/80 D-G: the direct-connect approval door anchors (all must be present to ACTIVATE; fail-closed).
|
|
@@ -929,6 +999,13 @@ function parseApprovalDomain(ctx) {
|
|
|
929
999
|
},
|
|
930
1000
|
askQuestionEnabled: postureOn("ASK_QUESTION_ENABLED"), // §4④ AskUserQuestion live HITL; posture-gated (single-user → ON)
|
|
931
1001
|
toolApprovalEnabled: postureOn("TOOL_APPROVAL_ENABLED"), // [816]/[820]② live tool-approval HITL; posture-gated (single-user → ON), mirrors askQuestion
|
|
1002
|
+
// #151 车2(design/172 §3.3 D3):ToolApprovalCoordinator 窗长三元的安全余量。numEnv fail-loud 形,
|
|
1003
|
+
// 照邻居旋钮(approvalTimeoutSec 等)抄——坏形(非数字)在启动期炸,不静默折成 NaN。
|
|
1004
|
+
streamAskWindowMarginMs: numEnv("STREAM_ASK_WINDOW_MARGIN_MS", "10000"),
|
|
1005
|
+
// #151 车3 刀 3a(design/172 流内审批协议):五键。**总开关默认 false** ⇒ 全链逐字现行为(不发
|
|
1006
|
+
// approval_request、不落 ask 行、窗仍是 DEFAULT_APPROVAL_TTL_MS)。窗/帽四键与邻居旋钮同 numEnv
|
|
1007
|
+
// fail-loud 形——坏形在启动期炸,不静默折 NaN。语义与三问分析见 config-types.ts 的 StreamApprovalConfig。
|
|
1008
|
+
streamApproval: streamApprovalConfig(),
|
|
932
1009
|
// [875]a 成文:0(缺省)= durable HITL 无限期等人,时间型 reapSuspended 不跑;file/memory lane 的回收
|
|
933
1010
|
// 探针只在 DURABLE_APPROVAL=true 时注入(无 durable 的部署两只 suspended 回收器恒 NO-OP,parked 行的
|
|
934
1011
|
// 恢复把手 = POST /v1/runs/:id/cancel,[868]①)。checkpoint 过期驱动的那只不受本旋钮门控。
|
|
@@ -1099,6 +1176,37 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1099
1176
|
}
|
|
1100
1177
|
return boolEnv("LSP_HOST_ENABLED", true);
|
|
1101
1178
|
})();
|
|
1179
|
+
// ── #157([2755] tag `server.exec-lane.silent-downgrade-to-host`,P 类车道选择)——执行车道的
|
|
1180
|
+
// fail-closed 校验。旧形:REMOTE_EXEC 点名了一条 lane 但必需 env 不全(或值拼错不在闭集里)时,下面
|
|
1181
|
+
// 三元链的合取逐臂失败、静默滑穿到尾部 ⇒ CONFIG_PROVIDER=local 落 host(worker 本机、无隔离)、否则落
|
|
1182
|
+
// in-process stub,且两种跑法的成功回执同形(status=completed,test [2753]§2 实测「在哪儿跑的」对外
|
|
1183
|
+
// 零信息量)。操作员配了沙箱却在真机上跑 = P 类判据表的样板案,必须拒启点名,不静默降级。
|
|
1184
|
+
// 语义边界:REMOTE_EXEC **未设**(或空串,env 管理器的显式「不选」形)不是降级,是文档化的部署姿态
|
|
1185
|
+
// (local ⇒ host 默认,fleet ⇒ stub),照旧;拒启只咬「点了名却配不齐/认不出」。缺失点名要**准**——
|
|
1186
|
+
// 只列缺的件,不列 lane 的全量需求(WORKFLOW_SIZE_GUIDELINE / MEMORY_SYNC_* 半配拒启同族先例)。
|
|
1187
|
+
{
|
|
1188
|
+
const lane = process.env.REMOTE_EXEC;
|
|
1189
|
+
if (lane !== undefined && lane !== "") {
|
|
1190
|
+
const REQUIRED_ENVS_BY_LANE = {
|
|
1191
|
+
e2b: ["E2B_API_KEY"],
|
|
1192
|
+
k8s: ["K8S_SANDBOX_IMAGE"],
|
|
1193
|
+
ssh: ["SSH_HOST", "SSH_USER", "SSH_PRIVATE_KEY"],
|
|
1194
|
+
adb: ["ADB_SERIAL"],
|
|
1195
|
+
host: [],
|
|
1196
|
+
"local-docker": ["DOCKER_IMAGE"],
|
|
1197
|
+
};
|
|
1198
|
+
const required = Object.hasOwn(REQUIRED_ENVS_BY_LANE, lane) ? REQUIRED_ENVS_BY_LANE[lane] : undefined;
|
|
1199
|
+
if (required === undefined) {
|
|
1200
|
+
throw new Error(`REMOTE_EXEC="${lane}" is not an execution lane — valid: e2b | k8s | ssh | adb | host | local-docker. ` +
|
|
1201
|
+
`An unrecognized value used to fall through to the host/in-process lane silently; refusing to start instead (#157).`);
|
|
1202
|
+
}
|
|
1203
|
+
const missing = required.filter((name) => !process.env[name]);
|
|
1204
|
+
if (missing.length > 0) {
|
|
1205
|
+
throw new Error(`REMOTE_EXEC=${lane} but required env ${missing.join(", ")} ${missing.length === 1 ? "is" : "are"} missing/empty — ` +
|
|
1206
|
+
`refusing to start rather than silently downgrading to the host/in-process lane (#157; unset REMOTE_EXEC if you meant no sandbox lane).`);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1102
1210
|
return {
|
|
1103
1211
|
remoteExec: process.env.REMOTE_EXEC === "e2b" && process.env.E2B_API_KEY
|
|
1104
1212
|
? {
|
|
@@ -1375,8 +1483,8 @@ const MODEL_PLANE_GROUP_KEYS = [
|
|
|
1375
1483
|
const APPROVAL_GROUP_KEYS = [
|
|
1376
1484
|
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
1377
1485
|
"approvalHmacKeys", "durableApproval", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
1378
|
-
"resourceSuspendTtlSec", "askQuestionEnabled", "toolApprovalEnabled", "
|
|
1379
|
-
"manualModeShellGate",
|
|
1486
|
+
"resourceSuspendTtlSec", "askQuestionEnabled", "toolApprovalEnabled", "streamAskWindowMarginMs", "streamApproval", "mcpElicitation",
|
|
1487
|
+
"sensitiveWritePatterns", "manualModeShellGate",
|
|
1380
1488
|
];
|
|
1381
1489
|
const MEMORY_GROUP_KEYS = [
|
|
1382
1490
|
"memoryEngineEnabled", "memoryEngineDir", "memoryEngineRemoteLaneAllowed", "memoryEngineBackend", "memoryScope",
|
|
@@ -1459,6 +1567,11 @@ export function attachConfigGroups(flat) {
|
|
|
1459
1567
|
return flat;
|
|
1460
1568
|
}
|
|
1461
1569
|
export function loadConfig() {
|
|
1570
|
+
// #157:落点清槽必须排在**第一条**语句 —— 比下面那组同类复位还早一格,因为 rejectRetiredEnvs 本身会抛,
|
|
1571
|
+
// 而一轮抛出的 loadConfig 绝不能把**上一轮**成功解析的落点留在槽里被下一次排空读成本轮事实
|
|
1572
|
+
// (交叉复审第二轮,2026-08-06)。下面三格同族收集器在这条最早拒启路径上仍有同形残留,那是既有面、
|
|
1573
|
+
// 且都按 warn 排空,不在本件射程内。
|
|
1574
|
+
EXEC_LANE_NOTICE = undefined;
|
|
1462
1575
|
rejectRetiredEnvs(); // [2354] 退役 env 墓碑:在场即拒启带新名指引
|
|
1463
1576
|
CONFIG_WARNINGS.length = 0; // repeated loadConfig() calls (test setup) must not accumulate stale warnings
|
|
1464
1577
|
CONFIG_NOTICES.length = 0;
|
|
@@ -1528,6 +1641,18 @@ export function loadConfig() {
|
|
|
1528
1641
|
throw new Error(`DIRECT_APPROVAL_DOOR=true but D-G anchors are missing: ${missing.join(", ")} — refusing to start a half-open direct door (design/80 D-G boot invariant)`);
|
|
1529
1642
|
}
|
|
1530
1643
|
}
|
|
1644
|
+
// 🔴 boot invariant (#157-①, silent-downgrade-batch-20260805 普查候选 1/2): the BFF multi-tenant durable form
|
|
1645
|
+
// (DURABLE_APPROVAL=true + REQUIRE_PRINCIPAL=true, direct door OFF) had NO invariant — the D-G one above only
|
|
1646
|
+
// binds when DIRECT_APPROVAL_DOOR=true. On that form the four READ surfaces in approvals-assistant.ts
|
|
1647
|
+
// (GET /v1/approvals, /stream, /v1/assistant/inbox, /tasks) scope by the bare isOperator, whose empty-list
|
|
1648
|
+
// "true-for-all" lets ANY authenticated tenant principal list/subscribe every other tenant's pending queue
|
|
1649
|
+
// (redacted tool-input previews + decide-binding fields) and inspect arbitrary owners via ?owner. The WRITE
|
|
1650
|
+
// surfaces (decide/preempt/resume/plan_review) already use explicitOperatorOk and were never exposed. The
|
|
1651
|
+
// single-tenant legacy semantic (REQUIRE_PRINCIPAL unset — the shared service token IS the operator boundary)
|
|
1652
|
+
// is deliberately preserved: only the multi-tenant form is a hole. Refuse to start it.
|
|
1653
|
+
if (dgDurable && requirePrincipal && dgOperators.length === 0) {
|
|
1654
|
+
throw new Error("DURABLE_APPROVAL=true + REQUIRE_PRINCIPAL=true but OPERATOR_PRINCIPALS is empty — on a multi-tenant worker the empty operator set makes isOperator() true-for-all, so every authenticated tenant could read every other tenant's pending-approval queue (GET /v1/approvals & siblings). Set OPERATOR_PRINCIPALS, or unset REQUIRE_PRINCIPAL if this is a single-tenant deployment (#157-① boot invariant)");
|
|
1655
|
+
}
|
|
1531
1656
|
// 🔴 boot invariant (IMAGE-API-DESIGN.md §P2.4a): the bake door is build-host-RCE-capable, and isOperator([],p)
|
|
1532
1657
|
// is true-for-all — so enabling bakes with an empty OPERATOR_PRINCIPALS = a WORLD-WRITABLE RCE door. Refuse to
|
|
1533
1658
|
// start (never a half-open bake door). The explicit-operator gate in server.ts is the runtime twin of this.
|
|
@@ -1541,6 +1666,9 @@ export function loadConfig() {
|
|
|
1541
1666
|
if (imageBakesEnabled && !Object.values(authTokens).includes(bakeRunnerPrincipal)) {
|
|
1542
1667
|
throw new Error(`IMAGE_BAKES_ENABLED=true but no SERVICE_AUTH_TOKENS entry maps to the runner principal '${bakeRunnerPrincipal}' — the bake-runner authenticates by its credential (token-derived source), so a '<BAKE_RUNNER_TOKEN>=${bakeRunnerPrincipal}' SERVICE_AUTH_TOKENS entry is required or claim/ingest/heartbeat all 403 (IMAGE-API-DESIGN.md §P2.12 boot invariant)`);
|
|
1543
1668
|
}
|
|
1669
|
+
// #157 F 类观测:执行车道落点记账放在这里 —— 九域解析与上面全部跨域不变量都过了之后,即「这一轮
|
|
1670
|
+
// 确实启得起来」才成立的位置(写在 REMOTE_EXEC 解析途中的话,后置拒启会留下一条没启起来的落点)。
|
|
1671
|
+
EXEC_LANE_NOTICE = buildExecLaneNotice(orchestration.remoteExec);
|
|
1544
1672
|
// 平铺面**单点展开**九域产物(唯一存储处),再挂上九个组视图。组视图是对这同一个对象的取景器,
|
|
1545
1673
|
// 不是第二份拷贝 —— 故 `config.limitsHttp.port === config.port` 恒成立,含热应用之后(见
|
|
1546
1674
|
// attachConfigGroups / config-types.ts 分组段)。
|
package/dist/elicitation.d.ts
CHANGED
|
@@ -15,6 +15,10 @@ export interface ElicitationFrame {
|
|
|
15
15
|
mode?: "form";
|
|
16
16
|
/** "elicitation_complete" only: how it resolved. */
|
|
17
17
|
action?: McpElicitResponse["action"];
|
|
18
|
+
/** Server-signed emit timestamp (additive, [2805]§五→[2806]). These side-frames are SERVER-minted — they have
|
|
19
|
+
* no core-stream anchor, so no `eventId` (absence is honest, not an omission); this is the frame's own time
|
|
20
|
+
* coordinate instead, same posture as the stream-approval frames' `serverNowMs`. */
|
|
21
|
+
serverNowMs?: number;
|
|
18
22
|
}
|
|
19
23
|
/** The per-run context `onElicit` recovers via ALS. `emit` delivers a frame to the run's live stream;
|
|
20
24
|
* `abortSignal` (the run's own cancel signal) lets a run-abort promptly cancel an elicitation parked awaiting a
|
package/dist/elicitation.js
CHANGED
|
@@ -182,7 +182,7 @@ export class ElicitationCoordinator {
|
|
|
182
182
|
// cursor (an invisible prompt that then TTL-declines). On a successful emit, charge the per-run-tree budget; a
|
|
183
183
|
// FAILED emit declines (nobody can answer) and must NOT burn maxTotal / the per-server interval.
|
|
184
184
|
try {
|
|
185
|
-
await ctx.emit({ type: "elicitation", elicitationId: id, mcpServerName: req.server, message, ...(requestedSchema !== undefined ? { requestedSchema } : {}), mode: "form" });
|
|
185
|
+
await ctx.emit({ type: "elicitation", elicitationId: id, mcpServerName: req.server, message, ...(requestedSchema !== undefined ? { requestedSchema } : {}), mode: "form", serverNowMs: this.now() });
|
|
186
186
|
rc.total += 1;
|
|
187
187
|
rc.lastByServer.set(req.server, t);
|
|
188
188
|
}
|
|
@@ -194,7 +194,7 @@ export class ElicitationCoordinator {
|
|
|
194
194
|
// returning the answer to core (which replies to the MCP server on the still-open connection). Best-effort +
|
|
195
195
|
// ordering-uncritical: the shell already has the answer via respond; a late/lost dismiss is cosmetic.
|
|
196
196
|
void Promise.resolve()
|
|
197
|
-
.then(() => ctx.emit({ type: "elicitation_complete", elicitationId: id, mcpServerName: req.server, action: answer.action }))
|
|
197
|
+
.then(() => ctx.emit({ type: "elicitation_complete", elicitationId: id, mcpServerName: req.server, action: answer.action, serverNowMs: this.now() }))
|
|
198
198
|
.catch(() => undefined);
|
|
199
199
|
return answer;
|
|
200
200
|
};
|
|
@@ -2,6 +2,7 @@ import { DEFAULT_EFFORT_LEVELS } from "@sema-agent/core";
|
|
|
2
2
|
import { cwdHonored } from "../../task-cwd.js";
|
|
3
3
|
import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
4
4
|
import { sendJson, sendError } from "../send.js";
|
|
5
|
+
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
5
6
|
export async function handleCapabilities(req, res, url, ctx) {
|
|
6
7
|
const miss = { fell: false };
|
|
7
8
|
await handleCapabilitiesBody(req, res, url, ctx, miss);
|
|
@@ -211,6 +212,19 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
211
212
|
// exists, and only fail-closed denies without one ([819]⑤). The durable-approval leg (checkpoint park +
|
|
212
213
|
// /decide) is independent of this.
|
|
213
214
|
toolApproval: Boolean(deps.toolApproval),
|
|
215
|
+
// #151 车3 刀 3b(设计稿 §2.4):design/172 **流内审批协议**(`approval_request` 呈卡帧 + 开流
|
|
216
|
+
// preamble 对账基准 + durable 回决端点)。判据走**单一谓词** `resolveStreamApprovalGate` —— 与协调器
|
|
217
|
+
// 的 askStore 注入、与 `POST /v1/tasks/:taskId/asks/:askId/decision` 的 501 门同一个符号,所以
|
|
218
|
+
// 「这里说 true」⟺「协议真的在场」是结构成立的,壳不必 trial-by-帧(诚实缺席纪律,同上文各格)。
|
|
219
|
+
// 五个合取项里最容易被忽略的两条:**账必须是持久的**(local 的 InMemory 形重启即丢已接受的决议)
|
|
220
|
+
// 与 **park 设施必须在场**(缺席时窗到期没有降级目的地,结局是 core fail-closed deny —— 比现行
|
|
221
|
+
// 5min 活卡更差,所以协议诚实地不上场、活卡腿逐字保留)。
|
|
222
|
+
streamApproval: resolveStreamApprovalGate({
|
|
223
|
+
toolApprovalEnabled: Boolean(deps.toolApproval),
|
|
224
|
+
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
|
225
|
+
backend: deps.backend,
|
|
226
|
+
parkFacility: deps.checkpointStore !== undefined,
|
|
227
|
+
}).active,
|
|
214
228
|
// [1469] POST /v1/side-query(core 1.361 Runner.sideQuery 包装):一次性 brain 路由问答,无 session
|
|
215
229
|
// 副作用。恒可用(runner 自带)——探测位供壳判「引擎腿在」而非 trial-by-404。
|
|
216
230
|
sideQuery: true,
|
|
@@ -22,6 +22,7 @@ export declare const RUN_COMPACT_RE: RegExp;
|
|
|
22
22
|
export declare const ELICIT_RESPOND_RE: RegExp;
|
|
23
23
|
export declare const QUESTION_RESPOND_RE: RegExp;
|
|
24
24
|
export declare const TOOL_APPROVAL_RESPOND_RE: RegExp;
|
|
25
|
+
export declare const TASK_ASK_DECISION_RE: RegExp;
|
|
25
26
|
export declare const RUN_SUBAGENT_STEER_RE: RegExp;
|
|
26
27
|
export declare const RUN_SUBAGENT_RESUME_RE: RegExp;
|
|
27
28
|
export declare const RUN_SUBAGENT_OUTPUT_RE: RegExp;
|