talon-agent 4.6.1 → 5.0.1

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 (310) hide show
  1. package/README.md +3 -3
  2. package/package.json +2 -1
  3. package/prompts/README.md +2 -2
  4. package/prompts/system/memory-recall.md +16 -0
  5. package/src/app.ts +22 -7
  6. package/src/backend/claude-sdk/factory.ts +21 -41
  7. package/src/backend/claude-sdk/handler.ts +28 -7
  8. package/src/backend/claude-sdk/host/in-process.ts +147 -0
  9. package/src/backend/claude-sdk/one-shot.ts +1 -1
  10. package/src/backend/claude-sdk/options.ts +7 -6
  11. package/src/backend/claude-sdk/stream.ts +30 -3
  12. package/src/backend/claude-sdk/warm.ts +1 -1
  13. package/src/backend/codex/constants.ts +1 -1
  14. package/src/backend/codex/factory.ts +2 -2
  15. package/src/backend/codex/handler/events.ts +1 -1
  16. package/src/backend/codex/handler/message.ts +26 -12
  17. package/src/backend/codex/handler/rollout-accounting.ts +1 -1
  18. package/src/backend/codex/init.ts +1 -1
  19. package/src/backend/codex/mcp-config.ts +2 -2
  20. package/src/backend/codex/one-shot.ts +1 -1
  21. package/src/backend/kilo/handler/message.ts +4 -1
  22. package/src/backend/openai-agents/constants.ts +1 -1
  23. package/src/backend/openai-agents/factory.ts +2 -2
  24. package/src/backend/openai-agents/handler/events.ts +1 -1
  25. package/src/backend/openai-agents/handler/message.ts +8 -4
  26. package/src/backend/openai-agents/init.ts +1 -1
  27. package/src/backend/openai-agents/mcp-pool.ts +2 -2
  28. package/src/backend/opencode/handler/message.ts +4 -1
  29. package/src/backend/remote-server/chat-turn.ts +31 -23
  30. package/src/backend/remote-server/events.ts +3 -3
  31. package/src/backend/remote-server/factory.ts +6 -3
  32. package/src/backend/remote-server/index.ts +1 -1
  33. package/src/backend/remote-server/mcp.ts +1 -1
  34. package/src/backend/remote-server/one-shot.ts +1 -1
  35. package/src/backend/remote-server/server-bindings.ts +1 -1
  36. package/src/backend/remote-server/turn.ts +1 -1
  37. package/src/backend/runtime/cache/cache-metrics.ts +126 -0
  38. package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
  39. package/src/backend/{shared → runtime}/index.ts +34 -25
  40. package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
  41. package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
  42. package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
  43. package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
  44. package/src/backend/{shared → runtime/turn}/delivery.ts +3 -3
  45. package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
  46. package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
  47. package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
  48. package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
  49. package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
  50. package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
  51. package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
  52. package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
  53. package/src/bootstrap.ts +6 -21
  54. package/src/cli.ts +6 -3
  55. package/src/core/agent-runtime/README.md +15 -0
  56. package/src/core/agent-runtime/agent-host.ts +387 -0
  57. package/src/core/agent-runtime/capabilities.ts +11 -0
  58. package/src/core/agent-runtime/contract-tests.ts +92 -1
  59. package/src/core/agent-runtime/events.ts +1 -1
  60. package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
  61. package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
  62. package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
  63. package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
  64. package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
  65. package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
  66. package/src/core/background/{dream.ts → dream/index.ts} +10 -15
  67. package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
  68. package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
  69. package/src/core/background/triggers/command.ts +2 -2
  70. package/src/{util → core/config}/harden.ts +1 -1
  71. package/src/core/config/index.ts +16 -12
  72. package/src/{util → core/daemon}/boot-timer.ts +15 -1
  73. package/src/core/daemon/pidfile.ts +1 -1
  74. package/src/core/daemon/resource-sampler.ts +121 -0
  75. package/src/{util → core/daemon}/respawn.ts +1 -1
  76. package/src/core/doctor/index.ts +1 -1
  77. package/src/core/engine/dispatcher.ts +16 -0
  78. package/src/core/engine/gateway-actions/cron.ts +2 -2
  79. package/src/core/engine/gateway-actions/fetch-url.ts +1 -1
  80. package/src/core/engine/gateway-actions/index.ts +4 -1
  81. package/src/core/engine/gateway-actions/memory.ts +335 -0
  82. package/src/core/engine/gateway-actions/native/exec-background.ts +129 -0
  83. package/src/core/engine/gateway-actions/native/exec-remote.ts +85 -0
  84. package/src/core/engine/gateway-actions/native/exec.ts +182 -0
  85. package/src/core/engine/gateway-actions/native/index.ts +46 -0
  86. package/src/core/engine/gateway-actions/native/params.ts +46 -0
  87. package/src/core/engine/gateway-actions/native/read.ts +182 -0
  88. package/src/core/engine/gateway-actions/native/results.ts +34 -0
  89. package/src/core/engine/gateway-actions/native/search.ts +212 -0
  90. package/src/core/engine/gateway-actions/native/shell.ts +39 -0
  91. package/src/core/engine/gateway-actions/native/teleport.ts +55 -0
  92. package/src/core/engine/gateway-actions/native/write.ts +170 -0
  93. package/src/core/engine/gateway.ts +0 -6
  94. package/src/core/frontend-runtime/builtins.ts +2 -2
  95. package/src/{util → core/frontend-runtime}/chat-id.ts +30 -0
  96. package/src/core/mcp-hub/index.ts +1 -1
  97. package/src/core/mcp-hub/talon-server.ts +1 -1
  98. package/src/core/memory/import.ts +3 -2
  99. package/src/core/memory/taps.ts +199 -0
  100. package/src/core/memory/turn-retrieval.ts +222 -0
  101. package/src/core/plugin/mcp.ts +1 -1
  102. package/src/core/prompt/assemble.ts +2 -12
  103. package/src/core/prompt/index.ts +2 -2
  104. package/src/core/prompt/invalidation.ts +1 -1
  105. package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +1 -1
  106. package/src/core/tasks/index.ts +1 -1
  107. package/src/core/tools/{chat.ts → chat/chat.ts} +1 -1
  108. package/src/core/tools/{cross-send.ts → chat/cross-send.ts} +1 -1
  109. package/src/core/tools/{history.ts → chat/history.ts} +2 -2
  110. package/src/core/tools/{media.ts → chat/media.ts} +1 -1
  111. package/src/core/tools/{members.ts → chat/members.ts} +2 -2
  112. package/src/core/tools/{messaging.ts → chat/messaging.ts} +2 -2
  113. package/src/core/tools/{moderation.ts → chat/moderation.ts} +2 -2
  114. package/src/core/tools/{stickers.ts → chat/stickers.ts} +2 -2
  115. package/src/core/tools/{whatsapp.ts → chat/whatsapp.ts} +1 -1
  116. package/src/core/tools/content/memory.ts +128 -0
  117. package/src/core/tools/{web.ts → content/web.ts} +1 -1
  118. package/src/core/tools/index.ts +21 -19
  119. package/src/core/tools/{admin.ts → ops/admin.ts} +1 -1
  120. package/src/core/tools/{bridge.ts → ops/bridge.ts} +2 -2
  121. package/src/core/tools/{goals.ts → ops/goals.ts} +2 -2
  122. package/src/core/tools/{mesh.ts → ops/mesh.ts} +1 -1
  123. package/src/core/tools/{models.ts → ops/models.ts} +1 -1
  124. package/src/core/tools/{native.ts → ops/native.ts} +1 -1
  125. package/src/core/tools/{scheduling.ts → ops/scheduling.ts} +1 -1
  126. package/src/core/tools/{scripts.ts → ops/scripts.ts} +1 -1
  127. package/src/core/tools/{skills.ts → ops/skills.ts} +1 -1
  128. package/src/core/tools/{triggers.ts → ops/triggers.ts} +1 -1
  129. package/src/core/tools/types.ts +1 -0
  130. package/src/{util → core/vfs}/workspace.ts +2 -2
  131. package/src/core/weaver/turn-cpu.ts +32 -0
  132. package/src/core/weaver/weaver.ts +36 -0
  133. package/src/frontend/discord/admin.ts +1 -1
  134. package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
  135. package/src/frontend/discord/callbacks/components/effort.ts +1 -1
  136. package/src/frontend/discord/callbacks/components/index.ts +1 -1
  137. package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
  138. package/src/frontend/discord/callbacks/components/settings.ts +2 -2
  139. package/src/frontend/discord/callbacks/modals.ts +4 -1
  140. package/src/frontend/discord/commands/admin.ts +2 -36
  141. package/src/frontend/discord/commands/definitions.ts +0 -11
  142. package/src/frontend/discord/commands/info.ts +1 -1
  143. package/src/frontend/discord/commands/interaction.ts +1 -1
  144. package/src/frontend/discord/commands/router.ts +0 -3
  145. package/src/frontend/discord/commands/session.ts +8 -2
  146. package/src/frontend/discord/commands/settings.ts +2 -2
  147. package/src/frontend/discord/handlers/messages.ts +1 -1
  148. package/src/frontend/discord/handlers/state.ts +1 -1
  149. package/src/frontend/discord/middleware.ts +2 -26
  150. package/src/frontend/discord/ready.ts +1 -1
  151. package/src/frontend/discord/render.ts +41 -259
  152. package/src/frontend/discord/runtime.ts +1 -2
  153. package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
  154. package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
  155. package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
  156. package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
  157. package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
  158. package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
  159. package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
  160. package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
  161. package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
  162. package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
  163. package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
  164. package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
  165. package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
  166. package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
  167. package/src/frontend/native/index.ts +13 -10
  168. package/src/frontend/native/{media.ts → media/media.ts} +3 -3
  169. package/src/frontend/native/runtime.ts +1 -1
  170. package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
  171. package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
  172. package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
  173. package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
  174. package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
  175. package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
  176. package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
  177. package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
  178. package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
  179. package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
  180. package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
  181. package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
  182. package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
  183. package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
  184. package/src/frontend/{shared → presentation}/format.ts +4 -3
  185. package/src/frontend/{shared → presentation}/model-commands.ts +1 -1
  186. package/src/frontend/{shared → presentation}/reasoning-levels.ts +12 -0
  187. package/src/frontend/presentation/reports.ts +493 -0
  188. package/src/frontend/{shared → presentation}/session-status.ts +36 -9
  189. package/src/frontend/{shared → presentation}/status-context.ts +101 -2
  190. package/src/frontend/teams/commands.ts +2 -2
  191. package/src/frontend/teams/turn.ts +1 -1
  192. package/src/frontend/telegram/admin/background.ts +1 -1
  193. package/src/frontend/telegram/admin/health.ts +1 -1
  194. package/src/frontend/telegram/admin/sessions.ts +1 -1
  195. package/src/frontend/telegram/callbacks/effort.ts +2 -2
  196. package/src/frontend/telegram/callbacks/metrics.ts +1 -1
  197. package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
  198. package/src/frontend/telegram/callbacks/model/views.ts +1 -1
  199. package/src/frontend/telegram/callbacks/pulse.ts +1 -1
  200. package/src/frontend/telegram/callbacks/query.ts +1 -1
  201. package/src/frontend/telegram/callbacks/settings.ts +3 -3
  202. package/src/frontend/telegram/commands/admin.ts +6 -41
  203. package/src/frontend/telegram/commands/index.ts +1 -1
  204. package/src/frontend/telegram/commands/info.ts +2 -2
  205. package/src/frontend/telegram/commands/session.ts +9 -3
  206. package/src/frontend/telegram/commands/settings.ts +5 -3
  207. package/src/frontend/telegram/handlers/messages.ts +0 -10
  208. package/src/frontend/telegram/handlers/state.ts +1 -1
  209. package/src/frontend/telegram/index.ts +1 -5
  210. package/src/frontend/telegram/middleware.ts +1 -15
  211. package/src/frontend/telegram/model-menu.ts +1 -1
  212. package/src/frontend/telegram/render/html.ts +30 -0
  213. package/src/frontend/telegram/{helpers → render}/menu.ts +46 -19
  214. package/src/frontend/telegram/render/reports.ts +182 -0
  215. package/src/frontend/terminal/builtins/context.ts +2 -2
  216. package/src/frontend/terminal/builtins/session.ts +1 -1
  217. package/src/frontend/terminal/builtins/status.ts +2 -2
  218. package/src/frontend/terminal/index.ts +2 -2
  219. package/src/frontend/terminal/renderer.ts +1 -1
  220. package/src/frontend/whatsapp/access.ts +2 -2
  221. package/src/frontend/whatsapp/actions/history.ts +2 -2
  222. package/src/frontend/whatsapp/actions/messaging.ts +2 -2
  223. package/src/frontend/whatsapp/actions/moderation.ts +1 -1
  224. package/src/frontend/whatsapp/actions/send.ts +1 -1
  225. package/src/frontend/whatsapp/commands.ts +12 -6
  226. package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
  227. package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
  228. package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
  229. package/src/frontend/whatsapp/index.ts +4 -4
  230. package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
  231. package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
  232. package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
  233. package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
  234. package/src/frontend/whatsapp/registry.ts +2 -2
  235. package/src/frontend/whatsapp/runtime.ts +1 -1
  236. package/src/index.ts +6 -3
  237. package/src/storage/db.ts +6 -1
  238. package/src/storage/memory.ts +59 -8
  239. package/src/storage/metrics.ts +38 -0
  240. package/src/storage/repositories/sessions-repo.ts +6 -0
  241. package/src/storage/session-record.ts +13 -2
  242. package/src/storage/sessions.ts +12 -0
  243. package/src/storage/sql/db.sql +5 -0
  244. package/src/storage/sql/schema.sql +4 -0
  245. package/src/storage/sql/sessions.sql +3 -3
  246. package/src/storage/sql/statements.generated.ts +10 -3
  247. package/src/storage/sql/turn-meta.sql +1 -1
  248. package/src/util/log.ts +1 -1
  249. package/src/util/paths.ts +0 -2
  250. package/src/util/runtime.ts +1 -1
  251. package/src/core/engine/gateway-actions/native.ts +0 -1035
  252. package/src/core/soul/README.md +0 -110
  253. package/src/core/soul/RESEARCH.md +0 -98
  254. package/src/core/soul/associative.ts +0 -98
  255. package/src/core/soul/centrality.ts +0 -98
  256. package/src/core/soul/cluster.ts +0 -83
  257. package/src/core/soul/compiler.ts +0 -207
  258. package/src/core/soul/consolidate.ts +0 -179
  259. package/src/core/soul/critic.ts +0 -162
  260. package/src/core/soul/dag.ts +0 -265
  261. package/src/core/soul/delta.ts +0 -123
  262. package/src/core/soul/drift.ts +0 -99
  263. package/src/core/soul/embedder.ts +0 -129
  264. package/src/core/soul/emergent-critic.ts +0 -96
  265. package/src/core/soul/forgetting.ts +0 -131
  266. package/src/core/soul/governance.ts +0 -93
  267. package/src/core/soul/hash.ts +0 -97
  268. package/src/core/soul/hdc.ts +0 -154
  269. package/src/core/soul/kernel.ts +0 -540
  270. package/src/core/soul/lattice.ts +0 -103
  271. package/src/core/soul/lens.ts +0 -110
  272. package/src/core/soul/projector.ts +0 -240
  273. package/src/core/soul/reflect.ts +0 -170
  274. package/src/core/soul/reflex.ts +0 -164
  275. package/src/core/soul/retrieve.ts +0 -146
  276. package/src/core/soul/salience.ts +0 -146
  277. package/src/core/soul/service.ts +0 -204
  278. package/src/core/soul/settings.ts +0 -47
  279. package/src/core/soul/signals.ts +0 -117
  280. package/src/core/soul/talon-embedder.ts +0 -80
  281. package/src/core/soul/taps.ts +0 -199
  282. package/src/core/soul/types.ts +0 -298
  283. package/src/core/soul/valence.ts +0 -83
  284. package/src/frontend/telegram/helpers/diagnostics.ts +0 -428
  285. package/src/frontend/telegram/helpers/format.ts +0 -42
  286. package/src/frontend/telegram/helpers/index.ts +0 -13
  287. package/src/util/cleanup-registry.ts +0 -36
  288. /package/src/backend/{shared → runtime}/frontends.ts +0 -0
  289. /package/src/backend/{shared → runtime}/metrics.ts +0 -0
  290. /package/src/backend/{shared → runtime}/sleep.ts +0 -0
  291. /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
  292. /package/src/backend/{shared → runtime}/usage.ts +0 -0
  293. /package/src/{util/mcp-launcher.ts → core/mcp-hub/launcher.ts} +0 -0
  294. /package/src/{util → core/tools/content}/web-content.ts +0 -0
  295. /package/src/core/tools/{mcp-env.ts → ops/mcp-env.ts} +0 -0
  296. /package/src/{util → core/weaver}/session-name.ts +0 -0
  297. /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
  298. /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
  299. /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
  300. /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
  301. /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
  302. /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
  303. /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
  304. /package/src/frontend/{shared → presentation}/access.ts +0 -0
  305. /package/src/frontend/{shared → presentation}/plan-usage-report.ts +0 -0
  306. /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
  307. /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
  308. /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
  309. /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
  310. /package/src/frontend/whatsapp/{pins.ts → messages/pins.ts} +0 -0
