@sema-agent/client-core 0.73.4 → 0.74.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/CHANGELOG.md +41 -0
- package/README.md +4 -2
- package/dist/engineAgentPanelStore.d.ts +29 -0
- package/dist/engineAgentPanelStore.js +31 -1
- package/dist/fleet/fleetLedger.d.ts +20 -0
- package/dist/fleet/fleetLedger.js +103 -10
- package/dist/fleet/workflowSizeWarning.d.ts +96 -0
- package/dist/fleet/workflowSizeWarning.js +155 -0
- package/dist/fleetAgentPanelProjection.d.ts +2 -0
- package/dist/fleetAgentPanelProjection.js +42 -7
- package/dist/hitl/planReviewWire.js +10 -0
- package/dist/hitl/toolApprovalWire.d.ts +7 -1
- package/dist/hitl/toolApprovalWire.js +5 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/notifications.js +3 -0
- package/dist/subagentContentStore.d.ts +2 -0
- package/docs/INTEGRATION-CLIENTS.md +101 -8
- package/package.json +2 -2
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/fleet/workflowSizeWarning.ts —— workflow **规模告警**的判定(0.73.5)。
|
|
3
|
+
*
|
|
4
|
+
* 三端 footer / 面板都要回答同一个问题:「这条 workflow 是不是大到该提醒用户了」。判定与 CC 同形
|
|
5
|
+
* (三个缺省帽、每 agent 用量估算、guideline 表、两个 env 覆盖、三档文案与宽度阶梯),属于客户端公共逻辑,
|
|
6
|
+
* 由本包单源;端只负责读环境、读设置、量宽度与渲染。
|
|
7
|
+
*
|
|
8
|
+
* 🔴 **入参按 fleet 视图的三态键读**(`FleetWorkflowView`:`totalCount` / `startedCount` / `doneCount` /
|
|
9
|
+
* `failedCount` / `tokens` 全是可选位,缺席 = wire 没报 = 不知道)。出参三态:
|
|
10
|
+
* · `warn` —— 有**正向证据**证明至少一条轴超帽(别的键缺席不挡它;下界分母算出的上界**不算**证据);
|
|
11
|
+
* · `ok` —— 两条轴都判得出,且都在帽内(或 ultracode 压掉了整条告警);
|
|
12
|
+
* · `unknown` —— 没有超帽的正向证据,且至少一条轴判不出。**未知的规模不能被当作规模正常**:
|
|
13
|
+
* `unknown` 绝不折成 `ok`,`missing` 点名缺了哪几键。
|
|
14
|
+
*
|
|
15
|
+
* 🔴 **分母口径**(每 agent 用量 `u = tokens / started`):
|
|
16
|
+
* · `startedCount` 在场且不小于 `doneCount + failedCount` ⇒ 用引擎真值(含真 0:「一个都还没起」是一条真信息
|
|
17
|
+
* ⇒ 用先验);比已完成数还小(帧间竞态)⇒ 这份读数是陈旧的,按下面的下界处置;
|
|
18
|
+
* · 缺席 ⇒ 只有 `doneCount` / `failedCount` **两键都在场**才回退成下界,结果标 `startedIsLowerBound`。
|
|
19
|
+
* 下界偏小 ⇒ `u` 偏大 ⇒ 算出来的是投影的**上界**(`projectedTokensUpperBound`,不是 `projectedTokens`):
|
|
20
|
+
* 上界在帽内 ⇒ 真的在帽内 ⇒ 可以判 `ok`;上界超帽**不是**超帽的证据(同一份读数补上真 `startedCount`
|
|
21
|
+
* 可能远在帽内)⇒ 投影轴判不出,`unknown`。想保守一点的端可以自己拿上界去提示,判词不替它撒谎;
|
|
22
|
+
* · 下界为 0 ⇒ 每 agent 用量判不出:「0 个完成」不是「一个没起」的证据,先验只在引擎**亲口**报
|
|
23
|
+
* `startedCount = 0` 时可用;
|
|
24
|
+
* · 只有一键在场 ⇒ 不回退(缺席的那键不读作 0)。
|
|
25
|
+
*/
|
|
26
|
+
/** 缺省 agent 数帽。 */
|
|
27
|
+
export const WORKFLOW_SIZE_WARNING_AGENT_CAP = 25;
|
|
28
|
+
/** 缺省 token 帽(已花 ∨ 投影)。 */
|
|
29
|
+
export const WORKFLOW_SIZE_WARNING_TOKEN_CAP = 1_500_000;
|
|
30
|
+
/** 还没有任何 agent 起跑时的每 agent 用量先验。 */
|
|
31
|
+
export const WORKFLOW_SIZE_WARNING_TOKENS_PER_AGENT = 70_000;
|
|
32
|
+
/** 显式 `workflowSizeGuideline` → agent 数帽。`unrestricted` 与缺省(未配置)**不贡献帽**,故不在表里。 */
|
|
33
|
+
export const WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS = Object.freeze({
|
|
34
|
+
small: 5,
|
|
35
|
+
medium: 15,
|
|
36
|
+
large: 50,
|
|
37
|
+
});
|
|
38
|
+
/** 两个 env 覆盖各自的名字,按读取优先级排(品牌名在前,兼容名在后)。本包不读 `process.env`,名字单源在这里。 */
|
|
39
|
+
export const WORKFLOW_SIZE_WARNING_ENV_NAMES = Object.freeze({
|
|
40
|
+
agents: Object.freeze(['SEMA_CODE_WORKFLOW_SIZE_WARNING_AGENTS', 'CLAUDE_CODE_WORKFLOW_SIZE_WARNING_AGENTS']),
|
|
41
|
+
tokens: Object.freeze(['SEMA_CODE_WORKFLOW_SIZE_WARNING_TOKENS', 'CLAUDE_CODE_WORKFLOW_SIZE_WARNING_TOKENS']),
|
|
42
|
+
});
|
|
43
|
+
/** 三档文案(宽 → 窄)。 */
|
|
44
|
+
export const WORKFLOW_SIZE_WARNING_TEXT_LONG = '⚠ Large workflow · /workflows to stop';
|
|
45
|
+
export const WORKFLOW_SIZE_WARNING_TEXT_SHORT = '⚠ Large workflow';
|
|
46
|
+
export const WORKFLOW_SIZE_WARNING_TEXT_MIN = '⚠';
|
|
47
|
+
/** 有限非负数,否则缺席(坏形按缺席,不猜)。 */
|
|
48
|
+
function count(v) {
|
|
49
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined;
|
|
50
|
+
}
|
|
51
|
+
/** 正有限数,否则缺席。 */
|
|
52
|
+
function positive(v) {
|
|
53
|
+
return typeof v === 'number' && Number.isFinite(v) && v > 0 ? v : undefined;
|
|
54
|
+
}
|
|
55
|
+
function guidelineCap(size) {
|
|
56
|
+
return size !== undefined && Object.hasOwn(WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS, size)
|
|
57
|
+
? WORKFLOW_SIZE_GUIDELINE_AGENT_CAPS[size]
|
|
58
|
+
: undefined;
|
|
59
|
+
}
|
|
60
|
+
/** 规模判定。纯函数:每个环境量都由调用方注入。 */
|
|
61
|
+
export function evaluateWorkflowSize(input) {
|
|
62
|
+
const agentCap = positive(input.envAgentCap) ?? guidelineCap(input.guidelineSize) ?? WORKFLOW_SIZE_WARNING_AGENT_CAP;
|
|
63
|
+
const tokenCap = positive(input.envTokenCap) ?? WORKFLOW_SIZE_WARNING_TOKEN_CAP;
|
|
64
|
+
const scheduled = count(input.totalCount);
|
|
65
|
+
const tokens = count(input.tokens);
|
|
66
|
+
const base = {
|
|
67
|
+
agentCap,
|
|
68
|
+
tokenCap,
|
|
69
|
+
...(scheduled !== undefined ? { scheduledAgents: scheduled } : {}),
|
|
70
|
+
...(tokens !== undefined ? { totalTokens: tokens } : {}),
|
|
71
|
+
};
|
|
72
|
+
if (input.ultracodeActive === true)
|
|
73
|
+
return { kind: 'ok', suppressed: true, ...base };
|
|
74
|
+
// 分母:真值 / 两键下界 / 判不出。
|
|
75
|
+
const startedWire = count(input.startedCount);
|
|
76
|
+
const done = count(input.doneCount);
|
|
77
|
+
const failed = count(input.failedCount);
|
|
78
|
+
const bound = done !== undefined && failed !== undefined ? done + failed : undefined;
|
|
79
|
+
let started;
|
|
80
|
+
let lowerBound = false;
|
|
81
|
+
if (startedWire !== undefined && (bound === undefined || startedWire >= bound)) {
|
|
82
|
+
started = startedWire;
|
|
83
|
+
}
|
|
84
|
+
else if (bound !== undefined) {
|
|
85
|
+
// `startedCount` 缺席,或它比已完成数还小(帧间竞态:已完成的 agent 必然起过 ⇒ 这份 started 是陈旧的)。
|
|
86
|
+
// 顶上来的 `done + failed` 只是**下界**,不是真值 —— 与缺席同一处置。
|
|
87
|
+
started = bound;
|
|
88
|
+
lowerBound = true;
|
|
89
|
+
}
|
|
90
|
+
// 投影:分母是真值 ⇒ 投影;分母是下界(> 0)⇒ 只有上界;其余判不出。
|
|
91
|
+
let projected;
|
|
92
|
+
let upperBound;
|
|
93
|
+
if (scheduled !== undefined && tokens !== undefined && started !== undefined) {
|
|
94
|
+
if (!lowerBound) {
|
|
95
|
+
projected = Math.max(tokens, Math.round((started > 0 ? tokens / started : WORKFLOW_SIZE_WARNING_TOKENS_PER_AGENT) * scheduled));
|
|
96
|
+
}
|
|
97
|
+
else if (started > 0) {
|
|
98
|
+
upperBound = Math.max(tokens, Math.round((tokens / started) * scheduled));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const facts = {
|
|
102
|
+
...base,
|
|
103
|
+
...(projected !== undefined ? { projectedTokens: projected } : {}),
|
|
104
|
+
...(upperBound !== undefined ? { projectedTokensUpperBound: upperBound } : {}),
|
|
105
|
+
...(lowerBound && scheduled !== undefined && tokens !== undefined ? { startedIsLowerBound: true } : {}),
|
|
106
|
+
};
|
|
107
|
+
/** 投影轴在帽内的证据:真投影在帽内,或上界在帽内。 */
|
|
108
|
+
const projectionWithinCap = (projected !== undefined && projected <= tokenCap) || (upperBound !== undefined && upperBound <= tokenCap);
|
|
109
|
+
const overAgents = scheduled !== undefined && scheduled > agentCap;
|
|
110
|
+
const overTokens = (tokens !== undefined && tokens > tokenCap) || (projected !== undefined && projected > tokenCap);
|
|
111
|
+
if (overAgents || overTokens) {
|
|
112
|
+
return { kind: 'warn', axis: overAgents && overTokens ? 'both' : overAgents ? 'agents' : 'tokens', ...facts };
|
|
113
|
+
}
|
|
114
|
+
const missing = [];
|
|
115
|
+
if (scheduled === undefined)
|
|
116
|
+
missing.push('totalCount');
|
|
117
|
+
if (tokens === undefined)
|
|
118
|
+
missing.push('tokens');
|
|
119
|
+
if (scheduled !== undefined && tokens !== undefined && !projectionWithinCap)
|
|
120
|
+
missing.push('startedCount');
|
|
121
|
+
if (missing.length > 0)
|
|
122
|
+
return { kind: 'unknown', missing, ...facts };
|
|
123
|
+
return { kind: 'ok', ...facts };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* 两个 env 覆盖的读口。`env` 由调用方交(本包不碰 `process.env`)。每组按 {@link WORKFLOW_SIZE_WARNING_ENV_NAMES}
|
|
127
|
+
* 的顺序取**第一个在场的名字**;那个名字的值不是正有限数 ⇒ 该键不在场,**不回落**到第二个名字
|
|
128
|
+
* (显式写错不该被悄悄换源)。
|
|
129
|
+
*/
|
|
130
|
+
export function readWorkflowSizeWarningEnvCaps(env) {
|
|
131
|
+
const pick = (names) => {
|
|
132
|
+
if (env === undefined || env === null)
|
|
133
|
+
return undefined;
|
|
134
|
+
for (const n of names) {
|
|
135
|
+
const raw = env[n];
|
|
136
|
+
if (typeof raw !== 'string')
|
|
137
|
+
continue; // 空串也是「在场」:在场的坏值就地止步,不回落
|
|
138
|
+
return positive(Number(raw));
|
|
139
|
+
}
|
|
140
|
+
return undefined;
|
|
141
|
+
};
|
|
142
|
+
const a = pick(WORKFLOW_SIZE_WARNING_ENV_NAMES.agents);
|
|
143
|
+
const t = pick(WORKFLOW_SIZE_WARNING_ENV_NAMES.tokens);
|
|
144
|
+
return { ...(a !== undefined ? { envAgentCap: a } : {}), ...(t !== undefined ? { envTokenCap: t } : {}) };
|
|
145
|
+
}
|
|
146
|
+
/** 宽度阶梯:挑放得下的最长一档;连字形都放不下 ⇒ `undefined`(整段不渲)。`width` 由调用方注入(与渲染器同一把尺)。 */
|
|
147
|
+
export function workflowSizeWarningTextForWidth(available, width) {
|
|
148
|
+
if (available >= width(WORKFLOW_SIZE_WARNING_TEXT_LONG))
|
|
149
|
+
return WORKFLOW_SIZE_WARNING_TEXT_LONG;
|
|
150
|
+
if (available >= width(WORKFLOW_SIZE_WARNING_TEXT_SHORT))
|
|
151
|
+
return WORKFLOW_SIZE_WARNING_TEXT_SHORT;
|
|
152
|
+
if (available >= width(WORKFLOW_SIZE_WARNING_TEXT_MIN))
|
|
153
|
+
return WORKFLOW_SIZE_WARNING_TEXT_MIN;
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
@@ -36,6 +36,8 @@ export interface FleetAgentRowLike {
|
|
|
36
36
|
/** server 1.250 起行带的 launch 真锚(epoch ms)。R4(clay 五报 07-31):此前投影载荷丢弃它,
|
|
37
37
|
* 详情页 elapsed 只能锚「壳建行时刻」——遗留行上渲出 1h+ 的假时长。缺席=老引擎,消费端保持现状。 */
|
|
38
38
|
startedAt?: number;
|
|
39
|
+
/** 0.74.0 —— 行的代际号(fleet 视图 `FleetTaskView.cycleSeq` 原样交)。缺席 = 这一行没有代际概念,不是第一代。 */
|
|
40
|
+
cycleSeq?: number;
|
|
39
41
|
}
|
|
40
42
|
/** 行从投影集消失多久之后上报 `absent`(0.72.12 起不再 settle;重连空窗 / snapshot 清表远小于此)。名字沿用(公面导出)。 */
|
|
41
43
|
export declare const ABSENT_SETTLE_MS = 30000;
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* run 发布器双生行的形)——REF-CC-042:与同包 `workflow.rowIdTail` 同一个函数(import,不再
|
|
48
48
|
* 就地维护第二份;该函数出过等价性事故,0.1.1 修,见 `workflow.ts` 头注)。
|
|
49
49
|
*/
|
|
50
|
-
import { publishEngineAgentPanelAbsence, publishEngineAgentPanelEvent } from './engineAgentPanelStore.js';
|
|
50
|
+
import { isStaleEngineAgentPanelEnd, publishEngineAgentPanelAbsence, publishEngineAgentPanelEvent } from './engineAgentPanelStore.js';
|
|
51
51
|
import { rowIdTail } from './workflow.js';
|
|
52
52
|
import { TERMINAL_FLEET_TASK_STATUSES } from './fleet/fleetProjection.js';
|
|
53
53
|
import { DEFAULT_SESSION_KEY } from './sessionSlot.js';
|
|
@@ -142,26 +142,53 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
142
142
|
const toolUses = typeof row.toolUses === 'number' ? row.toolUses : undefined;
|
|
143
143
|
const transcriptId = row.transcriptId || undefined;
|
|
144
144
|
const startedAt = typeof row.startedAt === 'number' && row.startedAt > 0 ? row.startedAt : undefined;
|
|
145
|
+
// 0.74.0:代际号(正整数才算;坏形 / 缺席 ⇒ undefined,绝不补 1)。
|
|
146
|
+
const cycleSeq = typeof row.cycleSeq === 'number' && Number.isInteger(row.cycleSeq) && row.cycleSeq >= 1 ? row.cycleSeq : undefined;
|
|
145
147
|
const currentTool = currentToolKeyOf(row.currentTool) !== undefined ? row.currentTool : undefined;
|
|
146
148
|
const currentToolKey = currentToolKeyOf(row.currentTool);
|
|
147
149
|
const prev = seenMap.get(taskId);
|
|
150
|
+
// 0.74.0(异源对抗复审 R1):行上已是更新的一代,这一帧却是更早的一代 ⇒ 前代迟到帧。整只跳过:不发 fleet-row
|
|
151
|
+
// (它会把消费端记的周期身份改写回旧代,让随后的 end{旧代} 过陈旧判据)、不发 end、不动台账(否则台账被标
|
|
152
|
+
// settled,新一代的真终态就被去重吞掉)。在场已记 ⇒ 不会因此误报缺席。
|
|
153
|
+
// 判据与消费端同一条(`isStaleEngineAgentPanelEnd`:cycleSeq 优先,startedAt 回退)—— 只比 cycleSeq 会让只带
|
|
154
|
+
// startedAt 的前代帧漏网(异源对抗复审 R2)。
|
|
155
|
+
const rowIdentity = { ...(cycleSeq !== undefined ? { cycleSeq } : {}), ...(startedAt !== undefined ? { startedAt } : {}) };
|
|
156
|
+
const prevIdentity = prev !== undefined
|
|
157
|
+
? { ...(prev.cycleSeq !== undefined ? { cycleSeq: prev.cycleSeq } : {}), ...(prev.startedAt !== undefined ? { startedAt: prev.startedAt } : {}) }
|
|
158
|
+
: undefined;
|
|
159
|
+
if (prev !== undefined && isStaleEngineAgentPanelEnd(rowIdentity, prevIdentity))
|
|
160
|
+
continue;
|
|
161
|
+
/** 这一帧的周期身份**可证明**比台账上的新(同一条规则反向)。 */
|
|
162
|
+
const rowNewerCycle = prev !== undefined && isStaleEngineAgentPanelEnd(prevIdentity, rowIdentity);
|
|
148
163
|
// 🔴 粘性只在**同一周期**内成立:上一条已 settle(同 id 复活 = 新周期)或 launch 锚换了 ⇒ 不继承上一周期的用量
|
|
149
164
|
// (否则新周期首帧没报 tokens 时会把上一周期的数当成当前读数发布;异源对抗复审)。
|
|
150
165
|
const sameCycle = prev !== undefined &&
|
|
151
166
|
!prev.settled &&
|
|
152
|
-
(startedAt === undefined || prev.startedAt === undefined || prev.startedAt === startedAt)
|
|
167
|
+
(startedAt === undefined || prev.startedAt === undefined || prev.startedAt === startedAt) &&
|
|
168
|
+
// 0.74.0:代际号变了同样是新周期(两条都报了才比;任一条没报 = 这一帧没说)
|
|
169
|
+
(cycleSeq === undefined || prev.cycleSeq === undefined || prev.cycleSeq === cycleSeq);
|
|
153
170
|
const knownTokens = tokens !== undefined ? tokens : sameCycle ? prev?.tokens : undefined;
|
|
154
171
|
// 本周期已知的 launch 锚:帧上给了用帧上的,没给则同周期沿用。🔴 它随**每一条** fleet-row 发布(不只是变化那一帧)——
|
|
155
172
|
// 事件缓冲靠它判「这两条部分帧是不是同一周期」;缺锚的部分帧会让跨周期合并绕过屏障(异源对抗复审 [high])。
|
|
156
|
-
|
|
173
|
+
// 🔴 身份的沿用与用量的沿用是两回事(异源对抗复审 R3):用量只在**活着的同周期**内粘(`sameCycle` 要求
|
|
174
|
+
// !prev.settled);身份只要这一帧**不可证更新**就沿用 —— 同周期重复的终态帧若把记住的 startedAt / cycleSeq
|
|
175
|
+
// 抹掉,下一代只带另一半身份时就判不出「更新」,被 settled 吞掉。
|
|
176
|
+
const carryIdentity = prev !== undefined && !rowNewerCycle;
|
|
177
|
+
const knownStartedAt = startedAt !== undefined ? startedAt : carryIdentity ? prev.startedAt : undefined;
|
|
178
|
+
const knownCycleSeq = cycleSeq !== undefined ? cycleSeq : carryIdentity ? prev.cycleSeq : undefined;
|
|
157
179
|
if (TERMINAL_FLEET_TASK_STATUSES.has(status)) {
|
|
158
|
-
// ② 终态一次性 settle(幂等台账;消费端 settle 也幂等)。
|
|
159
|
-
|
|
180
|
+
// ② 终态一次性 settle(幂等台账;消费端 settle 也幂等)。0.74.0:去重按**周期** —— 已 settle 的是上一代、这一帧是
|
|
181
|
+
// 更新的一代(中间没见到 running 帧)⇒ 新周期的终态照发,不被上一代的 settled 吞掉。
|
|
182
|
+
const settledThisCycle = prev?.settled === true && !rowNewerCycle;
|
|
183
|
+
if (!settledThisCycle) {
|
|
160
184
|
// 终态行也把最后一拍的累计工具数/转录锚带上:详情页在行翻终态后仍要显示真计数与
|
|
161
185
|
// 委派 prompt,而终态帧常常是这一行**唯一**带全 usage 的一帧(bg lane 只在活动边界发行帧)。
|
|
162
186
|
if (
|
|
163
|
-
//
|
|
164
|
-
|
|
187
|
+
// 0.74.0:周期身份本身推进(或首帧就带身份)也是变化 —— 消费端要先记下身份才能对 end 判陈旧。
|
|
188
|
+
rowNewerCycle ||
|
|
189
|
+
(cycleSeq !== undefined && prev?.cycleSeq !== cycleSeq) || // 含首帧(prev 缺席)就带身份的终态
|
|
190
|
+
// 最终用量也算(终态帧常是唯一带全 usage 的一帧;消费端不更新非 running 行 ⇒ 必须赶在 end 之前发)
|
|
191
|
+
(tokens !== undefined && prev?.tokens !== tokens) ||
|
|
165
192
|
(toolUses !== undefined && prev?.toolUses !== toolUses) ||
|
|
166
193
|
(transcriptId !== undefined && prev?.transcriptId !== transcriptId) ||
|
|
167
194
|
(startedAt !== undefined && prev?.startedAt !== startedAt)) {
|
|
@@ -174,6 +201,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
174
201
|
...(toolUses !== undefined ? { toolUses } : {}),
|
|
175
202
|
...(transcriptId !== undefined ? { transcriptId } : {}),
|
|
176
203
|
...(knownStartedAt !== undefined ? { startedAt: knownStartedAt } : {}),
|
|
204
|
+
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
177
205
|
});
|
|
178
206
|
}
|
|
179
207
|
publishEngineAgentPanelEvent({
|
|
@@ -183,6 +211,9 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
183
211
|
// **同一个病形**(内联两词 ⇒ core [6908] 的 `blocked` 漏成「成功」)。两处一次改齐,
|
|
184
212
|
// 读同一个单铸谓词;只修当格 = 下一次加词又漏一处。
|
|
185
213
|
isError: isTerminalNotSuccess(status),
|
|
214
|
+
// 0.74.0:这条终态属于哪个周期(在场才带)—— 消费端据此挡掉上一周期迟到的 end。
|
|
215
|
+
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
216
|
+
...(knownStartedAt !== undefined ? { startedAt: knownStartedAt } : {}),
|
|
186
217
|
});
|
|
187
218
|
}
|
|
188
219
|
seenMap.set(taskId, {
|
|
@@ -191,6 +222,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
191
222
|
toolUses: toolUses !== undefined ? toolUses : prev?.toolUses,
|
|
192
223
|
transcriptId: transcriptId !== undefined ? transcriptId : prev?.transcriptId,
|
|
193
224
|
startedAt: knownStartedAt,
|
|
225
|
+
cycleSeq: knownCycleSeq,
|
|
194
226
|
currentToolKey: currentToolKey !== undefined ? currentToolKey : prev?.currentToolKey,
|
|
195
227
|
lastSeenAt: nowMs,
|
|
196
228
|
settled: true,
|
|
@@ -212,6 +244,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
212
244
|
(toolUses !== undefined && prev.toolUses !== toolUses) ||
|
|
213
245
|
(transcriptId !== undefined && prev.transcriptId !== transcriptId) ||
|
|
214
246
|
(startedAt !== undefined && prev.startedAt !== startedAt) ||
|
|
247
|
+
(cycleSeq !== undefined && prev.cycleSeq !== cycleSeq) ||
|
|
215
248
|
(currentToolKey !== undefined && prev.currentToolKey !== currentToolKey)) {
|
|
216
249
|
publishEngineAgentPanelEvent({
|
|
217
250
|
kind: 'fleet-row',
|
|
@@ -223,6 +256,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
223
256
|
...(toolUses !== undefined ? { toolUses } : {}),
|
|
224
257
|
...(transcriptId !== undefined ? { transcriptId } : {}),
|
|
225
258
|
...(knownStartedAt !== undefined ? { startedAt: knownStartedAt } : {}),
|
|
259
|
+
...(knownCycleSeq !== undefined ? { cycleSeq: knownCycleSeq } : {}),
|
|
226
260
|
...(currentTool !== undefined ? { currentTool } : {}),
|
|
227
261
|
});
|
|
228
262
|
}
|
|
@@ -233,6 +267,7 @@ export function projectFleetAgentRowsFor(sessionKey, rows, nowMs = Date.now()) {
|
|
|
233
267
|
toolUses: toolUses !== undefined ? toolUses : prev?.toolUses,
|
|
234
268
|
transcriptId: transcriptId !== undefined ? transcriptId : prev?.transcriptId,
|
|
235
269
|
startedAt: knownStartedAt,
|
|
270
|
+
cycleSeq: knownCycleSeq,
|
|
236
271
|
currentToolKey: currentToolKey !== undefined ? currentToolKey : prev?.currentToolKey,
|
|
237
272
|
lastSeenAt: nowMs,
|
|
238
273
|
settled: false,
|
|
@@ -558,6 +558,16 @@ releaseLatch) {
|
|
|
558
558
|
'The plan approval was NOT applied: the engine refused "auto-accept edits" for this task (it was not started read-only in plan mode, so that option does not apply). ' +
|
|
559
559
|
'Nothing changed engine-side — the plan is still waiting for review. Tell the user plainly, and that approving with "manually approve edits" will go through.';
|
|
560
560
|
}
|
|
561
|
+
else if (status === 404 && refusedCode === 'not_found.plan_review') {
|
|
562
|
+
// 0.73.5:这道门已经不在了(早先的决断已生效,或已过期)。现场形:用户在一张**陈旧的重开卡**上作答 ⇒ 404;
|
|
563
|
+
// 通用句「decision failed: HTTP 404 …」被模型读成「计划被拒 / 计划失败」,于是对用户说计划被拒并重新规划 ——
|
|
564
|
+
// 而那份计划其实已批准并跑完。⇒ 明说这不是拒绝也不是失败,只是这次作答没有对象;效果态仍是 not_applied。
|
|
565
|
+
hostLog('debug', `planReviewWire: ${decision} → 404 not_found.plan_review (no pending plan review any more — an earlier decision likely settled it)`);
|
|
566
|
+
effect = 'not_applied';
|
|
567
|
+
outcome =
|
|
568
|
+
`This ${decision} was not applied: the engine no longer has a pending plan review for this task — an earlier decision most likely already went through (or the review expired). ` +
|
|
569
|
+
'This is NOT a rejection of the plan and NOT a failure of the task; the answer just given simply had nothing left to act on.';
|
|
570
|
+
}
|
|
561
571
|
else if (typeof status === 'number') {
|
|
562
572
|
const msg = e instanceof Error ? e.message : String(e);
|
|
563
573
|
hostLog('debug', `planReviewWire: ${decision} → ${status} ${msg.slice(0, 200)}`);
|
|
@@ -1107,11 +1107,17 @@ export interface ToolApprovalFrame {
|
|
|
1107
1107
|
}
|
|
1108
1108
|
/**
|
|
1109
1109
|
* 根外读 ask 附的「把 `dir` 加为本会话读目录」候选(core 7.19.0 #803;sdk 9.6.0 帧键第 30 员)。
|
|
1110
|
-
* 穷举表恰一臂:`clearsThisAsk` 域 = {true}。`dir` 原样(不截、不归一)
|
|
1110
|
+
* 穷举表恰一臂:`clearsThisAsk` 域 = {true}。`dir` 原样(不截、不归一);声明之外的成员不过境。
|
|
1111
1111
|
*/
|
|
1112
1112
|
export interface ReadRootCandidateView {
|
|
1113
1113
|
dir: string;
|
|
1114
1114
|
clearsThisAsk: true;
|
|
1115
|
+
/**
|
|
1116
|
+
* 0.73.6 —— `"exact"`(core ≥7.22.0 / server ≥7.85.0):**`dir` 这一串不是目录**,是命令点名的那条路径本身
|
|
1117
|
+
* (`cat ~/.gitconfig` 的 `~/.gitconfig`)。消费端必须**逐字添加这一串**;展成目录模式(`<dir>/**`)授不到任何东西、
|
|
1118
|
+
* 卡还在。渲染按它分支:在场那格不要说成「授权这个目录」。**缺席 = 目录出路,逐字节同旧;只读在场,永不读缺席。**
|
|
1119
|
+
*/
|
|
1120
|
+
covers?: 'exact';
|
|
1115
1121
|
}
|
|
1116
1122
|
/** {@link ToolApprovalFrame.delegation} 的形(命名形,不用内联匿名 —— typeshape 门 B4 棘轮口径)。 */
|
|
1117
1123
|
export interface ToolApprovalDelegation {
|
|
@@ -522,13 +522,17 @@ parkGatedCallId) {
|
|
|
522
522
|
}
|
|
523
523
|
}
|
|
524
524
|
}
|
|
525
|
-
/** 帧上 `readRootCandidate` 的窄读(模块内部,不出公面):在场且形对 ⇒
|
|
525
|
+
/** 帧上 `readRootCandidate` 的窄读(模块内部,不出公面):在场且形对 ⇒ 新铸两键形(`covers: "exact"` 在场时三键);缺席 / 坏形 ⇒ undefined(键不置)。 */
|
|
526
526
|
function readReadRootCandidate(v) {
|
|
527
527
|
if (typeof v !== 'object' || v === null)
|
|
528
528
|
return undefined;
|
|
529
529
|
const o = v;
|
|
530
530
|
if (typeof o.dir !== 'string' || o.dir.length === 0 || o.clearsThisAsk !== true)
|
|
531
531
|
return undefined;
|
|
532
|
+
// `covers` 域 = {缺席, "exact"}。出闭集(含键在场而值 undefined)⇒ **整只座不过境**,不是「摘掉坏成员、其余照发」:
|
|
533
|
+
// 两种读法在人那一侧是相反的动作(逐字添加 vs 加目录),发一半会让人按错的那一种回授。与上游同一处置。
|
|
534
|
+
if ('covers' in o)
|
|
535
|
+
return o.covers === 'exact' ? { dir: o.dir, clearsThisAsk: true, covers: 'exact' } : undefined;
|
|
532
536
|
return { dir: o.dir, clearsThisAsk: true };
|
|
533
537
|
}
|
|
534
538
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -227,6 +227,7 @@ export * from './headlessPermissionModeWire.js';
|
|
|
227
227
|
export * from './headlessReconnectWire.js';
|
|
228
228
|
export * from './fleet/fleetProjection.js';
|
|
229
229
|
export * from './fleet/fleetLedger.js';
|
|
230
|
+
export * from './fleet/workflowSizeWarning.js';
|
|
230
231
|
export * from './subagent/engineDelegatedPrompt.js';
|
|
231
232
|
export * from './subagent/engineRowStopGate.js';
|
|
232
233
|
export * from './subagent/engineSubagentOutput.js';
|
package/dist/index.js
CHANGED
|
@@ -364,6 +364,8 @@ export * from './headlessReconnectWire.js';
|
|
|
364
364
|
// · `engineWireTarget.ts` 的 `installed` —— 非 Node 宿主的显式 wire 目标。
|
|
365
365
|
export * from './fleet/fleetProjection.js';
|
|
366
366
|
export * from './fleet/fleetLedger.js';
|
|
367
|
+
// 0.73.5(CC-58):workflow 规模告警判定(三态;缺席 = 不知道,未知的规模不当作规模正常)。
|
|
368
|
+
export * from './fleet/workflowSizeWarning.js';
|
|
367
369
|
export * from './subagent/engineDelegatedPrompt.js';
|
|
368
370
|
export * from './subagent/engineRowStopGate.js';
|
|
369
371
|
export * from './subagent/engineSubagentOutput.js';
|
package/dist/notifications.js
CHANGED
|
@@ -931,6 +931,9 @@ export function enqueueBgChildNotification(n) {
|
|
|
931
931
|
// 后台 run 在面板上被 settle 成**成功**。🔴 `suspended`/`needs_review` 仍不在表里
|
|
932
932
|
// (那两词是「等一次人的决定」,判成终局会把一条正等着你的 run 在面板上判死)。
|
|
933
933
|
isError: isTerminalNotSuccess(n.status),
|
|
934
|
+
// 0.74.0:周期身份 —— **wire 真给了 seq 才带**。上面的 `cycle` 是去重键用的归一值(缺席归一成首周期),
|
|
935
|
+
// 那是键不是事实;把它上屏等于对一条没报代际的通知声称「这是第一代」。
|
|
936
|
+
...(typeof n.seq === 'number' && Number.isInteger(n.seq) && n.seq >= BG_FIRST_SEQ ? { cycleSeq: n.seq } : {}),
|
|
934
937
|
});
|
|
935
938
|
}
|
|
936
939
|
catch {
|
|
@@ -260,6 +260,8 @@ export declare function settleSubagentContent(taskId: string): void;
|
|
|
260
260
|
export declare function clearSubagentContent(taskId: string): void;
|
|
261
261
|
export interface BgTerminalFacts {
|
|
262
262
|
status: string;
|
|
263
|
+
/** 0.74.0 —— 这份终局属于哪个周期(通知带 `seq` 时记;缺席 = 通知没报)。洗绿保护只在同周期内生效。 */
|
|
264
|
+
cycleSeq?: number;
|
|
263
265
|
summary?: string;
|
|
264
266
|
recentSteps?: Array<{
|
|
265
267
|
tool?: string;
|