oxe-cc 1.2.1 → 1.4.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 (281) hide show
  1. package/.cursor/commands/oxe-ask.md +2 -2
  2. package/.cursor/commands/oxe-capabilities.md +2 -2
  3. package/.cursor/commands/oxe-checkpoint.md +2 -2
  4. package/.cursor/commands/oxe-compact.md +2 -2
  5. package/.cursor/commands/oxe-dashboard.md +2 -2
  6. package/.cursor/commands/oxe-debug.md +2 -2
  7. package/.cursor/commands/oxe-discuss.md +2 -2
  8. package/.cursor/commands/oxe-execute.md +5 -2
  9. package/.cursor/commands/oxe-forensics.md +2 -2
  10. package/.cursor/commands/oxe-help.md +2 -2
  11. package/.cursor/commands/oxe-loop.md +2 -2
  12. package/.cursor/commands/oxe-milestone.md +2 -2
  13. package/.cursor/commands/oxe-next.md +2 -2
  14. package/.cursor/commands/oxe-obs.md +2 -2
  15. package/.cursor/commands/oxe-plan-agent.md +2 -2
  16. package/.cursor/commands/oxe-plan.md +2 -2
  17. package/.cursor/commands/oxe-project.md +2 -2
  18. package/.cursor/commands/oxe-quick.md +2 -2
  19. package/.cursor/commands/oxe-research.md +2 -2
  20. package/.cursor/commands/oxe-retro.md +2 -2
  21. package/.cursor/commands/oxe-review-pr.md +2 -2
  22. package/.cursor/commands/oxe-route.md +2 -2
  23. package/.cursor/commands/oxe-scan.md +2 -2
  24. package/.cursor/commands/oxe-security.md +2 -2
  25. package/.cursor/commands/oxe-session.md +2 -2
  26. package/.cursor/commands/oxe-ship.md +2 -2
  27. package/.cursor/commands/oxe-skill.md +2 -2
  28. package/.cursor/commands/oxe-spec.md +2 -2
  29. package/.cursor/commands/oxe-ui-review.md +2 -2
  30. package/.cursor/commands/oxe-ui-spec.md +2 -2
  31. package/.cursor/commands/oxe-update.md +2 -2
  32. package/.cursor/commands/oxe-validate-gaps.md +2 -2
  33. package/.cursor/commands/oxe-verify.md +5 -2
  34. package/.cursor/commands/oxe-workstream.md +2 -2
  35. package/.cursor/commands/oxe.md +2 -2
  36. package/.github/copilot-instructions.md +13 -13
  37. package/.github/prompts/oxe-ask.prompt.md +2 -2
  38. package/.github/prompts/oxe-capabilities.prompt.md +2 -2
  39. package/.github/prompts/oxe-checkpoint.prompt.md +2 -2
  40. package/.github/prompts/oxe-compact.prompt.md +2 -2
  41. package/.github/prompts/oxe-dashboard.prompt.md +2 -2
  42. package/.github/prompts/oxe-debug.prompt.md +2 -2
  43. package/.github/prompts/oxe-discuss.prompt.md +2 -2
  44. package/.github/prompts/oxe-execute.prompt.md +5 -2
  45. package/.github/prompts/oxe-forensics.prompt.md +2 -2
  46. package/.github/prompts/oxe-help.prompt.md +2 -2
  47. package/.github/prompts/oxe-loop.prompt.md +2 -2
  48. package/.github/prompts/oxe-milestone.prompt.md +2 -2
  49. package/.github/prompts/oxe-next.prompt.md +2 -2
  50. package/.github/prompts/oxe-obs.prompt.md +2 -2
  51. package/.github/prompts/oxe-plan-agent.prompt.md +2 -2
  52. package/.github/prompts/oxe-plan.prompt.md +2 -2
  53. package/.github/prompts/oxe-project.prompt.md +2 -2
  54. package/.github/prompts/oxe-quick.prompt.md +2 -2
  55. package/.github/prompts/oxe-research.prompt.md +2 -2
  56. package/.github/prompts/oxe-retro.prompt.md +2 -2
  57. package/.github/prompts/oxe-review-pr.prompt.md +2 -2
  58. package/.github/prompts/oxe-route.prompt.md +2 -2
  59. package/.github/prompts/oxe-scan.prompt.md +2 -2
  60. package/.github/prompts/oxe-security.prompt.md +2 -2
  61. package/.github/prompts/oxe-session.prompt.md +2 -2
  62. package/.github/prompts/oxe-ship.prompt.md +2 -2
  63. package/.github/prompts/oxe-skill.prompt.md +2 -2
  64. package/.github/prompts/oxe-spec.prompt.md +2 -2
  65. package/.github/prompts/oxe-ui-review.prompt.md +2 -2
  66. package/.github/prompts/oxe-ui-spec.prompt.md +2 -2
  67. package/.github/prompts/oxe-update.prompt.md +2 -2
  68. package/.github/prompts/oxe-validate-gaps.prompt.md +2 -2
  69. package/.github/prompts/oxe-verify.prompt.md +5 -2
  70. package/.github/prompts/oxe-workstream.prompt.md +2 -2
  71. package/.github/prompts/oxe.prompt.md +2 -2
  72. package/AGENTS.md +5 -3
  73. package/CHANGELOG.md +72 -10
  74. package/LICENSE +21 -674
  75. package/README.md +631 -535
  76. package/bin/banner.txt +6 -6
  77. package/bin/lib/oxe-agent-install.cjs +69 -69
  78. package/bin/lib/oxe-azure.cjs +1445 -1445
  79. package/bin/lib/oxe-context-engine.cjs +867 -867
  80. package/bin/lib/oxe-dashboard.cjs +76 -28
  81. package/bin/lib/oxe-operational.cjs +2144 -1340
  82. package/bin/lib/oxe-project-health.cjs +483 -1
  83. package/bin/lib/oxe-runtime-semantics.cjs +12 -0
  84. package/bin/oxe-cc.js +554 -152
  85. package/commands/oxe/ask.md +2 -2
  86. package/commands/oxe/capabilities.md +2 -2
  87. package/commands/oxe/checkpoint.md +2 -2
  88. package/commands/oxe/compact.md +2 -2
  89. package/commands/oxe/dashboard.md +2 -2
  90. package/commands/oxe/debug.md +2 -2
  91. package/commands/oxe/discuss.md +2 -2
  92. package/commands/oxe/execute.md +5 -2
  93. package/commands/oxe/forensics.md +2 -2
  94. package/commands/oxe/help.md +2 -2
  95. package/commands/oxe/loop.md +2 -2
  96. package/commands/oxe/milestone.md +2 -2
  97. package/commands/oxe/next.md +2 -2
  98. package/commands/oxe/obs.md +2 -2
  99. package/commands/oxe/oxe.md +2 -2
  100. package/commands/oxe/plan-agent.md +2 -2
  101. package/commands/oxe/plan.md +2 -2
  102. package/commands/oxe/project.md +2 -2
  103. package/commands/oxe/quick.md +2 -2
  104. package/commands/oxe/research.md +2 -2
  105. package/commands/oxe/retro.md +2 -2
  106. package/commands/oxe/review-pr.md +2 -2
  107. package/commands/oxe/route.md +2 -2
  108. package/commands/oxe/scan.md +2 -2
  109. package/commands/oxe/security.md +2 -2
  110. package/commands/oxe/session.md +2 -2
  111. package/commands/oxe/ship.md +2 -2
  112. package/commands/oxe/skill.md +2 -2
  113. package/commands/oxe/spec.md +2 -2
  114. package/commands/oxe/ui-review.md +2 -2
  115. package/commands/oxe/ui-spec.md +2 -2
  116. package/commands/oxe/update.md +2 -2
  117. package/commands/oxe/validate-gaps.md +2 -2
  118. package/commands/oxe/verify.md +5 -2
  119. package/commands/oxe/workstream.md +2 -2
  120. package/lib/runtime/delivery/branch-manager.d.ts +1 -0
  121. package/lib/runtime/delivery/branch-manager.js +7 -0
  122. package/lib/runtime/delivery/ci-checks.js +34 -1
  123. package/lib/runtime/delivery/delivery-records.d.ts +34 -0
  124. package/lib/runtime/delivery/delivery-records.js +48 -0
  125. package/lib/runtime/delivery/index.d.ts +1 -0
  126. package/lib/runtime/delivery/index.js +1 -0
  127. package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
  128. package/lib/runtime/delivery/promotion-pipeline.js +111 -14
  129. package/lib/runtime/gate/gate-manager.d.ts +41 -0
  130. package/lib/runtime/gate/gate-manager.js +108 -1
  131. package/lib/runtime/index.d.ts +2 -2
  132. package/lib/runtime/index.js +3 -1
  133. package/lib/runtime/models/gate-decision.d.ts +4 -1
  134. package/lib/runtime/models/workspace.d.ts +3 -0
  135. package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
  136. package/lib/runtime/plugins/capability-adapter.js +204 -0
  137. package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
  138. package/lib/runtime/plugins/capability-matrix.js +48 -17
  139. package/lib/runtime/plugins/index.d.ts +1 -0
  140. package/lib/runtime/plugins/index.js +1 -0
  141. package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
  142. package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
  143. package/lib/runtime/plugins/plugin-manifest.js +6 -2
  144. package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
  145. package/lib/runtime/plugins/plugin-registry.js +79 -2
  146. package/lib/runtime/policy/policy-engine.d.ts +19 -0
  147. package/lib/runtime/policy/policy-engine.js +76 -4
  148. package/lib/runtime/projection/projection-engine.d.ts +9 -1
  149. package/lib/runtime/projection/projection-engine.js +73 -3
  150. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
  151. package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
  152. package/lib/runtime/scheduler/run-journal.d.ts +1 -1
  153. package/lib/runtime/scheduler/scheduler.d.ts +19 -1
  154. package/lib/runtime/scheduler/scheduler.js +258 -13
  155. package/lib/runtime/verification/verification-compiler.d.ts +43 -0
  156. package/lib/runtime/verification/verification-compiler.js +137 -0
  157. package/lib/runtime/verification/verification-manifest.d.ts +9 -0
  158. package/lib/runtime/verification/verification-manifest.js +56 -6
  159. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
  160. package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
  161. package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
  162. package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
  163. package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
  164. package/lib/runtime/workspace/strategies/inplace.js +2 -0
  165. package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
  166. package/lib/sdk/README.md +20 -8
  167. package/lib/sdk/index.cjs +33 -24
  168. package/lib/sdk/index.d.ts +149 -14
  169. package/oxe/templates/ACTIVE-RUN.template.json +32 -32
  170. package/oxe/templates/CAPABILITIES.template.md +7 -7
  171. package/oxe/templates/CAPABILITY.template.md +45 -45
  172. package/oxe/templates/CHECKPOINTS.template.md +7 -7
  173. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
  174. package/oxe/templates/HYPOTHESES.template.md +33 -33
  175. package/oxe/templates/LESSONS-METRICS.template.json +13 -13
  176. package/oxe/templates/NOTES.template.md +16 -16
  177. package/oxe/templates/PLAN-REVIEW.template.md +31 -31
  178. package/oxe/templates/SESSION.template.md +34 -34
  179. package/oxe/templates/SKILL.template.md +26 -26
  180. package/oxe/templates/STATE.md +55 -55
  181. package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
  182. package/oxe/workflows/ask.md +96 -96
  183. package/oxe/workflows/capabilities.md +25 -25
  184. package/oxe/workflows/dashboard.md +33 -33
  185. package/oxe/workflows/discuss.md +12 -12
  186. package/oxe/workflows/execute.md +14 -0
  187. package/oxe/workflows/help.md +352 -352
  188. package/oxe/workflows/next.md +22 -22
  189. package/oxe/workflows/oxe.md +6 -6
  190. package/oxe/workflows/plan-agent.md +9 -9
  191. package/oxe/workflows/plan.md +51 -20
  192. package/oxe/workflows/quick.md +10 -10
  193. package/oxe/workflows/references/reasoning-discovery.md +28 -28
  194. package/oxe/workflows/references/reasoning-execution.md +29 -29
  195. package/oxe/workflows/references/reasoning-planning.md +32 -32
  196. package/oxe/workflows/references/reasoning-review.md +29 -29
  197. package/oxe/workflows/references/reasoning-status.md +24 -24
  198. package/oxe/workflows/references/robustness-elevation.md +295 -295
  199. package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
  200. package/oxe/workflows/route.md +16 -16
  201. package/oxe/workflows/session.md +213 -213
  202. package/oxe/workflows/ship.md +142 -142
  203. package/oxe/workflows/skill.md +44 -44
  204. package/oxe/workflows/ui-review.md +36 -36
  205. package/oxe/workflows/verify-audit.md +73 -73
  206. package/oxe/workflows/verify.md +10 -0
  207. package/package.json +92 -92
  208. package/packages/runtime/package.json +16 -15
  209. package/packages/runtime/src/audit/audit-trail.ts +243 -243
  210. package/packages/runtime/src/audit/index.ts +2 -2
  211. package/packages/runtime/src/audit/policy-pack.ts +62 -62
  212. package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
  213. package/packages/runtime/src/compiler/index.ts +1 -1
  214. package/packages/runtime/src/context/context-pack-builder.ts +259 -259
  215. package/packages/runtime/src/context/context-pack-store.ts +197 -197
  216. package/packages/runtime/src/context/context-profiles.ts +60 -60
  217. package/packages/runtime/src/context/index.ts +3 -3
  218. package/packages/runtime/src/decision/decision-engine.ts +174 -174
  219. package/packages/runtime/src/decision/decision-memo.ts +211 -211
  220. package/packages/runtime/src/decision/index.ts +2 -2
  221. package/packages/runtime/src/delivery/branch-manager.ts +91 -84
  222. package/packages/runtime/src/delivery/ci-checks.ts +285 -252
  223. package/packages/runtime/src/delivery/delivery-records.ts +75 -0
  224. package/packages/runtime/src/delivery/index.ts +5 -4
  225. package/packages/runtime/src/delivery/pr-manager.ts +112 -112
  226. package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
  227. package/packages/runtime/src/events/bus.ts +92 -92
  228. package/packages/runtime/src/events/catalog.ts +29 -29
  229. package/packages/runtime/src/events/envelope.ts +14 -14
  230. package/packages/runtime/src/events/index.ts +3 -3
  231. package/packages/runtime/src/evidence/evidence-store.ts +130 -130
  232. package/packages/runtime/src/evidence/index.ts +1 -1
  233. package/packages/runtime/src/gate/gate-manager.ts +289 -137
  234. package/packages/runtime/src/gate/index.ts +1 -1
  235. package/packages/runtime/src/index.ts +41 -37
  236. package/packages/runtime/src/models/attempt.ts +19 -19
  237. package/packages/runtime/src/models/evidence.ts +21 -21
  238. package/packages/runtime/src/models/gate-decision.ts +25 -21
  239. package/packages/runtime/src/models/index.ts +8 -8
  240. package/packages/runtime/src/models/run.ts +24 -24
  241. package/packages/runtime/src/models/session.ts +11 -11
  242. package/packages/runtime/src/models/verification-result.ts +10 -10
  243. package/packages/runtime/src/models/work-item.ts +25 -25
  244. package/packages/runtime/src/models/workspace.ts +31 -28
  245. package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
  246. package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
  247. package/packages/runtime/src/plugins/index.ts +5 -4
  248. package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
  249. package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
  250. package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
  251. package/packages/runtime/src/policy/index.ts +1 -1
  252. package/packages/runtime/src/policy/policy-engine.ts +330 -244
  253. package/packages/runtime/src/projection/index.ts +1 -1
  254. package/packages/runtime/src/projection/projection-engine.ts +328 -249
  255. package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
  256. package/packages/runtime/src/reducers/index.ts +2 -2
  257. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
  258. package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
  259. package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
  260. package/packages/runtime/src/scheduler/index.ts +4 -4
  261. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
  262. package/packages/runtime/src/scheduler/run-journal.ts +62 -62
  263. package/packages/runtime/src/scheduler/scheduler.ts +722 -441
  264. package/packages/runtime/src/verification/index.ts +2 -2
  265. package/packages/runtime/src/verification/verification-compiler.ts +436 -225
  266. package/packages/runtime/src/verification/verification-manifest.ts +252 -192
  267. package/packages/runtime/src/workspace/index.ts +5 -5
  268. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
  269. package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
  270. package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
  271. package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
  272. package/packages/runtime/tsconfig.json +17 -17
  273. package/vscode-extension/.vscodeignore +7 -7
  274. package/vscode-extension/LICENSE +21 -0
  275. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  276. package/vscode-extension/oxe-agents-1.4.0.vsix +0 -0
  277. package/vscode-extension/package.json +184 -184
  278. package/vscode-extension/src/extension.js +310 -310
  279. package/vscode-extension/src/shared/contextLoader.js +137 -137
  280. package/vscode-extension/src/shared/contractBuilder.js +159 -159
  281. package/vscode-extension/src/shared/stateReader.js +101 -101
