@robbiesrobotics/alice-agents 1.4.4 → 1.4.6
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/README.md +19 -3
- package/bin/alice-install.mjs +22 -1
- package/lib/agent-registry.mjs +33 -0
- package/lib/coding-agent.mjs +187 -0
- package/lib/config-merger.mjs +5 -1
- package/lib/doctor.mjs +123 -43
- package/lib/installer.mjs +83 -19
- package/lib/license.mjs +158 -14
- package/lib/manifest.mjs +18 -9
- package/lib/mission-control.mjs +31 -0
- package/lib/release-guard.mjs +131 -0
- package/lib/skills.mjs +0 -1
- package/lib/workspace-scaffolder.mjs +11 -16
- package/package.json +5 -3
- package/templates/agents-pro.json +550 -0
- package/templates/mission-control-bridge/index.ts +494 -32
- package/templates/workspaces/dylan/SOUL.md +1 -1
- package/templates/workspaces/dylan/TOOLS.md +4 -4
- package/templates/workspaces/felix/SOUL.md +1 -1
- package/templates/workspaces/felix/TOOLS.md +3 -3
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ That's it. The installer detects your runtime (NemoClaw or OpenClaw) and sets ev
|
|
|
22
22
|
- installs the `mission-control-bridge` plugin into your OpenClaw home
|
|
23
23
|
- writes a portable local Mission Control config at `~/.openclaw/.alice-mission-control.json`
|
|
24
24
|
- enables the bridge in `openclaw.json` so your runtime can forward live telemetry to Mission Control
|
|
25
|
+
- installs a bundled `coding-agent` skill that prefers Codex for OpenAI defaults and Claude Code for Anthropic defaults
|
|
25
26
|
|
|
26
27
|
An orchestrator (A.L.I.C.E., also addressable as Alice or Olivia) backed by specialist agents across every domain:
|
|
27
28
|
|
|
@@ -79,11 +80,14 @@ When you install, the installer will **auto-detect your configured model** and l
|
|
|
79
80
|
# Interactive install
|
|
80
81
|
npx @robbiesrobotics/alice-agents
|
|
81
82
|
|
|
82
|
-
# Non-interactive with defaults (detected model if available, otherwise Sonnet; Starter tier)
|
|
83
|
+
# Non-interactive with defaults (detected model if available, otherwise Sonnet; Starter tier unless --tier pro)
|
|
83
84
|
npx @robbiesrobotics/alice-agents --yes
|
|
84
85
|
|
|
85
86
|
# Non-interactive Pro install with Mission Control Cloud enabled
|
|
86
|
-
npx @robbiesrobotics/alice-agents --cloud --cloud-token YOUR_TOKEN
|
|
87
|
+
npx @robbiesrobotics/alice-agents --yes --tier pro --license-key YOUR_KEY --cloud --cloud-token YOUR_TOKEN
|
|
88
|
+
|
|
89
|
+
# Force the coding tool preference for this install
|
|
90
|
+
npx @robbiesrobotics/alice-agents --yes --coding-tool codex
|
|
87
91
|
|
|
88
92
|
# Show help
|
|
89
93
|
npx @robbiesrobotics/alice-agents --help
|
|
@@ -100,7 +104,7 @@ npx @robbiesrobotics/alice-agents --help
|
|
|
100
104
|
If you're a Pro user with the cloud add-on, the installer can configure your local runtime for Mission Control in the same pass.
|
|
101
105
|
|
|
102
106
|
- Interactive install: choose `Pro`, validate your license, then enable the Mission Control Cloud add-on when prompted
|
|
103
|
-
- Non-interactive install: pass `--cloud`
|
|
107
|
+
- Non-interactive install: pass `--tier pro --license-key YOUR_KEY --cloud`
|
|
104
108
|
- Optional flags:
|
|
105
109
|
- `--cloud-token <token>` — access or ingest token for authenticated telemetry
|
|
106
110
|
- `--cloud-dashboard-url <url>` — defaults to `https://alice.av3.ai`
|
|
@@ -127,6 +131,18 @@ npx @robbiesrobotics/alice-agents --uninstall
|
|
|
127
131
|
|
|
128
132
|
Removes A.L.I.C.E. agents from `openclaw.json` while preserving any non-ALICE agents. Creates a backup before making changes.
|
|
129
133
|
|
|
134
|
+
## Maintainer Release Guard
|
|
135
|
+
|
|
136
|
+
Maintainer publishes are now gated.
|
|
137
|
+
|
|
138
|
+
- `npm publish` runs `prepublishOnly`
|
|
139
|
+
- that hook runs tests, syntax checks, and `npm run release:check`
|
|
140
|
+
- publish is blocked unless the package version has a matching git tag on `HEAD`
|
|
141
|
+
- publish is blocked unless `landing/content/changelog.md` contains the matching version entry
|
|
142
|
+
- publish is blocked unless `releases/vX.Y.Z.md` exists and is marked `Status: approved`
|
|
143
|
+
|
|
144
|
+
Use `releases/TEMPLATE.md` as the starting point for each release brief.
|
|
145
|
+
|
|
130
146
|
## How It Works
|
|
131
147
|
|
|
132
148
|
1. **You talk to A.L.I.C.E.** — she's your single point of contact
|
package/bin/alice-install.mjs
CHANGED
|
@@ -37,15 +37,22 @@ if (flags.has('--help') || flags.has('-h')) {
|
|
|
37
37
|
npx @robbiesrobotics/alice-agents --help Show this help
|
|
38
38
|
|
|
39
39
|
Options:
|
|
40
|
-
--yes Skip prompts, use
|
|
40
|
+
--yes Skip prompts, use detected model when available (otherwise Sonnet)
|
|
41
41
|
--update Non-interactive upgrade (alias for --yes with upgrade mode)
|
|
42
42
|
--uninstall Remove A.L.I.C.E. agents (preserves non-ALICE agents)
|
|
43
43
|
--doctor Run diagnostics and check install health
|
|
44
44
|
--cloud Enable Mission Control Cloud setup during install
|
|
45
45
|
--no-cloud Skip Mission Control Cloud setup during install
|
|
46
|
+
--tier <starter|pro> Force the install tier
|
|
47
|
+
--license-key <key> Provide a Pro license key for automation
|
|
48
|
+
--coding-tool <auto|claude|codex> Override the preferred coding CLI
|
|
46
49
|
--cloud-token <token> Mission Control ingest/access token
|
|
47
50
|
--cloud-dashboard-url <url> Mission Control dashboard URL
|
|
48
51
|
--cloud-ingest-url <url> Mission Control ingest endpoint
|
|
52
|
+
--cloud-team-id <id> Mission Control team UUID for hosted linkage
|
|
53
|
+
--cloud-team-slug <slug> Mission Control team slug
|
|
54
|
+
--cloud-team-name <name> Mission Control team display name
|
|
55
|
+
--cloud-team-plan <plan> Mission Control team plan
|
|
49
56
|
--version Print package version
|
|
50
57
|
`);
|
|
51
58
|
process.exit(0);
|
|
@@ -61,9 +68,16 @@ if (flags.has('--doctor')) {
|
|
|
61
68
|
yes: true,
|
|
62
69
|
modeOverride: 'upgrade',
|
|
63
70
|
cloud: flags.has('--cloud') ? true : flags.has('--no-cloud') ? false : undefined,
|
|
71
|
+
tierOverride: getFlagValue('--tier'),
|
|
72
|
+
licenseKey: getFlagValue('--license-key'),
|
|
73
|
+
codingTool: getFlagValue('--coding-tool'),
|
|
64
74
|
cloudToken: getFlagValue('--cloud-token'),
|
|
65
75
|
cloudDashboardUrl: getFlagValue('--cloud-dashboard-url'),
|
|
66
76
|
cloudIngestUrl: getFlagValue('--cloud-ingest-url'),
|
|
77
|
+
cloudTeamId: getFlagValue('--cloud-team-id'),
|
|
78
|
+
cloudTeamSlug: getFlagValue('--cloud-team-slug'),
|
|
79
|
+
cloudTeamName: getFlagValue('--cloud-team-name'),
|
|
80
|
+
cloudTeamPlan: getFlagValue('--cloud-team-plan'),
|
|
67
81
|
}).catch((err) => {
|
|
68
82
|
console.error(' ❌ Update failed:', err.message);
|
|
69
83
|
process.exit(1);
|
|
@@ -82,9 +96,16 @@ if (flags.has('--doctor')) {
|
|
|
82
96
|
runInstall({
|
|
83
97
|
yes: flags.has('--yes'),
|
|
84
98
|
cloud: flags.has('--cloud') ? true : flags.has('--no-cloud') ? false : undefined,
|
|
99
|
+
tierOverride: getFlagValue('--tier'),
|
|
100
|
+
licenseKey: getFlagValue('--license-key'),
|
|
101
|
+
codingTool: getFlagValue('--coding-tool'),
|
|
85
102
|
cloudToken: getFlagValue('--cloud-token'),
|
|
86
103
|
cloudDashboardUrl: getFlagValue('--cloud-dashboard-url'),
|
|
87
104
|
cloudIngestUrl: getFlagValue('--cloud-ingest-url'),
|
|
105
|
+
cloudTeamId: getFlagValue('--cloud-team-id'),
|
|
106
|
+
cloudTeamSlug: getFlagValue('--cloud-team-slug'),
|
|
107
|
+
cloudTeamName: getFlagValue('--cloud-team-name'),
|
|
108
|
+
cloudTeamPlan: getFlagValue('--cloud-team-plan'),
|
|
88
109
|
}).catch((err) => {
|
|
89
110
|
console.error(' ❌ Install failed:', err.message);
|
|
90
111
|
process.exit(1);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const TEMPLATES_DIR = join(__dirname, '..', 'templates');
|
|
7
|
+
|
|
8
|
+
const STARTER_TEMPLATE = 'agents-starter.json';
|
|
9
|
+
const PRO_ADDONS_TEMPLATE = 'agents-pro.json';
|
|
10
|
+
|
|
11
|
+
export function normalizeTier(tier) {
|
|
12
|
+
return tier === 'pro' ? 'pro' : 'starter';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function loadRegistryFile(filename) {
|
|
16
|
+
const raw = readFileSync(join(TEMPLATES_DIR, filename), 'utf8');
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function loadAgentRegistry(tier = 'starter') {
|
|
21
|
+
const normalizedTier = normalizeTier(tier);
|
|
22
|
+
const starterAgents = loadRegistryFile(STARTER_TEMPLATE);
|
|
23
|
+
if (normalizedTier === 'starter') {
|
|
24
|
+
return starterAgents;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const proAddons = loadRegistryFile(PRO_ADDONS_TEMPLATE);
|
|
28
|
+
return [...starterAgents, ...proAddons];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function getAgentIdsForTier(tier = 'starter') {
|
|
32
|
+
return loadAgentRegistry(tier).map((agent) => agent.id);
|
|
33
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
export const CODING_TOOL_OVERRIDES = new Set(['auto', 'claude', 'codex']);
|
|
4
|
+
|
|
5
|
+
const TOOL_CONFIG = {
|
|
6
|
+
claude: {
|
|
7
|
+
id: 'claude',
|
|
8
|
+
name: 'Claude Code',
|
|
9
|
+
cli: 'claude',
|
|
10
|
+
skillHeading: 'Claude Code',
|
|
11
|
+
primaryExample: `claude --permission-mode bypassPermissions --print 'YOUR TASK HERE. Run tests to verify.'`,
|
|
12
|
+
reviewExample: `claude --permission-mode bypassPermissions --print 'Review the current changes for bugs, regressions, and missing tests. Output a numbered list of findings.'`,
|
|
13
|
+
},
|
|
14
|
+
codex: {
|
|
15
|
+
id: 'codex',
|
|
16
|
+
name: 'Codex',
|
|
17
|
+
cli: 'codex',
|
|
18
|
+
skillHeading: 'Codex',
|
|
19
|
+
primaryExample: `codex exec --full-auto -C /path/to/project 'YOUR TASK HERE. Run tests to verify.'`,
|
|
20
|
+
reviewExample: `codex review --base main 'Review the current changes for bugs, regressions, and missing tests.'`,
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function commandExists(cmd) {
|
|
25
|
+
const probe = process.platform === 'win32' ? 'where' : 'which';
|
|
26
|
+
try {
|
|
27
|
+
execSync(`${probe} ${cmd}`, { stdio: 'pipe' });
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function normalizeProviderId(provider) {
|
|
35
|
+
if (!provider) return null;
|
|
36
|
+
if (provider === 'openai-codex') return 'openai';
|
|
37
|
+
return provider;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getModelProvider(model) {
|
|
41
|
+
if (!model || typeof model !== 'string' || !model.includes('/')) return null;
|
|
42
|
+
return normalizeProviderId(model.split('/')[0]);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function getPreferredToolForProvider(provider) {
|
|
46
|
+
if (provider === 'anthropic') return 'claude';
|
|
47
|
+
if (provider === 'openai') return 'codex';
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function validateOverride(override) {
|
|
52
|
+
const normalized = String(override || 'auto').trim().toLowerCase() || 'auto';
|
|
53
|
+
if (!CODING_TOOL_OVERRIDES.has(normalized)) {
|
|
54
|
+
throw new Error(`Invalid --coding-tool value "${override}". Use auto, claude, or codex.`);
|
|
55
|
+
}
|
|
56
|
+
return normalized;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function resolveCodingAgentPreference({ detectedModels = null, override = 'auto' } = {}) {
|
|
60
|
+
const normalizedOverride = validateOverride(override);
|
|
61
|
+
const available = {
|
|
62
|
+
claude: commandExists(TOOL_CONFIG.claude.cli),
|
|
63
|
+
codex: commandExists(TOOL_CONFIG.codex.cli),
|
|
64
|
+
};
|
|
65
|
+
const provider =
|
|
66
|
+
getModelProvider(detectedModels?.primary) ||
|
|
67
|
+
getModelProvider(detectedModels?.orchestrator) ||
|
|
68
|
+
(detectedModels?.providers || [])
|
|
69
|
+
.map(normalizeProviderId)
|
|
70
|
+
.find((entry) => entry === 'anthropic' || entry === 'openai') ||
|
|
71
|
+
null;
|
|
72
|
+
|
|
73
|
+
let preferredTool = normalizedOverride === 'auto' ? getPreferredToolForProvider(provider) : normalizedOverride;
|
|
74
|
+
|
|
75
|
+
if (!preferredTool) {
|
|
76
|
+
if (available.codex) {
|
|
77
|
+
preferredTool = 'codex';
|
|
78
|
+
} else if (available.claude) {
|
|
79
|
+
preferredTool = 'claude';
|
|
80
|
+
} else {
|
|
81
|
+
preferredTool = 'codex';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const fallbackTool = preferredTool === 'codex' ? 'claude' : 'codex';
|
|
86
|
+
const selectionReason =
|
|
87
|
+
normalizedOverride !== 'auto'
|
|
88
|
+
? `manual override (--coding-tool ${preferredTool})`
|
|
89
|
+
: provider
|
|
90
|
+
? `detected ${provider} as the default OpenClaw provider`
|
|
91
|
+
: available.codex || available.claude
|
|
92
|
+
? 'no provider-specific default detected; using the first available coding CLI'
|
|
93
|
+
: 'no coding CLI detected yet; generated guidance includes both Codex and Claude Code';
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
override: normalizedOverride,
|
|
97
|
+
provider,
|
|
98
|
+
preferredTool,
|
|
99
|
+
fallbackTool,
|
|
100
|
+
preferred: TOOL_CONFIG[preferredTool],
|
|
101
|
+
fallback: TOOL_CONFIG[fallbackTool],
|
|
102
|
+
available,
|
|
103
|
+
selectionReason,
|
|
104
|
+
skillId: 'coding-agent',
|
|
105
|
+
skillPath: '~/.openclaw/skills/coding-agent/SKILL.md',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function buildCodingAgentSkillContent(preference) {
|
|
110
|
+
const preferredAvailability = preference.available[preference.preferred.id]
|
|
111
|
+
? `${preference.preferred.cli} is installed on this machine.`
|
|
112
|
+
: `${preference.preferred.cli} is not currently installed; check before using it.`;
|
|
113
|
+
const fallbackAvailability = preference.available[preference.fallback.id]
|
|
114
|
+
? `${preference.fallback.cli} is also available as a fallback.`
|
|
115
|
+
: `${preference.fallback.cli} is the fallback if you install it later.`;
|
|
116
|
+
const providerLabel = preference.provider || 'unknown';
|
|
117
|
+
|
|
118
|
+
return `---
|
|
119
|
+
name: coding-agent
|
|
120
|
+
description: 'Delegate substantial coding work to the preferred coding CLI for this install. Use when: multi-file implementation, refactors, build/test verification, deep codebase exploration, or structured code review. Prefer ${preference.preferred.name} first, then fall back to ${preference.fallback.name} if needed.'
|
|
121
|
+
metadata:
|
|
122
|
+
{
|
|
123
|
+
"openclaw": { "emoji": "⚙️", "requires": { "anyBins": ["${preference.preferred.cli}", "${preference.fallback.cli}"] } }
|
|
124
|
+
}
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
# Coding Agent Skill
|
|
128
|
+
|
|
129
|
+
This bundled skill routes coding work through the preferred CLI for this install.
|
|
130
|
+
|
|
131
|
+
- Preferred tool: **${preference.preferred.name}**
|
|
132
|
+
- Fallback tool: **${preference.fallback.name}**
|
|
133
|
+
- Detection basis: ${preference.selectionReason}
|
|
134
|
+
- Current provider signal: ${providerLabel}
|
|
135
|
+
|
|
136
|
+
## Availability
|
|
137
|
+
|
|
138
|
+
- ${preferredAvailability}
|
|
139
|
+
- ${fallbackAvailability}
|
|
140
|
+
|
|
141
|
+
## When to use this skill
|
|
142
|
+
|
|
143
|
+
Use this skill when the task requires:
|
|
144
|
+
- Modifying multiple files
|
|
145
|
+
- Running build, lint, or test commands to verify correctness
|
|
146
|
+
- Exploring an unfamiliar codebase before implementing
|
|
147
|
+
- A focused code review with concrete findings
|
|
148
|
+
|
|
149
|
+
## Preferred path: ${preference.preferred.skillHeading}
|
|
150
|
+
|
|
151
|
+
\`\`\`
|
|
152
|
+
${preference.preferred.primaryExample}
|
|
153
|
+
\`\`\`
|
|
154
|
+
|
|
155
|
+
Review-only example:
|
|
156
|
+
|
|
157
|
+
\`\`\`
|
|
158
|
+
${preference.preferred.reviewExample}
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
## Fallback path: ${preference.fallback.skillHeading}
|
|
162
|
+
|
|
163
|
+
\`\`\`
|
|
164
|
+
${preference.fallback.primaryExample}
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
Review-only example:
|
|
168
|
+
|
|
169
|
+
\`\`\`
|
|
170
|
+
${preference.fallback.reviewExample}
|
|
171
|
+
\`\`\`
|
|
172
|
+
|
|
173
|
+
## Rules
|
|
174
|
+
|
|
175
|
+
1. Use the preferred tool first unless it is unavailable or blocked.
|
|
176
|
+
2. Always point the tool at the project root before asking it to work.
|
|
177
|
+
3. Ask it to run the relevant verification commands before finishing.
|
|
178
|
+
4. Summarize what changed and any remaining risks when reporting back.
|
|
179
|
+
5. Do not use this skill for trivial one-line edits or pure planning.
|
|
180
|
+
|
|
181
|
+
## Quick check
|
|
182
|
+
|
|
183
|
+
\`\`\`
|
|
184
|
+
which ${preference.preferred.cli} || which ${preference.fallback.cli}
|
|
185
|
+
\`\`\`
|
|
186
|
+
`;
|
|
187
|
+
}
|
package/lib/config-merger.mjs
CHANGED
|
@@ -272,7 +272,11 @@ export function mergeConfig({ agents, mode, preset, customModels }) {
|
|
|
272
272
|
config.tools = config.tools || {};
|
|
273
273
|
config.tools.agentToAgent = config.tools.agentToAgent || {};
|
|
274
274
|
config.tools.agentToAgent.enabled = true;
|
|
275
|
-
config.tools.agentToAgent.allow
|
|
275
|
+
const mergedAllow = new Set(config.tools.agentToAgent.allow || []);
|
|
276
|
+
for (const id of aliceIds) {
|
|
277
|
+
mergedAllow.add(id);
|
|
278
|
+
}
|
|
279
|
+
config.tools.agentToAgent.allow = [...mergedAllow];
|
|
276
280
|
|
|
277
281
|
writeConfigAtomic(config);
|
|
278
282
|
return { backupPath, agentCount: aliceEntries.length, effectivePreset, warning };
|
package/lib/doctor.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { execSync } from 'node:child_process';
|
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { icons, greenBold, green, red, yellow, cyan, dim, bold,
|
|
7
7
|
printSection, printSeparator, separator } from './colors.mjs';
|
|
8
|
+
import { getAgentIdsForTier } from './agent-registry.mjs';
|
|
8
9
|
|
|
9
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
11
|
const HOME = homedir();
|
|
@@ -20,10 +21,52 @@ function commandExists(cmd) {
|
|
|
20
21
|
}
|
|
21
22
|
const OPENCLAW_DIR = join(HOME, '.openclaw');
|
|
22
23
|
|
|
23
|
-
const STARTER_AGENTS =
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const STARTER_AGENTS = getAgentIdsForTier('starter');
|
|
25
|
+
const ALL_ALICE_AGENTS = getAgentIdsForTier('pro');
|
|
26
|
+
|
|
27
|
+
function normalizeProviderId(provider) {
|
|
28
|
+
if (!provider) return null;
|
|
29
|
+
if (provider === 'openai-codex') return 'openai';
|
|
30
|
+
return provider;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getConfigAgents(config) {
|
|
34
|
+
if (Array.isArray(config?.agents?.list)) return config.agents.list;
|
|
35
|
+
if (Array.isArray(config?.agents)) return config.agents;
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function detectConfiguredModel(config) {
|
|
40
|
+
if (!config || config === 'invalid') return { ok: false, label: null, inherited: false };
|
|
41
|
+
|
|
42
|
+
const defaults = config?.agents?.defaults?.model || {};
|
|
43
|
+
const primary = defaults.primary || config?.model || config?.default_model || null;
|
|
44
|
+
if (primary) {
|
|
45
|
+
return { ok: true, label: primary, inherited: true };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const providerKeys = Object.keys(config?.models?.providers || {});
|
|
49
|
+
if (providerKeys.length > 0) {
|
|
50
|
+
return { ok: true, label: providerKeys[0], inherited: false };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const profile = Object.values(config?.auth?.profiles || {}).find((entry) => entry?.provider);
|
|
54
|
+
if (profile?.provider) {
|
|
55
|
+
return { ok: true, label: normalizeProviderId(profile.provider), inherited: false };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (config.models && Object.keys(config.models).length > 0) {
|
|
59
|
+
return { ok: true, label: Object.keys(config.models)[0], inherited: false };
|
|
60
|
+
}
|
|
61
|
+
if (config.providers && Object.keys(config.providers).length > 0) {
|
|
62
|
+
return { ok: true, label: Object.keys(config.providers)[0], inherited: false };
|
|
63
|
+
}
|
|
64
|
+
if (config.llm && Object.keys(config.llm).length > 0) {
|
|
65
|
+
return { ok: true, label: Object.keys(config.llm)[0], inherited: false };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { ok: false, label: null, inherited: false };
|
|
69
|
+
}
|
|
27
70
|
|
|
28
71
|
function check(label, ok, hint) {
|
|
29
72
|
const icon = ok ? icons.ok : icons.fail;
|
|
@@ -158,10 +201,23 @@ export async function runDoctor() {
|
|
|
158
201
|
}
|
|
159
202
|
|
|
160
203
|
// 3. A.L.I.C.E. agents in config
|
|
161
|
-
const
|
|
204
|
+
const manifest = (() => {
|
|
205
|
+
try {
|
|
206
|
+
const mPath = join(OPENCLAW_DIR, '.alice-manifest.json');
|
|
207
|
+
if (!existsSync(mPath)) return null;
|
|
208
|
+
return JSON.parse(readFileSync(mPath, 'utf8'));
|
|
209
|
+
} catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
})();
|
|
213
|
+
const configAgents = getConfigAgents(config);
|
|
162
214
|
const agentsInConfig = configAgents
|
|
163
|
-
.filter((a) => a &&
|
|
215
|
+
.filter((a) => a && ALL_ALICE_AGENTS.includes(a.id))
|
|
164
216
|
.map((a) => a.id);
|
|
217
|
+
const expectedTier = manifest?.tier === 'pro' || agentsInConfig.some((id) => !STARTER_AGENTS.includes(id))
|
|
218
|
+
? 'pro'
|
|
219
|
+
: 'starter';
|
|
220
|
+
const expectedAgents = expectedTier === 'pro' ? ALL_ALICE_AGENTS : STARTER_AGENTS;
|
|
165
221
|
|
|
166
222
|
const agentsOk = agentsInConfig.length > 0;
|
|
167
223
|
check(
|
|
@@ -173,17 +229,17 @@ export async function runDoctor() {
|
|
|
173
229
|
);
|
|
174
230
|
allOk = allOk && agentsOk;
|
|
175
231
|
|
|
176
|
-
// Check for missing agents from
|
|
177
|
-
if (agentsInConfig.length > 0 && agentsInConfig.length <
|
|
178
|
-
const missing =
|
|
232
|
+
// Check for missing agents from the expected tier roster
|
|
233
|
+
if (agentsInConfig.length > 0 && agentsInConfig.length < expectedAgents.length) {
|
|
234
|
+
const missing = expectedAgents.filter((id) => !agentsInConfig.includes(id));
|
|
179
235
|
check(
|
|
180
|
-
`All
|
|
236
|
+
`All ${expectedTier} agents present (missing: ${missing.join(', ')})`,
|
|
181
237
|
false,
|
|
182
238
|
'Run: npx @robbiesrobotics/alice-agents --update'
|
|
183
239
|
);
|
|
184
240
|
allOk = false;
|
|
185
|
-
} else if (agentsInConfig.length ===
|
|
186
|
-
check(
|
|
241
|
+
} else if (agentsInConfig.length === expectedAgents.length) {
|
|
242
|
+
check(`All ${expectedTier} agents present`, true);
|
|
187
243
|
}
|
|
188
244
|
|
|
189
245
|
// 4. Agent workspaces exist on disk
|
|
@@ -207,26 +263,13 @@ export async function runDoctor() {
|
|
|
207
263
|
}
|
|
208
264
|
|
|
209
265
|
// 5. At least one model/provider configured
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (config.default_model) {
|
|
214
|
-
modelOk = true;
|
|
215
|
-
modelLabel = config.default_model;
|
|
216
|
-
} else if (config.models && Object.keys(config.models).length > 0) {
|
|
217
|
-
modelOk = true;
|
|
218
|
-
modelLabel = Object.keys(config.models)[0];
|
|
219
|
-
} else if (config.providers && Object.keys(config.providers).length > 0) {
|
|
220
|
-
modelOk = true;
|
|
221
|
-
modelLabel = Object.keys(config.providers)[0];
|
|
222
|
-
} else if (config.llm && Object.keys(config.llm).length > 0) {
|
|
223
|
-
modelOk = true;
|
|
224
|
-
modelLabel = Object.keys(config.llm)[0];
|
|
225
|
-
}
|
|
266
|
+
const modelState = detectConfiguredModel(config);
|
|
267
|
+
const modelOk = modelState.ok;
|
|
268
|
+
const modelLabel = modelState.label;
|
|
226
269
|
|
|
227
270
|
check(
|
|
228
271
|
modelOk
|
|
229
|
-
? `Model/provider configured: ${modelLabel}`
|
|
272
|
+
? `Model/provider configured: ${modelLabel}${modelState.inherited ? ' (shared default)' : ''}`
|
|
230
273
|
: 'No model/provider configured',
|
|
231
274
|
modelOk,
|
|
232
275
|
'Run: openclaw configure to set up a model provider'
|
|
@@ -246,9 +289,11 @@ export async function runDoctor() {
|
|
|
246
289
|
);
|
|
247
290
|
// Docker permission issue is a warning, not a hard failure for A.L.I.C.E. itself
|
|
248
291
|
// but it will break OpenClaw's own Docker features
|
|
292
|
+
allOk = false;
|
|
249
293
|
console.log(` ${icons.info} ${dim('This will affect OpenClaw features that use Docker.')}\n`);
|
|
250
294
|
} else {
|
|
251
295
|
check('Docker daemon not running or not accessible', false, docker.hint);
|
|
296
|
+
allOk = false;
|
|
252
297
|
}
|
|
253
298
|
}
|
|
254
299
|
|
|
@@ -260,24 +305,28 @@ export async function runDoctor() {
|
|
|
260
305
|
|
|
261
306
|
// 7. License check
|
|
262
307
|
const { checkProLicense } = await import('./license.mjs');
|
|
263
|
-
const manifest = (() => {
|
|
264
|
-
try {
|
|
265
|
-
const mPath = join(OPENCLAW_DIR, '.alice-manifest.json');
|
|
266
|
-
if (!existsSync(mPath)) return null;
|
|
267
|
-
return JSON.parse(readFileSync(mPath, 'utf8'));
|
|
268
|
-
} catch {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
})();
|
|
272
308
|
|
|
273
309
|
const licenseResult = await checkProLicense();
|
|
274
310
|
if (manifest?.tier === 'pro' || licenseResult.licensed) {
|
|
275
311
|
if (licenseResult.licensed) {
|
|
276
312
|
const maskedKey = licenseResult.key.slice(0, 13) + '****';
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
313
|
+
if (licenseResult.provisional) {
|
|
314
|
+
check(
|
|
315
|
+
`Pro license: ${maskedKey} (temporary grace until ${licenseResult.graceUntil})`,
|
|
316
|
+
false,
|
|
317
|
+
'Reconnect to the network and rerun the installer to complete validation'
|
|
318
|
+
);
|
|
319
|
+
allOk = false;
|
|
320
|
+
} else {
|
|
321
|
+
check(
|
|
322
|
+
`Pro license: ${maskedKey} (stored at ~/.alice/license)`,
|
|
323
|
+
true
|
|
324
|
+
);
|
|
325
|
+
if (licenseResult.needsRevalidation) {
|
|
326
|
+
console.log(` ${icons.info} ${dim('Validation service unavailable — using cached entitlement for now.')}`);
|
|
327
|
+
console.log('');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
281
330
|
} else {
|
|
282
331
|
check(
|
|
283
332
|
'Pro license: not found (running Starter tier)',
|
|
@@ -291,7 +340,38 @@ export async function runDoctor() {
|
|
|
291
340
|
check('License: Starter tier (no license required)', true);
|
|
292
341
|
}
|
|
293
342
|
|
|
294
|
-
// 8.
|
|
343
|
+
// 8. Mission Control cloud config
|
|
344
|
+
const missionControlConfigPath = join(OPENCLAW_DIR, '.alice-mission-control.json');
|
|
345
|
+
if (existsSync(missionControlConfigPath)) {
|
|
346
|
+
try {
|
|
347
|
+
const missionControlConfig = JSON.parse(readFileSync(missionControlConfigPath, 'utf8'));
|
|
348
|
+
const cloud = missionControlConfig?.cloud || {};
|
|
349
|
+
const hasDashboardUrl = typeof cloud.dashboardUrl === 'string' && cloud.dashboardUrl.length > 0;
|
|
350
|
+
const hasIngestUrl = typeof cloud.ingestUrl === 'string' && cloud.ingestUrl.length > 0;
|
|
351
|
+
const hasIngestToken = typeof cloud.ingestToken === 'string' && cloud.ingestToken.length > 0;
|
|
352
|
+
const cloudOk = hasDashboardUrl && hasIngestUrl && hasIngestToken;
|
|
353
|
+
|
|
354
|
+
check(
|
|
355
|
+
cloudOk
|
|
356
|
+
? `Mission Control cloud configured (${cloud.dashboardUrl})`
|
|
357
|
+
: 'Mission Control cloud config incomplete',
|
|
358
|
+
cloudOk,
|
|
359
|
+
'Run: npx @robbiesrobotics/alice-agents --cloud to repair cloud settings'
|
|
360
|
+
);
|
|
361
|
+
allOk = allOk && cloudOk;
|
|
362
|
+
} catch {
|
|
363
|
+
check(
|
|
364
|
+
'Mission Control cloud config invalid',
|
|
365
|
+
false,
|
|
366
|
+
'Repair ~/.openclaw/.alice-mission-control.json or rerun the installer with --cloud'
|
|
367
|
+
);
|
|
368
|
+
allOk = false;
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
console.log(` ${dim('–')} ${dim('Mission Control cloud not configured (optional)')}`);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 9. Skills disk check
|
|
295
375
|
const skillsManifestPath = join(OPENCLAW_DIR, '.alice-manifest.json');
|
|
296
376
|
const skillsManifestData = (() => {
|
|
297
377
|
try {
|