claude-code-kanban 4.23.1 → 4.23.2
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 +32 -19
- package/package.json +1 -1
- package/public/app.js +1 -3
- package/server.js +13 -9
package/lib/parsers.js
CHANGED
|
@@ -160,6 +160,17 @@ function formatTaskUsage(usage) {
|
|
|
160
160
|
return parts.length ? ' · ' + parts.join(' · ') : '';
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
+
// Raw per-agent cost for the agent modal's chips plus the formatted one-liner for
|
|
164
|
+
// the inline message row. Null when the source carried no numbers.
|
|
165
|
+
function agentUsageEntry(subagentTokens, toolUses, durationMs) {
|
|
166
|
+
const usage = {};
|
|
167
|
+
if (subagentTokens != null) usage.tokens = subagentTokens;
|
|
168
|
+
if (toolUses != null) usage.tools = toolUses;
|
|
169
|
+
if (durationMs != null) usage.durationMs = durationMs;
|
|
170
|
+
if (!Object.keys(usage).length) return null;
|
|
171
|
+
return { usage, usageText: formatTaskUsage({ subagentTokens, toolUses, durationMs }) };
|
|
172
|
+
}
|
|
173
|
+
|
|
163
174
|
function pushUserMessage(messages, text, timestamp, sysLabel, extras) {
|
|
164
175
|
if (sysLabel === '__skip__') return;
|
|
165
176
|
let safeText = text || '';
|
|
@@ -1099,6 +1110,7 @@ function buildSessionDigest(jsonlPath) {
|
|
|
1099
1110
|
const taskIdRe = /<task-id>([a-zA-Z0-9_-]+)<\/task-id>/;
|
|
1100
1111
|
const nameByToolUseId = {};
|
|
1101
1112
|
const descByToolUseId = {};
|
|
1113
|
+
const usageByToolUseId = {};
|
|
1102
1114
|
for (const line of content.split('\n')) {
|
|
1103
1115
|
// Terminated-teammate detection: check first since cheap substring guards
|
|
1104
1116
|
if (line.includes('teammate-message') &&
|
|
@@ -1171,34 +1183,34 @@ function buildSessionDigest(jsonlPath) {
|
|
|
1171
1183
|
} else if (line.includes('User rejected tool use') && line.includes('"tool_use_id"')) {
|
|
1172
1184
|
const m = tmToolIdRe.exec(line);
|
|
1173
1185
|
if (m) rejectedToolUseIds.add(m[1]);
|
|
1174
|
-
} else if (line.includes('<task-notification>')
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1186
|
+
} else if (line.includes('<task-notification>')) {
|
|
1187
|
+
// The envelope's tags survive JSON escaping, so read them off the raw line.
|
|
1188
|
+
// This covers every delivery shape (user message, queue-operation,
|
|
1189
|
+
// queued_command attachment) without enumerating them.
|
|
1190
|
+
if (line.includes('<status>killed</status>') || line.includes('<status>error</status>')) {
|
|
1191
|
+
const idMatch = taskIdRe.exec(line);
|
|
1192
|
+
if (idMatch) killedAgentIds.add(idMatch[1]);
|
|
1193
|
+
}
|
|
1194
|
+
if (line.includes('<subagent_tokens>')) {
|
|
1195
|
+
const notif = parseTaskNotification(line);
|
|
1196
|
+
const u = notif?.usage;
|
|
1197
|
+
const entry = u && notif.toolUseId ? agentUsageEntry(u.subagentTokens, u.toolUses, u.durationMs) : null;
|
|
1198
|
+
if (entry) usageByToolUseId[notif.toolUseId] = entry;
|
|
1199
|
+
}
|
|
1178
1200
|
} else if (line.includes('"toolUseResult"') && line.includes('"agentId"') && line.includes('"tool_result"')) {
|
|
1179
1201
|
try {
|
|
1180
1202
|
const obj = JSON.parse(line);
|
|
1181
1203
|
const tur = obj.toolUseResult;
|
|
1182
1204
|
if (tur?.agentId) {
|
|
1183
|
-
// Foreground agents report their cost on the completion toolUseResult
|
|
1184
|
-
//
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
// one-liner via the shared formatter (for the inline message row):
|
|
1188
|
-
// " · 31.5k tok · 5 tools · 34s".
|
|
1189
|
-
const usage = {};
|
|
1190
|
-
if (tur.totalTokens != null) usage.tokens = tur.totalTokens;
|
|
1191
|
-
if (tur.totalToolUseCount != null) usage.tools = tur.totalToolUseCount;
|
|
1192
|
-
if (tur.totalDurationMs != null) usage.durationMs = tur.totalDurationMs;
|
|
1193
|
-
const hasUsage = Object.keys(usage).length > 0;
|
|
1194
|
-
const usageText = hasUsage
|
|
1195
|
-
? formatTaskUsage({ subagentTokens: usage.tokens, toolUses: usage.tools, durationMs: usage.durationMs })
|
|
1196
|
-
: null;
|
|
1205
|
+
// Foreground agents report their cost on the completion toolUseResult;
|
|
1206
|
+
// background agents report the same numbers in their <task-notification>.
|
|
1207
|
+
const { usage, usageText } = agentUsageEntry(tur.totalTokens, tur.totalToolUseCount, tur.totalDurationMs)
|
|
1208
|
+
|| { usage: null, usageText: null };
|
|
1197
1209
|
const blocks = obj.message?.content;
|
|
1198
1210
|
if (Array.isArray(blocks)) {
|
|
1199
1211
|
for (const b of blocks) {
|
|
1200
1212
|
if (b.type === 'tool_result' && b.tool_use_id && !map[b.tool_use_id]) {
|
|
1201
|
-
map[b.tool_use_id] = { agentId: tur.agentId, prompt: tur.prompt || null, usage
|
|
1213
|
+
map[b.tool_use_id] = { agentId: tur.agentId, prompt: tur.prompt || null, usage, usageText };
|
|
1202
1214
|
}
|
|
1203
1215
|
}
|
|
1204
1216
|
}
|
|
@@ -1207,6 +1219,7 @@ function buildSessionDigest(jsonlPath) {
|
|
|
1207
1219
|
}
|
|
1208
1220
|
}
|
|
1209
1221
|
for (const [key, entry] of Object.entries(map)) {
|
|
1222
|
+
if (!entry.usage && usageByToolUseId[key]) Object.assign(entry, usageByToolUseId[key]);
|
|
1210
1223
|
if (nameByToolUseId[key]) entry.name = nameByToolUseId[key];
|
|
1211
1224
|
if (descByToolUseId[key]) entry.description = descByToolUseId[key];
|
|
1212
1225
|
// Prefer the full prompt from the assistant's tool_use input — Claude Code's
|
package/package.json
CHANGED
package/public/app.js
CHANGED
|
@@ -2883,10 +2883,8 @@ function showAgentModal(agentId) {
|
|
|
2883
2883
|
return `<span class="agent-chip${cls}"${style}${title}>${labelHtml}<span class="agent-chip-val">${value}</span></span>`;
|
|
2884
2884
|
};
|
|
2885
2885
|
|
|
2886
|
-
// Agent cost stats live on the launch row's message (resolved from the completion
|
|
2887
|
-
// toolUseResult), not on the agent-activity record — so read them from there.
|
|
2888
2886
|
const agentMsg = currentMessages.find((m) => m.tool === 'Agent' && m.agentId === agentId);
|
|
2889
|
-
const usageStats =
|
|
2887
|
+
const usageStats = agent.usage || null;
|
|
2890
2888
|
const fmtTok = fmtTokens;
|
|
2891
2889
|
|
|
2892
2890
|
const chips = [];
|
package/server.js
CHANGED
|
@@ -2007,16 +2007,16 @@ app.get('/api/sessions/:sessionId/agents', (req, res) => {
|
|
|
2007
2007
|
// (same pattern as agentsNeedingModel below). Each field shares the same resolve flow:
|
|
2008
2008
|
// look up in progressMap by agentId, fall back to per-field extractor, persist only
|
|
2009
2009
|
// on actual change.
|
|
2010
|
+
// progressMap is keyed by tool_use_id; re-key by agentId, first value per field wins.
|
|
2010
2011
|
const byAgentId = {};
|
|
2011
|
-
const nameByAgentId = {};
|
|
2012
|
-
const descByAgentId = {};
|
|
2013
2012
|
if (meta.jsonlPath) {
|
|
2014
2013
|
try {
|
|
2015
2014
|
const progressMap = getProgressMap(meta.jsonlPath);
|
|
2016
2015
|
for (const entry of Object.values(progressMap)) {
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2016
|
+
const e = (byAgentId[entry.agentId] ||= {});
|
|
2017
|
+
for (const f of ['prompt', 'name', 'description', 'usage']) {
|
|
2018
|
+
if (entry[f] && !e[f]) e[f] = entry[f];
|
|
2019
|
+
}
|
|
2020
2020
|
}
|
|
2021
2021
|
} catch (_) {}
|
|
2022
2022
|
}
|
|
@@ -2025,12 +2025,12 @@ app.get('/api/sessions/:sessionId/agents', (req, res) => {
|
|
|
2025
2025
|
field: 'prompt',
|
|
2026
2026
|
flag: 'promptUnavailable',
|
|
2027
2027
|
lookup: (a) => {
|
|
2028
|
-
if (byAgentId[a.agentId]) return byAgentId[a.agentId];
|
|
2028
|
+
if (byAgentId[a.agentId]?.prompt) return byAgentId[a.agentId].prompt;
|
|
2029
2029
|
try { return extractPromptFromTranscript(subagentJsonlForExtraction(meta, a.agentId)); } catch (_) { return null; }
|
|
2030
2030
|
},
|
|
2031
2031
|
},
|
|
2032
|
-
{ field: 'agentName', flag: 'agentNameUnavailable', lookup: (a) =>
|
|
2033
|
-
{ field: 'description', flag: 'descriptionUnavailable', lookup: (a) =>
|
|
2032
|
+
{ field: 'agentName', flag: 'agentNameUnavailable', lookup: (a) => byAgentId[a.agentId]?.name || null },
|
|
2033
|
+
{ field: 'description', flag: 'descriptionUnavailable', lookup: (a) => byAgentId[a.agentId]?.description || null },
|
|
2034
2034
|
];
|
|
2035
2035
|
if (meta.jsonlPath) {
|
|
2036
2036
|
for (const { field, flag, lookup } of reconcileFields) {
|
|
@@ -2133,6 +2133,11 @@ app.get('/api/sessions/:sessionId/agents', (req, res) => {
|
|
|
2133
2133
|
}
|
|
2134
2134
|
|
|
2135
2135
|
const waitingForUser = checkWaitingForUser(agentDir, logMtime);
|
|
2136
|
+
// Attached after persistAgent so it stays response-only.
|
|
2137
|
+
for (const agent of visibleAgents) {
|
|
2138
|
+
const usage = byAgentId[agent.agentId]?.usage;
|
|
2139
|
+
if (usage) agent.usage = usage;
|
|
2140
|
+
}
|
|
2136
2141
|
res.json({ agents: visibleAgents, waitingForUser, teamColors });
|
|
2137
2142
|
} catch {
|
|
2138
2143
|
res.json({ agents: [], waitingForUser: null });
|
|
@@ -2499,7 +2504,6 @@ app.get('/api/sessions/:sessionId/messages', (req, res) => {
|
|
|
2499
2504
|
msg.agentId = entry.agentId;
|
|
2500
2505
|
if (entry.description) msg.agentDescription = entry.description;
|
|
2501
2506
|
if (entry.usageText) msg.agentUsage = entry.usageText;
|
|
2502
|
-
if (entry.usage) msg.agentUsageStats = entry.usage;
|
|
2503
2507
|
if (entry.prompt && !msg.agentPrompt) msg.agentPrompt = entry.prompt;
|
|
2504
2508
|
try {
|
|
2505
2509
|
const agentFile = path.join(agentDir, `${entry.agentId}.jsonl`);
|