@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,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
|
+
};
|
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
getAgentDefinitions,
|
|
5
|
+
resolveAgent,
|
|
6
|
+
} = require('./registry');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_MAX_SELECTED_AGENTS = 10;
|
|
9
|
+
const DEFAULT_PANEL_WIDTH = 118;
|
|
10
|
+
const DEFAULT_PANEL_HEIGHT = 30;
|
|
11
|
+
const SIDEBAR_WIDTH = 36;
|
|
12
|
+
const PANEL_ACTIONS = [
|
|
13
|
+
['n', 'New agent', 'create an agent pane in this repo'],
|
|
14
|
+
['t', 'Terminal', 'open Kitty agent terminal'],
|
|
15
|
+
['p', 'Project', 'create pane in another project'],
|
|
16
|
+
['Alt+Shift+M', 'Pane menu', 'act on the selected pane'],
|
|
17
|
+
['j/k', 'Jump', 'move between panes in the list'],
|
|
18
|
+
['m', 'Menu', 'open pane context actions'],
|
|
19
|
+
['x', 'Close', 'close selected pane'],
|
|
20
|
+
['b', 'Child worktree', 'branch from selected pane'],
|
|
21
|
+
['f', 'Files', 'browse selected worktree read-only'],
|
|
22
|
+
['h/H', 'Hide panes', 'hide one or isolate selected pane'],
|
|
23
|
+
['P', 'Project focus', 'show only the selected project'],
|
|
24
|
+
['a/A', 'Add to pane', 'agent or terminal in worktree'],
|
|
25
|
+
['r', 'Reopen', 'restore a closed worktree'],
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const PANEL_SHORTCUT_MESSAGES = {
|
|
29
|
+
'?': 'Shortcut map is shown on the right.',
|
|
30
|
+
t: 'Kitty agent terminals open after multi-agent launch; pass --terminal none to skip.',
|
|
31
|
+
p: 'Project panes are managed in gx cockpit; open cockpit, then press p.',
|
|
32
|
+
m: 'Pane menu is available in gx cockpit with m or Alt+Shift+M.',
|
|
33
|
+
'alt-shift-m': 'Pane menu is available in gx cockpit for the selected pane.',
|
|
34
|
+
x: 'Close is available from gx cockpit pane menu.',
|
|
35
|
+
b: 'Child worktrees are available from gx cockpit pane menu.',
|
|
36
|
+
f: 'File browser is available from gx cockpit pane menu.',
|
|
37
|
+
h: 'Hide/show panes from gx cockpit pane menu.',
|
|
38
|
+
H: 'Hide/show panes from gx cockpit pane menu.',
|
|
39
|
+
P: 'Project focus is available from gx cockpit pane menu.',
|
|
40
|
+
a: 'Add agent to worktree from gx cockpit pane menu.',
|
|
41
|
+
A: 'Add terminal to worktree from gx cockpit pane menu.',
|
|
42
|
+
r: 'Reopen closed worktrees from gx cockpit pane menu.',
|
|
43
|
+
s: 'Settings are available from gx cockpit.',
|
|
44
|
+
l: 'Logs are available from gx cockpit.',
|
|
45
|
+
L: 'Layout reset is available from gx cockpit.',
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const ANSI = {
|
|
49
|
+
reset: '\x1b[0m',
|
|
50
|
+
blue: '\x1b[34m',
|
|
51
|
+
brightBlue: '\x1b[94m',
|
|
52
|
+
cyan: '\x1b[36m',
|
|
53
|
+
inverse: '\x1b[7m',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function parsePositiveInteger(value, flagName) {
|
|
57
|
+
const parsed = Number.parseInt(String(value || ''), 10);
|
|
58
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
59
|
+
throw new Error(`${flagName} requires a positive integer`);
|
|
60
|
+
}
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseAgentSelectionToken(rawToken) {
|
|
65
|
+
const token = String(rawToken || '').trim();
|
|
66
|
+
if (!token) return null;
|
|
67
|
+
const match = token.match(/^([^:=\s]+)(?::|=)?(\d+)?$/);
|
|
68
|
+
if (!match) {
|
|
69
|
+
throw new Error(`Invalid agent selection: ${token}`);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
id: match[1],
|
|
73
|
+
count: match[2] ? parsePositiveInteger(match[2], '--agents') : 1,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function parseAgentSelectionSpec(rawSpec) {
|
|
78
|
+
return String(rawSpec || '')
|
|
79
|
+
.split(',')
|
|
80
|
+
.map(parseAgentSelectionToken)
|
|
81
|
+
.filter(Boolean);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeAgentSelections(options = {}) {
|
|
85
|
+
const maxSelected = options.maxSelected || DEFAULT_MAX_SELECTED_AGENTS;
|
|
86
|
+
const merged = new Map();
|
|
87
|
+
const addSelection = (selection) => {
|
|
88
|
+
const agent = resolveAgent(selection.id);
|
|
89
|
+
const existing = merged.get(agent.id) || { agent, count: 0 };
|
|
90
|
+
existing.count += selection.count;
|
|
91
|
+
merged.set(agent.id, existing);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const specs = Array.isArray(options.agentSelectionSpecs) ? options.agentSelectionSpecs : [];
|
|
95
|
+
if (specs.length > 0) {
|
|
96
|
+
specs.flatMap(parseAgentSelectionSpec).forEach(addSelection);
|
|
97
|
+
} else {
|
|
98
|
+
addSelection({
|
|
99
|
+
id: options.agent || 'codex',
|
|
100
|
+
count: options.count || 1,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const selections = [...merged.values()];
|
|
105
|
+
const total = selectedAgentCount(selections);
|
|
106
|
+
if (total > maxSelected) {
|
|
107
|
+
throw new Error(`Selected agent count ${total} exceeds the maximum ${maxSelected}`);
|
|
108
|
+
}
|
|
109
|
+
return selections;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function selectedAgentCount(selections) {
|
|
113
|
+
return selections.reduce((sum, selection) => sum + selection.count, 0);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function countForAgent(selections, agentId) {
|
|
117
|
+
const selection = selections.find((candidate) => candidate.agent.id === agentId);
|
|
118
|
+
return selection ? selection.count : 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function countsFromSelections(selections) {
|
|
122
|
+
const counts = {};
|
|
123
|
+
for (const selection of selections) {
|
|
124
|
+
counts[selection.agent.id] = selection.count;
|
|
125
|
+
}
|
|
126
|
+
return counts;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function clampIndex(index, length) {
|
|
130
|
+
if (length <= 0) return 0;
|
|
131
|
+
if (!Number.isInteger(index)) return 0;
|
|
132
|
+
return Math.max(0, Math.min(index, length - 1));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createAgentSelectionPanelState(options = {}) {
|
|
136
|
+
const definitions = getAgentDefinitions();
|
|
137
|
+
const maxSelected = options.maxSelected || DEFAULT_MAX_SELECTED_AGENTS;
|
|
138
|
+
const selections = normalizeAgentSelections({ ...options, maxSelected });
|
|
139
|
+
const counts = countsFromSelections(selections);
|
|
140
|
+
const firstSelectedIndex = definitions.findIndex((agent) => (counts[agent.id] || 0) > 0);
|
|
141
|
+
const task = String(options.task || '');
|
|
142
|
+
return {
|
|
143
|
+
task,
|
|
144
|
+
base: options.base || '',
|
|
145
|
+
claims: Array.isArray(options.claims) ? [...options.claims] : [],
|
|
146
|
+
maxSelected,
|
|
147
|
+
focusIndex: firstSelectedIndex >= 0 ? firstSelectedIndex : 0,
|
|
148
|
+
counts,
|
|
149
|
+
taskInputActive: Object.prototype.hasOwnProperty.call(options, 'taskInputActive')
|
|
150
|
+
? Boolean(options.taskInputActive)
|
|
151
|
+
: !task.trim(),
|
|
152
|
+
message: options.message || '',
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function selectionsFromPanelState(state = {}) {
|
|
157
|
+
const counts = state.counts || {};
|
|
158
|
+
return getAgentDefinitions()
|
|
159
|
+
.map((agent) => ({
|
|
160
|
+
agent,
|
|
161
|
+
count: Number.isInteger(counts[agent.id]) ? counts[agent.id] : 0,
|
|
162
|
+
}))
|
|
163
|
+
.filter((selection) => selection.count > 0);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function selectedCountFromPanelState(state = {}) {
|
|
167
|
+
return selectedAgentCount(selectionsFromPanelState(state));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function focusedAgent(state = {}) {
|
|
171
|
+
const definitions = getAgentDefinitions();
|
|
172
|
+
return definitions[clampIndex(state.focusIndex, definitions.length)] || definitions[0];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function withCount(state, agentId, count, message = '') {
|
|
176
|
+
return {
|
|
177
|
+
...state,
|
|
178
|
+
counts: {
|
|
179
|
+
...(state.counts || {}),
|
|
180
|
+
[agentId]: Math.max(0, count),
|
|
181
|
+
},
|
|
182
|
+
message,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function rawPanelKey(value) {
|
|
187
|
+
if (!value) return '';
|
|
188
|
+
return Buffer.isBuffer(value) ? value.toString('utf8') : String(value);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function normalizePanelKey(value) {
|
|
192
|
+
if (value && typeof value === 'object' && !Buffer.isBuffer(value)) {
|
|
193
|
+
if ((value.meta || value.alt) && value.shift && String(value.name || value.key || '').toLowerCase() === 'm') {
|
|
194
|
+
return 'alt-shift-m';
|
|
195
|
+
}
|
|
196
|
+
return normalizePanelKey(value.name || value.sequence || value.key || '');
|
|
197
|
+
}
|
|
198
|
+
const raw = rawPanelKey(value);
|
|
199
|
+
if (raw === '\u0003') return 'ctrl-c';
|
|
200
|
+
if (raw === '\u0015') return 'ctrl-u';
|
|
201
|
+
if (raw === '\u001bM') return 'alt-shift-m';
|
|
202
|
+
if (raw === '\u001b') return 'escape';
|
|
203
|
+
if (raw === '\r' || raw === '\n') return 'enter';
|
|
204
|
+
if (raw === '\u007f' || raw === '\b') return 'backspace';
|
|
205
|
+
if (raw === '\u001b[A') return 'up';
|
|
206
|
+
if (raw === '\u001b[B') return 'down';
|
|
207
|
+
if (raw.length === 1 && raw !== raw.toUpperCase()) return raw.toLowerCase();
|
|
208
|
+
return raw;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function printableTaskInput(value) {
|
|
212
|
+
const raw = rawPanelKey(value);
|
|
213
|
+
if (raw.length !== 1) return '';
|
|
214
|
+
const code = raw.charCodeAt(0);
|
|
215
|
+
if (code < 32 || code > 126) return '';
|
|
216
|
+
return raw;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function taskLaunchState(state) {
|
|
220
|
+
const task = String(state.task || '').trim();
|
|
221
|
+
if (!task) {
|
|
222
|
+
return {
|
|
223
|
+
state: {
|
|
224
|
+
...state,
|
|
225
|
+
taskInputActive: true,
|
|
226
|
+
message: 'Type a task before launch.',
|
|
227
|
+
},
|
|
228
|
+
action: 'render',
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
if (selectedCountFromPanelState(state) <= 0) {
|
|
232
|
+
return { state: { ...state, message: 'Select at least one agent before launch.' }, action: 'render' };
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
state: {
|
|
236
|
+
...state,
|
|
237
|
+
task,
|
|
238
|
+
taskInputActive: false,
|
|
239
|
+
message: '',
|
|
240
|
+
},
|
|
241
|
+
action: 'launch',
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function applyAgentSelectionKey(state = {}, rawKey) {
|
|
246
|
+
const definitions = getAgentDefinitions();
|
|
247
|
+
const current = {
|
|
248
|
+
...state,
|
|
249
|
+
focusIndex: clampIndex(state.focusIndex, definitions.length),
|
|
250
|
+
counts: { ...(state.counts || {}) },
|
|
251
|
+
message: '',
|
|
252
|
+
};
|
|
253
|
+
const key = normalizePanelKey(rawKey);
|
|
254
|
+
|
|
255
|
+
if (key === 'ctrl-c' || key === 'escape' || (!current.taskInputActive && key === 'q')) {
|
|
256
|
+
return { state: current, action: 'cancel' };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (current.taskInputActive) {
|
|
260
|
+
if (key === '?') {
|
|
261
|
+
return {
|
|
262
|
+
state: {
|
|
263
|
+
...current,
|
|
264
|
+
message: PANEL_SHORTCUT_MESSAGES['?'],
|
|
265
|
+
},
|
|
266
|
+
action: 'render',
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (key === 'enter') {
|
|
270
|
+
return taskLaunchState(current);
|
|
271
|
+
}
|
|
272
|
+
if (key === 'backspace') {
|
|
273
|
+
return {
|
|
274
|
+
state: {
|
|
275
|
+
...current,
|
|
276
|
+
task: String(current.task || '').slice(0, -1),
|
|
277
|
+
message: '',
|
|
278
|
+
},
|
|
279
|
+
action: 'render',
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (key === 'ctrl-u') {
|
|
283
|
+
return { state: { ...current, task: '', message: '' }, action: 'render' };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const input = printableTaskInput(rawKey);
|
|
287
|
+
if (input) {
|
|
288
|
+
return {
|
|
289
|
+
state: {
|
|
290
|
+
...current,
|
|
291
|
+
task: `${current.task || ''}${input}`,
|
|
292
|
+
message: '',
|
|
293
|
+
},
|
|
294
|
+
action: 'render',
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return { state: current, action: 'render' };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (key === 'enter' || key === 'n') {
|
|
302
|
+
return taskLaunchState(current);
|
|
303
|
+
}
|
|
304
|
+
if (PANEL_SHORTCUT_MESSAGES[key]) {
|
|
305
|
+
return { state: { ...current, message: PANEL_SHORTCUT_MESSAGES[key] }, action: 'render' };
|
|
306
|
+
}
|
|
307
|
+
if (key === 'up' || key === 'k') {
|
|
308
|
+
return {
|
|
309
|
+
state: {
|
|
310
|
+
...current,
|
|
311
|
+
focusIndex: (current.focusIndex - 1 + definitions.length) % definitions.length,
|
|
312
|
+
},
|
|
313
|
+
action: 'render',
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
if (key === 'down' || key === 'j') {
|
|
317
|
+
return {
|
|
318
|
+
state: {
|
|
319
|
+
...current,
|
|
320
|
+
focusIndex: (current.focusIndex + 1) % definitions.length,
|
|
321
|
+
},
|
|
322
|
+
action: 'render',
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const codexCount = current.counts.codex || 0;
|
|
327
|
+
const selectedCount = selectedCountFromPanelState(current);
|
|
328
|
+
if (key === '+') {
|
|
329
|
+
if (selectedCount >= current.maxSelected) {
|
|
330
|
+
return { state: { ...current, message: `Selected agent count cannot exceed ${current.maxSelected}.` }, action: 'render' };
|
|
331
|
+
}
|
|
332
|
+
return { state: withCount(current, 'codex', codexCount + 1), action: 'render' };
|
|
333
|
+
}
|
|
334
|
+
if (key === '-') {
|
|
335
|
+
return { state: withCount(current, 'codex', Math.max(0, codexCount - 1)), action: 'render' };
|
|
336
|
+
}
|
|
337
|
+
if (key === ' ' || key === 'space') {
|
|
338
|
+
const agent = focusedAgent(current);
|
|
339
|
+
const nextCount = current.counts[agent.id] > 0 ? 0 : 1;
|
|
340
|
+
if (nextCount > 0 && selectedCount >= current.maxSelected) {
|
|
341
|
+
return { state: { ...current, message: `Selected agent count cannot exceed ${current.maxSelected}.` }, action: 'render' };
|
|
342
|
+
}
|
|
343
|
+
return { state: withCount(current, agent.id, nextCount), action: 'render' };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return { state: current, action: 'render' };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function padLine(value, width) {
|
|
350
|
+
const text = String(value || '');
|
|
351
|
+
if (text.length >= width) return text.slice(0, width);
|
|
352
|
+
return `${text}${' '.repeat(width - text.length)}`;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function colorize(value, color, options = {}) {
|
|
356
|
+
if (!options.color) return value;
|
|
357
|
+
const code = ANSI[color];
|
|
358
|
+
return code ? `${code}${value}${ANSI.reset}` : value;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function panelWidth(options = {}) {
|
|
362
|
+
const width = Number(options.width);
|
|
363
|
+
if (!Number.isFinite(width)) return DEFAULT_PANEL_WIDTH;
|
|
364
|
+
return Math.max(80, Math.floor(width));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function panelHeight(options = {}) {
|
|
368
|
+
const height = Number(options.height);
|
|
369
|
+
if (!Number.isFinite(height)) return DEFAULT_PANEL_HEIGHT;
|
|
370
|
+
return Math.max(24, Math.floor(height) - 1);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function framePanel(title, rows, width = 92) {
|
|
374
|
+
const safeWidth = Math.max(40, width);
|
|
375
|
+
const titleText = ` ${title} `;
|
|
376
|
+
const topFill = Math.max(0, safeWidth - titleText.length - 2);
|
|
377
|
+
const top = `┌${titleText}${'─'.repeat(topFill)}┐`;
|
|
378
|
+
const bottom = `└${'─'.repeat(safeWidth - 2)}┘`;
|
|
379
|
+
return [
|
|
380
|
+
top,
|
|
381
|
+
...rows.map((row) => `│${padLine(` ${row}`, safeWidth - 2)}│`),
|
|
382
|
+
bottom,
|
|
383
|
+
].join('\n');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function renderSidebarRows(options, selections, definitions, width, height) {
|
|
387
|
+
const total = selectedAgentCount(selections);
|
|
388
|
+
const maxSelected = options.maxSelected || DEFAULT_MAX_SELECTED_AGENTS;
|
|
389
|
+
const codexAccounts = countForAgent(selections, 'codex');
|
|
390
|
+
const claims = Array.isArray(options.claims) && options.claims.length > 0
|
|
391
|
+
? options.claims.join(', ')
|
|
392
|
+
: 'none';
|
|
393
|
+
const topBars = '█'.repeat(Math.max(0, width - 13));
|
|
394
|
+
const taskText = options.task ? options.task : (options.taskInputActive ? '_' : '-');
|
|
395
|
+
const rows = [
|
|
396
|
+
`─ gx ${'─'.repeat(Math.max(0, width - 5))}`,
|
|
397
|
+
`▦ gitguardex ${topBars}`.slice(0, width),
|
|
398
|
+
options.taskInputActive
|
|
399
|
+
? ' Type task [?]help Esc cancel'
|
|
400
|
+
: ' [n] launch [t] terminal [?] help',
|
|
401
|
+
'',
|
|
402
|
+
' Select Agent(s)',
|
|
403
|
+
` Selected: ${total}/${maxSelected}`,
|
|
404
|
+
'',
|
|
405
|
+
...definitions.map((agent) => {
|
|
406
|
+
const count = countForAgent(selections, agent.id);
|
|
407
|
+
const marker = count > 0 ? '●' : '○';
|
|
408
|
+
const suffix = count > 1 ? ` x${count}` : '';
|
|
409
|
+
const focus = options.focusedAgentId === agent.id ? '› ' : ' ';
|
|
410
|
+
return `${focus}${marker} ${agent.label} ${agent.shortLabel.toLowerCase()}${suffix}`;
|
|
411
|
+
}),
|
|
412
|
+
'',
|
|
413
|
+
' Settings',
|
|
414
|
+
` task: ${taskText}`,
|
|
415
|
+
` base: ${options.base || 'current branch'}`,
|
|
416
|
+
` Codex accounts: ${codexAccounts}`,
|
|
417
|
+
` claims: ${claims}`,
|
|
418
|
+
options.message
|
|
419
|
+
? ` status: ${options.message}`
|
|
420
|
+
: (options.taskInputActive ? ' status: Type task, then Enter.' : ' status: Enter launches selected agent.'),
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
while (rows.length < height - 5) {
|
|
424
|
+
rows.push('');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
rows.push(
|
|
428
|
+
'─'.repeat(width),
|
|
429
|
+
' [l]ogs [p]rojects [s]ettings',
|
|
430
|
+
' Press [?] for keyboard shortcuts',
|
|
431
|
+
' Tip: multi-agent terminals: Kitty',
|
|
432
|
+
'',
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
return rows.slice(0, height).map((row) => padLine(row, width));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function boundedText(value, width) {
|
|
439
|
+
const text = String(value || '');
|
|
440
|
+
if (text.length <= width) return text;
|
|
441
|
+
if (width <= 3) return text.slice(0, width);
|
|
442
|
+
return `${text.slice(0, width - 3)}...`;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function renderPaneManagementRows(options, selections, width, height) {
|
|
446
|
+
const total = selectedAgentCount(selections);
|
|
447
|
+
const maxSelected = options.maxSelected || DEFAULT_MAX_SELECTED_AGENTS;
|
|
448
|
+
const task = String(options.task || '').trim();
|
|
449
|
+
const bodyWidth = Math.max(24, width - 2);
|
|
450
|
+
const actionWidth = Math.min(17, Math.max(12, Math.floor(bodyWidth * 0.24)));
|
|
451
|
+
const labelWidth = Math.min(18, Math.max(12, Math.floor(bodyWidth * 0.25)));
|
|
452
|
+
const detailWidth = Math.max(12, bodyWidth - actionWidth - labelWidth - 4);
|
|
453
|
+
const rows = [
|
|
454
|
+
`─ Welcome / Pane Management ${'─'.repeat(Math.max(0, bodyWidth - 27))}`,
|
|
455
|
+
`Selected ${total}/${maxSelected} · ${task ? `task ${boundedText(task, Math.max(8, bodyWidth - 22))}` : 'type a task to start'}`,
|
|
456
|
+
'',
|
|
457
|
+
`${padLine('Key', actionWidth)} ${padLine('Action', labelWidth)} Detail`,
|
|
458
|
+
`${'─'.repeat(actionWidth)} ${'─'.repeat(labelWidth)} ${'─'.repeat(detailWidth)}`,
|
|
459
|
+
...PANEL_ACTIONS.map(([key, label, detail]) => (
|
|
460
|
+
`${padLine(key, actionWidth)} ${padLine(label, labelWidth)} ${boundedText(detail, detailWidth)}`
|
|
461
|
+
)),
|
|
462
|
+
'',
|
|
463
|
+
'Navigation',
|
|
464
|
+
'↑/↓ or j/k move agent focus · Space toggles selected agent',
|
|
465
|
+
'+/- adjusts Codex account count · Enter launches new agent',
|
|
466
|
+
'',
|
|
467
|
+
'Text input',
|
|
468
|
+
'Type task text directly · Backspace edits · Ctrl+U clears',
|
|
469
|
+
'? keeps this shortcut map visible · Esc cancels',
|
|
470
|
+
];
|
|
471
|
+
|
|
472
|
+
while (rows.length < height) rows.push('');
|
|
473
|
+
return rows.slice(0, height).map((row) => padLine(row, width));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function renderMainRows(options, selections, width, height) {
|
|
477
|
+
return renderPaneManagementRows(options, selections, width, height);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function renderDmuxAgentSelectionPanel(options = {}) {
|
|
481
|
+
const definitions = getAgentDefinitions();
|
|
482
|
+
const maxSelected = options.maxSelected || DEFAULT_MAX_SELECTED_AGENTS;
|
|
483
|
+
const selections = options.selections || normalizeAgentSelections({ ...options, maxSelected });
|
|
484
|
+
const width = panelWidth(options);
|
|
485
|
+
const height = panelHeight(options);
|
|
486
|
+
const sidebarWidth = Math.min(SIDEBAR_WIDTH, Math.max(28, Math.floor(width * 0.38)));
|
|
487
|
+
const mainWidth = Math.max(42, width - sidebarWidth - 1);
|
|
488
|
+
const sidebar = renderSidebarRows({ ...options, maxSelected }, selections, definitions, sidebarWidth, height);
|
|
489
|
+
const main = renderMainRows({ ...options, maxSelected }, selections, mainWidth, height);
|
|
490
|
+
const keyText = options.taskInputActive
|
|
491
|
+
? ' Type task · Backspace edit · Enter launch · ESC cancel '
|
|
492
|
+
: ' ↑/↓ navigate · Space toggle · +/- Codex accounts · [n]/Enter launch · ESC cancel ';
|
|
493
|
+
const keyLine = padLine(keyText, width);
|
|
494
|
+
const lines = [];
|
|
495
|
+
|
|
496
|
+
for (let index = 0; index < height; index += 1) {
|
|
497
|
+
const left = colorize(sidebar[index] || ''.padEnd(sidebarWidth, ' '), 'cyan', options);
|
|
498
|
+
const divider = colorize('│', 'blue', options);
|
|
499
|
+
const right = colorize(main[index] || ''.padEnd(mainWidth, ' '), 'brightBlue', options);
|
|
500
|
+
lines.push(`${left}${divider}${right}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
lines.push(colorize(keyLine, 'inverse', options));
|
|
504
|
+
return `${lines.join('\n')}\n`;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function renderAgentSelectionPanel(options = {}) {
|
|
508
|
+
if (!options.compact) {
|
|
509
|
+
return renderDmuxAgentSelectionPanel(options);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const definitions = getAgentDefinitions();
|
|
513
|
+
const maxSelected = options.maxSelected || DEFAULT_MAX_SELECTED_AGENTS;
|
|
514
|
+
const selections = options.selections || normalizeAgentSelections({ ...options, maxSelected });
|
|
515
|
+
const total = selectedAgentCount(selections);
|
|
516
|
+
const claims = Array.isArray(options.claims) && options.claims.length > 0
|
|
517
|
+
? options.claims.join(', ')
|
|
518
|
+
: 'none';
|
|
519
|
+
const codexAccounts = countForAgent(selections, 'codex');
|
|
520
|
+
const rows = [
|
|
521
|
+
`Select one or more agents, then launch. Selected: ${total}/${maxSelected}`,
|
|
522
|
+
'',
|
|
523
|
+
...definitions.map((agent) => {
|
|
524
|
+
const count = countForAgent(selections, agent.id);
|
|
525
|
+
const marker = count > 0 ? '●' : '○';
|
|
526
|
+
const suffix = count > 1 ? ` x${count}` : '';
|
|
527
|
+
const focus = options.focusedAgentId === agent.id ? '› ' : '';
|
|
528
|
+
return `${focus}${marker} ${agent.label} ${agent.shortLabel.toLowerCase()}${suffix}`;
|
|
529
|
+
}),
|
|
530
|
+
'',
|
|
531
|
+
'Settings',
|
|
532
|
+
`task: ${options.task || '-'}`,
|
|
533
|
+
`base: ${options.base || 'current branch'}`,
|
|
534
|
+
`Codex accounts: ${codexAccounts}`,
|
|
535
|
+
`claims: ${claims}`,
|
|
536
|
+
options.message ? `status: ${options.message}` : null,
|
|
537
|
+
'',
|
|
538
|
+
options.taskInputActive
|
|
539
|
+
? 'Type task · Backspace edit · Enter launch · ESC cancel'
|
|
540
|
+
: '↑/↓ navigate · Space toggle · +/- Codex accounts · [n]/Enter launch · ESC cancel',
|
|
541
|
+
].filter((row) => row !== null);
|
|
542
|
+
return `${framePanel('Select Agent(s)', rows)}\n`;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function renderInteractiveAgentSelectionPanel(state = {}, options = {}) {
|
|
546
|
+
return renderAgentSelectionPanel({
|
|
547
|
+
task: state.task,
|
|
548
|
+
base: state.base,
|
|
549
|
+
claims: state.claims,
|
|
550
|
+
maxSelected: state.maxSelected,
|
|
551
|
+
selections: selectionsFromPanelState(state),
|
|
552
|
+
focusedAgentId: focusedAgent(state)?.id,
|
|
553
|
+
message: state.message,
|
|
554
|
+
taskInputActive: state.taskInputActive,
|
|
555
|
+
...options,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
module.exports = {
|
|
560
|
+
applyAgentSelectionKey,
|
|
561
|
+
createAgentSelectionPanelState,
|
|
562
|
+
DEFAULT_MAX_SELECTED_AGENTS,
|
|
563
|
+
countForAgent,
|
|
564
|
+
framePanel,
|
|
565
|
+
normalizeAgentSelections,
|
|
566
|
+
parseAgentSelectionSpec,
|
|
567
|
+
renderInteractiveAgentSelectionPanel,
|
|
568
|
+
renderAgentSelectionPanel,
|
|
569
|
+
selectionsFromPanelState,
|
|
570
|
+
selectedAgentCount,
|
|
571
|
+
};
|