@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 CHANGED
@@ -1,12 +1,14 @@
1
1
  # Changelog
2
2
 
3
- ## 0.1.1622 (2026-04-29)
3
+ ## 0.1.1623 (2026-04-29)
4
4
 
5
5
  ### Features
6
- - harden action block parsing (#1863)
6
+ - resilient file-bug label preflight (#1840) (#1867)
7
7
 
8
- ### Other
9
- - refactor: move stream-event handling into runtime adapters
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 = '&#128027; Bug filed: <a href="' + escHtml(d10.url) + '" target="_blank" style="color:var(--blue)">' + escHtml(action.title) + '</a>';
1131
+ status.innerHTML = '&#128027; Bug filed: <a href="' + escHtml(d10.url) + '" target="_blank" style="color:var(--blue)">' + escHtml(action.title) + '</a>' + labelWarning;
1131
1132
  } else {
1132
- status.innerHTML = '&#128027; 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 = '&#128027; 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
- // Check gh CLI is available
4472
- try { shared.exec('gh --version', { encoding: 'utf-8', timeout: 5000, windowsHide: true }); }
4473
- catch { return jsonReply(res, 500, { error: 'gh CLI not installed. Run: npm install -g gh' }); }
4474
-
4475
- const repo = 'yemi33/minions';
4476
- const labels = (body.labels || ['bug']).join(',');
4477
- const bugBody = (body.description || '') + '\n\n---\n_Filed via Minions dashboard_';
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
- const msg = e.message || '';
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
 
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "runtime": "copilot",
3
3
  "models": null,
4
- "cachedAt": "2026-04-29T11:02:21.813Z"
4
+ "cachedAt": "2026-04-29T15:03:46.217Z"
5
5
  }
@@ -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.1622",
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"