@cgh567/agent 2.4.2 → 2.4.4
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/agents/business/talisman-ceo.md +183 -0
- package/agents/business/talisman-comms.md +257 -0
- package/agents/business/talisman-cto.md +153 -0
- package/agents/business/talisman-finance.md +246 -0
- package/agents/business/talisman-marketing.md +240 -0
- package/agents/business/talisman-sales.md +242 -0
- package/agents/business/talisman-support.md +236 -0
- package/bin/helios-rpc.js +19 -0
- package/daemon/adapters/helios-rpc-adapter.js +5 -12
- package/daemon/adapters/tui_wakeup.js +8 -0
- package/daemon/context-enrichment.js +27 -0
- package/daemon/daemon-manager.js +1 -1
- package/daemon/db/email-infrastructure-migrate.js +192 -0
- package/daemon/db/hbo-core-migrate.js +189 -0
- package/daemon/helios-api.js +863 -64
- package/daemon/helios-company-daemon.js +233 -33
- package/daemon/lib/blast-radius-analyzer.js +75 -0
- package/daemon/lib/domain-bootstrap-orchestrator.js +267 -0
- package/daemon/lib/forensic-log.js +113 -0
- package/daemon/lib/goal-research-pipeline.js +644 -0
- package/daemon/lib/harada/cascade-judge.js +84 -1
- package/daemon/lib/harada/cascade-research-dispatcher.js +282 -0
- package/daemon/lib/harada/pillar-dispatcher.js +23 -2
- package/daemon/lib/hbo-bridge.js +74 -6
- package/daemon/lib/headroom-middleware.js +129 -0
- package/daemon/lib/headroom-proxy-manager.js +309 -0
- package/daemon/lib/hed-engine.js +25 -0
- package/daemon/lib/intelligence/department-page-generator.js +46 -1
- package/daemon/lib/interpretation-engine.js +92 -0
- package/daemon/lib/mental-model-cache.js +96 -0
- package/daemon/lib/project-factory.js +47 -0
- package/daemon/lib/session-log-reader.js +93 -0
- package/daemon/lib/standard-work-bootstrap.js +87 -1
- package/daemon/lib/task-completion-processor.js +23 -0
- package/daemon/lib/wizard-engine.js +57 -6
- package/daemon/package.json +2 -1
- package/daemon/routes/agents.js +51 -6
- package/daemon/routes/channels.js +116 -2
- package/daemon/routes/crm.js +85 -0
- package/daemon/routes/dashboard.js +62 -16
- package/daemon/routes/dept.js +10 -1
- package/daemon/routes/email-triage.js +19 -10
- package/daemon/routes/hbo.js +618 -58
- package/daemon/routes/hed.js +133 -0
- package/daemon/routes/inbox.js +397 -8
- package/daemon/routes/project.js +580 -66
- package/daemon/routes/routines.js +14 -0
- package/daemon/routes/tasks.js +15 -1
- package/daemon/schema-apply.js +174 -0
- package/daemon/schema-definitions.js +433 -0
- package/daemon/schema-migrations-hbo.js +20 -0
- package/daemon/schema-migrations-hed.js +18 -0
- package/daemon/schema-migrations-proj.js +153 -0
- package/extensions/__tests__/codebase-index.test.ts +73 -0
- package/extensions/__tests__/extension-command-registration.test.ts +35 -0
- package/extensions/__tests__/git-push-guard.test.ts +68 -0
- package/extensions/context-compaction.ts +104 -76
- package/extensions/cortex/__tests__/cortex-core.test.ts +100 -0
- package/extensions/cortex/wal-replay.ts +91 -0
- package/extensions/email/actions/draft-response.ts +21 -1
- package/extensions/email/auth/accounts.ts +5 -11
- package/extensions/email/auth/inbox-dog.ts +5 -2
- package/extensions/email/backfill.ts +20 -13
- package/extensions/email/providers/gmail.ts +164 -0
- package/extensions/email/providers/google-calendar.ts +34 -5
- package/extensions/helios-browser/__tests__/browser-routing.test.ts +57 -0
- package/extensions/helios-browser/backends/playwright.ts +3 -1
- package/extensions/helios-governance/__tests__/governance-gates.test.ts +40 -0
- package/extensions/helios-governance/__tests__/tournament-consumer.test.js +66 -0
- package/extensions/hema-dispatch-v3/headroom-compress.ts +103 -0
- package/extensions/hema-dispatch-v3/index.ts +46 -72
- package/extensions/interview/__tests__/server.test.ts +117 -0
- package/extensions/lib/helios-root.cjs +46 -0
- package/extensions/subagent-mesh/__tests__/handlers.test.ts +98 -0
- package/extensions/warm-tick/warm-tick-maintenance.ts +164 -0
- package/lib/__tests__/bulk-ingest.live.test.ts +66 -0
- package/lib/__tests__/crash-fixes.test.ts +49 -0
- package/lib/__tests__/hbo-core-store.test.js +238 -0
- package/lib/__tests__/maintenance-mission-wiring.test.ts +35 -0
- package/lib/broker/__tests__/jit-subscription.test.js +44 -1
- package/lib/broker/__tests__/lifecycle-channels.test.js +25 -1
- package/lib/compression/__tests__/ccr-store.test.js +138 -0
- package/lib/compression/__tests__/pipeline.test.js +280 -0
- package/lib/compression/__tests__/smart-crusher.test.js +242 -0
- package/lib/compression/dist/server.js +34 -1
- package/lib/compression/dist/start-server.js +77 -0
- package/lib/event-bus.mts +1 -1
- package/lib/graph/learning/headroom-learn-bridge.js +175 -0
- package/lib/graph-availability.js +62 -0
- package/lib/hbo-core-store.compiled.js +834 -0
- package/lib/hbo-core-store.js +124 -0
- package/lib/hbo-core-store.ts +979 -0
- package/lib/mission-loop/__tests__/research-handler.test.ts +143 -0
- package/lib/skill-sync.js +6 -1
- package/lib/startup-integrity.js +9 -2
- package/lib/triage-core/__tests__/classifier-fixture.test.ts +254 -0
- package/lib/triage-core/__tests__/classifier-post-norm.test.ts +1 -1
- package/lib/triage-core/__tests__/classifier.test.ts +45 -7
- package/lib/triage-core/__tests__/correction-detector.test.ts +36 -0
- package/lib/triage-core/__tests__/d6-dunbar-boost.test.ts +5 -5
- package/lib/triage-core/__tests__/orchestrator-pipeline.test.ts +107 -0
- package/lib/triage-core/__tests__/orchestrator.test.ts +113 -1
- package/lib/triage-core/__tests__/signals.test.ts +357 -0
- package/lib/triage-core/__tests__/sql-parity.test.ts +216 -0
- package/lib/triage-core/backfill-cost-estimator.ts +91 -0
- package/lib/triage-core/backfill-orchestrator.ts +119 -0
- package/lib/triage-core/classifier.ts +41 -8
- package/lib/triage-core/cos/cross-channel-escalation.ts +2 -3
- package/lib/triage-core/cos/response-debt.ts +2 -2
- package/lib/triage-core/graph/__tests__/batch-persistence.test.ts +283 -0
- package/lib/triage-core/graph/batch-persistence.ts +66 -2
- package/lib/triage-core/graph/betweenness-worker.js +75 -0
- package/lib/triage-core/graph/graph-rank-sql.ts +67 -0
- package/lib/triage-core/graph/persistence.ts +1 -1
- package/lib/triage-core/graph/schema-v2.ts +2 -0
- package/lib/triage-core/graph/schema.cypher +11 -0
- package/lib/triage-core/graph/triage-query.ts +1 -1
- package/lib/triage-core/learning.ts +15 -20
- package/lib/triage-core/mental-model/bedrock-config.ts +78 -0
- package/lib/triage-core/mental-model/cos-integration.ts +1 -1
- package/lib/triage-core/mental-model/entity-extractor.ts +51 -4
- package/lib/triage-core/mental-model/identity-resolver.ts +5 -5
- package/lib/triage-core/mental-model/key-facts.ts +1 -2
- package/lib/triage-core/mental-model/model-assembler-sql.ts +200 -0
- package/lib/triage-core/mental-model/model-assembler.ts +16 -3
- package/lib/triage-core/orchestrator.ts +8 -15
- package/lib/triage-core/scheduled-sends.ts +39 -2
- package/lib/triage-core/signals/comms-style.ts +1 -1
- package/lib/triage-core/signals/cross-channel-escalation.ts +2 -2
- package/lib/triage-core/signals/favee-type.ts +6 -1
- package/lib/triage-core/signals/goal-relevance.ts +31 -2
- package/lib/triage-core/signals/personal-importance.ts +1 -1
- package/lib/triage-core/signals/referral-chain.ts +0 -1
- package/lib/triage-core/signals/relationship-decay.ts +4 -0
- package/lib/triage-core/signals/relationship-health.ts +6 -1
- package/lib/triage-core/signals/trajectory-signal.ts +38 -3
- package/lib/triage-core/tournament-runner.js +11 -1
- package/lib/triage-core/triage-llm-factory.ts +110 -0
- package/lib/triage-core/triage-local-llm.ts +145 -0
- package/lib/triage-core/triage-sql-store.ts +337 -0
- package/lib/triage-core/types.ts +2 -2
- package/lib/unified-graph.atomic.test.ts +52 -0
- package/lib/unified-graph.failure-categories.test.ts +55 -0
- package/package.json +18 -7
- package/prebuilds/darwin-arm64/better_sqlite3.node +0 -0
- package/prebuilds/linux-x64/better_sqlite3.node +0 -0
- package/prebuilds/win32-x64/better_sqlite3.node +0 -0
- package/skills/helios-bookkeeping/SKILL.md +321 -0
- package/skills/helios-briefer/SKILL.md +44 -0
- package/skills/helios-client-relations/SKILL.md +322 -0
- package/skills/helios-personal-triager/SKILL.md +45 -0
- package/skills/helios-recruitment/SKILL.md +317 -0
- package/skills/helios-relationship-nudger/SKILL.md +77 -0
- package/skills/helios-researcher/SKILL.md +44 -0
- package/skills/helios-scheduler/SKILL.md +58 -0
- package/skills/helios-tax-analyst/SKILL.md +280 -0
- package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -1823
|
@@ -104,6 +104,7 @@ import { recordDispatchOutcome } from '../../brainv2/channel-outcome-tracker.ts'
|
|
|
104
104
|
import { shouldInjectOracle, formatOracleInjection } from './oracle.ts';
|
|
105
105
|
import { shouldInjectHEMA } from './conditional-inject.ts';
|
|
106
106
|
import { routeSkills, formatSkillHints } from './skill-router.ts';
|
|
107
|
+
import { compressTextViaHeadroom } from './headroom-compress.ts';
|
|
107
108
|
|
|
108
109
|
// Expose skill router for cross-extension access (cortex uses this)
|
|
109
110
|
(globalThis as any).__hemaSkillRouter = { routeSkills };
|
|
@@ -682,15 +683,20 @@ export default function hemaDispatchV3(pi: any): void {
|
|
|
682
683
|
} catch (_) { process.stderr.write(`[hema:catch] fail-open: graceful degradation: ${(_ as Error)?.message || _}\n`); } // HeliosRuntime may not be initialized — fail-open
|
|
683
684
|
|
|
684
685
|
// ═══ TASK-13: Budget Pre-Admission Gate ═══
|
|
685
|
-
//
|
|
686
|
+
// MT-02: Path resolves from HELIOS_ROOT env var (required). If unset, daemon logs a startup error.
|
|
687
|
+
// budget-status.json is written by GraphSyncService at $HELIOS_ROOT/brainv2/budget-status.json.
|
|
686
688
|
// Fail-open: if file missing or unreadable, proceed normally.
|
|
687
689
|
// blocked=true → hard block, return early with ⛔ message
|
|
688
690
|
// warningActive → inject budget warning into agent context (soft signal)
|
|
689
691
|
let _budgetStatus: { blocked?: boolean; warningActive?: boolean; policies?: any[] } = {};
|
|
690
692
|
let _budgetWarning: string | null = null;
|
|
691
693
|
try {
|
|
692
|
-
const
|
|
693
|
-
if (
|
|
694
|
+
const _heliosRoot = process.env['HELIOS_ROOT'];
|
|
695
|
+
if (!_heliosRoot) {
|
|
696
|
+
process.stderr.write('[hema] HELIOS_ROOT not set — budget-status.json cannot be read. Set HELIOS_ROOT to the helios-agent repo root.\n');
|
|
697
|
+
}
|
|
698
|
+
const _budgetStatusPath = _heliosRoot ? join(_heliosRoot, 'brainv2/budget-status.json') : null;
|
|
699
|
+
if (_budgetStatusPath && fs.existsSync(_budgetStatusPath)) {
|
|
694
700
|
_budgetStatus = JSON.parse(fs.readFileSync(_budgetStatusPath, 'utf8'));
|
|
695
701
|
}
|
|
696
702
|
} catch (_budgetErr) {
|
|
@@ -1505,71 +1511,7 @@ export default function hemaDispatchV3(pi: any): void {
|
|
|
1505
1511
|
// No context available from either path
|
|
1506
1512
|
enrichedTask = taskText;
|
|
1507
1513
|
logHemaEvent('hema_no_context', { nativePacksUsed, legacyUsed: admitted.admitted?.length > 0 });
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
// ── Helios Compression: compress HEMA recall payload before injection ──
|
|
1511
|
-
// The recall context contains JSON graph payloads (leads, signals, tasks,
|
|
1512
|
-
// goals, code nodes). Send the assembled text as a tool_result block to
|
|
1513
|
-
// the compression server — it will find and compress embedded JSON arrays.
|
|
1514
|
-
//
|
|
1515
|
-
// Uses direct HTTP to HEADROOM_PROXY_URL (same pattern as context-compaction.ts)
|
|
1516
|
-
// rather than the headroom-ai npm package so this works in Pi subprocess context.
|
|
1517
|
-
// Applies to all companies: the role injection budgets enforce per-agent limits.
|
|
1518
|
-
if (enrichedTask.length > 2000) {
|
|
1519
|
-
const _hrUrl = process.env.HEADROOM_PROXY_URL;
|
|
1520
|
-
if (_hrUrl) {
|
|
1521
|
-
try {
|
|
1522
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
1523
|
-
const http = require('http');
|
|
1524
|
-
const _payload = JSON.stringify({
|
|
1525
|
-
messages: [{
|
|
1526
|
-
role: 'user',
|
|
1527
|
-
content: [{
|
|
1528
|
-
type: 'tool_result',
|
|
1529
|
-
tool_use_id: 'hema_recall',
|
|
1530
|
-
content: enrichedTask,
|
|
1531
|
-
}],
|
|
1532
|
-
}],
|
|
1533
|
-
});
|
|
1534
|
-
const _result: any = await new Promise((resolve, reject) => {
|
|
1535
|
-
const _url = new URL('/headroom/compress', _hrUrl);
|
|
1536
|
-
const _req = http.request(
|
|
1537
|
-
{
|
|
1538
|
-
hostname: _url.hostname,
|
|
1539
|
-
port: parseInt(_url.port || '8787', 10),
|
|
1540
|
-
path: '/headroom/compress',
|
|
1541
|
-
method: 'POST',
|
|
1542
|
-
headers: {
|
|
1543
|
-
'Content-Type': 'application/json',
|
|
1544
|
-
'Content-Length': Buffer.byteLength(_payload),
|
|
1545
|
-
},
|
|
1546
|
-
},
|
|
1547
|
-
(res: any) => {
|
|
1548
|
-
let body = '';
|
|
1549
|
-
res.on('data', (c: Buffer) => { body += c; });
|
|
1550
|
-
res.on('end', () => { try { resolve(JSON.parse(body)); } catch { reject(new Error('bad json')); } });
|
|
1551
|
-
res.on('error', reject);
|
|
1552
|
-
}
|
|
1553
|
-
);
|
|
1554
|
-
_req.setTimeout(3000, () => { _req.destroy(); reject(new Error('timeout')); });
|
|
1555
|
-
_req.on('error', reject);
|
|
1556
|
-
_req.write(_payload);
|
|
1557
|
-
_req.end();
|
|
1558
|
-
});
|
|
1559
|
-
|
|
1560
|
-
const _compressed = _result?.messages?.[0]?.content?.[0]?.content ?? enrichedTask;
|
|
1561
|
-
if (typeof _compressed === 'string' && _compressed.length < enrichedTask.length) {
|
|
1562
|
-
const _saved = enrichedTask.length - _compressed.length;
|
|
1563
|
-
process.stderr.write(`[hema-dispatch-v3] Headroom compressed recall context: -${_saved} chars (${agentType})\n`);
|
|
1564
|
-
enrichedTask = _compressed;
|
|
1565
|
-
logHemaEvent('hema_headroom_compressed', { saved: _saved, agentType });
|
|
1566
|
-
}
|
|
1567
|
-
} catch (_hrErr: any) {
|
|
1568
|
-
// Non-fatal: log and continue with uncompressed enrichedTask
|
|
1569
|
-
process.stderr.write(`[hema-dispatch-v3] Headroom recall compress skipped: ${_hrErr?.message}\n`);
|
|
1570
|
-
}
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1514
|
+
}
|
|
1573
1515
|
|
|
1574
1516
|
// Wire getReasoningHint for proven reasoning paths (lazy require to avoid circular import under jiti)
|
|
1575
1517
|
try {
|
|
@@ -1655,6 +1597,22 @@ export default function hemaDispatchV3(pi: any): void {
|
|
|
1655
1597
|
triggerMatrixRefresh(projectPath);
|
|
1656
1598
|
}
|
|
1657
1599
|
|
|
1600
|
+
// ── Helios Compression: compress full enrichedTask AFTER all appends ──
|
|
1601
|
+
// Fires after reasoningHint, V-Gate block, skillHints, and oracleInjection
|
|
1602
|
+
// are all appended. Compresses embedded JSON arrays (signals, leads, pipeline,
|
|
1603
|
+
// tasks) from the recall context. Prose appends pass through unchanged.
|
|
1604
|
+
// Uses compressTextViaHeadroom() — fail-open, 3s timeout, no throws.
|
|
1605
|
+
{
|
|
1606
|
+
const _hrUrl = process.env.HEADROOM_PROXY_URL;
|
|
1607
|
+
const _taskBefore = enrichedTask.length;
|
|
1608
|
+
enrichedTask = await compressTextViaHeadroom(enrichedTask, _hrUrl, 'hema_task');
|
|
1609
|
+
if (enrichedTask.length < _taskBefore) {
|
|
1610
|
+
const _saved = _taskBefore - enrichedTask.length;
|
|
1611
|
+
process.stderr.write(`[hema-dispatch-v3] Headroom compressed full task: -${_saved} chars (${agentType})\n`);
|
|
1612
|
+
logHemaEvent('hema_headroom_compressed', { saved: _saved, agentType });
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1658
1616
|
t.task = enrichedTask;
|
|
1659
1617
|
if (_budgetWarning) {
|
|
1660
1618
|
t.task = `${_budgetWarning}\n\n${t.task}`;
|
|
@@ -2676,9 +2634,10 @@ export default function hemaDispatchV3(pi: any): void {
|
|
|
2676
2634
|
} catch (_) { process.stderr.write(`[hema:catch] fail-open: ${(_ as Error)?.message || _}\n`); }
|
|
2677
2635
|
|
|
2678
2636
|
// Load hot memory — ensure file exists
|
|
2679
|
-
const
|
|
2637
|
+
const _heliosRootForMem = process.env['HELIOS_ROOT'] ?? join(homedir(), 'helios-agent');
|
|
2638
|
+
const hotMemoryPath = join(_heliosRootForMem, 'sessions', 'hot-memory.json');
|
|
2680
2639
|
if (!fs.existsSync(hotMemoryPath)) {
|
|
2681
|
-
const sessDir = join(
|
|
2640
|
+
const sessDir = join(_heliosRootForMem, 'sessions');
|
|
2682
2641
|
if (!fs.existsSync(sessDir)) fs.mkdirSync(sessDir, { recursive: true });
|
|
2683
2642
|
fs.writeFileSync(hotMemoryPath, JSON.stringify({ sessions: [] }, null, 2), 'utf8');
|
|
2684
2643
|
}
|
|
@@ -2706,7 +2665,7 @@ export default function hemaDispatchV3(pi: any): void {
|
|
|
2706
2665
|
const pendingIds = [..._pendingDispatchIds.keys()];
|
|
2707
2666
|
try {
|
|
2708
2667
|
const _walSessionId = di('sessionId') || process.pid;
|
|
2709
|
-
const walPath = join(homedir(),
|
|
2668
|
+
const walPath = join(process.env['HELIOS_ROOT'] ?? join(homedir(), 'helios-agent'), `sessions/.pending-dispatches-${_walSessionId}.json`);
|
|
2710
2669
|
fs.writeFileSync(walPath, JSON.stringify({ ids: pendingIds, timestamp: Date.now(), sessionId: di('sessionId') || 'unknown' }));
|
|
2711
2670
|
process.stderr.write(`[hema] session_shutdown: wrote ${pendingIds.length} pending dispatch ID(s) to WAL\n`);
|
|
2712
2671
|
} catch (e: any) {
|
|
@@ -3144,7 +3103,7 @@ export default function hemaDispatchV3(pi: any): void {
|
|
|
3144
3103
|
return { systemPrompt: appendDynamic(event.systemPrompt, '\n' + evalContext) };
|
|
3145
3104
|
}
|
|
3146
3105
|
|
|
3147
|
-
const hotMemoryPath = join(homedir(), 'helios-agent', 'sessions', 'hot-memory.json');
|
|
3106
|
+
const hotMemoryPath = join(process.env['HELIOS_ROOT'] ?? join(homedir(), 'helios-agent'), 'sessions', 'hot-memory.json');
|
|
3148
3107
|
|
|
3149
3108
|
let hotMemory: any = null;
|
|
3150
3109
|
if (fs.existsSync(hotMemoryPath)) {
|
|
@@ -3532,6 +3491,21 @@ export default function hemaDispatchV3(pi: any): void {
|
|
|
3532
3491
|
}
|
|
3533
3492
|
} catch { /* fail-open */ }
|
|
3534
3493
|
|
|
3494
|
+
// ── Helios Compression: compress combined system-prompt block ─────────
|
|
3495
|
+
// `combined` contains company context (JSON graph payloads: goals, tasks,
|
|
3496
|
+
// signals, pipeline), code matrix, EvidencePacks, skill hints, and mission
|
|
3497
|
+
// context. JSON arrays in company context and code matrix benefit from
|
|
3498
|
+
// SmartCrusher. Prose blocks pass through unchanged.
|
|
3499
|
+
// Uses compressTextViaHeadroom() — fail-open, 3s timeout, no throws.
|
|
3500
|
+
{
|
|
3501
|
+
const _hrUrlBAS = process.env.HEADROOM_PROXY_URL;
|
|
3502
|
+
const _combinedBefore = combined.length;
|
|
3503
|
+
combined = await compressTextViaHeadroom(combined, _hrUrlBAS, 'hema_before_agent');
|
|
3504
|
+
if (combined.length < _combinedBefore) {
|
|
3505
|
+
process.stderr.write(`[hema-dispatch-v3] Headroom compressed before_agent_start: -${_combinedBefore - combined.length} chars\n`);
|
|
3506
|
+
}
|
|
3507
|
+
}
|
|
3508
|
+
|
|
3535
3509
|
return { systemPrompt: appendDynamic(event.systemPrompt, '\n' + combined + observationsBlock) };
|
|
3536
3510
|
} catch (err) { /* fail-open: system prompt enrichment */ if (process.env.HELIOS_DEBUG) console.error(`[hema] system prompt enrichment error: ${String(err)}`); }
|
|
3537
3511
|
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* extensions/interview/__tests__/server.test.ts
|
|
3
|
+
* P3-N6: Interview server tests
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, afterAll, beforeAll } from 'vitest';
|
|
6
|
+
import * as http from 'node:http';
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import * as os from 'node:os';
|
|
10
|
+
|
|
11
|
+
const SERVER_PATH = path.resolve(__dirname, '../server.ts');
|
|
12
|
+
|
|
13
|
+
// Helper to make HTTP requests to the test server
|
|
14
|
+
function httpRequest(
|
|
15
|
+
port: number,
|
|
16
|
+
method: string,
|
|
17
|
+
urlPath: string,
|
|
18
|
+
body?: unknown
|
|
19
|
+
): Promise<{ status: number; body: unknown }> {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const data = body ? JSON.stringify(body) : undefined;
|
|
22
|
+
const req = http.request(
|
|
23
|
+
{ hostname: '127.0.0.1', port, method, path: urlPath,
|
|
24
|
+
headers: { 'Content-Type': 'application/json',
|
|
25
|
+
'Content-Length': data ? Buffer.byteLength(data) : 0 } },
|
|
26
|
+
(res) => {
|
|
27
|
+
let raw = '';
|
|
28
|
+
res.on('data', c => { raw += c; });
|
|
29
|
+
res.on('end', () => {
|
|
30
|
+
try { resolve({ status: res.statusCode ?? 0, body: JSON.parse(raw) }); }
|
|
31
|
+
catch { resolve({ status: res.statusCode ?? 0, body: raw }); }
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
req.on('error', reject);
|
|
36
|
+
if (data) req.write(data);
|
|
37
|
+
req.end();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
describe('interview/server — HTTP contract', () => {
|
|
42
|
+
let server: any = null;
|
|
43
|
+
let port = 0;
|
|
44
|
+
const tempSessionFile = path.join(os.tmpdir(), `interview-sessions-test-${Date.now()}.json`);
|
|
45
|
+
|
|
46
|
+
beforeAll(async () => {
|
|
47
|
+
if (!fs.existsSync(SERVER_PATH)) return;
|
|
48
|
+
const mod = await import('../server.ts').catch(() => null) as any;
|
|
49
|
+
if (!mod) return;
|
|
50
|
+
const createFn = mod.createServer ?? mod.default;
|
|
51
|
+
if (typeof createFn !== 'function') return;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
server = createFn({ sessionFile: tempSessionFile, port: 0 });
|
|
55
|
+
if (server && typeof server.listen === 'function') {
|
|
56
|
+
await new Promise<void>((res) => {
|
|
57
|
+
server.listen(0, '127.0.0.1', () => {
|
|
58
|
+
port = (server.address() as any)?.port ?? 0;
|
|
59
|
+
res();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
server = null;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
afterAll(async () => {
|
|
69
|
+
if (server && typeof server.close === 'function') {
|
|
70
|
+
await new Promise<void>((res) => server.close(() => res()));
|
|
71
|
+
}
|
|
72
|
+
if (fs.existsSync(tempSessionFile)) fs.unlinkSync(tempSessionFile);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('createServer returns an http.Server instance (if server starts)', () => {
|
|
76
|
+
if (!fs.existsSync(SERVER_PATH)) return;
|
|
77
|
+
if (server === null) return; // guard: module needs runtime
|
|
78
|
+
expect(server).toBeDefined();
|
|
79
|
+
expect(typeof server.listen === 'function').toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('GET /sessions returns 200 with array body', async () => {
|
|
83
|
+
if (!server || port === 0) return;
|
|
84
|
+
const { status, body } = await httpRequest(port, 'GET', '/sessions');
|
|
85
|
+
expect(status).toBe(200);
|
|
86
|
+
expect(Array.isArray(body)).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('POST /sessions creates session; GET /sessions includes it', async () => {
|
|
90
|
+
if (!server || port === 0) return;
|
|
91
|
+
const { status: createStatus, body: created } = await httpRequest(
|
|
92
|
+
port, 'POST', '/sessions', { goal: 'test interview goal', context: {} }
|
|
93
|
+
);
|
|
94
|
+
expect(createStatus).toBeLessThan(300);
|
|
95
|
+
|
|
96
|
+
const { body: list } = await httpRequest(port, 'GET', '/sessions');
|
|
97
|
+
expect(Array.isArray(list)).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('session file is written after session creation', async () => {
|
|
101
|
+
if (!server || port === 0) return;
|
|
102
|
+
await httpRequest(port, 'POST', '/sessions', { goal: 'file write test', context: {} });
|
|
103
|
+
// Give the server time to write
|
|
104
|
+
await new Promise(r => setTimeout(r, 100));
|
|
105
|
+
if (fs.existsSync(tempSessionFile)) {
|
|
106
|
+
const data = JSON.parse(fs.readFileSync(tempSessionFile, 'utf8'));
|
|
107
|
+
expect(Array.isArray(data) || typeof data === 'object').toBe(true);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('server.ts source exports startInterviewServer or createServer', () => {
|
|
112
|
+
if (!fs.existsSync(SERVER_PATH)) return;
|
|
113
|
+
const source = fs.readFileSync(SERVER_PATH, 'utf8');
|
|
114
|
+
const hasExport = source.includes('startInterviewServer') || source.includes('createServer') || source.includes('module.exports') || source.includes('export function');
|
|
115
|
+
expect(hasExport).toBe(true);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* helios-root.cjs — Cross-platform HELIOS_ROOT resolution.
|
|
3
|
+
*
|
|
4
|
+
* CommonJS module (works in both require() and jiti-transpiled CJS contexts).
|
|
5
|
+
* The extensions/ package declares "type":"commonjs" so this must use module.exports.
|
|
6
|
+
*
|
|
7
|
+
* Priority order:
|
|
8
|
+
* 1. HELIOS_ROOT env var — injected by helios-rpc.cjs at spawn time
|
|
9
|
+
* 2. HELIOS_CODING_AGENT_DIR — alternate env var used by some tools
|
|
10
|
+
* 3. ~/helios-agent — backward-compatible fallback for direct installs
|
|
11
|
+
*
|
|
12
|
+
* Works on:
|
|
13
|
+
* - Windows Desktop: C:\Users\<user>\Desktop\Helios\helios-agent-main (HELIOS_ROOT injected)
|
|
14
|
+
* - macOS/Linux: ~/helios-agent (fallback)
|
|
15
|
+
* - CI: any arbitrary checkout path (via HELIOS_ROOT env var)
|
|
16
|
+
*/
|
|
17
|
+
'use strict';
|
|
18
|
+
|
|
19
|
+
const { homedir } = require('node:os');
|
|
20
|
+
const { join } = require('node:path');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The helios-agent repo root. Set by helios-rpc.cjs as HELIOS_ROOT.
|
|
24
|
+
* Falls back to ~/helios-agent for backward compatibility.
|
|
25
|
+
*/
|
|
26
|
+
const HELIOS_ROOT =
|
|
27
|
+
process.env.HELIOS_ROOT ||
|
|
28
|
+
process.env.HELIOS_CODING_AGENT_DIR ||
|
|
29
|
+
join(homedir(), 'helios-agent');
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build an absolute path under HELIOS_ROOT.
|
|
33
|
+
* @param {...string} parts - path segments to join
|
|
34
|
+
* @returns {string} absolute path
|
|
35
|
+
* @example heliosPath('settings.json') // → /path/to/helios-agent/settings.json
|
|
36
|
+
* @example heliosPath('extensions', '.manifest.json')
|
|
37
|
+
*/
|
|
38
|
+
const heliosPath = (...parts) => join(HELIOS_ROOT, ...parts);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Cross-platform home directory (os.homedir()).
|
|
42
|
+
* Use this instead of process.env.HOME — HOME is not set on Windows.
|
|
43
|
+
*/
|
|
44
|
+
const HOME = homedir();
|
|
45
|
+
|
|
46
|
+
module.exports = { HELIOS_ROOT, heliosPath, HOME };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* extensions/subagent-mesh/__tests__/handlers.test.ts
|
|
3
|
+
* P3-N2: Subagent mesh handler tests
|
|
4
|
+
*
|
|
5
|
+
* This module requires the Pi runtime for full execution.
|
|
6
|
+
* Guards emit named warnings so gaps are visible in CI output.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
|
|
12
|
+
describe('subagent-mesh/handlers — module contract', () => {
|
|
13
|
+
it('handlers.ts exports a handler registration function', async () => {
|
|
14
|
+
const mod = await import('../handlers.ts').catch((e) => {
|
|
15
|
+
console.warn('[subagent-mesh/handlers] import failed (Pi runtime required):', String(e).slice(0, 120));
|
|
16
|
+
return null;
|
|
17
|
+
}) as any;
|
|
18
|
+
if (!mod) return;
|
|
19
|
+
const hasExport = typeof mod.registerHandlers === 'function'
|
|
20
|
+
|| typeof mod.registerToolCallHandler === 'function'
|
|
21
|
+
|| typeof mod.registerToolResultHandlers === 'function'
|
|
22
|
+
|| typeof mod.default === 'function';
|
|
23
|
+
if (!hasExport) {
|
|
24
|
+
console.warn('[subagent-mesh/handlers] no handler registration export found — check export name');
|
|
25
|
+
}
|
|
26
|
+
// Module loaded — must be an object
|
|
27
|
+
expect(typeof mod).toBe('object');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('registerHandlers called with mock Pi registers tool hooks', async () => {
|
|
31
|
+
const mod = await import('../handlers.ts').catch(() => null) as any;
|
|
32
|
+
if (!mod) {
|
|
33
|
+
console.warn('[subagent-mesh/handlers] SKIP: module unavailable — Pi runtime required');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const registerFn = mod.registerHandlers ?? mod.registerToolCallHandler;
|
|
37
|
+
if (typeof registerFn !== 'function') {
|
|
38
|
+
console.warn('[subagent-mesh/handlers] SKIP: no register function found — check export name');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const registeredHooks: string[] = [];
|
|
43
|
+
const mockPi: any = {
|
|
44
|
+
on: (event: string, _handler: unknown) => { registeredHooks.push(event); },
|
|
45
|
+
registerTool: vi.fn(),
|
|
46
|
+
log: vi.fn(),
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
expect(() => registerFn(mockPi)).not.toThrow();
|
|
50
|
+
if (registeredHooks.length === 0) {
|
|
51
|
+
console.warn('[subagent-mesh/handlers] no hooks registered — handler may need Pi session context');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
// At least one of tool_call or tool_result must be registered
|
|
55
|
+
const hasToolHook = registeredHooks.includes('tool_call') || registeredHooks.includes('tool_result');
|
|
56
|
+
expect(hasToolHook).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('Thompson sampling bridge: source has error handling around dynamic require', () => {
|
|
60
|
+
const handlersPath = path.resolve(__dirname, '../handlers.ts');
|
|
61
|
+
if (!fs.existsSync(handlersPath)) {
|
|
62
|
+
console.warn('[subagent-mesh/handlers] SKIP: handlers.ts not found at', handlersPath);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const source = fs.readFileSync(handlersPath, 'utf8');
|
|
66
|
+
const hasThompson = source.includes('thompson') || source.includes('Thompson');
|
|
67
|
+
if (!hasThompson) {
|
|
68
|
+
console.warn('[subagent-mesh/handlers] Thompson sampling not referenced — cortex bridge is absent');
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// Thompson require must be guarded
|
|
72
|
+
const hasTryCatch = source.includes('catch') || source.includes('?.') || source.includes('|| null');
|
|
73
|
+
expect(hasTryCatch).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('handlers.ts source file exists and is non-empty', () => {
|
|
77
|
+
const handlersPath = path.resolve(__dirname, '../handlers.ts');
|
|
78
|
+
if (!fs.existsSync(handlersPath)) {
|
|
79
|
+
console.warn('[subagent-mesh/handlers] SKIP: handlers.ts not found');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const stat = fs.statSync(handlersPath);
|
|
83
|
+
expect(stat.size).toBeGreaterThan(100);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('checkpoint eviction: source defines _activeCheckpoints with size limit', () => {
|
|
87
|
+
const handlersPath = path.resolve(__dirname, '../handlers.ts');
|
|
88
|
+
if (!fs.existsSync(handlersPath)) return;
|
|
89
|
+
const source = fs.readFileSync(handlersPath, 'utf8');
|
|
90
|
+
const hasCheckpoints = source.includes('_activeCheckpoints') || source.includes('activeCheckpoints');
|
|
91
|
+
if (!hasCheckpoints) {
|
|
92
|
+
console.warn('[subagent-mesh/handlers] _activeCheckpoints not found in source — eviction is unimplemented');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const hasLimit = source.includes('50') || source.includes('.size >=') || source.includes('.size>') || source.includes('.size >');
|
|
96
|
+
expect(hasLimit).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -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');
|
|
@@ -726,6 +734,24 @@ export async function runScheduledMaintenance(): Promise<void> {
|
|
|
726
734
|
} catch (bfErr: any) {
|
|
727
735
|
process.stderr.write("[warm-tick] backfill failed (non-fatal): " + (bfErr?.message || String(bfErr)) + "\n");
|
|
728
736
|
}
|
|
737
|
+
// EN3: comms-style enrichment — daily cadence
|
|
738
|
+
try {
|
|
739
|
+
const { computeAllStyles } = await import('../../lib/triage-core/mental-model/comms-style-computer.js');
|
|
740
|
+
await computeAllStyles();
|
|
741
|
+
process.stderr.write('[warm-tick] warm-tick: comms styles computed\n');
|
|
742
|
+
} catch (err) {
|
|
743
|
+
process.stderr.write(`[warm-tick] warm-tick: computeAllStyles failed: ${err}\n`);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// EN6: quality profiles — daily cadence
|
|
747
|
+
try {
|
|
748
|
+
const { computeAllQualityProfiles } = await import('../../lib/triage-core/mental-model/quality-scorer-v2.js');
|
|
749
|
+
await computeAllQualityProfiles();
|
|
750
|
+
process.stderr.write('[warm-tick] warm-tick: quality profiles computed\n');
|
|
751
|
+
} catch (err) {
|
|
752
|
+
process.stderr.write(`[warm-tick] warm-tick: computeAllQualityProfiles failed: ${err}\n`);
|
|
753
|
+
}
|
|
754
|
+
|
|
729
755
|
await budgetedYield(); // yield between cadence checks
|
|
730
756
|
|
|
731
757
|
// Every maintenance cycle: embedding catch-all (Layer 2 of 3-layer auto-embed pipeline).
|
|
@@ -1178,6 +1204,100 @@ export async function runScheduledMaintenance(): Promise<void> {
|
|
|
1178
1204
|
` roles=${report.rolesChanged} dunbar=${report.dunbarUpdated} elapsed=${report.durationMs}ms\n`
|
|
1179
1205
|
);
|
|
1180
1206
|
}
|
|
1207
|
+
|
|
1208
|
+
// SP1: Unsnooze emails past their snooze time
|
|
1209
|
+
try {
|
|
1210
|
+
const { rawRead: rRead, rawWrite: rWrite } = require('../../lib/safe-memgraph.js');
|
|
1211
|
+
const snoozed = await rRead(
|
|
1212
|
+
`MATCH (e:Email) WHERE e.snoozed = true AND e.snoozedUntil IS NOT NULL AND e.snoozedUntil < datetime() RETURN e.messageId AS mid, e.accountEmail AS acct`,
|
|
1213
|
+
{}
|
|
1214
|
+
);
|
|
1215
|
+
if (snoozed && snoozed.length > 0) {
|
|
1216
|
+
const { loadToken, refreshAccessToken } = require('../email/auth/inbox-dog.js');
|
|
1217
|
+
for (const row of snoozed) {
|
|
1218
|
+
try {
|
|
1219
|
+
const token = await loadToken(row.acct);
|
|
1220
|
+
const refreshed = token ? await refreshAccessToken(token) : null;
|
|
1221
|
+
const at = refreshed?.access_token || token?.access_token;
|
|
1222
|
+
if (at) {
|
|
1223
|
+
// Re-add INBOX, remove SNOOZED via raw Gmail API
|
|
1224
|
+
const body = JSON.stringify({ addLabelIds: ['INBOX'], removeLabelIds: ['SNOOZED'] });
|
|
1225
|
+
await new Promise<void>((resolve) => {
|
|
1226
|
+
const req = require('https').request({ hostname: 'gmail.googleapis.com', path: `/gmail/v1/users/me/messages/${encodeURIComponent(row.mid)}/modify`, method: 'POST', headers: { 'Authorization': `Bearer ${at}`, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, (r: any) => { r.resume(); r.on('end', resolve); });
|
|
1227
|
+
req.on('error', () => resolve()); req.write(body); req.end();
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
await rWrite(`MATCH (e:Email {messageId: $mid}) REMOVE e.snoozed, e.snoozedUntil`, { mid: row.mid });
|
|
1231
|
+
} catch (e: any) { console.warn(`[warm-tick] unsnooze failed for ${row.mid}: ${e.message}`); }
|
|
1232
|
+
}
|
|
1233
|
+
console.info(`[warm-tick] unsnoozed ${snoozed.length} email(s)`);
|
|
1234
|
+
}
|
|
1235
|
+
} catch (e: any) { console.warn(`[warm-tick] SP1 unsnooze error: ${e.message}`); }
|
|
1236
|
+
|
|
1237
|
+
// SP2: Execute due scheduled sends
|
|
1238
|
+
try {
|
|
1239
|
+
const scheduledSends = require('../../lib/triage-core/scheduled-sends.js');
|
|
1240
|
+
const due = scheduledSends.getDueMessages();
|
|
1241
|
+
if (due && due.length > 0) {
|
|
1242
|
+
// Load GmailProvider for each unique account
|
|
1243
|
+
for (const msg of due) {
|
|
1244
|
+
try {
|
|
1245
|
+
const { loadToken, refreshAccessToken } = require('../email/auth/inbox-dog.js');
|
|
1246
|
+
const token = await loadToken(msg.accountEmail || ''); // H1/M3: use accountEmail, not msg.to
|
|
1247
|
+
const refreshed = token ? await refreshAccessToken(token) : null;
|
|
1248
|
+
const at = refreshed?.access_token || token?.access_token;
|
|
1249
|
+
if (at && msg.draftId) {
|
|
1250
|
+
const body = JSON.stringify({ id: msg.draftId });
|
|
1251
|
+
await new Promise<void>((resolve) => {
|
|
1252
|
+
const req = require('https').request({ hostname: 'gmail.googleapis.com', path: '/gmail/v1/users/me/drafts/send', method: 'POST', headers: { 'Authorization': `Bearer ${at}`, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, (r: any) => { r.resume(); r.on('end', resolve); });
|
|
1253
|
+
req.on('error', () => resolve()); req.write(body); req.end();
|
|
1254
|
+
});
|
|
1255
|
+
scheduledSends.markSent(msg.id);
|
|
1256
|
+
console.info(`[warm-tick] SP2 fired scheduled send ${msg.id}`);
|
|
1257
|
+
} else {
|
|
1258
|
+
scheduledSends.markSent(msg.id);
|
|
1259
|
+
console.info(`[warm-tick] SP2 fired scheduled send ${msg.id}`);
|
|
1260
|
+
}
|
|
1261
|
+
} catch (e: any) {
|
|
1262
|
+
console.warn(`[warm-tick] SP2 scheduled send failed for ${msg.id}: ${e.message}`);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
} catch (e: any) { console.warn(`[warm-tick] SP2 scheduled sends error: ${e.message}`); }
|
|
1267
|
+
|
|
1268
|
+
// SP5: Auto-trash emails from blocked senders
|
|
1269
|
+
try {
|
|
1270
|
+
const { rawRead: rRead2, rawWrite: rWrite2 } = require('../../lib/safe-memgraph.js');
|
|
1271
|
+
const blocked = await rRead2(`MATCH (bs:BlockedSender) RETURN bs.email AS email`, {});
|
|
1272
|
+
if (blocked && blocked.length > 0) {
|
|
1273
|
+
const blockedEmails = blocked.map((r: any) => r.email);
|
|
1274
|
+
const newEmails = await rRead2(
|
|
1275
|
+
`MATCH (e:Email) WHERE e.from IN $emails AND NOT 'TRASH' IN e.labels AND e.snoozed IS NULL RETURN e.messageId AS mid, e.from AS from LIMIT 50`,
|
|
1276
|
+
{ emails: blockedEmails }
|
|
1277
|
+
);
|
|
1278
|
+
if (newEmails && newEmails.length > 0) {
|
|
1279
|
+
// M3: load the authenticated account token (not the blocked sender's email)
|
|
1280
|
+
const { loadToken: _loadDefaultToken, refreshAccessToken: _refreshDefault } = require('../email/auth/inbox-dog.js');
|
|
1281
|
+
const _defaultToken = await _loadDefaultToken(null).catch(() => null);
|
|
1282
|
+
const _refreshed = _defaultToken ? await _refreshDefault(_defaultToken).catch(() => null) : null;
|
|
1283
|
+
const _defaultAt = _refreshed?.access_token || _defaultToken?.access_token;
|
|
1284
|
+
for (const row of newEmails) {
|
|
1285
|
+
try {
|
|
1286
|
+
const body = JSON.stringify({ addLabelIds: ['TRASH'], removeLabelIds: ['INBOX', 'UNREAD'] });
|
|
1287
|
+
const at = _defaultAt;
|
|
1288
|
+
if (at) {
|
|
1289
|
+
await new Promise<void>((resolve) => {
|
|
1290
|
+
const req = require('https').request({ hostname: 'gmail.googleapis.com', path: `/gmail/v1/users/me/messages/${encodeURIComponent(row.mid)}/modify`, method: 'POST', headers: { 'Authorization': `Bearer ${at}`, 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, (r: any) => { r.resume(); r.on('end', resolve); });
|
|
1291
|
+
req.on('error', () => resolve()); req.write(body); req.end();
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
await rWrite2(`MATCH (e:Email {messageId: $mid}) SET e.labels = e.labels + ['TRASH']`, { mid: row.mid });
|
|
1295
|
+
} catch (e: any) { console.warn(`[warm-tick] SP5 block_sender trash failed for ${row.mid}: ${e.message}`); }
|
|
1296
|
+
}
|
|
1297
|
+
console.info(`[warm-tick] SP5 auto-trashed ${newEmails.length} email(s) from blocked senders`);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
} catch (e: any) { console.warn(`[warm-tick] SP5 block sender error: ${e.message}`); }
|
|
1181
1301
|
} catch (err: any) {
|
|
1182
1302
|
console.debug('[warm-tick] mental-model maintenance failed (non-fatal):', err?.message || String(err));
|
|
1183
1303
|
} finally {
|
|
@@ -1296,6 +1416,50 @@ export async function runScheduledMaintenance(): Promise<void> {
|
|
|
1296
1416
|
const counts: Record<string, number> = {};
|
|
1297
1417
|
for (const r of trajResults) counts[r.direction] = (counts[r.direction] || 0) + 1;
|
|
1298
1418
|
process.stderr.write(`[warm-tick] trajectory-detection: ${trajResults.length} contacts, distribution=${JSON.stringify(counts)}\n`);
|
|
1419
|
+
|
|
1420
|
+
// EN5: Snapshot prevCompositeStrength before trajectory overwrites it
|
|
1421
|
+
try {
|
|
1422
|
+
const { rawWrite: _rawWrite } = require('../../lib/safe-memgraph.js');
|
|
1423
|
+
await _rawWrite(
|
|
1424
|
+
"MATCH ()-[k:KNOWS]->() WHERE k.compositeStrength IS NOT NULL SET k.prevCompositeStrength = k.compositeStrength",
|
|
1425
|
+
{}
|
|
1426
|
+
);
|
|
1427
|
+
} catch (err) {
|
|
1428
|
+
process.stderr.write(`[warm-tick] warm-tick: prevCompositeStrength snapshot failed: ${err}\n`);
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
// EN1b: computeTrajectories (trajectoryVelocity) — weekly
|
|
1432
|
+
try {
|
|
1433
|
+
const { computeTrajectories } = await import('../../lib/triage-core/mental-model/strength-tracker.js');
|
|
1434
|
+
await computeTrajectories();
|
|
1435
|
+
process.stderr.write('[warm-tick] warm-tick: trajectoryVelocity computed\n');
|
|
1436
|
+
} catch (err) {
|
|
1437
|
+
process.stderr.write(`[warm-tick] warm-tick: computeTrajectories failed: ${err}\n`);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// EN2: clusteringCoefficient — weekly MAGE call
|
|
1441
|
+
try {
|
|
1442
|
+
const { rawWrite: _rawWrite } = require('../../lib/safe-memgraph.js');
|
|
1443
|
+
await _rawWrite(
|
|
1444
|
+
'MATCH (p:Person) CALL local_clustering_coefficient.get() YIELD node, clustering_coefficient WHERE node = p SET p.clusteringCoefficient = clustering_coefficient',
|
|
1445
|
+
{}
|
|
1446
|
+
);
|
|
1447
|
+
process.stderr.write('[warm-tick] warm-tick: clusteringCoefficient computed\n');
|
|
1448
|
+
} catch (err) {
|
|
1449
|
+
process.stderr.write(`[warm-tick] warm-tick: local_clustering_coefficient unavailable: ${err}\n`);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
// 7D: computeGraphRanksSQL — PageRank + Louvain on SQLite graph (weekly, non-blocking)
|
|
1453
|
+
try {
|
|
1454
|
+
const { computeGraphRanksSQL } = await import('../../lib/triage-core/graph/graph-rank-sql.js');
|
|
1455
|
+
const rankResult = await Promise.race([
|
|
1456
|
+
computeGraphRanksSQL(),
|
|
1457
|
+
new Promise<never>((_, reject) => trackTimer(setTimeout(() => reject(new Error('computeGraphRanksSQL timeout (10min)')), 600000))),
|
|
1458
|
+
]);
|
|
1459
|
+
process.stderr.write(`[warm-tick] computeGraphRanksSQL: personCount=${rankResult.personCount} edgeCount=${rankResult.edgeCount}\n`);
|
|
1460
|
+
} catch (err: any) {
|
|
1461
|
+
process.stderr.write(`[warm-tick] computeGraphRanksSQL failed (non-fatal): ${err?.message || err}\n`);
|
|
1462
|
+
}
|
|
1299
1463
|
} catch (err: any) {
|
|
1300
1464
|
console.debug('[warm-tick] favee-snapshots/trajectory failed (non-fatal):', err?.message || String(err));
|
|
1301
1465
|
} finally {
|