@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,265 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createCompressionServer = createCompressionServer;
|
|
7
|
+
/**
|
|
8
|
+
* lib/compression/server.ts
|
|
9
|
+
*
|
|
10
|
+
* Helios Compression Server — TypeScript implementation of the Headroom proxy
|
|
11
|
+
* HTTP interface.
|
|
12
|
+
*
|
|
13
|
+
* Replaces the Python headroom proxy entirely. Starts in milliseconds (no ML
|
|
14
|
+
* model loading), works natively on Windows and macOS.
|
|
15
|
+
*
|
|
16
|
+
* Endpoints (headroom proxy compatible):
|
|
17
|
+
* GET /health → { status, version, uptime }
|
|
18
|
+
* GET /stats → compression statistics
|
|
19
|
+
* POST /v1/messages → compress Anthropic message array
|
|
20
|
+
* POST /v1/chat/completions → compress OpenAI message array
|
|
21
|
+
* GET /v1/retrieve/:hash → retrieve original CCR content
|
|
22
|
+
* POST /headroom/compress → direct compress endpoint
|
|
23
|
+
*
|
|
24
|
+
* All endpoints are cross-company — there is no tenant scoping in the
|
|
25
|
+
* compression layer. Compression is a pure function of content shape,
|
|
26
|
+
* not company identity.
|
|
27
|
+
*/
|
|
28
|
+
const http_1 = __importDefault(require("http"));
|
|
29
|
+
const pipeline_js_1 = require("./pipeline.js");
|
|
30
|
+
const ccr_store_js_1 = require("./ccr-store.js");
|
|
31
|
+
const VERSION = '1.0.0-helios';
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
// Singleton pipeline and CCR store
|
|
34
|
+
const pipeline = new pipeline_js_1.CompressionPipeline();
|
|
35
|
+
const ccrStore = (0, ccr_store_js_1.getCcrStore)(7200);
|
|
36
|
+
// Compression telemetry (process-lifetime counters)
|
|
37
|
+
const stats = {
|
|
38
|
+
requests: { total: 0, compressed: 0, passthrough: 0, failed: 0 },
|
|
39
|
+
tokens: { saved: 0 },
|
|
40
|
+
ccrStore: { puts: 0 },
|
|
41
|
+
};
|
|
42
|
+
// ── HTTP helpers ──────────────────────────────────────────────────────────────
|
|
43
|
+
function readBody(req) {
|
|
44
|
+
return new Promise((resolve, reject) => {
|
|
45
|
+
const chunks = [];
|
|
46
|
+
req.on('data', (c) => chunks.push(c));
|
|
47
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
48
|
+
req.on('error', reject);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
function json(res, status, body) {
|
|
52
|
+
const payload = JSON.stringify(body);
|
|
53
|
+
res.writeHead(status, {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
'Access-Control-Allow-Origin': '*',
|
|
56
|
+
'X-Headroom-Version': VERSION,
|
|
57
|
+
});
|
|
58
|
+
res.end(payload);
|
|
59
|
+
}
|
|
60
|
+
function error(res, status, message) {
|
|
61
|
+
json(res, status, { error: message });
|
|
62
|
+
}
|
|
63
|
+
// ── Route handlers ────────────────────────────────────────────────────────────
|
|
64
|
+
function handleHealth(_req, res) {
|
|
65
|
+
json(res, 200, {
|
|
66
|
+
status: 'healthy',
|
|
67
|
+
version: VERSION,
|
|
68
|
+
uptime: (Date.now() - startedAt) / 1000,
|
|
69
|
+
available: true,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function handleStats(_req, res) {
|
|
73
|
+
const ccrStats = ccrStore.stats();
|
|
74
|
+
json(res, 200, {
|
|
75
|
+
available: true,
|
|
76
|
+
requests: stats.requests,
|
|
77
|
+
tokens: {
|
|
78
|
+
saved: stats.tokens.saved,
|
|
79
|
+
savingsPercent: stats.requests.total > 0
|
|
80
|
+
? Math.round((stats.tokens.saved / Math.max(stats.tokens.saved + (stats.requests.total * 100), 1)) * 100)
|
|
81
|
+
: 0,
|
|
82
|
+
},
|
|
83
|
+
ccr: {
|
|
84
|
+
items: ccrStats.entries,
|
|
85
|
+
totalOriginalBytes: ccrStats.totalOriginalBytes,
|
|
86
|
+
},
|
|
87
|
+
uptime: (Date.now() - startedAt) / 1000,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function handleCompressMessages(req, res) {
|
|
91
|
+
stats.requests.total++;
|
|
92
|
+
let body;
|
|
93
|
+
try {
|
|
94
|
+
body = JSON.parse(await readBody(req));
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
stats.requests.failed++;
|
|
98
|
+
return error(res, 400, 'Invalid JSON body');
|
|
99
|
+
}
|
|
100
|
+
const messages = body.messages;
|
|
101
|
+
if (!Array.isArray(messages)) {
|
|
102
|
+
// Not a messages request — passthrough
|
|
103
|
+
stats.requests.passthrough++;
|
|
104
|
+
json(res, 200, body);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const result = pipeline.compress(messages, ccrStore);
|
|
109
|
+
stats.tokens.saved += result.tokensSaved;
|
|
110
|
+
if (result.ccrHashes.length > 0)
|
|
111
|
+
stats.ccrStore.puts += result.ccrHashes.length;
|
|
112
|
+
if (result.transformsApplied.length > 0) {
|
|
113
|
+
stats.requests.compressed++;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
stats.requests.passthrough++;
|
|
117
|
+
}
|
|
118
|
+
// Return in same shape as input, with compressed messages
|
|
119
|
+
const responseBody = {
|
|
120
|
+
...body,
|
|
121
|
+
messages: result.messages,
|
|
122
|
+
};
|
|
123
|
+
// Attach headroom metadata in response headers
|
|
124
|
+
res.setHeader('X-Headroom-Saved', String(result.tokensSaved));
|
|
125
|
+
res.setHeader('X-Headroom-Ratio', result.compressionRatio.toFixed(3));
|
|
126
|
+
if (result.ccrHashes.length > 0) {
|
|
127
|
+
res.setHeader('X-Headroom-CCR', result.ccrHashes.join(','));
|
|
128
|
+
}
|
|
129
|
+
if (result.transformsApplied.length > 0) {
|
|
130
|
+
res.setHeader('X-Headroom-Transforms', result.transformsApplied.join(','));
|
|
131
|
+
}
|
|
132
|
+
json(res, 200, responseBody);
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
stats.requests.failed++;
|
|
136
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
137
|
+
error(res, 500, `Compression failed: ${msg}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function handleDirectCompress(req, res) {
|
|
141
|
+
stats.requests.total++;
|
|
142
|
+
let body;
|
|
143
|
+
try {
|
|
144
|
+
body = JSON.parse(await readBody(req));
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
stats.requests.failed++;
|
|
148
|
+
return error(res, 400, 'Invalid JSON body');
|
|
149
|
+
}
|
|
150
|
+
const messages = body.messages;
|
|
151
|
+
if (!Array.isArray(messages)) {
|
|
152
|
+
stats.requests.passthrough++;
|
|
153
|
+
json(res, 200, { messages: body, tokensSaved: 0, compressionRatio: 1, transformsApplied: [], ccrHashes: [] });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
const result = pipeline.compress(messages, ccrStore);
|
|
158
|
+
stats.tokens.saved += result.tokensSaved;
|
|
159
|
+
if (result.ccrHashes.length > 0)
|
|
160
|
+
stats.ccrStore.puts += result.ccrHashes.length;
|
|
161
|
+
if (result.transformsApplied.length > 0)
|
|
162
|
+
stats.requests.compressed++;
|
|
163
|
+
else
|
|
164
|
+
stats.requests.passthrough++;
|
|
165
|
+
json(res, 200, {
|
|
166
|
+
messages: result.messages,
|
|
167
|
+
tokensSaved: result.tokensSaved,
|
|
168
|
+
compressionRatio: result.compressionRatio,
|
|
169
|
+
transformsApplied: result.transformsApplied,
|
|
170
|
+
ccrHashes: result.ccrHashes,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
stats.requests.failed++;
|
|
175
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
176
|
+
error(res, 500, `Compression failed: ${msg}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function handleRetrieve(_req, res, hash) {
|
|
180
|
+
if (!hash || !/^[a-f0-9]{8,64}$/.test(hash)) {
|
|
181
|
+
return error(res, 400, 'Invalid hash format');
|
|
182
|
+
}
|
|
183
|
+
const data = ccrStore.get(hash);
|
|
184
|
+
if (!data) {
|
|
185
|
+
return error(res, 404, `CCR hash not found or expired: ${hash}`);
|
|
186
|
+
}
|
|
187
|
+
json(res, 200, {
|
|
188
|
+
hash,
|
|
189
|
+
data: JSON.parse(data),
|
|
190
|
+
originalBytes: data.length,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
// ── Server ────────────────────────────────────────────────────────────────────
|
|
194
|
+
function createCompressionServer() {
|
|
195
|
+
return http_1.default.createServer(async (req, res) => {
|
|
196
|
+
const url = req.url ?? '/';
|
|
197
|
+
const method = req.method ?? 'GET';
|
|
198
|
+
// CORS preflight
|
|
199
|
+
if (method === 'OPTIONS') {
|
|
200
|
+
res.writeHead(204, {
|
|
201
|
+
'Access-Control-Allow-Origin': '*',
|
|
202
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
203
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization, x-api-key, anthropic-version',
|
|
204
|
+
});
|
|
205
|
+
res.end();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
// Health
|
|
210
|
+
if (url === '/health' || url === '/livez' || url === '/readyz') {
|
|
211
|
+
return handleHealth(req, res);
|
|
212
|
+
}
|
|
213
|
+
// Stats
|
|
214
|
+
if (url === '/stats' || url === '/metrics') {
|
|
215
|
+
return handleStats(req, res);
|
|
216
|
+
}
|
|
217
|
+
// Direct compress (used by helios-agent internals and context-compaction.ts)
|
|
218
|
+
if (url === '/headroom/compress' && method === 'POST') {
|
|
219
|
+
return await handleDirectCompress(req, res);
|
|
220
|
+
}
|
|
221
|
+
// headroom-ai SDK compress endpoint — the SDK calls POST /v1/compress
|
|
222
|
+
// with { messages, model, config } and expects the same shape back.
|
|
223
|
+
// We map this to handleDirectCompress which is identical in behavior.
|
|
224
|
+
if (url === '/v1/compress' && method === 'POST') {
|
|
225
|
+
return await handleDirectCompress(req, res);
|
|
226
|
+
}
|
|
227
|
+
// Anthropic messages endpoint (intercept + compress)
|
|
228
|
+
if (url === '/v1/messages' && method === 'POST') {
|
|
229
|
+
return await handleCompressMessages(req, res);
|
|
230
|
+
}
|
|
231
|
+
// OpenAI chat completions endpoint (intercept + compress)
|
|
232
|
+
if (url === '/v1/chat/completions' && method === 'POST') {
|
|
233
|
+
return await handleCompressMessages(req, res);
|
|
234
|
+
}
|
|
235
|
+
// CCR retrieval
|
|
236
|
+
const ccrMatch = url.match(/^\/v1\/retrieve\/([a-f0-9]{8,64})$/);
|
|
237
|
+
if (ccrMatch && method === 'GET') {
|
|
238
|
+
return handleRetrieve(req, res, ccrMatch[1]);
|
|
239
|
+
}
|
|
240
|
+
// 404
|
|
241
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
242
|
+
res.end(JSON.stringify({ error: `Unknown endpoint: ${method} ${url}` }));
|
|
243
|
+
}
|
|
244
|
+
catch (e) {
|
|
245
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
246
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
247
|
+
res.end(JSON.stringify({ error: `Server error: ${msg}` }));
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
// ── Standalone entry point ────────────────────────────────────────────────────
|
|
252
|
+
if (require.main === module) {
|
|
253
|
+
const port = parseInt(process.env.HEADROOM_PORT ?? '8787', 10);
|
|
254
|
+
const server = createCompressionServer();
|
|
255
|
+
server.listen(port, '127.0.0.1', () => {
|
|
256
|
+
process.stdout.write(JSON.stringify({ event: 'ready', port, version: VERSION }) + '\n');
|
|
257
|
+
});
|
|
258
|
+
server.on('error', (err) => {
|
|
259
|
+
if (err.code === 'EADDRINUSE') {
|
|
260
|
+
process.stderr.write(`[compression-server] Port ${port} already in use\n`);
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
throw err;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SmartCrusher = exports.DEFAULT_CRUSHER_CONFIG = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* lib/compression/smart-crusher.ts
|
|
6
|
+
*
|
|
7
|
+
* TypeScript implementation of the Headroom SmartCrusher algorithm.
|
|
8
|
+
*
|
|
9
|
+
* SmartCrusher compresses JSON arrays by:
|
|
10
|
+
* 1. Lossless path: for homogeneous arrays, render as CSV+schema (removes
|
|
11
|
+
* repeated key names). Wins when savings ≥ 15%.
|
|
12
|
+
* 2. Lossy path: keep first 30% + last 15% + importance-scored middle 55%.
|
|
13
|
+
* Dropped rows are stored in the CCR store and retrievable on demand.
|
|
14
|
+
*
|
|
15
|
+
* Ported from headroom/crates/headroom-core (Rust) algorithm.
|
|
16
|
+
* No external dependencies — pure TypeScript.
|
|
17
|
+
*
|
|
18
|
+
* Every company's HBO data (signals, leads, pipeline, tasks, goals) flows
|
|
19
|
+
* through this compressor before reaching the LLM, reducing token cost
|
|
20
|
+
* by 70–92% for typical array payloads.
|
|
21
|
+
*/
|
|
22
|
+
const crypto_1 = require("crypto");
|
|
23
|
+
exports.DEFAULT_CRUSHER_CONFIG = {
|
|
24
|
+
minItemsToAnalyze: 5,
|
|
25
|
+
minCharsToCompress: 200,
|
|
26
|
+
maxItemsAfterCrush: 15,
|
|
27
|
+
firstFraction: 0.3,
|
|
28
|
+
lastFraction: 0.15,
|
|
29
|
+
losslessMinSavingsRatio: 0.15,
|
|
30
|
+
compactionCoreFieldFraction: 0.8,
|
|
31
|
+
};
|
|
32
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
33
|
+
function shortHash(data) {
|
|
34
|
+
return (0, crypto_1.createHash)('sha256').update(data).digest('hex').slice(0, 12);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Check if an array is homogeneous enough for lossless CSV compaction.
|
|
38
|
+
*
|
|
39
|
+
* Conditions (all must be true):
|
|
40
|
+
* 1. ≥90% of items are plain objects
|
|
41
|
+
* 2. At least 3 fields appear in ≥ coreFieldFraction of items
|
|
42
|
+
* (single shared field like 'id' is not enough structure for CSV)
|
|
43
|
+
* 3. Core fields cover ≥50% of the average object's field count
|
|
44
|
+
* (prevents trivial matches where most fields are non-shared)
|
|
45
|
+
*/
|
|
46
|
+
function isHomogeneousArray(items, coreFieldFraction) {
|
|
47
|
+
if (items.length === 0)
|
|
48
|
+
return { homogeneous: false, coreFields: [] };
|
|
49
|
+
const objectItems = items.filter((x) => x !== null && typeof x === 'object' && !Array.isArray(x));
|
|
50
|
+
if (objectItems.length / items.length < 0.9) {
|
|
51
|
+
return { homogeneous: false, coreFields: [] };
|
|
52
|
+
}
|
|
53
|
+
// Count field frequencies
|
|
54
|
+
const fieldFreq = new Map();
|
|
55
|
+
let totalFields = 0;
|
|
56
|
+
for (const obj of objectItems) {
|
|
57
|
+
const keys = Object.keys(obj);
|
|
58
|
+
totalFields += keys.length;
|
|
59
|
+
for (const key of keys) {
|
|
60
|
+
fieldFreq.set(key, (fieldFreq.get(key) ?? 0) + 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Core fields: appear in ≥ coreFieldFraction of objects
|
|
64
|
+
const threshold = objectItems.length * coreFieldFraction;
|
|
65
|
+
const coreFields = [...fieldFreq.entries()]
|
|
66
|
+
.filter(([, count]) => count >= threshold)
|
|
67
|
+
.map(([key]) => key)
|
|
68
|
+
.sort();
|
|
69
|
+
// Gate 1: must have at least 3 core fields
|
|
70
|
+
// A single shared key (like 'id') is not enough structure for meaningful CSV
|
|
71
|
+
if (coreFields.length < 3) {
|
|
72
|
+
return { homogeneous: false, coreFields: [] };
|
|
73
|
+
}
|
|
74
|
+
// Gate 2: core fields must cover ≥50% of the average object's field count
|
|
75
|
+
// Prevents trivial CSV on objects where most fields are non-shared
|
|
76
|
+
const avgFieldCount = totalFields / objectItems.length;
|
|
77
|
+
const coverage = coreFields.length / avgFieldCount;
|
|
78
|
+
if (coverage < 0.5) {
|
|
79
|
+
return { homogeneous: false, coreFields: [] };
|
|
80
|
+
}
|
|
81
|
+
return { homogeneous: true, coreFields };
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Lossless CSV compaction for homogeneous arrays.
|
|
85
|
+
* Format: header row + value rows, tab-separated within each row,
|
|
86
|
+
* pipe-separated rows. Significantly reduces token count for repeated keys.
|
|
87
|
+
*/
|
|
88
|
+
function losslessCsvCompress(items, coreFields) {
|
|
89
|
+
const header = coreFields.join('\t');
|
|
90
|
+
const rows = items.map((item) => coreFields
|
|
91
|
+
.map((f) => {
|
|
92
|
+
const v = item[f];
|
|
93
|
+
if (v === null || v === undefined)
|
|
94
|
+
return '';
|
|
95
|
+
if (typeof v === 'object')
|
|
96
|
+
return JSON.stringify(v);
|
|
97
|
+
return String(v);
|
|
98
|
+
})
|
|
99
|
+
.join('\t'));
|
|
100
|
+
return `[csv:${coreFields.length}cols×${items.length}rows]\n${header}\n${rows.join('\n')}`;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Score items for importance. Higher = more important to keep.
|
|
104
|
+
* Keeps items that: have errors, have non-null/non-zero values,
|
|
105
|
+
* or are at structural boundaries (first/last).
|
|
106
|
+
* Mid-range items are scored by field diversity (more unique values = more important).
|
|
107
|
+
*/
|
|
108
|
+
function scoreItems(items) {
|
|
109
|
+
return items.map((item) => {
|
|
110
|
+
let score = 0;
|
|
111
|
+
const str = JSON.stringify(item);
|
|
112
|
+
// Error/anomaly signals — always preserve
|
|
113
|
+
if (str.includes('"error"') ||
|
|
114
|
+
str.includes('"failed"') ||
|
|
115
|
+
str.includes('"ERROR"') ||
|
|
116
|
+
str.includes('"FAILED"')) {
|
|
117
|
+
score += 100;
|
|
118
|
+
}
|
|
119
|
+
// Non-trivial values (not null, not 0, not empty string)
|
|
120
|
+
const values = Object.values(item);
|
|
121
|
+
const nonTrivial = values.filter((v) => v !== null && v !== undefined && v !== '' && v !== 0 && v !== false).length;
|
|
122
|
+
score += (nonTrivial / Math.max(values.length, 1)) * 10;
|
|
123
|
+
// Field diversity (unique values signal interesting data)
|
|
124
|
+
const uniqueVals = new Set(values.map((v) => JSON.stringify(v))).size;
|
|
125
|
+
score += (uniqueVals / Math.max(values.length, 1)) * 5;
|
|
126
|
+
return score;
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// ── Main SmartCrusher ─────────────────────────────────────────────────────────
|
|
130
|
+
class SmartCrusher {
|
|
131
|
+
cfg;
|
|
132
|
+
constructor(config = {}) {
|
|
133
|
+
this.cfg = { ...exports.DEFAULT_CRUSHER_CONFIG, ...config };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Compress a JSON array string.
|
|
137
|
+
* If input is not a JSON array, returns passthrough.
|
|
138
|
+
* If array is too small, returns passthrough.
|
|
139
|
+
* Otherwise attempts lossless CSV then lossy sampling.
|
|
140
|
+
*/
|
|
141
|
+
compress(input, ccrStore) {
|
|
142
|
+
const passthrough = {
|
|
143
|
+
output: input,
|
|
144
|
+
compressed: false,
|
|
145
|
+
strategy: 'passthrough',
|
|
146
|
+
ccrHash: null,
|
|
147
|
+
rowsDropped: 0,
|
|
148
|
+
originalCount: 0,
|
|
149
|
+
keptCount: 0,
|
|
150
|
+
};
|
|
151
|
+
// Gate: too short
|
|
152
|
+
if (input.length < this.cfg.minCharsToCompress)
|
|
153
|
+
return passthrough;
|
|
154
|
+
// Parse JSON
|
|
155
|
+
let parsed;
|
|
156
|
+
try {
|
|
157
|
+
parsed = JSON.parse(input);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
return passthrough;
|
|
161
|
+
}
|
|
162
|
+
// Must be a non-empty array
|
|
163
|
+
if (!Array.isArray(parsed) || parsed.length < this.cfg.minItemsToAnalyze) {
|
|
164
|
+
return passthrough;
|
|
165
|
+
}
|
|
166
|
+
const items = parsed;
|
|
167
|
+
// ── Lossless path ────────────────────────────────────────────────────────
|
|
168
|
+
const { homogeneous, coreFields } = isHomogeneousArray(items, this.cfg.compactionCoreFieldFraction);
|
|
169
|
+
if (homogeneous) {
|
|
170
|
+
const objectItems = items.filter((x) => x !== null && typeof x === 'object' && !Array.isArray(x));
|
|
171
|
+
const losslessOutput = losslessCsvCompress(objectItems, coreFields);
|
|
172
|
+
const savingsRatio = 1 - losslessOutput.length / input.length;
|
|
173
|
+
if (savingsRatio >= this.cfg.losslessMinSavingsRatio) {
|
|
174
|
+
return {
|
|
175
|
+
output: losslessOutput,
|
|
176
|
+
compressed: true,
|
|
177
|
+
strategy: 'lossless-csv',
|
|
178
|
+
ccrHash: null,
|
|
179
|
+
rowsDropped: 0,
|
|
180
|
+
originalCount: items.length,
|
|
181
|
+
keptCount: items.length,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// ── Lossy path ───────────────────────────────────────────────────────────
|
|
186
|
+
const n = items.length;
|
|
187
|
+
const maxKeep = this.cfg.maxItemsAfterCrush;
|
|
188
|
+
if (n <= maxKeep) {
|
|
189
|
+
// Already small enough — check if lossless was worth it
|
|
190
|
+
return passthrough;
|
|
191
|
+
}
|
|
192
|
+
const firstCount = Math.max(1, Math.floor(n * this.cfg.firstFraction));
|
|
193
|
+
const lastCount = Math.max(1, Math.floor(n * this.cfg.lastFraction));
|
|
194
|
+
const midBudget = Math.max(0, maxKeep - firstCount - lastCount);
|
|
195
|
+
const firstItems = items.slice(0, firstCount);
|
|
196
|
+
const lastItems = items.slice(n - lastCount);
|
|
197
|
+
const midItems = items.slice(firstCount, n - lastCount);
|
|
198
|
+
// Score mid-range items and keep the most important
|
|
199
|
+
const objectMidItems = midItems.filter((x) => x !== null && typeof x === 'object' && !Array.isArray(x));
|
|
200
|
+
let keptMid;
|
|
201
|
+
let droppedMid;
|
|
202
|
+
if (objectMidItems.length > 0 && midBudget > 0) {
|
|
203
|
+
const scores = scoreItems(objectMidItems);
|
|
204
|
+
const indexed = scores.map((s, i) => ({ s, i }));
|
|
205
|
+
indexed.sort((a, b) => b.s - a.s);
|
|
206
|
+
const keepIndices = new Set(indexed.slice(0, midBudget).map((x) => x.i));
|
|
207
|
+
keptMid = objectMidItems.filter((_, i) => keepIndices.has(i));
|
|
208
|
+
droppedMid = objectMidItems.filter((_, i) => !keepIndices.has(i));
|
|
209
|
+
// Dedup: remove items from keptMid that are identical to firstItems or lastItems
|
|
210
|
+
const existingSet = new Set([...firstItems, ...lastItems].map((x) => JSON.stringify(x)));
|
|
211
|
+
keptMid = keptMid.filter((x) => !existingSet.has(JSON.stringify(x)));
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
keptMid = midBudget > 0 ? midItems.slice(0, midBudget) : [];
|
|
215
|
+
droppedMid = midItems.slice(midBudget);
|
|
216
|
+
}
|
|
217
|
+
const keptItems = [...firstItems, ...keptMid, ...lastItems];
|
|
218
|
+
const droppedItems = droppedMid;
|
|
219
|
+
const rowsDropped = droppedItems.length;
|
|
220
|
+
// Store dropped rows in CCR
|
|
221
|
+
let ccrHash = null;
|
|
222
|
+
if (rowsDropped > 0 && ccrStore) {
|
|
223
|
+
const droppedJson = JSON.stringify(droppedItems);
|
|
224
|
+
ccrHash = shortHash(droppedJson);
|
|
225
|
+
ccrStore.set(ccrHash, droppedJson);
|
|
226
|
+
}
|
|
227
|
+
// Build output
|
|
228
|
+
const ccrMarker = ccrHash && rowsDropped > 0
|
|
229
|
+
? `\n{"_ccr_dropped":"<<ccr:${ccrHash} ${rowsDropped}_rows_offloaded>>"}`
|
|
230
|
+
: '';
|
|
231
|
+
const output = JSON.stringify(keptItems) + ccrMarker;
|
|
232
|
+
const savingsRatio = 1 - output.length / input.length;
|
|
233
|
+
// Only return lossy result if it actually saved something meaningful
|
|
234
|
+
if (savingsRatio < this.cfg.losslessMinSavingsRatio) {
|
|
235
|
+
// Clean up CCR entry — not worth it
|
|
236
|
+
if (ccrHash && ccrStore)
|
|
237
|
+
ccrStore.delete(ccrHash);
|
|
238
|
+
return passthrough;
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
output,
|
|
242
|
+
compressed: true,
|
|
243
|
+
strategy: 'lossy-sample',
|
|
244
|
+
ccrHash,
|
|
245
|
+
rowsDropped,
|
|
246
|
+
originalCount: items.length,
|
|
247
|
+
keptCount: keptItems.length,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
exports.SmartCrusher = SmartCrusher;
|
package/lib/context-budget.ts
CHANGED
|
File without changes
|
package/lib/context-firewall.js
CHANGED
|
File without changes
|
|
File without changes
|
package/lib/email-utils.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/eval/index.ts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/event-bus.d.ts
CHANGED
|
File without changes
|
package/lib/event-bus.mts
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/lib/graph/lib/utils.js
CHANGED
|
@@ -468,7 +468,7 @@ async function mgQueryAsync(cypher, params = {}, opts = {}) {
|
|
|
468
468
|
let _queryTimer;
|
|
469
469
|
const result = await Promise.race([
|
|
470
470
|
session.run(cleanCypher, params),
|
|
471
|
-
new Promise((_, rej) => { _queryTimer = setTimeout(() => rej(new Error('direct bolt query timeout (15s)')),
|
|
471
|
+
new Promise((_, rej) => { _queryTimer = setTimeout(() => rej(new Error('direct bolt query timeout (15s)')), parseInt(process.env.HELIOS_BOLT_QUERY_TIMEOUT_MS || '25000', 10)); })
|
|
472
472
|
]);
|
|
473
473
|
clearTimeout(_queryTimer);
|
|
474
474
|
const keys = result.records.length > 0 ? result.records[0].keys : [];
|
package/lib/graph-audit.d.ts
CHANGED
|
File without changes
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* lib/graph-availability.js
|
|
4
|
+
*
|
|
5
|
+
* Single source of truth for Memgraph availability state within a SINGLE
|
|
6
|
+
* Node.js process. Listeners are notified on state change only (not on
|
|
7
|
+
* redundant sets of the same value).
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ PROCESS SCOPE WARNING:
|
|
10
|
+
* This module uses Node.js module-level state. Each OS process has its own
|
|
11
|
+
* module registry and therefore its own independent _available + _listeners.
|
|
12
|
+
* It is ONLY valid within a single process:
|
|
13
|
+
*
|
|
14
|
+
* ✅ Pi extension process — memgraph-autostart sets state, cortex/hema read it
|
|
15
|
+
* ✅ Eval container — same process, same state
|
|
16
|
+
* ❌ helios-company-daemon.js — runs in its own process; state here is NEVER
|
|
17
|
+
* set by memgraph-autostart (which runs in Pi's process). Do NOT use
|
|
18
|
+
* isMemgraphAvailable() in daemon code to gate Memgraph writes — use the
|
|
19
|
+
* try/catch + setImmediate fire-and-forget pattern instead.
|
|
20
|
+
* ❌ broker/server.js — forked process; has its own availability state
|
|
21
|
+
*
|
|
22
|
+
* Usage (within Pi's extension process only):
|
|
23
|
+
* const { isMemgraphAvailable, setMemgraphAvailable, onAvailabilityChange } = require('./graph-availability');
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
let _available = false;
|
|
27
|
+
let _listeners = [];
|
|
28
|
+
|
|
29
|
+
function setMemgraphAvailable(available) {
|
|
30
|
+
const v = !!available;
|
|
31
|
+
if (v === _available) return;
|
|
32
|
+
_available = v;
|
|
33
|
+
const snap = _listeners.slice();
|
|
34
|
+
for (const fn of snap) {
|
|
35
|
+
try { fn(_available); } catch (e) {
|
|
36
|
+
process.stderr.write('[graph-availability] listener error: ' + String(e) + '\n');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isMemgraphAvailable() {
|
|
42
|
+
return _available;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function onAvailabilityChange(fn) {
|
|
46
|
+
_listeners.push(fn);
|
|
47
|
+
return function unsubscribe() {
|
|
48
|
+
_listeners = _listeners.filter((l) => l !== fn);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function _resetForTest() {
|
|
53
|
+
_available = false;
|
|
54
|
+
_listeners = [];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
setMemgraphAvailable,
|
|
59
|
+
isMemgraphAvailable,
|
|
60
|
+
onAvailabilityChange,
|
|
61
|
+
_resetForTest,
|
|
62
|
+
};
|