oxe-cc 1.0.0 → 1.2.1

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 (207) hide show
  1. package/.cursor/commands/oxe-ask.md +1 -1
  2. package/.cursor/commands/oxe-capabilities.md +1 -1
  3. package/.cursor/commands/oxe-checkpoint.md +1 -1
  4. package/.cursor/commands/oxe-compact.md +1 -1
  5. package/.cursor/commands/oxe-dashboard.md +1 -1
  6. package/.cursor/commands/oxe-debug.md +1 -1
  7. package/.cursor/commands/oxe-discuss.md +1 -1
  8. package/.cursor/commands/oxe-execute.md +2 -2
  9. package/.cursor/commands/oxe-forensics.md +1 -1
  10. package/.cursor/commands/oxe-help.md +1 -1
  11. package/.cursor/commands/oxe-loop.md +1 -1
  12. package/.cursor/commands/oxe-milestone.md +1 -1
  13. package/.cursor/commands/oxe-next.md +1 -1
  14. package/.cursor/commands/oxe-obs.md +1 -1
  15. package/.cursor/commands/oxe-plan-agent.md +1 -1
  16. package/.cursor/commands/oxe-plan.md +1 -1
  17. package/.cursor/commands/oxe-project.md +1 -1
  18. package/.cursor/commands/oxe-quick.md +1 -1
  19. package/.cursor/commands/oxe-research.md +1 -1
  20. package/.cursor/commands/oxe-retro.md +1 -1
  21. package/.cursor/commands/oxe-review-pr.md +1 -1
  22. package/.cursor/commands/oxe-route.md +1 -1
  23. package/.cursor/commands/oxe-scan.md +1 -1
  24. package/.cursor/commands/oxe-security.md +1 -1
  25. package/.cursor/commands/oxe-session.md +2 -2
  26. package/.cursor/commands/oxe-ship.md +45 -0
  27. package/.cursor/commands/oxe-skill.md +1 -1
  28. package/.cursor/commands/oxe-spec.md +1 -1
  29. package/.cursor/commands/oxe-ui-review.md +1 -1
  30. package/.cursor/commands/oxe-ui-spec.md +1 -1
  31. package/.cursor/commands/oxe-update.md +1 -1
  32. package/.cursor/commands/oxe-validate-gaps.md +1 -1
  33. package/.cursor/commands/oxe-verify.md +1 -1
  34. package/.cursor/commands/oxe-workstream.md +1 -1
  35. package/.cursor/commands/oxe.md +4 -4
  36. package/.github/copilot-instructions.md +91 -1
  37. package/.github/prompts/oxe-ask.prompt.md +1 -1
  38. package/.github/prompts/oxe-capabilities.prompt.md +1 -1
  39. package/.github/prompts/oxe-checkpoint.prompt.md +1 -1
  40. package/.github/prompts/oxe-compact.prompt.md +1 -1
  41. package/.github/prompts/oxe-dashboard.prompt.md +1 -1
  42. package/.github/prompts/oxe-debug.prompt.md +1 -1
  43. package/.github/prompts/oxe-discuss.prompt.md +1 -1
  44. package/.github/prompts/oxe-execute.prompt.md +2 -2
  45. package/.github/prompts/oxe-forensics.prompt.md +1 -1
  46. package/.github/prompts/oxe-help.prompt.md +1 -1
  47. package/.github/prompts/oxe-loop.prompt.md +1 -1
  48. package/.github/prompts/oxe-milestone.prompt.md +1 -1
  49. package/.github/prompts/oxe-next.prompt.md +1 -1
  50. package/.github/prompts/oxe-obs.prompt.md +1 -1
  51. package/.github/prompts/oxe-plan-agent.prompt.md +1 -1
  52. package/.github/prompts/oxe-plan.prompt.md +1 -1
  53. package/.github/prompts/oxe-project.prompt.md +1 -1
  54. package/.github/prompts/oxe-quick.prompt.md +1 -1
  55. package/.github/prompts/oxe-research.prompt.md +1 -1
  56. package/.github/prompts/oxe-retro.prompt.md +1 -1
  57. package/.github/prompts/oxe-review-pr.prompt.md +1 -1
  58. package/.github/prompts/oxe-route.prompt.md +1 -1
  59. package/.github/prompts/oxe-scan.prompt.md +1 -1
  60. package/.github/prompts/oxe-security.prompt.md +1 -1
  61. package/.github/prompts/oxe-session.prompt.md +2 -2
  62. package/.github/prompts/oxe-ship.prompt.md +45 -0
  63. package/.github/prompts/oxe-skill.prompt.md +1 -1
  64. package/.github/prompts/oxe-spec.prompt.md +1 -1
  65. package/.github/prompts/oxe-ui-review.prompt.md +1 -1
  66. package/.github/prompts/oxe-ui-spec.prompt.md +1 -1
  67. package/.github/prompts/oxe-update.prompt.md +1 -1
  68. package/.github/prompts/oxe-validate-gaps.prompt.md +1 -1
  69. package/.github/prompts/oxe-verify.prompt.md +1 -1
  70. package/.github/prompts/oxe-workstream.prompt.md +1 -1
  71. package/.github/prompts/oxe.prompt.md +3 -3
  72. package/AGENTS.md +43 -28
  73. package/CHANGELOG.md +158 -0
  74. package/README.md +72 -50
  75. package/bin/banner.txt +1 -1
  76. package/bin/lib/oxe-project-health.cjs +1 -1
  77. package/commands/oxe/ask.md +5 -1
  78. package/commands/oxe/checkpoint.md +1 -1
  79. package/commands/oxe/compact.md +1 -1
  80. package/commands/oxe/debug.md +1 -1
  81. package/commands/oxe/execute.md +2 -2
  82. package/commands/oxe/forensics.md +1 -1
  83. package/commands/oxe/loop.md +1 -1
  84. package/commands/oxe/milestone.md +1 -1
  85. package/commands/oxe/next.md +1 -1
  86. package/commands/oxe/obs.md +1 -1
  87. package/commands/oxe/oxe.md +3 -3
  88. package/commands/oxe/project.md +1 -1
  89. package/commands/oxe/research.md +1 -1
  90. package/commands/oxe/retro.md +1 -1
  91. package/commands/oxe/review-pr.md +1 -1
  92. package/commands/oxe/route.md +1 -1
  93. package/commands/oxe/scan.md +1 -1
  94. package/commands/oxe/security.md +1 -1
  95. package/commands/oxe/session.md +2 -2
  96. package/commands/oxe/ship.md +49 -0
  97. package/commands/oxe/spec.md +2 -2
  98. package/commands/oxe/ui-review.md +1 -1
  99. package/commands/oxe/ui-spec.md +1 -1
  100. package/commands/oxe/validate-gaps.md +1 -1
  101. package/commands/oxe/verify.md +2 -2
  102. package/commands/oxe/workstream.md +1 -1
  103. package/lib/runtime/audit/audit-trail.d.ts +71 -0
  104. package/lib/runtime/audit/audit-trail.js +154 -0
  105. package/lib/runtime/audit/index.d.ts +2 -0
  106. package/lib/runtime/audit/index.js +18 -0
  107. package/lib/runtime/audit/policy-pack.d.ts +15 -0
  108. package/lib/runtime/audit/policy-pack.js +57 -0
  109. package/lib/runtime/context/context-pack-builder.d.ts +15 -0
  110. package/lib/runtime/context/context-pack-builder.js +42 -0
  111. package/lib/runtime/context/context-pack-store.d.ts +38 -0
  112. package/lib/runtime/context/context-pack-store.js +142 -0
  113. package/lib/runtime/context/context-profiles.d.ts +11 -0
  114. package/lib/runtime/context/context-profiles.js +51 -0
  115. package/lib/runtime/context/index.d.ts +2 -0
  116. package/lib/runtime/context/index.js +2 -0
  117. package/lib/runtime/decision/decision-engine.d.ts +43 -0
  118. package/lib/runtime/decision/decision-engine.js +127 -0
  119. package/lib/runtime/decision/decision-memo.d.ts +53 -0
  120. package/lib/runtime/decision/decision-memo.js +173 -0
  121. package/lib/runtime/decision/index.d.ts +2 -0
  122. package/lib/runtime/decision/index.js +18 -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 +39 -0
  126. package/lib/runtime/delivery/promotion-pipeline.js +127 -0
  127. package/lib/runtime/index.d.ts +3 -0
  128. package/lib/runtime/index.js +4 -0
  129. package/lib/runtime/plugins/capability-matrix.d.ts +20 -0
  130. package/lib/runtime/plugins/capability-matrix.js +59 -0
  131. package/lib/runtime/plugins/index.d.ts +2 -0
  132. package/lib/runtime/plugins/index.js +2 -0
  133. package/lib/runtime/plugins/plugin-manifest.d.ts +22 -0
  134. package/lib/runtime/plugins/plugin-manifest.js +91 -0
  135. package/lib/runtime/plugins/plugin-registry.js +5 -0
  136. package/lib/runtime/policy/policy-engine.d.ts +28 -1
  137. package/lib/runtime/policy/policy-engine.js +96 -5
  138. package/lib/runtime/reducers/run-state-reducer.d.ts +26 -0
  139. package/lib/runtime/reducers/run-state-reducer.js +117 -1
  140. package/lib/runtime/scheduler/agent-registry.d.ts +44 -0
  141. package/lib/runtime/scheduler/agent-registry.js +96 -0
  142. package/lib/runtime/scheduler/agent-roles.d.ts +54 -0
  143. package/lib/runtime/scheduler/agent-roles.js +62 -0
  144. package/lib/runtime/scheduler/index.d.ts +3 -0
  145. package/lib/runtime/scheduler/index.js +3 -0
  146. package/lib/runtime/scheduler/multi-agent-coordinator.d.ts +2 -0
  147. package/lib/runtime/scheduler/multi-agent-coordinator.js +91 -4
  148. package/lib/runtime/scheduler/run-journal.d.ts +18 -0
  149. package/lib/runtime/scheduler/run-journal.js +54 -0
  150. package/lib/runtime/scheduler/scheduler.d.ts +11 -1
  151. package/lib/runtime/scheduler/scheduler.js +135 -7
  152. package/lib/runtime/verification/index.d.ts +1 -0
  153. package/lib/runtime/verification/index.js +1 -0
  154. package/lib/runtime/verification/verification-manifest.d.ts +58 -0
  155. package/lib/runtime/verification/verification-manifest.js +129 -0
  156. package/oxe/workflows/ask.md +4 -0
  157. package/oxe/workflows/checkpoint.md +14 -10
  158. package/oxe/workflows/debug.md +19 -15
  159. package/oxe/workflows/execute.md +30 -2
  160. package/oxe/workflows/forensics.md +13 -9
  161. package/oxe/workflows/help.md +97 -49
  162. package/oxe/workflows/loop.md +17 -13
  163. package/oxe/workflows/obs.md +4 -0
  164. package/oxe/workflows/oxe.md +64 -31
  165. package/oxe/workflows/project.md +6 -1
  166. package/oxe/workflows/references/workflow-runtime-contracts.json +23 -0
  167. package/oxe/workflows/research.md +32 -28
  168. package/oxe/workflows/retro.md +4 -0
  169. package/oxe/workflows/review-pr.md +15 -11
  170. package/oxe/workflows/scan.md +4 -0
  171. package/oxe/workflows/security.md +14 -10
  172. package/oxe/workflows/session.md +17 -1
  173. package/oxe/workflows/ship.md +142 -0
  174. package/oxe/workflows/spec.md +15 -0
  175. package/oxe/workflows/ui-review.md +20 -16
  176. package/oxe/workflows/ui-spec.md +7 -3
  177. package/oxe/workflows/validate-gaps.md +13 -9
  178. package/oxe/workflows/verify.md +42 -3
  179. package/package.json +1 -1
  180. package/packages/runtime/src/audit/audit-trail.ts +243 -0
  181. package/packages/runtime/src/audit/index.ts +2 -0
  182. package/packages/runtime/src/audit/policy-pack.ts +62 -0
  183. package/packages/runtime/src/context/context-pack-builder.ts +66 -0
  184. package/packages/runtime/src/context/context-pack-store.ts +197 -0
  185. package/packages/runtime/src/context/context-profiles.ts +60 -0
  186. package/packages/runtime/src/context/index.ts +2 -0
  187. package/packages/runtime/src/decision/decision-engine.ts +174 -0
  188. package/packages/runtime/src/decision/decision-memo.ts +211 -0
  189. package/packages/runtime/src/decision/index.ts +2 -0
  190. package/packages/runtime/src/delivery/index.ts +1 -0
  191. package/packages/runtime/src/delivery/promotion-pipeline.ts +180 -0
  192. package/packages/runtime/src/index.ts +5 -0
  193. package/packages/runtime/src/plugins/capability-matrix.ts +83 -0
  194. package/packages/runtime/src/plugins/index.ts +2 -0
  195. package/packages/runtime/src/plugins/plugin-manifest.ts +113 -0
  196. package/packages/runtime/src/plugins/plugin-registry.ts +5 -0
  197. package/packages/runtime/src/policy/policy-engine.ts +138 -7
  198. package/packages/runtime/src/reducers/run-state-reducer.ts +143 -1
  199. package/packages/runtime/src/scheduler/agent-registry.ts +132 -0
  200. package/packages/runtime/src/scheduler/agent-roles.ts +109 -0
  201. package/packages/runtime/src/scheduler/index.ts +3 -0
  202. package/packages/runtime/src/scheduler/multi-agent-coordinator.ts +106 -4
  203. package/packages/runtime/src/scheduler/run-journal.ts +62 -0
  204. package/packages/runtime/src/scheduler/scheduler.ts +168 -8
  205. package/packages/runtime/src/verification/index.ts +1 -0
  206. package/packages/runtime/src/verification/verification-manifest.ts +192 -0
  207. package/vscode-extension/oxe-agents-1.0.0.vsix +0 -0
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./plugin-abi"), exports);
18
18
  __exportStar(require("./plugin-registry"), exports);
