@migor/cli 0.8.2 → 0.8.3
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 +77 -1
- package/dist/AGENTS.md +17 -1
- package/dist/templates/package.json +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,81 @@
|
|
|
5
5
|
(0.x 阶段:minor 可含破坏性变更,每个破坏性变更都在对应版本的「迁移」小节里写明)。
|
|
6
6
|
决策的完整证据链在 `docs/spec.md` §10(带时间线的决策日志)。
|
|
7
7
|
|
|
8
|
+
## [0.8.3] - 2026-09-21
|
|
9
|
+
|
|
10
|
+
### 变更
|
|
11
|
+
|
|
12
|
+
> 本版主题(窗口 `0.8.2 → 0.8.3`,含 #115 与 #116):**增量 trace 出口落地**。记账与交付之间
|
|
13
|
+
> 此前**没有缝** —— `TraceRecorder` 的唯一出口是收尾的 `snapshot()`,所以「等不了 run 收尾」
|
|
14
|
+
> 的消费者(终端面板 / SSE 前端 / 异步任务进度流)拿不到任何东西。本版补上那条缝,顺手把
|
|
15
|
+
> spec §7 F3 的两条后置项与 §9.4 的记录成本问题同日收口。
|
|
16
|
+
> **框架 API 无破坏性变更**:只新增(`onTraceEvent` / `traceLimits.maxEvents` /
|
|
17
|
+
> `AsyncRunner.streamBufferEvents` / `GET /tasks/:id/stream` / `/run` SSE 的 `trace.event` 帧),
|
|
18
|
+
> **既有签名、既有三帧 SSE、既有默认行为逐字不变** —— 不认识新帧的老客户端行为零变化。
|
|
19
|
+
> #116 全部落在**仓库自身**(守卫 + 一次行为等价的重构),不含任何运行时行为变更。
|
|
20
|
+
|
|
21
|
+
### 新增
|
|
22
|
+
|
|
23
|
+
- **`onTraceEvent`:run 进行中逐笔拿记账事件**。`RunInvocationOptions.onTraceEvent`(单次)与
|
|
24
|
+
`AppOptions.onTraceEvent`(应用级缺省)**叠加**(应用级在前)而不是覆盖 —— 观察者是注册不是
|
|
25
|
+
值覆盖,覆盖会让「某次 run 顺手传了个面板回调」把应用级那条静默顶掉。载荷是
|
|
26
|
+
`TraceRecordEvent`(`span.begin` / `span.end` / `span.event` / `span.attribute` / `span.link`,
|
|
27
|
+
**增量 + 此刻的拷贝**)。
|
|
28
|
+
四条纪律:同步派发不 await(订阅者是观察者,不该把 run 变成它的调度)、抛错被吞(与
|
|
29
|
+
`flushSinks` 同款)、无订阅者零派发、**seq 每次记账动作都占号**(与有没有订阅者无关 ——
|
|
30
|
+
否则「订阅晚的人」看到的序号会与一直订阅的人不一致,重放与去重都会错位)。
|
|
31
|
+
它与 `TraceSink` **不互相替代**:sink 是收尾拿整棵、不保证运行期可见;这条是运行期逐笔、
|
|
32
|
+
不保证送达。要「收尾的整棵」继续用 sink。
|
|
33
|
+
- **`POST /run` 的 SSE 新增一族 `trace.event` 帧**。帧名取**一族** + body 里带 `type`,而不是
|
|
34
|
+
每类型一帧:将来加新事件类型时,老客户端只是漏掉一种 `type`,而不是漏掉一种**帧名**(后者
|
|
35
|
+
更隐蔽)。既有三帧(`text.delta` / `run.end` / `error`)逐字未变。
|
|
36
|
+
- **`GET /tasks/:id/stream`:异步任务的进度流**。从头重放 → 转实时 → 终态以 `task.end` 收口关流;
|
|
37
|
+
`Last-Event-ID` / `?from=` 指**流自己的序号**(一个任务可能跨多个 run 段 —— HITL 挂起→恢复、
|
|
38
|
+
崩溃重投,每段是独立的一次 run、`seq` 从 1 重来,而流的读者要的是一条**连续的**流)。
|
|
39
|
+
四条边界:`awaiting_approval` **不是终态**(流继续开着 —— 关掉的话「等审批结果的前端」正好在
|
|
40
|
+
最需要的时候断线);这条流的读者是**旁观者**,背压 / 断开**不 abort 任务**(与 `/run` 的 SSE
|
|
41
|
+
刻意相反:那里的下游是 run 的所有者,背压等于「别继续烧 token 了」);跨进程(别的 runner 跑的
|
|
42
|
+
任务、同一个 store)→ 一帧 `stream.unavailable` + 终态 `task.end`,**不假装实时**;缓冲超限 →
|
|
43
|
+
丢最旧并先发一帧 `stream.truncated`(**不静默**)。缓冲用 `AsyncRunner` 的
|
|
44
|
+
`streamBufferEvents` 调(缺省每任务 500 条;终态流只留最近 16 条)。事件**不落 store** 是有意
|
|
45
|
+
的:实测事件数 = 2 × 工具调用、正文 KB 级 ⇒ 每条工具调用要把 KB 级正文写库两次(写放大),而
|
|
46
|
+
宿主本来就有自己的总线(`onTraceEvent` 就是给它的缝)。
|
|
47
|
+
- **`traceLimits.maxEvents`:整条 trace 的事件总数上限**。超限即**停止记账**,交付时在 run 根写
|
|
48
|
+
一笔 `trace.truncated{droppedEvents, limit}` —— 缺口位置可预测(尾巴)且**有计数**。
|
|
49
|
+
刻意**不做**环形缓冲(丢最旧、留最近):那会让 trace 中间出现空洞,而空洞比「尾巴截断」难解释
|
|
50
|
+
得多(「这一回合怎么没有工具事件」)。`0` = 一条都不记(**有意义的值**,仍有计数);不设 = 不限。
|
|
51
|
+
坏值(NaN / ±Infinity / 负数 / 小数)在 run 入口抛 `TypeError` —— 静默接受会让闸门**形同不存在**
|
|
52
|
+
(`NaN` 让 `>=` 恒假),而使用者以为自己设了上限。
|
|
53
|
+
与既有的 `maxEventChars` **正交**:一个管「单个事件正文多长」、一个管「多少」。
|
|
54
|
+
|
|
55
|
+
### 文档
|
|
56
|
+
|
|
57
|
+
- `docs/usage-guide.md`:`onTraceEvent` / `traceLimits` / `streamBufferEvents` 与
|
|
58
|
+
`GET /tasks/:id/stream` 的用法;§7 边界表补任务进度流的边界(内存 / 跨进程)。
|
|
59
|
+
- `docs/observability.md`:采样在「不内建」之外补上**可算 + 可数** —— §2.3 新增采样率换算表,
|
|
60
|
+
示例 `sampleSink` 增加丢弃计数(`dropped()` / `onDrop`)。**框架仍然不内建采样器**(既有决策
|
|
61
|
+
不变:采样是配方 2.3,`examples/observability` 已有成品;再造一个就是同一件事的两份实现)。
|
|
62
|
+
- `docs/spec.md` §9.4 从「唯一剩下的开放问题」变为**决定**:默认全量、截断默认开、采样不内建,
|
|
63
|
+
框架侧只新增「数量上限 + 丢弃计数」这一件 —— 让「少记了数据」可数。
|
|
64
|
+
- **一处面向使用者的说明被更正**:`formatTraceparent` 的 flags 继续恒 `00`,但理由从「本框架
|
|
65
|
+
不采样」改为**「运行期不可知」** —— 记录 / 导出决策发生在**收尾之后**,出站调用发生在
|
|
66
|
+
**运行期**,那时没有答案。所以它**不会**跟随采样率变成 `01`:原理由在采样成为推荐配法后已不
|
|
67
|
+
严谨,若照它改成「跟随采样」反而是错的。
|
|
68
|
+
|
|
69
|
+
### 仓库自身(不面向使用者)
|
|
70
|
+
|
|
71
|
+
- **`docs/guards.md` §2 从 4 行清到 1 行** —— 三条待守形状建成了机器守卫(`0` 的语义真源 /
|
|
72
|
+
穷尽转发 / 队列消费者配方门禁),细节见该文件 §1.2 / §1.3。顺带登记了两个此前无人写明的事实:
|
|
73
|
+
`intervalMs` 在 `Scheduler.every`(必须 > 0)与 `metricsSink`(`0` = 立即导出)里**同名反义**;
|
|
74
|
+
数量类旋钮里只有 `mapWithConcurrency` 把 `0` 读作「不限」(`maxRetries` / `maxEvents` /
|
|
75
|
+
`maxIterations` 都是「就是不做」)。
|
|
76
|
+
- `scripts/verify-all.sh` 与 CI 的**步数不变**:新增检查一律折进已有步骤(步骤数写在 CI 的必需
|
|
77
|
+
状态检查名里,加一步就要同时改 workflow 与分支保护)。
|
|
78
|
+
|
|
79
|
+
**迁移**:无(框架 API 无破坏性变更,既有代码不需要任何改动)。若你的宿主自己解析 `/run` 的 SSE,
|
|
80
|
+
它**不需要**认识新的 `trace.event` 帧 —— 不认识就忽略,行为与升级前逐字一致。要消费增量事件,
|
|
81
|
+
新接 `onTraceEvent`(程序内)或 `GET /tasks/:id/stream`(远程看进度)即可。
|
|
82
|
+
|
|
8
83
|
## [0.8.2] - 2026-09-21
|
|
9
84
|
|
|
10
85
|
### 变更
|
|
@@ -864,7 +939,8 @@
|
|
|
864
939
|
首个公开发布:`@migor/agentia` + `@migor/cli`(scope `@migor/*`),两包版本同步。
|
|
865
940
|
框架本体单包;CLI 独立成包(workspaces)。
|
|
866
941
|
|
|
867
|
-
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.8.
|
|
942
|
+
[Unreleased]: https://github.com/retrychx/agentia/compare/v0.8.3...HEAD
|
|
943
|
+
[0.8.3]: https://github.com/retrychx/agentia/releases/tag/v0.8.3
|
|
868
944
|
[0.8.2]: https://github.com/retrychx/agentia/releases/tag/v0.8.2
|
|
869
945
|
[0.8.1]: https://github.com/retrychx/agentia/releases/tag/v0.8.1
|
|
870
946
|
[0.8.0]: https://github.com/retrychx/agentia/releases/tag/v0.8.0
|
package/dist/AGENTS.md
CHANGED
|
@@ -181,6 +181,7 @@ npx agentia --version # CLI 版本(= -v)
|
|
|
181
181
|
| `tools` | 直接追加到主菜单的**裸工具**(`AgentTool[]`):给「构造期才知道有哪些工具」的场合(典型:MCP 桥,见 §6)。与能力**同过中间件、同进重名查重**,不是旁路 |
|
|
182
182
|
| `middleware` | 能力调用中间件(洋葱链,链序 = 注册顺序) |
|
|
183
183
|
| `sinks` | trace 出口,run 收尾投递 |
|
|
184
|
+
| `onTraceEvent` | **增量记账出口的缺省值**:run **进行中**逐笔回调(`span.begin` / `span.end` / `span.event` / `span.attribute` / `span.link`),给等不了收尾的消费者(面板 / SSE / 任务流)。与 `sinks` 是**两条缝**、可同时配;单次 run 给了自己的那个是**叠加**(应用级在前)而非覆盖。⚠️ **不保证送达**(宿主自己的流断了就断了),也不替代 `sinks` |
|
|
184
185
|
| `maxTotalTokens` | 缺省成本硬管控:整条 run(**含子 agent / skill 子循环**,上限经 `ToolRunContext` 透传)累计 token 上限(可被单次 run 覆盖) |
|
|
185
186
|
| `maxCostUsd` | 缺省成本硬管控:累计成本(美元)上限(**依赖模型在价格表内**,见 `priceOverrides`;未定价模型会留 `usage.unpriced` 事件,所以「护栏有没有真的生效」看得见) |
|
|
186
187
|
| `priceOverrides` | 价格表覆盖/追加(`$/1M tokens`):覆盖内置同名项,或给非 Anthropic 模型定价(如 `{ 'deepseek-chat': { in: 0.27, out: 1.10 } }`)。**透传给子 agent/skill 的子循环** —— 不会「主 agent 有成本、子 agent 恒 0」。非法单价在 run 开始即抛错 |
|
|
@@ -188,6 +189,7 @@ npx agentia --version # CLI 版本(= -v)
|
|
|
188
189
|
| `toolTimeoutMs` | 缺省单工具超时(毫秒);超时该条 tool_result 记 is_error,不杀 run |
|
|
189
190
|
| `maxToolConcurrency` | 缺省同回合并行工具上限;不设 = 不限(全并行) |
|
|
190
191
|
| `maxEventChars` | 缺省 trace 事件正文截断上限(可被单次 run 覆盖):数字 = 入参/出参统一用该上限,`false` = **不截断**(完整正文进 trace,面板里能展开看全文);不设 = 框架缺省 |
|
|
192
|
+
| `traceLimits` | **记账的数量上限** `{ maxEvents? }`(可被单次 run 覆盖):整条 trace 的事件总数闸。超限即停止记账,并在交付时于 run 根写一笔 `trace.truncated{droppedEvents, limit}` —— 缺口位置可预测(尾巴)且**有计数**。与管**长度**的 `maxEventChars` 正交(一个管「多长」、一个管「多少」);不设 = 不限(全量记账是本框架的承诺)。坏值(NaN / 负数 / 小数)在 run 入口抛 `TypeError` |
|
|
191
193
|
|
|
192
194
|
### `app.run(messages, opts?: RunAppOptions)`
|
|
193
195
|
|
|
@@ -199,6 +201,8 @@ npx agentia --version # CLI 版本(= -v)
|
|
|
199
201
|
| `maxIterations` | 单次覆盖 |
|
|
200
202
|
| `client` | 注入 `ModelClient`(换 OpenAI 兼容端点等) |
|
|
201
203
|
| `onText` | 文本增量回调(SSE/终端) |
|
|
204
|
+
| `onTraceEvent` | 单次 run 的**增量记账出口**(见 `createApp` 同名项):与**应用级那个叠加**(应用级在前),不是覆盖 |
|
|
205
|
+
| `traceLimits` | 单次 run 的记账数量上限(覆盖应用级缺省);见 `createApp` 同名项 |
|
|
202
206
|
| `signal` | `AbortSignal`:中止则在飞请求被取消,run 以 `stopReason='aborted'` 收尾(算失败) |
|
|
203
207
|
| `traceContext` | 入站链路上下文 `{ traceId, spanId? }`:触发本次 run 的上游 span 记成 run 根的一条 `links`(不改 `traceId == runId`)。HTTP 宿主认 `traceparent` 头,自动填 —— 见 §6「跨进程关联」 |
|
|
204
208
|
| `blackboard` | 预置黑板种子(配 `Blackboard` 声明合并有键补全) |
|
|
@@ -463,6 +467,7 @@ npm run client # 另一个终端:把四个 RPC 跑一遍
|
|
|
463
467
|
| `POST /run` | body 是 `RunInput`(string / messages / `{prompt\|text\|messages}`);带 `Accept: text/event-stream` 则走 SSE | 200 `{ runId, status, stopReason, finalText, typed?, trace, error? }` —— **`status=failed` 也照返 200**(`rethrow:false` 语义:硬失败以 `error` 字段表达,不用 HTTP 错误码) |
|
|
464
468
|
| `POST /tasks` | `{ input, idempotencyKey?, options? }` —— `input` 同 `RunInput`;`options` 是 `RunInvocationOptions` | 202 `TaskRecord`(`status: 'queued'`);同 `idempotencyKey` 未失败则去重、直接返回既有记录(**同步 store** 当场判定;**异步 store** 下只保证**同进程内并发提交**不重复执行,跨进程与终态后重提仍是 at-least-once —— 见 §7「同键去重的能力边界」) |
|
|
465
469
|
| `GET /tasks/:id` | — | 200 `TaskRecord`;不存在 → 404。**停机中仍可轮询**(否则拿不到在飞任务的结果) |
|
|
470
|
+
| `GET /tasks/:id/stream` | — | **任务进度流(SSE)**:先在 `id:` 里给流序号,逐帧下发 `trace.event`(body 即 `TraceRecordEvent`),终态发 `task.end` 并关闭。断线重连带 `Last-Event-ID`(或 `?from=<序号>`)即可续订 —— 只补该序号之后的事件。缓冲超限先发一帧 `stream.truncated{droppedBefore}`;别的进程在跑的任务发 `stream.unavailable` 后收口(**不假装实时**)。任务不存在 → 404;方法不对 → 405。⚠️ 它的读者是**旁观者**:背压/断开只收口这条流,**不中止任务** |
|
|
466
471
|
| `POST /tasks/:id/approve` | `{ decisions: { <tool_use_id>: { approved, reason? } }, decidedBy? }` | 200 `TaskRecord`(HITL 审批:批准/拒绝挂起任务,见 §6.6「人工审批」);任务不存在 → 404;不在 `awaiting_approval` 状态 → 409;body 非法 → 400。**停机中仍可审批**(与 GET 轮询同理由) |
|
|
467
472
|
| `GET /healthz` | — | 200 `HealthResponse`;**不鉴权**,停机中也回 200 |
|
|
468
473
|
| `GET /metrics` | — | 200 Prometheus 文本(`text/plain; version=0.0.4`);**需在 `createHttpHandler` 里传 `metrics`**,**不鉴权**(与 `/healthz` 同档),停机中也回 |
|
|
@@ -539,7 +544,7 @@ process.on('SIGTERM', async () => {
|
|
|
539
544
|
|
|
540
545
|
- **取消**:`app.run(messages, { signal })` 传 `AbortSignal` —— 框架会 abort 在飞请求(内置 Anthropic / OpenAI 适配器都转发 `signal`),run 以 `stopReason='aborted'` 收尾(算失败)。`createHttpHandler` 已内置「客户端断开即中止」;`AsyncRunner.runTimeoutMs` 到点同样是**真中止**(构造期校验:必须 ≥ 0 的**有限**数 —— NaN/Infinity 会被 `setTimeout` 钳到 1ms,等于每个任务立即超时,故直接抛错;要「不限」传 0 或不设)。
|
|
541
546
|
- **重试**:缺省自动重试可重试失败(429 / 5xx / 连接失败),指数退避 + 抖动。`retry: false` 关闭,或 `retry: { maxAttempts, baseDelayMs, maxDelayMs, jitter, onRetry }` 调参。**只在本次尝试尚未产出任何文本时重试**(已吐出的字无法撤回)。⚠️ 与底层 client 的**内置重试**叠加 —— **两条内置适配器口径一致**(`createAnthropicClient` / `createOpenAIClient` 都有 `maxRetries`,缺省 2,重试同一状态码集合 408/409/429/5xx)—— 建议二选一调(这里 `maxAttempts: 1`,或 `<适配器>({ maxRetries: 0 })`)。`maxRetries` 在**构造期**校验:只收非负安全整数(`0` = 不重试),NaN / ±Infinity / 负数 / 小数一律抛 `TypeError` —— 判定是 `attempt >= maxRetries`,NaN 恒假、Infinity 永不达到,两者都等于**无限重试**(且静默)。
|
|
542
|
-
- **流式**:`POST /run` 带 `Accept: text/event-stream` → SSE 逐帧下发(`text.delta` / `run.end` / `error
|
|
547
|
+
- **流式**:`POST /run` 带 `Accept: text/event-stream` → SSE 逐帧下发(`text.delta` / `run.end` / `error`,外加一族 **`trace.event`** —— 增量记账事件,body 即 `TraceRecordEvent`,用它做实时面板;不认识这一族的老客户端行为零变化);不带该头仍回一元 JSON。
|
|
543
548
|
- **工具超时 / 并发闸门**:`toolTimeoutMs` 超时**不杀 run**(该条 tool_result 记 `is_error`,模型可换路);`maxToolConcurrency` 给同回合的并行工具设上限(默认全并行)。⚠️ 超时 = **放弃等待**:`AgentTool.run` 没有 signal 参数,**副作用可能已发生**;但引擎放弃等待时会 abort `ToolRunContext.abandoned` —— 想真停的工具监听它自行收尾(框架自带的 @SubAgent / @Skill 已这么做:超时即中止子循环,capability span 以 error 收尾)。**超时判定只有一个裁判**:`toolTimeoutMs` 是唯一判据 —— 工具自带的超时(如 MCP 桥的 `timeoutMs`)在设了本项时**不参与**判定;反过来说,工具自判的超时(抛 `code='timeout'` 的错误)与引擎判的记**同一类账**(`errorKind='timeout'`),并同样回 `is_error`。
|
|
544
549
|
|
|
545
550
|
### 6.3 上下文预算与成本
|
|
@@ -629,6 +634,7 @@ trace 出去之后能干什么:指标、调用树面板、调优报告、生
|
|
|
629
634
|
| `TraceRecorder` | 内存 recorder(一次 run 一个);`addLink(spanId, { traceId, spanId? })` 记一条跨 trace 链路(见 §6「跨进程关联」) |
|
|
630
635
|
| `parseTraceparent` | 解析 W3C `traceparent` 头 → `{ traceId, spanId? }`;**非法 / 缺头一律返回 `undefined`**(不抛、不打 400)—— 结果直接交给 `traceContext` 选项,见 §6「跨进程关联」 |
|
|
631
636
|
| `createOtlpExporter` | OTLP/JSON 导出,零依赖;选项见下面「`OtlpExporterOptions`」表 |
|
|
637
|
+
| `TraceRecordEvent` | **增量记账事件**(`onTraceEvent` 的回调参数):`span.begin` / `span.end` / `span.event` / `span.attribute` / `span.link`,每条带单调 `seq`。载荷是**增量 + 此刻的拷贝**(`span.begin` 只给初始形状,属性/事件/链路各走自己的类型);按 `seq` 升序折回必须**逐字等于**收尾时的 trace |
|
|
632
638
|
| `metricsSink` | 指标累加器(Prometheus 文本 / OTLP metrics),满足 `TraceSink` 即接入 —— 见 §6「指标」 |
|
|
633
639
|
| `buildRunReport` | 从一条 trace 生成**调优报告**(能力/模型的耗时、token、成本、错误率排行)—— 见 §6「调优报告」 |
|
|
634
640
|
| `Score` | 质量评分:`{ name; value; source?; comment? }` —— LLM-judge / 人工标注 / eval 结论挂到 trace 上;约定 `value` 为 0–1(布尔结论用 0/1),`source` 记评分来源(eval 名 / `'human'` / judge 模型 id) |
|
|
@@ -696,6 +702,12 @@ runner.submit(msg.value, {
|
|
|
696
702
|
});
|
|
697
703
|
```
|
|
698
704
|
|
|
705
|
+
这条配方**有门禁真跑**:`tests/transport/queueConsumer.test.ts`(内存版 broker + 真
|
|
706
|
+
`AsyncRunner` + 真引擎)把三件事逐条跑出来 —— 同键重投**不重复执行**(断言的是副作用计数,
|
|
707
|
+
不只是 taskId)、`traceparent` 随 `spec.options` 落库后**他进程 `resumePending` 续跑**那次 run
|
|
708
|
+
仍带得上同一条 link、失败不 ack 必须 nack 重投(且**失败**的键允许新任务,否则重投永远拿不到
|
|
709
|
+
第二次执行)。真接 broker 时仍然要自己接:门禁守的是**提交位移与幂等的时机**,不是协议实现。
|
|
710
|
+
|
|
699
711
|
异步任务可以在 `POST /tasks` 的 body 里显式给 `options.traceContext`,它优先于 `traceparent` 头。
|
|
700
712
|
换宿主这条缝不变:gRPC 宿主把 metadata 的 `traceparent` 翻进 `options.traceContext`,
|
|
701
713
|
见 §6.2「gRPC 宿主」。
|
|
@@ -1255,6 +1267,10 @@ const callable = {
|
|
|
1255
1267
|
| schema 校验是**子集** | 只覆盖 `type/properties/required/additionalProperties/enum/items`;`format`/`minimum`/`oneOf` 一律放行 |
|
|
1256
1268
|
| 历史畸形就放弃裁剪 | `trimToolPairs` 遇到非严格交替历史会整体放弃(宁可少裁,也不切出孤立 tool_use 让请求 400) |
|
|
1257
1269
|
| 同键去重的能力边界 | `idempotencyKey` 的去重分三档:**同步 store** 下 `submit` 当场返回既有记录;**异步 store**(Redis / SQLite 等)下 `submit` 是同步门面、无法 await,去重靠**进程内认领表**,只覆盖「**同进程内并发提交**」(2026-09-21 修复前这里会两次都执行);**跨进程并发**与**终态之后重提同键**仍是 at-least-once —— store 的 idem 索引是 last-wins(重提即新任务,`redisStore` / `fsStore` / `sqliteStore` 头注释同口径)。要严格一次,请让副作用自身幂等(或在 store 层做唯一约束) |
|
|
1270
|
+
| 增量出口与 sink 是**两条缝** | `onTraceEvent` 是「运行期逐笔」,`sinks` 是「收尾拿整棵」——消费者与保证都不同:sink 的抛错被吞但仍会**投递**(有兜底语义),`onTraceEvent` **不保证送达**(宿主自己的流断了就断了,没有重试/重放)。要「一条都不能少」用 `sinks`;要「现在就看到」用 `onTraceEvent` |
|
|
1271
|
+
| 任务进度流的边界(内存 / 跨进程) | `GET /tasks/:id/stream` 的事件缓冲在**跑任务的进程内存**里:每任务最近 500 条(可用 `AsyncRunner` 的 `streamBufferEvents` 调),超限丢**最旧**并先发一帧 `stream.truncated`;终态流只留最近 16 条。**跨进程**(队列消费者在别的进程)时没有实时流,只能拿到一帧 `stream.unavailable` + 终态 —— 要跨进程实时请用 `onTraceEvent` 把事件转发到宿主自己的总线(Redis Streams / Kafka) |
|
|
1272
|
+
| `traceLimits` 与 `maxEventChars` 各管一头 | `maxEventChars` 管**单个事件正文多长**(既有),`traceLimits.maxEvents` 管**整条 trace 多少个事件**(本版)。两者正交、都「不设 = 不限」;上限触发时**丢弃量写在 run 根的 `trace.truncated`** 上(不静默)。⚠️ 实测 `maxEventChars: false` + 大出参会让 trace 放大 **13.7×**(`npm run bench:trace` 可复现)—— 先收长度再谈采样,收益顺序比反过来大 |
|
|
1273
|
+
| **采样是导出决策,不是记账决策** | 采样在 `sink` 外做(配方见 `docs/observability.md` 2.3):被采样掉的 trace 在框架内**仍然完整记账**,只是没发给下游。所以别拿「有采样」当「可以少记账」;也正因如此,出站 `traceparent` 的 flags 恒 `00`(记录/导出决策发生在收尾之后,运行期不可知——不替下游声明) |
|
|
1258
1274
|
| 缺省内存 store 不淘汰 | 长跑宿主请设 `InMemoryTaskStore({ maxRecords })` 或换 `FileTaskStore` / `SqliteTaskStore` |
|
|
1259
1275
|
| 能力引用两种粒度 | `tools` 写 **provider token** = 整片能力菜单;写 `'<token>/<能力名>'` = 只引单个能力(@Tool/@Skill/@SubAgent/@Prompt 都可点名,装配期校验,名字不存在即抛错并列出可用名单) |
|
|
1260
1276
|
| 能力名有格式校验 | 装饰器能力名(`name` 或缺省的方法名)必须匹配 `^[A-Za-z0-9_-]{1,64}$`(与 MCP 桥同口径),非法名在 `createApp` **装配期即抛错** —— 含空格/点/中文的名字会让模型 API 400,宁可在启动期拦住 |
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@migor/agentia": "^0.8.
|
|
12
|
+
"@migor/agentia": "^0.8.3"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
|
-
"@migor/cli": "^0.8.
|
|
15
|
+
"@migor/cli": "^0.8.3",
|
|
16
16
|
"tsx": "^4.19.0",
|
|
17
17
|
"typescript": "^7.0.2",
|
|
18
18
|
"@types/node": "^22.0.0"
|