@@ -1,121 +1,126 @@
1
- import { execFileSync, spawnSync } from 'child_process';
2
- import crypto from 'crypto';
3
- import type { WorkspaceManager, WorkspaceRequest } from '../workspace-manager';
4
- import type { WorkspaceLease, SnapshotRef } from '../../models/workspace';
5
- import { GitWorktreeManager } from './git-worktree';
6
-
7
- export interface ContainerOptions {
8
- image: string;
9
- mountPath: string;
10
- extraEnv?: Record<string, string>;
11
- /** Gracefully fall back to git_worktree if Docker is unavailable */
12
- fallback?: boolean;
13
- }
14
-
15
- function isDockerAvailable(): boolean {
16
- const result = spawnSync('docker', ['version', '--format', '{{.Server.Version}}'], {
17
- encoding: 'utf8',
18
- timeout: 5000,
19
- });
20
- return result.status === 0;
21
- }
22
-
23
- export class EphemeralContainerManager implements WorkspaceManager {
24
- private readonly fallbackManager: GitWorktreeManager;
25
- private containerIds = new Map<string, string>();
26
- private useFallback = false;
27
-
28
- constructor(
29
- private readonly projectRoot: string,
30
- private readonly opts: ContainerOptions = { image: 'node:20-alpine', mountPath: '/workspace', fallback: true }
31
- ) {
32
- this.fallbackManager = new GitWorktreeManager(projectRoot);
33
- if (!isDockerAvailable()) {
34
- if (opts.fallback !== false) {
35
- this.useFallback = true;
36
- } else {
37
- throw new Error('Docker is not available and fallback is disabled');
38
- }
39
- }
40
- }
41
-
42
- get usingFallback(): boolean { return this.useFallback; }
43
-
44
- async allocate(req: WorkspaceRequest): Promise<WorkspaceLease> {
45
- if (this.useFallback) return this.fallbackManager.allocate(req);
46
-
47
- const wsId = `ws-container-${req.work_item_id}-a${req.attempt_number}`;
48
- const envArgs = Object.entries(this.opts.extraEnv ?? {}).flatMap(([k, v]) => ['-e', `${k}=${v}`]);
49
-
50
- const result = spawnSync('docker', [
51
- 'run', '-d',
52
- '-v', `${this.projectRoot}:${this.opts.mountPath}`,
53
- '-w', this.opts.mountPath,
54
- ...envArgs,
55
- this.opts.image,
56
- 'sleep', '3600',
57
- ], { encoding: 'utf8' });
58
-
59
- if (result.status !== 0) {
60
- if (this.opts.fallback !== false) {
61
- this.useFallback = true;
62
- return this.fallbackManager.allocate(req);
63
- }
64
- throw new Error(`docker run failed: ${result.stderr}`);
65
- }
66
-
67
- const containerId = result.stdout.trim().slice(0, 12);
68
- this.containerIds.set(wsId, containerId);
69
-
70
- return {
71
- workspace_id: wsId,
72
- strategy: 'ephemeral_container',
73
- branch: null,
74
- base_commit: null,
75
- root_path: `docker:${containerId}:${this.opts.mountPath}`,
76
- ttl_minutes: 60,
77
- };
78
- }
79
-
80
- async snapshot(id: string): Promise<SnapshotRef> {
81
- if (this.useFallback) return this.fallbackManager.snapshot(id);
82
- const containerId = this.containerIds.get(id);
83
- if (!containerId) throw new Error(`Container for workspace ${id} not found`);
84
-
85
- const tag = `oxe-snap-${crypto.randomBytes(4).toString('hex')}`;
86
- execFileSync('docker', ['commit', containerId, tag]);
87
-
88
- return {
89
- snapshot_id: tag,
90
- workspace_id: id,
91
- commit: tag,
92
- created_at: new Date().toISOString(),
93
- };
94
- }
95
-
96
- async reset(id: string, snapRef: SnapshotRef): Promise<void> {
97
- if (this.useFallback) return this.fallbackManager.reset(id, snapRef);
98
- const containerId = this.containerIds.get(id);
99
- if (!containerId) return;
100
- // Stop current container and start from snapshot
101
- spawnSync('docker', ['stop', containerId]);
102
- spawnSync('docker', ['rm', containerId]);
103
- const result = spawnSync('docker', [
104
- 'run', '-d',
105
- '-v', `${this.projectRoot}:${this.opts.mountPath}`,
106
- snapRef.commit,
107
- 'sleep', '3600',
108
- ], { encoding: 'utf8' });
109
- const newId = result.stdout.trim().slice(0, 12);
110
- this.containerIds.set(id, newId);
111
- }
112
-
113
- async dispose(id: string): Promise<void> {
114
- if (this.useFallback) return this.fallbackManager.dispose(id);
115
- const containerId = this.containerIds.get(id);
116
- if (!containerId) return;
117
- spawnSync('docker', ['stop', containerId], { encoding: 'utf8' });
118
- spawnSync('docker', ['rm', containerId], { encoding: 'utf8' });
119
- this.containerIds.delete(id);
120
- }
121
- }
1
+ import { execFileSync, spawnSync } from 'child_process';
2
+ import crypto from 'crypto';
3
+ import type { WorkspaceManager, WorkspaceRequest } from '../workspace-manager';
4
+ import type { WorkspaceLease, SnapshotRef } from '../../models/workspace';
5
+ import { GitWorktreeManager } from './git-worktree';
6
+
7
+ export interface ContainerOptions {
8
+ image: string;
9
+ mountPath: string;
10
+ extraEnv?: Record<string, string>;
11
+ /** Gracefully fall back to git_worktree if Docker is unavailable */
12
+ fallback?: boolean;
13
+ }
14
+
15
+ function isDockerAvailable(): boolean {
16
+ const result = spawnSync('docker', ['version', '--format', '{{.Server.Version}}'], {
17
+ encoding: 'utf8',
18
+ timeout: 5000,
19
+ });
20
+ return result.status === 0;
21
+ }
22
+
23
+ export class EphemeralContainerManager implements WorkspaceManager {
24
+ private readonly fallbackManager: GitWorktreeManager;
25
+ private containerIds = new Map<string, string>();
26
+ private useFallback = false;
27
+
28
+ constructor(
29
+ private readonly projectRoot: string,
30
+ private readonly opts: ContainerOptions = { image: 'node:20-alpine', mountPath: '/workspace', fallback: true }
31
+ ) {
32
+ this.fallbackManager = new GitWorktreeManager(projectRoot);
33
+ if (!isDockerAvailable()) {
34
+ if (opts.fallback !== false) {
35
+ this.useFallback = true;
36
+ } else {
37
+ throw new Error('Docker is not available and fallback is disabled');
38
+ }
39
+ }
40
+ }
41
+
42
+ get isolation_level(): 'shared' | 'isolated' {
43
+ return this.useFallback ? this.fallbackManager.isolation_level : 'isolated';
44
+ }
45
+
46
+ get usingFallback(): boolean { return this.useFallback; }
47
+
48
+ async allocate(req: WorkspaceRequest): Promise<WorkspaceLease> {
49
+ if (this.useFallback) return this.fallbackManager.allocate(req);
50
+
51
+ const wsId = `ws-container-${req.work_item_id}-a${req.attempt_number}`;
52
+ const envArgs = Object.entries(this.opts.extraEnv ?? {}).flatMap(([k, v]) => ['-e', `${k}=${v}`]);
53
+
54
+ const result = spawnSync('docker', [
55
+ 'run', '-d',
56
+ '-v', `${this.projectRoot}:${this.opts.mountPath}`,
57
+ '-w', this.opts.mountPath,
58
+ ...envArgs,
59
+ this.opts.image,
60
+ 'sleep', '3600',
61
+ ], { encoding: 'utf8' });
62
+
63
+ if (result.status !== 0) {
64
+ if (this.opts.fallback !== false) {
65
+ this.useFallback = true;
66
+ return this.fallbackManager.allocate(req);
67
+ }
68
+ throw new Error(`docker run failed: ${result.stderr}`);
69
+ }
70
+
71
+ const containerId = result.stdout.trim().slice(0, 12);
72
+ this.containerIds.set(wsId, containerId);
73
+
74
+ return {
75
+ workspace_id: wsId,
76
+ strategy: 'ephemeral_container',
77
+ isolation_level: this.isolation_level,
78
+ branch: null,
79
+ base_commit: null,
80
+ root_path: `docker:${containerId}:${this.opts.mountPath}`,
81
+ ttl_minutes: 60,
82
+ };
83
+ }
84
+
85
+ async snapshot(id: string): Promise<SnapshotRef> {
86
+ if (this.useFallback) return this.fallbackManager.snapshot(id);
87
+ const containerId = this.containerIds.get(id);
88
+ if (!containerId) throw new Error(`Container for workspace ${id} not found`);
89
+
90
+ const tag = `oxe-snap-${crypto.randomBytes(4).toString('hex')}`;
91
+ execFileSync('docker', ['commit', containerId, tag]);
92
+
93
+ return {
94
+ snapshot_id: tag,
95
+ workspace_id: id,
96
+ commit: tag,
97
+ created_at: new Date().toISOString(),
98
+ };
99
+ }
100
+
101
+ async reset(id: string, snapRef: SnapshotRef): Promise<void> {
102
+ if (this.useFallback) return this.fallbackManager.reset(id, snapRef);
103
+ const containerId = this.containerIds.get(id);
104
+ if (!containerId) return;
105
+ // Stop current container and start from snapshot
106
+ spawnSync('docker', ['stop', containerId]);
107
+ spawnSync('docker', ['rm', containerId]);
108
+ const result = spawnSync('docker', [
109
+ 'run', '-d',
110
+ '-v', `${this.projectRoot}:${this.opts.mountPath}`,
111
+ snapRef.commit,
112
+ 'sleep', '3600',
113
+ ], { encoding: 'utf8' });
114
+ const newId = result.stdout.trim().slice(0, 12);
115
+ this.containerIds.set(id, newId);
116
+ }
117
+
118
+ async dispose(id: string): Promise<void> {
119
+ if (this.useFallback) return this.fallbackManager.dispose(id);
120
+ const containerId = this.containerIds.get(id);
121
+ if (!containerId) return;
122
+ spawnSync('docker', ['stop', containerId], { encoding: 'utf8' });
123
+ spawnSync('docker', ['rm', containerId], { encoding: 'utf8' });
124
+ this.containerIds.delete(id);
125
+ }
126
+ }
@@ -1,77 +1,79 @@
1
- import { execFileSync } from 'child_process';
2
- import path from 'path';
3
- import fs from 'fs';
4
- import crypto from 'crypto';
5
- import type { WorkspaceManager, WorkspaceRequest } from '../workspace-manager';
6
- import type { WorkspaceLease, SnapshotRef } from '../../models/workspace';
7
-
8
- export class GitWorktreeManager implements WorkspaceManager {
9
- private leases = new Map<string, WorkspaceLease>();
10
-
11
- constructor(private readonly projectRoot: string) {}
12
-
13
- async allocate(req: WorkspaceRequest): Promise<WorkspaceLease> {
14
- const wsId = `ws-${req.work_item_id}-a${req.attempt_number}`;
15
- const branch = `oxe/${req.work_item_id}-attempt${req.attempt_number}`;
16
- const worktreePath = path.join(this.projectRoot, '.oxe', 'workspaces', wsId);
17
-
18
- const baseCommit = this.git(['rev-parse', 'HEAD']).trim();
19
-
20
- fs.mkdirSync(path.dirname(worktreePath), { recursive: true });
21
-
22
- // Create worktree on a new branch starting from HEAD
23
- this.git(['worktree', 'add', worktreePath, '-b', branch]);
24
-
25
- const lease: WorkspaceLease = {
26
- workspace_id: wsId,
27
- strategy: 'git_worktree',
28
- branch,
29
- base_commit: baseCommit,
30
- root_path: worktreePath,
31
- ttl_minutes: 45,
32
- };
33
- this.leases.set(wsId, lease);
34
- return lease;
35
- }
36
-
37
- async snapshot(id: string): Promise<SnapshotRef> {
38
- const lease = this.leases.get(id);
39
- if (!lease || !lease.root_path) throw new Error(`Workspace ${id} not found`);
40
- const commit = this.git(['rev-parse', 'HEAD'], lease.root_path).trim();
41
- return {
42
- snapshot_id: `snap-${crypto.randomBytes(4).toString('hex')}`,
43
- workspace_id: id,
44
- commit,
45
- created_at: new Date().toISOString(),
46
- };
47
- }
48
-
49
- async reset(id: string, snapRef: SnapshotRef): Promise<void> {
50
- const lease = this.leases.get(id);
51
- if (!lease) return;
52
- this.git(['reset', '--hard', snapRef.commit], lease.root_path);
53
- }
54
-
55
- async dispose(id: string): Promise<void> {
56
- const lease = this.leases.get(id);
57
- if (!lease) return;
58
- try {
59
- this.git(['worktree', 'remove', lease.root_path, '--force']);
60
- } catch {
61
- // worktree may already be gone
62
- }
63
- try {
64
- if (lease.branch) this.git(['branch', '-D', lease.branch]);
65
- } catch {
66
- // branch may already be deleted
67
- }
68
- this.leases.delete(id);
69
- }
70
-
71
- private git(args: string[], cwd?: string): string {
72
- return execFileSync('git', args, {
73
- cwd: cwd ?? this.projectRoot,
74
- encoding: 'utf8',
75
- });
76
- }
77
- }
1
+ import { execFileSync } from 'child_process';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+ import crypto from 'crypto';
5
+ import type { WorkspaceManager, WorkspaceRequest } from '../workspace-manager';
6
+ import type { WorkspaceLease, SnapshotRef } from '../../models/workspace';
7
+
8
+ export class GitWorktreeManager implements WorkspaceManager {
9
+ readonly isolation_level = 'isolated' as const;
10
+ private leases = new Map<string, WorkspaceLease>();
11
+
12
+ constructor(private readonly projectRoot: string) {}
13
+
14
+ async allocate(req: WorkspaceRequest): Promise<WorkspaceLease> {
15
+ const wsId = `ws-${req.work_item_id}-a${req.attempt_number}`;
16
+ const branch = `oxe/${req.work_item_id}-attempt${req.attempt_number}`;
17
+ const worktreePath = path.join(this.projectRoot, '.oxe', 'workspaces', wsId);
18
+
19
+ const baseCommit = this.git(['rev-parse', 'HEAD']).trim();
20
+
21
+ fs.mkdirSync(path.dirname(worktreePath), { recursive: true });
22
+
23
+ // Create worktree on a new branch starting from HEAD
24
+ this.git(['worktree', 'add', worktreePath, '-b', branch]);
25
+
26
+ const lease: WorkspaceLease = {
27
+ workspace_id: wsId,
28
+ strategy: 'git_worktree',
29
+ isolation_level: this.isolation_level,
30
+ branch,
31
+ base_commit: baseCommit,
32
+ root_path: worktreePath,
33
+ ttl_minutes: 45,
34
+ };
35
+ this.leases.set(wsId, lease);
36
+ return lease;
37
+ }
38
+
39
+ async snapshot(id: string): Promise<SnapshotRef> {
40
+ const lease = this.leases.get(id);
41
+ if (!lease || !lease.root_path) throw new Error(`Workspace ${id} not found`);
42
+ const commit = this.git(['rev-parse', 'HEAD'], lease.root_path).trim();
43
+ return {
44
+ snapshot_id: `snap-${crypto.randomBytes(4).toString('hex')}`,
45
+ workspace_id: id,
46
+ commit,
47
+ created_at: new Date().toISOString(),
48
+ };
49
+ }
50
+
51
+ async reset(id: string, snapRef: SnapshotRef): Promise<void> {
52
+ const lease = this.leases.get(id);
53
+ if (!lease) return;
54
+ this.git(['reset', '--hard', snapRef.commit], lease.root_path);
55
+ }
56
+
57
+ async dispose(id: string): Promise<void> {
58
+ const lease = this.leases.get(id);
59
+ if (!lease) return;
60
+ try {
61
+ this.git(['worktree', 'remove', lease.root_path, '--force']);
62
+ } catch {
63
+ // worktree may already be gone
64
+ }
65
+ try {
66
+ if (lease.branch) this.git(['branch', '-D', lease.branch]);
67
+ } catch {
68
+ // branch may already be deleted
69
+ }
70
+ this.leases.delete(id);
71
+ }
72
+
73
+ private git(args: string[], cwd?: string): string {
74
+ return execFileSync('git', args, {
75
+ cwd: cwd ?? this.projectRoot,
76
+ encoding: 'utf8',
77
+ });
78
+ }
79
+ }
@@ -1,35 +1,38 @@
1
- import crypto from 'crypto';
2
- import type { WorkspaceManager, WorkspaceRequest } from '../workspace-manager';
3
- import type { WorkspaceLease, SnapshotRef } from '../../models/workspace';
4
-
5
- export class InplaceWorkspaceManager implements WorkspaceManager {
6
- constructor(private readonly projectRoot: string) {}
7
-
8
- async allocate(req: WorkspaceRequest): Promise<WorkspaceLease> {
9
- return {
10
- workspace_id: `ws-inplace-${req.work_item_id}-a${req.attempt_number}`,
11
- strategy: 'inplace',
12
- branch: null,
13
- base_commit: null,
14
- root_path: this.projectRoot,
15
- ttl_minutes: 60,
16
- };
17
- }
18
-
19
- async snapshot(id: string): Promise<SnapshotRef> {
20
- return {
21
- snapshot_id: `snap-${crypto.randomBytes(4).toString('hex')}`,
22
- workspace_id: id,
23
- commit: 'HEAD',
24
- created_at: new Date().toISOString(),
25
- };
26
- }
27
-
28
- async reset(_id: string, _snapRef: SnapshotRef): Promise<void> {
29
- // inplace: no filesystem isolation — reset is a no-op
30
- }
31
-
32
- async dispose(_id: string): Promise<void> {
33
- // inplace: nothing to tear down
34
- }
35
- }
1
+ import crypto from 'crypto';
2
+ import type { WorkspaceManager, WorkspaceRequest } from '../workspace-manager';
3
+ import type { WorkspaceLease, SnapshotRef } from '../../models/workspace';
4
+
5
+ export class InplaceWorkspaceManager implements WorkspaceManager {
6
+ readonly isolation_level = 'shared' as const;
7
+
8
+ constructor(private readonly projectRoot: string) {}
9
+
10
+ async allocate(req: WorkspaceRequest): Promise<WorkspaceLease> {
11
+ return {
12
+ workspace_id: `ws-inplace-${req.work_item_id}-a${req.attempt_number}`,
13
+ strategy: 'inplace',
14
+ isolation_level: this.isolation_level,
15
+ branch: null,
16
+ base_commit: null,
17
+ root_path: this.projectRoot,
18
+ ttl_minutes: 60,
19
+ };
20
+ }
21
+
22
+ async snapshot(id: string): Promise<SnapshotRef> {
23
+ return {
24
+ snapshot_id: `snap-${crypto.randomBytes(4).toString('hex')}`,
25
+ workspace_id: id,
26
+ commit: 'HEAD',
27
+ created_at: new Date().toISOString(),
28
+ };
29
+ }
30
+
31
+ async reset(_id: string, _snapRef: SnapshotRef): Promise<void> {
32
+ // inplace: no filesystem isolation — reset is a no-op
33
+ }
34
+
35
+ async dispose(_id: string): Promise<void> {
36
+ // inplace: nothing to tear down
37
+ }
38
+ }
@@ -1,15 +1,16 @@
1
- import type { WorkspaceLease, SnapshotRef, WorkspaceStrategy } from '../models/workspace';
2
-
3
- export interface WorkspaceRequest {
4
- work_item_id: string;
5
- attempt_number: number;
6
- strategy: WorkspaceStrategy;
7
- mutation_scope: string[];
8
- }
9
-
10
- export interface WorkspaceManager {
11
- allocate(req: WorkspaceRequest): Promise<WorkspaceLease>;
12
- snapshot(id: string): Promise<SnapshotRef>;
13
- reset(id: string, snapRef: SnapshotRef): Promise<void>;
14
- dispose(id: string): Promise<void>;
15
- }
1
+ import type { WorkspaceLease, SnapshotRef, WorkspaceStrategy, WorkspaceIsolationLevel } from '../models/workspace';
2
+
3
+ export interface WorkspaceRequest {
4
+ work_item_id: string;
5
+ attempt_number: number;
6
+ strategy: WorkspaceStrategy;
7
+ mutation_scope: string[];
8
+ }
9
+
10
+ export interface WorkspaceManager {
11
+ readonly isolation_level: WorkspaceIsolationLevel;
12
+ allocate(req: WorkspaceRequest): Promise<WorkspaceLease>;
13
+ snapshot(id: string): Promise<SnapshotRef>;
14
+ reset(id: string, snapRef: SnapshotRef): Promise<void>;
15
+ dispose(id: string): Promise<void>;
16
+ }
@@ -1,17 +1,17 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "commonjs",
5
- "moduleResolution": "node",
6
- "strict": true,
7
- "esModuleInterop": true,
8
- "declaration": true,
9
- "declarationMap": false,
10
- "sourceMap": false,
11
- "outDir": "../../lib/runtime",
12
- "rootDir": "src",
13
- "skipLibCheck": true
14
- },
15
- "include": ["src/**/*.ts"],
16
- "exclude": ["tests/**", "node_modules/**", "dist-tests/**"]
17
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "declaration": true,
9
+ "declarationMap": false,
10
+ "sourceMap": false,
11
+ "outDir": "../../lib/runtime",
12
+ "rootDir": "src",
13
+ "skipLibCheck": true
14
+ },
15
+ "include": ["src/**/*.ts"],
16
+ "exclude": ["tests/**", "node_modules/**", "dist-tests/**"]
17
+ }
@@ -1,7 +1,7 @@
1
- .vscode/**
2
- .git/**
3
- .gitignore
4
- node_modules/**
5
- **/*.map
6
- **/*.test.js
7
- tests/**
1
+ .vscode/**
2
+ .git/**
3
+ .gitignore
4
+ node_modules/**
5
+ **/*.map
6
+ **/*.test.js
7
+ tests/**
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OXE contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.