@yemi33/minions 0.1.577 → 0.1.579
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 +10 -0
- package/dashboard.html +4 -3
- package/engine/llm.js +52 -84
- package/engine.js +6 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.579 (2026-04-08)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- CC response renderer HTML-escape before innerHTML injection (closes #506) (#525)
|
|
7
|
+
|
|
8
|
+
## 0.1.578 (2026-04-08)
|
|
9
|
+
|
|
10
|
+
### Other
|
|
11
|
+
- refactor: extract _spawnProcess helper, fix dep fetch error tracking, cleanup file leak
|
|
12
|
+
|
|
3
13
|
## 0.1.577 (2026-04-08)
|
|
4
14
|
|
|
5
15
|
### Other
|
package/dashboard.html
CHANGED
|
@@ -4732,10 +4732,11 @@ async function _ccDoSend(message, skipUserMsg) {
|
|
|
4732
4732
|
ccUpdateSessionIndicator();
|
|
4733
4733
|
}
|
|
4734
4734
|
|
|
4735
|
-
// Render markdown-ish response
|
|
4735
|
+
// Render markdown-ish response (HTML-escape first to prevent XSS + broken rendering)
|
|
4736
4736
|
const ccElapsed = Math.round((Date.now() - ccStartTime) / 1000);
|
|
4737
|
-
const
|
|
4738
|
-
|
|
4737
|
+
const safe = escHtml(data.text || '');
|
|
4738
|
+
const rendered = safe.replace(/\*\*([^*\n]+)\*\*/g, '<strong>$1</strong>')
|
|
4739
|
+
.replace(/`([^`\n]+)`/g, '<code style="background:var(--surface);padding:1px 4px;border-radius:3px;font-size:11px">$1</code>')
|
|
4739
4740
|
.replace(/\n/g, '<br>');
|
|
4740
4741
|
ccAddMessage('assistant', rendered + '<div style="font-size:9px;color:var(--muted);margin-top:4px;text-align:right">' + ccElapsed + 's</div>');
|
|
4741
4742
|
|
package/engine/llm.js
CHANGED
|
@@ -68,54 +68,62 @@ function _buildCliArgs({ model, maxTurns, allowedTools, effort, sessionId, sysPr
|
|
|
68
68
|
return args;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Spawn a claude CLI process. Returns { proc, cleanupFiles } or null if binary not cached.
|
|
73
|
+
* When direct=true, spawns claude CLI directly (fewer syscalls). Otherwise uses spawn-agent.js.
|
|
74
|
+
*/
|
|
75
|
+
function _spawnProcess(promptText, sysPromptText, { direct, label, model, maxTurns, allowedTools, effort, sessionId }) {
|
|
76
|
+
const fs = require('fs');
|
|
77
|
+
const id = uid();
|
|
78
|
+
const tmpDir = path.join(ENGINE_DIR, 'tmp');
|
|
79
|
+
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
80
|
+
|
|
81
|
+
const cleanupFiles = [];
|
|
82
|
+
const resolved = direct ? _resolveClaudeBin() : null;
|
|
83
|
+
|
|
84
|
+
if (resolved) {
|
|
85
|
+
let sysTmpPath = null;
|
|
86
|
+
if (!sessionId && sysPromptText) {
|
|
87
|
+
sysTmpPath = path.join(tmpDir, `direct-sys-${id}.md`);
|
|
88
|
+
fs.writeFileSync(sysTmpPath, sysPromptText);
|
|
89
|
+
cleanupFiles.push(sysTmpPath);
|
|
90
|
+
}
|
|
91
|
+
const cliArgs = _buildCliArgs({ model, maxTurns, allowedTools, effort, sessionId, sysPromptFile: sysTmpPath });
|
|
92
|
+
const proc = resolved.native
|
|
93
|
+
? runFile(resolved.bin, cliArgs, { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() })
|
|
94
|
+
: runFile(process.execPath, [resolved.bin, ...cliArgs], { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() });
|
|
95
|
+
try { proc.stdin.write(promptText); proc.stdin.end(); } catch { /* broken pipe */ }
|
|
96
|
+
return { proc, cleanupFiles };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Indirect: use spawn-agent.js
|
|
100
|
+
const promptPath = path.join(tmpDir, `${label}-prompt-${id}.md`);
|
|
101
|
+
const sysPath = path.join(tmpDir, `${label}-sys-${id}.md`);
|
|
102
|
+
safeWrite(promptPath, promptText);
|
|
103
|
+
safeWrite(sysPath, sysPromptText || '');
|
|
104
|
+
cleanupFiles.push(promptPath, sysPath);
|
|
105
|
+
|
|
106
|
+
const spawnScript = path.join(ENGINE_DIR, 'spawn-agent.js');
|
|
107
|
+
const args = [
|
|
108
|
+
spawnScript, promptPath, sysPath,
|
|
109
|
+
'--output-format', 'stream-json', '--max-turns', String(maxTurns), '--model', model,
|
|
110
|
+
'--verbose',
|
|
111
|
+
];
|
|
112
|
+
if (allowedTools) args.push('--allowedTools', allowedTools);
|
|
113
|
+
if (effort) args.push('--effort', effort);
|
|
114
|
+
args.push('--permission-mode', 'bypassPermissions');
|
|
115
|
+
if (sessionId) args.push('--resume', sessionId);
|
|
116
|
+
|
|
117
|
+
const proc = runFile(process.execPath, args, { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() });
|
|
118
|
+
return { proc, cleanupFiles };
|
|
119
|
+
}
|
|
120
|
+
|
|
71
121
|
// ── Core LLM Call ───────────────────────────────────────────────────────────
|
|
72
122
|
|
|
73
123
|
function callLLM(promptText, sysPromptText, { timeout = 120000, label = 'llm', model = 'sonnet', maxTurns = 1, allowedTools = '', sessionId = null, effort = null, direct = false } = {}) {
|
|
74
124
|
return new Promise((resolve) => {
|
|
75
125
|
const _startMs = Date.now();
|
|
76
|
-
const
|
|
77
|
-
const id = uid();
|
|
78
|
-
const tmpDir = path.join(ENGINE_DIR, 'tmp');
|
|
79
|
-
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
80
|
-
|
|
81
|
-
let proc;
|
|
82
|
-
const cleanupFiles = [];
|
|
83
|
-
const resolved = direct ? _resolveClaudeBin() : null;
|
|
84
|
-
|
|
85
|
-
if (resolved) {
|
|
86
|
-
// Direct spawn: skip spawn-agent.js — fewer file syscalls, no extra process
|
|
87
|
-
let sysTmpPath = null;
|
|
88
|
-
if (!sessionId && sysPromptText) {
|
|
89
|
-
sysTmpPath = path.join(tmpDir, `direct-sys-${id}.md`);
|
|
90
|
-
fs.writeFileSync(sysTmpPath, sysPromptText);
|
|
91
|
-
cleanupFiles.push(sysTmpPath);
|
|
92
|
-
}
|
|
93
|
-
const cliArgs = _buildCliArgs({ model, maxTurns, allowedTools, effort, sessionId, sysPromptFile: sysTmpPath });
|
|
94
|
-
proc = resolved.native
|
|
95
|
-
? runFile(resolved.bin, cliArgs, { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() })
|
|
96
|
-
: runFile(process.execPath, [resolved.bin, ...cliArgs], { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() });
|
|
97
|
-
try { proc.stdin.write(promptText); proc.stdin.end(); } catch { /* broken pipe */ }
|
|
98
|
-
} else {
|
|
99
|
-
// Indirect: use spawn-agent.js (for agent dispatches or if binary not cached)
|
|
100
|
-
const promptPath = path.join(tmpDir, `${label}-prompt-${id}.md`);
|
|
101
|
-
const sysPath = path.join(tmpDir, `${label}-sys-${id}.md`);
|
|
102
|
-
safeWrite(promptPath, promptText);
|
|
103
|
-
safeWrite(sysPath, sysPromptText || '');
|
|
104
|
-
cleanupFiles.push(promptPath, sysPath);
|
|
105
|
-
|
|
106
|
-
const spawnScript = path.join(ENGINE_DIR, 'spawn-agent.js');
|
|
107
|
-
const args = [
|
|
108
|
-
spawnScript, promptPath, sysPath,
|
|
109
|
-
'--output-format', 'stream-json', '--max-turns', String(maxTurns), '--model', model,
|
|
110
|
-
'--verbose',
|
|
111
|
-
];
|
|
112
|
-
if (allowedTools) args.push('--allowedTools', allowedTools);
|
|
113
|
-
if (effort) args.push('--effort', effort);
|
|
114
|
-
args.push('--permission-mode', 'bypassPermissions');
|
|
115
|
-
if (sessionId) args.push('--resume', sessionId);
|
|
116
|
-
|
|
117
|
-
proc = runFile(process.execPath, args, { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() });
|
|
118
|
-
}
|
|
126
|
+
const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, { direct, label, model, maxTurns, allowedTools, effort, sessionId });
|
|
119
127
|
|
|
120
128
|
let stdout = '';
|
|
121
129
|
let stderr = '';
|
|
@@ -167,47 +175,7 @@ function callLLMStreaming(promptText, sysPromptText, { timeout = 120000, label =
|
|
|
167
175
|
let _abort = null;
|
|
168
176
|
const promise = new Promise((resolve) => {
|
|
169
177
|
const _startMs = Date.now();
|
|
170
|
-
const
|
|
171
|
-
const id = uid();
|
|
172
|
-
const tmpDir = path.join(ENGINE_DIR, 'tmp');
|
|
173
|
-
if (!fs.existsSync(tmpDir)) fs.mkdirSync(tmpDir, { recursive: true });
|
|
174
|
-
|
|
175
|
-
let proc;
|
|
176
|
-
const cleanupFiles = [];
|
|
177
|
-
const resolved = direct ? _resolveClaudeBin() : null;
|
|
178
|
-
|
|
179
|
-
if (resolved) {
|
|
180
|
-
let sysTmpPath = null;
|
|
181
|
-
if (!sessionId && sysPromptText) {
|
|
182
|
-
sysTmpPath = path.join(tmpDir, `direct-sys-${id}.md`);
|
|
183
|
-
fs.writeFileSync(sysTmpPath, sysPromptText);
|
|
184
|
-
cleanupFiles.push(sysTmpPath);
|
|
185
|
-
}
|
|
186
|
-
const cliArgs = _buildCliArgs({ model, maxTurns, allowedTools, effort, sessionId, sysPromptFile: sysTmpPath });
|
|
187
|
-
proc = resolved.native
|
|
188
|
-
? runFile(resolved.bin, cliArgs, { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() })
|
|
189
|
-
: runFile(process.execPath, [resolved.bin, ...cliArgs], { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() });
|
|
190
|
-
try { proc.stdin.write(promptText); proc.stdin.end(); } catch { /* broken pipe */ }
|
|
191
|
-
} else {
|
|
192
|
-
const promptPath = path.join(tmpDir, `${label}-prompt-${id}.md`);
|
|
193
|
-
const sysPath = path.join(tmpDir, `${label}-sys-${id}.md`);
|
|
194
|
-
safeWrite(promptPath, promptText);
|
|
195
|
-
safeWrite(sysPath, sysPromptText || '');
|
|
196
|
-
cleanupFiles.push(promptPath, sysPath);
|
|
197
|
-
|
|
198
|
-
const spawnScript = path.join(ENGINE_DIR, 'spawn-agent.js');
|
|
199
|
-
const args = [
|
|
200
|
-
spawnScript, promptPath, sysPath,
|
|
201
|
-
'--output-format', 'stream-json', '--max-turns', String(maxTurns), '--model', model,
|
|
202
|
-
'--verbose',
|
|
203
|
-
];
|
|
204
|
-
if (allowedTools) args.push('--allowedTools', allowedTools);
|
|
205
|
-
if (effort) args.push('--effort', effort);
|
|
206
|
-
args.push('--permission-mode', 'bypassPermissions');
|
|
207
|
-
if (sessionId) args.push('--resume', sessionId);
|
|
208
|
-
|
|
209
|
-
proc = runFile(process.execPath, args, { cwd: MINIONS_DIR, stdio: ['pipe', 'pipe', 'pipe'], env: cleanChildEnv() });
|
|
210
|
-
}
|
|
178
|
+
const { proc, cleanupFiles } = _spawnProcess(promptText, sysPromptText, { direct, label, model, maxTurns, allowedTools, effort, sessionId });
|
|
211
179
|
|
|
212
180
|
_abort = () => { shared.killImmediate(proc); };
|
|
213
181
|
|
package/engine.js
CHANGED
|
@@ -292,6 +292,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
292
292
|
safeWrite(promptPath, fullTaskPrompt);
|
|
293
293
|
const sysPromptPath = path.join(tmpDir, `sysprompt-${safeId}.md`);
|
|
294
294
|
safeWrite(sysPromptPath, systemPrompt);
|
|
295
|
+
const _cleanupPromptFiles = () => { safeUnlink(promptPath); safeUnlink(sysPromptPath); };
|
|
295
296
|
|
|
296
297
|
if (branchName) {
|
|
297
298
|
const wtSuffix = id ? id.split('-').pop() : shared.uid();
|
|
@@ -440,11 +441,11 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
440
441
|
execAsync(`git fetch origin "${depBranch}"`, { ..._gitOpts, cwd: rootDir }).then(() => depBranch)
|
|
441
442
|
)
|
|
442
443
|
);
|
|
443
|
-
for (
|
|
444
|
-
if (
|
|
445
|
-
const failedBranch = fetchable.
|
|
444
|
+
for (let i = 0; i < fetchResults.length; i++) {
|
|
445
|
+
if (fetchResults[i].status === 'rejected') {
|
|
446
|
+
const failedBranch = fetchable[i].branch;
|
|
446
447
|
_failedRefCache.add(failedBranch);
|
|
447
|
-
log('warn', `Failed to fetch dependency ${failedBranch}: ${
|
|
448
|
+
log('warn', `Failed to fetch dependency ${failedBranch}: ${fetchResults[i].reason?.message}`);
|
|
448
449
|
depMergeFailed = true;
|
|
449
450
|
}
|
|
450
451
|
}
|
|
@@ -462,6 +463,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
462
463
|
}
|
|
463
464
|
}
|
|
464
465
|
if (depMergeFailed) {
|
|
466
|
+
_cleanupPromptFiles();
|
|
465
467
|
completeDispatch(id, DISPATCH_RESULT.ERROR, `Dependency merge failed — will retry next tick`);
|
|
466
468
|
return;
|
|
467
469
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.579",
|
|
4
4
|
"description": "Multi-agent AI dev team that runs from ~/.minions/ — five autonomous agents share a single engine, dashboard, and knowledge base",
|
|
5
5
|
"bin": {
|
|
6
6
|
"minions": "bin/minions.js"
|