@sema-agent/server 7.98.0 → 7.99.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 +6 -0
- package/README.md +1 -1
- package/README.zh-CN.md +1 -1
- package/USAGE.md +12 -9
- package/deploy/sema-up/lib/envf.sh +110 -42
- package/deploy/sema-up/sema-up.sh +76 -34
- package/dist/boot/config-center.js +167 -22
- package/dist/boot/org-memory.d.ts +2 -1
- package/dist/boot/org-memory.js +9 -5
- package/dist/boot/runtime-caps.js +4 -4
- package/dist/capabilities/center-prompts.d.ts +3 -0
- package/dist/capabilities/center-prompts.js +1 -1
- package/dist/center-credential.d.ts +112 -0
- package/dist/center-credential.js +131 -0
- package/dist/config-catalog.js +3 -2
- package/dist/config-center/center-request.d.ts +60 -0
- package/dist/config-center/center-request.js +71 -0
- package/dist/config-center/http-client.d.ts +17 -9
- package/dist/config-center/http-client.js +27 -36
- package/dist/config-center/skills-mcp.d.ts +4 -1
- package/dist/config-center/skills-mcp.js +2 -2
- 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 +8 -4
- package/dist/config.js +11 -13
- 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/hosted-posture.d.ts +49 -93
- package/dist/hosted-posture.js +1 -25
- package/dist/observability/secret-env-scrub.d.ts +2 -26
- package/dist/observability/secret-env-scrub.js +2 -2
- package/dist/plugins/remote-env-host.d.ts +3 -1
- package/dist/plugins/remote-env-host.js +3 -2
- package/dist/project-memory.d.ts +28 -1
- package/dist/project-memory.js +22 -22
- package/dist/run-local.js +0 -2
- package/dist/runtime-caps-resolver.d.ts +8 -8
- package/dist/runtime-caps-resolver.js +16 -14
- package/dist/sealed-key.js +3 -7
- package/dist/server-secret-env.d.ts +41 -0
- package/dist/server-secret-env.js +18 -0
- package/dist/tool-approval.d.ts +48 -1
- package/dist/tool-approval.js +10 -3
- package/package.json +4 -3
package/dist/sealed-key.js
CHANGED
|
@@ -3,6 +3,8 @@ import { chmod, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import _sodium from "libsodium-wrappers";
|
|
5
5
|
import { buildDisplaySafeUrl } from "./trace/redact.js";
|
|
6
|
+
import { createCenterCredentialSource } from "./center-credential.js";
|
|
7
|
+
import { centerFetch } from "./config-center/center-request.js";
|
|
6
8
|
export const SEALED_KEY_ALG = "libsodium-sealedbox-x25519";
|
|
7
9
|
const SEAL_OVERHEAD_BYTES = 48;
|
|
8
10
|
const BASE64_STRICT = /^[A-Za-z0-9+/]+={0,2}$/;
|
|
@@ -114,13 +116,7 @@ export async function reportExecutionPublicKey(opts) {
|
|
|
114
116
|
return;
|
|
115
117
|
}
|
|
116
118
|
try {
|
|
117
|
-
const
|
|
118
|
-
const res = await doFetch(`${baseUrl.replace(/\/$/, "")}/api/config/execution-keys`, {
|
|
119
|
-
method: "POST",
|
|
120
|
-
headers: { authorization: `Bearer ${adminToken}`, "content-type": "application/json" },
|
|
121
|
-
body: JSON.stringify({ publicKey: key.publicKey, publicKeyId: key.publicKeyId, ...(notes ? { notes } : {}) }),
|
|
122
|
-
signal: AbortSignal.timeout(10_000),
|
|
123
|
-
});
|
|
119
|
+
const { res } = await centerFetch({ baseUrl, credential: createCenterCredentialSource({ from: "env", token: adminToken }, undefined) }, { kind: "execution-keys" }, { method: "POST", json: { publicKey: key.publicKey, publicKeyId: key.publicKeyId, ...(notes ? { notes } : {}) }, timeoutMs: 10_000, ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}) });
|
|
124
120
|
if (res.ok) {
|
|
125
121
|
let idempotent = false;
|
|
126
122
|
try {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 7.99.0 合并复审 F7(web-admin [ref] 实测;安全轴;DEBTS S-684)—— 子进程 env 交出去之前的**本仓**密钥剥除。
|
|
3
|
+
*
|
|
4
|
+
* ## 病
|
|
5
|
+
* 本仓两条子进程腿 —— 模型可驱动的 host shell(`inheritEnv:"scrub"` 缺省腿,`plugins/remote-env-host.ts`)与
|
|
6
|
+
* project-memory 的 git 子进程(`project-memory.ts`)—— 此前只调 core 的 `scrubSecretEnv`。它按键名**形状**判
|
|
7
|
+
* (`*_KEY` / `*_TOKEN` / `*_SECRET` …),而本仓自己的凭据键里有形状规则认不出的:`SERVICE_AUTH_TOKENS`(复数,
|
|
8
|
+
* per-system 凭据表)、`APPROVAL_HMAC_KEYS`(直连门 HMAC 密钥集)、`OTEL_EXPORTER_OTLP_HEADERS`(常载鉴权头)⇒
|
|
9
|
+
* 原样进子进程,模型选的一句 `printenv` 就读得到。
|
|
10
|
+
*
|
|
11
|
+
* ## 规则(一句)
|
|
12
|
+
* 本仓**知道**哪些 env 键是凭据 —— 配置目录(`config-catalog.ts`)里 `type:"secret"` 的每一行,从目录机器派生,
|
|
13
|
+
* 不手抄第二份名单 —— 这些键按名剥;其余照旧交 core 按形剥。{@link scrubServerSecrets} = 目录派生剥除 ∘ core
|
|
14
|
+
* `scrubSecretEnv`,两条腿都只调它。core 形状规则的缺口由 core 源头修(另案);本层不改 core 的判据,也不自造
|
|
15
|
+
* 第二份形状规则。目录里加一个 secret 型键,两条腿当场就剥它。
|
|
16
|
+
*
|
|
17
|
+
* ## 不变的面
|
|
18
|
+
* `inheritEnv:"all"`(显式全继承)与显式 allowlist 两形不经本函数,行为逐字不变;调用方显式传入的 env
|
|
19
|
+
* (`cfg.env` / 单次命令的 `env`)照旧原样覆盖在剥除结果之上。
|
|
20
|
+
*/
|
|
21
|
+
import { type SecretEnvFinding } from "@sema-agent/core";
|
|
22
|
+
/** 目录派生臂的一条剥除记录(形同 core 的 `SecretEnvFinding`:键名 / 规则 / 置信度 / 来源;值永不携带)。
|
|
23
|
+
* `source` 用本仓自己的词 `server-config-catalog`,与 core 的 `secret-env` 分开 —— 读观测面的人要分得清
|
|
24
|
+
* 「形状规则剥的」与「本仓目录点名剥的」(后者多出来的那部分正是 core 形状规则的缺口)。 */
|
|
25
|
+
export interface CatalogSecretEnvFinding {
|
|
26
|
+
readonly key: string;
|
|
27
|
+
readonly kind: "config-catalog";
|
|
28
|
+
readonly confidence: "high";
|
|
29
|
+
readonly source: "server-config-catalog";
|
|
30
|
+
}
|
|
31
|
+
/** 本仓两条子进程腿的剥除记录:core 的形状臂 ∪ 本仓的目录臂。 */
|
|
32
|
+
export type ServerSecretEnvFinding = SecretEnvFinding | CatalogSecretEnvFinding;
|
|
33
|
+
/** 配置目录里 `type:"secret"` 的 env 名(大写 —— Windows 的 env 名不分大小写,按大写比才不会漏)。 */
|
|
34
|
+
export declare const CATALOG_SECRET_ENV_NAMES: ReadonlySet<string>;
|
|
35
|
+
/**
|
|
36
|
+
* 先交 core 按形剥,再按目录名剥它漏下的。`findings` 是调用方持有的收集器(交给 `recordSecretEnvScrub` 留痕):
|
|
37
|
+
* 形状臂剥掉的记 core 的 finding;目录臂只记**形状臂漏下、被目录点名剥掉**的键 —— 于是既有的形状臂计数不变,
|
|
38
|
+
* 目录臂的计数恰好就是 core 形状规则的缺口读数。
|
|
39
|
+
*/
|
|
40
|
+
export declare function scrubServerSecrets(env: NodeJS.ProcessEnv, findings: ServerSecretEnvFinding[]): NodeJS.ProcessEnv;
|
|
41
|
+
//# sourceMappingURL=server-secret-env.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { scrubSecretEnv } from "@sema-agent/core";
|
|
2
|
+
import { CONFIG_CATALOG } from "./config-catalog.js";
|
|
3
|
+
export const CATALOG_SECRET_ENV_NAMES = new Set(CONFIG_CATALOG.filter((spec) => spec.type === "secret").map((spec) => spec.name.toUpperCase()));
|
|
4
|
+
export function scrubServerSecrets(env, findings) {
|
|
5
|
+
const shapeFindings = [];
|
|
6
|
+
const shaped = scrubSecretEnv(env, shapeFindings);
|
|
7
|
+
findings.push(...shapeFindings);
|
|
8
|
+
const out = {};
|
|
9
|
+
for (const key of Object.keys(shaped)) {
|
|
10
|
+
if (CATALOG_SECRET_ENV_NAMES.has(key.toUpperCase())) {
|
|
11
|
+
findings.push({ key, kind: "config-catalog", confidence: "high", source: "server-config-catalog" });
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
out[key] = shaped[key];
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=server-secret-env.js.map
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -76,6 +76,50 @@ export type ParkedAskRedeem = (req: ParkedAskRedeemRequest) => Promise<{
|
|
|
76
76
|
status: number;
|
|
77
77
|
body: Record<string, unknown>;
|
|
78
78
|
}>;
|
|
79
|
+
/**
|
|
80
|
+
* 一问身上与「站着的答案」有关的三个事实位 —— {@link standingAnswerMayRetire} 的全部入参。
|
|
81
|
+
*
|
|
82
|
+
* 🔴 **三格全必填布尔**,刻意不做 `?: true` 的「只记真」形:判据哪天多一位,每个读者都要当场编译红,
|
|
83
|
+
* 而不是漏传一格被静默读成 `false`(那是安全轴上的 fail-open)。读者各自从自己手里的形折成这三格:
|
|
84
|
+
* live 读侧取 `req` + 本地治理 peek,live 写侧取 pending 条目。本型**不绑** live 条目 —— 耐久 `/decide`
|
|
85
|
+
* 腿候 core 给 `PendingAction` 补 `mandated` 孪生位后按同一形接入(见 S-663;今天行上只有
|
|
86
|
+
* `requiresRealApproval` 孪生与依赖车道的 `ruleOffersAbsence`,缺一位不先接两位)。
|
|
87
|
+
*/
|
|
88
|
+
export interface StandingAnswerFacts {
|
|
89
|
+
/** 本仓治理层(`AUTONOMY` / `commandPolicy` / shellGate / `SENSITIVE_WRITE_PATTERNS`)产的门。 */
|
|
90
|
+
readonly governanceForced: boolean;
|
|
91
|
+
/** core 闸站盖的「必须真人逐次批」位(连一揽子 `onAsk:"allow"` 席也不得答)。 */
|
|
92
|
+
readonly requiresRealApproval: boolean;
|
|
93
|
+
/** core 闸站盖的强制位([ref]):规则清不掉、站着的答案退不掉,只有对**这一次调用**的应答能答。 */
|
|
94
|
+
readonly mandated: boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* 🔴 **S-660 —— 会话级放行的唯一判据:站着的答案能不能退掉这一问。** 纯谓词,两个(将来三个)读者:
|
|
98
|
+
* · **读侧**(`askBroadcast` 的会话授权短路):`true` 才可能由既有 grant 代答,否则往下走真出卡;
|
|
99
|
+
* · **写侧**(`finishRespond` 的 `grantable`):`true` 才可能把这张卡上的 `allow_session` 铸成 grant,
|
|
100
|
+
* 否则只放行这一次、`rememberApplied:false`。
|
|
101
|
+
* 两臂读同一份事实、说同一句话:一问若不许被记住的放行答掉,它的应答也不许变成一条记住的放行 —— 否则
|
|
102
|
+
* 同一只卡「重跑一遍」就被自己上一次的回答代答(test [ref] U1s-off 的真形)。
|
|
103
|
+
*
|
|
104
|
+
* **为什么三位是同一类**(「站着的答案退不掉的一问」):会话授权 = **上一次**应答记下的一揽子放行,不是对
|
|
105
|
+
* **这一次**调用的应答 —— 与持久 allow 规则同族,只是更短命(`askBroadcast` 短路处的注早已这么定性)。
|
|
106
|
+
* · `mandated`:core 7.29.0 `PermissionResult` ask 臂 `mandated` 成员的文档「no persisted allow rule and no standing
|
|
107
|
+
* posture may retire it, only an answer to THIS call」;`ruleClearanceOf` 的文档「Allow rules silence the
|
|
108
|
+
* classifier's questions, never a mandated one」。core 09-23 澄清(将在 7.30.0 落进 d.ts):「从早先某次
|
|
109
|
+
* 回答记住的放行,**任何 scope、会话级也算**」都是站着的答案。它**不**绑这一次调用的应答者(人、hook、
|
|
110
|
+
* 部署的一揽子 `onAsk:"allow"` 席、mandate 表允许的活判定者)—— 那一半本仓不动(见 `mandated` 帧键顶注)。
|
|
111
|
+
* · `requiresRealApproval`([ref]):更强的那条,连一揽子席也不得答,当然也不许记住的放行答。
|
|
112
|
+
* · `governanceForced`([ref]):运维治理层的门,客户端表态掀不掉;持久规则车道早已同判(`buildRuleLaneMaterial`)。
|
|
113
|
+
* 本谓词**不重推** core 的 `ruleClearanceOf` / `ruleMayClear`(core 刻意不导出:闸外重算拿不到同一份标记快照,
|
|
114
|
+
* 会分叉),只读闸站**盖好**的两位 + 本仓自己的治理标。
|
|
115
|
+
*
|
|
116
|
+
* **为什么读侧的行复核(`sessionGrantUncontestedByRow`)不必为 `mandated` 加一次行读**:`mandated` 是 `req`
|
|
117
|
+
* **自己**的位 —— core 每一次过闸(含 failover / 重启后的重入)都由 `runGateLanes` 里的 `stampAskOrigin`
|
|
118
|
+
* 重新盖、经同一处的 `carryOf` 带上 `AskRequest`,读侧手里的 `req` 恒是这一次过闸的真值。`governanceForced`
|
|
119
|
+
* 需要行读,是因为它来自本仓的**进程内**治理标表:failover / 重启后幂等命中既有行时本地表恒空,行上的卡才
|
|
120
|
+
* 存着落库那一刻的判定。何况行上本来就没有 `mandated`(耐久面不造孪生座)—— 读了也读不到。
|
|
121
|
+
*/
|
|
122
|
+
export declare function standingAnswerMayRetire(facts: StandingAnswerFacts): boolean;
|
|
79
123
|
/** A live approval frame delivered to whoever tails this run's stream. `type` IS the SSE event name (named-event
|
|
80
124
|
* convention, same as question). The shell renders `tool_approval` as the CC three-choice card and dismisses on
|
|
81
125
|
* `tool_approval_complete`. */
|
|
@@ -150,7 +194,10 @@ export interface ToolApprovalFrame {
|
|
|
150
194
|
* 「不是强制」「老引擎」「这是一张从 park 行上重铸的耐久卡」三形 —— **耐久面上本仓不造孪生座**:core 的
|
|
151
195
|
* 寄存行(`PendingAction`)没有这一位(KL-997a 亲读登记),行上强制表现为 `ruleOffersAbsence:"mandated"`
|
|
152
196
|
* 且无 `ruleOffers`;在 `card_json` 侧自铸一个就是本仓成了这个事实的第二个源(而 park 那侧根本没有输入)。
|
|
153
|
-
* 🔴
|
|
197
|
+
* 🔴 **server 不判「是不是强制」**(判定属主在 core 的闸站),只**读**闸站盖好的这一位 —— 读者恰一个:
|
|
198
|
+
* S-660 起的会话级放行判据 {@link standingAnswerMayRetire}(会话授权是**上一次**应答记下的站着的答案,
|
|
199
|
+
* 不是对这一次调用的应答 ⇒ 强制卡上既不被既有 grant 代答、也不铸 grant)。上一段「一揽子 `onAsk:"allow"`
|
|
200
|
+
* 席照常答得了」说的是**这一次调用的应答者**,本仓不改它。
|
|
154
201
|
*/
|
|
155
202
|
mandated?: true;
|
|
156
203
|
/**
|
package/dist/tool-approval.js
CHANGED
|
@@ -79,6 +79,9 @@ function sessionAllowCategory(toolName) {
|
|
|
79
79
|
function sessionAllowKey(owner, sessionId, category) {
|
|
80
80
|
return JSON.stringify([owner, sessionId, category]);
|
|
81
81
|
}
|
|
82
|
+
export function standingAnswerMayRetire(facts) {
|
|
83
|
+
return !facts.governanceForced && !facts.requiresRealApproval && !facts.mandated;
|
|
84
|
+
}
|
|
82
85
|
export const PERSIST_RULE_TEXT_ERROR = `persistRule.rule must be a non-empty string of at most ${MAX_RULE_TEXT_CHARS} characters`;
|
|
83
86
|
export const PERSIST_RULE_EDITED_FLAG_ERROR = "persistRule.edited must be a boolean when present";
|
|
84
87
|
export const PERSIST_RULE_BATCH_INDEX_ERROR = "persistRule.batchOfferIndex must be a non-negative integer when present";
|
|
@@ -829,8 +832,7 @@ export class ToolApprovalCoordinator {
|
|
|
829
832
|
}
|
|
830
833
|
const primary = aliveCtxs[0];
|
|
831
834
|
const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
|
|
832
|
-
if (
|
|
833
|
-
req.requiresRealApproval !== true &&
|
|
835
|
+
if (standingAnswerMayRetire({ governanceForced, requiresRealApproval: req.requiresRealApproval === true, mandated: req.mandated === true }) &&
|
|
834
836
|
origin.sessionId &&
|
|
835
837
|
this.allowAllSessions.has(sessionAllowKey(origin.owner, origin.sessionId, sessionAllowCategory(req.toolName))) &&
|
|
836
838
|
(await this.sessionGrantUncontestedByRow(origin, req))) {
|
|
@@ -1385,6 +1387,7 @@ export class ToolApprovalCoordinator {
|
|
|
1385
1387
|
...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
|
|
1386
1388
|
...(effectiveGovernanceForced ? { governanceForced: true } : {}),
|
|
1387
1389
|
...(req.requiresRealApproval === true ? { requiresRealApproval: true } : {}),
|
|
1390
|
+
...(req.mandated === true ? { mandated: true } : {}),
|
|
1388
1391
|
...(askId !== undefined && batchId !== undefined ? { askId, batchId } : {}),
|
|
1389
1392
|
};
|
|
1390
1393
|
selfEntry = pendingEntry;
|
|
@@ -1962,7 +1965,11 @@ export class ToolApprovalCoordinator {
|
|
|
1962
1965
|
}
|
|
1963
1966
|
finishRespond(id, entry, parsed, opts) {
|
|
1964
1967
|
let rememberApplied;
|
|
1965
|
-
const grantable =
|
|
1968
|
+
const grantable = standingAnswerMayRetire({
|
|
1969
|
+
governanceForced: entry.governanceForced === true,
|
|
1970
|
+
requiresRealApproval: entry.requiresRealApproval === true,
|
|
1971
|
+
mandated: entry.mandated === true,
|
|
1972
|
+
});
|
|
1966
1973
|
if (parsed.value === "allow_session")
|
|
1967
1974
|
rememberApplied = Boolean(entry.sessionId) && grantable;
|
|
1968
1975
|
if (parsed.value === "allow_session" && entry.sessionId && grantable) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.99.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -38,10 +38,11 @@
|
|
|
38
38
|
"sema-up": "deploy/sema-up/sema-up.sh"
|
|
39
39
|
},
|
|
40
40
|
"engines": {
|
|
41
|
-
"node": ">=
|
|
41
|
+
"node": "^20.19.0 || >=22.12.0"
|
|
42
42
|
},
|
|
43
43
|
"scripts": {
|
|
44
44
|
"typecheck": "tsc --noEmit -p tsconfig.typecheck.json",
|
|
45
|
+
"typecheck:scripts": "tsc --noEmit -p tsconfig.scripts.json",
|
|
45
46
|
"build": "rm -rf dist && tsc -p tsconfig.build-js.json && tsc -p tsconfig.build-dts.json && node scripts/scrub-dist-refs.mjs dist",
|
|
46
47
|
"prepublishOnly": "npm run build && vitest run test/pack-hygiene.test.ts",
|
|
47
48
|
"start": "node dist/main.js",
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
},
|
|
62
63
|
"dependencies": {
|
|
63
64
|
"@sema-agent/core": "7.29.0",
|
|
64
|
-
"@sema-agent/settings-schema": "
|
|
65
|
+
"@sema-agent/settings-schema": "5.0.0",
|
|
65
66
|
"e2b": "^2.28.0",
|
|
66
67
|
"libsodium-wrappers": "^0.8.4",
|
|
67
68
|
"mysql2": "^3.22.4",
|