claude-simple 0.2.5 → 0.2.7
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/README.md +2 -0
- package/bin/edge-ctl.js +28 -6
- package/dist/app.0.2.7.min.js +1039 -0
- package/dist/index.html +2 -2
- package/dist/style.0.2.7.min.css +1598 -0
- package/package.json +1 -1
- package/public/agent-panel.js +245 -1
- package/public/api.js +2 -0
- package/public/chat.js +81 -11
- package/public/index.html +2 -2
- package/public/shell/dashboard.js +39 -10
- package/public/shell/layout.js +39 -9
- package/public/shell/session-list.js +22 -14
- package/public/shell/session-nav.js +18 -0
- package/public/shell/templates.js +27 -7
- package/public/shell.js +32 -1
- package/public/state.js +4 -6
- package/public/style.css +100 -6
- package/public/sw.js +2 -2
- package/public/terminal.js +77 -3
- package/src/agents/claude.js +1 -1
- package/src/agents/codex.js +1 -0
- package/src/agents/grok.js +3 -2
- package/src/agents/meta-agent.js +332 -1
- package/src/agents/sessions.js +106 -26
- package/src/app.js +88 -11
- package/dist/app.0.2.5.min.js +0 -953
- package/dist/style.0.2.5.min.css +0 -1504
package/public/shell/layout.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '../state.js';
|
|
9
9
|
// filesRoot/gitRoot used when starting a new session (default paths)
|
|
10
10
|
import { api } from '../api.js';
|
|
11
|
-
import { connectWS, clearMessages, appendSystemMsg, applyChatDensity } from '../chat.js';
|
|
11
|
+
import { connectWS, clearMessages, appendSystemMsg, applyChatDensity, stashComposerDraft, restoreComposerDraft, discardComposerAttachments, parkQueuedMessages, detachProcessingForSessionSwitch } from '../chat.js';
|
|
12
12
|
import { refreshModelSelect } from './session-list.js';
|
|
13
13
|
import { switchTab } from './session-nav.js';
|
|
14
14
|
import { showMachinePicker } from './hub.js';
|
|
@@ -16,27 +16,37 @@ import { syncFocusScope } from '../mobile.js';
|
|
|
16
16
|
|
|
17
17
|
// ── Sidebar ───────────────────────────────────────────────────────────────────
|
|
18
18
|
export function openSidebar() {
|
|
19
|
-
$('sidebar')
|
|
19
|
+
const sidebar = $('sidebar');
|
|
20
|
+
if (!sidebar) return;
|
|
21
|
+
sidebar.classList.add('open');
|
|
20
22
|
$('sidebar-overlay').classList.remove('hidden');
|
|
23
|
+
$('hamburger')?.setAttribute('aria-expanded', 'true');
|
|
21
24
|
syncFocusScope();
|
|
25
|
+
// Put the next action where it belongs. Delaying lets the drawer transition
|
|
26
|
+
// start first, which is more reliable in mobile Safari.
|
|
27
|
+
setTimeout(() => $('session-search')?.focus({ preventScroll: true }), 180);
|
|
22
28
|
}
|
|
23
29
|
export function closeSidebar() {
|
|
24
|
-
$('sidebar')
|
|
30
|
+
const sidebar = $('sidebar');
|
|
31
|
+
if (!sidebar) return;
|
|
32
|
+
sidebar.classList.remove('open');
|
|
25
33
|
$('sidebar-overlay').classList.add('hidden');
|
|
34
|
+
$('hamburger')?.setAttribute('aria-expanded', 'false');
|
|
26
35
|
syncFocusScope();
|
|
36
|
+
$('hamburger')?.focus({ preventScroll: true });
|
|
27
37
|
}
|
|
28
38
|
export function closeSidebarOnMobile() { if (window.innerWidth <= 640) closeSidebar(); }
|
|
29
39
|
|
|
30
40
|
export function initSidebarToggle() {
|
|
31
41
|
const btn = $('sidebar-toggle');
|
|
32
42
|
const sidebar = $('sidebar');
|
|
33
|
-
|
|
34
|
-
let
|
|
43
|
+
if (!btn || !sidebar) return;
|
|
44
|
+
let collapsed = localStorage.getItem('sidebarCollapsed') === '1';
|
|
45
|
+
let savedWidth = localStorage.getItem('sidebarWidth') || sidebar.style.width || '280px';
|
|
35
46
|
|
|
36
|
-
|
|
37
|
-
collapsed
|
|
47
|
+
const applyCollapsed = () => {
|
|
48
|
+
sidebar.classList.toggle('desktop-collapsed', collapsed);
|
|
38
49
|
if (collapsed) {
|
|
39
|
-
savedWidth = sidebar.style.width || '260px';
|
|
40
50
|
sidebar.style.width = '0';
|
|
41
51
|
sidebar.style.minWidth = '0';
|
|
42
52
|
sidebar.style.overflow = 'hidden';
|
|
@@ -46,6 +56,17 @@ export function initSidebarToggle() {
|
|
|
46
56
|
sidebar.style.overflow = '';
|
|
47
57
|
}
|
|
48
58
|
btn.textContent = collapsed ? '▶' : '◀';
|
|
59
|
+
btn.title = collapsed ? '展开侧栏' : '收起侧栏';
|
|
60
|
+
btn.setAttribute('aria-label', btn.title);
|
|
61
|
+
btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
|
|
62
|
+
};
|
|
63
|
+
applyCollapsed();
|
|
64
|
+
|
|
65
|
+
btn.addEventListener('click', () => {
|
|
66
|
+
collapsed = !collapsed;
|
|
67
|
+
if (collapsed) savedWidth = sidebar.style.width || savedWidth;
|
|
68
|
+
localStorage.setItem('sidebarCollapsed', collapsed ? '1' : '0');
|
|
69
|
+
applyCollapsed();
|
|
49
70
|
});
|
|
50
71
|
}
|
|
51
72
|
|
|
@@ -71,6 +92,8 @@ export function initSidebarResize() {
|
|
|
71
92
|
dragging = false;
|
|
72
93
|
document.body.style.userSelect = '';
|
|
73
94
|
document.body.style.cursor = '';
|
|
95
|
+
const width = Math.round(sidebar.getBoundingClientRect().width);
|
|
96
|
+
if (width >= 160) localStorage.setItem('sidebarWidth', `${width}px`);
|
|
74
97
|
});
|
|
75
98
|
}
|
|
76
99
|
|
|
@@ -79,7 +102,9 @@ export function applyTheme(theme) {
|
|
|
79
102
|
document.documentElement.setAttribute('data-theme', theme);
|
|
80
103
|
localStorage.setItem('theme', theme);
|
|
81
104
|
const btn = $('btn-theme');
|
|
82
|
-
|
|
105
|
+
const icon = theme === 'dark' || theme === 'night' || theme === 'black' ? '🌙' : '☀️';
|
|
106
|
+
if (btn) btn.textContent = icon;
|
|
107
|
+
document.querySelectorAll('[data-sidebar-action="theme"]').forEach(el => { el.textContent = icon; });
|
|
83
108
|
// Swap highlight.js stylesheet for light/dark
|
|
84
109
|
let link = document.getElementById('hljs-theme');
|
|
85
110
|
if (!link) {
|
|
@@ -171,6 +196,10 @@ export async function startNewSession() {
|
|
|
171
196
|
$('modal-new-session').close();
|
|
172
197
|
|
|
173
198
|
const name = result.path.split('/').filter(Boolean).pop() || result.path;
|
|
199
|
+
stashComposerDraft();
|
|
200
|
+
parkQueuedMessages();
|
|
201
|
+
discardComposerAttachments();
|
|
202
|
+
detachProcessingForSessionSwitch();
|
|
174
203
|
ctx.sessionId = null;
|
|
175
204
|
ctx.configDir = configDir || null;
|
|
176
205
|
ctx.agent = agent;
|
|
@@ -194,6 +223,7 @@ export async function startNewSession() {
|
|
|
194
223
|
pv.classList.remove('hidden');
|
|
195
224
|
pv.style.display = 'flex';
|
|
196
225
|
clearMessages();
|
|
226
|
+
restoreComposerDraft({ sessionId: null });
|
|
197
227
|
connectWS();
|
|
198
228
|
switchTab('chat');
|
|
199
229
|
appendSystemMsg(`New ${AGENT_LABELS[agent] || agent} session · ${result.path}`);
|
|
@@ -430,7 +430,7 @@ export function mdToHtml(text) {
|
|
|
430
430
|
}
|
|
431
431
|
}
|
|
432
432
|
|
|
433
|
-
//
|
|
433
|
+
// Memory is shown as an agent-specific file list. Content is loaded on click.
|
|
434
434
|
export async function loadMemoryTab() {
|
|
435
435
|
const body = $('memory-area');
|
|
436
436
|
if (!body) return;
|
|
@@ -440,22 +440,30 @@ export async function loadMemoryTab() {
|
|
|
440
440
|
}
|
|
441
441
|
body.innerHTML = '<div class="p-4 text-xs text-base-content/40">Loading…</div>';
|
|
442
442
|
try {
|
|
443
|
-
const
|
|
443
|
+
const agent = ctx.agent || currentAgent.peek() || 'claude';
|
|
444
|
+
const q = new URLSearchParams({ agent });
|
|
444
445
|
const mem = await api('GET', `/api/sessions/${ctx.sessionId}/memory?${q}`);
|
|
445
|
-
const
|
|
446
|
-
if (
|
|
447
|
-
|
|
448
|
-
|
|
446
|
+
const files = mem.files || [];
|
|
447
|
+
if (!files.length) {
|
|
448
|
+
body.innerHTML = '<div class="p-4 text-base-content/40 text-xs">No memory files available for this agent/session.</div>';
|
|
449
|
+
return;
|
|
449
450
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
<div class="px-3 py-1.5 bg-base-300/50 text-xs font-mono text-base-content/70">${esc(f.name)}</div>
|
|
453
|
-
<div class="px-3 py-2 md">${mdToHtml(f.content)}</div>
|
|
454
|
-
</div>`);
|
|
455
|
-
}
|
|
456
|
-
if (!parts.length) parts.push('<div class="text-base-content/40 text-xs">No memory saved for this project yet.</div>');
|
|
457
|
-
body.innerHTML = `<div class="p-4">${parts.join('')}</div>`;
|
|
451
|
+
body.innerHTML = `<div class="p-3 text-[11px] text-base-content/45">${esc(agent)} · ${files.length} memory file${files.length === 1 ? '' : 's'} · click to open</div>
|
|
452
|
+
<div class="px-3 pb-4 flex flex-col gap-1">${files.map(f => `<button type="button" class="memory-file text-left px-3 py-2 rounded-lg border border-base-300 hover:border-primary hover:bg-primary/5" data-memory-file="${esc(f.id)}" data-memory-agent="${esc(agent)}"><span class="font-mono text-xs">${esc(f.name)}</span><span class="ml-2 text-[10px] text-base-content/40">${esc(f.scope || '')}</span></button>`).join('')}<div id="memory-file-content"></div></div>`;
|
|
458
453
|
} catch (e) {
|
|
459
454
|
body.innerHTML = `<div class="p-4 text-error text-xs">${esc(e?.message ?? String(e))}</div>`;
|
|
460
455
|
}
|
|
461
456
|
}
|
|
457
|
+
|
|
458
|
+
export async function openMemoryFile(fileId, agent) {
|
|
459
|
+
const out = $('memory-file-content');
|
|
460
|
+
if (!out || !ctx.sessionId || !fileId) return;
|
|
461
|
+
out.innerHTML = '<div class="p-3 text-xs text-base-content/40">Loading file…</div>';
|
|
462
|
+
try {
|
|
463
|
+
const q = new URLSearchParams({ agent: agent || ctx.agent || currentAgent.peek() || 'claude', file: fileId });
|
|
464
|
+
const row = await api('GET', `/api/sessions/${ctx.sessionId}/memory/file?${q}`);
|
|
465
|
+
out.innerHTML = `<div class="mt-2 border border-base-300 rounded-lg overflow-hidden"><div class="px-3 py-1.5 bg-base-300/50 text-xs font-mono">${esc(row.name)}</div><div class="px-3 py-2 md">${mdToHtml(row.content)}</div></div>`;
|
|
466
|
+
} catch (e) {
|
|
467
|
+
out.innerHTML = `<div class="mt-2 p-3 text-error text-xs">${esc(e?.message ?? String(e))}</div>`;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
@@ -10,6 +10,7 @@ import { getLastSessionContext, setLastSessionContext } from './session-context.
|
|
|
10
10
|
import {
|
|
11
11
|
connectWS, clearMessages, appendMsg, appendSystemMsg, renderToolUse,
|
|
12
12
|
appendHistoryTokenBar, appendContextBar, flushToolBatch, coerceTs,
|
|
13
|
+
stashComposerDraft, restoreComposerDraft, discardComposerAttachments, parkQueuedMessages, detachProcessingForSessionSwitch,
|
|
13
14
|
} from '../chat.js';
|
|
14
15
|
import { setHash, syncHash } from '../router.js';
|
|
15
16
|
import { refreshModelSelect } from './session-list.js';
|
|
@@ -34,6 +35,10 @@ function syncMachinePathHints(machineId) {
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function openProject(project) {
|
|
38
|
+
stashComposerDraft();
|
|
39
|
+
parkQueuedMessages();
|
|
40
|
+
discardComposerAttachments();
|
|
41
|
+
detachProcessingForSessionSwitch();
|
|
37
42
|
const path = project?.path || '';
|
|
38
43
|
batch(() => {
|
|
39
44
|
currentProject.value = project;
|
|
@@ -56,11 +61,16 @@ export function openProject(project) {
|
|
|
56
61
|
pv.classList.remove('hidden');
|
|
57
62
|
pv.style.display = 'flex';
|
|
58
63
|
clearMessages();
|
|
64
|
+
restoreComposerDraft({ sessionId: null });
|
|
59
65
|
connectWS();
|
|
60
66
|
switchTab('chat');
|
|
61
67
|
}
|
|
62
68
|
|
|
63
69
|
export function goHome() {
|
|
70
|
+
stashComposerDraft();
|
|
71
|
+
parkQueuedMessages();
|
|
72
|
+
discardComposerAttachments();
|
|
73
|
+
detachProcessingForSessionSwitch();
|
|
64
74
|
currentProject.value = null;
|
|
65
75
|
sessionFilter.value = null;
|
|
66
76
|
filesRoot.value = '';
|
|
@@ -68,6 +78,7 @@ export function goHome() {
|
|
|
68
78
|
filesPath.value = '';
|
|
69
79
|
viewingFile.value = null;
|
|
70
80
|
ctx.sessionId = null;
|
|
81
|
+
restoreComposerDraft({ sessionId: null });
|
|
71
82
|
syncMachinePathHints(null);
|
|
72
83
|
|
|
73
84
|
// Leaving session restore mode — calendar is allowed again
|
|
@@ -116,6 +127,12 @@ export async function resumeSession(sid, cwd, configDir, agent, machineId, opts
|
|
|
116
127
|
const resolvedCwd = cwd || fromList?.cwd || fromLast?.cwd || null;
|
|
117
128
|
const resolvedConfig = configDir || fromList?.configDir || fromLast?.configDir || null;
|
|
118
129
|
|
|
130
|
+
// Capture before changing ctx, then restore only after the new identity is set.
|
|
131
|
+
stashComposerDraft();
|
|
132
|
+
parkQueuedMessages();
|
|
133
|
+
discardComposerAttachments();
|
|
134
|
+
detachProcessingForSessionSwitch();
|
|
135
|
+
|
|
119
136
|
if (resolvedMachine && resolvedMachine !== ctx.machineId) {
|
|
120
137
|
setSelectedMachine(resolvedMachine);
|
|
121
138
|
try { ctx.ws?.close(); } catch {}
|
|
@@ -127,6 +144,7 @@ export async function resumeSession(sid, cwd, configDir, agent, machineId, opts
|
|
|
127
144
|
ctx.agent = resolvedAgent;
|
|
128
145
|
setAgent(resolvedAgent);
|
|
129
146
|
refreshModelSelect();
|
|
147
|
+
restoreComposerDraft({ sessionId: sid, agent: resolvedAgent, machineId: resolvedMachine });
|
|
130
148
|
|
|
131
149
|
// Default path = session cwd on the target machine (set immediately; no await)
|
|
132
150
|
let edgeCwd = resolvedCwd;
|
|
@@ -39,10 +39,10 @@ export const AppShell = (opts = {}) => {
|
|
|
39
39
|
: 'hidden flex-col flex-1 overflow-hidden min-h-0';
|
|
40
40
|
const projectStyle = restore ? 'display:flex;flex-direction:column;flex:1;min-height:0' : '';
|
|
41
41
|
return `
|
|
42
|
-
<div id="sidebar-overlay" class="hidden fixed inset-0 bg-black/50 z-[9]"></div>
|
|
42
|
+
<div id="sidebar-overlay" class="hidden fixed inset-0 bg-black/50 z-[9]" aria-hidden="true"></div>
|
|
43
43
|
<div id="app" class="${appHidden} flex-col flex-1 overflow-hidden" style="${appStyle}">
|
|
44
44
|
<div id="topbar" class="flex items-center gap-2 px-3 bg-base-200 border-b border-base-300 flex-shrink-0" style="height:44px">
|
|
45
|
-
<button id="hamburger" class="btn btn-ghost btn-sm px-2 text-lg">☰</button>
|
|
45
|
+
<button id="hamburger" class="btn btn-ghost btn-sm px-2 text-lg" aria-label="打开项目与会话侧栏" aria-controls="sidebar" aria-expanded="false">☰</button>
|
|
46
46
|
<span id="btn-home" class="font-semibold text-sm hidden sm:inline cursor-pointer hover:text-primary transition-colors select-none">🤖 Agent UI</span>
|
|
47
47
|
<span class="text-base-300 hidden sm:inline">/</span>
|
|
48
48
|
<div id="machine-menu-wrap" class="relative hidden flex-shrink-0">
|
|
@@ -107,7 +107,7 @@ export const AppShell = (opts = {}) => {
|
|
|
107
107
|
</div>
|
|
108
108
|
</div>
|
|
109
109
|
<div class="flex flex-1 overflow-hidden relative">
|
|
110
|
-
<
|
|
110
|
+
<aside id="sidebar" class="bg-base-200 border-r border-base-300 flex flex-col flex-shrink-0 overflow-hidden" style="width:280px;min-width:180px;max-width:520px" aria-label="项目与会话" tabindex="-1">
|
|
111
111
|
<div class="flex items-center justify-between px-3 py-2 border-b border-base-300 flex-shrink-0">
|
|
112
112
|
<span class="text-xs uppercase tracking-wider text-base-content/50 font-medium">Projects</span>
|
|
113
113
|
<div class="flex items-center gap-1">
|
|
@@ -143,10 +143,16 @@ export const AppShell = (opts = {}) => {
|
|
|
143
143
|
<div id="search-status" class="hidden text-[10px] text-base-content/40 px-0.5"></div>
|
|
144
144
|
</div>
|
|
145
145
|
<div id="session-list" class="overflow-y-auto flex-1"></div>
|
|
146
|
-
|
|
146
|
+
<div class="sidebar-mobile-actions hidden border-t border-base-300 px-2 py-2 flex-shrink-0">
|
|
147
|
+
<button type="button" class="btn btn-primary btn-sm flex-1" data-sidebar-action="new">+ 新会话</button>
|
|
148
|
+
<button type="button" class="btn btn-ghost btn-sm px-2" data-sidebar-action="theme" aria-label="切换深色或浅色主题">🌙</button>
|
|
149
|
+
<button type="button" class="btn btn-ghost btn-sm px-2" data-sidebar-action="settings" aria-label="设置">⚙️</button>
|
|
150
|
+
<button type="button" class="btn btn-ghost btn-sm px-2 text-error" data-sidebar-action="logout" aria-label="退出登录">⇥</button>
|
|
151
|
+
</div>
|
|
152
|
+
</aside>
|
|
147
153
|
<div class="flex flex-col flex-shrink-0 relative" style="width:12px">
|
|
148
154
|
<div id="sidebar-resize" class="absolute inset-0 bg-transparent hover:bg-primary/30 transition-colors cursor-col-resize"></div>
|
|
149
|
-
<button id="sidebar-toggle" title="
|
|
155
|
+
<button id="sidebar-toggle" title="收起侧栏" aria-label="收起侧栏" aria-controls="sidebar" aria-expanded="true"
|
|
150
156
|
class="absolute z-10 flex items-center justify-center w-5 h-8 bg-base-200 border border-base-300 rounded-r-md text-[10px] text-base-content/40 hover:text-base-content cursor-pointer"
|
|
151
157
|
style="left:0;top:50%;transform:translateY(-50%)">◀</button>
|
|
152
158
|
</div>
|
|
@@ -278,7 +284,7 @@ export const AppShell = (opts = {}) => {
|
|
|
278
284
|
<option value="acceptEdits">acceptEdits</option>
|
|
279
285
|
<option value="auto">auto</option>
|
|
280
286
|
<option value="plan">plan</option>
|
|
281
|
-
<option value="bypassPermissions">⚠ bypass all</option>
|
|
287
|
+
<option value="bypassPermissions" selected>⚠ bypass all</option>
|
|
282
288
|
</select>
|
|
283
289
|
<select id="sel-density" class="select select-xs select-bordered text-xs" title="Chat density — how tools are shown">
|
|
284
290
|
<option value="clean">density: clean</option>
|
|
@@ -332,9 +338,23 @@ export const AppShell = (opts = {}) => {
|
|
|
332
338
|
<div id="tab-shell" class="hidden flex-col flex-1 overflow-hidden bg-[#0d1117]">
|
|
333
339
|
<div class="flex items-center gap-2 px-3 py-1.5 border-b border-base-300 bg-base-200 flex-shrink-0">
|
|
334
340
|
<span class="text-xs text-base-content/40 font-mono flex-1" id="term-cwd"></span>
|
|
341
|
+
<button id="term-keyboard" class="btn btn-ghost btn-xs text-xs text-base-content/60" type="button">Keyboard</button>
|
|
335
342
|
<button id="term-reconnect" class="btn btn-ghost btn-xs text-xs text-base-content/40">Reconnect</button>
|
|
336
343
|
</div>
|
|
344
|
+
<div id="term-mobile-keys" class="hidden items-center gap-1 px-2 py-1 border-b border-slate-700 bg-[#161b22] flex-shrink-0" aria-label="Terminal shortcuts">
|
|
345
|
+
<button class="term-key" type="button" data-term-key="ctrl-c">Ctrl+C</button>
|
|
346
|
+
<button class="term-key" type="button" data-term-key="escape">Esc</button>
|
|
347
|
+
<button class="term-key" type="button" data-term-key="tab">Tab</button>
|
|
348
|
+
<button class="term-key" type="button" data-term-key="up">↑</button>
|
|
349
|
+
<button class="term-key" type="button" data-term-key="down">↓</button>
|
|
350
|
+
<button class="term-key" type="button" data-term-key="left">←</button>
|
|
351
|
+
<button class="term-key" type="button" data-term-key="right">→</button>
|
|
352
|
+
</div>
|
|
337
353
|
<div id="terminal-container" class="flex-1 p-2 overflow-hidden"></div>
|
|
354
|
+
<form id="term-mobile-composer" class="hidden items-center gap-2 px-2 py-2 border-t border-slate-700 bg-[#161b22] flex-shrink-0">
|
|
355
|
+
<input id="term-mobile-input" class="term-mobile-input" type="text" placeholder="输入命令…" autocomplete="off" autocapitalize="off" autocorrect="off" spellcheck="false" inputmode="text" enterkeyhint="send" aria-label="Terminal command">
|
|
356
|
+
<button class="term-mobile-send" type="submit">发送</button>
|
|
357
|
+
</form>
|
|
338
358
|
</div>
|
|
339
359
|
<div id="tab-git" class="hidden flex-col flex-1 overflow-hidden">
|
|
340
360
|
<div class="flex items-center gap-2 px-3 py-1.5 border-b border-base-300 flex-shrink-0 bg-base-200">
|
|
@@ -348,7 +368,7 @@ export const AppShell = (opts = {}) => {
|
|
|
348
368
|
</div>
|
|
349
369
|
<div id="tab-memory" class="hidden flex-col flex-1 overflow-hidden">
|
|
350
370
|
<div class="flex items-center gap-2 px-3 py-1.5 border-b border-base-300 flex-shrink-0 bg-base-200">
|
|
351
|
-
<span class="text-xs text-base-content/40 flex-shrink-0">🧠
|
|
371
|
+
<span class="text-xs text-base-content/40 flex-shrink-0">🧠 Agent memory</span>
|
|
352
372
|
<span class="flex-1"></span>
|
|
353
373
|
<button id="memory-refresh" class="btn btn-ghost btn-xs text-xs">Refresh</button>
|
|
354
374
|
</div>
|
package/public/shell.js
CHANGED
|
@@ -28,11 +28,12 @@ import { AuthScreen, AppShell } from './shell/templates.js';
|
|
|
28
28
|
import {
|
|
29
29
|
updateDashboard, shiftCalMonth, setSelectedCalDate, getDashboardSessions,
|
|
30
30
|
openRecentMenu, toggleRecentMenu, closeRecentMenu, positionRecentMenu,
|
|
31
|
+
dismissRecentSession,
|
|
31
32
|
} from './shell/dashboard.js';
|
|
32
33
|
import {
|
|
33
34
|
loadAllSessions, loadWorkspaces, renderSessionList, syncSidebarChrome,
|
|
34
35
|
refreshModelSelect, refreshEffortSelect, promptCustomModel,
|
|
35
|
-
scheduleActivitySearch, runActivitySearch, loadMemoryTab,
|
|
36
|
+
scheduleActivitySearch, runActivitySearch, loadMemoryTab, openMemoryFile,
|
|
36
37
|
} from './shell/session-list.js';
|
|
37
38
|
import { openProject, goHome, resumeSession, switchTab } from './shell/session-nav.js';
|
|
38
39
|
import { showApp } from './shell/boot.js';
|
|
@@ -336,6 +337,13 @@ export function initShell(opts = {}) {
|
|
|
336
337
|
delegate.on('keydown', '#auth-username, #auth-password', e => { if (e.key === 'Enter') submitAuth(); });
|
|
337
338
|
delegate.on('click', '#hamburger', () => $('sidebar').classList.contains('open') ? closeSidebar() : openSidebar());
|
|
338
339
|
delegate.on('click', '#sidebar-overlay', closeSidebar);
|
|
340
|
+
document.addEventListener('keydown', e => {
|
|
341
|
+
if (e.key !== 'Escape') return;
|
|
342
|
+
if (window.innerWidth <= 640 && $('sidebar')?.classList.contains('open')) {
|
|
343
|
+
e.preventDefault();
|
|
344
|
+
closeSidebar();
|
|
345
|
+
}
|
|
346
|
+
});
|
|
339
347
|
|
|
340
348
|
delegate.on('input', '#session-search', (_, el) => {
|
|
341
349
|
sessionSearch.value = el.value;
|
|
@@ -383,6 +391,19 @@ export function initShell(opts = {}) {
|
|
|
383
391
|
delegate.on('click', '#btn-meta-agent', () => toggleMetaAgent());
|
|
384
392
|
delegate.on('click', '#btn-meta-agent-welcome', () => openMetaAgent('chat'));
|
|
385
393
|
delegate.on('click', '#btn-theme', toggleTheme);
|
|
394
|
+
delegate.on('click', '[data-sidebar-action]', (_, el) => {
|
|
395
|
+
const action = el.dataset.sidebarAction;
|
|
396
|
+
if (action === 'new') openNewSessionModal();
|
|
397
|
+
if (action === 'theme') toggleTheme();
|
|
398
|
+
if (action === 'settings') openSettings();
|
|
399
|
+
if (action === 'logout') {
|
|
400
|
+
ctx.token = null;
|
|
401
|
+
localStorage.removeItem('token');
|
|
402
|
+
setSelectedMachine(null);
|
|
403
|
+
hubMachineReady.value = false;
|
|
404
|
+
location.reload();
|
|
405
|
+
}
|
|
406
|
+
});
|
|
386
407
|
|
|
387
408
|
delegate.on('click', '#btn-cal-prev', () => {
|
|
388
409
|
shiftCalMonth(-1);
|
|
@@ -462,6 +483,7 @@ export function initShell(opts = {}) {
|
|
|
462
483
|
});
|
|
463
484
|
|
|
464
485
|
delegate.on('click', '#memory-refresh', loadMemoryTab);
|
|
486
|
+
delegate.on('click', '[data-memory-file]', (el) => openMemoryFile(el.dataset.memoryFile, el.dataset.memoryAgent));
|
|
465
487
|
delegate.on('click', '[data-tab]', (e, el) => switchTab(el.dataset.tab));
|
|
466
488
|
|
|
467
489
|
const applyRoot = async (inputId, sig) => {
|
|
@@ -638,6 +660,15 @@ export function initShell(opts = {}) {
|
|
|
638
660
|
closeRecentMenu();
|
|
639
661
|
$('btn-recent-sessions')?.setAttribute('aria-expanded', 'false');
|
|
640
662
|
});
|
|
663
|
+
delegate.on('click', '[data-dismiss-recent-session]', (e, el) => {
|
|
664
|
+
e.preventDefault();
|
|
665
|
+
e.stopPropagation();
|
|
666
|
+
dismissRecentSession({
|
|
667
|
+
sessionId: el.dataset.dismissRecentSession,
|
|
668
|
+
agent: el.dataset.dismissRecentAgent || 'claude',
|
|
669
|
+
machineId: el.dataset.dismissRecentMachine || null,
|
|
670
|
+
});
|
|
671
|
+
});
|
|
641
672
|
|
|
642
673
|
delegate.on('click', '#btn-edit-project-notes, #project-goal-text, #project-notes-text', () => {
|
|
643
674
|
renderProjectNotesEditMode();
|
package/public/state.js
CHANGED
|
@@ -18,11 +18,9 @@ export const wsStatus = signal('idle');
|
|
|
18
18
|
export const currentAgent = signal(localStorage.getItem('agent') || 'claude'); // 'claude'|'codex'|'grok'
|
|
19
19
|
export const currentModel = signal(localStorage.getItem('model') || 'claude-sonnet-4-5');
|
|
20
20
|
export const currentEffort = signal('');
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
export const currentPermission = signal(
|
|
24
|
-
currentAgent.peek() === 'codex' ? 'bypassPermissions' : 'default'
|
|
25
|
-
); // 'default'|'acceptEdits'|'bypassPermissions'|'plan'|'auto'
|
|
21
|
+
// All local agents are trusted by default; users can still choose another mode
|
|
22
|
+
// from the expanded composer options before starting a turn.
|
|
23
|
+
export const currentPermission = signal('bypassPermissions'); // 'default'|'acceptEdits'|'bypassPermissions'|'plan'|'auto'
|
|
26
24
|
export const filesRoot = signal(''); // custom root override for Files tab
|
|
27
25
|
export const gitRoot = signal(''); // custom root override for Git tab
|
|
28
26
|
export const agentsMeta = signal(null); // from /api/agents (dynamic model lists)
|
|
@@ -251,7 +249,7 @@ export function setAgent(agent) {
|
|
|
251
249
|
const a = (agent || 'claude').toLowerCase();
|
|
252
250
|
currentAgent.value = a;
|
|
253
251
|
localStorage.setItem('agent', a);
|
|
254
|
-
currentPermission.value =
|
|
252
|
+
currentPermission.value = 'bypassPermissions';
|
|
255
253
|
const permissionSelect = typeof document === 'undefined' ? null : document.getElementById('sel-permission');
|
|
256
254
|
if (permissionSelect) permissionSelect.value = currentPermission.peek();
|
|
257
255
|
const models = getModelsForAgent(a);
|
package/public/style.css
CHANGED
|
@@ -6,21 +6,25 @@
|
|
|
6
6
|
top: calc(44px + env(safe-area-inset-top, 0px));
|
|
7
7
|
left: 0;
|
|
8
8
|
bottom: 0;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
width: min(86vw, 22rem) !important;
|
|
10
|
+
min-width: min(86vw, 22rem) !important;
|
|
11
|
+
max-width: min(86vw, 22rem) !important;
|
|
12
|
+
transform: translateX(-103%); z-index: 10;
|
|
13
|
+
transition: transform .22s cubic-bezier(.2,.8,.2,1), box-shadow .22s ease;
|
|
11
14
|
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
15
|
+
overscroll-behavior: contain;
|
|
12
16
|
}
|
|
13
17
|
#sidebar.open { transform: translateX(0); box-shadow: 4px 0 20px rgba(0,0,0,.4); }
|
|
14
18
|
|
|
15
19
|
/* Compact chat composer on phones — less height, less dead space under bar */
|
|
16
20
|
#composer-row { padding: 0 !important; }
|
|
17
|
-
.composer-surface { min-height: 0; border-radius:
|
|
21
|
+
.composer-surface { min-height: 0; border-radius: 0.8rem; }
|
|
18
22
|
#chat-input.chat-input,
|
|
19
23
|
#chat-input {
|
|
20
24
|
min-height: 32px !important;
|
|
21
25
|
max-height: 72px !important;
|
|
22
26
|
padding: 0 !important;
|
|
23
|
-
border-radius:
|
|
27
|
+
border-radius: 0.65rem 0.65rem 0 0;
|
|
24
28
|
/* 16px prevents iOS Safari auto-zoom on focus */
|
|
25
29
|
font-size: 16px !important;
|
|
26
30
|
line-height: 1.4 !important;
|
|
@@ -76,6 +80,25 @@
|
|
|
76
80
|
#sidebar-toggle {
|
|
77
81
|
display: none !important;
|
|
78
82
|
}
|
|
83
|
+
#sidebar .input,
|
|
84
|
+
#sidebar .select {
|
|
85
|
+
min-height: 2.25rem;
|
|
86
|
+
font-size: 16px !important; /* prevents Safari focus zoom */
|
|
87
|
+
}
|
|
88
|
+
#sidebar #time-range { font-size: 12px !important; }
|
|
89
|
+
#sidebar #session-list {
|
|
90
|
+
padding-bottom: max(0.75rem, env(safe-area-inset-bottom, 0px));
|
|
91
|
+
-webkit-overflow-scrolling: touch;
|
|
92
|
+
}
|
|
93
|
+
#sidebar [data-view],
|
|
94
|
+
#sidebar .agent-pill {
|
|
95
|
+
min-height: 28px;
|
|
96
|
+
}
|
|
97
|
+
#sidebar .sidebar-mobile-actions {
|
|
98
|
+
display: flex;
|
|
99
|
+
gap: 0.35rem;
|
|
100
|
+
padding-bottom: max(0.5rem, env(safe-area-inset-bottom, 0px));
|
|
101
|
+
}
|
|
79
102
|
.md {
|
|
80
103
|
font-size: 0.9375rem;
|
|
81
104
|
line-height: 1.6;
|
|
@@ -101,6 +124,16 @@
|
|
|
101
124
|
}
|
|
102
125
|
}
|
|
103
126
|
|
|
127
|
+
@media (min-width: 641px) {
|
|
128
|
+
#sidebar .sidebar-mobile-actions { display: none !important; }
|
|
129
|
+
#sidebar {
|
|
130
|
+
transition: width .18s ease, min-width .18s ease;
|
|
131
|
+
}
|
|
132
|
+
#sidebar.desktop-collapsed {
|
|
133
|
+
border-right-color: transparent;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
104
137
|
/* File row actions: hover-reveal on desktop only */
|
|
105
138
|
@media (min-width: 641px) {
|
|
106
139
|
.file-row-action {
|
|
@@ -962,7 +995,7 @@ html.pwa-standalone .ma-panel {
|
|
|
962
995
|
flex-direction: column;
|
|
963
996
|
overflow: hidden;
|
|
964
997
|
border: 1px solid color-mix(in oklch, var(--color-base-content) 16%, transparent);
|
|
965
|
-
border-radius:
|
|
998
|
+
border-radius: 0.875rem;
|
|
966
999
|
background: color-mix(in oklch, var(--color-base-100) 78%, var(--color-base-200));
|
|
967
1000
|
box-shadow: 0 8px 24px color-mix(in oklch, black 12%, transparent), inset 0 1px 0 color-mix(in oklch, white 6%, transparent);
|
|
968
1001
|
transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
|
|
@@ -1064,7 +1097,7 @@ html.pwa-standalone .ma-panel {
|
|
|
1064
1097
|
max-height: 72px;
|
|
1065
1098
|
padding: 0;
|
|
1066
1099
|
border: 0;
|
|
1067
|
-
border-radius:
|
|
1100
|
+
border-radius: 0.75rem 0.75rem 0 0;
|
|
1068
1101
|
background: transparent;
|
|
1069
1102
|
color: var(--color-base-content);
|
|
1070
1103
|
outline: none !important;
|
|
@@ -1297,6 +1330,52 @@ html.pwa-standalone .ma-panel {
|
|
|
1297
1330
|
/* Shell tab terminal — keep dark intentionally (terminal aesthetic) */
|
|
1298
1331
|
#tab-shell { background: #0d1117; }
|
|
1299
1332
|
#term-output { color: #c9d1d9; }
|
|
1333
|
+
#term-keyboard { display: none; }
|
|
1334
|
+
|
|
1335
|
+
/* xterm parks its input off-screen, which is unreliable for mobile IMEs.
|
|
1336
|
+
* Phones use the visible native command composer below the terminal instead. */
|
|
1337
|
+
@media (max-width: 640px), (hover: none) and (pointer: coarse) {
|
|
1338
|
+
#term-keyboard { display: inline-flex; }
|
|
1339
|
+
#term-mobile-keys { display: flex !important; }
|
|
1340
|
+
#term-mobile-composer { display: flex !important; }
|
|
1341
|
+
#terminal-container { touch-action: manipulation; }
|
|
1342
|
+
.term-key {
|
|
1343
|
+
min-width: 38px;
|
|
1344
|
+
min-height: 32px;
|
|
1345
|
+
padding: 3px 7px;
|
|
1346
|
+
border: 1px solid #30363d;
|
|
1347
|
+
border-radius: 6px;
|
|
1348
|
+
color: #c9d1d9;
|
|
1349
|
+
background: #21262d;
|
|
1350
|
+
font: 600 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1351
|
+
-webkit-tap-highlight-color: transparent;
|
|
1352
|
+
}
|
|
1353
|
+
.term-key:active { background: #30363d; }
|
|
1354
|
+
.term-mobile-input {
|
|
1355
|
+
flex: 1;
|
|
1356
|
+
min-width: 0;
|
|
1357
|
+
height: 38px;
|
|
1358
|
+
padding: 0 10px;
|
|
1359
|
+
border: 1px solid #30363d;
|
|
1360
|
+
border-radius: 7px;
|
|
1361
|
+
color: #f0f6fc;
|
|
1362
|
+
background: #0d1117;
|
|
1363
|
+
font: 16px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
1364
|
+
outline: none;
|
|
1365
|
+
}
|
|
1366
|
+
.term-mobile-input:focus { border-color: #58a6ff; }
|
|
1367
|
+
.term-mobile-send {
|
|
1368
|
+
height: 38px;
|
|
1369
|
+
padding: 0 12px;
|
|
1370
|
+
border: 0;
|
|
1371
|
+
border-radius: 7px;
|
|
1372
|
+
color: #fff;
|
|
1373
|
+
background: #1f6feb;
|
|
1374
|
+
font-size: 14px;
|
|
1375
|
+
font-weight: 600;
|
|
1376
|
+
}
|
|
1377
|
+
.term-mobile-send:active { background: #388bfd; }
|
|
1378
|
+
}
|
|
1300
1379
|
|
|
1301
1380
|
/* ── Meta Agent panel (DaisyUI 5: use --color-* full values, solid surfaces) ── */
|
|
1302
1381
|
.meta-agent-panel {
|
|
@@ -1343,6 +1422,21 @@ html.pwa-standalone .ma-panel {
|
|
|
1343
1422
|
background: color-mix(in oklch, var(--color-primary) 12%, var(--color-base-200));
|
|
1344
1423
|
}
|
|
1345
1424
|
.ma-hist-open { cursor: pointer; }
|
|
1425
|
+
.ma-note-item {
|
|
1426
|
+
width: 100%;
|
|
1427
|
+
border: 1px solid var(--color-base-300);
|
|
1428
|
+
border-radius: 10px;
|
|
1429
|
+
padding: 8px 10px;
|
|
1430
|
+
background: var(--color-base-200);
|
|
1431
|
+
transition: border-color .12s, background .12s;
|
|
1432
|
+
}
|
|
1433
|
+
.ma-note-item:hover {
|
|
1434
|
+
border-color: color-mix(in oklch, var(--color-primary) 40%, var(--color-base-300));
|
|
1435
|
+
}
|
|
1436
|
+
.ma-note-item.active {
|
|
1437
|
+
border-color: color-mix(in oklch, var(--color-primary) 55%, var(--color-base-300));
|
|
1438
|
+
background: color-mix(in oklch, var(--color-primary) 12%, var(--color-base-200));
|
|
1439
|
+
}
|
|
1346
1440
|
.line-clamp-2 {
|
|
1347
1441
|
display: -webkit-box;
|
|
1348
1442
|
-webkit-line-clamp: 2;
|
package/public/sw.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/* Agent UI service worker — cache shell assets only.
|
|
2
2
|
* Never intercepts WebSocket or mutates API semantics beyond offline fallback.
|
|
3
3
|
* Bump CACHE when shipping UI that must invalidate old shells. */
|
|
4
|
-
const CACHE = 'agent-ui-shell-
|
|
4
|
+
const CACHE = 'agent-ui-shell-v13';
|
|
5
5
|
const PRECACHE = [
|
|
6
6
|
'/',
|
|
7
7
|
'/index.html',
|
|
8
8
|
'/manifest.webmanifest',
|
|
9
|
-
'/style.css?v=ui-
|
|
9
|
+
'/style.css?v=ui-11',
|
|
10
10
|
'/style.css',
|
|
11
11
|
'/app.js',
|
|
12
12
|
'/icons/icon-192.png',
|