@yemi33/minions 0.1.2424 → 0.1.2426

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.
Files changed (185) hide show
  1. package/bin/install-internal-minions.js +1209 -0
  2. package/bin/minions.js +75 -9
  3. package/dashboard/docs/typography.md +27 -12
  4. package/dashboard/js/command-center.js +13 -11
  5. package/dashboard/js/live-stream.js +1 -1
  6. package/dashboard/js/memory-search.js +388 -43
  7. package/dashboard/js/modal-qa.js +5 -5
  8. package/dashboard/js/qa.js +39 -26
  9. package/dashboard/js/refresh.js +37 -18
  10. package/dashboard/js/render-agents.js +14 -18
  11. package/dashboard/js/render-dispatch.js +14 -74
  12. package/dashboard/js/render-inbox.js +3 -3
  13. package/dashboard/js/render-meetings.js +8 -8
  14. package/dashboard/js/render-other.js +45 -20
  15. package/dashboard/js/render-pipelines.js +6 -6
  16. package/dashboard/js/render-plans.js +14 -14
  17. package/dashboard/js/render-prd.js +46 -46
  18. package/dashboard/js/render-prs.js +9 -65
  19. package/dashboard/js/render-schedules.js +5 -5
  20. package/dashboard/js/render-skills.js +6 -6
  21. package/dashboard/js/render-utils.js +4 -4
  22. package/dashboard/js/render-watches.js +4 -16
  23. package/dashboard/js/render-work-items.js +95 -107
  24. package/dashboard/js/settings.js +175 -74
  25. package/dashboard/layout.html +3 -2
  26. package/dashboard/pages/inbox.html +1 -1
  27. package/dashboard/pages/work.html +1 -1
  28. package/dashboard/shared/model-display.js +9 -0
  29. package/dashboard/shared/watches-source.js +43 -0
  30. package/dashboard/shared/welcome-popup.js +259 -0
  31. package/dashboard/slim/body.html +6 -6
  32. package/dashboard/slim/js/chat.js +4 -2
  33. package/dashboard/slim/js/history.js +10 -3
  34. package/dashboard/slim/js/modals-tiles.js +85 -16
  35. package/dashboard/slim/js/status.js +12 -16
  36. package/dashboard/slim/styles.css +24 -4
  37. package/dashboard/styles.css +62 -27
  38. package/dashboard-build.js +8 -2
  39. package/dashboard.js +3854 -2514
  40. package/docs/README.md +7 -5
  41. package/docs/api-errors.md +144 -0
  42. package/docs/auto-discovery.md +83 -61
  43. package/docs/capture-demos.js +173 -30
  44. package/docs/command-center.md +5 -1
  45. package/docs/completion-reports.md +127 -11
  46. package/docs/constants.md +15 -3
  47. package/docs/constellation-style-telemetry.md +2 -2
  48. package/docs/copilot-cli-schema.md +32 -22
  49. package/docs/cross-repo-plans.md +30 -30
  50. package/docs/demo/memory-system.html +1 -1
  51. package/docs/deprecated.json +132 -18
  52. package/docs/diagnostics-memory.md +8 -4
  53. package/docs/human-vs-automated.md +2 -2
  54. package/docs/index.html +9 -2
  55. package/docs/internal-install.md +212 -0
  56. package/docs/kb-pr3223-cascade-archiving.md +16 -0
  57. package/docs/kb-pr696-merge-conflict-docs.md +23 -0
  58. package/docs/kb-sweep.md +2 -2
  59. package/docs/keep-processes.md +8 -1
  60. package/docs/live-checkout-mode.md +1 -1
  61. package/docs/managed-spawn.md +21 -3
  62. package/docs/named-agents.md +3 -2
  63. package/docs/onboarding.md +26 -0
  64. package/docs/plan-lifecycle.md +32 -32
  65. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-AFTER.png +0 -0
  66. package/docs/pr-screenshots/pr-899/worker-pool-worktrees-BEFORE.png +0 -0
  67. package/docs/pr-screenshots/pr-979/auto-fix-pane-AFTER.png +0 -0
  68. package/docs/pr-screenshots/pr-979/auto-fix-pane-BEFORE.png +0 -0
  69. package/docs/pr-screenshots/pr-985/pr-column-em-dash-AFTER.png +0 -0
  70. package/docs/pr-screenshots/pr-985/pr-column-em-dash-BEFORE.png +0 -0
  71. package/docs/qa-runbook-lifecycle.md +67 -51
  72. package/docs/qa-runbooks.md +2 -2
  73. package/docs/runtime-adapters.md +101 -50
  74. package/docs/security.md +28 -0
  75. package/docs/self-improvement.md +47 -13
  76. package/docs/skills.md +29 -0
  77. package/docs/slim-ux/concepts.md +2 -2
  78. package/docs/specs/agent-configurability.md +43 -41
  79. package/docs/specs/agent-rename.md +13 -13
  80. package/docs/team-memory.md +283 -14
  81. package/docs/tutorials/01-install-and-connect.md +23 -0
  82. package/docs/tutorials/03-plan-driven-feature.md +3 -3
  83. package/docs/tutorials/04-runtimes-and-harness.md +18 -21
  84. package/docs/tutorials/08-operations-and-recovery.md +3 -2
  85. package/docs/tutorials/README.md +4 -3
  86. package/docs/visual-evidence-ci.md +103 -0
  87. package/docs/watches.md +35 -30
  88. package/docs/worktree-lifecycle.md +151 -13
  89. package/engine/acp-transport.js +1 -0
  90. package/engine/ado.js +80 -33
  91. package/engine/agent-api-validation.js +571 -0
  92. package/engine/agent-worker-pool.js +7 -6
  93. package/engine/api-contracts/agent-content.js +761 -0
  94. package/engine/api-contracts/cc-ops.js +219 -0
  95. package/engine/api-contracts/config-runtime.js +419 -0
  96. package/engine/api-contracts/core.js +138 -0
  97. package/engine/api-contracts/index.js +506 -0
  98. package/engine/api-contracts/orchestration.js +602 -0
  99. package/engine/api-contracts/pull-requests.js +323 -0
  100. package/engine/api-contracts/qa-process.js +764 -0
  101. package/engine/api-contracts/work-plan-prd.js +494 -0
  102. package/engine/api-validation.js +543 -0
  103. package/engine/cc-api-validation.js +586 -0
  104. package/engine/cc-worker-pool.js +9 -1
  105. package/engine/cleanup.js +393 -271
  106. package/engine/cli.js +65 -7
  107. package/engine/comment-classifier.js +70 -5
  108. package/engine/consolidation.js +123 -5
  109. package/engine/content-api-validation.js +606 -0
  110. package/engine/create-pr-worktree.js +22 -1
  111. package/engine/db/migrations/027-review-learning-lifecycle.js +162 -0
  112. package/engine/dispatch.js +33 -0
  113. package/engine/distribution.js +189 -0
  114. package/engine/execution-model.js +59 -0
  115. package/engine/features.js +12 -0
  116. package/engine/github.js +67 -25
  117. package/engine/harness-context.js +313 -0
  118. package/engine/inbox-store.js +1 -1
  119. package/engine/kb-sweep.js +161 -38
  120. package/engine/keep-process-sweep.js +43 -3
  121. package/engine/lifecycle.js +1066 -165
  122. package/engine/llm.js +4 -0
  123. package/engine/managed-spawn.js +463 -48
  124. package/engine/meeting.js +197 -34
  125. package/engine/memory-retrieval.js +280 -0
  126. package/engine/memory-store.js +821 -38
  127. package/engine/model-discovery.js +17 -3
  128. package/engine/pipeline.js +802 -14
  129. package/engine/plan-prd-validation.js +745 -0
  130. package/engine/playbook.js +206 -17
  131. package/engine/pooled-agent-process.js +6 -1
  132. package/engine/pr-action.js +1 -1
  133. package/engine/pr-issue-validation.js +653 -0
  134. package/engine/pr-resolve.js +75 -13
  135. package/engine/prd-store.js +39 -6
  136. package/engine/preflight.js +7 -2
  137. package/engine/process-utils.js +79 -31
  138. package/engine/projects.js +198 -12
  139. package/engine/promotion.js +371 -0
  140. package/engine/qa-from-prd.js +3 -0
  141. package/engine/qa-process-validation.js +572 -0
  142. package/engine/qa-runbooks.js +41 -37
  143. package/engine/qa-runners/maestro.js +11 -7
  144. package/engine/qa-runners/playwright.js +37 -14
  145. package/engine/qa-runners.js +9 -10
  146. package/engine/qa-runs.js +124 -11
  147. package/engine/qa-sessions.js +139 -40
  148. package/engine/quarantine-refs.js +81 -11
  149. package/engine/queries.js +163 -9
  150. package/engine/review-learning-backfill.js +482 -0
  151. package/engine/review-learning.js +1236 -0
  152. package/engine/runtimes/claude.js +60 -2
  153. package/engine/runtimes/codex.js +57 -16
  154. package/engine/runtimes/contract.js +21 -0
  155. package/engine/runtimes/copilot.js +51 -3
  156. package/engine/runtimes/index.js +1 -0
  157. package/engine/scheduler.js +201 -32
  158. package/engine/settings-validation.js +909 -0
  159. package/engine/shared.js +705 -208
  160. package/engine/spawn-agent.js +48 -21
  161. package/engine/steering-constraints.js +31 -0
  162. package/engine/steering-store.js +10 -2
  163. package/engine/steering.js +14 -2
  164. package/engine/timeout.js +404 -111
  165. package/engine/untrusted-fence.js +24 -10
  166. package/engine/watch-actions.js +7 -2
  167. package/engine/watches.js +353 -51
  168. package/engine/work-item-validation.js +561 -0
  169. package/engine/work-items-store.js +61 -1
  170. package/engine/worktree-gc.js +656 -37
  171. package/engine/worktree-preflight.js +154 -0
  172. package/engine.js +2068 -342
  173. package/package.json +1 -1
  174. package/playbooks/_pr-description-audit.md +88 -41
  175. package/playbooks/build-fix-complex.md +3 -3
  176. package/playbooks/docs.md +2 -2
  177. package/playbooks/fix.md +18 -18
  178. package/playbooks/implement.md +18 -18
  179. package/playbooks/qa-session-draft.md +10 -12
  180. package/playbooks/qa-session-execute.md +16 -12
  181. package/playbooks/qa-validate.md +8 -7
  182. package/playbooks/shared-rules.md +45 -0
  183. package/playbooks/test.md +4 -0
  184. package/prompts/cc-system.md +1 -1
  185. package/skills/check-self-authored-review-comment/SKILL.md +2 -2
