@cgh567/agent 2.4.1 → 2.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/bin/helios +0 -0
  2. package/bin/helios-rpc-node-wrapper.cjs +0 -0
  3. package/bin/helios-rpc-wrapper.sh +0 -0
  4. package/daemon/adapters/helios-rpc-adapter.js +47 -25
  5. package/daemon/adapters/tui_wakeup.js +8 -0
  6. package/daemon/config/com.familiar.helios-daemon.plist +5 -0
  7. package/daemon/config/helios-daemon.service +4 -0
  8. package/daemon/context-enrichment.js +59 -21
  9. package/daemon/daemon-manager.js +1 -1
  10. package/daemon/db/email-infrastructure-migrate.js +192 -0
  11. package/daemon/db/hbo-core-migrate.js +189 -0
  12. package/daemon/helios-api.js +723 -57
  13. package/daemon/helios-company-daemon.js +616 -134
  14. package/daemon/lib/harada/cascade-judge.js +12 -50
  15. package/daemon/lib/harada/mandala.js +20 -0
  16. package/daemon/lib/harada/pillar-dispatcher.js +1 -1
  17. package/daemon/lib/harada/project-factory.js +7 -2
  18. package/daemon/lib/hbo-bridge.js +32 -13
  19. package/daemon/lib/hed-engine.js +10 -292
  20. package/daemon/lib/helios-hitl-host.js +15 -2
  21. package/daemon/lib/hitl-interaction-service.js +0 -0
  22. package/daemon/lib/memgraph-verify.js +38 -33
  23. package/daemon/lib/project-drift-detector.js +7 -17
  24. package/daemon/lib/project-semantic-updater.js +1 -14
  25. package/daemon/lib/task-completion-processor.js +11 -0
  26. package/daemon/lib/wizard-engine.js +57 -6
  27. package/daemon/routes/channels.js +10 -5
  28. package/daemon/routes/harada-map.js +11 -48
  29. package/daemon/routes/hbo.js +342 -75
  30. package/daemon/routes/hitl.js +0 -0
  31. package/daemon/routes/project.js +194 -62
  32. package/daemon/routes/routines.js +14 -0
  33. package/daemon/routes/tasks.js +15 -1
  34. package/daemon/routes/wizard.js +11 -4
  35. package/daemon/schema-apply.js +174 -0
  36. package/daemon/schema-definitions.js +423 -0
  37. package/daemon/schema-migrations-hbo.js +10 -0
  38. package/daemon/schema-migrations-hed.js +18 -0
  39. package/daemon/schema-migrations-hitl.js +0 -0
  40. package/daemon/schema-migrations-proj.js +131 -0
  41. package/extensions/001-tool-output-cap.ts +0 -0
  42. package/extensions/context-compaction.ts +45 -26
  43. package/extensions/cortex/activation-bridge.ts +5 -0
  44. package/extensions/cortex/learn.ts +26 -0
  45. package/extensions/cortex/wal-replay.ts +91 -0
  46. package/extensions/email/backfill.ts +0 -0
  47. package/extensions/helios-governance/analysis/ambiguity.ts +0 -0
  48. package/extensions/helios-governance/analysis/compliance.ts +0 -0
  49. package/extensions/helios-governance/analysis/long-task-detector.ts +0 -0
  50. package/extensions/helios-governance/analysis/output-contract.ts +0 -0
  51. package/extensions/helios-governance/analysis/patterns.ts +0 -0
  52. package/extensions/helios-governance/analysis/preflight.ts +0 -0
  53. package/extensions/helios-governance/analysis/recurring-violations.ts +0 -0
  54. package/extensions/helios-governance/analysis/task-classification.ts +0 -0
  55. package/extensions/helios-governance/analysis/task-intent.ts +0 -0
  56. package/extensions/helios-governance/gates/high-impact.ts +1 -1
  57. package/extensions/helios-governance/handlers/_jiti-require.ts +15 -8
  58. package/extensions/helios-governance/handlers/proxy-test-detector.ts +0 -0
  59. package/extensions/hema-dispatch-v3/graph-memory.ts +10 -0
  60. package/extensions/hema-dispatch-v3/index.ts +72 -47
  61. package/extensions/lib/elo-engine.js +0 -0
  62. package/extensions/lib/elo-engine.test.js +0 -0
  63. package/extensions/memgraph-autostart.ts +13 -0
  64. package/extensions/neuroplastic-eval.ts +0 -0
  65. package/extensions/shadow-loop/index.ts +0 -0
  66. package/extensions/warm-tick/warm-tick-maintenance.ts +8 -0
  67. package/lib/__tests__/hbo-core-store.test.js +238 -0
  68. package/lib/brain-v2-budget.js +0 -0
  69. package/lib/brain-v2-circuit-breaker.js +0 -0
  70. package/lib/brain-v2.js +0 -0
  71. package/lib/broker/adaptive-throttle.js +0 -0
  72. package/lib/broker/batch-coalescer.js +0 -0
  73. package/lib/broker/bulkhead.js +0 -0
  74. package/lib/broker/channel-registry.js +0 -0
  75. package/lib/broker/circuit-breaker.js +0 -0
  76. package/lib/broker/evidence-cache.js +0 -0
  77. package/lib/broker/health-monitor.js +0 -0
  78. package/lib/broker/mage-queue.js +0 -0
  79. package/lib/broker/priority-queue.js +0 -0
  80. package/lib/broker/server.js.bak-error2-fix +0 -0
  81. package/lib/broker/session-registry.js +0 -0
  82. package/lib/broker/singleton-timers.js +0 -0
  83. package/lib/broker/types.d.ts +0 -0
  84. package/lib/broker/vegas-limit.js +0 -0
  85. package/lib/compression/dist/ccr-store.js +74 -0
  86. package/lib/compression/dist/content-router.js +115 -0
  87. package/lib/compression/dist/pipeline.js +113 -0
  88. package/lib/compression/dist/server.js +265 -0
  89. package/lib/compression/dist/smart-crusher.js +251 -0
  90. package/lib/context-budget.ts +0 -0
  91. package/lib/context-firewall.js +0 -0
  92. package/lib/crm/integration/triage-bridge.js +0 -0
  93. package/lib/email-utils.ts +0 -0
  94. package/lib/eval/__tests__/preflight-checker.test.ts +0 -0
  95. package/lib/eval/__tests__/task-instruction-parser.test.ts +0 -0
  96. package/lib/eval/__tests__/verifier-runner.test.ts +0 -0
  97. package/lib/eval/index.ts +0 -0
  98. package/lib/eval/preflight-checker.ts +0 -0
  99. package/lib/eval/task-domain-classifier.ts +0 -0
  100. package/lib/eval/task-instruction-parser.ts +0 -0
  101. package/lib/eval/verifier-runner.ts +0 -0
  102. package/lib/event-bus.d.ts +0 -0
  103. package/lib/event-bus.mts +1 -1
  104. package/lib/governance-context-selector.ts +0 -0
  105. package/lib/graph/generate-extension-embeddings.js +0 -0
  106. package/lib/graph/generate-static-embeddings.js +0 -0
  107. package/lib/graph/lib/utils.js +1 -1
  108. package/lib/graph-audit.d.ts +0 -0
  109. package/lib/graph-availability.js +62 -0
  110. package/lib/hbo-core-store.compiled.js +834 -0
  111. package/lib/hbo-core-store.js +124 -0
  112. package/lib/hbo-core-store.ts +908 -0
  113. package/lib/mesh-circuit-breaker.js +0 -0
  114. package/lib/mission-loop/lesson-extractor.ts +0 -0
  115. package/lib/mission-loop/mental-model-scorer.ts +0 -0
  116. package/lib/mission-loop/occ-detector.ts +0 -0
  117. package/lib/mission-loop/query-variants.ts +0 -0
  118. package/lib/mission-loop/verifier-check.ts +0 -0
  119. package/lib/skill-reference-builder.ts +0 -0
  120. package/lib/telemetry/token-breakdown.ts +0 -0
  121. package/lib/tool-compressor.ts +0 -0
  122. package/lib/triage-core/classifier.ts +3 -2
  123. package/lib/triage-core/graph/schema.cypher +10 -0
  124. package/lib/triage-core/legal-routing.ts +0 -0
  125. package/lib/triage-core/mental-model/dunbar-classifier.ts +0 -0
  126. package/lib/triage-core/mental-model/enrich-all.ts +0 -0
  127. package/lib/triage-core/mental-model/identity-resolver.ts +0 -0
  128. package/lib/triage-core/mental-model/key-facts.ts +1 -2
  129. package/lib/triage-core/mental-model/model-assembler.ts +0 -0
  130. package/lib/triage-core/orchestrator.ts +4 -11
  131. package/lib/triage-core/orchestrator.ts.bak-r005-r006-r008 +0 -0
  132. package/package.json +18 -8
  133. package/skills/helios-business-operator/services/signals/upwork-signals.js +0 -0
  134. package/skills/talisman-ceo/SKILL.md +23 -25
  135. package/skills/talisman-comms/SKILL.md +5 -5
  136. package/skills/talisman-engineering/SKILL.md +5 -5
  137. package/skills/talisman-finance/SKILL.md +10 -8
  138. package/skills/talisman-marketing/SKILL.md +10 -10
  139. package/skills/talisman-sales/SKILL.md +12 -15
  140. package/skills/talisman-support/SKILL.md +5 -5
  141. package/agents/business/talisman-ceo.md +0 -183
  142. package/agents/business/talisman-comms.md +0 -257
  143. package/agents/business/talisman-cto.md +0 -153
  144. package/agents/business/talisman-finance.md +0 -246
  145. package/agents/business/talisman-marketing.md +0 -240
  146. package/agents/business/talisman-sales.md +0 -242
  147. package/agents/business/talisman-support.md +0 -236
  148. package/daemon/lib/approval-expiry.js +0 -162
  149. package/daemon/lib/blast-radius-analyzer.js +0 -75
  150. package/daemon/lib/domain-bootstrap-orchestrator.js +0 -267
  151. package/daemon/lib/forensic-log.js +0 -113
  152. package/daemon/lib/goal-research-pipeline.js +0 -644
  153. package/daemon/lib/harada/cascade-research-dispatcher.js +0 -261
  154. package/daemon/lib/headroom-middleware.js +0 -167
  155. package/daemon/lib/headroom-proxy-manager.js +0 -623
  156. package/daemon/lib/mental-model-cache.js +0 -96
  157. package/daemon/lib/project-factory.js +0 -47
  158. package/daemon/lib/session-log-reader.js +0 -93
  159. package/daemon/routes/hed.js +0 -133
  160. package/lib/graph/learning/headroom-learn-bridge.js +0 -215
  161. package/skills/helios-bookkeeping/SKILL.md +0 -321
  162. package/skills/helios-briefer/SKILL.md +0 -44
  163. package/skills/helios-client-relations/SKILL.md +0 -322
  164. package/skills/helios-personal-triager/SKILL.md +0 -45
  165. package/skills/helios-recruitment/SKILL.md +0 -317
  166. package/skills/helios-relationship-nudger/SKILL.md +0 -77
  167. package/skills/helios-researcher/SKILL.md +0 -44
  168. package/skills/helios-scheduler/SKILL.md +0 -58
  169. package/skills/helios-tax-analyst/SKILL.md +0 -280
