@cgh567/agent 2.4.2 → 2.4.3
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.
- package/daemon/adapters/tui_wakeup.js +8 -0
- package/daemon/daemon-manager.js +1 -1
- package/daemon/db/email-infrastructure-migrate.js +192 -0
- package/daemon/db/hbo-core-migrate.js +189 -0
- package/daemon/helios-api.js +574 -20
- package/daemon/helios-company-daemon.js +103 -13
- package/daemon/lib/hbo-bridge.js +1 -1
- package/daemon/lib/hed-engine.js +25 -0
- package/daemon/lib/task-completion-processor.js +11 -0
- package/daemon/lib/wizard-engine.js +57 -6
- package/daemon/routes/hbo.js +253 -47
- package/daemon/routes/project.js +190 -59
- package/daemon/routes/routines.js +14 -0
- package/daemon/routes/tasks.js +15 -1
- package/daemon/schema-apply.js +174 -0
- package/daemon/schema-definitions.js +423 -0
- package/daemon/schema-migrations-hbo.js +10 -0
- package/daemon/schema-migrations-hed.js +18 -0
- package/daemon/schema-migrations-proj.js +131 -0
- package/extensions/cortex/wal-replay.ts +91 -0
- package/extensions/hema-dispatch-v3/index.ts +13 -7
- package/extensions/warm-tick/warm-tick-maintenance.ts +8 -0
- package/lib/__tests__/hbo-core-store.test.js +238 -0
- package/lib/event-bus.mts +1 -1
- package/lib/graph-availability.js +62 -0
- package/lib/hbo-core-store.compiled.js +834 -0
- package/lib/hbo-core-store.js +124 -0
- package/lib/hbo-core-store.ts +908 -0
- package/lib/triage-core/classifier.ts +3 -2
- package/lib/triage-core/graph/schema.cypher +10 -0
- package/lib/triage-core/mental-model/key-facts.ts +1 -2
- package/lib/triage-core/orchestrator.ts +4 -11
- package/package.json +9 -5
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hbo-core-store.compiled.js
|
|
3
|
+
* Pre-compiled CJS version of hbo-core-store.ts — no TypeScript runtime required.
|
|
4
|
+
*
|
|
5
|
+
* Generated from hbo-core-store.ts by stripping type annotations.
|
|
6
|
+
* This file ships in the npm tarball so the store loads on Node 16+ without
|
|
7
|
+
* jiti, ts-node, or --experimental-sqlite (uses better-sqlite3 as fallback).
|
|
8
|
+
* node:sqlite is used when available (Node >= 22.13 without flags, or
|
|
9
|
+
* Node >= 22.5 with --experimental-sqlite).
|
|
10
|
+
*
|
|
11
|
+
* DO NOT EDIT this file directly. Edit hbo-core-store.ts and regenerate.
|
|
12
|
+
* Regenerate with: node -e "require('./lib/hbo-core-store.js')" (uses native strip path)
|
|
13
|
+
*/
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const os = require('os');
|
|
18
|
+
const { mkdirSync } = require('fs');
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Engine detection — node:sqlite (Node 22+) preferred, better-sqlite3 fallback
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
let _engine = null;
|
|
25
|
+
|
|
26
|
+
function _openDatabase(dbPath) {
|
|
27
|
+
// Attempt 1: node:sqlite built-in (Node >= 22.13 no flags needed; Node >= 22.5 with --experimental-sqlite)
|
|
28
|
+
try {
|
|
29
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
30
|
+
const db = new DatabaseSync(dbPath);
|
|
31
|
+
_engine = 'node-sqlite';
|
|
32
|
+
return db;
|
|
33
|
+
} catch (_nodeSqliteErr) {
|
|
34
|
+
// node:sqlite unavailable (Node < 22.13 without flag, or Node < 22.5)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Attempt 2: better-sqlite3 — synchronous, compiled native module, Node >= 16
|
|
38
|
+
try {
|
|
39
|
+
const BetterSqlite3 = require('better-sqlite3');
|
|
40
|
+
const db = new BetterSqlite3(dbPath);
|
|
41
|
+
_engine = 'better-sqlite3';
|
|
42
|
+
return db;
|
|
43
|
+
} catch (_betterSqliteErr) {
|
|
44
|
+
// better-sqlite3 unavailable (native build failed or not installed)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
throw new Error(
|
|
48
|
+
'[hbo-core-store] No SQLite engine available. ' +
|
|
49
|
+
'Requires Node >= 22.13 (no flags) or Node >= 22.5 with --experimental-sqlite, ' +
|
|
50
|
+
'OR better-sqlite3 installed. Run: npm install better-sqlite3'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// DB singleton
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
|
|
58
|
+
let _db = null;
|
|
59
|
+
|
|
60
|
+
function getDb() {
|
|
61
|
+
if (_db) return _db;
|
|
62
|
+
|
|
63
|
+
const dbPath = path.join(
|
|
64
|
+
process.env.HELIOS_ROOT || path.join(os.homedir(), 'helios-agent'),
|
|
65
|
+
'data',
|
|
66
|
+
'hbo-core.db',
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
mkdirSync(path.join(dbPath, '..'), { recursive: true });
|
|
70
|
+
|
|
71
|
+
_db = _openDatabase(dbPath);
|
|
72
|
+
|
|
73
|
+
// WAL mode for concurrent readers + single writer.
|
|
74
|
+
// node:sqlite uses .exec('PRAGMA ...'); better-sqlite3 uses .pragma('...')
|
|
75
|
+
if (_engine === 'better-sqlite3') {
|
|
76
|
+
_db.pragma('journal_mode = WAL');
|
|
77
|
+
_db.pragma('foreign_keys = ON');
|
|
78
|
+
} else {
|
|
79
|
+
_db.exec('PRAGMA journal_mode = WAL');
|
|
80
|
+
_db.exec('PRAGMA foreign_keys = ON');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
initSchema(_db);
|
|
84
|
+
return _db;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Schema
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
|
|
91
|
+
function initSchema(db) {
|
|
92
|
+
db.exec(`
|
|
93
|
+
CREATE TABLE IF NOT EXISTS tasks (
|
|
94
|
+
id TEXT PRIMARY KEY,
|
|
95
|
+
company_id TEXT NOT NULL,
|
|
96
|
+
title TEXT,
|
|
97
|
+
status TEXT,
|
|
98
|
+
assignee_agent_id TEXT,
|
|
99
|
+
goal_id TEXT,
|
|
100
|
+
origin_kind TEXT,
|
|
101
|
+
helios_issue_id TEXT,
|
|
102
|
+
execution_locked_at INTEGER,
|
|
103
|
+
created_at INTEGER,
|
|
104
|
+
updated_at INTEGER,
|
|
105
|
+
data_json TEXT
|
|
106
|
+
);
|
|
107
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_company_status ON tasks(company_id, status);
|
|
108
|
+
CREATE INDEX IF NOT EXISTS idx_tasks_assignee ON tasks(assignee_agent_id, company_id);
|
|
109
|
+
|
|
110
|
+
CREATE TABLE IF NOT EXISTS approvals (
|
|
111
|
+
id TEXT PRIMARY KEY,
|
|
112
|
+
company_id TEXT NOT NULL,
|
|
113
|
+
type TEXT,
|
|
114
|
+
status TEXT,
|
|
115
|
+
strategy_id TEXT,
|
|
116
|
+
requested_by TEXT,
|
|
117
|
+
created_at INTEGER,
|
|
118
|
+
updated_at INTEGER,
|
|
119
|
+
data_json TEXT
|
|
120
|
+
);
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_approvals_company_status ON approvals(company_id, status);
|
|
122
|
+
|
|
123
|
+
CREATE TABLE IF NOT EXISTS budget_policies (
|
|
124
|
+
id TEXT PRIMARY KEY,
|
|
125
|
+
company_id TEXT NOT NULL,
|
|
126
|
+
agent_id TEXT,
|
|
127
|
+
spent_cents INTEGER DEFAULT 0,
|
|
128
|
+
percent_used REAL DEFAULT 0,
|
|
129
|
+
updated_at INTEGER,
|
|
130
|
+
data_json TEXT
|
|
131
|
+
);
|
|
132
|
+
CREATE INDEX IF NOT EXISTS idx_budget_company_agent ON budget_policies(company_id, agent_id);
|
|
133
|
+
|
|
134
|
+
CREATE TABLE IF NOT EXISTS cost_events (
|
|
135
|
+
id TEXT PRIMARY KEY,
|
|
136
|
+
company_id TEXT NOT NULL,
|
|
137
|
+
agent_id TEXT,
|
|
138
|
+
feature TEXT,
|
|
139
|
+
model TEXT,
|
|
140
|
+
amount_usd REAL,
|
|
141
|
+
created_at INTEGER,
|
|
142
|
+
data_json TEXT
|
|
143
|
+
);
|
|
144
|
+
CREATE INDEX IF NOT EXISTS idx_cost_company ON cost_events(company_id, created_at);
|
|
145
|
+
|
|
146
|
+
CREATE TABLE IF NOT EXISTS business_agents (
|
|
147
|
+
id TEXT PRIMARY KEY,
|
|
148
|
+
company_id TEXT NOT NULL,
|
|
149
|
+
role TEXT,
|
|
150
|
+
status TEXT,
|
|
151
|
+
current_task_id TEXT,
|
|
152
|
+
skill TEXT,
|
|
153
|
+
adapter TEXT,
|
|
154
|
+
updated_at INTEGER,
|
|
155
|
+
data_json TEXT
|
|
156
|
+
);
|
|
157
|
+
CREATE INDEX IF NOT EXISTS idx_agents_company_status ON business_agents(company_id, status);
|
|
158
|
+
|
|
159
|
+
CREATE TABLE IF NOT EXISTS okrs (
|
|
160
|
+
id TEXT PRIMARY KEY,
|
|
161
|
+
company_id TEXT NOT NULL,
|
|
162
|
+
type TEXT,
|
|
163
|
+
status TEXT,
|
|
164
|
+
title TEXT,
|
|
165
|
+
updated_at INTEGER,
|
|
166
|
+
data_json TEXT
|
|
167
|
+
);
|
|
168
|
+
CREATE INDEX IF NOT EXISTS idx_okrs_company_type ON okrs(company_id, type, status);
|
|
169
|
+
|
|
170
|
+
CREATE TABLE IF NOT EXISTS goals (
|
|
171
|
+
id TEXT PRIMARY KEY,
|
|
172
|
+
company_id TEXT NOT NULL,
|
|
173
|
+
title TEXT,
|
|
174
|
+
description TEXT,
|
|
175
|
+
level TEXT,
|
|
176
|
+
status TEXT,
|
|
177
|
+
parent_id TEXT,
|
|
178
|
+
created_at INTEGER,
|
|
179
|
+
updated_at INTEGER,
|
|
180
|
+
data_json TEXT
|
|
181
|
+
);
|
|
182
|
+
CREATE INDEX IF NOT EXISTS idx_goals_company_status ON goals(company_id, status);
|
|
183
|
+
CREATE INDEX IF NOT EXISTS idx_goals_parent ON goals(company_id, parent_id);
|
|
184
|
+
|
|
185
|
+
CREATE TABLE IF NOT EXISTS leads (
|
|
186
|
+
id TEXT PRIMARY KEY,
|
|
187
|
+
company_id TEXT NOT NULL,
|
|
188
|
+
status TEXT,
|
|
189
|
+
email TEXT,
|
|
190
|
+
name TEXT,
|
|
191
|
+
updated_at INTEGER,
|
|
192
|
+
data_json TEXT
|
|
193
|
+
);
|
|
194
|
+
CREATE INDEX IF NOT EXISTS idx_leads_company_status ON leads(company_id, status);
|
|
195
|
+
|
|
196
|
+
CREATE TABLE IF NOT EXISTS crm_contacts (
|
|
197
|
+
id TEXT PRIMARY KEY,
|
|
198
|
+
company_id TEXT NOT NULL,
|
|
199
|
+
email TEXT,
|
|
200
|
+
name TEXT,
|
|
201
|
+
updated_at INTEGER,
|
|
202
|
+
data_json TEXT
|
|
203
|
+
);
|
|
204
|
+
CREATE INDEX IF NOT EXISTS idx_crm_company ON crm_contacts(company_id);
|
|
205
|
+
|
|
206
|
+
CREATE TABLE IF NOT EXISTS accounts (
|
|
207
|
+
id TEXT PRIMARY KEY,
|
|
208
|
+
company_id TEXT NOT NULL,
|
|
209
|
+
name TEXT,
|
|
210
|
+
updated_at INTEGER,
|
|
211
|
+
data_json TEXT
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
CREATE TABLE IF NOT EXISTS opportunities (
|
|
215
|
+
id TEXT PRIMARY KEY,
|
|
216
|
+
company_id TEXT NOT NULL,
|
|
217
|
+
title TEXT,
|
|
218
|
+
status TEXT,
|
|
219
|
+
updated_at INTEGER,
|
|
220
|
+
data_json TEXT
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
CREATE TABLE IF NOT EXISTS helios_projects (
|
|
224
|
+
id TEXT PRIMARY KEY,
|
|
225
|
+
company_id TEXT NOT NULL,
|
|
226
|
+
name TEXT,
|
|
227
|
+
pillar_id TEXT,
|
|
228
|
+
goal_id TEXT,
|
|
229
|
+
status TEXT,
|
|
230
|
+
phase TEXT,
|
|
231
|
+
created_at INTEGER,
|
|
232
|
+
updated_at INTEGER,
|
|
233
|
+
data_json TEXT
|
|
234
|
+
);
|
|
235
|
+
CREATE INDEX IF NOT EXISTS idx_helios_projects_company ON helios_projects(company_id, status);
|
|
236
|
+
|
|
237
|
+
CREATE TABLE IF NOT EXISTS project_documents (
|
|
238
|
+
id TEXT PRIMARY KEY,
|
|
239
|
+
project_id TEXT NOT NULL,
|
|
240
|
+
company_id TEXT,
|
|
241
|
+
purpose TEXT,
|
|
242
|
+
approach TEXT,
|
|
243
|
+
intent_anchor TEXT,
|
|
244
|
+
success_criteria TEXT,
|
|
245
|
+
exclusions TEXT,
|
|
246
|
+
content TEXT,
|
|
247
|
+
version INTEGER DEFAULT 1,
|
|
248
|
+
updated_at INTEGER,
|
|
249
|
+
data_json TEXT
|
|
250
|
+
);
|
|
251
|
+
CREATE INDEX IF NOT EXISTS idx_project_docs_project ON project_documents(project_id);
|
|
252
|
+
`);
|
|
253
|
+
|
|
254
|
+
// H-7 migration: add created_at column to existing goals tables that predate this commit.
|
|
255
|
+
try { db.exec(`ALTER TABLE goals ADD COLUMN created_at INTEGER`); } catch (_) { /* column already exists */ }
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
// Helpers
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
|
|
262
|
+
function _parseJson(value, fallback) {
|
|
263
|
+
if (!value) return fallback;
|
|
264
|
+
try { return JSON.parse(value); } catch { return fallback; }
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const _SQLITE_COLUMN_KEYS = new Set([
|
|
268
|
+
'id', 'company_id', 'title', 'description', 'level', 'status', 'parent_id',
|
|
269
|
+
'created_at', 'updated_at', 'data_json',
|
|
270
|
+
// tasks
|
|
271
|
+
'assignee_agent_id', 'goal_id', 'origin_kind', 'helios_issue_id', 'execution_locked_at',
|
|
272
|
+
// approvals
|
|
273
|
+
'type', 'strategy_id', 'requested_by',
|
|
274
|
+
// cost_events
|
|
275
|
+
'agent_id', 'feature', 'model', 'amount_usd',
|
|
276
|
+
// business_agents
|
|
277
|
+
'role', 'last_heartbeat_at', 'pause_reason',
|
|
278
|
+
// okrs / leads / crm_contacts / accounts / opportunities
|
|
279
|
+
'email', 'name', 'amount',
|
|
280
|
+
// helios_projects
|
|
281
|
+
'pillar_id', 'phase',
|
|
282
|
+
// project_documents
|
|
283
|
+
'project_id', 'section', 'value', 'updated_by',
|
|
284
|
+
'intent_anchor', 'success_criteria', 'exclusions', 'content', 'purpose', 'approach', 'version',
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
function _stripSqliteCols(obj) {
|
|
288
|
+
const result = {};
|
|
289
|
+
for (const key of Object.keys(obj)) {
|
|
290
|
+
if (!_SQLITE_COLUMN_KEYS.has(key)) result[key] = obj[key];
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function _mergeRow(row) {
|
|
296
|
+
const base = _parseJson(row.data_json, {});
|
|
297
|
+
const merged = { ...base };
|
|
298
|
+
for (const key of Object.keys(row)) {
|
|
299
|
+
if (key !== 'data_json') merged[key] = row[key];
|
|
300
|
+
}
|
|
301
|
+
return merged;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function _statusParams(statuses) {
|
|
305
|
+
const placeholders = statuses.map(() => '?').join(', ');
|
|
306
|
+
return { placeholders, params: statuses };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// ---------------------------------------------------------------------------
|
|
310
|
+
// Tasks
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
|
|
313
|
+
function createTask(task) {
|
|
314
|
+
const companyId = task.company_id || task.companyId;
|
|
315
|
+
if (!companyId) throw new Error(`[hbo-core-store] createTask: company_id is required (task id=${task.id})`);
|
|
316
|
+
const db = getDb();
|
|
317
|
+
const now = Date.now();
|
|
318
|
+
db.prepare(`
|
|
319
|
+
INSERT OR REPLACE INTO tasks
|
|
320
|
+
(id, company_id, title, status, assignee_agent_id, goal_id,
|
|
321
|
+
origin_kind, helios_issue_id, execution_locked_at, created_at, updated_at, data_json)
|
|
322
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
323
|
+
`).run(
|
|
324
|
+
task.id,
|
|
325
|
+
companyId,
|
|
326
|
+
task.title || null,
|
|
327
|
+
task.status || null,
|
|
328
|
+
task.assignee_agent_id || task.assigneeAgentId || null,
|
|
329
|
+
task.goal_id || task.goalId || null,
|
|
330
|
+
task.origin_kind || task.originKind || null,
|
|
331
|
+
task.helios_issue_id || task.heliosIssueId || null,
|
|
332
|
+
task.execution_locked_at != null ? task.execution_locked_at : (task.executionLockedAt != null ? task.executionLockedAt : null),
|
|
333
|
+
task.created_at || task.createdAt || now,
|
|
334
|
+
task.updated_at || task.updatedAt || now,
|
|
335
|
+
JSON.stringify(task),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function getTask(id, companyId) {
|
|
340
|
+
const db = getDb();
|
|
341
|
+
const row = db.prepare('SELECT * FROM tasks WHERE id = ? AND company_id = ?').get(id, companyId);
|
|
342
|
+
return row ? _mergeRow(row) : null;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function updateTask(id, companyId, update) {
|
|
346
|
+
const existing = getTask(id, companyId) || { id, company_id: companyId };
|
|
347
|
+
const merged = { ...existing, ...update, id, company_id: companyId, updated_at: Date.now() };
|
|
348
|
+
createTask(merged);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function getTasksByCompanyStatus(companyId, status) {
|
|
352
|
+
const db = getDb();
|
|
353
|
+
const statuses = Array.isArray(status) ? status : [status];
|
|
354
|
+
const { placeholders, params } = _statusParams(statuses);
|
|
355
|
+
return db.prepare(
|
|
356
|
+
`SELECT * FROM tasks WHERE company_id = ? AND status IN (${placeholders})`
|
|
357
|
+
).all(companyId, ...params).map(_mergeRow);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function deleteTask(id, companyId) {
|
|
361
|
+
const db = getDb();
|
|
362
|
+
db.prepare('DELETE FROM tasks WHERE id = ? AND company_id = ?').run(id, companyId);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ---------------------------------------------------------------------------
|
|
366
|
+
// Approvals
|
|
367
|
+
// ---------------------------------------------------------------------------
|
|
368
|
+
|
|
369
|
+
function createApproval(approval) {
|
|
370
|
+
const companyId = approval.company_id || approval.companyId;
|
|
371
|
+
if (!companyId) throw new Error(`[hbo-core-store] createApproval: company_id is required (approval id=${approval.id})`);
|
|
372
|
+
const db = getDb();
|
|
373
|
+
const now = Date.now();
|
|
374
|
+
db.prepare(`
|
|
375
|
+
INSERT OR REPLACE INTO approvals
|
|
376
|
+
(id, company_id, type, status, strategy_id, requested_by, created_at, updated_at, data_json)
|
|
377
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
378
|
+
`).run(
|
|
379
|
+
approval.id,
|
|
380
|
+
companyId,
|
|
381
|
+
approval.type || null,
|
|
382
|
+
approval.status || null,
|
|
383
|
+
approval.strategy_id || approval.strategyId || null,
|
|
384
|
+
approval.requested_by || approval.requestedBy || null,
|
|
385
|
+
approval.created_at || approval.createdAt || now,
|
|
386
|
+
approval.updated_at || approval.updatedAt || now,
|
|
387
|
+
JSON.stringify(approval),
|
|
388
|
+
);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function getApproval(id, companyId) {
|
|
392
|
+
const db = getDb();
|
|
393
|
+
const row = db.prepare('SELECT * FROM approvals WHERE id = ? AND company_id = ?').get(id, companyId);
|
|
394
|
+
return row ? _mergeRow(row) : null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function updateApproval(id, companyId, update) {
|
|
398
|
+
const existing = getApproval(id, companyId) || { id, company_id: companyId };
|
|
399
|
+
const merged = { ...existing, ...update, id, company_id: companyId, updated_at: Date.now() };
|
|
400
|
+
createApproval(merged);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function getApprovalsByCompanyStatus(companyId, status) {
|
|
404
|
+
const db = getDb();
|
|
405
|
+
const statuses = Array.isArray(status) ? status : [status];
|
|
406
|
+
const { placeholders, params } = _statusParams(statuses);
|
|
407
|
+
return db.prepare(
|
|
408
|
+
`SELECT * FROM approvals WHERE company_id = ? AND status IN (${placeholders})`
|
|
409
|
+
).all(companyId, ...params).map(_mergeRow);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// ---------------------------------------------------------------------------
|
|
413
|
+
// Budget Policies
|
|
414
|
+
// ---------------------------------------------------------------------------
|
|
415
|
+
|
|
416
|
+
function upsertBudgetPolicy(policy) {
|
|
417
|
+
const companyId = policy.company_id || policy.companyId;
|
|
418
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertBudgetPolicy: company_id is required`);
|
|
419
|
+
const db = getDb();
|
|
420
|
+
db.prepare(`
|
|
421
|
+
INSERT OR REPLACE INTO budget_policies
|
|
422
|
+
(id, company_id, agent_id, spent_cents, percent_used, updated_at, data_json)
|
|
423
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
424
|
+
`).run(
|
|
425
|
+
policy.id,
|
|
426
|
+
companyId,
|
|
427
|
+
policy.agent_id || policy.agentId || null,
|
|
428
|
+
policy.spent_cents != null ? policy.spent_cents : (policy.spentCents != null ? policy.spentCents : 0),
|
|
429
|
+
policy.percent_used != null ? policy.percent_used : (policy.percentUsed != null ? policy.percentUsed : 0),
|
|
430
|
+
policy.updated_at || policy.updatedAt || Date.now(),
|
|
431
|
+
JSON.stringify(policy),
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
function getBudgetPolicy(id, companyId) {
|
|
436
|
+
const db = getDb();
|
|
437
|
+
const row = db.prepare('SELECT * FROM budget_policies WHERE id = ? AND company_id = ?').get(id, companyId);
|
|
438
|
+
return row ? _mergeRow(row) : null;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
function getBudgetPoliciesByCompany(companyId) {
|
|
442
|
+
const db = getDb();
|
|
443
|
+
return db.prepare('SELECT * FROM budget_policies WHERE company_id = ?').all(companyId).map(_mergeRow);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ---------------------------------------------------------------------------
|
|
447
|
+
// Cost Events
|
|
448
|
+
// ---------------------------------------------------------------------------
|
|
449
|
+
|
|
450
|
+
function createCostEvent(event) {
|
|
451
|
+
const companyId = event.company_id || event.companyId;
|
|
452
|
+
if (!companyId) throw new Error(`[hbo-core-store] createCostEvent: company_id is required`);
|
|
453
|
+
const db = getDb();
|
|
454
|
+
const now = Date.now();
|
|
455
|
+
db.prepare(`
|
|
456
|
+
INSERT OR REPLACE INTO cost_events
|
|
457
|
+
(id, company_id, agent_id, feature, model, amount_usd, created_at, data_json)
|
|
458
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
459
|
+
`).run(
|
|
460
|
+
event.id,
|
|
461
|
+
companyId,
|
|
462
|
+
event.agent_id || event.agentId || null,
|
|
463
|
+
event.feature || null,
|
|
464
|
+
event.model || null,
|
|
465
|
+
event.amount_usd != null ? event.amount_usd : (event.amountUsd != null ? event.amountUsd : null),
|
|
466
|
+
event.created_at || event.createdAt || now,
|
|
467
|
+
JSON.stringify(event),
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function getCostEventsByCompanyRange(companyId, startMs, endMs) {
|
|
472
|
+
const db = getDb();
|
|
473
|
+
return db.prepare(
|
|
474
|
+
'SELECT * FROM cost_events WHERE company_id = ? AND created_at >= ? AND created_at <= ? ORDER BY created_at ASC'
|
|
475
|
+
).all(companyId, startMs, endMs).map(_mergeRow);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// ---------------------------------------------------------------------------
|
|
479
|
+
// Business Agents
|
|
480
|
+
// ---------------------------------------------------------------------------
|
|
481
|
+
|
|
482
|
+
function upsertBusinessAgent(agent) {
|
|
483
|
+
const companyId = agent.company_id || agent.companyId;
|
|
484
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertBusinessAgent: company_id is required`);
|
|
485
|
+
const db = getDb();
|
|
486
|
+
db.prepare(`
|
|
487
|
+
INSERT OR REPLACE INTO business_agents
|
|
488
|
+
(id, company_id, role, status, current_task_id, skill, adapter, updated_at, data_json)
|
|
489
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
490
|
+
`).run(
|
|
491
|
+
agent.id,
|
|
492
|
+
companyId,
|
|
493
|
+
agent.role || null,
|
|
494
|
+
agent.status || null,
|
|
495
|
+
agent.current_task_id || agent.currentTaskId || null,
|
|
496
|
+
agent.skill || null,
|
|
497
|
+
agent.adapter || null,
|
|
498
|
+
agent.updated_at || agent.updatedAt || Date.now(),
|
|
499
|
+
JSON.stringify(agent),
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function getBusinessAgent(id, companyId) {
|
|
504
|
+
const db = getDb();
|
|
505
|
+
const row = db.prepare('SELECT * FROM business_agents WHERE id = ? AND company_id = ?').get(id, companyId);
|
|
506
|
+
return row ? _mergeRow(row) : null;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function getBusinessAgentsByCompany(companyId) {
|
|
510
|
+
const db = getDb();
|
|
511
|
+
return db.prepare('SELECT * FROM business_agents WHERE company_id = ?').all(companyId).map(_mergeRow);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
// OKRs
|
|
516
|
+
// ---------------------------------------------------------------------------
|
|
517
|
+
|
|
518
|
+
function upsertOKR(okr) {
|
|
519
|
+
const companyId = okr.company_id || okr.companyId;
|
|
520
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertOKR: company_id is required`);
|
|
521
|
+
const db = getDb();
|
|
522
|
+
db.prepare(`
|
|
523
|
+
INSERT OR REPLACE INTO okrs
|
|
524
|
+
(id, company_id, type, status, title, updated_at, data_json)
|
|
525
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
526
|
+
`).run(
|
|
527
|
+
okr.id,
|
|
528
|
+
companyId,
|
|
529
|
+
okr.type || null,
|
|
530
|
+
okr.status || null,
|
|
531
|
+
okr.title || null,
|
|
532
|
+
okr.updated_at || okr.updatedAt || Date.now(),
|
|
533
|
+
JSON.stringify(okr),
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function getOKRsByCompanyType(companyId, type, status) {
|
|
538
|
+
const db = getDb();
|
|
539
|
+
if (type !== undefined && status !== undefined) {
|
|
540
|
+
return db.prepare('SELECT * FROM okrs WHERE company_id = ? AND type = ? AND status = ?').all(companyId, type, status).map(_mergeRow);
|
|
541
|
+
}
|
|
542
|
+
if (type !== undefined) {
|
|
543
|
+
return db.prepare('SELECT * FROM okrs WHERE company_id = ? AND type = ?').all(companyId, type).map(_mergeRow);
|
|
544
|
+
}
|
|
545
|
+
if (status !== undefined) {
|
|
546
|
+
return db.prepare('SELECT * FROM okrs WHERE company_id = ? AND status = ?').all(companyId, status).map(_mergeRow);
|
|
547
|
+
}
|
|
548
|
+
return db.prepare('SELECT * FROM okrs WHERE company_id = ?').all(companyId).map(_mergeRow);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// ---------------------------------------------------------------------------
|
|
552
|
+
// Goals
|
|
553
|
+
// ---------------------------------------------------------------------------
|
|
554
|
+
|
|
555
|
+
function createGoal(goal) {
|
|
556
|
+
const companyId = goal.company_id || goal.companyId;
|
|
557
|
+
if (!companyId) throw new Error(`[hbo-core-store] createGoal: company_id is required (goal id=${goal.id})`);
|
|
558
|
+
const db = getDb();
|
|
559
|
+
const now = Date.now();
|
|
560
|
+
db.prepare(`
|
|
561
|
+
INSERT OR REPLACE INTO goals
|
|
562
|
+
(id, company_id, title, description, level, status, parent_id, created_at, updated_at, data_json)
|
|
563
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
564
|
+
`).run(
|
|
565
|
+
goal.id,
|
|
566
|
+
companyId,
|
|
567
|
+
goal.title || null,
|
|
568
|
+
goal.description || goal.desc || null,
|
|
569
|
+
goal.level || null,
|
|
570
|
+
goal.status || null,
|
|
571
|
+
goal.parent_id || goal.parentId || goal.parentGoalId || null,
|
|
572
|
+
goal.created_at || goal.createdAt || now,
|
|
573
|
+
goal.updated_at || goal.updatedAt || now,
|
|
574
|
+
JSON.stringify(goal),
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function getGoal(id, companyId) {
|
|
579
|
+
const db = getDb();
|
|
580
|
+
const row = db.prepare('SELECT * FROM goals WHERE id = ? AND company_id = ?').get(id, companyId);
|
|
581
|
+
return row ? _mergeRow(row) : null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function updateGoal(id, companyId, update) {
|
|
585
|
+
const existing = getGoal(id, companyId) || { id, company_id: companyId };
|
|
586
|
+
const cleanExisting = _stripSqliteCols(existing);
|
|
587
|
+
createGoal({ ...cleanExisting, ...update, id, company_id: companyId, updated_at: Date.now() });
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function deleteGoal(id, companyId) {
|
|
591
|
+
const db = getDb();
|
|
592
|
+
db.prepare('DELETE FROM goals WHERE id = ? AND company_id = ?').run(id, companyId);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function getGoalsByCompany(companyId) {
|
|
596
|
+
const db = getDb();
|
|
597
|
+
return db.prepare('SELECT * FROM goals WHERE company_id = ?').all(companyId).map(_mergeRow);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// ---------------------------------------------------------------------------
|
|
601
|
+
// HeliosProjects
|
|
602
|
+
// ---------------------------------------------------------------------------
|
|
603
|
+
|
|
604
|
+
function upsertProject(project) {
|
|
605
|
+
const companyId = project.company_id || project.companyId;
|
|
606
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertProject: company_id is required`);
|
|
607
|
+
const db = getDb();
|
|
608
|
+
const now = Date.now();
|
|
609
|
+
db.prepare(`
|
|
610
|
+
INSERT OR REPLACE INTO helios_projects
|
|
611
|
+
(id, company_id, name, pillar_id, goal_id, status, phase, created_at, updated_at, data_json)
|
|
612
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
613
|
+
`).run(
|
|
614
|
+
project.id,
|
|
615
|
+
companyId,
|
|
616
|
+
project.name || null,
|
|
617
|
+
project.pillar_id || project.pillarId || null,
|
|
618
|
+
project.goal_id || project.goalId || null,
|
|
619
|
+
project.status || 'planning',
|
|
620
|
+
project.phase || 'planning',
|
|
621
|
+
project.created_at || project.createdAt || now,
|
|
622
|
+
project.updated_at || project.updatedAt || now,
|
|
623
|
+
JSON.stringify(project),
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function getProject(id, companyId) {
|
|
628
|
+
const db = getDb();
|
|
629
|
+
const row = db.prepare('SELECT * FROM helios_projects WHERE id = ? AND company_id = ?').get(id, companyId);
|
|
630
|
+
return row ? _mergeRow(row) : null;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
function getProjectsByCompany(companyId) {
|
|
634
|
+
const db = getDb();
|
|
635
|
+
return db.prepare('SELECT * FROM helios_projects WHERE company_id = ? ORDER BY created_at DESC LIMIT 100').all(companyId).map(_mergeRow);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
function updateProject(id, companyId, update) {
|
|
639
|
+
const existing = getProject(id, companyId) || { id, company_id: companyId };
|
|
640
|
+
const cleanExisting = _stripSqliteCols(existing);
|
|
641
|
+
upsertProject({ ...cleanExisting, ...update, id, company_id: companyId, updated_at: Date.now() });
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ---------------------------------------------------------------------------
|
|
645
|
+
// ProjectDocuments
|
|
646
|
+
// ---------------------------------------------------------------------------
|
|
647
|
+
|
|
648
|
+
function upsertProjectDocument(doc) {
|
|
649
|
+
const projectId = doc.project_id || doc.projectId;
|
|
650
|
+
if (!projectId) throw new Error(`[hbo-core-store] upsertProjectDocument: project_id is required`);
|
|
651
|
+
const db = getDb();
|
|
652
|
+
const id = doc.id || `pdoc:${projectId}:main`;
|
|
653
|
+
const now = Date.now();
|
|
654
|
+
db.prepare(`
|
|
655
|
+
INSERT OR REPLACE INTO project_documents
|
|
656
|
+
(id, project_id, company_id, purpose, approach, intent_anchor,
|
|
657
|
+
success_criteria, exclusions, content, version, updated_at, data_json)
|
|
658
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
659
|
+
`).run(
|
|
660
|
+
id,
|
|
661
|
+
projectId,
|
|
662
|
+
doc.company_id || doc.companyId || null,
|
|
663
|
+
doc.purpose || null,
|
|
664
|
+
doc.approach || null,
|
|
665
|
+
doc.intent_anchor || doc.intentAnchor || null,
|
|
666
|
+
doc.success_criteria || doc.successCriteria || '[]',
|
|
667
|
+
doc.exclusions || '[]',
|
|
668
|
+
doc.content || null,
|
|
669
|
+
doc.version != null ? doc.version : 1,
|
|
670
|
+
doc.updated_at || doc.updatedAt || now,
|
|
671
|
+
JSON.stringify(doc),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function getProjectDocument(projectId) {
|
|
676
|
+
const db = getDb();
|
|
677
|
+
const row = db.prepare('SELECT * FROM project_documents WHERE project_id = ? ORDER BY updated_at DESC LIMIT 1').get(projectId);
|
|
678
|
+
return row ? _mergeRow(row) : null;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// ---------------------------------------------------------------------------
|
|
682
|
+
// Leads
|
|
683
|
+
// ---------------------------------------------------------------------------
|
|
684
|
+
|
|
685
|
+
function upsertLead(lead) {
|
|
686
|
+
const companyId = lead.company_id || lead.companyId;
|
|
687
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertLead: company_id is required`);
|
|
688
|
+
const db = getDb();
|
|
689
|
+
db.prepare(`
|
|
690
|
+
INSERT OR REPLACE INTO leads
|
|
691
|
+
(id, company_id, status, email, name, updated_at, data_json)
|
|
692
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
693
|
+
`).run(
|
|
694
|
+
lead.id,
|
|
695
|
+
companyId,
|
|
696
|
+
lead.status || null,
|
|
697
|
+
lead.email || null,
|
|
698
|
+
lead.name || null,
|
|
699
|
+
lead.updated_at || lead.updatedAt || Date.now(),
|
|
700
|
+
JSON.stringify(lead),
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
function getLeadsByCompanyStatus(companyId, statuses) {
|
|
705
|
+
const db = getDb();
|
|
706
|
+
const { placeholders, params } = _statusParams(statuses);
|
|
707
|
+
return db.prepare(
|
|
708
|
+
`SELECT * FROM leads WHERE company_id = ? AND status IN (${placeholders})`
|
|
709
|
+
).all(companyId, ...params).map(_mergeRow);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// ---------------------------------------------------------------------------
|
|
713
|
+
// CRM Contacts
|
|
714
|
+
// ---------------------------------------------------------------------------
|
|
715
|
+
|
|
716
|
+
function upsertCRMContact(contact) {
|
|
717
|
+
const companyId = contact.company_id || contact.companyId;
|
|
718
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertCRMContact: company_id is required`);
|
|
719
|
+
const db = getDb();
|
|
720
|
+
db.prepare(`
|
|
721
|
+
INSERT OR REPLACE INTO crm_contacts
|
|
722
|
+
(id, company_id, email, name, updated_at, data_json)
|
|
723
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
724
|
+
`).run(
|
|
725
|
+
contact.id,
|
|
726
|
+
companyId,
|
|
727
|
+
contact.email || null,
|
|
728
|
+
contact.name || null,
|
|
729
|
+
contact.updated_at || contact.updatedAt || Date.now(),
|
|
730
|
+
JSON.stringify(contact),
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
function getCRMContactsByCompany(companyId) {
|
|
735
|
+
const db = getDb();
|
|
736
|
+
return db.prepare('SELECT * FROM crm_contacts WHERE company_id = ?').all(companyId).map(_mergeRow);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// ---------------------------------------------------------------------------
|
|
740
|
+
// Accounts
|
|
741
|
+
// ---------------------------------------------------------------------------
|
|
742
|
+
|
|
743
|
+
function upsertAccount(account) {
|
|
744
|
+
const companyId = account.company_id || account.companyId;
|
|
745
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertAccount: company_id is required`);
|
|
746
|
+
const db = getDb();
|
|
747
|
+
db.prepare(`
|
|
748
|
+
INSERT OR REPLACE INTO accounts
|
|
749
|
+
(id, company_id, name, updated_at, data_json)
|
|
750
|
+
VALUES (?, ?, ?, ?, ?)
|
|
751
|
+
`).run(
|
|
752
|
+
account.id,
|
|
753
|
+
companyId,
|
|
754
|
+
account.name || null,
|
|
755
|
+
account.updated_at || account.updatedAt || Date.now(),
|
|
756
|
+
JSON.stringify(account),
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ---------------------------------------------------------------------------
|
|
761
|
+
// Opportunities
|
|
762
|
+
// ---------------------------------------------------------------------------
|
|
763
|
+
|
|
764
|
+
function upsertOpportunity(opp) {
|
|
765
|
+
const companyId = opp.company_id || opp.companyId;
|
|
766
|
+
if (!companyId) throw new Error(`[hbo-core-store] upsertOpportunity: company_id is required`);
|
|
767
|
+
const db = getDb();
|
|
768
|
+
db.prepare(`
|
|
769
|
+
INSERT OR REPLACE INTO opportunities
|
|
770
|
+
(id, company_id, title, status, updated_at, data_json)
|
|
771
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
772
|
+
`).run(
|
|
773
|
+
opp.id,
|
|
774
|
+
companyId,
|
|
775
|
+
opp.title || null,
|
|
776
|
+
opp.status || null,
|
|
777
|
+
opp.updated_at || opp.updatedAt || Date.now(),
|
|
778
|
+
JSON.stringify(opp),
|
|
779
|
+
);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// ---------------------------------------------------------------------------
|
|
783
|
+
// Test / lifecycle helpers
|
|
784
|
+
// ---------------------------------------------------------------------------
|
|
785
|
+
|
|
786
|
+
function resetHboCoreStore() {
|
|
787
|
+
try { if (_db) _db.close(); } catch (_) { /* ignore */ }
|
|
788
|
+
_db = null;
|
|
789
|
+
_engine = null;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
// ---------------------------------------------------------------------------
|
|
793
|
+
// Exports
|
|
794
|
+
// ---------------------------------------------------------------------------
|
|
795
|
+
|
|
796
|
+
module.exports = {
|
|
797
|
+
createTask,
|
|
798
|
+
getTask,
|
|
799
|
+
updateTask,
|
|
800
|
+
getTasksByCompanyStatus,
|
|
801
|
+
deleteTask,
|
|
802
|
+
createApproval,
|
|
803
|
+
getApproval,
|
|
804
|
+
updateApproval,
|
|
805
|
+
getApprovalsByCompanyStatus,
|
|
806
|
+
upsertBudgetPolicy,
|
|
807
|
+
getBudgetPolicy,
|
|
808
|
+
getBudgetPoliciesByCompany,
|
|
809
|
+
createCostEvent,
|
|
810
|
+
getCostEventsByCompanyRange,
|
|
811
|
+
upsertBusinessAgent,
|
|
812
|
+
getBusinessAgent,
|
|
813
|
+
getBusinessAgentsByCompany,
|
|
814
|
+
upsertOKR,
|
|
815
|
+
getOKRsByCompanyType,
|
|
816
|
+
createGoal,
|
|
817
|
+
getGoal,
|
|
818
|
+
updateGoal,
|
|
819
|
+
deleteGoal,
|
|
820
|
+
getGoalsByCompany,
|
|
821
|
+
upsertProject,
|
|
822
|
+
getProject,
|
|
823
|
+
getProjectsByCompany,
|
|
824
|
+
updateProject,
|
|
825
|
+
upsertProjectDocument,
|
|
826
|
+
getProjectDocument,
|
|
827
|
+
upsertLead,
|
|
828
|
+
getLeadsByCompanyStatus,
|
|
829
|
+
upsertCRMContact,
|
|
830
|
+
getCRMContactsByCompany,
|
|
831
|
+
upsertAccount,
|
|
832
|
+
upsertOpportunity,
|
|
833
|
+
resetHboCoreStore,
|
|
834
|
+
};
|