@@ -40,7 +40,7 @@
40
40
  <div style="display:flex;align-items:center;gap:8px;">
41
41
  <button id="try-slim-ux-btn" class="try-slim-ux-btn" onclick="trySlimUx(this)" title="Switch to the experimental Slim UX — a focused, single-screen cockpit" style="display:none">&#10024; Try new Slim UX</button>
42
42
  <span class="engine-badge stopped" id="engine-badge">STOPPED</span>
43
- <span id="version-banner" style="font-size:var(--text-xs);color:var(--muted)"></span>
43
+ <span id="version-banner" style="font-size:var(--text-sm);color:var(--muted)"></span>
44
44
  <div class="timestamp" id="ts">—</div>
45
45
  <button onclick="toggleCommandCenter()" style="background:none;border:none;cursor:pointer;font-size:var(--text-md);color:var(--blue);padding:4px 10px;line-height:1;font-weight:700;border:1px solid var(--blue);border-radius:4px" title="Command Center" id="cc-toggle-btn">Command Center</button>
46
46
  <button onclick="openBugReport()" style="background:none;border:none;cursor:pointer;font-size:var(--text-base);color:var(--muted);padding:4px 8px;line-height:1;border:1px solid var(--border);border-radius:4px" title="Report a bug in Minions">Report Bug</button>
