Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/collapse-tilde-facades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
"@tryopenbot/agent-provider": minor
"@tryopenbot/agent-service-provider": minor
"@tryopenbot/auth-provider": minor
"openbot": minor
"@tryopenbot/computer-service-provider": minor
"@tryopenbot/client-runtime": minor
"@tryopenbot/computer-tools": minor
"@tryopenbot/computer-service": minor
"@tryopenbot/computer-service-proto": minor
"@tryopenbot/configuration": minor
"@tryopenbot/desktop": minor
"@tryopenbot/utilities": minor
"@tryopenbot/platform-integrations": minor
"@tryopenbot/control-service-provider": minor
"@tryopenbot/runtime-provider": minor
"@tryopenbot/control-service": minor
"@tryopenbot/ui": minor
"@tryopenbot/web": minor
"@tryopenbot/git-provider": minor
---

Use native Tilde plugin, connector, routine, and signal resources through one authenticated allowlisted bridge, and remove the corresponding control-service route APIs.

Plugin inventory now pages Tilde's native MCP, skill, provider, and registry collections directly; it no longer depends on Tilde's OpenBot-specific aggregate catalogue or its first-page limit.

Routines now consume Tilde's native trigger/version contract, and signal history uses native trigger IDs while accepting legacy rule IDs during the migration window. Signal provider and instance inventories follow every continuation token.

Development agent creation retains the completed source-generation result until asynchronous Tilde bundle provisioning becomes active, so queued provisioning no longer turns the next status poll into “job not found”.

Fresh installations and future agents now explicitly select ChatKit `agentLoop` response mode, matching the required SDK endpoint contract.

The ChatKit credential bridge now permits only the workspace, queue, observation, and attachment operations used by Client Runtime instead of forwarding the complete ChatKit namespace.

Migration:
- Replace direct calls to `/api/plugins`, `/api/connectors`, `/api/routines`, and `/api/signals` with `@tryopenbot/client-runtime`.
- Replace `registerConnectorRoutes` with `registerConnectorAuthorizedRoute` when constructing a custom control service.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ The production build stages the web app in the control provider's `.vercel/outpu
- `packages/agent-service-provider` owns Eve-compatible agent-directory discovery, instrumentation startup, concurrent per-agent Vercel bundles, the local agent server, and deployment.
- `apps/web` owns the workspace, agent selection, conversation composer, and frontend routes.
- `packages/client-runtime` owns grouped UI contracts, Tilde REST/SSE parsing, live-event reducers, and shared Zustand vanilla state without platform APIs. Every major UX surface and state interaction goes through it; renderers keep only presentation-only state.
- `apps/control-service` owns the portable Hono application, built web UI fallback, `/healthz`, the allowlisted Tilde ChatKit REST/SSE bridge under `/api/chat/*`, and the local control-service entrypoint.
- `apps/control-service` owns the portable Hono application, built web UI fallback, `/healthz`, raw operation-allowlisted Tilde bridges under `/api/chat/*` and `/api/tilde/*`, and the local control-service entrypoint. Plugin, connector, routine, and signal projections belong to Client Runtime rather than control-service domain facades.
- `packages/computer-service-proto` owns the API-key-protected internal computer API.
- `packages/git-provider` owns brokered GitHub access: the Tilde-managed GitHub App credential and the REST and git-over-HTTPS reverse-proxy profiles used by the trusted development sandbox and the factory agent.
- No control database is retained while the reset application has no persisted control state.
Expand Down
13 changes: 7 additions & 6 deletions apps/control-service/README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
# @tryopenbot/control-service

The portable Hono control application. It serves health, exposes an allowlisted same-origin Tilde ChatKit REST bridge under `/api/chat/*`, exchanges an HttpOnly browser session for a single-use registered-Origin ticket or an authenticated native bearer for an Origin-free native ticket, and serves the built web UI with SPA fallback both locally and in a Vercel Function. Client Runtime uses that ticket to connect directly to Tilde's team WebSocket.
The portable Hono control application. It serves health, exposes raw allowlisted same-origin Tilde bridges under `/api/chat/*` and `/api/tilde/*`, exchanges an HttpOnly browser session for a single-use registered-Origin ticket or an authenticated native bearer for an Origin-free native ticket, and serves the built web UI with SPA fallback both locally and in a Vercel Function. Client Runtime uses that ticket to connect directly to Tilde's team WebSocket and projects Tilde-owned settings resources without domain facades in this service.

## Public API

- `app` is the configured Web-standard Hono application exported for local and provider-generated entrypoints.
- `createApp(options)` constructs the portable control application with its configured authentication, Computer preview, ChatKit proxy, background agent-creation executor, and web-root behavior.
- `registerOwnerAuth(app, provider, options)` installs browser PKCE login, callback, session, and logout routes. Development options preserve a validated loopback browser origin through the Vite proxy.
- `requireOwner(provider, options)` returns the owner-authentication middleware used to protect browser-facing control routes.
- `registerTildeChatProxy(app, options)` preserves Tilde ChatKit request, response, and attachment semantics and exposes only the short-lived ChatKit realtime ticket needed for a direct browser WebSocket.
- `registerTildeChatProxy(app, options)` preserves Tilde ChatKit request, response, and attachment semantics for an exact Client Runtime operation allowlist and exposes only the short-lived ChatKit realtime ticket needed for a direct browser WebSocket.
- `registerTildeProxy(app, options)` preserves request and response bodies for a strict allowlist of Tilde-owned settings operations while keeping the installation API key out of clients.
- `registerComputerPreview(app, provider, options)` exposes the narrow owner preview redirect without making Computer service browser-accessible.
- `registerConnectorRoutes(app, options)` serves owner-authenticated connector (Tilde tool-provider) configuration under `/api/connectors/*` — provider catalog, enabled accounts, and new-account creation that encrypts credentials server-side and starts brokered OAuth — plus the public `/connectors/authorized` OAuth return page that returns Electron flows to the `openbot://` deep link.
- `registerConnectorAuthorizedRoute(app)` serves only the public OAuth completion page that bounces desktop flows to the `openbot://` deep link. Connector resources and setup use native Tilde APIs through `registerTildeProxy`.

