claude-code-kanban 5.3.0 → 5.4.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/README.md +2 -2
- package/cli.js +10 -5
- package/install.js +43 -123
- package/lib/approvals.js +16 -17
- package/lib/git-branch.js +47 -0
- package/lib/parsers.js +96 -59
- package/lib/plugin-status.js +1 -1
- package/lib/session-cache.js +1 -1
- package/lib/terminal.js +2 -1
- package/package.json +1 -2
- package/plugin/.claude-plugin/marketplace.json +1 -1
- package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +2 -2
- package/plugin/plugins/claude-code-kanban/hooks/activity.ts +266 -0
- package/plugin/plugins/claude-code-kanban/hooks/context.ts +77 -0
- package/plugin/plugins/claude-code-kanban/hooks/hooks.json +1 -93
- package/plugin/plugins/claude-code-kanban/hooks/index.ts +8 -0
- package/plugin/plugins/claude-code-kanban/scripts/postman.js +4 -4
- package/plugin/plugins/claude-code-kanban/tests/activity.test.ts +300 -0
- package/plugin/plugins/claude-code-kanban/tests/context.test.ts +72 -0
- package/plugin/plugins/claude-code-kanban/tsconfig.json +3 -0
- package/public/app.js +8 -8
- package/server.js +106 -95
- package/plugin/plugins/claude-code-kanban/scripts/agent-spy.sh +0 -135
- package/plugin/plugins/claude-code-kanban/scripts/approval-gate.sh +0 -208
- package/plugin/plugins/claude-code-kanban/scripts/context-status.sh +0 -18
package/lib/parsers.js
CHANGED
|
@@ -4,6 +4,21 @@ const path = require('node:path');
|
|
|
4
4
|
const os = require('node:os');
|
|
5
5
|
const { StringDecoder } = require('node:string_decoder');
|
|
6
6
|
|
|
7
|
+
// Full-transcript scans stream the file instead of reading it whole: a transcript can
|
|
8
|
+
// reach hundreds of megabytes, and the event loop it would block also carries the
|
|
9
|
+
// embedded terminal's keystrokes. Yields the same lines as content.split('\n').
|
|
10
|
+
async function* readLines(file) {
|
|
11
|
+
const decoder = new StringDecoder('utf8');
|
|
12
|
+
let rest = '';
|
|
13
|
+
for await (const chunk of fs.createReadStream(file, { highWaterMark: 1 << 20 })) {
|
|
14
|
+
const parts = decoder.write(chunk).split('\n');
|
|
15
|
+
parts[0] = rest + parts[0];
|
|
16
|
+
rest = parts.pop();
|
|
17
|
+
yield* parts;
|
|
18
|
+
}
|
|
19
|
+
yield rest + decoder.end();
|
|
20
|
+
}
|
|
21
|
+
|
|
7
22
|
function parseTask(raw) {
|
|
8
23
|
const task = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
|
9
24
|
return {
|
|
@@ -347,12 +362,24 @@ function scrapeScalarFromBlob(blob, re) {
|
|
|
347
362
|
const sessionInfoCache = new Map();
|
|
348
363
|
const SESSION_INFO_CACHE_MAX = 2000;
|
|
349
364
|
|
|
365
|
+
// A prompt line carries the mode it was sent in. After a Shift+Tab, Claude Code writes an
|
|
366
|
+
// auto_mode / auto_mode_exit attachment with the next tool result, and later rewrites a
|
|
367
|
+
// permission-mode line with the live mode, often tens of seconds after.
|
|
368
|
+
const modeOf = (data) => {
|
|
369
|
+
if (data.type === 'attachment') {
|
|
370
|
+
const t = data.attachment?.type;
|
|
371
|
+
return t === 'auto_mode' ? 'auto' : t === 'auto_mode_exit' ? 'default' : null;
|
|
372
|
+
}
|
|
373
|
+
return (data.type === 'user' || data.type === 'permission-mode') && data.permissionMode || null;
|
|
374
|
+
};
|
|
375
|
+
|
|
350
376
|
// gitBranch in the JSONL is pinned to the launch-time repo by Claude Code
|
|
351
377
|
// and goes stale once cwd shifts (Bash `cd`, submodule). Callers needing the
|
|
352
378
|
// live branch must resolve it from cwd separately. Cache is reset on inode
|
|
353
379
|
// change or truncation (size < scannedUpTo).
|
|
380
|
+
|
|
354
381
|
function readSessionInfoFromJsonl(jsonlPath) {
|
|
355
|
-
const result = { slug: null, projectPath: null, cwd: null, gitBranch: null, customTitle: null, logicalParentUuid: null, compactBoundaryUuid: null };
|
|
382
|
+
const result = { slug: null, projectPath: null, cwd: null, gitBranch: null, customTitle: null, logicalParentUuid: null, compactBoundaryUuid: null, permissionMode: null };
|
|
356
383
|
let stat;
|
|
357
384
|
let fd;
|
|
358
385
|
try {
|
|
@@ -372,6 +399,7 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
372
399
|
gitBranch: cached.gitBranch,
|
|
373
400
|
logicalParentUuid: cached.logicalParentUuid || null,
|
|
374
401
|
compactBoundaryUuid: cached.compactBoundaryUuid || null,
|
|
402
|
+
permissionMode: cached.permissionMode || null,
|
|
375
403
|
customTitle: readCustomTitle(jsonlPath, stat)
|
|
376
404
|
};
|
|
377
405
|
}
|
|
@@ -383,6 +411,7 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
383
411
|
result.gitBranch = cached.gitBranch;
|
|
384
412
|
result.logicalParentUuid = cached.logicalParentUuid || null;
|
|
385
413
|
result.compactBoundaryUuid = cached.compactBoundaryUuid || null;
|
|
414
|
+
result.permissionMode = cached.permissionMode || null;
|
|
386
415
|
}
|
|
387
416
|
|
|
388
417
|
let lastCwdSeen = result.cwd;
|
|
@@ -395,6 +424,8 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
395
424
|
lastCwdSeen = data.cwd;
|
|
396
425
|
}
|
|
397
426
|
if (data.gitBranch) result.gitBranch = data.gitBranch;
|
|
427
|
+
const mode = modeOf(data);
|
|
428
|
+
if (mode) result.permissionMode = mode;
|
|
398
429
|
if (data.subtype === 'compact_boundary' && data.logicalParentUuid && !result.logicalParentUuid) {
|
|
399
430
|
result.logicalParentUuid = data.logicalParentUuid;
|
|
400
431
|
// The boundary record's own uuid: a fork copies it verbatim from the
|
|
@@ -466,6 +497,7 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
466
497
|
const tn = fs.readSync(fd, tailBuf, 0, TAIL_SIZE, tailStart);
|
|
467
498
|
const lines = tailBuf.toString('utf8', 0, tn).split('\n');
|
|
468
499
|
let latestTailCwd = null;
|
|
500
|
+
let latestTailMode = null;
|
|
469
501
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
470
502
|
try {
|
|
471
503
|
const data = JSON.parse(lines[i]);
|
|
@@ -473,10 +505,12 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
473
505
|
if (!result.projectPath && data.cwd) result.projectPath = data.cwd;
|
|
474
506
|
if (!result.gitBranch && data.gitBranch) result.gitBranch = data.gitBranch;
|
|
475
507
|
if (!latestTailCwd && data.cwd) latestTailCwd = data.cwd;
|
|
476
|
-
if (
|
|
508
|
+
if (!latestTailMode) latestTailMode = modeOf(data);
|
|
509
|
+
if (latestTailCwd && latestTailMode && result.slug && result.projectPath && result.gitBranch) break;
|
|
477
510
|
} catch {}
|
|
478
511
|
}
|
|
479
512
|
if (latestTailCwd) lastCwdSeen = latestTailCwd;
|
|
513
|
+
if (latestTailMode) result.permissionMode = latestTailMode;
|
|
480
514
|
}
|
|
481
515
|
scannedUpTo = stat.size;
|
|
482
516
|
}
|
|
@@ -498,7 +532,8 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
498
532
|
gitBranch: result.gitBranch,
|
|
499
533
|
cwd: result.cwd,
|
|
500
534
|
logicalParentUuid: result.logicalParentUuid,
|
|
501
|
-
compactBoundaryUuid: result.compactBoundaryUuid
|
|
535
|
+
compactBoundaryUuid: result.compactBoundaryUuid,
|
|
536
|
+
permissionMode: result.permissionMode
|
|
502
537
|
});
|
|
503
538
|
if (sessionInfoCache.size > SESSION_INFO_CACHE_MAX) {
|
|
504
539
|
const firstKey = sessionInfoCache.keys().next().value;
|
|
@@ -1064,12 +1099,10 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
1064
1099
|
}
|
|
1065
1100
|
}
|
|
1066
1101
|
|
|
1067
|
-
function readFullToolResult(jsonlPath, toolUseId) {
|
|
1102
|
+
async function readFullToolResult(jsonlPath, toolUseId) {
|
|
1068
1103
|
if (!toolUseId || !jsonlPath || !existsSync(jsonlPath)) return null;
|
|
1069
1104
|
try {
|
|
1070
|
-
const
|
|
1071
|
-
const lines = content.split('\n');
|
|
1072
|
-
for (const line of lines) {
|
|
1105
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1073
1106
|
if (!line || line.indexOf(toolUseId) === -1) continue;
|
|
1074
1107
|
try {
|
|
1075
1108
|
const obj = JSON.parse(line);
|
|
@@ -1092,14 +1125,12 @@ function readFullToolResult(jsonlPath, toolUseId) {
|
|
|
1092
1125
|
return null;
|
|
1093
1126
|
}
|
|
1094
1127
|
|
|
1095
|
-
function readUserImage(jsonlPath, msgUuid, blockIndex) {
|
|
1128
|
+
async function readUserImage(jsonlPath, msgUuid, blockIndex) {
|
|
1096
1129
|
if (!msgUuid || !jsonlPath) return null;
|
|
1097
1130
|
const idx = Number(blockIndex);
|
|
1098
1131
|
if (!Number.isInteger(idx) || idx < 0) return null;
|
|
1099
1132
|
try {
|
|
1100
|
-
const
|
|
1101
|
-
const lines = content.split('\n');
|
|
1102
|
-
for (const line of lines) {
|
|
1133
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1103
1134
|
if (!line || line.indexOf(msgUuid) === -1) continue;
|
|
1104
1135
|
try {
|
|
1105
1136
|
const obj = JSON.parse(line);
|
|
@@ -1119,14 +1150,12 @@ function readUserImage(jsonlPath, msgUuid, blockIndex) {
|
|
|
1119
1150
|
|
|
1120
1151
|
// Read the Nth base64 image embedded in a tool_result block (e.g. the output of
|
|
1121
1152
|
// a Read on an image file). Scans for the tool_result matching toolUseId.
|
|
1122
|
-
function readToolResultImage(jsonlPath, toolUseId, n) {
|
|
1153
|
+
async function readToolResultImage(jsonlPath, toolUseId, n) {
|
|
1123
1154
|
if (!toolUseId || !jsonlPath) return null;
|
|
1124
1155
|
const idx = Number(n);
|
|
1125
1156
|
if (!Number.isInteger(idx) || idx < 0) return null;
|
|
1126
1157
|
try {
|
|
1127
|
-
const
|
|
1128
|
-
const lines = content.split('\n');
|
|
1129
|
-
for (const line of lines) {
|
|
1158
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1130
1159
|
if (!line || line.indexOf(toolUseId) === -1) continue;
|
|
1131
1160
|
let obj;
|
|
1132
1161
|
try { obj = JSON.parse(line); } catch (_) { continue; }
|
|
@@ -1196,14 +1225,13 @@ function readMessagesPage(jsonlPath, limit = 10, beforeTimestamp = null) {
|
|
|
1196
1225
|
};
|
|
1197
1226
|
}
|
|
1198
1227
|
|
|
1199
|
-
function buildSessionDigest(jsonlPath) {
|
|
1228
|
+
async function buildSessionDigest(jsonlPath) {
|
|
1200
1229
|
const map = {};
|
|
1201
1230
|
const terminated = new Map();
|
|
1202
1231
|
const rejectedToolUseIds = new Set();
|
|
1203
1232
|
const promptByToolUseId = {};
|
|
1204
1233
|
const killedAgentIds = new Set();
|
|
1205
1234
|
try {
|
|
1206
|
-
const content = readFileSync(jsonlPath, 'utf8');
|
|
1207
1235
|
const re = /"type":"agent_progress"[^}]*"agentId":"([^"]+)"/;
|
|
1208
1236
|
const parentRe = /"parentToolUseID":"([^"]+)"/;
|
|
1209
1237
|
const promptRe = /"prompt":"((?:[^"\\]|\\.)*)"/;
|
|
@@ -1215,7 +1243,7 @@ function buildSessionDigest(jsonlPath) {
|
|
|
1215
1243
|
const nameByToolUseId = {};
|
|
1216
1244
|
const descByToolUseId = {};
|
|
1217
1245
|
const usageByToolUseId = {};
|
|
1218
|
-
for (const line of
|
|
1246
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1219
1247
|
// Terminated-teammate detection: check first since cheap substring guards
|
|
1220
1248
|
if (line.includes('teammate-message') &&
|
|
1221
1249
|
(line.includes('teammate_terminated') || line.includes('shutdown_response'))) {
|
|
@@ -1346,8 +1374,8 @@ function buildSessionDigest(jsonlPath) {
|
|
|
1346
1374
|
return { progressMap: map, terminated, rejectedAgentIds, rejectedPrompts, killedAgentIds };
|
|
1347
1375
|
}
|
|
1348
1376
|
|
|
1349
|
-
function buildAgentProgressMap(jsonlPath) {
|
|
1350
|
-
return buildSessionDigest(jsonlPath).progressMap;
|
|
1377
|
+
async function buildAgentProgressMap(jsonlPath) {
|
|
1378
|
+
return (await buildSessionDigest(jsonlPath)).progressMap;
|
|
1351
1379
|
}
|
|
1352
1380
|
|
|
1353
1381
|
const ARTIFACT_URL_RE = /https:\/\/claude\.ai\/\S*artifact\/[A-Za-z0-9_-]+/;
|
|
@@ -1365,7 +1393,7 @@ function toolResultText(content) {
|
|
|
1365
1393
|
// only when its tool_use_id belongs to a publish call — an artifact URL quoted
|
|
1366
1394
|
// back by a grep or a file read would otherwise register another session's page as
|
|
1367
1395
|
// this session's own, and a `read` of someone else's artifact is not a publish.
|
|
1368
|
-
function readArtifactLinks(jsonlPath) {
|
|
1396
|
+
async function readArtifactLinks(jsonlPath) {
|
|
1369
1397
|
const byUrl = new Map();
|
|
1370
1398
|
const publishedFileByToolUseId = new Map();
|
|
1371
1399
|
const upsert = (url, ts) => {
|
|
@@ -1378,8 +1406,7 @@ function readArtifactLinks(jsonlPath) {
|
|
|
1378
1406
|
return a;
|
|
1379
1407
|
};
|
|
1380
1408
|
try {
|
|
1381
|
-
const
|
|
1382
|
-
for (const line of content.split('\n')) {
|
|
1409
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1383
1410
|
if (!line.includes('"frame-link"') && !line.includes('"name":"Artifact"') && !line.includes('claude.ai'))
|
|
1384
1411
|
continue;
|
|
1385
1412
|
let obj;
|
|
@@ -1426,11 +1453,10 @@ const SCRATCH_NAME_RE = /\bscratch\s+new\s+(?:"([^"]+)"|'([^']+)'|(?!-)(\S+))/;
|
|
|
1426
1453
|
// the manifest is the 4th of ~13 printed lines, so any `| tail -N` cuts it off. `name`
|
|
1427
1454
|
// stands in because it is what the manifest stores as its own `name`; the path is not
|
|
1428
1455
|
// recovered from the command, which would mean tracking the `cd` and a relative `--dir`.
|
|
1429
|
-
function readScratchpadCreations(jsonlPath) {
|
|
1456
|
+
async function readScratchpadCreations(jsonlPath) {
|
|
1430
1457
|
const calls = new Map();
|
|
1431
1458
|
try {
|
|
1432
|
-
const
|
|
1433
|
-
for (const line of content.split('\n')) {
|
|
1459
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1434
1460
|
// Substring reject before JSON.parse: transcripts reach tens of megabytes and
|
|
1435
1461
|
// only a handful of lines can ever match.
|
|
1436
1462
|
const isCall = line.includes('scratch new');
|
|
@@ -1456,13 +1482,12 @@ function readScratchpadCreations(jsonlPath) {
|
|
|
1456
1482
|
return [...calls.values()];
|
|
1457
1483
|
}
|
|
1458
1484
|
|
|
1459
|
-
function readCompactSummaries(jsonlPath) {
|
|
1485
|
+
async function readCompactSummaries(jsonlPath) {
|
|
1460
1486
|
const results = [];
|
|
1461
1487
|
// Inline format: newer Claude Code stores the summary directly in the parent
|
|
1462
1488
|
// session JSONL as a user message with isCompactSummary: true (no subagent file).
|
|
1463
1489
|
try {
|
|
1464
|
-
const
|
|
1465
|
-
for (const line of content.split('\n')) {
|
|
1490
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1466
1491
|
if (!line.trim() || !line.includes('isCompactSummary')) continue;
|
|
1467
1492
|
try {
|
|
1468
1493
|
const obj = JSON.parse(line);
|
|
@@ -1515,8 +1540,8 @@ function readCompactSummaries(jsonlPath) {
|
|
|
1515
1540
|
return results.sort((a, b) => (a.timestamp || '').localeCompare(b.timestamp || ''));
|
|
1516
1541
|
}
|
|
1517
1542
|
|
|
1518
|
-
function findTerminatedTeammates(jsonlPath) {
|
|
1519
|
-
return buildSessionDigest(jsonlPath).terminated;
|
|
1543
|
+
async function findTerminatedTeammates(jsonlPath) {
|
|
1544
|
+
return (await buildSessionDigest(jsonlPath)).terminated;
|
|
1520
1545
|
}
|
|
1521
1546
|
|
|
1522
1547
|
function extractPromptFromTranscript(jsonlPath) {
|
|
@@ -1585,28 +1610,28 @@ function extractModelFromTranscript(jsonlPath) {
|
|
|
1585
1610
|
// output tokens, and first/last timestamps (for duration). COLD PATH ONLY —
|
|
1586
1611
|
// used by the workflow run view on demand, never from the session-scan hot
|
|
1587
1612
|
// path (a full read per agent would blow the /api/sessions budget).
|
|
1588
|
-
function extractTranscriptStats(jsonlPath) {
|
|
1589
|
-
let content;
|
|
1590
|
-
try { content = readFileSync(jsonlPath, 'utf8'); } catch (_) { return null; }
|
|
1613
|
+
async function extractTranscriptStats(jsonlPath) {
|
|
1591
1614
|
let model = null;
|
|
1592
1615
|
let outputTokens = 0;
|
|
1593
1616
|
let firstTs = null;
|
|
1594
1617
|
let lastTs = null;
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
if (
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
if (
|
|
1606
|
-
|
|
1607
|
-
|
|
1618
|
+
try {
|
|
1619
|
+
for await (const line of readLines(jsonlPath)) {
|
|
1620
|
+
if (!line.trim()) continue;
|
|
1621
|
+
let obj;
|
|
1622
|
+
try { obj = JSON.parse(line); } catch (_) { continue; }
|
|
1623
|
+
if (obj.timestamp) {
|
|
1624
|
+
if (!firstTs) firstTs = obj.timestamp;
|
|
1625
|
+
lastTs = obj.timestamp;
|
|
1626
|
+
}
|
|
1627
|
+
const msg = obj.message;
|
|
1628
|
+
if (msg && typeof msg === 'object') {
|
|
1629
|
+
if (!model && msg.model) model = msg.model;
|
|
1630
|
+
const out = msg.usage?.output_tokens;
|
|
1631
|
+
if (typeof out === 'number') outputTokens += out;
|
|
1632
|
+
}
|
|
1608
1633
|
}
|
|
1609
|
-
}
|
|
1634
|
+
} catch (_) { return null; }
|
|
1610
1635
|
return { model, outputTokens, firstTs, lastTs };
|
|
1611
1636
|
}
|
|
1612
1637
|
|
|
@@ -1625,7 +1650,9 @@ function formatStructuredResult(input) {
|
|
|
1625
1650
|
// Some subagents end their run on a tool call and never emit a text response, so
|
|
1626
1651
|
// their lastMessage is empty: workflow subagents given a schema call StructuredOutput,
|
|
1627
1652
|
// background subagents report through SubagentHandback. Tail-read the transcript and
|
|
1628
|
-
// take the LAST such tool_use as the agent's result.
|
|
1653
|
+
// take the LAST such tool_use as the agent's result. Without one, take the last
|
|
1654
|
+
// assistant text: the plugin's mod records no lastMessage, because Claude Code's
|
|
1655
|
+
// turn.complete carries an empty answer for a subagent. Returns null when neither exists.
|
|
1629
1656
|
const RESULT_TOOLS = {
|
|
1630
1657
|
StructuredOutput: formatStructuredResult,
|
|
1631
1658
|
SubagentHandback: (input) => (typeof input.message === 'string' && input.message) || null,
|
|
@@ -1649,16 +1676,25 @@ function extractAgentResultFromTranscript(jsonlPath) {
|
|
|
1649
1676
|
const text = buf.toString('utf8');
|
|
1650
1677
|
// Drop the leading partial line unless we read from the very start.
|
|
1651
1678
|
const clean = start > 0 ? text.slice(text.indexOf('\n') + 1) : text;
|
|
1652
|
-
if (!RESULT_TOOL_TEXT.some((t) => clean.includes(t))) return null;
|
|
1653
1679
|
const lines = clean.split('\n');
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1680
|
+
const lastAssistant = (needle, pick) => {
|
|
1681
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
1682
|
+
if (!needle(lines[i])) continue;
|
|
1683
|
+
let obj;
|
|
1684
|
+
try { obj = JSON.parse(lines[i]); } catch (_) { continue; }
|
|
1685
|
+
const content = obj?.message?.content;
|
|
1686
|
+
const found = obj?.type === 'assistant' && Array.isArray(content) && pick(content);
|
|
1687
|
+
if (found) return found;
|
|
1688
|
+
}
|
|
1689
|
+
return null;
|
|
1690
|
+
};
|
|
1691
|
+
return lastAssistant(
|
|
1692
|
+
(line) => RESULT_TOOL_TEXT.some((t) => line.includes(t)),
|
|
1693
|
+
(content) => content.map(resultFromBlock).findLast(Boolean),
|
|
1694
|
+
) ?? lastAssistant(
|
|
1695
|
+
(line) => line.includes('"type":"assistant"'),
|
|
1696
|
+
(content) => toolResultText(content).trim(),
|
|
1697
|
+
);
|
|
1662
1698
|
} catch (_) {
|
|
1663
1699
|
} finally {
|
|
1664
1700
|
closeSync(fd);
|
|
@@ -1831,5 +1867,6 @@ module.exports = {
|
|
|
1831
1867
|
extractPromptFromTranscript,
|
|
1832
1868
|
extractModelFromTranscript,
|
|
1833
1869
|
extractAgentResultFromTranscript,
|
|
1834
|
-
extractTranscriptStats
|
|
1870
|
+
extractTranscriptStats,
|
|
1871
|
+
readLines
|
|
1835
1872
|
};
|
package/lib/plugin-status.js
CHANGED
|
@@ -31,7 +31,7 @@ function pluginStatus(claudeDir, bundledManifest = BUNDLED_MANIFEST) {
|
|
|
31
31
|
|
|
32
32
|
// --dir rather than CLAUDE_CONFIG_DIR=…, so the same line runs in bash, PowerShell and cmd.
|
|
33
33
|
function installCommand(claudeDir) {
|
|
34
|
-
const base = 'claude-code-kanban --install --
|
|
34
|
+
const base = 'claude-code-kanban --install --yes';
|
|
35
35
|
return isDefaultClaudeDir(claudeDir) ? base : `${base} --dir "${claudeDir}"`;
|
|
36
36
|
}
|
|
37
37
|
|
package/lib/session-cache.js
CHANGED
|
@@ -4,7 +4,7 @@ const { exportParentVerdicts, importParentVerdicts, parentVerdictsDirty } = requ
|
|
|
4
4
|
const { exportTaskCounts, importTaskCounts, taskCountsDirty } = require('./task-counts');
|
|
5
5
|
|
|
6
6
|
// Bump when an entry's shape or meaning changes: a file with another version is ignored.
|
|
7
|
-
const VERSION =
|
|
7
|
+
const VERSION = 3;
|
|
8
8
|
// A cache this large is not one cck wrote for a normal set of transcripts. Rebuild it cold.
|
|
9
9
|
const MAX_BYTES = 8 * 1024 * 1024;
|
|
10
10
|
|
package/lib/terminal.js
CHANGED
|
@@ -552,8 +552,9 @@ function createTerminalService(o) {
|
|
|
552
552
|
const cols = clampDim(msg.cols, 80);
|
|
553
553
|
const rows = clampDim(msg.rows, 24);
|
|
554
554
|
|
|
555
|
+
// An ended PTY stays in the map until its process exits; a hello for its id starts a new one.
|
|
555
556
|
const existing = sessions.get(id);
|
|
556
|
-
if (existing) return attach(ws, existing, true);
|
|
557
|
+
if (existing && !existing.ended) return attach(ws, existing, true);
|
|
557
558
|
|
|
558
559
|
if (mode === 'auto' && o.isLiveElsewhere(id)) {
|
|
559
560
|
send(ws, { t: 'live' });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-kanban",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.4.0",
|
|
4
4
|
"description": "A web-based Kanban board for viewing Claude Code tasks with agent teams support",
|
|
5
5
|
"main": "server.js",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
"start": "node server.js",
|
|
12
12
|
"dev": "node --watch server.js",
|
|
13
13
|
"test": "node --test test/*.test.js",
|
|
14
|
-
"test:hooks": "bash tests/test-agent-spy.sh && bash tests/test-approval-gate.sh",
|
|
15
14
|
"validate:schemas": "node test/validate-live-schemas.js",
|
|
16
15
|
"prepare": "husky"
|
|
17
16
|
},
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
{
|
|
8
8
|
"name": "claude-code-kanban",
|
|
9
9
|
"source": "./plugins/claude-code-kanban",
|
|
10
|
-
"description": "claude-code-kanban dashboard integration: agent activity tracking, context
|
|
10
|
+
"description": "claude-code-kanban dashboard integration: agent activity tracking, context and cost tracking, and a skill to drive the board from a session"
|
|
11
11
|
}
|
|
12
12
|
]
|
|
13
13
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-code-kanban",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "claude-code-kanban dashboard integration: agent activity tracking, context
|
|
3
|
+
"version": "2.21.4",
|
|
4
|
+
"description": "claude-code-kanban dashboard integration: agent activity tracking, context and cost tracking, skills to drive the board from a session and to follow it",
|
|
5
5
|
"experimental": {
|
|
6
6
|
"monitors": "./monitors.json"
|
|
7
7
|
}
|