@@ -72,13 +72,14 @@
72
72
  <div id="cc-tab-bar" style="display:flex;gap:8px;padding:4px 12px;border-bottom:1px solid var(--border);overflow:hidden;align-items:center;flex-shrink:0"></div>
73
73
  <div id="cc-messages" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:10px"></div>
74
74
  <div style="padding:12px 16px;border-top:1px solid var(--border)">
75
+ <div id="cc-suggestions-host"></div>
75
76
  <!-- P-2a6d8e74 — image attachment thumbnail chips (paste / drag-drop); hidden until images are attached -->
76
77
  <div id="cc-attachments" style="display:none;flex-wrap:wrap;gap:6px;margin-bottom:8px"></div>
77
78
  <div style="display:flex;gap:8px">
78
79
  <textarea id="cc-input" rows="2" placeholder="Ask anything or give a command... (paste or drop an image)" style="flex:1;padding:8px 10px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:var(--text-md);resize:none;font-family:inherit" onpaste="ccHandlePaste(event)" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();ccSend()}"></textarea>
79
80
  <button onclick="ccSend()" style="background:var(--blue);color:#fff;border:none;border-radius:6px;padding:8px 14px;font-size:var(--text-md);font-weight:600;cursor:pointer;align-self:flex-end">Send</button>
80
81
  </div>
81
- <div id="cc-powered-by" style="font-size:var(--text-xs);color:var(--muted);margin-top:4px">Full minions context. Enter to send, Shift+Enter for newline. Paste or drop images to attach.</div>
82
+ <div id="cc-powered-by" style="font-size:var(--text-sm);color:var(--muted);margin-top:4px">Full minions context. Enter to send, Shift+Enter for newline. Paste or drop images to attach.</div>
82
83
  </div>
83
84
  </div>
84
85
 
@@ -7,7 +7,7 @@
7
7
  <div id="notes-list">Loading...</div>
8
8
  </section>
9
9
  <section>
10
- <h2>Knowledge Base <span class="count" id="kb-count">0</span> <button class="btn-add" style="margin-left:8px" onclick="openCreateKbModal()">+ New</button><button class="btn-add" style="margin-left:8px" onclick="openMemorySearchModal()">Memory search</button><button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:var(--text-xs);padding:2px 8px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:4px;cursor:pointer;margin-left:8px;vertical-align:middle">sweep</button><span id="kb-swept-time" style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0;margin-left:8px"></span></h2>
10
+ <h2>Knowledge Base <span class="count" id="kb-count">0</span> <button class="btn-add" style="margin-left:8px" onclick="openCreateKbModal()">+ New</button><button class="btn-add" style="margin-left:8px" onclick="openMemorySearchModal()">Memory search</button><button id="kb-sweep-btn" onclick="kbSweep()" style="font-size:var(--text-sm);padding:2px 8px;background:var(--surface2);border:1px solid var(--border);color:var(--muted);border-radius:4px;cursor:pointer;margin-left:8px;vertical-align:middle">sweep</button><span id="kb-swept-time" style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0;margin-left:8px"></span></h2>
11
11
  <div id="kb-sweep-toast" class="cmd-toast cmd-toast-inline" style="margin:6px 0"></div>
12
12
  <div class="kb-tabs" id="kb-tabs"></div>
13
13
  <div class="kb-list" id="kb-list"><p class="empty">No knowledge entries yet. Notes are classified here after consolidation.</p></div>
@@ -1,7 +1,7 @@
1
1
  <section id="work-items-section" style="overflow:visible">
2
2
  <h2>Work Items <span class="count" id="wi-count">0</span>
3
3
  <button class="btn-add" style="margin-left:8px" onclick="openCreateWorkItemModal()">+ New</button>
4
- <button class="pr-pager-btn" style="font-size:var(--text-xs);padding:2px 8px;margin-left:4px" onclick="toggleWorkItemArchive()">See Archive</button>
4
+ <button class="pr-pager-btn" style="font-size:var(--text-sm);padding:2px 8px;margin-left:4px" onclick="toggleWorkItemArchive()">See Archive</button>
5
5
  <span style="font-size:var(--text-sm);color:var(--muted);font-weight:400;text-transform:none;letter-spacing:0">tasks dispatched to agents — auto-created from PRDs or added manually</span>
6
6
  </h2>
7
7
  <div id="work-items-content"><p class="empty">No work items yet.</p></div>
