@sema-agent/server 7.56.0 → 7.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/README.zh-CN.md +2 -2
- package/USAGE.md +101 -9
- package/dist/approval-ask-audit-store.d.ts +100 -1
- package/dist/approval-ask-audit-store.js +103 -2
- package/dist/approval-card.d.ts +148 -13
- package/dist/approval-card.js +82 -13
- package/dist/approval.d.ts +31 -0
- package/dist/approval.js +18 -0
- package/dist/auto-mode-face.d.ts +111 -0
- package/dist/auto-mode-face.js +99 -0
- package/dist/bench/s1/arms.js +5 -5
- package/dist/bench/s1/live-deps.js +11 -11
- package/dist/bench/s1/run-firm.js +3 -0
- package/dist/bench/s1/runner-ctx.d.ts +4 -0
- package/dist/bench/s1/runner-ctx.js +7 -0
- package/dist/boot/config-center.js +23 -1
- package/dist/boot/coordinators.js +13 -1
- package/dist/boot/parked-revive-gate.d.ts +7 -2
- package/dist/boot/parked-revive-gate.js +12 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +3 -1
- package/dist/boot/runner-deps.d.ts +12 -0
- package/dist/boot/runner-deps.js +28 -3
- package/dist/boot/runtime-caps.d.ts +19 -9
- package/dist/boot/runtime-caps.js +49 -21
- package/dist/boot/session-shell-gate-registry.d.ts +39 -0
- package/dist/boot/session-shell-gate-registry.js +21 -0
- package/dist/config-catalog.js +14 -5
- package/dist/config-types.d.ts +41 -10
- package/dist/config.d.ts +21 -0
- package/dist/config.js +40 -5
- package/dist/http/route-ctx.d.ts +75 -0
- package/dist/http/routes/a2a-serve.js +5 -3
- package/dist/http/routes/approvals-assistant.js +19 -6
- package/dist/http/routes/capabilities.js +21 -4
- package/dist/http/routes/memory-origin.d.ts +2 -2
- package/dist/http/routes/rules.js +3 -2
- package/dist/http/routes/runs.d.ts +0 -14
- package/dist/http/routes/runs.js +20 -8
- package/dist/http/routes/tasks.js +34 -8
- package/dist/http/routes/workflows.js +19 -4
- package/dist/http/server.d.ts +11 -1
- package/dist/http/server.js +215 -43
- package/dist/http/wire-types.d.ts +9 -4
- package/dist/leader/diffout.js +2 -1
- package/dist/leader/endpoint.js +45 -12
- package/dist/leader/fanout.js +6 -5
- package/dist/leader/leader.js +17 -14
- package/dist/leader/merge.js +17 -12
- package/dist/leader/planner.js +3 -2
- package/dist/leader/repair-oracle.js +6 -4
- package/dist/leader/repair-wire.js +5 -3
- package/dist/leader/wire.d.ts +30 -1
- package/dist/leader/wire.js +36 -19
- package/dist/main.js +43 -7
- package/dist/observability/err-text.d.ts +6 -0
- package/dist/observability/err-text.js +10 -0
- package/dist/observability/fail-open.d.ts +40 -0
- package/dist/observability/fail-open.js +19 -0
- package/dist/observability/metrics.js +1 -1
- package/dist/observability/run-terminal-log.d.ts +120 -0
- package/dist/observability/run-terminal-log.js +360 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +9 -0
- package/dist/orchestration/workflow-completion-inbox.js +8 -0
- package/dist/permission-rule-vocab.d.ts +40 -0
- package/dist/permission-rule-vocab.js +17 -0
- package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
- package/dist/plugins/checkpoint-store-sql.js +19 -3
- package/dist/plugins/file-run-store.d.ts +3 -34
- package/dist/plugins/file-run-store.js +19 -0
- package/dist/plugins/local-checkpoint-store.d.ts +10 -0
- package/dist/plugins/local-checkpoint-store.js +3 -0
- package/dist/plugins/memory-run-store.d.ts +3 -15
- package/dist/plugins/memory-run-store.js +19 -0
- package/dist/plugins/permission-rule-store-file.js +8 -3
- package/dist/plugins/permission-rule-store-sql.js +16 -10
- package/dist/plugins/remote-scratchpad.js +3 -2
- package/dist/plugins/run-store-sql.d.ts +3 -42
- package/dist/plugins/run-store-sql.js +30 -1
- package/dist/plugins/store-backend.d.ts +1 -1
- package/dist/plugins/store-contracts.d.ts +66 -1
- package/dist/plugins/workflow-run-store-sql.d.ts +5 -0
- package/dist/plugins/workflow-run-store-sql.js +10 -2
- package/dist/rules-consent.js +20 -13
- package/dist/run-cancel-context.d.ts +13 -0
- package/dist/run-cancel-context.js +15 -0
- package/dist/run-local.js +2 -2
- package/dist/runs.d.ts +4 -1
- package/dist/runs.js +41 -10
- package/dist/runtime-caps-resolver.d.ts +133 -17
- package/dist/runtime-caps-resolver.js +41 -3
- package/dist/task-settings.d.ts +57 -3
- package/dist/task-settings.js +78 -5
- package/dist/task-workflow.d.ts +32 -2
- package/dist/task-workflow.js +8 -3
- package/dist/tool-approval.d.ts +26 -3
- package/dist/tool-approval.js +96 -13
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-sink.d.ts +13 -4
- package/dist/trace/ledger-sink.js +14 -6
- package/dist/trace/project.d.ts +13 -0
- package/dist/trace/project.js +10 -1
- package/dist/trace/redact.d.ts +22 -11
- package/dist/trace/redact.js +655 -20
- package/dist/trace/sema-provenance.d.ts +26 -0
- package/dist/trace/sema-provenance.js +11 -0
- package/dist/turn-activity.d.ts +32 -2
- package/dist/turn-activity.js +29 -4
- package/package.json +2 -2
package/dist/task-settings.js
CHANGED
|
@@ -3,12 +3,18 @@ import { findCcRuleFormNames, findUnmatchableToolNames, formatUnmatchableToolNam
|
|
|
3
3
|
import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
|
|
4
4
|
import { parseHooksConfig } from "./hooks/hook-runner.js";
|
|
5
5
|
export function coercePermissionMode(raw) {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const strict = coercePermissionModeStrict(raw);
|
|
7
|
+
if (strict !== undefined)
|
|
8
|
+
return strict;
|
|
8
9
|
if (typeof raw === "string" && raw.length > 0)
|
|
9
10
|
return "default";
|
|
10
11
|
return undefined;
|
|
11
12
|
}
|
|
13
|
+
export function coercePermissionModeStrict(raw) {
|
|
14
|
+
if (raw === "plan" || raw === "acceptEdits" || raw === "default" || raw === "bypassPermissions" || raw === "auto")
|
|
15
|
+
return raw;
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
12
18
|
export function effectiveThinking(reasoningEffort, ultracode) {
|
|
13
19
|
const explicit = isThinkingLevel(reasoningEffort) ? reasoningEffort : undefined;
|
|
14
20
|
if (!ultracode)
|
|
@@ -21,7 +27,11 @@ export function withPermissionMode(settings, mode) {
|
|
|
21
27
|
return { ...base, permissions: { ...base.permissions, defaultMode: mode } };
|
|
22
28
|
}
|
|
23
29
|
export function effectivePermissionMode(body, settings) {
|
|
24
|
-
|
|
30
|
+
const requested = coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
|
|
31
|
+
const kill = settings?.permissions?.disableAutoMode;
|
|
32
|
+
if (requested === "auto" && (kill === "disable" || kill === true))
|
|
33
|
+
return "default";
|
|
34
|
+
return requested;
|
|
25
35
|
}
|
|
26
36
|
export function shellGateForMode(effMode) {
|
|
27
37
|
switch (effMode) {
|
|
@@ -62,6 +72,61 @@ function cleanStringArray(v) {
|
|
|
62
72
|
const out = v.filter((x) => typeof x === "string" && x.length > 0).slice(0, MAX_SETTINGS_PERMISSION_RULES);
|
|
63
73
|
return out.length > 0 ? out : undefined;
|
|
64
74
|
}
|
|
75
|
+
export const TASK_SETTINGS_KEYS = ["env", "hooks", "model", "outputStyle", "permissions", "ultracode", "webSearch"];
|
|
76
|
+
export const TASK_SETTINGS_PERMISSION_KEYS = ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
|
|
77
|
+
export const TASK_SETTINGS_UNSUPPORTED_KEYS = ["apiKeyHelper", "sandbox", "statusLine"];
|
|
78
|
+
export const TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS = ["additionalDirectories", "disableBypassPermissionsMode"];
|
|
79
|
+
export const MAX_SETTINGS_KEY_ECHO = 20;
|
|
80
|
+
const MAX_SETTINGS_KEY_ECHO_CHARS = 64;
|
|
81
|
+
const ACCEPTED_TOP = new Set(TASK_SETTINGS_KEYS);
|
|
82
|
+
const ACCEPTED_PERM = new Set(TASK_SETTINGS_PERMISSION_KEYS);
|
|
83
|
+
const UNSUPPORTED_TOP = new Set(TASK_SETTINGS_UNSUPPORTED_KEYS);
|
|
84
|
+
const UNSUPPORTED_PERM = new Set(TASK_SETTINGS_UNSUPPORTED_PERMISSION_KEYS);
|
|
85
|
+
function echoSettingsKey(prefix, key) {
|
|
86
|
+
const clean = key.replace(CONTROL_AND_BIDI_STRIP_RE, "");
|
|
87
|
+
return `${prefix}${clean.length > MAX_SETTINGS_KEY_ECHO_CHARS ? `${clean.slice(0, MAX_SETTINGS_KEY_ECHO_CHARS)}…` : clean}`;
|
|
88
|
+
}
|
|
89
|
+
export function taskSettingsKeyIssue(raw) {
|
|
90
|
+
if (raw === null || raw === undefined || typeof raw !== "object" || Array.isArray(raw))
|
|
91
|
+
return null;
|
|
92
|
+
const unknown = [];
|
|
93
|
+
const unsupported = [];
|
|
94
|
+
const scan = (bag, prefix, accepted, tolerated) => {
|
|
95
|
+
for (const k of Object.keys(bag)) {
|
|
96
|
+
if (accepted.has(k))
|
|
97
|
+
continue;
|
|
98
|
+
(tolerated.has(k) ? unsupported : unknown).push(echoSettingsKey(prefix, k));
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
scan(raw, "settings.", ACCEPTED_TOP, UNSUPPORTED_TOP);
|
|
102
|
+
const perms = raw.permissions;
|
|
103
|
+
if (perms !== null && typeof perms === "object" && !Array.isArray(perms)) {
|
|
104
|
+
scan(perms, "settings.permissions.", ACCEPTED_PERM, UNSUPPORTED_PERM);
|
|
105
|
+
}
|
|
106
|
+
if (unknown.length === 0 && unsupported.length === 0)
|
|
107
|
+
return null;
|
|
108
|
+
unknown.sort();
|
|
109
|
+
unsupported.sort();
|
|
110
|
+
const clause = (names, label) => {
|
|
111
|
+
const shown = names.slice(0, MAX_SETTINGS_KEY_ECHO);
|
|
112
|
+
const more = names.length - shown.length;
|
|
113
|
+
return `${label}: ${shown.join(", ")}${more > 0 ? ` (+${more} more)` : ""}`;
|
|
114
|
+
};
|
|
115
|
+
const parts = [
|
|
116
|
+
...(unknown.length > 0 ? [clause(unknown, "unknown (not a settings key here — check the spelling)")] : []),
|
|
117
|
+
...(unsupported.length > 0 ? [clause(unsupported, "unsupported (a CC settings key this service does not wire)")] : []),
|
|
118
|
+
];
|
|
119
|
+
const dirHint = unsupported.some((n) => n.endsWith(".additionalDirectories"))
|
|
120
|
+
? " Extra directories ride the top-level body.additionalDirectories instead."
|
|
121
|
+
: "";
|
|
122
|
+
return {
|
|
123
|
+
message: `settings: unrecognized key(s) — refused rather than dropped, because a dropped key reads as "in effect" to the caller. ` +
|
|
124
|
+
`${parts.join("; ")}.${dirHint} ` +
|
|
125
|
+
`Accepted: settings.{${[...TASK_SETTINGS_KEYS].join(",")}} and settings.permissions.{${[...TASK_SETTINGS_PERMISSION_KEYS].join(",")}}`,
|
|
126
|
+
unknownKeys: unknown.slice(0, MAX_SETTINGS_KEY_ECHO),
|
|
127
|
+
unsupportedKeys: unsupported.slice(0, MAX_SETTINGS_KEY_ECHO),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
65
130
|
export function parseTaskSettings(raw) {
|
|
66
131
|
if (raw === null || raw === undefined || typeof raw !== "object")
|
|
67
132
|
return { deferred: [] };
|
|
@@ -90,8 +155,16 @@ export function parseTaskSettings(raw) {
|
|
|
90
155
|
const ask = cleanStringArray(p.ask);
|
|
91
156
|
const mode = p.defaultMode;
|
|
92
157
|
const defaultMode = mode === "default" || mode === "acceptEdits" || mode === "plan" || mode === "bypassPermissions" || mode === "auto" ? mode : undefined;
|
|
93
|
-
|
|
94
|
-
|
|
158
|
+
const rawKill = p.disableAutoMode;
|
|
159
|
+
const disableAutoMode = rawKill === "disable" || rawKill === true || rawKill === false ? rawKill : undefined;
|
|
160
|
+
if (allow || deny || ask || defaultMode || disableAutoMode !== undefined) {
|
|
161
|
+
out.permissions = {
|
|
162
|
+
...(allow ? { allow } : {}),
|
|
163
|
+
...(deny ? { deny } : {}),
|
|
164
|
+
...(ask ? { ask } : {}),
|
|
165
|
+
...(defaultMode ? { defaultMode } : {}),
|
|
166
|
+
...(disableAutoMode !== undefined ? { disableAutoMode } : {}),
|
|
167
|
+
};
|
|
95
168
|
}
|
|
96
169
|
}
|
|
97
170
|
if (typeof s.model === "string" && s.model.length > 0)
|
package/dist/task-workflow.d.ts
CHANGED
|
@@ -15,13 +15,43 @@
|
|
|
15
15
|
* source must NOT leak the LLM-authored workflow engine). WITH a resolver, core enforces `allowWorkflows`
|
|
16
16
|
* per-principal (deny → fail-closed). Single-user (owns its own worker) honors directly. Three-gate: engine-can ∧
|
|
17
17
|
* center-may(allowWorkflows) ∧ shell-show.
|
|
18
|
+
*
|
|
19
|
+
* 🔴 [ref](2026-09-03):fail-closed 的**方向**从来是对的,错的是它**没人说**。多租户 + 无 entitlement 源
|
|
20
|
+
* 的部署上,这条腿把 `body.selfOrchestration:true`(以及 `settings.ultracode` 展开出来的同一个意图)静默
|
|
21
|
+
* 折成 `undefined`,而 `/v1/capabilities` 照报 `workflows:true` ⇒ 「壳显示支持、引擎按普通 run 跑」。修法
|
|
22
|
+
* 是把部署级半场抽成下面的 {@link selfOrchestrationDenial}(能力位与准入**同一只**谓词),提交面在拒因
|
|
23
|
+
* 在场时**响亮**拒(`routes` 的 501 `capability.self_orchestration_required`),不再静默折。
|
|
18
24
|
*/
|
|
19
25
|
export declare function selfOrchestrationFromBody(body: {
|
|
20
26
|
selfOrchestration?: unknown;
|
|
21
|
-
}, config:
|
|
27
|
+
}, config: SelfOrchestrationGateConfig, hasEntitlementResolver: boolean): true | undefined;
|
|
28
|
+
/** 本模块读的部署面(`selfOrchestrationEnabled` 恒在真 config 上;遗留双重断言 stub 可能真缺,故一律 `!== true` 读)。 */
|
|
29
|
+
export interface SelfOrchestrationGateConfig {
|
|
22
30
|
selfOrchestrationEnabled: boolean;
|
|
23
31
|
requirePrincipal?: boolean;
|
|
24
|
-
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* [ref](DEBTS,P1)—— 自编排准入的**部署级拒因**闭集。`null` = 本部署这一层不拒(是否真跑还看 engine-can
|
|
35
|
+
* 与 core 的 per-principal `allowWorkflows`)。
|
|
36
|
+
*
|
|
37
|
+
* 唯一成员 `"entitlement_resolver_absent"`:多租户(`requirePrincipal === true`)+ 运维**显式开了**
|
|
38
|
+
* `SELF_ORCHESTRATION_ENABLED` + 没有任何 per-principal entitlement 源(`hasEntitlementResolver` —— 判据由
|
|
39
|
+
* 调用方从 `centerEntitlementSourceWired(config)` / 装配好的 caps 客户端取)。这一形下上面的准入腿对
|
|
40
|
+
* **每一个** principal 都 fail-closed(理由见函数上方注:core 的 `allowWorkflows` 是 tighten-only,
|
|
41
|
+
* resolver 缺席 = 无限制,所以本层必须自己关死),于是这台机器上的自编排是**结构性死的**。
|
|
42
|
+
*
|
|
43
|
+
* 🔴 为什么这不是布尔而是**闭集拒因**:能力面要能对壳说清「为什么暗」——「这份二进制/这台机器没有工作流
|
|
44
|
+
* 引擎」与「引擎在、但本部署配不出 per-principal 授权」是两件事,壳对它们要做的事不同(前者别给这一格;
|
|
45
|
+
* 后者该提示运维接 center)。合成一个布尔就把这两件事打成同一个 false —— 与 `permissionModeAuto` 那一位
|
|
46
|
+
* 「三项不合成一个布尔」同判据。
|
|
47
|
+
*
|
|
48
|
+
* 🔴 为什么 `selfOrchestrationEnabled` 关着时返回 `null` 而不是另一个拒因:那是**运维刻意的姿态**
|
|
49
|
+
* (多租户缺省就是关),能力面的 engine-can 半场自己就已经诚实为假;而本拒因说的是**半配置**
|
|
50
|
+
* (阀门开着而配不出来),两者的处置不同——后者才需要点名。这也正是本件的补偿旋钮:把
|
|
51
|
+
* `SELF_ORCHESTRATION_ENABLED` 关掉(对这台机器而言是唯一诚实的姿态)即刻回到姿态形。
|
|
52
|
+
*/
|
|
53
|
+
export type SelfOrchestrationDenial = "entitlement_resolver_absent";
|
|
54
|
+
export declare function selfOrchestrationDenial(config: SelfOrchestrationGateConfig, hasEntitlementResolver: boolean): SelfOrchestrationDenial | null;
|
|
25
55
|
/**
|
|
26
56
|
* §4 (Fork) — map the per-task `body.enableFork` intent onto `TaskSpec.enableFork`. core 1.257 ([ref] BREAKING)
|
|
27
57
|
* RETIRED the standalone `Fork` tool; a fork is now `Agent(subagent_type:"fork")`, and `enableFork` is one of the two
|
package/dist/task-workflow.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
export function selfOrchestrationFromBody(body, config, hasEntitlementResolver) {
|
|
2
|
-
if (body.selfOrchestration !== true ||
|
|
2
|
+
if (body.selfOrchestration !== true || config.selfOrchestrationEnabled !== true)
|
|
3
3
|
return undefined;
|
|
4
|
+
return selfOrchestrationDenial(config, hasEntitlementResolver) === null ? true : undefined;
|
|
5
|
+
}
|
|
6
|
+
export function selfOrchestrationDenial(config, hasEntitlementResolver) {
|
|
7
|
+
if (config.selfOrchestrationEnabled !== true)
|
|
8
|
+
return null;
|
|
4
9
|
if (config.requirePrincipal !== true)
|
|
5
|
-
return
|
|
6
|
-
return hasEntitlementResolver ?
|
|
10
|
+
return null;
|
|
11
|
+
return hasEntitlementResolver ? null : "entitlement_resolver_absent";
|
|
7
12
|
}
|
|
8
13
|
export function enableForkFromBody(body, config, hasEntitlementResolver) {
|
|
9
14
|
if (!config.forkEnabled)
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -541,6 +541,10 @@ export declare function resolveStreamApprovalGate(input: StreamApprovalGateInput
|
|
|
541
541
|
* `prepare-task.js` 的 durableMandate/contentMandate(:1415/:1417)与 durableQuestionFace(:2136)等),
|
|
542
542
|
* 故不在本函数内。
|
|
543
543
|
*
|
|
544
|
+
* 🔴 **[ref](半接线旋钮族)**:`windowZero` 的第一源(`STREAM_ASK_WINDOW_MS=0`)自此**不再挂在
|
|
545
|
+
* `streamApprovalOn` 后面** —— 那是部署级旋钮,它的语义不该因为这台机器有没有 durable ask 账而改变
|
|
546
|
+
* (`ApprovalLegAssembly.windowZero` 顶注写了那条病)。第三源(贴 deadline)照旧只在协议上场时参与。
|
|
547
|
+
*
|
|
544
548
|
* **`windowZero` 的两件事必须同时做**(调用方契约):不包 ALS **且**注入 immediate-unavailable 闭包。
|
|
545
549
|
* 只做一件另一条路仍会呈卡。⚠️ [ref] 起**三腿都有** per-task `spec.onAsk` 装配点(sync=routes/tasks.ts,
|
|
546
550
|
* bg=runs.ts,resume=server.ts driveResumeLeg),windowZero 的第二件在三腿都是显式闭包 —— 旧文「bg/
|
|
@@ -554,7 +558,14 @@ export interface ApprovalLegAssembly {
|
|
|
554
558
|
* 同 (owner, session) 活流呈活卡(sync 腿 [ref] 起本就如此;bg/resume 的这条行为差 = [ref] CHANGELOG
|
|
555
559
|
* 披露的行为面变更)。旧文「本腿逐字保持协议之前的行为」只对协议件成立,别再把它读成「协议关 = 零呈卡」。 */
|
|
556
560
|
active: boolean;
|
|
557
|
-
/** §8.3 窗=0 恒 park
|
|
561
|
+
/** §8.3 窗=0 恒 park。
|
|
562
|
+
* 🔴 **[ref] 起它与 `active` 解耦**(旧文「`active` 为假时恒 false —— 协议都没上场,谈不上关窗」已
|
|
563
|
+
* 作废):`STREAM_ASK_WINDOW_MS` 是**部署级**旋钮,`0` 的语义(运维显式关窗 ⇒ 恒走
|
|
564
|
+
* {@link ToolApprovalCoordinator.unattendedAskOutcome},连卡都不发)在两条车道上必须是同一个意思。
|
|
565
|
+
* 旧形下 local 车道(`volatile_ask_ledger`)/ 无 park 设施 / 显式关协议的部署配了 `0`,拿到的是一张
|
|
566
|
+
* 等满 5min 的活卡 —— 与它自己的域注正好相反,且没有任何一行说出来。
|
|
567
|
+
* ⚠️ 「窗**缺席**」(`windowMs: undefined` = 这份装配根本没有 streamApproval 段,只出现在 stub-harness)
|
|
568
|
+
* **不是**关窗:那时逐字保持活卡腿。 */
|
|
558
569
|
windowZero: boolean;
|
|
559
570
|
/** §7.3 本 leg 的 walltime deadline(`performance.now()` 单调基)。只在 `active ∧ ¬windowZero ∧
|
|
560
571
|
* 有 walltime 墙` 时在场 —— 关着时供值会把既有 5min 活卡窗按三元公式缩短(§7.2 真行为变更)。 */
|
|
@@ -583,8 +594,13 @@ export declare function createApprovalCardEmitter(sinks: {
|
|
|
583
594
|
export declare function resolveApprovalLeg(input: {
|
|
584
595
|
/** = `resolveStreamApprovalGate(...).active`(协议在本 worker 上场没有)。 */
|
|
585
596
|
streamApprovalOn: boolean;
|
|
586
|
-
/** `config.streamApproval.windowMs`(`STREAM_ASK_WINDOW_MS`)。
|
|
587
|
-
|
|
597
|
+
/** `config.streamApproval.windowMs`(`STREAM_ASK_WINDOW_MS`)。
|
|
598
|
+
* 🔴 [ref]:`undefined` = **这份装配没有 `streamApproval` 段**(只出现在只填被测键的 stub-harness ——
|
|
599
|
+
* 真实 boot 无条件铸该段,见 `config.ts`)。此前三个调用点各写 `?? 0` 把这一形折成「0」,在旧语义下
|
|
600
|
+
* 是死代码(`windowZero` 那时挂在 `streamApprovalOn` 后面,协议关就恒 false);[ref] 让 `0` 在协议关的
|
|
601
|
+
* 腿上也真生效之后,那个折算会把一台**什么都没配**的 harness 判成「运维显式关窗」。故本字段收成
|
|
602
|
+
* `number | undefined`,由每个调用点如实交出「有没有这个值」,不许再就地编一个。 */
|
|
603
|
+
windowMs: number | undefined;
|
|
588
604
|
/** `config.streamAskWindowMarginMs`(`STREAM_ASK_WINDOW_MARGIN_MS`)。 */
|
|
589
605
|
windowMarginMs: number;
|
|
590
606
|
/** `spec.limits.maxWalltimeMs`。缺席 = 没有 walltime 墙 ⇒ 第三源不参与(**缺席 ≠ 0**)。 */
|
|
@@ -802,6 +818,8 @@ export declare class ToolApprovalCoordinator {
|
|
|
802
818
|
/** 两份候选是否**逐条逐键**相等(顺序即展示序 ⇒ 顺序敏感)。两侧都缺席 = 相等;一侧缺席 = 不等。
|
|
803
819
|
* 用在幂等重入的「行 = 真源」对账上(见 `ensureAsk` 那段撤回臂)。 */
|
|
804
820
|
private static ruleOffersEqual;
|
|
821
|
+
private static batchMemberEqual;
|
|
822
|
+
private static uncoveredDetailEqual;
|
|
805
823
|
/**
|
|
806
824
|
* [ref] / 5.58 复审 F-1 —— OFFER 的**文本座尺**(红先修)。
|
|
807
825
|
*
|
|
@@ -824,6 +842,11 @@ export declare class ToolApprovalCoordinator {
|
|
|
824
842
|
* 位置在**素材铸点**(与基数帽同一处):两族帧 + `card_json` + 回决口的等式左边从此拿的是**同一份**
|
|
825
843
|
* 已截素材,而不是两处各截一遍。
|
|
826
844
|
*/
|
|
845
|
+
/** 兑付侧座(single 的 rule/command、command 成员的 rule/command、directoryRead 成员的 rule/directory)任一超尺?
|
|
846
|
+
* `segment` 与 `uncoveredDetail[].segment` 是纯渲染座,不在此列(截长是诚实省略)。 */
|
|
847
|
+
private static ruleOfferTextsOverSize;
|
|
848
|
+
/** codex r1 [high]([ref]):offer 的自称座与 `parseAllowRuleText(rule)` 派生的三元组逐字一致?(理由见调用点) */
|
|
849
|
+
private static ruleOfferDerivedFactsAgree;
|
|
827
850
|
private static boundRuleOfferTexts;
|
|
828
851
|
private buildRuleLaneMaterial;
|
|
829
852
|
/** [ref]([ref]/[ref] 双属主裁定):把某 wire run 的**流内未决 ask** 立即转 durable park——断连支专用。
|
package/dist/tool-approval.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { contentKindOf } from "./approval-content-kind.js";
|
|
2
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3
|
-
import { uuidv7, MAX_RULE_TEXT_CHARS } from "@sema-agent/core";
|
|
3
|
+
import { uuidv7, MAX_RULE_TEXT_CHARS, parseAllowRuleText } from "@sema-agent/core";
|
|
4
4
|
import { precheckCardRuleText } from "./rules-consent.js";
|
|
5
5
|
import { redactDeep, redactSecrets } from "./trace/redact.js";
|
|
6
6
|
import { hasBidiControls } from "./text-bidi.js";
|
|
@@ -8,7 +8,7 @@ import { approvalPayloadFingerprint } from "./plugins/approval-payload-fingerpri
|
|
|
8
8
|
import { createLogger } from "./observability/logger.js";
|
|
9
9
|
import { recordFailOpen } from "./observability/fail-open.js";
|
|
10
10
|
import { deriveAskId, deriveBatchId, MAX_DECISION_NOTE_CHARS } from "./approval-ask-machine.js";
|
|
11
|
-
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, } from "./approval-card.js";
|
|
11
|
+
import { ApprovalCardEnvelopeSchema, MAX_RULE_OFFERS, buildApprovalCard, buildApprovalCardEnvelope, buildApprovalRequestFrame, buildRevokeFrame, readProbeCause, readRuleEvidence, readRuleOffersAbsence, RuleOfferRawSchema, backfillLegacyBatchMemberKind, screenUncoveredDetailSeat, } from "./approval-card.js";
|
|
12
12
|
import { projectAskTerminalClaimTruth } from "./plugins/approval-ask-store-sql.js";
|
|
13
13
|
import { governanceAskMarksFor, runWithGovernanceAskScope } from "./governance-ask-marks.js";
|
|
14
14
|
const defaultLogger = createLogger();
|
|
@@ -195,7 +195,7 @@ export function createApprovalCardEmitter(sinks) {
|
|
|
195
195
|
}
|
|
196
196
|
export function resolveApprovalLeg(input) {
|
|
197
197
|
if (!input.streamApprovalOn)
|
|
198
|
-
return { active: false, windowZero:
|
|
198
|
+
return { active: false, windowZero: input.windowMs === 0 };
|
|
199
199
|
const tightDeadline = input.legWalltimeMs !== undefined && input.legWalltimeMs - input.windowMarginMs <= 0;
|
|
200
200
|
if (input.windowMs === 0 || tightDeadline)
|
|
201
201
|
return { active: true, windowZero: true };
|
|
@@ -344,32 +344,115 @@ export class ToolApprovalCoordinator {
|
|
|
344
344
|
if (x.kind === "batch" && y.kind === "batch") {
|
|
345
345
|
return (x.uncoveredSegments === y.uncoveredSegments &&
|
|
346
346
|
x.rules.length === y.rules.length &&
|
|
347
|
-
x.rules.every((r, j) =>
|
|
347
|
+
x.rules.every((r, j) => ToolApprovalCoordinator.batchMemberEqual(r, y.rules[j])) &&
|
|
348
|
+
ToolApprovalCoordinator.uncoveredDetailEqual(x.uncoveredDetail, y.uncoveredDetail));
|
|
348
349
|
}
|
|
349
350
|
return false;
|
|
350
351
|
});
|
|
351
352
|
}
|
|
353
|
+
static batchMemberEqual(r, s) {
|
|
354
|
+
if (r.kind !== s.kind)
|
|
355
|
+
return false;
|
|
356
|
+
if (r.kind === "command" && s.kind === "command")
|
|
357
|
+
return r.rule === s.rule && r.match === s.match && r.command === s.command && r.segment === s.segment;
|
|
358
|
+
if (r.kind === "directoryRead" && s.kind === "directoryRead")
|
|
359
|
+
return r.rule === s.rule && r.directory === s.directory && r.segment === s.segment;
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
static uncoveredDetailEqual(a, b) {
|
|
363
|
+
if (a === undefined || b === undefined)
|
|
364
|
+
return a === b;
|
|
365
|
+
return a.length === b.length && a.every((d, i) => d.segment === b[i].segment && d.reason === b[i].reason);
|
|
366
|
+
}
|
|
367
|
+
static ruleOfferTextsOverSize(offer) {
|
|
368
|
+
const over = (s) => s.length > MAX_RULE_TEXT_CHARS;
|
|
369
|
+
if (offer.kind === "single")
|
|
370
|
+
return over(offer.rule) || over(offer.command);
|
|
371
|
+
return offer.rules.some((r) => {
|
|
372
|
+
switch (r.kind) {
|
|
373
|
+
case "command":
|
|
374
|
+
return over(r.rule) || over(r.command);
|
|
375
|
+
case "directoryRead":
|
|
376
|
+
return over(r.rule) || over(r.directory);
|
|
377
|
+
default:
|
|
378
|
+
throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
static ruleOfferDerivedFactsAgree(offer) {
|
|
383
|
+
const derive = (rule) => {
|
|
384
|
+
const p = parseAllowRuleText(rule);
|
|
385
|
+
return "rule" in p ? p.rule : undefined;
|
|
386
|
+
};
|
|
387
|
+
if (offer.kind === "single") {
|
|
388
|
+
const d = derive(offer.rule);
|
|
389
|
+
return d !== undefined && d.rule === offer.rule && d.tool === "Bash" && d.match === offer.match && d.command === offer.command;
|
|
390
|
+
}
|
|
391
|
+
return offer.rules.every((m) => {
|
|
392
|
+
const d = derive(m.rule);
|
|
393
|
+
if (d === undefined || d.rule !== m.rule)
|
|
394
|
+
return false;
|
|
395
|
+
switch (m.kind) {
|
|
396
|
+
case "command":
|
|
397
|
+
return d.tool === "Bash" && d.match === m.match && d.command === m.command;
|
|
398
|
+
case "directoryRead":
|
|
399
|
+
return d.tool === "Read" && d.match === "subpath" && d.command === m.directory;
|
|
400
|
+
default:
|
|
401
|
+
throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(m)}`);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
}
|
|
352
405
|
static boundRuleOfferTexts(offers) {
|
|
353
406
|
const over = (s) => s.length > MAX_RULE_TEXT_CHARS;
|
|
407
|
+
const clipSegment = (s) => (over(s) ? s.slice(0, MAX_RULE_TEXT_CHARS) : s);
|
|
354
408
|
const out = [];
|
|
355
|
-
for (const
|
|
409
|
+
for (const raw of offers) {
|
|
410
|
+
const parsed = RuleOfferRawSchema.safeParse(screenUncoveredDetailSeat(backfillLegacyBatchMemberKind(raw)));
|
|
411
|
+
if (!parsed.success) {
|
|
412
|
+
defaultLogger.warn("rule_offer_dropped_unreadable_shape", {
|
|
413
|
+
kind: typeof raw === "object" && raw !== null ? String(raw.kind) : typeof raw,
|
|
414
|
+
note: "this offer's shape is outside the closed vocabulary this build reads (member kind / match / reason) — dropped whole; sibling offers still render (core/dist skew?)",
|
|
415
|
+
});
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const offer = parsed.data;
|
|
419
|
+
if (ToolApprovalCoordinator.ruleOfferTextsOverSize(offer))
|
|
420
|
+
return undefined;
|
|
421
|
+
if (!ToolApprovalCoordinator.ruleOfferDerivedFactsAgree(offer)) {
|
|
422
|
+
defaultLogger.warn("rule_offer_dropped_derived_facts_disagree", {
|
|
423
|
+
kind: offer.kind,
|
|
424
|
+
note: "an offer's kind/match/command/directory seats do not agree with what the shared validator derives from its rule text — dropped whole so the card never shows one grant while redeeming another (core/dist skew or forged material?)",
|
|
425
|
+
});
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
356
428
|
switch (offer.kind) {
|
|
357
429
|
case "single":
|
|
358
|
-
|
|
359
|
-
return undefined;
|
|
360
|
-
out.push(offer);
|
|
430
|
+
out.push({ kind: "single", rule: offer.rule, match: offer.match, command: offer.command });
|
|
361
431
|
break;
|
|
362
|
-
case "batch":
|
|
363
|
-
|
|
364
|
-
|
|
432
|
+
case "batch": {
|
|
433
|
+
const rules = [];
|
|
434
|
+
for (const r of offer.rules) {
|
|
435
|
+
switch (r.kind) {
|
|
436
|
+
case "command":
|
|
437
|
+
rules.push({ kind: "command", rule: r.rule, match: r.match, command: r.command, segment: clipSegment(r.segment) });
|
|
438
|
+
break;
|
|
439
|
+
case "directoryRead":
|
|
440
|
+
rules.push({ kind: "directoryRead", rule: r.rule, directory: r.directory, segment: clipSegment(r.segment) });
|
|
441
|
+
break;
|
|
442
|
+
default:
|
|
443
|
+
throw new Error(`unknown rule-offer batch member kind: ${JSON.stringify(r)}`);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
365
446
|
out.push({
|
|
366
447
|
kind: "batch",
|
|
367
|
-
rules
|
|
448
|
+
rules,
|
|
368
449
|
uncoveredSegments: offer.uncoveredSegments,
|
|
450
|
+
...(offer.uncoveredDetail !== undefined ? { uncoveredDetail: offer.uncoveredDetail.map((d) => ({ segment: clipSegment(d.segment), reason: d.reason })) } : {}),
|
|
369
451
|
});
|
|
370
452
|
break;
|
|
453
|
+
}
|
|
371
454
|
default:
|
|
372
|
-
|
|
455
|
+
throw new Error(`unknown rule-offer kind: ${JSON.stringify(offer)}`);
|
|
373
456
|
}
|
|
374
457
|
}
|
|
375
458
|
return out;
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
import type { TaskNotificationPayload, BackgroundChildEvent, RosterEntry, AskRequest, CheckpointSummary, TaskEvent, TraceEvent, MailboxMessage, MailboxStore, TaskSpec, TaskResult } from "@sema-agent/core";
|
|
21
21
|
/** 编译期断言:T 必须收敛到 never(有残余键 = tsc 红)。 */
|
|
22
22
|
type AssertAllKeysHandled<T extends never> = T;
|
|
23
|
-
type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure";
|
|
23
|
+
type NotificationProjected = "task_id" | "task_type" | "toolUseId" | "status" | "summary" | "result" | "output_file" | "usage" | "sessionId" | "seq" | "lines" | "stoppedBy" | "source" | "exitCode" | "partial" | "diagnostics" | "recentSteps" | "editedFiles" | "resumable" | "completionId" | "error" | "errorCode" | "apiFailure" | "agentMessage" | "_sema_provenance";
|
|
24
24
|
type NotificationExcluded = "peer";
|
|
25
25
|
type _GuardNotification = AssertAllKeysHandled<Exclude<keyof TaskNotificationPayload, NotificationProjected | NotificationExcluded>>;
|
|
26
26
|
type BgNotifProjected = "taskId" | "sessionId" | "seq" | "status" | "summary" | "stoppedBy" | "resumable" | "recentSteps" | "editedFiles" | "usage" | "transcriptId" | "rootSessionId" | "parentTaskId" | "parentToolCallId" | "completionId";
|
|
@@ -29,7 +29,7 @@ type _GuardBgNotif = AssertAllKeysHandled<Exclude<keyof BackgroundChildEvent, Bg
|
|
|
29
29
|
type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
|
|
30
30
|
type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
|
|
31
31
|
type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval";
|
|
32
|
-
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld";
|
|
32
|
+
type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
|
|
33
33
|
type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
|
|
34
34
|
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" | "text_end";
|
|
35
35
|
type _GuardTaskEvent = AssertAllKeysHandled<Exclude<TaskEvent["type"], TaskEventHandled>>;
|
|
@@ -58,8 +58,14 @@ export interface LedgerSink {
|
|
|
58
58
|
onForwardEvent(e: {
|
|
59
59
|
type?: string;
|
|
60
60
|
}): void;
|
|
61
|
-
/** Park event (durable F4 suspend / [ref] D-B review pause): ONLY the
|
|
62
|
-
*
|
|
61
|
+
/** Park event (durable F4 suspend / [ref] D-B review pause): ONLY the `checkpointGate` is persisted —
|
|
62
|
+
* never the capability `checkpointToken` (search [18] Q1b).
|
|
63
|
+
* ⚠️ **"non-secret" was the old wording and it is not true** ([ref] R3, 2026-09-03 — wording only, behaviour
|
|
64
|
+
* unchanged; [ref] decides the arm): the gate carries NO capability token, but its `riskDescriptor.summary` /
|
|
65
|
+
* `shadowedRule` are model-/human-controlled display text that [ref] measured carrying a PEM private-key
|
|
66
|
+
* body verbatim. This durable row replays on every `GET /v1/runs/:id/events` reconnect and is NOT backfilled
|
|
67
|
+
* by core [ref]'s mint-point fix — it is the third face of the `checkpointGate` sister item recorded in
|
|
68
|
+
* `http/routes/approvals-assistant.ts` (`redactPendingDisclosures` header). */
|
|
63
69
|
appendParked(kind: "suspended" | "needs_review", result: unknown): Promise<void>;
|
|
64
70
|
/** Terminal `done` event. `result` must already be checkpoint-token-stripped (and, on the bg leg,
|
|
65
71
|
* model-usage-attached) by the caller. */
|
|
@@ -101,7 +107,10 @@ export declare function createLedgerSink(opts: {
|
|
|
101
107
|
* ——**text_delta/reasoning_delta 只进内存缓冲、到 flush 边界才 append**(codex S1-F1,2026-08-13:
|
|
102
108
|
* 只打 append 会让长时间流式输出被误读成「无进展」,方向与本键的设计目的正好相反);②`append`
|
|
103
109
|
* 入口——兜住不经 onEvent 的直写路径(appendParked/appendDone/onForwardEvent)。分诊提示性回调:
|
|
104
|
-
* 抛错不得断链(打点失败丢的只是判别材料,账本不能陪葬)。
|
|
105
|
-
|
|
110
|
+
* 抛错不得断链(打点失败丢的只是判别材料,账本不能陪葬)。
|
|
111
|
+
* 🔴 [ref] 件3:回调带**事件类型**(`kind`)——`cancelContext.lastEventKind` 的数据源。两个打点位
|
|
112
|
+
* 各传自己看得见的那个词(位①=流事件 `ev.type`,位②=账本行 `type`);拿不到类型的打点方
|
|
113
|
+
* (core 的 onActivity 第五席、resume 腿手动打点)省参,`kind` 诚实缺席(见 turn-activity.ts)。 */
|
|
114
|
+
onActivity?: (kind?: string, statusData?: Record<string, unknown>) => void;
|
|
106
115
|
}): LedgerSink;
|
|
107
116
|
//# sourceMappingURL=ledger-sink.d.ts.map
|
|
@@ -17,15 +17,15 @@ export function createLedgerSink(opts) {
|
|
|
17
17
|
let text = "";
|
|
18
18
|
let reasoning = "";
|
|
19
19
|
let textEventId;
|
|
20
|
-
const beat = () => {
|
|
20
|
+
const beat = (kind, statusData) => {
|
|
21
21
|
try {
|
|
22
|
-
opts.onActivity?.();
|
|
22
|
+
opts.onActivity?.(kind, statusData);
|
|
23
23
|
}
|
|
24
24
|
catch {
|
|
25
25
|
}
|
|
26
26
|
};
|
|
27
27
|
const append = (type, data) => {
|
|
28
|
-
beat();
|
|
28
|
+
beat(type);
|
|
29
29
|
const step = chain.then(() => opts.appendEvent(++seq, type, data));
|
|
30
30
|
chain = step.then(() => undefined, () => undefined);
|
|
31
31
|
return step;
|
|
@@ -41,7 +41,7 @@ export function createLedgerSink(opts) {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
const onEvent = async (ev) => {
|
|
44
|
-
beat();
|
|
44
|
+
beat(ev.type);
|
|
45
45
|
switch (ev.type) {
|
|
46
46
|
case "text_delta": {
|
|
47
47
|
const td = ev;
|
|
@@ -63,7 +63,11 @@ export function createLedgerSink(opts) {
|
|
|
63
63
|
break;
|
|
64
64
|
case "status":
|
|
65
65
|
await flush();
|
|
66
|
-
|
|
66
|
+
{
|
|
67
|
+
const statusData = brainStatusEventData(ev);
|
|
68
|
+
beat("status", statusData);
|
|
69
|
+
await append("status", statusData);
|
|
70
|
+
}
|
|
67
71
|
break;
|
|
68
72
|
case "context_usage":
|
|
69
73
|
await flush();
|
|
@@ -134,7 +138,11 @@ export function createLedgerSink(opts) {
|
|
|
134
138
|
void append("tool_end", toolEndEventData(e)).catch(() => undefined);
|
|
135
139
|
}
|
|
136
140
|
};
|
|
137
|
-
const appendStatus = (ev) =>
|
|
141
|
+
const appendStatus = (ev) => {
|
|
142
|
+
const statusData = brainStatusEventData(ev);
|
|
143
|
+
beat("status", statusData);
|
|
144
|
+
return append("status", statusData);
|
|
145
|
+
};
|
|
138
146
|
const appendParked = (kind, result) => append(kind, {
|
|
139
147
|
gate: result?.checkpointGate ?? null,
|
|
140
148
|
...(typeof result?.toolCallId === "string" ? { toolCallId: result.toolCallId } : {}),
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type { ApprovalSettledBy, TaskResult, SystemInjectionPriority } from "@se
|
|
|
16
16
|
import { type AskDenyResolution } from "@sema-agent/core";
|
|
17
17
|
import type { RunEvent, RunRecord } from "../plugins/store-contracts.js";
|
|
18
18
|
import type { ModelUsageDelta, ModelUsageTracker, PromptManifestRecord } from "../budget.js";
|
|
19
|
+
import { type SemaProvenanceInput } from "./sema-provenance.js";
|
|
19
20
|
import type { LedgerEventType } from "./ledger-events.js";
|
|
20
21
|
/** 账本写口的统一签名 —— `type` 是**闭集**(`ledger-events.ts`):新型必须先登记,否则调用点编译红
|
|
21
22
|
* (codex 交叉复审 F2:别名门 `ap("x")` 与动态首参让纯词法门结构性抓不全)。 */
|
|
@@ -214,6 +215,18 @@ export declare function taskNotificationEventData(ev: {
|
|
|
214
215
|
status?: number;
|
|
215
216
|
requestId?: string;
|
|
216
217
|
};
|
|
218
|
+
/** [ref](a) [ref] 片6 d1(core 7.2.0):子代→主代 uplink 帧的**渲染判别**成员。在场 = 这一帧是
|
|
219
|
+
* `SendMessage("main")` 的 agent-message(cli 渲 agent-message 卡;父模型面读的是顶层 `<agent-message from=…>`
|
|
220
|
+
* 帧而非 task-notification 外壳);缺席 = 老形卡。`body` 是子代文本 = UNTRUSTED(与 `result` 同姿势 redact);
|
|
221
|
+
* `from` 是 core 铸的发送方标签(引擎值,verbatim)。铸造点唯一:core `agents/send-message-tool.ts` uplink 腿。 */
|
|
222
|
+
agentMessage?: {
|
|
223
|
+
from: string;
|
|
224
|
+
body: string;
|
|
225
|
+
};
|
|
226
|
+
/** 同批:typed 归因侧记(wire 键;core 契约「a host projects it, the model never sees it」)。成员逐个挑取,
|
|
227
|
+
* `kind` 过闭集(`trace/sema-provenance.ts` 单点,park 半场同一判据);`agentType` 本 mount 恒缺席(工具无
|
|
228
|
+
* agent-type 席),在场即透传(fleet spawn 帧按 taskId 可 join 时上游给)。 */
|
|
229
|
+
_sema_provenance?: SemaProvenanceInput;
|
|
217
230
|
};
|
|
218
231
|
eventId?: string;
|
|
219
232
|
parentToolCallId?: string;
|
package/dist/trace/project.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { redactErrorMessage } from "../observability/run-terminal-log.js";
|
|
1
2
|
import { isApprovalSettledBy, isAskDenyResolution } from "@sema-agent/core";
|
|
2
3
|
import { redactSecrets, redactDeep } from "./redact.js";
|
|
3
4
|
import { injectionTierOrAbsent } from "./injection-tier.js";
|
|
5
|
+
import { semaProvenanceOrAbsent } from "./sema-provenance.js";
|
|
4
6
|
export async function appendModelUsageDelta(append, modelUsage, taskId) {
|
|
5
7
|
const delta = modelUsage?.drain(taskId);
|
|
6
8
|
if (delta) {
|
|
@@ -151,6 +153,13 @@ export function taskNotificationEventData(ev) {
|
|
|
151
153
|
...(n.result !== undefined ? { result: redactSecrets(n.result) } : {}),
|
|
152
154
|
...(n.output_file !== undefined ? { output_file: redactSecrets(n.output_file) } : {}),
|
|
153
155
|
...(n.usage !== undefined ? { usage: n.usage } : {}),
|
|
156
|
+
...(n.agentMessage !== undefined ? { agentMessage: { from: n.agentMessage.from, body: redactSecrets(n.agentMessage.body) } } : {}),
|
|
157
|
+
...(() => {
|
|
158
|
+
if (n._sema_provenance === undefined)
|
|
159
|
+
return {};
|
|
160
|
+
const p = semaProvenanceOrAbsent(n._sema_provenance);
|
|
161
|
+
return p !== undefined ? { _sema_provenance: p } : {};
|
|
162
|
+
})(),
|
|
154
163
|
...(() => {
|
|
155
164
|
const tier = injectionTierOrAbsent(ev.priority);
|
|
156
165
|
return tier !== undefined ? { priority: tier } : {};
|
|
@@ -522,7 +531,7 @@ function mapTraceEventInner(type, seq, data) {
|
|
|
522
531
|
case "needs_review":
|
|
523
532
|
return { event: "done", data: { seq, suspended: true } };
|
|
524
533
|
case "failed":
|
|
525
|
-
return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message:
|
|
534
|
+
return { event: "error", data: { type: "error", errorCode: "WORKER_DOWN", message: redactErrorMessage(String(data.errorMessage ?? "run failed")) ?? "run failed" } };
|
|
526
535
|
case "context_usage":
|
|
527
536
|
case "compaction_outcome":
|
|
528
537
|
case "wiring_manifest":
|
package/dist/trace/redact.d.ts
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
1
|
+
/** 测试可见:扫描器 indexOf 实际扫过的字节数(摊还线性 ⇒ ≤ 常数 × 输入长)。 */
|
|
2
|
+
export declare function ppkScanBytesForTest(): number;
|
|
3
|
+
export declare function resetPpkScanBytesForTest(): void;
|
|
4
|
+
/** Redact unambiguous secret formats from reasoning/CoT before it is persisted to the durable trace log. */
|
|
5
|
+
/** S24 (SILENT-FALLBACK P1): optional redaction observer — rewrites previously had zero fingerprint, so
|
|
6
|
+
* over-redaction (mangling legit content) and under-redaction (a pattern that never fires) were both
|
|
7
|
+
* invisible. main.ts wires this to `redactions_applied_total{pattern}`. Hot path: the observer only runs
|
|
8
|
+
* when a pattern actually matched; the label is derived once per pattern at module load. */
|
|
9
|
+
/** [ref]:第三参 `meta.arm` = 命中的是哪一组臂。加参而不是改参 —— 只读两参的既有观察者原样可用
|
|
10
|
+
* (TS 允许少形参),main.ts 把它铺成 `redactions_applied_total{pattern,arm}` 的第二个维度。
|
|
9
11
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
|
|
12
|
+
* 🔴 **词义如实**(codex r1 [medium] 三,采纳):这一维说的是「命中的是**哪一条臂**」,**不是**
|
|
13
|
+
* 「这段文本真的被上游截过」—— server 收到的是纯字节,core 的 `clip()` 是 `slice(0, 80)` **不留标记**
|
|
14
|
+
* (亲读 `@sema-agent/core/dist/agents/subagent-steps.js:9-11`),截过与天生就短在 wire 上不可区分。
|
|
15
|
+
* 所以一个 `arm="fragment"` 计数可能来自残片,也可能来自一个恰好落在阈值之上的 look-alike;
|
|
16
|
+
* core [ref] 落地后这一支**趋零是证据、不归零不是证明**。别把它当截断事实的度量用。 */
|
|
17
|
+
export type RedactionArm = "full" | "fragment";
|
|
18
|
+
export interface RedactionHitMeta {
|
|
19
|
+
/** `"fragment"` = {@link FRAGMENT_SECRET_PATTERNS} 里的前缀残片臂;`"full"` = 完整臂。闭集两词。 */
|
|
20
|
+
arm: RedactionArm;
|
|
21
|
+
}
|
|
22
|
+
export declare function setRedactionObserver(fn: ((patternLabel: string, count: number, meta: RedactionHitMeta) => void) | undefined): void;
|
|
13
23
|
export declare function redactSecrets(text: string, opts?: {
|
|
14
24
|
observe?: boolean;
|
|
25
|
+
json?: boolean;
|
|
15
26
|
}): string;
|
|
16
27
|
/**
|
|
17
28
|
* 🔴 环 + 深度预算(2026-07-25 补)。原实现是纯递归、**既不记已访问节点也不限深度**,而它的调用位置是
|