claude-simple 0.2.7 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/README.md +0 -0
  2. package/dist/app.0.2.9.min.js +1080 -0
  3. package/dist/index.html +2 -2
  4. package/dist/{style.0.2.7.min.css → style.0.2.9.min.css} +1 -1
  5. package/gateway.js +0 -0
  6. package/package.json +1 -1
  7. package/public/addon-fit.min.js +0 -0
  8. package/public/agent-panel.js +0 -0
  9. package/public/api.js +2 -0
  10. package/public/app.js +0 -0
  11. package/public/base.css +0 -0
  12. package/public/cache.js +0 -0
  13. package/public/chat.js +42 -8
  14. package/public/files.js +0 -0
  15. package/public/git.js +0 -0
  16. package/public/icons/icon-180.png +0 -0
  17. package/public/icons/icon-192.png +0 -0
  18. package/public/icons/icon-512.png +0 -0
  19. package/public/icons/icon-maskable-512.png +0 -0
  20. package/public/index.html +0 -0
  21. package/public/lib.js +0 -0
  22. package/public/manifest.webmanifest +0 -0
  23. package/public/mobile.js +0 -0
  24. package/public/notifications.js +215 -0
  25. package/public/offline.html +0 -0
  26. package/public/pwa.js +0 -0
  27. package/public/router.js +0 -0
  28. package/public/settings.js +0 -0
  29. package/public/shell/boot.js +0 -0
  30. package/public/shell/dashboard.js +46 -26
  31. package/public/shell/hub.js +0 -0
  32. package/public/shell/layout.js +2 -3
  33. package/public/shell/notes.js +0 -0
  34. package/public/shell/session-context.js +0 -0
  35. package/public/shell/session-list.js +22 -18
  36. package/public/shell/session-nav.js +18 -1
  37. package/public/shell/templates.js +18 -0
  38. package/public/shell/util.js +0 -0
  39. package/public/shell.js +24 -6
  40. package/public/state.js +64 -7
  41. package/public/style.css +0 -0
  42. package/public/sw.js +1 -1
  43. package/public/terminal.js +0 -0
  44. package/public/wake-lock.js +0 -0
  45. package/public/xterm.min.css +0 -0
  46. package/public/xterm.min.js +0 -0
  47. package/src/agents/claude.js +0 -0
  48. package/src/agents/codex.js +0 -0
  49. package/src/agents/grok.js +415 -271
  50. package/src/agents/meta-agent.js +0 -0
  51. package/src/agents/models.js +2 -1
  52. package/src/agents/sessions.js +0 -0
  53. package/src/agents/stream-batch.js +0 -0
  54. package/src/app.js +12 -2
  55. package/src/auto-update.js +0 -0
  56. package/src/runtime/approvals.js +0 -0
  57. package/src/runtime/outbox.js +0 -0
  58. package/src/runtime/protocol.js +0 -0
  59. package/dist/app.0.2.7.min.js +0 -1039
