claude-code-kanban 4.4.0 → 4.6.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 +171 -15
- package/package.json +1 -1
- package/plugin/plugins/claude-code-kanban/.claude-plugin/plugin.json +1 -1
- package/plugin/plugins/claude-code-kanban/scripts/agent-spy.sh +1 -1
- package/public/app.js +278 -56
- package/public/style.css +212 -81
- package/server.js +164 -59
package/lib/parsers.js
CHANGED
|
@@ -107,6 +107,15 @@ function parseJsonlLine(line) {
|
|
|
107
107
|
};
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
if (obj.type === 'queue-operation' && obj.operation === 'enqueue') {
|
|
111
|
+
return {
|
|
112
|
+
...base,
|
|
113
|
+
role: 'user',
|
|
114
|
+
queued: true,
|
|
115
|
+
content: typeof obj.content === 'string' ? obj.content : null
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
110
119
|
if (obj.type === 'progress') {
|
|
111
120
|
return { ...base, cwd: obj.cwd || null, version: obj.version || null, slug: obj.slug || null };
|
|
112
121
|
}
|
|
@@ -118,6 +127,25 @@ const TOOL_RESULT_MAX = 1500;
|
|
|
118
127
|
const USER_TEXT_MAX = 500;
|
|
119
128
|
const INTERRUPT_MARKER = '[Request interrupted by user]';
|
|
120
129
|
|
|
130
|
+
// Newer Claude Code no longer inlines pasted images as base64 blocks. It writes
|
|
131
|
+
// them to ~/.claude/image-cache/<sessionId>/<N>.<ext> and leaves only a text marker
|
|
132
|
+
// in the message — an inline "[Image #N]" placeholder and/or a standalone
|
|
133
|
+
// "[Image: source: ...\N.ext]" line. Pull out the N references (so the UI can serve
|
|
134
|
+
// the cached file) and strip the verbose source lines from the displayed text — the
|
|
135
|
+
// rendered preview replaces them; the short "[Image #N]" placeholders stay.
|
|
136
|
+
// Runs on the per-message scan hot path, so it skips the regexes entirely unless a
|
|
137
|
+
// marker is present. Returns { refs: [{ kind: 'cache', n }], text }.
|
|
138
|
+
function parseImageMarkers(text) {
|
|
139
|
+
const raw = text || '';
|
|
140
|
+
if (!raw.includes('[Image')) return { refs: [], text: raw.trim() };
|
|
141
|
+
const ns = new Set();
|
|
142
|
+
for (const m of raw.matchAll(/\[Image #(\d+)\]/g)) ns.add(Number(m[1]));
|
|
143
|
+
for (const m of raw.matchAll(/\[Image: source:[^\]]*?(\d+)\.[a-z0-9]+\]/gi)) ns.add(Number(m[1]));
|
|
144
|
+
const refs = [...ns].sort((a, b) => a - b).map((n) => ({ kind: 'cache', n }));
|
|
145
|
+
const cleaned = raw.replace(/\[Image: source:[^\]]*\]/gi, '').trim();
|
|
146
|
+
return { refs, text: cleaned };
|
|
147
|
+
}
|
|
148
|
+
|
|
121
149
|
function pushUserMessage(messages, text, timestamp, sysLabel, extras) {
|
|
122
150
|
if (sysLabel === '__skip__') return;
|
|
123
151
|
const safeText = text || '';
|
|
@@ -133,6 +161,7 @@ function pushUserMessage(messages, text, timestamp, sysLabel, extras) {
|
|
|
133
161
|
if (extras.uuid) msg.uuid = extras.uuid;
|
|
134
162
|
if (extras.images && extras.images.length) msg.images = extras.images;
|
|
135
163
|
if (extras.toolResultRefs && extras.toolResultRefs.length) msg.toolResultRefs = extras.toolResultRefs;
|
|
164
|
+
if (extras.queued) msg.queued = true;
|
|
136
165
|
}
|
|
137
166
|
messages.push(msg);
|
|
138
167
|
}
|
|
@@ -222,12 +251,29 @@ function scrapeScalarFromBlob(blob, re) {
|
|
|
222
251
|
const sessionInfoCache = new Map();
|
|
223
252
|
const SESSION_INFO_CACHE_MAX = 2000;
|
|
224
253
|
|
|
254
|
+
// Session-scoped /goal: a goal_status attachment carries the active condition
|
|
255
|
+
// (latest-wins as the Stop hook re-evaluates). Only an unmet goal is "active" —
|
|
256
|
+
// a met goal auto-clears in Claude Code, so treat met as removal, same as a
|
|
257
|
+
// `/goal clear` command line. Returns the goal value to apply, or `undefined`
|
|
258
|
+
// when the line is not a goal event (caller keeps its current value).
|
|
259
|
+
function extractGoalFromLine(data) {
|
|
260
|
+
if (data.type === 'attachment' && data.attachment?.type === 'goal_status') {
|
|
261
|
+
return data.attachment.met ? null : { condition: data.attachment.condition || '' };
|
|
262
|
+
}
|
|
263
|
+
if (data.type === 'user' && typeof data.message?.content === 'string'
|
|
264
|
+
&& data.message.content.includes('<command-name>/goal</command-name>')
|
|
265
|
+
&& /<command-args>\s*clear/.test(data.message.content)) {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
225
271
|
// gitBranch in the JSONL is pinned to the launch-time repo by Claude Code
|
|
226
272
|
// and goes stale once cwd shifts (Bash `cd`, submodule). Callers needing the
|
|
227
273
|
// live branch must resolve it from cwd separately. Cache is reset on inode
|
|
228
274
|
// change or truncation (size < scannedUpTo).
|
|
229
275
|
function readSessionInfoFromJsonl(jsonlPath) {
|
|
230
|
-
const result = { slug: null, projectPath: null, cwd: null, gitBranch: null, customTitle: null, logicalParentUuid: null };
|
|
276
|
+
const result = { slug: null, projectPath: null, cwd: null, gitBranch: null, customTitle: null, logicalParentUuid: null, goal: null };
|
|
231
277
|
let stat;
|
|
232
278
|
let fd;
|
|
233
279
|
try {
|
|
@@ -248,6 +294,7 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
248
294
|
cwd: cached.cwd,
|
|
249
295
|
gitBranch: cached.gitBranch,
|
|
250
296
|
logicalParentUuid: cached.logicalParentUuid || null,
|
|
297
|
+
goal: cached.goal || null,
|
|
251
298
|
customTitle: readCustomTitle(jsonlPath, stat)
|
|
252
299
|
};
|
|
253
300
|
}
|
|
@@ -258,6 +305,7 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
258
305
|
result.cwd = cached.cwd;
|
|
259
306
|
result.gitBranch = cached.gitBranch;
|
|
260
307
|
result.logicalParentUuid = cached.logicalParentUuid || null;
|
|
308
|
+
result.goal = cached.goal || null;
|
|
261
309
|
}
|
|
262
310
|
|
|
263
311
|
let lastCwdSeen = result.cwd;
|
|
@@ -273,6 +321,9 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
273
321
|
if (data.subtype === 'compact_boundary' && data.logicalParentUuid && !result.logicalParentUuid) {
|
|
274
322
|
result.logicalParentUuid = data.logicalParentUuid;
|
|
275
323
|
}
|
|
324
|
+
// Forward → last wins.
|
|
325
|
+
const goalUpdate = extractGoalFromLine(data);
|
|
326
|
+
if (goalUpdate !== undefined) result.goal = goalUpdate;
|
|
276
327
|
} catch (e) {}
|
|
277
328
|
};
|
|
278
329
|
|
|
@@ -337,6 +388,9 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
337
388
|
const tn = fs.readSync(fd, tailBuf, 0, TAIL_SIZE, tailStart);
|
|
338
389
|
const lines = tailBuf.toString('utf8', 0, tn).split('\n');
|
|
339
390
|
let latestTailCwd = null;
|
|
391
|
+
// undefined = no goal event seen in tail (keep head's); otherwise the
|
|
392
|
+
// most recent tail goal/clear supersedes head (tail bytes are later).
|
|
393
|
+
let tailGoal;
|
|
340
394
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
341
395
|
try {
|
|
342
396
|
const data = JSON.parse(lines[i]);
|
|
@@ -344,10 +398,12 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
344
398
|
if (!result.projectPath && data.cwd) result.projectPath = data.cwd;
|
|
345
399
|
if (!result.gitBranch && data.gitBranch) result.gitBranch = data.gitBranch;
|
|
346
400
|
if (!latestTailCwd && data.cwd) latestTailCwd = data.cwd;
|
|
347
|
-
if (
|
|
401
|
+
if (tailGoal === undefined) tailGoal = extractGoalFromLine(data);
|
|
402
|
+
if (latestTailCwd && tailGoal !== undefined && result.slug && result.projectPath && result.gitBranch) break;
|
|
348
403
|
} catch (e) {}
|
|
349
404
|
}
|
|
350
405
|
if (latestTailCwd) lastCwdSeen = latestTailCwd;
|
|
406
|
+
if (tailGoal !== undefined) result.goal = tailGoal;
|
|
351
407
|
}
|
|
352
408
|
scannedUpTo = stat.size;
|
|
353
409
|
}
|
|
@@ -367,7 +423,8 @@ function readSessionInfoFromJsonl(jsonlPath) {
|
|
|
367
423
|
projectPath: result.projectPath,
|
|
368
424
|
gitBranch: result.gitBranch,
|
|
369
425
|
cwd: result.cwd,
|
|
370
|
-
logicalParentUuid: result.logicalParentUuid
|
|
426
|
+
logicalParentUuid: result.logicalParentUuid,
|
|
427
|
+
goal: result.goal
|
|
371
428
|
});
|
|
372
429
|
if (sessionInfoCache.size > SESSION_INFO_CACHE_MAX) {
|
|
373
430
|
const firstKey = sessionInfoCache.keys().next().value;
|
|
@@ -402,6 +459,7 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
402
459
|
fd = require('fs').openSync(jsonlPath, 'r');
|
|
403
460
|
const messages = [];
|
|
404
461
|
const toolResults = new Map();
|
|
462
|
+
const toolResultExtras = new Map();
|
|
405
463
|
let readSize = Math.min(65536, stat.size);
|
|
406
464
|
|
|
407
465
|
while (messages.length < limit) {
|
|
@@ -417,6 +475,7 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
417
475
|
|
|
418
476
|
messages.length = 0;
|
|
419
477
|
toolResults.clear();
|
|
478
|
+
toolResultExtras.clear();
|
|
420
479
|
for (const line of clean.split('\n')) {
|
|
421
480
|
if (!line.trim()) continue;
|
|
422
481
|
try {
|
|
@@ -478,11 +537,20 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
478
537
|
const params = {};
|
|
479
538
|
if (inp) {
|
|
480
539
|
if (block.name === 'Edit') {
|
|
540
|
+
if (inp.file_path) params.file_path = inp.file_path;
|
|
481
541
|
if (inp.old_string) params.old_string = inp.old_string;
|
|
482
542
|
if (inp.new_string) params.new_string = inp.new_string;
|
|
483
543
|
if (inp.replace_all) params.replace_all = true;
|
|
484
544
|
} else if (block.name === 'Write') {
|
|
485
|
-
if (inp.
|
|
545
|
+
if (inp.file_path) params.file_path = inp.file_path;
|
|
546
|
+
if (inp.content) {
|
|
547
|
+
if (inp.content.length > TOOL_RESULT_MAX) {
|
|
548
|
+
params.content = inp.content.slice(0, TOOL_RESULT_MAX) + '\n... (truncated)';
|
|
549
|
+
params.contentFull = inp.content;
|
|
550
|
+
} else {
|
|
551
|
+
params.content = inp.content;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
486
554
|
} else if (block.name === 'Grep') {
|
|
487
555
|
if (inp.path) params.path = inp.path;
|
|
488
556
|
if (inp.glob) params.glob = inp.glob;
|
|
@@ -538,6 +606,19 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
538
606
|
if (inp.message && typeof inp.message === 'object') {
|
|
539
607
|
params.protocol = inp.message;
|
|
540
608
|
}
|
|
609
|
+
} else {
|
|
610
|
+
// Passthrough for unknown tools (e.g. MCP `mcp__...`) so the detail panel
|
|
611
|
+
// can render args instead of "No details". Truncate large strings to bound
|
|
612
|
+
// wire/cache size, matching the Write `content` cap above.
|
|
613
|
+
for (const [k, v] of Object.entries(inp)) {
|
|
614
|
+
if (k === 'description' || v == null) continue;
|
|
615
|
+
if (typeof v === 'string' && v.length > TOOL_RESULT_MAX) {
|
|
616
|
+
params[k] = v.slice(0, TOOL_RESULT_MAX) + '\n... (truncated)';
|
|
617
|
+
params[k + 'Full'] = v;
|
|
618
|
+
} else {
|
|
619
|
+
params[k] = v;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
541
622
|
}
|
|
542
623
|
}
|
|
543
624
|
const msg = {
|
|
@@ -617,6 +698,7 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
617
698
|
texts.push(block.text);
|
|
618
699
|
} else if (block.type === 'image' && block.source && block.source.type === 'base64') {
|
|
619
700
|
images.push({
|
|
701
|
+
kind: 'base64',
|
|
620
702
|
blockIndex: idx,
|
|
621
703
|
mediaType: block.source.media_type || 'image/png',
|
|
622
704
|
dataLen: typeof block.source.data === 'string' ? block.source.data.length : 0
|
|
@@ -634,25 +716,54 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
634
716
|
if (resultText) {
|
|
635
717
|
toolResults.set(block.tool_use_id, resultText);
|
|
636
718
|
}
|
|
719
|
+
// AskUserQuestion (and similar) stash the structured
|
|
720
|
+
// {questions, answers} payload at the line-level
|
|
721
|
+
// obj.toolUseResult — the block.content string is just a
|
|
722
|
+
// short confirmation. Capture it so the renderer can show
|
|
723
|
+
// the actual answers + option descriptions.
|
|
724
|
+
const tur = obj.toolUseResult;
|
|
725
|
+
let answerPayload = null;
|
|
726
|
+
if (tur && typeof tur === 'object' && !Array.isArray(tur)
|
|
727
|
+
&& tur.answers && typeof tur.answers === 'object') {
|
|
728
|
+
answerPayload = {
|
|
729
|
+
answers: tur.answers,
|
|
730
|
+
questions: Array.isArray(tur.questions) ? tur.questions : null,
|
|
731
|
+
};
|
|
732
|
+
toolResultExtras.set(block.tool_use_id, { answerPayload });
|
|
733
|
+
}
|
|
637
734
|
toolResultRefs.push({
|
|
638
735
|
toolUseId: block.tool_use_id,
|
|
639
|
-
preview: resultText ? resultText.slice(0, 200) : ''
|
|
736
|
+
preview: resultText ? resultText.slice(0, 200) : '',
|
|
737
|
+
answerPayload,
|
|
640
738
|
});
|
|
641
739
|
}
|
|
642
740
|
});
|
|
643
741
|
const joined = texts.join('\n').trim();
|
|
644
|
-
|
|
645
|
-
|
|
742
|
+
// Prefer inline base64 blocks when present; otherwise fall back to
|
|
743
|
+
// file-cache references parsed from the text markers.
|
|
744
|
+
const { refs, text: displayText } = parseImageMarkers(joined);
|
|
745
|
+
const allImages = images.length ? images : refs;
|
|
746
|
+
const hasText = displayText && displayText !== INTERRUPT_MARKER;
|
|
747
|
+
const hasImages = allImages.length > 0;
|
|
646
748
|
if (hasText || hasImages) {
|
|
647
749
|
pushUserMessage(
|
|
648
750
|
messages,
|
|
649
|
-
|
|
751
|
+
displayText,
|
|
650
752
|
obj.timestamp,
|
|
651
|
-
getSystemMessageLabel(
|
|
652
|
-
{ uuid: obj.uuid, images, toolResultRefs: hasText ? toolResultRefs : [] }
|
|
753
|
+
getSystemMessageLabel(displayText),
|
|
754
|
+
{ uuid: obj.uuid, images: allImages, toolResultRefs: hasText ? toolResultRefs : [] }
|
|
653
755
|
);
|
|
654
756
|
}
|
|
655
757
|
}
|
|
758
|
+
} else if (obj.type === 'queue-operation' && obj.operation === 'enqueue') {
|
|
759
|
+
// Queued messages are stored as top-level `content`, not under
|
|
760
|
+
// message.content, and never re-emitted as a type:'user' line.
|
|
761
|
+
// Surface them so the Session Log reflects what the user sent.
|
|
762
|
+
const qt = typeof obj.content === 'string' ? obj.content : '';
|
|
763
|
+
if (qt) {
|
|
764
|
+
const { refs, text } = parseImageMarkers(qt);
|
|
765
|
+
pushUserMessage(messages, text, obj.timestamp, null, { queued: true, images: refs });
|
|
766
|
+
}
|
|
656
767
|
}
|
|
657
768
|
} catch (e) { /* partial line */ }
|
|
658
769
|
}
|
|
@@ -662,15 +773,27 @@ function readRecentMessages(jsonlPath, limit = 10) {
|
|
|
662
773
|
}
|
|
663
774
|
|
|
664
775
|
// Attach tool results to their corresponding tool_use messages.
|
|
665
|
-
//
|
|
666
|
-
//
|
|
776
|
+
// When truncated, ship the full text inline as toolResultFull so the
|
|
777
|
+
// modal expand toggle is instant. The lazy fetch at
|
|
778
|
+
// /api/sessions/:id/tool-result/:toolUseId remains a fallback for older
|
|
779
|
+
// cached payloads that may lack toolResultFull.
|
|
667
780
|
for (const msg of messages) {
|
|
668
781
|
if (msg.type === 'tool_use' && msg.toolUseId && toolResults.has(msg.toolUseId)) {
|
|
669
782
|
const full = toolResults.get(msg.toolUseId);
|
|
670
783
|
const truncated = full.length > TOOL_RESULT_MAX;
|
|
671
|
-
|
|
784
|
+
if (truncated) {
|
|
785
|
+
msg.toolResult = full.slice(0, TOOL_RESULT_MAX) + '\n... (truncated)';
|
|
786
|
+
msg.toolResultFull = full;
|
|
787
|
+
} else {
|
|
788
|
+
msg.toolResult = full;
|
|
789
|
+
}
|
|
672
790
|
msg.toolResultTruncated = truncated;
|
|
673
791
|
}
|
|
792
|
+
if (msg.type === 'tool_use' && msg.tool === 'AskUserQuestion'
|
|
793
|
+
&& msg.toolUseId && toolResultExtras.has(msg.toolUseId)) {
|
|
794
|
+
const extra = toolResultExtras.get(msg.toolUseId);
|
|
795
|
+
if (extra.answerPayload) msg.answerPayload = extra.answerPayload;
|
|
796
|
+
}
|
|
674
797
|
}
|
|
675
798
|
|
|
676
799
|
require('fs').closeSync(fd);
|
|
@@ -741,6 +864,31 @@ function readUserImage(jsonlPath, msgUuid, blockIndex) {
|
|
|
741
864
|
return null;
|
|
742
865
|
}
|
|
743
866
|
|
|
867
|
+
const CACHED_IMAGE_MIME = {
|
|
868
|
+
png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp',
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
// Serve pasted image N from ~/.claude/image-cache/<sessionId>/. The cached file may
|
|
872
|
+
// be any image format, so resolve it by index rather than assuming .png. sessionId
|
|
873
|
+
// is validated to a bare id to prevent path traversal.
|
|
874
|
+
function readCachedImage(sessionId, n) {
|
|
875
|
+
const idx = Number(n);
|
|
876
|
+
if (!sessionId || !/^[A-Za-z0-9._-]+$/.test(sessionId)) return null;
|
|
877
|
+
if (!Number.isInteger(idx) || idx < 0) return null;
|
|
878
|
+
const dir = path.join(require('os').homedir(), '.claude', 'image-cache', sessionId);
|
|
879
|
+
try {
|
|
880
|
+
const match = readdirSync(dir).find((f) => {
|
|
881
|
+
const dot = f.lastIndexOf('.');
|
|
882
|
+
return dot > 0 && f.slice(0, dot) === String(idx) && CACHED_IMAGE_MIME[f.slice(dot + 1).toLowerCase()];
|
|
883
|
+
});
|
|
884
|
+
if (!match) return null;
|
|
885
|
+
const ext = match.slice(match.lastIndexOf('.') + 1).toLowerCase();
|
|
886
|
+
return { mediaType: CACHED_IMAGE_MIME[ext], buffer: readFileSync(path.join(dir, match)) };
|
|
887
|
+
} catch (_) {
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
|
|
744
892
|
function readMessagesPage(jsonlPath, limit = 10, beforeTimestamp = null) {
|
|
745
893
|
const fetchLimit = limit + 1;
|
|
746
894
|
const applyFilter = beforeTimestamp
|
|
@@ -878,6 +1026,13 @@ function buildSessionDigest(jsonlPath) {
|
|
|
878
1026
|
for (const [key, entry] of Object.entries(map)) {
|
|
879
1027
|
if (nameByToolUseId[key]) entry.name = nameByToolUseId[key];
|
|
880
1028
|
if (descByToolUseId[key]) entry.description = descByToolUseId[key];
|
|
1029
|
+
// Prefer the full prompt from the assistant's tool_use input — Claude Code's
|
|
1030
|
+
// agent_progress system messages embed a truncated prompt that ends mid-sentence.
|
|
1031
|
+
// Only override when entry already had a prompt (i.e. agent_progress path);
|
|
1032
|
+
// bg/teammate paths intentionally keep prompt null per existing contract.
|
|
1033
|
+
if (entry.prompt && promptByToolUseId[key] && promptByToolUseId[key].length > entry.prompt.length) {
|
|
1034
|
+
entry.prompt = promptByToolUseId[key];
|
|
1035
|
+
}
|
|
881
1036
|
}
|
|
882
1037
|
} catch (_) {}
|
|
883
1038
|
const rejectedAgentIds = new Set();
|
|
@@ -977,10 +1132,10 @@ function extractPromptFromTranscript(jsonlPath) {
|
|
|
977
1132
|
const obj = JSON.parse(firstLine);
|
|
978
1133
|
if (obj.type === 'user') {
|
|
979
1134
|
const content = obj.message?.content;
|
|
980
|
-
if (typeof content === 'string') return content
|
|
1135
|
+
if (typeof content === 'string') return content;
|
|
981
1136
|
if (Array.isArray(content)) {
|
|
982
1137
|
for (const b of content) {
|
|
983
|
-
if (b.type === 'text' && b.text) return b.text
|
|
1138
|
+
if (b.type === 'text' && b.text) return b.text;
|
|
984
1139
|
}
|
|
985
1140
|
}
|
|
986
1141
|
}
|
|
@@ -1150,6 +1305,7 @@ module.exports = {
|
|
|
1150
1305
|
readMessagesPage,
|
|
1151
1306
|
readFullToolResult,
|
|
1152
1307
|
readUserImage,
|
|
1308
|
+
readCachedImage,
|
|
1153
1309
|
updateLoopInfo,
|
|
1154
1310
|
buildLoopInfoFromState,
|
|
1155
1311
|
buildAgentProgressMap,
|
package/package.json
CHANGED
|
@@ -58,7 +58,7 @@ if [ "$EVENT" = "PermissionRequest" ] || { [ "$EVENT" = "PreToolUse" ] && { [ "$
|
|
|
58
58
|
status: "waiting",
|
|
59
59
|
kind: $kind,
|
|
60
60
|
toolName: (.tool_name // "unknown"),
|
|
61
|
-
toolInput: ((.tool_input | tostring)
|
|
61
|
+
toolInput: ((.tool_input | tostring) // ""),
|
|
62
62
|
timestamp: $ts
|
|
63
63
|
}' > "$DIR/_waiting.json"
|
|
64
64
|
exit 0
|