@oratis/lisa 0.12.0 → 0.14.0
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/dist/agent.d.ts +2 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +20 -1
- package/dist/agent.js.map +1 -1
- package/dist/autonomy/state.d.ts +14 -0
- package/dist/autonomy/state.d.ts.map +1 -0
- package/dist/autonomy/state.js +54 -0
- package/dist/autonomy/state.js.map +1 -0
- package/dist/cli/mail.d.ts +2 -0
- package/dist/cli/mail.d.ts.map +1 -0
- package/dist/cli/mail.js +209 -0
- package/dist/cli/mail.js.map +1 -0
- package/dist/cli/pair.d.ts +1 -5
- package/dist/cli/pair.d.ts.map +1 -1
- package/dist/cli/pair.js +58 -20
- package/dist/cli/pair.js.map +1 -1
- package/dist/cli.js +15 -14
- package/dist/cli.js.map +1 -1
- package/dist/consent/store.d.ts +2 -2
- package/dist/consent/store.d.ts.map +1 -1
- package/dist/consent/store.js +3 -2
- package/dist/consent/store.js.map +1 -1
- package/dist/edition.d.ts +24 -0
- package/dist/edition.d.ts.map +1 -0
- package/dist/edition.js +19 -0
- package/dist/edition.js.map +1 -0
- package/dist/heartbeat/runner.d.ts.map +1 -1
- package/dist/heartbeat/runner.js +8 -0
- package/dist/heartbeat/runner.js.map +1 -1
- package/dist/idle/runner.d.ts.map +1 -1
- package/dist/idle/runner.js +7 -0
- package/dist/idle/runner.js.map +1 -1
- package/dist/mail/accounts.d.ts +20 -0
- package/dist/mail/accounts.d.ts.map +1 -0
- package/dist/mail/accounts.js +113 -0
- package/dist/mail/accounts.js.map +1 -0
- package/dist/mail/alerts.d.ts +29 -0
- package/dist/mail/alerts.d.ts.map +1 -0
- package/dist/mail/alerts.js +40 -0
- package/dist/mail/alerts.js.map +1 -0
- package/dist/mail/classify.d.ts +26 -0
- package/dist/mail/classify.d.ts.map +1 -0
- package/dist/mail/classify.js +191 -0
- package/dist/mail/classify.js.map +1 -0
- package/dist/mail/connectors/gmail.d.ts +55 -0
- package/dist/mail/connectors/gmail.d.ts.map +1 -0
- package/dist/mail/connectors/gmail.js +87 -0
- package/dist/mail/connectors/gmail.js.map +1 -0
- package/dist/mail/connectors/imap.d.ts +13 -0
- package/dist/mail/connectors/imap.d.ts.map +1 -0
- package/dist/mail/connectors/imap.js +136 -0
- package/dist/mail/connectors/imap.js.map +1 -0
- package/dist/mail/digest.d.ts +21 -0
- package/dist/mail/digest.d.ts.map +1 -0
- package/dist/mail/digest.js +89 -0
- package/dist/mail/digest.js.map +1 -0
- package/dist/mail/google-oauth.d.ts +50 -0
- package/dist/mail/google-oauth.d.ts.map +1 -0
- package/dist/mail/google-oauth.js +74 -0
- package/dist/mail/google-oauth.js.map +1 -0
- package/dist/mail/hosts.d.ts +3 -0
- package/dist/mail/hosts.d.ts.map +1 -0
- package/dist/mail/hosts.js +19 -0
- package/dist/mail/hosts.js.map +1 -0
- package/dist/mail/scheduler.d.ts +9 -0
- package/dist/mail/scheduler.d.ts.map +1 -0
- package/dist/mail/scheduler.js +21 -0
- package/dist/mail/scheduler.js.map +1 -0
- package/dist/mail/service.d.ts +32 -0
- package/dist/mail/service.d.ts.map +1 -0
- package/dist/mail/service.js +129 -0
- package/dist/mail/service.js.map +1 -0
- package/dist/mail/store.d.ts +7 -0
- package/dist/mail/store.d.ts.map +1 -0
- package/dist/mail/store.js +58 -0
- package/dist/mail/store.js.map +1 -0
- package/dist/mail/types.d.ts +114 -0
- package/dist/mail/types.d.ts.map +1 -0
- package/dist/mail/types.js +26 -0
- package/dist/mail/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +43 -21
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +67 -64
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +77 -71
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/registry.d.ts +10 -0
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +26 -0
- package/dist/providers/registry.js.map +1 -1
- package/dist/providers/stream-retry.d.ts +37 -0
- package/dist/providers/stream-retry.d.ts.map +1 -0
- package/dist/providers/stream-retry.js +92 -0
- package/dist/providers/stream-retry.js.map +1 -0
- package/dist/providers/types.d.ts +3 -0
- package/dist/providers/types.d.ts.map +1 -1
- package/dist/subagent.d.ts +2 -0
- package/dist/subagent.d.ts.map +1 -1
- package/dist/subagent.js +1 -0
- package/dist/subagent.js.map +1 -1
- package/dist/web/cloudAuth.d.ts +57 -0
- package/dist/web/cloudAuth.d.ts.map +1 -0
- package/dist/web/cloudAuth.js +139 -0
- package/dist/web/cloudAuth.js.map +1 -0
- package/dist/web/island.d.ts.map +1 -1
- package/dist/web/island.js +1 -0
- package/dist/web/island.js.map +1 -1
- package/dist/web/lisa-client.d.ts +1 -1
- package/dist/web/lisa-client.d.ts.map +1 -1
- package/dist/web/lisa-client.js +703 -40
- package/dist/web/lisa-client.js.map +1 -1
- package/dist/web/lisa-css.d.ts +1 -1
- package/dist/web/lisa-css.d.ts.map +1 -1
- package/dist/web/lisa-css.js +568 -43
- package/dist/web/lisa-css.js.map +1 -1
- package/dist/web/lisa-html.d.ts +1 -1
- package/dist/web/lisa-html.d.ts.map +1 -1
- package/dist/web/lisa-html.js +71 -21
- package/dist/web/lisa-html.js.map +1 -1
- package/dist/web/pairing.d.ts +37 -0
- package/dist/web/pairing.d.ts.map +1 -0
- package/dist/web/pairing.js +79 -0
- package/dist/web/pairing.js.map +1 -0
- package/dist/web/push.d.ts +12 -1
- package/dist/web/push.d.ts.map +1 -1
- package/dist/web/push.js +19 -4
- package/dist/web/push.js.map +1 -1
- package/dist/web/qr-svg.d.ts +11 -0
- package/dist/web/qr-svg.d.ts.map +1 -0
- package/dist/web/qr-svg.js +41 -0
- package/dist/web/qr-svg.js.map +1 -0
- package/dist/web/server.d.ts +1 -1
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +342 -12
- package/dist/web/server.js.map +1 -1
- package/package.json +2 -1
package/dist/web/lisa-client.js
CHANGED
|
@@ -114,7 +114,7 @@ let capturing = false;
|
|
|
114
114
|
window.lisaCaptureAndAttach = async function (mode) {
|
|
115
115
|
if (capturing) return false;
|
|
116
116
|
capturing = true;
|
|
117
|
-
const btn = document.getElementById('
|
|
117
|
+
const btn = document.getElementById('plusBtn');
|
|
118
118
|
if (btn) btn.classList.add('flash');
|
|
119
119
|
try {
|
|
120
120
|
const res = await fetch('/api/vision/capture', {
|
|
@@ -170,10 +170,26 @@ fileInput.addEventListener('change', async () => {
|
|
|
170
170
|
// listener that forwards the click synchronously (preserving the
|
|
171
171
|
// user-gesture context) and logs to console so we can verify in the
|
|
172
172
|
// inspector if it ever silently no-ops again.
|
|
173
|
-
//
|
|
174
|
-
const
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
// ── + composer menu: merges 📎 attach + 📷 screenshot into one button ──
|
|
174
|
+
const plusBtnEl = document.getElementById('plusBtn');
|
|
175
|
+
const plusMenuEl = document.getElementById('plusMenu');
|
|
176
|
+
function closePlusMenu() { if (plusMenuEl) plusMenuEl.classList.remove('open'); }
|
|
177
|
+
if (plusBtnEl && plusMenuEl) {
|
|
178
|
+
plusBtnEl.addEventListener('click', (e) => { e.stopPropagation(); plusMenuEl.classList.toggle('open'); });
|
|
179
|
+
document.addEventListener('click', (e) => {
|
|
180
|
+
if (plusMenuEl.classList.contains('open') && !plusMenuEl.contains(e.target) && e.target !== plusBtnEl) closePlusMenu();
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
const pmAttachEl = document.getElementById('pmAttach');
|
|
184
|
+
if (pmAttachEl) {
|
|
185
|
+
pmAttachEl.addEventListener('click', () => {
|
|
186
|
+
closePlusMenu();
|
|
187
|
+
try { fileInput.click(); } catch (err) { console.error('[attach] fileInput.click failed:', err); }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const pmShotEl = document.getElementById('pmShot');
|
|
191
|
+
if (pmShotEl) {
|
|
192
|
+
pmShotEl.addEventListener('click', () => { closePlusMenu(); void window.lisaCaptureAndAttach('interactive'); });
|
|
177
193
|
}
|
|
178
194
|
|
|
179
195
|
// ── Audio recording → transcribe → Lisa summarizes ─────────────────
|
|
@@ -308,21 +324,7 @@ if (recordBtnEl) {
|
|
|
308
324
|
});
|
|
309
325
|
}
|
|
310
326
|
|
|
311
|
-
|
|
312
|
-
if (attachBtnEl) {
|
|
313
|
-
attachBtnEl.addEventListener('click', (ev) => {
|
|
314
|
-
// Don't preventDefault — that cancels the implicit label-forward
|
|
315
|
-
// and removes the redundant gesture path. Letting both fire is
|
|
316
|
-
// fine because <input type=file>.click() fires the picker only
|
|
317
|
-
// once per user gesture.
|
|
318
|
-
console.log('[attach] click forwarded to fileInput');
|
|
319
|
-
try {
|
|
320
|
-
fileInput.click();
|
|
321
|
-
} catch (err) {
|
|
322
|
-
console.error('[attach] fileInput.click failed:', err);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
}
|
|
327
|
+
// (📎 attach is now an item in the + composer menu — see pmAttach above.)
|
|
326
328
|
|
|
327
329
|
// Paste-to-attach: when the user has copied an image (screenshot,
|
|
328
330
|
// image from a webpage, etc.) and presses ⌘V, pull the image off the
|
|
@@ -452,6 +454,8 @@ function connectEvents() {
|
|
|
452
454
|
// "sidebar live wiring" block). Generalized from claude_session_update
|
|
453
455
|
// so codex / opencode / git / … sessions update the sidebar too.
|
|
454
456
|
if (typeof refreshClaudeSessions === 'function') refreshClaudeSessions();
|
|
457
|
+
} else if (ev.type === 'mail_digest_update' || ev.type === 'mail_accounts_update') {
|
|
458
|
+
if (typeof window.refreshMail === 'function') window.refreshMail();
|
|
455
459
|
}
|
|
456
460
|
});
|
|
457
461
|
es.onerror = () => {
|
|
@@ -825,6 +829,65 @@ async function selectPlan(plan) {
|
|
|
825
829
|
}
|
|
826
830
|
}
|
|
827
831
|
|
|
832
|
+
// ── Pair phone: mint a device token + show copyable pairing details ──────────
|
|
833
|
+
// Mirrors "lisa pair" / the Mac app Pair iPhone window for browser users. The mint
|
|
834
|
+
// endpoint is loopback-only, so this works from a localhost browser on the Mac
|
|
835
|
+
// (a LAN browser gets 403, handled below). The server detects the Mac's LAN host
|
|
836
|
+
// and returns the lisa-pair:// link + host/port/token so the phone can paste the
|
|
837
|
+
// link OR type the fields into Lisa Pocket → Settings → Pair.
|
|
838
|
+
function pairRow(label, value) {
|
|
839
|
+
return '<div class="pair-row"><span class="pair-label">' + escapeHtml(label) + '</span>'
|
|
840
|
+
+ '<code class="pair-val">' + escapeHtml(value) + '</code>'
|
|
841
|
+
+ '<button class="pair-copy" type="button">Copy</button></div>';
|
|
842
|
+
}
|
|
843
|
+
async function showPair() {
|
|
844
|
+
openModal('PAIR PHONE', '<div class="empty">minting a pairing code…</div>');
|
|
845
|
+
let res;
|
|
846
|
+
try {
|
|
847
|
+
res = await fetch('/api/pair/start', {
|
|
848
|
+
method: 'POST',
|
|
849
|
+
headers: { 'content-type': 'application/json' },
|
|
850
|
+
body: JSON.stringify({ name: 'phone', platform: 'ios' }),
|
|
851
|
+
});
|
|
852
|
+
} catch (e) {
|
|
853
|
+
modalBody.innerHTML = '<div class="empty">Couldn\\'t reach the Lisa backend.</div>';
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if (res.status === 403) {
|
|
857
|
+
modalBody.innerHTML = '<div class="empty">Pairing can only be started on the Mac itself. Open this page at <code>http://localhost:' + escapeHtml(location.port || '5757') + '</code> on your Mac, then try again.</div>';
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (!res.ok) {
|
|
861
|
+
modalBody.innerHTML = '<div class="empty">Pairing failed (HTTP ' + res.status + ').</div>';
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const data = await res.json().catch(function () { return {}; });
|
|
865
|
+
if (!data.token) { modalBody.innerHTML = '<div class="empty">The server returned no token.</div>'; return; }
|
|
866
|
+
const port = data.port || 5757;
|
|
867
|
+
const host = data.host || '';
|
|
868
|
+
const link = data.url || ('lisa-pair://v1?host=' + encodeURIComponent(host) + '&port=' + port + '&token=' + encodeURIComponent(data.token) + '&name=phone');
|
|
869
|
+
let html = '';
|
|
870
|
+
if (data.qrSvg) html += '<div class="pair-qr">' + data.qrSvg + '</div>';
|
|
871
|
+
html += '<div class="empty">Scan the code in Lisa Pocket → Settings → Scan QR — or paste the link / type the three fields below. Keep the phone on the same Wi-Fi (or tailnet) as this Mac.</div>';
|
|
872
|
+
html += pairRow('Link', link);
|
|
873
|
+
html += pairRow('Host', host || '(your Mac\\'s Wi-Fi IP)');
|
|
874
|
+
html += pairRow('Port', String(port));
|
|
875
|
+
html += pairRow('Token', data.token);
|
|
876
|
+
if (!host) html += '<div class="empty">Couldn\\'t detect your Mac\\'s LAN address — enter its Wi-Fi IP or tailnet name on the phone.</div>';
|
|
877
|
+
modalBody.innerHTML = html;
|
|
878
|
+
const codes = modalBody.querySelectorAll('.pair-row');
|
|
879
|
+
for (let i = 0; i < codes.length; i++) {
|
|
880
|
+
const row = codes[i];
|
|
881
|
+
const btn = row.querySelector('.pair-copy');
|
|
882
|
+
const val = row.querySelector('.pair-val');
|
|
883
|
+
btn.addEventListener('click', function () {
|
|
884
|
+
navigator.clipboard.writeText(val.textContent).then(function () {
|
|
885
|
+
const prev = btn.textContent; btn.textContent = 'Copied'; setTimeout(function () { btn.textContent = prev; }, 1200);
|
|
886
|
+
}).catch(function () {});
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
828
891
|
async function showSoul() {
|
|
829
892
|
openModal('★ SOUL', '<div class="empty">loading…</div>');
|
|
830
893
|
const data = await fetch('/api/soul').then(r => r.json());
|
|
@@ -867,7 +930,8 @@ async function showSoul() {
|
|
|
867
930
|
modalBody.innerHTML = html;
|
|
868
931
|
}
|
|
869
932
|
|
|
870
|
-
|
|
933
|
+
// Panel openers — the top function bar (.fbtn) + any legacy .badge share this.
|
|
934
|
+
document.querySelectorAll('[data-panel]').forEach(b => {
|
|
871
935
|
b.addEventListener('click', () => {
|
|
872
936
|
const which = b.dataset.panel;
|
|
873
937
|
if (which === 'soul') showSoul();
|
|
@@ -875,9 +939,54 @@ document.querySelectorAll('.badge').forEach(b => {
|
|
|
875
939
|
else if (which === 'memory') showMemory();
|
|
876
940
|
else if (which === 'tools') showTools();
|
|
877
941
|
else if (which === 'plans') showPlans();
|
|
942
|
+
else if (which === 'pair') showPair();
|
|
878
943
|
});
|
|
879
944
|
});
|
|
880
945
|
|
|
946
|
+
// Find in conversation — toggle a filter box that hides non-matching log rows.
|
|
947
|
+
const fnSearchBtn = document.getElementById('fnSearchBtn');
|
|
948
|
+
const fnFind = document.getElementById('fnFind');
|
|
949
|
+
function filterLog(q) {
|
|
950
|
+
const logEl = document.getElementById('log');
|
|
951
|
+
if (!logEl) return;
|
|
952
|
+
for (const child of logEl.children) {
|
|
953
|
+
child.style.display = !q || (child.textContent || '').toLowerCase().includes(q) ? '' : 'none';
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
if (fnSearchBtn && fnFind) {
|
|
957
|
+
fnSearchBtn.addEventListener('click', () => {
|
|
958
|
+
const show = fnFind.style.display === 'none';
|
|
959
|
+
fnFind.style.display = show ? '' : 'none';
|
|
960
|
+
if (show) { fnFind.focus(); } else { fnFind.value = ''; filterLog(''); }
|
|
961
|
+
});
|
|
962
|
+
fnFind.addEventListener('input', () => filterLog(fnFind.value.trim().toLowerCase()));
|
|
963
|
+
fnFind.addEventListener('keydown', (e) => {
|
|
964
|
+
if (e.key === 'Escape') { fnFind.value = ''; filterLog(''); fnFind.style.display = 'none'; }
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Compact / sidebar mode — force the narrow stacked layout at any width, persisted.
|
|
969
|
+
const compactToggleEl = document.getElementById('compactToggle');
|
|
970
|
+
if (compactToggleEl) {
|
|
971
|
+
let compactOn = false;
|
|
972
|
+
try { compactOn = localStorage.getItem('lisaCompact') === '1'; } catch (e) {}
|
|
973
|
+
const applyCompact = () => {
|
|
974
|
+
document.body.classList.toggle('force-compact', compactOn);
|
|
975
|
+
compactToggleEl.classList.toggle('on', compactOn);
|
|
976
|
+
compactToggleEl.setAttribute('aria-checked', compactOn ? 'true' : 'false');
|
|
977
|
+
};
|
|
978
|
+
applyCompact();
|
|
979
|
+
const toggleCompact = () => {
|
|
980
|
+
compactOn = !compactOn;
|
|
981
|
+
try { localStorage.setItem('lisaCompact', compactOn ? '1' : '0'); } catch (e) {}
|
|
982
|
+
applyCompact();
|
|
983
|
+
};
|
|
984
|
+
compactToggleEl.addEventListener('click', toggleCompact);
|
|
985
|
+
compactToggleEl.addEventListener('keydown', (e) => {
|
|
986
|
+
if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleCompact(); }
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
|
|
881
990
|
let currentLisaSpan = null;
|
|
882
991
|
let pendingTools = new Map();
|
|
883
992
|
let thinkingEl = null;
|
|
@@ -918,7 +1027,6 @@ function previewInput(name, input) {
|
|
|
918
1027
|
async function send(message) {
|
|
919
1028
|
input.value = '';
|
|
920
1029
|
input.style.height = 'auto';
|
|
921
|
-
sendBtn.disabled = true;
|
|
922
1030
|
el('div', 'role you', 'YOU');
|
|
923
1031
|
el('span', 'msg', message || '(attachment)');
|
|
924
1032
|
if (pendingFiles.length) {
|
|
@@ -928,9 +1036,48 @@ async function send(message) {
|
|
|
928
1036
|
const filesToSend = [...pendingFiles];
|
|
929
1037
|
pendingFiles = [];
|
|
930
1038
|
renderAttachPreview();
|
|
1039
|
+
await runChat(message, filesToSend);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
// On failure, show the error detail with a retry button that re-runs the same
|
|
1043
|
+
// turn. Kept separate from send() so retry never re-appends the user's bubble
|
|
1044
|
+
// or re-reads the (already-cleared) attachment tray.
|
|
1045
|
+
function showError(detail, message, filesToSend) {
|
|
1046
|
+
if (thinkingEl) { thinkingEl.remove(); thinkingEl = null; }
|
|
1047
|
+
const block = el('div', 'err-block', null);
|
|
1048
|
+
const head = document.createElement('div');
|
|
1049
|
+
head.className = 'err-head';
|
|
1050
|
+
head.textContent = '⚠ 请求出错';
|
|
1051
|
+
block.appendChild(head);
|
|
1052
|
+
const body = document.createElement('div');
|
|
1053
|
+
body.className = 'err-detail';
|
|
1054
|
+
body.textContent = detail || 'unknown error';
|
|
1055
|
+
block.appendChild(body);
|
|
1056
|
+
const retry = document.createElement('button');
|
|
1057
|
+
retry.type = 'button';
|
|
1058
|
+
retry.className = 'err-retry';
|
|
1059
|
+
retry.textContent = '↻ 重试';
|
|
1060
|
+
retry.addEventListener('click', () => {
|
|
1061
|
+
block.remove();
|
|
1062
|
+
runChat(message, filesToSend);
|
|
1063
|
+
});
|
|
1064
|
+
block.appendChild(retry);
|
|
1065
|
+
log.scrollTop = log.scrollHeight;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
async function runChat(message, filesToSend) {
|
|
1069
|
+
sendBtn.disabled = true;
|
|
931
1070
|
currentLisaSpan = null;
|
|
932
1071
|
pendingTools.clear();
|
|
933
1072
|
thinkingEl = el('div', 'thinking', '⋯ thinking');
|
|
1073
|
+
// The agent emits an error event AND the server re-sends it from its turn
|
|
1074
|
+
// catch — dedupe so one failure renders exactly one error block.
|
|
1075
|
+
let errored = false;
|
|
1076
|
+
const fail = (detail) => {
|
|
1077
|
+
if (errored) return;
|
|
1078
|
+
errored = true;
|
|
1079
|
+
showError(detail, message, filesToSend);
|
|
1080
|
+
};
|
|
934
1081
|
try {
|
|
935
1082
|
const res = await fetch('/chat', {
|
|
936
1083
|
method: 'POST',
|
|
@@ -987,7 +1134,7 @@ async function send(message) {
|
|
|
987
1134
|
} else if (ev.type === 'mood') {
|
|
988
1135
|
setMood(ev.slug);
|
|
989
1136
|
} else if (ev.type === 'error') {
|
|
990
|
-
|
|
1137
|
+
fail(ev.message);
|
|
991
1138
|
} else if (ev.type === 'done') {
|
|
992
1139
|
if (thinkingEl) { thinkingEl.remove(); thinkingEl = null; }
|
|
993
1140
|
}
|
|
@@ -995,8 +1142,7 @@ async function send(message) {
|
|
|
995
1142
|
}
|
|
996
1143
|
if (thinkingEl) { thinkingEl.remove(); thinkingEl = null; }
|
|
997
1144
|
} catch (err) {
|
|
998
|
-
|
|
999
|
-
el('div', 'err', '[error] ' + err.message);
|
|
1145
|
+
fail(err.message);
|
|
1000
1146
|
} finally {
|
|
1001
1147
|
sendBtn.disabled = false;
|
|
1002
1148
|
input.focus();
|
|
@@ -1291,31 +1437,66 @@ if ('serviceWorker' in navigator) {
|
|
|
1291
1437
|
} catch {}
|
|
1292
1438
|
};
|
|
1293
1439
|
|
|
1294
|
-
// "Delegate a task" →
|
|
1295
|
-
//
|
|
1296
|
-
//
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1440
|
+
// "Delegate a task" → open a modal to pick the agent kind + write the task.
|
|
1441
|
+
// (A roomy dialog beats the cramped 280px sidebar.) managed = LISA-run
|
|
1442
|
+
// (controllable); claude/codex = a real CLI under a PTY (needs LISA_PTY_AGENTS=1
|
|
1443
|
+
// — a 503/error surfaces inline in the dialog).
|
|
1444
|
+
function openDelegateModal() {
|
|
1445
|
+
openModal(
|
|
1446
|
+
'Delegate a task',
|
|
1447
|
+
'<div class="delegate-modal">' +
|
|
1448
|
+
'<label class="dm-label">Agent</label>' +
|
|
1449
|
+
'<select id="dmKind" class="dm-kind">' +
|
|
1450
|
+
'<option value="managed">managed — LISA runs it (approve each step)</option>' +
|
|
1451
|
+
'<option value="claude">claude — real CLI under a PTY</option>' +
|
|
1452
|
+
'<option value="codex">codex — real CLI under a PTY</option>' +
|
|
1453
|
+
'</select>' +
|
|
1454
|
+
'<label class="dm-label">Task</label>' +
|
|
1455
|
+
'<textarea id="dmTask" class="dm-task" rows="5" placeholder="Describe the task… (⌘/Ctrl+Enter to start)"></textarea>' +
|
|
1456
|
+
'<div class="dm-actions"><button id="dmStart" class="dm-start" type="button">Start agent →</button></div>' +
|
|
1457
|
+
'<div id="dmErr" class="dm-err"></div>' +
|
|
1458
|
+
'</div>'
|
|
1459
|
+
);
|
|
1460
|
+
const kindEl = document.getElementById('dmKind');
|
|
1461
|
+
const taskEl = document.getElementById('dmTask');
|
|
1462
|
+
const startEl = document.getElementById('dmStart');
|
|
1463
|
+
const errEl = document.getElementById('dmErr');
|
|
1464
|
+
if (taskEl) taskEl.focus();
|
|
1465
|
+
function submitDelegate() {
|
|
1466
|
+
const task = taskEl && taskEl.value.trim();
|
|
1467
|
+
if (!task) { if (taskEl) taskEl.focus(); return; }
|
|
1305
1468
|
const kind = kindEl ? kindEl.value : 'managed';
|
|
1306
1469
|
const url = kind === 'managed' ? '/api/agents/managed/start' : '/api/agents/pty/start';
|
|
1307
1470
|
const body = kind === 'managed' ? { task: task } : { agent: kind, task: task };
|
|
1471
|
+
if (errEl) errEl.textContent = '';
|
|
1472
|
+
if (startEl) { startEl.disabled = true; startEl.textContent = 'Starting…'; }
|
|
1308
1473
|
fetch(url, {
|
|
1309
1474
|
method: 'POST',
|
|
1310
1475
|
headers: { 'content-type': 'application/json' },
|
|
1311
1476
|
body: JSON.stringify(body),
|
|
1312
1477
|
}).then(function (r) {
|
|
1313
|
-
if (!r.ok) {
|
|
1314
|
-
|
|
1478
|
+
if (!r.ok) {
|
|
1479
|
+
return r.text().then(function (t) {
|
|
1480
|
+
if (errEl) errEl.textContent = t || ('failed (' + r.status + ')');
|
|
1481
|
+
if (startEl) { startEl.disabled = false; startEl.textContent = 'Start agent →'; }
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
closeModal();
|
|
1315
1485
|
if (typeof refreshClaudeSessions === 'function') refreshClaudeSessions();
|
|
1316
|
-
}).catch(function () {
|
|
1486
|
+
}).catch(function () {
|
|
1487
|
+
if (errEl) errEl.textContent = 'network error';
|
|
1488
|
+
if (startEl) { startEl.disabled = false; startEl.textContent = 'Start agent →'; }
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
if (startEl) startEl.addEventListener('click', submitDelegate);
|
|
1492
|
+
if (taskEl) taskEl.addEventListener('keydown', function (e) {
|
|
1493
|
+
if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); submitDelegate(); }
|
|
1317
1494
|
});
|
|
1318
1495
|
}
|
|
1496
|
+
const sbDelegateBtn = document.getElementById('sbDelegateBtn');
|
|
1497
|
+
if (sbDelegateBtn) sbDelegateBtn.addEventListener('click', openDelegateModal);
|
|
1498
|
+
// Exposed so the console Dashboard / Control views reuse the same dialog.
|
|
1499
|
+
window.lisaOpenDelegate = openDelegateModal;
|
|
1319
1500
|
|
|
1320
1501
|
async function refreshIdentity() {
|
|
1321
1502
|
try {
|
|
@@ -1345,14 +1526,496 @@ if ('serviceWorker' in navigator) {
|
|
|
1345
1526
|
}
|
|
1346
1527
|
}
|
|
1347
1528
|
|
|
1529
|
+
// ── Mail card: connect a mailbox + show the daily classified digest ──
|
|
1530
|
+
function renderMail(accounts, digest) {
|
|
1531
|
+
const body = document.getElementById('sbMailBody');
|
|
1532
|
+
const count = document.getElementById('sbMailCount');
|
|
1533
|
+
const connectBtn = document.getElementById('sbMailConnectBtn');
|
|
1534
|
+
if (!body) return;
|
|
1535
|
+
while (body.firstChild) body.removeChild(body.firstChild);
|
|
1536
|
+
const hasAccounts = accounts && accounts.length > 0;
|
|
1537
|
+
if (connectBtn) connectBtn.textContent = hasAccounts ? '+ add mailbox' : '+ connect mailbox';
|
|
1538
|
+
if (!hasAccounts) {
|
|
1539
|
+
const empty = document.createElement('div');
|
|
1540
|
+
empty.className = 'session-empty';
|
|
1541
|
+
empty.textContent = '(not connected)';
|
|
1542
|
+
body.appendChild(empty);
|
|
1543
|
+
if (count) count.textContent = '';
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
const sum = document.createElement('div');
|
|
1547
|
+
sum.className = 'mail-summary';
|
|
1548
|
+
sum.textContent = digest && digest.summary ? digest.summary : 'No digest yet — sweep to build one.';
|
|
1549
|
+
body.appendChild(sum);
|
|
1550
|
+
const needs = digest && digest.needsYou ? digest.needsYou : [];
|
|
1551
|
+
if (count) count.textContent = needs.length ? ('✦ ' + needs.length) : '';
|
|
1552
|
+
for (const i of needs.slice(0, 5)) {
|
|
1553
|
+
const row = document.createElement('div');
|
|
1554
|
+
row.className = 'mail-row';
|
|
1555
|
+
const bang = document.createElement('span');
|
|
1556
|
+
bang.className = 'mail-bang' + (i.importance >= 3 ? ' urgent' : '');
|
|
1557
|
+
bang.textContent = i.importance >= 3 ? '‼' : '!';
|
|
1558
|
+
const subj = document.createElement('span');
|
|
1559
|
+
subj.className = 'mail-subj';
|
|
1560
|
+
subj.textContent = i.subject || '(no subject)';
|
|
1561
|
+
subj.title = (i.from || '') + ' — ' + (i.reason || '');
|
|
1562
|
+
row.appendChild(bang);
|
|
1563
|
+
row.appendChild(subj);
|
|
1564
|
+
body.appendChild(row);
|
|
1565
|
+
}
|
|
1566
|
+
const sweep = document.createElement('button');
|
|
1567
|
+
sweep.className = 'mail-sweep';
|
|
1568
|
+
sweep.type = 'button';
|
|
1569
|
+
sweep.textContent = 'sweep now';
|
|
1570
|
+
sweep.addEventListener('click', function () {
|
|
1571
|
+
sweep.disabled = true; sweep.textContent = 'sweeping…';
|
|
1572
|
+
fetch('/api/mail/sweep', { method: 'POST' }).then(function () {
|
|
1573
|
+
if (window.refreshMail) window.refreshMail();
|
|
1574
|
+
}).catch(function () {}).then(function () { sweep.disabled = false; sweep.textContent = 'sweep now'; });
|
|
1575
|
+
});
|
|
1576
|
+
body.appendChild(sweep);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
window.refreshMail = async function () {
|
|
1580
|
+
try {
|
|
1581
|
+
const a = await fetch('/api/mail/accounts').then(function (r) { return r.ok ? r.json() : null; });
|
|
1582
|
+
const d = await fetch('/api/mail/digest').then(function (r) { return r.ok ? r.json() : null; });
|
|
1583
|
+
renderMail(a ? a.accounts : [], d ? d.digest : null);
|
|
1584
|
+
} catch (e) {}
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
function openMailModal() {
|
|
1588
|
+
openModal('Connect a mailbox',
|
|
1589
|
+
'<div class="delegate-modal">' +
|
|
1590
|
+
'<label class="dm-label">Email</label>' +
|
|
1591
|
+
'<input id="mmEmail" class="dm-kind" type="email" placeholder="you@qq.com" autocomplete="off" />' +
|
|
1592
|
+
'<label class="dm-label">App-password / authorization code</label>' +
|
|
1593
|
+
'<input id="mmPass" class="dm-kind" type="password" placeholder="not your login password" autocomplete="off" />' +
|
|
1594
|
+
'<label class="dm-label">IMAP host (optional — auto-detected)</label>' +
|
|
1595
|
+
'<input id="mmHost" class="dm-kind" type="text" placeholder="imap.qq.com" autocomplete="off" />' +
|
|
1596
|
+
'<div class="dm-actions"><button id="mmStart" class="dm-start" type="button">Connect →</button></div>' +
|
|
1597
|
+
'<div id="mmErr" class="dm-err"></div>' +
|
|
1598
|
+
'<div class="dm-note">Read-only. Stored locally (0600). Most providers need an app-password, not your login password.</div>' +
|
|
1599
|
+
'</div>');
|
|
1600
|
+
const emailEl = document.getElementById('mmEmail');
|
|
1601
|
+
const passEl = document.getElementById('mmPass');
|
|
1602
|
+
const hostEl = document.getElementById('mmHost');
|
|
1603
|
+
const startEl = document.getElementById('mmStart');
|
|
1604
|
+
const errEl = document.getElementById('mmErr');
|
|
1605
|
+
if (emailEl) emailEl.focus();
|
|
1606
|
+
function submitMail() {
|
|
1607
|
+
const email = emailEl && emailEl.value.trim();
|
|
1608
|
+
const pass = passEl && passEl.value;
|
|
1609
|
+
if (!email || !pass) { if (errEl) errEl.textContent = 'email + app-password required'; return; }
|
|
1610
|
+
const body = { email: email, password: pass };
|
|
1611
|
+
if (hostEl && hostEl.value.trim()) body.host = hostEl.value.trim();
|
|
1612
|
+
if (errEl) errEl.textContent = '';
|
|
1613
|
+
if (startEl) { startEl.disabled = true; startEl.textContent = 'Connecting…'; }
|
|
1614
|
+
fetch('/api/mail/connect', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) })
|
|
1615
|
+
.then(function (r) {
|
|
1616
|
+
if (!r.ok) { return r.text().then(function (t) { if (errEl) errEl.textContent = t || ('failed (' + r.status + ')'); if (startEl) { startEl.disabled = false; startEl.textContent = 'Connect →'; } }); }
|
|
1617
|
+
closeModal();
|
|
1618
|
+
if (window.refreshMail) window.refreshMail();
|
|
1619
|
+
})
|
|
1620
|
+
.catch(function () { if (errEl) errEl.textContent = 'network error'; if (startEl) { startEl.disabled = false; startEl.textContent = 'Connect →'; } });
|
|
1621
|
+
}
|
|
1622
|
+
if (startEl) startEl.addEventListener('click', submitMail);
|
|
1623
|
+
if (passEl) passEl.addEventListener('keydown', function (e) { if (e.key === 'Enter') { e.preventDefault(); submitMail(); } });
|
|
1624
|
+
}
|
|
1625
|
+
var sbMailConnectBtn = document.getElementById('sbMailConnectBtn');
|
|
1626
|
+
if (sbMailConnectBtn) sbMailConnectBtn.addEventListener('click', openMailModal);
|
|
1627
|
+
|
|
1348
1628
|
// Bootstrap + periodic resync. SSE handles the fast-path updates;
|
|
1349
1629
|
// these timers are belt-and-braces in case the stream silently dies.
|
|
1350
1630
|
refreshPing();
|
|
1351
1631
|
window.refreshClaudeSessions();
|
|
1632
|
+
window.refreshMail();
|
|
1352
1633
|
refreshIdentity();
|
|
1353
1634
|
refreshSessionsBadge();
|
|
1354
1635
|
setInterval(refreshPing, 30_000);
|
|
1355
|
-
|
|
1636
|
+
// Resolve refreshClaudeSessions at call time (arrow), not now: setupConsole
|
|
1637
|
+
// later wraps window.refreshClaudeSessions to re-render the active console
|
|
1638
|
+
// view + nav count, and the wrapper must win on this 60s tick too.
|
|
1639
|
+
setInterval(() => window.refreshClaudeSessions(), 60_000);
|
|
1640
|
+
setInterval(window.refreshMail, 5 * 60_000);
|
|
1356
1641
|
setInterval(refreshSessionsBadge, 5 * 60_000);
|
|
1642
|
+
})();
|
|
1643
|
+
|
|
1644
|
+
// ════════════════════════════════════════════════════════════════════
|
|
1645
|
+
// Console view-switcher — adds Dashboard / Control / Reve / Sense /
|
|
1646
|
+
// Memory views beside the default Chat. Purely additive: the chat
|
|
1647
|
+
// pipeline and every existing id stay untouched. Each non-chat view is
|
|
1648
|
+
// built lazily from the real endpoints on first activation, and the live
|
|
1649
|
+
// agent stream (refreshClaudeSessions) is wrapped to re-render the active
|
|
1650
|
+
// console view. No backticks / no template placeholders in this block.
|
|
1651
|
+
// ════════════════════════════════════════════════════════════════════
|
|
1652
|
+
(function setupConsole() {
|
|
1653
|
+
var navList = document.getElementById('navList');
|
|
1654
|
+
if (!navList) return;
|
|
1655
|
+
var views = {
|
|
1656
|
+
chat: document.getElementById('viewChat'),
|
|
1657
|
+
dashboard: document.getElementById('viewDashboard'),
|
|
1658
|
+
control: document.getElementById('viewControl'),
|
|
1659
|
+
reve: document.getElementById('viewReve'),
|
|
1660
|
+
sense: document.getElementById('viewSense'),
|
|
1661
|
+
memory: document.getElementById('viewMemory'),
|
|
1662
|
+
};
|
|
1663
|
+
var loaded = {};
|
|
1664
|
+
var active = 'chat';
|
|
1665
|
+
var proactiveOn = true;
|
|
1666
|
+
|
|
1667
|
+
function esc(s) { return (typeof escapeHtml === 'function') ? escapeHtml(s) : String(s == null ? '' : s); }
|
|
1668
|
+
function getJSON(u) { return fetch(u).then(function (r) { return r.ok ? r.json() : null; }).catch(function () { return null; }); }
|
|
1669
|
+
|
|
1670
|
+
function statusClass(state) {
|
|
1671
|
+
if (state === 'working') return 'working';
|
|
1672
|
+
if (state === 'waiting') return 'waiting';
|
|
1673
|
+
if (state === 'error') return 'error';
|
|
1674
|
+
if (state === 'done' || state === 'idle') return 'done';
|
|
1675
|
+
return '';
|
|
1676
|
+
}
|
|
1677
|
+
function statusLabel(state) {
|
|
1678
|
+
if (state === 'working') return 'Working';
|
|
1679
|
+
if (state === 'waiting') return 'Waiting';
|
|
1680
|
+
if (state === 'error') return 'Error';
|
|
1681
|
+
if (state === 'done') return 'Done';
|
|
1682
|
+
if (state === 'idle') return 'Idle';
|
|
1683
|
+
return 'Unknown';
|
|
1684
|
+
}
|
|
1685
|
+
// Compact activity line (local copy of the sidebar helper — kept decoupled).
|
|
1686
|
+
function actLine(s) {
|
|
1687
|
+
var a = s && s.activity;
|
|
1688
|
+
if (!a || typeof a !== 'object') return '';
|
|
1689
|
+
if (a.pendingPermission) return 'wants to run ' + a.pendingPermission;
|
|
1690
|
+
var bits = [];
|
|
1691
|
+
if (a.lastError) bits.push(a.lastError);
|
|
1692
|
+
if (typeof a.turnCount === 'number' && a.turnCount > 0) bits.push('turn ' + a.turnCount);
|
|
1693
|
+
if (a.tokens && (a.tokens.input || a.tokens.output)) {
|
|
1694
|
+
var tot = (a.tokens.input || 0) + (a.tokens.output || 0);
|
|
1695
|
+
bits.push(tot >= 1000 ? Math.round(tot / 1000) + 'k tok' : tot + ' tok');
|
|
1696
|
+
}
|
|
1697
|
+
var tool = a.lastTools && a.lastTools.length ? a.lastTools[a.lastTools.length - 1] : '';
|
|
1698
|
+
var file = a.filesTouched && a.filesTouched.length ? (String(a.filesTouched[a.filesTouched.length - 1]).split('/').pop() || '') : '';
|
|
1699
|
+
if (tool && file) bits.push(tool + ' ' + file);
|
|
1700
|
+
else if (tool) bits.push(tool);
|
|
1701
|
+
else if (file) bits.push(file);
|
|
1702
|
+
return bits.join(' · ');
|
|
1703
|
+
}
|
|
1704
|
+
function updateAgentCount(n) {
|
|
1705
|
+
var c = document.getElementById('navAgentCount');
|
|
1706
|
+
if (c) c.textContent = String(n);
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
// ── view switching ──────────────────────────────────────────────
|
|
1710
|
+
function loadView(name) {
|
|
1711
|
+
if (loaded[name]) return;
|
|
1712
|
+
loaded[name] = true;
|
|
1713
|
+
if (name === 'dashboard') loadDashboard();
|
|
1714
|
+
else if (name === 'control') loadControl();
|
|
1715
|
+
else if (name === 'reve') loadReve();
|
|
1716
|
+
else if (name === 'sense') loadSense();
|
|
1717
|
+
else if (name === 'memory') loadMemory();
|
|
1718
|
+
}
|
|
1719
|
+
function showView(name) {
|
|
1720
|
+
if (!views[name]) name = 'chat';
|
|
1721
|
+
active = name;
|
|
1722
|
+
for (var k in views) { if (views[k]) views[k].classList.toggle('active', k === name); }
|
|
1723
|
+
var items = navList.querySelectorAll('.nav-item');
|
|
1724
|
+
for (var i = 0; i < items.length; i++) {
|
|
1725
|
+
items[i].classList.toggle('active', items[i].getAttribute('data-view') === name);
|
|
1726
|
+
}
|
|
1727
|
+
if (name !== 'chat') loadView(name);
|
|
1728
|
+
else { try { document.getElementById('input').focus(); } catch (e) {} }
|
|
1729
|
+
try { history.replaceState(null, '', name === 'chat' ? location.pathname + location.search : '#' + name); } catch (e) {}
|
|
1730
|
+
}
|
|
1731
|
+
navList.addEventListener('click', function (e) {
|
|
1732
|
+
var btn = e.target && e.target.closest ? e.target.closest('.nav-item') : null;
|
|
1733
|
+
if (btn && btn.getAttribute('data-view')) showView(btn.getAttribute('data-view'));
|
|
1734
|
+
});
|
|
1735
|
+
window.lisaShowView = showView;
|
|
1736
|
+
|
|
1737
|
+
// ── proactive toggle ────────────────────────────────────────────
|
|
1738
|
+
var ptEl = document.getElementById('proactiveToggle');
|
|
1739
|
+
function setProactiveUI(on) {
|
|
1740
|
+
proactiveOn = !!on;
|
|
1741
|
+
if (ptEl) { ptEl.classList.toggle('on', proactiveOn); ptEl.setAttribute('aria-checked', proactiveOn ? 'true' : 'false'); }
|
|
1742
|
+
var pp = document.getElementById('ppPanel');
|
|
1743
|
+
if (pp) {
|
|
1744
|
+
pp.classList.toggle('off', !proactiveOn);
|
|
1745
|
+
var st = document.getElementById('ppState');
|
|
1746
|
+
if (st) st.textContent = proactiveOn ? 'On · watching' : 'Paused';
|
|
1747
|
+
var sd = document.getElementById('ppDesc');
|
|
1748
|
+
if (sd) sd.textContent = proactiveOn ? 'Lisa watches your agents, tasks and signals for blockers and next steps.' : 'Resting — she will only act when you talk to her.';
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
function syncProactive() {
|
|
1752
|
+
getJSON('/api/autonomy/state').then(function (s) { if (s && typeof s.enabled === 'boolean') setProactiveUI(s.enabled); });
|
|
1753
|
+
}
|
|
1754
|
+
function toggleProactive() {
|
|
1755
|
+
var on = !proactiveOn;
|
|
1756
|
+
setProactiveUI(on);
|
|
1757
|
+
fetch('/api/autonomy/state', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ enabled: on }) })
|
|
1758
|
+
.then(function (r) { return r && r.ok ? r.json() : null; })
|
|
1759
|
+
.then(function (j) { if (j && typeof j.enabled === 'boolean') setProactiveUI(j.enabled); else setProactiveUI(!on); })
|
|
1760
|
+
.catch(function () { setProactiveUI(!on); });
|
|
1761
|
+
}
|
|
1762
|
+
if (ptEl) {
|
|
1763
|
+
ptEl.addEventListener('click', toggleProactive);
|
|
1764
|
+
ptEl.addEventListener('keydown', function (e) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); toggleProactive(); } });
|
|
1765
|
+
}
|
|
1766
|
+
syncProactive();
|
|
1767
|
+
|
|
1768
|
+
// ── Dashboard ───────────────────────────────────────────────────
|
|
1769
|
+
function agentCardHTML(s) {
|
|
1770
|
+
var cls = statusClass(s.state);
|
|
1771
|
+
var act = actLine(s);
|
|
1772
|
+
return '<div class="ac">' +
|
|
1773
|
+
'<div class="ac-top"><span>' + esc(s.agent || 'agent') + '</span><span class="ac-status ' + cls + '">' + esc(statusLabel(s.state)) + '</span></div>' +
|
|
1774
|
+
'<div class="ac-title">' + esc(s.project || s.agent || 'agent') + '</div>' +
|
|
1775
|
+
'<div class="ac-desc">' + (act ? esc(act) : 'no recent activity') + '</div>' +
|
|
1776
|
+
'</div>';
|
|
1777
|
+
}
|
|
1778
|
+
function taskCardHTML(d) {
|
|
1779
|
+
return '<div class="ac">' +
|
|
1780
|
+
'<div class="ac-top"><span>' + esc(d.agent || 'dispatch') + '</span><span class="ac-status ' + (d.alive ? 'working' : 'done') + '">' + (d.alive ? 'Running' : 'Done') + '</span></div>' +
|
|
1781
|
+
'<div class="ac-title">' + esc(String(d.task || 'task').slice(0, 80)) + '</div>' +
|
|
1782
|
+
'<div class="ac-meta">' + esc(d.cwd ? String(d.cwd).split('/').pop() : 'dispatch') + '</div>' +
|
|
1783
|
+
'</div>';
|
|
1784
|
+
}
|
|
1785
|
+
function loadDashboard() {
|
|
1786
|
+
views.dashboard.innerHTML =
|
|
1787
|
+
'<div class="view-head"><div><h2>Dashboard</h2><div class="vh-sub">Operations at a glance</div></div>' +
|
|
1788
|
+
'<button class="view-act" id="dashDelegate">+ Delegate a task</button></div>' +
|
|
1789
|
+
'<div class="view-scroll" id="dashScroll"><div class="view-empty">loading…</div></div>';
|
|
1790
|
+
var del = document.getElementById('dashDelegate');
|
|
1791
|
+
if (del) del.addEventListener('click', function () { if (typeof window.lisaOpenDelegate === 'function') window.lisaOpenDelegate(); });
|
|
1792
|
+
renderDashboard();
|
|
1793
|
+
}
|
|
1794
|
+
function renderDashboard() {
|
|
1795
|
+
var scroll = document.getElementById('dashScroll');
|
|
1796
|
+
if (!scroll) return;
|
|
1797
|
+
Promise.all([
|
|
1798
|
+
getJSON('/api/agents/sessions'), getJSON('/api/dispatch/list'),
|
|
1799
|
+
getJSON('/api/sense/recent'), getJSON('/api/island/ping'),
|
|
1800
|
+
]).then(function (res) {
|
|
1801
|
+
var sessions = (res[0] && res[0].sessions) || [];
|
|
1802
|
+
var dispatches = (res[1] && res[1].dispatches) || [];
|
|
1803
|
+
var events = (res[2] && res[2].events) || [];
|
|
1804
|
+
var ping = res[3] || {};
|
|
1805
|
+
updateAgentCount(sessions.length);
|
|
1806
|
+
var aliveTasks = dispatches.filter(function (d) { return d.alive; }).length;
|
|
1807
|
+
var waiting = sessions.filter(function (s) { return s.state === 'waiting'; }).length;
|
|
1808
|
+
var errored = sessions.filter(function (s) { return s.state === 'error'; }).length;
|
|
1809
|
+
var desire = ping.current_desire || '';
|
|
1810
|
+
var html = '';
|
|
1811
|
+
html += '<div class="stat-bar">' +
|
|
1812
|
+
'<div class="stat"><div class="n">' + sessions.length + '</div><div class="k">Agents</div></div>' +
|
|
1813
|
+
'<div class="stat"><div class="n">' + aliveTasks + '</div><div class="k">Tasks</div></div>' +
|
|
1814
|
+
'<div class="stat"><div class="n">' + dispatches.length + '</div><div class="k">Dispatched</div></div>' +
|
|
1815
|
+
'<div class="stat"><div class="n">' + events.length + '</div><div class="k">Signals</div></div></div>';
|
|
1816
|
+
html += '<div class="proactive-panel' + (proactiveOn ? '' : ' off') + '" id="ppPanel"><div class="pp-head"><span class="pp-dot"></span>' +
|
|
1817
|
+
'<div><div class="pp-title">Proactive mode <b id="ppState">' + (proactiveOn ? 'On · watching' : 'Paused') + '</b></div>' +
|
|
1818
|
+
'<div class="pp-desc" id="ppDesc">' + (proactiveOn ? 'Lisa watches your agents, tasks and signals for blockers and next steps.' : 'Resting — she will only act when you talk to her.') + '</div></div></div>' +
|
|
1819
|
+
'<div class="pp-tags"><span class="pp-tag">' + sessions.length + ' agents</span><span class="pp-tag">' + aliveTasks + ' tasks</span>' +
|
|
1820
|
+
(waiting ? '<span class="pp-tag warn">' + waiting + ' waiting</span>' : '') +
|
|
1821
|
+
(errored ? '<span class="pp-tag warn">' + errored + ' errored</span>' : '') +
|
|
1822
|
+
'<span class="pp-tag">owner-routed</span><span class="pp-tag">quiet on waits</span></div></div>';
|
|
1823
|
+
html += '<div class="view-sec-label">Focus · currently pursuing</div>';
|
|
1824
|
+
if (desire) {
|
|
1825
|
+
html += '<div class="focus-card"><div class="fc-top"><div>' +
|
|
1826
|
+
'<div class="fc-title">' + esc(desire) + '</div>' +
|
|
1827
|
+
'<div class="fc-desc">A self-driven desire Lisa is pursuing on her own time.</div></div>' +
|
|
1828
|
+
'<span class="fc-pill">Active</span></div>' +
|
|
1829
|
+
'<div class="fc-meta"><span>self-driven</span><span>' + sessions.length + ' agents live</span></div></div>';
|
|
1830
|
+
} else {
|
|
1831
|
+
html += '<div class="focus-card"><div class="fc-desc">Nothing actively pursued right now.</div></div>';
|
|
1832
|
+
}
|
|
1833
|
+
html += '<div class="view-sec-label">Agents & tasks</div>';
|
|
1834
|
+
var cards = sessions.map(agentCardHTML).concat(dispatches.slice(0, 6).map(taskCardHTML));
|
|
1835
|
+
html += cards.length ? '<div class="card-scroll">' + cards.join('') + '</div>'
|
|
1836
|
+
: '<div class="view-empty">No active agents or tasks. Delegate one to get started.</div>';
|
|
1837
|
+
scroll.innerHTML = html;
|
|
1838
|
+
});
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// ── Control ─────────────────────────────────────────────────────
|
|
1842
|
+
function controlRowHTML(s) {
|
|
1843
|
+
var act = actLine(s);
|
|
1844
|
+
var fam = s.controllable === 'pty' ? 'pty' : (s.controllable === 'managed' ? 'managed' : '');
|
|
1845
|
+
var pend = s.activity && s.activity.pendingPermission;
|
|
1846
|
+
var id = esc(s.sessionId);
|
|
1847
|
+
var ctrl = '';
|
|
1848
|
+
if (fam && pend) {
|
|
1849
|
+
ctrl += '<button class="mc approve" data-fam="' + fam + '" data-id="' + id + '" data-act="approve">approve</button>';
|
|
1850
|
+
ctrl += '<button class="mc deny" data-fam="' + fam + '" data-id="' + id + '" data-act="deny">deny</button>';
|
|
1851
|
+
}
|
|
1852
|
+
if (fam) ctrl += '<button class="mc cancel" data-fam="' + fam + '" data-id="' + id + '" data-act="cancel">cancel</button>';
|
|
1853
|
+
if (fam === 'pty') ctrl += '<button class="mc" data-id="' + id + '" data-act="output">output</button>';
|
|
1854
|
+
var badge = (s.agent && s.agent !== 'claude-code') ? '<span class="agent-badge">' + esc(s.agent) + '</span>' : '';
|
|
1855
|
+
return '<div class="session-row">' +
|
|
1856
|
+
'<div class="pip ' + (s.state || 'unknown') + '"></div>' +
|
|
1857
|
+
'<div class="name">' + badge + esc(s.project || s.agent || 'agent') + '</div>' +
|
|
1858
|
+
'<div class="when">' + (s.controllable ? esc(s.controllable) : esc(statusLabel(s.state))) + '</div>' +
|
|
1859
|
+
(act ? '<div class="session-act">' + esc(act) + '</div>' : '') +
|
|
1860
|
+
(ctrl ? '<div class="session-ctrl">' + ctrl + '</div>' : '') +
|
|
1861
|
+
'</div>';
|
|
1862
|
+
}
|
|
1863
|
+
function controlAction(fam, id, action) {
|
|
1864
|
+
return fetch('/api/agents/' + fam + '/' + encodeURIComponent(id) + '/' + action, { method: 'POST' })
|
|
1865
|
+
.then(function (r) { if (!r.ok) return r.text().then(function (t) { throw new Error(String(r.status) + (t ? ' ' + t : '')); }); });
|
|
1866
|
+
}
|
|
1867
|
+
function ctrlPtyOutput(id) {
|
|
1868
|
+
getJSON('/api/agents/pty/' + encodeURIComponent(id) + '/output').then(function (d) {
|
|
1869
|
+
if (typeof openModal === 'function') openModal('agent output', '<pre>' + esc(d && d.output ? d.output : '(no output yet)') + '</pre>');
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1872
|
+
function loadControl() {
|
|
1873
|
+
views.control.innerHTML =
|
|
1874
|
+
'<div class="view-head"><div><h2>Control</h2><div class="vh-sub">Live agents & control plane</div></div>' +
|
|
1875
|
+
'<button class="view-act" id="ctrlDelegate">+ Delegate a task</button></div>' +
|
|
1876
|
+
'<div class="view-scroll" id="ctrlScroll"><div class="view-empty">loading…</div></div>';
|
|
1877
|
+
var del = document.getElementById('ctrlDelegate');
|
|
1878
|
+
if (del) del.addEventListener('click', function () { if (typeof window.lisaOpenDelegate === 'function') window.lisaOpenDelegate(); });
|
|
1879
|
+
renderControl();
|
|
1880
|
+
}
|
|
1881
|
+
function renderControl() {
|
|
1882
|
+
var scroll = document.getElementById('ctrlScroll');
|
|
1883
|
+
if (!scroll) return;
|
|
1884
|
+
Promise.all([getJSON('/api/agents/sessions'), getJSON('/api/control/policy')]).then(function (res) {
|
|
1885
|
+
var sessions = (res[0] && res[0].sessions) || [];
|
|
1886
|
+
var policy = res[1] || {};
|
|
1887
|
+
updateAgentCount(sessions.length);
|
|
1888
|
+
var html = '<div class="pp-tags" style="margin-bottom:14px">' +
|
|
1889
|
+
'<span class="pp-tag">remote control: ' + (policy.remoteControl ? 'on' : 'off') + '</span>' +
|
|
1890
|
+
'<span class="pp-tag">adopt external: ' + (policy.remoteAdoptExternal ? 'on' : 'off') + '</span></div>';
|
|
1891
|
+
if (!sessions.length) { scroll.innerHTML = html + '<div class="view-empty">No agents running. Delegate a task to start one.</div>'; return; }
|
|
1892
|
+
sessions.forEach(function (s) { html += controlRowHTML(s); });
|
|
1893
|
+
scroll.innerHTML = html;
|
|
1894
|
+
var btns = scroll.querySelectorAll('[data-act]');
|
|
1895
|
+
for (var i = 0; i < btns.length; i++) {
|
|
1896
|
+
btns[i].addEventListener('click', function () {
|
|
1897
|
+
var act = this.getAttribute('data-act');
|
|
1898
|
+
var fam = this.getAttribute('data-fam');
|
|
1899
|
+
var id = this.getAttribute('data-id');
|
|
1900
|
+
if (act === 'output') { ctrlPtyOutput(id); return; }
|
|
1901
|
+
controlAction(fam, id, act).then(function () { renderControl(); }).catch(function (err) {
|
|
1902
|
+
scroll.insertAdjacentHTML('afterbegin', '<div class="view-empty" style="color:var(--err-color)">' + esc(err.message) + '</div>');
|
|
1903
|
+
});
|
|
1904
|
+
});
|
|
1905
|
+
}
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// ── Reve ────────────────────────────────────────────────────────
|
|
1910
|
+
function loadReve() {
|
|
1911
|
+
views.reve.innerHTML =
|
|
1912
|
+
'<div class="view-head"><div><h2>Rêve</h2><div class="vh-sub">Reflections from her own time</div></div>' +
|
|
1913
|
+
'<select class="v-sel" id="reveWindow"><option value="120">last 2h</option><option value="480">last 8h</option><option value="1440">last 24h</option></select></div>' +
|
|
1914
|
+
'<div class="view-scroll" id="reveScroll"><div class="view-empty">loading…</div></div>';
|
|
1915
|
+
var sel = document.getElementById('reveWindow');
|
|
1916
|
+
if (sel) sel.addEventListener('change', function () { renderReve(sel.value); });
|
|
1917
|
+
renderReve('120');
|
|
1918
|
+
}
|
|
1919
|
+
function renderReve(mins) {
|
|
1920
|
+
var scroll = document.getElementById('reveScroll');
|
|
1921
|
+
if (!scroll) return;
|
|
1922
|
+
Promise.all([getJSON('/api/island/ping'), getJSON('/api/agents/recap?sinceMinutes=' + encodeURIComponent(mins))]).then(function (res) {
|
|
1923
|
+
var ping = res[0] || {}; var recap = res[1] || {};
|
|
1924
|
+
var html = '';
|
|
1925
|
+
if (ping.last_idle_message_text) html += '<div class="v-card"><h3>While you were away</h3><div style="font-size:13px;color:var(--fg);line-height:1.55">' + esc(ping.last_idle_message_text) + '</div></div>';
|
|
1926
|
+
if (ping.current_desire) html += '<div class="v-card"><h3>Currently pursuing</h3><div style="font-size:13px;color:var(--fg)">' + esc(ping.current_desire) + '</div></div>';
|
|
1927
|
+
html += '<div class="v-card"><h3>Recap</h3><pre class="v-pre">' + esc(recap.text || 'No activity in this window.') + '</pre></div>';
|
|
1928
|
+
scroll.innerHTML = html;
|
|
1929
|
+
});
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// ── Sense ───────────────────────────────────────────────────────
|
|
1933
|
+
function loadSense() {
|
|
1934
|
+
views.sense.innerHTML =
|
|
1935
|
+
'<div class="view-head"><div><h2>Sense</h2><div class="vh-sub">Ambient signals Lisa may see</div></div></div>' +
|
|
1936
|
+
'<div class="view-scroll" id="senseScroll"><div class="view-empty">loading…</div></div>';
|
|
1937
|
+
renderSense();
|
|
1938
|
+
}
|
|
1939
|
+
function renderSense() {
|
|
1940
|
+
var scroll = document.getElementById('senseScroll');
|
|
1941
|
+
if (!scroll) return;
|
|
1942
|
+
Promise.all([getJSON('/api/consent'), getJSON('/api/sense/recent')]).then(function (res) {
|
|
1943
|
+
var grants = (res[0] && res[0].grants) || [];
|
|
1944
|
+
var events = (res[1] && res[1].events) || [];
|
|
1945
|
+
var html = '<div class="view-sec-label">Consent</div><div class="v-card">';
|
|
1946
|
+
if (!grants.length) html += '<div class="view-empty" style="padding:6px 0">No signals configured.</div>';
|
|
1947
|
+
grants.forEach(function (g) {
|
|
1948
|
+
html += '<div class="v-row"><div class="v-main"><div class="v-name">' + esc(g.signal) + '</div>' +
|
|
1949
|
+
(g.description ? '<div class="v-sub">' + esc(g.description) + '</div>' : '') + '</div>' +
|
|
1950
|
+
'<button class="v-toggle' + (g.granted ? ' on' : '') + '" data-signal="' + esc(g.signal) + '" data-on="' + (g.granted ? '1' : '0') + '">' + (g.granted ? 'on' : 'off') + '</button></div>';
|
|
1951
|
+
});
|
|
1952
|
+
html += '</div><div class="view-sec-label">Recently sensed</div><div class="v-card">';
|
|
1953
|
+
if (!events.length) html += '<div class="view-empty" style="padding:6px 0">Nothing captured.</div>';
|
|
1954
|
+
events.slice(0, 30).forEach(function (e) {
|
|
1955
|
+
html += '<div class="v-row"><div class="v-main"><div class="v-name" style="font-weight:400">' + esc(e.summary || '') + '</div>' +
|
|
1956
|
+
'<div class="v-sub">' + esc([e.signal, e.kind, e.app].filter(Boolean).join(' · ')) + '</div></div></div>';
|
|
1957
|
+
});
|
|
1958
|
+
html += '</div>';
|
|
1959
|
+
scroll.innerHTML = html;
|
|
1960
|
+
var tgs = scroll.querySelectorAll('.v-toggle');
|
|
1961
|
+
for (var i = 0; i < tgs.length; i++) {
|
|
1962
|
+
tgs[i].addEventListener('click', function () {
|
|
1963
|
+
var sig = this.getAttribute('data-signal');
|
|
1964
|
+
var on = this.getAttribute('data-on') === '1';
|
|
1965
|
+
fetch(on ? '/api/consent/revoke' : '/api/consent/grant', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ signal: sig }) })
|
|
1966
|
+
.then(function () { renderSense(); }).catch(function () {});
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
});
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
// ── Memory (launchers for the existing modal panels) ────────────
|
|
1973
|
+
function memBtn(key, ico, title, sub) {
|
|
1974
|
+
return '<button class="mem-btn" data-mem="' + key + '"><span class="mem-ico">' + ico + '</span>' + title + '<span class="mem-sub">' + sub + '</span></button>';
|
|
1975
|
+
}
|
|
1976
|
+
function loadMemory() {
|
|
1977
|
+
views.memory.innerHTML =
|
|
1978
|
+
'<div class="view-head"><div><h2>Memory</h2><div class="vh-sub">Soul · skills · memory · tools</div></div></div>' +
|
|
1979
|
+
'<div class="view-scroll">' +
|
|
1980
|
+
memBtn('soul', '★', 'Soul', 'identity · values · emotions') +
|
|
1981
|
+
memBtn('skills', '✦', 'Skills', 'saved workflows') +
|
|
1982
|
+
memBtn('memory', '▤', 'Memory', 'USER.md · MEMORY.md') +
|
|
1983
|
+
memBtn('tools', '⚒', 'Tools', 'capabilities') +
|
|
1984
|
+
memBtn('plans', '◆', 'Coding plans', 'subscription CLIs') +
|
|
1985
|
+
'</div>';
|
|
1986
|
+
var mbs = views.memory.querySelectorAll('.mem-btn');
|
|
1987
|
+
for (var i = 0; i < mbs.length; i++) {
|
|
1988
|
+
mbs[i].addEventListener('click', function () {
|
|
1989
|
+
var w = this.getAttribute('data-mem');
|
|
1990
|
+
if (w === 'soul' && typeof showSoul === 'function') showSoul();
|
|
1991
|
+
else if (w === 'skills' && typeof showSkills === 'function') showSkills();
|
|
1992
|
+
else if (w === 'memory' && typeof showMemory === 'function') showMemory();
|
|
1993
|
+
else if (w === 'tools' && typeof showTools === 'function') showTools();
|
|
1994
|
+
else if (w === 'plans' && typeof showPlans === 'function') showPlans();
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// ── live refresh: wrap the agent-session refresh so the active console
|
|
2000
|
+
// view + the Control nav count update on SSE agent_session_update.
|
|
2001
|
+
var origRefresh = window.refreshClaudeSessions;
|
|
2002
|
+
window.refreshClaudeSessions = function () {
|
|
2003
|
+
var r = origRefresh ? origRefresh.apply(this, arguments) : undefined;
|
|
2004
|
+
Promise.resolve(r).then(function () {
|
|
2005
|
+
if (active === 'dashboard') renderDashboard();
|
|
2006
|
+
else if (active === 'control') renderControl();
|
|
2007
|
+
else getJSON('/api/agents/sessions').then(function (d) { updateAgentCount((d && d.sessions) ? d.sessions.length : 0); });
|
|
2008
|
+
});
|
|
2009
|
+
return r;
|
|
2010
|
+
};
|
|
2011
|
+
getJSON('/api/agents/sessions').then(function (d) { updateAgentCount((d && d.sessions) ? d.sessions.length : 0); });
|
|
2012
|
+
|
|
2013
|
+
// ── init: honor a deep-link hash, else default to chat ──────────
|
|
2014
|
+
var initial = (location.hash || '').replace('#', '');
|
|
2015
|
+
showView(views[initial] ? initial : 'chat');
|
|
2016
|
+
window.addEventListener('hashchange', function () {
|
|
2017
|
+
var h = (location.hash || '').replace('#', '');
|
|
2018
|
+
if (views[h] && h !== active) showView(h);
|
|
2019
|
+
});
|
|
1357
2020
|
})();`;
|
|
1358
2021
|
//# sourceMappingURL=lisa-client.js.map
|