@yemi33/minions 0.1.474 → 0.1.476
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 +5 -1
- package/engine/lifecycle.js +6 -5
- package/engine/llm.js +25 -26
- package/engine/queries.js +39 -8
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.476 (2026-04-07)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
+
- Optimize getAgentStatus() to read only head+tail of live-output.log
|
|
7
|
+
- Fix unlocked metrics.json in lifecycle.js post-merge hook
|
|
8
|
+
- Fix unlocked metrics.json read-modify-write in trackEngineUsage
|
|
6
9
|
- Limit SSE initial live-stream payload to last 64KB
|
|
7
10
|
|
|
8
11
|
### Fixes
|
|
12
|
+
- add optional chaining for config.agents in updatePrAfterReview
|
|
9
13
|
- pass config to updatePrAfterReview to fix test failure
|
|
10
14
|
|
|
11
15
|
## 0.1.473 (2026-04-07)
|
package/engine/lifecycle.js
CHANGED
|
@@ -717,7 +717,7 @@ function updatePrAfterReview(agentId, pr, project, config) {
|
|
|
717
717
|
if (!pr?.id) return;
|
|
718
718
|
|
|
719
719
|
if (!config) config = getConfig();
|
|
720
|
-
const reviewerName = config.agents[agentId]?.name || agentId;
|
|
720
|
+
const reviewerName = config.agents?.[agentId]?.name || agentId;
|
|
721
721
|
const dispatch = getDispatch();
|
|
722
722
|
const completedEntry = (dispatch.completed || []).find(d => d.agent === agentId && d.type === 'review');
|
|
723
723
|
|
|
@@ -853,10 +853,11 @@ async function handlePostMerge(pr, project, config, newStatus) {
|
|
|
853
853
|
const agentId = (pr.agent || '').toLowerCase();
|
|
854
854
|
if (agentId && config.agents?.[agentId]) {
|
|
855
855
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
856
|
+
mutateJsonFileLocked(metricsPath, (metrics) => {
|
|
857
|
+
if (!metrics[agentId]) metrics[agentId] = { tasksCompleted:0, tasksErrored:0, prsCreated:0, prsApproved:0, prsRejected:0, prsMerged:0, reviewsDone:0, lastTask:null, lastCompleted:null };
|
|
858
|
+
metrics[agentId].prsMerged = (metrics[agentId].prsMerged || 0) + 1;
|
|
859
|
+
return metrics;
|
|
860
|
+
});
|
|
860
861
|
}
|
|
861
862
|
|
|
862
863
|
const teamsUrl = process.env.TEAMS_PLAN_FLOW_URL;
|
package/engine/llm.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const shared = require('./shared');
|
|
8
|
-
const {
|
|
8
|
+
const { safeWrite, safeUnlink, uid, runFile, cleanChildEnv, parseStreamJsonOutput, mutateJsonFileLocked } = shared;
|
|
9
9
|
|
|
10
10
|
const MINIONS_DIR = path.resolve(__dirname, '..');
|
|
11
11
|
const ENGINE_DIR = __dirname;
|
|
@@ -14,31 +14,30 @@ function trackEngineUsage(category, usage) {
|
|
|
14
14
|
if (!usage) return;
|
|
15
15
|
try {
|
|
16
16
|
const metricsPath = path.join(ENGINE_DIR, 'metrics.json');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
metrics._engine[category]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
safeWrite(metricsPath, metrics);
|
|
17
|
+
mutateJsonFileLocked(metricsPath, (metrics) => {
|
|
18
|
+
if (!metrics._engine) metrics._engine = {};
|
|
19
|
+
if (!metrics._engine[category]) {
|
|
20
|
+
metrics._engine[category] = { calls: 0, costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, cacheCreation: 0 };
|
|
21
|
+
}
|
|
22
|
+
const cat = metrics._engine[category];
|
|
23
|
+
cat.calls++;
|
|
24
|
+
cat.costUsd += usage.costUsd || 0;
|
|
25
|
+
cat.inputTokens += usage.inputTokens || 0;
|
|
26
|
+
cat.outputTokens += usage.outputTokens || 0;
|
|
27
|
+
cat.cacheRead += usage.cacheRead || 0;
|
|
28
|
+
cat.cacheCreation = (cat.cacheCreation || 0) + (usage.cacheCreation || 0);
|
|
29
|
+
|
|
30
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
31
|
+
if (!metrics._daily) metrics._daily = {};
|
|
32
|
+
if (!metrics._daily[today]) metrics._daily[today] = { costUsd: 0, inputTokens: 0, outputTokens: 0, cacheRead: 0, tasks: 0 };
|
|
33
|
+
const daily = metrics._daily[today];
|
|
34
|
+
daily.costUsd += usage.costUsd || 0;
|
|
35
|
+
daily.inputTokens += usage.inputTokens || 0;
|
|
36
|
+
daily.outputTokens += usage.outputTokens || 0;
|
|
37
|
+
daily.cacheRead += usage.cacheRead || 0;
|
|
38
|
+
|
|
39
|
+
return metrics;
|
|
40
|
+
});
|
|
42
41
|
} catch (e) { console.error('metrics update:', e.message); }
|
|
43
42
|
}
|
|
44
43
|
|
package/engine/queries.js
CHANGED
|
@@ -13,6 +13,35 @@ const { safeRead, safeReadDir, safeJson, safeWrite, getProjects,
|
|
|
13
13
|
projectWorkItemsPath, projectPrPath, parseSkillFrontmatter, KB_CATEGORIES,
|
|
14
14
|
WI_STATUS } = shared;
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Read the first `bytes` and last `bytes` of a file efficiently using byte offsets.
|
|
18
|
+
* For files <= 2*bytes, reads the whole file. Returns { head, tail } strings.
|
|
19
|
+
* Returns { head: '', tail: '' } on any error.
|
|
20
|
+
*/
|
|
21
|
+
function readHeadTail(filePath, bytes = 1024) {
|
|
22
|
+
try {
|
|
23
|
+
const stat = fs.statSync(filePath);
|
|
24
|
+
const size = stat.size;
|
|
25
|
+
if (size === 0) return { head: '', tail: '' };
|
|
26
|
+
if (size <= bytes * 2) {
|
|
27
|
+
const full = fs.readFileSync(filePath, 'utf8');
|
|
28
|
+
return { head: full, tail: full };
|
|
29
|
+
}
|
|
30
|
+
const fd = fs.openSync(filePath, 'r');
|
|
31
|
+
try {
|
|
32
|
+
const headBuf = Buffer.alloc(bytes);
|
|
33
|
+
fs.readSync(fd, headBuf, 0, bytes, 0);
|
|
34
|
+
const tailBuf = Buffer.alloc(bytes);
|
|
35
|
+
fs.readSync(fd, tailBuf, 0, bytes, size - bytes);
|
|
36
|
+
return { head: headBuf.toString('utf8'), tail: tailBuf.toString('utf8') };
|
|
37
|
+
} finally {
|
|
38
|
+
fs.closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
return { head: '', tail: '' };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
16
45
|
// ── Paths ───────────────────────────────────────────────────────────────────
|
|
17
46
|
|
|
18
47
|
const MINIONS_DIR = shared.MINIONS_DIR;
|
|
@@ -130,19 +159,20 @@ function getAgentStatus(agentId) {
|
|
|
130
159
|
branch: active.meta?.branch || '',
|
|
131
160
|
started_at: active.started_at || active.created_at || null,
|
|
132
161
|
};
|
|
133
|
-
// Detect permission-waiting:
|
|
162
|
+
// Detect permission-waiting: read only head+tail of live-output.log (max 2KB total)
|
|
134
163
|
try {
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
164
|
+
const liveLogPath = path.join(AGENTS_DIR, agentId, 'live-output.log');
|
|
165
|
+
const { head, tail } = readHeadTail(liveLogPath, 1024);
|
|
166
|
+
if (head) {
|
|
167
|
+
// Check init message (in head) for permission mode
|
|
168
|
+
const initMatch = head.match(/"permissionMode"\s*:\s*"([^"]+)"/);
|
|
139
169
|
if (initMatch && initMatch[1] !== 'bypassPermissions') {
|
|
140
170
|
result._permissionMode = initMatch[1];
|
|
141
171
|
}
|
|
142
|
-
// Check if agent has been silent for >60s (
|
|
143
|
-
const lastLine =
|
|
172
|
+
// Check if agent has been silent for >60s (use tail for recent activity)
|
|
173
|
+
const lastLine = tail.trimEnd().split('\n').pop();
|
|
144
174
|
if (lastLine && lastLine.includes('"type":"assistant"') && lastLine.includes('"tool_use"')) {
|
|
145
|
-
const liveStat = fs.statSync(
|
|
175
|
+
const liveStat = fs.statSync(liveLogPath);
|
|
146
176
|
const silentMs = Date.now() - liveStat.mtimeMs;
|
|
147
177
|
if (silentMs > 60000 && result._permissionMode) {
|
|
148
178
|
result._warning = 'Possibly waiting for permission approval — agent is not in bypass mode';
|
|
@@ -748,6 +778,7 @@ module.exports = {
|
|
|
748
778
|
|
|
749
779
|
// Helpers
|
|
750
780
|
timeSince,
|
|
781
|
+
readHeadTail, // exported for testing
|
|
751
782
|
|
|
752
783
|
// Core state
|
|
753
784
|
getConfig, getControl, getDispatch, getDispatchQueue,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.476",
|
|
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"
|