oxe-cc 1.2.1 → 1.3.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 (276) 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/CHANGELOG.md +52 -17
  73. package/README.md +610 -551
  74. package/bin/banner.txt +1 -1
  75. package/bin/lib/oxe-agent-install.cjs +69 -69
  76. package/bin/lib/oxe-azure.cjs +1445 -1445
  77. package/bin/lib/oxe-context-engine.cjs +867 -867
  78. package/bin/lib/oxe-dashboard.cjs +76 -28
  79. package/bin/lib/oxe-operational.cjs +2144 -1340
  80. package/bin/lib/oxe-project-health.cjs +483 -1
  81. package/bin/lib/oxe-runtime-semantics.cjs +12 -0
  82. package/bin/oxe-cc.js +554 -152
  83. package/commands/oxe/ask.md +2 -2
  84. package/commands/oxe/capabilities.md +2 -2
  85. package/commands/oxe/checkpoint.md +2 -2
  86. package/commands/oxe/compact.md +2 -2
  87. package/commands/oxe/dashboard.md +2 -2
  88. package/commands/oxe/debug.md +2 -2
  89. package/commands/oxe/discuss.md +2 -2
  90. package/commands/oxe/execute.md +5 -2
  91. package/commands/oxe/forensics.md +2 -2
  92. package/commands/oxe/help.md +2 -2
  93. package/commands/oxe/loop.md +2 -2
  94. package/commands/oxe/milestone.md +2 -2
  95. package/commands/oxe/next.md +2 -2
  96. package/commands/oxe/obs.md +2 -2
  97. package/commands/oxe/oxe.md +2 -2
  98. package/commands/oxe/plan-agent.md +2 -2
  99. package/commands/oxe/plan.md +2 -2
  100. package/commands/oxe/project.md +2 -2
  101. package/commands/oxe/quick.md +2 -2
  102. package/commands/oxe/research.md +2 -2
  103. package/commands/oxe/retro.md +2 -2
  104. package/commands/oxe/review-pr.md +2 -2
  105. package/commands/oxe/route.md +2 -2
  106. package/commands/oxe/scan.md +2 -2
  107. package/commands/oxe/security.md +2 -2
  108. package/commands/oxe/session.md +2 -2
  109. package/commands/oxe/ship.md +2 -2
  110. package/commands/oxe/skill.md +2 -2
  111. package/commands/oxe/spec.md +2 -2
  112. package/commands/oxe/ui-review.md +2 -2
  113. package/commands/oxe/ui-spec.md +2 -2
  114. package/commands/oxe/update.md +2 -2
  115. package/commands/oxe/validate-gaps.md +2 -2
  116. package/commands/oxe/verify.md +5 -2
  117. package/commands/oxe/workstream.md +2 -2
  118. package/lib/runtime/delivery/branch-manager.d.ts +1 -0
  119. package/lib/runtime/delivery/branch-manager.js +7 -0
  120. package/lib/runtime/delivery/ci-checks.js +34 -1
  121. package/lib/runtime/delivery/delivery-records.d.ts +34 -0
  122. package/lib/runtime/delivery/delivery-records.js +48 -0
  123. package/lib/runtime/delivery/index.d.ts +1 -0
  124. package/lib/runtime/delivery/index.js +1 -0
  125. package/lib/runtime/delivery/promotion-pipeline.d.ts +26 -2
  126. package/lib/runtime/delivery/promotion-pipeline.js +111 -14
  127. package/lib/runtime/gate/gate-manager.d.ts +41 -0
  128. package/lib/runtime/gate/gate-manager.js +108 -1
  129. package/lib/runtime/index.d.ts +2 -2
  130. package/lib/runtime/index.js +3 -1
  131. package/lib/runtime/models/gate-decision.d.ts +4 -1
  132. package/lib/runtime/models/workspace.d.ts +3 -0
  133. package/lib/runtime/plugins/capability-adapter.d.ts +12 -0
  134. package/lib/runtime/plugins/capability-adapter.js +204 -0
  135. package/lib/runtime/plugins/capability-matrix.d.ts +5 -0
  136. package/lib/runtime/plugins/capability-matrix.js +48 -17
  137. package/lib/runtime/plugins/index.d.ts +1 -0
  138. package/lib/runtime/plugins/index.js +1 -0
  139. package/lib/runtime/plugins/plugin-abi.d.ts +2 -0
  140. package/lib/runtime/plugins/plugin-manifest.d.ts +1 -1
  141. package/lib/runtime/plugins/plugin-manifest.js +6 -2
  142. package/lib/runtime/plugins/plugin-registry.d.ts +46 -0
  143. package/lib/runtime/plugins/plugin-registry.js +79 -2
  144. package/lib/runtime/policy/policy-engine.d.ts +19 -0
  145. package/lib/runtime/policy/policy-engine.js +76 -4
  146. package/lib/runtime/projection/projection-engine.d.ts +9 -1
  147. package/lib/runtime/projection/projection-engine.js +73 -3
  148. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +43 -1
  149. package/lib/runtime/scheduler/multi-agent-coordinator.js +151 -39
  150. package/lib/runtime/scheduler/run-journal.d.ts +1 -1
  151. package/lib/runtime/scheduler/scheduler.d.ts +19 -1
  152. package/lib/runtime/scheduler/scheduler.js +258 -13
  153. package/lib/runtime/verification/verification-compiler.d.ts +43 -0
  154. package/lib/runtime/verification/verification-compiler.js +137 -0
  155. package/lib/runtime/verification/verification-manifest.d.ts +9 -0
  156. package/lib/runtime/verification/verification-manifest.js +56 -6
  157. package/lib/runtime/workspace/strategies/ephemeral-container.d.ts +1 -0
  158. package/lib/runtime/workspace/strategies/ephemeral-container.js +4 -0
  159. package/lib/runtime/workspace/strategies/git-worktree.d.ts +1 -0
  160. package/lib/runtime/workspace/strategies/git-worktree.js +2 -0
  161. package/lib/runtime/workspace/strategies/inplace.d.ts +1 -0
  162. package/lib/runtime/workspace/strategies/inplace.js +2 -0
  163. package/lib/runtime/workspace/workspace-manager.d.ts +2 -1
  164. package/lib/sdk/README.md +9 -9
  165. package/lib/sdk/index.cjs +33 -24
  166. package/lib/sdk/index.d.ts +149 -14
  167. package/oxe/templates/ACTIVE-RUN.template.json +32 -32
  168. package/oxe/templates/CAPABILITIES.template.md +7 -7
  169. package/oxe/templates/CAPABILITY.template.md +45 -45
  170. package/oxe/templates/CHECKPOINTS.template.md +7 -7
  171. package/oxe/templates/EXECUTION-RUNTIME.template.md +68 -68
  172. package/oxe/templates/HYPOTHESES.template.md +33 -33
  173. package/oxe/templates/LESSONS-METRICS.template.json +13 -13
  174. package/oxe/templates/NOTES.template.md +16 -16
  175. package/oxe/templates/PLAN-REVIEW.template.md +31 -31
  176. package/oxe/templates/SESSION.template.md +34 -34
  177. package/oxe/templates/SKILL.template.md +26 -26
  178. package/oxe/templates/STATE.md +55 -55
  179. package/oxe/templates/WORKFLOW_AUTHORING.md +18 -18
  180. package/oxe/workflows/ask.md +96 -96
  181. package/oxe/workflows/capabilities.md +25 -25
  182. package/oxe/workflows/dashboard.md +33 -33
  183. package/oxe/workflows/discuss.md +12 -12
  184. package/oxe/workflows/execute.md +14 -0
  185. package/oxe/workflows/help.md +352 -352
  186. package/oxe/workflows/next.md +22 -22
  187. package/oxe/workflows/oxe.md +6 -6
  188. package/oxe/workflows/plan-agent.md +9 -9
  189. package/oxe/workflows/quick.md +10 -10
  190. package/oxe/workflows/references/reasoning-discovery.md +28 -28
  191. package/oxe/workflows/references/reasoning-execution.md +29 -29
  192. package/oxe/workflows/references/reasoning-planning.md +32 -32
  193. package/oxe/workflows/references/reasoning-review.md +29 -29
  194. package/oxe/workflows/references/reasoning-status.md +24 -24
  195. package/oxe/workflows/references/robustness-elevation.md +295 -295
  196. package/oxe/workflows/references/workflow-runtime-contracts.json +952 -930
  197. package/oxe/workflows/route.md +16 -16
  198. package/oxe/workflows/session.md +213 -213
  199. package/oxe/workflows/ship.md +142 -142
  200. package/oxe/workflows/skill.md +44 -44
  201. package/oxe/workflows/ui-review.md +36 -36
  202. package/oxe/workflows/verify-audit.md +73 -73
  203. package/oxe/workflows/verify.md +10 -0
  204. package/package.json +92 -92
  205. package/packages/runtime/package.json +17 -17
  206. package/packages/runtime/src/audit/audit-trail.ts +243 -243
  207. package/packages/runtime/src/audit/index.ts +2 -2
  208. package/packages/runtime/src/audit/policy-pack.ts +62 -62
  209. package/packages/runtime/src/compiler/graph-compiler.ts +245 -245
  210. package/packages/runtime/src/compiler/index.ts +1 -1
  211. package/packages/runtime/src/context/context-pack-builder.ts +259 -259
  212. package/packages/runtime/src/context/context-pack-store.ts +197 -197
  213. package/packages/runtime/src/context/context-profiles.ts +60 -60
  214. package/packages/runtime/src/context/index.ts +3 -3
  215. package/packages/runtime/src/decision/decision-engine.ts +174 -174
  216. package/packages/runtime/src/decision/decision-memo.ts +211 -211
  217. package/packages/runtime/src/decision/index.ts +2 -2
  218. package/packages/runtime/src/delivery/branch-manager.ts +91 -84
  219. package/packages/runtime/src/delivery/ci-checks.ts +285 -252
  220. package/packages/runtime/src/delivery/delivery-records.ts +75 -0
  221. package/packages/runtime/src/delivery/index.ts +5 -4
  222. package/packages/runtime/src/delivery/pr-manager.ts +112 -112
  223. package/packages/runtime/src/delivery/promotion-pipeline.ts +334 -180
  224. package/packages/runtime/src/events/bus.ts +92 -92
  225. package/packages/runtime/src/events/catalog.ts +29 -29
  226. package/packages/runtime/src/events/envelope.ts +14 -14
  227. package/packages/runtime/src/events/index.ts +3 -3
  228. package/packages/runtime/src/evidence/evidence-store.ts +130 -130
  229. package/packages/runtime/src/evidence/index.ts +1 -1
  230. package/packages/runtime/src/gate/gate-manager.ts +289 -137
  231. package/packages/runtime/src/gate/index.ts +1 -1
  232. package/packages/runtime/src/index.ts +41 -37
  233. package/packages/runtime/src/models/attempt.ts +19 -19
  234. package/packages/runtime/src/models/evidence.ts +21 -21
  235. package/packages/runtime/src/models/gate-decision.ts +25 -21
  236. package/packages/runtime/src/models/index.ts +8 -8
  237. package/packages/runtime/src/models/run.ts +24 -24
  238. package/packages/runtime/src/models/session.ts +11 -11
  239. package/packages/runtime/src/models/verification-result.ts +10 -10
  240. package/packages/runtime/src/models/work-item.ts +25 -25
  241. package/packages/runtime/src/models/workspace.ts +31 -28
  242. package/packages/runtime/src/plugins/capability-adapter.ts +206 -0
  243. package/packages/runtime/src/plugins/capability-matrix.ts +126 -83
  244. package/packages/runtime/src/plugins/index.ts +5 -4
  245. package/packages/runtime/src/plugins/plugin-abi.ts +97 -95
  246. package/packages/runtime/src/plugins/plugin-manifest.ts +118 -113
  247. package/packages/runtime/src/plugins/plugin-registry.ts +232 -124
  248. package/packages/runtime/src/policy/index.ts +1 -1
  249. package/packages/runtime/src/policy/policy-engine.ts +330 -244
  250. package/packages/runtime/src/projection/index.ts +1 -1
  251. package/packages/runtime/src/projection/projection-engine.ts +328 -249
  252. package/packages/runtime/src/reducers/debug-reducer.ts +36 -36
  253. package/packages/runtime/src/reducers/index.ts +2 -2
  254. package/packages/runtime/src/reducers/run-state-reducer.ts +269 -269
  255. package/packages/runtime/src/scheduler/agent-registry.ts +132 -132
  256. package/packages/runtime/src/scheduler/agent-roles.ts +109 -109
  257. package/packages/runtime/src/scheduler/index.ts +4 -4
  258. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +521 -333
  259. package/packages/runtime/src/scheduler/run-journal.ts +62 -62
  260. package/packages/runtime/src/scheduler/scheduler.ts +722 -441
  261. package/packages/runtime/src/verification/index.ts +2 -2
  262. package/packages/runtime/src/verification/verification-compiler.ts +436 -225
  263. package/packages/runtime/src/verification/verification-manifest.ts +252 -192
  264. package/packages/runtime/src/workspace/index.ts +5 -5
  265. package/packages/runtime/src/workspace/strategies/ephemeral-container.ts +126 -121
  266. package/packages/runtime/src/workspace/strategies/git-worktree.ts +79 -77
  267. package/packages/runtime/src/workspace/strategies/inplace.ts +38 -35
  268. package/packages/runtime/src/workspace/workspace-manager.ts +16 -15
  269. package/packages/runtime/tsconfig.json +17 -17
  270. package/vscode-extension/.vscodeignore +7 -7
  271. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
  272. package/vscode-extension/package.json +185 -185
  273. package/vscode-extension/src/extension.js +310 -310
  274. package/vscode-extension/src/shared/contextLoader.js +137 -137
  275. package/vscode-extension/src/shared/contractBuilder.js +159 -159
  276. 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/**