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/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
- // Only remove team-named duplicate when leader is a different session
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 {