@@ -0,0 +1,9 @@
1
+ // Shared model label for classic and Slim dashboard surfaces.
2
+ var MinionsModelDisplay = {
3
+ label: function(model) {
4
+ var value = model === null || model === undefined ? '' : String(model).trim();
5
+ return value || 'Runtime model not observed yet';
6
+ },
7
+ };
8
+
9
+ if (typeof window !== 'undefined') window.MinionsModelDisplay = MinionsModelDisplay;
@@ -0,0 +1,43 @@
1
+ // Shared watch-loading source for the classic and Slim dashboard surfaces.
2
+ //
3
+ // Watches are SQL-backed: migration 007 moved `engine/watches.json` into the
4
+ // `watches` table, and the SQL-only cutover retired the JSON mirror entirely
5
+ // (see engine/db/index.js#_retiredJsonTargets). The engine mutates watch
6
+ // status/_history through engine/watches-store.js — nothing writes the old
7
+ // file, so serving it over the static `/state/` passthrough 404s forever and
8
+ // any UI reading it renders an empty list. `GET /api/watches` is the canonical
9
+ // read surface.
10
+ //
11
+ // Both dashboards go through this module so the endpoint and the response
12
+ // envelope are described in exactly one place.
13
+ var MinionsWatchesSource = {
14
+ ENDPOINT: '/api/watches',
15
+
16
+ // `GET /api/watches` responds `{ watches: [...] }`. A bare array is accepted
17
+ // defensively so an older/proxied response shape still renders. Returns null
18
+ // (never []) for anything unusable, so callers can tell "no data available"
19
+ // apart from "genuinely zero watches" and keep their last-known slice.
20
+ unwrap: function (body) {
21
+ if (Array.isArray(body)) return body;
22
+ if (body && Array.isArray(body.watches)) return body.watches;
23
+ return null;
24
+ },
25
+
26
+ // Resolves to the watch array, or null when the request fails or the payload
27
+ // is unusable. Never rejects — callers render their previous slice on null.
28
+ fetchWatches: function (fetchImpl) {
29
+ var doFetch = fetchImpl || function (url, opts) { return fetch(url, opts); };
30
+ var unwrap = MinionsWatchesSource.unwrap;
31
+ return Promise.resolve()
32
+ .then(function () {
33
+ return doFetch(MinionsWatchesSource.ENDPOINT, { headers: { Accept: 'application/json' } });
34
+ })
35
+ .then(function (res) {
36
+ if (!res || !res.ok) return null;
37
+ return Promise.resolve(res.json()).then(unwrap, function () { return null; });
38
+ })
39
+ .catch(function () { return null; });
40
+ },
41
+ };
42
+
43
+ if (typeof window !== 'undefined') window.MinionsWatchesSource = MinionsWatchesSource;
@@ -0,0 +1,259 @@
1
+ // Shared internal-distribution welcome popup for the classic and Slim dashboards.
2
+ //
3
+ // W-ms1jgr95000ke749 — the first dashboard launch on an INTERNAL Minions
4
+ // distribution (`@yemishin_microsoft/minions` et al, see engine/distribution.js)
5
+ // shows a one-time modal. Public `@yemi33/minions` installs never see it: the
6
+ // server decides via installed-package metadata and simply answers
7
+ // `{ show: false }`, so this module renders nothing there.
8
+ //
9
+ // Ships in BOTH bundles through dashboard-build.js#DASHBOARD_SHARED_JS — the
10
+ // classic <script> and the Slim IIFE get this one implementation rather than a
11
+ // forked copy each. It therefore runs under strict mode (Slim) and non-strict
12
+ // (classic), declares no implicit globals, and builds its DOM with
13
+ // createElement/textContent (no innerHTML) so it passes the XSS lint gate and
14
+ // can never interpolate server text as markup.
15
+ //
16
+ // Acknowledgement is server-side (POST /api/welcome/ack → SQL small-state keyed
17
+ // on the runtime installation id), NOT localStorage: the popup must stay
18
+ // dismissed across browsers, dashboard restarts and internal-package upgrades,
19
+ // and must reappear only for a genuinely new runtime installation.
20
+ var MinionsWelcomePopup = {
21
+ ENDPOINT: '/api/welcome',
22
+ ACK_ENDPOINT: '/api/welcome/ack',
23
+ MOUNT_ID: 'minions-welcome-popup',
24
+ TITLE_ID: 'minions-welcome-popup-title',
25
+ MESSAGE_ID: 'minions-welcome-popup-message',
26
+ DISMISS_ID: 'minions-welcome-popup-dismiss',
27
+ // Server-authoritative, but mirrored here so the popup still renders its
28
+ // required copy if an older engine omits `message`.
29
+ DEFAULT_MESSAGE: 'Welcome to the internal Minions!',
30
+
31
+ _previousFocus: null,
32
+ _keyHandler: null,
33
+
34
+ // Only an explicit `show: true` from the server opens the popup. Anything
35
+ // else — public distribution, already acknowledged, malformed/absent payload
36
+ // — resolves to false, so a transport hiccup can never pop a public install.
37
+ shouldShow: function (payload) {
38
+ return !!(payload && payload.show === true);
39
+ },
40
+
41
+ // Resolves to the payload, or null on any transport/parse failure. Never
42
+ // rejects: a failed welcome check must not break dashboard boot.
43
+ fetchState: function (fetchImpl) {
44
+ var doFetch = fetchImpl || function (url, opts) { return fetch(url, opts); };
45
+ return Promise.resolve()
46
+ .then(function () {
47
+ return doFetch(MinionsWelcomePopup.ENDPOINT, { headers: { Accept: 'application/json' } });
48
+ })
49
+ .then(function (res) {
50
+ if (!res || !res.ok) return null;
51
+ return Promise.resolve(res.json()).then(function (body) {
52
+ return body && typeof body === 'object' ? body : null;
53
+ }, function () { return null; });
54
+ })
55
+ .catch(function () { return null; });
56
+ },
57
+
58
+ // Fire-and-forget acknowledgement. Resolves true when the server persisted it.
59
+ acknowledge: function (fetchImpl) {
60
+ var doFetch = fetchImpl || function (url, opts) { return fetch(url, opts); };
61
+ return Promise.resolve()
62
+ .then(function () {
63
+ return doFetch(MinionsWelcomePopup.ACK_ENDPOINT, {
64
+ method: 'POST',
65
+ headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
66
+ body: '{}',
67
+ });
68
+ })
69
+ .then(function (res) { return !!(res && res.ok); })
70
+ .catch(function () { return false; });
71
+ },
72
+
73
+ _cardStyle: function () {
74
+ return [
75
+ 'background:var(--surface)',
76
+ 'border:1px solid var(--blue)',
77
+ 'border-radius:var(--radius-lg, 8px)',
78
+ 'box-shadow:var(--shadow-md, 0 8px 24px rgba(0,0,0,0.4))',
79
+ 'width:min(440px, calc(100vw - 32px))',
80
+ 'padding:22px 24px',
81
+ 'color:var(--text)',
82
+ 'font-size:var(--text-md)',
83
+ 'line-height:1.55',
84
+ 'text-align:left',
85
+ ].join(';');
86
+ },
87
+
88
+ _backdropStyle: function () {
89
+ return [
90
+ 'position:fixed',
91
+ 'inset:0',
92
+ 'background:rgba(0,0,0,0.75)',
93
+ 'z-index:600',
94
+ 'display:flex',
95
+ 'align-items:center',
96
+ 'justify-content:center',
97
+ ].join(';');
98
+ },
99
+
100
+ // Build (but do not mount) the dialog. `doc` is injectable for tests.
101
+ build: function (payload, doc) {
102
+ var d = doc || document;
103
+ var message = (payload && typeof payload.message === 'string' && payload.message.trim())
104
+ ? payload.message
105
+ : MinionsWelcomePopup.DEFAULT_MESSAGE;
106
+
107
+ var backdrop = d.createElement('div');
108
+ backdrop.id = MinionsWelcomePopup.MOUNT_ID;
109
+ backdrop.className = 'modal-bg minions-welcome-bg';
110
+ backdrop.setAttribute('style', MinionsWelcomePopup._backdropStyle());
111
+
112
+ var card = d.createElement('div');
113
+ card.className = 'modal minions-welcome-card';
114
+ card.setAttribute('role', 'dialog');
115
+ card.setAttribute('aria-modal', 'true');
116
+ card.setAttribute('aria-labelledby', MinionsWelcomePopup.TITLE_ID);
117
+ card.setAttribute('aria-describedby', MinionsWelcomePopup.MESSAGE_ID);
118
+ card.setAttribute('style', MinionsWelcomePopup._cardStyle());
119
+
120
+ // The primary message IS the heading — the required copy must be the first
121
+ // thing both a sighted user and a screen reader receive.
122
+ var title = d.createElement('h2');
123
+ title.id = MinionsWelcomePopup.TITLE_ID;
124
+ title.className = 'minions-welcome-title';
125
+ title.setAttribute('style', 'margin:0;font-size:var(--text-xl);font-weight:700;color:var(--blue)');
126
+ title.textContent = message;
127
+ card.appendChild(title);
128
+
129
+ var body = d.createElement('p');
130
+ body.id = MinionsWelcomePopup.MESSAGE_ID;
131
+ body.className = 'minions-welcome-message';
132
+ body.setAttribute('style', 'margin:10px 0 0;color:var(--muted)');
133
+ body.textContent = 'This is the internal build of Minions. Everything works the same as the public release — you are just on the internal distribution channel.';
134
+ card.appendChild(body);
135
+
136
+ var pkg = payload && payload.distribution && payload.distribution.packageName;
137
+ if (typeof pkg === 'string' && pkg) {
138
+ var pkgLine = d.createElement('p');
139
+ pkgLine.className = 'minions-welcome-package';
140
+ pkgLine.setAttribute('style', 'margin:8px 0 0;color:var(--muted);font-size:var(--text-sm)');
141
+ var pkgLabel = d.createElement('span');
142
+ pkgLabel.textContent = 'Package: ';
143
+ var pkgCode = d.createElement('code');
144
+ pkgCode.textContent = pkg;
145
+ pkgLine.appendChild(pkgLabel);
146
+ pkgLine.appendChild(pkgCode);
147
+ card.appendChild(pkgLine);
148
+ }
149
+
150
+ var actions = d.createElement('div');
151
+ actions.setAttribute('style', 'display:flex;justify-content:flex-end;margin-top:18px');
152
+ var dismiss = d.createElement('button');
153
+ dismiss.id = MinionsWelcomePopup.DISMISS_ID;
154
+ dismiss.type = 'button';
155
+ dismiss.className = 'btn btn-primary';
156
+ dismiss.setAttribute('style', 'padding:7px 18px;font-weight:600;cursor:pointer');
157
+ dismiss.textContent = 'Get started';
158
+ actions.appendChild(dismiss);
159
+ card.appendChild(actions);
160
+
161
+ backdrop.appendChild(card);
162
+ return backdrop;
163
+ },
164
+
165
+ isOpen: function (doc) {
166
+ var d = doc || document;
167
+ return !!d.getElementById(MinionsWelcomePopup.MOUNT_ID);
168
+ },
169
+
170
+ // Remove the dialog and hand focus back to whatever had it before opening.
171
+ close: function (opts) {
172
+ var d = (opts && opts.document) || document;
173
+ var node = d.getElementById(MinionsWelcomePopup.MOUNT_ID);
174
+ if (node && node.parentNode) node.parentNode.removeChild(node);
175
+ if (MinionsWelcomePopup._keyHandler) {
176
+ try { d.removeEventListener('keydown', MinionsWelcomePopup._keyHandler, true); } catch (e) { /* detached doc */ }
177
+ MinionsWelcomePopup._keyHandler = null;
178
+ }
179
+ var prev = MinionsWelcomePopup._previousFocus;
180
+ MinionsWelcomePopup._previousFocus = null;
181
+ if (prev && typeof prev.focus === 'function') {
182
+ try { prev.focus(); } catch (e) { /* element may be gone */ }
183
+ }
184
+ },
185
+
186
+ // Close + persist the acknowledgement. Closing is optimistic: the operator
187
+ // should never be blocked by a failing ack round-trip.
188
+ dismiss: function (opts) {
189
+ MinionsWelcomePopup.close(opts);
190
+ return MinionsWelcomePopup.acknowledge(opts && opts.fetchImpl);
191
+ },
192
+
193
+ // Mount the dialog, wire dismissal (button, Escape, backdrop click), trap Tab
194
+ // inside the card, and move focus to the primary action.
195
+ open: function (payload, opts) {
196
+ var d = (opts && opts.document) || document;
197
+ if (MinionsWelcomePopup.isOpen(d)) return d.getElementById(MinionsWelcomePopup.MOUNT_ID);
198
+ var node = MinionsWelcomePopup.build(payload, d);
199
+ MinionsWelcomePopup._previousFocus = d.activeElement || null;
200
+ (d.body || d.documentElement).appendChild(node);
201
+
202
+ var dismissBtn = d.getElementById(MinionsWelcomePopup.DISMISS_ID);
203
+ function doDismiss() { MinionsWelcomePopup.dismiss(opts); }
204
+ if (dismissBtn) dismissBtn.addEventListener('click', doDismiss);
205
+ node.addEventListener('click', function (ev) { if (ev.target === node) doDismiss(); });
206
+
207
+ MinionsWelcomePopup._keyHandler = function (ev) {
208
+ if (!MinionsWelcomePopup.isOpen(d)) return;
209
+ if (ev.key === 'Escape') {
210
+ if (typeof ev.preventDefault === 'function') ev.preventDefault();
211
+ doDismiss();
212
+ return;
213
+ }
214
+ if (ev.key === 'Tab') {
215
+ // Single focusable control — keep focus parked on it either direction.
216
+ if (typeof ev.preventDefault === 'function') ev.preventDefault();
217
+ if (dismissBtn && typeof dismissBtn.focus === 'function') {
218
+ try { dismissBtn.focus(); } catch (e) { /* ignore */ }
219
+ }
220
+ }
221
+ };
222
+ d.addEventListener('keydown', MinionsWelcomePopup._keyHandler, true);
223
+
224
+ if (dismissBtn && typeof dismissBtn.focus === 'function') {
225
+ try { dismissBtn.focus(); } catch (e) { /* ignore */ }
226
+ }
227
+ return node;
228
+ },
229
+
230
+ // Ask the server, then open when (and only when) it says so.
231
+ check: function (opts) {
232
+ return MinionsWelcomePopup.fetchState(opts && opts.fetchImpl).then(function (payload) {
233
+ if (!MinionsWelcomePopup.shouldShow(payload)) return null;
234
+ return MinionsWelcomePopup.open(payload, opts);
235
+ });
236
+ },
237
+
238
+ // Idempotent boot hook — runs at most once per page load in either surface.
239
+ init: function (opts) {
240
+ var w = (opts && opts.window) || (typeof window !== 'undefined' ? window : null);
241
+ if (!w) return Promise.resolve(null);
242
+ if (w.__minionsWelcomePopupInit) return Promise.resolve(null);
243
+ w.__minionsWelcomePopupInit = true;
244
+ return MinionsWelcomePopup.check(opts);
245
+ },
246
+ };
247
+
248
+ if (typeof window !== 'undefined') {
249
+ window.MinionsWelcomePopup = MinionsWelcomePopup;
250
+ // Both bundles inline their scripts at the end of <body>, so the DOM is
251
+ // normally parsed already; the readyState guard covers deferred/async loads.
252
+ if (typeof document !== 'undefined') {
253
+ if (document.readyState === 'loading') {
254
+ document.addEventListener('DOMContentLoaded', function () { MinionsWelcomePopup.init(); });
255
+ } else {
256
+ MinionsWelcomePopup.init();
257
+ }
258
+ }
259
+ }
@@ -35,6 +35,7 @@
35
35
  <span class="context-label">Working in</span>
