@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.
- 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/adapters/tui_wakeup.js +8 -0
- 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/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 +723 -57
- package/daemon/helios-company-daemon.js +616 -134
- 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 +32 -13
- package/daemon/lib/hed-engine.js +10 -292
- 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/lib/task-completion-processor.js +11 -0
- package/daemon/lib/wizard-engine.js +57 -6
- package/daemon/routes/channels.js +10 -5
- package/daemon/routes/harada-map.js +11 -48
- package/daemon/routes/hbo.js +342 -75
- package/daemon/routes/hitl.js +0 -0
- package/daemon/routes/project.js +194 -62
- package/daemon/routes/routines.js +14 -0
- package/daemon/routes/tasks.js +15 -1
- package/daemon/routes/wizard.js +11 -4
- package/daemon/schema-apply.js +174 -0
- package/daemon/schema-definitions.js +423 -0
- package/daemon/schema-migrations-hbo.js +10 -0
- package/daemon/schema-migrations-hed.js +18 -0
- package/daemon/schema-migrations-hitl.js +0 -0
- package/daemon/schema-migrations-proj.js +131 -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/cortex/wal-replay.ts +91 -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 +72 -47
- 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/extensions/warm-tick/warm-tick-maintenance.ts +8 -0
- package/lib/__tests__/hbo-core-store.test.js +238 -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/event-bus.mts +1 -1
- 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/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 +908 -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/classifier.ts +3 -2
- package/lib/triage-core/graph/schema.cypher +10 -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 +1 -2
- package/lib/triage-core/mental-model/model-assembler.ts +0 -0
- package/lib/triage-core/orchestrator.ts +4 -11
- package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -0
- package/package.json +18 -8
- 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/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/daemon/routes/hbo.js
CHANGED
|
@@ -14,6 +14,7 @@ const { randomUUID } = crypto;
|
|
|
14
14
|
const { HELIOS_ROOT } = require('../lib/paths');
|
|
15
15
|
const { CompanyBeliefService } = require('../lib/company-belief-service');
|
|
16
16
|
const { CompanyBeliefDiscovery } = require('../lib/company-belief-discovery');
|
|
17
|
+
const hboStore = require('../../lib/hbo-core-store');
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
const HBO_ROOT = path.resolve(__dirname, '../../skills/helios-business-operator');
|
|
@@ -31,7 +32,7 @@ function getCorsHeaders(req) {
|
|
|
31
32
|
const allowedOrigin = origin && HBO_ALLOWED_ORIGINS.has(origin) ? origin : 'http://localhost:9093';
|
|
32
33
|
return {
|
|
33
34
|
'Access-Control-Allow-Origin': allowedOrigin,
|
|
34
|
-
'Access-Control-Allow-Methods': 'GET, POST, PATCH, OPTIONS',
|
|
35
|
+
'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS',
|
|
35
36
|
'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization',
|
|
36
37
|
'Vary': 'Origin',
|
|
37
38
|
};
|
|
@@ -430,28 +431,37 @@ async function handleTransitionBusinessTask(req, res, ctx, taskId) {
|
|
|
430
431
|
const taskService = loadHBOService('lib/business-task-service.js');
|
|
431
432
|
if (taskService && typeof taskService.transition === 'function') {
|
|
432
433
|
try {
|
|
433
|
-
const task = await Promise.race([
|
|
434
|
-
taskService.transition(taskId, status, cid),
|
|
435
|
-
new Promise((_, reject) =>
|
|
436
|
-
setTimeout(() => reject(new Error('task-service timeout — falling back to direct')), 5000)
|
|
437
|
-
),
|
|
438
|
-
]);
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
434
|
+
const task = await Promise.race([
|
|
435
|
+
taskService.transition(taskId, status, cid),
|
|
436
|
+
new Promise((_, reject) =>
|
|
437
|
+
setTimeout(() => reject(new Error('task-service timeout — falling back to direct')), 5000)
|
|
438
|
+
),
|
|
439
|
+
]);
|
|
440
|
+
if (task !== null && task !== undefined) {
|
|
441
|
+
jsonOk(res, { task });
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
// Service did not find a task for this company; fall through to the
|
|
445
|
+
// direct path so we can return a clear 404 instead of a silent success.
|
|
446
|
+
} catch (_svcErr) {
|
|
447
|
+
// service failed for any reason — fall through to direct Memgraph path below
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// Fallback: update directly via ctx.mgQuery
|
|
452
|
+
const now = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
|
|
453
|
+
const result = await mgQuery(
|
|
454
|
+
`MATCH (bt:BusinessTask {id: $id, companyId: $cid})
|
|
455
|
+
SET bt.status = $status, bt.updatedAt = datetime($now)
|
|
456
|
+
RETURN bt.id AS id`,
|
|
457
|
+
{ id: taskId, cid, status, now }
|
|
458
|
+
);
|
|
459
|
+
if (parseRows(result).length === 0) {
|
|
460
|
+
jsonErr(res, 404, `BusinessTask ${taskId} not found for company ${cid}`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
_bc({ type: 'task.updated', taskId, companyId: cid, status });
|
|
464
|
+
jsonOk(res, { updated: true, taskId, status });
|
|
455
465
|
} catch (e) {
|
|
456
466
|
jsonErr(res, 500, `Transition BusinessTask failed: ${safeErrMsg(e)}`);
|
|
457
467
|
}
|
|
@@ -461,34 +471,199 @@ async function handleTransitionBusinessTask(req, res, ctx, taskId) {
|
|
|
461
471
|
|
|
462
472
|
async function handleGetBusinessGoals(req, res, ctx) {
|
|
463
473
|
const { mgQuery, cid } = ctx;
|
|
464
|
-
|
|
474
|
+
// SQL-P1: SQLite fallback when Memgraph is unavailable — not all users have Memgraph.
|
|
475
|
+
// Try Memgraph first; on failure (no connection or query error), fall through to SQLite.
|
|
476
|
+
if (mgQuery) {
|
|
477
|
+
try {
|
|
478
|
+
// Get all BusinessGoal nodes
|
|
479
|
+
// BUG-12 fix: BusinessGoal relationships are stored as (child)-[:CHILD_OF]->(parent).
|
|
480
|
+
const result = await mgQuery(
|
|
481
|
+
`MATCH (bg:BusinessGoal {companyId: $cid})
|
|
482
|
+
OPTIONAL MATCH (child:BusinessGoal)-[:CHILD_OF]->(bg)
|
|
483
|
+
WITH bg, [x IN collect(child.id) WHERE x IS NOT NULL] AS childIds
|
|
484
|
+
RETURN bg.id AS id, bg.title AS title, bg.level AS level,
|
|
485
|
+
bg.ownerAgentId AS ownerAgentId, bg.status AS status,
|
|
486
|
+
bg.progress AS progress, childIds
|
|
487
|
+
ORDER BY bg.level ASC`,
|
|
488
|
+
{ cid }
|
|
489
|
+
);
|
|
490
|
+
const rows = parseRows(result);
|
|
491
|
+
const keys = ['id', 'title', 'level', 'ownerAgentId', 'status', 'progress', 'childIds'];
|
|
492
|
+
const goals = rows.map(r => {
|
|
493
|
+
const goal = rowToObj(r, keys);
|
|
494
|
+
if (goal.level !== null && goal.level !== undefined) goal.level = String(goal.level);
|
|
495
|
+
return goal;
|
|
496
|
+
});
|
|
497
|
+
const rootGoals = goals.filter(g => !g.level || String(g.level) === 'company');
|
|
498
|
+
return jsonOk(res, { goals, rootGoals, count: goals.length });
|
|
499
|
+
} catch (e) {
|
|
500
|
+
// Memgraph query failed — fall through to SQLite fallback below
|
|
501
|
+
process.stderr.write(`[hbo] handleGetBusinessGoals Memgraph failed, falling back to SQLite: ${e.message}\n`);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
// SQLite fallback (Memgraph unavailable or query failed)
|
|
465
505
|
try {
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
506
|
+
const storeGoals = hboStore.getGoalsByCompany ? hboStore.getGoalsByCompany(cid) : [];
|
|
507
|
+
const goals = (storeGoals || []).map(g => ({
|
|
508
|
+
id: g.id ?? g.id,
|
|
509
|
+
title: g.title ?? null,
|
|
510
|
+
level: g.level ? String(g.level) : null,
|
|
511
|
+
ownerAgentId: g.ownerAgentId ?? null,
|
|
512
|
+
status: g.status ?? null,
|
|
513
|
+
progress: g.progress ?? null,
|
|
514
|
+
childIds: g.childIds ?? [],
|
|
515
|
+
}));
|
|
516
|
+
const rootGoals = goals.filter(g => !g.level || g.level === 'company');
|
|
517
|
+
return jsonOk(res, { goals, rootGoals, count: goals.length, _source: 'sqlite' });
|
|
518
|
+
} catch (storeErr) {
|
|
519
|
+
return jsonErr(res, 503, `Goals unavailable: Memgraph not connected and SQLite fallback failed: ${storeErr.message}`);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function handleGetBusinessGoal(req, res, ctx, goalId) {
|
|
524
|
+
const { mgQuery, cid } = ctx;
|
|
525
|
+
if (!goalId) { jsonErr(res, 400, 'Missing goal ID'); return; }
|
|
526
|
+
// SQL-P2: SQLite fallback when Memgraph unavailable.
|
|
527
|
+
if (mgQuery) {
|
|
528
|
+
try {
|
|
529
|
+
const result = await mgQuery(
|
|
530
|
+
`MATCH (g:BusinessGoal {id: $id, companyId: $cid})
|
|
531
|
+
RETURN g.id AS id, g.title AS title, g.description AS description,
|
|
532
|
+
g.level AS level, g.status AS status, g.parentId AS parentId,
|
|
533
|
+
g.ownerAgentId AS ownerAgentId, g.progress AS progress`,
|
|
534
|
+
{ id: goalId, cid }
|
|
535
|
+
);
|
|
536
|
+
const rows = parseRows(result);
|
|
537
|
+
if (rows.length === 0) { jsonErr(res, 404, `BusinessGoal ${goalId} not found`); return; }
|
|
538
|
+
const keys = ['id', 'title', 'description', 'level', 'status', 'parentId', 'ownerAgentId', 'progress'];
|
|
539
|
+
return jsonOk(res, { goal: rowToObj(rows[0], keys) });
|
|
540
|
+
} catch (e) {
|
|
541
|
+
process.stderr.write(`[hbo] handleGetBusinessGoal Memgraph failed, falling back to SQLite: ${e.message}\n`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
// SQLite fallback
|
|
545
|
+
try {
|
|
546
|
+
const g = hboStore.getGoal ? hboStore.getGoal(goalId, cid) : null;
|
|
547
|
+
if (!g) { jsonErr(res, 404, `BusinessGoal ${goalId} not found`); return; }
|
|
548
|
+
return jsonOk(res, { goal: g, _source: 'sqlite' });
|
|
549
|
+
} catch (storeErr) {
|
|
550
|
+
return jsonErr(res, 503, `Goal unavailable: ${storeErr.message}`);
|
|
489
551
|
}
|
|
490
552
|
}
|
|
491
553
|
|
|
554
|
+
async function handleCreateBusinessGoal(req, res, ctx) {
|
|
555
|
+
const { mgQuery, cid } = ctx;
|
|
556
|
+
if (!mgQuery) { jsonErr(res, 503, 'Memgraph not connected'); return; }
|
|
557
|
+
// H-4 fix: reject if cid is missing or is the fallback sentinel — prevents silent writes to 'default-company'
|
|
558
|
+
if (!cid || cid === 'default-company') { jsonErr(res, 400, 'companyId is required'); return; }
|
|
559
|
+
try {
|
|
560
|
+
const body = await readBody(req);
|
|
561
|
+
if (!assertValidBody(body, res)) return;
|
|
562
|
+
const title = typeof body.title === 'string' ? body.title.trim() : '';
|
|
563
|
+
if (!title) { jsonErr(res, 400, 'title is required'); return; }
|
|
564
|
+
|
|
565
|
+
const goalId = body.id ? String(body.id) : `bg:${randomUUID()}`;
|
|
566
|
+
const description = body.description !== undefined ? String(body.description) : null;
|
|
567
|
+
const level = body.level !== undefined ? String(body.level) : 'company';
|
|
568
|
+
const parentId = body.parentId || body.parentGoalId ? String(body.parentId || body.parentGoalId) : null;
|
|
569
|
+
|
|
570
|
+
await mgQuery(
|
|
571
|
+
`CREATE (g:BusinessGoal {
|
|
572
|
+
id: $id, companyId: $cid, title: $title, description: $desc,
|
|
573
|
+
level: $level, status: 'active', parentId: $parentId,
|
|
574
|
+
parentGoalId: $parentId, createdAt: datetime(), updatedAt: datetime()
|
|
575
|
+
})`,
|
|
576
|
+
{ id: goalId, cid, title, desc: description, level, parentId }
|
|
577
|
+
);
|
|
578
|
+
try {
|
|
579
|
+
hboStore.createGoal?.({ id: goalId, companyId: cid, title, description, level, status: 'active', parentId, createdAt: Date.now() });
|
|
580
|
+
} catch (storeErr) {
|
|
581
|
+
process.stderr.write(`[hbo] goal mirror failed: ${storeErr.message}\n`);
|
|
582
|
+
}
|
|
583
|
+
try { ctx._bc?.({ type: 'goal.created', companyId: cid, goalId }); } catch (_) {}
|
|
584
|
+
jsonOk(res, { goal: { id: goalId, companyId: cid, title, description, level, status: 'active', parentId } }, 201);
|
|
585
|
+
} catch (e) {
|
|
586
|
+
jsonErr(res, 500, `Create BusinessGoal failed: ${safeErrMsg(e)}`);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function handleUpdateBusinessGoal(req, res, ctx, goalId) {
|
|
591
|
+
const { mgQuery, cid } = ctx;
|
|
592
|
+
if (!mgQuery) { jsonErr(res, 503, 'Memgraph not connected'); return; }
|
|
593
|
+
if (!goalId) { jsonErr(res, 400, 'Missing goal ID'); return; }
|
|
594
|
+
try {
|
|
595
|
+
const body = await readBody(req);
|
|
596
|
+
if (!assertValidBody(body, res)) return;
|
|
597
|
+
const title = body.title !== undefined ? String(body.title).trim() : null;
|
|
598
|
+
const description = body.description !== undefined ? String(body.description) : null;
|
|
599
|
+
const level = body.level !== undefined ? String(body.level) : null;
|
|
600
|
+
const status = body.status !== undefined ? String(body.status) : null;
|
|
601
|
+
const parentId = body.parentId !== undefined ? (body.parentId ? String(body.parentId) : null) : undefined;
|
|
602
|
+
|
|
603
|
+
if (title === '' || (title === null && description === null && level === null && status === null && parentId === undefined)) {
|
|
604
|
+
jsonErr(res, 400, 'At least one of title, description, level, status, parentId must be provided');
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
const result = await mgQuery(
|
|
609
|
+
`MATCH (g:BusinessGoal {id: $id, companyId: $cid})
|
|
610
|
+
SET g.title = COALESCE($title, g.title),
|
|
611
|
+
g.description = CASE WHEN $hasDescription THEN $description ELSE g.description END,
|
|
612
|
+
g.level = COALESCE($level, g.level),
|
|
613
|
+
g.status = COALESCE($status, g.status),
|
|
614
|
+
g.parentId = CASE WHEN $hasParentId THEN $parentId ELSE g.parentId END,
|
|
615
|
+
g.parentGoalId = CASE WHEN $hasParentId THEN $parentId ELSE g.parentGoalId END,
|
|
616
|
+
g.updatedAt = datetime()
|
|
617
|
+
RETURN g.id AS id, g.title AS title, g.description AS description,
|
|
618
|
+
g.level AS level, g.status AS status, g.parentId AS parentId`,
|
|
619
|
+
{
|
|
620
|
+
id: goalId, cid, title, description, level, status,
|
|
621
|
+
parentId: parentId === undefined ? null : parentId,
|
|
622
|
+
hasDescription: body.description !== undefined,
|
|
623
|
+
hasParentId: parentId !== undefined,
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
const rows = parseRows(result);
|
|
627
|
+
if (rows.length === 0) { jsonErr(res, 404, `BusinessGoal ${goalId} not found`); return; }
|
|
628
|
+
const goal = rowToObj(rows[0], ['id', 'title', 'description', 'level', 'status', 'parentId']);
|
|
629
|
+
// H-3 fix: only pass fields that were explicitly provided in the PATCH body to updateGoal.
|
|
630
|
+
// rowToObj fills missing Memgraph fields with null — passing null for e.g. title would
|
|
631
|
+
// overwrite the existing SQLite title with null on a status-only update.
|
|
632
|
+
const storeUpdate: Record<string, any> = { id: goalId };
|
|
633
|
+
if (title !== null) storeUpdate.title = goal.title;
|
|
634
|
+
if (description !== null) storeUpdate.description = goal.description;
|
|
635
|
+
if (level !== null) storeUpdate.level = goal.level;
|
|
636
|
+
if (status !== null) storeUpdate.status = goal.status;
|
|
637
|
+
if (parentId !== undefined) storeUpdate.parentId = goal.parentId;
|
|
638
|
+
try { hboStore.updateGoal?.(goalId, cid, storeUpdate); } catch (storeErr) { process.stderr.write(`[hbo] goal mirror failed: ${storeErr.message}\n`); }
|
|
639
|
+
try { ctx._bc?.({ type: 'goal.updated', companyId: cid, goalId }); } catch (_) {}
|
|
640
|
+
jsonOk(res, { goal });
|
|
641
|
+
} catch (e) {
|
|
642
|
+
jsonErr(res, 500, `Update BusinessGoal failed: ${safeErrMsg(e)}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
async function handleDeleteBusinessGoal(req, res, ctx, goalId) {
|
|
647
|
+
const { mgQuery, cid } = ctx;
|
|
648
|
+
if (!mgQuery) { jsonErr(res, 503, 'Memgraph not connected'); return; }
|
|
649
|
+
if (!goalId) { jsonErr(res, 400, 'Missing goal ID'); return; }
|
|
650
|
+
try {
|
|
651
|
+
const result = await mgQuery(
|
|
652
|
+
`MATCH (g:BusinessGoal {id: $id, companyId: $cid})
|
|
653
|
+
WITH g, g.id AS deletedId
|
|
654
|
+
DETACH DELETE g
|
|
655
|
+
RETURN deletedId AS id`,
|
|
656
|
+
{ id: goalId, cid }
|
|
657
|
+
);
|
|
658
|
+
if (parseRows(result).length === 0) { jsonErr(res, 404, `BusinessGoal ${goalId} not found`); return; }
|
|
659
|
+
try { hboStore.deleteGoal?.(goalId, cid); } catch (storeErr) { process.stderr.write(`[hbo] goal mirror failed: ${storeErr.message}\n`); }
|
|
660
|
+
try { ctx._bc?.({ type: 'goal.deleted', companyId: cid, goalId }); } catch (_) {}
|
|
661
|
+
jsonOk(res, { deleted: true, goalId });
|
|
662
|
+
} catch (e) {
|
|
663
|
+
jsonErr(res, 500, `Delete BusinessGoal failed: ${safeErrMsg(e)}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
492
667
|
// ── Warm signals handler ──────────────────────────────────────────────────────
|
|
493
668
|
|
|
494
669
|
async function handleGetWarmSignals(req, res, ctx) {
|
|
@@ -648,20 +823,42 @@ async function handleGetMandala(req, res, ctx) {
|
|
|
648
823
|
// Build the pillar MATCH differently depending on whether goalId is provided.
|
|
649
824
|
// Memgraph requires extracting all properties into aliases BEFORE OPTIONAL MATCH;
|
|
650
825
|
// referencing gp.property AFTER OPTIONAL MATCH causes "Unbound variable: gp."
|
|
651
|
-
const pillarQuery = goalId
|
|
652
|
-
? `MATCH (gp:GoalPillar {companyId: $cid, goalId: $goalId})
|
|
653
|
-
WITH gp.id AS id, gp.name AS name, gp.pillarIndex AS pillarIndex,
|
|
654
|
-
gp.goalId AS goalId, gp.description AS description,
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
826
|
+
const pillarQuery = goalId
|
|
827
|
+
? `MATCH (gp:GoalPillar {companyId: $cid, goalId: $goalId})
|
|
828
|
+
WITH gp.id AS id, gp.name AS name, gp.pillarIndex AS pillarIndex,
|
|
829
|
+
gp.goalId AS goalId, gp.description AS description,
|
|
830
|
+
coalesce(gp.l2ReviewStatus, null) AS l2ReviewStatus,
|
|
831
|
+
coalesce(gp.l2Strategy, null) AS l2Strategy,
|
|
832
|
+
gp AS gpNode
|
|
833
|
+
OPTIONAL MATCH (gpNode)-[:HAS_CELL]->(ac:ActionCell)
|
|
834
|
+
OPTIONAL MATCH (gpNode)<-[:FOR_PILLAR]-(approval:Approval)
|
|
835
|
+
WHERE approval.type IN ['harada_l2_review','harada_l3_review'] AND approval.status = 'pending'
|
|
836
|
+
WITH id, name, pillarIndex, goalId, description, l2ReviewStatus, l2Strategy, gpNode,
|
|
837
|
+
collect(DISTINCT {id: ac.id, description: ac.description, status: ac.status, cellIndex: ac.cellIndex}) AS cells,
|
|
838
|
+
count(DISTINCT approval) AS openReviewCount
|
|
839
|
+
WITH id, name, pillarIndex, goalId, description, l2ReviewStatus, l2Strategy, cells, openReviewCount,
|
|
840
|
+
size(cells) AS cellCount,
|
|
841
|
+
reduce(acc = 0, c IN cells | acc + CASE WHEN c.status = 'closed' THEN 1 ELSE 0 END) AS closedCellCount
|
|
842
|
+
RETURN id, name, pillarIndex, goalId, description, l2ReviewStatus, l2Strategy,
|
|
843
|
+
cells, openReviewCount, cellCount, closedCellCount
|
|
844
|
+
ORDER BY pillarIndex ASC LIMIT toInteger(8)`
|
|
845
|
+
: `MATCH (gp:GoalPillar {companyId: $cid})
|
|
846
|
+
WITH gp.id AS id, gp.name AS name, gp.pillarIndex AS pillarIndex,
|
|
847
|
+
gp.goalId AS goalId, gp.description AS description,
|
|
848
|
+
coalesce(gp.l2ReviewStatus, null) AS l2ReviewStatus,
|
|
849
|
+
coalesce(gp.l2Strategy, null) AS l2Strategy,
|
|
850
|
+
gp AS gpNode
|
|
851
|
+
OPTIONAL MATCH (gpNode)-[:HAS_CELL]->(ac:ActionCell)
|
|
852
|
+
OPTIONAL MATCH (gpNode)<-[:FOR_PILLAR]-(approval:Approval)
|
|
853
|
+
WHERE approval.type IN ['harada_l2_review','harada_l3_review'] AND approval.status = 'pending'
|
|
854
|
+
WITH id, name, pillarIndex, goalId, description, l2ReviewStatus, l2Strategy, gpNode,
|
|
855
|
+
collect(DISTINCT {id: ac.id, description: ac.description, status: ac.status, cellIndex: ac.cellIndex}) AS cells,
|
|
856
|
+
count(DISTINCT approval) AS openReviewCount
|
|
857
|
+
WITH id, name, pillarIndex, goalId, description, l2ReviewStatus, l2Strategy, cells, openReviewCount,
|
|
858
|
+
size(cells) AS cellCount,
|
|
859
|
+
reduce(acc = 0, c IN cells | acc + CASE WHEN c.status = 'closed' THEN 1 ELSE 0 END) AS closedCellCount
|
|
860
|
+
RETURN id, name, pillarIndex, goalId, description, l2ReviewStatus, l2Strategy,
|
|
861
|
+
cells, openReviewCount, cellCount, closedCellCount
|
|
665
862
|
ORDER BY pillarIndex ASC LIMIT toInteger(8)`;
|
|
666
863
|
|
|
667
864
|
const saQuery = agentId
|
|
@@ -715,7 +912,20 @@ async function handleGetMandala(req, res, ctx) {
|
|
|
715
912
|
mgQuery(deptAgentQuery, { cid }).catch(() => ({ rows: [], keys: [] })),
|
|
716
913
|
]);
|
|
717
914
|
|
|
718
|
-
|
|
915
|
+
// Map pillar names to department slugs — GoalPillar nodes never have a .department
|
|
916
|
+
// property written to them, so we derive the slug from the pillar's canonical name.
|
|
917
|
+
const PILLAR_NAME_TO_DEPT_SLUG = {
|
|
918
|
+
'Revenue & Sales': 'sales',
|
|
919
|
+
'Product & Delivery': 'engineering',
|
|
920
|
+
'Customer Success': 'customer_success',
|
|
921
|
+
'Technology & Infrastructure': 'engineering',
|
|
922
|
+
'Team & Capability': 'people',
|
|
923
|
+
'Brand & Positioning': 'marketing',
|
|
924
|
+
'Finance & Sustainability': 'finance',
|
|
925
|
+
'Operations & Process': 'operations',
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const pillarKeys = ['id', 'name', 'pillarIndex', 'goalId', 'description', 'l2ReviewStatus', 'l2Strategy', 'cells', 'openReviewCount', 'cellCount', 'closedCellCount'];
|
|
719
929
|
const saKeys = ['id', 'agentId', 'strengths', 'weaknesses', 'opportunities', 'createdAt'];
|
|
720
930
|
const cascadeKeys = ['id', 'agentId', 'weekNumber', 'weeklyGoal', 'dailyTasks', 'completionRate'];
|
|
721
931
|
const deptAgentKeys = ['id', 'role', 'departmentId', 'status', 'currentTaskId', 'currentTask', 'taskStartedAt'];
|
|
@@ -733,7 +943,13 @@ async function handleGetMandala(req, res, ctx) {
|
|
|
733
943
|
});
|
|
734
944
|
|
|
735
945
|
jsonOk(res, {
|
|
736
|
-
pillars: parseRows(pillarResult).map(r =>
|
|
946
|
+
pillars: parseRows(pillarResult).map(r => {
|
|
947
|
+
const p = rowToObj(r, pillarKeys);
|
|
948
|
+
// Derive department slug from pillar name so the frontend can build dept nav items.
|
|
949
|
+
// GoalPillar.department is indexed in the schema but never written by any daemon path.
|
|
950
|
+
p.department = PILLAR_NAME_TO_DEPT_SLUG[p.name] || null;
|
|
951
|
+
return p;
|
|
952
|
+
}),
|
|
737
953
|
selfAnalysis: parseRows(selfAnalysisResult).map(r => rowToObj(r, saKeys)),
|
|
738
954
|
cascades: parseRows(cascadeResult).map(r => rowToObj(r, cascadeKeys)),
|
|
739
955
|
deptAgents, // live BusinessAgent + Task join for DepartmentPage TeamTab
|
|
@@ -1172,13 +1388,20 @@ async function handleCreatePdsa(req, res, ctx) {
|
|
|
1172
1388
|
// in every department's decisions tab regardless of which pillar they belong to.
|
|
1173
1389
|
// Task.pillarId is set by pillar-dispatcher.js when a task is dispatched to a pillar.
|
|
1174
1390
|
try {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
)
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1391
|
+
// SQLite-first task read for pillarId (P2-10)
|
|
1392
|
+
let pillarId = null;
|
|
1393
|
+
const _storeTask = hboStore.getTask ? hboStore.getTask(taskId.trim(), cid) : null;
|
|
1394
|
+
if (_storeTask) {
|
|
1395
|
+
pillarId = _storeTask.pillarId ?? null;
|
|
1396
|
+
} else {
|
|
1397
|
+
const taskRows = await mgQuery(
|
|
1398
|
+
`MATCH (t:Task {id: $taskId, companyId: $cid}) RETURN t.pillarId AS pillarId LIMIT 1`,
|
|
1399
|
+
{ taskId: taskId.trim(), cid }
|
|
1400
|
+
);
|
|
1401
|
+
pillarId = taskRows && taskRows.rows && taskRows.rows[0]
|
|
1402
|
+
? (Array.isArray(taskRows.rows[0]) ? taskRows.rows[0][0] : taskRows.rows[0].pillarId)
|
|
1403
|
+
: null;
|
|
1404
|
+
}
|
|
1182
1405
|
if (pillarId && typeof pillarId === 'string' && pillarId.trim()) {
|
|
1183
1406
|
await mgQuery(
|
|
1184
1407
|
`MATCH (p:PDSACycle {id: $id, companyId: $cid}) SET p.pillarId = $pillarId`,
|
|
@@ -2126,6 +2349,7 @@ async function handleResolveEmailInfraApproval(req, res, ctx, approvalId) {
|
|
|
2126
2349
|
*/
|
|
2127
2350
|
async function handleGetHboApprovals(req, res, ctx) {
|
|
2128
2351
|
try {
|
|
2352
|
+
if (!ctx.mgQuery) { jsonErr(res, 503, 'Memgraph not connected'); return; }
|
|
2129
2353
|
const url = new URL(req.url, 'http://localhost');
|
|
2130
2354
|
const status = url.searchParams.get('status') || null;
|
|
2131
2355
|
const type = url.searchParams.get('type') || null;
|
|
@@ -2149,6 +2373,17 @@ async function handleGetHboApprovals(req, res, ctx) {
|
|
|
2149
2373
|
agentId: p?.agentId ?? null, taskId: p?.taskId ?? null,
|
|
2150
2374
|
urgency: p?.urgency ?? null, humanAnswer: p?.humanAnswer ?? null,
|
|
2151
2375
|
answeredVia: p?.answeredVia ?? null, createdAt: p?.createdAt ?? null,
|
|
2376
|
+
description: p?.description ?? null,
|
|
2377
|
+
question: p?.question ?? null,
|
|
2378
|
+
defaultAnswer: p?.defaultAnswer ?? null,
|
|
2379
|
+
ruleStrength: p?.ruleStrength ?? null,
|
|
2380
|
+
sourceAgent: p?.sourceAgent ?? null,
|
|
2381
|
+
pillarId: p?.pillarId ?? null,
|
|
2382
|
+
expiresAt: p?.expiresAt ?? null,
|
|
2383
|
+
kaizenProposalId: p?.kaizenProposalId ?? null,
|
|
2384
|
+
department: p?.department ?? null,
|
|
2385
|
+
templateKey: p?.templateKey ?? null,
|
|
2386
|
+
inferredAnswer: p?.inferredAnswer ?? null,
|
|
2152
2387
|
};
|
|
2153
2388
|
});
|
|
2154
2389
|
jsonOk(res, { approvals, count: approvals.length });
|
|
@@ -2338,14 +2573,14 @@ async function handleGetTriageSummary(req, res, ctx) {
|
|
|
2338
2573
|
{ cid }
|
|
2339
2574
|
);
|
|
2340
2575
|
const rows = parseRows(result);
|
|
2341
|
-
const counts = {
|
|
2576
|
+
const counts = { p0Count: 0, p1Count: 0, p2Count: 0, p3Count: 0, total: 0, topItems: [], generatedAt: new Date().toISOString() };
|
|
2342
2577
|
for (const row of rows) {
|
|
2343
2578
|
const pri = (row[0] ?? row['priority'] ?? '').toString().toUpperCase();
|
|
2344
2579
|
const cnt = Number(row[1] ?? row['cnt'] ?? 0);
|
|
2345
|
-
if (pri === 'P0') counts.
|
|
2346
|
-
else if (pri === 'P1') counts.
|
|
2347
|
-
else if (pri === 'P2') counts.
|
|
2348
|
-
else if (pri === 'P3') counts.
|
|
2580
|
+
if (pri === 'P0') counts.p0Count = cnt;
|
|
2581
|
+
else if (pri === 'P1') counts.p1Count = cnt;
|
|
2582
|
+
else if (pri === 'P2') counts.p2Count = cnt;
|
|
2583
|
+
else if (pri === 'P3') counts.p3Count = cnt;
|
|
2349
2584
|
counts.total += cnt;
|
|
2350
2585
|
}
|
|
2351
2586
|
jsonOk(res, counts);
|
|
@@ -2683,6 +2918,14 @@ module.exports = function createHBORouter(handlers) {
|
|
|
2683
2918
|
return false;
|
|
2684
2919
|
}
|
|
2685
2920
|
|
|
2921
|
+
// H-2 fix: Handle OPTIONS preflight for all HBO routes (including DELETE /api/hbo/goals/:id).
|
|
2922
|
+
// Must be BEFORE auth check — browsers never send credentials on preflight requests.
|
|
2923
|
+
if (method === 'OPTIONS') {
|
|
2924
|
+
res.writeHead(204, getCorsHeaders(req));
|
|
2925
|
+
res.end();
|
|
2926
|
+
return true;
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2686
2929
|
// Auth check for all HBO routes
|
|
2687
2930
|
const authHeader = (req.headers || {})['authorization'];
|
|
2688
2931
|
const expected = ctx.apiToken;
|
|
@@ -2773,6 +3016,30 @@ module.exports = function createHBORouter(handlers) {
|
|
|
2773
3016
|
return true;
|
|
2774
3017
|
}
|
|
2775
3018
|
|
|
3019
|
+
// GET /api/hbo/goals/:id — must be checked before POST/PATCH/DELETE goalMatch
|
|
3020
|
+
const goalIdOnlyMatch = pathname.match(/^\/api\/hbo\/goals\/([^/]+)$/);
|
|
3021
|
+
if (method === 'GET' && goalIdOnlyMatch) {
|
|
3022
|
+
await handleGetBusinessGoal(req, res, ctx, decodeURIComponent(goalIdOnlyMatch[1]));
|
|
3023
|
+
return true;
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
// POST /api/hbo/goals
|
|
3027
|
+
if (method === 'POST' && pathname === '/api/hbo/goals') {
|
|
3028
|
+
await handleCreateBusinessGoal(req, res, ctx);
|
|
3029
|
+
return true;
|
|
3030
|
+
}
|
|
3031
|
+
|
|
3032
|
+
// PATCH/DELETE /api/hbo/goals/:id
|
|
3033
|
+
const goalMatch = pathname.match(/^\/api\/hbo\/goals\/(.+)$/);
|
|
3034
|
+
if (method === 'PATCH' && goalMatch) {
|
|
3035
|
+
await handleUpdateBusinessGoal(req, res, ctx, decodeURIComponent(goalMatch[1]));
|
|
3036
|
+
return true;
|
|
3037
|
+
}
|
|
3038
|
+
if (method === 'DELETE' && goalMatch) {
|
|
3039
|
+
await handleDeleteBusinessGoal(req, res, ctx, decodeURIComponent(goalMatch[1]));
|
|
3040
|
+
return true;
|
|
3041
|
+
}
|
|
3042
|
+
|
|
2776
3043
|
// GET /api/hbo/triage-summary
|
|
2777
3044
|
if (method === 'GET' && pathname === '/api/hbo/triage-summary') {
|
|
2778
3045
|
await handleGetTriageSummary(req, res, ctx);
|
package/daemon/routes/hitl.js
CHANGED
|
File without changes
|