@sema-agent/server 7.66.0 → 7.67.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/USAGE.md +6 -3
- package/dist/approval-card.d.ts +8 -0
- package/dist/approval.d.ts +3 -0
- package/dist/approval.js +2 -1
- package/dist/boot/resolve-spec.js +12 -5
- package/dist/config-catalog.js +2 -2
- package/dist/config-center/apply-effective.js +1 -1
- package/dist/config-types.d.ts +15 -2
- package/dist/config.d.ts +25 -3
- package/dist/config.js +58 -30
- package/dist/deployment-governance.d.ts +8 -0
- package/dist/http/routes/diagnostics.d.ts +27 -2
- package/dist/http/routes/diagnostics.js +21 -1
- package/dist/http/routes/rules.js +15 -10
- package/dist/http/routes/tasks.js +3 -8
- package/dist/http/server.js +3 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/permission-rule-vocab.d.ts +13 -21
- package/dist/permission-rule-vocab.js +6 -5
- package/dist/plugins/permission-rule-store-file.d.ts +3 -5
- package/dist/plugins/permission-rule-store-file.js +2 -1
- package/dist/plugins/permission-rule-store-sql.d.ts +10 -10
- package/dist/plugins/permission-rule-store-sql.js +35 -20
- package/dist/posture-source.d.ts +57 -0
- package/dist/posture-source.js +27 -0
- package/dist/read-face-posture.d.ts +9 -4
- package/dist/read-face-posture.js +7 -11
- package/dist/rules-consent.d.ts +15 -10
- package/dist/rules-consent.js +41 -21
- package/dist/session-titler.js +3 -12
- package/dist/task-settings.d.ts +30 -0
- package/dist/task-settings.js +39 -18
- package/dist/tool-approval.d.ts +5 -4
- package/dist/tool-approval.js +2 -2
- package/dist/trace/project.d.ts +4 -3
- package/package.json +3 -3
package/USAGE.md
CHANGED
|
@@ -307,8 +307,11 @@ MODEL_CASCADE_LADDER=deepseek-flash,deepseek-pro # 目录里的模型名,cheap
|
|
|
307
307
|
- 入口:`import "@sema-agent/server/main"`(exports 正门;此前宿主只能用 node_modules 路径字符串定位
|
|
308
308
|
`dist/main.js`,那是未承诺的内部路径)。语义承诺三条:**import 即 boot**(副作用模块形,无需调用);
|
|
309
309
|
**不读 `process.argv`**(宿主可用 argv 传自己的标记,`ps` args 判据不受干扰);`./package.json` 可读。
|
|
310
|
-
- `/health` 身份字段承诺:`pid`(= 引擎进程)
|
|
311
|
-
与 DB_BACKEND 无关)
|
|
310
|
+
- `/health` 身份字段承诺:`pid`(= 引擎进程)、`startedAt`(**7.67.0+**,= 引擎进程起点的 epoch ms,
|
|
311
|
+
一次铸)与 `dataRoot`(= 生效数据根,解析恒回退 `~/.ai-agent`,与 DB_BACKEND 无关)**恒在**——宿主用
|
|
312
|
+
它们验证「这个端口上的 /health 是不是我起的那个引擎」。`startedAt` 变了 = 这个端口后面换了一条命
|
|
313
|
+
(另一个宿主重启了共享引擎):壳据此把「引擎换代」当**一等事实**处理,不必等某个带鉴权的请求先撞一次
|
|
314
|
+
401 才发现(`/health` 无鉴权,心跳恒绿)。⚠️ 只给这一个数:service token 本体与代际计数**不上**这一面。
|
|
312
315
|
钉:`test/health-identity-contract.test.ts`。
|
|
313
316
|
- `/health` 活体 store 探测(5.14.0,SQL 后端专属 additive):后台探针(`STORE_PROBE_INTERVAL_MS`,
|
|
314
317
|
默认 15000;0=关;负/非数启动响亮拒)缓存一份 DB 真往返结果,`storeProbe:{live,ageMs,error?}` 在
|
|
@@ -961,7 +964,7 @@ curl -N http://<host>:8090/v1/tasks/stream -H 'content-type: application/json' \
|
|
|
961
964
|
| `POST /v1/admin/drain` `{"reason":"…"}` | **停机因由显式声明**(7.35.0+;operator-only —— principal 须在 `OPERATOR_PRINCIPALS` 内,空名单 = 谁都不是 operator ⇒ 恒 403)。编排壳在发 SIGTERM **之前**调它;server 记进程内 `drainState.reason`,draining 期由 `/health` 的 `drainReason` 与 503 `draining` 体的 `reason` 透出(未声明 = 两面键缺席)。声明是**覆盖式**、无 TTL、随进程重启清零 ⇒ 每次停机流程内都要重新声明。`reason` 非空、trim 后 ≤256 字符,坏值 400。🔴 **写口是特权的,读口不是**:`/health` 免鉴权(见下一行)且缺省全网卡监听 —— 任何能连到这个端口的人都读得到你写的那句话。**别在 reason 里写工单号、内部主机名或任何内部标识**。 |
|
|
962
965
|
| `GET /v1/config/catalog` | **配置目录自描述**(operator-only —— `explicitOperatorOk`,空名单=谁都不是)。逐 env 键一行:`{name, domain, type, semantics, writeLanes, effectiveLane, envHeld, envTiming, staticDefault?/derivedDefaultNote?, effectiveValue, valueClass, danger, center?{domain,path,precedence,timing,restartSlice?,domainExists,managedNow,effectiveNow}}`;顶层带 `restartSlices`(重启片闭集)与 `centerDomains`(settings-schema 现行域表)。**两轴不混**:`writeLanes`=谁能写,`effectiveLane`=现在谁在生效(env-wins 族 env 占位 ⇒ center 腿 `effectiveNow:false`)。secret 类值**永不回传**(只回在场位;opaque 回在场位+长度;URL 剥 userinfo)。完备性由仓内 AST 双向对账门执法(分母=全 `src/` 生产 env 读取点)。旋钮 `CONFIG_CATALOG_ENABLED=false`(合规部署)⇒ 路径 404 + 能力位 `configCatalog:false`。响应 `Cache-Control: no-store`。**7.57.0 起**:`enum` 行的 `effectiveValue` **恒落在该行自己的 `enumValues` 闭集内**(仓内机器门执法)——此前 `SESSION_BACKEND` 回的是**内部标签** `tidb`(自 5.0.0 起是**退役公名**,写回去 boot 拒),现归一成公名 `mysql`;`MODEL_DEFAULT_THINKING` 补上一直被解析器接受、却漏在目录闭集外的 `xhigh`,并改为回**实算值**(`off`/未设 ⇒ `null`)而不是 env 原文。 |
|
|
963
966
|
| `POST /v1/admin/config/refresh` | **配置手动刷新**(operator-only,与上一行同门:空 `OPERATOR_PRINCIPALS` ⇒ 恒 403)。改完中心配置 / `config.d` 之后不想等 60s 轮询就调它:触发**一次既有**的 refresh 拍(在飞则汇入那一拍,绝不并发双拍),返回时该拍已落地。200 体 `{triggered:true,targetVersion,appliedVersion}` —— 两个世代号与 `/health` 的 `configTargetVersion`/`configAppliedVersion` 同源。本部署没有配置管道(纯 env worker:既没配 `CONFIG_CENTER`、也不是 `CONFIG_PROVIDER=local`)⇒ 409 `config.refresh_unavailable` 指路,不会回一个「触发了但什么都没发生」的 200。 |
|
|
964
|
-
| `GET /health` | 健康(无需鉴权)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
|
|
967
|
+
| `GET /health` | 健康(无需鉴权)。**7.67.0+** 恒带 `startedAt`(引擎进程起点 epoch ms,一次铸;与 `pid` 并列的换代锚——值变了就是换了一条命)。7.37.0+ 配置管道在场时带世代账键:`configTargetVersion`(本副本**最后见到**的配置版本,身份标)、`configAppliedVersion`(最后一次真落地的版本)、`configTargetOrdinal`/`configAppliedOrdinal`(7.38.0-rc.2 起,**收货序数**——`CONFIG_PROVIDER=local` 下 version 是内容哈希不承诺序,「改了没生效」看序数对不对齐)、`configApplyStaleMs`(target≠applied **持续**时长;收敛时键缺席)—— 编排器据此摘掉「配置持续落后」的副本 |
|
|
965
968
|
| `GET /metrics` | Prometheus 指标(有 token 时需带) |
|
|
966
969
|
|
|
967
970
|
> **场景可用性(7.7.0 起)**:场景详情里 `enabled` 与 `available` 是**两件事**。`enabled` = 这条场景
|
package/dist/approval-card.d.ts
CHANGED
|
@@ -81,6 +81,7 @@ export declare const RuleOfferSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
81
81
|
kind: z.ZodLiteral<"single">;
|
|
82
82
|
rule: z.ZodString;
|
|
83
83
|
match: z.ZodEnum<{
|
|
84
|
+
path: "path";
|
|
84
85
|
exact: "exact";
|
|
85
86
|
prefix: "prefix";
|
|
86
87
|
wildcard: "wildcard";
|
|
@@ -93,6 +94,7 @@ export declare const RuleOfferSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
93
94
|
kind: z.ZodLiteral<"command">;
|
|
94
95
|
rule: z.ZodString;
|
|
95
96
|
match: z.ZodEnum<{
|
|
97
|
+
path: "path";
|
|
96
98
|
exact: "exact";
|
|
97
99
|
prefix: "prefix";
|
|
98
100
|
wildcard: "wildcard";
|
|
@@ -156,6 +158,7 @@ export declare const RuleOfferRawSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
156
158
|
kind: z.ZodLiteral<"single">;
|
|
157
159
|
rule: z.ZodString;
|
|
158
160
|
match: z.ZodEnum<{
|
|
161
|
+
path: "path";
|
|
159
162
|
exact: "exact";
|
|
160
163
|
prefix: "prefix";
|
|
161
164
|
wildcard: "wildcard";
|
|
@@ -168,6 +171,7 @@ export declare const RuleOfferRawSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
168
171
|
kind: z.ZodLiteral<"command">;
|
|
169
172
|
rule: z.ZodString;
|
|
170
173
|
match: z.ZodEnum<{
|
|
174
|
+
path: "path";
|
|
171
175
|
exact: "exact";
|
|
172
176
|
prefix: "prefix";
|
|
173
177
|
wildcard: "wildcard";
|
|
@@ -367,6 +371,7 @@ export declare const ApprovalCardSchema: z.ZodObject<{
|
|
|
367
371
|
kind: z.ZodLiteral<"single">;
|
|
368
372
|
rule: z.ZodString;
|
|
369
373
|
match: z.ZodEnum<{
|
|
374
|
+
path: "path";
|
|
370
375
|
exact: "exact";
|
|
371
376
|
prefix: "prefix";
|
|
372
377
|
wildcard: "wildcard";
|
|
@@ -379,6 +384,7 @@ export declare const ApprovalCardSchema: z.ZodObject<{
|
|
|
379
384
|
kind: z.ZodLiteral<"command">;
|
|
380
385
|
rule: z.ZodString;
|
|
381
386
|
match: z.ZodEnum<{
|
|
387
|
+
path: "path";
|
|
382
388
|
exact: "exact";
|
|
383
389
|
prefix: "prefix";
|
|
384
390
|
wildcard: "wildcard";
|
|
@@ -475,6 +481,7 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
|
|
|
475
481
|
kind: z.ZodLiteral<"single">;
|
|
476
482
|
rule: z.ZodString;
|
|
477
483
|
match: z.ZodEnum<{
|
|
484
|
+
path: "path";
|
|
478
485
|
exact: "exact";
|
|
479
486
|
prefix: "prefix";
|
|
480
487
|
wildcard: "wildcard";
|
|
@@ -487,6 +494,7 @@ export declare const ApprovalCardEnvelopeSchema: z.ZodObject<{
|
|
|
487
494
|
kind: z.ZodLiteral<"command">;
|
|
488
495
|
rule: z.ZodString;
|
|
489
496
|
match: z.ZodEnum<{
|
|
497
|
+
path: "path";
|
|
490
498
|
exact: "exact";
|
|
491
499
|
prefix: "prefix";
|
|
492
500
|
wildcard: "wildcard";
|
package/dist/approval.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* suspend/resume,见 createDurableAskPolicy 与 /v1/approvals 的 checkpointStore 腿)。
|
|
5
5
|
*/
|
|
6
6
|
import { type ToolPolicy } from "@sema-agent/core";
|
|
7
|
+
import { type PostureSource } from "./posture-source.js";
|
|
7
8
|
/**
|
|
8
9
|
* 运维**是否表达了门意图** —— boot 的单用户 allow-all 基线只在"零门意图"时才允许铺开,
|
|
9
10
|
* 所以这个谓词漏一格 = 那一格的意图被 allow-all 静默吞掉。
|
|
@@ -23,6 +24,7 @@ export declare function hasOperatorGateIntent(config: {
|
|
|
23
24
|
approvalDeny: readonly string[];
|
|
24
25
|
approvalNeverAuto: readonly string[];
|
|
25
26
|
durableApproval: boolean;
|
|
27
|
+
durableApprovalSource: PostureSource;
|
|
26
28
|
}): boolean;
|
|
27
29
|
/**
|
|
28
30
|
* 门意图不可服务=boot 拒启(全窗复审 D3-F1,HIGH;与 env 墓碑族同谱系)。
|
|
@@ -35,6 +37,7 @@ export declare function assertGateIntentServiceable(config: {
|
|
|
35
37
|
approvalDeny: readonly string[];
|
|
36
38
|
approvalNeverAuto: readonly string[];
|
|
37
39
|
durableApproval: boolean;
|
|
40
|
+
durableApprovalSource: PostureSource;
|
|
38
41
|
}, checkpointStorePresent: boolean): void;
|
|
39
42
|
/**
|
|
40
43
|
* [ref] 件⑩ —— **center 的 `forceDurableGate` 在无 checkpoint 店的部署上会静默 inert**,补一条 boot 披露。
|
package/dist/approval.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { isNamespacedCoveringRuleName, namespacedRuleNameCovers } from "@sema-agent/core";
|
|
2
|
+
import { isExplicitPosture } from "./posture-source.js";
|
|
2
3
|
export function hasOperatorGateIntent(config) {
|
|
3
4
|
return (config.approvalRequire.length > 0 ||
|
|
4
5
|
config.approvalDeny.length > 0 ||
|
|
5
6
|
config.approvalNeverAuto.length > 0 ||
|
|
6
|
-
config.durableApproval);
|
|
7
|
+
(config.durableApproval && isExplicitPosture(config.durableApprovalSource)));
|
|
7
8
|
}
|
|
8
9
|
export function assertGateIntentServiceable(config, checkpointStorePresent) {
|
|
9
10
|
if (!hasOperatorGateIntent(config) || checkpointStorePresent)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { realpathSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
3
4
|
import { NodeExecutionEnv, QUESTION_AWAITS_RESUME, deriveAskEffective, expandTiers, resolveAskSeamForm, tightenTaskSpec } from "@sema-agent/core";
|
|
4
5
|
import { cappedCeiling } from "../budget.js";
|
|
5
6
|
import { centerPromptProvider, centerIdentityAssembled } from "../capabilities/center-prompts.js";
|
|
@@ -411,11 +412,15 @@ export function createResolveSpec(ctx) {
|
|
|
411
412
|
const pathAdjudication = hostSemanticsLane
|
|
412
413
|
? (() => {
|
|
413
414
|
const sessionCwd = auth?.sessionId ? effectiveHostWorkspace(perSessionCwd.get(auth.sessionId), {}) : undefined;
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
415
|
+
const realRoot = sessionCwd ?? (config.remoteExec === undefined ? process.cwd() : undefined);
|
|
416
|
+
const gateCwd = realRoot ?? join(config.localDataRoot ?? localRoot, "fs-write-gate-unrooted");
|
|
417
|
+
const realHome = config.remoteExec === undefined ? homedir() : undefined;
|
|
418
|
+
return {
|
|
419
|
+
env: new NodeExecutionEnv({ cwd: gateCwd }),
|
|
420
|
+
cwd: gateCwd,
|
|
421
|
+
...(realRoot !== undefined ? { taskRoot: realRoot } : {}),
|
|
422
|
+
...(realHome !== undefined ? { taskHome: realHome } : {}),
|
|
423
|
+
};
|
|
419
424
|
})()
|
|
420
425
|
: {
|
|
421
426
|
env: new DeferredSandboxPathEnv({
|
|
@@ -461,6 +466,8 @@ export function createResolveSpec(ctx) {
|
|
|
461
466
|
? {
|
|
462
467
|
env: pathAdjudication.env,
|
|
463
468
|
...(pathAdjudication.cwd !== undefined ? { cwd: pathAdjudication.cwd } : {}),
|
|
469
|
+
...(pathAdjudication.taskRoot !== undefined ? { taskRoot: pathAdjudication.taskRoot } : {}),
|
|
470
|
+
...(pathAdjudication.taskHome !== undefined ? { taskHome: pathAdjudication.taskHome } : {}),
|
|
464
471
|
...(scratchpadDir ? { scratchpadDir } : {}),
|
|
465
472
|
...gateCommon,
|
|
466
473
|
}
|
package/dist/config-catalog.js
CHANGED
|
@@ -111,7 +111,7 @@ export const CONFIG_CATALOG = [
|
|
|
111
111
|
r("DOCKER_PIDS_LIMIT", "orchestration", "number", "local-docker fork-bomb 背栓(--pids-limit;非法值回 adapter 默认 512+warn)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ adapter 默认 512" }),
|
|
112
112
|
r("DOCKER_SANDBOX_ENV", "orchestration", "csv", "转发进容器的本进程 env **名字**表(值只在 worker env,不进 spec/center)", { danger: SEC }),
|
|
113
113
|
r("DRAIN_GRACE_MS", "limitsHttp", "number", "SIGTERM 优雅排空窗(ms;期间新计费提交 503+Retry-After)", { staticDefault: String(10 * 60 * 1000), configKey: "drainGraceMs", center: cLimits("drainGraceMs") }),
|
|
114
|
-
r("DURABLE_APPROVAL", "approval", "boolean", "durable suspend/resume HITL(需 SQL 店;决定 checkpointStore 与 /v1/approvals 面的存在性)", {
|
|
114
|
+
r("DURABLE_APPROVAL", "approval", "boolean", "durable suspend/resume HITL(需 SQL 店;决定 checkpointStore 与 /v1/approvals 面的存在性)", { danger: SEC, derivedDefaultNote: "posture 三态(S-178):显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)且有耐久店 ⇒ on / 多租户或无耐久店 ⇒ off;生效来源见 GET /v1/diagnostics/wiring 的 serverGates.durableApproval.source", configKey: "durableApproval" }),
|
|
115
115
|
r("E2B_ALLOW_NET", "orchestration", "boolean", "e2b 沙箱出网许可", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 不铸键(adapter 默认允许)" }),
|
|
116
116
|
r("E2B_API_KEY", "orchestration", "secret", "E2B 车道 API 凭据(REMOTE_EXEC=e2b 必需件)", { danger: [...SEC, ...BILL] }),
|
|
117
117
|
r("E2B_BG_DEFAULT_TIMEOUT_SEC", "orchestration", "number", "e2b 后台 shell 缺省超时(秒)", { staticDefault: "300" }),
|
|
@@ -403,7 +403,7 @@ export const CONFIG_CATALOG = [
|
|
|
403
403
|
r("STREAM_APPROVAL_RECONCILE_BATCH", "approval", "number", "对账收敛器每 tick 每段行数帽(整数)", { staticDefault: "200" }),
|
|
404
404
|
r("STREAM_APPROVAL_REPLAY_MAX", "approval", "number", "开流 preamble 重放帽", { staticDefault: "50" }),
|
|
405
405
|
r("STREAM_ASK_WINDOW_MARGIN_MS", "approval", "number", "审批窗长三元安全余量(ms)", { staticDefault: "10000", configKey: "streamAskWindowMarginMs" }),
|
|
406
|
-
r("STREAM_ASK_WINDOW_MS", "approval", "number", "活卡审批窗长(ms;所有腿生效,与流内协议上不上场无关;0=关窗⇒不发卡恒走无人值守终局;负值与超 Node 定时器上界 2147483647 的值拒启)", {
|
|
406
|
+
r("STREAM_ASK_WINDOW_MS", "approval", "number", "活卡审批窗长(ms;所有腿生效,与流内协议上不上场无关;0=关窗⇒不发卡恒走无人值守终局;负值与超 Node 定时器上界 2147483647 的值拒启)", { derivedDefaultNote: "posture 三态(S-178):显式值恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)⇒ 86400000(24h;若与显式 STREAM_APPROVAL_ORPHAN_TTL_MS 撞跨旋钮不变量则让位回 300000 并 warn)/ 多租户 ⇒ 300000;生效来源见 GET /v1/diagnostics/wiring 的 serverGates.streamAskWindowMs.source" }),
|
|
407
407
|
r("SYNC_IMPORT_LEASE_STALE_SEC", "memory", "number", "session-sync staged import 租约陈旧阈(秒)", { staticDefault: "600", configKey: "syncImportLeaseStaleSec" }),
|
|
408
408
|
r("TASK_MAX_OUTPUT_TOKENS_MAX", "limitsHttp", "number", "调用方自报 limits.maxOutputTokens 的运营方封顶(Math.min 钳显式请求值;坏值=封顶不生效+响亮 warn)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
|
|
409
409
|
r("TASK_MAX_TURNS_MAX", "limitsHttp", "number", "调用方自报 limits.maxTurns 的运营方封顶(同族三旋钮;只钳显式请求值,不是全队默认限额)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 不封顶" }),
|
|
@@ -373,7 +373,7 @@ function stageApprovalRequire(raw, logger) {
|
|
|
373
373
|
`Refusing the domain instead of dropping them: dropping every entry would SILENTLY remove the approval gate.`, nonString.map((e) => `[${e.i}]`), KEPT_WOULD_REMOVE_GATE);
|
|
374
374
|
}
|
|
375
375
|
const names = raw.filter((n) => typeof n === "string").map((n) => n.trim()).filter((n) => n.length > 0);
|
|
376
|
-
const bad = findUnmatchableToolNames(names);
|
|
376
|
+
const bad = findUnmatchableToolNames(names, { consumer: "tool-name-table", behavior: "deny" });
|
|
377
377
|
if (bad.length > 0) {
|
|
378
378
|
return refuse(formatUnmatchableToolNames("published runtime.approvalRequire", bad), bad.map((b) => b.name), KEPT_WOULD_MATCH_NOTHING);
|
|
379
379
|
}
|
package/dist/config-types.d.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* compiling unchanged. All imports below are top-level `import type` (erased — never a load edge,
|
|
7
7
|
* see test/module-cycle-gate.test.ts header on verbatimModuleSyntax). */
|
|
8
8
|
import type { ReadFaceSource } from "./read-face-posture.js";
|
|
9
|
+
import type { PostureSource } from "./posture-source.js";
|
|
9
10
|
import type { SealedKeyPoison } from "./sealed-key.js";
|
|
10
11
|
import type { A2aServerSpec, CompliancePosture, CrossSessionDialogExpiry, CrossSessionInboundSetting, FileHistoryRetentionPolicy, LockedKey, McpServerSpec, Model, ModelRoles, RetentionPolicy, WriteProtectedEntry } from "@sema-agent/core";
|
|
11
12
|
import type { ApprovalHmacKey, PrincipalJwtKey } from "./auth-keys.js";
|
|
@@ -962,6 +963,14 @@ export interface ServiceConfigFlat {
|
|
|
962
963
|
* operator resumes it on any replica. Default OFF([ref] A-5:轮询车道已退役,OFF+门意图 ⇒ core UNGATED
|
|
963
964
|
* 告警,不再有回退门形). `DURABLE_APPROVAL=true`. */
|
|
964
965
|
durableApproval: boolean;
|
|
966
|
+
/** S-178:上面那一位**是谁定的**(闭集四词,见 `posture-source.ts` —— 与 `readFaceSource` 同一张表)。
|
|
967
|
+
* 7.67.0 起本键进 posture 家族:单机 turnkey ∧ 有耐久店 ⇒ 默认 ON,多租户/内存底座仍 OFF,显式 env
|
|
968
|
+
* 两个方向恒赢。来源上 operator 读面(`GET /v1/diagnostics/wiring` 的 `serverGates`),因为「这台机器
|
|
969
|
+
* 为什么开着 park/resume」在升级后是运维必须看得见的一句话(与 S-167 的补偿项同族)。 */
|
|
970
|
+
durableApprovalSource: PostureSource;
|
|
971
|
+
/** S-178:`STREAM_ASK_WINDOW_MS` 的来源(同一张四词表)。值在 {@link StreamApprovalConfig.windowMs};
|
|
972
|
+
* 单机 turnkey 缺席 ⇒ 24h(posture),多租户缺席 ⇒ 5min(engine-default),显式 env ⇒ env。 */
|
|
973
|
+
streamAskWindowMsSource: PostureSource;
|
|
965
974
|
/** [ref] seam #2 (preemption) / [ref] ("失败≠没做完" third state): when true (+ durable mode), the
|
|
966
975
|
* /v1/runs durable path opts every task into core `resourceSuspend` — a budget/turns/walltime limit OR a
|
|
967
976
|
* scheduler preempt (`POST /v1/assistant/tasks/:id/preempt`) durably SUSPENDS the task (resumable via
|
|
@@ -1247,6 +1256,10 @@ export interface ServiceConfigFlat {
|
|
|
1247
1256
|
/** Auto-generate a session title at first submit (one cheap-model call per
|
|
1248
1257
|
* session, write-once; the user rename layer is BFF-side). Default ON; `SESSION_AUTO_TITLE=false` opts out. */
|
|
1249
1258
|
sessionAutoTitle: boolean;
|
|
1259
|
+
/** S-178:上面那一位的来源(同一张四词表)。本键**不进 posture**(缺省恒 true,两形同值)——
|
|
1260
|
+
* 报来源是为了让运维分辨「这台机器显式关了自动标题」与「它本来就开着」;壳此前注 `false` 的那条
|
|
1261
|
+
* 理由(gateway 不可达时的 warn 噪音)已在 `session-titler.ts` 侧修掉。 */
|
|
1262
|
+
sessionAutoTitleSource: PostureSource;
|
|
1250
1263
|
/** RFC A2 (clay 拍 A 切片先行): mount the model-facing `SelectEnvironment` tool (spec.tools, 判定②(a)) when
|
|
1251
1264
|
* the per-task image chain is live (k8s + image catalog). Default ON; `SELECT_ENVIRONMENT_TOOL=false` opts out. */
|
|
1252
1265
|
selectEnvironmentTool: boolean;
|
|
@@ -1682,7 +1695,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
|
|
|
1682
1695
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
|
|
1683
1696
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|
|
1684
1697
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
1685
|
-
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
1698
|
+
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "durableApprovalSource" | "streamAskWindowMsSource" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
1686
1699
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
1687
1700
|
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
1688
1701
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
|
@@ -1693,7 +1706,7 @@ export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled"
|
|
|
1693
1706
|
* config 这一层它只是一句声明。 */
|
|
1694
1707
|
export type ServiceAuthConfig = Pick<ServiceConfigFlat, "authToken" | "authTokens" | "allowUnauthedWrites" | "corsOrigins" | "principalHeader" | "requirePrincipal" | "autonomy" | "commandPolicy" | "compliancePosture" | "lockedConfigKeys" | "configCatalogEnabled" | "retentionPolicy" | "retentionSweep" | "operatorPrincipals" | "principalJwtPubkeys" | "principalJwtIss" | "principalJwtAud" | "principalJwtMaxTtlSec" | "bindHost" | "bindHostSource">;
|
|
1695
1708
|
/** 组:orchestration(编排 + 执行车道 + 沙箱面 + workflow/后台 agent 存留)。 */
|
|
1696
|
-
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1709
|
+
export type ServiceOrchestrationConfig = Pick<ServiceConfigFlat, "remoteExec" | "worktreeIsolation" | "leaderEnabled" | "leaderFanoutEnabled" | "routerEnabled" | "selfOrchestrationEnabled" | "selfOrchestrationModels" | "selfOrchestrationWorkerIsolation" | "forkEnabled" | "experimentalObserverAgents" | "schedulerEnabled" | "schedulerSessionWakeup" | "schedulerSessionLifetime" | "schedulerStorePath" | "planModeEnabled" | "hooksTimeoutMs" | "workflowRunStoreBackend" | "workflowOrphanGraceMs" | "workflowJournalRetentionMs" | "workflowRunRetentionMs" | "usageWindows" | "workflowAgentsReadOnly" | "workflowSizeGuideline" | "backgroundAgentRetentionMs" | "backgroundAgentStaleRunningMs" | "backgroundAgentParkClaimStaleMs" | "delegationEntryCaps" | "rosterRetentionMs" | "leaderRunStaleMs" | "scratchpadSweepTtlMs" | "sandboxPkgSource" | "sessionAutoTitle" | "sessionAutoTitleSource" | "selectEnvironmentTool" | "envFactsEnabled" | "toolDeferLongtail" | "lspEnabled" | "lspHostEnabled" | "imageBakes" | "readFace" | "readFaceSource" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude">;
|
|
1697
1710
|
/** 组:limitsHttp(HTTP 面 + 各类上限/配额/回收窗)。 */
|
|
1698
1711
|
export type ServiceLimitsHttpConfig = Pick<ServiceConfigFlat, "port" | "attachmentOrphanGraceMs" | "workspaceFileMaxBytes" | "attachmentMaxBytes" | "attachmentMimeAllowlist" | "attachmentUnboundTtlMs" | "infraCostRates" | "drainGraceMs" | "sighupIdleGraceMs" | "parentPid" | "engineAutoExit" | "engineLingerMs" | "rateLimitPerMin" | "maxTaskCostUsd" | "maxTaskTokens" | "maxPrincipalCostUsd" | "costQuotaWindowSec" | "reapIntervalSec" | "runStaleSec" | "toolResultTtlSec">;
|
|
1699
1712
|
/** 组:observability(可观测)。 */
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Model } from "@sema-agent/core";
|
|
1
|
+
import { type Model, type PathRuleBases, type RuleBehavior } from "@sema-agent/core";
|
|
2
2
|
import type { Autonomy } from "./runtime-governance.js";
|
|
3
3
|
import type { ServiceConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js";
|
|
4
4
|
/** [ref] A1: the config TYPE face lives in the leaf module config-types.ts; re-exported here so
|
|
@@ -237,8 +237,30 @@ export interface UnmatchableToolName {
|
|
|
237
237
|
name: string;
|
|
238
238
|
guidance: string;
|
|
239
239
|
}
|
|
240
|
-
|
|
241
|
-
|
|
240
|
+
/**
|
|
241
|
+
* S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。两个消费者形态:
|
|
242
|
+
*
|
|
243
|
+
* · `"tool-name-table"` —— 消费者按**整串工具名**比对(精确 ∪ covering):env `APPROVAL_*` 三键与
|
|
244
|
+
* 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`),**以及**
|
|
245
|
+
* `settings.permissions.allow`(消费者 core 的 `createAllowDenyPolicy` 的 allow 位)。带括号的内容形
|
|
246
|
+
* 在这里永远匹配不上任何活工具名 —— 与引擎的规则文法认不认得它**无关**,是这条腿的比对方式决定的,
|
|
247
|
+
* 所以内容形照旧响亮拒 + 指路。
|
|
248
|
+
* · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):
|
|
249
|
+
* `settings.permissions.deny` 与 `.ask`(S-177 / core 7.9.0 起两张表都走编译器)。引擎自此**真的按内容
|
|
250
|
+
* 匹配**它们(`Bash(rm -r:*)` / `Edit(~/.sema/**)`),所以本仓不许再替它拒 —— 判据整条交回 core 的
|
|
251
|
+
* `validatePermissionRules`,按这张表自己的 behavior 问。
|
|
252
|
+
*
|
|
253
|
+
* 🔴 **不对称不是特判,是从「消费者是谁」自然导出的**:`allow` 之所以豁免名字形与文法两族(退役名 /
|
|
254
|
+
* 短名 / 反斜杠 / 超帽),是因为它是**白名单** —— 一个匹配不上的条目让白名单空掉 ⇒ 一切都不在白名单
|
|
255
|
+
* ⇒ fail-CLOSED 且当场可见,那是可接受的另一种失败形;而内容形不同,它是一个 CC 用户为了**少弹窗**
|
|
256
|
+
* 写下的拼法族错,静默代价是整只 Bash 不可用。
|
|
257
|
+
*/
|
|
258
|
+
export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
|
|
259
|
+
export declare function findUnmatchableToolNames(names: readonly string[], opts: {
|
|
260
|
+
consumer: RuleEntryConsumer;
|
|
261
|
+
behavior: RuleBehavior;
|
|
262
|
+
pathBases?: PathRuleBases;
|
|
263
|
+
}): UnmatchableToolName[];
|
|
242
264
|
/** {@link findUnmatchableToolNames} 的成句形——三条腿的文案同源(只有前缀/出口不同)。 */
|
|
243
265
|
export declare function formatUnmatchableToolNames(source: string, bad: readonly UnmatchableToolName[]): string;
|
|
244
266
|
/**
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable } from "@sema-agent/core";
|
|
5
|
+
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, parseRuleText } from "@sema-agent/core";
|
|
6
6
|
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/settings-schema";
|
|
7
7
|
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
|
|
8
8
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
@@ -13,6 +13,7 @@ import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config
|
|
|
13
13
|
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
14
14
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
15
15
|
import { readFaceForPosture } from "./read-face-posture.js";
|
|
16
|
+
import {} from "./posture-source.js";
|
|
16
17
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
17
18
|
export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
18
19
|
function csv(name) {
|
|
@@ -321,6 +322,15 @@ export function configKnobTable() {
|
|
|
321
322
|
function registerKnob(rec) {
|
|
322
323
|
CONFIG_KNOBS.set(rec.name, rec);
|
|
323
324
|
}
|
|
325
|
+
function postureSourceOfKnob(name) {
|
|
326
|
+
const source = CONFIG_KNOBS.get(name)?.source;
|
|
327
|
+
return source === "env" ? "env" : source === "posture" ? "posture" : "engine-default";
|
|
328
|
+
}
|
|
329
|
+
function postureSourceOfNumEnv(raw, singleUserTurnkey) {
|
|
330
|
+
if (raw !== undefined && raw !== "")
|
|
331
|
+
return "env";
|
|
332
|
+
return singleUserTurnkey ? "posture" : "engine-default";
|
|
333
|
+
}
|
|
324
334
|
const BOOL_TRUE_WORDS = new Set(["true", "1", "yes", "on"]);
|
|
325
335
|
const BOOL_FALSE_WORDS = new Set(["false", "0", "no", "off"]);
|
|
326
336
|
function parseBoolWord(name, raw) {
|
|
@@ -918,49 +928,48 @@ function ccRuleFormGuidance(name) {
|
|
|
918
928
|
`to decide per COMMAND use \`runtime.commandPolicy\` ({command, decision} — bare argv[0] names); ` +
|
|
919
929
|
`to put every shell call behind an approval use MANUAL_MODE_SHELL_GATE=always`);
|
|
920
930
|
}
|
|
921
|
-
|
|
922
|
-
const
|
|
923
|
-
for (const name of names) {
|
|
924
|
-
const guidance = ccRuleFormGuidance(name);
|
|
925
|
-
if (guidance !== undefined)
|
|
926
|
-
out.push({ name, guidance });
|
|
927
|
-
}
|
|
928
|
-
return out;
|
|
929
|
-
}
|
|
930
|
-
function uncompilableRuleGuidance(name) {
|
|
931
|
-
const issues = validatePermissionRules([{ rule: name, behavior: "deny" }]);
|
|
931
|
+
function uncompilableRuleGuidance(name, behavior, pathBases) {
|
|
932
|
+
const issues = validatePermissionRules([{ rule: name, behavior }], pathBases !== undefined ? { pathBases } : undefined);
|
|
932
933
|
const issue = issues[0];
|
|
933
934
|
if (issue === undefined)
|
|
934
935
|
return undefined;
|
|
936
|
+
const ccGuidance = issue.code === "unsupported.bash_prefix" || issue.code === "unsupported.file_glob" ? ccRuleFormGuidance(name) : undefined;
|
|
937
|
+
if (ccGuidance !== undefined)
|
|
938
|
+
return ccGuidance;
|
|
935
939
|
return (`${issue.code} — core's rule grammar cannot compile this name (${issue.message}), so a table keyed by it matches nothing. ` +
|
|
936
940
|
`Tool names carry no backslashes and are far shorter than the 512-character rule cap; check the spelling, and use ` +
|
|
937
941
|
`mcp__<server>__* / a2a__<peer>__* for "every tool on that peer"`);
|
|
938
942
|
}
|
|
939
|
-
export function findUnmatchableToolNames(names) {
|
|
943
|
+
export function findUnmatchableToolNames(names, opts) {
|
|
940
944
|
const out = [];
|
|
941
945
|
for (const name of names) {
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
+
if (opts.consumer === "tool-name-table") {
|
|
947
|
+
const ccGuidance = ccRuleFormGuidance(name);
|
|
948
|
+
if (ccGuidance !== undefined) {
|
|
949
|
+
out.push({ name, guidance: ccGuidance });
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
946
952
|
}
|
|
953
|
+
if (opts.behavior === "allow")
|
|
954
|
+
continue;
|
|
947
955
|
const retired = RETIRED_TOOL_NAMES.get(name);
|
|
948
956
|
if (retired !== undefined) {
|
|
949
957
|
out.push({ name, guidance: retired });
|
|
950
958
|
continue;
|
|
951
959
|
}
|
|
952
|
-
const uncompilable = uncompilableRuleGuidance(name);
|
|
960
|
+
const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
|
|
961
|
+
const subject = opts.consumer === "tool-name-table" ? name : (() => { const parsed = parseRuleText(name, opts.behavior); return "rule" in parsed ? parsed.rule.tool : name; })();
|
|
953
962
|
if (uncompilable !== undefined) {
|
|
954
963
|
out.push({ name, guidance: uncompilable });
|
|
955
964
|
continue;
|
|
956
965
|
}
|
|
957
|
-
const ns = protocolOf(
|
|
966
|
+
const ns = protocolOf(subject);
|
|
958
967
|
if (ns === undefined) {
|
|
959
|
-
if (
|
|
968
|
+
if (subject.includes("__"))
|
|
960
969
|
out.push({ name, guidance: `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentToolName(name)}`).join(" or ")}` });
|
|
961
970
|
continue;
|
|
962
971
|
}
|
|
963
|
-
if (ns.parse(
|
|
972
|
+
if (ns.parse(subject) === undefined) {
|
|
964
973
|
out.push({ name, guidance: `incomplete ${ns.id} name — the live form is ${ns.prefix}<server>__<tool>, or ${ns.prefix}<server>__* to cover every tool on that server` });
|
|
965
974
|
}
|
|
966
975
|
}
|
|
@@ -974,7 +983,7 @@ const MAX_TIMER_DELAY_MS = 2_147_483_647;
|
|
|
974
983
|
function streamApprovalConfig(gates) {
|
|
975
984
|
const cfg = {
|
|
976
985
|
enabled: boolEnv("STREAM_APPROVAL_ENABLED", true),
|
|
977
|
-
windowMs: numEnv("STREAM_ASK_WINDOW_MS", "300000"),
|
|
986
|
+
windowMs: numEnv("STREAM_ASK_WINDOW_MS", gates.windowMsSource === "posture" ? "86400000" : "300000"),
|
|
978
987
|
replayMax: numEnv("STREAM_APPROVAL_REPLAY_MAX", "50"),
|
|
979
988
|
admitMaxPerTask: numEnv("STREAM_APPROVAL_ADMIT_MAX_PER_TASK", "32"),
|
|
980
989
|
admitMaxPerOwner: numEnv("STREAM_APPROVAL_ADMIT_MAX_PER_OWNER", "256"),
|
|
@@ -990,6 +999,10 @@ function streamApprovalConfig(gates) {
|
|
|
990
999
|
if (cfg.windowMs > MAX_TIMER_DELAY_MS) {
|
|
991
1000
|
throw new Error(`env STREAM_ASK_WINDOW_MS=${cfg.windowMs} exceeds the Node timer ceiling ${MAX_TIMER_DELAY_MS} (2^31-1 ms ≈ 24.8 days) — setTimeout SILENTLY collapses a larger delay to 1ms, so the window you meant to lengthen would expire immediately and every gated ask would take the unattended route on the next tick. Pick a window at or below the ceiling (and remember STREAM_ASK_WINDOW_MS + STREAM_APPROVAL_ADHOC_GRACE_MS must stay strictly below STREAM_APPROVAL_ORPHAN_TTL_MS).`);
|
|
992
1001
|
}
|
|
1002
|
+
if (gates.windowMsSource === "posture" && cfg.windowMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
|
|
1003
|
+
gates.onPostureYield?.({ windowMs: cfg.windowMs, adhocGraceMs: cfg.adhocGraceMs, orphanTtlMs: cfg.orphanTtlMs });
|
|
1004
|
+
cfg.windowMs = 300_000;
|
|
1005
|
+
}
|
|
993
1006
|
if (cfg.windowMs + cfg.adhocGraceMs >= cfg.orphanTtlMs) {
|
|
994
1007
|
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)`);
|
|
995
1008
|
}
|
|
@@ -1014,14 +1027,25 @@ function unattendedApprovalPolicyEnv() {
|
|
|
1014
1027
|
`Refusing to boot rather than treating a misspelled value as "unset": that runs the deployment on a policy the operator did not choose, with no trace anywhere`);
|
|
1015
1028
|
}
|
|
1016
1029
|
function parseApprovalDomain(ctx) {
|
|
1017
|
-
const { postureOn } = ctx;
|
|
1030
|
+
const { postureOn, singleUserTurnkey, infraReady } = ctx;
|
|
1018
1031
|
const unattendedPolicy = unattendedApprovalPolicyEnv();
|
|
1032
|
+
const streamAskWindowMsSource = postureSourceOfNumEnv(process.env.STREAM_ASK_WINDOW_MS, singleUserTurnkey);
|
|
1019
1033
|
const toolApprovalEnabled = postureOn("TOOL_APPROVAL_ENABLED");
|
|
1034
|
+
let streamAskWindowMsEffectiveSource = streamAskWindowMsSource;
|
|
1035
|
+
const streamApproval = streamApprovalConfig({
|
|
1036
|
+
unattendedPolicy,
|
|
1037
|
+
toolApprovalEnabled,
|
|
1038
|
+
windowMsSource: streamAskWindowMsSource,
|
|
1039
|
+
onPostureYield: (facts) => {
|
|
1040
|
+
streamAskWindowMsEffectiveSource = "engine-default";
|
|
1041
|
+
process.stderr.write(`[config] stream_ask_window_posture_yielded: the single-user posture default STREAM_ASK_WINDOW_MS=${facts.windowMs} would violate the explicit STREAM_APPROVAL_ORPHAN_TTL_MS=${facts.orphanTtlMs} (window + ADHOC_GRACE ${facts.adhocGraceMs} must be strictly less) — falling back to 300000; set STREAM_ASK_WINDOW_MS explicitly to choose\n`);
|
|
1042
|
+
},
|
|
1043
|
+
});
|
|
1020
1044
|
const directApprovalDoor = boolEnv("DIRECT_APPROVAL_DOOR", false);
|
|
1021
1045
|
const dgHmacKeys = parseApprovalHmacKeys(process.env.APPROVAL_HMAC_KEYS, announceKeySetRejection("APPROVAL_HMAC_KEYS"));
|
|
1022
|
-
const durableApproval =
|
|
1046
|
+
const durableApproval = postureOn("DURABLE_APPROVAL", infraReady);
|
|
1023
1047
|
const rejectUnmatchableToolNames = (envName, names) => {
|
|
1024
|
-
const bad = findUnmatchableToolNames(names);
|
|
1048
|
+
const bad = findUnmatchableToolNames(names, { consumer: "tool-name-table", behavior: "deny" });
|
|
1025
1049
|
if (bad.length > 0)
|
|
1026
1050
|
throw new Error(formatUnmatchableToolNames(envName, bad));
|
|
1027
1051
|
return names;
|
|
@@ -1092,7 +1116,9 @@ function parseApprovalDomain(ctx) {
|
|
|
1092
1116
|
permissionRulesEnabled: boolEnv("PERMISSION_RULES_ENABLED", true),
|
|
1093
1117
|
permissionRulesEnabledExplicit: parseBoolWord("PERMISSION_RULES_ENABLED", process.env["PERMISSION_RULES_ENABLED"]) !== undefined,
|
|
1094
1118
|
streamAskWindowMarginMs: numEnv("STREAM_ASK_WINDOW_MARGIN_MS", "10000"),
|
|
1095
|
-
streamApproval
|
|
1119
|
+
streamApproval,
|
|
1120
|
+
streamAskWindowMsSource: streamAskWindowMsEffectiveSource,
|
|
1121
|
+
durableApprovalSource: postureSourceOfKnob("DURABLE_APPROVAL"),
|
|
1096
1122
|
unattendedApprovalPolicy: unattendedPolicy,
|
|
1097
1123
|
approvalTimeoutSec: numEnv("APPROVAL_TIMEOUT_SEC", "0"),
|
|
1098
1124
|
};
|
|
@@ -1659,6 +1685,7 @@ function parseOrchestrationDomain(ctx) {
|
|
|
1659
1685
|
? { sandboxPkgSource: pkgSourceEnv() }
|
|
1660
1686
|
: {}),
|
|
1661
1687
|
sessionAutoTitle: boolEnv("SESSION_AUTO_TITLE", true),
|
|
1688
|
+
sessionAutoTitleSource: postureSourceOfKnob("SESSION_AUTO_TITLE"),
|
|
1662
1689
|
selectEnvironmentTool: boolEnv("SELECT_ENVIRONMENT_TOOL", true),
|
|
1663
1690
|
envFactsEnabled: boolEnv("SANDBOX_ENV_FACTS", true),
|
|
1664
1691
|
toolDeferLongtail: boolEnv("TOOL_DEFER_LONGTAIL", false),
|
|
@@ -1885,7 +1912,7 @@ const MODEL_PLANE_GROUP_KEYS = [
|
|
|
1885
1912
|
];
|
|
1886
1913
|
const APPROVAL_GROUP_KEYS = [
|
|
1887
1914
|
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
1888
|
-
"approvalHmacKeys", "durableApproval", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
1915
|
+
"approvalHmacKeys", "durableApproval", "durableApprovalSource", "streamAskWindowMsSource", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
1889
1916
|
"resourceSuspendTtlSec", "resourceSuspendTotalTokens", "resourceSuspendTotalBudgetUsd", "resourceSuspendMaxSlices", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "permissionRulesEnabled", "permissionRulesEnabledExplicit", "streamAskWindowMarginMs", "streamApproval", "unattendedApprovalPolicy", "mcpElicitation",
|
|
1890
1917
|
"sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "crossSessionInbound", "crossSessionDialogExpiry",
|
|
1891
1918
|
];
|
|
@@ -1907,7 +1934,7 @@ const ORCHESTRATION_GROUP_KEYS = [
|
|
|
1907
1934
|
"workflowOrphanGraceMs", "workflowJournalRetentionMs", "workflowRunRetentionMs", "usageWindows", "workflowAgentsReadOnly",
|
|
1908
1935
|
"workflowSizeGuideline", "backgroundAgentRetentionMs", "backgroundAgentStaleRunningMs",
|
|
1909
1936
|
"backgroundAgentParkClaimStaleMs", "delegationEntryCaps", "rosterRetentionMs", "leaderRunStaleMs", "scratchpadSweepTtlMs", "sandboxPkgSource",
|
|
1910
|
-
"sessionAutoTitle", "selectEnvironmentTool", "envFactsEnabled", "toolDeferLongtail", "lspEnabled", "lspHostEnabled",
|
|
1937
|
+
"sessionAutoTitle", "sessionAutoTitleSource", "selectEnvironmentTool", "envFactsEnabled", "toolDeferLongtail", "lspEnabled", "lspHostEnabled",
|
|
1911
1938
|
"imageBakes", "readFace", "readFaceSource", "readDenyPatterns", "readDenyBuiltinTiers", "readDenyBuiltinExclude",
|
|
1912
1939
|
];
|
|
1913
1940
|
const LIMITS_HTTP_GROUP_KEYS = [
|
|
@@ -1968,12 +1995,13 @@ export function loadConfig() {
|
|
|
1968
1995
|
const postureOn = (name, infraReady = true) => {
|
|
1969
1996
|
const explicit = parseBoolWord(name, process.env[name]);
|
|
1970
1997
|
const value = explicit ?? (singleUserTurnkey && infraReady);
|
|
1971
|
-
registerKnob({ name, polarity: "posture", value, source: explicit
|
|
1998
|
+
registerKnob({ name, polarity: "posture", value, source: explicit !== undefined ? "env" : singleUserTurnkey ? "posture" : "default" });
|
|
1972
1999
|
return value;
|
|
1973
2000
|
};
|
|
1974
2001
|
const store = parseStoreDomain({ requirePrincipal });
|
|
1975
2002
|
const modelPlane = parseModelDomain();
|
|
1976
|
-
const
|
|
2003
|
+
const durableStoreReady = store.dbBackend !== "memory";
|
|
2004
|
+
const approval = parseApprovalDomain({ postureOn, singleUserTurnkey, infraReady: durableStoreReady });
|
|
1977
2005
|
const memory = parseMemoryDomain({ requirePrincipal });
|
|
1978
2006
|
const auth = parseAuthDomain({ requirePrincipal });
|
|
1979
2007
|
const orchestration = parseOrchestrationDomain({ postureOn, singleUserTurnkey });
|
|
@@ -55,6 +55,14 @@ export interface PathAdjudication {
|
|
|
55
55
|
readonly env: ExecutionEnv;
|
|
56
56
|
/** 相对路径基准(core 的 `rootPath`)。缺席 ⇒ 相对形在真身那层判不了,补词法臂(见下)。 */
|
|
57
57
|
readonly cwd?: string;
|
|
58
|
+
/** 🔴 S-177:**真任务根**(权限规则模式的基)。与上面那个 `cwd` 分开:后者在 host 腿的无 session-cwd
|
|
59
|
+
* 形上是一个永不创建的哨兵目录 —— 对写门 fail-safe,对规则 fail-open。理由逐字见
|
|
60
|
+
* `task-settings.ts` 的 `FsWriteGateWiring.taskRoot`。缺席 ⇒ 需要基的规则拼法被 core 整表拒(fail-loud)。 */
|
|
61
|
+
readonly taskRoot?: string;
|
|
62
|
+
/** 🔴 S-177 / codex r2:**执行环境的 home**(`~/…` 形规则的基)。只在引擎与执行同机时在场 ——
|
|
63
|
+
* 远端车道上 server 的 home 是另一个用户的目录,回落它 = 规则静默失效。逐字见
|
|
64
|
+
* `task-settings.ts` 的 `FsWriteGateWiring.taskHome`。 */
|
|
65
|
+
readonly taskHome?: string;
|
|
58
66
|
}
|
|
59
67
|
/** [ref]([ref] 案二):durable 部署上的 AskUserQuestion 门的活体面探针(QuestionCoordinator 的切面)。 */
|
|
60
68
|
export interface LiveQuestionFace {
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [ref] 件二+件三 —— **装配自证**域:启动期静态自检(拒启判据)与 operator 诊断读面
|
|
3
|
+
* (`GET /v1/diagnostics/wiring`)。
|
|
4
|
+
*
|
|
5
|
+
* 🔴 两件为什么同住一个模块:它们回答的是同一个问题(「这台 worker 到底接了什么线」),只是一个在 boot
|
|
6
|
+
* 用它拒绝启动、一个在运行期把它读给 operator。分成两处 = 拒启用的判据与诊断报的读数会各自演化,而
|
|
7
|
+
* 「拒启说没矛盾、诊断页显示矛盾」正是本设计要消灭的那类装配谎言。故 boot 半场是本文件导出的**纯函数**
|
|
8
|
+
* (可单测、无 I/O),路由半场只是它的读口。
|
|
9
|
+
*
|
|
10
|
+
* 分层:本模块不值 import `server.ts`(那条边会闭合运行时装载环),对它只有 `import type`。
|
|
11
|
+
*/
|
|
12
|
+
import { type PostureKnobReading, type PostureSource } from "../../posture-source.js";
|
|
1
13
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
14
|
import type { WiringManifest } from "@sema-agent/core";
|
|
3
15
|
import { type StreamApprovalGate, type StreamApprovalGateInput } from "../../tool-approval.js";
|
|
@@ -7,10 +19,17 @@ import type { RouteCtx } from "../route-ctx.js";
|
|
|
7
19
|
export type StreamApprovalGateReading = "active" | Extract<StreamApprovalGate, {
|
|
8
20
|
active: false;
|
|
9
21
|
}>["reason"];
|
|
10
|
-
/** server 自己的装配谓词读数(core 的静态半场之外的那一半:本仓在这台 worker 上做出的判断)。
|
|
22
|
+
/** server 自己的装配谓词读数(core 的静态半场之外的那一半:本仓在这台 worker 上做出的判断)。
|
|
23
|
+
*
|
|
24
|
+
* 🔴 S-178:三个 **posture 家族**旋钮改报 `{value, source, note}`(与 S-167 的 `readFace` 同一张四词表、
|
|
25
|
+
* 同一只 note 函数)。裸布尔答不出裁定的补偿项要求的那句话 —— 单机形升级后 `durableApproval` 缺席即
|
|
26
|
+
* ON、审批窗缺席即 24h,运维必须在一个读面上看得见「这是 posture 派生的」以及「怎么钉回」。
|
|
27
|
+
* `streamApproval` 那一格是**门读数**(另一根轴:这条腿现在通不通),不是旋钮,故形不变。 */
|
|
11
28
|
export interface ServerWiringGates {
|
|
12
29
|
streamApproval: StreamApprovalGateReading;
|
|
13
|
-
durableApproval: boolean
|
|
30
|
+
durableApproval: PostureKnobReading<boolean>;
|
|
31
|
+
streamAskWindowMs: PostureKnobReading<number>;
|
|
32
|
+
sessionAutoTitle: PostureKnobReading<boolean>;
|
|
14
33
|
checkpointStore: boolean;
|
|
15
34
|
}
|
|
16
35
|
/** `buildServerWiringGates` 的入参 = 三个谓词各自的**真输入**(不接受已算好的布尔:那样这里读到的就
|
|
@@ -23,6 +42,12 @@ export interface ServerWiringGateInput {
|
|
|
23
42
|
/** 在场性即事实;本面不取实例,故 `unknown` 足够(取窄型会把整棵 store 类型树拖进诊断域)。 */
|
|
24
43
|
checkpointStore: unknown;
|
|
25
44
|
durableApproval: boolean;
|
|
45
|
+
/** S-178:三个 posture 旋钮的**来源**,取自 config 上与值同处写的那一格(不在这里重判)。 */
|
|
46
|
+
durableApprovalSource: PostureSource;
|
|
47
|
+
streamAskWindowMs: number;
|
|
48
|
+
streamAskWindowMsSource: PostureSource;
|
|
49
|
+
sessionAutoTitle: boolean;
|
|
50
|
+
sessionAutoTitleSource: PostureSource;
|
|
26
51
|
}
|
|
27
52
|
/** 纯数据产物(`build*`):三个门的当前读数。`streamApproval` 走**单一谓词**
|
|
28
53
|
* `resolveStreamApprovalGate` —— 与 `/v1/capabilities` 的 `streamApproval` 位、与回决口的 501 门同一个
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildPostureKnobReading } from "../../posture-source.js";
|
|
1
2
|
import { buildReadFaceWiring } from "../../read-face-posture.js";
|
|
2
3
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
3
4
|
import { sendJson, sendError } from "../send.js";
|
|
@@ -11,7 +12,21 @@ export function buildServerWiringGates(input) {
|
|
|
11
12
|
});
|
|
12
13
|
return {
|
|
13
14
|
streamApproval: gate.active ? "active" : gate.reason,
|
|
14
|
-
durableApproval: input.durableApproval,
|
|
15
|
+
durableApproval: buildPostureKnobReading(input.durableApproval, {
|
|
16
|
+
knob: "DURABLE_APPROVAL",
|
|
17
|
+
source: input.durableApprovalSource,
|
|
18
|
+
postureMeaning: "derived from the single-user turnkey posture (REQUIRE_PRINCIPAL is not set) composed with a durable store being present: park/resume and the /v1/approvals routes are on by default — set DURABLE_APPROVAL=false to pin them off",
|
|
19
|
+
}),
|
|
20
|
+
streamAskWindowMs: buildPostureKnobReading(input.streamAskWindowMs, {
|
|
21
|
+
knob: "STREAM_ASK_WINDOW_MS",
|
|
22
|
+
source: input.streamAskWindowMsSource,
|
|
23
|
+
postureMeaning: "derived from the single-user turnkey posture (REQUIRE_PRINCIPAL is not set): an approval card waits 24h instead of 5 minutes, so stepping away does not settle the run unattended — set STREAM_ASK_WINDOW_MS to pin your own window",
|
|
24
|
+
}),
|
|
25
|
+
sessionAutoTitle: buildPostureKnobReading(input.sessionAutoTitle, {
|
|
26
|
+
knob: "SESSION_AUTO_TITLE",
|
|
27
|
+
source: input.sessionAutoTitleSource,
|
|
28
|
+
postureMeaning: "this knob has no posture arm — its default is the same on every deployment shape",
|
|
29
|
+
}),
|
|
15
30
|
checkpointStore: input.checkpointStore !== undefined,
|
|
16
31
|
};
|
|
17
32
|
}
|
|
@@ -100,6 +115,11 @@ async function handleDiagnosticsBody(req, res, url, ctx, miss) {
|
|
|
100
115
|
backend: deps.backend,
|
|
101
116
|
checkpointStore: deps.checkpointStore,
|
|
102
117
|
durableApproval: deps.config.durableApproval === true,
|
|
118
|
+
durableApprovalSource: deps.config.durableApprovalSource,
|
|
119
|
+
streamAskWindowMs: deps.config.streamApproval?.windowMs ?? 0,
|
|
120
|
+
streamAskWindowMsSource: deps.config.streamAskWindowMsSource,
|
|
121
|
+
sessionAutoTitle: deps.config.sessionAutoTitle === true,
|
|
122
|
+
sessionAutoTitleSource: deps.config.sessionAutoTitleSource,
|
|
103
123
|
}),
|
|
104
124
|
});
|
|
105
125
|
return;
|