openclacky 0.9.32 → 0.9.34

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.
@@ -274,7 +274,40 @@ const Modal = (() => {
274
274
  });
275
275
  }
276
276
 
277
- return { confirm };
277
+ /** Show a text input prompt dialog. Returns a Promise<string|null>. */
278
+ function prompt(message, defaultValue = "") {
279
+ return new Promise(resolve => {
280
+ $("prompt-modal-message").textContent = message;
281
+ const input = $("prompt-modal-input");
282
+ input.value = defaultValue;
283
+ $("prompt-modal-overlay").style.display = "flex";
284
+
285
+ // Auto-focus and select all text
286
+ setTimeout(() => {
287
+ input.focus();
288
+ input.select();
289
+ }, 50);
290
+
291
+ const cleanup = (result) => {
292
+ $("prompt-modal-overlay").style.display = "none";
293
+ $("prompt-modal-ok").onclick = null;
294
+ $("prompt-modal-cancel").onclick = null;
295
+ input.onkeydown = null;
296
+ resolve(result);
297
+ };
298
+
299
+ $("prompt-modal-ok").onclick = () => cleanup(input.value.trim() || null);
300
+ $("prompt-modal-cancel").onclick = () => cleanup(null);
301
+
302
+ // Support Enter to confirm, Escape to cancel
303
+ input.onkeydown = (e) => {
304
+ if (e.key === "Enter") cleanup(input.value.trim() || null);
305
+ if (e.key === "Escape") cleanup(null);
306
+ };
307
+ });
308
+ }
309
+
310
+ return { confirm, prompt };
278
311
  })();
279
312
 
280
313
  // ── Confirmation modal ────────────────────────────────────────────────────
@@ -391,8 +424,13 @@ WS.onEvent(ev => {
391
424
  // Update chat title in case session was renamed
392
425
  $("chat-title").textContent = current?.name || "";
393
426
  }
394
- // When a session finishes, refresh tasks and skills
395
- if (patch.status === "idle") { Tasks.load(); Skills.load(); }
427
+ // When a session finishes, refresh tasks and skills, and clear any progress state
428
+ if (patch.status === "idle") {
429
+ Tasks.load();
430
+ Skills.load();
431
+ // Clear progress state for this session (even if not currently active)
432
+ Sessions.clearProgress(sid);
433
+ }
396
434
  break;
397
435
  }
398
436
 
@@ -825,10 +863,8 @@ function _mobileCloseSidebar() {
825
863
  }
826
864
 
827
865
  // ── New session split button [+ ▾] ────────────────────────────────────────
828
- // btn-new-session-inline: click create General session immediately
829
- // btn-new-session-arrow: click toggle dropdown
830
- // dropdown items: click → create session with chosen agent_profile
831
- // btn-new-project-entry: click → open inline new-project form
866
+ // Main button: quick create (like before)
867
+ // Arrow button: show dropdown with "Advanced Options..." to open modal
832
868
  if ($("btn-new-session-inline")) {
833
869
  $("btn-new-session-inline").addEventListener("click", () => Sessions.create("general"));
834
870
  }
@@ -839,21 +875,13 @@ if ($("btn-new-session-arrow")) {
839
875
  if (dd) dd.hidden = !dd.hidden;
840
876
  });
841
877
  }
