@cgh567/agent 2.4.1 → 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/bin/helios +0 -0
- package/bin/helios-rpc-node-wrapper.cjs +0 -0
- package/bin/helios-rpc-wrapper.sh +0 -0
- package/daemon/adapters/helios-rpc-adapter.js +47 -25
- package/daemon/adapters/tui_wakeup.js +8 -0
- package/daemon/config/com.familiar.helios-daemon.plist +5 -0
- package/daemon/config/helios-daemon.service +4 -0
- package/daemon/context-enrichment.js +59 -21
- 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 +723 -57
- package/daemon/helios-company-daemon.js +616 -134
- package/daemon/lib/harada/cascade-judge.js +12 -50
- package/daemon/lib/harada/mandala.js +20 -0
- package/daemon/lib/harada/pillar-dispatcher.js +1 -1
- package/daemon/lib/harada/project-factory.js +7 -2
- package/daemon/lib/hbo-bridge.js +32 -13
- package/daemon/lib/hed-engine.js +10 -292
- package/daemon/lib/helios-hitl-host.js +15 -2
- package/daemon/lib/hitl-interaction-service.js +0 -0
- package/daemon/lib/memgraph-verify.js +38 -33
- package/daemon/lib/project-drift-detector.js +7 -17
- package/daemon/lib/project-semantic-updater.js +1 -14
- package/daemon/lib/task-completion-processor.js +11 -0
- package/daemon/lib/wizard-engine.js +57 -6
- package/daemon/routes/channels.js +10 -5
- package/daemon/routes/harada-map.js +11 -48
- package/daemon/routes/hbo.js +342 -75
- package/daemon/routes/hitl.js +0 -0
- package/daemon/routes/project.js +194 -62
- package/daemon/routes/routines.js +14 -0
- package/daemon/routes/tasks.js +15 -1
- package/daemon/routes/wizard.js +11 -4
- 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-hitl.js +0 -0
- package/daemon/schema-migrations-proj.js +131 -0
- package/extensions/001-tool-output-cap.ts +0 -0
- package/extensions/context-compaction.ts +45 -26
- package/extensions/cortex/activation-bridge.ts +5 -0
- package/extensions/cortex/learn.ts +26 -0
- package/extensions/cortex/wal-replay.ts +91 -0
- package/extensions/email/backfill.ts +0 -0
- package/extensions/helios-governance/analysis/ambiguity.ts +0 -0
- package/extensions/helios-governance/analysis/compliance.ts +0 -0
- package/extensions/helios-governance/analysis/long-task-detector.ts +0 -0
- package/extensions/helios-governance/analysis/output-contract.ts +0 -0
- package/extensions/helios-governance/analysis/patterns.ts +0 -0
- package/extensions/helios-governance/analysis/preflight.ts +0 -0
- package/extensions/helios-governance/analysis/recurring-violations.ts +0 -0
- package/extensions/helios-governance/analysis/task-classification.ts +0 -0
- package/extensions/helios-governance/analysis/task-intent.ts +0 -0
- package/extensions/helios-governance/gates/high-impact.ts +1 -1
- package/extensions/helios-governance/handlers/_jiti-require.ts +15 -8
- package/extensions/helios-governance/handlers/proxy-test-detector.ts +0 -0
- package/extensions/hema-dispatch-v3/graph-memory.ts +10 -0
- package/extensions/hema-dispatch-v3/index.ts +72 -47
- package/extensions/lib/elo-engine.js +0 -0
- package/extensions/lib/elo-engine.test.js +0 -0
- package/extensions/memgraph-autostart.ts +13 -0
- package/extensions/neuroplastic-eval.ts +0 -0
- package/extensions/shadow-loop/index.ts +0 -0
- package/extensions/warm-tick/warm-tick-maintenance.ts +8 -0
- package/lib/__tests__/hbo-core-store.test.js +238 -0
- package/lib/brain-v2-budget.js +0 -0
- package/lib/brain-v2-circuit-breaker.js +0 -0
- package/lib/brain-v2.js +0 -0
- package/lib/broker/adaptive-throttle.js +0 -0
- package/lib/broker/batch-coalescer.js +0 -0
- package/lib/broker/bulkhead.js +0 -0
- package/lib/broker/channel-registry.js +0 -0
- package/lib/broker/circuit-breaker.js +0 -0
- package/lib/broker/evidence-cache.js +0 -0
- package/lib/broker/health-monitor.js +0 -0
- package/lib/broker/mage-queue.js +0 -0
- package/lib/broker/priority-queue.js +0 -0
- package/lib/broker/server.js.bak-error2-fix +0 -0
- package/lib/broker/session-registry.js +0 -0
- package/lib/broker/singleton-timers.js +0 -0
- package/lib/broker/types.d.ts +0 -0
- package/lib/broker/vegas-limit.js +0 -0
- package/lib/compression/dist/ccr-store.js +74 -0
- package/lib/compression/dist/content-router.js +115 -0
- package/lib/compression/dist/pipeline.js +113 -0
- package/lib/compression/dist/server.js +265 -0
- package/lib/compression/dist/smart-crusher.js +251 -0
- package/lib/context-budget.ts +0 -0
- package/lib/context-firewall.js +0 -0
- package/lib/crm/integration/triage-bridge.js +0 -0
- package/lib/email-utils.ts +0 -0
- package/lib/eval/__tests__/preflight-checker.test.ts +0 -0
- package/lib/eval/__tests__/task-instruction-parser.test.ts +0 -0
- package/lib/eval/__tests__/verifier-runner.test.ts +0 -0
- package/lib/eval/index.ts +0 -0
- package/lib/eval/preflight-checker.ts +0 -0
- package/lib/eval/task-domain-classifier.ts +0 -0
- package/lib/eval/task-instruction-parser.ts +0 -0
- package/lib/eval/verifier-runner.ts +0 -0
- package/lib/event-bus.d.ts +0 -0
- package/lib/event-bus.mts +1 -1
- package/lib/governance-context-selector.ts +0 -0
- package/lib/graph/generate-extension-embeddings.js +0 -0
- package/lib/graph/generate-static-embeddings.js +0 -0
- package/lib/graph/lib/utils.js +1 -1
- package/lib/graph-audit.d.ts +0 -0
- 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/mesh-circuit-breaker.js +0 -0
- package/lib/mission-loop/lesson-extractor.ts +0 -0
- package/lib/mission-loop/mental-model-scorer.ts +0 -0
- package/lib/mission-loop/occ-detector.ts +0 -0
- package/lib/mission-loop/query-variants.ts +0 -0
- package/lib/mission-loop/verifier-check.ts +0 -0
- package/lib/skill-reference-builder.ts +0 -0
- package/lib/telemetry/token-breakdown.ts +0 -0
- package/lib/tool-compressor.ts +0 -0
- package/lib/triage-core/classifier.ts +3 -2
- package/lib/triage-core/graph/schema.cypher +10 -0
- package/lib/triage-core/legal-routing.ts +0 -0
- package/lib/triage-core/mental-model/dunbar-classifier.ts +0 -0
- package/lib/triage-core/mental-model/enrich-all.ts +0 -0
- package/lib/triage-core/mental-model/identity-resolver.ts +0 -0
- package/lib/triage-core/mental-model/key-facts.ts +1 -2
- package/lib/triage-core/mental-model/model-assembler.ts +0 -0
- package/lib/triage-core/orchestrator.ts +4 -11
- package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -0
- package/package.json +18 -8
- package/skills/helios-business-operator/services/signals/upwork-signals.js +0 -0
- package/skills/talisman-ceo/SKILL.md +23 -25
- package/skills/talisman-comms/SKILL.md +5 -5
- package/skills/talisman-engineering/SKILL.md +5 -5
- package/skills/talisman-finance/SKILL.md +10 -8
- package/skills/talisman-marketing/SKILL.md +10 -10
- package/skills/talisman-sales/SKILL.md +12 -15
- package/skills/talisman-support/SKILL.md +5 -5
- package/agents/business/talisman-ceo.md +0 -183
- package/agents/business/talisman-comms.md +0 -257
- package/agents/business/talisman-cto.md +0 -153
- package/agents/business/talisman-finance.md +0 -246
- package/agents/business/talisman-marketing.md +0 -240
- package/agents/business/talisman-sales.md +0 -242
- package/agents/business/talisman-support.md +0 -236
- package/daemon/lib/approval-expiry.js +0 -162
- package/daemon/lib/blast-radius-analyzer.js +0 -75
- package/daemon/lib/domain-bootstrap-orchestrator.js +0 -267
- package/daemon/lib/forensic-log.js +0 -113
- package/daemon/lib/goal-research-pipeline.js +0 -644
- package/daemon/lib/harada/cascade-research-dispatcher.js +0 -261
- package/daemon/lib/headroom-middleware.js +0 -167
- package/daemon/lib/headroom-proxy-manager.js +0 -623
- package/daemon/lib/mental-model-cache.js +0 -96
- package/daemon/lib/project-factory.js +0 -47
- package/daemon/lib/session-log-reader.js +0 -93
- package/daemon/routes/hed.js +0 -133
- package/lib/graph/learning/headroom-learn-bridge.js +0 -215
- package/skills/helios-bookkeeping/SKILL.md +0 -321
- package/skills/helios-briefer/SKILL.md +0 -44
- package/skills/helios-client-relations/SKILL.md +0 -322
- package/skills/helios-personal-triager/SKILL.md +0 -45
- package/skills/helios-recruitment/SKILL.md +0 -317
- package/skills/helios-relationship-nudger/SKILL.md +0 -77
- package/skills/helios-researcher/SKILL.md +0 -44
- package/skills/helios-scheduler/SKILL.md +0 -58
- package/skills/helios-tax-analyst/SKILL.md +0 -280
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* lib/__tests__/hbo-core-store.test.js
|
|
4
|
+
*
|
|
5
|
+
* Unit tests for hbo-core-store.ts (SQLite fallback store).
|
|
6
|
+
* Uses node:sqlite (Node.js v22+ built-in) — no native compilation required.
|
|
7
|
+
* Runs natively on Windows and macOS.
|
|
8
|
+
*
|
|
9
|
+
* Run: node --experimental-sqlite lib/__tests__/hbo-core-store.test.js
|
|
10
|
+
* Or: pnpm run test:hbo-store
|
|
11
|
+
*
|
|
12
|
+
* Invariants verified:
|
|
13
|
+
* - Memgraph is the primary data model; SQLite is the fallback
|
|
14
|
+
* - No hardcoded company IDs (process.pid suffix)
|
|
15
|
+
* - INSERT OR REPLACE is idempotent
|
|
16
|
+
* - company_id isolation prevents cross-tenant data bleed
|
|
17
|
+
* - company_id=undefined throws (never silently stores null)
|
|
18
|
+
* - updateTask/updateApproval upsert when record is missing
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const os = require('os');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
|
|
25
|
+
// Unique per test run — no cross-run state pollution
|
|
26
|
+
const TEST_ROOT = path.join(os.tmpdir(), 'hbo-core-store-test-' + process.pid);
|
|
27
|
+
process.env.HELIOS_ROOT = TEST_ROOT;
|
|
28
|
+
fs.mkdirSync(path.join(TEST_ROOT, 'data'), { recursive: true });
|
|
29
|
+
|
|
30
|
+
// Must require AFTER setting HELIOS_ROOT
|
|
31
|
+
const store = require('../hbo-core-store');
|
|
32
|
+
|
|
33
|
+
// Unique company IDs — no hardcoded tenant names
|
|
34
|
+
const CID_A = 'test:hbo-store:co-a:' + process.pid;
|
|
35
|
+
const CID_B = 'test:hbo-store:co-b:' + process.pid;
|
|
36
|
+
|
|
37
|
+
let passed = 0;
|
|
38
|
+
let failed = 0;
|
|
39
|
+
const failures = [];
|
|
40
|
+
|
|
41
|
+
function assert(condition, message) {
|
|
42
|
+
if (!condition) {
|
|
43
|
+
const err = new Error('FAIL: ' + message);
|
|
44
|
+
failures.push(err);
|
|
45
|
+
failed++;
|
|
46
|
+
console.error(' ✗', message);
|
|
47
|
+
} else {
|
|
48
|
+
passed++;
|
|
49
|
+
console.log(' ✓', message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function assertEqual(a, b, message) {
|
|
54
|
+
assert(a === b, `${message} (expected ${JSON.stringify(b)}, got ${JSON.stringify(a)})`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function assertNull(a, message) {
|
|
58
|
+
assert(a === null, `${message} (expected null, got ${JSON.stringify(a)})`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function assertThrows(fn, messagePart, label) {
|
|
62
|
+
try {
|
|
63
|
+
fn();
|
|
64
|
+
assert(false, `${label}: expected throw but did not throw`);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
if (messagePart && !e.message.includes(messagePart)) {
|
|
67
|
+
assert(false, `${label}: threw but message "${e.message}" did not contain "${messagePart}"`);
|
|
68
|
+
} else {
|
|
69
|
+
assert(true, label);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Task tests ────────────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
console.log('\n[Tasks]');
|
|
77
|
+
|
|
78
|
+
store.createTask({ id: 't-1', companyId: CID_A, title: 'Test Task', status: 'todo', priority: 1 });
|
|
79
|
+
const t1 = store.getTask('t-1', CID_A);
|
|
80
|
+
assert(t1 !== null, 'getTask returns record after createTask');
|
|
81
|
+
assertEqual(t1.title, 'Test Task', 'getTask.title correct');
|
|
82
|
+
assertEqual(t1.status, 'todo', 'getTask.status correct');
|
|
83
|
+
|
|
84
|
+
assertNull(store.getTask('t-1', CID_B), 'getTask returns null for wrong company (isolation)');
|
|
85
|
+
|
|
86
|
+
store.updateTask('t-1', CID_A, { status: 'in_progress' });
|
|
87
|
+
assertEqual(store.getTask('t-1', CID_A).status, 'in_progress', 'updateTask changes status');
|
|
88
|
+
|
|
89
|
+
// updateTask upsert: record not yet in SQLite → creates it
|
|
90
|
+
store.updateTask('t-upsert', CID_A, { title: 'Upserted', status: 'todo' });
|
|
91
|
+
const tu = store.getTask('t-upsert', CID_A);
|
|
92
|
+
assert(tu !== null, 'updateTask upserts when record is missing');
|
|
93
|
+
assertEqual(tu.status, 'todo', 'upserted task has correct status');
|
|
94
|
+
|
|
95
|
+
store.createTask({ id: 't-2', companyId: CID_A, title: 'Task 2', status: 'done' });
|
|
96
|
+
const todos = store.getTasksByCompanyStatus(CID_A, 'todo');
|
|
97
|
+
assert(todos.some(t => t.id === 't-upsert'), 'getTasksByCompanyStatus(todo) includes todo tasks');
|
|
98
|
+
assert(!todos.some(t => t.id === 't-2'), 'getTasksByCompanyStatus(todo) excludes done tasks');
|
|
99
|
+
|
|
100
|
+
store.updateTask('t-1', CID_A, { status: 'in_progress' });
|
|
101
|
+
const active = store.getTasksByCompanyStatus(CID_A, ['todo', 'in_progress']);
|
|
102
|
+
assert(active.some(t => t.id === 't-1'), 'multi-status filter includes in_progress');
|
|
103
|
+
assert(active.some(t => t.id === 't-upsert'), 'multi-status filter includes todo');
|
|
104
|
+
assert(!active.some(t => t.id === 't-2'), 'multi-status filter excludes done');
|
|
105
|
+
|
|
106
|
+
assertThrows(
|
|
107
|
+
() => store.createTask({ id: 't-nocompany', title: 'X' }),
|
|
108
|
+
'company_id is required',
|
|
109
|
+
'createTask throws when companyId is missing'
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Idempotency
|
|
113
|
+
store.createTask({ id: 't-1', companyId: CID_A, title: 'Updated Title', status: 'in_progress' });
|
|
114
|
+
const allTasks = store.getTasksByCompanyStatus(CID_A, ['todo', 'in_progress', 'done']);
|
|
115
|
+
assertEqual(allTasks.filter(t => t.id === 't-1').length, 1, 'createTask is idempotent (no duplicate rows)');
|
|
116
|
+
assertEqual(store.getTask('t-1', CID_A).title, 'Updated Title', 'second createTask updates the record');
|
|
117
|
+
|
|
118
|
+
store.deleteTask('t-2', CID_A);
|
|
119
|
+
assertNull(store.getTask('t-2', CID_A), 'deleteTask removes the record');
|
|
120
|
+
|
|
121
|
+
// ── Approval tests ────────────────────────────────────────────────────────────
|
|
122
|
+
|
|
123
|
+
console.log('\n[Approvals]');
|
|
124
|
+
|
|
125
|
+
store.createApproval({ id: 'ap-1', companyId: CID_A, type: 'budget_exceeded', status: 'pending', requestedBy: 'agent:ceo' });
|
|
126
|
+
const ap1 = store.getApproval('ap-1', CID_A);
|
|
127
|
+
assert(ap1 !== null, 'getApproval returns record after createApproval');
|
|
128
|
+
assertEqual(ap1.type, 'budget_exceeded', 'approval type correct');
|
|
129
|
+
assertNull(store.getApproval('ap-1', CID_B), 'approval isolated from other company');
|
|
130
|
+
|
|
131
|
+
store.updateApproval('ap-1', CID_A, { status: 'approved', followUpTaskCreated: false });
|
|
132
|
+
assertEqual(store.getApproval('ap-1', CID_A).status, 'approved', 'updateApproval changes status');
|
|
133
|
+
|
|
134
|
+
store.updateApproval('ap-upsert', CID_A, { status: 'pending', type: 'belief_confirmation' });
|
|
135
|
+
assert(store.getApproval('ap-upsert', CID_A) !== null, 'updateApproval upserts when missing');
|
|
136
|
+
|
|
137
|
+
const pendingApprovals = store.getApprovalsByCompanyStatus(CID_A, 'pending');
|
|
138
|
+
assert(pendingApprovals.some(a => a.id === 'ap-upsert'), 'getApprovalsByCompanyStatus returns pending');
|
|
139
|
+
assert(!pendingApprovals.some(a => a.id === 'ap-1'), 'getApprovalsByCompanyStatus excludes approved');
|
|
140
|
+
|
|
141
|
+
assertThrows(
|
|
142
|
+
() => store.createApproval({ id: 'ap-nocompany', type: 'x' }),
|
|
143
|
+
'company_id is required',
|
|
144
|
+
'createApproval throws when companyId is missing'
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// ── BudgetPolicy tests ────────────────────────────────────────────────────────
|
|
148
|
+
|
|
149
|
+
console.log('\n[Goals]');
|
|
150
|
+
|
|
151
|
+
store.createGoal({
|
|
152
|
+
id: 'goal-1',
|
|
153
|
+
companyId: CID_A,
|
|
154
|
+
title: 'Increase retention',
|
|
155
|
+
description: 'Company-level goal',
|
|
156
|
+
level: 'company',
|
|
157
|
+
status: 'active',
|
|
158
|
+
});
|
|
159
|
+
const goal1 = store.getGoal('goal-1', CID_A);
|
|
160
|
+
assert(goal1 !== null, 'getGoal returns record after createGoal');
|
|
161
|
+
assertEqual(goal1.title, 'Increase retention', 'getGoal.title correct');
|
|
162
|
+
assertEqual(goal1.level, 'company', 'getGoal.level correct');
|
|
163
|
+
assertNull(store.getGoal('goal-1', CID_B), 'getGoal returns null for wrong company (isolation)');
|
|
164
|
+
|
|
165
|
+
store.updateGoal('goal-1', CID_A, { title: 'Increase net retention', status: 'paused' });
|
|
166
|
+
const updatedGoal = store.getGoal('goal-1', CID_A);
|
|
167
|
+
assertEqual(updatedGoal.title, 'Increase net retention', 'updateGoal changes title');
|
|
168
|
+
assertEqual(updatedGoal.status, 'paused', 'updateGoal changes status');
|
|
169
|
+
|
|
170
|
+
store.createGoal({
|
|
171
|
+
id: 'goal-child',
|
|
172
|
+
companyId: CID_A,
|
|
173
|
+
title: 'Improve onboarding',
|
|
174
|
+
level: 'department',
|
|
175
|
+
status: 'active',
|
|
176
|
+
parentId: 'goal-1',
|
|
177
|
+
});
|
|
178
|
+
const companyGoals = store.getGoalsByCompany(CID_A);
|
|
179
|
+
assert(companyGoals.some(g => g.id === 'goal-1'), 'getGoalsByCompany includes parent goal');
|
|
180
|
+
assert(companyGoals.some(g => g.id === 'goal-child' && g.parentId === 'goal-1'), 'getGoalsByCompany includes child goal with parentId');
|
|
181
|
+
assert(!store.getGoalsByCompany(CID_B).some(g => g.id === 'goal-1'), 'goals isolated from other company');
|
|
182
|
+
|
|
183
|
+
assertThrows(
|
|
184
|
+
() => store.createGoal({ id: 'goal-nocompany', title: 'X' }),
|
|
185
|
+
'company_id is required',
|
|
186
|
+
'createGoal throws when companyId is missing'
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
store.deleteGoal('goal-child', CID_A);
|
|
190
|
+
assertNull(store.getGoal('goal-child', CID_A), 'deleteGoal removes the record');
|
|
191
|
+
|
|
192
|
+
// ── BudgetPolicy tests ───────────────────────────────────────────────────────
|
|
193
|
+
|
|
194
|
+
console.log('\n[BudgetPolicy]');
|
|
195
|
+
|
|
196
|
+
store.upsertBudgetPolicy({ id: 'bp-1', companyId: CID_A, agentId: 'agent:sales', limitCents: 50000, warnPercent: 80, hardStopEnabled: true });
|
|
197
|
+
assert(store.getBudgetPolicy('bp-1', CID_A) !== null, 'getBudgetPolicy returns record after upsert');
|
|
198
|
+
assert(store.getBudgetPoliciesByCompany(CID_A).some(p => p.id === 'bp-1'), 'getBudgetPoliciesByCompany returns the policy');
|
|
199
|
+
assert(!store.getBudgetPoliciesByCompany(CID_B).some(p => p.id === 'bp-1'), 'policy isolated from other company');
|
|
200
|
+
|
|
201
|
+
// ── OKR tests ─────────────────────────────────────────────────────────────────
|
|
202
|
+
|
|
203
|
+
console.log('\n[OKRs]');
|
|
204
|
+
|
|
205
|
+
store.upsertOKR({ id: 'okr-1', companyId: CID_A, type: 'quarterly_okr', status: 'active', title: 'Q3 OKR' });
|
|
206
|
+
assert(store.getOKRsByCompanyType(CID_A, 'quarterly_okr', 'active').some(o => o.id === 'okr-1'), 'getOKRsByCompanyType returns active quarterly OKR');
|
|
207
|
+
assert(!store.getOKRsByCompanyType(CID_A, 'quarterly', 'active').some(o => o.id === 'okr-1'), "type='quarterly' does NOT match 'quarterly_okr'");
|
|
208
|
+
assert(!store.getOKRsByCompanyType(CID_B, 'quarterly_okr', 'active').some(o => o.id === 'okr-1'), 'OKR isolated from other company');
|
|
209
|
+
|
|
210
|
+
// ── Lead tests ────────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
console.log('\n[Leads]');
|
|
213
|
+
|
|
214
|
+
store.upsertLead({ id: 'lead-1', companyId: CID_A, status: 'new', name: 'Alice', email: 'alice@example.com' });
|
|
215
|
+
assert(store.getLeadsByCompanyStatus(CID_A, ['new', 'stale']).some(l => l.id === 'lead-1'), 'getLeadsByCompanyStatus returns new lead');
|
|
216
|
+
assert(!store.getLeadsByCompanyStatus(CID_B, ['new']).some(l => l.id === 'lead-1'), 'lead isolated from other company');
|
|
217
|
+
|
|
218
|
+
// ── CostEvent tests ───────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
console.log('\n[CostEvents]');
|
|
221
|
+
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
store.createCostEvent({ id: 'ce-1', companyId: CID_A, feature: 'llm', model: 'claude-sonnet', amountUsd: 0.05, createdAt: now });
|
|
224
|
+
assert(store.getCostEventsByCompanyRange(CID_A, now - 1000, now + 1000).some(e => e.id === 'ce-1'), 'getCostEventsByCompanyRange returns event in range');
|
|
225
|
+
assert(!store.getCostEventsByCompanyRange(CID_B, now - 1000, now + 1000).some(e => e.id === 'ce-1'), 'cost event isolated from other company');
|
|
226
|
+
|
|
227
|
+
// ── Summary ───────────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
console.log('\n─────────────────────────────────────────');
|
|
230
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
231
|
+
if (failed > 0) {
|
|
232
|
+
console.error('\nFailures:');
|
|
233
|
+
failures.forEach(f => console.error(' -', f.message));
|
|
234
|
+
process.exit(1);
|
|
235
|
+
} else {
|
|
236
|
+
console.log('All tests passed.');
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
package/lib/brain-v2-budget.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/brain-v2.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/broker/bulkhead.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/broker/mage-queue.js
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/broker/types.d.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CcrStore = void 0;
|
|
4
|
+
exports.getCcrStore = getCcrStore;
|
|
5
|
+
class CcrStore {
|
|
6
|
+
store = new Map();
|
|
7
|
+
ttlMs;
|
|
8
|
+
cleanupInterval = null;
|
|
9
|
+
constructor(ttlSeconds = 7200) {
|
|
10
|
+
this.ttlMs = ttlSeconds * 1000;
|
|
11
|
+
// Cleanup every 10 minutes
|
|
12
|
+
this.cleanupInterval = setInterval(() => this._cleanup(), 10 * 60 * 1000);
|
|
13
|
+
// Don't hold the event loop open
|
|
14
|
+
if (this.cleanupInterval.unref)
|
|
15
|
+
this.cleanupInterval.unref();
|
|
16
|
+
}
|
|
17
|
+
set(hash, data) {
|
|
18
|
+
this.store.set(hash, {
|
|
19
|
+
data,
|
|
20
|
+
expiresAt: Date.now() + this.ttlMs,
|
|
21
|
+
originalBytes: data.length,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
get(hash) {
|
|
25
|
+
const entry = this.store.get(hash);
|
|
26
|
+
if (!entry)
|
|
27
|
+
return null;
|
|
28
|
+
if (Date.now() > entry.expiresAt) {
|
|
29
|
+
this.store.delete(hash);
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return entry.data;
|
|
33
|
+
}
|
|
34
|
+
delete(hash) {
|
|
35
|
+
this.store.delete(hash);
|
|
36
|
+
}
|
|
37
|
+
has(hash) {
|
|
38
|
+
return this.get(hash) !== null;
|
|
39
|
+
}
|
|
40
|
+
stats() {
|
|
41
|
+
let totalOriginalBytes = 0;
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
let entries = 0;
|
|
44
|
+
for (const [, entry] of this.store) {
|
|
45
|
+
if (now <= entry.expiresAt) {
|
|
46
|
+
totalOriginalBytes += entry.originalBytes;
|
|
47
|
+
entries++;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { entries, totalOriginalBytes };
|
|
51
|
+
}
|
|
52
|
+
_cleanup() {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const [hash, entry] of this.store) {
|
|
55
|
+
if (now > entry.expiresAt)
|
|
56
|
+
this.store.delete(hash);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
destroy() {
|
|
60
|
+
if (this.cleanupInterval) {
|
|
61
|
+
clearInterval(this.cleanupInterval);
|
|
62
|
+
this.cleanupInterval = null;
|
|
63
|
+
}
|
|
64
|
+
this.store.clear();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.CcrStore = CcrStore;
|
|
68
|
+
// Singleton for the daemon process
|
|
69
|
+
let _singleton = null;
|
|
70
|
+
function getCcrStore(ttlSeconds = 7200) {
|
|
71
|
+
if (!_singleton)
|
|
72
|
+
_singleton = new CcrStore(ttlSeconds);
|
|
73
|
+
return _singleton;
|
|
74
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContentRouter = void 0;
|
|
4
|
+
exports.detectContentType = detectContentType;
|
|
5
|
+
/**
|
|
6
|
+
* lib/compression/content-router.ts
|
|
7
|
+
*
|
|
8
|
+
* ContentRouter: detects content type in a message block and routes to the
|
|
9
|
+
* appropriate compressor.
|
|
10
|
+
*
|
|
11
|
+
* Current compressors:
|
|
12
|
+
* JSON_ARRAY → SmartCrusher (70–92% reduction)
|
|
13
|
+
* PLAIN_TEXT → Passthrough (Kompress-base ML model not available in TS)
|
|
14
|
+
* SOURCE_CODE → Passthrough (CodeCompressor not yet implemented)
|
|
15
|
+
* other → Passthrough
|
|
16
|
+
*
|
|
17
|
+
* The passthrough path is correct behavior — we improve compression as we
|
|
18
|
+
* add more compressors. SmartCrusher alone covers the dominant HBO payload
|
|
19
|
+
* type (arrays of lead/signal/task/goal objects).
|
|
20
|
+
*
|
|
21
|
+
* Protection rules (content never compressed):
|
|
22
|
+
* - User messages (content might be misinterpreted if rewritten)
|
|
23
|
+
* - Error outputs ≤ 8000 chars (must be preserved exactly)
|
|
24
|
+
* - Tool outputs named 'headroom_retrieve' (infinite loop prevention)
|
|
25
|
+
* - Content shorter than 500 chars (overhead > savings)
|
|
26
|
+
*/
|
|
27
|
+
const smart_crusher_js_1 = require("./smart-crusher.js");
|
|
28
|
+
const CHARS_PER_TOKEN = 3.5; // Claude/Bedrock heuristic
|
|
29
|
+
function estimateTokens(s) {
|
|
30
|
+
return Math.ceil(s.length / CHARS_PER_TOKEN);
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Detect content type from a string block.
|
|
34
|
+
*/
|
|
35
|
+
function detectContentType(text) {
|
|
36
|
+
const trimmed = text.trim();
|
|
37
|
+
// JSON array: starts with [
|
|
38
|
+
if (trimmed.startsWith('[')) {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(trimmed);
|
|
41
|
+
if (Array.isArray(parsed))
|
|
42
|
+
return 'JSON_ARRAY';
|
|
43
|
+
}
|
|
44
|
+
catch { /* not valid JSON */ }
|
|
45
|
+
}
|
|
46
|
+
// JSON object: starts with {
|
|
47
|
+
if (trimmed.startsWith('{')) {
|
|
48
|
+
try {
|
|
49
|
+
JSON.parse(trimmed);
|
|
50
|
+
return 'JSON_OBJECT';
|
|
51
|
+
}
|
|
52
|
+
catch { /* not valid JSON */ }
|
|
53
|
+
}
|
|
54
|
+
// Source code heuristics (backtick code blocks, common keywords)
|
|
55
|
+
if (trimmed.startsWith('```') ||
|
|
56
|
+
trimmed.includes('\ndef ') ||
|
|
57
|
+
trimmed.includes('\nfunction ') ||
|
|
58
|
+
trimmed.includes('\nconst ') ||
|
|
59
|
+
trimmed.includes('\nclass ') ||
|
|
60
|
+
trimmed.includes('\nimport ')) {
|
|
61
|
+
return 'SOURCE_CODE';
|
|
62
|
+
}
|
|
63
|
+
return 'PLAIN_TEXT';
|
|
64
|
+
}
|
|
65
|
+
class ContentRouter {
|
|
66
|
+
crusher;
|
|
67
|
+
constructor(crusherConfig = {}) {
|
|
68
|
+
this.crusher = new smart_crusher_js_1.SmartCrusher(crusherConfig);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Route a single text block to the appropriate compressor.
|
|
72
|
+
* Returns the (possibly compressed) text plus metadata.
|
|
73
|
+
*/
|
|
74
|
+
route(text, toolId, ccrStore) {
|
|
75
|
+
const passthrough = {
|
|
76
|
+
output: text,
|
|
77
|
+
contentType: 'UNKNOWN',
|
|
78
|
+
strategy: 'passthrough',
|
|
79
|
+
compressed: false,
|
|
80
|
+
ccrHash: null,
|
|
81
|
+
tokensSaved: 0,
|
|
82
|
+
rowsDropped: 0,
|
|
83
|
+
};
|
|
84
|
+
// Protection: too short
|
|
85
|
+
if (text.length < 500)
|
|
86
|
+
return passthrough;
|
|
87
|
+
// Protection: never re-compress CCR retrieve results
|
|
88
|
+
if (toolId === 'headroom_retrieve')
|
|
89
|
+
return passthrough;
|
|
90
|
+
// Protection: small error outputs
|
|
91
|
+
if (text.length <= 8000 &&
|
|
92
|
+
(text.toLowerCase().includes('"error"') || text.toLowerCase().includes('"traceback"'))) {
|
|
93
|
+
return passthrough;
|
|
94
|
+
}
|
|
95
|
+
const contentType = detectContentType(text);
|
|
96
|
+
switch (contentType) {
|
|
97
|
+
case 'JSON_ARRAY': {
|
|
98
|
+
const result = this.crusher.compress(text, ccrStore);
|
|
99
|
+
const tokensSaved = estimateTokens(text) - estimateTokens(result.output);
|
|
100
|
+
return {
|
|
101
|
+
output: result.output,
|
|
102
|
+
contentType,
|
|
103
|
+
strategy: result.strategy,
|
|
104
|
+
compressed: result.compressed,
|
|
105
|
+
ccrHash: result.ccrHash,
|
|
106
|
+
tokensSaved: Math.max(0, tokensSaved),
|
|
107
|
+
rowsDropped: result.rowsDropped,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
default:
|
|
111
|
+
return { ...passthrough, contentType };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.ContentRouter = ContentRouter;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompressionPipeline = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* lib/compression/pipeline.ts
|
|
6
|
+
*
|
|
7
|
+
* Message compression pipeline.
|
|
8
|
+
*
|
|
9
|
+
* Processes a messages array (Anthropic or OpenAI format) through the
|
|
10
|
+
* ContentRouter for each tool_result block.
|
|
11
|
+
*
|
|
12
|
+
* The pipeline mirrors what the headroom Python proxy does:
|
|
13
|
+
* 1. Skip system messages (cache prefix must stay stable)
|
|
14
|
+
* 2. Skip user messages (content must be preserved)
|
|
15
|
+
* 3. For each assistant + tool_result message: run ContentRouter
|
|
16
|
+
* 4. Collect total tokens saved and CCR hashes
|
|
17
|
+
*
|
|
18
|
+
* This runs inside the helios-agent daemon process — no HTTP round-trip.
|
|
19
|
+
* Every company's HBO tool results (signals, leads, pipeline, tasks) are
|
|
20
|
+
* compressed before they reach the LLM.
|
|
21
|
+
*/
|
|
22
|
+
const content_router_js_1 = require("./content-router.js");
|
|
23
|
+
/**
|
|
24
|
+
* Deep-copy a messages array (shallow copy of each message, deep copy of
|
|
25
|
+
* content arrays so we don't mutate the caller's data).
|
|
26
|
+
*/
|
|
27
|
+
function deepCopyMessages(messages) {
|
|
28
|
+
return messages.map((msg) => ({
|
|
29
|
+
...msg,
|
|
30
|
+
content: Array.isArray(msg.content)
|
|
31
|
+
? msg.content.map((b) => ({ ...b }))
|
|
32
|
+
: msg.content,
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
function extractText(content) {
|
|
36
|
+
if (!content)
|
|
37
|
+
return '';
|
|
38
|
+
if (typeof content === 'string')
|
|
39
|
+
return content;
|
|
40
|
+
for (const block of content) {
|
|
41
|
+
if (typeof block.content === 'string')
|
|
42
|
+
return block.content;
|
|
43
|
+
if (typeof block.text === 'string')
|
|
44
|
+
return block.text;
|
|
45
|
+
}
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
function originalLength(messages) {
|
|
49
|
+
return JSON.stringify(messages).length;
|
|
50
|
+
}
|
|
51
|
+
class CompressionPipeline {
|
|
52
|
+
router;
|
|
53
|
+
constructor() {
|
|
54
|
+
this.router = new content_router_js_1.ContentRouter();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Compress a messages array.
|
|
58
|
+
*
|
|
59
|
+
* Compresses tool_result blocks in ALL messages (user and assistant).
|
|
60
|
+
* In Anthropic format, tool_result blocks appear in user messages (they
|
|
61
|
+
* are the response to the assistant's tool_use calls). These are the
|
|
62
|
+
* dominant source of large payloads — HBO signals, leads, pipeline data.
|
|
63
|
+
*
|
|
64
|
+
* What we do NOT compress:
|
|
65
|
+
* - Plain text content in user messages (user intent must be preserved)
|
|
66
|
+
* - System messages (prompt cache stability)
|
|
67
|
+
* - Anything shorter than 500 chars
|
|
68
|
+
*/
|
|
69
|
+
compress(messages, ccrStore) {
|
|
70
|
+
const origLen = originalLength(messages);
|
|
71
|
+
const copied = deepCopyMessages(messages);
|
|
72
|
+
let totalTokensSaved = 0;
|
|
73
|
+
const ccrHashes = [];
|
|
74
|
+
const transformsApplied = new Set();
|
|
75
|
+
for (const msg of copied) {
|
|
76
|
+
// Skip system messages (preserve prompt cache prefix exactly)
|
|
77
|
+
if (msg.role === 'system')
|
|
78
|
+
continue;
|
|
79
|
+
if (!Array.isArray(msg.content))
|
|
80
|
+
continue;
|
|
81
|
+
for (const block of msg.content) {
|
|
82
|
+
// Only process tool_result blocks — this is where HBO data lives
|
|
83
|
+
// (tool_result appears in user messages in Anthropic format)
|
|
84
|
+
if (block.type !== 'tool_result')
|
|
85
|
+
continue;
|
|
86
|
+
const text = typeof block.content === 'string'
|
|
87
|
+
? block.content
|
|
88
|
+
: extractText(block.content);
|
|
89
|
+
if (!text)
|
|
90
|
+
continue;
|
|
91
|
+
const toolId = block.tool_use_id;
|
|
92
|
+
const result = this.router.route(text, toolId, ccrStore);
|
|
93
|
+
if (result.compressed) {
|
|
94
|
+
block.content = result.output;
|
|
95
|
+
totalTokensSaved += result.tokensSaved;
|
|
96
|
+
transformsApplied.add(result.strategy);
|
|
97
|
+
if (result.ccrHash)
|
|
98
|
+
ccrHashes.push(result.ccrHash);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const newLen = originalLength(copied);
|
|
103
|
+
const compressionRatio = origLen > 0 ? newLen / origLen : 1;
|
|
104
|
+
return {
|
|
105
|
+
messages: copied,
|
|
106
|
+
tokensSaved: totalTokensSaved,
|
|
107
|
+
ccrHashes,
|
|
108
|
+
compressionRatio,
|
|
109
|
+
transformsApplied: [...transformsApplied],
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.CompressionPipeline = CompressionPipeline;
|