19
+ __exportStar(require("./plugin-manifest"), exports);
20
+ __exportStar(require("./capability-matrix"), exports);
@@ -0,0 +1,22 @@
1
+ import type { OxePlugin } from './plugin-abi';
2
+ export declare const CURRENT_ABI_VERSION = "1.0.0";
3
+ export interface PluginManifest {
4
+ name: string;
5
+ version: string;
6
+ abi_version: string;
7
+ capabilities: Array<'tool' | 'workspace' | 'verifier' | 'context' | 'hooks'>;
8
+ tool_action_types?: string[];
9
+ workspace_strategies?: string[];
10
+ verifier_check_types?: string[];
11
+ context_provider_names?: string[];
12
+ hook_names?: string[];
13
+ }
14
+ export interface PluginValidationResult {
15
+ valid: boolean;
16
+ errors: string[];
17
+ warnings: string[];
18
+ }
19
+ export declare function extractManifest(plugin: OxePlugin): PluginManifest;
20
+ export declare function validatePlugin(plugin: OxePlugin): PluginValidationResult;
21
+ export declare function isAbiCompatible(pluginAbiVersion: string): boolean;
22
+ export declare function sandboxInvoke<T>(fn: () => Promise<T>, timeoutMs?: number): Promise<T>;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CURRENT_ABI_VERSION = void 0;
4
+ exports.extractManifest = extractManifest;
5
+ exports.validatePlugin = validatePlugin;
6
+ exports.isAbiCompatible = isAbiCompatible;
7
+ exports.sandboxInvoke = sandboxInvoke;
8
+ exports.CURRENT_ABI_VERSION = '1.0.0';
9
+ function extractManifest(plugin) {
10
+ const capabilities = [];
11
+ if (plugin.toolProviders?.length)
12
+ capabilities.push('tool');
13
+ if (plugin.workspaceProviders?.length)
14
+ capabilities.push('workspace');
15
+ if (plugin.verifierProviders?.length)
16
+ capabilities.push('verifier');
17
+ if (plugin.contextProviders?.length)
18
+ capabilities.push('context');
19
+ if (plugin.hooks && Object.keys(plugin.hooks).length > 0)
20
+ capabilities.push('hooks');
21
+ return {
22
+ name: plugin.name,
23
+ version: plugin.version ?? '0.0.0',
24
+ abi_version: exports.CURRENT_ABI_VERSION,
25
+ capabilities,
26
+ tool_action_types: plugin.toolProviders?.flatMap((p) => ['read_code', 'generate_patch', 'run_tests', 'collect_evidence', 'custom'].filter((t) => p.supports(t))) ?? [],
27
+ workspace_strategies: plugin.workspaceProviders?.map((p) => p.name) ?? [],
28
+ verifier_check_types: plugin.verifierProviders?.flatMap((p) => ['unit', 'integration', 'smoke', 'policy', 'security', 'custom'].filter((t) => p.supports(t))) ?? [],
29
+ context_provider_names: plugin.contextProviders?.map((p) => p.name) ?? [],
30
+ hook_names: plugin.hooks ? Object.keys(plugin.hooks) : [],
31
+ };
32
+ }
33
+ function validatePlugin(plugin) {
34
+ const errors = [];
35
+ const warnings = [];
36
+ if (!plugin.name || typeof plugin.name !== 'string') {
37
+ errors.push('Plugin must have a non-empty string name');
38
+ }
39
+ if (plugin.version && !/^\d+\.\d+\.\d+/.test(plugin.version)) {
40
+ warnings.push(`Plugin version "${plugin.version}" does not follow semver`);
41
+ }
42
+ if (!plugin.toolProviders?.length &&
43
+ !plugin.workspaceProviders?.length &&
44
+ !plugin.verifierProviders?.length &&
45
+ !plugin.contextProviders?.length &&
46
+ !plugin.hooks) {
47
+ warnings.push('Plugin declares no providers or hooks — it has no effect');
48
+ }
49
+ // Validate each tool provider
50
+ for (const tp of plugin.toolProviders ?? []) {
51
+ if (!tp.name)
52
+ errors.push('ToolProvider missing name');
53
+ if (typeof tp.supports !== 'function')
54
+ errors.push(`ToolProvider "${tp.name}" missing supports() method`);
55
+ if (typeof tp.invoke !== 'function')
56
+ errors.push(`ToolProvider "${tp.name}" missing invoke() method`);
57
+ }
58
+ // Validate each workspace provider
59
+ for (const wp of plugin.workspaceProviders ?? []) {
60
+ if (!wp.name)
61
+ errors.push('WorkspaceProvider missing name');
62
+ if (typeof wp.supportsStrategy !== 'function')
63
+ errors.push(`WorkspaceProvider "${wp.name}" missing supportsStrategy()`);
64
+ if (typeof wp.allocate !== 'function')
65
+ errors.push(`WorkspaceProvider "${wp.name}" missing allocate()`);
66
+ }
67
+ // Validate each verifier provider
68
+ for (const vp of plugin.verifierProviders ?? []) {
69
+ if (!vp.name)
70
+ errors.push('VerifierProvider missing name');
71
+ if (typeof vp.supports !== 'function')
72
+ errors.push(`VerifierProvider "${vp.name}" missing supports()`);
73
+ if (typeof vp.execute !== 'function')
74
+ errors.push(`VerifierProvider "${vp.name}" missing execute()`);
75
+ }
76
+ return { valid: errors.length === 0, errors, warnings };
77
+ }
78
+ function isAbiCompatible(pluginAbiVersion) {
79
+ // Major version must match; minor/patch are backwards-compatible
80
+ const [currMajor] = exports.CURRENT_ABI_VERSION.split('.').map(Number);
81
+ const [plugMajor] = pluginAbiVersion.split('.').map(Number);
82
+ return currMajor === plugMajor;
83
+ }
84
+ function sandboxInvoke(fn, timeoutMs = 10000) {
85
+ return new Promise((resolve, reject) => {
86
+ const timer = setTimeout(() => {
87
+ reject(new Error(`Plugin invocation timed out after ${timeoutMs}ms`));
88
+ }, timeoutMs);
89
+ fn().then((result) => { clearTimeout(timer); resolve(result); }, (err) => { clearTimeout(timer); reject(err instanceof Error ? err : new Error(String(err))); });
90
+ });
91
+ }
@@ -8,6 +8,7 @@ exports.globalRegistry = globalRegistry;
8
8
  exports.resetGlobalRegistry = resetGlobalRegistry;
