@sema-agent/server 7.61.0 → 7.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/README.zh-CN.md +2 -0
- package/USAGE.md +31 -0
- package/dist/boot/runner-deps.d.ts +2 -2
- package/dist/boot/runner-deps.js +1 -0
- package/dist/config-catalog.js +2 -0
- package/dist/config-center/skills-mcp.js +4 -2
- package/dist/config-center/types.d.ts +3 -0
- package/dist/config-provider.js +2 -0
- package/dist/config-types.d.ts +27 -2
- package/dist/config.js +83 -2
- package/dist/http/principal-gate.d.ts +9 -0
- package/dist/http/principal-gate.js +12 -0
- package/dist/http/routes/capabilities.js +2 -0
- package/dist/http/routes/diagnostics.js +7 -4
- package/dist/http/server.d.ts +7 -0
- package/dist/main.js +2 -0
- package/dist/mcp-source-label.d.ts +11 -0
- package/dist/mcp-source-label.js +14 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/task-mcp.js +2 -0
- package/dist/write-protection.d.ts +80 -0
- package/dist/write-protection.js +30 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -162,6 +162,8 @@ The server is configured entirely through environment variables. The most import
|
|
|
162
162
|
| `DEFAULT_SCENARIO` | `code` | Default scenario when the request body names none |
|
|
163
163
|
| `SANDBOX_PKG_SOURCE` | `global` | Package sources inside sandboxes: `global` (official upstreams) / `cn` (China mirrors) / `custom` / `none` |
|
|
164
164
|
| `SENSITIVE_WRITE_PATTERNS` | core's recommended set | Sensitive-path write deny list; comma-separated value replaces the set, `off` **or an empty value** disables. Applied unconditionally at the governance layer (independent of client permission mode, lane or settings presence) — including on the `run-local` leg. A value that cannot compile into a guard set (e.g. `/`, a pattern with no path segment) refuses to start |
|
|
165
|
+
| `WRITE_PROTECTED_EXTRA` | unset (engine default table) | **Adds** rows to core's write-protection table (the literal-name table whose hit demotes a surviving `allow` to `ask` on Write/Edit/NotebookEdit). Comma-separated bare names (a bare name matches ANY path segment; a name containing `/` matches a consecutive segment run) or a JSON array of `"name"` strings / `{name, kind}` rows (`kind`: `basename` \| `segment` \| `segment-run`). The value is composed as `[...WRITE_PROTECTED_DEFAULT_TABLE, …]`, so **no default row can be lost**. Unset = no seat is wired = the engine's default table is in force (this server never copies that table). Form discrimination is by content, not by first character: a value containing any JSON structural character (`[ ] { } "`) is parsed as JSON and **must** be a top-level array (a missing pair of brackets refuses to start instead of being split into junk bare names). An empty value, a malformed entry (glob metacharacter, unknown kind, kind/name mismatch) or setting this together with `WRITE_PROTECTED_TABLE_REPLACE` **refuses to start**. Read faces: `GET /v1/capabilities` → `writeProtection.{armed,rows,replaced}`; `GET /v1/diagnostics/wiring` → `writeProtection.{rows,source,droppedDefaultRows}` (operator-only) |
|
|
166
|
+
| `WRITE_PROTECTED_TABLE_REPLACE` | unset (engine default table) | **Replaces the whole** write-protection table (core's seat is whole-table by contract). JSON array only — deliberately no comma shorthand, because a slipped bare string would swap 51 default rows for one. `[]` = the explicit "no write-protection table at all" posture. Replacing logs one **loud** boot line naming every default row you dropped (`write_protection_table_replaced`; the empty posture logs `write_protection_table_disabled`) — use `WRITE_PROTECTED_EXTRA` when you meant to ADD. Same refuse-to-start conditions as its sibling, plus: both knobs set = two writers on one surface = refuses to start |
|
|
165
167
|
| `MANUAL_MODE_SHELL_GATE` | unset | `always`\|`classify` — tighten `Bash` into the approval chain, applied unconditionally at the governance layer (≥7.1.0: independent of client permission mode, lane, or settings presence). **Unset is not "off"**: since 7.12.0 the caller's explicit `permissionMode` supplies the baseline this knob tightens from (`bypassPermissions` → `off`, `auto`/`default`/`acceptEdits`/`plan` → `classify`; **no** mode stated → no gate, core's `off` default). This knob only ever raises that baseline — it has no relax half, so `off` is accepted as an explicit **no-op** (a boot line says so; not symmetric with `SENSITIVE_WRITE_PATTERNS=off`, which really does clear a set). **Any other value refuses to start** (7.12.0, BREAKING for a deployment that had a typo: it was previously treated as unset, i.e. silently no gate at all) |
|
|
166
168
|
| `PERMISSIONS_DISABLE_AUTO_MODE` | `false` | Local mirror of CC `permissions.disableAutoMode` — the **org deny** bit for `permissionMode:"auto"` (paired with core's intent-arming rule). **Tighten-only**: `true` folds every principal's `runtimeCaps.autoMode` to `false` (a center grant cannot flip it back); unset leaves caps untouched, so on a center-less box a shell asking for `auto` **arms** the classifier once the paired core (intent-arming rule "requested ∧ classifier seat ∧ `autoMode !== false`" — absence is not a deny) is installed; on core 7.2.0 the engine still uses the old "org grant" rule (`permissionModeAuto.intentArming:false`), so the self-check answers `armed:true` only for a center-granted principal and `deployment_incapable` on a center-less box. Boolean word table; any other value refuses to start. Self-check: `GET /v1/capabilities?permissionMode=auto` → `permissionModeAuto.{armed, reason, model}` (USAGE §9.4) |
|
|
167
169
|
| `SCRATCHPAD_SWEEP_TTL_MS` | 7 days | Idle-reap window for per-session scratchpad dirs (by dir mtime; `0` disables). The scratchpad is **ephemeral by contract**: replica-local disk, NOT part of the durable-suspend persistence set — a resume on a different replica, or after a sweep, starts with an empty dir (same two-track posture as the Agent SDK hosting doc: conversation persists, working-directory artifacts don't). Raise/disable only on single-replica deployments that park approvals for longer than the window |
|
package/README.zh-CN.md
CHANGED
|
@@ -148,6 +148,8 @@ curl -s localhost:8090/v1/tasks -H "Authorization: Bearer <SERVICE_AUTH_TOKEN>"
|
|
|
148
148
|
| `DEFAULT_SCENARIO` | `code` | 请求体未指定场景时的缺省场景 |
|
|
149
149
|
| `SANDBOX_PKG_SOURCE` | `global` | 沙箱内装包源:`global`(官方源)/ `cn`(国内镜像)/ `custom` / `none` |
|
|
150
150
|
| `SENSITIVE_WRITE_PATTERNS` | core 推荐集 | 敏感路径写拒集;逗号分隔值为整体替换,`off` **或留空**关闭。在治理层无条件施加(与客户端权限模式/lane/settings 在场性无关),`run-local` 腿同样生效;编译不出守卫集的值(如 `/`)启动即拒 |
|
|
151
|
+
| `WRITE_PROTECTED_EXTRA` | 未设(引擎缺省表) | 给 core 的**写保护名表**加行(字面名表:命中即把幸存的 `allow` 降级成 `ask`,作用于 Write/Edit/NotebookEdit)。逗号分隔裸名(裸名匹配**任意路径段**,含 `/` 的名匹配连续段)或 JSON 数组(`"name"` 串 / `{name, kind}` 行,`kind`:`basename` \| `segment` \| `segment-run`)。值按 `[...core 缺省表, …]` 组合,**丢不掉任何缺省行**。未设 = 不铸座 = 引擎缺省表在岗(本仓从不复制那张表)。两形按**内容**判而不是猜首字符:值里出现 JSON 结构字符(`[ ] { } "`)即按 JSON 解析,且顶层**必须是数组**(少写一对方括号 ⇒ 拒启,而不是被拆成垃圾裸名静默收下)。空值 / 坏值(通配符、未知 kind、kind 与名字段数矛盾)/ 与 `WRITE_PROTECTED_TABLE_REPLACE` 同时设置 ⇒ **启动即拒**。读面:`GET /v1/capabilities` 的 `writeProtection.{armed,rows,replaced}`;`GET /v1/diagnostics/wiring` 的 `writeProtection.{rows,source,droppedDefaultRows}`(operator-only) |
|
|
152
|
+
| `WRITE_PROTECTED_TABLE_REPLACE` | 未设(引擎缺省表) | **整表替换**写保护名表(core 的座按契约就是整表)。只收 JSON 数组 —— 刻意不给逗号简写:一个手滑的裸串会把 51 行换成 1 行。`[]` = 显式「完全不要这张表」。替换时 boot 期发一条**响亮**日志逐名列出被丢的缺省行(`write_protection_table_replaced`;空表走 `write_protection_table_disabled`)—— 想「加两行」请用 `WRITE_PROTECTED_EXTRA`。拒启条件同姊妹键,外加:两根同写 = 一条语义面两个写者 ⇒ 拒启 |
|
|
151
153
|
| `MODEL_CONNECT_TIMEOUT_MS` | `30000` | 网关连接超时 |
|
|
152
154
|
| `MODEL_FIRST_TOKEN_TIMEOUT_MS` | `120000` | 首 token 超时 |
|
|
153
155
|
| `MODEL_IDLE_TIMEOUT_MS` | `300000` | 流中 idle 超时(`0` 关) |
|
package/USAGE.md
CHANGED
|
@@ -563,6 +563,37 @@ UNATTENDED_APPROVAL_POLICY=park
|
|
|
563
563
|
不是 server 的 ask 行,「有 durable 设施但没开协议」正是 R-13 要救的那类部署)。要回到旧的「窗满即拒」
|
|
564
564
|
就配 `UNATTENDED_APPROVAL_POLICY=deny`。
|
|
565
565
|
|
|
566
|
+
**可选 — 写保护名表(S-138;缺席=引擎缺省表在岗)**
|
|
567
|
+
|
|
568
|
+
```bash
|
|
569
|
+
WRITE_PROTECTED_EXTRA=.corp-deploy-key,team/secrets # 加法:[...引擎缺省表, 这两行]
|
|
570
|
+
WRITE_PROTECTED_TABLE_REPLACE='[{"name":".envrc","kind":"basename"}]' # 整表替换(与上一根互斥)
|
|
571
|
+
```
|
|
572
|
+
- **这是什么**:core 的**字面名表**写保护 —— 一次落在表行上的可定路径写(Write/Edit/NotebookEdit)会把
|
|
573
|
+
幸存的 `allow` 降级成 `ask`(拒/问的裁决不受影响)。表的缺省内容是 CC 的
|
|
574
|
+
`DANGEROUS_FILES`/`DANGEROUS_DIRECTORIES`/`DANGEROUS_DIRECTORY_PATHS` 三形 + 两行 sema 自有行。
|
|
575
|
+
匹配是**字面**的(`kind`:`basename` = 末段同名 / `segment` = 任意路径段同名 / `segment-run` = 连续段序列),
|
|
576
|
+
裸名简写 = `segment`(更宽的那一档),含 `/` 的裸名 = `segment-run`。
|
|
577
|
+
- **两根旋钮按意图分家**:core 的座是**整表替换**,所以「我想再保护两个文件」只能走 `WRITE_PROTECTED_EXTRA`
|
|
578
|
+
(它组合成 `[...缺省表, …]`,结构上丢不掉缺省行);真要换整张表才用 `WRITE_PROTECTED_TABLE_REPLACE`
|
|
579
|
+
(只收 JSON;`[]` = 显式「完全不要这张表」)。**两根同写 = 拒启**(一条语义面不许两个写者)。
|
|
580
|
+
- **两形怎么判**:值里出现 JSON 结构字符(`[` `]` `{` `}` `"`)⇒ 当 JSON 判 —— 解析失败或顶层不是**数组**
|
|
581
|
+
一律拒启(`WRITE_PROTECTED_EXTRA='{"name":".x","kind":"basename"}'` 这种少写一对方括号的写法会被当场拒,
|
|
582
|
+
而不是被拆成两个垃圾裸名静默收下);否则走逗号裸名表。真有名字带引号/花括号的文件 ⇒ 走 JSON 形。
|
|
583
|
+
- **响亮**:整表替换在 boot 期发一条日志**逐名**列出被丢的缺省行(`write_protection_table_replaced`),
|
|
584
|
+
`[]` 发 `write_protection_table_disabled`。空值 / 坏值(通配符、未知 kind、kind 与名字段数矛盾)一律
|
|
585
|
+
**启动期拒**,文案带引擎原话 + 出问题的 env 名。
|
|
586
|
+
- **缺席 ≠ 没装**:不设这两根 ⇒ 座不铸 ⇒ **引擎的缺省表在岗**(server 不复制那张表)。要确认这台机器上
|
|
587
|
+
它到底在不在,读**读面**而不是猜:
|
|
588
|
+
- 租户面 `GET /v1/capabilities` → `writeProtection: {armed, rows, replaced}`(只给计数,不给行名);
|
|
589
|
+
- operator 面 `GET /v1/diagnostics/wiring` → `writeProtection: {rows[], source, droppedDefaultRows?}`
|
|
590
|
+
(`source`:`default` / `extra` / `replace` / `off`;逐行内容只在这一面)。
|
|
591
|
+
- **与 `SENSITIVE_WRITE_PATTERNS` 并列、不合流**:那一根是**模式**(正则/glob)型 DENY 集,这一根是
|
|
592
|
+
字面名表的 `allow → ask` 降级。两套互不覆盖,读面也分开报 —— 一条路径被哪一套拦住,是两个不同的问题
|
|
593
|
+
(解法一个是改模式、一个是改名表)。
|
|
594
|
+
- **leader 车道同席**:与其余部署姿态席同一个取值点(`buildDeploymentPostureSeats`),主 runner / subRunner /
|
|
595
|
+
run-local / leader 五只 Runner 同表。
|
|
596
|
+
|
|
566
597
|
**可选 — READ 面姿态(readFace,7.18.0 起;缺席=引擎当家)**
|
|
567
598
|
|
|
568
599
|
```bash
|
|
@@ -154,7 +154,7 @@ export interface RunnerDepsCtx {
|
|
|
154
154
|
}
|
|
155
155
|
/** [ref] A10 留档发现②:main runner `RunnerDeps` 与 main.ts subRunner 字面量之间此前手工重复
|
|
156
156
|
* 的 ~15 个键,类型标注见 {@link createSharedRunnerDeps} 头注。 */
|
|
157
|
-
export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice" | "mcpRevocations">;
|
|
157
|
+
export type SharedRunnerDeps = Pick<RunnerDeps, "brain" | "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths" | "models" | "roles" | "tiers" | "pricing" | "tracer" | "promptSource" | "executionEnvFactory" | "lspManager" | "backgroundAgentStore" | "mailboxStore" | "rosterStore" | "hooks" | "toolResultStore" | "hands" | "sessionPolicyStore" | "usageWindows" | "usageWindowStore" | "memoryScopeAdmission" | "deploymentMemoryScopes" | "sharedMemoryStores" | "compliancePostureResolver" | "lockedConfig" | "retentionPolicy" | "onNotice" | "mcpRevocations">;
|
|
158
158
|
/**
|
|
159
159
|
* [ref] A10 留档发现②(review 2026-07-29,[ref]§三族A 同源修补的延续):main runner 的
|
|
160
160
|
* `RunnerDeps` 字面量(下方 `createRunnerDeps`)与 `main.ts` 里 subRunner 的 `new Runner({...})`
|
|
@@ -260,7 +260,7 @@ export declare function createEngineNoticeSeat(logger: {
|
|
|
260
260
|
* 「键为 undefined」与「键缺席」对引擎逐字等价)。**不复制引擎缺省**(`roots`/`static-face`/`carry`/`open`/
|
|
261
261
|
* CC parity 20/200 一个都不抄):上游改缺省之日起本仓不会成为第二份真源。
|
|
262
262
|
*/
|
|
263
|
-
export type DeploymentPostureSeats = Pick<RunnerDeps, "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
263
|
+
export type DeploymentPostureSeats = Pick<RunnerDeps, "readFace" | "readDenyPatterns" | "readDenyBuiltinTiers" | "readDenyBuiltinExclude" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "delegationEntryCaps" | "crossSessionInbound" | "crossSessionDialogExpiry" | "writeProtectedPaths">;
|
|
264
264
|
export declare function buildDeploymentPostureSeats(config: Pick<ServiceConfig, keyof DeploymentPostureSeats>): DeploymentPostureSeats;
|
|
265
265
|
export declare function createSharedRunnerDeps(ctx: SharedRunnerDepsCtx): SharedRunnerDeps;
|
|
266
266
|
export declare function createRunnerDeps(ctx: RunnerDepsCtx): RunnerDeps;
|
package/dist/boot/runner-deps.js
CHANGED
|
@@ -46,6 +46,7 @@ export function buildDeploymentPostureSeats(config) {
|
|
|
46
46
|
delegationEntryCaps: config.delegationEntryCaps ?? undefined,
|
|
47
47
|
crossSessionInbound: Object.keys(crossSessionLayers).length > 0 ? crossSessionLayers : undefined,
|
|
48
48
|
crossSessionDialogExpiry: crossSessionDialogExpiryOf(config),
|
|
49
|
+
writeProtectedPaths: config.writeProtectedPaths?.entries,
|
|
49
50
|
};
|
|
50
51
|
return seats;
|
|
51
52
|
}
|
package/dist/config-catalog.js
CHANGED
|
@@ -440,6 +440,8 @@ export const CONFIG_CATALOG = [
|
|
|
440
440
|
r("WORKTREE_BASE_COMMIT", "orchestration", "string", "worktree 基准 ref(缺省 core HEAD)"),
|
|
441
441
|
r("WORKTREE_ISOLATION_ENABLED", "orchestration", "boolean", "git-worktree 隔离(与 REPO_ROOT 同在才开,半配保持 OFF)", { staticDefault: "false", danger: SEC }),
|
|
442
442
|
r("WORKTREE_REPO_ROOT", "orchestration", "path", "worktree 隔离的受信 base repo"),
|
|
443
|
+
r("WRITE_PROTECTED_EXTRA", "approval", "csv", "写保护名表的**加法**(逗号裸名或 JSON [{name,kind}];组合 [...core 缺省表, …];空值拒启)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 不铸座,core 缺省表(51 行)在岗" }),
|
|
444
|
+
r("WRITE_PROTECTED_TABLE_REPLACE", "approval", "json", "写保护名表的**整表替换**(JSON 数组;[]=显式无表;boot 期 loud 列出被丢的缺省行)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 不铸座,core 缺省表在岗;与 WRITE_PROTECTED_EXTRA 同写 ⇒ 拒启" }),
|
|
443
445
|
];
|
|
444
446
|
function primitiveConfigValue(config, key) {
|
|
445
447
|
const v = Reflect.get(config, key);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readMcpSourceLabel } from "../mcp-source-label.js";
|
|
1
2
|
import { skillContentHash } from "@sema-agent/settings-schema";
|
|
2
3
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "../mcp-content-origin.js";
|
|
3
4
|
import { fetchSkillContent } from "./http-client.js";
|
|
@@ -95,13 +96,14 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
95
96
|
note: "declaration DROPPED — this server's tools keep the pre-378 default (external content, session gets marked). Fix the center entry; the server itself still mounts.",
|
|
96
97
|
});
|
|
97
98
|
}
|
|
99
|
+
const source = readMcpSourceLabel(s.name, s.source);
|
|
98
100
|
if (s.transport.kind === "stdio") {
|
|
99
101
|
const env = resolveRefs(s.transport.envRefs, s.name, "env", logger);
|
|
100
102
|
if (env === null)
|
|
101
103
|
continue;
|
|
102
104
|
out.push({
|
|
103
105
|
scenarios: s.scenarios ?? [],
|
|
104
|
-
spec: { name: s.name, transport: { kind: "stdio", command: s.transport.command, args: s.transport.args, ...(Object.keys(env).length ? { env } : {}) }, ...allow, ...elicit, ...contentOrigin },
|
|
106
|
+
spec: { name: s.name, transport: { kind: "stdio", command: s.transport.command, args: s.transport.args, ...(Object.keys(env).length ? { env } : {}) }, ...allow, ...elicit, ...contentOrigin, ...source },
|
|
105
107
|
});
|
|
106
108
|
}
|
|
107
109
|
else {
|
|
@@ -110,7 +112,7 @@ export function resolveMcpServers(mcp, logger) {
|
|
|
110
112
|
continue;
|
|
111
113
|
out.push({
|
|
112
114
|
scenarios: s.scenarios ?? [],
|
|
113
|
-
spec: { name: s.name, transport: { kind: "http", url: s.transport.url, ...(Object.keys(headers).length ? { headers } : {}), ...(s.transport.principalHeader ? { principalHeader: s.transport.principalHeader } : {}) }, ...allow, ...elicit, ...contentOrigin },
|
|
115
|
+
spec: { name: s.name, transport: { kind: "http", url: s.transport.url, ...(Object.keys(headers).length ? { headers } : {}), ...(s.transport.principalHeader ? { principalHeader: s.transport.principalHeader } : {}) }, ...allow, ...elicit, ...contentOrigin, ...source },
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
}
|
|
@@ -113,6 +113,9 @@ export interface CenterMcpServer {
|
|
|
113
113
|
* 自动翻红逼销账。
|
|
114
114
|
*/
|
|
115
115
|
contentOrigin?: ToolContentOrigin;
|
|
116
|
+
/** B-036 / S-143:声明方自述的分组标签(开集,非空 ≤80;settings-schema 1.7.0 `McpServerSpec.source`)——上 core
|
|
117
|
+
* `McpServerSpec.source`,由 `wiring_manifest.mcp[].source` 逐字回显。显示用,不进信任轴。 */
|
|
118
|
+
source?: string;
|
|
116
119
|
transport: {
|
|
117
120
|
kind: "stdio";
|
|
118
121
|
command: string;
|
package/dist/config-provider.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
1
2
|
import { createHmac, randomBytes } from "node:crypto";
|
|
2
3
|
import { foldEffectiveReadWarning } from "./config-center/read-warnings.js";
|
|
3
4
|
import { FileConfigStore } from "@sema-agent/settings-schema/node";
|
|
@@ -142,6 +143,7 @@ export function mapToServiceEffective(eff, version) {
|
|
|
142
143
|
...(s.allowTools !== undefined ? { allowTools: s.allowTools } : {}),
|
|
143
144
|
...(s.elicitation !== undefined ? { elicitation: s.elicitation } : {}),
|
|
144
145
|
...(s.contentOrigin !== undefined ? { contentOrigin: s.contentOrigin } : {}),
|
|
146
|
+
...readMcpSourceLabel(s.name, s.source),
|
|
145
147
|
};
|
|
146
148
|
if (s.transport.kind === "stdio") {
|
|
147
149
|
return {
|
package/dist/config-types.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* compiling unchanged. All imports below are top-level `import type` (erased — never a load edge,
|
|
7
7
|
* see test/module-cycle-gate.test.ts header on verbatimModuleSyntax). */
|
|
8
8
|
import type { SealedKeyPoison } from "./sealed-key.js";
|
|
9
|
-
import type { A2aServerSpec, CompliancePosture, CrossSessionDialogExpiry, CrossSessionInboundSetting, FileHistoryRetentionPolicy, LockedKey, McpServerSpec, Model, ModelRoles, RetentionPolicy } from "@sema-agent/core";
|
|
9
|
+
import type { A2aServerSpec, CompliancePosture, CrossSessionDialogExpiry, CrossSessionInboundSetting, FileHistoryRetentionPolicy, LockedKey, McpServerSpec, Model, ModelRoles, RetentionPolicy, WriteProtectedEntry } from "@sema-agent/core";
|
|
10
10
|
import type { ApprovalHmacKey, PrincipalJwtKey } from "./auth-keys.js";
|
|
11
11
|
import type { ElicitationThrottle } from "./elicitation.js";
|
|
12
12
|
import type { QuestionThrottle } from "./question.js";
|
|
@@ -1268,6 +1268,31 @@ export interface ServiceConfigFlat {
|
|
|
1268
1268
|
* 已收口:守卫按 `ToolCallRequest.cwd` 解析写目标,两条 lane 同得。剩余射程边界(引擎未盖戳的直接
|
|
1269
1269
|
* 调用形)成文在 `deployment-governance.ts` 的 `RelativeTargetLexicalEnv` 类注。 */
|
|
1270
1270
|
sensitiveWritePatterns: string[];
|
|
1271
|
+
/**
|
|
1272
|
+
* S-138 —— core **写保护名表**(`RunnerDeps.writeProtectedPaths` 座)的部署决定。两根 env 旋钮
|
|
1273
|
+
* 共铸这**一个**键(它们写的是同一条语义面,两根同写 ⇒ boot 拒启,见 config.ts 的解析点):
|
|
1274
|
+
* · `WRITE_PROTECTED_EXTRA` ⇒ `{knob:"extra", entries:[...WRITE_PROTECTED_DEFAULT_TABLE, …增量]}`;
|
|
1275
|
+
* · `WRITE_PROTECTED_TABLE_REPLACE` ⇒ `{knob:"replace", entries:<JSON 数组原样>}`(`[]` = 显式无表)。
|
|
1276
|
+
*
|
|
1277
|
+
* 🔴 **缺席 = 不铸键**(与 `readFace` 族同律):座缺席时 core 的 `resolveWriteProtectedTable` 返回它
|
|
1278
|
+
* 自己的缺省 51 行 —— 本仓**不复制那张表**,上游改表之日起不会有第二份真源。「缺席 = 没装表」是
|
|
1279
|
+
* B-023 F2 的那个误读,读面(`writeProtection` 两位)正是为了让它不可能再发生。
|
|
1280
|
+
*
|
|
1281
|
+
* 🔴 为什么带 `knob` 而不是只存数组:`extra` 与 `replace` 铸出的 `entries` 在**值**上分不出来
|
|
1282
|
+
* (一份 `[...DEFAULT, 两行]` 既可能是加法、也可能是运维手抄的整表),而两者对运维的意义相反 ——
|
|
1283
|
+
* 读面的 `source`/`replaced` 要回答的正是「运维动没动那张表」。从值反推 = 猜,故意存事实。
|
|
1284
|
+
* 座席(纯数组)由 `buildDeploymentPostureSeats` 从本键**派生**(`crossSessionInbound` 同族形制:
|
|
1285
|
+
* config 存决定、座席存引擎形)。
|
|
1286
|
+
*
|
|
1287
|
+
* ⚠️ 与 {@link sensitiveWritePatterns} 是**并列机制、不互相覆盖**(core d.ts 原话):那一套是模式
|
|
1288
|
+
* (正则/glob)型 deny,这一套是字面名表的 `allow → ask` 降级。两个读面因此分开报。
|
|
1289
|
+
*/
|
|
1290
|
+
writeProtectedPaths?: {
|
|
1291
|
+
/** 哪根旋钮写的(闭集;读面 `source` / `replaced` 的唯一判据 —— 不从 entries 的值反推)。 */
|
|
1292
|
+
readonly knob: "extra" | "replace";
|
|
1293
|
+
/** 座席原样递给 core 的表项(裸名简写或 `{name,kind}` 行;坏值在 boot 期由 core 响亮拒)。 */
|
|
1294
|
+
readonly entries: readonly WriteProtectedEntry[];
|
|
1295
|
+
};
|
|
1271
1296
|
/** [ref]§四 opt-in (cli[ref]② finding, core[ref] suggested mechanism): the CC manual-family permission
|
|
1272
1297
|
* modes (default/auto/acceptEdits) gate every fs WRITE hand tool (Write/Edit/NotebookEdit) but never touched
|
|
1273
1298
|
* Bash — a model can `echo -n '…' > file` straight past the write-approval card. `MANUAL_MODE_SHELL_GATE`
|
|
@@ -1638,7 +1663,7 @@ export type ServiceStoreConfig = Pick<ServiceConfigFlat, "sessionBackend" | "ses
|
|
|
1638
1663
|
export type ServiceModelPlaneConfig = Pick<ServiceConfigFlat, "gatewayBaseUrl" | "gatewayApiKey" | "gatewayFallbackUrls" | "gatewayMaxRetries" | "anthropic" | "resilience" | "model" | "models" | "modelApiKeyEnv" | "modelApiKeys" | "modelQuotaWeights" | "tiers" | "projects" | "roles" | "atModelAllowlist" | "cascadeLadder" | "degrade">;
|
|
1639
1664
|
/** 组:approval(审批 / HITL 门)。`directDoorActive` 无 env 解析腿(装配层三域合取的产物),但语义上
|
|
1640
1665
|
* 就是本组的门状态,故进组;`parseApprovalDomain` 的返回类型相应是 `Omit<…, "directDoorActive">`。 */
|
|
1641
|
-
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "manualModeShellGate" | "permissionsDisableAutoMode" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
1666
|
+
export type ServiceApprovalConfig = Pick<ServiceConfigFlat, "approvalRequire" | "approvalDeny" | "approvalTimeoutSec" | "approvalAutoBudget" | "approvalNeverAuto" | "approvalHmacKeys" | "durableApproval" | "directApprovalDoor" | "directDoorActive" | "resourceSuspend" | "resourceSuspendTtlSec" | "resourceSuspendTotalTokens" | "resourceSuspendTotalBudgetUsd" | "resourceSuspendMaxSlices" | "askQuestionEnabled" | "questionThrottle" | "toolApprovalEnabled" | "permissionRulesEnabled" | "permissionRulesEnabledExplicit" | "streamAskWindowMarginMs" | "streamApproval" | "unattendedApprovalPolicy" | "mcpElicitation" | "sensitiveWritePatterns" | "writeProtectedPaths" | "manualModeShellGate" | "permissionsDisableAutoMode" | "crossSessionInbound" | "crossSessionDialogExpiry">;
|
|
1642
1667
|
/** 组:memory(记忆面 + TOC 同步腿)。 */
|
|
1643
1668
|
export type ServiceMemoryConfig = Pick<ServiceConfigFlat, "memoryEngineEnabled" | "memoryEngineDir" | "memoryEngineRemoteLaneAllowed" | "memoryEngineBackend" | "memoryScope" | "memoryPersistenceCapable" | "memorySync" | "memoryEmbedder" | "memoryOrgAdmissionMode" | "memoryDelegationEvidence" | "memoryProvenance" | "memoryCapturePolicy" | "memoryConsolidationDriver" | "memoryOrgDirectoryJson" | "memoryOrgGrantTtlMs" | "memoryOrgUnavailableBackoffMs" | "projectMemoryEnabled" | "syncImportLeaseStaleSec">;
|
|
1644
1669
|
/** 组:auth(鉴权 / 身份 / 治理棒)。`commandPolicy` 只有 sema-registry 腿(无 env 标量形),故 env 解析
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules } from "@sema-agent/core";
|
|
5
|
+
import { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES, compileReadDeny, resolveReadDenyBuiltins, CODE_AGENT_PROMPT, formatUserScope, PROTOCOL_TABLE, protocolOf, RECOMMENDED_SENSITIVE_PATTERNS, RETIRED_TOOL_NAMES, resolveFileHistoryRetention, resolveLockedKeys, validatePermissionRules, WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable } from "@sema-agent/core";
|
|
6
6
|
import { ROSTER_PRIMARY_ROLES, ROSTER_CHEAP_ROLES } from "@sema-agent/settings-schema";
|
|
7
7
|
import { parseApprovalHmacKeys, parsePrincipalJwks } from "./auth-keys.js";
|
|
8
8
|
import { DEFAULT_ELICITATION_THROTTLE } from "./elicitation.js";
|
|
@@ -10,6 +10,7 @@ import { isV2ScopeKey } from "./memory-scope.js";
|
|
|
10
10
|
import { DEFAULT_QUESTION_THROTTLE } from "./question.js";
|
|
11
11
|
import { GIT_API_KINDS, isGitApiKind } from "./git-api-kind.js";
|
|
12
12
|
import { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
13
|
+
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
13
14
|
import { normalizeSshFingerprint } from "./ssh-host-key.js";
|
|
14
15
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
15
16
|
export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js";
|
|
@@ -414,6 +415,85 @@ function assertReadDenyBuiltinsResolvable(tiers, exclude) {
|
|
|
414
415
|
if (exclude !== undefined)
|
|
415
416
|
check("READ_DENY_BUILTIN_EXCLUDE", { ...(tiers !== undefined ? { tiers } : {}), exclude });
|
|
416
417
|
}
|
|
418
|
+
const WRITE_PROTECTED_JSON_SHAPED = /[[\]{}"]/;
|
|
419
|
+
function writeProtectedTableOrRefuse(name, raw, opts) {
|
|
420
|
+
const trimmed = raw.trim();
|
|
421
|
+
if (trimmed === "") {
|
|
422
|
+
throw new Error(`env ${name}="" is empty — refusing to start rather than guessing which of the two meanings you wanted. ` +
|
|
423
|
+
`An unexpanded compose variable and a deliberate posture must not look alike on a protection knob: unset the variable to keep the engine's default table, ` +
|
|
424
|
+
`or write WRITE_PROTECTED_TABLE_REPLACE=[] for the explicit "no write-protection table at all" posture.`);
|
|
425
|
+
}
|
|
426
|
+
let list;
|
|
427
|
+
if (WRITE_PROTECTED_JSON_SHAPED.test(trimmed)) {
|
|
428
|
+
let parsed;
|
|
429
|
+
try {
|
|
430
|
+
parsed = JSON.parse(trimmed);
|
|
431
|
+
}
|
|
432
|
+
catch (e) {
|
|
433
|
+
throw new Error(`env ${name} is not valid JSON: ${e instanceof Error ? e.message : String(e)} — expected a JSON ARRAY of "name" strings or {name, kind} rows.`);
|
|
434
|
+
}
|
|
435
|
+
if (!Array.isArray(parsed)) {
|
|
436
|
+
throw new Error(`env ${name} must be a JSON ARRAY of "name" strings or {name, kind} rows — got ${typeof parsed === "object" && parsed !== null ? "a JSON object" : `the JSON scalar ${JSON.stringify(parsed)}`}. ` +
|
|
437
|
+
`A single row still needs its brackets: [${trimmed}].`);
|
|
438
|
+
}
|
|
439
|
+
list = parsed;
|
|
440
|
+
}
|
|
441
|
+
else if (!opts.bareNames) {
|
|
442
|
+
throw new Error(`env ${name} must be a JSON array — the whole-table REPLACEMENT knob deliberately has no comma shorthand: ` +
|
|
443
|
+
`a slipped bare string there would swap the engine's whole table for one row. Write ["${trimmed.split(",")[0].trim()}"] if that is really what you mean, ` +
|
|
444
|
+
`or use WRITE_PROTECTED_EXTRA to ADD rows to the default table.`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
const parts = trimmed.split(",").map((s) => s.trim());
|
|
448
|
+
const names = parts.filter((s) => s.length > 0);
|
|
449
|
+
if (names.length !== parts.length)
|
|
450
|
+
throw new Error(`env ${name}="${raw}" has an empty entry between commas — refusing to start rather than silently applying a half-parsed list (#157).`);
|
|
451
|
+
list = names;
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
return resolveWriteProtectedTable([...(opts.prefix ?? []), ...list]);
|
|
455
|
+
}
|
|
456
|
+
catch (e) {
|
|
457
|
+
throw new Error(`env ${name}: ${e instanceof Error ? e.message : String(e)}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function parseWriteProtectedTableEnv(extraRaw, replaceRaw) {
|
|
461
|
+
if (extraRaw !== undefined && replaceRaw !== undefined) {
|
|
462
|
+
throw new Error(`env WRITE_PROTECTED_EXTRA and WRITE_PROTECTED_TABLE_REPLACE are both set — two writers on ONE semantic surface (the engine's write-protection table). ` +
|
|
463
|
+
`There is no honest composition of "add to the default table" and "replace the table wholesale", so this refuses to boot instead of picking one: ` +
|
|
464
|
+
`keep WRITE_PROTECTED_EXTRA for additions, or fold your additions into WRITE_PROTECTED_TABLE_REPLACE and unset the other.`);
|
|
465
|
+
}
|
|
466
|
+
if (extraRaw !== undefined) {
|
|
467
|
+
return { knob: "extra", entries: writeProtectedTableOrRefuse("WRITE_PROTECTED_EXTRA", extraRaw, { bareNames: true, prefix: WRITE_PROTECTED_DEFAULT_TABLE }) };
|
|
468
|
+
}
|
|
469
|
+
if (replaceRaw === undefined)
|
|
470
|
+
return undefined;
|
|
471
|
+
const decision = { knob: "replace", entries: writeProtectedTableOrRefuse("WRITE_PROTECTED_TABLE_REPLACE", replaceRaw, { bareNames: false }) };
|
|
472
|
+
const posture = buildWriteProtectionPosture({ writeProtectedPaths: decision });
|
|
473
|
+
const dropped = posture.droppedDefaultRows ?? [];
|
|
474
|
+
CONFIG_NOTICES.push(posture.source === "off"
|
|
475
|
+
? {
|
|
476
|
+
event: "write_protection_table_disabled",
|
|
477
|
+
fields: {
|
|
478
|
+
env: "WRITE_PROTECTED_TABLE_REPLACE",
|
|
479
|
+
raw: "[]",
|
|
480
|
+
droppedCount: String(dropped.length),
|
|
481
|
+
effect: `the engine's write-protection table is now EMPTY — all ${String(dropped.length)} default rows are gone and a write to any of them ` +
|
|
482
|
+
`(.zshrc / .ssh / .git / …) no longer gets the allow→ask demotion. This is a legal, explicit posture; it is logged loudly so it can never be a silent one`,
|
|
483
|
+
},
|
|
484
|
+
}
|
|
485
|
+
: {
|
|
486
|
+
event: "write_protection_table_replaced",
|
|
487
|
+
fields: {
|
|
488
|
+
env: "WRITE_PROTECTED_TABLE_REPLACE",
|
|
489
|
+
rows: String(posture.rows.length),
|
|
490
|
+
droppedCount: String(dropped.length),
|
|
491
|
+
droppedDefaultRows: dropped.join(","),
|
|
492
|
+
effect: "whole-table replacement: the default rows listed above are NO LONGER protected (use WRITE_PROTECTED_EXTRA if you meant to ADD rows to the default table)",
|
|
493
|
+
},
|
|
494
|
+
});
|
|
495
|
+
return decision;
|
|
496
|
+
}
|
|
417
497
|
function enumEnv(name, fallback, allowed) {
|
|
418
498
|
const v = (process.env[name] ?? fallback);
|
|
419
499
|
if (!allowed.includes(v))
|
|
@@ -966,6 +1046,7 @@ function parseApprovalDomain(ctx) {
|
|
|
966
1046
|
return [];
|
|
967
1047
|
return trimmed.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
968
1048
|
})(process.env.SENSITIVE_WRITE_PATTERNS),
|
|
1049
|
+
...((decision) => (decision === undefined ? {} : { writeProtectedPaths: decision }))(parseWriteProtectedTableEnv(process.env.WRITE_PROTECTED_EXTRA, process.env.WRITE_PROTECTED_TABLE_REPLACE)),
|
|
969
1050
|
...((raw) => {
|
|
970
1051
|
const trimmed = (raw ?? "").trim();
|
|
971
1052
|
if (trimmed === "always" || trimmed === "classify")
|
|
@@ -1803,7 +1884,7 @@ const APPROVAL_GROUP_KEYS = [
|
|
|
1803
1884
|
"approvalRequire", "approvalDeny", "approvalTimeoutSec", "approvalAutoBudget", "approvalNeverAuto",
|
|
1804
1885
|
"approvalHmacKeys", "durableApproval", "directApprovalDoor", "directDoorActive", "resourceSuspend",
|
|
1805
1886
|
"resourceSuspendTtlSec", "resourceSuspendTotalTokens", "resourceSuspendTotalBudgetUsd", "resourceSuspendMaxSlices", "askQuestionEnabled", "questionThrottle", "toolApprovalEnabled", "permissionRulesEnabled", "permissionRulesEnabledExplicit", "streamAskWindowMarginMs", "streamApproval", "unattendedApprovalPolicy", "mcpElicitation",
|
|
1806
|
-
"sensitiveWritePatterns", "manualModeShellGate", "permissionsDisableAutoMode", "crossSessionInbound", "crossSessionDialogExpiry",
|
|
1887
|
+
"sensitiveWritePatterns", "writeProtectedPaths", "manualModeShellGate", "permissionsDisableAutoMode", "crossSessionInbound", "crossSessionDialogExpiry",
|
|
1807
1888
|
];
|
|
1808
1889
|
const MEMORY_GROUP_KEYS = [
|
|
1809
1890
|
"memoryEngineEnabled", "memoryEngineDir", "memoryEngineRemoteLaneAllowed", "memoryEngineBackend", "memoryScope",
|
|
@@ -11,6 +11,15 @@ export declare function systemFor(req: IncomingMessage, config: ServiceConfig):
|
|
|
11
11
|
/** "presented credentials must verify":Authorization 带 Bearer/token scheme 且三路全验不过 → true。
|
|
12
12
|
* 其他 scheme(Basic 等,非我方凭证空间)不管——由原有门语义处置,避免误伤未知代理注头。 */
|
|
13
13
|
export declare function bearerPresentedButUnverified(req: IncomingMessage, config: ServiceConfig): boolean;
|
|
14
|
+
/** loopback 三形(IPv4 / IPv6 / IPv4-mapped)—— 一处定义,diagnostics 门与任何「本机自用」判据同吃。缺席 ⇒ false(fail-closed)。 */
|
|
15
|
+
export declare function isLoopbackAddress(addr: string | undefined): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* 这次请求的身份是不是**密码学验过**的(而不是调用方自述的 header):`sso` = registry JWT(入口预认证的签名真源)/
|
|
18
|
+
* `direct_door_jwt` = direct-door principal JWT 验过 / `service_token` = 出示了单一或 per-system service token。
|
|
19
|
+
* 缺席 = 只有自述头或什么都没有。一处判定(7.63.0 codex [high]+[medium]):off-box 读 operator 面的门用它——名单只在
|
|
20
|
+
* 有可验身份时才是身份,零凭据下它只是谁都能写的字符串;而 JWT 身份不依赖静态 token 的在场。
|
|
21
|
+
*/
|
|
22
|
+
export declare function verifiedIdentitySource(req: IncomingMessage, config: ServiceConfig): "sso" | "direct_door_jwt" | "service_token" | undefined;
|
|
14
23
|
export declare function gatedPrincipal(req: IncomingMessage, config: ServiceConfig): string | undefined;
|
|
15
24
|
/** The EXPLICIT operator gate (IMAGE-API-DESIGN.md §P2.4a MUST-FIX) — NOT the bare `isOperator` (whose empty-list
|
|
16
25
|
* "true-for-all" is an unrestricted-write exec door the instant OPERATOR_PRINCIPALS is empty). Requires a non-empty
|
|
@@ -33,6 +33,18 @@ export function bearerPresentedButUnverified(req, config) {
|
|
|
33
33
|
return false;
|
|
34
34
|
return true;
|
|
35
35
|
}
|
|
36
|
+
export function isLoopbackAddress(addr) {
|
|
37
|
+
return addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
38
|
+
}
|
|
39
|
+
export function verifiedIdentitySource(req, config) {
|
|
40
|
+
if (ssoVerifiedPrincipal(req))
|
|
41
|
+
return "sso";
|
|
42
|
+
if (systemFor(req, config) !== undefined)
|
|
43
|
+
return "service_token";
|
|
44
|
+
if (config.directDoorActive && verifiedPrincipal(req, config) !== undefined)
|
|
45
|
+
return "direct_door_jwt";
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
36
48
|
export function gatedPrincipal(req, config) {
|
|
37
49
|
if (bearerPresentedButUnverified(req, config))
|
|
38
50
|
return undefined;
|
|
@@ -9,6 +9,7 @@ import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
|
9
9
|
import { sendJson, sendError } from "../send.js";
|
|
10
10
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
11
11
|
import { projectMemoryEngineCapability } from "../../memory-posture.js";
|
|
12
|
+
import { projectWriteProtectionCapability } from "../../write-protection.js";
|
|
12
13
|
import { projectSqlEngineCapability } from "../../sql-engine-posture.js";
|
|
13
14
|
import { workflowModelAllowlistFor, selfOrchestrationDenial } from "../../task-workflow.js";
|
|
14
15
|
import { centerEntitlementSourceWired } from "../../runtime-caps-resolver.js";
|
|
@@ -114,6 +115,7 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
114
115
|
...autoMode,
|
|
115
116
|
},
|
|
116
117
|
readFace: deps.config.readFace ?? null,
|
|
118
|
+
writeProtection: projectWriteProtectionCapability(deps.writeProtection),
|
|
117
119
|
appendSystemPrompt: true,
|
|
118
120
|
compactionModel: true,
|
|
119
121
|
subagentOutput: Boolean(deps.subagentTaskOutput) && Boolean(deps.runStore),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
2
2
|
import { sendJson, sendError } from "../send.js";
|
|
3
|
-
import { gatedPrincipal, explicitOperatorOk } from "../principal-gate.js";
|
|
3
|
+
import { gatedPrincipal, explicitOperatorOk, isLoopbackAddress, verifiedIdentitySource } from "../principal-gate.js";
|
|
4
4
|
export function buildServerWiringGates(input) {
|
|
5
5
|
const gate = resolveStreamApprovalGate({
|
|
6
6
|
toolApprovalEnabled: input.toolApprovalEnabled,
|
|
@@ -56,9 +56,7 @@ export function zeroCredentialLoopbackDenied(remoteAddress, config) {
|
|
|
56
56
|
const anyServiceAuth = Boolean(config.authToken) || Object.keys(config.authTokens ?? {}).length > 0;
|
|
57
57
|
if (anyServiceAuth)
|
|
58
58
|
return false;
|
|
59
|
-
|
|
60
|
-
const isLoopback = addr === "127.0.0.1" || addr === "::1" || addr === "::ffff:127.0.0.1";
|
|
61
|
-
return !isLoopback;
|
|
59
|
+
return !isLoopbackAddress(remoteAddress);
|
|
62
60
|
}
|
|
63
61
|
export async function handleDiagnostics(req, res, url, ctx) {
|
|
64
62
|
const miss = { fell: false };
|
|
@@ -74,6 +72,10 @@ async function handleDiagnosticsBody(req, res, url, ctx, miss) {
|
|
|
74
72
|
sendError(res, 403, "auth.operator_only", "wiring diagnostics are operator-only on a multi-tenant worker");
|
|
75
73
|
return;
|
|
76
74
|
}
|
|
75
|
+
if (!isLoopbackAddress(req.socket.remoteAddress) && verifiedIdentitySource(req, deps.config) === undefined) {
|
|
76
|
+
sendError(res, 403, "auth.loopback_only", "wiring diagnostics on a zero-credential deployment are readable from loopback only — configure SERVICE_AUTH_TOKEN to read remotely");
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
77
79
|
}
|
|
78
80
|
else if (zeroCredentialLoopbackDenied(req.socket.remoteAddress, deps.config)) {
|
|
79
81
|
sendError(res, 403, "auth.loopback_only", "wiring diagnostics on a zero-credential deployment are readable from loopback only — configure SERVICE_AUTH_TOKEN to read remotely");
|
|
@@ -87,6 +89,7 @@ async function handleDiagnosticsBody(req, res, url, ctx, miss) {
|
|
|
87
89
|
static: deps.staticWiring,
|
|
88
90
|
memoryPosture: deps.memoryPosture ?? null,
|
|
89
91
|
sqlEngine: deps.sqlEngineFacts?.() ?? null,
|
|
92
|
+
writeProtection: deps.writeProtection ?? null,
|
|
90
93
|
configApply: deps.configApplyState?.() ?? null,
|
|
91
94
|
mcpRevocations: deps.mcpRevocationState?.() ?? null,
|
|
92
95
|
serverGates: buildServerWiringGates({
|
package/dist/http/server.d.ts
CHANGED
|
@@ -660,6 +660,13 @@ export interface ServiceDeploymentDeps {
|
|
|
660
660
|
* 的 `sql` 位(窄投影)。getter 而不是快照:池重连后驱动会重新初始化并刷新事实,快照会说谎。
|
|
661
661
|
* 缺席 / 返回 null = 本部署没有 SQL 后端(env-only worker、local 文件后端)—— 诚实缺席,不铸空壳。 */
|
|
662
662
|
sqlEngineFacts?: () => import("../plugins/sql-driver.js").SqlEngineFacts | null;
|
|
663
|
+
/** S-138:**写保护名表**的姿态(`write-protection.ts` 的 boot 期单一推导点 —— 与递给每只 Runner 的
|
|
664
|
+
* `writeProtectedPaths` 座同一份 config 决定)。两个读面共用:`GET /v1/diagnostics/wiring` 的
|
|
665
|
+
* `writeProtection` 段(operator-only,**逐行**表 + 来源 + 被丢的缺省行名)与 `GET /v1/capabilities`
|
|
666
|
+
* 的 `writeProtection` 位(窄投影 `{armed, rows, replaced}`,**不含行内容**)。
|
|
667
|
+
* boot 期算一次的**存活对象**(纯 config 派生,运行期不变;core 座没有热改腿)。
|
|
668
|
+
* 缺席 = 这个进程不是由 composition root 装起来的(测试夹具形)⇒ 诊断面报 null、能力位报 null。 */
|
|
669
|
+
writeProtection?: import("../write-protection.js").WriteProtectionPosture;
|
|
663
670
|
/** ① core ruling — SPLIT: `capabilities.workflows` = the ENGINE-CAN axis, boot-computed from core's own
|
|
664
671
|
* `workflowsCapability(deps)` (hardened script runner ∧ governance), NOT the `Boolean(workflowRunStore)` store
|
|
665
672
|
* proxy. Orthogonal to `workflowsList` (the durable-list axis = `workflowRunStore`). Falls back to the store
|
package/dist/main.js
CHANGED
|
@@ -8,6 +8,7 @@ import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js
|
|
|
8
8
|
import { subagentSendUserFileExtraTools } from "./capabilities/send-user-file-tool.js";
|
|
9
9
|
import { brainSummary } from "./brain.js";
|
|
10
10
|
import { loadConfig, logConfigDiagnostics, resolveBindHost, splitLocalRoots, parseCapEnv, parseNumOrFailNonNegative } from "./config.js";
|
|
11
|
+
import { buildWriteProtectionPosture } from "./write-protection.js";
|
|
11
12
|
import { cardRuleScopeRoot } from "./task-cwd.js";
|
|
12
13
|
import { isPricingConfigured } from "./budget.js";
|
|
13
14
|
import { drainNumEnvWarnings } from "./plugins/remote-shell.js";
|
|
@@ -701,6 +702,7 @@ async function main() {
|
|
|
701
702
|
staticWiring,
|
|
702
703
|
memoryPosture,
|
|
703
704
|
sqlEngineFacts,
|
|
705
|
+
writeProtection: buildWriteProtectionPosture(config),
|
|
704
706
|
instanceId,
|
|
705
707
|
capabilities: {
|
|
706
708
|
version: serviceVersion(),
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** 与 core `mcpManifestEntries` 的 `inlineUntrusted(entry.source, 80)` 同数。 */
|
|
2
|
+
export declare const MCP_SOURCE_LABEL_MAX = 80;
|
|
3
|
+
export declare function isMcpSourceLabel(v: unknown): v is string;
|
|
4
|
+
/**
|
|
5
|
+
* 从任一腿的原始条目里读出 `source`:合法 ⇒ `{ source }`(可直接 spread 进 spec);缺席 ⇒ `{}`;坏形 ⇒ `{}` +
|
|
6
|
+
* 留痕(`server` 与值的形进 detail,不进值本身——标签是 UNTRUSTED-for-display 的自由文本)。
|
|
7
|
+
*/
|
|
8
|
+
export declare function readMcpSourceLabel(server: string, v: unknown): {
|
|
9
|
+
source?: string;
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=mcp-source-label.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { recordFailOpen } from "./observability/fail-open.js";
|
|
2
|
+
export const MCP_SOURCE_LABEL_MAX = 80;
|
|
3
|
+
export function isMcpSourceLabel(v) {
|
|
4
|
+
return typeof v === "string" && v.length > 0 && v.length <= MCP_SOURCE_LABEL_MAX;
|
|
5
|
+
}
|
|
6
|
+
export function readMcpSourceLabel(server, v) {
|
|
7
|
+
if (v === undefined)
|
|
8
|
+
return {};
|
|
9
|
+
if (isMcpSourceLabel(v))
|
|
10
|
+
return { source: v };
|
|
11
|
+
recordFailOpen("server.task-mcp.source-label-dropped", `${server}: ${typeof v === "string" ? (v.length === 0 ? "empty string" : `string of ${v.length} chars (> ${MCP_SOURCE_LABEL_MAX})`) : typeof v}`);
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=mcp-source-label.js.map
|
|
@@ -9,6 +9,10 @@ export interface FailOpenTagEntry {
|
|
|
9
9
|
}
|
|
10
10
|
/** 闭集 tag 词表。形=`<repo>.<domain>.<site>`(跨仓同形,便于三仓遥测并表)。 */
|
|
11
11
|
export declare const FAIL_OPEN_TAGS: {
|
|
12
|
+
readonly "server.task-mcp.source-label-dropped": {
|
|
13
|
+
readonly cls: "F";
|
|
14
|
+
readonly note: "B-036 / S-143(`mcp-source-label.ts readMcpSourceLabel`,请求腿 + center 腿共用):`mcpServers[].source`(分组标签,显示用、开集)坏形——空串 / 超 80 字 / 非字符串——**丢键留 server**。放行的最坏后果=这台 server 在 wiring_manifest.mcp[] 里没有分组标签(难看),不是门/权限/凭证;本地 config.d 腿由 settings-schema 1.7.0 在解析处拒,故本臂只在请求腿与远端 /effective 直读 JSON 腿可达。";
|
|
15
|
+
};
|
|
12
16
|
readonly "server.memory-optout-grant.onfault-hook-threw": {
|
|
13
17
|
readonly cls: "F";
|
|
14
18
|
readonly note: "design/383 S-1(applyMemoryOptOutGrant):本地 grant 腿故障时的**披露钩子**(onFault → warn 日志)自己抛了。钩子只是观测面:吞掉它保住的是「resolver 不因一行日志失败而 reject」(那会把一条腿的故障扩成整车故障 —— core 的 resolver-throw 降级臂作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**那一行披露丢了**,而 `allowMemoryOptOut` 键缺席这件事本身不受影响(fail-closed/fail-safe 极性由 core 按姿态判)。本计数让「披露钩子一直在炸」显形。";
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { appendFileSync } from "node:fs";
|
|
2
2
|
export const FAIL_OPEN_TAGS = {
|
|
3
|
+
"server.task-mcp.source-label-dropped": {
|
|
4
|
+
cls: "F",
|
|
5
|
+
note: "B-036 / S-143(`mcp-source-label.ts readMcpSourceLabel`,请求腿 + center 腿共用):`mcpServers[].source`(分组标签,显示用、开集)坏形——空串 / 超 80 字 / 非字符串——**丢键留 server**。放行的最坏后果=这台 server 在 wiring_manifest.mcp[] 里没有分组标签(难看),不是门/权限/凭证;本地 config.d 腿由 settings-schema 1.7.0 在解析处拒,故本臂只在请求腿与远端 /effective 直读 JSON 腿可达。",
|
|
6
|
+
},
|
|
3
7
|
"server.memory-optout-grant.onfault-hook-threw": {
|
|
4
8
|
cls: "F",
|
|
5
9
|
note: "design/383 S-1(applyMemoryOptOutGrant):本地 grant 腿故障时的**披露钩子**(onFault → warn 日志)自己抛了。钩子只是观测面:吞掉它保住的是「resolver 不因一行日志失败而 reject」(那会把一条腿的故障扩成整车故障 —— core 的 resolver-throw 降级臂作废同一次解析里 center 已答好的 allowWorkflows/allowFork);代价是**那一行披露丢了**,而 `allowMemoryOptOut` 键缺席这件事本身不受影响(fail-closed/fail-safe 极性由 core 按姿态判)。本计数让「披露钩子一直在炸」显形。",
|
package/dist/task-mcp.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { resolveComplianceDenies } from "@sema-agent/core";
|
|
2
2
|
import { HttpError } from "./security.js";
|
|
3
3
|
import { CONTENT_ORIGIN_WORDS, describeBadContentOrigin, isContentOriginWord } from "./mcp-content-origin.js";
|
|
4
|
+
import { readMcpSourceLabel } from "./mcp-source-label.js";
|
|
4
5
|
export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
5
6
|
export function mcpInjectionHonored(config) {
|
|
6
7
|
if (config.lockedConfigKeys?.includes("mcp") === true)
|
|
@@ -77,6 +78,7 @@ function normalizeServer(e, noteBadOrigin) {
|
|
|
77
78
|
else
|
|
78
79
|
noteBadOrigin(spec.name, o.contentOrigin);
|
|
79
80
|
}
|
|
81
|
+
Object.assign(spec, readMcpSourceLabel(spec.name, o.source));
|
|
80
82
|
return spec;
|
|
81
83
|
}
|
|
82
84
|
export function validateRequestMcp(raw) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-138 —— **写保护表**(core `RunnerDeps.writeProtectedPaths` 座)的姿态推导点(形制照
|
|
3
|
+
* `memory-posture.ts` / `sql-engine-posture.ts`:一次推导,两个读面)。
|
|
4
|
+
*
|
|
5
|
+
* ## 病(B-023 F2 的根)
|
|
6
|
+
*
|
|
7
|
+
* core 的写保护表是**字面名表**(CC `DANGEROUS_FILES` / `DANGEROUS_DIRECTORIES` /
|
|
8
|
+
* `DANGEROUS_DIRECTORY_PATHS` 三形对位):一次落在表行上的可定路径写(Write/Edit/NotebookEdit)会把
|
|
9
|
+
* 幸存的 `allow` 降级成 `ask`。座**缺席 = 缺省表在岗**(不是「没装」)。可是本仓此前没有任何读面能
|
|
10
|
+
* 回答「这台部署的写保护表在不在、有几行」—— 邻仓因此只能用「`writeProtectedPaths` 键名 grep 零命中」
|
|
11
|
+
* 去推断「表未装配」,推错了(座缺席正是缺省表在岗的姿势),而**读面缺席让这个错误无法被证否**。
|
|
12
|
+
*
|
|
13
|
+
* ## 药
|
|
14
|
+
*
|
|
15
|
+
* 一次推导,两个读面:
|
|
16
|
+
* · operator 面(`GET /v1/diagnostics/wiring` 的 `writeProtection` 段)—— **逐行**表 + 来源 + 被丢的缺省行名;
|
|
17
|
+
* · 租户面(`GET /v1/capabilities` 的 `writeProtection` 位)—— `{armed, rows, replaced}` 三项,**不含行内容**。
|
|
18
|
+
*
|
|
19
|
+
* ## 三条诚实纪律
|
|
20
|
+
*
|
|
21
|
+
* ① **表由 core 解析,本仓零重算**:`rows` 逐字来自 `compileWriteProtection().rows`(core 自己的
|
|
22
|
+
* 「disclosure face」),连行的**身份折叠**(大小写 + ı/ſ 两个易混字)都借 core 的
|
|
23
|
+
* `resolveWriteProtectedTable` 去判(见 {@link defaultRowsMissingFrom})—— 本仓不铸第二套判定语义,
|
|
24
|
+
* 也**不复制缺省 51 行**(单一属主在 core;上游改表之日起本仓不会成为第二份真源)。
|
|
25
|
+
* ② **两项刻意不合成一个布尔**(`permissionModeAuto` 同判据):`armed`(表非空)与 `replaced`(整表
|
|
26
|
+
* 替换座被写)回答的是两件事 —— 一个「引擎会不会对这类路径弹卡」,一个「运维有没有动过那张表」。
|
|
27
|
+
* 合成之后,「缺省表在岗」与「运维自定义了一张同样非空的表」得到同一个 true,而壳/运维对这两种
|
|
28
|
+
* 要做的事不同。
|
|
29
|
+
* ③ **行内容只上 operator 面**:缺省 51 行是 CC 公开表(零增量信息),但**部署自定义行**可能含内部
|
|
30
|
+
* 路径名(`.corp-deploy-key` 之类)⇒ 逐行披露给租户面等于告诉想绕过的人「哪些名字不受保护」。
|
|
31
|
+
*
|
|
32
|
+
* ## 与 `SENSITIVE_WRITE_PATTERNS` 的关系(**并列,不合流**)
|
|
33
|
+
*
|
|
34
|
+
* 本仓另有一套**模式型**(正则/glob)写 deny 集(`createSensitivePathPolicy`,旋钮
|
|
35
|
+
* `SENSITIVE_WRITE_PATTERNS`,经 deployment-governance 无条件施加)。两套按 core 的 d.ts 是并列机制、
|
|
36
|
+
* 互不覆盖,所以两个读面**分开报**:合成一个 `writeProtected: true` 会让壳答不出「这条路径是被哪一套
|
|
37
|
+
* 拦的」,而两套的解法完全不同(一套改模式,一套改名表)。
|
|
38
|
+
*/
|
|
39
|
+
import { type WriteProtectedRow } from "@sema-agent/core";
|
|
40
|
+
import type { ServiceConfig } from "./config-types.js";
|
|
41
|
+
/** 表的来源(闭集;`switch` 穷尽 —— 新增词 = 编译红,[ref] 词表纪律)。 */
|
|
42
|
+
export declare const WRITE_PROTECTION_SOURCES: readonly ["default", "extra", "replace", "off"];
|
|
43
|
+
export type WriteProtectionSource = (typeof WRITE_PROTECTION_SOURCES)[number];
|
|
44
|
+
/** operator 面(`GET /v1/diagnostics/wiring` 的 `writeProtection` 段)。 */
|
|
45
|
+
export interface WriteProtectionPosture {
|
|
46
|
+
/** 生效表的**逐行**内容,逐字来自 core `compileWriteProtection().rows`(空 = 显式无表)。 */
|
|
47
|
+
readonly rows: readonly WriteProtectedRow[];
|
|
48
|
+
/** `default` = 两根旋钮都没写(引擎缺省表在岗);`extra` = 缺省表 + 增量;`replace` = 整表替换;
|
|
49
|
+
* `off` = 整表替换成空表(合法但响亮的姿势)。 */
|
|
50
|
+
readonly source: WriteProtectionSource;
|
|
51
|
+
/** 整表替换**丢掉**的缺省行名(替换族才在场;`extra`/`default` 结构上丢不掉任何行)。 */
|
|
52
|
+
readonly droppedDefaultRows?: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
/** 租户面窄投影(`GET /v1/capabilities` 的 `writeProtection` 位)。**不含行内容**(见顶注纪律③)。 */
|
|
55
|
+
export interface WriteProtectionCapability {
|
|
56
|
+
/** 生效表非空 = 引擎真会对表行弹卡。 */
|
|
57
|
+
readonly armed: boolean;
|
|
58
|
+
/** 生效表的**行数**(内容不上租户面)。 */
|
|
59
|
+
readonly rows: number;
|
|
60
|
+
/** 整表替换座被写(缺省表不再原样在岗)。与 `armed` 刻意不合成(顶注纪律②)。 */
|
|
61
|
+
readonly replaced: boolean;
|
|
62
|
+
}
|
|
63
|
+
/** 本模块读的 config 面(取型而非重述 —— 键名/形永不与真配置漂)。 */
|
|
64
|
+
export type WriteProtectionConfigView = Pick<ServiceConfig, "writeProtectedPaths">;
|
|
65
|
+
/**
|
|
66
|
+
* boot 期算一次的姿态(纯函数、零 I/O)。**同一只解析器**:`rows` 是 core 编译判子时用的那份表,不是
|
|
67
|
+
* 本仓照着 config 又算了一遍的复述 —— 「诊断页说 51 行、引擎按 2 行判」在结构上不可能。
|
|
68
|
+
*
|
|
69
|
+
* `compileWriteProtection` 对**空表**返回 `undefined`(core 契约:没有表就不挂 tighten)⇒ 本函数把它
|
|
70
|
+
* 读成 `rows: []`,那是 `off` 姿势的诚实形,不是「读不出来」。
|
|
71
|
+
*/
|
|
72
|
+
export declare function buildWriteProtectionPosture(config: WriteProtectionConfigView): WriteProtectionPosture;
|
|
73
|
+
/**
|
|
74
|
+
* operator 面 → 租户面的窄投影。
|
|
75
|
+
*
|
|
76
|
+
* `null` = **这个进程说不出来**(不是 composition root 装起来的测试夹具形)—— 与 `sql` / `memoryPosture`
|
|
77
|
+
* 两位同姿势:不铸一个看起来像答案的空壳(那会让壳把夹具形读成「本部署无表」)。
|
|
78
|
+
*/
|
|
79
|
+
export declare function projectWriteProtectionCapability(posture: WriteProtectionPosture | undefined): WriteProtectionCapability | null;
|
|
80
|
+
//# sourceMappingURL=write-protection.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { WRITE_PROTECTED_DEFAULT_TABLE, compileWriteProtection, resolveWriteProtectedTable } from "@sema-agent/core";
|
|
2
|
+
export const WRITE_PROTECTION_SOURCES = ["default", "extra", "replace", "off"];
|
|
3
|
+
function defaultRowsMissingFrom(rows) {
|
|
4
|
+
return WRITE_PROTECTED_DEFAULT_TABLE.filter((row) => resolveWriteProtectedTable([...rows, row]).length !== rows.length).map((row) => row.name);
|
|
5
|
+
}
|
|
6
|
+
export function buildWriteProtectionPosture(config) {
|
|
7
|
+
const decision = config.writeProtectedPaths;
|
|
8
|
+
const rows = compileWriteProtection(decision?.entries)?.rows ?? [];
|
|
9
|
+
if (decision === undefined)
|
|
10
|
+
return { rows, source: "default" };
|
|
11
|
+
if (decision.knob === "extra")
|
|
12
|
+
return { rows, source: "extra" };
|
|
13
|
+
return { rows, source: rows.length === 0 ? "off" : "replace", droppedDefaultRows: defaultRowsMissingFrom(rows) };
|
|
14
|
+
}
|
|
15
|
+
export function projectWriteProtectionCapability(posture) {
|
|
16
|
+
if (posture === undefined)
|
|
17
|
+
return null;
|
|
18
|
+
return { armed: posture.rows.length > 0, rows: posture.rows.length, replaced: replacedOf(posture.source) };
|
|
19
|
+
}
|
|
20
|
+
function replacedOf(source) {
|
|
21
|
+
switch (source) {
|
|
22
|
+
case "default":
|
|
23
|
+
case "extra":
|
|
24
|
+
return false;
|
|
25
|
+
case "replace":
|
|
26
|
+
case "off":
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=write-protection.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.63.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",
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "7.5.
|
|
58
|
-
"@sema-agent/settings-schema": "1.
|
|
57
|
+
"@sema-agent/core": "7.5.2",
|
|
58
|
+
"@sema-agent/settings-schema": "1.7.2",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|
|
61
61
|
"mysql2": "^3.22.4",
|