@phnx-labs/agents-cli 1.20.11 → 1.20.13

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.
Files changed (61) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +3 -0
  3. package/dist/commands/computer-actions.d.ts +3 -0
  4. package/dist/commands/computer-actions.js +16 -0
  5. package/dist/commands/exec.js +25 -4
  6. package/dist/commands/import.js +17 -6
  7. package/dist/commands/inspect.d.ts +11 -1
  8. package/dist/commands/inspect.js +53 -19
  9. package/dist/commands/mcp.js +3 -3
  10. package/dist/commands/plugins.d.ts +2 -0
  11. package/dist/commands/plugins.js +69 -26
  12. package/dist/commands/sync.js +1 -1
  13. package/dist/commands/teams.js +1 -0
  14. package/dist/commands/trash.d.ts +11 -0
  15. package/dist/commands/trash.js +57 -41
  16. package/dist/commands/versions.js +68 -20
  17. package/dist/commands/view.js +1 -12
  18. package/dist/commands/wallet.d.ts +14 -0
  19. package/dist/commands/wallet.js +199 -0
  20. package/dist/index.js +22 -1
  21. package/dist/lib/agents.js +70 -22
  22. package/dist/lib/browser/ipc.d.ts +7 -0
  23. package/dist/lib/browser/ipc.js +43 -27
  24. package/dist/lib/capabilities.js +7 -1
  25. package/dist/lib/command-skills.d.ts +1 -0
  26. package/dist/lib/command-skills.js +23 -7
  27. package/dist/lib/exec.d.ts +32 -1
  28. package/dist/lib/exec.js +79 -7
  29. package/dist/lib/hooks.js +37 -5
  30. package/dist/lib/mcp.js +33 -0
  31. package/dist/lib/models.js +5 -0
  32. package/dist/lib/picker.d.ts +2 -0
  33. package/dist/lib/picker.js +96 -6
  34. package/dist/lib/platform/index.d.ts +1 -0
  35. package/dist/lib/platform/index.js +1 -0
  36. package/dist/lib/platform/winpath.d.ts +35 -0
  37. package/dist/lib/platform/winpath.js +86 -0
  38. package/dist/lib/plugins.d.ts +14 -0
  39. package/dist/lib/plugins.js +23 -0
  40. package/dist/lib/project-launch.js +110 -5
  41. package/dist/lib/registry.js +15 -2
  42. package/dist/lib/runner.js +14 -0
  43. package/dist/lib/sandbox.js +5 -2
  44. package/dist/lib/settings-manifest.d.ts +39 -0
  45. package/dist/lib/settings-manifest.js +163 -0
  46. package/dist/lib/shims.d.ts +1 -1
  47. package/dist/lib/shims.js +16 -31
  48. package/dist/lib/staleness/detectors/subagents.js +16 -0
  49. package/dist/lib/staleness/writers/subagents.js +11 -3
  50. package/dist/lib/subagents.d.ts +9 -0
  51. package/dist/lib/subagents.js +33 -0
  52. package/dist/lib/teams/agents.js +1 -1
  53. package/dist/lib/teams/parsers.d.ts +1 -1
  54. package/dist/lib/teams/parsers.js +6 -0
  55. package/dist/lib/types.d.ts +1 -1
  56. package/dist/lib/versions.d.ts +15 -3
  57. package/dist/lib/versions.js +88 -19
  58. package/dist/lib/wallet/index.d.ts +78 -0
  59. package/dist/lib/wallet/index.js +253 -0
  60. package/package.json +3 -3
  61. package/scripts/postinstall.js +35 -7
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Settings carry-forward between version homes.
3
+ *
4
+ * Every installed version gets an isolated `home/`, so user-authored
5
+ * preferences (settings.json, config.toml, keybindings, auth) written while
6
+ * running one version do not exist in a freshly installed one. Resources
7
+ * managed in ~/.agents/ (commands, skills, hooks, rules, MCP YAML, plugins,
8
+ * subagents) are synced into every version home by syncResourcesToVersion and
9
+ * are deliberately NOT listed here — copying them would fight that sync.
10
+ *
11
+ * The manifest below classifies the remaining per-agent files, and
12
+ * carryForwardSettings() fills gaps in a target version home from a source
13
+ * version home. It never overwrites a value the target already has: scalars
14
+ * keep the target's value, objects merge recursively, arrays union. That makes
15
+ * the operation idempotent and safe to run on every `agents add` / `agents use`.
16
+ */
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+ import * as TOML from 'smol-toml';
20
+ import { getBackupsDir } from './state.js';
21
+ const SETTINGS_MANIFEST = {
22
+ claude: [
23
+ { rel: '.claude/settings.json', strategy: 'json-merge' },
24
+ { rel: '.claude/settings.local.json', strategy: 'copy-if-absent' },
25
+ { rel: '.claude/keybindings.json', strategy: 'copy-if-absent' },
26
+ ],
27
+ codex: [
28
+ {
29
+ rel: '.codex/config.toml',
30
+ strategy: 'toml-merge',
31
+ stateKeys: ['notice', 'windows_wsl_setup_acknowledged'],
32
+ },
33
+ { rel: '.codex/auth.json', strategy: 'copy-if-absent', restrictMode: true },
34
+ { rel: '.codex/instructions.md', strategy: 'copy-if-absent' },
35
+ { rel: '.codex/hooks.json', strategy: 'copy-if-absent' },
36
+ { rel: '.codex/prompts', strategy: 'dir-entries' },
37
+ { rel: '.codex/rules', strategy: 'dir-entries' },
38
+ ],
39
+ };
40
+ function isPlainObject(value) {
41
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
42
+ }
43
+ /**
44
+ * Fill gaps in `target` from `source` without overwriting target values:
45
+ * missing keys are copied, plain objects recurse, and everything else —
46
+ * scalars AND arrays — keeps the target's value. Arrays deliberately do not
47
+ * union: other writers (factory sync, hooks registration) mutate array entries
48
+ * in place, so a union would keep re-appending stale pre-mutation copies from
49
+ * the source on every carry (e.g. a user hook duplicated after the system
50
+ * hooks were merged into it). Returns a new object.
51
+ */
52
+ export function fillGaps(target, source) {
53
+ const out = { ...target };
54
+ for (const [key, sourceValue] of Object.entries(source)) {
55
+ if (!(key in out)) {
56
+ out[key] = sourceValue;
57
+ continue;
58
+ }
59
+ const targetValue = out[key];
60
+ if (isPlainObject(targetValue) && isPlainObject(sourceValue)) {
61
+ out[key] = fillGaps(targetValue, sourceValue);
62
+ }
63
+ // scalar, array, or type mismatch: target wins
64
+ }
65
+ return out;
66
+ }
67
+ function stripStateKeys(obj, stateKeys) {
68
+ if (!stateKeys?.length)
69
+ return obj;
70
+ const out = { ...obj };
71
+ for (const key of stateKeys)
72
+ delete out[key];
73
+ return out;
74
+ }
75
+ function backupFile(backupRoot, home, rel) {
76
+ const src = path.join(home, rel);
77
+ const dest = path.join(backupRoot, rel);
78
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
79
+ fs.copyFileSync(src, dest);
80
+ }
81
+ /**
82
+ * Carry user settings forward from one version home into another. Both paths
83
+ * are version-home roots (the directory containing `.claude/` / `.codex/`).
84
+ * Only fills gaps — never overwrites target values — so it is idempotent.
85
+ */
86
+ export function carryForwardSettings(agent, fromHome, toHome) {
87
+ const manifest = SETTINGS_MANIFEST[agent];
88
+ const result = { applied: [] };
89
+ if (!manifest || !fs.existsSync(fromHome) || fromHome === toHome)
90
+ return result;
91
+ const backupRoot = path.join(getBackupsDir(), 'settings-carry', agent, new Date().toISOString().replace(/[:.]/g, '-'));
92
+ for (const entry of manifest) {
93
+ const sourcePath = path.join(fromHome, entry.rel);
94
+ const targetPath = path.join(toHome, entry.rel);
95
+ if (!fs.existsSync(sourcePath))
96
+ continue;
97
+ try {
98
+ switch (entry.strategy) {
99
+ case 'copy-if-absent': {
100
+ if (fs.existsSync(targetPath))
101
+ break;
102
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
103
+ fs.copyFileSync(sourcePath, targetPath);
104
+ if (entry.restrictMode)
105
+ fs.chmodSync(targetPath, 0o600);
106
+ result.applied.push(entry.rel);
107
+ break;
108
+ }
109
+ case 'dir-entries': {
110
+ if (!fs.statSync(sourcePath).isDirectory())
111
+ break;
112
+ let copied = false;
113
+ fs.mkdirSync(targetPath, { recursive: true });
114
+ for (const name of fs.readdirSync(sourcePath)) {
115
+ const childTarget = path.join(targetPath, name);
116
+ if (fs.existsSync(childTarget))
117
+ continue;
118
+ fs.cpSync(path.join(sourcePath, name), childTarget, { recursive: true });
119
+ copied = true;
120
+ }
121
+ if (copied)
122
+ result.applied.push(entry.rel);
123
+ break;
124
+ }
125
+ case 'json-merge':
126
+ case 'toml-merge': {
127
+ const parse = entry.strategy === 'json-merge'
128
+ ? (text) => JSON.parse(text)
129
+ : (text) => TOML.parse(text);
130
+ const stringify = entry.strategy === 'json-merge'
131
+ ? (obj) => JSON.stringify(obj, null, 2) + '\n'
132
+ : (obj) => TOML.stringify(obj) + '\n';
133
+ const source = stripStateKeys(parse(fs.readFileSync(sourcePath, 'utf-8')), entry.stateKeys);
134
+ if (!fs.existsSync(targetPath)) {
135
+ if (Object.keys(source).length === 0)
136
+ break;
137
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
138
+ fs.writeFileSync(targetPath, stringify(source), 'utf-8');
139
+ result.applied.push(entry.rel);
140
+ break;
141
+ }
142
+ const targetText = fs.readFileSync(targetPath, 'utf-8');
143
+ const targetObj = parse(targetText);
144
+ const merged = fillGaps(targetObj, source);
145
+ // Compare parsed content, not text: other writers format differently,
146
+ // and a semantic no-op must not trigger a rewrite/backup every switch.
147
+ if (JSON.stringify(merged) === JSON.stringify(targetObj))
148
+ break;
149
+ backupFile(backupRoot, toHome, entry.rel);
150
+ result.backupDir = backupRoot;
151
+ fs.writeFileSync(targetPath, stringify(merged), 'utf-8');
152
+ result.applied.push(entry.rel);
153
+ break;
154
+ }
155
+ }
156
+ }
157
+ catch {
158
+ // A malformed source or target file must not break install/use.
159
+ // Leave the target untouched for this entry and move on.
160
+ }
161
+ }
162
+ return result;
163
+ }
@@ -77,7 +77,7 @@ export interface ConflictInfo {
77
77
  * top-level entry add/remove — deep edits to plugin contents won't
78
78
  * trigger auto-resync, run `agents sync` for that.
79
79
  */
80
- export declare const SHIM_SCHEMA_VERSION = 17;
80
+ export declare const SHIM_SCHEMA_VERSION = 18;
81
81
  /**
82
82
  * Generate the full bash shim script for the given agent. The returned string
83
83
  * is written to ~/.agents/shims/{cliCommand} and made executable.
package/dist/lib/shims.js CHANGED
@@ -11,10 +11,9 @@
11
11
  import * as fs from 'fs';
12
12
  import * as path from 'path';
13
13
  import * as os from 'os';
14
- import { execFileSync } from 'child_process';
15
14
  import { fileURLToPath } from 'url';
16
15
  import { confirm, select } from '@inquirer/prompts';
17
- import { IS_WINDOWS } from './platform/index.js';
16
+ import { IS_WINDOWS, prependToWindowsUserPath } from './platform/index.js';
18
17
  import { getShimsDir, getVersionsDir, getBackupsDir, ensureAgentsDir } from './state.js';
19
18
  export { getShimsDir };
20
19
  import { AGENTS } from './agents.js';
@@ -203,7 +202,7 @@ async function promptConflictStrategy(conflictInfos) {
203
202
  * top-level entry add/remove — deep edits to plugin contents won't
204
203
  * trigger auto-resync, run `agents sync` for that.
205
204
  */
206
- export const SHIM_SCHEMA_VERSION = 17;
205
+ export const SHIM_SCHEMA_VERSION = 18;
207
206
  /** Internal marker string used to embed the schema version in shim scripts. */
208
207
  const SHIM_VERSION_MARKER = 'agents-shim-version:';
209
208
  function shellQuote(value) {
@@ -273,7 +272,7 @@ export KIMI_CODE_HOME="$VERSION_DIR/home/${configDirName}"
273
272
  # Shim for ${agentConfig.name}
274
273
  # ${SHIM_VERSION_MARKER} ${SHIM_SCHEMA_VERSION}
275
274
 
276
- AGENTS_USER_DIR="$HOME/.agents"
275
+ AGENTS_USER_DIR="\${AGENTS_USER_DIR:-$HOME/.agents}"
277
276
  AGENTS_BIN=${agentsBin}
278
277
  AGENT="${agent}"
279
278
  CLI_COMMAND="${cliCommand}"
@@ -1579,36 +1578,22 @@ export function addShimsToPath(overrides) {
1579
1578
  * Register the shims dir on the Windows User PATH via the .NET environment API,
1580
1579
  * which writes the registry AND broadcasts WM_SETTINGCHANGE — the correct analog
1581
1580
  * of editing a shell rc file (no `setx` truncation, no manual step). Idempotent:
1582
- * a no-op when the dir is already present. The shims dir is passed via an env var
1583
- * so it is never interpolated into the PowerShell script text.
1581
+ * a no-op when the shims dir is already first in the User PATH. Moves it to the
1582
+ * front when it exists but is in the wrong position (e.g. appended by an old
1583
+ * install) so it overrides any npm/global installs that appear later. The shims
1584
+ * dir is passed via an env var so it is never interpolated into the script text.
1584
1585
  */
1585
1586
  function addShimsToWindowsUserPath(shimsDir) {
1586
- const script = [
1587
- '$d = $env:AGENTS_SHIMS_DIR',
1588
- "$u = [Environment]::GetEnvironmentVariable('Path','User')",
1589
- "if ($null -eq $u) { $u = '' }",
1590
- "$parts = @($u -split ';' | Where-Object { $_ -ne '' })",
1591
- "if ($parts -contains $d) { 'present' } else {",
1592
- " [Environment]::SetEnvironmentVariable('Path', (($parts + $d) -join ';'), 'User')",
1593
- " 'added'",
1594
- '}',
1595
- ].join('\n');
1596
- try {
1597
- const out = execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', script], {
1598
- encoding: 'utf-8',
1599
- env: { ...process.env, AGENTS_SHIMS_DIR: shimsDir },
1600
- stdio: ['ignore', 'pipe', 'pipe'],
1601
- }).trim();
1602
- return {
1603
- success: true,
1604
- alreadyPresent: out.includes('present'),
1605
- location: 'your user PATH',
1606
- reloadHint: 'Open a new terminal for the change to take effect.',
1607
- };
1608
- }
1609
- catch (err) {
1610
- return { success: false, error: `Could not update the Windows user PATH: ${err.message}` };
1587
+ const r = prependToWindowsUserPath(shimsDir);
1588
+ if (!r.success) {
1589
+ return { success: false, error: r.error };
1611
1590
  }
1591
+ return {
1592
+ success: true,
1593
+ alreadyPresent: r.alreadyPresent,
1594
+ location: 'your user PATH',
1595
+ reloadHint: 'Open a new terminal for the change to take effect.',
1596
+ };
1612
1597
  }
1613
1598
  export function listAgentsWithInstalledVersions() {
1614
1599
  const versionsDir = getVersionsDir();
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Subagents detector. Claude: flat .md files under `<agentDir>/agents/`.
3
+ * Droid: flat .md files under `<versionHome>/.factory/droids/`.
3
4
  * OpenClaw: subdirectories containing AGENTS.md under `<versionHome>/.openclaw/`.
4
5
  * Mirrors versions.ts:521-539.
5
6
  */
@@ -21,6 +22,20 @@ function buildClaudeDetector() {
21
22
  },
22
23
  };
23
24
  }
25
+ function buildDroidDetector() {
26
+ return {
27
+ kind: 'subagents',
28
+ agent: 'droid',
29
+ list({ versionHome }) {
30
+ const droidsDir = path.join(versionHome, '.factory', 'droids');
31
+ if (!fs.existsSync(droidsDir))
32
+ return [];
33
+ return fs.readdirSync(droidsDir)
34
+ .filter(f => f.endsWith('.md'))
35
+ .map(f => f.replace('.md', ''));
36
+ },
37
+ };
38
+ }
24
39
  function buildOpenclawDetector() {
25
40
  return {
26
41
  kind: 'subagents',
@@ -37,6 +52,7 @@ function buildOpenclawDetector() {
37
52
  }
38
53
  const handlers = {
39
54
  claude: buildClaudeDetector,
55
+ droid: buildDroidDetector,
40
56
  openclaw: buildOpenclawDetector,
41
57
  };
42
58
  export const subagentsDetectors = lazyAgentMap(() => {
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * Subagents writer. Claude flattens each subagent into a single .md file
3
- * under `<agentDir>/agents/`. OpenClaw copies the full subagent directory
4
- * (with AGENT.md renamed to AGENTS.md) into `<versionHome>/.openclaw/<name>/`.
3
+ * under `<agentDir>/agents/`. Droid (Factory AI) flattens each into a custom
4
+ * droid .md under `<versionHome>/.factory/droids/`. OpenClaw copies the full
5
+ * subagent directory (with AGENT.md renamed to AGENTS.md) into
6
+ * `<versionHome>/.openclaw/<name>/`.
5
7
  *
6
8
  * Source-side discovery is `listInstalledSubagents` from lib/subagents.ts —
7
9
  * it reads user + system layers only (project layer excluded for the same
@@ -10,7 +12,7 @@
10
12
  import * as fs from 'fs';
11
13
  import * as path from 'path';
12
14
  import { capableAgents } from '../../capabilities.js';
13
- import { listInstalledSubagents, transformSubagentForClaude, syncSubagentToOpenclaw } from '../../subagents.js';
15
+ import { listInstalledSubagents, transformSubagentForClaude, transformSubagentForDroid, syncSubagentToOpenclaw } from '../../subagents.js';
14
16
  import { safeJoin } from '../../paths.js';
15
17
  import { lazyAgentMap } from './lazy-map.js';
16
18
  function buildSubagentsWriter(agent) {
@@ -32,6 +34,12 @@ function buildSubagentsWriter(agent) {
32
34
  fs.writeFileSync(safeJoin(agentsDir, `${sub.name}.md`), transformSubagentForClaude(sub.path));
33
35
  synced.push(sub.name);
34
36
  }
37
+ else if (agent === 'droid') {
38
+ const droidsDir = path.join(versionHome, '.factory', 'droids');
39
+ fs.mkdirSync(droidsDir, { recursive: true });
40
+ fs.writeFileSync(safeJoin(droidsDir, `${sub.name}.md`), transformSubagentForDroid(sub.path));
41
+ synced.push(sub.name);
42
+ }
35
43
  else if (agent === 'openclaw') {
36
44
  const target = safeJoin(path.join(versionHome, '.openclaw'), sub.name);
37
45
  const r = syncSubagentToOpenclaw(sub.path, target);
@@ -47,6 +47,15 @@ export declare function removeSubagent(name: string): {
47
47
  * Combines AGENT.md frontmatter + body with other files as sections
48
48
  */
49
49
  export declare function transformSubagentForClaude(subagentDir: string): string;
50
+ /**
51
+ * Transform a subagent into a Factory AI Droid "custom droid" .md file.
52
+ *
53
+ * Mirrors transformSubagentForClaude (flatten frontmatter + body + appended
54
+ * .md sections), but emits only frontmatter keys Factory recognizes
55
+ * (name, description, model). Factory has no `color` field, so it is dropped.
56
+ * See https://docs.factory.ai/cli/configuration/custom-droids.
57
+ */
58
+ export declare function transformSubagentForDroid(subagentDir: string): string;
50
59
  /**
51
60
  * Sync a subagent to an OpenClaw workspace
52
61
  * Copies full directory, renames AGENT.md to AGENTS.md
@@ -229,6 +229,39 @@ export function transformSubagentForClaude(subagentDir) {
229
229
  }
230
230
  return result;
231
231
  }
232
+ /**
233
+ * Transform a subagent into a Factory AI Droid "custom droid" .md file.
234
+ *
235
+ * Mirrors transformSubagentForClaude (flatten frontmatter + body + appended
236
+ * .md sections), but emits only frontmatter keys Factory recognizes
237
+ * (name, description, model). Factory has no `color` field, so it is dropped.
238
+ * See https://docs.factory.ai/cli/configuration/custom-droids.
239
+ */
240
+ export function transformSubagentForDroid(subagentDir) {
241
+ const agentMd = path.join(subagentDir, 'AGENT.md');
242
+ const frontmatter = parseSubagentFrontmatter(agentMd);
243
+ const body = getSubagentBody(agentMd);
244
+ if (!frontmatter) {
245
+ throw new Error(`Invalid AGENT.md in ${subagentDir}`);
246
+ }
247
+ const frontmatterYaml = yaml.stringify({
248
+ name: frontmatter.name,
249
+ description: frontmatter.description,
250
+ ...(frontmatter.model && { model: frontmatter.model }),
251
+ }).trim();
252
+ let result = `---\n${frontmatterYaml}\n---\n\n${body}`;
253
+ const files = fs.readdirSync(subagentDir)
254
+ .filter(f => f.endsWith('.md') && f !== 'AGENT.md')
255
+ .sort();
256
+ for (const file of files) {
257
+ const filePath = path.join(subagentDir, file);
258
+ const content = fs.readFileSync(filePath, 'utf-8').trim();
259
+ const sectionName = file.replace('.md', '');
260
+ const title = sectionName.charAt(0).toUpperCase() + sectionName.slice(1).toLowerCase();
261
+ result += `\n\n## ${title}\n\n${content}`;
262
+ }
263
+ return result;
264
+ }
232
265
  /**
233
266
  * Sync a subagent to an OpenClaw workspace
234
267
  * Copies full directory, renames AGENT.md to AGENTS.md
@@ -173,7 +173,7 @@ export function captureProcessStartTime(pid) {
173
173
  }
174
174
  }
175
175
  /** Agent types the team runner supports. */
176
- const TEAM_AGENT_TYPES = ['codex', 'cursor', 'gemini', 'claude', 'opencode', 'grok', 'antigravity', 'kimi'];
176
+ const TEAM_AGENT_TYPES = ['codex', 'cursor', 'gemini', 'claude', 'opencode', 'grok', 'antigravity', 'kimi', 'droid'];
177
177
  // Suffix appended to all prompts to ensure agents provide a summary
178
178
  const PROMPT_SUFFIX = `
179
179
 
@@ -1,5 +1,5 @@
1
1
  /** Supported agent CLI types for team spawning. */
2
- export type AgentType = 'codex' | 'gemini' | 'cursor' | 'claude' | 'opencode' | 'grok' | 'antigravity' | 'kimi';
2
+ export type AgentType = 'codex' | 'gemini' | 'cursor' | 'claude' | 'opencode' | 'grok' | 'antigravity' | 'kimi' | 'droid';
3
3
  /** Normalize a raw JSON event from any agent type into an array of unified event objects. */
4
4
  export declare function normalizeEvents(agentType: AgentType, raw: any): any[];
5
5
  /** Normalize a raw JSON event, returning only the first unified event (convenience wrapper). */
@@ -31,6 +31,12 @@ export function normalizeEvents(agentType, raw) {
31
31
  else if (agentType === 'antigravity') {
32
32
  return normalizeAntigravity(raw);
33
33
  }
34
+ // droid (Factory AI) intentionally falls through to the generic normalizer
35
+ // below: its `-o stream-json` JSONL event schema is not yet verified against
36
+ // a live run (the documented `debug` format differs from stream-json). Events
37
+ // still stream and render; structured tool/file categorization will be added
38
+ // once a real `droid exec -o stream-json` sample is captured. Do NOT guess the
39
+ // schema here — a wrong discriminator silently mislabels every event.
34
40
  const timestamp = new Date().toISOString();
35
41
  return [{
36
42
  type: raw.type || 'unknown',
@@ -6,7 +6,7 @@
6
6
  * formats for each supported agent.
7
7
  */
8
8
  /** Unique identifier for a supported AI coding agent. */
9
- export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'roo' | 'antigravity' | 'grok' | 'kimi';
9
+ export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'roo' | 'antigravity' | 'grok' | 'kimi' | 'droid';
10
10
  /** How `agents run <agent>` chooses an installed version when none is pinned. */
11
11
  export type RunStrategy = 'pinned' | 'available' | 'balanced';
12
12
  /** Per-agent run strategy config. */
@@ -126,6 +126,10 @@ export declare function isVersionInstalled(agent: AgentId, version: string): boo
126
126
  * Get the latest available version from npm for an agent.
127
127
  */
128
128
  export declare function getLatestNpmVersion(agent: AgentId): Promise<string | null>;
129
+ /**
130
+ * Get the oldest published version from npm for an agent.
131
+ */
132
+ export declare function getOldestNpmVersion(agent: AgentId): Promise<string | null>;
129
133
  /**
130
134
  * Check if 'latest' version is already installed (by resolving to actual version).
131
135
  */
@@ -133,6 +137,13 @@ export declare function isLatestInstalled(agent: AgentId): Promise<{
133
137
  installed: boolean;
134
138
  version: string | null;
135
139
  }>;
140
+ /**
141
+ * Check if 'oldest' published version is already installed (by resolving to actual version).
142
+ */
143
+ export declare function isOldestInstalled(agent: AgentId): Promise<{
144
+ installed: boolean;
145
+ version: string | null;
146
+ }>;
136
147
  /**
137
148
  * List all installed versions for an agent.
138
149
  */
@@ -210,6 +221,7 @@ export declare function resolveVersion(agent: AgentId, projectPath?: string): st
210
221
  *
211
222
  * undefined / "" / "default" -> undefined (caller falls back to project pin or global default)
212
223
  * "latest" -> highest installed version (process.exit if none installed)
224
+ * "oldest" -> lowest installed version (process.exit if none installed)
213
225
  * "x.y.z" (installed) -> "x.y.z"
214
226
  * "x.y.z" (not installed) -> process.exit with installed-list hint
215
227
  *
@@ -221,9 +233,9 @@ export declare function resolveVersion(agent: AgentId, projectPath?: string): st
221
233
  export declare function resolveVersionAlias(agent: AgentId, raw: string | undefined | null): string | undefined;
222
234
  /**
223
235
  * Loose variant of resolveVersionAlias for record-filter contexts (sessions,
224
- * team history). Same `default`/`latest` semantics, but explicit versions
225
- * pass through unchanged so historical records of uninstalled versions remain
226
- * queryable.
236
+ * team history). Same `default`/`latest`/`oldest` semantics, but explicit
237
+ * versions pass through unchanged so historical records of uninstalled versions
238
+ * remain queryable.
227
239
  */
228
240
  export declare function resolveVersionAliasLoose(agent: AgentId, raw: string | undefined | null): string | undefined;
229
241
  /**