@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,591 @@
|
|
|
1
|
+
const {
|
|
2
|
+
path,
|
|
3
|
+
TOOL_NAME,
|
|
4
|
+
SHORT_TOOL_NAME,
|
|
5
|
+
} = require('../context');
|
|
6
|
+
const { runPackageAsset } = require('../core/runtime');
|
|
7
|
+
const { currentBranchName } = require('../git');
|
|
8
|
+
const { buildAgentLaunchCommand } = require('./launch');
|
|
9
|
+
const { resolveAgent } = require('./registry');
|
|
10
|
+
const {
|
|
11
|
+
applyAgentSelectionKey,
|
|
12
|
+
createAgentSelectionPanelState,
|
|
13
|
+
normalizeAgentSelections,
|
|
14
|
+
renderInteractiveAgentSelectionPanel,
|
|
15
|
+
renderAgentSelectionPanel,
|
|
16
|
+
selectionsFromPanelState,
|
|
17
|
+
selectedAgentCount,
|
|
18
|
+
} = require('./selection-panel');
|
|
19
|
+
const {
|
|
20
|
+
createAgentSession,
|
|
21
|
+
listAgentSessions,
|
|
22
|
+
updateAgentSession,
|
|
23
|
+
} = require('./sessions');
|
|
24
|
+
const { launchAgentTerminal } = require('./terminal');
|
|
25
|
+
|
|
26
|
+
function sanitizeSlug(value, fallback = 'task') {
|
|
27
|
+
const slug = String(value || '')
|
|
28
|
+
.toLowerCase()
|
|
29
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
30
|
+
.replace(/^-+/, '')
|
|
31
|
+
.replace(/-+$/, '')
|
|
32
|
+
.replace(/-{2,}/g, '-');
|
|
33
|
+
return slug || fallback;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizePositiveInt(value, fallback) {
|
|
37
|
+
const parsed = Number.parseInt(String(value || ''), 10);
|
|
38
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function shortenSlug(slug, maxLength) {
|
|
42
|
+
if (slug.length <= maxLength) return slug;
|
|
43
|
+
const shortened = slug.slice(0, maxLength).replace(/-+$/, '');
|
|
44
|
+
return shortened || slug.slice(0, maxLength);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function branchTimestamp(env = process.env, now = new Date()) {
|
|
48
|
+
if (env.GUARDEX_BRANCH_TIMESTAMP) {
|
|
49
|
+
return env.GUARDEX_BRANCH_TIMESTAMP;
|
|
50
|
+
}
|
|
51
|
+
const pad = (value) => String(value).padStart(2, '0');
|
|
52
|
+
return [
|
|
53
|
+
now.getFullYear(),
|
|
54
|
+
pad(now.getMonth() + 1),
|
|
55
|
+
pad(now.getDate()),
|
|
56
|
+
pad(now.getHours()),
|
|
57
|
+
pad(now.getMinutes()),
|
|
58
|
+
].join('-');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function worktreeLeaf(repoRoot, branchName) {
|
|
62
|
+
const repoPrefix = path.basename(repoRoot);
|
|
63
|
+
const withoutPrefix = branchName.startsWith('agent/') ? branchName.slice('agent/'.length) : branchName;
|
|
64
|
+
return `${repoPrefix}__${withoutPrefix.replace(/\//g, '__')}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function buildStartPlan(options, repoRoot, env = process.env) {
|
|
68
|
+
const requestedTask = String(options.task || '').trim();
|
|
69
|
+
const branchTask = String(options.branchTask || requestedTask).trim();
|
|
70
|
+
if (!requestedTask || !branchTask) {
|
|
71
|
+
throw new Error('gx agents start --dry-run requires a task');
|
|
72
|
+
}
|
|
73
|
+
const agent = resolveAgent(options.agent || 'codex');
|
|
74
|
+
const taskSlug = sanitizeSlug(branchTask, 'task');
|
|
75
|
+
const taskSlugMax = normalizePositiveInt(env.GUARDEX_BRANCH_TASK_SLUG_MAX, 40);
|
|
76
|
+
const branchDescriptor = `${shortenSlug(taskSlug, taskSlugMax)}-${branchTimestamp(env)}`;
|
|
77
|
+
const branchName = `agent/${agent.id}/${branchDescriptor}`;
|
|
78
|
+
const worktreeRoot = agent.worktreeRoot || (agent.id === 'claude' ? '.omc/agent-worktrees' : '.omx/agent-worktrees');
|
|
79
|
+
const worktreePath = path.join(repoRoot, worktreeRoot, worktreeLeaf(repoRoot, branchName));
|
|
80
|
+
const base = options.base || currentBranchName(repoRoot) || 'main';
|
|
81
|
+
return {
|
|
82
|
+
task: branchTask,
|
|
83
|
+
requestedTask,
|
|
84
|
+
taskSlug,
|
|
85
|
+
agent,
|
|
86
|
+
base,
|
|
87
|
+
branchName,
|
|
88
|
+
worktreePath,
|
|
89
|
+
claimedFiles: Array.isArray(options.claims) ? [...options.claims] : [],
|
|
90
|
+
metadata: options.metadata && typeof options.metadata === 'object' ? { ...options.metadata } : {},
|
|
91
|
+
tmux: options.tmux && typeof options.tmux === 'object' ? { ...options.tmux } : null,
|
|
92
|
+
launchCommand: buildAgentLaunchCommand({ agentId: agent.id, prompt: requestedTask, worktreePath }),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function launchTaskLabel(task, agentId, index, count) {
|
|
97
|
+
if (count <= 1) return task;
|
|
98
|
+
return `${task} ${agentId} ${String(index).padStart(2, '0')}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildLaunchOptions(options) {
|
|
102
|
+
const selections = normalizeAgentSelections(options);
|
|
103
|
+
const total = selectedAgentCount(selections);
|
|
104
|
+
const launchOptions = [];
|
|
105
|
+
let launchIndex = 0;
|
|
106
|
+
|
|
107
|
+
for (const selection of selections) {
|
|
108
|
+
for (let index = 1; index <= selection.count; index += 1) {
|
|
109
|
+
launchIndex += 1;
|
|
110
|
+
launchOptions.push({
|
|
111
|
+
...options,
|
|
112
|
+
agent: selection.agent.id,
|
|
113
|
+
branchTask: launchTaskLabel(options.task, selection.agent.id, index, selection.count),
|
|
114
|
+
launchIndex,
|
|
115
|
+
launchTotal: total,
|
|
116
|
+
agentAccountIndex: index,
|
|
117
|
+
agentAccountCount: selection.count,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return launchOptions;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function buildDryRunPayload(plan) {
|
|
126
|
+
const tmux = plan.tmux || {};
|
|
127
|
+
return {
|
|
128
|
+
schemaVersion: 1,
|
|
129
|
+
dryRun: true,
|
|
130
|
+
task: plan.task,
|
|
131
|
+
prompt: plan.requestedTask,
|
|
132
|
+
agent: plan.agent.id,
|
|
133
|
+
base: plan.base,
|
|
134
|
+
branch: plan.branchName,
|
|
135
|
+
worktree: plan.worktreePath,
|
|
136
|
+
worktreePath: plan.worktreePath,
|
|
137
|
+
claimedFiles: plan.claimedFiles,
|
|
138
|
+
launchCommand: plan.launchCommand,
|
|
139
|
+
tmuxSession: tmux.session || null,
|
|
140
|
+
tmuxTarget: tmux.target || null,
|
|
141
|
+
metadata: plan.metadata,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function renderDryRunPlan(plan) {
|
|
146
|
+
return [
|
|
147
|
+
'[gitguardex] Agents start dry-run:',
|
|
148
|
+
` task: ${plan.task}`,
|
|
149
|
+
plan.requestedTask && plan.requestedTask !== plan.task ? ` prompt: ${plan.requestedTask}` : null,
|
|
150
|
+
` agent: ${plan.agent.id}`,
|
|
151
|
+
` base: ${plan.base}`,
|
|
152
|
+
` task slug: ${plan.taskSlug}`,
|
|
153
|
+
` branch: ${plan.branchName}`,
|
|
154
|
+
` worktree: ${plan.worktreePath}`,
|
|
155
|
+
` launch: ${plan.launchCommand}`,
|
|
156
|
+
'[gitguardex] No branch, worktree, session metadata, or agent process was created.',
|
|
157
|
+
].filter(Boolean).join('\n');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function dryRunStart(options, repoRoot) {
|
|
161
|
+
const hasTask = Boolean(String(options.task || '').trim());
|
|
162
|
+
if (options.panel && !hasTask && !options.json) {
|
|
163
|
+
return renderAgentSelectionPanel({
|
|
164
|
+
task: '',
|
|
165
|
+
base: options.base,
|
|
166
|
+
claims: options.claims,
|
|
167
|
+
selections: normalizeAgentSelections(options),
|
|
168
|
+
taskInputActive: true,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const launchOptions = buildLaunchOptions(options);
|
|
173
|
+
const plans = launchOptions.map((launchOption) => buildStartPlan(launchOption, repoRoot));
|
|
174
|
+
if (options.json) {
|
|
175
|
+
if (plans.length === 1) {
|
|
176
|
+
return `${JSON.stringify(buildDryRunPayload(plans[0]), null, 2)}\n`;
|
|
177
|
+
}
|
|
178
|
+
return `${JSON.stringify({
|
|
179
|
+
schemaVersion: 1,
|
|
180
|
+
dryRun: true,
|
|
181
|
+
launches: plans.map(buildDryRunPayload),
|
|
182
|
+
}, null, 2)}\n`;
|
|
183
|
+
}
|
|
184
|
+
if (plans.length === 1 && !options.panel) {
|
|
185
|
+
return renderDryRunPlan(plans[0]);
|
|
186
|
+
}
|
|
187
|
+
if (options.noPanel) {
|
|
188
|
+
return plans.map(renderDryRunPlan).join('\n\n');
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return [
|
|
192
|
+
renderAgentSelectionPanel({
|
|
193
|
+
task: options.task,
|
|
194
|
+
base: options.base,
|
|
195
|
+
claims: options.claims,
|
|
196
|
+
selections: normalizeAgentSelections(options),
|
|
197
|
+
}).trimEnd(),
|
|
198
|
+
...plans.map(renderDryRunPlan),
|
|
199
|
+
].join('\n\n');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function panelSelectionSpecs(state) {
|
|
203
|
+
return selectionsFromPanelState(state).map((selection) => `${selection.agent.id}:${selection.count}`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function panelOptions(options, state) {
|
|
207
|
+
const specs = panelSelectionSpecs(state);
|
|
208
|
+
const first = selectionsFromPanelState(state)[0];
|
|
209
|
+
return {
|
|
210
|
+
...options,
|
|
211
|
+
task: String(state.task || '').trim(),
|
|
212
|
+
agent: first ? first.agent.id : options.agent,
|
|
213
|
+
count: 1,
|
|
214
|
+
agentSelectionSpecs: specs,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function executePanelSelection(repoRoot, options, state, deps = {}) {
|
|
219
|
+
const selectedOptions = panelOptions(options, state);
|
|
220
|
+
if (!selectedOptions.task) {
|
|
221
|
+
return {
|
|
222
|
+
status: 1,
|
|
223
|
+
stdout: '',
|
|
224
|
+
stderr: `[${TOOL_NAME}] Agent launch requires a task.\n`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
if (selectedOptions.dryRun) {
|
|
228
|
+
return {
|
|
229
|
+
status: 0,
|
|
230
|
+
stdout: dryRunStart({ ...selectedOptions, panel: false, noPanel: true }, repoRoot),
|
|
231
|
+
stderr: '',
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const startRunner = deps.startRunner || startAgentLane;
|
|
235
|
+
return startRunner(repoRoot, selectedOptions, deps);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function writeStream(stream, value) {
|
|
239
|
+
if (stream && typeof stream.write === 'function' && value) {
|
|
240
|
+
stream.write(String(value));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function shouldUseInteractivePanel(options = {}, stdin = process.stdin, stdout = process.stdout) {
|
|
245
|
+
return Boolean(options.panel && stdin && stdin.isTTY && stdout && stdout.isTTY);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function startInteractiveAgentPanel(repoRoot, options, deps = {}) {
|
|
249
|
+
const stdin = deps.stdin || process.stdin;
|
|
250
|
+
const stdout = deps.stdout || process.stdout;
|
|
251
|
+
const stderr = deps.stderr || process.stderr;
|
|
252
|
+
const onDone = typeof deps.onDone === 'function' ? deps.onDone : null;
|
|
253
|
+
let state = createAgentSelectionPanelState(options);
|
|
254
|
+
let stopped = false;
|
|
255
|
+
let rawModeEnabled = false;
|
|
256
|
+
|
|
257
|
+
function paint() {
|
|
258
|
+
if (stdout && stdout.isTTY) {
|
|
259
|
+
writeStream(stdout, '\x1b[?25l\x1b[H\x1b[2J\x1b[3J');
|
|
260
|
+
}
|
|
261
|
+
writeStream(stdout, renderInteractiveAgentSelectionPanel(state, {
|
|
262
|
+
color: Boolean(stdout && stdout.isTTY),
|
|
263
|
+
width: stdout && stdout.columns,
|
|
264
|
+
height: stdout && stdout.rows,
|
|
265
|
+
}));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function finish(result) {
|
|
269
|
+
if (onDone) onDone(result);
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function stop() {
|
|
274
|
+
if (stopped) return;
|
|
275
|
+
stopped = true;
|
|
276
|
+
if (stdin && typeof stdin.off === 'function') {
|
|
277
|
+
stdin.off('data', onData);
|
|
278
|
+
} else if (stdin && typeof stdin.removeListener === 'function') {
|
|
279
|
+
stdin.removeListener('data', onData);
|
|
280
|
+
}
|
|
281
|
+
if (rawModeEnabled && typeof stdin.setRawMode === 'function') {
|
|
282
|
+
stdin.setRawMode(false);
|
|
283
|
+
}
|
|
284
|
+
if (stdout && stdout.isTTY) {
|
|
285
|
+
writeStream(stdout, '\x1b[?25h');
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function launch() {
|
|
290
|
+
stop();
|
|
291
|
+
writeStream(stdout, '\n');
|
|
292
|
+
const result = executePanelSelection(repoRoot, options, state, deps);
|
|
293
|
+
writeStream(stdout, result.stdout);
|
|
294
|
+
writeStream(stderr, result.stderr);
|
|
295
|
+
return finish(result);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function cancel() {
|
|
299
|
+
stop();
|
|
300
|
+
const result = {
|
|
301
|
+
status: 130,
|
|
302
|
+
stdout: `[${TOOL_NAME}] Agent launch cancelled.\n`,
|
|
303
|
+
stderr: '',
|
|
304
|
+
};
|
|
305
|
+
writeStream(stdout, '\n');
|
|
306
|
+
writeStream(stdout, result.stdout);
|
|
307
|
+
return finish(result);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function dispatch(rawKey) {
|
|
311
|
+
const next = applyAgentSelectionKey(state, rawKey);
|
|
312
|
+
state = next.state;
|
|
313
|
+
if (next.action === 'launch') return launch();
|
|
314
|
+
if (next.action === 'cancel') return cancel();
|
|
315
|
+
paint();
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function onData(chunk) {
|
|
320
|
+
dispatch(chunk);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (!shouldUseInteractivePanel(options, stdin, stdout)) {
|
|
324
|
+
return finish(executePanelSelection(repoRoot, options, state, deps));
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (typeof stdin.setEncoding === 'function') stdin.setEncoding('utf8');
|
|
328
|
+
if (typeof stdin.setRawMode === 'function') {
|
|
329
|
+
stdin.setRawMode(true);
|
|
330
|
+
rawModeEnabled = true;
|
|
331
|
+
}
|
|
332
|
+
if (typeof stdin.resume === 'function') stdin.resume();
|
|
333
|
+
if (typeof stdin.on === 'function') stdin.on('data', onData);
|
|
334
|
+
paint();
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
dispatch,
|
|
338
|
+
stop,
|
|
339
|
+
getState: () => state,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function isSpawnFailure(result) {
|
|
344
|
+
return Boolean(result?.error) && typeof result?.status !== 'number';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function extractAgentBranchStartMetadata(output) {
|
|
348
|
+
const outputText = String(output || '');
|
|
349
|
+
const branchMatch = outputText.match(/^\[agent-branch-start\] (?:Created branch|Reusing existing branch): (.+)$/m);
|
|
350
|
+
const worktreeMatch = outputText.match(/^\[agent-branch-start\] Worktree: (.+)$/m);
|
|
351
|
+
return {
|
|
352
|
+
branch: branchMatch ? branchMatch[1].trim() : '',
|
|
353
|
+
worktreePath: worktreeMatch ? worktreeMatch[1].trim() : '',
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function sanitizeBranchForFile(branch) {
|
|
358
|
+
return String(branch || 'session')
|
|
359
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '__')
|
|
360
|
+
.replace(/^_+|_+$/g, '') || 'session';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function agentSessionIdForBranch(branch) {
|
|
364
|
+
return sanitizeBranchForFile(branch);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function buildSessionPayload(options, metadata, status, extra = {}) {
|
|
368
|
+
if (!metadata.branch || !metadata.worktreePath) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
id: extra.id || agentSessionIdForBranch(metadata.branch),
|
|
374
|
+
task: options.task,
|
|
375
|
+
agent: options.agent || 'codex',
|
|
376
|
+
branch: metadata.branch,
|
|
377
|
+
worktreePath: path.resolve(metadata.worktreePath),
|
|
378
|
+
base: options.base || null,
|
|
379
|
+
claims: Array.isArray(options.claims) ? [...options.claims] : [],
|
|
380
|
+
metadata: options.metadata && typeof options.metadata === 'object' ? { ...options.metadata } : {},
|
|
381
|
+
launchCommand: buildAgentLaunchCommand({
|
|
382
|
+
agentId: options.agent || 'codex',
|
|
383
|
+
prompt: options.task,
|
|
384
|
+
worktreePath: path.resolve(metadata.worktreePath),
|
|
385
|
+
}),
|
|
386
|
+
tmux: options.tmux && typeof options.tmux === 'object' ? { ...options.tmux } : null,
|
|
387
|
+
status,
|
|
388
|
+
...extra,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function findSessionByBranch(repoRoot, branch) {
|
|
393
|
+
return listAgentSessions(repoRoot).find((session) => session.branch === branch) || null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function writeAgentSession(repoRoot, options, metadata, status, extra = {}) {
|
|
397
|
+
const payload = buildSessionPayload(options, metadata, status, extra);
|
|
398
|
+
if (!payload) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const existing = findSessionByBranch(repoRoot, metadata.branch);
|
|
403
|
+
if (existing) {
|
|
404
|
+
return updateAgentSession(repoRoot, existing.id, payload);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return createAgentSession(repoRoot, payload);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function writeClaimFailedSession(repoRoot, options, metadata, claimResult) {
|
|
411
|
+
return writeAgentSession(repoRoot, options, metadata, 'claim-failed', {
|
|
412
|
+
claimFailure: {
|
|
413
|
+
claims: options.claims,
|
|
414
|
+
exitCode: typeof claimResult.status === 'number' ? claimResult.status : 1,
|
|
415
|
+
stderr: String(claimResult.stderr || '').trim(),
|
|
416
|
+
stdout: String(claimResult.stdout || '').trim(),
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function appendSessionId(stdout, session) {
|
|
422
|
+
if (!session?.id) return stdout;
|
|
423
|
+
return `${stdout}[${TOOL_NAME}] Agent session id: ${session.id}\n`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function buildBranchStartArgs(options) {
|
|
427
|
+
const args = ['--task', options.branchTask || options.task, '--agent', options.agent || 'codex'];
|
|
428
|
+
if (options.base) {
|
|
429
|
+
args.push('--base', options.base);
|
|
430
|
+
}
|
|
431
|
+
return args;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function buildRecoveryLines(metadata, claims, session) {
|
|
435
|
+
const quotedClaims = claims.map((claim) => JSON.stringify(claim)).join(' ');
|
|
436
|
+
const lines = [
|
|
437
|
+
`[${TOOL_NAME}] Claim failed after branch/worktree creation.`,
|
|
438
|
+
`[${TOOL_NAME}] Session status: claim-failed`,
|
|
439
|
+
];
|
|
440
|
+
if (session?.id) {
|
|
441
|
+
lines.push(`[${TOOL_NAME}] Agent session id: ${session.id}`);
|
|
442
|
+
}
|
|
443
|
+
if (metadata.worktreePath) {
|
|
444
|
+
lines.push(`[${TOOL_NAME}] Recovery: cd ${JSON.stringify(metadata.worktreePath)}`);
|
|
445
|
+
}
|
|
446
|
+
if (metadata.branch) {
|
|
447
|
+
lines.push(`[${TOOL_NAME}] Recovery: ${SHORT_TOOL_NAME} locks claim --branch ${JSON.stringify(metadata.branch)} ${quotedClaims}`);
|
|
448
|
+
}
|
|
449
|
+
lines.push(`[${TOOL_NAME}] Recovery: resolve the lock conflict or invalid path, then rerun the claim command above.`);
|
|
450
|
+
return `${lines.join('\n')}\n`;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function startSingleAgentLane(repoRoot, options, deps = {}) {
|
|
454
|
+
const packageAssetRunner = deps.packageAssetRunner || runPackageAsset;
|
|
455
|
+
const startResult = packageAssetRunner('branchStart', buildBranchStartArgs(options), { cwd: repoRoot });
|
|
456
|
+
let stdout = String(startResult.stdout || '');
|
|
457
|
+
let stderr = String(startResult.stderr || '');
|
|
458
|
+
if (isSpawnFailure(startResult)) {
|
|
459
|
+
return {
|
|
460
|
+
status: 1,
|
|
461
|
+
stdout,
|
|
462
|
+
stderr: `${stderr}${startResult.error.message}\n`,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (startResult.status !== 0) {
|
|
466
|
+
return {
|
|
467
|
+
status: typeof startResult.status === 'number' ? startResult.status : 1,
|
|
468
|
+
stdout,
|
|
469
|
+
stderr,
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const metadata = extractAgentBranchStartMetadata(stdout);
|
|
474
|
+
const session = writeAgentSession(repoRoot, options, metadata, 'active');
|
|
475
|
+
stdout = appendSessionId(stdout, session);
|
|
476
|
+
if (options.claims.length === 0) {
|
|
477
|
+
return { status: 0, stdout, stderr, session };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
if (!metadata.branch || !metadata.worktreePath) {
|
|
481
|
+
return {
|
|
482
|
+
status: 1,
|
|
483
|
+
stdout,
|
|
484
|
+
stderr: `${stderr}[${TOOL_NAME}] Unable to claim files: branch start output did not include branch/worktree metadata.\n`,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const claimResult = packageAssetRunner(
|
|
489
|
+
'lockTool',
|
|
490
|
+
['claim', '--branch', metadata.branch, ...options.claims],
|
|
491
|
+
{ cwd: metadata.worktreePath },
|
|
492
|
+
);
|
|
493
|
+
stdout += String(claimResult.stdout || '');
|
|
494
|
+
stderr += String(claimResult.stderr || '');
|
|
495
|
+
if (!isSpawnFailure(claimResult) && claimResult.status === 0) {
|
|
496
|
+
return { status: 0, stdout, stderr, session };
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (isSpawnFailure(claimResult)) {
|
|
500
|
+
stderr += `${claimResult.error.message}\n`;
|
|
501
|
+
}
|
|
502
|
+
const failedSession = writeClaimFailedSession(repoRoot, options, metadata, claimResult);
|
|
503
|
+
stdout += buildRecoveryLines(metadata, options.claims, failedSession);
|
|
504
|
+
return {
|
|
505
|
+
status: typeof claimResult.status === 'number' ? claimResult.status : 1,
|
|
506
|
+
stdout,
|
|
507
|
+
stderr,
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
function startAgentLane(repoRoot, options, deps = {}) {
|
|
512
|
+
const launchOptions = buildLaunchOptions(options);
|
|
513
|
+
if (launchOptions.length === 1) {
|
|
514
|
+
const result = startSingleAgentLane(repoRoot, launchOptions[0], deps);
|
|
515
|
+
if (result.status !== 0 || !result.session || !options.panel) {
|
|
516
|
+
return result;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const terminalResult = launchAgentTerminal(repoRoot, [result.session], {
|
|
520
|
+
terminal: options.terminal,
|
|
521
|
+
runner: deps.terminalRunner,
|
|
522
|
+
kittyBin: deps.kittyBin,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
...result,
|
|
527
|
+
stdout: `${String(result.stdout || '')}${String(terminalResult.stdout || '')}`,
|
|
528
|
+
stderr: `${String(result.stderr || '')}${String(terminalResult.stderr || '')}`,
|
|
529
|
+
terminal: terminalResult,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
let stdout = renderAgentSelectionPanel({
|
|
534
|
+
task: options.task,
|
|
535
|
+
base: options.base,
|
|
536
|
+
claims: options.claims,
|
|
537
|
+
selections: normalizeAgentSelections(options),
|
|
538
|
+
});
|
|
539
|
+
let stderr = '';
|
|
540
|
+
const sessions = [];
|
|
541
|
+
|
|
542
|
+
for (const launchOption of launchOptions) {
|
|
543
|
+
const result = startSingleAgentLane(repoRoot, launchOption, deps);
|
|
544
|
+
stdout += String(result.stdout || '');
|
|
545
|
+
stderr += String(result.stderr || '');
|
|
546
|
+
if (result.status !== 0) {
|
|
547
|
+
return {
|
|
548
|
+
status: result.status,
|
|
549
|
+
stdout,
|
|
550
|
+
stderr,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
if (result.session) {
|
|
554
|
+
sessions.push(result.session);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const terminalResult = launchAgentTerminal(repoRoot, sessions, {
|
|
559
|
+
terminal: options.terminal,
|
|
560
|
+
runner: deps.terminalRunner,
|
|
561
|
+
kittyBin: deps.kittyBin,
|
|
562
|
+
});
|
|
563
|
+
stdout += String(terminalResult.stdout || '');
|
|
564
|
+
stderr += String(terminalResult.stderr || '');
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
status: 0,
|
|
568
|
+
stdout,
|
|
569
|
+
stderr,
|
|
570
|
+
terminal: terminalResult,
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
module.exports = {
|
|
575
|
+
buildBranchStartArgs,
|
|
576
|
+
buildDryRunPayload,
|
|
577
|
+
buildLaunchOptions,
|
|
578
|
+
buildStartPlan,
|
|
579
|
+
buildRecoveryLines,
|
|
580
|
+
dryRunStart,
|
|
581
|
+
executePanelSelection,
|
|
582
|
+
extractAgentBranchStartMetadata,
|
|
583
|
+
agentSessionIdForBranch,
|
|
584
|
+
renderDryRunPlan,
|
|
585
|
+
sanitizeSlug,
|
|
586
|
+
shouldUseInteractivePanel,
|
|
587
|
+
startSingleAgentLane,
|
|
588
|
+
startInteractiveAgentPanel,
|
|
589
|
+
writeAgentSession,
|
|
590
|
+
startAgentLane,
|
|
591
|
+
};
|