mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-07 07:55:13 +02:00
fix: allow agent setup without context
This commit is contained in:
parent
eb41d084af
commit
fa01c5fb90
9 changed files with 54 additions and 78 deletions
|
|
@ -26,7 +26,7 @@ below.
|
|||
|
||||
| Flag | Description | Default |
|
||||
|------|-------------|---------|
|
||||
| `--agents` | Install agent integration only | `false` |
|
||||
| `--agents` | Install agent configuration and rules only | `false` |
|
||||
| `--target <target>` | Agent target: `claude-code`, `codex`, `cursor`, `opencode`, or `universal` | - |
|
||||
| `--global` | Install agent integration into the global target scope for `claude-code` or `codex` | `false` |
|
||||
| `--yes` | Accept safe defaults in non-interactive setup | `false` |
|
||||
|
|
@ -82,15 +82,19 @@ embedding credential source.
|
|||
### Runtime
|
||||
|
||||
Setup prepares the managed Python runtime when your selected configuration
|
||||
needs it. The runtime step runs after database and source setup and before the
|
||||
initial context build.
|
||||
needs it. In the full setup flow, the runtime step runs after database and
|
||||
source setup and before the initial context build.
|
||||
|
||||
KTX prepares the `core` runtime feature when agent integration, query-history
|
||||
ingest, Looker source ingest, or daemon-backed context build paths need it. KTX
|
||||
prepares the `local-embeddings` runtime feature when you choose managed local
|
||||
`sentence-transformers` embeddings. Existing external daemon URLs, such as
|
||||
`KTX_DAEMON_URL` or `KTX_SQL_ANALYSIS_URL`, satisfy the matching dependency and
|
||||
skip managed runtime installation for that dependency.
|
||||
KTX prepares the `core` runtime feature when query-history ingest, Looker
|
||||
source ingest, database introspection fallback, or daemon-backed context build
|
||||
paths need it. KTX prepares the `local-embeddings` runtime feature when you
|
||||
choose managed local `sentence-transformers` embeddings. Existing external
|
||||
daemon URLs, such as `KTX_DAEMON_URL` or `KTX_SQL_ANALYSIS_URL`, satisfy the
|
||||
matching dependency and skip managed runtime installation for that dependency.
|
||||
|
||||
`ktx setup --agents` doesn't prepare runtime features or build context. It only
|
||||
installs agent configuration and rules. Start MCP with `ktx mcp start` before
|
||||
using HTTP-based agents; MCP startup prepares the runtime it needs.
|
||||
|
||||
Interactive setup prompts before installing runtime features. Use `--yes` to
|
||||
install them without prompting. Use `--no-input` to fail fast when required
|
||||
|
|
|
|||
|
|
@ -13,18 +13,18 @@ a developer or operator agent also needs pinned `ktx` admin commands.
|
|||
|
||||
## Install with setup
|
||||
|
||||
Start the MCP server before connecting an end-user agent:
|
||||
|
||||
```bash
|
||||
ktx mcp start
|
||||
```
|
||||
|
||||
Then install client integration:
|
||||
Install client integration first:
|
||||
|
||||
```bash
|
||||
ktx setup --agents
|
||||
```
|
||||
|
||||
Then start the MCP server before using HTTP-based clients:
|
||||
|
||||
```bash
|
||||
ktx mcp start
|
||||
```
|
||||
|
||||
Use `--target` for one target:
|
||||
|
||||
```bash
|
||||
|
|
|
|||
2
docs-site/next-env.d.ts
vendored
2
docs-site/next-env.d.ts
vendored
|
|
@ -1,6 +1,6 @@
|
|||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/types/routes.d.ts";
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
|
|
|||
|
|
@ -7,10 +7,10 @@ import {
|
|||
} from './runtime-requirements.js';
|
||||
|
||||
describe('runtime requirement detection', () => {
|
||||
it('requires core for agent/MCP setup', () => {
|
||||
it('does not require runtime for agent/MCP setup alone', () => {
|
||||
const config = buildDefaultKtxProjectConfig();
|
||||
|
||||
expect(resolveProjectRuntimeRequirements(config, { agents: true }).features).toEqual(['core']);
|
||||
expect(resolveProjectRuntimeRequirements(config).features).toEqual([]);
|
||||
});
|
||||
|
||||
it('requires core for Looker source ingest unless an external daemon is configured', () => {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import type { KtxRuntimeFeature } from './managed-python-runtime.js';
|
|||
import type { KtxPublicIngestPlan } from './public-ingest.js';
|
||||
|
||||
type KtxRuntimeRequirementReason =
|
||||
| 'agent-mcp'
|
||||
| 'query-history'
|
||||
| 'looker-source'
|
||||
| 'database-introspection'
|
||||
|
|
@ -26,7 +25,6 @@ export interface KtxRuntimeRequirements {
|
|||
}
|
||||
|
||||
export interface KtxProjectRuntimeRequirementOptions {
|
||||
agents?: boolean;
|
||||
databaseIntrospectionFallback?: boolean;
|
||||
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
||||
}
|
||||
|
|
@ -92,14 +90,6 @@ export function resolveProjectRuntimeRequirements(
|
|||
const env = options.env ?? process.env;
|
||||
const requirements: KtxRuntimeRequirement[] = [];
|
||||
|
||||
if (options.agents === true) {
|
||||
requirements.push({
|
||||
feature: 'core',
|
||||
reason: 'agent-mcp',
|
||||
detail: 'Agent MCP setup uses semantic-layer query tools and SQL validation.',
|
||||
});
|
||||
}
|
||||
|
||||
if (options.databaseIntrospectionFallback === true && !hasDaemonOverride(env)) {
|
||||
requirements.push({
|
||||
feature: 'core',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { join } from 'node:path';
|
|||
import { MANAGED_SENTENCE_TRANSFORMERS_BASE_URL } from '@ktx/context';
|
||||
import { buildDefaultKtxProjectConfig, readKtxSetupState, type KtxProjectConfig } from '@ktx/context/project';
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import type { ManagedPythonCommandRuntime } from './managed-python-command.js';
|
||||
import { runKtxSetupRuntimeStep } from './setup-runtime.js';
|
||||
|
||||
function makeIo() {
|
||||
|
|
@ -43,9 +42,9 @@ describe('runKtxSetupRuntimeStep', () => {
|
|||
await rm(tempDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('ensures core runtime for agent setup and records the runtime step', async () => {
|
||||
it('skips runtime setup when the project has no direct runtime requirements', async () => {
|
||||
const io = makeIo();
|
||||
const ensureRuntime = vi.fn(async (): Promise<ManagedPythonCommandRuntime> => ({} as ManagedPythonCommandRuntime));
|
||||
const ensureRuntime = vi.fn();
|
||||
|
||||
await expect(
|
||||
runKtxSetupRuntimeStep(
|
||||
|
|
@ -54,7 +53,6 @@ describe('runKtxSetupRuntimeStep', () => {
|
|||
inputMode: 'auto',
|
||||
cliVersion: '0.2.0',
|
||||
runtimeInstallPolicy: 'prompt',
|
||||
agents: true,
|
||||
},
|
||||
io.io,
|
||||
{
|
||||
|
|
@ -63,17 +61,11 @@ describe('runKtxSetupRuntimeStep', () => {
|
|||
env: {},
|
||||
},
|
||||
),
|
||||
).resolves.toMatchObject({ status: 'ready' });
|
||||
).resolves.toMatchObject({ status: 'skipped' });
|
||||
|
||||
expect(ensureRuntime).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
cliVersion: '0.2.0',
|
||||
installPolicy: 'prompt',
|
||||
feature: 'core',
|
||||
}),
|
||||
);
|
||||
expect((await readKtxSetupState(tempDir)).completed_steps).toContain('runtime');
|
||||
expect(io.stdout()).toContain('Runtime ready: yes (core)');
|
||||
expect(ensureRuntime).not.toHaveBeenCalled();
|
||||
expect((await readKtxSetupState(tempDir)).completed_steps).not.toContain('runtime');
|
||||
expect(io.stdout()).toContain('Runtime setup skipped.');
|
||||
});
|
||||
|
||||
it('fails fast when required runtime features cannot be installed in no-input mode', async () => {
|
||||
|
|
@ -89,7 +81,7 @@ describe('runKtxSetupRuntimeStep', () => {
|
|||
inputMode: 'disabled',
|
||||
cliVersion: '0.2.0',
|
||||
runtimeInstallPolicy: 'never',
|
||||
agents: true,
|
||||
databaseIntrospectionFallback: true,
|
||||
},
|
||||
io.io,
|
||||
{
|
||||
|
|
@ -131,7 +123,6 @@ describe('runKtxSetupRuntimeStep', () => {
|
|||
inputMode: 'auto',
|
||||
cliVersion: '0.2.0',
|
||||
runtimeInstallPolicy: 'auto',
|
||||
agents: false,
|
||||
},
|
||||
io.io,
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ export interface KtxSetupRuntimeArgs {
|
|||
inputMode: 'auto' | 'disabled';
|
||||
cliVersion: string;
|
||||
runtimeInstallPolicy: KtxManagedPythonInstallPolicy;
|
||||
agents: boolean;
|
||||
databaseIntrospectionFallback?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -62,7 +61,6 @@ export async function runKtxSetupRuntimeStep(
|
|||
const loadProjectForRuntime = deps.loadProject ?? loadKtxProject;
|
||||
const project = await loadProjectForRuntime({ projectDir: args.projectDir });
|
||||
const requirements = resolveProjectRuntimeRequirements(project.config, {
|
||||
agents: args.agents,
|
||||
databaseIntrospectionFallback: args.databaseIntrospectionFallback,
|
||||
env: deps.env ?? process.env,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1733,7 +1733,7 @@ describe('setup status', () => {
|
|||
expect(committedConfig.stdout).toContain('warehouse:');
|
||||
});
|
||||
|
||||
it('runs agent setup after context succeeds in --agents mode', async () => {
|
||||
it('runs agent setup without runtime or context in --agents mode', async () => {
|
||||
const calls: string[] = [];
|
||||
const io = makeIo();
|
||||
await writeFile(join(tempDir, 'ktx.yaml'), ['connections: {}', ''].join('\n'), 'utf-8');
|
||||
|
|
@ -1765,11 +1765,11 @@ describe('setup status', () => {
|
|||
sources: async () => ({ status: 'skipped', projectDir: tempDir }),
|
||||
runtime: async () => {
|
||||
calls.push('runtime');
|
||||
return runtimeReady(tempDir);
|
||||
throw new Error('runtime should not run');
|
||||
},
|
||||
context: async () => {
|
||||
calls.push('context');
|
||||
return { status: 'ready', projectDir: tempDir, runId: 'setup-context-local-test' };
|
||||
throw new Error('context should not run');
|
||||
},
|
||||
agents: async () => {
|
||||
calls.push('agents');
|
||||
|
|
@ -1783,11 +1783,13 @@ describe('setup status', () => {
|
|||
),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(calls).toEqual(['runtime', 'context', 'agents']);
|
||||
expect(calls).toEqual(['agents']);
|
||||
});
|
||||
|
||||
it('does not install agents when non-interactive --agents finds context incomplete', async () => {
|
||||
it('installs agents when non-interactive --agents finds context incomplete', async () => {
|
||||
const io = makeIo();
|
||||
const runtime = vi.fn(async () => runtimeReady(tempDir));
|
||||
const context = vi.fn(async () => ({ status: 'skipped' as const, projectDir: tempDir }));
|
||||
const agents = vi.fn(async () => ({
|
||||
status: 'ready' as const,
|
||||
projectDir: tempDir,
|
||||
|
|
@ -1816,15 +1818,17 @@ describe('setup status', () => {
|
|||
},
|
||||
io.io,
|
||||
{
|
||||
runtime: async () => runtimeReady(tempDir),
|
||||
context: async () => ({ status: 'skipped', projectDir: tempDir }),
|
||||
runtime,
|
||||
context,
|
||||
agents,
|
||||
},
|
||||
),
|
||||
).resolves.toBe(1);
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(agents).not.toHaveBeenCalled();
|
||||
expect(io.stderr()).toContain('KTX context is not ready for agents.');
|
||||
expect(runtime).not.toHaveBeenCalled();
|
||||
expect(context).not.toHaveBeenCalled();
|
||||
expect(agents).toHaveBeenCalledTimes(1);
|
||||
expect(io.stderr()).not.toContain('KTX context is not ready for agents.');
|
||||
});
|
||||
|
||||
it('routes a ready project menu selection to agent setup', async () => {
|
||||
|
|
@ -1945,7 +1949,7 @@ describe('setup status', () => {
|
|||
}
|
||||
}
|
||||
|
||||
expect(calls).toEqual(['runtime', 'agents']);
|
||||
expect(calls).toEqual(['agents']);
|
||||
});
|
||||
|
||||
it('skips to agent setup when context is ready but agents are not configured', async () => {
|
||||
|
|
@ -2042,10 +2046,10 @@ describe('setup status', () => {
|
|||
).resolves.toBe(0);
|
||||
|
||||
expect(readyMenuSelect).not.toHaveBeenCalled();
|
||||
expect(calls).toEqual(['runtime', 'agents']);
|
||||
expect(calls).toEqual(['agents']);
|
||||
});
|
||||
|
||||
it('runs only project resolution, runtime, context gate, and agent setup in --agents mode', async () => {
|
||||
it('runs only project resolution and agent setup in --agents mode', async () => {
|
||||
const io = makeIo();
|
||||
const runtime = vi.fn(async () => runtimeReady(tempDir));
|
||||
const context = vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir, runId: 'setup-context-local-test' }));
|
||||
|
|
@ -2086,8 +2090,8 @@ describe('setup status', () => {
|
|||
),
|
||||
).resolves.toBe(0);
|
||||
|
||||
expect(runtime).toHaveBeenCalledTimes(1);
|
||||
expect(context).toHaveBeenCalledTimes(1);
|
||||
expect(runtime).not.toHaveBeenCalled();
|
||||
expect(context).not.toHaveBeenCalled();
|
||||
expect(agents).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -339,7 +339,6 @@ export async function readKtxSetupStatus(
|
|||
}
|
||||
const agents = [...agentMap.values()];
|
||||
const runtimeRequirements = resolveProjectRuntimeRequirements(project.config, {
|
||||
agents: agents.length > 0,
|
||||
env: options.env ?? process.env,
|
||||
});
|
||||
let runtimeReady = runtimeRequirements.features.length === 0 || completedSteps.includes('runtime');
|
||||
|
|
@ -493,12 +492,6 @@ function shouldPrintConciseReadySummary(status: KtxSetupStatus): boolean {
|
|||
return setupStatusReady(status) && setupContextReady(status) && status.agents.some((agent) => agent.ready);
|
||||
}
|
||||
|
||||
function writeContextNotReadyForAgents(projectDir: string, io: KtxCliIo): void {
|
||||
io.stderr.write('KTX context is not ready for agents.\n\n');
|
||||
io.stderr.write(`Build context first:\n ktx setup --project-dir ${resolve(projectDir)}\n\n`);
|
||||
io.stderr.write(`Then install agent integration:\n ktx setup --agents --project-dir ${resolve(projectDir)}\n`);
|
||||
}
|
||||
|
||||
function setupRuntimeInstallPolicy(args: Extract<KtxSetupArgs, { command: 'run' }>): 'prompt' | 'auto' | 'never' {
|
||||
if (args.yes) {
|
||||
return 'auto';
|
||||
|
|
@ -587,18 +580,19 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
|
|||
}
|
||||
|
||||
const runOnly = readyAction;
|
||||
const agentOnlySetup = agentsRequested || runOnly === 'agents';
|
||||
const shouldRunModels = !runOnly || runOnly === 'models';
|
||||
const shouldRunEmbeddings = !runOnly || runOnly === 'embeddings';
|
||||
const shouldRunDatabases = !runOnly || runOnly === 'databases';
|
||||
const shouldRunSources = !runOnly || runOnly === 'sources';
|
||||
const shouldRunRuntime =
|
||||
agentsRequested || !runOnly || runOnly === 'runtime' || runOnly === 'context' || runOnly === 'agents';
|
||||
const shouldRunContext = agentsRequested || !runOnly || runOnly === 'context';
|
||||
!agentOnlySetup && (!runOnly || runOnly === 'runtime' || runOnly === 'context');
|
||||
const shouldRunContext = !agentOnlySetup && (!runOnly || runOnly === 'context');
|
||||
const shouldRunAgents = agentsRequested || !runOnly || runOnly === 'agents';
|
||||
const showPromptInstructions = projectResult.confirmedCreation !== true;
|
||||
|
||||
const setupSteps: KtxSetupFlowStep[] = agentsRequested
|
||||
? ['runtime', 'context']
|
||||
const setupSteps: KtxSetupFlowStep[] = agentOnlySetup
|
||||
? []
|
||||
: ['models', 'embeddings', 'databases', 'sources', 'runtime', 'context'];
|
||||
if (shouldRunAgents && args.skipAgents !== true) {
|
||||
setupSteps.push('agents');
|
||||
|
|
@ -737,7 +731,6 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
|
|||
inputMode: args.inputMode,
|
||||
cliVersion: args.cliVersion,
|
||||
runtimeInstallPolicy: setupRuntimeInstallPolicy(args),
|
||||
agents: shouldRunAgents && args.skipAgents !== true,
|
||||
},
|
||||
io,
|
||||
);
|
||||
|
|
@ -799,10 +792,6 @@ async function runKtxSetupInner(args: KtxSetupArgs, io: KtxCliIo, deps: KtxSetup
|
|||
}
|
||||
if (step === 'context' && stepResult.status !== 'ready') {
|
||||
if (shouldRunAgents && args.skipAgents !== true) {
|
||||
if (agentsRequested) {
|
||||
writeContextNotReadyForAgents(projectResult.projectDir, io);
|
||||
return args.inputMode === 'disabled' ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue