@parallel-cli/parallel 0.4.9 → 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 +33 -0
- package/README.md +23 -6
- package/dist/agents/agent.js +194 -25
- package/dist/agents/execution-policy.js +58 -0
- package/dist/agents/tools.js +71 -17
- package/dist/commands.js +62 -5
- package/dist/controller.js +136 -5
- package/dist/diagnostics.js +209 -0
- package/dist/i18n.js +40 -4
- package/dist/index.js +12 -2
- package/dist/llm/client.js +7 -3
- package/dist/project-context.js +477 -0
- package/dist/project-index.js +186 -0
- package/dist/ui/AgentPanel.js +5 -2
- package/dist/ui/App.js +4 -2
- package/dist/ui/SettingsPanel.js +22 -23
- package/dist/ui/Wizard.js +49 -21
- package/dist/ui/views.js +4 -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
|
@@ -87,6 +87,10 @@ const en = {
|
|
|
87
87
|
'agent.compactingStart': 'Long memory: summarizing earlier history to keep useful context.',
|
|
88
88
|
'agent.compactingDone': 'Long memory: earlier history summarized and kept in context.',
|
|
89
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',
|
|
90
94
|
// input
|
|
91
95
|
'input.atHint': ' — send a real-time instruction',
|
|
92
96
|
'input.atAll': ' to all agents',
|
|
@@ -146,6 +150,7 @@ const en = {
|
|
|
146
150
|
'cmd.notes': 'Inter-agent notes history',
|
|
147
151
|
'cmd.diff': 'Live file diffs',
|
|
148
152
|
'cmd.status': 'Show session status: model, agents, changes, cost',
|
|
153
|
+
'cmd.memory': 'Show or refresh the shared project memory',
|
|
149
154
|
'cmd.raw': 'Toggle raw activity detail in focus view',
|
|
150
155
|
'cmd.copy': 'Copy the latest completed agent result',
|
|
151
156
|
'timeline.activity': 'Activity',
|
|
@@ -272,7 +277,10 @@ const en = {
|
|
|
272
277
|
'm.cleared': 'Finished agents removed from display.',
|
|
273
278
|
'm.clearedN': 'Removed {n} finished agent(s) from display.',
|
|
274
279
|
'm.clearedNone': 'No finished agents to clear.',
|
|
275
|
-
'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})',
|
|
276
284
|
'm.rawOn': 'Raw activity detail ON — raw agent logs visible in focus view.',
|
|
277
285
|
'm.rawOff': 'Raw activity detail OFF.',
|
|
278
286
|
'm.copyNone': 'No completed agent output to copy.',
|
|
@@ -345,6 +353,7 @@ const en = {
|
|
|
345
353
|
'q.keys': '↑/↓ + Enter or 1-4 to answer · no answer in 30s → the recommended option is chosen',
|
|
346
354
|
// cost view
|
|
347
355
|
'cost.title': '💰 SESSION COST — real time, per agent',
|
|
356
|
+
'cost.memory': 'Project memory',
|
|
348
357
|
'cost.empty': 'No agents yet — costs will appear here in real time.',
|
|
349
358
|
'cost.unknown': '(unknown price — set it in /settings → Model prices)',
|
|
350
359
|
'cost.total': 'Session total:',
|
|
@@ -488,6 +497,10 @@ const fr = {
|
|
|
488
497
|
'agent.compactingStart': "Mémoire longue : résumé automatique de l'historique pour garder le contexte utile.",
|
|
489
498
|
'agent.compactingDone': "Mémoire longue : l'historique ancien est résumé et conservé dans le contexte.",
|
|
490
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',
|
|
491
504
|
'input.atHint': ' — envoyer une instruction temps réel',
|
|
492
505
|
'input.atAll': ' à tous les agents',
|
|
493
506
|
'input.pasted': '[collé #{n} : {lines} lignes]',
|
|
@@ -543,6 +556,7 @@ const fr = {
|
|
|
543
556
|
'cmd.notes': 'Historique des notes inter-agents',
|
|
544
557
|
'cmd.diff': 'Modifications de fichiers (diffs en direct)',
|
|
545
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',
|
|
546
560
|
'cmd.raw': 'Basculer le détail brut de l\'activité dans la vue focus',
|
|
547
561
|
'cmd.copy': 'Copier le dernier résultat d\'agent terminé',
|
|
548
562
|
'timeline.activity': 'Activité',
|
|
@@ -668,7 +682,10 @@ const fr = {
|
|
|
668
682
|
'm.cleared': "Agents terminés retirés de l'affichage.",
|
|
669
683
|
'm.clearedN': "{n} agent(s) terminé(s) retiré(s) de l'affichage.",
|
|
670
684
|
'm.clearedNone': "Aucun agent terminé à retirer.",
|
|
671
|
-
'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})',
|
|
672
689
|
'm.rawOn': 'Détail brut activé — logs agents visibles dans la vue focus.',
|
|
673
690
|
'm.rawOff': 'Détail brut désactivé.',
|
|
674
691
|
'm.copyNone': "Aucun résultat d'agent à copier.",
|
|
@@ -738,6 +755,7 @@ const fr = {
|
|
|
738
755
|
'q.recommended': '★ recommandé',
|
|
739
756
|
'q.keys': '↑/↓ + Entrée ou 1-4 pour répondre · sans réponse en 30s → l’option recommandée est choisie',
|
|
740
757
|
'cost.title': '💰 COÛT DE LA SESSION — temps réel, par agent',
|
|
758
|
+
'cost.memory': 'Mémoire projet',
|
|
741
759
|
'cost.empty': 'Aucun agent pour le moment — les coûts apparaîtront ici en temps réel.',
|
|
742
760
|
'cost.unknown': '(prix inconnu — règle-le dans /settings → Prix des modèles)',
|
|
743
761
|
'cost.total': 'Total session :',
|
|
@@ -877,6 +895,10 @@ const es = {
|
|
|
877
895
|
'agent.compactingStart': 'Memoria larga: resumen automático del historial para conservar el contexto útil.',
|
|
878
896
|
'agent.compactingDone': 'Memoria larga: el historial anterior se resumió y se mantuvo en contexto.',
|
|
879
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',
|
|
880
902
|
'input.atHint': ' — enviar una instrucción en tiempo real',
|
|
881
903
|
'input.atAll': ' a todos los agentes',
|
|
882
904
|
'input.pasted': '[pegado #{n}: {lines} líneas]',
|
|
@@ -932,6 +954,7 @@ const es = {
|
|
|
932
954
|
'cmd.notes': 'Historial de notas entre agentes',
|
|
933
955
|
'cmd.diff': 'Diffs de archivos en vivo',
|
|
934
956
|
'cmd.status': 'Mostrar estado de la sesión: modelo, agentes, cambios, coste',
|
|
957
|
+
'cmd.memory': 'Mostrar o actualizar la memoria compartida del proyecto',
|
|
935
958
|
'cmd.raw': 'Alternar detalle de actividad sin procesar en la vista focus',
|
|
936
959
|
'cmd.copy': 'Copiar el último resultado de agente completado',
|
|
937
960
|
'timeline.activity': 'Actividad',
|
|
@@ -1057,7 +1080,10 @@ const es = {
|
|
|
1057
1080
|
'm.cleared': 'Agentes terminados quitados de la pantalla.',
|
|
1058
1081
|
'm.clearedN': '{n} agente(s) terminado(s) quitado(s) de la pantalla.',
|
|
1059
1082
|
'm.clearedNone': 'No hay agentes terminados que quitar.',
|
|
1060
|
-
'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})',
|
|
1061
1087
|
'm.rawOn': 'Detalle de actividad activado — logs visibles en vista focus.',
|
|
1062
1088
|
'm.rawOff': 'Detalle de actividad desactivado.',
|
|
1063
1089
|
'm.copyNone': 'No hay resultado de agente que copiar.',
|
|
@@ -1127,6 +1153,7 @@ const es = {
|
|
|
1127
1153
|
'q.recommended': '★ recomendado',
|
|
1128
1154
|
'q.keys': '↑/↓ + Enter o 1-4 para responder · sin respuesta en 30s → se elige la opción recomendada',
|
|
1129
1155
|
'cost.title': '💰 COSTE DE LA SESIÓN — tiempo real, por agente',
|
|
1156
|
+
'cost.memory': 'Memoria del proyecto',
|
|
1130
1157
|
'cost.empty': 'Aún no hay agentes — los costes aparecerán aquí en tiempo real.',
|
|
1131
1158
|
'cost.unknown': '(precio desconocido — defínelo en /settings → Precios de modelos)',
|
|
1132
1159
|
'cost.total': 'Total de la sesión:',
|
|
@@ -1266,6 +1293,10 @@ const zh = {
|
|
|
1266
1293
|
'agent.compactingStart': '长记忆:正在自动总结较早历史,以保留有用上下文。',
|
|
1267
1294
|
'agent.compactingDone': '长记忆:较早历史已总结并保留在上下文中。',
|
|
1268
1295
|
'agent.compactingFallback': '长记忆:较早历史已缩短,以保持上下文响应速度。',
|
|
1296
|
+
'memory.indexing': '项目记忆:正在索引代码库…',
|
|
1297
|
+
'memory.ready': '项目记忆已就绪。',
|
|
1298
|
+
'memory.fallback': '项目记忆使用确定性回退上下文。{detail}',
|
|
1299
|
+
'memory.label': '记忆',
|
|
1269
1300
|
'input.atHint': ' — 发送实时指令',
|
|
1270
1301
|
'input.atAll': ' 给所有智能体',
|
|
1271
1302
|
'input.pasted': '[粘贴 #{n}:{lines} 行]',
|
|
@@ -1321,6 +1352,7 @@ const zh = {
|
|
|
1321
1352
|
'cmd.notes': '智能体间便签历史',
|
|
1322
1353
|
'cmd.diff': '实时文件差异',
|
|
1323
1354
|
'cmd.status': '显示会话状态:模型、智能体、更改、费用',
|
|
1355
|
+
'cmd.memory': '显示或刷新共享项目记忆',
|
|
1324
1356
|
'cmd.raw': '在焦点视图中切换原始活动详情',
|
|
1325
1357
|
'cmd.copy': '复制最新完成的智能体结果',
|
|
1326
1358
|
'timeline.activity': '活动',
|
|
@@ -1446,7 +1478,10 @@ const zh = {
|
|
|
1446
1478
|
'm.cleared': '已从显示中移除完成的智能体。',
|
|
1447
1479
|
'm.clearedN': '已从显示中移除 {n} 个完成的智能体。',
|
|
1448
1480
|
'm.clearedNone': '没有可移除的已完成智能体。',
|
|
1449
|
-
'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})',
|
|
1450
1485
|
'm.rawOn': '原始活动详情已开启 — 焦点视图中可查看原始智能体日志。',
|
|
1451
1486
|
'm.rawOff': '原始活动详情已关闭。',
|
|
1452
1487
|
'm.copyNone': '没有可复制的已完成智能体输出。',
|
|
@@ -1516,6 +1551,7 @@ const zh = {
|
|
|
1516
1551
|
'q.recommended': '★ 推荐',
|
|
1517
1552
|
'q.keys': '↑/↓ + 回车 或 1-4 回答 · 30 秒内未回答 → 自动选择推荐选项',
|
|
1518
1553
|
'cost.title': '💰 会话成本 — 实时,按智能体',
|
|
1554
|
+
'cost.memory': '项目记忆',
|
|
1519
1555
|
'cost.empty': '尚无智能体 — 成本将在此实时显示。',
|
|
1520
1556
|
'cost.unknown': '(价格未知 — 在 /settings → 模型价格 中设置)',
|
|
1521
1557
|
'cost.total': '会话总计:',
|
package/dist/index.js
CHANGED
|
@@ -34,6 +34,15 @@ const noUpdate = argv.includes('--no-update');
|
|
|
34
34
|
if (noUpdate)
|
|
35
35
|
argv.splice(argv.indexOf('--no-update'), 1);
|
|
36
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
|
+
}
|
|
37
46
|
if (argv.includes('--help') || argv.includes('-h')) {
|
|
38
47
|
console.log(`⚡ Parallel — real-time parallel coding agents.
|
|
39
48
|
|
|
@@ -48,7 +57,7 @@ Usage:
|
|
|
48
57
|
Use <dir>/config.json instead of ~/.parallel/config.json
|
|
49
58
|
parallel --no-update [folder]
|
|
50
59
|
Start without checking npm for a newer Parallel version
|
|
51
|
-
parallel --headless "task1" ["task2"…] [--json]
|
|
60
|
+
parallel --headless "task1" ["task2"…] [--quick|--standard|--deep] [--json]
|
|
52
61
|
No TUI: one agent per task in the current folder,
|
|
53
62
|
auto-safe shell, summary (or JSON) on stdout — for CI
|
|
54
63
|
parallel --headless --yolo "task"
|
|
@@ -139,7 +148,7 @@ if (headless) {
|
|
|
139
148
|
ctl.answerQuestion(q.id, q.options[q.recommended] ?? '', true);
|
|
140
149
|
});
|
|
141
150
|
for (const task of tasks) {
|
|
142
|
-
if (!ctl.spawnAgent(task)) {
|
|
151
|
+
if (!ctl.spawnAgent(task, undefined, undefined, undefined, undefined, undefined, 'task', forcedProfile)) {
|
|
143
152
|
console.error(`Failed to spawn an agent for: ${task}`);
|
|
144
153
|
process.exit(1);
|
|
145
154
|
}
|
|
@@ -170,6 +179,7 @@ if (headless) {
|
|
|
170
179
|
alias: a.alias,
|
|
171
180
|
task: a.task,
|
|
172
181
|
state: a.state,
|
|
182
|
+
profile: a.profile,
|
|
173
183
|
steps: a.steps,
|
|
174
184
|
tokensIn: a.tokensIn,
|
|
175
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
|
}
|