@sema-agent/client-core 0.30.6 → 0.30.7
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/CHANGELOG.md +47 -0
- package/README.md +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/subagent/engineSubagentOutput.js +3 -0
- package/dist/subagent/engineSubagentResume.d.ts +198 -0
- package/dist/subagent/engineSubagentResume.js +210 -0
- package/dist/subagent/engineSubagentTail.js +5 -0
- package/dist/subagent/engineTaskHandleWire.js +3 -0
- package/dist/subagentContentStore.d.ts +49 -1
- package/dist/subagentContentStore.js +101 -5
- package/docs/INTEGRATION-CLIENTS.md +6 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,53 @@
|
|
|
16
16
|
> 🔴 **互链**(web [C166]⑦):各版「已知局限」段只记**该版新增**;接入面已知局限的完整台账在
|
|
17
17
|
> `docs/INTEGRATION-CLIENTS.md` §6e/§7 —— **只读其一会漏**,两处都过。
|
|
18
18
|
|
|
19
|
+
## 0.30.7(2026-08-15)
|
|
20
|
+
|
|
21
|
+
**#242 批 2 subagent 归属单表 + resume 腿归位(A-028.6/.7 收编)——公开面 additive,一处行为面加强:**
|
|
22
|
+
|
|
23
|
+
- **「这一行子代属于哪条 run」从此只有一张表**:`subagentContentStore` 的 `bgParentRun` 收编了
|
|
24
|
+
sema-cli 的壳侧 `subagentOwnerLedger`。收编前同一个问题两仓两份台账、**答案规则相反**(壳表按
|
|
25
|
+
`task_progress` tick 喂、缺席即诚实缺席;包表按 fleet 行帧/bg 通知喂、消费方一律回落在飞 run),
|
|
26
|
+
且两表覆盖面互补(壳表只覆盖发 tick 的子代、包表覆盖零 tick 的 fleet-row lane)。
|
|
27
|
+
- 新写入口 `recordSubagentOwnerFromProgress(ev, ownerRunId, ownerSessionId)` —— 收编前本表
|
|
28
|
+
**零 `task_progress` 腿**。🔴 宿主 run **必须由持有 stream-local 值的调用方显式传入**,包内
|
|
29
|
+
绝不从 `activeEngineRunId()` 推断(进程级值被并发 main/fork 流互相覆写 + 每个 query 的 finally
|
|
30
|
+
清空 ⇒ 推断出的宿主可能是别的 run,而**错值比缺席更坏**)。
|
|
31
|
+
- `recordBgParentRun(taskId, runId)` 补 **additive** 第三参 `sessionId`;新读口
|
|
32
|
+
`getBgParentRunOwner(taskId)` 回整条 `{runId, sessionId?}`(旧读口 `getBgParentRun` 签名不变)。
|
|
33
|
+
- 表上限 64 → **320**(收编前两表之和;收货订正:本行中期文案曾写 256「较大者」,与终版实现、
|
|
34
|
+
断言 R1l/R1m 及本段下方「表容量」条相矛盾——之和才不缩窄既有保障,详见下方)。
|
|
35
|
+
- **resume 腿与它的孪生 steer 同居**:新 `subagent/engineSubagentResume.ts`(判定半场:能力位 gate
|
|
36
|
+
`subagentResume` / 取址三态 / 409 **六族**分类禁合并 / AT-MOST-ONCE 调用腿 / UNTRUSTED 收据消毒)。
|
|
37
|
+
此前 steer 在包、resume 整条在 sema-cli,且两腿**取址的决定量不同**(steer 取在飞 run、resume 取
|
|
38
|
+
行锚)—— 那正是「同一个 agent 有时能说话有时不能」的成因。出路文案(CC 措辞面)留端。
|
|
39
|
+
新公开面:`resumeSettledSubagent` / `resolveSubagentResumeContext` / `resolveOwnerRunId` /
|
|
40
|
+
`classifySubagentResumeFailure` / `resolveSubagentContinueTarget` / `subagentResumeAvailable` /
|
|
41
|
+
`SUBAGENT_RESUME_CAP` + `SubagentResumeContext` / `SubagentResumeFacade` /
|
|
42
|
+
`SubagentResumeFailureKind` / `SubagentResumeOutcome` 类型。
|
|
43
|
+
- **caps × verb 组合告警**(`auditRetainWithoutWake`):引擎 caps 宣示 `subagentResume` ∧ 本端在请求
|
|
44
|
+
`retainSubagentSessions` ∧ 本端**未实现** `wakeSubagent` ⇒ 响亮一条 warn(每 baseUrl 至多一次)。
|
|
45
|
+
返回 `RetainWithoutWakeVerdict {message, emitted}`(`undefined` = 组合不成立):`message` 答「组合
|
|
46
|
+
成不成立」、`emitted` 答「**本次**响没响」——两问分开,按返回值呈现的端才不会重复提示。`CLIENT_VERBS.wakeSubagent` 按裁定**维持 `required:false`**(fail-soft)——
|
|
47
|
+
留存成本天天在付而一个兑现口都没有,这笔账不该只有沉默。
|
|
48
|
+
⚠️ 键名订正:caps 上没有 `retainSubagentSessions` 位,它是 **run 请求位**;引擎宣示兑现口的
|
|
49
|
+
caps 位是 `subagentResume`。
|
|
50
|
+
- **🔴 刻意未做(候三端表态)**:包内三处 `getBgParentRun(x) ?? activeEngineRunId()` 回落
|
|
51
|
+
(`engineSubagentTail` / `engineTaskHandleWire` / `engineSubagentOutput`)**口径一字未改**,只各加
|
|
52
|
+
一条候裁注释。统一到 resume 腿的「指名了行就诚实缺席」口径会让台账缺席时这三腿从「能连上(可能
|
|
53
|
+
连错 run)」变成「不连」= 真行为翻面,需 cli/web/desktop 表态后单批改。
|
|
54
|
+
⚠️ **本批的一个附带效果**:tick 腿补进单表后,这三处在过去恒缺席、必走回落的那些子代行,现在能
|
|
55
|
+
查到**真**宿主 run 了 —— 回落分支的触发面因此缩小(方向是从「可能连错」变成「连对」)。
|
|
56
|
+
- **🔴 resume 的 `?session=` 口径(端请注意)**:`resolveSubagentResumeContext` 对「台账认得这一行
|
|
57
|
+
但没捕到它的会话」**不带 `?session=`**,绝不用「此刻的会话」冒充 —— resume 是 AT-MOST-ONCE 的
|
|
58
|
+
副作用动作,猜错会话可能落到另一个会话的同名子代上(不可回收),而不带参数只是 server 干净 404。
|
|
59
|
+
今天 fleet 行帧腿/通知腿不传 session,所以 fleet-only 行稳定落这一格;**供给面欠账**:两腿补上
|
|
60
|
+
流会话后这一格自然变成「有真值」,判据无需再改。「完全没指名行」的通用调用仍退当前会话(口径不变)。
|
|
61
|
+
- **表容量**:合表上限取收编前**两表之和 320**(不是较大者 256)—— 按 256 钉会出现「tick 池已满、
|
|
62
|
+
再来一条 fleet 行就淘汰一条 tick」,那条子代的 resume 入口凭空消失而收编前还在。
|
|
63
|
+
另:同 `runId` 上**无 session 的刷新不覆盖已捕获的 session**(`runId` 真变了才丢)——
|
|
64
|
+
fleet/通知两腿的无 session 写此前会抹掉 tick 一次性捕获的会话。
|
|
65
|
+
|
|
19
66
|
## 0.30.6(2026-08-15)
|
|
20
67
|
|
|
21
68
|
**#242 批 1 workflow 单流化(A-028.8 收编)——wire 行为变更,消费端请对表:**
|
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ Renamed from **`@sema-agent/wire-cc-adapter`** (0.1.x, deprecated — see *Migra
|
|
|
35
35
|
|
|
36
36
|
## Scope
|
|
37
37
|
|
|
38
|
-
**Version:** 0.30.
|
|
38
|
+
**Version:** 0.30.6
|
|
39
39
|
|
|
40
40
|
- **Today** — the adapter seam, the whole `adapt()` pipeline (all 14 A-layer arms plus the
|
|
41
41
|
B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
|
package/dist/index.d.ts
CHANGED
|
@@ -203,6 +203,7 @@ export * from './subagent/engineDelegatedPrompt.js';
|
|
|
203
203
|
export * from './subagent/engineRowStopGate.js';
|
|
204
204
|
export * from './subagent/engineSubagentOutput.js';
|
|
205
205
|
export * from './subagent/engineSubagentSteer.js';
|
|
206
|
+
export * from './subagent/engineSubagentResume.js';
|
|
206
207
|
export * from './subagent/engineTaskHandleWire.js';
|
|
207
208
|
export * from './subagent/engineCompactWire.js';
|
|
208
209
|
export * from './subagent/engineSubagentTail.js';
|
package/dist/index.js
CHANGED
|
@@ -264,6 +264,8 @@ export * from './subagent/engineDelegatedPrompt.js';
|
|
|
264
264
|
export * from './subagent/engineRowStopGate.js';
|
|
265
265
|
export * from './subagent/engineSubagentOutput.js';
|
|
266
266
|
export * from './subagent/engineSubagentSteer.js';
|
|
267
|
+
// #242 批 2(A-028.7):resume 判定半场与 steer 同居 —— 两条孪生腿的取址口径才有单一真源。
|
|
268
|
+
export * from './subagent/engineSubagentResume.js';
|
|
267
269
|
export * from './subagent/engineTaskHandleWire.js';
|
|
268
270
|
export * from './subagent/engineCompactWire.js';
|
|
269
271
|
export * from './subagent/engineSubagentTail.js';
|
|
@@ -64,6 +64,9 @@ export async function fetchEngineSubagentReport(taskId, opts) {
|
|
|
64
64
|
const cfg = engineWireTarget();
|
|
65
65
|
if (!cfg)
|
|
66
66
|
return null;
|
|
67
|
+
// 🔴 候裁([4000] Q3 / design-242 §5 Q3,#242 批 2 **刻意不动**):这条回落与 resume 腿的
|
|
68
|
+
// `resolveOwnerRunId`(指名了行就诚实缺席)口径相反;统一口径是真行为翻面,候三端表态。
|
|
69
|
+
// 同族另两处:`engineSubagentTail.ts:tailEngineSubagent` / `engineTaskHandleWire.ts:resolveHostRun`。
|
|
67
70
|
const runId = getBgParentRun(taskId) ?? activeEngineRunId();
|
|
68
71
|
if (!runId)
|
|
69
72
|
return null;
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ #242 批 2 搬迁(2026-08-15,design/242 §3 批 2 / A-028.7):cli `src/sema/subagentResumeWire.ts`
|
|
3
|
+
* 的**判定半场**整搬(取址三态 + 409 六族分类 + at-most-once 调用腿),**逐条搬迁不重新发明**。
|
|
4
|
+
*
|
|
5
|
+
* ── 为什么搬(A-028.7 的真缺口)────────────────────────────────────────────────────────────────
|
|
6
|
+
* 孪生两腿此前**分居两仓**:steer 在包(`engineSubagentSteer.ts`),resume 整条在壳(270 行)。
|
|
7
|
+
* 而两腿的**决定量口径不同** —— steer 取址 `activeEngineRunId()`(在飞的那条),resume 取址
|
|
8
|
+
* `resolveOwnerRunId()`(按行锚)—— 正是壳侧头注自己警告的那种不对称:「同一个 agent 有时能说话
|
|
9
|
+
* 有时不能」。同居之后两腿的取址口径才有单一真源可对(口径统一本身是行为面,候 [4000] Q3)。
|
|
10
|
+
*
|
|
11
|
+
* ── 拆缝(判定进包 / 呈现留壳,与 T35/T36/T44 同一条原则)────────────────────────────────────
|
|
12
|
+
* **进包**:能力位 gate · 取址(`resolveOwnerRunId` 三态 + `resolveSubagentContinueTarget`)·
|
|
13
|
+
* 409 六族分类 · at-most-once 调用腿 · 收据消毒 · caps×verb 组合告警。
|
|
14
|
+
* **留壳**:出路文案(`subagentResumeCopy`,CC 措辞面)与 Ink 卡面动作。
|
|
15
|
+
*
|
|
16
|
+
* ── 搬迁差分(行为一字节不变,端资产上移到 port)────────────────────────────────────────────
|
|
17
|
+
* · `capsBaseUrl()` 直读 `process.env.SEMA_LIVE_BASEURL` → `engineWireTarget()?.baseUrl`
|
|
18
|
+
* (本包零 `process.env`,portability 门盯着;壳侧 env 臂由 `engineWireTarget` 同源派生,
|
|
19
|
+
* 且这一形对**显式装配** wire target 的宿主 —— web/桌面 —— 才答得对。同 `engineRowStopGate:44`)。
|
|
20
|
+
* · `logForDebugging` → `hostLog('debug', …)`(同 steer 腿搬迁差分)。
|
|
21
|
+
*
|
|
22
|
+
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
23
|
+
*
|
|
24
|
+
* subagentResumeWire — 保留子代的**复活口**消费面(A-022.1 + A-022.12 的人机面 / 缺口 G2+G9)。
|
|
25
|
+
*
|
|
26
|
+
* 取址/收据/错误族逐字对位既有的 `steerEngineSubagent` —— 同一个 `target`(parentToolCallId 或
|
|
27
|
+
* agentName)、同一个 `{content}` 体、同一个 200 `SubagentSteerReceipt`。**两条腿刻意同形**:
|
|
28
|
+
* steer 是「它还在跑,插句话」,resume 是「它已经结束了,叫醒它」——同一个人机动作的两个时相,
|
|
29
|
+
* 分不清哪个时相就会拿错 verb 吃 409。
|
|
30
|
+
*
|
|
31
|
+
* ── 🔴 能力位 gate:`subagentResume` ────────────────────────────────────────────────────────
|
|
32
|
+
* SDK `runs.d.ts` 头注逐字「Gate on `capabilities.subagentResume`」。未判/缺键 = false = **不渲入口**
|
|
33
|
+
* (渲了就是一个恒 404/409 的假 affordance)。
|
|
34
|
+
*
|
|
35
|
+
* ── 🔴 409 族是**六个不同的处置**,禁合并 ───────────────────────────────────────────────────
|
|
36
|
+
* `resume.retain_off`(这条 run 压根没开留存 —— 与「留存过期了」是两回事)/ `resume.evicted` /
|
|
37
|
+
* `resume.cap` / `resume.session_not_found`(留存失效)/ `steering.still_running`(**它还在跑,
|
|
38
|
+
* 该 steer 不该 resume**)/ `steering.not_running`(本副本无匹配 / run 已 settle)。
|
|
39
|
+
* 判据是 `errorCode` 串(机器轴,开集),**不按 HTTP 数字分支**;未知码原样透传落诚实兜底臂。
|
|
40
|
+
*
|
|
41
|
+
* ── UNTRUSTED ───────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
* 收据 `note` 是 server 铸的文案(引擎会把子代名字拼进去,名字是 spawning model 的自由文本)——
|
|
43
|
+
* 呈前消毒 + 截长,只渲染。
|
|
44
|
+
*/
|
|
45
|
+
import type { SubagentSteerReceipt } from '@sema-agent/sdk';
|
|
46
|
+
/** 能力位键(server `routes/capabilities.ts` 的 `subagentResume`;SDK `Capabilities.subagentResume`)。 */
|
|
47
|
+
export declare const SUBAGENT_RESUME_CAP = "subagentResume";
|
|
48
|
+
/** 复活口在不在。未判/缺键 = false = 详情面不渲「继续」腿。 */
|
|
49
|
+
export declare function subagentResumeAvailable(baseUrl?: string | undefined): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* 「继续」腿的 resume 取址(1.0.74 终扫 P2-11)。
|
|
52
|
+
*
|
|
53
|
+
* 🔴 resume API 的 `target` 契约只认两种取址:委派工具调用的 **parentToolCallId**(一等精确取址,
|
|
54
|
+
* 同型并发子代唯一能分开的键)与**子代显示名**(同名多员 ⇒ server 409 `steering.ambiguous_target`)。
|
|
55
|
+
* 行 taskId 不是契约取址形 —— 传它只会得到一条不带替代路径的歧义/未知错。
|
|
56
|
+
* alias 台账(`registerSubagentAlias`,task_progress 双携建;taskToParent 无删除无淘汰,子代 settle
|
|
57
|
+
* 后仍可取)⇒ 精确取址优先;显示名仅回落(它可能是 'Task' 字面占位,同名多员的 409 由每格出路
|
|
58
|
+
* 文案接);两支皆缺 ⇒ undefined,调用方按诚实缺席**不渲入口**。
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveSubagentContinueTarget(rowId: string, displayName: string | undefined): string | undefined;
|
|
61
|
+
/** 本层消费的 `client.runs` 切片(注入缝)。 */
|
|
62
|
+
export interface SubagentResumeFacade {
|
|
63
|
+
resumeSubagent(taskId: string, target: string, message: {
|
|
64
|
+
content: string;
|
|
65
|
+
}, opts?: {
|
|
66
|
+
session?: string;
|
|
67
|
+
signal?: AbortSignal;
|
|
68
|
+
}): Promise<SubagentSteerReceipt>;
|
|
69
|
+
}
|
|
70
|
+
export type SubagentResumeFailureKind =
|
|
71
|
+
/** 没有 live 引擎 / 没有在飞 run —— 连地址都拼不出来(与「引擎拒了」是两回事)。 */
|
|
72
|
+
'no-wire' | 'no-run'
|
|
73
|
+
/** `resume.retain_off` —— 这条 run 没开 retainSubagentSessions(改配置才有救,重试无用)。 */
|
|
74
|
+
| 'retain-off'
|
|
75
|
+
/** `resume.evicted` / `resume.cap` / `resume.session_not_found` —— 留存**曾经在**,现在没了。 */
|
|
76
|
+
| 'retention-lapsed'
|
|
77
|
+
/** `steering.still_running` —— 它还在跑:该 steer,不该 resume(唯一「换个动作就成」的一格)。 */
|
|
78
|
+
| 'still-running'
|
|
79
|
+
/** `steering.not_running` —— 本副本无匹配 / run 已 settle。 */
|
|
80
|
+
| 'not-resumable'
|
|
81
|
+
/** `steering.ambiguous_target` —— 按名取址撞上同名多员(取址口径本身可分,是唯一「换个地址就成」的一格)。 */
|
|
82
|
+
| 'ambiguous-target'
|
|
83
|
+
/** 404 —— 未知 run / 非属主(**无存在性谕示**,两者同形)。 */
|
|
84
|
+
| 'not-found'
|
|
85
|
+
/** 400 —— 空 content 等入参问题。 */
|
|
86
|
+
| 'bad-request'
|
|
87
|
+
/** 开集兜底。 */
|
|
88
|
+
| 'error';
|
|
89
|
+
export type SubagentResumeOutcome = {
|
|
90
|
+
ok: true;
|
|
91
|
+
receipt: string;
|
|
92
|
+
} | {
|
|
93
|
+
ok: false;
|
|
94
|
+
reason: SubagentResumeFailureKind;
|
|
95
|
+
detail: string;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* resume 失败 → 处置分类。机器轴 = `errorCode`(开集);`status` 只用于**码缺席**时的两格粗分
|
|
99
|
+
* (404 无存在性谕示 / 400 入参),绝不用数字去猜某个具体 409 成因(六个 409 只有 `errorCode`
|
|
100
|
+
* 分得开,按数字分支等于把六种处置压成一种)。
|
|
101
|
+
*/
|
|
102
|
+
export declare function classifySubagentResumeFailure(e: unknown): {
|
|
103
|
+
reason: SubagentResumeFailureKind;
|
|
104
|
+
detail: string;
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* 「这一行子代该打到哪条 run」——**纯函数,单点判据**(对抗复审 H1 的修 + 它的可证伪点)。
|
|
108
|
+
*
|
|
109
|
+
* 🔴 **按行取,不取「此刻在飞的那条」**:`activeEngineRunId()` 是 turn-scoped(壳 `seamQuery` 在
|
|
110
|
+
* turn 收尾 `setActiveEngineTaskId(null)`),而「继续」这个动作按定义只在子代**已 settle** 之后
|
|
111
|
+
* 才出现 —— 那时它恒空;更坏的一支是另一条 turn 正飞时它有值但**是别的 run**,请求会打到不
|
|
112
|
+
* 相干的 run 上(404,或叫醒那条 run 里的同名子代)。判据必须锚在决定量(这一行的宿主 run)
|
|
113
|
+
* 上,不是锚在「有没有 run 在飞」这个前置条件上([anchor-on-the-deciding-quantity])。
|
|
114
|
+
*
|
|
115
|
+
* 三态:
|
|
116
|
+
* · 台账有这一行 ⇒ 用台账值(**哪怕此刻另有 run 在飞**);
|
|
117
|
+
* · 台账没有、且调用方**指名了某一行** ⇒ `undefined`(诚实不知道 ⇒ no-run 出路),绝不回落成在飞的那条;
|
|
118
|
+
* · 调用方**没指名行**(无行上下文的通用调用)⇒ 才回落到在飞的那条。
|
|
119
|
+
*
|
|
120
|
+
* @param activeRunId 注入缝(缺省 = `activeEngineRunId()`)—— 判据要能在没有 SessionPort 的纯层里验。
|
|
121
|
+
*/
|
|
122
|
+
export declare function resolveOwnerRunId(childTaskId?: string, activeRunId?: string | undefined): string | undefined;
|
|
123
|
+
/** 一次 resume 调用需要的三件:facade(null = 无 wire)、宿主 run(undefined = 诚实缺席)、会话参数。 */
|
|
124
|
+
export interface SubagentResumeContext {
|
|
125
|
+
facade: SubagentResumeFacade | null;
|
|
126
|
+
runId: string | undefined;
|
|
127
|
+
session: Record<string, string>;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 产品 facade + 运行定位。**逐字复用 `steerEngineSubagent` 的三步**(engineWireTarget → 无则 no-wire;
|
|
131
|
+
* 取运行 → 无则 no-run;makeEngineWireClient)—— 两条腿共用同一套装配,免得 steer 能到达而 resume
|
|
132
|
+
* 到不了(或反过来),那种不对称在用户面上表现为「同一个 agent 有时能说话有时不能」。
|
|
133
|
+
*
|
|
134
|
+
* 🔴 `session` 的三态(#242 批 2 定口径,codex 复审 medium 收紧):
|
|
135
|
+
* · 台账有这一行**且**捕到了它的会话 ⇒ 用**行登记的**会话(resume 是 per-id 读面,配错的
|
|
136
|
+
* `{runId, session}` 组合在 session-bound run 上 fail-closed 404);
|
|
137
|
+
* · 台账**有这一行但没捕到会话**(fleet 行帧腿/通知腿今天不传 session)⇒ **不带 `?session=`**,
|
|
138
|
+
* 🔴 **绝不拿「此刻的会话」去猜** —— resume 是有副作用的动作(AT-MOST-ONCE,叫醒 = 真跑一轮),
|
|
139
|
+
* 猜错会话可能落到**另一个会话的同名子代**上,那是不可回收的伤害;不带参数的失败只是 server
|
|
140
|
+
* fail-closed 的干净 404,已有 `not-found` 出路文案接。与本模块「错值比缺席更坏」同一条口径。
|
|
141
|
+
* · 调用方**完全没指名行**(无行上下文的通用调用)⇒ 才退当前会话(既有口径不变)。
|
|
142
|
+
* ⚠️ 供给面欠账:fleet/通知两腿手里没有流会话,补上之后第二态自然变成第一态,判据无需再改。
|
|
143
|
+
*/
|
|
144
|
+
export declare function resolveSubagentResumeContext(childTaskId?: string): SubagentResumeContext;
|
|
145
|
+
/**
|
|
146
|
+
* 叫醒一个已 settle 的保留子代。
|
|
147
|
+
*
|
|
148
|
+
* @param target parentToolCallId 或 agentName(与 `steerEngineSubagent` 同一取址口径;
|
|
149
|
+
* 同名多个活体 ⇒ server 409 `steering.ambiguous_target`)。
|
|
150
|
+
* 🔴 AT-MOST-ONCE:server 明写不重试。本层因此**不重试任何一格** —— 「叫醒」是有副作用的动作,
|
|
151
|
+
* 重投一次可能真的多跑一轮。
|
|
152
|
+
*/
|
|
153
|
+
export declare function resumeSettledSubagent(facade: SubagentResumeFacade | null, runId: string | undefined, target: string, content: string, opts?: {
|
|
154
|
+
session?: string;
|
|
155
|
+
signal?: AbortSignal;
|
|
156
|
+
}): Promise<SubagentResumeOutcome>;
|
|
157
|
+
/**
|
|
158
|
+
* {@link auditRetainWithoutWake} 的判决(codex 复审 low 收紧的**返回契约**)。
|
|
159
|
+
*
|
|
160
|
+
* 两位分别回答两个不同的问题,**不许合成一个**:
|
|
161
|
+
* · `message` = 组合成不成立(成立才有这条记录;`undefined` 返回 = 不成立);
|
|
162
|
+
* · `emitted` = **本次**响没响(per-baseUrl once 去重命中时为 `false`)。
|
|
163
|
+
* 旧形(去重命中仍回正文)有双义:按返回值呈现的调用方会**重复提示**,而「至多一次」的承诺
|
|
164
|
+
* 只兑现在日志上。调用方要「只提示一次」就看 `emitted`,要「组合还成不成立」就看有没有记录。
|
|
165
|
+
*/
|
|
166
|
+
export interface RetainWithoutWakeVerdict {
|
|
167
|
+
message: string;
|
|
168
|
+
emitted: boolean;
|
|
169
|
+
}
|
|
170
|
+
/** {@link auditRetainWithoutWake} 的入参(命名形:导出签名里不留 ≥3 成员的内联匿名形)。 */
|
|
171
|
+
export interface RetainWithoutWakeInput {
|
|
172
|
+
baseUrl?: string | undefined;
|
|
173
|
+
/**
|
|
174
|
+
* 本端的 `ClientSliceLike` 实现(或它的任意子集)。类型刻意窄到 `wakeSubagent` 这一位的**函数
|
|
175
|
+
* 形**而不是 `unknown` —— 判据是「这个 verb 实现了没有」,而 `unknown` 会把「端传了个字符串」
|
|
176
|
+
* 也算成合法入参(边界层的 unknown 必须在本模块内窄化,typeshape 门守的正是这条)。
|
|
177
|
+
*/
|
|
178
|
+
slice?: {
|
|
179
|
+
wakeSubagent?: ((input: {
|
|
180
|
+
taskId: string;
|
|
181
|
+
message: string;
|
|
182
|
+
}) => Promise<void>) | undefined;
|
|
183
|
+
} | null | undefined;
|
|
184
|
+
retainRequested?: boolean | undefined;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* 「引擎有复活口 + 本端在付留存成本 + 本端没有 wake 实现」⇒ 响亮一条(每 baseUrl 至多一次)。
|
|
188
|
+
*
|
|
189
|
+
* 返回 {@link RetainWithoutWakeVerdict}:`undefined` = 组合不成立(没这笔账);有记录 = 组合成立,
|
|
190
|
+
* 其中 `emitted` 说明**本次**响没响。**返回值即判据** —— 只落日志的告警在测试里等于不存在,
|
|
191
|
+
* 而只回一个字符串又分不出「本次响了」与「早响过了」(按返回值呈现的调用方会重复提示)。
|
|
192
|
+
*
|
|
193
|
+
* @param slice 本端的 `ClientSliceLike` 实现(或它的任意子集);`wakeSubagent` 不是函数即算未实现。
|
|
194
|
+
* @param retainRequested 本端 run 请求里的 `retainSubagentSessions`(缺省 true = 包内 taskRequest 的缺省)。
|
|
195
|
+
*/
|
|
196
|
+
export declare function auditRetainWithoutWake(input: RetainWithoutWakeInput): RetainWithoutWakeVerdict | undefined;
|
|
197
|
+
/** 测试钩:清 once 去重台账。 */
|
|
198
|
+
export declare function __resetRetainWithoutWakeWarningForTests(): void;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { hostLog } from '../host.js';
|
|
2
|
+
import { engineCapTrue } from '../engineCapsCache.js';
|
|
3
|
+
import { makeEngineWireClient } from '../engineWireSdk.js';
|
|
4
|
+
import { engineWireTarget } from '../engineWireTarget.js';
|
|
5
|
+
import { activeEngineRunId, engineSessionParamSpread } from '../engineSessionParam.js';
|
|
6
|
+
import { getBgParentRunOwner, parentToolCallIdOf, } from '../subagentContentStore.js';
|
|
7
|
+
/** 能力位键(server `routes/capabilities.ts` 的 `subagentResume`;SDK `Capabilities.subagentResume`)。 */
|
|
8
|
+
export const SUBAGENT_RESUME_CAP = 'subagentResume';
|
|
9
|
+
/** 复活口在不在。未判/缺键 = false = 详情面不渲「继续」腿。 */
|
|
10
|
+
export function subagentResumeAvailable(baseUrl = engineWireTarget()?.baseUrl) {
|
|
11
|
+
return engineCapTrue(baseUrl, SUBAGENT_RESUME_CAP);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 「继续」腿的 resume 取址(1.0.74 终扫 P2-11)。
|
|
15
|
+
*
|
|
16
|
+
* 🔴 resume API 的 `target` 契约只认两种取址:委派工具调用的 **parentToolCallId**(一等精确取址,
|
|
17
|
+
* 同型并发子代唯一能分开的键)与**子代显示名**(同名多员 ⇒ server 409 `steering.ambiguous_target`)。
|
|
18
|
+
* 行 taskId 不是契约取址形 —— 传它只会得到一条不带替代路径的歧义/未知错。
|
|
19
|
+
* alias 台账(`registerSubagentAlias`,task_progress 双携建;taskToParent 无删除无淘汰,子代 settle
|
|
20
|
+
* 后仍可取)⇒ 精确取址优先;显示名仅回落(它可能是 'Task' 字面占位,同名多员的 409 由每格出路
|
|
21
|
+
* 文案接);两支皆缺 ⇒ undefined,调用方按诚实缺席**不渲入口**。
|
|
22
|
+
*/
|
|
23
|
+
export function resolveSubagentContinueTarget(rowId, displayName) {
|
|
24
|
+
const precise = parentToolCallIdOf(rowId);
|
|
25
|
+
if (precise !== undefined && precise !== '')
|
|
26
|
+
return precise;
|
|
27
|
+
const name = typeof displayName === 'string' ? displayName.trim() : '';
|
|
28
|
+
return name !== '' ? name : undefined;
|
|
29
|
+
}
|
|
30
|
+
/** 留存失效族(三码同处置:留存曾经在、现在没了;文案一致,留痕分得开)。 */
|
|
31
|
+
const RETENTION_LAPSED_CODES = new Set([
|
|
32
|
+
'resume.evicted',
|
|
33
|
+
'resume.cap',
|
|
34
|
+
'resume.session_not_found',
|
|
35
|
+
]);
|
|
36
|
+
function shapeOf(e) {
|
|
37
|
+
const o = (e ?? {});
|
|
38
|
+
return {
|
|
39
|
+
...(typeof o.status === 'number' ? { status: o.status } : {}),
|
|
40
|
+
...(typeof o.errorCode === 'string' ? { errorCode: o.errorCode } : {}),
|
|
41
|
+
message: typeof o.message === 'string' && o.message !== '' ? o.message : String(e),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* resume 失败 → 处置分类。机器轴 = `errorCode`(开集);`status` 只用于**码缺席**时的两格粗分
|
|
46
|
+
* (404 无存在性谕示 / 400 入参),绝不用数字去猜某个具体 409 成因(六个 409 只有 `errorCode`
|
|
47
|
+
* 分得开,按数字分支等于把六种处置压成一种)。
|
|
48
|
+
*/
|
|
49
|
+
export function classifySubagentResumeFailure(e) {
|
|
50
|
+
const { status, errorCode, message } = shapeOf(e);
|
|
51
|
+
if (errorCode === 'resume.retain_off')
|
|
52
|
+
return { reason: 'retain-off', detail: message };
|
|
53
|
+
if (errorCode !== undefined && RETENTION_LAPSED_CODES.has(errorCode)) {
|
|
54
|
+
return { reason: 'retention-lapsed', detail: message };
|
|
55
|
+
}
|
|
56
|
+
if (errorCode === 'steering.still_running')
|
|
57
|
+
return { reason: 'still-running', detail: message };
|
|
58
|
+
if (errorCode === 'steering.not_running')
|
|
59
|
+
return { reason: 'not-resumable', detail: message };
|
|
60
|
+
if (errorCode === 'steering.ambiguous_target')
|
|
61
|
+
return { reason: 'ambiguous-target', detail: message };
|
|
62
|
+
if (errorCode === undefined && status === 404)
|
|
63
|
+
return { reason: 'not-found', detail: message };
|
|
64
|
+
if (errorCode === undefined && status === 400)
|
|
65
|
+
return { reason: 'bad-request', detail: message };
|
|
66
|
+
if (errorCode?.startsWith('not_found.') === true)
|
|
67
|
+
return { reason: 'not-found', detail: message };
|
|
68
|
+
return { reason: 'error', detail: message };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 「这一行子代该打到哪条 run」——**纯函数,单点判据**(对抗复审 H1 的修 + 它的可证伪点)。
|
|
72
|
+
*
|
|
73
|
+
* 🔴 **按行取,不取「此刻在飞的那条」**:`activeEngineRunId()` 是 turn-scoped(壳 `seamQuery` 在
|
|
74
|
+
* turn 收尾 `setActiveEngineTaskId(null)`),而「继续」这个动作按定义只在子代**已 settle** 之后
|
|
75
|
+
* 才出现 —— 那时它恒空;更坏的一支是另一条 turn 正飞时它有值但**是别的 run**,请求会打到不
|
|
76
|
+
* 相干的 run 上(404,或叫醒那条 run 里的同名子代)。判据必须锚在决定量(这一行的宿主 run)
|
|
77
|
+
* 上,不是锚在「有没有 run 在飞」这个前置条件上([anchor-on-the-deciding-quantity])。
|
|
78
|
+
*
|
|
79
|
+
* 三态:
|
|
80
|
+
* · 台账有这一行 ⇒ 用台账值(**哪怕此刻另有 run 在飞**);
|
|
81
|
+
* · 台账没有、且调用方**指名了某一行** ⇒ `undefined`(诚实不知道 ⇒ no-run 出路),绝不回落成在飞的那条;
|
|
82
|
+
* · 调用方**没指名行**(无行上下文的通用调用)⇒ 才回落到在飞的那条。
|
|
83
|
+
*
|
|
84
|
+
* @param activeRunId 注入缝(缺省 = `activeEngineRunId()`)—— 判据要能在没有 SessionPort 的纯层里验。
|
|
85
|
+
*/
|
|
86
|
+
export function resolveOwnerRunId(childTaskId, activeRunId = activeEngineRunId() ?? undefined) {
|
|
87
|
+
const owner = getBgParentRunOwner(childTaskId);
|
|
88
|
+
if (owner !== undefined)
|
|
89
|
+
return owner.runId;
|
|
90
|
+
if (childTaskId !== undefined && childTaskId !== '')
|
|
91
|
+
return undefined;
|
|
92
|
+
return activeRunId;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 产品 facade + 运行定位。**逐字复用 `steerEngineSubagent` 的三步**(engineWireTarget → 无则 no-wire;
|
|
96
|
+
* 取运行 → 无则 no-run;makeEngineWireClient)—— 两条腿共用同一套装配,免得 steer 能到达而 resume
|
|
97
|
+
* 到不了(或反过来),那种不对称在用户面上表现为「同一个 agent 有时能说话有时不能」。
|
|
98
|
+
*
|
|
99
|
+
* 🔴 `session` 的三态(#242 批 2 定口径,codex 复审 medium 收紧):
|
|
100
|
+
* · 台账有这一行**且**捕到了它的会话 ⇒ 用**行登记的**会话(resume 是 per-id 读面,配错的
|
|
101
|
+
* `{runId, session}` 组合在 session-bound run 上 fail-closed 404);
|
|
102
|
+
* · 台账**有这一行但没捕到会话**(fleet 行帧腿/通知腿今天不传 session)⇒ **不带 `?session=`**,
|
|
103
|
+
* 🔴 **绝不拿「此刻的会话」去猜** —— resume 是有副作用的动作(AT-MOST-ONCE,叫醒 = 真跑一轮),
|
|
104
|
+
* 猜错会话可能落到**另一个会话的同名子代**上,那是不可回收的伤害;不带参数的失败只是 server
|
|
105
|
+
* fail-closed 的干净 404,已有 `not-found` 出路文案接。与本模块「错值比缺席更坏」同一条口径。
|
|
106
|
+
* · 调用方**完全没指名行**(无行上下文的通用调用)⇒ 才退当前会话(既有口径不变)。
|
|
107
|
+
* ⚠️ 供给面欠账:fleet/通知两腿手里没有流会话,补上之后第二态自然变成第一态,判据无需再改。
|
|
108
|
+
*/
|
|
109
|
+
export function resolveSubagentResumeContext(childTaskId) {
|
|
110
|
+
const cfg = engineWireTarget();
|
|
111
|
+
if (!cfg)
|
|
112
|
+
return { facade: null, runId: undefined, session: {} };
|
|
113
|
+
const client = makeEngineWireClient({
|
|
114
|
+
baseUrl: cfg.baseUrl,
|
|
115
|
+
...(cfg.token ? { token: cfg.token } : {}),
|
|
116
|
+
...(cfg.principal !== undefined ? { principal: cfg.principal } : {}),
|
|
117
|
+
});
|
|
118
|
+
if (!client)
|
|
119
|
+
return { facade: null, runId: undefined, session: {} };
|
|
120
|
+
const named = childTaskId !== undefined && childTaskId !== '';
|
|
121
|
+
const owner = getBgParentRunOwner(childTaskId);
|
|
122
|
+
const sessionFromRow = owner?.sessionId;
|
|
123
|
+
const session = sessionFromRow !== undefined
|
|
124
|
+
? { session: sessionFromRow }
|
|
125
|
+
: named
|
|
126
|
+
? {} // 指名了行却没有它的会话 ⇒ 诚实缺席,绝不用当前会话冒充
|
|
127
|
+
: engineSessionParamSpread();
|
|
128
|
+
return { facade: client.runs, runId: resolveOwnerRunId(childTaskId), session };
|
|
129
|
+
}
|
|
130
|
+
/** UNTRUSTED 串的呈前消毒。 */
|
|
131
|
+
function clean(v, max = 200) {
|
|
132
|
+
// eslint-disable-next-line no-control-regex
|
|
133
|
+
return String(v ?? '').replace(/[\x00-\x1f\x7f]/g, '.').slice(0, max);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 叫醒一个已 settle 的保留子代。
|
|
137
|
+
*
|
|
138
|
+
* @param target parentToolCallId 或 agentName(与 `steerEngineSubagent` 同一取址口径;
|
|
139
|
+
* 同名多个活体 ⇒ server 409 `steering.ambiguous_target`)。
|
|
140
|
+
* 🔴 AT-MOST-ONCE:server 明写不重试。本层因此**不重试任何一格** —— 「叫醒」是有副作用的动作,
|
|
141
|
+
* 重投一次可能真的多跑一轮。
|
|
142
|
+
*/
|
|
143
|
+
export async function resumeSettledSubagent(facade, runId, target, content, opts) {
|
|
144
|
+
if (facade === null)
|
|
145
|
+
return { ok: false, reason: 'no-wire', detail: 'no engine wire' };
|
|
146
|
+
if (runId === undefined || runId === '')
|
|
147
|
+
return { ok: false, reason: 'no-run', detail: 'no active engine run' };
|
|
148
|
+
const text = content.trim();
|
|
149
|
+
if (text === '')
|
|
150
|
+
return { ok: false, reason: 'bad-request', detail: 'empty message' };
|
|
151
|
+
try {
|
|
152
|
+
const receipt = await facade.resumeSubagent(runId, target, { content: text }, opts);
|
|
153
|
+
const note = receipt?.note;
|
|
154
|
+
return {
|
|
155
|
+
ok: true,
|
|
156
|
+
receipt: typeof note === 'string' && note !== ''
|
|
157
|
+
? clean(note, 240)
|
|
158
|
+
: 'Message queued for delivery; the agent will continue in the background.',
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch (e) {
|
|
162
|
+
const f = classifySubagentResumeFailure(e);
|
|
163
|
+
hostLog('debug', `engineSubagentResume: resume(${clean(target, 64)}) → ${f.reason} (${clean(f.detail, 160)})`);
|
|
164
|
+
return { ok: false, ...f };
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// ── caps × verb 组合告警([4000] Q5 裁定的另一半)────────────────────────────────────────────
|
|
168
|
+
// Q5 裁定:`ClientSliceLike.wakeSubagent` 在 `CLIENT_VERBS` 表里**维持 `required:false`**
|
|
169
|
+
// (fail-soft —— 不实现只是少一个入口,不丢已发生的行为);但「引擎这边留存在付费、客户端这边
|
|
170
|
+
// 一个兑现口都没有」这个**组合**必须响亮说出来,否则它就是一笔谁也看不见的常驻成本。
|
|
171
|
+
//
|
|
172
|
+
// 🔴 键名订正(勘设计稿措辞):caps 上**没有** `retainSubagentSessions` 这个位 ——
|
|
173
|
+
// `retainSubagentSessions` 是**run 请求位**(`request/taskRequest.ts:118/320`,壳默认 true),
|
|
174
|
+
// 引擎宣示兑现口的 caps 位是 **`subagentResume`**(SDK `Capabilities.subagentResume`,
|
|
175
|
+
// `types.d.ts:1879` 逐字「还需 run 侧 retainSubagentSessions」)。所以本告警的判据是
|
|
176
|
+
// **两半都在场**:caps 宣示 `subagentResume` ∧ 本端的 run 请求真在打 retain ⇒ 成本真在付。
|
|
177
|
+
const warnedRetainWithoutWake = new Set();
|
|
178
|
+
/**
|
|
179
|
+
* 「引擎有复活口 + 本端在付留存成本 + 本端没有 wake 实现」⇒ 响亮一条(每 baseUrl 至多一次)。
|
|
180
|
+
*
|
|
181
|
+
* 返回 {@link RetainWithoutWakeVerdict}:`undefined` = 组合不成立(没这笔账);有记录 = 组合成立,
|
|
182
|
+
* 其中 `emitted` 说明**本次**响没响。**返回值即判据** —— 只落日志的告警在测试里等于不存在,
|
|
183
|
+
* 而只回一个字符串又分不出「本次响了」与「早响过了」(按返回值呈现的调用方会重复提示)。
|
|
184
|
+
*
|
|
185
|
+
* @param slice 本端的 `ClientSliceLike` 实现(或它的任意子集);`wakeSubagent` 不是函数即算未实现。
|
|
186
|
+
* @param retainRequested 本端 run 请求里的 `retainSubagentSessions`(缺省 true = 包内 taskRequest 的缺省)。
|
|
187
|
+
*/
|
|
188
|
+
export function auditRetainWithoutWake(input) {
|
|
189
|
+
const baseUrl = input.baseUrl ?? engineWireTarget()?.baseUrl;
|
|
190
|
+
if (!engineCapTrue(baseUrl, SUBAGENT_RESUME_CAP))
|
|
191
|
+
return undefined;
|
|
192
|
+
if (input.retainRequested === false)
|
|
193
|
+
return undefined;
|
|
194
|
+
if (typeof input.slice?.wakeSubagent === 'function')
|
|
195
|
+
return undefined;
|
|
196
|
+
const message = 'client-core: engine advertises capabilities.subagentResume and this client requests ' +
|
|
197
|
+
'retainSubagentSessions, but the client slice implements no wakeSubagent verb — retained ' +
|
|
198
|
+
'subagent sessions are being paid for with no way to redeem them (CLIENT_VERBS.wakeSubagent ' +
|
|
199
|
+
'stays fail-soft by design; wire an entry point or stop requesting retention).';
|
|
200
|
+
const key = baseUrl ?? '<no-base>';
|
|
201
|
+
if (warnedRetainWithoutWake.has(key))
|
|
202
|
+
return { message, emitted: false };
|
|
203
|
+
warnedRetainWithoutWake.add(key);
|
|
204
|
+
hostLog('warn', message);
|
|
205
|
+
return { message, emitted: true };
|
|
206
|
+
}
|
|
207
|
+
/** 测试钩:清 once 去重台账。 */
|
|
208
|
+
export function __resetRetainWithoutWakeWarningForTests() {
|
|
209
|
+
warnedRetainWithoutWake.clear();
|
|
210
|
+
}
|
|
@@ -114,6 +114,11 @@ export function tailEngineSubagent(taskId) {
|
|
|
114
114
|
const cfg = engineWireTarget();
|
|
115
115
|
if (!cfg)
|
|
116
116
|
return;
|
|
117
|
+
// 🔴 候裁([4000] Q3 / design-242 §5 Q3,#242 批 2 **刻意不动**):这条 `?? activeEngineRunId()`
|
|
118
|
+
// 与 resume 腿的 `resolveOwnerRunId`(指名了行就诚实缺席,绝不回落)口径**相反**。壳侧对抗
|
|
119
|
+
// 复审的结论是「错值比缺席更坏」;统一到那个口径 ⇒ 台账缺席时本腿从「能连上(可能连错 run)」
|
|
120
|
+
// 变成「不连」= 真行为翻面,需三端(cli/web/desktop)表态后单批改。同族另两处:
|
|
121
|
+
// `engineTaskHandleWire.ts:resolveHostRun` / `engineSubagentOutput.ts:fetchEngineSubagentReport`。
|
|
117
122
|
const runId = getBgParentRun(taskId) ?? activeEngineRunId();
|
|
118
123
|
if (!runId)
|
|
119
124
|
return;
|
|
@@ -57,6 +57,9 @@ import { STOP_CONFLICT_CODES, STOP_NOT_LANDED, STOP_NOT_LOCAL, STOP_PARKED, STOP
|
|
|
57
57
|
/** 宿主 run 解析:fleet 行/通知帧喂的 parent 映射优先(行 id=`${runId} ${handle}` 的 parentId
|
|
58
58
|
* 投影),缺席退当前交互 run(register 时刻的活跃 run 即宿主——engineSide bash 行的出生形)。 */
|
|
59
59
|
function resolveHostRun(handle) {
|
|
60
|
+
// 🔴 候裁([4000] Q3 / design-242 §5 Q3,#242 批 2 **刻意不动**):这条回落与 resume 腿的
|
|
61
|
+
// `resolveOwnerRunId`(指名了行就诚实缺席)口径相反;统一口径是真行为翻面,候三端表态。
|
|
62
|
+
// 同族另两处:`engineSubagentTail.ts:tailEngineSubagent` / `engineSubagentOutput.ts`。
|
|
60
63
|
return getBgParentRun(handle) ?? activeEngineRunId() ?? null;
|
|
61
64
|
}
|
|
62
65
|
// 游标累积(按 handle;进程内存态,与查看态生命周期同级——端重启即空,重读从引擎再取)
|
|
@@ -192,8 +192,56 @@ export interface BgTerminalFacts {
|
|
|
192
192
|
}
|
|
193
193
|
export declare function recordBgTerminalFacts(taskId: string, facts: BgTerminalFacts): void;
|
|
194
194
|
export declare function getBgTerminalFacts(taskId: string): BgTerminalFacts | undefined;
|
|
195
|
-
|
|
195
|
+
/** 一行子代的宿主记录。`sessionId` = 登记时刻那条流的会话(per-id 读面要拼 `?session=`)。 */
|
|
196
|
+
export interface SubagentOwnerRecord {
|
|
197
|
+
/** 宿主引擎 run id(resume / subagentOutput / tail 三条读面寻址的第一段)。 */
|
|
198
|
+
runId: string;
|
|
199
|
+
/** 登记时的会话 id;缺席即不带 `?session=`(空串按「带了且不匹配」处理 = 404,恒不铸空串)。 */
|
|
200
|
+
sessionId?: string;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* fleet 行帧 / bg 通知腿的登记口(既有两写点)。
|
|
204
|
+
* `sessionId` 是 #242 批 2 additive 补位:缺席则不落键(读面据此判「不带 `?session=`」)。
|
|
205
|
+
*/
|
|
206
|
+
export declare function recordBgParentRun(taskId: string, runId: string, sessionId?: string): void;
|
|
207
|
+
/**
|
|
208
|
+
* 「这一行子代的宿主 run」统一登记口(**显式传值**,零推断,见本段头注)。
|
|
209
|
+
* `runId` 缺席/空串 ⇒ 什么都不记(宁可「不知道」也不记一个错的宿主);`taskId === runId`
|
|
210
|
+
* 的退化自指映射同样拒收(顶层 run 行不是任何人的子代)。
|
|
211
|
+
*
|
|
212
|
+
* 🔴 **`sessionId` 的缺席不覆盖在场**(合表的承重口径,#242 批 2 自查抓出):喂本表的三条腿里
|
|
213
|
+
* **只有 tick 腿带 session**(它在开流时一次性捕获),fleet 行帧腿与 bg 通知腿都不带。合表前
|
|
214
|
+
* 两表各记各的,合表后若让「后到的无 session 写」直接盖掉整条记录,tick 捕获的会话就会被抹掉,
|
|
215
|
+
* resume 退回「此刻的会话」—— 那正是对抗复审轮5 M1 要防的错组合(旧 runId + 新 sessionId ⇒
|
|
216
|
+
* session-bound run 上 fail-closed 404)。
|
|
217
|
+
* 口径:**同一 `runId` 上缺席不覆盖在场**(带值的写照常赢);`runId` 真变了 ⇒ 旧 session 必须丢
|
|
218
|
+
* (那是另一条宿主 run 的会话,留着就是错组合)。
|
|
219
|
+
*/
|
|
220
|
+
export declare function recordSubagentOwner(taskId: string, runId?: string, sessionId?: string): void;
|
|
221
|
+
/**
|
|
222
|
+
* `task_progress` 腿的登记口(#242 批 2 ①:收编前本表**零 task_progress 腿**,已直证)。
|
|
223
|
+
*
|
|
224
|
+
* 一条帧进,认得就记,认不得就什么都不做(**绝不改帧**)。只认 `task_progress` —— 引擎侧子代的
|
|
225
|
+
* 累计 tick,带子代自己的 `taskId`。
|
|
226
|
+
*
|
|
227
|
+
* 🔴 **`ownerRunId` / `ownerSessionId` 必须由调用方显式喂**,本函数**不许**从
|
|
228
|
+
* `activeEngineRunId()` 推断(壳 `subagentOwnerLedger.ts:20-22` 对抗复审轮3 H2 实撞的教训:
|
|
229
|
+
* 进程级值被并发 main/fork 流互相覆写 + 每个 query 的 finally 清空 ⇒ run A 的 tick 可能被
|
|
230
|
+
* 登记到 run B 名下,而错值比缺席更坏)。登记点必须在**持有 stream-local run id 的那个闭包**
|
|
231
|
+
* 里(壳 = `liveClient.ts` 的 `stampSubagentOwners`),把不可变的值传进来。
|
|
232
|
+
* 🔴 `ownerSessionId` 同理**开流时一次性捕获**,不逐帧现读:逐帧现读会让 `/clear`、resume、
|
|
233
|
+
* 会话切换之后晚到的旧流 tick 被登记成 `{旧 runId, 新 sessionId}`,这对组合去 resume 会带着
|
|
234
|
+
* 错 session 打旧 run,fail-closed 404。
|
|
235
|
+
*/
|
|
236
|
+
export declare function recordSubagentOwnerFromProgress(ev: unknown, ownerRunId?: string, ownerSessionId?: string): void;
|
|
196
237
|
export declare function getBgParentRun(taskId: string): string | undefined;
|
|
238
|
+
/**
|
|
239
|
+
* 整条宿主记录(runId + 登记时会话)。undefined = **诚实不知道**,调用方渲 no-run 出路 /
|
|
240
|
+
* 不渲入口 —— 绝不在这里回落成「此刻在飞的那条 run」([anchor-on-the-deciding-quantity])。
|
|
241
|
+
*/
|
|
242
|
+
export declare function getBgParentRunOwner(taskId: string | undefined): SubagentOwnerRecord | undefined;
|
|
243
|
+
/** 测试钩:清宿主台账(module 级单例,同进程多组断言必须能清)。 */
|
|
244
|
+
export declare function __resetSubagentOwnerLedgerForTests(): void;
|
|
197
245
|
export declare function recordOwnEngineRun(runId: string): void;
|
|
198
246
|
export declare function isOwnEngineRun(runId: string): boolean;
|
|
199
247
|
/** Single consumer (the panel hook). Returns an unsubscribe. */
|
|
@@ -358,23 +358,119 @@ export function getBgTerminalFacts(taskId) {
|
|
|
358
358
|
}
|
|
359
359
|
// ── bg 子代 → spawn 它的 leader run(server 1.244 subagentOutput 读面的寻址元组)──────────────
|
|
360
360
|
// GET /v1/runs/:runId/subagents/:handle/output 的 runId = spawn 该子代的 run(registry access 从
|
|
361
|
-
// run 行推导,[1491]①)
|
|
361
|
+
// run 行推导,[1491]①)。来源三路:fleet 行 parentId(出生帧即带)+ bg_notification.parentTaskId
|
|
362
|
+
// + `task_progress` tick(#242 批 2 新腿,见下)。
|
|
363
|
+
//
|
|
364
|
+
// 🔴 #242 批 2(A-028.6 收编,2026-08-15):本表**收编壳侧 `subagentOwnerLedger`**,成为
|
|
365
|
+
// 「这一行子代属于哪一条引擎 run」的**唯一台账**。收编前同一个问题有两份表、两个相反答案:
|
|
366
|
+
// · 壳表(cli `src/sema/subagentOwnerLedger.ts`,cap 256)由 `task_progress` tick 喂,
|
|
367
|
+
// 取址缺席时**诚实缺席**(指名了行就绝不回落到「此刻在飞的那条」);
|
|
368
|
+
// · 本表由 fleet 行帧(`fleet/fleetLedger.ts:394`)+ bg 通知(`:496`)喂,三处消费方一律
|
|
369
|
+
// `getBgParentRun(x) ?? activeEngineRunId()` 无条件回落。
|
|
370
|
+
// 两表覆盖面**互补**(壳表只覆盖发 tick 的子代;fleet-row lane 子代零 tick,只有本表覆盖),
|
|
371
|
+
// 所以合表不是二选一,是把两条 lane 的事实并进同一个答案。
|
|
372
|
+
// 键域两侧本就同域,合表零改键:fleet 腿写 `rowIdTail(row.id)` = 裸引擎 taskId、通知腿写
|
|
373
|
+
// `n.taskId` = registry handle(同一裸 id)、tick 腿写 `task_progress.taskId`。
|
|
374
|
+
// ⚠️ 那三处 `?? activeEngineRunId()` 回落口径**本批刻意不动**(engineSubagentTail.ts:172 /
|
|
375
|
+
// engineTaskHandleWire.ts:69 / engineSubagentOutput.ts:82),候 [4000] Q3 三端表态。
|
|
376
|
+
//
|
|
377
|
+
// 🔴 写入纪律(壳表头注 :20-22 的教训**原文搬**):宿主 run **必须由持有 stream-local 值的
|
|
378
|
+
// 调用方显式传入**,本模块绝不从进程级 `activeEngineRunId()` 推断 —— 那个值被并发的
|
|
379
|
+
// main/fork 流互相覆写、又被每个 query 的 finally 无条件清空,推断出来的宿主可能是**别的
|
|
380
|
+
// run**,而**错值比缺席更坏**(消费端的门只查「有没有值」,分辨不出错值:continue 会打到
|
|
381
|
+
// 不相干的 run,甚至叫醒那条 run 里的同名子代)。取不到 ⇒ 什么都不记。
|
|
382
|
+
//
|
|
383
|
+
// 🔴 有界:键来自 wire(不可信),无界表 = 引擎乱发 taskId 就能吃掉宿主内存。上限取**两表
|
|
384
|
+
// 上限之和**(壳表 256 + 本表原 `MAX_TASKS*2`=64)—— 合表**不许缩窄既有保障**:收编前两条
|
|
385
|
+
// lane 各有各的池子,能同时记 256 条 tick + 64 条 fleet;若按 256 钉,「tick 池已满 + 再来一条
|
|
386
|
+
// fleet 行」当场淘汰一条 tick,那条子代的 resume 入口凭空消失,而收编前它还在(codex 复审
|
|
387
|
+
// medium 实证形)。两读口的 LRU touch 也会让高频 fleet 行持续挤压 tick 历史,同一条道理。
|
|
388
|
+
// 满了按 LRU 淘汰,淘汰后果只是那一行退回「不知道宿主 run」= 诚实缺席,不是错值。
|
|
389
|
+
const MAX_BG_PARENT_RUNS = 320;
|
|
362
390
|
const bgParentRun = new Map();
|
|
363
|
-
|
|
364
|
-
|
|
391
|
+
/**
|
|
392
|
+
* fleet 行帧 / bg 通知腿的登记口(既有两写点)。
|
|
393
|
+
* `sessionId` 是 #242 批 2 additive 补位:缺席则不落键(读面据此判「不带 `?session=`」)。
|
|
394
|
+
*/
|
|
395
|
+
export function recordBgParentRun(taskId, runId, sessionId) {
|
|
396
|
+
recordSubagentOwner(taskId, runId, sessionId);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* 「这一行子代的宿主 run」统一登记口(**显式传值**,零推断,见本段头注)。
|
|
400
|
+
* `runId` 缺席/空串 ⇒ 什么都不记(宁可「不知道」也不记一个错的宿主);`taskId === runId`
|
|
401
|
+
* 的退化自指映射同样拒收(顶层 run 行不是任何人的子代)。
|
|
402
|
+
*
|
|
403
|
+
* 🔴 **`sessionId` 的缺席不覆盖在场**(合表的承重口径,#242 批 2 自查抓出):喂本表的三条腿里
|
|
404
|
+
* **只有 tick 腿带 session**(它在开流时一次性捕获),fleet 行帧腿与 bg 通知腿都不带。合表前
|
|
405
|
+
* 两表各记各的,合表后若让「后到的无 session 写」直接盖掉整条记录,tick 捕获的会话就会被抹掉,
|
|
406
|
+
* resume 退回「此刻的会话」—— 那正是对抗复审轮5 M1 要防的错组合(旧 runId + 新 sessionId ⇒
|
|
407
|
+
* session-bound run 上 fail-closed 404)。
|
|
408
|
+
* 口径:**同一 `runId` 上缺席不覆盖在场**(带值的写照常赢);`runId` 真变了 ⇒ 旧 session 必须丢
|
|
409
|
+
* (那是另一条宿主 run 的会话,留着就是错组合)。
|
|
410
|
+
*/
|
|
411
|
+
export function recordSubagentOwner(taskId, runId, sessionId) {
|
|
412
|
+
if (!taskId || typeof runId !== 'string' || runId === '' || taskId === runId)
|
|
365
413
|
return;
|
|
366
|
-
if (bgParentRun.size >=
|
|
414
|
+
if (bgParentRun.size >= MAX_BG_PARENT_RUNS && !bgParentRun.has(taskId)) {
|
|
367
415
|
const oldest = bgParentRun.keys().next().value;
|
|
368
416
|
if (oldest !== undefined)
|
|
369
417
|
bgParentRun.delete(oldest);
|
|
370
418
|
}
|
|
419
|
+
const prior = bgParentRun.get(taskId);
|
|
420
|
+
const kept = typeof sessionId === 'string' && sessionId !== ''
|
|
421
|
+
? sessionId
|
|
422
|
+
: (prior?.runId === runId ? prior.sessionId : undefined);
|
|
371
423
|
bgParentRun.delete(taskId); // LRU touch(F11):活跃映射不被容量淘汰挤掉 → 读面退错 runId
|
|
372
|
-
bgParentRun.set(taskId,
|
|
424
|
+
bgParentRun.set(taskId, {
|
|
425
|
+
runId,
|
|
426
|
+
...(kept !== undefined && kept !== '' ? { sessionId: kept } : {}),
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* `task_progress` 腿的登记口(#242 批 2 ①:收编前本表**零 task_progress 腿**,已直证)。
|
|
431
|
+
*
|
|
432
|
+
* 一条帧进,认得就记,认不得就什么都不做(**绝不改帧**)。只认 `task_progress` —— 引擎侧子代的
|
|
433
|
+
* 累计 tick,带子代自己的 `taskId`。
|
|
434
|
+
*
|
|
435
|
+
* 🔴 **`ownerRunId` / `ownerSessionId` 必须由调用方显式喂**,本函数**不许**从
|
|
436
|
+
* `activeEngineRunId()` 推断(壳 `subagentOwnerLedger.ts:20-22` 对抗复审轮3 H2 实撞的教训:
|
|
437
|
+
* 进程级值被并发 main/fork 流互相覆写 + 每个 query 的 finally 清空 ⇒ run A 的 tick 可能被
|
|
438
|
+
* 登记到 run B 名下,而错值比缺席更坏)。登记点必须在**持有 stream-local run id 的那个闭包**
|
|
439
|
+
* 里(壳 = `liveClient.ts` 的 `stampSubagentOwners`),把不可变的值传进来。
|
|
440
|
+
* 🔴 `ownerSessionId` 同理**开流时一次性捕获**,不逐帧现读:逐帧现读会让 `/clear`、resume、
|
|
441
|
+
* 会话切换之后晚到的旧流 tick 被登记成 `{旧 runId, 新 sessionId}`,这对组合去 resume 会带着
|
|
442
|
+
* 错 session 打旧 run,fail-closed 404。
|
|
443
|
+
*/
|
|
444
|
+
export function recordSubagentOwnerFromProgress(ev, ownerRunId, ownerSessionId) {
|
|
445
|
+
if (typeof ev !== 'object' || ev === null)
|
|
446
|
+
return;
|
|
447
|
+
const o = ev;
|
|
448
|
+
if (o.type !== 'task_progress')
|
|
449
|
+
return;
|
|
450
|
+
if (typeof o.taskId !== 'string' || o.taskId === '')
|
|
451
|
+
return;
|
|
452
|
+
if (typeof ownerRunId !== 'string' || ownerRunId === '')
|
|
453
|
+
return;
|
|
454
|
+
recordSubagentOwner(o.taskId, ownerRunId, ownerSessionId);
|
|
373
455
|
}
|
|
374
456
|
export function getBgParentRun(taskId) {
|
|
375
457
|
touchLru(bgParentRun, taskId);
|
|
458
|
+
return bgParentRun.get(taskId)?.runId;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* 整条宿主记录(runId + 登记时会话)。undefined = **诚实不知道**,调用方渲 no-run 出路 /
|
|
462
|
+
* 不渲入口 —— 绝不在这里回落成「此刻在飞的那条 run」([anchor-on-the-deciding-quantity])。
|
|
463
|
+
*/
|
|
464
|
+
export function getBgParentRunOwner(taskId) {
|
|
465
|
+
if (typeof taskId !== 'string' || taskId === '')
|
|
466
|
+
return undefined;
|
|
467
|
+
touchLru(bgParentRun, taskId);
|
|
376
468
|
return bgParentRun.get(taskId);
|
|
377
469
|
}
|
|
470
|
+
/** 测试钩:清宿主台账(module 级单例,同进程多组断言必须能清)。 */
|
|
471
|
+
export function __resetSubagentOwnerLedgerForTests() {
|
|
472
|
+
bgParentRun.clear();
|
|
473
|
+
}
|
|
378
474
|
// ── 本壳自己发起过的引擎 run 台账([1498]④ 帧级校验换锚)────────────────────────────────────
|
|
379
475
|
// 喂给:engineToolDetach.setActiveEngineTaskId(liveClient 从 run_started 帧绑定,本壳每个交互
|
|
380
476
|
// turn 的 run 必经)。与 bgParentRun 的关键区别:bgParentRun 由 fleet 行/通知帧喂(≤1.244 事实
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
| peer:wire 契约 | `@sema-agent/sdk` **>=6.17.2**(value-level,非 type-only) | `package.json` `peerDependencies` |
|
|
24
24
|
| peer:会话词汇表 | `@sema-agent/agent-types` **>=0.2.0**(type-only,零运行时) | 同上 |
|
|
25
25
|
| runtime dep | `diff` ^9.0.0(**唯一**一条;portability 门按**等值**钉死) | `package.json` `dependencies` |
|
|
26
|
-
| 公开导出面 | **
|
|
26
|
+
| 公开导出面 | **739** 个运行期符号(+ 36 个测试钩) | `scripts/public-export-baseline.json` 的 `count` / `testHookCount` —— **别手抄进别处,以该文件为准** |
|
|
27
27
|
| 常驻门 | 以 `scripts/gates-manifest.json` 的 `suites` 长度为准(**本档不抄这个数**) | `scripts/gates-manifest.json`;`npm test` 的名单等值门与它逐名对账 |
|
|
28
28
|
| 沿革档 | 0.29.0 起建 `CHANGELOG.md`;更早批次记账在 `src/index.ts` 文件头 + `docs/REFACTOR-LEDGER.md` | — |
|
|
29
29
|
|
|
@@ -99,7 +99,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
99
99
|
|
|
100
100
|
## §2 公共导出面地图(按域)
|
|
101
101
|
|
|
102
|
-
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**
|
|
102
|
+
> 全集真源 = `scripts/public-export-baseline.json` 的 `names`(**739** 项)。
|
|
103
103
|
> 本节**不逐名抄**,只给「域 → 承重导出 → 用途 → 实现锚」。承重导出 = 一个端为了让这个域干活
|
|
104
104
|
> **必须**直接调到的那几个符号;其余是它们的类型、变体与辅助位。
|
|
105
105
|
> 单一入口:`import { … } from '@sema-agent/client-core'`(`exports` 只有 `.` 一个;
|
|
@@ -109,7 +109,7 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
109
109
|
|
|
110
110
|
`public-export-baseline.json` 由 **`dist/index.js` 的运行期导出**生成(生成口径自述见
|
|
111
111
|
`scripts/run-client-core-typeshape-test.mjs`,双向精确集合门在 `scripts/run-public-surface-test.mjs`)。
|
|
112
|
-
实测:
|
|
112
|
+
实测:739 项 **100% 是运行期导出,零 type-only**。
|
|
113
113
|
|
|
114
114
|
**推论(端必须知道)**:
|
|
115
115
|
- barrel 导出的**类型**面比 707 大得多,且**不被这道门看守** —— `AdapterContext` / `SeamEvent` /
|
|
@@ -118,19 +118,19 @@ SDK 核对「本包 import 的每个值级符号仍然导出」「`TaskStats.cos
|
|
|
118
118
|
端依赖这些类型是合法的,但**不要**拿基线 diff 当"类型面没变"的证据。
|
|
119
119
|
- `src/agentSession/contract.ts` 对基线贡献 **0** 项(纯类型模块,`export *` 在 dist 里是空转发)。
|
|
120
120
|
|
|
121
|
-
|
|
121
|
+
739 项的内部构成(帮助端估读表大小):**218** 项是 `SCREAMING_SNAKE` 常量数据表/词汇表
|
|
122
122
|
(矩阵、键集、env 名、锚串)而非可调用物;**4** 项是 PascalCase 运行期值
|
|
123
123
|
(`ControlRouter` / `ControlSafetyError` / `HitlBridge` / `HitlSafetyError`);
|
|
124
124
|
**39** 项是 `*For(sessionKey, …)` 的 per-session 变体(§6;其中 `engineNamespaceKeyFor` 是命名巧合 —— 参数是 baseUrl 不是 sessionKey,见域 14)。
|
|
125
125
|
|
|
126
|
-
### 2b. 域图(16 域,逐域计数之和 =
|
|
126
|
+
### 2b. 域图(16 域,逐域计数之和 = 739)
|
|
127
127
|
|
|
128
128
|
| # | 域 | 名数 | 承重导出 | 用途 | 实现锚 |
|
|
129
129
|
|---|---|---|---|---|---|
|
|
130
130
|
| 1 | **适配内核(下行主链)** | 29 | `adapt` · `createWireToCcAdapter` · `runStream` · `eventToSdkMessage` · `terminalToSdkResult` · `turnUsageToModelUsage` · `isRunStreamActive` · `ADAPTER_DIVERGENCES` | 引擎 SSE `AgentEvent` → 端要渲的**双面输出**:transcript(`SDKMessage`)+ chrome(瞬态 `ChromeEvent`)。**本包存在的理由** | `src/adapt.ts`、`src/adapt/{arms,wireShapes,panelTasks}.ts`(经 `adapt.ts` 再导出)、`src/adapter/runStream.ts`、`src/adapter/downstream/*`、`src/adapter/types.ts` |
|
|
131
131
|
| 2 | **seam 公共契约** | 2(其余为 type-only) | `CHROME_ARMS` · `deriveTranscriptId` | 公共词汇 + **id 确定性不变量**(同一条流重放 ⇒ 同一串 id)。`CHROME_ARMS` = 端「我要消费哪些 chrome 臂」的对照清单 | `src/seam.ts` |
|
|
132
132
|
| 3 | **HITL 决断卡链**(§4/§5 主战场) | 119 | `makeHitlCanUseTool` · `HitlBridge` · `findPendingForTask` · `HitlSafetyError` · `bridgeAskUserQuestionGates` · `surfaceToolApprovalFrameAndRespond` / `surfaceFsApprovalAndDecide` · `readToolApprovalRespondAck` · `installApprovalCardPort(For)` · `installHitlHostSurface(For)` · `armPlanReviewApproval` · `reopenPlanReviewCard` · `decidePlanReview` · `startApprovalsFeed` · `pendingRowIsOwnedByThisSession` · `approvalCallKey`/`liveFrameCallKey`/`planReviewQuestionId` · `registerArmedGateFor`/`wasGateArmedFor`/`clearArmedGateFor` · `waitForGateArmed(For)`/`onGateArmed(For)`/`gateArmedWaitMs`(#244 F1 呈现回执事件源) · `planReviewArmedKey(For)`/`notePlanReviewAnswered(For)`/`notePlanReviewAnsweredIfDecisive(For)`(A-024.4 plan 呈现分代) · `toolEndOutputText` · `isAskTool` · `waitForParkRowBirth` · `classifyAskParkRows` / `askParkRowArm` / `classifyAskParkChainFailure` · `readDecisionNoteAudit` / `decisionNoteAuditLine` · `resumeRunningOptions` / `resumeChoiceFromLabels` · `persistedRulesLaneAvailable`/`persistedRulesGovernanceAvailable` · `classifyRulesFailure` · `listAllPersistedRules` · `classifySkippedReason` · `readRulePersistOutcome`(#244 F2:persist-ack 读口与 `readToolApprovalRespondAck` 合成一处) · `parseLocalAllowRule`(durable 腿本地落规则窄化骨架,谓词经 `LocalAllowRuleDeps` 注入) | suspended→decide→resume 环。🔴 **D-1 两元组 verbatim 回显**是字节级断言的安全不变量,端**不许重实现它的任何一段**。🔴 键空间边界(web [C1] d3 拦截):`gateIdentity` 四常量两函数只覆盖 HITL questionId/callKey 空间;seat 的 `TOOL_PERMISSION_REQUEST_ID_DOMAINS`(`plan:` 等)是另一键空间,**两者绝不合并**(合并=座位校验器静默拒全部 plan-review 卡) | `src/hitl/hitlBridge.ts`、`toolApprovalWire.ts`、`askGateWire.ts`、`planReviewWire.ts`、`hitlHostSurface.ts`、`gateIdentity.ts`、`armedGateRegistry.ts`、`parkOwnership.ts`、`parkResolver.ts`、`approvalsFeed.ts`、`frameRouter.ts`(**只挑名导出** `toolEndOutputText`/`ENGINE_ABORT_TOOL_RESULT`/`isAskTool`/`HITL_REJECT_MESSAGE`)、`parkRowBirthWait.ts`、`approvalDecisionNoteAudit.ts`、`askParkRowRouting.ts`、`resumeRunningCard.ts`(#265 上收的判定层)、`persistedRulesWire.ts`、`localAllowRule.ts`(#244 F2 规则侧) |
|
|
133
|
-
| 4 | **子代 wire + 面板侧信道台账** |
|
|
133
|
+
| 4 | **子代 wire + 面板侧信道台账** | 80 | `tailEngineSubagent` · `installSubagentActivitySink` · `installSubagentTailMetaSink`(#280 件2:tail meta 帧发布口,`contentFrames` 判别位载体)· `stopEngineTask` + `classifyTaskStopConflict` · `fetchEngineSubagentReport` · `steerEngineSubagent` · `resumeSettledSubagent` + `resolveSubagentResumeContext` + `resolveOwnerRunId` + `classifySubagentResumeFailure` + `subagentResumeAvailable`(#242 批 2 A-028.7:resume 判定半场上收,与 steer 孪生同居;取址三态 = 台账有行用行值 / 指名了行但台账缺席则**诚实缺席绝不回落在飞 run** / 没指名行才回落。出路文案归端)· `recordSubagentOwnerFromProgress` + `getBgParentRunOwner`(A-028.6:「子代 → 宿主 run」**单表**,宿主 run 必须由持 stream-local 值的调用方显式传入,包内绝不从 `activeEngineRunId()` 推断)· `auditRetainWithoutWake`([4000] Q5:引擎宣示 `subagentResume` + 本端在付 `retainSubagentSessions` + 端未实现 `wakeSubagent` ⇒ 响亮一条;`CLIENT_VERBS.wakeSubagent` 维持 fail-soft)· `subscribeSubagentContent` · `subscribeEngineAgentPanel` · `publishQuestionFrame` / `respondToQuestion` | 驱动与观测委派子代;经 module 级台账喂活体 agent/task 面板。全部**能力位 gate**(§5b) | `src/subagent/*.ts`、`src/subagentContentStore.ts`、`src/engineAgentPanelStore.ts`、`src/engineInlineTaskStats.ts`、`src/engineToolLabelStore.ts`、`src/liveQuestionStore.ts` |
|
|
134
134
|
| 5 | **fleet 投影** | 43 | `createFleetLedger` · `projectTasks` · `projectWorkflows` · `projectFleetAgentRows` · `readEngineActiveBgTasks` · `FLEET_TASK_VIEW_KEYS` | 老 `fleetClient` 那一刀的成品:**帧体归库、连接归端** —— 端持 SSE 连接,库做行投影 + 保留台账 | `src/fleet/fleetProjection.ts`、`src/fleet/fleetLedger.ts`、`src/fleetAgentPanelProjection.ts`、`src/fleetTaskDesc.ts` |
|
|
135
135
|
| 6 | **请求装配(上行唯一构造口)** | 8 | `buildTaskRequest` · `REQUEST_FIELD_MATRIX` · `unregisteredRequestKeys` · `applyLiveRequestDefaults` · `taskNotificationToPrintFrame` | 两条车道(`interactive`/`print`)出站请求的**唯一**构造器;`unregisteredRequestKeys` 是可执行门 —— 端偷带一个未登记键上 wire 就红 | `src/request/taskRequest.ts`、`src/request/printNotification.ts` |
|
|
136
136
|
| 7 | **通知与 outstanding 台账** | 37 | `installNotificationQueuePort` · `normalizeTaskNotification` · `taskNotificationDedupKeyFromWire` · `registerOutstandingBgTask` / `registerOutstandingWorkflowRun` · `notificationQueuePortMisses` · `subscribeOutstandingWorkflows` · `outstandingDeliverableWorkflowCount` | `task_notification` 归一 + 去重 + 投递进宿主命令队列的**一把闸**;`outstandingDeliverableWorkflowCount()` 是 headless `-p` 的**退出门** | `src/notifications.ts`(11 个 module 台账) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/client-core",
|
|
3
|
-
"version": "0.30.
|
|
3
|
+
"version": "0.30.7",
|
|
4
4
|
"description": "Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, and the notification/dedup ledgers. Every CC-skin shape is collected here so the wire itself stays neutral. Blackboard [1832] design axioms; [1651]/[1652]/[1653] signed seam design. Renamed from @sema-agent/wire-cc-adapter (0.1.x).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|