Files
wristcode/gateway/src/contract.ts
T

135 lines
3.4 KiB
TypeScript

// The JSON the watch app reads and sends. This is the contract between the gateway and the
// watch's Codable models (watch/WristCode/API/Models.swift). docs/api.md describes it for humans.
// Change both sides together.
import type { Phase } from "./awareness.ts";
import type { ApprovalDecision } from "./t3/types.ts";
import type { HostStatus } from "./t3/host.ts";
export interface HostSummary {
id: string;
label: string;
status: HostStatus;
error: string | null;
}
/** Something a thread is waiting on: an approval, or questions for the user. */
export interface PendingRequest {
id: string;
/** `command`, `file-change`, `permission`, `user_input`, ... */
kind: string;
/** What the agent wants to do, such as the command line. Null when the server gave none. */
prompt: string | null;
reason: string | null;
/** Approval choices, in the server's order. Empty for questions. */
options: Array<{ decision: ApprovalDecision; label: string }>;
/** Questions for the user. Empty for approvals. */
questions: Array<{
id: string;
header: string;
question: string;
options: Array<{ label: string; value: string }>;
allowCustomAnswer: boolean;
}>;
}
export interface FeedItem {
hostId: string;
hostLabel: string;
threadId: string;
projectId: string;
projectTitle: string;
title: string;
phase: Phase;
updatedAt: string;
/** The agent's latest message, trimmed for the watch. Null until the gateway has read it. */
lastMessage: string | null;
request: PendingRequest | null;
}
export interface Feed {
/** Waiting for approval or input. Oldest request first. */
needsYou: FeedItem[];
/** Starting or running. Most recently updated first. */
working: FeedItem[];
/** Completed or failed in the last 24 hours. Most recent first, at most 10. */
recent: FeedItem[];
hosts: HostSummary[];
}
export interface Message {
id: string;
role: "user" | "assistant" | "system";
text: string;
createdAt: string;
}
export interface ThreadDetail {
item: FeedItem;
/** Oldest first, at most 20. */
messages: Message[];
/** True while a run is active, so Stop makes sense. */
canStop: boolean;
}
export interface ProjectsResponse {
hosts: Array<
HostSummary & {
projects: Array<{
id: string;
title: string;
/** The model a new thread will use, such as `claude-opus-5-5`. Null when none is configured. */
model: string | null;
}>;
}
>;
}
export interface PairRequest {
code: string;
deviceName: string;
}
export interface PairResponse {
deviceId: string;
deviceToken: string;
}
export interface PushRegistration {
apnsToken: string;
apnsEnvironment: "sandbox" | "production";
}
export interface SendMessageRequest {
text: string;
}
export type RespondRequest =
| { requestId: string; decision: ApprovalDecision }
| { requestId: string; answers: Record<string, string> };
export interface NewThreadRequest {
hostId: string;
projectId: string;
text: string;
}
export interface NewThreadResponse {
hostId: string;
threadId: string;
}
export interface ErrorResponse {
error: string;
}
/** Custom keys on every push notification, beside `aps`. */
export interface PushPayload {
hostId: string;
threadId: string;
requestId?: string;
}
/** Notification categories the watch registers actions for. */
export type PushCategory = "approval" | "finished" | "failed" | "input" | "host";