@phnx-labs/agents-cli 1.20.72 → 1.20.74
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 +395 -0
- package/README.md +40 -4
- package/dist/bin/agents +0 -0
- package/dist/commands/activity.d.ts +12 -0
- package/dist/commands/activity.js +68 -0
- package/dist/commands/apply.d.ts +13 -0
- package/dist/commands/apply.js +28 -3
- package/dist/commands/browser.js +1 -1
- package/dist/commands/cloud.js +21 -11
- package/dist/commands/doctor.js +21 -5
- package/dist/commands/events.d.ts +11 -10
- package/dist/commands/events.js +30 -21
- package/dist/commands/exec.d.ts +34 -0
- package/dist/commands/exec.js +337 -39
- package/dist/commands/export.d.ts +11 -0
- package/dist/commands/export.js +215 -0
- package/dist/commands/feed.d.ts +13 -0
- package/dist/commands/feed.js +130 -29
- package/dist/commands/hq.d.ts +2 -0
- package/dist/commands/hq.js +58 -0
- package/dist/commands/import.js +51 -3
- package/dist/commands/lease.d.ts +19 -0
- package/dist/commands/lease.js +138 -11
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.js +123 -0
- package/dist/commands/logs.js +1 -1
- package/dist/commands/mine.d.ts +27 -0
- package/dist/commands/mine.js +207 -0
- package/dist/commands/monitors.js +79 -49
- package/dist/commands/plugins.js +1 -0
- package/dist/commands/profiles.js +158 -0
- package/dist/commands/repo.js +2 -60
- package/dist/commands/resources.d.ts +5 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/routines.d.ts +2 -0
- package/dist/commands/routines.js +139 -37
- package/dist/commands/secrets.js +234 -47
- package/dist/commands/send.d.ts +15 -0
- package/dist/commands/send.js +62 -0
- package/dist/commands/sessions-tail.d.ts +1 -0
- package/dist/commands/sessions-tail.js +20 -9
- package/dist/commands/sessions.js +18 -3
- package/dist/commands/setup-mine.d.ts +18 -0
- package/dist/commands/setup-mine.js +106 -0
- package/dist/commands/setup-share.js +2 -2
- package/dist/commands/setup.js +9 -2
- package/dist/commands/share.d.ts +8 -1
- package/dist/commands/share.js +122 -37
- package/dist/commands/ssh.d.ts +9 -0
- package/dist/commands/ssh.js +196 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +11 -2
- package/dist/commands/versions.js +36 -5
- package/dist/commands/view.js +75 -27
- package/dist/index.js +80 -10
- package/dist/lib/activity.d.ts +124 -0
- package/dist/lib/activity.js +542 -0
- package/dist/lib/agents.d.ts +35 -0
- package/dist/lib/agents.js +79 -6
- package/dist/lib/artifact-actions.d.ts +1 -1
- package/dist/lib/artifact-actions.js +1 -1
- package/dist/lib/ask-classifier.d.ts +2 -1
- package/dist/lib/ask-classifier.js +3 -3
- package/dist/lib/auth-health.d.ts +32 -4
- package/dist/lib/auth-health.js +40 -1
- package/dist/lib/auto-pull.js +8 -1
- package/dist/lib/brand.d.ts +40 -0
- package/dist/lib/brand.js +122 -0
- package/dist/lib/browser/drivers/ssh.js +4 -7
- package/dist/lib/browser/service.js +6 -8
- package/dist/lib/channels/providers/index.d.ts +2 -0
- package/dist/lib/channels/providers/index.js +20 -0
- package/dist/lib/channels/providers/mailbox.d.ts +2 -0
- package/dist/lib/channels/providers/mailbox.js +26 -0
- package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
- package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
- package/dist/lib/channels/providers/rush.d.ts +6 -0
- package/dist/lib/channels/providers/rush.js +55 -0
- package/dist/lib/channels/registry.d.ts +42 -0
- package/dist/lib/channels/registry.js +20 -0
- package/dist/lib/channels/resolve.d.ts +11 -0
- package/dist/lib/channels/resolve.js +12 -0
- package/dist/lib/cli-resources.d.ts +18 -0
- package/dist/lib/cli-resources.js +53 -4
- package/dist/lib/cloud/codex.js +6 -3
- package/dist/lib/cloud/factory.d.ts +1 -0
- package/dist/lib/cloud/factory.js +10 -4
- package/dist/lib/cloud/rush.d.ts +3 -1
- package/dist/lib/cloud/rush.js +6 -1
- package/dist/lib/cloud/types.d.ts +2 -0
- package/dist/lib/codex-home.d.ts +35 -0
- package/dist/lib/codex-home.js +136 -0
- package/dist/lib/config-transfer.d.ts +16 -0
- package/dist/lib/config-transfer.js +56 -0
- package/dist/lib/crabbox/cli.d.ts +44 -0
- package/dist/lib/crabbox/cli.js +120 -20
- package/dist/lib/crabbox/lease.d.ts +52 -1
- package/dist/lib/crabbox/lease.js +117 -16
- package/dist/lib/crabbox/progress.d.ts +31 -0
- package/dist/lib/crabbox/progress.js +76 -0
- package/dist/lib/crabbox/runtimes.d.ts +4 -0
- package/dist/lib/crabbox/runtimes.js +33 -8
- package/dist/lib/crabbox/setup-copy.d.ts +87 -0
- package/dist/lib/crabbox/setup-copy.js +127 -0
- package/dist/lib/daemon.d.ts +5 -5
- package/dist/lib/daemon.js +9 -9
- package/dist/lib/drive-sync.js +6 -3
- package/dist/lib/event-stream.d.ts +36 -0
- package/dist/lib/event-stream.js +68 -0
- package/dist/lib/events.d.ts +1 -1
- package/dist/lib/exec.d.ts +11 -0
- package/dist/lib/exec.js +23 -2
- package/dist/lib/export.d.ts +72 -0
- package/dist/lib/export.js +269 -0
- package/dist/lib/feed-policy.d.ts +1 -1
- package/dist/lib/feed-policy.js +14 -9
- package/dist/lib/feed-ranking.d.ts +32 -0
- package/dist/lib/feed-ranking.js +224 -0
- package/dist/lib/feed.d.ts +35 -2
- package/dist/lib/feed.js +66 -3
- package/dist/lib/fleet/auth-sync.d.ts +53 -0
- package/dist/lib/fleet/auth-sync.js +92 -0
- package/dist/lib/fleet/remote-login.d.ts +187 -0
- package/dist/lib/fleet/remote-login.js +556 -0
- package/dist/lib/format.d.ts +30 -3
- package/dist/lib/format.js +34 -5
- package/dist/lib/fs-atomic.d.ts +7 -2
- package/dist/lib/fs-atomic.js +8 -12
- package/dist/lib/git.d.ts +16 -0
- package/dist/lib/git.js +79 -2
- package/dist/lib/heal.js +11 -3
- package/dist/lib/hosts/progress.d.ts +28 -10
- package/dist/lib/hosts/progress.js +79 -22
- package/dist/lib/hosts/remote-cmd.js +4 -0
- package/dist/lib/hq/floor.d.ts +87 -0
- package/dist/lib/hq/floor.js +226 -0
- package/dist/lib/import.d.ts +23 -0
- package/dist/lib/import.js +74 -2
- package/dist/lib/isolation-boundary-report.d.ts +10 -0
- package/dist/lib/isolation-boundary-report.js +35 -0
- package/dist/lib/menubar/install-menubar.js +14 -20
- package/dist/lib/notify.d.ts +1 -0
- package/dist/lib/notify.js +3 -3
- package/dist/lib/open-url.d.ts +2 -0
- package/dist/lib/open-url.js +19 -0
- package/dist/lib/openclaw-keychain.d.ts +56 -0
- package/dist/lib/openclaw-keychain.js +236 -0
- package/dist/lib/overdue.js +10 -0
- package/dist/lib/permissions.d.ts +44 -1
- package/dist/lib/permissions.js +284 -7
- package/dist/lib/project-launch.d.ts +6 -12
- package/dist/lib/project-launch.js +13 -228
- package/dist/lib/project-resources.d.ts +7 -0
- package/dist/lib/project-resources.js +306 -0
- package/dist/lib/pty-client.d.ts +27 -0
- package/dist/lib/pty-client.js +136 -10
- package/dist/lib/refresh.js +14 -10
- package/dist/lib/resource-profiles.d.ts +26 -0
- package/dist/lib/resource-profiles.js +157 -0
- package/dist/lib/resources/permissions.js +7 -1
- package/dist/lib/resources/types.d.ts +1 -1
- package/dist/lib/resources.d.ts +1 -1
- package/dist/lib/resources.js +32 -3
- package/dist/lib/routines.d.ts +11 -0
- package/dist/lib/routines.js +56 -15
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +66 -5
- package/dist/lib/secrets/bundles.d.ts +16 -3
- package/dist/lib/secrets/bundles.js +206 -37
- package/dist/lib/secrets/icloud-import.d.ts +2 -2
- package/dist/lib/secrets/icloud-import.js +9 -6
- package/dist/lib/secrets/mcp.js +1 -1
- package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
- package/dist/lib/secrets/vault-age-helper.js +34 -0
- package/dist/lib/secrets/vault.d.ts +49 -0
- package/dist/lib/secrets/vault.js +397 -0
- package/dist/lib/self-heal/checks/path.js +3 -1
- package/dist/lib/self-heal/checks/shadowing.js +10 -2
- package/dist/lib/self-heal/checks/shims.js +19 -11
- package/dist/lib/session/cloud.d.ts +2 -2
- package/dist/lib/session/cloud.js +2 -2
- package/dist/lib/session/db.d.ts +4 -0
- package/dist/lib/session/db.js +44 -7
- package/dist/lib/session/discover.d.ts +2 -0
- package/dist/lib/session/discover.js +114 -3
- package/dist/lib/session/stream-render.d.ts +3 -0
- package/dist/lib/session/stream-render.js +130 -0
- package/dist/lib/session/types.d.ts +6 -0
- package/dist/lib/share/analytics.d.ts +13 -0
- package/dist/lib/share/analytics.js +45 -0
- package/dist/lib/share/config.d.ts +19 -5
- package/dist/lib/share/config.js +44 -8
- package/dist/lib/share/provision.d.ts +58 -6
- package/dist/lib/share/provision.js +97 -22
- package/dist/lib/share/publish.d.ts +36 -13
- package/dist/lib/share/publish.js +55 -8
- package/dist/lib/share/worker-template.js +83 -17
- package/dist/lib/shims.d.ts +35 -0
- package/dist/lib/shims.js +159 -13
- package/dist/lib/ssh-exec.d.ts +14 -0
- package/dist/lib/ssh-exec.js +57 -0
- package/dist/lib/staleness/detectors/hooks.js +25 -1
- package/dist/lib/staleness/detectors/permissions.js +66 -0
- package/dist/lib/staleness/detectors/workflows.js +20 -0
- package/dist/lib/staleness/writers/hooks.js +58 -4
- package/dist/lib/staleness/writers/permissions.js +2 -2
- package/dist/lib/staleness/writers/skills.js +1 -1
- package/dist/lib/staleness/writers/sources.d.ts +10 -1
- package/dist/lib/staleness/writers/sources.js +68 -1
- package/dist/lib/star-nudge.d.ts +45 -0
- package/dist/lib/star-nudge.js +91 -0
- package/dist/lib/startup/command-registry.d.ts +7 -1
- package/dist/lib/startup/command-registry.js +19 -3
- package/dist/lib/state.d.ts +2 -0
- package/dist/lib/state.js +17 -4
- package/dist/lib/subagents-registry.d.ts +2 -0
- package/dist/lib/subagents-registry.js +3 -0
- package/dist/lib/teams/parsers.js +214 -6
- package/dist/lib/teams/supervisor.d.ts +7 -0
- package/dist/lib/teams/supervisor.js +2 -1
- package/dist/lib/template.d.ts +1 -1
- package/dist/lib/template.js +1 -1
- package/dist/lib/triggers/webhook.js +36 -3
- package/dist/lib/types.d.ts +84 -0
- package/dist/lib/uninstall.js +1 -45
- package/dist/lib/version.d.ts +40 -0
- package/dist/lib/version.js +94 -16
- package/dist/lib/versions.d.ts +24 -0
- package/dist/lib/versions.js +269 -78
- package/dist/lib/workflows.d.ts +2 -0
- package/dist/lib/workflows.js +88 -0
- package/package.json +2 -1
- package/dist/commands/daemon.d.ts +0 -10
- package/dist/commands/daemon.js +0 -121
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
import { confirm, select } from '@inquirer/prompts';
|
|
4
|
+
import { AGENTS, agentLabel, formatAgentError, resolveAgentName } from '../lib/agents.js';
|
|
5
|
+
import { listInstalledVersions, isVersionIsolated } from '../lib/versions.js';
|
|
6
|
+
import { planExport, executeExport, CONFLICT_SUFFIX } from '../lib/export.js';
|
|
7
|
+
import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
|
|
8
|
+
function isolatedVersions(agent) {
|
|
9
|
+
return listInstalledVersions(agent).filter((v) => isVersionIsolated(agent, v));
|
|
10
|
+
}
|
|
11
|
+
/** Explain a blocker in terms of what the user should do instead. */
|
|
12
|
+
function reportBlocker(plan) {
|
|
13
|
+
const label = agentLabel(plan.agent);
|
|
14
|
+
const b = plan.blocker;
|
|
15
|
+
switch (b.kind) {
|
|
16
|
+
case 'not-installed':
|
|
17
|
+
console.log(chalk.red(`${label}@${plan.version} is not installed.`));
|
|
18
|
+
console.log(chalk.gray(` Installed: ${listInstalledVersions(plan.agent).join(', ') || 'none'}`));
|
|
19
|
+
break;
|
|
20
|
+
case 'not-isolated':
|
|
21
|
+
console.log(chalk.yellow(`${label}@${plan.version} is not an isolated install.`));
|
|
22
|
+
console.log(chalk.gray(` A normal install's config already IS ${plan.dest} (adoption symlinks it),`));
|
|
23
|
+
console.log(chalk.gray(' so there is nothing to export. To un-adopt it, use: agents uninstall'));
|
|
24
|
+
break;
|
|
25
|
+
case 'no-config':
|
|
26
|
+
console.log(chalk.yellow(`${label}@${plan.version} has no config to export yet (${b.source} is empty).`));
|
|
27
|
+
console.log(chalk.gray(` Run it once first: agents run ${plan.agent}@${plan.version}`));
|
|
28
|
+
break;
|
|
29
|
+
case 'dest-adopted':
|
|
30
|
+
console.log(chalk.red(`${b.realPath} is managed by agents-cli (adopted by ${label}@${b.adoptedVersion}).`));
|
|
31
|
+
console.log(chalk.gray(' Exporting would write into that version\'s home, not your real config.'));
|
|
32
|
+
console.log(chalk.gray(` Release it first: agents uninstall (or agents remove ${plan.agent}@${b.adoptedVersion})`));
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Unified diff between the user's file and the incoming one, via git. */
|
|
37
|
+
function printDiff(existing, incoming) {
|
|
38
|
+
try {
|
|
39
|
+
execFileSync('git', ['diff', '--no-index', '--color', '--', existing, incoming], {
|
|
40
|
+
stdio: ['ignore', 'inherit', 'ignore'],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// git diff --no-index exits 1 when files differ; output already streamed.
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function printPlan(plan, opts) {
|
|
48
|
+
const label = agentLabel(plan.agent);
|
|
49
|
+
console.log(chalk.bold(`Export ${label}@${plan.version} -> ${plan.dest} ${chalk.gray(`[${plan.mode}]`)}\n`));
|
|
50
|
+
console.log(` ${chalk.gray('from')} ${plan.source}`);
|
|
51
|
+
console.log(` ${chalk.gray('to')} ${plan.stagedPath ?? plan.dest}`);
|
|
52
|
+
console.log();
|
|
53
|
+
if (plan.mode === 'replace') {
|
|
54
|
+
console.log(chalk.yellow(` Replacing ${plan.dest} wholesale — ${plan.writes.length} file(s).`));
|
|
55
|
+
if (plan.backupPath)
|
|
56
|
+
console.log(chalk.gray(` Your current config moves to: ${plan.backupPath}`));
|
|
57
|
+
else if (plan.destKind === 'foreign-symlink') {
|
|
58
|
+
console.log(chalk.yellow(` ${plan.dest} is a symlink agents-cli does not own; it will be removed.`));
|
|
59
|
+
console.log(chalk.gray(' Its target is left untouched.'));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (plan.mode === 'staged') {
|
|
63
|
+
console.log(chalk.gray(` Writing ${plan.writes.length} file(s) into ${plan.stagedPath}.`));
|
|
64
|
+
console.log(chalk.gray(' Nothing is activated — your config is untouched.'));
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
if (plan.writes.length > 0) {
|
|
68
|
+
console.log(chalk.green(` ${plan.writes.length} new file(s) will be added:`));
|
|
69
|
+
for (const w of plan.writes.slice(0, 15))
|
|
70
|
+
console.log(chalk.gray(` + ${w.rel}`));
|
|
71
|
+
if (plan.writes.length > 15)
|
|
72
|
+
console.log(chalk.gray(` + ${plan.writes.length - 15} more`));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
console.log(chalk.gray(' No new files to add.'));
|
|
76
|
+
}
|
|
77
|
+
if (plan.conflicts.length > 0) {
|
|
78
|
+
console.log();
|
|
79
|
+
console.log(chalk.yellow(` ${plan.conflicts.length} file(s) you already have — YOURS ARE NOT MODIFIED.`));
|
|
80
|
+
console.log(chalk.gray(` The incoming version is written beside each as *${CONFLICT_SUFFIX}:`));
|
|
81
|
+
for (const c of plan.conflicts.slice(0, 15))
|
|
82
|
+
console.log(chalk.gray(` ~ ${c.rel}`));
|
|
83
|
+
if (plan.conflicts.length > 15)
|
|
84
|
+
console.log(chalk.gray(` ~ ${plan.conflicts.length - 15} more`));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (opts.diff && plan.conflicts.length > 0) {
|
|
88
|
+
for (const c of plan.conflicts) {
|
|
89
|
+
console.log(chalk.bold(`\n─── ${c.rel} ───`));
|
|
90
|
+
printDiff(c.existing, c.source);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
console.log(chalk.gray(`\n Receipt: ${plan.receiptPath}`));
|
|
94
|
+
}
|
|
95
|
+
export function registerExportCommand(program) {
|
|
96
|
+
program
|
|
97
|
+
.command('export <spec>')
|
|
98
|
+
.description('Copy an isolated install\'s config out to your real ~/.<agent>')
|
|
99
|
+
.option('--replace', 'Replace your config wholesale (yours is backed up) instead of merging')
|
|
100
|
+
.option('--staged', 'Write into ~/.<agent>/.agents-export-<ts>/ and activate nothing')
|
|
101
|
+
.option('--diff', 'Show a unified diff for each file you already have')
|
|
102
|
+
.option('--dry-run', 'Show what would change without writing anything')
|
|
103
|
+
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
104
|
+
.addHelpText('after', `
|
|
105
|
+
Modes:
|
|
106
|
+
(default) merge — additive. Your files are NEVER modified. Anything that
|
|
107
|
+
collides is written beside yours as <name>${CONFLICT_SUFFIX}
|
|
108
|
+
so you can diff and take the parts you want.
|
|
109
|
+
--replace — the isolated config becomes ~/.<agent>; yours is moved to
|
|
110
|
+
backups/<agent>/<ts>. For promoting a sandbox wholesale.
|
|
111
|
+
--staged — dump the tree into ~/.<agent>/.agents-export-<ts>/ and
|
|
112
|
+
activate nothing. For inspecting first.
|
|
113
|
+
|
|
114
|
+
Every mode writes a receipt to ~/.<agent>/.agents-cli-export.json listing exactly
|
|
115
|
+
what came from the export — so you can tell your settings from the CLI's, and undo it.
|
|
116
|
+
|
|
117
|
+
Examples:
|
|
118
|
+
agents export codex --dry-run # see the plan
|
|
119
|
+
agents export codex --diff # ...and the deltas on colliding files
|
|
120
|
+
agents export codex # additive; nothing of yours changes
|
|
121
|
+
agents export codex@0.144.6 --replace # promote the sandbox wholesale
|
|
122
|
+
|
|
123
|
+
Note: file CONTENTS are never auto-merged. The TOML parser here does not preserve
|
|
124
|
+
comments, so merging keys would silently delete them. You get both files and a diff.
|
|
125
|
+
`)
|
|
126
|
+
.action(async (spec, options) => {
|
|
127
|
+
if (options.replace && options.staged) {
|
|
128
|
+
console.error(chalk.red('--replace and --staged are mutually exclusive; pass only one.'));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
const mode = options.replace ? 'replace' : options.staged ? 'staged' : 'merge';
|
|
132
|
+
const [rawAgent, rawVersion] = spec.split('@');
|
|
133
|
+
const agent = resolveAgentName(rawAgent);
|
|
134
|
+
if (!agent) {
|
|
135
|
+
console.error(chalk.red(formatAgentError(rawAgent)));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
let version = rawVersion;
|
|
139
|
+
if (!version) {
|
|
140
|
+
const candidates = isolatedVersions(agent);
|
|
141
|
+
if (candidates.length === 0) {
|
|
142
|
+
console.log(chalk.yellow(`No isolated ${agentLabel(agent)} installs to export.`));
|
|
143
|
+
console.log(chalk.gray(` Create one with: agents add ${agent}@<version> --isolated`));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (candidates.length === 1) {
|
|
147
|
+
version = candidates[0];
|
|
148
|
+
}
|
|
149
|
+
else if (isInteractiveTerminal()) {
|
|
150
|
+
version = await select({
|
|
151
|
+
message: `Which isolated ${agentLabel(agent)} version?`,
|
|
152
|
+
choices: candidates.map((v) => ({ name: v, value: v })),
|
|
153
|
+
}).catch((err) => {
|
|
154
|
+
if (isPromptCancelled(err))
|
|
155
|
+
process.exit(130);
|
|
156
|
+
throw err;
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
console.error(chalk.red(`${agentLabel(agent)} has several isolated installs; name one.`));
|
|
161
|
+
console.error(chalk.gray(` ${candidates.map((v) => `agents export ${agent}@${v}`).join('\n ')}`));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const timestamp = Date.now();
|
|
166
|
+
const plan = planExport(agent, version, timestamp, mode);
|
|
167
|
+
if (plan.blocker) {
|
|
168
|
+
reportBlocker(plan);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
printPlan(plan, { diff: options.diff });
|
|
172
|
+
if (options.dryRun) {
|
|
173
|
+
console.log(chalk.gray('\nDry run — nothing was written.'));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
// Only `--replace` can destroy anything, so it is the only mode that demands
|
|
177
|
+
// confirmation. merge and staged are additive by construction.
|
|
178
|
+
if (mode === 'replace' && !options.yes) {
|
|
179
|
+
if (!isInteractiveTerminal()) {
|
|
180
|
+
console.error(chalk.red('\nRefusing to replace your config without confirmation in a non-interactive shell.'));
|
|
181
|
+
console.error(chalk.gray(' Re-run with --yes once you have checked --dry-run.'));
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const ok = await confirm({ message: `Replace ${plan.dest}?`, default: false }).catch((err) => {
|
|
185
|
+
if (isPromptCancelled(err))
|
|
186
|
+
process.exit(130);
|
|
187
|
+
throw err;
|
|
188
|
+
});
|
|
189
|
+
if (!ok) {
|
|
190
|
+
console.log(chalk.gray('Aborted.'));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const result = executeExport(plan, timestamp);
|
|
195
|
+
if (!result.exported) {
|
|
196
|
+
console.error(chalk.red(`Export failed: ${result.errors.join('; ')}`));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
console.log();
|
|
200
|
+
if (result.written.length > 0)
|
|
201
|
+
console.log(chalk.green(`Added ${result.written.length} file(s) to ${result.dest}`));
|
|
202
|
+
if (result.conflicts.length > 0) {
|
|
203
|
+
console.log(chalk.yellow(`${result.conflicts.length} of your file(s) were left untouched; incoming copies written as *${CONFLICT_SUFFIX}`));
|
|
204
|
+
console.log(chalk.gray(` Compare them with: agents export ${agent}@${version} --diff --dry-run`));
|
|
205
|
+
}
|
|
206
|
+
if (result.backupPath)
|
|
207
|
+
console.log(chalk.gray(`Previous config kept at: ${result.backupPath}`));
|
|
208
|
+
if (result.stagedPath)
|
|
209
|
+
console.log(chalk.gray(`Staged (nothing activated): ${result.stagedPath}`));
|
|
210
|
+
console.log(chalk.gray(`Receipt: ${result.receiptPath}`));
|
|
211
|
+
if (mode !== 'staged') {
|
|
212
|
+
console.log(chalk.gray(` ${AGENTS[plan.agent].cliCommand} reads this config directly.`));
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
}
|
package/dist/commands/feed.d.ts
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
import type { Command } from 'commander';
|
|
13
13
|
import { type OpenBlock } from '../lib/feed.js';
|
|
14
14
|
import { type OutcomeGroup, type SessionOutcomeHint } from '../lib/feed-outcome.js';
|
|
15
|
+
import { filterBlocksForFeed } from '../lib/ask-classifier.js';
|
|
16
|
+
import { type FeedSessionSignal } from '../lib/feed-ranking.js';
|
|
15
17
|
export declare const FEED_NO_FANOUT_ENV = "AGENTS_FEED_LOCAL";
|
|
16
18
|
/** Right-hand masthead summary: `N blocks · M agents`. */
|
|
17
19
|
export declare function formatFeedMastheadRight(blocks: OpenBlock[]): string;
|
|
@@ -20,8 +22,19 @@ export declare function formatFeedReplyHint(mailboxId: string): string;
|
|
|
20
22
|
export declare function parseRemoteFeed(stdout: string, machine: string): OpenBlock[];
|
|
21
23
|
/** Merge local and remote rows, keeping the first copy of a host/session block. */
|
|
22
24
|
export declare function mergeFeedBlocks(...groups: OpenBlock[][]): OpenBlock[];
|
|
25
|
+
export type FeedControlAction = 'pause' | 'kill';
|
|
26
|
+
export declare function controlFeedSession(action: FeedControlAction, target: string, signals: FeedSessionSignal[]): Promise<string>;
|
|
23
27
|
export declare function shouldIncludeLocalFeed(hosts: string[] | undefined, self: string): boolean;
|
|
24
28
|
export declare function remoteFeedHostsToDial(hosts: string[] | undefined, self: string): string[] | undefined;
|
|
29
|
+
export declare function prepareLocalFeedBlocks(localBlocks: OpenBlock[], opts: {
|
|
30
|
+
includeLocal: boolean;
|
|
31
|
+
all?: boolean;
|
|
32
|
+
dispatch?: boolean;
|
|
33
|
+
}): {
|
|
34
|
+
visible: OpenBlock[];
|
|
35
|
+
dispatch: OpenBlock[];
|
|
36
|
+
filter: ReturnType<typeof filterBlocksForFeed>;
|
|
37
|
+
};
|
|
25
38
|
/** Human summary line for one outcome rollup. */
|
|
26
39
|
export declare function formatOutcomeHeader(group: OutcomeGroup): string;
|
|
27
40
|
/** Map active sessions into the lightweight hints outcome enrichment needs. */
|
package/dist/commands/feed.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { ensureFeedPublishHook, listBlocks, recordNotified } from '../lib/feed.js';
|
|
2
|
+
import { ensureFeedPublishHook, listAskStats, listBlocks, recordNotified } from '../lib/feed.js';
|
|
3
|
+
import { ensureActivityLogHook, readRecentActivity, formatActivityLine } from '../lib/activity.js';
|
|
3
4
|
import { enrichBlocksFromSessions, groupBlocksByOutcome, isUnambiguousOutcomeAnswer, openBlocksForOutcome, stampBlockOutcomes, } from '../lib/feed-outcome.js';
|
|
4
5
|
import { classifyBlock, filterBlocksForFeed, suppressionDigest, } from '../lib/ask-classifier.js';
|
|
5
6
|
import { machineId, normalizeHost } from '../lib/machine-id.js';
|
|
@@ -12,6 +13,9 @@ import { isValidMailboxId } from '../lib/mailbox.js';
|
|
|
12
13
|
import { getActiveSessions } from '../lib/session/active.js';
|
|
13
14
|
import { mailboxIdForActiveSession } from '../lib/mailbox-target.js';
|
|
14
15
|
import { GLYPH, masthead } from '../lib/comms-render.js';
|
|
16
|
+
import { discoverSessions } from '../lib/session/discover.js';
|
|
17
|
+
import { resolveProvider } from '../lib/cloud/registry.js';
|
|
18
|
+
import { buildSessionSignals, rankFeedBlocks, synthesizeControlCards, } from '../lib/feed-ranking.js';
|
|
15
19
|
export const FEED_NO_FANOUT_ENV = 'AGENTS_FEED_LOCAL';
|
|
16
20
|
/** Right-hand masthead summary: `N blocks · M agents`. */
|
|
17
21
|
export function formatFeedMastheadRight(blocks) {
|
|
@@ -58,6 +62,36 @@ export function mergeFeedBlocks(...groups) {
|
|
|
58
62
|
}
|
|
59
63
|
return [...byIdentity.values()].sort((a, b) => Date.parse(b.ts) - Date.parse(a.ts));
|
|
60
64
|
}
|
|
65
|
+
function matchesControlTarget(signal, target) {
|
|
66
|
+
return [
|
|
67
|
+
signal.mailboxId,
|
|
68
|
+
signal.sessionId,
|
|
69
|
+
signal.cloudTaskId,
|
|
70
|
+
signal.pid !== undefined ? String(signal.pid) : undefined,
|
|
71
|
+
].some((value) => value === target);
|
|
72
|
+
}
|
|
73
|
+
export async function controlFeedSession(action, target, signals) {
|
|
74
|
+
const signal = signals.find((s) => matchesControlTarget(s, target));
|
|
75
|
+
if (!signal)
|
|
76
|
+
throw new Error(`No live feed session matches '${target}'.`);
|
|
77
|
+
if (signal.cloudProvider && signal.cloudTaskId) {
|
|
78
|
+
const provider = resolveProvider(signal.cloudProvider);
|
|
79
|
+
await provider.cancel(signal.cloudTaskId);
|
|
80
|
+
return `${action === 'pause' ? 'paused' : 'killed'} cloud task ${signal.cloudTaskId}`;
|
|
81
|
+
}
|
|
82
|
+
if (!signal.pid) {
|
|
83
|
+
throw new Error(`Session '${target}' has no local pid or cancellable cloud task.`);
|
|
84
|
+
}
|
|
85
|
+
if (action === 'pause') {
|
|
86
|
+
if (process.platform === 'win32') {
|
|
87
|
+
throw new Error('Pause is not supported for local Windows processes; use --kill.');
|
|
88
|
+
}
|
|
89
|
+
process.kill(signal.pid, 'SIGSTOP');
|
|
90
|
+
return `paused pid ${signal.pid}`;
|
|
91
|
+
}
|
|
92
|
+
process.kill(signal.pid, 'SIGTERM');
|
|
93
|
+
return `killed pid ${signal.pid}`;
|
|
94
|
+
}
|
|
61
95
|
function hostToken(host) {
|
|
62
96
|
return normalizeHost(host.split('@').pop() || host);
|
|
63
97
|
}
|
|
@@ -69,6 +103,16 @@ export function remoteFeedHostsToDial(hosts, self) {
|
|
|
69
103
|
return undefined;
|
|
70
104
|
return hosts.filter((host) => hostToken(host) !== self);
|
|
71
105
|
}
|
|
106
|
+
export function prepareLocalFeedBlocks(localBlocks, opts) {
|
|
107
|
+
const filter = filterBlocksForFeed(localBlocks, {
|
|
108
|
+
apply: opts.includeLocal && (!opts.all || opts.dispatch === true),
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
visible: opts.all ? localBlocks : filter.surfaced,
|
|
112
|
+
dispatch: filter.surfaced,
|
|
113
|
+
filter,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
72
116
|
function renderBlock(b, localHost, indent = '') {
|
|
73
117
|
const host = b.host !== localHost ? chalk.yellow(` [${b.host}]`) : '';
|
|
74
118
|
const runtime = chalk.gray(b.runtime);
|
|
@@ -76,13 +120,16 @@ function renderBlock(b, localHost, indent = '') {
|
|
|
76
120
|
const cls = b.blockClass ? chalk.gray(`(${b.blockClass})`) : '';
|
|
77
121
|
const consequence = b.consequence && b.consequence !== 'normal' ? chalk.red(`[${b.consequence}]`) : '';
|
|
78
122
|
const cost = b.costOfDelay ? chalk.gray(`cost:${b.costOfDelay}`) : '';
|
|
123
|
+
const rank = b.delayRank ? chalk.gray(`rank:${Math.round(b.delayRank.score)}`) : '';
|
|
79
124
|
// Shared fleet-comms glyphs: ▲ open ask, ✓ answered (see comms-render GLYPH).
|
|
80
125
|
const marker = b.answer
|
|
81
126
|
? chalk.green(GLYPH.delivered)
|
|
82
|
-
:
|
|
83
|
-
? chalk.
|
|
84
|
-
:
|
|
85
|
-
|
|
127
|
+
: b.kind === 'control'
|
|
128
|
+
? chalk.red('!')
|
|
129
|
+
: !b.parkedAt
|
|
130
|
+
? chalk.yellow(GLYPH.ask)
|
|
131
|
+
: ' ';
|
|
132
|
+
console.log(`${indent}${marker} ${chalk.cyan(b.mailboxId)}${host} ${runtime} ${age} ${cls} ${consequence} ${cost} ${rank}`.trimEnd());
|
|
86
133
|
for (const question of b.questions) {
|
|
87
134
|
const header = question.header ? chalk.gray(`[${question.header}] `) : '';
|
|
88
135
|
console.log(`${indent} ${header}${question.text}`);
|
|
@@ -119,7 +166,15 @@ function renderBlock(b, localHost, indent = '') {
|
|
|
119
166
|
if (b.notifiedAt) {
|
|
120
167
|
console.log(`${indent} ${chalk.dim('notified')} ${relTime(b.notifiedAt)}`);
|
|
121
168
|
}
|
|
122
|
-
if (
|
|
169
|
+
if (b.runaway) {
|
|
170
|
+
console.log(`${indent} ${chalk.red('runaway:')} ${b.runaway.reason}`);
|
|
171
|
+
console.log(`${indent} ${chalk.dim(`control: ag feed --pause ${b.mailboxId} · ag feed --kill ${b.mailboxId}`)}`);
|
|
172
|
+
}
|
|
173
|
+
if (b.needy) {
|
|
174
|
+
console.log(`${indent} ${chalk.yellow('needy:')} ${b.needy.askCountLastHour}/${b.needy.threshold} asks in the last hour`);
|
|
175
|
+
console.log(`${indent} ${chalk.dim(`inspect: ag sessions ${b.sessionId}`)}`);
|
|
176
|
+
}
|
|
177
|
+
if (!b.answer && !b.parkedAt && b.kind !== 'control') {
|
|
123
178
|
console.log(`${indent} ${chalk.dim(formatFeedReplyHint(b.mailboxId))}`);
|
|
124
179
|
}
|
|
125
180
|
console.log();
|
|
@@ -169,6 +224,8 @@ export function registerFeedCommand(program) {
|
|
|
169
224
|
.option('-H, --host <target...>', 'Scope to remote machine(s) over SSH; repeatable')
|
|
170
225
|
.option('--device <target...>', 'Alias for --host; repeatable')
|
|
171
226
|
.option('--dispatch', 'Run stall suppression + default-on-no-answer policy and urgent notifications')
|
|
227
|
+
.option('--pause <id>', 'Pause a runaway/needy local process (SIGSTOP) or cancel a cloud task')
|
|
228
|
+
.option('--kill <id>', 'Kill a runaway/needy local process (SIGTERM) or cancel a cloud task')
|
|
172
229
|
.action(async (opts) => {
|
|
173
230
|
if (opts.device?.length)
|
|
174
231
|
opts.host = [...(opts.host ?? []), ...opts.device];
|
|
@@ -176,11 +233,16 @@ export function registerFeedCommand(program) {
|
|
|
176
233
|
const includeLocal = shouldIncludeLocalFeed(opts.host, self);
|
|
177
234
|
const setupWarnings = [];
|
|
178
235
|
if (includeLocal) {
|
|
236
|
+
// Feed and activity hooks are independent -- install both, and register
|
|
237
|
+
// the manifest as long as at least one wrote its entries (don't couple
|
|
238
|
+
// activity registration to the feed hook succeeding).
|
|
179
239
|
const hookInstall = ensureFeedPublishHook();
|
|
180
|
-
|
|
240
|
+
const activityInstall = ensureActivityLogHook();
|
|
241
|
+
if (hookInstall.error)
|
|
181
242
|
setupWarnings.push(hookInstall.error);
|
|
182
|
-
|
|
183
|
-
|
|
243
|
+
if (activityInstall.error)
|
|
244
|
+
setupWarnings.push(activityInstall.error);
|
|
245
|
+
if (!hookInstall.error || !activityInstall.error) {
|
|
184
246
|
const [{ iterHooksCapableVersions, parseHookManifest, registerHooksToSettings }, { getVersionHomePath }] = await Promise.all([
|
|
185
247
|
import('../lib/hooks.js'),
|
|
186
248
|
import('../lib/versions.js'),
|
|
@@ -199,6 +261,17 @@ export function registerFeedCommand(program) {
|
|
|
199
261
|
if (includeLocal) {
|
|
200
262
|
sessions = await getActiveSessions();
|
|
201
263
|
}
|
|
264
|
+
const sessionMetas = includeLocal && sessions.length > 0 ? await discoverSessions({ all: true, limit: 5000 }) : [];
|
|
265
|
+
const localSignals = buildSessionSignals(sessions, sessionMetas);
|
|
266
|
+
if (opts.pause || opts.kill) {
|
|
267
|
+
if (!includeLocal) {
|
|
268
|
+
throw new Error('Feed controls run on the local machine. Re-run against the target host with --local.');
|
|
269
|
+
}
|
|
270
|
+
const action = opts.pause ? 'pause' : 'kill';
|
|
271
|
+
const target = opts.pause ?? opts.kill ?? '';
|
|
272
|
+
console.log(await controlFeedSession(action, target, localSignals));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
202
275
|
if (opts.dispatch && includeLocal) {
|
|
203
276
|
// Liveness sweep: drop messages to dead agents and retire stale blocks
|
|
204
277
|
// before we render the feed.
|
|
@@ -208,8 +281,26 @@ export function registerFeedCommand(program) {
|
|
|
208
281
|
console.log(chalk.yellow(`gc: ${gcResult.messagesDroppedDead} dead messages, ${gcResult.blocksRemoved} stale blocks removed`));
|
|
209
282
|
}
|
|
210
283
|
}
|
|
211
|
-
|
|
212
|
-
|
|
284
|
+
let localBlocks = includeLocal
|
|
285
|
+
? [...listBlocks(), ...synthesizeControlCards(localSignals, listAskStats())]
|
|
286
|
+
: [];
|
|
287
|
+
// Fill missing ticket/PR/worktree from live session meta before local
|
|
288
|
+
// policy mutates the store, so outcome keys land even when the publish
|
|
289
|
+
// hook had no deliverable stamp.
|
|
290
|
+
if (sessions.length > 0) {
|
|
291
|
+
localBlocks = enrichBlocksFromSessions(localBlocks, sessionHintsFromActive(sessions));
|
|
292
|
+
}
|
|
293
|
+
// Stall suppression (RUSH-1477) must only mutate blocks owned by this
|
|
294
|
+
// machine. Remote peers run their own `feed --json`; never enqueue a
|
|
295
|
+
// policy answer into a local mailbox for a remote agent.
|
|
296
|
+
const preparedLocal = prepareLocalFeedBlocks(localBlocks, {
|
|
297
|
+
includeLocal,
|
|
298
|
+
all: opts.all,
|
|
299
|
+
dispatch: opts.dispatch,
|
|
300
|
+
});
|
|
301
|
+
const visibleLocalBlocks = preparedLocal.visible;
|
|
302
|
+
const dispatchBlocks = preparedLocal.dispatch;
|
|
303
|
+
let blocks = visibleLocalBlocks;
|
|
213
304
|
const forceLocal = opts.local === true || process.env[FEED_NO_FANOUT_ENV] === '1';
|
|
214
305
|
if (!forceLocal) {
|
|
215
306
|
const remoteHosts = remoteFeedHostsToDial(opts.host, self);
|
|
@@ -221,31 +312,18 @@ export function registerFeedCommand(program) {
|
|
|
221
312
|
hosts: remoteHosts,
|
|
222
313
|
parse: parseRemoteFeed,
|
|
223
314
|
});
|
|
224
|
-
blocks = mergeFeedBlocks(
|
|
315
|
+
blocks = mergeFeedBlocks(visibleLocalBlocks, remote.items);
|
|
225
316
|
}
|
|
226
317
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (sessions.length > 0) {
|
|
230
|
-
blocks = enrichBlocksFromSessions(blocks, sessionHintsFromActive(sessions));
|
|
231
|
-
}
|
|
232
|
-
// Stall suppression (RUSH-1477): auto-answer "should I…?" / "what's next?"
|
|
233
|
-
// so they never render. Applied on every feed read unless --all; --dispatch
|
|
234
|
-
// also applies so unattended ticks drain the stall backlog.
|
|
235
|
-
const filter = filterBlocksForFeed(blocks, {
|
|
236
|
-
apply: !opts.all || opts.dispatch === true,
|
|
237
|
-
});
|
|
238
|
-
if (!opts.all) {
|
|
239
|
-
blocks = filter.surfaced;
|
|
240
|
-
}
|
|
241
|
-
const digest = suppressionDigest(filter);
|
|
318
|
+
blocks = rankFeedBlocks(blocks, localSignals);
|
|
319
|
+
const digest = suppressionDigest(preparedLocal.filter);
|
|
242
320
|
if (digest && !opts.json) {
|
|
243
321
|
console.log(chalk.dim(digest));
|
|
244
322
|
}
|
|
245
323
|
if (opts.dispatch) {
|
|
246
324
|
const policy = loadPolicy();
|
|
247
325
|
const now = new Date();
|
|
248
|
-
for (const b of
|
|
326
|
+
for (const b of dispatchBlocks) {
|
|
249
327
|
// Wrap per-block policy so one malformed block (e.g. a crafted
|
|
250
328
|
// mailboxId that throws in mailboxDir) can't abort the whole loop and
|
|
251
329
|
// strand every remaining block's dispatch.
|
|
@@ -285,6 +363,8 @@ export function registerFeedCommand(program) {
|
|
|
285
363
|
}
|
|
286
364
|
if (blocks.length === 0) {
|
|
287
365
|
console.log(chalk.gray(digest ? 'No open blocks after stall suppression.' : 'No open blocks.'));
|
|
366
|
+
if (includeLocal)
|
|
367
|
+
renderActivityLane();
|
|
288
368
|
return;
|
|
289
369
|
}
|
|
290
370
|
// Shared fleet-comms masthead (same family as `agents mailboxes`).
|
|
@@ -300,8 +380,29 @@ export function registerFeedCommand(program) {
|
|
|
300
380
|
renderBlock(b, self);
|
|
301
381
|
return;
|
|
302
382
|
}
|
|
303
|
-
const groups = groupBlocksByOutcome(blocks)
|
|
383
|
+
const groups = groupBlocksByOutcome(blocks).sort((a, b) => {
|
|
384
|
+
const ar = Math.max(...a.blocks.map((block) => block.delayRank?.score ?? 0));
|
|
385
|
+
const br = Math.max(...b.blocks.map((block) => block.delayRank?.score ?? 0));
|
|
386
|
+
return br - ar;
|
|
387
|
+
});
|
|
304
388
|
for (const g of groups)
|
|
305
389
|
renderOutcomeGroup(g, self);
|
|
390
|
+
if (includeLocal)
|
|
391
|
+
renderActivityLane();
|
|
306
392
|
});
|
|
307
393
|
}
|
|
394
|
+
/**
|
|
395
|
+
* Print a compact "recent activity" lane under the feed: the last few milestone
|
|
396
|
+
* events (plans, PRs, worktrees, sub-agents) from the append-only activity logs.
|
|
397
|
+
* Read-only tail of the logs -- no transcript re-parsing. Silent when empty.
|
|
398
|
+
*/
|
|
399
|
+
function renderActivityLane() {
|
|
400
|
+
const events = readRecentActivity({ sinceMs: Date.now() - 24 * 60 * 60 * 1000, limit: 6 })
|
|
401
|
+
.filter((e) => e.tier === 'milestone');
|
|
402
|
+
if (events.length === 0)
|
|
403
|
+
return;
|
|
404
|
+
console.log(chalk.bold('\n recent activity'));
|
|
405
|
+
for (const ev of events)
|
|
406
|
+
console.log(formatActivityLine(ev, { showHost: true }));
|
|
407
|
+
console.log(chalk.gray(' → agents activity for the full stream'));
|
|
408
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getActiveSessions } from '../lib/session/active.js';
|
|
3
|
+
import { AgentManager } from '../lib/teams/agents.js';
|
|
4
|
+
import { handleStatus, handleTasks } from '../lib/teams/api.js';
|
|
5
|
+
import { listAskStats, listBlocks } from '../lib/feed.js';
|
|
6
|
+
import { buildSessionSignals, synthesizeControlCards } from '../lib/feed-ranking.js';
|
|
7
|
+
import { discoverSessions } from '../lib/session/discover.js';
|
|
8
|
+
import { buildHqFloor } from '../lib/hq/floor.js';
|
|
9
|
+
import { die } from '../lib/format.js';
|
|
10
|
+
async function collectFloorSnapshot() {
|
|
11
|
+
const manager = new AgentManager();
|
|
12
|
+
const [sessions, tasksResult] = await Promise.all([
|
|
13
|
+
getActiveSessions(),
|
|
14
|
+
handleTasks(manager, 1000),
|
|
15
|
+
]);
|
|
16
|
+
const teammatesByTeam = new Map();
|
|
17
|
+
await Promise.all(tasksResult.tasks.map(async (team) => {
|
|
18
|
+
const status = await handleStatus(manager, team.task_name, 'all');
|
|
19
|
+
teammatesByTeam.set(team.task_name, status.agents);
|
|
20
|
+
}));
|
|
21
|
+
const sessionMetas = sessions.length > 0 ? await discoverSessions({ all: true, limit: 5000 }) : [];
|
|
22
|
+
const blocks = [
|
|
23
|
+
...listBlocks(),
|
|
24
|
+
...synthesizeControlCards(buildSessionSignals(sessions, sessionMetas), listAskStats()),
|
|
25
|
+
];
|
|
26
|
+
return buildHqFloor({
|
|
27
|
+
sessions,
|
|
28
|
+
teams: tasksResult.tasks,
|
|
29
|
+
teammatesByTeam,
|
|
30
|
+
blocks,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
export function registerHqCommand(program) {
|
|
34
|
+
const hq = program
|
|
35
|
+
.command('hq')
|
|
36
|
+
.description('Machine-readable bridge for Agents HQ floor management.');
|
|
37
|
+
hq
|
|
38
|
+
.command('floor')
|
|
39
|
+
.description('Emit the live floor snapshot: rooms, agents, ambient events, and runnable actions.')
|
|
40
|
+
.option('--json', 'Output machine-readable JSON')
|
|
41
|
+
.action(async (opts) => {
|
|
42
|
+
try {
|
|
43
|
+
const snapshot = await collectFloorSnapshot();
|
|
44
|
+
if (opts.json || !process.stdout.isTTY) {
|
|
45
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
console.log(chalk.bold('Agents HQ floor'));
|
|
49
|
+
console.log(chalk.gray(`${snapshot.counters.agents} agents, ${snapshot.counters.rooms} rooms, ${snapshot.counters.needsInput} need input`));
|
|
50
|
+
for (const room of snapshot.rooms) {
|
|
51
|
+
console.log(`${chalk.cyan(room.name)} ${chalk.gray(`${room.counts.agents} agents, ${room.counts.running} running, ${room.counts.needsInput} need input`)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
die(err.message);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
package/dist/commands/import.js
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
* In all forms, the agent's config dir (e.g. ~/.openclaw) is also moved under
|
|
21
21
|
* management — same behavior as the first-run `agents setup` import flow.
|
|
22
22
|
*/
|
|
23
|
+
import { withIsolationBoundary } from '../lib/isolation-boundary-report.js';
|
|
24
|
+
import { assertIsolationBoundary, createVersionedAlias } from '../lib/shims.js';
|
|
25
|
+
import { markVersionIsolated } from '../lib/versions.js';
|
|
23
26
|
import chalk from 'chalk';
|
|
24
27
|
import ora from 'ora';
|
|
25
28
|
import * as fs from 'fs';
|
|
@@ -29,7 +32,7 @@ import { confirm } from '@inquirer/prompts';
|
|
|
29
32
|
import { ALL_AGENT_IDS } from '../lib/agents.js';
|
|
30
33
|
import { AGENTS, getCliPath, getCliVersion, agentLabel, resolveAgentName } from '../lib/agents.js';
|
|
31
34
|
import { getVersionDir } from '../lib/versions.js';
|
|
32
|
-
import { finalizeImport, importAgentBinary, importAgentConfig, importInstallScriptBinary, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
|
|
35
|
+
import { finalizeImport, importAgentBinary, importAgentConfig, importInstallScriptBinary, seedIsolatedConfigFromLocal, isValidImportVersion, resolvePackageDirFromBinary, } from '../lib/import.js';
|
|
33
36
|
import { isPromptCancelled, isInteractiveTerminal } from './utils.js';
|
|
34
37
|
async function runImport(agentArg, opts) {
|
|
35
38
|
const agentId = resolveAgentName(agentArg);
|
|
@@ -39,6 +42,16 @@ async function runImport(agentArg, opts) {
|
|
|
39
42
|
process.exit(1);
|
|
40
43
|
}
|
|
41
44
|
const agent = AGENTS[agentId];
|
|
45
|
+
// Import registers the adopted install as a NORMAL version and only then sets the
|
|
46
|
+
// default / creates the shim / repoints the config. By that point the agent has a
|
|
47
|
+
// non-isolated version, so it is no longer protected and the primitive gates —
|
|
48
|
+
// which read the state as it is at call time — would let the adoption through.
|
|
49
|
+
// The boundary has to be checked against the state BEFORE the import mutates it.
|
|
50
|
+
// --isolated adopts nothing, so the boundary does not apply to it — it is in fact
|
|
51
|
+
// the supported way to bring a local install in while protection is on.
|
|
52
|
+
if (!opts.isolated) {
|
|
53
|
+
assertIsolationBoundary(agentId, 'adopt your existing install');
|
|
54
|
+
}
|
|
42
55
|
// installScript-based agents (Grok, Antigravity, Cursor, Kiro, Goose, Roo)
|
|
43
56
|
// don't have an npm package; their binary lives wherever the curl/brew
|
|
44
57
|
// installer dropped it. We adopt by symlinking that PATH binary directly
|
|
@@ -191,7 +204,27 @@ async function runImport(agentArg, opts) {
|
|
|
191
204
|
// user-visible side effect (renaming ~/.<agent>/), so if it fails we don't
|
|
192
205
|
// want a stranded symlink farm. Binary registration is cheap and reversible
|
|
193
206
|
// — if it fails after config, the next `agents import` call retries cleanly.
|
|
194
|
-
const willImportConfig = configDirExists && !configAlreadyManaged;
|
|
207
|
+
const willImportConfig = configDirExists && !configAlreadyManaged && !opts.isolated;
|
|
208
|
+
if (opts.isolated && configDirExists) {
|
|
209
|
+
// COPY the user's settings in; never move, never symlink. The original stays
|
|
210
|
+
// exactly where it is — that is what separates this from adoption.
|
|
211
|
+
const seedSpinner = ora(`Copying ${agent.configDir} into the isolated copy...`).start();
|
|
212
|
+
const seed = seedIsolatedConfigFromLocal(agentId, version, { withAuth: opts.withAuth });
|
|
213
|
+
if (seed.error) {
|
|
214
|
+
seedSpinner.fail(`Config: ${seed.error}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
else if (seed.seeded) {
|
|
218
|
+
seedSpinner.succeed(`Settings copied (${seed.from} -> ${seed.to}); your original is untouched`);
|
|
219
|
+
if (seed.skippedAuth.length > 0) {
|
|
220
|
+
console.log(chalk.gray(` Credentials NOT copied: ${seed.skippedAuth.join(', ')}`));
|
|
221
|
+
console.log(chalk.gray(' The copy signs in separately. Use --with-auth to copy them too.'));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
seedSpinner.info('No existing config to copy.');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
195
228
|
if (willImportConfig) {
|
|
196
229
|
const cfgSpinner = ora(`Importing config dir for ${agentLabel(agentId)} v${version}...`).start();
|
|
197
230
|
const cfgResult = await importAgentConfig(agentId, version);
|
|
@@ -224,6 +257,19 @@ async function runImport(agentArg, opts) {
|
|
|
224
257
|
// Wire the imported version into the resolver: global default, main shim,
|
|
225
258
|
// versioned alias, home-file symlinks. Idempotent — safe to call even if
|
|
226
259
|
// importAgentConfig already set the global default.
|
|
260
|
+
if (opts.isolated) {
|
|
261
|
+
// The isolated finalizer: launchable alias + marker, and nothing else. No global
|
|
262
|
+
// default, no bare shim, no config symlink — the same shape as
|
|
263
|
+
// `agents add --isolated`.
|
|
264
|
+
createVersionedAlias(agentId, version);
|
|
265
|
+
markVersionIsolated(agentId, version);
|
|
266
|
+
console.log();
|
|
267
|
+
console.log(chalk.green(`${agentLabel(agentId)} v${version} imported as an isolated copy.`));
|
|
268
|
+
console.log(chalk.gray(` Your ${agent.configDir} and ${agent.cliCommand} launcher are untouched.`));
|
|
269
|
+
console.log(chalk.gray(` Run it: agents run ${agentId}@${version}`));
|
|
270
|
+
console.log(chalk.gray(` Or make it the default isolated copy: agents use ${agentId}@${version}`));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
227
273
|
const finalizeSpinner = ora(`Wiring ${agentLabel(agentId)} v${version} as the active version...`).start();
|
|
228
274
|
try {
|
|
229
275
|
finalizeImport(agentId, version);
|
|
@@ -244,6 +290,8 @@ export function registerImportCommand(program) {
|
|
|
244
290
|
.description('Import an existing unmanaged agent install into agents-cli')
|
|
245
291
|
.option('--version <version>', 'Pin a version label (otherwise read from package.json)')
|
|
246
292
|
.option('--from-path <path>', 'Path to the npm package dir (otherwise auto-detected from PATH)')
|
|
293
|
+
.option('--isolated', 'Copy the install into a self-contained isolated version instead of adopting it')
|
|
294
|
+
.option('--with-auth', 'With --isolated, also copy credentials into the sandbox (skipped by default)')
|
|
247
295
|
.option('-y, --yes', 'Skip the confirmation prompt')
|
|
248
296
|
.addHelpText('after', `
|
|
249
297
|
Examples:
|
|
@@ -265,5 +313,5 @@ When to use:
|
|
|
265
313
|
npm-style packages (claude, codex, gemini, opencode, openclaw) and
|
|
266
314
|
installScript-based agents (grok, antigravity, cursor, kiro, goose).
|
|
267
315
|
`)
|
|
268
|
-
.action(runImport);
|
|
316
|
+
.action((...args) => withIsolationBoundary(() => runImport(...args)));
|
|
269
317
|
}
|