@yemi33/minions 0.1.2363 → 0.1.2364
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/command-center.js +3 -0
- package/dashboard.js +39 -17
- package/docs/architecture-review-2026-07-09.md +94 -0
- package/docs/command-center.md +2 -0
- package/engine/lifecycle.js +27 -0
- package/engine/playbook.js +5 -5
- package/engine/process-utils.js +18 -7
- package/engine/queries.js +8 -4
- package/engine/shared.js +11 -1
- package/engine/worktree-gc.js +2 -2
- package/engine.js +3 -1
- package/package.json +1 -1
|
@@ -1286,6 +1286,9 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
|
|
|
1286
1286
|
updateStreamDiv();
|
|
1287
1287
|
} else if (evt.type === 'heartbeat') {
|
|
1288
1288
|
return;
|
|
1289
|
+
} else if (evt.type === 'status') {
|
|
1290
|
+
streamStatusNote = evt.message || '';
|
|
1291
|
+
updateStreamDiv();
|
|
1289
1292
|
} else if (evt.type === 'tool') {
|
|
1290
1293
|
ccSegmentsApplyTool(segments, { name: evt.name, input: evt.input || {}, id: evt.id || null });
|
|
1291
1294
|
if (activeTab) activeTab._segments = segments;
|
package/dashboard.js
CHANGED
|
@@ -2644,15 +2644,14 @@ function _getMtimes() {
|
|
|
2644
2644
|
return result;
|
|
2645
2645
|
}
|
|
2646
2646
|
|
|
2647
|
-
// Reset
|
|
2648
|
-
//
|
|
2649
|
-
// (`_mcpServersCache` 5min) are the two with TTLs longer than our poll
|
|
2650
|
-
// cadence, and both back slices the user sees on screen. Called on every
|
|
2651
|
-
// mtime-detected rebuild so the rebuild always reads fresh disk state.
|
|
2647
|
+
// Reset per-source caches whose TTL would otherwise survive an mtime-triggered
|
|
2648
|
+
// status rebuild and serve stale data.
|
|
2652
2649
|
function _invalidateInnerCachesForRebuild() {
|
|
2653
2650
|
try { queries.invalidateSkillsCache(); } catch { /* optional */ }
|
|
2654
2651
|
_mcpServersCache = null;
|
|
2655
2652
|
_mcpServersCacheTs = 0;
|
|
2653
|
+
_diskVersionCache = null;
|
|
2654
|
+
_diskVersionCacheTs = 0;
|
|
2656
2655
|
}
|
|
2657
2656
|
|
|
2658
2657
|
function _mtimesChanged(prev, curr) {
|
|
@@ -3706,6 +3705,7 @@ function _ensureCcLiveStream(tabId) {
|
|
|
3706
3705
|
tabId,
|
|
3707
3706
|
text: '',
|
|
3708
3707
|
tools: [],
|
|
3708
|
+
statusMessage: '',
|
|
3709
3709
|
donePayload: null,
|
|
3710
3710
|
writer: null,
|
|
3711
3711
|
endResponse: null,
|
|
@@ -6341,6 +6341,11 @@ const server = http.createServer(async (req, res) => {
|
|
|
6341
6341
|
if (!wiPath) {
|
|
6342
6342
|
const prdFile = body.prdFile;
|
|
6343
6343
|
if (!prdFile) return jsonReply(res, 404, { error: 'work item not found in any source' });
|
|
6344
|
+
// W-mrdykt9m0006e459 — prdFile is unauthenticated client input; reject
|
|
6345
|
+
// traversal/absolute paths and anything that resolves outside PRD_DIR
|
|
6346
|
+
// before it ever reaches a filesystem read.
|
|
6347
|
+
try { shared.sanitizePath(prdFile, PRD_DIR); }
|
|
6348
|
+
catch { return jsonReply(res, 400, { error: 'invalid prdFile path' }); }
|
|
6344
6349
|
|
|
6345
6350
|
// Look up PRD item to create a new work item on-demand.
|
|
6346
6351
|
// safeJsonNoRestore — PRDs are terminal artifacts; a stale .backup
|
|
@@ -10048,6 +10053,14 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10048
10053
|
if (!hasImages && shared.resolveCcUseWorkerPool(engineConfig)) {
|
|
10049
10054
|
return _invokeCcStreamViaPool({ prompt, liveState, toolUses, model, effort, engineConfig, systemPrompt, tabId });
|
|
10050
10055
|
}
|
|
10056
|
+
const setStatus = (message) => {
|
|
10057
|
+
liveState.statusMessage = message;
|
|
10058
|
+
if (liveState.writer) liveState.writer({ type: 'status', message });
|
|
10059
|
+
};
|
|
10060
|
+
if (hasImages) {
|
|
10061
|
+
_touchCcLiveStream(liveState);
|
|
10062
|
+
setStatus('Looking at the image...');
|
|
10063
|
+
}
|
|
10051
10064
|
const { callLLMStreaming } = require('./engine/llm');
|
|
10052
10065
|
return callLLMStreaming(prompt, systemPrompt, {
|
|
10053
10066
|
timeout: CC_CALL_TIMEOUT_MS, label: 'command-center', model, maxTurns,
|
|
@@ -10056,11 +10069,17 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10056
10069
|
engineConfig, images,
|
|
10057
10070
|
onChunk: (text, segmentId) => {
|
|
10058
10071
|
_touchCcLiveStream(liveState);
|
|
10072
|
+
if (liveState.statusMessage) {
|
|
10073
|
+
setStatus('');
|
|
10074
|
+
}
|
|
10059
10075
|
liveState.text = text;
|
|
10060
10076
|
if (liveState.writer) liveState.writer({ type: 'chunk', text, segmentId });
|
|
10061
10077
|
},
|
|
10062
10078
|
onToolUse: (name, input, id) => {
|
|
10063
10079
|
_touchCcLiveStream(liveState);
|
|
10080
|
+
if (liveState.statusMessage) {
|
|
10081
|
+
setStatus('');
|
|
10082
|
+
}
|
|
10064
10083
|
// Claude (direct) now threads a tool_use id; with an id the chip starts
|
|
10065
10084
|
// 'pending' and can be flipped to completed/failed by onToolUpdate.
|
|
10066
10085
|
const entry = { name, input: input || {}, id: id || null, status: id ? 'pending' : null };
|
|
@@ -10515,6 +10534,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
10515
10534
|
writeCcEvent({ type: 'tool-update', id: tool.id, status: tool.status });
|
|
10516
10535
|
}
|
|
10517
10536
|
}
|
|
10537
|
+
if (live.statusMessage) writeCcEvent({ type: 'status', message: live.statusMessage });
|
|
10518
10538
|
if (live.text) writeCcEvent({ type: 'chunk', text: live.text });
|
|
10519
10539
|
if (live.donePayload) {
|
|
10520
10540
|
writeCcEvent(live.donePayload);
|
|
@@ -11276,6 +11296,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11276
11296
|
} catch { /* unknown/free-text runtime */ }
|
|
11277
11297
|
return modelStr;
|
|
11278
11298
|
}
|
|
11299
|
+
const _isClear = (v) => v === '' || v === null;
|
|
11300
|
+
let _registeredCliNames = null;
|
|
11301
|
+
const _validCli = (name) => {
|
|
11302
|
+
if (_registeredCliNames == null) {
|
|
11303
|
+
try { _registeredCliNames = _engineRuntimes.listRuntimes(); }
|
|
11304
|
+
catch { _registeredCliNames = []; }
|
|
11305
|
+
}
|
|
11306
|
+
return _registeredCliNames.length === 0 || _registeredCliNames.includes(String(name));
|
|
11307
|
+
};
|
|
11279
11308
|
if (body.engine) {
|
|
11280
11309
|
const e = body.engine;
|
|
11281
11310
|
const D = shared.ENGINE_DEFAULTS;
|
|
@@ -11385,15 +11414,6 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11385
11414
|
// chooses)" option submits '' and we must persist that as "unset".
|
|
11386
11415
|
// Validate `defaultCli` and `ccCli` against the runtime registry so a
|
|
11387
11416
|
// typo in the dashboard can't pin the fleet to a non-existent runtime.
|
|
11388
|
-
const _isClear = (v) => v === '' || v === null;
|
|
11389
|
-
let _registeredCliNames = null;
|
|
11390
|
-
const _validCli = (name) => {
|
|
11391
|
-
if (_registeredCliNames == null) {
|
|
11392
|
-
try { _registeredCliNames = require('./engine/runtimes').listRuntimes(); }
|
|
11393
|
-
catch { _registeredCliNames = []; }
|
|
11394
|
-
}
|
|
11395
|
-
return _registeredCliNames.length === 0 || _registeredCliNames.includes(String(name));
|
|
11396
|
-
};
|
|
11397
11417
|
if (e.defaultCli !== undefined) {
|
|
11398
11418
|
if (_isClear(e.defaultCli)) _deleteEngineConfig('defaultCli');
|
|
11399
11419
|
else if (_validCli(e.defaultCli)) _setEngineConfig('defaultCli', String(e.defaultCli));
|
|
@@ -11589,8 +11609,9 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
11589
11609
|
// CLI values pin the agent to a specific runtime. `0` is a valid
|
|
11590
11610
|
// maxBudgetUsd (read-only / dry-run agents).
|
|
11591
11611
|
if (updates.cli !== undefined) {
|
|
11592
|
-
if (updates.cli
|
|
11593
|
-
else config.agents[id].cli = String(updates.cli);
|
|
11612
|
+
if (_isClear(updates.cli)) delete config.agents[id].cli;
|
|
11613
|
+
else if (_validCli(updates.cli)) config.agents[id].cli = String(updates.cli);
|
|
11614
|
+
else _clamped.push(`agents.${id}.cli: "${updates.cli}" not registered (kept previous value)`);
|
|
11594
11615
|
}
|
|
11595
11616
|
if (updates.model !== undefined) {
|
|
11596
11617
|
if (updates.model === '' || updates.model === null) delete config.agents[id].model;
|
|
@@ -14901,7 +14922,8 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
14901
14922
|
// dashboard route serves dashboard/slim.html instead of the full SPA.
|
|
14902
14923
|
// Checked at request time so /api/features/toggle flips behavior with
|
|
14903
14924
|
// no restart; flag-off means zero behavior change for the catch-all.
|
|
14904
|
-
|
|
14925
|
+
const fullDashboardRequested = new URL(req.url, 'http://localhost').searchParams.get('fullDashboard') === '1';
|
|
14926
|
+
if (pathname === '/' && !fullDashboardRequested && features.isFeatureOn('slim-ux', CONFIG)) {
|
|
14905
14927
|
return serveSlimUx(req, res);
|
|
14906
14928
|
}
|
|
14907
14929
|
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Architecture Review — 2026-07-09
|
|
2
|
+
|
|
3
|
+
Scope: engine orchestration, SQL/JSON state boundaries, runtime/process recovery,
|
|
4
|
+
worktree lifecycle, pipelines/schedules, dashboard/API settings, and prompt
|
|
5
|
+
assembly. Findings were verified against `origin/main` at `b1b83dd2`.
|
|
6
|
+
|
|
7
|
+
## Fixed in this change
|
|
8
|
+
|
|
9
|
+
### High: Codex processes were rejected by orphan recovery
|
|
10
|
+
|
|
11
|
+
`isProcessCommandLineMatchingAgent` recognized only Claude and Copilot. After an
|
|
12
|
+
engine restart, a live Codex dispatch could therefore be classified as a
|
|
13
|
+
recycled PID and retried while the original process was still running.
|
|
14
|
+
|
|
15
|
+
The process gate now recognizes all bundled runtime CLIs, including native and
|
|
16
|
+
npm-installed Codex and Claude layouts, without accepting unrelated substring
|
|
17
|
+
matches.
|
|
18
|
+
|
|
19
|
+
### High: worktree GC lost the project name from real dispatch rows
|
|
20
|
+
|
|
21
|
+
Worktree GC assumed `dispatch.meta.project` was a string, while production
|
|
22
|
+
dispatches store a project descriptor object. In shared worktree-root layouts,
|
|
23
|
+
the resulting `default` hash could fail to protect another project's active
|
|
24
|
+
worktree from orphan cleanup.
|
|
25
|
+
|
|
26
|
+
GC now resolves both the current object shape and the legacy string shape.
|
|
27
|
+
|
|
28
|
+
### Medium: invalid per-agent runtimes bypassed settings validation
|
|
29
|
+
|
|
30
|
+
Fleet runtime fields were validated against the adapter registry, but
|
|
31
|
+
`agents.<id>.cli` was persisted verbatim. A typo could poison future dispatches
|
|
32
|
+
with a runtime-resolution failure. Per-agent overrides now share the fleet
|
|
33
|
+
validation path and preserve the previous value on invalid input.
|
|
34
|
+
|
|
35
|
+
### Medium: the no-TTL status cache omitted its own source files
|
|
36
|
+
|
|
37
|
+
`/api/status` includes config-, install-, and package-derived fields but its
|
|
38
|
+
mtime registry omitted `config.json`, `.install-id`, and `package.json`.
|
|
39
|
+
Out-of-process changes could remain stale indefinitely. These inputs now
|
|
40
|
+
participate in cache invalidation.
|
|
41
|
+
|
|
42
|
+
### Medium: Slim UX trapped users away from advanced settings
|
|
43
|
+
|
|
44
|
+
Slim linked to `/?fullDashboard=1`, but root takeover ignored the query string
|
|
45
|
+
and served Slim again. The root dispatcher now honors that explicit classic-UX
|
|
46
|
+
escape hatch.
|
|
47
|
+
|
|
48
|
+
### Low: prompt validation generated warnings for valid templates
|
|
49
|
+
|
|
50
|
+
The validator required the literal `## Your Task`, while established playbooks
|
|
51
|
+
also use `## Task Description` and `## Mission`. Those valid aliases now satisfy
|
|
52
|
+
the task-section contract, eliminating recurring false-positive warnings.
|
|
53
|
+
|
|
54
|
+
### Low: cancelled cron work blocked future schedule fires
|
|
55
|
+
|
|
56
|
+
The dashboard run-now path already treated cancelled work as terminal, but the
|
|
57
|
+
engine's cron enqueue dedup did not. Both paths now allow a new run after
|
|
58
|
+
cancellation.
|
|
59
|
+
|
|
60
|
+
## Verified follow-up findings
|
|
61
|
+
|
|
62
|
+
These require separate focused changes rather than expanding this PR:
|
|
63
|
+
|
|
64
|
+
1. **Dispatch SQL/JSON empty-state resurrection (high).** A process that has not
|
|
65
|
+
written SQL can still adopt stale `dispatch.json` when the SQL table is empty.
|
|
66
|
+
The durable fix is a mirror-generation/hash marker or removal of the legacy
|
|
67
|
+
fallback, not moving filesystem writes inside a SQLite transaction.
|
|
68
|
+
2. **Pipeline task terminal-state model (high).** Task stages use a boolean
|
|
69
|
+
completion check and promote failed work items to a completed pipeline stage;
|
|
70
|
+
cancelled/review-blocked work can wedge indefinitely. Replace the boolean
|
|
71
|
+
helper with an explicit terminal outcome (`completed`, `failed`,
|
|
72
|
+
`waiting-human`, non-terminal) and propagate it through parallel stages.
|
|
73
|
+
3. **Settings save spans two independent writes (medium).** Config and routing
|
|
74
|
+
are saved through separate endpoints, so routing failure can leave a partial
|
|
75
|
+
configuration update. A combined transactional settings contract should
|
|
76
|
+
replace the client-side two-step sequence.
|
|
77
|
+
4. **ADO identity lookup duplication (medium).** PR polling resolves the same
|
|
78
|
+
authenticated user more than once per PR. Cache it once per organization and
|
|
79
|
+
poll pass to reduce throttling pressure.
|
|
80
|
+
5. **Late descendant process leak window (medium).** Spawn cleanup snapshots
|
|
81
|
+
descendants at 3 seconds and then every 30 seconds. Children created and
|
|
82
|
+
reparented inside that window can evade final cleanup. Track descendants more
|
|
83
|
+
frequently while the runtime is alive.
|
|
84
|
+
6. **Supervisor stale PID reuse (medium).** Startup trusts any live PID in
|
|
85
|
+
`supervisor.pid`. Validate the command line against this checkout's
|
|
86
|
+
`engine/supervisor.js` before suppressing startup.
|
|
87
|
+
|
|
88
|
+
## Healthy areas
|
|
89
|
+
|
|
90
|
+
- Config/routing references and schedule schemas were valid.
|
|
91
|
+
- Runtime work-item and pull-request mirrors had no stuck dispatched rows,
|
|
92
|
+
duplicate PR IDs, or unknown PR states.
|
|
93
|
+
- No active dispatch was older than the five-hour wall-clock limit.
|
|
94
|
+
- Cooldown state was small and not exhibiting pending-context bloat.
|
package/docs/command-center.md
CHANGED
|
@@ -70,6 +70,8 @@ Any violation rejects the **whole turn** with a typed error envelope (`code: 'in
|
|
|
70
70
|
|
|
71
71
|
**Worker-pool interaction (W-mray463s001zcee2).** The opt-in Copilot ACP worker pool (`engine.ccUseWorkerPool: true`, default ON for Copilot CC) is a **separate protocol path** from the table above — `engine/cc-worker-pool.js`'s `session/prompt` call only ever sends a single text content block (`[{ type: 'text', text: prompt }]`) and has no image/attachment content-block support today. This used to mean image turns were silently dropped whenever the pool was active, with a comment incorrectly blaming a Copilot capability gap (`imageInput:false`) that doesn't exist — Copilot's `imageInput` is `true` and fully supports images on the direct spawn path. The fix: `_invokeCcStream` (dashboard.js) now checks for a non-empty `images` array **before** routing through `resolveCcUseWorkerPool` — image-bearing turns bypass the warm pool for that turn only and cold-spawn a one-off CLI process via the working `--attachment` path (same as the non-pooled path always used), while image-less turns keep using the fast warm pool. If `engine/cc-worker-pool.js`'s ACP session protocol is later extended to carry attachments (Option A), this per-turn bypass in `_invokeCcStream` should be revisited/removed.
|
|
72
72
|
|
|
73
|
+
Because that bypass can spend tens of seconds starting a one-off process before the first model token, the SSE stream shows a user-focused “Looking at the image…” status instead of exposing the cold-start implementation detail or leaving an apparently stalled spinner.
|
|
74
|
+
|
|
73
75
|
`_resolveImageOpts` (in `engine/llm.js`) is pure and unit-tested: it forwards the `images` list only when `runtime.capabilities.imageInput` is truthy, otherwise returns the typed error so CC/doc-chat surface the envelope instead of a silently-text-only reply. `_spawnProcess` re-applies the same gate (`if (!caps.imageInput) adapterOpts.images = undefined`) as defense in depth. Image **filenames** are run through the untrusted-input fence (`buildSource('cc-image-filename', …)`) before they reach prompt text, since they are user-supplied.
|
|
74
76
|
|
|
75
77
|
**No new `engine.*` flag.** The limits above are module-level constants in `dashboard.js` (`CC_IMAGE_MAX_COUNT`, `CC_IMAGE_MAX_DECODED_BYTES`, `CC_IMAGE_MIME_ALLOWLIST`), not config keys; `engine/shared.js` `ENGINE_DEFAULTS` was not touched. Per CLAUDE.md Best Practice #9 (Settings parity), no Settings toggle is added because no new `engine.*` flag was introduced. Runtime selection that determines whether images are accepted is the existing `engine.ccCli` / `ccModel` override, which already has a Settings control.
|
package/engine/lifecycle.js
CHANGED
|
@@ -6874,6 +6874,15 @@ function collapseAllDuplicatePrRecords(config) {
|
|
|
6874
6874
|
// a coding WI (not one of the validation types itself). Skips if the coding WI
|
|
6875
6875
|
// has no PR.
|
|
6876
6876
|
//
|
|
6877
|
+
// W-mrdq22j700056c6c — gated to real code-authoring completions only. Only
|
|
6878
|
+
// item.type in {implement, implement:large, fix} ever spawns a follow-up
|
|
6879
|
+
// validation WI. Every other work-item type (review, test, verify, setup,
|
|
6880
|
+
// explore, ask, docs, decompose, ...) is excluded even when it resolves a PR
|
|
6881
|
+
// ref and autoDispatch is on, because those completions don't author a code
|
|
6882
|
+
// diff worth re-validating — e.g. a `review` dispatch that merely comments on
|
|
6883
|
+
// someone else's PR previously spawned a spurious "Validate: Review opg PR
|
|
6884
|
+
// #792 ..." follow-up. See the ELIGIBLE_TYPES check below.
|
|
6885
|
+
//
|
|
6877
6886
|
// Files exactly ONE validation WI per coding-WI completion (W-mrhybval0001).
|
|
6878
6887
|
// `liveValidation.type` still governs CHECKOUT ROUTING — the set of work-item
|
|
6879
6888
|
// types that run in-place on the live checkout (see resolveCheckoutMode's
|
|
@@ -6893,6 +6902,10 @@ function collapseAllDuplicatePrRecords(config) {
|
|
|
6893
6902
|
// own type is excluded so a validation-type WI can't recursively validate
|
|
6894
6903
|
// itself (anti-loop).
|
|
6895
6904
|
//
|
|
6905
|
+
// Only these item.type values ever trigger this hook (see ELIGIBLE_TYPES
|
|
6906
|
+
// below) — real code-authoring completions, not review/test/verify/setup/
|
|
6907
|
+
// explore/ask/docs/decompose completions.
|
|
6908
|
+
//
|
|
6896
6909
|
// Deduplicates per codingWiId: any non-terminal WI with
|
|
6897
6910
|
// meta.liveValidationFor === codingWiId blocks a second dispatch, so only one
|
|
6898
6911
|
// validation task is ever in flight per coding completion.
|
|
@@ -6907,6 +6920,14 @@ function collapseAllDuplicatePrRecords(config) {
|
|
|
6907
6920
|
// Validate: ..."), repeating indefinitely. Any WI that was itself created
|
|
6908
6921
|
// as a liveValidation follow-up (tagged via meta.liveValidationFor at
|
|
6909
6922
|
// creation, below) must never re-trigger this hook, regardless of its type.
|
|
6923
|
+
//
|
|
6924
|
+
// W-mrdq22j700056c6c — only real code-authoring work-item types ever spawn a
|
|
6925
|
+
// live-validation follow-up. Every other completed type (review, test,
|
|
6926
|
+
// verify, setup, explore, ask, docs, decompose, ...) is excluded even when it
|
|
6927
|
+
// resolves a PR ref, because those completions don't author a code diff
|
|
6928
|
+
// worth re-validating.
|
|
6929
|
+
const ELIGIBLE_LIVE_VALIDATION_TYPES = new Set([WORK_TYPE.IMPLEMENT, WORK_TYPE.IMPLEMENT_LARGE, WORK_TYPE.FIX]);
|
|
6930
|
+
|
|
6910
6931
|
function autoDispatchLiveValidationWi(meta, config) {
|
|
6911
6932
|
const item = meta?.item;
|
|
6912
6933
|
if (!item?.id || !item?.type) return;
|
|
@@ -6916,6 +6937,12 @@ function autoDispatchLiveValidationWi(meta, config) {
|
|
|
6916
6937
|
// "Validate: Validate: ..." cascade (W-mr9u39az000db5c2 / issue #708).
|
|
6917
6938
|
if (item.meta && item.meta.liveValidationFor) return;
|
|
6918
6939
|
|
|
6940
|
+
// W-mrdq22j700056c6c — only implement/implement:large/fix completions ever
|
|
6941
|
+
// author a real code diff worth validating. Review, test, verify, setup,
|
|
6942
|
+
// explore, ask, docs, decompose, etc. completions must never spawn a
|
|
6943
|
+
// "Validate: ..." follow-up, even when they resolve a PR reference.
|
|
6944
|
+
if (!ELIGIBLE_LIVE_VALIDATION_TYPES.has(item.type)) return;
|
|
6945
|
+
|
|
6919
6946
|
// Issue #716 — QA Session infrastructure WIs (SETUP/DRAFT/EXECUTE, tagged
|
|
6920
6947
|
// via meta.sessionId and always dispatched with skipPr:true) never own a
|
|
6921
6948
|
// real code diff, so there is nothing for a live-validation WI to validate.
|
package/engine/playbook.js
CHANGED
|
@@ -1060,12 +1060,12 @@ function renderPlaybook(type, vars) {
|
|
|
1060
1060
|
// ─── Playbook Section Validator ──────────────────────────────────────────────
|
|
1061
1061
|
|
|
1062
1062
|
// Required structural section patterns — warn (do not throw) when absent.
|
|
1063
|
-
//
|
|
1064
|
-
//
|
|
1065
|
-
//
|
|
1066
|
-
//
|
|
1063
|
+
// Playbooks use a few established names for the task-bearing section; accept
|
|
1064
|
+
// all of them rather than generating warn-level noise for valid templates.
|
|
1065
|
+
// '## Tools' / '## Constraints' were removed (#775) because no template
|
|
1066
|
+
// consistently implements those headers.
|
|
1067
1067
|
const _REQUIRED_PROMPT_SECTIONS = [
|
|
1068
|
-
{ pattern: /^## Your Task\b/m,
|
|
1068
|
+
{ pattern: /^## (?:Your Task|Task Description|Mission)\b/m, label: 'task section' },
|
|
1069
1069
|
];
|
|
1070
1070
|
|
|
1071
1071
|
/**
|
package/engine/process-utils.js
CHANGED
|
@@ -566,9 +566,9 @@ function findProcessCwdHolders(worktreePath, opts = {}) {
|
|
|
566
566
|
return _parseCwdHolderLines(raw, resolved);
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
-
// Cross-check a single PID's command line for a Minions agent invocation
|
|
570
|
-
//
|
|
571
|
-
//
|
|
569
|
+
// Cross-check a single PID's command line for a Minions agent invocation,
|
|
570
|
+
// including the shared spawn wrapper and every bundled runtime CLI. Used by
|
|
571
|
+
// orphan/recycled-PID safety:
|
|
572
572
|
// - engine/cleanup.js: gate before killing a PID found in engine/tmp/pid-*.pid
|
|
573
573
|
// - engine/timeout.js: gate before parking a dispatch as still-alive when
|
|
574
574
|
// the OS PID is alive but may belong to an unrelated recycled-PID process
|
|
@@ -578,9 +578,20 @@ function findProcessCwdHolders(worktreePath, opts = {}) {
|
|
|
578
578
|
// macOS / when /proc isn't available: fallback `ps -p <pid> -o command=`.
|
|
579
579
|
//
|
|
580
580
|
// Returns false when the PID is invalid, the process doesn't exist, the
|
|
581
|
-
// command line can't be read, or the cmdline contains
|
|
582
|
-
//
|
|
581
|
+
// command line can't be read, or the cmdline contains no recognized agent
|
|
582
|
+
// marker. False is the safe default for both call sites: cleanup falls
|
|
583
583
|
// through to "skip kill" and timeout falls through to "treat PID as dead".
|
|
584
|
+
const AGENT_COMMAND_PATTERNS = [
|
|
585
|
+
/(?:^|[\s"'\\/])(?:claude|copilot|codex)(?:\.exe|\.cmd|\.bat|\.js)?(?=$|[\s"])/i,
|
|
586
|
+
/[\\/]claude-code[\\/]cli\.js(?=$|[\s"])/i,
|
|
587
|
+
/[\\/]@github[\\/](?:copilot|copilot-cli)[\\/](?:(?:dist|bin)[\\/])?index\.js(?=$|[\s"])/i,
|
|
588
|
+
];
|
|
589
|
+
|
|
590
|
+
function _commandLineMatchesAgent(cmdline) {
|
|
591
|
+
const value = String(cmdline || '');
|
|
592
|
+
return AGENT_COMMAND_PATTERNS.some(pattern => pattern.test(value));
|
|
593
|
+
}
|
|
594
|
+
|
|
584
595
|
function isProcessCommandLineMatchingAgent(pid) {
|
|
585
596
|
const n = Number(pid);
|
|
586
597
|
if (!Number.isInteger(n) || n <= 0) return false;
|
|
@@ -605,8 +616,7 @@ function isProcessCommandLineMatchingAgent(pid) {
|
|
|
605
616
|
}
|
|
606
617
|
} catch { return false; }
|
|
607
618
|
if (!cmdline) return false;
|
|
608
|
-
|
|
609
|
-
return lower.includes('claude') || lower.includes('copilot');
|
|
619
|
+
return _commandLineMatchesAgent(cmdline);
|
|
610
620
|
}
|
|
611
621
|
|
|
612
622
|
function _buildChildMap(processes) {
|
|
@@ -693,6 +703,7 @@ module.exports = {
|
|
|
693
703
|
_parseCwdHolderLines,
|
|
694
704
|
findProcessCwdHolders,
|
|
695
705
|
isProcessCommandLineMatchingAgent,
|
|
706
|
+
_commandLineMatchesAgent, // exported for testing
|
|
696
707
|
_buildChildMap,
|
|
697
708
|
listProcessDescendants,
|
|
698
709
|
listProcessReachable,
|
package/engine/queries.js
CHANGED
|
@@ -3283,10 +3283,6 @@ function getStatusFastStateMtimePaths(config) {
|
|
|
3283
3283
|
* Claude CLI mutations (mcp add/remove, settings edits), not on every
|
|
3284
3284
|
* prompt, so whole-file tracking is acceptable.
|
|
3285
3285
|
*
|
|
3286
|
-
* Files intentionally NOT tracked here:
|
|
3287
|
-
* - version, autoMode, installId — change only on human/CLI edits, which
|
|
3288
|
-
* already pop the slow-state via reloadConfig + the 60 s TTL.
|
|
3289
|
-
*
|
|
3290
3286
|
* Per-project `.git/logs/HEAD` + `.git/FETCH_HEAD` ARE tracked here in
|
|
3291
3287
|
* addition to the fast-state tracker. The project payload (`projects:`
|
|
3292
3288
|
* with ahead/behind counts) lives in the slow-state slice, and
|
|
@@ -3327,6 +3323,14 @@ function getStatusSlowStateMtimePaths(config) {
|
|
|
3327
3323
|
// in the payload anymore.
|
|
3328
3324
|
const files = [];
|
|
3329
3325
|
|
|
3326
|
+
// Config-derived status fields (projects, autoMode, version settings) can be
|
|
3327
|
+
// changed by the CLI or another process without calling dashboard-local
|
|
3328
|
+
// invalidateStatusCache(). The unified cache has no TTL fallback, so these
|
|
3329
|
+
// source files must participate in the mtime registry.
|
|
3330
|
+
files.push(CONFIG_PATH);
|
|
3331
|
+
files.push(path.join(MINIONS_DIR, '.install-id'));
|
|
3332
|
+
files.push(path.join(MINIONS_DIR, 'package.json'));
|
|
3333
|
+
|
|
3330
3334
|
// Skill discovery roots (surfaced by _buildStatusSlowState → getSkills).
|
|
3331
3335
|
// Mirrors collectSkillFiles' source enumeration so adding a new runtime
|
|
3332
3336
|
// adapter automatically extends the tracker. ENOENT is tolerated by
|
package/engine/shared.js
CHANGED
|
@@ -7882,7 +7882,17 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
|
|
|
7882
7882
|
...(Array.isArray(itemIds) ? itemIds : [itemId]),
|
|
7883
7883
|
]);
|
|
7884
7884
|
const prNumber = getPrNumber(entry.prNumber ?? entry.id ?? entry.url);
|
|
7885
|
-
|
|
7885
|
+
// Issue #802: when the caller already resolved a scope-qualified canonical
|
|
7886
|
+
// id (e.g. `ado:office/office/office#5370216`, as enrollPrFromCanonicalId
|
|
7887
|
+
// does before calling here), it must win over recomputing from a bare
|
|
7888
|
+
// entry.prNumber + project. Recomputing silently downgrades to a legacy
|
|
7889
|
+
// `PR-<n>` stub whenever project is null/unresolvable (getCanonicalPrId
|
|
7890
|
+
// can't derive a scope), discarding the good id even though it was passed
|
|
7891
|
+
// in. Prefer entry.id whenever it already parses as canonical.
|
|
7892
|
+
const hasCanonicalEntryId = typeof entry.id === 'string' && parseCanonicalPrId(entry.id) != null;
|
|
7893
|
+
const canonicalId = hasCanonicalEntryId
|
|
7894
|
+
? getCanonicalPrId(project, entry.id, entry.url || '')
|
|
7895
|
+
: getCanonicalPrId(project, entry.prNumber ?? entry.id ?? entry.url ?? prNumber, entry.url || '');
|
|
7886
7896
|
if (!canonicalId) throw new Error('PR id required');
|
|
7887
7897
|
const normalizedEntry = {
|
|
7888
7898
|
...entry,
|
package/engine/worktree-gc.js
CHANGED
|
@@ -769,9 +769,9 @@ function pruneOrphanWorktrees(opts) {
|
|
|
769
769
|
const globalLiveDirNames = new Set();
|
|
770
770
|
for (const d of [...(dispatchSnap.active || []), ...(dispatchSnap.pending || [])]) {
|
|
771
771
|
if (!d || !d.id || !d.meta || !d.meta.branch) continue;
|
|
772
|
-
const dispatchProject =
|
|
772
|
+
const dispatchProject = typeof d.meta.project === 'string'
|
|
773
773
|
? d.meta.project
|
|
774
|
-
: 'default';
|
|
774
|
+
: (d.meta.project?.name || 'default');
|
|
775
775
|
try {
|
|
776
776
|
globalLiveDirNames.add(_buildWorktreeDirName({
|
|
777
777
|
dispatchId: d.id,
|
package/engine.js
CHANGED
|
@@ -10141,7 +10141,9 @@ async function discoverWork(config) {
|
|
|
10141
10141
|
const activeMissionIds = new Set();
|
|
10142
10142
|
const activeScheduleIds = new Set();
|
|
10143
10143
|
for (const existing of items) {
|
|
10144
|
-
if (
|
|
10144
|
+
if (DONE_STATUSES.has(existing.status) ||
|
|
10145
|
+
existing.status === WI_STATUS.FAILED ||
|
|
10146
|
+
existing.status === WI_STATUS.CANCELLED) continue;
|
|
10145
10147
|
if (existing._missionId) activeMissionIds.add(existing._missionId);
|
|
10146
10148
|
if (existing._scheduleId) activeScheduleIds.add(existing._scheduleId);
|
|
10147
10149
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2364",
|
|
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"
|