@sema-agent/server 7.20.0 → 7.22.0-rc.1
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/USAGE.md +31 -3
- package/dist/adoption/runner.js +4 -5
- package/dist/approval-card.d.ts +52 -0
- package/dist/approval-card.js +108 -0
- package/dist/boot/governance-seams.d.ts +112 -0
- package/dist/boot/governance-seams.js +151 -0
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +7 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +10 -2
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +13 -0
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +24 -5
- package/dist/config-types.d.ts +50 -4
- package/dist/config.js +129 -3
- package/dist/fleet/fleet-bus.d.ts +23 -2
- package/dist/fleet/fleet-bus.js +41 -2
- package/dist/fleet/subagent-tail-bus.d.ts +59 -0
- package/dist/fleet/subagent-tail-bus.js +60 -0
- package/dist/http/routes/capabilities.js +27 -0
- package/dist/http/routes/diagnostics.js +6 -0
- package/dist/http/routes/runs.js +10 -1
- package/dist/http/routes/tasks.js +15 -4
- package/dist/http/server.d.ts +5 -0
- package/dist/http/server.js +130 -7
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +12 -5
- package/dist/main.js +33 -8
- package/dist/memory-posture.d.ts +80 -0
- package/dist/memory-posture.js +40 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +2 -1
- package/dist/parked-decide.js +9 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -1
- package/dist/plugins/approval-ask-store-sql.js +5 -6
- package/dist/plugins/background-agent-store-sql.js +4 -1
- package/dist/plugins/checkpoint-store-sql.d.ts +2 -0
- package/dist/plugins/checkpoint-store-sql.js +4 -5
- package/dist/plugins/file-run-store.d.ts +2 -1
- package/dist/plugins/image-bake-store-sql.js +5 -5
- package/dist/plugins/memory-embedder-fingerprint.d.ts +23 -0
- package/dist/plugins/memory-embedder-fingerprint.js +28 -1
- package/dist/plugins/memory-engine-tidb.js +3 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +0 -5
- package/dist/plugins/memory-engine-vector-util.js +10 -4
- package/dist/plugins/memory-run-store.d.ts +2 -1
- package/dist/plugins/pg-session-storage.js +3 -3
- package/dist/plugins/run-store-sql.d.ts +12 -3
- package/dist/plugins/run-store-sql.js +4 -3
- package/dist/plugins/session-policy-store-sql.d.ts +1 -0
- package/dist/plugins/session-policy-store-sql.js +3 -3
- package/dist/plugins/shared-memory-store-sql.js +4 -10
- package/dist/plugins/sql-driver.d.ts +7 -5
- package/dist/plugins/sql-errors.d.ts +41 -0
- package/dist/plugins/sql-errors.js +25 -0
- package/dist/plugins/tidb-session-storage.js +3 -3
- package/dist/plugins/workflow-run-store-sql.js +4 -4
- package/dist/run-local.js +15 -0
- package/dist/runs.js +106 -38
- package/dist/task-mcp.d.ts +22 -1
- package/dist/task-mcp.js +71 -1
- package/dist/tool-approval.d.ts +35 -0
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-events.d.ts +43 -0
- package/dist/trace/ledger-events.js +2 -0
- package/dist/trace/ledger-sink.d.ts +21 -2
- package/dist/trace/ledger-sink.js +5 -1
- package/dist/trace/project.d.ts +4 -1
- package/dist/trace/project.js +15 -0
- package/package.json +2 -2
package/dist/task-mcp.js
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [TOC MCP seam, R3] — project the TOC client's local
|
|
3
|
+
* `.mcp.json` (`TaskRequest.mcpServers`) into the engine, so a `sema` run honors the user's own MCP servers
|
|
4
|
+
* (CC-parity "配置即用" local-first), instead of ONLY the center-config MCP. Mirrors the `cwd` seam (task-cwd.ts).
|
|
5
|
+
*
|
|
6
|
+
* 🔒 SECURITY — per-request MCP is the constitutional "untrusted-client tool injection" red line, because an MCP
|
|
7
|
+
* server is a TOOL SOURCE and an stdio transport is **arbitrary command execution**. The decisive fact (verified):
|
|
8
|
+
* core materializes stdio MCP with `StdioClientTransport`, which spawns the command as a child of **the WORKER
|
|
9
|
+
* (core's process), NOT the execution env** — so an stdio MCP runs on the worker host REGARDLESS of the exec lane
|
|
10
|
+
* (host/e2b/docker). Therefore the trust boundary is NOT "the host execution lane" (that's where the agent's TOOL
|
|
11
|
+
* CALLS run, irrelevant to MCP) — it is **"is the worker single-user, i.e. is the requester the super-admin?"** =
|
|
12
|
+
* `requirePrincipal !== true`. core's ruling (R3): the SERVICE is core's caller-trust-root; body→`TaskSpec.mcp` is a
|
|
13
|
+
* trust decision the service makes, never a passthrough.
|
|
14
|
+
* - SINGLE-USER deployment (`requirePrincipal===false`): the requester IS the super-admin of their own worker, so
|
|
15
|
+
* their `.mcp.json` is authoritative — exactly what CC does on the user's machine. This holds across EVERY
|
|
16
|
+
* execution lane (host / E2B / k8s / local-docker), so a TOC user who switches execution local↔cloud keeps their
|
|
17
|
+
* MCP honored seamlessly (TOC↔cloud is a spectrum, not a wall — clay 2026-06-29: TOB = single-user + people-mgmt).
|
|
18
|
+
* The caller being the trust root, `toolAxes` (authoritative effect overrides) + `elicitation` are their own choices.
|
|
19
|
+
* - MULTI-TENANT deployment (`requirePrincipal===true`): a tenant must NOT make the SHARED worker run an arbitrary
|
|
20
|
+
* command (RCE) or connect to a body-chosen URL (SSRF). Gate CLOSED → body MCP ignored; the fleet gets MCP only
|
|
21
|
+
* through center config refs. Per-principal http-remote injection (a center `allowMcpInjection` entitlement via
|
|
22
|
+
* `runtimeCapsResolver`) is a DEFERRED v2.
|
|
23
|
+
* When the gate is closed, a sent `mcpServers` is IGNORED (advertised via `capabilities.mcpInjection`).
|
|
24
|
+
*/
|
|
25
|
+
import { resolveComplianceDenies } from "@sema-agent/core";
|
|
26
|
+
import { HttpError } from "./security.js";
|
|
1
27
|
/** Bound on caller-supplied servers (anti-DoS; a real local `.mcp.json` has a handful). */
|
|
2
28
|
export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
3
29
|
/**
|
|
@@ -8,6 +34,21 @@ export const MAX_REQUEST_MCP_SERVERS = 32;
|
|
|
8
34
|
* run an arbitrary command / connect to an arbitrary URL).
|
|
9
35
|
*/
|
|
10
36
|
export function mcpInjectionHonored(config) {
|
|
37
|
+
// design/170 件C(codex 对抗复审 F2,验真后采纳):**锁也是这条轴的一票否决**。旧谓词只看租户轴,
|
|
38
|
+
// 于是「单用户 + `mcp` 锁」的部署会广告 `mcpInjection:true`,而每一个照此渲出 affordance 的调用方发过
|
|
39
|
+
// 来都吃 `config.locked_key` 400 —— 本仓 capabilities 面的立面承诺(「says yes ⟺ route works」)当场破。
|
|
40
|
+
// 判据合取在这里而不是在能力位那行,是因为这只谓词是**唯一**属主(能力位与请求腿都读它)。
|
|
41
|
+
if (config.lockedConfigKeys?.includes("mcp") === true)
|
|
42
|
+
return false;
|
|
43
|
+
// 件B 同族(R2-F2):合规档位禁了 `mcp_servers` 时,core 对**带 spec.mcp 的每一条腿**整拒 prepare
|
|
44
|
+
// (`config.compliance_denied`,亲核 prepare-task 的 compliance 段)。同样是「广告了却用不了」,同样一票否决。
|
|
45
|
+
//
|
|
46
|
+
// ⚠️ **刻意不 catch**(#191 门② SHAPE C 当场逮到的那一版已撤):`resolveComplianceDenies` 对闭集外的值
|
|
47
|
+
// 是 fail-loud 的,而这个档位在到这里之前已经被验过两遍(config 解析层 + boot 装配层),所以它在本站点
|
|
48
|
+
// 抛不出来。真抛了 = 有人绕过那两道门往 config 里塞了一个解析不了的档位 —— 那种时候**响亮**(路由 500)
|
|
49
|
+
// 才是对的方向,吞成一个布尔位等于让一台档位坏掉的部署继续对外广告能力位(安全轴禁静默兜底)。
|
|
50
|
+
if (config.compliancePosture !== undefined && resolveComplianceDenies(config.compliancePosture).has("mcp_servers"))
|
|
51
|
+
return false;
|
|
11
52
|
return config.requirePrincipal !== true;
|
|
12
53
|
}
|
|
13
54
|
function isStringRecord(v) {
|
|
@@ -99,6 +140,32 @@ export function validateRequestMcp(raw) {
|
|
|
99
140
|
dropped.push(`(+${raw.length - MAX_REQUEST_MCP_SERVERS} over cap of ${MAX_REQUEST_MCP_SERVERS})`);
|
|
100
141
|
return { ok, dropped };
|
|
101
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* design/170 件C §4.3 —— **同步**拒面:部署锁了 `mcp` 而请求仍带 `mcpServers` ⇒ 400 `config.locked_key`。
|
|
145
|
+
*
|
|
146
|
+
* 为什么在这里也拒一次(与 core 的 prepare 门不是「两处裁决」):设计稿把裁决点定在 server 治理层,
|
|
147
|
+
* core 的 preflight 执行的是**同一条**判据的引擎侧终局形。二者语义等价、位置不同 —— 一个请求根本不该
|
|
148
|
+
* 进 core 才发现自己被锁,而消费端拿到一条同步 4xx 比拿到一个跑起来又终态失败的任务可诊断得多。
|
|
149
|
+
*
|
|
150
|
+
* 判据是「请求**占位**」而不是「请求的值合不合法」:锁是两态的,占了就整拒,不静默丢(静默丢正是
|
|
151
|
+
* design/170 §2.3 讲的那种把判决变成探针的形)。键缺席 / 显式 `null` 都不是占位。
|
|
152
|
+
*
|
|
153
|
+
* ⚠️ **不看车道**:多租户腿本来就会忽略 body MCP(`mcpInjectionHonored` 关),但锁在场时忽略是错的
|
|
154
|
+
* 失败方向 —— operator 声明了「本部署不收任务自带 MCP」,那就该说出来,而不是安静地当没看见。
|
|
155
|
+
*
|
|
156
|
+
* ⚠️ **resume 腿同拒,这是刻意的**(与本仓「4xx 会砖存量 task」的一般姿势有意分歧):锁上之后,一条存量
|
|
157
|
+
* 任务的 body 里带着 mcpServers 就再也过不了 core 的 preflight —— 放它进去只会换来一个跑起来又终态失败的
|
|
158
|
+
* 任务。同样是失败,同步 400 至少是**立刻可诊断**的那一种,而且拒得出理由(锁在哪、怎么改)。
|
|
159
|
+
*/
|
|
160
|
+
export function assertRequestMcpUnlocked(bodyMcp, lockedKeys) {
|
|
161
|
+
if (!lockedKeys.has("mcp"))
|
|
162
|
+
return;
|
|
163
|
+
if (bodyMcp === undefined || bodyMcp === null)
|
|
164
|
+
return;
|
|
165
|
+
throw new HttpError(400, "`mcpServers` is administratively locked by this deployment (locked key \"mcp\") — task-supplied MCP servers are refused; locks are config-time and there is no request-time unlock", {
|
|
166
|
+
code: "config.locked_key",
|
|
167
|
+
});
|
|
168
|
+
}
|
|
102
169
|
/**
|
|
103
170
|
* Merge GATED per-request MCP servers OVER the deployment baseline (center/config). The baseline WINS on a name
|
|
104
171
|
* clash — a caller can ADD servers but can never SHADOW a configured one (the security baseline is authoritative,
|
|
@@ -117,7 +184,10 @@ export function mergeRequestMcp(baseline, gated) {
|
|
|
117
184
|
* unchanged. Logs an `mcp_injection_ignored` when a caller SENT servers a closed gate dropped (so the shell never
|
|
118
185
|
* silently believes its `.mcp.json` took effect — it also reads `capabilities.mcpInjection`).
|
|
119
186
|
*/
|
|
120
|
-
export function resolveRequestMcp(baseline, bodyMcp,
|
|
187
|
+
export function resolveRequestMcp(baseline, bodyMcp,
|
|
188
|
+
// `lockedConfigKeys` 随 config 一并进(design/170 件C):`assertRequestMcpUnlocked` 已在腿首整拒,所以
|
|
189
|
+
// 这里的 honored 判据在锁形下不可达 —— 保留它是纵深(判据只有一只谓词,不许两处各判一半)。
|
|
190
|
+
config, logger) {
|
|
121
191
|
if (bodyMcp === undefined || bodyMcp === null)
|
|
122
192
|
return baseline;
|
|
123
193
|
const honored = mcpInjectionHonored(config);
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -108,6 +108,41 @@ export interface ToolApprovalFrame {
|
|
|
108
108
|
match: "exact" | "prefix";
|
|
109
109
|
command: string;
|
|
110
110
|
}>;
|
|
111
|
+
/**
|
|
112
|
+
* #253 件 G1(core 5.33.0 backlog #239,判据帖 [3930] G1;**ADDITIVE**,`"tool_approval"` only)——
|
|
113
|
+
* 这次 ask **为什么**被收紧的**结构化**因由(`AskRequest.probeCause`:工具的 `reversibilityProbe`
|
|
114
|
+
* 判不出可回滚时,引擎在 maybe 档收紧点铸并 `normalizeProbeCause` 校验过的那一只)。
|
|
115
|
+
*
|
|
116
|
+
* 🔴 **结构就是契约**:`{ code, roots:{shown,total}, further?:{shown,total} }` —— `code` 机器可读、
|
|
117
|
+
* 两个操作数族各带真实基数。引擎的原话是「ships a code and operand arrays, never a finished
|
|
118
|
+
* sentence」,句子归呈卡端渲。⇒ server **逐字透传结构**,不拍扁成文本、不重排、不合并两族。
|
|
119
|
+
* 值经 `approval-card.ts` 的 `readProbeCause` 窄读(与 `card_json` **同一个**函数 ⇒ 两面同值);
|
|
120
|
+
* 形不合按缺席处置,`code` 超限整只丢、`shown` 超限截条目而 `total` 保真(理由见那里的顶注)。
|
|
121
|
+
*
|
|
122
|
+
* 🔴 **与 `message` 刻意分列**(判据帖 G1 的负控):`message` 是分类器/模型的输入面,cause 永不进它;
|
|
123
|
+
* 本键是展示面的结构化数据。负控钉在 `test/approval-probe-cause-wire.test.ts` §3。
|
|
124
|
+
*
|
|
125
|
+
* 🔴 **缺席 ≠「没有原因」**:只有 maybe 档收紧且探针真给了 cause 的 ask 才有它。
|
|
126
|
+
*
|
|
127
|
+
* 耐久路的对偶 = `gate.riskDescriptor.probeCause`(同值,core `buildRiskDescriptor` 铸),走 checkpoint
|
|
128
|
+
* 行的**整体透传**到 `GET /v1/approvals` —— 键集零施工,钉在同一个文件的 §4。
|
|
129
|
+
*
|
|
130
|
+
* 姊妹键 `AskRequest.probeReason`(同一件事的**散文**兄弟)**今天不投影**:它与本键是同一个事实的两种
|
|
131
|
+
* 拼法,而 core 的边界哲学(交 code + 数组、不交句子)与「一张卡上不放两份同义解释」都指向只投结构化
|
|
132
|
+
* 那一份;散文那份还无法被消费端本地化。照 `preview` / `requiresRealApproval` 先例 = 候消费端(cli)
|
|
133
|
+
* 提渲染需求再翻(登记在 `trace/core-keyset-guard.ts` 的 EXCLUDED 栏)。
|
|
134
|
+
*/
|
|
135
|
+
probeCause?: {
|
|
136
|
+
code: string;
|
|
137
|
+
roots: {
|
|
138
|
+
shown: string[];
|
|
139
|
+
total: number;
|
|
140
|
+
};
|
|
141
|
+
further?: {
|
|
142
|
+
shown: string[];
|
|
143
|
+
total: number;
|
|
144
|
+
};
|
|
145
|
+
};
|
|
111
146
|
/** "tool_approval" only: the tool call's args, secret-redacted, UNTRUSTED-for-display. Absent (with
|
|
112
147
|
* `argsOmitted: true`) when over the byte cap or unserializable. */
|
|
113
148
|
args?: unknown;
|
package/dist/tool-approval.js
CHANGED
|
@@ -48,7 +48,7 @@ import { uuidv7, MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
|
|
|
48
48
|
import { redactDeep, redactSecrets } from "./trace/redact.js";
|
|
49
49
|
import { createLogger } from "./observability/logger.js";
|
|
50
50
|
import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
51
|
-
import { ApprovalCardEnvelopeSchema, MAX_RULE_SUGGESTIONS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, } from "./approval-card.js";
|
|
51
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_SUGGESTIONS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, } from "./approval-card.js";
|
|
52
52
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
53
53
|
/** #151 车2:本模块自有的日志出口——同 config-provider.ts/runtime-caps-resolver.ts 先例(协调器不走
|
|
54
54
|
* DI logger,构造签名是设计定稿钉死的三键 options bag,加第四个 logger 键属于重议已裁事项)。仅用于
|
|
@@ -900,6 +900,9 @@ export class ToolApprovalCoordinator {
|
|
|
900
900
|
: {}),
|
|
901
901
|
// #154 车二:候选逐字透传(条件见 `ruleLaneMaterial` 上方注)。
|
|
902
902
|
...(ruleLaneMaterial !== undefined ? { ruleSuggestions: ruleLaneMaterial.suggestions } : {}),
|
|
903
|
+
// #253 件 G1:结构化探针因由。窄读走 `approval-card.ts` 的 `readProbeCause` —— **卡也用它**,
|
|
904
|
+
// 所以活卡帧与 `card_json` 是同一份值(而不是两处各挑一次键)。缺席/形不合 ⇒ 不铸键。
|
|
905
|
+
...((c) => (c !== undefined ? { probeCause: c } : {}))(readProbeCause(req)),
|
|
903
906
|
...(bounded.omitted ? { argsOmitted: true } : { args: bounded.args }),
|
|
904
907
|
};
|
|
905
908
|
// #151 车2(design/172 §3.3 D3 窗长三元):有效窗——legDeadlineMonotonic 缺席时退化成 ttlMs(D1 零
|
|
@@ -26,8 +26,8 @@ type BgNotifExcluded = "kind" | "sessionScoped" | "owner" | "scope" | "descripti
|
|
|
26
26
|
type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, BgNotifProjected | BgNotifExcluded>>;
|
|
27
27
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
28
28
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
29
|
-
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleSuggestions" | "persistedRuleShadowed";
|
|
30
|
-
type AskExcluded = "preview" | "principal" | "requiresRealApproval" | "riskAxes" | "boundInputHash" | "isDelegatedChild";
|
|
29
|
+
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleSuggestions" | "persistedRuleShadowed" | "probeCause";
|
|
30
|
+
type AskExcluded = "preview" | "principal" | "requiresRealApproval" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason";
|
|
31
31
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
32
32
|
type TaskEventHandled = "text_delta" | "reasoning_delta" | "tool_start" | "tool_end" | "turn_end" | "compacted" | "diagnostics" | "message_committed" | "status" | "task_notification" | "task_progress" | "steering_injected" | "workspace_changed" | "done" | "context_usage" | "compaction_outcome" | "human_input" | "wiring_manifest";
|
|
33
33
|
type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* runs 账本(`task_run_event`)的**事件型闭集**——A-032 P1-③ / codex 对抗复审 F2。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么需要这个联合
|
|
5
|
+
* 账本行的 `type` 是消费端分流的键:`GET /v1/runs/:id/events` 裸回放照发,而 `trace/project.ts` 的
|
|
6
|
+
* `mapTraceEventInner` 要把其中一部分投影成 registry 契约 2.3 的 SSE 帧,剩下的**刻意**落 null。
|
|
7
|
+
* 「新加了一个账本事件型却没人给它归类」这件事此前只能靠源码扫描发现,而扫描对**别名门**是盲的
|
|
8
|
+
* (真例:`http/server.ts` 的 `const ap = (type, data) => rs2.appendEvent(...)`,以及
|
|
9
|
+
* `ap(reSuspended ? reParkStatus : "suspended", …)` 这种**动态**首参 —— 任何词法门都抓不到)。
|
|
10
|
+
* 把词表收成一个**类型**,新型就必须先在这里登记(否则调用点编译红),投影闭合门再从这里枚举 ⇒
|
|
11
|
+
* 「加型 ⇒ 必须归类」变成编译期 + 门的两道执法,不再依赖扫描器的视力。
|
|
12
|
+
*
|
|
13
|
+
* ## 执法面(收到哪一层)
|
|
14
|
+
* 联合被**三层**写口共同引用,所以"绕开 typed 门直写"这条路已经堵死(codex R2-F2:此前只收了
|
|
15
|
+
* `LedgerSink.append`,底层 `appendEvent` 仍吃 `string`,`const t = "x"; runStore.appendEvent(id, seq, t, d)`
|
|
16
|
+
* 既过编译又躲过逐行字面量扫描):
|
|
17
|
+
* 1. `LedgerSink.append` / `trace/project.ts` 的 `AppendFn` / 各腿的 `append`·`ap`·`approvalEmit` 闭包;
|
|
18
|
+
* 2. `createLedgerSink({ appendEvent })` 的回调签名;
|
|
19
|
+
* 3. **三个 RunStore 实现**的 `appendEvent(taskId, seq, type, data)`(SQL / file / memory)—— 账本行的
|
|
20
|
+
* 最终落点,类型收在这里才与"能不能写进去"同源。
|
|
21
|
+
*
|
|
22
|
+
* ## 射程上限(如实,别夸大)
|
|
23
|
+
* 仍有**一条动态门**:`http/server.ts` 的通知 drain 回调把 core 帧的 `type` 逐字落账(那里有一处
|
|
24
|
+
* 显式窄化 + 自陈注释)。它今天产出的是 `task_notification`(已在册),但 core 换帧名时本联合**不会**
|
|
25
|
+
* 自动红 —— 那条腿的对齐由 core 帧集的消费门(`trace/core-keyset-guard.ts` 一族)负责,不是这里。
|
|
26
|
+
* 另有若干测试用显式窄化写入**历史行名**(`brain_status`)或合成桩型:那是在模拟"库里躺着的旧行/
|
|
27
|
+
* 失败路径",不是本版本的写口 —— 本联合的语义是「**这个版本会写出来的型**」,故不收它们。
|
|
28
|
+
*
|
|
29
|
+
* ## 成员的两类
|
|
30
|
+
* · **流事件投影**:两条 durable 腿(bg = `runs.ts` + `trace/ledger-sink.ts`,resume = `http/server.ts`)
|
|
31
|
+
* 的 `switch (ev.type)` 白名单臂(差集门 `test/durable-append-arm-parity.test.ts` 看守两腿一致);
|
|
32
|
+
* · **腿自己的带外行**:park/终局/失败/建议/用量/装配自证(不来自流事件)。
|
|
33
|
+
*/
|
|
34
|
+
export type LedgerEventType = "reasoning" | "text" | "tool_start" | "tool_end" | "status" | "context_usage" | "compacted" | "diagnostics" | "steering_injected" | "compaction_outcome" | "workspace_changed" | "wiring_manifest" | "human_input" | "task_progress" | "task_notification" | "turn_end"
|
|
35
|
+
/** park 行名**两个**(`appendParked(kind)` 逐字取自 `result.status`)。 */
|
|
36
|
+
| "suspended" | "needs_review" | "done" | "failed" | "suggestions" | "model_usage" | "prompt_assembled" | "config_assembled" | "file_link"
|
|
37
|
+
/** 人在环三族的 open/close 帧对(投递面 = 本腿的 durable events tail)。 */
|
|
38
|
+
| "elicitation" | "elicitation_complete" | "question" | "question_complete" | "tool_approval" | "tool_approval_complete"
|
|
39
|
+
/** design/172 §4.3 新协议的呈卡帧(与上面两只 tool_approval 帧同一条投递面)。 */
|
|
40
|
+
| "approval_request"
|
|
41
|
+
/** 异步 workflow 完成的入箱 drain(`emitPendingWorkflowCompletions` 两个字面型之一)。 */
|
|
42
|
+
| "workflow_complete";
|
|
43
|
+
//# sourceMappingURL=ledger-events.d.ts.map
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { brainStatusEventData } from "./project.js";
|
|
1
2
|
import { NotifiedKeys } from "../orchestration/workflow-completion-inbox.js";
|
|
3
|
+
import type { LedgerEventType } from "./ledger-events.js";
|
|
2
4
|
/**
|
|
3
5
|
* LedgerSink — the SINGLE "stream event → durable runs-ledger" projection, shared by the background leg
|
|
4
6
|
* (runs.ts `runInBackground`) and the sync `/v1/tasks/stream` detach lane (server.ts, `x-detach-on-disconnect`).
|
|
@@ -21,7 +23,7 @@ export interface LedgerSink {
|
|
|
21
23
|
/** Seq-stamped raw append — for a leg's out-of-band rows (elicitation/question frames, model_usage deltas,
|
|
22
24
|
* suggestions, `failed`) that must share this run's single seq counter. Redaction is the CALLER's duty on
|
|
23
25
|
* this raw door (every existing site already redacts); the typed doors below redact internally. */
|
|
24
|
-
append(type:
|
|
26
|
+
append(type: LedgerEventType, data: unknown): Promise<void>;
|
|
25
27
|
/** Flush the buffered turn text/reasoning. Thinking precedes content within a turn → a buffered reasoning
|
|
26
28
|
* block flushes FIRST (redacted; core [R57]: CoT may carry reasoned-about secrets — TRACE_THINKING gates
|
|
27
29
|
* capture). The `text` row is stamped with the turn's first text eventId (E18 per-message resume handle,
|
|
@@ -32,6 +34,23 @@ export interface LedgerSink {
|
|
|
32
34
|
onEvent(ev: {
|
|
33
35
|
type: string;
|
|
34
36
|
}): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* #255(core 5.34.0 的 `RunInternals.onStatusEvent` 第五席):**「只回结果」腿**的 `status` 行写口。
|
|
39
|
+
*
|
|
40
|
+
* 与 {@link onEvent} 的 `case "status"` 共用**同一个**白名单 builder(`brainStatusEventData`),差别只有
|
|
41
|
+
* 一处:本门**同步入链** —— 在回调进入的那一刻就占住序号槽,而不是先 `await flush()` 再入链。
|
|
42
|
+
*
|
|
43
|
+
* 🔴 **为什么必须同步入链**(codex 对抗复审 R2 [medium],红先复现后修):`onForwardEvent` 是同步入链的,
|
|
44
|
+
* 而 core 的第五席与 forward sink 都是**同步回调**、可能同一拍相邻到达。走 `onEvent` 的话那一次
|
|
45
|
+
* `await flush()` 会让**后到**的 forward 帧抢到**更小**的序号 ⇒ durable 时间线倒挂(消费端读到「子代
|
|
46
|
+
* 进度」排在触发它的那次重连之前)。钉在 `test/issue190-status-wire-disclosure.test.ts` §4⑤b。
|
|
47
|
+
*
|
|
48
|
+
* 🔴 **为什么可以不 flush**:本门只服务「只回结果」的两条腿(verify/cascade、resume-verify),它们的
|
|
49
|
+
* sink **从不收** `text_delta`/`reasoning_delta` —— `onForwardEvent` 有意不逐块持久化子代文本,而
|
|
50
|
+
* `onEvent` 在这些腿上根本没有调用点 ⇒ 没有可被倒挂的已缓冲文本。真有文本缓冲的腿(普通流形 / sync
|
|
51
|
+
* detach / resume stream)走 `onEvent` 或自己那份 `case`,那里的 **flush-first 纪律一个字不动**。
|
|
52
|
+
*/
|
|
53
|
+
appendStatus(ev: Parameters<typeof brainStatusEventData>[0]): Promise<void>;
|
|
35
54
|
/** Forward-sink (subagent) events → ledger, bg-leg posture: fire-and-forget with SYNCHRONOUS seq allocation
|
|
36
55
|
* (sink-vs-loop appends get unique seqs), same whitelist+redact builders (forwarded events are UNTRUSTED
|
|
37
56
|
* RAW — §E1). Child text/reasoning deltas are deliberately NOT persisted per-chunk (a child's turn text has
|
|
@@ -53,7 +72,7 @@ export interface LedgerSink {
|
|
|
53
72
|
}
|
|
54
73
|
export declare function createLedgerSink(opts: {
|
|
55
74
|
/** The seq-stamped durable append — typically `(seq, type, data) => runStore.appendEvent(taskId, seq, type, data)`. */
|
|
56
|
-
appendEvent: (seq: number, type:
|
|
75
|
+
appendEvent: (seq: number, type: LedgerEventType, data: unknown) => Promise<void>;
|
|
57
76
|
/** TRACE_THINKING gate: reasoning deltas are buffered (later redacted+persisted) only when true (core [R57] —
|
|
58
77
|
* persisting CoT is a deployment decision). Both legs pass the SAME config source (`config.traceThinking`). */
|
|
59
78
|
persistThinking: boolean;
|
|
@@ -191,6 +191,10 @@ export function createLedgerSink(opts) {
|
|
|
191
191
|
void append("tool_end", toolEndEventData(e)).catch(() => undefined);
|
|
192
192
|
}
|
|
193
193
|
};
|
|
194
|
+
// #255:第五席的写口 —— 一行,但**位置**是它的全部:`append` 在这里被**同步**调用,序号槽在回调进入
|
|
195
|
+
// 的那一刻就占住(理由与判据全文见接口上的 doc 注)。别为了「与 onEvent 同形」把它改写成
|
|
196
|
+
// `onEvent({type:"status",…})`:那一次 `await flush()` 正是被修掉的病。
|
|
197
|
+
const appendStatus = (ev) => append("status", brainStatusEventData(ev));
|
|
194
198
|
const appendParked = (kind, result) =>
|
|
195
199
|
// Never the capability checkpointToken — only the non-secret gate (search [18] Q1b).
|
|
196
200
|
append(kind, { gate: result?.checkpointGate ?? null });
|
|
@@ -203,6 +207,6 @@ export function createLedgerSink(opts) {
|
|
|
203
207
|
}
|
|
204
208
|
await appendDone(result);
|
|
205
209
|
};
|
|
206
|
-
return { append, flush, onEvent, onForwardEvent, appendParked, appendDone, onDone };
|
|
210
|
+
return { append, flush, onEvent, appendStatus, onForwardEvent, appendParked, appendDone, onDone };
|
|
207
211
|
}
|
|
208
212
|
//# sourceMappingURL=ledger-sink.js.map
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
import type { ApprovalSettledBy, TaskResult } from "@sema-agent/core";
|
|
16
16
|
import type { RunEvent, RunRecord } from "../plugins/store-contracts.js";
|
|
17
17
|
import type { ModelUsageDelta, ModelUsageTracker, PromptManifestRecord } from "../budget.js";
|
|
18
|
-
|
|
18
|
+
import type { LedgerEventType } from "./ledger-events.js";
|
|
19
|
+
/** 账本写口的统一签名 —— `type` 是**闭集**(`ledger-events.ts`):新型必须先登记,否则调用点编译红
|
|
20
|
+
* (codex 对抗复审 F2:别名门 `ap("x")` 与动态首参让纯词法门结构性抓不全)。 */
|
|
21
|
+
type AppendFn = (type: LedgerEventType, data: unknown) => Promise<void>;
|
|
19
22
|
type GetEventsFn = (taskId: string, afterSeq: number) => Promise<RunEvent[]>;
|
|
20
23
|
/** E8 (shell-host): drain the per-task model-usage accumulator into an append-only `model_usage` DELTA event.
|
|
21
24
|
* Shared by BOTH durable-write legs (runInBackground + the resume leg) so the persisted shape never drifts; the
|
package/dist/trace/project.js
CHANGED
|
@@ -903,6 +903,21 @@ function mapTraceEventInner(type, seq, data) {
|
|
|
903
903
|
// approvals 的帧同形。这一处是 3.0.0 摘 legacy `code` 键时的漏网:帧在 src/trace/ 铸,而当时的门只扫
|
|
904
904
|
// src/http/,于是"SSE error 帧机器键统一为 errorCode"的公开声明与 wire 上跑的东西不一致(门是绿的)。
|
|
905
905
|
return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message: redactSecrets(String(data.errorMessage ?? "run failed")) } };
|
|
906
|
+
// 🔴 **显式不投影**(A-032 P1-③ 裁定,2026-08-14)。这四型是账本写口(`trace/ledger-sink.ts` 与
|
|
907
|
+
// resume 腿都 append 它们)却从来不在任何 case 臂上,于是走的是下面那个 `default` —— 而「刻意不发帧」
|
|
908
|
+
// 与「忘了接线」在码面上**同形**,两者的下一步动作却相反(前者什么都不做,后者是缺陷)。写成显式臂
|
|
909
|
+
// 就是把裁定钉在源码上。裁的是「不投影」,理由不是懒:契约 2.3 的帧集是**闭集**(thinking/content/
|
|
910
|
+
// tool/turn/prompt/done/error 七类),给它加一个新帧名会让穷举式 closed-oneOf 消费端收到公开联合之外
|
|
911
|
+
// 的帧 —— design/158 B2 那次(durable 腿发 `brain_status`、公开联合只有 `status`)真咬过的同形。
|
|
912
|
+
// 这四型的消费面是 `GET /v1/runs/:id/events` 裸回放,不是这条流。特别地 `human_input` **不能**折成
|
|
913
|
+
// content/thinking:它是「谁经哪条载体喂了这条 run」的归属事实,映射成 agent 内容就是编造。
|
|
914
|
+
// 要改成投影 ⇒ 那是一次 wire 帧集扩张(能力发布件,须点名消费方表态),不是顺手加一行。
|
|
915
|
+
// 闭合门:test/probe-wire-1.test.ts「A-032 P1-③ 账本写口 ↔ mapTraceEvent 投影的三方闭合门」。
|
|
916
|
+
case "context_usage":
|
|
917
|
+
case "compaction_outcome":
|
|
918
|
+
case "wiring_manifest":
|
|
919
|
+
case "human_input":
|
|
920
|
+
return null;
|
|
906
921
|
default:
|
|
907
922
|
return null; // compacted etc. — not a client trace event
|
|
908
923
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.22.0-rc.1",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "^5.
|
|
57
|
+
"@sema-agent/core": "^5.34.0",
|
|
58
58
|
"@sema-agent/registry-core": "^0.16.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|