@cgh567/agent 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) 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/config/com.familiar.helios-daemon.plist +5 -0
  6. package/daemon/config/helios-daemon.service +4 -0
  7. package/daemon/context-enrichment.js +59 -21
  8. package/daemon/helios-api.js +149 -37
  9. package/daemon/helios-company-daemon.js +516 -124
  10. package/daemon/lib/harada/cascade-judge.js +12 -50
  11. package/daemon/lib/harada/mandala.js +20 -0
  12. package/daemon/lib/harada/pillar-dispatcher.js +1 -1
  13. package/daemon/lib/harada/project-factory.js +7 -2
  14. package/daemon/lib/hbo-bridge.js +31 -12
  15. package/daemon/lib/helios-hitl-host.js +15 -2
  16. package/daemon/lib/hitl-interaction-service.js +0 -0
  17. package/daemon/lib/memgraph-verify.js +38 -33
  18. package/daemon/lib/project-drift-detector.js +7 -17
  19. package/daemon/lib/project-semantic-updater.js +1 -14
  20. package/daemon/routes/channels.js +10 -5
  21. package/daemon/routes/harada-map.js +11 -48
  22. package/daemon/routes/hbo.js +89 -28
  23. package/daemon/routes/hitl.js +0 -0
  24. package/daemon/routes/project.js +4 -3
  25. package/daemon/routes/wizard.js +11 -4
  26. package/daemon/schema-migrations-hitl.js +0 -0
  27. package/extensions/001-tool-output-cap.ts +0 -0
  28. package/extensions/context-compaction.ts +45 -26
  29. package/extensions/cortex/activation-bridge.ts +5 -0
  30. package/extensions/cortex/learn.ts +26 -0
  31. package/extensions/email/backfill.ts +0 -0
  32. package/extensions/helios-governance/analysis/ambiguity.ts +0 -0
  33. package/extensions/helios-governance/analysis/compliance.ts +0 -0
  34. package/extensions/helios-governance/analysis/long-task-detector.ts +0 -0
  35. package/extensions/helios-governance/analysis/output-contract.ts +0 -0
  36. package/extensions/helios-governance/analysis/patterns.ts +0 -0
  37. package/extensions/helios-governance/analysis/preflight.ts +0 -0
  38. package/extensions/helios-governance/analysis/recurring-violations.ts +0 -0
  39. package/extensions/helios-governance/analysis/task-classification.ts +0 -0
  40. package/extensions/helios-governance/analysis/task-intent.ts +0 -0
  41. package/extensions/helios-governance/gates/high-impact.ts +1 -1
  42. package/extensions/helios-governance/handlers/_jiti-require.ts +15 -8
  43. package/extensions/helios-governance/handlers/proxy-test-detector.ts +0 -0
  44. package/extensions/hema-dispatch-v3/graph-memory.ts +10 -0
  45. package/extensions/hema-dispatch-v3/index.ts +59 -40
  46. package/extensions/lib/elo-engine.js +0 -0
  47. package/extensions/lib/elo-engine.test.js +0 -0
  48. package/extensions/memgraph-autostart.ts +13 -0
  49. package/extensions/neuroplastic-eval.ts +0 -0
  50. package/extensions/shadow-loop/index.ts +0 -0
  51. package/lib/brain-v2-budget.js +0 -0
  52. package/lib/brain-v2-circuit-breaker.js +0 -0
  53. package/lib/brain-v2.js +0 -0
  54. package/lib/broker/adaptive-throttle.js +0 -0
  55. package/lib/broker/batch-coalescer.js +0 -0
  56. package/lib/broker/bulkhead.js +0 -0
  57. package/lib/broker/channel-registry.js +0 -0
  58. package/lib/broker/circuit-breaker.js +0 -0
  59. package/lib/broker/evidence-cache.js +0 -0
  60. package/lib/broker/health-monitor.js +0 -0
  61. package/lib/broker/mage-queue.js +0 -0
  62. package/lib/broker/priority-queue.js +0 -0
  63. package/lib/broker/server.js.bak-error2-fix +0 -0
  64. package/lib/broker/session-registry.js +0 -0
  65. package/lib/broker/singleton-timers.js +0 -0
  66. package/lib/broker/types.d.ts +0 -0
  67. package/lib/broker/vegas-limit.js +0 -0
  68. package/lib/compression/dist/ccr-store.js +74 -0
  69. package/lib/compression/dist/content-router.js +115 -0
  70. package/lib/compression/dist/pipeline.js +113 -0
  71. package/lib/compression/dist/server.js +265 -0
  72. package/lib/compression/dist/smart-crusher.js +251 -0
  73. package/lib/context-budget.ts +0 -0
  74. package/lib/context-firewall.js +0 -0
  75. package/lib/crm/integration/triage-bridge.js +0 -0
  76. package/lib/email-utils.ts +0 -0
  77. package/lib/eval/__tests__/preflight-checker.test.ts +0 -0
  78. package/lib/eval/__tests__/task-instruction-parser.test.ts +0 -0
  79. package/lib/eval/__tests__/verifier-runner.test.ts +0 -0
  80. package/lib/eval/index.ts +0 -0
  81. package/lib/eval/preflight-checker.ts +0 -0
  82. package/lib/eval/task-domain-classifier.ts +0 -0
  83. package/lib/eval/task-instruction-parser.ts +0 -0
  84. package/lib/eval/verifier-runner.ts +0 -0
  85. package/lib/event-bus.d.ts +0 -0
  86. package/lib/governance-context-selector.ts +0 -0
  87. package/lib/graph/generate-extension-embeddings.js +0 -0
  88. package/lib/graph/generate-static-embeddings.js +0 -0
  89. package/lib/graph/lib/utils.js +1 -1
  90. package/lib/graph-audit.d.ts +0 -0
  91. package/lib/mesh-circuit-breaker.js +0 -0
  92. package/lib/mission-loop/lesson-extractor.ts +0 -0
  93. package/lib/mission-loop/mental-model-scorer.ts +0 -0
  94. package/lib/mission-loop/occ-detector.ts +0 -0
  95. package/lib/mission-loop/query-variants.ts +0 -0
  96. package/lib/mission-loop/verifier-check.ts +0 -0
  97. package/lib/skill-reference-builder.ts +0 -0
  98. package/lib/telemetry/token-breakdown.ts +0 -0
  99. package/lib/tool-compressor.ts +0 -0
  100. package/lib/triage-core/legal-routing.ts +0 -0
  101. package/lib/triage-core/mental-model/dunbar-classifier.ts +0 -0
  102. package/lib/triage-core/mental-model/enrich-all.ts +0 -0
  103. package/lib/triage-core/mental-model/identity-resolver.ts +0 -0
  104. package/lib/triage-core/mental-model/key-facts.ts +0 -0
  105. package/lib/triage-core/mental-model/model-assembler.ts +0 -0
  106. package/lib/triage-core/orchestrator.ts +0 -0
  107. package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -0
  108. package/package.json +10 -4
  109. package/skills/helios-business-operator/services/signals/upwork-signals.js +0 -0
  110. package/skills/talisman-ceo/SKILL.md +23 -25
  111. package/skills/talisman-comms/SKILL.md +5 -5
  112. package/skills/talisman-engineering/SKILL.md +5 -5
  113. package/skills/talisman-finance/SKILL.md +10 -8
  114. package/skills/talisman-marketing/SKILL.md +10 -10
  115. package/skills/talisman-sales/SKILL.md +12 -15
  116. package/skills/talisman-support/SKILL.md +5 -5
  117. package/agents/business/talisman-ceo.md +0 -183
  118. package/agents/business/talisman-comms.md +0 -257
  119. package/agents/business/talisman-cto.md +0 -153
  120. package/agents/business/talisman-finance.md +0 -246
  121. package/agents/business/talisman-marketing.md +0 -240
  122. package/agents/business/talisman-sales.md +0 -242
  123. package/agents/business/talisman-support.md +0 -236
  124. package/daemon/lib/approval-expiry.js +0 -162
  125. package/daemon/lib/blast-radius-analyzer.js +0 -75
  126. package/daemon/lib/domain-bootstrap-orchestrator.js +0 -267
  127. package/daemon/lib/forensic-log.js +0 -113
  128. package/daemon/lib/goal-research-pipeline.js +0 -644
  129. package/daemon/lib/harada/cascade-research-dispatcher.js +0 -261
  130. package/daemon/lib/headroom-middleware.js +0 -167
  131. package/daemon/lib/headroom-proxy-manager.js +0 -623
  132. package/daemon/lib/hed-engine.js +0 -307
  133. package/daemon/lib/mental-model-cache.js +0 -96
  134. package/daemon/lib/project-factory.js +0 -47
  135. package/daemon/lib/session-log-reader.js +0 -93
  136. package/daemon/routes/hed.js +0 -133
  137. package/lib/graph/learning/headroom-learn-bridge.js +0 -215
  138. package/skills/helios-bookkeeping/SKILL.md +0 -321
  139. package/skills/helios-briefer/SKILL.md +0 -44
  140. package/skills/helios-client-relations/SKILL.md +0 -322
  141. package/skills/helios-personal-triager/SKILL.md +0 -45
  142. package/skills/helios-recruitment/SKILL.md +0 -317
  143. package/skills/helios-relationship-nudger/SKILL.md +0 -77
  144. package/skills/helios-researcher/SKILL.md +0 -44
  145. package/skills/helios-scheduler/SKILL.md +0 -58
  146. package/skills/helios-tax-analyst/SKILL.md +0 -280
File without changes
File without changes
File without changes
package/lib/brain-v2.js CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
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;
@@ -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
+ }