@migor/agentia 0.8.2 → 0.9.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 +283 -2
- package/README.en.md +2 -2
- package/README.md +7 -5
- package/dist/AGENTS.md +132 -8
- package/dist/core/limits.d.ts +226 -0
- package/dist/core/limits.js +216 -0
- package/dist/core/limits.js.map +1 -0
- package/dist/core/trace.d.ts +66 -2
- package/dist/core/trace.js +37 -2
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/forwarded.d.ts +84 -0
- package/dist/engine/forwarded.js +39 -0
- package/dist/engine/forwarded.js.map +1 -0
- package/dist/engine/run-config.d.ts +4 -1
- package/dist/engine/run-config.js +14 -1
- package/dist/engine/run-config.js.map +1 -1
- package/dist/engine/spec.d.ts +26 -1
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/tracer.d.ts +68 -1
- package/dist/engine/tracer.js +136 -14
- package/dist/engine/tracer.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/adapter-options.d.ts +0 -7
- package/dist/integrations/adapter-options.js +2 -1
- package/dist/integrations/adapter-options.js.map +1 -1
- package/dist/integrations/mcp-stdio.js +9 -1
- package/dist/integrations/mcp-stdio.js.map +1 -1
- package/dist/integrations/metrics-state.d.ts +2 -1
- package/dist/integrations/metrics-state.js +6 -2
- package/dist/integrations/metrics-state.js.map +1 -1
- package/dist/runtime/run.d.ts +13 -1
- package/dist/runtime/run.js +14 -3
- package/dist/runtime/run.js.map +1 -1
- package/dist/toolkit/module.d.ts +29 -1
- package/dist/toolkit/module.js +16 -0
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/skill.js +4 -13
- package/dist/toolkit/skill.js.map +1 -1
- package/dist/toolkit/subagent.js +6 -17
- package/dist/toolkit/subagent.js.map +1 -1
- package/dist/transport/async.d.ts +119 -0
- package/dist/transport/async.js +125 -9
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/http-route.d.ts +6 -1
- package/dist/transport/http-route.js +9 -1
- package/dist/transport/http-route.js.map +1 -1
- package/dist/transport/http.js +99 -1
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/sse.d.ts +6 -2
- package/dist/transport/sse.js +3 -2
- package/dist/transport/sse.js.map +1 -1
- package/dist/transport/task-events.d.ts +90 -0
- package/dist/transport/task-events.js +159 -0
- package/dist/transport/task-events.js.map +1 -0
- package/package.json +5 -3
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentia —— **limits 语义的单一真源**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有这个文件(`docs/guards.md` §2 的第一条待守形状):
|
|
5
|
+
*
|
|
6
|
+
* 本仓把「非正数 = 机制关掉」当成一条统一口径,但它被**每个旋钮各自解释**了一遍 ——
|
|
7
|
+
* 同一个 `0` 在这里是「不限」、在那里是「一次都不做」、在第三个地方是「配置错误」。
|
|
8
|
+
* 历史事故就出在这条缝上:`handler.drain({ timeoutMs: 1 })` 跨过 deadline 后**永不返回**
|
|
9
|
+
* (调用方按「1 毫秒 = 马上就超时」写,代码里却是另一套读法),而当时没有任何一处
|
|
10
|
+
* 集中写着「这个旋钮的 0 是什么意思」。改到相关代码时只能靠人去猜 —— 于是又猜错。
|
|
11
|
+
*
|
|
12
|
+
* 本文件把「每个旋钮的 0 是什么」变成**可执行的数据**,`tests/limits.test.ts` 再拿这份
|
|
13
|
+
* 表去**驱动真实站点**逐条对账:表里声明 `0 = 不限`,用例就去调那个真 API 并断言它真的
|
|
14
|
+
* 不限。所以:
|
|
15
|
+
*
|
|
16
|
+
* - 表与实现漂了 ⇒ 集中用例红(不是文档过期,是构建失败);
|
|
17
|
+
* - 表加了旋钮而探针没加 ⇒ 用例里 `Record<LimitKnob, …>` 少一项,`typecheck:tests` 红。
|
|
18
|
+
*
|
|
19
|
+
* ⚠️ 守卫方向的如实口径:`LimitKnob` 类型**来自这张表本身**,所以
|
|
20
|
+
* `Record<LimitKnob, …>` 只守得住「表 ↔ 探针」这一个方向的对账(表加了没探针、
|
|
21
|
+
* 探针加了表里摘了,都会被拦住);它**抓不到**「代码里新增了旋钮却根本没进表」——
|
|
22
|
+
* 那一步只能靠改代码的人主动登记(这张表就是登记处),别把它当成全自动的网。
|
|
23
|
+
* 表与探针的互查另有一条运行期用例兜底(`tests/limits.test.ts` 末尾)——
|
|
24
|
+
* tsx 不跑类型检查,光有 `Record` 的编译期守卫在单跑测试时看不见。
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ 本模块**不是公共 API**(不进 `src/index.ts`):它是内部口径的真源,不是给使用者的旋钮。
|
|
27
|
+
* 面向使用者的口径在 `docs/usage-guide.md` 的「边界」一节。
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ 表里的 `zeroClause` 是**报错文案的一部分**:有构造期校验的旋钮由实现代码直接把它插进
|
|
30
|
+
* 错误消息里(`runTimeoutMs` / `approvalTimeoutMs` / `traceLimits.maxEvents` / `maxRetries` /
|
|
31
|
+
* `streamBufferEvents` / `retainTerminal`)
|
|
32
|
+
* (`…(0 = 不重试),收到 -1 —— …`)。所以「文案里怎么解释 0」与「代码里怎么实现 0」
|
|
33
|
+
* 是同一个值,改一处等于同时改另一处 —— 这正是先前缺的那条单源。
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ **别在这里写死「几处」**:计数会随旋钮增减立刻过期 —— 本行曾写「五处」并列举五个,
|
|
36
|
+
* 而漏掉的正是**同一次改动里新增**的 `retainTerminal`(实为六处)。要核对当前全集:
|
|
37
|
+
* `grep -rn 'zeroClauseOf' src/`。清单本身有价值,计数没有。
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* 全部 limits 旋钮的 0 语义。**分组按读法**,好让人一眼看出哪几个是相反的。
|
|
41
|
+
*/
|
|
42
|
+
export const LIMIT_SEMANTICS = [
|
|
43
|
+
// ── 0 = 不限(时间的上限被去掉)──────────────────────────────────────────
|
|
44
|
+
{
|
|
45
|
+
knob: 'AsyncRunner.runTimeoutMs',
|
|
46
|
+
where: 'transport/async.ts',
|
|
47
|
+
unit: 'ms',
|
|
48
|
+
zero: 'unlimited',
|
|
49
|
+
zeroClause: '0 = 不限',
|
|
50
|
+
badValue: 'throws',
|
|
51
|
+
note: 'NaN/Infinity 会被 setTimeout 钳到 1ms ⇒ 每个任务立即「超时」失败,且 NaN 会绕过 `< 0` 静默通过;所以要「不限」必须显式传 0。',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
knob: 'AsyncRunner.approvalTimeoutMs',
|
|
55
|
+
where: 'transport/async.ts',
|
|
56
|
+
unit: 'ms',
|
|
57
|
+
zero: 'unlimited',
|
|
58
|
+
zeroClause: '0 = 不限',
|
|
59
|
+
badValue: 'throws',
|
|
60
|
+
note: '同 runTimeoutMs:非有限数会让「已挂起多久」的比较静默失效或立即超时。',
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
knob: 'AsyncRunner.drain.timeoutMs',
|
|
64
|
+
where: 'transport/drain-gate.ts',
|
|
65
|
+
unit: 'ms',
|
|
66
|
+
zero: 'unlimited',
|
|
67
|
+
zeroClause: '非正 = 一直等',
|
|
68
|
+
badValue: 'none',
|
|
69
|
+
note: '**历史事故现场**:`http.ts` 的优雅停机把剩余预算算成 `timeoutMs - elapsed` 再交给它,deadline 已过时算出 1 或 0 ⇒ 旧读法(`0` = 已到点)让它跨过 deadline 后**永不返回**。现在 `http.ts` 自己先判「已到点就认账」,不把 0 交给下游去猜。',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
knob: 'mapWithConcurrency.limit',
|
|
73
|
+
where: 'engine/concurrency.ts',
|
|
74
|
+
unit: 'count',
|
|
75
|
+
zero: 'unlimited',
|
|
76
|
+
zeroClause: '非正 = 不限',
|
|
77
|
+
badValue: 'coerced-to-unlimited',
|
|
78
|
+
note: '⚠️ 与 `maxToolConcurrency` 成对:这是**唯一**「数量」旋钮取 0 却读作「不限」的。另有一条反向的坑:`(0,1)` 区间的小数若交给 `Math.floor` 会压成 0 worker ⇒ 工具被静默丢弃,所以正数一律至少 1 个 worker(`maxToolConcurrency: cpus().length / 8` 在多核 < 8 的机器上正落在那个区间)。',
|
|
79
|
+
},
|
|
80
|
+
// ── 0 = 机制关掉 / 一次都不做(数量旋钮;与上面相反)─────────────────────
|
|
81
|
+
{
|
|
82
|
+
knob: 'traceLimits.maxEvents',
|
|
83
|
+
where: 'engine/tracer.ts',
|
|
84
|
+
unit: 'count',
|
|
85
|
+
zero: 'disabled',
|
|
86
|
+
zeroClause: '0 = 一条都不记',
|
|
87
|
+
badValue: 'throws',
|
|
88
|
+
note: '⚠️ 与 `maxEventChars`(管「多长」)正交,一个管「多少」。0 是**有意义的值**(一条都不记,但计数照记)—— 不是「不限条数」。',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
knob: 'maxRetries',
|
|
92
|
+
where: 'integrations/adapter-options.ts',
|
|
93
|
+
unit: 'count',
|
|
94
|
+
zero: 'disabled',
|
|
95
|
+
zeroClause: '0 = 不重试',
|
|
96
|
+
badValue: 'throws',
|
|
97
|
+
note: '坏值必须响亮失败:判定是 `attempt >= maxRetries` ⇒ `NaN` 比较恒假、`Infinity` 永远达不到,两者都是**无限重试**(429 场景下每一次都是真金白银);`-1` 静默变成「不重试」;`1.5` 实际只允许 1 次。',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
knob: 'maxEventChars',
|
|
101
|
+
where: 'engine/tool-events.ts',
|
|
102
|
+
unit: 'chars',
|
|
103
|
+
zero: 'disabled',
|
|
104
|
+
zeroClause: '0 = 截到零长度;false = 不截断',
|
|
105
|
+
badValue: 'none',
|
|
106
|
+
note: '两个「关掉」的写法语义不同:`false` 才是「不截断」(调试期看全文),`0` 是把正文截成空串。⚠️ 靠 `!= null` 判定透传(真值判定会吃掉 `false`)。',
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
knob: 'runAgent.maxIterations',
|
|
110
|
+
where: 'engine/loop.ts',
|
|
111
|
+
unit: 'count',
|
|
112
|
+
zero: 'disabled',
|
|
113
|
+
zeroClause: '0 = 一次都不跑',
|
|
114
|
+
badValue: 'none',
|
|
115
|
+
note: '循环写成 `for (iteration = 0; iteration < maxIterations; ...)` ⇒ 0 时循环体一次不执行,直接以「达到循环上限」收尾(**算失败**)。是「已到点」而非「不限」,与 `mapToolConcurrency` 的读法**相反**。',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
knob: 'withTimeout.ms',
|
|
119
|
+
where: 'core/timeout.ts',
|
|
120
|
+
unit: 'ms',
|
|
121
|
+
zero: 'disabled',
|
|
122
|
+
zeroClause: '非正 = 不设超时(原样透传)',
|
|
123
|
+
badValue: 'none',
|
|
124
|
+
note: '`toolTimeoutMs` 的底座。⚠️ 漏透传这个值不是「少一层保险」而是**反的**:`withTimeout(p, 0)` 直接返回原 promise = 永不超时,同时 MCP 桥找不到引擎预算又起自己的 60s 兜底 ⇒ 双计时器、双账本(`toolkit/subagent.ts` / `skill.ts` 的透传注释指的就是这条)。',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
knob: 'TaskEventStreams.retainTerminal',
|
|
128
|
+
where: 'transport/task-events.ts',
|
|
129
|
+
unit: 'count',
|
|
130
|
+
zero: 'disabled',
|
|
131
|
+
zeroClause: '0 = 不留终态流(终态即忘)',
|
|
132
|
+
badValue: 'throws',
|
|
133
|
+
note: '判定是 `excess <= 0 提前返回` ⇒ NaN 恒假、**全部**无订阅者的终态流被清空(保留机制静默失效,方向与 maxEvents 的「闸失效」相反、同族)。⚠️ 只跳过「还有订阅者」的终态流 —— 那是还没被读完的流,丢了下游会莫名断在半路。',
|
|
134
|
+
},
|
|
135
|
+
// ── 0 = 立即执行(等待预算为 0 ⇒ 这次等待不存在)─────────────────────────
|
|
136
|
+
{
|
|
137
|
+
knob: 'interruptibleSleep.ms',
|
|
138
|
+
where: 'core/timeout.ts',
|
|
139
|
+
unit: 'ms',
|
|
140
|
+
zero: 'immediate',
|
|
141
|
+
zeroClause: '非正 = 不睡',
|
|
142
|
+
badValue: 'none',
|
|
143
|
+
note: '⚠️ 这一判**先于** aborted 检查 —— 别把它「修」成「已中止就该 reject」。2026-09-19 外部复核把它当一致性缺口改反过一次,被 `tests/core/timeout.test.ts` + `tests/core/sse-text-stats.test.ts` 拦住。',
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
knob: 'metricsSink.intervalMs',
|
|
147
|
+
where: 'integrations/metrics.ts',
|
|
148
|
+
unit: 'ms',
|
|
149
|
+
zero: 'immediate',
|
|
150
|
+
zeroClause: '0 = 立即导出',
|
|
151
|
+
badValue: 'none',
|
|
152
|
+
note: '⚠️ **同名反义**:`Scheduler.every` 的 `intervalMs: 0` 是**配置错误**(抛错),这里却是「关掉定时器、每次累加后立即导出」。两个同名旋钮两种读法 —— 改任何一个之前先看这张表。',
|
|
153
|
+
},
|
|
154
|
+
// ── 0 = 非法配置(响亮失败)─────────────────────────────────────────────
|
|
155
|
+
{
|
|
156
|
+
knob: 'AsyncRunner.concurrency',
|
|
157
|
+
where: 'transport/async.ts',
|
|
158
|
+
unit: 'count',
|
|
159
|
+
zero: 'invalid',
|
|
160
|
+
zeroClause: '必须为正数(0 = 没有 worker)',
|
|
161
|
+
badValue: 'throws',
|
|
162
|
+
note: '0 个 worker 的池子会让每个任务永远排在队列里(既不跑也不失败)。',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
knob: 'AsyncRunner.streamBufferEvents',
|
|
166
|
+
where: 'transport/task-events.ts',
|
|
167
|
+
unit: 'count',
|
|
168
|
+
zero: 'invalid',
|
|
169
|
+
zeroClause: '必须为正安全整数(0 没有「缓冲几条」的读法)',
|
|
170
|
+
badValue: 'throws',
|
|
171
|
+
note: '0 既不是「关掉流」(live 推送仍在工作)也不是「不限」,三种合法读法都不沾 ⇒ 配置错误;旧实现 `Math.max(1, …)` 把 0 静默抬成 1(替使用者改配置),且 NaN 穿过后 `length > NaN` 恒假 ⇒ 每任务内存闸静默失效(使用者以为设了上限)。',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
knob: 'Scheduler.every.intervalMs',
|
|
175
|
+
where: 'transport/scheduler.ts',
|
|
176
|
+
unit: 'ms',
|
|
177
|
+
zero: 'invalid',
|
|
178
|
+
zeroClause: '必须为正有限数(0 会空转)',
|
|
179
|
+
badValue: 'throws',
|
|
180
|
+
note: '`setInterval(0)` 退化成「尽快重复」的空转循环(Node 钳到 1ms,仍是每毫秒一轮忙轮询);超过 2^31-1ms 会被**静默**钳到 1ms,同款失败。',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
knob: 'createAnthropicClient.timeout',
|
|
184
|
+
where: 'integrations/anthropic.ts',
|
|
185
|
+
unit: 'ms',
|
|
186
|
+
zero: 'invalid',
|
|
187
|
+
zeroClause: '不设 = 不限;0 = 非法',
|
|
188
|
+
badValue: 'throws',
|
|
189
|
+
note: '「不限」的表达方式是**不传**,不是传 0 —— 与 `runTimeoutMs` 的读法**相反**(那个要求显式传 0)。',
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
knob: 'metricsSink.windowSize',
|
|
193
|
+
where: 'integrations/metrics.ts',
|
|
194
|
+
unit: 'count',
|
|
195
|
+
zero: 'invalid',
|
|
196
|
+
zeroClause: '必须为正数(0 = 窗口里什么都没有)',
|
|
197
|
+
badValue: 'throws',
|
|
198
|
+
note: '同款的还有 `maxCapabilities` / `maxModels` / `maxScores` —— 都是「必须为正数」。',
|
|
199
|
+
},
|
|
200
|
+
];
|
|
201
|
+
/** 按旋钮取那行(拼错旋钮名 = 编译期报错,不是运行期 undefined) */
|
|
202
|
+
export function limitSemanticOf(knob) {
|
|
203
|
+
const found = LIMIT_SEMANTICS.find((s) => s.knob === knob);
|
|
204
|
+
// 表是 `as const` 的封闭集合,knob 类型来自它 ⇒ 这里不可能落到 undefined;
|
|
205
|
+
// 保留分支是为了让「表被改空」时给出可读的失败而不是 TypeError。
|
|
206
|
+
if (!found)
|
|
207
|
+
throw new Error(`limits 表里没有旋钮 ${knob}(表被改空了?)`);
|
|
208
|
+
return found;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 报错文案里那句「(0 = …)」。**构造期校验直接插它**,所以文案与表不可能各说各话。
|
|
212
|
+
*/
|
|
213
|
+
export function zeroClauseOf(knob) {
|
|
214
|
+
return limitSemanticOf(knob).zeroClause;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"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"}
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -89,8 +89,12 @@ export declare function wireSpanId(id: string): string;
|
|
|
89
89
|
/**
|
|
90
90
|
* 生成 W3C `traceparent`(`parseTraceparent` 的镜像:一个解析、一个生成)。
|
|
91
91
|
*
|
|
92
|
-
* flags 位恒为 `00
|
|
93
|
-
*
|
|
92
|
+
* flags 位恒为 `00`。**理由不是「本框架不采样」**(采样早就是明确推荐的宿主配法 ——
|
|
93
|
+
* `docs/observability.md` 2.3),而是**运行期不可知**:记录/导出决策发生在 run **收尾之后**
|
|
94
|
+
* (采样闸门要看完好整棵 trace 才判得出来),而出站调用发生在**运行期** ——
|
|
95
|
+
* 那时「这条 trace 会不会被采样掉」还没有答案。所以恒 `00` 是唯一不撒谎的选择
|
|
96
|
+
* (`00` = 不声明「已采样」);编一个 `01` 是替下游做决定。⚠️ 别下一轮「顺手」改成跟随采样
|
|
97
|
+
* (见 `docs/spec.md` §10 2026-09-21 ⑦)。入站侧 `parseTraceparent` 也从不读 flags。
|
|
94
98
|
*
|
|
95
99
|
* 调用方保证给的是内部 id 或线缆 id(本函数只做投影,不做合法性判定)。
|
|
96
100
|
*/
|
|
@@ -170,3 +174,63 @@ export declare function capabilityKindOf(span: Span): string;
|
|
|
170
174
|
export interface TraceSink {
|
|
171
175
|
export(trace: Trace): void | Promise<void>;
|
|
172
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* **记账事件**(增量出口,`docs/plans/2026-09-21-incremental-trace-export-and-sampling.md`)——
|
|
179
|
+
* run **进行中**就能拿到的明细。它与 `TraceSink` 是**两条缝**,不是替代关系:
|
|
180
|
+
*
|
|
181
|
+
* - `TraceSink`:run 收尾拿到**完整** trace(成功的 sink 该走这条);
|
|
182
|
+
* - 记账事件:**等不了收尾**的消费者 —— 终端面板、SSE 前端、异步任务进度流。
|
|
183
|
+
*
|
|
184
|
+
* 载荷一律是**增量 + 此刻的拷贝**(交付后框架不再变异它):`span.begin` 只给初始形状
|
|
185
|
+
* (`attributes` / `events` 为空),之后的属性 / 事件 / 链路各走自己的事件类型。
|
|
186
|
+
*
|
|
187
|
+
* **折叠规则**(`tests/engine/trace-events.test.ts` 钉着):按 `seq` 升序把同一次 run 的
|
|
188
|
+
* 全部事件应用到 `span.begin` 建出的 span 上,结果必须**逐字等于** `snapshot(status)`。
|
|
189
|
+
* ⇒ 这条不变量一次钉住四件事:不丢、不重、顺序正确、增量与终态同源。要改记账点而不派发事件,
|
|
190
|
+
* 那个用例会红。
|
|
191
|
+
*
|
|
192
|
+
* `seq`:recorder 内**每次记账动作都自增**(与当时有没有订阅者无关)—— 于是「订阅早」与
|
|
193
|
+
* 「订阅晚」看到的同一个事件序号一致。SSE 的 `Last-Event-ID` 重放、去重、跨段对齐都靠它。
|
|
194
|
+
*
|
|
195
|
+
* 投放纪律与 sink 同款:**同步派发、不 await**、订阅者抛错被吞(观测失败不击穿业务)。
|
|
196
|
+
* ⚠️ 但它**不保证送达**(宿主自己的流断了就断了,没有 sink 那层兜底/重试语义)。
|
|
197
|
+
*/
|
|
198
|
+
export type TraceRecordEventPayload = {
|
|
199
|
+
type: 'span.begin';
|
|
200
|
+
span: Span;
|
|
201
|
+
} | {
|
|
202
|
+
type: 'span.end';
|
|
203
|
+
spanId: SpanId;
|
|
204
|
+
endedAt: number;
|
|
205
|
+
status: SpanStatus;
|
|
206
|
+
error?: SpanError;
|
|
207
|
+
usage?: Usage;
|
|
208
|
+
} | {
|
|
209
|
+
type: 'span.event';
|
|
210
|
+
spanId: SpanId;
|
|
211
|
+
event: SpanEvent;
|
|
212
|
+
} | {
|
|
213
|
+
type: 'span.attribute';
|
|
214
|
+
spanId: SpanId;
|
|
215
|
+
key: string;
|
|
216
|
+
value: string | number | boolean;
|
|
217
|
+
} | {
|
|
218
|
+
type: 'span.link';
|
|
219
|
+
spanId: SpanId;
|
|
220
|
+
link: SpanLink;
|
|
221
|
+
};
|
|
222
|
+
export type TraceRecordEvent = TraceRecordEventPayload & {
|
|
223
|
+
seq: number;
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* 把两个增量记账回调**合成一个**订阅者(前者在前)。
|
|
227
|
+
*
|
|
228
|
+
* 为什么要合成而不是 `??` 覆盖:`onTraceEvent` 是**观察者注册**(同 `TraceSink`:
|
|
229
|
+
* 应用级与全局默认一起收),不是值覆盖 —— 覆盖会让「某次 run / 某个宿主顺手传了自己的
|
|
230
|
+
* 回调」把另一条静默顶掉。两个消费者都要它的场景是真实存在的:宿主自己配了一条应用级
|
|
231
|
+
* 面板回调,`AsyncRunner` 还要为 `GET /tasks/:id/stream` 再挂一条按任务分的缓冲。
|
|
232
|
+
*
|
|
233
|
+
* 为什么内部各自 try/catch:合成后它们在 recorder 眼里是**一个**订阅者,而 recorder 只在
|
|
234
|
+
* 这一层兜错 —— 不隔离的话前一个抛错会吞掉后一个(与「一条订阅者炸了不影响另一条」互为镜像)。
|
|
235
|
+
*/
|
|
236
|
+
export declare function composeTraceEvents(first: ((e: TraceRecordEvent) => void) | undefined, second: ((e: TraceRecordEvent) => void) | undefined): ((e: TraceRecordEvent) => void) | undefined;
|
package/dist/core/trace.js
CHANGED
|
@@ -49,8 +49,12 @@ export function wireSpanId(id) {
|
|
|
49
49
|
/**
|
|
50
50
|
* 生成 W3C `traceparent`(`parseTraceparent` 的镜像:一个解析、一个生成)。
|
|
51
51
|
*
|
|
52
|
-
* flags 位恒为 `00
|
|
53
|
-
*
|
|
52
|
+
* flags 位恒为 `00`。**理由不是「本框架不采样」**(采样早就是明确推荐的宿主配法 ——
|
|
53
|
+
* `docs/observability.md` 2.3),而是**运行期不可知**:记录/导出决策发生在 run **收尾之后**
|
|
54
|
+
* (采样闸门要看完好整棵 trace 才判得出来),而出站调用发生在**运行期** ——
|
|
55
|
+
* 那时「这条 trace 会不会被采样掉」还没有答案。所以恒 `00` 是唯一不撒谎的选择
|
|
56
|
+
* (`00` = 不声明「已采样」);编一个 `01` 是替下游做决定。⚠️ 别下一轮「顺手」改成跟随采样
|
|
57
|
+
* (见 `docs/spec.md` §10 2026-09-21 ⑦)。入站侧 `parseTraceparent` 也从不读 flags。
|
|
54
58
|
*
|
|
55
59
|
* 调用方保证给的是内部 id 或线缆 id(本函数只做投影,不做合法性判定)。
|
|
56
60
|
*/
|
|
@@ -85,4 +89,35 @@ export function capabilityKindOf(span) {
|
|
|
85
89
|
return 'subagent';
|
|
86
90
|
return 'capability';
|
|
87
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* 把两个增量记账回调**合成一个**订阅者(前者在前)。
|
|
94
|
+
*
|
|
95
|
+
* 为什么要合成而不是 `??` 覆盖:`onTraceEvent` 是**观察者注册**(同 `TraceSink`:
|
|
96
|
+
* 应用级与全局默认一起收),不是值覆盖 —— 覆盖会让「某次 run / 某个宿主顺手传了自己的
|
|
97
|
+
* 回调」把另一条静默顶掉。两个消费者都要它的场景是真实存在的:宿主自己配了一条应用级
|
|
98
|
+
* 面板回调,`AsyncRunner` 还要为 `GET /tasks/:id/stream` 再挂一条按任务分的缓冲。
|
|
99
|
+
*
|
|
100
|
+
* 为什么内部各自 try/catch:合成后它们在 recorder 眼里是**一个**订阅者,而 recorder 只在
|
|
101
|
+
* 这一层兜错 —— 不隔离的话前一个抛错会吞掉后一个(与「一条订阅者炸了不影响另一条」互为镜像)。
|
|
102
|
+
*/
|
|
103
|
+
export function composeTraceEvents(first, second) {
|
|
104
|
+
if (!first)
|
|
105
|
+
return second;
|
|
106
|
+
if (!second)
|
|
107
|
+
return first;
|
|
108
|
+
return (e) => {
|
|
109
|
+
try {
|
|
110
|
+
first(e);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* 观测不击穿业务(与 flushSinks 同款) */
|
|
114
|
+
}
|
|
115
|
+
try {
|
|
116
|
+
second(e);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
/* 同上 */
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
88
123
|
//# sourceMappingURL=trace.js.map
|
package/dist/core/trace.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trace.js","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqEH,sEAAsE;AACtE,MAAM,cAAc,GAAG,6DAA6D,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAgC;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACvC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED
|
|
1
|
+
{"version":3,"file":"trace.js","sourceRoot":"","sources":["../../src/core/trace.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqEH,sEAAsE;AACtE,MAAM,cAAc,GAAG,6DAA6D,CAAC;AAErF;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAgC;IAC/D,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACzB,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IACvC,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,SAAS,CAAC;IAClE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,OAAO,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,MAAc;IAC/D,OAAO,MAAM,WAAW,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,KAAK,CAAC;AAC/D,CAAC;AAqDD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,KAAY,EAAE,KAAY;IACpD,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,KAAK,CAAC,UAAU,CAAC,CAAC;IACpE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,gBAAgB,CAAC,IAAU;IACzC,IAAI,IAAI,CAAC,UAAU,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,OAAO,CAAC;IACxD,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC;IAC9D,OAAO,YAAY,CAAC;AACtB,CAAC;AAgDD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAkD,EAClD,MAAmD;IAEnD,IAAI,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC;IAC1B,IAAI,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAC1B,OAAO,CAAC,CAAC,EAAE,EAAE;QACX,IAAI,CAAC;YACH,KAAK,CAAC,CAAC,CAAC,CAAC;QACX,CAAC;QAAC,MAAM,CAAC;YACP,8BAA8B;QAChC,CAAC;QACD,IAAI,CAAC;YACH,MAAM,CAAC,CAAC,CAAC,CAAC;QACZ,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ;QACV,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「嵌套能力(@SubAgent / @Skill)必须原样交下去的旋钮」—— **穷尽转发的单一真源**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么要有这个文件(`docs/guards.md` §2 的「手写转发列表不得漏字段」):
|
|
5
|
+
*
|
|
6
|
+
* 主循环把一批旋钮经 `ToolRunContext` 交给工具,子 agent / skill 拉起自己的 llm 循环时
|
|
7
|
+
* **必须**再把它们交下去。这份清单此前手写在**两个**调用点(`toolkit/subagent.ts` 与
|
|
8
|
+
* `toolkit/skill.ts`),每处七八行 `ctx.X,` —— 而 TS 的结构类型对「少写一个键」**不报错**
|
|
9
|
+
* (键都是可选的),`exactOptionalPropertyTypes` 也只堵住「显式传 undefined」那一半。
|
|
10
|
+
* 于是漏字段是**静默**的:历史事故就是 `runAgentScoped` 漏 `toolTimeoutMs`,跨 engine →
|
|
11
|
+
* toolkit → ctx 三层没人发现,后果还是**反的** —— 子循环 `withTimeout(p, 0)` 永不超时,
|
|
12
|
+
* 而 MCP 桥又另起自己的 60s 兜底计时器(双计时器、双账本)。
|
|
13
|
+
*
|
|
14
|
+
* 本文件的机制有两半,缺一不可:
|
|
15
|
+
*
|
|
16
|
+
* 1. **取值收一处**:`forwardToolContext(ctx)` 是唯一的取值点,映射类型写成
|
|
17
|
+
* `{ [K in Key]-?: … }` —— 七个键**全必填**(值可以是 `undefined`)。少写一行 = 编译错误,
|
|
18
|
+
* 而不是静默漏。调用点播 `...forwardToolContext(ctx)` 即可,**没有**可漏的地方。
|
|
19
|
+
* 2. **归类要穷尽**:`ToolRunContext` 的每个键必须在「转发」或「引擎自装配」里各就各位。
|
|
20
|
+
* 新增一个可选字段而两边都没归类 ⇒ `UnclassifiedToolContextKey` 不再是 `never`
|
|
21
|
+
* ⇒ `tests/types/forwarding.types.ts` 的断言编译失败(`typecheck:types` 是 verify-all 第 3 步)。
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ 唯一需要**覆盖**而不是直取的是 `signal`:两个调用点交下去的是
|
|
24
|
+
* `combineSignals(ctx.signal, ctx.abandoned)`(子循环还要能被「放弃等待」打断),
|
|
25
|
+
* 所以那里写成 `{ ...forwardToolContext(ctx), signal: combined }` —— 覆盖是显式的,
|
|
26
|
+
* 其余六个键仍然不可能漏。
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ 本模块不是公共 API(不进 `src/index.ts`):它是内部纪律的落点。
|
|
29
|
+
*/
|
|
30
|
+
import type { ToolRunContext } from '../core/tool.js';
|
|
31
|
+
/**
|
|
32
|
+
* 从 `ToolRunContext` 转进子循环的键。顺序即文档顺序。
|
|
33
|
+
*
|
|
34
|
+
* 加/删这里**必须**同时改 `forwardToolContext` 的返回对象 —— 映射类型要求每个键都出现。
|
|
35
|
+
*/
|
|
36
|
+
export declare const FORWARDED_TOOL_CONTEXT_KEYS: readonly [
|
|
37
|
+
/** 中断信号(调用点通常再 combine 上 「放弃等待」) */
|
|
38
|
+
'signal',
|
|
39
|
+
/** 宿主价格表(漏了 ⇒ 子循环里自定义定价模型退化成「未定价」,`maxCostUsd` 静默失效) */
|
|
40
|
+
'priceOverrides',
|
|
41
|
+
/** 未定价告警回调(漏了 ⇒ 子循环里算不出成本却没人知道) */
|
|
42
|
+
'onUnpricedModel',
|
|
43
|
+
/** 正文截断口径(漏了 ⇒ 同一棵树上主/子 agent 的可见性不一致) */
|
|
44
|
+
'maxEventChars',
|
|
45
|
+
/** 成本护栏(漏了 ⇒ 护栏在子循环期间离线) */
|
|
46
|
+
'maxTotalTokens', 'maxCostUsd',
|
|
47
|
+
/** 超时裁判权(漏了 ⇒ 永不超时 + 桥另起计时器,见文件头) */
|
|
48
|
+
'toolTimeoutMs'];
|
|
49
|
+
export type ForwardedToolContextKey = (typeof FORWARDED_TOOL_CONTEXT_KEYS)[number];
|
|
50
|
+
/**
|
|
51
|
+
* 交给子循环的那一组值:**七个键全必填**(值可以是 `undefined` —— 那表示「本 run 没设」,
|
|
52
|
+
* 与「忘了转发」是两件事,后者在类型上写不出来)。
|
|
53
|
+
*/
|
|
54
|
+
export type ForwardedToolContext = {
|
|
55
|
+
[K in ForwardedToolContextKey]-?: ToolRunContext[K];
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* 引擎自己装配、**刻意不**转发的键。每一个都要写清理由 —— 不写的代价是下一个人
|
|
59
|
+
* 不知道该不该转发,于是又手写一遍清单。
|
|
60
|
+
*/
|
|
61
|
+
export type ToolContextNotForwardedKey =
|
|
62
|
+
/** 模型客户端:子循环用**它自己**的一份(capability 的 client 由调用点从别处拿) */
|
|
63
|
+
'client'
|
|
64
|
+
/** 记账器:子循环写**同一条** trace,但由引擎显式传 `recorder`,不走这组旋钮 */
|
|
65
|
+
| 'recorder'
|
|
66
|
+
/** 父 span:子循环的父是 capability span,由调用点显式给 */
|
|
67
|
+
| 'parentSpanId'
|
|
68
|
+
/** 「放弃等待」信号:折进 `signal`(见文件头的 combine),子循环的工具会拿到**它自己**的 */
|
|
69
|
+
| 'abandoned'
|
|
70
|
+
/** 审批决定:属于**本次工具调用**(外层那个工具被批没批),不该灌进子循环的每一次调用 */
|
|
71
|
+
| 'approval';
|
|
72
|
+
/**
|
|
73
|
+
* 未归类的键。全部归类时是 `never`;一旦 `ToolRunContext` 新增字段而上面两组都没收,
|
|
74
|
+
* 它就变成那个字段名 —— 于是 `tests/types/forwarding.types.ts` 里那句赋 `true` 编译失败。
|
|
75
|
+
*/
|
|
76
|
+
export type UnclassifiedToolContextKey = Exclude<keyof ToolRunContext, ForwardedToolContextKey | ToolContextNotForwardedKey>;
|
|
77
|
+
/**
|
|
78
|
+
* 取值:把 `ctx` 上该转发的七个键原样取出(**不判空** —— `undefined` 是有信息的值,
|
|
79
|
+
* 表示「本 run 没设这个旋钮」,子循环该用它自己的缺省;判空会把它吃成「没转发」)。
|
|
80
|
+
*
|
|
81
|
+
* ⚠️ 不做真值判定:`maxEventChars: false`(不截断)与 `maxTotalTokens: 0` 都是有意义的值,
|
|
82
|
+
* 真值判定会把它们变成「没设」。这与 `engine/tool-context.ts` 的三档判定口径一致。
|
|
83
|
+
*/
|
|
84
|
+
export declare function forwardToolContext(ctx: ToolRunContext): ForwardedToolContext;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 从 `ToolRunContext` 转进子循环的键。顺序即文档顺序。
|
|
3
|
+
*
|
|
4
|
+
* 加/删这里**必须**同时改 `forwardToolContext` 的返回对象 —— 映射类型要求每个键都出现。
|
|
5
|
+
*/
|
|
6
|
+
export const FORWARDED_TOOL_CONTEXT_KEYS = [
|
|
7
|
+
/** 中断信号(调用点通常再 combine 上 「放弃等待」) */
|
|
8
|
+
'signal',
|
|
9
|
+
/** 宿主价格表(漏了 ⇒ 子循环里自定义定价模型退化成「未定价」,`maxCostUsd` 静默失效) */
|
|
10
|
+
'priceOverrides',
|
|
11
|
+
/** 未定价告警回调(漏了 ⇒ 子循环里算不出成本却没人知道) */
|
|
12
|
+
'onUnpricedModel',
|
|
13
|
+
/** 正文截断口径(漏了 ⇒ 同一棵树上主/子 agent 的可见性不一致) */
|
|
14
|
+
'maxEventChars',
|
|
15
|
+
/** 成本护栏(漏了 ⇒ 护栏在子循环期间离线) */
|
|
16
|
+
'maxTotalTokens',
|
|
17
|
+
'maxCostUsd',
|
|
18
|
+
/** 超时裁判权(漏了 ⇒ 永不超时 + 桥另起计时器,见文件头) */
|
|
19
|
+
'toolTimeoutMs',
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* 取值:把 `ctx` 上该转发的七个键原样取出(**不判空** —— `undefined` 是有信息的值,
|
|
23
|
+
* 表示「本 run 没设这个旋钮」,子循环该用它自己的缺省;判空会把它吃成「没转发」)。
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ 不做真值判定:`maxEventChars: false`(不截断)与 `maxTotalTokens: 0` 都是有意义的值,
|
|
26
|
+
* 真值判定会把它们变成「没设」。这与 `engine/tool-context.ts` 的三档判定口径一致。
|
|
27
|
+
*/
|
|
28
|
+
export function forwardToolContext(ctx) {
|
|
29
|
+
return {
|
|
30
|
+
signal: ctx.signal,
|
|
31
|
+
priceOverrides: ctx.priceOverrides,
|
|
32
|
+
onUnpricedModel: ctx.onUnpricedModel,
|
|
33
|
+
maxEventChars: ctx.maxEventChars,
|
|
34
|
+
maxTotalTokens: ctx.maxTotalTokens,
|
|
35
|
+
maxCostUsd: ctx.maxCostUsd,
|
|
36
|
+
toolTimeoutMs: ctx.toolTimeoutMs,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=forwarded.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forwarded.js","sourceRoot":"","sources":["../../src/engine/forwarded.ts"],"names":[],"mappings":"AA+BA;;;;GAIG;AACH,MAAM,CAAC,MAAM,2BAA2B,GAAG;IACzC,oCAAoC;IACpC,QAAQ;IACR,wDAAwD;IACxD,gBAAgB;IAChB,mCAAmC;IACnC,iBAAiB;IACjB,0CAA0C;IAC1C,eAAe;IACf,4BAA4B;IAC5B,gBAAgB;IAChB,YAAY;IACZ,qCAAqC;IACrC,eAAe;CACP,CAAC;AAqCX;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAmB;IACpD,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,eAAe,EAAE,GAAG,CAAC,eAAe;QACpC,aAAa,EAAE,GAAG,CAAC,aAAa;QAChC,cAAc,EAAE,GAAG,CAAC,cAAc;QAClC,UAAU,EAAE,GAAG,CAAC,UAAU;QAC1B,aAAa,EAAE,GAAG,CAAC,aAAa;KACjC,CAAC;AACJ,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { JsonSchema } from '../core/tool.js';
|
|
2
2
|
import type { RunAgentOptions } from './types.js';
|
|
3
|
+
import type { TraceLimits } from './tracer.js';
|
|
3
4
|
/**
|
|
4
5
|
* Agentia —— run 生效旋钮:**缺省解析 + 快照编码**(spec §5 / §9 G3)。
|
|
5
6
|
*
|
|
@@ -25,4 +26,6 @@ export declare const DEFAULT_MAX_ITERATIONS = 40;
|
|
|
25
26
|
* 只放标量(OTLP/日志/看板都能直接吃);缺省值也记,这样"没配"与"配了缺省值"可区分于
|
|
26
27
|
* "该项不存在"。函数型选项只记"配没配",不记函数体。
|
|
27
28
|
*/
|
|
28
|
-
export declare function runConfigSnapshot(options: RunAgentOptions<JsonSchema>
|
|
29
|
+
export declare function runConfigSnapshot(options: RunAgentOptions<JsonSchema> & {
|
|
30
|
+
traceLimits?: TraceLimits;
|
|
31
|
+
}): Record<string, string | number | boolean>;
|
|
@@ -28,7 +28,12 @@ export const DEFAULT_MAX_ITERATIONS = 40;
|
|
|
28
28
|
* 只放标量(OTLP/日志/看板都能直接吃);缺省值也记,这样"没配"与"配了缺省值"可区分于
|
|
29
29
|
* "该项不存在"。函数型选项只记"配没配",不记函数体。
|
|
30
30
|
*/
|
|
31
|
-
export function runConfigSnapshot(
|
|
31
|
+
export function runConfigSnapshot(
|
|
32
|
+
// traceLimits 不在 RunAgentOptions 上:它是 run 层(ExecuteRunOptions /
|
|
33
|
+
// RunInvocationOptions)的旋钮 —— executeRun 在 run 入口用 resolveTraceLimits
|
|
34
|
+
// 校验后交给 recorder(见 runtime/run.ts),再把**同一份** options 透传到本函数,
|
|
35
|
+
// 所以这里读到的就是生效值(「认下缺省值的人就是写进 trace 的人」)。
|
|
36
|
+
options) {
|
|
32
37
|
const out = {
|
|
33
38
|
'config.model': resolveDefaultModel(options.model),
|
|
34
39
|
'config.maxTokens': options.maxTokens ?? DEFAULT_MAX_TOKENS,
|
|
@@ -52,6 +57,14 @@ export function runConfigSnapshot(options) {
|
|
|
52
57
|
// 容易被读成「上限为 0」,'off' 一句话说清是**没有上限**
|
|
53
58
|
if (options.maxEventChars != null)
|
|
54
59
|
out['config.maxEventChars'] = options.maxEventChars === false ? 'off' : options.maxEventChars;
|
|
60
|
+
// 记账数量闸:与 maxEventChars(管「多长」)正交,这个管「多少」。键名镜像选项路径
|
|
61
|
+
//(同 config.retry.maxAttempts / config.contextPolicy.budgetTokens 的口径)。
|
|
62
|
+
// 0 = 「一条都不记」是有意义的值,原样记 0 —— 不记成 'off'(本文件里 'off' = 不设上限,
|
|
63
|
+
// 与上限为 0 是两回事)。坏值(NaN / 负数 / 小数)在 run 入口已被 resolveTraceLimits
|
|
64
|
+
// 拦下(抛 TypeError),走不到这里,所以不需要 maxToolConcurrency 那样的净化分支。
|
|
65
|
+
const maxEvents = options.traceLimits?.maxEvents;
|
|
66
|
+
if (maxEvents != null)
|
|
67
|
+
out['config.traceLimits.maxEvents'] = maxEvents;
|
|
55
68
|
// 重试:记生效的 maxAttempts(0 = 关闭)—— 比记 "custom/default" 更有信息量
|
|
56
69
|
const retryCfg = resolveRetry(options.retry);
|
|
57
70
|
out['config.retry.maxAttempts'] = retryCfg ? retryCfg.maxAttempts : 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-config.js","sourceRoot":"","sources":["../../src/engine/run-config.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"run-config.js","sourceRoot":"","sources":["../../src/engine/run-config.ts"],"names":[],"mappings":"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"}
|
package/dist/engine/spec.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { MessageParam } from '../core/message.js';
|
|
2
2
|
import type { AgentTool, ApprovalDecision, ModelClient, ModelPricing } from '../core/tool.js';
|
|
3
3
|
import type { BlackboardSeed } from '../core/blackboard.js';
|
|
4
|
-
import type { TraceContext } from '../core/trace.js';
|
|
4
|
+
import type { TraceContext, TraceRecordEvent } from '../core/trace.js';
|
|
5
5
|
import type { ContextPolicy } from './types.js';
|
|
6
6
|
import type { RetryOptions } from './retry.js';
|
|
7
|
+
import type { TraceLimits } from './tracer.js';
|
|
7
8
|
/**
|
|
8
9
|
* Agentia —— run 调用契约(spec §6.3 三类触发共用同一份入参形态)。
|
|
9
10
|
* transport 层(同步 RPC / 异步任务 / 定时)只谈 RunInput / RunSpec,
|
|
@@ -30,6 +31,30 @@ export interface RunInvocationOptions {
|
|
|
30
31
|
retry?: RetryOptions | false;
|
|
31
32
|
/** 幂等键:异步宿主的 at-least-once 去重依据 */
|
|
32
33
|
idempotencyKey?: string;
|
|
34
|
+
/**
|
|
35
|
+
* **记账的数量上限**(spec §9.4 的答案里「让少记了数据可数」那一半):整条 trace 的
|
|
36
|
+
* 事件总数上限,超限即停止记账并在交付时于 run 根写一笔 `trace.truncated`
|
|
37
|
+
* (`{ droppedEvents, limit }`)—— 缺口位置可预测(尾巴)、且有计数 ⇒ 可解释。
|
|
38
|
+
*
|
|
39
|
+
* 与 `maxEventChars`(单个事件**正文长度**)正交:一个管「多长」、一个管「多少」,
|
|
40
|
+
* 各有各的家,两个都「不设 = 不限」。缺省**不设** —— 全量记账是本框架的承诺,
|
|
41
|
+
* 上限是给「长跑 + 大出参」的宿主一个显式的闸。坏值(NaN / 负数 / 小数)在 run 入口
|
|
42
|
+
* 抛 `TypeError`,不静默失效。见 `TraceLimits`。
|
|
43
|
+
*/
|
|
44
|
+
traceLimits?: TraceLimits;
|
|
45
|
+
/**
|
|
46
|
+
* **增量记账出口**(`docs/plans/2026-09-21-incremental-trace-export-and-sampling.md`):
|
|
47
|
+
* run **进行中**逐笔回调(span 开/合、事件、属性、链路),给「等不了收尾」的消费者 ——
|
|
48
|
+
* 终端面板、SSE 前端、异步任务进度流。
|
|
49
|
+
*
|
|
50
|
+
* 与 `TraceSink` 是**两条缝**(不是替代):sink 在 run 收尾拿**完整** trace,本回调在运行期逐笔给。
|
|
51
|
+
* 纪律与 sink 同款:**同步调用、不 await**、抛错被吞(观测失败不击穿业务)。
|
|
52
|
+
* ⚠️ 但它**不保证送达**(宿主自己的流断了就断了),也**不是** sink 的替代。
|
|
53
|
+
*
|
|
54
|
+
* 应用级缺省见 `AppOptions.onTraceEvent`;两者**叠加**(应用级在前),不是覆盖 ——
|
|
55
|
+
* 观察者注册不该互相顶掉(per-run 传了面板回调就让应用级那条失聪,是静默回退)。
|
|
56
|
+
*/
|
|
57
|
+
onTraceEvent?: (e: TraceRecordEvent) => void;
|
|
33
58
|
/**
|
|
34
59
|
* 入站链路上下文(spec §9.2 跨进程关联):触发本次 run 的上游 span 记成 run 根的一条
|
|
35
60
|
* `links`。宿主侧两种给法 —— HTTP 头 `traceparent`(W3C,`createHttpHandler` 自动解析)
|
package/dist/engine/spec.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/engine/spec.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/engine/spec.ts"],"names":[],"mappings":"AA4HA;;;;;;;;GAQG;AACH,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;IAC/B,CAAC;CACF;AAED,wCAAwC;AACxC,MAAM,UAAU,iBAAiB,CAAC,KAAyB;IACzD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,mDAAmD;QACnD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,cAAc,CAAC,kBAAkB,CAAC,CAAC;QACzD,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,cAAc,CAAC,kBAAkB,CAAC,CAAC;QACrE,OAAO,KAAuB,CAAC;IACjC,CAAC;IACD,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,CAAC,GAAG,KAAiE,CAAC;QAC5E,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,MAAM,IAAI,cAAc,CAAC,kBAAkB,CAAC,CAAC;YAC1E,OAAO,CAAC,CAAC,QAA0B,CAAC;QACtC,CAAC;QACD,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM;YAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QAC3F,IAAI,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IACvF,CAAC;IACD,MAAM,IAAI,cAAc,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;AACjF,CAAC"}
|