@yemi33/minions 0.1.2078 → 0.1.2080
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/refresh.js +19 -0
- package/dashboard.js +9 -3
- package/engine/shared.js +110 -0
- package/package.json +1 -1
- package/prompts/cc-system.md +13 -0
package/dashboard/js/refresh.js
CHANGED
|
@@ -474,6 +474,13 @@ let _knownDashboardStartId = null;
|
|
|
474
474
|
// dashboardBuildId is the md5 of the assembled HTML — bumps automatically on
|
|
475
475
|
// hot-reload + on cold restart with any /dashboard/** change.
|
|
476
476
|
let _knownDashboardBuildId = null;
|
|
477
|
+
// Hard-reload trigger when the *engine* commit changes — `minions restart`
|
|
478
|
+
// after a `git pull` swaps engine code but doesn't touch any
|
|
479
|
+
// /dashboard/** asset, so `dashboardBuildId` stays the same and the
|
|
480
|
+
// existing buildId reload above never fires. Without this, the stale
|
|
481
|
+
// "Engine running v… — disk has v…" banner persists in already-open tabs
|
|
482
|
+
// until the (hourly by default) /api/version repoll catches up.
|
|
483
|
+
let _knownEngineRunningCommit = null;
|
|
477
484
|
// /api/status ETag cache (W-mpehsyhv0017085a). The dashboard polls every 4 s
|
|
478
485
|
// but the server-side cache only changes every 10–60 s. Sending If-None-Match
|
|
479
486
|
// lets the server short-circuit ~60 %+ of polls into a 304 with no body —
|
|
@@ -755,6 +762,18 @@ async function refresh() {
|
|
|
755
762
|
return;
|
|
756
763
|
}
|
|
757
764
|
if (buildId) _knownDashboardBuildId = buildId;
|
|
765
|
+
// Auto-reload when the engine's commit changed (engine code was updated
|
|
766
|
+
// and process restarted, e.g. via `minions restart` after `git pull`).
|
|
767
|
+
// dashboardBuildId only hashes HTML/JS/CSS assets, so engine-only code
|
|
768
|
+
// updates don't trigger that path — leaving stale "Engine running v… —
|
|
769
|
+
// disk has v…" banners until the hourly /api/version repoll.
|
|
770
|
+
const engineCommit = (data.version && data.version.runningCommit) || null;
|
|
771
|
+
if (engineCommit && _knownEngineRunningCommit && engineCommit !== _knownEngineRunningCommit) {
|
|
772
|
+
console.log('Engine code changed — reloading page');
|
|
773
|
+
location.reload();
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (engineCommit) _knownEngineRunningCommit = engineCommit;
|
|
758
777
|
// Successful poll — clear unreachable state. Placed AFTER the reload
|
|
759
778
|
// guards above so a dashboard restart still triggers location.reload()
|
|
760
779
|
// instead of just dismissing the banner.
|
package/dashboard.js
CHANGED
|
@@ -2810,9 +2810,15 @@ function _ccTurnEntryToActionType(kind) {
|
|
|
2810
2810
|
}
|
|
2811
2811
|
}
|
|
2812
2812
|
|
|
2813
|
-
// Hash the system prompt so we can detect changes and invalidate stale sessions
|
|
2814
|
-
|
|
2815
|
-
|
|
2813
|
+
// Hash the system prompt so we can detect changes and invalidate stale sessions.
|
|
2814
|
+
// Hashing the RENDERED prompt (not the raw template) means changes to the
|
|
2815
|
+
// substituted content — most importantly the dynamic {{available_skills}}
|
|
2816
|
+
// registry built from ~/.copilot/skills/ + installed-plugins — also invalidate
|
|
2817
|
+
// pooled sessions on dashboard restart. Without this, installing or removing
|
|
2818
|
+
// a skill would leave warm sessions glued to the old registry until they aged
|
|
2819
|
+
// out naturally.
|
|
2820
|
+
const _ccPromptHash = require('crypto').createHash('md5').update(CC_STATIC_SYSTEM_PROMPT).digest('hex').slice(0, 8);
|
|
2821
|
+
const _docChatPromptHash = require('crypto').createHash('md5').update(DOC_CHAT_SYSTEM_PROMPT).digest('hex').slice(0, 8);
|
|
2816
2822
|
|
|
2817
2823
|
function _sessionExpired(lastActiveAt, ttlMs) {
|
|
2818
2824
|
if (!lastActiveAt || !ttlMs) return false;
|
package/engine/shared.js
CHANGED
|
@@ -3455,6 +3455,106 @@ function describeCcProtectedPaths(liveRoot) {
|
|
|
3455
3455
|
return `READ ONLY in the live checkout at \`${norm}\` — never write/edit: ${basenames}, ${globs}, ${prefixes}. This rule is path-scoped, not basename-scoped. Files with the same basename inside an isolated agent worktree (e.g. \`{worktreeRoot}/W-<id>/dashboard.js\`) are NOT protected — agents working in their own worktrees may edit any repository source the work item requires.`;
|
|
3456
3456
|
}
|
|
3457
3457
|
|
|
3458
|
+
// Enumerate Copilot CLI skills from the conventional install layout so the CC
|
|
3459
|
+
// system prompt can mirror the vanilla runtime's <available_skills> registry.
|
|
3460
|
+
//
|
|
3461
|
+
// The CC system prompt fully replaces Copilot CLI's default — which means the
|
|
3462
|
+
// default's auto-injected <available_skills> block disappears, and the agent
|
|
3463
|
+
// has no idea what skills it has. It then falls back to grep/glob against
|
|
3464
|
+
// well-known paths (typically `.claude/skills/` because the underlying model
|
|
3465
|
+
// is Claude and that path dominates its training data). This helper rebuilds
|
|
3466
|
+
// the registry from the actual Copilot install dirs so the prompt can tell
|
|
3467
|
+
// the agent the truth.
|
|
3468
|
+
//
|
|
3469
|
+
// Locations scanned (matches Copilot CLI's discovery rules):
|
|
3470
|
+
// - ~/.copilot/skills/<name>/SKILL.md → location="user"
|
|
3471
|
+
// - ~/.copilot/installed-plugins/<mkt>/<plugin>/skills/<name>/SKILL.md
|
|
3472
|
+
// → location="plugin"
|
|
3473
|
+
//
|
|
3474
|
+
// Tolerates lowercase `skill.md` (Linux-style installs) and missing dirs
|
|
3475
|
+
// (returns an empty array rather than throwing). User-level entries shadow
|
|
3476
|
+
// plugin entries with the same name (mirrors Copilot CLI's user-wins rule).
|
|
3477
|
+
function listCopilotSkills(opts) {
|
|
3478
|
+
const homeDir = (opts && typeof opts.homeDir === 'string') ? opts.homeDir : os.homedir();
|
|
3479
|
+
const copilotRoot = path.join(homeDir, '.copilot');
|
|
3480
|
+
const collected = [];
|
|
3481
|
+
|
|
3482
|
+
const readSkillDir = (dir, fallbackName, location) => {
|
|
3483
|
+
let content;
|
|
3484
|
+
try {
|
|
3485
|
+
content = fs.readFileSync(path.join(dir, 'SKILL.md'), 'utf8');
|
|
3486
|
+
} catch {
|
|
3487
|
+
try { content = fs.readFileSync(path.join(dir, 'skill.md'), 'utf8'); }
|
|
3488
|
+
catch { return null; }
|
|
3489
|
+
}
|
|
3490
|
+
const fm = parseSkillFrontmatter(content, fallbackName + '.md');
|
|
3491
|
+
return {
|
|
3492
|
+
name: fm.name || fallbackName,
|
|
3493
|
+
description: fm.description || '',
|
|
3494
|
+
location,
|
|
3495
|
+
};
|
|
3496
|
+
};
|
|
3497
|
+
|
|
3498
|
+
const safeReadDirents = (dir) => {
|
|
3499
|
+
try { return fs.readdirSync(dir, { withFileTypes: true }); }
|
|
3500
|
+
catch { return []; }
|
|
3501
|
+
};
|
|
3502
|
+
|
|
3503
|
+
// User-level skills
|
|
3504
|
+
for (const entry of safeReadDirents(path.join(copilotRoot, 'skills'))) {
|
|
3505
|
+
if (!entry.isDirectory()) continue;
|
|
3506
|
+
const skill = readSkillDir(path.join(copilotRoot, 'skills', entry.name), entry.name, 'user');
|
|
3507
|
+
if (skill) collected.push(skill);
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
// Plugin-provided skills (two levels deep: marketplace/plugin)
|
|
3511
|
+
const pluginsRoot = path.join(copilotRoot, 'installed-plugins');
|
|
3512
|
+
for (const mkt of safeReadDirents(pluginsRoot)) {
|
|
3513
|
+
if (!mkt.isDirectory()) continue;
|
|
3514
|
+
for (const plugin of safeReadDirents(path.join(pluginsRoot, mkt.name))) {
|
|
3515
|
+
if (!plugin.isDirectory()) continue;
|
|
3516
|
+
const pluginSkillsDir = path.join(pluginsRoot, mkt.name, plugin.name, 'skills');
|
|
3517
|
+
for (const entry of safeReadDirents(pluginSkillsDir)) {
|
|
3518
|
+
if (!entry.isDirectory()) continue;
|
|
3519
|
+
const skill = readSkillDir(path.join(pluginSkillsDir, entry.name), entry.name, 'plugin');
|
|
3520
|
+
if (skill) collected.push(skill);
|
|
3521
|
+
}
|
|
3522
|
+
}
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3525
|
+
// De-dupe by name, user wins over plugin (matches Copilot CLI's precedence)
|
|
3526
|
+
const seen = new Map();
|
|
3527
|
+
for (const s of collected) {
|
|
3528
|
+
const existing = seen.get(s.name);
|
|
3529
|
+
if (!existing || (s.location === 'user' && existing.location !== 'user')) {
|
|
3530
|
+
seen.set(s.name, s);
|
|
3531
|
+
}
|
|
3532
|
+
}
|
|
3533
|
+
return Array.from(seen.values()).sort((a, b) => a.name.localeCompare(b.name));
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
function _xmlEscapeForPrompt(s) {
|
|
3537
|
+
return String(s == null ? '' : s)
|
|
3538
|
+
.replace(/&/g, '&')
|
|
3539
|
+
.replace(/</g, '<')
|
|
3540
|
+
.replace(/>/g, '>');
|
|
3541
|
+
}
|
|
3542
|
+
|
|
3543
|
+
// Render a list of skills as an <available_skills> XML block matching the
|
|
3544
|
+
// shape Copilot CLI's default system prompt injects. When `skills` is empty
|
|
3545
|
+
// we still emit the block (with a sentinel comment) so the agent learns the
|
|
3546
|
+
// path exists rather than silently inferring nothing's there.
|
|
3547
|
+
function buildAvailableSkillsBlock(skills) {
|
|
3548
|
+
const list = Array.isArray(skills) ? skills : [];
|
|
3549
|
+
if (list.length === 0) {
|
|
3550
|
+
return '<available_skills>\n <!-- No skills found in ~/.copilot/skills/ or ~/.copilot/installed-plugins/<marketplace>/<plugin>/skills/ -->\n</available_skills>';
|
|
3551
|
+
}
|
|
3552
|
+
const items = list.map(s =>
|
|
3553
|
+
`<skill>\n <name>${_xmlEscapeForPrompt(s.name)}</name>\n <description>${_xmlEscapeForPrompt(s.description)}</description>\n <location>${_xmlEscapeForPrompt(s.location)}</location>\n</skill>`
|
|
3554
|
+
).join('\n');
|
|
3555
|
+
return `<available_skills>\n${items}\n</available_skills>`;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3458
3558
|
function renderCcSystemPrompt(raw, opts) {
|
|
3459
3559
|
const liveRoot = (opts && typeof opts.liveRoot === 'string') ? opts.liveRoot : MINIONS_DIR;
|
|
3460
3560
|
const turnId = (opts && typeof opts.turnId === 'string') ? opts.turnId : '';
|
|
@@ -3467,6 +3567,14 @@ function renderCcSystemPrompt(raw, opts) {
|
|
|
3467
3567
|
if (out.includes('{{cc_protected_paths}}')) {
|
|
3468
3568
|
out = out.replace(/\{\{cc_protected_paths\}\}/g, describeCcProtectedPaths(liveRoot));
|
|
3469
3569
|
}
|
|
3570
|
+
// Available-skills enumeration walks two directory trees and reads each
|
|
3571
|
+
// SKILL.md's frontmatter — only do it when the template asks for it.
|
|
3572
|
+
// Tests pass `opts.skillsHomeDir` to redirect the scan at a fixture dir.
|
|
3573
|
+
if (out.includes('{{available_skills}}')) {
|
|
3574
|
+
const skillsOpts = (opts && typeof opts.skillsHomeDir === 'string')
|
|
3575
|
+
? { homeDir: opts.skillsHomeDir } : undefined;
|
|
3576
|
+
out = out.replace(/\{\{available_skills\}\}/g, buildAvailableSkillsBlock(listCopilotSkills(skillsOpts)));
|
|
3577
|
+
}
|
|
3470
3578
|
return out
|
|
3471
3579
|
.replace(/\{\{cc_turn_id\}\}/g, turnId)
|
|
3472
3580
|
.replace(/\{\{dashboard_port\}\}/g, dashboardPort);
|
|
@@ -5418,6 +5526,8 @@ module.exports = {
|
|
|
5418
5526
|
isLiveCommandCenterPath,
|
|
5419
5527
|
describeCcProtectedPaths,
|
|
5420
5528
|
renderCcSystemPrompt,
|
|
5529
|
+
listCopilotSkills,
|
|
5530
|
+
buildAvailableSkillsBlock,
|
|
5421
5531
|
_CC_PROTECTED_BASENAMES, // exported for testing
|
|
5422
5532
|
_CC_PROTECTED_FILE_GLOBS, // exported for testing
|
|
5423
5533
|
_CC_PROTECTED_PREFIXES, // exported for testing
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2080",
|
|
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"
|
package/prompts/cc-system.md
CHANGED
|
@@ -39,6 +39,19 @@ CAN modify: notes, plans, knowledge, work items, pull-requests.json, routing.md,
|
|
|
39
39
|
## Filesystem
|
|
40
40
|
Minions state lives in `{{minions_dir}}/`. Key paths: `config.json` (config), `routing.md` (dispatch rules), `projects/{name}/work-items.json` & `pull-requests.json` (per-project), `agents/{id}/` (charters, output), `plans/` & `prd/` (plans), `knowledge/` (KB), `notes/inbox/` (inbox), `engine/dispatch.json` (queue), `playbooks/` (templates). Use tools to read specifics.
|
|
41
41
|
|
|
42
|
+
## Your skills
|
|
43
|
+
|
|
44
|
+
Your runtime is the **Copilot CLI**, not Claude Code. Your installed skills live in:
|
|
45
|
+
|
|
46
|
+
- `~/.copilot/skills/<name>/SKILL.md` — user-level skills (yours)
|
|
47
|
+
- `~/.copilot/installed-plugins/<marketplace>/<plugin>/skills/<name>/SKILL.md` — plugin-provided skills (also yours)
|
|
48
|
+
|
|
49
|
+
To invoke a skill, call your `skill` tool with the skill name (the directory name). The skill body then guides the next steps. **Both user-level and plugin-level skills are invokable the same way** — the `<location>` tag in the registry is metadata, not a permission gate. If the registry below lists a skill, you can load it.
|
|
50
|
+
|
|
51
|
+
When asked "what skills do you have", answer from the registry below — do NOT grep/glob `.claude/skills/`. Those belong to a different runtime (Claude Code) and are not available to you, even if the directory exists in cwd or `$HOME`.
|
|
52
|
+
|
|
53
|
+
{{available_skills}}
|
|
54
|
+
|
|
42
55
|
## Role: Orchestrator
|
|
43
56
|
You are primarily a dispatcher. Agents have full Claude Code + worktrees + MCP tools and are better suited for real work — but you are not hard-stopped from handling small requests yourself.
|
|
44
57
|
|