atabey 0.1.1-beta → 0.1.2

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 (229) hide show
  1. package/README.md +144 -78
  2. package/dist/atabey/src/cli/commands/coverage.js +17 -12
  3. package/dist/atabey/src/cli/commands/coverage.js.map +1 -1
  4. package/dist/atabey/src/cli/commands/lint.d.ts +2 -1
  5. package/dist/atabey/src/cli/commands/lint.js +20 -1
  6. package/dist/atabey/src/cli/commands/lint.js.map +1 -1
  7. package/dist/atabey/src/cli/commands/mcp.js +56 -23
  8. package/dist/atabey/src/cli/commands/mcp.js.map +1 -1
  9. package/dist/atabey/src/cli/commands/orchestrate.d.ts +6 -6
  10. package/dist/shared/src/agent-identity.d.ts +1 -34
  11. package/dist/shared/src/agent-identity.js +1 -76
  12. package/dist/shared/src/agent-identity.js.map +1 -1
  13. package/dist/shared/src/api-catalog.d.ts +1 -57
  14. package/dist/shared/src/api-catalog.js +1 -253
  15. package/dist/shared/src/api-catalog.js.map +1 -1
  16. package/dist/shared/src/audit-chain.d.ts +1 -80
  17. package/dist/shared/src/audit-chain.js +1 -261
  18. package/dist/shared/src/audit-chain.js.map +1 -1
  19. package/dist/shared/src/audit.d.ts +1 -121
  20. package/dist/shared/src/audit.js +1 -321
  21. package/dist/shared/src/audit.js.map +1 -1
  22. package/dist/shared/src/constants.d.ts +1 -150
  23. package/dist/shared/src/constants.js +1 -211
  24. package/dist/shared/src/constants.js.map +1 -1
  25. package/dist/shared/src/database.d.ts +1 -6
  26. package/dist/shared/src/database.js +1 -15
  27. package/dist/shared/src/database.js.map +1 -1
  28. package/dist/shared/src/errors.d.ts +1 -46
  29. package/dist/shared/src/errors.js +1 -68
  30. package/dist/shared/src/errors.js.map +1 -1
  31. package/dist/shared/src/file-lock.d.ts +1 -93
  32. package/dist/shared/src/file-lock.js +1 -239
  33. package/dist/shared/src/file-lock.js.map +1 -1
  34. package/dist/shared/src/fs.d.ts +1 -9
  35. package/dist/shared/src/fs.js +1 -53
  36. package/dist/shared/src/fs.js.map +1 -1
  37. package/dist/shared/src/index.d.ts +2 -31
  38. package/dist/shared/src/index.js +2 -31
  39. package/dist/shared/src/index.js.map +1 -1
  40. package/dist/shared/src/job-queue.d.ts +1 -76
  41. package/dist/shared/src/job-queue.js +1 -199
  42. package/dist/shared/src/job-queue.js.map +1 -1
  43. package/dist/shared/src/lock.d.ts +1 -82
  44. package/dist/shared/src/lock.js +1 -158
  45. package/dist/shared/src/lock.js.map +1 -1
  46. package/dist/shared/src/logger.d.ts +1 -31
  47. package/dist/shared/src/logger.js +1 -152
  48. package/dist/shared/src/logger.js.map +1 -1
  49. package/dist/shared/src/memory-prune.d.ts +1 -18
  50. package/dist/shared/src/memory-prune.js +1 -47
  51. package/dist/shared/src/memory-prune.js.map +1 -1
  52. package/dist/shared/src/migrations.d.ts +1 -69
  53. package/dist/shared/src/migrations.js +1 -393
  54. package/dist/shared/src/migrations.js.map +1 -1
  55. package/dist/shared/src/modules/agent/agent-identity.d.ts +34 -0
  56. package/dist/shared/src/modules/agent/agent-identity.js +77 -0
  57. package/dist/shared/src/modules/agent/agent-identity.js.map +1 -0
  58. package/dist/shared/src/modules/agent/api-catalog.d.ts +57 -0
  59. package/dist/shared/src/modules/agent/api-catalog.js +254 -0
  60. package/dist/shared/src/modules/agent/api-catalog.js.map +1 -0
  61. package/dist/shared/src/modules/agent/index.d.ts +3 -0
  62. package/dist/shared/src/modules/agent/index.js +4 -0
  63. package/dist/shared/src/modules/agent/index.js.map +1 -0
  64. package/dist/shared/src/modules/agent/tokenizer.d.ts +54 -0
  65. package/dist/shared/src/modules/agent/tokenizer.js +272 -0
  66. package/dist/shared/src/modules/agent/tokenizer.js.map +1 -0
  67. package/dist/shared/src/modules/concurrency/file-lock.d.ts +93 -0
  68. package/dist/shared/src/modules/concurrency/file-lock.js +240 -0
  69. package/dist/shared/src/modules/concurrency/file-lock.js.map +1 -0
  70. package/dist/shared/src/modules/concurrency/index.d.ts +6 -0
  71. package/dist/shared/src/modules/concurrency/index.js +7 -0
  72. package/dist/shared/src/modules/concurrency/index.js.map +1 -0
  73. package/dist/shared/src/modules/concurrency/job-queue.d.ts +76 -0
  74. package/dist/shared/src/modules/concurrency/job-queue.js +200 -0
  75. package/dist/shared/src/modules/concurrency/job-queue.js.map +1 -0
  76. package/dist/shared/src/modules/concurrency/lock.d.ts +82 -0
  77. package/dist/shared/src/modules/concurrency/lock.js +159 -0
  78. package/dist/shared/src/modules/concurrency/lock.js.map +1 -0
  79. package/dist/shared/src/modules/concurrency/resilience.d.ts +84 -0
  80. package/dist/shared/src/modules/concurrency/resilience.js +217 -0
  81. package/dist/shared/src/modules/concurrency/resilience.js.map +1 -0
  82. package/dist/shared/src/modules/concurrency/worker-pool.d.ts +52 -0
  83. package/dist/shared/src/modules/concurrency/worker-pool.js +229 -0
  84. package/dist/shared/src/modules/concurrency/worker-pool.js.map +1 -0
  85. package/dist/shared/src/modules/concurrency/worker-thread.d.ts +21 -0
  86. package/dist/shared/src/modules/concurrency/worker-thread.js +67 -0
  87. package/dist/shared/src/modules/concurrency/worker-thread.js.map +1 -0
  88. package/dist/shared/src/modules/index.d.ts +14 -0
  89. package/dist/shared/src/modules/index.js +15 -0
  90. package/dist/shared/src/modules/index.js.map +1 -0
  91. package/dist/shared/src/modules/observability/audit-chain.d.ts +80 -0
  92. package/dist/shared/src/modules/observability/audit-chain.js +262 -0
  93. package/dist/shared/src/modules/observability/audit-chain.js.map +1 -0
  94. package/dist/shared/src/modules/observability/audit.d.ts +121 -0
  95. package/dist/shared/src/modules/observability/audit.js +322 -0
  96. package/dist/shared/src/modules/observability/audit.js.map +1 -0
  97. package/dist/shared/src/modules/observability/index.d.ts +4 -0
  98. package/dist/shared/src/modules/observability/index.js +5 -0
  99. package/dist/shared/src/modules/observability/index.js.map +1 -0
  100. package/dist/shared/src/modules/observability/logger.d.ts +31 -0
  101. package/dist/shared/src/modules/observability/logger.js +153 -0
  102. package/dist/shared/src/modules/observability/logger.js.map +1 -0
  103. package/dist/shared/src/modules/observability/otel.d.ts +39 -0
  104. package/dist/shared/src/modules/observability/otel.js +93 -0
  105. package/dist/shared/src/modules/observability/otel.js.map +1 -0
  106. package/dist/shared/src/modules/primitives/constants.d.ts +150 -0
  107. package/dist/shared/src/modules/primitives/constants.js +212 -0
  108. package/dist/shared/src/modules/primitives/constants.js.map +1 -0
  109. package/dist/shared/src/modules/primitives/errors.d.ts +46 -0
  110. package/dist/shared/src/modules/primitives/errors.js +69 -0
  111. package/dist/shared/src/modules/primitives/errors.js.map +1 -0
  112. package/dist/shared/src/modules/primitives/fs.d.ts +9 -0
  113. package/dist/shared/src/modules/primitives/fs.js +54 -0
  114. package/dist/shared/src/modules/primitives/fs.js.map +1 -0
  115. package/dist/shared/src/modules/primitives/index.d.ts +7 -0
  116. package/dist/shared/src/modules/primitives/index.js +8 -0
  117. package/dist/shared/src/modules/primitives/index.js.map +1 -0
  118. package/dist/shared/src/modules/primitives/outbound-http.d.ts +32 -0
  119. package/dist/shared/src/modules/primitives/outbound-http.js +88 -0
  120. package/dist/shared/src/modules/primitives/outbound-http.js.map +1 -0
  121. package/dist/shared/src/modules/primitives/string.d.ts +1 -0
  122. package/dist/shared/src/modules/primitives/string.js +30 -0
  123. package/dist/shared/src/modules/primitives/string.js.map +1 -0
  124. package/dist/shared/src/modules/primitives/time.d.ts +5 -0
  125. package/dist/shared/src/modules/primitives/time.js +35 -0
  126. package/dist/shared/src/modules/primitives/time.js.map +1 -0
  127. package/dist/shared/src/modules/primitives/types.d.ts +27 -0
  128. package/dist/shared/src/modules/primitives/types.js +17 -0
  129. package/dist/shared/src/modules/primitives/types.js.map +1 -0
  130. package/dist/shared/src/modules/sandbox/index.d.ts +3 -0
  131. package/dist/shared/src/modules/sandbox/index.js +4 -0
  132. package/dist/shared/src/modules/sandbox/index.js.map +1 -0
  133. package/dist/shared/src/modules/sandbox/sandbox-fs.d.ts +28 -0
  134. package/dist/shared/src/modules/sandbox/sandbox-fs.js +143 -0
  135. package/dist/shared/src/modules/sandbox/sandbox-fs.js.map +1 -0
  136. package/dist/shared/src/modules/sandbox/sandbox-runtime.d.ts +78 -0
  137. package/dist/shared/src/modules/sandbox/sandbox-runtime.js +282 -0
  138. package/dist/shared/src/modules/sandbox/sandbox-runtime.js.map +1 -0
  139. package/dist/shared/src/modules/sandbox/sandbox.d.ts +54 -0
  140. package/dist/shared/src/modules/sandbox/sandbox.js +179 -0
  141. package/dist/shared/src/modules/sandbox/sandbox.js.map +1 -0
  142. package/dist/shared/src/modules/security/index.d.ts +5 -0
  143. package/dist/shared/src/modules/security/index.js +6 -0
  144. package/dist/shared/src/modules/security/index.js.map +1 -0
  145. package/dist/shared/src/modules/security/pii.d.ts +70 -0
  146. package/dist/shared/src/modules/security/pii.js +276 -0
  147. package/dist/shared/src/modules/security/pii.js.map +1 -0
  148. package/dist/shared/src/modules/security/policy-gate.d.ts +42 -0
  149. package/dist/shared/src/modules/security/policy-gate.js +172 -0
  150. package/dist/shared/src/modules/security/policy-gate.js.map +1 -0
  151. package/dist/shared/src/modules/security/retention.d.ts +68 -0
  152. package/dist/shared/src/modules/security/retention.js +218 -0
  153. package/dist/shared/src/modules/security/retention.js.map +1 -0
  154. package/dist/shared/src/modules/security/secrets.d.ts +99 -0
  155. package/dist/shared/src/modules/security/secrets.js +371 -0
  156. package/dist/shared/src/modules/security/secrets.js.map +1 -0
  157. package/dist/shared/src/modules/security/tls.d.ts +87 -0
  158. package/dist/shared/src/modules/security/tls.js +154 -0
  159. package/dist/shared/src/modules/security/tls.js.map +1 -0
  160. package/dist/shared/src/modules/storage/database.d.ts +6 -0
  161. package/dist/shared/src/modules/storage/database.js +16 -0
  162. package/dist/shared/src/modules/storage/database.js.map +1 -0
  163. package/dist/shared/src/modules/storage/index.d.ts +4 -0
  164. package/dist/shared/src/modules/storage/index.js +5 -0
  165. package/dist/shared/src/modules/storage/index.js.map +1 -0
  166. package/dist/shared/src/modules/storage/memory-prune.d.ts +18 -0
  167. package/dist/shared/src/modules/storage/memory-prune.js +48 -0
  168. package/dist/shared/src/modules/storage/memory-prune.js.map +1 -0
  169. package/dist/shared/src/modules/storage/migrations.d.ts +69 -0
  170. package/dist/shared/src/modules/storage/migrations.js +394 -0
  171. package/dist/shared/src/modules/storage/migrations.js.map +1 -0
  172. package/dist/shared/src/modules/storage/storage.d.ts +341 -0
  173. package/dist/shared/src/modules/storage/storage.js +1004 -0
  174. package/dist/shared/src/modules/storage/storage.js.map +1 -0
  175. package/dist/shared/src/otel.d.ts +1 -39
  176. package/dist/shared/src/otel.js +1 -92
  177. package/dist/shared/src/otel.js.map +1 -1
  178. package/dist/shared/src/outbound-http.d.ts +1 -32
  179. package/dist/shared/src/outbound-http.js +1 -87
  180. package/dist/shared/src/outbound-http.js.map +1 -1
  181. package/dist/shared/src/pii.d.ts +1 -70
  182. package/dist/shared/src/pii.js +1 -275
  183. package/dist/shared/src/pii.js.map +1 -1
  184. package/dist/shared/src/policy-gate.d.ts +1 -42
  185. package/dist/shared/src/policy-gate.js +1 -171
  186. package/dist/shared/src/policy-gate.js.map +1 -1
  187. package/dist/shared/src/resilience.d.ts +1 -84
  188. package/dist/shared/src/resilience.js +1 -216
  189. package/dist/shared/src/resilience.js.map +1 -1
  190. package/dist/shared/src/retention.d.ts +1 -68
  191. package/dist/shared/src/retention.js +1 -217
  192. package/dist/shared/src/retention.js.map +1 -1
  193. package/dist/shared/src/sandbox-fs.d.ts +1 -28
  194. package/dist/shared/src/sandbox-fs.js +1 -142
  195. package/dist/shared/src/sandbox-fs.js.map +1 -1
  196. package/dist/shared/src/sandbox-runtime.d.ts +1 -78
  197. package/dist/shared/src/sandbox-runtime.js +1 -281
  198. package/dist/shared/src/sandbox-runtime.js.map +1 -1
  199. package/dist/shared/src/sandbox.d.ts +1 -54
  200. package/dist/shared/src/sandbox.js +1 -178
  201. package/dist/shared/src/sandbox.js.map +1 -1
  202. package/dist/shared/src/secrets.d.ts +1 -99
  203. package/dist/shared/src/secrets.js +1 -370
  204. package/dist/shared/src/secrets.js.map +1 -1
  205. package/dist/shared/src/storage.d.ts +1 -341
  206. package/dist/shared/src/storage.js +1 -1003
  207. package/dist/shared/src/storage.js.map +1 -1
  208. package/dist/shared/src/string.d.ts +1 -1
  209. package/dist/shared/src/string.js +1 -29
  210. package/dist/shared/src/string.js.map +1 -1
  211. package/dist/shared/src/time.d.ts +1 -5
  212. package/dist/shared/src/time.js +1 -34
  213. package/dist/shared/src/time.js.map +1 -1
  214. package/dist/shared/src/tls.d.ts +1 -87
  215. package/dist/shared/src/tls.js +1 -153
  216. package/dist/shared/src/tls.js.map +1 -1
  217. package/dist/shared/src/tokenizer.d.ts +1 -54
  218. package/dist/shared/src/tokenizer.js +1 -271
  219. package/dist/shared/src/tokenizer.js.map +1 -1
  220. package/dist/shared/src/types.d.ts +1 -27
  221. package/dist/shared/src/types.js +1 -16
  222. package/dist/shared/src/types.js.map +1 -1
  223. package/dist/shared/src/worker-pool.d.ts +1 -52
  224. package/dist/shared/src/worker-pool.js +1 -228
  225. package/dist/shared/src/worker-pool.js.map +1 -1
  226. package/dist/shared/src/worker-thread.d.ts +1 -21
  227. package/dist/shared/src/worker-thread.js +1 -66
  228. package/dist/shared/src/worker-thread.js.map +1 -1
  229. package/package.json +4 -4
