@migor/agentia 0.10.5 → 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.
Files changed (124) hide show
  1. package/CHANGELOG.md +128 -1
  2. package/README.md +1 -1
  3. package/dist/AGENTS.md +9 -1
  4. package/dist/container/container.js +3 -0
  5. package/dist/container/container.js.map +1 -1
  6. package/dist/core/abort.js +6 -1
  7. package/dist/core/abort.js.map +1 -1
  8. package/dist/core/json.js +4 -0
  9. package/dist/core/json.js.map +1 -1
  10. package/dist/core/limits.d.ts +5 -5
  11. package/dist/core/limits.js +5 -5
  12. package/dist/core/limits.js.map +1 -1
  13. package/dist/core/sse.d.ts +0 -13
  14. package/dist/core/sse.js +28 -2
  15. package/dist/core/sse.js.map +1 -1
  16. package/dist/core/timeout.d.ts +8 -0
  17. package/dist/core/timeout.js +19 -1
  18. package/dist/core/timeout.js.map +1 -1
  19. package/dist/core/trace.d.ts +7 -0
  20. package/dist/core/trace.js +7 -0
  21. package/dist/core/trace.js.map +1 -1
  22. package/dist/engine/loop.js +37 -11
  23. package/dist/engine/loop.js.map +1 -1
  24. package/dist/engine/mcp-server.d.ts +17 -1
  25. package/dist/engine/mcp-server.js +36 -2
  26. package/dist/engine/mcp-server.js.map +1 -1
  27. package/dist/engine/policy.js +5 -1
  28. package/dist/engine/policy.js.map +1 -1
  29. package/dist/engine/replay.js +63 -1
  30. package/dist/engine/replay.js.map +1 -1
  31. package/dist/engine/run-config.d.ts +6 -0
  32. package/dist/engine/run-config.js +15 -6
  33. package/dist/engine/run-config.js.map +1 -1
  34. package/dist/engine/spec.d.ts +6 -0
  35. package/dist/engine/spec.js.map +1 -1
  36. package/dist/engine/stop-reason.js +19 -1
  37. package/dist/engine/stop-reason.js.map +1 -1
  38. package/dist/engine/tool-events.d.ts +1 -0
  39. package/dist/engine/tool-events.js +7 -0
  40. package/dist/engine/tool-events.js.map +1 -1
  41. package/dist/engine/tracer.js +10 -3
  42. package/dist/engine/tracer.js.map +1 -1
  43. package/dist/engine/turn.d.ts +4 -12
  44. package/dist/engine/turn.js +89 -8
  45. package/dist/engine/turn.js.map +1 -1
  46. package/dist/engine/types.d.ts +10 -1
  47. package/dist/engine/types.js +1 -0
  48. package/dist/engine/types.js.map +1 -1
  49. package/dist/engine/usage.js +12 -4
  50. package/dist/engine/usage.js.map +1 -1
  51. package/dist/index.d.ts +1 -1
  52. package/dist/index.js +1 -1
  53. package/dist/integrations/anthropic.d.ts +9 -1
  54. package/dist/integrations/anthropic.js +42 -7
  55. package/dist/integrations/anthropic.js.map +1 -1
  56. package/dist/integrations/file-sink.d.ts +1 -0
  57. package/dist/integrations/file-sink.js +35 -32
  58. package/dist/integrations/file-sink.js.map +1 -1
  59. package/dist/integrations/mcp-http.js +33 -9
  60. package/dist/integrations/mcp-http.js.map +1 -1
  61. package/dist/integrations/mcp-protocol.js +33 -16
  62. package/dist/integrations/mcp-protocol.js.map +1 -1
  63. package/dist/integrations/metrics-otlp.js +4 -0
  64. package/dist/integrations/metrics-otlp.js.map +1 -1
  65. package/dist/integrations/metrics.js +17 -2
  66. package/dist/integrations/metrics.js.map +1 -1
  67. package/dist/integrations/openai.d.ts +6 -1
  68. package/dist/integrations/openai.js +45 -8
  69. package/dist/integrations/openai.js.map +1 -1
  70. package/dist/integrations/otlp.js +6 -0
  71. package/dist/integrations/otlp.js.map +1 -1
  72. package/dist/runtime/memory.d.ts +0 -8
  73. package/dist/runtime/memory.js +24 -2
  74. package/dist/runtime/memory.js.map +1 -1
  75. package/dist/runtime/run.js +34 -9
  76. package/dist/runtime/run.js.map +1 -1
  77. package/dist/store/fsStore.d.ts +2 -0
  78. package/dist/store/fsStore.js +38 -5
  79. package/dist/store/fsStore.js.map +1 -1
  80. package/dist/store/record.d.ts +40 -0
  81. package/dist/store/record.js +72 -0
  82. package/dist/store/record.js.map +1 -1
  83. package/dist/store/redisStore.js +15 -4
  84. package/dist/store/redisStore.js.map +1 -1
  85. package/dist/store/sqliteStore.js +39 -14
  86. package/dist/store/sqliteStore.js.map +1 -1
  87. package/dist/store/store.js +10 -4
  88. package/dist/store/store.js.map +1 -1
  89. package/dist/toolkit/module.js +1 -0
  90. package/dist/toolkit/module.js.map +1 -1
  91. package/dist/toolkit/prompt.js +12 -3
  92. package/dist/toolkit/prompt.js.map +1 -1
  93. package/dist/transport/approval-policy.d.ts +1 -1
  94. package/dist/transport/approval-policy.js +18 -3
  95. package/dist/transport/approval-policy.js.map +1 -1
  96. package/dist/transport/approval-supervisor.d.ts +15 -3
  97. package/dist/transport/approval-supervisor.js +37 -11
  98. package/dist/transport/approval-supervisor.js.map +1 -1
  99. package/dist/transport/async.d.ts +1 -0
  100. package/dist/transport/async.js +143 -23
  101. package/dist/transport/async.js.map +1 -1
  102. package/dist/transport/drain-gate.js +13 -1
  103. package/dist/transport/drain-gate.js.map +1 -1
  104. package/dist/transport/http-endpoints.js +7 -0
  105. package/dist/transport/http-endpoints.js.map +1 -1
  106. package/dist/transport/resume-scanner.d.ts +3 -0
  107. package/dist/transport/resume-scanner.js +10 -1
  108. package/dist/transport/resume-scanner.js.map +1 -1
  109. package/dist/transport/run-call-options.js +43 -1
  110. package/dist/transport/run-call-options.js.map +1 -1
  111. package/dist/transport/run-outcome.js +18 -1
  112. package/dist/transport/run-outcome.js.map +1 -1
  113. package/dist/transport/serve.js +10 -0
  114. package/dist/transport/serve.js.map +1 -1
  115. package/dist/transport/signal-supervisor.d.ts +3 -0
  116. package/dist/transport/signal-supervisor.js +5 -1
  117. package/dist/transport/signal-supervisor.js.map +1 -1
  118. package/dist/transport/stream-janitor.d.ts +45 -0
  119. package/dist/transport/stream-janitor.js +137 -0
  120. package/dist/transport/stream-janitor.js.map +1 -0
  121. package/dist/transport/task-wake-gate.d.ts +40 -0
  122. package/dist/transport/task-wake-gate.js +69 -0
  123. package/dist/transport/task-wake-gate.js.map +1 -0
  124. package/package.json +2 -2
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 跨进程 SSE 流的**兜底收口**(2026-11-07 深评 F5;2026-11-09 深评 P2-1 提到 runner 级)。
3
+ *
4
+ * 本进程的 `TaskEventStreams.markDone` 只覆盖「本进程把任务写到终态」;任务在**别的进程**
5
+ * 被 approve / 到期唤醒并跑完时,本进程那条流既没有 `push` 也没有 `markDone`,只剩心跳。
6
+ * 这个纯件做低频 store 轮询:终态 → 交给调用方 markDone(由 onDone 发 end);
7
+ * 记录消失 / 已换主 → 发流级 closed。
8
+ *
9
+ * **为什么是「一组」而不是「一条流一个 janitor」**(P2-1):此前每个 SSE 订阅各建一个
10
+ * `setInterval`、每拍各 `store.get` 一次 —— N 个并发连接就是 N 个定时器 + N/5 QPS(SQLite 下
11
+ * 是 N/5 次读锁),而 `unref()` 只保证不挂住进程、不减少读放大。同一个 taskId 的多个订阅
12
+ * 本来该看**同一份**记录,现在由 runner 持有一个扫描器:一拍里每个**任务**只读一次 store,
13
+ * 再把结论分发给该任务的各个订阅。单连接仍是「登记 / 注销」。
14
+ *
15
+ * 不碰 store、不认识 trace:poll / isTerminal / 收口都以回调注入,单测可直接驱动
16
+ * (喂一个假 poll + 假计时,不碰真 store)。
17
+ */
18
+ /** 轮询只需要这两个字段:本进程归属 + 是否终态(由 `isTerminal` 判) */
19
+ export interface StreamJanitorRecord {
20
+ ownerId?: string | undefined;
21
+ }
22
+ /** 一条流的收口回调(同一任务的多个订阅各有一份) */
23
+ export interface StreamJanitorTarget {
24
+ isTerminal: (rec: StreamJanitorRecord) => boolean;
25
+ /** 任务已终态:调用方负责 markDone(终态帧由既有 onDone 发) */
26
+ onTerminal: () => void;
27
+ /** 任务记录消失 / 后续段在别的进程:发流级 closed 并收口 */
28
+ onClosed: () => void;
29
+ }
30
+ export interface StreamJanitorGroupDeps {
31
+ /** 读一条记录;同步 store 抛错、异步 store reject 都按「这一拍读不动,下一拍再试」处理 */
32
+ poll: (taskId: string) => Promise<StreamJanitorRecord | undefined>;
33
+ /** 本进程 ownerId */
34
+ ownerId: string;
35
+ /** 扫描间隔(毫秒);<= 0 = 不启动定时器 */
36
+ pollMs: number;
37
+ }
38
+ export interface StreamJanitorGroup {
39
+ /** 登记一条流;返回注销函数(幂等) */
40
+ add(taskId: string, target: StreamJanitorTarget): () => void;
41
+ /** 停掉定时器并清空登记(runner 停机 / 测试收尾) */
42
+ close(): void;
43
+ }
44
+ /** 启动(或复用)扫描器。定时器**有登记才存在**:没有订阅时零成本。 */
45
+ export declare function createStreamJanitorGroup(deps: StreamJanitorGroupDeps): StreamJanitorGroup;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * 跨进程 SSE 流的**兜底收口**(2026-11-07 深评 F5;2026-11-09 深评 P2-1 提到 runner 级)。
3
+ *
4
+ * 本进程的 `TaskEventStreams.markDone` 只覆盖「本进程把任务写到终态」;任务在**别的进程**
5
+ * 被 approve / 到期唤醒并跑完时,本进程那条流既没有 `push` 也没有 `markDone`,只剩心跳。
6
+ * 这个纯件做低频 store 轮询:终态 → 交给调用方 markDone(由 onDone 发 end);
7
+ * 记录消失 / 已换主 → 发流级 closed。
8
+ *
9
+ * **为什么是「一组」而不是「一条流一个 janitor」**(P2-1):此前每个 SSE 订阅各建一个
10
+ * `setInterval`、每拍各 `store.get` 一次 —— N 个并发连接就是 N 个定时器 + N/5 QPS(SQLite 下
11
+ * 是 N/5 次读锁),而 `unref()` 只保证不挂住进程、不减少读放大。同一个 taskId 的多个订阅
12
+ * 本来该看**同一份**记录,现在由 runner 持有一个扫描器:一拍里每个**任务**只读一次 store,
13
+ * 再把结论分发给该任务的各个订阅。单连接仍是「登记 / 注销」。
14
+ *
15
+ * 不碰 store、不认识 trace:poll / isTerminal / 收口都以回调注入,单测可直接驱动
16
+ * (喂一个假 poll + 假计时,不碰真 store)。
17
+ */
18
+ /** 启动(或复用)扫描器。定时器**有登记才存在**:没有订阅时零成本。 */
19
+ export function createStreamJanitorGroup(deps) {
20
+ const targets = new Map();
21
+ let timer;
22
+ let closed = false;
23
+ let ticking = false;
24
+ const stopTimer = () => {
25
+ if (timer === undefined)
26
+ return;
27
+ clearInterval(timer);
28
+ timer = undefined;
29
+ };
30
+ const ensureTimer = () => {
31
+ if (closed || timer !== undefined || deps.pollMs <= 0)
32
+ return;
33
+ timer = setInterval(() => void tick(), deps.pollMs);
34
+ timer.unref?.();
35
+ };
36
+ const safe = (fn) => {
37
+ try {
38
+ fn();
39
+ }
40
+ catch {
41
+ /* 收口回调自己的错不得变成 unhandled(观测不击穿业务) */
42
+ }
43
+ };
44
+ /** 一拍只读一次 store:同任务的多个订阅共用这次结果 */
45
+ const checkOne = async (taskId) => {
46
+ const first = targets.get(taskId);
47
+ if (first === undefined)
48
+ return;
49
+ let rec;
50
+ try {
51
+ rec = await deps.poll(taskId);
52
+ }
53
+ catch {
54
+ // 同步 store(已 close)的 poll 可能同步抛、异步 store 可能 reject:
55
+ // 两种都按「这一拍读不动」处理 —— 定时器回调里逃出去就是 uncaughtException。
56
+ return;
57
+ }
58
+ if (closed)
59
+ return;
60
+ // poll 期间流可能已经关掉 / 换主:重新取一次,别拿过期快照去调回调
61
+ const live = targets.get(taskId);
62
+ if (live === undefined)
63
+ return;
64
+ for (const target of [...live]) {
65
+ if (!live.has(target))
66
+ continue;
67
+ if (rec === undefined) {
68
+ live.delete(target);
69
+ safe(target.onClosed);
70
+ continue;
71
+ }
72
+ if (target.isTerminal(rec)) {
73
+ live.delete(target);
74
+ safe(target.onTerminal);
75
+ continue;
76
+ }
77
+ if (rec.ownerId !== undefined && rec.ownerId !== deps.ownerId) {
78
+ live.delete(target);
79
+ safe(target.onClosed);
80
+ }
81
+ }
82
+ if (live.size === 0)
83
+ targets.delete(taskId);
84
+ };
85
+ /** 扫描一拍。上一拍还没跑完(poll 慢)⇒ 跳过,不叠请求 */
86
+ const tick = async () => {
87
+ if (closed || ticking || targets.size === 0)
88
+ return;
89
+ ticking = true;
90
+ try {
91
+ await Promise.all([...targets.keys()].map((taskId) => checkOne(taskId)));
92
+ }
93
+ catch {
94
+ // 一拍里的意外不得外逃:`setInterval` 回调里的 rejection 就是 unhandledRejection
95
+ // (Node ≥15 终止宿主)—— 观测路径不允许有这种出口。下一拍照常继续。
96
+ }
97
+ finally {
98
+ ticking = false;
99
+ if (targets.size === 0)
100
+ stopTimer();
101
+ }
102
+ };
103
+ return {
104
+ add(taskId, target) {
105
+ if (closed)
106
+ return () => { };
107
+ let set = targets.get(taskId);
108
+ if (set === undefined) {
109
+ set = new Set();
110
+ targets.set(taskId, set);
111
+ }
112
+ set.add(target);
113
+ ensureTimer();
114
+ let removed = false;
115
+ return () => {
116
+ if (removed)
117
+ return; // 幂等:调用方可能在连接关闭路径上重复调
118
+ removed = true;
119
+ const live = targets.get(taskId);
120
+ if (live === undefined)
121
+ return;
122
+ live.delete(target);
123
+ if (live.size === 0)
124
+ targets.delete(taskId);
125
+ // 最后一条流也走了 ⇒ 定时器不必再跑(下一个订阅到来时重新起)
126
+ if (targets.size === 0)
127
+ stopTimer();
128
+ };
129
+ },
130
+ close() {
131
+ closed = true;
132
+ targets.clear();
133
+ stopTimer();
134
+ },
135
+ };
136
+ }
137
+ //# sourceMappingURL=stream-janitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream-janitor.js","sourceRoot":"","sources":["../../src/transport/stream-janitor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,40 @@
1
+ /**
2
+ * 每条任务的**唤醒互斥闸**(approve / signalTask / 审批超时 / 到期唤醒共用)。
3
+ *
4
+ * 为什么需要它(2026-11-07 深度评审 P1):四条唤醒路径各自有「在飞闸」,但**互不登记、互不查看**。
5
+ * `approve` 与 `wakeDue` 靠挂起原因天然互斥;而 `signalTask` 对两种挂起原因都放行 ⇒
6
+ * `approve × signalTask`、`wakeDue × signalTask` 可以并发。两边都先 `get` 到同一份 suspended
7
+ * 快照,再各自 `status='running'` + `save` + `dispatch` ⇒ 同一任务被 `#execute` 两次,
8
+ * 后写的那份记录还可能把已完成的段覆盖回 suspended、丢掉审批决定。
9
+ *
10
+ * 语义:**同一 taskId 上,进入本闸的唤醒路径串行**。最后一个登记的 tail 会在它及其之前
11
+ * 所有路径收口后才 resolve,所以 `cancel` 只要 `await gate.tail(taskId)` 就能等到
12
+ * 「此刻已登记的全部唤醒」—— 不需要知道它具体是 approve 还是 signal。
13
+ *
14
+ * 与各监督件自己的 `#inflight` 的关系:那些闸仍保留(approve 的「并发重入共享同一次」
15
+ * 语义依赖它;signal 的事件串行链也依赖它),本闸只负责**跨路径**互斥。两边在同一同步段
16
+ * 登记,先登记者赢。
17
+ *
18
+ * 纯件:不碰 store、不认识记录,只做「同一 key 的 Promise 串行」。单测见
19
+ * `tests/transport/task-wake-gate.test.ts`。
20
+ *
21
+ * ⚠️ **隐含前提:`fn` 必须最终 settle**(2026-11-09 深评「待确认」)。等待链**没有超时** ——
22
+ * 若某条路径的 `fn` 永远挂着(典型:宿主 store 的 `save` 不返回),同任务后续所有唤醒会
23
+ * 永久排队,`cancel` 的 `await tail` 也会一起挂住。当前四个调用点的 `fn` 只碰 store,
24
+ * 属于「宿主必须把 store 实现成会 settle」的契约;要打破它得在闸上加超时,而超时语义
25
+ * (「超时后放行」= 互斥失效)比挂住更危险,所以这里选择写明前提而不是加计时器。
26
+ */
27
+ export declare class TaskWakeGate {
28
+ #private;
29
+ /** 该任务上是否已有登记(排队中或执行中)——惰性路径据此跳过本轮 */
30
+ busy(taskId: string): boolean;
31
+ /** 该任务上全部已登记路径的收口 Promise;没有则为 undefined */
32
+ tail(taskId: string): Promise<unknown> | undefined;
33
+ /**
34
+ * 串行执行:前一条(若存在)收口后才开始 `fn`。
35
+ *
36
+ * `fn` 的同步前段(第一个 await 之前)在**调用者的同步段**里执行 —— 与不加闸时
37
+ * 的时序一致,既有「进闸后立刻重读」的测试不会因为多了一个微任务而变味。
38
+ */
39
+ run<T>(taskId: string, fn: () => Promise<T>): Promise<T>;
40
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 每条任务的**唤醒互斥闸**(approve / signalTask / 审批超时 / 到期唤醒共用)。
3
+ *
4
+ * 为什么需要它(2026-11-07 深度评审 P1):四条唤醒路径各自有「在飞闸」,但**互不登记、互不查看**。
5
+ * `approve` 与 `wakeDue` 靠挂起原因天然互斥;而 `signalTask` 对两种挂起原因都放行 ⇒
6
+ * `approve × signalTask`、`wakeDue × signalTask` 可以并发。两边都先 `get` 到同一份 suspended
7
+ * 快照,再各自 `status='running'` + `save` + `dispatch` ⇒ 同一任务被 `#execute` 两次,
8
+ * 后写的那份记录还可能把已完成的段覆盖回 suspended、丢掉审批决定。
9
+ *
10
+ * 语义:**同一 taskId 上,进入本闸的唤醒路径串行**。最后一个登记的 tail 会在它及其之前
11
+ * 所有路径收口后才 resolve,所以 `cancel` 只要 `await gate.tail(taskId)` 就能等到
12
+ * 「此刻已登记的全部唤醒」—— 不需要知道它具体是 approve 还是 signal。
13
+ *
14
+ * 与各监督件自己的 `#inflight` 的关系:那些闸仍保留(approve 的「并发重入共享同一次」
15
+ * 语义依赖它;signal 的事件串行链也依赖它),本闸只负责**跨路径**互斥。两边在同一同步段
16
+ * 登记,先登记者赢。
17
+ *
18
+ * 纯件:不碰 store、不认识记录,只做「同一 key 的 Promise 串行」。单测见
19
+ * `tests/transport/task-wake-gate.test.ts`。
20
+ *
21
+ * ⚠️ **隐含前提:`fn` 必须最终 settle**(2026-11-09 深评「待确认」)。等待链**没有超时** ——
22
+ * 若某条路径的 `fn` 永远挂着(典型:宿主 store 的 `save` 不返回),同任务后续所有唤醒会
23
+ * 永久排队,`cancel` 的 `await tail` 也会一起挂住。当前四个调用点的 `fn` 只碰 store,
24
+ * 属于「宿主必须把 store 实现成会 settle」的契约;要打破它得在闸上加超时,而超时语义
25
+ * (「超时后放行」= 互斥失效)比挂住更危险,所以这里选择写明前提而不是加计时器。
26
+ */
27
+ export class TaskWakeGate {
28
+ /**
29
+ * 每个 taskId 的**队列尾**。它 resolve 的条件是「本路径之后才登记的路径也都已完成」,
30
+ * 因此它同时可当 `cancel` 的等待句柄。
31
+ */
32
+ #tails = new Map();
33
+ /** 该任务上是否已有登记(排队中或执行中)——惰性路径据此跳过本轮 */
34
+ busy(taskId) {
35
+ return this.#tails.has(taskId);
36
+ }
37
+ /** 该任务上全部已登记路径的收口 Promise;没有则为 undefined */
38
+ tail(taskId) {
39
+ return this.#tails.get(taskId);
40
+ }
41
+ /**
42
+ * 串行执行:前一条(若存在)收口后才开始 `fn`。
43
+ *
44
+ * `fn` 的同步前段(第一个 await 之前)在**调用者的同步段**里执行 —— 与不加闸时
45
+ * 的时序一致,既有「进闸后立刻重读」的测试不会因为多了一个微任务而变味。
46
+ */
47
+ async run(taskId, fn) {
48
+ const prev = this.#tails.get(taskId);
49
+ let release;
50
+ const gate = new Promise((resolve) => {
51
+ release = resolve;
52
+ });
53
+ // tail 必须把 gate 串在 prev 之后:等待者拿到 tail 时,等待的是「这条路径也结束」。
54
+ const tail = prev === undefined ? gate : prev.catch(() => undefined).then(() => gate);
55
+ this.#tails.set(taskId, tail);
56
+ if (prev !== undefined)
57
+ await prev.catch(() => undefined);
58
+ try {
59
+ return await fn();
60
+ }
61
+ finally {
62
+ release();
63
+ // 只摘自己:若后面又排了新的 tail,留给它自己摘(无条件 delete 会摘掉别人的)。
64
+ if (this.#tails.get(taskId) === tail)
65
+ this.#tails.delete(taskId);
66
+ }
67
+ }
68
+ }
69
+ //# sourceMappingURL=task-wake-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-wake-gate.js","sourceRoot":"","sources":["../../src/transport/task-wake-gate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,OAAO,YAAY;IACvB;;;OAGG;IACM,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IAEtD,sCAAsC;IACtC,IAAI,CAAC,MAAc;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED,4CAA4C;IAC5C,IAAI,CAAC,MAAc;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,GAAG,CAAI,MAAc,EAAE,EAAoB;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACrC,IAAI,OAAoB,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;YACzC,OAAO,GAAG,OAAO,CAAC;QACpB,CAAC,CAAC,CAAC;QACH,uDAAuD;QACvD,MAAM,IAAI,GAAG,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACtF,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC9B,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1D,IAAI,CAAC;YACH,OAAO,MAAM,EAAE,EAAE,CAAC;QACpB,CAAC;gBAAS,CAAC;YACT,OAAO,EAAE,CAAC;YACV,gDAAgD;YAChD,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI;gBAAE,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;CACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@migor/agentia",
3
- "version": "0.10.5",
3
+ "version": "0.10.6",
4
4
  "description": "Declarative agent service framework for TypeScript: decorators + DI declare four capability kinds, durable runs, and per-run traces, cost and metrics. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -82,7 +82,7 @@
82
82
  "bench:otlp": "tsx scripts/bench-otlp-snapshot.ts"
83
83
  },
84
84
  "devDependencies": {
85
- "@anthropic-ai/sdk": "^0.134.0",
85
+ "@anthropic-ai/sdk": "^1.3.0",
86
86
  "@biomejs/biome": "^2.5.14",
87
87
  "@grpc/grpc-js": "^1.14.6",
88
88
  "@grpc/proto-loader": "^0.8.1",