@phnx-labs/agents-cli 1.20.34 → 1.20.36
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/CHANGELOG.md +113 -0
- package/LICENSE +185 -21
- package/README.md +13 -5
- package/dist/commands/audit.d.ts +14 -0
- package/dist/commands/audit.js +68 -0
- package/dist/commands/browser.js +82 -8
- package/dist/commands/check.d.ts +15 -0
- package/dist/commands/check.js +84 -0
- package/dist/commands/cloud.js +143 -3
- package/dist/commands/computer-actions.d.ts +15 -0
- package/dist/commands/computer-actions.js +120 -22
- package/dist/commands/computer.js +81 -0
- package/dist/commands/cost.js +17 -12
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/doctor.js +54 -97
- package/dist/commands/events.d.ts +16 -0
- package/dist/commands/events.js +173 -0
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +283 -12
- package/dist/commands/hosts.js +37 -29
- package/dist/commands/inspect.d.ts +2 -0
- package/dist/commands/inspect.js +62 -16
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/logs.d.ts +17 -0
- package/dist/commands/logs.js +139 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- package/dist/commands/models.d.ts +3 -0
- package/dist/commands/models.js +27 -8
- package/dist/commands/permissions.js +9 -2
- package/dist/commands/repo.d.ts +34 -0
- package/dist/commands/repo.js +243 -65
- package/dist/commands/resource-view.d.ts +20 -0
- package/dist/commands/resource-view.js +90 -28
- package/dist/commands/routines.js +101 -5
- package/dist/commands/secrets-migrate.js +106 -57
- package/dist/commands/secrets.d.ts +31 -18
- package/dist/commands/secrets.js +196 -91
- package/dist/commands/serve.d.ts +10 -0
- package/dist/commands/serve.js +37 -0
- package/dist/commands/sessions-inject.d.ts +14 -0
- package/dist/commands/sessions-inject.js +111 -0
- package/dist/commands/sessions-picker.d.ts +2 -0
- package/dist/commands/sessions-picker.js +24 -3
- package/dist/commands/sessions-resume.d.ts +2 -0
- package/dist/commands/sessions-resume.js +221 -0
- package/dist/commands/sessions-tail.d.ts +10 -0
- package/dist/commands/sessions-tail.js +11 -0
- package/dist/commands/sessions.d.ts +143 -1
- package/dist/commands/sessions.js +628 -80
- package/dist/commands/setup.js +4 -2
- package/dist/commands/sync.d.ts +3 -1
- package/dist/commands/sync.js +156 -4
- package/dist/commands/teams.js +217 -0
- package/dist/commands/versions.js +2 -4
- package/dist/commands/view.d.ts +3 -0
- package/dist/commands/view.js +19 -8
- package/dist/commands/wallet.d.ts +6 -0
- package/dist/commands/wallet.js +22 -5
- package/dist/commands/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +82 -32
- package/dist/lib/agents.js +30 -9
- package/dist/lib/audit/log.d.ts +92 -0
- package/dist/lib/audit/log.js +177 -0
- package/dist/lib/auto-pull.js +2 -1
- package/dist/lib/browser/cdp.js +5 -1
- package/dist/lib/browser/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.d.ts +8 -0
- package/dist/lib/browser/drivers/ssh.js +77 -16
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.d.ts +3 -0
- package/dist/lib/browser/ipc.js +37 -12
- package/dist/lib/browser/profiles.d.ts +1 -1
- package/dist/lib/browser/profiles.js +8 -10
- package/dist/lib/browser/refs.d.ts +65 -0
- package/dist/lib/browser/refs.js +73 -1
- package/dist/lib/browser/runtime-state.js +1 -0
- package/dist/lib/browser/service.d.ts +38 -2
- package/dist/lib/browser/service.js +183 -23
- package/dist/lib/browser/types.d.ts +14 -1
- package/dist/lib/budget/live-cloud.d.ts +42 -0
- package/dist/lib/budget/live-cloud.js +79 -0
- package/dist/lib/budget/live-team.d.ts +31 -0
- package/dist/lib/budget/live-team.js +115 -0
- package/dist/lib/cloud/codex.js +4 -0
- package/dist/lib/cloud/rush.d.ts +12 -1
- package/dist/lib/cloud/rush.js +13 -3
- package/dist/lib/cloud/types.d.ts +9 -0
- package/dist/lib/computer/dispatch.d.ts +8 -0
- package/dist/lib/computer/dispatch.js +125 -0
- package/dist/lib/computer/loop.d.ts +62 -0
- package/dist/lib/computer/loop.js +98 -0
- package/dist/lib/computer/model.d.ts +44 -0
- package/dist/lib/computer/model.js +157 -0
- package/dist/lib/concurrency.d.ts +19 -0
- package/dist/lib/concurrency.js +33 -0
- package/dist/lib/daemon.d.ts +92 -0
- package/dist/lib/daemon.js +222 -18
- package/dist/lib/devices/connect.d.ts +3 -2
- package/dist/lib/devices/connect.js +5 -3
- package/dist/lib/devices/registry.d.ts +7 -0
- package/dist/lib/devices/registry.js +24 -0
- package/dist/lib/devices/tailscale.js +1 -1
- package/dist/lib/drift.d.ts +52 -0
- package/dist/lib/drift.js +112 -0
- package/dist/lib/events.d.ts +10 -3
- package/dist/lib/events.js +101 -24
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +100 -12
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +56 -1
- package/dist/lib/hooks/cache.d.ts +6 -0
- package/dist/lib/hooks/cache.js +54 -12
- package/dist/lib/hooks.d.ts +27 -0
- package/dist/lib/hooks.js +135 -10
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +45 -13
- package/dist/lib/hosts/logs.d.ts +16 -0
- package/dist/lib/hosts/logs.js +74 -0
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/progress.d.ts +66 -0
- package/dist/lib/hosts/progress.js +125 -17
- package/dist/lib/hosts/ready.d.ts +51 -7
- package/dist/lib/hosts/ready.js +98 -25
- package/dist/lib/hosts/reconcile.d.ts +53 -0
- package/dist/lib/hosts/reconcile.js +81 -0
- package/dist/lib/hosts/registry.d.ts +19 -2
- package/dist/lib/hosts/registry.js +58 -2
- package/dist/lib/hosts/remote-cmd.d.ts +62 -1
- package/dist/lib/hosts/remote-cmd.js +70 -1
- package/dist/lib/hosts/remote-os.d.ts +17 -0
- package/dist/lib/hosts/remote-os.js +30 -0
- package/dist/lib/hosts/session-index.d.ts +34 -0
- package/dist/lib/hosts/session-index.js +56 -0
- package/dist/lib/hosts/tasks.d.ts +22 -0
- package/dist/lib/hosts/tasks.js +29 -0
- package/dist/lib/lock.d.ts +93 -0
- package/dist/lib/lock.js +207 -0
- package/dist/lib/loop.js +16 -1
- package/dist/lib/machine-id.d.ts +21 -0
- package/dist/lib/machine-id.js +26 -0
- package/dist/lib/mailbox-target.d.ts +36 -0
- package/dist/lib/mailbox-target.js +45 -0
- package/dist/lib/mailbox.d.ts +47 -0
- package/dist/lib/mailbox.js +194 -0
- package/dist/lib/mcp.d.ts +5 -0
- package/dist/lib/mcp.js +24 -8
- package/dist/lib/migrate.d.ts +19 -0
- package/dist/lib/migrate.js +134 -26
- package/dist/lib/overdue.js +3 -0
- package/dist/lib/permissions.d.ts +4 -0
- package/dist/lib/permissions.js +35 -0
- package/dist/lib/picker.d.ts +26 -0
- package/dist/lib/picker.js +127 -0
- package/dist/lib/platform/exec.d.ts +46 -0
- package/dist/lib/platform/exec.js +74 -0
- package/dist/lib/platform/process.d.ts +31 -0
- package/dist/lib/platform/process.js +34 -1
- package/dist/lib/platform/winpath.js +2 -0
- package/dist/lib/plugin-marketplace.d.ts +30 -0
- package/dist/lib/plugin-marketplace.js +215 -2
- package/dist/lib/plugins.d.ts +5 -0
- package/dist/lib/plugins.js +61 -10
- package/dist/lib/profiles.d.ts +25 -0
- package/dist/lib/profiles.js +22 -6
- package/dist/lib/pty-client.js +2 -1
- package/dist/lib/rotate.d.ts +61 -0
- package/dist/lib/rotate.js +52 -0
- package/dist/lib/routines.d.ts +40 -2
- package/dist/lib/routines.js +66 -8
- package/dist/lib/runner.d.ts +11 -2
- package/dist/lib/runner.js +49 -7
- package/dist/lib/scheduler.js +6 -1
- package/dist/lib/secrets/bundles.d.ts +60 -4
- package/dist/lib/secrets/bundles.js +131 -12
- package/dist/lib/secrets/filestore.d.ts +3 -0
- package/dist/lib/secrets/filestore.js +42 -16
- package/dist/lib/secrets/index.d.ts +43 -2
- package/dist/lib/secrets/index.js +102 -3
- package/dist/lib/secrets/mcp.d.ts +93 -0
- package/dist/lib/secrets/mcp.js +205 -0
- package/dist/lib/secrets/remote.js +12 -5
- package/dist/lib/secrets/sync.js +83 -4
- package/dist/lib/secrets/windows.js +14 -3
- package/dist/lib/serve/data.d.ts +81 -0
- package/dist/lib/serve/data.js +91 -0
- package/dist/lib/serve/page.d.ts +7 -0
- package/dist/lib/serve/page.js +140 -0
- package/dist/lib/serve/server.d.ts +46 -0
- package/dist/lib/serve/server.js +115 -0
- package/dist/lib/session/active.d.ts +61 -0
- package/dist/lib/session/active.js +196 -17
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +204 -27
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.d.ts +6 -0
- package/dist/lib/session/parse.js +286 -2
- package/dist/lib/session/pid-registry.d.ts +22 -0
- package/dist/lib/session/pid-registry.js +104 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.d.ts +26 -0
- package/dist/lib/session/remote-active.js +153 -0
- package/dist/lib/session/remote-list.d.ts +51 -0
- package/dist/lib/session/remote-list.js +213 -0
- package/dist/lib/session/remote.d.ts +7 -1
- package/dist/lib/session/remote.js +18 -8
- package/dist/lib/session/render.js +1 -1
- package/dist/lib/session/sync/config.d.ts +1 -7
- package/dist/lib/session/sync/config.js +4 -11
- package/dist/lib/session/types.d.ts +18 -1
- package/dist/lib/session/types.js +1 -1
- package/dist/lib/shims.d.ts +44 -6
- package/dist/lib/shims.js +143 -57
- package/dist/lib/ssh-exec.d.ts +26 -3
- package/dist/lib/ssh-exec.js +47 -3
- package/dist/lib/ssh-tunnel.d.ts +24 -5
- package/dist/lib/ssh-tunnel.js +62 -63
- package/dist/lib/staleness/writers/hooks.js +1 -1
- package/dist/lib/startup/command-registry.d.ts +8 -0
- package/dist/lib/startup/command-registry.js +17 -1
- package/dist/lib/state.d.ts +13 -0
- package/dist/lib/state.js +103 -9
- package/dist/lib/sync-umbrella.d.ts +14 -7
- package/dist/lib/sync-umbrella.js +17 -9
- package/dist/lib/teams/forEach.d.ts +110 -0
- package/dist/lib/teams/forEach.js +186 -0
- package/dist/lib/teams/index.d.ts +1 -0
- package/dist/lib/teams/index.js +1 -0
- package/dist/lib/teams/pr-watch.d.ts +226 -0
- package/dist/lib/teams/pr-watch.js +371 -0
- package/dist/lib/teams/registry.js +25 -9
- package/dist/lib/teams/supervisor.d.ts +14 -1
- package/dist/lib/teams/supervisor.js +19 -0
- package/dist/lib/teams/worktree.d.ts +9 -0
- package/dist/lib/teams/worktree.js +32 -0
- package/dist/lib/terminal/backends/ghostty.d.ts +6 -0
- package/dist/lib/terminal/backends/ghostty.js +69 -0
- package/dist/lib/terminal/backends/index.d.ts +19 -0
- package/dist/lib/terminal/backends/index.js +31 -0
- package/dist/lib/terminal/backends/iterm.d.ts +6 -0
- package/dist/lib/terminal/backends/iterm.js +62 -0
- package/dist/lib/terminal/backends/tmux.d.ts +14 -0
- package/dist/lib/terminal/backends/tmux.js +23 -0
- package/dist/lib/terminal/backends/vscodium-agent.d.ts +31 -0
- package/dist/lib/terminal/backends/vscodium-agent.js +72 -0
- package/dist/lib/terminal/engine.d.ts +39 -0
- package/dist/lib/terminal/engine.js +54 -0
- package/dist/lib/terminal/index.d.ts +17 -0
- package/dist/lib/terminal/index.js +8 -0
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/policy.d.ts +11 -0
- package/dist/lib/terminal/policy.js +11 -0
- package/dist/lib/terminal/quote.d.ts +11 -0
- package/dist/lib/terminal/quote.js +13 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/shell.d.ts +6 -0
- package/dist/lib/terminal/shell.js +23 -0
- package/dist/lib/terminal/transport.d.ts +15 -0
- package/dist/lib/terminal/transport.js +40 -0
- package/dist/lib/terminal/types.d.ts +59 -0
- package/dist/lib/terminal/types.js +13 -0
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/types.d.ts +7 -0
- package/dist/lib/versions.d.ts +30 -3
- package/dist/lib/versions.js +129 -18
- package/dist/lib/watchdog/index.d.ts +3 -0
- package/dist/lib/watchdog/index.js +5 -0
- package/dist/lib/watchdog/read.d.ts +35 -0
- package/dist/lib/watchdog/read.js +149 -0
- package/dist/lib/watchdog/runner.d.ts +127 -0
- package/dist/lib/watchdog/runner.js +322 -0
- package/dist/lib/watchdog/watchdog.d.ts +40 -0
- package/dist/lib/watchdog/watchdog.js +166 -0
- package/dist/lib/watchdog/watchdogTail.d.ts +5 -0
- package/dist/lib/watchdog/watchdogTail.js +154 -0
- package/dist/lib/whats-new.d.ts +9 -0
- package/dist/lib/whats-new.js +35 -0
- package/dist/lib/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Watchdog: pure logic for detecting stalled agent terminals and rendering
|
|
2
|
+
// prompts to a headless decider instance that decides whether to nudge them.
|
|
3
|
+
// Ported from Swarmify (extension/src/core/watchdog.ts) — behavior verbatim.
|
|
4
|
+
// Terminal/session delivery lives elsewhere; this module reads no files and
|
|
5
|
+
// touches no host APIs so it can be unit-tested in isolation.
|
|
6
|
+
const FORCE_REVIEW_STALL_MS = 15 * 60 * 1000;
|
|
7
|
+
const BLOCKED_HINTS = [
|
|
8
|
+
'blocked',
|
|
9
|
+
'stuck',
|
|
10
|
+
"can't",
|
|
11
|
+
'cannot',
|
|
12
|
+
'unable',
|
|
13
|
+
'failed',
|
|
14
|
+
'error',
|
|
15
|
+
'exception',
|
|
16
|
+
'traceback',
|
|
17
|
+
'timed out',
|
|
18
|
+
'timeout',
|
|
19
|
+
'rate limit',
|
|
20
|
+
'permission denied',
|
|
21
|
+
];
|
|
22
|
+
const WAITING_HINTS = [
|
|
23
|
+
'waiting on user',
|
|
24
|
+
'awaiting user',
|
|
25
|
+
'askuserquestion',
|
|
26
|
+
];
|
|
27
|
+
const COMPLETION_HINTS = [
|
|
28
|
+
'done',
|
|
29
|
+
'completed',
|
|
30
|
+
'all set',
|
|
31
|
+
'finished',
|
|
32
|
+
];
|
|
33
|
+
const TOOL_CALL_HINTS = [
|
|
34
|
+
'"type":"tool_use"',
|
|
35
|
+
'"type":"tool_call"',
|
|
36
|
+
'"type":"function_call"',
|
|
37
|
+
];
|
|
38
|
+
const ASSISTANT_LINE_HINTS = [
|
|
39
|
+
'"type":"assistant"',
|
|
40
|
+
'"role":"assistant"',
|
|
41
|
+
'"payload":{"type":"message"',
|
|
42
|
+
];
|
|
43
|
+
const PROMISE_HINTS = [
|
|
44
|
+
"i'll",
|
|
45
|
+
'i will',
|
|
46
|
+
'let me',
|
|
47
|
+
'going to',
|
|
48
|
+
"next i'll",
|
|
49
|
+
'next i will',
|
|
50
|
+
];
|
|
51
|
+
export function classifyTerminal(input) {
|
|
52
|
+
if (input.optedOut)
|
|
53
|
+
return { kind: 'opted_out' };
|
|
54
|
+
const age = input.nowMs - input.lastActivityMs;
|
|
55
|
+
if (age < input.stallMs)
|
|
56
|
+
return { kind: 'active' };
|
|
57
|
+
if (age > input.dormantMs)
|
|
58
|
+
return { kind: 'dormant' };
|
|
59
|
+
if (input.lastNudgeMs !== null) {
|
|
60
|
+
const sinceNudge = input.nowMs - input.lastNudgeMs;
|
|
61
|
+
if (sinceNudge < input.cooldownMs) {
|
|
62
|
+
return { kind: 'rate_limited', cooldownRemainingMs: input.cooldownMs - sinceNudge };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return { kind: 'stalled', stalledForMs: age };
|
|
66
|
+
}
|
|
67
|
+
export const WATCHDOG_SYSTEM_PROMPT = `You are a watchdog monitoring AI coding agents that run in terminals.
|
|
68
|
+
For each stalled terminal below, decide: NUDGE (send a short message to unstick it) or SKIP.
|
|
69
|
+
|
|
70
|
+
Nudge when:
|
|
71
|
+
- The last assistant turn announced an action ("I'll write X", "let me run Y")
|
|
72
|
+
but no matching tool call followed.
|
|
73
|
+
- The agent appears stuck mid-task with no recent progress and the task is incomplete.
|
|
74
|
+
|
|
75
|
+
Skip when:
|
|
76
|
+
- The agent asked the user a direct question (waiting on human input).
|
|
77
|
+
- The task looks complete.
|
|
78
|
+
- You cannot tell what the agent is doing.
|
|
79
|
+
|
|
80
|
+
Nudge text must be:
|
|
81
|
+
- One sentence, imperative ("Show me the file.", "Run the tests.").
|
|
82
|
+
- No emojis. No apologies. Under 120 characters.
|
|
83
|
+
|
|
84
|
+
Respond with ONLY a JSON array (no prose, no code fence):
|
|
85
|
+
[{"terminalId":"<id>","action":"nudge"|"skip","text":"<message or empty>","reason":"<brief>"}]`;
|
|
86
|
+
// User-editable playbook appended below the built-in prompt. The user maintains
|
|
87
|
+
// the source at ~/.agents/playbooks/watchdog.md (read by the delivery layer);
|
|
88
|
+
// this function is pure so it can be tested without filesystem access.
|
|
89
|
+
export function composePromptWithPlaybook(basePrompt, playbook) {
|
|
90
|
+
const trimmed = playbook.trim();
|
|
91
|
+
if (!trimmed)
|
|
92
|
+
return basePrompt;
|
|
93
|
+
return `${basePrompt}\n\n## House Rules (user playbook)\n\n${trimmed}`;
|
|
94
|
+
}
|
|
95
|
+
export function renderWatchdogPrompt(candidates, playbook = '') {
|
|
96
|
+
const systemPrompt = composePromptWithPlaybook(WATCHDOG_SYSTEM_PROMPT, playbook);
|
|
97
|
+
const parts = [systemPrompt, '', 'STALLED TERMINALS:', ''];
|
|
98
|
+
for (const c of candidates) {
|
|
99
|
+
const seconds = Math.round(c.stalledForMs / 1000);
|
|
100
|
+
parts.push(`--- terminal ${c.terminalId} (${c.agentType}, idle ${seconds}s) ---`);
|
|
101
|
+
parts.push('last JSONL lines:');
|
|
102
|
+
for (const line of c.tailLines) {
|
|
103
|
+
parts.push(line);
|
|
104
|
+
}
|
|
105
|
+
parts.push('');
|
|
106
|
+
}
|
|
107
|
+
return parts.join('\n');
|
|
108
|
+
}
|
|
109
|
+
export function parseWatchdogResponse(stdout) {
|
|
110
|
+
if (!stdout || !stdout.trim())
|
|
111
|
+
return [];
|
|
112
|
+
const arrayMatch = stdout.match(/\[[\s\S]*\]/);
|
|
113
|
+
if (!arrayMatch)
|
|
114
|
+
return [];
|
|
115
|
+
let parsed;
|
|
116
|
+
try {
|
|
117
|
+
parsed = JSON.parse(arrayMatch[0]);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
if (!Array.isArray(parsed))
|
|
123
|
+
return [];
|
|
124
|
+
const decisions = [];
|
|
125
|
+
for (const d of parsed) {
|
|
126
|
+
if (!d || typeof d !== 'object')
|
|
127
|
+
continue;
|
|
128
|
+
const obj = d;
|
|
129
|
+
const terminalId = typeof obj.terminalId === 'string' ? obj.terminalId : '';
|
|
130
|
+
const action = obj.action === 'nudge' || obj.action === 'skip' ? obj.action : null;
|
|
131
|
+
const text = typeof obj.text === 'string' ? obj.text : '';
|
|
132
|
+
const reason = typeof obj.reason === 'string' ? obj.reason : '';
|
|
133
|
+
if (!terminalId || !action)
|
|
134
|
+
continue;
|
|
135
|
+
decisions.push({ terminalId, action, text, reason });
|
|
136
|
+
}
|
|
137
|
+
return decisions;
|
|
138
|
+
}
|
|
139
|
+
export function isLikelyTrulyBlocked(candidate) {
|
|
140
|
+
if (candidate.stalledForMs >= FORCE_REVIEW_STALL_MS)
|
|
141
|
+
return true;
|
|
142
|
+
if (candidate.tailLines.length === 0)
|
|
143
|
+
return false;
|
|
144
|
+
const lowerTail = candidate.tailLines.join('\n').toLowerCase();
|
|
145
|
+
if (WAITING_HINTS.some((hint) => lowerTail.includes(hint)))
|
|
146
|
+
return false;
|
|
147
|
+
if (COMPLETION_HINTS.some((hint) => lowerTail.includes(hint)))
|
|
148
|
+
return false;
|
|
149
|
+
if (BLOCKED_HINTS.some((hint) => lowerTail.includes(hint)))
|
|
150
|
+
return true;
|
|
151
|
+
let sawToolAfter = false;
|
|
152
|
+
for (let i = candidate.tailLines.length - 1; i >= 0; i--) {
|
|
153
|
+
const line = candidate.tailLines[i].toLowerCase();
|
|
154
|
+
if (TOOL_CALL_HINTS.some((hint) => line.includes(hint))) {
|
|
155
|
+
sawToolAfter = true;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!sawToolAfter) {
|
|
159
|
+
const isAssistantLine = ASSISTANT_LINE_HINTS.some((hint) => line.includes(hint));
|
|
160
|
+
const hasPromise = PROMISE_HINTS.some((hint) => line.includes(hint));
|
|
161
|
+
if (isAssistantLine && hasPromise)
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// Watchdog tail summarization: pull the most recent user / assistant message
|
|
2
|
+
// out of the session JSONL window the watchdog read this tick. Pure functions —
|
|
3
|
+
// the watchdog runtime hands us tailLines + agentType and we read no files.
|
|
4
|
+
// Ported from Swarmify (extension/src/core/watchdogTail.ts) — behavior verbatim.
|
|
5
|
+
const MAX_MESSAGE_CHARS = 600;
|
|
6
|
+
export function summarizeWatchdogTail(tailLines, agentType) {
|
|
7
|
+
let lastUser;
|
|
8
|
+
let lastAssistant;
|
|
9
|
+
for (let i = tailLines.length - 1; i >= 0; i--) {
|
|
10
|
+
if (lastUser && lastAssistant)
|
|
11
|
+
break;
|
|
12
|
+
let raw;
|
|
13
|
+
try {
|
|
14
|
+
raw = JSON.parse(tailLines[i]);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (!raw || typeof raw !== 'object')
|
|
20
|
+
continue;
|
|
21
|
+
if (agentType === 'claude') {
|
|
22
|
+
const claim = readClaude(raw);
|
|
23
|
+
if (claim?.role === 'user' && !lastUser)
|
|
24
|
+
lastUser = claim.text;
|
|
25
|
+
else if (claim?.role === 'assistant' && !lastAssistant)
|
|
26
|
+
lastAssistant = claim.text;
|
|
27
|
+
}
|
|
28
|
+
else if (agentType === 'codex') {
|
|
29
|
+
const claim = readCodex(raw);
|
|
30
|
+
if (claim?.role === 'user' && !lastUser)
|
|
31
|
+
lastUser = claim.text;
|
|
32
|
+
else if (claim?.role === 'assistant' && !lastAssistant)
|
|
33
|
+
lastAssistant = claim.text;
|
|
34
|
+
}
|
|
35
|
+
else if (agentType === 'gemini') {
|
|
36
|
+
const claim = readGemini(raw);
|
|
37
|
+
if (claim?.role === 'user' && !lastUser)
|
|
38
|
+
lastUser = claim.text;
|
|
39
|
+
else if (claim?.role === 'assistant' && !lastAssistant)
|
|
40
|
+
lastAssistant = claim.text;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
lastUserMessage: clip(lastUser),
|
|
45
|
+
lastAssistantMessage: clip(lastAssistant),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function readClaude(raw) {
|
|
49
|
+
const t = raw.type;
|
|
50
|
+
if (t !== 'user' && t !== 'assistant')
|
|
51
|
+
return null;
|
|
52
|
+
const msg = raw.message;
|
|
53
|
+
if (!msg)
|
|
54
|
+
return null;
|
|
55
|
+
const text = pickContentText(msg.content);
|
|
56
|
+
if (!text)
|
|
57
|
+
return null;
|
|
58
|
+
return { role: t, text };
|
|
59
|
+
}
|
|
60
|
+
function readCodex(raw) {
|
|
61
|
+
if (raw.type !== 'response_item')
|
|
62
|
+
return null;
|
|
63
|
+
const payload = raw.payload;
|
|
64
|
+
if (!payload || payload.type !== 'message')
|
|
65
|
+
return null;
|
|
66
|
+
const role = payload.role;
|
|
67
|
+
if (role !== 'user' && role !== 'assistant')
|
|
68
|
+
return null;
|
|
69
|
+
const text = pickContentText(payload.content);
|
|
70
|
+
if (!text)
|
|
71
|
+
return null;
|
|
72
|
+
return { role, text };
|
|
73
|
+
}
|
|
74
|
+
// Gemini stores chats as a single pretty-printed JSON document — readTailLines
|
|
75
|
+
// returns partial fragments that JSON.parse rejects. So in practice this
|
|
76
|
+
// branch only fires for the JSONL-style events that some Gemini wrappers emit
|
|
77
|
+
// during a live session. Acceptable: the result is an empty summary, which
|
|
78
|
+
// the UI handles gracefully.
|
|
79
|
+
function readGemini(raw) {
|
|
80
|
+
const t = raw.type;
|
|
81
|
+
if (t === 'user_message') {
|
|
82
|
+
const text = typeof raw.text === 'string' ? raw.text : null;
|
|
83
|
+
return text ? { role: 'user', text } : null;
|
|
84
|
+
}
|
|
85
|
+
if (t === 'agent_message' || t === 'model_message') {
|
|
86
|
+
const text = typeof raw.text === 'string' ? raw.text : null;
|
|
87
|
+
return text ? { role: 'assistant', text } : null;
|
|
88
|
+
}
|
|
89
|
+
if (t === 'message') {
|
|
90
|
+
const role = raw.role === 'user' ? 'user' : raw.role === 'assistant' ? 'assistant' : null;
|
|
91
|
+
if (!role)
|
|
92
|
+
return null;
|
|
93
|
+
const text = typeof raw.text === 'string' ? raw.text : pickContentText(raw.content);
|
|
94
|
+
return text ? { role, text } : null;
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
function pickContentText(content) {
|
|
99
|
+
if (typeof content === 'string')
|
|
100
|
+
return isSyntheticTagged(content) ? null : content;
|
|
101
|
+
if (!Array.isArray(content))
|
|
102
|
+
return null;
|
|
103
|
+
const parts = [];
|
|
104
|
+
for (const part of content) {
|
|
105
|
+
if (!part || typeof part !== 'object')
|
|
106
|
+
continue;
|
|
107
|
+
const p = part;
|
|
108
|
+
if ((p.type === 'text' || p.type === 'input_text' || p.type === 'output_text') && typeof p.text === 'string') {
|
|
109
|
+
if (isSyntheticTagged(p.text))
|
|
110
|
+
continue;
|
|
111
|
+
parts.push(p.text);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return parts.length ? parts.join('\n') : null;
|
|
115
|
+
}
|
|
116
|
+
// Mirrors the synthetic-tag filter in the session readers so a "User: …"
|
|
117
|
+
// surfaced in the watchdog UI never shows a <local-command-stdout>,
|
|
118
|
+
// <system-reminder>, or similar harness chunk that Claude wraps tool output in.
|
|
119
|
+
const SYNTHETIC_TAG_PREFIXES = [
|
|
120
|
+
'<local-command-caveat',
|
|
121
|
+
'<local-command-stdout',
|
|
122
|
+
'<local-command-stderr',
|
|
123
|
+
'<command-name',
|
|
124
|
+
'<command-message',
|
|
125
|
+
'<command-args',
|
|
126
|
+
'<bash-input',
|
|
127
|
+
'<bash-stdout',
|
|
128
|
+
'<bash-stderr',
|
|
129
|
+
'<system-reminder',
|
|
130
|
+
'<user-prompt-submit-hook',
|
|
131
|
+
'<task-notification',
|
|
132
|
+
'<persisted-output',
|
|
133
|
+
];
|
|
134
|
+
function isSyntheticTagged(text) {
|
|
135
|
+
const trimmed = text.trim();
|
|
136
|
+
if (!trimmed)
|
|
137
|
+
return true;
|
|
138
|
+
const lower = trimmed.toLowerCase();
|
|
139
|
+
for (const prefix of SYNTHETIC_TAG_PREFIXES) {
|
|
140
|
+
if (lower.startsWith(prefix))
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
function clip(text) {
|
|
146
|
+
if (!text)
|
|
147
|
+
return undefined;
|
|
148
|
+
const cleaned = text.replace(/\s+/g, ' ').trim();
|
|
149
|
+
if (!cleaned)
|
|
150
|
+
return undefined;
|
|
151
|
+
return cleaned.length > MAX_MESSAGE_CHARS
|
|
152
|
+
? cleaned.slice(0, MAX_MESSAGE_CHARS - 1) + '…'
|
|
153
|
+
: cleaned;
|
|
154
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a compact "What's new" summary from a CHANGELOG.md body: one bullet
|
|
3
|
+
* per feature/fix heading (the `**...**` lines) for each version in the range
|
|
4
|
+
* the user actually moved through, `(fromVersion, toVersion]`. The verbose
|
|
5
|
+
* sub-bullets are intentionally dropped — the full notes live in the changelog.
|
|
6
|
+
*
|
|
7
|
+
* Returns colored lines ready to print, empty when nothing is in range.
|
|
8
|
+
*/
|
|
9
|
+
export declare function renderWhatsNew(changelog: string, fromVersion: string, toVersion: string): string[];
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { compareVersions } from './agent-spec/primitives.js';
|
|
3
|
+
/**
|
|
4
|
+
* Render a compact "What's new" summary from a CHANGELOG.md body: one bullet
|
|
5
|
+
* per feature/fix heading (the `**...**` lines) for each version in the range
|
|
6
|
+
* the user actually moved through, `(fromVersion, toVersion]`. The verbose
|
|
7
|
+
* sub-bullets are intentionally dropped — the full notes live in the changelog.
|
|
8
|
+
*
|
|
9
|
+
* Returns colored lines ready to print, empty when nothing is in range.
|
|
10
|
+
*/
|
|
11
|
+
export function renderWhatsNew(changelog, fromVersion, toVersion) {
|
|
12
|
+
const out = [];
|
|
13
|
+
let inRelevantSection = false;
|
|
14
|
+
for (const line of changelog.split('\n')) {
|
|
15
|
+
const versionMatch = line.match(/^## (\d+\.\d+\.\d+)/);
|
|
16
|
+
if (versionMatch) {
|
|
17
|
+
const currentVersion = versionMatch[1];
|
|
18
|
+
// Bounding the top end matters when upgrading to a specific older
|
|
19
|
+
// version, and guards against a changelog that lists unreleased entries.
|
|
20
|
+
inRelevantSection =
|
|
21
|
+
compareVersions(currentVersion, fromVersion) > 0 &&
|
|
22
|
+
compareVersions(currentVersion, toVersion) <= 0;
|
|
23
|
+
if (inRelevantSection) {
|
|
24
|
+
out.push('');
|
|
25
|
+
out.push(chalk.bold(`v${currentVersion}`));
|
|
26
|
+
}
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
// Only the bold headings — one bullet per feature/fix.
|
|
30
|
+
if (inRelevantSection && line.startsWith('**') && line.endsWith('**')) {
|
|
31
|
+
out.push(` ${chalk.cyan('•')} ${line.replace(/\*\*/g, '')}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
package/dist/lib/workflows.d.ts
CHANGED
|
@@ -21,6 +21,70 @@ export interface LoopConfigRaw {
|
|
|
21
21
|
/** Delay between iterations ("0" back-to-back, "30m" paces). */
|
|
22
22
|
interval?: string;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* The `verify:` sub-block of a `for_each:` construct (issue #343).
|
|
26
|
+
*
|
|
27
|
+
* Each produced item's stage teammate can be gated by a panel of independent
|
|
28
|
+
* skeptics: `votes` of them run (as teammates that depend on the stage), and
|
|
29
|
+
* `keep_if` records how their verdicts converge (`majority` / `all` / `any`).
|
|
30
|
+
* The vote-counting itself is a downstream concern — the declarative layer's
|
|
31
|
+
* job is to expand the panel; see `expandForEach`.
|
|
32
|
+
*/
|
|
33
|
+
export interface ForEachVerifySpec {
|
|
34
|
+
/** Subagent / agent id that plays skeptic for each item. */
|
|
35
|
+
agent: string;
|
|
36
|
+
/** Prompt template for each skeptic (`{{item}}`, `{{index}}` substituted). */
|
|
37
|
+
prompt?: string;
|
|
38
|
+
/** How many independent skeptics run per item (>= 1). */
|
|
39
|
+
votes: number;
|
|
40
|
+
/** Convergence rule for keeping a finding. */
|
|
41
|
+
keep_if: 'majority' | 'all' | 'any';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The `for_each:` block as it appears in WORKFLOW.md frontmatter (issue #343).
|
|
45
|
+
*
|
|
46
|
+
* Declarative dynamic fan-out: a producer emits a list at runtime, one stage
|
|
47
|
+
* teammate runs per produced item (runtime-computed N), optionally followed by
|
|
48
|
+
* a `verify` panel. This is a thin declarative layer over the existing teams
|
|
49
|
+
* substrate — each expanded teammate is staged into the supervisor's
|
|
50
|
+
* mid-flight-add path (`AgentManager.spawn`), NOT a new engine. See
|
|
51
|
+
* `expandForEach` and `src/lib/teams/forEach.ts`.
|
|
52
|
+
*
|
|
53
|
+
* Parsed defensively (mirrors `parseLoopBlock`): a malformed block drops to
|
|
54
|
+
* undefined rather than passing a bad shape downstream.
|
|
55
|
+
*/
|
|
56
|
+
export interface ForEachSpec {
|
|
57
|
+
/**
|
|
58
|
+
* The producer: a shell command or subagent whose stdout is a JSON array (or
|
|
59
|
+
* newline-delimited list) of items. Alternatively `itemsRef` names a prior
|
|
60
|
+
* step's output. At least one of the two is expected for a runnable spec.
|
|
61
|
+
*/
|
|
62
|
+
produce?: string;
|
|
63
|
+
/** `${step}`-style reference to a prior step's produced list. */
|
|
64
|
+
itemsRef?: string;
|
|
65
|
+
/** Subagent / agent id for the per-item stage. */
|
|
66
|
+
agent: string;
|
|
67
|
+
/** Base name for the expanded teammates (default `item`). */
|
|
68
|
+
name?: string;
|
|
69
|
+
/** Per-item prompt template (`{{item}}`, `{{index}}` substituted). */
|
|
70
|
+
prompt: string;
|
|
71
|
+
/** In-flight cap — maps to the supervisor's wave size (>= 1). */
|
|
72
|
+
concurrency?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Hard runaway guard: the producer can emit at most this many items before
|
|
75
|
+
* the fan-out is truncated. Defaults to `DEFAULT_FOR_EACH_CAP`.
|
|
76
|
+
*/
|
|
77
|
+
max_items?: number;
|
|
78
|
+
/** Optional convergence gate run after each item's stage. */
|
|
79
|
+
verify?: ForEachVerifySpec;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Hard upper bound on items a single `for_each` expands, absent an explicit
|
|
83
|
+
* `max_items`. A guard against a runaway producer spawning unbounded teammates
|
|
84
|
+
* (acceptance criterion in issue #343). Anthropic's Dynamic Workflows cap at
|
|
85
|
+
* 1000; we default lower and let authors raise it deliberately.
|
|
86
|
+
*/
|
|
87
|
+
export declare const DEFAULT_FOR_EACH_CAP = 256;
|
|
24
88
|
/** Parsed WORKFLOW.md frontmatter. */
|
|
25
89
|
export interface WorkflowFrontmatter {
|
|
26
90
|
name: string;
|
|
@@ -43,6 +107,12 @@ export interface WorkflowFrontmatter {
|
|
|
43
107
|
* `--loop` flag. Validated/coerced in parseWorkflowFrontmatter.
|
|
44
108
|
*/
|
|
45
109
|
loop?: LoopConfigRaw;
|
|
110
|
+
/**
|
|
111
|
+
* Optional declarative dynamic fan-out (issue #343): a producer emits a list
|
|
112
|
+
* and one stage teammate runs per item, with an optional verify panel.
|
|
113
|
+
* Validated/coerced in parseWorkflowFrontmatter via `parseForEachBlock`.
|
|
114
|
+
*/
|
|
115
|
+
forEach?: ForEachSpec;
|
|
46
116
|
}
|
|
47
117
|
/** A workflow found during repo discovery. */
|
|
48
118
|
export interface DiscoveredWorkflow {
|
|
@@ -76,6 +146,81 @@ export declare function parseWorkflowFrontmatter(workflowDir: string): WorkflowF
|
|
|
76
146
|
* - interval: a string (e.g. "0", "30m"); non-strings dropped.
|
|
77
147
|
*/
|
|
78
148
|
export declare function parseLoopBlock(v: unknown): LoopConfigRaw | undefined;
|
|
149
|
+
/**
|
|
150
|
+
* Defensively coerce a frontmatter `verify:` sub-block into a ForEachVerifySpec.
|
|
151
|
+
*
|
|
152
|
+
* Requires an `agent`; drops the whole block otherwise (a verify panel with no
|
|
153
|
+
* skeptic is meaningless). `votes` defaults to 1 (a single confirmation) and
|
|
154
|
+
* `keep_if` to `majority`; both are validated against their allowed shapes.
|
|
155
|
+
*/
|
|
156
|
+
export declare function parseVerifyBlock(v: unknown): ForEachVerifySpec | undefined;
|
|
157
|
+
/**
|
|
158
|
+
* Defensively coerce a frontmatter `for_each:` block into a ForEachSpec (issue
|
|
159
|
+
* #343). Mirrors `parseLoopBlock`'s discipline: a block missing the two
|
|
160
|
+
* load-bearing fields (`agent` + `prompt`) drops to undefined rather than
|
|
161
|
+
* passing a half-formed spec to the expander. Optional numeric/verify fields
|
|
162
|
+
* are individually validated and dropped when malformed.
|
|
163
|
+
*/
|
|
164
|
+
export declare function parseForEachBlock(v: unknown): ForEachSpec | undefined;
|
|
165
|
+
/** One teammate produced by expanding a `for_each` spec against a produced list. */
|
|
166
|
+
export interface ForEachTeammate {
|
|
167
|
+
/** `stage` runs the per-item work; `verify` is a skeptic in the panel. */
|
|
168
|
+
role: 'stage' | 'verify';
|
|
169
|
+
/** Unique teammate name within the team (used for `--after` linkage). */
|
|
170
|
+
name: string;
|
|
171
|
+
/** Subagent / agent id this teammate runs as. */
|
|
172
|
+
agentType: string;
|
|
173
|
+
/** Fully-resolved prompt (template variables already substituted). */
|
|
174
|
+
prompt: string;
|
|
175
|
+
/** Names of sibling teammates this one waits on (`--after` semantics). */
|
|
176
|
+
after: string[];
|
|
177
|
+
/** The produced item this teammate handles. */
|
|
178
|
+
item: string;
|
|
179
|
+
/** Zero-based index of the item in the (capped) produced list. */
|
|
180
|
+
itemIndex: number;
|
|
181
|
+
/** For `verify` teammates: 1-based vote index and the panel's gate config. */
|
|
182
|
+
vote?: number;
|
|
183
|
+
votes?: number;
|
|
184
|
+
keep_if?: 'majority' | 'all' | 'any';
|
|
185
|
+
}
|
|
186
|
+
/** Result of expanding a `for_each` spec: the teammates plus cap accounting. */
|
|
187
|
+
export interface ForEachExpansion {
|
|
188
|
+
teammates: ForEachTeammate[];
|
|
189
|
+
/** How many items the producer emitted (pre-cap). */
|
|
190
|
+
producedCount: number;
|
|
191
|
+
/** How many items were actually expanded (post-cap). */
|
|
192
|
+
usedCount: number;
|
|
193
|
+
/** producedCount - usedCount; > 0 means the runaway guard truncated. */
|
|
194
|
+
truncated: number;
|
|
195
|
+
/** The effective per-`for_each` item cap that was applied. */
|
|
196
|
+
cap: number;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Substitute `{{item}}` / `{{index}}` (and 1-based `{{n}}`) in a prompt
|
|
200
|
+
* template. Unknown `{{...}}` tokens are left intact so a template can carry
|
|
201
|
+
* placeholders the caller resolves elsewhere.
|
|
202
|
+
*/
|
|
203
|
+
export declare function renderForEachTemplate(template: string, item: string, index: number): string;
|
|
204
|
+
/**
|
|
205
|
+
* Expand a `for_each` spec against a producer's output into concrete teammate
|
|
206
|
+
* descriptors (issue #343) — the heart of the declarative fan-out.
|
|
207
|
+
*
|
|
208
|
+
* Pure and deterministic: no I/O, no spawning. `src/lib/teams/forEach.ts`
|
|
209
|
+
* feeds the result to `AgentManager.spawn`, staging each descriptor into the
|
|
210
|
+
* supervisor's existing mid-flight-add path — so this reuses the dynamic-DAG
|
|
211
|
+
* substrate rather than introducing a new engine.
|
|
212
|
+
*
|
|
213
|
+
* For N produced items (capped at `spec.max_items` / `DEFAULT_FOR_EACH_CAP`):
|
|
214
|
+
* - one `stage` teammate per item, depending on `producerName` if given;
|
|
215
|
+
* - when `verify` is set, `votes` `verify` teammates per item, each
|
|
216
|
+
* depending on that item's stage teammate.
|
|
217
|
+
*
|
|
218
|
+
* Names are unique (`<base>-<n>` and `<base>-<n>-verify-<v>`) so `--after`
|
|
219
|
+
* linkage and the teams cycle check carry over unchanged.
|
|
220
|
+
*/
|
|
221
|
+
export declare function expandForEach(spec: ForEachSpec, items: string[], opts?: {
|
|
222
|
+
producerName?: string;
|
|
223
|
+
}): ForEachExpansion;
|
|
79
224
|
/**
|
|
80
225
|
* Decide which subagent .md stems a workflow may use, given the discovered
|
|
81
226
|
* subagent files and the parsed `allowedAgents` frontmatter. This is the
|
|
@@ -95,6 +240,27 @@ export declare function resolveAllowedSubagents(available: string[], allowedAgen
|
|
|
95
240
|
allowedStems: string[];
|
|
96
241
|
missing: string[];
|
|
97
242
|
};
|
|
243
|
+
/**
|
|
244
|
+
* Prune stale workflow-managed subagent files from the shared per-agent agents
|
|
245
|
+
* dir before a scoped run writes the permitted set (issue #401, follow-up to
|
|
246
|
+
* #324). A prior *unrestricted* run of a workflow copies every subagent
|
|
247
|
+
* definition into the shared `~/.claude/agents/` dir; a later run that declares
|
|
248
|
+
* `allowedAgents:` copies only the permitted ones but never removes the
|
|
249
|
+
* leftovers — so an unlisted subagent stays on disk and remains dispatchable,
|
|
250
|
+
* silently defeating the fail-closed scope.
|
|
251
|
+
*
|
|
252
|
+
* Fail-closed fix (mirrors how `cleanupWorkflowMcpConfig` only tears down what
|
|
253
|
+
* the workflow itself created): remove any file that (a) belongs to THIS
|
|
254
|
+
* workflow's subagents/ — matched by filename, i.e. the workflow-managed
|
|
255
|
+
* universe — and (b) is NOT in the permitted set. A user's own hand-placed
|
|
256
|
+
* subagent shares no name with a workflow subagent file, so it is never
|
|
257
|
+
* touched. Permitted files are left in place; the caller (re)copies them.
|
|
258
|
+
*
|
|
259
|
+
* `workflowSubagentFiles` are the .md filenames in the workflow's subagents/
|
|
260
|
+
* dir (e.g. `security.md`); `allowedStems` are the permitted stems from
|
|
261
|
+
* `resolveAllowedSubagents`. Returns the filenames actually removed.
|
|
262
|
+
*/
|
|
263
|
+
export declare function pruneStaleWorkflowSubagents(sharedAgentsDir: string, workflowSubagentFiles: string[], allowedStems: string[]): string[];
|
|
98
264
|
/** Count subagent .md files in a workflow's subagents/ directory. */
|
|
99
265
|
export declare function countWorkflowSubagents(workflowDir: string): number;
|
|
100
266
|
/**
|