@yemi33/minions 0.1.2124 → 0.1.2125
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/dashboard.js +55 -4
- package/engine/steering.js +191 -20
- package/engine.js +2 -2
- package/package.json +1 -1
package/dashboard.js
CHANGED
|
@@ -11477,7 +11477,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11477
11477
|
invalidateStatusCache();
|
|
11478
11478
|
return jsonReply(res, 200, { ok: true });
|
|
11479
11479
|
}},
|
|
11480
|
-
{ method: 'POST', path: '/api/agents/steer', desc: 'Inject steering message into a running agent', params: 'agent, message', handler: async (req, res) => {
|
|
11480
|
+
{ method: 'POST', path: '/api/agents/steer', desc: 'Inject steering message into a running agent', params: 'agent, message, supersede? (all|unacked|<steerId>), scope? (agent|current-dispatch)', handler: async (req, res) => {
|
|
11481
11481
|
const body = await readBody(req);
|
|
11482
11482
|
const { agent, message } = body;
|
|
11483
11483
|
if (!agent || !message) return jsonReply(res, 400, { error: 'agent and message required' });
|
|
@@ -11491,7 +11491,54 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11491
11491
|
return jsonReply(res, 409, { error: 'Agent session is finishing; retry when the next session starts' });
|
|
11492
11492
|
}
|
|
11493
11493
|
|
|
11494
|
-
|
|
11494
|
+
// W-mq066js7000fff1f-e (Gap E) — supersede prior messages before
|
|
11495
|
+
// writing the new one. Accept 'all', 'unacked', or a specific
|
|
11496
|
+
// steerId. Bad shapes are silently ignored (best-effort).
|
|
11497
|
+
const supersede = body.supersede ? String(body.supersede).trim() : '';
|
|
11498
|
+
let superseded = [];
|
|
11499
|
+
if (supersede) {
|
|
11500
|
+
const provisionalSteerId = `steer-pending-${Date.now()}`;
|
|
11501
|
+
superseded = steering.supersedeMessages(agentId, supersede, { reason: `superseded by new steer (${supersede})`, newSteerId: provisionalSteerId });
|
|
11502
|
+
}
|
|
11503
|
+
|
|
11504
|
+
// W-mq066js7000fff1f-e — dedupe: if no supersede arg AND an
|
|
11505
|
+
// identical body is already pending within the 5-min window, echo
|
|
11506
|
+
// back the existing steerId instead of writing a duplicate file.
|
|
11507
|
+
if (!supersede) {
|
|
11508
|
+
const dup = steering.findRecentDuplicate(agentId, text);
|
|
11509
|
+
if (dup) {
|
|
11510
|
+
const delivery = _steeringDeliveryState(agentId);
|
|
11511
|
+
return jsonReply(res, 200, {
|
|
11512
|
+
ok: true,
|
|
11513
|
+
deduplicated: true,
|
|
11514
|
+
steerId: dup.steerId,
|
|
11515
|
+
status: dup.status || steering.STATUS.QUEUED,
|
|
11516
|
+
file: dup.file,
|
|
11517
|
+
// Gap D observability URL — points at the SQL delivery-state row
|
|
11518
|
+
// for /api/steering/:id (back-compat with master's contract).
|
|
11519
|
+
deliveryUrl: dup.steerId ? `/api/steering/${dup.steerId}` : null,
|
|
11520
|
+
message: 'Identical steering message already pending — returning existing entry',
|
|
11521
|
+
...delivery,
|
|
11522
|
+
inboxCount: steering.listUnreadSteeringMessages(agentId).length,
|
|
11523
|
+
});
|
|
11524
|
+
}
|
|
11525
|
+
}
|
|
11526
|
+
|
|
11527
|
+
// W-mq066js7000fff1f-f (Gap F) — per-dispatch scoping. With
|
|
11528
|
+
// scope='current-dispatch' we stamp the active dispatch id so the
|
|
11529
|
+
// engine filters this entry out of any future unrelated dispatch's
|
|
11530
|
+
// resume prompt. With scope='agent' (default) the message is
|
|
11531
|
+
// agent-wide and picks up on the next dispatch regardless of id.
|
|
11532
|
+
const scope = body.scope ? String(body.scope).trim().toLowerCase() : 'agent';
|
|
11533
|
+
let targetDispatchId = null;
|
|
11534
|
+
let scopeApplied = scope;
|
|
11535
|
+
if (scope === 'current-dispatch') {
|
|
11536
|
+
const active = (getDispatchQueue().active || []).find(d => d.agent === agentId);
|
|
11537
|
+
if (active?.id) targetDispatchId = String(active.id);
|
|
11538
|
+
else scopeApplied = 'agent'; // no active dispatch → fall back
|
|
11539
|
+
}
|
|
11540
|
+
|
|
11541
|
+
const entry = steering.writeSteeringMessage(agentId, text, { targetDispatchId });
|
|
11495
11542
|
const delivery = _steeringDeliveryState(agentId);
|
|
11496
11543
|
|
|
11497
11544
|
// Also append to live-output.log so it shows in the chat view
|
|
@@ -11506,12 +11553,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11506
11553
|
const steerId = entry?.steerId || null;
|
|
11507
11554
|
return jsonReply(res, 200, {
|
|
11508
11555
|
ok: true,
|
|
11556
|
+
deduplicated: false,
|
|
11557
|
+
steerId,
|
|
11558
|
+
status: entry?.status || (steerId ? steering.STATUS.QUEUED : null),
|
|
11559
|
+
targetDispatchId: entry?.targetDispatchId || null,
|
|
11560
|
+
scope: scopeApplied,
|
|
11561
|
+
superseded: superseded.map(s => ({ steerId: s.steerId, previousStatus: s.previousStatus })),
|
|
11509
11562
|
message: delivery.pendingDelivery ? 'Steering message pending delivery' : 'Steering message queued',
|
|
11510
11563
|
...delivery,
|
|
11511
11564
|
file: entry?.file || null,
|
|
11512
11565
|
inboxCount: steering.listUnreadSteeringMessages(agentId).length,
|
|
11513
|
-
steerId,
|
|
11514
|
-
status: steerId ? 'queued' : null,
|
|
11515
11566
|
deliveryUrl: steerId ? `/api/steering/${steerId}` : null,
|
|
11516
11567
|
});
|
|
11517
11568
|
}},
|
package/engine/steering.js
CHANGED
|
@@ -19,6 +19,43 @@ function _generateSteerId() {
|
|
|
19
19
|
return `steer-${crypto.randomBytes(8).toString('hex').slice(0, 10)}`;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// W-mq066js7000fff1f-e (Gap E): identical-text dedupe window. POST
|
|
23
|
+
// /api/agents/steer collapses identical bodies within this window to
|
|
24
|
+
// the existing steerId instead of writing a duplicate inbox file.
|
|
25
|
+
const DEDUPE_WINDOW_MS = 5 * 60 * 1000;
|
|
26
|
+
|
|
27
|
+
// W-mq066js7000fff1f-e/f: status enum for the inbox frontmatter +
|
|
28
|
+
// supersede operations. Mirrors the steering_deliveries observability
|
|
29
|
+
// table (Gap D) so callers can speak the same vocabulary regardless
|
|
30
|
+
// of which storage backend is live.
|
|
31
|
+
const STATUS = Object.freeze({
|
|
32
|
+
QUEUED: 'queued',
|
|
33
|
+
LIVE_KILL: 'live_kill',
|
|
34
|
+
DEFERRED: 'deferred',
|
|
35
|
+
RE_SPAWNING: 're_spawning',
|
|
36
|
+
DELIVERED: 'delivered',
|
|
37
|
+
ACKNOWLEDGED: 'acknowledged',
|
|
38
|
+
STRANDED: 'stranded',
|
|
39
|
+
DROPPED: 'dropped',
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const UNACKED_STATUSES = new Set([
|
|
43
|
+
STATUS.QUEUED,
|
|
44
|
+
STATUS.LIVE_KILL,
|
|
45
|
+
STATUS.DEFERRED,
|
|
46
|
+
STATUS.RE_SPAWNING,
|
|
47
|
+
STATUS.STRANDED,
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
// Statuses that should still appear as "live" in the dedupe lookup.
|
|
51
|
+
const DEDUPE_CANDIDATE_STATUSES = new Set([
|
|
52
|
+
STATUS.QUEUED,
|
|
53
|
+
STATUS.LIVE_KILL,
|
|
54
|
+
STATUS.DEFERRED,
|
|
55
|
+
STATUS.RE_SPAWNING,
|
|
56
|
+
STATUS.DELIVERED,
|
|
57
|
+
]);
|
|
58
|
+
|
|
22
59
|
function agentInboxDir(agentId) {
|
|
23
60
|
return path.join(AGENTS_DIR, agentId, 'inbox');
|
|
24
61
|
}
|
|
@@ -73,15 +110,22 @@ function _readEntry(filePath, legacy = false) {
|
|
|
73
110
|
? fmCreatedAtMs
|
|
74
111
|
: _createdAtFromPath(filePath, stat);
|
|
75
112
|
const steerId = _frontmatterValue(raw, 'steerId') || null;
|
|
113
|
+
const status = _frontmatterValue(raw, 'status') || STATUS.QUEUED;
|
|
114
|
+
const targetDispatchId = _frontmatterValue(raw, 'targetDispatchId') || null;
|
|
115
|
+
const lastError = _frontmatterValue(raw, 'lastError') || null;
|
|
116
|
+
const source = _frontmatterValue(raw, 'source') || 'human';
|
|
76
117
|
return {
|
|
77
118
|
path: filePath,
|
|
78
119
|
file: path.basename(filePath),
|
|
79
120
|
createdAtMs,
|
|
80
121
|
createdAt: new Date(createdAtMs).toISOString(),
|
|
81
|
-
steerId,
|
|
82
122
|
raw,
|
|
83
123
|
message: _messageFromRaw(raw),
|
|
84
124
|
steerId,
|
|
125
|
+
status,
|
|
126
|
+
targetDispatchId,
|
|
127
|
+
lastError,
|
|
128
|
+
source,
|
|
85
129
|
legacy,
|
|
86
130
|
};
|
|
87
131
|
}
|
|
@@ -94,10 +138,6 @@ function _uniqueSteeringPath(inboxDir, createdAtMs) {
|
|
|
94
138
|
return filePath;
|
|
95
139
|
}
|
|
96
140
|
|
|
97
|
-
function _generateSteerId() {
|
|
98
|
-
return crypto.randomBytes(6).toString('hex');
|
|
99
|
-
}
|
|
100
|
-
|
|
101
141
|
// Contract block describing the ACK-file protocol. Injected into the prompt
|
|
102
142
|
// alongside any pending steering messages so the agent knows how to confirm
|
|
103
143
|
// it has read+addressed a labeled message. Mirrored verbatim into
|
|
@@ -111,9 +151,24 @@ function ackContractBlock() {
|
|
|
111
151
|
].join('\n');
|
|
112
152
|
}
|
|
113
153
|
|
|
154
|
+
function _renderFrontmatter(data) {
|
|
155
|
+
const createdAtMs = Number(data.createdAtMs) || Date.now();
|
|
156
|
+
const lines = [
|
|
157
|
+
'---',
|
|
158
|
+
`createdAt: ${new Date(createdAtMs).toISOString()}`,
|
|
159
|
+
`createdAtMs: ${createdAtMs}`,
|
|
160
|
+
`source: ${data.source || 'human'}`,
|
|
161
|
+
`steerId: ${data.steerId}`,
|
|
162
|
+
`status: ${data.status || STATUS.QUEUED}`,
|
|
163
|
+
];
|
|
164
|
+
if (data.targetDispatchId) lines.push(`targetDispatchId: ${data.targetDispatchId}`);
|
|
165
|
+
if (data.lastError) lines.push(`lastError: ${String(data.lastError).replace(/[\r\n]+/g, ' ').trim()}`);
|
|
166
|
+
lines.push('---', '', String(data.message || '').trim(), '');
|
|
167
|
+
return lines.join('\n');
|
|
168
|
+
}
|
|
169
|
+
|
|
114
170
|
function writeSteeringMessage(agentId, message, opts = {}) {
|
|
115
171
|
const createdAtMs = Number(opts.createdAtMs) || Date.now();
|
|
116
|
-
const createdAt = new Date(createdAtMs).toISOString();
|
|
117
172
|
const inboxDir = agentInboxDir(agentId);
|
|
118
173
|
fs.mkdirSync(inboxDir, { recursive: true });
|
|
119
174
|
const filePath = _uniqueSteeringPath(inboxDir, createdAtMs);
|
|
@@ -134,17 +189,15 @@ function writeSteeringMessage(agentId, message, opts = {}) {
|
|
|
134
189
|
steerId,
|
|
135
190
|
}));
|
|
136
191
|
}
|
|
137
|
-
const body =
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
'',
|
|
147
|
-
].join('\n');
|
|
192
|
+
const body = _renderFrontmatter({
|
|
193
|
+
createdAtMs,
|
|
194
|
+
source,
|
|
195
|
+
steerId,
|
|
196
|
+
status: opts.status || STATUS.QUEUED,
|
|
197
|
+
targetDispatchId: opts.targetDispatchId || null,
|
|
198
|
+
lastError: opts.lastError || null,
|
|
199
|
+
message: bodyText,
|
|
200
|
+
});
|
|
148
201
|
shared.safeWrite(filePath, body);
|
|
149
202
|
|
|
150
203
|
// W-mq066js7000fff1f-a (Gap D): insert a 'queued' row into the
|
|
@@ -169,7 +222,22 @@ function writeSteeringMessage(agentId, message, opts = {}) {
|
|
|
169
222
|
return _readEntry(filePath);
|
|
170
223
|
}
|
|
171
224
|
|
|
172
|
-
function
|
|
225
|
+
function _updateEntryStatus(entry, newStatus, opts = {}) {
|
|
226
|
+
if (!entry?.path) return null;
|
|
227
|
+
const body = _renderFrontmatter({
|
|
228
|
+
createdAtMs: entry.createdAtMs,
|
|
229
|
+
source: entry.source || 'human',
|
|
230
|
+
steerId: entry.steerId,
|
|
231
|
+
status: newStatus,
|
|
232
|
+
targetDispatchId: entry.targetDispatchId,
|
|
233
|
+
lastError: opts.lastError !== undefined ? opts.lastError : entry.lastError,
|
|
234
|
+
message: entry.message,
|
|
235
|
+
});
|
|
236
|
+
shared.safeWrite(entry.path, body);
|
|
237
|
+
return _readEntry(entry.path);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function listAllSteeringEntries(agentId, opts = {}) {
|
|
173
241
|
const includeLegacy = opts.includeLegacy !== false;
|
|
174
242
|
const entries = [];
|
|
175
243
|
const inboxDir = agentInboxDir(agentId);
|
|
@@ -189,8 +257,40 @@ function listUnreadSteeringMessages(agentId, opts = {}) {
|
|
|
189
257
|
return entries;
|
|
190
258
|
}
|
|
191
259
|
|
|
192
|
-
function
|
|
193
|
-
|
|
260
|
+
function listUnreadSteeringMessages(agentId, opts = {}) {
|
|
261
|
+
// Back-compat shape: "unread" = anything still pending agent attention
|
|
262
|
+
// (queued/live_kill/deferred/re_spawning/delivered/stranded). Explicit
|
|
263
|
+
// 'dropped' or 'acknowledged' rows are filtered so supersede/ack don't
|
|
264
|
+
// resurrect carry-over messages on the next dispatch.
|
|
265
|
+
return listAllSteeringEntries(agentId, opts).filter(entry => {
|
|
266
|
+
const status = entry.status || STATUS.QUEUED;
|
|
267
|
+
return status !== STATUS.DROPPED && status !== STATUS.ACKNOWLEDGED;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// W-mq066js7000fff1f-f (Gap F): per-dispatch scoping. Callers that know
|
|
272
|
+
// which dispatch they're about to spawn can pass {currentDispatchId} so
|
|
273
|
+
// messages tagged for a different (older) dispatch are filtered out.
|
|
274
|
+
// Pre-spawn messages (targetDispatchId null) always pass through.
|
|
275
|
+
// Without currentDispatchId, no filtering happens — back-compat for
|
|
276
|
+
// callers that haven't been updated to pass the dispatch id yet.
|
|
277
|
+
function buildPendingSteeringPrompt(agentId, opts = {}) {
|
|
278
|
+
const includePrior = opts.includePrior === true;
|
|
279
|
+
const currentDispatchId = opts.currentDispatchId || null;
|
|
280
|
+
const allEntries = listUnreadSteeringMessages(agentId).filter(entry => entry.message.trim());
|
|
281
|
+
const entries = includePrior
|
|
282
|
+
? allEntries
|
|
283
|
+
: allEntries.filter(entry => {
|
|
284
|
+
// Pre-spawn / agent-scoped messages (null targetDispatchId) always
|
|
285
|
+
// pick up on the next dispatch — the agent never had a chance to
|
|
286
|
+
// hear them yet.
|
|
287
|
+
if (!entry.targetDispatchId) return true;
|
|
288
|
+
// No dispatch context → don't try to filter (back-compat).
|
|
289
|
+
if (!currentDispatchId) return true;
|
|
290
|
+
// Per-dispatch messages only belong to their tagged dispatch.
|
|
291
|
+
return entry.targetDispatchId === currentDispatchId;
|
|
292
|
+
});
|
|
293
|
+
|
|
194
294
|
if (entries.length === 0) return { entries, prompt: '' };
|
|
195
295
|
|
|
196
296
|
const sections = [
|
|
@@ -206,6 +306,69 @@ function buildPendingSteeringPrompt(agentId) {
|
|
|
206
306
|
return { entries, prompt: sections.join('\n') };
|
|
207
307
|
}
|
|
208
308
|
|
|
309
|
+
// W-mq066js7000fff1f-e (Gap E): dedupe lookup for POST /api/agents/steer.
|
|
310
|
+
// Returns the existing entry whose normalized body matches `message` and
|
|
311
|
+
// was created within `opts.windowMs` (default 5 min); null otherwise.
|
|
312
|
+
// Only entries in "live" delivery states qualify — dropped/acknowledged
|
|
313
|
+
// rows are ignored so a previously-superseded duplicate body is allowed
|
|
314
|
+
// to be re-sent.
|
|
315
|
+
function findRecentDuplicate(agentId, message, opts = {}) {
|
|
316
|
+
const trimmed = String(message || '').trim();
|
|
317
|
+
if (!trimmed) return null;
|
|
318
|
+
const windowMs = Number(opts.windowMs) > 0 ? Number(opts.windowMs) : DEDUPE_WINDOW_MS;
|
|
319
|
+
const now = Number(opts.now) > 0 ? Number(opts.now) : Date.now();
|
|
320
|
+
const entries = listAllSteeringEntries(agentId);
|
|
321
|
+
// Newest first — UI usually steers in a tight loop, the most recent
|
|
322
|
+
// identical message is the one we want to deduplicate against.
|
|
323
|
+
entries.sort((a, b) => b.createdAtMs - a.createdAtMs);
|
|
324
|
+
for (const entry of entries) {
|
|
325
|
+
const status = entry.status || STATUS.QUEUED;
|
|
326
|
+
if (!DEDUPE_CANDIDATE_STATUSES.has(status)) continue;
|
|
327
|
+
if (now - entry.createdAtMs > windowMs) continue;
|
|
328
|
+
if (entry.message.trim() !== trimmed) continue;
|
|
329
|
+
return entry;
|
|
330
|
+
}
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// W-mq066js7000fff1f-e (Gap E): supersede prior steering messages.
|
|
335
|
+
// mode:
|
|
336
|
+
// 'all' — drop every entry whose status is neither dropped nor
|
|
337
|
+
// acknowledged (queued/live_kill/deferred/re_spawning/
|
|
338
|
+
// delivered/stranded).
|
|
339
|
+
// 'unacked' — drop queued/live_kill/deferred/re_spawning/stranded but
|
|
340
|
+
// leave 'delivered' alone (agent already saw it).
|
|
341
|
+
// <steerId> — drop the single entry with that steerId.
|
|
342
|
+
// Returns the list of dropped {steerId, file, path, previousStatus}.
|
|
343
|
+
function supersedeMessages(agentId, mode, opts = {}) {
|
|
344
|
+
if (!mode) return [];
|
|
345
|
+
const newSteerId = opts.newSteerId || null;
|
|
346
|
+
const reason = opts.reason || (newSteerId ? `superseded by ${newSteerId}` : 'superseded');
|
|
347
|
+
const entries = listAllSteeringEntries(agentId);
|
|
348
|
+
const dropped = [];
|
|
349
|
+
for (const entry of entries) {
|
|
350
|
+
const status = entry.status || STATUS.QUEUED;
|
|
351
|
+
let target = false;
|
|
352
|
+
if (mode === 'all') {
|
|
353
|
+
if (status !== STATUS.ACKNOWLEDGED && status !== STATUS.DROPPED) target = true;
|
|
354
|
+
} else if (mode === 'unacked') {
|
|
355
|
+
if (UNACKED_STATUSES.has(status)) target = true;
|
|
356
|
+
} else {
|
|
357
|
+
// Specific steerId
|
|
358
|
+
if (entry.steerId && entry.steerId === mode) target = true;
|
|
359
|
+
}
|
|
360
|
+
if (!target) continue;
|
|
361
|
+
_updateEntryStatus(entry, STATUS.DROPPED, { lastError: reason });
|
|
362
|
+
dropped.push({
|
|
363
|
+
steerId: entry.steerId,
|
|
364
|
+
file: entry.file,
|
|
365
|
+
path: entry.path,
|
|
366
|
+
previousStatus: status,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return dropped;
|
|
370
|
+
}
|
|
371
|
+
|
|
209
372
|
function _eventTimestampMs(obj, observedAtMs) {
|
|
210
373
|
const value = obj?.timestamp || obj?.createdAt || obj?.created_at || obj?.time || obj?.data?.timestamp;
|
|
211
374
|
const parsed = value ? Date.parse(value) : NaN;
|
|
@@ -342,9 +505,17 @@ module.exports = {
|
|
|
342
505
|
ackContractBlock,
|
|
343
506
|
writeSteeringMessage,
|
|
344
507
|
listUnreadSteeringMessages,
|
|
508
|
+
listAllSteeringEntries,
|
|
345
509
|
buildPendingSteeringPrompt,
|
|
510
|
+
findRecentDuplicate,
|
|
511
|
+
supersedeMessages,
|
|
346
512
|
sessionIdFromEvent,
|
|
347
513
|
sessionIdFromOutputLine,
|
|
348
514
|
ackProcessedSteeringMessages,
|
|
349
515
|
ackSteeringFromAckDir,
|
|
516
|
+
STATUS,
|
|
517
|
+
DEDUPE_WINDOW_MS,
|
|
518
|
+
// Exposed for unit tests only.
|
|
519
|
+
_updateEntryStatus,
|
|
520
|
+
_generateSteerId,
|
|
350
521
|
};
|
package/engine.js
CHANGED
|
@@ -1326,7 +1326,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
1326
1326
|
// work-item prompts after setup because reused worktrees can live at arbitrary paths.
|
|
1327
1327
|
const systemPrompt = buildSystemPrompt(agentId, config, project);
|
|
1328
1328
|
const agentContext = buildAgentContext(agentId, config, project);
|
|
1329
|
-
const pendingSteering = steering.buildPendingSteeringPrompt(agentId);
|
|
1329
|
+
const pendingSteering = steering.buildPendingSteeringPrompt(agentId, { currentDispatchId: id });
|
|
1330
1330
|
const completionReportPath = shared.dispatchCompletionReportPath(id);
|
|
1331
1331
|
if (completionReportPath) {
|
|
1332
1332
|
try {
|
|
@@ -2793,7 +2793,7 @@ async function spawnAgent(dispatchItem, config) {
|
|
|
2793
2793
|
// Write new prompt with all unACKed steering messages. This keeps delivery
|
|
2794
2794
|
// durable if the killed process had older pending messages that never
|
|
2795
2795
|
// produced processing evidence before the resume.
|
|
2796
|
-
const pendingForResume = steering.buildPendingSteeringPrompt(agentId);
|
|
2796
|
+
const pendingForResume = steering.buildPendingSteeringPrompt(agentId, { currentDispatchId: id });
|
|
2797
2797
|
const steerPromptBody = pendingForResume.prompt || steerMsg;
|
|
2798
2798
|
const steerPrompt = `Message from your human teammate:\n\n${steerPromptBody}\n\nRespond to this, then continue working on your current task.`;
|
|
2799
2799
|
const steerPromptPath = path.join(dispatchTmpDir, `prompt-steer-${safeId}.md`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2125",
|
|
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"
|