@cgh567/agent 2.4.0 → 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.
- package/bin/helios +0 -0
- package/bin/helios-rpc-node-wrapper.cjs +0 -0
- package/bin/helios-rpc-wrapper.sh +0 -0
- package/daemon/adapters/helios-rpc-adapter.js +47 -25
- package/daemon/config/com.familiar.helios-daemon.plist +5 -0
- package/daemon/config/helios-daemon.service +4 -0
- package/daemon/context-enrichment.js +59 -21
- package/daemon/helios-api.js +149 -37
- package/daemon/helios-company-daemon.js +516 -124
- package/daemon/lib/harada/cascade-judge.js +12 -50
- package/daemon/lib/harada/mandala.js +20 -0
- package/daemon/lib/harada/pillar-dispatcher.js +1 -1
- package/daemon/lib/harada/project-factory.js +7 -2
- package/daemon/lib/hbo-bridge.js +31 -12
- package/daemon/lib/helios-hitl-host.js +15 -2
- package/daemon/lib/hitl-interaction-service.js +0 -0
- package/daemon/lib/memgraph-verify.js +38 -33
- package/daemon/lib/project-drift-detector.js +7 -17
- package/daemon/lib/project-semantic-updater.js +1 -14
- package/daemon/routes/channels.js +10 -5
- package/daemon/routes/harada-map.js +11 -48
- package/daemon/routes/hbo.js +89 -28
- package/daemon/routes/hitl.js +0 -0
- package/daemon/routes/project.js +4 -3
- package/daemon/routes/wizard.js +11 -4
- package/daemon/schema-migrations-hitl.js +0 -0
- package/extensions/001-tool-output-cap.ts +0 -0
- package/extensions/context-compaction.ts +45 -26
- package/extensions/cortex/activation-bridge.ts +5 -0
- package/extensions/cortex/learn.ts +26 -0
- package/extensions/email/backfill.ts +0 -0
- package/extensions/helios-governance/analysis/ambiguity.ts +0 -0
- package/extensions/helios-governance/analysis/compliance.ts +0 -0
- package/extensions/helios-governance/analysis/long-task-detector.ts +0 -0
- package/extensions/helios-governance/analysis/output-contract.ts +0 -0
- package/extensions/helios-governance/analysis/patterns.ts +0 -0
- package/extensions/helios-governance/analysis/preflight.ts +0 -0
- package/extensions/helios-governance/analysis/recurring-violations.ts +0 -0
- package/extensions/helios-governance/analysis/task-classification.ts +0 -0
- package/extensions/helios-governance/analysis/task-intent.ts +0 -0
- package/extensions/helios-governance/gates/high-impact.ts +1 -1
- package/extensions/helios-governance/handlers/_jiti-require.ts +15 -8
- package/extensions/helios-governance/handlers/proxy-test-detector.ts +0 -0
- package/extensions/hema-dispatch-v3/graph-memory.ts +10 -0
- package/extensions/hema-dispatch-v3/index.ts +59 -40
- package/extensions/lib/elo-engine.js +0 -0
- package/extensions/lib/elo-engine.test.js +0 -0
- package/extensions/memgraph-autostart.ts +13 -0
- package/extensions/neuroplastic-eval.ts +0 -0
- package/extensions/shadow-loop/index.ts +0 -0
- package/lib/brain-v2-budget.js +0 -0
- package/lib/brain-v2-circuit-breaker.js +0 -0
- package/lib/brain-v2.js +0 -0
- package/lib/broker/adaptive-throttle.js +0 -0
- package/lib/broker/batch-coalescer.js +0 -0
- package/lib/broker/bulkhead.js +0 -0
- package/lib/broker/channel-registry.js +0 -0
- package/lib/broker/circuit-breaker.js +0 -0
- package/lib/broker/evidence-cache.js +0 -0
- package/lib/broker/health-monitor.js +0 -0
- package/lib/broker/mage-queue.js +0 -0
- package/lib/broker/priority-queue.js +0 -0
- package/lib/broker/server.js.bak-error2-fix +0 -0
- package/lib/broker/session-registry.js +0 -0
- package/lib/broker/singleton-timers.js +0 -0
- package/lib/broker/types.d.ts +0 -0
- package/lib/broker/vegas-limit.js +0 -0
- package/lib/compression/dist/ccr-store.js +74 -0
- package/lib/compression/dist/content-router.js +115 -0
- package/lib/compression/dist/pipeline.js +113 -0
- package/lib/compression/dist/server.js +265 -0
- package/lib/compression/dist/smart-crusher.js +251 -0
- package/lib/context-budget.ts +0 -0
- package/lib/context-firewall.js +0 -0
- package/lib/crm/integration/triage-bridge.js +0 -0
- package/lib/email-utils.ts +0 -0
- package/lib/eval/__tests__/preflight-checker.test.ts +0 -0
- package/lib/eval/__tests__/task-instruction-parser.test.ts +0 -0
- package/lib/eval/__tests__/verifier-runner.test.ts +0 -0
- package/lib/eval/index.ts +0 -0
- package/lib/eval/preflight-checker.ts +0 -0
- package/lib/eval/task-domain-classifier.ts +0 -0
- package/lib/eval/task-instruction-parser.ts +0 -0
- package/lib/eval/verifier-runner.ts +0 -0
- package/lib/event-bus.d.ts +0 -0
- package/lib/governance-context-selector.ts +0 -0
- package/lib/graph/generate-extension-embeddings.js +0 -0
- package/lib/graph/generate-static-embeddings.js +0 -0
- package/lib/graph/lib/utils.js +1 -1
- package/lib/graph-audit.d.ts +0 -0
- package/lib/mesh-circuit-breaker.js +0 -0
- package/lib/mission-loop/lesson-extractor.ts +0 -0
- package/lib/mission-loop/mental-model-scorer.ts +0 -0
- package/lib/mission-loop/occ-detector.ts +0 -0
- package/lib/mission-loop/query-variants.ts +0 -0
- package/lib/mission-loop/verifier-check.ts +0 -0
- package/lib/skill-reference-builder.ts +0 -0
- package/lib/telemetry/token-breakdown.ts +0 -0
- package/lib/tool-compressor.ts +0 -0
- package/lib/triage-core/legal-routing.ts +0 -0
- package/lib/triage-core/mental-model/dunbar-classifier.ts +0 -0
- package/lib/triage-core/mental-model/enrich-all.ts +0 -0
- package/lib/triage-core/mental-model/identity-resolver.ts +0 -0
- package/lib/triage-core/mental-model/key-facts.ts +0 -0
- package/lib/triage-core/mental-model/model-assembler.ts +0 -0
- package/lib/triage-core/orchestrator.ts +0 -0
- package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -0
- package/package.json +10 -4
- package/skills/helios-business-operator/services/signals/upwork-signals.js +0 -0
- package/skills/talisman-ceo/SKILL.md +23 -25
- package/skills/talisman-comms/SKILL.md +5 -5
- package/skills/talisman-engineering/SKILL.md +5 -5
- package/skills/talisman-finance/SKILL.md +10 -8
- package/skills/talisman-marketing/SKILL.md +10 -10
- package/skills/talisman-sales/SKILL.md +12 -15
- package/skills/talisman-support/SKILL.md +5 -5
- package/agents/business/talisman-ceo.md +0 -183
- package/agents/business/talisman-comms.md +0 -257
- package/agents/business/talisman-cto.md +0 -153
- package/agents/business/talisman-finance.md +0 -246
- package/agents/business/talisman-marketing.md +0 -240
- package/agents/business/talisman-sales.md +0 -242
- package/agents/business/talisman-support.md +0 -236
- package/daemon/lib/approval-expiry.js +0 -162
- package/daemon/lib/blast-radius-analyzer.js +0 -75
- package/daemon/lib/domain-bootstrap-orchestrator.js +0 -267
- package/daemon/lib/forensic-log.js +0 -113
- package/daemon/lib/goal-research-pipeline.js +0 -644
- package/daemon/lib/harada/cascade-research-dispatcher.js +0 -261
- package/daemon/lib/headroom-middleware.js +0 -167
- package/daemon/lib/headroom-proxy-manager.js +0 -623
- package/daemon/lib/hed-engine.js +0 -307
- package/daemon/lib/mental-model-cache.js +0 -96
- package/daemon/lib/project-factory.js +0 -47
- package/daemon/lib/session-log-reader.js +0 -93
- package/daemon/routes/hed.js +0 -133
- package/lib/graph/learning/headroom-learn-bridge.js +0 -215
- package/skills/helios-bookkeeping/SKILL.md +0 -321
- package/skills/helios-briefer/SKILL.md +0 -44
- package/skills/helios-client-relations/SKILL.md +0 -322
- package/skills/helios-personal-triager/SKILL.md +0 -45
- package/skills/helios-recruitment/SKILL.md +0 -317
- package/skills/helios-relationship-nudger/SKILL.md +0 -77
- package/skills/helios-researcher/SKILL.md +0 -44
- package/skills/helios-scheduler/SKILL.md +0 -58
- package/skills/helios-tax-analyst/SKILL.md +0 -280
package/bin/helios
CHANGED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -33,11 +33,19 @@ const path = require('path');
|
|
|
33
33
|
const fs = require('fs');
|
|
34
34
|
const { randomUUID } = require('crypto');
|
|
35
35
|
|
|
36
|
-
const DEFAULT_TASK_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
37
|
-
const MAX_STDOUT_BUF = 10 * 1024 * 1024; // 10MB
|
|
38
|
-
|
|
39
36
|
// Resolve helios-agent root relative to this file: daemon/adapters/ → two levels up
|
|
40
37
|
const HELIOS_ROOT = path.resolve(__dirname, '..', '..');
|
|
38
|
+
|
|
39
|
+
// Lazy hboStore for SQLite-first CostEvent writes (P2-7)
|
|
40
|
+
let _hboStoreRpc = null;
|
|
41
|
+
function _getHboStoreRpc() {
|
|
42
|
+
if (_hboStoreRpc) return _hboStoreRpc;
|
|
43
|
+
try { _hboStoreRpc = require(path.join(HELIOS_ROOT, 'lib', 'hbo-core-store')); } catch (_) {}
|
|
44
|
+
return _hboStoreRpc;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const DEFAULT_TASK_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
|
|
48
|
+
const MAX_STDOUT_BUF = 10 * 1024 * 1024; // 10MB
|
|
41
49
|
const HELIOS_RPC_BIN = path.join(HELIOS_ROOT, 'bin', 'helios-rpc.js');
|
|
42
50
|
|
|
43
51
|
class HRpcAdapter {
|
|
@@ -120,21 +128,22 @@ class HRpcAdapter {
|
|
|
120
128
|
skill: skill,
|
|
121
129
|
wakeReason: config.originKind ?? 'heartbeat_timer',
|
|
122
130
|
}),
|
|
123
|
-
// ──
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
131
|
+
// ── Helios Compression Server ───────────────────────────────────────────
|
|
132
|
+
// Inject HEADROOM_PROXY_URL so the context-compaction.ts extension can
|
|
133
|
+
// call the compression server directly from the Pi subprocess.
|
|
134
|
+
//
|
|
135
|
+
// IMPORTANT: We do NOT set ANTHROPIC_BASE_URL — that would route all LLM
|
|
136
|
+
// calls through the compression server, which is not a full LLM proxy.
|
|
137
|
+
// Instead, compression happens in the context-compaction.ts extension hook
|
|
138
|
+
// BEFORE the LLM call (compress messages array → Pi sends compressed to Bedrock).
|
|
139
|
+
// This is the correct architecture: compress at assembly time, not at wire time.
|
|
129
140
|
...(() => {
|
|
130
141
|
try {
|
|
131
142
|
const { HeadroomProxyManager } = require('../lib/headroom-proxy-manager');
|
|
132
143
|
const baseUrl = HeadroomProxyManager.getInstance().getBaseUrl();
|
|
133
144
|
if (baseUrl) {
|
|
134
145
|
return {
|
|
135
|
-
|
|
136
|
-
OPENAI_BASE_URL: `${baseUrl}/v1`,
|
|
137
|
-
HEADROOM_PROXY_URL: baseUrl,
|
|
146
|
+
HEADROOM_PROXY_URL: baseUrl,
|
|
138
147
|
};
|
|
139
148
|
}
|
|
140
149
|
} catch (_) {}
|
|
@@ -395,20 +404,33 @@ class HRpcAdapter {
|
|
|
395
404
|
onMeta?.({ type: 'agent_end', data: { taskId, textLength: lastAssistantText.length } });
|
|
396
405
|
closeStdin(child);
|
|
397
406
|
// Write real Bedrock spend as CostEvent so BudgetEnforcer can track it
|
|
398
|
-
if (totalCostCents > 0 &&
|
|
407
|
+
if (totalCostCents > 0 && config.companyId) {
|
|
399
408
|
const ceId = 'ce:rpc:' + taskId + ':' + Date.now();
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
409
|
+
// SQLite-first CostEvent write (P2-7)
|
|
410
|
+
try {
|
|
411
|
+
const _store = _getHboStoreRpc();
|
|
412
|
+
if (_store && _store.createCostEvent) {
|
|
413
|
+
_store.createCostEvent({
|
|
414
|
+
id: ceId, companyId: config.companyId ?? '', agentId: config.agentId ?? agentId ?? '',
|
|
415
|
+
costCents: totalCostCents, source: 'helios_rpc', taskId, createdAt: Date.now(),
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
} catch (_) {}
|
|
419
|
+
// Non-blocking Memgraph projection (fire-and-forget)
|
|
420
|
+
if (context.mgQuery) {
|
|
421
|
+
setImmediate(() => context.mgQuery(
|
|
422
|
+
`MERGE (ce:CostEvent {id: $id})
|
|
423
|
+
ON CREATE SET
|
|
424
|
+
ce.companyId = $cid,
|
|
425
|
+
ce.agentId = $agentId,
|
|
426
|
+
ce.costCents = toInteger($costCents),
|
|
427
|
+
ce.source = 'helios_rpc',
|
|
428
|
+
ce.taskId = $taskId,
|
|
429
|
+
ce.createdAt = localdatetime()`,
|
|
430
|
+
{ id: ceId, cid: config.companyId ?? '', agentId: config.agentId ?? agentId ?? '',
|
|
431
|
+
costCents: totalCostCents, taskId }
|
|
432
|
+
).catch(e => onLog?.({ stream: 'stderr', chunk: '[helios-rpc-adapter] CostEvent write failed: ' + e.message, ts: new Date().toISOString() })));
|
|
433
|
+
}
|
|
412
434
|
}
|
|
413
435
|
|
|
414
436
|
// F1-3: if child doesn't exit within 30s after agent_end, settle now.
|
|
@@ -90,6 +90,11 @@
|
|
|
90
90
|
<string>unified</string>
|
|
91
91
|
<key>HELIOS_ALLOW_SEND</key>
|
|
92
92
|
<string>0</string>
|
|
93
|
+
<!-- --experimental-sqlite enables node:sqlite built-in (hbo-core-store).
|
|
94
|
+
Stable in Node ≥ 23; required flag in Node 22.
|
|
95
|
+
--max-old-space-size=1024 matches PM2 ecosystem.config.js memory cap (SVC-2 fix). -->
|
|
96
|
+
<key>NODE_OPTIONS</key>
|
|
97
|
+
<string>--experimental-sqlite --max-old-space-size=1024</string>
|
|
93
98
|
<!-- PATH extended to cover common Node.js install locations:
|
|
94
99
|
nvm default, volta, homebrew, system. The launch script will
|
|
95
100
|
source the user's shell profile which adds nvm to PATH. -->
|
|
@@ -35,6 +35,10 @@ Environment=TZ=UTC
|
|
|
35
35
|
Environment=ROUTING_AUTHORITY=unified
|
|
36
36
|
Environment=AWS_REGION=us-east-1
|
|
37
37
|
Environment=MEMGRAPH_BOLT_URL=bolt://127.0.0.1:7687
|
|
38
|
+
# --experimental-sqlite enables node:sqlite built-in (hbo-core-store fallback store).
|
|
39
|
+
# Stable in Node ≥ 23; required flag in Node 22.
|
|
40
|
+
# --max-old-space-size=1024 matches the PM2 ecosystem.config.js memory cap (SVC-1 fix).
|
|
41
|
+
Environment=NODE_OPTIONS=--experimental-sqlite --max-old-space-size=1024
|
|
38
42
|
# HELIOS_ROOT is resolved by launch-daemon.sh — not hardcoded here
|
|
39
43
|
# EnvironmentFile uses - prefix (dash) to not fail if file is missing
|
|
40
44
|
EnvironmentFile=-%h/helios-agent/.env
|
|
@@ -4,6 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
const { HELIOS_ROOT: _HELIOS_ROOT_CE } = require('./lib/paths');
|
|
6
6
|
|
|
7
|
+
// Lazy-require hbo-core-store for SQLite-first business entity reads (P2-11)
|
|
8
|
+
let _hboStoreCE = null;
|
|
9
|
+
function _getHboStoreCE() {
|
|
10
|
+
if (_hboStoreCE) return _hboStoreCE;
|
|
11
|
+
try { _hboStoreCE = require('../lib/hbo-core-store'); } catch (_) {}
|
|
12
|
+
return _hboStoreCE;
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
/** Marker appended when buildContextBrief truncates an oversized brief. */
|
|
8
16
|
const CONTEXT_BRIEF_TRUNCATED = '[CONTEXT BRIEF TRUNCATED — exceeded 30k char cap (aligned to adapter delivery limit)]';
|
|
9
17
|
|
|
@@ -430,19 +438,33 @@ async function buildContextBrief(mgQuery, agentId, taskTitle, companyId, hboBrid
|
|
|
430
438
|
} catch (e) { /* non-fatal */ }
|
|
431
439
|
|
|
432
440
|
// 3. Agent's own pending tasks (what else is on their plate)
|
|
441
|
+
// Memgraph primary — SQLite fallback on unavailability
|
|
433
442
|
try {
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
443
|
+
let pendingRecords = null;
|
|
444
|
+
try {
|
|
445
|
+
const pendingResult = await mgQuery(
|
|
446
|
+
`MATCH (t:Task {assigneeAgentId: $aid, companyId: $cid})
|
|
447
|
+
WHERE t.status IN ['todo', 'in_progress']
|
|
448
|
+
RETURN t.title AS title, t.status AS status, t.priority AS priority
|
|
449
|
+
ORDER BY CASE t.priority WHEN 'P0' THEN 0 WHEN 'P1' THEN 1 WHEN 'P2' THEN 2 ELSE 3 END
|
|
450
|
+
LIMIT 5`,
|
|
451
|
+
{ aid: agentId, cid: companyId }
|
|
452
|
+
);
|
|
453
|
+
pendingRecords = toRecords(pendingResult).map(r => ({ _title: r.get('title'), _status: r.get('status'), _priority: r.get('priority') }));
|
|
454
|
+
} catch (_mgErr) {
|
|
455
|
+
// Memgraph unavailable — fall back to SQLite pending tasks
|
|
456
|
+
try {
|
|
457
|
+
const _storeCE = _getHboStoreCE();
|
|
458
|
+
if (_storeCE && _storeCE.getTasksByCompanyStatus) {
|
|
459
|
+
const _rows = _storeCE.getTasksByCompanyStatus(companyId, ['todo', 'in_progress'])
|
|
460
|
+
.filter(t => t.assigneeAgentId === agentId);
|
|
461
|
+
pendingRecords = _rows.map(t => ({ _title: t.title, _status: t.status, _priority: t.priority }));
|
|
462
|
+
}
|
|
463
|
+
} catch (_sqliteErr) { /* SQLite also unavailable — skip pending tasks section */ }
|
|
464
|
+
}
|
|
465
|
+
if (pendingRecords && pendingRecords.length) {
|
|
444
466
|
const lines = pendingRecords.map(r =>
|
|
445
|
-
`- [${r.
|
|
467
|
+
`- [${r._priority || 'P3'}] ${r._title} (${r._status})`
|
|
446
468
|
);
|
|
447
469
|
sections.push({ id: 'active-tasks', content: `## Your Other Tasks\n${lines.join('\n')}` });
|
|
448
470
|
}
|
|
@@ -502,16 +524,32 @@ async function buildContextBrief(mgQuery, agentId, taskTitle, companyId, hboBrid
|
|
|
502
524
|
}
|
|
503
525
|
} catch (_) { /* non-fatal */ }
|
|
504
526
|
|
|
505
|
-
// § QUARTERLY OKR
|
|
527
|
+
// § QUARTERLY OKR — Memgraph primary, SQLite fallback on unavailability
|
|
506
528
|
try {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
529
|
+
let okrRec = null;
|
|
530
|
+
try {
|
|
531
|
+
const okrResult = await mgQuery(
|
|
532
|
+
`MATCH (o:QuarterlyOKR {companyId: $cid, status: 'active'})
|
|
533
|
+
WHERE o.agentId = $agentId OR o.assigneeAgentId = $agentId
|
|
534
|
+
RETURN o.objective AS objective, o.keyResults AS keyResults, o.quarter AS quarter
|
|
535
|
+
ORDER BY o.createdAt DESC LIMIT 1`,
|
|
536
|
+
{ agentId, cid: companyId }
|
|
537
|
+
);
|
|
538
|
+
okrRec = toRecords(okrResult)?.[0] ?? null;
|
|
539
|
+
} catch (_mgErr) {
|
|
540
|
+
// Memgraph unavailable — fall back to SQLite OKR data
|
|
541
|
+
try {
|
|
542
|
+
const _storeOKR = _getHboStoreCE();
|
|
543
|
+
if (_storeOKR && _storeOKR.getOKRsByCompanyType) {
|
|
544
|
+
const _okrs = _storeOKR.getOKRsByCompanyType(companyId, 'quarterly_okr')
|
|
545
|
+
.filter(o => (o.agentId === agentId || o.assigneeAgentId === agentId) && o.status === 'active');
|
|
546
|
+
if (_okrs.length) {
|
|
547
|
+
const o = _okrs[0];
|
|
548
|
+
okrRec = { get: (k) => o[k] ?? null };
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} catch (_sqliteErr) { /* SQLite also unavailable — skip OKR section */ }
|
|
552
|
+
}
|
|
515
553
|
if (okrRec) {
|
|
516
554
|
const objective = okrRec.get('objective');
|
|
517
555
|
const keyResults = okrRec.get('keyResults') || [];
|
|
@@ -1736,7 +1774,7 @@ ${lines.join('\n')}` });
|
|
|
1736
1774
|
try {
|
|
1737
1775
|
const excl = JSON.parse(pillarData.exclusions || '[]');
|
|
1738
1776
|
if (excl.length > 0) exclusionNote = `\nDo NOT: ${excl.slice(0, 3).join(', ')}`;
|
|
1739
|
-
|
|
1777
|
+
} catch (_) {}
|
|
1740
1778
|
sections.push(
|
|
1741
1779
|
`<north_star_reminder>\n` +
|
|
1742
1780
|
`Before responding, confirm your answer serves this intent:\n` +
|
package/daemon/helios-api.js
CHANGED
|
@@ -42,6 +42,7 @@ const fs = require('fs');
|
|
|
42
42
|
const path = require('path');
|
|
43
43
|
const crypto = require('crypto');
|
|
44
44
|
const { TranscriptStore } = require('./transcript-store');
|
|
45
|
+
const hboStore = require('../lib/hbo-core-store');
|
|
45
46
|
|
|
46
47
|
const TRANSCRIPTS_DIR = path.join(__dirname, 'transcripts');
|
|
47
48
|
const _transcriptStore = new TranscriptStore(TRANSCRIPTS_DIR);
|
|
@@ -280,6 +281,26 @@ async function handleGetTasks(req, res, ctx) {
|
|
|
280
281
|
let cypher;
|
|
281
282
|
const params = { limit, cid: ctx.cid };
|
|
282
283
|
|
|
284
|
+
// SQLite-first task list read (P2-9)
|
|
285
|
+
try {
|
|
286
|
+
const _storeTasks = hboStore.getTasksByCompanyStatus
|
|
287
|
+
? (q.status
|
|
288
|
+
? hboStore.getTasksByCompanyStatus(ctx.cid, q.status)
|
|
289
|
+
: (() => { try { return require('../lib/hbo-core-store').getTasksByCompanyStatus(ctx.cid, ['todo','in_progress','done','andon_paused','help_pending','cancelled']); } catch(_) { return null; } })()
|
|
290
|
+
)
|
|
291
|
+
: null;
|
|
292
|
+
if (_storeTasks) {
|
|
293
|
+
let tasks = _storeTasks;
|
|
294
|
+
if (q.agentId) tasks = tasks.filter(t => t.assigneeAgentId === q.agentId);
|
|
295
|
+
tasks = tasks
|
|
296
|
+
.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))
|
|
297
|
+
.slice(0, limit)
|
|
298
|
+
.map(t => ({ id: t.id, title: t.title, status: t.status, priority: t.priority, assignee: t.assigneeAgentId, body: t.body ?? null }));
|
|
299
|
+
jsonResponse(res, 200, { tasks, count: tasks.length });
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
} catch (_) {}
|
|
303
|
+
|
|
283
304
|
if (q.status && q.agentId) {
|
|
284
305
|
cypher = `MATCH (t:Task {companyId: $cid, status: $status, assigneeAgentId: $agentId})
|
|
285
306
|
RETURN t.id AS id, t.title AS title, t.status AS status,
|
|
@@ -503,6 +524,23 @@ async function handleGetApprovals(req, res, ctx) {
|
|
|
503
524
|
let cypher;
|
|
504
525
|
const params = { limit, cid: ctx.cid };
|
|
505
526
|
|
|
527
|
+
// SQLite-first approval list read (P2-9)
|
|
528
|
+
try {
|
|
529
|
+
const _storeApprovals = hboStore.getApprovalsByCompanyStatus
|
|
530
|
+
? (q.status
|
|
531
|
+
? hboStore.getApprovalsByCompanyStatus(ctx.cid, q.status)
|
|
532
|
+
: hboStore.getApprovalsByCompanyStatus(ctx.cid, ['pending','approved','rejected','expired'])
|
|
533
|
+
)
|
|
534
|
+
: null;
|
|
535
|
+
if (_storeApprovals) {
|
|
536
|
+
const approvals = _storeApprovals
|
|
537
|
+
.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0))
|
|
538
|
+
.slice(0, limit);
|
|
539
|
+
jsonResponse(res, 200, { approvals, count: approvals.length });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
} catch (_) {}
|
|
543
|
+
|
|
506
544
|
if (q.status) {
|
|
507
545
|
cypher = `MATCH (a:Approval {companyId: $cid, status: $status})${returnClause}`;
|
|
508
546
|
params.status = q.status;
|
|
@@ -2848,6 +2886,10 @@ let interpretationRoute = () => false;
|
|
|
2848
2886
|
let hedRoute = () => false;
|
|
2849
2887
|
// D4: Department intelligence page handler — initialized lazily inside startApi()
|
|
2850
2888
|
let handleGetDepartmentPage = null;
|
|
2889
|
+
// Domain-specific routes — initialized lazily inside startApi()
|
|
2890
|
+
let hitlRoute = null;
|
|
2891
|
+
let channelsRoute = null;
|
|
2892
|
+
let emailInfraRoute = null;
|
|
2851
2893
|
const { handleOKRRoutes } = require('./routes/okrs');
|
|
2852
2894
|
const { handleSenseiRoutes } = require('./routes/sensei');
|
|
2853
2895
|
let suggestionsRoute, suggestionsCron;
|
|
@@ -2967,6 +3009,14 @@ async function route(req, res, ctx) {
|
|
|
2967
3009
|
return;
|
|
2968
3010
|
}
|
|
2969
3011
|
const result = ctx.daemon.registerCompany(String(companyId));
|
|
3012
|
+
// Also update ctx.companies so the tenant isolation check accepts this company
|
|
3013
|
+
// on subsequent requests (the 403 guard checks ctx.companies, not _modulesByCompany).
|
|
3014
|
+
// This is safe: ctx.companies is an array that only grows — no entries are removed.
|
|
3015
|
+
if (!ctx.companies) ctx.companies = [];
|
|
3016
|
+
const cidStr = String(companyId);
|
|
3017
|
+
if (!ctx.companies.find(c => String(c.id) === cidStr)) {
|
|
3018
|
+
try { ctx.companies.push({ id: cidStr, name: cidStr }); } catch (_) { /* frozen array — ignore */ }
|
|
3019
|
+
}
|
|
2970
3020
|
jsonResponse(res, 200, result);
|
|
2971
3021
|
} catch (err) {
|
|
2972
3022
|
jsonResponse(res, 500, { error: `registerCompany failed: ${err.message}` });
|
|
@@ -2974,6 +3024,51 @@ async function route(req, res, ctx) {
|
|
|
2974
3024
|
return;
|
|
2975
3025
|
}
|
|
2976
3026
|
|
|
3027
|
+
// GET /api/v1/context-brief — build and return the agent context brief.
|
|
3028
|
+
//
|
|
3029
|
+
// Used by harbor tests (test_context_propagation.py) and desktop context panel.
|
|
3030
|
+
// Calls buildContextBrief() from context-enrichment.js — the same function the
|
|
3031
|
+
// daemon calls before every agent dispatch.
|
|
3032
|
+
//
|
|
3033
|
+
// Query params:
|
|
3034
|
+
// agentId (required) — the BusinessAgent id
|
|
3035
|
+
// companyId (optional) — defaults to primary company; checked against allowedCompanyIds
|
|
3036
|
+
// nocache (optional) — if set, bypasses the 5-minute context cache
|
|
3037
|
+
//
|
|
3038
|
+
// Primary path: GET /api/v1/context-brief?agentId=X&companyId=Y
|
|
3039
|
+
// → buildContextBrief(mgQuery, agentId, '', companyId)
|
|
3040
|
+
// → { brief: string, agentId, companyId }
|
|
3041
|
+
if (method === 'GET' && pathname === '/api/v1/context-brief') {
|
|
3042
|
+
try {
|
|
3043
|
+
const agentId = parsedUrl.searchParams.get('agentId');
|
|
3044
|
+
const explicitCid = parsedUrl.searchParams.get('companyId');
|
|
3045
|
+
if (!agentId) {
|
|
3046
|
+
jsonResponse(res, 400, { error: 'agentId required' });
|
|
3047
|
+
return;
|
|
3048
|
+
}
|
|
3049
|
+
if (!explicitCid) {
|
|
3050
|
+
jsonResponse(res, 400, { error: 'companyId required' });
|
|
3051
|
+
return;
|
|
3052
|
+
}
|
|
3053
|
+
const cid = resolvedCid;
|
|
3054
|
+
const { mgQuery } = ctx;
|
|
3055
|
+
if (!mgQuery) {
|
|
3056
|
+
jsonResponse(res, 503, { error: 'Memgraph not available' });
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
const { buildContextBrief, invalidateContextCache } = require('./context-enrichment');
|
|
3060
|
+
// Bust 5-minute context cache when nocache=1 is set (used by tests after seeding)
|
|
3061
|
+
if (parsedUrl.searchParams.get('nocache')) {
|
|
3062
|
+
try { invalidateContextCache(cid); } catch (_) {}
|
|
3063
|
+
}
|
|
3064
|
+
const brief = await buildContextBrief(mgQuery, agentId, '', cid, null);
|
|
3065
|
+
jsonResponse(res, 200, { brief: brief || '', agentId, companyId: cid });
|
|
3066
|
+
} catch (err) {
|
|
3067
|
+
jsonResponse(res, 500, { error: `context-brief failed: ${err.message}` });
|
|
3068
|
+
}
|
|
3069
|
+
return;
|
|
3070
|
+
}
|
|
3071
|
+
|
|
2977
3072
|
// GET /api/headroom/health — proxy to Headroom compression proxy health endpoint
|
|
2978
3073
|
// Used by HeliosInfraService (helios-desktop) to show proxy status in Ground Control.
|
|
2979
3074
|
if (method === 'GET' && pathname === '/api/headroom/health') {
|
|
@@ -3163,18 +3258,49 @@ async function route(req, res, ctx) {
|
|
|
3163
3258
|
const baseUrl = HeadroomProxyManager.getInstance().getBaseUrl();
|
|
3164
3259
|
if (!baseUrl) return _origEnd(chunk, encoding, callback);
|
|
3165
3260
|
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3261
|
+
// Direct HTTP POST /headroom/compress — no npm package required.
|
|
3262
|
+
// Same pattern as context-compaction.ts and headroom-middleware.js.
|
|
3263
|
+
const _payload = JSON.stringify({
|
|
3264
|
+
messages: [{
|
|
3265
|
+
role: 'user',
|
|
3266
|
+
content: [{
|
|
3267
|
+
type: 'tool_result',
|
|
3268
|
+
tool_use_id: 'hbo_get',
|
|
3269
|
+
content: body,
|
|
3270
|
+
}],
|
|
3271
|
+
}],
|
|
3272
|
+
});
|
|
3273
|
+
const _url = new URL(baseUrl);
|
|
3274
|
+
const result = await new Promise((resolve, reject) => {
|
|
3275
|
+
const http = require('http');
|
|
3276
|
+
const req = http.request(
|
|
3277
|
+
{
|
|
3278
|
+
hostname: _url.hostname,
|
|
3279
|
+
port: parseInt(_url.port || '8787', 10),
|
|
3280
|
+
path: '/headroom/compress',
|
|
3281
|
+
method: 'POST',
|
|
3282
|
+
headers: {
|
|
3283
|
+
'Content-Type': 'application/json',
|
|
3284
|
+
'Content-Length': Buffer.byteLength(_payload),
|
|
3285
|
+
},
|
|
3286
|
+
},
|
|
3287
|
+
(res) => {
|
|
3288
|
+
let buf = '';
|
|
3289
|
+
res.on('data', (c) => { buf += c; });
|
|
3290
|
+
res.on('end', () => { try { resolve(JSON.parse(buf)); } catch { reject(new Error('bad json')); } });
|
|
3291
|
+
res.on('error', reject);
|
|
3292
|
+
}
|
|
3293
|
+
);
|
|
3294
|
+
req.setTimeout(8000, () => { req.destroy(); reject(new Error('timeout')); });
|
|
3295
|
+
req.on('error', reject);
|
|
3296
|
+
req.write(_payload);
|
|
3297
|
+
req.end();
|
|
3298
|
+
});
|
|
3171
3299
|
|
|
3172
3300
|
let compressed = body;
|
|
3173
3301
|
try {
|
|
3174
|
-
const c = result?.messages?.[0]?.content;
|
|
3175
|
-
|
|
3176
|
-
: Array.isArray(c) ? (c[0]?.text ?? c[0]?.content ?? body) : body;
|
|
3177
|
-
compressed = text;
|
|
3302
|
+
const c = result?.messages?.[0]?.content?.[0]?.content;
|
|
3303
|
+
if (c) compressed = c;
|
|
3178
3304
|
} catch (_) {}
|
|
3179
3305
|
|
|
3180
3306
|
if (result?.ccrHashes?.length) {
|
|
@@ -3495,41 +3621,27 @@ async function handleGetDepartmentPageRoute(req, res, ctx, department) {
|
|
|
3495
3621
|
}
|
|
3496
3622
|
}
|
|
3497
3623
|
|
|
3498
|
-
// Cache miss or stale —
|
|
3624
|
+
// Cache miss or stale — return 202 immediately and generate async
|
|
3499
3625
|
if (!handleGetDepartmentPage) {
|
|
3500
3626
|
jsonResponse(res, 503, { error: 'Department page generator not initialized' });
|
|
3501
3627
|
return;
|
|
3502
3628
|
}
|
|
3503
3629
|
|
|
3504
|
-
|
|
3630
|
+
// D5: Return 202 immediately so the frontend can show a loading state.
|
|
3631
|
+
// Generation runs in setImmediate (next event-loop tick) and broadcasts
|
|
3632
|
+
// department:page:ready when complete so the client can refetch.
|
|
3633
|
+
jsonResponse(res, 202, { ok: true, generating: true, department, companyId: cid });
|
|
3505
3634
|
|
|
3506
|
-
|
|
3507
|
-
// When null, query the DepartmentPage node for its status/error so the desktop
|
|
3508
|
-
// can show a specific message (e.g. "AWS credentials required") instead of the
|
|
3509
|
-
// generic "no intelligence page available" empty state.
|
|
3510
|
-
if (!narrative) {
|
|
3511
|
-
let failureError = null;
|
|
3635
|
+
setImmediate(async () => {
|
|
3512
3636
|
try {
|
|
3513
|
-
const
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
{ cid, dept: department }
|
|
3517
|
-
).catch(() => null);
|
|
3518
|
-
const failRow = failedNode?.rows?.[0];
|
|
3519
|
-
if (failRow) {
|
|
3520
|
-
failureError = Array.isArray(failRow) ? failRow[0] : failRow.error;
|
|
3637
|
+
const narrative = await handleGetDepartmentPage.generatePage(cid, department);
|
|
3638
|
+
if (narrative) {
|
|
3639
|
+
broadcast({ type: 'department:page:ready', companyId: cid, department });
|
|
3521
3640
|
}
|
|
3522
|
-
} catch (
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
error: failureError || 'Generation failed — check daemon logs',
|
|
3527
|
-
cached: false,
|
|
3528
|
-
});
|
|
3529
|
-
return;
|
|
3530
|
-
}
|
|
3531
|
-
|
|
3532
|
-
jsonResponse(res, 200, { department, narrative, generatedAt: new Date().toISOString(), cached: false });
|
|
3641
|
+
} catch (_genErr) {
|
|
3642
|
+
console.warn('[dept-page] async generation error:', _genErr && _genErr.message);
|
|
3643
|
+
}
|
|
3644
|
+
});
|
|
3533
3645
|
|
|
3534
3646
|
} catch (e) {
|
|
3535
3647
|
jsonResponse(res, 500, { error: 'Department page generation failed: ' + (e.message || 'unknown error') });
|
|
@@ -3753,7 +3865,7 @@ function startApi(mgQuery, config = {}, state = {}) {
|
|
|
3753
3865
|
}
|
|
3754
3866
|
|
|
3755
3867
|
// Domain-specific routes: HITL, channel setup, email infrastructure
|
|
3756
|
-
|
|
3868
|
+
hitlRoute = null; channelsRoute = null; emailInfraRoute = null;
|
|
3757
3869
|
try { hitlRoute = require('./routes/hitl')({ mgQuery }); } catch(e) { log('warn', `hitl route unavailable: ${e.message}`); }
|
|
3758
3870
|
try { channelsRoute = require('./routes/channels')({ mgQuery }); } catch(e) { log('warn', `channels route unavailable: ${e.message}`); }
|
|
3759
3871
|
try { emailInfraRoute = require('./routes/email-infrastructure')({ mgQuery }); } catch(e) { log('warn', `emailInfra route unavailable: ${e.message}`); }
|