@yemi33/minions 0.1.2258 → 0.1.2260

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 CHANGED
@@ -13390,7 +13390,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13390
13390
  }
13391
13391
  }},
13392
13392
 
13393
- { method: 'POST', path: '/api/pull-requests/delete', desc: 'Remove a PR from tracking', params: 'id, project?', handler: async (req, res) => {
13393
+ { method: 'POST', path: '/api/pull-requests/delete', desc: 'Remove a PR from tracking (sets userDeleted tombstone so pollers do not re-add it)', params: 'id, project?', handler: async (req, res) => {
13394
13394
  const body = await readBody(req);
13395
13395
  const { id } = body;
13396
13396
  if (!id) return jsonReply(res, 400, { error: 'id required' });
@@ -13403,12 +13403,19 @@ What would you like to discuss or change? When you're happy, say "approve" and I
13403
13403
  let found = false;
13404
13404
  for (const prPath of prPaths) {
13405
13405
  if (found) break;
13406
- mutateJsonFileLocked(prPath, (prs) => {
13406
+ // Issue #384: use a tombstone (userDeleted:true) instead of splice so
13407
+ // the next poller cycle cannot re-add the PR. The entry stays in the
13408
+ // file but is filtered from API responses and skipped by pollers.
13409
+ shared.mutatePullRequests(prPath, (prs) => {
13407
13410
  if (!Array.isArray(prs)) return prs;
13408
- const idx = prs.findIndex(p => p.id === id);
13409
- if (idx >= 0) { prs.splice(idx, 1); found = true; }
13411
+ const pr = prs.find(p => p.id === id);
13412
+ if (pr) {
13413
+ pr.userDeleted = true;
13414
+ pr.userDeletedAt = shared.ts();
13415
+ found = true;
13416
+ }
13410
13417
  return prs;
13411
- }, { defaultValue: [] });
13418
+ });
13412
13419
  }
13413
13420
  if (!found) return jsonReply(res, 404, { error: 'PR not found' });
13414
13421
  invalidateStatusCache();
@@ -1123,7 +1123,7 @@ function cleanDispatchEntries(matchFn) {
1123
1123
  const { execFileSync } = require('child_process');
1124
1124
  execFileSync('taskkill', ['/PID', String(safePid), '/F', '/T'], { stdio: 'pipe', timeout: 5000, windowsHide: true });
1125
1125
  } else {
1126
- process.kill(safePid, 'SIGTERM');
1126
+ shared.killGracefully({ pid: safePid }, 5000);
1127
1127
  }
1128
1128
  } catch { /* may already be dead */ }
1129
1129
  }
@@ -722,7 +722,7 @@ async function executeApiStage(stage, stageState, run) {
722
722
  const timeoutMs = ENGINE_DEFAULTS.pipelineApiTimeoutMs;
723
723
 
724
724
  for (const call of calls) {
725
- const port = shared.readDashboardPortFile(MINIONS_DIR)?.port || (process.env.MINIONS_PORT && parseInt(process.env.MINIONS_PORT, 10)) || 7331;
725
+ const port = shared.readDashboardPortFile(MINIONS_DIR)?.port || 7331;
726
726
  const url = `http://localhost:${port}${call.endpoint}`;
727
727
  const body = typeof call.body === 'string' ? call.body : JSON.stringify(call.body || {});
728
728
 
package/engine/queries.js CHANGED
@@ -981,6 +981,8 @@ function getPullRequests(config) {
981
981
  const groupsById = new Map();
982
982
  for (const pr of sqlPrs) {
983
983
  if (!pr || !pr.id) continue;
984
+ // Issue #384: skip tombstoned records (user explicitly deleted)
985
+ if (pr.userDeleted === true) continue;
984
986
  if (!groupsById.has(pr.id)) {
985
987
  const g = [];
986
988
  groupsById.set(pr.id, g);
package/engine/shared.js CHANGED
@@ -5720,14 +5720,32 @@ function resolveProjectRootDir(localPath, minionsDir) {
5720
5720
  * unaffected — it uses GH_TOKEN / the OS credential store, not files under the
5721
5721
  * home — and the operator's real `~/.copilot` (interactive copilot) is untouched.
5722
5722
  *
5723
- * Placed on the MINIONS_DIR volume (copilot's session-store can grow to GBs, so
5724
- * keep it off a possibly-full system drive) but OUTSIDE the repo, at the drive
5725
- * root, so concurrent git branch switches in the working tree can never delete or
5726
- * dirty it. Stable path so copilot session resume (`--resume`) stays consistent
5727
- * across spawns. Falls back to the user home when no minionsDir is given.
5723
+ * Placed OUTSIDE the repo so concurrent git branch switches in the working tree
5724
+ * can never delete or dirty it, on the MINIONS_DIR volume (copilot's session-store
5725
+ * can grow to GBs, so keep it off a possibly-full system drive). Stable path so
5726
+ * copilot session resume (`--resume`) stays consistent across spawns. Falls back
5727
+ * to the user home when no minionsDir is given.
5728
+ *
5729
+ * Base selection is cross-platform (W-copilot-home-posix-root): on Windows the
5730
+ * MINIONS_DIR drive root (`C:\`, `D:\`, or a UNC share root) is user-writable, so
5731
+ * we anchor there. On POSIX the filesystem root (`/`) is NOT user-writable — a
5732
+ * home placed there can never be created (`mkdir` EACCES), `ensureAgentCopilotHome`
5733
+ * fail-opens and returns the uncreatable path anyway, and every spawned
5734
+ * `copilot --acp` / copilot agent then inherits a `COPILOT_HOME` it can't use and
5735
+ * exits code 1 silently (surfaced in CC as "Load failed"). So when the drive root
5736
+ * is the bare POSIX root we fall back to the user home — repo-external, writable,
5737
+ * and on the user's own volume.
5728
5738
  */
5729
5739
  function resolveAgentCopilotHome(minionsDir) {
5730
- const base = minionsDir ? path.parse(path.resolve(String(minionsDir))).root : os.homedir();
5740
+ let base;
5741
+ if (minionsDir) {
5742
+ const root = path.parse(path.resolve(String(minionsDir))).root;
5743
+ // The POSIX filesystem root ('/') is not user-writable; Windows drive/UNC
5744
+ // roots are. Anchor at the user home in the unwritable-root case.
5745
+ base = (root === '/' || root === path.sep) ? os.homedir() : root;
5746
+ } else {
5747
+ base = os.homedir();
5748
+ }
5731
5749
  return path.join(base, '.minions-agent-copilot-home');
5732
5750
  }
5733
5751
 
@@ -7003,6 +7021,13 @@ function upsertPullRequestRecord(prPath, entry, { project = null, itemId = null,
7003
7021
  mutatePullRequests(prPath, (prs) => {
7004
7022
  normalizePrRecords(prs, project);
7005
7023
  let target = findPrRecord(prs, normalizedEntry, project);
7024
+ // Issue #384: tombstoned records (userDeleted:true) must not be re-activated
7025
+ // by any poller path. Return skipped so reconcilePrs / shared-branch-reconcile
7026
+ // cannot resurrect a PR the user explicitly removed.
7027
+ if (target && target.userDeleted === true) {
7028
+ skipped = true;
7029
+ return prs;
7030
+ }
7006
7031
  if (!target && typeof beforeInsert === 'function' && beforeInsert(prs, normalizedEntry) === false) {
7007
7032
  skipped = true;
7008
7033
  return prs;
package/engine.js CHANGED
@@ -6622,6 +6622,8 @@ async function discoverFromPrs(config, project) {
6622
6622
 
6623
6623
  for (const pr of prs) {
6624
6624
  if (pr.status !== PR_STATUS.ACTIVE) continue;
6625
+ // Issue #384: skip tombstoned PRs (user explicitly deleted via dashboard)
6626
+ if (pr.userDeleted === true) continue;
6625
6627
  if (shared.isContextOnlyPrRecord(pr)) {
6626
6628
  _logPrDispatchSkipOnce(pr, 'context-only');
6627
6629
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yemi33/minions",
3
- "version": "0.1.2258",
3
+ "version": "0.1.2260",
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"