@@ -0,0 +1,1004 @@
1
+ import Database from "better-sqlite3";
2
+ import fs from "fs";
3
+ import os from "os";
4
+ import path from "path";
5
+ import { AUDIT_CHAIN_GENESIS, buildAgentLogChainPayload, chainDigest, readChainAnchor, writeChainAnchor, verifyHashChain } from "../observability/audit-chain.js";
6
+ import { databaseHolder } from "./database.js";
7
+ import { logger } from "../observability/logger.js";
8
+ import { backupDatabase, getMigrationStatus, restoreDatabase, runPendingMigrations, verifyDatabaseIntegrity, } from "./migrations.js";
9
+ import { maskText } from "../security/pii.js";
10
+ import { asAgentID, asLogID, asMessageID, asTaskID, } from "../primitives/types.js";
11
+ let vitestWorkerFrameworkDir = null;
12
+ function getVitestWorkerFrameworkDir() {
13
+ if (!vitestWorkerFrameworkDir) {
14
+ vitestWorkerFrameworkDir = path.join(os.tmpdir(), `atabey-vitest-worker-${process.pid}`, ".atabey");
15
+ }
16
+ return vitestWorkerFrameworkDir;
17
+ }
18
+ /**
19
+ * Resolves the framework directory (.atabey) intelligently by ascending the directory tree,
20
+ * preventing subpackages and test runners from spraying databases in random subfolders.
21
+ */
22
+ export function resolveFrameworkDir() {
23
+ const testDir = process.env.ATABEY_TEST_DIR || process.env.ATABEY_FRAMEWORK_DIR;
24
+ if (testDir)
25
+ return testDir;
26
+ if (process.env.ATABEY_PROJECT_ROOT) {
27
+ return path.join(process.env.ATABEY_PROJECT_ROOT, ".atabey");
28
+ }
29
+ let cur = process.cwd();
30
+ while (cur !== path.parse(cur).root) {
31
+ const pkgJson = path.join(cur, "package.json");
32
+ let isMonorepoRoot = false;
33
+ if (fs.existsSync(pkgJson)) {
34
+ try {
35
+ const pkg = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
36
+ if (pkg.name === "atabey-monorepo" || pkg.frameworkRepo === true || (pkg.workspaces && Array.isArray(pkg.workspaces))) {
37
+ isMonorepoRoot = true;
38
+ }
39
+ }
40
+ catch { /* ignore */ }
41
+ }
42
+ const candidate = path.join(cur, ".atabey");
43
+ if (fs.existsSync(candidate) || isMonorepoRoot) {
44
+ if (isMonorepoRoot && process.env.VITEST) {
45
+ return getVitestWorkerFrameworkDir();
46
+ }
47
+ return candidate;
48
+ }
49
+ cur = path.dirname(cur);
50
+ }
51
+ return path.join(process.cwd(), ".atabey");
52
+ }
53
+ /**
54
+ * [DB] Canonical Atabey Storage Engine (Single Source of Truth)
55
+ * Central SQLite database for enterprise-scale multi-agent state management.
56
+ */
57
+ export class AtabeyStorage {
58
+ static db = null;
59
+ static getDB() {
60
+ if (!this.db) {
61
+ const frameworkDir = resolveFrameworkDir();
62
+ if (!fs.existsSync(frameworkDir)) {
63
+ fs.mkdirSync(frameworkDir, { recursive: true });
64
+ }
65
+ const dbPath = path.join(frameworkDir, "atabey.db");
66
+ this.db = new Database(dbPath, { timeout: 5000 });
67
+ this.db.pragma("journal_mode = WAL");
68
+ databaseHolder.setDB(this.db);
69
+ this.initializeSchema();
70
+ }
71
+ return this.db;
72
+ }
73
+ static initializeSchema() {
74
+ const db = this.db;
75
+ db.exec(`
76
+ CREATE TABLE IF NOT EXISTS agents (
77
+ name TEXT PRIMARY KEY,
78
+ state TEXT DEFAULT 'READY',
79
+ task TEXT DEFAULT 'Idle',
80
+ description TEXT DEFAULT '',
81
+ skills TEXT DEFAULT '[]',
82
+ last_updated DATETIME DEFAULT CURRENT_TIMESTAMP
83
+ )
84
+ `);
85
+ db.exec(`
86
+ CREATE TABLE IF NOT EXISTS messages (
87
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
88
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
89
+ sender TEXT,
90
+ receiver TEXT,
91
+ category TEXT,
92
+ content TEXT,
93
+ trace_id TEXT,
94
+ parent_id TEXT,
95
+ status TEXT DEFAULT 'PENDING',
96
+ priority TEXT DEFAULT 'NORMAL',
97
+ requires_approval BOOLEAN DEFAULT 0
98
+ )
99
+ `);
100
+ db.exec(`
101
+ CREATE TABLE IF NOT EXISTS tasks (
102
+ id TEXT PRIMARY KEY,
103
+ trace_id TEXT,
104
+ description TEXT,
105
+ agent TEXT,
106
+ status TEXT DEFAULT 'PENDING',
107
+ priority TEXT DEFAULT 'NORMAL',
108
+ dependencies TEXT,
109
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
110
+ )
111
+ `);
112
+ db.exec(`
113
+ CREATE TABLE IF NOT EXISTS logs (
114
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
115
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
116
+ agent TEXT,
117
+ action TEXT,
118
+ trace_id TEXT,
119
+ status TEXT,
120
+ summary TEXT,
121
+ findings TEXT,
122
+ prev_hash TEXT,
123
+ hash TEXT
124
+ )
125
+ `);
126
+ try {
127
+ const tableInfo = db.prepare("PRAGMA table_info(logs)").all();
128
+ const columnNames = tableInfo.map(c => c.name);
129
+ if (!columnNames.includes("prev_hash"))
130
+ db.exec("ALTER TABLE logs ADD COLUMN prev_hash TEXT");
131
+ if (!columnNames.includes("hash"))
132
+ db.exec("ALTER TABLE logs ADD COLUMN hash TEXT");
133
+ }
134
+ catch (err) {
135
+ logger.error(`Failed to migrate logs table schema: ${err.message}`);
136
+ }
137
+ db.exec(`
138
+ CREATE TABLE IF NOT EXISTS metadata (
139
+ key TEXT PRIMARY KEY,
140
+ value TEXT
141
+ )
142
+ `);
143
+ db.exec(`
144
+ CREATE TABLE IF NOT EXISTS costs (
145
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
146
+ provider TEXT,
147
+ model TEXT,
148
+ input_tokens INTEGER,
149
+ output_tokens INTEGER,
150
+ cost REAL,
151
+ timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
152
+ trace_id TEXT
153
+ )
154
+ `);
155
+ db.exec(`
156
+ CREATE TABLE IF NOT EXISTS knowledge (
157
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
158
+ filename TEXT UNIQUE,
159
+ content TEXT,
160
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
161
+ )
162
+ `);
163
+ db.exec(`
164
+ CREATE TABLE IF NOT EXISTS users (
165
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
166
+ name TEXT UNIQUE,
167
+ token TEXT UNIQUE,
168
+ role TEXT DEFAULT 'user',
169
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
170
+ )
171
+ `);
172
+ db.exec(`
173
+ CREATE TABLE IF NOT EXISTS loop_cooldowns (
174
+ agent TEXT PRIMARY KEY,
175
+ cooldown_until INTEGER NOT NULL,
176
+ detail TEXT,
177
+ cooldown_count INTEGER DEFAULT 1,
178
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
179
+ )
180
+ `);
181
+ db.exec(`
182
+ CREATE TABLE IF NOT EXISTS approvals (
183
+ trace_id TEXT PRIMARY KEY,
184
+ tool_name TEXT NOT NULL,
185
+ agent TEXT NOT NULL,
186
+ risk_score INTEGER NOT NULL,
187
+ risk_level TEXT NOT NULL,
188
+ reason TEXT NOT NULL,
189
+ args TEXT NOT NULL,
190
+ created_at INTEGER NOT NULL,
191
+ expires_at INTEGER NOT NULL,
192
+ status TEXT DEFAULT 'PENDING',
193
+ passcode TEXT
194
+ )
195
+ `);
196
+ db.exec(`
197
+ CREATE TABLE IF NOT EXISTS discipline (
198
+ agent TEXT PRIMARY KEY,
199
+ total_calls INTEGER DEFAULT 0,
200
+ violations INTEGER DEFAULT 0,
201
+ last_violation TEXT,
202
+ cooldown_until INTEGER DEFAULT 0,
203
+ updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
204
+ )
205
+ `);
206
+ try {
207
+ runPendingMigrations(db);
208
+ const integrity = verifyDatabaseIntegrity(db, "quick");
209
+ if (!integrity.ok) {
210
+ logger.error(`[DB] SQLite startup integrity check failed: ${integrity.messages.join("; ")}`);
211
+ }
212
+ }
213
+ catch (err) {
214
+ const msg = err.message;
215
+ if (/Checksum mismatch/i.test(msg)) {
216
+ // Schema drift or tampering — fail closed instead of running on an unknown schema.
217
+ throw err;
218
+ }
219
+ logger.error(`[DB] Schema migration / integrity check warning: ${msg}`);
220
+ }
221
+ }
222
+ // === KNOWLEDGE PERSISTENCE ===
223
+ static getKnowledgeList() {
224
+ try {
225
+ return this.getDB().prepare("SELECT filename, updated_at FROM knowledge ORDER BY updated_at DESC").all();
226
+ }
227
+ catch (err) {
228
+ logger.debug(`Failed to get knowledge list: ${err.message}`);
229
+ return [];
230
+ }
231
+ }
232
+ static getKnowledgeFile(filename) {
233
+ try {
234
+ const row = this.getDB().prepare("SELECT * FROM knowledge WHERE filename = ?").get(filename);
235
+ return row || null;
236
+ }
237
+ catch (err) {
238
+ logger.debug(`Failed to get knowledge file: ${err.message}`);
239
+ return null;
240
+ }
241
+ }
242
+ static saveKnowledgeFile(filename, content) {
243
+ this.getDB().prepare(`
244
+ INSERT INTO knowledge (filename, content, updated_at) VALUES (?, ?, datetime('now'))
245
+ ON CONFLICT(filename) DO UPDATE SET content = excluded.content, updated_at = datetime('now')
246
+ `).run(filename, content);
247
+ }
248
+ static deleteKnowledgeFile(filename) {
249
+ const info = this.getDB().prepare("DELETE FROM knowledge WHERE filename = ?").run(filename);
250
+ return info.changes > 0;
251
+ }
252
+ // === USER PERSISTENCE ===
253
+ static hasUsers() {
254
+ try {
255
+ const row = this.getDB().prepare("SELECT COUNT(*) as count FROM users").get();
256
+ return row.count > 0;
257
+ }
258
+ catch {
259
+ return false;
260
+ }
261
+ }
262
+ static getUsers() {
263
+ try {
264
+ return this.getDB().prepare("SELECT name, token, role FROM users ORDER BY id ASC").all();
265
+ }
266
+ catch (err) {
267
+ logger.debug(`Failed to get users: ${err.message}`);
268
+ return [];
269
+ }
270
+ }
271
+ static getUserByToken(token) {
272
+ try {
273
+ const row = this.getDB().prepare("SELECT name, role FROM users WHERE token = ?").get(token);
274
+ return row || null;
275
+ }
276
+ catch (err) {
277
+ logger.debug(`Failed to get user by token: ${err.message}`);
278
+ return null;
279
+ }
280
+ }
281
+ static createUser(name, token, role) {
282
+ this.getDB().prepare("INSERT INTO users (name, token, role) VALUES (?, ?, ?)").run(name, token, role);
283
+ }
284
+ static deleteUser(name) {
285
+ const info = this.getDB().prepare("DELETE FROM users WHERE name = ?").run(name);
286
+ return info.changes > 0;
287
+ }
288
+ // === AGENT DETAILS PERSISTENCE ===
289
+ static createAgent(name, description, skills) {
290
+ const cleanName = name.replace("@", "");
291
+ this.getDB().prepare(`
292
+ INSERT INTO agents (name, state, task, description, skills, last_updated)
293
+ VALUES (?, 'READY', 'Idle', ?, ?, datetime('now'))
294
+ ON CONFLICT(name) DO UPDATE SET description = excluded.description, skills = excluded.skills
295
+ `).run(cleanName, description, skills);
296
+ }
297
+ static deleteAgent(name) {
298
+ const cleanName = name.replace("@", "");
299
+ const info = this.getDB().prepare("DELETE FROM agents WHERE name = ?").run(cleanName);
300
+ return info.changes > 0;
301
+ }
302
+ static updateAgentDetails(name, description, state, task, skills) {
303
+ const cleanName = name.replace("@", "");
304
+ this.getDB().prepare(`
305
+ INSERT INTO agents (name, state, task, description, skills, last_updated)
306
+ VALUES (?, ?, ?, ?, ?, datetime('now'))
307
+ ON CONFLICT(name) DO UPDATE SET
308
+ description = excluded.description,
309
+ state = excluded.state,
310
+ task = excluded.task,
311
+ skills = excluded.skills,
312
+ last_updated = datetime('now')
313
+ `).run(cleanName, state, task, description, skills);
314
+ }
315
+ // === EVENT HOOKS FOR REAL-TIME BROADCASTS ===
316
+ static onMessageSaved = null;
317
+ static onMessageStatusUpdated = null;
318
+ static saveLog(log) {
319
+ const db = this.getDB();
320
+ const timestamp = new Date().toISOString();
321
+ const agent = log.agent.replace("@", "");
322
+ const maskedSummary = maskText(log.summary);
323
+ const maskedFindings = log.findings ? maskText(log.findings) : null;
324
+ const traceId = log.trace_id || "";
325
+ let lastId = 0;
326
+ const insert = db.transaction(() => {
327
+ let prevHash = AUDIT_CHAIN_GENESIS;
328
+ try {
329
+ const lastRow = db.prepare("SELECT hash FROM logs WHERE hash IS NOT NULL ORDER BY id DESC LIMIT 1").get();
330
+ if (lastRow && lastRow.hash) {
331
+ prevHash = lastRow.hash;
332
+ }
333
+ }
334
+ catch { /* best-effort fallback to genesis */ }
335
+ const payload = buildAgentLogChainPayload({
336
+ prevHash,
337
+ timestamp,
338
+ agent,
339
+ action: log.action,
340
+ traceId: traceId || "",
341
+ status: log.status,
342
+ summary: maskedSummary,
343
+ });
344
+ const hash = chainDigest(payload);
345
+ const result = db.prepare(`
346
+ INSERT INTO logs (timestamp, agent, action, trace_id, status, summary, findings, prev_hash, hash)
347
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
348
+ `).run(timestamp, agent, log.action, log.trace_id || null, log.status, maskedSummary, maskedFindings, prevHash, hash);
349
+ lastId = Number(result.lastInsertRowid);
350
+ const count = db.prepare("SELECT COUNT(*) AS c FROM logs WHERE hash IS NOT NULL").get().c;
351
+ writeChainAnchor("agent-log", hash, count);
352
+ });
353
+ insert();
354
+ return lastId;
355
+ }
356
+ static verifyLogIntegrity() {
357
+ const db = this.getDB();
358
+ try {
359
+ const rows = db.prepare("SELECT * FROM logs ORDER BY id ASC").all();
360
+ const hashed = rows.filter((r) => r.hash);
361
+ if (hashed.length === 0) {
362
+ return { valid: true, checked: 0, reason: "No hashed log rows yet" };
363
+ }
364
+ const anchor = readChainAnchor("agent-log");
365
+ return verifyHashChain(hashed, (row, prevHash) => buildAgentLogChainPayload({
366
+ prevHash,
367
+ agent: row.agent,
368
+ action: row.action,
369
+ traceId: row.trace_id || "",
370
+ status: row.status,
371
+ summary: row.summary,
372
+ timestamp: row.timestamp,
373
+ }), { expectedTip: anchor?.tip, expectedCount: anchor?.count });
374
+ }
375
+ catch (err) {
376
+ return { valid: false, reason: `Verification error: ${err.message}` };
377
+ }
378
+ }
379
+ static saveCost(cost) {
380
+ const db = this.getDB();
381
+ db.prepare(`
382
+ INSERT INTO costs (provider, model, input_tokens, output_tokens, cost, timestamp, trace_id)
383
+ VALUES (?, ?, ?, ?, ?, ?, ?)
384
+ `).run(cost.provider, cost.model, cost.inputTokens, cost.outputTokens, cost.cost, cost.timestamp, cost.traceId || null);
385
+ }
386
+ static getAllCosts() {
387
+ const db = this.getDB();
388
+ const rows = db.prepare("SELECT * FROM costs ORDER BY timestamp ASC").all();
389
+ return rows.map(r => ({
390
+ provider: r.provider,
391
+ model: r.model,
392
+ inputTokens: r.input_tokens,
393
+ outputTokens: r.output_tokens,
394
+ cost: r.cost,
395
+ timestamp: r.timestamp,
396
+ traceId: r.trace_id || undefined
397
+ }));
398
+ }
399
+ static updateAgentStatus(name, state, task, lastUpdated) {
400
+ const cleanName = name.replace("@", "");
401
+ const timestamp = lastUpdated || new Date().toISOString();
402
+ this.getDB().prepare(`
403
+ INSERT INTO agents (name, state, task, last_updated)
404
+ VALUES (?, ?, ?, ?)
405
+ ON CONFLICT(name) DO UPDATE SET
406
+ state = excluded.state,
407
+ task = excluded.task,
408
+ last_updated = excluded.last_updated
409
+ `).run(cleanName, state, task, timestamp);
410
+ }
411
+ static getAllAgents() {
412
+ const db = this.getDB();
413
+ const rows = db.prepare("SELECT * FROM agents").all();
414
+ return rows.map(r => ({
415
+ name: asAgentID(r.name),
416
+ state: r.state,
417
+ task: r.task,
418
+ last_updated: r.last_updated
419
+ }));
420
+ }
421
+ static saveMessage(msg) {
422
+ const from = msg.from.replace("@", "");
423
+ const to = msg.to.replace("@", "");
424
+ const cleanContent = maskText(msg.content);
425
+ this.getDB().prepare(`
426
+ INSERT INTO messages (timestamp, sender, receiver, category, content, trace_id, parent_id, status, priority, requires_approval)
427
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
428
+ `).run(msg.timestamp, from, to, msg.category, cleanContent, msg.traceId, msg.parentId || null, msg.status, msg.priority, msg.requiresApproval ? 1 : 0);
429
+ if (this.onMessageSaved) {
430
+ try {
431
+ this.onMessageSaved({ traceId: msg.traceId, category: msg.category });
432
+ }
433
+ catch (err) {
434
+ logger.debug(`onMessageSaved hook error: ${err.message}`);
435
+ }
436
+ }
437
+ }
438
+ static getPendingMessages() {
439
+ const db = this.getDB();
440
+ const rows = db.prepare("SELECT * FROM messages WHERE status = 'PENDING'").all();
441
+ return rows.map(r => ({
442
+ id: asMessageID(r.id),
443
+ timestamp: r.timestamp,
444
+ from: asAgentID(r.sender),
445
+ to: asAgentID(r.receiver),
446
+ category: r.category,
447
+ content: maskText(r.content),
448
+ traceId: r.trace_id,
449
+ parentId: r.parent_id || undefined,
450
+ status: r.status,
451
+ priority: r.priority,
452
+ requiresApproval: r.requires_approval === 1
453
+ }));
454
+ }
455
+ static updateMessageStatus(id, status) {
456
+ this.getDB().prepare("UPDATE messages SET status = ? WHERE id = ?").run(status, id);
457
+ if (this.onMessageStatusUpdated) {
458
+ try {
459
+ this.onMessageStatusUpdated(id, status);
460
+ }
461
+ catch (err) {
462
+ logger.debug(`onMessageStatusUpdated hook error: ${err.message}`);
463
+ }
464
+ }
465
+ }
466
+ static saveTask(task) {
467
+ const agent = task.agent;
468
+ const cleanDesc = task.description;
469
+ this.getDB().prepare(`
470
+ INSERT INTO tasks (id, trace_id, description, agent, status, priority, dependencies, created_at)
471
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
472
+ ON CONFLICT(id) DO UPDATE SET
473
+ status = excluded.status,
474
+ priority = excluded.priority,
475
+ dependencies = excluded.dependencies,
476
+ agent = excluded.agent
477
+ `).run(task.id, task.traceId, cleanDesc, agent, task.status, task.priority, JSON.stringify(task.dependencies), task.createdAt || new Date().toISOString());
478
+ }
479
+ static getTasks(traceId) {
480
+ const db = this.getDB();
481
+ let rows;
482
+ if (traceId) {
483
+ rows = db.prepare("SELECT * FROM tasks WHERE trace_id = ?").all(traceId);
484
+ }
485
+ else {
486
+ rows = db.prepare("SELECT * FROM tasks").all();
487
+ }
488
+ return rows.map(r => {
489
+ let deps;
490
+ try {
491
+ deps = JSON.parse(r.dependencies || "[]");
492
+ }
493
+ catch {
494
+ deps = [];
495
+ }
496
+ return {
497
+ id: asTaskID(r.id),
498
+ traceId: r.trace_id,
499
+ description: maskText(r.description),
500
+ agent: asAgentID(r.agent),
501
+ status: r.status,
502
+ priority: r.priority,
503
+ dependencies: deps,
504
+ createdAt: r.created_at
505
+ };
506
+ });
507
+ }
508
+ static getLogs(agentName) {
509
+ const db = this.getDB();
510
+ let rows;
511
+ const cleanName = agentName ? agentName.replace("@", "") : undefined;
512
+ if (cleanName) {
513
+ rows = db.prepare("SELECT * FROM logs WHERE agent = ? ORDER BY timestamp DESC").all(cleanName);
514
+ }
515
+ else {
516
+ rows = db.prepare("SELECT * FROM logs ORDER BY timestamp DESC").all();
517
+ }
518
+ return rows.map(r => ({
519
+ id: asLogID(r.id),
520
+ timestamp: r.timestamp,
521
+ agent: asAgentID(r.agent),
522
+ action: r.action,
523
+ trace_id: r.trace_id || undefined,
524
+ status: r.status,
525
+ summary: maskText(r.summary),
526
+ findings: r.findings ? maskText(r.findings) : undefined,
527
+ prev_hash: r.prev_hash || undefined,
528
+ hash: r.hash || undefined
529
+ }));
530
+ }
531
+ static setMetadata(key, value) {
532
+ this.getDB().prepare(`
533
+ INSERT INTO metadata (key, value) VALUES (?, ?)
534
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value
535
+ `).run(key, value);
536
+ }
537
+ static getMetadata(key) {
538
+ const row = this.getDB().prepare("SELECT value FROM metadata WHERE key = ?").get(key);
539
+ return row ? row.value : null;
540
+ }
541
+ // === LOOP COOLDOWN PERSISTENCE ===
542
+ static saveLoopCooldown(agent, cooldownUntil, detail, cooldownCount = 1) {
543
+ const cleanAgent = agent.replace("@", "");
544
+ this.getDB().prepare(`
545
+ INSERT INTO loop_cooldowns (agent, cooldown_until, detail, cooldown_count, updated_at)
546
+ VALUES (?, ?, ?, ?, datetime('now'))
547
+ ON CONFLICT(agent) DO UPDATE SET
548
+ cooldown_until = excluded.cooldown_until,
549
+ detail = excluded.detail,
550
+ cooldown_count = excluded.cooldown_count,
551
+ updated_at = datetime('now')
552
+ `).run(cleanAgent, cooldownUntil, detail, cooldownCount);
553
+ }
554
+ static getLoopCooldown(agent) {
555
+ const cleanAgent = agent.replace("@", "");
556
+ try {
557
+ const row = this.getDB().prepare("SELECT * FROM loop_cooldowns WHERE agent = ?").get(cleanAgent);
558
+ if (!row)
559
+ return null;
560
+ return {
561
+ agent: row.agent,
562
+ cooldownUntil: row.cooldown_until,
563
+ detail: row.detail,
564
+ cooldownCount: row.cooldown_count
565
+ };
566
+ }
567
+ catch (err) {
568
+ logger.debug(`Failed to get loop cooldown: ${err.message}`);
569
+ return null;
570
+ }
571
+ }
572
+ static clearLoopCooldown(agent) {
573
+ const cleanAgent = agent.replace("@", "");
574
+ const info = this.getDB().prepare("DELETE FROM loop_cooldowns WHERE agent = ?").run(cleanAgent);
575
+ return info.changes > 0;
576
+ }
577
+ static clearAllLoopCooldowns() {
578
+ try {
579
+ this.getDB().prepare("DELETE FROM loop_cooldowns").run();
580
+ }
581
+ catch (err) {
582
+ logger.debug(`Failed to clear all loop cooldowns: ${err.message}`);
583
+ }
584
+ }
585
+ static listActiveLoopCooldowns() {
586
+ const now = Date.now();
587
+ try {
588
+ const rows = this.getDB().prepare("SELECT * FROM loop_cooldowns WHERE cooldown_until > ?").all(now);
589
+ return rows.map(r => ({
590
+ agent: r.agent,
591
+ cooldownUntil: r.cooldown_until,
592
+ detail: r.detail,
593
+ cooldownCount: r.cooldown_count
594
+ }));
595
+ }
596
+ catch (err) {
597
+ logger.debug(`Failed to list active loop cooldowns: ${err.message}`);
598
+ return [];
599
+ }
600
+ }
601
+ static purgeExpiredLoopCooldowns() {
602
+ const now = Date.now();
603
+ try {
604
+ const info = this.getDB().prepare("DELETE FROM loop_cooldowns WHERE cooldown_until <= ?").run(now);
605
+ return info.changes;
606
+ }
607
+ catch (err) {
608
+ logger.debug(`Failed to purge expired loop cooldowns: ${err.message}`);
609
+ return 0;
610
+ }
611
+ }
612
+ // === APPROVALS PERSISTENCE ===
613
+ static saveApproval(app) {
614
+ this.saveApprovalRequest(app);
615
+ }
616
+ static saveApprovalRequest(request) {
617
+ const db = this.getDB();
618
+ db.prepare(`
619
+ INSERT OR REPLACE INTO approvals (
620
+ trace_id, tool_name, agent, risk_score, risk_level, reason, args,
621
+ created_at, expires_at, status, passcode
622
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
623
+ `).run(request.traceId, request.toolName, request.agent, request.riskScore, request.riskLevel, request.reason, JSON.stringify(request.args), request.createdAt, request.expiresAt, request.status || "PENDING", request.passcode || null);
624
+ }
625
+ static getApproval(traceId) {
626
+ try {
627
+ const row = this.getDB().prepare("SELECT * FROM approvals WHERE trace_id = ?").get(traceId);
628
+ if (!row)
629
+ return null;
630
+ return {
631
+ traceId: row.trace_id,
632
+ toolName: row.tool_name,
633
+ agent: row.agent,
634
+ riskScore: row.risk_score,
635
+ riskLevel: row.risk_level,
636
+ reason: row.reason,
637
+ args: JSON.parse(row.args || "{}"),
638
+ createdAt: row.created_at,
639
+ expiresAt: row.expires_at,
640
+ status: row.status,
641
+ passcode: row.passcode || undefined
642
+ };
643
+ }
644
+ catch (err) {
645
+ logger.debug(`Failed to get approval: ${err.message}`);
646
+ return null;
647
+ }
648
+ }
649
+ static getPendingApprovals() {
650
+ try {
651
+ const rows = this.getDB().prepare("SELECT * FROM approvals WHERE status = 'PENDING'").all();
652
+ return rows.map(row => ({
653
+ traceId: row.trace_id,
654
+ toolName: row.tool_name,
655
+ agent: row.agent,
656
+ riskScore: row.risk_score,
657
+ riskLevel: row.risk_level,
658
+ reason: row.reason,
659
+ args: JSON.parse(row.args || "{}"),
660
+ createdAt: row.created_at,
661
+ expiresAt: row.expires_at,
662
+ status: row.status,
663
+ passcode: row.passcode || undefined
664
+ }));
665
+ }
666
+ catch (err) {
667
+ logger.debug(`Failed to get pending approvals: ${err.message}`);
668
+ return [];
669
+ }
670
+ }
671
+ static updateApprovalStatus(traceId, status) {
672
+ this.getDB().prepare("UPDATE approvals SET status = ? WHERE trace_id = ?").run(status, traceId);
673
+ }
674
+ static clearAllApprovals() {
675
+ try {
676
+ this.getDB().prepare("DELETE FROM approvals").run();
677
+ }
678
+ catch (err) {
679
+ logger.debug(`Failed to clear approvals: ${err.message}`);
680
+ }
681
+ }
682
+ // === DISCIPLINE PERSISTENCE ===
683
+ static saveDiscipline(agent, stats) {
684
+ const cleanAgent = agent.replace("@", "");
685
+ this.getDB().prepare(`
686
+ INSERT INTO discipline (agent, total_calls, violations, last_violation, cooldown_until, updated_at)
687
+ VALUES (?, ?, ?, ?, ?, datetime('now'))
688
+ ON CONFLICT(agent) DO UPDATE SET
689
+ total_calls = excluded.total_calls,
690
+ violations = excluded.violations,
691
+ last_violation = excluded.last_violation,
692
+ cooldown_until = excluded.cooldown_until,
693
+ updated_at = datetime('now')
694
+ `).run(cleanAgent, stats.totalCalls, stats.violations, stats.lastViolation, stats.cooldownUntil);
695
+ }
696
+ static getDiscipline(agent) {
697
+ const cleanAgent = agent.replace("@", "");
698
+ try {
699
+ const row = this.getDB().prepare("SELECT * FROM discipline WHERE agent = ?").get(cleanAgent);
700
+ if (!row)
701
+ return null;
702
+ return {
703
+ agent: row.agent,
704
+ totalCalls: row.total_calls,
705
+ violations: row.violations,
706
+ lastViolation: row.last_violation,
707
+ cooldownUntil: row.cooldown_until
708
+ };
709
+ }
710
+ catch (err) {
711
+ logger.debug(`Failed to get discipline: ${err.message}`);
712
+ return null;
713
+ }
714
+ }
715
+ static getAllDiscipline() {
716
+ try {
717
+ const rows = this.getDB().prepare("SELECT * FROM discipline").all();
718
+ return rows.map(row => ({
719
+ agent: row.agent,
720
+ totalCalls: row.total_calls,
721
+ violations: row.violations,
722
+ lastViolation: row.last_violation,
723
+ cooldownUntil: row.cooldown_until
724
+ }));
725
+ }
726
+ catch (err) {
727
+ logger.debug(`Failed to get all discipline: ${err.message}`);
728
+ return [];
729
+ }
730
+ }
731
+ static clearAllDiscipline() {
732
+ try {
733
+ this.getDB().prepare("DELETE FROM discipline").run();
734
+ }
735
+ catch (err) {
736
+ logger.debug(`Failed to clear discipline: ${err.message}`);
737
+ }
738
+ }
739
+ // === DISCIPLINE CALL HISTORY (restart-durable rate limiting) ===
740
+ static recordDisciplineCall(agent, tool, timestamp, argsSize) {
741
+ const cleanAgent = agent.replace("@", "");
742
+ try {
743
+ this.getDB().prepare("INSERT INTO discipline_calls (agent, tool, ts, args_size) VALUES (?, ?, ?, ?)").run(cleanAgent, tool, timestamp, argsSize);
744
+ }
745
+ catch (err) {
746
+ logger.debug(`Failed to record discipline call: ${err.message}`);
747
+ }
748
+ }
749
+ static getRecentDisciplineCalls(agent, sinceTs) {
750
+ const cleanAgent = agent.replace("@", "");
751
+ try {
752
+ const rows = this.getDB().prepare("SELECT tool, ts, args_size FROM discipline_calls WHERE agent = ? AND ts > ? ORDER BY ts ASC").all(cleanAgent, sinceTs);
753
+ return rows.map(r => ({ tool: r.tool, timestamp: r.ts, argsSize: r.args_size }));
754
+ }
755
+ catch (err) {
756
+ logger.debug(`Failed to get recent discipline calls: ${err.message}`);
757
+ return [];
758
+ }
759
+ }
760
+ static pruneDisciplineCalls(beforeTs) {
761
+ try {
762
+ this.getDB().prepare("DELETE FROM discipline_calls WHERE ts < ?").run(beforeTs);
763
+ }
764
+ catch (err) {
765
+ logger.debug(`Failed to prune discipline calls: ${err.message}`);
766
+ }
767
+ }
768
+ static clearDisciplineCalls(agent) {
769
+ try {
770
+ if (agent) {
771
+ this.getDB().prepare("DELETE FROM discipline_calls WHERE agent = ?").run(agent.replace("@", ""));
772
+ }
773
+ else {
774
+ this.getDB().prepare("DELETE FROM discipline_calls").run();
775
+ }
776
+ }
777
+ catch (err) {
778
+ logger.debug(`Failed to clear discipline calls: ${err.message}`);
779
+ }
780
+ }
781
+ // === CODE GRAPH (incremental symbol/file index) ===
782
+ static getCodeFileHash(filePath) {
783
+ try {
784
+ const row = this.getDB().prepare("SELECT hash FROM code_files WHERE path = ?").get(filePath);
785
+ return row?.hash ?? null;
786
+ }
787
+ catch (err) {
788
+ logger.debug(`Failed to get code file hash: ${err.message}`);
789
+ return null;
790
+ }
791
+ }
792
+ static upsertCodeFile(filePath, hash) {
793
+ this.getDB().prepare(`
794
+ INSERT INTO code_files (path, hash, updated_at) VALUES (?, ?, datetime('now'))
795
+ ON CONFLICT(path) DO UPDATE SET hash = excluded.hash, updated_at = datetime('now')
796
+ `).run(filePath, hash);
797
+ }
798
+ static replaceCodeSymbols(filePath, symbols) {
799
+ const db = this.getDB();
800
+ const tx = db.transaction(() => {
801
+ db.prepare("DELETE FROM code_symbols WHERE file = ?").run(filePath);
802
+ const ins = db.prepare("INSERT INTO code_symbols (file, name, kind, start_line, end_line, signature, exported) VALUES (?, ?, ?, ?, ?, ?, ?)");
803
+ for (const s of symbols) {
804
+ ins.run(filePath, s.name, s.kind, s.startLine, s.endLine, s.signature, s.exported ? 1 : 0);
805
+ }
806
+ });
807
+ tx();
808
+ }
809
+ static replaceCodeImports(filePath, specifiers) {
810
+ const db = this.getDB();
811
+ const tx = db.transaction(() => {
812
+ db.prepare("DELETE FROM code_imports WHERE file = ?").run(filePath);
813
+ const ins = db.prepare("INSERT INTO code_imports (file, specifier) VALUES (?, ?)");
814
+ for (const s of specifiers)
815
+ ins.run(filePath, s);
816
+ });
817
+ tx();
818
+ }
819
+ static deleteCodeFile(filePath) {
820
+ const db = this.getDB();
821
+ const tx = db.transaction(() => {
822
+ db.prepare("DELETE FROM code_symbols WHERE file = ?").run(filePath);
823
+ db.prepare("DELETE FROM code_imports WHERE file = ?").run(filePath);
824
+ db.prepare("DELETE FROM code_files WHERE path = ?").run(filePath);
825
+ });
826
+ tx();
827
+ }
828
+ static getAllCodeFilePaths() {
829
+ try {
830
+ return this.getDB().prepare("SELECT path FROM code_files").all().map((r) => r.path);
831
+ }
832
+ catch (err) {
833
+ logger.debug(`Failed to list code files: ${err.message}`);
834
+ return [];
835
+ }
836
+ }
837
+ static searchCodeSymbols(query, limit = 20) {
838
+ try {
839
+ const rows = this.getDB()
840
+ .prepare("SELECT file, name, kind, start_line, end_line, signature, exported FROM code_symbols " +
841
+ "WHERE name LIKE ? ORDER BY (name = ?) DESC, length(name) ASC LIMIT ?")
842
+ .all(`%${query}%`, query, limit);
843
+ return rows.map((r) => ({
844
+ file: r.file,
845
+ name: r.name,
846
+ kind: r.kind,
847
+ startLine: r.start_line,
848
+ endLine: r.end_line,
849
+ signature: r.signature,
850
+ exported: r.exported === 1,
851
+ }));
852
+ }
853
+ catch (err) {
854
+ logger.debug(`Failed to search code symbols: ${err.message}`);
855
+ return [];
856
+ }
857
+ }
858
+ static getCodeGraphStats() {
859
+ try {
860
+ const db = this.getDB();
861
+ const files = db.prepare("SELECT COUNT(*) AS c FROM code_files").get().c;
862
+ const symbols = db.prepare("SELECT COUNT(*) AS c FROM code_symbols").get().c;
863
+ const imports = db.prepare("SELECT COUNT(*) AS c FROM code_imports").get().c;
864
+ return { files, symbols, imports };
865
+ }
866
+ catch (err) {
867
+ logger.debug(`Failed to read code graph stats: ${err.message}`);
868
+ return { files: 0, symbols: 0, imports: 0 };
869
+ }
870
+ }
871
+ static clearCodeGraph() {
872
+ const db = this.getDB();
873
+ const tx = db.transaction(() => {
874
+ db.prepare("DELETE FROM code_symbols").run();
875
+ db.prepare("DELETE FROM code_imports").run();
876
+ db.prepare("DELETE FROM code_files").run();
877
+ });
878
+ tx();
879
+ }
880
+ static checkIntegrity() {
881
+ try {
882
+ const db = this.getDB();
883
+ const res = verifyDatabaseIntegrity(db, "quick");
884
+ return { ok: res.ok, message: res.messages.join("; ") || "unknown" };
885
+ }
886
+ catch (err) {
887
+ return { ok: false, message: err.message };
888
+ }
889
+ }
890
+ static verifyDatabaseIntegrity(mode = "full") {
891
+ return verifyDatabaseIntegrity(this.getDB(), mode);
892
+ }
893
+ static getMigrationStatus() {
894
+ return getMigrationStatus(this.getDB());
895
+ }
896
+ static backup(destinationPath) {
897
+ const db = this.getDB();
898
+ const frameworkDir = resolveFrameworkDir();
899
+ const defaultDest = path.join(frameworkDir, "backups", `atabey-${new Date().toISOString().replace(/[:.]/g, "-")}.db`);
900
+ return backupDatabase(db, destinationPath || defaultDest);
901
+ }
902
+ static restore(backupPath, targetDbPath) {
903
+ const frameworkDir = resolveFrameworkDir();
904
+ const target = targetDbPath || path.join(frameworkDir, "atabey.db");
905
+ this.reset();
906
+ return restoreDatabase(backupPath, target);
907
+ }
908
+ // === API CATALOG (endpoint registry) ===
909
+ static saveApiEndpoint(endpoint) {
910
+ this.getDB().prepare(`
911
+ INSERT INTO api_endpoints (method, path, tag, summary, auth_required, request_schema, response_schema, source, updated_at)
912
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
913
+ ON CONFLICT(method, path) DO UPDATE SET
914
+ tag = excluded.tag,
915
+ summary = excluded.summary,
916
+ auth_required = excluded.auth_required,
917
+ request_schema = excluded.request_schema,
918
+ response_schema = excluded.response_schema,
919
+ source = excluded.source,
920
+ updated_at = excluded.updated_at
921
+ `).run(endpoint.method.toUpperCase(), endpoint.path, endpoint.tag ?? null, endpoint.summary ?? null, endpoint.authRequired ? 1 : 0, endpoint.requestSchema ?? null, endpoint.responseSchema ?? null, endpoint.source ?? null, endpoint.updatedAt || new Date().toISOString());
922
+ }
923
+ static getApiEndpoints(filter = {}) {
924
+ const conditions = [];
925
+ const params = [];
926
+ if (filter.tag) {
927
+ conditions.push("tag = ?");
928
+ params.push(filter.tag);
929
+ }
930
+ if (filter.method) {
931
+ conditions.push("method = ?");
932
+ params.push(filter.method.toUpperCase());
933
+ }
934
+ if (filter.search) {
935
+ conditions.push("(LOWER(path) LIKE ? OR LOWER(request_schema) LIKE ? OR LOWER(response_schema) LIKE ? OR LOWER(summary) LIKE ?)");
936
+ const like = `%${filter.search.toLowerCase()}%`;
937
+ params.push(like, like, like, like);
938
+ }
939
+ const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
940
+ const rows = this.getDB().prepare(`SELECT method, path, tag, summary, auth_required, request_schema, response_schema, source, updated_at
941
+ FROM api_endpoints ${where} ORDER BY path ASC, method ASC`).all(...params);
942
+ return rows.map((r) => this.rowToApiEndpoint(r));
943
+ }
944
+ static findApiEndpoint(pathQuery, method) {
945
+ const conditions = ["path = ?"];
946
+ const params = [pathQuery];
947
+ if (method) {
948
+ conditions.push("method = ?");
949
+ params.push(method.toUpperCase());
950
+ }
951
+ let rows = this.getDB().prepare(`SELECT method, path, tag, summary, auth_required, request_schema, response_schema, source, updated_at
952
+ FROM api_endpoints WHERE ${conditions.join(" AND ")}`).all(...params);
953
+ if (rows.length === 0) {
954
+ // Fallback: substring match when exact path not found.
955
+ rows = this.getDB().prepare(`SELECT method, path, tag, summary, auth_required, request_schema, response_schema, source, updated_at
956
+ FROM api_endpoints WHERE path LIKE ?${method ? " AND method = ?" : ""}`).all(`%${pathQuery}%`, ...(method ? [method.toUpperCase()] : []));
957
+ }
958
+ return rows.map((r) => this.rowToApiEndpoint(r));
959
+ }
960
+ static deleteApiEndpoint(method, path) {
961
+ return this.getDB().prepare("DELETE FROM api_endpoints WHERE method = ? AND path = ?")
962
+ .run(method.toUpperCase(), path).changes > 0;
963
+ }
964
+ /** Replace the whole catalog with a fresh scan; returns upserted/deleted counts. */
965
+ static replaceApiCatalog(endpoints) {
966
+ const db = this.getDB();
967
+ const tx = db.transaction(() => {
968
+ const keys = new Set(endpoints.map((e) => `${e.method.toUpperCase()} ${e.path}`));
969
+ const existing = db.prepare("SELECT method, path FROM api_endpoints").all();
970
+ let deleted = 0;
971
+ for (const row of existing) {
972
+ if (!keys.has(`${row.method} ${row.path}`)) {
973
+ deleted += db.prepare("DELETE FROM api_endpoints WHERE method = ? AND path = ?").run(row.method, row.path).changes;
974
+ }
975
+ }
976
+ for (const e of endpoints)
977
+ this.saveApiEndpoint(e);
978
+ return { upserted: endpoints.length, deleted };
979
+ });
980
+ return tx();
981
+ }
982
+ static rowToApiEndpoint(r) {
983
+ return {
984
+ method: String(r.method),
985
+ path: String(r.path),
986
+ tag: r.tag ?? undefined,
987
+ summary: r.summary ?? undefined,
988
+ authRequired: Number(r.auth_required) === 1,
989
+ requestSchema: r.request_schema ?? undefined,
990
+ responseSchema: r.response_schema ?? undefined,
991
+ source: r.source ?? undefined,
992
+ updatedAt: String(r.updated_at),
993
+ };
994
+ }
995
+ static reset() {
996
+ if (this.db) {
997
+ this.db.close();
998
+ this.db = null;
999
+ }
1000
+ }
1001
+ }
1002
+ /** Canonical alias */
1003
+ export const Storage = AtabeyStorage;
1004
+ //# sourceMappingURL=storage.js.map