@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
@@ -345,35 +345,55 @@ export default function contextCompaction(pi: ExtensionAPI): void {
345
345
  metrics.compactionTriggered++;
346
346
  metrics.lastCompactionAt = Date.now();
347
347
 
348
- // ── Headroom compression (L1 + L2 replacement) ────────────────────────
349
- // Uses the local Headroom proxy (SmartCrusher + IntelligentContext + CCR)
350
- // instead of the hand-rolled L1 tool-result clearing and L2 extractive
351
- // summarization. Headroom's content-routing selects the right compressor
352
- // per content type (JSON arrays → SmartCrusher, prose → Kompress-base).
353
- // CCR stores originals locally so the agent can retrieve them on demand.
348
+ // ── Helios Compression (L1 + L2 replacement) ────────────────────────
349
+ // Calls the Helios Compression Server (lib/compression/server.ts) via
350
+ // HTTP. The server runs as a sidecar process managed by HeadroomProxyManager.
351
+ // Its URL is injected into the Pi subprocess env as HEADROOM_PROXY_URL.
354
352
  //
355
- // This fires on the same threshold as the old L1/L2 — no behaviour change
356
- // for users, but compression quality is dramatically better:
357
- // - SmartCrusher preserves statistical distribution (30% start, 55%
358
- // importance-scored, 15% end) and always keeps errors/anomalies.
359
- // - IntelligentContext scores on 6 dimensions instead of oldest-first.
360
- // - CacheAligner stabilizes Bedrock KV-cache prefix on every call.
353
+ // No npm package required uses Node's built-in http module.
354
+ // Works identically on Windows and macOS (the server is pure TypeScript).
355
+ //
356
+ // SmartCrusher preserves statistical distribution:
357
+ // Lossless: CSV format for homogeneous arrays (51–84% savings)
358
+ // Lossy: 30% start + 55% importance-scored + 15% end kept;
359
+ // dropped rows stored in CCR and retrievable on demand.
361
360
  let headroomApplied = false;
362
361
  let headroomTokensSaved = 0;
363
362
 
364
363
  try {
365
- // Dynamic import headroom-ai is a regular npm dep, not a Pi module.
366
- // eslint-disable-next-line @typescript-eslint/no-require-imports
367
- const headroomAi = require('headroom-ai');
368
- // eslint-disable-next-line @typescript-eslint/no-require-imports
369
- const { HeadroomProxyManager } = require('../daemon/lib/headroom-proxy-manager');
370
- const baseUrl = HeadroomProxyManager.getInstance().getBaseUrl();
371
-
372
- if (baseUrl && headroomAi?.compress) {
373
- const result = await headroomAi.compress(messages, {
374
- model: modelId || 'claude',
375
- baseUrl,
376
- tokenBudget: compactionThreshold,
364
+ const baseUrl = process.env.HEADROOM_PROXY_URL || process.env.ANTHROPIC_BASE_URL;
365
+ if (baseUrl && baseUrl.includes('127.0.0.1')) {
366
+ // POST /headroom/compress with the current messages
367
+ const payload = JSON.stringify({ messages });
368
+ const result: any = await new Promise((resolve, reject) => {
369
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
370
+ const http = require('http');
371
+ const url = new URL('/headroom/compress', baseUrl);
372
+ const req = http.request(
373
+ {
374
+ hostname: url.hostname,
375
+ port: parseInt(url.port || '8787', 10),
376
+ path: '/headroom/compress',
377
+ method: 'POST',
378
+ headers: {
379
+ 'Content-Type': 'application/json',
380
+ 'Content-Length': Buffer.byteLength(payload),
381
+ },
382
+ },
383
+ (res: any) => {
384
+ let body = '';
385
+ res.on('data', (c: Buffer) => { body += c; });
386
+ res.on('end', () => {
387
+ try { resolve(JSON.parse(body)); }
388
+ catch { reject(new Error('Invalid JSON from compression server')); }
389
+ });
390
+ res.on('error', reject);
391
+ }
392
+ );
393
+ req.setTimeout(5000, () => { req.destroy(); reject(new Error('Compression server timeout')); });
394
+ req.on('error', reject);
395
+ req.write(payload);
396
+ req.end();
377
397
  });
378
398
 
379
399
  if (result?.messages?.length) {
@@ -403,8 +423,7 @@ export default function contextCompaction(pi: ExtensionAPI): void {
403
423
  }
404
424
  }
405
425
  } catch (hrErr) {
406
- // Headroom compress failed — this should not happen if the proxy is running.
407
- // Log prominently and fall through to legacy L1 below.
426
+ // Headroom compress failed — log and fall through to legacy L1.
408
427
  process.stderr.write(
409
428
  `[context-compaction] ⚠️ Headroom compress error: ${String(hrErr)}\n` +
410
429
  `[context-compaction] Falling back to legacy L1 tool result clearing.\n`
@@ -230,6 +230,11 @@ export async function spreadActivation(
230
230
  initialActivation: EnrichedActivation,
231
231
  graphClient?: { safeRead: (q: string, p?: any) => Promise<any[]> } | null,
232
232
  ): Promise<EnrichedActivation> {
233
+ const { isMemgraphAvailable } = require('../../lib/graph-availability');
234
+ if (!isMemgraphAvailable()) {
235
+ process.stderr.write('[cortex:activation-bridge] Memgraph unavailable — skipping activation spread\n');
236
+ return {};
237
+ }
233
238
  if (!initialActivation.concepts.length) return initialActivation;
234
239
 
235
240
  const mg = graphClient !== undefined ? graphClient : getDefaultMg();
@@ -154,6 +154,32 @@ try {
154
154
  }
155
155
  } catch (err) { /* fail-open: WAL module init */ if (process.env.HELIOS_DEBUG) console.error(`[cortex-learn] WAL init error: ${String(err)}`); }
156
156
 
157
+ // Register availability listener so cortex WAL replays automatically
158
+ // when Memgraph comes back up after a period of unavailability (P4-2).
159
+ // _walReplayInProgress guard prevents concurrent replays racing on the journal
160
+ // file when Memgraph flaps rapidly (F13).
161
+ let _walReplayInProgress = false;
162
+ try {
163
+ const { onAvailabilityChange } = require('../../lib/graph-availability');
164
+ onAvailabilityChange((available: boolean) => {
165
+ if (!available || _walReplayInProgress) return;
166
+ _walReplayInProgress = true;
167
+ // Fire-and-forget: WAL replay runs in background, never blocks dispatch
168
+ import('./wal-replay').then(({ replayCortexWal }) => {
169
+ replayCortexWal()
170
+ .catch((e: unknown) =>
171
+ process.stderr.write(`[cortex-learn] WAL replay failed: ${String(e)}\n`)
172
+ )
173
+ .finally(() => { _walReplayInProgress = false; });
174
+ }).catch(() => {
175
+ // wal-replay not available (e.g. pre-build) — silently skip
176
+ _walReplayInProgress = false;
177
+ });
178
+ });
179
+ } catch (_) {
180
+ // graph-availability not available — WAL replay disabled
181
+ }
182
+
157
183
  function _persistCoherenceCounts(): void {
158
184
  try {
159
185
  const obj: Record<string, number> = {};
@@ -0,0 +1,91 @@
1
+ /**
2
+ * extensions/cortex/wal-replay.ts
3
+ *
4
+ * Auto-replay cortex WAL entries when Memgraph becomes available.
5
+ * Called by learn.ts on the memgraph:available event (P4-2).
6
+ *
7
+ * Reads cortex-write-journal.jsonl, replays each entry via rawWrite,
8
+ * removes successfully replayed entries atomically (temp file + rename).
9
+ *
10
+ * Uses extensions/lib/helios-root (same module as learn.ts) to ensure
11
+ * the journal path resolves identically in both files.
12
+ */
13
+ import * as fs from 'fs';
14
+
15
+ // Re-use the same path derivation as learn.ts.
16
+ // MUST use '../lib/helios-root' (extensions/lib/helios-root), NOT
17
+ // '../../lib/helios-root' (lib/helios-root) — different fallback strategies.
18
+ const { heliosPath } = require('../lib/helios-root');
19
+ const WRITE_JOURNAL_PATH: string = heliosPath('sessions', 'cortex-write-journal.jsonl');
20
+
21
+ interface WalEntry {
22
+ cypher: string;
23
+ params: Record<string, unknown>;
24
+ timestamp?: number;
25
+ }
26
+
27
+ function readJournalEntries(): WalEntry[] {
28
+ if (!fs.existsSync(WRITE_JOURNAL_PATH)) return [];
29
+ try {
30
+ const lines = fs.readFileSync(WRITE_JOURNAL_PATH, 'utf8')
31
+ .split('\n')
32
+ .filter(Boolean);
33
+ return lines.map(line => {
34
+ try { return JSON.parse(line) as WalEntry; } catch { return null; }
35
+ }).filter((e): e is WalEntry => e !== null && typeof e.cypher === 'string');
36
+ } catch (e) {
37
+ process.stderr.write(`[cortex-wal-replay] failed to read journal: ${String(e)}\n`);
38
+ return [];
39
+ }
40
+ }
41
+
42
+ function writeJournalEntries(entries: WalEntry[]): void {
43
+ const tmp = WRITE_JOURNAL_PATH + '.tmp';
44
+ try {
45
+ fs.writeFileSync(tmp, entries.map(e => JSON.stringify(e)).join('\n') + (entries.length ? '\n' : ''), 'utf8');
46
+ fs.renameSync(tmp, WRITE_JOURNAL_PATH);
47
+ } catch (e) {
48
+ process.stderr.write(`[cortex-wal-replay] failed to write journal: ${String(e)}\n`);
49
+ try { fs.unlinkSync(tmp); } catch {}
50
+ }
51
+ }
52
+
53
+ export async function replayCortexWal(): Promise<void> {
54
+ const entries = readJournalEntries();
55
+ if (entries.length === 0) return;
56
+
57
+ let rawWrite: ((cypher: string, params: Record<string, unknown>) => Promise<unknown>) | null = null;
58
+ try {
59
+ const mg = require('../../lib/safe-memgraph');
60
+ rawWrite = mg.rawWrite ?? mg.safeWrite ?? null;
61
+ } catch (e) {
62
+ process.stderr.write(`[cortex-wal-replay] failed to load safe-memgraph: ${String(e)}\n`);
63
+ return;
64
+ }
65
+
66
+ if (!rawWrite) {
67
+ process.stderr.write('[cortex-wal-replay] rawWrite not available — skipping replay\n');
68
+ return;
69
+ }
70
+
71
+ const remaining: WalEntry[] = [];
72
+ let replayed = 0;
73
+ let failed = 0;
74
+
75
+ for (const entry of entries) {
76
+ try {
77
+ await rawWrite(entry.cypher, entry.params ?? {});
78
+ replayed++;
79
+ } catch (e) {
80
+ failed++;
81
+ remaining.push(entry);
82
+ process.stderr.write(`[cortex-wal-replay] entry replay failed (kept in journal): ${String(e)}\n`);
83
+ }
84
+ }
85
+
86
+ writeJournalEntries(remaining);
87
+
88
+ process.stderr.write(
89
+ `[cortex-wal-replay] Replayed ${replayed} / ${entries.length} entries. ${failed} failed (left in journal).\n`
90
+ );
91
+ }
File without changes
File without changes
@@ -11,7 +11,7 @@ const HIGH_IMPACT_SHARED_BRANCHES = /\b(main|master|dev|development|staging|stag
11
11
  const HIGH_IMPACT_GIT_OPS = /\b(cherry.?pick|git\s+push|force.?push|git\s+merge|merge\s+(?:to|into)\s+|push\s+to\s+(?:origin|remote|upstream)|git\s+rebase|git\s+reset\s+--hard)\b/i;
12
12
  const HIGH_IMPACT_AUTH_CRYPTO = /\b(?:auth|crypto|session|oauth|jwt|encrypt|decrypt|secret|cipher|password)\.(ts|js|tsx|jsx)\b|(?:session|token|secret|password|credential|encrypt|decrypt|hash|cipher|jwt|oauth)\s+(?:file|module|key|management)/i;
13
13
  const HIGH_IMPACT_INFRA = /\b(?:(?:run|apply|execute|deploy|push|revert|rollback|sync)\s+migration|schema\s+change|drop\s+table|alter\s+table|environment\s+variable|aws\s+ssm|amplify\s+deploy|\.env\b)\b/i;
14
- const HIGH_IMPACT_RUNTIME_UPGRADE = /\b(npm\s+install|npm\s+i|npm\s+update|npm\s+up)\b.*(pi-coding-agent|@mariozechner\/pi|@helios-agent\/pi)/i;
14
+ const HIGH_IMPACT_RUNTIME_UPGRADE = /\b(npm\s+install|npm\s+i|npm\s+update|npm\s+up|pnpm\s+add|pnpm\s+install)\b.*(pi-coding-agent|@mariozechner\/pi|@helios-agent\/pi|@cgh567\/cli|@cgh567\/agent|@cgh567\/pi)/i;
15
15
  export const SHARED_INFRA_PATHS = /(?:~\/|\/Users\/\w+\/)?\.\.?pi\/agent\/(extensions|skills|agents)\//i;
16
16
 
17
17
  export function isHighImpactOperation(task: string): boolean {
@@ -28,15 +28,22 @@ import { homedir } from 'os';
28
28
 
29
29
  // Ordered list of jiti lib/jiti.cjs locations (lib/ not dist/ - dist requires module.createRequire which is unavailable in CJS context)
30
30
  const JITI_PATHS = [
31
+ // ── npm package layout (production: @cgh567/agent installed in node_modules) ──
32
+ // When helios-agent runs from node_modules/@cgh567/agent, jiti is provided by
33
+ // @cgh567/cli which is in the same scope. Walk up to workspace root node_modules.
34
+ // Resolve relative to __dirname (this file's location in extensions/helios-governance/handlers/)
35
+ // node_modules/@cgh567/agent/extensions/helios-governance/handlers/ -> up 6 levels -> workspace root
36
+ join(__dirname, '..', '..', '..', '..', '..', '..', 'node_modules', '@mariozechner', 'jiti', 'lib', 'jiti.cjs'),
37
+ // @cgh567/cli bundles jiti under its own node_modules
38
+ join(__dirname, '..', '..', '..', '..', '..', '..', 'node_modules', '@cgh567', 'cli', 'node_modules', '@mariozechner', 'jiti', 'lib', 'jiti.cjs'),
39
+ // ── local clone layout (dev: ~/Desktop/Helios/helios-agent-main) ──
40
+ join(homedir(), 'Desktop', 'Helios', 'helios-agent-main', 'node_modules', '@mariozechner', 'jiti', 'lib', 'jiti.cjs'),
41
+ join(homedir(), 'helios-agent-main', 'node_modules', '@mariozechner', 'jiti', 'lib', 'jiti.cjs'),
31
42
  join(homedir(), 'helios-agent', 'node_modules', '@mariozechner', 'jiti', 'lib', 'jiti.cjs'),
32
- join(homedir(), 'helios-agent', 'node_modules', '@helios-agent', 'pi-coding-agent', 'node_modules', 'jiti', 'lib', 'jiti.cjs'),
33
- join(homedir(), 'helios-agent', 'node_modules', '@earendil-works', 'pi-coding-agent', 'node_modules', 'jiti', 'lib', 'jiti.cjs'),
34
- join(process.env.NVM_DIR || join(homedir(), '.nvm'),
35
- 'versions', 'node', process.version, 'lib', 'node_modules',
36
- '@helios-agent', 'pi-coding-agent', 'node_modules', 'jiti', 'lib', 'jiti.cjs'),
37
- join(process.env.NVM_DIR || join(homedir(), '.nvm'),
38
- 'versions', 'node', process.version, 'lib', 'node_modules',
39
- '@earendil-works', 'pi-coding-agent', 'node_modules', 'jiti', 'lib', 'jiti.cjs'),
43
+ // ── HELIOS_ROOT env var override ──
44
+ ...(process.env.HELIOS_ROOT
45
+ ? [join(process.env.HELIOS_ROOT, 'node_modules', '@mariozechner', 'jiti', 'lib', 'jiti.cjs')]
46
+ : []),
40
47
  ];
41
48
 
42
49
  /** Module-level jiti instance cache: callerFile → jiti fn */
@@ -23,6 +23,11 @@ export async function queryGraphMemories(
23
23
  taskText: string,
24
24
  limit: number = 10
25
25
  ): Promise<GraphMemoryResult[]> {
26
+ const { isMemgraphAvailable } = require('../../lib/graph-availability');
27
+ if (!isMemgraphAvailable()) {
28
+ process.stderr.write('[hema:graph-memory] Memgraph unavailable — skipping graph memory recall\n');
29
+ return [];
30
+ }
26
31
  try {
27
32
  const mg = require('../../lib/safe-memgraph.js');
28
33
 
@@ -102,6 +107,11 @@ export async function queryCausalLessons(
102
107
  taskText: string,
103
108
  limit: number = 5
104
109
  ): Promise<GraphMemoryResult[]> {
110
+ const { isMemgraphAvailable } = require('../../lib/graph-availability');
111
+ if (!isMemgraphAvailable()) {
112
+ process.stderr.write('[hema:graph-memory] Memgraph unavailable — skipping causal lesson recall\n');
113
+ return [];
114
+ }
105
115
  try {
106
116
  const mg = require('../../lib/safe-memgraph.js');
107
117
 
@@ -682,15 +682,20 @@ export default function hemaDispatchV3(pi: any): void {
682
682
  } catch (_) { process.stderr.write(`[hema:catch] fail-open: graceful degradation: ${(_ as Error)?.message || _}\n`); } // HeliosRuntime may not be initialized — fail-open
683
683
 
684
684
  // ═══ TASK-13: Budget Pre-Admission Gate ═══
685
- // Reads ~/helios-agent/brainv2/budget-status.json (written by GraphSyncService).
685
+ // MT-02: Path resolves from HELIOS_ROOT env var (required). If unset, daemon logs a startup error.
686
+ // budget-status.json is written by GraphSyncService at $HELIOS_ROOT/brainv2/budget-status.json.
686
687
  // Fail-open: if file missing or unreadable, proceed normally.
687
688
  // blocked=true → hard block, return early with ⛔ message
688
689
  // warningActive → inject budget warning into agent context (soft signal)
689
690
  let _budgetStatus: { blocked?: boolean; warningActive?: boolean; policies?: any[] } = {};
690
691
  let _budgetWarning: string | null = null;
691
692
  try {
692
- const _budgetStatusPath = join(homedir(), 'helios-agent/brainv2/budget-status.json');
693
- if (fs.existsSync(_budgetStatusPath)) {
693
+ const _heliosRoot = process.env['HELIOS_ROOT'];
694
+ if (!_heliosRoot) {
695
+ process.stderr.write('[hema] HELIOS_ROOT not set — budget-status.json cannot be read. Set HELIOS_ROOT to the helios-agent repo root.\n');
696
+ }
697
+ const _budgetStatusPath = _heliosRoot ? join(_heliosRoot, 'brainv2/budget-status.json') : null;
698
+ if (_budgetStatusPath && fs.existsSync(_budgetStatusPath)) {
694
699
  _budgetStatus = JSON.parse(fs.readFileSync(_budgetStatusPath, 'utf8'));
695
700
  }
696
701
  } catch (_budgetErr) {
@@ -1507,48 +1512,67 @@ export default function hemaDispatchV3(pi: any): void {
1507
1512
  logHemaEvent('hema_no_context', { nativePacksUsed, legacyUsed: admitted.admitted?.length > 0 });
1508
1513
  }
1509
1514
 
1510
- // ── Headroom: compress HEMA recall payload before injection ──────────
1511
- // The recall context injected into every subagent call is dominated by
1512
- // JSON graph payloads (leads, signals, tasks, goals, code nodes).
1513
- // SmartCrusher compresses these at 70–90%, preserving errors/anomalies
1514
- // and statistical distribution (30% start / 55% importance / 15% end).
1515
- // This reduces injection cost for all roles and all companies.
1516
- if (enrichedTask.length > 2000) { // only compress if there's meaningful context
1517
- try {
1518
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1519
- const headroomAi = require('headroom-ai');
1520
- // eslint-disable-next-line @typescript-eslint/no-require-imports
1521
- const { HeadroomProxyManager } = require('../../daemon/lib/headroom-proxy-manager');
1522
- const _hrUrl = HeadroomProxyManager.getInstance().getBaseUrl();
1523
- if (_hrUrl && headroomAi?.compress) {
1524
- const _hrResult = await headroomAi.compress(
1525
- [{ role: 'user', content: [{ type: 'text', text: enrichedTask }] }],
1526
- {
1527
- model: 'claude',
1528
- baseUrl: _hrUrl,
1529
- // Use role injection budget as the token budget
1530
- tokenBudget: agentType === 'planner' ? 8000
1531
- : agentType === 'orchestrator' ? 10000
1532
- : agentType === 'worker' ? 6000
1533
- : agentType === 'scout' ? 3000
1534
- : 6000,
1535
- }
1536
- );
1537
- if (_hrResult?.messages?.[0]?.content) {
1538
- const _compressed = typeof _hrResult.messages[0].content === 'string'
1539
- ? _hrResult.messages[0].content
1540
- : _hrResult.messages[0].content?.[0]?.text ?? enrichedTask;
1541
- if (_compressed.length < enrichedTask.length) {
1542
- const _saved = enrichedTask.length - _compressed.length;
1543
- process.stderr.write(`[hema-dispatch-v3] Headroom compressed recall context: -${_saved} chars (${agentType})\n`);
1544
- enrichedTask = _compressed;
1545
- logHemaEvent('hema_headroom_compressed', { saved: _saved, agentType });
1546
- }
1515
+ // ── Helios Compression: compress HEMA recall payload before injection ──
1516
+ // The recall context contains JSON graph payloads (leads, signals, tasks,
1517
+ // goals, code nodes). Send the assembled text as a tool_result block to
1518
+ // the compression server it will find and compress embedded JSON arrays.
1519
+ //
1520
+ // Uses direct HTTP to HEADROOM_PROXY_URL (same pattern as context-compaction.ts)
1521
+ // rather than the headroom-ai npm package so this works in Pi subprocess context.
1522
+ // Applies to all companies: the role injection budgets enforce per-agent limits.
1523
+ if (enrichedTask.length > 2000) {
1524
+ const _hrUrl = process.env.HEADROOM_PROXY_URL;
1525
+ if (_hrUrl) {
1526
+ try {
1527
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
1528
+ const http = require('http');
1529
+ const _payload = JSON.stringify({
1530
+ messages: [{
1531
+ role: 'user',
1532
+ content: [{
1533
+ type: 'tool_result',
1534
+ tool_use_id: 'hema_recall',
1535
+ content: enrichedTask,
1536
+ }],
1537
+ }],
1538
+ });
1539
+ const _result: any = await new Promise((resolve, reject) => {
1540
+ const _url = new URL('/headroom/compress', _hrUrl);
1541
+ const _req = http.request(
1542
+ {
1543
+ hostname: _url.hostname,
1544
+ port: parseInt(_url.port || '8787', 10),
1545
+ path: '/headroom/compress',
1546
+ method: 'POST',
1547
+ headers: {
1548
+ 'Content-Type': 'application/json',
1549
+ 'Content-Length': Buffer.byteLength(_payload),
1550
+ },
1551
+ },
1552
+ (res: any) => {
1553
+ let body = '';
1554
+ res.on('data', (c: Buffer) => { body += c; });
1555
+ res.on('end', () => { try { resolve(JSON.parse(body)); } catch { reject(new Error('bad json')); } });
1556
+ res.on('error', reject);
1557
+ }
1558
+ );
1559
+ _req.setTimeout(3000, () => { _req.destroy(); reject(new Error('timeout')); });
1560
+ _req.on('error', reject);
1561
+ _req.write(_payload);
1562
+ _req.end();
1563
+ });
1564
+
1565
+ const _compressed = _result?.messages?.[0]?.content?.[0]?.content ?? enrichedTask;
1566
+ if (typeof _compressed === 'string' && _compressed.length < enrichedTask.length) {
1567
+ const _saved = enrichedTask.length - _compressed.length;
1568
+ process.stderr.write(`[hema-dispatch-v3] Headroom compressed recall context: -${_saved} chars (${agentType})\n`);
1569
+ enrichedTask = _compressed;
1570
+ logHemaEvent('hema_headroom_compressed', { saved: _saved, agentType });
1547
1571
  }
1572
+ } catch (_hrErr: any) {
1573
+ // Non-fatal: log and continue with uncompressed enrichedTask
1574
+ process.stderr.write(`[hema-dispatch-v3] Headroom recall compress skipped: ${_hrErr?.message}\n`);
1548
1575
  }
1549
- } catch (_hrErr: any) {
1550
- // Non-fatal: log and continue with uncompressed enrichedTask
1551
- process.stderr.write(`[hema-dispatch-v3] Headroom recall compress failed (non-fatal): ${_hrErr?.message}\n`);
1552
1576
  }
1553
1577
  }
1554
1578
 
@@ -2657,9 +2681,10 @@ export default function hemaDispatchV3(pi: any): void {
2657
2681
  } catch (_) { process.stderr.write(`[hema:catch] fail-open: ${(_ as Error)?.message || _}\n`); }
2658
2682
 
2659
2683
  // Load hot memory — ensure file exists
2660
- const hotMemoryPath = join(homedir(), 'helios-agent', 'sessions', 'hot-memory.json');
2684
+ const _heliosRootForMem = process.env['HELIOS_ROOT'] ?? join(homedir(), 'helios-agent');
2685
+ const hotMemoryPath = join(_heliosRootForMem, 'sessions', 'hot-memory.json');
2661
2686
  if (!fs.existsSync(hotMemoryPath)) {
2662
- const sessDir = join(homedir(), 'helios-agent', 'sessions');
2687
+ const sessDir = join(_heliosRootForMem, 'sessions');
2663
2688
  if (!fs.existsSync(sessDir)) fs.mkdirSync(sessDir, { recursive: true });
2664
2689
  fs.writeFileSync(hotMemoryPath, JSON.stringify({ sessions: [] }, null, 2), 'utf8');
2665
2690
  }
@@ -2687,7 +2712,7 @@ export default function hemaDispatchV3(pi: any): void {
2687
2712
  const pendingIds = [..._pendingDispatchIds.keys()];
2688
2713
  try {
2689
2714
  const _walSessionId = di('sessionId') || process.pid;
2690
- const walPath = join(homedir(), `helios-agent/sessions/.pending-dispatches-${_walSessionId}.json`);
2715
+ const walPath = join(process.env['HELIOS_ROOT'] ?? join(homedir(), 'helios-agent'), `sessions/.pending-dispatches-${_walSessionId}.json`);
2691
2716
  fs.writeFileSync(walPath, JSON.stringify({ ids: pendingIds, timestamp: Date.now(), sessionId: di('sessionId') || 'unknown' }));
2692
2717
  process.stderr.write(`[hema] session_shutdown: wrote ${pendingIds.length} pending dispatch ID(s) to WAL\n`);
2693
2718
  } catch (e: any) {
@@ -3125,7 +3150,7 @@ export default function hemaDispatchV3(pi: any): void {
3125
3150
  return { systemPrompt: appendDynamic(event.systemPrompt, '\n' + evalContext) };
3126
3151
  }
3127
3152
 
3128
- const hotMemoryPath = join(homedir(), 'helios-agent', 'sessions', 'hot-memory.json');
3153
+ const hotMemoryPath = join(process.env['HELIOS_ROOT'] ?? join(homedir(), 'helios-agent'), 'sessions', 'hot-memory.json');
3129
3154
 
3130
3155
  let hotMemory: any = null;
3131
3156
  if (fs.existsSync(hotMemoryPath)) {
File without changes
File without changes
@@ -23,6 +23,17 @@ import { createRequire } from 'module';
23
23
  const require = createRequire(import.meta.url);
24
24
  const QUIET = process.env.PI_QUIET === '1' || process.env.PI_LOG_LEVEL === 'error';
25
25
 
26
+ // Graph availability singleton — updated whenever Memgraph comes up or goes down
27
+ let _graphAvailability: { setMemgraphAvailable: (v: boolean) => void } | null = null;
28
+ function getGraphAvailability() {
29
+ if (!_graphAvailability) {
30
+ try {
31
+ _graphAvailability = require('../lib/graph-availability');
32
+ } catch (_) {}
33
+ }
34
+ return _graphAvailability;
35
+ }
36
+
26
37
  // ── Event emission helpers ───────────────────────────────────────────────────
27
38
 
28
39
  /** Load MAGE query modules after Bolt becomes reachable.
@@ -109,6 +120,7 @@ function emitMemgraphReady(log: (msg: string) => void): void {
109
120
  bus.emit('memgraph_ready', payload);
110
121
  } catch (e) { process.stderr.write(`[extensions] non-fatal if bus unavailable: ${String(e)}\n`); }
111
122
  log(`[memgraph-autostart] Memgraph ready — bolt reachable at ${BOLT_HOST}:${BOLT_PORT}`);
123
+ getGraphAvailability()?.setMemgraphAvailable(true);
112
124
  }
113
125
 
114
126
  /**
@@ -185,6 +197,7 @@ function emitMemgraphDegraded(log: (msg: string) => void, reason: string): void
185
197
  try {
186
198
  bus.emit('memgraph_degraded', payload);
187
199
  } catch (e) { process.stderr.write(`[extensions] non-fatal: ${String(e)}\n`); }
200
+ getGraphAvailability()?.setMemgraphAvailable(false);
188
201
  }
189
202
 
190
203
  /** Returns true if Memgraph bolt was confirmed reachable during this session. */
File without changes
File without changes
@@ -713,6 +713,14 @@ export async function runScheduledMaintenance(): Promise<void> {
713
713
  }
714
714
  }
715
715
 
716
+ // SEC-5: Daily cleanup of expired DraftAction PII nodes (30-day TTL)
717
+ try {
718
+ const { rawWrite: _rawWrite } = require('../../lib/safe-memgraph.js');
719
+ await _rawWrite('MATCH (da:DraftAction) WHERE da.expiresAt < datetime() DETACH DELETE da', {});
720
+ } catch (err: any) {
721
+ console.warn('[warm-tick] DraftAction TTL cleanup failed:', err?.message || String(err));
722
+ }
723
+
716
724
  // Daily: incremental label backfill (ensures critical graph labels are never empty)
717
725
  try {
718
726
  const { runIncrementalBackfill } = await import('./warm-tick-backfill.js');