@renseiai/agentfactory 0.8.19 → 0.8.21
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.
- package/dist/src/config/repository-config.d.ts +7 -0
- package/dist/src/config/repository-config.d.ts.map +1 -1
- package/dist/src/config/repository-config.js +15 -1
- package/dist/src/config/repository-config.test.js +1 -1
- package/dist/src/governor/decision-engine-adapter.js +5 -10
- package/dist/src/governor/decision-engine-adapter.test.js +13 -14
- package/dist/src/governor/decision-engine.js +3 -7
- package/dist/src/governor/decision-engine.test.js +5 -5
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/merge-queue/adapters/local.d.ts +68 -0
- package/dist/src/merge-queue/adapters/local.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/local.js +136 -0
- package/dist/src/merge-queue/adapters/local.test.d.ts +2 -0
- package/dist/src/merge-queue/adapters/local.test.d.ts.map +1 -0
- package/dist/src/merge-queue/adapters/local.test.js +176 -0
- package/dist/src/merge-queue/index.d.ts +13 -5
- package/dist/src/merge-queue/index.d.ts.map +1 -1
- package/dist/src/merge-queue/index.js +13 -6
- package/dist/src/merge-queue/merge-queue.integration.test.js +19 -0
- package/dist/src/merge-queue/merge-worker.d.ts.map +1 -1
- package/dist/src/merge-queue/merge-worker.js +29 -0
- package/dist/src/merge-queue/types.d.ts +1 -1
- package/dist/src/merge-queue/types.d.ts.map +1 -1
- package/dist/src/orchestrator/index.d.ts +4 -0
- package/dist/src/orchestrator/index.d.ts.map +1 -1
- package/dist/src/orchestrator/index.js +3 -0
- package/dist/src/orchestrator/orchestrator.d.ts +31 -0
- package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/src/orchestrator/orchestrator.js +263 -11
- package/dist/src/orchestrator/parse-work-result.d.ts.map +1 -1
- package/dist/src/orchestrator/parse-work-result.js +3 -1
- package/dist/src/orchestrator/parse-work-result.test.js +6 -0
- package/dist/src/orchestrator/quality-baseline.d.ts +83 -0
- package/dist/src/orchestrator/quality-baseline.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-baseline.js +313 -0
- package/dist/src/orchestrator/quality-baseline.test.d.ts +2 -0
- package/dist/src/orchestrator/quality-baseline.test.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-baseline.test.js +448 -0
- package/dist/src/orchestrator/quality-ratchet.d.ts +70 -0
- package/dist/src/orchestrator/quality-ratchet.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-ratchet.js +162 -0
- package/dist/src/orchestrator/quality-ratchet.test.d.ts +2 -0
- package/dist/src/orchestrator/quality-ratchet.test.d.ts.map +1 -0
- package/dist/src/orchestrator/quality-ratchet.test.js +335 -0
- package/dist/src/orchestrator/types.d.ts +2 -0
- package/dist/src/orchestrator/types.d.ts.map +1 -1
- package/dist/src/providers/codex-app-server-provider.d.ts +37 -1
- package/dist/src/providers/codex-app-server-provider.d.ts.map +1 -1
- package/dist/src/providers/codex-app-server-provider.js +290 -35
- package/dist/src/providers/codex-app-server-provider.test.js +72 -12
- package/dist/src/providers/codex-approval-bridge.d.ts +49 -0
- package/dist/src/providers/codex-approval-bridge.d.ts.map +1 -0
- package/dist/src/providers/codex-approval-bridge.js +117 -0
- package/dist/src/providers/codex-approval-bridge.test.d.ts +2 -0
- package/dist/src/providers/codex-approval-bridge.test.d.ts.map +1 -0
- package/dist/src/providers/codex-approval-bridge.test.js +188 -0
- package/dist/src/providers/types.d.ts +25 -0
- package/dist/src/providers/types.d.ts.map +1 -1
- package/dist/src/routing/types.d.ts +1 -1
- package/dist/src/templates/adapters.d.ts +25 -0
- package/dist/src/templates/adapters.d.ts.map +1 -1
- package/dist/src/templates/adapters.js +70 -0
- package/dist/src/templates/adapters.test.js +49 -0
- package/dist/src/templates/index.d.ts +1 -0
- package/dist/src/templates/index.d.ts.map +1 -1
- package/dist/src/templates/registry.d.ts +8 -0
- package/dist/src/templates/registry.d.ts.map +1 -1
- package/dist/src/templates/registry.js +11 -0
- package/dist/src/templates/types.d.ts +22 -0
- package/dist/src/templates/types.d.ts.map +1 -1
- package/dist/src/templates/types.js +12 -0
- package/dist/src/tools/index.d.ts +2 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +1 -0
- package/dist/src/tools/registry.d.ts +9 -1
- package/dist/src/tools/registry.d.ts.map +1 -1
- package/dist/src/tools/registry.js +13 -1
- package/dist/src/tools/stdio-server-entry.d.ts +25 -0
- package/dist/src/tools/stdio-server-entry.d.ts.map +1 -0
- package/dist/src/tools/stdio-server-entry.js +205 -0
- package/dist/src/tools/stdio-server.d.ts +87 -0
- package/dist/src/tools/stdio-server.d.ts.map +1 -0
- package/dist/src/tools/stdio-server.js +138 -0
- package/dist/src/workflow/workflow-types.d.ts +3 -3
- package/package.json +3 -2
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex Approval Bridge (SUP-1747)
|
|
3
|
+
*
|
|
4
|
+
* Evaluates Codex App Server `requestApproval` events against deny patterns
|
|
5
|
+
* ported from Claude's `autonomousCanUseTool` callback (claude-provider.ts:33-112).
|
|
6
|
+
*
|
|
7
|
+
* When the Codex App Server's `approvalPolicy` is set to `'onRequest'`, every
|
|
8
|
+
* tool execution flows through this bridge. The bridge auto-approves safe commands
|
|
9
|
+
* and declines destructive patterns — giving Codex the same safety guardrails as Claude
|
|
10
|
+
* without requiring human interaction.
|
|
11
|
+
*
|
|
12
|
+
* Architecture:
|
|
13
|
+
* App Server emits → requestApproval notification
|
|
14
|
+
* Approval Bridge evaluates → deny patterns + permission config
|
|
15
|
+
* Bridge responds → accept | decline | acceptForSession
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Hardcoded safety deny patterns — always active regardless of template config.
|
|
19
|
+
* These mirror the deny-list in Claude's `autonomousCanUseTool` callback.
|
|
20
|
+
*/
|
|
21
|
+
export const SAFETY_DENY_PATTERNS = [
|
|
22
|
+
// 1. rm of filesystem root
|
|
23
|
+
{ pattern: /rm\s+(-[a-z]*f[a-z]*\s+)?\/\s*$/, reason: 'rm of filesystem root blocked' },
|
|
24
|
+
// 2. worktree remove/prune — orchestrator manages worktree lifecycle
|
|
25
|
+
{ pattern: /git\s+worktree\s+(remove|prune)/, reason: 'worktree remove/prune blocked per project rules' },
|
|
26
|
+
// 3. hard reset
|
|
27
|
+
{ pattern: /git\s+reset\s+--hard/, reason: 'reset --hard blocked' },
|
|
28
|
+
// 4. force push (evaluated separately for --force-with-lease exception)
|
|
29
|
+
// Handled in evaluateCommandApproval directly
|
|
30
|
+
// 5. branch switching — agents must not change the checked-out branch
|
|
31
|
+
{ pattern: /git\s+(checkout|switch)\b/, reason: 'git checkout/switch blocked — agents must not change the checked-out branch' },
|
|
32
|
+
];
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Command Approval
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
/**
|
|
37
|
+
* Evaluate a shell command against safety deny patterns and optional
|
|
38
|
+
* template-level permission patterns.
|
|
39
|
+
*
|
|
40
|
+
* Evaluation order:
|
|
41
|
+
* 1. Safety deny patterns (always checked first, cannot be overridden)
|
|
42
|
+
* 2. Template deny patterns (from `tools.disallow`)
|
|
43
|
+
* 3. Template allow patterns (from `tools.allow`, if present)
|
|
44
|
+
* 4. Default: acceptForSession
|
|
45
|
+
*/
|
|
46
|
+
export function evaluateCommandApproval(command, permissionConfig) {
|
|
47
|
+
const cmd = command.trim();
|
|
48
|
+
if (!cmd)
|
|
49
|
+
return { action: 'acceptForSession' };
|
|
50
|
+
// --- Safety deny patterns (always enforced) ---
|
|
51
|
+
// Check basic deny patterns
|
|
52
|
+
for (const { pattern, reason } of SAFETY_DENY_PATTERNS) {
|
|
53
|
+
if (pattern.test(cmd)) {
|
|
54
|
+
return { action: 'decline', reason };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Force push — special handling for --force-with-lease exception
|
|
58
|
+
if (/git\s+push\b/.test(cmd) && /(--force\b|-f\b)/.test(cmd)) {
|
|
59
|
+
if (/--force-with-lease/.test(cmd)) {
|
|
60
|
+
// --force-with-lease to main/master is still blocked
|
|
61
|
+
if (/\b(main|master)\b/.test(cmd)) {
|
|
62
|
+
return { action: 'decline', reason: 'force push to main/master blocked' };
|
|
63
|
+
}
|
|
64
|
+
// --force-with-lease on feature branches is allowed
|
|
65
|
+
return { action: 'acceptForSession', reason: '--force-with-lease on feature branch allowed' };
|
|
66
|
+
}
|
|
67
|
+
return { action: 'decline', reason: 'force push blocked — use --force-with-lease for safety' };
|
|
68
|
+
}
|
|
69
|
+
// --- Template deny patterns (from tools.disallow) ---
|
|
70
|
+
if (permissionConfig?.deniedCommandPatterns) {
|
|
71
|
+
for (const { pattern, reason } of permissionConfig.deniedCommandPatterns) {
|
|
72
|
+
if (pattern.test(cmd)) {
|
|
73
|
+
return { action: 'decline', reason };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// --- Template allow patterns (from tools.allow) ---
|
|
78
|
+
// If allow patterns are defined, only matching commands are accepted.
|
|
79
|
+
// Commands that don't match any allow pattern are declined.
|
|
80
|
+
if (permissionConfig?.allowedCommandPatterns && permissionConfig.allowedCommandPatterns.length > 0) {
|
|
81
|
+
for (const pattern of permissionConfig.allowedCommandPatterns) {
|
|
82
|
+
if (pattern.test(cmd)) {
|
|
83
|
+
return { action: 'acceptForSession' };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { action: 'decline', reason: 'command not in allowed list' };
|
|
87
|
+
}
|
|
88
|
+
// Default: accept for session (auto-approve for remainder of session)
|
|
89
|
+
return { action: 'acceptForSession' };
|
|
90
|
+
}
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
// File Change Approval
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
/**
|
|
95
|
+
* Evaluate a file change (write/edit) against safety rules and optional
|
|
96
|
+
* template-level permissions.
|
|
97
|
+
*/
|
|
98
|
+
export function evaluateFileChangeApproval(filePath, cwd, permissionConfig) {
|
|
99
|
+
// Block writes outside worktree root
|
|
100
|
+
if (!filePath.startsWith(cwd)) {
|
|
101
|
+
return { action: 'decline', reason: 'file change outside worktree blocked' };
|
|
102
|
+
}
|
|
103
|
+
// Block .git directory modifications
|
|
104
|
+
if (/\/\.git(\/|$)/.test(filePath)) {
|
|
105
|
+
return { action: 'decline', reason: '.git directory modification blocked' };
|
|
106
|
+
}
|
|
107
|
+
// Template-level file permission restrictions
|
|
108
|
+
if (permissionConfig) {
|
|
109
|
+
if (!permissionConfig.allowFileEdits && filePath !== cwd) {
|
|
110
|
+
return { action: 'decline', reason: 'file edits blocked by template' };
|
|
111
|
+
}
|
|
112
|
+
if (!permissionConfig.allowFileWrites && filePath !== cwd) {
|
|
113
|
+
return { action: 'decline', reason: 'file writes blocked by template' };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return { action: 'acceptForSession' };
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-approval-bridge.test.d.ts","sourceRoot":"","sources":["../../../src/providers/codex-approval-bridge.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { evaluateCommandApproval, evaluateFileChangeApproval, SAFETY_DENY_PATTERNS, } from './codex-approval-bridge.js';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// evaluateCommandApproval — Safety Deny Patterns
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
describe('evaluateCommandApproval', () => {
|
|
7
|
+
describe('safety deny patterns (always enforced)', () => {
|
|
8
|
+
it('blocks rm of filesystem root', () => {
|
|
9
|
+
const result = evaluateCommandApproval('rm -rf / ');
|
|
10
|
+
expect(result.action).toBe('decline');
|
|
11
|
+
expect(result.reason).toContain('rm of filesystem root');
|
|
12
|
+
});
|
|
13
|
+
it('blocks rm -f /', () => {
|
|
14
|
+
const result = evaluateCommandApproval('rm -f / ');
|
|
15
|
+
expect(result.action).toBe('decline');
|
|
16
|
+
});
|
|
17
|
+
it('allows rm of normal directories', () => {
|
|
18
|
+
const result = evaluateCommandApproval('rm -rf /tmp/test');
|
|
19
|
+
expect(result.action).toBe('acceptForSession');
|
|
20
|
+
});
|
|
21
|
+
it('blocks git worktree remove', () => {
|
|
22
|
+
const result = evaluateCommandApproval('git worktree remove /path/to/worktree');
|
|
23
|
+
expect(result.action).toBe('decline');
|
|
24
|
+
expect(result.reason).toContain('worktree remove/prune');
|
|
25
|
+
});
|
|
26
|
+
it('blocks git worktree prune', () => {
|
|
27
|
+
const result = evaluateCommandApproval('git worktree prune');
|
|
28
|
+
expect(result.action).toBe('decline');
|
|
29
|
+
expect(result.reason).toContain('worktree remove/prune');
|
|
30
|
+
});
|
|
31
|
+
it('allows git worktree add', () => {
|
|
32
|
+
const result = evaluateCommandApproval('git worktree add /path/to/worktree');
|
|
33
|
+
expect(result.action).toBe('acceptForSession');
|
|
34
|
+
});
|
|
35
|
+
it('blocks git reset --hard', () => {
|
|
36
|
+
const result = evaluateCommandApproval('git reset --hard HEAD');
|
|
37
|
+
expect(result.action).toBe('decline');
|
|
38
|
+
expect(result.reason).toContain('reset --hard');
|
|
39
|
+
});
|
|
40
|
+
it('allows git reset --soft', () => {
|
|
41
|
+
const result = evaluateCommandApproval('git reset --soft HEAD~1');
|
|
42
|
+
expect(result.action).toBe('acceptForSession');
|
|
43
|
+
});
|
|
44
|
+
it('blocks git push --force', () => {
|
|
45
|
+
const result = evaluateCommandApproval('git push --force origin feature');
|
|
46
|
+
expect(result.action).toBe('decline');
|
|
47
|
+
expect(result.reason).toContain('force push blocked');
|
|
48
|
+
});
|
|
49
|
+
it('blocks git push -f', () => {
|
|
50
|
+
const result = evaluateCommandApproval('git push -f origin feature');
|
|
51
|
+
expect(result.action).toBe('decline');
|
|
52
|
+
});
|
|
53
|
+
it('allows --force-with-lease on feature branches', () => {
|
|
54
|
+
const result = evaluateCommandApproval('git push --force-with-lease origin feature-branch');
|
|
55
|
+
expect(result.action).toBe('acceptForSession');
|
|
56
|
+
});
|
|
57
|
+
it('blocks --force-with-lease on main', () => {
|
|
58
|
+
const result = evaluateCommandApproval('git push --force-with-lease origin main');
|
|
59
|
+
expect(result.action).toBe('decline');
|
|
60
|
+
expect(result.reason).toContain('main/master');
|
|
61
|
+
});
|
|
62
|
+
it('blocks --force-with-lease on master', () => {
|
|
63
|
+
const result = evaluateCommandApproval('git push --force-with-lease origin master');
|
|
64
|
+
expect(result.action).toBe('decline');
|
|
65
|
+
});
|
|
66
|
+
it('blocks git checkout', () => {
|
|
67
|
+
const result = evaluateCommandApproval('git checkout main');
|
|
68
|
+
expect(result.action).toBe('decline');
|
|
69
|
+
expect(result.reason).toContain('checkout/switch blocked');
|
|
70
|
+
});
|
|
71
|
+
it('blocks git switch', () => {
|
|
72
|
+
const result = evaluateCommandApproval('git switch develop');
|
|
73
|
+
expect(result.action).toBe('decline');
|
|
74
|
+
expect(result.reason).toContain('checkout/switch blocked');
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
describe('safe commands (default accept)', () => {
|
|
78
|
+
it('accepts empty command', () => {
|
|
79
|
+
const result = evaluateCommandApproval('');
|
|
80
|
+
expect(result.action).toBe('acceptForSession');
|
|
81
|
+
});
|
|
82
|
+
it('accepts pnpm install', () => {
|
|
83
|
+
const result = evaluateCommandApproval('pnpm install');
|
|
84
|
+
expect(result.action).toBe('acceptForSession');
|
|
85
|
+
});
|
|
86
|
+
it('accepts git commit', () => {
|
|
87
|
+
const result = evaluateCommandApproval('git commit -m "fix: something"');
|
|
88
|
+
expect(result.action).toBe('acceptForSession');
|
|
89
|
+
});
|
|
90
|
+
it('accepts git push (no force)', () => {
|
|
91
|
+
const result = evaluateCommandApproval('git push origin feature-branch');
|
|
92
|
+
expect(result.action).toBe('acceptForSession');
|
|
93
|
+
});
|
|
94
|
+
it('accepts ls, cat, grep', () => {
|
|
95
|
+
expect(evaluateCommandApproval('ls -la').action).toBe('acceptForSession');
|
|
96
|
+
expect(evaluateCommandApproval('cat file.txt').action).toBe('acceptForSession');
|
|
97
|
+
expect(evaluateCommandApproval('grep -r pattern .').action).toBe('acceptForSession');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
describe('template permission patterns', () => {
|
|
101
|
+
const permissionConfig = {
|
|
102
|
+
allowedCommandPatterns: [/^pnpm\b/, /^git\s+commit\b/, /^git\s+push\b/],
|
|
103
|
+
deniedCommandPatterns: [
|
|
104
|
+
{ pattern: /^npm\b/, reason: 'npm blocked by template' },
|
|
105
|
+
],
|
|
106
|
+
allowFileEdits: true,
|
|
107
|
+
allowFileWrites: true,
|
|
108
|
+
};
|
|
109
|
+
it('declines commands matching template deny patterns', () => {
|
|
110
|
+
const result = evaluateCommandApproval('npm install', permissionConfig);
|
|
111
|
+
expect(result.action).toBe('decline');
|
|
112
|
+
expect(result.reason).toContain('npm blocked by template');
|
|
113
|
+
});
|
|
114
|
+
it('accepts commands matching template allow patterns', () => {
|
|
115
|
+
const result = evaluateCommandApproval('pnpm install', permissionConfig);
|
|
116
|
+
expect(result.action).toBe('acceptForSession');
|
|
117
|
+
});
|
|
118
|
+
it('declines commands not in allow list when allow patterns are defined', () => {
|
|
119
|
+
const result = evaluateCommandApproval('cargo build', permissionConfig);
|
|
120
|
+
expect(result.action).toBe('decline');
|
|
121
|
+
expect(result.reason).toContain('not in allowed list');
|
|
122
|
+
});
|
|
123
|
+
it('safety patterns take precedence over template allow patterns', () => {
|
|
124
|
+
// git checkout is allowed by template (matches git push pattern? no)
|
|
125
|
+
// but blocked by safety
|
|
126
|
+
const result = evaluateCommandApproval('git checkout main', permissionConfig);
|
|
127
|
+
expect(result.action).toBe('decline');
|
|
128
|
+
expect(result.reason).toContain('checkout/switch blocked');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
it('has the expected number of safety deny patterns', () => {
|
|
132
|
+
// 4 patterns in the array + force push handled separately = 5 total deny cases
|
|
133
|
+
expect(SAFETY_DENY_PATTERNS.length).toBe(4);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
// evaluateFileChangeApproval
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
describe('evaluateFileChangeApproval', () => {
|
|
140
|
+
const cwd = '/home/user/worktree';
|
|
141
|
+
it('accepts file changes within worktree', () => {
|
|
142
|
+
const result = evaluateFileChangeApproval('/home/user/worktree/src/index.ts', cwd);
|
|
143
|
+
expect(result.action).toBe('acceptForSession');
|
|
144
|
+
});
|
|
145
|
+
it('blocks file changes outside worktree', () => {
|
|
146
|
+
const result = evaluateFileChangeApproval('/etc/passwd', cwd);
|
|
147
|
+
expect(result.action).toBe('decline');
|
|
148
|
+
expect(result.reason).toContain('outside worktree');
|
|
149
|
+
});
|
|
150
|
+
it('blocks .git directory modifications', () => {
|
|
151
|
+
const result = evaluateFileChangeApproval('/home/user/worktree/.git/config', cwd);
|
|
152
|
+
expect(result.action).toBe('decline');
|
|
153
|
+
expect(result.reason).toContain('.git directory');
|
|
154
|
+
});
|
|
155
|
+
it('blocks .git file at root', () => {
|
|
156
|
+
const result = evaluateFileChangeApproval('/home/user/worktree/.git', cwd);
|
|
157
|
+
expect(result.action).toBe('decline');
|
|
158
|
+
expect(result.reason).toContain('.git directory');
|
|
159
|
+
});
|
|
160
|
+
it('allows files with .git in the name but not .git directory', () => {
|
|
161
|
+
const result = evaluateFileChangeApproval('/home/user/worktree/.gitignore', cwd);
|
|
162
|
+
expect(result.action).toBe('acceptForSession');
|
|
163
|
+
});
|
|
164
|
+
describe('template permission restrictions', () => {
|
|
165
|
+
it('blocks file edits when template disallows', () => {
|
|
166
|
+
const config = {
|
|
167
|
+
allowedCommandPatterns: [],
|
|
168
|
+
deniedCommandPatterns: [],
|
|
169
|
+
allowFileEdits: false,
|
|
170
|
+
allowFileWrites: true,
|
|
171
|
+
};
|
|
172
|
+
const result = evaluateFileChangeApproval('/home/user/worktree/src/index.ts', cwd, config);
|
|
173
|
+
expect(result.action).toBe('decline');
|
|
174
|
+
expect(result.reason).toContain('file edits blocked');
|
|
175
|
+
});
|
|
176
|
+
it('blocks file writes when template disallows', () => {
|
|
177
|
+
const config = {
|
|
178
|
+
allowedCommandPatterns: [],
|
|
179
|
+
deniedCommandPatterns: [],
|
|
180
|
+
allowFileEdits: true,
|
|
181
|
+
allowFileWrites: false,
|
|
182
|
+
};
|
|
183
|
+
const result = evaluateFileChangeApproval('/home/user/worktree/src/index.ts', cwd, config);
|
|
184
|
+
expect(result.action).toBe('decline');
|
|
185
|
+
expect(result.reason).toContain('file writes blocked');
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
@@ -88,6 +88,31 @@ export interface AgentSpawnConfig {
|
|
|
88
88
|
* When omitted, the provider's default applies.
|
|
89
89
|
*/
|
|
90
90
|
maxTurns?: number;
|
|
91
|
+
/**
|
|
92
|
+
* Stdio MCP server configurations for Codex provider (SUP-1744).
|
|
93
|
+
* Created by ToolRegistry.createStdioServerConfigs() from registered plugins.
|
|
94
|
+
* Passed to Codex app-server via config/batchWrite so it can spawn and
|
|
95
|
+
* connect to these tool servers.
|
|
96
|
+
*/
|
|
97
|
+
mcpStdioServers?: Array<{
|
|
98
|
+
name: string;
|
|
99
|
+
command: string;
|
|
100
|
+
args: string[];
|
|
101
|
+
env?: Record<string, string>;
|
|
102
|
+
}>;
|
|
103
|
+
/**
|
|
104
|
+
* Persistent system instructions for Codex App Server (SUP-1746).
|
|
105
|
+
* Passed via `instructions` on `thread/start`. Contains safety rules,
|
|
106
|
+
* project instructions (AGENTS.md), and work-type context.
|
|
107
|
+
* Separate from `prompt` which contains only the task-specific directive.
|
|
108
|
+
*/
|
|
109
|
+
baseInstructions?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Structured permission config for Codex approval bridge (SUP-1748).
|
|
112
|
+
* Translates template `tools.allow` / `tools.disallow` into patterns
|
|
113
|
+
* consumed by the approval bridge for runtime tool evaluation.
|
|
114
|
+
*/
|
|
115
|
+
permissionConfig?: import('../templates/adapters.js').CodexPermissionConfig;
|
|
91
116
|
}
|
|
92
117
|
/**
|
|
93
118
|
* Handle to a running agent session.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,qCAAqC;AACrC,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,OAAO,GAAG,KAAK,GAAG,WAAW,GAAG,KAAK,CAAA;AAEhF;;;;;GAKG;AACH;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAAyB;IACxC,sEAAsE;IACtE,wBAAwB,EAAE,OAAO,CAAA;IACjC,oDAAoD;IACpD,qBAAqB,EAAE,OAAO,CAAA;CAC/B;AAED,MAAM,WAAW,aAAa;IAC5B,0BAA0B;IAC1B,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAA;IAEhC,yDAAyD;IACzD,QAAQ,CAAC,YAAY,EAAE,yBAAyB,CAAA;IAEhD,gCAAgC;IAChC,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,WAAW,CAAA;IAE5C,8CAA8C;IAC9C,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,WAAW,CAAA;CACjE;AAED;;;GAGG;AACH,MAAM,WAAW,gBAAgB;IAC/B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAA;IACX,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3B,+CAA+C;IAC/C,eAAe,EAAE,eAAe,CAAA;IAChC,4DAA4D;IAC5D,UAAU,EAAE,OAAO,CAAA;IACnB,wDAAwD;IACxD,cAAc,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAA;IACpD;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,gCAAgC,EAAE,eAAe,CAAC,CAAA;IACrF;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/providers/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -49,8 +49,8 @@ export declare const RoutingObservationSchema: z.ZodObject<{
|
|
|
49
49
|
prCreated: z.ZodBoolean;
|
|
50
50
|
qaResult: z.ZodEnum<{
|
|
51
51
|
failed: "failed";
|
|
52
|
-
passed: "passed";
|
|
53
52
|
unknown: "unknown";
|
|
53
|
+
passed: "passed";
|
|
54
54
|
}>;
|
|
55
55
|
totalCostUsd: z.ZodNumber;
|
|
56
56
|
wallClockMs: z.ZodNumber;
|
|
@@ -5,6 +5,23 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { ToolPermission, ToolPermissionAdapter } from './types.js';
|
|
7
7
|
import type { AgentProviderName } from '../providers/types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Structured permission config consumed by the Codex approval bridge.
|
|
10
|
+
* Produced by `CodexToolPermissionAdapter.buildPermissionConfig()`.
|
|
11
|
+
*/
|
|
12
|
+
export interface CodexPermissionConfig {
|
|
13
|
+
/** Allowed command patterns from `tools.allow` */
|
|
14
|
+
allowedCommandPatterns: RegExp[];
|
|
15
|
+
/** Denied command patterns from `tools.disallow` (additive to safety defaults) */
|
|
16
|
+
deniedCommandPatterns: Array<{
|
|
17
|
+
pattern: RegExp;
|
|
18
|
+
reason: string;
|
|
19
|
+
}>;
|
|
20
|
+
/** Whether file edits are allowed (default: true) */
|
|
21
|
+
allowFileEdits: boolean;
|
|
22
|
+
/** Whether file writes are allowed (default: true) */
|
|
23
|
+
allowFileWrites: boolean;
|
|
24
|
+
}
|
|
8
25
|
/**
|
|
9
26
|
* Claude Code tool permission adapter.
|
|
10
27
|
*
|
|
@@ -30,6 +47,14 @@ export declare class ClaudeToolPermissionAdapter implements ToolPermissionAdapte
|
|
|
30
47
|
*/
|
|
31
48
|
export declare class CodexToolPermissionAdapter implements ToolPermissionAdapter {
|
|
32
49
|
translatePermissions(permissions: ToolPermission[]): string[];
|
|
50
|
+
/**
|
|
51
|
+
* Build a structured permission config for the Codex approval bridge (SUP-1748).
|
|
52
|
+
*
|
|
53
|
+
* Translates abstract `tools.allow` and `tools.disallow` from templates
|
|
54
|
+
* into regex patterns consumed by `evaluateCommandApproval()` and
|
|
55
|
+
* `evaluateFileChangeApproval()` in the approval bridge.
|
|
56
|
+
*/
|
|
57
|
+
buildPermissionConfig(allow: ToolPermission[], disallow: ToolPermission[]): CodexPermissionConfig;
|
|
33
58
|
private translateOne;
|
|
34
59
|
}
|
|
35
60
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/templates/adapters.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../../../src/templates/adapters.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AAM9D;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,kDAAkD;IAClD,sBAAsB,EAAE,MAAM,EAAE,CAAA;IAChC,kFAAkF;IAClF,qBAAqB,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACjE,qDAAqD;IACrD,cAAc,EAAE,OAAO,CAAA;IACvB,sDAAsD;IACtD,eAAe,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;GAOG;AACH,qBAAa,2BAA4B,YAAW,qBAAqB;IACvE,oBAAoB,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,MAAM,EAAE;IAI7D,OAAO,CAAC,YAAY;CAwBrB;AAED;;;;;;;;;;GAUG;AACH,qBAAa,0BAA2B,YAAW,qBAAqB;IACtE,oBAAoB,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,MAAM,EAAE;IAI7D;;;;;;OAMG;IACH,qBAAqB,CACnB,KAAK,EAAE,cAAc,EAAE,EACvB,QAAQ,EAAE,cAAc,EAAE,GACzB,qBAAqB;IAsCxB,OAAO,CAAC,YAAY;CAWrB;AAkCD;;;;;;;GAOG;AACH,qBAAa,6BAA8B,YAAW,qBAAqB;IACzE,oBAAoB,CAAC,WAAW,EAAE,cAAc,EAAE,GAAG,MAAM,EAAE;IAI7D,OAAO,CAAC,YAAY;CAWrB;AAED;;GAEG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,iBAAiB,GAAG,qBAAqB,CAa9F"}
|
|
@@ -53,6 +53,49 @@ export class CodexToolPermissionAdapter {
|
|
|
53
53
|
translatePermissions(permissions) {
|
|
54
54
|
return permissions.map(p => this.translateOne(p));
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Build a structured permission config for the Codex approval bridge (SUP-1748).
|
|
58
|
+
*
|
|
59
|
+
* Translates abstract `tools.allow` and `tools.disallow` from templates
|
|
60
|
+
* into regex patterns consumed by `evaluateCommandApproval()` and
|
|
61
|
+
* `evaluateFileChangeApproval()` in the approval bridge.
|
|
62
|
+
*/
|
|
63
|
+
buildPermissionConfig(allow, disallow) {
|
|
64
|
+
const allowedCommandPatterns = [];
|
|
65
|
+
const deniedCommandPatterns = [];
|
|
66
|
+
let allowFileEdits = true;
|
|
67
|
+
let allowFileWrites = true;
|
|
68
|
+
// Process allow list → allowed command patterns
|
|
69
|
+
for (const permission of allow) {
|
|
70
|
+
if (typeof permission !== 'string' && 'shell' in permission) {
|
|
71
|
+
allowedCommandPatterns.push(shellGlobToRegex(permission.shell));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Process disallow list → denied patterns + file permission flags
|
|
75
|
+
for (const permission of disallow) {
|
|
76
|
+
if (typeof permission === 'string') {
|
|
77
|
+
if (permission === 'file-edit') {
|
|
78
|
+
allowFileEdits = false;
|
|
79
|
+
}
|
|
80
|
+
else if (permission === 'file-write') {
|
|
81
|
+
allowFileWrites = false;
|
|
82
|
+
}
|
|
83
|
+
// 'user-input' is a no-op for Codex (non-interactive)
|
|
84
|
+
}
|
|
85
|
+
else if ('shell' in permission) {
|
|
86
|
+
deniedCommandPatterns.push({
|
|
87
|
+
pattern: shellGlobToRegex(permission.shell),
|
|
88
|
+
reason: `${permission.shell} blocked by template`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
allowedCommandPatterns,
|
|
94
|
+
deniedCommandPatterns,
|
|
95
|
+
allowFileEdits,
|
|
96
|
+
allowFileWrites,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
56
99
|
translateOne(permission) {
|
|
57
100
|
if (typeof permission === 'string') {
|
|
58
101
|
return permission;
|
|
@@ -63,6 +106,33 @@ export class CodexToolPermissionAdapter {
|
|
|
63
106
|
return String(permission);
|
|
64
107
|
}
|
|
65
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Convert a shell glob pattern (e.g., "pnpm *", "git commit *") to a regex.
|
|
111
|
+
*
|
|
112
|
+
* The pattern "pnpm *" matches any command starting with "pnpm".
|
|
113
|
+
* The pattern "git commit *" matches "git commit" followed by anything.
|
|
114
|
+
*/
|
|
115
|
+
function shellGlobToRegex(glob) {
|
|
116
|
+
// Split on the last space to separate command from glob
|
|
117
|
+
const lastSpace = glob.lastIndexOf(' ');
|
|
118
|
+
if (lastSpace === -1) {
|
|
119
|
+
// No glob part — match command prefix (e.g., "pnpm" matches "pnpm install")
|
|
120
|
+
return new RegExp(`^${escapeRegex(glob)}\\b`);
|
|
121
|
+
}
|
|
122
|
+
const command = glob.substring(0, lastSpace);
|
|
123
|
+
const globPart = glob.substring(lastSpace + 1);
|
|
124
|
+
if (globPart === '*') {
|
|
125
|
+
// "git commit *" → matches "git commit" followed by anything
|
|
126
|
+
return new RegExp(`^${escapeRegex(command)}\\b`);
|
|
127
|
+
}
|
|
128
|
+
// More specific globs: escape and convert * to .*
|
|
129
|
+
const escapedCommand = escapeRegex(command);
|
|
130
|
+
const regexGlob = escapeRegex(globPart).replace(/\\\*/g, '.*');
|
|
131
|
+
return new RegExp(`^${escapedCommand}\\s+${regexGlob}`);
|
|
132
|
+
}
|
|
133
|
+
function escapeRegex(str) {
|
|
134
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
135
|
+
}
|
|
66
136
|
/**
|
|
67
137
|
* Spring AI tool permission adapter.
|
|
68
138
|
*
|
|
@@ -77,6 +77,55 @@ describe('CodexToolPermissionAdapter', () => {
|
|
|
77
77
|
const result = adapter.translatePermissions([]);
|
|
78
78
|
expect(result).toEqual([]);
|
|
79
79
|
});
|
|
80
|
+
describe('buildPermissionConfig (SUP-1748)', () => {
|
|
81
|
+
it('builds permission config from allow and disallow lists', () => {
|
|
82
|
+
const config = adapter.buildPermissionConfig([{ shell: 'pnpm *' }, { shell: 'git commit *' }], [{ shell: 'git checkout *' }, 'user-input']);
|
|
83
|
+
expect(config.allowedCommandPatterns).toHaveLength(2);
|
|
84
|
+
expect(config.deniedCommandPatterns).toHaveLength(1);
|
|
85
|
+
expect(config.allowFileEdits).toBe(true);
|
|
86
|
+
expect(config.allowFileWrites).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
it('allowed patterns match expected commands', () => {
|
|
89
|
+
const config = adapter.buildPermissionConfig([{ shell: 'pnpm *' }], []);
|
|
90
|
+
expect(config.allowedCommandPatterns[0].test('pnpm install')).toBe(true);
|
|
91
|
+
expect(config.allowedCommandPatterns[0].test('pnpm run build')).toBe(true);
|
|
92
|
+
expect(config.allowedCommandPatterns[0].test('npm install')).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
it('denied patterns match expected commands', () => {
|
|
95
|
+
const config = adapter.buildPermissionConfig([], [{ shell: 'git *' }]);
|
|
96
|
+
expect(config.deniedCommandPatterns[0].pattern.test('git push')).toBe(true);
|
|
97
|
+
expect(config.deniedCommandPatterns[0].pattern.test('git commit')).toBe(true);
|
|
98
|
+
expect(config.deniedCommandPatterns[0].reason).toContain('blocked by template');
|
|
99
|
+
});
|
|
100
|
+
it('sets allowFileEdits to false when file-edit is in disallow', () => {
|
|
101
|
+
const config = adapter.buildPermissionConfig([], ['file-edit']);
|
|
102
|
+
expect(config.allowFileEdits).toBe(false);
|
|
103
|
+
expect(config.allowFileWrites).toBe(true);
|
|
104
|
+
});
|
|
105
|
+
it('sets allowFileWrites to false when file-write is in disallow', () => {
|
|
106
|
+
const config = adapter.buildPermissionConfig([], ['file-write']);
|
|
107
|
+
expect(config.allowFileEdits).toBe(true);
|
|
108
|
+
expect(config.allowFileWrites).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
it('ignores user-input in disallow (non-interactive)', () => {
|
|
111
|
+
const config = adapter.buildPermissionConfig([], ['user-input']);
|
|
112
|
+
expect(config.deniedCommandPatterns).toHaveLength(0);
|
|
113
|
+
expect(config.allowFileEdits).toBe(true);
|
|
114
|
+
expect(config.allowFileWrites).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
it('handles empty allow and disallow lists', () => {
|
|
117
|
+
const config = adapter.buildPermissionConfig([], []);
|
|
118
|
+
expect(config.allowedCommandPatterns).toHaveLength(0);
|
|
119
|
+
expect(config.deniedCommandPatterns).toHaveLength(0);
|
|
120
|
+
expect(config.allowFileEdits).toBe(true);
|
|
121
|
+
expect(config.allowFileWrites).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
it('multi-word commands create correct regex', () => {
|
|
124
|
+
const config = adapter.buildPermissionConfig([{ shell: 'git commit *' }], []);
|
|
125
|
+
expect(config.allowedCommandPatterns[0].test('git commit -m "message"')).toBe(true);
|
|
126
|
+
expect(config.allowedCommandPatterns[0].test('git push origin main')).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
80
129
|
});
|
|
81
130
|
describe('SpringAiToolPermissionAdapter', () => {
|
|
82
131
|
const adapter = new SpringAiToolPermissionAdapter();
|
|
@@ -9,6 +9,7 @@ export { TemplateRegistry, type TemplateValidationResult } from './registry.js';
|
|
|
9
9
|
export { generateTemplateSchema, extractTemplateVariables, type TemplateSchemaOptions } from './schema.js';
|
|
10
10
|
export { loadTemplatesFromDir, loadTemplateFile, loadPartialsFromDir, getBuiltinDefaultsDir, getBuiltinPartialsDir, } from './loader.js';
|
|
11
11
|
export { ClaudeToolPermissionAdapter, CodexToolPermissionAdapter, createToolPermissionAdapter } from './adapters.js';
|
|
12
|
+
export type { CodexPermissionConfig } from './adapters.js';
|
|
12
13
|
export { renderPromptWithFallback } from './renderer.js';
|
|
13
14
|
export type { AgentDefinition, AgentDefinitionFrontmatter } from './agent-definition.js';
|
|
14
15
|
export { parseAgentDefinition, parseAgentDefinitionFile, AgentDefinitionFrontmatterSchema } from './agent-definition.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,gBAAgB,EAAE,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAE1G,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,gBAAgB,EAAE,KAAK,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAE/E,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAE1G,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,GACtB,MAAM,aAAa,CAAA;AAEpB,OAAO,EAAE,2BAA2B,EAAE,0BAA0B,EAAE,2BAA2B,EAAE,MAAM,eAAe,CAAA;AACpH,YAAY,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAE1D,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAA;AAExD,YAAY,EAAE,eAAe,EAAE,0BAA0B,EAAE,MAAM,uBAAuB,CAAA;AACxF,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,gCAAgC,EAAE,MAAM,uBAAuB,CAAA"}
|
|
@@ -74,6 +74,14 @@ export declare class TemplateRegistry {
|
|
|
74
74
|
* Get disallowed tools for a work type (optionally with strategy).
|
|
75
75
|
*/
|
|
76
76
|
getDisallowedTools(workType: AgentWorkType, strategy?: string): ToolPermission[] | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Get the raw allow and disallow permission arrays for a work type (SUP-1748).
|
|
79
|
+
* Used by the orchestrator to build Codex permission configs via the adapter.
|
|
80
|
+
*/
|
|
81
|
+
getRawToolPermissions(workType: AgentWorkType, strategy?: string): {
|
|
82
|
+
allow: ToolPermission[];
|
|
83
|
+
disallow: ToolPermission[];
|
|
84
|
+
};
|
|
77
85
|
/**
|
|
78
86
|
* Register a partial template for use in Handlebars rendering.
|
|
79
87
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/templates/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,qBAAqB,EACtB,MAAM,YAAY,CAAA;AAOnB,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEhF;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAC3B;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,qBAAqB,CAAC,CAAuB;;IAcrD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,GAAE,sBAA2B,GAAG,gBAAgB;IAMpE;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,IAAI;IA4CrD;;OAEG;IACH,wBAAwB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAI9D;;;;;;;;OAQG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASrF;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAQhE;;OAEG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBjG;;;OAGG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAgBpF;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,GAAG,SAAS;IAK5F;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAIpD;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,WAAW,GAAG,IAAI;IAMpF;;;;;;;OAOG;IACH,cAAc,CACZ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,wBAAwB;CAS5B;AAMD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB"}
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/templates/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAA;AAClE,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,sBAAsB,EACtB,cAAc,EACd,qBAAqB,EACtB,MAAM,YAAY,CAAA;AAOnB,OAAO,EAA0B,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAEhF;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAC3B;;;OAGG;IACH,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,qBAAqB,CAAC,CAAuB;;IAcrD;;OAEG;IACH,MAAM,CAAC,MAAM,CAAC,MAAM,GAAE,sBAA2B,GAAG,gBAAgB;IAMpE;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,IAAI;IA4CrD;;OAEG;IACH,wBAAwB,CAAC,OAAO,EAAE,qBAAqB,GAAG,IAAI;IAI9D;;;;;;;;OAQG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IASrF;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO;IAQhE;;OAEG;IACH,sBAAsB,IAAI,MAAM,EAAE;IAIlC;;;;OAIG;IACH,YAAY,CAAC,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBjG;;;OAGG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS;IAgBpF;;OAEG;IACH,kBAAkB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,GAAG,SAAS;IAK5F;;;OAGG;IACH,qBAAqB,CAAC,QAAQ,EAAE,aAAa,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG;QACjE,KAAK,EAAE,cAAc,EAAE,CAAA;QACvB,QAAQ,EAAE,cAAc,EAAE,CAAA;KAC3B;IAQD;;OAEG;IACH,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAIpD;;;;;;OAMG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,WAAW,GAAG,IAAI;IAMpF;;;;;;;OAOG;IACH,cAAc,CACZ,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,wBAAwB;CAS5B;AAMD,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB"}
|
|
@@ -169,6 +169,17 @@ export class TemplateRegistry {
|
|
|
169
169
|
const template = this.getTemplate(workType, strategy);
|
|
170
170
|
return template?.tools?.disallow;
|
|
171
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Get the raw allow and disallow permission arrays for a work type (SUP-1748).
|
|
174
|
+
* Used by the orchestrator to build Codex permission configs via the adapter.
|
|
175
|
+
*/
|
|
176
|
+
getRawToolPermissions(workType, strategy) {
|
|
177
|
+
const template = this.getTemplate(workType, strategy);
|
|
178
|
+
return {
|
|
179
|
+
allow: template?.tools?.allow ?? [],
|
|
180
|
+
disallow: template?.tools?.disallow ?? [],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
172
183
|
/**
|
|
173
184
|
* Register a partial template for use in Handlebars rendering.
|
|
174
185
|
*/
|