consortium
Version:
Remote control and session sharing CLI for AI coding agents
2,679 lines • 102 kB
text/typescript
import { z } from 'zod';
import { EventEmitter } from 'node:events';
import { Socket } from 'socket.io-client';
import { ExpoPushMessage } from 'expo-server-sdk';
/**
* Simplified schema that only validates fields actually used in the codebase
* while preserving all other fields through passthrough()
*/
declare const UsageSchema: z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>;
declare const RawJSONLinesSchema: z.ZodDiscriminatedUnion<"type", [z.ZodObject<{
type: z.ZodLiteral<"user">;
isSidechain: z.ZodOptional<z.ZodBoolean>;
isMeta: z.ZodOptional<z.ZodBoolean>;
uuid: z.ZodString;
message: z.ZodObject<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, z.ZodTypeAny, "passthrough">>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
type: z.ZodLiteral<"user">;
isSidechain: z.ZodOptional<z.ZodBoolean>;
isMeta: z.ZodOptional<z.ZodBoolean>;
uuid: z.ZodString;
message: z.ZodObject<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, z.ZodTypeAny, "passthrough">>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
type: z.ZodLiteral<"user">;
isSidechain: z.ZodOptional<z.ZodBoolean>;
isMeta: z.ZodOptional<z.ZodBoolean>;
uuid: z.ZodString;
message: z.ZodObject<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
content: z.ZodUnion<[z.ZodString, z.ZodAny]>;
}, z.ZodTypeAny, "passthrough">>;
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
uuid: z.ZodString;
type: z.ZodLiteral<"assistant">;
message: z.ZodOptional<z.ZodObject<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
uuid: z.ZodString;
type: z.ZodLiteral<"assistant">;
message: z.ZodOptional<z.ZodObject<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
uuid: z.ZodString;
type: z.ZodLiteral<"assistant">;
message: z.ZodOptional<z.ZodObject<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
usage: z.ZodOptional<z.ZodObject<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
input_tokens: z.ZodNumber;
cache_creation_input_tokens: z.ZodOptional<z.ZodNumber>;
cache_read_input_tokens: z.ZodOptional<z.ZodNumber>;
output_tokens: z.ZodNumber;
service_tier: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
model: z.ZodOptional<z.ZodString>;
}, z.ZodTypeAny, "passthrough">>>;
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
type: z.ZodLiteral<"summary">;
summary: z.ZodString;
leafUuid: z.ZodString;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
type: z.ZodLiteral<"summary">;
summary: z.ZodString;
leafUuid: z.ZodString;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
type: z.ZodLiteral<"summary">;
summary: z.ZodString;
leafUuid: z.ZodString;
}, z.ZodTypeAny, "passthrough">>, z.ZodObject<{
type: z.ZodLiteral<"system">;
uuid: z.ZodString;
}, "passthrough", z.ZodTypeAny, z.objectOutputType<{
type: z.ZodLiteral<"system">;
uuid: z.ZodString;
}, z.ZodTypeAny, "passthrough">, z.objectInputType<{
type: z.ZodLiteral<"system">;
uuid: z.ZodString;
}, z.ZodTypeAny, "passthrough">>]>;
type RawJSONLines = z.infer<typeof RawJSONLinesSchema>;
/**
* Permission mode type - includes both Claude and Codex modes
* Must match MessageMetaSchema.permissionMode enum values
*
* Claude modes: default, acceptEdits, bypassPermissions, plan
* Codex modes: read-only, safe-yolo, yolo
*
* When calling Claude SDK, Codex modes are mapped at the SDK boundary:
* - yolo → bypassPermissions
* - safe-yolo → default
* - read-only → default
*/
type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'read-only' | 'safe-yolo' | 'yolo';
/**
* Usage data type from Claude
*/
type Usage = z.infer<typeof UsageSchema>;
/**
* Update event from server
*/
declare const UpdateSchema: z.ZodObject<{
id: z.ZodString;
seq: z.ZodNumber;
body: z.ZodUnion<[z.ZodObject<{
message: z.ZodObject<{
id: z.ZodString;
seq: z.ZodNumber;
content: z.ZodObject<{
c: z.ZodString;
t: z.ZodLiteral<"encrypted">;
}, "strip", z.ZodTypeAny, {
c: string;
t: "encrypted";
}, {
c: string;
t: "encrypted";
}>;
}, "strip", z.ZodTypeAny, {
content: {
c: string;
t: "encrypted";
};
id: string;
seq: number;
}, {
content: {
c: string;
t: "encrypted";
};
id: string;
seq: number;
}>;
sid: z.ZodString;
t: z.ZodLiteral<"new-message">;
}, "strip", z.ZodTypeAny, {
message: {
content: {
c: string;
t: "encrypted";
};
id: string;
seq: number;
};
t: "new-message";
sid: string;
}, {
message: {
content: {
c: string;
t: "encrypted";
};
id: string;
seq: number;
};
t: "new-message";
sid: string;
}>, z.ZodObject<{
t: z.ZodLiteral<"update-session">;
sid: z.ZodString;
metadata: z.ZodOptional<z.ZodNullable<z.ZodObject<{
version: z.ZodNumber;
value: z.ZodString;
}, "strip", z.ZodTypeAny, {
value: string;
version: number;
}, {
value: string;
version: number;
}>>>;
agentState: z.ZodOptional<z.ZodNullable<z.ZodObject<{
version: z.ZodNumber;
value: z.ZodString;
}, "strip", z.ZodTypeAny, {
value: string;
version: number;
}, {
value: string;
version: number;
}>>>;
}, "strip", z.ZodTypeAny, {
t: "update-session";
sid: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
agentState?: {
value: string;
version: number;
} | null | undefined;
}, {
t: "update-session";
sid: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
agentState?: {
value: string;
version: number;
} | null | undefined;
}>, z.ZodObject<{
t: z.ZodLiteral<"update-machine">;
machineId: z.ZodString;
metadata: z.ZodOptional<z.ZodNullable<z.ZodObject<{
version: z.ZodNumber;
value: z.ZodString;
}, "strip", z.ZodTypeAny, {
value: string;
version: number;
}, {
value: string;
version: number;
}>>>;
daemonState: z.ZodOptional<z.ZodNullable<z.ZodObject<{
version: z.ZodNumber;
value: z.ZodString;
}, "strip", z.ZodTypeAny, {
value: string;
version: number;
}, {
value: string;
version: number;
}>>>;
}, "strip", z.ZodTypeAny, {
t: "update-machine";
machineId: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
daemonState?: {
value: string;
version: number;
} | null | undefined;
}, {
t: "update-machine";
machineId: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
daemonState?: {
value: string;
version: number;
} | null | undefined;
}>]>;
createdAt: z.ZodNumber;
}, "strip", z.ZodTypeAny, {
id: string;
seq: number;
body: {
message: {
content: {
c: string;
t: "encrypted";
};
id: string;
seq: number;
};
t: "new-message";
sid: string;
} | {
t: "update-session";
sid: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
agentState?: {
value: string;
version: number;
} | null | undefined;
} | {
t: "update-machine";
machineId: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
daemonState?: {
value: string;
version: number;
} | null | undefined;
};
createdAt: number;
}, {
id: string;
seq: number;
body: {
message: {
content: {
c: string;
t: "encrypted";
};
id: string;
seq: number;
};
t: "new-message";
sid: string;
} | {
t: "update-session";
sid: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
agentState?: {
value: string;
version: number;
} | null | undefined;
} | {
t: "update-machine";
machineId: string;
metadata?: {
value: string;
version: number;
} | null | undefined;
daemonState?: {
value: string;
version: number;
} | null | undefined;
};
createdAt: number;
}>;
type Update = z.infer<typeof UpdateSchema>;
/**
* Session information
*/
type Session = {
id: string;
/**
* Server-side idempotency tag for this session. Two `getOrCreateSession`
* calls with the same `tag` return the same row, so callers who want
* to resume an existing session (e.g. `consortium code --resume <id>`)
* must pass the original tag — not a fresh random one. Surfacing it
* here so the TUI wizard can hand it to the spawned agent process.
*/
tag: string;
seq: number;
encryptionKey: Uint8Array;
encryptionVariant: 'legacy' | 'dataKey';
/**
* The user's content X25519 public key (from `credential.encryption` when
* type is 'dataKey'). Used to wrap per-artifact DEKs when the CLI creates
* library artifacts via /v1/artifacts. Undefined for legacy-encryption
* accounts (which cannot create interactive artifacts).
*/
userContentPublicKey?: Uint8Array;
metadata: Metadata;
metadataVersion: number;
agentState: AgentState | null;
agentStateVersion: number;
};
declare const MachineMetadataSchema: z.ZodObject<{
host: z.ZodString;
platform: z.ZodString;
consortiumCliVersion: z.ZodString;
homeDir: z.ZodString;
consortiumHomeDir: z.ZodString;
consortiumLibDir: z.ZodString;
cpuCount: z.ZodOptional<z.ZodNumber>;
memoryTotalMb: z.ZodOptional<z.ZodNumber>;
hardware: z.ZodOptional<z.ZodObject<{
schemaVersion: z.ZodLiteral<1>;
platform: z.ZodString;
arch: z.ZodString;
cpuModel: z.ZodString;
cpuCoresPhysical: z.ZodNumber;
cpuThreads: z.ZodNumber;
ramTotalBytes: z.ZodNumber;
gpus: z.ZodArray<z.ZodObject<{
vendor: z.ZodEnum<["apple", "nvidia", "amd", "intel", "unknown"]>;
name: z.ZodString;
vramBytes: z.ZodOptional<z.ZodNumber>;
driver: z.ZodOptional<z.ZodString>;
computeCapability: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
vendor: "unknown" | "apple" | "nvidia" | "amd" | "intel";
name: string;
vramBytes?: number | undefined;
driver?: string | undefined;
computeCapability?: string | undefined;
}, {
vendor: "unknown" | "apple" | "nvidia" | "amd" | "intel";
name: string;
vramBytes?: number | undefined;
driver?: string | undefined;
computeCapability?: string | undefined;
}>, "many">;
unifiedMemory: z.ZodBoolean;
usableModelBytes: z.ZodNumber;
memBandwidthGBs: z.ZodOptional<z.ZodNumber>;
bandwidthSource: z.ZodEnum<["measured", "chip-table", "unknown"]>;
diskFreeBytes: z.ZodNumber;
detectedAt: z.ZodNumber;
warnings: z.ZodArray<z.ZodString, "many">;
}, "strip", z.ZodTypeAny, {
schemaVersion: 1;
platform: string;
arch: string;
cpuModel: string;
cpuCoresPhysical: number;
cpuThreads: number;
ramTotalBytes: number;
gpus: {
vendor: "unknown" | "apple" | "nvidia" | "amd" | "intel";
name: string;
vramBytes?: number | undefined;
driver?: string | undefined;
computeCapability?: string | undefined;
}[];
unifiedMemory: boolean;
usableModelBytes: number;
bandwidthSource: "unknown" | "measured" | "chip-table";
diskFreeBytes: number;
detectedAt: number;
warnings: string[];
memBandwidthGBs?: number | undefined;
}, {
schemaVersion: 1;
platform: string;
arch: string;
cpuModel: string;
cpuCoresPhysical: number;
cpuThreads: number;
ramTotalBytes: number;
gpus: {
vendor: "unknown" | "apple" | "nvidia" | "amd" | "intel";
name: string;
vramBytes?: number | undefined;
driver?: string | undefined;
computeCapability?: string | undefined;
}[];
unifiedMemory: boolean;
usableModelBytes: number;
bandwidthSource: "unknown" | "measured" | "chip-table";
diskFreeBytes: number;
detectedAt: number;
warnings: string[];
memBandwidthGBs?: number | undefined;
}>>;
openclawInstalled: z.ZodOptional<z.ZodBoolean>;
openclawVersion: z.ZodOptional<z.ZodNullable<z.ZodString>>;
openclawWorkspaceExists: z.ZodOptional<z.ZodBoolean>;
openclawGatewayRunning: z.ZodOptional<z.ZodBoolean>;
openclawGatewayPort: z.ZodOptional<z.ZodNumber>;
openclawChannels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
openclawAgentCount: z.ZodOptional<z.ZodNumber>;
openclawAgents: z.ZodOptional<z.ZodArray<z.ZodObject<{
name: z.ZodString;
workspace: z.ZodString;
agentDir: z.ZodString;
model: z.ZodString;
routingRules: z.ZodNumber;
isDefault: z.ZodBoolean;
}, "strip", z.ZodTypeAny, {
model: string;
name: string;
workspace: string;
agentDir: string;
routingRules: number;
isDefault: boolean;
}, {
model: string;
name: string;
workspace: string;
agentDir: string;
routingRules: number;
isDefault: boolean;
}>, "many">>;
harnessInstances: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
type: z.ZodString;
family: z.ZodOptional<z.ZodString>;
installed: z.ZodBoolean;
version: z.ZodOptional<z.ZodNullable<z.ZodString>>;
workspaceExists: z.ZodOptional<z.ZodBoolean>;
gatewayRunning: z.ZodOptional<z.ZodBoolean>;
gatewayPort: z.ZodOptional<z.ZodNumber>;
channels: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
agentCount: z.ZodOptional<z.ZodNumber>;
agents: z.ZodOptional<z.ZodArray<z.ZodObject<{
id: z.ZodOptional<z.ZodString>;
name: z.ZodString;
status: z.ZodOptional<z.ZodString>;
port: z.ZodOptional<z.ZodNumber>;
workspace: z.ZodOptional<z.ZodString>;
agentDir: z.ZodOptional<z.ZodString>;
model: z.ZodOptional<z.ZodString>;
routingRules: z.ZodOptional<z.ZodNumber>;
isDefault: z.ZodOptional<z.ZodBoolean>;
}, "strip", z.ZodTypeAny, {
name: string;
status?: string | undefined;
model?: string | undefined;
id?: string | undefined;
workspace?: string | undefined;
agentDir?: string | undefined;
routingRules?: number | undefined;
isDefault?: boolean | undefined;
port?: number | undefined;
}, {
name: string;
status?: string | undefined;
model?: string | undefined;
id?: string | undefined;
workspace?: string | undefined;
agentDir?: string | undefined;
routingRules?: number | undefined;
isDefault?: boolean | undefined;
port?: number | undefined;
}>, "many">>;
}, "strip", z.ZodTypeAny, {
type: string;
installed: boolean;
version?: string | null | undefined;
family?: string | undefined;
workspaceExists?: boolean | undefined;
gatewayRunning?: boolean | undefined;
gatewayPort?: number | undefined;
channels?: string[] | undefined;
agentCount?: number | undefined;
agents?: {
name: string;
status?: string | undefined;
model?: string | undefined;
id?: string | undefined;
workspace?: string | undefined;
agentDir?: string | undefined;
routingRules?: number | undefined;
isDefault?: boolean | undefined;
port?: number | undefined;
}[] | undefined;
}, {
type: string;
installed: boolean;
version?: string | null | undefined;
family?: string | undefined;
workspaceExists?: boolean | undefined;
gatewayRunning?: boolean | undefined;
gatewayPort?: number | undefined;
channels?: string[] | undefined;
agentCount?: number | undefined;
agents?: {
name: string;
status?: string | undefined;
model?: string | undefined;
id?: string | undefined;
workspace?: string | undefined;
agentDir?: string | undefined;
routingRules?: number | undefined;
isDefault?: boolean | undefined;
port?: number | undefined;
}[] | undefined;
}>>>;
terminalCapabilities: z.ZodOptional<z.ZodObject<{
tmux: z.ZodBoolean;
zellij: z.ZodBoolean;
}, "strip", z.ZodTypeAny, {
tmux: boolean;
zellij: boolean;
}, {
tmux: boolean;
zellij: boolean;
}>>;
}, "strip", z.ZodTypeAny, {
platform: string;
host: string;
consortiumCliVersion: string;
homeDir: string;
consortiumHomeDir: string;
consortiumLibDir: string;
cpuCount?: number | undefined;
memoryTotalMb?: number | undefined;
hardware?: {
schemaVersion: 1;
platform: string;
arch: string;
cpuModel: string;
cpuCoresPhysical: number;
cpuThreads: number;
ramTotalBytes: number;
gpus: {
vendor: "unknown" | "apple" | "nvidia" | "amd" | "intel";
name: string;
vramBytes?: number | undefined;
driver?: string | undefined;
computeCapability?: string | undefined;
}[];
unifiedMemory: boolean;
usableModelBytes: number;
bandwidthSource: "unknown" | "measured" | "chip-table";
diskFreeBytes: number;
detectedAt: number;
warnings: string[];
memBandwidthGBs?: number | undefined;
} | undefined;
openclawInstalled?: boolean | undefined;
openclawVersion?: string | null | undefined;
openclawWorkspaceExists?: boolean | undefined;
openclawGatewayRunning?: boolean | undefined;
openclawGatewayPort?: number | undefined;
openclawChannels?: string[] | undefined;
openclawAgentCount?: number | undefined;
openclawAgents?: {
model: string;
name: string;
workspace: string;
agentDir: string;
routingRules: number;
isDefault: boolean;
}[] | undefined;
harnessInstances?: Record<string, {
type: string;
installed: boolean;
version?: string | null | undefined;
family?: string | undefined;
workspaceExists?: boolean | undefined;
gatewayRunning?: boolean | undefined;
gatewayPort?: number | undefined;
channels?: string[] | undefined;
agentCount?: number | undefined;
agents?: {
name: string;
status?: string | undefined;
model?: string | undefined;
id?: string | undefined;
workspace?: string | undefined;
agentDir?: string | undefined;
routingRules?: number | undefined;
isDefault?: boolean | undefined;
port?: number | undefined;
}[] | undefined;
}> | undefined;
terminalCapabilities?: {
tmux: boolean;
zellij: boolean;
} | undefined;
}, {
platform: string;
host: string;
consortiumCliVersion: string;
homeDir: string;
consortiumHomeDir: string;
consortiumLibDir: string;
cpuCount?: number | undefined;
memoryTotalMb?: number | undefined;
hardware?: {
schemaVersion: 1;
platform: string;
arch: string;
cpuModel: string;
cpuCoresPhysical: number;
cpuThreads: number;
ramTotalBytes: number;
gpus: {
vendor: "unknown" | "apple" | "nvidia" | "amd" | "intel";
name: string;
vramBytes?: number | undefined;
driver?: string | undefined;
computeCapability?: string | undefined;
}[];
unifiedMemory: boolean;
usableModelBytes: number;
bandwidthSource: "unknown" | "measured" | "chip-table";
diskFreeBytes: number;
detectedAt: number;
warnings: string[];
memBandwidthGBs?: number | undefined;
} | undefined;
openclawInstalled?: boolean | undefined;
openclawVersion?: string | null | undefined;
openclawWorkspaceExists?: boolean | undefined;
openclawGatewayRunning?: boolean | undefined;
openclawGatewayPort?: number | undefined;
openclawChannels?: string[] | undefined;
openclawAgentCount?: number | undefined;
openclawAgents?: {
model: string;
name: string;
workspace: string;
agentDir: string;
routingRules: number;
isDefault: boolean;
}[] | undefined;
harnessInstances?: Record<string, {
type: string;
installed: boolean;
version?: string | null | undefined;
family?: string | undefined;
workspaceExists?: boolean | undefined;
gatewayRunning?: boolean | undefined;
gatewayPort?: number | undefined;
channels?: string[] | undefined;
agentCount?: number | undefined;
agents?: {
name: string;
status?: string | undefined;
model?: string | undefined;
id?: string | undefined;
workspace?: string | undefined;
agentDir?: string | undefined;
routingRules?: number | undefined;
isDefault?: boolean | undefined;
port?: number | undefined;
}[] | undefined;
}> | undefined;
terminalCapabilities?: {
tmux: boolean;
zellij: boolean;
} | undefined;
}>;
type MachineMetadata = z.infer<typeof MachineMetadataSchema>;
/**
* Daemon state - dynamic runtime information (frequently updated)
*/
declare const DaemonStateSchema: z.ZodObject<{
status: z.ZodUnion<[z.ZodEnum<["running", "shutting-down"]>, z.ZodString]>;
pid: z.ZodOptional<z.ZodNumber>;
httpPort: z.ZodOptional<z.ZodNumber>;
startedAt: z.ZodOptional<z.ZodNumber>;
shutdownRequestedAt: z.ZodOptional<z.ZodNumber>;
shutdownSource: z.ZodOptional<z.ZodUnion<[z.ZodEnum<["mobile-app", "cli", "os-signal", "unknown"]>, z.ZodString]>>;
}, "strip", z.ZodTypeAny, {
status: string;
pid?: number | undefined;
httpPort?: number | undefined;
startedAt?: number | undefined;
shutdownRequestedAt?: number | undefined;
shutdownSource?: string | undefined;
}, {
status: string;
pid?: number | undefined;
httpPort?: number | undefined;
startedAt?: number | undefined;
shutdownRequestedAt?: number | undefined;
shutdownSource?: string | undefined;
}>;
type DaemonState = z.infer<typeof DaemonStateSchema>;
type Machine = {
id: string;
encryptionKey: Uint8Array;
encryptionVariant: 'legacy' | 'dataKey';
metadata: MachineMetadata;
metadataVersion: number;
daemonState: DaemonState | null;
daemonStateVersion: number;
/**
* False when this object is the offline fallback fabricated by
* getOrCreateMachine after a failed POST /v1/machines — the server has
* NOT accepted this machine and will silently drop its heartbeats.
* Callers must not present the daemon as fully connected until a real
* registration succeeds. Absent/true = server-confirmed.
*/
registered?: boolean;
};
declare const UserMessageSchema: z.ZodObject<{
role: z.ZodLiteral<"user">;
content: z.ZodObject<{
type: z.ZodLiteral<"text">;
text: z.ZodString;
attachmentIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
localId: z.ZodOptional<z.ZodString>;
driveId: z.ZodOptional<z.ZodString>;
driveNodeIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
driveDek: z.ZodOptional<z.ZodString>;
attachments: z.ZodOptional<z.ZodArray<z.ZodObject<{
nodeId: z.ZodString;
driveId: z.ZodString;
kind: z.ZodString;
key: z.ZodOptional<z.ZodString>;
name: z.ZodOptional<z.ZodString>;
mime: z.ZodOptional<z.ZodString>;
size: z.ZodOptional<z.ZodNumber>;
linkUri: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
driveId: string;
nodeId: string;
kind: string;
name?: string | undefined;
key?: string | undefined;
mime?: string | undefined;
size?: number | undefined;
linkUri?: string | undefined;
}, {
driveId: string;
nodeId: string;
kind: string;
name?: string | undefined;
key?: string | undefined;
mime?: string | undefined;
size?: number | undefined;
linkUri?: string | undefined;
}>, "many">>;
}, "strip", z.ZodTypeAny, {
type: "text";
text: string;
attachmentIds?: string[] | undefined;
localId?: string | undefined;
driveId?: string | undefined;
driveNodeIds?: string[] | undefined;
driveDek?: string | undefined;
attachments?: {
driveId: string;
nodeId: string;
kind: string;
name?: string | undefined;
key?: string | undefined;
mime?: string | undefined;
size?: number | undefined;
linkUri?: string | undefined;
}[] | undefined;
}, {
type: "text";
text: string;
attachmentIds?: string[] | undefined;
localId?: string | undefined;
driveId?: string | undefined;
driveNodeIds?: string[] | undefined;
driveDek?: string | undefined;
attachments?: {
driveId: string;
nodeId: string;
kind: string;
name?: string | undefined;
key?: string | undefined;
mime?: string | undefined;
size?: number | undefined;
linkUri?: string | undefined;
}[] | undefined;
}>;
localKey: z.ZodOptional<z.ZodString>;
meta: z.ZodOptional<z.ZodObject<{
sentFrom: z.ZodOptional<z.ZodString>;
permissionMode: z.ZodOptional<z.ZodEnum<["default", "acceptEdits", "bypassPermissions", "plan", "read-only", "safe-yolo", "yolo"]>>;
model: z.ZodOptional<z.ZodNullable<z.ZodString>>;
fallbackModel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
customSystemPrompt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
appendSystemPrompt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
allowedTools: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
disallowedTools: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
}, "strip", z.ZodTypeAny, {
model?: string | null | undefined;
sentFrom?: string | undefined;
permissionMode?: "default" | "acceptEdits" | "bypassPermissions" | "plan" | "read-only" | "safe-yolo" | "yolo" | undefined;
fallbackModel?: string | null | undefined;
customSystemPrompt?: string | null | undefined;
appendSystemPrompt?: string | null | undefined;
allowedTools?: string[] | null | undefined;
disallowedTools?: string[] | null | undefined;
}, {
model?: string | null | undefined;
sentFrom?: string | undefined;
permissionMode?: "default" | "acceptEdits" | "bypassPermissions" | "plan" | "read-only" | "safe-yolo" | "yolo" | undefined;
fallbackModel?: string | null | undefined;
customSystemPrompt?: string | null | undefined;
appendSystemPrompt?: string | null | undefined;
allowedTools?: string[] | null | undefined;
disallowedTools?: string[] | null | undefined;
}>>;
attachmentIds: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
localId: z.ZodOptional<z.ZodString>;
driveId: z.ZodOptional<z.ZodString>;
driveDek: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
content: {
type: "text";
text: string;
attachmentIds?: string[] | undefined;
localId?: string | undefined;
driveId?: string | undefined;
driveNodeIds?: string[] | undefined;
driveDek?: string | undefined;
attachments?: {
driveId: string;
nodeId: string;
kind: string;
name?: string | undefined;
key?: string | undefined;
mime?: string | undefined;
size?: number | undefined;
linkUri?: string | undefined;
}[] | undefined;
};
role: "user";
attachmentIds?: string[] | undefined;
localId?: string | undefined;
driveId?: string | undefined;
driveDek?: string | undefined;
localKey?: string | undefined;
meta?: {
model?: string | null | undefined;
sentFrom?: string | undefined;
permissionMode?: "default" | "acceptEdits" | "bypassPermissions" | "plan" | "read-only" | "safe-yolo" | "yolo" | undefined;
fallbackModel?: string | null | undefined;
customSystemPrompt?: string | null | undefined;
appendSystemPrompt?: string | null | undefined;
allowedTools?: string[] | null | undefined;
disallowedTools?: string[] | null | undefined;
} | undefined;
}, {
content: {
type: "text";
text: string;
attachmentIds?: string[] | undefined;
localId?: string | undefined;
driveId?: string | undefined;
driveNodeIds?: string[] | undefined;
driveDek?: string | undefined;
attachments?: {
driveId: string;
nodeId: string;
kind: string;
name?: string | undefined;
key?: string | undefined;
mime?: string | undefined;
size?: number | undefined;
linkUri?: string | undefined;
}[] | undefined;
};
role: "user";
attachmentIds?: string[] | undefined;
localId?: string | undefined;
driveId?: string | undefined;
driveDek?: string | undefined;
localKey?: string | undefined;
meta?: {
model?: string | null | undefined;
sentFrom?: string | undefined;
permissionMode?: "default" | "acceptEdits" | "bypassPermissions" | "plan" | "read-only" | "safe-yolo" | "yolo" | undefined;
fallbackModel?: string | null | undefined;
customSystemPrompt?: string | null | undefined;
appendSystemPrompt?: string | null | undefined;
allowedTools?: string[] | null | undefined;
disallowedTools?: string[] | null | undefined;
} | undefined;
}>;
type UserMessage = z.infer<typeof UserMessageSchema>;
type Metadata = {
path: string;
host: string;
version?: string;
name?: string;
os?: string;
summary?: {
text: string;
updatedAt: number;
};
machineId?: string;
claudeSessionId?: string;
agentSessionId?: string;
codexRolloutPath?: string;
tools?: string[];
slashCommands?: string[];
homeDir: string;
consortiumHomeDir: string;
consortiumLibDir: string;
consortiumToolsDir: string;
startedFromDaemon?: boolean;
hostPid?: number;
startedBy?: 'daemon' | 'terminal';
/**
* Daemon-hosted PTY this session's process runs in (a tmux session named
* `consortium-<terminalId>` managed by the daemon's TerminalManager). The
* app attaches to it with `pty:open { persistentTerminalId }` to render the
* raw terminal view. Absent when the session has no capturable PTY (plain
* daemon spawn, or started by hand in a user's terminal).
*/
terminalId?: string;
/** Multiplexer hosting `terminalId` ('tmux' today). */
terminalMode?: string;
lifecycleState?: 'running' | 'archiveRequested' | 'archived' | string;
lifecycleStateSince?: number;
archivedBy?: string;
archiveReason?: string;
flavor?: string;
firstMessage?: string;
/** Resolved model id (cli-set; Pi updates per message_start). */
model?: string;
/** Resolved provider id (cli-set alongside `model`). */
provider?: string;
/**
* Startup latency breakdown in milliseconds (label → elapsed), pushed once
* when the runner is ready. Makes slow session starts measurable from the
* app instead of requiring daemon debug logs.
*/
startupTimings?: Record<string, number>;
};
type AgentState = {
controlledByUser?: boolean | null | undefined;
requests?: {
[id: string]: {
tool: string;
arguments: any;
createdAt: number;
};
};
completedRequests?: {
[id: string]: {
tool: string;
arguments: any;
createdAt: number;
completedAt: number;
status: 'canceled' | 'denied' | 'approved';
reason?: string;
mode?: PermissionMode;
decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort';
allowTools?: string[];
};
};
};
/**
* Common RPC types and interfaces for both session and machine clients
*/
/**
* Generic RPC handler function type
* @template TRequest - The request data type
* @template TResponse - The response data type
*/
type RpcHandler<TRequest = any, TResponse = any> = (data: TRequest) => TResponse | Promise<TResponse>;
/**
* RPC request data from server
*/
interface RpcRequest {
method: string;
params: string;
}
/**
* Configuration for RPC handler manager
*/
interface RpcHandlerConfig {
scopePrefix: string;
encryptionKey: Uint8Array;
encryptionVariant: 'legacy' | 'dataKey';
logger?: (message: string, data?: any) => void;
/**
* Called for every failed request (decryption failure or handler throw)
* with the error and structured context. Wire this to crash reporting
* (observability.captureError) — the manager itself stays transport-only.
*/
onError?: (error: unknown, context: Record<string, unknown>) => void;
}
/**
* Generic RPC handler manager for session and machine clients
* Manages RPC method registration, encryption/decryption, and handler execution
*/
declare class RpcHandlerManager {
private handlers;
private readonly scopePrefix;
private readonly encryptionKey;
private readonly encryptionVariant;
private readonly logger;
private readonly onError?;
private socket;
constructor(config: RpcHandlerConfig);
/**
* Register an RPC handler for a specific method
* @param method - The method name (without prefix)
* @param handler - The handler function
*/
registerHandler<TRequest = any, TResponse = any>(method: string, handler: RpcHandler<TRequest, TResponse>): void;
/**
* Handle an incoming RPC request
* @param request - The RPC request data
* @param callback - The response callback
*/
handleRequest(request: RpcRequest): Promise<any>;
onSocketConnect(socket: Socket): Promise<void>;
onSocketDisconnect(): void;
/**
* Get the number of registered handlers
*/
getHandlerCount(): number;
/**
* Check if a handler is registered
* @param method - The method name (without prefix)
*/
hasHandler(method: string): boolean;
/**
* Invoke a registered handler in-process with already-decrypted params.
* Used by the generic harness dispatcher to fall through to a legacy
* per-harness RPC (e.g. `openclaw-agent-add`) when the harness provider
* doesn't expose the dispatcher-facing method directly.
*/
callLocal<TRequest = any, TResponse = any>(method: string, params: TRequest): Promise<TResponse | undefined>;
/**
* Clear all handlers
*/
clearHandlers(): void;
/**
* Get the prefixed method name
* @param method - The method name
*/
private getPrefixedMethod;
}
/**
* The shared vocabulary for agent-CLI *operational* failures — not authed,
* out of plan usage, out of credits, rate limited, CLI missing.
*
* These are the errors Consortium used to lose. Before this module an error
* was a `string` produced at the end of the pipeline and recovered by regex
* over chat prose; every failure the user could actually act on collapsed to
* `'Process exited unexpectedly'`.
*
* The contract here is deliberately narrow:
*
* - The CLI emits a `code` plus structured params. It does NOT own the copy.
* User-facing wording lives in the app (`sources/text/_default.ts`), which
* ships ten locales behind a compile-time structural check and is
* OTA-updatable — CLI strings are English-only and gated behind a dist sync
* plus a daemon restart. `message` here is a LEGACY FALLBACK, rendered only
* by clients too old to understand `code`.
* - Every code maps to exactly one `action`, so the app never has to infer
* what button to draw.
* - `retryable` is a property of the code, not of the call site, so two
* runners can't disagree about whether the same failure is worth retrying.
*/
/**
* What went wrong, at the granularity the user can act on.
*
* Ordering note: this is a closed set on purpose. A failure that doesn't map
* cleanly is `unknown` (retryable) rather than a new code invented at a call
* site — otherwise the app's action table silently falls out of sync.
*/
type AgentErrorCode = 'not_authenticated' | 'auth_expired' | 'plan_limit' | 'rate_limit' | 'insufficient_credits' | 'model_unavailable' | 'cli_missing' | 'cli_incompatible' | 'context_exceeded' | 'network' | 'crashed' | 'unknown';
/** The single UI affordance a code earns. One code → one button. */
type AgentErrorAction = 'login' | 'retry' | 'switch_model' | 'add_credits' | 'install_cli' | 'update_cli' | 'compact' | 'wait_or_switch';
/**
* ACP (Agent Communication Protocol) message data types.
* This is the unified format for all agent messages - CLI adapts each provider's format to ACP.
*/
type ACPMessageData = {
type: 'message';
message: string;
} | {
type: 'reasoning';
message: string;
} | {
type: 'thinking';
text: string;
} | {
type: 'tool-call';
callId: string;
name: string;
input: unknown;
id: string;
} | {
type: 'tool-result';
callId: string;
output: unknown;
id: string;
isError?: boolean;
} | {
type: 'file-edit';
description: string;
filePath: string;
diff?: string;
oldContent?: string;
newContent?: string;
id: string;
} | {
type: 'terminal-output';
data: string;
callId: string;
} | {
type: 'task_started';
id: string;
} | {
type: 'task_complete';
id: string;
} | {
type: 'turn_aborted';
id: string;
} | {
type: 'permission-request';
permissionId: string;
toolName: string;
description: string;
options?: unknown;
} | {
type: 'artifact';
artifactId: string;
mime: string;
name?: string;
sizeBytes?: number;
} | {
type: 'artifact-ref';
artifactId: string;
title: string;
artifactKind: ArtifactKind;
versionHint?: number;
id: string;
} | {
type: 'token_count';
[key: string]: unknown;
};
/** Interactive artifact kinds (mirrors the app's ArtifactKind). */
type ArtifactKind = 'code' | 'html' | 'react' | 'svg' | 'mermaid' | 'markdown' | 'document';
type ACPProvider = 'gemini' | 'grok' | 'pi' | 'codex' | 'claude' | 'consortium-code';
declare class ApiSessionClient extends EventEmitter {
private readonly token;
readonly sessionId: string;
private metadata;
private metadataVersion;
private agentState;
private agentStateVersion;
private socket;
private pendingMessages;
private pendingMessageCallback;
private modelChangeCallback;
readonly rpcHandlerManager: RpcHandlerManager;
private agentStateLock;
private metadataLock;
private encryptionKey;
private encryptionVariant;
private readonly userContentPublicKey?;
private artifactStates;
private lastClaudeModel;
private pendingEmits;
private static readonly MAX_PENDING_EMITS;
private reconnectTimer;
private manualReconnectAttempts;
private destroyed;
private flushWatchdog;
private static readonly FLUSH_WATCHDOG_MS;
/** Get encryption details for passing to child processes. */
getEncryptionDetails(): {
key: Uint8Array;
variant: 'legacy' | 'dataKey';
};
/**
* Upload an agent-produced media blob to the server as a SessionArtifact.
*
* Phase 1.5 (E2EE): when the session content key is available (it always
* is for a live ApiSessionClient — both 'legacy' and 'dataKey' variants
* carry a 32-byte symmetric key that the app derives for the same
* session), the bytes are encrypted client-side with libsodium secretbox
* as `nonce || ciphertext` — the exact Drive-content primitive, so the
* app decrypts with the code path it already ships — and the POST carries
* `encVersion: 1`. If the key is missing/malformed (defensive: should not
* happen), we fall back to the Phase-1 plaintext upload with no
* `encVersion`, which the private-prefix + presigned-URL model still
* gates. Server-side size caps apply to the CIPHERTEXT (secretbox adds
* only 24-byte nonce + 16-byte MAC).
*
* Returns the artifact id and metadata so the caller can immediately
* reference it in an outgoing agent message
* (`{ type: 'artifact', artifactId, mime }`).
*/
uploadArtifact(args: {
bytes: Uint8Array;
mime: string;
}): Promise<{
id: string;
mime: string;
sizeBytes: number;
}>;
/** True when this session can create interactive library artifacts. */
canCreateLibraryArtifacts(): boolean;
/**
* Create a versioned interactive artifact (the /v1/artifacts entity),
* linked to this session. Encrypts header+body with a fresh per-artifact
* DEK wrapped to the user's content public key — byte-compatible with the
* mobile app's ArtifactEncryption, so the artifact opens in the app's
* slide-in panel and library. Returns the new artifact id.
*/
createLibraryArtifact(input: {
title: string;
kind: ArtifactKind;
content: string;
language?: string;
mime?: string;
}): Promise<{
id: string;
}>;
/**
* Update an artifact previously created in this session, producing a new
* body version and appending the prior body to the inline version history
* (capped). Requires the artifact to exist in this session's in-memory
* state (i.e. created in the same run) — otherwise the DEK is unavailable
* and the caller should create a new artifact instead.
*/
updateLibraryArtifact(input: {
id: string;
content: string;
title?: string;
summary?: string;
}): Promise<{
bodyVersion: number;
title: string;
kind: ArtifactKind;
}>;
constructor(token: string, session: Session);
onUserMessage(callback: (data: UserMessage) => void): void;
/**
* EPHEMERAL-1: subscribe to server-broadcast model-change events
* (emitted on Path A set AND clear — CONTRACT A). The server fans these
* out so every live client/session reconciles the active model
* immediately instead of only learning on the next request. Runners use
* this to update their in-memory model + repaint the model pill.
*
* `modelId === null` means the override was cleared (reset to default).
* `sessionId` is present for session-scoped changes and absent for
* account-level (default-for-new-sessions) changes.
*/
onModelChange(callback: (change: {
providerId: string;
modelId: string | null;
sessionId?: string;
}) => void): void;
/**
* Parse + dispatch a model-change ephemeral. Defensive: tolerates the
* broadcast arriving as either `model-change` or `model-changed` and
* silently ignores anything that isn't a well-formed model-change event
* (the same socket carries relay + rotate ephemerals).
*/
private onModelChangeEphemeral;
/**
* Emit a 'message' payload, buffering it if the socket is currently
* disconnected. Drained on the next 'connect' event.
*/
private reliableEmitMessage;
private flushPendingEmits;
/**
* Send message to session
* @param body - Message body (can be MessageContent or raw content for agent messages)
*/
sendClaudeSessionMessage(body: RawJSONLines): void;
sendCodexMessage(body: any): void;
/**
* Send a generic agent message to the session using ACP (Agent Communication Protocol) format.
* Works for any agent type (Gemini, Codex, Claude, etc.) - CLI normalizes to unified ACP format.
*
* @param provider - The agent provider sending the message (e.g., 'gemini', 'codex', 'claude')
* @param body - The message payload (type: 'message' | 'reasoning' | 'tool-call' | 'tool-result')
*/
/**
* Send a user message to the session, rendered as a chat bubble on the web client.
* Used to mirror terminal TUI input to the web session.
*/
sendUserChatMessage(text: string): void;
sendAgentMessage(provider: ACPProvider, body: ACPMessageData): void;
private pendingWriteCalls;
private fileArtifactIds;
private autoArtifactSeen;
private artifactDedupeKey;
/**
* Scan agent text for substantial fenced blocks and promote them to
* interactive artifacts — the safety net for agents that print a code
* block instead of calling create_artifact. Works for ALL agents: full-
* message agents (gemini/grok/pi/claude) are scanned per outgoing message
* from sendAgentMessage; streaming agents (consortium-code) call this once
* with the whole turn's text. Best-effort and fire-and-forget.
*
* Default ON; set CONSORTIUM_ARTIFACTS_AUTODETECT=0 to disable.
*/
autoDetectArtifactsFromText(provider: ACPProvider, text: string): Promise<void>;
private extractWritePath;
private fileArtifactKind;
/**
* Promote a written file to an interactive artifact: read it, infer the
* kind from its extension, and create (or, for a re-write of the same path,
* version) the artifact + drop a chat card. This is what makes coding
* agents behave like Claude artifacts — they write files, not fences.
*
* `content` may be passed when the caller already has it in hand (e.g.
* Claude's Write tool input, Codex add patches); otherwise the file is read
* from disk (it exists by the time the write tool-result fires). Best-effort
* and fire-and-forget. Honours CONSORTIUM_ARTIFACTS_AUTODETECT.
*/
promoteFileWriteArtifact(provider: ACPProvider, filePath: string, content?: string): Promise<void>;
sendSessionEvent(event: {
type: 'switch';
mode: 'local' | 'remote';
} | {
/**
* The `message` arm doubles as the error channel. It is the SAME event
* the cloud path already emits, so both surfaces speak one language
* and clients too old to understand `code` still render `message` as a
* plain line.
*
* `message` is a LEGACY English fallback — the app renders from `code`,
* because the app ships ten locales and is OTA-updatable while CLI
* strings are English-only and gated behind a dist sync.
*/
type: 'message';
message: string;
isError?: boolean;
code?: AgentErrorCode;
retryable?: boolean;
action?: AgentErrorAction;
/** Epoch ms when a limit lifts. Only set when the CLI actually said so. */
resetsAt?: number;
agent?: string;
providerId?: string;
providerAccountId?: string;
/** Raw CLI text, truncated, for the details toggle. */
detail?: string;
} | {
type: 'permission-mode-changed';
mode: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';
} | {
type: 'ready';
}, id?: string): void;
/**
* Emit a live token-stream delta over the ephemeral `session-stream`
* channel. Unlike `sendAgentMessage`, this is NOT persisted — the
* server relays it to clients currently viewing the session and drops
* it otherwise. The durable, authoritative message still flows through
* `sendAgentMessage` at turn end; this only powers the live "typing"
* feed. Deltas are best-effort (`volatile`): if the socket is
* congested a dropped delta is harmless because the final message
* reconciles the text.
*
* The `textDelta` is encrypted with the session key exactly like a
* durable message body, so the relay server never sees plaintext.
*
* @param provider agent id ('grok' | 'gemini' | 'codex' | 'claude' | 'pi' | …)
* @param streamId stable id for one assistant turn (answer+reasoning share it)
* @param channel 'answer' for response text, 'reasoning' for thinking
* @param seq monotonically increasing index within the stream (ordering)
* @param textDelta the incremental chunk (omit on the terminal frame)
* @param done true to signal the stream is finished (clears the buffer)
*/
sendStreamDelta(provider: string, streamId: string, channel: 'answer' | 'reasoning', seq: number, textDelta: string, done?: boolean): void;
/**
* Optional provider for message-queue observability. When set, every
* keepAlive heartbeat carries `queued`/`queuedIds` so clients can render
* exact queue state (level-triggered: re-sent every ~2s on a volatile
* emit, so a dropped frame self-heals on the next tick — no ack contract).
* Old servers/clients simply ignore the extra fields.
*/
private queueStatsProvider;
setQueueStatsProvider(provider: (() => {
queued: number;
queuedIds: string[];
}) | null): void;
/**
* Send a ping message to keep the connection alive
*/
keepAlive(thinking: boolean, mode: 'local' | 'remote'): void;
/**
* Send session death message
*/
sendSessionDeath(): void;
/**
* Infers the LLM provider ID from a model name string.
*/
private inferProvider;
/**
* Detects the usage mode based on environment variables (B-8).
*
* · 'managed' — the agent's traffic is rewritten through the Consortium
* proxy, so the SERVER already metered and billed this call and the
* report is supplementary analytics. This used to check only
* `ANTHROPIC_BASE_URL`, so a managed OpenAI or Google session was
* mislabeled 'wrapped' and got billed a SECOND time from the
* client-reported cost. All three vendor base-URL vars are checked now.
* · 'byok' — the daemon injected the user's OWN credential for this spawn
* (custom base URL and/or a vendor API-key env var it set). The user
* pays their vendor directly, so the server must not bill the wallet.
* · 'wrapped' — everything else; the server bills the client-reported cost.
*
* The BYOK signals are deliberately restricted to variables the DAEMON
* sets (`daemon/run.ts` — `CONSORTIUM_CODE_BYOK_BASE_URL` /
* `CONSORTIUM_CODE_BYOK_ENV_VAR` / `CONSORTIUM_CODE_BYOK_PROVIDER`). A bare
* `ANTHROPIC_API_KEY` inherited from the user's shell is NOT treated as
* BYOK: that would silently stop billing existing wrapped sessions, which
* is a revenue change, not an accounting fix.
*/
private detectUsageMode;
/**
* Send usage data to the server
*
* `opts` (L4.2 — non-claude agents + sub-agent rollup):
* · `keyPrefix` — report-key namespace. Defaults to the legacy
* 'claude-session' so existing claude rows keep upserting under their
* historical keys; codex/gemini/grok pass their agent name so the
* ledger can tell who reported.
* · `providerId` — explicit provider when the model id alone can't infer
* it (e.g. codex running its default model: the CLI never learns the
* model string, but the vendor is definitionally openai).
* · `zeroCost` — report tokens only, cost 0. REQUIRED for the non-claude
* agent self-reports: `calculateCost` falls back to Anthropic Sonnet
* pricing for unknown model ids, and the server debits the wallet for
* any wrapped-mode report with cost > 0 — a codex/gemini/grok session
* running on the user's OWN vendor account must never create a wallet
* charge. Vendor-true cost adoption is lane L4.4's scope, not ours.
*/
sendUsageData(usage: Usage, model?: string, turnKey?: string, opts?: {
keyPrefix?: string;
providerId?: string;
zeroCost?: boolean;
}): void;
/**
* Update session metadata
* @param handler - Handler function that returns the updated metadata
*/
updateMetadata(handler: (metadata: Metadata) => Metadata): void;
/**
* Update session agent state
* @param handler - Handler function that returns the updated agent state
*/
updateAgentState(handler: (metadata: AgentState) => AgentState): void;
/**
* Wait for socket buffer to flush
*/
flush(): Promise<void>;
close(): Promise<void>;
/**
* Schedule a manual reconnect after a connect_error. Mirrors
* ApiMachineClient.scheduleManualReconnect; see comment there for the
* Socket.IO middleware-rejection rationale. Session sockets don't
* carry their own refresh path — the session token comes from the
* parent process, so the only thing we can do here is retry with
* the existing token until it works (or until close() is called).
*/
private scheduleManualReconnect;
}
/**
* Shared types for the persistent terminal multiplexer subsystem.
*
* TerminalBackend is the common interface implemented by tmux, zellij, and
* the fallback ephemeral (node-pty) backend. TerminalManager owns backend
* instances and dispatches I/O through them.
*/
type TerminalMode = 'tmux' | 'zellij' | 'ephemeral' | 'external';
type TerminalStatus = 'running' | 'exited';
interface TerminalDescriptor {
id: string;
mode: TerminalMode;
status: TerminalStatus;
/** Epoch ms; best-effort, may be 0 if unknown post-restart */
createdAt: number;
}
interface SpawnSessionOptions {
machineId?: string;
directory: string;
sessionId?: string;
/**
* Correlation ID from the originating RPC (`_reqId` in the encrypted
* params). Propagated into the spawned session's environment as
* CONSORTIUM_REQUEST_ID so the session's logs link back to the request
* that spawned it.
*/
requestId?: string;
approvedNewDirectoryCreation?: boolean;
agent?: 'claude' | 'codex' | 'gemini' | 'grok' | 'pi' | 'consortium-code';
token?: string;
/** Start as Project Manager agent with session orchestration tools */
pmMode?: boolean;
/** When true, create a worktree before spawning the agent process */
worktreeMode?: boolean;
/** Card ID for worktree branch naming */
cardId?: string;
/** Card slug for worktree directory naming */
cardSlug?: string;
/**
* Project the session is started under. Forwarded to the spawned agent
* child process as CONSORTIUM_PROJECT_ID so the created session is
* persisted with Session.projectId. Optional — machine sessions with no
* project are unaffected.
*/
projectId?: string;
/**
* Automations: first user message, injected LOCALLY by the spawned agent
* (forwarded as CONSORTIUM_INITIAL_PROMPT). Used by server-scheduled runs
* where no app is online to send the opening message. Consumed today by
* the claude and consortium-code runners.
*/
initialPrompt?: string;
/**
* Automations: initial permission mode for the spawned agent (forwarded
* as CONSORTIUM_PERMISSION_MODE; same values as the app's per-session
* permission mode, e.g. 'read-only', 'default', 'safe-yolo').
*/
permissionMode?: string;
/** Base branch for worktree (defaults to 'main') */
repoBaseBranch?: string;
/**
* Legacy: Claude session ID to resume (uses `claude --resume <id>`).
* Still honored for claude agents when `resume` is not set. Prefer `resume`
* below for all new callers — it carries agent-specific resume data.
*/
resumeSessionId?: string;
/**
* Agent-specific resume hint. Only one of these is consulted, based on
* `agent`:
* - claude → uses `sessionId` as `--resume <id>`
* - codex → uses `rolloutPath` via `experimental_resume`
* config (daemon writes it to a --config arg)
* and passes `sessionId` for logging.
* - consortium-code → uses `--continue` (no id). `sessionId` carried
* for logging only; actual resume is last-session.
* - gemini → resume not supported; daemon rejects with a
* user-visible error if this is set.
*/
resume?: {
sessionId?: string;
rolloutPath?: string;
};
environmentVariables?: {
ANTHROPIC_BASE_URL?: string;
ANTHROPIC_AUTH_TOKEN?: string;
ANTHROPIC_MODEL?: string;
TMUX_SESSION_NAME?: string;
TMUX_TMPDIR?: string;
};
/** Per-provider key mode from profile: managed, byok, or none */
profileKeyMode?: Record<string, 'managed' | 'byok' | 'none'>;
/**
* How auth credentials should be sourced for this session. Mirrors
* `AuthSource` in `src/auth/types.ts` — duplicated as a string-literal
* union here to avoid a circular import between the common RPC layer
* and the auth module. Defaults to `'auto'` server-side: prefer
* machine creds if the probe finds them, else fall back to managed
* proxy if the user has credits, else error.
*/
authSource?: 'machine' | 'managed' | 'byok' | 'auto';
/**
* Optional expectation about the resolved identity. When supplied,
* the daemon verifies the probe's identity matches before spawning;
* a mismatch surfaces a clear error rather than silently using the
* wrong account.
*/
authIdentity?: {
agent: 'claude' | 'codex' | 'gemini' | 'consortium';
email?: string;
};
/**
* Provider id picked in the mobile Provider button (W3A). When set and
* non-`consortium`, the daemon injects this provider's keyEnvVar from
* the encrypted BYOK keystore so the agent CLI talks directly to the
* native upstream instead of routing through the Consortium proxy.
* Higher precedence than `profileKeyMode` for choosing the BYOK
* provider id.
*/
providerId?: string;
/**
* Selected model id (W3A). Currently advisory — the agent CLI reads
* the model from its own per-agent flags or environment. Carried so
* future telemetry / provider-routing decisions can avoid re-parsing
* provider:model strings.
*/
modelId?: string;
/**
* Linked provider account to authenticate this session with (M3).
* Pins spawn-time resolution to a specific account in the daemon's
* encrypted keystore (e.g. one of two claude.ai subscriptions);
* absent → the keystore's provider default is used. Ignored when an
* explicit `token` is supplied or `authSource === 'machine'`.
*/
accountKeyId?: string;
/**
* Explicit environment variable name to bind the BYOK key under when
* the daemon can't infer it from its catalogs (custom providers).
* Mobile supplies this for `customProviders` entries; the daemon uses
* it verbatim instead of looking up via AGENT_PROVIDERS or
* OPENCODE_PROVIDERS. Ignored when the daemon already knows the
* provider.
*/
byokEnvVar?: string;
/**
* Optional OpenAI-compatible base URL for the custom provider. When
* present the daemon exports `OPENCODE_CUSTOM_BASE_URL` so users can
* reference it from their opencode.json provider config.
*/
byokBaseURL?: string;
}
type SpawnSessionResult = {
type: 'success';
sessionId: string;
} | {
type: 'requestToApproveDirectoryCreation';
directory: string;
} | {
type: 'error';
errorMessage: string;
}
/**
* Proxy-token mint failed for a required scope. Distinct from the
* generic 'error' so the app can route to billing/upgrade UI instead
* of a generic "Failed to spawn" alert. `errorCode` mirrors the
* structured server response (`spending_cap_required`,
* `subscription_canceled`, `daily_limit_hit`, `insufficient_credits`,
* etc.). The CLI never silently downgrades to direct upstream — a
* failed mint is a hard spawn refusal.
*/
| {
type: 'mint_failed';
errorCode: string;
message: string;
scope: 'consortium' | 'anthropic' | 'openai' | 'google' | 'zai';
};
declare const DeviceFrameSchema: z.ZodObject<{
deviceId: z.ZodString;
seq: z.ZodNumber;
codec: z.ZodEnum<["jpeg", "h264"]>;
keyframe: z.ZodOptional<z.ZodBoolean>;
w: z.ZodNumber;
h: z.ZodNumber;
ctMs: z.ZodNumber;
sealed: z.ZodString;
}, "strip", z.ZodTypeAny, {
w: number;
h: number;
deviceId: string;
seq: number;
codec: "jpeg" | "h264";
ctMs: number;
sealed: string;
keyframe?: boolean | undefined;
}, {
w: number;
h: number;
deviceId: string;
seq: number;
codec: "jpeg" | "h264";
ctMs: number;
sealed: string;
keyframe?: boolean | undefined;
}>;
type DeviceFrame = z.infer<typeof DeviceFrameSchema>;
/** An input event as it crosses the relay: the event is sealed under the
* session DEK and carries a monotonic nonce so a compromised relay can neither
* read nor replay/forge taps. deviceHost rejects anything that fails to
* decrypt or whose nonce is <= the last accepted nonce. */
declare const SealedInputSchema: z.ZodObject<{
deviceId: z.ZodString;
nonce: z.ZodNumber;
sealed: z.ZodString;
}, "strip", z.ZodTypeAny, {
deviceId: string;
sealed: string;
nonce: number;
}, {
deviceId: string;
sealed: string;
nonce: number;
}>;
type SealedInput = z.infer<typeof SealedInputSchema>;
/** A control request. `id` correlates the matching DeviceControlResult. `params`
* is validated by the matching *InputSchema for `method`. */
declare const DeviceControlRequestSchema: z.ZodObject<{
type: z.ZodLiteral<"control">;
id: z.ZodString;
deviceId: z.ZodOptional<z.ZodString>;
method: z.ZodEnum<["listDevices", "boot", "attach", "detach", "screenshot", "install", "launch", "openUrl", "build", "buildStatus", "shutdown"]>;
params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
}, "strip", z.ZodTypeAny, {
type: "control";
id: string;
method: "listDevices" | "boot" | "attach" | "detach" | "screenshot" | "install" | "launch" | "openUrl" | "build" | "buildStatus" | "shutdown";
params?: Record<string, unknown> | undefined;
deviceId?: string | undefined;
}, {
type: "control";
id: string;
method: "listDevices" | "boot" | "attach" | "detach" | "screenshot" | "install" | "launch" | "openUrl" | "build" | "buildStatus" | "shutdown";
params?: Record<string, unknown> | undefined;
deviceId?: string | undefined;
}>;
type DeviceControlRequest = z.infer<typeof DeviceControlRequestSchema>;
declare const DeviceControlResultSchema: z.ZodUnion<[z.ZodObject<{
type: z.ZodLiteral<"control-result">;
id: z.ZodString;
ok: z.ZodLiteral<true>;
result: z.ZodOptional<z.ZodUnknown>;
}, "strip", z.ZodTypeAny, {
type: "control-result";
id: string;
ok: true;
result?: unknown;
}, {
type: "control-result";
id: string;
ok: true;
result?: unknown;
}>, z.ZodObject<{
type: z.ZodLiteral<"control-result">;
id: z.ZodString;
ok: z.ZodLiteral<false>;
error: z.ZodString;
}, "strip", z.ZodTypeAny, {
type: "control-result";
id: string;
error: string;
ok: false;
}, {
type: "control-result";
id: string;
error: string;
ok: false;
}>]>;
type DeviceControlResult = z.infer<typeof DeviceControlResultSchema>;
declare const DeviceLifecycleEventSchema: z.ZodObject<{
type: z.ZodLiteral<"event">;
deviceId: z.ZodString;
event: z.ZodEnum<["booted", "attached", "detached", "shutdown", "driver-changed", "error"]>;
driver: z.ZodOptional<z.ZodEnum<["agent", "human", "shared"]>>;
message: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
type: "event";
deviceId: string;
event: "error" | "shutdown" | "booted" | "attached" | "detached" | "driver-changed";
message?: string | undefined;
driver?: "agent" | "human" | "shared" | undefined;
}, {
type: "event";
deviceId: string;
event: "error" | "shutdown" | "booted" | "attached" | "detached" | "driver-changed";
message?: string | undefined;
driver?: "agent" | "human" | "shared" | undefined;
}>;
type DeviceLifecycleEvent = z.infer<typeof DeviceLifecycleEventSchema>;
interface ServerToDaemonEvents {
update: (data: Update) => void;
'rpc-request': (data: {
method: string;
params: string;
}, callback: (response: string) => void) => void;
'rpc-registered': (data: {
method: string;
}) => void;
'rpc-unregistered': (data: {
method: string;
}) => void;
'rpc-error': (data: {
type: string;
error: string;
}) => void;
auth: (data: {
success: boolean;
user: string;
}) => void;
error: (data: {
message: string;
}) => void;
'pty:open': (data: {
terminalId: string;
cols: number;
rows: number;
persistentTerminalId?: string;
}, callback: (response: {
ok: boolean;
error?: string;
}) => void) => void;
'pty:data': (data: {
terminalId: string;
data: string;
}) => void;
'pty:resize': (data: {
terminalId: string;
cols: number;
rows: number;
}) => void;
'pty:close': (data: {
terminalId: string;
}) => void;
'terminal:create': (data: {
terminalId: string;
persistenceMode?: string;
cols?: number;
rows?: number;
cwd?: string;
shell?: string;
}, callback: (response: {
ok: boolean;
mode: string;
error?: string;
}) => void) => void;
'terminal:list': (data: unknown, callback: (response: {
ok: boolean;
terminals: TerminalDescriptor[];
}) => void) => void;
'terminal:destroy': (data: {
terminalId: string;
}, callback: (response: {
ok: boolean;
error?: string;
}) => void) => void;
'server-shutting-down': (data: {
reason: string;
timestamp: number;
}) => void;
'machine:fs-readdir': (data: {
machineId: string;
path: string;
requestId: string;
}) => void;
'machine:fs-stat': (data: {
machineId: string;
path: string;
requestId: string;
}) => void;
'device:control': (data: {
machineId: string;
sessionId: string;
} & DeviceControlRequest) => void;
'device:input': (data: {
machineId: string;
sessionId: string;
} & SealedInput) => void;
}
interface DaemonToServerEvents {
'machine-alive': (data: {
machineId: string;
time: number;
}) => void;
'machine-update-metadata': (data: {
machineId: string;
metadata: string;
expectedVersion: number;
}, cb: (answer: {
result: 'error';
} | {
result: 'version-mismatch';
version: number;
metadata: string;
} | {
result: 'success';
version: number;
metadata: string;
}) => void) => void;
'machine-update-state': (data: {
machineId: string;
daemonState: string;
expectedVersion: number;
}, cb: (answer: {
result: 'error';
} | {
result: 'version-mismatch';
version: number;
daemonState: string;
} | {
result: 'success';
version: number;
daemonState: string;
}) => void) => void;
'openclaw:activity': (data: {
machineId: string;
eventType: string;
agentId: string | null;
encryptedPayload: string;
timestamp: number;
}) => void;
'openclaw:status': (data: {
machineId: string;
status: string;
version: string | null;
gatewayPort: number;
channels: string[];
agentCount: number;
timestamp: number;
}) => void;
'harness:agent-chat-frame': (data: {
machineId: string;
requestId: string;
agentId: string | null;
seq: number;
encryptedPayload: string;
done: boolean;
timestamp: number;
}) => void;
'rpc-register': (data: {
method: string;
}, callback: (response: {
ok: boolean;
method: string;
}) => void) => void;
'rpc-unregister': (data: {
method: string;
}) => void;
'rpc-ready': () => void;
'rpc-call': (data: {
method: string;
params: any;
}, callback: (response: {
ok: boolean;
result?: any;
error?: string;
}) => void) => void;
'pty:data': (data: {
machineId: string;
terminalId: string;
data: string;
}) => void;
'pty:exit': (data: {
machineId: string;
terminalId: string;
}) => void;
'codrive:frame': (data: {
machineId: string;
sessionId: string;
jpegBase64: string;
w: number;
h: number;
seq: number;
}) => void;
'terminal:list': (data: {
machineId: string;
terminals: TerminalDescriptor[];
}) => void;
'machine:fs-readdir-result': (data: {
requestId: string;
payload: string;
}) => void;
'machine:fs-stat-result': (data: {
requestId: string;
payload: string;
}) => void;
'device:frame': (data: {
machineId: string;
sessionId: string;
} & DeviceFrame) => void;
'device:control-result': (data: {
machineId: string;
sessionId: string;
} & DeviceControlResult) => void;
'device:event': (data: {
machineId: string;
sessionId: string;
} & DeviceLifecycleEvent) => void;
}
type MachineRpcHandlers = {
spawnSession: (options: SpawnSessionOptions) => Promise<SpawnSessionResult>;
stopSession: (sessionId: string) => boolean;
/**
* Live-apply a marketplace connector change to a running session: stop the
* agent process and re-spawn it with a resume hint so the conversation
* survives and provisioning re-runs on launch. `resume` carries the
* freshest agent-native resume data the caller holds (agent session id or
* codex rollout path); when omitted the daemon falls back to the session's
* tracked metadata.
*/
reprovisionSession: (sessionId: string, resume?: {
sessionId?: string;
rolloutPath?: string;
}) => Promise<SpawnSessionResult>;
requestShutdown: () => void;
};
interface ApiMachineConnectOptions {
onReconnect?: () => Promise<Machine>;
organizationId?: string;
onAuthFailure?: () => void;
/**
* Called when the websocket handshake is rejected (typically auth-related,
* but can fire for any connect_error). Should return a fresh token if one
* is available, or null if refresh failed/isn't possible. The new token,
* if returned, is swapped into the socket auth before the next reconnect
* attempt. See connect_error handler for the back-off schedule.
*
* Why this exists: Socket.IO v4 does not auto-retry middleware-level
* handshake rejections (the path the server takes for "Invalid
* authentication token"). Without explicit refresh + manual reconnect,
* a single transient auth blip permanently disconnects the daemon.
*/
refreshToken?: () => Promise<string | null>;
}
declare class ApiMachineClient {
private token;
private machine;
private socket;
/**
* Escape hatch for subsystems (e.g. harness profile-sync) that need to
* register custom events on the machine-scoped socket. Returns the live
* Socket.IO client; may be undefined before connect() is called.
*/
getRawSocket(): Socket<ServerToDaemonEvents, DaemonToServerEvents> | undefined;
/** Initialised async in connect(); guarded by the null check in handlers */
private terminalManager;
private keepAliveInterval;
private rpcHandlerManager;
/**
* Single shared device-code flow runner per daemon process. Wave 2
* of agent-auth-foundation. Handles `claude login` / `gcloud auth
* login --no-browser` and any future RFC 8628 device-code flow.
*/
private deviceCodeFlow;
private pasteFlow;
/** Claude subscription paste-code (OOB OAuth) flow. */
private claudePasteCodeFlow;
/**
* Drives an agent's OWN `login` command (see auth/flows/cliLogin.ts). This
* is preferred over every daemon-implemented OAuth path when the binary is
* installed, because the CLI holds its own PKCE verifier, talks to its own
* (current) endpoints, and writes its own credential file.
*/
private cliLoginFlow;
/**
* Single shared agent-CLI install runner (start → poll → cancel). Gated
* daemon-side by CONSORTIUM_AGENT_INSTALL=0. See cli-install/installFlow.ts.
*/
private installFlow;
private hasConnectedOnce;
private consecutiveAuthErrors;
private disconnectedAt;
private reconnectTimer;
private manualReconnectAttempts;
private destroyed;
/**
* L3.3 — drains the durable machine-origin UsageEvent queue to
* `POST /v1/usage-events`. The daemon owns it because the daemon owns the
* server credential; the per-session LMP only ever writes to the queue.
* Created lazily on first successful connect (see `startUsageUploader`).
*/
private usageUploader;
constructor(token: string, machine: Machine);
setRPCHandlers({ spawnSession, stopSession, reprovisionSession, requestShutdown }: MachineRpcHandlers): void;
/**
* Update machine metadata
* Currently unused, changes from the mobile client are more likely
* for example to set a custom name.
*/
updateMachineMetadata(handler: (metadata: MachineMetadata | null) => MachineMetadata): Promise<void>;
/**
* Update daemon state (runtime info) - similar to session updateAgentState
* Simplified without lock - relies on backoff for retry
*/
updateDaemonState(handler: (state: DaemonState | null) => DaemonState): Promise<void>;
connect(options?: ApiMachineConnectOptions): void;
/**
* Recovery for a socket that was ESTABLISHED and then dropped.
*
* `scheduleManualReconnect` covers handshake failures (`connect_error`).
* Nothing covered a *post-connect* drop: the disconnect handler only
* stopped the keep-alive and trusted `reconnection: true` to bring the
* socket back. That trust is misplaced for two of Socket.IO v4's
* disconnect reasons, and the gap is a real outage — on 2026-08-16
* Station 1 logged `Disconnected from server` and then went silent for
* hours: process alive, log still writing, zero sockets open, machine
* reading offline. `Restart=always` cannot help because nothing exits.
*
* Reasons and who owns recovery:
* `io client disconnect` — we called disconnect(). Intentional; do
* nothing, or shutdown() would reopen itself.
* `io server disconnect` — the server called socket.disconnect(). The
* Manager treats this as terminal and will
* NOT retry. We must reconnect ourselves.
* everything else — transport close/error, ping timeout, parse
* error. The Manager does retry these, so
* arm a watchdog instead of racing it, and
* take over only if it has not recovered.
*
* The watchdog shares `reconnectTimer` with the manual scheduler on
* purpose: a successful `connect` clears that timer, so recovering by
* either route disarms the other automatically.
*/
private scheduleRecoveryFromDisconnect;
/**
* Schedule a manual reconnect with exponential backoff + jitter. Called
* from the connect_error handler because Socket.IO v4 does not retry
* middleware-rejected handshakes on its own (see comment in the handler).
*
* If a refreshToken callback is provided and the rejection looked like
* an auth error, the new token is swapped into socket.auth before the
* next attempt. Backoff: 1s, 2s, 4s, 8s, 10s (cap), with up to 1s of
* random jitter on top. The 10s cap matches socket.io's
* reconnectionDelayMax and keeps the worst-case heartbeat gap under the
* app's 60s offline threshold so a reconnecting daemon never reads as
* offline. Reset to 0 on successful connect.
*/
private scheduleManualReconnect;
/**
* L3.3 — start the durable usage-queue drain.
*
* Idempotent: the uploader is created once per client and `start()` is a
* no-op while it is already running, so reconnect storms cannot stack
* timers. `getToken` is read lazily on every batch so a token refreshed by
* `scheduleManualReconnect` is picked up without recreating anything.
*
* Never fatal: a queue that cannot be opened (read-only home dir) must not
* stop the daemon from serving sessions — metering is telemetry, not the
* product.
*/
private startUsageUploader;
private startKeepAlive;
private stopKeepAlive;
/**
* Emit an encrypted OpenClaw activity event to be relayed to the mobile app.
* The encryptedPayload should already be base64(encrypted(event data)).
*/
emitOpenClawActivity(eventType: string, agentId: string | null, encryptedPayload: string): void;
/**
* Emit one streaming agent-chat frame. The payload is encrypted here
* with the machine key so the server relays ciphertext only; the app
* decrypts with the same machine key it already holds.
*/
emitAgentChatFrame(frame: {
requestId: string;
agentId: string | null;
seq: number;
payload: {
delta?: string;
reply?: string;
error?: string;
};
done: boolean;
}): void;
/**
* Emit an OpenClaw status update (unencrypted, for quick display).
*/
emitOpenClawStatus(status: string, version: string | null, gatewayPort: number, channels: string[], agentCount: number): void;
/**
* Emit a deployment health status event to the server for mobile app display.
*
* Accepts the full set of deployment lifecycle statuses (health, container,
* deploy/stop/remove) plus optional metadata fields used by the deployment
* RPC handlers in daemon/run.ts.
*/
emitDeploymentStatus(data: {
deploymentId: string;
status: 'healthy' | 'unhealthy' | 'running' | 'stopped' | 'pulling' | 'failed' | 'removing' | string;
healthStatus?: 'healthy' | 'unhealthy' | 'unknown';
statusMessage?: string;
containerName?: string;
url?: string;
timestamp?: number;
[extra: string]: unknown;
}): void;
/** Exposes the RPC handler manager for registering additional handlers (e.g. OpenClaw) */
get rpcHandlers(): RpcHandlerManager;
/** Exposes the machine's encryption key for the bridge to encrypt activity payloads */
get encryptionKey(): Uint8Array;
/** Exposes the encryption variant */
get encryptionVariant(): 'legacy' | 'dataKey';
shutdown(): void;
}
interface PushToken {
id: string;
token: string;
createdAt: number;
updatedAt: number;
}
declare class PushNotificationClient {
private readonly token;
private readonly baseUrl;
private readonly expo;
constructor(token: string, baseUrl?: string);
/**
* Fetch all push tokens for the authenticated user
*/
fetchPushTokens(): Promise<PushToken[]>;
/**
* Send push notification via Expo Push API with retry
* @param messages - Array of push messages to send
*/
sendPushNotifications(messages: ExpoPushMessage[]): Promise<void>;
/**
* Send a push notification to all registered devices for the user
* @param title - Notification title
* @param body - Notification body
* @param data - Additional data to send with the notification
*/
sendToAllDevices(title: string, body: string, data?: Record<string, any>): void;
}
/**
* Minimal persistence functions for consortium CLI
*
* Handles settings and private key storage in ~/.consortium/ or local .consortium/
*/
type Credentials = {
token: string;
encryption: {
type: 'legacy';
secret: Uint8Array;
} | {
type: 'dataKey';
publicKey: Uint8Array;
machineKey: Uint8Array;
} | null;
};
declare class ApiClient {
static create(credential: Credentials): Promise<ApiClient>;
private readonly credential;
private readonly pushClient;
private constructor();
/**
* Create a new session or load existing one with the given tag
*/
getOrCreateSession(opts: {
tag: string;
metadata: Metadata;
state: AgentState | null;
organizationId?: string;
projectId?: string;
}): Promise<Session | null>;
/**
* Register or update machine with the server
* Returns the current machine state from the server with decrypted metadata and daemonState
*/
getOrCreateMachine(opts: {
machineId: string;
metadata: MachineMetadata;
daemonState?: DaemonState;
vpsInstanceId?: string;
organizationId?: string;
}): Promise<Machine>;
sessionSyncClient(session: Session): ApiSessionClient;
machineSyncClient(machine: Machine): ApiMachineClient;
push(): PushNotificationClient;
/**
* Register a vendor API token with the server
* The token is sent as a JSON string - server handles encryption
*/
registerVendorToken(vendor: 'openai' | 'anthropic' | 'gemini' | 'xai', apiKey: any): Promise<void>;
/**
* Get vendor API token from the server
* Returns the token if it exists, null otherwise
*/
getVendorToken(vendor: 'openai' | 'anthropic' | 'gemini' | 'xai'): Promise<any | null>;
/**
* Fetches the list of organizations the user belongs to.
* Used for the interactive org picker on CLI startup.
*
* THROWS on failure (network / 5xx / auth). Previously this swallowed
* errors and returned `[]`, which conflated "the user has no orgs" with
* "we couldn't reach the server". The daemon path used that empty array to
* register the machine UNSCOPED, so a transient 5xx during startup
* orphaned the machine outside any org. Callers must now distinguish the
* two: a real empty list returns `[]`, an error rejects. Interactive
* callers (`auth`) already wrap this in try/catch and degrade gracefully;
* the daemon falls back to the persisted org or refuses to register
* unscoped on a fetch failure.
*/
fetchOrgs(): Promise<Array<{
id: string;
name: string;
slug: string;
type: 'personal' | 'team';
tier: string;
memberCount: number;
}>>;
}
/**
* serializeError — convert unknown thrown values into a JSON-safe shape.
*
* Error objects have non-enumerable `name`/`message`/`stack`, so a plain
* JSON.stringify(err) — or embedding an error inside a context object like
* `{ error }` — yields `{}` and destroys the diagnostics. This is exactly how
* the daemon used to log `[RPC] [ERROR] Error handling request {"error":{}}`.
* Any code that puts an error inside a log/context object must go through
* this helper.
*/
interface SerializedError {
name: string;
message: string;
stack?: string;
/** Node ErrnoException code (ENOENT, ECONNREFUSED, EADDRINUSE, ...) when present */
code?: string;
cause?: SerializedError;
}
/**
* Design decisions:
* - Logging should be done only through file for debugging, otherwise we might disturb the claude session when in interactive mode
* - Use info for logs that are useful to the user - this is our UI
* - File output location: ~/.consortium/logs/<date time in local timezone>.log
* - Every entry is mirrored as a structured JSONL line to a sibling `.jsonl`
* file ({ts, level, msg, err?, ctx?, reqId?}) so tooling and the log
* watchdog can parse logs without regexes. The plaintext file format is
* unchanged for humans and existing tools.
* - A ring buffer of recent lines feeds last-exit.json (crash forensics), and
* subscribers (daemon/logWatchdog.ts) get every structured entry in-process.
*/
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
/** One structured log entry — the JSONL line shape. */
interface StructuredLogEntry {
ts: string;
level: LogLevel;
msg: string;
/** Serialized error when the entry carries one */
err?: SerializedError;
/** Additional structured context */
ctx?: Record<string, unknown>;
/** Correlation ID linking this entry to the originating RPC/request */
reqId?: string;
}
declare class Logger {
readonly logFilePath: string;
private dangerouslyUnencryptedServerLoggingUrl;
/** Sibling structured log: same path with .jsonl extension */
readonly structuredLogFilePath: string;
private recentLines;
private subscribers;
/** Correlation ID inherited from the spawning daemon, if any */
private readonly inheritedReqId;
constructor(logFilePath?: string);
/**
* Subscribe to every structured entry (in-process). Used by the daemon log
* watchdog. Subscribers must never throw — exceptions are swallowed so a
* broken subscriber can't take down logging.
*/
subscribe(fn: (entry: StructuredLogEntry) => void): () => void;
/** Last N plaintext log lines, oldest first. Feeds last-exit.json forensics. */
getRecentLines(): string[];
/**
* Structured logging entry point: writes both the plaintext line (for
* humans) and the JSONL line (for tooling), with an explicit level and
* optional error / context / correlation ID.
*/
event(level: LogLevel, message: string, opts?: {
err?: unknown;
ctx?: Record<string, unknown>;
reqId?: string;
}): void;
/**
* Unified sink: one call produces exactly one plaintext line and one JSONL
* line, updates the ring buffer, and notifies subscribers. All public log
* methods route through here.
*/
private write;
localTimezoneTimestamp(): string;
debug(message: string, ...args: unknown[]): void;
debugLargeJson(message: string, object: unknown, maxStringLength?: number, maxArrayLength?: number): void;
info(message: string, ...args: unknown[]): void;
infoDeveloper(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
getLogPath(): string;
private logToConsole;
private sendToRemoteServer;
private logToFile;
}
declare let logger: Logger;
/**
* Global configuration for consortium CLI
*
* Centralizes all configuration including environment variables and paths
* Environment files should be loaded using Node's --env-file flag
*/
declare class Configuration {
readonly serverUrl: string;
readonly webappUrl: string;
readonly isDaemonProcess: boolean;
readonly consortiumHomeDir: string;
readonly logsDir: string;
readonly settingsFile: string;
readonly privateKeyFile: string;
readonly daemonStateFile: string;
readonly daemonLockFile: string;
readonly currentCliVersion: string;
readonly isExperimentalEnabled: boolean;
readonly disableCaffeinate: boolean;
readonly authToken: string | undefined;
constructor();
}
declare const configuration: Configuration;
export { ApiClient, ApiSessionClient, RawJSONLinesSchema, configuration, logger };
export type { RawJSONLines };