Skip to content

Commit 6329427

Browse files
committed
feat(agent): add agent command group with session and state management
1 parent 39f9256 commit 6329427

31 files changed

Lines changed: 2230 additions & 41 deletions

packages/cli/src/commands.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ import {
8383
pluginLink,
8484
pluginList,
8585
pluginRemove,
86+
agentInit,
87+
agentValidate,
88+
agentPlan,
89+
agentApply,
90+
agentDestroy,
91+
agentStateList,
92+
agentStateShow,
93+
agentStateRm,
94+
agentStateImport,
95+
agentSessionCreate,
96+
agentSessionList,
97+
agentSessionGet,
98+
agentSessionDelete,
99+
agentSessionRun,
100+
agentSessionSend,
101+
agentSessionEvents,
86102
} from "bailian-cli-commands";
87103

88104
// Full bailian-cli product: every command, exposed under the `bl` binary.
@@ -174,4 +190,20 @@ export const commands: Record<string, AnyCommand> = {
174190
"plugin link": pluginLink,
175191
"plugin list": pluginList,
176192
"plugin remove": pluginRemove,
193+
"agent init": agentInit,
194+
"agent validate": agentValidate,
195+
"agent plan": agentPlan,
196+
"agent apply": agentApply,
197+
"agent destroy": agentDestroy,
198+
"agent state list": agentStateList,
199+
"agent state show": agentStateShow,
200+
"agent state rm": agentStateRm,
201+
"agent state import": agentStateImport,
202+
"agent session create": agentSessionCreate,
203+
"agent session list": agentSessionList,
204+
"agent session get": agentSessionGet,
205+
"agent session delete": agentSessionDelete,
206+
"agent session run": agentSessionRun,
207+
"agent session send": agentSessionSend,
208+
"agent session events": agentSessionEvents,
177209
};

