nemoris 0.1.0

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 (223) hide show
  1. package/.env.example +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +209 -0
  4. package/SECURITY.md +119 -0
  5. package/bin/nemoris +46 -0
  6. package/config/agents/agent.toml.example +28 -0
  7. package/config/agents/default.toml +22 -0
  8. package/config/agents/orchestrator.toml +18 -0
  9. package/config/delivery.toml +73 -0
  10. package/config/embeddings.toml +5 -0
  11. package/config/identity/default-purpose.md +1 -0
  12. package/config/identity/default-soul.md +3 -0
  13. package/config/identity/orchestrator-purpose.md +1 -0
  14. package/config/identity/orchestrator-soul.md +1 -0
  15. package/config/improvement-targets.toml +15 -0
  16. package/config/jobs/heartbeat-check.toml +30 -0
  17. package/config/jobs/memory-rollup.toml +46 -0
  18. package/config/jobs/workspace-health.toml +63 -0
  19. package/config/mcp.toml +16 -0
  20. package/config/output-contracts.toml +17 -0
  21. package/config/peers.toml +32 -0
  22. package/config/peers.toml.example +32 -0
  23. package/config/policies/memory-default.toml +10 -0
  24. package/config/policies/memory-heartbeat.toml +5 -0
  25. package/config/policies/memory-ops.toml +10 -0
  26. package/config/policies/tools-heartbeat-minimal.toml +8 -0
  27. package/config/policies/tools-interactive-safe.toml +8 -0
  28. package/config/policies/tools-ops-bounded.toml +8 -0
  29. package/config/policies/tools-orchestrator.toml +7 -0
  30. package/config/providers/anthropic.toml +15 -0
  31. package/config/providers/ollama.toml +5 -0
  32. package/config/providers/openai-codex.toml +9 -0
  33. package/config/providers/openrouter.toml +5 -0
  34. package/config/router.toml +22 -0
  35. package/config/runtime.toml +114 -0
  36. package/config/skills/self-improvement.toml +15 -0
  37. package/config/skills/telegram-onboarding-spec.md +240 -0
  38. package/config/skills/workspace-monitor.toml +15 -0
  39. package/config/task-router.toml +42 -0
  40. package/install.sh +50 -0
  41. package/package.json +90 -0
  42. package/src/auth/auth-profiles.js +169 -0
  43. package/src/auth/openai-codex-oauth.js +285 -0
  44. package/src/battle.js +449 -0
  45. package/src/cli/help.js +265 -0
  46. package/src/cli/output-filter.js +49 -0
  47. package/src/cli/runtime-control.js +704 -0
  48. package/src/cli-main.js +2763 -0
  49. package/src/cli.js +78 -0
  50. package/src/config/loader.js +332 -0
  51. package/src/config/schema-validator.js +214 -0
  52. package/src/config/toml-lite.js +8 -0
  53. package/src/daemon/action-handlers.js +71 -0
  54. package/src/daemon/healing-tick.js +87 -0
  55. package/src/daemon/health-probes.js +90 -0
  56. package/src/daemon/notifier.js +57 -0
  57. package/src/daemon/nurse.js +218 -0
  58. package/src/daemon/repair-log.js +106 -0
  59. package/src/daemon/rule-staging.js +90 -0
  60. package/src/daemon/rules.js +29 -0
  61. package/src/daemon/telegram-commands.js +54 -0
  62. package/src/daemon/updater.js +85 -0
  63. package/src/jobs/job-runner.js +78 -0
  64. package/src/mcp/consumer.js +129 -0
  65. package/src/memory/active-recall.js +171 -0
  66. package/src/memory/backend-manager.js +97 -0
  67. package/src/memory/backends/file-backend.js +38 -0
  68. package/src/memory/backends/qmd-backend.js +219 -0
  69. package/src/memory/embedding-guards.js +24 -0
  70. package/src/memory/embedding-index.js +118 -0
  71. package/src/memory/embedding-service.js +179 -0
  72. package/src/memory/file-index.js +177 -0
  73. package/src/memory/memory-signature.js +5 -0
  74. package/src/memory/memory-store.js +648 -0
  75. package/src/memory/retrieval-planner.js +66 -0
  76. package/src/memory/scoring.js +145 -0
  77. package/src/memory/simhash.js +78 -0
  78. package/src/memory/sqlite-active-store.js +824 -0
  79. package/src/memory/write-policy.js +36 -0
  80. package/src/onboarding/aliases.js +33 -0
  81. package/src/onboarding/auth/api-key.js +224 -0
  82. package/src/onboarding/auth/ollama-detect.js +42 -0
  83. package/src/onboarding/clack-prompter.js +77 -0
  84. package/src/onboarding/doctor.js +530 -0
  85. package/src/onboarding/lock.js +42 -0
  86. package/src/onboarding/model-catalog.js +344 -0
  87. package/src/onboarding/phases/auth.js +589 -0
  88. package/src/onboarding/phases/build.js +130 -0
  89. package/src/onboarding/phases/choose.js +82 -0
  90. package/src/onboarding/phases/detect.js +98 -0
  91. package/src/onboarding/phases/hatch.js +216 -0
  92. package/src/onboarding/phases/identity.js +79 -0
  93. package/src/onboarding/phases/ollama.js +345 -0
  94. package/src/onboarding/phases/scaffold.js +99 -0
  95. package/src/onboarding/phases/telegram.js +377 -0
  96. package/src/onboarding/phases/validate.js +204 -0
  97. package/src/onboarding/phases/verify.js +206 -0
  98. package/src/onboarding/platform.js +482 -0
  99. package/src/onboarding/status-bar.js +95 -0
  100. package/src/onboarding/templates.js +794 -0
  101. package/src/onboarding/toml-writer.js +38 -0
  102. package/src/onboarding/tui.js +250 -0
  103. package/src/onboarding/uninstall.js +153 -0
  104. package/src/onboarding/wizard.js +499 -0
  105. package/src/providers/anthropic.js +168 -0
  106. package/src/providers/base.js +247 -0
  107. package/src/providers/circuit-breaker.js +136 -0
  108. package/src/providers/ollama.js +163 -0
  109. package/src/providers/openai-codex.js +149 -0
  110. package/src/providers/openrouter.js +136 -0
  111. package/src/providers/registry.js +36 -0
  112. package/src/providers/router.js +16 -0
  113. package/src/runtime/bootstrap-cache.js +47 -0
  114. package/src/runtime/capabilities-prompt.js +25 -0
  115. package/src/runtime/completion-ping.js +99 -0
  116. package/src/runtime/config-validator.js +121 -0
  117. package/src/runtime/context-ledger.js +360 -0
  118. package/src/runtime/cutover-readiness.js +42 -0
  119. package/src/runtime/daemon.js +729 -0
  120. package/src/runtime/delivery-ack.js +195 -0
  121. package/src/runtime/delivery-adapters/local-file.js +41 -0
  122. package/src/runtime/delivery-adapters/openclaw-cli.js +94 -0
  123. package/src/runtime/delivery-adapters/openclaw-peer.js +98 -0
  124. package/src/runtime/delivery-adapters/shadow.js +13 -0
  125. package/src/runtime/delivery-adapters/standalone-http.js +98 -0
  126. package/src/runtime/delivery-adapters/telegram.js +104 -0
  127. package/src/runtime/delivery-adapters/tui.js +128 -0
  128. package/src/runtime/delivery-manager.js +807 -0
  129. package/src/runtime/delivery-store.js +168 -0
  130. package/src/runtime/dependency-health.js +118 -0
  131. package/src/runtime/envelope.js +114 -0
  132. package/src/runtime/evaluation.js +1089 -0
  133. package/src/runtime/exec-approvals.js +216 -0
  134. package/src/runtime/executor.js +500 -0
  135. package/src/runtime/failure-ping.js +67 -0
  136. package/src/runtime/flows.js +83 -0
  137. package/src/runtime/guards.js +45 -0
  138. package/src/runtime/handoff.js +51 -0
  139. package/src/runtime/identity-cache.js +28 -0
  140. package/src/runtime/improvement-engine.js +109 -0
  141. package/src/runtime/improvement-harness.js +581 -0
  142. package/src/runtime/input-sanitiser.js +72 -0
  143. package/src/runtime/interaction-contract.js +347 -0
  144. package/src/runtime/lane-readiness.js +226 -0
  145. package/src/runtime/migration.js +323 -0
  146. package/src/runtime/model-resolution.js +78 -0
  147. package/src/runtime/network.js +64 -0
  148. package/src/runtime/notification-store.js +97 -0
  149. package/src/runtime/notifier.js +256 -0
  150. package/src/runtime/orchestrator.js +53 -0
  151. package/src/runtime/orphan-reaper.js +41 -0
  152. package/src/runtime/output-contract-schema.js +139 -0
  153. package/src/runtime/output-contract-validator.js +439 -0
  154. package/src/runtime/peer-readiness.js +69 -0
  155. package/src/runtime/peer-registry.js +133 -0
  156. package/src/runtime/pilot-status.js +108 -0
  157. package/src/runtime/prompt-builder.js +261 -0
  158. package/src/runtime/provider-attempt.js +582 -0
  159. package/src/runtime/report-fallback.js +71 -0
  160. package/src/runtime/result-normalizer.js +183 -0
  161. package/src/runtime/retention.js +74 -0
  162. package/src/runtime/review.js +244 -0
  163. package/src/runtime/route-job.js +15 -0
  164. package/src/runtime/run-store.js +38 -0
  165. package/src/runtime/schedule.js +88 -0
  166. package/src/runtime/scheduler-state.js +434 -0
  167. package/src/runtime/scheduler.js +656 -0
  168. package/src/runtime/session-compactor.js +182 -0
  169. package/src/runtime/session-search.js +155 -0
  170. package/src/runtime/slack-inbound.js +249 -0
  171. package/src/runtime/ssrf.js +102 -0
  172. package/src/runtime/status-aggregator.js +330 -0
  173. package/src/runtime/task-contract.js +140 -0
  174. package/src/runtime/task-packet.js +107 -0
  175. package/src/runtime/task-router.js +140 -0
  176. package/src/runtime/telegram-inbound.js +1565 -0
  177. package/src/runtime/token-counter.js +134 -0
  178. package/src/runtime/token-estimator.js +59 -0
  179. package/src/runtime/tool-loop.js +200 -0
  180. package/src/runtime/transport-server.js +311 -0
  181. package/src/runtime/tui-server.js +411 -0
  182. package/src/runtime/ulid.js +44 -0
  183. package/src/security/ssrf-check.js +197 -0
  184. package/src/setup.js +369 -0
  185. package/src/shadow/bridge.js +303 -0
  186. package/src/skills/loader.js +84 -0
  187. package/src/tools/catalog.json +49 -0
  188. package/src/tools/cli-delegate.js +44 -0
  189. package/src/tools/mcp-client.js +106 -0
  190. package/src/tools/micro/cancel-task.js +6 -0
  191. package/src/tools/micro/complete-task.js +6 -0
  192. package/src/tools/micro/fail-task.js +6 -0
  193. package/src/tools/micro/http-fetch.js +74 -0
  194. package/src/tools/micro/index.js +36 -0
  195. package/src/tools/micro/lcm-recall.js +60 -0
  196. package/src/tools/micro/list-dir.js +17 -0
  197. package/src/tools/micro/list-skills.js +46 -0
  198. package/src/tools/micro/load-skill.js +38 -0
  199. package/src/tools/micro/memory-search.js +45 -0
  200. package/src/tools/micro/read-file.js +11 -0
  201. package/src/tools/micro/session-search.js +54 -0
  202. package/src/tools/micro/shell-exec.js +43 -0
  203. package/src/tools/micro/trigger-job.js +79 -0
  204. package/src/tools/micro/web-search.js +58 -0
  205. package/src/tools/micro/workspace-paths.js +39 -0
  206. package/src/tools/micro/write-file.js +14 -0
  207. package/src/tools/micro/write-memory.js +41 -0
  208. package/src/tools/registry.js +348 -0
  209. package/src/tools/tool-result-contract.js +36 -0
  210. package/src/tui/chat.js +835 -0
  211. package/src/tui/renderer.js +175 -0
  212. package/src/tui/socket-client.js +217 -0
  213. package/src/utils/canonical-json.js +29 -0
  214. package/src/utils/compaction.js +30 -0
  215. package/src/utils/env-loader.js +5 -0
  216. package/src/utils/errors.js +80 -0
  217. package/src/utils/fs.js +101 -0
  218. package/src/utils/ids.js +5 -0
  219. package/src/utils/model-context-limits.js +30 -0
  220. package/src/utils/token-budget.js +74 -0
  221. package/src/utils/usage-cost.js +25 -0
  222. package/src/utils/usage-metrics.js +14 -0
  223. package/vendor/smol-toml-1.5.2.tgz +0 -0
