@parallel-cli/parallel 0.3.3 → 0.4.1
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 +156 -82
- package/dist/agents/agent.js +24 -2
- package/dist/agents/tools.js +4 -2
- package/dist/commands.js +179 -135
- package/dist/config.js +79 -0
- package/dist/controller.js +58 -5
- package/dist/i18n.js +304 -40
- package/dist/index.js +4 -2
- package/dist/pricing.js +27 -0
- package/dist/server.js +2 -1
- package/dist/ui/AgentPanel.js +85 -16
- package/dist/ui/App.js +285 -86
- package/dist/ui/AttachApp.js +46 -21
- package/dist/ui/CommandInput.js +56 -15
- package/dist/ui/SettingsPanel.js +170 -55
- package/dist/ui/Timeline.js +60 -0
- package/dist/ui/Wizard.js +13 -6
- package/dist/ui/events.js +229 -0
- package/dist/ui/theme.js +5 -4
- package/dist/ui/tokens.js +77 -0
- package/dist/ui/views.js +9 -3
- package/package.json +2 -2
package/dist/commands.js
CHANGED
|
@@ -1,57 +1,64 @@
|
|
|
1
|
-
import { Controller } from './controller.js';
|
|
1
|
+
import { Controller, normalizeShellApprovalMode } from './controller.js';
|
|
2
2
|
import { createSkillTemplate, createSpecialistTemplate } from './skills.js';
|
|
3
3
|
import { t } from './i18n.js';
|
|
4
4
|
// Grouped by intent so /help reads as a story: create agents → steer them →
|
|
5
5
|
// inspect the session → git safety net → session & config → exit.
|
|
6
6
|
export const COMMANDS = [
|
|
7
7
|
// create agents
|
|
8
|
-
{ name: '/
|
|
9
|
-
{ name: '/
|
|
10
|
-
{ name: '/
|
|
11
|
-
{ name: '/
|
|
12
|
-
{ name: '/
|
|
13
|
-
{ name: '/
|
|
14
|
-
{ name: '/
|
|
8
|
+
{ name: '/ask', args: '[Name:] <question> [--model=m]', descKey: 'cmd.ask', group: 'modes', aliases: ['/a'] },
|
|
9
|
+
{ name: '/task', args: '[Name:] <task> [--model=m] [#skill]', descKey: 'cmd.task', group: 'modes', aliases: ['/t'] },
|
|
10
|
+
{ name: '/plan', args: '[Name:] <task> [--model=m]', descKey: 'cmd.plan', group: 'modes', aliases: ['/p'] },
|
|
11
|
+
{ name: '/issue', args: '<n>', descKey: 'cmd.issue', group: 'git' },
|
|
12
|
+
{ name: '/specialist', args: '<name> <task> | new <name> [global]', descKey: 'cmd.specialist', group: 'modes' },
|
|
13
|
+
{ name: '/specialists', args: '', descKey: 'cmd.specialists', group: 'views' },
|
|
14
|
+
{ name: '/skill', args: 'new <name> [global]', descKey: 'cmd.skill', group: 'settings' },
|
|
15
|
+
{ name: '/skills', args: '', descKey: 'cmd.skills', group: 'views' },
|
|
15
16
|
// steer agents
|
|
16
|
-
{ name: '/send', args: '<agent|all> <message>', descKey: 'cmd.send' },
|
|
17
|
-
{ name: '/attach', args: '<agent|on|off>', descKey: 'cmd.attach' },
|
|
18
|
-
{ name: '/focus', args: '<agent|off>', descKey: 'cmd.focus' },
|
|
19
|
-
{ name: '/pause', args: '<agent|all>', descKey: 'cmd.pause' },
|
|
20
|
-
{ name: '/resume', args: '<agent|all>', descKey: 'cmd.resume' },
|
|
21
|
-
{ name: '/stop', args: '<agent|all>', descKey: 'cmd.stop' },
|
|
22
|
-
{ name: '/clear', args: '', descKey: 'cmd.clear' },
|
|
17
|
+
{ name: '/send', args: '<agent|all> <message>', descKey: 'cmd.send', group: 'control' },
|
|
18
|
+
{ name: '/attach', args: '<agent|on|off>', descKey: 'cmd.attach', group: 'control' },
|
|
19
|
+
{ name: '/focus', args: '<agent|off>', descKey: 'cmd.focus', group: 'control' },
|
|
20
|
+
{ name: '/pause', args: '<agent|all>', descKey: 'cmd.pause', group: 'control' },
|
|
21
|
+
{ name: '/resume', args: '<agent|all>', descKey: 'cmd.resume', group: 'control' },
|
|
22
|
+
{ name: '/stop', args: '<agent|all>', descKey: 'cmd.stop', group: 'control' },
|
|
23
|
+
{ name: '/clear', args: '', descKey: 'cmd.clear', group: 'control' },
|
|
24
|
+
{ name: '/raw', args: '', descKey: 'cmd.raw', group: 'control' },
|
|
25
|
+
{ name: '/copy', args: '', descKey: 'cmd.copy', group: 'control' },
|
|
23
26
|
// git safety net
|
|
24
|
-
{ name: '/undo', args: '[agent]', descKey: 'cmd.undo' },
|
|
25
|
-
{ name: '/commit', args: '[agent|all] [message]', descKey: 'cmd.commit' },
|
|
26
|
-
{ name: '/autocommit', args: '<on|off>', descKey: 'cmd.autocommit' },
|
|
27
|
+
{ name: '/undo', args: '[agent]', descKey: 'cmd.undo', group: 'git' },
|
|
28
|
+
{ name: '/commit', args: '[agent|all] [message]', descKey: 'cmd.commit', group: 'git' },
|
|
29
|
+
{ name: '/autocommit', args: '<on|off>', descKey: 'cmd.autocommit', group: 'git' },
|
|
27
30
|
// inspect the session
|
|
28
|
-
{ name: '/agents', args: '', descKey: 'cmd.agents' },
|
|
29
|
-
{ name: '/board', args: '', descKey: 'cmd.board' },
|
|
30
|
-
{ name: '/notes', args: '', descKey: 'cmd.notes' },
|
|
31
|
-
{ name: '/diff', args: '', descKey: 'cmd.diff' },
|
|
32
|
-
{ name: '/cost', args: '', descKey: 'cmd.cost' },
|
|
31
|
+
{ name: '/agents', args: '', descKey: 'cmd.agents', group: 'views' },
|
|
32
|
+
{ name: '/board', args: '', descKey: 'cmd.board', group: 'views' },
|
|
33
|
+
{ name: '/notes', args: '', descKey: 'cmd.notes', group: 'views' },
|
|
34
|
+
{ name: '/diff', args: '', descKey: 'cmd.diff', group: 'views' },
|
|
35
|
+
{ name: '/cost', args: '', descKey: 'cmd.cost', group: 'views' },
|
|
36
|
+
{ name: '/status', args: '', descKey: 'cmd.status', group: 'views' },
|
|
33
37
|
// sessions
|
|
34
|
-
{ name: '/save', args: '[name]', descKey: 'cmd.save' },
|
|
35
|
-
{ name: '/sessions', args: '', descKey: 'cmd.sessions' },
|
|
36
|
-
{ name: '/session', args: '[n|latest]', descKey: 'cmd.session' },
|
|
37
|
-
{ name: '/restore', args: '<agent>', descKey: 'cmd.restore' },
|
|
38
|
+
{ name: '/save', args: '[name]', descKey: 'cmd.save', group: 'git' },
|
|
39
|
+
{ name: '/sessions', args: '', descKey: 'cmd.sessions', group: 'git' },
|
|
40
|
+
{ name: '/session', args: '[n|latest]', descKey: 'cmd.session', group: 'git' },
|
|
41
|
+
{ name: '/restore', args: '<agent>', descKey: 'cmd.restore', group: 'git' },
|
|
38
42
|
// config
|
|
39
|
-
{ name: '/model', args: '[[provider:]model]', descKey: 'cmd.model' },
|
|
40
|
-
{ name: '/key', args: '<key>', descKey: 'cmd.key' },
|
|
41
|
-
{ name: '/approvals', args: '<ask|auto>', descKey: 'cmd.approvals' },
|
|
42
|
-
{ name: '/sound', args: '<on|off>', descKey: 'cmd.sound' },
|
|
43
|
-
{ name: '/settings', args: '', descKey: 'cmd.settings' },
|
|
44
|
-
{ name: '/settings-session', args: '', descKey: 'cmd.ssettings', aliases: ['/ssettings'] },
|
|
45
|
-
{ name: '/doctor', args: '', descKey: 'cmd.doctor' },
|
|
43
|
+
{ name: '/model', args: '[[provider:]model]', descKey: 'cmd.model', group: 'settings' },
|
|
44
|
+
{ name: '/key', args: '<key>', descKey: 'cmd.key', group: 'settings' },
|
|
45
|
+
{ name: '/approvals', args: '<ask|auto|auto-safe|yolo>', descKey: 'cmd.approvals', group: 'settings' },
|
|
46
|
+
{ name: '/sound', args: '<on|off>', descKey: 'cmd.sound', group: 'settings' },
|
|
47
|
+
{ name: '/settings', args: '', descKey: 'cmd.settings', group: 'settings' },
|
|
48
|
+
{ name: '/settings-session', args: '', descKey: 'cmd.ssettings', group: 'settings', aliases: ['/ssettings'] },
|
|
49
|
+
{ name: '/doctor', args: '', descKey: 'cmd.doctor', group: 'settings' },
|
|
46
50
|
// exit
|
|
47
|
-
{ name: '/help', args: '', descKey: 'cmd.help' },
|
|
48
|
-
{ name: '/quit', args: '', descKey: 'cmd.quit', aliases: ['/exit'] },
|
|
51
|
+
{ name: '/help', args: '', descKey: 'cmd.help', group: 'other' },
|
|
52
|
+
{ name: '/quit', args: '', descKey: 'cmd.quit', group: 'other', aliases: ['/exit'] },
|
|
49
53
|
];
|
|
50
|
-
export function
|
|
54
|
+
export function visibleCommands() {
|
|
55
|
+
return COMMANDS.filter((c) => !c.hidden);
|
|
56
|
+
}
|
|
57
|
+
export function matchCommands(input, opts = {}) {
|
|
51
58
|
if (!input.startsWith('/'))
|
|
52
59
|
return [];
|
|
53
60
|
const word = input.split(/\s+/)[0].toLowerCase();
|
|
54
|
-
return COMMANDS.filter((c) => c.name.startsWith(word) || c.aliases?.some((a) => a.startsWith(word)));
|
|
61
|
+
return COMMANDS.filter((c) => opts.includeHidden || !c.hidden).filter((c) => c.name.startsWith(word) || c.aliases?.some((a) => a.startsWith(word)));
|
|
55
62
|
}
|
|
56
63
|
function agentList(ctl) {
|
|
57
64
|
return [...ctl.board.agents.values()].map((a) => a.name).join(', ') || t('m.none');
|
|
@@ -64,18 +71,14 @@ function soloAgent(ctl) {
|
|
|
64
71
|
const list = [...ctl.board.agents.values()];
|
|
65
72
|
return list.length === 1 ? list[0].name : null;
|
|
66
73
|
}
|
|
67
|
-
|
|
68
|
-
const PLAN_FIRST = `
|
|
69
|
-
|
|
70
|
-
PLAN-FIRST MODE — MANDATORY: before modifying ANY file, explore the code (list_files, read_file, search), then present your implementation plan to the user with ask_user: the question is the full plan (steps + files you will touch + risks), the options are ["Approve", "Revise"], recommended = "Approve". If the user answers "Revise", ask what to change (ask_user) and present an updated plan. Start editing files ONLY after an explicit "Approve".`;
|
|
71
|
-
function spawnFrom(arg, ctl, ui, images, specialist, plan = false) {
|
|
74
|
+
function spawnFrom(arg, ctl, ui, images, specialist, mode = 'task') {
|
|
72
75
|
const p = ctl.sessionProvider();
|
|
73
76
|
if (!p)
|
|
74
|
-
return ui.system(t('m.missingProvider'));
|
|
77
|
+
return ui.system(t('m.missingProvider'), 'error');
|
|
75
78
|
if (!p.apiKey)
|
|
76
|
-
return ui.system(t('m.missingKey', { name: p.name }));
|
|
79
|
+
return ui.system(t('m.missingKey', { name: p.name }), 'error');
|
|
77
80
|
if (!ctl.session.model && !p.defaultModel && !p.models[0])
|
|
78
|
-
return ui.system(t('m.missingModel', { name: p.name }));
|
|
81
|
+
return ui.system(t('m.missingModel', { name: p.name }), 'error');
|
|
79
82
|
// optional --model=xxx flag
|
|
80
83
|
let model;
|
|
81
84
|
let task = arg;
|
|
@@ -101,14 +104,14 @@ function spawnFrom(arg, ctl, ui, images, specialist, plan = false) {
|
|
|
101
104
|
}
|
|
102
105
|
// optional "Name:" prefix
|
|
103
106
|
const named = task.match(/^([\p{L}\p{N}_-]{1,16}):\s+(.+)$/su);
|
|
104
|
-
const finalTask =
|
|
105
|
-
const agent = ctl.spawnAgent(finalTask, named ? named[1] : undefined, model, images, specialist);
|
|
107
|
+
const finalTask = named ? named[2] : task;
|
|
108
|
+
const agent = ctl.spawnAgent(finalTask, named ? named[1] : undefined, model, images, specialist, undefined, mode);
|
|
106
109
|
if (!agent)
|
|
107
|
-
return ui.system(specialist ? t('m.noSpecialist', { name: specialist }) : t('m.spawnFail'));
|
|
110
|
+
return ui.system(specialist ? t('m.noSpecialist', { name: specialist }) : t('m.spawnFail'), 'error');
|
|
108
111
|
ui.system(t('m.spawned', { name: agent.name, model: model ? ` (${model})` : '' }) +
|
|
109
|
-
|
|
112
|
+
` /${mode}` +
|
|
110
113
|
(specialist ? ` 🎓${specialist}` : '') +
|
|
111
|
-
(forced.length > 0 ? ` 🧩${forced.join(',')}` : ''));
|
|
114
|
+
(forced.length > 0 ? ` 🧩${forced.join(',')}` : ''), 'info');
|
|
112
115
|
}
|
|
113
116
|
export function executeInput(raw, ctl, ui, images) {
|
|
114
117
|
const input = raw.trim();
|
|
@@ -117,20 +120,20 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
117
120
|
// "@Agent message" or "@all message" → live instruction
|
|
118
121
|
if (input.startsWith('@')) {
|
|
119
122
|
if (images?.length)
|
|
120
|
-
ui.system(t('m.imagesIgnored'));
|
|
123
|
+
ui.system(t('m.imagesIgnored'), 'warn');
|
|
121
124
|
const m = input.match(/^@(\S+)\s+(.+)$/s);
|
|
122
125
|
if (!m)
|
|
123
|
-
return ui.system(t('m.usageAt'));
|
|
126
|
+
return ui.system(t('m.usageAt'), 'warn');
|
|
124
127
|
const [, target, content] = m;
|
|
125
128
|
if (target.toLowerCase() === 'all') {
|
|
126
129
|
ctl.broadcast(content);
|
|
127
|
-
ui.system(t('m.broadcast'));
|
|
130
|
+
ui.system(t('m.broadcast'), 'ok');
|
|
128
131
|
}
|
|
129
132
|
else if (ctl.sendToAgent(target, content)) {
|
|
130
|
-
ui.system(t('m.sent', { target }));
|
|
133
|
+
ui.system(t('m.sent', { target }), 'ok');
|
|
131
134
|
}
|
|
132
135
|
else {
|
|
133
|
-
ui.system(t('m.notFound', { target, list: agentList(ctl) }));
|
|
136
|
+
ui.system(t('m.notFound', { target, list: agentList(ctl) }), 'error');
|
|
134
137
|
}
|
|
135
138
|
return;
|
|
136
139
|
}
|
|
@@ -140,51 +143,68 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
140
143
|
return;
|
|
141
144
|
}
|
|
142
145
|
if (images?.length)
|
|
143
|
-
ui.system(t('m.imagesIgnored'));
|
|
144
|
-
const [
|
|
146
|
+
ui.system(t('m.imagesIgnored'), 'warn');
|
|
147
|
+
const [rawCmd, ...rest] = input.split(/\s+/);
|
|
148
|
+
const cmd = rawCmd.toLowerCase() === '/a'
|
|
149
|
+
? '/ask'
|
|
150
|
+
: rawCmd.toLowerCase() === '/t'
|
|
151
|
+
? '/task'
|
|
152
|
+
: rawCmd.toLowerCase() === '/p'
|
|
153
|
+
? '/plan'
|
|
154
|
+
: rawCmd.toLowerCase() === '/ssettings'
|
|
155
|
+
? '/settings-session'
|
|
156
|
+
: rawCmd.toLowerCase() === '/exit'
|
|
157
|
+
? '/quit'
|
|
158
|
+
: rawCmd;
|
|
145
159
|
const arg = rest.join(' ').trim();
|
|
146
160
|
switch (cmd.toLowerCase()) {
|
|
147
|
-
case '/
|
|
161
|
+
case '/ask': {
|
|
148
162
|
if (!arg)
|
|
149
|
-
return ui.system(t('m.
|
|
150
|
-
spawnFrom(arg, ctl, ui, images);
|
|
163
|
+
return ui.system(t('m.usageAsk'), 'warn');
|
|
164
|
+
spawnFrom(arg, ctl, ui, images, undefined, 'ask');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
case '/task': {
|
|
168
|
+
if (!arg)
|
|
169
|
+
return ui.system(t('m.usageSpawn'), 'warn');
|
|
170
|
+
spawnFrom(arg, ctl, ui, images, undefined, 'task');
|
|
151
171
|
return;
|
|
152
172
|
}
|
|
153
173
|
case '/plan': {
|
|
154
174
|
// Plan-first agent: presents its plan (ask_user) and waits for approval
|
|
155
175
|
// before touching any file.
|
|
156
176
|
if (!arg)
|
|
157
|
-
return ui.system(t('m.usagePlan'));
|
|
158
|
-
spawnFrom(arg, ctl, ui, images, undefined,
|
|
177
|
+
return ui.system(t('m.usagePlan'), 'warn');
|
|
178
|
+
spawnFrom(arg, ctl, ui, images, undefined, 'plan');
|
|
159
179
|
return;
|
|
160
180
|
}
|
|
161
181
|
case '/issue': {
|
|
162
182
|
// Import a task from GitHub Issues (requires the gh CLI, authenticated).
|
|
163
183
|
const n = Number.parseInt(arg, 10);
|
|
164
184
|
if (!arg || Number.isNaN(n))
|
|
165
|
-
return ui.system(t('m.usageIssue'));
|
|
185
|
+
return ui.system(t('m.usageIssue'), 'warn');
|
|
166
186
|
const issue = ctl.fetchIssue(n);
|
|
167
187
|
if ('error' in issue) {
|
|
168
|
-
return ui.system(issue.error === 'gh-missing' ? t('m.ghMissing') : t('m.issueFail', { msg: issue.error }));
|
|
188
|
+
return ui.system(issue.error === 'gh-missing' ? t('m.ghMissing') : t('m.issueFail', { msg: issue.error }), 'error');
|
|
169
189
|
}
|
|
170
190
|
const task = `GitHub issue #${issue.number}: ${issue.title}\n\n${issue.body || '(no description)'}\n\nResolve this issue.`;
|
|
171
191
|
const agent = ctl.spawnAgent(task);
|
|
172
192
|
if (!agent)
|
|
173
|
-
return ui.system(t('m.spawnFail'));
|
|
174
|
-
ui.system(t('m.issueSpawned', { n: String(issue.number), name: agent.name, title: issue.title.slice(0, 60) }));
|
|
193
|
+
return ui.system(t('m.spawnFail'), 'error');
|
|
194
|
+
ui.system(t('m.issueSpawned', { n: String(issue.number), name: agent.name, title: issue.title.slice(0, 60) }), 'info');
|
|
175
195
|
return;
|
|
176
196
|
}
|
|
177
197
|
case '/undo': {
|
|
178
198
|
// Revert the agent's LAST file change (blackboard checkpoint).
|
|
179
199
|
const who = arg || soloAgent(ctl);
|
|
180
200
|
if (!who)
|
|
181
|
-
return ui.system(t('m.usageUndo'));
|
|
201
|
+
return ui.system(t('m.usageUndo'), 'warn');
|
|
182
202
|
const r = ctl.undoAgent(who);
|
|
183
203
|
if (r === null)
|
|
184
|
-
return ui.system(t('m.notFound', { target: who, list: agentList(ctl) }));
|
|
204
|
+
return ui.system(t('m.notFound', { target: who, list: agentList(ctl) }), 'error');
|
|
185
205
|
if (r === 'none')
|
|
186
|
-
return ui.system(t('m.undoNone', { name: who }));
|
|
187
|
-
ui.system(t('m.undone', { name: who, path: r.path }) + (r.conflict ? ' ' + t('m.undoConflict') : ''));
|
|
206
|
+
return ui.system(t('m.undoNone', { name: who }), 'info');
|
|
207
|
+
ui.system(t('m.undone', { name: who, path: r.path }) + (r.conflict ? ' ' + t('m.undoConflict') : ''), r.conflict ? 'warn' : 'ok');
|
|
188
208
|
return;
|
|
189
209
|
}
|
|
190
210
|
case '/commit': {
|
|
@@ -192,21 +212,21 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
192
212
|
const [target0, ...msg] = rest;
|
|
193
213
|
const target = target0 || soloAgent(ctl);
|
|
194
214
|
if (!target)
|
|
195
|
-
return ui.system(t('m.usageCommit'));
|
|
215
|
+
return ui.system(t('m.usageCommit'), 'warn');
|
|
196
216
|
const r = ctl.commitFor(target, msg.join(' ').trim() || undefined);
|
|
197
217
|
if (r.ok)
|
|
198
|
-
return ui.system(t('m.committed', { name: target, files: String(r.files) }));
|
|
218
|
+
return ui.system(t('m.committed', { name: target, files: String(r.files) }), 'ok');
|
|
199
219
|
if (r.reason === 'not-found')
|
|
200
|
-
return ui.system(t('m.notFound', { target, list: agentList(ctl) }));
|
|
220
|
+
return ui.system(t('m.notFound', { target, list: agentList(ctl) }), 'error');
|
|
201
221
|
if (r.reason === 'no-changes')
|
|
202
|
-
return ui.system(t('m.commitNone', { name: target }));
|
|
203
|
-
return ui.system(t('m.commitFail', { msg: r.detail ?? '' }));
|
|
222
|
+
return ui.system(t('m.commitNone', { name: target }), 'info');
|
|
223
|
+
return ui.system(t('m.commitFail', { msg: r.detail ?? '' }), 'error');
|
|
204
224
|
}
|
|
205
225
|
case '/autocommit': {
|
|
206
226
|
if (arg !== 'on' && arg !== 'off')
|
|
207
|
-
return ui.system(t('m.usageAutocommit', { state: ctl.autoCommit ? 'on' : 'off' }));
|
|
227
|
+
return ui.system(t('m.usageAutocommit', { state: ctl.autoCommit ? 'on' : 'off' }), 'warn');
|
|
208
228
|
ctl.autoCommit = arg === 'on';
|
|
209
|
-
ui.system(t('m.autocommit', { state: arg }));
|
|
229
|
+
ui.system(t('m.autocommit', { state: arg }), 'info');
|
|
210
230
|
return;
|
|
211
231
|
}
|
|
212
232
|
case '/agents':
|
|
@@ -224,27 +244,27 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
224
244
|
}
|
|
225
245
|
const sessions = Controller.listSessions(ctl.projectRoot);
|
|
226
246
|
if (sessions.length === 0)
|
|
227
|
-
return ui.system(t('m.usageSession'));
|
|
247
|
+
return ui.system(t('m.usageSession'), 'warn');
|
|
228
248
|
const idx = arg.toLowerCase() === 'latest' ? 0 : Number.parseInt(arg, 10) - 1;
|
|
229
249
|
const session = sessions[idx];
|
|
230
250
|
if (!session)
|
|
231
|
-
return ui.system(t('m.usageSession'));
|
|
251
|
+
return ui.system(t('m.usageSession'), 'warn');
|
|
232
252
|
ctl.loadSession(session.data);
|
|
233
|
-
ui.system(t('m.sessionLoaded', { date: new Date(session.data.savedAt).toLocaleString() }));
|
|
253
|
+
ui.system(t('m.sessionLoaded', { date: new Date(session.data.savedAt).toLocaleString() }), 'ok');
|
|
234
254
|
return;
|
|
235
255
|
}
|
|
236
256
|
case '/restore': {
|
|
237
257
|
// Relaunch an agent from the restored session with its FULL conversation.
|
|
238
258
|
if (!arg)
|
|
239
|
-
return ui.system(t('m.usageRestore'));
|
|
259
|
+
return ui.system(t('m.usageRestore'), 'warn');
|
|
240
260
|
if (!ctl.loadedSession)
|
|
241
|
-
return ui.system(t('m.usageSession'));
|
|
261
|
+
return ui.system(t('m.usageSession'), 'warn');
|
|
242
262
|
const res = ctl.respawnAgent(arg);
|
|
243
263
|
if (res === 'no-conversation')
|
|
244
|
-
return ui.system(t('m.noConversation', { name: arg }));
|
|
264
|
+
return ui.system(t('m.noConversation', { name: arg }), 'error');
|
|
245
265
|
if (!res)
|
|
246
|
-
return ui.system(t('m.spawnFail'));
|
|
247
|
-
ui.system(t('m.restored', { name: res.name }));
|
|
266
|
+
return ui.system(t('m.spawnFail'), 'error');
|
|
267
|
+
ui.system(t('m.restored', { name: res.name }), 'ok');
|
|
248
268
|
return;
|
|
249
269
|
}
|
|
250
270
|
case '/attach': {
|
|
@@ -252,55 +272,74 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
252
272
|
// terminal per agent, connected to this session.
|
|
253
273
|
const who = arg || soloAgent(ctl);
|
|
254
274
|
if (!who)
|
|
255
|
-
return ui.system(t('m.usageAttach', { state: ctl.autoAttach ? 'on' : 'off' }));
|
|
275
|
+
return ui.system(t('m.usageAttach', { state: ctl.autoAttach ? 'on' : 'off' }), 'warn');
|
|
256
276
|
if (who === 'on' || who === 'off') {
|
|
257
277
|
ctl.autoAttach = who === 'on';
|
|
258
|
-
ui.system(t('m.attachAuto', { state: who }));
|
|
278
|
+
ui.system(t('m.attachAuto', { state: who }), 'info');
|
|
259
279
|
return;
|
|
260
280
|
}
|
|
261
281
|
const a = ctl.board.getAgentByName(who);
|
|
262
282
|
if (!a)
|
|
263
|
-
return ui.system(t('m.notFound', { target: who, list: agentList(ctl) }));
|
|
283
|
+
return ui.system(t('m.notFound', { target: who, list: agentList(ctl) }), 'error');
|
|
264
284
|
if (!ctl.attachEnabled)
|
|
265
|
-
return ui.system(t('m.attachManual', { cmd: `parallel attach ${a.alias}` }));
|
|
285
|
+
return ui.system(t('m.attachManual', { cmd: `parallel attach ${a.alias}` }), 'warn');
|
|
266
286
|
const r = ctl.openTerminal(a.alias);
|
|
267
287
|
ui.system(r === 'opened'
|
|
268
288
|
? t('m.attachOpened', { name: a.name })
|
|
269
|
-
: t('m.attachManual', { cmd: `parallel attach ${a.alias}` }));
|
|
289
|
+
: t('m.attachManual', { cmd: `parallel attach ${a.alias}` }), r === 'opened' ? 'ok' : 'warn');
|
|
270
290
|
return;
|
|
271
291
|
}
|
|
272
292
|
case '/focus': {
|
|
273
293
|
const who = arg || soloAgent(ctl);
|
|
274
294
|
if (!who)
|
|
275
|
-
return ui.system(t('m.usageFocus'));
|
|
295
|
+
return ui.system(t('m.usageFocus'), 'warn');
|
|
276
296
|
if (!ui.setFocus)
|
|
277
|
-
return;
|
|
297
|
+
return ui.system(t('m.focusOff'), 'info');
|
|
278
298
|
if (who.toLowerCase() === 'off') {
|
|
279
299
|
ui.setFocus(null);
|
|
280
|
-
ui.system(t('m.focusOff'));
|
|
300
|
+
ui.system(t('m.focusOff'), 'info');
|
|
281
301
|
return;
|
|
282
302
|
}
|
|
283
303
|
const a = ctl.board.getAgentByName(who);
|
|
284
304
|
if (!a)
|
|
285
|
-
return ui.system(t('m.notFound', { target: who, list: agentList(ctl) }));
|
|
305
|
+
return ui.system(t('m.notFound', { target: who, list: agentList(ctl) }), 'error');
|
|
286
306
|
ui.setFocus(a.name);
|
|
287
|
-
ui.system(t('m.focusOn', { name: a.name }));
|
|
307
|
+
ui.system(t('m.focusOn', { name: a.name }), 'ok');
|
|
288
308
|
return;
|
|
289
309
|
}
|
|
290
310
|
case '/doctor': {
|
|
291
311
|
const p = ctl.sessionProvider();
|
|
292
312
|
if (!p)
|
|
293
|
-
return ui.system(t('m.missingProvider'));
|
|
313
|
+
return ui.system(t('m.missingProvider'), 'error');
|
|
294
314
|
if (!p.apiKey)
|
|
295
|
-
return ui.system(t('m.missingKey', { name: p.name }));
|
|
315
|
+
return ui.system(t('m.missingKey', { name: p.name }), 'error');
|
|
296
316
|
if (!ctl.session.model && !p.defaultModel && !p.models[0])
|
|
297
|
-
return ui.system(t('m.missingModel', { name: p.name }));
|
|
298
|
-
ui.system(t('m.doctorOk', { pm: `${p.name}:${ctl.session.model || p.defaultModel || p.models[0]}` }));
|
|
317
|
+
return ui.system(t('m.missingModel', { name: p.name }), 'error');
|
|
318
|
+
ui.system(t('m.doctorOk', { pm: `${p.name}:${ctl.session.model || p.defaultModel || p.models[0]}` }), 'ok');
|
|
299
319
|
return;
|
|
300
320
|
}
|
|
301
321
|
case '/cost':
|
|
302
322
|
ui.setView('cost');
|
|
303
323
|
return;
|
|
324
|
+
case '/status': {
|
|
325
|
+
const p = ctl.sessionProvider();
|
|
326
|
+
const agents = [...ctl.board.agents.values()];
|
|
327
|
+
const active = agents.filter((a) => ['working', 'thinking', 'listening', 'waiting'].includes(a.state)).length;
|
|
328
|
+
const cost = agents.reduce((s, a) => s + (a.cost ?? 0), 0);
|
|
329
|
+
const changed = new Set(ctl.board.changes.map((c) => c.path)).size;
|
|
330
|
+
const pm = p ? `${p.name}:${ctl.session.model}` : '-';
|
|
331
|
+
// Multiline: each metric on its own line for readability.
|
|
332
|
+
ui.system(t('m.status', { pm, approval: ctl.session.approvalMode, total: agents.length, active, changed, cost: cost.toFixed(3) }), 'info');
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
case '/raw':
|
|
336
|
+
ui.toggleRaw?.();
|
|
337
|
+
// The rawLogs state is toggled by toggleRaw — the caller in App.tsx
|
|
338
|
+
// provides the current value via a closure; we let App.tsx decide which message.
|
|
339
|
+
return;
|
|
340
|
+
case '/copy':
|
|
341
|
+
ui.copyLatest?.();
|
|
342
|
+
return;
|
|
304
343
|
case '/skills':
|
|
305
344
|
ui.setView('skills');
|
|
306
345
|
return;
|
|
@@ -311,41 +350,41 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
311
350
|
// /skill new <name> [global] → create a template file to edit
|
|
312
351
|
const m = arg.match(/^new\s+([\p{L}\p{N}_-]+)(\s+global)?$/iu);
|
|
313
352
|
if (!m)
|
|
314
|
-
return ui.system(t('m.usageSkill'));
|
|
353
|
+
return ui.system(t('m.usageSkill'), 'warn');
|
|
315
354
|
try {
|
|
316
355
|
const file = createSkillTemplate(m[1], '', m[2] ? 'global' : 'project', ctl.projectRoot);
|
|
317
|
-
ui.system(t('m.skillCreated', { file }));
|
|
356
|
+
ui.system(t('m.skillCreated', { file }), 'ok');
|
|
318
357
|
}
|
|
319
358
|
catch (e) {
|
|
320
|
-
ui.system(t('m.alreadyExists', { msg: e?.message ?? '' }));
|
|
359
|
+
ui.system(t('m.alreadyExists', { msg: e?.message ?? '' }), 'error');
|
|
321
360
|
}
|
|
322
361
|
return;
|
|
323
362
|
}
|
|
324
363
|
case '/specialist': {
|
|
325
364
|
if (!arg)
|
|
326
|
-
return ui.system(t('m.usageSpecialist'));
|
|
365
|
+
return ui.system(t('m.usageSpecialist'), 'warn');
|
|
327
366
|
// /specialist new <name> [global] → create a template file
|
|
328
367
|
const created = arg.match(/^new\s+([\p{L}\p{N}_-]+)(\s+global)?$/iu);
|
|
329
368
|
if (created) {
|
|
330
369
|
try {
|
|
331
370
|
const file = createSpecialistTemplate(created[1], '', created[2] ? 'global' : 'project', ctl.projectRoot);
|
|
332
|
-
ui.system(t('m.specCreated', { file }));
|
|
371
|
+
ui.system(t('m.specCreated', { file }), 'ok');
|
|
333
372
|
}
|
|
334
373
|
catch (e) {
|
|
335
|
-
ui.system(t('m.alreadyExists', { msg: e?.message ?? '' }));
|
|
374
|
+
ui.system(t('m.alreadyExists', { msg: e?.message ?? '' }), 'error');
|
|
336
375
|
}
|
|
337
376
|
return;
|
|
338
377
|
}
|
|
339
378
|
// /specialist <name> [Name:] <task> → spawn an agent with this persona
|
|
340
379
|
const m = arg.match(/^([\p{L}\p{N}_-]+)\s+(.+)$/su);
|
|
341
380
|
if (!m)
|
|
342
|
-
return ui.system(t('m.usageSpecialist'));
|
|
381
|
+
return ui.system(t('m.usageSpecialist'), 'warn');
|
|
343
382
|
const exists = ctl.getSpecialists().some((s) => s.name === m[1].toLowerCase());
|
|
344
383
|
if (!exists) {
|
|
345
384
|
const list = ctl.getSpecialists().map((s) => s.name).join(', ') || t('m.none');
|
|
346
|
-
return ui.system(t('m.noSpecialist', { name: m[1] }) + ` (${list})
|
|
385
|
+
return ui.system(t('m.noSpecialist', { name: m[1] }) + ` (${list})`, 'error');
|
|
347
386
|
}
|
|
348
|
-
spawnFrom(m[2], ctl, ui, images, m[1].toLowerCase());
|
|
387
|
+
spawnFrom(m[2], ctl, ui, images, m[1].toLowerCase(), 'task');
|
|
349
388
|
return;
|
|
350
389
|
}
|
|
351
390
|
case '/board':
|
|
@@ -361,48 +400,51 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
361
400
|
const [target, ...msg] = rest;
|
|
362
401
|
const content = msg.join(' ').trim();
|
|
363
402
|
if (!target || !content)
|
|
364
|
-
return ui.system(t('m.usageSend'));
|
|
403
|
+
return ui.system(t('m.usageSend'), 'warn');
|
|
365
404
|
executeInput(`@${target} ${content}`, ctl, ui);
|
|
366
405
|
return;
|
|
367
406
|
}
|
|
368
407
|
case '/pause': {
|
|
369
408
|
const who = arg || soloAgent(ctl);
|
|
370
409
|
if (!who)
|
|
371
|
-
return ui.system(t('m.usagePause'));
|
|
410
|
+
return ui.system(t('m.usagePause'), 'warn');
|
|
372
411
|
if (who === 'all') {
|
|
373
412
|
for (const a of ctl.board.agents.values())
|
|
374
413
|
ctl.pauseAgent(a.name);
|
|
375
|
-
ui.system(t('m.allPaused'));
|
|
414
|
+
ui.system(t('m.allPaused'), 'ok');
|
|
376
415
|
}
|
|
377
416
|
else {
|
|
378
|
-
|
|
417
|
+
const ok = ctl.pauseAgent(who);
|
|
418
|
+
ui.system(ok ? t('m.paused', { name: who }) : t('m.notFound', { target: who, list: agentList(ctl) }), ok ? 'ok' : 'error');
|
|
379
419
|
}
|
|
380
420
|
return;
|
|
381
421
|
}
|
|
382
422
|
case '/resume': {
|
|
383
423
|
const who = arg || soloAgent(ctl);
|
|
384
424
|
if (!who)
|
|
385
|
-
return ui.system(t('m.usageResume'));
|
|
425
|
+
return ui.system(t('m.usageResume'), 'warn');
|
|
386
426
|
if (who === 'all') {
|
|
387
427
|
for (const a of ctl.board.agents.values())
|
|
388
428
|
ctl.resumeAgent(a.name);
|
|
389
|
-
ui.system(t('m.allResumed'));
|
|
429
|
+
ui.system(t('m.allResumed'), 'ok');
|
|
390
430
|
}
|
|
391
431
|
else {
|
|
392
|
-
|
|
432
|
+
const ok = ctl.resumeAgent(who);
|
|
433
|
+
ui.system(ok ? t('m.resumed', { name: who }) : t('m.notFound', { target: who, list: agentList(ctl) }), ok ? 'ok' : 'error');
|
|
393
434
|
}
|
|
394
435
|
return;
|
|
395
436
|
}
|
|
396
437
|
case '/stop': {
|
|
397
438
|
const who = arg || soloAgent(ctl);
|
|
398
439
|
if (!who)
|
|
399
|
-
return ui.system(t('m.usageStop'));
|
|
440
|
+
return ui.system(t('m.usageStop'), 'warn');
|
|
400
441
|
if (who === 'all') {
|
|
401
442
|
ctl.stopAll();
|
|
402
|
-
ui.system(t('m.allStopped'));
|
|
443
|
+
ui.system(t('m.allStopped'), 'ok');
|
|
403
444
|
}
|
|
404
445
|
else {
|
|
405
|
-
|
|
446
|
+
const ok = ctl.stopAgent(who);
|
|
447
|
+
ui.system(ok ? t('m.stopped', { name: who }) : t('m.notFound', { target: who, list: agentList(ctl) }), ok ? 'ok' : 'error');
|
|
406
448
|
}
|
|
407
449
|
return;
|
|
408
450
|
}
|
|
@@ -410,71 +452,73 @@ export function executeInput(raw, ctl, ui, images) {
|
|
|
410
452
|
case '/model': {
|
|
411
453
|
if (!arg) {
|
|
412
454
|
const p = ctl.sessionProvider();
|
|
413
|
-
return ui.system(t('m.model', { pm: p ? `${p.name}:${ctl.session.model}` : '—' }));
|
|
455
|
+
return ui.system(t('m.model', { pm: p ? `${p.name}:${ctl.session.model}` : '—' }), 'info');
|
|
414
456
|
}
|
|
415
457
|
const r = ctl.setSessionModel(arg);
|
|
416
458
|
if (!r) {
|
|
417
459
|
const provName = arg.includes(':') ? arg.split(':')[0] : arg;
|
|
418
|
-
return ui.system(t('m.noProvider', { name: provName, list: ctl.config.providers.map((p) => p.name).join(', ') || t('m.none') }));
|
|
460
|
+
return ui.system(t('m.noProvider', { name: provName, list: ctl.config.providers.map((p) => p.name).join(', ') || t('m.none') }), 'error');
|
|
419
461
|
}
|
|
420
|
-
ui.system(t('m.modelSet', { pm: `${r.provider}:${r.model}` }));
|
|
462
|
+
ui.system(t('m.modelSet', { pm: `${r.provider}:${r.model}` }), 'ok');
|
|
421
463
|
return;
|
|
422
464
|
}
|
|
423
465
|
case '/settings':
|
|
424
466
|
ui.setView('settings');
|
|
425
467
|
return;
|
|
426
468
|
case '/settings-session':
|
|
427
|
-
case '/ssettings':
|
|
428
469
|
ui.setView('settings-session');
|
|
429
470
|
return;
|
|
430
471
|
// SESSION-only approvals & sound (global defaults editable in /settings).
|
|
431
472
|
case '/approvals': {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
473
|
+
const mode = normalizeShellApprovalMode(arg);
|
|
474
|
+
if (!mode)
|
|
475
|
+
return ui.system(t('m.usageApprovals'), 'warn');
|
|
476
|
+
ctl.setSessionApprovalMode(mode);
|
|
477
|
+
const approvalLevel = mode === 'yolo' ? 'warn' : 'ok';
|
|
478
|
+
ui.system(t('m.approvals', { mode }) + (mode === 'auto-safe' ? t('m.approvalsWarn') : mode === 'yolo' ? t('m.approvalsYoloWarn') : ''), approvalLevel);
|
|
436
479
|
return;
|
|
437
480
|
}
|
|
438
481
|
case '/sound': {
|
|
439
482
|
if (arg !== 'on' && arg !== 'off')
|
|
440
|
-
return ui.system(t('m.usageSound', { state: ctl.session.soundEnabled ? 'on' : 'off' }));
|
|
483
|
+
return ui.system(t('m.usageSound', { state: ctl.session.soundEnabled ? 'on' : 'off' }), 'warn');
|
|
441
484
|
ctl.setSessionSound(arg === 'on');
|
|
442
|
-
ui.system(t('m.sound', { state: arg }));
|
|
485
|
+
ui.system(t('m.sound', { state: arg }), 'ok');
|
|
443
486
|
return;
|
|
444
487
|
}
|
|
445
488
|
case '/save': {
|
|
446
489
|
const file = ctl.saveSession(arg || undefined);
|
|
447
|
-
ui.system(file ? (arg ? t('m.savedAs', { name: arg }) : t('m.saved')) : t('m.nothing'));
|
|
490
|
+
ui.system(file ? (arg ? t('m.savedAs', { name: arg }) : t('m.saved')) : t('m.nothing'), file ? 'ok' : 'warn');
|
|
448
491
|
return;
|
|
449
492
|
}
|
|
450
493
|
case '/key': {
|
|
451
494
|
if (!arg)
|
|
452
|
-
return ui.system(t('m.usageKey'));
|
|
495
|
+
return ui.system(t('m.usageKey'), 'warn');
|
|
453
496
|
const ok = ctl.setApiKey(arg);
|
|
454
|
-
ui.system(ok ? t('m.keySaved', { name: ctl.sessionProvider()?.name ?? '?' }) : t('m.spawnFail'));
|
|
497
|
+
ui.system(ok ? t('m.keySaved', { name: ctl.sessionProvider()?.name ?? '?' }) : t('m.spawnFail'), ok ? 'ok' : 'error');
|
|
455
498
|
return;
|
|
456
499
|
}
|
|
457
500
|
case '/clear': {
|
|
501
|
+
let cleared = 0;
|
|
458
502
|
for (const [id, a] of [...ctl.board.agents.entries()]) {
|
|
459
503
|
if (['done', 'stopped', 'error'].includes(a.state)) {
|
|
460
504
|
ctl.board.agents.delete(id);
|
|
461
505
|
ctl.agents.delete(id);
|
|
506
|
+
cleared++;
|
|
462
507
|
}
|
|
463
508
|
}
|
|
464
509
|
ctl.emit('update');
|
|
465
|
-
ui.system(t('m.cleared'));
|
|
510
|
+
ui.system(cleared > 0 ? t('m.clearedN', { n: cleared }) : t('m.clearedNone'), 'ok');
|
|
466
511
|
return;
|
|
467
512
|
}
|
|
468
513
|
case '/help':
|
|
469
514
|
ui.setView('help');
|
|
470
515
|
return;
|
|
471
516
|
case '/quit':
|
|
472
|
-
case '/exit':
|
|
473
517
|
ctl.saveSession();
|
|
474
518
|
ctl.stopAll();
|
|
475
519
|
ui.exit();
|
|
476
520
|
return;
|
|
477
521
|
default:
|
|
478
|
-
ui.system(t('m.unknown', { cmd }));
|
|
522
|
+
ui.system(t('m.unknown', { cmd }), 'error');
|
|
479
523
|
}
|
|
480
524
|
}
|