@migor/agentia 0.9.4 → 0.9.5

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 (196) hide show
  1. package/CHANGELOG.md +688 -4
  2. package/README.md +1 -1
  3. package/dist/AGENTS.md +313 -33
  4. package/dist/container/container.js +15 -0
  5. package/dist/container/container.js.map +1 -1
  6. package/dist/core/limits.d.ts +93 -13
  7. package/dist/core/limits.js +103 -13
  8. package/dist/core/limits.js.map +1 -1
  9. package/dist/core/line-framing.d.ts +36 -0
  10. package/dist/core/line-framing.js +39 -0
  11. package/dist/core/line-framing.js.map +1 -0
  12. package/dist/core/message.d.ts +11 -0
  13. package/dist/core/run.d.ts +68 -4
  14. package/dist/core/run.js +27 -1
  15. package/dist/core/run.js.map +1 -1
  16. package/dist/core/schema.js +59 -8
  17. package/dist/core/schema.js.map +1 -1
  18. package/dist/core/timeout.d.ts +43 -1
  19. package/dist/core/timeout.js +76 -3
  20. package/dist/core/timeout.js.map +1 -1
  21. package/dist/core/tool.d.ts +25 -1
  22. package/dist/core/trace.d.ts +40 -1
  23. package/dist/core/trace.js +26 -0
  24. package/dist/core/trace.js.map +1 -1
  25. package/dist/engine/defer.d.ts +49 -0
  26. package/dist/engine/defer.js +72 -0
  27. package/dist/engine/defer.js.map +1 -0
  28. package/dist/engine/forwarded.d.ts +14 -5
  29. package/dist/engine/forwarded.js +4 -1
  30. package/dist/engine/forwarded.js.map +1 -1
  31. package/dist/engine/loop-result.d.ts +65 -7
  32. package/dist/engine/loop-result.js +24 -5
  33. package/dist/engine/loop-result.js.map +1 -1
  34. package/dist/engine/loop.d.ts +2 -0
  35. package/dist/engine/loop.js +157 -17
  36. package/dist/engine/loop.js.map +1 -1
  37. package/dist/engine/mcp-server-http.d.ts +2 -0
  38. package/dist/engine/mcp-server-http.js +215 -0
  39. package/dist/engine/mcp-server-http.js.map +1 -0
  40. package/dist/engine/mcp-server-stdio.d.ts +15 -0
  41. package/dist/engine/mcp-server-stdio.js +106 -0
  42. package/dist/engine/mcp-server-stdio.js.map +1 -0
  43. package/dist/engine/mcp-server.d.ts +134 -0
  44. package/dist/engine/mcp-server.js +232 -0
  45. package/dist/engine/mcp-server.js.map +1 -0
  46. package/dist/engine/menu-drift.d.ts +34 -0
  47. package/dist/engine/menu-drift.js +81 -0
  48. package/dist/engine/menu-drift.js.map +1 -0
  49. package/dist/engine/policy.d.ts +2 -1
  50. package/dist/engine/policy.js +7 -1
  51. package/dist/engine/policy.js.map +1 -1
  52. package/dist/engine/replay.d.ts +2 -1
  53. package/dist/engine/replay.js +3 -2
  54. package/dist/engine/replay.js.map +1 -1
  55. package/dist/engine/resume-input.d.ts +10 -0
  56. package/dist/engine/resume-input.js +11 -0
  57. package/dist/engine/resume-input.js.map +1 -1
  58. package/dist/engine/run-config.d.ts +26 -2
  59. package/dist/engine/run-config.js +60 -0
  60. package/dist/engine/run-config.js.map +1 -1
  61. package/dist/engine/spec.d.ts +33 -4
  62. package/dist/engine/spec.js.map +1 -1
  63. package/dist/engine/tool-context.d.ts +10 -5
  64. package/dist/engine/tool-context.js +5 -1
  65. package/dist/engine/tool-context.js.map +1 -1
  66. package/dist/engine/tracer.d.ts +43 -5
  67. package/dist/engine/tracer.js +73 -10
  68. package/dist/engine/tracer.js.map +1 -1
  69. package/dist/engine/trimming.js +28 -2
  70. package/dist/engine/trimming.js.map +1 -1
  71. package/dist/engine/turn.d.ts +60 -3
  72. package/dist/engine/turn.js +180 -71
  73. package/dist/engine/turn.js.map +1 -1
  74. package/dist/engine/types.d.ts +89 -3
  75. package/dist/engine/types.js +38 -2
  76. package/dist/engine/types.js.map +1 -1
  77. package/dist/eval/export.d.ts +48 -0
  78. package/dist/eval/export.js +146 -0
  79. package/dist/eval/export.js.map +1 -0
  80. package/dist/eval/harvest.js +7 -2
  81. package/dist/eval/harvest.js.map +1 -1
  82. package/dist/index.d.ts +8 -5
  83. package/dist/index.js +6 -1
  84. package/dist/index.js.map +1 -1
  85. package/dist/integrations/adapter-options.d.ts +22 -0
  86. package/dist/integrations/adapter-options.js +51 -0
  87. package/dist/integrations/adapter-options.js.map +1 -1
  88. package/dist/integrations/anthropic.d.ts +4 -2
  89. package/dist/integrations/anthropic.js +27 -27
  90. package/dist/integrations/anthropic.js.map +1 -1
  91. package/dist/integrations/file-sink.d.ts +20 -0
  92. package/dist/integrations/file-sink.js +27 -0
  93. package/dist/integrations/file-sink.js.map +1 -0
  94. package/dist/integrations/mcp-http.d.ts +3 -3
  95. package/dist/integrations/mcp-http.js +1 -1
  96. package/dist/integrations/mcp-http.js.map +1 -1
  97. package/dist/integrations/mcp-protocol.d.ts +115 -0
  98. package/dist/integrations/mcp-protocol.js +69 -0
  99. package/dist/integrations/mcp-protocol.js.map +1 -0
  100. package/dist/integrations/mcp-stdio.d.ts +3 -3
  101. package/dist/integrations/mcp-stdio.js +17 -3
  102. package/dist/integrations/mcp-stdio.js.map +1 -1
  103. package/dist/integrations/mcp.d.ts +6 -95
  104. package/dist/integrations/mcp.js +24 -66
  105. package/dist/integrations/mcp.js.map +1 -1
  106. package/dist/integrations/metrics-otlp.js +52 -7
  107. package/dist/integrations/metrics-otlp.js.map +1 -1
  108. package/dist/integrations/metrics-render.d.ts +15 -0
  109. package/dist/integrations/metrics-render.js +79 -2
  110. package/dist/integrations/metrics-render.js.map +1 -1
  111. package/dist/integrations/metrics-state.d.ts +99 -1
  112. package/dist/integrations/metrics-state.js +128 -4
  113. package/dist/integrations/metrics-state.js.map +1 -1
  114. package/dist/integrations/metrics.d.ts +62 -6
  115. package/dist/integrations/metrics.js +49 -8
  116. package/dist/integrations/metrics.js.map +1 -1
  117. package/dist/integrations/openai.js +9 -31
  118. package/dist/integrations/openai.js.map +1 -1
  119. package/dist/integrations/otlp.d.ts +3 -3
  120. package/dist/integrations/report.d.ts +7 -0
  121. package/dist/integrations/report.js +20 -1
  122. package/dist/integrations/report.js.map +1 -1
  123. package/dist/runtime/memory.d.ts +61 -1
  124. package/dist/runtime/memory.js +47 -7
  125. package/dist/runtime/memory.js.map +1 -1
  126. package/dist/runtime/run.d.ts +12 -2
  127. package/dist/runtime/run.js +112 -15
  128. package/dist/runtime/run.js.map +1 -1
  129. package/dist/store/fsStore.js +10 -4
  130. package/dist/store/fsStore.js.map +1 -1
  131. package/dist/store/record.d.ts +41 -0
  132. package/dist/store/record.js +54 -0
  133. package/dist/store/record.js.map +1 -0
  134. package/dist/store/redisStore.d.ts +3 -1
  135. package/dist/store/redisStore.js +21 -7
  136. package/dist/store/redisStore.js.map +1 -1
  137. package/dist/store/sqliteStore.d.ts +23 -0
  138. package/dist/store/sqliteStore.js +103 -8
  139. package/dist/store/sqliteStore.js.map +1 -1
  140. package/dist/store/store.d.ts +65 -2
  141. package/dist/store/store.js +5 -2
  142. package/dist/store/store.js.map +1 -1
  143. package/dist/toolkit/capability-cycles.d.ts +50 -0
  144. package/dist/toolkit/capability-cycles.js +88 -0
  145. package/dist/toolkit/capability-cycles.js.map +1 -0
  146. package/dist/toolkit/capability-slice.d.ts +86 -0
  147. package/dist/toolkit/capability-slice.js +98 -0
  148. package/dist/toolkit/capability-slice.js.map +1 -0
  149. package/dist/toolkit/module.d.ts +7 -1
  150. package/dist/toolkit/module.js +65 -32
  151. package/dist/toolkit/module.js.map +1 -1
  152. package/dist/transport/approval-policy.d.ts +1 -1
  153. package/dist/transport/approval-policy.js +8 -4
  154. package/dist/transport/approval-policy.js.map +1 -1
  155. package/dist/transport/async.d.ts +197 -18
  156. package/dist/transport/async.js +871 -85
  157. package/dist/transport/async.js.map +1 -1
  158. package/dist/transport/drain-gate.d.ts +6 -2
  159. package/dist/transport/drain-gate.js +14 -2
  160. package/dist/transport/drain-gate.js.map +1 -1
  161. package/dist/transport/http-endpoints.d.ts +151 -0
  162. package/dist/transport/http-endpoints.js +480 -0
  163. package/dist/transport/http-endpoints.js.map +1 -0
  164. package/dist/transport/http-io.d.ts +66 -0
  165. package/dist/transport/http-io.js +145 -0
  166. package/dist/transport/http-io.js.map +1 -0
  167. package/dist/transport/http-route.d.ts +10 -3
  168. package/dist/transport/http-route.js +18 -3
  169. package/dist/transport/http-route.js.map +1 -1
  170. package/dist/transport/http-shapes.d.ts +11 -0
  171. package/dist/transport/http-shapes.js +31 -1
  172. package/dist/transport/http-shapes.js.map +1 -1
  173. package/dist/transport/http.d.ts +22 -35
  174. package/dist/transport/http.js +98 -476
  175. package/dist/transport/http.js.map +1 -1
  176. package/dist/transport/owner-id.d.ts +29 -0
  177. package/dist/transport/owner-id.js +46 -0
  178. package/dist/transport/owner-id.js.map +1 -0
  179. package/dist/transport/owner-liveness.d.ts +8 -0
  180. package/dist/transport/owner-liveness.js +56 -0
  181. package/dist/transport/owner-liveness.js.map +1 -0
  182. package/dist/transport/resume-policy.d.ts +48 -10
  183. package/dist/transport/resume-policy.js +25 -10
  184. package/dist/transport/resume-policy.js.map +1 -1
  185. package/dist/transport/scheduler.js +9 -23
  186. package/dist/transport/scheduler.js.map +1 -1
  187. package/dist/transport/slot-pool.d.ts +9 -1
  188. package/dist/transport/slot-pool.js +9 -1
  189. package/dist/transport/slot-pool.js.map +1 -1
  190. package/dist/transport/task-events.d.ts +34 -3
  191. package/dist/transport/task-events.js +86 -5
  192. package/dist/transport/task-events.js.map +1 -1
  193. package/dist/transport/wake-policy.d.ts +50 -0
  194. package/dist/transport/wake-policy.js +43 -0
  195. package/dist/transport/wake-policy.js.map +1 -0
  196. package/package.json +6 -4
