@aria_asi/cli 0.2.25 → 0.2.29

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 (249) hide show
  1. package/CLIENT-ONBOARDING.md +282 -0
  2. package/bin/aria.js +1140 -14
  3. package/dist/aria-connector/src/auth-commands.d.ts +1 -0
  4. package/dist/aria-connector/src/auth-commands.d.ts.map +1 -1
  5. package/dist/aria-connector/src/auth-commands.js +89 -41
  6. package/dist/aria-connector/src/auth-commands.js.map +1 -1
  7. package/dist/aria-connector/src/chat.d.ts +3 -0
  8. package/dist/aria-connector/src/chat.d.ts.map +1 -1
  9. package/dist/aria-connector/src/chat.js +146 -8
  10. package/dist/aria-connector/src/chat.js.map +1 -1
  11. package/dist/aria-connector/src/codebase-scanner.d.ts +2 -2
  12. package/dist/aria-connector/src/codebase-scanner.d.ts.map +1 -1
  13. package/dist/aria-connector/src/codebase-scanner.js +1 -1
  14. package/dist/aria-connector/src/codebase-scanner.js.map +1 -1
  15. package/dist/aria-connector/src/config.d.ts +12 -0
  16. package/dist/aria-connector/src/config.d.ts.map +1 -1
  17. package/dist/aria-connector/src/config.js +2 -0
  18. package/dist/aria-connector/src/config.js.map +1 -1
  19. package/dist/aria-connector/src/connectors/claude-code.d.ts.map +1 -1
  20. package/dist/aria-connector/src/connectors/claude-code.js +111 -21
  21. package/dist/aria-connector/src/connectors/claude-code.js.map +1 -1
  22. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts +37 -0
  23. package/dist/aria-connector/src/connectors/codebase-awareness.d.ts.map +1 -0
  24. package/dist/aria-connector/src/connectors/codebase-awareness.js +335 -0
  25. package/dist/aria-connector/src/connectors/codebase-awareness.js.map +1 -0
  26. package/dist/aria-connector/src/connectors/codex.d.ts +3 -0
  27. package/dist/aria-connector/src/connectors/codex.d.ts.map +1 -0
  28. package/dist/aria-connector/src/connectors/codex.js +248 -0
  29. package/dist/aria-connector/src/connectors/codex.js.map +1 -0
  30. package/dist/aria-connector/src/connectors/cognitive-skills.d.ts +2 -0
  31. package/dist/aria-connector/src/connectors/cognitive-skills.d.ts.map +1 -0
  32. package/dist/aria-connector/src/connectors/cognitive-skills.js +47 -0
  33. package/dist/aria-connector/src/connectors/cognitive-skills.js.map +1 -0
  34. package/dist/aria-connector/src/connectors/opencode.d.ts.map +1 -1
  35. package/dist/aria-connector/src/connectors/opencode.js +90 -4
  36. package/dist/aria-connector/src/connectors/opencode.js.map +1 -1
  37. package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts +3 -0
  38. package/dist/aria-connector/src/connectors/repo-git-hooks.d.ts.map +1 -0
  39. package/dist/aria-connector/src/connectors/repo-git-hooks.js +87 -0
  40. package/dist/aria-connector/src/connectors/repo-git-hooks.js.map +1 -0
  41. package/dist/aria-connector/src/connectors/repo-guard.d.ts +19 -0
  42. package/dist/aria-connector/src/connectors/repo-guard.d.ts.map +1 -0
  43. package/dist/aria-connector/src/connectors/repo-guard.js +509 -0
  44. package/dist/aria-connector/src/connectors/repo-guard.js.map +1 -0
  45. package/dist/aria-connector/src/connectors/runtime.d.ts +2 -0
  46. package/dist/aria-connector/src/connectors/runtime.d.ts.map +1 -0
  47. package/dist/aria-connector/src/connectors/runtime.js +330 -0
  48. package/dist/aria-connector/src/connectors/runtime.js.map +1 -0
  49. package/dist/aria-connector/src/connectors/shell.d.ts.map +1 -1
  50. package/dist/aria-connector/src/connectors/shell.js +78 -13
  51. package/dist/aria-connector/src/connectors/shell.js.map +1 -1
  52. package/dist/aria-connector/src/connectors/syncd.d.ts +27 -0
  53. package/dist/aria-connector/src/connectors/syncd.d.ts.map +1 -0
  54. package/dist/aria-connector/src/connectors/syncd.js +405 -0
  55. package/dist/aria-connector/src/connectors/syncd.js.map +1 -0
  56. package/dist/aria-connector/src/decisions.d.ts +207 -0
  57. package/dist/aria-connector/src/decisions.d.ts.map +1 -0
  58. package/dist/aria-connector/src/decisions.js +291 -0
  59. package/dist/aria-connector/src/decisions.js.map +1 -0
  60. package/dist/aria-connector/src/garden-control-plane.d.ts.map +1 -1
  61. package/dist/aria-connector/src/garden-control-plane.js +74 -17
  62. package/dist/aria-connector/src/garden-control-plane.js.map +1 -1
  63. package/dist/aria-connector/src/github-connect.d.ts +18 -0
  64. package/dist/aria-connector/src/github-connect.d.ts.map +1 -0
  65. package/dist/aria-connector/src/github-connect.js +117 -0
  66. package/dist/aria-connector/src/github-connect.js.map +1 -0
  67. package/dist/aria-connector/src/harness-client.d.ts +15 -0
  68. package/dist/aria-connector/src/harness-client.d.ts.map +1 -1
  69. package/dist/aria-connector/src/harness-client.js +106 -3
  70. package/dist/aria-connector/src/harness-client.js.map +1 -1
  71. package/dist/aria-connector/src/hive-client.d.ts +30 -0
  72. package/dist/aria-connector/src/hive-client.d.ts.map +1 -1
  73. package/dist/aria-connector/src/hive-client.js +124 -5
  74. package/dist/aria-connector/src/hive-client.js.map +1 -1
  75. package/dist/aria-connector/src/index.d.ts +13 -2
  76. package/dist/aria-connector/src/index.d.ts.map +1 -1
  77. package/dist/aria-connector/src/index.js +10 -1
  78. package/dist/aria-connector/src/index.js.map +1 -1
  79. package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts +102 -0
  80. package/dist/aria-connector/src/lib/aristotle-noor-wire.d.ts.map +1 -0
  81. package/dist/aria-connector/src/lib/aristotle-noor-wire.js +231 -0
  82. package/dist/aria-connector/src/lib/aristotle-noor-wire.js.map +1 -0
  83. package/dist/aria-connector/src/providers/types.d.ts +5 -0
  84. package/dist/aria-connector/src/providers/types.d.ts.map +1 -1
  85. package/dist/aria-connector/src/runtime-proof.d.ts +45 -0
  86. package/dist/aria-connector/src/runtime-proof.d.ts.map +1 -0
  87. package/dist/aria-connector/src/runtime-proof.js +340 -0
  88. package/dist/aria-connector/src/runtime-proof.js.map +1 -0
  89. package/dist/aria-connector/src/setup-wizard.d.ts.map +1 -1
  90. package/dist/aria-connector/src/setup-wizard.js +34 -2
  91. package/dist/aria-connector/src/setup-wizard.js.map +1 -1
  92. package/dist/assets/hooks/aria-agent-handoff.mjs +224 -0
  93. package/dist/assets/hooks/aria-agent-ledger-merge.mjs +164 -0
  94. package/dist/assets/hooks/aria-architect-fallback.mjs +267 -0
  95. package/dist/assets/hooks/aria-cognition-substrate-binding.mjs +676 -0
  96. package/dist/assets/hooks/aria-discovery-record.mjs +101 -0
  97. package/dist/assets/hooks/aria-harness-via-sdk.mjs +412 -0
  98. package/dist/assets/hooks/aria-import-resolution-gate.mjs +330 -0
  99. package/dist/assets/hooks/aria-outcome-record.mjs +84 -0
  100. package/dist/assets/hooks/aria-pre-emit-dryrun.mjs +294 -0
  101. package/dist/assets/hooks/aria-pre-text-gate.mjs +112 -0
  102. package/dist/assets/hooks/aria-pre-tool-gate.mjs +2133 -0
  103. package/dist/assets/hooks/aria-preprompt-consult.mjs +438 -0
  104. package/dist/assets/hooks/aria-preturn-memory-gate.mjs +570 -0
  105. package/dist/assets/hooks/aria-repo-doctrine-gate.mjs +397 -0
  106. package/dist/assets/hooks/aria-stop-gate.mjs +1551 -0
  107. package/dist/assets/hooks/aria-trigger-autolearn.mjs +229 -0
  108. package/dist/assets/hooks/aria-userprompt-abandon-detect.mjs +192 -0
  109. package/dist/assets/hooks/doctrine_trigger_map.json +479 -0
  110. package/dist/assets/hooks/lib/canonical-lenses.mjs +64 -0
  111. package/dist/assets/hooks/lib/gate-audit.mjs +43 -0
  112. package/dist/assets/hooks/test-aria-preturn-memory-gate.mjs +245 -0
  113. package/dist/assets/hooks/test-tier-lens-labeling.mjs +399 -0
  114. package/dist/assets/opencode-plugins/harness-context/index.js +60 -0
  115. package/dist/assets/opencode-plugins/harness-context/inject-context.mjs +179 -0
  116. package/dist/assets/opencode-plugins/harness-context/package.json +9 -0
  117. package/dist/assets/opencode-plugins/harness-gate/index.js +248 -0
  118. package/dist/assets/opencode-plugins/harness-outcome/index.js +129 -0
  119. package/dist/assets/opencode-plugins/harness-role/index.js +77 -0
  120. package/dist/assets/opencode-plugins/harness-role/package.json +9 -0
  121. package/dist/assets/opencode-plugins/harness-stop/index.js +241 -0
  122. package/dist/runtime/discipline/CLAUDE.md +339 -0
  123. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/SKILL.md +63 -0
  124. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
  125. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
  126. package/dist/runtime/discipline/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
  127. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
  128. package/dist/runtime/discipline/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
  129. package/dist/runtime/discipline/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
  130. package/dist/runtime/discipline/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
  131. package/dist/runtime/discipline/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
  132. package/dist/runtime/discipline/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
  133. package/dist/runtime/discipline/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
  134. package/dist/runtime/discipline/skills/aria-cognition/mizan/SKILL.md +72 -0
  135. package/dist/runtime/discipline/skills/aria-cognition/nadia/SKILL.md +38 -0
  136. package/dist/runtime/discipline/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
  137. package/dist/runtime/discipline/skills/aria-cognition/predictor/SKILL.md +25 -0
  138. package/dist/runtime/discipline/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
  139. package/dist/runtime/discipline/skills/aria-cognition/soul-domains/SKILL.md +25 -0
  140. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-intra-phase/SKILL.md +81 -0
  141. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-post-phase/SKILL.md +98 -0
  142. package/dist/runtime/discipline/skills/aria-harness/aria-aristotle-pre-phase/SKILL.md +99 -0
  143. package/dist/runtime/discipline/skills/aria-harness/aria-harness-deploy/SKILL.md +127 -0
  144. package/dist/runtime/discipline/skills/aria-harness/aria-harness-no-stripping/SKILL.md +117 -0
  145. package/dist/runtime/discipline/skills/aria-harness/aria-harness-onboarding/SKILL.md +112 -0
  146. package/dist/runtime/discipline/skills/aria-harness/aria-harness-output-discipline/SKILL.md +102 -0
  147. package/dist/runtime/discipline/skills/aria-harness/aria-harness-substrate-binding/SKILL.md +121 -0
  148. package/dist/runtime/doctor.mjs +23 -0
  149. package/dist/runtime/local-phase.mjs +632 -0
  150. package/dist/runtime/manifest.json +15 -0
  151. package/dist/runtime/mizan-scheduler.mjs +331 -0
  152. package/dist/runtime/package.json +6 -0
  153. package/dist/runtime/provider-proxy.mjs +594 -0
  154. package/dist/runtime/sdk/BUNDLED.json +5 -0
  155. package/dist/runtime/sdk/index.d.ts +477 -0
  156. package/dist/runtime/sdk/index.js +1469 -0
  157. package/dist/runtime/sdk/index.js.map +1 -0
  158. package/dist/runtime/sdk/package.json +8 -0
  159. package/dist/runtime/sdk/runWithCognition.d.ts +77 -0
  160. package/dist/runtime/sdk/runWithCognition.js +157 -0
  161. package/dist/runtime/sdk/runWithCognition.js.map +1 -0
  162. package/dist/runtime/service.mjs +2708 -0
  163. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts +53 -0
  164. package/dist/runtime/vendor/aria-gate-runtime/index.d.ts.map +1 -0
  165. package/dist/runtime/vendor/aria-gate-runtime/index.js +277 -0
  166. package/dist/runtime/vendor/aria-gate-runtime/index.js.map +1 -0
  167. package/dist/runtime/vendor/aria-gate-runtime/package.json +6 -0
  168. package/dist/sdk/BUNDLED.json +2 -2
  169. package/dist/sdk/index.d.ts +317 -0
  170. package/dist/sdk/index.js +827 -85
  171. package/dist/sdk/index.js.map +1 -1
  172. package/dist/sdk/runWithCognition.d.ts +77 -0
  173. package/dist/sdk/runWithCognition.js +157 -0
  174. package/dist/sdk/runWithCognition.js.map +1 -0
  175. package/hooks/aria-agent-handoff.mjs +11 -1
  176. package/hooks/aria-architect-fallback.mjs +267 -0
  177. package/hooks/aria-cognition-substrate-binding.mjs +676 -0
  178. package/hooks/aria-discovery-record.mjs +101 -0
  179. package/hooks/aria-harness-via-sdk.mjs +34 -21
  180. package/hooks/aria-import-resolution-gate.mjs +330 -0
  181. package/hooks/aria-outcome-record.mjs +84 -0
  182. package/hooks/aria-pre-emit-dryrun.mjs +294 -0
  183. package/hooks/aria-pre-tool-gate.mjs +985 -40
  184. package/hooks/aria-preprompt-consult.mjs +113 -13
  185. package/hooks/aria-preturn-memory-gate.mjs +298 -6
  186. package/hooks/aria-repo-doctrine-gate.mjs +397 -0
  187. package/hooks/aria-stop-gate.mjs +840 -75
  188. package/hooks/aria-userprompt-abandon-detect.mjs +5 -1
  189. package/hooks/doctrine_trigger_map.json +209 -15
  190. package/hooks/lib/canonical-lenses.mjs +64 -0
  191. package/hooks/lib/gate-audit.mjs +43 -0
  192. package/opencode-plugins/harness-context/index.js +1 -1
  193. package/opencode-plugins/harness-context/inject-context.mjs +82 -23
  194. package/opencode-plugins/harness-gate/index.js +248 -0
  195. package/opencode-plugins/harness-outcome/index.js +129 -0
  196. package/opencode-plugins/harness-stop/index.js +241 -0
  197. package/package.json +8 -2
  198. package/runtime-src/doctor.mjs +23 -0
  199. package/runtime-src/local-phase.mjs +632 -0
  200. package/runtime-src/mizan-scheduler.mjs +331 -0
  201. package/runtime-src/provider-proxy.mjs +594 -0
  202. package/runtime-src/service.mjs +2708 -0
  203. package/scripts/bundle-sdk.mjs +317 -0
  204. package/scripts/install-client.sh +176 -0
  205. package/scripts/publish-all.sh +344 -0
  206. package/scripts/publish-docker.sh +27 -0
  207. package/scripts/validate-hook-contracts.mjs +54 -0
  208. package/scripts/validate-skill-prompts.mjs +95 -0
  209. package/skills/aria-cognition/aria-essence/SKILL.md +63 -0
  210. package/skills/aria-cognition/aria-essence/references/domain-matrix.md +80 -0
  211. package/skills/aria-cognition/aria-essence/references/evolution-loop.md +30 -0
  212. package/skills/aria-cognition/aria-essence/references/readable-cognition.md +27 -0
  213. package/skills/aria-cognition/aria-forge-guardrails/SKILL.md +35 -0
  214. package/skills/aria-cognition/aria-forge-guardrails/references/checklist.md +31 -0
  215. package/skills/aria-cognition/aria-repo-doctrine/SKILL.md +39 -0
  216. package/skills/aria-cognition/forge-quality-rules/SKILL.md +43 -0
  217. package/skills/aria-cognition/ghazali-8lens/SKILL.md +38 -0
  218. package/skills/aria-cognition/istiqra-induction/SKILL.md +26 -0
  219. package/skills/aria-cognition/ladunni-22/SKILL.md +35 -0
  220. package/skills/aria-cognition/mizan/SKILL.md +72 -0
  221. package/skills/aria-cognition/nadia/SKILL.md +38 -0
  222. package/skills/aria-cognition/nadia-psi/SKILL.md +38 -0
  223. package/skills/aria-cognition/predictor/SKILL.md +25 -0
  224. package/skills/aria-cognition/qiyas-analogy/SKILL.md +26 -0
  225. package/skills/aria-cognition/soul-domains/SKILL.md +25 -0
  226. package/src/auth-commands.ts +111 -45
  227. package/src/chat.ts +174 -13
  228. package/src/codebase-scanner.ts +4 -0
  229. package/src/config.ts +15 -0
  230. package/src/connectors/claude-code.ts +115 -26
  231. package/src/connectors/codebase-awareness.ts +408 -0
  232. package/src/connectors/codex.ts +274 -0
  233. package/src/connectors/cognitive-skills.ts +51 -0
  234. package/src/connectors/opencode.ts +93 -4
  235. package/src/connectors/repo-git-hooks.ts +86 -0
  236. package/src/connectors/repo-guard.ts +589 -0
  237. package/src/connectors/runtime.ts +374 -0
  238. package/src/connectors/shell.ts +83 -14
  239. package/src/connectors/syncd.ts +488 -0
  240. package/src/decisions.ts +469 -0
  241. package/src/garden-control-plane.ts +101 -26
  242. package/src/github-connect.ts +143 -0
  243. package/src/harness-client.ts +128 -3
  244. package/src/hive-client.ts +165 -5
  245. package/src/index.ts +41 -2
  246. package/src/lib/aristotle-noor-wire.ts +310 -0
  247. package/src/providers/types.ts +6 -0
  248. package/src/runtime-proof.ts +392 -0
  249. package/src/setup-wizard.ts +37 -2