842
- // Dropdown item clicks
843
- document.querySelectorAll("#new-session-dropdown .dropdown-item").forEach(item => {
844
- item.addEventListener("click", (e) => {
878
+ // Dropdown item: Advanced Options → open modal
879
+ document.addEventListener("click", (e) => {
880
+ if (e.target && e.target.id === "btn-new-session-modal") {
845
881
  e.stopPropagation();
846
882
  $("new-session-dropdown").hidden = true;
847
-
848
- // "New Project" entry — open inline form instead of creating immediately
849
- if (item.id === "btn-new-project-entry") {
850
- NewProjectForm.open();
851
- return;
852
- }
853
-
854
- const profile = item.dataset.profile || "general";
855
- Sessions.create(profile);
856
- });
883
+ Sessions.openNewSessionModal();
884
+ }
857
885
  });
858
886
  // Close dropdown when clicking elsewhere
859
887
  document.addEventListener("click", () => {
@@ -863,103 +891,28 @@ document.addEventListener("click", () => {
863
891
 
864
892
  $("btn-welcome-new").addEventListener("click", () => Sessions.create("general"));
865
893
 
866
- // ── New Project inline form ────────────────────────────────────────────────
867
- const NewProjectForm = {
868
- _defaultDir() {
869
- // Generate default dir like ~/clacky_projects/my-project
870
- return "~/clacky_projects/my-project";
871
- },
872
-
873
- open() {
874
- const panel = $("new-project-form");
875
- const input = $("new-project-dir");
876
- if (!panel) return;
877
- // Set default directory value
878
- if (input && !input.value) input.value = this._defaultDir();
879
- // Animate open
880
- panel.hidden = false;
881
- requestAnimationFrame(() => panel.classList.add("panel--open"));
882
- // Focus input and select the "my-project" part so user can rename easily
883
- if (input) {
884
- input.focus();
885
- const val = input.value;
886
- const lastSlash = val.lastIndexOf("/");
887
- if (lastSlash >= 0) {
888
- input.setSelectionRange(lastSlash + 1, val.length);
889
- } else {
890
- input.select();
891
- }
892
- }
893
- },
894
-
895
- close() {
896
- const panel = $("new-project-form");
897
- if (!panel) return;
898
- panel.classList.remove("panel--open");
899
- setTimeout(() => { panel.hidden = true; }, 200);
900
- },
901
-
902
- async submit() {
903
- const input = $("new-project-dir");
904
- const createBtn = $("btn-new-project-create");
905
- const dir = input ? input.value.trim() : "";
906
- if (!dir) { input && input.focus(); return; }
907
-
908
- // Derive session name from directory basename
909
- const basename = dir.replace(/\/$/, "").split("/").pop() || "Project";
910
- const name = basename;
911
-
912
- if (createBtn) createBtn.disabled = true;
913
-
914
- const res = await fetch("/api/sessions", {
915
- method: "POST",
916
- headers: { "Content-Type": "application/json" },
917
- body: JSON.stringify({
918
- name,
919
- agent_profile: "coding",
920
- source: "manual",
921
- working_dir: dir
922
- })
923
- });
924
- const data = await res.json();
925
-
926
- if (createBtn) createBtn.disabled = false;
927
-
928
- if (!res.ok) {
929
- const msg = data.error || "unknown error";
930
- const friendly = res.status === 409
931
- ? I18n.t("sessions.dirNotEmpty")
932
- : I18n.t("sessions.createError") + msg;
933
- alert(friendly);
934
- return;
935
- }
936
-
937
- const session = data.session;
938
- if (!session) return;
939
-
940
- // Reset form for next use
941
- if (input) input.value = "";
942
- this.close();
943
-
944
- Sessions.add(session);
945
- Sessions.renderList();
946
- Sessions.select(session.id);
947
- // After subscribe is confirmed, auto-send /new to trigger the new-project skill
948
- Sessions.setPendingMessage(session.id, "/new");
949
- }
950
- };
951
-
952
- if ($("btn-new-project-cancel")) {
953
- $("btn-new-project-cancel").addEventListener("click", () => NewProjectForm.close());
894
+ // ── New Session Modal event handlers ───────────────────────────────────────
895
+ if ($("new-session-modal-close")) {
896
+ $("new-session-modal-close").addEventListener("click", () => Sessions.closeNewSessionModal());
954
897
  }
955
- if ($("btn-new-project-create")) {
956
- $("btn-new-project-create").addEventListener("click", () => NewProjectForm.submit());
898
+ if ($("new-session-cancel")) {
899
+ $("new-session-cancel").addEventListener("click", () => Sessions.closeNewSessionModal());
957
900
  }
958
- // Enter key inside directory input
959
- if ($("new-project-dir")) {
960
- $("new-project-dir").addEventListener("keydown", (e) => {
961
- if (e.key === "Enter") { e.preventDefault(); NewProjectForm.submit(); }
962
- if (e.key === "Escape") { e.preventDefault(); NewProjectForm.close(); }
901
+ if ($("new-session-create")) {
902
+ $("new-session-create").addEventListener("click", () => Sessions.createFromModal());
903
+ }
904
+ // Close modal when clicking overlay
905
+ if ($("new-session-modal")) {
906
+ $("new-session-modal").addEventListener("click", (e) => {
907
+ if (e.target.id === "new-session-modal") {
908
+ Sessions.closeNewSessionModal();
909
+ }
910
+ });
911
+ }
912
+ // Browse button (placeholder - actual file browsing would need native integration)
913
+ if ($("new-session-browse-btn")) {
914
+ $("new-session-browse-btn").addEventListener("click", () => {
915
+ alert("Tip: Enter your desired path directly, e.g., ~/clacky_workspace/my-project");
963
916
  });
964
917
  }
965
918
 
@@ -1596,3 +1549,168 @@ window.bootAfterBrand = async function() {
1596
1549
  }
1597
1550
  });
1598
1551
  })();
1552
+
1553
+ // ── Session Info Bar Model Switcher ───────────────────────────────────────
1554
+ (function() {
1555
+ let _isOpen = false;
1556
+
1557
+ // Toggle model dropdown when clicking on model name
1558
+ document.addEventListener("click", async (e) => {
1559
+ const modelEl = e.target.closest("#sib-model");
1560
+ if (modelEl) {
1561
+ e.stopPropagation();
1562
+ const dropdown = $("sib-model-dropdown");
1563
+ if (!dropdown) return;
1564
+
1565
+ if (_isOpen) {
1566
+ dropdown.style.display = "none";
1567
+ _isOpen = false;
1568
+ } else {
1569
+ await _populateModelDropdown(modelEl.dataset.sessionId, modelEl.textContent.trim());
1570
+
1571
+ // Calculate position relative to the model element (fixed positioning)
1572
+ const rect = modelEl.getBoundingClientRect();
1573
+ dropdown.style.left = `${rect.left + rect.width / 2}px`;
1574
+ dropdown.style.top = `${rect.top - 6}px`; // 6px above the element
1575
+ dropdown.style.transform = "translate(-50%, -100%)"; // Center horizontally, move up by its own height
1576
+
1577
+ dropdown.style.display = "block";
1578
+ _isOpen = true;
1579
+ }
1580
+ return;
1581
+ }
1582
+
1583
+ // Close dropdown when clicking outside
1584
+ if (_isOpen && !e.target.closest(".sib-model-dropdown")) {
1585
+ const dropdown = $("sib-model-dropdown");
1586
+ if (dropdown) dropdown.style.display = "none";
1587
+ _isOpen = false;
1588
+ }
1589
+ });
1590
+
1591
+ // Populate dropdown with available models
1592
+ async function _populateModelDropdown(sessionId, currentModel) {
1593
+ const dropdown = $("sib-model-dropdown");
1594
+ if (!dropdown) return;
1595
+
1596
+ try {
1597
+ console.log("[Model Switcher] Fetching /api/config...");
1598
+ const res = await fetch("/api/config");
1599
+ const data = await res.json();
1600
+ console.log("[Model Switcher] Received data:", data);
1601
+ const models = data.models || [];
1602
+ console.log("[Model Switcher] Models count:", models.length);
1603
+
1604
+ if (models.length === 0) {
1605
+ dropdown.innerHTML = '<div style="padding:12px;text-align:center;color:var(--color-text-secondary);font-size:11px;">No models configured</div>';
1606
+ return;
1607
+ }
1608
+
1609
+ dropdown.innerHTML = "";
1610
+
1611
+ models.forEach(m => {
1612
+ console.log("[Model Switcher] Adding model:", m.model, "current:", currentModel);
1613
+ const opt = document.createElement("div");
1614
+ opt.className = "sib-model-option";
1615
+ if (m.model === currentModel) opt.classList.add("current");
1616
+
1617
+ const modelName = document.createElement("span");
1618
+ modelName.textContent = m.model;
1619
+ opt.appendChild(modelName);
1620
+
1621
+ if (m.type === "default" || m.type === "lite") {
1622
+ const badge = document.createElement("span");
1623
+ badge.className = `model-badge ${m.type}`;
1624
+ badge.textContent = m.type;
1625
+ opt.appendChild(badge);
1626
+ }
1627
+
1628
+ opt.addEventListener("click", () => _switchModel(sessionId, m.model));
1629
+ dropdown.appendChild(opt);
1630
+ });
1631
+ console.log("[Model Switcher] Dropdown populated, children count:", dropdown.children.length);
1632
+ } catch (e) {
1633
+ console.error("Failed to load models:", e);
1634
+ dropdown.innerHTML = '<div style="padding:12px;text-align:center;color:var(--color-error);font-size:11px;">Error loading models</div>';
1635
+ }
1636
+ }
1637
+
1638
+ // Switch session model via API
1639
+ async function _switchModel(sessionId, newModel) {
1640
+ const dropdown = $("sib-model-dropdown");
1641
+ if (dropdown) {
1642
+ dropdown.style.display = "none";
1643
+ _isOpen = false;
1644
+ }
1645
+
1646
+ try {
1647
+ const res = await fetch(`/api/sessions/${sessionId}/model`, {
1648
+ method: "PATCH",
1649
+ headers: { "Content-Type": "application/json" },
1650
+ body: JSON.stringify({ model: newModel })
1651
+ });
1652
+
1653
+ const data = await res.json();
1654
+
1655
+ if (!res.ok) {
1656
+ throw new Error(data.error || "Unknown error");
1657
+ }
1658
+
1659
+ // Update UI optimistically (will be confirmed by session_update broadcast)
1660
+ const sibModel = $("sib-model");
1661
+ if (sibModel) sibModel.textContent = newModel;
1662
+
1663
+ console.log(`Switched session ${sessionId} to model ${newModel}`);
1664
+ } catch (e) {
1665
+ console.error("Failed to switch model:", e);
1666
+ alert("Failed to switch model: " + e.message);
1667
+ }
1668
+ }
1669
+ })();
1670
+
1671
+ // ── Session Info Bar Working Directory Switcher ───────────────────────────
1672
+ (function() {
1673
+ // Handle click on working directory
1674
+ document.addEventListener("click", async (e) => {
1675
+ const dirEl = e.target.closest("#sib-dir");
1676
+ if (dirEl) {
1677
+ e.stopPropagation();
1678
+ const sessionId = dirEl.dataset.sessionId;
1679
+ const currentDir = dirEl.title.replace(" (click to change)", "");
1680
+
1681
+ const newDir = await Modal.prompt("Change working directory:", currentDir);
1682
+ if (newDir && newDir !== currentDir) {
1683
+ _changeWorkingDirectory(sessionId, newDir);
1684
+ }
1685
+ }
1686
+ });
1687
+
1688
+ // Change working directory via backend API
1689
+ async function _changeWorkingDirectory(sessionId, newDir) {
1690
+ try {
1691
+ const res = await fetch(`/api/sessions/${sessionId}/working_dir`, {
1692
+ method: "PATCH",
1693
+ headers: { "Content-Type": "application/json" },
1694
+ body: JSON.stringify({ working_dir: newDir })
1695
+ });
1696
+
1697
+ const data = await res.json();
1698
+
1699
+ if (!res.ok) {
1700
+ throw new Error(data.error || "Unknown error");
1701
+ }
1702
+
1703
+ // Update UI optimistically (will be confirmed by session_update broadcast)
1704
+ const sibDir = $("sib-dir");
1705
+ if (sibDir) {
1706
+ sibDir.textContent = newDir;
1707
+ sibDir.title = newDir + " (click to change)";
1708
+ }
1709
+
1710
+ console.log(`Changed session ${sessionId} directory to ${newDir}`);
1711
+ } catch (e) {
1712
+ console.error("Failed to change directory:", e);
1713
+ alert("Failed to change directory: " + e.message);
1714
+ }
1715
+ }
1716
+ })();
@@ -43,6 +43,7 @@ const I18n = (() => {
43
43
  "chat.interrupted": "Interrupted.",
44
44
  "chat.feedback_hint": "Or type your own answer below ↓",
45
45
  "chat.newMessageHint": "New messages ↓",
46
+ "chat.retry": "Retry",
46
47
 
47
48
  // ── Session list ──
48
49
  "sessions.empty": "No sessions yet",
@@ -58,6 +59,11 @@ const I18n = (() => {
58
59
  "sessions.badge.coding": "Coding",
59
60
  "sessions.badge.setup": "Setup",
60
61
  "sessions.newSession": "+ New Session",
62
+ "sessions.actions.pin": "Pin",
63
+ "sessions.actions.unpin": "Unpin",
64
+ "sessions.actions.rename": "Rename",
65
+ "sessions.actions.delete": "Delete",
66
+ "sessions.newSessionAdvanced": "More Options",
61
67
  "sessions.loadMore": "Load more sessions",
62
68
  "sessions.loadingMore": "Loading…",
63
69
  "sessions.search.placeholder": "Search sessions…",
@@ -71,6 +77,21 @@ const I18n = (() => {
71
77
  // ── Modal ──
72
78
  "modal.yes": "Yes",
73
79
  "modal.no": "No",
80
+ "modal.ok": "OK",
81
+ "modal.cancel": "Cancel",
82
+
83
+ // ── New Session Modal ──
84
+ "sessions.modal.title": "Create New Session",
85
+ "sessions.modal.agent": "Agent",
86
+ "sessions.modal.agent.general": "General",
87
+ "sessions.modal.agent.coding": "Coding",
88
+ "sessions.modal.name": "Session Name",
89
+ "sessions.modal.name.placeholder": "Leave empty to auto-generate",
90
+ "sessions.modal.model": "Model",
91
+ "sessions.modal.directory": "Working Directory",
92
+ "sessions.modal.directory.placeholder": "~/clacky_workspace",
93
+ "sessions.modal.initProject": "Initialize full-stack project (/new)",
94
+ "sessions.modal.create": "Create Session",
74
95
 
75
96
  // ── Offline banner ──
76
97
  "offline.banner": "Server disconnected — reconnecting…",
@@ -381,6 +402,7 @@ const I18n = (() => {
381
402
  "chat.interrupted": "已中断。",
382
403
  "chat.feedback_hint": "或在下方输入框自由作答 ↓",
383
404
  "chat.newMessageHint": "有新消息 ↓",
405
+ "chat.retry": "重试",
384
406
 
385
407
  // ── Session list ──
386
408
  "sessions.empty": "暂无对话",
@@ -396,7 +418,12 @@ const I18n = (() => {
396
418
  "sessions.badge.coding": "Coding",
397
419
  "sessions.badge.setup": "配置",
398
420
  "sessions.newSession": "+ 新会话",
421
+ "sessions.newSessionAdvanced": "更多选项",
399
422
  "sessions.loadMore": "加载更多会话",
423
+ "sessions.actions.pin": "置顶",
424
+ "sessions.actions.unpin": "取消置顶",
425
+ "sessions.actions.rename": "重命名",
426
+ "sessions.actions.delete": "删除",
400
427
  "sessions.loadingMore": "加载中…",
401
428
  "sessions.search.placeholder": "搜索会话…",
402
429
  "sessions.search.typeAll": "全部类型",
@@ -409,6 +436,21 @@ const I18n = (() => {
409
436
  // ── Modal ──
410
437
  "modal.yes": "确认",
411
438
  "modal.no": "取消",
439
+ "modal.ok": "确定",
440
+ "modal.cancel": "取消",
441
+
442
+ // ── New Session Modal ──
443
+ "sessions.modal.title": "创建新会话",
444
+ "sessions.modal.agent": "助手类型",
445
+ "sessions.modal.agent.general": "通用",
446
+ "sessions.modal.agent.coding": "编程",
447
+ "sessions.modal.name": "会话名称",
448
+ "sessions.modal.name.placeholder": "留空自动生成",
449
+ "sessions.modal.model": "模型",
450
+ "sessions.modal.directory": "工作目录",
451
+ "sessions.modal.directory.placeholder": "~/clacky_workspace",
452
+ "sessions.modal.initProject": "初始化全栈项目 (/new)",
453
+ "sessions.modal.create": "创建会话",
412
454
 
413
455
  // ── Offline banner ──
414
456
  "offline.banner": "服务已断开,正在重连…",
@@ -68,13 +68,15 @@
68
68
  </button>
69
69
  <div class="btn-split-wrap">
70
70
  <button id="btn-new-session-inline" class="btn-split-main" title="New Session" data-i18n="sessions.newSession">+ New Session</button>
71
- <button id="btn-new-session-arrow" class="btn-split-arrow" title="Choose agent">▾</button>
71
+ <button id="btn-new-session-arrow" class="btn-split-arrow" title="Options">▾</button>
72
72
  <!-- Dropdown -->
73
73
  <div id="new-session-dropdown" class="new-session-dropdown" hidden>
74
- <div class="dropdown-item" data-profile="general">✦ General</div>
75
- <div class="dropdown-item" data-profile="coding">👨‍💻 Coding</div>
76
- <div class="dropdown-divider"></div>
77
- <div class="dropdown-item" id="btn-new-project-entry">🚀 New Project</div>
74
+ <div class="dropdown-item" id="btn-new-session-modal" data-i18n="sessions.newSessionAdvanced">
75
+ <svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor" style="vertical-align: -1px; margin-right: 5px; opacity: 0.6;">
76
+ <path d="M8 0a.5.5 0 01.5.5v2a.5.5 0 01-1 0v-2A.5.5 0 018 0zm0 13a.5.5 0 01.5.5v2a.5.5 0 01-1 0v-2A.5.5 0 018 13zm8-5a.5.5 0 01-.5.5h-2a.5.5 0 010-1h2a.5.5 0 01.5.5zM3 8a.5.5 0 01-.5.5h-2a.5.5 0 010-1h2A.5.5 0 013 8zm10.657-5.657a.5.5 0 010 .707l-1.414 1.415a.5.5 0 11-.707-.708l1.414-1.414a.5.5 0 01.707 0zm-9.193 9.193a.5.5 0 010 .707L3.05 13.657a.5.5 0 01-.707-.707l1.414-1.414a.5.5 0 01.707 0zm9.193 2.121a.5.5 0 01-.707 0l-1.414-1.414a.5.5 0 01.707-.707l1.414 1.414a.5.5 0 010 .707zM4.464 4.465a.5.5 0 01-.707 0L2.343 3.05a.5.5 0 11.707-.707l1.414 1.414a.5.5 0 010 .708z"/>
77
+ </svg>
78
+ <span data-i18n="sessions.newSessionAdvanced">More Options</span>
79
+ </div>
78
80
  </div>
79
81
  </div>
80
82
  </div>
@@ -109,23 +111,6 @@
109
111
  </div>
110
112
  </div>
111
113
 
112
- <!-- New Project inline form (hidden by default, toggled by "New Project" dropdown item) -->
113
- <div id="new-project-form" class="new-project-panel" hidden>
114
- <div class="new-project-card">
115
- <div class="new-project-title">🚀 New Project</div>
116
- <div class="new-project-field">
117
- <label class="new-project-label" for="new-project-dir">Working Directory</label>
118
- <input id="new-project-dir" type="text" class="new-project-input"
119
- placeholder="~/clacky_projects/my-project"
120
- autocomplete="off" spellcheck="false" />
121
- </div>
122
- <div class="new-project-actions">
123
- <button id="btn-new-project-cancel" class="btn-new-project-cancel">Cancel</button>
124
- <button id="btn-new-project-create" class="btn-new-project-create">Create →</button>
125
- </div>
126
- </div>
127
- </div>
128
-
129
114
  <!-- Unified session list (all sources + profiles, newest first) -->
130
115
  <div id="session-list"></div>
131
116
  <!-- Load more button rendered dynamically by Sessions.renderList() -->
@@ -271,20 +256,26 @@
271
256
  </div>
272
257
  <!-- Session info bar — mirrors CLI bottom status bar -->
273
258
  <div id="session-info-bar" style="display:none">
274
- <!-- Always visible: status | id | model | cost -->
259
+ <!-- Order: status | id | dir | model | mode | tasks | cost -->
260
+ <!-- Pattern: element + separator-after-element (except last) -->
275
261
  <span id="sib-status"></span>
276
- <span class="sib-sep sib-sep-before-id">│</span>
262
+ <span class="sib-sep sib-sep-after-status">│</span>
277
263
  <span id="sib-id"></span>
278
- <span id="sib-model-wrap"><span class="sib-sep">│</span><span id="sib-model"></span></span>
279
- <span class="sib-sep">│</span>
280
- <span id="sib-cost"></span>
281
- <!-- Detail fields: hidden by default, revealed on hover -->
264
+ <span class="sib-sep sib-sep-after-id">│</span>
265
+ <span id="sib-dir" title="Click to change directory"></span>
266
+ <span class="sib-sep sib-sep-after-dir">│</span>
267
+ <span id="sib-model-wrap">
268
+ <span id="sib-model" class="sib-model-clickable" title="Click to switch model"></span>
269
+ <div id="sib-model-dropdown" class="sib-model-dropdown" style="display:none"></div>
270
+ </span>
271
+ <span class="sib-sep sib-sep-after-model">│</span>
272
+ <!-- Detail fields: mode, tasks, cost -->
282
273
  <span class="sib-detail">
283
- <span class="sib-sep">│</span>
284
- <span id="sib-dir"></span>
285
- <span id="sib-mode-wrap"><span class="sib-sep">│</span><span id="sib-mode"></span></span>
286
- <span class="sib-sep sib-sep-before-tasks">│</span>
274
+ <span id="sib-mode"></span>
275
+ <span class="sib-sep sib-sep-after-mode">│</span>
287
276
  <span id="sib-tasks"></span>
277
+ <span class="sib-sep sib-sep-after-tasks">│</span>
278
+ <span id="sib-cost"></span>
288
279
  </span>
289
280
  </div>
290
281
 
@@ -718,6 +709,57 @@
718
709
  </div>
719
710
  </div>
720
711
 
712
+ <!-- ── NEW SESSION MODAL ─────────────────────────────────────────────────── -->
713
+ <div id="new-session-modal" class="modal-overlay" style="display:none">
714
+ <div class="modal-box new-session-modal">
715
+ <div class="modal-header">
716
+ <h3 class="modal-title" data-i18n="sessions.modal.title">Create New Session</h3>
717
+ <button id="new-session-modal-close" class="modal-close-btn" title="Close">×</button>
718
+ </div>
719
+ <div class="modal-body">
720
+ <div class="modal-field">
721
+ <label class="modal-label" data-i18n="sessions.modal.agent">Agent</label>
722
+ <select id="new-session-agent" class="modal-select">
723
+ <option value="general" data-i18n="sessions.modal.agent.general">General</option>
724
+ <option value="coding" data-i18n="sessions.modal.agent.coding">Coding</option>
725
+ </select>
726
+ </div>
727
+
728
+ <div class="modal-field">
729
+ <label class="modal-label" data-i18n="sessions.modal.name">Session Name</label>
730
+ <input id="new-session-name" type="text" class="modal-input"
731
+ data-i18n-placeholder="sessions.modal.name.placeholder"
732
+ placeholder="Leave empty to auto-generate">
733
+ </div>
734
+
735
+ <div class="modal-field">
736
+ <label class="modal-label" data-i18n="sessions.modal.model">Model</label>
737
+ <select id="new-session-model" class="modal-select">
738
+ <!-- Populated dynamically from configured models -->
739
+ </select>
740
+ </div>
741
+
742
+ <div class="modal-field">
743
+ <label class="modal-label" data-i18n="sessions.modal.directory">Working Directory</label>
744
+ <input id="new-session-directory" type="text" class="modal-input"
745
+ data-i18n-placeholder="sessions.modal.directory.placeholder"
746
+ placeholder="~/workspace/my-project">
747
+ </div>
748
+
749
+ <div id="new-session-init-project-field" class="modal-field-checkbox" style="display:none">
750
+ <label class="modal-checkbox-label">
751
+ <input id="new-session-init-project" type="checkbox" class="modal-checkbox">
752
+ <span data-i18n="sessions.modal.initProject">Initialize full-stack project (/new)</span>
753
+ </label>
754
+ </div>
755
+ </div>
756
+ <div class="modal-footer">
757
+ <button id="new-session-create" class="btn-primary" data-i18n="sessions.modal.create">Create Session</button>
758
+ <button id="new-session-cancel" class="btn-secondary" data-i18n="modal.cancel">Cancel</button>
759
+ </div>
760
+ </div>
761
+ </div>
762
+
721
763
  <!-- ── CONFIRMATION MODAL ────────────────────────────────────────────────── -->
722
764
  <div id="modal-overlay" class="modal-overlay" style="display:none">
723
765
  <div class="modal-box sm">
@@ -729,6 +771,18 @@
729
771
  </div>
730
772
  </div>
731
773
 
774
+ <!-- Prompt modal for text input -->
775
+ <div id="prompt-modal-overlay" class="modal-overlay" style="display:none">
776
+ <div class="modal-box sm">
777
+ <div id="prompt-modal-message" style="font-size:14px;line-height:1.6;margin-bottom:12px"></div>
778
+ <input type="text" id="prompt-modal-input" class="prompt-modal-input" autocomplete="off" spellcheck="false">
779
+ <div class="modal-actions">
780
+ <button id="prompt-modal-ok" class="btn-primary" data-i18n="modal.ok">OK</button>
781
+ <button id="prompt-modal-cancel" class="btn-secondary" data-i18n="modal.cancel">Cancel</button>
782
+ </div>
783
+ </div>
784
+ </div>
785
+
732
786
 
733
787
 
734
788
  <script src="/marked.min.js"></script>