@rex_koh/subagent-budget-guard 0.5.1 → 0.5.3
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/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -2
- package/bin/hook.js +3 -1
- package/bin/statusline.js +1 -1
- package/bin/view.js +1 -0
- package/commands/sub-agent-view.md +3 -6
- package/hooks/hooks.json +16 -0
- package/lib/guard.js +241 -9
- package/lib/verifier.js +2 -1
- package/package.json +1 -1
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "subagent-cap",
|
|
3
3
|
"displayName": "Subagent Cap",
|
|
4
4
|
"description": "Hard-deny subagent launches, record verified subagent usage, and enforce a session budget against Claude Code's 5-hour rate-limit percentage.",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.3",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ClaudeSubAgentSuppressor"
|
|
8
8
|
},
|
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Recommended Claude Code install:
|
|
|
15
15
|
|
|
16
16
|
After `/subagent-cap:init`, fully exit and reopen Claude Code so the statusLine bridge from `settings.json` is active. Some Claude Code builds do not provide an in-session plugin reload command.
|
|
17
17
|
|
|
18
|
-
`/sub-agent-view` can be run after a session to display how many subagents were spawned, the verified token total, total duration, and each saved subagent run with its token count, duration, model, and tool-call count.
|
|
18
|
+
`/sub-agent-view` can be run after a session to display how many subagents were spawned, queued subagents waiting for retry, the verified token total, total duration, and each saved subagent run with its token count, duration, model, and tool-call count.
|
|
19
19
|
|
|
20
20
|
## NPM Package
|
|
21
21
|
|
|
@@ -30,7 +30,9 @@ subagent-cap status
|
|
|
30
30
|
sub-agent-view
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
`sub-agent-view` prints the latest session's recorded subagents with per-subagent status, type, description, verified token count, duration, model,
|
|
33
|
+
`sub-agent-view` prints the latest session's recorded subagents with per-subagent status, type, description, verified token count, duration, model, tool-call count, and queued retry items. Use `sub-agent-view --session <session-id>` for a specific saved session, or `sub-agent-view --json` for machine-readable output. The same view is also available as the Claude command `/sub-agent-view` and the npm alias `subagent-cap view`.
|
|
34
|
+
|
|
35
|
+
When an `Agent` launch fails only because `max_concurrent_subagents` is already reached, the plugin stores that subagent in a local retry queue with the full original prompt. The default text view does not print full queued prompts. Once active subagents drop below the cap, the plugin injects a reminder for the highest-priority queued item after a tool batch or on the next user prompt so Claude can retry it before lower-priority new work. Hooks cannot autonomously launch a subagent after `SubagentStop`; the queue is surfaced as context for Claude's next action.
|
|
34
36
|
|
|
35
37
|
Maintainer publish command:
|
|
36
38
|
|
package/bin/hook.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
handlePostToolBatch,
|
|
3
4
|
handlePostToolUseAgent,
|
|
4
5
|
handlePreToolUseAgent,
|
|
5
6
|
handleSubagentStart,
|
|
@@ -15,7 +16,7 @@ async function readStdin() {
|
|
|
15
16
|
for await (const chunk of process.stdin) {
|
|
16
17
|
input += chunk;
|
|
17
18
|
}
|
|
18
|
-
return input ? JSON.parse(input) : {};
|
|
19
|
+
return input ? JSON.parse(input.replace(/^\uFEFF/, '')) : {};
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
function emit(result) {
|
|
@@ -30,6 +31,7 @@ function emit(result) {
|
|
|
30
31
|
|
|
31
32
|
const handlers = {
|
|
32
33
|
'pretool-agent': handlePreToolUseAgent,
|
|
34
|
+
'posttool-batch': handlePostToolBatch,
|
|
33
35
|
'posttool-agent': handlePostToolUseAgent,
|
|
34
36
|
'subagent-start': handleSubagentStart,
|
|
35
37
|
'subagent-stop': handleSubagentStop,
|
package/bin/statusline.js
CHANGED
package/bin/view.js
CHANGED
|
@@ -17,6 +17,7 @@ async function main() {
|
|
|
17
17
|
spawnedSubagents: report.state.subagents.runs.length,
|
|
18
18
|
verifiedTokens: report.state.subagents.verifiedTokens,
|
|
19
19
|
totalDurationMs: report.state.subagents.totalDurationMs,
|
|
20
|
+
queuedSubagents: report.state.subagents.queue || [],
|
|
20
21
|
subagents: report.state.subagents.runs
|
|
21
22
|
};
|
|
22
23
|
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
description: Show recorded subagent count, verified tokens, and duration for saved sessions.
|
|
3
3
|
argument-hint: "[--session <session-id>] [--json]"
|
|
4
|
+
allowed-tools: Bash(node:*)
|
|
4
5
|
disable-model-invocation: true
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# View Recorded Subagents
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
The saved subagent view is:
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
node "${CLAUDE_PLUGIN_ROOT}/bin/view.js" $ARGUMENTS
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Show the command output verbatim.
|
|
12
|
+
!`node "${CLAUDE_PLUGIN_ROOT}/bin/view.js" $ARGUMENTS`
|
|
16
13
|
|
|
17
14
|
If the command fails, show the error output and say that `/subagent-cap:init` must be run before saved session data is available.
|
package/hooks/hooks.json
CHANGED
|
@@ -34,6 +34,22 @@
|
|
|
34
34
|
]
|
|
35
35
|
}
|
|
36
36
|
],
|
|
37
|
+
"PostToolBatch": [
|
|
38
|
+
{
|
|
39
|
+
"hooks": [
|
|
40
|
+
{
|
|
41
|
+
"type": "command",
|
|
42
|
+
"command": "node",
|
|
43
|
+
"args": [
|
|
44
|
+
"${CLAUDE_PLUGIN_ROOT}/bin/hook.js",
|
|
45
|
+
"posttool-batch"
|
|
46
|
+
],
|
|
47
|
+
"timeout": 10,
|
|
48
|
+
"statusMessage": "Checking queued subagents"
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
],
|
|
37
53
|
"SubagentStart": [
|
|
38
54
|
{
|
|
39
55
|
"matcher": "",
|
package/lib/guard.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
2
3
|
import { constants as fsConstants, readFileSync } from 'node:fs';
|
|
3
4
|
import {
|
|
4
5
|
access,
|
|
@@ -186,6 +187,10 @@ function initialState(sessionId) {
|
|
|
186
187
|
tokenBudgetWarnings: 0,
|
|
187
188
|
tokenBudgetExceeded: false,
|
|
188
189
|
lastTokenBudgetNoticeAt: null,
|
|
190
|
+
queued: 0,
|
|
191
|
+
queueLaunched: 0,
|
|
192
|
+
queueNotices: 0,
|
|
193
|
+
queue: [],
|
|
189
194
|
runs: []
|
|
190
195
|
},
|
|
191
196
|
agentTeam: {
|
|
@@ -379,6 +384,196 @@ function formatCount(value) {
|
|
|
379
384
|
return Number(value || 0).toLocaleString('en-US');
|
|
380
385
|
}
|
|
381
386
|
|
|
387
|
+
function normalizeText(value) {
|
|
388
|
+
return String(value || '').trim();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function agentIdentity(input) {
|
|
392
|
+
const toolInput = input?.tool_input || {};
|
|
393
|
+
return {
|
|
394
|
+
description: normalizeText(toolInput.description),
|
|
395
|
+
subagentType: normalizeText(toolInput.subagent_type),
|
|
396
|
+
prompt: normalizeText(toolInput.prompt)
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function agentFingerprint(input) {
|
|
401
|
+
const identity = agentIdentity(input);
|
|
402
|
+
return createHash('sha256')
|
|
403
|
+
.update(JSON.stringify(identity))
|
|
404
|
+
.digest('hex');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function agentQueuePriority(input) {
|
|
408
|
+
const identity = agentIdentity(input);
|
|
409
|
+
const text = `${identity.description} ${identity.subagentType} ${identity.prompt}`.toLowerCase();
|
|
410
|
+
|
|
411
|
+
if (/(urgent|critical|blocker|high[- ]priority|priority|asap|production)/.test(text)) {
|
|
412
|
+
return 100;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (/(security|auth|bug|failure|failing|fix|test|review)/.test(text)) {
|
|
416
|
+
return 50;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return 0;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function queuedAgentSummary(item) {
|
|
423
|
+
const type = item.subagentType || 'unknown';
|
|
424
|
+
const description = item.description || 'no description';
|
|
425
|
+
return `${type} "${description}"`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function findQueuedAgentIndex(state, fingerprint) {
|
|
429
|
+
return state.subagents.queue.findIndex((item) => item.fingerprint === fingerprint);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function compareQueuedAgents(a, b) {
|
|
433
|
+
const priorityDiff = Number(b.priority || 0) - Number(a.priority || 0);
|
|
434
|
+
if (priorityDiff !== 0) return priorityDiff;
|
|
435
|
+
return String(a.queuedAt || '').localeCompare(String(b.queuedAt || ''));
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function sortQueuedAgents(state) {
|
|
439
|
+
state.subagents.queue.sort(compareQueuedAgents);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function queueConcurrencyDeniedAgent(state, input, reason) {
|
|
443
|
+
const fingerprint = agentFingerprint(input);
|
|
444
|
+
const existingIndex = findQueuedAgentIndex(state, fingerprint);
|
|
445
|
+
const identity = agentIdentity(input);
|
|
446
|
+
|
|
447
|
+
state.subagents.queued += 1;
|
|
448
|
+
|
|
449
|
+
if (existingIndex !== -1) {
|
|
450
|
+
const existing = state.subagents.queue[existingIndex];
|
|
451
|
+
existing.attempts += 1;
|
|
452
|
+
existing.lastQueuedAt = nowIso();
|
|
453
|
+
existing.priority = Math.max(existing.priority || 0, agentQueuePriority(input));
|
|
454
|
+
existing.reason = reason;
|
|
455
|
+
sortQueuedAgents(state);
|
|
456
|
+
pushEvent(state, {
|
|
457
|
+
type: 'agent-queue-duplicate',
|
|
458
|
+
queueId: existing.queueId,
|
|
459
|
+
attempts: existing.attempts,
|
|
460
|
+
reason
|
|
461
|
+
});
|
|
462
|
+
return existing;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const queueId = `queue-${fingerprint.slice(0, 12)}`;
|
|
466
|
+
const item = {
|
|
467
|
+
queueId,
|
|
468
|
+
fingerprint,
|
|
469
|
+
status: 'queued',
|
|
470
|
+
priority: agentQueuePriority(input),
|
|
471
|
+
attempts: 1,
|
|
472
|
+
queuedAt: nowIso(),
|
|
473
|
+
lastQueuedAt: nowIso(),
|
|
474
|
+
lastNotifiedAt: null,
|
|
475
|
+
notifyCount: 0,
|
|
476
|
+
reason,
|
|
477
|
+
description: identity.description || null,
|
|
478
|
+
subagentType: identity.subagentType || null,
|
|
479
|
+
prompt: identity.prompt || null
|
|
480
|
+
};
|
|
481
|
+
|
|
482
|
+
state.subagents.queue.push(item);
|
|
483
|
+
sortQueuedAgents(state);
|
|
484
|
+
pushEvent(state, {
|
|
485
|
+
type: 'agent-queued',
|
|
486
|
+
queueId,
|
|
487
|
+
priority: item.priority,
|
|
488
|
+
reason,
|
|
489
|
+
description: item.description,
|
|
490
|
+
subagentType: item.subagentType
|
|
491
|
+
});
|
|
492
|
+
return item;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function removeMatchingQueuedAgent(state, input) {
|
|
496
|
+
const index = findQueuedAgentIndex(state, agentFingerprint(input));
|
|
497
|
+
if (index === -1) return null;
|
|
498
|
+
|
|
499
|
+
const [item] = state.subagents.queue.splice(index, 1);
|
|
500
|
+
state.subagents.queueLaunched += 1;
|
|
501
|
+
pushEvent(state, {
|
|
502
|
+
type: 'agent-queue-launched',
|
|
503
|
+
queueId: item.queueId,
|
|
504
|
+
description: item.description,
|
|
505
|
+
subagentType: item.subagentType
|
|
506
|
+
});
|
|
507
|
+
return item;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function nextQueuedAgent(state) {
|
|
511
|
+
const queued = [...state.subagents.queue].filter((item) => item.status === 'queued');
|
|
512
|
+
queued.sort(compareQueuedAgents);
|
|
513
|
+
return queued[0] || null;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function canRetryQueuedAgent(state, config) {
|
|
517
|
+
return (
|
|
518
|
+
config.enforcement_enabled &&
|
|
519
|
+
config.max_concurrent_subagents > 0 &&
|
|
520
|
+
state.subagents.active < config.max_concurrent_subagents &&
|
|
521
|
+
state.subagents.queue.length > 0
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
function formatQueuedAgentContext(item, state, config) {
|
|
526
|
+
const available = Math.max(0, config.max_concurrent_subagents - state.subagents.active);
|
|
527
|
+
return [
|
|
528
|
+
'Queued subagent ready to retry.',
|
|
529
|
+
`Queue id: ${item.queueId}`,
|
|
530
|
+
`Priority: ${Number(item.priority || 0)}`,
|
|
531
|
+
`Attempts: ${Number(item.attempts || 0)}`,
|
|
532
|
+
`Concurrency available: ${available}/${config.max_concurrent_subagents}`,
|
|
533
|
+
`Subagent type: ${item.subagentType || 'unknown'}`,
|
|
534
|
+
`Description: ${item.description || 'no description'}`,
|
|
535
|
+
'Retry this queued Agent task before starting new lower-priority subagent work.',
|
|
536
|
+
'Use the full original prompt below when retrying:',
|
|
537
|
+
item.prompt || '(empty prompt)'
|
|
538
|
+
].join('\n');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function buildQueuedAgentNotice(sessionId, env, hookEventName) {
|
|
542
|
+
const config = loadConfig(env);
|
|
543
|
+
let context = null;
|
|
544
|
+
|
|
545
|
+
await updateState(sessionId, env, (state) => {
|
|
546
|
+
if (!canRetryQueuedAgent(state, config)) return state;
|
|
547
|
+
|
|
548
|
+
const item = nextQueuedAgent(state);
|
|
549
|
+
if (!item) return state;
|
|
550
|
+
|
|
551
|
+
item.notifyCount = Number(item.notifyCount || 0) + 1;
|
|
552
|
+
item.lastNotifiedAt = nowIso();
|
|
553
|
+
state.subagents.queueNotices += 1;
|
|
554
|
+
context = formatQueuedAgentContext(item, state, config);
|
|
555
|
+
pushEvent(state, {
|
|
556
|
+
type: 'agent-queue-notice',
|
|
557
|
+
queueId: item.queueId,
|
|
558
|
+
hookEventName,
|
|
559
|
+
notifyCount: item.notifyCount
|
|
560
|
+
});
|
|
561
|
+
return state;
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
if (!context) return null;
|
|
565
|
+
return {
|
|
566
|
+
exitCode: 0,
|
|
567
|
+
stdout: {
|
|
568
|
+
hookSpecificOutput: {
|
|
569
|
+
hookEventName,
|
|
570
|
+
additionalContext: context
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
stderr: ''
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
|
|
382
577
|
function subagentTokenBudgetStatus(state, config) {
|
|
383
578
|
const limit = config.max_subagent_tokens_per_session;
|
|
384
579
|
if (!limit || limit <= 0) return null;
|
|
@@ -422,21 +617,31 @@ function subagentTokenBudgetDecision(state, config, { includeWarning = true } =
|
|
|
422
617
|
return null;
|
|
423
618
|
}
|
|
424
619
|
|
|
425
|
-
function
|
|
620
|
+
function agentDenyDecision(state, config) {
|
|
426
621
|
if (!config.enforcement_enabled) return null;
|
|
427
622
|
|
|
428
623
|
const budgetReason = fiveHourBudgetDecision(state, config);
|
|
429
|
-
if (budgetReason)
|
|
624
|
+
if (budgetReason) {
|
|
625
|
+
return { reason: budgetReason, queueable: false };
|
|
626
|
+
}
|
|
430
627
|
|
|
431
628
|
const tokenBudgetReason = subagentTokenBudgetDecision(state, config);
|
|
432
|
-
if (tokenBudgetReason)
|
|
629
|
+
if (tokenBudgetReason) {
|
|
630
|
+
return { reason: tokenBudgetReason.reason, queueable: false };
|
|
631
|
+
}
|
|
433
632
|
|
|
434
633
|
if (config.max_concurrent_subagents === 0) {
|
|
435
|
-
return
|
|
634
|
+
return {
|
|
635
|
+
reason: 'Subagent launch denied: max_concurrent_subagents is 0.',
|
|
636
|
+
queueable: false
|
|
637
|
+
};
|
|
436
638
|
}
|
|
437
639
|
|
|
438
640
|
if (state.subagents.active >= config.max_concurrent_subagents) {
|
|
439
|
-
return
|
|
641
|
+
return {
|
|
642
|
+
reason: `Subagent launch queued: max_concurrent_subagents ${config.max_concurrent_subagents} already reached. Retry this queued agent when active subagents drop below the cap.`,
|
|
643
|
+
queueable: true
|
|
644
|
+
};
|
|
440
645
|
}
|
|
441
646
|
|
|
442
647
|
return null;
|
|
@@ -446,12 +651,18 @@ export async function handlePreToolUseAgent(input, env = process.env) {
|
|
|
446
651
|
const sessionId = input?.session_id || 'unknown-session';
|
|
447
652
|
const config = loadConfig(env);
|
|
448
653
|
let reason = null;
|
|
654
|
+
let queuedItem = null;
|
|
449
655
|
|
|
450
656
|
await updateState(sessionId, env, (state) => {
|
|
451
657
|
state.subagents.requested += 1;
|
|
452
|
-
|
|
453
|
-
|
|
658
|
+
const decision = agentDenyDecision(state, config);
|
|
659
|
+
reason = decision?.reason || null;
|
|
660
|
+
if (decision) {
|
|
454
661
|
state.subagents.denied += 1;
|
|
662
|
+
if (decision.queueable) {
|
|
663
|
+
queuedItem = queueConcurrencyDeniedAgent(state, input, reason);
|
|
664
|
+
reason = `${reason} Queue id: ${queuedItem.queueId}.`;
|
|
665
|
+
}
|
|
455
666
|
pushEvent(state, {
|
|
456
667
|
type: 'agent-denied',
|
|
457
668
|
reason,
|
|
@@ -459,9 +670,11 @@ export async function handlePreToolUseAgent(input, env = process.env) {
|
|
|
459
670
|
subagentType: input?.tool_input?.subagent_type || null
|
|
460
671
|
});
|
|
461
672
|
} else {
|
|
673
|
+
const launchedQueuedItem = removeMatchingQueuedAgent(state, input);
|
|
462
674
|
state.subagents.allowed += 1;
|
|
463
675
|
pushEvent(state, {
|
|
464
676
|
type: 'agent-allowed',
|
|
677
|
+
queueId: launchedQueuedItem?.queueId || null,
|
|
465
678
|
description: input?.tool_input?.description || null,
|
|
466
679
|
subagentType: input?.tool_input?.subagent_type || null
|
|
467
680
|
});
|
|
@@ -569,6 +782,13 @@ export async function handlePostToolUseAgent(input, env = process.env) {
|
|
|
569
782
|
return { exitCode: 0, stdout: null, stderr: '' };
|
|
570
783
|
}
|
|
571
784
|
|
|
785
|
+
export async function handlePostToolBatch(input, env = process.env) {
|
|
786
|
+
const sessionId = input?.session_id || 'unknown-session';
|
|
787
|
+
const notice = await buildQueuedAgentNotice(sessionId, env, 'PostToolBatch');
|
|
788
|
+
|
|
789
|
+
return notice || { exitCode: 0, stdout: null, stderr: '' };
|
|
790
|
+
}
|
|
791
|
+
|
|
572
792
|
export async function handleSubagentStart(input, env = process.env) {
|
|
573
793
|
const sessionId = input?.session_id || 'unknown-session';
|
|
574
794
|
await updateState(sessionId, env, (state) => {
|
|
@@ -676,7 +896,8 @@ export async function handleUserPromptSubmit(input, env = process.env) {
|
|
|
676
896
|
const reason = fiveHourBudgetDecision(state, config);
|
|
677
897
|
|
|
678
898
|
if (!reason) {
|
|
679
|
-
|
|
899
|
+
const notice = await buildQueuedAgentNotice(sessionId, env, 'UserPromptSubmit');
|
|
900
|
+
return notice || { exitCode: 0, stdout: null, stderr: '' };
|
|
680
901
|
}
|
|
681
902
|
|
|
682
903
|
await updateState(sessionId, env, (nextState) => {
|
|
@@ -788,14 +1009,16 @@ function formatDuration(ms) {
|
|
|
788
1009
|
|
|
789
1010
|
export function formatSubagentView(report) {
|
|
790
1011
|
const runs = report.state.subagents.runs;
|
|
1012
|
+
const queued = report.state.subagents.queue || [];
|
|
791
1013
|
const lines = [
|
|
792
1014
|
`Sub-agent view for ${report.sessionId}`,
|
|
793
1015
|
`Spawned subagents: ${runs.length}`,
|
|
1016
|
+
`Queued subagents: ${queued.length}`,
|
|
794
1017
|
`Verified tokens: ${formatCount(report.state.subagents.verifiedTokens)}`,
|
|
795
1018
|
`Total duration: ${formatDuration(report.state.subagents.totalDurationMs)}`
|
|
796
1019
|
];
|
|
797
1020
|
|
|
798
|
-
if (runs.length === 0) {
|
|
1021
|
+
if (runs.length === 0 && queued.length === 0) {
|
|
799
1022
|
lines.push('No subagents recorded for this session.');
|
|
800
1023
|
return lines.join('\n');
|
|
801
1024
|
}
|
|
@@ -810,6 +1033,15 @@ export function formatSubagentView(report) {
|
|
|
810
1033
|
lines.push(` tools: ${Number(run.totalToolUseCount || 0)}`);
|
|
811
1034
|
}
|
|
812
1035
|
|
|
1036
|
+
if (queued.length > 0) {
|
|
1037
|
+
lines.push('Queued:');
|
|
1038
|
+
for (const item of queued) {
|
|
1039
|
+
lines.push(`- ${item.queueId} ${queuedAgentSummary(item)}`);
|
|
1040
|
+
lines.push(` priority: ${Number(item.priority || 0)}, attempts: ${Number(item.attempts || 0)}`);
|
|
1041
|
+
lines.push(` queued_at: ${item.queuedAt || 'unknown'}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
813
1045
|
return lines.join('\n');
|
|
814
1046
|
}
|
|
815
1047
|
|
package/lib/verifier.js
CHANGED
|
@@ -111,7 +111,7 @@ export async function runOfflineVerification({
|
|
|
111
111
|
entry.source?.package === '@rex_koh/subagent-budget-guard',
|
|
112
112
|
'marketplace npm package mismatch'
|
|
113
113
|
);
|
|
114
|
-
assert(entry.source?.version === '0.5.
|
|
114
|
+
assert(entry.source?.version === '0.5.3', 'marketplace npm version mismatch');
|
|
115
115
|
return marketplacePath;
|
|
116
116
|
});
|
|
117
117
|
} else {
|
|
@@ -151,6 +151,7 @@ export async function runOfflineVerification({
|
|
|
151
151
|
const requiredEvents = [
|
|
152
152
|
'PreToolUse',
|
|
153
153
|
'PostToolUse',
|
|
154
|
+
'PostToolBatch',
|
|
154
155
|
'SubagentStart',
|
|
155
156
|
'SubagentStop',
|
|
156
157
|
'TaskCreated',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rex_koh/subagent-budget-guard",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"description": "Claude Code plugin that blocks subagents by default, records verified subagent usage, and enforces 5-hour usage budgets.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ClaudeSubAgentSuppressor",
|