@sema-agent/server 7.42.0 → 7.43.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/dist/audit.js +6 -2
- package/dist/brain.js +11 -0
- package/dist/config-center/apply-effective.js +4 -2
- package/dist/config-provider.js +4 -0
- package/dist/config.d.ts +11 -4
- package/dist/config.js +40 -17
- package/dist/fleet/fleet-reconciler.js +18 -6
- package/dist/hooks/cc-agent-hook-prompt.d.ts +34 -0
- package/dist/hooks/cc-agent-hook-prompt.js +38 -0
- package/dist/hooks/cc-stop-prompt.d.ts +5 -2
- package/dist/hooks/cc-stop-prompt.js +5 -2
- package/dist/hooks/hook-runner.d.ts +8 -3
- package/dist/hooks/hook-runner.js +9 -5
- package/dist/http/routes/approvals-assistant.js +9 -1
- package/dist/http/routes/fleet.js +8 -2
- package/dist/http/routes/notify-wake.js +4 -0
- package/dist/main.js +16 -5
- package/dist/memory-sync.js +8 -0
- package/dist/plugins/checkpoint-store-sql.js +4 -1
- package/dist/plugins/memory-engine-pg.js +22 -6
- package/dist/plugins/memory-engine-tidb.js +22 -6
- package/dist/plugins/memory-origin-law.d.ts +31 -7
- package/dist/plugins/memory-origin-law.js +42 -8
- package/dist/plugins/remote-shell.js +6 -2
- package/dist/task-settings.d.ts +9 -4
- package/dist/task-settings.js +9 -4
- package/dist/tool-approval.d.ts +24 -0
- package/dist/tool-approval.js +27 -0
- package/dist/trace/project.d.ts +2 -0
- package/dist/trace/project.js +12 -1
- package/package.json +2 -2
package/dist/audit.js
CHANGED
|
@@ -82,8 +82,12 @@ export function windowMessages(messages, opts) {
|
|
|
82
82
|
}
|
|
83
83
|
/** 引擎注入块的闭集标签([1075]①,web [1069] 同款拆法):user wire 消息里每轮前置的
|
|
84
84
|
* <system-reminder>/<task-notification> 原文段。裁剪时这些段**优先整段丢弃**——blobCap 预算留给
|
|
85
|
-
* 用户真正说的话(clay 报障现场:注入块排在人话前把 4096 字节吃光,`[truncated …]` 后正文整段消失)。
|
|
86
|
-
|
|
85
|
+
* 用户真正说的话(clay 报障现场:注入块排在人话前把 4096 字节吃光,`[truncated …]` 后正文整段消失)。
|
|
86
|
+
* ⚠️ 开标签必须**属性容忍**([4974] 同形第三例):core ≥5.46(design/319)铸的 <system-reminder> 恒带
|
|
87
|
+
* `mark="<22位base64url>"` 属性,裸标签精确匹配对新 core 恒不命中=本剥离整条失效、[1075]① 报障病静默回归
|
|
88
|
+
* (常绿测试固件手写裸标签掩盖了它——红先格改用 core 真 mintSystemReminder 铸形)。剥离是裁剪优先级判定
|
|
89
|
+
* 不是信任判定,宽容属性无伪造面(带假属性的假 reminder 被剥=同样正确的让位)。 */
|
|
90
|
+
const INJECTED_BLOCK_RE = /<(system-reminder|task-notification)(?:\s[^>]*)?>[\s\S]*?<\/\1>\s*/g;
|
|
87
91
|
/** blobCap 下先剥注入块再量预算:剥后若已在预算内=保住全部人话(加 `injectedDropped:true` 注记);
|
|
88
92
|
* 仍超才截人话本身。剥离只在「原文本超预算」时发生——预算内的消息原样(含注入块)零变化,旧行为不动。 */
|
|
89
93
|
function capWithInjectionAwareness(s, capBytes) {
|
package/dist/brain.js
CHANGED
|
@@ -87,6 +87,12 @@ export function createBrain(config, deps = {}) {
|
|
|
87
87
|
? { maxRetries: FAST_FAIL_MAX_RETRIES }
|
|
88
88
|
: {};
|
|
89
89
|
// Local openai-compatible gateway stack: primary + optional same-protocol fallbacks (failover).
|
|
90
|
+
// 🔴 key↔URL 配对不变式([4998],clay 定性:两个不同 URL 不得引用同一个 key):这组 routes 共享
|
|
91
|
+
// `gatewayApiKey` 的**语义前提**=MODEL_GATEWAY_FALLBACK_URLS 是**同一逻辑网关的镜像/备用地址**
|
|
92
|
+
// (同一供应商、同一账户、同一 key 有效域)——在此语义下多 URL 共享一 key 是「拉平后仍一一对应」
|
|
93
|
+
// 的合法形,不是配对拆散。把异供应商 URL 配进 fallback=违约使用(主 key 会被送往异站),契约在此
|
|
94
|
+
// 成文;机器强制(URL 域校验/响亮拒)不在本仓单点做,归 [4995]/[4996] core 路由完整性裁决器同窗
|
|
95
|
+
// ——届时「配对拆」与「key 缺」并入同一个「路由不可用」判定。
|
|
90
96
|
const routes = [config.gatewayBaseUrl, ...config.gatewayFallbackUrls];
|
|
91
97
|
const openaiBrains = routes.map((baseUrl, i) => {
|
|
92
98
|
// 🔴 #303 顺修:网关 key **活取**(getter),不是 boot 快照。core 每次请求在 `buildRequest` 里读
|
|
@@ -172,6 +178,11 @@ export function createBrain(config, deps = {}) {
|
|
|
172
178
|
// env-lane catalogs). Poison in either lookup throws — never falls through to the gateway key.
|
|
173
179
|
const own = resolveModelApiKey(fallbackModel.name, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {}) ??
|
|
174
180
|
resolveModelApiKey(degradeTo, config.modelApiKeyEnv ?? {}, process.env, config.modelApiKeys ?? {});
|
|
181
|
+
// 🔴 BROKEN 实锤登记([4998] server 首扫,clay 定性=key↔URL 配对不变式):`own` 缺席时这里回落
|
|
182
|
+
// **主网关 key**,而本 brain 的 baseUrl 是 `fallbackModel.baseUrl || gatewayBaseUrl`——降级目标指
|
|
183
|
+
// **外路** URL 且无自 key 时=外路 URL+主 key,配对拆散(同仓 hook-llm.ts 2026-07-13 已修对:主
|
|
184
|
+
// key 只随 sameGateway 走;本腿是漏网的姊妹)。修=候 [4995]/[4996] core 路由完整性裁决器同窗换装
|
|
185
|
+
// (外路+缺 key ⇒「路由不可用」,降级链按不可用后退),本仓不抢单点行为修——两处两答比单病更糟。
|
|
175
186
|
return own ?? config.gatewayApiKey;
|
|
176
187
|
};
|
|
177
188
|
const fallback = fallbackModel.provider === "anthropic" && anthropicBrain
|
|
@@ -64,8 +64,10 @@ function toModel(m, envDefaults = {}) {
|
|
|
64
64
|
// Per-model behavior-slot sentences ([998]⑤c consumer half) — verbatim, same posture as extraBody.
|
|
65
65
|
...(Array.isArray(m.promptGuidance) && m.promptGuidance.length > 0 ? { promptGuidance: m.promptGuidance.map(String) } : {}),
|
|
66
66
|
};
|
|
67
|
-
// [818]
|
|
68
|
-
//
|
|
67
|
+
// [818]①/A-065 P2-6 1M dual-window: an explicit roster `autoCompactTokens` (structural, like tier
|
|
68
|
+
// passthroughs) wins; else the model-id table (sonnet-5 family ∧ >=1M ⇒ 967000 — CC ships that value
|
|
69
|
+
// per-model, not per window width; see applyAutoCompactWindow header); else absent. A non-sonnet 1M roster
|
|
70
|
+
// model gets NO implicit value — roster culture is explicit declaration. Same helper as the env lane.
|
|
69
71
|
const explicitAct = m.autoCompactTokens;
|
|
70
72
|
applyAutoCompactWindow(model, typeof explicitAct === "number" ? explicitAct : undefined);
|
|
71
73
|
return model;
|
package/dist/config-provider.js
CHANGED
|
@@ -241,6 +241,10 @@ export function mapToServiceEffective(eff, version) {
|
|
|
241
241
|
// projection. [2373]B-6d:registry-core 0.13.0 已把它收进 ModelEntry schema(types.d.ts:157/212)
|
|
242
242
|
// ——旧注的「strip-mode 会在上游丢值」的 honest gap 已随那次 schema addition 关闭,结构读改类型直读。
|
|
243
243
|
...(typeof m.autoCompactTokens === "number" ? { autoCompactTokens: m.autoCompactTokens } : {}),
|
|
244
|
+
// A-067 C2-M1(F1a,本函数第 6 例同形丢字段):charsPerToken 远程腿 toModel 一直消费
|
|
245
|
+
// (apply-effective charsPerTokenOf 判形),本 CLOSED 投影此前剥掉 ⇒ 本地 lane 的 token
|
|
246
|
+
// 估算静默退回 env 默认/core 4。判形归 toModel 单点(此处只过境,不重判)。
|
|
247
|
+
...(typeof m.charsPerToken === "number" ? { charsPerToken: m.charsPerToken } : {}),
|
|
244
248
|
...(m.cost !== undefined ? { cost: m.cost } : {}),
|
|
245
249
|
...(m.extraBody !== undefined ? { extraBody: m.extraBody } : {}),
|
|
246
250
|
// registry-core 0.10.13 promptGuidance ([998]⑤c): the CLOSED projection must pass it through or
|
package/dist/config.d.ts
CHANGED
|
@@ -149,11 +149,18 @@ export declare const configLkgEnabled: () => boolean;
|
|
|
149
149
|
export declare const hostBackgroundShellEnabled: () => boolean;
|
|
150
150
|
/** The `host` exec lane's spool-file stdio form; false ⇒ the pre-1.226 pipe fallback (read per exec). */
|
|
151
151
|
export declare const hostExecSpoolEnabled: () => boolean;
|
|
152
|
-
/** [818]① 1M dual-window
|
|
153
|
-
*
|
|
154
|
-
*
|
|
152
|
+
/** [818]① 1M dual-window (core 1.289 `Model.autoCompactTokens`): an EXPLICIT positive value wins (env
|
|
153
|
+
* `MODEL_AUTO_COMPACT_TOKENS` / center roster `autoCompactTokens`); else the **model-id table** applies —
|
|
154
|
+
* sonnet-5-family id ∧ contextWindow>=1e6 ⇒ 967000 (the faithful shape of CC's single-entry per-model
|
|
155
|
+
* config; a small-window sonnet is NOT derived — a field above the physical window is undefined core
|
|
156
|
+
* behavior); anything else ⇒ REMOVED (a spread-inherited value from a bigger-window base model would
|
|
157
|
+
* mis-trigger a small window). ⚠️ A-065 P2-6 rescinded the old `contextWindow>=1e6 ⇒ 967000` derivation:
|
|
158
|
+
* it handed a sonnet-5-only constant to ANY 1M model with no corpus/upstream backing (the earlier note
|
|
159
|
+
* here claiming this was "the exact CC alignment core recommends embedders set" mis-read core's per-model
|
|
160
|
+
* advice as a width rule). Returns a discriminant so the env lane can turn the removed implicit value into
|
|
161
|
+
* a loud CONFIG_NOTICES line ("removed-1m"); the center lane's roster culture is explicit declaration.
|
|
155
162
|
* Shared by the env lane (main + cheap models) and the center lane (sema-registry toModel). */
|
|
156
|
-
export declare function applyAutoCompactWindow(m: Model, explicit?: number):
|
|
163
|
+
export declare function applyAutoCompactWindow(m: Model, explicit?: number): "explicit" | "sonnet5" | "removed-1m" | "removed";
|
|
157
164
|
/**
|
|
158
165
|
* 一条工具名**永不匹配任何现役工具**的三种拼法,及其修正指引。审批/权限名单是 server 侧 ToolPolicy 的
|
|
159
166
|
* RAW 逐字比对(canonicalToolName 折叠面已随 core 5.0.0 RB-476 退役),所以写错的名字不会报错、只会静默
|
package/dist/config.js
CHANGED
|
@@ -622,20 +622,37 @@ function buildExtraBody() {
|
|
|
622
622
|
body.presence_penalty = parseNumOrFail("MODEL_PRESENCE_PENALTY", pp);
|
|
623
623
|
return Object.keys(body).length > 0 ? body : undefined;
|
|
624
624
|
}
|
|
625
|
-
/**
|
|
626
|
-
*
|
|
627
|
-
*
|
|
625
|
+
/** A-065 P2-6:967000 的适用面是 **model-id 表**,不是窗宽几何——core `context-edit.d.ts` 原文:CC 的
|
|
626
|
+
* 1M autocompact 特判是 "sonnet-5-only … a per-model config delivery, **not geometry**"。判 sonnet-5 系
|
|
627
|
+
* 容忍两种部署 id 形:日期后缀(claude-sonnet-5-20260101)与网关路由前缀(anthropic/claude-sonnet-5)。 */
|
|
628
|
+
function isSonnet5Family(id) {
|
|
629
|
+
const bare = id.slice(id.lastIndexOf("/") + 1);
|
|
630
|
+
return bare === "claude-sonnet-5" || bare.startsWith("claude-sonnet-5-");
|
|
631
|
+
}
|
|
632
|
+
/** [818]① 1M dual-window (core 1.289 `Model.autoCompactTokens`): an EXPLICIT positive value wins (env
|
|
633
|
+
* `MODEL_AUTO_COMPACT_TOKENS` / center roster `autoCompactTokens`); else the **model-id table** applies —
|
|
634
|
+
* sonnet-5-family id ∧ contextWindow>=1e6 ⇒ 967000 (the faithful shape of CC's single-entry per-model
|
|
635
|
+
* config; a small-window sonnet is NOT derived — a field above the physical window is undefined core
|
|
636
|
+
* behavior); anything else ⇒ REMOVED (a spread-inherited value from a bigger-window base model would
|
|
637
|
+
* mis-trigger a small window). ⚠️ A-065 P2-6 rescinded the old `contextWindow>=1e6 ⇒ 967000` derivation:
|
|
638
|
+
* it handed a sonnet-5-only constant to ANY 1M model with no corpus/upstream backing (the earlier note
|
|
639
|
+
* here claiming this was "the exact CC alignment core recommends embedders set" mis-read core's per-model
|
|
640
|
+
* advice as a width rule). Returns a discriminant so the env lane can turn the removed implicit value into
|
|
641
|
+
* a loud CONFIG_NOTICES line ("removed-1m"); the center lane's roster culture is explicit declaration.
|
|
628
642
|
* Shared by the env lane (main + cheap models) and the center lane (sema-registry toModel). */
|
|
629
643
|
export function applyAutoCompactWindow(m, explicit) {
|
|
630
644
|
const target = m;
|
|
631
645
|
if (explicit !== undefined && Number.isFinite(explicit) && explicit > 0) {
|
|
632
646
|
target.autoCompactTokens = Math.floor(explicit);
|
|
633
|
-
return;
|
|
647
|
+
return "explicit";
|
|
634
648
|
}
|
|
635
|
-
if ((m.contextWindow ?? 0) >= 1_000_000)
|
|
649
|
+
if (isSonnet5Family(m.id) && (m.contextWindow ?? 0) >= 1_000_000) {
|
|
636
650
|
target.autoCompactTokens = 967_000;
|
|
637
|
-
|
|
638
|
-
|
|
651
|
+
return "sonnet5";
|
|
652
|
+
}
|
|
653
|
+
const was1m = (m.contextWindow ?? 0) >= 1_000_000;
|
|
654
|
+
delete target.autoCompactTokens;
|
|
655
|
+
return was1m ? "removed-1m" : "removed";
|
|
639
656
|
}
|
|
640
657
|
/** 域:store(持久化)—— DB 引擎三态、session 后端、SQL coords、快照 BLOB / SendUserFile 对象存储。 */
|
|
641
658
|
function parseStoreDomain(ctx) {
|
|
@@ -939,17 +956,23 @@ function parseModelDomain() {
|
|
|
939
956
|
reasoning: parseBoolWord("MODEL_CHEAP_REASONING", process.env.MODEL_CHEAP_REASONING) ?? model.reasoning, // #245:三态同上
|
|
940
957
|
}
|
|
941
958
|
: undefined;
|
|
942
|
-
// [818]
|
|
943
|
-
//
|
|
944
|
-
//
|
|
945
|
-
//
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
959
|
+
// [818]①/A-065 P2-6:1M dual-window is now the MODEL-ID table (sonnet-5 family ∧ >=1e6 ⇒ 967000, see
|
|
960
|
+
// applyAutoCompactWindow header — core's own words: "sonnet-5-only … per-model config delivery, not
|
|
961
|
+
// geometry"). MODEL_AUTO_COMPACT_TOKENS is the explicit knob for any other model; a 1M window that gets
|
|
962
|
+
// neither is announced loudly (the pre-P2-6 implicit 967000 must not vanish silently).
|
|
963
|
+
const mainAct = optFinitePositiveEnv("MODEL_AUTO_COMPACT_TOKENS");
|
|
964
|
+
if (applyAutoCompactWindow(model, mainAct) === "removed-1m") {
|
|
965
|
+
CONFIG_NOTICES.push({
|
|
966
|
+
event: "auto_compact_window_not_derived_1m",
|
|
967
|
+
fields: {
|
|
968
|
+
note: `model "${model.id}" declares a >=1M context window but no autoCompactTokens — earlier versions implicitly applied 967000 here, which is CC's claude-sonnet-5-only per-model value (A-065 P2-6 rescinded the width-based derivation). Set MODEL_AUTO_COMPACT_TOKENS for a dual-window trigger; without it core uses its plain W-33000 trigger geometry on the full window.`,
|
|
969
|
+
},
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
// cheap 槽走同一 id 表(自己的 id 自己查;sonnet 专属值不给非 sonnet cheap);无独立 env 旋钮——
|
|
973
|
+
// 1M cheap 罕见,需要时用 center roster 显式 autoCompactTokens。spread 脏字段照旧被删。
|
|
951
974
|
if (cheapModel)
|
|
952
|
-
applyAutoCompactWindow(cheapModel);
|
|
975
|
+
applyAutoCompactWindow(cheapModel);
|
|
953
976
|
const models = { default: model, [model.id]: model };
|
|
954
977
|
if (cheapModel)
|
|
955
978
|
models[cheapModel.id] = cheapModel;
|
|
@@ -130,14 +130,21 @@ export function createFleetReconciler(deps) {
|
|
|
130
130
|
/** 一次点读。抛错/超预算 ⇒ 抛 {@link ReadAborted},由 runOnce 统一收成 F 类 fail-open + 熔断。 */
|
|
131
131
|
const readRun = async (id) => {
|
|
132
132
|
tally.reads++;
|
|
133
|
+
// A-067 C5(F7):竞速一结束就 clearTimeout(A-002.10 同款,approval-reconciler withDeadline 正形)
|
|
134
|
+
// ——unref 只保证不吊住进程退出,不回收定时器本身;每周期最多 FLEET_RECONCILE_MAX_ROWS 次点读,
|
|
135
|
+
// 不清就是同数量级带闭包定时器的逐周期累积。finally 对胜败两路都清。
|
|
136
|
+
let t;
|
|
133
137
|
const rec = await Promise.race([
|
|
134
138
|
deps.runs.getRun(id),
|
|
135
139
|
new Promise((_r, reject) => {
|
|
136
|
-
|
|
140
|
+
t = setTimeout(() => reject(new ReadAborted(`fleet reconcile read exceeded ${FLEET_RECONCILE_READ_BUDGET_MS}ms`)), Math.max(0, deadline - now()));
|
|
137
141
|
if (typeof t.unref === "function")
|
|
138
142
|
t.unref();
|
|
139
143
|
}),
|
|
140
|
-
])
|
|
144
|
+
]).finally(() => {
|
|
145
|
+
if (t !== undefined)
|
|
146
|
+
clearTimeout(t);
|
|
147
|
+
});
|
|
141
148
|
return rec;
|
|
142
149
|
};
|
|
143
150
|
const snap = deps.bus.snapshot();
|
|
@@ -303,14 +310,19 @@ export function createFleetReconciler(deps) {
|
|
|
303
310
|
break;
|
|
304
311
|
}
|
|
305
312
|
tally.reads++;
|
|
313
|
+
// A-067 C5(F7):同上 readRun——竞速一结束 finally 清(A-002.10 同款)。
|
|
314
|
+
let wfTimer;
|
|
306
315
|
const run = await Promise.race([
|
|
307
316
|
wfGet(wf.id),
|
|
308
317
|
new Promise((_r, reject) => {
|
|
309
|
-
|
|
310
|
-
if (typeof
|
|
311
|
-
|
|
318
|
+
wfTimer = setTimeout(() => reject(new ReadAborted("fleet reconcile workflow read over budget")), Math.max(0, deadline - now()));
|
|
319
|
+
if (typeof wfTimer.unref === "function")
|
|
320
|
+
wfTimer.unref();
|
|
312
321
|
}),
|
|
313
|
-
])
|
|
322
|
+
]).finally(() => {
|
|
323
|
+
if (wfTimer !== undefined)
|
|
324
|
+
clearTimeout(wfTimer);
|
|
325
|
+
});
|
|
314
326
|
if (!run || !isTerminalWorkflowStatus(run.status))
|
|
315
327
|
continue;
|
|
316
328
|
// 租户复核:`store.get` 是**无 scope 门**的裸读(带门的是 core 的 `getWorkflowRun`)——
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CC `hook_agent` 臂(`type:"agent"` 钩子条目)的评估者系统提示 —— **CC 逐字移植**(A-065 P2-5 修:
|
|
3
|
+
* 此前 agent 臂一个字的系统提示都不发,评估者拿不到「你在判什么/可以用工具去查/少走几步」任何一条,
|
|
4
|
+
* 判词质量静默劣化且黑盒极难察觉;`cc-stop-prompt.ts` 把孪生的 prompt 臂做到字节级 ALIGNED,本臂却整块
|
|
5
|
+
* 漏在考据之外=UNANCHORED)。
|
|
6
|
+
*
|
|
7
|
+
* ── 出处(第一手核过)────────────────────────────────────────────────────────────────────────
|
|
8
|
+
* `sema-agent/cc-decoded` 的 `pretty223.js:645295-645313`(`querySource:"hook_agent"` 在 `:645360`)。
|
|
9
|
+
* CC 该臂与 prompt 臂(`cc-stop-prompt.ts`)**不是同一批文本**:两档头句按事件名选
|
|
10
|
+
* (`r === "Stop" || r === "SubagentStop"` ⇒ verify 档,其余 ⇒ evaluate 档),尾段共用。
|
|
11
|
+
*
|
|
12
|
+
* ── 登记偏离(载体差异,[1134] 锚定纪律:能逐字的逐字,搬不动的显式登记而非静默改写)──────────
|
|
13
|
+
* CC 的完整模板还有两段本仓**刻意不搬**:
|
|
14
|
+
* 1. transcript 文件句("The conversation transcript is available at: ${路径}\nYou can read this
|
|
15
|
+
* file to analyze the conversation history if needed.")——CC 给评估者落一个转录**文件**并往
|
|
16
|
+
* session allow 规则里加 `Read(/<path>)`;本仓 agent 载体(main.ts hookAgent = runTask 读-only
|
|
17
|
+
* 子代理)没有转录文件设施,照搬=指向不存在文件的幻觉指令,比不发更糟。
|
|
18
|
+
* 2. 判词形句("When done, return your result using the ${结构化工具} tool with:\n- ok: true …\n
|
|
19
|
+
* - ok: false with reason …")——CC 给评估者挂了一只专用结构化输出工具并按 {ok, reason} 转译;
|
|
20
|
+
* 本仓决策面=子代理 final 文本折伪 stdout 走 `parseHookStdout`(JSON=SyncHookOutput 决策,
|
|
21
|
+
* 非 JSON=该事件对 plain text 的既有语义),判词形由 hook 作者自己的 prompt 承担——系统提示再
|
|
22
|
+
* 指定一个 {ok, reason} 形会与作者 prompt 的输出指令**打架**,且本仓没有那只工具。
|
|
23
|
+
* 另两处已有登记的正当偏离(不在本文件):步数上限 CC `M = 50` vs 本仓 `maxTurns: 10`(成本收窄,
|
|
24
|
+
* main.ts 成文);`thinkingConfig`/`mode:"dontAsk"` 等 CC 载体旋钮不适用本仓 runTask 形。
|
|
25
|
+
*/
|
|
26
|
+
/** Stop / SubagentStop 档头句(CC 逐字)。 */
|
|
27
|
+
export declare const CC_AGENT_HOOK_HEAD_STOP = "You are verifying a stop condition in Claude Code. Your task is to verify that the agent completed the given plan.";
|
|
28
|
+
/** 其余事件档头句(CC 逐字,`${event}` 为事件名插值——CC 原文即模板插值形)。 */
|
|
29
|
+
export declare function ccAgentHookHeadFor(event: string): string;
|
|
30
|
+
/** 工具指令段(CC 逐字;两档共用尾段中可搬的部分——transcript 句与判词形句见头注登记偏离)。 */
|
|
31
|
+
export declare const CC_AGENT_HOOK_TOOLS_SEGMENT = "Use the available tools to inspect the codebase and verify the condition.\nUse as few steps as possible - be efficient and direct.";
|
|
32
|
+
/** agent 臂评估者的完整系统提示:两档头句 + 工具指令段。 */
|
|
33
|
+
export declare function ccAgentHookSystemFor(event: string): string;
|
|
34
|
+
//# sourceMappingURL=cc-agent-hook-prompt.d.ts.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CC `hook_agent` 臂(`type:"agent"` 钩子条目)的评估者系统提示 —— **CC 逐字移植**(A-065 P2-5 修:
|
|
3
|
+
* 此前 agent 臂一个字的系统提示都不发,评估者拿不到「你在判什么/可以用工具去查/少走几步」任何一条,
|
|
4
|
+
* 判词质量静默劣化且黑盒极难察觉;`cc-stop-prompt.ts` 把孪生的 prompt 臂做到字节级 ALIGNED,本臂却整块
|
|
5
|
+
* 漏在考据之外=UNANCHORED)。
|
|
6
|
+
*
|
|
7
|
+
* ── 出处(第一手核过)────────────────────────────────────────────────────────────────────────
|
|
8
|
+
* `sema-agent/cc-decoded` 的 `pretty223.js:645295-645313`(`querySource:"hook_agent"` 在 `:645360`)。
|
|
9
|
+
* CC 该臂与 prompt 臂(`cc-stop-prompt.ts`)**不是同一批文本**:两档头句按事件名选
|
|
10
|
+
* (`r === "Stop" || r === "SubagentStop"` ⇒ verify 档,其余 ⇒ evaluate 档),尾段共用。
|
|
11
|
+
*
|
|
12
|
+
* ── 登记偏离(载体差异,[1134] 锚定纪律:能逐字的逐字,搬不动的显式登记而非静默改写)──────────
|
|
13
|
+
* CC 的完整模板还有两段本仓**刻意不搬**:
|
|
14
|
+
* 1. transcript 文件句("The conversation transcript is available at: ${路径}\nYou can read this
|
|
15
|
+
* file to analyze the conversation history if needed.")——CC 给评估者落一个转录**文件**并往
|
|
16
|
+
* session allow 规则里加 `Read(/<path>)`;本仓 agent 载体(main.ts hookAgent = runTask 读-only
|
|
17
|
+
* 子代理)没有转录文件设施,照搬=指向不存在文件的幻觉指令,比不发更糟。
|
|
18
|
+
* 2. 判词形句("When done, return your result using the ${结构化工具} tool with:\n- ok: true …\n
|
|
19
|
+
* - ok: false with reason …")——CC 给评估者挂了一只专用结构化输出工具并按 {ok, reason} 转译;
|
|
20
|
+
* 本仓决策面=子代理 final 文本折伪 stdout 走 `parseHookStdout`(JSON=SyncHookOutput 决策,
|
|
21
|
+
* 非 JSON=该事件对 plain text 的既有语义),判词形由 hook 作者自己的 prompt 承担——系统提示再
|
|
22
|
+
* 指定一个 {ok, reason} 形会与作者 prompt 的输出指令**打架**,且本仓没有那只工具。
|
|
23
|
+
* 另两处已有登记的正当偏离(不在本文件):步数上限 CC `M = 50` vs 本仓 `maxTurns: 10`(成本收窄,
|
|
24
|
+
* main.ts 成文);`thinkingConfig`/`mode:"dontAsk"` 等 CC 载体旋钮不适用本仓 runTask 形。
|
|
25
|
+
*/
|
|
26
|
+
/** Stop / SubagentStop 档头句(CC 逐字)。 */
|
|
27
|
+
export const CC_AGENT_HOOK_HEAD_STOP = "You are verifying a stop condition in Claude Code. Your task is to verify that the agent completed the given plan.";
|
|
28
|
+
/** 其余事件档头句(CC 逐字,`${event}` 为事件名插值——CC 原文即模板插值形)。 */
|
|
29
|
+
export function ccAgentHookHeadFor(event) {
|
|
30
|
+
return event === "Stop" || event === "SubagentStop" ? CC_AGENT_HOOK_HEAD_STOP : `You are evaluating a ${event} hook in Claude Code. Your task is to evaluate the condition described in the user message.`;
|
|
31
|
+
}
|
|
32
|
+
/** 工具指令段(CC 逐字;两档共用尾段中可搬的部分——transcript 句与判词形句见头注登记偏离)。 */
|
|
33
|
+
export const CC_AGENT_HOOK_TOOLS_SEGMENT = "Use the available tools to inspect the codebase and verify the condition.\nUse as few steps as possible - be efficient and direct.";
|
|
34
|
+
/** agent 臂评估者的完整系统提示:两档头句 + 工具指令段。 */
|
|
35
|
+
export function ccAgentHookSystemFor(event) {
|
|
36
|
+
return `${ccAgentHookHeadFor(event)}\n\n${CC_AGENT_HOOK_TOOLS_SEGMENT}`;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=cc-agent-hook-prompt.js.map
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* `sema-agent/cc-decoded` 的 `pretty220.js:609309` 起(219/218 同构)。逐字核实的四件:
|
|
10
10
|
* · 两档判据是 **hook 事件名**:`c = r === "Stop" || r === "SubagentStop"` ⇒ 这两个事件用**完整版**,其余用简版;
|
|
11
11
|
* · user 消息也被包装(见 {@link wrapCondition}),原文预设**会话就在上文**("transcript **above**");
|
|
12
|
-
* · 评估者 `thinkingConfig: { type: "disabled" }`
|
|
12
|
+
* · 评估者 `thinkingConfig: { type: "disabled", mechanical: !0 }`(A-065 P4-2:引文补 `mechanical` 键)+
|
|
13
|
+
* **`tools: []`** —— 它是**纯判官**,不能自己去查;
|
|
13
14
|
* · 会话消息**整段前置**(`s && s.length > 0 ? [...prepend(s), p] : [p]`),不是摘要。
|
|
14
|
-
* ⚠️
|
|
15
|
+
* ⚠️ 语料把**正文/标点**非 ASCII 存成 `\uXXXX` 六字符字面量(正文里那两个破折号是 `\u2014`)——**grep 真字符会全假阴**。
|
|
15
16
|
* 所以下面也用 `\u2014` 转义:源码保持 ASCII,运行时是同一个字符。
|
|
17
|
+
* (A-065 P4-5 量词修:locale/语言名表是例外——`日本語`/`русский` 等 127 个字面非 ASCII 字节在语料里
|
|
18
|
+
* 存活;另注 `grep -c` 数行不数次,计数用它会低估。)
|
|
16
19
|
* ⚠️ **为什么必须自己核**:cli 报过他们移植的那段是**第三种措辞**(与 CC 220 的两个分支都不逐字相同,
|
|
17
20
|
* 应是从更早版本移植后 CC 改过而没跟)。转述会漂,语料不会。
|
|
18
21
|
*/
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* `sema-agent/cc-decoded` 的 `pretty220.js:609309` 起(219/218 同构)。逐字核实的四件:
|
|
10
10
|
* · 两档判据是 **hook 事件名**:`c = r === "Stop" || r === "SubagentStop"` ⇒ 这两个事件用**完整版**,其余用简版;
|
|
11
11
|
* · user 消息也被包装(见 {@link wrapCondition}),原文预设**会话就在上文**("transcript **above**");
|
|
12
|
-
* · 评估者 `thinkingConfig: { type: "disabled" }`
|
|
12
|
+
* · 评估者 `thinkingConfig: { type: "disabled", mechanical: !0 }`(A-065 P4-2:引文补 `mechanical` 键)+
|
|
13
|
+
* **`tools: []`** —— 它是**纯判官**,不能自己去查;
|
|
13
14
|
* · 会话消息**整段前置**(`s && s.length > 0 ? [...prepend(s), p] : [p]`),不是摘要。
|
|
14
|
-
* ⚠️
|
|
15
|
+
* ⚠️ 语料把**正文/标点**非 ASCII 存成 `\uXXXX` 六字符字面量(正文里那两个破折号是 `\u2014`)——**grep 真字符会全假阴**。
|
|
15
16
|
* 所以下面也用 `\u2014` 转义:源码保持 ASCII,运行时是同一个字符。
|
|
17
|
+
* (A-065 P4-5 量词修:locale/语言名表是例外——`日本語`/`русский` 等 127 个字面非 ASCII 字节在语料里
|
|
18
|
+
* 存活;另注 `grep -c` 数行不数次,计数用它会低估。)
|
|
16
19
|
* ⚠️ **为什么必须自己核**:cli 报过他们移植的那段是**第三种措辞**(与 CC 220 的两个分支都不逐字相同,
|
|
17
20
|
* 应是从更早版本移植后 CC 改过而没跟)。转述会漂,语料不会。
|
|
18
21
|
*/
|
|
@@ -146,13 +146,18 @@ export declare function buildHookContext(parts: readonly string[]): string | und
|
|
|
146
146
|
* 载体失败/超时 → spawnError/timedOut 同款非阻断。可重入禁是【载体】的责任(部署组装的 runTask
|
|
147
147
|
* 不装 hooks——结构性,见 main.ts);hook-runner 自身不发模型调用,fake 载体即可单测。
|
|
148
148
|
*/
|
|
149
|
-
/**
|
|
149
|
+
/** CC 评估者的额外输入(prompt 臂=Stop×prompt,见 `cc-stop-prompt.ts` / `branch-transcript.ts`;
|
|
150
|
+
* agent 臂=每事件,见 `cc-agent-hook-prompt.ts`,A-065 P2-5)。 */
|
|
150
151
|
export interface LlmHookExtra {
|
|
151
152
|
/** CC 逐字的系统提示。 */
|
|
152
153
|
system: string;
|
|
154
|
+
/** 触发事件名——no_content 重试的 warn/notice 记账用(此前硬编 "Stop",扩 agent 臂后必须真名)。 */
|
|
155
|
+
event: string;
|
|
153
156
|
/** **会话 transcript,放在条件之前** —— CC 的包装句写的是 "transcript **above**",
|
|
154
|
-
*
|
|
155
|
-
|
|
157
|
+
* 所以它必须真的在上文,否则那句话本身就是在骗模型。仅 prompt 臂(Stop×prompt)供给;
|
|
158
|
+
* agent 臂缺席=登记偏离(CC 给的是转录**文件路径**,本仓 agent 载体无该设施,
|
|
159
|
+
* 见 cc-agent-hook-prompt.ts 头注)。 */
|
|
160
|
+
transcript?: string;
|
|
156
161
|
}
|
|
157
162
|
/**
|
|
158
163
|
* 把校验过的 {@link HooksConfig} 翻成 core `Hooks` 回调(阶段二:engine-owned 9 事件全点亮)。
|
|
@@ -45,6 +45,7 @@ import { hostShell, resolveHostShell } from "../plugins/host-platform.js";
|
|
|
45
45
|
import { HooksConfig, DEFAULT_HOOK_TIMEOUT_SECONDS, HOOK_EVENT_OWNER, } from "@sema-agent/registry-core/hooks";
|
|
46
46
|
import { redactSecrets } from "../trace/redact.js";
|
|
47
47
|
import { ccPromptSystemFor, wrapCondition, parseCcVerdict, CC_EVALUATOR_MAX_OUTPUT_TOKENS } from "./cc-stop-prompt.js";
|
|
48
|
+
import { ccAgentHookSystemFor } from "./cc-agent-hook-prompt.js";
|
|
48
49
|
import { renderBranchTranscript } from "./branch-transcript.js";
|
|
49
50
|
/** 服务侧防线上限(契约本身不设量纲;不设界=单请求可塞任意大配置/任意长命令)。超界=校验错(fail-loud)。 */
|
|
50
51
|
// S1([1870]):hook 的 shell 与 host lane 同源(core getShellConfig,bash 优先)。模块装载即预热
|
|
@@ -497,7 +498,7 @@ async function runLlmHook(entry, payload, ctx, extra) {
|
|
|
497
498
|
// 因为 CC 的包装句逐字写着 "Based on the conversation transcript **above**"。
|
|
498
499
|
// CC 形:transcript 在前,条件被 CC 的包装句包起来("Based on the conversation transcript **above**…")。
|
|
499
500
|
// 顺序是承重的 —— 包装句逐字预设会话在上文,放反了那句话本身就是在骗模型。
|
|
500
|
-
const prompt = extra ? `${extra.transcript}\n\n---\n\n${wrapCondition("Stop", substituted)}` : substituted;
|
|
501
|
+
const prompt = extra?.transcript !== undefined ? `${extra.transcript}\n\n---\n\n${wrapCondition("Stop", substituted)}` : substituted;
|
|
501
502
|
// 载体外再包一层硬顶:契约说载体自己兜超时,但一个部署组装 bug 不该能挂死工具门(纵深)。
|
|
502
503
|
// A-002.12:硬顶只 unref 不清 ⇒ 每次被门到的工具调用留一只带闭包的定时器,最长挂
|
|
503
504
|
// MAX_HOOK_TIMEOUT_SECONDS+5s=605s(工具门是热路径,一个任务几十上百次)。整个函数收在
|
|
@@ -524,12 +525,12 @@ async function runLlmHook(entry, payload, ctx, extra) {
|
|
|
524
525
|
// (该拦没拦),而它是**瞬态**的(推理档模型偶尔把额度用在 thinking 上)。
|
|
525
526
|
// ⚠️ 只重试**无内容**,不重试"有内容但读不懂" —— 后者重试一次多半还是读不懂,而且那一格按设计就该放行。
|
|
526
527
|
if (extra && !res.ok && res.code === "no_content") { // B8:判别走码不走文案(v3.1 批2)
|
|
527
|
-
ctx.logger.warn("hook_llm_no_content_retry", { event:
|
|
528
|
+
ctx.logger.warn("hook_llm_no_content_retry", { event: extra.event });
|
|
528
529
|
res = await invoke();
|
|
529
530
|
// 重试之后**仍然**没有内容 ⇒ 这一轮的守卫确实没能评估。发观测帧(纯 observe,不改变运行)——
|
|
530
531
|
// 方向仍是 fail-open,但用户/壳侧要能知道「这轮没看住」,否则那个放行与「已达成」无法区分。
|
|
531
532
|
if (!res.ok && res.code === "no_content") {
|
|
532
|
-
ctx.onHookNotice?.({ kind: "hook_decision_unavailable", event:
|
|
533
|
+
ctx.onHookNotice?.({ kind: "hook_decision_unavailable", event: extra.event, reason: "no_content", detail: "carrier returned no content (after one retry)" });
|
|
533
534
|
}
|
|
534
535
|
}
|
|
535
536
|
if (!res.ok) {
|
|
@@ -735,7 +736,10 @@ llmExtra) {
|
|
|
735
736
|
if (entry.type === "prompt" || entry.type === "agent")
|
|
736
737
|
llmEntries++;
|
|
737
738
|
const run = entry.type === "http" ? await runHttpHook(entry, payload, ctx)
|
|
738
|
-
|
|
739
|
+
// agent 臂每事件供 CC 两档系统提示(A-065 P2-5:此前恒 undefined=评估者裸跑);顺带获得
|
|
740
|
+
// no_content 一次重试与 CC_EVALUATOR_MAX_OUTPUT_TOKENS(载体不消费则忽略,agent 载体的
|
|
741
|
+
// 输出预算=limits,非 completion 概念)。prompt 臂维持 Stop×prompt 专属 llmExtra(带 transcript)。
|
|
742
|
+
: entry.type === "prompt" || entry.type === "agent" ? await runLlmHook(entry, payload, ctx, entry.type === "prompt" ? llmExtra : { system: ccAgentHookSystemFor(event), event })
|
|
739
743
|
: await runCommandHook(entry, payload, ctx);
|
|
740
744
|
if (run.spawnError) {
|
|
741
745
|
ctx.logger.warn(entry.type === "http" ? "hook_http_request_failed" : entry.type === "prompt" || entry.type === "agent" ? "hook_llm_failed" : "hook_command_spawn_failed", { event, type: entry.type, error: clip(run.spawnError, 300) });
|
|
@@ -1199,7 +1203,7 @@ export function createTaskHooks(config, ctx) {
|
|
|
1199
1203
|
}
|
|
1200
1204
|
const rendered = renderBranchTranscript(branch);
|
|
1201
1205
|
if (rendered.sufficient) {
|
|
1202
|
-
llmExtra = { system: ccPromptSystemFor("Stop"), transcript: rendered.text };
|
|
1206
|
+
llmExtra = { system: ccPromptSystemFor("Stop"), event: "Stop", transcript: rendered.text };
|
|
1203
1207
|
}
|
|
1204
1208
|
else {
|
|
1205
1209
|
// 诚实降级:**跳过 prompt 条目**(记账可见),而不是"送个空会话进去看看模型怎么说"。
|
|
@@ -74,7 +74,15 @@ async function handleApprovalsAssistantBody(req, res, url, ctx, miss) {
|
|
|
74
74
|
// #209 件4 + [3684]②:行整只上 wire(键集契约照旧「整只透传、不按键投影」),但两格由投影函数
|
|
75
75
|
// 负责——`riskDescriptor.shadowedRule` 脱敏、`governanceForced` 归因。与 `/v1/approvals/stream`
|
|
76
76
|
// **共用同一个** `projectPendingForWire`(两条读面各投各的必漂,见其顶注)。
|
|
77
|
-
|
|
77
|
+
// #315([4658] 案):第二顶层键 `livePending` = streamApproval 窗内的流内 ask(此前对轮询消费端
|
|
78
|
+
// 结构性不可见,要等窗超时转 park 才浮现)。**分数组不混编**:两族行形与决议路由不同(live 行
|
|
79
|
+
// 决议口=respond,durable 行=decide;数组名即路由判据)。scope 复用同一表达式(operator=全量,
|
|
80
|
+
// principal=只见自己;"__none__" 恒空)。协调器未装配 ⇒ 键**缺席**不编 [](缺席=「本部署无
|
|
81
|
+
// live 审批面」,空数组=「有面、此刻无待批」,两义须可判别)。
|
|
82
|
+
sendJson(res, 200, {
|
|
83
|
+
pending: (await cs.listPending(scope)).map((r) => projectPendingForWire(r, deps.config)),
|
|
84
|
+
...(deps.toolApproval ? { livePending: deps.toolApproval.listLivePending(scope) } : {}),
|
|
85
|
+
});
|
|
78
86
|
return;
|
|
79
87
|
}
|
|
80
88
|
// exemptions surface — the UI's "本会话不再询问" state (list) + revoke. Same authz shape as
|
|
@@ -367,8 +367,14 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
|
|
|
367
367
|
// 进缓冲不丢):超时=recover 真挂了,按旧行为出快照(可用性路径,行还有 first-sight-terminal
|
|
368
368
|
// 豁免与下次重连兜底),不无限拖住连接。
|
|
369
369
|
if (terminal?.ready !== undefined) {
|
|
370
|
-
|
|
371
|
-
|
|
370
|
+
// A-067 C5 补遗(merge-rescan 车证伪「唯二反例」的第三处):竞速一结束 finally 清
|
|
371
|
+
// (A-002.10 同款)——unref 只是不吊进程退,不回收定时器本身。
|
|
372
|
+
let readyTimer;
|
|
373
|
+
await Promise.race([terminal.ready, new Promise((r) => { readyTimer = setTimeout(r, 3000); if (typeof readyTimer.unref === "function")
|
|
374
|
+
readyTimer.unref(); })]).finally(() => {
|
|
375
|
+
if (readyTimer !== undefined)
|
|
376
|
+
clearTimeout(readyTimer);
|
|
377
|
+
});
|
|
372
378
|
if (done || res.writableEnded)
|
|
373
379
|
return;
|
|
374
380
|
}
|
|
@@ -49,7 +49,11 @@ async function handleNotifyWakeBody(req, res, url, ctx, miss) {
|
|
|
49
49
|
sendError(res, 400, "request.field_invalid", "task_id must be a non-empty string of at most 190 characters");
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
+
// [5028]/A-002.1 同纪律双向锚:satisfies 保「表内无词表外的词」(core 删/改词 ⇒ 编译红),
|
|
53
|
+
// 穷举断言保「词表无表外的词」(core 加词 ⇒ 编译红)——单靠 satisfies 只有前半,加词会静默 400 拒合法值。
|
|
52
54
|
const NOTIFY_STATUSES = ["completed", "failed", "killed", "cancelled", "event"];
|
|
55
|
+
const _notifyExhaustive = true;
|
|
56
|
+
void _notifyExhaustive;
|
|
53
57
|
if (typeof nb.status !== "string" || !NOTIFY_STATUSES.includes(nb.status)) {
|
|
54
58
|
sendError(res, 400, "request.field_invalid", `status must be one of ${NOTIFY_STATUSES.join("/")}`);
|
|
55
59
|
return;
|
package/dist/main.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { Runner, InMemoryToolResultStore, TtlSessionStore, uuidv7, defaultTaskRegistry, createAllowDenyPolicy, workflowsCapability, probeSearchBackend, describeStaticWiring } from "@sema-agent/core";
|
|
4
|
+
import { Runner, InMemoryToolResultStore, TtlSessionStore, uuidv7, defaultTaskRegistry, createAllowDenyPolicy, workflowsCapability, probeSearchBackend, describeStaticWiring, DEFAULT_SUBAGENT_TOOL_NAME } from "@sema-agent/core";
|
|
5
5
|
import { createSessionTitler } from "./session-titler.js";
|
|
6
6
|
import { posIntEnv } from "./session-watch.js";
|
|
7
7
|
import { selectEnvironmentTool } from "./capabilities/select-environment-tool.js";
|
|
@@ -423,13 +423,18 @@ async function main() {
|
|
|
423
423
|
const runnerTierFrozen = Object.keys(config.tiers).length > 0;
|
|
424
424
|
// hooks 阶段三b(design/HOOKS-PHASE3B-PROMPT-AGENT.md):prompt/agent 条目的模型调用载体。
|
|
425
425
|
// - hookLlm(prompt 条目)= 直连网关一次非流式 completion——一次判定调用不起 core task(重炮打蚊子
|
|
426
|
-
// 且引入可重入面)
|
|
426
|
+
// 且引入可重入面);系统注入=事件分档(A-065 P3-10 订正,旧句「无系统注入」已过期):Stop/SubagentStop
|
|
427
|
+
// 注入 CC 逐字系统提示(cc-stop-prompt.ts,hook-runner.ts 的 extra.system),其余事件无注入。
|
|
428
|
+
// agent 条目现状零系统注入=A-065 P2-5 立案中(UNANCHORED,CC 该臂有自己的两档提示)。默认模型=
|
|
429
|
+
// summarize 角色(cheap tier,
|
|
427
430
|
// council/压缩同款;缺角色→主模型);entry.model 覆盖须命中部署 catalog(单用户=自家目录全开,与
|
|
428
431
|
// workflow 模型 allowlist 同纪律),未命中=该条目 fail(非阻断记账)。费用不折 task budget(hook=
|
|
429
432
|
// 部署策略面开销;hook_llm_calls_total/hook_llm_cost 由 metrics 记)。
|
|
430
433
|
// - hookAgent(agent 条目)= runner.runTask 读-only 子代理:handsReadOnly + enableFork:false +
|
|
431
434
|
// 不装 hooks(🔴 可重入禁——结构性:这个 spec 永不携带 TaskSpec.hooks)+ maxTurns 10 + maxCostUsd
|
|
432
|
-
// 0.05
|
|
435
|
+
// 0.05 独立小预算,影响范围受限(CC 该臂 M=50 步,收窄=已声明理由的正当偏离)。系统提示=CC
|
|
436
|
+
// hook_agent 两档逐字(cc-agent-hook-prompt.ts,A-065 P2-5 修;hook-runner 按事件铸、此处消费进
|
|
437
|
+
// spec.systemPrompt 整替形)。final result 文本即输出(与 prompt 同一 SyncHookOutput 决策面)。
|
|
433
438
|
// hookModelFor/hookLlm 抽到 src/hooks/hook-llm.ts(可注入 fetch/keyResolver ⇒ 可单测):api 形分派
|
|
434
439
|
// (openai /chat/completions 与 anthropic /v1/messages 双腿)、缺省 baseUrl 按 api 分家、per-model key
|
|
435
440
|
// 恒胜 + foreign-no-key 连 prompt 都不发的 fail-closed 纪律,全在该模块内(注释含 audit 原文)。
|
|
@@ -440,7 +445,7 @@ async function main() {
|
|
|
440
445
|
// 清理(reaper 只扫 staging orphan),cap=4/事件 × 长会话=无主行堆积。专用 Runner 覆盖 sessionStore 为
|
|
441
446
|
// 进程内 TTL 店(1h 短 TTL,hook 子代理无 resume 语义,行随进程/TTL 消失);其余 deps 原样共享。
|
|
442
447
|
const hookAgentRunner = new Runner({ ...runnerDeps, sessionStore: new TtlSessionStore({ defaultTtlDays: 1 / 24 }) });
|
|
443
|
-
const hookAgent = async ({ prompt, model, timeoutMs }) => {
|
|
448
|
+
const hookAgent = async ({ prompt, model, timeoutMs, system }) => {
|
|
444
449
|
const pick = hookModelFor(model);
|
|
445
450
|
if (!pick.ok)
|
|
446
451
|
return pick;
|
|
@@ -449,6 +454,10 @@ async function main() {
|
|
|
449
454
|
objective: prompt,
|
|
450
455
|
sessionId: uuidv7(),
|
|
451
456
|
model: pick.catalogRef,
|
|
457
|
+
// A-065 P2-5:CC hook_agent 评估者系统提示(cc-agent-hook-prompt.ts 两档,hook-runner 按事件铸)。
|
|
458
|
+
// 整替形与 CC 对齐(CC 该臂 systemPrompt=O 整段)。maxOutputTokens 座刻意不消费:agent 载体
|
|
459
|
+
// 的输出预算=limits(maxTurns/maxCostUsd),非单次 completion 概念。
|
|
460
|
+
...(system !== undefined ? { systemPrompt: system } : {}),
|
|
452
461
|
handsReadOnly: true,
|
|
453
462
|
enableFork: false,
|
|
454
463
|
// core 5.8.0:预算四键全入 limits(顶层 maxCostUsd 删);timeoutSec(秒)→ maxWalltimeMs(毫秒,原生同单位)
|
|
@@ -853,7 +862,9 @@ async function main() {
|
|
|
853
862
|
// #318:这条取用**刻意不带** ScenarioContext —— boot 期没有任何一条真会话,给它编一个假 sessionId
|
|
854
863
|
// 会在清单表里铸出一份无人认领的分区。它只取裸 Agent 工具,task-list 家族在这份 bundle 里根本不被
|
|
855
864
|
// 消费([4692]③ 已核),缺席 = 修前字节形。
|
|
856
|
-
|
|
865
|
+
// A-067 C3-3(F1b):裸 "Agent" 字面量换 core 单源常量——runtime-governance.ts 同概念已用
|
|
866
|
+
// DEFAULT_SUBAGENT_TOOL_NAME(5.0.0 RB-476 旧名 "Task" 改名史=这个字面量真漂过一次)。
|
|
867
|
+
const parkedReviveTool = checkpointStore && backgroundAgentStore ? scenarios.default?.({})?.tools.find((t) => t.name === DEFAULT_SUBAGENT_TOOL_NAME) : undefined;
|
|
857
868
|
// [1596]/[1597] 跨副本父约束重供席工厂:host 任务的 toolPolicy 解析槽按**当前**部署配置重建
|
|
858
869
|
// 「部署 ⊇ 操作员」两层完整链(design/181 件二收编;实现与全部理由在 boot/parked-revive-gate.ts,
|
|
859
870
|
// 提出去的唯一理由是 main.ts 顶层 `void main()` 让那条腿的运行期语义在原地一格都钉不住)。
|
package/dist/memory-sync.js
CHANGED
|
@@ -120,6 +120,13 @@ export function parseMemorySyncRequest(body, scope) {
|
|
|
120
120
|
}
|
|
121
121
|
if (fm["trust"] !== undefined && fm["trust"] !== "untrusted")
|
|
122
122
|
return { ok: false, error: 'entry.frontmatter.trust must be the literal "untrusted" when present (single-value union — there is no trusted spelling)' };
|
|
123
|
+
// core 5.55.0 第 9 键 distilled(consolidation 产物块):受理为**不透明块**浅验过境——成员级判据
|
|
124
|
+
// 不在这里手抄(F1b 温床),真闸=store add/update 臂的 distilledWhitewashRefusal(committedDistilledOf/
|
|
125
|
+
// distilledEquals);sync 剥掉它=洗白(#323 origin 同病),深验它=第二份 core 结构。
|
|
126
|
+
const rawDistilled = fm["distilled"];
|
|
127
|
+
if (rawDistilled !== undefined && (rawDistilled === null || typeof rawDistilled !== "object" || Array.isArray(rawDistilled))) {
|
|
128
|
+
return { ok: false, error: "entry.frontmatter.distilled must be an object (opaque consolidation block) when present" };
|
|
129
|
+
}
|
|
123
130
|
const rawProv = fm["provenance"];
|
|
124
131
|
if (rawProv !== undefined) {
|
|
125
132
|
if (rawProv === null || typeof rawProv !== "object" || Array.isArray(rawProv))
|
|
@@ -160,6 +167,7 @@ export function parseMemorySyncRequest(body, scope) {
|
|
|
160
167
|
? { provenance: { kind: "repo_file", path: rawProv.path, contentHash: rawProv.contentHash, ingestedAt: rawProv.ingestedAt } }
|
|
161
168
|
: {}),
|
|
162
169
|
...(fm["extra"] !== undefined ? { extra: [...fm["extra"]] } : {}),
|
|
170
|
+
...(rawDistilled !== undefined ? { distilled: structuredClone(rawDistilled) } : {}),
|
|
163
171
|
},
|
|
164
172
|
});
|
|
165
173
|
}
|
|
@@ -48,7 +48,10 @@ const MAX_TOOL_INPUT_CHARS = 8192;
|
|
|
48
48
|
* Generous (default 30d) so it never pre-empts a legitimately long-lived human/irreversible_ask gate;
|
|
49
49
|
* env-overridable. Clamped ≥1min so a misconfig can't expire live suspensions instantly.
|
|
50
50
|
*/
|
|
51
|
-
|
|
51
|
+
// A-067 C1-2(F2a):解析必须钳有限——`"Infinity"` 经 `Number(env) || def` 取 Infinity、Math.max 恒
|
|
52
|
+
// Infinity ⇒ 本常量存在的唯一理由(SLA 服务死掉后 pending 行仍被 GC 的 crash-safe 绝对上限)整条失效。
|
|
53
|
+
// 非有限/非法值落默认(与 remote-shell numEnvOr 同判据方向;此处零告警通道,静默落默认承原形)。
|
|
54
|
+
export const TERMINAL_BACKSTOP_MS = Math.max(60_000, (Number.isFinite(Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS)) ? Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS) : 0) || 30 * 86_400_000);
|
|
52
55
|
/**
|
|
53
56
|
* design/80 D-D (adversarial fix): the crash-safe `terminal_at_ms` backstop must fall STRICTLY AFTER any SLA
|
|
54
57
|
* `deadline`, never AT it. `terminal_at_ms = max(createdAt+backstop, deadline)` made the two coincide whenever an
|
|
@@ -25,7 +25,7 @@ import { computeEntryRev, serializeEntryFile } from "@sema-agent/core";
|
|
|
25
25
|
import { pgSafeJsonStringify, pgHasUnstorable } from "./pg-safe-json.js";
|
|
26
26
|
import { assertMemoryScopeWidth, memoryEntryKeyWidthRefusal } from "./memory-key-guards.js"; // R5 批γ + #271 件1:scope/slug 写前宽守卫
|
|
27
27
|
// #307 件6(core 5.46.0 design/336 §2.3-2):external-origin 不可变律 —— 两支 SQL 孪生共用**一份**判据。
|
|
28
|
-
import { ambiguousOriginRefusal,
|
|
28
|
+
import { ambiguousOriginRefusal, createBatchCommittedBaseline, exposureBandOf, exposureCarriage, originMarkedSqlPredicate, originWhitewashRefusal, rememberBatchCommitted, distilledWhitewashRefusal, distilledCarriage, unknownPatchOpRefusal, } from "./memory-origin-law.js";
|
|
29
29
|
/** Table names (single source). Deliberately DISJOINT from the legacy `agent_memory*` tables —
|
|
30
30
|
* the retired MemoryStore plane and this entry plane must never cross-write. */
|
|
31
31
|
export const PG_MEMORY_ENGINE_TABLES = {
|
|
@@ -136,6 +136,8 @@ export class PgMemoryEngineBackend {
|
|
|
136
136
|
// #319(design/336 §5.2):committed external-origin 事实随**每一个**头面(listHeaders + search)。
|
|
137
137
|
// 归一化只有一份(memory-origin-law.exposureCarriage → core 的 committedOriginOf),两支方言同源。
|
|
138
138
|
...exposureCarriage(fm),
|
|
139
|
+
// #337(conformance c38,TiDB 孪生同注):distilled 事实(carrierRev + supersedes)随每一个头面。
|
|
140
|
+
...distilledCarriage(fm),
|
|
139
141
|
};
|
|
140
142
|
}
|
|
141
143
|
entryFromRow(r) {
|
|
@@ -249,7 +251,7 @@ export class PgMemoryEngineBackend {
|
|
|
249
251
|
// #307 件6(design/336 §2.3-2):external-origin 不可变律的**批次基线**。本后端逐条 patch 直打真库,
|
|
250
252
|
// 所以同批 delete 之后再 add 时库里已无该行 —— 只看当下会把它误判成「合法新生命」。按本批第一次
|
|
251
253
|
// 触碰记下已提交 origin,整批按那一份判(理由与两半判据见 memory-origin-law.ts 顶注)。
|
|
252
|
-
const originBaseline =
|
|
254
|
+
const originBaseline = createBatchCommittedBaseline();
|
|
253
255
|
for (const patch of patches) {
|
|
254
256
|
try {
|
|
255
257
|
if (patch.op !== "delete") {
|
|
@@ -352,12 +354,19 @@ export class PgMemoryEngineBackend {
|
|
|
352
354
|
// `/malformed patch refused/`;顺序反了会先报 `add_guard_absent_conflict`,同一输入两后端两个答复。
|
|
353
355
|
// 基线取「本批第一次触碰」那一份(同批 delete 之后的 re-add 照样被这条拒;已提交的墓碑才是合法出口)。
|
|
354
356
|
{
|
|
355
|
-
const
|
|
356
|
-
const refusal = originWhitewashRefusal("add",
|
|
357
|
+
const committedBaseFm = rememberBatchCommitted(originBaseline, entry.id, priorRow !== undefined ? fromJson(priorRow.frontmatter) : undefined);
|
|
358
|
+
const refusal = originWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
|
|
357
359
|
if (refusal !== undefined) {
|
|
358
360
|
report.conflicts.push({ op: "add", id: entry.id, reason: refusal });
|
|
359
361
|
return;
|
|
360
362
|
}
|
|
363
|
+
// #337(conformance c36,design/339,TiDB 孪生同注):distilled 不可变律的 add 腿 —— 与 origin
|
|
364
|
+
// 律同基线同判位(guard 之前;同批 delete+re-add 靠基线拒,已提交墓碑后的 add 放行)。
|
|
365
|
+
const dRefusal = distilledWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
|
|
366
|
+
if (dRefusal !== undefined) {
|
|
367
|
+
report.conflicts.push({ op: "add", id: entry.id, reason: dRefusal });
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
361
370
|
}
|
|
362
371
|
// E-02 add-if-absent guard (core 1.275.1/1.276.0 contract clause, design/142 S2.5): a
|
|
363
372
|
// `guard:"absent"` add must NEVER blind-overwrite an existing id — same rev AND same
|
|
@@ -448,7 +457,7 @@ export class PgMemoryEngineBackend {
|
|
|
448
457
|
const committedFm = fromJson(cur.rows[0].frontmatter);
|
|
449
458
|
// #307 件6:**delete 腿也要记基线** —— 同批「delete 掉一条带标记的行、再 add 一条不带标记的」正是
|
|
450
459
|
// 洗白的最直接写法,而 delete 一旦落库,后面那条 add 的 SELECT 就什么都看不到了。
|
|
451
|
-
const
|
|
460
|
+
const committedBaseFm = rememberBatchCommitted(originBaseline, patch.id, committedFm);
|
|
452
461
|
if (patch.op === "update" && patch.entry !== undefined) {
|
|
453
462
|
if (committedFm.provenance?.kind === "repo_file") {
|
|
454
463
|
const next = patch.entry.frontmatter;
|
|
@@ -465,11 +474,18 @@ export class PgMemoryEngineBackend {
|
|
|
465
474
|
}
|
|
466
475
|
// #307 件6:origin 不可变律的 update 腿。顺序与 core File backend 逐字同 —— repo_file 白洗判据
|
|
467
476
|
// 在前、origin 在后、两者都在 rev CAS 之前(judged on the committed frontmatter of THIS SELECT)。
|
|
468
|
-
const refusal = originWhitewashRefusal("update",
|
|
477
|
+
const refusal = originWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
|
|
469
478
|
if (refusal !== undefined) {
|
|
470
479
|
report.conflicts.push({ op: "update", id: patch.id, reason: refusal });
|
|
471
480
|
return;
|
|
472
481
|
}
|
|
482
|
+
// #337(conformance c36/c37,TiDB 孪生同注):distilled 不可变律的 update 腿 —— 同样先于 rev CAS
|
|
483
|
+
// (c37:骑着 stale baseRev 的 strip 答 malformed refusal,不是 rev mismatch)。
|
|
484
|
+
const dRefusal = distilledWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
|
|
485
|
+
if (dRefusal !== undefined) {
|
|
486
|
+
report.conflicts.push({ op: "update", id: patch.id, reason: dRefusal });
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
473
489
|
}
|
|
474
490
|
if (patch.baseRev !== undefined && patch.baseRev !== currentRev) {
|
|
475
491
|
// Per-id CAS (§2.4 并发): NEVER blind-write over a concurrent change. No shadow-restore leg here —
|
|
@@ -26,7 +26,7 @@ import { computeEntryRev, serializeEntryFile } from "@sema-agent/core";
|
|
|
26
26
|
import { pgHasUnstorable } from "./pg-safe-json.js";
|
|
27
27
|
import { assertMemoryScopeWidth, memoryEntryKeyWidthRefusal } from "./memory-key-guards.js"; // R5 批γ + #271 件1:scope/slug 写前宽守卫
|
|
28
28
|
// #307 件6(core 5.46.0 design/336 §2.3-2):external-origin 不可变律 —— 与 Pg 孪生共用**一份**判据。
|
|
29
|
-
import { ambiguousOriginRefusal,
|
|
29
|
+
import { ambiguousOriginRefusal, createBatchCommittedBaseline, exposureBandOf, exposureCarriage, originWhitewashRefusal, rememberBatchCommitted, distilledWhitewashRefusal, distilledCarriage, unknownPatchOpRefusal, } from "./memory-origin-law.js";
|
|
30
30
|
import { isMysqlDupKeyError } from "./sql-errors.js";
|
|
31
31
|
/** Table names (single source) — SAME names as PG_MEMORY_ENGINE_TABLES (the two dialects never share
|
|
32
32
|
* one database), deliberately DISJOINT from the legacy `agent_memory*` MemoryStore plane. */
|
|
@@ -119,6 +119,8 @@ export class TiDBMemoryEngineBackend {
|
|
|
119
119
|
sizeBytes: Number(r.size_bytes),
|
|
120
120
|
// #319(design/336 §5.2,Pg 孪生同注):committed external-origin 事实随每一个头面;归一化同源。
|
|
121
121
|
...exposureCarriage(fm),
|
|
122
|
+
// #337(conformance c38,Pg 孪生同注):distilled 事实(carrierRev + supersedes)随每一个头面。
|
|
123
|
+
...distilledCarriage(fm),
|
|
122
124
|
};
|
|
123
125
|
}
|
|
124
126
|
entryFromRow(r) {
|
|
@@ -187,7 +189,7 @@ export class TiDBMemoryEngineBackend {
|
|
|
187
189
|
const historyRows = [];
|
|
188
190
|
// #307 件6(design/336 §2.3-2):external-origin 不可变律的**批次基线**(Pg 版同注)。逐条 patch 直打
|
|
189
191
|
// 真库 ⇒ 同批 delete 之后再 add 时库里已无该行,只看当下会误判成「合法新生命」。
|
|
190
|
-
const originBaseline =
|
|
192
|
+
const originBaseline = createBatchCommittedBaseline();
|
|
191
193
|
for (const patch of patches) {
|
|
192
194
|
try {
|
|
193
195
|
if (patch.op !== "delete") {
|
|
@@ -283,12 +285,19 @@ export class TiDBMemoryEngineBackend {
|
|
|
283
285
|
// File backend 的 planOne 同序;顺序反了带 guard 的洗白 add 会先报 add_guard_absent_conflict,
|
|
284
286
|
// 而契约试剂盒要的是 /malformed patch refused/,同一输入两后端两个答复)。
|
|
285
287
|
{
|
|
286
|
-
const
|
|
287
|
-
const refusal = originWhitewashRefusal("add",
|
|
288
|
+
const committedBaseFm = rememberBatchCommitted(originBaseline, entry.id, priorRow !== undefined ? fromJson(priorRow.frontmatter) : undefined);
|
|
289
|
+
const refusal = originWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
|
|
288
290
|
if (refusal !== undefined) {
|
|
289
291
|
report.conflicts.push({ op: "add", id: entry.id, reason: refusal });
|
|
290
292
|
return;
|
|
291
293
|
}
|
|
294
|
+
// #337(conformance c36,design/339):distilled 不可变律的 add 腿 —— origin 律同基线同判位
|
|
295
|
+
// (guard 之前;同批 delete+re-add 靠基线拒,已提交墓碑后的 add 放行)。
|
|
296
|
+
const dRefusal = distilledWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
|
|
297
|
+
if (dRefusal !== undefined) {
|
|
298
|
+
report.conflicts.push({ op: "add", id: entry.id, reason: dRefusal });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
292
301
|
}
|
|
293
302
|
// E-02 add-if-absent guard (core 1.275.1/1.276.0 contract clause; caught live by the contract
|
|
294
303
|
// suite on real TiDB 2026-07-13): same rev AND same (scope, slug) projection = idempotent
|
|
@@ -395,7 +404,7 @@ export class TiDBMemoryEngineBackend {
|
|
|
395
404
|
const committedFm = fromJson(cur[0].frontmatter);
|
|
396
405
|
// #307 件6:delete 腿也要记基线(Pg 版同注)—— 同批「delete 掉带标记的行、再 add 一条不带标记的」
|
|
397
406
|
// 正是洗白的最直接写法,而 delete 一旦落库,后面那条 add 的 SELECT 什么都看不到。
|
|
398
|
-
const
|
|
407
|
+
const committedBaseFm = rememberBatchCommitted(originBaseline, patch.id, committedFm);
|
|
399
408
|
if (patch.op === "update" && patch.entry !== undefined) {
|
|
400
409
|
if (committedFm.provenance?.kind === "repo_file") {
|
|
401
410
|
const next = patch.entry.frontmatter;
|
|
@@ -411,11 +420,18 @@ export class TiDBMemoryEngineBackend {
|
|
|
411
420
|
}
|
|
412
421
|
}
|
|
413
422
|
// #307 件6:origin 不可变律的 update 腿(Pg 版同注:repo_file 在前、origin 在后、两者都在 rev CAS 之前)。
|
|
414
|
-
const refusal = originWhitewashRefusal("update",
|
|
423
|
+
const refusal = originWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
|
|
415
424
|
if (refusal !== undefined) {
|
|
416
425
|
report.conflicts.push({ op: "update", id: patch.id, reason: refusal });
|
|
417
426
|
return;
|
|
418
427
|
}
|
|
428
|
+
// #337(conformance c36/c37):distilled 不可变律的 update 腿 —— 同样先于 rev CAS(c37:骑着
|
|
429
|
+
// stale baseRev 的 strip 答 malformed refusal,不是 rev mismatch)。
|
|
430
|
+
const dRefusal = distilledWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
|
|
431
|
+
if (dRefusal !== undefined) {
|
|
432
|
+
report.conflicts.push({ op: "update", id: patch.id, reason: dRefusal });
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
419
435
|
}
|
|
420
436
|
if (patch.baseRev !== undefined && patch.baseRev !== currentRev) {
|
|
421
437
|
// Per-id CAS (§2.4 并发): NEVER blind-write over a concurrent change. ABA note shared with the
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* 之后同批的任何 op 都按那一份判。⇒「同批 delete+re-add 拒 / 已提交墓碑后 add 放」这条分界
|
|
37
37
|
* (core 契约逐字要求的那条)才成立。
|
|
38
38
|
*/
|
|
39
|
-
import { type MemoryEntryFrontmatter, type
|
|
39
|
+
import { type MemoryEntryFrontmatter, type MemoryEntryHeader } from "@sema-agent/core";
|
|
40
40
|
/**
|
|
41
41
|
* #319 增量(sema-comms [4768] 勘误 + core 仓 `1a5ebaa1`,conformance 30→31)—— **歧义 origin 表示**
|
|
42
42
|
* 的拒绝门,与不可变律同批、同一个写路径上。
|
|
@@ -106,18 +106,25 @@ export declare const ORIGIN_CARRIER_LINE_SQL_REGEX = "^[\\u0009\\u000a\\u000b\\u
|
|
|
106
106
|
* `column` = 该表 frontmatter 列的表达式(调用方自己保证它是列名/限定名,**不接受外来串**)。
|
|
107
107
|
*/
|
|
108
108
|
export declare function originMarkedSqlPredicate(column: string): string;
|
|
109
|
-
/**
|
|
110
|
-
|
|
109
|
+
/**
|
|
110
|
+
* 本批次的「已提交 frontmatter」基线:id → 本批**第一次**触碰它时库里那一份(无行 ⇒ `undefined`)。
|
|
111
|
+
*
|
|
112
|
+
* #337(core 5.55.0 conformance c35-c40,design/339)从「只记 origin」升为记**整份** committed
|
|
113
|
+
* frontmatter:origin 不可变律与 distilled 不可变律读的是同一份基线,将来第三个不可变 frontmatter
|
|
114
|
+
* 字段进契约时零基线改动 —— 两只平行 Map 就是 F1a(姊妹腿)的温床,一只忘了在 delete 腿记账,
|
|
115
|
+
* 那一律的「同批 delete+re-add 拒」就静默失效。
|
|
116
|
+
*/
|
|
117
|
+
export type BatchCommittedBaseline = Map<string, MemoryEntryFrontmatter | undefined>;
|
|
111
118
|
/** 新建一只批次基线(每次 `applyPatches` 一只;跨批次**不得**复用 —— 已提交的墓碑正是靠「换批清零」放行)。 */
|
|
112
|
-
export declare function
|
|
119
|
+
export declare function createBatchCommittedBaseline(): BatchCommittedBaseline;
|
|
113
120
|
/**
|
|
114
|
-
* 记下(或取回)某 id 在**本批开始时**的已提交
|
|
121
|
+
* 记下(或取回)某 id 在**本批开始时**的已提交 frontmatter。
|
|
115
122
|
*
|
|
116
123
|
* `committedFm` = 这一次 I/O 真的从库里读到的已提交 frontmatter(没有行就传 `undefined`)。
|
|
117
124
|
* **首次触碰写入,之后只读** —— 同批里后面的 op 看到的必须还是批开始那一份,否则本批自己的写会把
|
|
118
125
|
* 基线推走(delete 之后 re-add 就又变成「库里没有 ⇒ 未标记」)。
|
|
119
126
|
*/
|
|
120
|
-
export declare function
|
|
127
|
+
export declare function rememberBatchCommitted(baseline: BatchCommittedBaseline, id: string, committedFm: MemoryEntryFrontmatter | undefined): MemoryEntryFrontmatter | undefined;
|
|
121
128
|
/**
|
|
122
129
|
* 未知 op 拼写的**前置**拒绝(codex R1-[high],验真后采纳;**既有**缺口,不是本批引入)。
|
|
123
130
|
*
|
|
@@ -134,5 +141,22 @@ export declare function rememberBatchOrigin(baseline: BatchOriginBaseline, id: s
|
|
|
134
141
|
* id-mismatch 拒绝同族文案,消费方按同一条正则分类)。
|
|
135
142
|
*/
|
|
136
143
|
export declare function unknownPatchOpRefusal(op: string): string | undefined;
|
|
137
|
-
export declare function originWhitewashRefusal(op: "add" | "update",
|
|
144
|
+
export declare function originWhitewashRefusal(op: "add" | "update", committedFm: MemoryEntryFrontmatter | undefined, next: MemoryEntryFrontmatter): string | undefined;
|
|
145
|
+
/**
|
|
146
|
+
* #337(core 5.55.0 conformance c36/c37,design/339)—— **distilled 血统不可变律**的判据,与 origin
|
|
147
|
+
* 律同族同基线同判位:一旦某 id 的已提交状态带 `frontmatter.distilled` 块(consolidation 蒸馏产物的
|
|
148
|
+
* 血统:planId/at/carrierRev/inputs),任何 update / 裸 add / guard add / 同批 delete+re-add 都必须把
|
|
149
|
+
* 它 DEEP-EQUAL 地带下去,否则拒 —— 合法出口只有已提交的墓碑(c36 逐字)。判序:**先于 rev CAS**
|
|
150
|
+
* (c37:骑着 stale baseRev 的 strip 也必须答 malformed refusal,不是 rev mismatch)。
|
|
151
|
+
* 判据用 core 导出的 {@link committedDistilledOf} / {@link distilledEquals},不手抄(F1b);文案与
|
|
152
|
+
* core File backend 两处逐字同形,契约按 `/malformed patch refused/` 匹配。
|
|
153
|
+
*/
|
|
154
|
+
export declare function distilledWhitewashRefusal(op: "add" | "update", committedFm: MemoryEntryFrontmatter | undefined, next: MemoryEntryFrontmatter): string | undefined;
|
|
155
|
+
/**
|
|
156
|
+
* #337(conformance c38)—— **distilled 事实的头面携带**,与 {@link exposureCarriage} 同住同因:
|
|
157
|
+
* `listHeaders` 与 `search` 两个头面都携带 `{ carrierRev, supersedes }` 投影(supersedes = inputs 中
|
|
158
|
+
* `superseded === true` 的行,形状与 core File backend 的 header 投影逐字同);无块条目两面都不携带。
|
|
159
|
+
* 蒸馏产物的「取代了哪些输入行」必须在检索面可见,否则被取代的旧行与蒸馏产物在读侧无从判序。
|
|
160
|
+
*/
|
|
161
|
+
export declare function distilledCarriage(fm: MemoryEntryFrontmatter): Pick<MemoryEntryHeader, "distilled">;
|
|
138
162
|
//# sourceMappingURL=memory-origin-law.d.ts.map
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* 之后同批的任何 op 都按那一份判。⇒「同批 delete+re-add 拒 / 已提交墓碑后 add 放」这条分界
|
|
37
37
|
* (core 契约逐字要求的那条)才成立。
|
|
38
38
|
*/
|
|
39
|
-
import { ambiguousOriginRepresentation, committedOriginOf, originEquals } from "@sema-agent/core";
|
|
39
|
+
import { ambiguousOriginRepresentation, committedDistilledOf, committedOriginOf, distilledEquals, originEquals } from "@sema-agent/core";
|
|
40
40
|
/**
|
|
41
41
|
* #319 增量(sema-comms [4768] 勘误 + core 仓 `1a5ebaa1`,conformance 30→31)—— **歧义 origin 表示**
|
|
42
42
|
* 的拒绝门,与不可变律同批、同一个写路径上。
|
|
@@ -155,22 +155,21 @@ export function originMarkedSqlPredicate(column) {
|
|
|
155
155
|
`WHERE origin_line.line ~ '${ORIGIN_CARRIER_LINE_SQL_REGEX}')))`);
|
|
156
156
|
}
|
|
157
157
|
/** 新建一只批次基线(每次 `applyPatches` 一只;跨批次**不得**复用 —— 已提交的墓碑正是靠「换批清零」放行)。 */
|
|
158
|
-
export function
|
|
158
|
+
export function createBatchCommittedBaseline() {
|
|
159
159
|
return new Map();
|
|
160
160
|
}
|
|
161
161
|
/**
|
|
162
|
-
* 记下(或取回)某 id 在**本批开始时**的已提交
|
|
162
|
+
* 记下(或取回)某 id 在**本批开始时**的已提交 frontmatter。
|
|
163
163
|
*
|
|
164
164
|
* `committedFm` = 这一次 I/O 真的从库里读到的已提交 frontmatter(没有行就传 `undefined`)。
|
|
165
165
|
* **首次触碰写入,之后只读** —— 同批里后面的 op 看到的必须还是批开始那一份,否则本批自己的写会把
|
|
166
166
|
* 基线推走(delete 之后 re-add 就又变成「库里没有 ⇒ 未标记」)。
|
|
167
167
|
*/
|
|
168
|
-
export function
|
|
168
|
+
export function rememberBatchCommitted(baseline, id, committedFm) {
|
|
169
169
|
if (baseline.has(id))
|
|
170
170
|
return baseline.get(id);
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
return origin;
|
|
171
|
+
baseline.set(id, committedFm);
|
|
172
|
+
return committedFm;
|
|
174
173
|
}
|
|
175
174
|
/**
|
|
176
175
|
* 不可变律的判据。返回 `undefined` = 放行;返回字符串 = 该字符串就是 `PatchReport.conflicts[].reason`。
|
|
@@ -205,11 +204,46 @@ export function unknownPatchOpRefusal(op) {
|
|
|
205
204
|
return undefined;
|
|
206
205
|
return `unknown patch op ${JSON.stringify(op)} (expected add | update | delete — malformed patch refused)`;
|
|
207
206
|
}
|
|
208
|
-
export function originWhitewashRefusal(op,
|
|
207
|
+
export function originWhitewashRefusal(op, committedFm, next) {
|
|
208
|
+
const committed = committedFm !== undefined ? committedOriginOf(committedFm) : undefined;
|
|
209
209
|
if (committed === undefined)
|
|
210
210
|
return undefined;
|
|
211
211
|
if (originEquals(committed, committedOriginOf(next)))
|
|
212
212
|
return undefined;
|
|
213
213
|
return `external-origin marker whitewash refused: the ${op} strips or rewrites the origin marker of a marked entry (malformed patch refused)`;
|
|
214
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* #337(core 5.55.0 conformance c36/c37,design/339)—— **distilled 血统不可变律**的判据,与 origin
|
|
217
|
+
* 律同族同基线同判位:一旦某 id 的已提交状态带 `frontmatter.distilled` 块(consolidation 蒸馏产物的
|
|
218
|
+
* 血统:planId/at/carrierRev/inputs),任何 update / 裸 add / guard add / 同批 delete+re-add 都必须把
|
|
219
|
+
* 它 DEEP-EQUAL 地带下去,否则拒 —— 合法出口只有已提交的墓碑(c36 逐字)。判序:**先于 rev CAS**
|
|
220
|
+
* (c37:骑着 stale baseRev 的 strip 也必须答 malformed refusal,不是 rev mismatch)。
|
|
221
|
+
* 判据用 core 导出的 {@link committedDistilledOf} / {@link distilledEquals},不手抄(F1b);文案与
|
|
222
|
+
* core File backend 两处逐字同形,契约按 `/malformed patch refused/` 匹配。
|
|
223
|
+
*/
|
|
224
|
+
export function distilledWhitewashRefusal(op, committedFm, next) {
|
|
225
|
+
const committed = committedFm !== undefined ? committedDistilledOf(committedFm) : undefined;
|
|
226
|
+
if (committed === undefined)
|
|
227
|
+
return undefined;
|
|
228
|
+
if (distilledEquals(committed, committedDistilledOf(next)))
|
|
229
|
+
return undefined;
|
|
230
|
+
return `distilled lineage whitewash refused: the ${op} strips or rewrites the distilled block of a consolidation product (malformed patch refused)`;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* #337(conformance c38)—— **distilled 事实的头面携带**,与 {@link exposureCarriage} 同住同因:
|
|
234
|
+
* `listHeaders` 与 `search` 两个头面都携带 `{ carrierRev, supersedes }` 投影(supersedes = inputs 中
|
|
235
|
+
* `superseded === true` 的行,形状与 core File backend 的 header 投影逐字同);无块条目两面都不携带。
|
|
236
|
+
* 蒸馏产物的「取代了哪些输入行」必须在检索面可见,否则被取代的旧行与蒸馏产物在读侧无从判序。
|
|
237
|
+
*/
|
|
238
|
+
export function distilledCarriage(fm) {
|
|
239
|
+
const d = fm.distilled;
|
|
240
|
+
if (d === undefined)
|
|
241
|
+
return {};
|
|
242
|
+
return {
|
|
243
|
+
distilled: {
|
|
244
|
+
carrierRev: d.carrierRev,
|
|
245
|
+
supersedes: d.inputs.filter((i) => i.superseded === true).map((i) => ({ id: i.id, rev: i.rev })),
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
215
249
|
//# sourceMappingURL=memory-origin-law.js.map
|
|
@@ -57,10 +57,14 @@ export function drainNumEnvWarnings() {
|
|
|
57
57
|
export function numEnvOr(name, def, min) {
|
|
58
58
|
const raw = process.env[name] ?? "";
|
|
59
59
|
const n = Math.floor(Number(raw));
|
|
60
|
-
|
|
60
|
+
// A-067 C1-1(F2a):判据必须是 isFinite,不是 isNaN——`"Infinity"`/`"1e999"` 穿过 NaN 检查后
|
|
61
|
+
// `n || def` 取 Infinity、`Math.max(min, Infinity)` 恒 Infinity ⇒ 本函数独供的 17 个执行 lane
|
|
62
|
+
// 旋钮里,E2B_SANDBOX_MAX_MS 的消费点 `Math.min(callerDeadline, cap)`(BL-31 防挂钳)被整条击穿。
|
|
63
|
+
// 非有限=非法字面量同待遇:警告 + 落默认。
|
|
64
|
+
if (raw !== "" && !Number.isFinite(n) && !NUM_ENV_SEEN.has(name)) {
|
|
61
65
|
NUM_ENV_SEEN.add(name); // 首登记守卫(boolEnv 同款):懒/重复读不无界增长
|
|
62
66
|
NUM_ENV_WARNINGS.push({ env: name, raw });
|
|
63
67
|
}
|
|
64
|
-
return Math.max(min, n || def);
|
|
68
|
+
return Math.max(min, (Number.isFinite(n) ? n : 0) || def);
|
|
65
69
|
}
|
|
66
70
|
//# sourceMappingURL=remote-shell.js.map
|
package/dist/task-settings.d.ts
CHANGED
|
@@ -37,8 +37,11 @@
|
|
|
37
37
|
*/
|
|
38
38
|
import { type ExecutionEnv, type TaskSpec, type ThinkingLevel, type ToolPolicy, type PromptProvider } from "@sema-agent/core";
|
|
39
39
|
import type { HooksConfig } from "@sema-agent/registry-core/hooks";
|
|
40
|
-
/** The permission MODE a client may request (
|
|
41
|
-
* table.
|
|
40
|
+
/** The permission MODE a client may request (SDK `SettingsPermissions.defaultMode`), the [820]/[822] five-word
|
|
41
|
+
* table. ⚠️ This is a DELIBERATE deployment set, NOT a copy of any CC set (A-065 P2-1 corpus check): CC's own
|
|
42
|
+
* full enum is SIX words (= ours + `dontAsk`), CC's managed/remote form accepts FOUR (= ours − `bypassPermissions`,
|
|
43
|
+
* which it warns-and-ignores). Ours = {CC-remote four} ∪ {`bypassPermissions`} — registered divergence, not parity.
|
|
44
|
+
* `bypassPermissions` and `auto` joined the set with the [816] fs-write gate: since the gate exists, every
|
|
42
45
|
* mode is a choice of HOW MUCH mode-derived gating the interpretation layer ADDS on top of the deployment baseline —
|
|
43
46
|
* `bypassPermissions` adds none (= the pre-[816] behavior of every mode: "不加门" not "开门", [820] table row 4; the
|
|
44
47
|
* deployment approval/governance baseline is composed OUTSIDE this derive and is untouchable from a settings stamp),
|
|
@@ -50,8 +53,10 @@ export type SettingsPermissionMode = "default" | "acceptEdits" | "plan" | "bypas
|
|
|
50
53
|
* CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
|
|
51
54
|
* 🔴 TIGHTEN-ONLY vs the DEPLOYMENT baseline still holds for every value: the mode only selects the CLIENT-derived
|
|
52
55
|
* gate ({@link deriveSettingsPolicy}); it can never subtract from the deployment/operator policy (applyTaskSettings
|
|
53
|
-
* folds via tightenTaskSpec, deny-wins). Post-[816] the
|
|
54
|
-
*
|
|
56
|
+
* folds via tightenTaskSpec, deny-wins). Post-[816] the honored set is the five-word table above (NOT "CC verbatim" —
|
|
57
|
+
* see the {@link SettingsPermissionMode} header for the three-set contrast; the earlier "five CC modes" wording here
|
|
58
|
+
* was A-065 P2-1's finding: no CC set has five members). `acceptEdits`,
|
|
59
|
+
* `bypassPermissions` and `auto` are HONORED as gate-shape choices (they no longer coerce to `default`, which
|
|
55
60
|
* would FORCE the manual ask gate onto a caller that explicitly asked for less asking — with the gate live, the old
|
|
56
61
|
* coercion would have been a behavior change for them, incl. a headless-deny regression for bypass callers). An
|
|
57
62
|
* UNKNOWN string still coerces to `default` (fail-safe: the most-asking mode).
|
package/dist/task-settings.js
CHANGED
|
@@ -43,8 +43,10 @@ import { parseHooksConfig } from "./hooks/hook-runner.js";
|
|
|
43
43
|
* CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
|
|
44
44
|
* 🔴 TIGHTEN-ONLY vs the DEPLOYMENT baseline still holds for every value: the mode only selects the CLIENT-derived
|
|
45
45
|
* gate ({@link deriveSettingsPolicy}); it can never subtract from the deployment/operator policy (applyTaskSettings
|
|
46
|
-
* folds via tightenTaskSpec, deny-wins). Post-[816] the
|
|
47
|
-
*
|
|
46
|
+
* folds via tightenTaskSpec, deny-wins). Post-[816] the honored set is the five-word table above (NOT "CC verbatim" —
|
|
47
|
+
* see the {@link SettingsPermissionMode} header for the three-set contrast; the earlier "five CC modes" wording here
|
|
48
|
+
* was A-065 P2-1's finding: no CC set has five members). `acceptEdits`,
|
|
49
|
+
* `bypassPermissions` and `auto` are HONORED as gate-shape choices (they no longer coerce to `default`, which
|
|
48
50
|
* would FORCE the manual ask gate onto a caller that explicitly asked for less asking — with the gate live, the old
|
|
49
51
|
* coercion would have been a behavior change for them, incl. a headless-deny regression for bypass callers). An
|
|
50
52
|
* UNKNOWN string still coerces to `default` (fail-safe: the most-asking mode).
|
|
@@ -57,7 +59,7 @@ export function coercePermissionMode(raw) {
|
|
|
57
59
|
if (raw === "plan" || raw === "acceptEdits" || raw === "default" || raw === "bypassPermissions" || raw === "auto")
|
|
58
60
|
return raw;
|
|
59
61
|
if (typeof raw === "string" && raw.length > 0)
|
|
60
|
-
return "default"; // unknown → default (
|
|
62
|
+
return "default"; // unknown → default (most-asking of OUR five; A-065 P3-1: for CC's `dontAsk` (=auto-deny) this fold IS a widening by CC semantics — reachable only via resume replay, fresh submits 400 at the HTTP gate)
|
|
61
63
|
return undefined; // absent → no opinion (don't touch the spec)
|
|
62
64
|
}
|
|
63
65
|
/** L2 ultracode (design/111): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
|
|
@@ -207,7 +209,10 @@ export function parseTaskSettings(raw) {
|
|
|
207
209
|
const allow = cleanStringArray(p.allow);
|
|
208
210
|
const deny = cleanStringArray(p.deny);
|
|
209
211
|
const ask = cleanStringArray(p.ask);
|
|
210
|
-
// 🔒 whitelist the five
|
|
212
|
+
// 🔒 whitelist the five honored modes (OUR set, not a CC set — see SettingsPermissionMode header / A-065 P2-1);
|
|
213
|
+
// any other value is dropped (absent = no opinion). This drop arm is the RESUME-replay lenient layer — fresh
|
|
214
|
+
// submits already 400 at the HTTP gate (server.ts #157-③, [2766] ruling), same two-layer posture as the
|
|
215
|
+
// top-level permissionMode leg. `bypassPermissions` is
|
|
211
216
|
// accepted post-[816] because it now means "do not ADD the mode-derived fs-write ask gate" — exactly every
|
|
212
217
|
// mode's pre-[816] behavior — and can never subtract from the deployment baseline (tightenTaskSpec deny-wins;
|
|
213
218
|
// the module-header §3.3 invariant is thereby REFINED, not weakened: bypass still cannot LOOSEN anything).
|
package/dist/tool-approval.d.ts
CHANGED
|
@@ -243,6 +243,23 @@ export interface ToolApprovalFrame {
|
|
|
243
243
|
outcome?: "allowed" | "denied" | "expired";
|
|
244
244
|
}
|
|
245
245
|
/** The per-run context `ask` recovers via ALS (mirrors QuestionRunContext + sessionId, which keys the allow-all). */
|
|
246
|
+
/** #315:`GET /v1/approvals` 第二顶层键 `livePending` 的行形(与 durable `pending` 行**分数组不混编**
|
|
247
|
+
* ——两族行形与决议路由不同:本族行的决议口=`POST /v1/tool-approvals/:approvalId/respond`,durable 行
|
|
248
|
+
* =decide 口;数组名即路由判据)。可选位全部「只记真、缺席不编」。 */
|
|
249
|
+
export interface LivePendingRow {
|
|
250
|
+
/** = `respond()` 收的 wire id(pending Map 的 uuidv7 键;同源可达)。 */
|
|
251
|
+
approvalId: string;
|
|
252
|
+
toolName: string;
|
|
253
|
+
/** 登记时刻(ms epoch)。 */
|
|
254
|
+
ts: number;
|
|
255
|
+
/** 窗绝对死线——与 `tool_approval` 帧 #288 三键同一次铸定的那个数。 */
|
|
256
|
+
expiresAtMs: number;
|
|
257
|
+
sessionId?: string;
|
|
258
|
+
requiresRealApproval?: true;
|
|
259
|
+
governanceForced?: true;
|
|
260
|
+
/** 发起者为委派子代(originTaskId 在场)。 */
|
|
261
|
+
fromSubagent?: true;
|
|
262
|
+
}
|
|
246
263
|
export interface ToolApprovalRunContext {
|
|
247
264
|
taskId: string;
|
|
248
265
|
sessionId?: string;
|
|
@@ -903,6 +920,13 @@ export declare class ToolApprovalCoordinator {
|
|
|
903
920
|
/** 回决收尾(D1 现行为逐字不变的那一半)——session allow-all 记账 + settle + 200 响应体。原 `respond()`
|
|
904
921
|
* 方法体的逐字搬运(#151 车2 拆分,行为零改动)。 */
|
|
905
922
|
private finishRespond;
|
|
923
|
+
/** #315([4658] 案):live pending 的**列表读面**——`GET /v1/approvals` 响应第二顶层键 `livePending`
|
|
924
|
+
* 的唯一数据源。纯读投影,零锁/零结算语义;行键集刻意窄(**input/args 不上列表**,不开第二个脱敏
|
|
925
|
+
* 面;详情走流帧)。`scope` 与 durable 面同一表达式:`undefined` = operator 全量(含 owner null 的
|
|
926
|
+
* 条目),串 = 只见 `owner === scope` 的条目(null-owner 行对 principal 不可见,fail-closed——
|
|
927
|
+
* 宁少列不越权;`"__none__"` 未鉴权哨兵自然恒空)。行的 `approvalId` 就是 `respond()` 收的 id
|
|
928
|
+
* (同源可达,A-058 判据);`expiresAtMs` 照抄登记时单点铸定的窗死线(#288 三键同一个数)。 */
|
|
929
|
+
listLivePending(scope: string | undefined): LivePendingRow[];
|
|
906
930
|
/** Test/observability hooks. */
|
|
907
931
|
pendingCount(): number;
|
|
908
932
|
sessionAllowedCount(): number;
|
package/dist/tool-approval.js
CHANGED
|
@@ -1910,6 +1910,9 @@ export class ToolApprovalCoordinator {
|
|
|
1910
1910
|
...(child ? { originTaskId: req.sourceTaskId } : { targetCtxs: new Set(aliveCtxs), onTargetGone }),
|
|
1911
1911
|
...(primary.sessionId ? { sessionId: primary.sessionId } : {}),
|
|
1912
1912
|
toolName: req.toolName,
|
|
1913
|
+
// #315:live pending 列表的两只时间键——expiresAtMs 照抄 :1823 的单点铸定值(不重算)。
|
|
1914
|
+
registeredAtMs: Date.now(),
|
|
1915
|
+
expiresAtMs,
|
|
1913
1916
|
// #154 车二:规则车道素材 —— 与帧上的 `ruleSuggestions` **同一个条件**(店在场 ∧ 非治理档 ∧ 引擎真
|
|
1914
1917
|
// 铸了候选 ∧ 命令原字节读得出)。任一不成立 ⇒ 不登记,回决带 `persistRule` 时如实拒(不猜命令)。
|
|
1915
1918
|
...(ruleLaneMaterial !== undefined ? { ruleLane: ruleLaneMaterial } : {}),
|
|
@@ -2637,6 +2640,30 @@ export class ToolApprovalCoordinator {
|
|
|
2637
2640
|
},
|
|
2638
2641
|
};
|
|
2639
2642
|
}
|
|
2643
|
+
/** #315([4658] 案):live pending 的**列表读面**——`GET /v1/approvals` 响应第二顶层键 `livePending`
|
|
2644
|
+
* 的唯一数据源。纯读投影,零锁/零结算语义;行键集刻意窄(**input/args 不上列表**,不开第二个脱敏
|
|
2645
|
+
* 面;详情走流帧)。`scope` 与 durable 面同一表达式:`undefined` = operator 全量(含 owner null 的
|
|
2646
|
+
* 条目),串 = 只见 `owner === scope` 的条目(null-owner 行对 principal 不可见,fail-closed——
|
|
2647
|
+
* 宁少列不越权;`"__none__"` 未鉴权哨兵自然恒空)。行的 `approvalId` 就是 `respond()` 收的 id
|
|
2648
|
+
* (同源可达,A-058 判据);`expiresAtMs` 照抄登记时单点铸定的窗死线(#288 三键同一个数)。 */
|
|
2649
|
+
listLivePending(scope) {
|
|
2650
|
+
const rows = [];
|
|
2651
|
+
for (const [id, e] of this.pending) {
|
|
2652
|
+
if (scope !== undefined && e.owner !== scope)
|
|
2653
|
+
continue;
|
|
2654
|
+
rows.push({
|
|
2655
|
+
approvalId: id,
|
|
2656
|
+
toolName: e.toolName,
|
|
2657
|
+
ts: e.registeredAtMs,
|
|
2658
|
+
expiresAtMs: e.expiresAtMs,
|
|
2659
|
+
...(e.sessionId !== undefined ? { sessionId: e.sessionId } : {}),
|
|
2660
|
+
...(e.requiresRealApproval ? { requiresRealApproval: true } : {}),
|
|
2661
|
+
...(e.governanceForced ? { governanceForced: true } : {}),
|
|
2662
|
+
...(e.originTaskId !== undefined ? { fromSubagent: true } : {}),
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
return rows;
|
|
2666
|
+
}
|
|
2640
2667
|
/** Test/observability hooks. */
|
|
2641
2668
|
pendingCount() {
|
|
2642
2669
|
return this.pending.size;
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ export type TraceBlock = {
|
|
|
97
97
|
settledBy?: ApprovalSettledBy;
|
|
98
98
|
resolution?: AskDenyResolution;
|
|
99
99
|
approver?: string;
|
|
100
|
+
gatedCallId?: string;
|
|
100
101
|
eventId?: string;
|
|
101
102
|
parentToolCallId?: string;
|
|
102
103
|
} | {
|
|
@@ -141,6 +142,7 @@ export declare function toolEndEventData(ev: {
|
|
|
141
142
|
settledBy?: ApprovalSettledBy;
|
|
142
143
|
resolution?: AskDenyResolution;
|
|
143
144
|
approver?: string;
|
|
145
|
+
gatedCallId?: string;
|
|
144
146
|
eventId?: string;
|
|
145
147
|
parentToolCallId?: string;
|
|
146
148
|
}): Record<string, unknown>;
|
package/dist/trace/project.js
CHANGED
|
@@ -193,6 +193,12 @@ export function toolEndEventData(ev) {
|
|
|
193
193
|
// ③ 值是**宿主派生的身份串**(login/email/账号 id),照 humanInputEventData 的 issuer/actor.id
|
|
194
194
|
// 先例过 redactSecrets(对正常标识恒等,对密钥形打码;core 已拒控制符/超长,无需重复筛)。
|
|
195
195
|
...(typeof ev.approver === "string" && ev.approver !== "" ? { approver: redactSecrets(ev.approver) } : {}),
|
|
196
|
+
// core 5.55.0 #333/[4918](#337 提货):`gatedCallId` = durable park 正扣着**哪个** tool call 的 id——
|
|
197
|
+
// core runtask 铸点从 committed checkpoint 的 pendingAction 直读(**禁由工具自报**:core 契约原话,
|
|
198
|
+
// 能自报的 id 是对「另一个 call」的断言,工具能写它就能把审批 UI 指向一个没人等的 call),只挂在
|
|
199
|
+
// park 污染帧上。id 串非用户内容不脱敏;缺席=park 不扣 call(resource_limit/plan_review 形挂的
|
|
200
|
+
// pendingAction 没有 tool call),core 契约「ABSENT, never guessed」——缺席不铸键。
|
|
201
|
+
...(typeof ev.gatedCallId === "string" && ev.gatedCallId !== "" ? { gatedCallId: ev.gatedCallId } : {}),
|
|
196
202
|
...identityFields(ev),
|
|
197
203
|
};
|
|
198
204
|
}
|
|
@@ -764,6 +770,12 @@ export function toolResultFieldsOf(d) {
|
|
|
764
770
|
// core 5.35.0 G-7(#263):结算归属(settledBy 旁的 WHOSE)——写侧已 redactSecrets 后落账,读腿
|
|
765
771
|
// 同键过境;判形独立一道(同上款理由),开集身份串故只判 string 非空,不设词表。
|
|
766
772
|
...(typeof d.approver === "string" && d.approver !== "" ? { approver: d.approver } : {}),
|
|
773
|
+
// 🔴 #337 红先补(A-067 F1a 现行案):`resolution` #326 提货时写侧落账、TraceBlock 留了槽位,本
|
|
774
|
+
// 共享挑键漏挑 ⇒ live 有、turns/trace 冷回放丢——[1622]/label 剥键族在「单点挑键」建立之后的复发。
|
|
775
|
+
// 读腿闭集门独立一道(账本行可来自任何世代/第三方 producer),判据同 core 谓词。
|
|
776
|
+
...(isAskDenyResolution(d.resolution) ? { resolution: d.resolution } : {}),
|
|
777
|
+
// core 5.55.0 #333(#337 提货):park 扣着的 call id——写侧已判形落账,读腿独立同判(string 非空)。
|
|
778
|
+
...(typeof d.gatedCallId === "string" && d.gatedCallId !== "" ? { gatedCallId: d.gatedCallId } : {}),
|
|
767
779
|
...identityFields(d),
|
|
768
780
|
};
|
|
769
781
|
}
|
|
@@ -798,7 +810,6 @@ export function projectEvents(events, opts = {}) {
|
|
|
798
810
|
blocks.push({ type: "tool-call", ...toolCallFieldsOf(d) });
|
|
799
811
|
break;
|
|
800
812
|
case "tool_end": {
|
|
801
|
-
const callId = String(d.toolCallId ?? "");
|
|
802
813
|
// E1: output is read straight off the (already-redacted-at-append) event — no session join. Absent ⇒ undefined.
|
|
803
814
|
// `structured` (core 1.203 CC card) was redacted at APPEND (toolEndEventData) — passthrough on the read
|
|
804
815
|
// path so a cold-replay/turns consumer renders the same rich card the live wire carried (additive field).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.43.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,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.55.0",
|
|
58
58
|
"@sema-agent/registry-core": "^0.19.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|