@imdeadpool/guardex 7.0.41 → 7.0.43
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 +68 -13
- package/package.json +2 -1
- package/skills/gitguardex/SKILL.md +13 -0
- package/skills/guardex-merge-skills-to-dev/SKILL.md +59 -0
- package/src/agents/cleanup-sessions.js +126 -0
- package/src/agents/detect.js +160 -0
- package/src/agents/finish.js +172 -0
- package/src/agents/inspect.js +189 -0
- package/src/agents/launch.js +240 -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 +143 -0
- package/src/agents/terminal.js +152 -0
- package/src/budget/index.js +343 -0
- package/src/ci-init/index.js +265 -0
- package/src/cli/args.js +305 -1
- package/src/cli/main.js +262 -132
- 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/keybindings.js +224 -0
- package/src/cockpit/kitty-layout.js +549 -0
- package/src/cockpit/kitty-tree.js +144 -0
- package/src/cockpit/layout.js +224 -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 +76 -33
- package/src/doctor/index.js +3 -2
- package/src/finish/index.js +39 -2
- package/src/git/index.js +65 -0
- package/src/kitty/command.js +101 -0
- package/src/kitty/runtime.js +250 -0
- package/src/output/index.js +1 -1
- package/src/pr-review.js +241 -0
- package/src/scaffold/index.js +19 -0
- package/src/submodule/index.js +288 -0
- package/src/terminal/index.js +120 -0
- package/src/terminal/kitty.js +622 -0
- package/src/terminal/tmux.js +126 -0
- package/src/tmux/command.js +27 -0
- package/src/tmux/session.js +89 -0
- package/templates/AGENTS.multiagent-safety.md +27 -1
- package/templates/codex/skills/gitguardex/SKILL.md +2 -0
- package/templates/githooks/pre-commit +22 -1
- 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 +544 -26
- package/templates/scripts/agent-branch-start.sh +89 -22
- package/templates/scripts/agent-preflight.sh +89 -0
- package/templates/scripts/agent-worktree-prune.sh +96 -5
- package/templates/scripts/codex-agent.sh +41 -6
- 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,172 @@
|
|
|
1
|
+
const { TOOL_NAME } = require('../context');
|
|
2
|
+
const finishCommands = require('../finish');
|
|
3
|
+
const {
|
|
4
|
+
readAgentSession,
|
|
5
|
+
updateAgentSession,
|
|
6
|
+
listAgentSessions,
|
|
7
|
+
} = require('./sessions');
|
|
8
|
+
|
|
9
|
+
function resolveSessionByBranch(repoRoot, branch) {
|
|
10
|
+
const matches = listAgentSessions(repoRoot).filter((session) => session.branch === branch);
|
|
11
|
+
if (matches.length === 0) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
if (matches.length > 1) {
|
|
15
|
+
throw new Error(`Multiple agent sessions found for branch: ${branch}`);
|
|
16
|
+
}
|
|
17
|
+
return matches[0];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function resolveAgentSessionForFinish(repoRoot, options) {
|
|
21
|
+
if (options.sessionId) {
|
|
22
|
+
const session = readAgentSession(repoRoot, options.sessionId);
|
|
23
|
+
if (!session) {
|
|
24
|
+
throw new Error(`Agent session not found: ${options.sessionId}`);
|
|
25
|
+
}
|
|
26
|
+
return session;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (options.branch) {
|
|
30
|
+
const session = resolveSessionByBranch(repoRoot, options.branch);
|
|
31
|
+
if (!session) {
|
|
32
|
+
throw new Error(`Agent session not found for branch: ${options.branch}`);
|
|
33
|
+
}
|
|
34
|
+
return session;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
throw new Error('agents finish requires --session <id> or --branch <agent/...>');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function sessionStatusAfterFinish(finishArgs) {
|
|
41
|
+
const modeIndex = finishArgs.indexOf('--mode');
|
|
42
|
+
const directMode = finishArgs.includes('--direct-only') || finishArgs[modeIndex + 1] === 'direct';
|
|
43
|
+
return finishArgs.includes('--no-wait-for-merge') && !directMode ? 'pr-opened' : 'finished';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function cleanupResultAfterFinish(finishArgs, status) {
|
|
47
|
+
if (status === 'failed') return 'failed';
|
|
48
|
+
if (finishArgs.includes('--no-cleanup')) return 'skipped';
|
|
49
|
+
if (finishArgs.includes('--cleanup')) return status === 'finished' ? 'completed' : 'pending';
|
|
50
|
+
return 'unknown';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function firstMatch(text, patterns) {
|
|
54
|
+
for (const pattern of patterns) {
|
|
55
|
+
const match = text.match(pattern);
|
|
56
|
+
if (match && match[1]) return match[1].trim();
|
|
57
|
+
}
|
|
58
|
+
return '';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function finishOutputText(result, captured = {}) {
|
|
62
|
+
return [
|
|
63
|
+
captured.stdout,
|
|
64
|
+
captured.stderr,
|
|
65
|
+
result?.stdout,
|
|
66
|
+
result?.stderr,
|
|
67
|
+
].map((value) => String(value || '')).join('\n');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function buildFinishEvidence(session, finishArgs, status, result, captured = {}) {
|
|
71
|
+
const outputText = finishOutputText(result, captured);
|
|
72
|
+
const prUrl = firstMatch(outputText, [
|
|
73
|
+
/\[agent-branch-finish\] (?:Merged PR|PR):\s+(https?:\/\/\S+)/,
|
|
74
|
+
/\b(https?:\/\/\S+\/pull\/\d+)\b/,
|
|
75
|
+
]);
|
|
76
|
+
const mergeState = status === 'finished' ? 'MERGED' : status === 'pr-opened' ? 'OPEN' : status.toUpperCase();
|
|
77
|
+
return {
|
|
78
|
+
schemaVersion: 1,
|
|
79
|
+
sessionId: session.id || '',
|
|
80
|
+
branch: session.branch || '',
|
|
81
|
+
prUrl,
|
|
82
|
+
mergeState,
|
|
83
|
+
cleanupResult: cleanupResultAfterFinish(finishArgs, status),
|
|
84
|
+
status,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function captureProcessOutput(fn) {
|
|
89
|
+
let stdout = '';
|
|
90
|
+
let stderr = '';
|
|
91
|
+
const originalStdoutWrite = process.stdout.write;
|
|
92
|
+
const originalStderrWrite = process.stderr.write;
|
|
93
|
+
process.stdout.write = function captureStdout(chunk, encoding, callback) {
|
|
94
|
+
stdout += Buffer.isBuffer(chunk) ? chunk.toString(encoding || 'utf8') : String(chunk || '');
|
|
95
|
+
if (typeof encoding === 'function') encoding();
|
|
96
|
+
if (typeof callback === 'function') callback();
|
|
97
|
+
return true;
|
|
98
|
+
};
|
|
99
|
+
process.stderr.write = function captureStderr(chunk, encoding, callback) {
|
|
100
|
+
stderr += Buffer.isBuffer(chunk) ? chunk.toString(encoding || 'utf8') : String(chunk || '');
|
|
101
|
+
if (typeof encoding === 'function') encoding();
|
|
102
|
+
if (typeof callback === 'function') callback();
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
return { result: fn(), captured: { stdout, stderr } };
|
|
107
|
+
} finally {
|
|
108
|
+
process.stdout.write = originalStdoutWrite;
|
|
109
|
+
process.stderr.write = originalStderrWrite;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function finishAgentSession(repoRoot, options, deps = {}) {
|
|
114
|
+
const finishRunner = deps.finishRunner || finishCommands.finish;
|
|
115
|
+
const output = deps.output || process.stdout;
|
|
116
|
+
const session = resolveAgentSessionForFinish(repoRoot, options);
|
|
117
|
+
const jsonMode = Boolean(options.json);
|
|
118
|
+
|
|
119
|
+
if (!session.branch) {
|
|
120
|
+
throw new Error(`Agent session '${session.id}' has no branch metadata.`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
updateAgentSession(repoRoot, session.id, { status: 'finishing' });
|
|
124
|
+
|
|
125
|
+
const finishArgs = [
|
|
126
|
+
'--target',
|
|
127
|
+
repoRoot,
|
|
128
|
+
'--branch',
|
|
129
|
+
session.branch,
|
|
130
|
+
...options.finishArgs,
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
if (!jsonMode) {
|
|
134
|
+
output.write(`[${TOOL_NAME}] Agent session: ${session.id}\n`);
|
|
135
|
+
output.write(`[${TOOL_NAME}] Branch: ${session.branch}\n`);
|
|
136
|
+
output.write(`[${TOOL_NAME}] Worktree: ${session.worktreePath || '(unknown)'}\n`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const runnerResult = jsonMode
|
|
141
|
+
? captureProcessOutput(() => finishRunner(finishArgs))
|
|
142
|
+
: { result: finishRunner(finishArgs), captured: {} };
|
|
143
|
+
const result = runnerResult.result;
|
|
144
|
+
const status = sessionStatusAfterFinish(finishArgs);
|
|
145
|
+
const evidence = buildFinishEvidence(session, finishArgs, status, result, runnerResult.captured);
|
|
146
|
+
updateAgentSession(repoRoot, session.id, {
|
|
147
|
+
status,
|
|
148
|
+
pr: { url: evidence.prUrl, state: evidence.mergeState },
|
|
149
|
+
finishEvidence: evidence,
|
|
150
|
+
});
|
|
151
|
+
if (!jsonMode) {
|
|
152
|
+
output.write(`[${TOOL_NAME}] Finish result: ${status}\n`);
|
|
153
|
+
}
|
|
154
|
+
return { session, status, result, finishArgs, evidence };
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const evidence = buildFinishEvidence(session, finishArgs, 'failed', null);
|
|
157
|
+
updateAgentSession(repoRoot, session.id, {
|
|
158
|
+
status: 'failed',
|
|
159
|
+
finishEvidence: evidence,
|
|
160
|
+
});
|
|
161
|
+
if (!jsonMode) {
|
|
162
|
+
output.write(`[${TOOL_NAME}] Finish result: failed\n`);
|
|
163
|
+
}
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
module.exports = {
|
|
169
|
+
buildFinishEvidence,
|
|
170
|
+
finishAgentSession,
|
|
171
|
+
resolveAgentSessionForFinish,
|
|
172
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
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 worktreePathForBranch(repoRoot, branch) {
|
|
49
|
+
const result = git(repoRoot, ['worktree', 'list', '--porcelain'], { allowFailure: true });
|
|
50
|
+
if (result.status !== 0) return { worktreePath: repoRoot, worktreeFound: false };
|
|
51
|
+
const match = parseWorktreeList(result.stdout).find((entry) => entry.branch === branch);
|
|
52
|
+
return {
|
|
53
|
+
worktreePath: match?.path || repoRoot,
|
|
54
|
+
worktreeFound: Boolean(match?.path),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function resolveBaseBranch(repoRoot, branch) {
|
|
59
|
+
return (
|
|
60
|
+
readGitConfig(repoRoot, `branch.${branch}.guardexBase`) ||
|
|
61
|
+
readGitConfig(repoRoot, 'multiagent.baseBranch') ||
|
|
62
|
+
DEFAULT_BASE_BRANCH
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function compareRefForBase(repoRoot, baseBranch) {
|
|
67
|
+
if (refExists(repoRoot, `refs/remotes/origin/${baseBranch}`)) {
|
|
68
|
+
return `origin/${baseBranch}`;
|
|
69
|
+
}
|
|
70
|
+
return baseBranch;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readLockRegistry(repoRoot, branch) {
|
|
74
|
+
const lockPath = path.join(repoRoot, LOCK_FILE_RELATIVE);
|
|
75
|
+
let parsed = null;
|
|
76
|
+
try {
|
|
77
|
+
parsed = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
|
|
78
|
+
} catch (_error) {
|
|
79
|
+
parsed = null;
|
|
80
|
+
}
|
|
81
|
+
const locks = parsed?.locks && typeof parsed.locks === 'object' && !Array.isArray(parsed.locks)
|
|
82
|
+
? parsed.locks
|
|
83
|
+
: {};
|
|
84
|
+
|
|
85
|
+
return Object.entries(locks)
|
|
86
|
+
.map(([filePath, entry]) => {
|
|
87
|
+
if (!entry || typeof entry !== 'object' || entry.branch !== branch) return null;
|
|
88
|
+
return {
|
|
89
|
+
file: filePath,
|
|
90
|
+
branch: entry.branch,
|
|
91
|
+
claimedAt: entry.claimed_at || '',
|
|
92
|
+
allowDelete: Boolean(entry.allow_delete),
|
|
93
|
+
};
|
|
94
|
+
})
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.sort((left, right) => left.file.localeCompare(right.file));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function splitGitLines(outputText) {
|
|
100
|
+
return String(outputText || '')
|
|
101
|
+
.split(/\r?\n/)
|
|
102
|
+
.map((line) => line.trim())
|
|
103
|
+
.filter(Boolean);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readUntrackedFiles(worktreePath) {
|
|
107
|
+
const result = git(worktreePath, ['ls-files', '--others', '--exclude-standard'], { allowFailure: true });
|
|
108
|
+
return result.status === 0
|
|
109
|
+
? splitGitLines(result.stdout).filter((filePath) => !INSPECT_EXCLUDE_PATHS.has(filePath))
|
|
110
|
+
: [];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function inspectAgentBranch(options) {
|
|
114
|
+
const repoRoot = resolveRepoRoot(options.target || process.cwd());
|
|
115
|
+
const branch = String(options.branch || '').trim();
|
|
116
|
+
if (!branch) {
|
|
117
|
+
throw new Error('--branch requires an agent branch name');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const baseBranch = resolveBaseBranch(repoRoot, branch);
|
|
121
|
+
const compareRef = compareRefForBase(repoRoot, baseBranch);
|
|
122
|
+
const { worktreePath, worktreeFound } = worktreePathForBranch(repoRoot, branch);
|
|
123
|
+
return { repoRoot, branch, baseBranch, compareRef, worktreePath, worktreeFound };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function changedFiles(options) {
|
|
127
|
+
const context = inspectAgentBranch(options);
|
|
128
|
+
const diffTarget = context.worktreeFound ? context.compareRef : `${context.compareRef}...${context.branch}`;
|
|
129
|
+
const result = git(context.worktreePath, ['diff', '--name-only', diffTarget, '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`]);
|
|
130
|
+
const files = [...splitGitLines(result.stdout), ...(context.worktreeFound ? readUntrackedFiles(context.worktreePath) : [])];
|
|
131
|
+
const uniqueFiles = Array.from(new Set(files)).sort((left, right) => left.localeCompare(right));
|
|
132
|
+
return { ...context, files: uniqueFiles };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function branchDiff(options) {
|
|
136
|
+
const context = inspectAgentBranch(options);
|
|
137
|
+
const diffTarget = context.worktreeFound ? context.compareRef : `${context.compareRef}...${context.branch}`;
|
|
138
|
+
const result = git(context.worktreePath, ['diff', diffTarget, '--', '.', `:(exclude)${LOCK_FILE_RELATIVE}`]);
|
|
139
|
+
const untrackedDiff = context.worktreeFound
|
|
140
|
+
? readUntrackedFiles(context.worktreePath)
|
|
141
|
+
.map((filePath) => git(context.worktreePath, ['diff', '--no-index', '--', '/dev/null', filePath], { allowFailure: true }).stdout || '')
|
|
142
|
+
.join('')
|
|
143
|
+
: '';
|
|
144
|
+
return { ...context, diff: `${result.stdout || ''}${untrackedDiff}` };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function branchLocks(options) {
|
|
148
|
+
const context = inspectAgentBranch(options);
|
|
149
|
+
return { ...context, locks: readLockRegistry(context.repoRoot, context.branch) };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function renderFiles(payload, { json = false } = {}) {
|
|
153
|
+
if (json) return `${JSON.stringify(payload, null, 2)}\n`;
|
|
154
|
+
return payload.files.length > 0 ? `${payload.files.join('\n')}\n` : '';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function renderDiff(payload, { json = false } = {}) {
|
|
158
|
+
if (json) return `${JSON.stringify(payload, null, 2)}\n`;
|
|
159
|
+
return payload.diff;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function renderLocks(payload, { json = false } = {}) {
|
|
163
|
+
if (json) return `${JSON.stringify(payload, null, 2)}\n`;
|
|
164
|
+
if (payload.locks.length === 0) return '';
|
|
165
|
+
return `${payload.locks
|
|
166
|
+
.map((lock) => `${lock.file}\t${lock.branch}\t${lock.claimedAt}\tallow_delete=${lock.allowDelete ? 'true' : 'false'}`)
|
|
167
|
+
.join('\n')}\n`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function runInspectCommand(options) {
|
|
171
|
+
if (options.subcommand === 'files') return renderFiles(changedFiles(options), options);
|
|
172
|
+
if (options.subcommand === 'diff') return renderDiff(branchDiff(options), options);
|
|
173
|
+
if (options.subcommand === 'locks') return renderLocks(branchLocks(options), options);
|
|
174
|
+
throw new Error(`Unknown agents subcommand: ${options.subcommand}`);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = {
|
|
178
|
+
branchDiff,
|
|
179
|
+
branchLocks,
|
|
180
|
+
changedFiles,
|
|
181
|
+
inspectAgentBranch,
|
|
182
|
+
parseWorktreeList,
|
|
183
|
+
readLockRegistry,
|
|
184
|
+
renderDiff,
|
|
185
|
+
renderFiles,
|
|
186
|
+
renderLocks,
|
|
187
|
+
resolveBaseBranch,
|
|
188
|
+
runInspectCommand,
|
|
189
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
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 buildAgentLaunchCommand(options) {
|
|
189
|
+
if (!options || typeof options !== 'object') {
|
|
190
|
+
throw new TypeError('options are required');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const { agentId, prompt, worktreePath, permissionMode, sessionId } = options;
|
|
194
|
+
const agent = resolveAgent(agentId);
|
|
195
|
+
const command = normalizeCommandParts(agent.launchCommand || agent.launch || agent.command || agent.executable || agent.bin);
|
|
196
|
+
const baseParts = [
|
|
197
|
+
...command,
|
|
198
|
+
...normalizeCommandParts(agent.defaultArgs),
|
|
199
|
+
...normalizeCommandParts(agent.args),
|
|
200
|
+
...normalizeCommandParts(agent.launchArgs),
|
|
201
|
+
...buildPermissionParts(agent, permissionMode),
|
|
202
|
+
...buildSessionParts(agent, sessionId),
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
if (baseParts.length === 0) {
|
|
206
|
+
throw new Error(`Unsupported agent: ${agentId}`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const launchCommand = buildPromptCommand(baseParts, agent, prompt);
|
|
210
|
+
const envPrefix = buildSessionEnv(agent, sessionId).join(' ');
|
|
211
|
+
const launchWithEnv = envPrefix ? `${envPrefix} ${launchCommand}` : launchCommand;
|
|
212
|
+
if (!worktreePath) return launchWithEnv;
|
|
213
|
+
return `cd ${shellQuote(worktreePath)} && ${launchWithEnv}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function buildAgentResumeCommand(agentId, permissionMode) {
|
|
217
|
+
const agent = resolveAgent(agentId);
|
|
218
|
+
const commandParts = normalizeCommandParts(agent.command || agent.executable || agent.bin);
|
|
219
|
+
const explicitResume = agent.resumeCommand || agent.resume;
|
|
220
|
+
const resumeArgs = normalizeCommandParts(agent.resumeArgs);
|
|
221
|
+
const resumeParts = explicitResume
|
|
222
|
+
? normalizeCommandParts(explicitResume)
|
|
223
|
+
: [...commandParts, ...(resumeArgs.length > 0 ? resumeArgs : ['resume'])];
|
|
224
|
+
const command = [
|
|
225
|
+
...resumeParts,
|
|
226
|
+
...buildPermissionParts(agent, permissionMode),
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
if (command.length === 0) {
|
|
230
|
+
throw new Error(`Unsupported agent: ${agentId}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return commandToShell(command);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
buildAgentLaunchCommand,
|
|
238
|
+
buildAgentResumeCommand,
|
|
239
|
+
shellQuote,
|
|
240
|
+
};
|