36
36
  <span id="chat-context-controls"></span>
37
37
  </div>
38
+ <div id="chat-suggestions-host"></div>
38
39
  <div class="chat-input-wrap">
39
40
  <textarea
40
41
  id="chat-input"
@@ -63,7 +64,7 @@
63
64
  <!-- STATUS — cockpit tiles. Lit when their indicator is non-zero. -->
64
65
  <div class="panel panel-status">
65
66
  <div class="panel-header">
66
- Agent Team
67
+ Status
67
68
  <span class="panel-sub" id="cockpit-stamp">refreshing&hellip;</span>
68
69
  </div>
69
70
  <div class="panel-body">
@@ -110,10 +111,10 @@
110
111
  <div class="cockpit-value dim">0</div>
111
112
  <div class="cockpit-detail">no open PRs</div>
112
113
  </div>
113
- <div class="cockpit-tile" data-tile="watches">
114
- <div class="cockpit-label"><span class="cockpit-dot"></span> Watches</div>
114
+ <div class="cockpit-tile" data-tile="automation">
115
+ <div class="cockpit-label"><span class="cockpit-dot"></span> Automation</div>
115
116
  <div class="cockpit-value dim">0</div>
116
- <div class="cockpit-detail">no watches set</div>
117
+ <div class="cockpit-detail">no automation set</div>
117
118
  </div>
