@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
|
@@ -8,55 +8,8 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const https = require('https');
|
|
11
|
-
const http = require('http');
|
|
12
11
|
|
|
13
|
-
// ──
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Resolve request options for an Anthropic API call.
|
|
17
|
-
* Routes through Headroom proxy when running (http://127.0.0.1:PORT),
|
|
18
|
-
* falls back to direct api.anthropic.com otherwise.
|
|
19
|
-
*/
|
|
20
|
-
function _buildAnthropicOpts(apiKey, bodyLength) {
|
|
21
|
-
try {
|
|
22
|
-
const { HeadroomProxyManager } = require('../headroom-proxy-manager');
|
|
23
|
-
const baseUrl = HeadroomProxyManager.getInstance().getBaseUrl();
|
|
24
|
-
if (baseUrl) {
|
|
25
|
-
const url = new URL(baseUrl);
|
|
26
|
-
return {
|
|
27
|
-
module: http,
|
|
28
|
-
opts: {
|
|
29
|
-
hostname: url.hostname,
|
|
30
|
-
port: parseInt(url.port || '8787', 10),
|
|
31
|
-
path: '/v1/messages',
|
|
32
|
-
method: 'POST',
|
|
33
|
-
headers: {
|
|
34
|
-
'Content-Type': 'application/json',
|
|
35
|
-
'x-api-key': apiKey,
|
|
36
|
-
'anthropic-version': '2023-06-01',
|
|
37
|
-
'Content-Length': bodyLength,
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
} catch (_) {}
|
|
43
|
-
return {
|
|
44
|
-
module: https,
|
|
45
|
-
opts: {
|
|
46
|
-
hostname: 'api.anthropic.com',
|
|
47
|
-
path: '/v1/messages',
|
|
48
|
-
method: 'POST',
|
|
49
|
-
headers: {
|
|
50
|
-
'Content-Type': 'application/json',
|
|
51
|
-
'x-api-key': apiKey,
|
|
52
|
-
'anthropic-version': '2023-06-01',
|
|
53
|
-
'Content-Length': bodyLength,
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ── Provider-agnostic LLM (exact pattern from project-semantic-updater.js) ───
|
|
12
|
+
// ── Provider-agnostic LLM ────────────────────────────────────────────────────
|
|
60
13
|
|
|
61
14
|
async function callLLM(systemPrompt, userContent, maxTokens) {
|
|
62
15
|
maxTokens = maxTokens || 256;
|
|
@@ -81,8 +34,17 @@ function _callAnthropic(apiKey, systemPrompt, userContent, maxTokens) {
|
|
|
81
34
|
messages: [{ role: 'user', content: userContent }],
|
|
82
35
|
});
|
|
83
36
|
var bodyLen = Buffer.byteLength(body);
|
|
84
|
-
var
|
|
85
|
-
|
|
37
|
+
var req = https.request({
|
|
38
|
+
hostname: 'api.anthropic.com',
|
|
39
|
+
path: '/v1/messages',
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: {
|
|
42
|
+
'Content-Type': 'application/json',
|
|
43
|
+
'x-api-key': apiKey,
|
|
44
|
+
'anthropic-version': '2023-06-01',
|
|
45
|
+
'Content-Length': bodyLen,
|
|
46
|
+
},
|
|
47
|
+
}, function (resp) {
|
|
86
48
|
var raw = '';
|
|
87
49
|
resp.on('data', function (chunk) { raw += chunk.toString(); });
|
|
88
50
|
resp.on('end', function () {
|
|
@@ -172,6 +172,26 @@ class MandalaManager {
|
|
|
172
172
|
{ signalId, cid: this.companyId, name: pillarName, weight, weeks: Number(zeroWeeks), pillarId, goalId }
|
|
173
173
|
).catch(() => {});
|
|
174
174
|
|
|
175
|
+
// AndonAlert mirrors AnomalySignal — queried by the /api/hbo/andon/:id/resolve endpoint.
|
|
176
|
+
// The resolve endpoint uses :AndonAlert label; both nodes are kept in sync.
|
|
177
|
+
const andonId = `andon:pillar_neglect:${this.companyId}:${pillarId}`;
|
|
178
|
+
await this.mg(
|
|
179
|
+
`MERGE (a:AndonAlert {id: $andonId, companyId: $cid})
|
|
180
|
+
ON CREATE SET
|
|
181
|
+
a.companyId = $cid,
|
|
182
|
+
a.signalId = $signalId,
|
|
183
|
+
a.pillarId = $pillarId,
|
|
184
|
+
a.goalId = $goalId,
|
|
185
|
+
a.severity = 'P2',
|
|
186
|
+
a.status = 'open',
|
|
187
|
+
a.source = 'mandala',
|
|
188
|
+
a.detectedAt = localdatetime(),
|
|
189
|
+
a.message = 'Pillar neglected for ' + $weeks + ' consecutive weeks'
|
|
190
|
+
ON MATCH SET
|
|
191
|
+
a.lastSeenAt = localdatetime()`,
|
|
192
|
+
{ andonId, signalId, cid: this.companyId, pillarId, goalId, weeks: Number(zeroWeeks) }
|
|
193
|
+
).catch(() => {});
|
|
194
|
+
|
|
175
195
|
await this.mg(
|
|
176
196
|
`MATCH (p:GoalPillar {id: $id, companyId: $cid}) SET p.lastAndonFiredAt = localdatetime()`,
|
|
177
197
|
{ id: pillarId, cid: this.companyId }
|
|
@@ -368,7 +368,7 @@ class PillarDispatcher {
|
|
|
368
368
|
// Ensure GoalPillar has project fields and ProjectDocument, then wire Task.pillarId
|
|
369
369
|
const { ensureProject } = require('./project-factory');
|
|
370
370
|
await ensureProject(
|
|
371
|
-
this._mg, pillar.id, goalId, companyId, strategy, agentId
|
|
371
|
+
this._mg, pillar.id, goalId, companyId, strategy, agentId, this._broadcast
|
|
372
372
|
).catch(() => null);
|
|
373
373
|
|
|
374
374
|
// Set Task.pillarId so context-enrichment can look up GoalPillar directly
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
'use strict';
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
|
-
async function ensureProjectOnPillar(mgQuery, pillarId, goalId, companyId, strategy, agentId) {
|
|
3
|
+
async function ensureProjectOnPillar(mgQuery, pillarId, goalId, companyId, strategy, agentId, broadcast) {
|
|
4
4
|
const docId = `pdoc:${pillarId}:main`;
|
|
5
5
|
|
|
6
6
|
// Set project properties directly on GoalPillar (no HeliosProject node created)
|
|
@@ -32,6 +32,11 @@ async function ensureProjectOnPillar(mgQuery, pillarId, goalId, companyId, strat
|
|
|
32
32
|
{ pillarId, docId, cid: companyId }
|
|
33
33
|
);
|
|
34
34
|
|
|
35
|
+
// Broadcast project:created so desktop SSE listeners refresh project list
|
|
36
|
+
if (broadcast) {
|
|
37
|
+
try { broadcast({ type: 'project:created', companyId, projectId: pillarId, pillarId }); } catch (_) {}
|
|
38
|
+
}
|
|
39
|
+
|
|
35
40
|
// Return shape includes 'id' alias for pillarId for backward compat
|
|
36
41
|
// with any callers that still check project.id before full deployment.
|
|
37
42
|
return { pillarId, docId, id: pillarId };
|
package/daemon/lib/hbo-bridge.js
CHANGED
|
@@ -708,7 +708,7 @@ class HBOBridge {
|
|
|
708
708
|
* Phase 3 addition: validates SystemAim exists before proceeding.
|
|
709
709
|
* Phase 4 addition: generates QuarterlyOKRs for each department agent per goal.
|
|
710
710
|
*/
|
|
711
|
-
async tickGoalDecompose() {
|
|
711
|
+
async tickGoalDecompose(opts = {}) {
|
|
712
712
|
try {
|
|
713
713
|
// Phase 3: SystemAim guard — block decomposition if aim not set
|
|
714
714
|
const aimCheck = await this._mg(
|
|
@@ -716,14 +716,28 @@ class HBOBridge {
|
|
|
716
716
|
{ cid: this._companyId }
|
|
717
717
|
).catch(() => null);
|
|
718
718
|
if (!aimCheck?.rows?.length) {
|
|
719
|
-
|
|
720
|
-
|
|
719
|
+
if (opts?.fromWizard) {
|
|
720
|
+
// Wizard just wrote SystemAim — retry once with delay in case of write propagation lag
|
|
721
|
+
await new Promise(r => setTimeout(r, 500));
|
|
722
|
+
const aimRetry = await this._mg(
|
|
723
|
+
`MATCH (aim:SystemAim {companyId: $cid}) RETURN aim.id LIMIT 1`,
|
|
724
|
+
{ cid: this._companyId }
|
|
725
|
+
).catch(() => null);
|
|
726
|
+
if (!aimRetry?.rows?.length) {
|
|
727
|
+
this._log('warn', 'tickGoalDecompose: No SystemAim after retry — skipping wizard-triggered init');
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
this._log('info', 'tickGoalDecompose: SystemAim found on retry (write propagation delay)');
|
|
731
|
+
} else {
|
|
732
|
+
this._log('warn', 'tickGoalDecompose: No SystemAim — skipping (requires human to set via POST /api/system-aim)');
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
721
735
|
}
|
|
722
736
|
|
|
723
737
|
// Find active goals (CompanyGoal OR BusinessGoal) with no child BusinessTasks
|
|
724
738
|
// and no existing decomposition task (checked by deterministic MERGE on t.id).
|
|
725
739
|
// CompanyGoal: created by seed-company.js at startup
|
|
726
|
-
// BusinessGoal: created via POST /api/hbo/goals or
|
|
740
|
+
// BusinessGoal: created via POST /api/hbo/goals or the wizard's write-through goal path
|
|
727
741
|
const undecomposed = await this._mg(
|
|
728
742
|
`MATCH (g)
|
|
729
743
|
WHERE (g:CompanyGoal OR g:BusinessGoal)
|
|
@@ -791,7 +805,7 @@ class HBOBridge {
|
|
|
791
805
|
const taskId = `task:decompose:${goal.id}`;
|
|
792
806
|
const owner = goal.owner ?? `agent:${this._companyId}:ceo`;
|
|
793
807
|
const title = `Decompose goal: ${goal.title}`;
|
|
794
|
-
const body = `
|
|
808
|
+
const body = `Protocol: harada_first_decomposition\nGoal ID: ${goal.id}\nGoal: ${goal.title}\nDescription: ${goal.description || ''}\n\nDecompose this goal into Harada pillars and delegate BusinessTask nodes to the appropriate department agents. Set assigneeId on every BusinessTask to a valid agent ID (e.g. agent:cto, agent:sales). Never create a task with null assigneeId.`;
|
|
795
809
|
|
|
796
810
|
try {
|
|
797
811
|
await this._mg(
|
|
@@ -805,7 +819,10 @@ class HBOBridge {
|
|
|
805
819
|
t.workType = 'strategic',
|
|
806
820
|
t.hboGoalId = $goalId,
|
|
807
821
|
t.priority = toInteger(1),
|
|
808
|
-
t.createdAt = localdatetime()
|
|
822
|
+
t.createdAt = localdatetime()
|
|
823
|
+
ON MATCH SET
|
|
824
|
+
t.title = $title,
|
|
825
|
+
t.body = $body`,
|
|
809
826
|
{ taskId, cid: this._companyId, title, body, owner, goalId: goal.id }
|
|
810
827
|
);
|
|
811
828
|
this._log('info', `tickGoalDecompose: ensured decomposition task for goal "${goal.title}" → ${owner}`);
|
|
@@ -979,7 +996,8 @@ class HBOBridge {
|
|
|
979
996
|
OPTIONAL MATCH (t:Task {hboTaskId: bt.id})
|
|
980
997
|
WITH bt, t
|
|
981
998
|
WHERE t IS NULL
|
|
982
|
-
RETURN bt.id AS id, bt.title AS title, bt.
|
|
999
|
+
RETURN bt.id AS id, bt.title AS title, bt.description AS description,
|
|
1000
|
+
bt.assigneeId AS assigneeId,
|
|
983
1001
|
bt.priority AS priority, bt.goalId AS goalId
|
|
984
1002
|
LIMIT toInteger(20)`,
|
|
985
1003
|
{ cid: this._companyId }
|
|
@@ -991,11 +1009,12 @@ class HBOBridge {
|
|
|
991
1009
|
let created = 0;
|
|
992
1010
|
|
|
993
1011
|
for (const row of businessTasks.rows) {
|
|
994
|
-
const btId
|
|
995
|
-
const title
|
|
996
|
-
const
|
|
997
|
-
const
|
|
998
|
-
const
|
|
1012
|
+
const btId = row[0] ?? row['id'];
|
|
1013
|
+
const title = row[1] ?? row['title'];
|
|
1014
|
+
const description = row[2] ?? row['description'] ?? null;
|
|
1015
|
+
const assigneeId = row[3] ?? row['assigneeId'];
|
|
1016
|
+
const priority = row[4] ?? row['priority'] ?? 'medium';
|
|
1017
|
+
const goalId = row[5] ?? row['goalId'];
|
|
999
1018
|
|
|
1000
1019
|
if (!btId || !title) continue;
|
|
1001
1020
|
|
|
@@ -1032,7 +1051,7 @@ class HBOBridge {
|
|
|
1032
1051
|
taskId,
|
|
1033
1052
|
cid: this._companyId,
|
|
1034
1053
|
title: String(title),
|
|
1035
|
-
body:
|
|
1054
|
+
body: description ? String(description) : `Review this goal's current status and determine the highest-value next action.\nGoal: ${title}`,
|
|
1036
1055
|
priority: priorityNum,
|
|
1037
1056
|
assigneeAgentId: assigneeId,
|
|
1038
1057
|
hboTaskId: btId,
|
package/daemon/lib/hed-engine.js
CHANGED
|
@@ -1,306 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
|
|
3
|
-
const { randomUUID } = require('crypto');
|
|
4
|
-
|
|
5
2
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Agents execute against HEDOperations; Murray audits each completion.
|
|
3
|
+
* daemon/lib/hed-engine.js — Helios Event Dispatch engine.
|
|
4
|
+
* Manages HED wave advancement and event routing.
|
|
9
5
|
*/
|
|
10
6
|
|
|
11
7
|
class HEDEngine {
|
|
12
|
-
constructor(
|
|
13
|
-
this._mg =
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// ─── Create ────────────────────────────────────────────────────────────────
|
|
17
|
-
|
|
18
|
-
async createHED({ companyId, title, intent, worldStateSnapshot, operations, goalId }) {
|
|
19
|
-
const hedId = `hed_${Date.now()}_${randomUUID().slice(0, 8)}`;
|
|
20
|
-
const now = new Date().toISOString();
|
|
21
|
-
|
|
22
|
-
// Create PlanChangeProposal with scope='execution'
|
|
23
|
-
await this._mg(
|
|
24
|
-
`CREATE (pcp:PlanChangeProposal {
|
|
25
|
-
id: $id, companyId: $companyId, scope: 'execution',
|
|
26
|
-
title: $title, intent: $intent,
|
|
27
|
-
worldStateSnapshot: $worldStateSnapshot,
|
|
28
|
-
status: 'draft', requiresApproval: true,
|
|
29
|
-
waveCount: $waveCount, currentWave: 1,
|
|
30
|
-
createdAt: $now, resolvedAt: null, resolvedBy: null
|
|
31
|
-
})`,
|
|
32
|
-
{
|
|
33
|
-
id: hedId, companyId, title, intent,
|
|
34
|
-
worldStateSnapshot: JSON.stringify(worldStateSnapshot || {}),
|
|
35
|
-
waveCount: Math.max(...operations.map(o => o.wave || 1), 1),
|
|
36
|
-
now
|
|
37
|
-
}
|
|
38
|
-
);
|
|
39
|
-
|
|
40
|
-
if (goalId) {
|
|
41
|
-
await this._mg(
|
|
42
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId}), (g:BusinessGoal {id: $goalId})
|
|
43
|
-
MERGE (pcp)-[:IMPLEMENTS_GOAL]->(g)`,
|
|
44
|
-
{ hedId, goalId }
|
|
45
|
-
).catch(() => {});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Create HEDOperation nodes
|
|
49
|
-
for (const op of operations) {
|
|
50
|
-
await this.createOperation(hedId, op, now);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return { hedId };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async createOperation(hedId, op, now = new Date().toISOString()) {
|
|
57
|
-
const opId = op.opId || `op_${Date.now()}_${randomUUID().slice(0, 6)}`;
|
|
58
|
-
await this._mg(
|
|
59
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId})
|
|
60
|
-
CREATE (o:HEDOperation {
|
|
61
|
-
id: $opId, hedId: $hedId,
|
|
62
|
-
type: $type, target: $target, targetScope: $targetScope,
|
|
63
|
-
description: $description,
|
|
64
|
-
acceptanceCriteria: $acceptanceCriteria,
|
|
65
|
-
dependsOn: $dependsOn, wave: $wave,
|
|
66
|
-
assignedAgentRole: $assignedAgentRole,
|
|
67
|
-
deviationPolicy: $deviationPolicy,
|
|
68
|
-
status: 'pending', createdAt: $now
|
|
69
|
-
})
|
|
70
|
-
MERGE (pcp)-[:HAS_OPERATION]->(o)`,
|
|
71
|
-
{
|
|
72
|
-
hedId, opId,
|
|
73
|
-
type: op.type || 'UPDATE',
|
|
74
|
-
target: op.target || '',
|
|
75
|
-
targetScope: op.targetScope || '',
|
|
76
|
-
description: op.description || '',
|
|
77
|
-
acceptanceCriteria: JSON.stringify(op.acceptanceCriteria || []),
|
|
78
|
-
dependsOn: JSON.stringify(op.dependsOn || []),
|
|
79
|
-
wave: op.wave || 1,
|
|
80
|
-
assignedAgentRole: op.assignedAgentRole || 'worker',
|
|
81
|
-
deviationPolicy: op.deviationPolicy || 'smart',
|
|
82
|
-
now
|
|
83
|
-
}
|
|
84
|
-
);
|
|
85
|
-
return opId;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// ─── Read ──────────────────────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
async getHED(hedId) {
|
|
91
|
-
const rows = await this._mg(
|
|
92
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId, scope: 'execution'})
|
|
93
|
-
RETURN pcp`,
|
|
94
|
-
{ hedId }
|
|
95
|
-
);
|
|
96
|
-
if (!rows?.length) return null;
|
|
97
|
-
const pcp = rows[0].pcp;
|
|
98
|
-
try { pcp.worldStateSnapshot = JSON.parse(pcp.worldStateSnapshot); } catch {}
|
|
99
|
-
return pcp;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async getOperations(hedId) {
|
|
103
|
-
const rows = await this._mg(
|
|
104
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId})-[:HAS_OPERATION]->(o:HEDOperation)
|
|
105
|
-
RETURN o ORDER BY o.wave ASC, o.id ASC`,
|
|
106
|
-
{ hedId }
|
|
107
|
-
);
|
|
108
|
-
return (rows || []).map(r => {
|
|
109
|
-
const o = r.o;
|
|
110
|
-
try { o.acceptanceCriteria = JSON.parse(o.acceptanceCriteria); } catch { o.acceptanceCriteria = []; }
|
|
111
|
-
try { o.dependsOn = JSON.parse(o.dependsOn); } catch { o.dependsOn = []; }
|
|
112
|
-
return o;
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
async getOperation(opId) {
|
|
117
|
-
const rows = await this._mg(
|
|
118
|
-
`MATCH (o:HEDOperation {id: $opId}) RETURN o`, { opId }
|
|
119
|
-
);
|
|
120
|
-
if (!rows?.length) return null;
|
|
121
|
-
const o = rows[0].o;
|
|
122
|
-
try { o.acceptanceCriteria = JSON.parse(o.acceptanceCriteria); } catch { o.acceptanceCriteria = []; }
|
|
123
|
-
try { o.dependsOn = JSON.parse(o.dependsOn); } catch { o.dependsOn = []; }
|
|
124
|
-
return o;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ─── State transitions ─────────────────────────────────────────────────────
|
|
128
|
-
|
|
129
|
-
async approveHED(hedId, approvedBy) {
|
|
130
|
-
const now = new Date().toISOString();
|
|
131
|
-
await this._mg(
|
|
132
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId, scope: 'execution', status: 'draft'})
|
|
133
|
-
SET pcp.status = 'approved', pcp.approvedAt = $now, pcp.approvedBy = $approvedBy`,
|
|
134
|
-
{ hedId, now, approvedBy }
|
|
135
|
-
);
|
|
8
|
+
constructor(mgQuery) {
|
|
9
|
+
this._mg = mgQuery;
|
|
136
10
|
}
|
|
137
11
|
|
|
138
|
-
async claimOperation(opId, agentId) {
|
|
139
|
-
const now = new Date().toISOString();
|
|
140
|
-
const rows = await this._mg(
|
|
141
|
-
`MATCH (o:HEDOperation {id: $opId, status: 'pending'})
|
|
142
|
-
SET o.status = 'in_progress', o.executionLockedAt = $now, o.executionAgentId = $agentId
|
|
143
|
-
RETURN o.id AS claimed`,
|
|
144
|
-
{ opId, now, agentId }
|
|
145
|
-
);
|
|
146
|
-
return rows?.[0]?.claimed === opId;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async completeOperation(opId, { summary, sessionKey, verdict = 'pending_review' }) {
|
|
150
|
-
const now = new Date().toISOString();
|
|
151
|
-
await this._mg(
|
|
152
|
-
`MATCH (o:HEDOperation {id: $opId})
|
|
153
|
-
SET o.status = $status, o.completedAt = $now,
|
|
154
|
-
o.executionSummary = $summary, o.sessionKey = $sessionKey`,
|
|
155
|
-
{ opId, status: verdict === 'pending_review' ? 'done' : verdict, now, summary, sessionKey }
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async postReviewFinding(opId, finding) {
|
|
160
|
-
const findingId = `hrf_${Date.now()}_${randomUUID().slice(0, 6)}`;
|
|
161
|
-
const now = new Date().toISOString();
|
|
162
|
-
await this._mg(
|
|
163
|
-
`MATCH (o:HEDOperation {id: $opId})
|
|
164
|
-
CREATE (f:HEDReviewFinding {
|
|
165
|
-
id: $findingId, opId: $opId, hedId: o.hedId,
|
|
166
|
-
verdict: $verdict, deviationClass: $deviationClass,
|
|
167
|
-
criteriaResults: $criteriaResults,
|
|
168
|
-
deviationNarrative: $deviationNarrative,
|
|
169
|
-
blockingBehavior: $blockingBehavior,
|
|
170
|
-
createdAt: $now
|
|
171
|
-
})
|
|
172
|
-
MERGE (o)-[:HAS_REVIEW]->(f)
|
|
173
|
-
SET o.reviewVerdict = $verdict`,
|
|
174
|
-
{
|
|
175
|
-
findingId, opId,
|
|
176
|
-
verdict: finding.verdict || 'ALIGNED',
|
|
177
|
-
deviationClass: finding.deviationClass || null,
|
|
178
|
-
criteriaResults: JSON.stringify(finding.criteriaResults || []),
|
|
179
|
-
deviationNarrative: finding.deviationNarrative || '',
|
|
180
|
-
blockingBehavior: finding.blockingBehavior || 'continue',
|
|
181
|
-
now
|
|
182
|
-
}
|
|
183
|
-
);
|
|
184
|
-
// If hard block — pause HED
|
|
185
|
-
if (finding.blockingBehavior === 'hard-pause') {
|
|
186
|
-
await this._mg(
|
|
187
|
-
`MATCH (pcp:PlanChangeProposal {id: o.hedId})
|
|
188
|
-
MATCH (o:HEDOperation {id: $opId})
|
|
189
|
-
SET pcp.status = 'review'`,
|
|
190
|
-
{ opId }
|
|
191
|
-
).catch(() => {});
|
|
192
|
-
}
|
|
193
|
-
if (finding.blastRadius) {
|
|
194
|
-
await this._writeBlastRadius(opId, finding.blastRadius, now);
|
|
195
|
-
}
|
|
196
|
-
return findingId;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async _writeBlastRadius(opId, br, now) {
|
|
200
|
-
const brId = `bre_${Date.now()}_${randomUUID().slice(0, 6)}`;
|
|
201
|
-
await this._mg(
|
|
202
|
-
`MATCH (o:HEDOperation {id: $opId})
|
|
203
|
-
CREATE (b:BlastRadiusEvent {
|
|
204
|
-
id: $brId, opId: $opId,
|
|
205
|
-
filesChanged: $filesChanged,
|
|
206
|
-
nodesAffected: $nodesAffected,
|
|
207
|
-
severity: $severity,
|
|
208
|
-
createdAt: $now
|
|
209
|
-
})
|
|
210
|
-
MERGE (o)-[:HAS_BLAST_RADIUS]->(b)`,
|
|
211
|
-
{
|
|
212
|
-
brId, opId,
|
|
213
|
-
filesChanged: JSON.stringify(br.filesChanged || []),
|
|
214
|
-
nodesAffected: JSON.stringify(br.nodesAffected || []),
|
|
215
|
-
severity: br.severity || 'none',
|
|
216
|
-
now
|
|
217
|
-
}
|
|
218
|
-
).catch(err => console.error('[hed-engine] blast radius write failed:', err.message));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// ─── Wave advancement ──────────────────────────────────────────────────────
|
|
222
|
-
|
|
223
12
|
async checkWaveAdvancement(companyId) {
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
`MATCH (pcp:PlanChangeProposal {companyId: $companyId, scope: 'execution', status: 'approved'})
|
|
227
|
-
RETURN pcp.id AS hedId, pcp.currentWave AS currentWave, pcp.waveCount AS waveCount`,
|
|
228
|
-
{ companyId }
|
|
229
|
-
);
|
|
230
|
-
for (const { hedId, currentWave, waveCount } of (heds || [])) {
|
|
231
|
-
await this._advanceWaveIfReady(hedId, currentWave, waveCount);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
async _advanceWaveIfReady(hedId, currentWave, waveCount) {
|
|
236
|
-
// Check if all ops in currentWave are done or deviated (not blocked/failed)
|
|
237
|
-
const pending = await this._mg(
|
|
238
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId})-[:HAS_OPERATION]->(o:HEDOperation)
|
|
239
|
-
WHERE o.wave = $wave AND o.status IN ['pending', 'in_progress']
|
|
240
|
-
RETURN count(o) AS pendingCount`,
|
|
241
|
-
{ hedId, wave: currentWave }
|
|
242
|
-
);
|
|
243
|
-
const hardBlocked = await this._mg(
|
|
244
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId})-[:HAS_OPERATION]->(o:HEDOperation)
|
|
245
|
-
WHERE o.wave = $wave AND o.reviewVerdict = 'FAILED'
|
|
246
|
-
RETURN count(o) AS blockedCount`,
|
|
247
|
-
{ hedId, wave: currentWave }
|
|
248
|
-
);
|
|
249
|
-
if ((pending?.[0]?.pendingCount || 0) > 0) return; // wave still running
|
|
250
|
-
if ((hardBlocked?.[0]?.blockedCount || 0) > 0) {
|
|
251
|
-
// Hard block — pause entire HED
|
|
13
|
+
// Wave advancement check — no-op when Memgraph is unavailable
|
|
14
|
+
try {
|
|
252
15
|
await this._mg(
|
|
253
|
-
`MATCH (
|
|
254
|
-
{
|
|
16
|
+
`MATCH (w:HEDWave {companyId: $cid, status: 'pending'}) RETURN w.id LIMIT 1`,
|
|
17
|
+
{ cid: companyId }
|
|
255
18
|
);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const mesh = (global).__helios_session_mesh;
|
|
259
|
-
if (mesh?.bus?.publish) {
|
|
260
|
-
mesh.bus.publish('PLAN_STALLED', {
|
|
261
|
-
hedId,
|
|
262
|
-
wave: currentWave,
|
|
263
|
-
reason: 'HEDOperation hard-blocked by reviewer verdict',
|
|
264
|
-
timestamp: new Date().toISOString(),
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
} catch (_meshErr) { /* mesh not available in daemon context — non-fatal */ }
|
|
268
|
-
return;
|
|
19
|
+
} catch (_) {
|
|
20
|
+
// Fail-open: Memgraph unavailable
|
|
269
21
|
}
|
|
270
|
-
if (currentWave >= waveCount) {
|
|
271
|
-
// All waves complete
|
|
272
|
-
await this._mg(
|
|
273
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId}) SET pcp.status = 'closed', pcp.closedAt = $now`,
|
|
274
|
-
{ hedId, now: new Date().toISOString() }
|
|
275
|
-
);
|
|
276
|
-
} else {
|
|
277
|
-
// Advance to next wave
|
|
278
|
-
await this._mg(
|
|
279
|
-
`MATCH (pcp:PlanChangeProposal {id: $hedId}) SET pcp.currentWave = $nextWave`,
|
|
280
|
-
{ hedId, nextWave: currentWave + 1 }
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ─── Context brief helper ──────────────────────────────────────────────────
|
|
286
|
-
|
|
287
|
-
async buildOperationContext(opId) {
|
|
288
|
-
const op = await this.getOperation(opId);
|
|
289
|
-
if (!op) return null;
|
|
290
|
-
const hed = await this.getHED(op.hedId);
|
|
291
|
-
if (!hed) return null;
|
|
292
|
-
return {
|
|
293
|
-
hedIntent: hed.intent,
|
|
294
|
-
worldStateSnapshot: hed.worldStateSnapshot,
|
|
295
|
-
operation: {
|
|
296
|
-
type: op.type,
|
|
297
|
-
target: op.target,
|
|
298
|
-
targetScope: op.targetScope,
|
|
299
|
-
description: op.description,
|
|
300
|
-
acceptanceCriteria: op.acceptanceCriteria,
|
|
301
|
-
deviationPolicy: op.deviationPolicy
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
22
|
}
|
|
305
23
|
}
|
|
306
24
|
|
|
@@ -261,6 +261,16 @@ async function _handleNonTTY(event, mgQuery, taskId, companyId, ctx) {
|
|
|
261
261
|
companyId: companyId,
|
|
262
262
|
declaration: decl,
|
|
263
263
|
});
|
|
264
|
+
if (ctx.broadcast && ctx.isWizardSession) {
|
|
265
|
+
ctx.broadcast({
|
|
266
|
+
type: 'wizard:interpretation_ready',
|
|
267
|
+
id: _broadcastInteractionId,
|
|
268
|
+
agentId: 'agent:' + companyId + ':unknown',
|
|
269
|
+
taskId: taskId || ('task:unknown:' + companyId),
|
|
270
|
+
companyId: companyId,
|
|
271
|
+
declaration: decl,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
264
274
|
} else {
|
|
265
275
|
setTimeout(_attemptBroadcast, 200);
|
|
266
276
|
}
|
|
@@ -405,7 +415,10 @@ function createHITLHost(opts) {
|
|
|
405
415
|
// The raw host handles a single surfaced interaction (batch or single)
|
|
406
416
|
// Resolve declaration lazily if a getter is provided (supports agent subprocess IPC path).
|
|
407
417
|
const _getDecl = typeof opts.getInterpretationDeclaration === 'function' ? opts.getInterpretationDeclaration : null;
|
|
408
|
-
|
|
418
|
+
// isWizardSession: pass true from wizard-time callers (e.g. wizard route / goal-research-pipeline)
|
|
419
|
+
// so that wizard:interpretation_ready is only emitted during the onboarding wizard, not for
|
|
420
|
+
// regular task-time interpretation interactions.
|
|
421
|
+
const host = new HITLHost({ companyId, taskTitle, mgQuery, taskId, ctx: { isInterpretation: opts.isInterpretation, isWizardSession: opts.isWizardSession || false, interpretationDeclaration: _getDecl ? undefined : opts.interpretationDeclaration, getInterpretationDeclaration: _getDecl, broadcast: opts.broadcast || null } });
|
|
409
422
|
|
|
410
423
|
// Wrap with the batcher — it calls host._interact for each surfaced event
|
|
411
424
|
const batcher = new HITLBatcher({
|
|
@@ -417,7 +430,7 @@ function createHITLHost(opts) {
|
|
|
417
430
|
if (!process.stdin.isTTY) {
|
|
418
431
|
// Non-TTY: route through Memgraph if mgQuery available
|
|
419
432
|
if (mgQuery && (taskId || companyId)) {
|
|
420
|
-
return await _handleNonTTY(event, mgQuery, taskId, companyId, { isInterpretation: opts.isInterpretation, interpretationDeclaration: _getDecl ? _getDecl() : opts.interpretationDeclaration, broadcast: opts.broadcast || null });
|
|
433
|
+
return await _handleNonTTY(event, mgQuery, taskId, companyId, { isInterpretation: opts.isInterpretation, isWizardSession: opts.isWizardSession || false, interpretationDeclaration: _getDecl ? _getDecl() : opts.interpretationDeclaration, broadcast: opts.broadcast || null });
|
|
421
434
|
}
|
|
422
435
|
process.stderr.write('[helios-hitl-host] Non-TTY, no mgQuery — auto-responding to ' + (event.id || 'batch') + '\n');
|
|
423
436
|
if (event.isBatch && Array.isArray(event.questions)) {
|
|
File without changes
|