@@ -1,4 +1,5 @@
1
1
  import { createRequire } from 'node:module';
2
+ import { parseTaskRecord } from './record.js';
2
3
  // node:sqlite 是 Node ≥22.5 才有的内置模块。这里**延迟加载**而非顶层静态 import:
3
4
  // 顶层 import 会让**整个包**在未提供该模块的运行时「加载即崩」——`src/index.ts` 对
4
5
  // SqliteTaskStore 是 eager 再导出,于是连不用 SQLite 的用户也被殃及。延迟到真正构造
@@ -19,6 +20,8 @@ function loadDatabaseSync() {
19
20
  * - save = INSERT OR REPLACE(整行记录 JSON 存 json 列,last-wins);
20
21
  * - byIdempotency 取该键最近一次 save 的记录(按 rowid 倒序,失败重提产生的新任务胜出);
21
22
  * - list 按写入顺序返回;clear 清空整表;
23
+ * - `listDue(before)`(到期索引,2026-09-28 ⑤):派生列 `wake_at` + `(status, wake_at)`
24
+ * 索引,只回「在睡且到点」的记录(存量库构造期就地迁移:ALTER + 从 json 回填);
22
25
  * - 构造即就绪(建表 + 索引,无异步),重启续跑同样交给 AsyncRunner.resumePending()。
23
26
  *
24
27
  * 对比 FileTaskStore 的**单写者前提**:多进程安全由 SQLite WAL/事务天然保证 ——
@@ -26,6 +29,8 @@ function loadDatabaseSync() {
26
29
  */
27
30
  export class SqliteTaskStore {
28
31
  db;
32
+ /** 列自愈失败已告警过的 taskId(同一条记录每次读都会进自愈路径,去重防止刷屏) */
33
+ healWarned = new Set();
29
34
  constructor(path) {
30
35
  const DatabaseSync = loadDatabaseSync();
31
36
  this.db = new DatabaseSync(path);
@@ -51,26 +56,116 @@ export class SqliteTaskStore {
51
56
  )
52
57
  `);
53
58
  this.db.exec('CREATE INDEX IF NOT EXISTS idx_tasks_idempotency ON tasks(idempotency_key)');
59
+ // wake_at 派生列(2026-09-28 ⑤,到期索引):json 内 wakeAt 的反规范化副本,
60
+ // 专供 `listDue` 的索引过滤(status, wake_at)。
61
+ //
62
+ // 为什么是派生列而不是查询时 `json_extract`:json_extract 要对**扫到的每一行**解析
63
+ // JSON(O(全表) 的 C 版 —— 正是要消掉的那笔);派生列 + 索引才是 O(到期数)。
64
+ // 代价是与 status 列同类的「多一份真源」:save 时同步维护(单写点,见 save),
65
+ // 读时自愈判据扩到两列(见 healRow)。存量库走下面的**就地迁移**:
66
+ // CREATE TABLE IF NOT EXISTS 不会给旧表加列,所以查列 → 缺则 ALTER + 从 json 回填。
67
+ const cols = this.db.prepare('PRAGMA table_info(tasks)').all();
68
+ if (!cols.some((c) => c.name === 'wake_at')) {
69
+ this.db.exec('ALTER TABLE tasks ADD COLUMN wake_at INTEGER');
70
+ // 回填:旧行的 wakeAt 只在 json 里(不补的话旧库里的在睡任务从此醒不来 ——
71
+ // 那正是本列要服务的那批记录)
72
+ this.db.exec("UPDATE tasks SET wake_at = json_extract(json, '$.wakeAt') " +
73
+ "WHERE json_extract(json, '$.wakeAt') IS NOT NULL");
74
+ }
75
+ this.db.exec('CREATE INDEX IF NOT EXISTS idx_tasks_due ON tasks(status, wake_at)');
54
76
  }
55
77
  save(rec) {
56
78
  this.db
57
- .prepare('INSERT OR REPLACE INTO tasks (task_id, idempotency_key, status, json) VALUES (?, ?, ?, ?)')
58
- .run(rec.taskId, rec.idempotencyKey ?? null, rec.status, JSON.stringify(rec));
79
+ .prepare('INSERT OR REPLACE INTO tasks (task_id, idempotency_key, status, wake_at, json) VALUES (?, ?, ?, ?, ?)')
80
+ .run(rec.taskId, rec.idempotencyKey ?? null, rec.status, rec.wakeAt ?? null, JSON.stringify(rec));
81
+ }
82
+ /**
83
+ * 把派生列(`status` / `wake_at`)拉回与 json 一致(**只在不一致时写**,一次代价)。
84
+ *
85
+ * 为什么只有 SQLite 需要这一步:派生列是 json 的反规范化副本(`status` 供外部/DBA
86
+ * 按状态统计;`wake_at` 供 `listDue` 的索引过滤)。只在读时归一 json 的话,
87
+ * 那个统计/过滤会**继续**按旧值走 —— 于是「框架说一种事实、查询看到另一种」
88
+ * (对 `wake_at` 来说症状更脆:一条该醒的在睡任务从此醒不来)。
89
+ * fsStore / Redis 没有派生副本,读时归一对它们已经够了(写回时机是这条记录的下一次 save)。
90
+ *
91
+ * 判据是「列 ≠ json」而不是「刚才归一过没有」:任何原因(手改过、旧版本写的、将来又一次
92
+ * 改名)造成的漂移都该被顺手修掉,不依赖调用方传一个「我改过」的旗子。
93
+ */
94
+ healRow(storedStatus, storedWakeAt, rec) {
95
+ const wakeAt = rec.wakeAt ?? null;
96
+ if (storedStatus === rec.status && storedWakeAt === wakeAt)
97
+ return;
98
+ try {
99
+ this.db
100
+ .prepare('UPDATE tasks SET status = ?, wake_at = ?, json = ? WHERE task_id = ?')
101
+ .run(rec.status, wakeAt, JSON.stringify(rec), rec.taskId);
102
+ }
103
+ catch (err) {
104
+ // 只读库 / 他进程占锁超时:读照常返回归一后的记录,只有列这一处没自愈。**响亮一次**,
105
+ // 不静默 —— 否则「DBA 看到旧值」这件事没有任何痕迹(按 taskId 去重,见字段注释)。
106
+ if (!this.healWarned.has(rec.taskId)) {
107
+ this.healWarned.add(rec.taskId);
108
+ console.warn(`[agentia] 记录 ${rec.taskId} 的 status 列自愈失败(json 已按新形状读出):`, err);
109
+ }
110
+ }
59
111
  }
60
112
  get(taskId) {
61
- const row = this.db.prepare('SELECT json FROM tasks WHERE task_id = ?').get(taskId);
62
- return row ? JSON.parse(row.json) : undefined;
113
+ const row = this.db
114
+ .prepare('SELECT status, wake_at, json FROM tasks WHERE task_id = ?')
115
+ .get(taskId);
116
+ if (!row)
117
+ return undefined;
118
+ const rec = parseTaskRecord(row.json);
119
+ if (rec)
120
+ this.healRow(row.status, row.wake_at, rec);
121
+ return rec;
63
122
  }
64
123
  byIdempotency(key) {
65
124
  // last-wins:INSERT OR REPLACE 会删除旧行重插,rowid 最大者即最近一次 save
66
125
  const row = this.db
67
- .prepare('SELECT json FROM tasks WHERE idempotency_key = ? ORDER BY rowid DESC LIMIT 1')
126
+ .prepare('SELECT status, wake_at, json FROM tasks WHERE idempotency_key = ? ORDER BY rowid DESC LIMIT 1')
68
127
  .get(key);
69
- return row ? JSON.parse(row.json) : undefined;
128
+ if (!row)
129
+ return undefined;
130
+ const rec = parseTaskRecord(row.json);
131
+ if (rec)
132
+ this.healRow(row.status, row.wake_at, rec);
133
+ return rec;
70
134
  }
71
135
  list() {
72
- const rows = this.db.prepare('SELECT json FROM tasks ORDER BY rowid').all();
73
- return rows.map((r) => JSON.parse(r.json));
136
+ const rows = this.db
137
+ .prepare('SELECT status, wake_at, json FROM tasks ORDER BY rowid')
138
+ .all();
139
+ const out = [];
140
+ for (const r of rows) {
141
+ // 形状不合格的行**跳过**(不是记录);坏 JSON 照旧抛(与 get 口径一致)
142
+ const rec = parseTaskRecord(r.json);
143
+ if (!rec)
144
+ continue;
145
+ this.healRow(r.status, r.wake_at, rec);
146
+ out.push(rec);
147
+ }
148
+ return out;
149
+ }
150
+ /**
151
+ * 到期索引(2026-09-28 ⑤ 落地):只回「在睡且到点」的记录,走 `idx_tasks_due` 索引
152
+ * —— 代价是 O(到期数) 而不是 O(全表)。口径与 `wake-policy.ts` 的 `timerDue` 对齐
153
+ * (status + wake_at 两列;`suspendedReason` 那一半由调用方复核,见 #redispatch)。
154
+ */
155
+ listDue(before) {
156
+ const rows = this.db
157
+ .prepare('SELECT status, wake_at, json FROM tasks ' +
158
+ "WHERE status = 'suspended' AND wake_at IS NOT NULL AND wake_at <= ? ORDER BY rowid")
159
+ .all(before);
160
+ const out = [];
161
+ for (const r of rows) {
162
+ const rec = parseTaskRecord(r.json);
163
+ if (!rec)
164
+ continue;
165
+ this.healRow(r.status, r.wake_at, rec);
166
+ out.push(rec);
167
+ }
168
+ return out;
74
169
  }
75
170
  clear() {
76
171
  this.db.exec('DELETE FROM tasks');
@@ -1 +1 @@
1
- {"version":3,"file":"sqliteStore.js","sourceRoot":"","sources":["../../src/store/sqliteStore.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,4DAA4D;AAC5D,yDAAyD;AACzD,2DAA2D;AAC3D,2DAA2D;AAC3D,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAEtD,SAAS,gBAAgB;IACvB,IAAI,CAAC;QACH,OAAQ,cAAc,CAAC,aAAa,CAAkC,CAAC,YAAY,CAAC;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,eAAe;IACT,EAAE,CAAe;IAElC,YAAY,IAAY;QACtB,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;QACxC,IAAI,CAAC,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QACjC,gDAAgD;QAChD,0DAA0D;QAC1D,uDAAuD;QACvD,sDAAsD;QACtD,mCAAmC;QACnC,kCAAkC;QAClC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YACxB,gCAAgC;YAChC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAC5C,CAAC;QACD,4DAA4D;QAC5D,4EAA4E;QAC5E,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;;;KAOZ,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,4EAA4E,CAAC,CAAC;IAC7F,CAAC;IAED,IAAI,CAAC,GAAe;QAClB,IAAI,CAAC,EAAE;aACJ,OAAO,CACN,2FAA2F,CAC5F;aACA,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,cAAc,IAAI,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAClF,CAAC;IAED,GAAG,CAAC,MAAc;QAChB,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,MAAM,CAErE,CAAC;QACd,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAgB,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC;IAED,aAAa,CAAC,GAAW;QACvB,0DAA0D;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CAAC,8EAA8E,CAAC;aACvF,GAAG,CAAC,GAAG,CAAiC,CAAC;QAC5C,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAgB,CAAC,CAAC,CAAC,SAAS,CAAC;IAChE,CAAC;IAED,IAAI;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC,GAAG,EAEvE,CAAC;QACH,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAe,CAAC,CAAC;IAC3D,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACpC,CAAC;IAED,yCAAyC;IACzC,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
1
+ {"version":3,"file":"sqliteStore.js","sourceRoot":"","sources":["../../src/store/sqliteStore.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C,4DAA4D;AAC5D,yDAAyD;AACzD,2DAA2D;AAC3D,2DAA2D;AAC3D,MAAM,cAAc,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAEtD,SAAS,gBAAgB;IACvB,IAAI,CAAC;QACH,OAAQ,cAAc,CAAC,aAAa,CAAkC,CAAC,YAAY,CAAC;IACtF,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,OAAO,eAAe;IACT,EAAE,CAAe;IAClC,gDAAgD;IAC/B,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAEhD,YAAY,IAAY;QACtB,MAAM,YAAY,GAAG,gBAAgB,EAAE,CAAC;QACxC,IAAI,CAAC,EAAE,GAAG,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC;QACjC,gDAAgD;QAChD,0DAA0D;QAC1D,uDAAuD;QACvD,sDAAsD;QACtD,mCAAmC;QACnC,kCAAkC;QAClC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAC3C,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;YACxB,gCAAgC;YAChC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAC5C,CAAC;QACD,4DAA4D;QAC5D,4EAA4E;QAC5E,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;;;KAOZ,CAAC,CAAC;QACH,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,4EAA4E,CAAC,CAAC;QAC3F,wDAAwD;QACxD,uCAAuC;QACvC,EAAE;QACF,2DAA2D;QAC3D,mDAAmD;QACnD,kDAAkD;QAClD,yCAAyC;QACzC,kEAAkE;QAClE,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC,GAAG,EAA6B,CAAC;QAC1F,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,SAAS,CAAC,EAAE,CAAC;YAC5C,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC;YAC7D,+CAA+C;YAC/C,iBAAiB;YACjB,IAAI,CAAC,EAAE,CAAC,IAAI,CACV,4DAA4D;gBAC1D,kDAAkD,CACrD,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,oEAAoE,CAAC,CAAC;IACrF,CAAC;IAED,IAAI,CAAC,GAAe;QAClB,IAAI,CAAC,EAAE;aACJ,OAAO,CACN,uGAAuG,CACxG;aACA,GAAG,CACF,GAAG,CAAC,MAAM,EACV,GAAG,CAAC,cAAc,IAAI,IAAI,EAC1B,GAAG,CAAC,MAAM,EACV,GAAG,CAAC,MAAM,IAAI,IAAI,EAClB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CACpB,CAAC;IACN,CAAC;IAED;;;;;;;;;;;OAWG;IACK,OAAO,CAAC,YAA2B,EAAE,YAA2B,EAAE,GAAe;QACvF,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,IAAI,CAAC;QAClC,IAAI,YAAY,KAAK,GAAG,CAAC,MAAM,IAAI,YAAY,KAAK,MAAM;YAAE,OAAO;QACnE,IAAI,CAAC;YACH,IAAI,CAAC,EAAE;iBACJ,OAAO,CAAC,sEAAsE,CAAC;iBAC/E,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QAC9D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gDAAgD;YAChD,mDAAmD;YACnD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACrC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;gBAChC,OAAO,CAAC,IAAI,CACV,gBAAgB,GAAG,CAAC,MAAM,gCAAgC,EAC1D,GAAG,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;IACH,CAAC;IAED,GAAG,CAAC,MAAc;QAChB,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CAAC,2DAA2D,CAAC;aACpE,GAAG,CAAC,MAAM,CAAgF,CAAC;QAC9F,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,GAAG;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACpD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,aAAa,CAAC,GAAW;QACvB,0DAA0D;QAC1D,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE;aAChB,OAAO,CACN,+FAA+F,CAChG;aACA,GAAG,CAAC,GAAG,CAAgF,CAAC;QAC3F,IAAI,CAAC,GAAG;YAAE,OAAO,SAAS,CAAC;QAC3B,MAAM,GAAG,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,GAAG;YAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACpD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,IAAI;QACF,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CAAC,wDAAwD,CAAC;aACjE,GAAG,EAIJ,CAAC;QACH,MAAM,GAAG,GAAiB,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,6CAA6C;YAC7C,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACvC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,MAAc;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE;aACjB,OAAO,CACN,0CAA0C;YACxC,oFAAoF,CACvF;aACA,GAAG,CAAC,MAAM,CAIX,CAAC;QACH,MAAM,GAAG,GAAiB,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,MAAM,GAAG,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,CAAC,GAAG;gBAAE,SAAS;YACnB,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;YACvC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAChB,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IACpC,CAAC;IAED,yCAAyC;IACzC,KAAK;QACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAClB,CAAC;CACF"}
@@ -1,6 +1,6 @@
1
1
  import type { AgentRunResult } from '../engine/types.js';
2
2
  import type { SpanError } from '../core/trace.js';
3
- import type { RunStatus } from '../core/run.js';
3
+ import type { RunStatus, SuspendedReason, TaskEvent } from '../core/run.js';
4
4
  import type { ApprovalDecision } from '../core/tool.js';
5
5
  import type { RunSpec } from '../engine/spec.js';
6
6
  /**
@@ -45,13 +45,45 @@ export interface TaskRecord {
45
45
  /**
46
46
  * HITL:当前**待决**的 tool_use_id 列表(挂起时由引擎写进结果、宿主落库)。
47
47
  * 审批方据此知道该批哪些 id;决定齐了之后宿主把任务恢复执行。
48
+ * ⚠️ 时间挂起(`suspendedReason === 'timer'`)也写这个位 —— 装的是**请求延后**的那几条
49
+ * (它们是「在等」的那批;醒来重跑的是整个回合,见 `core/tool.ts` 的 `deferUntil` 契约②)。
48
50
  */
49
51
  pendingApprovals?: string[] | undefined;
50
52
  /**
51
53
  * HITL:进入挂起的时刻(epoch ms)。`approvalTimeoutMs` 的**惰性**判定与
52
54
  * `approval.decided` 事件的 `waitedMs` 都以它为基准。
53
55
  */
54
- approvalPendingSince?: number | undefined;
56
+ suspendedSince?: number | undefined;
57
+ /**
58
+ * 挂起原因(2026-09-28 ①):`approval` = 等人工决定、`timer` = 等一个时刻。
59
+ * **随记录落库**(与 `suspendedSince` 同批):进程重启后靠它决定用哪条闸 ——
60
+ * 审批超时只对 `approval` 成立,到期唤醒只对 `timer` 成立。
61
+ */
62
+ suspendedReason?: SuspendedReason | undefined;
63
+ /**
64
+ * 时间挂起的目标时刻(epoch ms,2026-09-28 ① 的 timer 侧):到点由到期扫描唤醒
65
+ * (`resumePending` / `poll` 的惰性路径,判据见 `transport/wake-policy.ts`)。
66
+ * **随记录落库** —— 「睡到后天下午三点接着跑」这件事只有它活得比进程久。
67
+ *
68
+ * 恒缺省的有两类:等审批的挂起(人什么时候批就是什么时候)与一切非挂起记录;
69
+ * 记录离开挂起态时一并清掉(`#executeInner` 的终态分支与两条恢复路径)。
70
+ */
71
+ wakeAt?: number | undefined;
72
+ /**
73
+ * 挂起期间投递、**尚未注入消息历史**的事件(2026-09-28 ⑥,run 事件投入口)。
74
+ * **随记录落库**(重启不丢;先落库再派发那条纪律的载体)。续跑段由引擎在未决
75
+ * tool_use 解决之后渲染成 user 文本消息注入(见 `engine/loop.ts` 的注入点纪律),
76
+ * 跑通(不再挂起)后由宿主清掉 —— 它们已进历史;再次挂起则**保留**(那次续跑
77
+ * 没有注入它们,清掉就是丢事件)。
78
+ */
79
+ pendingEvents?: TaskEvent[] | undefined;
80
+ /**
81
+ * 已投递事件的 eventId 簿记(幂等去重依据;**随记录落库** ⇒ 跨进程/重启不丢)。
82
+ * 有界:最多保留 `MAX_DELIVERED_EVENT_IDS`(`transport/async.ts`)条,超出 FIFO 裁
83
+ * 最旧 —— 它是去重簿记,不是审计日志;超出上限后同 id 重投会再进一次历史(如实:
84
+ * 簿记有界,恰好一次的承诺也就有界)。
85
+ */
86
+ deliveredEventIds?: string[] | undefined;
55
87
  }
56
88
  export interface TaskStore {
57
89
  save(rec: TaskRecord): MaybePromise<void>;
@@ -59,7 +91,38 @@ export interface TaskStore {
59
91
  /** 幂等键 → 最近一次任务(last-wins) */
60
92
  byIdempotency(key: string): MaybePromise<TaskRecord | undefined>;
61
93
  list(): MaybePromise<TaskRecord[]>;
94
+ /**
95
+ * **可选**:到期索引(2026-09-28 ⑤ 落地)—— 只回「`suspended` 且 `wakeAt <= before`」
96
+ * 的记录(`wakeAt` 缺失的不回)。`AsyncRunner` 的扫描在**到期唤醒那一半**用它替代全表
97
+ * 过滤(`listDue?.(now) ?? list()` 的输入),没有实现的 store 回退全表,语义不变。
98
+ *
99
+ * 实现与否的分判(spec §10 2026-09-28 ⑤ 的实测):`list()` 是**真查询**的 store
100
+ * (sqlite:10k 记录一轮 ~105ms 线性增长)值得实现;记录本就在内存的(InMemory /
101
+ * File —— list 只是返回 Map snapshot)**不实现**,回退路径就是它们的现状。
102
+ *
103
+ * ⚠️ 它**不能**替代 `list()` 本身:`resumePending` 的扫描还干别的事(挂起读数重建 /
104
+ * 审批超时 / 孤儿认领),那些职责的输入仍是全表。
105
+ */
106
+ listDue?(before: number): MaybePromise<TaskRecord[]>;
62
107
  clear(): MaybePromise<void>;
108
+ /**
109
+ * **可选**:压实底层存储(`FileTaskStore` 的 JSONL 压实:一 task 一行、丢掉历史覆写行)。
110
+ *
111
+ * 为什么进接口(2026-09-28 外部深评 S5):它此前只活在具体类上 —— 而宿主拿到的通常是
112
+ * **接口类型**(DI 注入、工厂返回、配置驱动地选 store 三种都是这形态),想周期性压实
113
+ * 只能 `as FileTaskStore` 强转;强转在换 store 时**不会报错**(sqlite 没有 compact),
114
+ * 要到运行期才炸。放进接口(保持**可选**)之后,调用点写 `store.compact?.()`:
115
+ * 「可能有、可能没有」在类型上就是显式的,每个实现「有没有」也一目了然。
116
+ */
117
+ compact?(): MaybePromise<void>;
118
+ /**
119
+ * **可选**:释放底层资源(`SqliteTaskStore` 的 `db.close()`)。
120
+ *
121
+ * 同样是为「宿主能多态调用」而进接口(S5),**不是**让框架自动调:
122
+ * `AsyncRunner.drain()` 刻意**不**关 store —— 同一个 store 可能被调度器 / 另一个宿主
123
+ * 共用,关掉它是**宿主的生命周期决定**(框架替它关 = 把别人还在用的东西关了)。
124
+ */
125
+ close?(): MaybePromise<void>;
63
126
  }
64
127
  export declare class InMemoryTaskStore implements TaskStore {
65
128
  private readonly byTask;
@@ -8,6 +8,9 @@ export function nextTaskId() {
8
8
  return `task_${randomUUID()}`;
9
9
  }
10
10
  export class InMemoryTaskStore {
11
+ // 刻意**不实现** `listDue`(到期索引):记录本就在内存 Map 里,`list()` 只是返回 snapshot ——
12
+ // 到期过滤的成本与全表相同,索引无利可省(实测与触发条件见 spec §10 2026-09-28 ⑤)。
13
+ // AsyncRunner 的扫描对缺失的 listDue 回退全表过滤,语义不变。
11
14
  byTask = new Map();
12
15
  byKey = new Map(); // idempotencyKey → taskId
13
16
  /** 记录条数上限;Infinity = 不限(缺省,保持旧行为) */
@@ -37,8 +40,8 @@ export class InMemoryTaskStore {
37
40
  for (const [taskId, rec] of this.byTask) {
38
41
  if (this.byTask.size <= this.maxRecords)
39
42
  break;
40
- // awaiting_approval 同样不可淘汰:它不在跑、但也没完 —— 淘汰了审批决定就无家可归
41
- if (rec.status === 'queued' || rec.status === 'running' || rec.status === 'awaiting_approval')
43
+ // suspended 同样不可淘汰:它不在跑、但也没完 —— 淘汰了审批决定就无家可归
44
+ if (rec.status === 'queued' || rec.status === 'running' || rec.status === 'suspended')
42
45
  continue;
43
46
  this.byTask.delete(taskId);
44
47
  if (rec.idempotencyKey && this.byKey.get(rec.idempotencyKey) === taskId) {
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAqBzC,oDAAoD;AACpD,MAAM,UAAU,UAAU,CAAI,CAAkB;IAC9C,OAAO,CAAC,CAAC,CAAC,IAAI,OAAQ,CAAgB,CAAC,IAAI,KAAK,UAAU,CAAC;AAC7D,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,UAAU;IACxB,OAAO,QAAQ,UAAU,EAAE,EAAE,CAAC;AAChC,CAAC;AA8CD,MAAM,OAAO,iBAAiB;IACX,MAAM,GAAG,IAAI,GAAG,EAAsB,CAAC;IACvC,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,0BAA0B;IAC9E,qCAAqC;IACpB,UAAU,CAAS;IAEpC;;;;;OAKG;IACH,YAAY,IAAI,GAA4B,EAAE;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,IAAI,MAAM,CAAC,iBAAiB,CAAC;QACxD,IAAI,GAAG,KAAK,MAAM,CAAC,iBAAiB,IAAI,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,KAAK,CACb,qDAAqD,IAAI,CAAC,UAAU,EAAE,CACvE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,UAAU,GAAG,GAAG,CAAC;IACxB,CAAC;IAED,IAAI,CAAC,GAAe;QAClB,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACjC,IAAI,GAAG,CAAC,cAAc;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;QACvE,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,IAAI,CAAC,UAAU;YAAE,IAAI,CAAC,KAAK,EAAE,CAAC;IACvD,CAAC;IAED,+CAA+C;IACvC,KAAK;QACX,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACxC,IAAI,IAAI,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU;gBAAE,MAAM;YAC/C,qDAAqD;YACrD,IAAI,GAAG,CAAC,MAAM,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,GAAG,CAAC,MAAM,KAAK,mBAAmB;gBAC3F,SAAS;YACX,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YAC3B,IAAI,GAAG,CAAC,cAAc,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,MAAM,EAAE,CAAC;gBACxE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;IACH,CAAC;IAED,GAAG,CAAC,MAAc;QAChB,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IACD,aAAa,CAAC,GAAW;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnC,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACtD,CAAC;IACD,IAAI;QACF,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,CAAC;IACD,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACpB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF"}
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/store/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Agentia —— 能力引用图的**成环检测**(装配期纯件,2026-09-28)。
3
+ *
4
+ * 图的两端:节点 = `@SubAgent` / `@Skill` 能力(`@Tool` / `@Prompt` 是**叶子** —— 它们的
5
+ * 声明里没有 `tools` 字段,不可能再引用别人,所以不进图);边 = 一个能力的 `tools` 引用
6
+ * 能到达的能力(`'token'` 整片引用 ⇒ 扇出到该 provider 上的**每一个**能力;
7
+ * `'token/名字'` ⇒ 只点名那一个)。
8
+ *
9
+ * **为什么要挡成环**:能力在运行期展开(`subagentToTool(capability, resolveTools)` 的第二个
10
+ * 参数是**延迟求值 thunk**,`wrappedByToken` 在装配期已全部建满)⇒ 一旦成环,模型点一次就是
11
+ * **无限递归**:`maxIterations` 只限每层的**宽度**,深度**没有任何闸**,递归树按
12
+ * 宽度^深度 炸开 —— token 与内存双爆,且要到运行期才显形。DI 容器有环检测
13
+ * (`container/container.ts`),能力图此前没有:同一类「配置错误该在装配期响亮失败」的
14
+ * 不变量,在能力层缺了第二处。
15
+ *
16
+ * 刻意**不**提供「递归深度上限」这类旋钮:检测即拒绝(装配期抛错),要支持真递归得先有
17
+ * 深度闸 —— 两件事必须同时做,先放开闸就是把这个坑重新打开(同款纪律见 `scheduler.ts`
18
+ * 那句「远端单发请拆成多次自检」)。
19
+ *
20
+ * 纯的边界:不碰 DI、不读装饰器注册表、不改任何东西 —— 输入是「收集好的引用表」,
21
+ * 输出是节点或环。所以它能在不装配 App 的情况下被单测。
22
+ */
23
+ /** 引用的两种形态(与 `module.ts` 的 `resolveRefTools` 同口径,不各写一份解析) */
24
+ export type CapabilityRefKind = 'subagent' | 'skill';
25
+ export interface CapabilityRefInput {
26
+ /** 宿主 provider token */
27
+ readonly token: string;
28
+ readonly kind: CapabilityRefKind;
29
+ /** 能力名(菜单名;`collect` 已校验为 `^[A-Za-z0-9_-]{1,64}$`,不含 `/`) */
30
+ readonly name: string;
31
+ /** `tools` 引用原文列表 */
32
+ readonly refs: readonly string[];
33
+ }
34
+ export interface CapabilityGraphNode {
35
+ /** 稳定标识。用 `\u0000` 分隔而不是 `#`/`/` —— token 是使用者给的任意字符串,那些字符不作保证 */
36
+ readonly id: string;
37
+ /** 给人看的标签(报错文案直接拼它,如 `@SubAgent "runner" (agents)`) */
38
+ readonly label: string;
39
+ /** 它能调用的**能力**节点 id(不含 @Tool/@Prompt:叶子不可能成环) */
40
+ readonly callees: readonly string[];
41
+ }
42
+ /** 引用表 → 有向图(节点顺序与输入一致;`callees` 只含能力节点) */
43
+ export declare function buildCapabilityGraph(inputs: readonly CapabilityRefInput[]): CapabilityGraphNode[];
44
+ /**
45
+ * 寻环(DFS + 三色标记)。返回**环上的节点序列**(首尾是同一个节点,便于直接拼成
46
+ * `a → b → a`);无环返回 `undefined`。
47
+ *
48
+ * 返回路径而不是布尔:这条错误会出现在使用者的启动日志里,他需要知道**从哪切**。
49
+ */
50
+ export declare function findCapabilityCycle(nodes: readonly CapabilityGraphNode[]): CapabilityGraphNode[] | undefined;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Agentia —— 能力引用图的**成环检测**(装配期纯件,2026-09-28)。
3
+ *
4
+ * 图的两端:节点 = `@SubAgent` / `@Skill` 能力(`@Tool` / `@Prompt` 是**叶子** —— 它们的
5
+ * 声明里没有 `tools` 字段,不可能再引用别人,所以不进图);边 = 一个能力的 `tools` 引用
6
+ * 能到达的能力(`'token'` 整片引用 ⇒ 扇出到该 provider 上的**每一个**能力;
7
+ * `'token/名字'` ⇒ 只点名那一个)。
8
+ *
9
+ * **为什么要挡成环**:能力在运行期展开(`subagentToTool(capability, resolveTools)` 的第二个
10
+ * 参数是**延迟求值 thunk**,`wrappedByToken` 在装配期已全部建满)⇒ 一旦成环,模型点一次就是
11
+ * **无限递归**:`maxIterations` 只限每层的**宽度**,深度**没有任何闸**,递归树按
12
+ * 宽度^深度 炸开 —— token 与内存双爆,且要到运行期才显形。DI 容器有环检测
13
+ * (`container/container.ts`),能力图此前没有:同一类「配置错误该在装配期响亮失败」的
14
+ * 不变量,在能力层缺了第二处。
15
+ *
16
+ * 刻意**不**提供「递归深度上限」这类旋钮:检测即拒绝(装配期抛错),要支持真递归得先有
17
+ * 深度闸 —— 两件事必须同时做,先放开闸就是把这个坑重新打开(同款纪律见 `scheduler.ts`
18
+ * 那句「远端单发请拆成多次自检」)。
19
+ *
20
+ * 纯的边界:不碰 DI、不读装饰器注册表、不改任何东西 —— 输入是「收集好的引用表」,
21
+ * 输出是节点或环。所以它能在不装配 App 的情况下被单测。
22
+ */
23
+ const idOf = (i) => `${i.token}\u0000${i.kind}\u0000${i.name}`;
24
+ const labelOf = (i) => `${i.kind === 'subagent' ? '@SubAgent' : '@Skill'} "${i.name}" (${i.token})`;
25
+ /** 引用表 → 有向图(节点顺序与输入一致;`callees` 只含能力节点) */
26
+ export function buildCapabilityGraph(inputs) {
27
+ const calleesOf = (refs) => {
28
+ const out = [];
29
+ for (const ref of refs) {
30
+ // 按第一个 '/' 切分(能力名不含 '/',见 collect 的校验)—— 与 resolveRefTools 同口径
31
+ const slash = ref.indexOf('/');
32
+ const token = slash === -1 ? ref : ref.slice(0, slash);
33
+ const name = slash === -1 ? undefined : ref.slice(slash + 1);
34
+ for (const target of inputs) {
35
+ if (target.token !== token)
36
+ continue;
37
+ // 整片 token(无名字段)⇒ 该 provider 上的每个能力都在子菜单里;
38
+ // 能力级路径 ⇒ 只点名同名的那个。指向 @Tool/@Prompt 的引用在这里自然落空(叶子)。
39
+ if (name === undefined || target.name === name)
40
+ out.push(idOf(target));
41
+ }
42
+ }
43
+ return out;
44
+ };
45
+ return inputs.map((i) => ({ id: idOf(i), label: labelOf(i), callees: calleesOf(i.refs) }));
46
+ }
47
+ /**
48
+ * 寻环(DFS + 三色标记)。返回**环上的节点序列**(首尾是同一个节点,便于直接拼成
49
+ * `a → b → a`);无环返回 `undefined`。
50
+ *
51
+ * 返回路径而不是布尔:这条错误会出现在使用者的启动日志里,他需要知道**从哪切**。
52
+ */
53
+ export function findCapabilityCycle(nodes) {
54
+ const byId = new Map(nodes.map((n) => [n.id, n]));
55
+ /** 1 = 在当前 DFS 路径上(灰);2 = 已判定无环(黑) */
56
+ const mark = new Map();
57
+ const path = [];
58
+ const visit = (node) => {
59
+ const seen = mark.get(node.id);
60
+ if (seen === 2)
61
+ return undefined;
62
+ if (seen === 1) {
63
+ // 撞回当前路径上的节点 ⇒ 环;从它**第一次**出现处截出来(前面那段是引线,不是环)
64
+ const at = path.findIndex((n) => n.id === node.id);
65
+ return [...path.slice(at), node];
66
+ }
67
+ mark.set(node.id, 1);
68
+ path.push(node);
69
+ for (const callee of node.callees) {
70
+ const next = byId.get(callee);
71
+ if (next === undefined)
72
+ continue; // 指向图外(不存在/非能力):不是环的来源
73
+ const found = visit(next);
74
+ if (found !== undefined)
75
+ return found;
76
+ }
77
+ path.pop();
78
+ mark.set(node.id, 2);
79
+ return undefined;
80
+ };
81
+ for (const node of nodes) {
82
+ const found = visit(node);
83
+ if (found !== undefined)
84
+ return found;
85
+ }
86
+ return undefined;
87
+ }
88
+ //# sourceMappingURL=capability-cycles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-cycles.js","sourceRoot":"","sources":["../../src/toolkit/capability-cycles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 能力切片 —— **四类能力在装配期的全部差异集中在这一张表里**(外部深评 K2 的收口)。
3
+ *
4
+ * ## 为什么需要它
5
+ *
6
+ * 这四类能力(`@Tool` / `@SubAgent` / `@Skill` / `@Prompt`)的装配差异此前**平行散落在
7
+ * `toolkit/module.ts` 的五处按类分支**里:收集(四个 Map)、可用名单(四路拼接)、
8
+ * `buildSlice`(四路展开)、`@Prompt` 版本表、孤儿能力计数(四路求和)。
9
+ * 后果不是「代码不好看」,而是**新增第五类能力时不会有任何东西报错**:漏改某一处只表现为
10
+ * 「那类能力静默不进菜单 / 不进版本表 / 不计入孤儿告警」,构建与全部用例照样全绿。
11
+ *
12
+ * 现在按类的那五处只剩**一次遍历**,而遍历的面是这张表派生的;新增一类能力的动作变成
13
+ * 「给 `CapabilityPayloads` 加一个成员 + 给 `KIND_SPEC` 加一行」——
14
+ * **`KIND_SPEC` 的类型是映射类型 `{[K in CapabilityKind]: CapabilityKindSpec<K>}` ⇒ 加了
15
+ * payload 不给表项,`tsc` 直接报 `TS2741`(缺属性)**(与 `SUCCESS_STOP_REASON` /
16
+ * `TERMINAL_STATUS` 同款护栏:加成员不表态就构建红,而不是「下次谁发现」)。
17
+ *
18
+ * ⚠️ 两处**刻意不在本表里**(别为了「看起来整齐」搬进来):
19
+ * - **菜单重名校验**(`module.ts` 最后那段):它跑在**合并后的工具列表**上,本来就与类别无关,
20
+ * 按类拆开反而要重新拼回去;
21
+ * - **能力引用图**(`capability-cycles`):只有 `@SubAgent` / `@Skill` 有 `tools` 引用,
22
+ * 另外两类没有「能力引用」这回事 —— 那是**语义上的不对称**,不是漏项。
23
+ */
24
+ import type { AgentTool } from '../core/tool.js';
25
+ import type { SubAgentCapability } from './subagent.js';
26
+ import type { SkillCapability } from './skill.js';
27
+ import type { CollectedPrompts } from './prompt.js';
28
+ /**
29
+ * 四类能力各自的**收集产物** —— 这张表(而不是别处)就是「一类能力」的定义。
30
+ * 加一个成员 = 加一类能力,代价是 `KIND_SPEC` 必须多一行(编译期强制)。
31
+ */
32
+ export interface CapabilityPayloads {
33
+ tool: AgentTool[];
34
+ subagent: SubAgentCapability[];
35
+ skill: SkillCapability[];
36
+ prompt: CollectedPrompts;
37
+ }
38
+ export type CapabilityKind = keyof CapabilityPayloads;
39
+ /** 一个 token 上收集到的**全部**类别(每类都在场,空数组也算在场 —— 缺席与空是两件事) */
40
+ export type CollectedByKind = {
41
+ [K in CapabilityKind]: CapabilityPayloads[K];
42
+ };
43
+ /** 解析 `tools` 引用(`'token'` / `'token/能力名'`)—— 由 `module.ts` 注入(它持有 DI 与包装后的菜单) */
44
+ export type ResolveRefTools = (owner: string, refs: string[] | undefined) => () => AgentTool[];
45
+ /**
46
+ * 一类能力在装配期的**全部差异**。四个方法各自对应 `module.ts` 里原先的一处按类分支;
47
+ * `versions` 可选是因为**只有 `@Prompt` 有资产版本**(另外三类没有「版本」这回事,
48
+ * 缺省 = 本类不参与版本表)。
49
+ */
50
+ export interface CapabilityKindSpec<K extends CapabilityKind> {
51
+ /** 从 provider 实例上收集本类能力(对应原先的 `collectX(inst)`) */
52
+ collect(inst: object): CapabilityPayloads[K];
53
+ /** 本类能力编译成的工具(对应 `buildSlice` 里那一路展开) */
54
+ toTools(payload: CapabilityPayloads[K], resolve: ResolveRefTools): AgentTool[];
55
+ /** 本类能力贡献的**工具名**(可用名单 / 与最终菜单名一致的口径) */
56
+ toolNames(payload: CapabilityPayloads[K]): string[];
57
+ /** 本类能力的条数(孤儿能力告警的口径:**能力**数,不是工具数) */
58
+ count(payload: CapabilityPayloads[K]): number;
59
+ /** 资产版本表(只有 `@Prompt` 有);缺省 = 本类不参与 */
60
+ versions?(payload: CapabilityPayloads[K]): Record<string, string> | undefined;
61
+ }
62
+ /**
63
+ * 注册表 —— **加一类能力就在这里加一行**。
64
+ *
65
+ * ⚠️ 类型是映射类型(每行各自收窄到自己的载荷类型),所以:
66
+ * ① 少了任何一行 ⇒ `TS2741`;② 行里的方法签名与载荷类型不匹配 ⇒ 当场报错;
67
+ * ③ 遍历代码(下面几个 helper)不需要跟着改 —— 它们从这张表派生。
68
+ */
69
+ export declare const KIND_SPEC: {
70
+ [K in CapabilityKind]: CapabilityKindSpec<K>;
71
+ };
72
+ /**
73
+ * 遍历顺序 = 这张表的键序(收集 / 展开 / 计数都用它)。
74
+ * **从表派生而不是另写一份清单** —— 否则「加了表项忘了加清单」又是一种静默漏项。
75
+ */
76
+ export declare const CAPABILITY_KINDS: CapabilityKind[];
77
+ /** 收集一个 provider 实例上的四类能力(每类都在场,空数组也算在场) */
78
+ export declare function collectCapabilities(inst: object): CollectedByKind;
79
+ /** 该 token 的**可用名单**(引用某个不存在的能力名时,报错文案里列的那串) */
80
+ export declare function capabilityToolNames(collected: CollectedByKind): string[];
81
+ /** 该 token 的能力切片(主菜单的一路;调用方再用中间件包装) */
82
+ export declare function buildCapabilitySlice(collected: CollectedByKind, resolve: ResolveRefTools): AgentTool[];
83
+ /** 该 token 上的**能力**条数(孤儿告警口径:能力数,不是编译出来的工具数) */
84
+ export declare function capabilityCount(collected: CollectedByKind): number;
85
+ /** 该 token 上的资产版本表(只有 `@Prompt` 有;无可版本化资产时返回 `undefined`) */
86
+ export declare function capabilityVersions(collected: CollectedByKind): Record<string, string> | undefined;
@@ -0,0 +1,98 @@
1
+ import { collectTools } from './tool.js';
2
+ import { collectSubAgents, subagentToTool } from './subagent.js';
3
+ import { collectSkills, skillToTool } from './skill.js';
4
+ import { collectPromptEntries } from './prompt.js';
5
+ /**
6
+ * 注册表 —— **加一类能力就在这里加一行**。
7
+ *
8
+ * ⚠️ 类型是映射类型(每行各自收窄到自己的载荷类型),所以:
9
+ * ① 少了任何一行 ⇒ `TS2741`;② 行里的方法签名与载荷类型不匹配 ⇒ 当场报错;
10
+ * ③ 遍历代码(下面几个 helper)不需要跟着改 —— 它们从这张表派生。
11
+ */
12
+ export const KIND_SPEC = {
13
+ tool: {
14
+ collect: (inst) => collectTools(inst),
15
+ toolNames: (tools) => tools.map((t) => t.name),
16
+ count: (tools) => tools.length,
17
+ toTools: (tools) => tools,
18
+ },
19
+ subagent: {
20
+ collect: (inst) => collectSubAgents(inst),
21
+ toolNames: (caps) => caps.map((c) => c.name),
22
+ count: (caps) => caps.length,
23
+ toTools: (caps, resolve) => caps.map((capability) => subagentToTool(capability, resolve(`@SubAgent "${capability.name}"`, capability.spec.tools))),
24
+ },
25
+ skill: {
26
+ collect: (inst) => collectSkills(inst),
27
+ toolNames: (caps) => caps.map((c) => c.name),
28
+ count: (caps) => caps.length,
29
+ toTools: (caps, resolve) => caps.map((capability) => skillToTool(capability, resolve(`@Skill "${capability.name}"`, capability.spec.tools))),
30
+ },
31
+ prompt: {
32
+ collect: (inst) => collectPromptEntries(inst),
33
+ toolNames: (p) => p.tools.map((t) => t.name),
34
+ count: (p) => p.tools.length,
35
+ toTools: (p) => p.tools,
36
+ versions: (p) => p.versions,
37
+ },
38
+ };
39
+ /**
40
+ * 遍历顺序 = 这张表的键序(收集 / 展开 / 计数都用它)。
41
+ * **从表派生而不是另写一份清单** —— 否则「加了表项忘了加清单」又是一种静默漏项。
42
+ */
43
+ export const CAPABILITY_KINDS = Object.keys(KIND_SPEC);
44
+ /**
45
+ * 按类遍历的**唯一**类型擦除点。
46
+ *
47
+ * 为什么必须有这一处:TS 无法把一个循环变量 `kind: CapabilityKind` 与「它对应的那个载荷类型」
48
+ * 关联起来(相关联合,microsoft/TypeScript#30581)—— 泛型在这里推不出来,只能擦除一次。
49
+ * 安全性由三件事共同兜住:① `KIND_SPEC` 是映射类型,**每一行在定义处就逐个收窄**(不是
50
+ * 在这里靠断言);② `CAPABILITY_KINDS` 从表派生 ⇒ 擦除的覆盖面就是表的覆盖面;
51
+ * ③ `tests/toolkit/capability-slice.test.ts` 用真数据逐类驱动(每类都真的被走到过)。
52
+ */
53
+ function specOf(kind) {
54
+ return KIND_SPEC[kind];
55
+ }
56
+ /** 收集一个 provider 实例上的四类能力(每类都在场,空数组也算在场) */
57
+ export function collectCapabilities(inst) {
58
+ // 逐类填满:`{}` 起步 + 下方立刻补齐全部键(键集 = CAPABILITY_KINDS = 表的键集)
59
+ const out = {};
60
+ const sink = out;
61
+ for (const kind of CAPABILITY_KINDS) {
62
+ sink[kind] = specOf(kind).collect(inst);
63
+ }
64
+ return out;
65
+ }
66
+ /** 该 token 的**可用名单**(引用某个不存在的能力名时,报错文案里列的那串) */
67
+ export function capabilityToolNames(collected) {
68
+ const out = [];
69
+ for (const kind of CAPABILITY_KINDS)
70
+ out.push(...specOf(kind).toolNames(collected[kind]));
71
+ return out;
72
+ }
73
+ /** 该 token 的能力切片(主菜单的一路;调用方再用中间件包装) */
74
+ export function buildCapabilitySlice(collected, resolve) {
75
+ const out = [];
76
+ for (const kind of CAPABILITY_KINDS)
77
+ out.push(...specOf(kind).toTools(collected[kind], resolve));
78
+ return out;
79
+ }
80
+ /** 该 token 上的**能力**条数(孤儿告警口径:能力数,不是编译出来的工具数) */
81
+ export function capabilityCount(collected) {
82
+ let n = 0;
83
+ for (const kind of CAPABILITY_KINDS)
84
+ n += specOf(kind).count(collected[kind]);
85
+ return n;
86
+ }
87
+ /** 该 token 上的资产版本表(只有 `@Prompt` 有;无可版本化资产时返回 `undefined`) */
88
+ export function capabilityVersions(collected) {
89
+ const out = {};
90
+ for (const kind of CAPABILITY_KINDS) {
91
+ const spec = specOf(kind);
92
+ if (!spec.versions)
93
+ continue; // 本类不参与版本表(缺省,见 CapabilityKindSpec.versions)
94
+ Object.assign(out, spec.versions(collected[kind]));
95
+ }
96
+ return Object.keys(out).length > 0 ? out : undefined;
97
+ }
98
+ //# sourceMappingURL=capability-slice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capability-slice.js","sourceRoot":"","sources":["../../src/toolkit/capability-slice.ts"],"names":[],"mappings":"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"}