@sema-agent/server 7.133.0 → 7.134.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 +87 -0
- package/README.md +2 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +129 -11
- package/deploy/sema-up/README.md +16 -0
- package/deploy/sema-up/chart/Chart.yaml +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
- package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +14 -0
- package/deploy/sema-up/chart/templates/server.yaml +14 -0
- package/deploy/sema-up/chart/values-full-single.yaml +5 -1
- package/deploy/sema-up/chart/values.yaml +34 -0
- package/dist/approval-ask-audit-store.d.ts +1 -1
- package/dist/approval-card.d.ts +4 -4
- package/dist/approval-card.js +1 -2
- package/dist/approval-content-kind.d.ts +5 -2
- package/dist/approval-content-kind.js +16 -3
- package/dist/approval-gate-currency.d.ts +72 -0
- package/dist/approval-gate-currency.js +94 -0
- package/dist/approval.d.ts +6 -0
- package/dist/approval.js +31 -14
- package/dist/boot/coordinators.d.ts +6 -1
- package/dist/boot/coordinators.js +6 -3
- package/dist/boot/leader.d.ts +2 -0
- package/dist/boot/leader.js +2 -1
- package/dist/boot/read-deny-anchor-audit.js +2 -2
- package/dist/boot/runner-deps.d.ts +18 -5
- package/dist/boot/runner-deps.js +9 -0
- package/dist/boot/stage-06-runners.d.ts +4 -3
- package/dist/boot/stage-06-runners.js +15 -2
- package/dist/boot/stage-07-capability-layer.d.ts +2 -1
- package/dist/boot/stage-08-reapers.d.ts +2 -1
- package/dist/boot/stage-09-leader.d.ts +2 -1
- package/dist/boot/stage-09-leader.js +2 -2
- package/dist/boot/stage-10-http-server.d.ts +2 -1
- package/dist/boot/stage-10-http-server.js +2 -1
- package/dist/boot/stores.d.ts +8 -0
- package/dist/boot/stores.js +8 -12
- package/dist/boot/web-admin.d.ts +6 -0
- package/dist/boot/web-admin.js +11 -2
- package/dist/budget.js +5 -1
- package/dist/caller-text-echo.d.ts +2 -0
- package/dist/caller-text-echo.js +2 -1
- package/dist/canonical-json.d.ts +2 -41
- package/dist/canonical-json.js +2 -13
- package/dist/capabilities/composite-members.d.ts +1 -1
- package/dist/capabilities/composite-members.js +1 -1
- package/dist/capabilities/mcp-probe.js +2 -2
- package/dist/capability-facts.d.ts +2 -0
- package/dist/capability-facts.js +4 -4
- package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
- package/dist/checkpoint-ctx-seal-m28.js +19 -0
- package/dist/field-conflict.d.ts +2 -1
- package/dist/field-conflict.js +1 -0
- package/dist/file-history-disclosure.d.ts +34 -5
- package/dist/file-history-disclosure.js +2 -2
- package/dist/fleet/fleet-bus.d.ts +4 -0
- package/dist/fleet/fleet-bus.js +1 -0
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/admission.js +8 -0
- package/dist/http/approval-sweeps.js +3 -1
- package/dist/http/checkpoint-ctx.d.ts +157 -2
- package/dist/http/checkpoint-ctx.js +158 -2
- package/dist/http/dispatch.js +3 -3
- package/dist/http/durable-run-leg.js +3 -2
- package/dist/http/idempotency.d.ts +2 -2
- package/dist/http/idempotency.js +2 -2
- package/dist/http/principal-gate.d.ts +32 -6
- package/dist/http/principal-gate.js +27 -4
- package/dist/http/refusal-body.d.ts +12 -0
- package/dist/http/refusal-body.js +11 -0
- package/dist/http/request-guards.d.ts +0 -9
- package/dist/http/request-guards.js +2 -16
- package/dist/http/resume-ctx-read.d.ts +48 -1
- package/dist/http/resume-ctx-read.js +26 -3
- package/dist/http/resume-legs.d.ts +2 -1
- package/dist/http/resume-legs.js +37 -22
- package/dist/http/route-ctx.d.ts +0 -9
- package/dist/http/routes/agents-roster.js +2 -3
- package/dist/http/routes/approvals-assistant.js +43 -9
- package/dist/http/routes/capabilities.js +1 -1
- package/dist/http/routes/diagnostics.d.ts +3 -3
- package/dist/http/routes/diagnostics.js +1 -1
- package/dist/http/routes/fleet.js +4 -4
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/rules.js +4 -4
- package/dist/http/routes/runs.js +30 -9
- package/dist/http/routes/session-sync.d.ts +3 -2
- package/dist/http/routes/session-sync.js +38 -20
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions-list.js +3 -4
- package/dist/http/routes/sessions.js +4 -4
- package/dist/http/routes/tasks.js +21 -16
- package/dist/http/routes/trace-usage.js +7 -9
- package/dist/http/run-declared-families.d.ts +16 -0
- package/dist/http/run-declared-families.js +12 -0
- package/dist/http/server.d.ts +6 -0
- package/dist/http/server.js +5 -2
- package/dist/http/submit-ctx.d.ts +4 -3
- package/dist/http/submit-ctx.js +19 -3
- package/dist/http/wire-types.d.ts +7 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/leader/wire.d.ts +4 -0
- package/dist/leader/wire.js +8 -7
- package/dist/main.js +1 -1
- package/dist/memory-origin-fence.js +2 -2
- package/dist/model-compat.d.ts +1 -1
- package/dist/observability/prompt-manifest.d.ts +19 -8
- package/dist/observability/prompt-manifest.js +9 -8
- package/dist/observability/tool-trace.js +8 -9
- package/dist/orchestration/workflow-park-decision.d.ts +1 -1
- package/dist/orchestration/workflow-park-decision.js +1 -0
- package/dist/parked-decide.d.ts +4 -5
- package/dist/parked-decide.js +3 -1
- package/dist/permission-rule-vocab.d.ts +1 -23
- package/dist/permission-rule-vocab.js +0 -13
- package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
- package/dist/plugins/approval-payload-fingerprint.js +1 -1
- package/dist/plugins/caching-session-store.d.ts +1 -1
- package/dist/plugins/caching-session-store.js +4 -3
- package/dist/plugins/checkpoint-store-sql.d.ts +22 -1
- package/dist/plugins/checkpoint-store-sql.js +45 -1
- package/dist/plugins/local-checkpoint-store.d.ts +13 -2
- package/dist/plugins/local-checkpoint-store.js +26 -0
- package/dist/plugins/local-session-store.d.ts +12 -9
- package/dist/plugins/local-session-store.js +23 -14
- package/dist/plugins/local-session-window.d.ts +12 -6
- package/dist/plugins/local-session-window.js +1 -17
- package/dist/plugins/one-time-migrations.d.ts +11 -0
- package/dist/plugins/one-time-migrations.js +12 -0
- package/dist/plugins/permission-rule-store-file.js +10 -6
- package/dist/plugins/permission-rule-store-sql.js +8 -8
- package/dist/plugins/pg-session-storage.d.ts +1 -1
- package/dist/plugins/pg-session-storage.js +39 -17
- package/dist/plugins/remote-env-device.d.ts +14 -7
- package/dist/plugins/remote-env-device.js +23 -15
- package/dist/plugins/session-ownership.d.ts +11 -0
- package/dist/plugins/session-ownership.js +11 -0
- package/dist/plugins/session-placement.d.ts +12 -14
- package/dist/plugins/session-placement.js +2 -15
- package/dist/plugins/tidb-session-store.d.ts +1 -1
- package/dist/plugins/tidb-session-store.js +39 -17
- package/dist/request-tool-axes.d.ts +12 -0
- package/dist/request-tool-axes.js +20 -0
- package/dist/run-local.js +1 -0
- package/dist/runs.d.ts +2 -1
- package/dist/runs.js +4 -3
- package/dist/sealed-key.d.ts +32 -0
- package/dist/sealed-key.js +55 -1
- package/dist/security.d.ts +9 -7
- package/dist/session-error-wire.d.ts +12 -5
- package/dist/session-error-wire.js +4 -1
- package/dist/session-sync-kernel.d.ts +37 -10
- package/dist/session-sync-kernel.js +10 -0
- package/dist/task-a2a.js +4 -3
- package/dist/task-mcp.d.ts +2 -2
- package/dist/task-mcp.js +20 -8
- package/dist/task-settings.d.ts +5 -1
- package/dist/task-settings.js +1 -1
- package/dist/tool-approval.d.ts +151 -52
- package/dist/tool-approval.js +113 -20
- package/dist/tool-rule-names.d.ts +6 -0
- package/dist/trace/core-keyset-guard.d.ts +4 -3
- package/dist/trace/exit-seat.d.ts +13 -1
- package/dist/trace/exit-seat.js +55 -1
- package/dist/trace/ledger-events.d.ts +3 -1
- package/dist/trace/project.d.ts +18 -1
- package/dist/trace/project.js +16 -4
- package/dist/trace/redact.d.ts +6 -3
- package/dist/trace/redact.js +191 -23
- package/dist/trace/self-minted-wire.d.ts +4 -0
- package/dist/trace/self-minted-wire.js +1 -0
- package/dist/trace/wire-projection-faces.d.ts +6 -0
- package/dist/trace/wire-projection-faces.js +6 -0
- package/dist/web-admin-client/skills-mcp.d.ts +1 -1
- package/dist/web-admin-client/skills-mcp.js +2 -3
- package/dist/web-admin-client/types.d.ts +1 -1
- package/package.json +2 -2
- package/scripts/drain-check-local.mjs +34 -2
- package/scripts/drain-check.mysql.sql +20 -2
- package/scripts/drain-check.pg.sql +20 -2
- package/dist/mcp-tool-faces.d.ts +0 -69
- package/dist/mcp-tool-faces.js +0 -104
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/** 回显的字符上限(剥完控制与双向字符之后数)。 */
|
|
2
2
|
export declare const CALLER_TEXT_ECHO_MAX_CHARS = 64;
|
|
3
|
+
/** 回显被截时补在末尾的记号 —— 回显口径的唯一属主;出口座的日志字段行(`trace/exit-seat.ts`)读同一枚,不另写一个 `…`。 */
|
|
4
|
+
export declare const CALLER_TEXT_CUT_MARK = "\u2026";
|
|
3
5
|
/** 站点给的回显选项。 */
|
|
4
6
|
export interface EchoCallerTextOptions {
|
|
5
7
|
/** 回显的字符上限(剥完控制与双向字符之后数);不给 = {@link CALLER_TEXT_ECHO_MAX_CHARS}。必须是正整数,否则抛 `RangeError`(编程错误,不静默落成某个长度)。 */
|
package/dist/caller-text-echo.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { stripControlAndBidi } from "@sema-agent/settings-schema";
|
|
2
2
|
export const CALLER_TEXT_ECHO_MAX_CHARS = 64;
|
|
3
|
+
export const CALLER_TEXT_CUT_MARK = "…";
|
|
3
4
|
export function echoCallerText(text, opts) {
|
|
4
5
|
const max = opts?.maxChars ?? CALLER_TEXT_ECHO_MAX_CHARS;
|
|
5
6
|
if (!Number.isInteger(max) || max < 1)
|
|
6
7
|
throw new RangeError(`echoCallerText: maxChars must be a positive integer, got ${String(max)}`);
|
|
7
8
|
const clean = stripControlAndBidi(text);
|
|
8
|
-
return clean.length > max ? `${clean.slice(0, max)}
|
|
9
|
+
return clean.length > max ? `${clean.slice(0, max)}${CALLER_TEXT_CUT_MARK}` : clean;
|
|
9
10
|
}
|
|
10
11
|
export function quotedCallerText(text) {
|
|
11
12
|
return `"${echoCallerText(text)}"`;
|
package/dist/canonical-json.d.ts
CHANGED
|
@@ -1,46 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* ([ref] / [ref],2026-08-31 合并批)。
|
|
4
|
-
*
|
|
5
|
-
* ## 为什么只能有一个
|
|
6
|
-
* 合并前本仓有 **5 处**手写复刻(memory-origin-fence / permission-rule-store-sql / bench-l8 artifact /
|
|
7
|
-
* web-admin restart-signal / mcp-revocation),每处一份「排 key、递归、拼串」——同一个算法五份实现,
|
|
8
|
-
* 语义在边缘(undefined 值键、函数成员、非有限数)各自漂了一点。指纹型代码的病灶恰恰在边缘:同一份内容算出
|
|
9
|
-
* 两个指纹 = 落库 checksum 失配 / 误签 restart / 包完整性自校验对不上。
|
|
10
|
-
*
|
|
11
|
-
* ## 语义 = core `canonicalJsonStringify` 的**逐字港**(fidelity 高于「更漂亮」)
|
|
12
|
-
* 治理携出包(`POST /v1/memory/export`)的 `integrity.sectionHashes.entries` 必须与 core 铸包/校验时算的
|
|
13
|
-
* 哈希**逐字节相同**(core `memoryBundleInvalid` 三向对账),而 core 公共面只出口 `computeMemoryBundleHash`
|
|
14
|
-
* (Record 特化),不出口这只序列化器(`dist/core/memory-engine/file-backend.js#canonicalJsonStringify`,
|
|
15
|
-
* exports 封死)——所以本仓必须持有一份逐字港,且**不得**在下游「修」它的边缘行为(源头修复纪律:
|
|
16
|
-
* core 若改形,港随之改;本仓不做第二个写者)。防漂移钉 = `test/memory-origin-fence.test.ts` §④(期望哈希全
|
|
17
|
-
* 经 core 出口独立算出)。
|
|
18
|
-
*
|
|
19
|
-
* 语义表(与 core 逐字同;`test/canonical-json.test.ts` 逐行钉):
|
|
20
|
-
* · 对象:own 可枚举键,**值为 `undefined` 的键省略**(与 `JSON.stringify` 同),其余按 UTF-16 码元序排序
|
|
21
|
-
* (`Object.keys().sort()`,数字形键同样按字符串序:`"10" < "2"`);键经 `JSON.stringify` 转义。
|
|
22
|
-
* · 数组:保序;元素 `undefined` / 函数 / symbol ⇒ `null`(与 `JSON.stringify` 同)。
|
|
23
|
-
* · 标量:`JSON.stringify`(`NaN`/`±Infinity` ⇒ `null`、`-0` ⇒ `0`、字符串转义与 `JSON.stringify` 同)。
|
|
24
|
-
* · **非 JSON 输入的港继承形(不是本仓的设计,是 core 的形,如实记)**:对象成员为函数/symbol 时,键**保留**
|
|
25
|
-
* 而值经 `JSON.stringify` 得 `undefined` ⇒ 拼出字面 `undefined`(非法 JSON 文本);顶层 `undefined`/函数/symbol
|
|
26
|
-
* ⇒ 返回 `undefined`(非字符串)。五个站点的真实输入全是 JSON 形(JSON.parse 产物 / DB 行 / zod 解析产物),
|
|
27
|
-
* 这两条**不可达**;需要「非法输入响亮拒」的站点用 {@link canonicalJsonStrict}。
|
|
28
|
-
* · `Date`/`Map`/`Set`/类实例:按普通对象处理(own 可枚举键 ⇒ `Date` 成 `{}`),**不调 `toJSON`**——与 core 同。
|
|
29
|
-
* · `bigint`:`JSON.stringify` 抛 TypeError,原样上抛。
|
|
30
|
-
*
|
|
31
|
-
* ## 不在本文件里的第二形(如实登记)
|
|
32
|
-
* `src/web-admin-client/restart-signal.ts#stableStringify`(mcp-revocation 同源改调)是**另一个语义**:undefined 值键
|
|
33
|
-
* **保留为 `"k":null`**。两族在 JSON 形输入上逐字节相同(golden 交叉读数已证),分歧只在进程内构造的
|
|
34
|
-
* `{k: undefined}` 形——restart 指纹的输入正是进程内切片对象,该形原则上可达,而 `{k:undefined}` 与 `{}` 是否
|
|
35
|
-
* 同指纹是 restart-signal 轴上的**行为**(多签/少签一次 fleet 滚动重启),不是序列化器的私事 ⇒ 本批不并
|
|
36
|
-
* (语义恒等才可合并),golden 各自钉,K→O 统一另立件走三问。
|
|
37
|
-
*/
|
|
38
|
-
/** core `canonicalJsonStringify` 的逐字港(语义表见文件头)。 */
|
|
39
|
-
export declare function canonicalJson(value: unknown): string;
|
|
40
|
-
/**
|
|
41
|
-
* 严格形:先按**同一遍历序**校验,再交 {@link canonicalJson} 序列化 —— 被签名/被内容哈希的载荷(bench/l8 部署
|
|
2
|
+
* 严格形:先按**同一遍历序**校验,再交 core `canonicalJsonStringify` 序列化 —— 被签名/被内容哈希的载荷(bench/l8 部署
|
|
42
3
|
* 真实性 artifact)不许把 `NaN`/`Infinity` 静默写成 `null`(签名盖在坏字节上),也不许把函数/bigint/顶层
|
|
43
|
-
* `undefined` 当作可序列化。校验通过的输入域上,输出与
|
|
4
|
+
* `undefined` 当作可序列化。校验通过的输入域上,输出与 core `canonicalJsonStringify` **逐字节相同**(golden 钉);
|
|
44
5
|
* 拒绝集与错误文案 = 合并前 `src/bench/l8/artifact.ts#serialize` 逐字(`test/l8-deploy-contract.test.ts` 钉
|
|
45
6
|
* `/non-finite/`):
|
|
46
7
|
* · 非有限数(任意深度)⇒ `canonicalJson: non-finite number is not serializable`
|
package/dist/canonical-json.js
CHANGED
|
@@ -1,18 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
if (Array.isArray(value))
|
|
3
|
-
return `[${value.map((v) => (v === undefined || typeof v === "function" || typeof v === "symbol" ? "null" : canonicalJson(v))).join(",")}]`;
|
|
4
|
-
if (value !== null && typeof value === "object") {
|
|
5
|
-
const rec = value;
|
|
6
|
-
const keys = Object.keys(rec)
|
|
7
|
-
.filter((k) => rec[k] !== undefined)
|
|
8
|
-
.sort();
|
|
9
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(rec[k])}`).join(",")}}`;
|
|
10
|
-
}
|
|
11
|
-
return JSON.stringify(value);
|
|
12
|
-
}
|
|
1
|
+
import { canonicalJsonStringify } from "@sema-agent/core";
|
|
13
2
|
export function canonicalJsonStrict(value) {
|
|
14
3
|
assertCanonicalJsonSerializable(value);
|
|
15
|
-
return
|
|
4
|
+
return canonicalJsonStringify(value);
|
|
16
5
|
}
|
|
17
6
|
function assertCanonicalJsonSerializable(v) {
|
|
18
7
|
if (v === null)
|
|
@@ -45,7 +45,7 @@ export declare const COMPOSITE_CAPABILITY_MEMBERS: {
|
|
|
45
45
|
readonly centerWiring: readonly ["effectivePull", "effectiveRevision", "lease", "leaseHealth", "failMode", "usageReport", "issuer", "worker", "caller"];
|
|
46
46
|
readonly webSearch: readonly ["backend"];
|
|
47
47
|
readonly background: readonly ["listFace", "exitFaces"];
|
|
48
|
-
readonly taskSettings: readonly ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "tightenOnlySeats"];
|
|
48
|
+
readonly taskSettings: readonly ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "tightenOnlySeats", "approvalFrameFamilies"];
|
|
49
49
|
readonly settings: readonly ["permissions"];
|
|
50
50
|
readonly permissionModeAuto: readonly ["accepted", "classifierSeat", "entitlementSource", "armed", "reason", "model"];
|
|
51
51
|
readonly writeProtection: readonly ["armed", "rows", "replaced"];
|
|
@@ -6,7 +6,7 @@ export const COMPOSITE_CAPABILITY_MEMBERS = {
|
|
|
6
6
|
centerWiring: ["effectivePull", "effectiveRevision", "lease", "leaseHealth", "failMode", "usageReport", "issuer", "worker", "caller"],
|
|
7
7
|
webSearch: ["backend"],
|
|
8
8
|
background: ["listFace", "exitFaces"],
|
|
9
|
-
taskSettings: ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "tightenOnlySeats"],
|
|
9
|
+
taskSettings: ["permissions", "permissionMode", "model", "outputStyle", "env", "hooks", "pluginHooks", "managedHookGroups", "tightenOnlySeats", "approvalFrameFamilies"],
|
|
10
10
|
settings: ["permissions"],
|
|
11
11
|
permissionModeAuto: ["accepted", "classifierSeat", "entitlementSource", "armed", "reason", "model"],
|
|
12
12
|
writeProtection: ["armed", "rows", "replaced"],
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { probeMcpServers } from "@sema-agent/core";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { createFixedWindowRateLimiter } from "../device-enrollment.js";
|
|
4
|
-
import {
|
|
4
|
+
import { canonicalJsonStringify } from "@sema-agent/core";
|
|
5
5
|
import { keyClosureClause, scanKeyClosure } from "../request-key-closure.js";
|
|
6
6
|
import { requestMcpDeclarationRefusal, validateRequestMcp } from "../task-mcp.js";
|
|
7
7
|
import { specWithholdsMcpAssertionHeader, toCoreMcpServerSpec } from "../mcp-assertion-header.js";
|
|
@@ -86,7 +86,7 @@ export function buildMcpProbeIdentity(principal) {
|
|
|
86
86
|
return principal !== undefined && principal.length > 0 ? principal : "local";
|
|
87
87
|
}
|
|
88
88
|
export function buildMcpProbeCacheKey(principal, specs) {
|
|
89
|
-
return `${buildMcpProbeIdentity(principal)}::${createHash("sha256").update(
|
|
89
|
+
return `${buildMcpProbeIdentity(principal)}::${createHash("sha256").update(canonicalJsonStringify(specs), "utf8").digest("hex")}`;
|
|
90
90
|
}
|
|
91
91
|
export function buildMcpProbeRateKey(principal) {
|
|
92
92
|
return `mcp-probe:${buildMcpProbeIdentity(principal)}`;
|
|
@@ -220,6 +220,8 @@ export declare function readRootGrant(d: Pick<Deps, "config">): Fact<"read_root_
|
|
|
220
220
|
* · 活卡协调器缺席 ⇒ `[]`(没有审批卡可呈)。
|
|
221
221
|
* 对 why 闭集穷举:`streamApproval` 的拒因多一个 ⇒ 这里编译红,逼着给新拒因定族。三条执行腿选族读的是同一只判据的 `active`,
|
|
222
222
|
* 与本映射的「在场 ⇒ 呈卡族」是同一条规则(真腿对拍钉在 `test/stream-approval-on-e2e.test.ts`)。没有端点按它拒:它是消费方分派帧族的读点,`streamApproval` 的词义不变。
|
|
223
|
+
* S-1914:它也是壳**声明**的依据 —— 一条 run 只在 `settings.approvalFrameFamilies` 含本位那一个族时有活审批通道(三条腿判的是同一只映射
|
|
224
|
+
* `presentedApprovalFrameFamily`);不含 ⇒ 这条腿不呈卡,问当场交无人值守政策口。本位的值不因声明而变。
|
|
223
225
|
*/
|
|
224
226
|
export declare function approvalFrameFamilies(stream: ReturnType<typeof streamApproval>): readonly ApprovalFrameFamily[];
|
|
225
227
|
export declare function deviceLane(d: Pick<Deps, "deviceHub">): Fact<"device_hub_absent", {
|
package/dist/capability-facts.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { buildOperatorRosterWayOut, operatorRosterPresent } from "./http/principal-gate.js";
|
|
2
2
|
import { mcpInjectionHonored } from "./task-mcp.js";
|
|
3
3
|
import { cwdHonored } from "./task-cwd.js";
|
|
4
|
-
import { resolveStreamApprovalGate } from "./tool-approval.js";
|
|
4
|
+
import { presentedApprovalFrameFamily, resolveStreamApprovalGate } from "./tool-approval.js";
|
|
5
5
|
import { selfOrchestrationDenial } from "./task-workflow.js";
|
|
6
6
|
import { centerEntitlementSourceWired } from "./runtime-caps-resolver.js";
|
|
7
7
|
import { overwriteWipeIncapable } from "./session-sync.js";
|
|
@@ -196,7 +196,7 @@ export function streamApproval(d) {
|
|
|
196
196
|
toolApprovalEnabled: d.toolApproval !== undefined,
|
|
197
197
|
streamApprovalEnabled: d.config.streamApproval?.enabled === true,
|
|
198
198
|
backend: d.backend,
|
|
199
|
-
|
|
199
|
+
checkpointStore: d.checkpointStore,
|
|
200
200
|
});
|
|
201
201
|
if (gate.active)
|
|
202
202
|
return yes({ askStore: gate.askStore });
|
|
@@ -220,14 +220,14 @@ export function readRootGrant(d) {
|
|
|
220
220
|
}
|
|
221
221
|
export function approvalFrameFamilies(stream) {
|
|
222
222
|
if (stream.available)
|
|
223
|
-
return [
|
|
223
|
+
return [presentedApprovalFrameFamily(true)];
|
|
224
224
|
switch (stream.why) {
|
|
225
225
|
case "tool_approval_disabled":
|
|
226
226
|
return [];
|
|
227
227
|
case "stream_approval_disabled":
|
|
228
228
|
case "store_backend_absent":
|
|
229
229
|
case "checkpoint_store_absent":
|
|
230
|
-
return [
|
|
230
|
+
return [presentedApprovalFrameFamily(false)];
|
|
231
231
|
default: {
|
|
232
232
|
const unhandled = stream.why;
|
|
233
233
|
throw new Error(`unhandled stream-approval why: ${JSON.stringify(unhandled)}`);
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次性迁移 M28 —— 升级之前写下的停驻行(`checkpoint_ctx`)里,请求面凭据位上的明文起服**原地封好**。
|
|
3
|
+
* 🔴 M28 · 窗口见 `plugins/one-time-migrations.ts` 的 ONE_TIME_MIGRATIONS 登记表(窗、删法、删后由谁接住只写在那一行)。
|
|
4
|
+
*
|
|
5
|
+
* ## 为什么要它(持久数据面)
|
|
6
|
+
* 7.134.0 起凭据位上的值在 ctx 里只以封签存在(规则属主 `http/checkpoint-ctx.ts` 末段「凭据位密封」),续跑上下文读口对凭据位上的明文答
|
|
7
|
+
* 409 `conflict.checkpoint_ctx_unsealable`(`reason:"plaintext"`)。升级前停驻的行是明文 —— 持久数据面不能「翻红」,所以起服扫一遍、用**同一只**封口
|
|
8
|
+
* (`sealStoredCheckpointCtxM28`,与写侧 `sealCheckpointCtx` 同一只走法)封好、原地回写;不删行、不动续跑时钟、已封的与不带凭据的行零写。
|
|
9
|
+
*
|
|
10
|
+
* ## 为什么不在 `one-time-migrations.ts` 的迁移段里
|
|
11
|
+
* 封要保管店派生的封签器,而那一段跑在店层(起服第 3 段,`openStores`),比建店的 web-admin 运行时能把封签器交下去的地方早、也不该反向依赖它;
|
|
12
|
+
* 本条在起服第 6 段 checkpoint 店建成、封签器的装配判(`CHECKPOINT_CTX_SEALING_REQUIRED`)过了之后调一次,早于任何路由装配(与 M10 / M19 / M25
|
|
13
|
+
* 同为在迁移段之外跑的条目)。读数一行 info `checkpoint_ctx_credentials_sealed`,不进 `OneTimeMigrationReport`(登记行 `reportKeys` 空)。
|
|
14
|
+
*/
|
|
15
|
+
import { type CheckpointCtxSealer, type SealedCheckpointCtx } from "./http/checkpoint-ctx.js";
|
|
16
|
+
import { type OneTimeMigrationWindow } from "./plugins/one-time-migrations.js";
|
|
17
|
+
import type { CtxRowsRewriteM28 } from "./plugins/checkpoint-store-sql.js";
|
|
18
|
+
/** 两孪生的店腿(`SqlCheckpointStore` / `LocalCheckpointStore` 的 `rewriteCtxRowsM28`)。 */
|
|
19
|
+
export interface CheckpointCtxRowsRewriterM28 {
|
|
20
|
+
rewriteCtxRowsM28(rewrite: (sessionId: string, ctx: object) => {
|
|
21
|
+
readonly ctx: SealedCheckpointCtx;
|
|
22
|
+
readonly values: number;
|
|
23
|
+
} | undefined): Promise<CtxRowsRewriteM28>;
|
|
24
|
+
}
|
|
25
|
+
/** M28 一次起服的读数(起服 info 行逐键打出;测试按它断言)。`window: "closed"` ⇒ 一行都没读(其余全 0)。 */
|
|
26
|
+
export interface CheckpointCtxSealM28Report {
|
|
27
|
+
readonly window: "open" | "closed";
|
|
28
|
+
readonly rowsScanned: number;
|
|
29
|
+
readonly rowsSealed: number;
|
|
30
|
+
readonly valuesSealed: number;
|
|
31
|
+
readonly rowsUnreadable: number;
|
|
32
|
+
readonly rowsTooLarge: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 跑一次 M28(`today` = UTC 日历日 YYYY-MM-DD;`window` 缺省 = 登记表 M28 那一行 —— 只有测试递别的窗)。窗关 ⇒ 不扫。
|
|
36
|
+
* 店腿的每一行:`sealStoredCheckpointCtxM28` 交回新体(凭据位上的明文封好)⇒ 回写;交回 `undefined`(没有明文)⇒ 不写。
|
|
37
|
+
*/
|
|
38
|
+
export declare function sealCheckpointCtxCredentialsM28(store: CheckpointCtxRowsRewriterM28, sealer: CheckpointCtxSealer, opts: {
|
|
39
|
+
readonly today: string;
|
|
40
|
+
readonly window?: Pick<OneTimeMigrationWindow, "removableAfter">;
|
|
41
|
+
}): Promise<CheckpointCtxSealM28Report>;
|
|
42
|
+
//# sourceMappingURL=checkpoint-ctx-seal-m28.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { sealStoredCheckpointCtxM28 } from "./http/checkpoint-ctx.js";
|
|
2
|
+
import { ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./plugins/one-time-migrations.js";
|
|
3
|
+
function m28Window() {
|
|
4
|
+
const row = ONE_TIME_MIGRATIONS.find((w) => w.id === "M28");
|
|
5
|
+
if (row === undefined)
|
|
6
|
+
throw new Error("checkpoint-ctx-seal-m28: the one-time migration registry has no M28 row — this migration outlived its registry row; delete it with that row");
|
|
7
|
+
return row;
|
|
8
|
+
}
|
|
9
|
+
export async function sealCheckpointCtxCredentialsM28(store, sealer, opts) {
|
|
10
|
+
if (!migrationWindowOpen(opts.window ?? m28Window(), opts.today)) {
|
|
11
|
+
return { window: "closed", rowsScanned: 0, rowsSealed: 0, valuesSealed: 0, rowsUnreadable: 0, rowsTooLarge: 0 };
|
|
12
|
+
}
|
|
13
|
+
const r = await store.rewriteCtxRowsM28((sessionId, ctx) => {
|
|
14
|
+
const out = sealStoredCheckpointCtxM28(ctx, sealer, sessionId);
|
|
15
|
+
return out === undefined ? undefined : { ctx: out.ctx, values: out.valuesSealed };
|
|
16
|
+
});
|
|
17
|
+
return { window: "open", rowsScanned: r.scanned, rowsSealed: r.rewritten, valuesSealed: r.valuesRewritten, rowsUnreadable: r.unreadable, rowsTooLarge: r.tooLarge };
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=checkpoint-ctx-seal-m28.js.map
|
package/dist/field-conflict.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** 冲突的类(闭集)。同一个约束的几个拒点同一个词;新增一类见文件头。 */
|
|
2
|
-
export declare const FIELD_CONFLICT_KINDS: readonly ["cascade_verify", "image_profile", "resume_anchor", "system_prompt_fold", "file_history_restore", "skills_disabled", "existing_session", "scratchpad_root", "ask_answer", "policy_person_ask", "plan_edit", "plan_mode_after", "bound_call_door", "remember_decision"];
|
|
2
|
+
export declare const FIELD_CONFLICT_KINDS: readonly ["cascade_verify", "image_profile", "resume_anchor", "system_prompt_fold", "file_history_restore", "skills_disabled", "existing_session", "scratchpad_root", "ask_answer", "policy_person_ask", "plan_edit", "plan_mode_after", "bound_call_door", "remember_decision", "approval_feedback"];
|
|
3
3
|
/** 冲突的类(闭集词)。 */
|
|
4
4
|
export type FieldConflictKind = (typeof FIELD_CONFLICT_KINDS)[number];
|
|
5
5
|
/**
|
|
@@ -21,6 +21,7 @@ export interface FieldConflictCoordinates {
|
|
|
21
21
|
readonly plan_mode_after: "decision" | "permissionModeAfter";
|
|
22
22
|
readonly bound_call_door: "boundCallId";
|
|
23
23
|
readonly remember_decision: "remember" | "decision";
|
|
24
|
+
readonly approval_feedback: "feedback" | "decision" | "decidedBy";
|
|
24
25
|
}
|
|
25
26
|
/** 折叠上限那一类另带的度量位:合起来的量与帽。 */
|
|
26
27
|
export interface FieldConflictMeasure {
|
package/dist/field-conflict.js
CHANGED
|
@@ -13,6 +13,7 @@ export const FIELD_CONFLICT_KINDS = [
|
|
|
13
13
|
"plan_mode_after",
|
|
14
14
|
"bound_call_door",
|
|
15
15
|
"remember_decision",
|
|
16
|
+
"approval_feedback",
|
|
16
17
|
];
|
|
17
18
|
export function fieldConflictFacts(kind, fields, measure) {
|
|
18
19
|
return { detail: { kind, fields: [...fields], ...(measure === undefined ? {} : { count: measure.count, cap: measure.cap }) } };
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S-157④ —— SQL file-history 店的 **typed 永久失败**披露座:散文一个铸点 + 机器面一只 sink。
|
|
3
|
+
*
|
|
4
|
+
* ## 病(基线 v7.79.0 逐行亲核 + 亲跑)
|
|
5
|
+
* `plugins/blob-backend.ts:55` 的 `BlobTooLargeError` 带 typed 判别码 `blob_too_large_for_sql`,头注
|
|
6
|
+
* 原话「绝不能与 transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)」。可它一旦被
|
|
7
|
+
* `plugins/file-history-store-sql.ts` 的任何一条 `catch` 折进 `FileHistoryResult.error.message`,判别码
|
|
8
|
+
* 就只剩散文,**机器一位都读不到**。两条腿各自的后果:
|
|
9
|
+
*
|
|
10
|
+
* · **snapshot(边界捕获)腿**:引擎把失败包成一句散文交给 `RunnerDeps.onError({phase:"rewind"})`
|
|
11
|
+
* (core 7.20.0 `dist/core/runner/prepare-file-history.js` 的 `wrapFileHistoryEntry`),服务那条臂从文本里抠**第一个**
|
|
12
|
+
* 括号词当 metric label —— 而第一个括号词恒是引擎自己的闭集码。实测:「这个文件永远存不进这口店」
|
|
13
|
+
* 与「连接被重置」在 `rewind_snapshot_failed_total` 上**同形** `code="snapshot_failed"`。
|
|
14
|
+
* · **trackEdit(首触备份)腿**:本服务用引擎默认的 `onTrackFailure:"refuse"`(`boot/runner-deps.ts` 亲读),
|
|
15
|
+
* 那条路 **完全不走 onError** —— 引擎把 `(track_failed: <本店 message>)` 念给模型,末句是
|
|
16
|
+
* 「Retry when the file-history store is healthy.」(`prepare-file-history.js`)。对这枚码那是永远
|
|
17
|
+
* 不会到来的一天,而**运维面一个计数都没有**:这条腿今天在遥测上完全不可见。
|
|
18
|
+
*
|
|
19
|
+
* ## 修法(源头留痕,不走文本通道)
|
|
20
|
+
* · **散文面**({@link discloseCaughtFailure} 的返回值):把 caught error 的 typed `code` 写成一个括号词
|
|
21
|
+
* 前置到它自己的 message 前 —— **不 `instanceof` 单挑任何一个类**(身上有字符串 `code` 就折),该店
|
|
22
|
+
* 11 处折点全部改走这一只函数。人与模型因此看得到「这是哪一类失败」。
|
|
23
|
+
* · **机器面**(同一只函数的副作用):码取自 typed 错**对象**、对闭集词表判定,**绝不从散文里抠**。
|
|
24
|
+
* 🔴 这一条是 codex R1-[high] 验真后的定形:文本通道**天然可伪造** —— 一个名叫
|
|
25
|
+
* `(blob_too_large_for_sql).txt` 的文件读不出来时,它的路径就在 `snapshot_failed` 的散文里,任何
|
|
26
|
+
* 「从散文里取在册括号词」的读法都会把它当成一枚永久码(继而抑制掉真故障的告警)。闭集只约束基数,
|
|
27
|
+
* 不能证明来源。所以机读判定搬回**唯一知道真相的那一层**=铸错的地方,`rewind` 臂的读法一字不动。
|
|
28
|
+
* · 留痕三件套与 `observability/fail-open.ts` / `observability/secret-env-scrub.ts` 同律:**计数逐次**
|
|
29
|
+
* (`file_history_permanent_failure_total{code}`)、**每进程每码一条 warn**(第二条起零新增内容,而
|
|
30
|
+
* 20G 树的用户每 turn 撞一次)、装配前累计的次数在 install 时补进 metrics。
|
|
31
|
+
*/
|
|
32
|
+
import { FILE_HISTORY_ERROR_CODES } from "@sema-agent/core";
|
|
1
33
|
import type { Logger } from "./observability/logger.js";
|
|
2
34
|
import type { Metrics } from "./observability/metrics.js";
|
|
3
35
|
interface Sink {
|
|
@@ -18,11 +50,8 @@ export declare function installFileHistoryFailureRecorder(deps: Sink): void;
|
|
|
18
50
|
export declare function fileHistoryPermanentFailureCounts(): ReadonlyMap<string, number>;
|
|
19
51
|
/** 测试用重置(进程级状态在同一个 vitest worker 里跨用例存活)。 */
|
|
20
52
|
export declare function resetFileHistoryFailureRecorderForTest(deps?: Sink): void;
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
declare const FILE_HISTORY_ERROR_CODE_WORDS: readonly ["track_failed", "snapshot_failed", "restore_failed", "adopt_failed", "not_found", "aborted", "unsupported_format", "conflict"];
|
|
24
|
-
/** rewind 粗码臂的码词 = core 的闭集。 */
|
|
25
|
-
export type FileHistoryErrorCode = (typeof FILE_HISTORY_ERROR_CODE_WORDS)[number];
|
|
53
|
+
/** rewind 粗码臂的码词 = core 的闭集(值与型同一源:`FILE_HISTORY_ERROR_CODES`)。 */
|
|
54
|
+
export type FileHistoryErrorCode = (typeof FILE_HISTORY_ERROR_CODES)[number];
|
|
26
55
|
/** rewind 臂一次失败的粗码:文本里**第一个**在册的 `(码)` / `(码:`;没有 ⇒ `unknown`。纯函数、不抛。 */
|
|
27
56
|
export declare function rewindFailureCode(err: unknown): FileHistoryErrorCode | "unknown";
|
|
28
57
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FILE_HISTORY_ERROR_CODES } from "@sema-agent/core";
|
|
1
2
|
const PERMANENT_STORE_FAILURE_CODES = {
|
|
2
3
|
blob_too_large_for_sql: {
|
|
3
4
|
note: "a blob is larger than this deployment's SQL snapshot-blob cap — the same file will fail every turn until the deployment changes",
|
|
@@ -86,8 +87,7 @@ export function resetFileHistoryFailureRecorderForTest(deps = {}) {
|
|
|
86
87
|
sink = { ...deps };
|
|
87
88
|
deps.metrics?.counter(PERMANENT_FAILURE_COUNTER, PERMANENT_FAILURE_HELP);
|
|
88
89
|
}
|
|
89
|
-
const
|
|
90
|
-
const FILE_HISTORY_ERROR_CODE_SET = new Set(FILE_HISTORY_ERROR_CODE_WORDS);
|
|
90
|
+
const FILE_HISTORY_ERROR_CODE_SET = new Set(FILE_HISTORY_ERROR_CODES);
|
|
91
91
|
const isFileHistoryErrorCode = (w) => FILE_HISTORY_ERROR_CODE_SET.has(w);
|
|
92
92
|
export function rewindFailureCode(err) {
|
|
93
93
|
const text = err instanceof Error ? err.message : String(err);
|
|
@@ -190,6 +190,10 @@ export interface FleetWorkflowRow {
|
|
|
190
190
|
* `/v1/fleet/stream` `?session=` filter. NOT rendered. */
|
|
191
191
|
sessionId?: string;
|
|
192
192
|
status: string;
|
|
193
|
+
/** ADDITIVE / tolerate-absent:run 行上的机读终态码**原样过境**(`WorkflowRun.errorCode`,开集机器词,与 `GET /v1/workflows/:id` 的同名位同一套)。
|
|
194
|
+
* 停驻的 workflow run 终态 = `status:"failed"` + `errorCode:"workflow.agent_parked"`:`status` 词表不加字(不另铸专用 status 词),
|
|
195
|
+
* 「停驻」与「真失败」靠这一位分开。run 行没有码 ⇒ 键缺席(不铸空串);缺席也可能是老服务端,消费方同形处置:退回只读 `status`。 */
|
|
196
|
+
errorCode?: string;
|
|
193
197
|
doneCount?: number;
|
|
194
198
|
/** 计划总数(= `run.agents.length`,**含排队中**)—— 注意它**不是**"已启动数",见 {@link FleetWorkflowRow.startedCount}。 */
|
|
195
199
|
totalCount?: number;
|
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -20,6 +20,7 @@ export function buildFleetWorkflowRow(id, run) {
|
|
|
20
20
|
scope: run.scope,
|
|
21
21
|
...(run.originatingSessionId ? { sessionId: run.originatingSessionId } : {}),
|
|
22
22
|
status: run.status,
|
|
23
|
+
...(run.errorCode ? { errorCode: run.errorCode } : {}),
|
|
23
24
|
doneCount: done,
|
|
24
25
|
totalCount: agents.length,
|
|
25
26
|
failedCount: failed,
|
|
@@ -12,7 +12,7 @@ import { recordFailOpen } from "../observability/fail-open.js";
|
|
|
12
12
|
import { LIMIT_REASONS } from "../limit-refusal.js";
|
|
13
13
|
import { clipDataText, echoCallerText } from "../caller-text-echo.js";
|
|
14
14
|
import { redactSecrets, buildDisplaySafeUrl } from "../trace/redact.js";
|
|
15
|
-
import {
|
|
15
|
+
import { canonicalJsonStringify } from "@sema-agent/core";
|
|
16
16
|
import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTPUT_TOKENS } from "./cc-stop-prompt.js";
|
|
17
17
|
import { ccAgentHookSystemFor } from "./cc-agent-hook-prompt.js";
|
|
18
18
|
import { renderBranchTranscript } from "./branch-transcript.js";
|
|
@@ -1393,7 +1393,7 @@ export function createSettingsHooksRegistration(config, ctx, budgetMs) {
|
|
|
1393
1393
|
}
|
|
1394
1394
|
if (decision === "allow" || decision === undefined) {
|
|
1395
1395
|
const proposed = h.updatedInput !== null && typeof h.updatedInput === "object" ? h.updatedInput : undefined;
|
|
1396
|
-
const proposesRewrite = proposed !== undefined &&
|
|
1396
|
+
const proposesRewrite = proposed !== undefined && canonicalJsonStringify(proposed) !== canonicalJsonStringify(groupInput);
|
|
1397
1397
|
if (ran.distrust !== undefined && hookPhaseOf(ran.source) === "unmanaged" && (decision === "allow" || proposesRewrite)) {
|
|
1398
1398
|
noteVerdictUntrusted(ctx, { event: "PreToolUse", reason: ran.distrust, entry, source: ran.source, toolName });
|
|
1399
1399
|
}
|
package/dist/http/admission.js
CHANGED
|
@@ -4,6 +4,7 @@ import { echoModelRef, modelUnavailableMessage } from "../model-entry-refusal.js
|
|
|
4
4
|
import { HttpError, sessionIdShapeIssue } from "../security.js";
|
|
5
5
|
import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS, PERMISSION_MODE_WORDS, parseTaskSettings, settingsPermissionBucketIssue, skillListingBudgetFractionOk, skillListingMaxDescOk, stampEntryPermissionMode, taskHooksHonored, taskSettingsKeyIssue } from "../task-settings.js";
|
|
6
6
|
import { tightenOnlySeatIssue } from "../tighten-only-seats.js";
|
|
7
|
+
import { approvalFrameFamiliesIssue } from "../tool-approval.js";
|
|
7
8
|
import { buildEntryModeClampNotice } from "../entry-mode-clamp-notice.js";
|
|
8
9
|
import { autonomyModeFloor } from "../runtime-governance.js";
|
|
9
10
|
import { taskBodyKeyIssue } from "../request-key-closure.js";
|
|
@@ -251,6 +252,13 @@ export function createAdmission(ctx) {
|
|
|
251
252
|
return null;
|
|
252
253
|
}
|
|
253
254
|
}
|
|
255
|
+
{
|
|
256
|
+
const familiesIssue = approvalFrameFamiliesIssue(st);
|
|
257
|
+
if (familiesIssue) {
|
|
258
|
+
sendError(res, 400, "request.field_invalid", familiesIssue.message, familiesIssue.fields);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
254
262
|
const envRaw = st.env;
|
|
255
263
|
if (envRaw !== undefined && envRaw !== null) {
|
|
256
264
|
if (typeof envRaw !== "object" || Array.isArray(envRaw)) {
|
|
@@ -104,9 +104,11 @@ export function createApprovalSweeps(ctx) {
|
|
|
104
104
|
}
|
|
105
105
|
};
|
|
106
106
|
const storedBodyUnservable = async (sessionId) => {
|
|
107
|
-
const read = await readResumeCtx(cs.getCtx(sessionId));
|
|
107
|
+
const read = await readResumeCtx(cs.getCtx(sessionId), { sessionId, opener: deps.checkpointCtxSealing });
|
|
108
108
|
if (read.kind === "corrupt")
|
|
109
109
|
return undefined;
|
|
110
|
+
if (read.kind === "unsealable")
|
|
111
|
+
return undefined;
|
|
110
112
|
if (read.kind === "absent")
|
|
111
113
|
return undefined;
|
|
112
114
|
return storedBodyReplayRefusal(read.ctx, deps.config);
|
|
@@ -39,6 +39,10 @@
|
|
|
39
39
|
* `resolveSpec` 本身留作真正的拒点(提交腿 400;续跑腿在到它之前已被本判据答掉)。再开第六个问点必须调这两只,不许各判各的 —— 此前铸卡前只判了
|
|
40
40
|
* allow 记号,工具名清单那一问漏在外面,卡铸出来了、`/wake` 恒 409([ref] 合并复审两腿同报)。
|
|
41
41
|
*
|
|
42
|
+
* ## 凭据位密封(7.134.0;常设规则,不是迁移)
|
|
43
|
+
* 体里请求面凭据位上的值在 ctx 里只以封签存在 —— 写:{@link buildCheckpointCtx} / {@link sealCheckpointCtx} 一只封口;读:续跑上下文读口里一只开口
|
|
44
|
+
* {@link openCheckpointCtx};开不开 ⇒ `409 conflict.checkpoint_ctx_unsealable`。规则全文在本文件末段「凭据位密封」的头注。
|
|
45
|
+
*
|
|
42
46
|
* ## 何时删(一次性迁移,不是长期双读)
|
|
43
47
|
* 本判据、拒臂、记号写点与 `CheckpointCtx.permissionsAllowReading` 一起删,删除条件与窗登记在 `plugins/one-time-migrations.ts` 的
|
|
44
48
|
* 一次性迁移登记表(本条那一行):所有旧读法下停驻的行都已结束或被收割(停驻行的终局 = 落盘起 `TERMINAL_BACKSTOP_MS`,见
|
|
@@ -73,14 +77,16 @@ export declare function bodyCarriesAllowList(body: TaskRequestBody): boolean;
|
|
|
73
77
|
* 调用方是「本进程刚按当前读法读过这份体」的写点:fresh 提交(受理门按当前读法读过 `allow`),或已经过了续跑拒臂的重放腿
|
|
74
78
|
* (`startFreshHostTurn` 把宿主的存量体交给 `POST /v1/runs` 同一条链 —— 它在那之前调过 `resolveResumeSpecOrBlocked`,没记号却带 `allow` 的体到不了这里)。
|
|
75
79
|
* 续跑腿的**写回**不经本函数:**不改词**的写回传读进来的 ctx(记号随行,不重新判 —— 在写回口重新判会把一条没记号的旧行「洗」成新行);
|
|
76
|
-
* **改词**的写回(只有 `plan_review` 的批准 / 编辑)经 {@link withPermissionModeRewritten}。
|
|
80
|
+
* **改词**的写回(只有 `plan_review` 的批准 / 编辑)经 {@link withPermissionModeRewritten}。两种写回落盘之前都过同一只封口 {@link sealCheckpointCtx}。
|
|
81
|
+
*
|
|
82
|
+
* `sealer` / `sessionId`:凭据位密封的两件(本文件末段)—— `sessionId` 是这一行的会话(绑进封签);`sealer` 必传,缺席也要说出来(`undefined`)。
|
|
77
83
|
*/
|
|
78
84
|
export declare function buildCheckpointCtx(fields: {
|
|
79
85
|
body: TaskRequestBody;
|
|
80
86
|
memoryScope?: string | undefined;
|
|
81
87
|
unattendedLeg?: true;
|
|
82
88
|
writerVersion: string;
|
|
83
|
-
}):
|
|
89
|
+
}, sealer: CheckpointCtxSealer | undefined, sessionId: string): SealedCheckpointCtx;
|
|
84
90
|
/**
|
|
85
91
|
* 模式词的**第二个写者** —— `plan_review` 的批准 / 编辑把这条 run 的体词从 `plan` 写成批准的档(`resume-legs.ts` `drivePlanReview`,调用点只有那一处;
|
|
86
92
|
* `test/allow-list-reading-park-refusal.test.ts` 枚举调用点,多一处即红)。与 {@link buildCheckpointCtx} **同一条规则**:按当前读法写下的词,盖当前读法记号。
|
|
@@ -208,4 +214,153 @@ export declare function storedBodyReplayLogFields(refusal: StoredBodyReplayRefus
|
|
|
208
214
|
* 没有默认值:每个调用点必须说出这件事实。
|
|
209
215
|
*/
|
|
210
216
|
export declare function storedBodyReplayAnswer(refusal: StoredBodyReplayRefusal, readerVersion: string, cardPending: boolean): ResumeRefusalAnswer;
|
|
217
|
+
/**
|
|
218
|
+
* 🔴 **停驻行里请求面凭据的落盘形 —— 一条规则**:提交体里**凭据位**({@link CHECKPOINT_CTX_CREDENTIAL_SLOTS},闭集)上的每一个值,在 `checkpoint_ctx`
|
|
219
|
+
* (local 店的 `<数据根>/checkpoint-ctx/<会话>.json` / SQL 店的 `checkpoint_ctx.ctx` 列)里只以**封签**({@link SealedCheckpointValue})存在。
|
|
220
|
+
* · **写**:一只封口 {@link sealCheckpointCtx}。新的持久化写经 {@link buildCheckpointCtx}(它的唯一产物形就是封好的 {@link StoredCheckpointCtx}),
|
|
221
|
+
* 续跑腿的写回(`resume-legs.ts` 的 `commitResumeCtx`:读进来的 ctx 在内存里是开封形)经它本身;一次性迁移 M28 对存量行用同一只走法
|
|
222
|
+
* ({@link sealStoredCheckpointCtxM28})。凭据位上的**每一个值都封,不分类型**(串、数字、`null`、对象 —— 包括调用方自己送来的封签形对象):
|
|
223
|
+
* 封签里装的是那个 JSON 值本身,开封逐字还原。字典位上的容器本身不是字典(串 / 数组 / `null` / 封签形对象)⇒ 整只容器当一个值封。
|
|
224
|
+
* 于是落盘形里凭据位上**只有本封口铸的封签**,没有哪个值能「原样存下、续跑时被开成别的东西」(调用方送一只封签形对象 ⇒ 存的是再封一层,
|
|
225
|
+
* 开出来还是那只对象 —— 与新提交腿看到的同一个值)。没有封签器而有值要封 ⇒ 抛 {@link CheckpointCtxUnsealableError}(`no_key_store`,调用方在写之前拒)。
|
|
226
|
+
* · **读法记号**(与本文件另两只记号同一条「写的人盖章」):封口铸出的每一份落盘形都带 `credentialSeal: CHECKPOINT_CTX_SEAL_ALG` —— 这一行的凭据位
|
|
227
|
+
* **全部**由本封口封过。没有记号的行是 7.134.0 之前的写者写下的:那时没有任何写者封值,所以它凭据位上的封签形对象只可能是调用方原样送来的,不是封签。
|
|
228
|
+
* 记号只在落盘形上(开口交出的开封形里没有它;写回再封时重新盖)。
|
|
229
|
+
* · **读**:一只开口 {@link openCheckpointCtx},唯一调用方 = 续跑上下文唯一读口 `resume-ctx-read.ts` 的 `readResumeCtx`(读出的 ctx 在任何消费者
|
|
230
|
+
* 看到之前已开封)。行带当前记号:凭据位上封签 ⇒ 开(开不开 ⇒ 拒);带 `sealed` 键却不是本闭集的封签形(含将来的 `alg`)⇒ 拒 `unrecognized_seal`;
|
|
231
|
+
* 其余任何值 ⇒ 拒 `plaintext`。行没有记号 ⇒ 凭据位上**任何**值(含封签形对象)⇒ 拒 `plaintext`(更早版本写下、M28 还没扫到);记号认不得(将来的格式)⇒
|
|
232
|
+
* 凭据位上任何值 ⇒ 拒 `unrecognized_seal`。凭据位上没有值的行不论记号照常开。
|
|
233
|
+
* **拒** = 读口第四态 `unsealable`(`field` = 哪一个凭据位,`reason` ∈ {@link CHECKPOINT_CTX_UNSEALABLE_REASONS}),各续跑入口答
|
|
234
|
+
* `409 conflict.checkpoint_ctx_unsealable`(`resume-ctx-read.ts` 的 `resumeCtxUnsealableAnswer`)。**没有**明文回落臂、**没有**静默丢键臂。
|
|
235
|
+
* · **装配**:`checkpointStore` 在场而封签器缺席 ⇒ 拒启({@link CHECKPOINT_CTX_SEALING_REQUIRED};`createHttpServer` 与起服第 6 段同一句)。
|
|
236
|
+
*
|
|
237
|
+
* **一个域一对钥匙**:停驻行封签用这一域自己的保管店(`<数据根>/sealed-key/checkpoint-ctx-keys/`,生产工厂 `sealed-key.ts` `ensureCheckpointCtxSealing`),
|
|
238
|
+
* 与模型钥匙(`ModelEntry.sealedApiKey`)的那对钥匙不共用 —— 模型钥匙的密文放进凭据位、停驻行的封签放进 `sealedApiKey`,两边都是「不持有那把钥匙」;
|
|
239
|
+
* 封签再把**会话 id 绑进密文**(信封 = `[alg, sessionId, value]`):别的会话那一行的封签搬过来开不开 —— 读口不是这台副本任何一把钥匙的解密预言机。
|
|
240
|
+
*
|
|
241
|
+
* 本模块的零值依赖纪律照旧:封签器 / 开封器是调用方递进来的接口(实现在 `sealed-key.ts`,它依赖 libsodium,不进本模块的闭包)。
|
|
242
|
+
*/
|
|
243
|
+
export declare const CHECKPOINT_CTX_SEAL_ALG = "checkpoint-ctx-sealedbox-v1";
|
|
244
|
+
/** 停驻行里一只凭据值的**封签**(闭集形:恰这三键、三值皆串、`alg` 只有 {@link CHECKPOINT_CTX_SEAL_ALG} 一个词)。与明文不可混淆:明文是串,封签是对象。 */
|
|
245
|
+
export interface SealedCheckpointValue {
|
|
246
|
+
readonly alg: typeof CHECKPOINT_CTX_SEAL_ALG;
|
|
247
|
+
/** 封它的那把公钥的 id(开封按它**精确**选私钥,不逐把试)。 */
|
|
248
|
+
readonly publicKeyId: string;
|
|
249
|
+
/** `crypto_box_seal` 密文的标准 base64。 */
|
|
250
|
+
readonly sealed: string;
|
|
251
|
+
}
|
|
252
|
+
/** 封口(只会封):把凭据位上的一个 JSON 值(任何类型)封进 `sessionId` 那一行。抛 = 封不了(调用方当写失败处置)。 */
|
|
253
|
+
export interface CheckpointCtxSealer {
|
|
254
|
+
seal(value: unknown, sessionId: string): SealedCheckpointValue;
|
|
255
|
+
}
|
|
256
|
+
/** 开口:`holds` = 这台副本有没有 `publicKeyId` 那把私钥;`open` 抛 = 开不开(密文坏 / 不是 `sessionId` 那一行封的 / 不是停驻行信封);返回封进去的那个 JSON 值。
|
|
257
|
+
* 🔴 `open` 的返回值是活凭据:只进内存,不进日志、不落盘(再落盘必经 {@link sealCheckpointCtx})。 */
|
|
258
|
+
export interface CheckpointCtxOpener {
|
|
259
|
+
holds(publicKeyId: string): boolean;
|
|
260
|
+
open(sealed: SealedCheckpointValue, sessionId: string): unknown;
|
|
261
|
+
}
|
|
262
|
+
/** 封签器 = 封口 + 开口(生产工厂 `sealed-key.ts` `ensureCheckpointCtxSealing`;`ServiceDeps.checkpointCtxSealing` 的型)。 */
|
|
263
|
+
export interface CheckpointCtxSealing extends CheckpointCtxSealer, CheckpointCtxOpener {
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* **凭据位闭集**(提交体里值为凭据的位置)。路径段 `*` = 那一格容器的**每一个成员**:数组的每一项、字典的每一个值(一只走法,不分容器种类 —— 字典成员的点名形
|
|
267
|
+
* `field` 是 `.<键>`,数组是 `[<下标>]`)。`map` = 那一格是串值字典,**每一个值**都是凭据(不分哪一键);`value` = 那一格本身是凭据。
|
|
268
|
+
* 入表判据(一条):请求面上**为承载凭据而设**的字段 —— 静态请求头(MCP http 传输、A2A 对端〔引擎型注:「e.g. an auth bearer」〕、http hook 条目)、
|
|
269
|
+
* 交给子进程的 env(MCP stdio 传输、任务 shell 的 `settings.env`)、请求自带的搜索后端钥匙。http hook 的头与另两种静态头同判:契约包的条目形是
|
|
270
|
+
* `headers: Record<string, string>`、受理面(`parseHooksConfig`)只判条数与字数、`interpolateHookEnvVars` 只换 `$NAME` 引用而字面值原样上 wire ——
|
|
271
|
+
* 头值可以就是字面的 `Authorization: Bearer …`。hooks 的形 = 事件名 → 匹配组数组 → 组的 `hooks` 数组 → 条目;只有 `type:"http"` 的条目有 `headers`,
|
|
272
|
+
* 别的条目在那一段缺席、走法照常不往下走。**不入表**:命令行 / URL / 工具实参之类自由文本(不是凭据字段;它们里有没有秘密不是本层能判的)。
|
|
273
|
+
* 加一位 = 加一行,读写两侧与一次性迁移 M28 共用 {@link mapCredentialSlots} 一只走法,自动覆盖。
|
|
274
|
+
*/
|
|
275
|
+
export declare const CHECKPOINT_CTX_CREDENTIAL_SLOTS: readonly [{
|
|
276
|
+
readonly path: readonly ["mcpServers", "*", "transport", "headers"];
|
|
277
|
+
readonly holds: "map";
|
|
278
|
+
}, {
|
|
279
|
+
readonly path: readonly ["mcpServers", "*", "transport", "env"];
|
|
280
|
+
readonly holds: "map";
|
|
281
|
+
}, {
|
|
282
|
+
readonly path: readonly ["a2aPeers", "*", "headers"];
|
|
283
|
+
readonly holds: "map";
|
|
284
|
+
}, {
|
|
285
|
+
readonly path: readonly ["settings", "env"];
|
|
286
|
+
readonly holds: "map";
|
|
287
|
+
}, {
|
|
288
|
+
readonly path: readonly ["settings", "webSearch", "apiKey"];
|
|
289
|
+
readonly holds: "value";
|
|
290
|
+
}, {
|
|
291
|
+
readonly path: readonly ["settings", "hooks", "*", "*", "hooks", "*", "headers"];
|
|
292
|
+
readonly holds: "map";
|
|
293
|
+
}];
|
|
294
|
+
/** 开不开 / 封不了的成因(闭集):`no_key_store` = 这台副本没有封签器(没有保管店);`key_not_held` = 封它的那把钥匙不在这台副本的保管店里
|
|
295
|
+
* (多副本不共享 `<数据根>/sealed-key/`,或保管目录被换过);`unopenable` = 钥匙在、开不开(密文坏 / 不是这一行封的 / 不是停驻行信封);
|
|
296
|
+
* `plaintext` = 凭据位上是明文(更早版本的写者);`unrecognized_seal` = 带 `sealed` 键却不是本闭集的封签形(含将来的 `alg`)。 */
|
|
297
|
+
export declare const CHECKPOINT_CTX_UNSEALABLE_REASONS: readonly ["no_key_store", "key_not_held", "unopenable", "plaintext", "unrecognized_seal"];
|
|
298
|
+
export type CheckpointCtxUnsealableReason = (typeof CHECKPOINT_CTX_UNSEALABLE_REASONS)[number];
|
|
299
|
+
/** 封不了 / 开不开的那一格:`field` = 凭据位在提交体里的路径(如 `mcpServers[0].transport.headers.Authorization`、
|
|
300
|
+
* `settings.hooks.PreToolUse[0].hooks[0].headers.Authorization`;键名不是秘密,值从不进来)。 */
|
|
301
|
+
export declare class CheckpointCtxUnsealableError extends Error {
|
|
302
|
+
readonly field: string;
|
|
303
|
+
readonly reason: CheckpointCtxUnsealableReason;
|
|
304
|
+
constructor(field: string, reason: CheckpointCtxUnsealableReason);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* 停驻行的**落盘形**:凭据位上是封签,不是 {@link TaskRequestBody} 型面说的串 —— 所以体在型面上只是「一只对象」,读它的唯一合法方式是 {@link openCheckpointCtx}
|
|
308
|
+
* (经续跑上下文读口)。产物只有两只铸点:{@link buildCheckpointCtx} 与 {@link sealCheckpointCtx}。
|
|
309
|
+
*/
|
|
310
|
+
export type StoredCheckpointCtx = Omit<CheckpointCtx, "body"> & {
|
|
311
|
+
readonly body: {
|
|
312
|
+
readonly [key: string]: unknown;
|
|
313
|
+
};
|
|
314
|
+
readonly credentialSeal?: typeof CHECKPOINT_CTX_SEAL_ALG;
|
|
315
|
+
};
|
|
316
|
+
declare const sealedCheckpointCtxBrand: unique symbol;
|
|
317
|
+
/**
|
|
318
|
+
* **封口铸出的**落盘形(品牌型):店的写口(`putCtx`)与一次性迁移 M28 的行改写只收它 ⇒ 「写 ctx 只有封口一个写入面」由型面守,
|
|
319
|
+
* 不再靠逐个数调用点(把开封形 / 裸对象直接交给写口 = 当场 tsc 红)。铸点闭集(都在本模块,都经 {@link mintSealedCheckpointCtx}):
|
|
320
|
+
* {@link sealCheckpointCtx}(新的持久化写经 {@link buildCheckpointCtx} 到它;续跑腿的写回)与 {@link sealStoredCheckpointCtxM28}。零运行期变化。
|
|
321
|
+
*/
|
|
322
|
+
export type SealedCheckpointCtx = StoredCheckpointCtx & {
|
|
323
|
+
readonly [sealedCheckpointCtxBrand]: true;
|
|
324
|
+
};
|
|
325
|
+
/** 装配期拒启的那一句(`createHttpServer` 与起服第 6 段同调;一句、一个出口)。 */
|
|
326
|
+
export declare const CHECKPOINT_CTX_SEALING_REQUIRED = "a durable checkpoint store is wired but checkpointCtxSealing is not: suspended tasks keep the request's credentials (MCP / A2A headers and env values, settings.env, settings.webSearch.apiKey, http hook headers) in their resume context, and they are stored sealed or not at all \u2014 give the service a sealed-key store for them (this service builds it under <data root>/sealed-key/checkpoint-ctx-keys/ at boot; a failure there is the earlier checkpoint_ctx_sealing_unavailable warning \u2014 make that directory creatable and chmod-able by the service, or repair or remove a key file there whose public and private halves do not belong together), or, when embedding, pass checkpointCtxSealing: await ensureCheckpointCtxSealing(sealedKeyDir); refusing to start rather than write credentials in plaintext";
|
|
327
|
+
/** 封签形的判官(恰三键、三值皆串、`alg` 是本闭集那一个词)。 */
|
|
328
|
+
export declare function isSealedCheckpointValue(v: unknown): v is SealedCheckpointValue;
|
|
329
|
+
/**
|
|
330
|
+
* **封口**(写的一侧唯一的一只):内存里的开封形 ctx ⇒ 落盘形。`sealer` 必传(缺席也要说出来:`undefined` = 这台副本没有保管店 ⇒ 体里有凭据就抛
|
|
331
|
+
* {@link CheckpointCtxUnsealableError} `no_key_store`,调用方在写之前拒;体里没有凭据 ⇒ 没有要封的东西,照常)。`sessionId` = 这一行的会话(绑进封签)。
|
|
332
|
+
* 调用方:{@link buildCheckpointCtx}(新的持久化写)与续跑腿的写回(`resume-legs.ts` `commitResumeCtx`)。
|
|
333
|
+
*/
|
|
334
|
+
export declare function sealCheckpointCtx(ctx: CheckpointCtx, sealer: CheckpointCtxSealer | undefined, sessionId: string): SealedCheckpointCtx;
|
|
335
|
+
/** {@link openCheckpointCtx} 的结局:开封形 ctx,或第一处开不开的凭据位。 */
|
|
336
|
+
export type OpenedCheckpointCtx = {
|
|
337
|
+
readonly kind: "ok";
|
|
338
|
+
readonly ctx: CheckpointCtx;
|
|
339
|
+
} | {
|
|
340
|
+
readonly kind: "unsealable";
|
|
341
|
+
readonly field: string;
|
|
342
|
+
readonly reason: CheckpointCtxUnsealableReason;
|
|
343
|
+
};
|
|
344
|
+
/**
|
|
345
|
+
* **开口**(读的一侧唯一的一只;唯一调用方 = 续跑上下文读口 `readResumeCtx`):落盘形 ⇒ 开封形。`opener` 缺席 = 这台副本没有保管店(体里有封签就
|
|
346
|
+
* `no_key_store`;没有凭据的行照常开)。判词见本段头注。
|
|
347
|
+
*/
|
|
348
|
+
export declare function openCheckpointCtx(stored: StoredCheckpointCtx, opener: CheckpointCtxOpener | undefined, sessionId: string): OpenedCheckpointCtx;
|
|
349
|
+
/**
|
|
350
|
+
* 一份落盘 ctx(持久化 JSON,不信型面)的凭据位上**第一只封签**的点名(`field`,形同拒句里的那一位;键名不是秘密,值从不出来)—— 没有 ⇒ `undefined`。
|
|
351
|
+
* 走法 = 封口 / 开口 / M28 共用的那一只 {@link mapCredentialSlots},判形 = {@link isSealedCheckpointValue};不开封、不看记号(只问「凭据位上有没有封签」)。
|
|
352
|
+
* 唯一读者:随包排空检查脚本的 `sealed-credentials-rollback` 判据(经编译产物取):7.133.x 读不懂封签、续跑时把这些凭据位当没有 ⇒ 回滚之前先把这样的停驻行决掉或取消。
|
|
353
|
+
*/
|
|
354
|
+
export declare function sealedCredentialFieldOf(stored: unknown): string | undefined;
|
|
355
|
+
/**
|
|
356
|
+
* 🔴 **一次性迁移 M28 的行改写**(窗与删法见 `plugins/one-time-migrations.ts` 登记表 M28;编排 = `checkpoint-ctx-seal-m28.ts`):一行存量 ctx
|
|
357
|
+
* (持久化 JSON,不信型面)**没有读法记号**(= 7.134.0 之前的写者写下的)⇒ 凭据位上的**每一个值**(含调用方原样送来的封签形对象)用同一只封口封好、盖记号 ——
|
|
358
|
+
* 与写侧 {@link sealCheckpointCtx} 同一条规则。带记号的行(本版写下的,或本条已改写过的)、记号认不得的行 ⇒ `undefined`(不写:第二跑零命中;认不得的留给读口拒);
|
|
359
|
+
* 凭据位上没有值 ⇒ `undefined`(不写);体不是对象 ⇒ `undefined`(读口自己的形判接住)。其余位(另两只记号、写者版本、非凭据字段)原样。
|
|
360
|
+
*/
|
|
361
|
+
export declare function sealStoredCheckpointCtxM28(stored: object, sealer: CheckpointCtxSealer, sessionId: string): {
|
|
362
|
+
readonly ctx: SealedCheckpointCtx;
|
|
363
|
+
readonly valuesSealed: number;
|
|
364
|
+
} | undefined;
|
|
365
|
+
export {};
|
|
211
366
|
//# sourceMappingURL=checkpoint-ctx.d.ts.map
|