@phnx-labs/agents-cli 1.20.64 → 1.20.66
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 +51 -3
- package/README.md +156 -3
- package/dist/bin/agents +0 -0
- package/dist/commands/apply.d.ts +12 -0
- package/dist/commands/apply.js +274 -0
- package/dist/commands/browser.js +2 -2
- package/dist/commands/cloud.js +32 -2
- package/dist/commands/doctor.js +4 -1
- package/dist/commands/exec.d.ts +48 -0
- package/dist/commands/exec.js +159 -49
- package/dist/commands/feed.js +25 -11
- package/dist/commands/hosts.js +44 -6
- package/dist/commands/mcp.js +55 -5
- package/dist/commands/monitors.d.ts +12 -0
- package/dist/commands/monitors.js +748 -0
- package/dist/commands/output.js +2 -2
- package/dist/commands/plugins.js +28 -7
- package/dist/commands/routines.js +23 -2
- package/dist/commands/secrets.d.ts +16 -0
- package/dist/commands/secrets.js +215 -64
- package/dist/commands/serve.js +31 -0
- package/dist/commands/sessions-browser.d.ts +82 -0
- package/dist/commands/sessions-browser.js +320 -0
- package/dist/commands/sessions-export.js +8 -3
- package/dist/commands/sessions.d.ts +17 -0
- package/dist/commands/sessions.js +157 -10
- package/dist/commands/share.d.ts +2 -0
- package/dist/commands/share.js +150 -0
- package/dist/commands/ssh.js +54 -3
- package/dist/commands/versions.js +7 -3
- package/dist/commands/view.d.ts +26 -0
- package/dist/commands/view.js +32 -9
- package/dist/commands/webhook.js +10 -2
- package/dist/index.js +35 -14
- package/dist/lib/agents.d.ts +46 -0
- package/dist/lib/agents.js +121 -3
- package/dist/lib/auto-dispatch-provider.js +7 -2
- package/dist/lib/auto-dispatch.d.ts +3 -0
- package/dist/lib/auto-dispatch.js +3 -0
- package/dist/lib/browser/chrome.js +2 -2
- package/dist/lib/cloud/antigravity.js +2 -2
- package/dist/lib/cloud/host.d.ts +59 -0
- package/dist/lib/cloud/host.js +224 -0
- package/dist/lib/cloud/registry.js +4 -0
- package/dist/lib/cloud/types.d.ts +6 -4
- package/dist/lib/computer-rpc.js +3 -1
- package/dist/lib/crabbox/cli.js +5 -1
- package/dist/lib/crabbox/runtimes.js +11 -2
- package/dist/lib/daemon.d.ts +20 -4
- package/dist/lib/daemon.js +62 -19
- package/dist/lib/devices/connect.d.ts +18 -1
- package/dist/lib/devices/connect.js +10 -2
- package/dist/lib/devices/fleet.d.ts +3 -2
- package/dist/lib/devices/fleet.js +9 -0
- package/dist/lib/devices/known-hosts.d.ts +62 -0
- package/dist/lib/devices/known-hosts.js +137 -0
- package/dist/lib/devices/registry.d.ts +15 -0
- package/dist/lib/devices/registry.js +9 -0
- package/dist/lib/exec.d.ts +19 -2
- package/dist/lib/exec.js +41 -13
- package/dist/lib/fleet/apply.d.ts +63 -0
- package/dist/lib/fleet/apply.js +214 -0
- package/dist/lib/fleet/auth-sync.d.ts +67 -0
- package/dist/lib/fleet/auth-sync.js +142 -0
- package/dist/lib/fleet/manifest.d.ts +29 -0
- package/dist/lib/fleet/manifest.js +127 -0
- package/dist/lib/fleet/types.d.ts +129 -0
- package/dist/lib/fleet/types.js +13 -0
- package/dist/lib/git.d.ts +27 -0
- package/dist/lib/git.js +34 -2
- package/dist/lib/hosts/dispatch.d.ts +29 -8
- package/dist/lib/hosts/dispatch.js +66 -20
- package/dist/lib/hosts/passthrough.js +2 -0
- package/dist/lib/hosts/providers/devices.d.ts +27 -0
- package/dist/lib/hosts/providers/devices.js +98 -0
- package/dist/lib/hosts/registry.d.ts +10 -16
- package/dist/lib/hosts/registry.js +17 -50
- package/dist/lib/hosts/remote-cmd.d.ts +23 -0
- package/dist/lib/hosts/remote-cmd.js +79 -4
- package/dist/lib/hosts/run-target.d.ts +84 -0
- package/dist/lib/hosts/run-target.js +99 -0
- package/dist/lib/hosts/types.d.ts +23 -5
- package/dist/lib/hosts/types.js +22 -4
- package/dist/lib/linear-autoclose.d.ts +30 -0
- package/dist/lib/linear-autoclose.js +22 -0
- package/dist/lib/mcp.d.ts +27 -1
- package/dist/lib/mcp.js +126 -12
- package/dist/lib/monitors/config.d.ts +161 -0
- package/dist/lib/monitors/config.js +372 -0
- package/dist/lib/monitors/dispatch.d.ts +28 -0
- package/dist/lib/monitors/dispatch.js +91 -0
- package/dist/lib/monitors/engine.d.ts +61 -0
- package/dist/lib/monitors/engine.js +205 -0
- package/dist/lib/monitors/sources/command.d.ts +11 -0
- package/dist/lib/monitors/sources/command.js +31 -0
- package/dist/lib/monitors/sources/device.d.ts +13 -0
- package/dist/lib/monitors/sources/device.js +45 -0
- package/dist/lib/monitors/sources/file.d.ts +14 -0
- package/dist/lib/monitors/sources/file.js +57 -0
- package/dist/lib/monitors/sources/http.d.ts +10 -0
- package/dist/lib/monitors/sources/http.js +34 -0
- package/dist/lib/monitors/sources/index.d.ts +14 -0
- package/dist/lib/monitors/sources/index.js +31 -0
- package/dist/lib/monitors/sources/poll.d.ts +9 -0
- package/dist/lib/monitors/sources/poll.js +9 -0
- package/dist/lib/monitors/sources/types.d.ts +18 -0
- package/dist/lib/monitors/sources/types.js +9 -0
- package/dist/lib/monitors/sources/webhook.d.ts +23 -0
- package/dist/lib/monitors/sources/webhook.js +47 -0
- package/dist/lib/monitors/sources/ws.d.ts +14 -0
- package/dist/lib/monitors/sources/ws.js +45 -0
- package/dist/lib/monitors/state.d.ts +69 -0
- package/dist/lib/monitors/state.js +144 -0
- package/dist/lib/picker.d.ts +53 -0
- package/dist/lib/picker.js +214 -1
- package/dist/lib/platform/exec.d.ts +16 -0
- package/dist/lib/platform/exec.js +17 -0
- package/dist/lib/plugins.d.ts +31 -1
- package/dist/lib/plugins.js +175 -15
- package/dist/lib/redact.d.ts +14 -1
- package/dist/lib/redact.js +47 -1
- package/dist/lib/remote-agents-json.js +7 -1
- package/dist/lib/rotate.d.ts +6 -3
- package/dist/lib/rotate.js +0 -1
- package/dist/lib/routines.d.ts +16 -0
- package/dist/lib/routines.js +19 -0
- package/dist/lib/runner.d.ts +1 -0
- package/dist/lib/runner.js +102 -9
- package/dist/lib/secrets/agent.d.ts +83 -10
- package/dist/lib/secrets/agent.js +237 -70
- package/dist/lib/secrets/bundles.d.ts +26 -0
- package/dist/lib/secrets/bundles.js +59 -8
- package/dist/lib/secrets/filestore.d.ts +9 -0
- package/dist/lib/secrets/filestore.js +21 -8
- package/dist/lib/secrets/index.d.ts +7 -0
- package/dist/lib/secrets/index.js +26 -6
- package/dist/lib/secrets/mcp.js +4 -2
- package/dist/lib/secrets/remote.d.ts +17 -0
- package/dist/lib/secrets/remote.js +40 -0
- package/dist/lib/self-update.d.ts +20 -0
- package/dist/lib/self-update.js +54 -1
- package/dist/lib/serve/control.d.ts +95 -0
- package/dist/lib/serve/control.js +260 -0
- package/dist/lib/serve/server.d.ts +35 -1
- package/dist/lib/serve/server.js +106 -76
- package/dist/lib/serve/stream.d.ts +43 -0
- package/dist/lib/serve/stream.js +116 -0
- package/dist/lib/serve/token.d.ts +35 -0
- package/dist/lib/serve/token.js +85 -0
- package/dist/lib/session/bundle.d.ts +14 -0
- package/dist/lib/session/bundle.js +12 -1
- package/dist/lib/session/remote-list.js +5 -1
- package/dist/lib/session/state.d.ts +7 -25
- package/dist/lib/session/state.js +16 -6
- package/dist/lib/session/sync/config.js +8 -2
- package/dist/lib/session/types.d.ts +30 -0
- package/dist/lib/share/capture.d.ts +29 -0
- package/dist/lib/share/capture.js +140 -0
- package/dist/lib/share/config.d.ts +35 -0
- package/dist/lib/share/config.js +100 -0
- package/dist/lib/share/og.d.ts +25 -0
- package/dist/lib/share/og.js +84 -0
- package/dist/lib/share/provision.d.ts +10 -0
- package/dist/lib/share/provision.js +91 -0
- package/dist/lib/share/publish.d.ts +57 -0
- package/dist/lib/share/publish.js +145 -0
- package/dist/lib/share/worker-template.d.ts +2 -0
- package/dist/lib/share/worker-template.js +82 -0
- package/dist/lib/shims.d.ts +13 -0
- package/dist/lib/shims.js +42 -2
- package/dist/lib/ssh-exec.d.ts +24 -0
- package/dist/lib/ssh-exec.js +15 -3
- package/dist/lib/ssh-tunnel.d.ts +19 -1
- package/dist/lib/ssh-tunnel.js +86 -7
- package/dist/lib/startup/command-registry.d.ts +3 -0
- package/dist/lib/startup/command-registry.js +6 -0
- package/dist/lib/state.d.ts +5 -0
- package/dist/lib/state.js +12 -0
- package/dist/lib/tmux/session.d.ts +7 -0
- package/dist/lib/tmux/session.js +3 -1
- package/dist/lib/triggers/webhook.d.ts +18 -0
- package/dist/lib/triggers/webhook.js +105 -0
- package/dist/lib/types.d.ts +36 -1
- package/dist/lib/usage.js +7 -5
- package/dist/lib/versions.js +14 -11
- package/dist/lib/workflows.d.ts +20 -0
- package/dist/lib/workflows.js +24 -0
- package/package.json +2 -1
package/dist/commands/view.js
CHANGED
|
@@ -4,7 +4,7 @@ import { visibleWidth, termLink } from '../lib/format.js';
|
|
|
4
4
|
import ora from 'ora';
|
|
5
5
|
import * as fs from 'fs';
|
|
6
6
|
import * as path from 'path';
|
|
7
|
-
import { AGENTS, ALL_AGENT_IDS, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
7
|
+
import { AGENTS, ALL_AGENT_IDS, accountOrgBadge, getAllCliStates, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
|
|
8
8
|
import { deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, } from '../lib/usage.js';
|
|
9
9
|
import { readManifest } from '../lib/manifest.js';
|
|
10
10
|
import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, getAvailableResources, getActuallySyncedResources, getNewResources, getProjectOnlyResources, hasNewResources, promptNewResourceSelection, syncResourcesToVersion, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, } from '../lib/versions.js';
|
|
@@ -34,12 +34,20 @@ const SIGNED_IN_LABEL = 'signed in';
|
|
|
34
34
|
* account id (Kimi's user_id), show `id:<user_id>` so distinct accounts read
|
|
35
35
|
* distinctly instead of a generic "signed in". Falls back to "signed in" when we
|
|
36
36
|
* have neither (Antigravity), and empty when signed out.
|
|
37
|
+
*
|
|
38
|
+
* When the account carries a Claude organizationType, the org badge is appended
|
|
39
|
+
* — "email (Turing Labs · Team)" — so two installs signed into the same email
|
|
40
|
+
* under different orgs (personal Max vs a Team seat) read distinctly. The
|
|
41
|
+
* suffix is plain text inside the label so the existing column-width pass
|
|
42
|
+
* measures and pads it for free.
|
|
37
43
|
*/
|
|
38
|
-
function accountColumnLabel(info) {
|
|
44
|
+
export function accountColumnLabel(info) {
|
|
39
45
|
if (!info)
|
|
40
46
|
return '';
|
|
41
|
-
if (info.email)
|
|
42
|
-
|
|
47
|
+
if (info.email) {
|
|
48
|
+
const badge = accountOrgBadge(info);
|
|
49
|
+
return badge ? `${info.email} (${badge})` : info.email;
|
|
50
|
+
}
|
|
43
51
|
if (info.signedIn)
|
|
44
52
|
return info.accountId ? `id:${info.accountId}` : SIGNED_IN_LABEL;
|
|
45
53
|
return '';
|
|
@@ -1149,6 +1157,8 @@ async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1149
1157
|
signedIn: info.signedIn,
|
|
1150
1158
|
email: info.email,
|
|
1151
1159
|
accountId: info.accountId,
|
|
1160
|
+
organizationType: info.organizationType ?? null,
|
|
1161
|
+
organizationName: info.organizationName ?? null,
|
|
1152
1162
|
plan: info.plan,
|
|
1153
1163
|
usageStatus: info.usageStatus,
|
|
1154
1164
|
overageCredits: info.overageCredits,
|
|
@@ -1184,6 +1194,17 @@ async function collectAgentsJson(filterAgentId, resourceSections) {
|
|
|
1184
1194
|
}
|
|
1185
1195
|
return out;
|
|
1186
1196
|
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Identity key for duplicate-install detection. Prefers accountKey — which
|
|
1199
|
+
* encodes account AND org — over the bare email: two installs can share an
|
|
1200
|
+
* email yet belong to different orgs (a personal Max plan and a Team seat),
|
|
1201
|
+
* and grouping those by email alone would propose pruning a live account.
|
|
1202
|
+
* Falls back to the lowercased email for agents whose credentials expose no
|
|
1203
|
+
* identity key. Null when there is no usable identity.
|
|
1204
|
+
*/
|
|
1205
|
+
export function pruneGroupKey(info) {
|
|
1206
|
+
return info.accountKey ?? info.email?.toLowerCase() ?? null;
|
|
1207
|
+
}
|
|
1187
1208
|
async function buildAgentPrunePlan(agentId) {
|
|
1188
1209
|
const dirInfos = listInstalledVersionDirs(agentId);
|
|
1189
1210
|
const entries = await Promise.all(dirInfos.map(async ({ version, hasBinary }) => {
|
|
@@ -1198,16 +1219,18 @@ async function buildAgentPrunePlan(agentId) {
|
|
|
1198
1219
|
// working binary — those are the things that compete for "the live install
|
|
1199
1220
|
// for this account."
|
|
1200
1221
|
const installed = entries.filter((e) => e.hasBinary);
|
|
1201
|
-
const
|
|
1222
|
+
const byAccount = new Map();
|
|
1202
1223
|
for (const e of installed) {
|
|
1203
1224
|
if (!e.info.email)
|
|
1204
1225
|
continue;
|
|
1205
|
-
const key = e.info
|
|
1206
|
-
|
|
1226
|
+
const key = pruneGroupKey(e.info);
|
|
1227
|
+
if (!key)
|
|
1228
|
+
continue;
|
|
1229
|
+
const list = byAccount.get(key) ?? [];
|
|
1207
1230
|
list.push(e);
|
|
1208
|
-
|
|
1231
|
+
byAccount.set(key, list);
|
|
1209
1232
|
}
|
|
1210
|
-
for (const [, group] of
|
|
1233
|
+
for (const [, group] of byAccount) {
|
|
1211
1234
|
if (group.length < 2)
|
|
1212
1235
|
continue;
|
|
1213
1236
|
const sorted = [...group].sort((a, b) => compareVersions(b.version, a.version));
|
package/dist/commands/webhook.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
1
2
|
import chalk from 'chalk';
|
|
2
|
-
import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
|
|
3
|
-
import { startWebhookServer } from '../lib/triggers/webhook.js';
|
|
3
|
+
import { readAndResolveBundleEnv, isHeadlessSecretsContext } from '../lib/secrets/bundles.js';
|
|
4
|
+
import { createFileDeliveryStore, startWebhookServer } from '../lib/triggers/webhook.js';
|
|
5
|
+
import { getRuntimeStateDir } from '../lib/state.js';
|
|
4
6
|
const DEFAULT_HOST = '127.0.0.1';
|
|
5
7
|
const DEFAULT_PORT = 8787;
|
|
6
8
|
function positiveInt(value, fallback) {
|
|
@@ -10,6 +12,9 @@ function positiveInt(value, fallback) {
|
|
|
10
12
|
function readWebhookSecrets(bundleName) {
|
|
11
13
|
const { env } = readAndResolveBundleEnv(bundleName, {
|
|
12
14
|
caller: 'webhook serve',
|
|
15
|
+
// `webhook serve` is a long-running background server; when started detached
|
|
16
|
+
// (no TTY) it must resolve broker-only rather than pop an unanswerable prompt.
|
|
17
|
+
agentOnly: isHeadlessSecretsContext(),
|
|
13
18
|
});
|
|
14
19
|
const secrets = {};
|
|
15
20
|
if (env.GITHUB_WEBHOOK_SECRET)
|
|
@@ -69,6 +74,9 @@ export function registerWebhookCommand(program) {
|
|
|
69
74
|
port,
|
|
70
75
|
secrets,
|
|
71
76
|
rateLimitPerMinute: rateLimit,
|
|
77
|
+
// Durable delivery dedup: replays survive a receiver restart (an
|
|
78
|
+
// in-memory store would forget every seen delivery on restart).
|
|
79
|
+
deliveryStore: createFileDeliveryStore(path.join(getRuntimeStateDir(), 'webhook', 'deliveries.json')),
|
|
72
80
|
onDelivery: (webhook, fired) => {
|
|
73
81
|
console.log(`${new Date().toISOString()} ${webhook.source}:${webhook.event} ` +
|
|
74
82
|
`${fired.length ? `fired ${fired.map((f) => f.jobName).join(', ')}` : 'no match'}`);
|
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
26
26
|
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
27
27
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
28
28
|
const VERSION = packageJson.version;
|
|
29
|
-
import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, } from './lib/self-update.js';
|
|
29
|
+
import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, downloadVerifiedTarball, } from './lib/self-update.js';
|
|
30
30
|
// Detect dev/working-tree builds and default the noisy startup steps off.
|
|
31
31
|
// Three cases trip this:
|
|
32
32
|
// 1. Dev install (scripts/install.sh) — package.json version stamped 0.0.0-dev.<sha>
|
|
@@ -50,7 +50,7 @@ if (IS_DEV_BUILD) {
|
|
|
50
50
|
// module on each invocation (which loaded the whole ~50-module tree before the
|
|
51
51
|
// first byte of output), the registry maps a command name to a thunk that
|
|
52
52
|
// imports only what that command needs. See src/lib/startup/command-registry.ts.
|
|
53
|
-
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadCheck, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadOutput, loadBudget, loadAlias, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
53
|
+
import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, loadView, loadInspect, loadFeedback, loadCommands, loadHooks, loadSkills, loadRules, loadMemory, loadPermissions, loadMcp, loadCli, loadSubagents, loadPlugins, loadWorkflows, loadWorktree, loadVersions, loadImport, loadPackages, loadDaemon, loadRoutines, loadMonitors, loadRun, loadDefaults, loadModels, loadPrune, loadTrash, loadRestore, loadDoctor, loadApply, loadCheck, loadStatus, loadProfiles, loadSecrets, loadWallet, loadHelper, loadMenubar, loadBeta, loadSync, loadLock, loadRefreshRules, loadDrive, loadFactory, loadUsage, loadCost, loadOutput, loadBudget, loadAlias, loadPty, loadTmux, loadWatchdog, loadBrowser, loadComputer, loadHosts, loadLogs, loadEvents, loadAudit, loadWebhook, loadFunnel, loadSsh, loadPull, loadPush, loadRepo, loadSetup, loadShare, loadFeed, loadMailboxes, } from './lib/startup/command-registry.js';
|
|
54
54
|
import { applyGlobalHelpConventions } from './lib/help.js';
|
|
55
55
|
import { renderWhatsNew } from './lib/whats-new.js';
|
|
56
56
|
import { emit, redactArgs } from './lib/events.js';
|
|
@@ -315,10 +315,12 @@ async function fetchNpmPackageMetadata(versionOrTag = 'latest', timeoutMs = 5000
|
|
|
315
315
|
throw new Error('Could not reach npm registry');
|
|
316
316
|
}
|
|
317
317
|
const data = await response.json();
|
|
318
|
-
if (typeof data.version !== 'string' ||
|
|
319
|
-
|
|
318
|
+
if (typeof data.version !== 'string' ||
|
|
319
|
+
typeof data.dist?.integrity !== 'string' ||
|
|
320
|
+
typeof data.dist?.tarball !== 'string') {
|
|
321
|
+
throw new Error('npm registry response did not include version, integrity, and tarball');
|
|
320
322
|
}
|
|
321
|
-
return { version: data.version, integrity: data.dist.integrity };
|
|
323
|
+
return { version: data.version, integrity: data.dist.integrity, tarball: data.dist.tarball };
|
|
322
324
|
}
|
|
323
325
|
function printResolvedPackage(metadata) {
|
|
324
326
|
console.log(chalk.gray(`Resolved: ${NPM_PACKAGE_NAME}@${metadata.version}`));
|
|
@@ -326,16 +328,32 @@ function printResolvedPackage(metadata) {
|
|
|
326
328
|
}
|
|
327
329
|
async function installResolvedPackage(metadata) {
|
|
328
330
|
const packageRoot = path.resolve(__dirname, '..');
|
|
329
|
-
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
// Download the published tarball and prove its bytes match the registry
|
|
332
|
+
// integrity BEFORE installing anything. A `name@version` spec would let the
|
|
333
|
+
// package manager fetch and install whatever the registry serves with no
|
|
334
|
+
// hash check on our side; instead we verify here and install the LOCAL, now
|
|
335
|
+
// trusted .tgz. A mismatch throws and nothing below runs — fail closed.
|
|
336
|
+
const tarball = await downloadVerifiedTarball(metadata.tarball, metadata.integrity);
|
|
337
|
+
try {
|
|
338
|
+
// Upgrade with the package manager that owns this install. A bun global
|
|
339
|
+
// install lives at <bunGlobalDir>/node_modules/... (no `lib` segment), so an
|
|
340
|
+
// `npm install --prefix` would write to <bunGlobalDir>/lib/node_modules and
|
|
341
|
+
// never touch the running copy — npm exits 0, the verify below fails.
|
|
342
|
+
if (detectPackageManager(packageRoot) === 'bun') {
|
|
343
|
+
await installPackageWithBun(tarball);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
await installPackageIntoPrefix(tarball, deriveGlobalPrefix(packageRoot));
|
|
347
|
+
}
|
|
336
348
|
}
|
|
337
|
-
|
|
338
|
-
|
|
349
|
+
finally {
|
|
350
|
+
// Best-effort cleanup of the verified tarball and its temp dir.
|
|
351
|
+
try {
|
|
352
|
+
fs.rmSync(path.dirname(tarball), { recursive: true, force: true });
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
/* leave it for the OS temp sweep */
|
|
356
|
+
}
|
|
339
357
|
}
|
|
340
358
|
verifyInstalledVersion(packageRoot, metadata.version);
|
|
341
359
|
refreshAliasShims(packageRoot);
|
|
@@ -649,6 +667,7 @@ async function registerEagerForRequest(name) {
|
|
|
649
667
|
*/
|
|
650
668
|
async function registerAllEagerCommands() {
|
|
651
669
|
await reg(loadView);
|
|
670
|
+
await reg(loadShare);
|
|
652
671
|
await reg(loadInspect);
|
|
653
672
|
await reg(loadFeedback);
|
|
654
673
|
await reg(loadCommands);
|
|
@@ -669,6 +688,7 @@ async function registerAllEagerCommands() {
|
|
|
669
688
|
await reg(loadPackages);
|
|
670
689
|
await reg(loadDaemon);
|
|
671
690
|
await reg(loadRoutines);
|
|
691
|
+
await reg(loadMonitors);
|
|
672
692
|
await reg(loadRun);
|
|
673
693
|
await reg(loadDefaults);
|
|
674
694
|
await reg(loadModels);
|
|
@@ -676,6 +696,7 @@ async function registerAllEagerCommands() {
|
|
|
676
696
|
await reg(loadTrash);
|
|
677
697
|
await reg(loadRestore);
|
|
678
698
|
await reg(loadDoctor);
|
|
699
|
+
await reg(loadApply);
|
|
679
700
|
await reg(loadCheck);
|
|
680
701
|
await reg(loadStatus);
|
|
681
702
|
registerExecAliasCommand(program);
|
package/dist/lib/agents.d.ts
CHANGED
|
@@ -127,7 +127,25 @@ export interface AccountInfo {
|
|
|
127
127
|
} | null;
|
|
128
128
|
lastActive: Date | null;
|
|
129
129
|
signedIn: boolean;
|
|
130
|
+
organizationType?: string | null;
|
|
131
|
+
organizationName?: string | null;
|
|
130
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Human-readable label for a Claude account's organizationType as read from
|
|
135
|
+
* .claude.json's oauthAccount ("claude_team" -> "Team"). Unrecognized values
|
|
136
|
+
* (future tiers) are rendered by stripping the "claude_" prefix and
|
|
137
|
+
* title-casing the rest — an unfamiliar-but-visible label beats silence.
|
|
138
|
+
* Returns null for missing input.
|
|
139
|
+
*/
|
|
140
|
+
export declare function formatClaudeOrgLabel(orgType: string | null | undefined): string | null;
|
|
141
|
+
/**
|
|
142
|
+
* Short badge identifying which org an account belongs to: "Turing Labs · Team"
|
|
143
|
+
* for multi-seat orgs, just the tier label ("Max") for personal plans — a
|
|
144
|
+
* personal org's name is auto-generated boilerplate ("<email>'s Organization"),
|
|
145
|
+
* not identity. Returns null when the account carries no organizationType
|
|
146
|
+
* (signed out, non-Claude agents, configs predating the field).
|
|
147
|
+
*/
|
|
148
|
+
export declare function accountOrgBadge(info?: Pick<AccountInfo, 'organizationType' | 'organizationName'> | null): string | null;
|
|
131
149
|
/** Return the email address associated with the agent's auth config, or null. */
|
|
132
150
|
export declare function getAccountEmail(agentId: AgentId, home?: string): Promise<string | null>;
|
|
133
151
|
/** Decrypted contents of Droid's auth.v2.file (subset we consume). */
|
|
@@ -146,6 +164,34 @@ export interface DroidAuthPayload {
|
|
|
146
164
|
* in usage.ts.
|
|
147
165
|
*/
|
|
148
166
|
export declare function decryptDroidAuthPayload(base: string): DroidAuthPayload | null;
|
|
167
|
+
/**
|
|
168
|
+
* Decrypt a Droid `auth.v2.file` (AES-256-GCM `ivB64:tagB64:ctB64`) using the
|
|
169
|
+
* raw 32-byte key stored base64 in `auth.v2.key`, given the EXACT paths to both.
|
|
170
|
+
* Same crypto as decryptDroidAuthPayload but without the account-global HOME
|
|
171
|
+
* fallback, so the identity of a SPECIFIC version home resolves against only
|
|
172
|
+
* that home's files (carryForwardAuthFiles needs per-dir identity). Returns null
|
|
173
|
+
* on any failure (missing file/key, wrong key length, bad GCM tag, malformed
|
|
174
|
+
* JSON). Never throws.
|
|
175
|
+
*/
|
|
176
|
+
export declare function decryptDroidAuthFile(filePath: string, keyPath: string): DroidAuthPayload | null;
|
|
177
|
+
/**
|
|
178
|
+
* Stable account identity for a *file-auth* agent's credential directory
|
|
179
|
+
* (droid / kimi / antigravity), or null when the directory holds no decodable
|
|
180
|
+
* account claim. Unlike a naive top-level JSON key-scan (which matched NO real
|
|
181
|
+
* credential file), this decrypts / decodes each agent's REAL on-disk format so
|
|
182
|
+
* the identity resolves against production credentials:
|
|
183
|
+
* - droid: AES-256-GCM auth.v2.file (+ auth.v2.key) -> WorkOS access-token JWT
|
|
184
|
+
* -> email / org_id / sub.
|
|
185
|
+
* - kimi: credentials/kimi-code.json -> access-token JWT -> user_id / sub.
|
|
186
|
+
* - antigravity: antigravity-oauth-token -> token.refresh_token -> JWT sub
|
|
187
|
+
* when the token is a JWT, else the raw refresh-token value (opaque Google
|
|
188
|
+
* consumer tokens are stable per login).
|
|
189
|
+
* Two directories for the SAME account compare equal; two DIFFERENT accounts
|
|
190
|
+
* compare distinct. Used by carryForwardAuthFiles to refuse overwriting one
|
|
191
|
+
* account's login with a credential that belongs to a DIFFERENT account
|
|
192
|
+
* (RUSH-1764). Never throws.
|
|
193
|
+
*/
|
|
194
|
+
export declare function readAuthAccountIdentity(agent: AgentId, configDir: string): string | null;
|
|
149
195
|
/**
|
|
150
196
|
* Antigravity (`agy`) stores its OAuth token via the Go keyring library
|
|
151
197
|
* (zalando/go-keyring), which is platform-split:
|
package/dist/lib/agents.js
CHANGED
|
@@ -17,7 +17,7 @@ import * as os from 'os';
|
|
|
17
17
|
import * as TOML from 'smol-toml';
|
|
18
18
|
import * as yaml from 'yaml';
|
|
19
19
|
import chalk from 'chalk';
|
|
20
|
-
import {
|
|
20
|
+
import { execFileShellSpec } from './platform/index.js';
|
|
21
21
|
import { latestFileMtimeMs } from './fs-walk.js';
|
|
22
22
|
import { damerauLevenshtein } from './fuzzy.js';
|
|
23
23
|
import { getCacheDir, getVersionsDir, getShimsDir, getCliVersionCachePath } from './state.js';
|
|
@@ -943,6 +943,48 @@ export function ensureSkillsDir(agentId) {
|
|
|
943
943
|
export function agentConfigDirName(agentId) {
|
|
944
944
|
return path.relative(HOME, AGENTS[agentId].configDir);
|
|
945
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* Human-readable label for a Claude account's organizationType as read from
|
|
948
|
+
* .claude.json's oauthAccount ("claude_team" -> "Team"). Unrecognized values
|
|
949
|
+
* (future tiers) are rendered by stripping the "claude_" prefix and
|
|
950
|
+
* title-casing the rest — an unfamiliar-but-visible label beats silence.
|
|
951
|
+
* Returns null for missing input.
|
|
952
|
+
*/
|
|
953
|
+
export function formatClaudeOrgLabel(orgType) {
|
|
954
|
+
if (!orgType)
|
|
955
|
+
return null;
|
|
956
|
+
const known = {
|
|
957
|
+
claude_max: 'Max',
|
|
958
|
+
claude_pro: 'Pro',
|
|
959
|
+
claude_team: 'Team',
|
|
960
|
+
claude_enterprise: 'Enterprise',
|
|
961
|
+
claude_free: 'Free',
|
|
962
|
+
};
|
|
963
|
+
if (known[orgType])
|
|
964
|
+
return known[orgType];
|
|
965
|
+
return orgType
|
|
966
|
+
.replace(/^claude_/, '')
|
|
967
|
+
.split('_')
|
|
968
|
+
.filter(Boolean)
|
|
969
|
+
.map((w) => w[0].toUpperCase() + w.slice(1))
|
|
970
|
+
.join(' ');
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Short badge identifying which org an account belongs to: "Turing Labs · Team"
|
|
974
|
+
* for multi-seat orgs, just the tier label ("Max") for personal plans — a
|
|
975
|
+
* personal org's name is auto-generated boilerplate ("<email>'s Organization"),
|
|
976
|
+
* not identity. Returns null when the account carries no organizationType
|
|
977
|
+
* (signed out, non-Claude agents, configs predating the field).
|
|
978
|
+
*/
|
|
979
|
+
export function accountOrgBadge(info) {
|
|
980
|
+
const label = formatClaudeOrgLabel(info?.organizationType);
|
|
981
|
+
if (!label)
|
|
982
|
+
return null;
|
|
983
|
+
const isMultiSeat = info?.organizationType === 'claude_team' || info?.organizationType === 'claude_enterprise';
|
|
984
|
+
if (isMultiSeat && info?.organizationName)
|
|
985
|
+
return `${info.organizationName} · ${label}`;
|
|
986
|
+
return label;
|
|
987
|
+
}
|
|
946
988
|
/** Return the email address associated with the agent's auth config, or null. */
|
|
947
989
|
export async function getAccountEmail(agentId, home) {
|
|
948
990
|
const info = await getAccountInfo(agentId, home);
|
|
@@ -994,6 +1036,18 @@ export function decryptDroidAuthPayload(base) {
|
|
|
994
1036
|
const keyPath = resolveAccountCredentialPath(base, '.factory', 'auth.v2.key');
|
|
995
1037
|
if (!filePath || !keyPath)
|
|
996
1038
|
return null;
|
|
1039
|
+
return decryptDroidAuthFile(filePath, keyPath);
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Decrypt a Droid `auth.v2.file` (AES-256-GCM `ivB64:tagB64:ctB64`) using the
|
|
1043
|
+
* raw 32-byte key stored base64 in `auth.v2.key`, given the EXACT paths to both.
|
|
1044
|
+
* Same crypto as decryptDroidAuthPayload but without the account-global HOME
|
|
1045
|
+
* fallback, so the identity of a SPECIFIC version home resolves against only
|
|
1046
|
+
* that home's files (carryForwardAuthFiles needs per-dir identity). Returns null
|
|
1047
|
+
* on any failure (missing file/key, wrong key length, bad GCM tag, malformed
|
|
1048
|
+
* JSON). Never throws.
|
|
1049
|
+
*/
|
|
1050
|
+
export function decryptDroidAuthFile(filePath, keyPath) {
|
|
997
1051
|
try {
|
|
998
1052
|
const blob = fs.readFileSync(filePath, 'utf-8').trim();
|
|
999
1053
|
const key = Buffer.from(fs.readFileSync(keyPath, 'utf-8').trim(), 'base64');
|
|
@@ -1015,6 +1069,62 @@ export function decryptDroidAuthPayload(base) {
|
|
|
1015
1069
|
return null;
|
|
1016
1070
|
}
|
|
1017
1071
|
}
|
|
1072
|
+
/**
|
|
1073
|
+
* Stable account identity for a *file-auth* agent's credential directory
|
|
1074
|
+
* (droid / kimi / antigravity), or null when the directory holds no decodable
|
|
1075
|
+
* account claim. Unlike a naive top-level JSON key-scan (which matched NO real
|
|
1076
|
+
* credential file), this decrypts / decodes each agent's REAL on-disk format so
|
|
1077
|
+
* the identity resolves against production credentials:
|
|
1078
|
+
* - droid: AES-256-GCM auth.v2.file (+ auth.v2.key) -> WorkOS access-token JWT
|
|
1079
|
+
* -> email / org_id / sub.
|
|
1080
|
+
* - kimi: credentials/kimi-code.json -> access-token JWT -> user_id / sub.
|
|
1081
|
+
* - antigravity: antigravity-oauth-token -> token.refresh_token -> JWT sub
|
|
1082
|
+
* when the token is a JWT, else the raw refresh-token value (opaque Google
|
|
1083
|
+
* consumer tokens are stable per login).
|
|
1084
|
+
* Two directories for the SAME account compare equal; two DIFFERENT accounts
|
|
1085
|
+
* compare distinct. Used by carryForwardAuthFiles to refuse overwriting one
|
|
1086
|
+
* account's login with a credential that belongs to a DIFFERENT account
|
|
1087
|
+
* (RUSH-1764). Never throws.
|
|
1088
|
+
*/
|
|
1089
|
+
export function readAuthAccountIdentity(agent, configDir) {
|
|
1090
|
+
try {
|
|
1091
|
+
switch (agent) {
|
|
1092
|
+
case 'droid': {
|
|
1093
|
+
const payload = decryptDroidAuthFile(path.join(configDir, 'auth.v2.file'), path.join(configDir, 'auth.v2.key'));
|
|
1094
|
+
const claims = typeof payload?.access_token === 'string' ? decodeJwtPayload(payload.access_token) : null;
|
|
1095
|
+
if (!claims)
|
|
1096
|
+
return null;
|
|
1097
|
+
return buildIdentityKey(agent, [
|
|
1098
|
+
['email', normalizeIdentityPart(claims.email)],
|
|
1099
|
+
['org', normalizeIdentityPart(claims.org_id ?? payload?.active_organization_id)],
|
|
1100
|
+
['sub', normalizeIdentityPart(claims.sub)],
|
|
1101
|
+
]);
|
|
1102
|
+
}
|
|
1103
|
+
case 'kimi': {
|
|
1104
|
+
const data = JSON.parse(fs.readFileSync(path.join(configDir, 'credentials', 'kimi-code.json'), 'utf-8'));
|
|
1105
|
+
const accessToken = data?.access_token;
|
|
1106
|
+
const claims = typeof accessToken === 'string' ? decodeJwtPayload(accessToken) : null;
|
|
1107
|
+
return buildIdentityKey(agent, [
|
|
1108
|
+
['user', normalizeIdentityPart(claims?.user_id ?? claims?.sub)],
|
|
1109
|
+
]);
|
|
1110
|
+
}
|
|
1111
|
+
case 'antigravity': {
|
|
1112
|
+
const data = JSON.parse(fs.readFileSync(path.join(configDir, 'antigravity-oauth-token'), 'utf-8'));
|
|
1113
|
+
const refreshToken = data?.token?.refresh_token;
|
|
1114
|
+
if (typeof refreshToken !== 'string' || !refreshToken)
|
|
1115
|
+
return null;
|
|
1116
|
+
const claims = decodeJwtPayload(refreshToken);
|
|
1117
|
+
const sub = normalizeIdentityPart(claims?.sub ?? claims?.user_id);
|
|
1118
|
+
return buildIdentityKey(agent, [['sub', sub ?? refreshToken]]);
|
|
1119
|
+
}
|
|
1120
|
+
default:
|
|
1121
|
+
return null;
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
catch {
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1018
1128
|
/**
|
|
1019
1129
|
* Derive Droid account identity from the decrypted credential. The
|
|
1020
1130
|
* `access_token` is a WorkOS JWT carrying an `email` claim (plus org_id /
|
|
@@ -1248,6 +1358,8 @@ export async function getAccountInfo(agentId, home) {
|
|
|
1248
1358
|
overageCredits,
|
|
1249
1359
|
lastActive,
|
|
1250
1360
|
signedIn: !!email,
|
|
1361
|
+
organizationType: oa?.organizationType ?? null,
|
|
1362
|
+
organizationName: oa?.organizationName ?? null,
|
|
1251
1363
|
};
|
|
1252
1364
|
}
|
|
1253
1365
|
case 'codex': {
|
|
@@ -1659,7 +1771,10 @@ export async function registerMcp(agentId, name, command, scope = 'user', transp
|
|
|
1659
1771
|
// On Windows a bare command name / `.cmd` wrapper (the npm-installed agent
|
|
1660
1772
|
// CLI) can't be exec'd directly — it needs shell:true for PATHEXT/cmd. Off
|
|
1661
1773
|
// Windows this is always false, so the no-shell argv path is unchanged.
|
|
1662
|
-
|
|
1774
|
+
// RUSH-1752: when shell is needed, compose a fully-quoted command line and
|
|
1775
|
+
// pass EMPTY argv so user-controlled MCP command/args never reach cmd.exe unescaped.
|
|
1776
|
+
const spec = execFileShellSpec(bin, args);
|
|
1777
|
+
await execFileAsync(spec.command, spec.args, { ...(env ? { env } : {}), shell: spec.shell });
|
|
1663
1778
|
return { success: true };
|
|
1664
1779
|
}
|
|
1665
1780
|
catch (err) {
|
|
@@ -1687,7 +1802,10 @@ export async function unregisterMcp(agentId, name, options) {
|
|
|
1687
1802
|
try {
|
|
1688
1803
|
const bin = options?.binary || agent.cliCommand;
|
|
1689
1804
|
const env = options?.home ? { ...process.env, HOME: options.home } : undefined;
|
|
1690
|
-
|
|
1805
|
+
// RUSH-1752: same shell-safe path as registerMcp — attacker-controlled MCP
|
|
1806
|
+
// `name` must not reach cmd.exe unescaped when shell:true is required.
|
|
1807
|
+
const spec = execFileShellSpec(bin, ['mcp', 'remove', name]);
|
|
1808
|
+
await execFileAsync(spec.command, spec.args, { ...(env ? { env } : {}), shell: spec.shell });
|
|
1691
1809
|
return { success: true };
|
|
1692
1810
|
}
|
|
1693
1811
|
catch (err) {
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import { resolveProvider } from './cloud/registry.js';
|
|
10
10
|
export function createProviderDispatcher() {
|
|
11
11
|
return {
|
|
12
|
-
async dispatch({ agent, prompt, repo, provider }) {
|
|
12
|
+
async dispatch({ agent, prompt, repo, provider, host }) {
|
|
13
13
|
const p = resolveProvider(provider, agent);
|
|
14
14
|
const caps = p.capabilities();
|
|
15
15
|
if (!caps.available) {
|
|
@@ -18,7 +18,12 @@ export function createProviderDispatcher() {
|
|
|
18
18
|
if (!caps.dispatch) {
|
|
19
19
|
throw new Error(`cloud provider '${p.id}' does not support dispatch`);
|
|
20
20
|
}
|
|
21
|
-
|
|
21
|
+
// The host provider clones nothing — a repo target is meaningless there
|
|
22
|
+
// and its dispatch() rejects it; a ticket project pinned to 'host' relies
|
|
23
|
+
// on the machine's own checkout (the project's `host` names the machine).
|
|
24
|
+
const task = p.id === 'host'
|
|
25
|
+
? await p.dispatch({ agent, prompt, providerOptions: { host } })
|
|
26
|
+
: await p.dispatch({ agent, prompt, repo });
|
|
22
27
|
return { id: task.id };
|
|
23
28
|
},
|
|
24
29
|
};
|
|
@@ -22,6 +22,7 @@ export interface AutoDispatchProject {
|
|
|
22
22
|
autoDispatch?: boolean;
|
|
23
23
|
maxAgents?: number;
|
|
24
24
|
provider?: string;
|
|
25
|
+
host?: string;
|
|
25
26
|
}
|
|
26
27
|
/** A Linear issue that is a candidate for dispatch. */
|
|
27
28
|
export interface DelegatedIssue {
|
|
@@ -36,6 +37,7 @@ export interface PlannedDispatch {
|
|
|
36
37
|
projectId: string;
|
|
37
38
|
repoSlug?: string;
|
|
38
39
|
provider?: string;
|
|
40
|
+
host?: string;
|
|
39
41
|
issueId: string;
|
|
40
42
|
identifier: string;
|
|
41
43
|
title: string;
|
|
@@ -73,6 +75,7 @@ export interface Dispatcher {
|
|
|
73
75
|
prompt: string;
|
|
74
76
|
repo?: string;
|
|
75
77
|
provider?: string;
|
|
78
|
+
host?: string;
|
|
76
79
|
}): Promise<{
|
|
77
80
|
id: string;
|
|
78
81
|
}>;
|
|
@@ -46,6 +46,7 @@ export function readAutoDispatchProjects() {
|
|
|
46
46
|
autoDispatch: o.autoDispatch === true,
|
|
47
47
|
maxAgents: typeof o.maxAgents === 'number' ? o.maxAgents : undefined,
|
|
48
48
|
provider: typeof o.provider === 'string' ? o.provider : undefined,
|
|
49
|
+
host: typeof o.host === 'string' ? o.host : undefined,
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
return out;
|
|
@@ -76,6 +77,7 @@ export function planAutoDispatch(projects, inFlightByProject, delegatedTodoByPro
|
|
|
76
77
|
projectId: p.id,
|
|
77
78
|
repoSlug: p.repoSlug,
|
|
78
79
|
provider: p.provider,
|
|
80
|
+
host: p.host,
|
|
79
81
|
issueId: issue.id,
|
|
80
82
|
identifier: issue.identifier,
|
|
81
83
|
title: issue.title,
|
|
@@ -122,6 +124,7 @@ export async function autoDispatchTick(deps) {
|
|
|
122
124
|
prompt: dispatchPrompt(d.identifier, d.title),
|
|
123
125
|
repo: d.repoSlug,
|
|
124
126
|
provider: d.provider,
|
|
127
|
+
host: d.host,
|
|
125
128
|
});
|
|
126
129
|
// Bookkeeping only — dispatch already happened; moving to Doing keeps the
|
|
127
130
|
// ticket out of the next Todo poll. A failure here is non-fatal.
|
|
@@ -4,7 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import * as os from 'os';
|
|
5
5
|
import { getProfileRuntimeDir } from './profiles.js';
|
|
6
6
|
import { discoverBrowserWsUrl, registerPipeTransport } from './cdp.js';
|
|
7
|
-
import { readAndResolveBundleEnv, bundleExists } from '../secrets/bundles.js';
|
|
7
|
+
import { readAndResolveBundleEnv, bundleExists, isHeadlessSecretsContext } from '../secrets/bundles.js';
|
|
8
8
|
import { writeProfileRuntime, readProfileRuntime } from './runtime-state.js';
|
|
9
9
|
// Windows install roots. Resolve from the environment (fall back to the usual
|
|
10
10
|
// defaults) so per-user installs under %LOCALAPPDATA% and 64-bit Program Files
|
|
@@ -260,7 +260,7 @@ isElectron = false) {
|
|
|
260
260
|
let env = { ...process.env };
|
|
261
261
|
if (secrets && bundleExists(secrets)) {
|
|
262
262
|
try {
|
|
263
|
-
const { env: bundleEnv } = readAndResolveBundleEnv(secrets, { caller: 'browser profile' });
|
|
263
|
+
const { env: bundleEnv } = readAndResolveBundleEnv(secrets, { caller: 'browser profile', agentOnly: isHeadlessSecretsContext() });
|
|
264
264
|
env = { ...env, ...bundleEnv };
|
|
265
265
|
}
|
|
266
266
|
catch {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
|
|
21
21
|
*/
|
|
22
22
|
import { resolveDispatchRepos, normalizeProviderStatus } from './types.js';
|
|
23
|
-
import { readAndResolveBundleEnv } from '../secrets/bundles.js';
|
|
23
|
+
import { readAndResolveBundleEnv, isHeadlessSecretsContext } from '../secrets/bundles.js';
|
|
24
24
|
const INTERACTIONS_URL = 'https://generativelanguage.googleapis.com/v1beta/interactions';
|
|
25
25
|
const DEFAULT_MODEL = 'antigravity-preview-05-2026';
|
|
26
26
|
const KEY_NAMES = ['GEMINI_API_KEY', 'GOOGLE_API_KEY'];
|
|
@@ -65,7 +65,7 @@ export class AntigravityCloudProvider {
|
|
|
65
65
|
resolveApiKey() {
|
|
66
66
|
if (this.secretsBundle) {
|
|
67
67
|
try {
|
|
68
|
-
const { env } = readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:antigravity' });
|
|
68
|
+
const { env } = readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:antigravity', agentOnly: isHeadlessSecretsContext() });
|
|
69
69
|
for (const k of KEY_NAMES) {
|
|
70
70
|
if (env[k])
|
|
71
71
|
return env[k];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host cloud provider — your own machines as a task-execution backend.
|
|
3
|
+
*
|
|
4
|
+
* A thin adapter over the hosts subsystem (lib/hosts/*): `agents cloud run
|
|
5
|
+
* --provider host --host <name>` dispatches through the SAME detached-SSH
|
|
6
|
+
* launch as `agents run --host`, and the resulting task shows up in BOTH
|
|
7
|
+
* `agents cloud ps` and `agents hosts ps` — one store (the host-task sidecars
|
|
8
|
+
* under ~/.agents/.cache/hosts/), two views. No new transport, no relay: SSH
|
|
9
|
+
* is the only wire, exactly like the rest of the hosts design (docs/hosts.md).
|
|
10
|
+
*
|
|
11
|
+
* Status semantics follow reconcile.ts's prime rule: completion is only ever
|
|
12
|
+
* CONFIRMED from the remote `.exit` file — an unreachable host leaves a task
|
|
13
|
+
* `running`, never guessed as failed. The cloud SQLite store row is a cached
|
|
14
|
+
* index (the `cloud list` refresh loop upserts what `status()` returns); the
|
|
15
|
+
* sidecar stays the source of truth. Reachability is memoized per target for
|
|
16
|
+
* the life of the process so a down host costs ONE short timeout, not one per
|
|
17
|
+
* task ("care around the cloud status-refresh path", lib/hosts/tasks.ts).
|
|
18
|
+
*/
|
|
19
|
+
import type { CloudEvent, CloudProvider, CloudTarget, CloudTask, CloudTaskStatus, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
20
|
+
import type { HostTask } from '../hosts/tasks.js';
|
|
21
|
+
/** Project a host-task sidecar into the cloud task shape. */
|
|
22
|
+
export declare function hostTaskToCloudTask(task: HostTask): CloudTask;
|
|
23
|
+
export declare class HostCloudProvider implements CloudProvider {
|
|
24
|
+
readonly id: "host";
|
|
25
|
+
readonly name = "Host (your machines)";
|
|
26
|
+
readonly targetKind: "host";
|
|
27
|
+
/**
|
|
28
|
+
* Reachability memo, per target, for the life of this process. The cloud
|
|
29
|
+
* refresh loop calls `status()` once per active task; without the memo a
|
|
30
|
+
* down host would cost one ~6s ssh timeout PER task instead of one total.
|
|
31
|
+
* CLI processes are short-lived, so staleness is bounded by the invocation.
|
|
32
|
+
*/
|
|
33
|
+
private reachable;
|
|
34
|
+
capabilities(): ProviderCapabilities;
|
|
35
|
+
dispatch(options: DispatchOptions): Promise<CloudTask>;
|
|
36
|
+
status(taskId: string): Promise<CloudTask>;
|
|
37
|
+
list(filter?: {
|
|
38
|
+
status?: CloudTaskStatus;
|
|
39
|
+
}): Promise<CloudTask[]>;
|
|
40
|
+
/**
|
|
41
|
+
* `reconcileTask` with the per-process reachability memo folded in: probe a
|
|
42
|
+
* target at most once per process; a down host leaves its tasks `running`.
|
|
43
|
+
*/
|
|
44
|
+
private reconcileMemoized;
|
|
45
|
+
/**
|
|
46
|
+
* Offset-tail the remote log (the same one-round-trip fetch the `run --host`
|
|
47
|
+
* follow uses) and yield it as `text` events until the `.exit` file lands.
|
|
48
|
+
*/
|
|
49
|
+
stream(taskId: string): AsyncIterable<CloudEvent>;
|
|
50
|
+
cancel(taskId: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Follow-up message = resume the run's session on the same host. Only runs
|
|
53
|
+
* that captured a session id (Claude — the one agent that takes
|
|
54
|
+
* `--session-id`) can be resumed; others get an actionable refusal.
|
|
55
|
+
*/
|
|
56
|
+
message(taskId: string, content: string): Promise<void>;
|
|
57
|
+
/** The unified host pool (enrolled hosts ∪ devices), dispatchable ones only. */
|
|
58
|
+
listTargets(): Promise<CloudTarget[]>;
|
|
59
|
+
}
|