@kontextmind/kxm 0.6.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 (227) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.kxm/README.md +14 -0
  3. package/.kxm/assets/README.md +5 -0
  4. package/.kxm/assets/retrospectives/README.md +5 -0
  5. package/.kxm/config/README.md +5 -0
  6. package/.kxm/config/agents.json +43 -0
  7. package/.kxm/config/env.example +56 -0
  8. package/.kxm/config/update.example.yaml +9 -0
  9. package/.kxm/config/workflows/fix.json +160 -0
  10. package/.kxm/config/workflows/jira-development.json +116 -0
  11. package/.kxm/config/workflows/provenance-quorum.json +150 -0
  12. package/.kxm/config/workflows/v04-dogfood.json +72 -0
  13. package/CHANGELOG.md +465 -0
  14. package/LICENSE +21 -0
  15. package/README.md +306 -0
  16. package/SECURITY.md +72 -0
  17. package/docs/README.md +48 -0
  18. package/docs/agent-communication-envelopes-and-gates.md +553 -0
  19. package/docs/architecture.md +242 -0
  20. package/docs/assignment-runner.md +241 -0
  21. package/docs/configuration.md +361 -0
  22. package/docs/continuous-improvement.md +114 -0
  23. package/docs/getting-started.md +253 -0
  24. package/docs/kxm-handbook.md +1090 -0
  25. package/docs/operations.md +205 -0
  26. package/docs/provenance-gates.md +291 -0
  27. package/docs/skills.md +45 -0
  28. package/docs/templates/README.md +95 -0
  29. package/docs/templates/adr.md +88 -0
  30. package/docs/templates/architecture.md +120 -0
  31. package/docs/templates/bug-fix.md +109 -0
  32. package/docs/templates/feature.md +108 -0
  33. package/docs/templates/handoff.md +72 -0
  34. package/docs/templates/postmortem.md +77 -0
  35. package/docs/templates/research.md +100 -0
  36. package/docs/templates/review.md +85 -0
  37. package/docs/templates/runbook.md +73 -0
  38. package/docs/templates/test-plan.md +87 -0
  39. package/docs/templates/test-report.md +72 -0
  40. package/docs/test-matrix.md +121 -0
  41. package/docs/troubleshooting.md +249 -0
  42. package/docs/vnext/README.md +62 -0
  43. package/docs/vnext/architecture.md +185 -0
  44. package/docs/vnext/effects-and-recovery.md +172 -0
  45. package/docs/vnext/lifecycles.md +235 -0
  46. package/docs/vnext/migration.md +220 -0
  47. package/docs/vnext/routing.md +184 -0
  48. package/docs/vnext/synchronization.md +172 -0
  49. package/docs/vnext/terminology.md +240 -0
  50. package/docs/vnext/validation.md +335 -0
  51. package/docs/webhook-workflows.md +240 -0
  52. package/docs/workflow-guide.md +1150 -0
  53. package/examples/README.md +102 -0
  54. package/examples/provenance-workflow.json +40 -0
  55. package/examples/requester.ts +30 -0
  56. package/examples/reviewer-agent.ts +29 -0
  57. package/examples/roundtrip.ts +46 -0
  58. package/examples/vnext/.kxm/agents/coordinator.yaml +16 -0
  59. package/examples/vnext/.kxm/agents/critic-1.yaml +16 -0
  60. package/examples/vnext/.kxm/agents/critic-2.yaml +15 -0
  61. package/examples/vnext/.kxm/agents/critic-3.yaml +15 -0
  62. package/examples/vnext/.kxm/agents/implementer.yaml +15 -0
  63. package/examples/vnext/.kxm/agents/planner.yaml +13 -0
  64. package/examples/vnext/.kxm/agents/reproducer.yaml +15 -0
  65. package/examples/vnext/.kxm/agents/reviewer.yaml +15 -0
  66. package/examples/vnext/.kxm/gates.yaml +8 -0
  67. package/examples/vnext/.kxm/models/critic-claude.yaml +11 -0
  68. package/examples/vnext/.kxm/models/critic-gemini.yaml +11 -0
  69. package/examples/vnext/.kxm/models/critic-grok.yaml +12 -0
  70. package/examples/vnext/.kxm/models/implementation.yaml +14 -0
  71. package/examples/vnext/.kxm/models/primary.yaml +17 -0
  72. package/examples/vnext/.kxm/prices.yaml +111 -0
  73. package/examples/vnext/.kxm/project/env.yaml +7 -0
  74. package/examples/vnext/.kxm/project.yaml +32 -0
  75. package/examples/vnext/.kxm/repo/repo.yaml +8 -0
  76. package/examples/vnext/.kxm/workflows/default.yaml +92 -0
  77. package/examples/vnext/.kxm/workflows/fix.yaml +376 -0
  78. package/examples/vnext/.kxm/workflows/improve.yaml +57 -0
  79. package/examples/vnext/README.md +53 -0
  80. package/examples/vnext/records/assignment-result-recorded.json +63 -0
  81. package/examples/vnext/records/assignment-result.json +46 -0
  82. package/examples/vnext/records/context-candidate.json +42 -0
  83. package/examples/vnext/records/delivery-manifest.json +66 -0
  84. package/examples/vnext/records/effect-uncertainty-resolved-sync.json +67 -0
  85. package/examples/vnext/records/effect-uncertainty-resolved.json +62 -0
  86. package/examples/vnext/records/run-created.json +54 -0
  87. package/examples/vnext/records/sync-event.json +65 -0
  88. package/examples/vnext/repositories/api/.kxm/repo/env.yaml +7 -0
  89. package/examples/vnext/repositories/api/.kxm/repo/repo.yaml +8 -0
  90. package/examples/vnext/repositories/web/.kxm/repo/repo.yaml +8 -0
  91. package/examples/workflow-signal.ts +63 -0
  92. package/package.json +129 -0
  93. package/plugins/kxm/.claude-plugin/plugin.json +73 -0
  94. package/plugins/kxm/.mcp.json +19 -0
  95. package/plugins/kxm/README.md +93 -0
  96. package/plugins/kxm/dist/cli.js +42853 -0
  97. package/plugins/kxm/dist/client.js +416 -0
  98. package/plugins/kxm/dist/core.js +1823 -0
  99. package/plugins/kxm/dist/extension.js +3797 -0
  100. package/plugins/kxm/dist/mcp-server.js +17104 -0
  101. package/plugins/kxm/dist/runtime.js +23361 -0
  102. package/plugins/kxm/dist/server.js +13640 -0
  103. package/plugins/kxm/dist/vnext-runtime-supervisor.js +21109 -0
  104. package/plugins/kxm/package.json +12 -0
  105. package/plugins/kxm/skills/kxm/SKILL.md +97 -0
  106. package/plugins/kxm/skills/kxm/references/protocol.md +103 -0
  107. package/plugins/kxm/skills/kxm-session/SKILL.md +53 -0
  108. package/plugins/kxm/src/arbiter.ts +355 -0
  109. package/plugins/kxm/src/artifacts-exist.ts +62 -0
  110. package/plugins/kxm/src/autocomplete.ts +236 -0
  111. package/plugins/kxm/src/cli.ts +3707 -0
  112. package/plugins/kxm/src/client.ts +614 -0
  113. package/plugins/kxm/src/commands.ts +1063 -0
  114. package/plugins/kxm/src/config.ts +290 -0
  115. package/plugins/kxm/src/context/providers.ts +101 -0
  116. package/plugins/kxm/src/context-packet.ts +332 -0
  117. package/plugins/kxm/src/context.ts +499 -0
  118. package/plugins/kxm/src/core.ts +6 -0
  119. package/plugins/kxm/src/database.ts +563 -0
  120. package/plugins/kxm/src/diagnostics.ts +184 -0
  121. package/plugins/kxm/src/envelope.ts +118 -0
  122. package/plugins/kxm/src/extension.ts +895 -0
  123. package/plugins/kxm/src/external-effects.ts +299 -0
  124. package/plugins/kxm/src/github-watch.ts +255 -0
  125. package/plugins/kxm/src/hub-binding.ts +160 -0
  126. package/plugins/kxm/src/hub.ts +2502 -0
  127. package/plugins/kxm/src/improve.ts +383 -0
  128. package/plugins/kxm/src/inbox.ts +10 -0
  129. package/plugins/kxm/src/kxm-install-kind.ts +113 -0
  130. package/plugins/kxm/src/kxm-update-config.ts +39 -0
  131. package/plugins/kxm/src/kxm-update.ts +238 -0
  132. package/plugins/kxm/src/local-snapshot.ts +406 -0
  133. package/plugins/kxm/src/logger.ts +198 -0
  134. package/plugins/kxm/src/mcp-server.ts +143 -0
  135. package/plugins/kxm/src/memory.ts +385 -0
  136. package/plugins/kxm/src/nous-pi.ts +287 -0
  137. package/plugins/kxm/src/nous-provider.ts +729 -0
  138. package/plugins/kxm/src/price-calc.ts +87 -0
  139. package/plugins/kxm/src/prices.ts +121 -0
  140. package/plugins/kxm/src/protocol.ts +172 -0
  141. package/plugins/kxm/src/recovery.ts +211 -0
  142. package/plugins/kxm/src/redact.ts +26 -0
  143. package/plugins/kxm/src/retrospective.ts +400 -0
  144. package/plugins/kxm/src/routing.ts +830 -0
  145. package/plugins/kxm/src/runtime.ts +9 -0
  146. package/plugins/kxm/src/server.ts +117 -0
  147. package/plugins/kxm/src/session-work.ts +571 -0
  148. package/plugins/kxm/src/session.ts +184 -0
  149. package/plugins/kxm/src/skills.ts +535 -0
  150. package/plugins/kxm/src/state.ts +326 -0
  151. package/plugins/kxm/src/store.ts +637 -0
  152. package/plugins/kxm/src/studio-layout.ts +268 -0
  153. package/plugins/kxm/src/suggest.ts +162 -0
  154. package/plugins/kxm/src/task-manager.ts +244 -0
  155. package/plugins/kxm/src/telemetry.ts +116 -0
  156. package/plugins/kxm/src/tui.ts +1046 -0
  157. package/plugins/kxm/src/vnext-bindings.ts +403 -0
  158. package/plugins/kxm/src/vnext-config.ts +1646 -0
  159. package/plugins/kxm/src/vnext-engine-artifacts.ts +86 -0
  160. package/plugins/kxm/src/vnext-engine-command.ts +533 -0
  161. package/plugins/kxm/src/vnext-engine-compile.ts +722 -0
  162. package/plugins/kxm/src/vnext-engine-evidence.ts +273 -0
  163. package/plugins/kxm/src/vnext-engine-fold.ts +1400 -0
  164. package/plugins/kxm/src/vnext-engine-gate-records.ts +583 -0
  165. package/plugins/kxm/src/vnext-engine-plan.ts +717 -0
  166. package/plugins/kxm/src/vnext-engine.ts +2458 -0
  167. package/plugins/kxm/src/vnext-gate-hash.ts +10 -0
  168. package/plugins/kxm/src/vnext-harness.ts +1142 -0
  169. package/plugins/kxm/src/vnext-init.ts +430 -0
  170. package/plugins/kxm/src/vnext-migrate.ts +1848 -0
  171. package/plugins/kxm/src/vnext-oneshot-producer.ts +424 -0
  172. package/plugins/kxm/src/vnext-permission.ts +936 -0
  173. package/plugins/kxm/src/vnext-pi-producer.ts +628 -0
  174. package/plugins/kxm/src/vnext-repair.ts +1094 -0
  175. package/plugins/kxm/src/vnext-runtime-owner.ts +320 -0
  176. package/plugins/kxm/src/vnext-runtime-store.ts +1560 -0
  177. package/plugins/kxm/src/vnext-runtime-supervisor.ts +586 -0
  178. package/plugins/kxm/src/vnext-runtime.ts +663 -0
  179. package/plugins/kxm/src/vnext-template.ts +247 -0
  180. package/plugins/kxm/src/wiki.ts +313 -0
  181. package/plugins/kxm/src/workflow.ts +1548 -0
  182. package/schemas/vnext/README.md +46 -0
  183. package/schemas/vnext/agent.schema.json +40 -0
  184. package/schemas/vnext/assignment-result.schema.json +66 -0
  185. package/schemas/vnext/backup-manifest.schema.json +89 -0
  186. package/schemas/vnext/candidate.schema.json +109 -0
  187. package/schemas/vnext/common.schema.json +422 -0
  188. package/schemas/vnext/context-candidate.schema.json +76 -0
  189. package/schemas/vnext/context-packet.schema.json +192 -0
  190. package/schemas/vnext/delivery-manifest.schema.json +159 -0
  191. package/schemas/vnext/environment.schema.json +66 -0
  192. package/schemas/vnext/gate-registry.schema.json +109 -0
  193. package/schemas/vnext/handoff-manifest.schema.json +146 -0
  194. package/schemas/vnext/init-operation.schema.json +61 -0
  195. package/schemas/vnext/local-repository-bindings.schema.json +30 -0
  196. package/schemas/vnext/memory-record.schema.json +45 -0
  197. package/schemas/vnext/migration-decision.schema.json +26 -0
  198. package/schemas/vnext/migration-plan.schema.json +123 -0
  199. package/schemas/vnext/migration-receipt.schema.json +52 -0
  200. package/schemas/vnext/model.schema.json +42 -0
  201. package/schemas/vnext/permission-diff.schema.json +57 -0
  202. package/schemas/vnext/prices.schema.json +115 -0
  203. package/schemas/vnext/project.schema.json +85 -0
  204. package/schemas/vnext/repository.schema.json +24 -0
  205. package/schemas/vnext/run-event.schema.json +460 -0
  206. package/schemas/vnext/session-brief.schema.json +153 -0
  207. package/schemas/vnext/sync-event.schema.json +234 -0
  208. package/schemas/vnext/template-provenance.schema.json +38 -0
  209. package/schemas/vnext/workflow.schema.json +248 -0
  210. package/scripts/assignment-run.d.mts +354 -0
  211. package/scripts/assignment-run.mjs +4451 -0
  212. package/scripts/build-runtime.mjs +56 -0
  213. package/scripts/check-generated.mjs +77 -0
  214. package/scripts/check-versions.mjs +34 -0
  215. package/scripts/emit-codex-artifacts.d.mts +9 -0
  216. package/scripts/emit-codex-artifacts.mjs +91 -0
  217. package/scripts/harness-run.d.mts +83 -0
  218. package/scripts/harness-run.mjs +2095 -0
  219. package/scripts/kxm-hub.mjs +105 -0
  220. package/scripts/kxm-publish-npm.mjs +327 -0
  221. package/scripts/kxm-release-github.mjs +472 -0
  222. package/scripts/kxm-runtime-supervisor.mjs +7 -0
  223. package/scripts/kxm-worker.mjs +1127 -0
  224. package/scripts/kxm.mjs +27 -0
  225. package/scripts/roster-policy.d.mts +20 -0
  226. package/scripts/roster-policy.mjs +161 -0
  227. package/scripts/smoke-multi-pi.mjs +479 -0
