claude-code-kanban 3.3.0 → 3.5.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/lib/parsers.js +69 -33
- package/package.json +1 -1
- package/public/app.js +184 -36
- package/public/index.html +2 -1
- package/public/style.css +77 -0
- package/server.js +39 -33
package/lib/parsers.js
CHANGED
|
@@ -582,8 +582,9 @@ function readMessagesPage(jsonlPath, limit = 10, beforeTimestamp = null) {
|
|
|
582
582
|
};
|
|
583
583
|
}
|
|
584
584
|
|
|
585
|
-
function
|
|
585
|
+
function buildSessionDigest(jsonlPath) {
|
|
586
586
|
const map = {};
|
|
587
|
+
const terminated = new Map();
|
|
587
588
|
try {
|
|
588
589
|
const content = readFileSync(jsonlPath, 'utf8');
|
|
589
590
|
const re = /"type":"agent_progress"[^}]*"agentId":"([^"]+)"/;
|
|
@@ -596,6 +597,34 @@ function buildAgentProgressMap(jsonlPath) {
|
|
|
596
597
|
const nameByToolUseId = {};
|
|
597
598
|
const descByToolUseId = {};
|
|
598
599
|
for (const line of content.split('\n')) {
|
|
600
|
+
// Terminated-teammate detection: check first since cheap substring guards
|
|
601
|
+
if (line.includes('teammate-message') &&
|
|
602
|
+
(line.includes('teammate_terminated') || line.includes('shutdown_response'))) {
|
|
603
|
+
try {
|
|
604
|
+
const obj = JSON.parse(line);
|
|
605
|
+
if (obj.type === 'user') {
|
|
606
|
+
const text = typeof obj.message?.content === 'string' ? obj.message.content : null;
|
|
607
|
+
if (text) {
|
|
608
|
+
const ts = obj.timestamp || null;
|
|
609
|
+
for (const tmMatch of text.matchAll(/<teammate-message\s+[^>]*teammate_id="([^"]+)"[^>]*>([\s\S]*?)<\/teammate-message>/g)) {
|
|
610
|
+
try {
|
|
611
|
+
const tid = tmMatch[1];
|
|
612
|
+
const body = tmMatch[2].trim();
|
|
613
|
+
const protocol = JSON.parse(body);
|
|
614
|
+
if (protocol.type === 'teammate_terminated') {
|
|
615
|
+
const name = protocol.from || (protocol.message?.match(/^(\S+)\s/)?.[1]) || tid;
|
|
616
|
+
if (name !== 'system') terminated.set(name, ts);
|
|
617
|
+
} else if (protocol.type === 'shutdown_response' && protocol.approve) {
|
|
618
|
+
const name = protocol.from || tid;
|
|
619
|
+
if (name !== 'system') terminated.set(name, ts);
|
|
620
|
+
}
|
|
621
|
+
} catch (_) {}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
} catch (_) {}
|
|
626
|
+
}
|
|
627
|
+
|
|
599
628
|
if (line.includes('"agent_progress"')) {
|
|
600
629
|
const agentMatch = re.exec(line);
|
|
601
630
|
const parentMatch = parentRe.exec(line);
|
|
@@ -657,7 +686,11 @@ function buildAgentProgressMap(jsonlPath) {
|
|
|
657
686
|
if (descByToolUseId[key]) entry.description = descByToolUseId[key];
|
|
658
687
|
}
|
|
659
688
|
} catch (_) {}
|
|
660
|
-
return map;
|
|
689
|
+
return { progressMap: map, terminated };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function buildAgentProgressMap(jsonlPath) {
|
|
693
|
+
return buildSessionDigest(jsonlPath).progressMap;
|
|
661
694
|
}
|
|
662
695
|
|
|
663
696
|
function readCompactSummaries(jsonlPath) {
|
|
@@ -699,36 +732,7 @@ function readCompactSummaries(jsonlPath) {
|
|
|
699
732
|
}
|
|
700
733
|
|
|
701
734
|
function findTerminatedTeammates(jsonlPath) {
|
|
702
|
-
|
|
703
|
-
try {
|
|
704
|
-
const content = readFileSync(jsonlPath, 'utf8');
|
|
705
|
-
for (const line of content.split('\n')) {
|
|
706
|
-
if (!line.includes('teammate-message')) continue;
|
|
707
|
-
if (!line.includes('teammate_terminated') && !line.includes('shutdown_response')) continue;
|
|
708
|
-
try {
|
|
709
|
-
const obj = JSON.parse(line);
|
|
710
|
-
if (obj.type !== 'user') continue;
|
|
711
|
-
const text = typeof obj.message?.content === 'string' ? obj.message.content : null;
|
|
712
|
-
if (!text) continue;
|
|
713
|
-
const ts = obj.timestamp || null;
|
|
714
|
-
for (const tmMatch of text.matchAll(/<teammate-message\s+[^>]*teammate_id="([^"]+)"[^>]*>([\s\S]*?)<\/teammate-message>/g)) {
|
|
715
|
-
try {
|
|
716
|
-
const tid = tmMatch[1];
|
|
717
|
-
const body = tmMatch[2].trim();
|
|
718
|
-
const protocol = JSON.parse(body);
|
|
719
|
-
if (protocol.type === 'teammate_terminated') {
|
|
720
|
-
const name = protocol.from || (protocol.message?.match(/^(\S+)\s/)?.[1]) || tid;
|
|
721
|
-
if (name !== 'system') terminated.set(name, ts);
|
|
722
|
-
} else if (protocol.type === 'shutdown_response' && protocol.approve) {
|
|
723
|
-
const name = protocol.from || tid;
|
|
724
|
-
if (name !== 'system') terminated.set(name, ts);
|
|
725
|
-
}
|
|
726
|
-
} catch (_) {}
|
|
727
|
-
}
|
|
728
|
-
} catch (_) {}
|
|
729
|
-
}
|
|
730
|
-
} catch (_) {}
|
|
731
|
-
return terminated;
|
|
735
|
+
return buildSessionDigest(jsonlPath).terminated;
|
|
732
736
|
}
|
|
733
737
|
|
|
734
738
|
function extractPromptFromTranscript(jsonlPath) {
|
|
@@ -766,6 +770,36 @@ function extractPromptFromTranscript(jsonlPath) {
|
|
|
766
770
|
return null;
|
|
767
771
|
}
|
|
768
772
|
|
|
773
|
+
function extractModelFromTranscript(jsonlPath) {
|
|
774
|
+
const { openSync, readSync, closeSync } = fs;
|
|
775
|
+
const MAX_READ = 65536;
|
|
776
|
+
const CHUNK = 4096;
|
|
777
|
+
const fd = openSync(jsonlPath, 'r');
|
|
778
|
+
try {
|
|
779
|
+
let accumulated = '';
|
|
780
|
+
const buf = Buffer.alloc(CHUNK);
|
|
781
|
+
while (accumulated.length < MAX_READ) {
|
|
782
|
+
const bytesRead = readSync(fd, buf, 0, CHUNK, null);
|
|
783
|
+
if (bytesRead === 0) break;
|
|
784
|
+
accumulated += buf.toString('utf8', 0, bytesRead);
|
|
785
|
+
let nlIdx;
|
|
786
|
+
while ((nlIdx = accumulated.indexOf('\n')) !== -1) {
|
|
787
|
+
const line = accumulated.slice(0, nlIdx);
|
|
788
|
+
accumulated = accumulated.slice(nlIdx + 1);
|
|
789
|
+
if (!line.trim()) continue;
|
|
790
|
+
try {
|
|
791
|
+
const obj = JSON.parse(line);
|
|
792
|
+
const model = obj.model || (obj.message && obj.message.model);
|
|
793
|
+
if (model) return model;
|
|
794
|
+
} catch (_) {}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
} finally {
|
|
798
|
+
closeSync(fd);
|
|
799
|
+
}
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
802
|
+
|
|
769
803
|
module.exports = {
|
|
770
804
|
parseTask,
|
|
771
805
|
parseAgent,
|
|
@@ -777,7 +811,9 @@ module.exports = {
|
|
|
777
811
|
readRecentMessages,
|
|
778
812
|
readMessagesPage,
|
|
779
813
|
buildAgentProgressMap,
|
|
814
|
+
buildSessionDigest,
|
|
780
815
|
readCompactSummaries,
|
|
781
816
|
findTerminatedTeammates,
|
|
782
|
-
extractPromptFromTranscript
|
|
817
|
+
extractPromptFromTranscript,
|
|
818
|
+
extractModelFromTranscript
|
|
783
819
|
};
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -78,11 +78,34 @@ function updateUrl() {
|
|
|
78
78
|
const qs = params.toString();
|
|
79
79
|
const url = qs ? `?${qs}` : window.location.pathname;
|
|
80
80
|
history.replaceState(null, '', url);
|
|
81
|
+
persistLastView();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const LAST_VIEW_KEY = 'lastView';
|
|
85
|
+
function persistLastView() {
|
|
86
|
+
try {
|
|
87
|
+
const data = {
|
|
88
|
+
view: viewMode,
|
|
89
|
+
session: currentSessionId,
|
|
90
|
+
projectPath: viewMode === 'project' ? currentProjectPath : null,
|
|
91
|
+
};
|
|
92
|
+
localStorage.setItem(LAST_VIEW_KEY, JSON.stringify(data));
|
|
93
|
+
} catch (_) {}
|
|
94
|
+
}
|
|
95
|
+
function loadLastView() {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.parse(localStorage.getItem(LAST_VIEW_KEY)) || null;
|
|
98
|
+
} catch (_) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
81
101
|
}
|
|
82
102
|
|
|
83
103
|
// biome-ignore lint/correctness/noUnusedVariables: used in HTML
|
|
84
104
|
function resetState() {
|
|
85
105
|
history.replaceState(null, '', window.location.pathname);
|
|
106
|
+
try {
|
|
107
|
+
localStorage.removeItem(LAST_VIEW_KEY);
|
|
108
|
+
} catch (_) {}
|
|
86
109
|
sessionFilter = 'active';
|
|
87
110
|
sessionLimit = '20';
|
|
88
111
|
filterProject = '__recent__';
|
|
@@ -478,9 +501,10 @@ async function fetchTasks(sessionId) {
|
|
|
478
501
|
for (const k of Object.keys(ownerColorCache)) delete ownerColorCache[k];
|
|
479
502
|
for (const k of Object.keys(teamColorMap)) delete teamColorMap[k];
|
|
480
503
|
sessionJustSelected = true;
|
|
504
|
+
resetAgentState();
|
|
481
505
|
updateUrl();
|
|
482
506
|
renderSession();
|
|
483
|
-
|
|
507
|
+
fetchAgents(sessionId);
|
|
484
508
|
if (!agentLogMode) fetchMessages(sessionId);
|
|
485
509
|
} catch (error) {
|
|
486
510
|
console.error('Failed to fetch tasks:', error);
|
|
@@ -497,13 +521,18 @@ const _AGENT_STALE_MS = 5 * 60 * 1000; // kept for reference; no longer used for
|
|
|
497
521
|
const WAITING_TTL_MS = 30 * 60 * 1000;
|
|
498
522
|
const AGENT_LOG_MAX = 8;
|
|
499
523
|
|
|
524
|
+
function resetAgentState() {
|
|
525
|
+
currentAgents = [];
|
|
526
|
+
currentWaiting = null;
|
|
527
|
+
lastAgentsHash = '';
|
|
528
|
+
renderAgentFooter();
|
|
529
|
+
}
|
|
530
|
+
|
|
500
531
|
async function fetchAgents(sessionId) {
|
|
501
532
|
try {
|
|
502
533
|
const res = await fetch(`/api/sessions/${sessionId}/agents`);
|
|
503
534
|
if (!res.ok) {
|
|
504
|
-
|
|
505
|
-
currentWaiting = null;
|
|
506
|
-
renderAgentFooter();
|
|
535
|
+
resetAgentState();
|
|
507
536
|
return;
|
|
508
537
|
}
|
|
509
538
|
const data = await res.json();
|
|
@@ -632,6 +661,17 @@ function toggleMessagePanel() {
|
|
|
632
661
|
updateUrl();
|
|
633
662
|
}
|
|
634
663
|
|
|
664
|
+
// biome-ignore lint/correctness/noUnusedVariables: used in HTML onclick
|
|
665
|
+
async function openSessionWithBookmarks(sessionId) {
|
|
666
|
+
if (!messagePanelOpen) {
|
|
667
|
+
const panel = document.getElementById('message-panel');
|
|
668
|
+
messagePanelOpen = true;
|
|
669
|
+
panel.classList.add('visible');
|
|
670
|
+
document.getElementById('message-toggle')?.classList.add('active');
|
|
671
|
+
}
|
|
672
|
+
await fetchTasks(sessionId);
|
|
673
|
+
}
|
|
674
|
+
|
|
635
675
|
// biome-ignore lint/correctness/noUnusedVariables: used in HTML
|
|
636
676
|
async function viewAgentLog(agentId) {
|
|
637
677
|
let agent = findAgentById(agentId);
|
|
@@ -1336,6 +1376,10 @@ const MARKETPLACE_SVG =
|
|
|
1336
1376
|
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>';
|
|
1337
1377
|
const MEMORY_SVG =
|
|
1338
1378
|
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>';
|
|
1379
|
+
const LINK_SVG_PATHS =
|
|
1380
|
+
'<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>';
|
|
1381
|
+
const linkSvg = (size) =>
|
|
1382
|
+
`<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${LINK_SVG_PATHS}</svg>`;
|
|
1339
1383
|
|
|
1340
1384
|
//#endregion
|
|
1341
1385
|
|
|
@@ -1978,34 +2022,35 @@ function showAgentModal(agentId) {
|
|
|
1978
2022
|
const modalNameLabel = agent.agentName ? ` · ${escapeHtml(agent.agentName)}` : '';
|
|
1979
2023
|
title.innerHTML = `${statusDot} ${escapeHtml(agent.type || 'unknown')}${modalNameLabel}`;
|
|
1980
2024
|
|
|
1981
|
-
const
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2025
|
+
const shortModel = agent.model ? agent.model.replace(/^claude-/, '').replace(/-\d{8}$/, '') : null;
|
|
2026
|
+
const shortId = agent.agentId ? agent.agentId.slice(0, 8) : '';
|
|
2027
|
+
const chip = (label, value, opts = {}) => {
|
|
2028
|
+
const cls = opts.cls ? ` ${opts.cls}` : '';
|
|
2029
|
+
const style = opts.style ? ` style="${opts.style}"` : '';
|
|
2030
|
+
const title = opts.title ? ` title="${escapeHtml(opts.title)}"` : '';
|
|
2031
|
+
const labelHtml = label ? `<span class="agent-chip-label">${label}</span>` : '';
|
|
2032
|
+
return `<span class="agent-chip${cls}"${style}${title}>${labelHtml}<span class="agent-chip-val">${value}</span></span>`;
|
|
2033
|
+
};
|
|
2034
|
+
|
|
2035
|
+
const chips = [];
|
|
2036
|
+
if (agent.agentId) chips.push(chip('id', escapeHtml(shortId), { cls: 'agent-chip-mono', title: agent.agentId }));
|
|
2037
|
+
chips.push(chip('', escapeHtml(agent.status), { cls: `agent-chip-status agent-chip-${agent.status}` }));
|
|
2038
|
+
chips.push(chip('⏱', formatDuration(elapsed)));
|
|
2039
|
+
if (shortModel) chips.push(chip('model', escapeHtml(shortModel), { cls: 'agent-chip-mono' }));
|
|
1986
2040
|
if (agent.agentName) {
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1989
|
-
'
|
|
1990
|
-
|
|
1991
|
-
|
|
2041
|
+
const c = getOwnerColor(agent.agentName);
|
|
2042
|
+
chips.push(
|
|
2043
|
+
chip('owner', escapeHtml(agent.agentName), {
|
|
2044
|
+
style: `background:${c.bg};color:${c.color};border-color:transparent;`,
|
|
2045
|
+
}),
|
|
2046
|
+
);
|
|
1992
2047
|
}
|
|
1993
|
-
if (
|
|
1994
|
-
|
|
1995
|
-
if (started) rows.push(['Started', started.toLocaleTimeString()]);
|
|
1996
|
-
if (stopped) rows.push(['Stopped', stopped.toLocaleTimeString()]);
|
|
2048
|
+
if (started) chips.push(chip('started', started.toLocaleTimeString()));
|
|
2049
|
+
if (stopped) chips.push(chip('stopped', stopped.toLocaleTimeString()));
|
|
1997
2050
|
|
|
1998
2051
|
const agentMsg = currentMessages.find((m) => m.tool === 'Agent' && m.agentId === agentId);
|
|
1999
2052
|
|
|
2000
|
-
let html =
|
|
2001
|
-
`<table style="width:100%;border-collapse:collapse;">` +
|
|
2002
|
-
rows
|
|
2003
|
-
.map(
|
|
2004
|
-
([k, v]) =>
|
|
2005
|
-
`<tr><td style="padding:6px 12px 6px 0;color:var(--text-tertiary);white-space:nowrap;vertical-align:top;">${k}</td><td style="padding:6px 0;color:var(--text-primary);">${v}</td></tr>`,
|
|
2006
|
-
)
|
|
2007
|
-
.join('') +
|
|
2008
|
-
`</table>`;
|
|
2053
|
+
let html = `<div class="agent-chips">${chips.join('')}</div>`;
|
|
2009
2054
|
|
|
2010
2055
|
const promptText = stripTeammateWrapper(agentMsg?.agentPrompt || agent.prompt || null);
|
|
2011
2056
|
const responseText = agent.lastMessage ? stripAnsi(agent.lastMessage.trim()) : null;
|
|
@@ -2064,10 +2109,7 @@ async function showAllTasks() {
|
|
|
2064
2109
|
if (agentLogMode) exitAgentLogMode();
|
|
2065
2110
|
currentSessionId = null;
|
|
2066
2111
|
ownerFilter = '';
|
|
2067
|
-
|
|
2068
|
-
currentWaiting = null;
|
|
2069
|
-
lastAgentsHash = '';
|
|
2070
|
-
renderAgentFooter();
|
|
2112
|
+
resetAgentState();
|
|
2071
2113
|
const res = await fetch('/api/tasks/all');
|
|
2072
2114
|
allTasksCache = await res.json();
|
|
2073
2115
|
let tasks = allTasksCache;
|
|
@@ -2241,6 +2283,8 @@ function renderSessions() {
|
|
|
2241
2283
|
const pinClass = pinState === 'sticky' ? ' sticky' : pinState === 'pinned' ? ' pinned' : '';
|
|
2242
2284
|
const pinTitle = pinState === 'pinned' || pinState === 'sticky' ? 'Unpin' : 'Pin';
|
|
2243
2285
|
const showCtx = !!session.contextStatus;
|
|
2286
|
+
const linkedDocsCount = getSessionPreviewPaths(session.id).length;
|
|
2287
|
+
const bookmarksCount = loadPins(session.id).length;
|
|
2244
2288
|
return `
|
|
2245
2289
|
<button onclick="fetchTasks('${session.id}')" data-session-id="${session.id}" class="session-item ${isActive ? 'active' : ''} ${session.hasWaitingForUser ? 'permission-pending' : ''} ${!session.hasRecentLog && !session.inProgress && !session.hasWaitingForUser ? 'stale' : ''} ${showCtx ? 'has-context' : ''}" title="${tooltip}">
|
|
2246
2290
|
<span class="session-pin-btn${pinClass}" onclick="event.stopPropagation();toggleSessionPin('${escapeHtml(session.id)}')" title="${pinTitle} session">${SESSION_PIN_SVG}</span>
|
|
@@ -2251,9 +2295,11 @@ function renderSessions() {
|
|
|
2251
2295
|
<div class="session-progress">
|
|
2252
2296
|
<span class="session-indicators">
|
|
2253
2297
|
${isTeam ? `<span class="team-badge" title="${memberCount} team members"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>${memberCount}</span>` : ''}
|
|
2254
|
-
${session.sharedTaskList ? `<span class="shared-tasklist-badge" title="Shared task list: ${escapeHtml(session.sharedTaskList)}"
|
|
2298
|
+
${session.sharedTaskList ? `<span class="shared-tasklist-badge" title="Shared task list: ${escapeHtml(session.sharedTaskList)}">${linkSvg(12)}</span>` : ''}
|
|
2255
2299
|
${isTeam || session.project || showCtx ? `<span class="team-info-btn" onclick="event.stopPropagation(); showSessionInfoModal('${session.id}')" title="View session info">ℹ</span>` : ''}
|
|
2256
2300
|
${session.hasPlan ? `<span class="plan-indicator" onclick="event.stopPropagation(); openPlanForSession('${session.id}')" title="View plan"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg></span>` : ''}
|
|
2301
|
+
${linkedDocsCount > 0 ? `<span class="linked-docs-badge" onclick="event.stopPropagation(); showSessionInfoModal('${session.id}')" title="${linkedDocsCount} linked document${linkedDocsCount > 1 ? 's' : ''}">${linkSvg(10)}${linkedDocsCount}</span>` : ''}
|
|
2302
|
+
${bookmarksCount > 0 ? `<span class="bookmarks-badge" onclick="event.stopPropagation(); openSessionWithBookmarks('${session.id}')" title="${bookmarksCount} bookmarked message${bookmarksCount > 1 ? 's' : ''}"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>${bookmarksCount}</span>` : ''}
|
|
2257
2303
|
${session.hasRunningAgents ? '<span class="agent-badge" title="Active agents">🤖</span>' : ''}
|
|
2258
2304
|
${session.planSourceSessionId ? `<span class="plan-indicator" title="Implements plan — click to reveal plan session" onclick="event.stopPropagation(); revealPlanSession('${escapeHtml(session.planSourceSessionId)}')">📋</span>` : ''}
|
|
2259
2305
|
${session.hasWaitingForUser ? '<span class="agent-badge" title="Waiting for user">❓</span>' : ''}
|
|
@@ -3531,6 +3577,7 @@ function _renderStorageTab() {
|
|
|
3531
3577
|
const tab = document.querySelector('.storage-tab.active')?.dataset.tab || 'sessions';
|
|
3532
3578
|
if (tab === 'sessions') body.innerHTML = _renderStorageSessions();
|
|
3533
3579
|
else if (tab === 'scratchpads') body.innerHTML = _renderStorageScratchpads();
|
|
3580
|
+
else if (tab === 'linked-docs') body.innerHTML = _renderStorageLinkedDocs();
|
|
3534
3581
|
}
|
|
3535
3582
|
|
|
3536
3583
|
function _renderStorageSessions() {
|
|
@@ -3753,6 +3800,87 @@ function _storageDeleteScratchpad(key) {
|
|
|
3753
3800
|
_updateStorageTotal();
|
|
3754
3801
|
}
|
|
3755
3802
|
|
|
3803
|
+
function _renderStorageLinkedDocs() {
|
|
3804
|
+
const entries = [];
|
|
3805
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
3806
|
+
const key = localStorage.key(i);
|
|
3807
|
+
if (!key.startsWith(PREVIEW_STORAGE_PREFIX)) continue;
|
|
3808
|
+
try {
|
|
3809
|
+
const arr = JSON.parse(localStorage.getItem(key)) || [];
|
|
3810
|
+
if (Array.isArray(arr) && arr.length) {
|
|
3811
|
+
entries.push({ sessionId: key.slice(PREVIEW_STORAGE_PREFIX.length), paths: arr });
|
|
3812
|
+
}
|
|
3813
|
+
} catch {}
|
|
3814
|
+
}
|
|
3815
|
+
if (!entries.length) return '<div class="storage-empty">No linked documents</div>';
|
|
3816
|
+
|
|
3817
|
+
const byId = new Map(entries.map((e) => [e.sessionId, e]));
|
|
3818
|
+
const { groups, orphans } = _groupByProject(entries.map((e) => e.sessionId));
|
|
3819
|
+
|
|
3820
|
+
function renderDocRow(sessionId, p) {
|
|
3821
|
+
const name = p.split(/[\\/]/).pop();
|
|
3822
|
+
const sid = _escapeForJsAttr(sessionId);
|
|
3823
|
+
const jsPath = _escapeForJsAttr(p);
|
|
3824
|
+
return `<div class="storage-item" style="padding-left:24px;">
|
|
3825
|
+
<span class="storage-item-id" title="${escapeHtml(p)}">${escapeHtml(name)}</span>
|
|
3826
|
+
<div class="storage-item-actions">
|
|
3827
|
+
<button onclick="_storagePreviewLinkedDoc('${jsPath}')">View</button>
|
|
3828
|
+
<button class="danger" onclick="_storageUnlinkDoc('${sid}','${jsPath}')">Unlink</button>
|
|
3829
|
+
</div>
|
|
3830
|
+
</div>`;
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
function renderSessionItem({ id, session }) {
|
|
3834
|
+
const entry = byId.get(id);
|
|
3835
|
+
if (!entry) return '';
|
|
3836
|
+
const eid = escapeHtml(id);
|
|
3837
|
+
const count = entry.paths.length;
|
|
3838
|
+
const header = `<div class="storage-group-header">
|
|
3839
|
+
<span>${_sessionLabel(session, id)} <span class="storage-item-badge">${count} doc${count > 1 ? 's' : ''}</span></span>
|
|
3840
|
+
<div class="storage-item-actions">
|
|
3841
|
+
<button class="danger" onclick="_storageClearLinkedDocs('${eid}')">Clear All</button>
|
|
3842
|
+
</div>
|
|
3843
|
+
</div>`;
|
|
3844
|
+
const rows = entry.paths.map((p) => renderDocRow(id, p)).join('');
|
|
3845
|
+
return header + rows;
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
let html = '';
|
|
3849
|
+
for (const [project, items] of groups) {
|
|
3850
|
+
const count = items.length;
|
|
3851
|
+
html += _renderProjectGroup(
|
|
3852
|
+
escapeHtml(_projectLabel(project)),
|
|
3853
|
+
`${count} session${count > 1 ? 's' : ''}`,
|
|
3854
|
+
items.map(renderSessionItem).join(''),
|
|
3855
|
+
);
|
|
3856
|
+
}
|
|
3857
|
+
if (orphans.length) {
|
|
3858
|
+
html += _renderOrphanGroup(orphans.length, orphans.map(renderSessionItem).join(''));
|
|
3859
|
+
}
|
|
3860
|
+
return html;
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3863
|
+
function _storagePreviewLinkedDoc(path) {
|
|
3864
|
+
closeStorageManager();
|
|
3865
|
+
openPreviewByPath(path);
|
|
3866
|
+
}
|
|
3867
|
+
|
|
3868
|
+
function _storageUnlinkDoc(sessionId, path) {
|
|
3869
|
+
removeSessionPreviewPath(sessionId, path);
|
|
3870
|
+
if (sessionId === _infoModalSessionId) refreshInfoModalLinkedDocs();
|
|
3871
|
+
renderSessions();
|
|
3872
|
+
_renderStorageTab();
|
|
3873
|
+
_updateStorageTotal();
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
function _storageClearLinkedDocs(sessionId) {
|
|
3877
|
+
localStorage.removeItem(PREVIEW_STORAGE_PREFIX + sessionId);
|
|
3878
|
+
if (sessionId === _infoModalSessionId) refreshInfoModalLinkedDocs();
|
|
3879
|
+
renderSessions();
|
|
3880
|
+
_renderStorageTab();
|
|
3881
|
+
_updateStorageTotal();
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3756
3884
|
function _findOrphanedKeys() {
|
|
3757
3885
|
const known = _getKnownSessionIds();
|
|
3758
3886
|
if (!known.size) return [];
|
|
@@ -3765,6 +3893,8 @@ function _findOrphanedKeys() {
|
|
|
3765
3893
|
if (!known.has(key.slice('pinned-messages-'.length))) orphaned.push(key);
|
|
3766
3894
|
} else if (key.startsWith('scratchpad-') && !key.startsWith('scratchpad-project:')) {
|
|
3767
3895
|
if (!known.has(key.slice('scratchpad-'.length))) orphaned.push(key);
|
|
3896
|
+
} else if (key.startsWith(PREVIEW_STORAGE_PREFIX)) {
|
|
3897
|
+
if (!known.has(key.slice(PREVIEW_STORAGE_PREFIX.length))) orphaned.push(key);
|
|
3768
3898
|
}
|
|
3769
3899
|
}
|
|
3770
3900
|
return orphaned;
|
|
@@ -4136,6 +4266,7 @@ function togglePreviewSessionLink() {
|
|
|
4136
4266
|
if (_infoModalSessionId === currentSessionId) {
|
|
4137
4267
|
refreshInfoModalLinkedDocs();
|
|
4138
4268
|
}
|
|
4269
|
+
renderSessions();
|
|
4139
4270
|
}
|
|
4140
4271
|
|
|
4141
4272
|
function refreshInfoModalLinkedDocs() {
|
|
@@ -4212,11 +4343,11 @@ function handleSessionOpenEvent(data) {
|
|
|
4212
4343
|
fetchTasks(id);
|
|
4213
4344
|
}
|
|
4214
4345
|
|
|
4215
|
-
function handlePreviewOpenEvent(data) {
|
|
4346
|
+
async function handlePreviewOpenEvent(data) {
|
|
4216
4347
|
const { path: filePath, content, sessionId } = data;
|
|
4217
4348
|
if (sessionId && sessionId !== currentSessionId) {
|
|
4218
4349
|
if (sessions.find((s) => s.id === sessionId)) {
|
|
4219
|
-
fetchTasks(sessionId);
|
|
4350
|
+
await fetchTasks(sessionId);
|
|
4220
4351
|
} else {
|
|
4221
4352
|
showToast(`Preview received for unknown session ${sessionId.slice(0, 8)}`);
|
|
4222
4353
|
}
|
|
@@ -4234,7 +4365,11 @@ function renderLinkedDocsHtml(sessionId) {
|
|
|
4234
4365
|
})
|
|
4235
4366
|
.join(', ');
|
|
4236
4367
|
return `<div class="linked-docs-section" style="margin-bottom:16px;font-size:12px;">
|
|
4237
|
-
<div style="font-size:11px;font-weight:500;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:6px;">
|
|
4368
|
+
<div style="font-size:11px;font-weight:500;color:var(--text-muted);text-transform:uppercase;letter-spacing:0.5px;margin-bottom:6px;display:flex;align-items:center;gap:6px;">
|
|
4369
|
+
${linkSvg(12)}
|
|
4370
|
+
<span>Linked documents</span>
|
|
4371
|
+
<span style="background:var(--bg-elevated);border:1px solid var(--border);border-radius:10px;padding:0 6px;font-size:10px;color:var(--text-secondary);">${paths.length}</span>
|
|
4372
|
+
</div>
|
|
4238
4373
|
<div>${items}</div>
|
|
4239
4374
|
</div>`;
|
|
4240
4375
|
}
|
|
@@ -5642,8 +5777,21 @@ Promise.all([
|
|
|
5642
5777
|
}
|
|
5643
5778
|
} else if (urlState.session) {
|
|
5644
5779
|
await fetchTasks(urlState.session);
|
|
5645
|
-
} else {
|
|
5780
|
+
} else if (urlState.view === 'all') {
|
|
5646
5781
|
showAllTasks();
|
|
5782
|
+
} else {
|
|
5783
|
+
const last = loadLastView();
|
|
5784
|
+
if (last?.view === 'project' && last.projectPath && sessions.some((s) => s.project === last.projectPath)) {
|
|
5785
|
+
try {
|
|
5786
|
+
await fetchProjectView(last.projectPath);
|
|
5787
|
+
} catch (_) {
|
|
5788
|
+
showAllTasks();
|
|
5789
|
+
}
|
|
5790
|
+
} else if (last?.view === 'session' && last.session && sessions.some((s) => s.id === last.session)) {
|
|
5791
|
+
await fetchTasks(last.session);
|
|
5792
|
+
} else {
|
|
5793
|
+
showAllTasks();
|
|
5794
|
+
}
|
|
5647
5795
|
}
|
|
5648
5796
|
if (urlState.messages && currentSessionId) {
|
|
5649
5797
|
toggleMessagePanel();
|
package/public/index.html
CHANGED
|
@@ -544,7 +544,7 @@
|
|
|
544
544
|
</div>
|
|
545
545
|
<div id="team-modal-body" class="modal-body"></div>
|
|
546
546
|
<div class="modal-footer">
|
|
547
|
-
<button id="session-info-dismiss-btn" class="btn btn-secondary" onclick="toggleDismissSession(_infoModalSessionId)">Dismiss</button>
|
|
547
|
+
<button id="session-info-dismiss-btn" class="btn btn-secondary" onclick="toggleDismissSession(_infoModalSessionId); closeTeamModal()">Dismiss</button>
|
|
548
548
|
<button class="btn btn-primary" onclick="closeTeamModal()">Close</button>
|
|
549
549
|
</div>
|
|
550
550
|
</div>
|
|
@@ -671,6 +671,7 @@
|
|
|
671
671
|
<div class="storage-tabs">
|
|
672
672
|
<button class="storage-tab active" data-tab="sessions" onclick="switchStorageTab('sessions')">Sessions</button>
|
|
673
673
|
<button class="storage-tab" data-tab="scratchpads" onclick="switchStorageTab('scratchpads')">Scratchpads</button>
|
|
674
|
+
<button class="storage-tab" data-tab="linked-docs" onclick="switchStorageTab('linked-docs')">Linked Docs</button>
|
|
674
675
|
</div>
|
|
675
676
|
<div class="modal-body" id="storage-modal-body" style="overflow-y:auto;min-height:200px;max-height:60vh;padding-top:16px;padding-right:8px;"></div>
|
|
676
677
|
<div class="modal-footer">
|
package/public/style.css
CHANGED
|
@@ -1707,6 +1707,61 @@ body::before {
|
|
|
1707
1707
|
flex-shrink: 0;
|
|
1708
1708
|
}
|
|
1709
1709
|
|
|
1710
|
+
.agent-chips {
|
|
1711
|
+
display: flex;
|
|
1712
|
+
flex-wrap: wrap;
|
|
1713
|
+
gap: 6px;
|
|
1714
|
+
margin-bottom: 10px;
|
|
1715
|
+
}
|
|
1716
|
+
.agent-chip {
|
|
1717
|
+
display: inline-flex;
|
|
1718
|
+
align-items: center;
|
|
1719
|
+
gap: 4px;
|
|
1720
|
+
padding: 3px 9px;
|
|
1721
|
+
font-size: 11px;
|
|
1722
|
+
font-weight: 500;
|
|
1723
|
+
border-radius: 999px;
|
|
1724
|
+
border: 1px solid var(--border-color, rgba(127, 127, 127, 0.25));
|
|
1725
|
+
background: var(--bg-secondary, rgba(127, 127, 127, 0.08));
|
|
1726
|
+
color: var(--text-secondary);
|
|
1727
|
+
white-space: nowrap;
|
|
1728
|
+
line-height: 1.4;
|
|
1729
|
+
}
|
|
1730
|
+
.agent-chip-label {
|
|
1731
|
+
color: var(--text-tertiary);
|
|
1732
|
+
text-transform: uppercase;
|
|
1733
|
+
font-size: 9.5px;
|
|
1734
|
+
font-weight: 600;
|
|
1735
|
+
letter-spacing: 0.5px;
|
|
1736
|
+
opacity: 0.75;
|
|
1737
|
+
}
|
|
1738
|
+
.agent-chip-val {
|
|
1739
|
+
color: inherit;
|
|
1740
|
+
}
|
|
1741
|
+
.agent-chip-mono .agent-chip-val {
|
|
1742
|
+
font-family: var(--font-mono, monospace);
|
|
1743
|
+
font-size: 10.5px;
|
|
1744
|
+
letter-spacing: 0.2px;
|
|
1745
|
+
}
|
|
1746
|
+
.agent-chip-status {
|
|
1747
|
+
text-transform: capitalize;
|
|
1748
|
+
}
|
|
1749
|
+
.agent-chip-running {
|
|
1750
|
+
background: rgba(34, 197, 94, 0.15);
|
|
1751
|
+
color: rgb(34, 160, 80);
|
|
1752
|
+
border-color: rgba(34, 197, 94, 0.3);
|
|
1753
|
+
}
|
|
1754
|
+
.agent-chip-stopped {
|
|
1755
|
+
background: rgba(127, 127, 127, 0.15);
|
|
1756
|
+
color: var(--text-tertiary);
|
|
1757
|
+
border-color: rgba(127, 127, 127, 0.3);
|
|
1758
|
+
}
|
|
1759
|
+
.agent-chip-error,
|
|
1760
|
+
.agent-chip-failed {
|
|
1761
|
+
background: rgba(239, 68, 68, 0.15);
|
|
1762
|
+
color: rgb(220, 60, 60);
|
|
1763
|
+
border-color: rgba(239, 68, 68, 0.3);
|
|
1764
|
+
}
|
|
1710
1765
|
.team-modal-desc {
|
|
1711
1766
|
font-size: 12px;
|
|
1712
1767
|
color: var(--text-secondary);
|
|
@@ -2498,6 +2553,28 @@ body::before {
|
|
|
2498
2553
|
cursor: default;
|
|
2499
2554
|
}
|
|
2500
2555
|
|
|
2556
|
+
.linked-docs-badge,
|
|
2557
|
+
.bookmarks-badge {
|
|
2558
|
+
display: inline-flex;
|
|
2559
|
+
align-items: center;
|
|
2560
|
+
gap: 2px;
|
|
2561
|
+
font-size: 10px;
|
|
2562
|
+
padding: 2px 6px;
|
|
2563
|
+
background: var(--bg-elevated);
|
|
2564
|
+
border: 1px solid var(--border);
|
|
2565
|
+
border-radius: 10px;
|
|
2566
|
+
color: var(--text-secondary);
|
|
2567
|
+
cursor: pointer;
|
|
2568
|
+
flex-shrink: 0;
|
|
2569
|
+
line-height: 1;
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
.linked-docs-badge:hover,
|
|
2573
|
+
.bookmarks-badge:hover {
|
|
2574
|
+
border-color: var(--accent);
|
|
2575
|
+
color: var(--text-primary);
|
|
2576
|
+
}
|
|
2577
|
+
|
|
2501
2578
|
/* #endregion */
|
|
2502
2579
|
|
|
2503
2580
|
/* #region PERMISSION_PENDING */
|
package/server.js
CHANGED
|
@@ -15,9 +15,11 @@ const {
|
|
|
15
15
|
readMessagesPage: _readMessagesPageUncached,
|
|
16
16
|
readSessionInfoFromJsonl,
|
|
17
17
|
buildAgentProgressMap,
|
|
18
|
+
buildSessionDigest,
|
|
18
19
|
readCompactSummaries,
|
|
19
20
|
findTerminatedTeammates,
|
|
20
|
-
extractPromptFromTranscript
|
|
21
|
+
extractPromptFromTranscript,
|
|
22
|
+
extractModelFromTranscript
|
|
21
23
|
} = require('./lib/parsers');
|
|
22
24
|
|
|
23
25
|
if (process.argv.includes("--install") || process.argv.includes("--uninstall")) {
|
|
@@ -80,7 +82,10 @@ const WAITING_RESOLVE_GRACE_MS = 15000;
|
|
|
80
82
|
|
|
81
83
|
function persistAgent(dir, agent) {
|
|
82
84
|
const file = path.join(dir, agent.agentId + '.json');
|
|
83
|
-
|
|
85
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
86
|
+
fs.writeFile(tmp, JSON.stringify(agent), 'utf8')
|
|
87
|
+
.then(() => fs.rename(tmp, file))
|
|
88
|
+
.catch(() => { fs.unlink(tmp).catch(() => {}); });
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
function checkWaitingForUser(agentDir, logMtime) {
|
|
@@ -210,8 +215,6 @@ app.use(express.static(path.join(__dirname, 'public')));
|
|
|
210
215
|
const messageCache = new Map();
|
|
211
216
|
const MESSAGE_CACHE_TTL = 5000;
|
|
212
217
|
const MAX_CACHE_ENTRIES = 200;
|
|
213
|
-
const progressMapCache = new Map();
|
|
214
|
-
const terminatedCache = new Map();
|
|
215
218
|
const compactSummaryCache = new Map();
|
|
216
219
|
const taskCountsCache = new Map();
|
|
217
220
|
const contextStatusCache = new Map();
|
|
@@ -325,12 +328,17 @@ function cachedByMtime(cache, cacheKey, filePath, loadFn, fallback) {
|
|
|
325
328
|
} catch (_) { return fallback; }
|
|
326
329
|
}
|
|
327
330
|
|
|
331
|
+
const sessionDigestCache = new Map();
|
|
332
|
+
function getSessionDigest(jsonlPath) {
|
|
333
|
+
return cachedByMtime(sessionDigestCache, jsonlPath, jsonlPath, () => buildSessionDigest(jsonlPath), { progressMap: {}, terminated: new Map() });
|
|
334
|
+
}
|
|
335
|
+
|
|
328
336
|
function getProgressMap(jsonlPath) {
|
|
329
|
-
return
|
|
337
|
+
return getSessionDigest(jsonlPath).progressMap;
|
|
330
338
|
}
|
|
331
339
|
|
|
332
340
|
function getTerminatedTeammates(jsonlPath) {
|
|
333
|
-
return
|
|
341
|
+
return getSessionDigest(jsonlPath).terminated;
|
|
334
342
|
}
|
|
335
343
|
|
|
336
344
|
function readRecentMessages(jsonlPath, limit = 10) {
|
|
@@ -965,10 +973,14 @@ app.post('/api/open-folder', (req, res) => {
|
|
|
965
973
|
}
|
|
966
974
|
});
|
|
967
975
|
|
|
968
|
-
// API: Open
|
|
976
|
+
// API: Open file in editor — either an existing path ({ file }) or content as a temp file ({ content, title })
|
|
969
977
|
app.post('/api/open-in-editor', (req, res) => {
|
|
970
978
|
try {
|
|
971
|
-
const { content, title } = req.body;
|
|
979
|
+
const { content, title, file } = req.body;
|
|
980
|
+
if (file) {
|
|
981
|
+
openInEditor(file);
|
|
982
|
+
return res.json({ success: true, path: file });
|
|
983
|
+
}
|
|
972
984
|
if (!content) return res.status(400).json({ error: 'No content provided' });
|
|
973
985
|
|
|
974
986
|
const safeName = (title || 'message').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 50);
|
|
@@ -1056,14 +1068,11 @@ app.get('/api/sessions/:sessionId/agents', (req, res) => {
|
|
|
1056
1068
|
} catch (_) {}
|
|
1057
1069
|
}
|
|
1058
1070
|
|
|
1059
|
-
|
|
1060
|
-
agent.prompt = prompt;
|
|
1061
|
-
persistAgent(agentDir, agent);
|
|
1062
|
-
}
|
|
1071
|
+
const dirty = new Set();
|
|
1063
1072
|
|
|
1064
|
-
const agentsNeedingPrompt = agents.filter(a => !a.prompt);
|
|
1065
|
-
const agentsNeedingName = agents.filter(a => !a.agentName);
|
|
1066
|
-
const agentsNeedingDesc = agents.filter(a => !a.description);
|
|
1073
|
+
const agentsNeedingPrompt = agents.filter(a => !a.prompt && !a.promptUnavailable);
|
|
1074
|
+
const agentsNeedingName = agents.filter(a => !a.agentName && !a.agentNameUnavailable);
|
|
1075
|
+
const agentsNeedingDesc = agents.filter(a => !a.description && !a.descriptionUnavailable);
|
|
1067
1076
|
if ((agentsNeedingPrompt.length || agentsNeedingName.length || agentsNeedingDesc.length) && meta.jsonlPath) {
|
|
1068
1077
|
let byAgentId = {};
|
|
1069
1078
|
let nameByAgentId = {};
|
|
@@ -1079,37 +1088,34 @@ app.get('/api/sessions/:sessionId/agents', (req, res) => {
|
|
|
1079
1088
|
for (const agent of agentsNeedingPrompt) {
|
|
1080
1089
|
const prompt = byAgentId[agent.agentId]
|
|
1081
1090
|
|| (() => { try { return extractPromptFromTranscript(subagentJsonlPath(meta, agent.agentId)); } catch (_) { return null; } })();
|
|
1082
|
-
if (prompt)
|
|
1091
|
+
if (prompt) agent.prompt = prompt;
|
|
1092
|
+
else agent.promptUnavailable = true;
|
|
1093
|
+
dirty.add(agent);
|
|
1083
1094
|
}
|
|
1084
1095
|
for (const agent of agentsNeedingName) {
|
|
1085
1096
|
if (nameByAgentId[agent.agentId]) agent.agentName = nameByAgentId[agent.agentId];
|
|
1097
|
+
else agent.agentNameUnavailable = true;
|
|
1098
|
+
dirty.add(agent);
|
|
1086
1099
|
}
|
|
1087
1100
|
for (const agent of agentsNeedingDesc) {
|
|
1088
1101
|
if (descByAgentId[agent.agentId]) agent.description = descByAgentId[agent.agentId];
|
|
1102
|
+
else agent.descriptionUnavailable = true;
|
|
1103
|
+
dirty.add(agent);
|
|
1089
1104
|
}
|
|
1090
1105
|
}
|
|
1091
1106
|
|
|
1092
|
-
const agentsNeedingModel = agents.filter(a => !a.model);
|
|
1107
|
+
const agentsNeedingModel = agents.filter(a => !a.model && !a.modelUnavailable);
|
|
1093
1108
|
if (agentsNeedingModel.length && meta.jsonlPath) {
|
|
1094
1109
|
for (const agent of agentsNeedingModel) {
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
try {
|
|
1101
|
-
const obj = JSON.parse(line);
|
|
1102
|
-
const model = obj.model || (obj.message && obj.message.model);
|
|
1103
|
-
if (model) {
|
|
1104
|
-
agent.model = model;
|
|
1105
|
-
persistAgent(agentDir, agent);
|
|
1106
|
-
break;
|
|
1107
|
-
}
|
|
1108
|
-
} catch (_) {}
|
|
1109
|
-
}
|
|
1110
|
-
} catch (_) {}
|
|
1110
|
+
let model = null;
|
|
1111
|
+
try { model = extractModelFromTranscript(subagentJsonlPath(meta, agent.agentId)); } catch (_) {}
|
|
1112
|
+
if (model) agent.model = model;
|
|
1113
|
+
else agent.modelUnavailable = true;
|
|
1114
|
+
dirty.add(agent);
|
|
1111
1115
|
}
|
|
1112
1116
|
}
|
|
1117
|
+
|
|
1118
|
+
for (const agent of dirty) persistAgent(agentDir, agent);
|
|
1113
1119
|
const teamColors = {};
|
|
1114
1120
|
if (teamConfig?.members) {
|
|
1115
1121
|
for (const m of teamConfig.members) {
|