@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/config.js
CHANGED
|
@@ -69,6 +69,76 @@ export const PROVIDER_PRESETS = [
|
|
|
69
69
|
models: ['openai/gpt-oss-120b', 'openai/gpt-oss-20b'],
|
|
70
70
|
defaultModel: 'openai/gpt-oss-120b',
|
|
71
71
|
},
|
|
72
|
+
{
|
|
73
|
+
name: 'xAI',
|
|
74
|
+
baseUrl: 'https://api.x.ai/v1',
|
|
75
|
+
apiKey: '',
|
|
76
|
+
models: ['grok-4', 'grok-3-beta', 'grok-3-mini'],
|
|
77
|
+
defaultModel: 'grok-3-beta',
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
name: 'Perplexity',
|
|
81
|
+
baseUrl: 'https://api.perplexity.ai',
|
|
82
|
+
apiKey: '',
|
|
83
|
+
models: ['sonar-pro', 'sonar', 'sonar-reasoning'],
|
|
84
|
+
defaultModel: 'sonar-pro',
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'Cohere',
|
|
88
|
+
baseUrl: 'https://api.cohere.com/v2',
|
|
89
|
+
apiKey: '',
|
|
90
|
+
models: ['command-a', 'command-r-plus', 'command-r'],
|
|
91
|
+
defaultModel: 'command-a',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'DeepInfra',
|
|
95
|
+
baseUrl: 'https://api.deepinfra.com/v1/openai',
|
|
96
|
+
apiKey: '',
|
|
97
|
+
models: ['meta-llama/llama-4-maverick', 'deepseek-ai/deepseek-chat', 'microsoft/wizardlm-2-8x22b'],
|
|
98
|
+
defaultModel: 'meta-llama/llama-4-maverick',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'Fireworks',
|
|
102
|
+
baseUrl: 'https://api.fireworks.ai/inference/v1',
|
|
103
|
+
apiKey: '',
|
|
104
|
+
models: ['llama-4-maverick', 'llama-4-scout', 'mixtral-8x22b'],
|
|
105
|
+
defaultModel: 'llama-4-maverick',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'Cerebras',
|
|
109
|
+
baseUrl: 'https://api.cerebras.ai/v1',
|
|
110
|
+
apiKey: '',
|
|
111
|
+
models: ['llama-3.3-70b', 'llama-3.1-8b'],
|
|
112
|
+
defaultModel: 'llama-3.3-70b',
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: 'Novita',
|
|
116
|
+
baseUrl: 'https://api.novita.ai/v3/openai',
|
|
117
|
+
apiKey: '',
|
|
118
|
+
models: ['deepseek-r1', 'deepseek-v3', 'llama-3.1-70b'],
|
|
119
|
+
defaultModel: 'deepseek-v3',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'Hyperbolic',
|
|
123
|
+
baseUrl: 'https://api.hyperbolic.xyz/v1',
|
|
124
|
+
apiKey: '',
|
|
125
|
+
models: ['deepseek-v3', 'llama-4-maverick', 'qwen3-235b'],
|
|
126
|
+
defaultModel: 'deepseek-v3',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'SambaNova',
|
|
130
|
+
baseUrl: 'https://api.sambanova.ai/v1',
|
|
131
|
+
apiKey: '',
|
|
132
|
+
models: ['llama-4-maverick', 'llama-4-scout', 'deepseek-r1'],
|
|
133
|
+
defaultModel: 'llama-4-maverick',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'Ollama',
|
|
137
|
+
baseUrl: 'http://localhost:11434/v1',
|
|
138
|
+
apiKey: '',
|
|
139
|
+
models: ['llama3', 'codellama', 'mistral'],
|
|
140
|
+
defaultModel: 'llama3',
|
|
141
|
+
},
|
|
72
142
|
];
|
|
73
143
|
export const DEFAULTS = {
|
|
74
144
|
providers: [],
|
|
@@ -78,6 +148,13 @@ export const DEFAULTS = {
|
|
|
78
148
|
soundEnabled: true,
|
|
79
149
|
recentFolders: [],
|
|
80
150
|
};
|
|
151
|
+
function normalizeApprovalMode(mode) {
|
|
152
|
+
if (mode === 'ask' || mode === 'auto-safe' || mode === 'yolo')
|
|
153
|
+
return mode;
|
|
154
|
+
if (mode === 'auto')
|
|
155
|
+
return 'auto-safe';
|
|
156
|
+
return 'ask';
|
|
157
|
+
}
|
|
81
158
|
export function getProvider(cfg, name) {
|
|
82
159
|
const n = (name ?? cfg.defaultProvider).toLowerCase();
|
|
83
160
|
return cfg.providers.find((p) => p.name.toLowerCase() === n) ?? (name ? undefined : cfg.providers[0]);
|
|
@@ -117,6 +194,7 @@ export function loadConfig() {
|
|
|
117
194
|
cfg = { ...cfg, ...raw };
|
|
118
195
|
if (!Array.isArray(cfg.providers))
|
|
119
196
|
cfg.providers = [];
|
|
197
|
+
cfg.approvalMode = normalizeApprovalMode(raw.approvalMode);
|
|
120
198
|
migrate(raw, cfg);
|
|
121
199
|
}
|
|
122
200
|
}
|
|
@@ -146,6 +224,7 @@ export function loadConfig() {
|
|
|
146
224
|
}
|
|
147
225
|
if (!Array.isArray(cfg.recentFolders))
|
|
148
226
|
cfg.recentFolders = [];
|
|
227
|
+
cfg.approvalMode = normalizeApprovalMode(cfg.approvalMode);
|
|
149
228
|
return cfg;
|
|
150
229
|
}
|
|
151
230
|
export function saveConfig(cfg) {
|
package/dist/controller.js
CHANGED
|
@@ -10,6 +10,35 @@ import { priceFor, fmtCost } from './pricing.js';
|
|
|
10
10
|
import { loadSkills, loadSpecialists } from './skills.js';
|
|
11
11
|
import { t } from './i18n.js';
|
|
12
12
|
const AGENT_COLORS = ['cyan', 'magenta', 'yellow', 'green', 'blue', 'redBright', 'cyanBright', 'magentaBright'];
|
|
13
|
+
export function normalizeShellApprovalMode(mode) {
|
|
14
|
+
if (mode === 'ask' || mode === 'auto-safe' || mode === 'yolo')
|
|
15
|
+
return mode;
|
|
16
|
+
if (mode === 'auto')
|
|
17
|
+
return 'auto-safe';
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
export function isRiskyCommand(command) {
|
|
21
|
+
const c = command.toLowerCase();
|
|
22
|
+
if (/\b(sudo|su|dd|mkfs|fdisk|parted)\b/.test(c))
|
|
23
|
+
return true;
|
|
24
|
+
if (/\b(rm|unlink|rmdir)\b/.test(c))
|
|
25
|
+
return true;
|
|
26
|
+
if (/\b(chmod|chown)\s+-[^\s]*r\b/.test(c) || /\b(chmod|chown)\s+.*\s-r\b/.test(c))
|
|
27
|
+
return true;
|
|
28
|
+
if (/\bmv\b.*\s(\/|~|\.\.)/.test(c))
|
|
29
|
+
return true;
|
|
30
|
+
if (/\b(curl|wget)\b.*\|\s*(sh|bash|zsh|python|node)\b/.test(c))
|
|
31
|
+
return true;
|
|
32
|
+
if (/\bgit\s+(reset|clean)\b/.test(c))
|
|
33
|
+
return true;
|
|
34
|
+
if (/\bgit\s+push\b.*(--force|-f)\b/.test(c))
|
|
35
|
+
return true;
|
|
36
|
+
if (/\b(drop|truncate)\s+(table|database|schema)\b/.test(c))
|
|
37
|
+
return true;
|
|
38
|
+
if (/\b(prisma|knex|typeorm|sequelize|rails)\b.*\b(drop|reset|rollback|migrate)\b/.test(c))
|
|
39
|
+
return true;
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
13
42
|
/**
|
|
14
43
|
* The Controller glues everything together: it owns the blackboard, the LLM
|
|
15
44
|
* clients, the live agents and the approval queue. The UI talks only to it.
|
|
@@ -52,7 +81,7 @@ export class Controller extends EventEmitter {
|
|
|
52
81
|
this.session = {
|
|
53
82
|
providerName: p?.name ?? '',
|
|
54
83
|
model: p?.defaultModel || p?.models[0] || '',
|
|
55
|
-
approvalMode: config.approvalMode,
|
|
84
|
+
approvalMode: normalizeShellApprovalMode(config.approvalMode) ?? 'ask',
|
|
56
85
|
soundEnabled: config.soundEnabled,
|
|
57
86
|
};
|
|
58
87
|
this.board.on('update', () => this.emit('update'));
|
|
@@ -145,11 +174,13 @@ export class Controller extends EventEmitter {
|
|
|
145
174
|
}
|
|
146
175
|
// ---------- approvals ----------
|
|
147
176
|
requestApproval = (agentId, command) => {
|
|
148
|
-
if (this.session.approvalMode === 'auto')
|
|
149
|
-
return Promise.resolve(true);
|
|
150
177
|
const base = command.trim().split(/\s+/)[0];
|
|
151
178
|
if (this.sessionAllowedCommands.has(base))
|
|
152
179
|
return Promise.resolve(true);
|
|
180
|
+
if (this.session.approvalMode === 'yolo')
|
|
181
|
+
return Promise.resolve(true);
|
|
182
|
+
if (this.session.approvalMode === 'auto-safe' && !isRiskyCommand(command))
|
|
183
|
+
return Promise.resolve(true);
|
|
153
184
|
return new Promise((resolve) => {
|
|
154
185
|
const agent = this.board.agents.get(agentId);
|
|
155
186
|
this.approvals.push({
|
|
@@ -223,7 +254,7 @@ export class Controller extends EventEmitter {
|
|
|
223
254
|
}
|
|
224
255
|
}
|
|
225
256
|
/** Launch agent N+1 — works at any time, even while others are running. */
|
|
226
|
-
spawnAgent(task, name, modelSpec, images, specialistName, initialHistory) {
|
|
257
|
+
spawnAgent(task, name, modelSpec, images, specialistName, initialHistory, mode = 'task') {
|
|
227
258
|
// Specialist persona: role appended to the system prompt, may pin a model.
|
|
228
259
|
let specialist;
|
|
229
260
|
if (specialistName) {
|
|
@@ -263,6 +294,7 @@ export class Controller extends EventEmitter {
|
|
|
263
294
|
alias,
|
|
264
295
|
color,
|
|
265
296
|
task,
|
|
297
|
+
mode,
|
|
266
298
|
model: resolved.model,
|
|
267
299
|
llm: this.llmFor(resolved.provider, resolved.model),
|
|
268
300
|
board: this.board,
|
|
@@ -364,7 +396,7 @@ export class Controller extends EventEmitter {
|
|
|
364
396
|
}
|
|
365
397
|
if (history.length === 0)
|
|
366
398
|
return 'no-conversation';
|
|
367
|
-
return this.spawnAgent(sa.task, sa.name, sa.model, undefined, undefined, history);
|
|
399
|
+
return this.spawnAgent(sa.task, sa.name, sa.model, undefined, undefined, history, sa.mode ?? 'task');
|
|
368
400
|
}
|
|
369
401
|
pauseAgent(name) {
|
|
370
402
|
const a = this.findAgent(name);
|
|
@@ -562,6 +594,7 @@ export class Controller extends EventEmitter {
|
|
|
562
594
|
agents: [...this.board.agents.values()].map((a) => ({
|
|
563
595
|
name: a.name,
|
|
564
596
|
task: a.task,
|
|
597
|
+
mode: a.mode,
|
|
565
598
|
state: a.state,
|
|
566
599
|
lastResult: a.lastResult,
|
|
567
600
|
steps: a.steps,
|
|
@@ -685,6 +718,26 @@ export class Controller extends EventEmitter {
|
|
|
685
718
|
this.emit('update');
|
|
686
719
|
return true;
|
|
687
720
|
}
|
|
721
|
+
/** Remove a provider by name. Clears the default if it was the removed one. */
|
|
722
|
+
removeProvider(name) {
|
|
723
|
+
const idx = this.config.providers.findIndex(p => p.name.toLowerCase() === name.toLowerCase());
|
|
724
|
+
if (idx < 0)
|
|
725
|
+
return false;
|
|
726
|
+
this.config.providers.splice(idx, 1);
|
|
727
|
+
if (this.config.defaultProvider.toLowerCase() === name.toLowerCase()) {
|
|
728
|
+
this.config.defaultProvider = this.config.providers[0]?.name ?? '';
|
|
729
|
+
}
|
|
730
|
+
// If the session was using the removed provider, reset it
|
|
731
|
+
if (this.session.providerName.toLowerCase() === name.toLowerCase()) {
|
|
732
|
+
const fallback = this.config.providers[0];
|
|
733
|
+
this.session.providerName = fallback?.name ?? '';
|
|
734
|
+
this.session.model = fallback?.defaultModel ?? '';
|
|
735
|
+
}
|
|
736
|
+
saveConfig(this.config);
|
|
737
|
+
this.llmCache.clear();
|
|
738
|
+
this.emit('update');
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
688
741
|
setGlobalApprovalMode(mode) {
|
|
689
742
|
this.config.approvalMode = mode;
|
|
690
743
|
saveConfig(this.config);
|