@yemi33/minions 0.1.2118 → 0.1.2120
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/utils.js +2 -2
- package/dashboard.js +63 -2
- package/docs/deprecated.json +11 -0
- package/docs/team-memory.md +24 -0
- package/engine/cli.js +64 -0
- package/engine/consolidation.js +339 -35
- package/engine/db/migrations/012-steering-deliveries.js +43 -0
- package/engine/issues.js +1 -1
- package/engine/shared.js +20 -5
- package/engine/steering-store.js +184 -0
- package/engine/steering.js +143 -3
- package/engine/timeout.js +60 -0
- package/engine/untrusted-fence.js +15 -0
- package/engine.js +51 -0
- package/package.json +1 -1
- package/playbooks/shared-rules.md +6 -0
package/dashboard/js/utils.js
CHANGED
|
@@ -494,7 +494,7 @@ function openBugReport() {
|
|
|
494
494
|
container.style.cssText = 'display:flex;flex-direction:column;gap:12px';
|
|
495
495
|
var intro = document.createElement('p');
|
|
496
496
|
intro.style.cssText = 'color:var(--muted);font-size:var(--text-md);margin:0';
|
|
497
|
-
intro.textContent = 'File a bug on the Minions repo (
|
|
497
|
+
intro.textContent = 'File a bug on the public Minions repo (opg-microsoft/minions).';
|
|
498
498
|
|
|
499
499
|
var titleLabel = document.createElement('label');
|
|
500
500
|
titleLabel.style.cssText = 'color:var(--text);font-size:var(--text-md)';
|
|
@@ -577,7 +577,7 @@ async function submitBugReport() {
|
|
|
577
577
|
} else {
|
|
578
578
|
var msg = document.createElement('span');
|
|
579
579
|
msg.style.cssText = 'color:var(--muted);font-size:var(--text-md)';
|
|
580
|
-
msg.textContent = 'Issue created on
|
|
580
|
+
msg.textContent = 'Issue created on opg-microsoft/minions';
|
|
581
581
|
container.appendChild(msg);
|
|
582
582
|
}
|
|
583
583
|
var actions = document.createElement('div');
|
package/dashboard.js
CHANGED
|
@@ -39,6 +39,7 @@ const dispatchMod = require('./engine/dispatch');
|
|
|
39
39
|
const dispatchEvents = require('./engine/dispatch-events');
|
|
40
40
|
const { wrapUntrusted, buildSource } = require('./engine/untrusted-fence');
|
|
41
41
|
const steering = require('./engine/steering');
|
|
42
|
+
const steeringStore = require('./engine/steering-store');
|
|
42
43
|
const projectDiscovery = require('./engine/project-discovery');
|
|
43
44
|
const features = require('./engine/features');
|
|
44
45
|
const ccWorkerPool = require('./engine/cc-worker-pool');
|
|
@@ -7795,7 +7796,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
7795
7796
|
title: body.title,
|
|
7796
7797
|
description: body.description || '',
|
|
7797
7798
|
labels: body.labels,
|
|
7798
|
-
repo: '
|
|
7799
|
+
repo: 'opg-microsoft/minions',
|
|
7799
7800
|
tmpDir: path.join(ENGINE_DIR, 'tmp'),
|
|
7800
7801
|
});
|
|
7801
7802
|
return jsonReply(res, 200, result);
|
|
@@ -9632,6 +9633,32 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
9632
9633
|
} catch (e) { return jsonReply(res, e.statusCode || 500, { error: e.message }); }
|
|
9633
9634
|
}
|
|
9634
9635
|
|
|
9636
|
+
// W-mq03l6zh0006f0a1-d — read-only diagnostics surface for the per-org ADO
|
|
9637
|
+
// throttle tracker. Returns { orgs: { [orgBase]: { throttled, retryAfter,
|
|
9638
|
+
// consecutiveHits } } }. Prefers the per-org getter ado.getAdoThrottleStateAll
|
|
9639
|
+
// when present (introduced by W-mq03l6zh0006f0a1-b). Falls back to the
|
|
9640
|
+
// process-global ado.getAdoThrottleState() under the synthetic key `global`
|
|
9641
|
+
// when the per-org getter is not present, so the endpoint stays live across
|
|
9642
|
+
// the staged rollout of the per-org isolation work.
|
|
9643
|
+
async function handleDiagnosticsAdoThrottle(req, res) {
|
|
9644
|
+
try {
|
|
9645
|
+
let orgs = {};
|
|
9646
|
+
if (typeof ado.getAdoThrottleStateAll === 'function') {
|
|
9647
|
+
const all = ado.getAdoThrottleStateAll() || {};
|
|
9648
|
+
// Defensive copy — handler must never expose internal mutable state.
|
|
9649
|
+
for (const [k, v] of Object.entries(all)) {
|
|
9650
|
+
if (v && typeof v === 'object') {
|
|
9651
|
+
orgs[k] = { throttled: !!v.throttled, retryAfter: Number(v.retryAfter) || 0, consecutiveHits: Number(v.consecutiveHits) || 0 };
|
|
9652
|
+
}
|
|
9653
|
+
}
|
|
9654
|
+
} else if (typeof ado.getAdoThrottleState === 'function') {
|
|
9655
|
+
const v = ado.getAdoThrottleState() || {};
|
|
9656
|
+
orgs.global = { throttled: !!v.throttled, retryAfter: Number(v.retryAfter) || 0, consecutiveHits: Number(v.consecutiveHits) || 0 };
|
|
9657
|
+
}
|
|
9658
|
+
return jsonReply(res, 200, { orgs });
|
|
9659
|
+
} catch (e) { return jsonReply(res, e.statusCode || 500, { error: e.message }); }
|
|
9660
|
+
}
|
|
9661
|
+
|
|
9635
9662
|
// Slim UX surface for the experimental redesigned dashboard.
|
|
9636
9663
|
// The markup/CSS/JS live as fragments under dashboard/slim/ (layout.html +
|
|
9637
9664
|
// styles.css + body.html + js/*.js) and are assembled by buildSlimHtml() —
|
|
@@ -11468,14 +11495,46 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11468
11495
|
const liveLogPath = path.join(agentDir, 'live-output.log');
|
|
11469
11496
|
try { fs.appendFileSync(liveLogPath, '\n[human-steering] ' + text + '\n'); } catch { /* optional */ }
|
|
11470
11497
|
|
|
11498
|
+
// W-mq066js7000fff1f-a (Gap D): surface the observable
|
|
11499
|
+
// delivery-state row to the UI. steerId / status / deliveryUrl
|
|
11500
|
+
// let the dashboard poll /api/steering/:id without re-listing.
|
|
11501
|
+
// Existing fields (ok, message, file, inboxCount, ...delivery)
|
|
11502
|
+
// are preserved for back-compat with the current chat panel.
|
|
11503
|
+
const steerId = entry?.steerId || null;
|
|
11471
11504
|
return jsonReply(res, 200, {
|
|
11472
11505
|
ok: true,
|
|
11473
11506
|
message: delivery.pendingDelivery ? 'Steering message pending delivery' : 'Steering message queued',
|
|
11474
11507
|
...delivery,
|
|
11475
11508
|
file: entry?.file || null,
|
|
11476
11509
|
inboxCount: steering.listUnreadSteeringMessages(agentId).length,
|
|
11510
|
+
steerId,
|
|
11511
|
+
status: steerId ? 'queued' : null,
|
|
11512
|
+
deliveryUrl: steerId ? `/api/steering/${steerId}` : null,
|
|
11477
11513
|
});
|
|
11478
11514
|
}},
|
|
11515
|
+
{ method: 'GET', path: /^\/api\/agents\/([\w-]+)\/steering$/, template: '/api/agents/:agentId/steering', desc: 'List recent steering delivery-state rows for an agent (latest 50 by default)', params: 'limit? (default 50, max 200)', handler: (req, res, match) => {
|
|
11516
|
+
const agentId = match && match[1];
|
|
11517
|
+
if (!agentId) return jsonReply(res, 400, { error: 'agentId required' }, req);
|
|
11518
|
+
let limit = 50;
|
|
11519
|
+
try {
|
|
11520
|
+
const raw = new URL(req.url, 'http://localhost').searchParams.get('limit');
|
|
11521
|
+
if (raw != null) {
|
|
11522
|
+
const n = parseInt(raw, 10);
|
|
11523
|
+
if (Number.isFinite(n) && n > 0) limit = Math.min(n, 200);
|
|
11524
|
+
}
|
|
11525
|
+
} catch { /* default */ }
|
|
11526
|
+
let rows = [];
|
|
11527
|
+
try { rows = steeringStore.listForAgent(agentId, { limit }); } catch { rows = []; }
|
|
11528
|
+
return jsonReply(res, 200, { agentId, deliveries: rows, count: rows.length }, req);
|
|
11529
|
+
} },
|
|
11530
|
+
{ method: 'GET', path: /^\/api\/steering\/([\w-]+)$/, template: '/api/steering/:id', desc: 'Get a single steering delivery-state row by steerId', handler: (req, res, match) => {
|
|
11531
|
+
const steerId = match && match[1];
|
|
11532
|
+
if (!steerId) return jsonReply(res, 400, { error: 'steerId required' }, req);
|
|
11533
|
+
let row = null;
|
|
11534
|
+
try { row = steeringStore.getById(steerId); } catch { row = null; }
|
|
11535
|
+
if (!row) return jsonReply(res, 404, { error: 'steering delivery not found' }, req);
|
|
11536
|
+
return jsonReply(res, 200, row, req);
|
|
11537
|
+
} },
|
|
11479
11538
|
{ method: 'POST', path: '/api/agents/cancel', desc: 'Cancel an active agent by ID or task substring', params: 'agent? or agentId?, task?', handler: handleAgentsCancel },
|
|
11480
11539
|
{ method: 'POST', path: /^\/api\/agent\/([\w-]+)\/kill$/, template: '/api/agent/:id/kill', desc: 'Kill a running agent: stop process, clear dispatch, reset work items to pending', handler: handleAgentKill },
|
|
11481
11540
|
{ method: 'GET', path: /^\/api\/agent\/([\w-]+)\/live-stream(?:\?.*)?$/, template: '/api/agent/:id/live-stream', desc: 'SSE real-time live output streaming', handler: handleAgentLiveStream },
|
|
@@ -11548,7 +11607,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11548
11607
|
{ method: 'POST', path: '/api/projects/remove', desc: 'Unlink a project: cancels WIs, drains dispatch, kills agents, cleans worktrees, archives data dir', params: 'name or path, keepData?, purge?', handler: handleProjectsRemove },
|
|
11549
11608
|
|
|
11550
11609
|
// Bug Filing
|
|
11551
|
-
{ method: 'POST', path: '/api/issues/create', desc: 'File a bug on the Minions repo (
|
|
11610
|
+
{ method: 'POST', path: '/api/issues/create', desc: 'File a bug on the public Minions repo (opg-microsoft/minions)', params: 'title, description?, labels?', handler: handleFileBug },
|
|
11552
11611
|
|
|
11553
11612
|
// Command Center
|
|
11554
11613
|
{ method: 'POST', path: '/api/command-center/new-session', desc: 'Clear active CC session', handler: handleCommandCenterNewSession },
|
|
@@ -11837,6 +11896,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11837
11896
|
|
|
11838
11897
|
// Diagnostics — refresh ring buffer persistence (W-mphejzx100081972).
|
|
11839
11898
|
{ method: 'POST', path: '/api/diagnostics/refresh', desc: 'Append a dashboard refresh-diagnostic ring buffer batch to engine/dashboard-diagnostics.log (rotated at 1 MB)', params: 'entries[]', handler: handleDiagnosticsRefresh },
|
|
11899
|
+
// Diagnostics — per-org ADO throttle state (W-mq03l6zh0006f0a1-d).
|
|
11900
|
+
{ method: 'GET', path: '/api/diagnostics/ado-throttle', desc: 'Snapshot of per-org ADO throttle tracker state — { orgs: { [orgBase]: { throttled, retryAfter, consecutiveHits } } }. Falls back to a single `global` key when running against pre-per-org engines.', handler: handleDiagnosticsAdoThrottle },
|
|
11840
11901
|
];
|
|
11841
11902
|
|
|
11842
11903
|
// ── Route Dispatcher ────────────────────────────────────────────────────────
|
package/docs/deprecated.json
CHANGED
|
@@ -98,5 +98,16 @@
|
|
|
98
98
|
"removalGate": "Telemetry: pruneDefaultClaudeConfig must return false (no mutation) for every call across all known engines for >=30 consecutive days (add an `_engine.pruneDefaultClaudeConfigStrips` counter if needed to observe this), AND the parent `config-claude-binary-override` entry must have already cleared its own gate. The dependency is strict: removing the prune while users still rely on the override branch would surface the `deprecated-config-claude` warning on every stale generated default. Once both conditions hold, removal is the function definition (engine/shared.js:3126), the export at :5673, all 5 call sites (dashboard.js:202, :9116, :9331, :9450; minions.js:385), and the tests at unit.test.js:2260-2303 + runtime-fleet-helpers.test.js:546.",
|
|
99
99
|
"targetRemovalDate": null,
|
|
100
100
|
"notes": "Do NOT set targetRemovalDate — gating is signal-based AND ordered. This entry MUST NOT be removed before `config-claude-binary-override` clears its gate, otherwise installs with stale defaults will flood the deprecation channel until their next config save. The 5 call sites form a complete coverage net: load (dashboard.js:202 + minions.js:385) + save (dashboard.js:9116/9331/9450), so any code path that touches config.json runs the sanitizer."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "ado-throttle-arg-less-shim",
|
|
104
|
+
"description": "Arg-less form of isAdoThrottled() in engine/ado.js. Introduced by W-mq03l6zh0006f0a1-b as a back-compat shim during the per-org ADO throttle isolation rollout: pre-rollout, isAdoThrottled() collapsed the single process-global tracker to one boolean; post-rollout, the canonical form is isAdoThrottled(orgBase) against the per-org Map. The arg-less call site is preserved transiently so engine code (and any in-process callers) that haven't yet been threaded through with a per-org `orgBase` keep returning the safe global-OR (true if ANY org is currently throttled) — preventing a regression where new poll work bypasses a still-warm throttle backoff on an unrelated noisy org.",
|
|
105
|
+
"deprecated": "2026-06-04",
|
|
106
|
+
"code": [
|
|
107
|
+
{ "file": "engine/ado.js", "note": "isAdoThrottled() arg-less branch and the global-OR fold over the per-org Map. Single call site to migrate: shared.getAdoOrgBase(project) is already in scope at every consumer." }
|
|
108
|
+
],
|
|
109
|
+
"removalGate": "Two conditions must hold simultaneously: (a) grep `engine/ado.js` for `isAdoThrottled\\s*\\(\\s*\\)` and confirm zero arg-less call sites remain across the engine — every caller passes a concrete `orgBase` resolved via `shared.getAdoOrgBase(project)`; (b) `GET /api/diagnostics/ado-throttle` on a live engine has been observed for >=2 consecutive weeks reporting per-org keys (proves the per-org Map is populated under load and the global-OR isn't masking a regression). Once both hold, removal deletes the arg-less branch in isAdoThrottled and the global-OR fold; callers that still pass no argument become an immediate, surfaced bug rather than a silent over-throttle.",
|
|
110
|
+
"targetRemovalDate": "2026-08-03",
|
|
111
|
+
"notes": "Introduced by W-mq03l6zh0006f0a1 (Per-org ADO throttle isolation). 60-day window (2 release cycles + buffer) gives the in-flight per-org migration time to land + observe per-org keys on the diagnostics endpoint. Observable live at GET /api/diagnostics/ado-throttle — the endpoint reports a single `global` key while the arg-less shim is still load-bearing, and per-org `<orgBase>` keys once isolation is complete; that key shape is the human-readable signal for whether this shim can retire."
|
|
101
112
|
}
|
|
102
113
|
]
|
package/docs/team-memory.md
CHANGED
|
@@ -98,6 +98,30 @@ If an agent thinks a `knowledge/` file is wrong, the correct response is to **no
|
|
|
98
98
|
|
|
99
99
|
The same constraint applies to `knowledge/agents/<agentId>.md` — those are curated by the sweep and should not be hand-edited.
|
|
100
100
|
|
|
101
|
+
## Session State vs. Persistent Memory
|
|
102
|
+
|
|
103
|
+
The PRD that introduced sliding-window memory (W-mq07b8do000nc86a) referenced two distinct write paths — `update_session_state()` and `update_memory()` — borrowed from agent frameworks that model agents as long-lived in-process objects. Minions has neither method because it doesn't model agents that way; understanding the mapping prevents fruitless searches for non-existent APIs.
|
|
104
|
+
|
|
105
|
+
**Session state** = the dispatch's worktree + child process. Each Minions agent runs as a fresh OS process spawned by `engine.js → engine/spawn-agent.js` inside a per-work-item git worktree (`work/<wi-id>` by default; see `shared.deriveWorkItemBranchName`). When the dispatch ends, the engine deletes the worktree and the child exits. There is no persistent "session" object to update — ephemeral state lives in process memory and disk paths under the worktree, both of which are reclaimed automatically. No code is needed to "clear" session state; it never persists in the first place.
|
|
106
|
+
|
|
107
|
+
**Persistent memory** = `knowledge/agents/<agentId>.md`, the per-agent file appended to by `engine/consolidation.js` during the inbox sweep. This is the analog of `update_memory()` in PRD terms. It is written only by the consolidation sweep (the [sweep-write-only constraint](#sweep-write-only-constraint) applies), is injected into every subsequent dispatch's prompt for that same agent ID via `engine/playbook.js`, and is bounded by two complementary cuts plus an optional summary pass:
|
|
108
|
+
|
|
109
|
+
| Tunable (under `engine.*` in `config.json`) | Default | Behavior |
|
|
110
|
+
|---------------------------------------------------------|---------|-------------------------------------------------------------------------------------------------------------------|
|
|
111
|
+
| `agentMemoryMaxEntries` | `300` | Sliding-window entry-count cap. Older non-summary sections evicted oldest-first when exceeded. |
|
|
112
|
+
| (built-in) `AGENT_MEMORY_BUDGET_BYTES` | `25000` | Hard byte ceiling for prompt-injection safety. Always wins when both caps bind. Sticky summary sections obey it. |
|
|
113
|
+
| `agentMemorySummaryEnabled` | `false` | Master switch for the LLM-driven compression pass. Off by default to avoid surprise Haiku spend. |
|
|
114
|
+
| `agentMemorySummaryThreshold` | `30` | When the summary pass fires, fold this many oldest entries into one summary section. |
|
|
115
|
+
| `agentMemorySummaryDays` | `30` | Age trigger: if the oldest entry is older than this, fold even when under the entry cap. |
|
|
116
|
+
|
|
117
|
+
The summary pass runs fire-and-forget after every successful `appendToAgentMemory` write inside `classifyToKnowledgeBase`. It re-reads outside the lock, calls Haiku, then re-acquires the lock and verifies the same oldest sections are still in place (stale-candidate guard) before swapping. Any failure — disabled, no trigger, LLM unavailable, race detected — is a silent no-op; the consolidation pipeline is never blocked on the LLM.
|
|
118
|
+
|
|
119
|
+
The compressed summary is wrapped in an `<UNTRUSTED-INPUT source="agent-memory-summary:agent=...">` fence on disk. The source material was the inbox bodies of evicted entries, which are themselves untrusted; without the fence, any imperative laundered through summarization could later be executed by an agent reading its own memory.
|
|
120
|
+
|
|
121
|
+
Summary sections are **sticky** under the entry-count cap — they represent compressed knowledge that should outlive ordinary inbox entries. They are detected by their title prefix `Earlier learnings summary` and only the byte budget can evict them.
|
|
122
|
+
|
|
123
|
+
**Default-off rationale.** `agentMemorySummaryEnabled` defaults to `false` (intentional deviation from PRD wording that implies "always on"). Enabling it commits operators to per-agent Haiku spend on every consolidation cycle; the entry-count cap on its own already prevents unbounded growth. Operators who have weighed the cost set `engine.agentMemorySummaryEnabled: true` in `config.json` to opt in.
|
|
124
|
+
|
|
101
125
|
## Quick reference for agents
|
|
102
126
|
|
|
103
127
|
```
|
package/engine/cli.js
CHANGED
|
@@ -159,6 +159,21 @@ function handleCommand(cmd, args) {
|
|
|
159
159
|
if (!cmd) {
|
|
160
160
|
return commands.start();
|
|
161
161
|
} else if (commands[cmd]) {
|
|
162
|
+
// W-mq07mjzi000s1cc9: Centralized help-flag interception.
|
|
163
|
+
//
|
|
164
|
+
// `minions work --help` was creating ghost work items with title='--help'
|
|
165
|
+
// because the bare-string `title` was truthy and bypassed the `!title`
|
|
166
|
+
// usage check. Same class of bug exists in `spawn`/`plan`/`complete` —
|
|
167
|
+
// every command that takes a positional arg and tests it with `if (!arg)`.
|
|
168
|
+
//
|
|
169
|
+
// Intercept here so a single guard covers the whole command set. `pr` and
|
|
170
|
+
// `bridge` already handle `help`/`--help`/`-h` inline (see their own
|
|
171
|
+
// first-arg branches), so let them route through unchanged.
|
|
172
|
+
if (cmd !== 'pr' && cmd !== 'bridge' && isHelpToken(args && args[0])) {
|
|
173
|
+
console.log('Commands:');
|
|
174
|
+
for (const line of formatCliCommandHelpLines()) console.log(line);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
162
177
|
return commands[cmd](...args);
|
|
163
178
|
} else {
|
|
164
179
|
console.log(`Unknown command: ${cmd}`);
|
|
@@ -168,6 +183,26 @@ function handleCommand(cmd, args) {
|
|
|
168
183
|
}
|
|
169
184
|
}
|
|
170
185
|
|
|
186
|
+
// W-mq07mjzi000s1cc9: Help-flag token recognition.
|
|
187
|
+
//
|
|
188
|
+
// Matches the exact tokens the user typed on the CLI (`--help`, `-h`, `help`).
|
|
189
|
+
// Used by handleCommand's centralized guard and by per-command defensive
|
|
190
|
+
// checks (work/spawn/plan/complete) for defense-in-depth.
|
|
191
|
+
function isHelpToken(arg) {
|
|
192
|
+
return arg === '--help' || arg === '-h' || arg === 'help';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// W-mq07mjzi000s1cc9: Stricter guard for command first-positionals.
|
|
196
|
+
//
|
|
197
|
+
// Real work-item titles, agent ids, plan source paths, and dispatch ids never
|
|
198
|
+
// start with `--`. Rejecting any leading-`--` token catches the exact bug
|
|
199
|
+
// reported (`--help`) plus typos like `--hep`, `--h`, `-help` that would
|
|
200
|
+
// otherwise still slip through as ghost-WI titles.
|
|
201
|
+
function looksLikeFlagOrHelp(arg) {
|
|
202
|
+
if (isHelpToken(arg)) return true;
|
|
203
|
+
return typeof arg === 'string' && arg.startsWith('--');
|
|
204
|
+
}
|
|
205
|
+
|
|
171
206
|
// SoT for engine-CLI metadata: drives handleCommand's help text and the
|
|
172
207
|
// CC preamble's CLI index in dashboard.js. Drift-checked against `commands`.
|
|
173
208
|
const CLI_COMMAND_DOCS = Object.freeze({
|
|
@@ -1295,6 +1330,11 @@ const commands = {
|
|
|
1295
1330
|
console.log('Usage: minions complete <dispatch-id>');
|
|
1296
1331
|
return;
|
|
1297
1332
|
}
|
|
1333
|
+
// W-mq07mjzi000s1cc9 — defensive guard mirrors work/spawn/plan.
|
|
1334
|
+
if (looksLikeFlagOrHelp(id)) {
|
|
1335
|
+
console.log('Usage: minions complete <dispatch-id>');
|
|
1336
|
+
process.exit(2);
|
|
1337
|
+
}
|
|
1298
1338
|
const dispatch = getDispatch();
|
|
1299
1339
|
const item = (dispatch.active || []).find(d => d.id === id);
|
|
1300
1340
|
if (!item) {
|
|
@@ -1333,6 +1373,11 @@ const commands = {
|
|
|
1333
1373
|
console.log('Usage: node .minions/engine.js spawn <agent-id> "<prompt>"');
|
|
1334
1374
|
return;
|
|
1335
1375
|
}
|
|
1376
|
+
// W-mq07mjzi000s1cc9 — defensive guard mirrors work/plan/complete.
|
|
1377
|
+
if (looksLikeFlagOrHelp(agentId)) {
|
|
1378
|
+
console.log('Usage: node .minions/engine.js spawn <agent-id> "<prompt>"');
|
|
1379
|
+
process.exit(2);
|
|
1380
|
+
}
|
|
1336
1381
|
|
|
1337
1382
|
const config = getConfig();
|
|
1338
1383
|
if (!config.agents[agentId]) {
|
|
@@ -1365,6 +1410,16 @@ const commands = {
|
|
|
1365
1410
|
console.log(' id Optional caller-supplied work item ID. Defaults to a cuid-style W-<id>.');
|
|
1366
1411
|
return;
|
|
1367
1412
|
}
|
|
1413
|
+
// W-mq07mjzi000s1cc9 — Defense-in-depth: reject `--help`/`-h`/`help` or any
|
|
1414
|
+
// leading-`--` title even if a future caller bypasses handleCommand. The
|
|
1415
|
+
// original bug created ghost WIs with title='--help' because the truthy
|
|
1416
|
+
// check above let the flag through.
|
|
1417
|
+
if (looksLikeFlagOrHelp(title)) {
|
|
1418
|
+
console.log('Usage: node .minions/engine.js work "<title>" [options-json]');
|
|
1419
|
+
console.log('Options: {"id":"W-customid","type":"implement","priority":"high","agent":"dallas","description":"...","branch":"feature/...","project":"minions"}');
|
|
1420
|
+
console.log(' id Optional caller-supplied work item ID. Defaults to a cuid-style W-<id>.');
|
|
1421
|
+
process.exit(2);
|
|
1422
|
+
}
|
|
1368
1423
|
|
|
1369
1424
|
let opts = {};
|
|
1370
1425
|
const optStr = rest.join(' ');
|
|
@@ -1452,6 +1507,15 @@ const commands = {
|
|
|
1452
1507
|
console.log(' node engine.js plan "Add auth middleware with JWT tokens and role-based access"');
|
|
1453
1508
|
return;
|
|
1454
1509
|
}
|
|
1510
|
+
// W-mq07mjzi000s1cc9 — defensive guard mirrors work/spawn/complete.
|
|
1511
|
+
if (looksLikeFlagOrHelp(source)) {
|
|
1512
|
+
console.log('Usage: node .minions/engine.js plan <source> [project]');
|
|
1513
|
+
console.log('');
|
|
1514
|
+
console.log('Source can be:');
|
|
1515
|
+
console.log(' - A file path (markdown, txt, or json)');
|
|
1516
|
+
console.log(' - Inline text wrapped in quotes');
|
|
1517
|
+
process.exit(2);
|
|
1518
|
+
}
|
|
1455
1519
|
|
|
1456
1520
|
const config = getConfig();
|
|
1457
1521
|
const { getProjects, resolveProjectSource } = require('./shared');
|