@sema-agent/server 7.20.0 → 7.22.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/USAGE.md +31 -3
- package/dist/adoption/runner.js +4 -5
- package/dist/approval-card.d.ts +52 -0
- package/dist/approval-card.js +108 -0
- package/dist/boot/governance-seams.d.ts +112 -0
- package/dist/boot/governance-seams.js +151 -0
- package/dist/boot/leader.d.ts +3 -0
- package/dist/boot/leader.js +7 -1
- package/dist/boot/resolve-spec.d.ts +3 -0
- package/dist/boot/resolve-spec.js +10 -2
- package/dist/boot/runner-deps.d.ts +6 -2
- package/dist/boot/runner-deps.js +13 -0
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +24 -5
- package/dist/config-types.d.ts +50 -4
- package/dist/config.js +129 -3
- package/dist/fleet/fleet-bus.d.ts +23 -2
- package/dist/fleet/fleet-bus.js +41 -2
- package/dist/fleet/subagent-tail-bus.d.ts +59 -0
- package/dist/fleet/subagent-tail-bus.js +60 -0
- package/dist/http/routes/capabilities.js +27 -0
- package/dist/http/routes/diagnostics.js +6 -0
- package/dist/http/routes/runs.js +10 -1
- package/dist/http/routes/tasks.js +15 -4
- package/dist/http/server.d.ts +5 -0
- package/dist/http/server.js +130 -7
- package/dist/leader/wire.d.ts +7 -1
- package/dist/leader/wire.js +12 -5
- package/dist/main.js +33 -8
- package/dist/memory-posture.d.ts +80 -0
- package/dist/memory-posture.js +40 -0
- package/dist/observability/fail-open.d.ts +4 -0
- package/dist/observability/fail-open.js +4 -0
- package/dist/orchestration/workflow-completion-inbox.d.ts +2 -1
- package/dist/parked-decide.js +9 -0
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -1
- package/dist/plugins/approval-ask-store-sql.js +5 -6
- package/dist/plugins/background-agent-store-sql.js +4 -1
- package/dist/plugins/checkpoint-store-sql.d.ts +2 -0
- package/dist/plugins/checkpoint-store-sql.js +4 -5
- package/dist/plugins/file-run-store.d.ts +2 -1
- package/dist/plugins/image-bake-store-sql.js +5 -5
- package/dist/plugins/memory-embedder-fingerprint.d.ts +23 -0
- package/dist/plugins/memory-embedder-fingerprint.js +28 -1
- package/dist/plugins/memory-engine-tidb.js +3 -5
- package/dist/plugins/memory-engine-vector-util.d.ts +0 -5
- package/dist/plugins/memory-engine-vector-util.js +10 -4
- package/dist/plugins/memory-run-store.d.ts +2 -1
- package/dist/plugins/pg-session-storage.js +3 -3
- package/dist/plugins/run-store-sql.d.ts +12 -3
- package/dist/plugins/run-store-sql.js +4 -3
- package/dist/plugins/session-policy-store-sql.d.ts +1 -0
- package/dist/plugins/session-policy-store-sql.js +3 -3
- package/dist/plugins/shared-memory-store-sql.js +4 -10
- package/dist/plugins/sql-driver.d.ts +7 -5
- package/dist/plugins/sql-errors.d.ts +41 -0
- package/dist/plugins/sql-errors.js +25 -0
- package/dist/plugins/tidb-session-storage.js +3 -3
- package/dist/plugins/workflow-run-store-sql.js +4 -4
- package/dist/run-local.js +15 -0
- package/dist/runs.js +106 -38
- package/dist/task-mcp.d.ts +22 -1
- package/dist/task-mcp.js +71 -1
- package/dist/tool-approval.d.ts +35 -0
- package/dist/tool-approval.js +4 -1
- package/dist/trace/core-keyset-guard.d.ts +2 -2
- package/dist/trace/ledger-events.d.ts +43 -0
- package/dist/trace/ledger-events.js +2 -0
- package/dist/trace/ledger-sink.d.ts +21 -2
- package/dist/trace/ledger-sink.js +5 -1
- package/dist/trace/project.d.ts +4 -1
- package/dist/trace/project.js +15 -0
- package/package.json +2 -2
|
@@ -11,8 +11,67 @@ export type SubagentTailFrame = Record<string, unknown> & {
|
|
|
11
11
|
export declare function projectTailFrame(e: Record<string, unknown> & {
|
|
12
12
|
type?: string;
|
|
13
13
|
}): SubagentTailFrame | null;
|
|
14
|
+
/**
|
|
15
|
+
* 宿主腿的 **content 供给形**(#252 / [3911] 定谳)——本面能不能拿到内容帧,由**宿主 run 的 spec**
|
|
16
|
+
* `forwardSubagentEvents` 决定,而不是由订阅者决定:
|
|
17
|
+
*
|
|
18
|
+
* · `"on"` = 宿主 run 提交时带了 `forwardSubagentEvents:true` ⇒ core 的 forward 门
|
|
19
|
+
* (`prepare-task` 的 `spec.forwardSubagentEvents === true` 分支)放行子代的
|
|
20
|
+
* text_delta/reasoning_delta/tool_start/tool_end ⇒ 本面有内容帧;
|
|
21
|
+
* · `"progress_only"` = 宿主 run 没带 ⇒ 那道门**只**放 task_progress ⇒ 本面恒只有 rollup 帧。
|
|
22
|
+
*
|
|
23
|
+
* 🔴 为什么必须上 wire(#157「不许静默 fail-open」在本接缝的落实):`progress_only` 形下本端点
|
|
24
|
+
* **一切正常**——200、meta 说 running、心跳照发、task_progress 照来——就是永远没有内容帧。消费端
|
|
25
|
+
* (cli engineSubagentTail 明文「task_progress 不重复消费」)看到的是一个永远空的转录面,与「子代此刻
|
|
26
|
+
* 恰好没说话」在 wire 上**不可区分**;而订阅方在查看态**没有任何办法**把它打开(那是 run 提交时的
|
|
27
|
+
* 一次性表态)。[3911] cli 一手取证的指纹(建连成功 / 75s 零内容帧 / 同期 fleet token 在涨)与本形
|
|
28
|
+
* 逐条吻合。所以 meta 帧如实声明供给形,让「空」有诚实的来路。
|
|
29
|
+
*
|
|
30
|
+
* 缺席(`undefined` ⇒ meta 出 `"unknown"`)= **还不知道**:子代还没产出第一帧,或宿主腿在别的副本
|
|
31
|
+
* (跨实例 serve 的 durable 行)——不猜、不折默认值。
|
|
32
|
+
*/
|
|
33
|
+
export type TailContentMode = "on" | "progress_only";
|
|
14
34
|
export declare class SubagentTailBus {
|
|
15
35
|
private readonly subs;
|
|
36
|
+
/**
|
|
37
|
+
* a\* 句柄 → 该句柄**自己的宿主腿**的供给形。
|
|
38
|
+
*
|
|
39
|
+
* 🔴 键必须是**句柄**,不是「查看时用的那条 run」(codex 复审 high,2026-08-14 采纳):
|
|
40
|
+
* session-scoped 子代(`backgroundScope:"session"`)活得比派它的那条 run 久,而订阅方寻址用的是
|
|
41
|
+
* 「派它的 run ?? 当前活跃 run」——同一会话里 A 腿(带 forwardSubagentEvents)派的子代,完全可能
|
|
42
|
+
* 被经**后起的 B 腿**(没带)去看。按 run/会话键答题在这一档会**确定性谎报**(A=on 被答成
|
|
43
|
+
* progress_only ⇒ 客户端提前放弃真内容;反向 ⇒ 永久空等)。按句柄登记则天然正确:登记发生在
|
|
44
|
+
* **产出这一帧的那条闭包**里,帧上的 bgAgentId 与它的宿主腿是同一件事,不经任何寻址推断。
|
|
45
|
+
*
|
|
46
|
+
* 时间轴与降级:子代产出第一帧之前 = 无登记 ⇒ 路由出 `"unknown"`(诚实:还不知道);宿主腿在别的
|
|
47
|
+
* 副本 ⇒ 本副本永远收不到它的帧 ⇒ 同样 `"unknown"`(而不是拿本地同会话的无关腿冒充);容量逐出
|
|
48
|
+
* 同理只会把 `"on"/"progress_only"` 退成 `"unknown"`,不会退成错答案。
|
|
49
|
+
* ⚠️ `progress_only` 形下 task_progress **照来**,所以那一档同样在子代首拍 tick 就登记上了——
|
|
50
|
+
* 不需要"先有内容帧"才能学到"没有内容帧"。
|
|
51
|
+
*/
|
|
52
|
+
private readonly modeByHandle;
|
|
53
|
+
/** 三条 run 腿的 forward 回调里逐帧调(在 `hasSubscribers` 快门**之前**——没人在听时也要学,
|
|
54
|
+
* 否则晚开的 tail 永远读不到供给形)。同值重复写是 no-op(热路径只付一次 Map.get)。 */
|
|
55
|
+
noteHandleContentMode(bgAgentId: string, mode: TailContentMode): void;
|
|
56
|
+
/**
|
|
57
|
+
* 句柄终态时**撤销**登记(三条腿的 `onTaskNotification` 观察者调,与 `task_settled` 帧同处)。
|
|
58
|
+
*
|
|
59
|
+
* 🔴 为什么必须撤(codex 复审 round2 high,2026-08-14 采纳):后台子代可以用**原 a\* 句柄**进入下一个
|
|
60
|
+
* 复活周期(`POST /v1/runs/:id/subagents/:target/resume` → core `registerBackgroundAgent({ id:
|
|
61
|
+
* reviveRow.handle })`),而新周期的宿主腿是**另一条 run**,它的 forwardSubagentEvents 完全可能相反。
|
|
62
|
+
* 不撤的话,新周期首帧之前 meta 会把**上一周期**的供给形当成本周期的答案:上周期 on / 本周期
|
|
63
|
+
* progress_only ⇒ 客户端等一个永远不来的内容;反向 ⇒ 提前放弃真内容。撤到 `"unknown"` 是唯一诚实的
|
|
64
|
+
* 中间态(新周期首帧一到自然重新登记)。
|
|
65
|
+
* 非 a\* 句柄(bash / monitor 那些从未登记过的句柄)进来是 no-op。
|
|
66
|
+
*
|
|
67
|
+
* 残余(如实记,不假装没有):撤销之后若还有**同周期的迟到帧**进来,会按那条腿的模式重新登记一次
|
|
68
|
+
* ——值仍是「刚结束那个周期」的真话,只是万一紧接着发生复活、且新周期首帧未到,那一瞬读到的是旧周期
|
|
69
|
+
* 的答案。core 今天把终态通知排在最后一条 forward 之后(fleet-bus 同注),复活又是人手动作,这条窗
|
|
70
|
+
* 实际上不可达;真要关死需要给句柄加墓碑态,代价大于一位诊断标签的收益,故不做。
|
|
71
|
+
*/
|
|
72
|
+
forgetHandleContentMode(bgAgentId: string): void;
|
|
73
|
+
/** 路由读(按 tail 的 target 句柄)。缺席 = 不知道,路由出 `"unknown"`。 */
|
|
74
|
+
handleContentMode(bgAgentId: string): TailContentMode | undefined;
|
|
16
75
|
/** 两条 run 腿的 fwdInternals 调用点:帧已投影(projectTailFrame),null 不进来。 */
|
|
17
76
|
publish(bgAgentId: string, frame: SubagentTailFrame): void;
|
|
18
77
|
/** 有无订阅者(publish 侧的免投影快门:没人听就不投影)。 */
|
|
@@ -48,8 +48,68 @@ export function projectTailFrame(e) {
|
|
|
48
48
|
}
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
+
/** 有界 put(FIFO 逐最老)——`Map` 的迭代序即插入序,取首键即最老。 */
|
|
52
|
+
function putBounded(map, key, value) {
|
|
53
|
+
map.delete(key); // 重写=刷新它的插入序(值真变了的活句柄不该按最初那次登记的年龄被逐)
|
|
54
|
+
map.set(key, value);
|
|
55
|
+
if (map.size > HOST_MODE_CAP) {
|
|
56
|
+
const oldest = map.keys().next();
|
|
57
|
+
if (!oldest.done)
|
|
58
|
+
map.delete(oldest.value);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** 句柄→供给形登记的容量上限(FIFO 逐最老)。诊断料,不是账本——满了逐最老,读侧缺席退回 "unknown"
|
|
62
|
+
* (诚实降级:把"我知道"退成"我不知道",不会退成一个错答案)。 */
|
|
63
|
+
const HOST_MODE_CAP = 4096;
|
|
51
64
|
export class SubagentTailBus {
|
|
52
65
|
subs = new Map();
|
|
66
|
+
/**
|
|
67
|
+
* a\* 句柄 → 该句柄**自己的宿主腿**的供给形。
|
|
68
|
+
*
|
|
69
|
+
* 🔴 键必须是**句柄**,不是「查看时用的那条 run」(codex 复审 high,2026-08-14 采纳):
|
|
70
|
+
* session-scoped 子代(`backgroundScope:"session"`)活得比派它的那条 run 久,而订阅方寻址用的是
|
|
71
|
+
* 「派它的 run ?? 当前活跃 run」——同一会话里 A 腿(带 forwardSubagentEvents)派的子代,完全可能
|
|
72
|
+
* 被经**后起的 B 腿**(没带)去看。按 run/会话键答题在这一档会**确定性谎报**(A=on 被答成
|
|
73
|
+
* progress_only ⇒ 客户端提前放弃真内容;反向 ⇒ 永久空等)。按句柄登记则天然正确:登记发生在
|
|
74
|
+
* **产出这一帧的那条闭包**里,帧上的 bgAgentId 与它的宿主腿是同一件事,不经任何寻址推断。
|
|
75
|
+
*
|
|
76
|
+
* 时间轴与降级:子代产出第一帧之前 = 无登记 ⇒ 路由出 `"unknown"`(诚实:还不知道);宿主腿在别的
|
|
77
|
+
* 副本 ⇒ 本副本永远收不到它的帧 ⇒ 同样 `"unknown"`(而不是拿本地同会话的无关腿冒充);容量逐出
|
|
78
|
+
* 同理只会把 `"on"/"progress_only"` 退成 `"unknown"`,不会退成错答案。
|
|
79
|
+
* ⚠️ `progress_only` 形下 task_progress **照来**,所以那一档同样在子代首拍 tick 就登记上了——
|
|
80
|
+
* 不需要"先有内容帧"才能学到"没有内容帧"。
|
|
81
|
+
*/
|
|
82
|
+
modeByHandle = new Map();
|
|
83
|
+
/** 三条 run 腿的 forward 回调里逐帧调(在 `hasSubscribers` 快门**之前**——没人在听时也要学,
|
|
84
|
+
* 否则晚开的 tail 永远读不到供给形)。同值重复写是 no-op(热路径只付一次 Map.get)。 */
|
|
85
|
+
noteHandleContentMode(bgAgentId, mode) {
|
|
86
|
+
if (this.modeByHandle.get(bgAgentId) === mode)
|
|
87
|
+
return;
|
|
88
|
+
putBounded(this.modeByHandle, bgAgentId, mode);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* 句柄终态时**撤销**登记(三条腿的 `onTaskNotification` 观察者调,与 `task_settled` 帧同处)。
|
|
92
|
+
*
|
|
93
|
+
* 🔴 为什么必须撤(codex 复审 round2 high,2026-08-14 采纳):后台子代可以用**原 a\* 句柄**进入下一个
|
|
94
|
+
* 复活周期(`POST /v1/runs/:id/subagents/:target/resume` → core `registerBackgroundAgent({ id:
|
|
95
|
+
* reviveRow.handle })`),而新周期的宿主腿是**另一条 run**,它的 forwardSubagentEvents 完全可能相反。
|
|
96
|
+
* 不撤的话,新周期首帧之前 meta 会把**上一周期**的供给形当成本周期的答案:上周期 on / 本周期
|
|
97
|
+
* progress_only ⇒ 客户端等一个永远不来的内容;反向 ⇒ 提前放弃真内容。撤到 `"unknown"` 是唯一诚实的
|
|
98
|
+
* 中间态(新周期首帧一到自然重新登记)。
|
|
99
|
+
* 非 a\* 句柄(bash / monitor 那些从未登记过的句柄)进来是 no-op。
|
|
100
|
+
*
|
|
101
|
+
* 残余(如实记,不假装没有):撤销之后若还有**同周期的迟到帧**进来,会按那条腿的模式重新登记一次
|
|
102
|
+
* ——值仍是「刚结束那个周期」的真话,只是万一紧接着发生复活、且新周期首帧未到,那一瞬读到的是旧周期
|
|
103
|
+
* 的答案。core 今天把终态通知排在最后一条 forward 之后(fleet-bus 同注),复活又是人手动作,这条窗
|
|
104
|
+
* 实际上不可达;真要关死需要给句柄加墓碑态,代价大于一位诊断标签的收益,故不做。
|
|
105
|
+
*/
|
|
106
|
+
forgetHandleContentMode(bgAgentId) {
|
|
107
|
+
this.modeByHandle.delete(bgAgentId);
|
|
108
|
+
}
|
|
109
|
+
/** 路由读(按 tail 的 target 句柄)。缺席 = 不知道,路由出 `"unknown"`。 */
|
|
110
|
+
handleContentMode(bgAgentId) {
|
|
111
|
+
return this.modeByHandle.get(bgAgentId);
|
|
112
|
+
}
|
|
53
113
|
/** 两条 run 腿的 fwdInternals 调用点:帧已投影(projectTailFrame),null 不进来。 */
|
|
54
114
|
publish(bgAgentId, frame) {
|
|
55
115
|
const set = this.subs.get(bgAgentId);
|
|
@@ -4,6 +4,7 @@ import { cwdHonored } from "../../task-cwd.js";
|
|
|
4
4
|
import { mcpInjectionHonored } from "../../task-mcp.js";
|
|
5
5
|
import { sendJson, sendError } from "../send.js";
|
|
6
6
|
import { resolveStreamApprovalGate } from "../../tool-approval.js";
|
|
7
|
+
import { projectMemoryEngineCapability } from "../../memory-posture.js";
|
|
7
8
|
import { gatedPrincipal } from "../principal-gate.js";
|
|
8
9
|
export async function handleCapabilities(req, res, url, ctx) {
|
|
9
10
|
const miss = { fell: false };
|
|
@@ -91,6 +92,32 @@ async function handleCapabilitiesBody(req, res, url, ctx, miss) {
|
|
|
91
92
|
// the old caps honestly hides the affordances (the adaptation was pre-announced).
|
|
92
93
|
memory: false,
|
|
93
94
|
memoryWrite: false,
|
|
95
|
+
// #252 件3(黑板 [3921] cli 记忆 UX 的读面前置):记忆**引擎**位 —— 对象 = 本部署的记忆面真点亮
|
|
96
|
+
// (`backend` 形 + `vectorMode` 检索档),`false` = 暗。判据**逐字**是 boot 的 `memoryPosture`
|
|
97
|
+
// (boot/stores.ts 的单一推导点)的窄投影,与 operator 诊断面 `GET /v1/diagnostics/wiring` 的
|
|
98
|
+
// `memoryPosture` 段同源 —— 「诊断页说亮、能力位说暗」结构上不可能。
|
|
99
|
+
// ⚠️ 与上面两个 legacy 位**不是**一回事:`memory`/`memoryWrite` 说的是退役的 MemoryStore HTTP 动词
|
|
100
|
+
// (恒假,且会一直恒假);本位说的是引擎接线,它没有 HTTP 动词面 —— 记忆是模型在注入目录上的文件
|
|
101
|
+
// 技能。所以本位为真**不**意味着有记忆的读写端点可打(那正是 legacy 两位在否认的东西)。
|
|
102
|
+
// `vectorMode: null` = 这条腿没有档位指示面(file 引擎),不是「档位是词面」。
|
|
103
|
+
memoryEngine: projectMemoryEngineCapability(deps.memoryPosture),
|
|
104
|
+
// [3928]/[3929]/[3931](clay 准并入 #252 车窗):**远端车道可 detach**。零新机制 —— 说的就是本仓
|
|
105
|
+
// 早已有的那条腿:`POST /v1/tasks/stream` 带请求头 `x-detach-on-disconnect: true` ⇒ 客户端断连**不杀**
|
|
106
|
+
// run,它在 server 跑完;回来按响应头 `X-Task-Id` 走 `GET /v1/runs/:id` 与 `/events`(durable 事件日志
|
|
107
|
+
// 重放 + `Last-Event-ID` 续流)拿全离开期间的帧。
|
|
108
|
+
//
|
|
109
|
+
// 谓词 = **部署半场**:`deps.runStore` —— 逐字就是那条腿自己的前置判据(`routes/tasks.ts` 的
|
|
110
|
+
// `earlyDurableTid = deps.runStore && prepared.spec.sessionId`,缺则 400 `request.precondition_unmet`
|
|
111
|
+
// 「detached 的结果无处可查」)。**per-request 半场(调用方必须带 sessionId)不在本位里**:那是每次
|
|
112
|
+
// 调用自己的事,部署级布尔对它说不了话 —— 本位说的是「这台 worker 支不支持」。
|
|
113
|
+
// 今天它与 `asyncRuns` 取值相同,这是诚实记账而不是冗余:那一位问「异步 run API 在不在」,本位问
|
|
114
|
+
// 「壳的转后台键在远端车道该不该映射成 detach」(与 `workflows`/`workflowsList` 同族先例)。
|
|
115
|
+
// ⚠️ **辖域只到远端车道**([3929] 精确化 1):本地车道的转后台归壳自己的判据,本位不覆盖、也不许被
|
|
116
|
+
// 拿去禁本地(本地转后台今天是好的)。
|
|
117
|
+
// ⚠️ **不承诺** detach 期间流内审批的存活:那条(优雅断连 ⇒ 转 durable park,#241)是另一根轴,由
|
|
118
|
+
// `approvals` / `streamApproval` 两位各自广告 —— 本位为真而它们为假的部署上,detach 照常工作,
|
|
119
|
+
// 只是断连那一刻挂起的审批走 core 的 fail-closed 臂。
|
|
120
|
+
sessionBackgroundable: Boolean(deps.runStore),
|
|
94
121
|
// design/177 —— org 共享记忆库只读面。判据**逐字**等于 server.ts 里那个域的挂载合取式(供给面 ∧
|
|
95
122
|
// org 折叠面),所以"说 yes ⟺ 面真能用"是结构性的:少一个,域不挂载、这里也翻假,消费方看到的
|
|
96
123
|
// 与它真能打通的永远一致。折叠面缺席时刻意不报 true —— 一个没有成员性判据的共享读面比没有更糟。
|
|
@@ -156,6 +156,12 @@ async function handleDiagnosticsBody(req, res, url, ctx, miss) {
|
|
|
156
156
|
}
|
|
157
157
|
sendJson(res, 200, {
|
|
158
158
|
static: deps.staticWiring,
|
|
159
|
+
// #252 件2:记忆面姿态(backend / vectorMode / 点亮态 / dark 成因 / embedder 身份)。此前这些事实
|
|
160
|
+
// **只**活在启动日志里,而日志不可查询、跨副本不可聚合 —— cli 的记忆 UX 与运维都只能读日志或按 env
|
|
161
|
+
// 自己推(推不出来:多租户 file 底座上 `MEMORY_ENGINE=on` 其实是暗的)。additive 键,与 `serverGates`
|
|
162
|
+
// 同受众(operator-only,门在上方,本段不自带更松的门)。
|
|
163
|
+
// 缺席 ⇒ 显式 `null`(「这个进程说不出来」),不铸一个看起来像真答案的空壳对象。
|
|
164
|
+
memoryPosture: deps.memoryPosture ?? null,
|
|
159
165
|
serverGates: buildServerWiringGates({
|
|
160
166
|
toolApprovalEnabled: deps.toolApproval !== undefined,
|
|
161
167
|
streamApprovalEnabled: deps.config.streamApproval?.enabled === true,
|
package/dist/http/routes/runs.js
CHANGED
|
@@ -1296,7 +1296,16 @@ async function handleRunVerbsBody(req, res, url, ctx, miss) {
|
|
|
1296
1296
|
// 或 tier-3 均 bump——codex 快审 #11 纠,勿窄读成仅 tier-3)。
|
|
1297
1297
|
// `type: "meta"` 与 `event: meta` 同名双发(tasks/trace-usage/approvals 三流同款):部分代理只转发
|
|
1298
1298
|
// `data:` 行,按 data.type 分派的消费端否则认不出首帧。additive,老消费端忽略未知键。
|
|
1299
|
-
|
|
1299
|
+
// [3911] `contentFrames` = 本面的**内容帧供给形**(宿主腿登记,SubagentTailBus.TailContentMode 顶注
|
|
1300
|
+
// 有全文理由):"on" = 内容帧(text/reasoning delta、tool_start/end)会来;"progress_only" = 宿主 run
|
|
1301
|
+
// 提交时没带 `forwardSubagentEvents` ⇒ core 的 forward 门只放 task_progress,本流**结构上**不会有内容
|
|
1302
|
+
// 帧(订阅方无法在查看态打开它,只能让宿主 run 带上那个键重开);"unknown" = 还不知道(子代还没产出
|
|
1303
|
+
// 第一帧,或宿主腿在别的副本)。
|
|
1304
|
+
// additive 键,老消费端忽略;它把此前「一切正常但永远空」的静默降级变成 wire 上可读的事实。
|
|
1305
|
+
// 🔴 按 **target 句柄**读,不按 URL 里的 runId(codex 复审 high):session-scoped 子代活得比派它的
|
|
1306
|
+
// run 久,订阅方常经**后起的另一条 run** 来看它——按 run 键答题在那一档确定性谎报。
|
|
1307
|
+
const contentFrames = defaultSubagentTailBus.handleContentMode(target) ?? "unknown";
|
|
1308
|
+
res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, taskId: runId, target, status: probeDetails.status ?? "running", ...(typeof probeDetails.seq === "number" ? { seq: probeDetails.seq } : {}), live: "replica-local", contentFrames, replayFace: "GET /v1/runs/:id/subagents/:handle/output" })}\n\n`);
|
|
1300
1309
|
// core 1.382([1561] 提货单③):"parked" 非活非终态(durably suspended pending approval)——跟
|
|
1301
1310
|
// running/pending 同归"还没完",tail 该继续等而不是收尾指去 replay 面(那是给真终态用的)。子代获批
|
|
1302
1311
|
// 恢复后会在**同一条**活流上继续产帧,提前收尾会让客户端误判"已经结束",错过恢复后的后续内容。
|
|
@@ -532,6 +532,10 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
532
532
|
// so the conversation lane never saw a real tick and the in-loop task_progress branch below was
|
|
533
533
|
// forward-compat then — [2373]B-4:floor ^4.0.0 下它是热路径(腐注方向已翻转,勿按旧注当死支)。
|
|
534
534
|
// core swallow-guards a throwing sink.
|
|
535
|
+
// [3911] 定谳(bg 腿 twin 在 runs.ts):per-agent tail 的**内容帧供给**由本腿 spec 的
|
|
536
|
+
// forwardSubagentEvents 决定(core prepare-task 的 forward 门);缺席 ⇒ 那条流恒只有
|
|
537
|
+
// task_progress。逐帧按**句柄**登记供给形(键为什么是句柄:SubagentTailBus.modeByHandle 顶注)。
|
|
538
|
+
const tailContentMode = prepared.spec.forwardSubagentEvents === true ? "on" : "progress_only";
|
|
535
539
|
const fwdInternals = {
|
|
536
540
|
onForwardEvent: (e) => {
|
|
537
541
|
fleetPub?.onForwardEvent(e);
|
|
@@ -542,10 +546,14 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
542
546
|
// ——主流断了 tail 订阅者照收(tail 是独立连接)。
|
|
543
547
|
{
|
|
544
548
|
const bg = e.bgAgentId;
|
|
545
|
-
if (bg !== undefined
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
+
if (bg !== undefined) {
|
|
550
|
+
// 登记在 hasSubscribers 快门**之前**:没人在听时也要学,否则晚开的 tail 读不到供给形。
|
|
551
|
+
defaultSubagentTailBus.noteHandleContentMode(bg, tailContentMode);
|
|
552
|
+
if (defaultSubagentTailBus.hasSubscribers(bg)) {
|
|
553
|
+
const f = projectTailFrame(e);
|
|
554
|
+
if (f)
|
|
555
|
+
defaultSubagentTailBus.publish(bg, f);
|
|
556
|
+
}
|
|
549
557
|
}
|
|
550
558
|
}
|
|
551
559
|
if (res.writableEnded)
|
|
@@ -588,6 +596,9 @@ async function handleTasksBody(req, res, url, ctx, miss) {
|
|
|
588
596
|
if (n.task_type === "workflow")
|
|
589
597
|
return;
|
|
590
598
|
// S2 复审 #3:中途终态信号——task_settled 帧让订阅者不再 heartbeat 永挂(路由收帧即收流)。
|
|
599
|
+
// [3911] 终态即撤供给形登记:同一 a* 句柄可进入下一个**复活周期**,而新周期的宿主腿模式
|
|
600
|
+
// 可能相反——不撤会把上一周期的答案报给新周期的首帧前窗口(理由全文见 forgetHandleContentMode)。
|
|
601
|
+
defaultSubagentTailBus.forgetHandleContentMode(n.task_id);
|
|
591
602
|
if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
|
|
592
603
|
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
593
604
|
}
|
package/dist/http/server.d.ts
CHANGED
|
@@ -453,6 +453,11 @@ export interface ServiceDeploymentDeps {
|
|
|
453
453
|
* operator-only 面,故整份原样下发(含 `governance` 段:本面就是那个段的受众)。
|
|
454
454
|
* 缺席 = 这个进程不是由 composition root 装起来的(测试夹具形)⇒ 该路由诚实地不存在(404)。 */
|
|
455
455
|
staticWiring?: import("@sema-agent/core").WiringManifest;
|
|
456
|
+
/** #252 件2/件3:记忆面姿态(boot/stores.ts 的 `buildMemoryPosture` 产物 —— 与启动日志同一份)。
|
|
457
|
+
* 两个读面共用:`GET /v1/diagnostics/wiring` 的 `memoryPosture` 段(operator-only,全量)与
|
|
458
|
+
* `GET /v1/capabilities` 的 `memoryEngine` 位(窄投影)。缺席 = 这个进程不是由 composition root 装
|
|
459
|
+
* 起来的(测试夹具形)⇒ 诊断面报 null、能力位取诚实下限 false。 */
|
|
460
|
+
memoryPosture?: import("../memory-posture.js").MemoryPosture;
|
|
456
461
|
/** ① core ruling — SPLIT: `capabilities.workflows` = the ENGINE-CAN axis, boot-computed from core's own
|
|
457
462
|
* `workflowsCapability(deps)` (hardened script runner ∧ governance), NOT the `Boolean(workflowRunStore)` store
|
|
458
463
|
* proxy. Orthogonal to `workflowsList` (the durable-list axis = `workflowRunStore`). Falls back to the store
|
package/dist/http/server.js
CHANGED
|
@@ -25,6 +25,7 @@ import {} from "../observability/rate-limit.js";
|
|
|
25
25
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
26
26
|
import { turnEndEventData, contextUsageEventData, toolStartEventData, toolEndEventData, taskProgressEventData, taskNotificationEventData, compactedEventData, diagnosticsEventData, brainStatusEventData, steeringInjectedEventData, compactionOutcomeEventData, workspaceChangedEventData, wiringManifestEventData, humanInputEventData, appendModelUsageDelta, appendPromptManifest, attachModelUsage } from "../trace/project.js";
|
|
27
27
|
import { redactSecrets } from "../trace/redact.js";
|
|
28
|
+
import { createLedgerSink } from "../trace/ledger-sink.js"; // #253:resume-verify 臂的账本写口 = 与 bg/sync 两腿同一只投影件
|
|
28
29
|
import { IdempotencyCache, scopedIdempotencyKey } from "./idempotency.js";
|
|
29
30
|
export { scopedIdempotencyKey };
|
|
30
31
|
import { streamSseLog } from "./sse-log.js";
|
|
@@ -1868,6 +1869,10 @@ export function createHttpServer(rawDeps) {
|
|
|
1868
1869
|
const rs0 = deps.runStore;
|
|
1869
1870
|
let drainSeq = (await rs0.maxSeq(taskId)) + 1;
|
|
1870
1871
|
await emitPendingWorkflowCompletions(deps.workflowCompletionInbox, sessionId, principal ?? null, (frame) => {
|
|
1872
|
+
// 🔴 本仓唯一的**动态**账本写口:`type` 逐字来自 core 帧,不是本仓字面量,所以闭集
|
|
1873
|
+
// `LedgerEventType` 在这里只能是**声明**而非证明(A-032 P1-③ 射程上限,ledger-events.ts 顶注
|
|
1874
|
+
// 有同款自陈)。今天这条腿产出的是 `task_notification`;core 换帧名时**本处不会红**,对齐由
|
|
1875
|
+
// core 帧集的消费门(`trace/core-keyset-guard.ts` 一族)负责。
|
|
1871
1876
|
const { type, ...rest } = frame;
|
|
1872
1877
|
// codex F1 + 修2(三路复审 notify-1):the claim must carry the WRITE OUTCOME, not a pre-resolved
|
|
1873
1878
|
// true — a transient append failure with a `Promise.resolve(true)` claim would fake-suppress the
|
|
@@ -1890,9 +1895,113 @@ export function createHttpServer(rawDeps) {
|
|
|
1890
1895
|
// so the verifier judges the POST-resume working tree directly — there is no stale pre-suspend diff to
|
|
1891
1896
|
// grade, which closes the "approve then plant a backdoor" window by construction.
|
|
1892
1897
|
if (verifyRounds !== undefined) {
|
|
1898
|
+
// ── #253(core 5.33.0 #246 提货):**这条腿的供给座席**。5.32 及以前 `resumeWithVerification`
|
|
1899
|
+
// 的签名里没有 internals 那一位 ⇒ 带 verify 的 resume 对子代进度 / 活性 / bg 终态通知全盲
|
|
1900
|
+
// (旁边 resumeStream 臂自 core 1.210 起就有);5.33.0 开了尾可选 `internals`,逐字转发给
|
|
1901
|
+
// **恢复实现腿 + 验证器腿 + 每一轮修复腿**。这里把 resumeStream 臂**同一套**供给链接上去。
|
|
1902
|
+
// ⚠️ `isolation` 一位永不传(core 在这道门上双相位响亮拒 `config.verify_internals_invalid`)。
|
|
1903
|
+
const rsV = deps.runStore;
|
|
1904
|
+
// 🔴 **本臂的账本写口 = 共享 `LedgerSink`**(codex 对抗复审 R1 [high],验真后修)。为什么是它
|
|
1905
|
+
// 而不是在这里手写三行 append:
|
|
1906
|
+
// ① **提交序 == 序号序**:sink 的单写串行链在**链步内**分配序号(它的 [1.211 codex H2] 注)。
|
|
1907
|
+
// 此前这条臂的 forward 写是 fire-and-forget、终局/manifest 是 awaited,各自独立发起 ⇒
|
|
1908
|
+
// 池化 SQL 后端上**高序号可以先提交**,游标读方(`afterSeq`)推进过去之后,迟到的低序号
|
|
1909
|
+
// 行**永久读不到**;
|
|
1910
|
+
// ② 序号**一次铸定**:此前在腿跑完之后才 `maxSeq()+1` 现读,与运行期的 forward 写撞号 ——
|
|
1911
|
+
// 改成腿开跑前读一次当**基址**,sink 的 1..N 加上去(零 forward 帧时与旧写法逐字等价);
|
|
1912
|
+
// ③ forward 帧的**投影**(task_progress / tool_start / tool_end 三型 + 白名单 builder + §E1
|
|
1913
|
+
// 脱敏)也归它,本臂不再抄一份 —— 三腿同源正是那个模块存在的理由。
|
|
1914
|
+
// ⚠️ **不设「终局即封口」的准入闸**(codex R1/R2 都提了「终局后不该再落行」,**验真后驳回**):
|
|
1915
|
+
// 本仓已有属主裁定 —— `runs-coverage` 的 [1840]§三定性钉(mock-anthropic 确定性复现器取证):
|
|
1916
|
+
// `run_in_background` **收养形**子代的整个生命周期在宿主腿终态**之后**,live 流按设计 terminal
|
|
1917
|
+
// break,帧**照落账**、消费端 done 后重拉即全见;「leg teardown 撕掉 sink」对那一族子代就是
|
|
1918
|
+
// **真丢帧**(施工期实测:在 bg 腿加同款围栏,那条钉当场红)。⇒ 本臂与 bg 腿同一条语义:
|
|
1919
|
+
// 晚到照落、序号单调。
|
|
1920
|
+
const vSeqBase = rsV && taskId ? await rsV.maxSeq(taskId) : 0;
|
|
1921
|
+
const verifySink = createLedgerSink({
|
|
1922
|
+
appendEvent: (seq, type, data) => (rsV && taskId ? rsV.appendEvent(taskId, vSeqBase + seq, type, data) : Promise.resolve()),
|
|
1923
|
+
persistThinking: deps.config.traceThinking,
|
|
1924
|
+
notifiedKeys: resumeNotifiedKeys, // 与本腿其余臂共享「已投递的通知键」(bg 腿同款)
|
|
1925
|
+
...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
|
|
1926
|
+
});
|
|
1927
|
+
const vAppend = (type, data) => verifySink.append(type, data);
|
|
1928
|
+
const verifyTailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
|
|
1929
|
+
const verifyInternals = {
|
|
1930
|
+
// 活性打点:这条臂没有事件循环(stream 臂的 `recordTurnActivity` 在 for-await 里),
|
|
1931
|
+
// 不接 ⇒ 一条长跑的 verify-resume 腿在活性面上从 suspend 那刻起就冻住。
|
|
1932
|
+
...(taskId ? { onActivity: () => recordTurnActivity(taskId) } : {}),
|
|
1933
|
+
onForwardEvent: (e) => {
|
|
1934
|
+
fleetPub?.onForwardEvent(e);
|
|
1935
|
+
{
|
|
1936
|
+
const bg = e.bgAgentId;
|
|
1937
|
+
if (bg !== undefined) {
|
|
1938
|
+
defaultSubagentTailBus.noteHandleContentMode(bg, verifyTailContentMode);
|
|
1939
|
+
if (defaultSubagentTailBus.hasSubscribers(bg)) {
|
|
1940
|
+
const f = projectTailFrame(e);
|
|
1941
|
+
if (f)
|
|
1942
|
+
defaultSubagentTailBus.publish(bg, f);
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
// 三型投影 + 白名单 builder + 单写串行链全在共享 sink 里(见上面的顶注);写失败留痕由
|
|
1947
|
+
// sink 自己的 catch 兜(它对失败步不断链)。
|
|
1948
|
+
if (rsV && taskId)
|
|
1949
|
+
verifySink.onForwardEvent(e);
|
|
1950
|
+
},
|
|
1951
|
+
// 🔴 **#255(core 5.34.0 #253 / 判据帖 [3967] G1):status 第五席**。这条臂没有事件循环 ——
|
|
1952
|
+
// 旁边 resumeStream 臂的顶层 status 走它自己那条 for-await 的 `case "status"`(driveResume
|
|
1953
|
+
// switch),本臂在 5.33 时代整段重试期是**黑的**(座位那时还不存在)。core 5.34.0 的
|
|
1954
|
+
// `statusEmit` 双写(queue 照旧 + 座位喂**同一个** frozen 帧)把这条腿补齐,这里接的是
|
|
1955
|
+
// **同一条**投影链:`brain_retry_total` 计数 + 共享 `verifySink` 的白名单 builder/单写串行链。
|
|
1956
|
+
// ⚠️ 帧 frozen ⇒ 这条链上不许写帧字段;同步回调不能 await ⇒ fire-and-forget。下面那个
|
|
1957
|
+
// `.catch` 买的是**留痕**不是进程安全:`appendStatus` 返回的链步在 `LedgerSink.append` 内部
|
|
1958
|
+
// 已被挂上处理器(「失败不断链」)⇒ 没有逃逸的 rejection(实测:摘掉它 §4⑥b 仍绿);
|
|
1959
|
+
// 判别力由 §4⑥b 的「诊断行在场」断言看守(与 `src/runs.ts` 的 `statusSeat` 同款注)。
|
|
1960
|
+
// ⚠️ 与 `src/runs.ts` 的 `statusSeat` 同一条纪律:**只**挂在「只回结果」的这条臂上,
|
|
1961
|
+
// 旁边 resumeStream 臂那只 internals 不带它(带了就是同一帧两行账本 + 计数翻倍)。
|
|
1962
|
+
// 🔴 走 sink 的 **`appendStatus` 同步入链门**,不是 `onEvent` —— 后者先 `await flush()`
|
|
1963
|
+
// 才入链,同一拍后到的 `onForwardEvent`(同步入链)会抢到更小的序号,账本时间线倒挂
|
|
1964
|
+
// (codex 对抗复审 R2 [medium],红先复现后修;本臂与 `src/runs.ts` 的 `statusSeat` 同病同修)。
|
|
1965
|
+
onStatusEvent: (e) => {
|
|
1966
|
+
deps.metrics?.inc("brain_retry_total", { phase: String(e.phase) });
|
|
1967
|
+
if (rsV && taskId) {
|
|
1968
|
+
void verifySink.appendStatus(e).catch((err) => deps.logger?.warn?.("status_event_append_failed", { route: "resume-verify", taskId, err: err instanceof Error ? err.message : String(err) }));
|
|
1969
|
+
}
|
|
1970
|
+
},
|
|
1971
|
+
// bg 子代终态:与 resumeStream 臂逐字同款(理由全在那份孪生注里)。
|
|
1972
|
+
onTaskNotification: (n) => {
|
|
1973
|
+
if (n.task_type === "workflow")
|
|
1974
|
+
return;
|
|
1975
|
+
defaultSubagentTailBus.forgetHandleContentMode(n.task_id);
|
|
1976
|
+
if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
|
|
1977
|
+
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
1978
|
+
}
|
|
1979
|
+
const hadRow = isFleetAgentTerminalNotification(n) ? (fleetPub?.onChildTerminal(n.sessionId ?? n.task_id, n.status, n.task_id, n.toolUseId) ?? false) : false;
|
|
1980
|
+
const parked = !resumeLegLive && Boolean(deps.workflowCompletionInbox && sessionId);
|
|
1981
|
+
deps.logger?.info?.("task_notification_observed", { route: "resume-verify", taskId: n.task_id, taskType: n.task_type, status: n.status, hadFleetRow: hadRow, legLive: resumeLegLive, parkedDurable: parked });
|
|
1982
|
+
if (parked) {
|
|
1983
|
+
void deps.workflowCompletionInbox.enqueue(taskNotificationInboxEntry(sessionId, principal ?? null, n, Date.now(), taskId)).catch((err) => deps.logger?.warn?.("park_enqueue_failed", { route: "resume-verify", taskId: n.task_id, err: err instanceof Error ? err.message : String(err) }));
|
|
1984
|
+
}
|
|
1985
|
+
else if (resumeLegLive) {
|
|
1986
|
+
const key = taskNotificationStreamKey(n);
|
|
1987
|
+
if (resumeNotifiedKeys.get(key) === undefined) {
|
|
1988
|
+
resumeNotifiedKeys.set(key, vAppend("task_notification", taskNotificationEventData({ notification: n })).then(() => true, () => false));
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
},
|
|
1992
|
+
// 子代 steer 句柄登记:与 resumeStream 臂同款(腿末尾统一撤,见 finally)。
|
|
1993
|
+
...(deps.subagentSteerRegistry && taskId
|
|
1994
|
+
? {
|
|
1995
|
+
onSubagentSpawn: (handle) => {
|
|
1996
|
+
subagentHandleEvictions.push(deps.subagentSteerRegistry.register(taskId, handle));
|
|
1997
|
+
},
|
|
1998
|
+
}
|
|
1999
|
+
: {}),
|
|
2000
|
+
};
|
|
1893
2001
|
// resumeWithVerification runs the SAME pre-CAS guard + atomic CAS as resume() internally, so a lost
|
|
1894
2002
|
// CAS rejects with CheckpointError before any work — handled by the catch below, same as the stream path.
|
|
1895
|
-
|
|
2003
|
+
resumeLegLive = true; // 与 stream 臂同义:腿在跑 ⇒ bg 终态走 live 落账而非 durable park(finally 清)
|
|
2004
|
+
const vr = await resumeWithVerification(legRunner, token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, resumeObjective, verifyRounds, verifyInternals); // 快审 F1:整对象直传;codex F3:带 taskId 的形
|
|
1896
2005
|
let safe = stripCheckpointToken(vr);
|
|
1897
2006
|
// 🔴 A re-suspend on a LATER gate is NOT terminal: core maps it to status:"failed" +
|
|
1898
2007
|
// verification.unverifiedReason with the checkpoint token still live. Treat it exactly like
|
|
@@ -1922,8 +2031,9 @@ export function createHttpServer(rawDeps) {
|
|
|
1922
2031
|
// confirmed (maxSeq + manifest + usage + suspended event): a thrown append must not rethrow past
|
|
1923
2032
|
// the authoritative row re-park below (stream twin). reSuspended-only keeps its fail-loud shape.
|
|
1924
2033
|
try {
|
|
1925
|
-
|
|
1926
|
-
|
|
2034
|
+
// #253:序号来自腿开跑前铸的**同一个**分配器(`vseq`/`vChain`),不再在这里二次 `maxSeq()`
|
|
2035
|
+
// ——腿运行期间 forward sink 可能已经写过行,二次现读会与那些 fire-and-forget 写撞号。
|
|
2036
|
+
const ap = vAppend;
|
|
1927
2037
|
await appendPromptManifest(ap, deps.promptManifests, taskId); // [998]②: the verified-resume leg has no stream — persist the re-fired manifest before its terminal/suspend event
|
|
1928
2038
|
if (reSuspended || vrReopened) {
|
|
1929
2039
|
await appendModelUsageDelta(ap, deps.modelUsage, taskId); // E8: persist pre-re-suspend per-model usage
|
|
@@ -1998,6 +2108,12 @@ export function createHttpServer(rawDeps) {
|
|
|
1998
2108
|
// (`++seq` is a sync increment — sink-vs-loop appends get unique seqs); only when a durable log exists.
|
|
1999
2109
|
// false once this leg settles — notifications after that take the durable-inbox path.
|
|
2000
2110
|
resumeLegLive = true;
|
|
2111
|
+
// [3911] 定谳(bg/sync 两腿 twin):per-agent tail 的**内容帧供给**由本腿 spec 的
|
|
2112
|
+
// forwardSubagentEvents 决定(core prepare-task 的 forward 门)。逐帧按**句柄**登记
|
|
2113
|
+
// (键为什么是句柄:SubagentTailBus.modeByHandle 顶注)。⚠️ 旧注这里写的「verify 腿不传
|
|
2114
|
+
// internals ⇒ 无帧 ⇒ meta 出 unknown」自 #253(core 5.33.0 #246)起**已过期**:verify 臂
|
|
2115
|
+
// 现在也传 internals(见上面那段),两条 resume 臂的供给形因此同形。
|
|
2116
|
+
const tailContentMode = resumeTaskConfig.forwardSubagentEvents === true ? "on" : "progress_only";
|
|
2001
2117
|
const stream = await legRunner.resumeStream(token, outcome, { ...resumeTaskConfig, signal: cancelCtrl.signal, preemptSignal: preemptCtrl.signal }, {
|
|
2002
2118
|
onForwardEvent: (e) => {
|
|
2003
2119
|
fleetPub?.onForwardEvent(e);
|
|
@@ -2005,10 +2121,14 @@ export function createHttpServer(rawDeps) {
|
|
|
2005
2121
|
// bgAgentId,漏挂=同副本开流只见 heartbeat)。publish 在 rs/taskId 门之前(tail 独立于账本)。
|
|
2006
2122
|
{
|
|
2007
2123
|
const bg = e.bgAgentId;
|
|
2008
|
-
if (bg !== undefined
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2124
|
+
if (bg !== undefined) {
|
|
2125
|
+
// 登记在 hasSubscribers 快门**之前**:没人在听时也要学,否则晚开的 tail 读不到供给形。
|
|
2126
|
+
defaultSubagentTailBus.noteHandleContentMode(bg, tailContentMode);
|
|
2127
|
+
if (defaultSubagentTailBus.hasSubscribers(bg)) {
|
|
2128
|
+
const f = projectTailFrame(e);
|
|
2129
|
+
if (f)
|
|
2130
|
+
defaultSubagentTailBus.publish(bg, f);
|
|
2131
|
+
}
|
|
2012
2132
|
}
|
|
2013
2133
|
}
|
|
2014
2134
|
if (!rs || !taskId)
|
|
@@ -2038,6 +2158,9 @@ export function createHttpServer(rawDeps) {
|
|
|
2038
2158
|
if (n.task_type === "workflow")
|
|
2039
2159
|
return;
|
|
2040
2160
|
// S2 复审 #3(sync 腿同注):中途终态信号。
|
|
2161
|
+
// [3911] 终态即撤供给形登记:同一 a* 句柄可进入下一个**复活周期**,而新周期的宿主腿模式
|
|
2162
|
+
// 可能相反——不撤会把上一周期的答案报给新周期的首帧前窗口(理由全文见 forgetHandleContentMode)。
|
|
2163
|
+
defaultSubagentTailBus.forgetHandleContentMode(n.task_id);
|
|
2041
2164
|
if (defaultSubagentTailBus.hasSubscribers(n.task_id)) {
|
|
2042
2165
|
defaultSubagentTailBus.publish(n.task_id, { type: "task_settled", taskId: n.task_id, status: n.status, ...(typeof n.seq === "number" ? { seq: n.seq } : {}), ...(n.summary ? { summary: redactSecrets(n.summary) } : {}) });
|
|
2043
2166
|
}
|
package/dist/leader/wire.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Brain, type Model, type ModelRoles, type ModelPricing, type TaskSpec, type ExecutionEnvFactory, type RemoteExecutionEnv, type ToolResultStore, type SessionStore } from "@sema-agent/core";
|
|
1
|
+
import { type Brain, type Model, type ModelRoles, type ModelPricing, type TaskSpec, type ExecutionEnvFactory, type RemoteExecutionEnv, type ToolResultStore, type SessionStore, type RunnerDeps } from "@sema-agent/core";
|
|
2
2
|
import type { CheckpointStoreFull } from "../plugins/store-backend.js";
|
|
3
3
|
import { type LeaderDeps, type LeaderResult } from "./leader.js";
|
|
4
4
|
import type { LeaderRequestBody } from "./endpoint.js";
|
|
@@ -30,6 +30,12 @@ export interface LeaderWireConfig {
|
|
|
30
30
|
* session history must be readable by the MAIN runner on /decide resume — with the per-wire in-memory default
|
|
31
31
|
* the resume dies with SessionError "Entry <leafId> not found" (found live: drill c3 deny path). */
|
|
32
32
|
sessionStore?: SessionStore;
|
|
33
|
+
/**
|
|
34
|
+
* design/170 件B/C/D(#252,codex R2-F1)—— 部署治理三座席,**原样递给本车道的每一只 Runner**。
|
|
35
|
+
* 主车道的 `boot/governance-seams.ts` 是唯一属主(判据、拒启、解析全在那里),本字段只是把它的产物
|
|
36
|
+
* 搬过来:一个进程里「这台部署禁哪些能力、锁了哪些键」只能有一个答案。缺席 = 与接线前逐字相同。
|
|
37
|
+
*/
|
|
38
|
+
governance?: Pick<RunnerDeps, "compliancePostureResolver" | "lockedConfig" | "retentionPolicy">;
|
|
33
39
|
/** Worker model brain + catalog (the same the service runs tasks on). */
|
|
34
40
|
brain: Brain;
|
|
35
41
|
models: Record<string, Model>;
|
package/dist/leader/wire.js
CHANGED
|
@@ -256,6 +256,13 @@ export function createLeaderRunner(cfg) {
|
|
|
256
256
|
// 这一族变异**文本上照样有 `warn`**、值上却是 truthy 闭包(info-only 部署照铸席、通告被可选链吞掉)。
|
|
257
257
|
// 搬进函数之后判据可被**行为**测,门不再是关键字搜索。
|
|
258
258
|
const onNoticeSeat = createEngineNoticeSeat(cfg.logger);
|
|
259
|
+
// design/170 件B/C/D(#252,codex 对抗复审 R2-F1 验真后修):**leader 车道也是一个执行面**。
|
|
260
|
+
// 修前这五处 Runner 一个治理座席都没有 —— 开了 leader 端点的部署因此有一条在部署治理**之外**的执行
|
|
261
|
+
// 面:合规档位说禁 web_fetch,leader 的 worker 照挂;锁与留存声明同样够不着。与 `onNotice` 席当年的
|
|
262
|
+
// 病同族(那次的修法也是「判据收进单一属主 + 五处同席」)。
|
|
263
|
+
// 座席由 cfg 递进来(leader 绝不自己造一份治理判据 —— 一个进程里治理只能有一个属主);缺席 ⇒ 展开空
|
|
264
|
+
// 对象 = 与修前逐字相同,不铸死键。
|
|
265
|
+
const governanceSeat = cfg.governance ?? {};
|
|
259
266
|
// worker resource bounds (limits/maxCostUsd) + presign ttl are derived in leaderResourceConfig(), above.
|
|
260
267
|
// Bounded integration-repair (search 2026-06-14): when the merged tree compiles-clean-but-fails, run a strong
|
|
261
268
|
// agent WITH HANDS in the live merged sandbox to fix the cross-worker integration, then mergeBranches re-runs
|
|
@@ -282,7 +289,7 @@ export function createLeaderRunner(cfg) {
|
|
|
282
289
|
// rootPath: repoDir (Codex review #4) — without it core roots the file tools at executionEnv.cwd
|
|
283
290
|
// (/workspace on Kata), one level above the repo (/workspace/repo). bash can cd, but the dedicated
|
|
284
291
|
// read/edit tools would start too high + lose containment.
|
|
285
|
-
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
292
|
+
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
286
293
|
const objective = [
|
|
287
294
|
`The integrated project at ${repoDir} fails its build/test. Make the MINIMAL change to the working tree so this command exits 0 (cd into the repo and run it yourself to confirm):`,
|
|
288
295
|
` ${testCmd}`,
|
|
@@ -338,7 +345,7 @@ export function createLeaderRunner(cfg) {
|
|
|
338
345
|
throw new Error(`oracle still present after remove (${f.path}) — refusing conflict-resolve (measurement integrity)`);
|
|
339
346
|
}
|
|
340
347
|
try {
|
|
341
|
-
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
348
|
+
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
342
349
|
const objective = [
|
|
343
350
|
`A parallel worker '${workerId}' ported a module on its own branch, but applying its patch onto the already-integrated tree at ${repoDir} produced a MERGE CONFLICT (git apply --3way). The OTHER workers' patches already applied cleanly — integrate THIS worker's changes too, resolving the overlap.`,
|
|
344
351
|
`Resolve EVERY conflict in the working tree: open each file containing conflict markers (<<<<<<< / ======= / >>>>>>>) and merge BOTH sides' real intent (keep both workers' behaviour — never drop one side just to make it apply). Then apply any rejected hunks recorded in *.rej files by hand, and DELETE every *.rej and *.orig file.`,
|
|
@@ -426,7 +433,7 @@ export function createLeaderRunner(cfg) {
|
|
|
426
433
|
// the Coordinator's parallel merge relies on. Was a straight map() that skipped all of it.
|
|
427
434
|
return validateSubtasks(body.subtasks);
|
|
428
435
|
}
|
|
429
|
-
const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat });
|
|
436
|
+
const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat });
|
|
430
437
|
const runRoute = async (prompt) => {
|
|
431
438
|
const res = await planRunner.runTaskStream({
|
|
432
439
|
objective: prompt,
|
|
@@ -577,7 +584,7 @@ export function createLeaderRunner(cfg) {
|
|
|
577
584
|
workerId: sub.workerId, sessionId, branch: sub.branch,
|
|
578
585
|
baseSha, // seedCmd reproduces the durable base — same sha for every worker
|
|
579
586
|
runner: keepCtxWarm(new Runner({
|
|
580
|
-
brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat,
|
|
587
|
+
brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat,
|
|
581
588
|
...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}),
|
|
582
589
|
...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}),
|
|
583
590
|
executionEnvFactory: (ctx) => withStaging(envFactory(ctx), stage, async (e) => {
|
|
@@ -600,7 +607,7 @@ export function createLeaderRunner(cfg) {
|
|
|
600
607
|
const baseSha = await sh(env)(`cd ${repo} && git rev-parse HEAD`);
|
|
601
608
|
return {
|
|
602
609
|
workerId: sub.workerId, sessionId, branch: sub.branch, baseSha,
|
|
603
|
-
runner: keepCtxWarm(new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}), ...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}), executionEnv: env })),
|
|
610
|
+
runner: keepCtxWarm(new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, ...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}), ...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}), executionEnv: env })),
|
|
604
611
|
diffEnv: env,
|
|
605
612
|
destroy: () => env.destroy().then(() => { }),
|
|
606
613
|
spec: { ...workerLimits, ...sub.spec, ...durableSpec, ...resourceSpec },
|