@yemi33/minions 0.1.1651 → 0.1.1653
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/js/live-stream.js +6 -4
- package/engine/copilot-models.json +1 -1
- package/engine/lifecycle.js +10 -0
- package/engine/steering.js +23 -0
- package/engine.js +59 -27
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1653 (2026-05-01)
|
|
4
|
+
|
|
5
|
+
### Fixes
|
|
6
|
+
- exempt fix/test work items from PR contract when meta.pr is set
|
|
7
|
+
|
|
8
|
+
## 0.1.1652 (2026-05-01)
|
|
9
|
+
|
|
10
|
+
### Other
|
|
11
|
+
- Harden agent steering reliability
|
|
12
|
+
|
|
3
13
|
## 0.1.1651 (2026-05-01)
|
|
4
14
|
|
|
5
15
|
### Other
|
|
@@ -136,12 +136,14 @@ async function sendSteering() {
|
|
|
136
136
|
try {
|
|
137
137
|
const liveRes = await fetch('/api/agent/' + encodeURIComponent(currentAgentId) + '/live-output');
|
|
138
138
|
const text = await liveRes.text();
|
|
139
|
-
// Check if there's
|
|
139
|
+
// Check if there's runtime output after the [human-steering] line.
|
|
140
|
+
// Claude emits "assistant"; Copilot emits "assistant.message_delta"
|
|
141
|
+
// / "assistant.message", and either one proves the resume turn ran.
|
|
140
142
|
const steerIdx = text.lastIndexOf('[human-steering]');
|
|
141
143
|
if (steerIdx >= 0) {
|
|
142
|
-
const afterSteer = text.slice(steerIdx +
|
|
143
|
-
|
|
144
|
-
if (afterSteer.length >
|
|
144
|
+
const afterSteer = text.slice(steerIdx + '[human-steering]'.length);
|
|
145
|
+
const sawRuntimeOutput = /"type"\s*:\s*"(assistant(?:\.|")|tool\.|session\.task_complete"|result")/.test(afterSteer);
|
|
146
|
+
if (afterSteer.length > 20 && sawRuntimeOutput) {
|
|
145
147
|
clearInterval(ackInterval);
|
|
146
148
|
pending.textContent = '\u2713 Agent acknowledged';
|
|
147
149
|
pending.style.color = 'var(--green)';
|
package/engine/lifecycle.js
CHANGED
|
@@ -924,6 +924,15 @@ function isPrAttachmentRequired(type, item, meta = {}) {
|
|
|
924
924
|
|| item.requiresPullRequest === true
|
|
925
925
|
|| item.itemType === 'pr';
|
|
926
926
|
if (meta.branchStrategy === 'shared-branch' && item.itemType !== 'pr' && !explicit) return false;
|
|
927
|
+
|
|
928
|
+
// Fix/test work items dispatched against an existing PR don't produce a new
|
|
929
|
+
// PR — the agent updates meta.pr in place. Only require fresh PR attachment
|
|
930
|
+
// when there's NO existing PR to operate on (rare: standalone fix dispatched
|
|
931
|
+
// from CC without a PR target). The meta.pr short-circuit beats the
|
|
932
|
+
// explicit-flag fallthrough so a legacy requiresPr:true fix doesn't trigger
|
|
933
|
+
// the contract when there's already a PR attached.
|
|
934
|
+
if ((type === WORK_TYPE.FIX || type === WORK_TYPE.TEST) && meta?.pr) return false;
|
|
935
|
+
|
|
927
936
|
return explicit
|
|
928
937
|
|| type === WORK_TYPE.IMPLEMENT
|
|
929
938
|
|| type === WORK_TYPE.IMPLEMENT_LARGE
|
|
@@ -2558,4 +2567,5 @@ module.exports = {
|
|
|
2558
2567
|
diagnoseEmptyOutput,
|
|
2559
2568
|
processPendingRebases,
|
|
2560
2569
|
findDependentActivePrs,
|
|
2570
|
+
isPrAttachmentRequired,
|
|
2561
2571
|
};
|
package/engine/steering.js
CHANGED
|
@@ -162,6 +162,27 @@ function _processEvidenceTimes(rawOutput, observedAtMs) {
|
|
|
162
162
|
return times;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
function sessionIdFromEvent(obj) {
|
|
166
|
+
if (!obj || typeof obj !== 'object') return null;
|
|
167
|
+
const candidates = [
|
|
168
|
+
obj.session_id,
|
|
169
|
+
obj.sessionId,
|
|
170
|
+
obj.data?.session_id,
|
|
171
|
+
obj.data?.sessionId,
|
|
172
|
+
];
|
|
173
|
+
for (const value of candidates) {
|
|
174
|
+
if (typeof value === 'string' && value.trim()) return value.trim();
|
|
175
|
+
}
|
|
176
|
+
if (obj.raw && obj.raw !== obj) return sessionIdFromEvent(obj.raw);
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function sessionIdFromOutputLine(line) {
|
|
181
|
+
const trimmed = String(line || '').trim();
|
|
182
|
+
if (!trimmed.startsWith('{')) return null;
|
|
183
|
+
try { return sessionIdFromEvent(JSON.parse(trimmed)); } catch { return null; }
|
|
184
|
+
}
|
|
185
|
+
|
|
165
186
|
function ackProcessedSteeringMessages(agentId, pendingEntries, rawOutput, opts = {}) {
|
|
166
187
|
const entries = Array.isArray(pendingEntries) ? pendingEntries : [];
|
|
167
188
|
if (entries.length === 0) return [];
|
|
@@ -183,5 +204,7 @@ module.exports = {
|
|
|
183
204
|
writeSteeringMessage,
|
|
184
205
|
listUnreadSteeringMessages,
|
|
185
206
|
buildPendingSteeringPrompt,
|
|
207
|
+
sessionIdFromEvent,
|
|
208
|
+
sessionIdFromOutputLine,
|
|
186
209
|
ackProcessedSteeringMessages,
|
|
187
210
|
};
|
package/engine.js
CHANGED
|
@@ -283,6 +283,7 @@ function _classifyAgentFailure(runtime, code, stdout, stderr) {
|
|
|
283
283
|
|
|
284
284
|
function ackPendingSteeringFiles(agentId, procInfo, rawOutput, observedAtMs = Date.now()) {
|
|
285
285
|
if (!procInfo?._pendingSteeringFiles?.length || !rawOutput) return;
|
|
286
|
+
if (procInfo._steeringMessage || procInfo._steeringNoSession) return;
|
|
286
287
|
const acked = steering.ackProcessedSteeringMessages(agentId, procInfo._pendingSteeringFiles, rawOutput, { observedAtMs });
|
|
287
288
|
if (acked.length === 0) return;
|
|
288
289
|
|
|
@@ -292,6 +293,45 @@ function ackPendingSteeringFiles(agentId, procInfo, rawOutput, observedAtMs = Da
|
|
|
292
293
|
log('info', `Steering: ACKed ${acked.length} processed message(s) for ${agentId}`);
|
|
293
294
|
}
|
|
294
295
|
|
|
296
|
+
function captureSessionIdFromStdoutChunk(agentId, dispatchId, branchName, runtime, procInfo, chunk, state) {
|
|
297
|
+
if (!procInfo || procInfo.sessionId || !chunk) return;
|
|
298
|
+
const text = String(state.sessionLineBuffer || '') + String(chunk);
|
|
299
|
+
const lines = text.split('\n');
|
|
300
|
+
state.sessionLineBuffer = /[\r\n]$/.test(text) ? '' : (lines.pop() || '');
|
|
301
|
+
if (state.sessionLineBuffer.length > 65536) state.sessionLineBuffer = '';
|
|
302
|
+
|
|
303
|
+
for (const line of lines) {
|
|
304
|
+
const sessionId = steering.sessionIdFromOutputLine(line);
|
|
305
|
+
if (!sessionId) continue;
|
|
306
|
+
procInfo.sessionId = sessionId;
|
|
307
|
+
if (runtime && typeof runtime.saveSession === 'function') {
|
|
308
|
+
runtime.saveSession({
|
|
309
|
+
agentId,
|
|
310
|
+
dispatchId,
|
|
311
|
+
branch: branchName,
|
|
312
|
+
sessionId,
|
|
313
|
+
agentsDir: AGENTS_DIR,
|
|
314
|
+
logger: _runtimeLogger(),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function mergePendingSteeringEntries(...groups) {
|
|
322
|
+
const merged = [];
|
|
323
|
+
const seen = new Set();
|
|
324
|
+
for (const group of groups) {
|
|
325
|
+
const entries = Array.isArray(group) ? group : (group ? [group] : []);
|
|
326
|
+
for (const entry of entries) {
|
|
327
|
+
if (!entry?.path || seen.has(entry.path)) continue;
|
|
328
|
+
seen.add(entry.path);
|
|
329
|
+
merged.push(entry);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
return merged;
|
|
333
|
+
}
|
|
334
|
+
|
|
295
335
|
// Resolve dependency plan item IDs to their PR branches
|
|
296
336
|
function resolveDependencyBranches(depIds, sourcePlan, project, config) {
|
|
297
337
|
const results = []; // [{ branch, prId }]
|
|
@@ -1005,6 +1045,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1005
1045
|
const MAX_OUTPUT = 1024 * 1024; // 1MB
|
|
1006
1046
|
let stdout = '';
|
|
1007
1047
|
let stderr = '';
|
|
1048
|
+
const sessionCaptureState = { sessionLineBuffer: '' };
|
|
1008
1049
|
let _trustCheckDone = false;
|
|
1009
1050
|
const _spawnTime = Date.now();
|
|
1010
1051
|
|
|
@@ -1028,30 +1069,10 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1028
1069
|
_trustCheckDone = true; // past 30s window
|
|
1029
1070
|
}
|
|
1030
1071
|
|
|
1031
|
-
// Capture sessionId early for mid-session steering
|
|
1072
|
+
// Capture sessionId early for mid-session steering. Claude emits session_id;
|
|
1073
|
+
// Copilot emits sessionId, so use the runtime-neutral steering helper.
|
|
1032
1074
|
const procInfo = activeProcesses.get(id);
|
|
1033
|
-
|
|
1034
|
-
try {
|
|
1035
|
-
for (const line of chunk.split('\n')) {
|
|
1036
|
-
if (!line.trim() || !line.startsWith('{')) continue;
|
|
1037
|
-
const obj = JSON.parse(line);
|
|
1038
|
-
if (obj.session_id) {
|
|
1039
|
-
procInfo.sessionId = obj.session_id;
|
|
1040
|
-
if (runtime && typeof runtime.saveSession === 'function') {
|
|
1041
|
-
runtime.saveSession({
|
|
1042
|
-
agentId,
|
|
1043
|
-
dispatchId: id,
|
|
1044
|
-
branch: branchName,
|
|
1045
|
-
sessionId: obj.session_id,
|
|
1046
|
-
agentsDir: AGENTS_DIR,
|
|
1047
|
-
logger: _runtimeLogger(),
|
|
1048
|
-
});
|
|
1049
|
-
}
|
|
1050
|
-
break;
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
} catch { /* JSON parse — output may not be valid JSON */ }
|
|
1054
|
-
}
|
|
1075
|
+
captureSessionIdFromStdoutChunk(agentId, id, branchName, runtime, procInfo, chunk, sessionCaptureState);
|
|
1055
1076
|
|
|
1056
1077
|
ackPendingSteeringFiles(agentId, procInfo, chunk);
|
|
1057
1078
|
});
|
|
@@ -1112,8 +1133,12 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1112
1133
|
}
|
|
1113
1134
|
}
|
|
1114
1135
|
|
|
1115
|
-
// Write new prompt with steering
|
|
1116
|
-
|
|
1136
|
+
// Write new prompt with all unACKed steering messages. This keeps delivery
|
|
1137
|
+
// durable if the killed process had older pending messages that never
|
|
1138
|
+
// produced processing evidence before the resume.
|
|
1139
|
+
const pendingForResume = steering.buildPendingSteeringPrompt(agentId);
|
|
1140
|
+
const steerPromptBody = pendingForResume.prompt || steerMsg;
|
|
1141
|
+
const steerPrompt = `Message from your human teammate:\n\n${steerPromptBody}\n\nRespond to this, then continue working on your current task.`;
|
|
1117
1142
|
const steerPromptPath = path.join(ENGINE_DIR, 'tmp', `prompt-steer-${safeId}.md`);
|
|
1118
1143
|
try { safeWrite(steerPromptPath, steerPrompt); } catch (e) {
|
|
1119
1144
|
log('warn', `Steering: failed to write prompt for ${agentId}: ${e.message}`);
|
|
@@ -1180,19 +1205,26 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1180
1205
|
startedAt: procInfo.startedAt,
|
|
1181
1206
|
sessionId: steerSessionId,
|
|
1182
1207
|
lastRealOutputAt: Date.now(),
|
|
1183
|
-
_pendingSteeringFiles:
|
|
1208
|
+
_pendingSteeringFiles: mergePendingSteeringEntries(
|
|
1209
|
+
procInfo._pendingSteeringFiles,
|
|
1210
|
+
pendingForResume.entries,
|
|
1211
|
+
steerEntry,
|
|
1212
|
+
),
|
|
1184
1213
|
});
|
|
1185
1214
|
|
|
1186
1215
|
// Reset output buffers so post-completion parsing only sees the resumed session
|
|
1187
1216
|
stdout = '';
|
|
1188
1217
|
stderr = '';
|
|
1218
|
+
sessionCaptureState.sessionLineBuffer = '';
|
|
1189
1219
|
// Re-wire stdout/stderr handlers (same as original)
|
|
1190
1220
|
resumeProc.stdout.on('data', (data) => {
|
|
1191
1221
|
const chunk = data.toString();
|
|
1192
1222
|
realActivityMap.set(id, Date.now());
|
|
1193
1223
|
if (stdout.length < MAX_OUTPUT) stdout += chunk.slice(0, MAX_OUTPUT - stdout.length);
|
|
1194
1224
|
try { fs.appendFileSync(liveOutputPath, chunk); } catch { /* optional */ }
|
|
1195
|
-
|
|
1225
|
+
const resumeInfo = activeProcesses.get(id);
|
|
1226
|
+
captureSessionIdFromStdoutChunk(agentId, id, branchName, runtime, resumeInfo, chunk, sessionCaptureState);
|
|
1227
|
+
ackPendingSteeringFiles(agentId, resumeInfo, chunk);
|
|
1196
1228
|
});
|
|
1197
1229
|
resumeProc.stderr.on('data', (data) => {
|
|
1198
1230
|
const chunk = data.toString();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1653",
|
|
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"
|