@phnx-labs/agents-cli 1.20.51 → 1.20.53
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 +71 -0
- package/README.md +12 -0
- package/dist/bin/agents +0 -0
- package/dist/commands/browser.js +215 -7
- package/dist/commands/cloud.d.ts +3 -0
- package/dist/commands/cloud.js +8 -1
- package/dist/commands/events.d.ts +1 -1
- package/dist/commands/events.js +2 -3
- package/dist/commands/exec.js +82 -3
- package/dist/commands/factory.js +8 -0
- package/dist/commands/feed.d.ts +38 -0
- package/dist/commands/feed.js +274 -0
- package/dist/commands/logs.d.ts +5 -1
- package/dist/commands/logs.js +248 -3
- package/dist/commands/mcp.js +7 -0
- package/dist/commands/memory.d.ts +9 -0
- package/dist/commands/memory.js +164 -0
- package/dist/commands/message.d.ts +11 -6
- package/dist/commands/message.js +140 -5
- package/dist/commands/routines.js +12 -0
- package/dist/commands/secrets-migrate.d.ts +2 -1
- package/dist/commands/secrets-migrate.js +88 -13
- package/dist/commands/secrets.d.ts +22 -0
- package/dist/commands/secrets.js +176 -42
- package/dist/commands/sessions.js +10 -1
- package/dist/commands/teams.js +4 -0
- package/dist/commands/worktree.js +4 -2
- package/dist/index.js +21 -22
- package/dist/lib/agents.js +249 -17
- package/dist/lib/answer-router.d.ts +75 -0
- package/dist/lib/answer-router.js +149 -0
- package/dist/lib/ask-classifier.d.ts +71 -0
- package/dist/lib/ask-classifier.js +197 -0
- package/dist/lib/browser/login-detection.d.ts +94 -0
- package/dist/lib/browser/login-detection.js +274 -0
- package/dist/lib/browser/profiles.d.ts +17 -8
- package/dist/lib/browser/profiles.js +27 -8
- package/dist/lib/browser/secret-ref.d.ts +10 -0
- package/dist/lib/browser/secret-ref.js +14 -0
- package/dist/lib/browser/service.js +14 -12
- package/dist/lib/cloud/antigravity.d.ts +0 -2
- package/dist/lib/cloud/antigravity.js +2 -17
- package/dist/lib/cloud/codex.js +3 -18
- package/dist/lib/cloud/rush.d.ts +15 -0
- package/dist/lib/cloud/rush.js +10 -16
- package/dist/lib/cloud/stream.js +2 -0
- package/dist/lib/cloud/types.d.ts +21 -0
- package/dist/lib/cloud/types.js +81 -0
- package/dist/lib/crabbox/cli.d.ts +1 -1
- package/dist/lib/crabbox/cli.js +12 -2
- package/dist/lib/crabbox/lease.d.ts +19 -0
- package/dist/lib/crabbox/lease.js +22 -11
- package/dist/lib/crabbox/progress.d.ts +62 -0
- package/dist/lib/crabbox/progress.js +129 -0
- package/dist/lib/crabbox/runtimes.d.ts +38 -1
- package/dist/lib/crabbox/runtimes.js +98 -5
- package/dist/lib/daemon.d.ts +12 -9
- package/dist/lib/daemon.js +32 -17
- package/dist/lib/events.d.ts +31 -5
- package/dist/lib/events.js +291 -101
- package/dist/lib/exec.js +20 -1
- package/dist/lib/feed-outcome.d.ts +101 -0
- package/dist/lib/feed-outcome.js +244 -0
- package/dist/lib/feed-policy.d.ts +30 -0
- package/dist/lib/feed-policy.js +133 -0
- package/dist/lib/feed.d.ts +180 -0
- package/dist/lib/feed.js +627 -0
- package/dist/lib/git.d.ts +17 -1
- package/dist/lib/git.js +20 -1
- package/dist/lib/hooks.js +529 -14
- package/dist/lib/hosts/passthrough.d.ts +3 -3
- package/dist/lib/hosts/passthrough.js +3 -3
- package/dist/lib/mailbox-gc.d.ts +22 -0
- package/dist/lib/mailbox-gc.js +161 -0
- package/dist/lib/mailbox.d.ts +26 -2
- package/dist/lib/mailbox.js +80 -5
- package/dist/lib/mcp.js +82 -0
- package/dist/lib/memory.d.ts +55 -0
- package/dist/lib/memory.js +274 -0
- package/dist/lib/notify.d.ts +16 -0
- package/dist/lib/notify.js +61 -0
- package/dist/lib/operator.d.ts +26 -0
- package/dist/lib/operator.js +107 -0
- package/dist/lib/plugins.d.ts +35 -0
- package/dist/lib/plugins.js +217 -0
- package/dist/lib/remote-agents-json.d.ts +14 -0
- package/dist/lib/remote-agents-json.js +94 -0
- package/dist/lib/resources/mcp.js +44 -0
- package/dist/lib/resources/memory.d.ts +15 -0
- package/dist/lib/resources/memory.js +46 -0
- package/dist/lib/resources/types.d.ts +2 -2
- package/dist/lib/rotate.js +2 -0
- package/dist/lib/runner.d.ts +43 -0
- package/dist/lib/runner.js +323 -74
- package/dist/lib/sandbox.js +6 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
- package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
- package/dist/lib/secrets/agent.d.ts +21 -0
- package/dist/lib/secrets/agent.js +63 -1
- package/dist/lib/secrets/bundles.d.ts +33 -1
- package/dist/lib/secrets/bundles.js +76 -21
- package/dist/lib/secrets/icloud-import.d.ts +79 -0
- package/dist/lib/secrets/icloud-import.js +203 -0
- package/dist/lib/secrets/index.d.ts +142 -2
- package/dist/lib/secrets/index.js +695 -30
- package/dist/lib/secrets/remote.js +1 -1
- package/dist/lib/secrets/sync.js +1 -1
- package/dist/lib/session/active.d.ts +18 -0
- package/dist/lib/session/active.js +47 -17
- package/dist/lib/session/db.d.ts +9 -1
- package/dist/lib/session/db.js +18 -3
- package/dist/lib/session/discover.d.ts +13 -0
- package/dist/lib/session/discover.js +32 -2
- package/dist/lib/session/parse.d.ts +8 -0
- package/dist/lib/session/parse.js +42 -21
- package/dist/lib/session/remote-active.js +8 -89
- package/dist/lib/session/state.d.ts +11 -0
- package/dist/lib/session/state.js +50 -1
- package/dist/lib/session/tail.d.ts +23 -4
- package/dist/lib/session/tail.js +34 -16
- package/dist/lib/session/throughput.d.ts +30 -0
- package/dist/lib/session/throughput.js +86 -0
- package/dist/lib/shim-heal.d.ts +12 -3
- package/dist/lib/shim-heal.js +12 -6
- package/dist/lib/staleness/detectors/subagents.js +57 -3
- package/dist/lib/staleness/writers/hooks.js +7 -3
- package/dist/lib/staleness/writers/subagents.js +37 -6
- package/dist/lib/startup/command-registry.d.ts +2 -0
- package/dist/lib/startup/command-registry.js +4 -0
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +25 -8
- package/dist/lib/subagents.d.ts +52 -0
- package/dist/lib/subagents.js +315 -12
- package/dist/lib/teams/agents.js +6 -3
- package/dist/lib/teams/worktree.d.ts +8 -0
- package/dist/lib/teams/worktree.js +8 -0
- package/dist/lib/types.d.ts +20 -2
- package/dist/lib/versions.js +38 -48
- package/dist/lib/whats-new.d.ts +5 -3
- package/dist/lib/whats-new.js +25 -5
- package/package.json +4 -3
- package/scripts/postinstall.js +61 -1
package/dist/lib/hooks.js
CHANGED
|
@@ -994,6 +994,18 @@ export function registerHooksToSettings(agentId, versionHome, hookManifest, agen
|
|
|
994
994
|
if (agentId === 'kimi') {
|
|
995
995
|
return registerHooksForKimi(versionHome, manifest, resolveScript, managedPrefixes);
|
|
996
996
|
}
|
|
997
|
+
if (agentId === 'copilot') {
|
|
998
|
+
return registerHooksForCopilot(versionHome, manifest, resolveScript, managedPrefixes);
|
|
999
|
+
}
|
|
1000
|
+
if (agentId === 'kiro') {
|
|
1001
|
+
return registerHooksForKiro(versionHome, manifest, resolveScript, managedPrefixes);
|
|
1002
|
+
}
|
|
1003
|
+
if (agentId === 'goose') {
|
|
1004
|
+
return registerHooksForGoose(versionHome, manifest, resolveScript, managedPrefixes);
|
|
1005
|
+
}
|
|
1006
|
+
if (agentId === 'cursor') {
|
|
1007
|
+
return registerHooksForCursor(versionHome, manifest, resolveScript, managedPrefixes);
|
|
1008
|
+
}
|
|
997
1009
|
return { registered: [], errors: [] };
|
|
998
1010
|
}
|
|
999
1011
|
/**
|
|
@@ -1028,9 +1040,11 @@ function registerHooksForClaude(versionHome, manifest, resolveScript, managedPre
|
|
|
1028
1040
|
const configDir = path.join(versionHome, configDirName);
|
|
1029
1041
|
const settingsPath = path.join(configDir, 'settings.json');
|
|
1030
1042
|
let config = {};
|
|
1043
|
+
let existingRaw;
|
|
1031
1044
|
if (fs.existsSync(settingsPath)) {
|
|
1032
1045
|
try {
|
|
1033
|
-
|
|
1046
|
+
existingRaw = fs.readFileSync(settingsPath, 'utf-8');
|
|
1047
|
+
config = JSON.parse(existingRaw);
|
|
1034
1048
|
}
|
|
1035
1049
|
catch {
|
|
1036
1050
|
errors.push('Failed to parse settings.json');
|
|
@@ -1111,7 +1125,10 @@ function registerHooksForClaude(versionHome, manifest, resolveScript, managedPre
|
|
|
1111
1125
|
}
|
|
1112
1126
|
try {
|
|
1113
1127
|
fs.mkdirSync(configDir, { recursive: true });
|
|
1114
|
-
|
|
1128
|
+
const nextRaw = JSON.stringify(config, null, 2);
|
|
1129
|
+
if (existingRaw !== nextRaw) {
|
|
1130
|
+
fs.writeFileSync(settingsPath, nextRaw, 'utf-8');
|
|
1131
|
+
}
|
|
1115
1132
|
}
|
|
1116
1133
|
catch (err) {
|
|
1117
1134
|
errors.push(`Failed to write settings.json: ${err.message}`);
|
|
@@ -1508,9 +1525,13 @@ function registerHooksForAntigravity(versionHome, manifest, resolveScript, manag
|
|
|
1508
1525
|
if (!hooks[agyEvent]) {
|
|
1509
1526
|
hooks[agyEvent] = [];
|
|
1510
1527
|
}
|
|
1528
|
+
// Antigravity settings entries: command + optional matcher (tool scope).
|
|
1529
|
+
// Without matcher every PreToolUse guard fires on ALL tools (RUSH-1353).
|
|
1511
1530
|
const list = hooks[agyEvent];
|
|
1512
1531
|
const existingIdx = list.findIndex((e) => e && typeof e === 'object' && e.command === commandPath);
|
|
1513
1532
|
const entry = { command: commandPath };
|
|
1533
|
+
if (hookDef.matcher)
|
|
1534
|
+
entry.matcher = hookDef.matcher;
|
|
1514
1535
|
if (existingIdx >= 0) {
|
|
1515
1536
|
list[existingIdx] = entry;
|
|
1516
1537
|
}
|
|
@@ -1529,9 +1550,43 @@ function registerHooksForAntigravity(versionHome, manifest, resolveScript, manag
|
|
|
1529
1550
|
}
|
|
1530
1551
|
return { registered, errors };
|
|
1531
1552
|
}
|
|
1553
|
+
/**
|
|
1554
|
+
* Grok events that accept a `matcher` field. Per the Grok hooks guide
|
|
1555
|
+
* (docs/user-guide/10-hooks.md, "Key Fields"): the matcher applies to the tool
|
|
1556
|
+
* events — `PreToolUse`, `PostToolUse`, `PostToolUseFailure`, `PermissionDenied`
|
|
1557
|
+
* (matches the tool name) — and to `Notification` (matches the notification
|
|
1558
|
+
* type). The lifecycle events `SessionStart`, `SessionEnd`, `Stop`,
|
|
1559
|
+
* `UserPromptSubmit` REJECT a matcher (they raise loading errors); other events
|
|
1560
|
+
* ignore it. Of the events this registrar emits (see GROK_EVENT_MAP), only these
|
|
1561
|
+
* three accept a matcher, so we emit it for these alone.
|
|
1562
|
+
*/
|
|
1563
|
+
const GROK_MATCHER_EVENTS = new Set(['PreToolUse', 'PostToolUse', 'Notification']);
|
|
1564
|
+
/**
|
|
1565
|
+
* Tool-name matcher aliases for tools Grok does NOT auto-alias. Grok maps common
|
|
1566
|
+
* Claude tool names to its own (Bash → run_terminal_command, Read → read_file,
|
|
1567
|
+
* etc. — docs/user-guide/10-hooks.md, "Tool Name Aliases") and "a matcher keeps
|
|
1568
|
+
* its original name too", so most matchers need no translation. But there is no
|
|
1569
|
+
* alias for `ExitPlanMode`: Grok's plan tools are `enter_plan_mode` /
|
|
1570
|
+
* `exit_plan_mode` (docs/user-guide/19-plan-mode.md line 14), so a bare
|
|
1571
|
+
* `ExitPlanMode` matcher would never fire. Broaden it to a regex that matches
|
|
1572
|
+
* both names. Keep this an explicit, minimal map — not a general translation
|
|
1573
|
+
* engine.
|
|
1574
|
+
*/
|
|
1575
|
+
const GROK_MATCHER_ALIASES = {
|
|
1576
|
+
ExitPlanMode: 'ExitPlanMode|exit_plan_mode',
|
|
1577
|
+
};
|
|
1532
1578
|
/**
|
|
1533
1579
|
* Register hooks for Grok Build.
|
|
1534
|
-
*
|
|
1580
|
+
*
|
|
1581
|
+
* Grok merges ALL of `~/.grok/hooks/*.json` (docs/user-guide/10-hooks.md, "Hook
|
|
1582
|
+
* Locations"), so this registrar writes exactly ONE manifest file, `hooks.json`,
|
|
1583
|
+
* and prunes any stale per-event files (`pretooluse.json`, …) that an older
|
|
1584
|
+
* build wrote — otherwise every hook would run twice per event, forever, on
|
|
1585
|
+
* already-synced installs.
|
|
1586
|
+
*
|
|
1587
|
+
* Matchers are grouped one-per-distinct-matcher (like the Claude writer) and are
|
|
1588
|
+
* emitted only for the events that accept them (GROK_MATCHER_EVENTS); tool-name
|
|
1589
|
+
* matchers Grok does not auto-alias are translated via GROK_MATCHER_ALIASES.
|
|
1535
1590
|
*/
|
|
1536
1591
|
function registerHooksForGrok(versionHome, manifest, resolveScript, managedPrefixes) {
|
|
1537
1592
|
const registered = [];
|
|
@@ -1563,12 +1618,33 @@ function registerHooksForGrok(versionHome, manifest, resolveScript, managedPrefi
|
|
|
1563
1618
|
if (!grokHooks.hooks[grokEvent]) {
|
|
1564
1619
|
grokHooks.hooks[grokEvent] = [];
|
|
1565
1620
|
}
|
|
1566
|
-
grokHooks.hooks[grokEvent]
|
|
1567
|
-
|
|
1568
|
-
|
|
1621
|
+
const groups = grokHooks.hooks[grokEvent];
|
|
1622
|
+
// Emit the matcher only for events that accept one; translate tool names
|
|
1623
|
+
// Grok does not auto-alias. Empty/omitted matcher matches everything.
|
|
1624
|
+
let matcher;
|
|
1625
|
+
if (GROK_MATCHER_EVENTS.has(grokEvent) && hookDef.matcher) {
|
|
1626
|
+
matcher = GROK_MATCHER_ALIASES[hookDef.matcher] ?? hookDef.matcher;
|
|
1627
|
+
}
|
|
1628
|
+
// Group by matcher the way the Claude writer does: one group per distinct
|
|
1629
|
+
// matcher, hooks appended into the group — not one group per hook.
|
|
1630
|
+
let group = groups.find((g) => (g.matcher ?? '') === (matcher ?? ''));
|
|
1631
|
+
if (!group) {
|
|
1632
|
+
group = matcher ? { matcher, hooks: [] } : { hooks: [] };
|
|
1633
|
+
groups.push(group);
|
|
1634
|
+
}
|
|
1635
|
+
const hookEntry = { type: 'command', command: commandPath, timeout };
|
|
1636
|
+
const existingIdx = group.hooks.findIndex((h) => h.command === commandPath);
|
|
1637
|
+
if (existingIdx >= 0) {
|
|
1638
|
+
group.hooks[existingIdx] = hookEntry;
|
|
1639
|
+
}
|
|
1640
|
+
else {
|
|
1641
|
+
group.hooks.push(hookEntry);
|
|
1642
|
+
}
|
|
1569
1643
|
registered.push(`${name} -> ${grokEvent}`);
|
|
1570
1644
|
}
|
|
1571
1645
|
}
|
|
1646
|
+
// Single source of truth: hooks.json. Written fresh from the manifest every
|
|
1647
|
+
// sync, so the registrar owns it outright.
|
|
1572
1648
|
const mainHooksPath = path.join(grokHooksDir, 'hooks.json');
|
|
1573
1649
|
try {
|
|
1574
1650
|
fs.writeFileSync(mainHooksPath, JSON.stringify(grokHooks, null, 2));
|
|
@@ -1576,18 +1652,66 @@ function registerHooksForGrok(versionHome, manifest, resolveScript, managedPrefi
|
|
|
1576
1652
|
catch (e) {
|
|
1577
1653
|
errors.push(`Failed to write hooks.json: ${e.message}`);
|
|
1578
1654
|
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1655
|
+
// Clean up stale per-event files (pretooluse.json, session-start.json, …) that
|
|
1656
|
+
// older builds double-wrote. Grok merges every *.json in this dir, so leaving
|
|
1657
|
+
// them would run each hook twice per event. Only remove files whose contents
|
|
1658
|
+
// are entirely managed by us (a lone `hooks` object referencing our managed
|
|
1659
|
+
// command paths); a user's own custom *.json is left untouched.
|
|
1660
|
+
try {
|
|
1661
|
+
for (const file of fs.readdirSync(grokHooksDir)) {
|
|
1662
|
+
if (!file.endsWith('.json') || file === 'hooks.json')
|
|
1663
|
+
continue;
|
|
1664
|
+
const filePath = path.join(grokHooksDir, file);
|
|
1665
|
+
if (isManagedGrokHookFile(filePath, managedPrefixes)) {
|
|
1666
|
+
fs.rmSync(filePath, { force: true });
|
|
1667
|
+
}
|
|
1587
1668
|
}
|
|
1588
1669
|
}
|
|
1670
|
+
catch (e) {
|
|
1671
|
+
errors.push(`Failed to prune stale grok hook files: ${e.message}`);
|
|
1672
|
+
}
|
|
1589
1673
|
return { registered, errors };
|
|
1590
1674
|
}
|
|
1675
|
+
/**
|
|
1676
|
+
* A per-event Grok hook file is "ours" (safe to prune) when it is a
|
|
1677
|
+
* `{ hooks: { <Event>: [...] } }` document in which every command entry points
|
|
1678
|
+
* at a managed path (see isManagedHookCommand). This is exactly the shape older
|
|
1679
|
+
* builds double-wrote; a user's hand-authored *.json that mixes in unmanaged
|
|
1680
|
+
* commands is preserved. A file we can't parse is treated as not-ours.
|
|
1681
|
+
*/
|
|
1682
|
+
function isManagedGrokHookFile(filePath, managedPrefixes) {
|
|
1683
|
+
let parsed;
|
|
1684
|
+
try {
|
|
1685
|
+
parsed = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1686
|
+
}
|
|
1687
|
+
catch {
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
if (!parsed || typeof parsed !== 'object')
|
|
1691
|
+
return false;
|
|
1692
|
+
const hooks = parsed.hooks;
|
|
1693
|
+
if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks))
|
|
1694
|
+
return false;
|
|
1695
|
+
const commands = [];
|
|
1696
|
+
for (const groups of Object.values(hooks)) {
|
|
1697
|
+
if (!Array.isArray(groups))
|
|
1698
|
+
return false;
|
|
1699
|
+
for (const group of groups) {
|
|
1700
|
+
const entries = group.hooks;
|
|
1701
|
+
if (!Array.isArray(entries))
|
|
1702
|
+
return false;
|
|
1703
|
+
for (const entry of entries) {
|
|
1704
|
+
const cmd = entry.command;
|
|
1705
|
+
if (typeof cmd !== 'string')
|
|
1706
|
+
return false;
|
|
1707
|
+
commands.push(cmd);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
if (commands.length === 0)
|
|
1712
|
+
return false;
|
|
1713
|
+
return commands.every((cmd) => isManagedHookCommand(cmd, managedPrefixes));
|
|
1714
|
+
}
|
|
1591
1715
|
function registerHooksForKimi(versionHome, manifest, resolveScript, managedPrefixes) {
|
|
1592
1716
|
const registered = [];
|
|
1593
1717
|
const errors = [];
|
|
@@ -1671,3 +1795,394 @@ function registerHooksForKimi(versionHome, manifest, resolveScript, managedPrefi
|
|
|
1671
1795
|
}
|
|
1672
1796
|
return { registered, errors };
|
|
1673
1797
|
}
|
|
1798
|
+
/**
|
|
1799
|
+
* Canonical hooks.yaml event names → Copilot camelCase event names.
|
|
1800
|
+
* Copilot accepts both camelCase (native) and PascalCase (VS Code-compatible).
|
|
1801
|
+
* We emit camelCase, the format documented at
|
|
1802
|
+
* https://docs.github.com/en/copilot/reference/hooks-configuration.
|
|
1803
|
+
* Unmapped events are skipped so a Claude-only event does not land in the file.
|
|
1804
|
+
*/
|
|
1805
|
+
const COPILOT_EVENT_MAP = {
|
|
1806
|
+
SessionStart: 'sessionStart',
|
|
1807
|
+
SessionEnd: 'sessionEnd',
|
|
1808
|
+
UserPromptSubmit: 'userPromptSubmitted',
|
|
1809
|
+
PreToolUse: 'preToolUse',
|
|
1810
|
+
PostToolUse: 'postToolUse',
|
|
1811
|
+
PostToolUseFailure: 'postToolUseFailure',
|
|
1812
|
+
Stop: 'agentStop',
|
|
1813
|
+
SubagentStart: 'subagentStart',
|
|
1814
|
+
SubagentStop: 'subagentStop',
|
|
1815
|
+
OnError: 'errorOccurred',
|
|
1816
|
+
PreCompact: 'preCompact',
|
|
1817
|
+
Notification: 'notification',
|
|
1818
|
+
PermissionRequest: 'permissionRequest',
|
|
1819
|
+
};
|
|
1820
|
+
/**
|
|
1821
|
+
* Copilot events that accept a `matcher` field (regex, full-string match).
|
|
1822
|
+
* See "Matcher filtering" in the Copilot hooks reference.
|
|
1823
|
+
*/
|
|
1824
|
+
const COPILOT_MATCHER_EVENTS = new Set([
|
|
1825
|
+
'preToolUse',
|
|
1826
|
+
'postToolUse',
|
|
1827
|
+
'permissionRequest',
|
|
1828
|
+
'preCompact',
|
|
1829
|
+
'notification',
|
|
1830
|
+
'subagentStart',
|
|
1831
|
+
]);
|
|
1832
|
+
/** Managed filename under ~/.copilot/hooks/ — we own this file entirely. */
|
|
1833
|
+
const COPILOT_MANAGED_HOOKS_FILE = 'agents-cli-hooks.json';
|
|
1834
|
+
/**
|
|
1835
|
+
* Register hooks for GitHub Copilot CLI.
|
|
1836
|
+
*
|
|
1837
|
+
* Copilot loads every `*.json` under `~/.copilot/hooks/` (and project
|
|
1838
|
+
* `.github/hooks/`). Schema: `{ "version": 1, "hooks": { event: [entries] } }`
|
|
1839
|
+
* with command entries `{ type: "command", bash|command, timeoutSec?, matcher? }`.
|
|
1840
|
+
*
|
|
1841
|
+
* We rewrite a single managed file (`agents-cli-hooks.json`) on every sync so
|
|
1842
|
+
* GC is trivial — user-authored sibling JSON files are never touched.
|
|
1843
|
+
*/
|
|
1844
|
+
function registerHooksForCopilot(versionHome, manifest, resolveScript, _managedPrefixes) {
|
|
1845
|
+
const registered = [];
|
|
1846
|
+
const errors = [];
|
|
1847
|
+
const copilotHooksDir = path.join(versionHome, '.copilot', 'hooks');
|
|
1848
|
+
fs.mkdirSync(copilotHooksDir, { recursive: true });
|
|
1849
|
+
const hooks = {};
|
|
1850
|
+
for (const [name, hookDef] of Object.entries(manifest)) {
|
|
1851
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
1852
|
+
continue;
|
|
1853
|
+
const commandPath = resolveHookCommand(name, hookDef, resolveScript);
|
|
1854
|
+
if (!commandPath) {
|
|
1855
|
+
errors.push(`${name}: script not found in user or system hooks dir`);
|
|
1856
|
+
continue;
|
|
1857
|
+
}
|
|
1858
|
+
const timeoutSec = hookDef.timeout ?? 30;
|
|
1859
|
+
for (const event of hookDef.events) {
|
|
1860
|
+
const copilotEvent = COPILOT_EVENT_MAP[event];
|
|
1861
|
+
if (!copilotEvent)
|
|
1862
|
+
continue; // unmapped — skip silently
|
|
1863
|
+
if (!hooks[copilotEvent])
|
|
1864
|
+
hooks[copilotEvent] = [];
|
|
1865
|
+
const entry = {
|
|
1866
|
+
type: 'command',
|
|
1867
|
+
command: commandPath,
|
|
1868
|
+
timeoutSec,
|
|
1869
|
+
};
|
|
1870
|
+
if (COPILOT_MATCHER_EVENTS.has(copilotEvent) && hookDef.matcher) {
|
|
1871
|
+
entry.matcher = hookDef.matcher;
|
|
1872
|
+
}
|
|
1873
|
+
// De-dupe on (event, command, matcher) so repeated sync is idempotent.
|
|
1874
|
+
const existingIdx = hooks[copilotEvent].findIndex((h) => h.command === entry.command && (h.matcher ?? '') === (entry.matcher ?? ''));
|
|
1875
|
+
if (existingIdx >= 0) {
|
|
1876
|
+
hooks[copilotEvent][existingIdx] = entry;
|
|
1877
|
+
}
|
|
1878
|
+
else {
|
|
1879
|
+
hooks[copilotEvent].push(entry);
|
|
1880
|
+
}
|
|
1881
|
+
registered.push(`${name} -> ${copilotEvent}`);
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
const outPath = path.join(copilotHooksDir, COPILOT_MANAGED_HOOKS_FILE);
|
|
1885
|
+
try {
|
|
1886
|
+
// Always rewrite: empty manifest → empty hooks object (GC of prior managed entries).
|
|
1887
|
+
fs.writeFileSync(outPath, JSON.stringify({ version: 1, hooks }, null, 2) + '\n', 'utf-8');
|
|
1888
|
+
}
|
|
1889
|
+
catch (err) {
|
|
1890
|
+
errors.push(`Failed to write ${COPILOT_MANAGED_HOOKS_FILE}: ${err.message}`);
|
|
1891
|
+
}
|
|
1892
|
+
return { registered, errors };
|
|
1893
|
+
}
|
|
1894
|
+
/**
|
|
1895
|
+
* Canonical hooks.yaml event names → Kiro v3 trigger names.
|
|
1896
|
+
* Kiro v3 uses PascalCase triggers under `.kiro/hooks/*.json`
|
|
1897
|
+
* (https://kiro.dev/docs/cli/v3/hooks/). Unmapped events are skipped.
|
|
1898
|
+
*/
|
|
1899
|
+
const KIRO_EVENT_MAP = {
|
|
1900
|
+
SessionStart: 'SessionStart',
|
|
1901
|
+
Stop: 'Stop',
|
|
1902
|
+
PreToolUse: 'PreToolUse',
|
|
1903
|
+
PostToolUse: 'PostToolUse',
|
|
1904
|
+
UserPromptSubmit: 'UserPromptSubmit',
|
|
1905
|
+
};
|
|
1906
|
+
/**
|
|
1907
|
+
* Kiro triggers that evaluate the `matcher` regex (tool name, file path, or
|
|
1908
|
+
* prompt text depending on the trigger). Lifecycle SessionStart/Stop always fire.
|
|
1909
|
+
*/
|
|
1910
|
+
const KIRO_MATCHER_EVENTS = new Set([
|
|
1911
|
+
'PreToolUse',
|
|
1912
|
+
'PostToolUse',
|
|
1913
|
+
'UserPromptSubmit',
|
|
1914
|
+
]);
|
|
1915
|
+
/** Managed filename under ~/.kiro/hooks/ — we own this file entirely. */
|
|
1916
|
+
const KIRO_MANAGED_HOOKS_FILE = 'agents-cli-hooks.json';
|
|
1917
|
+
/**
|
|
1918
|
+
* Register hooks for Kiro CLI (v3 standalone hooks format).
|
|
1919
|
+
*
|
|
1920
|
+
* Each file under `~/.kiro/hooks/*.json` is:
|
|
1921
|
+
* { "version": "v1", "hooks": [ { name, trigger, matcher?, action, timeout?, enabled? } ] }
|
|
1922
|
+
*
|
|
1923
|
+
* We rewrite a single managed file so GC is a rewrite and user-authored sibling
|
|
1924
|
+
* JSON files are never touched. Embedded agent-config hooks (2.x) still work
|
|
1925
|
+
* in Kiro but we only write the v3 path.
|
|
1926
|
+
*/
|
|
1927
|
+
function registerHooksForKiro(versionHome, manifest, resolveScript, _managedPrefixes) {
|
|
1928
|
+
const registered = [];
|
|
1929
|
+
const errors = [];
|
|
1930
|
+
const kiroHooksDir = path.join(versionHome, '.kiro', 'hooks');
|
|
1931
|
+
fs.mkdirSync(kiroHooksDir, { recursive: true });
|
|
1932
|
+
const hooks = [];
|
|
1933
|
+
for (const [name, hookDef] of Object.entries(manifest)) {
|
|
1934
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
1935
|
+
continue;
|
|
1936
|
+
const commandPath = resolveHookCommand(name, hookDef, resolveScript);
|
|
1937
|
+
if (!commandPath) {
|
|
1938
|
+
errors.push(`${name}: script not found in user or system hooks dir`);
|
|
1939
|
+
continue;
|
|
1940
|
+
}
|
|
1941
|
+
const timeout = hookDef.timeout ?? 60;
|
|
1942
|
+
for (const event of hookDef.events) {
|
|
1943
|
+
const trigger = KIRO_EVENT_MAP[event];
|
|
1944
|
+
if (!trigger)
|
|
1945
|
+
continue;
|
|
1946
|
+
const entry = {
|
|
1947
|
+
name,
|
|
1948
|
+
trigger,
|
|
1949
|
+
action: { type: 'command', command: commandPath },
|
|
1950
|
+
timeout,
|
|
1951
|
+
enabled: true,
|
|
1952
|
+
};
|
|
1953
|
+
if (KIRO_MATCHER_EVENTS.has(trigger) && hookDef.matcher) {
|
|
1954
|
+
entry.matcher = hookDef.matcher;
|
|
1955
|
+
}
|
|
1956
|
+
// De-dupe on (name, trigger, matcher)
|
|
1957
|
+
const existingIdx = hooks.findIndex((h) => h.name === entry.name &&
|
|
1958
|
+
h.trigger === entry.trigger &&
|
|
1959
|
+
(h.matcher ?? '') === (entry.matcher ?? ''));
|
|
1960
|
+
if (existingIdx >= 0) {
|
|
1961
|
+
hooks[existingIdx] = entry;
|
|
1962
|
+
}
|
|
1963
|
+
else {
|
|
1964
|
+
hooks.push(entry);
|
|
1965
|
+
}
|
|
1966
|
+
registered.push(`${name} -> ${trigger}`);
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
const outPath = path.join(kiroHooksDir, KIRO_MANAGED_HOOKS_FILE);
|
|
1970
|
+
try {
|
|
1971
|
+
fs.writeFileSync(outPath, JSON.stringify({ version: 'v1', hooks }, null, 2) + '\n', 'utf-8');
|
|
1972
|
+
}
|
|
1973
|
+
catch (err) {
|
|
1974
|
+
errors.push(`Failed to write ${KIRO_MANAGED_HOOKS_FILE}: ${err.message}`);
|
|
1975
|
+
}
|
|
1976
|
+
return { registered, errors };
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Canonical hooks.yaml event names that goose supports (Open Plugins PascalCase).
|
|
1980
|
+
* Unmapped events are skipped. Goose ≥ 1.34.0.
|
|
1981
|
+
*/
|
|
1982
|
+
const GOOSE_EVENT_MAP = {
|
|
1983
|
+
SessionStart: 'SessionStart',
|
|
1984
|
+
SessionEnd: 'SessionEnd',
|
|
1985
|
+
Stop: 'Stop',
|
|
1986
|
+
UserPromptSubmit: 'UserPromptSubmit',
|
|
1987
|
+
PreToolUse: 'PreToolUse',
|
|
1988
|
+
PostToolUse: 'PostToolUse',
|
|
1989
|
+
PostToolUseFailure: 'PostToolUseFailure',
|
|
1990
|
+
BeforeReadFile: 'BeforeReadFile',
|
|
1991
|
+
AfterFileEdit: 'AfterFileEdit',
|
|
1992
|
+
BeforeShellExecution: 'BeforeShellExecution',
|
|
1993
|
+
AfterShellExecution: 'AfterShellExecution',
|
|
1994
|
+
// SubagentStart/SubagentStop are not emitted by Goose — do not advertise them.
|
|
1995
|
+
};
|
|
1996
|
+
/** Managed Open Plugins directory name under ~/.agents/plugins/. */
|
|
1997
|
+
const GOOSE_MANAGED_PLUGIN_NAME = 'agents-cli-hooks';
|
|
1998
|
+
/**
|
|
1999
|
+
* Register hooks for Goose (block-goose-cli ≥ 1.34.0).
|
|
2000
|
+
*
|
|
2001
|
+
* Goose auto-discovers Open Plugins under `$HOME/.agents/plugins/<name>/` that
|
|
2002
|
+
* contain `hooks/hooks.json` (HOME is the version home under the agents-cli
|
|
2003
|
+
* shim). Schema (Open Plugins / Claude-shaped):
|
|
2004
|
+
* { "hooks": { Event: [ { matcher?, hooks: [{ type: "command", command }] } ] } }
|
|
2005
|
+
*
|
|
2006
|
+
* We own a single managed plugin (`agents-cli-hooks`) under the version home
|
|
2007
|
+
* and rewrite its hooks.json on every sync. Command paths are absolute
|
|
2008
|
+
* (portable ~/ form) pointing at the already-copied hook scripts.
|
|
2009
|
+
*/
|
|
2010
|
+
function registerHooksForGoose(versionHome, manifest, resolveScript, _managedPrefixes) {
|
|
2011
|
+
const registered = [];
|
|
2012
|
+
const errors = [];
|
|
2013
|
+
const eventHooks = {};
|
|
2014
|
+
for (const [name, hookDef] of Object.entries(manifest)) {
|
|
2015
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
2016
|
+
continue;
|
|
2017
|
+
const commandPath = resolveHookCommand(name, hookDef, resolveScript);
|
|
2018
|
+
if (!commandPath) {
|
|
2019
|
+
errors.push(`${name}: script not found in user or system hooks dir`);
|
|
2020
|
+
continue;
|
|
2021
|
+
}
|
|
2022
|
+
const timeout = hookDef.timeout ?? 60;
|
|
2023
|
+
const cmd = { type: 'command', command: commandPath, timeout };
|
|
2024
|
+
for (const event of hookDef.events) {
|
|
2025
|
+
const gooseEvent = GOOSE_EVENT_MAP[event];
|
|
2026
|
+
if (!gooseEvent)
|
|
2027
|
+
continue;
|
|
2028
|
+
if (!eventHooks[gooseEvent])
|
|
2029
|
+
eventHooks[gooseEvent] = [];
|
|
2030
|
+
const groups = eventHooks[gooseEvent];
|
|
2031
|
+
const matcher = hookDef.matcher || undefined;
|
|
2032
|
+
let group = groups.find((g) => (g.matcher || undefined) === matcher);
|
|
2033
|
+
if (!group) {
|
|
2034
|
+
group = { hooks: [] };
|
|
2035
|
+
if (matcher)
|
|
2036
|
+
group.matcher = matcher;
|
|
2037
|
+
groups.push(group);
|
|
2038
|
+
}
|
|
2039
|
+
const existingIdx = group.hooks.findIndex((h) => h.command === cmd.command);
|
|
2040
|
+
if (existingIdx >= 0) {
|
|
2041
|
+
group.hooks[existingIdx] = cmd;
|
|
2042
|
+
}
|
|
2043
|
+
else {
|
|
2044
|
+
group.hooks.push(cmd);
|
|
2045
|
+
}
|
|
2046
|
+
registered.push(`${name} -> ${gooseEvent}`);
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
// Goose discovers Open Plugins at ~/.agents/plugins/<name>/ when HOME is the
|
|
2050
|
+
// version home (shim sets HOME). Write under versionHome so each installed
|
|
2051
|
+
// goose version gets its own managed plugin and tests stay hermetic.
|
|
2052
|
+
const pluginRoot = path.join(versionHome, '.agents', 'plugins', GOOSE_MANAGED_PLUGIN_NAME);
|
|
2053
|
+
const hooksDir = path.join(pluginRoot, 'hooks');
|
|
2054
|
+
const outPath = path.join(hooksDir, 'hooks.json');
|
|
2055
|
+
try {
|
|
2056
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
2057
|
+
// Minimal plugin marker so the directory is a valid Open Plugins bundle.
|
|
2058
|
+
const markerPath = path.join(pluginRoot, '.agents-cli-managed');
|
|
2059
|
+
if (!fs.existsSync(markerPath)) {
|
|
2060
|
+
fs.writeFileSync(markerPath, 'managed by agents-cli hooks sync\n', 'utf-8');
|
|
2061
|
+
}
|
|
2062
|
+
fs.writeFileSync(outPath, JSON.stringify({ hooks: eventHooks }, null, 2) + '\n', 'utf-8');
|
|
2063
|
+
}
|
|
2064
|
+
catch (err) {
|
|
2065
|
+
errors.push(`Failed to write goose hooks plugin: ${err.message}`);
|
|
2066
|
+
}
|
|
2067
|
+
return { registered, errors };
|
|
2068
|
+
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Canonical → Cursor CLI camelCase events.
|
|
2071
|
+
*
|
|
2072
|
+
* Only events verified to fire in cursor-agent CLI (not full IDE parity).
|
|
2073
|
+
* Ticket RUSH-1326 note (2026-06): working = sessionStart, stop, preToolUse,
|
|
2074
|
+
* postToolUse, beforeShellExecution, afterShellExecution, beforeReadFile,
|
|
2075
|
+
* afterFileEdit. Also map SessionEnd / beforeSubmitPrompt / preCompact /
|
|
2076
|
+
* subagent* which Cursor documents for agent hooks.
|
|
2077
|
+
* Unmapped events (e.g. afterAgentResponse) are skipped.
|
|
2078
|
+
*/
|
|
2079
|
+
const CURSOR_EVENT_MAP = {
|
|
2080
|
+
SessionStart: 'sessionStart',
|
|
2081
|
+
SessionEnd: 'sessionEnd',
|
|
2082
|
+
Stop: 'stop',
|
|
2083
|
+
UserPromptSubmit: 'beforeSubmitPrompt',
|
|
2084
|
+
PreToolUse: 'preToolUse',
|
|
2085
|
+
PostToolUse: 'postToolUse',
|
|
2086
|
+
PostToolUseFailure: 'postToolUseFailure',
|
|
2087
|
+
PreCompact: 'preCompact',
|
|
2088
|
+
SubagentStart: 'subagentStart',
|
|
2089
|
+
SubagentStop: 'subagentStop',
|
|
2090
|
+
BeforeShellExecution: 'beforeShellExecution',
|
|
2091
|
+
AfterShellExecution: 'afterShellExecution',
|
|
2092
|
+
BeforeReadFile: 'beforeReadFile',
|
|
2093
|
+
AfterFileEdit: 'afterFileEdit',
|
|
2094
|
+
};
|
|
2095
|
+
/**
|
|
2096
|
+
* Register hooks for Cursor CLI (`cursor-agent`).
|
|
2097
|
+
*
|
|
2098
|
+
* Writes `~/.cursor/hooks.json` (under version home):
|
|
2099
|
+
* { "version": 1, "hooks": { event: [{ command, timeout?, matcher? }] } }
|
|
2100
|
+
*
|
|
2101
|
+
* GC: rewrite managed entries by command path under managedPrefixes; preserve
|
|
2102
|
+
* user-authored entries whose command is outside managed roots.
|
|
2103
|
+
*/
|
|
2104
|
+
function registerHooksForCursor(versionHome, manifest, resolveScript, managedPrefixes) {
|
|
2105
|
+
const registered = [];
|
|
2106
|
+
const errors = [];
|
|
2107
|
+
const configDir = path.join(versionHome, '.cursor');
|
|
2108
|
+
const hooksPath = path.join(configDir, 'hooks.json');
|
|
2109
|
+
let existing = { version: 1, hooks: {} };
|
|
2110
|
+
if (fs.existsSync(hooksPath)) {
|
|
2111
|
+
try {
|
|
2112
|
+
existing = JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
|
|
2113
|
+
if (!existing.hooks || typeof existing.hooks !== 'object')
|
|
2114
|
+
existing.hooks = {};
|
|
2115
|
+
}
|
|
2116
|
+
catch {
|
|
2117
|
+
errors.push('Failed to parse existing hooks.json');
|
|
2118
|
+
return { registered, errors };
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
// Desired managed entries keyed by event|command|matcher so a matcher or
|
|
2122
|
+
// event change drops the stale entry instead of retaining it by command path alone.
|
|
2123
|
+
const desiredManaged = new Set();
|
|
2124
|
+
for (const [hookName, hookDef] of Object.entries(manifest)) {
|
|
2125
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
2126
|
+
continue;
|
|
2127
|
+
const resolved = resolveHookCommand(hookName, hookDef, resolveScript);
|
|
2128
|
+
if (!resolved)
|
|
2129
|
+
continue;
|
|
2130
|
+
for (const event of hookDef.events) {
|
|
2131
|
+
const cursorEvent = CURSOR_EVENT_MAP[event];
|
|
2132
|
+
if (!cursorEvent)
|
|
2133
|
+
continue;
|
|
2134
|
+
desiredManaged.add(`${cursorEvent}|${resolved}|${hookDef.matcher ?? ''}`);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
// GC managed entries that are no longer in the manifest (by event+command+matcher)
|
|
2138
|
+
const hooks = {};
|
|
2139
|
+
for (const [event, entries] of Object.entries(existing.hooks || {})) {
|
|
2140
|
+
if (!Array.isArray(entries))
|
|
2141
|
+
continue;
|
|
2142
|
+
hooks[event] = entries.filter((e) => {
|
|
2143
|
+
if (typeof e?.command !== 'string')
|
|
2144
|
+
return true;
|
|
2145
|
+
if (!isManagedHookCommand(e.command, managedPrefixes))
|
|
2146
|
+
return true;
|
|
2147
|
+
return desiredManaged.has(`${event}|${e.command}|${e.matcher ?? ''}`);
|
|
2148
|
+
});
|
|
2149
|
+
if (hooks[event].length === 0)
|
|
2150
|
+
delete hooks[event];
|
|
2151
|
+
}
|
|
2152
|
+
for (const [name, hookDef] of Object.entries(manifest)) {
|
|
2153
|
+
if (!hookDef.events || hookDef.events.length === 0)
|
|
2154
|
+
continue;
|
|
2155
|
+
const commandPath = resolveHookCommand(name, hookDef, resolveScript);
|
|
2156
|
+
if (!commandPath) {
|
|
2157
|
+
errors.push(`${name}: script not found in user or system hooks dir`);
|
|
2158
|
+
continue;
|
|
2159
|
+
}
|
|
2160
|
+
const timeout = hookDef.timeout ?? 30;
|
|
2161
|
+
for (const event of hookDef.events) {
|
|
2162
|
+
const cursorEvent = CURSOR_EVENT_MAP[event];
|
|
2163
|
+
if (!cursorEvent)
|
|
2164
|
+
continue;
|
|
2165
|
+
if (!hooks[cursorEvent])
|
|
2166
|
+
hooks[cursorEvent] = [];
|
|
2167
|
+
const entry = { command: commandPath, timeout };
|
|
2168
|
+
if (hookDef.matcher)
|
|
2169
|
+
entry.matcher = hookDef.matcher;
|
|
2170
|
+
const existingIdx = hooks[cursorEvent].findIndex((h) => h.command === entry.command && (h.matcher ?? '') === (entry.matcher ?? ''));
|
|
2171
|
+
if (existingIdx >= 0) {
|
|
2172
|
+
hooks[cursorEvent][existingIdx] = entry;
|
|
2173
|
+
}
|
|
2174
|
+
else {
|
|
2175
|
+
hooks[cursorEvent].push(entry);
|
|
2176
|
+
}
|
|
2177
|
+
registered.push(`${name} -> ${cursorEvent}`);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
try {
|
|
2181
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
2182
|
+
fs.writeFileSync(hooksPath, JSON.stringify({ version: 1, hooks }, null, 2) + '\n', 'utf-8');
|
|
2183
|
+
}
|
|
2184
|
+
catch (err) {
|
|
2185
|
+
errors.push(`Failed to write hooks.json: ${err.message}`);
|
|
2186
|
+
}
|
|
2187
|
+
return { registered, errors };
|
|
2188
|
+
}
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* long-running case — `teams start --watch` — dispatches detached so the remote
|
|
13
13
|
* supervisor outlives a dropped connection.
|
|
14
14
|
*
|
|
15
|
-
* `run` and `
|
|
16
|
-
* richer `--host` handling in their own command actions (detached run
|
|
17
|
-
* multi-host
|
|
15
|
+
* `run`, `sessions`, and `feed` are deliberately absent from the table below:
|
|
16
|
+
* they own richer `--host` handling in their own command actions (detached run
|
|
17
|
+
* dispatch or multi-host fan-out) and must fall through to it.
|
|
18
18
|
*/
|
|
19
19
|
/** Pull the value of `--host`/`-H`/`--remote-cwd` (any form) out of an argv. */
|
|
20
20
|
export declare function flagValue(args: string[], long: string, short?: string): string | undefined;
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
* long-running case — `teams start --watch` — dispatches detached so the remote
|
|
13
13
|
* supervisor outlives a dropped connection.
|
|
14
14
|
*
|
|
15
|
-
* `run` and `
|
|
16
|
-
* richer `--host` handling in their own command actions (detached run
|
|
17
|
-
* multi-host
|
|
15
|
+
* `run`, `sessions`, and `feed` are deliberately absent from the table below:
|
|
16
|
+
* they own richer `--host` handling in their own command actions (detached run
|
|
17
|
+
* dispatch or multi-host fan-out) and must fall through to it.
|
|
18
18
|
*/
|
|
19
19
|
import chalk from 'chalk';
|
|
20
20
|
import { assertValidSshTarget, sshStream } from '../ssh-exec.js';
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface GcResult {
|
|
2
|
+
boxesScanned: number;
|
|
3
|
+
deadBoxes: number;
|
|
4
|
+
messagesDroppedExpired: number;
|
|
5
|
+
messagesDroppedDead: number;
|
|
6
|
+
consumedPruned: number;
|
|
7
|
+
blocksRemoved: number;
|
|
8
|
+
}
|
|
9
|
+
export interface GcOptions {
|
|
10
|
+
root?: string;
|
|
11
|
+
/** Feed store root. Defaults to getFeedDir(). */
|
|
12
|
+
feedRoot?: string;
|
|
13
|
+
now?: Date;
|
|
14
|
+
/** Age in minutes after which consumed entries are pruned. Default 24h. */
|
|
15
|
+
maxConsumedAgeMinutes?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Sweep all mailboxes. For dead boxes (not in `activeBoxIds`), archive every
|
|
19
|
+
* pending message as `dropped: dead` and remove any feed block tied to that
|
|
20
|
+
* mailbox. For live boxes, drop expired messages and prune old consumed files.
|
|
21
|
+
*/
|
|
22
|
+
export declare function gcMailbox(activeBoxIds: Set<string>, options?: GcOptions): GcResult;
|