@sema-agent/server 7.135.0 → 7.136.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 +64 -2
- package/USAGE.md +62 -25
- package/deploy/sema-up/README.md +1 -1
- package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
- package/deploy/sema-up/chart/values.yaml +1 -1
- package/dist/approval-card.d.ts +60 -4
- package/dist/approval-card.js +18 -1
- package/dist/approval-content-kind.d.ts +1 -0
- package/dist/approval-content-kind.js +1 -1
- package/dist/auth-bridge.d.ts +34 -5
- package/dist/auth-bridge.js +20 -3
- package/dist/boot/budget-tracing.d.ts +1 -2
- package/dist/boot/budget-tracing.js +2 -2
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/resolve-spec.js +10 -7
- package/dist/boot/runner-deps.js +4 -2
- package/dist/boot/stage-04-stores.d.ts +1 -1
- package/dist/boot/stage-05-execution-env.d.ts +1 -1
- package/dist/boot/stage-06-runners.d.ts +1 -1
- package/dist/boot/stage-07-capability-layer.d.ts +1 -1
- package/dist/boot/stage-08-reapers.d.ts +1 -1
- package/dist/boot/stage-09-leader.d.ts +1 -1
- package/dist/boot/stage-10-http-server.d.ts +1 -1
- package/dist/boot/stores.js +2 -3
- package/dist/boot/web-admin.js +10 -17
- package/dist/brain.js +12 -3
- package/dist/budget.d.ts +3 -3
- package/dist/budget.js +4 -0
- package/dist/capabilities/mcp-probe.d.ts +5 -4
- package/dist/capability-facts.d.ts +3 -7
- package/dist/capability-facts.js +3 -5
- package/dist/center-credential.d.ts +18 -4
- package/dist/center-credential.js +18 -3
- package/dist/config-catalog.js +12 -10
- package/dist/config-d-migration.js +1 -1
- package/dist/config-invariants.d.ts +1 -1
- package/dist/config-invariants.js +8 -0
- package/dist/config-lkg.d.ts +0 -38
- package/dist/config-lkg.js +1 -83
- package/dist/config-types.d.ts +12 -4
- package/dist/config.js +26 -8
- package/dist/deployment-shape.d.ts +1 -1
- package/dist/elicitation.d.ts +8 -7
- package/dist/elicitation.js +9 -8
- package/dist/file-history-disclosure.d.ts +6 -4
- package/dist/file-history-disclosure.js +22 -6
- package/dist/fleet-client.d.ts +32 -1
- package/dist/fleet-client.js +24 -4
- package/dist/fleet-lease.d.ts +7 -3
- package/dist/fleet-lease.js +31 -23
- package/dist/hooks/hook-llm.js +14 -8
- package/dist/hooks/hook-runner.d.ts +4 -4
- package/dist/hooks/hook-runner.js +2 -2
- package/dist/http/checkpoint-ctx.d.ts +28 -9
- package/dist/http/checkpoint-ctx.js +5 -1
- package/dist/http/dispatch.js +9 -9
- package/dist/http/resume-ctx-read.d.ts +2 -16
- package/dist/http/resume-ctx-read.js +2 -3
- package/dist/http/routes/approvals-assistant.d.ts +0 -2
- package/dist/http/routes/approvals-assistant.js +47 -12
- package/dist/http/routes/runs.js +2 -2
- package/dist/http/routes/session-sync.js +31 -9
- package/dist/http/routes/sessions.js +18 -14
- package/dist/http/submit-ctx.js +1 -1
- package/dist/http/wire-types.d.ts +5 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/model-route-gap.d.ts +49 -4
- package/dist/model-route-gap.js +34 -1
- package/dist/object-store-buckets.d.ts +13 -0
- package/dist/object-store-buckets.js +3 -0
- package/dist/observability/fail-open.d.ts +6 -6
- package/dist/observability/fail-open.js +6 -6
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/principal-context.d.ts +4 -1
- package/dist/observability/principal-context.js +6 -1
- package/dist/parked-decide.d.ts +4 -3
- package/dist/parked-decide.js +26 -7
- package/dist/peer-directory.d.ts +2 -1
- package/dist/peer-directory.js +5 -5
- package/dist/plugins/approval-ask-store-sql.d.ts +8 -0
- package/dist/plugins/approval-ask-store-sql.js +3 -0
- package/dist/plugins/blob-backend.js +2 -1
- package/dist/plugins/checkpoint-index-columns.d.ts +50 -1
- package/dist/plugins/checkpoint-index-columns.js +28 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +27 -3
- package/dist/plugins/checkpoint-store-sql.js +58 -29
- package/dist/plugins/file-history-store-sql.d.ts +6 -0
- package/dist/plugins/file-history-store-sql.js +30 -23
- package/dist/plugins/local-checkpoint-store.js +4 -3
- package/dist/plugins/one-time-migrations.d.ts +21 -28
- package/dist/plugins/one-time-migrations.js +39 -123
- package/dist/plugins/remote-env-k8s.d.ts +18 -5
- package/dist/plugins/remote-env-k8s.js +24 -7
- package/dist/plugins/s3-answer.d.ts +15 -0
- package/dist/plugins/s3-answer.js +5 -0
- package/dist/plugins/snapshot-bucket-presence.d.ts +39 -4
- package/dist/plugins/snapshot-bucket-presence.js +35 -7
- package/dist/plugins/sql-errors.d.ts +39 -0
- package/dist/plugins/sql-errors.js +49 -1
- package/dist/plugins/store-fault.d.ts +4 -0
- package/dist/plugins/store-fault.js +8 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/web-search.d.ts +1 -1
- package/dist/plugins/web-search.js +2 -2
- package/dist/request-declaration-refusal.d.ts +94 -0
- package/dist/request-declaration-refusal.js +78 -0
- package/dist/request-image-urls.d.ts +6 -0
- package/dist/request-image-urls.js +27 -0
- package/dist/request-key-closure.d.ts +5 -0
- package/dist/request-key-closure.js +4 -1
- package/dist/runtime-caps-resolver.js +1 -1
- package/dist/sandbox-pkg-source.d.ts +28 -1
- package/dist/sandbox-pkg-source.js +1 -2
- package/dist/sealed-key.d.ts +19 -0
- package/dist/sealed-key.js +47 -4
- package/dist/session-incarnation.d.ts +16 -5
- package/dist/session-incarnation.js +7 -4
- package/dist/session-row.d.ts +3 -17
- package/dist/session-row.js +4 -0
- package/dist/session-sync.d.ts +15 -3
- package/dist/session-sync.js +5 -2
- package/dist/task-a2a.d.ts +52 -81
- package/dist/task-a2a.js +219 -89
- package/dist/task-mcp.d.ts +6 -14
- package/dist/task-mcp.js +16 -51
- package/dist/tool-approval.d.ts +23 -10
- package/dist/tool-approval.js +51 -36
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/exit-seat.js +37 -13
- package/dist/trace/redact.d.ts +28 -12
- package/dist/trace/redact.js +233 -94
- package/dist/trace/self-minted-wire.d.ts +267 -0
- package/dist/trace/self-minted-wire.js +79 -0
- package/dist/url-credential-refusal.d.ts +52 -0
- package/dist/url-credential-refusal.js +14 -0
- package/dist/url-refusal.d.ts +13 -4
- package/dist/url-refusal.js +8 -2
- package/dist/workspace-space.d.ts +27 -0
- package/dist/workspace-space.js +26 -0
- package/package.json +3 -2
- package/scripts/drain-check-local.mjs +2 -1
- package/scripts/drain-check.mysql.sql +3 -1
- package/scripts/drain-check.pg.sql +3 -1
- package/skills/schedule.md +1 -1
- package/wire-changes/server@7.104.0.json +134 -0
- package/wire-changes/server@7.105.0.json +53 -0
- package/wire-changes/server@7.106.0.json +475 -0
- package/wire-changes/server@7.107.0.json +64 -0
- package/wire-changes/server@7.108.0.json +153 -0
- package/wire-changes/server@7.109.0.json +46 -0
- package/wire-changes/server@7.110.0.json +90 -0
- package/wire-changes/server@7.111.0.json +86 -0
- package/wire-changes/server@7.112.0.json +60 -0
- package/wire-changes/server@7.113.0.json +26 -0
- package/wire-changes/server@7.114.0.json +26 -0
- package/wire-changes/server@7.115.0.json +97 -0
- package/wire-changes/server@7.116.0.json +218 -0
- package/wire-changes/server@7.116.1.json +26 -0
- package/wire-changes/server@7.117.0.json +65 -0
- package/wire-changes/server@7.118.0.json +129 -0
- package/wire-changes/server@7.119.0.json +65 -0
- package/wire-changes/server@7.120.0.json +89 -0
- package/wire-changes/server@7.121.0.json +219 -0
- package/wire-changes/server@7.122.0.json +54 -0
- package/wire-changes/server@7.123.0.json +26 -0
- package/wire-changes/server@7.124.0.json +336 -0
- package/wire-changes/server@7.124.1.json +26 -0
- package/wire-changes/server@7.125.0.json +111 -0
- package/wire-changes/server@7.126.0.json +285 -0
- package/wire-changes/server@7.127.0.json +77 -0
- package/wire-changes/server@7.127.1.json +26 -0
- package/wire-changes/server@7.127.2.json +53 -0
- package/wire-changes/server@7.128.0.json +26 -0
- package/wire-changes/server@7.129.0.json +106 -0
- package/wire-changes/server@7.129.1.json +26 -0
- package/wire-changes/server@7.130.0.json +26 -0
- package/wire-changes/server@7.131.0.json +118 -0
- package/wire-changes/server@7.132.0.json +226 -0
- package/wire-changes/server@7.133.0.json +1155 -0
- package/wire-changes/server@7.134.0.json +107 -0
- package/wire-changes/server@7.135.0.json +174 -0
- package/wire-changes/server@7.136.0.json +448 -0
- package/dist/url-credential.d.ts +0 -52
- package/dist/url-credential.js +0 -49
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type HookEntry, type HookEntryType, type HookEventName, type HooksDroppedPiece, type HooksDroppedPieceCode, type HooksRejection, type HooksRejectionCode } from "@sema-agent/settings-schema/hooks";
|
|
2
|
-
import { type ConfigPath } from "@sema-agent/settings-schema";
|
|
2
|
+
import { type ConfigPath, type UrlCredential } from "@sema-agent/settings-schema";
|
|
3
3
|
import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
|
|
4
4
|
import type { EngineDollar, ModRegistration } from "@sema-agent/core";
|
|
5
5
|
import type { Logger } from "../observability/logger.js";
|
|
6
6
|
import type { HookManagedUnrun, HookNoticeObservation } from "../fleet/fleet-bus.js";
|
|
7
7
|
import { LIMIT_REASONS, type LimitMeasure } from "../limit-refusal.js";
|
|
8
|
-
import { type
|
|
8
|
+
import { type UrlCredentialRefusal } from "../url-credential-refusal.js";
|
|
9
9
|
import { type HookGroupSource } from "./hook-source.js";
|
|
10
10
|
import { type RunnableCommandHookEntry } from "./command-spawn.js";
|
|
11
11
|
/** 服务侧防线上限(契约本身不设量纲;不设界=单请求可塞任意大配置/任意长命令)。超界=校验错(fail-loud)。 */
|
|
@@ -414,7 +414,7 @@ export interface HooksUrlCredentialIssue {
|
|
|
414
414
|
readonly credential: UrlCredential;
|
|
415
415
|
}
|
|
416
416
|
/**
|
|
417
|
-
* fresh 提交面专用判据([ref]):配置里第一条「url 带凭据」的 http 钩子 —— userinfo,或名字在日志脱敏器凭据名表里的 query / fragment 参数(
|
|
417
|
+
* fresh 提交面专用判据([ref]):配置里第一条「url 带凭据」的 http 钩子 —— userinfo,或名字在日志脱敏器凭据名表里的 query / fragment 参数(判官契约包根的 `findUrlCredential` 一只,
|
|
418
418
|
* MCP `transport.url` · A2A 对端 · 搜索后端 `endpoint` · 包源六址同读);没有 ⇒ undefined。这一座有为凭据设的位(条目的 `headers`,停驻任务的续跑上下文里只以封签存在),
|
|
419
419
|
* url 却照原样进停驻上下文、日志与读面 —— 凭据只许走前者(`$NAME` 引用 + `allowedEnvVars` 让配置本身不携带密钥值,见 USAGE 9.5c)。
|
|
420
420
|
* 入参是 {@link parseHooksConfig} 已校验过的配置:**按配置序**(事件 → 组 → 条目)遇到的第一条定结局;只看 `type:"http"` 条目(别的条目型没有 `url`)。
|
|
@@ -425,7 +425,7 @@ export interface HooksUrlCredentialIssue {
|
|
|
425
425
|
export declare function hooksUrlCredentialIssue(config: RunnableHooksConfig): HooksUrlCredentialIssue | undefined;
|
|
426
426
|
/**
|
|
427
427
|
* {@link hooksUrlCredentialIssue} 的判决 → 拒句 + 机读位。`field` 是这一条 `url` 在请求体里的全坐标(`settings.hooks.<事件>[g].hooks[h].url`,与封签位、本族别的 `field` 同一种写法),
|
|
428
|
-
* 出路指到同一条的 `headers`。机读位的形与 MCP / A2A / 搜索后端三座同一只铸点(`url-credential.ts`),所以**不**走上面 {@link buildHooksRefusal} 的 `field:"settings.hooks"` + `detail` 形 ——
|
|
428
|
+
* 出路指到同一条的 `headers`。机读位的形与 MCP / A2A / 搜索后端三座同一只铸点(`url-credential-refusal.ts`),所以**不**走上面 {@link buildHooksRefusal} 的 `field:"settings.hooks"` + `detail` 形 ——
|
|
429
429
|
* 那一形按 `detail.reason` 分形、属于配置读者与 matcher 判据;「URL 带凭据」是跨座位的一个事实,消费端按 `reason` 穷举读一次。
|
|
430
430
|
*/
|
|
431
431
|
export declare function buildHooksUrlCredentialRefusal(issue: HooksUrlCredentialIssue): UrlCredentialRefusal;
|
|
@@ -3,7 +3,7 @@ import { composeHostSubprocessEnv, hostSubprocessEnv } from "../server-secret-en
|
|
|
3
3
|
import vm from "node:vm";
|
|
4
4
|
import { hostShell, resolveHostShell, adoptHostForegroundChild, killTreeSoft, killTreeHard } from "../plugins/host-platform.js";
|
|
5
5
|
import { classifyHooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_ENTRY_TYPES, HOOK_EVENTS, HOOK_EVENT_OWNER, HOOK_EVENT_MATCHER_FIELD, hookMatcherForm, hookMatcherSelects, interpolateHookEnvVars, } from "@sema-agent/settings-schema/hooks";
|
|
6
|
-
import { formatConfigPath, outgoingPath } from "@sema-agent/settings-schema";
|
|
6
|
+
import { findUrlCredential, formatConfigPath, outgoingPath } from "@sema-agent/settings-schema";
|
|
7
7
|
import { ENGINE_OWNED_HOOK_EVENTS } from "@sema-agent/settings-schema/hooks";
|
|
8
8
|
import { describeToolCatalog } from "@sema-agent/core";
|
|
9
9
|
import { hangClassic } from "./classic-stations.js";
|
|
@@ -17,7 +17,7 @@ import { canonicalJsonStringify } from "@sema-agent/core";
|
|
|
17
17
|
import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTPUT_TOKENS } from "./cc-stop-prompt.js";
|
|
18
18
|
import { ccAgentHookSystemFor } from "./cc-agent-hook-prompt.js";
|
|
19
19
|
import { renderBranchTranscript } from "./branch-transcript.js";
|
|
20
|
-
import { buildRequestUrlCredentialRefusal,
|
|
20
|
+
import { buildRequestUrlCredentialRefusal, urlCredentialHeaderExit } from "../url-credential-refusal.js";
|
|
21
21
|
import { HOOK_PHASES, HOOK_SOURCE_VOCABULARY_SENTENCE, hookPhaseOf, readHookGroupSource } from "./hook-source.js";
|
|
22
22
|
import { commandLineChars, composeHookEnvLayer, hookPluginChars, planCommandSpawn, readHookArgs, readHookPlugin } from "./command-spawn.js";
|
|
23
23
|
export const MAX_HOOK_ENTRIES_PER_EVENT = 32;
|
|
@@ -268,14 +268,17 @@ export interface CheckpointCtxSealing extends CheckpointCtxSealer, CheckpointCtx
|
|
|
268
268
|
}
|
|
269
269
|
/**
|
|
270
270
|
* **凭据位闭集**(提交体里值为凭据的位置)。路径段 `*` = 那一格容器的**每一个成员**:数组的每一项、字典的每一个值(一只走法,不分容器种类 —— 字典成员的点名形
|
|
271
|
-
* `field` 是 `.<键>`,数组是 `[<下标>]`)。`map` = 那一格是串值字典,**每一个值**都是凭据(不分哪一键);`value` =
|
|
272
|
-
* 入表判据(一条)
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
271
|
+
* `field` 是 `.<键>`,数组是 `[<下标>]`)。`map` = 那一格是串值字典,**每一个值**都是凭据(不分哪一键);`value` = 那一格本身是凭据(不论是串、数组还是别的形,整只一个值)。
|
|
272
|
+
* 入表判据(一条):本服务替调用方**原样交出去**的配置值 —— **子进程的启动面**(命令行 `command` / `args` 与交给它的 env)、发往对端的**静态请求头**、
|
|
273
|
+
* 请求自带的**搜索后端钥匙**。值是不是秘密不是本层能判的,所以整格封、不判值形(`--token=…` / `-p …` 拆在两个元素里、shell 形的整行,都不靠认形)。
|
|
274
|
+
* · 子进程两座:MCP stdio 传输(`command` / `args` / `env`)与 command hook 条目(`command` / `args`,插件选项 `plugin.options` 的每一个值就是 hook 子进程环境里的
|
|
275
|
+
* `CLAUDE_PLUGIN_OPTION_<KEY>` —— `hooks/command-spawn.ts` 的 `hookPluginEnv`),外加任务 shell 的 `settings.env`。命令行与 env 同判:壳在投影前把 `${user_config.*}`
|
|
276
|
+
* 替成字面值(执行器不做这一替换),参照实现也把 sensitive 选项照样交进子进程,所以命令行里可以就是密钥。`args` 整只数组封成**一只**封签(不逐元素:逐元素判值形是第二只判官)。
|
|
277
|
+
* · 静态头三座:MCP http 传输、A2A 对端〔引擎型注:「e.g. an auth bearer」〕、http hook 条目(契约包的条目形是 `headers: Record<string, string>`、受理面(`parseHooksConfig`)
|
|
278
|
+
* 只判条数与字数、`interpolateHookEnvVars` 只换 `$NAME` 引用而字面值原样上 wire —— 头值可以就是字面的 `Authorization: Bearer …`)。
|
|
279
|
+
* hooks 的形 = 事件名 → 匹配组数组 → 组的 `hooks` 数组 → 条目;只有 `type:"http"` 的条目有 `headers`、只有 command 条目有 `command` / `args` / `plugin.options`,
|
|
280
|
+
* 别的条目在那一段缺席、走法照常不往下走。**不在本表、各归另一条规则**:值是 URL 的字段 —— 带凭据的 URL 在受理口就拒(凭据只许走头位;判官 = 契约包根的 `findUrlCredential`),
|
|
281
|
+
* 所以落盘的 URL 结构上不带凭据;交给模型的文本(目标 / 提示词 / 系统提示)—— 模型看得见的东西本来就进会话记录,封停驻档护不住它。
|
|
279
282
|
* 加一位 = 加一行,读写两侧与一次性迁移 M28 共用 {@link mapCredentialSlots} 一只走法:写侧与读侧立即覆盖;已落盘的行由 M28 补封 ——
|
|
280
283
|
* 新行的 `since` 取下一代:封口铸的落盘形盖「这一行里带值的位最新的那一代」(`credentialSlotGeneration`),读者据它判那一行用到、封过哪几位 —— 代数更早的行上新位的值一律是明文(记号本身不证明它封过新位)。
|
|
281
284
|
* M28 封的是「读口会答 `plaintext` 的每一个值」({@link credentialValueReading}),所以新加的一位上的明文在带记号的行里也被补封(只在 M28 的窗内;窗外加位要另起迁移)。
|
|
@@ -308,6 +311,22 @@ export declare const CHECKPOINT_CTX_CREDENTIAL_SLOTS: readonly [{
|
|
|
308
311
|
readonly path: readonly ["settings", "hooks", "*", "*", "hooks", "*", "plugin", "options"];
|
|
309
312
|
readonly holds: "map";
|
|
310
313
|
readonly since: 2;
|
|
314
|
+
}, {
|
|
315
|
+
readonly path: readonly ["mcpServers", "*", "transport", "command"];
|
|
316
|
+
readonly holds: "value";
|
|
317
|
+
readonly since: 3;
|
|
318
|
+
}, {
|
|
319
|
+
readonly path: readonly ["mcpServers", "*", "transport", "args"];
|
|
320
|
+
readonly holds: "value";
|
|
321
|
+
readonly since: 3;
|
|
322
|
+
}, {
|
|
323
|
+
readonly path: readonly ["settings", "hooks", "*", "*", "hooks", "*", "command"];
|
|
324
|
+
readonly holds: "value";
|
|
325
|
+
readonly since: 3;
|
|
326
|
+
}, {
|
|
327
|
+
readonly path: readonly ["settings", "hooks", "*", "*", "hooks", "*", "args"];
|
|
328
|
+
readonly holds: "value";
|
|
329
|
+
readonly since: 3;
|
|
311
330
|
}];
|
|
312
331
|
/** 本版闭集的代数 = 表里最大的 `since`(读者认得的最新一代)。落盘形的 `credentialSlotGeneration` 不大于它;大于它的行是更新的写者带着本版不认识的位写下的。 */
|
|
313
332
|
export declare const CHECKPOINT_CTX_CREDENTIAL_GENERATION: number;
|
|
@@ -345,7 +364,7 @@ export type SealedCheckpointCtx = StoredCheckpointCtx & {
|
|
|
345
364
|
readonly [sealedCheckpointCtxBrand]: true;
|
|
346
365
|
};
|
|
347
366
|
/** 装配期拒启的那一句(`createHttpServer` 与起服第 6 段同调;一句、一个出口)。 */
|
|
348
|
-
export declare const CHECKPOINT_CTX_SEALING_REQUIRED = "a durable checkpoint store is wired but checkpointCtxSealing is not: suspended tasks keep the request's credentials (
|
|
367
|
+
export declare const CHECKPOINT_CTX_SEALING_REQUIRED = "a durable checkpoint store is wired but checkpointCtxSealing is not: suspended tasks keep the request's credentials (static request headers, the command lines and environment values given to MCP stdio servers and command hooks, settings.env, settings.webSearch.apiKey) 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 at boot in checkpoint-ctx-keys/ under the sealed-key custody directory \u2014 SEALED_KEY_DIR, default <data root>/sealed-key/; 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";
|
|
349
368
|
/** 封签形的判官(恰三键、三值皆串、`alg` 是本闭集那一个词)。 */
|
|
350
369
|
export declare function isSealedCheckpointValue(v: unknown): v is SealedCheckpointValue;
|
|
351
370
|
/**
|
|
@@ -132,6 +132,10 @@ export const CHECKPOINT_CTX_CREDENTIAL_SLOTS = [
|
|
|
132
132
|
{ path: ["settings", "webSearch", "apiKey"], holds: "value", since: 1 },
|
|
133
133
|
{ path: ["settings", "hooks", "*", "*", "hooks", "*", "headers"], holds: "map", since: 1 },
|
|
134
134
|
{ path: ["settings", "hooks", "*", "*", "hooks", "*", "plugin", "options"], holds: "map", since: 2 },
|
|
135
|
+
{ path: ["mcpServers", "*", "transport", "command"], holds: "value", since: 3 },
|
|
136
|
+
{ path: ["mcpServers", "*", "transport", "args"], holds: "value", since: 3 },
|
|
137
|
+
{ path: ["settings", "hooks", "*", "*", "hooks", "*", "command"], holds: "value", since: 3 },
|
|
138
|
+
{ path: ["settings", "hooks", "*", "*", "hooks", "*", "args"], holds: "value", since: 3 },
|
|
135
139
|
];
|
|
136
140
|
export const CHECKPOINT_CTX_CREDENTIAL_GENERATION = Math.max(...CHECKPOINT_CTX_CREDENTIAL_SLOTS.map((slot) => slot.since));
|
|
137
141
|
export const CHECKPOINT_CTX_UNSEALABLE_REASONS = ["no_key_store", "key_not_held", "unopenable", "plaintext", "unrecognized_seal"];
|
|
@@ -148,7 +152,7 @@ export class CheckpointCtxUnsealableError extends Error {
|
|
|
148
152
|
function mintSealedCheckpointCtx(sealed) {
|
|
149
153
|
return sealed;
|
|
150
154
|
}
|
|
151
|
-
export const CHECKPOINT_CTX_SEALING_REQUIRED = "a durable checkpoint store is wired but checkpointCtxSealing is not: suspended tasks keep the request's credentials (
|
|
155
|
+
export const CHECKPOINT_CTX_SEALING_REQUIRED = "a durable checkpoint store is wired but checkpointCtxSealing is not: suspended tasks keep the request's credentials (static request headers, the command lines and environment values given to MCP stdio servers and command hooks, settings.env, settings.webSearch.apiKey) in their resume context, and they are stored sealed or not at all — give the service a sealed-key store for them (this service builds it at boot in checkpoint-ctx-keys/ under the sealed-key custody directory — SEALED_KEY_DIR, default <data root>/sealed-key/; a failure there is the earlier checkpoint_ctx_sealing_unavailable warning — 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";
|
|
152
156
|
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
153
157
|
export function isSealedCheckpointValue(v) {
|
|
154
158
|
if (!isRecord(v))
|
package/dist/http/dispatch.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, setSsoRole, isDestructiveSessionWrite, PRINCIPAL_TOKEN_HEADER, APPROVAL_MAC_HEADER, APPROVAL_MAC_KID_HEADER } from "../security.js";
|
|
3
3
|
import { publicStoreProbeError } from "../store-live-probe.js";
|
|
4
4
|
import { looksLikeJwt } from "@sema-agent/settings-schema/api/auth-bridge";
|
|
5
|
+
import { admitBridgeIdentity } from "../auth-bridge.js";
|
|
5
6
|
import { ROUTE_METHOD_ORDER } from "./route-ctx.js";
|
|
6
7
|
import { READ_CREDENTIAL_ROUTES, PUBLIC_ROUTES, POST_CREDENTIAL_ROUTES, routeLabel, isBillableSubmitPath, billableKindOf, isCredentialGatedRewrite, isMethodDispatchedSubmitPath, lookupRoute, isProbePath, isSubscriptionRequest } from "./route-table.js";
|
|
7
8
|
import { sendJson, sendError, isSseResponse, endSseWithError } from "./send.js";
|
|
@@ -212,15 +213,14 @@ export function createDispatch(ctx) {
|
|
|
212
213
|
const authz = req.headers.authorization ?? "";
|
|
213
214
|
const rawBearer = /^Bearer\s/i.test(authz) ? authz.replace(/^Bearer\s+/i, "") : "";
|
|
214
215
|
if (rawBearer && looksLikeJwt(rawBearer)) {
|
|
215
|
-
const
|
|
216
|
-
if (
|
|
217
|
-
setSsoPrincipal(req,
|
|
218
|
-
req.headers[deps.config.principalHeader.toLowerCase()] =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
deps.logger?.info?.("auth_bridge_principal_accepted", { principal: v.identity.principal, ...(v.identity.scope ? { scope: v.identity.scope } : {}) });
|
|
216
|
+
const identity = admitBridgeIdentity(await deps.webAdminJwtVerifier.verify(rawBearer), { logger: deps.logger, metrics: deps.metrics });
|
|
217
|
+
if (identity) {
|
|
218
|
+
setSsoPrincipal(req, identity.principal);
|
|
219
|
+
req.headers[deps.config.principalHeader.toLowerCase()] = identity.principal;
|
|
220
|
+
setSsoScope(req, identity.scope);
|
|
221
|
+
if (identity.role)
|
|
222
|
+
setSsoRole(req, identity.role);
|
|
223
|
+
deps.logger?.info?.("auth_bridge_principal_accepted", { principal: identity.principal, scope: identity.scope });
|
|
224
224
|
}
|
|
225
225
|
}
|
|
226
226
|
}
|
|
@@ -29,9 +29,8 @@
|
|
|
29
29
|
* —— `key_not_held` 多半是多副本没共享保管目录,运维修好之后同一个决定原样重发即可),**不明文回落、不丢键**。
|
|
30
30
|
* 开封要知道这一行的会话(封签绑会话)与开口(`ServiceDeps.checkpointCtxSealing`;缺席 = 本副本没有保管店):两件一起交({@link CheckpointCtxOpening})。
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* 结局里没有 `unsealable` 这一态(型面就没有;店边界的三态与开封形逐字同一段)。凭据位上的值在这种读法里仍是封签 —— 谁要用它们就必须走读法 ①。
|
|
32
|
+
* **一种读法**(第二个实参 = 开封的两件 {@link CheckpointCtxOpening}):读续跑上下文的只有续跑重建 spec 的那几条腿。7.136.0 起([ref])
|
|
33
|
+
* inbox 的展示位 `objective` 改读 run 行的目标预览,此前为它而设的第二种读法(「信封原样留着」,只读非凭据字段)随唯一的读者一起退役。
|
|
35
34
|
*/
|
|
36
35
|
import type { CheckpointCtx } from "./route-ctx.js";
|
|
37
36
|
import { type CheckpointCtxOpener, type CheckpointCtxUnsealableReason, type StoredCheckpointCtx } from "./checkpoint-ctx.js";
|
|
@@ -49,25 +48,12 @@ export type ResumeCtxRead = {
|
|
|
49
48
|
readonly field: string;
|
|
50
49
|
readonly reason: CheckpointCtxUnsealableReason;
|
|
51
50
|
};
|
|
52
|
-
/** 读法 ② 的结局:店边界的三态,`ok` 交落盘形(凭据位上仍是封签)。 */
|
|
53
|
-
export type SealedResumeCtxRead = {
|
|
54
|
-
readonly kind: "ok";
|
|
55
|
-
readonly ctx: StoredCheckpointCtx;
|
|
56
|
-
} | {
|
|
57
|
-
readonly kind: "absent";
|
|
58
|
-
} | {
|
|
59
|
-
readonly kind: "corrupt";
|
|
60
|
-
readonly where: string;
|
|
61
|
-
};
|
|
62
51
|
/** 开封的两件:这一行的会话(= 交给店 `getCtx` 的那一个)与开口(缺席 = 本副本没有保管店)。 */
|
|
63
52
|
export interface CheckpointCtxOpening {
|
|
64
53
|
readonly sessionId: string;
|
|
65
54
|
readonly opener: CheckpointCtxOpener | undefined;
|
|
66
55
|
}
|
|
67
|
-
/** 读法 ② 的词:信封原样留着(只读非凭据字段)。 */
|
|
68
|
-
export declare const KEEP_SEALED = "keep_sealed";
|
|
69
56
|
export declare function readResumeCtx(load: Promise<StoredCheckpointCtx | null>, opening: CheckpointCtxOpening): Promise<ResumeCtxRead>;
|
|
70
|
-
export declare function readResumeCtx(load: Promise<StoredCheckpointCtx | null>, opening: typeof KEEP_SEALED): Promise<SealedResumeCtxRead>;
|
|
71
57
|
/**
|
|
72
58
|
* 开不开的对外答复([ref])—— 409 `conflict.checkpoint_ctx_unsealable`,机读 `field`(哪一个凭据位,提交体路径;键名不是秘密,值从不进来)+ `reason`
|
|
73
59
|
* (闭集 `CHECKPOINT_CTX_UNSEALABLE_REASONS`)。句子说「这条任务的续跑上下文里带的凭据本服务开不开」、五种成因与出路(按 `reason` 修好保管目录后原样重发,
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { openCheckpointCtx } from "./checkpoint-ctx.js";
|
|
2
2
|
import { SqlJsonColumnError, StoreCorruptError } from "../plugins/sql-json-column.js";
|
|
3
3
|
import { refusalBody } from "./refusal-body.js";
|
|
4
|
-
export const KEEP_SEALED = "keep_sealed";
|
|
5
4
|
export async function readResumeCtx(load, opening) {
|
|
6
5
|
const read = await readStoredCtx(load);
|
|
7
|
-
if (read.kind !== "ok"
|
|
6
|
+
if (read.kind !== "ok")
|
|
8
7
|
return read;
|
|
9
8
|
const opened = openCheckpointCtx(read.ctx, opening.opener, opening.sessionId);
|
|
10
9
|
return opened.kind === "ok" ? { kind: "ok", ctx: opened.ctx } : { kind: "unsealable", field: opened.field, reason: opened.reason };
|
|
@@ -28,7 +27,7 @@ export function resumeCtxUnsealableAnswer(read) {
|
|
|
28
27
|
status: 409,
|
|
29
28
|
body: refusalBody({
|
|
30
29
|
errorCode: "conflict.checkpoint_ctx_unsealable",
|
|
31
|
-
message: "this task's stored resume context holds credentials from its request (
|
|
30
|
+
message: "this task's stored resume context holds credentials from its request (static request headers, the command lines and environment values given to MCP stdio servers and command hooks, settings.env, or settings.webSearch.apiKey) that this server cannot unseal — reason says why: no_key_store (this server has no sealed-key store), key_not_held (they were sealed with a key this server does not hold: another replica's, or a replaced sealed-key directory), unopenable (the sealed value is damaged or belongs to another session), plaintext (an older server version stored them unsealed), unrecognized_seal (a seal format this version does not know). Nothing was decided and nothing was resumed. If whoever operates this deployment restores the sealed-key directory that sealed them and restarts this server (it reads its keys only at startup), the same request can be sent again; otherwise submit the work again as a new task, and if this task is still suspended cancel it first (POST /v1/runs/:id/cancel).",
|
|
32
31
|
facts: {
|
|
33
32
|
field: read.field,
|
|
34
33
|
reason: read.reason,
|
|
@@ -102,8 +102,6 @@ export type QuestionAnswerCheck = {
|
|
|
102
102
|
* [ref] 起**同时闭键**:词表外的键(拼错 / 未来键 / `__proto__`)拒不丢。
|
|
103
103
|
*/
|
|
104
104
|
export declare function checkQuestionAnswer(v: unknown): QuestionAnswerCheck;
|
|
105
|
-
/** 路由体 = 从 `server.ts` 的 `handle()` 里**整段剪切**过来的原文(唯一改动:统一去缩进两格)。裸 `return;`
|
|
106
|
-
* = 「本域已应答」;走到函数尾才是「没匹配上」。跨域的东西一律在首行解构出来。 */
|
|
107
105
|
export declare function handleApprovalsAssistant(req: IncomingMessage, res: ServerResponse, match: RouteMatch<ApprovalsAssistantRouteId>, ctx: RouteCtx): Promise<void>;
|
|
108
106
|
/**
|
|
109
107
|
* 🔴 [ref]-2 / [ref]②:两条 durable 读面(`GET /v1/approvals` + `/v1/approvals/stream`)的**唯一**
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { screenedGate } from "../../trace/project.js";
|
|
2
|
+
import { skillCardFacts } from "../../approval-card.js";
|
|
2
3
|
import { hostDecisionFacts, readDecidedBy, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, DECIDED_BY_OUTSIDE_CLOSED_SET } from "../../host-decision.js";
|
|
3
4
|
import { sameExecutionRecord } from "@sema-agent/core";
|
|
5
|
+
import { findParkedAgentForCheckpoint } from "../../parked-decide.js";
|
|
4
6
|
import { DECIDED_REPLAY_SCAN_CAP, approvalPayloadFingerprint } from "../../plugins/checkpoint-store-sql.js";
|
|
5
7
|
import { carriesDeploymentListQuestion, standingAnswerDurablyRecordable } from "../../tool-approval.js";
|
|
6
8
|
import { APPROVAL_GATE_KINDS } from "../../approval-content-kind.js";
|
|
@@ -24,8 +26,7 @@ import { callGrantBindingOf, questionGrantBindingOf } from "../../standing-answe
|
|
|
24
26
|
import { sendResumeOutcome } from "../route-ctx.js";
|
|
25
27
|
import { assertNever } from "../route-ctx.js";
|
|
26
28
|
import * as facts from "../../capability-facts.js";
|
|
27
|
-
import {
|
|
28
|
-
import { StoreCorruptError } from "../../plugins/sql-json-column.js";
|
|
29
|
+
import { storeFaultClassOf } from "../../plugins/store-fault.js";
|
|
29
30
|
import { recordFailOpen } from "../../observability/fail-open.js";
|
|
30
31
|
import { durableRowIdentity } from "../../checkpoint-identity.js";
|
|
31
32
|
import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "../../request-key-closure.js";
|
|
@@ -260,6 +261,26 @@ async function decideIdempotentReplay(cs, args) {
|
|
|
260
261
|
},
|
|
261
262
|
};
|
|
262
263
|
}
|
|
264
|
+
async function gateObjectivePreview(reader, runStore, agentStore, gate) {
|
|
265
|
+
const previewOf = async (taskId) => readOwnedRun(reader.config, runStore, reader.principal, taskId, "owner_or_operator");
|
|
266
|
+
const own = (await runStore.getActiveTaskId(gate.sessionId)) ?? (await runStore.latestRunForSession(gate.sessionId))?.taskId;
|
|
267
|
+
if (own !== undefined)
|
|
268
|
+
return (await previewOf(own))?.objectivePreview ?? null;
|
|
269
|
+
if (agentStore === undefined)
|
|
270
|
+
return null;
|
|
271
|
+
let row = (await findParkedAgentForCheckpoint(agentStore, gate))?.row;
|
|
272
|
+
for (let hop = 0; row !== undefined && hop < MAX_DELEGATION_HOPS; hop++) {
|
|
273
|
+
const spawner = row.parentTaskId;
|
|
274
|
+
if (spawner === undefined)
|
|
275
|
+
return null;
|
|
276
|
+
const run = await previewOf(spawner);
|
|
277
|
+
if (run !== undefined)
|
|
278
|
+
return run.objectivePreview ?? null;
|
|
279
|
+
row = (await agentStore.get(spawner, row.scope)) ?? undefined;
|
|
280
|
+
}
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
const MAX_DELEGATION_HOPS = 8;
|
|
263
284
|
export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
264
285
|
const { deps } = ctx;
|
|
265
286
|
const { preemptableRuns } = ctx.registry;
|
|
@@ -357,13 +378,18 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
357
378
|
sendApprovalsReadFailure(req, res, deps.logger, err);
|
|
358
379
|
return;
|
|
359
380
|
}
|
|
381
|
+
const ledger = facts.durableRuns(deps);
|
|
382
|
+
const agentStore = deps.backgroundAgentStore;
|
|
360
383
|
const inbox = (await Promise.all(summaries.map(async (s) => {
|
|
361
|
-
|
|
362
|
-
if (
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
384
|
+
let objective = null;
|
|
385
|
+
if (ledger.available) {
|
|
386
|
+
try {
|
|
387
|
+
objective = await gateObjectivePreview({ config: deps.config, principal }, ledger.runStore, agentStore, s);
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
recordFailOpen("server.assistant-inbox.objective-preview-unreadable", `run row read for the inbox row of session ${JSON.stringify(s.sessionId)} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
367
393
|
const input = s.toolInput != null ? redactedPreview(s.toolInput) : null;
|
|
368
394
|
return {
|
|
369
395
|
sessionId: s.sessionId,
|
|
@@ -380,9 +406,7 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
380
406
|
...(s.mandate !== undefined ? { mandate: s.mandate } : {}),
|
|
381
407
|
...(s.denialLimitFallback !== undefined ? { denialLimitFallback: s.denialLimitFallback } : {}),
|
|
382
408
|
...(s.origin !== undefined ? { origin: s.origin } : {}),
|
|
383
|
-
...(s
|
|
384
|
-
...(s.boundGrants !== undefined ? { boundGrants: s.boundGrants } : {}),
|
|
385
|
-
...(s.classifierNoRuling !== undefined ? { classifierNoRuling: s.classifierNoRuling } : {}),
|
|
409
|
+
...skillCardFacts(s),
|
|
386
410
|
...(s.boundGrantAbsence !== undefined ? { boundGrantAbsence: s.boundGrantAbsence } : {}),
|
|
387
411
|
objective,
|
|
388
412
|
input,
|
|
@@ -871,7 +895,18 @@ export async function handleApprovalsAssistant(req, res, match, ctx) {
|
|
|
871
895
|
}
|
|
872
896
|
const APPROVALS_STREAM_POLL_MS = 3000;
|
|
873
897
|
function approvalsReadFailureOf(err) {
|
|
874
|
-
|
|
898
|
+
const fault = storeFaultClassOf(err);
|
|
899
|
+
switch (fault) {
|
|
900
|
+
case "corrupt":
|
|
901
|
+
return "approvals.read_corrupt";
|
|
902
|
+
case "connection":
|
|
903
|
+
case "unclassified":
|
|
904
|
+
return "approvals.read_unavailable";
|
|
905
|
+
default: {
|
|
906
|
+
const unhandled = fault;
|
|
907
|
+
throw new Error(`store fault class without an approvals read answer: ${String(unhandled)}`);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
875
910
|
}
|
|
876
911
|
function sendApprovalsReadFailure(req, res, logger, err) {
|
|
877
912
|
logRequestError(logger, req, err);
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -3,7 +3,7 @@ import { redactHead, redactLedgerEventForRead } from "../../observability/run-te
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { uuidv7, mintCheckpointToken, CheckpointError, policyCannotAnswer, validatePendingSteer, TASK_OUTPUT_REPLAY_KINDS, isSystemInjectionPriority } from "@sema-agent/core";
|
|
5
5
|
import { isTerminalRunStatus, isParkedRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
|
|
6
|
-
import { askRowCarriesOwner } from "../../plugins/approval-ask-store-sql.js";
|
|
6
|
+
import { askOwnerAdmits, askRowCarriesOwner } from "../../plugins/approval-ask-store-sql.js";
|
|
7
7
|
import { LIVE_RUN_GATE_KINDS, TERMINAL_PARK_GATE_KINDS } from "../../plugins/checkpoint-store-sql.js";
|
|
8
8
|
import { redactSecrets } from "../../trace/redact.js";
|
|
9
9
|
import { isUuidV7, encodeCheckpointScope } from "../../security.js";
|
|
@@ -1345,7 +1345,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
|
|
|
1345
1345
|
askNotFound();
|
|
1346
1346
|
return;
|
|
1347
1347
|
}
|
|
1348
|
-
const ownerOk =
|
|
1348
|
+
const ownerOk = askOwnerAdmits(row0.owner, principal);
|
|
1349
1349
|
const operatorOk = explicitOperatorOk(principal, deps.config);
|
|
1350
1350
|
if (!ownerOk && !operatorOk) {
|
|
1351
1351
|
askNotFound();
|
|
@@ -8,6 +8,7 @@ import { classifySyncRelationshipByIds, SyncConflictError } from "../../session-
|
|
|
8
8
|
import { isUuidShape, legacyUnownedSessionOk, sessionOwnerCellOf } from "../../security.js";
|
|
9
9
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
|
|
10
10
|
import { buildDisplaySafeUrl } from "../../trace/redact.js";
|
|
11
|
+
import { storeFaultClassOf } from "../../plugins/store-fault.js";
|
|
11
12
|
import { fleetViewOf, gatedPrincipal, explicitOperatorOk, sendUnauthorized, sessionCallerOf } from "../principal-gate.js";
|
|
12
13
|
const SYNC_BLOB_MAX_BYTES = 64 * 1024 * 1024;
|
|
13
14
|
const SYNC_NDJSON_LINE_MAX_BYTES = 8 * 1024 * 1024;
|
|
@@ -96,7 +97,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
96
97
|
out = await exportSessionManifest(sessionId, backend, historyStore);
|
|
97
98
|
}
|
|
98
99
|
catch (e) {
|
|
99
|
-
if (sendSessionPolicyError(res, e) ||
|
|
100
|
+
if (sendSessionPolicyError(res, e) || sendPullUnreadable(req, res, deps.logger, e))
|
|
100
101
|
return;
|
|
101
102
|
throw e;
|
|
102
103
|
}
|
|
@@ -120,7 +121,15 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
120
121
|
}
|
|
121
122
|
afterSeq = n;
|
|
122
123
|
}
|
|
123
|
-
|
|
124
|
+
let it;
|
|
125
|
+
try {
|
|
126
|
+
it = await sessionStorage.exportEntriesStream(sessionId, afterSeq === undefined ? undefined : { afterSeq });
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
if (sendPullUnreadable(req, res, deps.logger, e))
|
|
130
|
+
return;
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
124
133
|
if (it === null) {
|
|
125
134
|
sendSessionAbsent();
|
|
126
135
|
return;
|
|
@@ -167,7 +176,7 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
167
176
|
envelope = await exportedFileHistory(fs, sessionId);
|
|
168
177
|
}
|
|
169
178
|
catch (e) {
|
|
170
|
-
if (
|
|
179
|
+
if (sendPullUnreadable(req, res, deps.logger, e))
|
|
171
180
|
return;
|
|
172
181
|
throw e;
|
|
173
182
|
}
|
|
@@ -176,7 +185,15 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
176
185
|
sendError(res, 404, "not_found.blob", "blob not found");
|
|
177
186
|
return;
|
|
178
187
|
}
|
|
179
|
-
|
|
188
|
+
let bytes;
|
|
189
|
+
try {
|
|
190
|
+
bytes = await fs.getBlob(hash);
|
|
191
|
+
}
|
|
192
|
+
catch (e) {
|
|
193
|
+
if (sendPullUnreadable(req, res, deps.logger, e))
|
|
194
|
+
return;
|
|
195
|
+
throw e;
|
|
196
|
+
}
|
|
180
197
|
if (!bytes) {
|
|
181
198
|
sendError(res, 404, "not_found.blob", "blob not found");
|
|
182
199
|
return;
|
|
@@ -569,12 +586,17 @@ export async function handleSessionSync(req, res, match, ctx) {
|
|
|
569
586
|
}
|
|
570
587
|
}
|
|
571
588
|
}
|
|
572
|
-
function
|
|
573
|
-
if (
|
|
589
|
+
function sendPullUnreadable(req, res, logger, e) {
|
|
590
|
+
if (e instanceof FileHistoryUnreadableError) {
|
|
591
|
+
const permanence = e.failure.permanence;
|
|
592
|
+
const advice = permanence === "transient" ? "the history store judged the fault transient: retry later" : permanence === "terminal" ? "the history store judged the fault permanent: retrying will not help until the store is repaired" : "the history store did not judge whether retrying can help";
|
|
593
|
+
sendError(res, 503, "state.file_history_unreadable", `this session's file history cannot be read whole (${e.failure.code}), so nothing was exported — ${advice}`, permanence === undefined ? undefined : { permanence });
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
if (storeFaultClassOf(e) !== "connection")
|
|
574
597
|
return false;
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
sendError(res, 503, "state.file_history_unreadable", `this session's file history cannot be read whole (${e.failure.code}), so nothing was exported — ${advice}`, permanence === undefined ? undefined : { permanence });
|
|
598
|
+
logRequestError(logger, req, e);
|
|
599
|
+
sendError(res, 503, "state.file_history_unreadable", "this session's stored state could not be read right now (the store connection failed), so nothing was exported — retry later", { permanence: "transient" });
|
|
578
600
|
return true;
|
|
579
601
|
}
|
|
580
602
|
function sendMissingBlob(res) {
|
|
@@ -10,7 +10,7 @@ import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_EN
|
|
|
10
10
|
import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
|
|
11
11
|
import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
|
|
12
12
|
import { fleetViewOf, gatedPrincipal, explicitOperatorOk, sessionCallerOf, sessionOwnerReadable, sendPrincipalRefusal, sendUnauthorized } from "../principal-gate.js";
|
|
13
|
-
import { headReadOf, sessionFaceOfHead } from "../../session-incarnation.js";
|
|
13
|
+
import { headReadOf, leafOfAdmittedHead, sessionFaceOfHead } from "../../session-incarnation.js";
|
|
14
14
|
import { runRowReadableOnSessionFace } from "../owner-access.js";
|
|
15
15
|
import { assertNever, SESSION_NOT_FOUND_MESSAGE } from "../route-ctx.js";
|
|
16
16
|
import * as facts from "../../capability-facts.js";
|
|
@@ -272,7 +272,7 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
272
272
|
}
|
|
273
273
|
if (!sessionOwnerOk(req, res, await sessionOwnerCellOf(deps.sessionStorage, headSession, head.owner)))
|
|
274
274
|
return;
|
|
275
|
-
const leafId = head
|
|
275
|
+
const leafId = leafOfAdmittedHead(head);
|
|
276
276
|
const etag = `"${leafId ?? "empty"}"`;
|
|
277
277
|
if (req.headers["if-none-match"] === etag) {
|
|
278
278
|
res.writeHead(304, { etag }).end();
|
|
@@ -290,7 +290,8 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
290
290
|
sendError(res, 501, "capability.session_store_required", "session events require a durable session store (DB_BACKEND=mysql|pg|local)");
|
|
291
291
|
return;
|
|
292
292
|
}
|
|
293
|
-
const
|
|
293
|
+
const readHead = (id) => evFact.sessionStorage.headOf(id);
|
|
294
|
+
const readOwner = async (id) => (await readHead(id))?.owner;
|
|
294
295
|
const watch = evFact.watch;
|
|
295
296
|
const evSession = safeDecode(match.params.sessionId);
|
|
296
297
|
if (evSession === null) {
|
|
@@ -326,13 +327,13 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
326
327
|
return;
|
|
327
328
|
}
|
|
328
329
|
sse.sseOwnerLookups++;
|
|
329
|
-
const
|
|
330
|
-
void
|
|
330
|
+
const admissionP = readHead(evSession).then(async (head) => (head === undefined ? undefined : { head, cell: await sessionOwnerCellOf(evFact.sessionStorage, evSession, head.owner) }));
|
|
331
|
+
void admissionP.catch(() => undefined).then(() => { sse.sseOwnerLookups--; });
|
|
331
332
|
let stallTimer;
|
|
332
|
-
let
|
|
333
|
+
let admission;
|
|
333
334
|
try {
|
|
334
|
-
|
|
335
|
-
|
|
335
|
+
admission = await Promise.race([
|
|
336
|
+
admissionP,
|
|
336
337
|
new Promise((r) => { stallTimer = setTimeout(() => r("__stall__"), stallBound); stallTimer.unref?.(); }),
|
|
337
338
|
]);
|
|
338
339
|
}
|
|
@@ -342,22 +343,23 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
342
343
|
}
|
|
343
344
|
if (closed)
|
|
344
345
|
return;
|
|
345
|
-
if (
|
|
346
|
+
if (admission === "__stall__") {
|
|
346
347
|
cleanup();
|
|
347
348
|
res.setHeader("retry-after", "5");
|
|
348
349
|
sendError(res, 503, "state.sse_owner_lookup_stalled", "session-events owner lookup stalled — retry");
|
|
349
350
|
return;
|
|
350
351
|
}
|
|
351
|
-
if (
|
|
352
|
+
if (admission === undefined) {
|
|
352
353
|
cleanup();
|
|
353
354
|
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
354
355
|
return;
|
|
355
356
|
}
|
|
356
|
-
if (!sessionOwnerOk(req, res, cell)) {
|
|
357
|
+
if (!sessionOwnerOk(req, res, admission.cell)) {
|
|
357
358
|
cleanup();
|
|
358
359
|
return;
|
|
359
360
|
}
|
|
360
|
-
|
|
361
|
+
leafOfAdmittedHead(admission.head);
|
|
362
|
+
const owner = admission.cell.owner;
|
|
361
363
|
const q = new URLSearchParams(match.search);
|
|
362
364
|
const lastLeafId = q.get("lastLeafId");
|
|
363
365
|
let first = true;
|
|
@@ -656,13 +658,15 @@ export async function handleSessions(req, res, match, ctx) {
|
|
|
656
658
|
}
|
|
657
659
|
const store = rowFact.sessionStorage;
|
|
658
660
|
const sessionId = match.params.sessionId;
|
|
659
|
-
const
|
|
660
|
-
if (
|
|
661
|
+
const head = await store.headOf(sessionId);
|
|
662
|
+
if (head === undefined) {
|
|
661
663
|
sendError(res, 404, "not_found.session", SESSION_NOT_FOUND_MESSAGE);
|
|
662
664
|
return;
|
|
663
665
|
}
|
|
666
|
+
const face = sessionFaceOfHead(sessionId, head);
|
|
664
667
|
if (!sessionOwnerOk(req, res, await sessionOwnerCellOf(store, sessionId, face.owner)))
|
|
665
668
|
return;
|
|
669
|
+
leafOfAdmittedHead(head);
|
|
666
670
|
const read = readSessionRowPatch(await readJson(req));
|
|
667
671
|
if ("problem" in read) {
|
|
668
672
|
sendError(res, 400, read.problem.code, read.problem.message);
|
package/dist/http/submit-ctx.js
CHANGED
|
@@ -8,7 +8,7 @@ function unsealableRefusal(e) {
|
|
|
8
8
|
status: 409,
|
|
9
9
|
body: refusalBody({
|
|
10
10
|
errorCode: "conflict.checkpoint_ctx_unsealable",
|
|
11
|
-
message: "this deployment cannot store this task's credentials (
|
|
11
|
+
message: "this deployment cannot store this task's credentials (static request headers, the command lines and environment values given to MCP stdio servers and command hooks, settings.env, or settings.webSearch.apiKey) for resuming it: they are kept only sealed, and this server has no sealed-key store — nothing was run; ask whoever operates this deployment to give the service a writable sealed-key directory (<data root>/sealed-key/), or submit the task without those credentials",
|
|
12
12
|
facts: { field: e.field, reason: e.reason },
|
|
13
13
|
}),
|
|
14
14
|
};
|
|
@@ -541,12 +541,14 @@ export interface TaskRequestBody {
|
|
|
541
541
|
* 声明被接收、server 照常挂载但不带断言头,并有一行运维 info `mcp_assertion_header_not_sent { server }`;发送随引擎座。 */
|
|
542
542
|
mcpServers?: RequestMcpServerSpec[];
|
|
543
543
|
/** [DESIGN-269 车1] Caller-supplied per-request A2A peers (remote agents this task may call), aligned to
|
|
544
|
-
* core `A2aServerSpec`. 🔒 honored on a SINGLE-USER deployment
|
|
545
|
-
* had `a2a_peers` denied by its compliance posture (task-a2a.ts `a2aInjectionHonored`, the same three-veto
|
|
544
|
+
* core `A2aServerSpec`. 🔒 honored on a SINGLE-USER deployment (task-a2a.ts `a2aInjectionHonored`, the same
|
|
546
545
|
* predicate `capabilities.a2aInjection` advertises) — the requester is the super-admin of their OWN worker,
|
|
547
546
|
* on ANY execution lane (an A2A call is a plain outbound HTTPS request from the worker, so the lane where
|
|
548
547
|
* tool calls run is irrelevant); IGNORED on multi-tenant (a tenant can't point the shared worker at an
|
|
549
|
-
* agent of their choosing).
|
|
548
|
+
* agent of their choosing). A2A has no operator lock and no compliance capability today. A new submission
|
|
549
|
+
* with a malformed list or entry is refused 400 (`assertRequestA2aEntries`: not an array / over the 32-entry
|
|
550
|
+
* cap / an entry that is not an object / a key outside {@link A2A_PEER_ENTRY_KEYS} / a key whose value is
|
|
551
|
+
* unusable / a url or cardUrl carrying a credential) — the same refusal shape as `mcpServers`, whatever the lane.
|
|
550
552
|
* 🔴 Every skill a peer advertises mounts `egress:true` + `effect:"write"` (core's ruling) — declaring a
|
|
551
553
|
* peer is declaring an outbound WRITE channel, not a data source. Validated + gated downstream; the
|
|
552
554
|
* center/config baseline wins on a name clash (the peer name IS the tool namespace). */
|
package/dist/index.d.ts
CHANGED
|
@@ -34,4 +34,5 @@ export type { ModelRouteGap } from "./model-route-endpoint.js";
|
|
|
34
34
|
export { judgeServedModelRoute, type ServedModelRouteView, type ServedModelRouteOptions, type ModelRouteEndpointVerdict } from "./model-route-endpoint.js";
|
|
35
35
|
export type { UserInputLedgerStore, UserInputAcceptance, NewUserInputAcceptance, OpenScanCursor, OpenScanPage, SettleHow, WriterGoneEvidence } from "./user-input-ledger.js";
|
|
36
36
|
export type { OrgDeliveryQuotaStore, OrgDeliveryQuotaPolicy } from "./plugins/org-delivery-quota-store.js";
|
|
37
|
+
export { APPROVAL_GATE_KINDS, type ApprovalGateKind } from "./approval-content-kind.js";
|
|
37
38
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -27,4 +27,5 @@ export { createKeyResolver, resolveModelApiKey } from "./key-resolver.js";
|
|
|
27
27
|
export { isModelKeyPoison, ModelKeyPoisonedError } from "./key-resolver.js";
|
|
28
28
|
export { CATALOG_ENTRY_HEADER_LANES, CATALOG_ENTRY_HEADER_NAMES } from "./model-entry-headers.js";
|
|
29
29
|
export { judgeServedModelRoute } from "./model-route-endpoint.js";
|
|
30
|
+
export { APPROVAL_GATE_KINDS } from "./approval-content-kind.js";
|
|
30
31
|
//# sourceMappingURL=index.js.map
|