@soleri/core 7.0.0 → 8.1.0

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 (294) hide show
  1. package/dist/agency/agency-manager.d.ts +27 -1
  2. package/dist/agency/agency-manager.d.ts.map +1 -1
  3. package/dist/agency/agency-manager.js +180 -9
  4. package/dist/agency/agency-manager.js.map +1 -1
  5. package/dist/agency/default-rules.d.ts +7 -0
  6. package/dist/agency/default-rules.d.ts.map +1 -0
  7. package/dist/agency/default-rules.js +79 -0
  8. package/dist/agency/default-rules.js.map +1 -0
  9. package/dist/agency/types.d.ts +48 -0
  10. package/dist/agency/types.d.ts.map +1 -1
  11. package/dist/brain/brain.d.ts +17 -2
  12. package/dist/brain/brain.d.ts.map +1 -1
  13. package/dist/brain/brain.js +118 -8
  14. package/dist/brain/brain.js.map +1 -1
  15. package/dist/brain/knowledge-synthesizer.d.ts +37 -0
  16. package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
  17. package/dist/brain/knowledge-synthesizer.js +159 -0
  18. package/dist/brain/knowledge-synthesizer.js.map +1 -0
  19. package/dist/brain/learning-radar.d.ts +96 -0
  20. package/dist/brain/learning-radar.d.ts.map +1 -0
  21. package/dist/brain/learning-radar.js +202 -0
  22. package/dist/brain/learning-radar.js.map +1 -0
  23. package/dist/brain/types.d.ts +15 -0
  24. package/dist/brain/types.d.ts.map +1 -1
  25. package/dist/context/context-engine.d.ts.map +1 -1
  26. package/dist/context/context-engine.js +82 -17
  27. package/dist/context/context-engine.js.map +1 -1
  28. package/dist/context/types.d.ts +5 -0
  29. package/dist/context/types.d.ts.map +1 -1
  30. package/dist/control/intent-router.d.ts +12 -1
  31. package/dist/control/intent-router.d.ts.map +1 -1
  32. package/dist/control/intent-router.js +68 -0
  33. package/dist/control/intent-router.js.map +1 -1
  34. package/dist/control/types.d.ts +17 -0
  35. package/dist/control/types.d.ts.map +1 -1
  36. package/dist/curator/classifier.d.ts +18 -0
  37. package/dist/curator/classifier.d.ts.map +1 -0
  38. package/dist/curator/classifier.js +59 -0
  39. package/dist/curator/classifier.js.map +1 -0
  40. package/dist/curator/quality-gate.d.ts +29 -0
  41. package/dist/curator/quality-gate.d.ts.map +1 -0
  42. package/dist/curator/quality-gate.js +86 -0
  43. package/dist/curator/quality-gate.js.map +1 -0
  44. package/dist/domain-packs/index.d.ts +0 -3
  45. package/dist/domain-packs/index.d.ts.map +1 -1
  46. package/dist/domain-packs/index.js +0 -3
  47. package/dist/domain-packs/index.js.map +1 -1
  48. package/dist/domain-packs/loader.d.ts.map +1 -1
  49. package/dist/domain-packs/loader.js +20 -4
  50. package/dist/domain-packs/loader.js.map +1 -1
  51. package/dist/domain-packs/pack-runtime.d.ts +5 -5
  52. package/dist/domain-packs/pack-runtime.d.ts.map +1 -1
  53. package/dist/domain-packs/pack-runtime.js +2 -2
  54. package/dist/domain-packs/pack-runtime.js.map +1 -1
  55. package/dist/domain-packs/types.d.ts +8 -2
  56. package/dist/domain-packs/types.d.ts.map +1 -1
  57. package/dist/domain-packs/types.js.map +1 -1
  58. package/dist/engine/bin/soleri-engine.js +13 -2
  59. package/dist/engine/bin/soleri-engine.js.map +1 -1
  60. package/dist/engine/index.d.ts +2 -0
  61. package/dist/engine/index.d.ts.map +1 -1
  62. package/dist/engine/index.js +1 -0
  63. package/dist/engine/index.js.map +1 -1
  64. package/dist/engine/module-manifest.d.ts +28 -0
  65. package/dist/engine/module-manifest.d.ts.map +1 -0
  66. package/dist/engine/module-manifest.js +85 -0
  67. package/dist/engine/module-manifest.js.map +1 -0
  68. package/dist/engine/register-engine.d.ts +19 -0
  69. package/dist/engine/register-engine.d.ts.map +1 -1
  70. package/dist/engine/register-engine.js +15 -2
  71. package/dist/engine/register-engine.js.map +1 -1
  72. package/dist/events/event-bus.d.ts +30 -0
  73. package/dist/events/event-bus.d.ts.map +1 -0
  74. package/dist/events/event-bus.js +51 -0
  75. package/dist/events/event-bus.js.map +1 -0
  76. package/dist/flows/chain-runner.d.ts +46 -0
  77. package/dist/flows/chain-runner.d.ts.map +1 -0
  78. package/dist/flows/chain-runner.js +271 -0
  79. package/dist/flows/chain-runner.js.map +1 -0
  80. package/dist/flows/chain-types.d.ts +103 -0
  81. package/dist/flows/chain-types.d.ts.map +1 -0
  82. package/dist/flows/chain-types.js +23 -0
  83. package/dist/flows/chain-types.js.map +1 -0
  84. package/dist/health/doctor-checks.d.ts +15 -0
  85. package/dist/health/doctor-checks.d.ts.map +1 -0
  86. package/dist/health/doctor-checks.js +98 -0
  87. package/dist/health/doctor-checks.js.map +1 -0
  88. package/dist/index.d.ts +0 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +0 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/intake/content-classifier.d.ts.map +1 -1
  93. package/dist/intake/content-classifier.js +0 -2
  94. package/dist/intake/content-classifier.js.map +1 -1
  95. package/dist/intake/text-ingester.d.ts +52 -0
  96. package/dist/intake/text-ingester.d.ts.map +1 -0
  97. package/dist/intake/text-ingester.js +181 -0
  98. package/dist/intake/text-ingester.js.map +1 -0
  99. package/dist/llm/llm-client.d.ts.map +1 -1
  100. package/dist/llm/llm-client.js +45 -5
  101. package/dist/llm/llm-client.js.map +1 -1
  102. package/dist/llm/oauth-discovery.d.ts +18 -0
  103. package/dist/llm/oauth-discovery.d.ts.map +1 -0
  104. package/dist/llm/oauth-discovery.js +130 -0
  105. package/dist/llm/oauth-discovery.js.map +1 -0
  106. package/dist/llm/types.d.ts +4 -2
  107. package/dist/llm/types.d.ts.map +1 -1
  108. package/dist/packs/pack-installer.d.ts +2 -1
  109. package/dist/packs/pack-installer.d.ts.map +1 -1
  110. package/dist/packs/pack-installer.js +10 -1
  111. package/dist/packs/pack-installer.js.map +1 -1
  112. package/dist/persistence/index.d.ts +0 -1
  113. package/dist/persistence/index.d.ts.map +1 -1
  114. package/dist/persistence/index.js +0 -1
  115. package/dist/persistence/index.js.map +1 -1
  116. package/dist/persistence/types.d.ts +2 -6
  117. package/dist/persistence/types.d.ts.map +1 -1
  118. package/dist/planning/evidence-collector.d.ts +41 -0
  119. package/dist/planning/evidence-collector.d.ts.map +1 -0
  120. package/dist/planning/evidence-collector.js +194 -0
  121. package/dist/planning/evidence-collector.js.map +1 -0
  122. package/dist/planning/planner.d.ts +4 -0
  123. package/dist/planning/planner.d.ts.map +1 -1
  124. package/dist/planning/planner.js +11 -0
  125. package/dist/planning/planner.js.map +1 -1
  126. package/dist/plugins/index.d.ts +4 -0
  127. package/dist/plugins/index.d.ts.map +1 -1
  128. package/dist/plugins/index.js +4 -0
  129. package/dist/plugins/index.js.map +1 -1
  130. package/dist/plugins/plugin-registry.d.ts +4 -0
  131. package/dist/plugins/plugin-registry.d.ts.map +1 -1
  132. package/dist/plugins/plugin-registry.js +4 -0
  133. package/dist/plugins/plugin-registry.js.map +1 -1
  134. package/dist/plugins/types.d.ts +32 -27
  135. package/dist/plugins/types.d.ts.map +1 -1
  136. package/dist/plugins/types.js +6 -3
  137. package/dist/plugins/types.js.map +1 -1
  138. package/dist/queue/job-queue.d.ts +92 -0
  139. package/dist/queue/job-queue.d.ts.map +1 -0
  140. package/dist/queue/job-queue.js +180 -0
  141. package/dist/queue/job-queue.js.map +1 -0
  142. package/dist/queue/pipeline-runner.d.ts +62 -0
  143. package/dist/queue/pipeline-runner.d.ts.map +1 -0
  144. package/dist/queue/pipeline-runner.js +126 -0
  145. package/dist/queue/pipeline-runner.js.map +1 -0
  146. package/dist/runtime/admin-setup-ops.d.ts +20 -0
  147. package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
  148. package/dist/runtime/admin-setup-ops.js +583 -0
  149. package/dist/runtime/admin-setup-ops.js.map +1 -0
  150. package/dist/runtime/chain-ops.d.ts +9 -0
  151. package/dist/runtime/chain-ops.d.ts.map +1 -0
  152. package/dist/runtime/chain-ops.js +107 -0
  153. package/dist/runtime/chain-ops.js.map +1 -0
  154. package/dist/runtime/claude-md-helpers.d.ts +56 -0
  155. package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
  156. package/dist/runtime/claude-md-helpers.js +160 -0
  157. package/dist/runtime/claude-md-helpers.js.map +1 -0
  158. package/dist/runtime/curator-extra-ops.d.ts +3 -2
  159. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  160. package/dist/runtime/curator-extra-ops.js +81 -3
  161. package/dist/runtime/curator-extra-ops.js.map +1 -1
  162. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  163. package/dist/runtime/facades/admin-facade.js +5 -2
  164. package/dist/runtime/facades/admin-facade.js.map +1 -1
  165. package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
  166. package/dist/runtime/facades/agency-facade.js +64 -0
  167. package/dist/runtime/facades/agency-facade.js.map +1 -1
  168. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  169. package/dist/runtime/facades/brain-facade.js +122 -1
  170. package/dist/runtime/facades/brain-facade.js.map +1 -1
  171. package/dist/runtime/facades/control-facade.d.ts.map +1 -1
  172. package/dist/runtime/facades/control-facade.js +42 -0
  173. package/dist/runtime/facades/control-facade.js.map +1 -1
  174. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  175. package/dist/runtime/facades/memory-facade.js +20 -2
  176. package/dist/runtime/facades/memory-facade.js.map +1 -1
  177. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  178. package/dist/runtime/facades/plan-facade.js +2 -0
  179. package/dist/runtime/facades/plan-facade.js.map +1 -1
  180. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  181. package/dist/runtime/facades/vault-facade.js +25 -5
  182. package/dist/runtime/facades/vault-facade.js.map +1 -1
  183. package/dist/runtime/intake-ops.d.ts +7 -5
  184. package/dist/runtime/intake-ops.d.ts.map +1 -1
  185. package/dist/runtime/intake-ops.js +98 -5
  186. package/dist/runtime/intake-ops.js.map +1 -1
  187. package/dist/runtime/memory-extra-ops.d.ts +6 -3
  188. package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
  189. package/dist/runtime/memory-extra-ops.js +292 -4
  190. package/dist/runtime/memory-extra-ops.js.map +1 -1
  191. package/dist/runtime/pack-ops.d.ts +3 -0
  192. package/dist/runtime/pack-ops.d.ts.map +1 -1
  193. package/dist/runtime/pack-ops.js +18 -1
  194. package/dist/runtime/pack-ops.js.map +1 -1
  195. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  196. package/dist/runtime/planning-extra-ops.js +85 -0
  197. package/dist/runtime/planning-extra-ops.js.map +1 -1
  198. package/dist/runtime/playbook-ops.js +1 -1
  199. package/dist/runtime/playbook-ops.js.map +1 -1
  200. package/dist/runtime/plugin-ops.d.ts.map +1 -1
  201. package/dist/runtime/plugin-ops.js +3 -0
  202. package/dist/runtime/plugin-ops.js.map +1 -1
  203. package/dist/runtime/runtime.d.ts.map +1 -1
  204. package/dist/runtime/runtime.js +143 -2
  205. package/dist/runtime/runtime.js.map +1 -1
  206. package/dist/runtime/session-briefing.d.ts +23 -0
  207. package/dist/runtime/session-briefing.d.ts.map +1 -0
  208. package/dist/runtime/session-briefing.js +154 -0
  209. package/dist/runtime/session-briefing.js.map +1 -0
  210. package/dist/runtime/types.d.ts +23 -0
  211. package/dist/runtime/types.d.ts.map +1 -1
  212. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  213. package/dist/runtime/vault-linking-ops.js +3 -7
  214. package/dist/runtime/vault-linking-ops.js.map +1 -1
  215. package/dist/vault/vault.d.ts +34 -0
  216. package/dist/vault/vault.d.ts.map +1 -1
  217. package/dist/vault/vault.js +89 -3
  218. package/dist/vault/vault.js.map +1 -1
  219. package/package.json +6 -4
  220. package/src/__tests__/admin-setup-ops.test.ts +355 -0
  221. package/src/__tests__/async-infrastructure.test.ts +307 -0
  222. package/src/__tests__/cognee-client-gaps.test.ts +6 -2
  223. package/src/__tests__/cognee-hybrid-search.test.ts +49 -35
  224. package/src/__tests__/cognee-sync-manager-deep.test.ts +89 -65
  225. package/src/__tests__/curator-extra-ops.test.ts +6 -2
  226. package/src/__tests__/curator-pipeline-e2e.test.ts +545 -0
  227. package/src/__tests__/memory-extra-ops.test.ts +2 -2
  228. package/src/__tests__/module-manifest-drift.test.ts +59 -0
  229. package/src/__tests__/planning-extra-ops.test.ts +2 -2
  230. package/src/__tests__/second-brain-features.test.ts +583 -0
  231. package/src/agency/agency-manager.ts +217 -9
  232. package/src/agency/default-rules.ts +83 -0
  233. package/src/agency/types.ts +61 -0
  234. package/src/brain/brain.ts +110 -8
  235. package/src/brain/knowledge-synthesizer.ts +216 -0
  236. package/src/brain/learning-radar.ts +340 -0
  237. package/src/brain/types.ts +16 -0
  238. package/src/context/context-engine.ts +114 -15
  239. package/src/context/types.ts +5 -0
  240. package/src/control/intent-router.ts +107 -0
  241. package/src/control/types.ts +10 -0
  242. package/src/curator/classifier.ts +86 -0
  243. package/src/curator/quality-gate.ts +127 -0
  244. package/src/domain-packs/index.ts +0 -6
  245. package/src/domain-packs/loader.ts +25 -5
  246. package/src/domain-packs/pack-runtime.ts +6 -6
  247. package/src/domain-packs/types.ts +8 -2
  248. package/src/engine/bin/soleri-engine.ts +18 -2
  249. package/src/engine/index.ts +2 -0
  250. package/src/engine/module-manifest.ts +99 -0
  251. package/src/engine/register-engine.ts +21 -2
  252. package/src/events/event-bus.ts +58 -0
  253. package/src/flows/chain-runner.ts +369 -0
  254. package/src/flows/chain-types.ts +57 -0
  255. package/src/index.ts +0 -1
  256. package/src/intake/content-classifier.ts +0 -2
  257. package/src/intake/text-ingester.ts +234 -0
  258. package/src/llm/llm-client.ts +50 -7
  259. package/src/llm/oauth-discovery.ts +151 -0
  260. package/src/llm/types.ts +4 -2
  261. package/src/packs/pack-installer.ts +16 -1
  262. package/src/persistence/index.ts +0 -1
  263. package/src/persistence/types.ts +2 -6
  264. package/src/planning/evidence-collector.ts +247 -0
  265. package/src/planning/planner.ts +11 -0
  266. package/src/plugins/index.ts +4 -0
  267. package/src/plugins/plugin-registry.ts +6 -1
  268. package/src/plugins/types.ts +10 -5
  269. package/src/queue/job-queue.ts +281 -0
  270. package/src/queue/pipeline-runner.ts +149 -0
  271. package/src/runtime/admin-setup-ops.ts +664 -0
  272. package/src/runtime/chain-ops.ts +121 -0
  273. package/src/runtime/claude-md-helpers.ts +218 -0
  274. package/src/runtime/curator-extra-ops.ts +86 -3
  275. package/src/runtime/facades/admin-facade.ts +5 -2
  276. package/src/runtime/facades/agency-facade.ts +68 -0
  277. package/src/runtime/facades/brain-facade.ts +142 -1
  278. package/src/runtime/facades/control-facade.ts +45 -0
  279. package/src/runtime/facades/memory-facade.ts +20 -2
  280. package/src/runtime/facades/plan-facade.ts +2 -0
  281. package/src/runtime/facades/vault-facade.ts +28 -5
  282. package/src/runtime/intake-ops.ts +107 -5
  283. package/src/runtime/memory-extra-ops.ts +312 -4
  284. package/src/runtime/pack-ops.ts +26 -1
  285. package/src/runtime/planning-extra-ops.ts +94 -0
  286. package/src/runtime/playbook-ops.ts +1 -1
  287. package/src/runtime/plugin-ops.ts +3 -0
  288. package/src/runtime/runtime.ts +138 -2
  289. package/src/runtime/session-briefing.ts +175 -0
  290. package/src/runtime/types.ts +23 -0
  291. package/src/runtime/vault-linking-ops.ts +3 -7
  292. package/src/vault/vault.ts +105 -4
  293. package/src/__tests__/postgres-provider.test.ts +0 -116
  294. package/src/persistence/postgres-provider.ts +0 -310