9
9
  const fs_1 = __importDefault(require("fs"));
10
10
  const path_1 = __importDefault(require("path"));
11
+ const plugin_manifest_1 = require("./plugin-manifest");
11
12
  class PluginRegistry {
12
13
  constructor() {
13
14
  this.plugins = [];
@@ -16,6 +17,10 @@ class PluginRegistry {
16
17
  if (this.plugins.some((p) => p.name === plugin.name)) {
17
18
  throw new Error(`Plugin "${plugin.name}" is already registered`);
18
19
  }
20
+ const validation = (0, plugin_manifest_1.validatePlugin)(plugin);
21
+ if (!validation.valid && validation.errors.length > 0) {
22
+ throw new Error(`Plugin "${plugin.name}" failed validation: ${validation.errors.join('; ')}`);
23
+ }
19
24
  this.plugins.push(plugin);
20
25
  }
21
26
  unregister(name) {
@@ -1,12 +1,27 @@
1
1
  export type PolicyAction = 'allow' | 'deny' | 'require_human_gate';
2
+ export type SideEffectClass = 'read_fs' | 'write_fs' | 'spawn_process' | 'network_call' | 'git_mutation' | 'db_change' | 'secret_access' | 'infra_operation';
3
+ export type AutonomyTier = 'L0' | 'L1' | 'L2' | 'L3';
2
4
  export interface PolicyWhenClause {
3
5
  tool?: string;
4
6
  env?: string;
5
7
  kind?: string;
8
+ side_effect_class?: SideEffectClass;
9
+ autonomy_tier?: AutonomyTier;
6
10
  }
7
11
  export interface PolicyAssertClause {
8
12
  diff_within_scope?: boolean;
9
13
  }
14
+ export interface NodePolicyConfig {
15
+ max_retries: number;
16
+ mutation_budget?: number;
17
+ autonomy_tier?: AutonomyTier;
18
+ allowed_side_effects?: SideEffectClass[];
19
+ }
20
+ export interface EnvironmentGuardrail {
21
+ protected_paths: string[];
22
+ protected_branches: string[];
23
+ require_human_gate_on: SideEffectClass[];
24
+ }
10
25
  export interface PolicyRule {
11
26
  id: string;
12
27
  when: PolicyWhenClause;
@@ -19,6 +34,10 @@ export interface PolicyContext {
19
34
  kind?: string;
20
35
  mutation_scope?: string[];
21
36
  affected_paths?: string[];
37
+ side_effect_class?: SideEffectClass;
38
+ autonomy_tier?: AutonomyTier;
39
+ mutation_count?: number;
40
+ node_policy?: NodePolicyConfig;
22
41
  }
23
42
  export interface PolicyDecision {
24
43
  allowed: boolean;
@@ -28,13 +47,21 @@ export interface PolicyDecision {
28
47
  }
29
48
  export declare class PolicyEngine {
30
49
  private readonly rules;
31
- constructor(rules?: PolicyRule[]);
50
+ private readonly guardrail;
51
+ constructor(rules?: PolicyRule[], guardrail?: EnvironmentGuardrail);
32
52
  evaluate(ctx: PolicyContext): PolicyDecision;
53
+ private checkGuardrails;
54
+ private checkAutonomyTier;
55
+ private checkMutationBudget;
33
56
  private matches;
34
57
  private checkAssert;
35
58
  withRule(rule: PolicyRule): PolicyEngine;
59
+ withGuardrail(guardrail: EnvironmentGuardrail): PolicyEngine;
60
+ getGuardrail(): EnvironmentGuardrail;
36
61
  static fromConfig(config: {
37
62
  policies?: PolicyRule[];
63
+ guardrail?: EnvironmentGuardrail;
38
64
  }): PolicyEngine;
39
65
  static fromConfigFile(configPath: string): PolicyEngine;
66
+ static defaultGuardrail(): EnvironmentGuardrail;
40
67
  }
@@ -7,11 +7,37 @@ const ALLOW_ALL = {
7
7
  reason: 'no matching policy — default allow',
8
8
  rule_id: null,
9
9
  };
10
+ const DEFAULT_GUARDRAIL = {
11
+ protected_paths: ['.oxe/config.json', '.env', 'package.json'],
12
+ protected_branches: ['main', 'master', 'production', 'release'],
13
+ require_human_gate_on: ['infra_operation', 'db_change', 'secret_access'],
14
+ };
15
+ // Autonomy tier → max side effect class allowed without a gate
16
+ const TIER_SIDE_EFFECT_MAP = {
17
+ L0: ['read_fs'],
18
+ L1: ['read_fs', 'write_fs', 'spawn_process'],
19
+ L2: ['read_fs', 'write_fs', 'spawn_process', 'network_call', 'git_mutation'],
20
+ L3: ['read_fs', 'write_fs', 'spawn_process', 'network_call', 'git_mutation', 'db_change', 'secret_access', 'infra_operation'],
21
+ };
10
22
  class PolicyEngine {
11
- constructor(rules = []) {
23
+ constructor(rules = [], guardrail = DEFAULT_GUARDRAIL) {
12
24
  this.rules = rules;
25
+ this.guardrail = guardrail;
13
26
  }
14
27
  evaluate(ctx) {
28
+ // Check autonomy tier first — a denial takes priority over guardrail gates
29
+ const tierDecision = this.checkAutonomyTier(ctx);
30
+ if (tierDecision)
31
+ return tierDecision;
32
+ // Check environment guardrails (may require gate even when tier permits)
33
+ const guardrailDecision = this.checkGuardrails(ctx);
34
+ if (guardrailDecision)
35
+ return guardrailDecision;
36
+ // Check mutation budget
37
+ const budgetDecision = this.checkMutationBudget(ctx);
38
+ if (budgetDecision)
39
+ return budgetDecision;
40
+ // Evaluate rules (first match wins)
15
41
  for (const rule of this.rules) {
16
42
  if (!this.matches(rule.when, ctx))
17
43
  continue;
@@ -37,6 +63,59 @@ class PolicyEngine {
37
63
  }
38
64
  return ALLOW_ALL;
39
65
  }
66
+ checkGuardrails(ctx) {
67
+ // Protected path check
68
+ const affected = ctx.affected_paths ?? [];
69
+ for (const p of affected) {
70
+ if (this.guardrail.protected_paths.some((pp) => p === pp || p.startsWith(pp + '/'))) {
71
+ return {
72
+ allowed: true,
73
+ gate_required: true,
74
+ reason: `Protected path affected: ${p}`,
75
+ rule_id: '__guardrail_path',
76
+ };
77
+ }
78
+ }
79
+ // Side effect class requiring gate
80
+ if (ctx.side_effect_class && this.guardrail.require_human_gate_on.includes(ctx.side_effect_class)) {
81
+ return {
82
+ allowed: true,
83
+ gate_required: true,
84
+ reason: `Side effect class '${ctx.side_effect_class}' requires human gate`,
85
+ rule_id: '__guardrail_side_effect',
86
+ };
87
+ }
88
+ return null;
89
+ }
90
+ checkAutonomyTier(ctx) {
91
+ if (!ctx.autonomy_tier || !ctx.side_effect_class)
92
+ return null;
93
+ const allowed = TIER_SIDE_EFFECT_MAP[ctx.autonomy_tier] ?? [];
94
+ if (!allowed.includes(ctx.side_effect_class)) {
95
+ return {
96
+ allowed: false,
97
+ gate_required: false,
98
+ reason: `Autonomy tier ${ctx.autonomy_tier} does not permit side effect '${ctx.side_effect_class}'`,
99
+ rule_id: '__autonomy_tier',
100
+ };
101
+ }
102
+ return null;
103
+ }
104
+ checkMutationBudget(ctx) {
105
+ const budget = ctx.node_policy?.mutation_budget;
106
+ if (budget === undefined || budget === null)
107
+ return null;
108
+ const count = ctx.mutation_count ?? 0;
109
+ if (count >= budget) {
110
+ return {
111
+ allowed: false,
112
+ gate_required: false,
113
+ reason: `Mutation budget exhausted: ${count}/${budget}`,
114
+ rule_id: '__mutation_budget',
115
+ };
116
+ }
117
+ return null;
118
+ }
40
119
  matches(when, ctx) {
41
120
  if (when.tool && when.tool !== ctx.tool)
42
121
  return false;
@@ -44,6 +123,10 @@ class PolicyEngine {
44
123
  return false;
45
124
  if (when.kind && when.kind !== ctx.kind)
46
125
  return false;
126
+ if (when.side_effect_class && when.side_effect_class !== ctx.side_effect_class)
127
+ return false;
128
+ if (when.autonomy_tier && when.autonomy_tier !== ctx.autonomy_tier)
129
+ return false;
47
130
  return true;
48
131
  }
49
132
  checkAssert(assert, ctx) {
@@ -51,7 +134,7 @@ class PolicyEngine {
51
134
  const scope = ctx.mutation_scope ?? [];
52
135
  const affected = ctx.affected_paths ?? [];
53
136
  if (scope.length === 0)
54
- return null; // no scope declared — pass
137
+ return null;
55
138
  const outsideScope = affected.filter((p) => !scope.some((s) => p.startsWith(s) || s.startsWith(p)));
56
139
  if (outsideScope.length > 0) {
57
140
  return `paths outside mutation scope: ${outsideScope.join(', ')}`;
@@ -60,14 +143,19 @@ class PolicyEngine {
60
143
  return null;
61
144
  }
62
145
  withRule(rule) {
63
- return new PolicyEngine([...this.rules, rule]);
146
+ return new PolicyEngine([...this.rules, rule], this.guardrail);
147
+ }
148
+ withGuardrail(guardrail) {
149
+ return new PolicyEngine(this.rules, guardrail);
150
+ }
151
+ getGuardrail() {
152
+ return this.guardrail;
64
153
  }
65
154
  static fromConfig(config) {
66
- return new PolicyEngine(config.policies ?? []);
155
+ return new PolicyEngine(config.policies ?? [], config.guardrail ?? DEFAULT_GUARDRAIL);
67
156
  }
68
157
  static fromConfigFile(configPath) {
69
158
  try {
70
- // Dynamic require to avoid bundling issues
71
159
  // eslint-disable-next-line @typescript-eslint/no-var-requires
72
160
  const cfg = require(configPath);
73
161
  return PolicyEngine.fromConfig(cfg);
@@ -76,5 +164,8 @@ class PolicyEngine {
76
164
  return new PolicyEngine();
77
165
  }
78
166
  }
167
+ static defaultGuardrail() {
168
+ return { ...DEFAULT_GUARDRAIL };
169
+ }
79
170
  }
80
171
  exports.PolicyEngine = PolicyEngine;
@@ -3,6 +3,17 @@ import type { Run } from '../models/run';
3
3
  import type { WorkItem } from '../models/work-item';
4
4
  import type { Attempt } from '../models/attempt';
5
5
  import type { Workspace } from '../models/workspace';
6
+ export interface PolicyDecisionRecord {
7
+ allowed: boolean;
8
+ gate_required: boolean;
9
+ reason: string;
10
+ rule_id: string | null;
11
+ }
12
+ export interface ToolFailureRecord {
13
+ tool: string;
14
+ error: string;
15
+ timestamp: string;
16
+ }
6
17
  export interface RunState {
7
18
  run: Run | null;
8
19
  workItems: Map<string, WorkItem>;
@@ -11,6 +22,16 @@ export interface RunState {
11
22
  completedWorkItems: Set<string>;
12
23
  failedWorkItems: Set<string>;
13
24
  blockedWorkItems: Set<string>;
25
+ retryCounts: Map<string, number>;
26
+ policyDecisions: Map<string, PolicyDecisionRecord>;
27
+ pendingGates: Set<string>;
28
+ resolvedGates: Map<string, {
29
+ decision: string;
30
+ actor?: string;
31
+ }>;
32
+ verificationStatus: Map<string, 'started' | 'completed' | 'failed'>;
33
+ evidenceRefs: Map<string, string[]>;
34
+ toolFailures: Map<string, ToolFailureRecord[]>;
14
35
  }
15
36
  export declare function createEmptyRunState(): RunState;
16
37
  export declare function reduce(events: OxeEvent[]): RunState;
@@ -18,3 +39,8 @@ export { applyEvent as applyEventExported };
18
39
  declare function applyEvent(state: RunState, event: OxeEvent): RunState;
19
40
  export declare function getWorkItemStatus(state: RunState, workItemId: string): WorkItem['status'] | null;
20
41
  export declare function getAttemptCount(state: RunState, workItemId: string): number;
42
+ export declare function getRetryCount(state: RunState, workItemId: string): number;
43
+ export declare function getPolicyDecision(state: RunState, workItemId: string): PolicyDecisionRecord | null;
44
+ export declare function getVerificationStatus(state: RunState, workItemId: string): 'started' | 'completed' | 'failed' | null;
45
+ export declare function getEvidenceRefs(state: RunState, workItemId: string): string[];
46
+ export declare function getToolFailures(state: RunState, workItemId: string): ToolFailureRecord[];
@@ -5,6 +5,11 @@ exports.reduce = reduce;
5
5
  exports.applyEventExported = applyEvent;
6
6
  exports.getWorkItemStatus = getWorkItemStatus;
7
7
  exports.getAttemptCount = getAttemptCount;
8
+ exports.getRetryCount = getRetryCount;
9
+ exports.getPolicyDecision = getPolicyDecision;
10
+ exports.getVerificationStatus = getVerificationStatus;
11
+ exports.getEvidenceRefs = getEvidenceRefs;
12
+ exports.getToolFailures = getToolFailures;
8
13
  function createEmptyRunState() {
9
14
  return {
10
15
  run: null,
@@ -14,6 +19,13 @@ function createEmptyRunState() {
14
19
  completedWorkItems: new Set(),
15
20
  failedWorkItems: new Set(),
16
21
  blockedWorkItems: new Set(),
22
+ retryCounts: new Map(),
23
+ policyDecisions: new Map(),
24
+ pendingGates: new Set(),
25
+ resolvedGates: new Map(),
26
+ verificationStatus: new Map(),
27
+ evidenceRefs: new Map(),
28
+ toolFailures: new Map(),
17
29
  };
18
30
  }
19
31
  function reduce(events) {
@@ -43,7 +55,6 @@ function applyEvent(state, event) {
43
55
  workItems.set(event.work_item_id, { ...existing, status: 'ready' });
44
56
  }
45
57
  else {
46
- // First time we see this work item — create from payload
47
58
  const item = event.payload;
48
59
  workItems.set(event.work_item_id, { ...item, work_item_id: event.work_item_id, status: 'ready' });
49
60
  }
@@ -85,6 +96,14 @@ function applyEvent(state, event) {
85
96
  workItems.set(event.work_item_id, { ...item, status: 'completed' });
86
97
  const completedWorkItems = new Set(state.completedWorkItems);
87
98
  completedWorkItems.add(event.work_item_id);
99
+ // Collect evidence refs from payload
100
+ const evidence = event.payload.evidence ?? [];
101
+ if (evidence.length > 0) {
102
+ const evidenceRefs = new Map(state.evidenceRefs);
103
+ const existing = evidenceRefs.get(event.work_item_id) ?? [];
104
+ evidenceRefs.set(event.work_item_id, [...existing, ...evidence]);
105
+ return { ...state, workItems, completedWorkItems, evidenceRefs };
106
+ }
88
107
  return { ...state, workItems, completedWorkItems };
89
108
  }
90
109
  case 'WorkItemBlocked': {
@@ -98,6 +117,88 @@ function applyEvent(state, event) {
98
117
  blockedWorkItems.add(event.work_item_id);
99
118
  return { ...state, workItems, blockedWorkItems };
100
119
  }
120
+ case 'RetryScheduled': {
121
+ if (!event.work_item_id)
122
+ return state;
123
+ const retryCounts = new Map(state.retryCounts);
124
+ const current = retryCounts.get(event.work_item_id) ?? 0;
125
+ retryCounts.set(event.work_item_id, current + 1);
126
+ return { ...state, retryCounts };
127
+ }
128
+ case 'PolicyEvaluated': {
129
+ const p = event.payload;
130
+ const key = p.work_item_id ?? event.work_item_id;
131
+ if (!key)
132
+ return state;
133
+ const policyDecisions = new Map(state.policyDecisions);
134
+ policyDecisions.set(key, {
135
+ allowed: p.allowed ?? true,
136
+ gate_required: p.gate_required ?? false,
137
+ reason: p.reason ?? '',
138
+ rule_id: p.rule_id ?? null,
139
+ });
140
+ return { ...state, policyDecisions };
141
+ }
142
+ case 'GateRequested': {
143
+ const gateId = event.payload.gate_id;
144
+ if (!gateId)
145
+ return state;
146
+ const pendingGates = new Set(state.pendingGates);
147
+ pendingGates.add(gateId);
148
+ return { ...state, pendingGates };
149
+ }
150
+ case 'GateResolved': {
151
+ const p = event.payload;
152
+ if (!p.gate_id)
153
+ return state;
154
+ const pendingGates = new Set(state.pendingGates);
155
+ pendingGates.delete(p.gate_id);
156
+ const resolvedGates = new Map(state.resolvedGates);
157
+ resolvedGates.set(p.gate_id, { decision: p.decision ?? 'approved', actor: p.actor });
158
+ return { ...state, pendingGates, resolvedGates };
159
+ }
160
+ case 'VerificationStarted': {
161
+ const key = event.work_item_id ?? event.payload.work_item_id;
162
+ if (!key)
163
+ return state;
164
+ const verificationStatus = new Map(state.verificationStatus);
165
+ verificationStatus.set(key, 'started');
166
+ return { ...state, verificationStatus };
167
+ }
168
+ case 'VerificationCompleted': {
169
+ const p = event.payload;
170
+ const key = event.work_item_id ?? p.work_item_id;
171
+ if (!key)
172
+ return state;
173
+ const verificationStatus = new Map(state.verificationStatus);
174
+ verificationStatus.set(key, p.status ?? 'completed');
175
+ return { ...state, verificationStatus };
176
+ }
177
+ case 'ToolFailed': {
178
+ if (!event.work_item_id)
179
+ return state;
180
+ const p = event.payload;
181
+ const toolFailures = new Map(state.toolFailures);
182
+ const existing = toolFailures.get(event.work_item_id) ?? [];
183
+ toolFailures.set(event.work_item_id, [
184
+ ...existing,
185
+ { tool: p.tool ?? 'unknown', error: p.error ?? '', timestamp: event.timestamp },
186
+ ]);
187
+ return { ...state, toolFailures };
188
+ }
189
+ case 'EvidenceCollected': {
190
+ const p = event.payload;
191
+ const key = event.work_item_id ?? p.work_item_id;
192
+ if (!key)
193
+ return state;
194
+ const refs = p.refs ?? (p.ref ? [p.ref] : []);
195
+ if (refs.length === 0)
196
+ return state;
197
+ const evidenceRefs = new Map(state.evidenceRefs);
198
+ const existing = evidenceRefs.get(key) ?? [];
199
+ evidenceRefs.set(key, [...existing, ...refs]);
200
+ return { ...state, evidenceRefs };
201
+ }
101
202
  default:
102
203
  return state;
103
204
  }
@@ -108,3 +209,18 @@ function getWorkItemStatus(state, workItemId) {
108
209
  function getAttemptCount(state, workItemId) {
109
210
  return state.attempts.get(workItemId)?.length ?? 0;
110
211
  }
212
+ function getRetryCount(state, workItemId) {
213
+ return state.retryCounts.get(workItemId) ?? 0;
214
+ }
215
+ function getPolicyDecision(state, workItemId) {
216
+ return state.policyDecisions.get(workItemId) ?? null;
217
+ }
218
+ function getVerificationStatus(state, workItemId) {
219
+ return state.verificationStatus.get(workItemId) ?? null;
220
+ }
221
+ function getEvidenceRefs(state, workItemId) {
222
+ return state.evidenceRefs.get(workItemId) ?? [];
223
+ }
224
+ function getToolFailures(state, workItemId) {
225
+ return state.toolFailures.get(workItemId) ?? [];
226
+ }
@@ -0,0 +1,44 @@
1
+ import type { TaskExecutor } from './scheduler';
2
+ import type { WorkspaceManager } from '../workspace/workspace-manager';
3
+ import type { AgentRole, AgentActionLog } from './agent-roles';
4
+ export type AgentStatus = 'idle' | 'running' | 'paused' | 'failed' | 'timeout';
5
+ export interface AgentHeartbeat {
6
+ agent_id: string;
7
+ last_seen: string;
8
+ current_task: string | null;
9
+ status: AgentStatus;
10
+ }
11
+ export interface RegisteredAgent {
12
+ id: string;
13
+ executor: TaskExecutor;
14
+ workspaceManager: WorkspaceManager;
15
+ assignedTaskIds: string[];
16
+ heartbeat: AgentHeartbeat;
17
+ role?: AgentRole;
18
+ actionLog: AgentActionLog[];
19
+ }
20
+ export declare class AgentRegistry {
21
+ private agents;
22
+ private readonly heartbeatTimeoutMs;
23
+ constructor(heartbeatTimeoutMs?: number);
24
+ register(id: string, executor: TaskExecutor, workspaceManager: WorkspaceManager, assignedTaskIds?: string[], role?: AgentRole): RegisteredAgent;
25
+ unregister(id: string): void;
26
+ beat(id: string, currentTask?: string | null): void;
27
+ setStatus(id: string, status: AgentStatus): void;
28
+ isAlive(id: string): boolean;
29
+ /** Returns agents that haven't sent a heartbeat within the timeout window */
30
+ timedOut(): RegisteredAgent[];
31
+ liveAgents(): RegisteredAgent[];
32
+ get(id: string): RegisteredAgent | null;
33
+ list(): RegisteredAgent[];
34
+ /**
35
+ * Reassign orphaned tasks from timed-out agents to a fallback agent.
36
+ * Returns the list of task IDs that were reassigned.
37
+ */
38
+ failover(fallbackAgentId: string): string[];
39
+ /** Return all agents assigned to a given role */
40
+ getByRole(role: AgentRole): RegisteredAgent[];
41
+ /** Append an action log entry for a registered agent (no-op if unknown) */
42
+ logAction(agentId: string, log: AgentActionLog): void;
43
+ clear(): void;
44
+ }