118
119
  <div class="cockpit-tile" data-tile="knowledge">
119
120
  <button id="slim-tile-pin-chip" class="linkpr-chip on-tile" type="button" title="Pin content">+ Pin</button>
@@ -161,7 +162,7 @@
161
162
  <p>A focused, single-screen cockpit for your Minions team. Here&rsquo;s what&rsquo;s new:</p>
162
163
  <ul class="welcome-list">
163
164
  <li><strong>Command Center front and center</strong> &mdash; chat with your team from the main panel, with multi-tab conversations that carry over from the classic dashboard.</li>
164
- <li><strong>Live status tiles</strong> &mdash; engine, dispatches, queue, PRs and watches at a glance. Click any tile for details.</li>
165
+ <li><strong>Live status tiles</strong> &mdash; engine, dispatches, queue, PRs and automation at a glance. Click any tile for details.</li>
165
166
  <li><strong>Your team</strong> &mdash; every agent as a card with live status.</li>
166
167
  <li><strong>Recent activity</strong> &mdash; a running feed of completions and pull requests.</li>
167
168
  </ul>
@@ -426,4 +427,3 @@
426
427
  <!-- Floating toast target for the reused panel's charter-save / steering
427
428
  feedback (showToast in panel-bootstrap.js routes here). -->