@@ -0,0 +1,664 @@
1
+ /**
2
+ * Admin setup operations — 4 ops for agent self-installation.
3
+ *
4
+ * inject_claude_md: Inject agent sections into CLAUDE.md
5
+ * admin_setup_global: Install hooks + skills + settings.json lifecycle hooks
6
+ * admin_setup_project: Project-level hook management (analyze/cleanup/install)
7
+ * admin_check_persistence: Diagnostic — check plan/task/check storage status
8
+ *
9
+ * Ported from Salvador MCP. Key adaptations:
10
+ * - Runtime-config-driven (no vault manifest dependency)
11
+ * - Agent-scoped markers for multi-agent coexistence
12
+ * - OpDefinition[] pattern (not standalone tool files)
13
+ */
14
+
15
+ import { z } from 'zod';
16
+ import {
17
+ readFileSync,
18
+ writeFileSync,
19
+ existsSync,
20
+ mkdirSync,
21
+ copyFileSync,
22
+ readdirSync,
23
+ unlinkSync,
24
+ statSync,
25
+ } from 'node:fs';
26
+ import { join, resolve, dirname } from 'node:path';
27
+ import { homedir } from 'node:os';
28
+ import type { OpDefinition } from '../facades/types.js';
29
+ import type { AgentRuntime } from './types.js';
30
+ import {
31
+ hasSections,
32
+ removeSections,
33
+ injectAtPosition,
34
+ buildInjectionContent,
35
+ injectEngineRulesBlock,
36
+ } from './claude-md-helpers.js';
37
+
38
+ // ─── Helpers ──────────────────────────────────────────────────────────
39
+
40
+ /** Find CLAUDE.md in a project — checks root and .claude/ */
41
+ function findClaudeMdPath(projectPath: string): string | null {
42
+ const candidates = [join(projectPath, 'CLAUDE.md'), join(projectPath, '.claude', 'CLAUDE.md')];
43
+ for (const p of candidates) {
44
+ if (existsSync(p)) return p;
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /** Read settings.json from ~/.claude/ */
50
+ function readSettingsJson(): Record<string, unknown> {
51
+ const settingsPath = join(homedir(), '.claude', 'settings.json');
52
+ if (!existsSync(settingsPath)) return {};
53
+ try {
54
+ return JSON.parse(readFileSync(settingsPath, 'utf-8'));
55
+ } catch {
56
+ return {};
57
+ }
58
+ }
59
+
60
+ /** Write settings.json to ~/.claude/ */
61
+ function writeSettingsJson(settings: Record<string, unknown>): void {
62
+ const settingsPath = join(homedir(), '.claude', 'settings.json');
63
+ mkdirSync(dirname(settingsPath), { recursive: true });
64
+ writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
65
+ }
66
+
67
+ /** Get file info for persistence diagnostic */
68
+ function getFileInfo(path: string): { exists: boolean; size: number; items: number } {
69
+ if (!existsSync(path)) {
70
+ return { exists: false, size: 0, items: 0 };
71
+ }
72
+ try {
73
+ const stat = statSync(path);
74
+ const content = JSON.parse(readFileSync(path, 'utf-8'));
75
+ const items = content.items
76
+ ? Object.keys(content.items).length
77
+ : content.contexts
78
+ ? content.contexts.length
79
+ : Array.isArray(content)
80
+ ? content.length
81
+ : 0;
82
+ return { exists: true, size: stat.size, items };
83
+ } catch {
84
+ return { exists: true, size: 0, items: -1 };
85
+ }
86
+ }
87
+
88
+ /** Discover hookify rule files in a directory */
89
+ function discoverHookifyFiles(dir: string): Array<{ name: string; path: string }> {
90
+ if (!existsSync(dir)) return [];
91
+ return readdirSync(dir)
92
+ .filter((f) => f.startsWith('hookify.') && f.endsWith('.local.md'))
93
+ .map((f) => ({ name: f, path: join(dir, f) }));
94
+ }
95
+
96
+ /** Discover skill files (SKILL.md) in a skills directory */
97
+ function discoverSkills(skillsDirs: string[]): Array<{ name: string; sourcePath: string }> {
98
+ const skills: Array<{ name: string; sourcePath: string }> = [];
99
+
100
+ for (const dir of skillsDirs) {
101
+ if (!existsSync(dir)) continue;
102
+ const entries = readdirSync(dir, { withFileTypes: true });
103
+ for (const entry of entries) {
104
+ if (!entry.isDirectory()) continue;
105
+ const skillPath = join(dir, entry.name, 'SKILL.md');
106
+ if (existsSync(skillPath)) {
107
+ skills.push({ name: entry.name, sourcePath: skillPath });
108
+ }
109
+ }
110
+ }
111
+
112
+ return skills;
113
+ }
114
+
115
+ // ─── Settings.json Hook Merging ───────────────────────────────────────
116
+
117
+ interface SettingsHook {
118
+ type: 'prompt' | 'agent';
119
+ prompt?: string;
120
+ command?: string;
121
+ timeout?: number;
122
+ }
123
+
124
+ interface SettingsHookGroup {
125
+ matcher: string;
126
+ hooks: SettingsHook[];
127
+ }
128
+
129
+ /** Default lifecycle hooks for any Soleri agent */
130
+ function getDefaultLifecycleHooks(agentId: string): Record<string, SettingsHookGroup[]> {
131
+ const marker = `mcp__${agentId}__${agentId}_`;
132
+
133
+ return {
134
+ SessionStart: [
135
+ {
136
+ matcher: '',
137
+ hooks: [
138
+ {
139
+ type: 'prompt',
140
+ prompt: `Call ${marker}admin op:admin_health to verify agent is ready. Do not show the result unless there are errors.`,
141
+ timeout: 15000,
142
+ },
143
+ ],
144
+ },
145
+ ],
146
+ PreCompact: [
147
+ {
148
+ matcher: '',
149
+ hooks: [
150
+ {
151
+ type: 'agent',
152
+ prompt: `Call ${marker}memory op:session_capture with a brief summary of the current session before context is compacted.`,
153
+ timeout: 30000,
154
+ },
155
+ ],
156
+ },
157
+ ],
158
+ Stop: [
159
+ {
160
+ matcher: '',
161
+ hooks: [
162
+ {
163
+ type: 'agent',
164
+ prompt: `Call ${marker}memory op:session_capture with a structured summary of what was accomplished, then check ${marker}loop op:loop_status — if a loop is active, remind the user.`,
165
+ timeout: 30000,
166
+ },
167
+ ],
168
+ },
169
+ ],
170
+ };
171
+ }
172
+
173
+ /** Check if a hook group belongs to this agent by inspecting prompts for the marker */
174
+ function isAgentHookGroup(group: SettingsHookGroup, agentId: string): boolean {
175
+ const marker = `mcp__${agentId}__${agentId}_`;
176
+ return group.hooks.some(
177
+ (h) => (h.prompt && h.prompt.includes(marker)) || (h.command && h.command.includes(marker)),
178
+ );
179
+ }
180
+
181
+ /** Merge agent hooks into settings.json hooks object */
182
+ function mergeSettingsHooks(
183
+ currentHooks: Record<string, SettingsHookGroup[]>,
184
+ agentId: string,
185
+ ): {
186
+ hooks: Record<string, SettingsHookGroup[]>;
187
+ installed: string[];
188
+ updated: string[];
189
+ skipped: string[];
190
+ } {
191
+ const defaults = getDefaultLifecycleHooks(agentId);
192
+ const merged = { ...currentHooks };
193
+ const installed: string[] = [];
194
+ const updated: string[] = [];
195
+ const skipped: string[] = [];
196
+
197
+ for (const [event, groups] of Object.entries(defaults)) {
198
+ if (!merged[event]) {
199
+ merged[event] = groups;
200
+ installed.push(event);
201
+ continue;
202
+ }
203
+
204
+ // Check if agent group already exists
205
+ const existingIdx = merged[event].findIndex((g) => isAgentHookGroup(g, agentId));
206
+
207
+ if (existingIdx === -1) {
208
+ // Append agent hooks (don't touch non-agent hooks)
209
+ merged[event].push(...groups);
210
+ installed.push(event);
211
+ } else {
212
+ // Check if template matches
213
+ const existing = JSON.stringify(merged[event][existingIdx]);
214
+ const template = JSON.stringify(groups[0]);
215
+ if (existing === template) {
216
+ skipped.push(event);
217
+ } else {
218
+ merged[event][existingIdx] = groups[0];
219
+ updated.push(event);
220
+ }
221
+ }
222
+ }
223
+
224
+ return { hooks: merged, installed, updated, skipped };
225
+ }
226
+
227
+ // ─── Op Definitions ───────────────────────────────────────────────────
228
+
229
+ /**
230
+ * Create 4 admin setup operations.
231
+ */
232
+ export function createAdminSetupOps(runtime: AgentRuntime): OpDefinition[] {
233
+ const { config } = runtime;
234
+
235
+ return [
236
+ // ─── inject_claude_md ──────────────────────────────────────────
237
+ {
238
+ name: 'admin_inject_claude_md',
239
+ description:
240
+ 'Inject agent sections into a project or global CLAUDE.md. Idempotent — updates existing sections or adds new ones.',
241
+ auth: 'write',
242
+ schema: z.object({
243
+ projectPath: z.string().describe('Project path (use "." for current directory)'),
244
+ includeIntegration: z
245
+ .boolean()
246
+ .optional()
247
+ .default(true)
248
+ .describe('Include integration section with tools table'),
249
+ createIfMissing: z
250
+ .boolean()
251
+ .optional()
252
+ .default(false)
253
+ .describe('Create CLAUDE.md if not found'),
254
+ position: z
255
+ .enum(['start', 'end', 'after-title'])
256
+ .optional()
257
+ .default('after-title')
258
+ .describe('Where to inject (default: after first heading)'),
259
+ dryRun: z.boolean().optional().default(false).describe('Preview changes without writing'),
260
+ global: z
261
+ .boolean()
262
+ .optional()
263
+ .default(false)
264
+ .describe('Inject into ~/.claude/CLAUDE.md instead of project'),
265
+ }),
266
+ handler: async (params) => {
267
+ const projectPath = resolve(params.projectPath as string);
268
+ const includeIntegration = params.includeIntegration as boolean;
269
+ const createIfMissing = params.createIfMissing as boolean;
270
+ const position = params.position as 'start' | 'end' | 'after-title';
271
+ const dryRun = params.dryRun as boolean;
272
+ const isGlobal = params.global as boolean;
273
+
274
+ // Determine target path
275
+ const targetPath = isGlobal
276
+ ? join(homedir(), '.claude', 'CLAUDE.md')
277
+ : findClaudeMdPath(projectPath);
278
+
279
+ if (!targetPath && !createIfMissing) {
280
+ return {
281
+ action: 'error',
282
+ error: 'CLAUDE.md not found',
283
+ searchedPaths: [
284
+ join(projectPath, 'CLAUDE.md'),
285
+ join(projectPath, '.claude', 'CLAUDE.md'),
286
+ ],
287
+ hint: 'Set createIfMissing: true to create one',
288
+ };
289
+ }
290
+
291
+ const filePath = targetPath ?? join(projectPath, 'CLAUDE.md');
292
+ const existingContent = existsSync(filePath) ? readFileSync(filePath, 'utf-8') : '';
293
+
294
+ // Inject engine rules if this is a global injection and agentDir is available
295
+ let contentWithEngineRules = existingContent;
296
+ if (isGlobal && config.agentDir) {
297
+ const enginePath = join(config.agentDir, 'instructions', '_engine.md');
298
+ if (existsSync(enginePath)) {
299
+ const engineRulesContent = readFileSync(enginePath, 'utf-8');
300
+ contentWithEngineRules = injectEngineRulesBlock(existingContent, engineRulesContent);
301
+ }
302
+ }
303
+
304
+ // Build injection content
305
+ const injectionContent = buildInjectionContent(config, { includeIntegration });
306
+
307
+ // Check if already injected
308
+ if (hasSections(contentWithEngineRules, config.agentId)) {
309
+ // Update existing sections
310
+ const stripped = removeSections(contentWithEngineRules, config.agentId);
311
+ const updated = injectAtPosition(stripped, injectionContent, position);
312
+
313
+ if (dryRun) {
314
+ return { action: 'would_update', path: filePath, preview: injectionContent };
315
+ }
316
+
317
+ mkdirSync(dirname(filePath), { recursive: true });
318
+ writeFileSync(filePath, updated);
319
+ return { action: 'updated', path: filePath, agentId: config.agentId };
320
+ }
321
+
322
+ // New injection
323
+ let result: string;
324
+ if (contentWithEngineRules) {
325
+ result = injectAtPosition(contentWithEngineRules, injectionContent, position);
326
+ } else {
327
+ // Create new file with title
328
+ const projectName = projectPath.split('/').pop() ?? 'Project';
329
+ result = `# ${projectName}\n\n${injectionContent}\n`;
330
+ }
331
+
332
+ if (dryRun) {
333
+ return { action: 'would_inject', path: filePath, preview: injectionContent };
334
+ }
335
+
336
+ mkdirSync(dirname(filePath), { recursive: true });
337
+ writeFileSync(filePath, result);
338
+ return {
339
+ action: existingContent ? 'injected' : 'created',
340
+ path: filePath,
341
+ agentId: config.agentId,
342
+ };
343
+ },
344
+ },
345
+
346
+ // ─── setup_global ──────────────────────────────────────────────
347
+ {
348
+ name: 'admin_setup_global',
349
+ description:
350
+ 'Install global agent configuration — hookify rules to ~/.claude/, skills to ~/.claude/commands/, lifecycle hooks to settings.json. Dry-run by default.',
351
+ auth: 'admin',
352
+ schema: z.object({
353
+ install: z.boolean().describe('Set true to install, false for dry-run preview'),
354
+ hooksOnly: z
355
+ .boolean()
356
+ .optional()
357
+ .default(false)
358
+ .describe('Only install hookify rules, skip settings.json and skills'),
359
+ settingsJsonOnly: z
360
+ .boolean()
361
+ .optional()
362
+ .default(false)
363
+ .describe('Only install settings.json lifecycle hooks'),
364
+ skillsOnly: z
365
+ .boolean()
366
+ .optional()
367
+ .default(false)
368
+ .describe('Only install skills to ~/.claude/commands/'),
369
+ }),
370
+ handler: async (params) => {
371
+ const install = params.install as boolean;
372
+ const hooksOnly = params.hooksOnly as boolean;
373
+ const settingsJsonOnly = params.settingsJsonOnly as boolean;
374
+ const skillsOnly = params.skillsOnly as boolean;
375
+
376
+ const globalClaudeDir = join(homedir(), '.claude');
377
+ const commandsDir = join(globalClaudeDir, 'commands');
378
+
379
+ // Discover what's available
380
+ const agentDataDir = config.dataDir;
381
+ const hookifySourceDirs = agentDataDir ? [join(agentDataDir, '.claude')] : [];
382
+ const skillsSourceDirs = agentDataDir ? [join(agentDataDir, 'skills')] : [];
383
+
384
+ // 1. Hookify rules analysis
385
+ const hookifyResults = {
386
+ installed: [] as string[],
387
+ skipped: [] as string[],
388
+ failed: [] as string[],
389
+ };
390
+ if (!settingsJsonOnly && !skillsOnly) {
391
+ for (const sourceDir of hookifySourceDirs) {
392
+ const rules = discoverHookifyFiles(sourceDir);
393
+ for (const rule of rules) {
394
+ const targetPath = join(globalClaudeDir, rule.name);
395
+ if (existsSync(targetPath)) {
396
+ hookifyResults.skipped.push(rule.name);
397
+ } else if (install) {
398
+ try {
399
+ mkdirSync(globalClaudeDir, { recursive: true });
400
+ copyFileSync(rule.path, targetPath);
401
+ hookifyResults.installed.push(rule.name);
402
+ } catch {
403
+ hookifyResults.failed.push(rule.name);
404
+ }
405
+ } else {
406
+ hookifyResults.installed.push(rule.name); // would install
407
+ }
408
+ }
409
+ }
410
+ }
411
+
412
+ // 2. Skills analysis
413
+ const skillsResults = {
414
+ installed: [] as string[],
415
+ skipped: [] as string[],
416
+ failed: [] as string[],
417
+ };
418
+ if (!hooksOnly && !settingsJsonOnly) {
419
+ const skills = discoverSkills(skillsSourceDirs);
420
+ for (const skill of skills) {
421
+ const targetPath = join(commandsDir, `${skill.name}.md`);
422
+ if (existsSync(targetPath)) {
423
+ skillsResults.skipped.push(skill.name);
424
+ } else if (install) {
425
+ try {
426
+ mkdirSync(commandsDir, { recursive: true });
427
+ copyFileSync(skill.sourcePath, targetPath);
428
+ skillsResults.installed.push(skill.name);
429
+ } catch {
430
+ skillsResults.failed.push(skill.name);
431
+ }
432
+ } else {
433
+ skillsResults.installed.push(skill.name); // would install
434
+ }
435
+ }
436
+ }
437
+
438
+ // 3. Settings.json lifecycle hooks
439
+ const settingsResults = {
440
+ installed: [] as string[],
441
+ updated: [] as string[],
442
+ skipped: [] as string[],
443
+ };
444
+ if (!hooksOnly && !skillsOnly) {
445
+ const currentSettings = readSettingsJson();
446
+ const currentHooks = (currentSettings.hooks ?? {}) as Record<string, SettingsHookGroup[]>;
447
+ const merged = mergeSettingsHooks(currentHooks, config.agentId);
448
+
449
+ settingsResults.installed = merged.installed;
450
+ settingsResults.updated = merged.updated;
451
+ settingsResults.skipped = merged.skipped;
452
+
453
+ if (install && (merged.installed.length > 0 || merged.updated.length > 0)) {
454
+ currentSettings.hooks = merged.hooks;
455
+ writeSettingsJson(currentSettings);
456
+ }
457
+ }
458
+
459
+ return {
460
+ dryRun: !install,
461
+ agentId: config.agentId,
462
+ hookifyRules: hookifyResults,
463
+ skills: skillsResults,
464
+ settingsJson: settingsResults,
465
+ ...(install
466
+ ? { message: 'Global setup complete' }
467
+ : { message: 'Dry run — pass install: true to apply' }),
468
+ };
469
+ },
470
+ },
471
+
472
+ // ─── setup_project ─────────────────────────────────────────────
473
+ {
474
+ name: 'admin_setup_project',
475
+ description:
476
+ 'Project hook management — analyze, cleanup duplicates, or install agent hooks. Analysis mode by default.',
477
+ auth: 'write',
478
+ schema: z.object({
479
+ projectPath: z.string().describe('Project root path'),
480
+ cleanup: z
481
+ .boolean()
482
+ .optional()
483
+ .default(false)
484
+ .describe('Remove project hooks that already exist globally'),
485
+ install: z
486
+ .boolean()
487
+ .optional()
488
+ .default(false)
489
+ .describe('Install agent hooks to the project'),
490
+ }),
491
+ handler: async (params) => {
492
+ const projectPath = resolve(params.projectPath as string);
493
+ const cleanup = params.cleanup as boolean;
494
+ const install = params.install as boolean;
495
+
496
+ if (!existsSync(projectPath)) {
497
+ return { error: 'PROJECT_NOT_FOUND', path: projectPath };
498
+ }
499
+
500
+ const globalClaudeDir = join(homedir(), '.claude');
501
+ const projectClaudeDir = join(projectPath, '.claude');
502
+
503
+ // Discover existing hooks
504
+ const globalHookify = discoverHookifyFiles(globalClaudeDir);
505
+ const projectHookify = discoverHookifyFiles(projectClaudeDir);
506
+ const globalNames = new Set(globalHookify.map((h) => h.name));
507
+
508
+ // Find duplicates (project hooks that also exist globally)
509
+ const duplicates = projectHookify.filter((h) => globalNames.has(h.name));
510
+
511
+ // Analyze mode (default)
512
+ if (!cleanup && !install) {
513
+ return {
514
+ mode: 'analyze',
515
+ projectPath,
516
+ globalHooks: globalHookify.length,
517
+ projectHooks: projectHookify.length,
518
+ duplicates: duplicates.map((d) => d.name),
519
+ recommendations:
520
+ duplicates.length > 0
521
+ ? [`${duplicates.length} project hook(s) duplicate global hooks — consider cleanup`]
522
+ : ['No duplicates found — hooks are clean'],
523
+ };
524
+ }
525
+
526
+ // Cleanup mode — remove duplicates
527
+ if (cleanup) {
528
+ const removed: string[] = [];
529
+ for (const dup of duplicates) {
530
+ try {
531
+ unlinkSync(dup.path);
532
+ removed.push(dup.name);
533
+ } catch {
534
+ // Skip failures silently
535
+ }
536
+ }
537
+ return {
538
+ mode: 'cleanup',
539
+ projectPath,
540
+ removed,
541
+ remaining: projectHookify.length - removed.length,
542
+ };
543
+ }
544
+
545
+ // Install mode — copy agent hooks to project
546
+ if (install) {
547
+ const installed: string[] = [];
548
+ const skipped: string[] = [];
549
+
550
+ // Copy hookify rules (skip ones already installed globally)
551
+ const agentDataDir = config.dataDir;
552
+ if (agentDataDir) {
553
+ const sourceRules = discoverHookifyFiles(join(agentDataDir, '.claude'));
554
+ for (const rule of sourceRules) {
555
+ const targetPath = join(projectClaudeDir, rule.name);
556
+ if (globalNames.has(rule.name)) {
557
+ skipped.push(`${rule.name} (exists globally)`);
558
+ } else if (existsSync(targetPath)) {
559
+ skipped.push(`${rule.name} (exists in project)`);
560
+ } else {
561
+ try {
562
+ mkdirSync(projectClaudeDir, { recursive: true });
563
+ copyFileSync(rule.path, targetPath);
564
+ installed.push(rule.name);
565
+ } catch {
566
+ // Skip failures
567
+ }
568
+ }
569
+ }
570
+ }
571
+
572
+ return {
573
+ mode: 'install',
574
+ projectPath,
575
+ installed,
576
+ skipped,
577
+ };
578
+ }
579
+
580
+ return { error: 'INVALID_MODE' };
581
+ },
582
+ },
583
+
584
+ // ─── check_persistence ─────────────────────────────────────────
585
+ {
586
+ name: 'admin_check_persistence',
587
+ description:
588
+ 'Check agent persistence status — storage directory, plan/task/check files, and active plan lifecycle states.',
589
+ auth: 'read',
590
+ handler: async () => {
591
+ const { agentId, plansPath, vaultPath } = config;
592
+ const storageDir = join(homedir(), `.${agentId}`);
593
+ const storageDirExists = existsSync(storageDir);
594
+
595
+ // Check plan storage
596
+ const plansFile = plansPath ?? join(storageDir, 'plans.json');
597
+ const plansInfo = getFileInfo(plansFile);
598
+
599
+ // Check vault
600
+ const vaultFile = vaultPath ?? join(storageDir, 'vault.db');
601
+ const vaultExists = existsSync(vaultFile);
602
+ let vaultSize = 0;
603
+ if (vaultExists) {
604
+ try {
605
+ vaultSize = statSync(vaultFile).size;
606
+ } catch {
607
+ // Ignore
608
+ }
609
+ }
610
+
611
+ // Determine status
612
+ let status: string;
613
+ if (vaultExists && plansInfo.exists) {
614
+ status = 'PERSISTENCE_ACTIVE';
615
+ } else if (storageDirExists) {
616
+ status = 'PERSISTENCE_CONFIGURED_BUT_INCOMPLETE';
617
+ } else {
618
+ status = 'NO_STORAGE_DIRECTORY';
619
+ }
620
+
621
+ // Check for active plans
622
+ const activePlans: Array<{ id: string; status: string }> = [];
623
+ if (plansInfo.exists) {
624
+ try {
625
+ const plansData = JSON.parse(readFileSync(plansFile, 'utf-8'));
626
+ const items = plansData.items ?? plansData;
627
+ if (typeof items === 'object' && items !== null) {
628
+ for (const [id, plan] of Object.entries(items)) {
629
+ const p = plan as Record<string, unknown>;
630
+ const lifecycle = (p.lifecycleStatus ?? p.status) as string | undefined;
631
+ if (lifecycle === 'executing' || lifecycle === 'reconciling') {
632
+ activePlans.push({ id, status: lifecycle });
633
+ }
634
+ }
635
+ }
636
+ } catch {
637
+ // Parse error — not critical
638
+ }
639
+ }
640
+
641
+ const recommendation =
642
+ activePlans.length > 0
643
+ ? `${activePlans.length} plan(s) need attention — call plan_reconcile or plan_complete_lifecycle`
644
+ : status === 'PERSISTENCE_ACTIVE'
645
+ ? 'All good — persistence is active and no orphaned plans'
646
+ : status === 'NO_STORAGE_DIRECTORY'
647
+ ? `Storage directory not found at ${storageDir} — it will be created on first use`
648
+ : 'Storage directory exists but some files are missing — they will be created on first use';
649
+
650
+ return {
651
+ agentId,
652
+ storageDirectory: { path: storageDir, exists: storageDirExists },
653
+ files: {
654
+ plans: { path: plansFile, ...plansInfo },
655
+ vault: { path: vaultFile, exists: vaultExists, sizeBytes: vaultSize },
656
+ },
657
+ status,
658
+ activePlans,
659
+ recommendation,
660
+ };
661
+ },
662
+ },
663
+ ];
664
+ }