From 6ae128db361b1afa3fc0982e34c6bdbba39c10b1 Mon Sep 17 00:00:00 2001 From: abeatrix Date: Wed, 18 Mar 2026 18:17:23 -0700 Subject: [PATCH] refactor: @clinebot/core/node for Node runtime helpers and session backend imports `@clinebot/core/node` (instead of `@clinebot/core/server`) for Node runtime helpers and session backend imports, keeping workspace boundary guidance accurate after the package path change. --- sdk/ARCHITECTURE.md | 8 +- sdk/DOC.md | 4 +- sdk/README.md | 10 +- sdk/apps/cli/README.md | 2 +- sdk/apps/cli/package.json | 1 + sdk/apps/cli/src/commands/auth.test.ts | 2 +- sdk/apps/cli/src/commands/auth.ts | 110 +++--- sdk/apps/cli/src/commands/list.ts | 2 +- sdk/apps/cli/src/commands/rpc-runtime.ts | 5 +- .../src/commands/rpc-runtime/event-bridge.ts | 6 +- .../commands/rpc-runtime/provider-actions.ts | 42 ++- .../commands/rpc-runtime/provider-oauth.ts | 118 ------- .../rpc-runtime/provider-registry.test.ts | 14 +- .../commands/rpc-runtime/session-helpers.ts | 5 +- sdk/apps/cli/src/commands/rpc.ts | 4 +- sdk/apps/cli/src/commands/schedule.test.ts | 2 +- sdk/apps/cli/src/commands/schedule.ts | 2 +- sdk/apps/cli/src/connectors/adapters/gchat.ts | 13 +- .../cli/src/connectors/adapters/telegram.ts | 13 +- .../cli/src/connectors/adapters/whatsapp.ts | 13 +- sdk/apps/cli/src/connectors/common.ts | 2 +- sdk/apps/cli/src/connectors/connector-host.ts | 2 +- sdk/apps/cli/src/connectors/runtime-turn.ts | 2 +- .../cli/src/connectors/session-runtime.ts | 4 +- .../cli/src/connectors/task-updates.test.ts | 132 +++++++ sdk/apps/cli/src/connectors/task-updates.ts | 206 +++++++++++ sdk/apps/cli/src/main.test.ts | 2 +- sdk/apps/cli/src/main.ts | 4 +- .../cli/src/runtime/interactive-config.ts | 2 +- .../cli/src/runtime/interactive-welcome.ts | 2 +- sdk/apps/cli/src/runtime/prompt.ts | 2 +- sdk/apps/cli/src/runtime/run-agent.ts | 2 +- sdk/apps/cli/src/runtime/run-interactive.ts | 2 +- sdk/apps/cli/src/utils/approval.ts | 2 +- sdk/apps/cli/src/utils/session.ts | 19 +- sdk/apps/cli/src/utils/types.ts | 2 +- sdk/apps/cli/tsconfig.json | 3 +- sdk/apps/code/components/utils.ts | 6 +- .../views/settings/provider-list-view.tsx | 2 +- sdk/apps/code/hooks/chat-session/constants.ts | 10 +- sdk/apps/code/package.json | 1 + sdk/apps/code/scripts/chat-runtime-bridge.ts | 2 +- sdk/apps/code/scripts/provider-oauth-login.ts | 57 +++- sdk/apps/code/scripts/provider-settings.ts | 43 ++- sdk/apps/code/scripts/routine-schedules.ts | 2 +- .../code/scripts/workspace-file-search.ts | 2 +- sdk/apps/code/tsconfig.dev.json | 3 +- sdk/apps/desktop/README.md | 2 +- sdk/apps/desktop/package.json | 1 + .../desktop/scripts/chat-runtime-bridge.ts | 2 +- sdk/apps/examples/cline-plugin/index.ts | 2 +- sdk/apps/examples/cline-sdk/01-minimal.ts | 2 +- .../examples/cline-sdk/02-custom-model.ts | 2 +- .../examples/cline-sdk/03-system-prompt.ts | 2 +- sdk/apps/examples/cline-sdk/04-tools.ts | 2 +- .../examples/cline-sdk/05-custom-tools.ts | 2 +- sdk/apps/examples/cline-sdk/06-hooks.ts | 2 +- sdk/apps/examples/cline-sdk/07-extensions.ts | 2 +- .../examples/cline-sdk/08-context-files.ts | 2 +- sdk/apps/examples/cline-sdk/09-sessions.ts | 2 +- .../examples/cline-sdk/10-spawn-agents.ts | 2 +- sdk/apps/examples/cline-sdk/11-teams.ts | 2 +- sdk/apps/examples/cline-sdk/12-rpc-client.ts | 2 +- .../examples/cline-sdk/13-custom-executors.ts | 2 +- .../examples/cline-sdk/14-full-control.ts | 2 +- sdk/apps/examples/cline-sdk/README.md | 12 +- sdk/apps/examples/cline-sdk/index.ts | 2 +- sdk/apps/examples/cline-sdk/package.json | 3 +- sdk/apps/vscode/src/extension.ts | 2 +- sdk/apps/vscode/tsconfig.json | 2 +- sdk/biome.json | 6 +- sdk/bun.lock | 15 +- sdk/packages/README.md | 2 +- sdk/packages/core/README.md | 12 +- sdk/packages/core/bun.mts | 20 +- sdk/packages/core/package.json | 15 - .../core/src/default-tools/definitions.ts | 2 +- .../core/src/default-tools/executors/index.ts | 2 +- sdk/packages/core/src/default-tools/index.ts | 2 +- sdk/packages/core/src/index.node.ts | 223 ++++++++++++ .../src/providers/local-provider-service.ts} | 192 ++++++++--- sdk/packages/core/src/server/index.ts | 321 ------------------ sdk/packages/core/tsconfig.build.json | 7 +- sdk/packages/rpc/README.md | 2 +- sdk/scripts/verify-packages.ts | 62 ++++ sdk/scripts/version.ts | 14 + 86 files changed, 1124 insertions(+), 723 deletions(-) delete mode 100644 sdk/apps/cli/src/commands/rpc-runtime/provider-oauth.ts create mode 100644 sdk/apps/cli/src/connectors/task-updates.test.ts create mode 100644 sdk/apps/cli/src/connectors/task-updates.ts rename sdk/{apps/cli/src/commands/rpc-runtime/provider-registry.ts => packages/core/src/providers/local-provider-service.ts} (73%) delete mode 100644 sdk/packages/core/src/server/index.ts diff --git a/sdk/ARCHITECTURE.md b/sdk/ARCHITECTURE.md index e360cd9022..e5d7b8711e 100644 --- a/sdk/ARCHITECTURE.md +++ b/sdk/ARCHITECTURE.md @@ -66,7 +66,7 @@ flowchart LR 1. Host uses `RpcCoreSessionService` (through `@clinebot/core`) for session persistence/control-plane calls. 2. `@clinebot/rpc` server handles session/task/event/approval RPCs and schedule/execution RPCs. 3. `@clinebot/rpc` embeds `@clinebot/scheduler` to trigger scheduled runtime turns with concurrency and timeout guards. -4. SQLite session backend is provided by `@clinebot/core/server` (`createSqliteRpcSessionBackend`). +4. SQLite session backend is provided by `@clinebot/core/node` (`createSqliteRpcSessionBackend`). ### Session persistence implementation (latest) @@ -109,7 +109,7 @@ flowchart LR Workspace boundary rule: - Use explicit Node runtime imports: `@clinebot/llms/node`, `@clinebot/agents/node`, `@clinebot/rpc/node`. -- Import core runtime services from `@clinebot/core/server/node` and shared contracts from `@clinebot/core/node`. +- Import core runtime services from `@clinebot/core/node` and shared contracts from `@clinebot/core`. ### Runtime composition @@ -383,7 +383,7 @@ Outside `@clinebot/agents` (in `@clinebot/core`): Boundary rule: - Frontend/browser modules: `@clinebot/llms/browser` -- Node runtime hosts (CLI/Tauri scripts): `@clinebot/llms/node`, `@clinebot/agents/node`, `@clinebot/core/node`, `@clinebot/core/server/node`, `@clinebot/rpc/node` +- Node runtime hosts (CLI/Tauri scripts): `@clinebot/llms/node`, `@clinebot/agents/node`, `@clinebot/core/node`, `@clinebot/rpc/node` ### Layer details @@ -409,7 +409,7 @@ RPC runtime (`apps/cli/src/commands/rpc.ts`, `apps/cli/src/commands/rpc-runtime. - hosts shared runtime handlers (`start/send/abort runtime session`) - publishes runtime chat events (`runtime.chat.text_delta`, `runtime.chat.tool_call_*`) -- owns stateful runtime/session lifecycle via `@clinebot/core/server` +- owns stateful runtime/session lifecycle via `@clinebot/core/node` CLI + Agents (`apps/cli/src/index.ts`): diff --git a/sdk/DOC.md b/sdk/DOC.md index 42a3ff1d2f..de3f6ee0a9 100644 --- a/sdk/DOC.md +++ b/sdk/DOC.md @@ -987,7 +987,7 @@ Each line is a newline-delimited JSON record with a `ts` timestamp prepended. ### Provider & Model Configuration -**Source:** `src/main.ts` → `runCli()`, `ProviderSettingsManager` from `@clinebot/core/server` +**Source:** `src/main.ts` → `runCli()`, `ProviderSettingsManager` from `@clinebot/core/node` #### Provider Selection @@ -1100,7 +1100,7 @@ Workflows are loaded from the `UserInstructionConfigWatcher` which watches `.cli ## Configuration Object (`Config`) -The `Config` interface is the internal runtime configuration assembled from parsed args and environment variables. It extends `CoreSessionConfig` from `@clinebot/core/server`: +The `Config` interface is the internal runtime configuration assembled from parsed args and environment variables. It extends `CoreSessionConfig` from `@clinebot/core/node`: ```typescript interface Config extends Omit { diff --git a/sdk/README.md b/sdk/README.md index 847fbbc1d2..15e4241851 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -149,11 +149,11 @@ Allowed cross-workspace imports: - `@clinebot/agents` - `@clinebot/rpc` - `@clinebot/core` -- `@clinebot/core/server` (intentional Node-runtime-only exception) +- `@clinebot/core/node` (intentional Node-runtime-only exception) Disallowed: -- all other deep imports like `@clinebot/llms/*`, `@clinebot/agents/*`, `@clinebot/core/*` (except `@clinebot/core/server`) +- all other deep imports like `@clinebot/llms/*`, `@clinebot/agents/*`, `@clinebot/core/*` (except `@clinebot/core/node`) Keep these boundaries in mind when adding imports — cross-boundary deep imports will cause build/type errors. @@ -365,7 +365,7 @@ Start with: Development notes: - `@clinebot/core` owns stateful runtime assembly, storage, provider settings, and default host tools; keep the stateless agent loop in `@clinebot/agents` -- Host-oriented Node helpers belong under `@clinebot/core/server` +- Host-oriented Node helpers belong under `@clinebot/core/node` - RPC session persistence backends, team persistence, plugin loading, OAuth refresh, and hook/config discovery all belong in core rather than app packages ### `apps/cli` (`@clinebot/cli`) @@ -376,7 +376,7 @@ Use this package to see how the SDK packages are composed in a real app: - argument parsing + runtime config (`apps/cli/src/index.ts`) - provider/model refresh (`@clinebot/llms`) -- runtime assembly/session management (`@clinebot/core/server`) +- runtime assembly/session management (`@clinebot/core/node`) - agent loop execution + tools + hooks (`@clinebot/agents`) - gRPC server mode (`clite rpc start`) (`@clinebot/rpc`) @@ -451,7 +451,7 @@ Minimal composition sketch: ```ts import { Agent, createBuiltinTools } from "@clinebot/agents" -import { DefaultRuntimeBuilder } from "@clinebot/core/server" +import { DefaultRuntimeBuilder } from "@clinebot/core/node" import { providers } from "@clinebot/llms" const catalog = await providers.getLiveModelsCatalog() diff --git a/sdk/apps/cli/README.md b/sdk/apps/cli/README.md index 35108b740f..20cfb5f190 100644 --- a/sdk/apps/cli/README.md +++ b/sdk/apps/cli/README.md @@ -44,7 +44,7 @@ bun cli/dist/index.js "your prompt" Dev runtime note: -- Distinct host ID resolution is handled by `@clinebot/core/server` `createSessionHost(...)`. +- Distinct host ID resolution is handled by `@clinebot/core/node` `createSessionHost(...)`. - When no explicit `distinctId` is provided, core persists a fallback ID at `/machine-id` (for example `~/.cline/data/sessions/machine-id`). ## Publishing diff --git a/sdk/apps/cli/package.json b/sdk/apps/cli/package.json index 346606ac82..b73ce6b61c 100644 --- a/sdk/apps/cli/package.json +++ b/sdk/apps/cli/package.json @@ -35,6 +35,7 @@ "@clinebot/agents": "workspace:*", "@clinebot/core": "workspace:*", "@clinebot/llms": "workspace:*", + "@clinebot/rpc": "workspace:*", "better-sqlite3": "^11.10.0", "chat": "^4.20.2", "ink": "^6.8.0", diff --git a/sdk/apps/cli/src/commands/auth.test.ts b/sdk/apps/cli/src/commands/auth.test.ts index cf661cba67..396e870250 100644 --- a/sdk/apps/cli/src/commands/auth.test.ts +++ b/sdk/apps/cli/src/commands/auth.test.ts @@ -1,4 +1,4 @@ -import type { ProviderSettingsManager } from "@clinebot/core/server"; +import type { ProviderSettingsManager } from "@clinebot/core/node"; import { describe, expect, it, vi } from "vitest"; import { saveOAuthProviderSettings } from "./auth"; diff --git a/sdk/apps/cli/src/commands/auth.ts b/sdk/apps/cli/src/commands/auth.ts index 7f3a420c8c..a3dcc8feb6 100644 --- a/sdk/apps/cli/src/commands/auth.ts +++ b/sdk/apps/cli/src/commands/auth.ts @@ -1,9 +1,11 @@ import { createInterface } from "node:readline"; import { createOAuthClientCallbacks, + ensureCustomProvidersLoaded, + listLocalProviders, type ProviderSettingsManager, -} from "@clinebot/core/server"; -import { models, providers } from "@clinebot/llms"; +} from "@clinebot/core/node"; +import { providers } from "@clinebot/llms"; import { Box, render, Text, useApp, useInput } from "ink"; import open from "open"; import React, { useEffect, useMemo, useState } from "react"; @@ -90,7 +92,7 @@ let cachedCoreOAuthApi: Promise | undefined; async function getCoreOAuthApi(): Promise { if (!cachedCoreOAuthApi) { - cachedCoreOAuthApi = import("@clinebot/core/server").then((module) => { + cachedCoreOAuthApi = import("@clinebot/core/node").then((module) => { const runtimeApi = module as Partial; if ( typeof runtimeApi.loginClineOAuth !== "function" || @@ -189,15 +191,27 @@ export function parseAuthCommandArgs(args: string[]): ParsedAuthCommandArgs { return parsed; } -function getSupportedProviderIds(): string[] { - return models.getProviderIds().sort((a, b) => a.localeCompare(b)); +async function loadProviderCatalog( + providerSettingsManager: ProviderSettingsManager, +): Promise> { + await ensureCustomProvidersLoaded(providerSettingsManager); + const catalog = await listLocalProviders(providerSettingsManager); + return catalog.providers + .map((provider) => ({ + id: provider.id.trim(), + name: provider.name.trim() || provider.id.trim(), + })) + .filter((provider) => provider.id.length > 0) + .sort((a, b) => a.id.localeCompare(b.id)); } -function ensureQuickSetupInputValid( +async function ensureQuickSetupInputValid( input: AuthQuickSetupInput, -): string | undefined { + providerSettingsManager: ProviderSettingsManager, +): Promise { const normalizedProvider = normalizeProviderId(input.provider); - if (!models.hasProvider(normalizedProvider)) { + const providerCatalog = await loadProviderCatalog(providerSettingsManager); + if (!providerCatalog.some((provider) => provider.id === normalizedProvider)) { return `invalid provider "${input.provider}"`; } if (!input.apikey.trim()) { @@ -379,12 +393,15 @@ async function runQuickAuthSetup(input: AuthCommandInput): Promise { const apikey = input.apikey?.trim() ?? ""; const modelid = input.modelid?.trim() ?? ""; const baseurl = input.baseurl?.trim(); - const validationError = ensureQuickSetupInputValid({ - provider: providerId, - apikey, - modelid, - baseurl, - }); + const validationError = await ensureQuickSetupInputValid( + { + provider: providerId, + apikey, + modelid, + baseurl, + }, + input.providerSettingsManager, + ); if (validationError) { input.io.writeErr(validationError); return 1; @@ -490,7 +507,13 @@ async function runInteractiveAuthTui(input: AuthCommandInput): Promise { ); return 1; } - const providerIds = getSupportedProviderIds(); + const providerCatalog = await loadProviderCatalog( + input.providerSettingsManager, + ); + const providerIds = providerCatalog.map((provider) => provider.id); + const providerLabels = providerCatalog.map( + (provider) => `${provider.name} (${provider.id})`, + ); const defaultProvider = normalizeProviderId( input.providerSettingsManager.getLastUsedProviderSettings()?.provider ?? @@ -722,35 +745,44 @@ async function runInteractiveAuthTui(input: AuthCommandInput): Promise { modelid: state.modelId, baseurl: state.baseUrl.trim() || undefined, }; - const validationError = ensureQuickSetupInputValid(payload); - if (validationError) { - setState((prev) => ({ ...prev, errorMessage: validationError })); - return; - } setState((prev) => ({ ...prev, busy: true, busyMessage: "Saving provider settings...", errorMessage: undefined, })); - try { - saveQuickAuthProviderSettings({ - providerSettingsManager: input.providerSettingsManager, - providerId: normalizeProviderId(payload.provider), - apikey: payload.apikey, - modelid: payload.modelid, - baseurl: payload.baseurl, - }); - finalize(0); - } catch (error) { - setState((prev) => ({ - ...prev, - busy: false, - busyMessage: undefined, - errorMessage: - error instanceof Error ? error.message : String(error), - })); - } + void ensureQuickSetupInputValid( + payload, + input.providerSettingsManager, + ).then((validationError) => { + if (validationError) { + setState((prev) => ({ + ...prev, + busy: false, + busyMessage: undefined, + errorMessage: validationError, + })); + return; + } + try { + saveQuickAuthProviderSettings({ + providerSettingsManager: input.providerSettingsManager, + providerId: normalizeProviderId(payload.provider), + apikey: payload.apikey, + modelid: payload.modelid, + baseurl: payload.baseurl, + }); + finalize(0); + } catch (error) { + setState((prev) => ({ + ...prev, + busy: false, + busyMessage: undefined, + errorMessage: + error instanceof Error ? error.message : String(error), + })); + } + }); return; } if (key.backspace || key.delete) { @@ -780,7 +812,7 @@ async function runInteractiveAuthTui(input: AuthCommandInput): Promise { }, [exit, props, state.exitCode, state.screen]); const menuItems = AUTH_MENU_ITEMS.map((item) => item.label); - const providerItems = providerIds; + const providerItems = providerLabels; const showBaseUrlInput = currentProvider === "openai" || currentProvider === "openai-native"; diff --git a/sdk/apps/cli/src/commands/list.ts b/sdk/apps/cli/src/commands/list.ts index 3c7f27dee5..dff9a73cd6 100644 --- a/sdk/apps/cli/src/commands/list.ts +++ b/sdk/apps/cli/src/commands/list.ts @@ -13,7 +13,7 @@ import { resolveWorkflowsConfigSearchPaths, type SkillConfig, type WorkflowConfig, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; import type { CliOutputMode } from "../utils/types"; type ListIo = { diff --git a/sdk/apps/cli/src/commands/rpc-runtime.ts b/sdk/apps/cli/src/commands/rpc-runtime.ts index 7c43f3e90e..cc6f6419ef 100644 --- a/sdk/apps/cli/src/commands/rpc-runtime.ts +++ b/sdk/apps/cli/src/commands/rpc-runtime.ts @@ -2,11 +2,10 @@ import { randomUUID } from "node:crypto"; import { CoreSessionService, DefaultSessionManager, - type RpcRuntimeHandlers, - RpcSessionClient, SqliteSessionStore, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; import type { providers as LlmsProviders } from "@clinebot/llms"; +import { type RpcRuntimeHandlers, RpcSessionClient } from "@clinebot/rpc"; import { createCliLoggerAdapter, flushCliLoggerAdapters, diff --git a/sdk/apps/cli/src/commands/rpc-runtime/event-bridge.ts b/sdk/apps/cli/src/commands/rpc-runtime/event-bridge.ts index 292a79481c..6bd095c040 100644 --- a/sdk/apps/cli/src/commands/rpc-runtime/event-bridge.ts +++ b/sdk/apps/cli/src/commands/rpc-runtime/event-bridge.ts @@ -8,10 +8,8 @@ import { RPC_TEAM_PROGRESS_EVENT_TYPE, type TeamProgressProjectionEvent, } from "@clinebot/core"; -import type { - DefaultSessionManager, - RpcSessionClient, -} from "@clinebot/core/server"; +import type { DefaultSessionManager } from "@clinebot/core/node"; +import type { RpcSessionClient } from "@clinebot/rpc"; export function createRpcToolApprovalRequester(input: { eventClient: RpcSessionClient; diff --git a/sdk/apps/cli/src/commands/rpc-runtime/provider-actions.ts b/sdk/apps/cli/src/commands/rpc-runtime/provider-actions.ts index 087c35f33d..86bdc1e46b 100644 --- a/sdk/apps/cli/src/commands/rpc-runtime/provider-actions.ts +++ b/sdk/apps/cli/src/commands/rpc-runtime/provider-actions.ts @@ -4,23 +4,19 @@ import type { RpcProviderActionRequest, } from "@clinebot/core"; import { + addLocalProvider, ClineAccountService, - executeRpcClineAccountAction, - ProviderSettingsManager, -} from "@clinebot/core/server"; -import { - loginProvider, - normalizeOAuthProvider, - resolveClineAuthToken, - saveProviderOAuthCredentials, -} from "./provider-oauth"; -import { - addProvider, ensureCustomProvidersLoaded, - getProviderModels, - listProviders, - saveProviderSettings, -} from "./provider-registry"; + executeRpcClineAccountAction, + getLocalProviderModels, + listLocalProviders, + loginLocalProvider, + normalizeOAuthProvider, + ProviderSettingsManager, + resolveLocalClineAuthToken, + saveLocalProviderOAuthCredentials, + saveLocalProviderSettings, +} from "@clinebot/core/node"; export async function runProviderAction( request: RpcProviderActionRequest, @@ -33,7 +29,7 @@ export async function runProviderAction( const settings = manager.getProviderSettings("cline"); const accountService = new ClineAccountService({ apiBaseUrl: settings?.baseUrl?.trim() || "https://api.cline.bot", - getAuthToken: async () => resolveClineAuthToken(settings), + getAuthToken: async () => resolveLocalClineAuthToken(settings), }); return { result: await executeRpcClineAccountAction( @@ -43,16 +39,16 @@ export async function runProviderAction( }; } if (parsed.action === "listProviders") { - return { result: await listProviders(manager) }; + return { result: await listLocalProviders(manager) }; } if (parsed.action === "getProviderModels") { - return { result: await getProviderModels(parsed.providerId) }; + return { result: await getLocalProviderModels(parsed.providerId) }; } if (parsed.action === "addProvider") { - return { result: await addProvider(manager, parsed) }; + return { result: await addLocalProvider(manager, parsed) }; } if (parsed.action === "saveProviderSettings") { - return { result: saveProviderSettings(manager, parsed) }; + return { result: saveLocalProviderSettings(manager, parsed) }; } throw new Error(`unsupported provider action: ${String(parsed)}`); } @@ -63,8 +59,10 @@ export async function runProviderOAuthLogin( const providerId = normalizeOAuthProvider(provider); const manager = new ProviderSettingsManager(); const existing = manager.getProviderSettings(providerId); - const credentials = await loginProvider(providerId, existing); - const saved = saveProviderOAuthCredentials( + const credentials = await loginLocalProvider(providerId, existing, (url) => { + throw new Error(`RPC OAuth login cannot open browser directly: ${url}`); + }); + const saved = saveLocalProviderOAuthCredentials( manager, providerId, existing, diff --git a/sdk/apps/cli/src/commands/rpc-runtime/provider-oauth.ts b/sdk/apps/cli/src/commands/rpc-runtime/provider-oauth.ts deleted file mode 100644 index 8544d5f35a..0000000000 --- a/sdk/apps/cli/src/commands/rpc-runtime/provider-oauth.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { spawn } from "node:child_process"; -import type { RpcOAuthProviderId } from "@clinebot/core"; -import { - createOAuthClientCallbacks, - loginClineOAuth, - loginOcaOAuth, - loginOpenAICodex, - type ProviderSettingsManager, -} from "@clinebot/core/server"; -import type { providers as LlmsProviders } from "@clinebot/llms"; - -export function normalizeOAuthProvider(provider: string): RpcOAuthProviderId { - const normalized = provider.trim().toLowerCase(); - if (normalized === "codex" || normalized === "openai-codex") { - return "openai-codex"; - } - if (normalized === "cline" || normalized === "oca") { - return normalized; - } - throw new Error( - `provider "${provider}" does not support OAuth login (supported: cline, oca, openai-codex)`, - ); -} - -function openUrl(url: string): void { - const platform = process.platform; - const command = - platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open"; - const args = - platform === "darwin" - ? [url] - : platform === "win32" - ? ["/c", "start", "", url] - : [url]; - const child = spawn(command, args, { - stdio: "ignore", - detached: true, - }); - child.unref(); -} - -function toProviderApiKey( - providerId: RpcOAuthProviderId, - credentials: { access: string }, -): string { - if (providerId === "cline") { - return `workos:${credentials.access}`; - } - return credentials.access; -} - -export async function loginProvider( - providerId: RpcOAuthProviderId, - existing: LlmsProviders.ProviderSettings | undefined, -): Promise<{ - access: string; - refresh: string; - expires: number; - accountId?: string; -}> { - const callbacks = createOAuthClientCallbacks({ - onPrompt: async (prompt) => prompt.defaultValue ?? "", - openUrl: (url) => openUrl(url), - onOpenUrlError: ({ error }) => { - throw error instanceof Error ? error : new Error(String(error)); - }, - }); - - if (providerId === "cline") { - return loginClineOAuth({ - apiBaseUrl: existing?.baseUrl?.trim() || "https://api.cline.bot", - callbacks, - }); - } - if (providerId === "oca") { - return loginOcaOAuth({ - mode: existing?.oca?.mode, - callbacks, - }); - } - return loginOpenAICodex(callbacks); -} - -export function saveProviderOAuthCredentials( - manager: ProviderSettingsManager, - providerId: RpcOAuthProviderId, - existing: LlmsProviders.ProviderSettings | undefined, - credentials: { - access: string; - refresh: string; - expires: number; - accountId?: string; - }, -): LlmsProviders.ProviderSettings { - const auth = { - ...(existing?.auth ?? {}), - accessToken: toProviderApiKey(providerId, credentials), - refreshToken: credentials.refresh, - accountId: credentials.accountId, - } as LlmsProviders.ProviderSettings["auth"] & { expiresAt?: number }; - auth.expiresAt = credentials.expires; - const merged: LlmsProviders.ProviderSettings = { - ...(existing ?? { - provider: providerId as LlmsProviders.ProviderSettings["provider"], - }), - provider: providerId as LlmsProviders.ProviderSettings["provider"], - auth, - }; - manager.saveProviderSettings(merged, { tokenSource: "oauth" }); - return merged; -} - -export function resolveClineAuthToken( - settings: LlmsProviders.ProviderSettings | undefined, -): string | undefined { - const token = settings?.auth?.accessToken?.trim() || settings?.apiKey?.trim(); - return token && token.length > 0 ? token : undefined; -} diff --git a/sdk/apps/cli/src/commands/rpc-runtime/provider-registry.test.ts b/sdk/apps/cli/src/commands/rpc-runtime/provider-registry.test.ts index 64dcd49571..acb49eca67 100644 --- a/sdk/apps/cli/src/commands/rpc-runtime/provider-registry.test.ts +++ b/sdk/apps/cli/src/commands/rpc-runtime/provider-registry.test.ts @@ -1,9 +1,11 @@ import type { RpcSaveProviderSettingsActionRequest } from "@clinebot/core"; -import type { ProviderSettingsManager } from "@clinebot/core/server"; +import { + type ProviderSettingsManager, + saveLocalProviderSettings, +} from "@clinebot/core/node"; import { describe, expect, it, vi } from "vitest"; -import { saveProviderSettings } from "./provider-registry"; -describe("saveProviderSettings", () => { +describe("saveLocalProviderSettings", () => { it("ignores null apiKey/baseUrl updates", () => { const save = vi.fn(); const manager = { @@ -20,7 +22,7 @@ describe("saveProviderSettings", () => { saveProviderSettings: save, }; - saveProviderSettings( + saveLocalProviderSettings( manager as unknown as ProviderSettingsManager, { action: "saveProviderSettings", @@ -57,7 +59,7 @@ describe("saveProviderSettings", () => { saveProviderSettings: save, }; - saveProviderSettings( + saveLocalProviderSettings( manager as unknown as ProviderSettingsManager, { action: "saveProviderSettings", @@ -96,7 +98,7 @@ describe("saveProviderSettings", () => { saveProviderSettings: save, }; - saveProviderSettings( + saveLocalProviderSettings( manager as unknown as ProviderSettingsManager, { action: "saveProviderSettings", diff --git a/sdk/apps/cli/src/commands/rpc-runtime/session-helpers.ts b/sdk/apps/cli/src/commands/rpc-runtime/session-helpers.ts index a0b1a8aed6..ceb540ca55 100644 --- a/sdk/apps/cli/src/commands/rpc-runtime/session-helpers.ts +++ b/sdk/apps/cli/src/commands/rpc-runtime/session-helpers.ts @@ -9,10 +9,7 @@ import type { RpcChatTurnResult, } from "@clinebot/core"; import { setHomeDir, setHomeDirIfUnset } from "@clinebot/core"; -import { - type DefaultSessionManager, - SessionSource, -} from "@clinebot/core/server"; +import { type DefaultSessionManager, SessionSource } from "@clinebot/core/node"; import type { providers as LlmsProviders } from "@clinebot/llms"; import { providers } from "@clinebot/llms"; import { createCliLoggerAdapter } from "../../logging/adapter"; diff --git a/sdk/apps/cli/src/commands/rpc.ts b/sdk/apps/cli/src/commands/rpc.ts index f7c17d3ede..63a46bb4e0 100644 --- a/sdk/apps/cli/src/commands/rpc.ts +++ b/sdk/apps/cli/src/commands/rpc.ts @@ -1,15 +1,15 @@ import { spawn, spawnSync } from "node:child_process"; import { createServer } from "node:net"; import { isAbsolute, resolve as resolvePath } from "node:path"; +import { createSqliteRpcSessionBackend } from "@clinebot/core/node"; import { - createSqliteRpcSessionBackend, getRpcServerHealth, RpcSessionClient, registerRpcClient, requestRpcServerShutdown, startRpcServer, stopRpcServer, -} from "@clinebot/core/server"; +} from "@clinebot/rpc"; import { createCliLoggerAdapter } from "../logging/adapter"; import { createRpcRuntimeHandlers } from "./rpc-runtime"; diff --git a/sdk/apps/cli/src/commands/schedule.test.ts b/sdk/apps/cli/src/commands/schedule.test.ts index a0f99c2b34..ee2c3bb7d4 100644 --- a/sdk/apps/cli/src/commands/schedule.test.ts +++ b/sdk/apps/cli/src/commands/schedule.test.ts @@ -5,7 +5,7 @@ const mockListSchedules = vi.hoisted(() => vi.fn()); const mockClientClose = vi.hoisted(() => vi.fn()); const mockGetRpcServerHealth = vi.hoisted(() => vi.fn()); -vi.mock("@clinebot/core/server", () => ({ +vi.mock("@clinebot/rpc", () => ({ getRpcServerHealth: mockGetRpcServerHealth, RpcSessionClient: class { async listSchedules(input: unknown) { diff --git a/sdk/apps/cli/src/commands/schedule.ts b/sdk/apps/cli/src/commands/schedule.ts index 544c207b11..51b243fd9e 100644 --- a/sdk/apps/cli/src/commands/schedule.ts +++ b/sdk/apps/cli/src/commands/schedule.ts @@ -1,5 +1,5 @@ import { readFile } from "node:fs/promises"; -import { getRpcServerHealth, RpcSessionClient } from "@clinebot/core/server"; +import { getRpcServerHealth, RpcSessionClient } from "@clinebot/rpc"; import { runRpcEnsureCommand } from "./rpc"; interface CommandIo { diff --git a/sdk/apps/cli/src/connectors/adapters/gchat.ts b/sdk/apps/cli/src/connectors/adapters/gchat.ts index 5773d7db12..528fe6ecbd 100644 --- a/sdk/apps/cli/src/connectors/adapters/gchat.ts +++ b/sdk/apps/cli/src/connectors/adapters/gchat.ts @@ -3,7 +3,7 @@ import { join } from "node:path"; import { createGoogleChatAdapter } from "@chat-adapter/gchat"; import type { RpcChatStartSessionRequest } from "@clinebot/core"; import { resolveClineDataDir } from "@clinebot/core"; -import { RpcSessionClient, registerRpcClient } from "@clinebot/core/server"; +import { RpcSessionClient, registerRpcClient } from "@clinebot/rpc"; import { Chat, ConsoleLogger, type Thread } from "chat"; import { ensureRpcRuntimeAddress } from "../../commands/rpc"; import type { CliLoggerAdapter } from "../../logging/adapter"; @@ -39,6 +39,7 @@ import { readSessionReplyText, stopConnectorSessions, } from "../session-runtime"; +import { startConnectorTaskUpdateRelay } from "../task-updates"; import { type ConnectorBindingStore, type ConnectorThreadState, @@ -648,6 +649,15 @@ async function runConnectGoogleChatCommand( }); await bot.initialize(); + const stopTaskUpdateStream = + startConnectorTaskUpdateRelay({ + client, + clientId, + bot, + logger: loggerAdapter, + bindingsPath, + transport: "gchat", + }); const server = await startConnectorWebhookServer({ host: options.host, @@ -726,6 +736,7 @@ async function runConnectGoogleChatCommand( io.writeln(`[gchat] configure Google Chat App URL: ${endpointUrl}`); await stopPromise; + stopTaskUpdateStream(); stopEventStream(); await server.close(); client.close(); diff --git a/sdk/apps/cli/src/connectors/adapters/telegram.ts b/sdk/apps/cli/src/connectors/adapters/telegram.ts index 8ccaab2489..248ea35253 100644 --- a/sdk/apps/cli/src/connectors/adapters/telegram.ts +++ b/sdk/apps/cli/src/connectors/adapters/telegram.ts @@ -3,7 +3,7 @@ import { join } from "node:path"; import { createTelegramAdapter } from "@chat-adapter/telegram"; import type { RpcChatStartSessionRequest } from "@clinebot/core"; import { resolveClineDataDir } from "@clinebot/core"; -import { RpcSessionClient, registerRpcClient } from "@clinebot/core/server"; +import { RpcSessionClient, registerRpcClient } from "@clinebot/rpc"; import { Chat, ConsoleLogger, type Thread } from "chat"; import { ensureRpcRuntimeAddress } from "../../commands/rpc"; import type { CliLoggerAdapter } from "../../logging/adapter"; @@ -38,6 +38,7 @@ import { readSessionReplyText, stopConnectorSessions, } from "../session-runtime"; +import { startConnectorTaskUpdateRelay } from "../task-updates"; import { type ConnectorBindingStore, type ConnectorThreadState, @@ -701,6 +702,15 @@ async function runConnectTelegramCommand( }); await bot.initialize(); + const stopTaskUpdateStream = + startConnectorTaskUpdateRelay({ + client, + clientId, + bot, + logger: loggerAdapter, + bindingsPath, + transport: "telegram", + }); const stopEventStream = client.streamEvents( { clientId: `${clientId}-server-events` }, @@ -796,6 +806,7 @@ async function runConnectTelegramCommand( await stopPromise; + stopTaskUpdateStream(); stopEventStream(); await dispatchConnectorHook( options.hookCommand, diff --git a/sdk/apps/cli/src/connectors/adapters/whatsapp.ts b/sdk/apps/cli/src/connectors/adapters/whatsapp.ts index d5b7ca072e..b248aa9e61 100644 --- a/sdk/apps/cli/src/connectors/adapters/whatsapp.ts +++ b/sdk/apps/cli/src/connectors/adapters/whatsapp.ts @@ -3,7 +3,7 @@ import { join } from "node:path"; import { createWhatsAppAdapter } from "@chat-adapter/whatsapp"; import type { RpcChatStartSessionRequest } from "@clinebot/core"; import { resolveClineDataDir } from "@clinebot/core"; -import { RpcSessionClient, registerRpcClient } from "@clinebot/core/server"; +import { RpcSessionClient, registerRpcClient } from "@clinebot/rpc"; import { Chat, ConsoleLogger, type Thread } from "chat"; import { ensureRpcRuntimeAddress } from "../../commands/rpc"; import type { CliLoggerAdapter } from "../../logging/adapter"; @@ -39,6 +39,7 @@ import { readSessionReplyText, stopConnectorSessions, } from "../session-runtime"; +import { startConnectorTaskUpdateRelay } from "../task-updates"; import { type ConnectorBindingStore, type ConnectorThreadState, @@ -671,6 +672,15 @@ async function runConnectWhatsAppCommand( }); await bot.initialize(); + const stopTaskUpdateStream = + startConnectorTaskUpdateRelay({ + client, + clientId, + bot, + logger: loggerAdapter, + bindingsPath, + transport: "whatsapp", + }); const endpointUrl = `${options.baseUrl.replace(/\/$/, "")}/api/webhooks/whatsapp`; const server = await startConnectorWebhookServer({ @@ -751,6 +761,7 @@ async function runConnectWhatsAppCommand( io.writeln(`[whatsapp] configure WhatsApp webhook URL: ${endpointUrl}`); await stopPromise; + stopTaskUpdateStream(); stopEventStream(); await server.close(); client.close(); diff --git a/sdk/apps/cli/src/connectors/common.ts b/sdk/apps/cli/src/connectors/common.ts index b22ff8c68d..16cccd2da3 100644 --- a/sdk/apps/cli/src/connectors/common.ts +++ b/sdk/apps/cli/src/connectors/common.ts @@ -2,7 +2,7 @@ import { spawn } from "node:child_process"; import { existsSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { ensureParentDir } from "@clinebot/core"; -import type { RpcSessionClient, RpcSessionRow } from "@clinebot/core/server"; +import type { RpcSessionClient, RpcSessionRow } from "@clinebot/rpc"; export function parseBooleanFlag(rawArgs: string[], flag: string): boolean { return rawArgs.includes(flag); diff --git a/sdk/apps/cli/src/connectors/connector-host.ts b/sdk/apps/cli/src/connectors/connector-host.ts index 351ec3158c..7404b3be98 100644 --- a/sdk/apps/cli/src/connectors/connector-host.ts +++ b/sdk/apps/cli/src/connectors/connector-host.ts @@ -2,7 +2,7 @@ import type { RpcChatRunTurnRequest, RpcChatStartSessionRequest, } from "@clinebot/core"; -import type { RpcSessionClient } from "@clinebot/core/server"; +import type { RpcSessionClient } from "@clinebot/rpc"; import type { Thread } from "chat"; import type { CliLoggerAdapter } from "../logging/adapter"; import { resolveSystemPrompt } from "../runtime/prompt"; diff --git a/sdk/apps/cli/src/connectors/runtime-turn.ts b/sdk/apps/cli/src/connectors/runtime-turn.ts index 8666ceef41..8c8b602231 100644 --- a/sdk/apps/cli/src/connectors/runtime-turn.ts +++ b/sdk/apps/cli/src/connectors/runtime-turn.ts @@ -1,5 +1,5 @@ import type { RpcChatRunTurnRequest } from "@clinebot/core"; -import type { RpcSessionClient } from "@clinebot/core/server"; +import type { RpcSessionClient } from "@clinebot/rpc"; import type { CliLoggerAdapter } from "../logging/adapter"; import { formatToolInput } from "../utils/helpers"; diff --git a/sdk/apps/cli/src/connectors/session-runtime.ts b/sdk/apps/cli/src/connectors/session-runtime.ts index 44832ee55b..39ebc78d53 100644 --- a/sdk/apps/cli/src/connectors/session-runtime.ts +++ b/sdk/apps/cli/src/connectors/session-runtime.ts @@ -5,9 +5,9 @@ import type { import { CoreSessionService, ProviderSettingsManager, - RpcSessionClient, SqliteSessionStore, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; +import { RpcSessionClient } from "@clinebot/rpc"; import type { Thread } from "chat"; import { ensureOAuthProviderApiKey, diff --git a/sdk/apps/cli/src/connectors/task-updates.test.ts b/sdk/apps/cli/src/connectors/task-updates.test.ts new file mode 100644 index 0000000000..72184b2272 --- /dev/null +++ b/sdk/apps/cli/src/connectors/task-updates.test.ts @@ -0,0 +1,132 @@ +import type { TeamProgressProjectionEvent } from "@clinebot/shared"; +import { describe, expect, it } from "vitest"; +import { + createTaskUpdateFingerprint, + findBindingForSessionId, + formatConnectorTaskUpdate, +} from "./task-updates"; + +function createProjection( + overrides: Partial = {}, +): TeamProgressProjectionEvent { + return { + type: "team_progress_projection", + version: 1, + sessionId: "session-1", + summary: { + teamName: "telegram-team", + updatedAt: "2026-03-18T00:00:00.000Z", + members: { + total: 2, + byStatus: { idle: 0, running: 2, stopped: 0 }, + leadCount: 1, + teammateCount: 1, + }, + tasks: { + total: 3, + byStatus: { + pending: 0, + in_progress: 1, + blocked: 0, + completed: 2, + }, + blockedTaskIds: [], + readyTaskIds: [], + completionPct: 67, + }, + runs: { + total: 1, + byStatus: { + queued: 0, + running: 1, + completed: 0, + failed: 0, + cancelled: 0, + interrupted: 0, + }, + activeRunIds: ["run-1"], + latestRunId: "run-1", + }, + outcomes: { + total: 0, + byStatus: { draft: 0, in_review: 0, finalized: 0 }, + finalizedPct: 0, + missingRequiredSections: [], + }, + fragments: { + total: 0, + byStatus: { draft: 0, reviewed: 0, rejected: 0 }, + }, + }, + lastEvent: { + teamName: "telegram-team", + sessionId: "session-1", + eventType: "run_progress", + ts: "2026-03-18T00:00:00.000Z", + runId: "run-1", + taskId: "task-1", + agentId: "agent-1", + message: "Investigating failing test and patching the connector relay.", + ...overrides, + }, + }; +} + +describe("connector task updates", () => { + it("finds a binding by session id in either binding slot", () => { + const bindings = { + thread_a: { + channelId: "channel-a", + isDM: true, + serializedThread: "{}", + sessionId: "session-a", + updatedAt: "2026-03-18T00:00:00.000Z", + }, + thread_b: { + channelId: "channel-b", + isDM: false, + serializedThread: "{}", + state: { sessionId: "session-b" }, + updatedAt: "2026-03-18T00:00:00.000Z", + }, + }; + + expect(findBindingForSessionId(bindings, "session-a")?.threadId).toBe( + "thread_a", + ); + expect(findBindingForSessionId(bindings, "session-b")?.threadId).toBe( + "thread_b", + ); + }); + + it("formats run progress updates for chat delivery", () => { + expect(formatConnectorTaskUpdate(createProjection())).toBe( + [ + "[telegram-team] Task update", + "Investigating failing test and patching the connector relay.", + "1 run active | 1 task in progress | 2/3 tasks complete", + ].join("\n"), + ); + }); + + it("suppresses generic task update noise when nothing is in progress", () => { + const projection = createProjection({ + eventType: "team_task_updated", + message: undefined, + }); + projection.summary.tasks.byStatus.in_progress = 0; + + expect(formatConnectorTaskUpdate(projection)).toBeUndefined(); + }); + + it("changes the fingerprint when the progress payload changes", () => { + const first = createProjection(); + const second = createProjection({ + message: "Finished the patch and validating the CLI adapters now.", + }); + + expect(createTaskUpdateFingerprint(first)).not.toBe( + createTaskUpdateFingerprint(second), + ); + }); +}); diff --git a/sdk/apps/cli/src/connectors/task-updates.ts b/sdk/apps/cli/src/connectors/task-updates.ts new file mode 100644 index 0000000000..4f6d70a073 --- /dev/null +++ b/sdk/apps/cli/src/connectors/task-updates.ts @@ -0,0 +1,206 @@ +import type { RpcSessionClient } from "@clinebot/rpc"; +import type { TeamProgressProjectionEvent } from "@clinebot/shared"; +import type { Chat, Thread } from "chat"; +import type { CliLoggerAdapter } from "../logging/adapter"; +import { truncateConnectorText } from "./runtime-turn"; +import { + type ConnectorThreadBinding, + type ConnectorThreadState, + readBindings, +} from "./thread-bindings"; + +function formatCountLabel(input: { + count: number; + singular: string; + plural: string; +}): string { + return `${input.count} ${input.count === 1 ? input.singular : input.plural}`; +} + +function buildProgressSummary(event: TeamProgressProjectionEvent): string { + const activeRuns = event.summary.runs.byStatus.running; + const activeTasks = event.summary.tasks.byStatus.in_progress; + const blockedTasks = event.summary.tasks.byStatus.blocked; + const completedTasks = event.summary.tasks.byStatus.completed; + const totalTasks = event.summary.tasks.total; + const parts = [ + formatCountLabel({ + count: activeRuns, + singular: "run active", + plural: "runs active", + }), + formatCountLabel({ + count: activeTasks, + singular: "task in progress", + plural: "tasks in progress", + }), + ]; + if (blockedTasks > 0) { + parts.push( + formatCountLabel({ + count: blockedTasks, + singular: "blocked task", + plural: "blocked tasks", + }), + ); + } + if (totalTasks > 0) { + parts.push(`${completedTasks}/${totalTasks} tasks complete`); + } + return parts.join(" | "); +} + +export function formatConnectorTaskUpdate( + event: TeamProgressProjectionEvent, +): string | undefined { + const message = event.lastEvent.message + ? truncateConnectorText(event.lastEvent.message, 220) + : undefined; + const teamName = event.summary.teamName; + switch (event.lastEvent.eventType) { + case "run_started": + return [ + `[${teamName}] Task started`, + buildProgressSummary(event), + message, + ] + .filter(Boolean) + .join("\n"); + case "run_progress": + return [ + `[${teamName}] Task update`, + message ?? buildProgressSummary(event), + message ? buildProgressSummary(event) : undefined, + ] + .filter(Boolean) + .join("\n"); + case "run_completed": + return [`[${teamName}] Task completed`, buildProgressSummary(event)] + .filter(Boolean) + .join("\n"); + case "run_failed": + return [`[${teamName}] Task failed`, message, buildProgressSummary(event)] + .filter(Boolean) + .join("\n"); + case "run_cancelled": + return [`[${teamName}] Task cancelled`, buildProgressSummary(event)] + .filter(Boolean) + .join("\n"); + case "run_interrupted": + return [`[${teamName}] Task interrupted`, buildProgressSummary(event)] + .filter(Boolean) + .join("\n"); + case "team_task_updated": + if (event.summary.tasks.byStatus.in_progress <= 0) { + return undefined; + } + return [`[${teamName}] Task queue updated`, buildProgressSummary(event)] + .filter(Boolean) + .join("\n"); + default: + return undefined; + } +} + +export function findBindingForSessionId( + bindings: Record>, + sessionId: string, +): { threadId: string; binding: ConnectorThreadBinding } | undefined { + const trimmedSessionId = sessionId.trim(); + if (!trimmedSessionId) { + return undefined; + } + for (const [threadId, binding] of Object.entries(bindings)) { + if ( + binding.sessionId?.trim() === trimmedSessionId || + binding.state?.sessionId?.trim() === trimmedSessionId + ) { + return { threadId, binding }; + } + } + return undefined; +} + +export function createTaskUpdateFingerprint( + event: TeamProgressProjectionEvent, +): string { + return JSON.stringify({ + eventType: event.lastEvent.eventType, + runId: event.lastEvent.runId ?? "", + taskId: event.lastEvent.taskId ?? "", + message: event.lastEvent.message ?? "", + updatedAt: event.summary.updatedAt, + }); +} + +export function startConnectorTaskUpdateRelay< + TState extends ConnectorThreadState, +>(input: { + client: RpcSessionClient; + clientId: string; + bot: Chat; + logger: CliLoggerAdapter; + bindingsPath: string; + transport: string; +}): () => void { + const lastSentBySession = new Map(); + + const handleProjection = async ( + event: TeamProgressProjectionEvent, + ): Promise => { + const body = formatConnectorTaskUpdate(event); + if (!body) { + return; + } + const fingerprint = createTaskUpdateFingerprint(event); + if (lastSentBySession.get(event.sessionId) === fingerprint) { + return; + } + const match = findBindingForSessionId( + readBindings(input.bindingsPath), + event.sessionId, + ); + if (!match?.binding.serializedThread) { + return; + } + lastSentBySession.set(event.sessionId, fingerprint); + try { + const thread = JSON.parse( + match.binding.serializedThread, + input.bot.reviver(), + ) as Thread; + await thread.post(body); + input.logger.core.info?.("Connector task update sent", { + transport: input.transport, + threadId: match.threadId, + sessionId: event.sessionId, + eventType: event.lastEvent.eventType, + runId: event.lastEvent.runId, + taskId: event.lastEvent.taskId, + }); + } catch (error) { + input.logger.core.warn?.("Connector task update delivery failed", { + transport: input.transport, + threadId: match.threadId, + sessionId: event.sessionId, + eventType: event.lastEvent.eventType, + error, + }); + } + }; + + return input.client.streamTeamProgress( + { clientId: `${input.clientId}-task-updates` }, + { + onProjection: (event) => { + void handleProjection(event); + }, + onError: (error) => { + input.logger.core.warn?.("Connector task update stream failed", { + transport: input.transport, + error, + }); + }, + }, + ); +} diff --git a/sdk/apps/cli/src/main.test.ts b/sdk/apps/cli/src/main.test.ts index 94969b79f2..901eff6fb1 100644 --- a/sdk/apps/cli/src/main.test.ts +++ b/sdk/apps/cli/src/main.test.ts @@ -53,7 +53,7 @@ vi.mock("./runtime/run-interactive", () => { }; }); vi.mock("./utils/session", () => sessionMocks); -vi.mock("@clinebot/core/server", () => ({ +vi.mock("@clinebot/core/node", () => ({ createTeamName: vi.fn(() => "team-test"), createUserInstructionConfigWatcher: vi.fn(() => ({ start: vi.fn(async () => {}), diff --git a/sdk/apps/cli/src/main.ts b/sdk/apps/cli/src/main.ts index 98c40b00e0..d093dc9df0 100644 --- a/sdk/apps/cli/src/main.ts +++ b/sdk/apps/cli/src/main.ts @@ -41,13 +41,13 @@ function mergeToolPolicies( } async function createProviderSettingsManager() { - const { ProviderSettingsManager } = await import("@clinebot/core/server"); + const { ProviderSettingsManager } = await import("@clinebot/core/node"); return new ProviderSettingsManager(); } async function loadCliRuntimeModules() { const [coreServer, llms, prompt, runAgentModule] = await Promise.all([ - import("@clinebot/core/server"), + import("@clinebot/core/node"), import("@clinebot/llms"), import("./runtime/prompt"), import("./runtime/run-agent"), diff --git a/sdk/apps/cli/src/runtime/interactive-config.ts b/sdk/apps/cli/src/runtime/interactive-config.ts index 3097d8d04b..5fb7bf29cd 100644 --- a/sdk/apps/cli/src/runtime/interactive-config.ts +++ b/sdk/apps/cli/src/runtime/interactive-config.ts @@ -3,7 +3,7 @@ import { createAgentConfigWatcher, listHookConfigFiles, type UserInstructionConfigWatcher, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; export type InteractiveConfigTab = | "workflows" diff --git a/sdk/apps/cli/src/runtime/interactive-welcome.ts b/sdk/apps/cli/src/runtime/interactive-welcome.ts index c19a64c326..571b5a36e1 100644 --- a/sdk/apps/cli/src/runtime/interactive-welcome.ts +++ b/sdk/apps/cli/src/runtime/interactive-welcome.ts @@ -3,7 +3,7 @@ import { getFileIndex, listAvailableWorkflowsFromWatcher, type UserInstructionConfigWatcher, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; import type { providers } from "@clinebot/llms"; import { formatCreditBalance, normalizeCreditBalance } from "../utils/output"; import type { Config } from "../utils/types"; diff --git a/sdk/apps/cli/src/runtime/prompt.ts b/sdk/apps/cli/src/runtime/prompt.ts index 1cea4aa4cd..3a817e09e3 100644 --- a/sdk/apps/cli/src/runtime/prompt.ts +++ b/sdk/apps/cli/src/runtime/prompt.ts @@ -3,7 +3,7 @@ import { buildWorkspaceMetadata, resolveWorkflowSlashCommandFromWatcher, type UserInstructionConfigWatcher, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; const WORKSPACE_CONFIGURATION_MARKER = "# Workspace Configuration"; diff --git a/sdk/apps/cli/src/runtime/run-agent.ts b/sdk/apps/cli/src/runtime/run-agent.ts index 5118a87938..9cc12fdaf9 100644 --- a/sdk/apps/cli/src/runtime/run-agent.ts +++ b/sdk/apps/cli/src/runtime/run-agent.ts @@ -3,7 +3,7 @@ import { prewarmFileIndex, SessionSource, type UserInstructionConfigWatcher, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; import type { providers } from "@clinebot/llms"; import { askQuestionInTerminal, requestToolApproval } from "../utils/approval"; import { handleEvent, handleTeamEvent } from "../utils/events"; diff --git a/sdk/apps/cli/src/runtime/run-interactive.ts b/sdk/apps/cli/src/runtime/run-interactive.ts index cf6b1a3301..9ff6e4d984 100644 --- a/sdk/apps/cli/src/runtime/run-interactive.ts +++ b/sdk/apps/cli/src/runtime/run-interactive.ts @@ -4,7 +4,7 @@ import { prewarmFileIndex, SessionSource, type UserInstructionConfigWatcher, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; import type { providers } from "@clinebot/llms"; import { render } from "ink"; import React from "react"; diff --git a/sdk/apps/cli/src/utils/approval.ts b/sdk/apps/cli/src/utils/approval.ts index 8fceeabd45..f6b3fb2562 100644 --- a/sdk/apps/cli/src/utils/approval.ts +++ b/sdk/apps/cli/src/utils/approval.ts @@ -23,7 +23,7 @@ async function requestDesktopToolApprovalFromCore( request: ToolApprovalRequest, ): Promise { if (!cachedDesktopApprovalRequester) { - cachedDesktopApprovalRequester = import("@clinebot/core/server") + cachedDesktopApprovalRequester = import("@clinebot/core/node") .then((module) => { const fn = ( module as { diff --git a/sdk/apps/cli/src/utils/session.ts b/sdk/apps/cli/src/utils/session.ts index 80033ff7b3..4b082a8d39 100644 --- a/sdk/apps/cli/src/utils/session.ts +++ b/sdk/apps/cli/src/utils/session.ts @@ -17,15 +17,14 @@ import type { } from "@clinebot/core"; import { createSessionHost, - getRpcServerDefaultAddress, RpcCoreSessionService, - RpcSessionClient, resolveSessionBackend, type SessionAccumulatedUsage, type SessionBackend, type SessionManifest, SessionSource, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; +import { getRpcServerDefaultAddress, RpcSessionClient } from "@clinebot/rpc"; function resolveRpcAddress(): string { return process.env.CLINE_RPC_ADDRESS?.trim() || getRpcServerDefaultAddress(); @@ -33,20 +32,18 @@ function resolveRpcAddress(): string { export interface CliSessionManager { start(input: { - config: import("@clinebot/core/server").CoreSessionConfig & { + config: import("@clinebot/core/node").CoreSessionConfig & { loggerConfig?: RpcChatRuntimeLoggerConfig; }; - source?: import("@clinebot/core/server").SessionSource; + source?: import("@clinebot/core/node").SessionSource; prompt?: string; interactive?: boolean; initialMessages?: import("@clinebot/llms").providers.Message[]; userImages?: string[]; userFiles?: string[]; - userInstructionWatcher?: import("@clinebot/core/server").UserInstructionConfigWatcher; + userInstructionWatcher?: import("@clinebot/core/node").UserInstructionConfigWatcher; onTeamRestored?: () => void; - defaultToolExecutors?: Partial< - import("@clinebot/core/server").ToolExecutors - >; + defaultToolExecutors?: Partial; toolPolicies?: import("@clinebot/agents").AgentConfig["toolPolicies"]; requestToolApproval?: ( request: import("@clinebot/agents").ToolApprovalRequest, @@ -122,7 +119,7 @@ export async function getCoreSessionBackend(): Promise { } export async function createDefaultCliSessionManager(options?: { - defaultToolExecutors?: Partial; + defaultToolExecutors?: Partial; toolPolicies?: import("@clinebot/agents").AgentConfig["toolPolicies"]; requestToolApproval?: ( request: ToolApprovalRequest, @@ -467,7 +464,7 @@ function createRpcRuntimeCliSessionManager( options: | { defaultToolExecutors?: Partial< - import("@clinebot/core/server").ToolExecutors + import("@clinebot/core/node").ToolExecutors >; toolPolicies?: import("@clinebot/agents").AgentConfig["toolPolicies"]; requestToolApproval?: ( diff --git a/sdk/apps/cli/src/utils/types.ts b/sdk/apps/cli/src/utils/types.ts index afbd733e71..8456828908 100644 --- a/sdk/apps/cli/src/utils/types.ts +++ b/sdk/apps/cli/src/utils/types.ts @@ -4,7 +4,7 @@ import type { SessionLineage, ToolPolicy, } from "@clinebot/core"; -import type { CoreSessionConfig, SessionManifest } from "@clinebot/core/server"; +import type { CoreSessionConfig, SessionManifest } from "@clinebot/core/node"; import type { providers as LlmsProviders } from "@clinebot/llms"; export type CliOutputMode = "text" | "json"; diff --git a/sdk/apps/cli/tsconfig.json b/sdk/apps/cli/tsconfig.json index 8000db384a..8206bc0a38 100644 --- a/sdk/apps/cli/tsconfig.json +++ b/sdk/apps/cli/tsconfig.json @@ -10,7 +10,8 @@ "../../packages/agents/src/index.browser.ts" ], "@clinebot/core": ["../../packages/core/src/index.node.ts"], - "@clinebot/core/server": ["../../packages/core/src/server/index.ts"], + "@clinebot/core/node": ["../../packages/core/src/index.node.ts"], + "@clinebot/rpc": ["../../packages/rpc/src/index.ts"], "@clinebot/llms": ["../../packages/llms/src/index.ts"], "@clinebot/llms/node": ["../../packages/llms/src/index.ts"], "@clinebot/llms/browser": ["../../packages/llms/src/index.browser.ts"], diff --git a/sdk/apps/code/components/utils.ts b/sdk/apps/code/components/utils.ts index b887f6006d..a528bac324 100644 --- a/sdk/apps/code/components/utils.ts +++ b/sdk/apps/code/components/utils.ts @@ -1,5 +1,5 @@ -import { normalizeUserInput } from "@clinebot/core"; - export function normalizeTitle(title?: string): string { - return normalizeUserInput(title); + if (!title?.trim()) return ""; + // Strip ... wrapper tags if present + return title.replace(/(.*?)<\/user_input>/g, "$1"); } diff --git a/sdk/apps/code/components/views/settings/provider-list-view.tsx b/sdk/apps/code/components/views/settings/provider-list-view.tsx index 8b05bb20b8..3c88b6edb5 100644 --- a/sdk/apps/code/components/views/settings/provider-list-view.tsx +++ b/sdk/apps/code/components/views/settings/provider-list-view.tsx @@ -80,7 +80,7 @@ export function ProviderListContent({ onToggle(prov.id)} + onCheckedChange={() => onToggle(prov.id)} /> ))} diff --git a/sdk/apps/code/hooks/chat-session/constants.ts b/sdk/apps/code/hooks/chat-session/constants.ts index 9a59e9e58d..5052358d6b 100644 --- a/sdk/apps/code/hooks/chat-session/constants.ts +++ b/sdk/apps/code/hooks/chat-session/constants.ts @@ -1,4 +1,3 @@ -import { models } from "@clinebot/llms"; import type { ChatSessionConfig } from "@/lib/chat-schema"; import { readModelSelectionStorageFromWindow } from "@/lib/model-selection"; @@ -15,13 +14,16 @@ export const OAUTH_MANAGED_PROVIDERS = new Set([ "openai-codex", ]); +// Default Cline model — keep in sync with @clinebot/llms CLINE_DEFAULT_MODEL +const CLINE_DEFAULT_MODEL = "anthropic/claude-sonnet-4.6"; + export const DEFAULT_CHAT_CONFIG: ChatSessionConfig = { workspaceRoot: "", cwd: "", - provider: "anthropic", - model: models.ANTHROPIC_DEFAULT_MODEL, + provider: "cline", + model: CLINE_DEFAULT_MODEL, + apiKey: process.env.CLINE_API_KEY || "", mode: "act", - apiKey: process.env.ANTHROPIC_API_KEY || "", systemPrompt: undefined, maxIterations: undefined, enableTools: true, diff --git a/sdk/apps/code/package.json b/sdk/apps/code/package.json index 2e31684b0e..b8ecae9680 100644 --- a/sdk/apps/code/package.json +++ b/sdk/apps/code/package.json @@ -17,6 +17,7 @@ "@clinebot/agents": "workspace:*", "@clinebot/core": "workspace:*", "@clinebot/llms": "workspace:*", + "@clinebot/rpc": "workspace:*", "@hookform/resolvers": "^3.9.1", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", diff --git a/sdk/apps/code/scripts/chat-runtime-bridge.ts b/sdk/apps/code/scripts/chat-runtime-bridge.ts index 18eac642f7..742eb719e6 100644 --- a/sdk/apps/code/scripts/chat-runtime-bridge.ts +++ b/sdk/apps/code/scripts/chat-runtime-bridge.ts @@ -7,7 +7,7 @@ import { import { type RpcRuntimeBridgeCommandOutputLine, runRpcRuntimeCommandBridge, -} from "@clinebot/core/server"; +} from "@clinebot/rpc"; function writeLine(line: RpcRuntimeBridgeCommandOutputLine): void { process.stdout.write(`${JSON.stringify(line)}\n`); diff --git a/sdk/apps/code/scripts/provider-oauth-login.ts b/sdk/apps/code/scripts/provider-oauth-login.ts index 4bd946dad3..8ead1fa437 100644 --- a/sdk/apps/code/scripts/provider-oauth-login.ts +++ b/sdk/apps/code/scripts/provider-oauth-login.ts @@ -1,6 +1,12 @@ +import { spawn } from "node:child_process"; import { readFileSync } from "node:fs"; import type { RpcProviderOAuthLoginResponse } from "@clinebot/core"; -import { RpcSessionClient } from "@clinebot/core/server"; +import { + loginLocalProvider, + normalizeOAuthProvider, + ProviderSettingsManager, + saveLocalProviderOAuthCredentials, +} from "@clinebot/core/node"; type RequestBody = { provider: string; @@ -16,19 +22,42 @@ async function main() { if (!provider) { throw new Error("provider is required"); } - - const address = process.env.CLINE_RPC_ADDRESS?.trim() || "127.0.0.1:4317"; - const client = new RpcSessionClient({ address }); - try { - const response = (await client.runProviderOAuthLogin( - provider, - )) as RpcProviderOAuthLoginResponse; - process.stdout.write( - `${JSON.stringify({ provider: response.provider, accessToken: response.accessToken })}\n`, - ); - } finally { - client.close(); - } + const providerId = normalizeOAuthProvider(provider); + const manager = new ProviderSettingsManager(); + const existing = manager.getProviderSettings(providerId); + const credentials = await loginLocalProvider(providerId, existing, (url) => { + const platform = process.platform; + const command = + platform === "darwin" + ? "open" + : platform === "win32" + ? "cmd" + : "xdg-open"; + const args = + platform === "darwin" + ? [url] + : platform === "win32" + ? ["/c", "start", "", url] + : [url]; + const child = spawn(command, args, { + stdio: "ignore", + detached: true, + }); + child.unref(); + }); + const saved = saveLocalProviderOAuthCredentials( + manager, + providerId, + existing, + credentials, + ); + const response: RpcProviderOAuthLoginResponse = { + provider: providerId, + accessToken: saved.auth?.accessToken ?? saved.apiKey ?? "", + }; + process.stdout.write( + `${JSON.stringify({ provider: response.provider, accessToken: response.accessToken })}\n`, + ); } main().catch((error) => { diff --git a/sdk/apps/code/scripts/provider-settings.ts b/sdk/apps/code/scripts/provider-settings.ts index 9b3eb26f75..885368e7e0 100644 --- a/sdk/apps/code/scripts/provider-settings.ts +++ b/sdk/apps/code/scripts/provider-settings.ts @@ -1,6 +1,13 @@ import { readFileSync } from "node:fs"; import type { RpcProviderActionRequest } from "@clinebot/core"; -import { RpcSessionClient } from "@clinebot/core/server"; +import { + addLocalProvider, + ensureCustomProvidersLoaded, + getLocalProviderModels, + listLocalProviders, + ProviderSettingsManager, + saveLocalProviderSettings, +} from "@clinebot/core/node"; function readStdin(): string { return readFileSync(0, "utf8"); @@ -8,14 +15,34 @@ function readStdin(): string { async function main() { const parsed = JSON.parse(readStdin()) as RpcProviderActionRequest; - const address = process.env.CLINE_RPC_ADDRESS?.trim() || "127.0.0.1:4317"; - const client = new RpcSessionClient({ address }); - try { - const response = await client.runProviderAction(parsed); - process.stdout.write(`${JSON.stringify(response.result)}\n`); - } finally { - client.close(); + const manager = new ProviderSettingsManager(); + await ensureCustomProvidersLoaded(manager); + + if (parsed.action === "listProviders") { + process.stdout.write( + `${JSON.stringify(await listLocalProviders(manager))}\n`, + ); + return; } + if (parsed.action === "getProviderModels") { + process.stdout.write( + `${JSON.stringify(await getLocalProviderModels(parsed.providerId))}\n`, + ); + return; + } + if (parsed.action === "addProvider") { + process.stdout.write( + `${JSON.stringify(await addLocalProvider(manager, parsed))}\n`, + ); + return; + } + if (parsed.action === "saveProviderSettings") { + process.stdout.write( + `${JSON.stringify(saveLocalProviderSettings(manager, parsed))}\n`, + ); + return; + } + throw new Error(`unsupported provider action: ${String(parsed.action)}`); } main().catch((error) => { diff --git a/sdk/apps/code/scripts/routine-schedules.ts b/sdk/apps/code/scripts/routine-schedules.ts index 559b182622..6460cb392f 100644 --- a/sdk/apps/code/scripts/routine-schedules.ts +++ b/sdk/apps/code/scripts/routine-schedules.ts @@ -1,5 +1,5 @@ import { readFileSync } from "node:fs"; -import { RpcSessionClient } from "@clinebot/core/server"; +import { RpcSessionClient } from "@clinebot/rpc"; type RoutineAction = | "listOverview" diff --git a/sdk/apps/code/scripts/workspace-file-search.ts b/sdk/apps/code/scripts/workspace-file-search.ts index 63d2f13963..8e30daa8b6 100644 --- a/sdk/apps/code/scripts/workspace-file-search.ts +++ b/sdk/apps/code/scripts/workspace-file-search.ts @@ -1,5 +1,5 @@ import { readFileSync } from "node:fs"; -import { getFileIndex } from "@clinebot/core/server"; +import { getFileIndex } from "@clinebot/core/node"; type WorkspaceFileSearchRequest = { workspaceRoot?: string; diff --git a/sdk/apps/code/tsconfig.dev.json b/sdk/apps/code/tsconfig.dev.json index 078e08d46d..54234b81e7 100644 --- a/sdk/apps/code/tsconfig.dev.json +++ b/sdk/apps/code/tsconfig.dev.json @@ -4,7 +4,8 @@ "paths": { "@/*": ["./*"], "@clinebot/core": ["../../packages/core/src/index.node.ts"], - "@clinebot/core/server": ["../../packages/core/src/server/index.ts"], + "@clinebot/core/node": ["../../packages/core/src/index.node.ts"], + "@clinebot/rpc": ["../../packages/rpc/src/index.ts"], "@clinebot/shared": ["../../packages/shared/src/index.ts"], "@clinebot/shared/*": [ "../../packages/shared/src/*", diff --git a/sdk/apps/desktop/README.md b/sdk/apps/desktop/README.md index 075dcddab2..82e637c971 100644 --- a/sdk/apps/desktop/README.md +++ b/sdk/apps/desktop/README.md @@ -28,7 +28,7 @@ Desktop should use explicit runtime imports: - Frontend/browser modules: `@clinebot/llms/browser` -- Node runtime modules (CLI/Tauri/scripts): `@clinebot/llms/node`, `@clinebot/agents/node`, `@clinebot/core/node`, `@clinebot/core/server/node` +- Node runtime modules (CLI/Tauri/scripts): `@clinebot/llms/node`, `@clinebot/agents/node`, `@clinebot/core/node`, `@clinebot/rpc` ## Scripts diff --git a/sdk/apps/desktop/package.json b/sdk/apps/desktop/package.json index cb2f4d5f0b..28b1eee461 100644 --- a/sdk/apps/desktop/package.json +++ b/sdk/apps/desktop/package.json @@ -16,6 +16,7 @@ "@clinebot/agents": "workspace:*", "@clinebot/core": "workspace:*", "@clinebot/llms": "workspace:*", + "@clinebot/rpc": "workspace:*", "@hookform/resolvers": "^3.9.1", "@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-alert-dialog": "1.1.4", diff --git a/sdk/apps/desktop/scripts/chat-runtime-bridge.ts b/sdk/apps/desktop/scripts/chat-runtime-bridge.ts index c64866c58d..4b4f7469b1 100644 --- a/sdk/apps/desktop/scripts/chat-runtime-bridge.ts +++ b/sdk/apps/desktop/scripts/chat-runtime-bridge.ts @@ -1,7 +1,7 @@ import { type RpcRuntimeBridgeCommandOutputLine, runRpcRuntimeCommandBridge, -} from "@clinebot/core/server"; +} from "@clinebot/rpc"; function writeLine(line: RpcRuntimeBridgeCommandOutputLine): void { process.stdout.write(`${JSON.stringify(line)}\n`); diff --git a/sdk/apps/examples/cline-plugin/index.ts b/sdk/apps/examples/cline-plugin/index.ts index f85cdd5069..09fdc65e99 100644 --- a/sdk/apps/examples/cline-plugin/index.ts +++ b/sdk/apps/examples/cline-plugin/index.ts @@ -8,7 +8,7 @@ * ANTHROPIC_API_KEY=sk-... bun run apps/examples/cline-plugin/index.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; type SessionHost = Awaited>; type SessionStartConfig = Parameters[0]["config"]; diff --git a/sdk/apps/examples/cline-sdk/01-minimal.ts b/sdk/apps/examples/cline-sdk/01-minimal.ts index ea5c770aa4..6ca19c5437 100644 --- a/sdk/apps/examples/cline-sdk/01-minimal.ts +++ b/sdk/apps/examples/cline-sdk/01-minimal.ts @@ -14,7 +14,7 @@ * Run: bun run 01-minimal.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; async function main() { // Create a session manager (handles all session lifecycle) diff --git a/sdk/apps/examples/cline-sdk/02-custom-model.ts b/sdk/apps/examples/cline-sdk/02-custom-model.ts index 4cf3a53ff7..a35a01970f 100644 --- a/sdk/apps/examples/cline-sdk/02-custom-model.ts +++ b/sdk/apps/examples/cline-sdk/02-custom-model.ts @@ -15,7 +15,7 @@ * Run: bun run 02-custom-model.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; async function demoAnthropicModel() { console.log("\n=== Anthropic Claude Sonnet ==="); diff --git a/sdk/apps/examples/cline-sdk/03-system-prompt.ts b/sdk/apps/examples/cline-sdk/03-system-prompt.ts index aa8ed91af0..379c672a84 100644 --- a/sdk/apps/examples/cline-sdk/03-system-prompt.ts +++ b/sdk/apps/examples/cline-sdk/03-system-prompt.ts @@ -15,7 +15,7 @@ * Run: bun run 03-system-prompt.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; async function demoBasicSystemPrompt() { console.log("\n=== Basic System Prompt ==="); diff --git a/sdk/apps/examples/cline-sdk/04-tools.ts b/sdk/apps/examples/cline-sdk/04-tools.ts index 42f4ea1a43..832a877487 100644 --- a/sdk/apps/examples/cline-sdk/04-tools.ts +++ b/sdk/apps/examples/cline-sdk/04-tools.ts @@ -24,7 +24,7 @@ * Run: bun run 04-tools.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; const POLICY_ALLOWED = { enabled: true, autoApprove: true }; const POLICY_BLOCKED = { enabled: false, autoApprove: false }; diff --git a/sdk/apps/examples/cline-sdk/05-custom-tools.ts b/sdk/apps/examples/cline-sdk/05-custom-tools.ts index 60744dc158..be7ddab8c0 100644 --- a/sdk/apps/examples/cline-sdk/05-custom-tools.ts +++ b/sdk/apps/examples/cline-sdk/05-custom-tools.ts @@ -17,7 +17,7 @@ */ import type { Tool } from "@clinebot/agents"; -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; // Example 1: Simple calculator tool const calculatorTool: Tool = { diff --git a/sdk/apps/examples/cline-sdk/06-hooks.ts b/sdk/apps/examples/cline-sdk/06-hooks.ts index e697b730c8..5fc7f1fb0a 100644 --- a/sdk/apps/examples/cline-sdk/06-hooks.ts +++ b/sdk/apps/examples/cline-sdk/06-hooks.ts @@ -27,7 +27,7 @@ */ import type { AgentConfig } from "@clinebot/agents"; -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; type AgentHooksConfig = NonNullable; diff --git a/sdk/apps/examples/cline-sdk/07-extensions.ts b/sdk/apps/examples/cline-sdk/07-extensions.ts index 76a5039b38..aa59e07640 100644 --- a/sdk/apps/examples/cline-sdk/07-extensions.ts +++ b/sdk/apps/examples/cline-sdk/07-extensions.ts @@ -22,7 +22,7 @@ */ import type { AgentConfig, Tool } from "@clinebot/agents"; -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; type AgentExtension = NonNullable[number]; diff --git a/sdk/apps/examples/cline-sdk/08-context-files.ts b/sdk/apps/examples/cline-sdk/08-context-files.ts index 00f168268c..64baa0438f 100644 --- a/sdk/apps/examples/cline-sdk/08-context-files.ts +++ b/sdk/apps/examples/cline-sdk/08-context-files.ts @@ -15,7 +15,7 @@ * Run: bun run 08-context-files.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; async function demoSingleFile() { console.log("\n=== Single File Context ===\n"); diff --git a/sdk/apps/examples/cline-sdk/09-sessions.ts b/sdk/apps/examples/cline-sdk/09-sessions.ts index 2de3d2cd85..83da700af2 100644 --- a/sdk/apps/examples/cline-sdk/09-sessions.ts +++ b/sdk/apps/examples/cline-sdk/09-sessions.ts @@ -25,7 +25,7 @@ import { readFileSync } from "node:fs"; import { homedir } from "node:os"; import { join } from "node:path"; -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; async function demoCreateSession() { console.log("\n=== Create New Session ===\n"); diff --git a/sdk/apps/examples/cline-sdk/10-spawn-agents.ts b/sdk/apps/examples/cline-sdk/10-spawn-agents.ts index 95aecee0ad..d126f54bb7 100644 --- a/sdk/apps/examples/cline-sdk/10-spawn-agents.ts +++ b/sdk/apps/examples/cline-sdk/10-spawn-agents.ts @@ -22,7 +22,7 @@ * Run: bun run 10-spawn-agents.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; async function demoBasicSpawn() { console.log("\n=== Basic Agent Spawning ===\n"); diff --git a/sdk/apps/examples/cline-sdk/11-teams.ts b/sdk/apps/examples/cline-sdk/11-teams.ts index d2e7003a43..1850fb88a6 100644 --- a/sdk/apps/examples/cline-sdk/11-teams.ts +++ b/sdk/apps/examples/cline-sdk/11-teams.ts @@ -23,7 +23,7 @@ * Run: bun run 11-teams.ts */ -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; async function demoBasicTeam() { console.log("\n=== Basic Agent Team ===\n"); diff --git a/sdk/apps/examples/cline-sdk/12-rpc-client.ts b/sdk/apps/examples/cline-sdk/12-rpc-client.ts index 061c42bec5..5e78097a37 100644 --- a/sdk/apps/examples/cline-sdk/12-rpc-client.ts +++ b/sdk/apps/examples/cline-sdk/12-rpc-client.ts @@ -19,7 +19,7 @@ import process from "node:process"; import type { RpcChatStartSessionRequest } from "@clinebot/core"; -import { getRpcServerHealth, RpcSessionClient } from "@clinebot/core/server"; +import { getRpcServerHealth, RpcSessionClient } from "@clinebot/rpc"; type StreamEvent = { eventId: string; diff --git a/sdk/apps/examples/cline-sdk/13-custom-executors.ts b/sdk/apps/examples/cline-sdk/13-custom-executors.ts index e937163a7f..3539748b52 100644 --- a/sdk/apps/examples/cline-sdk/13-custom-executors.ts +++ b/sdk/apps/examples/cline-sdk/13-custom-executors.ts @@ -15,7 +15,7 @@ import { exec as execCb } from "node:child_process"; import { readFile } from "node:fs/promises"; import { promisify } from "node:util"; import type { ToolContext } from "@clinebot/agents"; -import { createSessionHost, type ToolExecutors } from "@clinebot/core/server"; +import { createSessionHost, type ToolExecutors } from "@clinebot/core/node"; const exec = promisify(execCb); diff --git a/sdk/apps/examples/cline-sdk/14-full-control.ts b/sdk/apps/examples/cline-sdk/14-full-control.ts index 860a88c120..7a9d1b4b41 100644 --- a/sdk/apps/examples/cline-sdk/14-full-control.ts +++ b/sdk/apps/examples/cline-sdk/14-full-control.ts @@ -13,7 +13,7 @@ import { exec as execCb } from "node:child_process"; import { promisify } from "node:util"; import type { AgentConfig, Tool, ToolContext } from "@clinebot/agents"; -import { createSessionHost, type ToolExecutors } from "@clinebot/core/server"; +import { createSessionHost, type ToolExecutors } from "@clinebot/core/node"; const exec = promisify(execCb); diff --git a/sdk/apps/examples/cline-sdk/README.md b/sdk/apps/examples/cline-sdk/README.md index 6a8752dd10..92509004f6 100644 --- a/sdk/apps/examples/cline-sdk/README.md +++ b/sdk/apps/examples/cline-sdk/README.md @@ -64,7 +64,7 @@ Using these SDK packages in your own app (npm/pnpm/yarn): npm add @clinebot/core @clinebot/agents @clinebot/llms ``` -If you need RPC client/server helpers, use `@clinebot/core` exports (for example `RpcSessionClient`, `getRpcServerHealth`) instead of depending on `@clinebot/rpc` directly. +If you need RPC client/server helpers, import them from `@clinebot/rpc` directly. `@clinebot/core` is now the transport-agnostic session/runtime package. > **Note:** `createSessionHost` works without the CLI app installed. It runs local in-process sessions and falls back to a local SQLite backend when RPC is unavailable. Use `CLINE_BACKEND_MODE=local` to force local mode explicitly. @@ -80,7 +80,7 @@ CLINE_BACKEND_MODE=local bun run 14-full-control.ts The Cline SDK is organized into focused packages: - **`@clinebot/core`** - Session management, storage, runtime orchestration -- **`@clinebot/core` RPC re-exports** - Optional remote-session helpers (`RpcSessionClient`, `getRpcServerHealth`, etc.) +- **`@clinebot/rpc`** - Optional remote-session helpers (`RpcSessionClient`, `getRpcServerHealth`, etc.) - **`@clinebot/agents`** - Agent runtime loop, tools, hooks, teams - **`@clinebot/llms`** - Model catalog, provider settings, handlers - Shared primitives/types/path helpers are consumed through `@clinebot/core` re-exports in app/example code. @@ -114,7 +114,7 @@ All tools respect the agent's working directory and can be customized with polic ### Minimal Session ```typescript -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; const sessionManager = await createSessionHost({}); @@ -139,7 +139,7 @@ console.log(result.result?.text); ### With Custom Model ```typescript -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; await sessionManager.start({ config: { @@ -199,7 +199,7 @@ await sessionManager.stop(sessionId); ### Custom Tools ```typescript -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; import type { Tool } from "@clinebot/agents"; const myTool: Tool = { @@ -396,7 +396,7 @@ process.env.CLINE_SESSION_DATA_DIR = "/custom/path"; Enable verbose logging: ```typescript -import { createLogger } from "@clinebot/core/server"; +import { createLogger } from "@clinebot/core/node"; const logger = createLogger({ level: "debug" }); diff --git a/sdk/apps/examples/cline-sdk/index.ts b/sdk/apps/examples/cline-sdk/index.ts index 6255829bef..bb8e547b87 100644 --- a/sdk/apps/examples/cline-sdk/index.ts +++ b/sdk/apps/examples/cline-sdk/index.ts @@ -15,7 +15,7 @@ import { mkdir } from "node:fs/promises"; import path from "node:path"; import process, { stdin as input, stdout as output } from "node:process"; import { createInterface } from "node:readline/promises"; -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; type ProviderOption = { label: string; diff --git a/sdk/apps/examples/cline-sdk/package.json b/sdk/apps/examples/cline-sdk/package.json index c0864601bd..ce4af89212 100644 --- a/sdk/apps/examples/cline-sdk/package.json +++ b/sdk/apps/examples/cline-sdk/package.json @@ -28,7 +28,8 @@ "dependencies": { "@clinebot/agents": "nightly", "@clinebot/core": "nightly", - "@clinebot/llms": "nightly" + "@clinebot/llms": "nightly", + "@clinebot/rpc": "nightly" }, "devDependencies": { "@types/node": "^22.10.5", diff --git a/sdk/apps/vscode/src/extension.ts b/sdk/apps/vscode/src/extension.ts index bd60af2b91..1efa9e27d0 100644 --- a/sdk/apps/vscode/src/extension.ts +++ b/sdk/apps/vscode/src/extension.ts @@ -9,7 +9,7 @@ import { buildWorkspaceMetadata, createSessionHost, type SessionHost, -} from "@clinebot/core/server"; +} from "@clinebot/core/node"; import { models as llmModels, providers as llmProviders } from "@clinebot/llms"; import * as vscode from "vscode"; import type { diff --git a/sdk/apps/vscode/tsconfig.json b/sdk/apps/vscode/tsconfig.json index 026bc88e38..c4c9840145 100644 --- a/sdk/apps/vscode/tsconfig.json +++ b/sdk/apps/vscode/tsconfig.json @@ -6,7 +6,7 @@ "paths": { "@clinebot/agents": ["../../packages/agents/src/index.ts"], "@clinebot/core": ["../../packages/core/src/index.node.ts"], - "@clinebot/core/server": ["../../packages/core/src/server/index.ts"], + "@clinebot/core/node": ["../../packages/core/src/index.node.ts"], "@clinebot/llms": ["../../packages/llms/src/index.ts"], "@clinebot/shared": ["../../packages/shared/src/index.ts"] }, diff --git a/sdk/biome.json b/sdk/biome.json index 218ec7dc32..4984d12cad 100644 --- a/sdk/biome.json +++ b/sdk/biome.json @@ -46,10 +46,10 @@ "@clinebot/llms/*", "@clinebot/agents/*", "@clinebot/core/*", - "!@clinebot/core/server", - "!@clinebot/core/server/*" + "!@clinebot/core/node", + "!@clinebot/core/node/*" ], - "message": "Cross-workspace deep imports are forbidden (except @clinebot/core/server). Import from package entry points instead." + "message": "Cross-workspace deep imports are forbidden (except @clinebot/core/node). Import from package entry points instead." } ] } diff --git a/sdk/bun.lock b/sdk/bun.lock index f1a261b827..189ec33819 100644 --- a/sdk/bun.lock +++ b/sdk/bun.lock @@ -30,6 +30,7 @@ "@clinebot/agents": "workspace:*", "@clinebot/core": "workspace:*", "@clinebot/llms": "workspace:*", + "@clinebot/rpc": "workspace:*", "better-sqlite3": "^11.10.0", "chat": "^4.20.2", "ink": "^6.8.0", @@ -51,6 +52,7 @@ "@clinebot/agents": "workspace:*", "@clinebot/core": "workspace:*", "@clinebot/llms": "workspace:*", + "@clinebot/rpc": "workspace:*", "@hookform/resolvers": "^3.9.1", "@radix-ui/react-accordion": "1.2.12", "@radix-ui/react-alert-dialog": "1.1.15", @@ -124,6 +126,7 @@ "@clinebot/agents": "workspace:*", "@clinebot/core": "workspace:*", "@clinebot/llms": "workspace:*", + "@clinebot/rpc": "workspace:*", "@hookform/resolvers": "^3.9.1", "@radix-ui/react-accordion": "1.2.2", "@radix-ui/react-alert-dialog": "1.1.4", @@ -274,7 +277,7 @@ }, "packages/agents": { "name": "@clinebot/agents", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@clinebot/llms": "workspace:*", "@clinebot/shared": "workspace:*", @@ -283,7 +286,7 @@ }, "packages/core": { "name": "@clinebot/core", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@clinebot/agents": "workspace:*", "@clinebot/llms": "workspace:*", @@ -297,7 +300,7 @@ }, "packages/llms": { "name": "@clinebot/llms", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@ai-sdk/amazon-bedrock": "^4.0.67", "@ai-sdk/google-vertex": "^4.0.74", @@ -325,7 +328,7 @@ }, "packages/rpc": { "name": "@clinebot/rpc", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@clinebot/scheduler": "workspace:*", "@clinebot/shared": "workspace:*", @@ -335,14 +338,14 @@ }, "packages/scheduler": { "name": "@clinebot/scheduler", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@clinebot/shared": "workspace:*", }, }, "packages/shared": { "name": "@clinebot/shared", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "better-sqlite3": "^11.10.0", "jsonrepair": "^3.13.2", diff --git a/sdk/packages/README.md b/sdk/packages/README.md index 9c170062b8..c3a64c607c 100644 --- a/sdk/packages/README.md +++ b/sdk/packages/README.md @@ -38,7 +38,7 @@ This directory is the single documentation source for package-level responsibili - Node-oriented imports: `@clinebot//node` - Browser entry points exist for API compatibility; some packages intentionally throw in browser mode for server-only features (`agents`, `core`, `rpc`). -- `@clinebot/core/server/node` contains server-side runtime/session services. +- `@clinebot/core/node` contains Node-only runtime/session services. ## Notes for Doc Consolidation diff --git a/sdk/packages/core/README.md b/sdk/packages/core/README.md index a9f8f6ad8e..19fc357868 100644 --- a/sdk/packages/core/README.md +++ b/sdk/packages/core/README.md @@ -10,7 +10,7 @@ session lifecycle into a host-ready runtime. - provider settings and account services - default runtime tools and MCP integration - storage-backed session and team state helpers -- host-facing Node helpers through `@clinebot/core/server` +- host-facing Node helpers through `@clinebot/core/node` ## Installation @@ -21,14 +21,14 @@ npm install @clinebot/core ## Entry Points - `@clinebot/core`: core contracts, shared utilities, and RPC helper re-exports (`RpcSessionClient`, `getRpcServerHealth`, runtime bridge helpers) -- `@clinebot/core/server`: Node/server helpers for building hosts and runtimes +- `@clinebot/core/node`: Node/server helpers for building hosts and runtimes ## Typical Usage -Most host apps should start with `@clinebot/core/server`. +Most host apps should start with `@clinebot/core/node`. ```ts -import { createSessionHost } from "@clinebot/core/server"; +import { createSessionHost } from "@clinebot/core/node"; const host = await createSessionHost({}); @@ -53,7 +53,7 @@ await host.dispose(); ### Runtime and Sessions -Use `@clinebot/core/server` for host-facing runtime assembly: +Use `@clinebot/core/node` for host-facing runtime assembly: - `createSessionHost(...)` - `DefaultSessionManager` @@ -73,7 +73,7 @@ The package also exports storage and settings helpers such as: - `ProviderSettingsManager` - `SqliteTeamStore` -- SQLite RPC session backend helpers from `@clinebot/core/server` +- SQLite RPC session backend helpers from `@clinebot/core/node` ## Related Packages diff --git a/sdk/packages/core/bun.mts b/sdk/packages/core/bun.mts index 0fddf3f92f..d19793393d 100644 --- a/sdk/packages/core/bun.mts +++ b/sdk/packages/core/bun.mts @@ -5,6 +5,7 @@ export {}; const external = [ "@clinebot/agents", "@clinebot/llms", + "@clinebot/rpc", "nanoid", "simple-git", "yaml", @@ -12,6 +13,16 @@ const external = [ ]; const builds: Parameters[0][] = [ + { + entrypoints: ["./src/index.ts"], + outdir: "./dist", + target: "node", + format: "esm", + minify: true, + packages: "bundle", + sourcemap: "none", + external, + }, { entrypoints: ["./src/index.node.ts"], outdir: "./dist", @@ -31,15 +42,6 @@ const builds: Parameters[0][] = [ packages: "bundle", external, }, - { - entrypoints: ["./src/server/index.ts"], - outdir: "./dist/server", - target: "node", - minify: true, - sourcemap: "none", - packages: "bundle", - external, - }, ]; for (const config of builds) { diff --git a/sdk/packages/core/package.json b/sdk/packages/core/package.json index f2bed19ce4..831082936a 100644 --- a/sdk/packages/core/package.json +++ b/sdk/packages/core/package.json @@ -28,21 +28,6 @@ "development": "./dist/index.browser.js", "types": "./dist/index.browser.d.ts", "import": "./dist/index.browser.js" - }, - "./server": { - "development": "./dist/server/index.js", - "types": "./dist/server/index.d.ts", - "import": "./dist/server/index.js" - }, - "./server/node": { - "development": "./dist/server/index.js", - "types": "./dist/server/index.d.ts", - "import": "./dist/server/index.js" - }, - "./server/browser": { - "development": "./dist/server/index.js", - "types": "./dist/server.browser.d.ts", - "import": "./dist/server.browser.js" } }, "description": "State-aware orchestration for Cline Agent runtimes", diff --git a/sdk/packages/core/src/default-tools/definitions.ts b/sdk/packages/core/src/default-tools/definitions.ts index 1449cc8653..2155e18e32 100644 --- a/sdk/packages/core/src/default-tools/definitions.ts +++ b/sdk/packages/core/src/default-tools/definitions.ts @@ -547,7 +547,7 @@ export function createAskQuestionTool( * @example * ```typescript * import { Agent } from "@clinebot/agents" - * import { createDefaultTools } from "@clinebot/core/server" + * import { createDefaultTools } from "@clinebot/core/node" * import * as fs from "fs/promises" * import { exec } from "child_process" * diff --git a/sdk/packages/core/src/default-tools/executors/index.ts b/sdk/packages/core/src/default-tools/executors/index.ts index 79502650fa..31b0fd9e54 100644 --- a/sdk/packages/core/src/default-tools/executors/index.ts +++ b/sdk/packages/core/src/default-tools/executors/index.ts @@ -48,7 +48,7 @@ export interface DefaultExecutorsOptions { * * @example * ```typescript - * import { createDefaultTools, createDefaultExecutors } from "@clinebot/core/server" + * import { createDefaultTools, createDefaultExecutors } from "@clinebot/core/node" * * const executors = createDefaultExecutors({ * bash: { timeoutMs: 60000 }, diff --git a/sdk/packages/core/src/default-tools/index.ts b/sdk/packages/core/src/default-tools/index.ts index 479f7e16b2..1f48b3aeba 100644 --- a/sdk/packages/core/src/default-tools/index.ts +++ b/sdk/packages/core/src/default-tools/index.ts @@ -126,7 +126,7 @@ export interface CreateBuiltinToolsOptions * @example * ```typescript * import { Agent } from "@clinebot/agents" - * import { createBuiltinTools } from "@clinebot/core/server" + * import { createBuiltinTools } from "@clinebot/core/node" * * const tools = createBuiltinTools({ * cwd: "/path/to/project", diff --git a/sdk/packages/core/src/index.node.ts b/sdk/packages/core/src/index.node.ts index 6d1e0ee83e..1980440917 100644 --- a/sdk/packages/core/src/index.node.ts +++ b/sdk/packages/core/src/index.node.ts @@ -1 +1,224 @@ +export type { + AgentConfigWatcher, + AgentConfigWatcherEvent, + AgentYamlConfig, + BuildAgentConfigOverridesOptions, + CreateAgentConfigWatcherOptions, + CreateInstructionWatcherOptions, + CreateRulesConfigDefinitionOptions, + CreateSkillsConfigDefinitionOptions, + CreateUserInstructionConfigWatcherOptions, + CreateWorkflowsConfigDefinitionOptions, + HookConfigFileEntry, + LoadAgentPluginFromPathOptions, + ParseMarkdownFrontmatterResult, + ParseYamlFrontmatterResult, + ResolveAgentPluginPathsOptions, + RuleConfig, + SkillConfig, + UnifiedConfigDefinition, + UnifiedConfigFileCandidate, + UnifiedConfigFileContext, + UnifiedConfigRecord, + UnifiedConfigWatcherEvent, + UnifiedConfigWatcherOptions, + UserInstructionConfig, + UserInstructionConfigType, + UserInstructionConfigWatcher, + UserInstructionConfigWatcherEvent, + WorkflowConfig, +} from "./agents"; +export { + createAgentConfigDefinition, + createAgentConfigWatcher, + createRulesConfigDefinition, + createSkillsConfigDefinition, + createUserInstructionConfigWatcher, + createWorkflowsConfigDefinition, + discoverPluginModulePaths, + HOOK_CONFIG_FILE_EVENT_MAP, + HOOKS_CONFIG_DIRECTORY_NAME, + HookConfigFileName, + listHookConfigFiles, + loadAgentPluginFromPath, + loadAgentPluginsFromPaths, + parseAgentConfigFromYaml, + parsePartialAgentConfigFromYaml, + parseRuleConfigFromMarkdown, + parseSkillConfigFromMarkdown, + parseWorkflowConfigFromMarkdown, + RULES_CONFIG_DIRECTORY_NAME, + resolveAgentPluginPaths, + resolveAgentTools, + resolveAndLoadAgentPlugins, + resolveDocumentsHooksDirectoryPath, + resolveDocumentsRulesDirectoryPath, + resolveDocumentsWorkflowsDirectoryPath, + resolveHooksConfigSearchPaths, + resolvePluginConfigSearchPaths, + resolveRulesConfigSearchPaths, + resolveSkillsConfigSearchPaths, + resolveWorkflowsConfigSearchPaths, + SKILLS_CONFIG_DIRECTORY_NAME, + toHookConfigFileName, + toPartialAgentConfig, + UnifiedConfigFileWatcher, + WORKFLOWS_CONFIG_DIRECTORY_NAME, +} from "./agents"; +export { + createOAuthClientCallbacks, + type OAuthClientCallbacksOptions, +} from "./auth/client"; +export { + createClineOAuthProvider, + getValidClineCredentials, + loginClineOAuth, + refreshClineToken, +} from "./auth/cline"; +export { + getValidOpenAICodexCredentials, + isOpenAICodexTokenExpired, + loginOpenAICodex, + normalizeOpenAICodexCredentials, + openaiCodexOAuthProvider, + refreshOpenAICodexToken, +} from "./auth/codex"; +export { + createOcaOAuthProvider, + createOcaRequestHeaders, + DEFAULT_EXTERNAL_IDCS_CLIENT_ID, + DEFAULT_EXTERNAL_IDCS_SCOPES, + DEFAULT_EXTERNAL_IDCS_URL, + DEFAULT_EXTERNAL_OCA_BASE_URL, + DEFAULT_INTERNAL_IDCS_CLIENT_ID, + DEFAULT_INTERNAL_IDCS_SCOPES, + DEFAULT_INTERNAL_IDCS_URL, + DEFAULT_INTERNAL_OCA_BASE_URL, + generateOcaOpcRequestId, + getValidOcaCredentials, + loginOcaOAuth, + OCI_HEADER_OPC_REQUEST_ID, + refreshOcaToken, +} from "./auth/oca"; +export { startLocalOAuthServer } from "./auth/server"; +export type { + OAuthCredentials, + OAuthLoginCallbacks, + OAuthPrompt, + OAuthProviderInterface, + OcaClientMetadata, + OcaMode, + OcaOAuthConfig, + OcaOAuthEnvironmentConfig, + OcaOAuthProviderOptions, + OcaTokenResolution, +} from "./auth/types"; export * from "./index"; +export type { + FastFileIndexOptions, + MentionEnricherOptions, + MentionEnrichmentResult, +} from "./input"; +export { + enrichPromptWithMentions, + getFileIndex, + prewarmFileIndex, +} from "./input"; +export { + addLocalProvider, + ensureCustomProvidersLoaded, + getLocalProviderModels, + listLocalProviders, + loginLocalProvider, + normalizeOAuthProvider, + resolveLocalClineAuthToken, + saveLocalProviderOAuthCredentials, + saveLocalProviderSettings, +} from "./providers/local-provider-service"; +export { + formatRulesForSystemPrompt, + isRuleEnabled, + listEnabledRulesFromWatcher, + loadRulesForSystemPromptFromWatcher, +} from "./runtime/rules"; +export { + createTeamName, + DefaultRuntimeBuilder, +} from "./runtime/runtime-builder"; +export { + type SandboxCallOptions, + SubprocessSandbox, + type SubprocessSandboxOptions, +} from "./runtime/sandbox/subprocess-sandbox"; +export type { + BuiltRuntime, + RuntimeBuilder, + RuntimeBuilderInput, + SessionRuntime, +} from "./runtime/session-runtime"; +export { + type DesktopToolApprovalOptions, + requestDesktopToolApproval, +} from "./runtime/tool-approval"; +export type { AvailableWorkflow } from "./runtime/workflows"; +export { + listAvailableWorkflowsFromWatcher, + resolveWorkflowSlashCommandFromWatcher, +} from "./runtime/workflows"; +export { DefaultSessionManager } from "./session/default-session-manager"; +export { RpcCoreSessionService } from "./session/rpc-session-service"; +export { + deriveSubsessionStatus, + makeSubSessionId, + makeTeamTaskSubSessionId, + sanitizeSessionToken, +} from "./session/session-graph"; +export type { + CreateSessionHostOptions, + SessionBackend, + SessionHost, +} from "./session/session-host"; +export { + createSessionHost, + resolveSessionBackend, +} from "./session/session-host"; +export type { + SendSessionInput, + SessionAccumulatedUsage, + SessionManager, + StartSessionInput, + StartSessionResult, +} from "./session/session-manager"; +export type { SessionManifest } from "./session/session-manifest"; +export type { + CreateRootSessionWithArtifactsInput, + RootSessionArtifacts, +} from "./session/session-service"; +export { CoreSessionService } from "./session/session-service"; +export { + createSqliteRpcSessionBackend, + SqliteRpcSessionBackend, + type SqliteRpcSessionBackendOptions, +} from "./session/sqlite-rpc-session-backend"; +export type { + WorkspaceManager, + WorkspaceManagerEvent, +} from "./session/workspace-manager"; +export { InMemoryWorkspaceManager } from "./session/workspace-manager"; +export type { WorkspaceManifest } from "./session/workspace-manifest"; +export { + buildWorkspaceMetadata, + emptyWorkspaceManifest, + generateWorkspaceInfo, + normalizeWorkspacePath, + upsertWorkspaceInfo, + WorkspaceInfoSchema, + WorkspaceManifestSchema, +} from "./session/workspace-manifest"; +export { + type MigrateLegacyProviderSettingsOptions, + type MigrateLegacyProviderSettingsResult, + migrateLegacyProviderSettings, +} from "./storage/provider-settings-legacy-migration"; +export { ProviderSettingsManager } from "./storage/provider-settings-manager"; +export { SqliteSessionStore } from "./storage/sqlite-session-store"; diff --git a/sdk/apps/cli/src/commands/rpc-runtime/provider-registry.ts b/sdk/packages/core/src/providers/local-provider-service.ts similarity index 73% rename from sdk/apps/cli/src/commands/rpc-runtime/provider-registry.ts rename to sdk/packages/core/src/providers/local-provider-service.ts index 45cc4d04c5..048bdc7c84 100644 --- a/sdk/apps/cli/src/commands/rpc-runtime/provider-registry.ts +++ b/sdk/packages/core/src/providers/local-provider-service.ts @@ -1,15 +1,45 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; +import type { providers as LlmsProviders } from "@clinebot/llms"; +import { models } from "@clinebot/llms"; import type { RpcAddProviderActionRequest, + RpcOAuthProviderId, RpcProviderCapability, RpcProviderListItem, RpcProviderModel, RpcSaveProviderSettingsActionRequest, -} from "@clinebot/core"; -import type { ProviderSettingsManager } from "@clinebot/core/server"; -import { models } from "@clinebot/llms"; -import type { StoredModelsFile } from "./types"; +} from "@clinebot/shared"; +import { createOAuthClientCallbacks } from "../auth/client"; +import { loginClineOAuth } from "../auth/cline"; +import { loginOpenAICodex } from "../auth/codex"; +import { loginOcaOAuth } from "../auth/oca"; +import type { ProviderSettingsManager } from "../storage/provider-settings-manager"; + +type StoredModelsFile = { + version: 1; + providers: Record< + string, + { + provider: { + name: string; + baseUrl: string; + defaultModelId?: string; + capabilities?: RpcProviderCapability[]; + modelsSourceUrl?: string; + }; + models: Record< + string, + { + id: string; + name: string; + supportsVision?: boolean; + supportsAttachments?: boolean; + } + >; + } + >; +}; function resolveVisibleApiKey(settings: { apiKey?: string; @@ -132,18 +162,10 @@ function toModelCapabilities( if (!capabilities || capabilities.length === 0) { return [...next]; } - if (capabilities.includes("streaming")) { - next.add("streaming"); - } - if (capabilities.includes("tools")) { - next.add("tools"); - } - if (capabilities.includes("reasoning")) { - next.add("reasoning"); - } - if (capabilities.includes("prompt-cache")) { - next.add("prompt-cache"); - } + if (capabilities.includes("streaming")) next.add("streaming"); + if (capabilities.includes("tools")) next.add("tools"); + if (capabilities.includes("reasoning")) next.add("reasoning"); + if (capabilities.includes("prompt-cache")) next.add("prompt-cache"); if (capabilities.includes("vision")) { next.add("images"); next.add("files"); @@ -207,9 +229,7 @@ function parseModelIdList(input: unknown): string[] { if (Array.isArray(input)) { return input .map((item) => { - if (typeof item === "string") { - return item.trim(); - } + if (typeof item === "string") return item.trim(); if (item && typeof item === "object" && "id" in item) { const id = (item as { id?: unknown }).id; return typeof id === "string" ? id.trim() : ""; @@ -226,21 +246,15 @@ function extractModelIdsFromPayload( providerId: string, ): string[] { const rootArray = parseModelIdList(payload); - if (rootArray.length > 0) { - return rootArray; - } - if (!payload || typeof payload !== "object") { - return []; - } + if (rootArray.length > 0) return rootArray; + if (!payload || typeof payload !== "object") return []; const data = payload as { data?: unknown; models?: unknown; providers?: Record; }; const direct = parseModelIdList(data.data ?? data.models); - if (direct.length > 0) { - return direct; - } + if (direct.length > 0) return direct; if ( data.models && typeof data.models === "object" && @@ -249,17 +263,13 @@ function extractModelIdsFromPayload( const modelKeys = Object.keys(data.models).filter( (key) => key.trim().length > 0, ); - if (modelKeys.length > 0) { - return modelKeys; - } + if (modelKeys.length > 0) return modelKeys; } const providerScoped = data.providers?.[providerId]; if (providerScoped && typeof providerScoped === "object") { const nested = providerScoped as { models?: unknown }; const nestedList = parseModelIdList(nested.models ?? providerScoped); - if (nestedList.length > 0) { - return nestedList; - } + if (nestedList.length > 0) return nestedList; } return []; } @@ -278,7 +288,7 @@ async function fetchModelIdsFromSource( return extractModelIdsFromPayload(payload, providerId); } -export async function addProvider( +export async function addLocalProvider( manager: ProviderSettingsManager, request: RpcAddProviderActionRequest, ): Promise<{ @@ -288,20 +298,14 @@ export async function addProvider( modelsCount: number; }> { const providerId = request.providerId.trim().toLowerCase(); - if (!providerId) { - throw new Error("providerId is required"); - } + if (!providerId) throw new Error("providerId is required"); if (models.hasProvider(providerId)) { throw new Error(`provider "${providerId}" already exists`); } const providerName = request.name.trim(); - if (!providerName) { - throw new Error("name is required"); - } + if (!providerName) throw new Error("name is required"); const baseUrl = request.baseUrl.trim(); - if (!baseUrl) { - throw new Error("baseUrl is required"); - } + if (!baseUrl) throw new Error("baseUrl is required"); const typedModels = (request.models ?? []) .map((model) => model.trim()) @@ -379,7 +383,9 @@ export async function addProvider( }; } -export async function listProviders(manager: ProviderSettingsManager): Promise<{ +export async function listLocalProviders( + manager: ProviderSettingsManager, +): Promise<{ providers: RpcProviderListItem[]; settingsPath: string; }> { @@ -417,7 +423,7 @@ export async function listProviders(manager: ProviderSettingsManager): Promise<{ }; } -export async function getProviderModels( +export async function getLocalProviderModels( providerId: string, ): Promise<{ providerId: string; models: RpcProviderModel[] }> { const id = providerId.trim(); @@ -436,7 +442,7 @@ export async function getProviderModels( }; } -export function saveProviderSettings( +export function saveLocalProviderSettings( manager: ProviderSettingsManager, request: RpcSaveProviderSettingsActionRequest, ): { providerId: string; enabled: boolean; settingsPath: string } { @@ -491,3 +497,95 @@ export function saveProviderSettings( settingsPath: manager.getFilePath(), }; } + +export function normalizeOAuthProvider(provider: string): RpcOAuthProviderId { + const normalized = provider.trim().toLowerCase(); + if (normalized === "codex" || normalized === "openai-codex") { + return "openai-codex"; + } + if (normalized === "cline" || normalized === "oca") { + return normalized; + } + throw new Error( + `provider "${provider}" does not support OAuth login (supported: cline, oca, openai-codex)`, + ); +} + +function toProviderApiKey( + providerId: RpcOAuthProviderId, + credentials: { access: string }, +): string { + if (providerId === "cline") { + return `workos:${credentials.access}`; + } + return credentials.access; +} + +export async function loginLocalProvider( + providerId: RpcOAuthProviderId, + existing: LlmsProviders.ProviderSettings | undefined, + openUrl: (url: string) => void, +): Promise<{ + access: string; + refresh: string; + expires: number; + accountId?: string; +}> { + const callbacks = createOAuthClientCallbacks({ + onPrompt: async (prompt) => prompt.defaultValue ?? "", + openUrl, + onOpenUrlError: ({ error }) => { + throw error instanceof Error ? error : new Error(String(error)); + }, + }); + + if (providerId === "cline") { + return loginClineOAuth({ + apiBaseUrl: existing?.baseUrl?.trim() || "https://api.cline.bot", + callbacks, + }); + } + if (providerId === "oca") { + return loginOcaOAuth({ + mode: existing?.oca?.mode, + callbacks, + }); + } + return loginOpenAICodex(callbacks); +} + +export function saveLocalProviderOAuthCredentials( + manager: ProviderSettingsManager, + providerId: RpcOAuthProviderId, + existing: LlmsProviders.ProviderSettings | undefined, + credentials: { + access: string; + refresh: string; + expires: number; + accountId?: string; + }, +): LlmsProviders.ProviderSettings { + const auth = { + ...(existing?.auth ?? {}), + accessToken: toProviderApiKey(providerId, credentials), + refreshToken: credentials.refresh, + accountId: credentials.accountId, + } as LlmsProviders.ProviderSettings["auth"] & { expiresAt?: number }; + auth.expiresAt = credentials.expires; + const merged: LlmsProviders.ProviderSettings = { + ...(existing ?? { + provider: providerId as LlmsProviders.ProviderSettings["provider"], + }), + provider: providerId as LlmsProviders.ProviderSettings["provider"], + auth, + }; + manager.saveProviderSettings(merged, { tokenSource: "oauth" }); + return merged; +} + +export function resolveLocalClineAuthToken( + settings: LlmsProviders.ProviderSettings | undefined, +): string | undefined { + const token = settings?.auth?.accessToken?.trim() || settings?.apiKey?.trim(); + return token && token.length > 0 ? token : undefined; +} diff --git a/sdk/packages/core/src/server/index.ts b/sdk/packages/core/src/server/index.ts deleted file mode 100644 index 40ed1fc1ab..0000000000 --- a/sdk/packages/core/src/server/index.ts +++ /dev/null @@ -1,321 +0,0 @@ -/** - * @clinebot/core/server - * - * Node/runtime services for host applications (CLI, desktop runtime, servers). - * Includes RPC server/client bindings — requires @clinebot/rpc at runtime. - */ - -export { - getRpcServerDefaultAddress, - getRpcServerHealth, - type RpcRuntimeBridgeCommand, - type RpcRuntimeBridgeCommandOutputLine, - type RpcRuntimeBridgeControlLine, - type RpcRuntimeBridgeOutputLine, - type RpcRuntimeBridgeRequestEnvelope, - type RpcRuntimeBridgeResponseEnvelope, - RpcRuntimeChatClient, - type RpcRuntimeEvent, - type RpcRuntimeHandlers, - type RpcRuntimeStreamStop, - type RpcScheduleExecution, - type RpcScheduleExecutionStatus, - type RpcScheduleMode, - type RpcScheduleRecord, - type RpcServerHandle, - type RpcServerOptions, - type RpcSessionBackend, - RpcSessionClient, - type RpcSessionRow, - type RpcSessionStatus, - type RpcSessionUpdateInput, - type RpcSpawnQueueItem, - type RpcStreamTeamProgressHandlers, - registerRpcClient, - requestRpcServerShutdown, - runRpcRuntimeCommandBridge, - runRpcRuntimeEventBridge, - startRpcServer, - stopRpcServer, -} from "@clinebot/rpc"; -export { - type ClineAccountBalance, - type ClineAccountOperations, - type ClineAccountOrganization, - type ClineAccountOrganizationBalance, - type ClineAccountOrganizationUsageTransaction, - type ClineAccountPaymentTransaction, - ClineAccountService, - type ClineAccountServiceOptions, - type ClineAccountUsageTransaction, - type ClineAccountUser, - executeRpcClineAccountAction, - isRpcClineAccountActionRequest, - RpcClineAccountService, - type RpcProviderActionExecutor, -} from "../account"; -export type { - AgentConfigWatcher, - AgentConfigWatcherEvent, - AgentYamlConfig, - BuildAgentConfigOverridesOptions, - CreateAgentConfigWatcherOptions, - CreateInstructionWatcherOptions, - CreateRulesConfigDefinitionOptions, - CreateSkillsConfigDefinitionOptions, - CreateUserInstructionConfigWatcherOptions, - CreateWorkflowsConfigDefinitionOptions, - HookConfigFileEntry, - LoadAgentPluginFromPathOptions, - ParseMarkdownFrontmatterResult, - ParseYamlFrontmatterResult, - ResolveAgentPluginPathsOptions, - RuleConfig, - SkillConfig, - UnifiedConfigDefinition, - UnifiedConfigFileCandidate, - UnifiedConfigFileContext, - UnifiedConfigRecord, - UnifiedConfigWatcherEvent, - UnifiedConfigWatcherOptions, - UserInstructionConfig, - UserInstructionConfigType, - UserInstructionConfigWatcher, - UserInstructionConfigWatcherEvent, - WorkflowConfig, -} from "../agents"; -export { - createAgentConfigDefinition, - createAgentConfigWatcher, - createRulesConfigDefinition, - createSkillsConfigDefinition, - createUserInstructionConfigWatcher, - createWorkflowsConfigDefinition, - discoverPluginModulePaths, - HOOK_CONFIG_FILE_EVENT_MAP, - HOOKS_CONFIG_DIRECTORY_NAME, - HookConfigFileName, - listHookConfigFiles, - loadAgentPluginFromPath, - loadAgentPluginsFromPaths, - parseAgentConfigFromYaml, - parsePartialAgentConfigFromYaml, - parseRuleConfigFromMarkdown, - parseSkillConfigFromMarkdown, - parseWorkflowConfigFromMarkdown, - RULES_CONFIG_DIRECTORY_NAME, - resolveAgentPluginPaths, - resolveAgentTools, - resolveAndLoadAgentPlugins, - resolveDocumentsHooksDirectoryPath, - resolveDocumentsRulesDirectoryPath, - resolveDocumentsWorkflowsDirectoryPath, - resolveHooksConfigSearchPaths, - resolvePluginConfigSearchPaths, - resolveRulesConfigSearchPaths, - resolveSkillsConfigSearchPaths, - resolveWorkflowsConfigSearchPaths, - SKILLS_CONFIG_DIRECTORY_NAME, - toHookConfigFileName, - toPartialAgentConfig, - UnifiedConfigFileWatcher, - WORKFLOWS_CONFIG_DIRECTORY_NAME, -} from "../agents"; -export { - createOAuthClientCallbacks, - type OAuthClientCallbacksOptions, -} from "../auth/client"; -export { - createClineOAuthProvider, - getValidClineCredentials, - loginClineOAuth, - refreshClineToken, -} from "../auth/cline"; -export { - getValidOpenAICodexCredentials, - isOpenAICodexTokenExpired, - loginOpenAICodex, - normalizeOpenAICodexCredentials, - openaiCodexOAuthProvider, - refreshOpenAICodexToken, -} from "../auth/codex"; -export { - createOcaOAuthProvider, - createOcaRequestHeaders, - DEFAULT_EXTERNAL_IDCS_CLIENT_ID, - DEFAULT_EXTERNAL_IDCS_SCOPES, - DEFAULT_EXTERNAL_IDCS_URL, - DEFAULT_EXTERNAL_OCA_BASE_URL, - DEFAULT_INTERNAL_IDCS_CLIENT_ID, - DEFAULT_INTERNAL_IDCS_SCOPES, - DEFAULT_INTERNAL_IDCS_URL, - DEFAULT_INTERNAL_OCA_BASE_URL, - generateOcaOpcRequestId, - getValidOcaCredentials, - loginOcaOAuth, - OCI_HEADER_OPC_REQUEST_ID, - refreshOcaToken, -} from "../auth/oca"; -export { startLocalOAuthServer } from "../auth/server"; -export type { - OAuthCredentials, - OAuthLoginCallbacks, - OAuthPrompt, - OAuthProviderInterface, - OcaClientMetadata, - OcaMode, - OcaOAuthConfig, - OcaOAuthEnvironmentConfig, - OcaOAuthProviderOptions, - OcaTokenResolution, -} from "../auth/types"; -export { - ALL_DEFAULT_TOOL_NAMES, - type AskQuestionExecutor, - type CreateBuiltinToolsOptions, - type CreateDefaultToolsOptions, - createBuiltinTools, - createDefaultExecutors, - createDefaultTools, - createDefaultToolsWithPreset, - createToolPoliciesWithPreset, - type DefaultExecutorsOptions, - type DefaultToolName, - DefaultToolNames, - type DefaultToolsConfig, - type ToolExecutors, - type ToolPolicyPresetName, - type ToolPresetName, - ToolPresets, -} from "../default-tools"; -export type { - FastFileIndexOptions, - MentionEnricherOptions, - MentionEnrichmentResult, -} from "../input"; -export { - enrichPromptWithMentions, - getFileIndex, - prewarmFileIndex, -} from "../input"; - -export type { - LoadMcpSettingsOptions, - McpConnectionStatus, - McpManager, - McpManagerOptions, - McpServerClient, - McpServerClientFactory, - McpServerRegistration, - McpServerSnapshot, - McpServerTransportConfig, - McpSettingsFile, - McpSseTransportConfig, - McpStdioTransportConfig, - McpStreamableHttpTransportConfig, - RegisterMcpServersFromSettingsOptions, -} from "../mcp"; -export { - hasMcpSettingsFile, - InMemoryMcpManager, - loadMcpSettingsFile, - registerMcpServersFromSettingsFile, - resolveDefaultMcpSettingsPath, - resolveMcpServerRegistrations, -} from "../mcp"; -export { - formatRulesForSystemPrompt, - isRuleEnabled, - listEnabledRulesFromWatcher, - loadRulesForSystemPromptFromWatcher, -} from "../runtime/rules"; -export { - createTeamName, - DefaultRuntimeBuilder, -} from "../runtime/runtime-builder"; -export { - type SandboxCallOptions, - SubprocessSandbox, - type SubprocessSandboxOptions, -} from "../runtime/sandbox/subprocess-sandbox"; -export type { - BuiltRuntime, - RuntimeBuilder, - RuntimeBuilderInput, - SessionRuntime, -} from "../runtime/session-runtime"; -export { - type DesktopToolApprovalOptions, - requestDesktopToolApproval, -} from "../runtime/tool-approval"; -export type { AvailableWorkflow } from "../runtime/workflows"; -export { - listAvailableWorkflowsFromWatcher, - resolveWorkflowSlashCommandFromWatcher, -} from "../runtime/workflows"; -export { DefaultSessionManager } from "../session/default-session-manager"; -export { RpcCoreSessionService } from "../session/rpc-session-service"; -export { - deriveSubsessionStatus, - makeSubSessionId, - makeTeamTaskSubSessionId, - sanitizeSessionToken, -} from "../session/session-graph"; -export type { - CreateSessionHostOptions, - SessionBackend, - SessionHost, -} from "../session/session-host"; -export { - createSessionHost, - resolveSessionBackend, -} from "../session/session-host"; -export type { - SendSessionInput, - SessionAccumulatedUsage, - SessionManager, - StartSessionInput, - StartSessionResult, -} from "../session/session-manager"; -export type { SessionManifest } from "../session/session-manifest"; -export type { - CreateRootSessionWithArtifactsInput, - RootSessionArtifacts, -} from "../session/session-service"; -export { CoreSessionService } from "../session/session-service"; -export { - createSqliteRpcSessionBackend, - SqliteRpcSessionBackend, - type SqliteRpcSessionBackendOptions, -} from "../session/sqlite-rpc-session-backend"; -export type { - WorkspaceManager, - WorkspaceManagerEvent, -} from "../session/workspace-manager"; -export { InMemoryWorkspaceManager } from "../session/workspace-manager"; -export type { WorkspaceManifest } from "../session/workspace-manifest"; -export { - buildWorkspaceMetadata, - emptyWorkspaceManifest, - generateWorkspaceInfo, - normalizeWorkspacePath, - upsertWorkspaceInfo, - WorkspaceInfoSchema, - WorkspaceManifestSchema, -} from "../session/workspace-manifest"; -export { - type MigrateLegacyProviderSettingsOptions, - type MigrateLegacyProviderSettingsResult, - migrateLegacyProviderSettings, -} from "../storage/provider-settings-legacy-migration"; -export { ProviderSettingsManager } from "../storage/provider-settings-manager"; -export { SqliteSessionStore } from "../storage/sqlite-session-store"; -export type { SessionStatus } from "../types/common"; -export { SESSION_STATUSES, SessionSource } from "../types/common"; -export type { - CoreAgentMode, - CoreModelConfig, - CoreRuntimeFeatures, - CoreSessionConfig, -} from "../types/config"; -export type { WorkspaceInfo } from "../types/workspace"; diff --git a/sdk/packages/core/tsconfig.build.json b/sdk/packages/core/tsconfig.build.json index 60fb064582..4a8a6ca3ed 100644 --- a/sdk/packages/core/tsconfig.build.json +++ b/sdk/packages/core/tsconfig.build.json @@ -9,10 +9,5 @@ "rootDir": "./src", "incremental": false }, - "include": [ - "src/index.ts", - "src/index.node.ts", - "src/index.browser.ts", - "src/server/index.ts" - ] + "include": ["src/index.ts", "src/index.node.ts", "src/index.browser.ts"] } diff --git a/sdk/packages/rpc/README.md b/sdk/packages/rpc/README.md index 856c31dd4e..8c9e7c16a8 100644 --- a/sdk/packages/rpc/README.md +++ b/sdk/packages/rpc/README.md @@ -72,7 +72,7 @@ These allow host clients (for example code/desktop apps) to share one implementa - `startRpcServer(...)` now requires a `sessionBackend` implementation via `RpcServerOptions`. - Session persistence contracts live in `RpcSessionBackend` / `RpcSessionRow` / `RpcSessionUpdateInput`. -- `@clinebot/core/server` provides a ready-to-use SQLite backend (`createSqliteRpcSessionBackend`). +- `@clinebot/core/node` provides a ready-to-use SQLite backend (`createSqliteRpcSessionBackend`). - Scheduled execution orchestration is provided via `@clinebot/scheduler` and is hosted inside the same RPC server process. - Runtime shutdown can now include host cleanup via optional `RpcRuntimeHandlers.dispose()`, which `startRpcServer(...)/stopRpcServer()` invokes during server stop. diff --git a/sdk/scripts/verify-packages.ts b/sdk/scripts/verify-packages.ts index 532b4870a7..f2585dde34 100644 --- a/sdk/scripts/verify-packages.ts +++ b/sdk/scripts/verify-packages.ts @@ -236,6 +236,68 @@ try { } } + console.log("\n--- Verifying publish-only package invariants ---"); + for (const pkg of published) { + if (pkg.name !== "@clinebot/core") { + continue; + } + + const testFile = join(testDir, `test-${pkg.workspace}-publish-shape.ts`); + await writeFile( + testFile, + [ + `import { readFileSync } from "node:fs";`, + `import { join } from "node:path";`, + `const pkgJson = JSON.parse(readFileSync(join(process.cwd(), "node_modules", "@clinebot", "core", "package.json"), "utf8"));`, + `if (pkgJson.exports?.["./node"]) {`, + ` console.error(" FAIL @clinebot/core: publish manifest still exposes ./node");`, + ` process.exit(1);`, + `}`, + `try {`, + ` await import("@clinebot/core/node");`, + ` console.error(" FAIL @clinebot/core: @clinebot/core/node should not be importable from published package");`, + ` process.exit(1);`, + `} catch (error) {`, + ` const message = error instanceof Error ? error.message : String(error);`, + ` if (!message.includes("Package subpath") && !message.includes("Cannot find module") && !message.includes("Cannot find package")) {`, + ` console.error(" FAIL @clinebot/core: unexpected error when importing removed subpath:", message);`, + ` process.exit(1);`, + ` }`, + `}`, + `console.log(" OK @clinebot/core publish shape");`, + ].join("\n"), + ); + try { + const proc = Bun.spawn(["bun", testFile], { + cwd: testDir, + stdout: "pipe", + stderr: "pipe", + }); + const result = await Promise.race([ + proc.exited, + new Promise((_, reject) => + setTimeout(() => { + proc.kill(); + reject(new Error("timed out after 30s")); + }, 30_000), + ), + ]); + const stdout = await new Response(proc.stdout).text(); + const stderr = await new Response(proc.stderr).text(); + const output = (stdout + stderr).trim(); + if (output) console.log(output); + if (result !== 0 || output.includes("FAIL")) { + importFailed = true; + } + } catch (e: unknown) { + importFailed = true; + const msg = e instanceof Error ? e.message : String(e); + console.error( + ` FAIL — could not verify publish shape for ${pkg.name}: ${msg}`, + ); + } + } + if (importFailed) { console.error("\nSome packages failed to import."); exitCode = 1; diff --git a/sdk/scripts/version.ts b/sdk/scripts/version.ts index cbe68f70b8..81e55e14a7 100644 --- a/sdk/scripts/version.ts +++ b/sdk/scripts/version.ts @@ -88,6 +88,20 @@ for (const workspace of workspaces) { } } } + + if (pkg.name === "@clinebot/core") { + pkg.main = "./dist/index.js"; + pkg.types = "./dist/index.d.ts"; + if (pkg.exports?.["."]) { + pkg.exports["."] = { + browser: "./dist/index.browser.js", + development: "./dist/index.js", + types: "./dist/index.d.ts", + import: "./dist/index.js", + }; + } + delete pkg.exports?.["./node"]; + } } const out = `${JSON.stringify(pkg, null, "\t")}\n`;