claude-code-kanban 4.8.0 → 4.10.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/package.json +1 -1
- package/public/app.js +87 -213
- package/public/index.html +16 -54
- package/public/style.css +116 -88
- package/public/themes.css +823 -0
- package/server.js +25 -42
package/server.js
CHANGED
|
@@ -270,6 +270,20 @@ function resolveSessionId(sessionId) {
|
|
|
270
270
|
return (teamConfig && teamConfig.leadSessionId) ? teamConfig.leadSessionId : sessionId;
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
+
// Recent Claude Code releases auto-create a single-member "self-team" for every
|
|
274
|
+
// session: a teams/session-<id>/config.json whose only member is the "team-lead"
|
|
275
|
+
// (the session itself). These are not real multi-agent teams — surfacing them
|
|
276
|
+
// makes every solo session render a team badge, member panel, and (via the empty
|
|
277
|
+
// team-named task dir) a shared-task-list link. Treat them as plain sessions.
|
|
278
|
+
// As soon as a real teammate joins (members.length > 1) it becomes a true team again.
|
|
279
|
+
function isAutoSelfTeam(cfg) {
|
|
280
|
+
if (!cfg || !Array.isArray(cfg.members)) return false;
|
|
281
|
+
const namedSession = typeof cfg.name === 'string' && cfg.name.startsWith('session-');
|
|
282
|
+
const soleLead = cfg.members.length === 0
|
|
283
|
+
|| (cfg.members.length === 1 && cfg.members[0]?.agentType === 'team-lead');
|
|
284
|
+
return namedSession && soleLead;
|
|
285
|
+
}
|
|
286
|
+
|
|
273
287
|
// SSE clients for live updates
|
|
274
288
|
const clients = new Set();
|
|
275
289
|
|
|
@@ -931,8 +945,11 @@ app.get('/api/sessions', async (req, res) => {
|
|
|
931
945
|
const cfg = loadTeamConfig(dir.name);
|
|
932
946
|
if (!cfg?.leadSessionId) continue;
|
|
933
947
|
const leaderId = cfg.leadSessionId;
|
|
934
|
-
//
|
|
948
|
+
// Remove the team-named duplicate before bailing on self-teams. Otherwise an
|
|
949
|
+
// auto-created session-<uuid> self-team dir leaves a duplicate session card whose
|
|
950
|
+
// id (session-<uuid>) resolves no messages, so switching to it shows a stale log.
|
|
935
951
|
if (sessionsMap.has(dir.name) && dir.name !== leaderId) sessionsMap.delete(dir.name);
|
|
952
|
+
if (isAutoSelfTeam(cfg)) continue;
|
|
936
953
|
const existing = sessionsMap.get(leaderId);
|
|
937
954
|
if (existing) {
|
|
938
955
|
existing.isTeam = true;
|
|
@@ -1170,7 +1187,7 @@ app.get('/api/projects/:encodedPath/tasks', (req, res) => {
|
|
|
1170
1187
|
app.get('/api/sessions/:sessionId/plan', async (req, res) => {
|
|
1171
1188
|
try {
|
|
1172
1189
|
const metadata = loadSessionMetadata();
|
|
1173
|
-
const meta = metadata[req.params.sessionId];
|
|
1190
|
+
const meta = metadata[req.params.sessionId] || metadata[resolveSessionId(req.params.sessionId)];
|
|
1174
1191
|
const slug = meta?.slug;
|
|
1175
1192
|
if (!slug) return res.status(404).json({ error: 'No plan found' });
|
|
1176
1193
|
|
|
@@ -1188,7 +1205,7 @@ app.get('/api/sessions/:sessionId/plan', async (req, res) => {
|
|
|
1188
1205
|
app.get('/api/sessions/:sessionId/loop', (req, res) => {
|
|
1189
1206
|
try {
|
|
1190
1207
|
const metadata = loadSessionMetadata();
|
|
1191
|
-
const meta = metadata[req.params.sessionId];
|
|
1208
|
+
const meta = metadata[req.params.sessionId] || metadata[resolveSessionId(req.params.sessionId)];
|
|
1192
1209
|
if (!meta?.jsonlPath) return res.json({ wakeups: [], crons: [] });
|
|
1193
1210
|
const state = refreshLoopInfoState(meta.jsonlPath);
|
|
1194
1211
|
const filtered = filterActiveLoopInfo(buildLoopInfoFromState(state));
|
|
@@ -1211,7 +1228,7 @@ function openInEditor(...targets) {
|
|
|
1211
1228
|
app.post('/api/sessions/:sessionId/plan/open', (req, res) => {
|
|
1212
1229
|
try {
|
|
1213
1230
|
const metadata = loadSessionMetadata();
|
|
1214
|
-
const meta = metadata[req.params.sessionId];
|
|
1231
|
+
const meta = metadata[req.params.sessionId] || metadata[resolveSessionId(req.params.sessionId)];
|
|
1215
1232
|
const slug = meta?.slug;
|
|
1216
1233
|
if (!slug) return res.status(404).json({ error: 'No plan found' });
|
|
1217
1234
|
|
|
@@ -1725,7 +1742,7 @@ app.get('/api/sessions/:sessionId/messages', (req, res) => {
|
|
|
1725
1742
|
const limit = Math.min(parseInt(req.query.limit, 10) || 10, 50);
|
|
1726
1743
|
const before = req.query.before || null;
|
|
1727
1744
|
const metadata = loadSessionMetadata();
|
|
1728
|
-
const meta = metadata[req.params.sessionId];
|
|
1745
|
+
const meta = metadata[req.params.sessionId] || metadata[resolveSessionId(req.params.sessionId)];
|
|
1729
1746
|
const jsonlPath = meta?.jsonlPath;
|
|
1730
1747
|
if (!jsonlPath) return res.json({ messages: [], hasMore: false, sessionId: req.params.sessionId });
|
|
1731
1748
|
let messages, hasMore;
|
|
@@ -1820,7 +1837,7 @@ app.get('/api/sessions/:sessionId/messages', (req, res) => {
|
|
|
1820
1837
|
|
|
1821
1838
|
app.get('/api/sessions/:sessionId/tool-result/:toolUseId', (req, res) => {
|
|
1822
1839
|
const metadata = loadSessionMetadata();
|
|
1823
|
-
const meta = metadata[req.params.sessionId];
|
|
1840
|
+
const meta = metadata[req.params.sessionId] || metadata[resolveSessionId(req.params.sessionId)];
|
|
1824
1841
|
const jsonlPath = meta?.jsonlPath;
|
|
1825
1842
|
if (!jsonlPath) return res.status(404).json({ error: 'session not found' });
|
|
1826
1843
|
const content = readFullToolResult(jsonlPath, req.params.toolUseId);
|
|
@@ -1927,7 +1944,7 @@ function buildToolStats(jsonlPath) {
|
|
|
1927
1944
|
|
|
1928
1945
|
app.get('/api/sessions/:sessionId/tool-stats', (req, res) => {
|
|
1929
1946
|
const metadata = loadSessionMetadata();
|
|
1930
|
-
const meta = metadata[req.params.sessionId];
|
|
1947
|
+
const meta = metadata[req.params.sessionId] || metadata[resolveSessionId(req.params.sessionId)];
|
|
1931
1948
|
const jsonlPath = meta?.jsonlPath;
|
|
1932
1949
|
if (!jsonlPath) return res.status(404).json({ error: 'session not found' });
|
|
1933
1950
|
try {
|
|
@@ -1941,7 +1958,7 @@ app.get('/api/sessions/:sessionId/tool-stats', (req, res) => {
|
|
|
1941
1958
|
|
|
1942
1959
|
app.get('/api/sessions/:sessionId/user-image/:msgUuid/:blockIndex', (req, res) => {
|
|
1943
1960
|
const metadata = loadSessionMetadata();
|
|
1944
|
-
const meta = metadata[req.params.sessionId];
|
|
1961
|
+
const meta = metadata[req.params.sessionId] || metadata[resolveSessionId(req.params.sessionId)];
|
|
1945
1962
|
const jsonlPath = meta?.jsonlPath;
|
|
1946
1963
|
if (!jsonlPath) return res.status(404).end();
|
|
1947
1964
|
const img = readUserImage(jsonlPath, req.params.msgUuid, req.params.blockIndex);
|
|
@@ -2023,40 +2040,6 @@ app.get('/api/tasks/all', async (req, res) => {
|
|
|
2023
2040
|
}
|
|
2024
2041
|
});
|
|
2025
2042
|
|
|
2026
|
-
// API: Add note to a task
|
|
2027
|
-
app.post('/api/tasks/:sessionId/:taskId/note', async (req, res) => {
|
|
2028
|
-
try {
|
|
2029
|
-
const { sessionId, taskId } = req.params;
|
|
2030
|
-
const { note } = req.body;
|
|
2031
|
-
|
|
2032
|
-
if (!note || !note.trim()) {
|
|
2033
|
-
return res.status(400).json({ error: 'Note cannot be empty' });
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
const sessionDir = getCustomTaskDir(sessionId) || path.join(TASKS_DIR, sessionId);
|
|
2037
|
-
const taskPath = path.join(sessionDir, `${taskId}.json`);
|
|
2038
|
-
|
|
2039
|
-
if (!existsSync(taskPath)) {
|
|
2040
|
-
return res.status(404).json({ error: 'Task not found' });
|
|
2041
|
-
}
|
|
2042
|
-
|
|
2043
|
-
// Read current task
|
|
2044
|
-
const task = JSON.parse(await fs.readFile(taskPath, 'utf8'));
|
|
2045
|
-
|
|
2046
|
-
// Append note to description
|
|
2047
|
-
const noteBlock = `\n\n---\n\n#### [Note added by user]\n\n${note.trim()}`;
|
|
2048
|
-
task.description = (task.description || '') + noteBlock;
|
|
2049
|
-
|
|
2050
|
-
// Write updated task
|
|
2051
|
-
await fs.writeFile(taskPath, JSON.stringify(task, null, 2));
|
|
2052
|
-
|
|
2053
|
-
res.json({ success: true, task });
|
|
2054
|
-
} catch (error) {
|
|
2055
|
-
console.error('Error adding note:', error);
|
|
2056
|
-
res.status(500).json({ error: 'Failed to add note' });
|
|
2057
|
-
}
|
|
2058
|
-
});
|
|
2059
|
-
|
|
2060
2043
|
// API: Update task fields (subject, description)
|
|
2061
2044
|
app.put('/api/tasks/:sessionId/:taskId', async (req, res) => {
|
|
2062
2045
|
try {
|