@yemi33/minions 0.1.1622 → 0.1.1623
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 +6 -4
- package/dashboard/js/command-center.js +3 -2
- package/dashboard.js +10 -33
- package/engine/copilot-models.json +1 -1
- package/engine/issues.js +206 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.1.
|
|
3
|
+
## 0.1.1623 (2026-04-29)
|
|
4
4
|
|
|
5
5
|
### Features
|
|
6
|
-
-
|
|
6
|
+
- resilient file-bug label preflight (#1840) (#1867)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
## 0.1.1621 (2026-04-29)
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
- harden action block parsing (#1863)
|
|
10
12
|
|
|
11
13
|
## 0.1.1620 (2026-04-29)
|
|
12
14
|
|
|
@@ -1126,10 +1126,11 @@ async function ccExecuteAction(action, targetTabId) {
|
|
|
1126
1126
|
case 'file-bug': {
|
|
1127
1127
|
var res10 = await _ccFetch('/api/issues/create', { title: action.title, description: action.description, labels: action.labels });
|
|
1128
1128
|
var d10 = await res10.json();
|
|
1129
|
+
var labelWarning = d10.warning ? ' <span style="color:var(--orange)">(' + escHtml(d10.warning) + ')</span>' : '';
|
|
1129
1130
|
if (d10.url) {
|
|
1130
|
-
status.innerHTML = '🐛 Bug filed: <a href="' + escHtml(d10.url) + '" target="_blank" style="color:var(--blue)">' + escHtml(action.title) + '</a>';
|
|
1131
|
+
status.innerHTML = '🐛 Bug filed: <a href="' + escHtml(d10.url) + '" target="_blank" style="color:var(--blue)">' + escHtml(action.title) + '</a>' + labelWarning;
|
|
1131
1132
|
} else {
|
|
1132
|
-
status.innerHTML = '🐛 Bug filed: <strong>' + escHtml(action.title) + '</strong> — <a href="https://github.com/yemi33/minions/issues" target="_blank" style="color:var(--blue)">view issues</a>';
|
|
1133
|
+
status.innerHTML = '🐛 Bug filed: <strong>' + escHtml(action.title) + '</strong> — <a href="https://github.com/yemi33/minions/issues" target="_blank" style="color:var(--blue)">view issues</a>' + labelWarning;
|
|
1133
1134
|
}
|
|
1134
1135
|
status.style.color = 'var(--green)';
|
|
1135
1136
|
break;
|
package/dashboard.js
CHANGED
|
@@ -23,6 +23,7 @@ const queries = require('./engine/queries');
|
|
|
23
23
|
const teams = require('./engine/teams');
|
|
24
24
|
const ado = require('./engine/ado');
|
|
25
25
|
const gh = require('./engine/github');
|
|
26
|
+
const issues = require('./engine/issues');
|
|
26
27
|
const watchesMod = require('./engine/watches');
|
|
27
28
|
const os = require('os');
|
|
28
29
|
|
|
@@ -4467,40 +4468,16 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
4467
4468
|
try {
|
|
4468
4469
|
const body = await readBody(req);
|
|
4469
4470
|
if (!body.title) return jsonReply(res, 400, { error: 'title required' });
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
// Write body to temp file to avoid shell escaping issues with quotes, backticks, newlines
|
|
4480
|
-
const tmpBody = path.join(ENGINE_DIR, 'tmp', `bug-body-${Date.now()}.md`);
|
|
4481
|
-
safeWrite(tmpBody, bugBody);
|
|
4482
|
-
const safeTitle = body.title.replace(/["`$\\]/g, '');
|
|
4483
|
-
try {
|
|
4484
|
-
const cmd = `gh issue create --repo "${repo}" --title "${safeTitle}" --body-file "${tmpBody}" --label "${labels}" 2>&1`;
|
|
4485
|
-
const result = shared.exec(cmd, { encoding: 'utf-8', timeout: 30000, windowsHide: true });
|
|
4486
|
-
shared.safeUnlink(tmpBody);
|
|
4487
|
-
// Detect gh errors in output
|
|
4488
|
-
if (result.includes('authentication') || result.includes('auth login')) {
|
|
4489
|
-
return jsonReply(res, 401, { error: 'GitHub auth required. Run: gh auth login' });
|
|
4490
|
-
}
|
|
4491
|
-
const urlMatch = result.match(/https:\/\/github\.com\/\S+/);
|
|
4492
|
-
if (!urlMatch) {
|
|
4493
|
-
return jsonReply(res, 500, { error: 'Issue may not have been created: ' + result.trim().slice(0, 200) });
|
|
4494
|
-
}
|
|
4495
|
-
return jsonReply(res, 200, { ok: true, url: urlMatch[0], output: result.trim() });
|
|
4496
|
-
} catch (e) {
|
|
4497
|
-
shared.safeUnlink(tmpBody);
|
|
4498
|
-
throw e;
|
|
4499
|
-
}
|
|
4471
|
+
const result = issues.createGitHubIssue({
|
|
4472
|
+
title: body.title,
|
|
4473
|
+
description: body.description || '',
|
|
4474
|
+
labels: body.labels,
|
|
4475
|
+
repo: 'yemi33/minions',
|
|
4476
|
+
tmpDir: path.join(ENGINE_DIR, 'tmp'),
|
|
4477
|
+
});
|
|
4478
|
+
return jsonReply(res, 200, result);
|
|
4500
4479
|
} catch (e) {
|
|
4501
|
-
|
|
4502
|
-
if (msg.includes('ENOENT') || msg.includes('not found')) return jsonReply(res, 500, { error: 'gh CLI not found. Install from https://cli.github.com/' });
|
|
4503
|
-
return jsonReply(res, 500, { error: msg });
|
|
4480
|
+
return jsonReply(res, e.statusCode || 500, { error: e.message || 'Issue creation failed' });
|
|
4504
4481
|
}
|
|
4505
4482
|
}
|
|
4506
4483
|
|
package/engine/issues.js
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engine/issues.js — GitHub issue creation helpers.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { execFileSync: _execFileSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
const DEFAULT_REPO = 'yemi33/minions';
|
|
10
|
+
const DEFAULT_LABELS = ['bug'];
|
|
11
|
+
|
|
12
|
+
class GitHubIssueError extends Error {
|
|
13
|
+
constructor(message, statusCode = 500) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'GitHubIssueError';
|
|
16
|
+
this.statusCode = statusCode;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function normalizeLabels(labels, defaultLabels = DEFAULT_LABELS) {
|
|
21
|
+
let raw;
|
|
22
|
+
if (labels == null) raw = defaultLabels;
|
|
23
|
+
else if (Array.isArray(labels)) raw = labels;
|
|
24
|
+
else if (typeof labels === 'string') raw = labels.split(',');
|
|
25
|
+
else raw = [];
|
|
26
|
+
|
|
27
|
+
const seen = new Set();
|
|
28
|
+
const out = [];
|
|
29
|
+
for (const label of raw) {
|
|
30
|
+
const value = String(label || '').trim();
|
|
31
|
+
if (!value) continue;
|
|
32
|
+
const key = value.toLowerCase();
|
|
33
|
+
if (seen.has(key)) continue;
|
|
34
|
+
seen.add(key);
|
|
35
|
+
out.push(value);
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ghMessage(err) {
|
|
41
|
+
if (!err) return '';
|
|
42
|
+
return [err.message, err.stdout, err.stderr]
|
|
43
|
+
.filter(Boolean)
|
|
44
|
+
.map(String)
|
|
45
|
+
.join('\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function conciseGhMessage(errOrText) {
|
|
49
|
+
const text = typeof errOrText === 'string' ? errOrText : ghMessage(errOrText);
|
|
50
|
+
return text.replace(/\s+/g, ' ').trim().slice(0, 240);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function isAuthError(errOrText) {
|
|
54
|
+
return /(authentication|auth login|not authenticated|bad credentials|http 401|requires authentication)/i.test(
|
|
55
|
+
typeof errOrText === 'string' ? errOrText : ghMessage(errOrText)
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isLabelUnavailableError(errOrText) {
|
|
60
|
+
const msg = typeof errOrText === 'string' ? errOrText : ghMessage(errOrText);
|
|
61
|
+
return /label/i.test(msg) && /(not found|not exist|unavailable|invalid|could not add|could not resolve|does not exist)/i.test(msg);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function extractIssueUrl(output) {
|
|
65
|
+
const match = String(output || '').match(/https:\/\/github\.com\/\S+\/issues\/\d+/);
|
|
66
|
+
return match ? match[0] : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function runGh(execFileSync, args, timeout) {
|
|
70
|
+
return execFileSync('gh', args, {
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
timeout,
|
|
73
|
+
windowsHide: true,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function listRepoLabels({ repo, execFileSync }) {
|
|
78
|
+
const output = runGh(execFileSync, ['label', 'list', '--repo', repo, '--json', 'name', '--limit', '1000'], 15000);
|
|
79
|
+
const parsed = JSON.parse(output || '[]');
|
|
80
|
+
if (!Array.isArray(parsed)) {
|
|
81
|
+
throw new GitHubIssueError('GitHub label list returned an unexpected response shape');
|
|
82
|
+
}
|
|
83
|
+
const labelsByLower = new Map();
|
|
84
|
+
for (const item of parsed) {
|
|
85
|
+
if (!item || typeof item.name !== 'string') continue;
|
|
86
|
+
labelsByLower.set(item.name.toLowerCase(), item.name);
|
|
87
|
+
}
|
|
88
|
+
return labelsByLower;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveLabels({ labels, repo, execFileSync }) {
|
|
92
|
+
const requested = normalizeLabels(labels);
|
|
93
|
+
if (requested.length === 0) {
|
|
94
|
+
return { requested, labelsToApply: [], labelsSkipped: [], validationUnavailable: false };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const available = listRepoLabels({ repo, execFileSync });
|
|
99
|
+
const labelsToApply = [];
|
|
100
|
+
const labelsSkipped = [];
|
|
101
|
+
for (const label of requested) {
|
|
102
|
+
const matched = available.get(label.toLowerCase());
|
|
103
|
+
if (matched) labelsToApply.push(matched);
|
|
104
|
+
else labelsSkipped.push(label);
|
|
105
|
+
}
|
|
106
|
+
return { requested, labelsToApply, labelsSkipped, validationUnavailable: false };
|
|
107
|
+
} catch (e) {
|
|
108
|
+
if (e instanceof GitHubIssueError) throw e;
|
|
109
|
+
if (isAuthError(e)) throw new GitHubIssueError('GitHub auth required. Run: gh auth login', 401);
|
|
110
|
+
return { requested, labelsToApply: requested, labelsSkipped: [], validationUnavailable: true };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildWarning(labelsSkipped, filedWithoutLabels) {
|
|
115
|
+
if (!labelsSkipped.length) return undefined;
|
|
116
|
+
const base = `Skipped unavailable GitHub label(s): ${labelsSkipped.join(', ')}.`;
|
|
117
|
+
return filedWithoutLabels ? `${base} Filed without labels.` : base;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function createIssueWithLabels({ title, bodyFile, repo, labels, execFileSync }) {
|
|
121
|
+
const args = ['issue', 'create', '--repo', repo, '--title', title, '--body-file', bodyFile];
|
|
122
|
+
if (labels.length > 0) args.push('--label', labels.join(','));
|
|
123
|
+
const output = runGh(execFileSync, args, 30000);
|
|
124
|
+
const url = extractIssueUrl(output);
|
|
125
|
+
if (!url) {
|
|
126
|
+
throw new GitHubIssueError(`Issue may not have been created: ${conciseGhMessage(output)}`);
|
|
127
|
+
}
|
|
128
|
+
return { url, output: String(output || '').trim() };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createGitHubIssue({
|
|
132
|
+
title,
|
|
133
|
+
description = '',
|
|
134
|
+
labels,
|
|
135
|
+
repo = DEFAULT_REPO,
|
|
136
|
+
tmpDir,
|
|
137
|
+
execFileSync = _execFileSync,
|
|
138
|
+
} = {}) {
|
|
139
|
+
if (!title) throw new GitHubIssueError('title required', 400);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
runGh(execFileSync, ['--version'], 5000);
|
|
143
|
+
} catch (e) {
|
|
144
|
+
throw new GitHubIssueError('gh CLI not found. Install from https://cli.github.com/');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const issueBody = `${description || ''}\n\n---\n_Filed via Minions dashboard_`;
|
|
148
|
+
const dir = tmpDir || path.join(__dirname, 'tmp');
|
|
149
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
150
|
+
const bodyFile = path.join(dir, `bug-body-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.md`);
|
|
151
|
+
fs.writeFileSync(bodyFile, issueBody);
|
|
152
|
+
|
|
153
|
+
let resolved;
|
|
154
|
+
try {
|
|
155
|
+
resolved = resolveLabels({ labels, repo, execFileSync });
|
|
156
|
+
const created = createIssueWithLabels({
|
|
157
|
+
title,
|
|
158
|
+
bodyFile,
|
|
159
|
+
repo,
|
|
160
|
+
labels: resolved.labelsToApply,
|
|
161
|
+
execFileSync,
|
|
162
|
+
});
|
|
163
|
+
const filedWithoutLabels = resolved.requested.length > 0 && resolved.labelsToApply.length === 0;
|
|
164
|
+
return {
|
|
165
|
+
ok: true,
|
|
166
|
+
url: created.url,
|
|
167
|
+
output: created.output,
|
|
168
|
+
labelsRequested: resolved.requested,
|
|
169
|
+
labelsApplied: resolved.labelsToApply,
|
|
170
|
+
labelsSkipped: resolved.labelsSkipped,
|
|
171
|
+
warning: buildWarning(resolved.labelsSkipped, filedWithoutLabels),
|
|
172
|
+
};
|
|
173
|
+
} catch (e) {
|
|
174
|
+
if (e instanceof GitHubIssueError) throw e;
|
|
175
|
+
if (isAuthError(e)) throw new GitHubIssueError('GitHub auth required. Run: gh auth login', 401);
|
|
176
|
+
if (resolved && resolved.labelsToApply.length > 0 && isLabelUnavailableError(e)) {
|
|
177
|
+
try {
|
|
178
|
+
const created = createIssueWithLabels({ title, bodyFile, repo, labels: [], execFileSync });
|
|
179
|
+
const skipped = normalizeLabels([...resolved.labelsSkipped, ...resolved.labelsToApply], []);
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
url: created.url,
|
|
183
|
+
output: created.output,
|
|
184
|
+
labelsRequested: resolved.requested,
|
|
185
|
+
labelsApplied: [],
|
|
186
|
+
labelsSkipped: skipped,
|
|
187
|
+
warning: buildWarning(skipped, true),
|
|
188
|
+
};
|
|
189
|
+
} catch (retryErr) {
|
|
190
|
+
if (isAuthError(retryErr)) throw new GitHubIssueError('GitHub auth required. Run: gh auth login', 401);
|
|
191
|
+
throw new GitHubIssueError(`GitHub issue creation failed after retrying without labels: ${conciseGhMessage(retryErr)}`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
throw new GitHubIssueError(`GitHub issue creation failed: ${conciseGhMessage(e)}`);
|
|
195
|
+
} finally {
|
|
196
|
+
try { fs.unlinkSync(bodyFile); } catch {}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
module.exports = {
|
|
201
|
+
DEFAULT_LABELS,
|
|
202
|
+
GitHubIssueError,
|
|
203
|
+
normalizeLabels,
|
|
204
|
+
isLabelUnavailableError,
|
|
205
|
+
createGitHubIssue,
|
|
206
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yemi33/minions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1623",
|
|
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"
|