@sema-agent/server 7.98.0 → 7.100.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 +19 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +86 -60
- package/deploy/sema-up/lib/envf.sh +110 -42
- package/deploy/sema-up/sema-up.sh +76 -34
- package/dist/approval-ask-machine.d.ts +10 -0
- package/dist/approval-ask-machine.js +3 -0
- package/dist/approval-card.d.ts +74 -1
- package/dist/approval-card.js +41 -8
- package/dist/approval.d.ts +44 -20
- package/dist/approval.js +14 -6
- package/dist/boot/config-center.d.ts +16 -8
- package/dist/boot/config-center.js +206 -103
- package/dist/boot/execution-env.d.ts +1 -2
- package/dist/boot/execution-env.js +2 -2
- package/dist/boot/leader.js +12 -9
- package/dist/boot/memory-consolidation.d.ts +38 -0
- package/dist/boot/memory-consolidation.js +35 -0
- package/dist/boot/org-memory.d.ts +2 -1
- package/dist/boot/org-memory.js +9 -5
- package/dist/boot/resolve-spec.d.ts +0 -1
- package/dist/boot/resolve-spec.js +4 -3
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/runtime-caps.js +4 -4
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +2 -2
- package/dist/boot/stage-06-runners.js +3 -3
- package/dist/boot/stage-07-capability-layer.d.ts +2 -3
- package/dist/boot/stage-07-capability-layer.js +6 -6
- package/dist/boot/stage-08-reapers.d.ts +3 -4
- package/dist/boot/stage-08-reapers.js +2 -2
- package/dist/boot/stage-09-leader.d.ts +2 -3
- package/dist/boot/stage-10-http-server.d.ts +2 -3
- package/dist/boot/stage-10-http-server.js +2 -2
- package/dist/capabilities/center-plugins.d.ts +14 -2
- package/dist/capabilities/center-plugins.js +13 -5
- package/dist/capabilities/center-prompts.d.ts +3 -0
- package/dist/capabilities/center-prompts.js +1 -1
- package/dist/capabilities/hands-lane.js +2 -1
- package/dist/capabilities/scenarios.js +18 -5
- package/dist/capabilities/tool-defer.d.ts +2 -2
- package/dist/center-credential.d.ts +112 -0
- package/dist/center-credential.js +131 -0
- package/dist/config-catalog.d.ts +2 -2
- package/dist/config-catalog.js +15 -15
- package/dist/config-center/apply-effective.d.ts +34 -39
- package/dist/config-center/apply-effective.js +100 -64
- package/dist/config-center/apply-ledger.d.ts +35 -16
- package/dist/config-center/apply-ledger.js +25 -18
- package/dist/config-center/center-request.d.ts +60 -0
- package/dist/config-center/center-request.js +71 -0
- package/dist/config-center/facade.d.ts +11 -5
- package/dist/config-center/facade.js +1 -1
- package/dist/config-center/hot-keys-registry.d.ts +11 -4
- package/dist/config-center/hot-keys-registry.js +11 -7
- package/dist/config-center/http-client.d.ts +17 -9
- package/dist/config-center/http-client.js +27 -36
- package/dist/config-center/restart-signal.d.ts +21 -14
- package/dist/config-center/restart-signal.js +16 -22
- package/dist/config-center/skills-mcp.d.ts +4 -1
- package/dist/config-center/skills-mcp.js +2 -2
- package/dist/config-center/types.d.ts +3 -3
- package/dist/config-invariants.d.ts +2 -2
- package/dist/config-invariants.js +0 -8
- package/dist/config-lkg.d.ts +47 -8
- package/dist/config-lkg.js +107 -20
- package/dist/config-provider.d.ts +8 -7
- package/dist/config-provider.js +5 -5
- package/dist/config-types.d.ts +30 -11
- package/dist/config.js +11 -13
- package/dist/cross-session-settings.js +1 -1
- package/dist/fleet-client.d.ts +8 -8
- package/dist/fleet-client.js +7 -7
- package/dist/fleet-lease.d.ts +11 -11
- package/dist/fleet-lease.js +17 -13
- package/dist/host-lsp-manager.d.ts +29 -0
- package/dist/host-lsp-manager.js +14 -0
- package/dist/hosted-posture.d.ts +49 -93
- package/dist/hosted-posture.js +1 -25
- package/dist/http/active-run-conflict.d.ts +2 -12
- package/dist/http/active-run-conflict.js +1 -6
- package/dist/http/admission.js +8 -0
- package/dist/http/resume-legs.d.ts +1 -1
- package/dist/http/resume-legs.js +14 -10
- package/dist/http/route-ctx.d.ts +2 -2
- package/dist/http/routes/approvals-assistant.d.ts +1 -1
- package/dist/http/routes/approvals-assistant.js +2 -2
- package/dist/http/routes/memory-compliance.js +5 -0
- package/dist/http/routes/memory-origin.js +5 -0
- package/dist/http/routes/sessions.js +6 -0
- package/dist/http/routes/tasks.js +7 -6
- package/dist/http/server.d.ts +6 -5
- package/dist/http/wire-types.d.ts +6 -3
- package/dist/leader/wire.d.ts +20 -12
- package/dist/leader/wire.js +7 -6
- package/dist/memory-layer-preflight.d.ts +46 -0
- package/dist/memory-layer-preflight.js +121 -0
- package/dist/memory-operator-faces.js +17 -1
- package/dist/observability/corrupt-read-seat.d.ts +21 -4
- package/dist/observability/corrupt-read-seat.js +31 -0
- package/dist/observability/fail-open.d.ts +8 -4
- package/dist/observability/fail-open.js +8 -4
- package/dist/observability/run-terminal.js +1 -0
- package/dist/observability/secret-env-scrub.d.ts +3 -26
- package/dist/observability/secret-env-scrub.js +3 -2
- package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
- package/dist/orchestration/workflow-completion-inbox.js +8 -3
- package/dist/plugins/approval-ask-store-file.d.ts +20 -2
- package/dist/plugins/approval-ask-store-file.js +21 -3
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
- package/dist/plugins/approval-ask-store-memory.js +39 -13
- package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
- package/dist/plugins/approval-ask-store-sql.js +27 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
- package/dist/plugins/checkpoint-store-sql.js +19 -2
- package/dist/plugins/local-checkpoint-store.js +2 -0
- package/dist/plugins/mailbox-store-sql.d.ts +7 -5
- package/dist/plugins/mailbox-store-sql.js +6 -4
- package/dist/plugins/permission-rule-store-file.js +3 -2
- package/dist/plugins/remote-env-host.d.ts +3 -1
- package/dist/plugins/remote-env-host.js +3 -2
- package/dist/plugins/sql-json-column.d.ts +16 -3
- package/dist/plugins/sql-json-column.js +15 -4
- package/dist/plugins/web-search.d.ts +42 -22
- package/dist/plugins/web-search.js +141 -49
- package/dist/project-memory.d.ts +28 -1
- package/dist/project-memory.js +22 -22
- package/dist/run-local.js +6 -7
- package/dist/runs.js +6 -5
- package/dist/runtime-caps-resolver.d.ts +8 -8
- package/dist/runtime-caps-resolver.js +16 -14
- package/dist/runtime-governance.d.ts +50 -3
- package/dist/runtime-governance.js +5 -0
- package/dist/sealed-key.js +3 -7
- package/dist/server-secret-env.d.ts +43 -0
- package/dist/server-secret-env.js +18 -0
- package/dist/task-settings.d.ts +1 -1
- package/dist/tool-approval.d.ts +144 -9
- package/dist/tool-approval.js +74 -18
- package/dist/trace/core-keyset-guard.d.ts +5 -5
- package/dist/trace/ledger-sink.js +2 -2
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +22 -5
- package/dist/trace/projection-drop.d.ts +2 -0
- package/dist/trace/projection-drop.js +6 -0
- package/dist/trace/sema-provenance.d.ts +4 -1
- package/dist/trace/sema-provenance.js +1 -1
- package/dist/trace/task-notification-facets.d.ts +65 -0
- package/dist/trace/task-notification-facets.js +31 -0
- package/dist/trace/wire-projection-faces.d.ts +3 -3
- package/dist/trace/wire-projection-faces.js +3 -3
- package/package.json +5 -4
package/dist/fleet-lease.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { QuotaLease, leaseExhausted, shouldRenewLease } from "@sema-agent/settings-schema/fleet";
|
|
2
|
+
import { CenterCredentialUnavailableError } from "./center-credential.js";
|
|
3
|
+
import { centerFetch } from "./config-center/center-request.js";
|
|
2
4
|
export const NOT_LEASE_TTL_MS = 10 * 60 * 1000;
|
|
3
5
|
export const EXHAUSTED_RETRY_MS = 30 * 1000;
|
|
4
6
|
export const LEASE_PRINCIPAL_CAP = 4096;
|
|
@@ -8,15 +10,11 @@ export class FleetLeaseManager {
|
|
|
8
10
|
state = new Map();
|
|
9
11
|
inflight = new Map();
|
|
10
12
|
f;
|
|
11
|
-
base;
|
|
12
|
-
headers;
|
|
13
13
|
now;
|
|
14
14
|
failStreak = 0;
|
|
15
15
|
constructor(opts) {
|
|
16
16
|
this.opts = opts;
|
|
17
17
|
this.f = opts.fetchImpl ?? fetch;
|
|
18
|
-
this.base = opts.center.baseUrl.replace(/\/+$/, "");
|
|
19
|
-
this.headers = { authorization: `Bearer ${opts.center.token}`, "content-type": "application/json" };
|
|
20
18
|
this.now = opts.now ?? Date.now;
|
|
21
19
|
}
|
|
22
20
|
async admit(principal) {
|
|
@@ -46,6 +44,11 @@ export class FleetLeaseManager {
|
|
|
46
44
|
peek(principal) {
|
|
47
45
|
return this.state.get(principal)?.kind;
|
|
48
46
|
}
|
|
47
|
+
authFailOpen(principal, status, note) {
|
|
48
|
+
this.opts.logger?.warn?.("fleet_lease_auth_error_fail_open", { status, principal, note });
|
|
49
|
+
this.opts.metrics?.inc("fleet_lease_auth_error_total", { status: String(status) });
|
|
50
|
+
return { ok: true };
|
|
51
|
+
}
|
|
49
52
|
set(principal, st) {
|
|
50
53
|
if (!this.state.has(principal) && this.state.size >= LEASE_PRINCIPAL_CAP) {
|
|
51
54
|
const oldest = this.state.keys().next().value;
|
|
@@ -64,11 +67,11 @@ export class FleetLeaseManager {
|
|
|
64
67
|
}
|
|
65
68
|
async issueOrRenewInner(principal) {
|
|
66
69
|
try {
|
|
67
|
-
const res = await this.
|
|
70
|
+
const { res } = await centerFetch(this.opts.center, { kind: "fleet-leases" }, {
|
|
68
71
|
method: "POST",
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
+
json: { principal, worker: this.opts.center.worker },
|
|
73
|
+
timeoutMs: LEASE_FETCH_TIMEOUT_MS,
|
|
74
|
+
fetchImpl: this.f,
|
|
72
75
|
});
|
|
73
76
|
if (res.status === 200) {
|
|
74
77
|
const body = (await res.json());
|
|
@@ -100,13 +103,14 @@ export class FleetLeaseManager {
|
|
|
100
103
|
return { ok: true };
|
|
101
104
|
}
|
|
102
105
|
if (res.status === 401 || res.status === 403 || res.status === 400) {
|
|
103
|
-
this.
|
|
104
|
-
this.opts.metrics?.inc("fleet_lease_auth_error_total", { status: String(res.status) });
|
|
105
|
-
return { ok: true };
|
|
106
|
+
return this.authFailOpen(principal, res.status, "center rejected the lease request — check the service token / worker id / center URL (budget NOT enforced until fixed)");
|
|
106
107
|
}
|
|
107
108
|
throw new Error(`lease ${res.status}`);
|
|
108
109
|
}
|
|
109
110
|
catch (err) {
|
|
111
|
+
if (err instanceof CenterCredentialUnavailableError) {
|
|
112
|
+
return this.authFailOpen(principal, "credential-unreadable", `${err.reason} — no lease request sent (budget NOT enforced until the credential is readable)`);
|
|
113
|
+
}
|
|
110
114
|
this.failStreak += 1;
|
|
111
115
|
if (this.failStreak === 1)
|
|
112
116
|
this.opts.logger?.warn?.("fleet_lease_unavailable_fail_open", { error: err instanceof Error ? err.message : String(err) });
|
|
@@ -117,8 +121,8 @@ export class FleetLeaseManager {
|
|
|
117
121
|
}
|
|
118
122
|
export function createFleetLeaseFromEnv(cfg, opts) {
|
|
119
123
|
const cc = cfg.configCenter;
|
|
120
|
-
if (!cc?.baseUrl || !cc.
|
|
124
|
+
if (!cc?.baseUrl || !cc.credential.declared || !cc.worker)
|
|
121
125
|
return undefined;
|
|
122
|
-
return new FleetLeaseManager({ ...opts, center: { baseUrl: cc.baseUrl,
|
|
126
|
+
return new FleetLeaseManager({ ...opts, center: { baseUrl: cc.baseUrl, credential: cc.credential, worker: cc.worker } });
|
|
123
127
|
}
|
|
124
128
|
//# sourceMappingURL=fleet-lease.js.map
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-616(core 7.30.1 #1073 提货)—— **宿主车道本地语言服务器**的唯一构造点。
|
|
3
|
+
*
|
|
4
|
+
* ## 病(DEBTS S-684 同轴的第二条,7.99.0 修复验证 opus 腿普查带出)
|
|
5
|
+
* core 的 `NodeLspManager` 默认 spawn(`defaultLspSpawn`)在 7.30.1 之前**不带 env** ⇒ 语言服务器整份继承本进程的
|
|
6
|
+
* `process.env`,脑钥 / 仓库令牌 / 本仓自己的凭据键全在里面,而语言服务器会执行仓里的代码(构建脚本、插件、
|
|
7
|
+
* 工作区配置)。7.30.1 起 core 默认改成 `scrubSecretEnv(process.env)`(按键名**形状**剥),并开了
|
|
8
|
+
* `NodeLspManagerOptions.env` 一格让宿主自己交 env(core 帖 [ref] ④;core 确认本仓的正确读法 = 交 host 车道**同一只**
|
|
9
|
+
* `scrubServerSecrets` 的结果)。
|
|
10
|
+
*
|
|
11
|
+
* ## 规则(一句)
|
|
12
|
+
* 语言服务器子进程的 env = {@link scrubServerSecrets}(process.env) —— 与模型可驱动的 host shell(`inheritEnv:"scrub"`)
|
|
13
|
+
* 和 project-memory 的 git 子进程**同一只函数**:目录 `type:"secret"` 键按名剥 ∘ core 形状规则按形剥。只交 core 的默认
|
|
14
|
+
* 会漏掉形状规则认不出的本仓凭据键(今天是 `OTEL_EXPORTER_OTLP_HEADERS`),那正是 S-684 当初的病形。
|
|
15
|
+
* 剥掉了什么经既有观测面留痕(site = `server.lsp.spawn-env`,每 site×key 一次披露 + 逐次计数)—— core 那侧
|
|
16
|
+
* 刻意不报(无收集器,KL),本仓这一格补上。
|
|
17
|
+
*
|
|
18
|
+
* ## 时刻
|
|
19
|
+
* env 在**构造时**取一次快照(boot / run-local 起腿时),而不是 core 默认那样每次 spawn 现取:`NodeLspManagerOptions.env`
|
|
20
|
+
* 是一份静态记录。本仓的 `process.env` 是 boot 期输入(热配置走配置中心,不改 `process.env`),两者在本仓部署上同值。
|
|
21
|
+
*
|
|
22
|
+
* ## 两个调用点
|
|
23
|
+
* `boot/execution-env.ts`(服务的 host 车道)与 `run-local.ts`(纯本地 CLI)此前各写一遍 `new NodeLspManager({ log })`;
|
|
24
|
+
* 现在都经本函数 —— 构造形只有一份,剥除规则也就只能有一份(源码对账钉在 `test/secret-env-scrub-disclosure.test.ts`)。
|
|
25
|
+
*/
|
|
26
|
+
import { NodeLspManager } from "@sema-agent/core";
|
|
27
|
+
/** 构造宿主车道的本地语言服务器管理器(`create*`:返回活对象)。`log` = 管理器的观测钩(open/heal/degrade/evict 事件)。 */
|
|
28
|
+
export declare function createHostLspManager(log: (event: string, fields: Record<string, unknown>) => void): NodeLspManager;
|
|
29
|
+
//# sourceMappingURL=host-lsp-manager.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NodeLspManager } from "@sema-agent/core";
|
|
2
|
+
import { recordSecretEnvScrub } from "./observability/secret-env-scrub.js";
|
|
3
|
+
import { scrubServerSecrets } from "./server-secret-env.js";
|
|
4
|
+
export function createHostLspManager(log) {
|
|
5
|
+
const findings = [];
|
|
6
|
+
const scrubbed = scrubServerSecrets(process.env, findings);
|
|
7
|
+
recordSecretEnvScrub("server.lsp.spawn-env", findings);
|
|
8
|
+
const env = {};
|
|
9
|
+
for (const [k, v] of Object.entries(scrubbed))
|
|
10
|
+
if (v !== undefined)
|
|
11
|
+
env[k] = v;
|
|
12
|
+
return new NodeLspManager({ log, env });
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=host-lsp-manager.js.map
|
package/dist/hosted-posture.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* S-591 / S-564(车LT,7.95.0)—— **「这台部署是不是托管 / 多租」的单一铸点**。
|
|
2
|
+
* S-591 / S-564(车LT,7.95.0)→ S-671 / B-10(车NB,7.99.0)—— **「这台部署是不是托管 / 多租」的单一铸点**。
|
|
3
3
|
*
|
|
4
4
|
* ## 为什么是一处
|
|
5
5
|
*
|
|
@@ -21,60 +21,62 @@
|
|
|
21
21
|
* **`deviceCwdHonored` 这一臂里没有 `requirePrincipal` 项**(`task-cwd.ts`:device 车道上那条路径落在
|
|
22
22
|
* **发起者自己的设备**上,归属由 `device_session` 绑定行验过 ⇒ 跨租户面结构性不存在)。
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
* 那台机器上每一条赦免都成立(它自称单用户),而每一道天花板都不成立(它没有 org 分区)。
|
|
26
|
-
* ⇒ 先把这个判定位铸成**一处**,再让两条腿都读它(clay 裁定③,2026-09-22):**不许各自加特判**——
|
|
24
|
+
* ⇒ 先把这个判定位铸成**一处**,再让各条腿都读它(clay 裁定③,2026-09-22):**不许各自加特判**——
|
|
27
25
|
* 每加一条特判,「这台机器算不算托管」在本仓就多一个可以自己漂的答案。
|
|
28
26
|
*
|
|
29
|
-
* ##
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* (
|
|
33
|
-
* ·
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* 两者都坏,但假阳会当场打到在跑的部署,所以词表只收**单用户形写不出来**的证据:
|
|
27
|
+
* ## 判据:托管 ⇔ 运维显式声明 `REQUIRE_PRINCIPAL=true`(clay 裁定 [ref] ①,2026-09-23)
|
|
28
|
+
*
|
|
29
|
+
* 只有一条定义式证据,零推断。7.95.0–7.98.x 还收过两条「运维点名了一个外部身份签发方」的推断臂
|
|
30
|
+
* (`PRINCIPAL_JWT_PUBKEYS/ISS/AUD` 任一在场;`AUTH_BRIDGE_ISSUER` 显式在场),[ref] ① 裁 (a) 撤回:
|
|
31
|
+
* · **真假阳**:本机自起引擎、配了 principal-JWT / auth-bridge 签发方的单用户部署(web-admin 8090 真案)
|
|
32
|
+
* 在 7.98.0 被判托管而拒启,只能滚回 7.93.5;
|
|
33
|
+
* · **与回退 issuer 不同判**:auth-bridge 的 `?? configCenter.baseUrl` 回退(`boot/stage-09-leader.ts`)
|
|
34
|
+
* 从来不算证据,而回退桥的引擎同样把单用户授予开给每一个验签通过的 registry JWT 调用者 —— 同一个
|
|
35
|
+
* 租户轴事实,「显式写了 issuer」与「从 center 推出 issuer」是同一形。裁定后两者同判。
|
|
36
|
+
* ⇒ 签发方配置是**身份验签轴**(谁的 JWT 算数),不是租户轴(这台机器有几个主人)。「受控于控制面」
|
|
37
|
+
* (`SEMA_REGISTRY_*`,含 worker 名)同理是 fleet 管理轴,本判据同样不读。
|
|
38
|
+
*
|
|
39
|
+
* 随两臂一起删掉的(按类删干净,规则集变小):S-564 腿①「托管 ∧ 未开 `REQUIRE_PRINCIPAL` ⇒ 拒启」
|
|
40
|
+
* (判据只剩读 `requirePrincipal` 的那一臂之后,那条合取恒假 ⇒ 结构上不可达)及其跨键不变量行;
|
|
41
|
+
* `run-local.ts` 为那两臂清 env 的清除口。
|
|
42
|
+
*
|
|
43
|
+
* ## 被排除的候选(逐条写下,不留「我没想到」的空白)
|
|
44
|
+
*
|
|
45
|
+
* 假阳 = 一台单用户部署被收走一个它一直在用的能力(7.98.0 时还会被拒启)。词表不收任何推断臂:
|
|
46
|
+
* · ❌ `PRINCIPAL_JWT_*` / `AUTH_BRIDGE_ISSUER` 在场 —— 见上一段([ref] ① 撤回;加回来 = 翻案,要新裁定)。
|
|
47
|
+
* · ❌ `configCenter`(含 `worker` 名)/ auth-bridge 的 center 回退 —— fleet 管理轴。初版(车LT)收过
|
|
48
|
+
* `managed-worker` 臂,实测两个反例后撤回:`run-local.ts` 强设 `REQUIRE_PRINCIPAL=false` 的本机 CLI 在一个
|
|
49
|
+
* 带 registry 键的共享 `.env` 上拒启;本仓自己的 `test/config-coverage-blocks.test.ts` configCenter 两格当场红。
|
|
53
50
|
* · ❌ `operatorPrincipals` 非空 —— **真假阳**:`IMAGE_BAKES_ENABLED` 那条既有不变量
|
|
54
|
-
* (`config-invariants.ts` 的 `bake-operator-principals`)
|
|
55
|
-
* 于是一台开了烤门的单用户机器会被判成托管。
|
|
56
|
-
* · ❌ `configCenter`(含 `worker` 名)/ auth-bridge 的 center 回退 —— 见上一段,已实测撤回。
|
|
51
|
+
* (`config-invariants.ts` 的 `bake-operator-principals`)**强制**开烤门的部署把它填非空。
|
|
57
52
|
* · ❌ `SERVICE_AUTH_TOKENS` 条目数 —— 一台机器上同时给 cli 与 web-admin 发一只令牌是单用户常态。
|
|
58
53
|
* · ❌ `DB_BACKEND=mysql|pg` —— 那是**耐久性**轴,不是租户轴(单用户要 durable run 也得开)。
|
|
59
54
|
* · ❌ `BIND_HOST` 非回环 —— 容器里 `0.0.0.0` 是单用户常态(compose/k8s 默认)。
|
|
60
|
-
* · ❌ `COMPLIANCE_PROFILE` / `RETENTION_MAX_AGE_DAYS` / `LOCKED_CONFIG_KEYS` ——
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* `
|
|
66
|
-
*
|
|
55
|
+
* · ❌ `COMPLIANCE_PROFILE` / `RETENTION_MAX_AGE_DAYS` / `LOCKED_CONFIG_KEYS` —— 数据/治理轴。
|
|
56
|
+
*
|
|
57
|
+
* ## 🔴 召回缺口是**全量**的(显式登记,不用「应该很少」填空)
|
|
58
|
+
*
|
|
59
|
+
* 一台多用户 / 团队机器只要运维**忘设** `REQUIRE_PRINCIPAL`,本判据就**看不见**它 —— 无论它有没有
|
|
60
|
+
* 配签发方、受不受 reconciler 管。那台机器上:`principal` 缺席走单用户 `"default"` 赦免 ⇒ 全服无主会话
|
|
61
|
+
* 被列给每一个调用方;上面四条能力授予面(hooks / `settings.env` / `cwd` / `additionalDirectories`)对每个
|
|
62
|
+
* 调用方打开;写容纳面天花板与 `approverPosture` 留声两条腿(下文)也按单用户放行。
|
|
63
|
+
* `settings.env` 的射程要说准:适配器的 `env` 是**并**到 `process.env` 上的逐键覆盖(`plugins/remote-env-host.ts`
|
|
64
|
+
* 的 `mergeEnv`),而 `task-settings.ts` 的 `cleanEnvMap` 没有任何键名拒集 ⇒ `PATH` / `LD_PRELOAD` /
|
|
65
|
+
* `NODE_OPTIONS` 一视同仁。
|
|
66
|
+
* **补偿**:`REQUIRE_PRINCIPAL=true` 本来就是那台机器该设的键(运维自己的声明;server 不替他推断)——
|
|
67
|
+
* `docs/DEPLOY-PREREQS.md` 的「托管 / 多租部署」段把它写成部署前置,并列出忘设它的后果。
|
|
67
68
|
*/
|
|
68
69
|
import { type ServiceConfigFlat } from "./config-types.js";
|
|
69
|
-
/** 托管/多租的**证据词表**(闭集;加词 ⇒ {@link hostedEvidenceNote} 的穷举 switch 编译红)。
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
/** 托管/多租的**证据词表**(闭集;加词 ⇒ {@link hostedEvidenceNote} 的穷举 switch 编译红)。
|
|
71
|
+
* 🔴 恰一词是裁定([ref] ①),不是暂时的:加回一条推断臂 = 翻案,先要新裁定。 */
|
|
72
|
+
export declare const HOSTED_EVIDENCE_WORDS: readonly ["require-principal"];
|
|
73
|
+
/** 一条证据(闭集一词)。 */
|
|
72
74
|
export type HostedEvidence = (typeof HOSTED_EVIDENCE_WORDS)[number];
|
|
73
75
|
/**
|
|
74
|
-
* 本判据读的配置键集 —— 取自平铺配置型(键名/类型永不与真配置漂)
|
|
75
|
-
*
|
|
76
|
+
* 本判据读的配置键集 —— 取自平铺配置型(键名/类型永不与真配置漂)。`ServiceConfig` **结构上**满足本形,
|
|
77
|
+
* 所以请求腿(阶段④)与能力面读同一只函数。
|
|
76
78
|
*/
|
|
77
|
-
export type HostedPostureInput = Readonly<Pick<ServiceConfigFlat, "requirePrincipal"
|
|
79
|
+
export type HostedPostureInput = Readonly<Pick<ServiceConfigFlat, "requirePrincipal">>;
|
|
78
80
|
/** 一台 worker 的托管姿态事实(值 + 证据)——纯数据。 */
|
|
79
81
|
export interface HostedPostureFacts {
|
|
80
82
|
/** 这台部署是不是托管 / 多租。 */
|
|
@@ -83,60 +85,14 @@ export interface HostedPostureFacts {
|
|
|
83
85
|
readonly evidence: readonly HostedEvidence[];
|
|
84
86
|
}
|
|
85
87
|
/**
|
|
86
|
-
* 判定位的**唯一**铸点(纯数据 ⇒ `build*`)。
|
|
88
|
+
* 判定位的**唯一**铸点(纯数据 ⇒ `build*`)。托管 ⇔ `requirePrincipal === true`([ref] ①)。
|
|
87
89
|
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
+
* `evidence` 仍是数组(按词表顺序收**全部**命中):拒因逐字点它们,运维才知道改哪儿;词表是闭集,
|
|
91
|
+
* 形状不随词数变。
|
|
90
92
|
*/
|
|
91
93
|
export declare function buildHostedPosture(c: HostedPostureInput): HostedPostureFacts;
|
|
92
|
-
/**
|
|
93
|
-
* 判据里**非定义式**那两条证据背后的 env 名 —— 即「运维显式点名了一个外部身份签发方」的全部拼法。
|
|
94
|
-
*
|
|
95
|
-
* 🔴 **单一属主**:`src/run-local.ts` 那条「强制单用户」的腿按本表清 env(W-新8)。它**结构上**兑现不了
|
|
96
|
-
* 这些键(它把 `REQUIRE_PRINCIPAL` 焊死成 false ⇒ principal 门根本不上场),留着它们只会让判据看见一个
|
|
97
|
-
* 假事实、然后拿一个**它自己会覆盖回去**的旋钮当拒因。名表抄第二份的那天,加一条证据 = run-local 静默漏清。
|
|
98
|
-
* ⚠️ 表里**没有** `REQUIRE_PRINCIPAL`(那是定义式那一条,run-local 自己显式写值)也**没有**
|
|
99
|
-
* `SEMA_REGISTRY_*`(fleet 管理轴,本判据根本不读 —— 本机 CLI 从组织下发面取配置是正当用法)。
|
|
100
|
-
*/
|
|
101
|
-
export declare const HOSTED_EVIDENCE_ENV_KEYS: readonly ["PRINCIPAL_JWT_PUBKEYS", "PRINCIPAL_JWT_ISS", "PRINCIPAL_JWT_AUD", "AUTH_BRIDGE_ISSUER"];
|
|
102
|
-
/**
|
|
103
|
-
* 把上表那几根 env **清掉**(`run-local.ts` 的「强制单用户」腿调它)。
|
|
104
|
-
*
|
|
105
|
-
* 🔴 **为什么是四行字面 `delete` 而不是照表循环**:配置目录的 D1 对账门按文件数**动态 env 读点**并且是
|
|
106
|
-
* **只降不升的棘轮** —— `process.env[k]` 那种计算属性读它一个都不放过(理由是动态读让「这个文件读了哪些
|
|
107
|
-
* 键」变成机器看不见的事)。照表循环会给 `run-local.ts` 顶上第 3 处动态读、当场撞棘轮。⇒ 名字写成字面,
|
|
108
|
-
* **但只写这一处**;上面那张表仍是**声明面**的属主,两者的一致性由
|
|
109
|
-
* `test/hosted-posture.test.ts` 的行为格看着(逐个设上再调一次,断言全没了)——不是靠人眼比对。
|
|
110
|
-
*
|
|
111
|
-
* ⚠️ 表里**没有** `REQUIRE_PRINCIPAL`(定义式那条,调用方自己显式写值)也没有 `SEMA_REGISTRY_*`
|
|
112
|
-
* (fleet 管理轴,本判据根本不读)。
|
|
113
|
-
*/
|
|
114
|
-
export declare function clearHostedEvidenceEnv(): void;
|
|
115
94
|
/** 一条证据的运维事实句 —— **穷举 switch,无缺省臂**(词表加员即编译红)。 */
|
|
116
95
|
export declare function hostedEvidenceNote(e: HostedEvidence): string;
|
|
117
|
-
/**
|
|
118
|
-
* **腿①(S-564)** —— 托管形部署必须显式开 `REQUIRE_PRINCIPAL`。返回拒因;`undefined` = 通过。
|
|
119
|
-
*
|
|
120
|
-
* 判据与文案的**唯一属主**:boot 期的拒启 throw(`config.ts` 的跨域不变量区)与
|
|
121
|
-
* `CONFIG_CROSS_INVARIANTS` 表(`config-invariants.ts`)**调同一只**,不各抄一份([ref] 的纪律 +
|
|
122
|
-
* [ref]「禁手抄投影」)。
|
|
123
|
-
*
|
|
124
|
-
* 🔴 **表里那一行今天是「预置纵深」,不是在岗执法**(主会话裁定⑤,撤回初版那句「热路径同样在岗」):
|
|
125
|
-
* `checkCrossInvariants` 的候选视图(`config-hot` 那条腿)今天只覆写 limits 那 14 员键,本判据读的
|
|
126
|
-
* 5 个键**一个都不在里面** ⇒ 那一行在热路径上**永不返回拒因**。它先在场是纵深(哪天候选视图覆盖到
|
|
127
|
-
* auth 域,它自己就活),不是现在有人在执法。**假覆盖比没覆盖更坏**(7.94.0 自己刚立的反面规矩)。
|
|
128
|
-
*
|
|
129
|
-
* 🔴 **为什么是拒启而不是 warn**:`principal` 缺席在本仓走 `"default"` 赦免 —— 一台没开这根旋钮的
|
|
130
|
-
* 多用户机器会把**全服无主会话**列进 `ListAgents`,并且把 hooks / shellEnv / cwd / 根围栏拓宽四条
|
|
131
|
-
* 能力授予面一并对每个调用方打开(四条面的谓词各不相同,逐条在模块头注 —— 别照「都一样」读)。
|
|
132
|
-
* 安全轴上的 fail-open 不许有安静的兜底臂([ref])。
|
|
133
|
-
*
|
|
134
|
-
* 🔴 **`settings.env` 那一条的射程要说准**(亲读 `plugins/remote-env-host.ts` 的 `mergeEnv`):适配器的
|
|
135
|
-
* `env` 是**并**到 `process.env` 上的逐键覆盖(不是替),而 `task-settings.ts` 的 `cleanEnvMap` 只判
|
|
136
|
-
* 类型/键长/值长/条数、**没有任何键名拒集** ⇒ `PATH` / `LD_PRELOAD` / `NODE_OPTIONS` 一视同仁。
|
|
137
|
-
* 那条面唯一的防线就是上面那道闸;闸一漂,面全开。
|
|
138
|
-
*/
|
|
139
|
-
export declare function hostedPrincipalRefusal(c: HostedPostureInput): string | undefined;
|
|
140
96
|
/** 腿② 读的键集 = 射程判据 + 天花板旋钮。
|
|
141
97
|
* 🔴 档位的**词表属主在 `config-types.ts`**(`TASK_WRITE_FACE_ESCALATIONS`)—— 本模块刻意**不留第二份**:
|
|
142
98
|
* 施工期真犯过一次(两处各写一份 `["allow","refuse"]`,加词的那天两边会分家),[ref] 第一排查点。 */
|
package/dist/hosted-posture.js
CHANGED
|
@@ -1,45 +1,21 @@
|
|
|
1
1
|
import { TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
|
|
2
2
|
import { deviceCwdHonored } from "./task-cwd.js";
|
|
3
|
-
export const HOSTED_EVIDENCE_WORDS = ["require-principal"
|
|
3
|
+
export const HOSTED_EVIDENCE_WORDS = ["require-principal"];
|
|
4
4
|
export function buildHostedPosture(c) {
|
|
5
5
|
const evidence = [];
|
|
6
6
|
if (c.requirePrincipal === true)
|
|
7
7
|
evidence.push("require-principal");
|
|
8
|
-
if (c.principalJwtPubkeys.length > 0 || c.principalJwtIss !== undefined || c.principalJwtAud !== undefined)
|
|
9
|
-
evidence.push("principal-jwt");
|
|
10
|
-
if (c.authBridgeIssuer !== undefined)
|
|
11
|
-
evidence.push("auth-bridge-issuer");
|
|
12
8
|
return { hosted: evidence.length > 0, evidence };
|
|
13
9
|
}
|
|
14
|
-
export const HOSTED_EVIDENCE_ENV_KEYS = ["PRINCIPAL_JWT_PUBKEYS", "PRINCIPAL_JWT_ISS", "PRINCIPAL_JWT_AUD", "AUTH_BRIDGE_ISSUER"];
|
|
15
|
-
export function clearHostedEvidenceEnv() {
|
|
16
|
-
delete process.env.PRINCIPAL_JWT_PUBKEYS;
|
|
17
|
-
delete process.env.PRINCIPAL_JWT_ISS;
|
|
18
|
-
delete process.env.PRINCIPAL_JWT_AUD;
|
|
19
|
-
delete process.env.AUTH_BRIDGE_ISSUER;
|
|
20
|
-
}
|
|
21
10
|
export function hostedEvidenceNote(e) {
|
|
22
11
|
switch (e) {
|
|
23
12
|
case "require-principal":
|
|
24
13
|
return "REQUIRE_PRINCIPAL=true (this deployment declares itself multi-tenant)";
|
|
25
|
-
case "principal-jwt":
|
|
26
|
-
return "PRINCIPAL_JWT_PUBKEYS/ISS/AUD is configured (principal identities are verified against an external issuer — a single-user turnkey box has no issuer)";
|
|
27
|
-
case "auth-bridge-issuer":
|
|
28
|
-
return "AUTH_BRIDGE_ISSUER is set explicitly (a second external SSO-JWT issuer; the registry-URL fallback is deliberately NOT read here — that fact is fleet management, not tenancy)";
|
|
29
14
|
}
|
|
30
15
|
}
|
|
31
16
|
function hostedBecause(facts) {
|
|
32
17
|
return facts.evidence.map(hostedEvidenceNote).join("; ");
|
|
33
18
|
}
|
|
34
|
-
export function hostedPrincipalRefusal(c) {
|
|
35
|
-
const facts = buildHostedPosture(c);
|
|
36
|
-
if (!facts.hosted || c.requirePrincipal === true)
|
|
37
|
-
return undefined;
|
|
38
|
-
return (`this deployment looks HOSTED/multi-tenant (${hostedBecause(facts)}) but REQUIRE_PRINCIPAL is not set — ` +
|
|
39
|
-
`an absent principal takes the single-user "default" amnesty, which would list every ownerless session to every caller ` +
|
|
40
|
-
`and open the single-user capability grants (settings.hooks / settings.env / cwd / additionalDirectories) to all of them. ` +
|
|
41
|
-
`Set REQUIRE_PRINCIPAL=true (and OPERATOR_PRINCIPALS for the operator-only routes), or remove the hosted-deployment configuration if this really is a single-user box.`);
|
|
42
|
-
}
|
|
43
19
|
export const HOST_POSTURE_REFUSED_CODE = "request.host_posture_refused";
|
|
44
20
|
function hostPostureDeclarationsHonored(c) {
|
|
45
21
|
if (deviceCwdHonored(c))
|
|
@@ -13,15 +13,10 @@
|
|
|
13
13
|
* (`gate_not_tool_approval`/`gate_not_resumable`/`wake.gate_pending` 的指路句)。三处文案与本表若漂移,
|
|
14
14
|
* test/session-active-conflict-materials.test.ts 的分门用例会红。认不出的 kind ⇒ null(诚实缺席,不铸假门)。
|
|
15
15
|
*/
|
|
16
|
-
import { type
|
|
16
|
+
import { type GovernancePosture } from "../runtime-governance.js";
|
|
17
17
|
import type { ShellGateDoctrine } from "@sema-agent/core";
|
|
18
18
|
import { type RunRecord } from "../plugins/store-contracts.js";
|
|
19
19
|
import { type PendingRowBinding } from "../plugins/checkpoint-store-sql.js";
|
|
20
|
-
/** [ref] 归因的部署侧入参(两个治理旋钮;判据属主见 `governanceMandatesShellGateAlways`)。 */
|
|
21
|
-
export interface GovernancePosture {
|
|
22
|
-
autonomy?: Autonomy;
|
|
23
|
-
manualModeShellGate?: "always" | "classify";
|
|
24
|
-
}
|
|
25
20
|
/** 与 runs.ts/tasks.ts 三个 409 位共享的旧文案(byte-frozen:api-error-text-freeze 门认这句)。 */
|
|
26
21
|
export declare const ACTIVE_RUN_CONFLICT_BASE_TEXT = "session already has an active run \u2014 POST /v1/runs/{activeTaskId}/cancel stops it (same-instance interactive runs abort immediately)";
|
|
27
22
|
/**
|
|
@@ -123,11 +118,6 @@ export declare function resumeEntryForGate(kind: string, ids: {
|
|
|
123
118
|
sessionId: string;
|
|
124
119
|
taskId: string;
|
|
125
120
|
}): string | null;
|
|
126
|
-
export declare function governanceOriginOf(gate: {
|
|
127
|
-
riskDescriptor?: {
|
|
128
|
-
shellGateDoctrine?: ShellGateDoctrine;
|
|
129
|
-
};
|
|
130
|
-
} | undefined, governance: GovernancePosture | undefined): true | undefined;
|
|
131
121
|
/** token 泛型:真身是 branded CheckpointToken(秘密能力,只在本函数内部流转,绝不进响应体)。 */
|
|
132
122
|
/**
|
|
133
123
|
* SSE 车道的 done 帧 result(拒绝形)。与 409 body **同一铸体处** —— 此前 tasks.ts 在 res.write 里
|
|
@@ -177,7 +167,7 @@ interface ConflictProbeDeps<TToken> {
|
|
|
177
167
|
findPendingTokenBySession?: (sessionId: string, scope?: string, binding?: PendingRowBinding) => Promise<TToken | null | undefined>;
|
|
178
168
|
/** 结构形(不 import core 的 `Checkpoint`):只声明本模块**真读**的几格。`riskDescriptor` 是
|
|
179
169
|
* core 在 mint 时挂到升级门上的判读元数据(display/triage only),`shellGateDoctrine` 是 2026-08-05
|
|
180
|
-
* 裁定加的取证格 ——
|
|
170
|
+
* 裁定加的取证格 —— 见 `runtime-governance.ts` 的 `governanceOriginOf` 推导论证。`checkpointId`([ref],core 5.42.0)
|
|
181
171
|
* 是行的非秘密稳定身份,可选 —— legacy 行没有它(见 `PendingGateMaterial.checkpointId`)。 */
|
|
182
172
|
get?: (token: TToken) => Promise<{
|
|
183
173
|
checkpointId?: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { governanceOriginOf } from "../runtime-governance.js";
|
|
2
2
|
import { isParkedRunStatus } from "../plugins/store-contracts.js";
|
|
3
3
|
import { LIVE_RUN_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
4
4
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
@@ -27,11 +27,6 @@ function safeCheckpointIdOf(raw, token) {
|
|
|
27
27
|
return undefined;
|
|
28
28
|
return raw;
|
|
29
29
|
}
|
|
30
|
-
export function governanceOriginOf(gate, governance) {
|
|
31
|
-
if (gate?.riskDescriptor?.shellGateDoctrine !== "always")
|
|
32
|
-
return undefined;
|
|
33
|
-
return governanceMandatesShellGateAlways(governance ?? {}) ? true : undefined;
|
|
34
|
-
}
|
|
35
30
|
export function toDoneFrameResult(body) {
|
|
36
31
|
return {
|
|
37
32
|
status: "failed",
|
package/dist/http/admission.js
CHANGED
|
@@ -7,6 +7,7 @@ import { singleMemberOffFieldIssue } from "../request-value-closure.js";
|
|
|
7
7
|
import { selfOrchestrationDenial } from "../task-workflow.js";
|
|
8
8
|
import { centerEntitlementSourceWired } from "../runtime-caps-resolver.js";
|
|
9
9
|
import { parseHooksConfig } from "../hooks/hook-runner.js";
|
|
10
|
+
import { judgeRequestWebSearch } from "../plugins/web-search.js";
|
|
10
11
|
import { normalizeApproachNotice, unknownTaskAgentModelRef, validateTaskAgents } from "../spec-fields.js";
|
|
11
12
|
import { isValidCwd, MAX_ADDITIONAL_DIRS, MAX_CWD_CHARS } from "../task-cwd.js";
|
|
12
13
|
import { verifyRoundsFromBody } from "./verify-rounds.js";
|
|
@@ -193,6 +194,13 @@ export function createAdmission(ctx) {
|
|
|
193
194
|
return null;
|
|
194
195
|
}
|
|
195
196
|
}
|
|
197
|
+
{
|
|
198
|
+
const webSearchVerdict = judgeRequestWebSearch(st.webSearch);
|
|
199
|
+
if (webSearchVerdict.kind === "malformed") {
|
|
200
|
+
sendError(res, 400, "request.field_invalid", webSearchVerdict.issue);
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
196
204
|
}
|
|
197
205
|
const skillsErr = validateUserSkills(body.skills);
|
|
198
206
|
if (skillsErr) {
|
|
@@ -29,7 +29,7 @@ export declare function createResumeLegs(ctx: {
|
|
|
29
29
|
routeCtxBase: RouteCtxBase;
|
|
30
30
|
resumeCheckpoint: (sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding, onResumeCommitted?: (grant: {
|
|
31
31
|
overrideSessionId?: string;
|
|
32
|
-
|
|
32
|
+
rememberableToolName: string | null;
|
|
33
33
|
}) => Promise<void>, acceptEarly?: boolean, decider?: {
|
|
34
34
|
principal?: string;
|
|
35
35
|
explicitOperator: boolean;
|
package/dist/http/resume-legs.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { redactErrorMessage, redactHead } from "../observability/run-terminal.js";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, validatePendingSteer, GLOBAL_USAGE_KEY, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
3
|
+
import { uuidv7, resumeWithVerification, CheckpointError, defaultTaskRegistry, taskNotificationDedupKey, validatePendingSteer, GLOBAL_USAGE_KEY, PAUSE_REGISTRY } from "@sema-agent/core";
|
|
4
4
|
import { decideParkedAgent, denyWorkflowParksUnreadable, findParkOrigin, planWorkflowParkedDecide } from "../parked-decide.js";
|
|
5
5
|
import { HttpError, decodeCheckpointScope, CHECKPOINT_PUBLIC_SCOPE } from "../security.js";
|
|
6
6
|
import { evictIfConflict, parkToolCallId, TurnAnchorCapture, HEARTBEAT_MS, markChildrenStoppedByUserOnAbort } from "../runs.js";
|
|
@@ -9,10 +9,10 @@ import { mintCancelledResult } from "../run-cancel-context.js";
|
|
|
9
9
|
import {} from "../host-decision.js";
|
|
10
10
|
import { readTurnActivityMs, recordTurnActivity } from "../turn-activity.js";
|
|
11
11
|
import { recordResumeResourceWindow } from "../resource-window.js";
|
|
12
|
-
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry,
|
|
12
|
+
import { emitPendingWorkflowCompletions, taskNotificationInboxEntry, taskNotificationStreamKeyOf, NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
13
13
|
import { fleetRunPublisher, fleetRunLabels, fleetRunResiduals, isFleetAgentTerminalNotification } from "../fleet/fleet-bus.js";
|
|
14
14
|
import { defaultSubagentTailBus } from "../fleet/subagent-tail-bus.js";
|
|
15
|
-
import { APPROVAL_GATE_KINDS, createApprovalCardEmitter, isApprovalGateKind, resolveApprovalLeg, resolveStreamApprovalGate } from "../tool-approval.js";
|
|
15
|
+
import { APPROVAL_GATE_KINDS, createApprovalCardEmitter, isApprovalGateKind, parkRowStandingAnswerFacts, resolveApprovalLeg, resolveStreamApprovalGate, standingAnswerMayRetire } from "../tool-approval.js";
|
|
16
16
|
import { buildCheckpointAnchorHash } from "../approval-ask-audit-store.js";
|
|
17
17
|
import { TERMINAL_PARK_GATE_KINDS } from "../plugins/checkpoint-store-sql.js";
|
|
18
18
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
@@ -198,11 +198,14 @@ export function createResumeLegs(ctx) {
|
|
|
198
198
|
};
|
|
199
199
|
}
|
|
200
200
|
const paForGrant = cp.pendingAction.kind === "tool_approval" ? cp.pendingAction : undefined;
|
|
201
|
-
const
|
|
201
|
+
const rememberableTool = paForGrant !== undefined &&
|
|
202
|
+
paForGrant.toolName &&
|
|
203
|
+
paForGrant.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
204
|
+
standingAnswerMayRetire(parkRowStandingAnswerFacts(paForGrant, cp.gate, deps.config))
|
|
202
205
|
? paForGrant.toolName
|
|
203
206
|
: null;
|
|
204
207
|
const boundGrant = onResumeCommitted
|
|
205
|
-
? (overrideSessionId) => onResumeCommitted({ ...(overrideSessionId !== undefined ? { overrideSessionId } : {}),
|
|
208
|
+
? (overrideSessionId) => onResumeCommitted({ ...(overrideSessionId !== undefined ? { overrideSessionId } : {}), rememberableToolName: rememberableTool })
|
|
206
209
|
: undefined;
|
|
207
210
|
const lateSettleSink = deps.approvalLateSettle;
|
|
208
211
|
const lateSettleAnchor = lateSettleSink !== undefined && req !== undefined && token
|
|
@@ -496,8 +499,9 @@ export function createResumeLegs(ctx) {
|
|
|
496
499
|
const { type, ...rest } = frame;
|
|
497
500
|
const f = frame;
|
|
498
501
|
const write = rs0.appendEvent(taskId, drainSeq++, type, rest);
|
|
499
|
-
|
|
500
|
-
|
|
502
|
+
const fKey = f.type === "task_notification" ? taskNotificationStreamKeyOf(f) : undefined;
|
|
503
|
+
if (fKey !== undefined)
|
|
504
|
+
resumeNotifiedKeys.set(fKey, write.then(() => true, () => false));
|
|
501
505
|
return write;
|
|
502
506
|
}, { route: "resume-open", connection: "durable-append", log: (m, x) => deps.logger?.info?.(m, x) });
|
|
503
507
|
}
|
|
@@ -544,7 +548,7 @@ export function createResumeLegs(ctx) {
|
|
|
544
548
|
void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(sessionId, principal ?? null, n, Date.now(), taskId, opts?.priority)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "resume-verify", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
|
|
545
549
|
}
|
|
546
550
|
else if (resumeLegLive) {
|
|
547
|
-
const key =
|
|
551
|
+
const key = taskNotificationDedupKey(n);
|
|
548
552
|
if (resumeNotifiedKeys.get(key) === undefined) {
|
|
549
553
|
resumeNotifiedKeys.set(key, vAppend("task_notification", taskNotificationEventData({ notification: n, ...(opts?.priority !== undefined ? { priority: opts.priority } : {}) })).then(() => true, () => false));
|
|
550
554
|
}
|
|
@@ -671,7 +675,7 @@ export function createResumeLegs(ctx) {
|
|
|
671
675
|
void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(sessionId, principal ?? null, n, Date.now(), taskId, opts?.priority)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "resume", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
|
|
672
676
|
}
|
|
673
677
|
else if (resumeLegLive) {
|
|
674
|
-
const key =
|
|
678
|
+
const key = taskNotificationDedupKey(n);
|
|
675
679
|
if (resumeNotifiedKeys.get(key) === undefined) {
|
|
676
680
|
resumeNotifiedKeys.set(key, append("task_notification", taskNotificationEventData({ notification: n, ...(opts?.priority !== undefined ? { priority: opts.priority } : {}) })).then(() => true, () => false));
|
|
677
681
|
}
|
|
@@ -780,7 +784,7 @@ export function createResumeLegs(ctx) {
|
|
|
780
784
|
case "task_notification": {
|
|
781
785
|
await flush();
|
|
782
786
|
const tnr = ev.notification;
|
|
783
|
-
const tnrKey =
|
|
787
|
+
const tnrKey = taskNotificationStreamKeyOf(tnr);
|
|
784
788
|
const tnrPrior = tnrKey !== undefined ? resumeNotifiedKeys.get(tnrKey) : undefined;
|
|
785
789
|
if (tnrPrior === undefined || !(await tnrPrior)) {
|
|
786
790
|
const write = append("task_notification", taskNotificationEventData(ev));
|
package/dist/http/route-ctx.d.ts
CHANGED
|
@@ -357,11 +357,11 @@ export interface RouteLegs {
|
|
|
357
357
|
resumeCheckpoint(sessionId: string, decision: "approve" | "deny", reason: string | undefined, hostDecision: HostDecision, req?: IncomingMessage, answer?: QuestionAnswer, binding?: DecideBinding,
|
|
358
358
|
/** [ref] codex R4(验真后修):remember 豁免的 grant 挂点。**工具名不再由路由自己读**(那是与本函数
|
|
359
359
|
* 载入行分歧的第三次 scope-less 读,三读分歧下会给他人工具落豁免=提权)——由本函数把**已属主校验的
|
|
360
|
-
* 载入 cp**
|
|
360
|
+
* 载入 cp** 的可记工具名(`rememberableToolName`,非 tool_approval / AskUserQuestion / legacy null / S-663 强制卡 ⇒ `null`
|
|
361
361
|
* = 无可记)一并回传,路由只对这个逐字落店。`overrideSessionId` = parked 腿的 root/host 会话 grant 锚。 */
|
|
362
362
|
onResumeCommitted?: (grant: {
|
|
363
363
|
overrideSessionId?: string;
|
|
364
|
-
|
|
364
|
+
rememberableToolName: string | null;
|
|
365
365
|
}) => Promise<void>,
|
|
366
366
|
/** [ref]:透传给 {@link DriveResumeArgs.acceptEarly}(200 受理语义)。HTTP `/decide` 腿传 `true`;
|
|
367
367
|
* 内部 D-D SLA deny-sweep 不传 —— 它没有在等响应的人,提前受理对它零收益而少一条同步失败面。 */
|
|
@@ -2,7 +2,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
|
|
|
2
2
|
import type { QuestionAnswer } from "@sema-agent/core";
|
|
3
3
|
import type { CheckpointStoreFull } from "../../plugins/store-backend.js";
|
|
4
4
|
import { type ToolApprovalCoordinator } from "../../tool-approval.js";
|
|
5
|
-
import { type GovernancePosture } from "
|
|
5
|
+
import { type GovernancePosture } from "../../runtime-governance.js";
|
|
6
6
|
import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
|
|
7
7
|
import { type KeyClosureIssue } from "../../request-key-closure.js";
|
|
8
8
|
/**
|
|
@@ -16,7 +16,7 @@ import { sleep } from "../sse-log.js";
|
|
|
16
16
|
import { bindSseLifecycle } from "../sse-lifecycle.js";
|
|
17
17
|
import { sendJson, sendError, sseHeaders, SSE_MAX_STREAM_MS, SSE_HEARTBEAT_IDLE_MS } from "../send.js";
|
|
18
18
|
import { gatedPrincipal, explicitOperatorOk, isOperator, sendPrincipalRefusal } from "../principal-gate.js";
|
|
19
|
-
import { governanceOriginOf } from "
|
|
19
|
+
import { governanceOriginOf } from "../../runtime-governance.js";
|
|
20
20
|
import { sendResumeOutcome } from "../route-ctx.js";
|
|
21
21
|
import { assertNever } from "../route-ctx.js";
|
|
22
22
|
import { readResumeCtx, resumeCtxCorruptAnswer } from "../resume-ctx-read.js";
|
|
@@ -689,7 +689,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
689
689
|
let pendingGrant;
|
|
690
690
|
const grantOnCommit = remember && deps.approvalExemptionStore
|
|
691
691
|
? async (grant) => {
|
|
692
|
-
const canonical = grant.
|
|
692
|
+
const canonical = grant.rememberableToolName;
|
|
693
693
|
if (!canonical)
|
|
694
694
|
return;
|
|
695
695
|
pendingGrant = { sessionId: grant.overrideSessionId ?? sessionId, toolName: canonical };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
3
|
import { erasureInputSmugglesPrototypeKey } from "../../memory-operator-faces.js";
|
|
4
|
+
import { MemoryLayerUnwritableError } from "../../memory-layer-preflight.js";
|
|
4
5
|
import { sendJson, sendError } from "../send.js";
|
|
5
6
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
6
7
|
import { assertNever } from "../route-ctx.js";
|
|
@@ -35,6 +36,10 @@ function codeOf(err) {
|
|
|
35
36
|
return typeof code === "string" ? code : undefined;
|
|
36
37
|
}
|
|
37
38
|
function sentClassifiedFailure(res, ctx, face, err) {
|
|
39
|
+
if (face === "erase" && err instanceof MemoryLayerUnwritableError) {
|
|
40
|
+
sendError(res, 409, "memory.layer_unwritable", `memory layer directories are not writable on disk: ${err.dirList} — refused before the engine was called: nothing was written and no journal was left. Restore the owner write bit with \`chmod u+w ${err.chmodTargets}\` and retry. This is a known issue of the memory engine's mount: it leaves a layer that a session reads but does not write read-only on disk (directories 0555, entries 0444) and does not restore it, so a later project session can lock it again`);
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
38
43
|
const coreCode = codeOf(err);
|
|
39
44
|
if (coreCode !== undefined && sentCoreCode(res, coreCode, err instanceof Error ? err.message : String(err)))
|
|
40
45
|
return true;
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { ControlPlaneCorruptError } from "@sema-agent/core";
|
|
3
3
|
import { sendJson, sendError } from "../send.js";
|
|
4
4
|
import { buildOriginFence, readOriginAware } from "../../memory-origin-fence.js";
|
|
5
|
+
import { MemoryLayerUnwritableError } from "../../memory-layer-preflight.js";
|
|
5
6
|
import { gatedPrincipal, explicitOperatorOk, sendPrincipalRefusal } from "../principal-gate.js";
|
|
6
7
|
import { assertNever } from "../route-ctx.js";
|
|
7
8
|
export const MEMORY_ORIGIN_EXTERNAL_PATH = "/v1/memory/origin/external";
|
|
@@ -47,6 +48,10 @@ function codeOf(err) {
|
|
|
47
48
|
return typeof code === "string" ? code : undefined;
|
|
48
49
|
}
|
|
49
50
|
function sentClassifiedFailure(res, ctx, face, err) {
|
|
51
|
+
if (face === "clear" && err instanceof MemoryLayerUnwritableError) {
|
|
52
|
+
sendError(res, 409, "memory.layer_unwritable", `memory layer directories are not writable on disk: ${err.dirList} — refused before the engine was called: nothing was written and no journal was left. Restore the owner write bit with \`chmod u+w ${err.chmodTargets}\` and retry. This is a known issue of the memory engine's mount: it leaves a layer that a session reads but does not write read-only on disk (directories 0555, entries 0444) and does not restore it, so a later project session can lock it again`);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
50
55
|
if (face === "clear") {
|
|
51
56
|
const coreCode = codeOf(err);
|
|
52
57
|
if (coreCode !== undefined && sentClearCode(res, coreCode, err instanceof Error ? err.message : String(err)))
|