@@ -0,0 +1,168 @@
1
+ import path from "node:path";
2
+ import { ensureDir, listFilesRecursive, readJson, writeJson } from "../utils/fs.js";
3
+ import { buildRetentionPolicy, pruneJsonBuckets } from "./retention.js";
4
+ import { createRuntimeId } from "../utils/ids.js";
5
+
6
+ function stamp() {
7
+ return new Date().toISOString().replace(/[:.]/g, "-");
8
+ }
9
+
10
+ function deliveryKey(mode, notificationFilePath, dedupeKey = null) {
11
+ return dedupeKey ? `${mode}:dedupe:${dedupeKey}` : `${mode}:file:${notificationFilePath}`;
12
+ }
13
+
14
+ export class DeliveryStore {
15
+ constructor({ rootDir, retention = {} }) {
16
+ this.rootDir = rootDir;
17
+ this.indexPath = path.join(rootDir, "index.json");
18
+ this.setRetentionPolicy(retention);
19
+ }
20
+
21
+ setRetentionPolicy(retention = {}) {
22
+ this.retentionPolicy = buildRetentionPolicy(retention, {
23
+ ttlDays: 14,
24
+ maxFilesPerBucket: 1000
25
+ });
26
+ }
27
+
28
+ async loadIndex() {
29
+ await ensureDir(this.rootDir);
30
+ return readJson(this.indexPath, { delivered: {} });
31
+ }
32
+
33
+ async saveIndex(index) {
34
+ await ensureDir(this.rootDir);
35
+ await writeJson(this.indexPath, index);
36
+ return index;
37
+ }
38
+
39
+ async isDelivered(mode, notificationFilePath, dedupeKey = null) {
40
+ const record = await this.getDeliveryRecord(mode, notificationFilePath, dedupeKey);
41
+ return Boolean(record && ["delivered", "uncertain"].includes(record.status || "delivered"));
42
+ }
43
+
44
+ async getDeliveryRecord(mode, notificationFilePath, dedupeKey = null) {
45
+ const index = await this.loadIndex();
46
+ return (
47
+ index.delivered[deliveryKey(mode, notificationFilePath, dedupeKey)] ||
48
+ index.delivered[deliveryKey(mode, notificationFilePath)] ||
49
+ null
50
+ );
51
+ }
52
+
53
+ async markDelivered(mode, notificationFilePath, receiptFilePath, options = {}) {
54
+ const index = await this.loadIndex();
55
+ const current = await this.getDeliveryRecord(mode, notificationFilePath, options.dedupeKey);
56
+ const record = {
57
+ id: current?.id || createRuntimeId("delivery-record"),
58
+ receiptFilePath,
59
+ deliveredAt: new Date().toISOString(),
60
+ status: "delivered",
61
+ dedupeKey: options.dedupeKey || null,
62
+ retryEligible: false,
63
+ uncertain: false,
64
+ attempts: (current?.attempts || 0) + 1,
65
+ lastError: null
66
+ };
67
+ index.delivered[deliveryKey(mode, notificationFilePath)] = record;
68
+ if (options.dedupeKey) {
69
+ index.delivered[deliveryKey(mode, notificationFilePath, options.dedupeKey)] = record;
70
+ }
71
+ await this.saveIndex(index);
72
+ return record;
73
+ }
74
+
75
+ async markUncertain(mode, notificationFilePath, receiptFilePath, options = {}) {
76
+ const index = await this.loadIndex();
77
+ const current = await this.getDeliveryRecord(mode, notificationFilePath, options.dedupeKey);
78
+ const record = {
79
+ id: current?.id || createRuntimeId("delivery-record"),
80
+ receiptFilePath,
81
+ deliveredAt: null,
82
+ status: "uncertain",
83
+ dedupeKey: options.dedupeKey || null,
84
+ retryEligible: false,
85
+ uncertain: true,
86
+ attempts: (current?.attempts || 0) + 1,
87
+ lastError: options.error || null,
88
+ updatedAt: new Date().toISOString()
89
+ };
90
+ index.delivered[deliveryKey(mode, notificationFilePath)] = record;
91
+ if (options.dedupeKey) {
92
+ index.delivered[deliveryKey(mode, notificationFilePath, options.dedupeKey)] = record;
93
+ }
94
+ await this.saveIndex(index);
95
+ return record;
96
+ }
97
+
98
+ async markFailed(mode, notificationFilePath, receiptFilePath, options = {}) {
99
+ const index = await this.loadIndex();
100
+ const current = await this.getDeliveryRecord(mode, notificationFilePath, options.dedupeKey);
101
+ const record = {
102
+ id: current?.id || createRuntimeId("delivery-record"),
103
+ receiptFilePath,
104
+ deliveredAt: null,
105
+ status: "failed",
106
+ dedupeKey: options.dedupeKey || null,
107
+ retryEligible: options.retryEligible ?? false,
108
+ uncertain: false,
109
+ attempts: (current?.attempts || 0) + 1,
110
+ lastError: options.error || null,
111
+ updatedAt: new Date().toISOString()
112
+ };
113
+ index.delivered[deliveryKey(mode, notificationFilePath)] = record;
114
+ if (options.dedupeKey) {
115
+ index.delivered[deliveryKey(mode, notificationFilePath, options.dedupeKey)] = record;
116
+ }
117
+ await this.saveIndex(index);
118
+ return record;
119
+ }
120
+
121
+ async saveReceipt(jobId, receipt) {
122
+ const receiptDir = path.join(this.rootDir, jobId);
123
+ await ensureDir(receiptDir);
124
+ const filePath = path.join(receiptDir, `${stamp()}.json`);
125
+ await writeJson(filePath, {
126
+ id: receipt.id || createRuntimeId("delivery-attempt"),
127
+ ...receipt
128
+ });
129
+ await pruneJsonBuckets(this.rootDir, this.retentionPolicy, {
130
+ excludePaths: [this.indexPath]
131
+ });
132
+ return filePath;
133
+ }
134
+
135
+ async listAll() {
136
+ const files = (await listFilesRecursive(this.rootDir)).filter(
137
+ (filePath) => filePath.endsWith(".json") && filePath !== this.indexPath
138
+ );
139
+ const receipts = [];
140
+
141
+ for (const filePath of files) {
142
+ const data = await readJson(filePath, null);
143
+ if (!data) continue;
144
+ receipts.push({
145
+ filePath,
146
+ ...data
147
+ });
148
+ }
149
+
150
+ return receipts.sort((a, b) => String(b.timestamp || "").localeCompare(String(a.timestamp || "")));
151
+ }
152
+
153
+ async listRecent(limit = 10) {
154
+ const receipts = await this.listAll();
155
+ return receipts.slice(0, limit);
156
+ }
157
+
158
+ async prune(options = {}) {
159
+ const result = await pruneJsonBuckets(this.rootDir, this.retentionPolicy, {
160
+ ...options,
161
+ excludePaths: [...(options.excludePaths || []), this.indexPath]
162
+ });
163
+ return {
164
+ ...result,
165
+ indexFilePath: this.indexPath
166
+ };
167
+ }
168
+ }
@@ -0,0 +1,118 @@
1
+ import path from "node:path";
2
+ import { execFile } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { ConfigLoader } from "../config/loader.js";
5
+ import { ProviderRegistry } from "../providers/registry.js";
6
+ import { PeerReadinessProbe } from "./peer-readiness.js";
7
+ import { Scheduler } from "./scheduler.js";
8
+
9
+ const execFileAsync = promisify(execFile);
10
+
11
+ function resolveNamedProfile(profiles, profileName) {
12
+ if (!profileName) return null;
13
+ return profiles[profileName] || profiles[String(profileName).replace(/_([a-z])/g, (_, letter) => letter.toUpperCase())] || null;
14
+ }
15
+
16
+ export class DependencyHealth {
17
+ constructor({ projectRoot, liveRoot, fetchImpl, execFileImpl } = {}) {
18
+ this.loader = new ConfigLoader({ rootDir: path.join(projectRoot, "config") });
19
+ this.registry = new ProviderRegistry({ fetchImpl });
20
+ this.scheduler = new Scheduler({ projectRoot, liveRoot, stateRoot: path.join(projectRoot, "state") });
21
+ this.peerProbe = new PeerReadinessProbe({ liveRoot });
22
+ this.execFileImpl = execFileImpl || execFileAsync;
23
+ }
24
+
25
+ async provider(providerId, runtime = null) {
26
+ const loaded = runtime || (await this.loader.loadAll());
27
+ const providerConfig = loaded.providers?.[providerId];
28
+ if (!providerConfig) {
29
+ return { providerId, configured: false, reachable: false, failureClass: "provider_missing" };
30
+ }
31
+ try {
32
+ const adapter = this.registry.create(providerConfig);
33
+ const health = typeof adapter.healthCheck === "function" ? await adapter.healthCheck() : { ok: true, status: null };
34
+ return {
35
+ providerId,
36
+ configured: true,
37
+ reachable: Boolean(health.ok),
38
+ status: health.status ?? null,
39
+ failureClass: health.ok ? null : "provider_unhealthy"
40
+ };
41
+ } catch (error) {
42
+ return {
43
+ providerId,
44
+ configured: true,
45
+ reachable: false,
46
+ status: null,
47
+ failureClass: error.failureClass || error.networkClass || "provider_unreachable",
48
+ error: error.message
49
+ };
50
+ }
51
+ }
52
+
53
+ async probeGatewayHealth() {
54
+ try {
55
+ await this.execFileImpl("openclaw", ["--version"], { timeout: 5000, maxBuffer: 64 * 1024 });
56
+ return { ok: true };
57
+ } catch (error) {
58
+ const code = error.code || "";
59
+ if (code === "ENOENT") {
60
+ return { ok: false, reason: "openclaw CLI binary not found on PATH." };
61
+ }
62
+ if (code === "ETIMEDOUT" || error.killed) {
63
+ return { ok: false, reason: "openclaw CLI timed out; the gateway may be stalled." };
64
+ }
65
+ return { ok: false, reason: `openclaw CLI probe failed: ${error.message || code}` };
66
+ }
67
+ }
68
+
69
+ async delivery(profileName, runtime = null) {
70
+ const loaded = runtime || (await this.loader.loadAll());
71
+ const profile = resolveNamedProfile(loaded.delivery?.profiles || {}, profileName);
72
+ if (!profile) {
73
+ return { profileName, configured: false, reachable: false, failureClass: "delivery_profile_missing" };
74
+ }
75
+ const adapter = profile.adapter || null;
76
+ let reachable = true;
77
+ let failureClass = null;
78
+ const findings = [];
79
+ if (adapter === "openclaw_cli" || adapter === "openclaw_peer") {
80
+ const probeResult = await this.probeGatewayHealth();
81
+ reachable = probeResult.ok;
82
+ if (!reachable) {
83
+ failureClass = "delivery_gateway_unreachable";
84
+ findings.push(probeResult.reason || "OpenClaw gateway is not reachable for delivery.");
85
+ }
86
+ }
87
+ return {
88
+ profileName,
89
+ configured: true,
90
+ adapter,
91
+ reachable,
92
+ failureClass: reachable ? null : (failureClass || "delivery_not_ready"),
93
+ findings
94
+ };
95
+ }
96
+
97
+ async peer(peerId, runtime = null) {
98
+ const loaded = runtime || (await this.loader.loadAll());
99
+ return this.peerProbe.probe(loaded, peerId);
100
+ }
101
+
102
+ async forJob(jobId) {
103
+ const runtime = await this.loader.loadAll();
104
+ const plan = await this.scheduler.hydrateJob(jobId, { shadowImport: false });
105
+ const primaryProviderId = String(plan.routing.selectedLane || "").startsWith("openrouter")
106
+ ? "openrouter"
107
+ : null;
108
+ const providerId = plan.routing.selectedLane === "local_report" || plan.routing.selectedLane === "local_cheap"
109
+ ? "ollama"
110
+ : primaryProviderId || "anthropic";
111
+
112
+ const fallbackProfile = plan.agent.deliveryProfile || runtime.delivery?.defaultInteractiveProfile || null;
113
+ return {
114
+ provider: await this.provider(providerId, runtime),
115
+ delivery: await this.delivery(fallbackProfile, runtime)
116
+ };
117
+ }
118
+ }
@@ -0,0 +1,114 @@
1
+ import { ulid } from "./ulid.js";
2
+
3
+ export class EnvelopeStore {
4
+ constructor(db, stateStore = null) {
5
+ this.db = db;
6
+ this.stateStore = stateStore;
7
+ this._ensureSchema();
8
+ }
9
+
10
+ _ensureSchema() {
11
+ this.db.exec(`
12
+ CREATE TABLE IF NOT EXISTS envelopes (
13
+ id TEXT PRIMARY KEY,
14
+ correlation_id TEXT,
15
+ source_agent TEXT,
16
+ criticality TEXT NOT NULL,
17
+ created_at TEXT NOT NULL,
18
+ ack_status TEXT NOT NULL DEFAULT 'pending',
19
+ ack_updated_at TEXT,
20
+ payload_type TEXT NOT NULL,
21
+ payload TEXT NOT NULL
22
+ );
23
+ CREATE INDEX IF NOT EXISTS idx_envelopes_correlation ON envelopes(correlation_id);
24
+ CREATE INDEX IF NOT EXISTS idx_envelopes_ack_status ON envelopes(ack_status);
25
+ CREATE INDEX IF NOT EXISTS idx_envelopes_source_agent ON envelopes(source_agent);
26
+ `);
27
+ }
28
+
29
+ create({ sourceAgent, criticality, payloadType, payload, correlationId = null }) {
30
+ const id = ulid();
31
+ const now = new Date().toISOString();
32
+ const payloadJson = JSON.stringify(payload);
33
+ this.db.prepare(`
34
+ INSERT INTO envelopes (id, correlation_id, source_agent, criticality, created_at, ack_status, payload_type, payload)
35
+ VALUES (?, ?, ?, ?, ?, 'pending', ?, ?)
36
+ `).run(id, correlationId, sourceAgent, criticality, now, payloadType, payloadJson);
37
+ return this.get(id);
38
+ }
39
+
40
+ get(id) {
41
+ return this.db.prepare("SELECT * FROM envelopes WHERE id = ?").get(id) || null;
42
+ }
43
+
44
+ list({ limit = 50, ackStatus = null } = {}) {
45
+ if (ackStatus) {
46
+ return this.db.prepare("SELECT * FROM envelopes WHERE ack_status = ? ORDER BY created_at DESC LIMIT ?").all(ackStatus, limit);
47
+ }
48
+ return this.db.prepare("SELECT * FROM envelopes ORDER BY created_at DESC LIMIT ?").all(limit);
49
+ }
50
+
51
+ updateAck(id, newStatus) {
52
+ const now = new Date().toISOString();
53
+ const result = this.db.prepare(`
54
+ UPDATE envelopes SET ack_status = ?, ack_updated_at = ?
55
+ WHERE id = ? AND ack_status != 'dead_letter'
56
+ `).run(newStatus, now, id);
57
+ return result.changes > 0;
58
+ }
59
+
60
+ prune(retentionDays = 30) {
61
+ const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000).toISOString();
62
+ const result = this.db.prepare(`
63
+ DELETE FROM envelopes
64
+ WHERE created_at < ? AND ack_status IN ('delivered', 'responded')
65
+ `).run(cutoff);
66
+ return result.changes;
67
+ }
68
+
69
+ getSchemaVersion() {
70
+ if (!this.stateStore) return 1;
71
+ const row = this.stateStore.db.prepare(
72
+ "SELECT value_json FROM scheduler_meta WHERE key = ?"
73
+ ).get("schema_version");
74
+ return row ? parseInt(JSON.parse(row.value_json), 10) : 1;
75
+ }
76
+
77
+ setSchemaVersion(version) {
78
+ if (!this.stateStore) return;
79
+ this.stateStore.db.prepare(`
80
+ INSERT INTO scheduler_meta(key, value_json) VALUES (?, ?)
81
+ ON CONFLICT(key) DO UPDATE SET value_json = excluded.value_json
82
+ `).run("schema_version", JSON.stringify(version));
83
+ }
84
+
85
+ runBackfill(stateStore, taskContract) {
86
+ const version = this.getSchemaVersion();
87
+ if (version >= 2) return { backfilled: 0, skipped: true };
88
+
89
+ let backfilled = 0;
90
+ try {
91
+ const inFlight = stateStore.db.prepare(`
92
+ SELECT * FROM interactive_jobs
93
+ WHERE triggered_by IS NOT NULL AND status IN ('queued', 'running')
94
+ `).all();
95
+
96
+ for (const job of inFlight) {
97
+ if (taskContract) {
98
+ try {
99
+ taskContract.createTask({
100
+ ownerAgent: job.triggered_by,
101
+ assignedAgent: job.agent_id,
102
+ objective: (job.input || "migrated task").slice(0, 500),
103
+ deadlineMinutes: 30,
104
+ });
105
+ backfilled++;
106
+ } catch { /* skip if creation fails */ }
107
+ }
108
+ }
109
+ } catch { /* table may not have these columns */ }
110
+
111
+ this.setSchemaVersion(2);
112
+ return { backfilled, skipped: false };
113
+ }
114
+ }