@@ -8,55 +8,8 @@
8
8
  */
9
9
 
10
10
  const https = require('https');
11
- const http = require('http');
12
11
 
13
- // ── Headroom proxy routing helper ────────────────────────────────────────────
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 _r = _buildAnthropicOpts(apiKey, bodyLen);
85
- var req = _r.module.request(_r.opts, function (resp) {
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 };
@@ -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
- this._log('warn', 'tickGoalDecompose: No SystemAim — skipping (requires human to set via POST /api/system-aim)');
720
- return;
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 business-goal-service.js
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 = `Goal ID: ${goal.id}\nGoal: ${goal.title}\nDescription: ${goal.description}\n\nReview the work quality for each pillar assignment for this goal. Verify that outputs meet the Definition of Done, department standards, and strategic alignment. Escalate any quality gaps via Andon.`;
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.assigneeId AS assigneeId,
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 = row[0] ?? row['id'];
995
- const title = row[1] ?? row['title'];
996
- const assigneeId = row[2] ?? row['assigneeId'];
997
- const priority = row[3] ?? row['priority'] ?? 'medium';
998
- const goalId = row[4] ?? row['goalId'];
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: `Goal sync task.\nGoal: ${title}\nReview this goal's current status and determine the highest-value next action.`,
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,
@@ -1,306 +1,24 @@
1
1
  'use strict';
2
-
3
- const { randomUUID } = require('crypto');
4
-
5
2
  /**
6
- * HEDEngine — Helios Execution Document orchestration.
7
- * A HED is a PlanChangeProposal {scope:'execution'} with wave-based CRUD operations.
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(mg) {
13
- this._mg = 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
- // Find approved HEDs for this company that are executing
225
- const heds = await this._mg(
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 (pcp:PlanChangeProposal {id: $hedId}) SET pcp.status = 'review'`,
254
- { hedId }
16
+ `MATCH (w:HEDWave {companyId: $cid, status: 'pending'}) RETURN w.id LIMIT 1`,
17
+ { cid: companyId }
255
18
  );
256
- // Publish PLAN_STALLED to mesh bus — fires the already-wired reflexion loop subscriber
257
- try {
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
- const host = new HITLHost({ companyId, taskTitle, mgQuery, taskId, ctx: { isInterpretation: opts.isInterpretation, interpretationDeclaration: _getDecl ? undefined : opts.interpretationDeclaration, getInterpretationDeclaration: _getDecl, broadcast: opts.broadcast || null } });
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