@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.
Files changed (169) hide show
  1. package/bin/helios +0 -0
  2. package/bin/helios-rpc-node-wrapper.cjs +0 -0
  3. package/bin/helios-rpc-wrapper.sh +0 -0
  4. package/daemon/adapters/helios-rpc-adapter.js +47 -25
  5. package/daemon/adapters/tui_wakeup.js +8 -0
  6. package/daemon/config/com.familiar.helios-daemon.plist +5 -0
  7. package/daemon/config/helios-daemon.service +4 -0
  8. package/daemon/context-enrichment.js +59 -21
  9. package/daemon/daemon-manager.js +1 -1
  10. package/daemon/db/email-infrastructure-migrate.js +192 -0
  11. package/daemon/db/hbo-core-migrate.js +189 -0
  12. package/daemon/helios-api.js +723 -57
  13. package/daemon/helios-company-daemon.js +616 -134
  14. package/daemon/lib/harada/cascade-judge.js +12 -50
  15. package/daemon/lib/harada/mandala.js +20 -0
  16. package/daemon/lib/harada/pillar-dispatcher.js +1 -1
  17. package/daemon/lib/harada/project-factory.js +7 -2
  18. package/daemon/lib/hbo-bridge.js +32 -13
  19. package/daemon/lib/hed-engine.js +10 -292
  20. package/daemon/lib/helios-hitl-host.js +15 -2
  21. package/daemon/lib/hitl-interaction-service.js +0 -0
  22. package/daemon/lib/memgraph-verify.js +38 -33
  23. package/daemon/lib/project-drift-detector.js +7 -17
  24. package/daemon/lib/project-semantic-updater.js +1 -14
  25. package/daemon/lib/task-completion-processor.js +11 -0
  26. package/daemon/lib/wizard-engine.js +57 -6
  27. package/daemon/routes/channels.js +10 -5
  28. package/daemon/routes/harada-map.js +11 -48
  29. package/daemon/routes/hbo.js +342 -75
  30. package/daemon/routes/hitl.js +0 -0
  31. package/daemon/routes/project.js +194 -62
  32. package/daemon/routes/routines.js +14 -0
  33. package/daemon/routes/tasks.js +15 -1
  34. package/daemon/routes/wizard.js +11 -4
  35. package/daemon/schema-apply.js +174 -0
  36. package/daemon/schema-definitions.js +423 -0
  37. package/daemon/schema-migrations-hbo.js +10 -0
  38. package/daemon/schema-migrations-hed.js +18 -0
  39. package/daemon/schema-migrations-hitl.js +0 -0
  40. package/daemon/schema-migrations-proj.js +131 -0
  41. package/extensions/001-tool-output-cap.ts +0 -0
  42. package/extensions/context-compaction.ts +45 -26
  43. package/extensions/cortex/activation-bridge.ts +5 -0
  44. package/extensions/cortex/learn.ts +26 -0
  45. package/extensions/cortex/wal-replay.ts +91 -0
  46. package/extensions/email/backfill.ts +0 -0
  47. package/extensions/helios-governance/analysis/ambiguity.ts +0 -0
  48. package/extensions/helios-governance/analysis/compliance.ts +0 -0
  49. package/extensions/helios-governance/analysis/long-task-detector.ts +0 -0
  50. package/extensions/helios-governance/analysis/output-contract.ts +0 -0
  51. package/extensions/helios-governance/analysis/patterns.ts +0 -0
  52. package/extensions/helios-governance/analysis/preflight.ts +0 -0
  53. package/extensions/helios-governance/analysis/recurring-violations.ts +0 -0
  54. package/extensions/helios-governance/analysis/task-classification.ts +0 -0
  55. package/extensions/helios-governance/analysis/task-intent.ts +0 -0
  56. package/extensions/helios-governance/gates/high-impact.ts +1 -1
  57. package/extensions/helios-governance/handlers/_jiti-require.ts +15 -8
  58. package/extensions/helios-governance/handlers/proxy-test-detector.ts +0 -0
  59. package/extensions/hema-dispatch-v3/graph-memory.ts +10 -0
  60. package/extensions/hema-dispatch-v3/index.ts +72 -47
  61. package/extensions/lib/elo-engine.js +0 -0
  62. package/extensions/lib/elo-engine.test.js +0 -0
  63. package/extensions/memgraph-autostart.ts +13 -0
  64. package/extensions/neuroplastic-eval.ts +0 -0
  65. package/extensions/shadow-loop/index.ts +0 -0
  66. package/extensions/warm-tick/warm-tick-maintenance.ts +8 -0
  67. package/lib/__tests__/hbo-core-store.test.js +238 -0
  68. package/lib/brain-v2-budget.js +0 -0
  69. package/lib/brain-v2-circuit-breaker.js +0 -0
  70. package/lib/brain-v2.js +0 -0
  71. package/lib/broker/adaptive-throttle.js +0 -0
  72. package/lib/broker/batch-coalescer.js +0 -0
  73. package/lib/broker/bulkhead.js +0 -0
  74. package/lib/broker/channel-registry.js +0 -0
  75. package/lib/broker/circuit-breaker.js +0 -0
  76. package/lib/broker/evidence-cache.js +0 -0
  77. package/lib/broker/health-monitor.js +0 -0
  78. package/lib/broker/mage-queue.js +0 -0
  79. package/lib/broker/priority-queue.js +0 -0
  80. package/lib/broker/server.js.bak-error2-fix +0 -0
  81. package/lib/broker/session-registry.js +0 -0
  82. package/lib/broker/singleton-timers.js +0 -0
  83. package/lib/broker/types.d.ts +0 -0
  84. package/lib/broker/vegas-limit.js +0 -0
  85. package/lib/compression/dist/ccr-store.js +74 -0
  86. package/lib/compression/dist/content-router.js +115 -0
  87. package/lib/compression/dist/pipeline.js +113 -0
  88. package/lib/compression/dist/server.js +265 -0
  89. package/lib/compression/dist/smart-crusher.js +251 -0
  90. package/lib/context-budget.ts +0 -0
  91. package/lib/context-firewall.js +0 -0
  92. package/lib/crm/integration/triage-bridge.js +0 -0
  93. package/lib/email-utils.ts +0 -0
  94. package/lib/eval/__tests__/preflight-checker.test.ts +0 -0
  95. package/lib/eval/__tests__/task-instruction-parser.test.ts +0 -0
  96. package/lib/eval/__tests__/verifier-runner.test.ts +0 -0
  97. package/lib/eval/index.ts +0 -0
  98. package/lib/eval/preflight-checker.ts +0 -0
  99. package/lib/eval/task-domain-classifier.ts +0 -0
  100. package/lib/eval/task-instruction-parser.ts +0 -0
  101. package/lib/eval/verifier-runner.ts +0 -0
  102. package/lib/event-bus.d.ts +0 -0
  103. package/lib/event-bus.mts +1 -1
  104. package/lib/governance-context-selector.ts +0 -0
  105. package/lib/graph/generate-extension-embeddings.js +0 -0
  106. package/lib/graph/generate-static-embeddings.js +0 -0
  107. package/lib/graph/lib/utils.js +1 -1
  108. package/lib/graph-audit.d.ts +0 -0
  109. package/lib/graph-availability.js +62 -0
  110. package/lib/hbo-core-store.compiled.js +834 -0
  111. package/lib/hbo-core-store.js +124 -0
  112. package/lib/hbo-core-store.ts +908 -0
  113. package/lib/mesh-circuit-breaker.js +0 -0
  114. package/lib/mission-loop/lesson-extractor.ts +0 -0
  115. package/lib/mission-loop/mental-model-scorer.ts +0 -0
  116. package/lib/mission-loop/occ-detector.ts +0 -0
  117. package/lib/mission-loop/query-variants.ts +0 -0
  118. package/lib/mission-loop/verifier-check.ts +0 -0
  119. package/lib/skill-reference-builder.ts +0 -0
  120. package/lib/telemetry/token-breakdown.ts +0 -0
  121. package/lib/tool-compressor.ts +0 -0
  122. package/lib/triage-core/classifier.ts +3 -2
  123. package/lib/triage-core/graph/schema.cypher +10 -0
  124. package/lib/triage-core/legal-routing.ts +0 -0
  125. package/lib/triage-core/mental-model/dunbar-classifier.ts +0 -0
  126. package/lib/triage-core/mental-model/enrich-all.ts +0 -0
  127. package/lib/triage-core/mental-model/identity-resolver.ts +0 -0
  128. package/lib/triage-core/mental-model/key-facts.ts +1 -2
  129. package/lib/triage-core/mental-model/model-assembler.ts +0 -0
  130. package/lib/triage-core/orchestrator.ts +4 -11
  131. package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -0
  132. package/package.json +18 -8
  133. package/skills/helios-business-operator/services/signals/upwork-signals.js +0 -0
  134. package/skills/talisman-ceo/SKILL.md +23 -25
  135. package/skills/talisman-comms/SKILL.md +5 -5
  136. package/skills/talisman-engineering/SKILL.md +5 -5
  137. package/skills/talisman-finance/SKILL.md +10 -8
  138. package/skills/talisman-marketing/SKILL.md +10 -10
  139. package/skills/talisman-sales/SKILL.md +12 -15
  140. package/skills/talisman-support/SKILL.md +5 -5
  141. package/agents/business/talisman-ceo.md +0 -183
  142. package/agents/business/talisman-comms.md +0 -257
  143. package/agents/business/talisman-cto.md +0 -153
  144. package/agents/business/talisman-finance.md +0 -246
  145. package/agents/business/talisman-marketing.md +0 -240
  146. package/agents/business/talisman-sales.md +0 -242
  147. package/agents/business/talisman-support.md +0 -236
  148. package/daemon/lib/approval-expiry.js +0 -162
  149. package/daemon/lib/blast-radius-analyzer.js +0 -75
  150. package/daemon/lib/domain-bootstrap-orchestrator.js +0 -267
  151. package/daemon/lib/forensic-log.js +0 -113
  152. package/daemon/lib/goal-research-pipeline.js +0 -644
  153. package/daemon/lib/harada/cascade-research-dispatcher.js +0 -261
  154. package/daemon/lib/headroom-middleware.js +0 -167
  155. package/daemon/lib/headroom-proxy-manager.js +0 -623
  156. package/daemon/lib/mental-model-cache.js +0 -96
  157. package/daemon/lib/project-factory.js +0 -47
  158. package/daemon/lib/session-log-reader.js +0 -93
  159. package/daemon/routes/hed.js +0 -133
  160. package/lib/graph/learning/headroom-learn-bridge.js +0 -215
  161. package/skills/helios-bookkeeping/SKILL.md +0 -321
  162. package/skills/helios-briefer/SKILL.md +0 -44
  163. package/skills/helios-client-relations/SKILL.md +0 -322
  164. package/skills/helios-personal-triager/SKILL.md +0 -45
  165. package/skills/helios-recruitment/SKILL.md +0 -317
  166. package/skills/helios-relationship-nudger/SKILL.md +0 -77
  167. package/skills/helios-researcher/SKILL.md +0 -44
  168. package/skills/helios-scheduler/SKILL.md +0 -58
  169. 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;
File without changes
File without changes
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
File without changes
package/lib/event-bus.mts CHANGED
@@ -46,7 +46,7 @@ interface EventMap {
46
46
  senderHandle: string;
47
47
  subject: string;
48
48
  snippet: string;
49
- body: string;
49
+ // body intentionally excluded — PII (SEC-6)
50
50
  threadId: string;
51
51
  receivedAt: string;
52
52
  suggestedAction: string;
File without changes
File without changes
File without changes
@@ -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)')), 15000); })
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 : [];
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
+ };