@phnx-labs/agents-cli 1.20.21 → 1.20.23
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 +14 -0
- package/dist/commands/cloud.js +142 -13
- package/dist/commands/exec.js +13 -1
- package/dist/commands/menubar.d.ts +10 -0
- package/dist/commands/menubar.js +83 -0
- package/dist/commands/routines.js +34 -1
- package/dist/commands/secrets.d.ts +1 -1
- package/dist/commands/secrets.js +95 -38
- package/dist/index.js +292 -225
- package/dist/lib/agents.js +8 -0
- package/dist/lib/cloud/antigravity.d.ts +70 -0
- package/dist/lib/cloud/antigravity.js +196 -0
- package/dist/lib/cloud/codex.d.ts +1 -0
- package/dist/lib/cloud/codex.js +8 -2
- package/dist/lib/cloud/factory.d.ts +79 -18
- package/dist/lib/cloud/factory.js +324 -26
- package/dist/lib/cloud/registry.d.ts +18 -2
- package/dist/lib/cloud/registry.js +28 -4
- package/dist/lib/cloud/types.d.ts +73 -2
- package/dist/lib/cloud/types.js +17 -0
- package/dist/lib/exec.d.ts +2 -0
- package/dist/lib/exec.js +5 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +20 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +115 -0
- package/dist/lib/menubar/install-menubar.d.ts +57 -0
- package/dist/lib/menubar/install-menubar.js +291 -0
- package/dist/lib/secrets/agent.d.ts +9 -1
- package/dist/lib/secrets/agent.js +91 -10
- package/dist/lib/secrets/bundles.d.ts +19 -12
- package/dist/lib/secrets/bundles.js +22 -14
- package/dist/lib/self-update.d.ts +34 -0
- package/dist/lib/self-update.js +63 -2
- package/dist/lib/startup/command-registry.d.ts +99 -0
- package/dist/lib/startup/command-registry.js +136 -0
- package/dist/lib/types.d.ts +8 -0
- package/dist/lib/version.d.ts +11 -0
- package/dist/lib/version.js +20 -0
- package/package.json +5 -3
- package/scripts/postinstall.js +35 -0
package/dist/lib/agents.js
CHANGED
|
@@ -207,6 +207,9 @@ export const AGENTS = {
|
|
|
207
207
|
format: 'markdown',
|
|
208
208
|
variableSyntax: '$ARGUMENTS',
|
|
209
209
|
supportsHooks: true,
|
|
210
|
+
// Claude Code has no headless Anthropic-hosted dispatch CLI (only
|
|
211
|
+
// --remote-control, which bridges a *local* session). Its cloud is Rush.
|
|
212
|
+
cloudProvider: 'rush',
|
|
210
213
|
capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, subagents: true, rules: { file: 'CLAUDE.md' }, workflows: true, modes: ['plan', 'edit', 'auto', 'skip'], rulesImports: true },
|
|
211
214
|
},
|
|
212
215
|
// codex hooks: gated to >= 0.116.0 (introduced [features] codex_hooks flag).
|
|
@@ -226,6 +229,7 @@ export const AGENTS = {
|
|
|
226
229
|
format: 'markdown',
|
|
227
230
|
variableSyntax: '$ARGUMENTS',
|
|
228
231
|
supportsHooks: true,
|
|
232
|
+
cloudProvider: 'codex',
|
|
229
233
|
capabilities: { hooks: { since: '0.116.0' }, mcp: true, allowlist: false, skills: true, commands: { until: '0.117.0' }, plugins: { since: '0.128.0' }, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['plan', 'edit', 'skip'] },
|
|
230
234
|
},
|
|
231
235
|
gemini: {
|
|
@@ -412,6 +416,7 @@ export const AGENTS = {
|
|
|
412
416
|
format: 'markdown',
|
|
413
417
|
variableSyntax: '{{args}}',
|
|
414
418
|
supportsHooks: true,
|
|
419
|
+
cloudProvider: 'antigravity',
|
|
415
420
|
capabilities: { hooks: true, mcp: true, allowlist: true, skills: true, commands: true, plugins: true, subagents: false, rules: { file: 'AGENTS.md' }, workflows: false, modes: ['edit', 'skip'], rulesImports: false },
|
|
416
421
|
},
|
|
417
422
|
// xAI Grok Build CLI (`grok`) — early beta, SuperGrok Heavy. Auth via OAuth on
|
|
@@ -509,6 +514,9 @@ export const AGENTS = {
|
|
|
509
514
|
format: 'markdown',
|
|
510
515
|
variableSyntax: '$ARGUMENTS',
|
|
511
516
|
supportsHooks: false,
|
|
517
|
+
// Factory Droid Computers (cloud VMs) reached via `droid computer ssh` +
|
|
518
|
+
// remote headless `droid exec`.
|
|
519
|
+
cloudProvider: 'factory',
|
|
512
520
|
capabilities: {
|
|
513
521
|
hooks: false,
|
|
514
522
|
mcp: true,
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity cloud provider — Google's Antigravity agent via the Gemini
|
|
3
|
+
* **Managed Agents Interactions API**.
|
|
4
|
+
*
|
|
5
|
+
* The `agy` CLI is local-only (`--print` / `--sandbox`); Antigravity's cloud
|
|
6
|
+
* surface is an HTTP endpoint that runs the Antigravity harness in a remote
|
|
7
|
+
* ephemeral Linux sandbox:
|
|
8
|
+
*
|
|
9
|
+
* POST https://generativelanguage.googleapis.com/v1beta/interactions
|
|
10
|
+
* x-goog-api-key: <GEMINI_API_KEY>
|
|
11
|
+
* { "agent": "antigravity-preview-05-2026", "input": "<prompt>", "environment": "remote" }
|
|
12
|
+
*
|
|
13
|
+
* The call is synchronous by default (the response carries `status` +
|
|
14
|
+
* `output_text`), so `dispatch()` awaits it, returns a terminal CloudTask, and
|
|
15
|
+
* `stream()` replays the buffered text + done events — same shape as the Factory
|
|
16
|
+
* provider. It is a raw sandbox: no GitHub repo → PR (that's Rush's job).
|
|
17
|
+
*
|
|
18
|
+
* Auth: the Gemini API key comes from an `agents secrets` bundle named in
|
|
19
|
+
* `cloud.providers.antigravity.secretsBundle` (never from agents.yaml), falling
|
|
20
|
+
* back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
|
|
21
|
+
*/
|
|
22
|
+
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
23
|
+
/** Shape of the Interactions API response we consume (defensive: all optional). */
|
|
24
|
+
interface InteractionResponse {
|
|
25
|
+
id?: string;
|
|
26
|
+
interaction_id?: string;
|
|
27
|
+
environment_id?: string;
|
|
28
|
+
status?: string;
|
|
29
|
+
output_text?: string;
|
|
30
|
+
error?: {
|
|
31
|
+
message?: string;
|
|
32
|
+
} | string;
|
|
33
|
+
}
|
|
34
|
+
/** Map an Interactions API status string to the canonical CloudTaskStatus. */
|
|
35
|
+
export declare function mapStatus(s: string | undefined): CloudTaskStatus;
|
|
36
|
+
/** Build the Interactions API request body for a fresh dispatch. */
|
|
37
|
+
export declare function buildInteractionBody(prompt: string, model: string): Record<string, unknown>;
|
|
38
|
+
/** Parse an Interactions API response into a CloudTask (minus prompt/timestamps). */
|
|
39
|
+
export declare function parseInteraction(resp: InteractionResponse): {
|
|
40
|
+
id: string;
|
|
41
|
+
status: CloudTaskStatus;
|
|
42
|
+
summary?: string;
|
|
43
|
+
environmentId?: string;
|
|
44
|
+
};
|
|
45
|
+
export declare class AntigravityCloudProvider implements CloudProvider {
|
|
46
|
+
id: "antigravity";
|
|
47
|
+
name: string;
|
|
48
|
+
private secretsBundle?;
|
|
49
|
+
private model;
|
|
50
|
+
private runs;
|
|
51
|
+
constructor(config?: {
|
|
52
|
+
secretsBundle?: string;
|
|
53
|
+
model?: string;
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* True when a key *source* is configured. Cheap: never resolves the bundle
|
|
57
|
+
* (which could prompt for biometry) — that happens lazily at dispatch.
|
|
58
|
+
*/
|
|
59
|
+
private hasKeySource;
|
|
60
|
+
/** Resolve the Gemini API key from the configured bundle or the environment. */
|
|
61
|
+
private resolveApiKey;
|
|
62
|
+
capabilities(): ProviderCapabilities;
|
|
63
|
+
dispatch(options: DispatchOptions): Promise<CloudTask>;
|
|
64
|
+
status(taskId: string): Promise<CloudTask>;
|
|
65
|
+
list(): Promise<CloudTask[]>;
|
|
66
|
+
stream(taskId: string): AsyncIterable<CloudEvent>;
|
|
67
|
+
cancel(_taskId: string): Promise<void>;
|
|
68
|
+
message(_taskId: string, _content: string): Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity cloud provider — Google's Antigravity agent via the Gemini
|
|
3
|
+
* **Managed Agents Interactions API**.
|
|
4
|
+
*
|
|
5
|
+
* The `agy` CLI is local-only (`--print` / `--sandbox`); Antigravity's cloud
|
|
6
|
+
* surface is an HTTP endpoint that runs the Antigravity harness in a remote
|
|
7
|
+
* ephemeral Linux sandbox:
|
|
8
|
+
*
|
|
9
|
+
* POST https://generativelanguage.googleapis.com/v1beta/interactions
|
|
10
|
+
* x-goog-api-key: <GEMINI_API_KEY>
|
|
11
|
+
* { "agent": "antigravity-preview-05-2026", "input": "<prompt>", "environment": "remote" }
|
|
12
|
+
*
|
|
13
|
+
* The call is synchronous by default (the response carries `status` +
|
|
14
|
+
* `output_text`), so `dispatch()` awaits it, returns a terminal CloudTask, and
|
|
15
|
+
* `stream()` replays the buffered text + done events — same shape as the Factory
|
|
16
|
+
* provider. It is a raw sandbox: no GitHub repo → PR (that's Rush's job).
|
|
17
|
+
*
|
|
18
|
+
* Auth: the Gemini API key comes from an `agents secrets` bundle named in
|
|
19
|
+
* `cloud.providers.antigravity.secretsBundle` (never from agents.yaml), falling
|
|
20
|
+
* back to GEMINI_API_KEY / GOOGLE_API_KEY in the environment.
|
|
21
|
+
*/
|
|
22
|
+
import { resolveDispatchRepos } from './types.js';
|
|
23
|
+
import { readAndResolveBundleEnv } from '../secrets/bundles.js';
|
|
24
|
+
const INTERACTIONS_URL = 'https://generativelanguage.googleapis.com/v1beta/interactions';
|
|
25
|
+
const DEFAULT_MODEL = 'antigravity-preview-05-2026';
|
|
26
|
+
const KEY_NAMES = ['GEMINI_API_KEY', 'GOOGLE_API_KEY'];
|
|
27
|
+
/** Map an Interactions API status string to the canonical CloudTaskStatus. */
|
|
28
|
+
export function mapStatus(s) {
|
|
29
|
+
const lower = (s ?? '').toLowerCase();
|
|
30
|
+
if (lower.includes('queue') || lower.includes('pending'))
|
|
31
|
+
return 'queued';
|
|
32
|
+
if (lower.includes('run') || lower.includes('progress'))
|
|
33
|
+
return 'running';
|
|
34
|
+
if (lower.includes('complete') || lower.includes('success'))
|
|
35
|
+
return 'completed';
|
|
36
|
+
if (lower.includes('fail') || lower.includes('error'))
|
|
37
|
+
return 'failed';
|
|
38
|
+
if (lower.includes('cancel'))
|
|
39
|
+
return 'cancelled';
|
|
40
|
+
return 'completed';
|
|
41
|
+
}
|
|
42
|
+
/** Build the Interactions API request body for a fresh dispatch. */
|
|
43
|
+
export function buildInteractionBody(prompt, model) {
|
|
44
|
+
return {
|
|
45
|
+
agent: model,
|
|
46
|
+
input: prompt,
|
|
47
|
+
environment: 'remote',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Parse an Interactions API response into a CloudTask (minus prompt/timestamps). */
|
|
51
|
+
export function parseInteraction(resp) {
|
|
52
|
+
const id = resp.id ?? resp.interaction_id ?? `antigravity-${Date.now()}`;
|
|
53
|
+
return {
|
|
54
|
+
id,
|
|
55
|
+
status: mapStatus(resp.status),
|
|
56
|
+
summary: resp.output_text,
|
|
57
|
+
environmentId: resp.environment_id,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export class AntigravityCloudProvider {
|
|
61
|
+
id = 'antigravity';
|
|
62
|
+
name = 'Antigravity (Gemini)';
|
|
63
|
+
secretsBundle;
|
|
64
|
+
model;
|
|
65
|
+
runs = new Map();
|
|
66
|
+
constructor(config) {
|
|
67
|
+
this.secretsBundle = config?.secretsBundle;
|
|
68
|
+
this.model = config?.model ?? DEFAULT_MODEL;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* True when a key *source* is configured. Cheap: never resolves the bundle
|
|
72
|
+
* (which could prompt for biometry) — that happens lazily at dispatch.
|
|
73
|
+
*/
|
|
74
|
+
hasKeySource() {
|
|
75
|
+
if (this.secretsBundle)
|
|
76
|
+
return true;
|
|
77
|
+
return KEY_NAMES.some((k) => Boolean(process.env[k]));
|
|
78
|
+
}
|
|
79
|
+
/** Resolve the Gemini API key from the configured bundle or the environment. */
|
|
80
|
+
resolveApiKey() {
|
|
81
|
+
if (this.secretsBundle) {
|
|
82
|
+
try {
|
|
83
|
+
const { env } = readAndResolveBundleEnv(this.secretsBundle, { caller: 'cloud:antigravity' });
|
|
84
|
+
for (const k of KEY_NAMES) {
|
|
85
|
+
if (env[k])
|
|
86
|
+
return env[k];
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Secrets bundle '${this.secretsBundle}' has no ${KEY_NAMES.join(' or ')}. Add one: agents secrets add ${this.secretsBundle} GEMINI_API_KEY`);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
throw new Error(`Could not read Gemini API key from bundle '${this.secretsBundle}': ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const k of KEY_NAMES) {
|
|
95
|
+
const v = process.env[k];
|
|
96
|
+
if (v)
|
|
97
|
+
return v;
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Antigravity cloud needs a Gemini API key. Set cloud.providers.antigravity.secretsBundle in ~/.agents/agents.yaml, or export ${KEY_NAMES.join(' / ')}.`);
|
|
100
|
+
}
|
|
101
|
+
capabilities() {
|
|
102
|
+
const available = this.hasKeySource();
|
|
103
|
+
return {
|
|
104
|
+
available,
|
|
105
|
+
dispatch: available,
|
|
106
|
+
status: available,
|
|
107
|
+
list: available,
|
|
108
|
+
stream: available,
|
|
109
|
+
cancel: false,
|
|
110
|
+
message: false,
|
|
111
|
+
multiRepo: false,
|
|
112
|
+
skills: false,
|
|
113
|
+
images: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
async dispatch(options) {
|
|
117
|
+
// The Interactions sandbox has no GitHub repo → PR flow. Reject repos
|
|
118
|
+
// loudly rather than silently ignoring them (point the user at Rush).
|
|
119
|
+
const repos = resolveDispatchRepos(options);
|
|
120
|
+
if (repos.length > 0) {
|
|
121
|
+
throw new Error(`Antigravity cloud is a raw sandbox with no GitHub repo → PR flow. Got repo(s): ${repos.join(', ')}. ` +
|
|
122
|
+
`Use --provider rush for repo-backed dispatch.`);
|
|
123
|
+
}
|
|
124
|
+
const apiKey = this.resolveApiKey();
|
|
125
|
+
const model = options.model ?? this.model;
|
|
126
|
+
const body = buildInteractionBody(options.prompt, model);
|
|
127
|
+
let resp;
|
|
128
|
+
try {
|
|
129
|
+
resp = await fetch(INTERACTIONS_URL, {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: { 'content-type': 'application/json', 'x-goog-api-key': apiKey },
|
|
132
|
+
body: JSON.stringify(body),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
throw new Error(`Antigravity dispatch failed (network): ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
const text = await resp.text();
|
|
139
|
+
if (!resp.ok) {
|
|
140
|
+
throw new Error(`Antigravity dispatch failed (${resp.status}): ${text.slice(0, 500)}`);
|
|
141
|
+
}
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(text);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
throw new Error(`Antigravity returned non-JSON response: ${text.slice(0, 300)}`);
|
|
148
|
+
}
|
|
149
|
+
const { id, status, summary } = parseInteraction(parsed);
|
|
150
|
+
const now = new Date().toISOString();
|
|
151
|
+
const task = {
|
|
152
|
+
id,
|
|
153
|
+
provider: 'antigravity',
|
|
154
|
+
status,
|
|
155
|
+
agent: 'antigravity',
|
|
156
|
+
prompt: options.prompt,
|
|
157
|
+
summary,
|
|
158
|
+
createdAt: now,
|
|
159
|
+
updatedAt: now,
|
|
160
|
+
};
|
|
161
|
+
const events = [];
|
|
162
|
+
if (summary)
|
|
163
|
+
events.push({ type: 'text', content: summary, timestamp: now });
|
|
164
|
+
events.push({ type: 'done', status, summary, timestamp: now });
|
|
165
|
+
this.runs.set(id, { events, task });
|
|
166
|
+
return task;
|
|
167
|
+
}
|
|
168
|
+
async status(taskId) {
|
|
169
|
+
const run = this.runs.get(taskId);
|
|
170
|
+
if (run)
|
|
171
|
+
return run.task;
|
|
172
|
+
throw new Error(`No live status for Antigravity task ${taskId} (synchronous interaction; see local cache).`);
|
|
173
|
+
}
|
|
174
|
+
async list() {
|
|
175
|
+
return [...this.runs.values()].map((r) => r.task);
|
|
176
|
+
}
|
|
177
|
+
async *stream(taskId) {
|
|
178
|
+
const run = this.runs.get(taskId);
|
|
179
|
+
if (!run) {
|
|
180
|
+
yield {
|
|
181
|
+
type: 'error',
|
|
182
|
+
message: `Antigravity interaction ${taskId} is not buffered in this process. The interaction is synchronous — see 'agents cloud status ${taskId}' for the result.`,
|
|
183
|
+
timestamp: new Date().toISOString(),
|
|
184
|
+
};
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const event of run.events)
|
|
188
|
+
yield event;
|
|
189
|
+
}
|
|
190
|
+
async cancel(_taskId) {
|
|
191
|
+
throw new Error('Cancel is not supported for Antigravity — interactions run synchronously to completion.');
|
|
192
|
+
}
|
|
193
|
+
async message(_taskId, _content) {
|
|
194
|
+
throw new Error('Follow-up messages are not yet supported for Antigravity cloud tasks.');
|
|
195
|
+
}
|
|
196
|
+
}
|
|
@@ -9,6 +9,7 @@ import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOpt
|
|
|
9
9
|
export declare class CodexCloudProvider implements CloudProvider {
|
|
10
10
|
id: "codex";
|
|
11
11
|
name: string;
|
|
12
|
+
targetKind: "env";
|
|
12
13
|
private defaultEnv?;
|
|
13
14
|
constructor(config?: {
|
|
14
15
|
env?: string;
|
package/dist/lib/cloud/codex.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { spawn, execFileSync } from 'child_process';
|
|
9
9
|
import * as fs from 'fs';
|
|
10
10
|
import * as path from 'path';
|
|
11
|
-
import { resolveDispatchRepos } from './types.js';
|
|
11
|
+
import { resolveDispatchRepos, MissingTargetError } from './types.js';
|
|
12
12
|
import { getShimsDir } from '../state.js';
|
|
13
13
|
const SHIMS_DIR = getShimsDir();
|
|
14
14
|
/** Map a Codex Cloud status string to the canonical CloudTaskStatus enum. */
|
|
@@ -85,6 +85,10 @@ function parseTaskFromText(text) {
|
|
|
85
85
|
export class CodexCloudProvider {
|
|
86
86
|
id = 'codex';
|
|
87
87
|
name = 'Codex Cloud';
|
|
88
|
+
targetKind = 'env';
|
|
89
|
+
// No listTargets: OpenAI ships no non-interactive "list environments"
|
|
90
|
+
// command. `codex cloud exec` requires --env and the help points at the
|
|
91
|
+
// interactive `codex cloud` TUI to browse. So discovery is guidance-only.
|
|
88
92
|
defaultEnv;
|
|
89
93
|
constructor(config) {
|
|
90
94
|
this.defaultEnv = config?.env;
|
|
@@ -107,7 +111,9 @@ export class CodexCloudProvider {
|
|
|
107
111
|
async dispatch(options) {
|
|
108
112
|
const env = options.providerOptions?.env ?? this.defaultEnv;
|
|
109
113
|
if (!env) {
|
|
110
|
-
throw new
|
|
114
|
+
throw new MissingTargetError('env', 'Codex Cloud requires --env <id>.', 'Codex environments are created in the Codex web UI and bundle a repo + setup. ' +
|
|
115
|
+
'Browse yours with `codex cloud` (interactive), then re-run with --env <id> ' +
|
|
116
|
+
'or set a default in ~/.agents/agents.yaml under cloud.providers.codex.env.');
|
|
111
117
|
}
|
|
112
118
|
// Codex envs bundle their own repo list — the repos a task can touch are
|
|
113
119
|
// fixed at env-creation time, not per-dispatch. Passing 2+ repos here is
|
|
@@ -1,31 +1,92 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Factory
|
|
2
|
+
* Factory (Droid) cloud provider.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Dispatches to a Factory **Droid Computer** — a persistent cloud VM (managed
|
|
5
|
+
* by Factory, or bring-your-own via `droid computer register`). There is no
|
|
6
|
+
* `droid cloud run`; remote execution = reach the computer over the Droid relay
|
|
7
|
+
* (`droid computer ssh <name>`) and run a headless `droid exec` there.
|
|
8
|
+
*
|
|
9
|
+
* `droid exec` is synchronous (it runs to completion and exits, unlike Codex
|
|
10
|
+
* Cloud which is async server-side). So `dispatch()` runs the remote exec to
|
|
11
|
+
* completion with `--output-format stream-json`, buffers the NDJSON events, and
|
|
12
|
+
* `stream()` replays them. The task id is droid's own `session_id` (captured
|
|
13
|
+
* from the run output), so it lines up with `droid exec -s <id>` for future
|
|
14
|
+
* resume support.
|
|
15
|
+
*
|
|
16
|
+
* Transport note: the exact relay SSH composition (user + ProxyCommand) is
|
|
17
|
+
* built in `buildSshArgs()` and must be confirmed against a live provisioned
|
|
18
|
+
* Droid Computer (Factory auth required). `capabilities().available` gates on
|
|
19
|
+
* the droid binary + a configured computer so the provider fails with a clear
|
|
20
|
+
* message rather than misfiring when unconfigured.
|
|
6
21
|
*/
|
|
7
|
-
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, DispatchOptions, ProviderCapabilities } from './types.js';
|
|
22
|
+
import type { CloudProvider, CloudTask, CloudTaskStatus, CloudEvent, CloudTarget, DispatchOptions, ProviderCapabilities, DroidAutonomy } from './types.js';
|
|
23
|
+
/** Locate the droid binary, checking agents-cli shims first then PATH. */
|
|
24
|
+
export declare function findDroidBinary(): string | null;
|
|
25
|
+
/** Normalize an autonomy value, falling back to the safe cloud default (`high`). */
|
|
26
|
+
export declare function resolveAutonomy(value: unknown, fallback?: DroidAutonomy): DroidAutonomy;
|
|
8
27
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
28
|
+
* Parse `droid computer list` text into targets. Defensive: the exact column
|
|
29
|
+
* layout isn't documented, so we take the first whitespace token of each data
|
|
30
|
+
* row as the computer name and keep the remainder as a label, skipping headers,
|
|
31
|
+
* separators, and status messages. The interactive picker degrades to free-text
|
|
32
|
+
* entry if this yields nothing, so an unexpected layout never blocks a dispatch.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseComputerList(text: string): CloudTarget[];
|
|
35
|
+
/**
|
|
36
|
+
* Build the remote `droid exec` argv. Headless, stream-json output, given
|
|
37
|
+
* autonomy. `sessionId` (when resuming) maps to `-s`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildExecArgs(prompt: string, opts: {
|
|
40
|
+
autonomy: DroidAutonomy;
|
|
41
|
+
sessionId?: string;
|
|
42
|
+
}): string[];
|
|
43
|
+
/**
|
|
44
|
+
* Build the `ssh` argv that runs a remote command on a Droid Computer through
|
|
45
|
+
* the Droid relay. The relay is used as an OpenSSH ProxyCommand
|
|
46
|
+
* (`droid computer ssh <name> --proxy`), so the connection rides Factory's
|
|
47
|
+
* brokered tunnel rather than a directly reachable host.
|
|
13
48
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
|
|
17
|
-
|
|
49
|
+
* `remoteArgv` is the already-built remote command (e.g. droid exec argv); it is
|
|
50
|
+
* shell-quoted into a single remote command string.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildSshArgs(computer: string, remoteBin: string, remoteArgv: string[], opts: {
|
|
53
|
+
droidBin: string;
|
|
54
|
+
user: string;
|
|
55
|
+
port?: string;
|
|
56
|
+
}): string[];
|
|
57
|
+
/** Map a droid stream-json `result.subtype` / `is_error` to a CloudTaskStatus. */
|
|
58
|
+
export declare function mapResultStatus(line: {
|
|
59
|
+
is_error?: boolean;
|
|
60
|
+
subtype?: string;
|
|
61
|
+
}): CloudTaskStatus;
|
|
62
|
+
/**
|
|
63
|
+
* Map one parsed droid stream-json event to a CloudEvent. The stream-json
|
|
64
|
+
* schema is only partially documented, so this is defensive: known shapes map
|
|
65
|
+
* to typed events, everything else surfaces as `unknown` rather than being
|
|
66
|
+
* dropped (mirrors the rest of the cloud event pipeline).
|
|
18
67
|
*/
|
|
68
|
+
export declare function mapDroidEvent(obj: Record<string, unknown>): CloudEvent;
|
|
19
69
|
export declare class FactoryCloudProvider implements CloudProvider {
|
|
20
70
|
id: "factory";
|
|
21
71
|
name: string;
|
|
72
|
+
targetKind: "computer";
|
|
73
|
+
private defaultComputer?;
|
|
74
|
+
private defaultAutonomy;
|
|
75
|
+
/** session_id → buffered run, populated by dispatch, drained by stream. */
|
|
76
|
+
private runs;
|
|
77
|
+
constructor(config?: {
|
|
78
|
+
computer?: string;
|
|
79
|
+
autonomy?: DroidAutonomy;
|
|
80
|
+
});
|
|
22
81
|
capabilities(): ProviderCapabilities;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
82
|
+
/** Enumerate Droid Computers via `droid computer list`. Throws if not signed in. */
|
|
83
|
+
listTargets(): Promise<CloudTarget[]>;
|
|
84
|
+
dispatch(options: DispatchOptions): Promise<CloudTask>;
|
|
85
|
+
/** Run the remote droid exec to completion, collecting events + final result. */
|
|
86
|
+
private runRemote;
|
|
87
|
+
status(taskId: string): Promise<CloudTask>;
|
|
88
|
+
list(): Promise<CloudTask[]>;
|
|
89
|
+
stream(taskId: string): AsyncIterable<CloudEvent>;
|
|
29
90
|
cancel(_taskId: string): Promise<void>;
|
|
30
91
|
message(_taskId: string, _content: string): Promise<void>;
|
|
31
92
|
}
|