@migor/agentia 0.10.4 → 0.10.6
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 +281 -1
- package/README.en.md +1 -1
- package/README.md +2 -2
- package/dist/AGENTS.md +93 -11
- package/dist/container/container.js +3 -0
- package/dist/container/container.js.map +1 -1
- package/dist/core/abort.js +6 -1
- package/dist/core/abort.js.map +1 -1
- package/dist/core/json.js +4 -0
- package/dist/core/json.js.map +1 -1
- package/dist/core/limits.d.ts +13 -5
- package/dist/core/limits.js +14 -5
- package/dist/core/limits.js.map +1 -1
- package/dist/core/sse.d.ts +0 -13
- package/dist/core/sse.js +28 -2
- package/dist/core/sse.js.map +1 -1
- package/dist/core/timeout.d.ts +8 -0
- package/dist/core/timeout.js +19 -1
- package/dist/core/timeout.js.map +1 -1
- package/dist/core/tool.d.ts +18 -0
- package/dist/core/trace.d.ts +9 -0
- package/dist/core/trace.js +21 -0
- package/dist/core/trace.js.map +1 -1
- package/dist/engine/forwarded.d.ts +3 -1
- package/dist/engine/forwarded.js +5 -0
- package/dist/engine/forwarded.js.map +1 -1
- package/dist/engine/loop.d.ts +5 -1
- package/dist/engine/loop.js +45 -12
- package/dist/engine/loop.js.map +1 -1
- package/dist/engine/mcp-server.d.ts +17 -1
- package/dist/engine/mcp-server.js +36 -2
- package/dist/engine/mcp-server.js.map +1 -1
- package/dist/engine/policy.js +5 -1
- package/dist/engine/policy.js.map +1 -1
- package/dist/engine/replay.js +63 -1
- package/dist/engine/replay.js.map +1 -1
- package/dist/engine/run-config.d.ts +6 -0
- package/dist/engine/run-config.js +18 -6
- package/dist/engine/run-config.js.map +1 -1
- package/dist/engine/spec.d.ts +11 -1
- package/dist/engine/spec.js.map +1 -1
- package/dist/engine/stop-reason.js +19 -1
- package/dist/engine/stop-reason.js.map +1 -1
- package/dist/engine/tool-context.d.ts +3 -1
- package/dist/engine/tool-context.js +3 -1
- package/dist/engine/tool-context.js.map +1 -1
- package/dist/engine/tool-events.d.ts +7 -2
- package/dist/engine/tool-events.js +24 -3
- package/dist/engine/tool-events.js.map +1 -1
- package/dist/engine/tracer.js +10 -3
- package/dist/engine/tracer.js.map +1 -1
- package/dist/engine/turn.d.ts +9 -13
- package/dist/engine/turn.js +96 -10
- package/dist/engine/turn.js.map +1 -1
- package/dist/engine/types.d.ts +19 -2
- package/dist/engine/types.js +1 -0
- package/dist/engine/types.js.map +1 -1
- package/dist/engine/usage.js +12 -4
- package/dist/engine/usage.js.map +1 -1
- package/dist/index.d.ts +11 -3
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/anthropic.d.ts +9 -1
- package/dist/integrations/anthropic.js +42 -7
- package/dist/integrations/anthropic.js.map +1 -1
- package/dist/integrations/file-sink.d.ts +1 -0
- package/dist/integrations/file-sink.js +35 -32
- package/dist/integrations/file-sink.js.map +1 -1
- package/dist/integrations/mcp-http.js +33 -9
- package/dist/integrations/mcp-http.js.map +1 -1
- package/dist/integrations/mcp-protocol.js +33 -16
- package/dist/integrations/mcp-protocol.js.map +1 -1
- package/dist/integrations/metrics-otlp.js +4 -0
- package/dist/integrations/metrics-otlp.js.map +1 -1
- package/dist/integrations/metrics.js +17 -2
- package/dist/integrations/metrics.js.map +1 -1
- package/dist/integrations/openai.d.ts +6 -1
- package/dist/integrations/openai.js +45 -8
- package/dist/integrations/openai.js.map +1 -1
- package/dist/integrations/otlp.js +6 -0
- package/dist/integrations/otlp.js.map +1 -1
- package/dist/runtime/memory.d.ts +0 -8
- package/dist/runtime/memory.js +24 -2
- package/dist/runtime/memory.js.map +1 -1
- package/dist/runtime/run.js +34 -9
- package/dist/runtime/run.js.map +1 -1
- package/dist/store/fsStore.d.ts +2 -0
- package/dist/store/fsStore.js +38 -5
- package/dist/store/fsStore.js.map +1 -1
- package/dist/store/record.d.ts +40 -0
- package/dist/store/record.js +72 -0
- package/dist/store/record.js.map +1 -1
- package/dist/store/redisStore.js +15 -4
- package/dist/store/redisStore.js.map +1 -1
- package/dist/store/sqliteStore.js +39 -14
- package/dist/store/sqliteStore.js.map +1 -1
- package/dist/store/store.d.ts +6 -1
- package/dist/store/store.js +10 -4
- package/dist/store/store.js.map +1 -1
- package/dist/toolkit/capability-loop.d.ts +92 -0
- package/dist/toolkit/capability-loop.js +81 -0
- package/dist/toolkit/capability-loop.js.map +1 -0
- package/dist/toolkit/manifest.d.ts +21 -0
- package/dist/toolkit/manifest.js +38 -0
- package/dist/toolkit/manifest.js.map +1 -0
- package/dist/toolkit/module.d.ts +5 -0
- package/dist/toolkit/module.js +9 -0
- package/dist/toolkit/module.js.map +1 -1
- package/dist/toolkit/prompt.d.ts +3 -1
- package/dist/toolkit/prompt.js +22 -3
- package/dist/toolkit/prompt.js.map +1 -1
- package/dist/toolkit/skill.d.ts +3 -1
- package/dist/toolkit/skill.js +2 -0
- package/dist/toolkit/skill.js.map +1 -1
- package/dist/toolkit/subagent.d.ts +3 -1
- package/dist/toolkit/subagent.js +2 -0
- package/dist/toolkit/subagent.js.map +1 -1
- package/dist/toolkit/tool.d.ts +3 -1
- package/dist/toolkit/tool.js +2 -0
- package/dist/toolkit/tool.js.map +1 -1
- package/dist/transport/approval-policy.d.ts +1 -1
- package/dist/transport/approval-policy.js +18 -3
- package/dist/transport/approval-policy.js.map +1 -1
- package/dist/transport/approval-supervisor.d.ts +15 -3
- package/dist/transport/approval-supervisor.js +37 -11
- package/dist/transport/approval-supervisor.js.map +1 -1
- package/dist/transport/async.d.ts +3 -4
- package/dist/transport/async.js +157 -30
- package/dist/transport/async.js.map +1 -1
- package/dist/transport/drain-gate.js +13 -1
- package/dist/transport/drain-gate.js.map +1 -1
- package/dist/transport/http-endpoints.d.ts +13 -0
- package/dist/transport/http-endpoints.js +108 -28
- package/dist/transport/http-endpoints.js.map +1 -1
- package/dist/transport/http-shapes.d.ts +26 -3
- package/dist/transport/http-shapes.js +12 -2
- package/dist/transport/http-shapes.js.map +1 -1
- package/dist/transport/http.d.ts +46 -2
- package/dist/transport/http.js +43 -18
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/resume-scanner.d.ts +3 -0
- package/dist/transport/resume-scanner.js +10 -1
- package/dist/transport/resume-scanner.js.map +1 -1
- package/dist/transport/run-call-options.js +46 -1
- package/dist/transport/run-call-options.js.map +1 -1
- package/dist/transport/run-options.d.ts +98 -0
- package/dist/transport/run-options.js +121 -0
- package/dist/transport/run-options.js.map +1 -0
- package/dist/transport/run-outcome.js +21 -1
- package/dist/transport/run-outcome.js.map +1 -1
- package/dist/transport/serve.js +10 -0
- package/dist/transport/serve.js.map +1 -1
- package/dist/transport/signal-supervisor.d.ts +3 -0
- package/dist/transport/signal-supervisor.js +5 -1
- package/dist/transport/signal-supervisor.js.map +1 -1
- package/dist/transport/stream-janitor.d.ts +45 -0
- package/dist/transport/stream-janitor.js +137 -0
- package/dist/transport/stream-janitor.js.map +1 -0
- package/dist/transport/task-wake-gate.d.ts +40 -0
- package/dist/transport/task-wake-gate.js +69 -0
- package/dist/transport/task-wake-gate.js.map +1 -0
- package/package.json +2 -2
package/dist/AGENTS.md
CHANGED
|
@@ -123,6 +123,7 @@ cd my-service && npm install
|
|
|
123
123
|
npm run dev # = agentia dev:本地 inspector 面板(输入 prompt / 选能力 / 选工作目录)
|
|
124
124
|
npx agentia g tool fetch-weather # 生成到 src/tools/fetch-weather/(skill/prompt/subagent 同理)
|
|
125
125
|
npx agentia doctor # 静态体检(未登记/悬空/命名/重复)
|
|
126
|
+
npx agentia manifest # 真实装配 → 能力契约 JSON(不调用模型)
|
|
126
127
|
npx agentia --version # CLI 版本(= -v)
|
|
127
128
|
```
|
|
128
129
|
|
|
@@ -326,6 +327,7 @@ run 在**文件末尾** —— 取最早那种写法在长跑 dev 环里等于
|
|
|
326
327
|
| `name` | 模型可见的工具名,缺省取方法名(建议 snake_case) |
|
|
327
328
|
| `strict` | 透传给 Anthropic 的 strict 模式(**框架不校验 schema 合规性**) |
|
|
328
329
|
| `approval` | `'required'` = 每次调用先挂起等人工审批(HITL,见 §6.6「人工审批」) |
|
|
330
|
+
| `metadata` | 可选 `{ owner?, version?, risk?: 'read'\|'write'\|'external', sideEffect?: 'none'\|'idempotent'\|'non-idempotent' }`;进能力 manifest,供 owner 分工与变更审查 |
|
|
329
331
|
|
|
330
332
|
### `@Skill(spec: SkillSpec)`
|
|
331
333
|
|
|
@@ -338,6 +340,7 @@ run 在**文件末尾** —— 取最早那种写法在长跑 dev 环里等于
|
|
|
338
340
|
| `maxTokens` | 同上 |
|
|
339
341
|
| `maxIterations` | 同上 |
|
|
340
342
|
| `tools` | `ctx.llm()` 可调工具:**provider token 列表**(复用该 provider 的能力菜单),或 `'<token>/<能力名>'` 能力级路径(只引菜单里的单个能力) |
|
|
343
|
+
| `metadata` | 同 `@Tool`:owner / 版本 / 风险 / 副作用幂等性,进能力 manifest |
|
|
341
344
|
|
|
342
345
|
技能方法体拿到的第二参是 `SkillContext`:`ctx.llm({ prompt })` 才会真正调模型(脚本式,调几次由你写死)。
|
|
343
346
|
|
|
@@ -354,6 +357,7 @@ run 在**文件末尾** —— 取最早那种写法在长跑 dev 环里等于
|
|
|
354
357
|
| `maxTokens` | 同上 |
|
|
355
358
|
| `maxIterations` | 同上 |
|
|
356
359
|
| `resultSchema` | 给出后子 agent 用隐藏 `submit_result` 提交结构化结果 |
|
|
360
|
+
| `metadata` | 同 `@Tool`:owner / 版本 / 风险 / 副作用幂等性,进能力 manifest |
|
|
357
361
|
|
|
358
362
|
子 agent 是**独立循环 + 裁剪上下文**:主 agent 的历史它看不到,只有最终文本(或 `{ report, result }`)以 tool_result 回流。
|
|
359
363
|
|
|
@@ -365,6 +369,7 @@ run 在**文件末尾** —— 取最早那种写法在长跑 dev 环里等于
|
|
|
365
369
|
| `name` | 缺省取方法名 |
|
|
366
370
|
| `schema` | 模板化入参 schema;缺省空对象(无参资产) |
|
|
367
371
|
| `version` | 资产版本号(git hash / `'v3'` 等);装配期随能力名收集,每次 run 落 run 根 span 的 `prompts.versions` attribute —— 见 §6「提示词版本化」。缺省(无版本)则该能力不进表 |
|
|
372
|
+
| `metadata` | 同 `@Tool`;`metadata.version` 缺省回落 `version`,两项都给时以 metadata 为准 |
|
|
368
373
|
|
|
369
374
|
`@Prompt` **只支持方法形态**(标准装饰器下字段拿不到值/类引用)。实例方法与静态方法都**沿继承链**收集(父类的 `@Prompt` 资产子类自动带上)。方法体内用 `asset(import.meta.url, './x.md')` 读同目录长文本。
|
|
370
375
|
|
|
@@ -401,6 +406,7 @@ run 在**文件末尾** —— 取最早那种写法在长跑 dev 环里等于
|
|
|
401
406
|
| `toolTimeoutMs` | 缺省单工具超时(毫秒);超时该条 tool_result 记 is_error,不杀 run |
|
|
402
407
|
| `maxToolConcurrency` | 缺省同回合并行工具上限;不设 = 不限(全并行) |
|
|
403
408
|
| `maxEventChars` | 缺省 trace 事件正文截断上限(可被单次 run 覆盖):数字 = 入参/出参统一用该上限,`false` = **不截断**(完整正文进 trace,面板里能展开看全文);不设 = 框架缺省 |
|
|
409
|
+
| `maxToolResultChars` | 缺省单条工具结果**回模型**的字符上限:非负安全整数;`false` / 不设 = 不截断,0 = 截到零长度并留 `…(+N)`。与 maxEventChars 正交,会透传子循环 |
|
|
404
410
|
| `traceLimits` | **记账的数量上限** `{ maxEvents? }`(可被单次 run 覆盖):整条 trace 的事件总数闸。超限即停止记账,并在 run 根留**两笔** `trace.truncated` —— **起点标记** `{limit}`(断点在这里)+ **收尾摘要** `{droppedEvents, limit}`(丢了多少),两笔**同时**进交付的 trace 与 `onTraceEvent` 增量流。缺口位置可预测(尾巴)且**有计数**。与管**长度**的 `maxEventChars` 正交(一个管「多长」、一个管「多少」);不设 = 不限(全量记账是本框架的承诺)。坏值(NaN / 负数 / 小数)在 run 入口抛 `TypeError` |
|
|
405
411
|
|
|
406
412
|
### `app.run(messages, opts?: RunAppOptions)`
|
|
@@ -433,12 +439,15 @@ run 在**文件末尾** —— 取最早那种写法在长跑 dev 环里等于
|
|
|
433
439
|
| `onUnpricedModel` | 单次覆盖未定价回调(**是函数,因此不在 transport 的 `RunInvocationOptions` 里** —— 异步宿主不会替你传) |
|
|
434
440
|
| `toolTimeoutMs` | 单个工具执行超时(毫秒);超时该条 tool_result 记 is_error,run 继续 |
|
|
435
441
|
| `maxToolConcurrency` | 同回合并行工具上限;缺省不限 |
|
|
436
|
-
| `maxEventChars` | trace 事件正文截断上限(字符):数字 = 入参/出参统一用该上限,`false` = **不截断**;缺省按类型收敛(入参/成功出参 2000、失败出参 1000)。**透传给子 agent/skill 的子循环** ——
|
|
442
|
+
| `maxEventChars` | trace 事件正文截断上限(字符):数字 = 入参/出参统一用该上限,`false` = **不截断**;缺省按类型收敛(入参/成功出参 2000、失败出参 1000)。**透传给子 agent/skill 的子循环** —— 同一棵调用树上口径一致。只影响**记账**;回模型上限另看 `maxToolResultChars` |
|
|
443
|
+
| `maxToolResultChars` | 单次覆盖工具结果回模型的字符上限;缺省 / false = 不截断,0 = 截到零长度 |
|
|
444
|
+
| `usageOffset` | 本段之前已落账的 `Usage`;通常只由 AsyncRunner 在挂起恢复时注入,让预算按整任务累计。HTTP 客户端不能自填 |
|
|
437
445
|
| `session` | 会话持久化 `{ store, id }`:run 前拼历史、成功收尾追加本轮(见 `SessionStore`) |
|
|
438
446
|
| `sessionId` | 会话**引用**(可序列化):**只被异步宿主消费** —— 「`sessionId` + runner 的 `sessionStore`」那条路(任务里只落 id,store 实例不可序列化;runner 没配 `sessionStore` 而任务带了它 ⇒ submit 当场报错)。⚠️ 直接 `app.run` 传它**不生效**(不建 `session.id`、不接历史),入口会 `console.warn` 一次指出改用上一行的 `session: { store, id }`;只要 run 根的 `session.id` 标记而不要历史,直连 `executeRun` / `runAgent` 的引擎级 `sessionId`(见 §6.4「提示词版本化与会话标记」) |
|
|
439
447
|
| `memory` | 跨 run 记忆 `{ store, keys }`:run 前水合进 blackboard(用户种子优先)、收尾写回;与 `session` 正交(见 `MemoryStore`) |
|
|
440
448
|
| `beforeFlush` | `(trace, result) => void \| Promise<void>`:**sinks 冲刷之前**的最后一笔(run 正常收尾后调一次,抛错被吞)。给「**拿到结果才判得出**的结论」用的缝 —— 典型是 `defineEval` 的 score:等 `app.run` 返回再 `attachScore`,`metricsSink` 早在导出那一刻聚完账,分数就永远进不了指标。读 trace 就够的判断不必用它,写进 sinks 里即可(见 §6 判官配方) |
|
|
441
449
|
| `approvals` | HITL 审批决定(`Record<tool_use_id, ApprovalDecision>`):恢复 `suspended` 的 run 时传入(异步宿主会自动带,见 §6.6「人工审批」);手工续跑「assistant 结尾带 tool_use」的消息历史时也可直接给 |
|
|
450
|
+
| `approvalEpoch` | 续跑段的挂起时刻(= `TaskRecord.suspendedSince`,异步宿主自动注入)。引擎只认 `requestedAt` 与它相等的决定 —— 防止旧挂起留下的同名 `tool_use_id`(OpenAI 兼容端点会复用 `call_1`)自动批准新一次调用;手工续跑省略 = 不做 epoch 过滤(同 run 内仍只消费一次) |
|
|
442
451
|
| `events` | run 事件(`TaskEvent[]`,`{ eventId?, type, payload }` 全是字符串):挂起期间投递、随 `TaskRecord` 落库的事件,续跑段由引擎在**未决 tool_use 解决之后**渲染成 user 文本消息注入消息流(异步宿主经 `signalTask` 自动带,见 §6.6「事件投递」);手工续跑时也可直接给 |
|
|
443
452
|
|
|
444
453
|
返回 `AgentRunOutput`:`{ run, result }`。`result` 含 `trace` / `stopReason` / `finalText` / `iterations` / `error` / `typed`;
|
|
@@ -557,12 +566,56 @@ run 内取上下文(黑板 / `@Skill` 第二参)、把装饰器装配成 app
|
|
|
557
566
|
| `model` | 本次技能的缺省模型 |
|
|
558
567
|
| `llm` | 受限子运行:`await ctx.llm({ prompt })` / `{ messages, system, model, maxTokens, maxIterations }` |
|
|
559
568
|
|
|
569
|
+
#### `runCapabilityLoop(ctx, opts)` —— 代码控制顺序的子循环(2026-11-04)
|
|
570
|
+
|
|
571
|
+
四类能力之外还有一格:**顺序由你的代码定、但活不过这次请求**。`@SubAgent` / `@Skill` 的流程由
|
|
572
|
+
模型或方法体驱动、子循环里的审批挂不起整条 run;`AsyncRunner` 的 task 能挂起能续跑,但要为它
|
|
573
|
+
造一条任务记录。中间这一格就是本原语(它把 `runAgentScoped` 包成「旋钮不可漏」的形状 ——
|
|
574
|
+
`ToolRunContext` 上那七个必须原样下交的键一次取自单源,使用方接触不到)。
|
|
575
|
+
|
|
576
|
+
| 选哪个 | 判据 |
|
|
577
|
+
|---|---|
|
|
578
|
+
| `@SubAgent` | 流程交给**模型**决定,子 agent 自己挑工具 |
|
|
579
|
+
| `@Skill` + `ctx.llm()` | 流程由方法体定,但**不要**结构化结果 / 裁剪 / 人批 |
|
|
580
|
+
| **`runCapabilityLoop`** | 流程由方法体定,且需要 `contextPolicy`(长跑不撞上限)/ `resultSchema` / 同一棵树 |
|
|
581
|
+
| `AsyncRunner.submit` | 这件事会**活过这次请求**(耐久、崩溃续跑、审批原生) |
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
@Tool({
|
|
585
|
+
name: 'approve_invoice',
|
|
586
|
+
description: '核对并批准一张发票(会改账,需人工批准)',
|
|
587
|
+
inputSchema: INVOICE_SCHEMA,
|
|
588
|
+
approval: 'required', // 审批落在**这个工具调用**上 ⇒ 整条 run 可挂起等人批
|
|
589
|
+
})
|
|
590
|
+
class InvoiceTool {
|
|
591
|
+
constructor(private readonly client: ModelClient) {}
|
|
592
|
+
|
|
593
|
+
async run(input: { id: string }, ctx: ToolRunContext) {
|
|
594
|
+
const ledger = await runCapabilityLoop(ctx, {
|
|
595
|
+
name: 'invoice-review', // trace 里那个 capability span 的名字
|
|
596
|
+
input: `核对发票 ${input.id}:先查合同,再比对金额`,
|
|
597
|
+
tools: [contractTool, ledgerTool], // 子循环自己的菜单(不继承主菜单)
|
|
598
|
+
contextPolicy: createBudgetPolicy({ maxTokens: 40_000 }), // 长跑子循环不撞上限
|
|
599
|
+
resultSchema: VERDICT_SCHEMA,
|
|
600
|
+
});
|
|
601
|
+
// 成败自己判(框架不替它抛):非正常收尾时 stopReason 会告诉你为什么
|
|
602
|
+
if (!isSuccessStopReason(ledger.stopReason)) throw new Error(`核对失败:${ledger.stopReason}`);
|
|
603
|
+
return ledger.typed;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
三条边界(都是刻意):**不自开 run 根**(`llm.turn` 挂在本条 run 的树里,`runId` 不会多一个);
|
|
609
|
+
**不继承主 run 的 `fallbacks` 链**(同 `runAgentScoped` 的有意边界);**挂起不穿透** ——
|
|
610
|
+
子循环里触发 `deferUntil` / 审批挂起时,语义同其它嵌套能力(以 `is_error` 交回,整条 run 不挂起)。
|
|
611
|
+
|
|
560
612
|
#### 装配与执行
|
|
561
613
|
|
|
562
614
|
| API | 说明 |
|
|
563
615
|
|---|---|
|
|
564
616
|
| `createApp` | 装配应用(同步;带 `discover` 时为 `Promise<AgentApp>`) |
|
|
565
|
-
| `AgentApp` | `run` / `tools` / `container` / `name` |
|
|
617
|
+
| `AgentApp` | `run` / `tools` / `container` / `name` / `getCapabilityManifest()` |
|
|
618
|
+
| `buildCapabilityManifest` | 从最终菜单生成 `{ app, hash, capabilities }`;hash 覆盖描述/schema/审批/元数据且与顺序无关,适合 PR 留档比较 |
|
|
566
619
|
| `defineModule` | 定义能力包 |
|
|
567
620
|
| `executeRun` | 低层 run 入口(自己管 `Run`/trace sink/记忆) |
|
|
568
621
|
| `runAgent` | 更底层:只要一个循环 + trace,不管 run 生命周期 |
|
|
@@ -621,6 +674,7 @@ const app = await createApp({ ... });
|
|
|
621
674
|
| `FileTaskStore` | JSONL 耐久存储(`compact()` 可压实日志) |
|
|
622
675
|
| `SqliteTaskStore` | `node:sqlite` 耐久存储(WAL + busy_timeout) |
|
|
623
676
|
| `RedisTaskStore` | duck-typed Redis 存储(可设 `ttlSeconds`);客户端结构面 `get` / `set` / `del` / `keys`(或 `scanIterator`),外加设 TTL 时必需的 `expire`。`set` **只传两参** —— 尾参的选项形状两家相反:ioredis 认位置参数 `('EX', n)`、node-redis 认对象 `{ EX: n }`,取任何一种都会在另一家上失效(ioredis 会把对象字符串化成 `"[object Object]"` 报语法错;**node-redis 的 `SET` 只声明三个形参,位置参数被静默丢弃**)。所以 TTL 一律走 `expire(key, seconds)`(两家同名同形);设了 `ttlSeconds > 0` 却没给 `expire` 时**构造期抛错**,不静默丢掉 TTL |
|
|
677
|
+
| `parseTaskRecord` | 自定义 store 的公共读回入口:JSON 解析 + 形状守卫 + 旧版本归一;坏 JSON 抛错,非记录返回 undefined |
|
|
624
678
|
|
|
625
679
|
**`compact()` / `close()` 是接口上的可选能力,但由宿主按需调**:`FileTaskStore.compact()`
|
|
626
680
|
(压实 JSONL:一 task 一行、丢掉历史覆写行)与 `SqliteTaskStore.close()` 此前只活在具体类上 ——
|
|
@@ -713,7 +767,7 @@ npm run client # 另一个终端:把四个 RPC 跑一遍
|
|
|
713
767
|
| 端点 | 请求 | 响应 |
|
|
714
768
|
|---|---|---|
|
|
715
769
|
| `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 错误码) |
|
|
716
|
-
| `POST /tasks` | `{ input, idempotencyKey?, options? }` —— `input` 同 `RunInput`;`options`
|
|
770
|
+
| `POST /tasks` | `{ input, idempotencyKey?, options? }` —— `input` 同 `RunInput`;`options` 只接受**白名单**:`traceContext` / `maxCostUsd` / `maxTotalTokens`(多一个键 → 400,含 `blackboard` / `tools` / `model`)。**身份与租户不走 body** —— 由宿主的 `resolveRunOptions` 按请求注入 | 202 `TaskRecord`(`status: 'queued'`);同 `idempotencyKey` 未失败则去重、直接返回既有记录(**同步 store** 当场判定;**异步 store** 下只保证**同进程内并发提交**不重复执行,跨进程与终态后重提仍是 at-least-once —— 见 §7「同键去重的能力边界」) |
|
|
717
771
|
| `GET /tasks/:id` | — | 200 `TaskRecord`;不存在 → 404。**停机中仍可轮询**(否则拿不到在飞任务的结果) |
|
|
718
772
|
| `GET /tasks/:id/stream` | — | **任务进度流(SSE)**:先在 `id:` 里给流序号,逐帧下发 `trace.event`(body 即 `TraceRecordEvent`),终态发 `task.end` 并关闭。断线重连带 `Last-Event-ID`(或 `?from=<序号>`)即可续订 —— 只补该序号之后的事件。缓冲超限先发一帧 `stream.truncated{droppedBefore}`;别的进程在跑的任务发 `stream.unavailable` 后收口(**不假装实时**)。任务不存在 → 404;方法不对 → 405。⚠️ 它的读者是**旁观者**:背压/断开只收口这条流,**不中止任务** |
|
|
719
773
|
| `POST /tasks/:id/approve` | `{ decisions: { <tool_use_id>: { approved, reason? } }, decidedBy? }` | 200 `TaskRecord`(HITL 审批:批准/拒绝挂起任务,见 §6.6「人工审批」);任务不存在 → 404;**挂起原因不是 `approval`**(含未挂起)→ 409;`decisions` 里有不在该任务 `pendingApprovals` 里的 id → **400(整批拒,记录一字不动)**;body 非法 → 400。**停机中仍可审批**(与 GET 轮询同理由) |
|
|
@@ -725,6 +779,10 @@ npm run client # 另一个终端:把四个 RPC 跑一遍
|
|
|
725
779
|
方法不符 → 405(带 `Allow` 头);路径不符 → 404;body 非法 JSON → 400;body 超 `maxBodyBytes` → 413;
|
|
726
780
|
`POST /run` 超 `maxConcurrentRuns` → 503 + `Retry-After`;停机中 `POST /run`、`POST /tasks` → 503。
|
|
727
781
|
|
|
782
|
+
上表是缺省 `responseMode:'full'` 的兼容形状。对外多租户服务建议 `responseMode:'public'`:
|
|
783
|
+
run/task 响应不再携带 trace、spec.messages、suspendedMessages 与 ownerId,SSE 的 trace 帧也默认关;
|
|
784
|
+
它不改变任务库和 sinks 的完整记录。对象级授权由 `authorizeTask` 另行决定,两者不能互相替代。
|
|
785
|
+
|
|
728
786
|
#### 本地怎么调(可直接复制的命令)
|
|
729
787
|
|
|
730
788
|
⚠️ 先认下这条,否则会照着别的框架的经验找错东西:**没有「一个业务一个 URL」的接口层**。
|
|
@@ -774,7 +832,12 @@ const handler = createHttpHandler(callable, { runner });
|
|
|
774
832
|
|
|
775
833
|
| 选项 | 说明 |
|
|
776
834
|
|---|---|
|
|
777
|
-
| `authenticate` | 入口鉴权钩子:**除 `/healthz` 与 `/metrics` 外所有路径**都过它,且在**读 body 之前**(未通过就不收 body
|
|
835
|
+
| `authenticate` | 入口鉴权钩子:**除 `/healthz` 与 `/metrics` 外所有路径**都过它,且在**读 body 之前**(未通过就不收 body)。正常返回即通过,**返回值作为 `principal` 交给 `resolveRunOptions`**;抛 `HttpException` 按其 `status`/`body` 回;抛别的错误回 401,原文只进服务端日志。框架**不实现策略**(不读 env、不碰凭据) |
|
|
836
|
+
| `resolveRunOptions` | `(req, { route, principal }) => { blackboard?, labels?, maxCostUsd?, maxTotalTokens? }` —— **身份 / 租户 / 配额封顶的唯一可信通道**(`/run` 与 `/tasks` 都生效)。`blackboard` **整体替换**、`labels` 按键盘合并(服务端胜)、预算与客户端值取**更严者**(上限不能由叫价方抬高)。抛 `HttpException` 按其状态码回。为什么必须有它:`POST /tasks` 的 `body.options` 走白名单之后,客户端已经**设不了** `blackboard` —— 身份不从这里进就没有地方进了(见 §6.6「多租户配额」) |
|
|
837
|
+
| `authorizeTask` | `(req, { action, taskId, task, principal })`:任务控制面的**对象级授权**,action 为 `poll/approve/cancel/event/stream`;返回 false → 403,抛 HttpException 按状态码回。审批/事件在读 body 之前判;缺省不拦(旧行为) |
|
|
838
|
+
| `resolveApprovalActor` | `(req, principal) => string\|undefined`:从认证结果生成可信审批人;配置后**覆盖**审批 body 的自报 `decidedBy`。空字符串按宿主配置错误 500 |
|
|
839
|
+
| `responseMode` | `'full'`(缺省,旧形状)/ `'public'`:公共模式去掉 run/task 响应的 trace、任务 spec、挂起历史与 ownerId;TaskStore/sinks 仍保留完整记录 |
|
|
840
|
+
| `exposeTraceEvents` | 是否开放 `/run` 与 `/tasks/:id/stream` 的 trace 事件;缺省 = `responseMode==='full'`。公共模式下显式 true 可单独开放,但不会把完整 trace 加回 run.end |
|
|
778
841
|
| `metrics` | 指标出口:给 `metricsSink()`(或任意 `{ render() }` / 返回字符串的闭包)后,`GET /metrics` 回它的 Prometheus 文本。**不鉴权**(拉取端在集群内网);要保护请放反代后面。不给则该路径 404。⚠️ 这只管**渲染** —— 数字要真的累计,必须把**同一个** sink 注册进 `createApp({ sinks: [metrics] })`(它靠 run 收尾投递,不自己埋点),否则 `/metrics` 恒为 0 **且不报错** |
|
|
779
842
|
| `maxBodyBytes` | 请求 body 上限(字节),超限回 413;缺省 1 MiB。构造期校验:必须 > 0 或 `Infinity` —— 0 会让**每个带 body 的请求**都 413(`POST /run` / `POST /tasks` / 审批全废),故直接抛错。⚠️ 还挡 `Number('') === 0`:从**空的环境变量**读出来的配置会静默变成「拒绝一切」 |
|
|
780
843
|
| `maxConcurrentRuns` | 同时在跑的 `POST /run` 上限,超限回 503 + `Retry-After`;缺省 32(传 `Infinity` 恢复无上限)。构造期校验:必须 > 0 或 Infinity —— NaN 会让闸门静默失效、0/负数会全部 503,故直接抛错 |
|
|
@@ -809,6 +872,13 @@ const handler = createHttpHandler(app, {
|
|
|
809
872
|
// 只给缝:token/JWT/签名策略由你的宿主或反代实现
|
|
810
873
|
authenticate: (req) => {
|
|
811
874
|
if (req.headers['x-api-key'] !== expected) throw new HttpException(401, { error: '无效凭据' });
|
|
875
|
+
// 返回值不是白抛的:它就是 principal,下一步交给 resolveRunOptions
|
|
876
|
+
return { userId: String(req.headers['x-user-id'] ?? '') };
|
|
877
|
+
},
|
|
878
|
+
// 身份 / 租户进 run 的唯一可信通道(客户端在 body 里填不了黑板)
|
|
879
|
+
resolveRunOptions: (_req, { principal }) => {
|
|
880
|
+
const { userId } = principal as { userId: string };
|
|
881
|
+
return { blackboard: { tenant: tenantOf(userId) }, labels: { user: userId } };
|
|
812
882
|
},
|
|
813
883
|
});
|
|
814
884
|
|
|
@@ -949,7 +1019,7 @@ trace 出去之后能干什么:指标、调用树面板、调优报告、生
|
|
|
949
1019
|
| `TraceRecorder` | 内存 recorder(一次 run 一个);`addLink(spanId, { traceId, spanId? })` 记一条跨 trace 链路(见 §6「跨进程关联」) |
|
|
950
1020
|
| `parseTraceparent` | 解析 W3C `traceparent` 头 → `{ traceId, spanId? }`;**非法 / 缺头一律返回 `undefined`**(不抛、不打 400)—— 结果直接交给 `traceContext` 选项,见 §6「跨进程关联」 |
|
|
951
1021
|
| `createOtlpExporter` | OTLP/JSON 导出,零依赖;选项见下面「`OtlpExporterOptions`」表 |
|
|
952
|
-
| `jsonlTraceSink` | JSONL 文件 sink:`jsonlTraceSink({ path })` → `TraceSink
|
|
1022
|
+
| `jsonlTraceSink` | JSONL 文件 sink:`jsonlTraceSink({ path, fsync? })` → `TraceSink`(`fsync: true` 时每次追加后同步落盘;缺省 false 保持旧性能),一行一条裸 Trace 追加落盘(父目录自动创建、追加不覆盖)—— **`agentia report` / `diff` / `harvest` / `export` / `view` 的输入格式**,见 §6「调优报告」。⚠️ **同一路径进程内幂等**(两次调用返回同一个 sink):两条 sink 对口一个文件会让一次 run 落两行同 traceId,而下面的命令按「一行 = 一次 run」计数 ⇒ 一次 run 被数两次(不报错,比不落盘更难发现)。所以「应用级 `sinks` 与 `registerDefaultTraceSink` 都往 `.agentia/traces.jsonl` 写」是安全的 |
|
|
953
1023
|
| `TraceRecordEvent` | **增量记账事件**(`onTraceEvent` 的回调参数):`span.begin` / `span.end` / `span.event` / `span.attribute` / `span.link`,每条带单调 `seq`。载荷是**增量 + 此刻的拷贝**(`span.begin` 只给初始形状,属性/事件/链路各走自己的类型);按 `seq` 升序折回必须**逐字等于**收尾时的 trace |
|
|
954
1024
|
| `metricsSink` | 指标累加器(Prometheus 文本 / OTLP metrics),满足 `TraceSink` 即接入 —— 见 §6「指标」 |
|
|
955
1025
|
| `buildRunReport` | 从一条 trace 生成**调优报告**(能力/模型的耗时、token、成本、错误率排行)—— 见 §6「调优报告」 |
|
|
@@ -1451,6 +1521,7 @@ server —— Claude Code / Cursor / 任何 MCP 宿主都能直接调你的 `@To
|
|
|
1451
1521
|
- **结果映射与正向桥方向对称**:工具返回 string → `content: [{ type: 'text', text }]`,其他 JSON 化进 text;抛错 → 协议层成功 + `isError: true`(MCP 惯例;正向桥的连接器正好把 `isError` 转回抛错)。
|
|
1452
1522
|
- **取消是协作式**:客户端断连 / `close()` 会中止该次调用的 `ToolRunContext.signal`;`toolTimeoutMs` 与引擎同款语义(非正数 / 不设 = 不限;超时 = **放弃等待** + abort `abandoned`,该次调用回 `isError`,server 不挂)。
|
|
1453
1523
|
- **默认 client 是惰性构造的**:`client` 不给时第一次 `tools/call` 才走 `createAnthropicClient()`(与引擎默认同款)—— 纯工具 server 不需要 API key 在场。
|
|
1524
|
+
- **`approval: 'required'` 的工具默认不暴露**(2026-11-07):MCP 没有 HITL 挂起通道,反向桥直接 `tool.run` —— 暴露出去等于绕过人工审批。要暴露请显式设 `exposeApprovalRequiredTools: true`,并理解「MCP 客户端即审批人」;预算类旋钮(`maxTotalTokens` / `maxCostUsd` / `priceOverrides` / `maxToolResultChars` / `traceContent`)会透传给 `ToolRunContext`,否则子 agent 类工具在桥内不受预算约束。
|
|
1454
1525
|
- **想看一个真用它干活的例子**:`examples/observability-mcp/` 把 `buildRunReport` / `diffTraces` 包成两个只读工具(`report_trace` / `diff_traces`)暴露给任意 MCP 宿主,让 agent 自己查「我上一轮慢在哪」;还带一条**实边界**(工具参数由模型给 ⇒ 只读 trace 根内的文件,realpath 比前缀),以及一条端到端的验收脚本(起真 server ⇐ 出厂连接器 + `mcpTools`)。
|
|
1455
1526
|
|
|
1456
1527
|
```ts
|
|
@@ -1486,6 +1557,12 @@ console.log(mcp.url); // http://127.0.0.1:8787/mcp
|
|
|
1486
1557
|
| `traceLimits` | 每次 `tools/call` 那棵 trace 的事件数闸(与引擎 `traceLimits` 同款:超限停记 + run 根留 `trace.truncated` 簿记;缺省 = 不限;坏值构造期抛 TypeError) |
|
|
1487
1558
|
| `maxEventChars` | 事件正文截断上限(与引擎 `maxEventChars` 同款;`false` = 不截断;缺省 = 引擎缺省) |
|
|
1488
1559
|
| `toolTimeoutMs` | 单次 `tools/call` 的工具执行超时(毫秒),语义与引擎 `toolTimeoutMs` 同款;非正数 / 不设 = 不限 |
|
|
1560
|
+
| `exposeApprovalRequiredTools` | 是否把 `approval: 'required'` 的工具也暴露出去;**缺省 false**(MCP 没有 HITL 挂起通道,暴露 = 绕过人工审批)。显式打开即表示「MCP 客户端就是审批人」 |
|
|
1561
|
+
| `maxTotalTokens` | 单次 `tools/call` 的累计 token 上限(透传 `ToolRunContext.maxTotalTokens`) |
|
|
1562
|
+
| `maxCostUsd` | 单次 `tools/call` 的累计成本上限(透传 `ToolRunContext.maxCostUsd`) |
|
|
1563
|
+
| `priceOverrides` | 价格覆盖表(透传 `ToolRunContext.priceOverrides`,否则子循环里的自定义模型算不出成本) |
|
|
1564
|
+
| `maxToolResultChars` | 单条工具结果回模型的字符上限(透传 `ToolRunContext.maxToolResultChars`;`false` = 不截断) |
|
|
1565
|
+
| `traceContent` | assistant 文本记录口径(透传 `ToolRunContext.traceContent`;`'full'` = 记 `output.text`) |
|
|
1489
1566
|
| `auth` | http 侧鉴权钩子:在读 body 之前调用,抛错即 401(原文只进服务端日志);stdio 侧信任父进程、本项不生效 |
|
|
1490
1567
|
| `name` | `serverInfo.name`(握手回给客户端),缺省 `'agentia'` |
|
|
1491
1568
|
|
|
@@ -1679,6 +1756,9 @@ createApp({ system, providers: [...], middleware: [quota], sinks: [billing] });
|
|
|
1679
1756
|
|
|
1680
1757
|
- 拦下来的那次 run **仍然要记账**(模型的钱已经花了)—— 记账在 sink 里、拦截在 middleware 里,两者独立。
|
|
1681
1758
|
- 被拦下的能力**不会执行**(副作用不发生),但 run 继续跑(模型可以换路)。
|
|
1759
|
+
- ⚠️ **`tenant` 从哪来**(2026-11-04 起):只能由宿主注入 —— `createHttpHandler({ authenticate, resolveRunOptions })` 里
|
|
1760
|
+
`resolveRunOptions` 返回 `{ blackboard: { tenant } }`。客户端在 `POST /tasks` 的 `options` 里**填不了黑板**
|
|
1761
|
+
(白名单外即 400):这条配方此前照着抄会被绕过 —— 谁能调 `/tasks` 谁就能自填 `tenant`,额度记到别人头上。
|
|
1682
1762
|
|
|
1683
1763
|
#### 人工审批
|
|
1684
1764
|
|
|
@@ -1733,7 +1813,9 @@ class DeployTools {
|
|
|
1733
1813
|
`{ decisions: { '<tool_use_id>': { approved: true } }, decidedBy: 'alice' }`
|
|
1734
1814
|
(或直接 `runner.approve(taskId, decisions, { decidedBy })`)。**逐 tool_use_id 幂等**
|
|
1735
1815
|
(第一次决定赢,重复提交不推翻)。决定**随任务落库**(`TaskRecord.approvals`,
|
|
1736
|
-
|
|
1816
|
+
进程重启不丢);待决集合齐了任务自动恢复执行。生产 HTTP 端建议同时配置
|
|
1817
|
+
`authorizeTask`(谁能批这个任务)与 `resolveApprovalActor`(从 principal 生成 decidedBy,
|
|
1818
|
+
覆盖请求体自报身份);否则 `decidedBy` 只是调用方填写的审计字段,不是可信身份。
|
|
1737
1819
|
4. 恢复时:**批准**的工具正常执行(工具体内经 `ToolRunContext.approval` 读到自己的决定 —
|
|
1738
1820
|
谁批的、什么时候、什么理由);**拒绝**的工具得到 `tool_result(is_error: true,
|
|
1739
1821
|
content: '审批被拒绝:…')` —— 理由回给模型,可自行换路。恢复后再遇未决审批 ⇒
|
|
@@ -1901,7 +1983,7 @@ const callable = {
|
|
|
1901
1983
|
| 任务进度流的边界(内存 / 跨进程) | `GET /tasks/:id/stream` 的事件缓冲在**跑任务的进程内存**里:每任务最近 500 条(可用 `AsyncRunner` 的 `streamBufferEvents` 调,须为正整数,坏值构造期抛错),超限丢**最旧**并先发一帧 `stream.truncated`;终态流只留最近 16 条。**没跑完**的流另有一道**缓冲配额**(`streamNonTerminalBuffers`,缺省 32 条流;`0` = 不留):超出即从最旧、**没有订阅者**的流开始回收**回放历史** —— 表项与序号都留着,连上来的读者先收一帧 `stream.truncated` 再转实时(**不会**变成 `stream.unavailable`,那是对别的进程说的话);正在被订阅的流不受配额约束(不抽走正在读的数据)。**跨进程**(队列消费者在别的进程)时没有实时流:发一帧 `stream.unavailable` 后**立即收口** —— 任务已终态则补 `task.end`(随后关连接);**非终态**则补一帧 `stream.closed`(流级收尾,不是伪造终态)并关连接,客户端此后应转去轮询 `GET /tasks/:id`。要跨进程实时请用 `onTraceEvent` 把事件转发到宿主自己的总线(Redis Streams / Kafka) |
|
|
1902
1984
|
| 异步任务的**排队段**边界(内存) | `concurrency` 管「同时在跑几个」,**不管**「跑不上、排在后面」那一段 —— 缺省**排队段无上限**:`submit` 总是收单、`POST /tasks` 总是 202,灌多少就在内存里排多少(每条 = 一条记录 + 一棵悬挂的 `#execute` promise + 一个槽位等待者)。要背压就配 `AsyncRunner({ maxQueued })`:超限 `submit` 抛 `TaskQueueFullError`、HTTP 回 **503 + `Retry-After`**(**只在 `submit` 判** —— 恢复路径推进的是已受理任务,拦下来等于把它搁死在 store 里;幂等键命中的重复提交也不吃 503)。`runner.queued` 是实时读数(与闸同一判据)。⚠️ 进程内拒单**不等于**集群级背压:多副本各算各的,要全局就得上队列 |
|
|
1903
1985
|
| `traceLimits` 与 `maxEventChars` 各管一头 | `maxEventChars` 管**单个事件正文多长**(既有),`traceLimits.maxEvents` 管**整条 trace 记多少条记录事件**(本版)。两者正交、都「不设 = 不限」;超限即**停止记账**,并在 run 根留**两笔** `trace.truncated`:**起点标记** `{limit}`(断点在这里)+ **收尾摘要** `{droppedEvents, limit}`(丢了多少)—— 两笔**同时**进交付的 trace 与 `onTraceEvent` 增量流(⇒ 按 `seq` 折回仍逐字等于收尾那份;两笔簿记**不占** `maxEvents` 配额)。⚠️ 实测 `maxEventChars: false` + 大出参会让 trace 放大 **13.7×**(`npm run bench:trace` 可复现)—— 先收长度再谈采样,收益顺序比反过来大 |
|
|
1904
|
-
|
|
|
1986
|
+
| 工具结果缺省不截断;上限要显式开 | `maxEventChars` / `traceContent` 管**记账侧**;`maxToolResultChars` 管回模型的单条结果,缺省/false = 不截断,给非负整数即截并附 `…(+N)`。它按字符而非 token/业务行数计费;要语义化摘要仍在工具内做 |
|
|
1905
1987
|
| **采样是导出决策,不是记账决策** | 采样在 `sink` 外做(配方见 `docs/observability.md` 2.3):被采样掉的 trace 在框架内**仍然完整记账**,只是没发给下游。所以别拿「有采样」当「可以少记账」;也正因如此,出站 `traceparent` 的 flags 恒 `00`(记录/导出决策发生在收尾之后,运行期不可知——不替下游声明) |
|
|
1906
1988
|
| 菜单漂移只**标记**、不判失败 | 续跑时未决 tool_use 的工具不在当前菜单 ⇒ 三处信号(`menu.drift` 事件 / 父 span attribute / `console.warn`),run **照常收尾**(那些 tool_use 以 `unknown tool` 回给模型)。**射程如实标注**:判据只看**名字** —— 「工具还在、schema 变了」能被 `tools.menuHash` 比出来,但**不会**在续跑时报 `menu.drift`;**只改 `description`** 连签名都不变(有意:润色文案不算漂移)。严格模式(带具名 error 的收尾)与「结果级字段」记在 spec §11 开放项 |
|
|
1907
1989
|
| `approve` 只收**待决的** id(多一个 → 400 整批拒) | `decisions` 的键必须都在该任务的 `pendingApprovals` 里(调用方从记录的 `pendingApprovals` 读)。多出任何一个 ⇒ **400 + 整批拒 + 记录一字不动**。不校验的代价(已修,2026-09-28 外部深评):多出来的键会写进 `TaskRecord.approvals`,随每次 `save` 全文重写、且终态也不清 ⇒ 一个**认证调用方**单次请求就能把记录撑大(实证:一次 5000 个无关键 = +419 KB),反复调用可无限叠加 |
|
|
@@ -1969,15 +2051,15 @@ const callable = {
|
|
|
1969
2051
|
| 审批超时是**惰性**判定 | `approvalTimeoutMs` 不起定时器:`approve` / `poll` / `resumePending` 读到过期挂起任务时才自动全拒并重派 —— 没人读的任务不会自己超时(要定期扫就调 `resumePending()`) |
|
|
1970
2052
|
| 挂起段与恢复段是两棵 trace | 每段执行一棵独立的树(`traceId == runId`),恢复段经根 span 的 `links` 挂到上一段;指标按段计(挂起段算一次 ok 的 run —— 「等人」不算失败,区分看 `stop_reason` attribute) |
|
|
1971
2053
|
| 审批是 at-least-once | 崩溃发生在「批准后、恢复执行中」时,副作用工具会重执行(与 `resumePending` 续跑同口径)—— 副作用工具自己保证幂等 |
|
|
1972
|
-
|
|
|
1973
|
-
|
|
|
1974
|
-
| 嵌套能力内的审批不支持挂起 | @SubAgent / @Skill 子循环里的 `approval: 'required'` 工具无法把整个 run 挂起 —— 子循环挂起会以 `is_error` 交回主 agent
|
|
2054
|
+
| 恢复预算按任务累计,但崩溃未落账段不可追回 | 每个已收尾段折进 `TaskRecord.usageTotal`;恢复段经 `usageOffset` 参与 maxTotalTokens/maxCostUsd,trace 仍每段一棵。进程崩在「模型已花、结果未落库」窗口时那笔无从恢复,不能把它当全局精确账单 |
|
|
2055
|
+
| 恢复段的会话输入随任务落库 | 首个挂起段把本轮输入写进 `TaskRecord.sessionInput`,恢复成功后由 AsyncRunner 补写并终态清除;挂起期间重启不再丢这一轮。旧版本留下的挂起记录没有该字段,仍只能跳过补写 |
|
|
2056
|
+
| 嵌套能力内的审批不支持挂起 | @SubAgent / @Skill 子循环里的 `approval: 'required'` 工具无法把整个 run 挂起 —— 子循环挂起会以 `is_error` 交回主 agent(要审批的能力请放主菜单)。要「代码定顺序 + 人批」两样都要:把 `runCapabilityLoop` 放进一个 `@Tool({ approval: 'required' })` —— 审批落在**外层工具调用**上,于是照样能挂起等人批 |
|
|
1975
2057
|
| 同步 `/run` 撞上审批没人可批 | 同步 RPC 会带着 `stopReason: 'suspended'` 收尾返回 —— 但响应体(`toHttpBody`)**不含** `suspendedMessages`,也没有任务记录可审批(待决清单只能去 trace 的 `approval.requested` 事件里看)。**要审批请走 `POST /tasks` 异步宿主** |
|
|
1976
2058
|
| Scheduler 调度表不落库 | `every` / `at` 的调度本身只在内存:已 submit 的任务记录能经 `resumePending` 续跑,但「未来某刻再触发」的调度在重启后不存在(远期单发由宿主自己的 cron 驱动)。另:`drain()` 不停 Scheduler —— 停机窗口内到点的 tick 会打一条触发失败日志(无害但吵),介意就 `scheduler.stop()` 先行 |
|
|
1977
2059
|
| file store 的撕裂写只在启动时自愈 | 写入中途失败(磁盘满等)留下的残行由 `healTail` 在**构造期**修复;同进程内继续 append 会把新记录粘在残行尾部、下次启动时一起丢弃 —— 磁盘满告警后先恢复写入能力再继续依赖它 |
|
|
1978
2060
|
| 终态落库失败 ⇒ 重启会重跑 | AsyncRunner 终态 `save` 失败**不遮罩主流程**(「不击穿业务」的代价):store 抖动时任务可能永远停在 `running`,重启后 `resumePending` 会重跑一个**实际已成功**(副作用已发生)的任务 —— 所以副作用工具必须自身幂等。**但失败本身不再静默**:`new AsyncRunner(app, { onPersistError })` 会收到 `{ record, error, phase }`(`phase: 'initial' \| 'outcome'`,后者就是这条)。⚠️ 框架**修不了**它(写不进去就是写不进去)—— 出口的职责是让你能对账、让「记录无声丢失」不再是默认行为(与 `createOtlpExporter({ onExportError })` 同因同形) |
|
|
1979
2061
|
| 缺省**不裁剪**上下文(长跑每回合重发全量历史 ⇒ 成本随回合数平方增长) | `contextPolicy` 不注入就一个回合都不裁:每回合把**整段历史**重发给模型,于是回合数翻倍、总输入约翻两番。框架给了缝与一份参考实现(`createBudgetPolicy`),但**不替你决定** —— 裁剪有损(截在哪、留什么会影响答案),是宿主的策略。长任务(几十回合以上)请在 `createApp` 里显式注入 `contextPolicy` |
|
|
1980
|
-
| `contextPolicy` 不进子循环 | 应用级 `contextPolicy` / `onText` 只对主循环生效:@SubAgent / @Skill 的子运行不做上下文裁剪(长跑子 agent 撞上下文上限会以 api 错误收尾)。预算护栏(`maxTotalTokens` / `maxCostUsd
|
|
2062
|
+
| `contextPolicy` 不进子循环 | 应用级 `contextPolicy` / `onText` 只对主循环生效:@SubAgent / @Skill 的子运行不做上下文裁剪(长跑子 agent 撞上下文上限会以 api 错误收尾)。预算护栏(`maxTotalTokens` / `maxCostUsd`)正常透传。要裁剪就显式给:`runCapabilityLoop(ctx, { contextPolicy })`(不继承应用级 —— 子循环的策略是调用点的决策) |
|
|
1981
2063
|
| 记忆没有删除语义 | `MemoryStore` 只有 load/save:run 内 `ctx.delete` 掉的键回写时不会从 store 移除(下一轮水合会复活)。要真删请直接操作 store 实现 |
|
|
1982
2064
|
| 同 keys 的并发回写不合并 | CAS(`loadWithRev` + `saveIfRev`)只做到「冲突时**不写 + 出声**」:框架**不合并、不重试**(合并语义是 store 与宿主的决定,配方见 §6.5)。只有 `load` / `save` 的 store 连冲突都**看不见** —— 版本号是唯一依据,没有它就检测不了覆盖;`InMemoryMemoryStore` 的版本粒度是**整店一个计数**(宁可多报冲突,不漏报真实覆盖) |
|
|
1983
2065
|
| 内容护栏不给实现 | 同「配额」:只给缝(入参包 `app.run` / 工具前 `middleware` / 出参包返回值或 `sinks`),策略(正则 / 分类器 / 外部 API)是你的 |
|
|
@@ -103,6 +103,9 @@ export class Container {
|
|
|
103
103
|
// `container: []`,不许引内部任何模块),为几行逻辑破叶子边界不值得。
|
|
104
104
|
// 逃逸口:确实要注入 Promise **本体**,包一层即可(`useValue: { promise }`)。
|
|
105
105
|
if (value instanceof Promise) {
|
|
106
|
+
// 抛类型错前先消费 rejection(2026-11-07 深评):一个会 reject 的 async 工厂此前会
|
|
107
|
+
// 留下 unhandledRejection,Node ≥15 默认终止宿主进程 —— 装配错误已经报了,不该再死一次。
|
|
108
|
+
value.catch(() => undefined);
|
|
106
109
|
throw new TypeError(`provider "${token}" 解析出的是 Promise:容器同步解析,它会被原样缓存成「值」,` +
|
|
107
110
|
'下游拿到 Promise 而非 await 过的结果(首次属性访问全 undefined、且零报错)。' +
|
|
108
111
|
'异步初始化请在装配前 await 好再用 useValue 注入;确实要注入 Promise 本体,包一层:useValue: { promise }');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/container/container.ts"],"names":[],"mappings":"AAoCA,MAAM,UAAU,UAAU,CAAC,CAAU;IACnC,OAAO,CACL,OAAO,CAAC,KAAK,QAAQ;QACrB,CAAC,KAAK,IAAI;QACV,OAAQ,CAA2B,CAAC,OAAO,KAAK,QAAQ;QACxD,CAAC,UAAU,IAAK,CAAY,IAAI,UAAU,IAAK,CAAY,IAAI,YAAY,IAAK,CAAY,CAAC,CAC9F,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,SAAS;IACH,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;IACtC,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;IACnD,6BAA6B;IACrB,SAAS,GAAY,EAAE,CAAC;IAEhC;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,SAAqB;QAC/B,KAAK,MAAM,CAAC,IAAI,SAAS,EAAE,CAAC;YAC1B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnB,MAAM,IAAI,KAAK,CAAC,gBAAgB,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACvD,CAAC;YACD,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAChC,6CAA6C;YAC7C,4CAA4C;YAC5C,uCAAuC;YACvC,4BAA4B;YAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC7B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,OAAO,CAAC;gBAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iDAAiD;IACzC,YAAY,CAAC,IAAW;QAC9B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAS,CAAC;QAC7B,MAAM,KAAK,GAAY,CAAC,IAAI,CAAC,CAAC;QAC9B,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxB,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,EAAG,CAAC;YAC3B,KAAK,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAC7C,IAAI,KAAK,KAAK,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,SAAS;gBAC/C,MAAM,IAAI,GAAI,OAA8B,CAAC,IAAI,CAAC;gBAClD,IAAI,IAAI,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;oBACxB,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBACf,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACpB,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,GAAG,CAAC,KAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,OAAO,CAAc,KAAY;QAC/B,6DAA6D;QAC7D,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAM,CAAC;QAE7D,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC9C,IAAI,OAAO,KAAK,CAAC,CAAC,EAAE,CAAC;YACnB,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrE,MAAM,IAAI,KAAK,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC;QACxC,CAAC;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzC,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,GAAG,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,KAAc,CAAC;QACnB,IAAI,CAAC;YACH,IAAI,UAAU,IAAI,OAAO,EAAE,CAAC;gBAC1B,KAAK,GAAG,OAAO,CAAC,QAAQ,CAAC;YAC3B,CAAC;iBAAM,IAAI,UAAU,IAAI,OAAO,EAAE,CAAC;gBACjC,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9D,KAAK,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAI,IAAgB,CAAC,CAAC;YACrD,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC9D,uDAAuD;gBACvD,KAAK,GAAI,OAAO,CAAC,UAA2C,CAAC,GAAG,IAAI,CAAC,CAAC;YACxE,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC;QACvB,CAAC;QAED,kEAAkE;QAClE,wDAAwD;QACxD,oDAAoD;QACpD,sBAAsB;QACtB,EAAE;QACF,0EAA0E;QAC1E,oEAAoE;QACpE,0EAA0E;QAC1E,yDAAyD;QACzD,qCAAqC;QACrC,mEAAmE;QACnE,wBAAwB;QACxB,EAAE;QACF,kEAAkE;QAClE,4CAA4C;QAC5C,2DAA2D;QAC3D,IAAI,KAAK,YAAY,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAI,SAAS,CACjB,aAAa,KAAK,qCAAqC;gBACrD,qDAAqD;gBACrD,6EAA6E,CAChF,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC7B,OAAO,KAAU,CAAC;IACpB,CAAC;IAED,WAAW;IACX,UAAU;QACR,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IACnC,CAAC;CACF"}
|
|
1
|
+
{"version":3,"file":"container.js","sourceRoot":"","sources":["../../src/container/container.ts"],"names":[],"mappings":"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"}
|
package/dist/core/abort.js
CHANGED
|
@@ -13,7 +13,12 @@ export function combineSignals(...signals) {
|
|
|
13
13
|
// 去重:同一个源被传两次时,下面的 listeners Map 按源建(后者覆盖前者),
|
|
14
14
|
// 却对同一源挂了两个同名监听 ⇒ 摘除只摘掉一个,另一个(连闭包)滞留在宿主级
|
|
15
15
|
// 长寿 signal 上 —— 正是这段代码要防的累积。Set 保序,行为不变。
|
|
16
|
-
|
|
16
|
+
// 结构守卫(2026-11-07 深评):持久化 store 反序列化回来的 signal 可能是 `{}`(旧记录),
|
|
17
|
+
// 只判 undefined 会在 `s.addEventListener` 处抛 TypeError。不是合法 AbortSignal 就当没给。
|
|
18
|
+
const real = [
|
|
19
|
+
...new Set(signals.filter((s) => s !== undefined &&
|
|
20
|
+
typeof s.addEventListener === 'function')),
|
|
21
|
+
];
|
|
17
22
|
// 单源直接复用,避免多包一层 listener
|
|
18
23
|
if (real.length === 1)
|
|
19
24
|
return real[0];
|
package/dist/core/abort.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/core/abort.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gEAAgE;AAChE,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA2B,CAAC;AAExD,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,GAAG,OAAuC;IACvE,8CAA8C;IAC9C,yCAAyC;IACzC,0CAA0C;IAC1C,MAAM,IAAI,GAAG,
|
|
1
|
+
{"version":3,"file":"abort.js","sourceRoot":"","sources":["../../src/core/abort.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,gEAAgE;AAChE,MAAM,QAAQ,GAAG,IAAI,OAAO,EAA2B,CAAC;AAExD,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,GAAG,OAAuC;IACvE,8CAA8C;IAC9C,yCAAyC;IACzC,0CAA0C;IAC1C,8DAA8D;IAC9D,2EAA2E;IAC3E,MAAM,IAAI,GAAG;QACX,GAAG,IAAI,GAAG,CACR,OAAO,CAAC,MAAM,CACZ,CAAC,CAAC,EAAoB,EAAE,CACtB,CAAC,KAAK,SAAS;YACf,OAAQ,CAAoC,CAAC,gBAAgB,KAAK,UAAU,CAC/E,CACF;KACF,CAAC;IACF,yBAAyB;IACzB,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC;IAEtC,MAAM,EAAE,GAAG,IAAI,eAAe,EAAE,CAAC;IACjC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;YACd,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACnB,MAAM;QACR,CAAC;IACH,CAAC;IACD,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,+CAA+C;QAC/C,gDAAgD;QAChD,iDAAiD;QACjD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA2B,CAAC;QACrD,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,SAAS;gBAAE,CAAC,CAAC,mBAAmB,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAClE,SAAS,CAAC,KAAK,EAAE,CAAC;YAClB,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC;QAC7B,CAAC,CAAC;QACF,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,GAAG,GAAS,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;YACzC,SAAS,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACpB,CAAC,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;QACD,EAAE,CAAC,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,EAAE,CAAC,MAAM,CAAC;AACnB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAmB;IACvD,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;AAC3B,CAAC"}
|
package/dist/core/json.js
CHANGED
|
@@ -11,6 +11,10 @@ export function stringifySafe(x) {
|
|
|
11
11
|
}
|
|
12
12
|
/** 截断到上限字符、超长加省略标记 `…(+N)`。trace 展示(engine/loop)与 replay 共用同一格式。 */
|
|
13
13
|
export function truncateWithMark(s, n) {
|
|
14
|
+
// n <= 0 = 截到零长度(`maxEventChars` / `maxToolResultChars` 的 limits 表口径)。
|
|
15
|
+
// 此前 0 会走 `slice(0,0) + 省略标记`,回给模型的是 `…(+N)` 而不是空串。
|
|
16
|
+
if (n <= 0)
|
|
17
|
+
return '';
|
|
14
18
|
return s.length > n ? `${s.slice(0, n)}…(+${s.length - n})` : s;
|
|
15
19
|
}
|
|
16
20
|
//# sourceMappingURL=json.js.map
|
package/dist/core/json.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/core/json.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,CAAU;IACtC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,CAAS,EAAE,CAAS;IACnD,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/core/json.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAChE,MAAM,UAAU,aAAa,CAAC,CAAU;IACtC,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,gBAAgB,CAAC,CAAS,EAAE,CAAS;IACnD,uEAAuE;IACvE,oDAAoD;IACpD,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACtB,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC"}
|
package/dist/core/limits.d.ts
CHANGED
|
@@ -164,8 +164,16 @@ export declare const LIMIT_SEMANTICS: readonly [{
|
|
|
164
164
|
readonly unit: 'chars';
|
|
165
165
|
readonly zero: 'disabled';
|
|
166
166
|
readonly zeroClause: '0 = 截到零长度;false = 不截断';
|
|
167
|
-
readonly badValue: '
|
|
168
|
-
readonly note: '两个「关掉」的写法语义不同:`false` 才是「不截断」(调试期看全文),`0`
|
|
167
|
+
readonly badValue: 'throws';
|
|
168
|
+
readonly note: '两个「关掉」的写法语义不同:`false` 才是「不截断」(调试期看全文),`0` 是把正文截成空串(`truncateWithMark` 对 n<=0 返回空串)。⚠️ 靠 `!= null` 判定透传(真值判定会吃掉 `false`)。NaN / 负数 / 小数在第一次模型调用前拒(2026-11-07 深评补登:此前 NaN 让 `s.length > NaN` 恒假 ⇒ 静默不截断)。';
|
|
169
|
+
}, {
|
|
170
|
+
readonly knob: 'maxToolResultChars';
|
|
171
|
+
readonly where: 'engine/tool-events.ts(toolResultBlock / validateMaxToolResultChars)';
|
|
172
|
+
readonly unit: 'chars';
|
|
173
|
+
readonly zero: 'disabled';
|
|
174
|
+
readonly zeroClause: '0 = 截到零长度;false = 不截断';
|
|
175
|
+
readonly badValue: 'throws';
|
|
176
|
+
readonly note: '与 maxEventChars 同读法但不同保护面:这个裁回模型的 tool_result,那个裁 trace。NaN/负数/小数在第一次模型调用前拒,否则上限可能静默失效或行为不定。';
|
|
169
177
|
}, {
|
|
170
178
|
readonly knob: 'runAgent.maxIterations';
|
|
171
179
|
readonly where: 'engine/loop.ts';
|
|
@@ -211,9 +219,9 @@ export declare const LIMIT_SEMANTICS: readonly [{
|
|
|
211
219
|
readonly where: 'integrations/metrics.ts';
|
|
212
220
|
readonly unit: 'ms';
|
|
213
221
|
readonly zero: 'immediate';
|
|
214
|
-
readonly zeroClause: '0 =
|
|
215
|
-
readonly badValue: '
|
|
216
|
-
readonly note: '⚠️ **同名反义**:`Scheduler.every` 的 `intervalMs: 0` 是**配置错误**(抛错),这里却是「关掉定时器、每次累加后立即导出」。两个同名旋钮两种读法 —— 改任何一个之前先看这张表。';
|
|
222
|
+
readonly zeroClause: '0 = 立即导出,负数同 0';
|
|
223
|
+
readonly badValue: 'throws';
|
|
224
|
+
readonly note: '⚠️ **同名反义**:`Scheduler.every` 的 `intervalMs: 0` 是**配置错误**(抛错),这里却是「关掉定时器、每次累加后立即导出」。两个同名旋钮两种读法 —— 改任何一个之前先看这张表。NaN / Infinity / 超 2^31-1ms 在构造期拒(2026-11-07 深评:NaN 此前让定时器与立即导出**同时**不成立 ⇒ 永远不导出,且零信号)。**负数读同 0**(2026-11-09 深评 P3-4:代码判的是 `<= 0`,此前这条读法只在代码里、没进表 ⇒ 同一形状在 `Scheduler.every.intervalMs` 上却是抛错;现在两种口径都写明,负数由 `metrics.test.ts` 钉住)。';
|
|
217
225
|
}, {
|
|
218
226
|
readonly knob: 'RetryOptions.baseDelayMs';
|
|
219
227
|
readonly where: 'engine/retry.ts(resolveRetry)';
|
package/dist/core/limits.js
CHANGED
|
@@ -129,8 +129,17 @@ export const LIMIT_SEMANTICS = [
|
|
|
129
129
|
unit: 'chars',
|
|
130
130
|
zero: 'disabled',
|
|
131
131
|
zeroClause: '0 = 截到零长度;false = 不截断',
|
|
132
|
-
badValue: '
|
|
133
|
-
note: '两个「关掉」的写法语义不同:`false` 才是「不截断」(调试期看全文),`0`
|
|
132
|
+
badValue: 'throws',
|
|
133
|
+
note: '两个「关掉」的写法语义不同:`false` 才是「不截断」(调试期看全文),`0` 是把正文截成空串(`truncateWithMark` 对 n<=0 返回空串)。⚠️ 靠 `!= null` 判定透传(真值判定会吃掉 `false`)。NaN / 负数 / 小数在第一次模型调用前拒(2026-11-07 深评补登:此前 NaN 让 `s.length > NaN` 恒假 ⇒ 静默不截断)。',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
knob: 'maxToolResultChars',
|
|
137
|
+
where: 'engine/tool-events.ts(toolResultBlock / validateMaxToolResultChars)',
|
|
138
|
+
unit: 'chars',
|
|
139
|
+
zero: 'disabled',
|
|
140
|
+
zeroClause: '0 = 截到零长度;false = 不截断',
|
|
141
|
+
badValue: 'throws',
|
|
142
|
+
note: '与 maxEventChars 同读法但不同保护面:这个裁回模型的 tool_result,那个裁 trace。NaN/负数/小数在第一次模型调用前拒,否则上限可能静默失效或行为不定。',
|
|
134
143
|
},
|
|
135
144
|
{
|
|
136
145
|
knob: 'runAgent.maxIterations',
|
|
@@ -183,9 +192,9 @@ export const LIMIT_SEMANTICS = [
|
|
|
183
192
|
where: 'integrations/metrics.ts',
|
|
184
193
|
unit: 'ms',
|
|
185
194
|
zero: 'immediate',
|
|
186
|
-
zeroClause: '0 =
|
|
187
|
-
badValue: '
|
|
188
|
-
note: '⚠️ **同名反义**:`Scheduler.every` 的 `intervalMs: 0` 是**配置错误**(抛错),这里却是「关掉定时器、每次累加后立即导出」。两个同名旋钮两种读法 —— 改任何一个之前先看这张表。',
|
|
195
|
+
zeroClause: '0 = 立即导出,负数同 0',
|
|
196
|
+
badValue: 'throws',
|
|
197
|
+
note: '⚠️ **同名反义**:`Scheduler.every` 的 `intervalMs: 0` 是**配置错误**(抛错),这里却是「关掉定时器、每次累加后立即导出」。两个同名旋钮两种读法 —— 改任何一个之前先看这张表。NaN / Infinity / 超 2^31-1ms 在构造期拒(2026-11-07 深评:NaN 此前让定时器与立即导出**同时**不成立 ⇒ 永远不导出,且零信号)。**负数读同 0**(2026-11-09 深评 P3-4:代码判的是 `<= 0`,此前这条读法只在代码里、没进表 ⇒ 同一形状在 `Scheduler.every.intervalMs` 上却是抛错;现在两种口径都写明,负数由 `metrics.test.ts` 钉住)。',
|
|
189
198
|
},
|
|
190
199
|
{
|
|
191
200
|
knob: 'RetryOptions.baseDelayMs',
|
package/dist/core/limits.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAoDH;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,gEAAgE;IAChE;QACE,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,WAAW;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,qOAAqO;KAC5O;IACD;QACE,IAAI,EAAE,+BAA+B;QACrC,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,WAAW;QACjB,UAAU,EAAE,QAAQ;QACpB,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,2CAA2C;KAClD;IACD;QACE,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,WAAW;QACjB,UAAU,EAAE,gBAAgB;QAC5B,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,+TAA+T;KACtU;IACD;QACE,IAAI,EAAE,6BAA6B;QACnC,KAAK,EAAE,yBAAyB;QAChC,IAAI,EAAE,IAAI;QACV,IAAI,EAAE,WAAW;QACjB,UAAU,EAAE,UAAU;QACtB,QAAQ,EAAE,MAAM;QAChB,IAAI,EAAE,uRAAuR;KAC9R;IACD;QACE,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,WAAW;QACjB,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,sBAAsB;QAChC,IAAI,EAAE,yMAAyM;KAChN;IAED,uDAAuD;IACvD;QACE,IAAI,EAAE,uBAAuB;QAC7B,KAAK,EAAE,kBAAkB;QACzB,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,UAAU;QAChB,UAAU,EAAE,WAAW;QACvB,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,6EAA6E;KACpF;IACD;QACE,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,iCAAiC;QACxC,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,UAAU;QAChB,UAAU,EAAE,SAAS;QACrB,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,mIAAmI;KAC1I;IACD;QACE,IAAI,EAAE,0BAA0B;QAChC,KAAK,EAAE,+BAA+B;QACtC,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,UAAU;QAChB,UAAU,EAAE,UAAU;QACtB,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,oPAAoP;KAC3P;IACD;QACE,IAAI,EAAE,qBAAqB;QAC3B,KAAK,EAAE,+BAA+B;QACtC,IAAI,EAAE,KAAK;QACX,IAAI,EAAE,UAAU;QAChB,UAAU,EAAE,UAAU;QACtB,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,yGAAyG;KAChH;IACD;QACE,IAAI,EAAE,eAAe;QACrB,KAAK,EAAE,uBAAuB;QAC9B,IAAI,EAAE,OAAO;QACb,IAAI,EAAE,UAAU;QAChB,UAAU,EAAE,uBAAuB;QACnC,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"limits.js","sourceRoot":"","sources":["../../src/core/limits.ts"],"names":[],"mappings":"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"}
|
package/dist/core/sse.d.ts
CHANGED
|
@@ -1,14 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agentia —— SSE 逐行分帧(**单源**)。
|
|
3
|
-
*
|
|
4
|
-
* Anthropic 与 OpenAI 兼容端点的事件流都是「一行一条 `data:`」(不需要处理跨行
|
|
5
|
-
* payload),两个适配器此前各写了一份**逐字相同**的 `sseLines`。合并的意义不止整洁:
|
|
6
|
-
* `finally` 里的 `reader.cancel()` 是「提前 break(abort / `[DONE]`)时底层流仍被回收」
|
|
7
|
-
* 的唯一保证 —— 这份代码在两处各存一份,漏改一处就是一处泄漏(读锁不释放,连接挂着)。
|
|
8
|
-
*
|
|
9
|
-
* 只做分帧,不解释协议:空行照常 yield,是不是 `data:` 前缀、要不要认 `[DONE]`
|
|
10
|
-
* 全由调用方判。
|
|
11
|
-
*
|
|
12
|
-
* core 是叶子层:本文件零 import(只用全局的 `ReadableStream` / `TextDecoder`)。
|
|
13
|
-
*/
|
|
14
1
|
export declare function sseLines(body: ReadableStream<Uint8Array>): AsyncGenerator<string>;
|
package/dist/core/sse.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { frameLimitText, MAX_FRAME_CHARS } from './line-framing.js';
|
|
1
2
|
/**
|
|
2
3
|
* Agentia —— SSE 逐行分帧(**单源**)。
|
|
3
4
|
*
|
|
@@ -9,8 +10,18 @@
|
|
|
9
10
|
* 只做分帧,不解释协议:空行照常 yield,是不是 `data:` 前缀、要不要认 `[DONE]`
|
|
10
11
|
* 全由调用方判。
|
|
11
12
|
*
|
|
12
|
-
* core
|
|
13
|
+
* core 是叶子层:只 import 同层的 `line-framing.js`(纯常量),不依赖任何上层。
|
|
13
14
|
*/
|
|
15
|
+
/**
|
|
16
|
+
* 「单帧超限」的错误(归入**上游故障**:server 一档可重试,与适配器的流内错误同口径)。
|
|
17
|
+
*
|
|
18
|
+
* 单源:判据落在**一条切出来的帧**上(见下方,2026-11-09 深评 P3-2)。
|
|
19
|
+
*/
|
|
20
|
+
function frameTooLarge() {
|
|
21
|
+
const e = new Error(`SSE 单行超过分帧上限(${frameLimitText()})—— 对端持续发送不带换行的数据,已中止`);
|
|
22
|
+
e.status = 500;
|
|
23
|
+
return e;
|
|
24
|
+
}
|
|
14
25
|
export async function* sseLines(body) {
|
|
15
26
|
const reader = body.getReader();
|
|
16
27
|
const decoder = new TextDecoder();
|
|
@@ -21,13 +32,28 @@ export async function* sseLines(body) {
|
|
|
21
32
|
if (done)
|
|
22
33
|
break;
|
|
23
34
|
buf += decoder.decode(value, { stream: true });
|
|
35
|
+
// 单条 `data:` 行无上限 = 坏 server 持续吐不带换行的字节就能把进程 OOM
|
|
36
|
+
// (2026-11-07 深评)。与 MCP 两条桥共用同一个 8M 码元硬上限。
|
|
37
|
+
//
|
|
38
|
+
// ⚠️ 判据必须是**切出来的那一行**,不是整个 `buf`(2026-11-09 深评 P3-2):此前先判
|
|
39
|
+
// `buf.length` 再切分,一个 9MB 但每行都很短的 chunk 会被误判成「单行超限」(假阳性,
|
|
40
|
+
// 且报的文案与事实相反)。先切后判同时也保住了原意 —— 一次 read 就把整条 9MB 行连
|
|
41
|
+
// 换行一起送来的情形,下面那条 `line.length` 照样拦得住。
|
|
24
42
|
let idx = buf.indexOf('\n');
|
|
25
43
|
while (idx >= 0) {
|
|
26
|
-
|
|
44
|
+
const line = buf.slice(0, idx);
|
|
45
|
+
if (line.length > MAX_FRAME_CHARS)
|
|
46
|
+
throw frameTooLarge();
|
|
47
|
+
yield line.replace(/\r$/, '');
|
|
27
48
|
buf = buf.slice(idx + 1);
|
|
28
49
|
idx = buf.indexOf('\n');
|
|
29
50
|
}
|
|
51
|
+
// 切完之后剩下的,才是「还没等到换行」的那一条(跨 read 累积的那一半)
|
|
52
|
+
if (buf.length > MAX_FRAME_CHARS)
|
|
53
|
+
throw frameTooLarge();
|
|
30
54
|
}
|
|
55
|
+
// 冲掉 TextDecoder 的尾字节(流若在码点中间结束,此前会静默丢掉最后半个字符)。
|
|
56
|
+
buf += decoder.decode();
|
|
31
57
|
// 末尾没有换行收尾的残行也要交出去(不是所有端点都补 \n)
|
|
32
58
|
if (buf)
|
|
33
59
|
yield buf.replace(/\r$/, '');
|
package/dist/core/sse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/core/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAgC;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,IAAI,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5B,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;gBAChB,MAAM,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"sse.js","sourceRoot":"","sources":["../../src/core/sse.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpE;;;;;;;;;;;;GAYG;AACH;;;;GAIG;AACH,SAAS,aAAa;IACpB,MAAM,CAAC,GAAG,IAAI,KAAK,CACjB,gBAAgB,cAAc,EAAE,uBAAuB,CACxD,CAAC;IACD,CAAyB,CAAC,MAAM,GAAG,GAAG,CAAC;IACxC,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,QAAQ,CAAC,IAAgC;IAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,IAAI,CAAC;QACH,SAAS,CAAC;YACR,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;YAC5C,IAAI,IAAI;gBAAE,MAAM;YAChB,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;YAC/C,iDAAiD;YACjD,2CAA2C;YAC3C,EAAE;YACF,0DAA0D;YAC1D,yDAAyD;YACzD,kDAAkD;YAClD,sCAAsC;YACtC,IAAI,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5B,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC;gBAChB,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;gBAC/B,IAAI,IAAI,CAAC,MAAM,GAAG,eAAe;oBAAE,MAAM,aAAa,EAAE,CAAC;gBACzD,MAAM,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC9B,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;gBACzB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YACD,wCAAwC;YACxC,IAAI,GAAG,CAAC,MAAM,GAAG,eAAe;gBAAE,MAAM,aAAa,EAAE,CAAC;QAC1D,CAAC;QACD,gDAAgD;QAChD,GAAG,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACxB,gCAAgC;QAChC,IAAI,GAAG;YAAE,MAAM,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACxC,CAAC;YAAS,CAAC;QACT,yCAAyC;QACzC,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,MAAM,EAAE,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,aAAa;QACf,CAAC;IACH,CAAC;AACH,CAAC"}
|
package/dist/core/timeout.d.ts
CHANGED
|
@@ -121,6 +121,14 @@ export declare function isTimeoutError(e: unknown): boolean;
|
|
|
121
121
|
* SSE 心跳、metrics 刷盘)成立,对**「等待的终点」**不成立:那正是调用方在等的东西。
|
|
122
122
|
*/
|
|
123
123
|
export declare function withTimeout<T>(p: Promise<T>, timeoutMs: number): Promise<T | typeof TIMED_OUT>;
|
|
124
|
+
/**
|
|
125
|
+
* 上游 `Retry-After` → 毫秒(**夹到可兑现区间**;解析不了返回 undefined)。
|
|
126
|
+
*
|
|
127
|
+
* 2026-11-07 深评:引擎层重试此前只看指数退避,client 层读到 header 却在最后一次失败时
|
|
128
|
+
* 丢掉 —— 429 + `Retry-After: 60` 会在限流窗口里热重试。适配器把结果挂到错误上,
|
|
129
|
+
* 引擎用 `max(backoff, retryAfterMs)`。
|
|
130
|
+
*/
|
|
131
|
+
export declare function retryAfterMsOf(value: string | null | undefined): number | undefined;
|
|
124
132
|
/**
|
|
125
133
|
* 可被 signal 中断的 sleep(**单源**)。
|
|
126
134
|
*
|
package/dist/core/timeout.js
CHANGED
|
@@ -66,7 +66,7 @@ export function assertTimerDelay(ms, where) {
|
|
|
66
66
|
export function clampTimerDelay(ms) {
|
|
67
67
|
if (!(ms > 0))
|
|
68
68
|
return 0; // 含 NaN / 负数:调用点读 0 即「不睡」(见 limits 表)
|
|
69
|
-
return ms
|
|
69
|
+
return Math.min(ms, MAX_TIMER_DELAY_MS);
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* 超时错误(`code === 'timeout'`)。
|
|
@@ -192,6 +192,24 @@ export async function withTimeout(p, timeoutMs) {
|
|
|
192
192
|
clearTimeout(timer);
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* 上游 `Retry-After` → 毫秒(**夹到可兑现区间**;解析不了返回 undefined)。
|
|
197
|
+
*
|
|
198
|
+
* 2026-11-07 深评:引擎层重试此前只看指数退避,client 层读到 header 却在最后一次失败时
|
|
199
|
+
* 丢掉 —— 429 + `Retry-After: 60` 会在限流窗口里热重试。适配器把结果挂到错误上,
|
|
200
|
+
* 引擎用 `max(backoff, retryAfterMs)`。
|
|
201
|
+
*/
|
|
202
|
+
export function retryAfterMsOf(value) {
|
|
203
|
+
if (!value)
|
|
204
|
+
return undefined;
|
|
205
|
+
const secs = Number(value);
|
|
206
|
+
if (Number.isFinite(secs))
|
|
207
|
+
return clampTimerDelay(Math.max(0, Math.round(secs * 1000)));
|
|
208
|
+
const at = Date.parse(value);
|
|
209
|
+
if (Number.isFinite(at))
|
|
210
|
+
return clampTimerDelay(Math.max(0, at - Date.now()));
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
195
213
|
/**
|
|
196
214
|
* 可被 signal 中断的 sleep(**单源**)。
|
|
197
215
|
*
|