428
429
  <div class="cmd-toast" id="cmd-toast" role="status" aria-live="polite"></div>
429
-
@@ -116,6 +116,7 @@
116
116
  var tabId = _ensureActiveTab().id;
117
117
 
118
118
  var msgsEl = document.getElementById('chat-messages');
119
+ var suggestionsEl = document.getElementById('chat-suggestions-host');
119
120
  var inputEl = document.getElementById('chat-input');
120
121
  var sendBtn = document.getElementById('chat-send');
121
122
  var stopBtn = document.getElementById('chat-stop');
@@ -307,7 +308,7 @@
307
308
  }
308
309
 
309
310
  function clearEmpty() {
310
- clearCcSuggestedPrompts(msgsEl);
311
+ clearCcSuggestedPrompts(suggestionsEl);
311
312
  var empty = msgsEl.querySelector('.chat-empty');
312
313
  if (empty) empty.remove();
313
314
  }
@@ -626,9 +627,10 @@
626
627
 
627
628
  function rerenderHistory() {
628
629
  msgsEl.innerHTML = '';
630
+ clearCcSuggestedPrompts(suggestionsEl);
629
631
  queueEl = null; // detached by the innerHTML reset; renderQueue rebuilds it
630
632
  if (!messages.length) {
631
- renderCcSuggestedPrompts(msgsEl, function(prompt) {
633
+ renderCcSuggestedPrompts(suggestionsEl, function(prompt) {
632
634
  inputEl.value = prompt;
633
635
  sendMessage();
634
636
  });
@@ -506,8 +506,15 @@
506
506
  }
507
507
  // Resolve to `fallback` instead of rejecting, so one transient endpoint
508
508
  // error doesn't blank the whole cockpit — we keep the last-known slice.
509
- function _slimSettle(url, fallback) {
510
- return _slimFetchJson(url).then(function(v) { return v; }, function() { return fallback; });
509
+ // `map` optionally unwraps an enveloped payload (e.g. /api/watches returns
510
+ // `{ watches: [...] }`); a null mapping is treated as unusable data and also
511
+ // falls back to the last-known slice.
512
+ function _slimSettle(url, fallback, map) {
513
+ return _slimFetchJson(url).then(function(v) {
514
+ if (!map) return v;
515
+ var mapped = map(v);
516
+ return mapped == null ? fallback : mapped;
517
+ }, function() { return fallback; });
511
518
  }
512
519
 
513
520
  async function pollStatusOnce() {
@@ -522,7 +529,7 @@
522
529
  _slimSettle('/api/agents', prev.agents),
523
530
  _slimSettle('/api/pull-requests', prev.pullRequests),
524
531
  _slimSettle('/api/pinned', prev.pinned),
525
- _slimSettle('/state/engine/watches.json', prev.watches),
532
+ _slimSettle(MinionsWatchesSource.ENDPOINT, prev.watches, MinionsWatchesSource.unwrap),
526
533
  _slimSettle('/api/work-items', prev.workItems),
527
534
  ]);
528
535
  var data = results[0] || {};
@@ -180,18 +180,85 @@
180
180
  body.appendChild(frame);
181
181
  }
182
182
 
183
- function renderWatchTileBody(body, data) {
184
- var watches = Array.isArray(data.watches) ? data.watches : [];
185
- if (!watches.length) { tileEmpty(body, 'No watches configured.'); return; }
186
- watches.forEach(function(w) {
187
- var st = w.status || '';
188
- var cls = st === 'active' ? 'blue' : (st === 'triggered' ? 'green' : (st === 'expired' ? 'red' : 'amber'));
189
- body.appendChild(tileCard({
190
- title: (w.targetType ? w.targetType + ': ' : '') + (w.target || w.id || '(target)'),
191
- meta: [w.condition ? 'when ' + w.condition : null, w.interval ? 'every ' + w.interval + ' ticks' : null].filter(Boolean).join(' · '),
192
- chip: { text: st || '—', cls: cls },
193
- }));
183
+ // Automation tile body — a tabbed composite (Watches · Schedules · Pipelines)
184
+ // that reuses the LITERAL classic dashboard screens (/watches, /schedule,
185
+ // /pipelines) instead of a slim-specific list, so there is one Automation UI,
186
+ // not two (W-mrz0x2a400030f90 / "reuse, don't fork" — mirrors renderPlansBody
187
+ // for the Plans tile and renderPrsBody for the PRs tile). Each tab embeds the
188
+ // real classic screen in an iframe with the chrome-off ?embed=1 mode; the
189
+ // iframed page IS the classic screen — full create/edit/pause/run + row
190
+ // actions, backed by the same /api/{watches,schedules,pipelines} endpoints
191
+ // with zero duplicated rendering logic. Classic is reachable at each route
192
+ // even with slim-ux ON (only / is taken over). Iframes are LAZY: each tab's
193
+ // frame is mounted only on first activation (default Watches), so opening the
194
+ // modal never triple-loads all three screens. Built with createElement (no
195
+ // innerHTML) to satisfy the dashboard no-unsanitized lint gate; tab buttons
196
+ // reuse the shared .kn-tab pill primitive and carry role="tab"/aria-selected
197
+ // for keyboard/AT parity with the Knowledge modal tabs.
198
+ function renderAutomationTileBody(body) {
199
+ var TABS = [
200
+ { key: 'watches', label: 'Watches', src: '/watches?embed=1' },
201
+ { key: 'schedule', label: 'Schedules', src: '/schedule?embed=1' },
202
+ { key: 'pipelines', label: 'Pipelines', src: '/pipelines?embed=1' },
203
+ ];
204
+ var tablist = document.createElement('div');
205
+ tablist.className = 'kn-tabs slim-automation-tabs';
206
+ tablist.setAttribute('role', 'tablist');
207
+ tablist.setAttribute('aria-label', 'Automation surfaces');
208
+
209
+ var panes = document.createElement('div');
210
+ panes.className = 'slim-automation-panes';
211
+
212
+ var btns = {};
213
+ var panels = {};
214
+ var frames = {};
215
+
216
+ function activate(key) {
217
+ TABS.forEach(function(t) {
218
+ var isActive = t.key === key;
219
+ var btn = btns[t.key];
220
+ btn.classList.toggle('active', isActive);
221
+ btn.setAttribute('aria-selected', isActive ? 'true' : 'false');
222
+ btn.setAttribute('tabindex', isActive ? '0' : '-1');
223
+ var panel = panels[t.key];
224
+ panel.hidden = !isActive;
225
+ // Lazy-mount: only build the iframe on first activation of its tab.
226
+ if (isActive && !frames[t.key]) {
227
+ var frame = document.createElement('iframe');
228
+ frame.className = 'slim-automation-embed';
229
+ frame.src = t.src;
230
+ frame.title = t.label;
231
+ panel.appendChild(frame);
232
+ frames[t.key] = frame;
233
+ }
234
+ });
235
+ }
236
+
237
+ TABS.forEach(function(t) {
238
+ var btn = document.createElement('button');
239
+ btn.type = 'button';
240
+ btn.className = 'kn-tab';
241
+ btn.id = 'slim-automation-tab-' + t.key;
242
+ btn.textContent = t.label;
243
+ btn.setAttribute('role', 'tab');
244
+ btn.setAttribute('aria-controls', 'slim-automation-panel-' + t.key);
245
+ btn.addEventListener('click', function() { activate(t.key); });
246
+ btns[t.key] = btn;
247
+ tablist.appendChild(btn);
248
+
249
+ var panel = document.createElement('div');
250
+ panel.className = 'slim-automation-panel';
251
+ panel.id = 'slim-automation-panel-' + t.key;
252
+ panel.setAttribute('role', 'tabpanel');
253
+ panel.setAttribute('aria-labelledby', btn.id);
254
+ panel.hidden = true;
255
+ panels[t.key] = panel;
256
+ panes.appendChild(panel);
194
257
  });
258
+
259
+ body.appendChild(tablist);
260
+ body.appendChild(panes);
261
+ activate('watches'); // default to the Watches tab
195
262
  }
196
263
 
197
264
  // tile key -> { title, render }. Adding a tile is a one-line table edit.
@@ -201,7 +268,7 @@
201
268
  queued: { title: 'Queued work', render: function(body) { renderQueuedWorkBody(body); } },
202
269
  plans: { title: 'Plans', render: function(body) { renderPlansBody(body); } },
203
270
  prs: { title: 'Pull requests', render: function(body) { renderPrsBody(body); } },
204
- watches: { title: 'Watches', render: renderWatchTileBody },
271
+ automation: { title: 'Automation', render: function(body) { renderAutomationTileBody(body); } },
205
272
  };
206
273
 
207
274
  // Populate + open the cockpit-tile detail modal from the latest status
@@ -221,14 +288,16 @@
221
288
  // The "+ Link PR" header chip only applies to the PR list.
222
289
  var headerChip = document.getElementById('slim-tile-modal-linkpr');
223
290
  if (headerChip) headerChip.style.display = (key === 'prs') ? '' : 'none';
224
- // The engine, queued, plans + prs tiles embed a full classic screen
225
- // (/engine, /work, /plans, /prs) in an iframe — widen the modal + drop the
226
- // body padding so the embedded screen gets real estate. All four reuse the
227
- // shared wide-modal treatment.
291
+ // The engine, queued, plans, prs + automation tiles embed a full classic
292
+ // screen (/engine, /work, /plans, /prs, and the /watches·/schedule·
293
+ // /pipelines tabs) in an iframe widen the modal + drop the body padding so
294
+ // the embedded screen gets real estate. All reuse the shared wide-modal
295
+ // treatment.
228
296
  modal.classList.toggle('tile-modal--engine', key === 'engine');
229
297
  modal.classList.toggle('tile-modal--work', key === 'queued');
230
298
  modal.classList.toggle('tile-modal--plans', key === 'plans');
231
299
  modal.classList.toggle('tile-modal--prs', key === 'prs');
300
+ modal.classList.toggle('tile-modal--automation', key === 'automation');
232
301
  view.render(body, lastStatusData || {});
233
302
  modal.classList.add('open');
234
303
  }