@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,663 @@
1
+ import { createHash } from "node:crypto";
2
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import type { ContextItem } from "./context.ts";
5
+ import {
6
+ VnextConfigError,
7
+ loadVnextProject,
8
+ vnextCanonicalJson,
9
+ type JsonObject,
10
+ type JsonValue,
11
+ type VnextConfigOptions,
12
+ type VnextProjectBundle,
13
+ } from "./vnext-config.ts";
14
+ import { foldVnextRunState, isTerminalRunStatus, VNEXT_RUN_STATE_SCHEMA, vnextFoldPanelAttemptIds, type VnextRunState } from "./vnext-engine-fold.ts";
15
+ import { verifyVnextGateEvidence } from "./vnext-engine-evidence.ts";
16
+ import { loadVnextRunPlanEnvelope } from "./vnext-engine-plan.ts";
17
+ import {
18
+ registerVnextRuntimeHandle,
19
+ resolveVnextGateHold,
20
+ unregisterVnextRuntimeHandle,
21
+ vnextAttemptControllers,
22
+ } from "./vnext-runtime-owner.ts";
23
+ import {
24
+ VNEXT_ABSENT_MEMORY_REVISION,
25
+ VNEXT_RUN_EVENT_SCHEMA,
26
+ VnextRunEventStore,
27
+ VnextRuntimeRegistry,
28
+ newVnextCommandId,
29
+ newVnextEventId,
30
+ newVnextRunId,
31
+ runtimeError,
32
+ vnextRuntimePaths,
33
+ type VnextRunEvent,
34
+ type VnextRunRecord,
35
+ type VnextRunStatus,
36
+ } from "./vnext-runtime-store.ts";
37
+
38
+ /* ------------------------------------------------------------------ *
39
+ * Policy revisions derived deterministically from the bundle
40
+ * ------------------------------------------------------------------ */
41
+
42
+ export interface VnextPolicyRevisions {
43
+ configRevision: string;
44
+ memoryRevision: string;
45
+ executorPolicyRevision: string;
46
+ toolPolicyRevision: string;
47
+ }
48
+
49
+ export interface VnextMemoryRevisionOptions {
50
+ promotedState?: readonly ContextItem[];
51
+ memoryDir?: string;
52
+ skillsDir?: string;
53
+ }
54
+
55
+ function compareCodeUnits(left: string, right: string): number {
56
+ return left < right ? -1 : left > right ? 1 : 0;
57
+ }
58
+
59
+ function sha256Of(input: string): string {
60
+ return `sha256:${createHash("sha256").update(input, "utf8").digest("hex")}`;
61
+ }
62
+
63
+ function objectValue(value: JsonValue | undefined): JsonObject | undefined {
64
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value as JsonObject : undefined;
65
+ }
66
+
67
+ /** Executor policy: the union of executor identities referenced anywhere in the bundle. */
68
+ export function vnextExecutorPolicyRevision(bundle: VnextProjectBundle): string {
69
+ const executors = new Set<string>();
70
+ const defaultExecutor = bundle.project.value.defaultExecutor;
71
+ if (typeof defaultExecutor === "string") executors.add(defaultExecutor);
72
+ for (const agent of bundle.agents.values()) {
73
+ const executor = agent.value.executor;
74
+ if (typeof executor === "string") executors.add(executor);
75
+ }
76
+ return sha256Of(vnextCanonicalJson([...executors].sort() as JsonValue));
77
+ }
78
+
79
+ /** Tool policy: canonical agent/step tool ceilings across the bundle. */
80
+ export function vnextToolPolicyRevision(bundle: VnextProjectBundle): string {
81
+ const policies: JsonObject[] = [];
82
+ for (const agent of [...bundle.agents.values()].sort((left, right) => String(left.id).localeCompare(String(right.id)))) {
83
+ policies.push({ agent: agent.id ?? "unknown", tools: (objectValue(agent.value.tools) ?? null) as JsonValue });
84
+ }
85
+ for (const workflow of [...bundle.workflows.values()].sort((left, right) => String(left.id).localeCompare(String(right.id)))) {
86
+ const steps = Array.isArray(workflow.value.steps) ? workflow.value.steps : [];
87
+ for (const step of steps) {
88
+ const record = objectValue(step);
89
+ if (record?.tools !== undefined) {
90
+ policies.push({ workflow: workflow.id ?? "unknown", step: record.id ?? null, tools: (objectValue(record.tools) ?? null) as JsonValue });
91
+ }
92
+ }
93
+ }
94
+ return sha256Of(vnextCanonicalJson({ policies, gateRegistry: bundle.gateRegistry?.value ?? null }));
95
+ }
96
+
97
+ function collectMemoryFiles(dir: string, baseDir: string, ignoreSubdirs: Set<string> = new Set()): { relPath: string; fullPath: string }[] {
98
+ if (!existsSync(dir)) return [];
99
+ const entries = readdirSync(dir, { withFileTypes: true });
100
+ const results: { relPath: string; fullPath: string }[] = [];
101
+ for (const entry of entries) {
102
+ const fullPath = join(dir, entry.name);
103
+ if (entry.isDirectory()) {
104
+ if (ignoreSubdirs.has(entry.name)) continue;
105
+ results.push(...collectMemoryFiles(fullPath, baseDir, ignoreSubdirs));
106
+ } else if (entry.isFile()) {
107
+ const relPath = fullPath.slice(baseDir.length + 1).replace(/\\/g, "/");
108
+ results.push({ relPath, fullPath });
109
+ }
110
+ }
111
+ return results;
112
+ }
113
+
114
+ /**
115
+ * Compute the memory revision pinned at run creation as the SHA-256 hash of the
116
+ * Git-authored set (.kxm/memory, excluding candidates; .kxm/skills/promoted) plus
117
+ * the promoted-state snapshot.
118
+ */
119
+ export function computeVnextMemoryRevision(
120
+ bundle: VnextProjectBundle,
121
+ options: VnextMemoryRevisionOptions = {},
122
+ ): string {
123
+ const hash = createHash("sha256");
124
+
125
+ // 1. Git-authored memory files (.kxm/memory), excluding candidates
126
+ const memoryDir = options.memoryDir ?? join(bundle.projectRoot, ".kxm", "memory");
127
+ const memoryFiles = collectMemoryFiles(memoryDir, memoryDir, new Set(["candidates"]))
128
+ .sort((left, right) => compareCodeUnits(left.relPath, right.relPath));
129
+ for (const file of memoryFiles) {
130
+ hash.update(`memory:${file.relPath}\0`, "utf8");
131
+ hash.update(readFileSync(file.fullPath));
132
+ hash.update("\0", "utf8");
133
+ }
134
+
135
+ // 2. Promoted skills (.kxm/skills/promoted)
136
+ const skillsDir = options.skillsDir ?? join(bundle.projectRoot, ".kxm", "skills");
137
+ const promotedSkillsDir = join(skillsDir, "promoted");
138
+ const skillFiles = collectMemoryFiles(promotedSkillsDir, promotedSkillsDir)
139
+ .sort((left, right) => compareCodeUnits(left.relPath, right.relPath));
140
+ for (const file of skillFiles) {
141
+ hash.update(`skill:${file.relPath}\0`, "utf8");
142
+ hash.update(readFileSync(file.fullPath));
143
+ hash.update("\0", "utf8");
144
+ }
145
+
146
+ // 3. Promoted-state snapshot
147
+ if (options.promotedState && options.promotedState.length > 0) {
148
+ const currentItems = options.promotedState
149
+ .filter((item) => !item.status || item.status === "current")
150
+ .slice()
151
+ .sort((left, right) => compareCodeUnits(left.id, right.id));
152
+ for (const item of currentItems) {
153
+ hash.update(`state:${item.id}\0`, "utf8");
154
+ hash.update(vnextCanonicalJson(item as unknown as JsonValue), "utf8");
155
+ hash.update("\0", "utf8");
156
+ }
157
+ }
158
+
159
+ return `ctxrev_${hash.digest("hex")}`;
160
+ }
161
+
162
+ export function vnextPolicyRevisions(
163
+ bundle: VnextProjectBundle,
164
+ options?: VnextMemoryRevisionOptions,
165
+ ): VnextPolicyRevisions {
166
+ return {
167
+ configRevision: bundle.configRevision,
168
+ memoryRevision: computeVnextMemoryRevision(bundle, options),
169
+ executorPolicyRevision: vnextExecutorPolicyRevision(bundle),
170
+ toolPolicyRevision: vnextToolPolicyRevision(bundle),
171
+ };
172
+ }
173
+
174
+ export function vnextDeclaredRepositoryIds(bundle: VnextProjectBundle): string[] {
175
+ return [...bundle.repositories.keys()].sort();
176
+ }
177
+
178
+ export function vnextDeclaredExecutorIds(bundle: VnextProjectBundle): string[] {
179
+ return [...new Set([
180
+ typeof bundle.project.value.defaultExecutor === "string" ? bundle.project.value.defaultExecutor : undefined,
181
+ ...[...bundle.agents.values()].map((agent) => agent.value.executor).filter((value): value is string => typeof value === "string"),
182
+ ].filter((value): value is string => value !== undefined))].sort();
183
+ }
184
+
185
+ export function vnextProjectAdmissionLimits(bundle: VnextProjectBundle): {
186
+ maxConcurrentRuns: number;
187
+ maxRunDurationMs?: number;
188
+ maxAgentTimeMs?: number;
189
+ } {
190
+ const limits = objectValue(bundle.project.value.limits);
191
+ const maxConcurrentRuns = typeof limits?.maxConcurrentRuns === "number" && Number.isInteger(limits.maxConcurrentRuns) && limits.maxConcurrentRuns >= 1
192
+ ? limits.maxConcurrentRuns
193
+ : 1;
194
+ return {
195
+ maxConcurrentRuns,
196
+ ...(typeof limits?.maxRunDurationMs === "number" ? { maxRunDurationMs: limits.maxRunDurationMs } : {}),
197
+ ...(typeof limits?.maxAgentTimeMs === "number" ? { maxAgentTimeMs: limits.maxAgentTimeMs } : {}),
198
+ };
199
+ }
200
+
201
+ /* ------------------------------------------------------------------ *
202
+ * Run acceptance and lifecycle
203
+ * ------------------------------------------------------------------ */
204
+
205
+ export interface VnextRunAcceptanceRequest {
206
+ commandId?: string;
207
+ workflowId: string;
208
+ prompt: string;
209
+ }
210
+
211
+ export interface VnextRunAcceptance {
212
+ accepted: boolean;
213
+ idempotent: boolean;
214
+ run: VnextRunRecord;
215
+ event: VnextRunEvent;
216
+ }
217
+
218
+ export interface VnextRuntimeContext {
219
+ registry: VnextRuntimeRegistry;
220
+ projectRoot: string;
221
+ projectId: string;
222
+ homeRuntimeId: string;
223
+ eventStore: VnextRunEventStore;
224
+ }
225
+
226
+ const closedRuntimeContexts = new WeakSet<VnextRuntimeContext>();
227
+ const runtimeCloseHooks = new WeakMap<VnextRuntimeContext, Set<() => void>>();
228
+
229
+ export function isVnextRuntimeContextClosed(context: VnextRuntimeContext): boolean {
230
+ return closedRuntimeContexts.has(context);
231
+ }
232
+
233
+ export function registerVnextRuntimeCloseHook(context: VnextRuntimeContext, hook: () => void): () => void {
234
+ if (closedRuntimeContexts.has(context)) {
235
+ hook();
236
+ return () => undefined;
237
+ }
238
+ let hooks = runtimeCloseHooks.get(context);
239
+ if (!hooks) {
240
+ hooks = new Set();
241
+ runtimeCloseHooks.set(context, hooks);
242
+ }
243
+ hooks.add(hook);
244
+ return () => {
245
+ hooks.delete(hook);
246
+ };
247
+ }
248
+
249
+ /**
250
+ * Open (and register if needed) the runtime context for a project root.
251
+ * homeRuntimeId is the accepting Runtime's identity: the supervisor passes its
252
+ * active runtime id; direct callers must supply one explicitly. An existing
253
+ * registration always wins and is revalidated immutably.
254
+ */
255
+ export function openVnextRuntimeContext(
256
+ projectRoot: string,
257
+ options: VnextConfigOptions & { stateRoot?: string; now?: string; homeRuntimeId: string },
258
+ ): VnextRuntimeContext {
259
+ const paths = vnextRuntimePaths(options.stateRoot !== undefined ? { stateRoot: options.stateRoot } : {});
260
+ const registry = new VnextRuntimeRegistry(paths.registryDb);
261
+ try {
262
+ const bundle = loadVnextProject(projectRoot, options);
263
+ const projectId = String(bundle.project.value.id);
264
+ const registration = registry.registerProject({
265
+ projectId,
266
+ projectRoot: bundle.projectRoot,
267
+ homeRuntimeId: options.homeRuntimeId,
268
+ configRevision: bundle.configRevision,
269
+ now: options.now ?? new Date().toISOString(),
270
+ });
271
+ const eventStore = new VnextRunEventStore(join(paths.projectsDir, registration.projectKey, "run-events.db"));
272
+ registerVnextRuntimeHandle(eventStore.path);
273
+ return {
274
+ registry,
275
+ projectRoot: bundle.projectRoot,
276
+ projectId: registration.projectId,
277
+ homeRuntimeId: registration.homeRuntimeId,
278
+ eventStore,
279
+ };
280
+ } catch (error) {
281
+ registry.close();
282
+ throw error;
283
+ }
284
+ }
285
+
286
+ export function closeVnextRuntimeContext(context: VnextRuntimeContext): void {
287
+ if (closedRuntimeContexts.has(context)) return;
288
+ closedRuntimeContexts.add(context);
289
+ const hooks = runtimeCloseHooks.get(context);
290
+ runtimeCloseHooks.delete(context);
291
+ if (hooks) {
292
+ for (const hook of hooks) {
293
+ try {
294
+ hook();
295
+ } catch {
296
+ // Close must still close stores; observer cleanup is best-effort.
297
+ }
298
+ }
299
+ }
300
+ unregisterVnextRuntimeHandle(context.eventStore.path);
301
+ context.eventStore.close();
302
+ context.registry.close();
303
+ }
304
+
305
+ const RUNTIME_EPOCH_NS = process.hrtime.bigint();
306
+
307
+ /** Intra-process monotonic nanoseconds as a decimal string, ordered only by sequence. */
308
+ export function vnextMonotonicNs(): string {
309
+ return (process.hrtime.bigint() - RUNTIME_EPOCH_NS).toString();
310
+ }
311
+
312
+ export function vnextIncrementMonotonicNs(value: string): string {
313
+ return (BigInt(value) + 1n).toString();
314
+ }
315
+
316
+ export function vnextEventBase(context: VnextRuntimeContext, run: VnextRunRecord, now: string, monotonicNs: string, commandId?: string): Omit<VnextRunEvent, "eventId" | "eventType" | "sequence" | "payload"> {
317
+ return {
318
+ schema: VNEXT_RUN_EVENT_SCHEMA,
319
+ projectId: context.projectId,
320
+ runId: run.runId,
321
+ homeRuntimeId: run.homeRuntimeId,
322
+ occurredAt: now,
323
+ recordedAt: now,
324
+ monotonicNs,
325
+ configRevision: run.configRevision,
326
+ memoryRevision: run.memoryRevision,
327
+ executorPolicyRevision: run.executorPolicyRevision,
328
+ toolPolicyRevision: run.toolPolicyRevision,
329
+ ...(commandId !== undefined ? { commandId } : {}),
330
+ };
331
+ }
332
+
333
+ export interface VnextRunAcceptanceOptions {
334
+ now?: string;
335
+ monotonicNs?: string;
336
+ memoryRevision?: string;
337
+ promotedState?: readonly ContextItem[];
338
+ memoryDir?: string;
339
+ skillsDir?: string;
340
+ }
341
+
342
+ /** Accept a run idempotently: a repeated commandId returns the prior acceptance. */
343
+ export function acceptVnextRun(
344
+ context: VnextRuntimeContext,
345
+ bundle: VnextProjectBundle,
346
+ request: VnextRunAcceptanceRequest,
347
+ options: VnextRunAcceptanceOptions = {},
348
+ ): VnextRunAcceptance {
349
+ const commandId = request.commandId ?? newVnextCommandId();
350
+ const now = options.now ?? new Date().toISOString();
351
+ const monotonicNs = options.monotonicNs ?? vnextMonotonicNs();
352
+
353
+ const workflow = bundle.workflows.get(request.workflowId);
354
+ if (!workflow) {
355
+ throw runtimeError("run_workflow_unknown", ".kxm/workflows", `workflow ${request.workflowId} does not exist in this project`);
356
+ }
357
+ const revisions = vnextPolicyRevisions(bundle, options);
358
+ const memoryRevision = options.memoryRevision ?? revisions.memoryRevision;
359
+ const promptSha256 = `sha256:${createHash("sha256").update(request.prompt, "utf8").digest("hex")}`;
360
+
361
+ return context.eventStore.transaction(() => {
362
+ const prior = context.eventStore.command(commandId);
363
+ if (prior) {
364
+ if (prior.kind !== "run.accept") {
365
+ throw runtimeError("run_command_conflict", commandId, `command ${commandId} was already used for ${prior.kind}`);
366
+ }
367
+ const run = context.eventStore.run(prior.runId);
368
+ if (!run) throw runtimeError("run_command_corrupt", commandId, "idempotent command references a missing run");
369
+ if (run.workflowId !== request.workflowId || run.promptSha256 !== promptSha256) {
370
+ throw runtimeError("run_command_conflict", commandId, "idempotent retry must repeat the same workflow and prompt");
371
+ }
372
+ const events = context.eventStore.events(run.runId, 0, 1);
373
+ if (events.length === 0) throw runtimeError("run_command_corrupt", commandId, "idempotent command references a run with no creation event");
374
+ return { accepted: true, idempotent: true, run, event: events[0]! };
375
+ }
376
+
377
+ const runId = newVnextRunId();
378
+ const payload: Record<string, unknown> = {
379
+ workflowId: request.workflowId,
380
+ status: "created",
381
+ promptHash: promptSha256,
382
+ repositoryIds: vnextDeclaredRepositoryIds(bundle),
383
+ executorIds: vnextDeclaredExecutorIds(bundle),
384
+ };
385
+ const event: VnextRunEvent = {
386
+ schema: VNEXT_RUN_EVENT_SCHEMA,
387
+ eventId: newVnextEventId(),
388
+ eventType: "run.created",
389
+ projectId: context.projectId,
390
+ runId,
391
+ homeRuntimeId: context.homeRuntimeId,
392
+ sequence: 1,
393
+ commandId,
394
+ occurredAt: now,
395
+ recordedAt: now,
396
+ monotonicNs,
397
+ configRevision: revisions.configRevision,
398
+ memoryRevision,
399
+ executorPolicyRevision: revisions.executorPolicyRevision,
400
+ toolPolicyRevision: revisions.toolPolicyRevision,
401
+ payload,
402
+ };
403
+ const run: VnextRunRecord = {
404
+ runId,
405
+ projectId: context.projectId,
406
+ homeRuntimeId: context.homeRuntimeId,
407
+ workflowId: request.workflowId,
408
+ promptSha256,
409
+ status: "created",
410
+ configRevision: revisions.configRevision,
411
+ memoryRevision,
412
+ executorPolicyRevision: revisions.executorPolicyRevision,
413
+ toolPolicyRevision: revisions.toolPolicyRevision,
414
+ createdAt: now,
415
+ updatedAt: now,
416
+ };
417
+ context.eventStore.insertRun(run);
418
+ context.eventStore.appendEvent(event);
419
+ context.eventStore.insertCommand({
420
+ commandId,
421
+ runId,
422
+ kind: "run.accept",
423
+ result: { runId, homeRuntimeId: context.homeRuntimeId, status: "created" },
424
+ recordedAt: now,
425
+ });
426
+ return { accepted: true, idempotent: false, run, event };
427
+ });
428
+ }
429
+
430
+ /* ------------------------------------------------------------------ *
431
+ * Projection
432
+ * ------------------------------------------------------------------ */
433
+
434
+ export function foldStoredVnextRun(context: VnextRuntimeContext, run: VnextRunRecord): VnextRunState {
435
+ const events = context.eventStore.events(run.runId, 0, 1_000_000);
436
+ const planRow = context.eventStore.runPlan(run.runId);
437
+ if (!planRow && events.some((event) => event.eventType.startsWith("step.") || event.eventType.startsWith("effect.") || event.payload.status === "preparing" || event.payload.status === "running" || event.payload.status === "cancelling")) {
438
+ throw runtimeError("run_plan_missing", run.runId, "run reached preparing or later without a pinned plan");
439
+ }
440
+ const envelope = planRow ? loadVnextRunPlanEnvelope(context.eventStore, run) : undefined;
441
+ const state = foldVnextRunState(run, envelope?.plan, events, envelope
442
+ ? {
443
+ observationLookup: (attemptId, observationId) => {
444
+ const row = context.eventStore.gateObservationForAttempt(attemptId);
445
+ if (!row || row.observationId !== observationId) return undefined;
446
+ return { completeness: row.completeness };
447
+ },
448
+ gateKind: (stepId) => {
449
+ const step = envelope.plan.steps[stepId];
450
+ if (!step || step.kind !== "gate") return undefined;
451
+ return envelope.gates.definitions[step.gate]?.kind;
452
+ },
453
+ }
454
+ : {});
455
+ if (envelope) verifyVnextGateEvidence(context.eventStore, run, envelope, events, state);
456
+ assertStoredProjection(context, run);
457
+ return state;
458
+ }
459
+
460
+ function storedProjectionSchema(stateJson: string): string | undefined {
461
+ try {
462
+ const parsed = JSON.parse(stateJson) as { schema?: unknown };
463
+ return typeof parsed.schema === "string" ? parsed.schema : undefined;
464
+ } catch {
465
+ return undefined;
466
+ }
467
+ }
468
+
469
+ function assertStoredProjection(context: VnextRuntimeContext, run: VnextRunRecord): void {
470
+ const stored = context.eventStore.runState(run.runId);
471
+ if (!stored) return;
472
+ const schema = storedProjectionSchema(stored.state);
473
+ if (schema !== VNEXT_RUN_STATE_SCHEMA) {
474
+ throw runtimeError("run_projection_divergent", run.runId, "stored run_state does not match the folded projection");
475
+ }
476
+ }
477
+
478
+ /** Fold a stored run into its current status with evidence verification. */
479
+ export function readVnextRunStatus(context: VnextRuntimeContext, run: VnextRunRecord): VnextRunStatus {
480
+ return foldStoredVnextRun(context, run).status;
481
+ }
482
+
483
+ function persistProjection(context: VnextRuntimeContext, run: VnextRunRecord, state: VnextRunState, now: string): VnextRunRecord {
484
+ const canonical = vnextCanonicalJson(state as unknown as JsonValue);
485
+ const stored = context.eventStore.runState(run.runId);
486
+ if (stored && stored.state !== canonical) {
487
+ throw runtimeError("run_projection_divergent", run.runId, "stored run_state does not match the folded projection");
488
+ }
489
+ if (!stored) {
490
+ context.eventStore.upsertRunState({
491
+ runId: run.runId,
492
+ lastSequence: context.eventStore.nextSequence(run.runId) - 1,
493
+ state: canonical,
494
+ });
495
+ }
496
+ const updated = { ...run, status: state.status, updatedAt: now };
497
+ if (run.status !== state.status) context.eventStore.updateRunStatus(run.runId, state.status, now);
498
+ return updated;
499
+ }
500
+
501
+ /** Rebuild the stored run row from the event log; returns the projected record. */
502
+ export function rebuildVnextRunProjection(context: VnextRuntimeContext, runId: string): VnextRunRecord {
503
+ const stored = context.eventStore.run(runId);
504
+ if (!stored) throw runtimeError("run_unknown", runId, "run does not exist in this event store");
505
+ const state = foldStoredVnextRun(context, stored);
506
+ const events = context.eventStore.events(runId, 0, 1_000_000);
507
+ if (events.length === 0) throw runtimeError("run_events_corrupt", runId, "run has no events");
508
+ const last = events[events.length - 1]!;
509
+ return persistProjection(context, stored, state, last.occurredAt);
510
+ }
511
+
512
+ export function persistVnextRunState(context: VnextRuntimeContext, runId: string, state: VnextRunState, lastSequence: number): void {
513
+ const stored = context.eventStore.runState(runId);
514
+ if (stored) {
515
+ const schema = storedProjectionSchema(stored.state);
516
+ if (schema !== VNEXT_RUN_STATE_SCHEMA) {
517
+ throw runtimeError("run_projection_divergent", runId, "stored run_state does not match the folded projection");
518
+ }
519
+ }
520
+ context.eventStore.upsertRunState({
521
+ runId,
522
+ lastSequence,
523
+ state: vnextCanonicalJson(state as unknown as JsonValue),
524
+ });
525
+ }
526
+
527
+ /* ------------------------------------------------------------------ *
528
+ * Cancel
529
+ * ------------------------------------------------------------------ */
530
+
531
+ export interface VnextRunCancelResult {
532
+ run: VnextRunRecord;
533
+ idempotent: boolean;
534
+ events: VnextRunEvent[];
535
+ }
536
+
537
+ export function cancelVnextRun(
538
+ context: VnextRuntimeContext,
539
+ runId: string,
540
+ options: { commandId?: string; now?: string; monotonicNs?: string } = {},
541
+ ): VnextRunCancelResult {
542
+ const commandId = options.commandId ?? newVnextCommandId();
543
+ const now = options.now ?? new Date().toISOString();
544
+ const monotonicNs = options.monotonicNs ?? vnextMonotonicNs();
545
+ const abortControllers: AbortController[] = [];
546
+
547
+ const result = context.eventStore.transaction(() => {
548
+ const prior = context.eventStore.command(commandId);
549
+ if (prior) {
550
+ if (prior.kind !== "run.cancel" || prior.runId !== runId) {
551
+ throw runtimeError("run_command_conflict", commandId, `command ${commandId} was already used for ${prior.kind}${prior.runId === runId ? "" : " on a different run"}`);
552
+ }
553
+ const run = context.eventStore.run(prior.runId);
554
+ if (!run) throw runtimeError("run_command_corrupt", commandId, "idempotent command references a missing run");
555
+ return { run, idempotent: true, events: [] as VnextRunEvent[] };
556
+ }
557
+ const run = context.eventStore.run(runId);
558
+ if (!run) throw runtimeError("run_unknown", runId, "run does not exist in this event store");
559
+ const folded = foldStoredVnextRun(context, run);
560
+ if (isTerminalRunStatus(folded.status) || folded.status === "cancelling") {
561
+ context.eventStore.insertCommand({
562
+ commandId,
563
+ runId,
564
+ kind: "run.cancel",
565
+ result: { runId, status: folded.status },
566
+ recordedAt: now,
567
+ });
568
+ return { run: { ...run, status: folded.status }, idempotent: true, events: [] as VnextRunEvent[] };
569
+ }
570
+
571
+ const events: VnextRunEvent[] = [];
572
+ let sequence = context.eventStore.nextSequence(runId);
573
+ let nextMono = monotonicNs;
574
+ const push = (eventType: string, payload: Record<string, unknown>): VnextRunEvent => {
575
+ const event: VnextRunEvent = {
576
+ ...vnextEventBase(context, run, now, nextMono, commandId),
577
+ eventId: newVnextEventId(),
578
+ eventType,
579
+ sequence: sequence++,
580
+ payload,
581
+ };
582
+ nextMono = vnextIncrementMonotonicNs(nextMono);
583
+ events.push(event);
584
+ return event;
585
+ };
586
+
587
+ const activeAttempt = Boolean(folded.currentStep?.attemptId) || vnextFoldPanelAttemptIds(folded.currentStep).length > 0;
588
+ push("run.cancel_requested", {
589
+ actor: { kind: "runtime", id: context.homeRuntimeId },
590
+ reason: "operator_cancel",
591
+ });
592
+
593
+ let status: VnextRunStatus = "cancelled";
594
+ if (folded.status === "running" && activeAttempt) {
595
+ status = "cancelling";
596
+ push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
597
+ const attemptIds = new Set(vnextFoldPanelAttemptIds(folded.currentStep));
598
+ if (folded.currentStep?.attemptId) attemptIds.add(folded.currentStep.attemptId);
599
+ for (const attemptId of attemptIds) {
600
+ const capability = context.eventStore.capabilityByAttempt(attemptId);
601
+ if (capability && capability.state === "issued") {
602
+ context.eventStore.settleCapability(attemptId, "revoked");
603
+ }
604
+ }
605
+ for (const owned of vnextAttemptControllers(context.eventStore.path, runId)) {
606
+ abortControllers.push(owned.controller);
607
+ }
608
+ } else if (folded.status === "blocked_uncertain") {
609
+ push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
610
+ const attemptId = folded.currentStep?.attemptId;
611
+ if (attemptId) {
612
+ const capability = context.eventStore.capabilityByAttempt(attemptId);
613
+ if (capability && (capability.state === "issued" || capability.state === "revoked")) {
614
+ context.eventStore.settleCapability(attemptId, "revoked");
615
+ }
616
+ resolveVnextGateHold(context.eventStore.path, runId, attemptId);
617
+ }
618
+ for (const owned of vnextAttemptControllers(context.eventStore.path, runId)) {
619
+ abortControllers.push(owned.controller);
620
+ }
621
+ push("run.status_changed", { status: "cancelled", reason: "operator_cancel" });
622
+ status = "cancelled";
623
+ } else if (folded.status === "running" && !activeAttempt) {
624
+ push("run.status_changed", { status: "cancelling", reason: "operator_cancel" });
625
+ push("run.status_changed", { status: "cancelled", reason: "operator_cancel" });
626
+ } else {
627
+ push("run.status_changed", { status: "cancelled", reason: "operator_cancel" });
628
+ }
629
+
630
+ for (const event of events) context.eventStore.appendEvent(event);
631
+ const nextState = foldStoredVnextRun(context, run);
632
+ persistVnextRunState(context, runId, nextState, events[events.length - 1]!.sequence);
633
+ context.eventStore.updateRunStatus(runId, status, now);
634
+ context.eventStore.insertCommand({
635
+ commandId,
636
+ runId,
637
+ kind: "run.cancel",
638
+ result: { runId, status },
639
+ recordedAt: now,
640
+ });
641
+ return { run: { ...run, status, updatedAt: now }, idempotent: false, events };
642
+ });
643
+
644
+ for (const controller of abortControllers) controller.abort();
645
+ return result;
646
+ }
647
+
648
+ /* ------------------------------------------------------------------ *
649
+ * Revision drift check
650
+ * ------------------------------------------------------------------ */
651
+
652
+ /** Runs pin revisions at acceptance; drift is reported, never silently adopted. */
653
+ export function vnextRunRevisionDrift(run: VnextRunRecord, bundle: VnextProjectBundle): { configDrift: boolean; currentRevision: string; pinnedRevision: string } {
654
+ return {
655
+ configDrift: run.configRevision !== bundle.configRevision,
656
+ currentRevision: bundle.configRevision,
657
+ pinnedRevision: run.configRevision,
658
+ };
659
+ }
660
+
661
+ export function assertVnextConfigError(error: unknown): asserts error is VnextConfigError {
662
+ if (!(error instanceof VnextConfigError)) throw error;
663
+ }