@sema-agent/server 7.119.0 → 7.120.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/MIGRATION.md +54 -0
- package/USAGE.md +5 -3
- package/dist/boot/config-center.js +5 -5
- package/dist/boot/execution-env.js +3 -2
- package/dist/boot/fatal-exit.d.ts +7 -1
- package/dist/boot/fatal-exit.js +2 -1
- package/dist/boot/runner-deps.d.ts +4 -3
- package/dist/boot/runner-deps.js +7 -3
- package/dist/boot/stage-01-config.js +2 -0
- package/dist/capabilities/builtin-tools.d.ts +31 -8
- package/dist/capabilities/builtin-tools.js +22 -1
- package/dist/capabilities/scenarios.d.ts +8 -6
- package/dist/capabilities/scenarios.js +3 -3
- package/dist/capabilities/send-user-file-tool.d.ts +6 -3
- package/dist/capabilities/send-user-file-tool.js +1 -1
- package/dist/config-catalog.js +2 -2
- package/dist/config-lkg.d.ts +33 -5
- package/dist/config-lkg.js +55 -11
- package/dist/config-types.d.ts +8 -1
- package/dist/config.js +9 -9
- package/dist/deployment-governance.d.ts +3 -1
- package/dist/deployment-governance.js +3 -1
- package/dist/device-store.d.ts +7 -0
- package/dist/device-store.js +4 -0
- package/dist/device-ws-hub.js +17 -9
- package/dist/device-ws-protocol.d.ts +15 -0
- package/dist/http/resume-legs.js +4 -0
- package/dist/http/routes/capabilities.js +1 -0
- package/dist/http/routes/devices.js +3 -3
- package/dist/http/routes/rules.js +13 -6
- package/dist/list-entry-display.d.ts +14 -0
- package/dist/list-entry-display.js +14 -0
- package/dist/namespaced-rule-name.js +2 -6
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/operator-ask.d.ts +1 -1
- package/dist/plugins/one-time-migrations.d.ts +8 -0
- package/dist/plugins/one-time-migrations.js +72 -3
- package/dist/plugins/permission-rule-store-file.js +2 -6
- package/dist/plugins/permission-rule-store-sql.d.ts +64 -6
- package/dist/plugins/permission-rule-store-sql.js +166 -59
- package/dist/plugins/remote-env-device.d.ts +12 -1
- package/dist/plugins/remote-env-device.js +13 -5
- package/dist/plugins/scheduler-support.d.ts +8 -4
- package/dist/plugins/scheduler-support.js +5 -5
- package/dist/rule-entry-list.d.ts +0 -1
- package/dist/rule-entry-list.js +4 -9
- package/dist/rules-consent.d.ts +12 -3
- package/dist/rules-consent.js +5 -4
- package/dist/run-local.js +2 -0
- package/dist/sema-config-dir.d.ts +19 -0
- package/dist/sema-config-dir.js +33 -0
- package/dist/session-policy-wire.js +5 -4
- package/dist/spec-fields.d.ts +5 -4
- package/dist/task-settings.d.ts +3 -3
- package/dist/trace/engine-notice-wire.d.ts +6 -4
- package/dist/trace/engine-notice-wire.js +16 -9
- package/dist/trace/self-minted-wire.d.ts +14 -0
- package/dist/trace/self-minted-wire.js +2 -0
- package/docs/DEVICE-EXECUTOR-PROTOCOL.md +56 -14
- package/package.json +2 -2
package/MIGRATION.md
CHANGED
|
@@ -7,6 +7,60 @@
|
|
|
7
7
|
> ⚠️ 完整清单在仓库根 `CHANGELOG.md`——它**不随 npm tarball 出包**(本文件随包)。看完整迁移窗的
|
|
8
8
|
> 权威姿势是源码 tag diff:`git diff v<旧>..v<新>`(每版都推 `v<版本>` tag);npm 包页也镜像 CHANGELOG。
|
|
9
9
|
|
|
10
|
+
## 7.120.0 —— 引擎 7.46.0:子代工具清单回到开跑前拒退役拼法、管到内建工具 · 配置根搬家后同护 · 回显条目一种渲染 · 路径规则锚在装入处
|
|
11
|
+
|
|
12
|
+
**升级前核查(一句话)**:设了 `SEMA_CONFIG_DIR` 的部署确认那个目录能建出来、不是空串;`CONFIG_LKG_PATH` 不是空串;
|
|
13
|
+
按 `agents[].denyTools` 拼错只告的调用方改正拼写。停驻中的委派子代不必为本版排空(第 5 条)。
|
|
14
|
+
|
|
15
|
+
1. **勘误(7.119.0 节,已发节不改字节)**:7.119.0 节说 `/health` 多一键 `sessionBackend` —— 「多一键」错:`/health.sessionBackend` 是既有键(≤7.117.0 已在),
|
|
16
|
+
7.119.0 只把它与 `listening` 日志的读法收成一只、让 sema-up 的冒烟改读这一位。消费方按「已消费 / 不变」读,不要删读点。
|
|
17
|
+
2. **子代 `agents[].denyTools` 的退役拼法(`bash`)与大小写孪生(`BASH`)回到开跑前拒**(`config.agent.unknown_tool`,句子点名现名 `Bash`;7.119.0 上只告不拒、子代照拿 `Bash`):
|
|
18
|
+
本服务的委派工具现在声明它逐次注入的工具名册,引擎列得出子代工具池。无人挂的名照旧只告不拒(运维日志 `runner_config_advisory`)。
|
|
19
|
+
`denyTools` / `allowTools` 现在管子代手里的**全部**工具 —— 委派池与内建 hands(`Read` / `Edit` / `Write` / `NotebookEdit` / `Bash` / `Grep` / `Glob` …):
|
|
20
|
+
`denyTools: ["Write"]` ⇒ 子代没有 `Write`;`allowTools` 只列调用方工具 ⇒ 子代一只内建工具都没有(要什么点名什么);内建只读子代(Explore / Plan)没有 `Edit` / `Write` / `NotebookEdit`。
|
|
21
|
+
hands 之外的引擎工具(`Monitor` 等)仍不受这两张表管;要按路径 / 命令拦而保留工具,写调用方权限策略的 deny 规则。
|
|
22
|
+
3. **配置根搬家(`SEMA_CONFIG_DIR`)后,根下的配置文件与家目录下同护**:根下的 `settings.json` / `settings.local.json` / `engine-data/.env` / `config-lkg-*.json`
|
|
23
|
+
模型读不到(家目录 `~/.sema/` 下同名文件照旧读不到,两处都拒),写进根下要问一次。起服时建出那个目录(不在就建,权限 `0700`);建不出来或不是目录 ⇒
|
|
24
|
+
引擎拒启,拒句带码 `config.agent_config_dir_invalid`(建不出来时本服务先打一行 `sema_config_dir_unprovisioned`,点名旋钮与原因)。
|
|
25
|
+
**`SEMA_CONFIG_DIR=`(空串)**:≤7.119.x 空串 = 进程工作目录(LKG / skill 缓存 / scheduler 店落在那里);7.120.0 起空串**拒启** —— 去掉变量用 `~/.sema`,
|
|
26
|
+
或写出要的目录(要保持旧位置就写工作目录的路径)。
|
|
27
|
+
LKG 覆写到配置根外(`CONFIG_LKG_PATH`)⇒ 派生读拒行:写下的路径与经符号链接目录后真正落的路径各一行(拒句与内建行同一句)。行的射程按路径段串算 ——
|
|
28
|
+
去掉开头的 `/`、不锚到根:这串段在任意深度出现即拒、大小写不敏感(`CONFIG_LKG_PATH=/app/config-lkg.json` ⇒ 用户仓里的 `…/app/config-lkg.json`、
|
|
29
|
+
`…/APP/Config-LKG.json` 同样读不到,Grep 的结果里也不出现);引擎提供锚定单文件的读拒行之前如此,要少挡就把覆写路径写得更具体。`CONFIG_LKG_PATH=`(空串)拒启。
|
|
30
|
+
拒启行的形:带串码的异常印成 `<name> [<code>]: <message>`(按码匹配,不按句子)。
|
|
31
|
+
`AGENT_DATA_DIR` 搬到配置根之外的部署:引擎数据根下的 `.env` 不在这条规则的射程里 —— 用部署自己的读拒行(`READ_DENY_PATTERNS`)守它。
|
|
32
|
+
4. **回显条目:人读句一种渲染,通告里的规则文本是原值**:拒句、起服拒启行里的工具名 / 规则条目一律是引擎的渲染 —— JSON 串形(带引号),看不见 / 印成空白的字符
|
|
33
|
+
写成 `\uXXXX`,超长截断;「can never take effect」拒句里的名字与「pre-prefix short form」指引里的建议名从不带引号变成带引号。
|
|
34
|
+
通告里的规则文本(`permission.allowed_tool_not_granted` 的 `detail.entries[].entry`、`permission.rule_row_dropped` 的 `detail.rows[].rule`)是条目的**脱敏原值**:
|
|
35
|
+
不加引号、不转义、本服务不另截断;认出的凭据换成脱敏记号,双向控制符 / 零宽字符等格式字符剥掉,C0 控制符保留(JSON 里写成转义)。7.119.0 的 `entry` 剥掉了
|
|
36
|
+
控制 / 双向符、超过 160 字截断并标原长 —— 把 `entry` 直接打到终端的消费方先自己中和控制符,或显示通告文本 `message`(那是渲染好的句子)。
|
|
37
|
+
5. **停驻中的委派子代:本服务的续跑腿不触发引擎的「装入记录缺席」拒,升级前不必排空**:引擎 7.46.0 对宿主**重供**的父约束链查「每条规则装在哪条腿」的记录,
|
|
38
|
+
缺了且规则依赖装入腿(相对形、`/x` 形、`~/` 形)⇒ 拒 `resume.parent_constraint_mismatch`(`detail.reason: "installation_record_absent"`)。本服务 `/decide` 一只停驻子代时
|
|
39
|
+
重供的链只有部署段(审批名单、命令策略、敏感路径守卫集、誊本门、问答门),不带随腿锚定的路径规则,请求的 `settings.permissions` 规则不在链里 ⇒ 没有要拒的条目。
|
|
40
|
+
实测:7.119.0 停驻(父请求带 deny `Edit(src/**)` / ask `Edit(/src/**)` / deny `Read(~/secret/**)`)→ 7.120.0 `/decide` 三形都照常续跑完。
|
|
41
|
+
自建续跑腿、自己重供带这几形路径规则的父约束链的嵌入方不在此列(照引擎说明:让旧版 worker 做完,或过期后重提)。
|
|
42
|
+
6. **请求规则的相对 / `/x` 形路径模式在沙箱 / 设备车道(与还没登记工作目录的 host 腿)上照收**(修前 422):引擎把规则锚在装入它的那条腿的真根;
|
|
43
|
+
相对模式**不随 `cd` 走**(`Read(a/b/**)` 在 `cd sub` 之后不再够到 `sub/a/b/…`;要任意层写 `**/a/b/**`)。
|
|
44
|
+
7. **新通告码 `permission.rule_row_dropped`(上会话流)**:持久规则里有项目行的 scope root 不是完整绝对路径 ⇒ 那几行不参与本 run 的检查;`detail` 点名前八行(`rows[].rule` 与第 4 条同一只写法 = 规则文本的脱敏原值)。
|
|
45
|
+
这种行是 7.119.0 及更早的本服务入口收下的(`/v1/rules` 写口与两条导入面按「root 非空」收 `project:myproj`,当时引擎也收)。本版:
|
|
46
|
+
- **入口按引擎同一只谓词拒**(project root 必须是完整绝对路径 `/…`、`X:\…`、`\\host\…`):`POST` / `DELETE /v1/rules` 与 cc-import 预览答 `400 request.body_shape`、
|
|
47
|
+
`GET /v1/rules?scope=` 答 `400 request.query_invalid`、local-import 预览把那一行判 `scope_unreadable`;修前写口答 `503 state.rule_write_failed`「再发一次」(重试永远不会好)、
|
|
48
|
+
撤销口答 200 而行留在店里、导入预览收下而兑付时被引擎拒。拒句从「with a non-empty root」改成「with an absolute root」。
|
|
49
|
+
- **SQL 后端(MySQL-protocol / PostgreSQL)起服时一次性删掉存量的这种行**(规则行与墓碑;一行一条运维 warn `permission_rule_row_removed`,带桶键、态(`kind`)、工具名与 root
|
|
50
|
+
(root 已脱敏),不带规则正文;启动汇总行 `one_time_migrations_applied` 的 `undurableScopeRuleRowsRemoved`)。只有 `kind: "rule"`(仍生效的那几行)的 warn 劝重写:
|
|
51
|
+
还要的规则按项目的完整路径重新写。`kind: "revoked-rule"`(早已撤销:它背后的每一次同意都被同桶的撤销记录删掉了)与 `kind: "tombstone"`(撤销记录本身)随之删掉,
|
|
52
|
+
**不要**重加 —— 撤销照旧成立。
|
|
53
|
+
- **读路径整桶拒的桶一行不动**(形不合、指纹不符、behavior 不认识、撤销记录的规则文本不规范、规则文本与匹配字段不一致 —— 与读规则时整桶拒的判据是同一只):
|
|
54
|
+
迁移窗内每次起服逐桶一条运维 warn `permission_rule_bucket_unreadable`(桶键 + 拒因,不带规则正文;汇总行 `undurableScopeRuleBucketsUnreadable` 计数),
|
|
55
|
+
直到**人工处置** —— 处置 = 修正或删除那一行,并按既有指纹算法(`buildRuleBucketChecksum`,算在规则 / 墓碑 / 隔离三列与观测向量上)重算该桶的 `checksum`。
|
|
56
|
+
这种桶在升级前后都整桶读不出;迁移不替它删行(从一只整桶拒的桶里删行再重算指纹,等于让原本被拒的规则上线)。
|
|
57
|
+
- **M11 回写与正常写同形**:被改动的桶里 `rules_json` / `tombstones_json` / `quarantined_json` 的旧行(写于 `behavior` 字段之前)都会补上显式 `behavior`
|
|
58
|
+
(缺席的本义就是 `allow`,补上的就是 `"allow"`,语义不变);`observed_vector_json` 不动;`checksum` 按回写后的字节重算。
|
|
59
|
+
- **local 后端删不动**:规则在引擎自己的文件店里,引擎的写面对这种 scope 一律拒 —— 那些行照旧每次 run 被丢并告知;撤销口同样删不动(本版的已知边界,候引擎给删除缝)。
|
|
60
|
+
8. **引擎 7.46.0 的 shell 读法**:喂给 shell 标准输入的脚本(`bash <<'EOF' …`、`curl … | sh`、`cat x.sh | bash`、`bash < x.sh`)按 `-c` 文本判 —— 运行时拼出的一律要人答
|
|
61
|
+
(`dangerous_delete`,`bypassPermissions` 不免;7.119.0 节第 1 条的已知边界由此收掉);读名单里列了写程序(`cp` / `mv` / `mkdir` / `rm` / `rmdir` / `tee` / `touch` / `flock`)的部署,
|
|
62
|
+
这些调用现在出卡;开放读面且没有 deny 行时,「说不清读哪」不再出强制卡;经链接 / 别名 / 大小写变体 / `cd` 后相对拼写够到受守路径的调用现在出卡或在闸上拒。
|
|
63
|
+
|
|
10
64
|
## 7.119.0 —— 场景指派悬空不再静默落 `default`:`DEFAULT_SCENARIO` 不在册 ⇒ 拒启 · 中心指派的场景名本部署没有 ⇒ 该主体的 run 503 · `APPROVAL_REQUIRE` 的「本会话放行」恢复生效
|
|
11
65
|
|
|
12
66
|
**升级前核查(一句话)**:把 `DEFAULT_SCENARIO`(缺省 `code`)与**中心里每个主体 / 组 / 全局的场景指派**逐名对本部署的在册场景
|
package/USAGE.md
CHANGED
|
@@ -1875,9 +1875,11 @@ tag 词表与每条的最坏后果见 `docs/FAIL-OPEN-CENSUS.md`。引擎装配
|
|
|
1875
1875
|
(core 给宿主定级的判别词;词表外原样透传并标 `classificationKnown:false`),指标 `runner_config_advisory_total`
|
|
1876
1876
|
按 `code` / `classification` / `level` 打标;其中 `shell-gate-off` 在本 session 显式声明 `permissionMode:
|
|
1877
1877
|
"bypassPermissions"` 时是调用方要的姿态,降为 **info** 并标 `expected`——只有没表态落到引擎默认 off 的无壳直连
|
|
1878
|
-
形才 warn。子代 `agents[].denyTools` / `allowTools`
|
|
1879
|
-
(`
|
|
1880
|
-
|
|
1878
|
+
形才 warn。子代 `agents[].denyTools` / `allowTools` 的**名字**也归引擎开跑前一只判、同落这一行:无人挂的名只告不拒;退役拼法 / 大小写孪生(`bash` / `BASH`)
|
|
1879
|
+
**开跑前拒**(`config.agent.unknown_tool`;委派工具声明了逐次注入工具的名册,引擎列得出子代工具池),名字按工具名原拼写 `Bash`。这两张表管子代手里的**全部**
|
|
1880
|
+
工具 —— 委派池与内建 hands(`Read` / `Edit` / `Write` / `NotebookEdit` / `Bash` / `Grep` / `Glob` …):`denyTools:["Write"]` ⇒ 子代没有 `Write`;`allowTools` 只列
|
|
1881
|
+
调用方工具 ⇒ 子代一只内建 hands 工具都没有(要什么就点名什么)。内建只读子代(Explore / Plan)没有 `Edit` / `Write` / `NotebookEdit`。hands 之外的引擎工具(`Monitor`
|
|
1882
|
+
等)仍不受这两张表管;要按路径 / 命令拦而保留工具,写调用方权限策略的 deny 规则。
|
|
1881
1883
|
|
|
1882
1884
|
🔴 **`shell-gate-off` 这条注的语义自 7.73.0(core 7.15.0)起收窄,文案由引擎改写**:`off` 档**不再**意味着
|
|
1883
1885
|
「这条会话的 shell 完全没有门」。**读边界**(内建读拒表 + 工作区容纳面)现在**每一档都判**,`shellGate` 只管
|
|
@@ -316,7 +316,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
316
316
|
};
|
|
317
317
|
let bootConfigPending;
|
|
318
318
|
const lkgEnabled = configProvider?.kind === "remote" && configLkgEnabled();
|
|
319
|
-
const lkgPathFor = (identity) =>
|
|
319
|
+
const lkgPathFor = (identity) => config.configLkgPath ?? defaultLkgPath(config, cc?.worker, identity);
|
|
320
320
|
const lkgSurvivesRestart = lkgEnabled && process.env.CONFIG_LKG_DURABLE === "true";
|
|
321
321
|
let lkgBooted = false;
|
|
322
322
|
let bootLkgCandidate;
|
|
@@ -335,10 +335,10 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
335
335
|
const lkgPath = lkgPathFor(identity);
|
|
336
336
|
try {
|
|
337
337
|
if (effectiveObj.skills && config.configCenter) {
|
|
338
|
-
await applyCenterSkills([], effectiveObj.skills, config.configCenter, logger, undefined, defaultSkillCacheDir());
|
|
338
|
+
await applyCenterSkills([], effectiveObj.skills, config.configCenter, logger, undefined, defaultSkillCacheDir(config));
|
|
339
339
|
}
|
|
340
340
|
const enabledSkills = (effectiveObj.skills?.skills ?? []).filter((m) => m.enabled !== false);
|
|
341
|
-
const cacheDir = defaultSkillCacheDir();
|
|
341
|
+
const cacheDir = defaultSkillCacheDir(config);
|
|
342
342
|
for (const m of enabledSkills) {
|
|
343
343
|
const hex = m.contentHash.replace(/^sha256:/, "").toLowerCase();
|
|
344
344
|
if (!SHA256_HEX_RE.test(hex))
|
|
@@ -375,7 +375,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
375
375
|
return null;
|
|
376
376
|
}
|
|
377
377
|
const lkgPath = lkgPathFor(identity);
|
|
378
|
-
const legacy = await migrateV1Lkg({ legacyPath:
|
|
378
|
+
const legacy = await migrateV1Lkg({ legacyPath: config.configLkgPath ?? legacyV1LkgPath(config, cc?.worker), binPath: lkgPath, worker: cc?.worker, identity });
|
|
379
379
|
switch (legacy.outcome) {
|
|
380
380
|
case "none":
|
|
381
381
|
break;
|
|
@@ -622,7 +622,7 @@ export async function createConfigCenterRuntime(ctx) {
|
|
|
622
622
|
},
|
|
623
623
|
async applyCenterCapabilities(skills) {
|
|
624
624
|
if (effective?.skills && config.configCenter) {
|
|
625
|
-
skills = await applyCenterSkills(skills, effective.skills, config.configCenter, logger, undefined, configLkgEnabled() ? defaultSkillCacheDir() : undefined);
|
|
625
|
+
skills = await applyCenterSkills(skills, effective.skills, config.configCenter, logger, undefined, configLkgEnabled() ? defaultSkillCacheDir(config) : undefined);
|
|
626
626
|
}
|
|
627
627
|
if (effective?.plugins !== undefined) {
|
|
628
628
|
const pluginOut = await applyCenterPlugins(skills, effective, {
|
|
@@ -205,9 +205,10 @@ export function createExecutionEnv(ctx) {
|
|
|
205
205
|
}
|
|
206
206
|
else if (config.remoteExec?.provider === "host") {
|
|
207
207
|
{
|
|
208
|
+
const schedulerStorePath = config.schedulerStorePath ?? defaultSchedulerStorePath(config);
|
|
208
209
|
const hostScheduler = facts.hostScheduler(config).available
|
|
209
210
|
? new FileSchedulerBackend({
|
|
210
|
-
|
|
211
|
+
storePath: schedulerStorePath,
|
|
211
212
|
...(config.schedulerSessionWakeup === false || config.schedulerSessionLifetime === false
|
|
212
213
|
? {
|
|
213
214
|
caps: {
|
|
@@ -219,7 +220,7 @@ export function createExecutionEnv(ctx) {
|
|
|
219
220
|
})
|
|
220
221
|
: undefined;
|
|
221
222
|
if (hostScheduler)
|
|
222
|
-
logger.info(`R7: self-wake scheduler enabled (host lane), store=${
|
|
223
|
+
logger.info(`R7: self-wake scheduler enabled (host lane), store=${schedulerStorePath}`);
|
|
223
224
|
const hostCfg = {
|
|
224
225
|
...homeDirSeat,
|
|
225
226
|
...(config.remoteExec.workspaceBase ? { workspaceBase: config.remoteExec.workspaceBase } : {}),
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* `err` 的 fatal 日志文本:标题过形状律一次。纯函数,零副作用。
|
|
3
|
+
*
|
|
4
|
+
* 标题 = `String(err)`;异常**自带机器码**(`err.code` 是串 —— 引擎的构造期拒因都带,如 `config.agent_config_dir_invalid`)⇒ 按 Node 给自家
|
|
5
|
+
* 带码异常的写法 `<name> [<code>]: <message>` 把码写进标题(`String(err)` 不带 `code`,修前拒启行里看不到引擎的码)。一条规则,不分谁铸的:
|
|
6
|
+
* 多数 Node 自己的 `ERR_*` 异常本来就印成这一形(逐字不变),系统调用异常(`EEXIST` 等)标题里多一次码。
|
|
7
|
+
*/
|
|
2
8
|
export declare function buildFatalErrorText(err: unknown): string;
|
|
3
9
|
/**
|
|
4
10
|
* 进程级 fatal 出口的**唯一一只腿**:同一只 {@link buildFatalErrorText} 产物写两个面,然后按崩溃码退。
|
package/dist/boot/fatal-exit.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { redactSecrets } from "../trace/redact.js";
|
|
2
2
|
import { createLogger } from "../observability/logger.js";
|
|
3
3
|
export function buildFatalErrorText(err) {
|
|
4
|
-
|
|
4
|
+
const code = err instanceof Error ? err.code : undefined;
|
|
5
|
+
return redactSecrets(typeof code === "string" && err instanceof Error ? `${err.name} [${code}]: ${err.message}` : String(err));
|
|
5
6
|
}
|
|
6
7
|
export function fatalExit(err) {
|
|
7
8
|
const text = buildFatalErrorText(err);
|
|
@@ -311,9 +311,10 @@ export declare function createEngineNoticeSeat(logger: {
|
|
|
311
311
|
* 「键为 undefined」与「键缺席」对引擎逐字等价)。**不复制引擎缺省**(`roots`/`static-face`/`carry`/`open`/
|
|
312
312
|
* CC parity 20/200 一个都不抄):上游改缺省之日起本仓不会成为第二份真源。
|
|
313
313
|
*/
|
|
314
|
-
export type DeploymentPostureSeats = Pick<RunnerDeps, "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "allowedTools">;
|
|
315
|
-
/** 本族读的配置切面:席名与 config 键同名的那几席 + 运维 allow 表(席 `allowedTools` ← config `approvalGrant`,env 名 `APPROVAL_GRANT` 不改)
|
|
316
|
-
|
|
314
|
+
export type DeploymentPostureSeats = Pick<RunnerDeps, "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "allowedTools" | "agentConfigDir">;
|
|
315
|
+
/** 本族读的配置切面:席名与 config 键同名的那几席 + 运维 allow 表(席 `allowedTools` ← config `approvalGrant`,env 名 `APPROVAL_GRANT` 不改)
|
|
316
|
+
* + LKG 覆写路径(`readDenyPatterns` 席并入它派生的读拒行,`lkgReadDenyRows`)。 */
|
|
317
|
+
export type DeploymentPostureConfig = Pick<ServiceConfig, Exclude<keyof DeploymentPostureSeats, "allowedTools"> | "approvalGrant" | "configLkgPath">;
|
|
317
318
|
export declare function buildDeploymentPostureSeats(config: DeploymentPostureConfig): DeploymentPostureSeats;
|
|
318
319
|
export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
|
|
319
320
|
export declare function createRunnerDeps(ctx: RunnerDepsCtx): RunnerDeps;
|
package/dist/boot/runner-deps.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFileWorkflowScriptStore } from "@sema-agent/core";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { listCollabWorkflows, resolveCollabWorkflow } from "../capabilities/collab-workflows.js";
|
|
4
|
+
import { lkgReadDenyRows } from "../config-lkg.js";
|
|
4
5
|
import { fleetBackgroundChildPublisher, FleetEventBus } from "../fleet/fleet-bus.js";
|
|
5
6
|
import { createMemoryEngineIncidentSink } from "../memory-incident-sink.js";
|
|
6
7
|
import { createMemorySessionIncarnationSeat, sessionFaceReaderOf } from "./session-incarnation-seat.js";
|
|
@@ -9,7 +10,7 @@ import { createHardenedVmRunner } from "../orchestration/hardened-vm-runner.js";
|
|
|
9
10
|
import { createWorkerHardenedVmRunner } from "../orchestration/hardened-vm-worker-runner.js";
|
|
10
11
|
import { WorkflowAgentRegistry } from "../orchestration/workflow-agent-steer.js";
|
|
11
12
|
import { resolveServedSession } from "../orchestration/workflow-completion-inbox.js";
|
|
12
|
-
import { defaultEngineNoticeRouter,
|
|
13
|
+
import { defaultEngineNoticeRouter, redactNoticeRuleText } from "../trace/engine-notice-wire.js";
|
|
13
14
|
import { WorkflowNotifyGate } from "../orchestration/workflow-notify-journal.js";
|
|
14
15
|
import { makeLoadProjectMemory, makeProbeInstructionSources } from "../project-memory.js";
|
|
15
16
|
import { createInstructionSourceSeat } from "../instruction-source-notice.js";
|
|
@@ -41,7 +42,7 @@ const PROMPT_CACHE_BREAK_LEVELS = new Map(Object.entries(PROMPT_CACHE_BREAK_CAUS
|
|
|
41
42
|
const CEILING_ORIGINS = new Set(["walltime", "turns"]);
|
|
42
43
|
export function createEngineNoticeForwarder(logger, router = defaultEngineNoticeRouter, metrics) {
|
|
43
44
|
return (notice) => {
|
|
44
|
-
const detail =
|
|
45
|
+
const detail = redactNoticeRuleText(notice.code, notice.detail);
|
|
45
46
|
logger.warn("engine_notice", { code: notice.code, message: notice.message, ...(detail !== undefined ? { detail } : {}) });
|
|
46
47
|
if (metrics !== undefined && notice.code === "task.interrupt_unconsumed") {
|
|
47
48
|
const origin = notice.detail?.origin;
|
|
@@ -57,11 +58,14 @@ export function createEngineNoticeSeat(logger, router = defaultEngineNoticeRoute
|
|
|
57
58
|
}
|
|
58
59
|
export function buildDeploymentPostureSeats(config) {
|
|
59
60
|
const crossSessionLayers = buildCrossSessionInboundLayers(config);
|
|
61
|
+
const lkgRows = lkgReadDenyRows(config.configLkgPath);
|
|
62
|
+
const readDenyAdditions = lkgRows.length === 0 ? config.readDenyPatterns : [...(config.readDenyPatterns ?? []), ...lkgRows];
|
|
60
63
|
const seats = {
|
|
61
64
|
readFace: config.readFace ? config.readFace : undefined,
|
|
62
|
-
readDenyPatterns:
|
|
65
|
+
readDenyPatterns: readDenyAdditions ? readDenyAdditions : undefined,
|
|
63
66
|
readDenyBuiltinTiers: config.readDenyBuiltinTiers ?? undefined,
|
|
64
67
|
readDenyBuiltinExclude: config.readDenyBuiltinExclude ?? undefined,
|
|
68
|
+
agentConfigDir: config.agentConfigDir ?? undefined,
|
|
65
69
|
memoryDelegationEvidence: config.memoryDelegationEvidence ?? undefined,
|
|
66
70
|
memoryProvenance: config.memoryProvenance ?? undefined,
|
|
67
71
|
memoryCapturePolicy: config.memoryCapturePolicy ?? undefined,
|
|
@@ -13,6 +13,7 @@ import { installSecretEnvScrubRecorder } from "../observability/secret-env-scrub
|
|
|
13
13
|
import { setRunTerminalLogObserver } from "../observability/run-terminal.js";
|
|
14
14
|
import { setRedactionObserver } from "../trace/redact.js";
|
|
15
15
|
import { installOutboundDispatcher } from "../outbound-dispatcher.js";
|
|
16
|
+
import { provisionSemaConfigDir } from "../sema-config-dir.js";
|
|
16
17
|
export function runStage01() {
|
|
17
18
|
const config = loadConfig();
|
|
18
19
|
const runStaleFailure = crossInvariant("run-stale-heartbeat").check({ live: config, next: config });
|
|
@@ -44,6 +45,7 @@ export function runStage01() {
|
|
|
44
45
|
else
|
|
45
46
|
logger.info("run_failed", fields);
|
|
46
47
|
});
|
|
48
|
+
provisionSemaConfigDir(config, logger);
|
|
47
49
|
const localRoot = configLocalRoot(config);
|
|
48
50
|
const rootsSplit = splitLocalRoots({
|
|
49
51
|
localDataRoot: config.localDataRoot,
|
|
@@ -1,19 +1,42 @@
|
|
|
1
|
-
import type { SubagentSpawnContext, ToolSpec } from "@sema-agent/core";
|
|
1
|
+
import type { SubagentSpawnContext, SubagentToolOptions, ToolSpec } from "@sema-agent/core";
|
|
2
2
|
/** Current time — the model has no clock; OA/most tasks need it. No side effects. CC-parity tool name `Now`
|
|
3
3
|
* (PascalCase, core 1.176 first-party rename wrap-up — the SERVICE owns this one; `nowTool` is not a core tool).
|
|
4
4
|
* effect:"read" so it never suspends → no durable-resume rename strand (no pending-tool checkpoint to canonicalize). */
|
|
5
5
|
export declare function nowTool(): ToolSpec;
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* 委派子代逐次注入的**一组**工具:`tools` 是这组**可能**注入的全部工具对象,`forSpawn` 只在这些对象里选「这一次 spawn 拿不拿」(缺席 = 每次都拿)。
|
|
8
|
+
* 名册与工厂读的是同一张 `tools` —— 工厂只能交出名册里那几只对象,结构上交不出名册外的东西(core 7.46.0 #1432 的「声明不说谎」在本仓由形状保证,
|
|
9
|
+
* 不靠两张表对拍)。
|
|
10
|
+
*/
|
|
11
|
+
export interface SubagentExtraToolSet {
|
|
12
|
+
readonly tools: readonly ToolSpec[];
|
|
13
|
+
readonly forSpawn?: (ctx: SubagentSpawnContext) => boolean;
|
|
14
|
+
}
|
|
15
|
+
/** 委派工具上与逐次注入相关的两键(core `SubagentToolOptions` 的 `extraTools` / `extraToolNames`):工厂(本仓的恒同步)+ 它的名册。 */
|
|
16
|
+
export interface SubagentExtraToolOptions {
|
|
17
|
+
readonly extraTools: (ctx: SubagentSpawnContext) => ToolSpec[];
|
|
18
|
+
readonly extraToolNames: NonNullable<SubagentToolOptions["extraToolNames"]>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* #1110(core 7.32.0)—— 委派子代的**逐次注入**:`Now` + 部署的 extra tools(今天 = SendUserFile)。
|
|
8
22
|
*
|
|
9
23
|
* 为什么 `Now` 走这条通道:core 7.32.0 起子代按**身份**继承场景层 bundle 的工具(`assembleCodeTools` 交给 Agent 的
|
|
10
24
|
* `inheritedTools` ∩ 父 run 真挂载),而 `Now` 是本仓在 bundle **旁边**组的宿主工具(scenarios.ts),宿主工具**从不**被继承
|
|
11
25
|
* (core:继承整张 `spec.tools` 会把宿主工具带过宿主自己的逐次门)—— 逐次注入是它到达子代的唯一通道。一个工厂、两个产地:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
26
|
+
* 部署那一组的门(只读内建排除)在它自己的 `forSpawn` 里,这里只做合成。
|
|
27
|
+
*
|
|
28
|
+
* core 7.46.0 #1432 —— **工厂与名册一个来源**:返回委派工具的两键,`extraTools`(每次 spawn 交出 `forSpawn` 选中的那几只)与
|
|
29
|
+
* `extraToolNames`(全部可能交出的那几只的名字 / 别名 / 内容来源,从同一批 `ToolSpec` 读出)。名册在场 ⇒ 引擎重新能列出子代的工具池:
|
|
30
|
+
* 定义级 / 工具级 / `TaskSpec.agents` 清单里的退役拼法与大小写孪生回到开跑前拒(`config.agent.unknown_tool`),记忆内容来源读者按声明读这几只。
|
|
31
|
+
* 修前(core 7.32.0–7.45.x)工厂恒在场 ⇒ 池不可静态知 ⇒ 那一族拼法只告不拒、委派结果一律按「外部」读。
|
|
32
|
+
* 工厂**不抛**:各组的 `tools` 是装配期就建好的对象;部署 `forSpawn` 的抛由 core 收成「这一 spawn 什么都不继承 + 模型面披露句」(onExtraToolsError 记 warn)。
|
|
33
|
+
* 一种拼法出现在两只工具上(部署工具与 `Now` 撞名 / 别名撞)⇒ core 在建委派工具处抛 `config.extra_tool_names`(`shape`),不静默取其一。
|
|
34
|
+
*/
|
|
35
|
+
export declare function createSubagentExtraTools(deployment?: SubagentExtraToolSet): SubagentExtraToolOptions;
|
|
36
|
+
/**
|
|
37
|
+
* `onExtraToolsError` 收到的错误 → 运维行 `subagent_extra_tools_error` 的字段:句子,加上错误**自带**的码与 `detail.reason`(在场才铸)。
|
|
38
|
+
* core 7.46.0 #1432 被拒的 spawn(产物不在名册里)带 `config.extra_tool_names` + `undeclared_name` / `undeclared_origin`;部署工厂自己抛的错多半两位都不带。
|
|
39
|
+
* 本仓不按码分支、不重铸 —— 只把错误说的话原样过境到运维面(修前只记句子,码与原因词丢在这一跳)。
|
|
17
40
|
*/
|
|
18
|
-
export declare function
|
|
41
|
+
export declare function extraToolsErrorFields(err: unknown): Record<string, unknown>;
|
|
19
42
|
//# sourceMappingURL=builtin-tools.d.ts.map
|
|
@@ -9,6 +9,27 @@ export function nowTool() {
|
|
|
9
9
|
};
|
|
10
10
|
}
|
|
11
11
|
export function createSubagentExtraTools(deployment) {
|
|
12
|
-
|
|
12
|
+
const sets = [{ tools: [nowTool()] }, ...(deployment !== undefined ? [deployment] : [])];
|
|
13
|
+
return {
|
|
14
|
+
extraTools: (ctx) => sets.flatMap((s) => (s.forSpawn === undefined || s.forSpawn(ctx) ? [...s.tools] : [])),
|
|
15
|
+
extraToolNames: sets.flatMap((s) => s.tools).map((t) => ({
|
|
16
|
+
name: t.name,
|
|
17
|
+
...(t.aliases !== undefined ? { aliases: [...t.aliases] } : {}),
|
|
18
|
+
...(t.contentOrigin !== undefined ? { contentOrigin: t.contentOrigin } : {}),
|
|
19
|
+
})),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function extraToolsErrorFields(err) {
|
|
23
|
+
const fields = { err: err instanceof Error ? err.message : String(err) };
|
|
24
|
+
if (err === null || typeof err !== "object")
|
|
25
|
+
return fields;
|
|
26
|
+
const code = Reflect.get(err, "code");
|
|
27
|
+
if (typeof code === "string")
|
|
28
|
+
fields.code = code;
|
|
29
|
+
const detail = Reflect.get(err, "detail");
|
|
30
|
+
const reason = detail !== null && typeof detail === "object" ? Reflect.get(detail, "reason") : undefined;
|
|
31
|
+
if (typeof reason === "string")
|
|
32
|
+
fields.reason = reason;
|
|
33
|
+
return fields;
|
|
13
34
|
}
|
|
14
35
|
//# sourceMappingURL=builtin-tools.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { type WebFetchConfig, type BackgroundAgentStore, type CheckpointStore, type PromptProvider, type Runner, type SkillSpec, type
|
|
1
|
+
import { type WebFetchConfig, type BackgroundAgentStore, type CheckpointStore, type PromptProvider, type Runner, type SkillSpec, type TaskListStore, type ToolSpec, type WebSearchConfig } from "@sema-agent/core";
|
|
2
2
|
import type { Metrics } from "../observability/metrics.js";
|
|
3
3
|
import type { Logger } from "../observability/logger.js";
|
|
4
|
+
import { type SubagentExtraToolSet } from "./builtin-tools.js";
|
|
4
5
|
import { type RepoReadClient } from "./repo-tools.js";
|
|
5
6
|
import { type LoadedSkill } from "./skills.js";
|
|
6
7
|
import { type ScenarioHands } from "./hands-lane.js";
|
|
@@ -90,12 +91,13 @@ export interface ScenarioDeps {
|
|
|
90
91
|
* core 1.287 `SubagentToolOptions.extraTools` seam wire-through — per-SPAWN dynamic tool injection into the
|
|
91
92
|
* delegated (Agent-tool) child roster. Deployment-injected per-task tools (today: SendUserFile — its instance is
|
|
92
93
|
* execute-time late-bound via ctx.taskId → TaskEnvRegistry, so ONE spec serves every spawn) cannot ride the
|
|
93
|
-
* ASSEMBLY-time static `subagent.tools` pool;
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
94
|
+
* ASSEMBLY-time static `subagent.tools` pool; core evaluates the factory at each spawn and merges its product into the
|
|
95
|
+
* pool, then it goes through core's `resolveToolSubset` whitelist like any other tool (agent-def allowTools/denyTools
|
|
96
|
+
* still govern — no extra gate needed on our side). A SET, not a bare factory (core 7.46.0 #1432): its `tools` are both
|
|
97
|
+
* what a spawn can be handed and the roster the delegation tool declares (`extraToolNames`), so the factory cannot hand
|
|
98
|
+
* out a tool the roster does not name. Absent ⇒ the delegation tool injects `Now` alone.
|
|
97
99
|
*/
|
|
98
|
-
subagentExtraTools?:
|
|
100
|
+
subagentExtraTools?: SubagentExtraToolSet;
|
|
99
101
|
/**
|
|
100
102
|
* core 1.364 durable background agents([ref] S1,[ref] 提货单②写半场):后台子代 `a*` 行的
|
|
101
103
|
* durable 执行记录 store。**必须与 `RunnerDeps.backgroundAgentStore` 同实例**(读写双点配对,engine
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, WEB_FETCH_TOOL_NAME, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
|
|
2
2
|
import { HttpError, createCodedHttpError } from "../security.js";
|
|
3
|
-
import { createSubagentExtraTools, nowTool } from "./builtin-tools.js";
|
|
3
|
+
import { createSubagentExtraTools, extraToolsErrorFields, nowTool } from "./builtin-tools.js";
|
|
4
4
|
import { parseRepo, repoToolsFor } from "./repo-tools.js";
|
|
5
5
|
import { createCouncilTool, clampRounds } from "./code-review-council.js";
|
|
6
6
|
import { stablePrompt } from "./prompt.js";
|
|
@@ -70,9 +70,9 @@ export function buildScenarios(deps) {
|
|
|
70
70
|
...(deps.checkpointStore ? { checkpointStore: deps.checkpointStore } : {}),
|
|
71
71
|
...(deps.ensureChildSessionDurable ? { ensureChildSessionDurable: deps.ensureChildSessionDurable } : {}),
|
|
72
72
|
},
|
|
73
|
-
|
|
73
|
+
...createSubagentExtraTools(deps.subagentExtraTools),
|
|
74
74
|
...(deps.logger
|
|
75
|
-
? { onExtraToolsError: (err, info) => deps.logger?.warn?.("subagent_extra_tools_error", { ...info,
|
|
75
|
+
? { onExtraToolsError: (err, info) => deps.logger?.warn?.("subagent_extra_tools_error", { ...info, ...extraToolsErrorFields(err) }) }
|
|
76
76
|
: {}),
|
|
77
77
|
},
|
|
78
78
|
...(webSearch ? { webSearch } : {}),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ToolSpec } from "@sema-agent/core";
|
|
2
2
|
import type { IssuedFileLink } from "../plugins/send-user-file.js";
|
|
3
|
+
import type { SubagentExtraToolSet } from "./builtin-tools.js";
|
|
3
4
|
/** The frame the web/shell renders as a file card. Rides the durable events tail (replayable). */
|
|
4
5
|
export interface FileLinkFrame {
|
|
5
6
|
type: "file_link";
|
|
@@ -44,7 +45,9 @@ export declare function sendUserFileTool(deps: {
|
|
|
44
45
|
}): ToolSpec;
|
|
45
46
|
export declare const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
46
47
|
/**
|
|
47
|
-
*
|
|
48
|
+
* The deployment's group for the delegated-child injection (`createSubagentExtraTools` in `builtin-tools.ts` composes it with
|
|
49
|
+
* `Now` into the delegation tool's `extraTools` factory AND its `extraToolNames` roster — one list, read twice): SendUserFile,
|
|
50
|
+
* handed to every spawn except a read-only builtin agent's.
|
|
48
51
|
*
|
|
49
52
|
* F2 (MED, codex; a product judgment of this server, kept): read-only builtin agents (Explore/Plan) must NOT get
|
|
50
53
|
* SendUserFile — a read-only agent that can read sensitive files AND mint public links off them breaks the read-only
|
|
@@ -59,5 +62,5 @@ export declare const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
|
59
62
|
*/
|
|
60
63
|
export declare function subagentSendUserFileExtraTools(deps: {
|
|
61
64
|
spec: ToolSpec;
|
|
62
|
-
}):
|
|
65
|
+
}): SubagentExtraToolSet;
|
|
63
66
|
//# sourceMappingURL=send-user-file-tool.d.ts.map
|
|
@@ -94,6 +94,6 @@ export function sendUserFileTool(deps) {
|
|
|
94
94
|
export const SEND_USER_FILE_TOOL_NAME = "SendUserFile";
|
|
95
95
|
const READONLY_BUILTIN_AGENT_TYPES = new Set(builtinAgentDefinitions().map((d) => d.name));
|
|
96
96
|
export function subagentSendUserFileExtraTools(deps) {
|
|
97
|
-
return (ctx) => (ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType)
|
|
97
|
+
return { tools: [deps.spec], forSpawn: (ctx) => !(ctx.agentType !== undefined && READONLY_BUILTIN_AGENT_TYPES.has(ctx.agentType)) };
|
|
98
98
|
}
|
|
99
99
|
//# sourceMappingURL=send-user-file-tool.js.map
|
package/dist/config-catalog.js
CHANGED
|
@@ -94,7 +94,7 @@ export const CONFIG_CATALOG = [
|
|
|
94
94
|
r("CONFIG_CATALOG_ENABLED", "auth", "boolean", "本目录端点 GET /v1/config/catalog 的旋钮(operator-only 面;合规部署可关 ⇒ 路径 404 + 能力位翻假)", { staticDefault: "true", danger: SEC, configKey: "configCatalogEnabled" }),
|
|
95
95
|
r("CONFIG_LKG_DURABLE", "integrations", "boolean", "LKG 跨重启 handoff(严格 'true' 才开:重启先用 LKG 起服,tier 候选借此落地)", { staticDefault: "false" }),
|
|
96
96
|
r("CONFIG_LKG_ENABLED", "integrations", "boolean", "last-known-good 配置盘缓存读写开关(仅远程 provider 有意义)", { staticDefault: "true" }),
|
|
97
|
-
r("CONFIG_LKG_PATH", "integrations", "path", "LKG 落盘路径", { derivedDefaultNote: "缺席 ⇒ SEMA_CONFIG_DIR
|
|
97
|
+
r("CONFIG_LKG_PATH", "integrations", "path", "LKG 落盘路径(设了 ⇒ 派生读拒行:这条路径与它的真实落点各一行,按路径段串匹配 —— 任意深度出现即拒、大小写不敏感,用户仓里同段串的路径一并读不到;空串拒启)", { derivedDefaultNote: "缺席 ⇒ 配置根(SEMA_CONFIG_DIR,缺省 ~/.sema)下按 worker + 凭证身份分箱派生", configKey: "configLkgPath" }),
|
|
98
98
|
r("CONFIG_LOCAL_DIR", "integrations", "path", "本地配置源根(config.d/<domain>.json);兼作数据根回落链一环", { configKey: "configLocalDir", danger: DATA }),
|
|
99
99
|
r("CONFIG_PROVIDER", "integrations", "enum", "配置源双模选择(闭集,逐字比较;认不出的词 ⇒ 拒启,S-841)", { enumValues: CONFIG_PROVIDER_WORDS, derivedDefaultNote: "缺席:SEMA_REGISTRY_URL 在场 ⇒ remote,否则纯 env(无管道)", configKey: "configProvider" }),
|
|
100
100
|
r("CONFIG_REQUIRE_ROSTER", "integrations", "boolean", "远程 registry 非 dryRun 时布防 ready 门:roster 未落地 ⇒ 计费提交 503 + /health ready:false(严格 'true')", { staticDefault: "false" }),
|
|
@@ -391,7 +391,7 @@ export const CONFIG_CATALOG = [
|
|
|
391
391
|
r("SELF_ORCHESTRATION_MODELS", "orchestration", "csv", "workflow agent({modelName}) 显式词表(恒赢;缺席单用户=目录键全集/多租户 fail-closed)", { configKey: "selfOrchestrationModels" }),
|
|
392
392
|
r("SELF_ORCHESTRATION_WORKER_ISOLATION", "orchestration", "boolean", "workflow 脚本跑 worker_thread(堆帽+terminate 的 DoS 隔离)", { staticDefault: "false", danger: SEC, configKey: "selfOrchestrationWorkerIsolation" }),
|
|
393
393
|
r("SEMA_APT_MIRROR", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 apt 源地址(注入沙箱 pkg-source hook)"),
|
|
394
|
-
r("SEMA_CONFIG_DIR", "integrations", "path", "~/.sema 配置目录覆写(LKG/skill 缓存/scheduler
|
|
394
|
+
r("SEMA_CONFIG_DIR", "integrations", "path", "~/.sema 配置目录覆写(LKG/skill 缓存/scheduler 店同门;设了 ⇒ 起服时建出目录,交引擎在根下同护配置文件,能不能用由引擎判;空串拒启)", { derivedDefaultNote: "缺席 ⇒ ~/.sema", configKey: "agentConfigDir" }),
|
|
395
395
|
r("SEMA_FAILOPEN_PROBE", "observability", "path", "F 类 fail-open 命中的探针文件路径(在场时逐次追加 fail-open 行;缺席只走计数+一次性 warn)"),
|
|
396
396
|
r("SEMA_NODE_MIRROR", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 node 分发镜像地址"),
|
|
397
397
|
r("SEMA_NPM_REGISTRY", "orchestration", "url", "SANDBOX_PKG_SOURCE=custom 的显式 npm registry 地址"),
|
package/dist/config-lkg.d.ts
CHANGED
|
@@ -11,11 +11,37 @@ export interface PersistedLkg {
|
|
|
11
11
|
/** skill 正文的 content-addressed 盘缓存目录(LKG 的姊妹面):LKG 只含 skills MANIFEST(正文按
|
|
12
12
|
* hash 另拉)——黑洞中心下 LKG-boot 会在 skill 正文拉取上再挂数秒(live 实测 +5.3s)。正文按
|
|
13
13
|
* `<dir>/<sha-hex>` 落盘,读侧**必验 hash**(content-addressed 的全部意义;坏文件=忽略重拉)。 */
|
|
14
|
-
export declare function defaultSkillCacheDir(
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export declare function defaultSkillCacheDir(config: {
|
|
15
|
+
readonly agentConfigDir?: string;
|
|
16
|
+
}): string;
|
|
17
|
+
/** 默认路径(scheduler store 同门:配置根 = 配置席 `agentConfigDir` → ~/.sema,{@link configRootOf}),按「worker + 凭证身份」分箱。`CONFIG_LKG_PATH`
|
|
18
|
+
* 覆写(`config.configLkgPath`,读法 {@link lkgPathOverrideOf};单一路径 ⇒ 不分箱,信封的 worker + identity 核仍守读回);`CONFIG_LKG_ENABLED=false` 关闭(读写双关)。
|
|
17
19
|
* 🪦 正名前的 `CONFIG_LKG_DISABLED` 自 server 3.0.0 起是**fail-loud 墓碑**——设了(任何值)即拒启并指路新名,不再被读取。 */
|
|
18
|
-
export declare function defaultLkgPath(
|
|
20
|
+
export declare function defaultLkgPath(config: {
|
|
21
|
+
readonly agentConfigDir?: string;
|
|
22
|
+
}, worker: string | undefined, identity: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* `CONFIG_LKG_PATH`(LKG 落盘路径覆写)的**唯一读法**:没设 ⇒ `undefined`(LKG 落在配置根下按箱派生,{@link defaultLkgPath});设了 ⇒ 绝对路径
|
|
25
|
+
* (相对值按进程 cwd 解,与文件被打开时同一个解法);空串 ⇒ 拒(起服拒):空串解出来是进程工作目录本身 —— 一条落盘路径不该是一个目录,
|
|
26
|
+
* 由它派生的读拒行({@link lkgReadDenyRows})会把整个工作目录读拒掉。`loadConfig` 调它铸 `configLkgPath`,读者只读那一席。
|
|
27
|
+
*/
|
|
28
|
+
export declare function lkgPathOverrideOf(raw: string | undefined): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* LKG(含凭据)实际所在位置的**读拒行** —— 一条规则:读拒从 LKG 落在哪派生,不写死位置。
|
|
31
|
+
* · 没覆写 ⇒ LKG 落在配置根下(`config-lkg-<箱>.json`):引擎内建表的 `~/.sema/config-lkg-*.json` 行在家目录与声明的根(`agentConfigDir`,
|
|
32
|
+
* 根经链接时引擎连目标一起守)两处守它 ⇒ 不另加行(加了就是同一个文件两只判据,`READ_DENY_BUILTIN_EXCLUDE` 删那一行也删不掉它);
|
|
33
|
+
* · 覆写 ⇒ LKG 在配置根外,引擎那一行够不着 ⇒ 这里派生行:它的**两种拼法**各一行 —— 覆写写下的绝对路径,与文件真正落的地方(路径经符号链接的
|
|
34
|
+
* 目录时,写进去的文件在链接目标下;只守拼写的话读真实路径就绕过了 —— 车内复审 [high])。两者相同 ⇒ 一行。大小写不敏感(与内建行同读法)。
|
|
35
|
+
* 拒句与内建行同一句(`sensitive-path read deny list (pattern …)`)。
|
|
36
|
+
* **行的射程**(引擎读拒表的读法,不是本函数选的):行是路径段串 —— 引擎去掉开头的 `/`、不锚到根,这串段在任意深度出现即拒(大小写不敏感),
|
|
37
|
+
* 所以挡的不止 LKG 那一个文件:用户仓里同段串的路径(`/app/config-lkg.json` ⇒ `…/app/config-lkg.json`)一并读不到、Grep 结果里也不出现。
|
|
38
|
+
* 挡多了优于不挡;引擎提供锚定单文件的行之前如此(随包文案 MIGRATION / 目录行同句)。
|
|
39
|
+
* 读者:部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats` 的 `readDenyPatterns` 席 = 运维的行 ∪ 本函数的行)—— 每次铸席现解一次链接。
|
|
40
|
+
*/
|
|
41
|
+
export declare function lkgReadDenyRows(configLkgPath: string | undefined): {
|
|
42
|
+
pattern: string;
|
|
43
|
+
caseSensitive: boolean;
|
|
44
|
+
}[];
|
|
19
45
|
/** 原子落盘一个干净候选(tmp+rename,0600)。BEST-EFFORT:失败=一条 warn 的素材(调用方发),绝不
|
|
20
46
|
* 影响在飞的应用链——LKG 缓存丢失的代价只是下次冷启动回到 env 兜底。 */
|
|
21
47
|
export declare function saveLkg(path: string, worker: string | undefined, identity: string, effective: Record<string, unknown>, etag: string | undefined): Promise<void>;
|
|
@@ -31,7 +57,9 @@ export declare function loadLkg(path: string, worker: string | undefined, identi
|
|
|
31
57
|
reason: string;
|
|
32
58
|
}>;
|
|
33
59
|
/** 7.98.0 及以前的默认 LKG 路径(按当时的写法逐字保留 —— 这是盘上已有文件的名字,不是现行分箱规则)。 */
|
|
34
|
-
export declare function legacyV1LkgPath(
|
|
60
|
+
export declare function legacyV1LkgPath(config: {
|
|
61
|
+
readonly agentConfigDir?: string;
|
|
62
|
+
}, worker: string | undefined): string;
|
|
35
63
|
export type LegacyLkgMigration = {
|
|
36
64
|
readonly outcome: "none";
|
|
37
65
|
}
|
package/dist/config-lkg.js
CHANGED
|
@@ -1,15 +1,60 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import {
|
|
3
|
-
import { join, dirname } from "node:path";
|
|
1
|
+
import { promises as fs, lstatSync, realpathSync } from "node:fs";
|
|
2
|
+
import { basename, join, dirname, resolve } from "node:path";
|
|
4
3
|
import { randomBytes } from "node:crypto";
|
|
5
4
|
import { validatePromptsDomain } from "./prompts-domain-validate.js";
|
|
6
5
|
import { centerStateBin, pullTokenIdentity } from "./center-credential.js";
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { configRootOf } from "./sema-config-dir.js";
|
|
7
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
8
|
+
export function defaultSkillCacheDir(config) {
|
|
9
|
+
return join(configRootOf(config), "skill-cache");
|
|
9
10
|
}
|
|
10
|
-
export function defaultLkgPath(worker, identity) {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
export function defaultLkgPath(config, worker, identity) {
|
|
12
|
+
return join(configRootOf(config), `config-lkg-${centerStateBin(worker, identity)}.json`);
|
|
13
|
+
}
|
|
14
|
+
export function lkgPathOverrideOf(raw) {
|
|
15
|
+
if (raw === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (raw === "") {
|
|
18
|
+
throw new Error('env CONFIG_LKG_PATH="" is empty — refusing to start: an empty path resolves to the working directory itself, which is no file the last-known-good ' +
|
|
19
|
+
"config can be kept in. Unset the variable to keep the LKG below the configuration root (SEMA_CONFIG_DIR, default ~/.sema), or set it to the file path you mean.");
|
|
20
|
+
}
|
|
21
|
+
return resolve(raw);
|
|
22
|
+
}
|
|
23
|
+
export function lkgReadDenyRows(configLkgPath) {
|
|
24
|
+
if (configLkgPath === undefined)
|
|
25
|
+
return [];
|
|
26
|
+
return [...new Set([configLkgPath, landedPathOf(configLkgPath)])].map((pattern) => ({ pattern, caseSensitive: false }));
|
|
27
|
+
}
|
|
28
|
+
function landedPathOf(p) {
|
|
29
|
+
const rest = [];
|
|
30
|
+
let landed = p;
|
|
31
|
+
let unresolved;
|
|
32
|
+
for (let head = p;; head = dirname(head)) {
|
|
33
|
+
try {
|
|
34
|
+
landed = join(realpathSync(head), ...rest);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
if (unresolved === undefined && head !== p && segmentPresent(head))
|
|
39
|
+
unresolved = head;
|
|
40
|
+
if (dirname(head) === head)
|
|
41
|
+
break;
|
|
42
|
+
rest.unshift(basename(head));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (unresolved !== undefined)
|
|
46
|
+
recordFailOpen("server.config-lkg.landed-path-unresolved", `segment=${unresolved}`);
|
|
47
|
+
return landed;
|
|
48
|
+
}
|
|
49
|
+
function segmentPresent(p) {
|
|
50
|
+
try {
|
|
51
|
+
lstatSync(p);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
const code = e.code;
|
|
56
|
+
return code !== "ENOENT" && code !== "ENOTDIR";
|
|
57
|
+
}
|
|
13
58
|
}
|
|
14
59
|
export async function saveLkg(path, worker, identity, effective, etag) {
|
|
15
60
|
const body = { formatVersion: 2, savedAt: new Date().toISOString(), worker: worker ?? "global", identity, ...(etag !== undefined ? { etag } : {}), effective };
|
|
@@ -79,10 +124,9 @@ function lkgPayloadProblem(effective, etag) {
|
|
|
79
124
|
return "prompts face fails re-validation";
|
|
80
125
|
return undefined;
|
|
81
126
|
}
|
|
82
|
-
export function legacyV1LkgPath(worker) {
|
|
83
|
-
const dir = process.env.SEMA_CONFIG_DIR ?? join(homedir(), ".sema");
|
|
127
|
+
export function legacyV1LkgPath(config, worker) {
|
|
84
128
|
const key = (worker ?? "global").replace(/[^a-zA-Z0-9._-]/g, "_").slice(0, 64);
|
|
85
|
-
return join(
|
|
129
|
+
return join(configRootOf(config), `config-lkg-${key}.json`);
|
|
86
130
|
}
|
|
87
131
|
export async function migrateV1Lkg(o) {
|
|
88
132
|
const refuse = (why) => ({
|