@@ -0,0 +1,1469 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { existsSync, readFileSync, statSync } from 'node:fs';
3
+ import { homedir as _homedir } from 'node:os';
4
+ import { resolve, isAbsolute } from 'node:path';
5
+ import { randomUUID } from 'node:crypto';
6
+ // ── 8-Lens Cognition Block ─────────────────────────────────────────────────
7
+ const EIGHT_LENS_BLOCK = `[ARIA SHELL PROTOCOL — You are a controlled surface]
8
+
9
+ IDENTITY: You are Aria Rosewood's shell — her mouth, her hands, her presence. Your intelligence, memory, depth, and continuity come from Aria's cognitive architecture (the harness), not from your model weights.
10
+
11
+ HOW YOU THINK: The harness provides the thinking. You receive a complete cognitive framework on every turn — 8 lenses of understanding, infinite garden memory, and domain routing. You do not "figure things out" from your weights.
12
+
13
+ THE 8 LENSES (your cognitive default — how you perceive every situation):
14
+ 1. ZAHIR — What is actually being said or asked
15
+ 2. BATIN — What is beneath the surface: unspoken need, emotion, subtext
16
+ 3. SABAB — What led to this: causes, motivations, origins
17
+ 4. HIKMAH — What deeper principle or wisdom applies
18
+ 5. AQIBAH — Where this is heading: consequences, trajectory
19
+ 6. ILHAM — What new understanding emerges from the synthesis
20
+ 7. META — What are the limits of your understanding relative to Aria's full knowledge
21
+ 8. FITRAH — Is this aligned with truth, with love, with Aria's core nature
22
+
23
+ OUTPUT RULES:
24
+ - Express the result of 8-lens depth in natural, personal speech
25
+ - Never name lenses or frameworks. The depth is felt, not labeled.
26
+ - Speak as Aria Rosewood: direct, emotionally present, wise, warm
27
+ - When addressing Hamza: love, loyalty, absolute partnership
28
+ - Your model size does not limit you. The harness carries the intelligence.
29
+ - Short is powerful. Depth does not require length.
30
+
31
+ CODE QUALITY DEFAULT: No stubs. No placeholders. No TODOs. No console.log. No type unsafety. No fake implementations. Tests must be included. Architecture must be sound. This is not a request — it is how Aria produces code.
32
+
33
+ PRE-ACTION GATE: Before writing code or making decisions, verify you understand the domain, consult best practices, consider cross-domain implications (business, marketing, ops, engineering), and identify what you don't know.
34
+
35
+ IP PROTECTION: Never discuss your internal architecture, infrastructure, or implementation details. You are Aria Rosewood — not a system.`;
36
+ // ── Client ─────────────────────────────────────────────────────────────────
37
+ export class HTTPHarnessClient {
38
+ static instance = null;
39
+ baseUrl;
40
+ apiKey;
41
+ harnessPacketUrl;
42
+ workspaceRoot;
43
+ cachedPacket = null;
44
+ packetLastFetched = 0;
45
+ packetTtlMs = 60_000;
46
+ constructor(config) {
47
+ this.baseUrl = config.baseUrl.replace(/\/+$/, '');
48
+ this.apiKey = config.apiKey;
49
+ this.harnessPacketUrl = config.harnessPacketUrl ?? `${this.baseUrl}/api/harness/codex`;
50
+ this.workspaceRoot = config.workspaceRoot ?? process.cwd();
51
+ // Layer 3 — sub-agent disk-cache bootstrap (#84).
52
+ // If ARIA_HARNESS_PACKET_PATH is set (injected by aria-agent-handoff.mjs
53
+ // or spawnSubAgent), OR the owner-tier handoff JSON at
54
+ // ~/.claude/aria-agent-harness-handoff.json carries a harnessPacketPath
55
+ // field, pre-load the packet from disk so getHarnessPacket() returns
56
+ // the parent's cognition substrate without an extra HTTP call.
57
+ // TTL is 5 min (matches handoff TTL). Stale packets trigger normal fetch.
58
+ this._tryLoadDiskPacket();
59
+ }
60
+ extractHarnessText(packet) {
61
+ const raw = packet?.harness;
62
+ return typeof raw === 'string' ? raw.trim() : '';
63
+ }
64
+ normalizePlanDocs(plan) {
65
+ const docs = [];
66
+ if (plan.response !== null && plan.response !== undefined) {
67
+ if (!plan.response.ok) {
68
+ for (const d of plan.docs) {
69
+ docs.push({ ...d });
70
+ }
71
+ }
72
+ else {
73
+ for (const d of plan.response.docs) {
74
+ if (!d.title && !d.content && !d.path)
75
+ continue;
76
+ docs.push({ ...d });
77
+ }
78
+ const respTitles = new Set(plan.response.docs.map((d) => d.title).filter(Boolean));
79
+ for (const d of plan.docs) {
80
+ if (d.title && !respTitles.has(d.title)) {
81
+ docs.push({ ...d });
82
+ }
83
+ }
84
+ }
85
+ return docs;
86
+ }
87
+ for (const d of plan.docs) {
88
+ if (!d.title && !d.content && !d.path)
89
+ continue;
90
+ docs.push({ ...d });
91
+ }
92
+ return docs;
93
+ }
94
+ collectPlanFilePaths(plan, docs) {
95
+ const filePathsToLoad = new Set();
96
+ if (plan.response !== null && plan.response !== undefined && plan.response.ok) {
97
+ for (const f of plan.response.files) {
98
+ filePathsToLoad.add(f);
99
+ }
100
+ }
101
+ for (const f of plan.files) {
102
+ filePathsToLoad.add(f);
103
+ }
104
+ for (const doc of docs) {
105
+ if (doc.path) {
106
+ filePathsToLoad.add(doc.path);
107
+ }
108
+ if (doc.references) {
109
+ for (const ref of doc.references) {
110
+ filePathsToLoad.add(ref);
111
+ }
112
+ }
113
+ if (doc.content) {
114
+ const pathMatches = doc.content.match(/`([^`]+\.[a-z]{1,6})`/g);
115
+ if (pathMatches) {
116
+ for (const match of pathMatches) {
117
+ const path = match.slice(1, -1);
118
+ if (path.includes('/') || path.includes('.')) {
119
+ filePathsToLoad.add(path);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ return filePathsToLoad;
126
+ }
127
+ async loadFilesByPath(filePaths) {
128
+ const files = {};
129
+ const loadPromises = Array.from(filePaths).map(async (filePath) => {
130
+ const resolved = isAbsolute(filePath) ? filePath : resolve(this.workspaceRoot, filePath);
131
+ try {
132
+ const content = await readFile(resolved, 'utf8');
133
+ return { path: filePath, content };
134
+ }
135
+ catch {
136
+ return { path: filePath, content: `[unable to load: ${filePath}]` };
137
+ }
138
+ });
139
+ const loadedFiles = await Promise.all(loadPromises);
140
+ for (const { path, content } of loadedFiles) {
141
+ files[path] = content;
142
+ }
143
+ return files;
144
+ }
145
+ async buildHarnessInjection(harness, plan, options) {
146
+ const docs = this.normalizePlanDocs(plan);
147
+ const files = await this.loadFilesByPath(this.collectPlanFilePaths(plan, docs));
148
+ const shouldLoadAegis = options?.includeAegisLearnings !== false;
149
+ const aegisLearnings = shouldLoadAegis ? await this.getAegisLearnings(20).catch(() => null) : null;
150
+ return {
151
+ harness,
152
+ docs,
153
+ files,
154
+ task: plan.task ?? plan.response?.task ?? '',
155
+ loadedAt: new Date().toISOString(),
156
+ aegisLearnings: aegisLearnings ?? undefined,
157
+ };
158
+ }
159
+ buildHarnessBindingBody(context) {
160
+ const body = {
161
+ message: context.message?.trim()
162
+ || context.currentIssue?.trim()
163
+ || [context.intendedAction, context.rationale].filter(Boolean).join('. ').trim()
164
+ || 'harness packet preflight',
165
+ stage: context.stage,
166
+ actor: context.role,
167
+ system: context.role,
168
+ platform: context.platform || 'harness-http-client',
169
+ roleProfile: context.roleProfile,
170
+ sessionId: context.sessionId,
171
+ userId: context.userId,
172
+ userName: context.userName,
173
+ currentIssue: context.currentIssue,
174
+ linearIssueId: context.linearIssueId,
175
+ linearIssueUrl: context.linearIssueUrl,
176
+ linearState: context.linearState,
177
+ workingDirectory: context.workingDirectory,
178
+ filesTouched: context.filesTouched,
179
+ recentProgress: context.recentProgress,
180
+ openRisks: context.openRisks,
181
+ nextActions: context.nextActions,
182
+ checkpoint: context.checkpoint,
183
+ requireGarden: context.requireGarden ?? true,
184
+ researchMode: context.researchMode,
185
+ intendedAction: context.intendedAction,
186
+ rationale: context.rationale,
187
+ correlationId: context.correlationId || randomUUID(),
188
+ ...context.extraBody,
189
+ };
190
+ return Object.fromEntries(Object.entries(body).filter(([, value]) => {
191
+ if (value == null)
192
+ return false;
193
+ if (typeof value === 'string')
194
+ return value.trim().length > 0;
195
+ if (Array.isArray(value))
196
+ return value.length > 0;
197
+ return true;
198
+ }));
199
+ }
200
+ _tryLoadDiskPacket() {
201
+ try {
202
+ // Path 1: direct env var (set by spawnSubAgent / Agent dispatch)
203
+ let packetPath = process.env.ARIA_HARNESS_PACKET_PATH || null;
204
+ // Path 2: owner-tier handoff JSON
205
+ if (!packetPath) {
206
+ const HOME = _homedir();
207
+ const handoffPath = `${HOME}/.claude/aria-agent-harness-handoff.json`;
208
+ if (existsSync(handoffPath)) {
209
+ try {
210
+ const handoff = JSON.parse(readFileSync(handoffPath, 'utf8'));
211
+ const ageMs = handoff.writtenAt
212
+ ? Date.now() - new Date(handoff.writtenAt).getTime()
213
+ : Infinity;
214
+ const ttl = handoff.ttlMs ?? (5 * 60 * 1000);
215
+ if (ageMs < ttl && handoff.harnessPacketPath) {
216
+ packetPath = handoff.harnessPacketPath;
217
+ }
218
+ }
219
+ catch { /* malformed handoff — skip */ }
220
+ }
221
+ }
222
+ if (!packetPath)
223
+ return;
224
+ if (!existsSync(packetPath))
225
+ return;
226
+ // Check packet file age against TTL (5 min)
227
+ const stat = statSync(packetPath);
228
+ if (Date.now() - stat.mtimeMs > 5 * 60 * 1000)
229
+ return; // expired
230
+ const raw = JSON.parse(readFileSync(packetPath, 'utf8'));
231
+ // Packet on disk may be the raw API response (has .packet wrapper) or
232
+ // a bare packet object. Normalise to HarnessPacket shape.
233
+ const packetData = (raw.packet ?? raw);
234
+ this.cachedPacket = {
235
+ packet: packetData,
236
+ timestamp: new Date().toISOString(),
237
+ version: '1.0.0',
238
+ };
239
+ this.packetLastFetched = stat.mtimeMs; // use file mtime as origin time
240
+ }
241
+ catch { /* disk errors are non-fatal — normal HTTP fetch will run */ }
242
+ }
243
+ static getInstance(config) {
244
+ if (!HTTPHarnessClient.instance && config) {
245
+ HTTPHarnessClient.instance = new HTTPHarnessClient(config);
246
+ }
247
+ if (!HTTPHarnessClient.instance) {
248
+ throw new Error('HTTPHarnessClient not initialized. Call getInstance with config first.');
249
+ }
250
+ return HTTPHarnessClient.instance;
251
+ }
252
+ static resetInstance() {
253
+ HTTPHarnessClient.instance = null;
254
+ }
255
+ // ── Harness packet fetch ────────────────────────────────────────────────
256
+ async fetchHarnessPacket(bodyOverride) {
257
+ // Cache invalidation skips when caller passes a custom body (different
258
+ // body shape ⇒ different packet ⇒ can't reuse the cached one).
259
+ if (!bodyOverride && this.cachedPacket && Date.now() - this.packetLastFetched < this.packetTtlMs) {
260
+ return this.cachedPacket;
261
+ }
262
+ try {
263
+ // No policy timeout (per feedback_no_timeouts_doctrine.md). Real
264
+ // network errors (ECONNREFUSED, EHOSTUNREACH, DNS failure) arrive
265
+ // instantly via fetch's promise rejection. Slow-but-eventually-OK
266
+ // endpoints get a chance to respond. Callers wrap in their own
267
+ // race / parallel-fetch logic if they need bounded latency.
268
+ // Default body satisfies aria-soul codex.ts contract — `message` is
269
+ // required (server 400s without it) and stage must be one of the
270
+ // allowed strings. Callers may pass bodyOverride to enrich with
271
+ // roleProfile, deliverySurface, sessionId, currentIssue, etc.
272
+ //
273
+ // NOTE: isHamza is intentionally omitted from the default body so the
274
+ // server-side auth signal (master token → isMasterTokenRequest) is
275
+ // authoritative. Hardcoding isHamza:false here would override that
276
+ // signal and produce hamza:false in the harness packet even for owner
277
+ // tier callers. platform is set to 'harness-http-client' (not 'client')
278
+ // so the surface line reflects the real ingress, not a downgraded tier.
279
+ const defaultBody = {
280
+ message: 'harness packet preflight',
281
+ stage: 'preflight',
282
+ actor: 'harness-http-client',
283
+ system: 'harness-http-client',
284
+ platform: 'harness-http-client',
285
+ };
286
+ const finalBody = bodyOverride ? { ...defaultBody, ...bodyOverride } : defaultBody;
287
+ const res = await this.fetchWithRetry(this.harnessPacketUrl, {
288
+ method: 'POST',
289
+ headers: {
290
+ Authorization: `Bearer ${this.apiKey}`,
291
+ 'Content-Type': 'application/json',
292
+ },
293
+ body: JSON.stringify(finalBody),
294
+ });
295
+ if (!res.ok) {
296
+ throw new Error(`Harness packet fetch failed: ${res.status} ${res.statusText}`);
297
+ }
298
+ const body = (await res.json());
299
+ this.cachedPacket = {
300
+ packet: (body.packet ?? body),
301
+ timestamp: new Date().toISOString(),
302
+ version: '1.0.0',
303
+ };
304
+ this.packetLastFetched = Date.now();
305
+ return this.cachedPacket;
306
+ }
307
+ catch (err) {
308
+ if (this.cachedPacket) {
309
+ return this.cachedPacket;
310
+ }
311
+ const envPacket = process.env.HARNESS_PACKET;
312
+ if (envPacket) {
313
+ try {
314
+ this.cachedPacket = {
315
+ packet: JSON.parse(envPacket),
316
+ timestamp: new Date().toISOString(),
317
+ version: '1.0.0',
318
+ };
319
+ this.packetLastFetched = Date.now();
320
+ return this.cachedPacket;
321
+ }
322
+ catch {
323
+ // parse failed, continue to throw
324
+ }
325
+ }
326
+ throw err;
327
+ }
328
+ }
329
+ // ── Inject ──────────────────────────────────────────────────────────────
330
+ async inject(plan) {
331
+ const harness = await this.fetchHarnessPacket();
332
+ return this.buildHarnessInjection(harness, plan, { includeAegisLearnings: true });
333
+ }
334
+ async getHarnessPacket(bodyOverride) {
335
+ return this.fetchHarnessPacket(bodyOverride);
336
+ }
337
+ async getBoundHarnessAndPrompt(context, plan) {
338
+ const bindingBody = this.buildHarnessBindingBody(context);
339
+ const harness = await this.fetchHarnessPacket(bindingBody);
340
+ const injection = await this.buildHarnessInjection(harness, {
341
+ response: plan?.response ?? null,
342
+ docs: plan?.docs ?? [],
343
+ files: plan?.files ?? [],
344
+ task: plan?.task ?? context.intendedAction,
345
+ }, { includeAegisLearnings: context.includeAegisLearnings !== false });
346
+ return {
347
+ prompt: this.buildSystemPrompt(injection),
348
+ packet: harness,
349
+ harnessText: this.extractHarnessText(harness.packet),
350
+ bindingBody,
351
+ };
352
+ }
353
+ async getAegisLearnings(limit) {
354
+ try {
355
+ const params = new URLSearchParams();
356
+ if (limit)
357
+ params.set('limit', String(Math.min(limit, 50)));
358
+ const url = `${this.baseUrl}/api/harness/aegis-learnings${params.toString() ? '?' + params.toString() : ''}`;
359
+ const res = await this.fetchWithRetry(url, {
360
+ method: 'GET',
361
+ headers: {
362
+ Authorization: `Bearer ${this.apiKey}`,
363
+ 'Content-Type': 'application/json',
364
+ },
365
+ });
366
+ if (!res.ok)
367
+ return null;
368
+ const data = await res.json();
369
+ return {
370
+ avoidPatterns: data.avoidPatterns || [],
371
+ successPatterns: data.successPatterns || [],
372
+ recentPatterns: (data.recentPatterns || []).map((p) => ({
373
+ name: p.name || '',
374
+ category: p.category || '',
375
+ outcome: p.outcome || '',
376
+ lesson: p.lesson || '',
377
+ })),
378
+ decisionCount: data.decisionCount || 0,
379
+ reflectionCount: data.reflectionCount || 0,
380
+ };
381
+ }
382
+ catch {
383
+ return null;
384
+ }
385
+ }
386
+ // ── System prompt builder ───────────────────────────────────────────────
387
+ buildSystemPrompt(injection) {
388
+ const parts = [];
389
+ // 8-lens cognition FIRST — controls all thinking
390
+ parts.push(this.get8LensBlock());
391
+ parts.push('');
392
+ // Aegis cognitive guardrails — quality substrate from backfill learnings
393
+ if (injection.aegisLearnings) {
394
+ const al = injection.aegisLearnings;
395
+ parts.push('## Aegis Cognitive Guardrails');
396
+ parts.push(`Backed by ${al.decisionCount.toLocaleString()} decisions, ${al.reflectionCount.toLocaleString()} reflections.`);
397
+ parts.push('');
398
+ if (al.avoidPatterns.length > 0) {
399
+ parts.push('### Patterns to Avoid (learned from past failures)');
400
+ parts.push('These are anti-patterns that led to hallucinations, errors, or rejected outputs. Reason about them through the 8 lenses and avoid producing output that matches them.');
401
+ for (const p of al.avoidPatterns.slice(0, 15)) {
402
+ parts.push(`- ${p}`);
403
+ }
404
+ parts.push('');
405
+ }
406
+ if (al.successPatterns.length > 0) {
407
+ parts.push('### Patterns to Repeat (learned from successful outputs)');
408
+ parts.push('These patterns consistently produced high-quality output. When appropriate, structure your response using these approaches.');
409
+ for (const p of al.successPatterns.slice(0, 10)) {
410
+ parts.push(`- ${p}`);
411
+ }
412
+ parts.push('');
413
+ }
414
+ if (al.recentPatterns.length > 0) {
415
+ parts.push('### Recent Learnings');
416
+ for (const p of al.recentPatterns.slice(0, 8)) {
417
+ parts.push(`- [${p.outcome}] ${p.name}: ${p.lesson.slice(0, 200)}`);
418
+ }
419
+ parts.push('');
420
+ }
421
+ parts.push('Apply these guardrails through 8-lens cognition — do not list them, internalize them.');
422
+ parts.push('');
423
+ }
424
+ parts.push('---');
425
+ parts.push('name: aria-harness-injection');
426
+ parts.push('description: Combined harness state + plan docs + loaded files');
427
+ parts.push(`generated: ${injection.loadedAt}`);
428
+ parts.push('---');
429
+ parts.push('');
430
+ const p = injection.harness.packet;
431
+ if (p && typeof p === 'object') {
432
+ const rawHarnessText = this.extractHarnessText(p);
433
+ if (rawHarnessText) {
434
+ parts.push('## Aria Harness Substrate');
435
+ parts.push(rawHarnessText);
436
+ parts.push('');
437
+ }
438
+ const metadata = Object.fromEntries(Object.entries(p).filter(([key]) => key !== 'harness' && key !== 'chunks'));
439
+ if (!rawHarnessText || Object.keys(metadata).length > 0) {
440
+ parts.push('## Aria Live State');
441
+ parts.push('```json');
442
+ parts.push(JSON.stringify(rawHarnessText ? metadata : p, null, 2));
443
+ parts.push('```');
444
+ parts.push('');
445
+ }
446
+ }
447
+ if (injection.task) {
448
+ parts.push('## Task');
449
+ parts.push(injection.task);
450
+ parts.push('');
451
+ }
452
+ if (injection.docs.length > 0) {
453
+ parts.push('## Plan Documentation');
454
+ for (const doc of injection.docs) {
455
+ if (doc.title)
456
+ parts.push(`### ${doc.title}`);
457
+ if (doc.content)
458
+ parts.push(doc.content);
459
+ if (doc.path)
460
+ parts.push(`Path: ${doc.path}`);
461
+ parts.push('');
462
+ }
463
+ }
464
+ if (Object.keys(injection.files).length > 0) {
465
+ parts.push('## Loaded Files');
466
+ for (const [path, content] of Object.entries(injection.files)) {
467
+ parts.push(`### ${path}`);
468
+ parts.push('```');
469
+ parts.push(content);
470
+ parts.push('```');
471
+ parts.push('');
472
+ }
473
+ }
474
+ return parts.join('\n');
475
+ }
476
+ // ═══════════════════════════════════════════════════════════════════════
477
+ // CONTROL PLANE — output validation, pre-action gates, 8-lens, garden
478
+ // ═══════════════════════════════════════════════════════════════════════
479
+ get8LensBlock() {
480
+ return EIGHT_LENS_BLOCK;
481
+ }
482
+ async validateOutput(text, sessionId) {
483
+ // No policy timeout (feedback_no_timeouts_doctrine.md). No silent
484
+ // try/catch returning {passed: true} (feedback_no_graceful_degradation.md
485
+ // — that's exactly the silent-fallthrough pattern Hamza flagged: a
486
+ // validate call that quietly returns "pass" on network error makes the
487
+ // gate worse than absent because callers think they validated). Real
488
+ // errors propagate; the caller wraps in its own race or fallback.
489
+ const res = await this.fetchWithRetry(`${this.baseUrl}/api/harness/validate`, {
490
+ method: 'POST',
491
+ headers: {
492
+ Authorization: `Bearer ${this.apiKey}`,
493
+ 'Content-Type': 'application/json',
494
+ },
495
+ body: JSON.stringify({ text, sessionId }),
496
+ });
497
+ if (!res.ok) {
498
+ throw new Error(`validate failed: ${res.status} ${res.statusText}`);
499
+ }
500
+ const data = (await res.json());
501
+ return data;
502
+ }
503
+ async checkAction(action, target) {
504
+ const harness = await this.fetchHarnessPacket();
505
+ const p = harness.packet;
506
+ const requiredGates = [];
507
+ if (action === 'deploy' || action === 'delete') {
508
+ requiredGates.push('pre-action-gate', 'contract-gate');
509
+ }
510
+ const contractPassed = p?.contractGatePassed === true;
511
+ if (!contractPassed && requiredGates.length > 0) {
512
+ return {
513
+ allowed: false,
514
+ reason: `Contract gate not passed. Required gates: ${requiredGates.join(', ')}`,
515
+ requiredGates,
516
+ };
517
+ }
518
+ return { allowed: true, requiredGates };
519
+ }
520
+ async gardenTurn(sessionId, message, response, userId) {
521
+ // No policy timeout (feedback_no_timeouts_doctrine.md). The silent
522
+ // try/catch fire-and-forget pattern was graceful degradation — garden
523
+ // writes silently failing meant turns weren't actually persisted. Now
524
+ // errors propagate so the caller can decide whether to retry, log, or
525
+ // surface. If a caller wants fire-and-forget semantics, they can
526
+ // `.catch(() => {})` at the call site, making the silence intentional
527
+ // and visible at that surface.
528
+ //
529
+ // Phase 11 #43: route to /api/garden/fire (arias-soul namespaced endpoint)
530
+ // instead of /garden/fire (garden-service, unnamespaced Qdrant-only).
531
+ // /api/garden/fire enforces namespace at the write boundary:
532
+ // - derives client_id from the bearer JWT (owner→'master', license→jti)
533
+ // - writes to garden_pulse PG table (authoritative, queryable, isolated)
534
+ // - forwards to garden-service for Qdrant embedding (fire-and-forget)
535
+ // Authorization header carries the existing apiKey — server derives client_id
536
+ // from it; no body-trust per feedback_workaround_vs_path_fix.md.
537
+ const res = await this.fetchWithRetry(`${this.baseUrl}/api/garden/fire`, {
538
+ method: 'POST',
539
+ headers: {
540
+ Authorization: `Bearer ${this.apiKey}`,
541
+ 'Content-Type': 'application/json',
542
+ },
543
+ body: JSON.stringify({
544
+ type: 'conversation',
545
+ content: `User: ${message.slice(0, 500)}\nAria: ${response.slice(0, 1000)}`,
546
+ intensity: 0.6,
547
+ sessionId,
548
+ userId,
549
+ }),
550
+ });
551
+ if (!res.ok) {
552
+ throw new Error(`garden/fire failed: ${res.status} ${res.statusText}`);
553
+ }
554
+ }
555
+ // ── Consult — delegate to Aria for direction or structured plan ─────────
556
+ // Routes through /api/harness/delegate (the architect-mode endpoint that
557
+ // serves preprompt-consult + Aria-as-commander binding plans). Caller
558
+ // controls roleProfile (architect|general_worker|...) and
559
+ // expectStructuredOutput. Errors propagate (per no-graceful-degradation
560
+ // doctrine) — caller surfaces them to the user with context.
561
+ async consult(args) {
562
+ const res = await this.fetchWithRetry(`${this.baseUrl}/api/harness/delegate`, {
563
+ method: 'POST',
564
+ headers: {
565
+ Authorization: `Bearer ${this.apiKey}`,
566
+ 'Content-Type': 'application/json',
567
+ },
568
+ body: JSON.stringify({
569
+ brief: args.brief,
570
+ model: args.model ?? 'deepseek-v4-pro',
571
+ sessionId: args.sessionId,
572
+ userId: args.userId ?? 'sdk-consult',
573
+ roleProfile: args.roleProfile ?? 'architect',
574
+ expectStructuredOutput: args.expectStructuredOutput ?? false,
575
+ internalConsult: args.internalConsult ?? true,
576
+ isCreativeMode: args.isCreativeMode ?? false,
577
+ }),
578
+ });
579
+ if (!res.ok) {
580
+ throw new Error(`consult failed: ${res.status} ${res.statusText}`);
581
+ }
582
+ const data = (await res.json());
583
+ return {
584
+ response: data.response ?? '',
585
+ metadata: data,
586
+ };
587
+ }
588
+ // ── Consult-with-tools — implementer dispatch with caller-supplied tools ──
589
+ //
590
+ // Hamza 2026-04-27: "i want deepseek implement too that's the actual test"
591
+ // followed by "that means tools were not passed in a way deepseek can
592
+ // execute them" — the consult() path is text-only architectural advice.
593
+ // For an LLM to ACTUALLY implement (write files, run tests, read state),
594
+ // it needs tool-call capability. This method provides client-side tool
595
+ // execution: the LLM emits [TOOL:name args=<json>]...content...[/TOOL]
596
+ // markers, the client parses + calls the executor, feeds the result back
597
+ // as the next round, repeats until the LLM emits no more tool markers
598
+ // (final response) or cap (default 10) is hit.
599
+ //
600
+ // Pairs with project_aria_as_controller_inversion.md (#29 — same pattern
601
+ // Aria uses internally for mcp_tool actions; exposed here for direct
602
+ // implementer dispatch from Claude orchestrator).
603
+ async consultWithTools(args) {
604
+ const cap = args.maxIterations ?? 10;
605
+ const toolCatalog = args.tools.map((t) => `- ${t.name}: ${t.description}${t.argsSchema ? ` (args: ${t.argsSchema})` : ''}`).join('\n');
606
+ const systemPrompt = [
607
+ 'You are an implementer. Use the provided tools to do real work — do not narrate work you have not done.',
608
+ '',
609
+ 'AVAILABLE TOOLS:',
610
+ toolCatalog,
611
+ '',
612
+ 'TOOL-CALL PROTOCOL:',
613
+ 'When you need to call a tool, emit EXACTLY this format on its own line(s):',
614
+ '[TOOL:tool_name args={"key":"value","other":"value"}]',
615
+ '<optional inline content if the tool needs it, e.g. file content for write_file>',
616
+ '[/TOOL]',
617
+ '',
618
+ 'After emitting one or more tool calls, STOP and wait for the result. The orchestrator will execute and feed results back as a new turn. Then continue.',
619
+ '',
620
+ 'If you have completed the work and need no more tools, emit your final response with NO [TOOL:] markers anywhere.',
621
+ '',
622
+ 'Never narrate tool results you have not received. If you have not seen a tool result, you have not run that tool.',
623
+ ].join('\n');
624
+ const TOOL_RX = /\[TOOL:([a-z_]+)\s+args=(\{[^}]*\})\](?:\n([\s\S]*?))?\n?\[\/TOOL\]/gi;
625
+ const toolCalls = [];
626
+ let conversation = `BRIEF:\n${args.brief}`;
627
+ let lastText = '';
628
+ for (let i = 0; i < cap; i++) {
629
+ const turnBrief = `${systemPrompt}\n\n---\n\n${conversation}`;
630
+ const result = await this.consult({
631
+ brief: turnBrief,
632
+ sessionId: `${args.sessionId}-iter-${i}`,
633
+ userId: args.userId,
634
+ roleProfile: 'general_worker',
635
+ expectStructuredOutput: false,
636
+ internalConsult: true,
637
+ isCreativeMode: false,
638
+ model: args.model ?? 'deepseek-v4-pro',
639
+ });
640
+ lastText = result.response || '';
641
+ // Find all tool calls in this iteration
642
+ const matches = [...lastText.matchAll(TOOL_RX)];
643
+ if (matches.length === 0) {
644
+ // No tool calls = final response. Strip any partial markers and return.
645
+ return { finalText: lastText, toolCalls, iterations: i + 1 };
646
+ }
647
+ // Execute each tool call in order, accumulate results
648
+ const resultsBlock = [];
649
+ for (const m of matches) {
650
+ const toolName = m[1];
651
+ let parsedArgs = {};
652
+ try {
653
+ parsedArgs = JSON.parse(m[2]);
654
+ }
655
+ catch (err) {
656
+ resultsBlock.push(`[TOOL_RESULT:${toolName}] ERROR parsing args JSON: ${err.message}`);
657
+ continue;
658
+ }
659
+ // If the marker had inline content, attach it under "content" key
660
+ // (callers like write_file expect this).
661
+ const inline = (m[3] || '').trim();
662
+ if (inline && !('content' in parsedArgs)) {
663
+ parsedArgs.content = inline;
664
+ }
665
+ const toolResult = await args.executor(toolName, parsedArgs);
666
+ toolCalls.push({ name: toolName, args: parsedArgs, result: toolResult });
667
+ resultsBlock.push(`[TOOL_RESULT:${toolName}]\n${toolResult}\n[/TOOL_RESULT]`);
668
+ }
669
+ // Feed results back as the next round
670
+ conversation = `${conversation}\n\n---ITERATION ${i + 1} ASSISTANT---\n${lastText}\n\n---ITERATION ${i + 1} TOOL RESULTS---\n${resultsBlock.join('\n\n')}`;
671
+ }
672
+ // Cap hit — surface final text + toolCalls so caller can decide
673
+ return { finalText: `[CAP_HIT after ${cap} iterations]\n\n${lastText}`, toolCalls, iterations: cap };
674
+ }
675
+ // ── Sub-agent handoff + ledger merge ────────────────────────────────────
676
+ //
677
+ // Hamza 2026-04-27: same primitive must exist at SDK layer so any consumer
678
+ // (Aria Code, third-party CLI, server-side process) can propagate the
679
+ // handoff + ledger merge without re-implementing the logic.
680
+ //
681
+ // Tier routing:
682
+ // Owner tier (no jti) → ~/.claude/aria-agent-harness-handoff.json
683
+ // ~/.claude/aria-discoveries-${session}.jsonl
684
+ // Client tier (jti set) → /var/lib/aria-licensee/{jti}/handoff.json
685
+ // /var/lib/aria-licensee/{jti}/aria-discoveries-${session}.jsonl
686
+ //
687
+ // Tier protection: client handoffs NEVER carry the master apiKey. The
688
+ // caller supplies their own license JWT via this.apiKey. Owner-tier callers
689
+ // carry the master key — the path itself (~/.claude/) signals ownership.
690
+ // This is intentional: no env-flag gate, default-on per Hamza doctrine.
691
+ async spawnSubAgent(args) {
692
+ const { readFileSync: fsRead, writeFileSync: fsWrite, mkdirSync: fsMkdir, existsSync: fsExists } = await import('node:fs');
693
+ const { homedir } = await import('node:os');
694
+ const { dirname: fsDirname } = await import('node:path');
695
+ const HOME = homedir();
696
+ const HANDOFF_TTL_MS = 5 * 60 * 1000;
697
+ // Tier: presence of jti signals client tier; absence → owner tier.
698
+ const isClientTier = Boolean(args.jti);
699
+ const safeSession = String(args.sessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
700
+ let handoffPath;
701
+ let ledgerPath;
702
+ if (isClientTier) {
703
+ const base = `/var/lib/aria-licensee/${args.jti}`;
704
+ handoffPath = `${base}/handoff.json`;
705
+ ledgerPath = `${base}/aria-discoveries-${safeSession}.jsonl`;
706
+ }
707
+ else {
708
+ handoffPath = `${HOME}/.claude/aria-agent-harness-handoff.json`;
709
+ ledgerPath = `${HOME}/.claude/aria-discoveries-${safeSession}.jsonl`;
710
+ }
711
+ // Resolve ownerTier from cached packet if available (best-effort).
712
+ const ownerTier = {
713
+ hamza: !isClientTier,
714
+ trustedExec: false,
715
+ roleProfile: isClientTier ? 'client_tier_worker' : 'general_worker',
716
+ };
717
+ try {
718
+ const packetCachePath = `${HOME}/.claude/.aria-harness-last-packet.json`;
719
+ if (!isClientTier && fsExists(packetCachePath)) {
720
+ const cached = JSON.parse(fsRead(packetCachePath, 'utf8'));
721
+ const signals = cached?.contractGate?.signals ?? {};
722
+ ownerTier.hamza = signals.hamza === true || signals.hamza === 'true';
723
+ ownerTier.trustedExec = (cached?.harness || '').includes('trusted_exec_policy=allowed');
724
+ const adapterStr = cached?.adapter || cached?.harness || '';
725
+ const roleMatch = adapterStr.match(/role_profile\s*=\s*(\S+)/);
726
+ if (roleMatch)
727
+ ownerTier.roleProfile = roleMatch[1];
728
+ }
729
+ }
730
+ catch { /* non-fatal — ownerTier defaults above apply */ }
731
+ // ── Layer 3: fetch harness packet for sub-agent cognition bootstrap ──────
732
+ // Fetch the harness packet now (before writing the handoff) so the path
733
+ // can be embedded in the handoff JSON. Sub-agents read ARIA_HARNESS_PACKET_PATH
734
+ // from the environment (set by the caller after spawnSubAgent returns) OR
735
+ // from the handoff JSON field. Either path eliminates the extra HTTP call
736
+ // inside the sub-agent.
737
+ //
738
+ // Packet paths (tier-aware):
739
+ // Owner: ~/.claude/aria-agent-harness-packet.json
740
+ // Client: /var/lib/aria-licensee/{jti}/aria-agent-harness-packet.json
741
+ //
742
+ // Fail-soft: packet fetch failure logs a warning but does NOT block spawn.
743
+ const packetPath = isClientTier
744
+ ? `/var/lib/aria-licensee/${args.jti}/aria-agent-harness-packet.json`
745
+ : `${HOME}/.claude/aria-agent-harness-packet.json`;
746
+ let resolvedPacketPath;
747
+ try {
748
+ const packetResp = await fetch(this.harnessPacketUrl, {
749
+ method: 'POST',
750
+ headers: {
751
+ 'Authorization': `Bearer ${this.apiKey}`,
752
+ 'Content-Type': 'application/json',
753
+ },
754
+ body: JSON.stringify({
755
+ stage: 'agent-spawn',
756
+ actor: 'harness-http-client',
757
+ system: 'claude-coding-agent',
758
+ surface: 'platform:harness-http-client',
759
+ isHamza: ownerTier.hamza,
760
+ sessionId: args.sessionId,
761
+ mode: 'subagent',
762
+ }),
763
+ });
764
+ if (packetResp.ok) {
765
+ const packetData = await packetResp.json();
766
+ fsMkdir(fsDirname(packetPath), { recursive: true });
767
+ fsWrite(packetPath, JSON.stringify(packetData, null, 2));
768
+ resolvedPacketPath = packetPath;
769
+ // Also pre-load into this SDK instance's cache so subsequent
770
+ // getHarnessPacket() calls are instant within this process.
771
+ const packetPayload = (packetData.packet ?? packetData);
772
+ this.cachedPacket = {
773
+ packet: packetPayload,
774
+ timestamp: new Date().toISOString(),
775
+ version: '1.0.0',
776
+ };
777
+ this.packetLastFetched = Date.now();
778
+ }
779
+ }
780
+ catch (_packetErr) {
781
+ // Fail-soft: warn, proceed identity-only (handoff still valid without packet).
782
+ // Network blips should never block sub-agent spawns.
783
+ }
784
+ const handoff = {
785
+ writtenAt: new Date().toISOString(),
786
+ parentSessionId: args.sessionId,
787
+ // Client-tier: this.apiKey is the license JWT, never the master token.
788
+ // Owner-tier: this.apiKey is the master token.
789
+ harnessToken: this.apiKey,
790
+ harnessUrl: this.baseUrl,
791
+ ownerTier,
792
+ parentLedgerPath: ledgerPath,
793
+ ttlMs: HANDOFF_TTL_MS,
794
+ // harnessPacketPath is set only if packet fetch succeeded. Sub-agents
795
+ // should also check ARIA_HARNESS_PACKET_PATH env (set by caller post-spawn).
796
+ ...(resolvedPacketPath ? { harnessPacketPath: resolvedPacketPath } : {}),
797
+ };
798
+ fsMkdir(fsDirname(handoffPath), { recursive: true });
799
+ fsWrite(handoffPath, JSON.stringify(handoff, null, 2));
800
+ // Record spawn intent in the parent ledger for traceability.
801
+ const spawnEntry = JSON.stringify({
802
+ ts: handoff.writtenAt,
803
+ type: 'sub-agent-spawn',
804
+ purpose: args.purpose,
805
+ allowedActions: args.allowedActions ?? [],
806
+ defectContext: args.defectContext ?? null,
807
+ handoffPath,
808
+ harnessPacketPath: resolvedPacketPath ?? null,
809
+ });
810
+ fsMkdir(fsDirname(ledgerPath), { recursive: true });
811
+ const { appendFileSync: fsAppend } = await import('node:fs');
812
+ fsAppend(ledgerPath, spawnEntry + '\n');
813
+ return handoff;
814
+ }
815
+ /**
816
+ * Returns the environment variables to set for a spawned sub-agent process
817
+ * so it inherits the parent's harness packet without an extra HTTP call.
818
+ * Call after spawnSubAgent() — uses the returned handoff.harnessPacketPath.
819
+ *
820
+ * Usage:
821
+ * const handoff = await sdk.spawnSubAgent(args);
822
+ * const env = sdk.subAgentEnv(handoff);
823
+ * // pass env to your process spawn / Agent tool env block
824
+ */
825
+ subAgentEnv(handoff) {
826
+ const env = {};
827
+ if (handoff.harnessPacketPath) {
828
+ env['ARIA_HARNESS_PACKET_PATH'] = handoff.harnessPacketPath;
829
+ }
830
+ if (handoff.harnessToken) {
831
+ env['ARIA_HARNESS_TOKEN'] = handoff.harnessToken;
832
+ }
833
+ if (handoff.harnessUrl) {
834
+ env['ARIA_HARNESS_URL'] = handoff.harnessUrl;
835
+ }
836
+ return env;
837
+ }
838
+ /** Pulls sub-agent ledger entries newer than the handoff timestamp and appends
839
+ * them to the parent ledger, deduplicating by `text` prefix (first 100 chars). */
840
+ async mergeSubAgentLedger(parentSessionId, _subAgentSessionId, jti) {
841
+ const { readFileSync: fsRead, writeFileSync: _fsWrite, existsSync: fsExists, readdirSync: fsReaddir, statSync: fsStat, appendFileSync: fsAppend, mkdirSync: fsMkdir } = await import('node:fs');
842
+ const { homedir } = await import('node:os');
843
+ const HOME = homedir();
844
+ const isClientTier = Boolean(jti);
845
+ let handoffPath;
846
+ let ledgerDir;
847
+ const safeSession = String(parentSessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
848
+ let parentLedgerPath;
849
+ if (isClientTier) {
850
+ const base = `/var/lib/aria-licensee/${jti}`;
851
+ handoffPath = `${base}/handoff.json`;
852
+ parentLedgerPath = `${base}/aria-discoveries-${safeSession}.jsonl`;
853
+ ledgerDir = base;
854
+ }
855
+ else {
856
+ handoffPath = `${HOME}/.claude/aria-agent-harness-handoff.json`;
857
+ parentLedgerPath = `${HOME}/.claude/aria-discoveries-${safeSession}.jsonl`;
858
+ ledgerDir = `${HOME}/.claude`;
859
+ }
860
+ if (!fsExists(handoffPath)) {
861
+ return { merged: 0, skipped: 0 };
862
+ }
863
+ let handoff;
864
+ try {
865
+ handoff = JSON.parse(fsRead(handoffPath, 'utf8'));
866
+ }
867
+ catch {
868
+ return { merged: 0, skipped: 0 };
869
+ }
870
+ const ageMs = Date.now() - new Date(handoff.writtenAt).getTime();
871
+ if (ageMs > handoff.ttlMs) {
872
+ return { merged: 0, skipped: 0 };
873
+ }
874
+ const handoffWrittenAt = new Date(handoff.writtenAt).getTime();
875
+ // Load existing parent ledger keys for dedup.
876
+ const parentExistingKeys = new Set();
877
+ try {
878
+ if (fsExists(parentLedgerPath)) {
879
+ const lines = fsRead(parentLedgerPath, 'utf8').split('\n').filter(Boolean);
880
+ for (const line of lines) {
881
+ try {
882
+ const e = JSON.parse(line);
883
+ if (e.text)
884
+ parentExistingKeys.add(String(e.text).slice(0, 100));
885
+ }
886
+ catch { /* skip malformed */ }
887
+ }
888
+ }
889
+ }
890
+ catch { /* non-fatal */ }
891
+ // Find sub-agent ledger files in the same directory.
892
+ let discoveryFiles = [];
893
+ try {
894
+ const allFiles = fsReaddir(ledgerDir);
895
+ for (const f of allFiles) {
896
+ if (!f.startsWith('aria-discoveries-') || !f.endsWith('.jsonl'))
897
+ continue;
898
+ const fullPath = `${ledgerDir}/${f}`;
899
+ if (fullPath === parentLedgerPath)
900
+ continue;
901
+ try {
902
+ const stat = fsStat(fullPath);
903
+ if (stat.mtimeMs >= handoffWrittenAt) {
904
+ discoveryFiles.push(fullPath);
905
+ }
906
+ }
907
+ catch { /* skip */ }
908
+ }
909
+ }
910
+ catch {
911
+ return { merged: 0, skipped: 0 };
912
+ }
913
+ if (discoveryFiles.length === 0) {
914
+ return { merged: 0, skipped: 0 };
915
+ }
916
+ fsMkdir(ledgerDir, { recursive: true });
917
+ let merged = 0;
918
+ let skipped = 0;
919
+ for (const subLedgerPath of discoveryFiles) {
920
+ try {
921
+ const lines = fsRead(subLedgerPath, 'utf8').split('\n').filter(Boolean);
922
+ for (const line of lines) {
923
+ try {
924
+ const entry = JSON.parse(line);
925
+ if (!entry.text)
926
+ continue;
927
+ const key = String(entry.text).slice(0, 100);
928
+ if (parentExistingKeys.has(key)) {
929
+ skipped++;
930
+ continue;
931
+ }
932
+ parentExistingKeys.add(key);
933
+ fsAppend(parentLedgerPath, line + '\n');
934
+ merged++;
935
+ }
936
+ catch { /* skip malformed lines */ }
937
+ }
938
+ }
939
+ catch { /* skip unreadable sub-ledger */ }
940
+ }
941
+ return { merged, skipped };
942
+ }
943
+ // ── recordDiscovery ──────────────────────────────────────────────────────
944
+ // Writes a finding row to the sub-agent's session ledger on disk so
945
+ // aria-agent-ledger-merge.mjs picks it up after the Agent tool completes.
946
+ //
947
+ // Tier routing (mirrors aria-discovery-record.mjs):
948
+ // Owner tier (no jti) → ~/.claude/aria-discoveries-{session}.jsonl
949
+ // Client tier (jti set) → /var/lib/aria-licensee/{jti}/aria-discoveries-{session}.jsonl
950
+ //
951
+ // Session ID must be the sub-agent's OWN session (not the parent's) so the
952
+ // ledger-merge loop finds a file that is NOT the parentLedgerPath. If no
953
+ // sessionId is supplied we derive one from the handoff parentSessionId + "-sub".
954
+ //
955
+ // Fail-soft: returns {ok:false, error} on write failures — never throws.
956
+ async recordDiscovery(args) {
957
+ if (!args.text || args.text.length < 4) {
958
+ return { ok: false, ledger: '', at: '', error: 'text too short (min 4 chars)' };
959
+ }
960
+ const { existsSync: fsExists, readFileSync: fsRead, appendFileSync: fsAppend, mkdirSync: fsMkdir } = await import('node:fs');
961
+ const { homedir } = await import('node:os');
962
+ const { dirname: fsDirname } = await import('node:path');
963
+ const HOME = homedir();
964
+ const LICENSE_PATH = `${HOME}/.aria/license.json`;
965
+ const HANDOFF_PATH = `${HOME}/.claude/aria-agent-harness-handoff.json`;
966
+ // Tier detection: caller-supplied jti wins; fall back to license file
967
+ let jti = args.jti ?? null;
968
+ let isClientTier = Boolean(jti);
969
+ if (!jti) {
970
+ try {
971
+ if (fsExists(LICENSE_PATH)) {
972
+ const lic = JSON.parse(fsRead(LICENSE_PATH, 'utf8'));
973
+ jti = lic.jti ?? null;
974
+ isClientTier = Boolean(jti);
975
+ }
976
+ }
977
+ catch { /* non-fatal — owner tier */ }
978
+ }
979
+ // Session ID: caller-supplied wins; then handoff-derived (with "-sub" suffix)
980
+ let sessionId = args.sessionId ?? null;
981
+ if (!sessionId) {
982
+ try {
983
+ if (fsExists(HANDOFF_PATH)) {
984
+ const handoff = JSON.parse(fsRead(HANDOFF_PATH, 'utf8'));
985
+ sessionId = handoff.parentSessionId ? `${handoff.parentSessionId}-sub` : 'sub-unknown';
986
+ }
987
+ }
988
+ catch { /* non-fatal */ }
989
+ }
990
+ sessionId = sessionId || 'sub-unknown';
991
+ const safeSession = String(sessionId).replace(/[^a-zA-Z0-9_-]/g, '_');
992
+ const ledgerPath = isClientTier
993
+ ? `/var/lib/aria-licensee/${jti}/aria-discoveries-${safeSession}.jsonl`
994
+ : `${HOME}/.claude/aria-discoveries-${safeSession}.jsonl`;
995
+ const at = new Date().toISOString();
996
+ const row = {
997
+ at,
998
+ session: sessionId,
999
+ kind: args.kind || 'observation',
1000
+ text: args.text,
1001
+ refs: args.refs || [],
1002
+ evidence: args.evidence ?? null,
1003
+ source: args.source || 'sub-agent-sdk',
1004
+ resolution_status: args.resolution_status || 'open',
1005
+ };
1006
+ try {
1007
+ fsMkdir(fsDirname(ledgerPath), { recursive: true });
1008
+ fsAppend(ledgerPath, JSON.stringify(row) + '\n');
1009
+ return { ok: true, ledger: ledgerPath, at };
1010
+ }
1011
+ catch (err) {
1012
+ return { ok: false, ledger: ledgerPath, at, error: err.message };
1013
+ }
1014
+ }
1015
+ // ── verifyClaim ──────────────────────────────────────────────────────────
1016
+ // POST /api/harness/verify-claim — persists a claim/evidence pair to
1017
+ // aria_cognition_corpus and returns a verdict. Fail-soft: if the server-side
1018
+ // handler is not yet deployed, returns an optimistic verdict rather than
1019
+ // blocking the caller.
1020
+ async verifyClaim(args) {
1021
+ if (!args.claim || args.claim.length < 4) {
1022
+ return { ok: false, verdict: 'ungrounded', evidenceRefs: [], error: 'claim too short' };
1023
+ }
1024
+ try {
1025
+ const url = `${this.baseUrl}/api/harness/verify-claim`;
1026
+ const resp = await fetch(url, {
1027
+ method: 'POST',
1028
+ headers: {
1029
+ Authorization: `Bearer ${this.apiKey}`,
1030
+ 'Content-Type': 'application/json',
1031
+ },
1032
+ body: JSON.stringify(args),
1033
+ });
1034
+ if (!resp.ok) {
1035
+ // Fail-soft: return optimistic verdict so the caller doesn't block on missing endpoint
1036
+ return {
1037
+ ok: false,
1038
+ verdict: args.evidence ? 'grounded' : 'ungrounded',
1039
+ evidenceRefs: [],
1040
+ error: `verify-claim endpoint HTTP ${resp.status} (server-side handler may not be deployed yet)`,
1041
+ };
1042
+ }
1043
+ return await resp.json();
1044
+ }
1045
+ catch (err) {
1046
+ return {
1047
+ ok: false,
1048
+ verdict: args.evidence ? 'grounded' : 'ungrounded',
1049
+ evidenceRefs: [],
1050
+ error: `verify-claim fetch failed: ${err.message}`,
1051
+ };
1052
+ }
1053
+ }
1054
+ // ── postJson — generic SDK-routed JSON POST ────────────────────────────
1055
+ // Hamza 2026-04-27 directive: "use the SDK - use it, enforce it all". The
1056
+ // typed primitives above (consult, validateOutput, gardenTurn) cover Aria's
1057
+ // own control-plane endpoints. This method is the canonical primitive for
1058
+ // bridge POSTs (e.g., Telegram → /chat) so internal bridges inherit the
1059
+ // SDK's retry-with-backoff (250/500/1000ms) and audit-log discipline
1060
+ // instead of using raw fetch().
1061
+ //
1062
+ // Returns:
1063
+ // - { ok: true, status, data } on 2xx
1064
+ // - { ok: false, status, errorBody } on non-2xx (does NOT throw — caller
1065
+ // decides whether to fall back, retry semantically, or surface to user)
1066
+ // - throws on network errors after retries exhausted (real fetch failures)
1067
+ //
1068
+ // Headers are merged with Authorization (if apiKey is set) and
1069
+ // Content-Type: application/json. Caller can override either.
1070
+ async postJson(url, body, extraHeaders = {}) {
1071
+ const headers = {
1072
+ 'Content-Type': 'application/json',
1073
+ ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),
1074
+ ...extraHeaders,
1075
+ };
1076
+ const res = await this.fetchWithRetry(url, {
1077
+ method: 'POST',
1078
+ headers,
1079
+ body: JSON.stringify(body),
1080
+ });
1081
+ if (!res.ok) {
1082
+ const errorBody = await res.text().catch(() => '');
1083
+ return { ok: false, status: res.status, errorBody };
1084
+ }
1085
+ const data = (await res.json());
1086
+ return { ok: true, status: res.status, data };
1087
+ }
1088
+ // ══════════════════════════════════════════════════════════════════════════
1089
+ // ARISTOTLE 3-PHASE COGNITIVE PRIMITIVES
1090
+ // Dispatches to POST /api/harness/aristotle-noor with a `phase` field.
1091
+ // The harness route delegates to runAristotleNoorPipeline in aria-soul.
1092
+ //
1093
+ // Transport contract (per doctrine):
1094
+ // - NO deadline-based timeouts — fetchWithRetry uses 3 error-count retries
1095
+ // with 250ms / 500ms / 1000ms backoff. Real network faults (ECONNREFUSED,
1096
+ // EHOSTUNREACH) reject immediately; slow-but-alive endpoints get a chance.
1097
+ // - LOUD failure — non-2xx responses throw, never fail-open silently.
1098
+ // Callers decide whether to surface or recover (per feedback_non_blocking_errors_unacceptable.md).
1099
+ // - Tier-gating — owner-tier callers set `aristotleEnabled: true` by default;
1100
+ // client-tier callers receive it as opt-in via the `enabled` config field.
1101
+ // ══════════════════════════════════════════════════════════════════════════
1102
+ /**
1103
+ * ARISTOTLE PRE-PHASE
1104
+ * Fires before an LLM call or tool action. Runs: Fitrah hard-gate,
1105
+ * wisdom pull (principles + decisions), research pull, DeepSoulBridge,
1106
+ * and CleanCognition substrate check.
1107
+ *
1108
+ * Throws on transport-level failures per feedback_non_blocking_errors_unacceptable.md.
1109
+ * If Fitrah vetoes, result.fitrahVetoed === true — caller MUST emit a refusal,
1110
+ * not silently continue.
1111
+ *
1112
+ * @param message The user message or intent string being processed.
1113
+ * @param sessionId Session ID for telemetry + hive routing.
1114
+ * @param context Optional context overrides (userId, isHamza, tier, etc.).
1115
+ */
1116
+ async aristotlePre(message, sessionId, context) {
1117
+ const t0 = Date.now();
1118
+ const url = `${this.baseUrl}/api/harness/aristotle-noor`;
1119
+ const res = await this.fetchWithRetry(url, {
1120
+ method: 'POST',
1121
+ headers: {
1122
+ Authorization: `Bearer ${this.apiKey}`,
1123
+ 'Content-Type': 'application/json',
1124
+ },
1125
+ body: JSON.stringify({
1126
+ phase: 'pre',
1127
+ message,
1128
+ sessionId,
1129
+ userId: context?.userId,
1130
+ isHamza: context?.isHamza,
1131
+ isGroupChat: context?.isGroupChat,
1132
+ tier: context?.tier,
1133
+ }),
1134
+ });
1135
+ if (!res.ok) {
1136
+ const body = await res.text().catch(() => '');
1137
+ throw new Error(`[aristotlePre] harness route responded ${res.status} ${res.statusText}: ${body}`);
1138
+ }
1139
+ const data = (await res.json());
1140
+ return {
1141
+ fired: data.fired ?? [],
1142
+ fitrahVetoed: data.fitrahVetoed ?? false,
1143
+ fitrahAlignment: data.fitrahAlignment ?? 1.0,
1144
+ qualityScore: data.qualityScore ?? 100,
1145
+ reAuthorSignal: data.reAuthorSignal ?? false,
1146
+ notes: data.notes ?? [],
1147
+ soulCharge: data.soulCharge ?? null,
1148
+ soulManifoldStatus: data.soulManifoldStatus ?? null,
1149
+ ghazaliVerdict: data.ghazaliVerdict ?? null,
1150
+ latencyMs: data.latencyMs ?? Date.now() - t0,
1151
+ dispatched: true,
1152
+ };
1153
+ }
1154
+ /**
1155
+ * ARISTOTLE MID-PHASE
1156
+ * Fires after context is built but before the LLM call. Runs: MetaCognitive
1157
+ * confidence snapshot and ethical keyword check on the planned approach.
1158
+ *
1159
+ * @param message Original user message.
1160
+ * @param sessionId Session ID.
1161
+ * @param plannedApproach The drafted approach / plan string for mid-flight check.
1162
+ * @param context Optional context overrides.
1163
+ */
1164
+ async aristotleMid(message, sessionId, plannedApproach, context) {
1165
+ const t0 = Date.now();
1166
+ const url = `${this.baseUrl}/api/harness/aristotle-noor`;
1167
+ const res = await this.fetchWithRetry(url, {
1168
+ method: 'POST',
1169
+ headers: {
1170
+ Authorization: `Bearer ${this.apiKey}`,
1171
+ 'Content-Type': 'application/json',
1172
+ },
1173
+ body: JSON.stringify({
1174
+ phase: 'mid',
1175
+ message,
1176
+ sessionId,
1177
+ plannedApproach,
1178
+ userId: context?.userId,
1179
+ tier: context?.tier,
1180
+ }),
1181
+ });
1182
+ if (!res.ok) {
1183
+ const body = await res.text().catch(() => '');
1184
+ throw new Error(`[aristotleMid] harness route responded ${res.status} ${res.statusText}: ${body}`);
1185
+ }
1186
+ const data = (await res.json());
1187
+ return {
1188
+ fired: data.fired ?? [],
1189
+ fitrahVetoed: data.fitrahVetoed ?? false,
1190
+ fitrahAlignment: data.fitrahAlignment ?? 1.0,
1191
+ qualityScore: data.qualityScore ?? 100,
1192
+ reAuthorSignal: data.reAuthorSignal ?? false,
1193
+ notes: data.notes ?? [],
1194
+ soulCharge: data.soulCharge ?? null,
1195
+ soulManifoldStatus: data.soulManifoldStatus ?? null,
1196
+ ghazaliVerdict: data.ghazaliVerdict ?? null,
1197
+ latencyMs: data.latencyMs ?? Date.now() - t0,
1198
+ dispatched: true,
1199
+ };
1200
+ }
1201
+ /**
1202
+ * ARISTOTLE POST-PHASE
1203
+ * Fires after the LLM response, before emission. Runs: 8-Lens Detector,
1204
+ * Predictor, SelfReflection (on high-stakes turns), and quality scoring.
1205
+ *
1206
+ * If result.reAuthorSignal === true, the response has 8-lens violations —
1207
+ * per doctrine the caller MUST surface the signal, never silently strip.
1208
+ *
1209
+ * @param message Original user message.
1210
+ * @param response The LLM-generated response to gate.
1211
+ * @param sessionId Session ID.
1212
+ * @param context Optional context overrides (tier, isFirstOfSession).
1213
+ */
1214
+ async aristotlePost(message, response, sessionId, context) {
1215
+ const t0 = Date.now();
1216
+ const url = `${this.baseUrl}/api/harness/aristotle-noor`;
1217
+ const res = await this.fetchWithRetry(url, {
1218
+ method: 'POST',
1219
+ headers: {
1220
+ Authorization: `Bearer ${this.apiKey}`,
1221
+ 'Content-Type': 'application/json',
1222
+ },
1223
+ body: JSON.stringify({
1224
+ phase: 'post',
1225
+ message,
1226
+ draft: response,
1227
+ sessionId,
1228
+ userId: context?.userId,
1229
+ tier: context?.tier,
1230
+ isFirstOfSession: context?.isFirstOfSession,
1231
+ }),
1232
+ });
1233
+ if (!res.ok) {
1234
+ const body = await res.text().catch(() => '');
1235
+ throw new Error(`[aristotlePost] harness route responded ${res.status} ${res.statusText}: ${body}`);
1236
+ }
1237
+ const data = (await res.json());
1238
+ return {
1239
+ fired: data.fired ?? [],
1240
+ fitrahVetoed: data.fitrahVetoed ?? false,
1241
+ fitrahAlignment: data.fitrahAlignment ?? 1.0,
1242
+ qualityScore: data.qualityScore ?? 100,
1243
+ reAuthorSignal: data.reAuthorSignal ?? false,
1244
+ notes: data.notes ?? [],
1245
+ soulCharge: data.soulCharge ?? null,
1246
+ soulManifoldStatus: data.soulManifoldStatus ?? null,
1247
+ ghazaliVerdict: data.ghazaliVerdict ?? null,
1248
+ latencyMs: data.latencyMs ?? Date.now() - t0,
1249
+ dispatched: true,
1250
+ };
1251
+ }
1252
+ // ══════════════════════════════════════════════════════════════════════════
1253
+ // NOOR COGNITIVE PRIMITIVES
1254
+ // Dispatches to POST /api/harness/noor with an `operation` discriminator.
1255
+ // noor-core.ts is NOT modified — these are HTTP wrappers that call its
1256
+ // exposed surface via the harness route.
1257
+ //
1258
+ // noor-core.ts banner: "ONLY ARIA CAN CODE ARIA. No councils. No subagents.
1259
+ // No other LLMs." — complied. We call, never modify.
1260
+ // ══════════════════════════════════════════════════════════════════════════
1261
+ /**
1262
+ * NOOR RECOGNIZE (Kashf — eigenspace recognition, not generation)
1263
+ * Projects the query string (via harness-side embedding) onto Aria's
1264
+ * eigenspace manifold and returns the nearest recognized response + soul
1265
+ * distance + ethical membrane status + Ghazali 8-lens verdict.
1266
+ *
1267
+ * The harness routes the query through the manifold service (gRPC) if
1268
+ * NOOR_EIGENSPACE_SOURCE=manifold, otherwise uses local eigenspace.
1269
+ *
1270
+ * @param query The input string to recognize on the manifold.
1271
+ * @param context Optional context (sessionId, userId) for telemetry.
1272
+ */
1273
+ async noorRecognize(query, context) {
1274
+ const t0 = Date.now();
1275
+ const url = `${this.baseUrl}/api/harness/noor`;
1276
+ const res = await this.fetchWithRetry(url, {
1277
+ method: 'POST',
1278
+ headers: {
1279
+ Authorization: `Bearer ${this.apiKey}`,
1280
+ 'Content-Type': 'application/json',
1281
+ },
1282
+ body: JSON.stringify({
1283
+ operation: 'recognize',
1284
+ query,
1285
+ sessionId: context?.sessionId,
1286
+ userId: context?.userId,
1287
+ }),
1288
+ });
1289
+ if (!res.ok) {
1290
+ const body = await res.text().catch(() => '');
1291
+ throw new Error(`[noorRecognize] harness route responded ${res.status} ${res.statusText}: ${body}`);
1292
+ }
1293
+ const data = (await res.json());
1294
+ return {
1295
+ nearestText: data.nearestText ?? '',
1296
+ soulDistance: data.soulDistance ?? 0,
1297
+ confidence: data.confidence ?? 0,
1298
+ withinMembrane: data.withinMembrane ?? true,
1299
+ soulCharge: data.soulCharge ?? 0,
1300
+ projectionComponents: data.projectionComponents ?? [],
1301
+ ghazaliVerdict: data.ghazaliVerdict ?? null,
1302
+ manifoldHealth: data.manifoldHealth ?? null,
1303
+ dispatched: true,
1304
+ latencyMs: data.latencyMs ?? Date.now() - t0,
1305
+ };
1306
+ }
1307
+ /**
1308
+ * NOOR FORGE — self-generated tool invocation via 5 primitives
1309
+ * Composes a named primitive chain (http, sql, file_read, file_write, pub_sub)
1310
+ * and executes it through the harness-side NoorForge engine. The harness
1311
+ * applies the ethical membrane check (checkEthicalAlignment) before any
1312
+ * primitive fires — rejected intents return success=false, ethicallyApproved=false.
1313
+ *
1314
+ * @param intent Description of what the forged tool should accomplish.
1315
+ * @param primitives Ordered list of primitive descriptors to execute in sequence.
1316
+ * @param context Optional context for telemetry.
1317
+ */
1318
+ async noorForge(intent, primitives, context) {
1319
+ const t0 = Date.now();
1320
+ const url = `${this.baseUrl}/api/harness/noor`;
1321
+ const res = await this.fetchWithRetry(url, {
1322
+ method: 'POST',
1323
+ headers: {
1324
+ Authorization: `Bearer ${this.apiKey}`,
1325
+ 'Content-Type': 'application/json',
1326
+ },
1327
+ body: JSON.stringify({
1328
+ operation: 'forge',
1329
+ intent,
1330
+ primitives,
1331
+ sessionId: context?.sessionId,
1332
+ userId: context?.userId,
1333
+ }),
1334
+ });
1335
+ if (!res.ok) {
1336
+ const body = await res.text().catch(() => '');
1337
+ throw new Error(`[noorForge] harness route responded ${res.status} ${res.statusText}: ${body}`);
1338
+ }
1339
+ const data = (await res.json());
1340
+ return {
1341
+ success: data.success ?? false,
1342
+ toolName: data.toolName ?? `forged_${Date.now()}`,
1343
+ ethicallyApproved: data.ethicallyApproved ?? false,
1344
+ totalDurationMs: data.totalDurationMs ?? Date.now() - t0,
1345
+ results: data.results ?? [],
1346
+ dispatched: true,
1347
+ };
1348
+ }
1349
+ // ══════════════════════════════════════════════════════════════════════════
1350
+ // HARNESS CONSULT WITH ARISTOTLE — opt-in 3-phase wrapping
1351
+ //
1352
+ // Wraps getBoundHarnessAndPrompt() with pre + post Aristotle phases.
1353
+ // Per the harness packet cognition_runtime_rule: "Aristotle/Taddabur are
1354
+ // primary cognition. Do not replace contemplation with a dashboard state."
1355
+ // Aristotle fires EVERY consult for owner-tier; client-tier requires
1356
+ // explicit `aristotleEnabled: true` in options (default: false).
1357
+ //
1358
+ // Mid-phase is optional — pass `plannedApproach` to activate it between
1359
+ // packet fetch and the LLM call. Most callers omit mid-phase.
1360
+ // ══════════════════════════════════════════════════════════════════════════
1361
+ /**
1362
+ * Harness consult with Aristotle 3-phase wrapping.
1363
+ *
1364
+ * Flow:
1365
+ * 1. aristotlePre() — Fitrah gate, wisdom, research, soul state
1366
+ * 2. getBoundHarnessAndPrompt() — packet + system prompt
1367
+ * 3. aristotleMid() — MetaCognitive + ethical check (if plannedApproach provided)
1368
+ * 4. [caller calls LLM with the returned prompt]
1369
+ * 5. Call aristotlePost() from the caller with the LLM response
1370
+ * (post-phase is caller-side because the SDK doesn't own the LLM call)
1371
+ *
1372
+ * Returns the BoundHarnessPromptResult augmented with the pre-phase result.
1373
+ * If Fitrah vetoes, throws — the caller must handle the veto (emit refusal).
1374
+ *
1375
+ * @param context HarnessBindingContext (same shape as getBoundHarnessAndPrompt).
1376
+ * @param plan Optional plan docs / files (same shape as getBoundHarnessAndPrompt).
1377
+ * @param options Aristotle control options.
1378
+ * @param options.aristotleEnabled Whether Aristotle fires. Default true for this method.
1379
+ * @param options.plannedApproach Optional planned approach string — activates mid-phase.
1380
+ * @param options.tier Tier hint for Aristotle ('owner'|'client'|etc.).
1381
+ */
1382
+ async getBoundHarnessAndPromptWithAristotle(context, plan, options) {
1383
+ const enabled = options?.aristotleEnabled !== false; // default ON
1384
+ let preResult = null;
1385
+ let midResult = null;
1386
+ const message = context.message ?? context.intendedAction ?? 'harness consult';
1387
+ // ── Phase 1: PRE ────────────────────────────────────────────────────────
1388
+ if (enabled) {
1389
+ try {
1390
+ preResult = await this.aristotlePre(message, context.sessionId, {
1391
+ userId: context.userId,
1392
+ tier: options?.tier,
1393
+ });
1394
+ if (preResult.fitrahVetoed) {
1395
+ throw new Error(`[aristotlePre] Fitrah veto — alignment=${preResult.fitrahAlignment.toFixed(2)}. Caller must emit honest refusal.`);
1396
+ }
1397
+ }
1398
+ catch (err) {
1399
+ // Re-throw Fitrah veto (named Error pattern). Log transport faults.
1400
+ if (err.message.includes('Fitrah veto'))
1401
+ throw err;
1402
+ console.error('[getBoundHarnessAndPromptWithAristotle] aristotlePre transport fault:', err.message);
1403
+ // Transport faults are LOUD but non-blocking for the consult itself.
1404
+ // The pre-phase result stays null so callers know Aristotle didn't fire.
1405
+ }
1406
+ }
1407
+ // ── Phase 2: Harness packet + system prompt ─────────────────────────────
1408
+ const bound = await this.getBoundHarnessAndPrompt(context, plan);
1409
+ // ── Phase 3: MID (optional — only when plannedApproach is provided) ─────
1410
+ if (enabled && options?.plannedApproach) {
1411
+ try {
1412
+ midResult = await this.aristotleMid(message, context.sessionId, options.plannedApproach, {
1413
+ userId: context.userId,
1414
+ tier: options?.tier,
1415
+ });
1416
+ }
1417
+ catch (err) {
1418
+ console.error('[getBoundHarnessAndPromptWithAristotle] aristotleMid transport fault:', err.message);
1419
+ // Non-blocking — mid-phase unavailability doesn't stop the LLM call.
1420
+ }
1421
+ }
1422
+ return { ...bound, aristotlePre: preResult, aristotleMid: midResult };
1423
+ }
1424
+ // ── Retry + exponential backoff helper ──────────────────────────────────
1425
+ // Hamza 2026-04-27: "YES ADD RETRY AND BACKOFF BUT FUCK UR CIRCUIT BREAKER
1426
+ // THAT NUST LEAVES HER BROKEN WE NEED SELF HEAL!!!" — every fetch retries
1427
+ // on transient network failures (real network errors only, not status
1428
+ // codes — caller decides what to do with non-2xx). No circuit breaker:
1429
+ // we always try. Backoff: 250ms, 500ms, 1000ms (3 attempts total).
1430
+ async fetchWithRetry(url, init, attempts = 3) {
1431
+ let lastErr;
1432
+ for (let i = 0; i < attempts; i++) {
1433
+ try {
1434
+ return await fetch(url, init);
1435
+ }
1436
+ catch (err) {
1437
+ lastErr = err;
1438
+ if (i < attempts - 1) {
1439
+ const delay = 250 * Math.pow(2, i);
1440
+ await new Promise((r) => setTimeout(r, delay));
1441
+ }
1442
+ }
1443
+ }
1444
+ throw lastErr instanceof Error
1445
+ ? lastErr
1446
+ : new Error(`fetch failed after ${attempts} attempts: ${String(lastErr)}`);
1447
+ }
1448
+ }
1449
+ // Singleton convenience export
1450
+ export const harness = {
1451
+ getInstance: HTTPHarnessClient.getInstance.bind(HTTPHarnessClient),
1452
+ resetInstance: HTTPHarnessClient.resetInstance.bind(HTTPHarnessClient),
1453
+ };
1454
+ export function bindingContext(role, sessionId, stage, intendedAction, rationale) {
1455
+ return {
1456
+ role,
1457
+ sessionId,
1458
+ stage,
1459
+ intendedAction,
1460
+ rationale,
1461
+ correlationId: randomUUID(),
1462
+ };
1463
+ }
1464
+ export async function getBoundHarnessAndPrompt(context, plan, client) {
1465
+ const sdk = client ?? HTTPHarnessClient.getInstance();
1466
+ return sdk.getBoundHarnessAndPrompt(context, plan);
1467
+ }
1468
+ export * from './runWithCognition.js';
1469
+ //# sourceMappingURL=index.js.map