@parallel-cli/parallel 0.4.8 → 0.5.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/CHANGELOG.md +57 -0
- package/README.md +43 -7
- package/dist/agents/agent.js +213 -31
- package/dist/agents/execution-policy.js +58 -0
- package/dist/agents/tools.js +83 -22
- package/dist/commands.js +67 -5
- package/dist/config.js +5 -2
- package/dist/controller.js +229 -11
- package/dist/coordination/blackboard.js +8 -7
- package/dist/diagnostics.js +209 -0
- package/dist/i18n.js +60 -4
- package/dist/index.js +31 -7
- package/dist/llm/client.js +7 -3
- package/dist/project-context.js +477 -0
- package/dist/project-index.js +186 -0
- package/dist/security.js +93 -0
- package/dist/server.js +41 -2
- package/dist/ui/AgentPanel.js +6 -2
- package/dist/ui/App.js +4 -2
- package/dist/ui/AttachApp.js +6 -3
- package/dist/ui/CommandInput.js +9 -0
- package/dist/ui/SettingsPanel.js +22 -23
- package/dist/ui/Timeline.js +3 -0
- package/dist/ui/Wizard.js +49 -21
- package/dist/ui/events.js +4 -0
- package/dist/ui/views.js +4 -2
- package/dist/update.js +3 -2
- package/dist/version.js +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
export const SIMPLE_DIAGNOSTIC_BUDGETS = {
|
|
2
|
+
maxRounds: 6,
|
|
3
|
+
maxToolCalls: 12,
|
|
4
|
+
maxShellCommands: 2,
|
|
5
|
+
maxRepeatedReads: 1,
|
|
6
|
+
maxRepeatedCommands: 1,
|
|
7
|
+
maxInputTokens: 150_000,
|
|
8
|
+
maxContextAmplification: 12,
|
|
9
|
+
};
|
|
10
|
+
function textSize(value) {
|
|
11
|
+
if (typeof value === 'string')
|
|
12
|
+
return value.length;
|
|
13
|
+
if (value === null || value === undefined)
|
|
14
|
+
return 0;
|
|
15
|
+
return JSON.stringify(value).length;
|
|
16
|
+
}
|
|
17
|
+
function parseArgs(raw) {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = JSON.parse(raw || '{}');
|
|
20
|
+
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function counts(values) {
|
|
27
|
+
const map = new Map();
|
|
28
|
+
for (const value of values)
|
|
29
|
+
map.set(value, (map.get(value) ?? 0) + 1);
|
|
30
|
+
return [...map.entries()]
|
|
31
|
+
.filter(([, count]) => count > 1)
|
|
32
|
+
.map(([value, count]) => ({ value, count }))
|
|
33
|
+
.sort((a, b) => b.count - a.count || a.value.localeCompare(b.value));
|
|
34
|
+
}
|
|
35
|
+
export function analyzeAgentConversation(messages, observed = {}) {
|
|
36
|
+
const budgets = { ...SIMPLE_DIAGNOSTIC_BUDGETS, ...(observed.budgets ?? {}) };
|
|
37
|
+
const toolCounts = {};
|
|
38
|
+
const commands = [];
|
|
39
|
+
const reads = [];
|
|
40
|
+
let assistantRounds = 0;
|
|
41
|
+
let toolCalls = 0;
|
|
42
|
+
let updateOnlyRounds = 0;
|
|
43
|
+
let totalToolResultChars = 0;
|
|
44
|
+
let largestToolResultChars = 0;
|
|
45
|
+
let approximateContextCharsSent = 0;
|
|
46
|
+
let cumulativeChars = 0;
|
|
47
|
+
let virtualHistoryMessages = 0;
|
|
48
|
+
let estimatedCompactionCalls = 0;
|
|
49
|
+
for (let index = 0; index < messages.length; index++) {
|
|
50
|
+
const message = messages[index];
|
|
51
|
+
virtualHistoryMessages++;
|
|
52
|
+
if (message.role === 'assistant') {
|
|
53
|
+
assistantRounds++;
|
|
54
|
+
approximateContextCharsSent += cumulativeChars;
|
|
55
|
+
const calls = Array.isArray(message.tool_calls) ? message.tool_calls : [];
|
|
56
|
+
const names = calls.map((call) => String(call.function?.name ?? 'unknown'));
|
|
57
|
+
if (names.length > 0 && names.every((name) => name === 'update_status' || name === 'update_steps')) {
|
|
58
|
+
updateOnlyRounds++;
|
|
59
|
+
}
|
|
60
|
+
for (const call of calls) {
|
|
61
|
+
const name = String(call.function?.name ?? 'unknown');
|
|
62
|
+
const args = parseArgs(call.function?.arguments);
|
|
63
|
+
toolCalls++;
|
|
64
|
+
toolCounts[name] = (toolCounts[name] ?? 0) + 1;
|
|
65
|
+
if (name === 'run_command')
|
|
66
|
+
commands.push(String(args.command ?? '').trim().replace(/\s+/g, ' '));
|
|
67
|
+
if (name === 'read_file')
|
|
68
|
+
reads.push(String(args.path ?? '').trim());
|
|
69
|
+
if (name === 'read_many' && Array.isArray(args.paths))
|
|
70
|
+
reads.push(...args.paths.map(String));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (message.role === 'tool') {
|
|
74
|
+
const size = textSize(message.content);
|
|
75
|
+
totalToolResultChars += size;
|
|
76
|
+
largestToolResultChars = Math.max(largestToolResultChars, size);
|
|
77
|
+
}
|
|
78
|
+
cumulativeChars += textSize(message.content) + textSize(message.tool_calls) + 20;
|
|
79
|
+
const next = messages[index + 1];
|
|
80
|
+
const endsToolRound = message.role === 'tool' && next?.role !== 'tool';
|
|
81
|
+
if (endsToolRound && virtualHistoryMessages > 80) {
|
|
82
|
+
estimatedCompactionCalls++;
|
|
83
|
+
virtualHistoryMessages = 42;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const transcriptChars = Math.max(1, cumulativeChars);
|
|
87
|
+
const repeatedCommands = counts(commands.filter(Boolean)).map(({ value, count }) => ({ command: value, count }));
|
|
88
|
+
const repeatedReads = counts(reads.filter(Boolean)).map(({ value, count }) => ({ path: value, count }));
|
|
89
|
+
const shellCommands = toolCounts.run_command ?? 0;
|
|
90
|
+
const readOnlyShellCommands = observed.perf?.readOnlyShellCommands ?? shellCommands;
|
|
91
|
+
const contextAmplification = approximateContextCharsSent / transcriptChars;
|
|
92
|
+
const findings = [];
|
|
93
|
+
if (assistantRounds > budgets.maxRounds) {
|
|
94
|
+
findings.push({
|
|
95
|
+
severity: assistantRounds > budgets.maxRounds * 2 ? 'critical' : 'warn',
|
|
96
|
+
code: 'too-many-model-rounds',
|
|
97
|
+
message: `${assistantRounds} sequential model rounds exceed the simple-task budget of ${budgets.maxRounds}.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (toolCalls > budgets.maxToolCalls) {
|
|
101
|
+
findings.push({
|
|
102
|
+
severity: toolCalls > budgets.maxToolCalls * 2 ? 'critical' : 'warn',
|
|
103
|
+
code: 'tool-churn',
|
|
104
|
+
message: `${toolCalls} tool calls exceed the budget of ${budgets.maxToolCalls}.`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (shellCommands > budgets.maxShellCommands) {
|
|
108
|
+
findings.push({
|
|
109
|
+
severity: 'critical',
|
|
110
|
+
code: 'shell-micro-command-loop',
|
|
111
|
+
message: `${shellCommands} shell commands were used; batch inspection tools should keep this below ${budgets.maxShellCommands}.`,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
if (repeatedReads.some((item) => item.count > budgets.maxRepeatedReads + 1)) {
|
|
115
|
+
findings.push({
|
|
116
|
+
severity: 'warn',
|
|
117
|
+
code: 'repeated-file-reads',
|
|
118
|
+
message: `Files were repeatedly re-read: ${repeatedReads.slice(0, 4).map((item) => `${item.path}×${item.count}`).join(', ')}.`,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (repeatedCommands.some((item) => item.count > budgets.maxRepeatedCommands + 1)) {
|
|
122
|
+
findings.push({
|
|
123
|
+
severity: 'warn',
|
|
124
|
+
code: 'repeated-shell-commands',
|
|
125
|
+
message: `Shell commands were repeated: ${repeatedCommands.slice(0, 3).map((item) => `${item.command}×${item.count}`).join(', ')}.`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if ((observed.inputTokens ?? 0) > budgets.maxInputTokens) {
|
|
129
|
+
findings.push({
|
|
130
|
+
severity: 'critical',
|
|
131
|
+
code: 'prompt-token-amplification',
|
|
132
|
+
message: `${observed.inputTokens?.toLocaleString()} cumulative input tokens exceed the simple-task budget of ${budgets.maxInputTokens.toLocaleString()}.`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (contextAmplification > budgets.maxContextAmplification) {
|
|
136
|
+
findings.push({
|
|
137
|
+
severity: 'critical',
|
|
138
|
+
code: 'full-history-resend',
|
|
139
|
+
message: `Approximate context amplification is ${contextAmplification.toFixed(1)}× because prior tool output is resent on every model turn.`,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (largestToolResultChars > 10_000) {
|
|
143
|
+
findings.push({
|
|
144
|
+
severity: 'warn',
|
|
145
|
+
code: 'large-tool-result',
|
|
146
|
+
message: `The largest tool result was ${largestToolResultChars.toLocaleString()} characters and remained in later prompts.`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const compactionCalls = Math.max(observed.perf?.compactionTurns ?? 0, estimatedCompactionCalls);
|
|
150
|
+
if (compactionCalls > 0) {
|
|
151
|
+
findings.push({
|
|
152
|
+
severity: 'warn',
|
|
153
|
+
code: 'hidden-compaction-calls',
|
|
154
|
+
message: `Approximately ${compactionCalls} extra model call(s) were spent compacting history outside the visible step count.`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
if (updateOnlyRounds > 1) {
|
|
158
|
+
findings.push({
|
|
159
|
+
severity: 'info',
|
|
160
|
+
code: 'coordination-only-rounds',
|
|
161
|
+
message: `${updateOnlyRounds} model rounds only updated status/steps without inspecting or completing work.`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
messages: messages.length,
|
|
166
|
+
assistantRounds,
|
|
167
|
+
toolCalls,
|
|
168
|
+
toolCounts,
|
|
169
|
+
shellCommands,
|
|
170
|
+
readOnlyShellCommands,
|
|
171
|
+
repeatedCommands,
|
|
172
|
+
repeatedReads,
|
|
173
|
+
inspectProjectCalls: toolCounts.inspect_project ?? 0,
|
|
174
|
+
readManyCalls: toolCounts.read_many ?? 0,
|
|
175
|
+
updateOnlyRounds,
|
|
176
|
+
estimatedCompactionCalls,
|
|
177
|
+
totalToolResultChars,
|
|
178
|
+
largestToolResultChars,
|
|
179
|
+
approximateContextCharsSent,
|
|
180
|
+
transcriptChars,
|
|
181
|
+
contextAmplification,
|
|
182
|
+
inputTokens: observed.inputTokens,
|
|
183
|
+
outputTokens: observed.outputTokens,
|
|
184
|
+
elapsedMs: observed.elapsedMs,
|
|
185
|
+
perf: observed.perf,
|
|
186
|
+
findings,
|
|
187
|
+
withinBudget: !findings.some((finding) => finding.severity === 'critical'),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export function formatAgentDiagnostic(report) {
|
|
191
|
+
const lines = [
|
|
192
|
+
'Agent performance diagnostic',
|
|
193
|
+
` rounds: ${report.assistantRounds}`,
|
|
194
|
+
` tools: ${report.toolCalls} (${Object.entries(report.toolCounts).map(([name, count]) => `${name}=${count}`).join(', ')})`,
|
|
195
|
+
` shell: ${report.shellCommands}`,
|
|
196
|
+
` tool output: ${report.totalToolResultChars.toLocaleString()} chars, max ${report.largestToolResultChars.toLocaleString()}`,
|
|
197
|
+
` context amplification: ${report.contextAmplification.toFixed(1)}×`,
|
|
198
|
+
` estimated hidden compactions: ${report.estimatedCompactionCalls}`,
|
|
199
|
+
];
|
|
200
|
+
if (report.inputTokens !== undefined)
|
|
201
|
+
lines.push(` input tokens: ${report.inputTokens.toLocaleString()}`);
|
|
202
|
+
if (report.elapsedMs !== undefined)
|
|
203
|
+
lines.push(` elapsed: ${(report.elapsedMs / 1000).toFixed(1)}s`);
|
|
204
|
+
lines.push(` budget: ${report.withinBudget ? 'PASS' : 'FAIL'}`);
|
|
205
|
+
for (const finding of report.findings) {
|
|
206
|
+
lines.push(` [${finding.severity.toUpperCase()}] ${finding.code}: ${finding.message}`);
|
|
207
|
+
}
|
|
208
|
+
return lines.join('\n');
|
|
209
|
+
}
|
package/dist/i18n.js
CHANGED
|
@@ -83,6 +83,14 @@ const en = {
|
|
|
83
83
|
'main.status': 'Enter = new agent N+1 (even while others work) · @Name = real-time instruction · /help · views: /agents /board /diff /notes',
|
|
84
84
|
'main.placeholder': 'Type a task (= new agent N+1) · @Agent message · /command',
|
|
85
85
|
'agent.summary': 'Summary',
|
|
86
|
+
'agent.compactingShort': 'Long memory summary',
|
|
87
|
+
'agent.compactingStart': 'Long memory: summarizing earlier history to keep useful context.',
|
|
88
|
+
'agent.compactingDone': 'Long memory: earlier history summarized and kept in context.',
|
|
89
|
+
'agent.compactingFallback': 'Long memory: earlier history was shortened to keep context responsive.',
|
|
90
|
+
'memory.indexing': 'Project memory: indexing the codebase…',
|
|
91
|
+
'memory.ready': 'Project memory ready.',
|
|
92
|
+
'memory.fallback': 'Project memory fallback: deterministic context. {detail}',
|
|
93
|
+
'memory.label': 'Memory',
|
|
86
94
|
// input
|
|
87
95
|
'input.atHint': ' — send a real-time instruction',
|
|
88
96
|
'input.atAll': ' to all agents',
|
|
@@ -92,6 +100,7 @@ const en = {
|
|
|
92
100
|
'input.attImage': '🖼 image #{n} · {file}',
|
|
93
101
|
'input.imageNone': 'No image in clipboard (requires xclip or wl-clipboard).',
|
|
94
102
|
'input.imageAdded': '🖼 Image attached from clipboard (Ctrl+V).',
|
|
103
|
+
'input.imageConsent': 'Image found. Press Ctrl+V again to attach and send it to the selected model provider.',
|
|
95
104
|
'input.imageHint': 'Ctrl+V: paste an image (multimodal models)',
|
|
96
105
|
// approval
|
|
97
106
|
'appr.title': '⚠ APPROVAL REQUIRED',
|
|
@@ -141,6 +150,7 @@ const en = {
|
|
|
141
150
|
'cmd.notes': 'Inter-agent notes history',
|
|
142
151
|
'cmd.diff': 'Live file diffs',
|
|
143
152
|
'cmd.status': 'Show session status: model, agents, changes, cost',
|
|
153
|
+
'cmd.memory': 'Show or refresh the shared project memory',
|
|
144
154
|
'cmd.raw': 'Toggle raw activity detail in focus view',
|
|
145
155
|
'cmd.copy': 'Copy the latest completed agent result',
|
|
146
156
|
'timeline.activity': 'Activity',
|
|
@@ -267,7 +277,10 @@ const en = {
|
|
|
267
277
|
'm.cleared': 'Finished agents removed from display.',
|
|
268
278
|
'm.clearedN': 'Removed {n} finished agent(s) from display.',
|
|
269
279
|
'm.clearedNone': 'No finished agents to clear.',
|
|
270
|
-
'm.status': 'Status\n Model {pm}\n Approvals {approval}\n Agents {total} total / {active} active\n Changed files {changed}\n Session cost ${cost}',
|
|
280
|
+
'm.status': 'Status\n Model {pm}\n Approvals {approval}\n Agents {total} total / {active} active\n Changed files {changed}\n Session cost ${cost}\n Project memory {memory} / ${memoryCost}',
|
|
281
|
+
'm.usageMemory': 'Usage: /memory or /memory refresh',
|
|
282
|
+
'm.memoryStatus': 'Project memory\n Status {status}\n Generated {date}\n Model {model}\n Tokens {tokens}\n Cost ${cost}',
|
|
283
|
+
'm.doctorMemory': '• Project memory: {status} (generated {date})',
|
|
271
284
|
'm.rawOn': 'Raw activity detail ON — raw agent logs visible in focus view.',
|
|
272
285
|
'm.rawOff': 'Raw activity detail OFF.',
|
|
273
286
|
'm.copyNone': 'No completed agent output to copy.',
|
|
@@ -340,6 +353,7 @@ const en = {
|
|
|
340
353
|
'q.keys': '↑/↓ + Enter or 1-4 to answer · no answer in 30s → the recommended option is chosen',
|
|
341
354
|
// cost view
|
|
342
355
|
'cost.title': '💰 SESSION COST — real time, per agent',
|
|
356
|
+
'cost.memory': 'Project memory',
|
|
343
357
|
'cost.empty': 'No agents yet — costs will appear here in real time.',
|
|
344
358
|
'cost.unknown': '(unknown price — set it in /settings → Model prices)',
|
|
345
359
|
'cost.total': 'Session total:',
|
|
@@ -479,6 +493,14 @@ const fr = {
|
|
|
479
493
|
'main.status': 'Entrée = nouvel agent N+1 (même pendant que les autres travaillent) · @Nom = instruction temps réel · /help · vues : /agents /board /diff /notes',
|
|
480
494
|
'main.placeholder': 'Tape une tâche (= nouvel agent N+1) · @Agent message · /commande',
|
|
481
495
|
'agent.summary': 'Récapitulatif',
|
|
496
|
+
'agent.compactingShort': 'Résumé mémoire longue',
|
|
497
|
+
'agent.compactingStart': "Mémoire longue : résumé automatique de l'historique pour garder le contexte utile.",
|
|
498
|
+
'agent.compactingDone': "Mémoire longue : l'historique ancien est résumé et conservé dans le contexte.",
|
|
499
|
+
'agent.compactingFallback': "Mémoire longue : l'historique ancien a été raccourci pour garder le contexte réactif.",
|
|
500
|
+
'memory.indexing': 'Mémoire projet : indexation du codebase…',
|
|
501
|
+
'memory.ready': 'Mémoire projet prête.',
|
|
502
|
+
'memory.fallback': 'Mémoire projet en fallback : contexte déterministe. {detail}',
|
|
503
|
+
'memory.label': 'Mémoire',
|
|
482
504
|
'input.atHint': ' — envoyer une instruction temps réel',
|
|
483
505
|
'input.atAll': ' à tous les agents',
|
|
484
506
|
'input.pasted': '[collé #{n} : {lines} lignes]',
|
|
@@ -487,6 +509,7 @@ const fr = {
|
|
|
487
509
|
'input.attImage': '🖼 image #{n} · {file}',
|
|
488
510
|
'input.imageNone': "Aucune image dans le presse-papiers (nécessite xclip ou wl-clipboard).",
|
|
489
511
|
'input.imageAdded': '🖼 Image attachée depuis le presse-papiers (Ctrl+V).',
|
|
512
|
+
'input.imageConsent': "Image détectée. Appuie encore sur Ctrl+V pour l'attacher et l'envoyer au provider du modèle sélectionné.",
|
|
490
513
|
'input.imageHint': 'Ctrl+V : coller une image (modèles multimodaux)',
|
|
491
514
|
'appr.title': '⚠ APPROBATION REQUISE',
|
|
492
515
|
'appr.pending': ' ({n} en attente)',
|
|
@@ -533,6 +556,7 @@ const fr = {
|
|
|
533
556
|
'cmd.notes': 'Historique des notes inter-agents',
|
|
534
557
|
'cmd.diff': 'Modifications de fichiers (diffs en direct)',
|
|
535
558
|
'cmd.status': 'Afficher le statut de la session : modèle, agents, changements, coût',
|
|
559
|
+
'cmd.memory': 'Afficher ou régénérer la mémoire projet partagée',
|
|
536
560
|
'cmd.raw': 'Basculer le détail brut de l\'activité dans la vue focus',
|
|
537
561
|
'cmd.copy': 'Copier le dernier résultat d\'agent terminé',
|
|
538
562
|
'timeline.activity': 'Activité',
|
|
@@ -658,7 +682,10 @@ const fr = {
|
|
|
658
682
|
'm.cleared': "Agents terminés retirés de l'affichage.",
|
|
659
683
|
'm.clearedN': "{n} agent(s) terminé(s) retiré(s) de l'affichage.",
|
|
660
684
|
'm.clearedNone': "Aucun agent terminé à retirer.",
|
|
661
|
-
'm.status': "Statut\n Modèle {pm}\n Approbations {approval}\n Agents {total} total / {active} actif(s)\n Fichiers modifiés {changed}\n Coût session ${cost}",
|
|
685
|
+
'm.status': "Statut\n Modèle {pm}\n Approbations {approval}\n Agents {total} total / {active} actif(s)\n Fichiers modifiés {changed}\n Coût session ${cost}\n Mémoire projet {memory} / ${memoryCost}",
|
|
686
|
+
'm.usageMemory': 'Usage : /memory ou /memory refresh',
|
|
687
|
+
'm.memoryStatus': 'Mémoire projet\n Statut {status}\n Générée {date}\n Modèle {model}\n Tokens {tokens}\n Coût ${cost}',
|
|
688
|
+
'm.doctorMemory': '• Mémoire projet : {status} (générée {date})',
|
|
662
689
|
'm.rawOn': 'Détail brut activé — logs agents visibles dans la vue focus.',
|
|
663
690
|
'm.rawOff': 'Détail brut désactivé.',
|
|
664
691
|
'm.copyNone': "Aucun résultat d'agent à copier.",
|
|
@@ -728,6 +755,7 @@ const fr = {
|
|
|
728
755
|
'q.recommended': '★ recommandé',
|
|
729
756
|
'q.keys': '↑/↓ + Entrée ou 1-4 pour répondre · sans réponse en 30s → l’option recommandée est choisie',
|
|
730
757
|
'cost.title': '💰 COÛT DE LA SESSION — temps réel, par agent',
|
|
758
|
+
'cost.memory': 'Mémoire projet',
|
|
731
759
|
'cost.empty': 'Aucun agent pour le moment — les coûts apparaîtront ici en temps réel.',
|
|
732
760
|
'cost.unknown': '(prix inconnu — règle-le dans /settings → Prix des modèles)',
|
|
733
761
|
'cost.total': 'Total session :',
|
|
@@ -863,6 +891,14 @@ const es = {
|
|
|
863
891
|
'main.status': 'Enter = nuevo agente N+1 (incluso mientras otros trabajan) · @Nombre = instrucción en tiempo real · /help · vistas: /agents /board /diff /notes',
|
|
864
892
|
'main.placeholder': 'Escribe una tarea (= nuevo agente N+1) · @Agente mensaje · /comando',
|
|
865
893
|
'agent.summary': 'Resumen',
|
|
894
|
+
'agent.compactingShort': 'Resumen de memoria larga',
|
|
895
|
+
'agent.compactingStart': 'Memoria larga: resumen automático del historial para conservar el contexto útil.',
|
|
896
|
+
'agent.compactingDone': 'Memoria larga: el historial anterior se resumió y se mantuvo en contexto.',
|
|
897
|
+
'agent.compactingFallback': 'Memoria larga: el historial anterior se acortó para mantener el contexto ágil.',
|
|
898
|
+
'memory.indexing': 'Memoria del proyecto: indexando el código…',
|
|
899
|
+
'memory.ready': 'Memoria del proyecto lista.',
|
|
900
|
+
'memory.fallback': 'Memoria del proyecto en modo fallback: contexto determinista. {detail}',
|
|
901
|
+
'memory.label': 'Memoria',
|
|
866
902
|
'input.atHint': ' — enviar una instrucción en tiempo real',
|
|
867
903
|
'input.atAll': ' a todos los agentes',
|
|
868
904
|
'input.pasted': '[pegado #{n}: {lines} líneas]',
|
|
@@ -871,6 +907,7 @@ const es = {
|
|
|
871
907
|
'input.attImage': '🖼 imagen #{n} · {file}',
|
|
872
908
|
'input.imageNone': 'No hay imagen en el portapapeles (requiere xclip o wl-clipboard).',
|
|
873
909
|
'input.imageAdded': '🖼 Imagen adjuntada desde el portapapeles (Ctrl+V).',
|
|
910
|
+
'input.imageConsent': 'Imagen detectada. Pulsa Ctrl+V otra vez para adjuntarla y enviarla al proveedor del modelo seleccionado.',
|
|
874
911
|
'input.imageHint': 'Ctrl+V: pegar una imagen (modelos multimodales)',
|
|
875
912
|
'appr.title': '⚠ APROBACIÓN REQUERIDA',
|
|
876
913
|
'appr.pending': ' ({n} pendientes)',
|
|
@@ -917,6 +954,7 @@ const es = {
|
|
|
917
954
|
'cmd.notes': 'Historial de notas entre agentes',
|
|
918
955
|
'cmd.diff': 'Diffs de archivos en vivo',
|
|
919
956
|
'cmd.status': 'Mostrar estado de la sesión: modelo, agentes, cambios, coste',
|
|
957
|
+
'cmd.memory': 'Mostrar o actualizar la memoria compartida del proyecto',
|
|
920
958
|
'cmd.raw': 'Alternar detalle de actividad sin procesar en la vista focus',
|
|
921
959
|
'cmd.copy': 'Copiar el último resultado de agente completado',
|
|
922
960
|
'timeline.activity': 'Actividad',
|
|
@@ -1042,7 +1080,10 @@ const es = {
|
|
|
1042
1080
|
'm.cleared': 'Agentes terminados quitados de la pantalla.',
|
|
1043
1081
|
'm.clearedN': '{n} agente(s) terminado(s) quitado(s) de la pantalla.',
|
|
1044
1082
|
'm.clearedNone': 'No hay agentes terminados que quitar.',
|
|
1045
|
-
'm.status': 'Estado\n Modelo {pm}\n Aprobaciones {approval}\n Agentes {total} total / {active} activo(s)\n Archivos cambiados {changed}\n Coste sesión ${cost}',
|
|
1083
|
+
'm.status': 'Estado\n Modelo {pm}\n Aprobaciones {approval}\n Agentes {total} total / {active} activo(s)\n Archivos cambiados {changed}\n Coste sesión ${cost}\n Memoria del proyecto {memory} / ${memoryCost}',
|
|
1084
|
+
'm.usageMemory': 'Uso: /memory o /memory refresh',
|
|
1085
|
+
'm.memoryStatus': 'Memoria del proyecto\n Estado {status}\n Generada {date}\n Modelo {model}\n Tokens {tokens}\n Coste ${cost}',
|
|
1086
|
+
'm.doctorMemory': '• Memoria del proyecto: {status} (generada {date})',
|
|
1046
1087
|
'm.rawOn': 'Detalle de actividad activado — logs visibles en vista focus.',
|
|
1047
1088
|
'm.rawOff': 'Detalle de actividad desactivado.',
|
|
1048
1089
|
'm.copyNone': 'No hay resultado de agente que copiar.',
|
|
@@ -1112,6 +1153,7 @@ const es = {
|
|
|
1112
1153
|
'q.recommended': '★ recomendado',
|
|
1113
1154
|
'q.keys': '↑/↓ + Enter o 1-4 para responder · sin respuesta en 30s → se elige la opción recomendada',
|
|
1114
1155
|
'cost.title': '💰 COSTE DE LA SESIÓN — tiempo real, por agente',
|
|
1156
|
+
'cost.memory': 'Memoria del proyecto',
|
|
1115
1157
|
'cost.empty': 'Aún no hay agentes — los costes aparecerán aquí en tiempo real.',
|
|
1116
1158
|
'cost.unknown': '(precio desconocido — defínelo en /settings → Precios de modelos)',
|
|
1117
1159
|
'cost.total': 'Total de la sesión:',
|
|
@@ -1247,6 +1289,14 @@ const zh = {
|
|
|
1247
1289
|
'main.status': '回车 = 新智能体 N+1(即使其他智能体正在工作)· @名称 = 实时指令 · /help · 视图:/agents /board /diff /notes',
|
|
1248
1290
|
'main.placeholder': '输入任务(= 新智能体 N+1)· @智能体 消息 · /命令',
|
|
1249
1291
|
'agent.summary': '摘要',
|
|
1292
|
+
'agent.compactingShort': '长记忆摘要',
|
|
1293
|
+
'agent.compactingStart': '长记忆:正在自动总结较早历史,以保留有用上下文。',
|
|
1294
|
+
'agent.compactingDone': '长记忆:较早历史已总结并保留在上下文中。',
|
|
1295
|
+
'agent.compactingFallback': '长记忆:较早历史已缩短,以保持上下文响应速度。',
|
|
1296
|
+
'memory.indexing': '项目记忆:正在索引代码库…',
|
|
1297
|
+
'memory.ready': '项目记忆已就绪。',
|
|
1298
|
+
'memory.fallback': '项目记忆使用确定性回退上下文。{detail}',
|
|
1299
|
+
'memory.label': '记忆',
|
|
1250
1300
|
'input.atHint': ' — 发送实时指令',
|
|
1251
1301
|
'input.atAll': ' 给所有智能体',
|
|
1252
1302
|
'input.pasted': '[粘贴 #{n}:{lines} 行]',
|
|
@@ -1255,6 +1305,7 @@ const zh = {
|
|
|
1255
1305
|
'input.attImage': '🖼 图片 #{n} · {file}',
|
|
1256
1306
|
'input.imageNone': '剪贴板中没有图片(需要 xclip 或 wl-clipboard)。',
|
|
1257
1307
|
'input.imageAdded': '🖼 已从剪贴板附加图片(Ctrl+V)。',
|
|
1308
|
+
'input.imageConsent': '检测到图片。再次按 Ctrl+V 即会附加图片并发送给当前模型提供商。',
|
|
1258
1309
|
'input.imageHint': 'Ctrl+V:粘贴图片(多模态模型)',
|
|
1259
1310
|
'appr.title': '⚠ 需要批准',
|
|
1260
1311
|
'appr.pending': '({n} 个待处理)',
|
|
@@ -1301,6 +1352,7 @@ const zh = {
|
|
|
1301
1352
|
'cmd.notes': '智能体间便签历史',
|
|
1302
1353
|
'cmd.diff': '实时文件差异',
|
|
1303
1354
|
'cmd.status': '显示会话状态:模型、智能体、更改、费用',
|
|
1355
|
+
'cmd.memory': '显示或刷新共享项目记忆',
|
|
1304
1356
|
'cmd.raw': '在焦点视图中切换原始活动详情',
|
|
1305
1357
|
'cmd.copy': '复制最新完成的智能体结果',
|
|
1306
1358
|
'timeline.activity': '活动',
|
|
@@ -1426,7 +1478,10 @@ const zh = {
|
|
|
1426
1478
|
'm.cleared': '已从显示中移除完成的智能体。',
|
|
1427
1479
|
'm.clearedN': '已从显示中移除 {n} 个完成的智能体。',
|
|
1428
1480
|
'm.clearedNone': '没有可移除的已完成智能体。',
|
|
1429
|
-
'm.status': '状态\n 模型 {pm}\n 批准 {approval}\n 智能体 {total} 个总计 / {active} 个活跃\n 已更改文件 {changed}\n 会话费用 ${cost}',
|
|
1481
|
+
'm.status': '状态\n 模型 {pm}\n 批准 {approval}\n 智能体 {total} 个总计 / {active} 个活跃\n 已更改文件 {changed}\n 会话费用 ${cost}\n 项目记忆 {memory} / ${memoryCost}',
|
|
1482
|
+
'm.usageMemory': '用法:/memory 或 /memory refresh',
|
|
1483
|
+
'm.memoryStatus': '项目记忆\n 状态 {status}\n 生成时间 {date}\n 模型 {model}\n Tokens {tokens}\n 费用 ${cost}',
|
|
1484
|
+
'm.doctorMemory': '• 项目记忆:{status}(生成于 {date})',
|
|
1430
1485
|
'm.rawOn': '原始活动详情已开启 — 焦点视图中可查看原始智能体日志。',
|
|
1431
1486
|
'm.rawOff': '原始活动详情已关闭。',
|
|
1432
1487
|
'm.copyNone': '没有可复制的已完成智能体输出。',
|
|
@@ -1496,6 +1551,7 @@ const zh = {
|
|
|
1496
1551
|
'q.recommended': '★ 推荐',
|
|
1497
1552
|
'q.keys': '↑/↓ + 回车 或 1-4 回答 · 30 秒内未回答 → 自动选择推荐选项',
|
|
1498
1553
|
'cost.title': '💰 会话成本 — 实时,按智能体',
|
|
1554
|
+
'cost.memory': '项目记忆',
|
|
1499
1555
|
'cost.empty': '尚无智能体 — 成本将在此实时显示。',
|
|
1500
1556
|
'cost.unknown': '(价格未知 — 在 /settings → 模型价格 中设置)',
|
|
1501
1557
|
'cost.total': '会话总计:',
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,9 @@ if (firstRun)
|
|
|
24
24
|
const headless = argv.includes('--headless');
|
|
25
25
|
if (headless)
|
|
26
26
|
argv.splice(argv.indexOf('--headless'), 1);
|
|
27
|
+
const yolo = argv.includes('--yolo');
|
|
28
|
+
if (yolo)
|
|
29
|
+
argv.splice(argv.indexOf('--yolo'), 1);
|
|
27
30
|
const jsonOut = argv.includes('--json');
|
|
28
31
|
if (jsonOut)
|
|
29
32
|
argv.splice(argv.indexOf('--json'), 1);
|
|
@@ -31,6 +34,15 @@ const noUpdate = argv.includes('--no-update');
|
|
|
31
34
|
if (noUpdate)
|
|
32
35
|
argv.splice(argv.indexOf('--no-update'), 1);
|
|
33
36
|
const configHome = takeFlagValue('--config-home');
|
|
37
|
+
let forcedProfile;
|
|
38
|
+
for (const candidate of ['quick', 'standard', 'deep']) {
|
|
39
|
+
const flag = `--${candidate}`;
|
|
40
|
+
if (argv.includes(flag)) {
|
|
41
|
+
forcedProfile = candidate;
|
|
42
|
+
argv.splice(argv.indexOf(flag), 1);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
34
46
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
35
47
|
console.log(`⚡ Parallel — real-time parallel coding agents.
|
|
36
48
|
|
|
@@ -45,9 +57,11 @@ Usage:
|
|
|
45
57
|
Use <dir>/config.json instead of ~/.parallel/config.json
|
|
46
58
|
parallel --no-update [folder]
|
|
47
59
|
Start without checking npm for a newer Parallel version
|
|
48
|
-
parallel --headless "task1" ["task2"…] [--json]
|
|
60
|
+
parallel --headless "task1" ["task2"…] [--quick|--standard|--deep] [--json]
|
|
49
61
|
No TUI: one agent per task in the current folder,
|
|
50
|
-
auto-
|
|
62
|
+
auto-safe shell, summary (or JSON) on stdout — for CI
|
|
63
|
+
parallel --headless --yolo "task"
|
|
64
|
+
Dangerous: approve every shell command without prompts.
|
|
51
65
|
|
|
52
66
|
Environment variables:
|
|
53
67
|
PARALLEL_API_KEY API key for the default provider
|
|
@@ -87,17 +101,23 @@ if (argv[0] === 'attach') {
|
|
|
87
101
|
const config = loadConfig();
|
|
88
102
|
if (config.language)
|
|
89
103
|
setLang(config.language);
|
|
90
|
-
const { socketPath } = await import('./server.js');
|
|
104
|
+
const { readSessionToken, socketPath } = await import('./server.js');
|
|
91
105
|
const sock = socketPath(root);
|
|
92
106
|
if (!fs.existsSync(sock)) {
|
|
93
107
|
console.error(`No running Parallel session found in ${root} (missing ${sock}).`);
|
|
94
108
|
console.error('Start `parallel` in that folder first, then re-run attach.');
|
|
95
109
|
process.exit(1);
|
|
96
110
|
}
|
|
111
|
+
const token = readSessionToken(root);
|
|
112
|
+
if (!token) {
|
|
113
|
+
console.error(`No attach authentication token found in ${root}.`);
|
|
114
|
+
console.error('Restart the main Parallel session, then re-run attach.');
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
97
117
|
const { AttachApp } = await import('./ui/AttachApp.js');
|
|
98
118
|
// NO alternate screen here: <Static> writes into the native scrollback,
|
|
99
119
|
// so the user can scroll this agent's history like any terminal output.
|
|
100
|
-
const attachApp = render(_jsx(AttachApp, { agentRef: agentRef, sock: sock }), { exitOnCtrlC: true });
|
|
120
|
+
const attachApp = render(_jsx(AttachApp, { agentRef: agentRef, sock: sock, token: token }), { exitOnCtrlC: true });
|
|
101
121
|
await attachApp.waitUntilExit();
|
|
102
122
|
process.exit(0);
|
|
103
123
|
}
|
|
@@ -112,8 +132,9 @@ if (headless) {
|
|
|
112
132
|
if (config.language)
|
|
113
133
|
setLang(config.language);
|
|
114
134
|
const ctl = new Controller(config, process.cwd());
|
|
115
|
-
// No
|
|
116
|
-
|
|
135
|
+
// No TUI approval prompt in headless: keep a conservative shell policy unless the
|
|
136
|
+
// user explicitly opts into the dangerous legacy behavior.
|
|
137
|
+
ctl.setSessionApprovalMode(yolo ? 'yolo' : 'auto-safe');
|
|
117
138
|
const provider = ctl.sessionProvider();
|
|
118
139
|
if (!provider || !providerReady(provider)) {
|
|
119
140
|
console.error('Headless mode needs a ready provider and model. Run `parallel` interactively once, or set PARALLEL_API_KEY / PARALLEL_MODEL.');
|
|
@@ -121,11 +142,13 @@ if (headless) {
|
|
|
121
142
|
}
|
|
122
143
|
// Agent questions cannot be asked: auto-answer with the recommended option.
|
|
123
144
|
ctl.on('update', () => {
|
|
145
|
+
for (const approval of [...ctl.approvals])
|
|
146
|
+
ctl.answerApproval(approval.id, false, false);
|
|
124
147
|
for (const q of [...ctl.questions])
|
|
125
148
|
ctl.answerQuestion(q.id, q.options[q.recommended] ?? '', true);
|
|
126
149
|
});
|
|
127
150
|
for (const task of tasks) {
|
|
128
|
-
if (!ctl.spawnAgent(task)) {
|
|
151
|
+
if (!ctl.spawnAgent(task, undefined, undefined, undefined, undefined, undefined, 'task', forcedProfile)) {
|
|
129
152
|
console.error(`Failed to spawn an agent for: ${task}`);
|
|
130
153
|
process.exit(1);
|
|
131
154
|
}
|
|
@@ -156,6 +179,7 @@ if (headless) {
|
|
|
156
179
|
alias: a.alias,
|
|
157
180
|
task: a.task,
|
|
158
181
|
state: a.state,
|
|
182
|
+
profile: a.profile,
|
|
159
183
|
steps: a.steps,
|
|
160
184
|
tokensIn: a.tokensIn,
|
|
161
185
|
tokensOut: a.tokensOut,
|
package/dist/llm/client.js
CHANGED
|
@@ -10,8 +10,9 @@ export class LLMClient {
|
|
|
10
10
|
* One chat completion round, with optional function-calling tools.
|
|
11
11
|
* Retries on transient errors (429/5xx) with exponential backoff.
|
|
12
12
|
*/
|
|
13
|
-
async chat(messages, tools, signal) {
|
|
13
|
+
async chat(messages, tools, signal, options = {}) {
|
|
14
14
|
let lastErr;
|
|
15
|
+
let retries = 0;
|
|
15
16
|
for (let attempt = 0; attempt < 4; attempt++) {
|
|
16
17
|
try {
|
|
17
18
|
const res = await this.client.chat.completions.create({
|
|
@@ -19,8 +20,8 @@ export class LLMClient {
|
|
|
19
20
|
messages,
|
|
20
21
|
tools: tools && tools.length > 0 ? tools : undefined,
|
|
21
22
|
temperature: 0.2,
|
|
22
|
-
max_tokens: 4096,
|
|
23
|
-
}, { signal });
|
|
23
|
+
max_tokens: options.maxTokens ?? 4096,
|
|
24
|
+
}, { signal, timeout: options.timeoutMs });
|
|
24
25
|
const choice = res.choices[0];
|
|
25
26
|
if (!choice?.message)
|
|
26
27
|
throw new Error('Empty response from model');
|
|
@@ -28,6 +29,8 @@ export class LLMClient {
|
|
|
28
29
|
message: choice.message,
|
|
29
30
|
tokensIn: res.usage?.prompt_tokens ?? 0,
|
|
30
31
|
tokensOut: res.usage?.completion_tokens ?? 0,
|
|
32
|
+
cachedTokens: Number(res.usage?.prompt_tokens_details?.cached_tokens ?? 0),
|
|
33
|
+
retries,
|
|
31
34
|
};
|
|
32
35
|
}
|
|
33
36
|
catch (err) {
|
|
@@ -38,6 +41,7 @@ export class LLMClient {
|
|
|
38
41
|
const retriable = status === 429 || (typeof status === 'number' && status >= 500) || err?.code === 'ECONNRESET';
|
|
39
42
|
if (!retriable || attempt === 3)
|
|
40
43
|
throw err;
|
|
44
|
+
retries++;
|
|
41
45
|
await new Promise((r) => setTimeout(r, 1500 * Math.pow(2, attempt)));
|
|
42
46
|
}
|
|
43
47
|
}
|