Add git, gh, openssh-client, curl, and sudo to the computer image with a docker smoke test. Mount deployment-owner project workspacePath at /home/rakazo/project, align /home/rakazo file paths with shell, and resolve default branch from origin/HEAD. Inject the owner GitHub token from encrypted deployment settings only into project computers; enforce branch-only push, block force-push, and require approval before the first push or gh pr in a run. Add runaway guards (tool fuse and stall pause), collapse live narration, fix streaming progress merge, DE run strings, and Lead as the project main Oo name. Fix evo-deploy test flake. Co-authored-by: cynarAI <cynarAI@users.noreply.github.com>
480 lines
17 KiB
TypeScript
480 lines
17 KiB
TypeScript
import type {
|
|
AdapterContext,
|
|
AdapterDescriptor,
|
|
AgentRunRequest,
|
|
AgentRuntimeCapabilities,
|
|
AgentRuntimeEvent,
|
|
ArtifactByteRange,
|
|
ArtifactPut,
|
|
ArtifactStreamPut,
|
|
BackgroundJob,
|
|
BackgroundJobHandlers,
|
|
BrowserActRequest,
|
|
BrowserActResult,
|
|
BrowserCapabilities,
|
|
BrowserNavigateRequest,
|
|
BrowserNavigateResult,
|
|
BrowserSnapshotRequest,
|
|
BrowserSnapshotResult,
|
|
CloudAgentCapabilities,
|
|
CloudAgentHandle,
|
|
CloudAgentLaunchRequest,
|
|
CloudAgentReplyRequest,
|
|
CloudAgentSnapshot,
|
|
CommandRequest,
|
|
ComputerActionRequest,
|
|
ComputerActionResult,
|
|
ComputerFileEntry,
|
|
ComputerInput,
|
|
ComputerObservation,
|
|
ComputerRef,
|
|
ConnectorCall,
|
|
ConnectorCapabilities,
|
|
ConnectorCatalogItem,
|
|
ConnectorEvent,
|
|
ConnectorTool,
|
|
ControlLeaseRef,
|
|
EmbeddingCapabilities,
|
|
MemoryCapabilities,
|
|
MemoryCommitRequest,
|
|
MemoryExportRequest,
|
|
MemoryReadRequest,
|
|
MemoryRevision,
|
|
MemorySearchRequest,
|
|
MemorySearchResult,
|
|
MemorySnapshot,
|
|
MessagingInboundEvent,
|
|
MessagingPlatformDescriptor,
|
|
MessagingSendRequest,
|
|
MessagingSendResult,
|
|
NotificationMessage,
|
|
PageBrowserCommand,
|
|
PageBrowserResult,
|
|
PortableFile,
|
|
PortableFileStream,
|
|
ProcessEvent,
|
|
SandboxCapabilities,
|
|
ScreenRequest,
|
|
ScreenSession,
|
|
SecretRecord,
|
|
SemanticMemoryCapabilities,
|
|
SemanticMemoryForgetRequest,
|
|
SemanticMemoryPurgeHistoryRequest,
|
|
SemanticMemoryRecallRequest,
|
|
SemanticMemoryResponse,
|
|
SemanticMemoryResult,
|
|
SemanticMemorySaveRequest,
|
|
SnapshotRef,
|
|
SpeechClip,
|
|
StoredArtifactStream,
|
|
TransactionalEmail,
|
|
VoiceCapabilities,
|
|
VoiceInfo,
|
|
VoiceSynthesizeRequest,
|
|
VoiceTranscribeRequest,
|
|
VoiceVerifyResult,
|
|
WebFetchCapabilities,
|
|
WebFetchRequest,
|
|
WebFetchResult,
|
|
WebSearchCapabilities,
|
|
WebSearchHit,
|
|
WebSearchRequest,
|
|
} from "./types.js";
|
|
|
|
export interface SandboxProvider {
|
|
describe(): AdapterDescriptor<SandboxCapabilities>;
|
|
/** Optional live browser on the same leased screen as observe/act. */
|
|
pageBrowser?(
|
|
computer: ComputerRef,
|
|
request: PageBrowserCommand,
|
|
context: AdapterContext,
|
|
): Promise<PageBrowserResult>;
|
|
/** Allocate or reconnect the computer, returning its reference before fallible setup. */
|
|
provision(
|
|
request: {
|
|
botId: string;
|
|
homePath: string;
|
|
projectWorkspacePath?: string;
|
|
providerRef?: string;
|
|
providerKind?: ComputerRef["kind"];
|
|
},
|
|
context: AdapterContext,
|
|
): Promise<ComputerRef>;
|
|
/** Perform idempotent provider setup after the lifecycle has captured the reference. */
|
|
prepare(computer: ComputerRef, context: AdapterContext): Promise<void>;
|
|
execute(
|
|
computer: ComputerRef,
|
|
request: CommandRequest,
|
|
context: AdapterContext,
|
|
): AsyncIterable<ProcessEvent>;
|
|
inspectBackgroundWork?(
|
|
computer: ComputerRef,
|
|
markerId: string,
|
|
context: AdapterContext,
|
|
): Promise<"active" | "idle" | "unknown">;
|
|
connectScreen(
|
|
computer: ComputerRef,
|
|
request: ScreenRequest,
|
|
context: AdapterContext,
|
|
): Promise<ScreenSession>;
|
|
setScreenControl?(
|
|
computer: ComputerRef,
|
|
interactive: boolean,
|
|
context: AdapterContext,
|
|
controlToken?: string,
|
|
): Promise<void>;
|
|
sendInput(
|
|
computer: ComputerRef,
|
|
input: ComputerInput,
|
|
lease: ControlLeaseRef,
|
|
context: AdapterContext,
|
|
): Promise<void>;
|
|
observe(computer: ComputerRef, context: AdapterContext): Promise<ComputerObservation>;
|
|
act(
|
|
computer: ComputerRef,
|
|
request: ComputerActionRequest,
|
|
context: AdapterContext,
|
|
): Promise<ComputerActionResult>;
|
|
listFiles(
|
|
computer: ComputerRef,
|
|
path: string,
|
|
context: AdapterContext,
|
|
): Promise<ComputerFileEntry[]>;
|
|
readFile(
|
|
computer: ComputerRef,
|
|
path: string,
|
|
context: AdapterContext,
|
|
options?: { maxBytes?: number },
|
|
): Promise<Uint8Array>;
|
|
writeFile(computer: ComputerRef, file: PortableFile, context: AdapterContext): Promise<void>;
|
|
/**
|
|
* Bounded-memory variants of readFile and writeFile for large media on computers without a
|
|
* transfer cap. Same path rules as their byte versions; a failed or aborted write leaves no file.
|
|
*/
|
|
readFileStream?(
|
|
computer: ComputerRef,
|
|
path: string,
|
|
context: AdapterContext,
|
|
options?: { maxBytes?: number },
|
|
): Promise<ReadableStream<Uint8Array>>;
|
|
writeFileStream?(
|
|
computer: ComputerRef,
|
|
file: PortableFileStream,
|
|
context: AdapterContext,
|
|
): Promise<void>;
|
|
exportWorkspace(computer: ComputerRef, context: AdapterContext): AsyncIterable<PortableFile>;
|
|
importWorkspace(
|
|
computer: ComputerRef,
|
|
files: AsyncIterable<PortableFile>,
|
|
context: AdapterContext,
|
|
): Promise<void>;
|
|
snapshot(computer: ComputerRef, context: AdapterContext): Promise<SnapshotRef>;
|
|
keepAlive?(computer: ComputerRef): Promise<void>;
|
|
/** Drop a single-screen graphical claim for this bot so another Team bot can use the display. */
|
|
releaseScreen?(computer: ComputerRef, context: AdapterContext): Promise<void>;
|
|
stop(computer: ComputerRef, context: AdapterContext): Promise<void>;
|
|
destroy(computer: ComputerRef, context: AdapterContext): Promise<void>;
|
|
}
|
|
|
|
export interface ConnectorProvider {
|
|
describe(): AdapterDescriptor<ConnectorCapabilities>;
|
|
discoverTools(context: AdapterContext): Promise<ConnectorTool[]>;
|
|
/** Resolve a lazy catalog call to its authoritative authorized tool before approval. */
|
|
resolveCall?(
|
|
call: ConnectorCall,
|
|
context: AdapterContext,
|
|
): Promise<{ call: ConnectorCall; tool: ConnectorTool } | undefined>;
|
|
execute(call: ConnectorCall, context: AdapterContext): AsyncIterable<ConnectorEvent>;
|
|
}
|
|
|
|
export interface ConnectionAuthProvider {
|
|
describe(): AdapterDescriptor<{ oauth: boolean }>;
|
|
begin(
|
|
request: { provider: string; redirectUrl: string },
|
|
context: AdapterContext,
|
|
): Promise<{ authorizationUrl: string | null; state: string }>;
|
|
complete(
|
|
request: { state: string; code?: string },
|
|
context: AdapterContext,
|
|
): Promise<{ connectionRef: string }>;
|
|
revoke(connectionRef: string, context: AdapterContext): Promise<void>;
|
|
}
|
|
|
|
/** A connector that also owns an end-user app catalog and connection lifecycle. */
|
|
export interface ManagedConnectorProvider
|
|
extends ConnectorProvider,
|
|
Omit<ConnectionAuthProvider, "describe"> {
|
|
catalog(context: AdapterContext, query?: string): Promise<ConnectorCatalogItem[]>;
|
|
listConnectedExternalIds(context: AdapterContext): Promise<string[]>;
|
|
connectionReady(context: AdapterContext, externalId: string): Promise<boolean>;
|
|
warmDirectory?(): Promise<void>;
|
|
}
|
|
|
|
export interface MemoryStore {
|
|
describe(): AdapterDescriptor<MemoryCapabilities>;
|
|
read(request: MemoryReadRequest, context: AdapterContext): Promise<MemorySnapshot>;
|
|
search(request: MemorySearchRequest, context: AdapterContext): Promise<MemorySearchResult[]>;
|
|
commit(request: MemoryCommitRequest, context: AdapterContext): Promise<MemoryRevision>;
|
|
exportMarkdown(
|
|
request: MemoryExportRequest,
|
|
context: AdapterContext,
|
|
): AsyncIterable<PortableFile>;
|
|
importMarkdown(
|
|
files: AsyncIterable<PortableFile>,
|
|
context: AdapterContext,
|
|
): Promise<MemoryRevision>;
|
|
}
|
|
|
|
/** Optional semantic memory. Durable Markdown memory remains owned by MemoryStore. */
|
|
export interface SemanticMemoryProvider {
|
|
describe(): AdapterDescriptor<SemanticMemoryCapabilities>;
|
|
recall(
|
|
request: SemanticMemoryRecallRequest,
|
|
context: AdapterContext,
|
|
): Promise<SemanticMemoryResponse<SemanticMemoryResult[]>>;
|
|
save(
|
|
request: SemanticMemorySaveRequest,
|
|
context: AdapterContext,
|
|
): Promise<SemanticMemoryResponse>;
|
|
purgeHistory(
|
|
request: SemanticMemoryPurgeHistoryRequest,
|
|
context: AdapterContext,
|
|
): Promise<SemanticMemoryResponse>;
|
|
/** Optional durable forget. Providers without a forget verb omit this. */
|
|
forget?(
|
|
request: SemanticMemoryForgetRequest,
|
|
context: AdapterContext,
|
|
): Promise<SemanticMemoryResponse<{ id: string; expired: boolean; reason: string | null }>>;
|
|
}
|
|
|
|
/** Optional text embeddings, such as a self-hosted model server, for recall and deduplication. */
|
|
export interface EmbeddingProvider {
|
|
describe(): AdapterDescriptor<EmbeddingCapabilities>;
|
|
/** One vector per text, in input order, all of the same length. */
|
|
embed(texts: string[], signal?: AbortSignal): Promise<number[][]>;
|
|
}
|
|
|
|
export interface AgentRuntime {
|
|
describe(): AdapterDescriptor<AgentRuntimeCapabilities>;
|
|
run(
|
|
request: AgentRunRequest,
|
|
context?: Partial<AdapterContext>,
|
|
): AsyncIterable<AgentRuntimeEvent>;
|
|
abort(runId: string): Promise<void>;
|
|
}
|
|
|
|
export interface ModelProvider {
|
|
describe(): AdapterDescriptor<{ catalog: boolean; byok: boolean }>;
|
|
listModels(): Promise<Array<{ provider: string; id: string; label: string; billing: string }>>;
|
|
}
|
|
|
|
export interface JobPublisher {
|
|
enqueue(job: BackgroundJob): Promise<void>;
|
|
cancel(key: string): Promise<void>;
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
export interface JobWorkerHost {
|
|
start(handlers: BackgroundJobHandlers): Promise<void>;
|
|
stop(): Promise<void>;
|
|
}
|
|
|
|
export interface AgentHomeStore {
|
|
describe(): AdapterDescriptor<{ revisions: boolean }>;
|
|
checkout(botId: string, dest: string, context: AdapterContext): Promise<string>;
|
|
commit(botId: string, src: string, context: AdapterContext): Promise<string>;
|
|
restore(botId: string, revision: string, dest: string, context: AdapterContext): Promise<void>;
|
|
exportHome(
|
|
botId: string,
|
|
context: AdapterContext,
|
|
options?: { prefix?: string },
|
|
): AsyncIterable<PortableFile>;
|
|
readFile(
|
|
botId: string,
|
|
path: string,
|
|
context: AdapterContext,
|
|
options?: { maxBytes?: number },
|
|
): Promise<string>;
|
|
writeFile(botId: string, path: string, content: string, context: AdapterContext): Promise<void>;
|
|
list(
|
|
botId: string,
|
|
path: string,
|
|
context: AdapterContext,
|
|
): Promise<Array<{ path: string; kind: "file" | "dir"; size: number }>>;
|
|
}
|
|
|
|
/**
|
|
* Artifact bytes, keyed per space (`context.spaceId`). `stream: true` means large media can be
|
|
* stored and served without holding it in memory.
|
|
*/
|
|
export interface ArtifactStore {
|
|
describe(): AdapterDescriptor<{ stream: boolean }>;
|
|
put(artifact: ArtifactPut, context: AdapterContext): Promise<{ id: string; hash: string }>;
|
|
get(id: string, context: AdapterContext): Promise<Uint8Array>;
|
|
remove(id: string, context: AdapterContext): Promise<void>;
|
|
stat(key: string, context: AdapterContext): Promise<{ size: number }>;
|
|
/** Rejects with a RangeError when the range does not fit the stored size. */
|
|
createReadStream(
|
|
key: string,
|
|
context: AdapterContext,
|
|
range?: ArtifactByteRange,
|
|
): Promise<ReadableStream<Uint8Array>>;
|
|
putStream(artifact: ArtifactStreamPut, context: AdapterContext): Promise<StoredArtifactStream>;
|
|
/** A local file path for tools such as ffmpeg that read in place; only stores on local disk. */
|
|
localPath?(key: string, context: AdapterContext): Promise<string>;
|
|
}
|
|
|
|
export interface SecretStore {
|
|
describe(): AdapterDescriptor<{ rotate: boolean }>;
|
|
/** Optional recordId binds ciphertext AAD to the persisted secret/session row id. */
|
|
put(plaintext: string, context: AdapterContext, recordId?: string): Promise<SecretRecord>;
|
|
get(id: string, context: AdapterContext): Promise<string>;
|
|
redact(value: string): string;
|
|
}
|
|
|
|
export interface RealtimeFanout {
|
|
describe(): AdapterDescriptor<{ distributed: boolean; push: boolean }>;
|
|
publish(topic: string, payload: string): Promise<void>;
|
|
subscribe(topic: string, onMessage: (payload: string) => void): Promise<() => Promise<void>>;
|
|
close(): Promise<void>;
|
|
}
|
|
|
|
export interface NotificationProvider {
|
|
describe(): AdapterDescriptor<{ push: boolean; email: boolean }>;
|
|
send(message: NotificationMessage, context: AdapterContext): Promise<void>;
|
|
}
|
|
|
|
/** Outbound account and security email. Product code owns content; adapters own delivery. */
|
|
export interface TransactionalEmailProvider {
|
|
describe(): AdapterDescriptor<{ transactional: boolean }>;
|
|
send(message: TransactionalEmail): Promise<void>;
|
|
/** Wait for accepted in-flight deliveries before a graceful shutdown completes. */
|
|
drain?(): Promise<void>;
|
|
}
|
|
|
|
export interface ExecutionRunner {
|
|
describe(): AdapterDescriptor<{ cloud: boolean; selfHosted: boolean; desktop: boolean }>;
|
|
dispatch(runId: string, target: "cloud" | "self-hosted" | "desktop"): Promise<void>;
|
|
}
|
|
|
|
export interface VoiceProvider {
|
|
describe(): AdapterDescriptor<VoiceCapabilities>;
|
|
verify(apiKey: string, context: AdapterContext): Promise<VoiceVerifyResult>;
|
|
listVoices(apiKey: string, context: AdapterContext): Promise<VoiceInfo[]>;
|
|
synthesize(request: VoiceSynthesizeRequest, context: AdapterContext): Promise<SpeechClip>;
|
|
transcribe?(request: VoiceTranscribeRequest, context: AdapterContext): Promise<{ text: string }>;
|
|
}
|
|
|
|
/**
|
|
* Deployment-wide external chat surface: one bot presence across messaging
|
|
* platforms (iMessage/SMS, Slack, WhatsApp, …). Conversations are addressed
|
|
* by opaque provider-prefixed thread ids ("sendblue:…", "slack:C1:…"), so
|
|
* orchestration never sees platform wire formats. Webhook verification and
|
|
* payload translation live behind handleWebhook, per platform.
|
|
*/
|
|
export interface MessagingSurface {
|
|
describe(): AdapterDescriptor<{ providers: string[] }>;
|
|
/** Enabled platforms and what each supports. */
|
|
platforms(): MessagingPlatformDescriptor[];
|
|
/**
|
|
* Verify and process one platform webhook request. Parsed events reach the
|
|
* sink registered via onInbound before the returned response resolves.
|
|
* Returns null for a provider this surface does not host.
|
|
*/
|
|
handleWebhook(provider: string, request: Request): Promise<Response> | null;
|
|
/** Register the single downstream consumer of inbound events. */
|
|
onInbound(sink: (event: MessagingInboundEvent) => Promise<void>): void;
|
|
sendToThread(
|
|
request: MessagingSendRequest,
|
|
context: AdapterContext,
|
|
): Promise<MessagingSendResult>;
|
|
/** Resolve (or create) the 1:1 thread for a provider address. */
|
|
openDirectThread(provider: string, address: string, context: AdapterContext): Promise<string>;
|
|
/**
|
|
* Best-effort "…" typing bubbles for 1:1 chats. Cosmetic: never gates
|
|
* message delivery, and silently no-ops on platforms without support.
|
|
*/
|
|
sendTyping(threadId: string, context: AdapterContext): Promise<void>;
|
|
/**
|
|
* Start the surface eagerly instead of waiting for the first inbound
|
|
* webhook or outbound send to touch it. Needed for adapters that pull
|
|
* updates themselves (e.g. Telegram in "auto"/"polling" mode, used when
|
|
* no public webhook URL is reachable): without an explicit call here,
|
|
* nothing kicks off that pull loop until something else happens to
|
|
* initialize the adapter first.
|
|
*/
|
|
initialize?(): Promise<void>;
|
|
/**
|
|
* Release long-lived inbound resources (Telegram getUpdates polling) so a
|
|
* replacement process can claim them. No-op when the surface never started.
|
|
*/
|
|
shutdown?(): Promise<void>;
|
|
}
|
|
|
|
/**
|
|
* Provider-neutral web search. Builtin `web_search` routes here so backends
|
|
* (keyless HTTP, model-native search, future paid APIs) can be swapped without
|
|
* changing tool names or the executor.
|
|
*/
|
|
export interface WebSearchProvider {
|
|
describe(): AdapterDescriptor<WebSearchCapabilities>;
|
|
search(request: WebSearchRequest, context: AdapterContext): Promise<WebSearchHit[]>;
|
|
}
|
|
|
|
/**
|
|
* Provider-neutral page fetch. Builtin `web_fetch` routes here. Read-only:
|
|
* no JS execution, no headless browser.
|
|
*/
|
|
export interface WebFetchProvider {
|
|
describe(): AdapterDescriptor<WebFetchCapabilities>;
|
|
fetch(request: WebFetchRequest, context: AdapterContext): Promise<WebFetchResult>;
|
|
}
|
|
|
|
/** Convenience when one adapter owns both search and fetch. */
|
|
export interface WebProvider extends WebSearchProvider, WebFetchProvider {
|
|
describe(): AdapterDescriptor<WebSearchCapabilities & WebFetchCapabilities>;
|
|
}
|
|
|
|
/**
|
|
* Provider-neutral page browser on the bot computer. Builtin browser_* tools
|
|
* route here so backends (in-process page session, CDP against the computer's
|
|
* Chrome, future adapters) can swap without changing tool names. Not a second
|
|
* hosted browser product. Prefer this for web pages; use computer_act when the
|
|
* page tool cannot operate or the task needs the desktop itself.
|
|
*/
|
|
export interface BrowserProvider {
|
|
describe(): AdapterDescriptor<BrowserCapabilities>;
|
|
navigate(
|
|
computer: ComputerRef,
|
|
request: BrowserNavigateRequest,
|
|
context: AdapterContext,
|
|
): Promise<BrowserNavigateResult>;
|
|
snapshot(
|
|
computer: ComputerRef,
|
|
request: BrowserSnapshotRequest,
|
|
context: AdapterContext,
|
|
): Promise<BrowserSnapshotResult>;
|
|
act(
|
|
computer: ComputerRef,
|
|
request: BrowserActRequest,
|
|
context: AdapterContext,
|
|
): Promise<BrowserActResult>;
|
|
}
|
|
|
|
/**
|
|
* Provider-neutral remote cloud coding agents (clone a repo on a vendor VM,
|
|
* open a PR). Not the bot computer — shell/sandbox stays separate. Core runs
|
|
* with none configured; tools are injected only when a provider is present.
|
|
*/
|
|
export interface CloudAgentProvider {
|
|
describe(): AdapterDescriptor<CloudAgentCapabilities>;
|
|
launch(request: CloudAgentLaunchRequest, context: AdapterContext): Promise<CloudAgentHandle>;
|
|
get(id: string, context: AdapterContext, runId?: string): Promise<CloudAgentSnapshot>;
|
|
reply(
|
|
id: string,
|
|
request: CloudAgentReplyRequest,
|
|
context: AdapterContext,
|
|
): Promise<CloudAgentHandle>;
|
|
cancel(id: string, context: AdapterContext, runId?: string): Promise<CloudAgentSnapshot>;
|
|
}
|