@imdeadpool/guardex 7.0.41 → 7.1.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.
- package/README.md +94 -13
- package/package.json +3 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/skills/gx-act/SKILL.md +82 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +202 -0
- package/src/agents/launch.js +249 -0
- package/src/agents/registry.js +133 -0
- package/src/agents/selection-panel.js +571 -0
- package/src/agents/sessions.js +151 -0
- package/src/agents/start.js +591 -0
- package/src/agents/status.js +146 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +344 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +357 -3
- package/src/cli/commands/agents.js +364 -0
- package/src/cli/commands/bootstrap.js +92 -0
- package/src/cli/commands/branch.js +127 -0
- package/src/cli/commands/claude.js +674 -0
- package/src/cli/commands/doctor.js +268 -0
- package/src/cli/commands/finish.js +26 -0
- package/src/cli/commands/mcp.js +122 -0
- package/src/cli/commands/misc.js +304 -0
- package/src/cli/commands/pr.js +439 -0
- package/src/cli/commands/prompt.js +92 -0
- package/src/cli/commands/release.js +305 -0
- package/src/cli/commands/report.js +244 -0
- package/src/cli/commands/review.js +32 -0
- package/src/cli/commands/setup.js +242 -0
- package/src/cli/commands/status.js +338 -0
- package/src/cli/commands/watch.js +234 -0
- package/src/cli/main.js +85 -3613
- package/src/cli/shared/repo-env.js +161 -0
- package/src/cli/shared/sandbox.js +417 -0
- package/src/cli/shared/scaffolding.js +535 -0
- package/src/cli/shared/toolchain-shims.js +420 -0
- package/src/cockpit/action-runner.js +3 -0
- package/src/cockpit/actions.js +80 -0
- package/src/cockpit/control.js +1121 -0
- package/src/cockpit/index.js +426 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/logs-reader.js +182 -0
- package/src/cockpit/menu.js +204 -0
- package/src/cockpit/pane-actions.js +597 -0
- package/src/cockpit/pane-menu.js +387 -0
- package/src/cockpit/projects-finder.js +178 -0
- package/src/cockpit/render.js +215 -0
- package/src/cockpit/settings-render.js +128 -0
- package/src/cockpit/settings.js +124 -0
- package/src/cockpit/shortcuts.js +24 -0
- package/src/cockpit/sidebar.js +311 -0
- package/src/cockpit/state.js +72 -0
- package/src/cockpit/theme.js +128 -0
- package/src/cockpit/welcome.js +266 -0
- package/src/context.js +304 -43
- package/src/core/runtime.js +6 -1
- package/src/doctor/index.js +45 -15
- package/src/finish/index.js +186 -7
- package/src/finish/preflight.js +177 -0
- package/src/finish/review-gate.js +182 -0
- package/src/git/index.js +511 -4
- package/src/hooks/index.js +0 -64
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/mcp/collect.js +370 -0
- package/src/mcp/server.js +157 -0
- package/src/output/index.js +68 -2
- package/src/pr-review.js +264 -0
- package/src/pr.js +381 -0
- package/src/sandbox/index.js +13 -2
- package/src/scaffold/agent-worktree-prep.js +213 -0
- package/src/scaffold/index.js +127 -10
- package/src/speckit/index.js +226 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +45 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +125 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/src/toolchain/index.js +20 -0
- package/templates/AGENTS.monorepo-apps.md +26 -0
- package/templates/AGENTS.multiagent-safety.md +63 -323
- package/templates/AGENTS.multiagent-safety.min.md +11 -0
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/codex/skills/gx-act/SKILL.md +82 -0
- package/templates/githooks/pre-commit +44 -20
- package/templates/github/workflows/README.md +87 -0
- package/templates/github/workflows/ci-full.yml +55 -0
- package/templates/github/workflows/ci.yml +56 -0
- package/templates/github/workflows/cr.yml +20 -1
- package/templates/scripts/agent-branch-finish.sh +519 -23
- package/templates/scripts/agent-branch-merge.sh +4 -1
- package/templates/scripts/agent-branch-start.sh +176 -24
- package/templates/scripts/agent-preflight.sh +115 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -97
- package/templates/scripts/openspec/init-plan-workspace.sh +43 -0
- package/templates/scripts/review-bot-watch.sh +31 -2
- package/templates/scripts/agent-session-state.js +0 -171
- package/templates/scripts/install-vscode-active-agents-extension.js +0 -135
- package/templates/vscode/guardex-active-agents/README.md +0 -34
- package/templates/vscode/guardex-active-agents/extension.js +0 -3782
- package/templates/vscode/guardex-active-agents/fileicons/gitguardex-fileicons.json +0 -54
- package/templates/vscode/guardex-active-agents/fileicons/icons/agent.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/branch.svg +0 -7
- package/templates/vscode/guardex-active-agents/fileicons/icons/config.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/hook.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/openspec.svg +0 -5
- package/templates/vscode/guardex-active-agents/fileicons/icons/plan.svg +0 -4
- package/templates/vscode/guardex-active-agents/fileicons/icons/spec.svg +0 -5
- package/templates/vscode/guardex-active-agents/icon.png +0 -0
- package/templates/vscode/guardex-active-agents/media/active-agents-hivemind.svg +0 -14
- package/templates/vscode/guardex-active-agents/package.json +0 -169
- package/templates/vscode/guardex-active-agents/session-schema.js +0 -1348
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
const { fs, path, DEFAULT_BASE_BRANCH, LOCK_FILE_RELATIVE } = require('../context');
|
|
2
|
+
const { run } = require('../core/runtime');
|
|
3
|
+
const { resolveRepoRoot } = require('../git');
|
|
4
|
+
|
|
5
|
+
const INSPECT_EXCLUDE_PATHS = new Set([LOCK_FILE_RELATIVE]);
|
|
6
|
+
|
|
7
|
+
function git(repoRoot, args, options = {}) {
|
|
8
|
+
const result = run('git', ['-C', repoRoot, ...args], options);
|
|
9
|
+
if (result.status !== 0 && !options.allowFailure) {
|
|
10
|
+
throw new Error(`git ${args.join(' ')} failed: ${(result.stderr || '').trim()}`);
|
|
11
|
+
}
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readGitConfig(repoRoot, key) {
|
|
16
|
+
const result = git(repoRoot, ['config', '--get', key], { allowFailure: true });
|
|
17
|
+
return result.status === 0 ? String(result.stdout || '').trim() : '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function refExists(repoRoot, ref) {
|
|
21
|
+
return git(repoRoot, ['show-ref', '--verify', '--quiet', ref], { allowFailure: true }).status === 0;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function parseWorktreeList(outputText) {
|
|
25
|
+
const worktrees = [];
|
|
26
|
+
let current = null;
|
|
27
|
+
|
|
28
|
+
for (const line of String(outputText || '').split(/\r?\n/)) {
|
|
29
|
+
if (!line.trim()) {
|
|
30
|
+
if (current) worktrees.push(current);
|
|
31
|
+
current = null;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (line.startsWith('worktree ')) {
|
|
35
|
+
if (current) worktrees.push(current);
|
|
36
|
+
current = { path: line.slice('worktree '.length), branch: '' };
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (current && line.startsWith('branch ')) {
|
|
40
|
+
current.branch = line.slice('branch '.length).replace(/^refs\/heads\//, '');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (current) worktrees.push(current);
|
|
44
|
+
|
|
45
|
+
return worktrees;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function listWorktrees(repoRoot) {
|
|
49
|
+
const result = git(repoRoot, ['worktree', 'list', '--porcelain'], { allowFailure: true });
|
|
50
|
+
return result.status === 0 ? parseWorktreeList(result.stdout) : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// `worktrees` (pre-parsed via listWorktrees) lets callers iterating many branches
|
|
54
|
+
// in one pass hoist the invariant `git worktree list` out of their loop. When
|
|
55
|
+
// omitted, the list is fetched on demand — unchanged behavior.
|
|
56
|
+
function worktreePathForBranch(repoRoot, branch, worktrees = null) {
|
|
57
|
+
let entries = worktrees;
|
|
58
|
+
if (entries == null) {
|
|
59
|
+
const result = git(repoRoot, ['worktree', 'list', '--porcelain'], { allowFailure: true });
|
|
60
|
+
if (result.status !== 0) return { worktreePath: repoRoot, worktreeFound: false };
|
|
61
|
+
entries = parseWorktreeList(result.stdout);
|
|
62
|
+
}
|
|
63
|
+
const match = entries.find((entry) => entry.branch === branch);
|
|
64
|
+
return {
|
|
65
|
+
worktreePath: match?.path || repoRoot,
|
|
66
|
+
worktreeFound: Boolean(match?.path),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolveBaseBranch(repoRoot, branch) {
|
|
71
|
+
return (
|
|
72
|
+
readGitConfig(repoRoot, `branch.${branch}.guardexBase`) ||
|
|
73
|
+
readGitConfig(repoRoot, 'multiagent.baseBranch') ||
|
|
74
|
+
DEFAULT_BASE_BRANCH
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function compareRefForBase(repoRoot, baseBranch) {
|
|
79
|
+
if (refExists(repoRoot, `refs/remotes/origin/${baseBranch}`)) {
|
|
80
|
+
return `origin/${baseBranch}`;
|
|
81
|
+
}
|
|
82
|
+
return baseBranch;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function readLockRegistry(repoRoot, branch) {
|
|
86
|
+
const lockPath = path.join(repoRoot, LOCK_FILE_RELATIVE);
|
|
87
|
+
let parsed = null;
|
|
88
|
+
try {
|
|
89
|
+
parsed = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
90
|
+
} catch (_error) {
|
|
91
|
+
parsed = null;
|
|
92
|
+
}
|
|
93
|
+
const locks = parsed?.locks && typeof parsed.locks === 'object' && !Array.isArray(parsed.locks)
|
|
94
|
+
? parsed.locks
|
|
95
|
+
: {};
|
|
96
|
+
|
|
97
|
+
return Object.entries(locks)
|
|
98
|
+
.map(([filePath, entry]) => {
|
|
99
|
+
if (!entry || typeof entry !== 'object' || entry.branch !== branch) return null;
|
|
100
|
+
return {
|
|
101
|
+
file: filePath,
|
|
102
|
+
branch: entry.branch,
|
|
103
|
+
claimedAt: entry.claimed_at || '',
|
|
104
|
+
allowDelete: Boolean(entry.allow_delete),
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
.filter(Boolean)
|
|
108
|
+
.sort((left, right) => left.file.localeCompare(right.file));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function splitGitLines(outputText) {
|
|
112
|
+
return String(outputText || '')
|
|
113
|
+
.split(/\r?\n/)
|
|
114
|
+
.map((line) => line.trim())
|
|
115
|
+
.filter(Boolean);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readUntrackedFiles(worktreePath) {
|
|
119
|
+
const result = git(worktreePath, ['ls-files', '--others', '--exclude-standard'], { allowFailure: true });
|
|
120
|
+
return result.status === 0
|
|
121
|
+
? splitGitLines(result.stdout).filter((filePath) => !INSPECT_EXCLUDE_PATHS.has(filePath))
|
|
122
|
+
: [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function inspectAgentBranch(options) {
|
|
126
|
+
const repoRoot = resolveRepoRoot(options.target || process.cwd());
|
|
127
|
+
const branch = String(options.branch || '').trim();
|
|
128
|
+
if (!branch) {
|
|
129
|
+
throw new Error('--branch requires an agent branch name');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const baseBranch = resolveBaseBranch(repoRoot, branch);
|
|
133
|
+
const compareRef = compareRefForBase(repoRoot, baseBranch);
|
|
134
|
+
const { worktreePath, worktreeFound } = worktreePathForBranch(repoRoot, branch, options.worktrees);
|
|
135
|
+
return { repoRoot, branch, baseBranch, compareRef, worktreePath, worktreeFound };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function changedFiles(options) {
|
|
139
|
+
const context = inspectAgentBranch(options);
|
|
140
|
+
const diffTarget = context.worktreeFound ? context.compareRef : `${context.compareRef}...${context.branch}`;
|
|
141
|
+
const result = git(context.worktreePath, ['diff', '--name-only', diffTarget, '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`]);
|
|
142
|
+
const files = [...splitGitLines(result.stdout), ...(context.worktreeFound ? readUntrackedFiles(context.worktreePath) : [])];
|
|
143
|
+
const uniqueFiles = Array.from(new Set(files)).sort((left, right) => left.localeCompare(right));
|
|
144
|
+
return { ...context, files: uniqueFiles };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function branchDiff(options) {
|
|
148
|
+
const context = inspectAgentBranch(options);
|
|
149
|
+
const diffTarget = context.worktreeFound ? context.compareRef : `${context.compareRef}...${context.branch}`;
|
|
150
|
+
const result = git(context.worktreePath, ['diff', diffTarget, '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`]);
|
|
151
|
+
const untrackedDiff = context.worktreeFound
|
|
152
|
+
? readUntrackedFiles(context.worktreePath)
|
|
153
|
+
.map((filePath) => git(context.worktreePath, ['diff', '--no-index', '--', '/dev/null', filePath], { allowFailure: true }).stdout || '')
|
|
154
|
+
.join('')
|
|
155
|
+
: '';
|
|
156
|
+
return { ...context, diff: `${result.stdout || ''}${untrackedDiff}` };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function branchLocks(options) {
|
|
160
|
+
const context = inspectAgentBranch(options);
|
|
161
|
+
return { ...context, locks: readLockRegistry(context.repoRoot, context.branch) };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function renderFiles(payload, { json = false } = {}) {
|
|
165
|
+
if (json) return `${JSON.stringify(payload, null, 2)}\n`;
|
|
166
|
+
return payload.files.length > 0 ? `${payload.files.join('\n')}\n` : '';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function renderDiff(payload, { json = false } = {}) {
|
|
170
|
+
if (json) return `${JSON.stringify(payload, null, 2)}\n`;
|
|
171
|
+
return payload.diff;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function renderLocks(payload, { json = false } = {}) {
|
|
175
|
+
if (json) return `${JSON.stringify(payload, null, 2)}\n`;
|
|
176
|
+
if (payload.locks.length === 0) return '';
|
|
177
|
+
return `${payload.locks
|
|
178
|
+
.map((lock) => `${lock.file}\t${lock.branch}\t${lock.claimedAt}\tallow_delete=${lock.allowDelete ? 'true' : 'false'}`)
|
|
179
|
+
.join('\n')}\n`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function runInspectCommand(options) {
|
|
183
|
+
if (options.subcommand === 'files') return renderFiles(changedFiles(options), options);
|
|
184
|
+
if (options.subcommand === 'diff') return renderDiff(branchDiff(options), options);
|
|
185
|
+
if (options.subcommand === 'locks') return renderLocks(branchLocks(options), options);
|
|
186
|
+
throw new Error(`Unknown agents subcommand: ${options.subcommand}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = {
|
|
190
|
+
branchDiff,
|
|
191
|
+
branchLocks,
|
|
192
|
+
changedFiles,
|
|
193
|
+
inspectAgentBranch,
|
|
194
|
+
listWorktrees,
|
|
195
|
+
parseWorktreeList,
|
|
196
|
+
readLockRegistry,
|
|
197
|
+
renderDiff,
|
|
198
|
+
renderFiles,
|
|
199
|
+
renderLocks,
|
|
200
|
+
resolveBaseBranch,
|
|
201
|
+
runInspectCommand,
|
|
202
|
+
};
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let registryModule = {};
|
|
4
|
+
let registryPath = null;
|
|
5
|
+
|
|
6
|
+
try {
|
|
7
|
+
registryPath = require.resolve('./registry');
|
|
8
|
+
} catch (error) {
|
|
9
|
+
if (error && error.code !== 'MODULE_NOT_FOUND') {
|
|
10
|
+
throw error;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (registryPath) {
|
|
15
|
+
registryModule = require(registryPath);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const FALLBACK_AGENTS = {
|
|
19
|
+
codex: {
|
|
20
|
+
command: 'codex',
|
|
21
|
+
promptMode: 'positional',
|
|
22
|
+
resumeCommand: ['codex', 'resume'],
|
|
23
|
+
permissionFlag: '--permission-mode',
|
|
24
|
+
},
|
|
25
|
+
claude: {
|
|
26
|
+
command: 'claude',
|
|
27
|
+
promptMode: 'option',
|
|
28
|
+
promptFlag: '--prompt',
|
|
29
|
+
resumeCommand: ['claude', '--continue'],
|
|
30
|
+
permissionFlag: '--permission-mode',
|
|
31
|
+
},
|
|
32
|
+
opencode: {
|
|
33
|
+
command: 'opencode',
|
|
34
|
+
promptMode: 'positional',
|
|
35
|
+
resumeCommand: ['opencode', 'resume'],
|
|
36
|
+
permissionFlag: '--permission-mode',
|
|
37
|
+
},
|
|
38
|
+
cursor: {
|
|
39
|
+
command: 'cursor-agent',
|
|
40
|
+
promptMode: 'stdin',
|
|
41
|
+
resumeCommand: ['cursor-agent', 'resume'],
|
|
42
|
+
permissionFlag: '--permission-mode',
|
|
43
|
+
},
|
|
44
|
+
gemini: {
|
|
45
|
+
command: 'gemini',
|
|
46
|
+
promptMode: 'option',
|
|
47
|
+
promptFlag: '--prompt',
|
|
48
|
+
resumeCommand: ['gemini', 'resume'],
|
|
49
|
+
permissionFlag: '--permission-mode',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const SUPPORTED_PROMPT_MODES = new Set(['positional', 'option', 'stdin', 'argument']);
|
|
54
|
+
|
|
55
|
+
function shellQuote(value) {
|
|
56
|
+
const stringValue = String(value);
|
|
57
|
+
if (stringValue.length === 0) return "''";
|
|
58
|
+
return `'${stringValue.replace(/'/g, `'\\''`)}'`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeCommandParts(command) {
|
|
62
|
+
if (Array.isArray(command)) return command.map(String);
|
|
63
|
+
if (command && typeof command === 'object') {
|
|
64
|
+
return [
|
|
65
|
+
...normalizeCommandParts(command.command || command.executable || command.bin),
|
|
66
|
+
...normalizeCommandParts(command.args || command.defaultArgs),
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
if (typeof command === 'string' && command.trim()) return [command];
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function normalizeAgentsCollection(collection) {
|
|
74
|
+
if (!collection) return null;
|
|
75
|
+
if (collection instanceof Map) return Object.fromEntries(collection.entries());
|
|
76
|
+
if (Array.isArray(collection)) {
|
|
77
|
+
return Object.fromEntries(collection.map((agent) => [agent.id || agent.agentId || agent.name, agent]));
|
|
78
|
+
}
|
|
79
|
+
if (typeof collection === 'object') return collection;
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function getRegistryAgent(agentId) {
|
|
84
|
+
const lookupFunctions = [
|
|
85
|
+
registryModule.getAgent,
|
|
86
|
+
registryModule.getAgentById,
|
|
87
|
+
registryModule.getAgentDefinition,
|
|
88
|
+
registryModule.resolveAgent,
|
|
89
|
+
].filter((candidate) => typeof candidate === 'function');
|
|
90
|
+
|
|
91
|
+
for (const lookup of lookupFunctions) {
|
|
92
|
+
const agent = lookup(agentId);
|
|
93
|
+
if (agent) return agent;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const collections = [
|
|
97
|
+
registryModule.AGENTS,
|
|
98
|
+
registryModule.AGENT_REGISTRY,
|
|
99
|
+
registryModule.SUPPORTED_AGENTS,
|
|
100
|
+
registryModule.agents,
|
|
101
|
+
registryModule.registry,
|
|
102
|
+
registryModule.default,
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
for (const collection of collections) {
|
|
106
|
+
const normalized = normalizeAgentsCollection(collection);
|
|
107
|
+
if (normalized && normalized[agentId]) return normalized[agentId];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolveAgent(agentId) {
|
|
114
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
115
|
+
throw new TypeError('agentId is required');
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const registryAgent = getRegistryAgent(agentId);
|
|
119
|
+
const fallbackAgent = FALLBACK_AGENTS[agentId];
|
|
120
|
+
const agent = { ...fallbackAgent, ...registryAgent };
|
|
121
|
+
|
|
122
|
+
if (!agent || (!agent.command && !agent.executable && !agent.bin)) {
|
|
123
|
+
throw new Error(`Unsupported agent: ${agentId}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return agent;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildPermissionParts(agent, permissionMode) {
|
|
130
|
+
if (!permissionMode) return [];
|
|
131
|
+
|
|
132
|
+
if (typeof agent.buildPermissionArgs === 'function') {
|
|
133
|
+
return normalizeCommandParts(agent.buildPermissionArgs(permissionMode));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const flag = agent.permissionFlag ||
|
|
137
|
+
agent.permissionModeFlag ||
|
|
138
|
+
agent.permission?.flag ||
|
|
139
|
+
agent.permissionMode?.flag;
|
|
140
|
+
if (!flag) return [];
|
|
141
|
+
return [flag, permissionMode];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function buildSessionParts(agent, sessionId) {
|
|
145
|
+
if (!sessionId) return [];
|
|
146
|
+
|
|
147
|
+
if (typeof agent.buildSessionArgs === 'function') {
|
|
148
|
+
return normalizeCommandParts(agent.buildSessionArgs(sessionId));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const flag = agent.sessionFlag || agent.sessionIdFlag || agent.session?.flag;
|
|
152
|
+
if (!flag) return [];
|
|
153
|
+
return [flag, sessionId];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildSessionEnv(agent, sessionId) {
|
|
157
|
+
if (!sessionId) return [];
|
|
158
|
+
const envVar = agent.sessionEnvVar || agent.session?.env || 'OMX_SESSION_ID';
|
|
159
|
+
return [`${envVar}=${shellQuote(sessionId)}`];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function commandToShell(parts) {
|
|
163
|
+
return parts.map(shellQuote).join(' ');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function buildPromptCommand(parts, agent, prompt) {
|
|
167
|
+
if (prompt === undefined || prompt === null || prompt === '') {
|
|
168
|
+
return commandToShell(parts);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const promptMode = agent.promptMode || agent.prompt?.mode || 'positional';
|
|
172
|
+
if (!SUPPORTED_PROMPT_MODES.has(promptMode)) {
|
|
173
|
+
throw new Error(`Unsupported prompt mode for ${agent.id || agent.command}: ${promptMode}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (promptMode === 'stdin') {
|
|
177
|
+
return `printf %s ${shellQuote(prompt)} | ${commandToShell(parts)}`;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (promptMode === 'option') {
|
|
181
|
+
const promptFlag = agent.promptFlag || agent.promptOption || agent.prompt?.flag || agent.prompt?.option || '--prompt';
|
|
182
|
+
return commandToShell([...parts, promptFlag, prompt]);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return commandToShell([...parts, prompt]);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function buildResourceEnv() {
|
|
189
|
+
const cpus = require('node:os').cpus().length || 8;
|
|
190
|
+
// Cap each agent's cargo parallelism to avoid overwhelming the system
|
|
191
|
+
// when multiple agents build concurrently.
|
|
192
|
+
const jobs = Math.max(2, Math.floor(cpus / 4));
|
|
193
|
+
return [`CARGO_BUILD_JOBS=${jobs}`];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildAgentLaunchCommand(options) {
|
|
197
|
+
if (!options || typeof options !== 'object') {
|
|
198
|
+
throw new TypeError('options are required');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const { agentId, prompt, worktreePath, permissionMode, sessionId } = options;
|
|
202
|
+
const agent = resolveAgent(agentId);
|
|
203
|
+
const command = normalizeCommandParts(agent.launchCommand || agent.launch || agent.command || agent.executable || agent.bin);
|
|
204
|
+
const baseParts = [
|
|
205
|
+
...command,
|
|
206
|
+
...normalizeCommandParts(agent.defaultArgs),
|
|
207
|
+
...normalizeCommandParts(agent.args),
|
|
208
|
+
...normalizeCommandParts(agent.launchArgs),
|
|
209
|
+
...buildPermissionParts(agent, permissionMode),
|
|
210
|
+
...buildSessionParts(agent, sessionId),
|
|
211
|
+
];
|
|
212
|
+
|
|
213
|
+
if (baseParts.length === 0) {
|
|
214
|
+
throw new Error(`Unsupported agent: ${agentId}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const launchCommand = buildPromptCommand(baseParts, agent, prompt);
|
|
218
|
+
const envParts = [...buildResourceEnv(), ...buildSessionEnv(agent, sessionId)];
|
|
219
|
+
const envPrefix = envParts.join(' ');
|
|
220
|
+
const launchWithEnv = envPrefix ? `${envPrefix} ${launchCommand}` : launchCommand;
|
|
221
|
+
if (!worktreePath) return launchWithEnv;
|
|
222
|
+
return `cd ${shellQuote(worktreePath)} && ${launchWithEnv}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function buildAgentResumeCommand(agentId, permissionMode) {
|
|
226
|
+
const agent = resolveAgent(agentId);
|
|
227
|
+
const commandParts = normalizeCommandParts(agent.command || agent.executable || agent.bin);
|
|
228
|
+
const explicitResume = agent.resumeCommand || agent.resume;
|
|
229
|
+
const resumeArgs = normalizeCommandParts(agent.resumeArgs);
|
|
230
|
+
const resumeParts = explicitResume
|
|
231
|
+
? normalizeCommandParts(explicitResume)
|
|
232
|
+
: [...commandParts, ...(resumeArgs.length > 0 ? resumeArgs : ['resume'])];
|
|
233
|
+
const command = [
|
|
234
|
+
...resumeParts,
|
|
235
|
+
...buildPermissionParts(agent, permissionMode),
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
if (command.length === 0) {
|
|
239
|
+
throw new Error(`Unsupported agent: ${agentId}`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return commandToShell(command);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = {
|
|
246
|
+
buildAgentLaunchCommand,
|
|
247
|
+
buildAgentResumeCommand,
|
|
248
|
+
shellQuote,
|
|
249
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const AGENT_DEFINITIONS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'codex',
|
|
4
|
+
label: 'Codex',
|
|
5
|
+
shortLabel: 'CX',
|
|
6
|
+
description: 'OpenAI Codex CLI for repository-aware coding tasks.',
|
|
7
|
+
command: 'codex',
|
|
8
|
+
detectCommand: 'codex --version',
|
|
9
|
+
defaultEnabled: true,
|
|
10
|
+
promptMode: 'argument',
|
|
11
|
+
resumeCommandTemplate: 'codex resume {sessionId}',
|
|
12
|
+
worktreeRoot: '.omx/agent-worktrees',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'claude',
|
|
16
|
+
label: 'Claude Code',
|
|
17
|
+
shortLabel: 'CC',
|
|
18
|
+
description: 'Anthropic Claude Code CLI for interactive coding sessions.',
|
|
19
|
+
command: 'claude',
|
|
20
|
+
detectCommand: 'claude --version',
|
|
21
|
+
defaultEnabled: true,
|
|
22
|
+
promptMode: 'argument',
|
|
23
|
+
resumeCommandTemplate: 'claude --resume {sessionId}',
|
|
24
|
+
worktreeRoot: '.omc/agent-worktrees',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: 'opencode',
|
|
28
|
+
label: 'OpenCode',
|
|
29
|
+
shortLabel: 'OC',
|
|
30
|
+
description: 'OpenCode CLI for terminal-native AI coding workflows.',
|
|
31
|
+
command: 'opencode',
|
|
32
|
+
detectCommand: 'opencode --version',
|
|
33
|
+
defaultEnabled: true,
|
|
34
|
+
promptMode: 'argument',
|
|
35
|
+
worktreeRoot: '.omx/agent-worktrees',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'cursor',
|
|
39
|
+
label: 'Cursor Agent',
|
|
40
|
+
shortLabel: 'CA',
|
|
41
|
+
description: 'Cursor command-line agent for AI coding tasks.',
|
|
42
|
+
command: 'cursor-agent',
|
|
43
|
+
detectCommand: 'cursor-agent --version',
|
|
44
|
+
defaultEnabled: true,
|
|
45
|
+
promptMode: 'argument',
|
|
46
|
+
worktreeRoot: '.omx/agent-worktrees',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 'gemini',
|
|
50
|
+
label: 'Gemini CLI',
|
|
51
|
+
shortLabel: 'GM',
|
|
52
|
+
description: 'Google Gemini CLI for AI-assisted development workflows.',
|
|
53
|
+
command: 'gemini',
|
|
54
|
+
detectCommand: 'gemini --version',
|
|
55
|
+
defaultEnabled: true,
|
|
56
|
+
promptMode: 'argument',
|
|
57
|
+
worktreeRoot: '.omx/agent-worktrees',
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
function validateAgentDefinitions(definitions) {
|
|
62
|
+
const ids = new Set();
|
|
63
|
+
const shortLabels = new Set();
|
|
64
|
+
|
|
65
|
+
for (const definition of definitions) {
|
|
66
|
+
if (ids.has(definition.id)) {
|
|
67
|
+
throw new Error(`Duplicate agent id: ${definition.id}`);
|
|
68
|
+
}
|
|
69
|
+
ids.add(definition.id);
|
|
70
|
+
|
|
71
|
+
if (shortLabels.has(definition.shortLabel)) {
|
|
72
|
+
throw new Error(`Duplicate agent short label: ${definition.shortLabel}`);
|
|
73
|
+
}
|
|
74
|
+
shortLabels.add(definition.shortLabel);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
validateAgentDefinitions(AGENT_DEFINITIONS);
|
|
79
|
+
|
|
80
|
+
const AGENT_IDS = Object.freeze(AGENT_DEFINITIONS.map((definition) => definition.id));
|
|
81
|
+
const AGENT_REGISTRY = Object.freeze(
|
|
82
|
+
Object.fromEntries(
|
|
83
|
+
AGENT_DEFINITIONS.map((definition) => [
|
|
84
|
+
definition.id,
|
|
85
|
+
Object.freeze({ ...definition }),
|
|
86
|
+
]),
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
function normalizeAgentId(rawAgentId) {
|
|
91
|
+
return String(rawAgentId || 'codex').trim().toLowerCase();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isAgentId(value) {
|
|
95
|
+
return Object.prototype.hasOwnProperty.call(AGENT_REGISTRY, value);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getAgentDefinition(id) {
|
|
99
|
+
return AGENT_REGISTRY[id];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getAgentDefinitions() {
|
|
103
|
+
return AGENT_IDS.map((id) => AGENT_REGISTRY[id]);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getDefaultEnabledAgents() {
|
|
107
|
+
return getAgentDefinitions().filter((definition) => definition.defaultEnabled);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function listAgentIds() {
|
|
111
|
+
return [...AGENT_IDS];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function resolveAgent(rawAgentId) {
|
|
115
|
+
const agentId = normalizeAgentId(rawAgentId);
|
|
116
|
+
const agent = getAgentDefinition(agentId);
|
|
117
|
+
if (!agent) {
|
|
118
|
+
throw new Error(`Unknown agent id: ${rawAgentId || '(empty)'} (expected one of: ${listAgentIds().join(', ')})`);
|
|
119
|
+
}
|
|
120
|
+
return agent;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
module.exports = {
|
|
124
|
+
AGENT_IDS,
|
|
125
|
+
AGENT_REGISTRY,
|
|
126
|
+
isAgentId,
|
|
127
|
+
getAgentDefinition,
|
|
128
|
+
getAgentDefinitions,
|
|
129
|
+
getDefaultEnabledAgents,
|
|
130
|
+
listAgentIds,
|
|
131
|
+
normalizeAgentId,
|
|
132
|
+
resolveAgent,
|
|
133
|
+
};
|