@@ -69,6 +69,13 @@ export const AppShell = (opts = {}) => {
69
69
  <span class="text-[10px] opacity-50">▾</span>
70
70
  </button>
71
71
  </div>
72
+ <div id="notifications-wrap" class="relative flex-shrink-0">
73
+ <button type="button" id="btn-notifications" class="btn btn-ghost btn-xs border border-base-300 gap-1 min-h-8 px-2"
74
+ title="通知" aria-haspopup="dialog" aria-expanded="false">
75
+ <span>🔔</span>
76
+ <span id="notifications-badge" class="hidden inline-flex min-w-5 h-5 px-1 items-center justify-center rounded-full bg-error text-error-content text-[10px] font-bold">0</span>
77
+ </button>
78
+ </div>
72
79
  <span id="topbar-project" class="text-sm text-base-content/50 flex-1 truncate min-w-0">Select a session</span>
73
80
  <span id="conn-dot" class="w-2 h-2 rounded-full bg-base-content/30 flex-shrink-0" title="连接状态"></span>
74
81
  <div class="flex gap-2 topbar-actions">
@@ -93,6 +100,17 @@ export const AppShell = (opts = {}) => {
93
100
  <div id="recent-menu-list" class="flex flex-col gap-0.5"></div>
94
101
  <p id="recent-menu-empty" class="text-xs text-base-content/40 px-1 py-2 hidden">暂无最近会话</p>
95
102
  </div>
103
+ <div id="notifications-backdrop" class="hidden fixed inset-0 z-[55] bg-black/40" aria-hidden="true"></div>
104
+ <div id="notifications-menu" class="hidden fixed z-[60] w-[min(26rem,calc(100vw-1rem))] max-h-[min(30rem,75vh)] overflow-y-auto
105
+ rounded-lg border border-base-300 bg-base-100 shadow-xl p-2"
106
+ role="dialog" aria-label="通知中心">
107
+ <div class="flex items-center justify-between px-1 pb-1.5 mb-1 border-b border-base-300 sticky top-0 bg-base-100 z-[1]">
108
+ <span class="text-[10px] uppercase tracking-wide text-base-content/45 font-semibold">通知</span>
109
+ <button type="button" id="btn-notifications-read-all" class="btn btn-ghost btn-xs px-1 text-[10px]">全部已读</button>
110
+ </div>
111
+ <div id="notifications-menu-list" class="flex flex-col gap-1"></div>
112
+ <p id="notifications-menu-empty" class="text-xs text-base-content/40 px-1 py-2 hidden">暂无通知</p>
113
+ </div>
96
114
  <!-- Hub: only when no remembered machine (or last one offline) -->
97
115
  <div id="machine-picker" class="hidden fixed inset-0 z-[60] bg-base-100 flex items-center justify-center p-4">
98
116
  <div class="w-full max-w-md">
File without changes
package/public/shell.js CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  sessionsData, workspacesData, sessionFilter, sessionSearch,
7
7
  expandedFolders, collapsedFolders, filesPath, viewingFile, filteredSessions, projectGroups,
8
8
  currentProject, currentTab, filesRoot, gitRoot,
9
- currentModel, currentEffort, currentPermission, currentAgent, setAgent,
9
+ currentModel, currentEffort, currentPermission, currentAgent, setAgent, setCurrentModel,
10
10
  sidebarView, agentFilter, timeRange, activityHits, activityLoading,
11
11
  chatDensity, setChatDensity,
12
12
  favoritesOnly, showHiddenOnly, hubMode, hubMachineReady, selectedMachineId, setSelectedMachine,
@@ -23,6 +23,9 @@ import {
23
23
  import { syncHash } from './router.js';
24
24
  import { initSettings, openSettings } from './settings.js';
25
25
  import { openMetaAgent, toggleMetaAgent } from './agent-panel.js';
26
+ import {
27
+ initNotifications, refreshNotifications, toggleNotificationsMenu, closeNotificationsMenu,
28
+ } from './notifications.js';
26
29
 
27
30
  import { AuthScreen, AppShell } from './shell/templates.js';
28
31
  import {
@@ -306,6 +309,7 @@ export function initShell(opts = {}) {
306
309
 
307
310
  initSidebarResize();
308
311
  initSidebarToggle();
312
+ initNotifications({ openSession: resumeSession });
309
313
 
310
314
  let sessionsChangedTimer = null;
311
315
  document.addEventListener('sessions-changed', () => {
@@ -519,8 +523,7 @@ export function initShell(opts = {}) {
519
523
  if (prev !== next) {
520
524
  ctx.sessionId = null;
521
525
  setAgent(next);
522
- currentModel.value = getDefaultModel(next) || currentModel.peek();
523
- localStorage.setItem('model', currentModel.peek());
526
+ setCurrentModel(getDefaultModel(next) || currentModel.peek());
524
527
  $('sel-permission').value = currentPermission.peek();
525
528
  refreshModelSelect();
526
529
  appendSystemMsg(`Agent → ${AGENT_LABELS[next] || next} · model ${currentModel.peek()} · new session`);
@@ -554,8 +557,7 @@ export function initShell(opts = {}) {
554
557
  }
555
558
  });
556
559
  delegate.on('change', '#sel-model', (_, el) => {
557
- currentModel.value = el.value;
558
- localStorage.setItem('model', el.value);
560
+ setCurrentModel(el.value);
559
561
  refreshEffortSelect();
560
562
  });
561
563
  delegate.on('click', '#btn-model-add', () => promptCustomModel({ editCurrent: false }));
@@ -590,6 +592,7 @@ export function initShell(opts = {}) {
590
592
  return;
591
593
  }
592
594
  await enterMachine(id, { forceReload: true });
595
+ refreshNotifications();
593
596
  });
594
597
  const refreshMachinesUi = async () => {
595
598
  const st = $('machine-picker-status');
@@ -611,6 +614,7 @@ export function initShell(opts = {}) {
611
614
  delegate.on('click', '#btn-machine-menu', async e => {
612
615
  e.stopPropagation();
613
616
  closeRecentMenu();
617
+ closeNotificationsMenu();
614
618
  const menu = $('machine-menu');
615
619
  if (!menu) return;
616
620
  if (menu.classList.contains('hidden')) {
@@ -625,7 +629,8 @@ export function initShell(opts = {}) {
625
629
  e.preventDefault();
626
630
  e.stopPropagation();
627
631
  closeMachineMenu();
628
- toggleRecentMenu(getDashboardSessions());
632
+ closeNotificationsMenu();
633
+ toggleRecentMenu();
629
634
  const open = !$('recent-menu')?.classList.contains('hidden');
630
635
  const btn = $('btn-recent-sessions');
631
636
  if (btn) btn.setAttribute('aria-expanded', open ? 'true' : 'false');
@@ -649,6 +654,13 @@ export function initShell(opts = {}) {
649
654
  btn?.setAttribute('aria-expanded', 'false');
650
655
  }
651
656
  });
657
+ delegate.on('click', '#btn-notifications', e => {
658
+ e.preventDefault();
659
+ e.stopPropagation();
660
+ closeMachineMenu();
661
+ closeRecentMenu();
662
+ toggleNotificationsMenu();
663
+ });
652
664
  // Keep panel glued under topbar on rotate / URL bar show-hide
653
665
  window.addEventListener('resize', () => {
654
666
  if ($('recent-menu') && !$('recent-menu').classList.contains('hidden')) {
@@ -761,6 +773,12 @@ export function initShell(opts = {}) {
761
773
  });
762
774
 
763
775
  refreshModelSelect();
776
+ effect(() => {
777
+ void hubMode.value;
778
+ void selectedMachineId.value;
779
+ if (!ctx.token) return;
780
+ refreshNotifications();
781
+ });
764
782
  $('sel-permission').value = currentPermission.peek();
765
783
  applyChatDensity();
766
784
  const dens = $('sel-density');
package/public/state.js CHANGED
@@ -15,8 +15,48 @@ export const currentTab = signal('chat');
15
15
  export const isProcessing = signal(false);
16
16
  /** Chat socket: idle | connecting | open | reconnecting | offline */
17
17
  export const wsStatus = signal('idle');
18
- export const currentAgent = signal(localStorage.getItem('agent') || 'claude'); // 'claude'|'codex'|'grok'
19
- export const currentModel = signal(localStorage.getItem('model') || 'claude-sonnet-4-5');
18
+ const MODEL_BY_SESSION_KEY = 'modelBySession';
19
+ const initialAgent = localStorage.getItem('agent') || 'claude';
20
+
21
+ function readModelBySession() {
22
+ try {
23
+ const value = JSON.parse(localStorage.getItem(MODEL_BY_SESSION_KEY) || '{}');
24
+ return value && typeof value === 'object' ? value : {};
25
+ } catch {
26
+ return {};
27
+ }
28
+ }
29
+
30
+ function modelSessionKey(agent, sessionId = ctx.sessionId, machineId = ctx.machineId) {
31
+ if (!sessionId) return '';
32
+ const a = (agent || 'claude').toLowerCase();
33
+ return `${machineId || ''}:${a}:${sessionId}`;
34
+ }
35
+
36
+ /** Restore the model explicitly selected for the active session. */
37
+ export function getSavedModel(agent, sessionId = ctx.sessionId, machineId = ctx.machineId) {
38
+ const key = modelSessionKey(agent, sessionId, machineId);
39
+ if (!key) return null;
40
+ const saved = readModelBySession()[key];
41
+ if (typeof saved === 'string' && saved.trim()) return saved;
42
+ return null;
43
+ }
44
+
45
+ /** Update the picker and persist its value only when a session exists. */
46
+ export function setCurrentModel(model, agent = currentAgent.peek(), sessionId = ctx.sessionId, machineId = ctx.machineId) {
47
+ const value = String(model || '').trim();
48
+ if (!value) return;
49
+ const key = modelSessionKey(agent, sessionId, machineId);
50
+ if (key) {
51
+ const saved = readModelBySession();
52
+ saved[key] = value;
53
+ localStorage.setItem(MODEL_BY_SESSION_KEY, JSON.stringify(saved));
54
+ }
55
+ currentModel.value = value;
56
+ }
57
+
58
+ export const currentAgent = signal(initialAgent); // 'claude'|'codex'|'grok'
59
+ export const currentModel = signal('claude-sonnet-4-5');
20
60
  export const currentEffort = signal('');
21
61
  // All local agents are trusted by default; users can still choose another mode
22
62
  // from the expanded composer options before starting a turn.
@@ -245,6 +285,18 @@ export function getEffortsForModel(agent, modelId) {
245
285
  return ['low', 'medium', 'high', 'xhigh', 'max'];
246
286
  }
247
287
 
288
+ /** Default reasoning effort for agent/model (Grok → low). */
289
+ export function getDefaultEffort(agent, modelId) {
290
+ const a = (agent || 'claude').toLowerCase();
291
+ const meta = agentsMeta.peek()?.defaults?.[a];
292
+ if (meta?.effortDefault) return String(meta.effortDefault);
293
+ const models = getModelsForAgent(a);
294
+ const m = models.find(x => x.value === modelId);
295
+ if (m?.defaultEffort) return String(m.defaultEffort);
296
+ if (a === 'grok') return 'low';
297
+ return '';
298
+ }
299
+
248
300
  export function setAgent(agent) {
249
301
  const a = (agent || 'claude').toLowerCase();
250
302
  currentAgent.value = a;
@@ -252,12 +304,17 @@ export function setAgent(agent) {
252
304
  currentPermission.value = 'bypassPermissions';
253
305
  const permissionSelect = typeof document === 'undefined' ? null : document.getElementById('sel-permission');
254
306
  if (permissionSelect) permissionSelect.value = currentPermission.peek();
255
- const models = getModelsForAgent(a);
256
- const def = getDefaultModel(a);
257
- if (!models.some(m => m.value === currentModel.peek())) {
258
- currentModel.value = def;
259
- localStorage.setItem('model', def);
307
+ // Model selection belongs to a concrete session, not to the whole agent.
308
+ // New sessions start from the agent default until they receive a session id.
309
+ const saved = getSavedModel(a);
310
+ if (saved) {
311
+ currentModel.value = saved;
312
+ } else {
313
+ setCurrentModel(getDefaultModel(a), a);
260
314
  }
315
+ const defEffort = getDefaultEffort(a, currentModel.peek());
316
+ if (defEffort) currentEffort.value = defEffort;
317
+ else if (a !== 'grok') currentEffort.value = '';
261
318
  }
262
319
 
263
320
  function matchesAgentFilter(s, filter) {
package/public/style.css CHANGED
File without changes
package/public/sw.js CHANGED
@@ -1,7 +1,7 @@
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-v13';
4
+ const CACHE = 'agent-ui-shell-v15';
5
5
  const PRECACHE = [
6
6
  '/',
7
7
  '/index.html',
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes