@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.
- package/CHANGELOG.md +17 -0
- package/README.md +3 -0
- package/dist/commands/computer-actions.d.ts +3 -0
- package/dist/commands/computer-actions.js +16 -0
- package/dist/commands/exec.js +25 -4
- package/dist/commands/import.js +17 -6
- package/dist/commands/inspect.d.ts +11 -1
- package/dist/commands/inspect.js +53 -19
- package/dist/commands/mcp.js +3 -3
- package/dist/commands/plugins.d.ts +2 -0
- package/dist/commands/plugins.js +69 -26
- package/dist/commands/sync.js +1 -1
- package/dist/commands/teams.js +1 -0
- package/dist/commands/trash.d.ts +11 -0
- package/dist/commands/trash.js +57 -41
- package/dist/commands/versions.js +68 -20
- package/dist/commands/view.js +1 -12
- package/dist/commands/wallet.d.ts +14 -0
- package/dist/commands/wallet.js +199 -0
- package/dist/index.js +22 -1
- package/dist/lib/agents.js +70 -22
- package/dist/lib/browser/ipc.d.ts +7 -0
- package/dist/lib/browser/ipc.js +43 -27
- package/dist/lib/capabilities.js +7 -1
- package/dist/lib/command-skills.d.ts +1 -0
- package/dist/lib/command-skills.js +23 -7
- package/dist/lib/exec.d.ts +32 -1
- package/dist/lib/exec.js +79 -7
- package/dist/lib/hooks.js +37 -5
- package/dist/lib/mcp.js +33 -0
- package/dist/lib/models.js +5 -0
- package/dist/lib/picker.d.ts +2 -0
- package/dist/lib/picker.js +96 -6
- package/dist/lib/platform/index.d.ts +1 -0
- package/dist/lib/platform/index.js +1 -0
- package/dist/lib/platform/winpath.d.ts +35 -0
- package/dist/lib/platform/winpath.js +86 -0
- package/dist/lib/plugins.d.ts +14 -0
- package/dist/lib/plugins.js +23 -0
- package/dist/lib/project-launch.js +110 -5
- package/dist/lib/registry.js +15 -2
- package/dist/lib/runner.js +14 -0
- package/dist/lib/sandbox.js +5 -2
- package/dist/lib/settings-manifest.d.ts +39 -0
- package/dist/lib/settings-manifest.js +163 -0
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +16 -31
- package/dist/lib/staleness/detectors/subagents.js +16 -0
- package/dist/lib/staleness/writers/subagents.js +11 -3
- package/dist/lib/subagents.d.ts +9 -0
- package/dist/lib/subagents.js +33 -0
- package/dist/lib/teams/agents.js +1 -1
- package/dist/lib/teams/parsers.d.ts +1 -1
- package/dist/lib/teams/parsers.js +6 -0
- package/dist/lib/types.d.ts +1 -1
- package/dist/lib/versions.d.ts +15 -3
- package/dist/lib/versions.js +88 -19
- package/dist/lib/wallet/index.d.ts +78 -0
- package/dist/lib/wallet/index.js +253 -0
- package/package.json +3 -3
- 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
|
+
}
|
package/dist/lib/shims.d.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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="
|
|
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
|
|
1583
|
-
*
|
|
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
|
|
1587
|
-
|
|
1588
|
-
|
|
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/`.
|
|
4
|
-
*
|
|
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);
|
package/dist/lib/subagents.d.ts
CHANGED
|
@@ -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
|
package/dist/lib/subagents.js
CHANGED
|
@@ -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
|
package/dist/lib/teams/agents.js
CHANGED
|
@@ -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',
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -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. */
|
package/dist/lib/versions.d.ts
CHANGED
|
@@ -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
|
|
225
|
-
* pass through unchanged so historical records of uninstalled versions
|
|
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
|
/**
|