Skip to content

Commit 766452a

Browse files
os-zhuangclaude
andcommitted
feat(cli): gate AI service auto-registration on a declared @objectstack/service-ai dependency
The CLI auto-registered the headless AIServicePlugin whenever the `ai` tier was enabled (default) and @objectstack/service-ai was merely *resolvable*. In a workspace/monorepo the package is hoist-resolvable even when an app doesn't declare it, so every app got the AI service — discovery reported services.ai as available and the agent runtime served any metadata-defined agents — including Community-Edition apps that ship no AI. Make the DECLARED dependency the AI edition boundary: auto-register AIService only when the host app lists @objectstack/service-ai in its dependencies. A Community-Edition app that omits it now gets no AI service, no agents, and services.ai { enabled:false, status:'unavailable' } in discovery — so the MIT console hides its AI surface — while MCP and every other capability are unaffected. MCP is a separate capability: it bridges data/metadata regardless and only optionally bridges the AI tool registry when the 'ai' service is present. Declare @objectstack/service-ai in the showcase and crm examples (they ship AI agents) so they keep AI. Verified e2e in an isolated build: - app WITHOUT the dep → discovery AI unavailable, /api/v1/ai/agents 404, AIService not loaded; MCP server still starts and serves (OS_MCP_SERVER_ENABLED=true → 401 auth-gate, i.e. live). - app that DECLARES it → services.ai available + agents served. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent ffa0aca commit 766452a

5 files changed

Lines changed: 53 additions & 1 deletion

File tree

.changeset/ce-ai-opt-in.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@objectstack/cli": minor
3+
---
4+
5+
feat(cli): gate AI service auto-registration on a declared `@objectstack/service-ai` dependency
6+
7+
The CLI auto-registered the headless `AIServicePlugin` whenever the `ai` tier was
8+
enabled (default) and `@objectstack/service-ai` was merely *resolvable*. In a
9+
workspace/monorepo the package is hoist-resolvable even when an app does not
10+
declare it, so every app got the AI service — discovery reported
11+
`services.ai: available` and the agent runtime served any metadata-defined
12+
agents — including Community-Edition apps that ship no AI.
13+
14+
Make the *declared* dependency the AI edition boundary: auto-register the AI
15+
service only when the host app lists `@objectstack/service-ai` in its
16+
dependencies. A Community-Edition app that omits it now gets no AI service, no
17+
agents, and `services.ai: { enabled: false, status: 'unavailable' }` in discovery
18+
(so the MIT console hides its AI surface). MCP and every other capability are
19+
unaffected — MCP is a separate capability that bridges data/metadata regardless
20+
and only *optionally* bridges the AI tool registry when present.
21+
22+
The `app-showcase` and `app-crm` examples now declare `@objectstack/service-ai`
23+
(they ship AI agents) so they keep their AI service.

examples/app-crm/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
},
2121
"dependencies": {
2222
"@objectstack/runtime": "workspace:*",
23+
"@objectstack/service-ai": "workspace:*",
2324
"@objectstack/spec": "workspace:*"
2425
},
2526
"devDependencies": {

examples/app-showcase/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@objectstack/connector-slack": "workspace:*",
2828
"@objectstack/driver-sql": "workspace:*",
2929
"@objectstack/runtime": "workspace:*",
30+
"@objectstack/service-ai": "workspace:*",
3031
"@objectstack/spec": "workspace:*"
3132
},
3233
"devDependencies": {

packages/cli/src/commands/serve.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1416,7 +1416,28 @@ export default class Serve extends Command {
14161416
return import(/* webpackIgnore: true */ pkg);
14171417
}
14181418
};
1419-
if (!hasAIPlugin && tierEnabled('ai')) {
1419+
// [CE AI opt-in] Auto-register the headless AI service ONLY when the host
1420+
// app DECLARES `@objectstack/service-ai`. Declaration is the edition
1421+
// boundary: a Community-Edition app that omits it gets no AI service, no
1422+
// agents, and no `services.ai` in discovery (so the console hides its AI
1423+
// surface), while MCP and every other capability are unaffected. Gating on
1424+
// a *declared* dep — not mere resolvability — makes this reliable in a
1425+
// workspace/monorepo, where the package stays hoist-resolvable when undeclared.
1426+
const _fs = await import('node:fs');
1427+
const hostDeclaresDependency = (pkg: string): boolean => {
1428+
try {
1429+
const hostPkg = JSON.parse(
1430+
_fs.readFileSync(_hostRequire.resolve('./package.json'), 'utf8'),
1431+
) as Record<string, Record<string, string> | undefined>;
1432+
return Boolean(
1433+
hostPkg.dependencies?.[pkg] ?? hostPkg.devDependencies?.[pkg]
1434+
?? hostPkg.optionalDependencies?.[pkg] ?? hostPkg.peerDependencies?.[pkg],
1435+
);
1436+
} catch {
1437+
return false;
1438+
}
1439+
};
1440+
if (!hasAIPlugin && tierEnabled('ai') && hostDeclaresDependency('@objectstack/service-ai')) {
14201441
try {
14211442
const aiPkg = '@objectstack/service-ai';
14221443
const { AIServicePlugin } = await importFromHost(aiPkg);

pnpm-lock.yaml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)