The package default application also exposes `GET /healthz`. There is no owner-facing ConnectRPC surface or pairing-code setup route.

Owner-authenticated `POST /api/agents` starts `openbot new-agent` inside the trusted development
Computer as a background job. `GET /api/agents/setup/:jobId` reports that job without exposing the
Computer API key or shell output to the browser. When Tilde is configured, the status route also
establishes the new ChatKit Agent Resource Bundle with the deployment API key delegated by the
signed-in human, so later machine-only deployments preserve that individual lifecycle owner.
Computer API key or shell output to the browser. The command owns source creation and idempotent
Tilde reconciliation; the status route does not provision a second time or require a separate
human credential.
74 changes: 15 additions & 59 deletions apps/control-service/src/agent-create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,12 @@ import { createConnectTransport } from "@connectrpc/connect-node";
import type { Hono } from "hono";
import { ComputerService } from "@tryopenbot/computer-service-proto";
import { agentIdFromName } from "@tryopenbot/utilities";
import { tildeJson, tildeOptionsFromEnvironment } from "./tilde-upstream.js";

export interface AgentCreationOptions {
environment?: NodeJS.ProcessEnv;
repositoryRoot?: string;
execute?: AgentCreationExecutor;
awaitExecution?: AgentCreationWaiter;
tildeFetch?: typeof globalThis.fetch;
}

export interface AgentCreationRequest {
Expand Down Expand Up @@ -130,68 +128,28 @@ export function registerAgentCreation(app: Hono, options: AgentCreationOptions =
{ authorization: apiKey ? `Bearer ${apiKey}` : "", signal: context.req.raw.signal },
);
if (response.running) return context.json({ status: "setting_up" });
if (response.exitCode !== 0)
if (response.exitCode !== 0) {
localCreation?.forget(jobId);
return context.json({ status: "failed", error: commandError(response) });
}
const created = parseCreatedAgent(response.stdout);
if (!created)
if (!created) {
localCreation?.forget(jobId);
return context.json({ status: "failed", error: "Agent creation returned no result" });
const tilde = tildeOptionsFromEnvironment(environment);
if (tilde && options.tildeFetch) tilde.fetch = options.tildeFetch;
const agentServiceOrigin = environment.AGENT_SERVICE_ORIGIN?.trim();
const headerAuthorization = context.req.header("authorization");
const ownerAccessToken = context.get("ownerAccessToken") as string | undefined;
const authorization = ownerAccessToken ? `Bearer ${ownerAccessToken}` : headerAuthorization;
if (tilde && agentServiceOrigin) {
if (!authorization)
return context.json({
status: "failed",
error: "Owner authorization is unavailable for Tilde agent provisioning",
});
try {
const operation = (await tildeJson(
tilde,
`/chatkit/agents/${encodeURIComponent(created.id)}/provision`,
{
method: "PUT",
authorization,
body: {
agent: {
display_name: created.name,
endpoint: {
url: new URL(`/api/agents/${created.id}`, `${agentServiceOrigin}/`).toString(),
streaming: true,
timeout_ms: 300_000,
local_running_endpoint: false,
concurrency_policy: "queue",
},
status: "enabled",
credential_strategy: "preserve",
},
},
},
)) as { status?: string; error_message?: string };
if (operation.status === "error")
return context.json({
status: "failed",
error: operation.error_message ?? "Tilde agent provisioning failed",
});
if (operation.status !== "active")
return context.json({ status: "setting_up", job_id: jobId, agent: created });
} catch (error) {
return context.json({
status: "failed",
error: error instanceof Error ? error.message : "Tilde agent provisioning failed",
});
}
}
localCreation?.forget(jobId);
return context.json({ status: "ready", agent: created });
});
}

function createLocalAgentCreation(
repositoryRoot: string,
environment: NodeJS.ProcessEnv = process.env,
): { execute: AgentCreationExecutor; awaitExecution: AgentCreationWaiter } {
): {
execute: AgentCreationExecutor;
awaitExecution: AgentCreationWaiter;
forget(jobId: string): boolean;
} {
const jobs = new Map<string, AgentCreationResult>();
return {
execute: async (request) => {
Expand Down Expand Up @@ -222,16 +180,14 @@ function createLocalAgentCreation(
});
return jobs.get(jobId)!;
},
awaitExecution: async ({ jobId }) => {
const result = jobs.get(jobId) ?? {
awaitExecution: async ({ jobId }) =>
jobs.get(jobId) ?? {
exitCode: 1,
stdout: "",
stderr: "Agent creation job was not found",
jobId,
};
if (!result.running) jobs.delete(jobId);
return result;
},
},
forget: (jobId: string) => jobs.delete(jobId),
};
}

Expand Down
Loading