@@ -0,0 +1,198 @@
1
+ import { appendFileSync, existsSync, mkdirSync, renameSync, statSync, unlinkSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { redactSecrets } from "./redact.ts";
4
+
5
+ export type LogLevel = "debug" | "info" | "warn" | "error";
6
+
7
+ export const LOG_LEVEL_PRIORITY: Record<LogLevel, number> = {
8
+ debug: 10,
9
+ info: 20,
10
+ warn: 30,
11
+ error: 40,
12
+ };
13
+
14
+ export const DEFAULT_LOG_MAX_BYTES = 10 * 1024 * 1024; // 10 MB
15
+ export const DEFAULT_LOG_MAX_FILES = 3;
16
+
17
+ const SENSITIVE_KEY_PATTERN = /(?:^|_)(?:token|secret|password|apiKey|api_key|authorization|bearer)(?:$|_)/i;
18
+ const ALLOWED_EXACT_KEYS = new Set(["auth", "authType", "authMethod", "authArgs", "canUpdate", "status"]);
19
+
20
+ export function redactLogValue(val: unknown, key?: string): unknown {
21
+ if (val === null || val === undefined) return val;
22
+ if (typeof val === "string") {
23
+ if (key && SENSITIVE_KEY_PATTERN.test(key) && !ALLOWED_EXACT_KEYS.has(key)) {
24
+ return "[redacted]";
25
+ }
26
+ return redactSecrets(val);
27
+ }
28
+ if (typeof val === "number" || typeof val === "boolean") {
29
+ return val;
30
+ }
31
+ if (Array.isArray(val)) {
32
+ return val.map((item) => redactLogValue(item, key));
33
+ }
34
+ if (typeof val === "object") {
35
+ const out: Record<string, unknown> = {};
36
+ for (const [k, v] of Object.entries(val as Record<string, unknown>)) {
37
+ out[k] = redactLogValue(v, k);
38
+ }
39
+ return out;
40
+ }
41
+ return String(val);
42
+ }
43
+
44
+ export function rotateLogFiles(filePath: string, maxFiles: number): void {
45
+ for (let i = maxFiles; i >= 1; i--) {
46
+ const current = `${filePath}.${i}`;
47
+ if (existsSync(current)) {
48
+ if (i >= maxFiles) {
49
+ try { unlinkSync(current); } catch { /* best effort */ }
50
+ } else {
51
+ try { renameSync(current, `${filePath}.${i + 1}`); } catch { /* best effort */ }
52
+ }
53
+ }
54
+ }
55
+ if (existsSync(filePath)) {
56
+ try { renameSync(filePath, `${filePath}.1`); } catch { /* best effort */ }
57
+ }
58
+ }
59
+
60
+ export interface LogEntry {
61
+ timestamp?: string | undefined;
62
+ level?: LogLevel | string | undefined;
63
+ component?: string | undefined;
64
+ event?: string | undefined;
65
+ message?: string | undefined;
66
+ correlationId?: string | undefined;
67
+ requestId?: string | undefined;
68
+ runId?: string | undefined;
69
+ sessionId?: string | undefined;
70
+ traceId?: string | undefined;
71
+ [key: string]: unknown;
72
+ }
73
+
74
+ export interface LoggerOptions {
75
+ component: string;
76
+ path?: string | undefined;
77
+ maxBytes?: number | undefined;
78
+ maxFiles?: number | undefined;
79
+ level?: LogLevel | undefined;
80
+ stdout?: boolean | undefined;
81
+ daemon?: boolean | undefined;
82
+ correlation?: Record<string, string | number | undefined> | undefined;
83
+ }
84
+
85
+ export interface Logger {
86
+ (entryOrEvent: string | Record<string, unknown>, extra?: Record<string, unknown>): void;
87
+ info(entryOrEvent: string | Record<string, unknown>, extra?: Record<string, unknown>): void;
88
+ warn(entryOrEvent: string | Record<string, unknown>, extra?: Record<string, unknown>): void;
89
+ error(entryOrEvent: string | Record<string, unknown>, extra?: Record<string, unknown>): void;
90
+ debug(entryOrEvent: string | Record<string, unknown>, extra?: Record<string, unknown>): void;
91
+ child(context: { component?: string | undefined; correlation?: Record<string, string | number | undefined> | undefined }): Logger;
92
+ close(): void;
93
+ readonly options: Readonly<LoggerOptions>;
94
+ }
95
+
96
+ export function createLogger(options: LoggerOptions): Logger {
97
+ const component = options.component;
98
+ const filePath = options.path;
99
+ const maxBytes = Math.max(100, options.maxBytes ?? DEFAULT_LOG_MAX_BYTES);
100
+ const maxFiles = Math.max(1, options.maxFiles ?? DEFAULT_LOG_MAX_FILES);
101
+ const configuredLevel: LogLevel = options.level ?? "info";
102
+ const isDaemon = Boolean(options.daemon ?? (process.env.KXM_DAEMON === "1" || process.env.KXM_DAEMON === "true"));
103
+ const shouldStdout = options.stdout ?? !isDaemon;
104
+ const correlationDefaults = options.correlation ?? {};
105
+
106
+ let currentSize = 0;
107
+ if (filePath && existsSync(filePath)) {
108
+ try {
109
+ currentSize = statSync(filePath).size;
110
+ } catch {
111
+ currentSize = 0;
112
+ }
113
+ }
114
+
115
+ function emit(level: LogLevel, entryOrEvent: string | Record<string, unknown>, extra?: Record<string, unknown>): void {
116
+ const minPriority = LOG_LEVEL_PRIORITY[configuredLevel] ?? LOG_LEVEL_PRIORITY.info;
117
+ const currentPriority = LOG_LEVEL_PRIORITY[level] ?? LOG_LEVEL_PRIORITY.info;
118
+ if (currentPriority < minPriority) return;
119
+
120
+ let base: Record<string, unknown>;
121
+ if (typeof entryOrEvent === "string") {
122
+ base = { event: entryOrEvent, ...extra };
123
+ } else {
124
+ base = { ...entryOrEvent, ...extra };
125
+ }
126
+
127
+ const timestamp = typeof base.timestamp === "string" ? base.timestamp : new Date().toISOString();
128
+ delete base.timestamp;
129
+ delete base.level;
130
+ delete base.component;
131
+
132
+ const payload: Record<string, unknown> = {
133
+ timestamp,
134
+ level,
135
+ component,
136
+ ...correlationDefaults,
137
+ ...base,
138
+ };
139
+
140
+ const sanitized = redactLogValue(payload) as Record<string, unknown>;
141
+ const line = `${JSON.stringify(sanitized)}\n`;
142
+
143
+ if (filePath) {
144
+ const lineBytes = Buffer.byteLength(line, "utf8");
145
+ if (currentSize + lineBytes > maxBytes) {
146
+ rotateLogFiles(filePath, maxFiles);
147
+ currentSize = 0;
148
+ }
149
+ try {
150
+ mkdirSync(dirname(filePath), { recursive: true });
151
+ appendFileSync(filePath, line, { encoding: "utf8", mode: 0o600 });
152
+ currentSize += lineBytes;
153
+ } catch {
154
+ // best effort write
155
+ }
156
+ }
157
+
158
+ if (shouldStdout) {
159
+ process.stdout.write(line);
160
+ }
161
+ }
162
+
163
+ const logFn = ((entryOrEvent: string | Record<string, unknown>, extra?: Record<string, unknown>) => {
164
+ let lvl: LogLevel = "info";
165
+ if (typeof entryOrEvent === "object" && entryOrEvent !== null && typeof entryOrEvent.level === "string") {
166
+ const candidate = entryOrEvent.level.toLowerCase();
167
+ if (candidate === "debug" || candidate === "info" || candidate === "warn" || candidate === "error") {
168
+ lvl = candidate;
169
+ }
170
+ }
171
+ emit(lvl, entryOrEvent, extra);
172
+ }) as Logger;
173
+
174
+ logFn.info = (entryOrEvent, extra) => emit("info", entryOrEvent, extra);
175
+ logFn.warn = (entryOrEvent, extra) => emit("warn", entryOrEvent, extra);
176
+ logFn.error = (entryOrEvent, extra) => emit("error", entryOrEvent, extra);
177
+ logFn.debug = (entryOrEvent, extra) => emit("debug", entryOrEvent, extra);
178
+
179
+ logFn.child = (sub) => {
180
+ return createLogger({
181
+ ...options,
182
+ component: sub.component ? `${component}.${sub.component}` : component,
183
+ correlation: { ...correlationDefaults, ...sub.correlation },
184
+ });
185
+ };
186
+
187
+ logFn.close = () => {
188
+ // Synchronous file appends need no stream flush
189
+ };
190
+
191
+ Object.defineProperty(logFn, "options", {
192
+ value: Object.freeze({ ...options }),
193
+ writable: false,
194
+ enumerable: true,
195
+ });
196
+
197
+ return logFn;
198
+ }
@@ -0,0 +1,143 @@
1
+ import { basename } from "node:path";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
5
+ import { HubClient } from "./client.ts";
6
+ import { AGENT_COMMANDS_MAP, enforceToolPolicy, getMcpTools, reconcileInbox } from "./commands.ts";
7
+ import { deliverInboxNotification } from "./inbox.ts";
8
+ import type { HubEvent, MessageRecord } from "./protocol.ts";
9
+
10
+ const VERSION = "0.6.0";
11
+ const inbox = new Map<string, MessageRecord>();
12
+ const notifiedInbox = new Set<string>();
13
+ let meshClient: HubClient | undefined;
14
+ let starting: Promise<HubClient> | undefined;
15
+
16
+ const mcp = new Server(
17
+ { name: "kxm", version: VERSION },
18
+ {
19
+ capabilities: {
20
+ experimental: { "claude/channel": {} },
21
+ tools: {},
22
+ },
23
+ instructions: [
24
+ 'KXM peer requests can arrive as <channel source="kxm" message_id="..."> events.',
25
+ "Handle the request using normal safety rules, then call kxm_reply with message_id and the final response.",
26
+ "Use kxm_inbox as a fallback when channel delivery is not enabled.",
27
+ "For durable workflow requests, call kxm_workflow_get, record material plans/decisions/contradictions/errors/lessons, and pass every checkpoint before replying.",
28
+ "If work is running in an external system, call kxm_workflow_wait and then kxm_reply so a signed callback can resume the workflow later.",
29
+ ].join(" "),
30
+ },
31
+ );
32
+
33
+ function textResult(value: unknown) {
34
+ return { content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }] };
35
+ }
36
+
37
+ function optionalString(value: unknown): string | undefined {
38
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
39
+ }
40
+
41
+ function asRecord(value: unknown): Record<string, unknown> {
42
+ return value && typeof value === "object" && !Array.isArray(value)
43
+ ? (value as Record<string, unknown>)
44
+ : {};
45
+ }
46
+
47
+ async function onHubEvent(event: HubEvent): Promise<void> {
48
+ if (event.type === "cancelled" || event.type === "expired") {
49
+ inbox.delete(event.message.id);
50
+ notifiedInbox.delete(event.message.id);
51
+ return;
52
+ }
53
+ if (event.type !== "message") return;
54
+ const client = meshClient;
55
+ if (!client) return;
56
+ if (event.message.status === "queued") await client.acknowledge(event.message.id);
57
+ inbox.set(event.message.id, event.message);
58
+ const meta: Record<string, string> = {
59
+ message_id: event.message.id,
60
+ from_agent: event.message.fromName,
61
+ delivery: event.message.delivery,
62
+ };
63
+ if (event.message.correlationId) meta.correlation_id = event.message.correlationId;
64
+ await deliverInboxNotification(event.message.id, notifiedInbox, async () => {
65
+ await mcp.notification({
66
+ method: "notifications/claude/channel",
67
+ params: {
68
+ content: [
69
+ `Peer request from ${event.message.fromName}:`,
70
+ "",
71
+ event.message.content,
72
+ "",
73
+ `When complete, call kxm_reply with messageId ${event.message.id}.`,
74
+ ].join("\n"),
75
+ meta,
76
+ },
77
+ });
78
+ });
79
+ }
80
+
81
+ async function ensureClient(): Promise<HubClient> {
82
+ if (meshClient?.agent) return meshClient;
83
+ if (starting) return starting;
84
+ starting = (async () => {
85
+ const projectDir = process.env.KXM_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
86
+ const authToken = optionalString(process.env.KXM_AUTH_TOKEN);
87
+ const candidate = new HubClient({
88
+ serverUrl: process.env.KXM_SERVER_URL?.trim() || "http://127.0.0.1:7331",
89
+ name: process.env.KXM_AGENT_NAME?.trim() || `claude-${process.pid}`,
90
+ purpose: process.env.KXM_AGENT_PURPOSE?.trim() || "Claude Code implementation and review agent",
91
+ project: process.env.KXM_PROJECT?.trim() || basename(projectDir),
92
+ model: "claude-code",
93
+ ...(authToken ? { authToken } : {}),
94
+ });
95
+ try {
96
+ await candidate.start(onHubEvent);
97
+ meshClient = candidate;
98
+ return candidate;
99
+ } catch (error) {
100
+ await candidate.stop();
101
+ throw error;
102
+ }
103
+ })();
104
+ try {
105
+ return await starting;
106
+ } finally {
107
+ starting = undefined;
108
+ }
109
+ }
110
+
111
+ const tools = getMcpTools();
112
+
113
+ mcp.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
114
+
115
+ mcp.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
116
+ try {
117
+ const policy = enforceToolPolicy(request.params.name);
118
+ if (!policy.allowed) {
119
+ throw new Error(`tool_policy_denied: ${policy.detail ?? policy.error}`);
120
+ }
121
+ const client = await ensureClient();
122
+ const cmd = AGENT_COMMANDS_MAP.get(request.params.name);
123
+ if (!cmd) throw new Error(`unknown tool: ${request.params.name}`);
124
+ const args = asRecord(request.params.arguments);
125
+ const result = await cmd.execute(client, args, { signal: extra.signal, inbox, notifiedInbox });
126
+ return textResult(result);
127
+ } catch (error) {
128
+ return {
129
+ content: [{ type: "text" as const, text: error instanceof Error ? error.message : String(error) }],
130
+ isError: true,
131
+ };
132
+ }
133
+ });
134
+
135
+ await mcp.connect(new StdioServerTransport());
136
+
137
+ async function shutdown(): Promise<void> {
138
+ await meshClient?.stop();
139
+ await mcp.close();
140
+ }
141
+
142
+ process.once("SIGINT", () => void shutdown());
143
+ process.once("SIGTERM", () => void shutdown());