@@ -1,123 +0,0 @@
1
- /**
2
- * Soul Kernel — the delta stream ("what changed in me, and why").
3
- *
4
- * One of the kernel's projection targets is a human-readable account of how the
5
- * soul changed between two versions. It is computed by diffing two DAG snapshots
6
- * and TEMPLATED from the structural difference — never model-written — so the
7
- * change log is as auditable as the identity itself. The kernel uses it to give
8
- * each commit a meaningful summary and to power a `/soul` introspection view.
9
- */
10
-
11
- import type { DagSnapshot } from "./dag.js";
12
- import type {
13
- Hash,
14
- NodePayload,
15
- SpinePayload,
16
- ThemePayload,
17
- ValuePayload,
18
- } from "./types.js";
19
-
20
- export interface SoulDelta {
21
- readonly addedValues: string[];
22
- readonly addedThemes: string[];
23
- readonly addedSpine: string[];
24
- readonly addedReflexes: string[];
25
- readonly addedLenses: string[];
26
- readonly supersededCount: number;
27
- readonly newTensions: number;
28
- }
29
-
30
- function payloadMap(snap: DagSnapshot): Map<Hash, NodePayload> {
31
- return new Map(snap.nodes.map((n) => [n.hash, n.payload]));
32
- }
33
-
34
- function evidenceText(map: Map<Hash, NodePayload>, hash: Hash): string {
35
- const p = map.get(hash);
36
- return p?.kind === "evidence" ? p.text : "";
37
- }
38
-
39
- function countEdges(snap: DagSnapshot, kind: string): number {
40
- return snap.edges.filter((e) => e.kind === kind).length;
41
- }
42
-
43
- /** Diff two snapshots into a structural delta. Append-only ⇒ only additions. */
44
- export function diffSnapshots(prev: DagSnapshot, curr: DagSnapshot): SoulDelta {
45
- const prevHashes = new Set(prev.nodes.map((n) => n.hash));
46
- const map = payloadMap(curr);
47
-
48
- const addedValues: string[] = [];
49
- const addedThemes: string[] = [];
50
- const addedSpine: string[] = [];
51
- const addedReflexes: string[] = [];
52
- const addedLenses: string[] = [];
53
-
54
- for (const node of curr.nodes) {
55
- if (prevHashes.has(node.hash)) continue;
56
- const p = node.payload;
57
- switch (p.kind) {
58
- case "value":
59
- addedValues.push(evidenceText(map, (p as ValuePayload).medoid));
60
- break;
61
- case "theme":
62
- addedThemes.push(
63
- (p as ThemePayload).insight ??
64
- evidenceText(map, (p as ThemePayload).medoid),
65
- );
66
- break;
67
- case "spine":
68
- addedSpine.push((p as SpinePayload).event);
69
- break;
70
- case "reflex":
71
- addedReflexes.push(p.name);
72
- break;
73
- case "lens":
74
- addedLenses.push(p.subject);
75
- break;
76
- default:
77
- break;
78
- }
79
- }
80
-
81
- return {
82
- addedValues,
83
- addedThemes,
84
- addedSpine,
85
- addedReflexes,
86
- addedLenses,
87
- supersededCount:
88
- countEdges(curr, "supersedes") - countEdges(prev, "supersedes"),
89
- newTensions: countEdges(curr, "tension") - countEdges(prev, "tension"),
90
- };
91
- }
92
-
93
- /** Render a delta as a compact, templated one-paragraph summary. */
94
- export function renderDelta(delta: SoulDelta): string {
95
- const parts: string[] = [];
96
- if (delta.addedValues.length)
97
- parts.push(`+${delta.addedValues.length} value(s)`);
98
- if (delta.addedThemes.length)
99
- parts.push(`+${delta.addedThemes.length} theme(s)`);
100
- if (delta.supersededCount > 0) parts.push(`${delta.supersededCount} merged`);
101
- if (delta.newTensions > 0) parts.push(`+${delta.newTensions} tension(s)`);
102
- if (delta.addedReflexes.length)
103
- parts.push(`+reflex ${delta.addedReflexes.join(", ")}`);
104
- if (delta.addedLenses.length)
105
- parts.push(`+lens ${delta.addedLenses.join(", ")}`);
106
- if (delta.addedSpine.length) parts.push(`+${delta.addedSpine.length} spine`);
107
- return parts.length ? parts.join(", ") : "no structural change";
108
- }
109
-
110
- /** A fuller multi-line introspection of the delta (for a `/soul` view). */
111
- export function explainDelta(delta: SoulDelta): string {
112
- const lines: string[] = [];
113
- for (const v of delta.addedValues) lines.push(`+ value: "${v}"`);
114
- for (const t of delta.addedThemes) lines.push(`+ theme: ${t}`);
115
- for (const s of delta.addedSpine) lines.push(`+ spine: ${s}`);
116
- for (const r of delta.addedReflexes) lines.push(`+ reflex: ${r}`);
117
- for (const l of delta.addedLenses) lines.push(`+ lens: ${l}`);
118
- if (delta.supersededCount > 0)
119
- lines.push(`~ ${delta.supersededCount} value(s) merged away`);
120
- if (delta.newTensions > 0)
121
- lines.push(`~ ${delta.newTensions} new tension(s)`);
122
- return lines.length ? lines.join("\n") : "no structural change";
123
- }
@@ -1,99 +0,0 @@
1
- /**
2
- * Soul Kernel — concept-drift detection (ADWIN).
3
- *
4
- * Bifet & Gavaldà, "Learning from Time-Changing Data with Adaptive Windowing"
5
- * (2007). ADWIN keeps a window of recent observations and, whenever the means of
6
- * an older and a newer sub-window differ beyond a variance-aware Hoeffding bound,
7
- * declares a change and forgets the stale older half. It needs no fixed window
8
- * size — the window adapts to how stationary the stream is.
9
- *
10
- * The soul feeds it the scalar outcome of interactions (the valence of what
11
- * happens). A detected change is a genuine shift in how Talon's behavior is
12
- * landing — a developmental inflection — which the kernel records as an epoch in
13
- * the Spine. Pure statistics; model-free.
14
- *
15
- * This is the readable ADWIN0 variant (exhaustive cut search over a flat window)
16
- * rather than the bucketed/exponential-histogram optimization, which is faithful
17
- * to the test and bound but O(n) per insert — fine at the soul's data rate.
18
- */
19
-
20
- export interface AdwinChange {
21
- /** True on the insert that triggered a change. */
22
- readonly changed: boolean;
23
- /** Mean before the cut (the era being forgotten), when changed. */
24
- readonly meanBefore?: number;
25
- /** Mean after the cut (the new era), when changed. */
26
- readonly meanAfter?: number;
27
- }
28
-
29
- export class Adwin {
30
- private window: number[] = [];
31
-
32
- /** @param delta confidence; smaller = fewer false alarms (default 0.002). */
33
- constructor(private readonly delta = 0.002) {}
34
-
35
- get width(): number {
36
- return this.window.length;
37
- }
38
-
39
- get total(): number {
40
- return this.window.reduce((s, x) => s + x, 0);
41
- }
42
-
43
- get mean(): number {
44
- return this.window.length ? this.total / this.window.length : 0;
45
- }
46
-
47
- private static stats(xs: number[]): { mean: number; variance: number } {
48
- const n = xs.length;
49
- if (n === 0) return { mean: 0, variance: 0 };
50
- const mean = xs.reduce((s, x) => s + x, 0) / n;
51
- const variance = xs.reduce((s, x) => s + (x - mean) ** 2, 0) / n;
52
- return { mean, variance };
53
- }
54
-
55
- /**
56
- * Add an observation. Searches every split; if a split's sub-window means
57
- * differ beyond the variance-aware Hoeffding bound, drops the older half and
58
- * reports the change.
59
- */
60
- add(value: number): AdwinChange {
61
- this.window.push(value);
62
- const n = this.window.length;
63
- if (n < 2) return { changed: false };
64
-
65
- const wholeVar = Adwin.stats(this.window).variance;
66
-
67
- for (let cut = 1; cut < n; cut++) {
68
- const left = this.window.slice(0, cut);
69
- const right = this.window.slice(cut);
70
- const n0 = left.length;
71
- const n1 = right.length;
72
- const m0 = left.reduce((s, x) => s + x, 0) / n0;
73
- const m1 = right.reduce((s, x) => s + x, 0) / n1;
74
-
75
- // Harmonic window size and the ADWIN variance-aware bound.
76
- const m = 1 / (1 / n0 + 1 / n1);
77
- const deltaPrime = this.delta / n;
78
- const eps =
79
- Math.sqrt((2 / m) * wholeVar * Math.log(2 / deltaPrime)) +
80
- (2 / (3 * m)) * Math.log(2 / deltaPrime);
81
-
82
- if (Math.abs(m0 - m1) > eps) {
83
- this.window = right; // forget the stale older era
84
- return { changed: true, meanBefore: m0, meanAfter: m1 };
85
- }
86
- }
87
- return { changed: false };
88
- }
89
-
90
- snapshot(): readonly number[] {
91
- return [...this.window];
92
- }
93
-
94
- static restore(window: readonly number[], delta = 0.002): Adwin {
95
- const a = new Adwin(delta);
96
- a.window = [...window];
97
- return a;
98
- }
99
- }
@@ -1,129 +0,0 @@
1
- /**
2
- * Soul Kernel — the embedder boundary and the vector math.
3
- *
4
- * The embedder is the ONLY neural component the kernel may touch, and it is a
5
- * frozen fixed function, not a reasoning agent — so it stays inside the
6
- * model-free principle. It is injected: production wires MemPalace's local
7
- * embedding model; tests and offline operation use the deterministic
8
- * `HashingEmbedder` below.
9
- *
10
- * Everything the kernel does *with* embeddings — clustering evidence into values,
11
- * picking a medoid label, measuring contradiction/drift — is plain vector
12
- * arithmetic implemented here. No model decides what relates to what; geometry
13
- * does.
14
- */
15
-
16
- import { createHash } from "node:crypto";
17
-
18
- /** A pluggable text→vector function. Vectors should be L2-normalized. */
19
- export interface Embedder {
20
- readonly dim: number;
21
- embed(texts: readonly string[]): Promise<number[][]>;
22
- }
23
-
24
- // ── Vector math ──────────────────────────────────────────────────────────────
25
-
26
- function dot(a: readonly number[], b: readonly number[]): number {
27
- let s = 0;
28
- for (let i = 0; i < a.length; i++) s += a[i]! * b[i]!;
29
- return s;
30
- }
31
-
32
- function norm(a: readonly number[]): number {
33
- return Math.sqrt(dot(a, a));
34
- }
35
-
36
- /** L2-normalize a vector; a zero vector is returned unchanged. */
37
- export function normalize(a: readonly number[]): number[] {
38
- const n = norm(a);
39
- if (n === 0) return [...a];
40
- return a.map((x) => x / n);
41
- }
42
-
43
- /** Cosine similarity in [-1, 1] (assumes finite inputs). */
44
- export function cosineSimilarity(
45
- a: readonly number[],
46
- b: readonly number[],
47
- ): number {
48
- const na = norm(a);
49
- const nb = norm(b);
50
- if (na === 0 || nb === 0) return 0;
51
- return dot(a, b) / (na * nb);
52
- }
53
-
54
- /** Cosine distance in [0, 2]. 0 = identical direction. */
55
- export function cosineDistance(
56
- a: readonly number[],
57
- b: readonly number[],
58
- ): number {
59
- return 1 - cosineSimilarity(a, b);
60
- }
61
-
62
- /** Mean vector of a set, L2-normalized. The cluster centroid. */
63
- export function centroid(vectors: readonly (readonly number[])[]): number[] {
64
- if (vectors.length === 0) return [];
65
- const dim = vectors[0]!.length;
66
- const acc = Array.from({ length: dim }, () => 0);
67
- for (const v of vectors) {
68
- for (let i = 0; i < dim; i++) acc[i]! += v[i]!;
69
- }
70
- for (let i = 0; i < dim; i++) acc[i]! /= vectors.length;
71
- return normalize(acc);
72
- }
73
-
74
- /**
75
- * Index of the medoid: the member with minimum total cosine distance to all
76
- * others — the single most central *real* item. This is what labels a value:
77
- * never generated prose, always a genuine fragment.
78
- */
79
- export function medoidIndex(vectors: readonly (readonly number[])[]): number {
80
- if (vectors.length === 0) return -1;
81
- let best = 0;
82
- let bestSum = Infinity;
83
- for (let i = 0; i < vectors.length; i++) {
84
- let sum = 0;
85
- for (let j = 0; j < vectors.length; j++) {
86
- if (i !== j) sum += cosineDistance(vectors[i]!, vectors[j]!);
87
- }
88
- if (sum < bestSum) {
89
- bestSum = sum;
90
- best = i;
91
- }
92
- }
93
- return best;
94
- }
95
-
96
- // ── Deterministic offline embedder ───────────────────────────────────────────
97
-
98
- /**
99
- * A frozen, dependency-free embedder via feature hashing over character n-grams
100
- * and word tokens. Deterministic and model-free: similar strings share n-grams
101
- * and therefore land near each other in cosine space. Crude next to a real
102
- * sentence encoder, but it is a genuine fixed function — suitable for tests,
103
- * offline operation, and as a fallback when the local model is unavailable.
104
- */
105
- export class HashingEmbedder implements Embedder {
106
- constructor(readonly dim = 256) {}
107
-
108
- async embed(texts: readonly string[]): Promise<number[][]> {
109
- return texts.map((t) => this.embedOne(t));
110
- }
111
-
112
- private embedOne(text: string): number[] {
113
- const vec = Array.from({ length: this.dim }, () => 0);
114
- const lower = text.toLowerCase();
115
- const add = (feature: string): void => {
116
- const h = createHash("md5").update(feature).digest();
117
- const idx = ((h[0]! << 8) | h[1]!) % this.dim;
118
- const sign = (h[2]! & 1) === 0 ? 1 : -1; // sign hashing reduces bias
119
- vec[idx]! += sign;
120
- };
121
- // word tokens
122
- for (const w of lower.split(/[^a-z0-9]+/).filter(Boolean)) add(`w:${w}`);
123
- // character 3-grams over the normalized stream
124
- const stream = lower.replace(/\s+/g, " ");
125
- for (let i = 0; i + 3 <= stream.length; i++)
126
- add(`g:${stream.slice(i, i + 3)}`);
127
- return normalize(vec);
128
- }
129
- }
@@ -1,96 +0,0 @@
1
- /**
2
- * Soul Kernel — the emergent critic.
3
- *
4
- * The lexical Critic (critic.ts) hardcodes three failure modes and their word
5
- * lists. That is a useful bootstrap, but it is exactly the kind of "overly
6
- * programmed and specific" thing the soul should grow past. The emergent critic
7
- * derives failure modes from data: corrections carry negative valence, and
8
- * clusters of corrections ARE the failure modes — discovered, not declared.
9
- *
10
- * To assess a candidate reply, embed it and measure its proximity to those
11
- * correction clusters. If it lands near a cluster of past corrections, it is at
12
- * risk of repeating that mistake — and the warning is a real past correction
13
- * (the cluster's medoid), not an invented category. As Dylan corrects Talon, new
14
- * failure modes appear on their own; nothing here is hand-authored.
15
- */
16
-
17
- import { clusterEvidence } from "./cluster.js";
18
- import type { SoulDag } from "./dag.js";
19
- import { cosineDistance, type Embedder } from "./embedder.js";
20
- import type { EvidencePayload, Hash } from "./types.js";
21
-
22
- export interface EmergentFailureMode {
23
- /** A real past correction representing the cluster — never generated. */
24
- readonly label: string;
25
- readonly medoid: Hash;
26
- readonly centroid: readonly number[];
27
- /** Number of corrections in this mode. */
28
- readonly size: number;
29
- }
30
-
31
- export interface FailureRisk {
32
- readonly label: string;
33
- readonly distance: number;
34
- readonly atRisk: boolean;
35
- }
36
-
37
- function correctionEvidence(dag: SoulDag): { hash: Hash; text: string }[] {
38
- const out: { hash: Hash; text: string }[] = [];
39
- for (const node of dag.nodesOfKind("evidence")) {
40
- const p = node.payload as EvidencePayload;
41
- if (p.source.origin === "correction")
42
- out.push({ hash: node.hash, text: p.text });
43
- }
44
- return out;
45
- }
46
-
47
- /**
48
- * Derive failure modes by clustering Talon's actual corrections. `threshold` is
49
- * the cluster tightness; each resulting cluster is one emergent failure mode,
50
- * labeled by its medoid (a representative real correction).
51
- */
52
- export async function deriveFailureModes(
53
- dag: SoulDag,
54
- embedder: Embedder,
55
- threshold: number,
56
- ): Promise<EmergentFailureMode[]> {
57
- const corrections = correctionEvidence(dag);
58
- if (corrections.length === 0) return [];
59
-
60
- const vectors = await embedder.embed(corrections.map((c) => c.text));
61
- const embedded = corrections.map((c, i) => ({
62
- hash: c.hash,
63
- vector: vectors[i]!,
64
- }));
65
- const clusters = clusterEvidence(embedded, threshold);
66
-
67
- const textByHash = new Map(corrections.map((c) => [c.hash, c.text]));
68
- return clusters.map((cl) => ({
69
- label: textByHash.get(cl.medoid) ?? "",
70
- medoid: cl.medoid,
71
- centroid: cl.centroid,
72
- size: cl.members.length,
73
- }));
74
- }
75
-
76
- /**
77
- * Assess a candidate reply against the emergent failure modes. Returns the risk
78
- * per mode (sorted nearest first); `atRisk` when the candidate sits within
79
- * `riskDistance` of a correction cluster — i.e. it resembles something Talon was
80
- * already corrected for.
81
- */
82
- export async function assessText(
83
- text: string,
84
- modes: readonly EmergentFailureMode[],
85
- embedder: Embedder,
86
- riskDistance: number,
87
- ): Promise<FailureRisk[]> {
88
- if (modes.length === 0) return [];
89
- const [vec] = await embedder.embed([text]);
90
- return modes
91
- .map((m) => {
92
- const distance = cosineDistance(vec!, m.centroid);
93
- return { label: m.label, distance, atRisk: distance <= riskDistance };
94
- })
95
- .sort((a, b) => a.distance - b.distance);
96
- }
@@ -1,131 +0,0 @@
1
- /**
2
- * Soul Kernel — adaptive forgetting (FSRS / DSR-inspired).
3
- *
4
- * Fixed-half-life exponential decay treats a trait recalled a hundred times the
5
- * same as one recalled once. Human memory does not work that way, and neither
6
- * should the soul. This module implements a Difficulty-Stability-Retrievability
7
- * style model (Wozniak's DSR; the basis of FSRS, the algorithm modern Anki uses),
8
- * adapted to continuous time:
9
- *
10
- * - Each node carries a STABILITY S (a time constant). Retrievability follows a
11
- * POWER LAW, R(t) = (1 + FACTOR·t/S)^DECAY, which fits human forgetting far
12
- * better than an exponential (Wickelgren; FSRS). At t = S, R ≈ 0.9.
13
- * - Successful recall GROWS stability, and grows it MORE when the recall
14
- * happened at low retrievability (the spacing effect) — so a trait that keeps
15
- * proving itself across time becomes durable. A correction (negative valence)
16
- * SHRINKS stability, so a discredited trait becomes easy to forget.
17
- *
18
- * The effective strength used for projection is salience × retrievability. The
19
- * net effect: core identity persists, fads evaporate — emergently, from the
20
- * timing and outcome of activations, not a hand-set constant.
21
- */
22
-
23
- import type { ActivationState, NodeKind, SoulConfig } from "./types.js";
24
- import { halfLifeForKind } from "./types.js";
25
-
26
- // FSRS power-law constants: R = (1 + FACTOR·t/S)^DECAY, with R(S) ≈ 0.9.
27
- const DECAY = -0.5;
28
- const FACTOR = 19 / 81;
29
-
30
- const MIN_STABILITY = 1; // never let a node become un-forgettable-fast to 0
31
- const GROWTH = 2.0; // spacing-effect stability gain scale
32
- const LAPSE = 0.5; // stability multiplier on a correction
33
-
34
- /** Power-law retrievability in (0, 1]. */
35
- export function retrievability(elapsedMs: number, stability: number): number {
36
- if (elapsedMs <= 0) return 1;
37
- const s = Math.max(MIN_STABILITY, stability);
38
- return Math.pow(1 + (FACTOR * elapsedMs) / s, DECAY);
39
- }
40
-
41
- /**
42
- * Initial stability for a node's first exposure, honoring the per-kind
43
- * half-life when the kind is known. No-decay kinds (infinite half-life) never
44
- * reach the FSRS path, but fall back to the base rate defensively so an
45
- * Infinity is never stored into state (it would not survive JSON persistence).
46
- */
47
- export function initialStability(cfg: SoulConfig, kind?: NodeKind): number {
48
- const hl = halfLifeForKind(cfg, kind);
49
- return Number.isFinite(hl) ? hl : cfg.decayHalfLifeMs;
50
- }
51
-
52
- /**
53
- * Stability after a recall. Positive valence grows it (more when retrievability
54
- * was low — the spacing effect); negative valence (a correction) shrinks it.
55
- */
56
- export function nextStability(
57
- stability: number,
58
- r: number,
59
- valence: number,
60
- ): number {
61
- const s = Math.max(MIN_STABILITY, stability);
62
- if (valence < 0) return Math.max(MIN_STABILITY, s * LAPSE);
63
- // grow more when r is low (recall that "shouldn't" have succeeded teaches most)
64
- const gain = 1 + GROWTH * (1 - r) * Math.max(0.25, Math.min(1, valence));
65
- return s * gain;
66
- }
67
-
68
- /**
69
- * Salience after lazy power-law decay to `now`, using the node's stability when
70
- * present. Falls back to the caller's exponential decay when stability is unset.
71
- * A no-decay kind (infinite half-life, e.g. reflexes) returns the stored
72
- * salience untouched regardless of stability.
73
- */
74
- export function effectiveStrength(
75
- state: ActivationState,
76
- now: number,
77
- cfg: SoulConfig,
78
- kind?: NodeKind,
79
- ): number {
80
- const halfLifeMs = halfLifeForKind(cfg, kind);
81
- if (!Number.isFinite(halfLifeMs)) return state.salience;
82
- if (state.stability === undefined) {
83
- // exponential fallback (mirrors salience.effectiveSalience)
84
- const f = Math.pow(
85
- 0.5,
86
- Math.max(0, now - state.lastActivatedAt) / halfLifeMs,
87
- );
88
- return state.salience * f;
89
- }
90
- return (
91
- state.salience *
92
- retrievability(now - state.lastActivatedAt, state.stability)
93
- );
94
- }
95
-
96
- export interface FsrsReinforceOptions {
97
- readonly now: number;
98
- readonly cfg: SoulConfig;
99
- readonly amount: number;
100
- readonly valence: number;
101
- /** Node kind, when known — selects the per-kind decay half-life. */
102
- readonly kind?: NodeKind;
103
- }
104
-
105
- /**
106
- * Reinforcement under adaptive forgetting: decays salience by retrievability,
107
- * adds the increment, accumulates evidence, and updates stability per the
108
- * spacing/lapse rule. Mutates and returns the state. No-decay kinds (infinite
109
- * half-life) accumulate undecayed and skip stability bookkeeping entirely —
110
- * their strength is time-invariant by construction.
111
- */
112
- export function reinforceFsrs(
113
- state: ActivationState,
114
- opts: FsrsReinforceOptions,
115
- ): ActivationState {
116
- if (!Number.isFinite(halfLifeForKind(opts.cfg, opts.kind))) {
117
- state.salience += opts.amount;
118
- state.evidence += opts.valence;
119
- state.activations += 1;
120
- state.lastActivatedAt = opts.now;
121
- return state;
122
- }
123
- const stability = state.stability ?? initialStability(opts.cfg, opts.kind);
124
- const r = retrievability(opts.now - state.lastActivatedAt, stability);
125
- state.salience = state.salience * r + opts.amount;
126
- state.evidence += opts.valence;
127
- state.stability = nextStability(stability, r, opts.valence);
128
- state.activations += 1;
129
- state.lastActivatedAt = opts.now;
130
- return state;
131
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * Soul Kernel — governance (the protected-node approval queue).
3
- *
4
- * Most of the soul grows autonomously; that is the point. But some facets are
5
- * load-bearing enough that drift must never happen silently — the reflexes that
6
- * the harness enforces, and any change to core identity. The mechanical compiler
7
- * may *propose* such mutations, but they do not take effect until a human
8
- * approves them. This is the concrete machinery behind "identity cannot drift
9
- * behind your back".
10
- *
11
- * The queue is pure data (persisted with the kernel). Proposing enqueues a
12
- * payload + reason; approving returns the payload to the kernel to materialize;
13
- * rejecting drops it. Nothing here writes to the DAG — the kernel owns that — so
14
- * the queue stays a simple, auditable ledger.
15
- */
16
-
17
- import { randomUUID } from "node:crypto";
18
- import { hashPayload } from "./hash.js";
19
- import type { NodePayload } from "./types.js";
20
-
21
- type ProposalStatus = "pending" | "approved" | "rejected";
22
-
23
- export interface Proposal {
24
- readonly id: string;
25
- readonly at: number;
26
- /** Content hash of the proposed node — stable identity for dedup. */
27
- readonly target: string;
28
- readonly payload: NodePayload;
29
- /** Why the compiler proposed this (templated, never model-written). */
30
- readonly reason: string;
31
- status: ProposalStatus;
32
- resolvedAt?: number;
33
- }
34
-
35
- export interface ApprovalSnapshot {
36
- readonly proposals: readonly Proposal[];
37
- }
38
-
39
- export class ApprovalQueue {
40
- private proposals: Proposal[] = [];
41
-
42
- /**
43
- * Enqueue a proposed protected mutation. Idempotent on payload content: a
44
- * pending proposal for the same node is returned rather than duplicated.
45
- */
46
- propose(payload: NodePayload, reason: string, at: number): Proposal {
47
- const target = hashPayload(payload);
48
- const existing = this.proposals.find(
49
- (p) => p.target === target && p.status === "pending",
50
- );
51
- if (existing) return existing;
52
- const proposal: Proposal = {
53
- id: randomUUID(),
54
- at,
55
- target,
56
- payload,
57
- reason,
58
- status: "pending",
59
- };
60
- this.proposals.push(proposal);
61
- return proposal;
62
- }
63
-
64
- pending(): Proposal[] {
65
- return this.proposals.filter((p) => p.status === "pending");
66
- }
67
-
68
- get(id: string): Proposal | undefined {
69
- return this.proposals.find((p) => p.id === id);
70
- }
71
-
72
- /**
73
- * Resolve a pending proposal. Returns the payload to apply when approved, or
74
- * undefined otherwise (rejected, missing, or already resolved).
75
- */
76
- resolve(id: string, approved: boolean, at: number): NodePayload | undefined {
77
- const p = this.proposals.find((x) => x.id === id && x.status === "pending");
78
- if (!p) return undefined;
79
- p.status = approved ? "approved" : "rejected";
80
- p.resolvedAt = at;
81
- return approved ? p.payload : undefined;
82
- }
83
-
84
- snapshot(): ApprovalSnapshot {
85
- return { proposals: this.proposals.map((p) => ({ ...p })) };
86
- }
87
-
88
- static restore(snap: ApprovalSnapshot): ApprovalQueue {
89
- const q = new ApprovalQueue();
90
- q.proposals = snap.proposals.map((p) => ({ ...p }));
91
- return q;
92
- }
93
- }