@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,614 @@
1
+ import { createHash } from "node:crypto";
2
+ import type { AgentRecord, DeliveryMode, HubEvent, MessageRecord, WorkflowMessageContext } from "./protocol.ts";
3
+ import type { ContextAuthority, ContextConfidence, ContextItem, ContextItemAuditMetadata, ContextItemKind, ContextPacket } from "./context.ts";
4
+ import {
5
+ canonicalWorkflowEvidenceKey,
6
+ type ImprovementArea,
7
+ type ImprovementAreaReport,
8
+ type JournalCategory,
9
+ type WorkflowCheckpointStatus,
10
+ type WorkflowEvidenceInput,
11
+ type WorkflowEvidenceReferenceInput,
12
+ type WorkflowJournalEntry,
13
+ type WorkflowRun,
14
+ } from "./workflow.ts";
15
+
16
+ /** Metadata-only audit of an assembled context packet. Never contains raw
17
+ * item bodies. */
18
+ export interface ContextRequestAudit {
19
+ request: { project: string; role: string; task: string; workflowRunId?: string; stageId?: string };
20
+ selectedIds: string[];
21
+ provenanceSummary: Record<string, number>;
22
+ estimatedTokens: number;
23
+ budgetTokens: number;
24
+ candidateCount: number;
25
+ excludedSuperseded: number;
26
+ unresolvedGaps: string[];
27
+ }
28
+
29
+ export interface ContextExplanation {
30
+ found: boolean;
31
+ lineage: string[];
32
+ evidenceRefs: string[];
33
+ sources: { id: string; sourceType: string; sourceRef?: string }[];
34
+ }
35
+
36
+ export interface ContextWikiCompilation {
37
+ audit: {
38
+ project: string;
39
+ pages: string[];
40
+ stateItems: number;
41
+ contextItems: number;
42
+ contradictions: number;
43
+ compiledAt: string;
44
+ };
45
+ pages: { path: string; content: string }[];
46
+ }
47
+
48
+ export interface HubClientOptions {
49
+ serverUrl: string;
50
+ authToken?: string;
51
+ name: string;
52
+ purpose: string;
53
+ project: string;
54
+ model?: string;
55
+ heartbeatMs?: number;
56
+ reconnectMs?: number;
57
+ requestTimeoutMs?: number;
58
+ }
59
+
60
+ export interface SendOptions {
61
+ target: string;
62
+ content: string;
63
+ delivery?: DeliveryMode;
64
+ hops?: number;
65
+ maxHops?: number;
66
+ correlationId?: string;
67
+ replyTo?: string;
68
+ idempotencyKey?: string;
69
+ workflowContext?: Omit<WorkflowMessageContext, "schema">;
70
+ ttlMs?: number;
71
+ }
72
+
73
+ export interface FanoutResult {
74
+ target: string;
75
+ messageId?: string;
76
+ status: "pending" | "replied" | "cancelled" | "expired" | "error";
77
+ messageStatus?: "queued" | "delivered";
78
+ expiresAt?: string;
79
+ waitStatus?: "timed_out" | "aborted";
80
+ reply?: string;
81
+ error?: string;
82
+ }
83
+
84
+ class MeshWaitError extends Error {
85
+ readonly waitStatus: "timed_out" | "aborted";
86
+
87
+ constructor(waitStatus: "timed_out" | "aborted", messageId: string) {
88
+ super(waitStatus === "aborted" ? "await cancelled" : `timed out waiting for ${messageId}`);
89
+ this.name = "MeshWaitError";
90
+ this.waitStatus = waitStatus;
91
+ }
92
+ }
93
+
94
+ function completedFanoutResult(target: string, message: MessageRecord): FanoutResult {
95
+ if (message.status === "queued" || message.status === "delivered") {
96
+ throw new Error(`message ${message.id} is not complete`);
97
+ }
98
+ return {
99
+ target,
100
+ messageId: message.id,
101
+ status: message.status,
102
+ ...(message.reply ? { reply: message.reply.content } : {}),
103
+ ...(message.error ? { error: message.error } : {}),
104
+ };
105
+ }
106
+
107
+ function fanoutIdempotencyKey(
108
+ prefix: string,
109
+ target: string,
110
+ correlationId?: string,
111
+ workflowContext?: Omit<WorkflowMessageContext, "schema">,
112
+ ): string {
113
+ // Preserve the exact pre-0.4.3 hash input when no workflow provenance is
114
+ // requested. A pending fanout created before upgrade must remain an exact
115
+ // idempotent retry instead of dispatching duplicate work.
116
+ const scope = JSON.stringify(workflowContext
117
+ ? {
118
+ prefix,
119
+ correlationId: correlationId ?? null,
120
+ target: target.toLowerCase(),
121
+ workflowContext: {
122
+ runId: workflowContext.runId,
123
+ stageId: workflowContext.stageId,
124
+ requirementKey: canonicalWorkflowEvidenceKey(workflowContext.requirementKey),
125
+ attempt: workflowContext.attempt,
126
+ },
127
+ }
128
+ : {
129
+ prefix,
130
+ correlationId: correlationId ?? null,
131
+ target: target.toLowerCase(),
132
+ });
133
+ return `fanout:${createHash("sha256").update(scope).digest("hex")}`;
134
+ }
135
+
136
+ export class HubHttpError extends Error {
137
+ readonly statusCode: number;
138
+ readonly code?: string;
139
+ readonly requestId?: string;
140
+ readonly extras?: Record<string, unknown>;
141
+
142
+ constructor(
143
+ statusCode: number,
144
+ message: string,
145
+ code?: string,
146
+ requestId?: string,
147
+ extras?: Record<string, unknown>,
148
+ ) {
149
+ super(message);
150
+ this.name = "HubHttpError";
151
+ this.statusCode = statusCode;
152
+ if (code) this.code = code;
153
+ if (requestId) this.requestId = requestId;
154
+ if (extras) this.extras = extras;
155
+ }
156
+ }
157
+
158
+ export class HubClient {
159
+ readonly options: HubClientOptions;
160
+ agent: AgentRecord | undefined;
161
+ private agentKey: string | undefined;
162
+ private heartbeatTimer?: NodeJS.Timeout;
163
+ private eventsAbort?: AbortController;
164
+ private stopped = true;
165
+ private onEvent: ((event: HubEvent) => void | Promise<void>) | undefined;
166
+ private registration: Promise<AgentRecord> | undefined;
167
+ private eventLoop: Promise<void> | undefined;
168
+
169
+ constructor(options: HubClientOptions) {
170
+ this.options = { heartbeatMs: 10_000, reconnectMs: 1_000, requestTimeoutMs: 15_000, ...options };
171
+ }
172
+
173
+ async start(onEvent: (event: HubEvent) => void | Promise<void>): Promise<AgentRecord> {
174
+ if (!this.stopped) throw new Error("hub client is already started");
175
+ this.stopped = false;
176
+ this.onEvent = onEvent;
177
+ try {
178
+ await this.register();
179
+ } catch (error) {
180
+ this.stopped = true;
181
+ throw error;
182
+ }
183
+ this.heartbeatTimer = setInterval(() => void this.heartbeat(), this.options.heartbeatMs);
184
+ this.heartbeatTimer.unref();
185
+ this.eventLoop = this.runEventLoop();
186
+ return this.agent!;
187
+ }
188
+
189
+ async stop(): Promise<void> {
190
+ if (this.stopped) return;
191
+ this.stopped = true;
192
+ this.eventsAbort?.abort();
193
+ if (this.heartbeatTimer) clearInterval(this.heartbeatTimer);
194
+ await this.eventLoop;
195
+ if (this.agent) {
196
+ try {
197
+ await this.request(`/v1/agents/${encodeURIComponent(this.agent.id)}`, { method: "DELETE" });
198
+ } catch {
199
+ // The hub may already be gone during process shutdown.
200
+ }
201
+ }
202
+ this.agent = undefined;
203
+ this.agentKey = undefined;
204
+ this.onEvent = undefined;
205
+ this.eventLoop = undefined;
206
+ }
207
+
208
+ async listAgents(): Promise<AgentRecord[]> {
209
+ const result = await this.request<{ agents: AgentRecord[] }>("/v1/agents");
210
+ return result.agents;
211
+ }
212
+
213
+ async send(options: SendOptions): Promise<MessageRecord> {
214
+ const result = await this.request<{ message: MessageRecord }>("/v1/messages", {
215
+ method: "POST",
216
+ body: JSON.stringify(options),
217
+ });
218
+ return result.message;
219
+ }
220
+
221
+ async fanout(options: {
222
+ targets: string[];
223
+ content: string;
224
+ correlationId?: string;
225
+ idempotencyKeyPrefix?: string;
226
+ workflowContext?: Omit<WorkflowMessageContext, "schema">;
227
+ ttlMs?: number;
228
+ timeoutMs?: number;
229
+ signal?: AbortSignal;
230
+ }): Promise<FanoutResult[]> {
231
+ const targets = [...new Set(options.targets.map((target) => target.trim().toLowerCase()).filter(Boolean))];
232
+ if (targets.length < 1 || targets.length > 3) throw new Error("fanout requires between one and three unique targets");
233
+ return await Promise.all(targets.map(async (target): Promise<FanoutResult> => {
234
+ let message: MessageRecord | undefined;
235
+ try {
236
+ message = await this.send({
237
+ target,
238
+ content: options.content,
239
+ delivery: "followUp",
240
+ ...(options.correlationId ? { correlationId: options.correlationId } : {}),
241
+ ...(options.workflowContext ? { workflowContext: options.workflowContext } : {}),
242
+ ...(options.idempotencyKeyPrefix ? {
243
+ idempotencyKey: fanoutIdempotencyKey(
244
+ options.idempotencyKeyPrefix,
245
+ target,
246
+ options.correlationId,
247
+ options.workflowContext,
248
+ ),
249
+ } : {}),
250
+ ...(options.ttlMs ? { ttlMs: options.ttlMs } : {}),
251
+ });
252
+ const completed = await this.awaitResponse(
253
+ message.id,
254
+ options.timeoutMs ?? 30 * 60_000,
255
+ options.signal,
256
+ );
257
+ return completedFanoutResult(target, completed);
258
+ } catch (error) {
259
+ if (message && error instanceof MeshWaitError) {
260
+ try {
261
+ const current = await this.getMessage(message.id);
262
+ if (current.status === "queued" || current.status === "delivered") {
263
+ return {
264
+ target,
265
+ messageId: current.id,
266
+ status: "pending",
267
+ messageStatus: current.status,
268
+ expiresAt: current.expiresAt,
269
+ waitStatus: error.waitStatus,
270
+ };
271
+ }
272
+ return completedFanoutResult(target, current);
273
+ } catch (finalError) {
274
+ return {
275
+ target,
276
+ messageId: message.id,
277
+ status: "error",
278
+ error: finalError instanceof Error ? finalError.message : String(finalError),
279
+ };
280
+ }
281
+ }
282
+ return {
283
+ target,
284
+ ...(message ? { messageId: message.id } : {}),
285
+ status: "error",
286
+ error: error instanceof Error ? error.message : String(error),
287
+ };
288
+ }
289
+ }));
290
+ }
291
+
292
+ async getMessage(messageId: string): Promise<MessageRecord> {
293
+ const result = await this.request<{ message: MessageRecord }>(`/v1/messages/${encodeURIComponent(messageId)}`);
294
+ return result.message;
295
+ }
296
+
297
+ async acknowledge(messageId: string): Promise<MessageRecord> {
298
+ const result = await this.request<{ message: MessageRecord }>(
299
+ `/v1/messages/${encodeURIComponent(messageId)}/ack`,
300
+ { method: "POST", body: "{}" },
301
+ );
302
+ return result.message;
303
+ }
304
+
305
+ async reply(messageId: string, content: string): Promise<MessageRecord> {
306
+ const result = await this.request<{ message: MessageRecord }>(
307
+ `/v1/messages/${encodeURIComponent(messageId)}/reply`,
308
+ { method: "POST", body: JSON.stringify({ content }) },
309
+ );
310
+ return result.message;
311
+ }
312
+
313
+ async cancel(messageId: string): Promise<MessageRecord> {
314
+ const result = await this.request<{ message: MessageRecord }>(
315
+ `/v1/messages/${encodeURIComponent(messageId)}`,
316
+ { method: "DELETE" },
317
+ );
318
+ return result.message;
319
+ }
320
+
321
+ async listWorkflows(): Promise<WorkflowRun[]> {
322
+ const result = await this.request<{ runs: WorkflowRun[] }>("/v1/workflows");
323
+ return result.runs;
324
+ }
325
+
326
+ async getWorkflow(runId: string): Promise<{ run: WorkflowRun; journal: WorkflowJournalEntry[] }> {
327
+ return await this.request(`/v1/workflows/${encodeURIComponent(runId)}`);
328
+ }
329
+
330
+ async checkpointWorkflow(
331
+ runId: string,
332
+ input: {
333
+ stageId: string;
334
+ status: WorkflowCheckpointStatus;
335
+ summary: string;
336
+ evidence?: WorkflowEvidenceInput;
337
+ evidenceRefs?: WorkflowEvidenceReferenceInput;
338
+ },
339
+ ): Promise<{ run: WorkflowRun; retry: boolean; completed: boolean; instruction: string }> {
340
+ return await this.request(`/v1/workflows/${encodeURIComponent(runId)}/checkpoints`, {
341
+ method: "POST",
342
+ body: JSON.stringify(input),
343
+ });
344
+ }
345
+
346
+ async waitForWorkflowSignal(
347
+ runId: string,
348
+ input: {
349
+ stageId: string;
350
+ signalKey: string;
351
+ summary: string;
352
+ evidence?: WorkflowEvidenceInput;
353
+ evidenceRefs?: WorkflowEvidenceReferenceInput;
354
+ timeoutMs?: number;
355
+ },
356
+ ): Promise<{ run: WorkflowRun; instruction: string }> {
357
+ return await this.request(`/v1/workflows/${encodeURIComponent(runId)}/waits`, {
358
+ method: "POST",
359
+ body: JSON.stringify(input),
360
+ });
361
+ }
362
+
363
+ async recordWorkflowEntry(
364
+ runId: string,
365
+ input: {
366
+ category: JournalCategory;
367
+ area: ImprovementArea;
368
+ severity?: "info" | "warning" | "error";
369
+ summary: string;
370
+ details?: string;
371
+ evidence?: string[];
372
+ relatedEntryIds?: string[];
373
+ /** Stage the entry is recorded against; binds run/stage/attempt provenance. */
374
+ stageId?: string;
375
+ },
376
+ ): Promise<WorkflowJournalEntry> {
377
+ const result = await this.request<{ entry: WorkflowJournalEntry }>(
378
+ `/v1/workflows/${encodeURIComponent(runId)}/journal`,
379
+ { method: "POST", body: JSON.stringify(input) },
380
+ );
381
+ return result.entry;
382
+ }
383
+
384
+ async improvementReport(): Promise<{ reports: ImprovementAreaReport[]; entries: number }> {
385
+ return await this.request("/v1/improvements");
386
+ }
387
+
388
+ // ----- Context operating-system API (v0.5, issue #34) -----
389
+
390
+ async contextGet(input: {
391
+ project: string;
392
+ role: string;
393
+ task: string;
394
+ workflowRunId?: string;
395
+ stageId?: string;
396
+ budgetTokens?: number;
397
+ includeKinds?: ContextItemKind[];
398
+ }): Promise<{ packet: ContextPacket; audit: ContextRequestAudit }> {
399
+ return await this.request("/v1/context/get", { method: "POST", body: JSON.stringify(input) });
400
+ }
401
+
402
+ async contextRecall(input: {
403
+ project: string;
404
+ query?: string;
405
+ kinds?: ContextItemKind[];
406
+ limit?: number;
407
+ }): Promise<{ items: ContextItemAuditMetadata[]; unresolvedGaps: string[] }> {
408
+ return await this.request("/v1/context/recall", { method: "POST", body: JSON.stringify(input) });
409
+ }
410
+
411
+ async contextState(input: {
412
+ project: string;
413
+ key: string;
414
+ asOf?: string;
415
+ }): Promise<{ state: ContextItem | null; key: string }> {
416
+ return await this.request("/v1/context/state", { method: "POST", body: JSON.stringify(input) });
417
+ }
418
+
419
+ async contextStatePropose(input: {
420
+ project: string;
421
+ key: string;
422
+ summary: string;
423
+ authority: ContextAuthority;
424
+ confidence: ContextConfidence;
425
+ evidenceRefs: string[];
426
+ }): Promise<{ proposalId: string }> {
427
+ return await this.request("/v1/context/state/propose", { method: "POST", body: JSON.stringify(input) });
428
+ }
429
+
430
+ async contextStatePromote(input: {
431
+ project: string;
432
+ proposalId: string;
433
+ evidence: string[];
434
+ }): Promise<{ state: ContextItem }> {
435
+ return await this.request("/v1/context/state/promote", { method: "POST", body: JSON.stringify(input) });
436
+ }
437
+
438
+ async contextEpisode(input: {
439
+ project: string;
440
+ workflowRunId?: string;
441
+ }): Promise<{ episodes: ContextItem[] }> {
442
+ return await this.request("/v1/context/episode", { method: "POST", body: JSON.stringify(input) });
443
+ }
444
+
445
+ async contextExplain(input: {
446
+ project: string;
447
+ id: string;
448
+ }): Promise<ContextExplanation> {
449
+ return await this.request("/v1/context/explain", { method: "POST", body: JSON.stringify(input) });
450
+ }
451
+
452
+ async contextWikiCompile(input: { project: string }): Promise<ContextWikiCompilation> {
453
+ return await this.request("/v1/context/wiki/compile", { method: "POST", body: JSON.stringify(input) });
454
+ }
455
+
456
+ async awaitResponse(messageId: string, timeoutMs = 30 * 60_000, signal?: AbortSignal): Promise<MessageRecord> {
457
+ const deadline = Date.now() + timeoutMs;
458
+ while (Date.now() < deadline) {
459
+ if (signal?.aborted) throw new MeshWaitError("aborted", messageId);
460
+ const message = await this.getMessage(messageId);
461
+ if (["replied", "cancelled", "expired", "error"].includes(message.status)) return message;
462
+ await new Promise<void>((resolve, reject) => {
463
+ const onAbort = () => {
464
+ signal?.removeEventListener("abort", onAbort);
465
+ clearTimeout(timer);
466
+ reject(new MeshWaitError("aborted", messageId));
467
+ };
468
+ const timer = setTimeout(() => {
469
+ signal?.removeEventListener("abort", onAbort);
470
+ resolve();
471
+ }, Math.min(500, Math.max(1, deadline - Date.now())));
472
+ signal?.addEventListener("abort", onAbort, { once: true });
473
+ if (signal?.aborted) onAbort();
474
+ timer.unref();
475
+ });
476
+ }
477
+ throw new MeshWaitError("timed_out", messageId);
478
+ }
479
+
480
+ private async heartbeat(): Promise<void> {
481
+ if (this.stopped || !this.agent) return;
482
+ try {
483
+ await this.request(`/v1/agents/${encodeURIComponent(this.agent.id)}/heartbeat`, {
484
+ method: "POST",
485
+ body: "{}",
486
+ });
487
+ } catch (error) {
488
+ if (error instanceof HubHttpError && error.statusCode === 401) void this.recoverRegistration();
489
+ }
490
+ }
491
+
492
+ private async runEventLoop(): Promise<void> {
493
+ while (!this.stopped && this.agent) {
494
+ this.eventsAbort = new AbortController();
495
+ try {
496
+ const response = await fetch(
497
+ `${this.options.serverUrl.replace(/\/$/, "")}/v1/events?agentId=${encodeURIComponent(this.agent.id)}`,
498
+ {
499
+ headers: this.headers(),
500
+ signal: this.eventsAbort.signal,
501
+ },
502
+ );
503
+ if (response.status === 401) {
504
+ await response.body?.cancel();
505
+ await this.recoverRegistration();
506
+ continue;
507
+ }
508
+ if (!response.ok || !response.body) throw new Error(`event stream failed with HTTP ${response.status}`);
509
+ const decoder = new TextDecoder();
510
+ let buffer = "";
511
+ for await (const chunk of response.body) {
512
+ if (this.stopped) break;
513
+ buffer += decoder.decode(chunk, { stream: true }).replaceAll("\r\n", "\n");
514
+ let boundary: number;
515
+ while ((boundary = buffer.indexOf("\n\n")) >= 0) {
516
+ const frame = buffer.slice(0, boundary);
517
+ buffer = buffer.slice(boundary + 2);
518
+ const data = frame
519
+ .split("\n")
520
+ .filter((line) => line.startsWith("data:"))
521
+ .map((line) => line.slice(5).trimStart())
522
+ .join("\n");
523
+ if (!data) continue;
524
+ const parsed = JSON.parse(data) as HubEvent | { agent: AgentRecord };
525
+ if ("type" in parsed) await this.onEvent?.(parsed);
526
+ }
527
+ }
528
+ } catch (error) {
529
+ if (this.stopped || (error instanceof Error && error.name === "AbortError")) return;
530
+ }
531
+ if (!this.stopped) await new Promise((resolve) => setTimeout(resolve, this.options.reconnectMs));
532
+ }
533
+ }
534
+
535
+ private headers(includeIdentity = true): Record<string, string> {
536
+ const headers: Record<string, string> = { "content-type": "application/json" };
537
+ if (this.options.authToken) headers.authorization = `Bearer ${this.options.authToken}`;
538
+ if (includeIdentity && this.agent && this.agentKey) {
539
+ headers["x-kxm-agent-id"] = this.agent.id;
540
+ headers["x-kxm-agent-key"] = this.agentKey;
541
+ }
542
+ return headers;
543
+ }
544
+
545
+ private async register(): Promise<AgentRecord> {
546
+ if (this.registration) return this.registration;
547
+ this.registration = (async () => {
548
+ const registration = await this.request<{ agent: AgentRecord; agentKey: string }>("/v1/agents/register", {
549
+ method: "POST",
550
+ body: JSON.stringify({
551
+ name: this.options.name,
552
+ purpose: this.options.purpose,
553
+ project: this.options.project,
554
+ model: this.options.model,
555
+ }),
556
+ }, false);
557
+ this.agent = registration.agent;
558
+ this.agentKey = registration.agentKey;
559
+ return registration.agent;
560
+ })();
561
+ try {
562
+ return await this.registration;
563
+ } finally {
564
+ this.registration = undefined;
565
+ }
566
+ }
567
+
568
+ private async recoverRegistration(): Promise<void> {
569
+ if (this.stopped) return;
570
+ this.agent = undefined;
571
+ this.agentKey = undefined;
572
+ await this.register();
573
+ }
574
+
575
+ private async request<T = unknown>(path: string, init: RequestInit = {}, includeIdentity = true): Promise<T> {
576
+ const requestTimeoutMs = this.options.requestTimeoutMs ?? 15_000;
577
+ const timeoutSignal = AbortSignal.timeout(requestTimeoutMs);
578
+ const signal = init.signal ? AbortSignal.any([init.signal, timeoutSignal]) : timeoutSignal;
579
+ let response: Response;
580
+ try {
581
+ response = await fetch(`${this.options.serverUrl.replace(/\/$/, "")}${path}`, {
582
+ ...init,
583
+ signal,
584
+ headers: { ...this.headers(includeIdentity), ...(init.headers ?? {}) },
585
+ });
586
+ } catch (error) {
587
+ if (timeoutSignal.aborted) throw new Error(`request timed out after ${requestTimeoutMs}ms`);
588
+ throw error;
589
+ }
590
+ const text = await response.text();
591
+ let body: Record<string, unknown> = {};
592
+ if (text) {
593
+ try {
594
+ body = JSON.parse(text) as Record<string, unknown>;
595
+ } catch {
596
+ throw new Error(`hub returned invalid JSON with HTTP ${response.status}`);
597
+ }
598
+ }
599
+ if (!response.ok) {
600
+ const extras: Record<string, unknown> = {};
601
+ for (const key of ["operation", "nextAction", "assignedCoordinatorName"]) {
602
+ if (typeof body[key] === "string") extras[key] = body[key];
603
+ }
604
+ throw new HubHttpError(
605
+ response.status,
606
+ String(body.error ?? `HTTP ${response.status}`),
607
+ typeof body.code === "string" ? body.code : undefined,
608
+ response.headers.get("x-request-id") ?? undefined,
609
+ Object.keys(extras).length > 0 ? extras : undefined,
610
+ );
611
+ }
612
+ return body as T;
613
+ }
614
+ }