packages/commands/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"check": "vp check"
4141
},
4242
"dependencies": {
43+
"@openagentpack/sdk": "0.1.0",
4344
"bailian-cli-core": "workspace:*",
4445
"bailian-cli-runtime": "workspace:*",
4546
"boxen": "catalog:",
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { ResourceAddress } from "@openagentpack/sdk";
2+
3+
/** Full state address: provider.type.name */
4+
export function formatResourceAddress(address: ResourceAddress): string {
5+
return `${address.provider}.${address.type}.${address.name}`;
6+
}
7+
8+
/** CLI display short label: type.name (provider) */
9+
export function formatResourceLabel(address: ResourceAddress): string {
10+
return `${address.type}.${address.name} (${address.provider})`;
11+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {
2+
createProjectRuntime,
3+
type ProjectRuntimeContext,
4+
resolveProjectConfig,
5+
UserError,
6+
} from "@openagentpack/sdk";
7+
import { ensureCredentials } from "./credentials.ts";
8+
import { loadFileState } from "./file-state-manager.ts";
9+
10+
/**
11+
* Build a full ProjectRuntimeContext from a config file path — the standard
12+
* entry point for agent commands that need the SDK engine. Mirrors OpenAgentPack
13+
* CLI's buildCliRuntime: resolve config → load local state → assemble runtime.
14+
*/
15+
export async function buildAgentRuntime(
16+
filePath: string,
17+
options: { resolveEnv?: boolean; projectName?: string; statePath?: string } = {},
18+
): Promise<ProjectRuntimeContext & { configPath: string }> {
19+
ensureCredentials();
20+
const { config, configPath, projectName } = await resolveProjectConfig(filePath, options);
21+
const state = await loadFileState(configPath, options.statePath, projectName);
22+
const ctx = createProjectRuntime({
23+
projectName,
24+
config,
25+
state,
26+
configPath,
27+
providers: config.providers,
28+
});
29+
return { ...ctx, configPath };
30+
}
31+
32+
/** Ensure a user-supplied --provider value is actually configured in agents.yaml. */
33+
export function assertProviderConfigured(
34+
ctx: ProjectRuntimeContext,
35+
provider: string | undefined,
36+
): void {
37+
if (!provider || provider === "all") return;
38+
if (ctx.providers.has(provider)) return;
39+
const available = Array.from(ctx.providers.keys()).join(", ") || "none";
40+
throw new UserError(
41+
`Provider '${provider}' is not configured. Available providers: ${available}.`,
42+
);
43+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Redirect `console.log` / `console.info` to stderr while `fn` runs.
3+
*
4+
* The OpenAgentPack SDK's provider adapters emit progress/debug logging via
5+
* `console.log` (e.g. `[skill-upload]`), which would corrupt bl's stdout data
6+
* channel in `--output json` mode. Wrapping SDK calls that may log keeps stdout
7+
* a clean data channel. Restores the originals on completion.
8+
*/
9+
export async function withStdoutProtected<T>(fn: () => Promise<T>): Promise<T> {
10+
const originalLog = console.log;
11+
const originalInfo = console.info;
12+
const toStderr = (...args: unknown[]): void => {
13+
process.stderr.write(`${args.map((arg) => String(arg)).join(" ")}\n`);
14+
};
15+
console.log = toStderr;
16+
console.info = toStderr;
17+
try {
18+
return await fn();
19+
} finally {
20+
console.log = originalLog;
21+
console.info = originalInfo;
22+
}
23+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { bootstrapRuntimeCredentialsSync } from "@openagentpack/sdk";
2+
3+
let bootstrapped = false;
4+
5+
/**
6+
* Lazily load `.env` and `~/.agents/config.json` into `process.env` so the
7+
* OpenAgentPack SDK can resolve provider credentials (e.g. DASHSCOPE_API_KEY,
8+
* BAILIAN_WORKSPACE_ID). Safe to call repeatedly — only the first call does I/O.
9+
*
10+
* NOTE: agent commands declare `auth: "none"` and let the SDK own credential
11+
* resolution. This is the documented tradeoff of the wholesale SDK integration;
12+
* bl's own `--api-key` / `bl auth login` flow is bridged separately as a follow-up.
13+
*/
14+
export function ensureCredentials(): void {
15+
if (bootstrapped) return;
16+
bootstrapped = true;
17+
bootstrapRuntimeCredentialsSync();
18+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { UserError } from "@openagentpack/sdk";
2+
import { BailianError, ExitCode } from "bailian-cli-core";
3+
4+
/**
5+
* Run an SDK-backed operation, translating SDK error types into BailianError so
6+
* bl's error handler produces the right exit code and hint formatting.
7+
* SDK `UserError` → USAGE/GENERAL; any other Error → GENERAL (message passed
8+
* through, per bl's "don't translate server errors" boundary).
9+
*/
10+
export async function withAgentErrors<T>(fn: () => Promise<T>): Promise<T> {
11+
try {
12+
return await fn();
13+
} catch (error) {
14+
if (error instanceof BailianError) throw error;
15+
if (error instanceof UserError) throw new BailianError(error.message, ExitCode.USAGE);
16+
if (error instanceof Error) throw new BailianError(error.message, ExitCode.GENERAL);
17+
throw error;
18+
}
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { RuntimeFeedbackEvent } from "@openagentpack/sdk";
2+
3+
/**
4+
* Render SDK runtime feedback to stderr, keeping stdout a clean data channel.
5+
* Used as the `onFeedback` sink for plan/apply so progress messages don't mix
6+
* with structured output.
7+
*/
8+
export function renderAgentFeedback(event: RuntimeFeedbackEvent): void {
9+
process.stderr.write(`${event.message}\n`);
10+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { basename, dirname, resolve } from "node:path";
2+
import {
3+
type IStateManager,
4+
LocalFileStateBackend,
5+
StateManager,
6+
type StateScope,
7+
} from "@openagentpack/sdk";
8+
9+
function createStateScope(configPath: string, projectName?: string): StateScope {
10+
const resolved = resolve(configPath);
11+
return { projectId: projectName ?? basename(dirname(resolved)) };
12+
}
13+
14+
/** Load or initialize a file-based StateManager (mirrors OpenAgentPack CLI). */
15+
export async function loadFileState(
16+
configPath: string,
17+
statePath?: string,
18+
projectName?: string,
19+
): Promise<IStateManager> {
20+
const resolved = resolve(configPath);
21+
const backend = new LocalFileStateBackend({ configPath: resolved, statePath });
22+
const path = backend.getStatePath(createStateScope(resolved, projectName));
23+
return StateManager.load(path);
24+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export interface PagedResult<T> {
2+
items: T[];
3+
hasMore: boolean;
4+
nextPage?: string;
5+
}
6+
7+
/** Fetch the first page, then follow cursors while `all` is true. */
8+
export async function fetchAllPages<T>(
9+
fetchPage: (page?: string) => Promise<PagedResult<T>>,
10+
all?: boolean,
11+
): Promise<PagedResult<T>> {
12+
const first = await fetchPage();
13+
const items = [...first.items];
14+
let hasMore = first.hasMore;
15+
let nextPage = first.nextPage;
16+
17+
while (all && nextPage) {
18+
const next = await fetchPage(nextPage);
19+
items.push(...next.items);
20+
hasMore = next.hasMore;
21+
nextPage = next.nextPage;
22+
}
23+
24+
return { items, hasMore, nextPage };
25+
}

0 commit comments

Comments
 (0)