@yemi33/minions 0.1.2212 → 0.1.2214
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/command-center.js +161 -4
- package/dashboard/js/command-input.js +21 -1
- package/dashboard/js/render-work-items.js +9 -0
- package/dashboard/layout.html +5 -3
- package/dashboard/pages/home.html +1 -1
- package/dashboard/styles.css +14 -0
- package/dashboard.js +208 -33
- package/docs/command-center.md +39 -0
- package/docs/self-improvement.md +2 -2
- package/engine/cc-worker-pool.js +28 -11
- package/engine/cli.js +2 -2
- package/engine/db-events.js +2 -4
- package/engine/dispatch.js +1 -0
- package/engine/lifecycle.js +9 -1
- package/engine/llm.js +74 -6
- package/engine/runtimes/claude.js +71 -7
- package/engine/runtimes/codex.js +3 -0
- package/engine/runtimes/copilot.js +5 -0
- package/engine/shared.js +1 -0
- package/engine/stdio-timestamps.js +1 -2
- package/engine.js +45 -3
- package/package.json +1 -1
|
@@ -17,6 +17,158 @@ var _ccSending = false; // true if active tab is sending (UI indicator only)
|
|
|
17
17
|
// Clear stale sending state on page load — SSE streams don't survive refresh
|
|
18
18
|
try { localStorage.removeItem('cc-sending'); } catch {}
|
|
19
19
|
|
|
20
|
+
// ── P-2a6d8e74 — Image attachments (paste / drag-drop) ──────────────────────
|
|
21
|
+
// Pending images for the NEXT CC turn. Each entry: { mimeType, dataBase64,
|
|
22
|
+
// filename, dataUrl }. dataBase64 is the raw (prefix-stripped) base64 sent to
|
|
23
|
+
// the server; dataUrl (full data: URI) is for the local thumbnail only.
|
|
24
|
+
// Cleared after every dispatch (v1 sends images for the current turn only).
|
|
25
|
+
var _ccAttachments = [];
|
|
26
|
+
// Client-side limits MIRROR the server (dashboard.js _validateCcImages) — they
|
|
27
|
+
// are UX-only pre-screening; the server remains the single trust boundary.
|
|
28
|
+
var CC_IMG_MAX_COUNT = 4;
|
|
29
|
+
var CC_IMG_MAX_BYTES = 5 * 1024 * 1024; // 5 MB decoded, per image
|
|
30
|
+
var CC_IMG_MIME_ALLOW = ['image/png', 'image/jpeg', 'image/gif', 'image/webp'];
|
|
31
|
+
|
|
32
|
+
// Read one File/Blob into the attachment shape. Resolves { error } on a
|
|
33
|
+
// client-side limit miss so the caller can toast and skip it.
|
|
34
|
+
function _ccReadImageFile(file) {
|
|
35
|
+
return new Promise(function(resolve) {
|
|
36
|
+
var mime = String(file && file.type || '').toLowerCase();
|
|
37
|
+
if (CC_IMG_MIME_ALLOW.indexOf(mime) === -1) { resolve({ error: 'Unsupported image type' + (mime ? ': ' + mime : '') }); return; }
|
|
38
|
+
if (file.size > CC_IMG_MAX_BYTES) { resolve({ error: (file.name || 'image') + ' is too large (max 5 MB)' }); return; }
|
|
39
|
+
var reader = new FileReader();
|
|
40
|
+
reader.onload = function() {
|
|
41
|
+
var result = String(reader.result || '');
|
|
42
|
+
var comma = result.indexOf(',');
|
|
43
|
+
var dataBase64 = comma >= 0 ? result.slice(comma + 1) : '';
|
|
44
|
+
if (!dataBase64) { resolve({ error: 'Could not read image data' }); return; }
|
|
45
|
+
resolve({ mimeType: mime, dataBase64: dataBase64, filename: file.name || 'pasted-image.png', dataUrl: result });
|
|
46
|
+
};
|
|
47
|
+
reader.onerror = function() { resolve({ error: 'Could not read ' + (file.name || 'image') }); };
|
|
48
|
+
reader.readAsDataURL(file);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Add image files (from paste, drop, or the command bar). Non-images are
|
|
53
|
+
// ignored; over-limit / oversized files are toasted and skipped. Exposed so
|
|
54
|
+
// command-input.js can forward clipboard images into the CC drawer.
|
|
55
|
+
async function ccAddImageFiles(fileList) {
|
|
56
|
+
var files = [];
|
|
57
|
+
for (var i = 0; i < (fileList ? fileList.length : 0); i++) {
|
|
58
|
+
var f = fileList[i];
|
|
59
|
+
if (f && String(f.type || '').indexOf('image/') === 0) files.push(f);
|
|
60
|
+
}
|
|
61
|
+
if (!files.length) return false;
|
|
62
|
+
var added = 0;
|
|
63
|
+
for (var j = 0; j < files.length; j++) {
|
|
64
|
+
if (_ccAttachments.length >= CC_IMG_MAX_COUNT) { showToast('cmd-toast', 'Max ' + CC_IMG_MAX_COUNT + ' images per message', false); break; }
|
|
65
|
+
var res = await _ccReadImageFile(files[j]);
|
|
66
|
+
if (res.error) { showToast('cmd-toast', res.error, false); continue; }
|
|
67
|
+
_ccAttachments.push(res);
|
|
68
|
+
added++;
|
|
69
|
+
}
|
|
70
|
+
if (added) _ccRenderAttachments();
|
|
71
|
+
return added > 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function ccRemoveAttachment(idx) {
|
|
75
|
+
if (idx >= 0 && idx < _ccAttachments.length) { _ccAttachments.splice(idx, 1); _ccRenderAttachments(); }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function _ccClearAttachments() {
|
|
79
|
+
if (!_ccAttachments.length) return;
|
|
80
|
+
_ccAttachments = [];
|
|
81
|
+
_ccRenderAttachments();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Render the thumbnail chips above the input. Built with createElement +
|
|
85
|
+
// textContent / .src (no innerHTML) so untrusted filenames and base64 data
|
|
86
|
+
// can never become an XSS sink and the no-unsanitized lint gate stays clean.
|
|
87
|
+
function _ccRenderAttachments() {
|
|
88
|
+
var box = document.getElementById('cc-attachments');
|
|
89
|
+
if (!box) return;
|
|
90
|
+
box.textContent = '';
|
|
91
|
+
if (!_ccAttachments.length) { box.style.display = 'none'; return; }
|
|
92
|
+
box.style.display = 'flex';
|
|
93
|
+
_ccAttachments.forEach(function(att, i) {
|
|
94
|
+
var chip = document.createElement('div');
|
|
95
|
+
chip.className = 'cc-attach-chip';
|
|
96
|
+
var img = document.createElement('img');
|
|
97
|
+
img.className = 'cc-attach-thumb';
|
|
98
|
+
img.src = att.dataUrl;
|
|
99
|
+
img.alt = att.filename;
|
|
100
|
+
var name = document.createElement('span');
|
|
101
|
+
name.className = 'cc-attach-name';
|
|
102
|
+
name.textContent = att.filename;
|
|
103
|
+
name.title = att.filename;
|
|
104
|
+
var rm = document.createElement('button');
|
|
105
|
+
rm.className = 'cc-attach-remove';
|
|
106
|
+
rm.type = 'button';
|
|
107
|
+
rm.title = 'Remove';
|
|
108
|
+
rm.textContent = '×';
|
|
109
|
+
rm.onclick = function() { ccRemoveAttachment(i); };
|
|
110
|
+
chip.appendChild(img);
|
|
111
|
+
chip.appendChild(name);
|
|
112
|
+
chip.appendChild(rm);
|
|
113
|
+
box.appendChild(chip);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Snapshot the pending attachments as the server payload shape
|
|
118
|
+
// ([{ mimeType, dataBase64, filename }] — no local-only dataUrl).
|
|
119
|
+
function _ccImagesPayload() {
|
|
120
|
+
return _ccAttachments.map(function(a) { return { mimeType: a.mimeType, dataBase64: a.dataBase64, filename: a.filename }; });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Small "📎 N image(s) attached" note appended to the user bubble so a sent
|
|
124
|
+
// turn visibly records its attachments. n is a number — safe to interpolate.
|
|
125
|
+
function _ccAttachmentBadge(n) {
|
|
126
|
+
if (!n) return '';
|
|
127
|
+
return '<div class="cc-msg-attach-note">📎 ' + n + ' image' + (n === 1 ? '' : 's') + ' attached</div>';
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Inline event handlers wired from layout.html. Paste intercepts image
|
|
131
|
+
// clipboard items (and lets normal text paste through); drag/drop accepts
|
|
132
|
+
// image files dropped anywhere on the drawer.
|
|
133
|
+
function ccHandlePaste(e) {
|
|
134
|
+
var items = (e.clipboardData && e.clipboardData.items) || [];
|
|
135
|
+
var imgs = [];
|
|
136
|
+
for (var i = 0; i < items.length; i++) {
|
|
137
|
+
if (items[i] && items[i].kind === 'file' && String(items[i].type || '').indexOf('image/') === 0) {
|
|
138
|
+
var f = items[i].getAsFile();
|
|
139
|
+
if (f) imgs.push(f);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (!imgs.length) return; // no image — allow the default text paste
|
|
143
|
+
e.preventDefault();
|
|
144
|
+
ccAddImageFiles(imgs);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function ccHandleDragOver(e) {
|
|
148
|
+
var types = (e.dataTransfer && e.dataTransfer.types) || [];
|
|
149
|
+
if (Array.prototype.indexOf.call(types, 'Files') === -1) return;
|
|
150
|
+
e.preventDefault();
|
|
151
|
+
if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';
|
|
152
|
+
var dz = document.getElementById('cc-drawer');
|
|
153
|
+
if (dz) dz.classList.add('cc-dragover');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function ccHandleDragLeave() {
|
|
157
|
+
var dz = document.getElementById('cc-drawer');
|
|
158
|
+
if (dz) dz.classList.remove('cc-dragover');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function ccHandleDrop(e) {
|
|
162
|
+
var dz = document.getElementById('cc-drawer');
|
|
163
|
+
if (dz) dz.classList.remove('cc-dragover');
|
|
164
|
+
var files = (e.dataTransfer && e.dataTransfer.files) || [];
|
|
165
|
+
var hasImage = false;
|
|
166
|
+
for (var i = 0; i < files.length; i++) { if (String(files[i].type || '').indexOf('image/') === 0) { hasImage = true; break; } }
|
|
167
|
+
if (!hasImage) return; // let non-image drops fall through
|
|
168
|
+
e.preventDefault();
|
|
169
|
+
ccAddImageFiles(files);
|
|
170
|
+
}
|
|
171
|
+
|
|
20
172
|
function _ccStripActionBlockFromText(value) {
|
|
21
173
|
var text = value || '';
|
|
22
174
|
if (!text) return text;
|
|
@@ -947,6 +1099,11 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
|
|
|
947
1099
|
var activeTabId = forceTabId || _ccActiveTabId;
|
|
948
1100
|
var activeTab = _ccTabs.find(function(t) { return t.id === activeTabId; }) || _ccActiveTab();
|
|
949
1101
|
if (!activeTab) return;
|
|
1102
|
+
// P-2a6d8e74 — snapshot + clear pending image attachments for this turn.
|
|
1103
|
+
// Only fresh user sends carry images (retries/programmatic sends set
|
|
1104
|
+
// skipUserMsg and reuse the already-dispatched text). v1 = current turn only.
|
|
1105
|
+
var sendImages = (!skipUserMsg && _ccAttachments.length) ? _ccImagesPayload() : [];
|
|
1106
|
+
if (sendImages.length) _ccClearAttachments();
|
|
950
1107
|
activeTab._sending = true;
|
|
951
1108
|
activeTab._sendStartedAt = Date.now();
|
|
952
1109
|
activeTab._abortController = new AbortController();
|
|
@@ -959,7 +1116,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
|
|
|
959
1116
|
// Scoped helper — always targets the originating tab, even if user switches tabs
|
|
960
1117
|
function addMsg(role, html, skipSave, meta) { ccAddMessage(role, html, skipSave, activeTabId, meta); }
|
|
961
1118
|
|
|
962
|
-
if (!skipUserMsg) addMsg('user', escHtml(message));
|
|
1119
|
+
if (!skipUserMsg) addMsg('user', escHtml(message) + _ccAttachmentBadge(sendImages.length));
|
|
963
1120
|
|
|
964
1121
|
// Remove queue indicator before processing (it'll be re-added if more queued)
|
|
965
1122
|
var existingQueueEl = document.getElementById('cc-queue-indicator');
|
|
@@ -1067,7 +1224,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
|
|
|
1067
1224
|
if (!isReconnect && res.status === 429 && (!activeTab._429retries || activeTab._429retries < 3)) {
|
|
1068
1225
|
activeTab._429retries = (activeTab._429retries || 0) + 1;
|
|
1069
1226
|
await new Promise(function(r) { setTimeout(r, 1500); });
|
|
1070
|
-
return await _ccConsumeStream({ message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null, transcript: _ccBuildTranscript(activeTab), intentMetadata: intentMetadata || null }, false);
|
|
1227
|
+
return await _ccConsumeStream({ message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null, images: sendImages.length ? sendImages : undefined, transcript: _ccBuildTranscript(activeTab), intentMetadata: intentMetadata || null }, false);
|
|
1071
1228
|
}
|
|
1072
1229
|
activeTab._429retries = 0;
|
|
1073
1230
|
var errText = await res.text();
|
|
@@ -1263,7 +1420,7 @@ async function _ccDoSend(message, skipUserMsg, forceTabId, intentMetadata) {
|
|
|
1263
1420
|
while (true) {
|
|
1264
1421
|
var consume = await _ccConsumeStream(
|
|
1265
1422
|
reconnectAttempts === 0
|
|
1266
|
-
? { message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null, transcript: _ccBuildTranscript(activeTab), intentMetadata: intentMetadata || null }
|
|
1423
|
+
? { message: message, tabId: activeTabId, sessionId: activeTab.sessionId || null, transcript: _ccBuildTranscript(activeTab), intentMetadata: intentMetadata || null, images: sendImages.length ? sendImages : undefined }
|
|
1267
1424
|
: { tabId: activeTabId, sessionId: activeTab.sessionId || null, reconnect: true },
|
|
1268
1425
|
reconnectAttempts > 0
|
|
1269
1426
|
);
|
|
@@ -2193,4 +2350,4 @@ if (document.readyState === 'loading') {
|
|
|
2193
2350
|
ccInitResize();
|
|
2194
2351
|
}
|
|
2195
2352
|
|
|
2196
|
-
window.MinionsCC = { toggleCommandCenter, ccNewSession, ccNewTab, ccSwitchTab, ccCloseTab, ccRenderTabBar, ccRestoreMessages, ccSaveState, ccUpdateSessionIndicator, ccAddMessage, ccSend, ccAbort, ccExecuteAction, ccPrActionFollowup };
|
|
2353
|
+
window.MinionsCC = { toggleCommandCenter, ccNewSession, ccNewTab, ccSwitchTab, ccCloseTab, ccRenderTabBar, ccRestoreMessages, ccSaveState, ccUpdateSessionIndicator, ccAddMessage, ccSend, ccAbort, ccExecuteAction, ccPrActionFollowup, ccAddImageFiles, ccRemoveAttachment, ccHandlePaste, ccHandleDragOver, ccHandleDragLeave, ccHandleDrop };
|
|
@@ -146,6 +146,26 @@ async function cmdSubmit() {
|
|
|
146
146
|
return;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// P-2a6d8e74 — paste a screenshot into the top command bar: open the Command
|
|
150
|
+
// Center drawer and forward the clipboard images into its attachment tray.
|
|
151
|
+
// Non-image pastes fall through to the default textarea behavior.
|
|
152
|
+
function cmdHandlePaste(e) {
|
|
153
|
+
const items = (e.clipboardData && e.clipboardData.items) || [];
|
|
154
|
+
const imgs = [];
|
|
155
|
+
for (let i = 0; i < items.length; i++) {
|
|
156
|
+
if (items[i] && items[i].kind === 'file' && String(items[i].type || '').indexOf('image/') === 0) {
|
|
157
|
+
const f = items[i].getAsFile();
|
|
158
|
+
if (f) imgs.push(f);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (!imgs.length) return; // text paste — leave it to the textarea
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
if (!_ccOpen) toggleCommandCenter();
|
|
164
|
+
if (window.MinionsCC && typeof window.MinionsCC.ccAddImageFiles === 'function') {
|
|
165
|
+
window.MinionsCC.ccAddImageFiles(imgs);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
149
169
|
// Render the parsed meta chips below input
|
|
150
170
|
function cmdRenderMeta() {
|
|
151
171
|
const el = document.getElementById('cmd-meta');
|
|
@@ -272,4 +292,4 @@ function cmdInsertPopupItem(id) {
|
|
|
272
292
|
cmdRenderMeta();
|
|
273
293
|
}
|
|
274
294
|
|
|
275
|
-
window.MinionsCmdInput = { cmdAutoResize, cmdUpdateHighlight, syncHighlightScroll, cmdInputChanged, cmdKeyDown, cmdSubmit, cmdRenderMeta, cmdShowMentions, cmdShowProjects, cmdHidePopup, cmdInsertPopupItem };
|
|
295
|
+
window.MinionsCmdInput = { cmdAutoResize, cmdUpdateHighlight, syncHighlightScroll, cmdInputChanged, cmdKeyDown, cmdSubmit, cmdRenderMeta, cmdShowMentions, cmdShowProjects, cmdHidePopup, cmdInsertPopupItem, cmdHandlePaste };
|
|
@@ -699,6 +699,9 @@ function _wiRenderDetail(item) {
|
|
|
699
699
|
return ' <span class="pr-badge needs-attention" style="font-size:var(--text-xs);margin-left:4px" title="See "Why this is blocked" below">⚠ Needs attention</span>';
|
|
700
700
|
})() +
|
|
701
701
|
'</div>';
|
|
702
|
+
// Work item id — shown in the modal body (not just the title) so operators can
|
|
703
|
+
// read/copy the canonical id (W-… or sched-…) when deeplinking in from a note.
|
|
704
|
+
html += field('ID', '<code style="font-size:var(--text-sm);background:var(--surface2);padding:2px 6px;border-radius:var(--radius-sm)">' + escapeHtml(item.id || '—') + '</code>');
|
|
702
705
|
// W-mq08kuog001110a6 — "Why this is blocked" section, rendered FIRST so the
|
|
703
706
|
// human eye lands on the unblocking reason before scrolling through agent,
|
|
704
707
|
// source, dates, etc. _preDispatchEval reasons are evaluator prose and can
|
|
@@ -1073,6 +1076,12 @@ function openInboxNote(filename) {
|
|
|
1073
1076
|
titleEl.textContent = filename + ' (archived)';
|
|
1074
1077
|
// eslint-disable-next-line no-unsanitized/property -- reason: renderMd() escapes note content before assembling HTML; filename is set via textContent
|
|
1075
1078
|
bodyEl.innerHTML = '<div style="font-size:var(--text-md);line-height:1.7;color:var(--muted)">' + renderMd(content) + '</div>';
|
|
1079
|
+
// Wire Doc Chat for the deeplinked archive note, mirroring the live-inbox
|
|
1080
|
+
// openModal() path (render-prs.js). Without this the archive branch opened
|
|
1081
|
+
// the note as plain text with no Q&A panel, so deeplinking into a note
|
|
1082
|
+
// from a (completed) work item sometimes silently dropped doc-chat.
|
|
1083
|
+
_modalDocContext = { title: filename + ' (archived)', content: content, selection: '' };
|
|
1084
|
+
_modalFilePath = 'notes/archive/' + filename; showModalQa();
|
|
1076
1085
|
document.getElementById('modal').classList.add('open');
|
|
1077
1086
|
// P-34fa5d79 — Source-WI chip for archive notes. Same convention as the
|
|
1078
1087
|
// live-inbox branch above; parsed off the freshly-fetched content.
|
package/dashboard/layout.html
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
|
|
45
45
|
<!-- Command Center Drawer -->
|
|
46
46
|
<div id="cc-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:340" onclick="toggleCommandCenter()"></div>
|
|
47
|
-
<div id="cc-drawer" style="display:none;position:fixed;top:0;right:0;bottom:0;width:420px;background:var(--surface);border-left:1px solid var(--border);z-index:350;flex-direction:column;overscroll-behavior:contain">
|
|
47
|
+
<div id="cc-drawer" style="display:none;position:fixed;top:0;right:0;bottom:0;width:420px;background:var(--surface);border-left:1px solid var(--border);z-index:350;flex-direction:column;overscroll-behavior:contain" ondragover="ccHandleDragOver(event)" ondragleave="ccHandleDragLeave(event)" ondrop="ccHandleDrop(event)">
|
|
48
48
|
<div id="cc-resize-handle" class="cc-resize-handle"></div>
|
|
49
49
|
<div style="padding:12px 16px;border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between">
|
|
50
50
|
<div style="display:flex;align-items:center;gap:8px">
|
|
@@ -59,11 +59,13 @@
|
|
|
59
59
|
<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>
|
|
60
60
|
<div id="cc-messages" style="flex:1;overflow-y:auto;padding:12px 16px;display:flex;flex-direction:column;gap:10px"></div>
|
|
61
61
|
<div style="padding:12px 16px;border-top:1px solid var(--border)">
|
|
62
|
+
<!-- P-2a6d8e74 — image attachment thumbnail chips (paste / drag-drop); hidden until images are attached -->
|
|
63
|
+
<div id="cc-attachments" style="display:none;flex-wrap:wrap;gap:6px;margin-bottom:8px"></div>
|
|
62
64
|
<div style="display:flex;gap:8px">
|
|
63
|
-
<textarea id="cc-input" rows="2" placeholder="Ask anything or give a command..." 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" onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();ccSend()}"></textarea>
|
|
65
|
+
<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>
|
|
64
66
|
<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>
|
|
65
67
|
</div>
|
|
66
|
-
<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.</div>
|
|
68
|
+
<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>
|
|
67
69
|
</div>
|
|
68
70
|
</div>
|
|
69
71
|
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
<div class="cmd-input-wrap" id="cmd-input-wrap">
|
|
4
4
|
<div class="cmd-highlight-layer" id="cmd-highlight" aria-hidden="true"></div>
|
|
5
5
|
<textarea id="cmd-input" rows="1" placeholder='What do you need? e.g. "Fix the auth bug @dallas", "explain the dispatch flow", or "/note always use feature flags"'
|
|
6
|
-
oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)" onscroll="syncHighlightScroll()"></textarea>
|
|
6
|
+
oninput="cmdInputChanged()" onkeydown="cmdKeyDown(event)" onscroll="syncHighlightScroll()" onpaste="cmdHandlePaste(event)"></textarea>
|
|
7
7
|
<button class="cmd-send-btn" id="cmd-send-btn" onclick="cmdSubmit()">Send <kbd>Ctrl+Enter</kbd></button>
|
|
8
8
|
</div>
|
|
9
9
|
<div class="cmd-mention-popup" id="cmd-mention-popup"></div>
|
package/dashboard/styles.css
CHANGED
|
@@ -1146,6 +1146,20 @@
|
|
|
1146
1146
|
.cc-resize-handle:hover::after, .cc-resize-handle.active::after { opacity: 1; background: var(--blue); }
|
|
1147
1147
|
body.cc-resizing { cursor: col-resize !important; user-select: none !important; }
|
|
1148
1148
|
|
|
1149
|
+
/* ── P-2a6d8e74 — CC image attachment chips + drag-drop affordance ───── */
|
|
1150
|
+
#cc-drawer.cc-dragover { outline: 2px dashed var(--blue); outline-offset: -2px; }
|
|
1151
|
+
.cc-attach-chip { display: inline-flex; align-items: center; gap: 6px; max-width: 180px;
|
|
1152
|
+
padding: 4px 6px; background: var(--surface2); border: 1px solid var(--border);
|
|
1153
|
+
border-radius: var(--radius-sm); font-size: var(--text-xs); }
|
|
1154
|
+
.cc-attach-thumb { width: 28px; height: 28px; object-fit: cover; border-radius: 3px;
|
|
1155
|
+
flex-shrink: 0; background: var(--bg); }
|
|
1156
|
+
.cc-attach-name { color: var(--muted); white-space: nowrap; overflow: hidden;
|
|
1157
|
+
text-overflow: ellipsis; }
|
|
1158
|
+
.cc-attach-remove { flex-shrink: 0; background: none; border: none; color: var(--muted);
|
|
1159
|
+
cursor: pointer; font-size: var(--text-md); line-height: 1; padding: 0 2px; }
|
|
1160
|
+
.cc-attach-remove:hover { color: var(--red); }
|
|
1161
|
+
.cc-msg-attach-note { margin-top: 4px; font-size: var(--text-xs); opacity: 0.85; }
|
|
1162
|
+
|
|
1149
1163
|
.modal-body { padding: var(--space-7) var(--space-8); overflow-y: auto; overflow-x: auto; white-space: pre-wrap; font-size: var(--text-md); line-height: 1.7; color: var(--muted); font-family: Consolas, monospace; }
|
|
1150
1164
|
|
|
1151
1165
|
/* ── Settings: left-rail tabbed layout (W-mpmwxkcn000646cc) ─────────────
|