jinzd-ai-cli 0.4.53 → 0.4.55
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/{chunk-W6AK76UM.js → chunk-DJ342VFS.js} +1 -1
- package/dist/{chunk-6I5FUNPR.js → chunk-JL5NK6AR.js} +216 -67
- package/dist/{chunk-YIMTDKUW.js → chunk-W7QVBFIJ.js} +1 -1
- package/dist/{chunk-IXDGWT2Z.js → chunk-YQEIQJ6K.js} +1 -1
- package/dist/{hub-4DNFD6JK.js → hub-AUWP4SWJ.js} +1 -1
- package/dist/index.js +103 -35
- package/dist/{run-tests-NJQK4B43.js → run-tests-I6UDHVIS.js} +1 -1
- package/dist/{run-tests-3NNL7Z2E.js → run-tests-X4PCLXA2.js} +1 -1
- package/dist/{server-PFHWO3HL.js → server-YPAZWGUE.js} +74 -28
- package/dist/{task-orchestrator-C42TNHE6.js → task-orchestrator-MWO6A4KQ.js} +2 -2
- package/dist/web/client/app.js +162 -44
- package/package.json +1 -1
package/dist/web/client/app.js
CHANGED
|
@@ -182,7 +182,7 @@ function handleServerMessage(msg) {
|
|
|
182
182
|
case 'todo_update': handleTodoUpdate(msg.todos); break;
|
|
183
183
|
case 'status': handleStatus(msg); break;
|
|
184
184
|
case 'session_list': renderSessionList(msg.sessions); break;
|
|
185
|
-
case 'session_messages':renderSessionMessages(msg
|
|
185
|
+
case 'session_messages':renderSessionMessages(msg); break;
|
|
186
186
|
case 'tools_list': renderToolsList(msg); switchSidebarTab('tools'); break;
|
|
187
187
|
case 'export_data': handleExportData(msg); break;
|
|
188
188
|
case 'memory_content': handleMemoryContent(msg); break;
|
|
@@ -449,34 +449,77 @@ function handleTodoUpdate(todos) {
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
function handleStatus(msg) {
|
|
452
|
+
// Global UI (provider list, toolbar toggles) always reflects the latest
|
|
453
|
+
// server state regardless of which Tab the status belongs to — these are
|
|
454
|
+
// handler-scoped, not session-scoped.
|
|
452
455
|
providers = msg.providers || [];
|
|
453
456
|
updateProviderSelect(msg.provider);
|
|
454
457
|
updateModelSelect(msg.provider, msg.model);
|
|
455
458
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
459
|
+
// ── Route by sessionId ────────────────────────────────────────────
|
|
460
|
+
// Find the Tab this status payload belongs to. If the active Tab matches,
|
|
461
|
+
// we apply the full UI update. Otherwise we just refresh that Tab's stored
|
|
462
|
+
// metadata (title, token usage) without touching the live DOM — prevents
|
|
463
|
+
// "wrong content flashing on wrong tab" during fast tab switches.
|
|
464
|
+
let targetIdx = sessionTabs.findIndex(t => t.sessionId && t.sessionId === msg.sessionId);
|
|
465
|
+
|
|
466
|
+
// Fallback: if no tab owns this sessionId yet, bind it to the first tab
|
|
467
|
+
// that is still waiting for a `session new` response (FIFO matches the
|
|
468
|
+
// serial order of server command processing).
|
|
469
|
+
if (targetIdx < 0 && msg.sessionId) {
|
|
470
|
+
targetIdx = sessionTabs.findIndex(t => t.pendingBind);
|
|
471
|
+
if (targetIdx >= 0) {
|
|
472
|
+
sessionTabs[targetIdx].sessionId = msg.sessionId;
|
|
473
|
+
sessionTabs[targetIdx].pendingBind = false;
|
|
474
|
+
}
|
|
462
475
|
}
|
|
463
476
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
// Persist active session for this tab (for page reload restore)
|
|
470
|
-
if (msg.sessionId) {
|
|
471
|
-
sessionStorage.setItem('aicli-active-session', msg.sessionId);
|
|
477
|
+
if (targetIdx < 0) {
|
|
478
|
+
// Stale response — the tab that asked for it was closed before the
|
|
479
|
+
// server responded. Drop silently.
|
|
480
|
+
return;
|
|
472
481
|
}
|
|
473
482
|
|
|
474
|
-
|
|
475
|
-
|
|
483
|
+
const targetTab = sessionTabs[targetIdx];
|
|
484
|
+
const isActiveTarget = targetIdx === activeTabIdx;
|
|
485
|
+
|
|
486
|
+
// Update tab-owned metadata (applies whether active or not)
|
|
487
|
+
if (msg.sessionTitle) targetTab.title = msg.sessionTitle;
|
|
488
|
+
else if (msg.sessionId && (!targetTab.title || targetTab.title === 'New Chat')) {
|
|
489
|
+
targetTab.title = msg.sessionId.slice(0, 8);
|
|
490
|
+
}
|
|
491
|
+
if (msg.tokenUsage) targetTab.tokenUsage = msg.tokenUsage;
|
|
492
|
+
|
|
493
|
+
if (isActiveTarget) {
|
|
494
|
+
// Active tab: full UI reflection
|
|
495
|
+
btnThink.classList.toggle('btn-active-toggle', msg.thinkingMode);
|
|
496
|
+
btnPlan.classList.toggle('btn-active-toggle', msg.planMode);
|
|
497
|
+
statusSession.textContent = `📋 ${msg.sessionId?.slice(0, 8) || '—'} (${msg.messageCount} msgs)`;
|
|
498
|
+
if (msg.tokenUsage) {
|
|
499
|
+
const u = msg.tokenUsage;
|
|
500
|
+
const cacheRead = u.cacheReadTokens || 0;
|
|
501
|
+
let line = `📊 in: ${u.inputTokens.toLocaleString()} out: ${u.outputTokens.toLocaleString()}`;
|
|
502
|
+
if (cacheRead > 0) line += ` cache: ${cacheRead.toLocaleString()}`;
|
|
503
|
+
if (msg.costUsd != null) {
|
|
504
|
+
const cost = msg.costUsd;
|
|
505
|
+
const costStr = cost === 0 ? '$0' : cost < 0.01 ? `$${cost.toFixed(4)}` : cost < 1 ? `$${cost.toFixed(3)}` : `$${cost.toFixed(2)}`;
|
|
506
|
+
line += ` 💰 ${costStr}`;
|
|
507
|
+
}
|
|
508
|
+
statusTokens.textContent = line;
|
|
509
|
+
}
|
|
510
|
+
sessionListEl.querySelectorAll('.session-item').forEach(el => {
|
|
511
|
+
el.classList.toggle('active', el.dataset.sessionId === msg.sessionId);
|
|
512
|
+
});
|
|
513
|
+
if (msg.sessionId) {
|
|
514
|
+
sessionStorage.setItem('aicli-active-session', msg.sessionId);
|
|
515
|
+
}
|
|
516
|
+
targetTab.isProcessing = processing;
|
|
517
|
+
const title = msg.sessionTitle || msg.sessionId?.slice(0, 8) || 'New Session';
|
|
518
|
+
document.title = `ai-cli — ${title}`;
|
|
519
|
+
}
|
|
476
520
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
document.title = `ai-cli — ${title}`;
|
|
521
|
+
renderTabBar();
|
|
522
|
+
saveTabState();
|
|
480
523
|
}
|
|
481
524
|
|
|
482
525
|
// ── Response helpers ───────────────────────────────────────────────
|
|
@@ -795,6 +838,7 @@ function loadSessionInTab(sessionId, title) {
|
|
|
795
838
|
// Load in current active tab
|
|
796
839
|
if (activeTabIdx >= 0 && activeTabIdx < sessionTabs.length) {
|
|
797
840
|
sessionTabs[activeTabIdx].sessionId = sessionId;
|
|
841
|
+
sessionTabs[activeTabIdx].pendingBind = false; // explicit load cancels any pending bind
|
|
798
842
|
if (title) sessionTabs[activeTabIdx].title = title;
|
|
799
843
|
renderTabBar();
|
|
800
844
|
saveTabState();
|
|
@@ -965,18 +1009,97 @@ function updateBatchBar() {
|
|
|
965
1009
|
}
|
|
966
1010
|
}
|
|
967
1011
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
1012
|
+
/**
|
|
1013
|
+
* Render the `session_messages` payload for a session.
|
|
1014
|
+
* Routes to the correct UI tab based on payload.sessionId so that fast
|
|
1015
|
+
* tab-switching does not cause content to be applied to the wrong tab.
|
|
1016
|
+
*
|
|
1017
|
+
* msg = { type: 'session_messages', sessionId, messages: [...] }
|
|
1018
|
+
*
|
|
1019
|
+
* If the matched tab is active → render directly to the live DOM (and
|
|
1020
|
+
* snapshot into the tab cache). Otherwise build the HTML off-DOM and
|
|
1021
|
+
* write it into the tab's `messagesHtml` cache; the live DOM is left
|
|
1022
|
+
* untouched so the active tab's content never flashes wrong data.
|
|
1023
|
+
*/
|
|
1024
|
+
function renderSessionMessages(msg) {
|
|
1025
|
+
// Back-compat: if called with a bare array (legacy), treat as active-tab apply
|
|
1026
|
+
const messages = Array.isArray(msg) ? msg : msg.messages;
|
|
1027
|
+
const sessionId = Array.isArray(msg) ? null : msg.sessionId;
|
|
1028
|
+
|
|
1029
|
+
// Locate the target tab
|
|
1030
|
+
let targetIdx = -1;
|
|
1031
|
+
if (sessionId) {
|
|
1032
|
+
targetIdx = sessionTabs.findIndex(t => t.sessionId === sessionId);
|
|
1033
|
+
}
|
|
1034
|
+
if (targetIdx < 0) {
|
|
1035
|
+
// No explicit match — fall back to active tab (legacy behavior)
|
|
1036
|
+
targetIdx = activeTabIdx;
|
|
1037
|
+
}
|
|
1038
|
+
if (targetIdx < 0 || targetIdx >= sessionTabs.length) return;
|
|
1039
|
+
|
|
1040
|
+
const isActiveTarget = targetIdx === activeTabIdx;
|
|
1041
|
+
|
|
1042
|
+
if (isActiveTarget) {
|
|
1043
|
+
// Active tab: paint directly into the live DOM (preserves any in-flight
|
|
1044
|
+
// streaming helpers that rely on messagesEl)
|
|
1045
|
+
messagesEl.innerHTML = '';
|
|
1046
|
+
for (const m of messages) {
|
|
1047
|
+
if (m.role === 'user') {
|
|
1048
|
+
addUserMessage(m.content);
|
|
1049
|
+
} else if (m.role === 'assistant') {
|
|
1050
|
+
const el = createAssistantMessage();
|
|
1051
|
+
renderMarkdown(el, m.content);
|
|
1052
|
+
}
|
|
977
1053
|
}
|
|
1054
|
+
scrollToBottom();
|
|
1055
|
+
// Snapshot into cache so subsequent tab-snapshots see the latest content
|
|
1056
|
+
sessionTabs[targetIdx].messagesHtml = messagesEl.innerHTML;
|
|
1057
|
+
} else {
|
|
1058
|
+
// Inactive tab: build HTML off-DOM, store in tab cache, do NOT touch
|
|
1059
|
+
// the live DOM. When the user eventually switches to this tab,
|
|
1060
|
+
// restoreTab() will paint the cached HTML.
|
|
1061
|
+
sessionTabs[targetIdx].messagesHtml = buildMessagesHtmlOffDom(messages);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
/**
|
|
1066
|
+
* Build the HTML string for a list of messages without affecting the
|
|
1067
|
+
* currently visible DOM. Uses a save-clear-render-capture-restore cycle
|
|
1068
|
+
* on the existing messagesEl so we can re-use addUserMessage /
|
|
1069
|
+
* createAssistantMessage (both of which reference messagesEl directly).
|
|
1070
|
+
* The round-trip is synchronous so the user never sees it.
|
|
1071
|
+
*/
|
|
1072
|
+
function buildMessagesHtmlOffDom(messages) {
|
|
1073
|
+
const savedHtml = messagesEl.innerHTML;
|
|
1074
|
+
const savedScroll = chatArea.scrollTop;
|
|
1075
|
+
// Save streaming pointers so we don't orphan them
|
|
1076
|
+
const savedAssistantEl = currentAssistantEl;
|
|
1077
|
+
const savedAssistantContent = currentAssistantContent;
|
|
1078
|
+
const savedThinkingEl = currentThinkingEl;
|
|
1079
|
+
const savedThinkingContent = currentThinkingContent;
|
|
1080
|
+
try {
|
|
1081
|
+
messagesEl.innerHTML = '';
|
|
1082
|
+
currentAssistantEl = null;
|
|
1083
|
+
currentAssistantContent = '';
|
|
1084
|
+
currentThinkingEl = null;
|
|
1085
|
+
currentThinkingContent = '';
|
|
1086
|
+
for (const m of messages) {
|
|
1087
|
+
if (m.role === 'user') {
|
|
1088
|
+
addUserMessage(m.content);
|
|
1089
|
+
} else if (m.role === 'assistant') {
|
|
1090
|
+
const el = createAssistantMessage();
|
|
1091
|
+
renderMarkdown(el, m.content);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
return messagesEl.innerHTML;
|
|
1095
|
+
} finally {
|
|
1096
|
+
messagesEl.innerHTML = savedHtml;
|
|
1097
|
+
chatArea.scrollTop = savedScroll;
|
|
1098
|
+
currentAssistantEl = savedAssistantEl;
|
|
1099
|
+
currentAssistantContent = savedAssistantContent;
|
|
1100
|
+
currentThinkingEl = savedThinkingEl;
|
|
1101
|
+
currentThinkingContent = savedThinkingContent;
|
|
978
1102
|
}
|
|
979
|
-
scrollToBottom();
|
|
980
1103
|
}
|
|
981
1104
|
|
|
982
1105
|
// New session button — opens in a new tab
|
|
@@ -1867,6 +1990,10 @@ function addTab(sessionId, title) {
|
|
|
1867
1990
|
scrollPos: 0,
|
|
1868
1991
|
tokenUsage: { inputTokens: 0, outputTokens: 0 },
|
|
1869
1992
|
isProcessing: false,
|
|
1993
|
+
// pendingBind: this tab is waiting for the server to assign a sessionId.
|
|
1994
|
+
// Set when `session new` is dispatched; cleared once `status` arrives with
|
|
1995
|
+
// the assigned id. Prevents duplicate `session new` requests on fast re-switch.
|
|
1996
|
+
pendingBind: !sessionId,
|
|
1870
1997
|
_currentAssistantContent: '',
|
|
1871
1998
|
};
|
|
1872
1999
|
sessionTabs.push(tab);
|
|
@@ -1908,7 +2035,11 @@ function switchToTab(index) {
|
|
|
1908
2035
|
// Tell server to switch session
|
|
1909
2036
|
if (tab.sessionId) {
|
|
1910
2037
|
send({ type: 'command', name: 'session', args: ['load', tab.sessionId] });
|
|
1911
|
-
} else {
|
|
2038
|
+
} else if (!tab.pendingBind) {
|
|
2039
|
+
// No id yet AND no outstanding `session new` in flight — request one.
|
|
2040
|
+
// If pendingBind is true, the original `session new` response is still
|
|
2041
|
+
// inbound; don't send another request or we'd create a dangling session.
|
|
2042
|
+
tab.pendingBind = true;
|
|
1912
2043
|
send({ type: 'command', name: 'session', args: ['new'] });
|
|
1913
2044
|
}
|
|
1914
2045
|
|
|
@@ -1942,19 +2073,6 @@ function findTabBySessionId(sessionId) {
|
|
|
1942
2073
|
return sessionTabs.findIndex(t => t.sessionId === sessionId);
|
|
1943
2074
|
}
|
|
1944
2075
|
|
|
1945
|
-
/** Update the active tab's metadata from a status message */
|
|
1946
|
-
function updateActiveTabFromStatus(msg) {
|
|
1947
|
-
if (activeTabIdx < 0 || activeTabIdx >= sessionTabs.length) return;
|
|
1948
|
-
const tab = sessionTabs[activeTabIdx];
|
|
1949
|
-
if (msg.sessionId) tab.sessionId = msg.sessionId;
|
|
1950
|
-
if (msg.sessionTitle) tab.title = msg.sessionTitle;
|
|
1951
|
-
else if (msg.sessionId && !tab.title) tab.title = msg.sessionId.slice(0, 8);
|
|
1952
|
-
tab.isProcessing = processing;
|
|
1953
|
-
if (msg.tokenUsage) tab.tokenUsage = msg.tokenUsage;
|
|
1954
|
-
renderTabBar();
|
|
1955
|
-
saveTabState();
|
|
1956
|
-
}
|
|
1957
|
-
|
|
1958
2076
|
/** Persist tab state to sessionStorage for page reload */
|
|
1959
2077
|
function saveTabState() {
|
|
1960
2078
|
try {
|