@phnx-labs/agents-cli 1.20.35 → 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 +91 -0
- package/LICENSE +185 -21
- package/README.md +8 -4
- 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.js +81 -0
- package/dist/commands/daemon.js +4 -1
- package/dist/commands/doctor.js +1 -89
- package/dist/commands/events.js +3 -3
- package/dist/commands/exec.d.ts +19 -0
- package/dist/commands/exec.js +277 -11
- package/dist/commands/hosts.js +10 -6
- package/dist/commands/inspect.js +8 -9
- package/dist/commands/lock.d.ts +12 -0
- package/dist/commands/lock.js +70 -0
- package/dist/commands/message.d.ts +15 -0
- package/dist/commands/message.js +56 -0
- 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 +156 -75
- 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.js +20 -8
- package/dist/commands/sessions.d.ts +71 -1
- package/dist/commands/sessions.js +365 -37
- 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/watchdog.d.ts +18 -0
- package/dist/commands/watchdog.js +238 -0
- package/dist/index.js +25 -2
- 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/chrome.d.ts +10 -0
- package/dist/lib/browser/chrome.js +18 -7
- package/dist/lib/browser/drivers/ssh.js +2 -1
- package/dist/lib/browser/har.d.ts +84 -0
- package/dist/lib/browser/har.js +77 -0
- package/dist/lib/browser/ipc.js +24 -3
- 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 +112 -8
- 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 +57 -0
- package/dist/lib/daemon.js +192 -16
- 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 +1 -1
- package/dist/lib/events.js +31 -13
- package/dist/lib/exec.d.ts +17 -0
- package/dist/lib/exec.js +79 -13
- 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 +127 -8
- package/dist/lib/hosts/dispatch.d.ts +15 -0
- package/dist/lib/hosts/dispatch.js +39 -6
- package/dist/lib/hosts/logs.js +30 -1
- package/dist/lib/hosts/option.js +1 -1
- package/dist/lib/hosts/passthrough.js +3 -1
- package/dist/lib/hosts/ready.d.ts +29 -6
- package/dist/lib/hosts/ready.js +66 -15
- 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 +14 -0
- package/dist/lib/hosts/tasks.js +15 -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/picker.d.ts +2 -0
- package/dist/lib/picker.js +4 -1
- 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/plugins.js +16 -6
- 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 +54 -0
- package/dist/lib/session/active.js +190 -19
- package/dist/lib/session/discover.d.ts +37 -0
- package/dist/lib/session/discover.js +111 -28
- package/dist/lib/session/inject.d.ts +18 -0
- package/dist/lib/session/inject.js +21 -0
- package/dist/lib/session/parse.js +23 -20
- package/dist/lib/session/pid-registry.d.ts +1 -0
- package/dist/lib/session/pid-registry.js +24 -0
- package/dist/lib/session/provenance.d.ts +14 -2
- package/dist/lib/session/provenance.js +39 -8
- package/dist/lib/session/remote-active.js +19 -7
- 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 +16 -2
- package/dist/lib/session/sync/config.d.ts +1 -15
- package/dist/lib/session/sync/config.js +4 -20
- package/dist/lib/session/types.d.ts +17 -0
- package/dist/lib/shims.d.ts +36 -6
- package/dist/lib/shims.js +91 -29
- package/dist/lib/ssh-exec.js +2 -0
- package/dist/lib/ssh-tunnel.js +2 -1
- package/dist/lib/startup/command-registry.d.ts +6 -0
- package/dist/lib/startup/command-registry.js +13 -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/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/index.d.ts +2 -1
- package/dist/lib/terminal/backends/index.js +3 -1
- 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/index.d.ts +4 -1
- package/dist/lib/terminal/index.js +4 -1
- package/dist/lib/terminal/inject.d.ts +204 -0
- package/dist/lib/terminal/inject.js +247 -0
- package/dist/lib/terminal/resolve.d.ts +64 -0
- package/dist/lib/terminal/resolve.js +90 -0
- package/dist/lib/terminal/types.d.ts +1 -1
- package/dist/lib/triggers/webhook.d.ts +85 -0
- package/dist/lib/triggers/webhook.js +141 -0
- package/dist/lib/versions.d.ts +23 -0
- package/dist/lib/versions.js +119 -13
- 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/workflows.d.ts +166 -0
- package/dist/lib/workflows.js +193 -0
- package/package.json +5 -4
package/dist/commands/sync.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* `agents sync` — synchronize central resources into an installed agent version.
|
|
3
3
|
*
|
|
4
4
|
* Forms:
|
|
5
|
-
* agents sync # umbrella: fetch
|
|
5
|
+
* agents sync # umbrella: fetch config repos -> reconcile all (secrets/sessions opt-in)
|
|
6
6
|
* agents sync --repos|--secrets|--sessions # umbrella: fetch only those, then reconcile
|
|
7
7
|
* agents sync --cloud # umbrella: fetch all, skip reconcile
|
|
8
8
|
* agents sync --local # umbrella: reconcile all, no fetch
|
|
9
|
+
* agents sync system # one repo: git pull --rebase (pull-only mirror)
|
|
10
|
+
* agents sync user # one repo: git pull --rebase + push
|
|
9
11
|
* agents sync claude # one agent: uses default/sole installed version
|
|
10
12
|
* agents sync claude@2.1.142 # one agent: explicit version
|
|
11
13
|
* agents sync claude@latest # one agent: newest installed
|
|
@@ -29,18 +31,22 @@
|
|
|
29
31
|
*/
|
|
30
32
|
import * as path from 'path';
|
|
31
33
|
import chalk from 'chalk';
|
|
32
|
-
import { agentLabel, resolveAgentName } from '../lib/agents.js';
|
|
33
|
-
import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildRepoScopedSelection, listRepoNames, } from '../lib/versions.js';
|
|
34
|
+
import { agentLabel, resolveAgentName, ALL_AGENT_IDS } from '../lib/agents.js';
|
|
35
|
+
import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildRepoScopedSelection, mergeRepoScopedSelections, listRepoNames, getVersionHomePath, } from '../lib/versions.js';
|
|
36
|
+
import { capableAgents } from '../lib/capabilities.js';
|
|
37
|
+
import { parseHookManifest, registerHooksToSettings } from '../lib/hooks.js';
|
|
34
38
|
import { compileRulesForProject } from '../lib/rules/compile.js';
|
|
35
39
|
import { runLaunchSync } from '../lib/project-launch.js';
|
|
36
40
|
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
37
41
|
import { runUmbrellaSync } from '../lib/sync-umbrella.js';
|
|
38
42
|
import { addHostOption } from '../lib/hosts/option.js';
|
|
43
|
+
import { syncRepoGit } from '../lib/git.js';
|
|
44
|
+
import { getSystemAgentsDir, getUserAgentsDir, getEnabledExtraRepos } from '../lib/state.js';
|
|
39
45
|
/** Register the `agents sync` command. */
|
|
40
46
|
export function registerSyncCommand(program) {
|
|
41
47
|
addHostOption(program.command('sync [agentSpec] [repo]'))
|
|
42
48
|
.summary('Make this machine current, or sync resources into one agent')
|
|
43
|
-
.description('With an [agentSpec], syncs resources (commands, skills, hooks, rules, MCPs, plugins, etc.) into that installed agent version — previews changes and lets you pick. e.g. "claude", "claude@2.1.142", a selector: @latest / @oldest / @pinned (= @default), or @all for every installed version.\n\nAppend a [repo] (or pass --repo) to scope the sync to a single DotAgent repo — system / user / project / <alias>. e.g. "agents sync claude@all system" reconciles only the system repo\'s resources into every installed Claude.\n\nWith NO agent, runs the umbrella verb: fetch
|
|
49
|
+
.description('With an [agentSpec], syncs resources (commands, skills, hooks, rules, MCPs, plugins, etc.) into that installed agent version — previews changes and lets you pick. e.g. "claude", "claude@2.1.142", a selector: @latest / @oldest / @pinned (= @default), or @all for every installed version.\n\nAppend a [repo] (or pass --repo) to scope the sync to a single DotAgent repo — system / user / project / <alias>. e.g. "agents sync claude@all system" reconciles only the system repo\'s resources into every installed Claude.\n\nGive a DotAgent repo name ALONE — "agents sync system" / "agents sync user" / "agents sync <alias>" — to git-sync that one repo: refuse if the tree is dirty, else git pull --rebase against origin. The user repo and extra aliases also push local commits up; the system repo is a pull-only mirror.\n\nWith NO agent, runs the umbrella verb: fetch the config repos then reconcile them into every installed agent. Secrets and sessions are opt-in — add --secrets to pull secret bundles or --sessions to sync transcripts (sessions are also queryable live via "agents sessions --host <machine>"). Also: --cloud (fetch only), --local (reconcile only).')
|
|
44
50
|
.option('--agent <agent>', 'Agent identifier (legacy form; prefer the positional spec)')
|
|
45
51
|
.option('--agent-version <version>', 'Version to sync into (legacy form; prefer "agent@version")')
|
|
46
52
|
.option('--repo <name>', 'Scope the sync to a single DotAgent repo: system / user / project / <alias> (also accepted as a positional)')
|
|
@@ -60,6 +66,135 @@ export function registerSyncCommand(program) {
|
|
|
60
66
|
await runSync(agentSpec, repo, opts);
|
|
61
67
|
});
|
|
62
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve a DotAgent repo name to its git working directory + whether local
|
|
71
|
+
* commits should be pushed. `system` is a pull-only mirror of the npm-shipped
|
|
72
|
+
* upstream; `user` and enabled extra aliases are user-owned and push. `project`
|
|
73
|
+
* (and unknown names) return null — the project `.agents/` lives inside the
|
|
74
|
+
* user's own project repo and is not independently git-synced here.
|
|
75
|
+
*/
|
|
76
|
+
function resolveRepoGitTarget(repo) {
|
|
77
|
+
if (repo === 'system')
|
|
78
|
+
return { dir: getSystemAgentsDir(), push: false };
|
|
79
|
+
if (repo === 'user')
|
|
80
|
+
return { dir: getUserAgentsDir(), push: true };
|
|
81
|
+
const extra = getEnabledExtraRepos().find((e) => e.alias === repo);
|
|
82
|
+
if (extra)
|
|
83
|
+
return { dir: extra.dir, push: true };
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* `agents sync <repo>` — git-sync a single DotAgent repo: refuse on a dirty
|
|
88
|
+
* tree, else pull --rebase against origin, pushing local commits for user-owned
|
|
89
|
+
* repos. Delegates the git work to `syncRepoGit`.
|
|
90
|
+
*/
|
|
91
|
+
async function runRepoGitSync(repo, quiet, outLog, errLog) {
|
|
92
|
+
const target = resolveRepoGitTarget(repo);
|
|
93
|
+
if (!target) {
|
|
94
|
+
errLog(chalk.red(`The '${repo}' repo isn't independently git-synced.`));
|
|
95
|
+
errLog(chalk.gray('Syncable repos: system (pull-only), user, and enabled extra-repo aliases.'));
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!quiet)
|
|
100
|
+
outLog(chalk.bold(`Syncing ${repo} repo…`) + chalk.gray(` (${target.dir})`));
|
|
101
|
+
const result = await syncRepoGit(target.dir, { push: target.push });
|
|
102
|
+
if (!result.success) {
|
|
103
|
+
errLog(chalk.red(`sync ${repo} failed: ${result.error}`));
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (!quiet) {
|
|
108
|
+
const note = result.pushed ? ' · pushed' : ' · pull-only';
|
|
109
|
+
outLog(chalk.green(`✓ ${repo} → ${result.commit}${note}`));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** Human label for a repo choice in the interactive picker. */
|
|
113
|
+
function repoChoiceLabel(repo) {
|
|
114
|
+
switch (repo) {
|
|
115
|
+
case 'system': return 'system — shared, npm-shipped defaults';
|
|
116
|
+
case 'user': return 'user — your ~/.agents config';
|
|
117
|
+
case 'project': return "project — this repo's .agents";
|
|
118
|
+
default: return `${repo} — extra repo`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Interactive bare `agents sync` (TTY, no flags): two checklists — which
|
|
123
|
+
* DotAgent repos to sync FROM, and which installed agents to sync INTO. Then
|
|
124
|
+
* freshen the selected git-syncable repos (pull-only) and reconcile the chosen
|
|
125
|
+
* repos' resources into each selected agent's default version, registering
|
|
126
|
+
* hooks so synced hook scripts actually fire.
|
|
127
|
+
*/
|
|
128
|
+
async function runInteractiveReconcile(opts, outLog, errLog) {
|
|
129
|
+
const { checkbox } = await import('@inquirer/prompts');
|
|
130
|
+
const cwd = opts.cwd || process.cwd();
|
|
131
|
+
const installedAgents = ALL_AGENT_IDS.filter((a) => listInstalledVersions(a).length > 0);
|
|
132
|
+
if (installedAgents.length === 0) {
|
|
133
|
+
errLog(chalk.red('No agents installed. Install one: agents add claude@latest'));
|
|
134
|
+
process.exitCode = 1;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
let repos;
|
|
138
|
+
let agents;
|
|
139
|
+
try {
|
|
140
|
+
repos = await checkbox({
|
|
141
|
+
message: 'Sync resources FROM which repos?',
|
|
142
|
+
choices: listRepoNames().map((r) => ({ value: r, name: repoChoiceLabel(r), checked: true })),
|
|
143
|
+
});
|
|
144
|
+
if (repos.length === 0) {
|
|
145
|
+
outLog(chalk.gray('No repos selected. Nothing to do.'));
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
agents = await checkbox({
|
|
149
|
+
message: 'Sync INTO which agents?',
|
|
150
|
+
choices: installedAgents.map((a) => ({ value: a, name: agentLabel(a), checked: true })),
|
|
151
|
+
});
|
|
152
|
+
if (agents.length === 0) {
|
|
153
|
+
outLog(chalk.gray('No agents selected. Nothing to do.'));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
catch (e) {
|
|
158
|
+
if (isPromptCancelled(e)) {
|
|
159
|
+
outLog(chalk.gray('Cancelled. No changes made.'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
throw e;
|
|
163
|
+
}
|
|
164
|
+
// 1. Freshen the selected git-syncable repos (pull-only; `project` has no
|
|
165
|
+
// independent remote). Failures are non-fatal — reconcile still runs.
|
|
166
|
+
for (const repo of repos) {
|
|
167
|
+
const target = resolveRepoGitTarget(repo);
|
|
168
|
+
if (!target)
|
|
169
|
+
continue;
|
|
170
|
+
const res = await syncRepoGit(target.dir, { push: false });
|
|
171
|
+
if (res.success)
|
|
172
|
+
outLog(chalk.gray(` pulled ${repo} → ${res.commit}`));
|
|
173
|
+
else
|
|
174
|
+
outLog(chalk.yellow(` ! ${repo}: ${(res.error ?? 'pull failed').split('\n')[0]}`));
|
|
175
|
+
}
|
|
176
|
+
// 2. One selection spanning the chosen repos.
|
|
177
|
+
const selection = mergeRepoScopedSelections(repos, cwd);
|
|
178
|
+
const hasResources = selection.memory === 'all' || Object.entries(selection).some(([kind, v]) => kind !== 'memory' && Array.isArray(v) && v.length > 0);
|
|
179
|
+
if (!hasResources) {
|
|
180
|
+
outLog(chalk.gray(`Nothing from ${repos.join(', ')} to sync.`));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
// 3. Reconcile into each selected agent's default (or sole) version, then
|
|
184
|
+
// register hooks so synced hook scripts fire.
|
|
185
|
+
const hookManifest = parseHookManifest();
|
|
186
|
+
const hookCapable = new Set(capableAgents('hooks'));
|
|
187
|
+
for (const agentId of agents) {
|
|
188
|
+
const version = resolveVersion(agentId, cwd) || listInstalledVersions(agentId).slice(-1)[0];
|
|
189
|
+
if (!version)
|
|
190
|
+
continue;
|
|
191
|
+
const result = syncResourcesToVersion(agentId, version, selection, { cwd });
|
|
192
|
+
printSyncDetail(result, agentId, version, cwd);
|
|
193
|
+
if (result.hooks && hookCapable.has(agentId) && Object.keys(hookManifest).length > 0) {
|
|
194
|
+
registerHooksToSettings(agentId, getVersionHomePath(agentId, version), hookManifest);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
63
198
|
/**
|
|
64
199
|
* The umbrella verb: bare `agents sync` (no agent) makes this machine current.
|
|
65
200
|
* Resolves the flags + a secrets passphrase (env-only for now; tokenized auth
|
|
@@ -67,6 +202,14 @@ export function registerSyncCommand(program) {
|
|
|
67
202
|
* a one-line summary. Stage failures are non-fatal and surfaced as warnings.
|
|
68
203
|
*/
|
|
69
204
|
async function runUmbrella(opts, quiet, outLog, errLog) {
|
|
205
|
+
// Interactive bare `agents sync` (a TTY, no --yes, no scope flag) drops into
|
|
206
|
+
// the two-checklist picker: which repos to sync from, which agents to sync
|
|
207
|
+
// into. Any explicit flag or --yes keeps the non-interactive umbrella below.
|
|
208
|
+
const anyExplicitFlag = !!(opts.repos || opts.secrets || opts.sessions || opts.cloud || opts.local);
|
|
209
|
+
if (!quiet && !opts.yes && !anyExplicitFlag && isInteractiveTerminal()) {
|
|
210
|
+
await runInteractiveReconcile(opts, outLog, errLog);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
70
213
|
const flags = {
|
|
71
214
|
repos: opts.repos,
|
|
72
215
|
secrets: opts.secrets,
|
|
@@ -126,6 +269,15 @@ async function runSync(agentSpec, repoArg, opts) {
|
|
|
126
269
|
// when an '@' was actually typed, keeping bare `claude` on the
|
|
127
270
|
// default-version path.
|
|
128
271
|
let selector;
|
|
272
|
+
// Repo-level git sync: a DotAgent repo name given ALONE (no agent, no second
|
|
273
|
+
// positional) means "git-sync that repo" — pull --rebase, and push for
|
|
274
|
+
// user-owned repos. This is distinct from the [repo] resource-scoping arg
|
|
275
|
+
// below, and it precedes agent-spec parsing because repo names like
|
|
276
|
+
// "system"/"user" would otherwise fail parseAgentSpec.
|
|
277
|
+
if (agentSpec && !opts.agent && !repoArg && listRepoNames().includes(agentSpec)) {
|
|
278
|
+
await runRepoGitSync(agentSpec, quiet, outLog, errLog);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
129
281
|
if (agentSpec) {
|
|
130
282
|
const parsed = parseAgentSpec(agentSpec);
|
|
131
283
|
if (!parsed) {
|
package/dist/commands/teams.js
CHANGED
|
@@ -5,6 +5,8 @@ import * as path from 'path';
|
|
|
5
5
|
import { AgentManager, checkAllClis, checkCliSignedIn, getAgentsDir, resolveSignInAdvisory, VALID_TASK_TYPES, } from '../lib/teams/agents.js';
|
|
6
6
|
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
7
7
|
import { runSupervisor } from '../lib/teams/supervisor.js';
|
|
8
|
+
import { debug } from '../lib/teams/debug.js';
|
|
9
|
+
import { runPrWatch, DEFAULT_MAX_WAVES, } from '../lib/teams/pr-watch.js';
|
|
8
10
|
import { handleSpawn, handleStatus, handleStop, handleTasks, toTaskStatusSummary, } from '../lib/teams/api.js';
|
|
9
11
|
import { createTeam, ensureTeam, getTeam, loadTeams, removeTeam, teamExists, } from '../lib/teams/registry.js';
|
|
10
12
|
import { setHelpSections } from '../lib/help.js';
|
|
@@ -243,6 +245,105 @@ async function runOneWave(mgr, team, json) {
|
|
|
243
245
|
}
|
|
244
246
|
}
|
|
245
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Path to the pr-watch dedupe-state file for a team. Persists the set of
|
|
250
|
+
* already-handled check/comment dedupe keys so a restart of `teams pr-watch`
|
|
251
|
+
* never re-spawns a fix wave for a failure it already reacted to. Lives in the
|
|
252
|
+
* teammate base dir; it's a flat file so loadExistingAgents (dir-only) skips it.
|
|
253
|
+
*/
|
|
254
|
+
async function prWatchStatePath(team) {
|
|
255
|
+
const safe = team.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
256
|
+
return path.join(await getAgentsDir(), `pr-watch-${safe}.json`);
|
|
257
|
+
}
|
|
258
|
+
async function loadPrWatchState(team) {
|
|
259
|
+
try {
|
|
260
|
+
const raw = await fs.readFile(await prWatchStatePath(team), 'utf-8');
|
|
261
|
+
const parsed = JSON.parse(raw);
|
|
262
|
+
const handled = new Set(Array.isArray(parsed?.handled) ? parsed.handled : []);
|
|
263
|
+
const waves = new Map(parsed?.waves && typeof parsed.waves === 'object'
|
|
264
|
+
? Object.entries(parsed.waves).map(([k, v]) => [k, Number(v) || 0])
|
|
265
|
+
: []);
|
|
266
|
+
return { handled, waves };
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return { handled: new Set(), waves: new Map() };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
async function savePrWatchState(team, state) {
|
|
273
|
+
try {
|
|
274
|
+
await fs.writeFile(await prWatchStatePath(team), JSON.stringify({ handled: [...state.handled], waves: Object.fromEntries(state.waves) }, null, 2));
|
|
275
|
+
}
|
|
276
|
+
catch (err) {
|
|
277
|
+
debug(`Could not persist pr-watch state for ${team}: ${err.message}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Resolve the PRs opened by a team's teammates, de-duplicated by PR URL. A
|
|
282
|
+
* teammate's PR URL comes from its own `pr_url` field or, failing that, the
|
|
283
|
+
* session it ran (detected from the transcript's `gh pr create`). The teammate
|
|
284
|
+
* name is the `--after` anchor for the fix/bugfix teammate we spawn.
|
|
285
|
+
*/
|
|
286
|
+
async function resolvePrWatchTargets(mgr, team) {
|
|
287
|
+
const status = await handleStatus(mgr, team, 'all');
|
|
288
|
+
const sessions = await resolveTeammateSessions(status.agents);
|
|
289
|
+
const byPr = new Map();
|
|
290
|
+
for (const a of status.agents) {
|
|
291
|
+
const prUrl = a.pr_url || sessions.get(a.agent_id)?.prUrl || null;
|
|
292
|
+
if (!prUrl)
|
|
293
|
+
continue;
|
|
294
|
+
if (byPr.has(prUrl))
|
|
295
|
+
continue; // first teammate to own a PR is its source
|
|
296
|
+
byPr.set(prUrl, { prUrl, sourceTeammate: a.name ?? null });
|
|
297
|
+
}
|
|
298
|
+
return [...byPr.values()];
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* React to one decided pr-watch action by spawning a follow-up teammate on the
|
|
302
|
+
* same PR — a ci-fix teammate on RED CI, or a `bugfix` teammate on a new review
|
|
303
|
+
* comment — linked `--after` the teammate that opened the PR so it slots into
|
|
304
|
+
* the same DAG the supervisor already drains. Returns the teammate label.
|
|
305
|
+
*/
|
|
306
|
+
async function reactWithTeammate(mgr, team, action, prompt) {
|
|
307
|
+
// Unique name per reaction. The dedupe key is stable across waves (keyed on the
|
|
308
|
+
// check NAME), so the wave number is what keeps successive fixers' names — and
|
|
309
|
+
// their worktrees — distinct within the team.
|
|
310
|
+
const uniq = action.dedupeKey.replace(/[^A-Za-z0-9]/g, '').slice(-10) || `${action.wave}`;
|
|
311
|
+
const slug = `${uniq}-w${action.wave}`;
|
|
312
|
+
const name = action.kind === 'ci-fix' ? `cifix-${slug}` : `bugfix-${slug}`;
|
|
313
|
+
const taskType = action.kind === 'review-fix' ? 'bugfix' : null;
|
|
314
|
+
// Link --after the source teammate when it's a real, resolvable sibling; a
|
|
315
|
+
// missing source means the PR couldn't be traced to a named teammate, so the
|
|
316
|
+
// fix runs immediately with no dependency edge.
|
|
317
|
+
const after = [];
|
|
318
|
+
if (action.sourceTeammate) {
|
|
319
|
+
const resolved = await mgr.resolveAgentIdInTask(team, action.sourceTeammate);
|
|
320
|
+
if (resolved.kind === 'ok')
|
|
321
|
+
after.push(action.sourceTeammate);
|
|
322
|
+
}
|
|
323
|
+
// Isolate each fixer in its own worktree so concurrent `gh pr checkout`s (across
|
|
324
|
+
// PRs, or across waves) never clash in a shared cwd. Requires a git repo — which
|
|
325
|
+
// pr-watch always is, since it operates on PRs; fall back to the cwd only when
|
|
326
|
+
// the checkout somehow isn't a repo.
|
|
327
|
+
const baseCwd = process.cwd();
|
|
328
|
+
let worktreeName = null;
|
|
329
|
+
let worktreePath = null;
|
|
330
|
+
let cwd = baseCwd;
|
|
331
|
+
if (await isGitRepo(baseCwd)) {
|
|
332
|
+
try {
|
|
333
|
+
worktreeName = `prwatch-${name}`;
|
|
334
|
+
worktreePath = await createWorktree(baseCwd, worktreeName);
|
|
335
|
+
cwd = worktreePath;
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
debug(`pr-watch: could not create worktree for ${name}: ${err.message}`);
|
|
339
|
+
worktreeName = null;
|
|
340
|
+
worktreePath = null;
|
|
341
|
+
cwd = baseCwd;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const result = await handleSpawn(mgr, team, 'claude', prompt, cwd, 'edit', 'medium', null, cwd, null, name, after, null, null, taskType, null, null, null, null, worktreeName, worktreePath);
|
|
345
|
+
return result.name ?? shortId(result.agent_id);
|
|
346
|
+
}
|
|
246
347
|
// Pick the display handle for a teammate: explicit teammate name, Claude
|
|
247
348
|
// session label, then the 8-char UUID prefix.
|
|
248
349
|
function handle(a) {
|
|
@@ -1193,10 +1294,22 @@ export function registerTeamsCommands(program) {
|
|
|
1193
1294
|
const intervalMs = Math.max(1000, Number.parseInt(opts.interval, 10) * 1000 || 8000);
|
|
1194
1295
|
const maxWaves = Math.max(1, Number.parseInt(opts.maxWaves, 10) || 1000);
|
|
1195
1296
|
const json = isJsonMode(opts);
|
|
1297
|
+
// Live budget kill-switch (issue #399). Dormant when no caps set — the
|
|
1298
|
+
// factory dropping this in returns null and runSupervisor short-circuits.
|
|
1299
|
+
const { createTeamBudgetWatcher } = await import('../lib/budget/live-team.js');
|
|
1300
|
+
const budgetWatcher = createTeamBudgetWatcher({
|
|
1301
|
+
manager: mgr,
|
|
1302
|
+
team,
|
|
1303
|
+
cwd: process.cwd(),
|
|
1304
|
+
onBreach: (b) => {
|
|
1305
|
+
process.stderr.write(`[budget] cap ${b.cap} exceeded ($${b.spend.toFixed(2)} > $${b.limit.toFixed(2)}) — stopping team ${team}\n`);
|
|
1306
|
+
},
|
|
1307
|
+
});
|
|
1196
1308
|
const result = await runSupervisor(mgr, {
|
|
1197
1309
|
team,
|
|
1198
1310
|
intervalMs,
|
|
1199
1311
|
maxWaves,
|
|
1312
|
+
budgetWatcher,
|
|
1200
1313
|
onWave: (s) => {
|
|
1201
1314
|
const ts = s.timestamp.slice(11, 19);
|
|
1202
1315
|
if (json) {
|
|
@@ -1222,6 +1335,110 @@ export function registerTeamsCommands(program) {
|
|
|
1222
1335
|
else if (result.stoppedBy === 'signal') {
|
|
1223
1336
|
console.error(chalk.yellow(`Stopped by signal after ${result.waves} waves.`));
|
|
1224
1337
|
}
|
|
1338
|
+
else if (result.stoppedBy === 'budget') {
|
|
1339
|
+
const b = result.budgetBreach;
|
|
1340
|
+
console.error(chalk.red(`Budget kill-switch tripped after ${result.waves} waves` +
|
|
1341
|
+
(b ? ` (cap ${b.cap}: $${b.spend.toFixed(2)} > $${b.limit.toFixed(2)})` : '') +
|
|
1342
|
+
`.`));
|
|
1343
|
+
process.exitCode = 7; // Mirrors BUDGET_KILL_EXIT_CODE for CI/headless.
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
// pr-watch — autonomous PR lifecycle (issue #338)
|
|
1347
|
+
addHostOption(teams.command('pr-watch [team]'))
|
|
1348
|
+
.description('Watch the PRs a team opened and react autonomously: RED CI -> spawn a fix teammate with the failure logs; new review comment -> route a bugfix teammate. Both slot into the team DAG (visible in `teams status`).')
|
|
1349
|
+
.option('--interval <seconds>', 'Seconds between polls (default 30)', '30')
|
|
1350
|
+
.option('--max-polls <n>', 'Stop after this many polls (default: run until Ctrl-C)', '0')
|
|
1351
|
+
.option('--max-waves <n>', `Fix waves per PR before escalating to a human (default ${DEFAULT_MAX_WAVES})`, String(DEFAULT_MAX_WAVES))
|
|
1352
|
+
.option('--once', 'Poll a single time and exit (equivalent to --max-polls 1)')
|
|
1353
|
+
.option('--json', 'Emit one JSON line per pr-watch event')
|
|
1354
|
+
.action(async (team, opts) => {
|
|
1355
|
+
const mgr = mkManager();
|
|
1356
|
+
if (!team) {
|
|
1357
|
+
const picked = await pickTeamOr(mgr, 'agents teams pr-watch');
|
|
1358
|
+
if (!picked)
|
|
1359
|
+
return;
|
|
1360
|
+
team = picked;
|
|
1361
|
+
}
|
|
1362
|
+
const resolvedTeam = team;
|
|
1363
|
+
const intervalMs = Math.max(1000, (Number.parseInt(opts.interval, 10) || 30) * 1000);
|
|
1364
|
+
const maxPolls = opts.once ? 1 : Math.max(0, Number.parseInt(opts.maxPolls, 10) || 0);
|
|
1365
|
+
const maxWaves = Math.max(1, Number.parseInt(opts.maxWaves, 10) || DEFAULT_MAX_WAVES);
|
|
1366
|
+
const json = isJsonMode(opts);
|
|
1367
|
+
const { handled, waves } = await loadPrWatchState(resolvedTeam);
|
|
1368
|
+
let stopSignal = false;
|
|
1369
|
+
const onSig = () => { stopSignal = true; };
|
|
1370
|
+
process.once('SIGINT', onSig);
|
|
1371
|
+
process.once('SIGTERM', onSig);
|
|
1372
|
+
const emit = (e) => {
|
|
1373
|
+
if (json) {
|
|
1374
|
+
console.log(JSON.stringify(e));
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const ts = e.timestamp.slice(11, 19);
|
|
1378
|
+
if (e.type === 'poll') {
|
|
1379
|
+
console.log(`[${ts}] polled ${chalk.cyan(resolvedTeam)} — ${e.targets} PR(s) under watch`);
|
|
1380
|
+
}
|
|
1381
|
+
else if (e.type === 'spawned') {
|
|
1382
|
+
const verb = e.action.kind === 'ci-fix' ? 'CI-fix' : 'bugfix';
|
|
1383
|
+
const detail = e.action.kind === 'ci-fix'
|
|
1384
|
+
? `check ${chalk.yellow(e.action.check.name)}`
|
|
1385
|
+
: `comment ${chalk.yellow('#' + e.action.comment.id)}`;
|
|
1386
|
+
console.log(`[${ts}] ${chalk.green('spawned')} ${verb} teammate ${chalk.cyan(e.label ?? '?')} ` +
|
|
1387
|
+
`(wave ${e.action.wave}/${maxWaves}) for ${detail} on ${e.action.prUrl}`);
|
|
1388
|
+
}
|
|
1389
|
+
else if (e.type === 'needs-human') {
|
|
1390
|
+
console.error(`[${ts}] ${chalk.red('needs human')} — ${e.prUrl} still failing after ${e.waves} wave(s) ` +
|
|
1391
|
+
`(${e.subject}). Not spawning again; hand it to a human.`);
|
|
1392
|
+
}
|
|
1393
|
+
else if (e.type === 'error') {
|
|
1394
|
+
console.error(`[${ts}] ${chalk.red('error')} on ${e.prUrl}: ${e.message}`);
|
|
1395
|
+
}
|
|
1396
|
+
};
|
|
1397
|
+
// A fixer's dedupe guard clears once it settles, so a still-red check can
|
|
1398
|
+
// spawn its next (budget-bounded) wave. Terminal = completed/failed/stopped.
|
|
1399
|
+
const reactionSettled = async (label) => {
|
|
1400
|
+
const roster = await mgr.listByTask(resolvedTeam);
|
|
1401
|
+
const teammate = roster.find((a) => a.name === label || shortId(a.agentId) === label);
|
|
1402
|
+
if (!teammate)
|
|
1403
|
+
return false;
|
|
1404
|
+
const s = String(teammate.status);
|
|
1405
|
+
return s === 'completed' || s === 'failed' || s === 'stopped';
|
|
1406
|
+
};
|
|
1407
|
+
try {
|
|
1408
|
+
const result = await runPrWatch({
|
|
1409
|
+
resolveTargets: async () => {
|
|
1410
|
+
// Drive the DAG each pass so fix/bugfix teammates staged --after a
|
|
1411
|
+
// now-completed source teammate actually launch — no separate
|
|
1412
|
+
// `teams start --watch` needed.
|
|
1413
|
+
await mgr.rescanFromDisk();
|
|
1414
|
+
await mgr.startReady(resolvedTeam);
|
|
1415
|
+
return resolvePrWatchTargets(mgr, resolvedTeam);
|
|
1416
|
+
},
|
|
1417
|
+
react: (action, prompt) => reactWithTeammate(mgr, resolvedTeam, action, prompt),
|
|
1418
|
+
reactionSettled,
|
|
1419
|
+
onEvent: emit,
|
|
1420
|
+
}, {
|
|
1421
|
+
intervalMs,
|
|
1422
|
+
maxPolls,
|
|
1423
|
+
maxWaves,
|
|
1424
|
+
handled,
|
|
1425
|
+
waves,
|
|
1426
|
+
shouldStop: () => stopSignal,
|
|
1427
|
+
});
|
|
1428
|
+
await savePrWatchState(resolvedTeam, { handled: result.handled, waves: result.waves });
|
|
1429
|
+
if (!json) {
|
|
1430
|
+
const humanNote = result.neededHuman > 0
|
|
1431
|
+
? ` ${result.neededHuman} PR(s) escalated to a human.`
|
|
1432
|
+
: '';
|
|
1433
|
+
console.log(chalk.gray(`pr-watch stopped (${result.stoppedBy}) after ${result.polls} poll(s); ` +
|
|
1434
|
+
`spawned ${result.spawned} follow-up teammate(s).${humanNote}`));
|
|
1435
|
+
console.log(chalk.gray(`Check the team: agents teams status ${resolvedTeam}`));
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
finally {
|
|
1439
|
+
process.off('SIGINT', onSig);
|
|
1440
|
+
process.off('SIGTERM', onSig);
|
|
1441
|
+
}
|
|
1225
1442
|
});
|
|
1226
1443
|
// stop
|
|
1227
1444
|
addHostOption(teams.command('stop [team] [teammate]'))
|
|
@@ -155,10 +155,8 @@ async function versionPruneAction(specs, options, commandName) {
|
|
|
155
155
|
console.log(chalk.green(`Moved ${agentLabel(agentConfig.id)}@${v} to trash`));
|
|
156
156
|
moved.push({ agent, version: v });
|
|
157
157
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log(chalk.yellow(`Default version removed. Run: agents use ${agent}@<version> to set a new default`));
|
|
161
|
-
}
|
|
158
|
+
// Default reassignment/clearing is handled at the source in
|
|
159
|
+
// removeVersion() so it applies to every removal path uniformly.
|
|
162
160
|
const remaining = listInstalledVersions(agent);
|
|
163
161
|
if (remaining.length === 0) {
|
|
164
162
|
removeShim(agent);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `agents watchdog` — the watchdog CONSUMER (RUSH-1415).
|
|
3
|
+
*
|
|
4
|
+
* Runs the tick loop that ties the merged pieces together: list active sessions,
|
|
5
|
+
* classify stalls, read the tail, decide (deterministic promise-without-toolcall
|
|
6
|
+
* by default), run the resolver safety gate, and inject "Continue." into the EXACT
|
|
7
|
+
* split — all without the Swift menu-bar. See src/lib/watchdog/runner.ts.
|
|
8
|
+
*
|
|
9
|
+
* agents watchdog one tick, dry — prints what it WOULD nudge/skip and why
|
|
10
|
+
* agents watchdog --nudge one tick, actually injects (explicit opt-in)
|
|
11
|
+
* agents watchdog --watch daemon loop (poll every --interval)
|
|
12
|
+
* agents watchdog --json machine-readable tick output (for the menu-bar)
|
|
13
|
+
* agents watchdog enable|disable flip the global auto-nudge sentinel (default OFF)
|
|
14
|
+
* agents watchdog policy <id> <p> per-session policy: off | keep | handsoff
|
|
15
|
+
*/
|
|
16
|
+
import type { Command } from 'commander';
|
|
17
|
+
/** Register the `agents watchdog` command tree. */
|
|
18
|
+
export declare function registerWatchdogCommand(program: Command): void;
|