@yemi33/minions 0.1.2304 → 0.1.2306
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 +17 -13
- package/dashboard/js/render-other.js +251 -3
- package/dashboard/js/render-plans.js +1 -1
- package/dashboard/js/render-prs.js +4 -1
- package/dashboard/js/render-utils.js +13 -0
- package/dashboard/js/render-work-items.js +13 -14
- package/dashboard/js/settings.js +3 -3
- package/dashboard/layout.html +13 -0
- package/dashboard/slim/js/modals-tiles.js +21 -130
- package/dashboard/slim/styles.css +9 -14
- package/dashboard/styles.css +79 -0
- package/dashboard.js +41 -9
- package/docs/completion-reports.md +25 -0
- package/docs/copilot-cli-schema.md +31 -1
- package/docs/design-state-storage.md +1 -1
- package/docs/live-checkout-mode.md +5 -3
- package/engine/ado.js +37 -0
- package/engine/consolidation.js +59 -2
- package/engine/lifecycle.js +149 -0
- package/engine/live-checkout.js +290 -79
- package/engine/playbook.js +2 -2
- package/engine/queries.js +11 -7
- package/engine/shared.js +6 -5
- package/engine/supervisor.js +131 -19
- package/engine.js +2 -2
- package/package.json +1 -1
|
@@ -123,135 +123,23 @@
|
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
head.setAttribute('tabindex', '0');
|
|
144
|
-
head.setAttribute('aria-expanded', 'false');
|
|
145
|
-
var caret = document.createElement('span');
|
|
146
|
-
caret.className = 'tile-caret';
|
|
147
|
-
caret.textContent = '▸';
|
|
148
|
-
var label = document.createElement('span');
|
|
149
|
-
label.className = 'tile-completed-section-title';
|
|
150
|
-
label.textContent = 'Completed work (' + completed.length + ')';
|
|
151
|
-
head.appendChild(caret);
|
|
152
|
-
head.appendChild(label);
|
|
153
|
-
|
|
154
|
-
var listWrap = document.createElement('div');
|
|
155
|
-
listWrap.className = 'tile-completed-section-body';
|
|
156
|
-
listWrap.hidden = true;
|
|
157
|
-
|
|
158
|
-
if (!completed.length) {
|
|
159
|
-
tileEmpty(listWrap, 'No completed work yet.');
|
|
160
|
-
} else {
|
|
161
|
-
completed.forEach(function(c) {
|
|
162
|
-
var who = c.agentName || c.agent || c.dispatched_to || '—';
|
|
163
|
-
var when = c.completed_at || c.completedAt || c.endedAt || c.started_at || c.startedAt;
|
|
164
|
-
var status = completionStatus(c);
|
|
165
|
-
var chip = status === 'ok' ? { text: 'done', cls: 'green' }
|
|
166
|
-
: status === 'warn' ? { text: 'partial', cls: 'amber' }
|
|
167
|
-
: { text: 'failed', cls: 'red' };
|
|
168
|
-
listWrap.appendChild(tileCard({
|
|
169
|
-
title: completionTitle(c),
|
|
170
|
-
meta: who + (when ? ' · completed ' + relTime(when) : ''),
|
|
171
|
-
chip: chip,
|
|
172
|
-
}));
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function toggle() {
|
|
177
|
-
var open = !section.classList.toggle('collapsed');
|
|
178
|
-
head.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
179
|
-
caret.textContent = open ? '▾' : '▸';
|
|
180
|
-
listWrap.hidden = !open;
|
|
181
|
-
}
|
|
182
|
-
head.addEventListener('click', toggle);
|
|
183
|
-
head.addEventListener('keydown', function(ev) {
|
|
184
|
-
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); toggle(); }
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
section.appendChild(head);
|
|
188
|
-
section.appendChild(listWrap);
|
|
189
|
-
body.appendChild(section);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Collapsible "Pending work items" section appended to the queued popout
|
|
193
|
-
// (W-mqv1xbcm). Sources data.workItems filtered to status === 'pending' — the
|
|
194
|
-
// same /api/work-items slice the slim poll merges onto the snapshot — so it
|
|
195
|
-
// adds no new API calls and refreshes whenever the popout is re-opened.
|
|
196
|
-
// Expanded by DEFAULT (more prominent than the collapsed Completed section)
|
|
197
|
-
// since these are the queued WIs the operator most wants to scan. Mirrors
|
|
198
|
-
// renderCompletedSection's structure + tileCard usage; all text via
|
|
199
|
-
// textContent (no innerHTML) to keep the SEC-03 baseline clean. Each card
|
|
200
|
-
// shows title, id, project (project || _source), type, and the engine's
|
|
201
|
-
// _pendingReason as a chip when set.
|
|
202
|
-
function renderPendingSection(body, data) {
|
|
203
|
-
var pending = (Array.isArray(data.workItems) ? data.workItems : []).filter(function(w) {
|
|
204
|
-
return w && w.status === 'pending';
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
var section = document.createElement('div');
|
|
208
|
-
section.className = 'tile-completed-section';
|
|
209
|
-
|
|
210
|
-
var head = document.createElement('div');
|
|
211
|
-
head.className = 'tile-completed-section-head';
|
|
212
|
-
head.setAttribute('role', 'button');
|
|
213
|
-
head.setAttribute('tabindex', '0');
|
|
214
|
-
head.setAttribute('aria-expanded', 'true');
|
|
215
|
-
var caret = document.createElement('span');
|
|
216
|
-
caret.className = 'tile-caret';
|
|
217
|
-
caret.textContent = '▾';
|
|
218
|
-
var label = document.createElement('span');
|
|
219
|
-
label.className = 'tile-completed-section-title';
|
|
220
|
-
label.textContent = 'Pending work items (' + pending.length + ')';
|
|
221
|
-
head.appendChild(caret);
|
|
222
|
-
head.appendChild(label);
|
|
223
|
-
|
|
224
|
-
var listWrap = document.createElement('div');
|
|
225
|
-
listWrap.className = 'tile-completed-section-body';
|
|
226
|
-
|
|
227
|
-
if (!pending.length) {
|
|
228
|
-
tileEmpty(listWrap, 'No pending work items.');
|
|
229
|
-
} else {
|
|
230
|
-
pending.forEach(function(w) {
|
|
231
|
-
var project = w.project || w._source;
|
|
232
|
-
var meta = [w.id || null, project || null, w.type || null].filter(Boolean).join(' · ');
|
|
233
|
-
var card = { title: w.title || w.id || '(untitled)', meta: meta };
|
|
234
|
-
if (w._pendingReason) {
|
|
235
|
-
card.chip = { text: String(w._pendingReason).replace(/_/g, ' '), cls: 'amber' };
|
|
236
|
-
}
|
|
237
|
-
listWrap.appendChild(tileCard(card));
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function toggle() {
|
|
242
|
-
var open = !section.classList.toggle('collapsed');
|
|
243
|
-
head.setAttribute('aria-expanded', open ? 'true' : 'false');
|
|
244
|
-
caret.textContent = open ? '▾' : '▸';
|
|
245
|
-
listWrap.hidden = !open;
|
|
246
|
-
}
|
|
247
|
-
head.addEventListener('click', toggle);
|
|
248
|
-
head.addEventListener('keydown', function(ev) {
|
|
249
|
-
if (ev.key === 'Enter' || ev.key === ' ') { ev.preventDefault(); toggle(); }
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
section.appendChild(head);
|
|
253
|
-
section.appendChild(listWrap);
|
|
254
|
-
body.appendChild(section);
|
|
126
|
+
// Queued-work tile body — reuses the LITERAL classic dashboard Work Items
|
|
127
|
+
// screen instead of a slim-specific list, so there is one work-items UI, not
|
|
128
|
+
// two (W-mqrdggys000f94a4 / "reuse, don't fork"). Slim and classic are two
|
|
129
|
+
// separate IIFE bundles with no shared scope, so we can't call the classic
|
|
130
|
+
// renderWorkItems() in-process; instead we embed the real /work screen in an
|
|
131
|
+
// iframe with the chrome-off ?embed=1 mode. The iframed page IS the classic
|
|
132
|
+
// screen — full filter/sort + every row action (edit/cancel/retry/archive/
|
|
133
|
+
// delete/feedback), backed by the same /api/work-items data + endpoints —
|
|
134
|
+
// with zero duplicated rendering logic. Classic is reachable at /work even
|
|
135
|
+
// with slim-ux ON (only / is taken over). Built with createElement (no
|
|
136
|
+
// innerHTML) to satisfy the dashboard no-unsanitized lint gate.
|
|
137
|
+
function renderQueuedWorkBody(body) {
|
|
138
|
+
var frame = document.createElement('iframe');
|
|
139
|
+
frame.className = 'slim-work-embed';
|
|
140
|
+
frame.src = '/work?embed=1';
|
|
141
|
+
frame.title = 'Work Items';
|
|
142
|
+
body.appendChild(frame);
|
|
255
143
|
}
|
|
256
144
|
|
|
257
145
|
function renderPrTileBody(body, data) {
|
|
@@ -375,7 +263,7 @@
|
|
|
375
263
|
var TILE_VIEWS = {
|
|
376
264
|
engine: { title: 'Engine', render: renderEngineTileBody },
|
|
377
265
|
dispatches: { title: 'Active dispatches', render: function(body, data) { renderDispatchTileBody(body, data, true); } },
|
|
378
|
-
queued: { title: 'Queued work', render: function(body
|
|
266
|
+
queued: { title: 'Queued work', render: function(body) { renderQueuedWorkBody(body); } },
|
|
379
267
|
prs: { title: 'Pull requests', render: renderPrTileBody },
|
|
380
268
|
watches: { title: 'Watches', render: renderWatchTileBody },
|
|
381
269
|
};
|
|
@@ -400,6 +288,9 @@
|
|
|
400
288
|
// The "+ Link PR" header chip only applies to the PR list.
|
|
401
289
|
var headerChip = document.getElementById('slim-tile-modal-linkpr');
|
|
402
290
|
if (headerChip) headerChip.style.display = (key === 'prs') ? '' : 'none';
|
|
291
|
+
// The queued tile embeds the full classic /work screen in an iframe — widen
|
|
292
|
+
// the modal + drop the body padding so the embedded table gets real estate.
|
|
293
|
+
modal.classList.toggle('tile-modal--work', key === 'queued');
|
|
403
294
|
view.render(body, lastStatusData || {});
|
|
404
295
|
modal.classList.add('open');
|
|
405
296
|
}
|
|
@@ -680,6 +680,15 @@
|
|
|
680
680
|
/* Cockpit-tile detail modal: list of dispatches / PRs / watches, or a
|
|
681
681
|
key/value engine readout (reuses .agent-detail-row). */
|
|
682
682
|
#slim-tile-modal .modal { width: 720px; max-width: calc(100vw - 32px); }
|
|
683
|
+
/* W-mqrdggys000f94a4 — the Queued-work tile embeds the full classic /work
|
|
684
|
+
screen in an iframe; widen the modal + remove body padding so the
|
|
685
|
+
embedded work-items table gets the full width/height. */
|
|
686
|
+
#slim-tile-modal.tile-modal--work .modal { width: 1180px; }
|
|
687
|
+
#slim-tile-modal.tile-modal--work .modal-body { padding: 0; }
|
|
688
|
+
.slim-work-embed {
|
|
689
|
+
display: block; width: 100%; height: calc(100vh - 160px); min-height: 360px;
|
|
690
|
+
border: 0; background: var(--bg);
|
|
691
|
+
}
|
|
683
692
|
.tile-item {
|
|
684
693
|
display: block;
|
|
685
694
|
text-decoration: none;
|
|
@@ -712,20 +721,6 @@
|
|
|
712
721
|
.tile-chip.blue { background: rgba(88, 166, 255, 0.12); color: var(--blue); border-color: var(--blue); }
|
|
713
722
|
.tile-empty { color: var(--muted); font-style: italic; font-size: var(--text-md); }
|
|
714
723
|
|
|
715
|
-
/* Collapsible "Completed work" section in the queued popout. Collapsed by
|
|
716
|
-
default; the caret rotates and the body unhides on toggle. */
|
|
717
|
-
.tile-completed-section { margin-top: 12px; border-top: 1px solid var(--border); padding-top: 10px; }
|
|
718
|
-
.tile-completed-section-head {
|
|
719
|
-
display: flex; align-items: center; gap: 6px;
|
|
720
|
-
cursor: pointer; user-select: none;
|
|
721
|
-
font-size: var(--text-md); font-weight: 600; color: var(--text);
|
|
722
|
-
padding: 2px 0;
|
|
723
|
-
}
|
|
724
|
-
.tile-completed-section-head:hover { color: var(--blue); }
|
|
725
|
-
.tile-completed-section-head:focus-visible { outline: 1px solid var(--blue); outline-offset: 2px; border-radius: var(--radius); }
|
|
726
|
-
.tile-caret { font-size: var(--text-sm); color: var(--muted); width: 12px; display: inline-block; text-align: center; }
|
|
727
|
-
.tile-completed-section-body { margin-top: 8px; }
|
|
728
|
-
|
|
729
724
|
/* Collapsible PR sections (Active / Merged-Completed / Abandoned). Uses
|
|
730
725
|
native <details>/<summary> — no JS wiring needed for the toggle. */
|
|
731
726
|
.tile-section { margin-bottom: 10px; }
|
package/dashboard/styles.css
CHANGED
|
@@ -1288,6 +1288,64 @@
|
|
|
1288
1288
|
.project-mode-live { background: rgba(210,153,34,0.15); color: var(--yellow); border: 1px solid var(--yellow); }
|
|
1289
1289
|
.project-mode-hybrid { background: rgba(88,166,255,0.15); color: var(--blue); border: 1px solid var(--blue); }
|
|
1290
1290
|
|
|
1291
|
+
/* Clickable checkout-mode pill (W-mr1b67zi0006b788) — swaps the "help"
|
|
1292
|
+
* cursor for "pointer" and adds a hover/focus highlight so it reads as
|
|
1293
|
+
* interactive, opening the picker in _openCheckoutModeMenu(). */
|
|
1294
|
+
.project-mode-pill-clickable { cursor: pointer; }
|
|
1295
|
+
.project-mode-pill-clickable:hover,
|
|
1296
|
+
.project-mode-pill-clickable:focus-visible {
|
|
1297
|
+
filter: brightness(1.2);
|
|
1298
|
+
outline: 1px solid var(--blue);
|
|
1299
|
+
outline-offset: 1px;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
/* Checkout-mode picker popover (W-mr1b67zi0006b788). Rendered via
|
|
1303
|
+
* document.createElement in _openCheckoutModeMenu (no innerHTML), appended
|
|
1304
|
+
* to <body> and positioned under the clicked pill. */
|
|
1305
|
+
.checkout-mode-menu {
|
|
1306
|
+
z-index: 1000;
|
|
1307
|
+
min-width: 220px;
|
|
1308
|
+
background: var(--surface2);
|
|
1309
|
+
border: 1px solid var(--border);
|
|
1310
|
+
border-radius: var(--radius-md, 6px);
|
|
1311
|
+
box-shadow: 0 4px 16px rgba(0,0,0,0.3);
|
|
1312
|
+
padding: 6px;
|
|
1313
|
+
font-size: var(--text-sm);
|
|
1314
|
+
}
|
|
1315
|
+
.checkout-mode-menu-title {
|
|
1316
|
+
font-weight: 600; color: var(--muted);
|
|
1317
|
+
padding: 4px 8px 6px; border-bottom: 1px solid var(--border);
|
|
1318
|
+
margin-bottom: 4px;
|
|
1319
|
+
}
|
|
1320
|
+
.checkout-mode-menu-item {
|
|
1321
|
+
padding: 6px 8px; border-radius: 4px; cursor: pointer;
|
|
1322
|
+
color: var(--text);
|
|
1323
|
+
}
|
|
1324
|
+
.checkout-mode-menu-item:hover,
|
|
1325
|
+
.checkout-mode-menu-item:focus {
|
|
1326
|
+
background: var(--surface); outline: none;
|
|
1327
|
+
}
|
|
1328
|
+
.checkout-mode-menu-item-active { color: var(--blue); font-weight: 600; }
|
|
1329
|
+
.checkout-mode-menu-item-desc {
|
|
1330
|
+
font-size: var(--text-xs); color: var(--muted); font-weight: 400;
|
|
1331
|
+
margin-top: 2px; line-height: 1.35;
|
|
1332
|
+
}
|
|
1333
|
+
.checkout-mode-menu-select {
|
|
1334
|
+
width: 100%; margin: 4px 0 8px; padding: 4px 6px;
|
|
1335
|
+
background: var(--surface); color: var(--text); border: 1px solid var(--border);
|
|
1336
|
+
border-radius: 4px;
|
|
1337
|
+
}
|
|
1338
|
+
.checkout-mode-menu-actions {
|
|
1339
|
+
display: flex; justify-content: flex-end; gap: 6px; padding-top: 4px;
|
|
1340
|
+
}
|
|
1341
|
+
.checkout-mode-menu-btn {
|
|
1342
|
+
padding: 4px 10px; border-radius: 4px; border: 1px solid var(--border);
|
|
1343
|
+
background: var(--surface); color: var(--text); cursor: pointer; font-size: var(--text-sm);
|
|
1344
|
+
}
|
|
1345
|
+
.checkout-mode-menu-btn-primary {
|
|
1346
|
+
background: var(--blue); color: #fff; border-color: var(--blue);
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1291
1349
|
/* QA tab (W-mpeiwz6k0005bf34-d) — targets / runbooks / runs sections.
|
|
1292
1350
|
* Reuses surface/border/text tokens defined in :root so the QA page
|
|
1293
1351
|
* blends with the rest of the dashboard. */
|
|
@@ -1547,3 +1605,24 @@
|
|
|
1547
1605
|
.cc-error strong { color: var(--red); }
|
|
1548
1606
|
.cc-error code { font-family: monospace; font-size: var(--text-xs);
|
|
1549
1607
|
color: var(--muted); }
|
|
1608
|
+
|
|
1609
|
+
/* W-mqrdggys000f94a4 — chrome-off "embed" mode (?embed=1). Strips the global
|
|
1610
|
+
dashboard chrome so a single page (e.g. /work?embed=1) can be iframed inside
|
|
1611
|
+
the slim cockpit's Queued-work modal and render as just its panel. The
|
|
1612
|
+
html.embed class is applied in layout.html's head before first paint. */
|
|
1613
|
+
html.embed body > header,
|
|
1614
|
+
html.embed #projects-bar,
|
|
1615
|
+
html.embed #setup-banner,
|
|
1616
|
+
html.embed #fre-banner,
|
|
1617
|
+
html.embed .engine-alert,
|
|
1618
|
+
html.embed .paused-banner,
|
|
1619
|
+
html.embed .sidebar,
|
|
1620
|
+
html.embed #cc-drawer,
|
|
1621
|
+
html.embed #cc-overlay { display: none !important; }
|
|
1622
|
+
/* Do NOT override .page-layout to display:block here — that detaches
|
|
1623
|
+
.page-content from its bounded-height flex parent, so it grows to full
|
|
1624
|
+
content height and the fixed-height iframe clips it with no scrollbar (the
|
|
1625
|
+
Queued-work popup "doesn't scroll"). The sidebar is hidden above, so the
|
|
1626
|
+
base .page-layout flex row already gives .page-content full width plus a
|
|
1627
|
+
bounded height to scroll within. */
|
|
1628
|
+
html.embed .page-content { padding: 12px 16px; overflow-y: auto; }
|
package/dashboard.js
CHANGED
|
@@ -1350,12 +1350,15 @@ function linkPullRequestForTracking({ url, title, project: projectName, contextO
|
|
|
1350
1350
|
// W-mpmwxkzm0009ba0b — Per-row auto-observe toggle backing helper for
|
|
1351
1351
|
// POST /api/pull-requests/observe. Flips canonical `contextOnly` on an
|
|
1352
1352
|
// existing tracked PR record under a lock (per CLAUDE.md mutate convention).
|
|
1353
|
-
// Body shape: { host: 'github'|'ado', slug, number,
|
|
1354
|
-
//
|
|
1355
|
-
// (W-mq5s5ttx000j7ab8-c)
|
|
1353
|
+
// Body shape: { host: 'github'|'ado', slug, number, contextOnly: boolean }.
|
|
1354
|
+
// `contextOnly` is canonical and read first; the legacy `observe` body
|
|
1355
|
+
// param (W-mq5s5ttx000j7ab8-c) is accepted as a documented back-compat
|
|
1356
|
+
// fallback (`contextOnly = !observe`) for any caller that still sends it —
|
|
1357
|
+
// the dashboard client itself now sends `contextOnly` exclusively
|
|
1358
|
+
// (P-2b6e4d81; see docs/deprecated.json#pr-observe-observe-body-param).
|
|
1356
1359
|
// Returns the updated record + the PR path that was touched. Throws an
|
|
1357
1360
|
// Error with `statusCode` for the route handler to map to an HTTP status.
|
|
1358
|
-
function updatePullRequestObserveFlag({ host, slug, number, observe } = {}, config = CONFIG, minionsDir = MINIONS_DIR) {
|
|
1361
|
+
function updatePullRequestObserveFlag({ host, slug, number, observe, contextOnly } = {}, config = CONFIG, minionsDir = MINIONS_DIR) {
|
|
1359
1362
|
const hostStr = String(host || '').trim().toLowerCase();
|
|
1360
1363
|
const slugStr = String(slug || '').trim();
|
|
1361
1364
|
const numberInt = Number.parseInt(number, 10);
|
|
@@ -1374,8 +1377,13 @@ function updatePullRequestObserveFlag({ host, slug, number, observe } = {}, conf
|
|
|
1374
1377
|
err.statusCode = 400;
|
|
1375
1378
|
throw err;
|
|
1376
1379
|
}
|
|
1377
|
-
|
|
1378
|
-
|
|
1380
|
+
let contextOnlyValue;
|
|
1381
|
+
if (typeof contextOnly === 'boolean') {
|
|
1382
|
+
contextOnlyValue = contextOnly;
|
|
1383
|
+
} else if (typeof observe === 'boolean') {
|
|
1384
|
+
contextOnlyValue = !observe;
|
|
1385
|
+
} else {
|
|
1386
|
+
const err = new Error('contextOnly (or legacy observe) must be a boolean');
|
|
1379
1387
|
err.statusCode = 400;
|
|
1380
1388
|
throw err;
|
|
1381
1389
|
}
|
|
@@ -1394,7 +1402,7 @@ function updatePullRequestObserveFlag({ host, slug, number, observe } = {}, conf
|
|
|
1394
1402
|
shared.mutatePullRequests(prPath, (prs) => {
|
|
1395
1403
|
const pr = prs.find(p => p && p.id === canonicalId);
|
|
1396
1404
|
if (!pr) return prs;
|
|
1397
|
-
pr.contextOnly =
|
|
1405
|
+
pr.contextOnly = contextOnlyValue;
|
|
1398
1406
|
updated = { id: pr.id, contextOnly: pr.contextOnly };
|
|
1399
1407
|
updatedPath = prPath;
|
|
1400
1408
|
return prs;
|
|
@@ -13086,6 +13094,11 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13086
13094
|
const inputs = [
|
|
13087
13095
|
CONFIG_PATH,
|
|
13088
13096
|
path.join(ENGINE_DIR, 'dispatch.json'),
|
|
13097
|
+
// Central work-items.json at MINIONS_DIR root — backs the 'central'
|
|
13098
|
+
// scope (schedule/pipeline/CC-created rootless WIs, per
|
|
13099
|
+
// work-items-store.js#_filePathForScope). Without this, a new
|
|
13100
|
+
// central-scope WI never busts the ETag until dispatch.json changes.
|
|
13101
|
+
shared.centralWorkItemsPath(MINIONS_DIR),
|
|
13089
13102
|
];
|
|
13090
13103
|
for (const p of projects) {
|
|
13091
13104
|
if (p && p.name) {
|
|
@@ -13106,7 +13119,13 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13106
13119
|
{ method: 'GET', path: '/api/pull-requests', desc: 'Fully-enriched pull requests (per-project files joined + url backfill + _project stamp)', handler: (req, res) => {
|
|
13107
13120
|
const config = queries.getConfig();
|
|
13108
13121
|
const projects = config.projects || [];
|
|
13109
|
-
const inputs = [
|
|
13122
|
+
const inputs = [
|
|
13123
|
+
CONFIG_PATH,
|
|
13124
|
+
// Central pull-requests.json at MINIONS_DIR root — backs the
|
|
13125
|
+
// 'central' scope per pull-requests-store.js#_filePathForScope.
|
|
13126
|
+
// Mirrors the same class of staleness fix as /api/work-items.
|
|
13127
|
+
shared.centralPullRequestsPath(MINIONS_DIR),
|
|
13128
|
+
];
|
|
13110
13129
|
for (const p of projects) {
|
|
13111
13130
|
if (p && p.name) inputs.push(path.join(MINIONS_DIR, 'projects', p.name, 'pull-requests.json'));
|
|
13112
13131
|
}
|
|
@@ -13137,6 +13156,10 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13137
13156
|
const inputs = [
|
|
13138
13157
|
path.join(ENGINE_DIR, 'metrics.json'),
|
|
13139
13158
|
path.join(ENGINE_DIR, 'dispatch.json'),
|
|
13159
|
+
// getMetrics() enriches from getPullRequests(), which includes the
|
|
13160
|
+
// central pull-requests.json scope — include it here too so a new
|
|
13161
|
+
// central-scope PR busts the ETag (same class of bug as /api/work-items).
|
|
13162
|
+
shared.centralPullRequestsPath(MINIONS_DIR),
|
|
13140
13163
|
];
|
|
13141
13164
|
for (const p of projects) {
|
|
13142
13165
|
if (p && p.name) inputs.push(path.join(MINIONS_DIR, 'projects', p.name, 'pull-requests.json'));
|
|
@@ -13721,7 +13744,7 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
13721
13744
|
}
|
|
13722
13745
|
}},
|
|
13723
13746
|
|
|
13724
|
-
{ method: 'POST', path: '/api/pull-requests/observe', desc: 'Toggle canonical contextOnly flag on a tracked PR (
|
|
13747
|
+
{ method: 'POST', path: '/api/pull-requests/observe', desc: 'Toggle canonical contextOnly flag on a tracked PR (legacy `observe` body param is preserved as the inverse for backward compat)', params: 'host (github|ado), slug, number, contextOnly (boolean; legacy observe (boolean) also accepted)', handler: async (req, res) => {
|
|
13725
13748
|
const body = await readBody(req);
|
|
13726
13749
|
reloadConfig();
|
|
13727
13750
|
try {
|
|
@@ -14463,6 +14486,15 @@ What would you like to discuss or change? When you're happy, say "approve" and I
|
|
|
14463
14486
|
"img-src 'self' data:; " +
|
|
14464
14487
|
"connect-src 'self'"
|
|
14465
14488
|
);
|
|
14489
|
+
// The Slim UX "Queued work" tile (W-mqrdggys000f94a4) embeds this classic SPA
|
|
14490
|
+
// at /work?embed=1 in a SAME-ORIGIN iframe. buildSecurityHeaders() stamped
|
|
14491
|
+
// X-Frame-Options: DENY on every response above, which makes the browser
|
|
14492
|
+
// refuse to render the iframe ("localhost refused to connect"). Relax to
|
|
14493
|
+
// SAMEORIGIN for the chrome-off embed request only — the standalone classic
|
|
14494
|
+
// dashboard keeps DENY. Mirrors serveSlimUx's embeddable relaxation for /slim.
|
|
14495
|
+
if (/[?&]embed=1(?:&|$)/.test(req.url)) {
|
|
14496
|
+
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
14497
|
+
}
|
|
14466
14498
|
if (req.headers['if-none-match'] === HTML_ETAG) {
|
|
14467
14499
|
res.statusCode = 304;
|
|
14468
14500
|
res.end();
|
|
@@ -85,6 +85,7 @@ Do **not** invent, regenerate, or share the nonce across dispatches — each spa
|
|
|
85
85
|
| `tests` | string | `pass`, `fail`, `skipped`, `N/A`, or a free-form note like `skipped — relying on PR pipeline`. |
|
|
86
86
|
| `pending` | string | Any remaining work, or `none`. |
|
|
87
87
|
| `followups` | array | Optional. PR-comment follow-up work items the agent dispatched via `POST /api/work-items` with `meta.pr_followup` set. Each entry: `{wi_id, title, reason, parent_comment_id}`. See [PR-comment follow-ups](#pr-comment-follow-ups). |
|
|
88
|
+
| `invalidates` | string[] | Optional. List of work-item IDs (e.g. `["W-abc123"]`) whose goals are superseded by this completion. The engine cancels each listed WI that is currently in `pending` or `queued` status, stamping `cancellationReason: "invalidated-by:<source-wi-id>"`. WIs in any other status (dispatched, done, failed, cancelled) are skipped with a warning — they are NOT cancelled. Missing IDs also log a warning and are skipped. Only processed on successful (`effectiveSuccess`) completions. See [Goal invalidation](#goal-invalidation). |
|
|
88
89
|
| `meta.review` | object | Optional, review tasks only. Records project-local review-skill outcome — see [Review skill outcomes](#review-skill-outcomes). Aliased by the generalized `meta.skill` (W-mq1cczi90006b21f). |
|
|
89
90
|
| `meta.skill` | object | Optional. Records project-local skill outcome for ANY playbook type that surfaces a `## Project skills` block (implement / fix / plan / review / etc.). See [Project skill outcomes](#project-skill-outcomes). |
|
|
90
91
|
| `meta.descriptionAudit` | object | Optional, `fix` / `implement` dispatches that push commits. Records the PR description audit + screenshot-refresh outcome — see [PR description audit](#pr-description-audit). |
|
|
@@ -191,6 +192,30 @@ Record the outcome under `meta.descriptionAudit`. All fields are optional and ba
|
|
|
191
192
|
|
|
192
193
|
Dispatchers can suppress the audit entirely by setting `meta.skipDescriptionAudit: true` on the work item — the playbook then renders an explicit "audit suppressed" notice instead of the audit steps. Use this for skill-meta updates, doc-only fixes whose PR description text won't be affected by the diff, and follow-up dispatches that explicitly own the description themselves.
|
|
193
194
|
|
|
195
|
+
## Goal invalidation
|
|
196
|
+
|
|
197
|
+
P-mqyp0009y025z6a7. An optional `invalidates: string[]` field in the completion report lets a completing work item cancel the goals of sibling or downstream WIs that are no longer needed.
|
|
198
|
+
|
|
199
|
+
```json
|
|
200
|
+
{
|
|
201
|
+
"status": "success",
|
|
202
|
+
"summary": "Implemented approach A; approach B work items are no longer needed.",
|
|
203
|
+
"invalidates": ["W-approach-b-001", "W-approach-b-002"]
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Semantics:**
|
|
208
|
+
|
|
209
|
+
- `invalidates` is an array of work-item ID strings (same format as WI IDs: `W-xxx`).
|
|
210
|
+
- The field is **optional and additive** — existing completion reports without it are unaffected.
|
|
211
|
+
- The engine processes `invalidates[]` only on **successful** completions (code 0, no `agentReportedFailure`, no `skipDoneStatus`).
|
|
212
|
+
- Each listed WI is cancelled only if it is currently in `pending` or `queued` status. WIs in any other status (`dispatched`, `done`, `failed`, `cancelled`, …) are **not** cancelled; a warning is logged instead.
|
|
213
|
+
- Missing WI IDs (IDs not found in any project's work-items) produce a warning but do **not** throw — the rest of the `invalidates[]` list is still processed.
|
|
214
|
+
- Each cancellation stamps `cancellationReason: "invalidated-by:<source-wi-id>"` and `cancelledAt` on the cancelled WI.
|
|
215
|
+
- A `work_items` state event is emitted for each successful cancellation so the dashboard reflects it.
|
|
216
|
+
|
|
217
|
+
**Implementation:** `engine/lifecycle.js#applyGoalInvalidation`, called from `runPostCompletionHooks` after the source WI is marked done.
|
|
218
|
+
|
|
194
219
|
## Harness usage (`harnessUsed`)
|
|
195
220
|
|
|
196
221
|
P-a8f3c2d1. Optional self-report of the harness affordances the agent actually
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
| `capabilities.bareMode` | **`false`** | No `--bare`. Closest equivalent is `--no-custom-instructions` (suppresses AGENTS.md only, not all auto-discovery). |
|
|
27
27
|
| `capabilities.fallbackModel` | **`false`** | No `--fallback-model` flag. |
|
|
28
28
|
| `capabilities.sessionPersistenceControl` | **`false`** | Copilot manages session state internally in `~/.copilot/session-state/`. Engine cannot opt out without `--config-dir`. |
|
|
29
|
+
| `capabilities.imageInput` | **`true`** | Base64 image payloads are materialized to temp files and passed as `--attachment <path>` (repeatable flag, W-mqv7324u0021db5d). See §10a. |
|
|
29
30
|
|
|
30
31
|
| Default | Value |
|
|
31
32
|
|---|---|
|
|
@@ -182,6 +183,7 @@ Empirically confirmed flags for non-interactive Copilot invocations:
|
|
|
182
183
|
| `--stream on` / `--stream off` | optional | Default is `on`. See §4. |
|
|
183
184
|
| `--enable-reasoning-summaries` | optional | Maps from `opts.reasoningSummaries`; only Anthropic models populate `assistant.reasoning_delta`. |
|
|
184
185
|
| `--add-dir <path>` | injected by spawn-agent | Same role as on the Claude path — registers extra read-allowed dirs (skill discovery). |
|
|
186
|
+
| `--attachment <path>` | injected for image inputs | Passes an image file to the model. Repeatable. The adapter writes each base64 image payload to a temp file, emits one `--attachment` per file, and deletes temps after spawn (W-mqv7324u0021db5d). Gated by `capabilities.imageInput`. |
|
|
185
187
|
| `-v` / `--verbose` | **never emit** | Does not exist on Copilot. The Claude adapter emits `--verbose`; the Copilot adapter MUST NOT. |
|
|
186
188
|
|
|
187
189
|
### 3.1 `--autopilot` vs single-shot
|
|
@@ -602,7 +604,8 @@ When implementing `engine/runtimes/copilot.js`:
|
|
|
602
604
|
3. `buildArgs(opts)` always emits:
|
|
603
605
|
`--output-format json -s --allow-all --no-ask-user --autopilot --log-level error`
|
|
604
606
|
plus the conditional flags from §3, plus `--no-custom-instructions` /
|
|
605
|
-
`--disable-builtin-mcps` per `opts.suppressAgentsMd` / `opts.disableBuiltinMcps
|
|
607
|
+
`--disable-builtin-mcps` per `opts.suppressAgentsMd` / `opts.disableBuiltinMcps`,
|
|
608
|
+
plus `--attachment <path>` (repeatable) for each image in `opts.images` (§10a).
|
|
606
609
|
**Never** emit `--verbose`.
|
|
607
610
|
4. `buildPrompt()` injects `<system>...</system>\n\n` block when sysprompt is
|
|
608
611
|
non-empty; passthrough otherwise (§2).
|
|
@@ -643,6 +646,33 @@ When the spike's findings disagree with the plan text, **this document wins**
|
|
|
643
646
|
|
|
644
647
|
---
|
|
645
648
|
|
|
649
|
+
## 10a. Image Attachments — `--attachment` (W-mqv7324u0021db5d)
|
|
650
|
+
|
|
651
|
+
`capabilities.imageInput: true`. The Copilot CLI accepts image files via
|
|
652
|
+
`--attachment <path>` (the flag is repeatable). The adapter's `buildArgs(opts)`
|
|
653
|
+
materializes each base64 payload from `opts.images` to a temp file in
|
|
654
|
+
`opts.tmpDir`, appends `--attachment <path>` per file, and arranges cleanup after
|
|
655
|
+
`spawn` returns.
|
|
656
|
+
|
|
657
|
+
```js
|
|
658
|
+
// Simplified adapter flow (see engine/runtimes/copilot.js _buildAttachmentArgs)
|
|
659
|
+
for (const img of opts.images ?? []) {
|
|
660
|
+
const ext = MIME_TO_EXT[img.mimeType] ?? 'bin';
|
|
661
|
+
const filePath = path.join(opts.tmpDir, `attachment-${i}.${ext}`);
|
|
662
|
+
fs.writeFileSync(filePath, Buffer.from(img.dataBase64, 'base64'));
|
|
663
|
+
args.push('--attachment', filePath);
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
**Supported MIME types** (empirically confirmed against Copilot v1.0.36):
|
|
668
|
+
`image/jpeg`, `image/png`, `image/gif`, `image/webp`.
|
|
669
|
+
|
|
670
|
+
When `capabilities.imageInput` is false (e.g., Codex adapter), the engine's
|
|
671
|
+
`_resolveImageOpts` returns a typed `model-unavailable` error before spawn so
|
|
672
|
+
callers get a clean rejection instead of a silent drop.
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
646
676
|
## Provenance
|
|
647
677
|
|
|
648
678
|
- Test host: Windows 11, PowerShell 7+, `copilot.exe` 1.0.36 from WinGet.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Author: Rebecca (Architect) | Date: 2026-04-07 | Status: **Accepted — implementation in progress**
|
|
4
4
|
|
|
5
|
-
> **Implementation status (as of 2026-06):** The `node:sqlite` recommendation in §3 has been adopted ahead of schedule. Phases 0–
|
|
5
|
+
> **Implementation status (as of 2026-06):** The `node:sqlite` recommendation in §3 has been adopted ahead of schedule. Phases 0–10 have shipped (events, dispatches, work_items, pull_requests, logs, metrics, watches, schedule_runs + pipeline_runs + managed_processes + worktree_pool, qa_runs + qa_sessions, pr_links, cooldowns + pending_rebases + cc_sessions + doc_sessions, and steering_deliveries (Phases 0–9); plans + prds + prd_items + prd_verify_prs (Phase 10, migration `015-plans-prds.js`) — see `CHANGELOG.md` and `engine/db/migrations/`). The SQLite schema lives under `engine/db/migrations/` and the singleton opens `engine/state.db` in WAL mode. Phase 8 added the first opt-out toggle for the JSON sidecars (`engine.qaDualWriteJson`, default true). Phase 9.4 went further and deleted the silent SQL-unavailable JSON fallbacks in the engine — SQL is now the only reader/writer for everything migrated; the JSON mirror layer is dual-written as a passive mirror and slated for deletion in Phase 9.5. Phase 10 dual-writes PRD state to SQL (`engine/prd-store.js`) and flips reads to SQL when the `prdReadsFromSql` feature flag is ON (default ON — reversible). The "Phase 2: estimated Node 26 LTS" timeline in §3 is now historical context; treat sections 1–3 as design rationale rather than a forward plan.
|
|
6
6
|
|
|
7
7
|
## Executive Summary
|
|
8
8
|
|
|
@@ -54,7 +54,7 @@ By default the dirty tree refusal (Guarantee 2) is terminal — the engine never
|
|
|
54
54
|
3. If ON: `git fetch origin` + `git reset --hard origin/<branch>`, then **re-run the porcelain preflight once**. If the tree is now clean, dispatch proceeds normally. If the reset failed or the tree is *still* dirty, it falls back to the safe `{ ok:false, reason:'dirty' }` refusal — the engine never dispatches onto an unexpected tree.
|
|
55
55
|
4. On a successful reset it writes a single `live-checkout-autoreset-<wiId>` inbox note listing **exactly which paths were discarded**, so the operator can recover them from `git reflog` / `git fsck --lost-found`.
|
|
56
56
|
|
|
57
|
-
This is **DESTRUCTIVE** — it permanently discards the operator's uncommitted changes in the live checkout
|
|
57
|
+
This is **DESTRUCTIVE** — it permanently discards the operator's uncommitted changes in the live checkout. As of W-mqzbbhn2 it is **ON by default**: discarding disposable dirt via reset-to-remote keeps live-checkout branches aligned with origin instead of accumulating `minions: auto-save agent WIP` commits on every dirty exit. Set `liveCheckoutAutoReset: false` (per-project or fleet-wide) to restore the terminal dirty-tree refusal for live checkouts whose local drift you want preserved.
|
|
58
58
|
|
|
59
59
|
- **Fleet-wide:** Dashboard → Settings → `Live-checkout auto-reset (fleet-wide)` (`engine.liveCheckoutAutoReset`).
|
|
60
60
|
- **Per-project override:** set `liveCheckoutAutoReset: true|false` on the project object in `config.json` (see the config snippet under *Enabling live mode*). A per-project boolean overrides the fleet-wide default for that project. *(Per-project Settings-UI persistence is deferred — the per-project override is config.json-only for now.)*
|
|
@@ -103,6 +103,8 @@ Live-mode agents run **in-place** in the operator's checkout, so when a dispatch
|
|
|
103
103
|
|
|
104
104
|
- **Original-ref capture.** `prepareLiveCheckout` records the operator's starting ref *before* the first checkout: `git symbolic-ref --short HEAD` → `{ originalRef:<branch>, originalRefType:'branch' }`, falling back to `git rev-parse HEAD` → `{ originalRef:<sha>, originalRefType:'detached' }`. `spawnAgent` persists `originalRef` / `originalRefType` onto the dispatch record via `mutateDispatch`, so the restore survives an engine restart, where the in-memory spawn closure is gone and only the persisted record remains.
|
|
105
105
|
- **Self-healing dirty recovery (PL-live-checkout-reliability-hardening).** Before the plain checkout, if the tree is **dirty AND HEAD is on the agent branch** (and that branch differs from `originalRef`), the dirt is provably **agent-authored** — the engine created that branch and verified the tree clean before switching to it. The leftovers are committed onto the **agent branch** (`git add -A` + `git commit --no-verify -m "minions: auto-save agent WIP (dispatch …)"`), so the plain `git checkout <originalRef>` then succeeds and the operator tree returns clean. This is the fix for the *"no recovery from dirty checkout"* deadlock, where an agent's crash-leftover WIP refused the restore, stranded the tree dirty on the agent branch, and then made **every future WI** for that project fail `LIVE_CHECKOUT_DIRTY` until a human cleaned it. It only ever mutates the engine-created branch, never the operator's branch, never discards (the WIP lands as a visible, revertable commit / on its PR), and gitignored artifacts are never staged (so a tree dirty only with ignored build output never reaches here). Best-effort: a failed auto-commit falls through to the manual-recovery alert below.
|
|
106
|
+
- **Retry-with-backoff on the WIP auto-save commit (#608).** The `git add -A` + `git commit` above (and the equivalent self-heal commit described below) go through `_commitAgentWipWithRetry`: a bounded retry (3 attempts, linear backoff) that fires only when the commit fails with an `index.lock` / "another git process" message — i.e. a transient collision with a concurrent git invocation — never on a genuine failure like "nothing to commit". Previously a single failed attempt (e.g. a `.git/index.lock` race) silently aborted the auto-save, leaving the tree dirty on the agent branch and letting the pollution below take hold.
|
|
107
|
+
- **Orphaned-agent-branch self-heal at dispatch start (#608).** If dispatch-end restore is ever skipped or interrupted (crash, forced kill, an engine restart racing the restore), the *next* dispatch for that project can start with HEAD already sitting on a stray `work/<wi-id>` branch from the previous run — and, before this fix, `prepareLiveCheckout`'s dirty-tree check ran *before* `originalRef` was even captured, so the function returned `{ reason: 'dirty' }` immediately and the branch was never noticed or cleaned, silently repeating for every subsequent dispatch. `prepareLiveCheckout` now recognizes this case: if the tree is still dirty after the existing auto-clean-artifacts recheck, and the current branch (read from the already-fetched `git status --porcelain -b` header, no extra git call) matches the `work/<id>` naming convention (`_looksLikeAgentBranch`) and differs from this dispatch's own target branch, the WIP is committed onto that stray branch (again via `_commitAgentWipWithRetry`) and HEAD is switched back to `mainRef` before the normal flow continues. This only ever touches a branch matching the engine's own naming convention — an operator's own feature branch (e.g. `feature/x`) is never mistaken for engine litter and is left untouched.
|
|
106
108
|
- **AUTO-RESTORE (best-effort, never `--force`).** At dispatch-end `restoreLiveCheckoutAtDispatchEnd` issues a **plain** `git checkout <originalRef>` — no `--force`, no `-B`, no reset, no clean, no stash. It no-ops when there is nothing to restore: no captured `originalRef`, the agent branch *is* the original ref, or HEAD already sits on the original ref (matched against the branch name *or* the raw sha so the detached-HEAD case is recognized). It is strictly best-effort: every error is swallowed and logged, and a restore never alters the dispatch result.
|
|
107
109
|
- **Fallback notify (only when a safe switch is impossible).** If git declines the plain checkout — most likely because the agent left uncommitted changes a checkout would overwrite — the refusal is **honored**: the tree is left exactly as the agent left it and a deduped `live-checkout-branch-<dispatchId>` inbox alert tells the operator how to switch back manually (`git -C <localPath> checkout <originalRef>`). The engine never forces the switch. An **unexpected** restore error (git missing, repo corruption, a GVFS blob fetch on the switch-back) now also writes this alert, so a non-refusal failure never silently strands the tree.
|
|
108
110
|
- **Terminal-failure alert.** When the dispatch ends in a non-success terminal state, a deduped `live-checkout-failed-<dispatchId>` inbox alert is written so the operator knows a live-mode run failed inside their own checkout (where any partial work is visible). This is independent of the restore and fires even when the restore itself succeeds.
|
|
@@ -178,7 +180,7 @@ The engine has no opinion about local branches; this hygiene is the operator's r
|
|
|
178
180
|
Live-checkout mode is deliberately small. These are NOT supported and will not be added:
|
|
179
181
|
|
|
180
182
|
- **No `auto` mode.** The choice between `worktree` and `live` is per-project and operator-set. The engine will not auto-detect submodules / `repo` workspaces and silently switch modes.
|
|
181
|
-
- **No auto-stash on dirty refusal.** The engine refuses and exits; it never `git stash`es to "make room" for a dispatch. Stashes silently mutate the operator's tree and conflate engine state with operator state. (The
|
|
183
|
+
- **No auto-stash on dirty refusal.** The engine refuses and exits; it never `git stash`es to "make room" for a dispatch. Stashes silently mutate the operator's tree and conflate engine state with operator state. (The `liveCheckoutAutoReset` escape hatch — ON by default as of W-mqzbbhn2 — **discards** rather than stashes; set it to `false` per-project / fleet-wide to keep the terminal dirty-tree refusal. See [§2b](#2b-opt-in-auto-reset-on-dirty-livecheckoutautoreset-w-mqvejug6000eeb20).)
|
|
182
184
|
- **No concurrent dispatches per project.** The cap is 1; raising it would require per-WI subdirectories, which live mode explicitly does not provide.
|
|
183
185
|
- **No per-WI subdirectory isolation.** Live mode is one-checkout-per-project by design. If you need isolation, use `checkoutMode: 'worktree'` (the default).
|
|
184
186
|
- **No per-WI override.** `checkoutMode` is per-project only. There is no `meta.checkoutMode` on a work item that overrides the project setting.
|
|
@@ -190,7 +192,7 @@ Live-checkout mode is deliberately small. These are NOT supported and will not b
|
|
|
190
192
|
| File | Purpose |
|
|
191
193
|
|---|---|
|
|
192
194
|
| `engine/shared.js` — `CHECKOUT_MODES`, `validateCheckoutMode`, `resolveCheckoutMode`, `isLiveCheckoutProject` | Enum + validator + back-compat resolver (P-a3f9b201; consolidated W-mqiaw974). |
|
|
193
|
-
| `engine/shared.js` — `resolveLiveCheckoutAutoReset` + `ENGINE_DEFAULTS.liveCheckoutAutoReset` | Pure precedence resolver (per-project boolean > fleet-wide engine default > false) + the fleet-wide default (
|
|
195
|
+
| `engine/shared.js` — `resolveLiveCheckoutAutoReset` + `ENGINE_DEFAULTS.liveCheckoutAutoReset` | Pure precedence resolver (per-project boolean > fleet-wide engine default > false) + the fleet-wide default (ON as of W-mqzbbhn2). Gates the dirty-tree auto-reset in `prepareLiveCheckout` (W-mqvejug6000eeb20). |
|
|
194
196
|
| `engine/shared.js` — `resolveSpawnPaths` | Returns `{ cwd: localPath, worktreeRootDir: null, liveMode: true }` for live projects (P-a3f9b202). |
|
|
195
197
|
| `engine/live-checkout.js` — `prepareLiveCheckout` | Pure helper: dirty check, mid-operation / detached-HEAD preflight (incl. `BISECT_LOG`; throw-on-git-dir-failure; exit-1-only detached), original-ref capture, **already-on-branch fast path**, `refs/heads/<branch>` existence check, branch resolution from HEAD (no fetch — issue #226), **no-half-switch + `blob-fetch` classification** for partial-clone hydration failures, **opt-in dirty auto-reset** (`git fetch origin` + `reset --hard origin/<branch>` + re-check + `live-checkout-autoreset-<wiId>` note when `liveCheckoutAutoReset` is on), **50 MB git maxBuffer** (P-a3f9b203; preflight + capture P-b2e8d4a6; hardening PL-live-checkout-reliability-hardening; auto-reset + maxBuffer W-mqvejug6000eeb20). |
|
|
196
198
|
| `engine/live-checkout.js` — `restoreLiveCheckoutAtDispatchEnd` | Dispatch-end auto-restore (plain `git checkout <originalRef>`, never `--force`/reset/clean/stash, best-effort) + **self-healing dirty recovery** (auto-commit agent WIP onto the agent branch) + `live-checkout-failed-<dispatchId>` terminal-failure alert + `live-checkout-branch-<dispatchId>` fallback notify (now also on unexpected restore errors) (P-d9e6b2c4; self-heal PL-live-checkout-reliability-hardening). |
|