summaryrefslogtreecommitdiffhomepage
path: root/.dispatch
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-25 22:45:51 +0900
committerAdam Malczewski <[email protected]>2026-06-25 22:45:51 +0900
commit1285564f12238b22f6b39b9f3fbcecaca8456911 (patch)
tree9d8ca532969a5a11ee61b5c42135ac6f54159183 /.dispatch
parentc5ea2232f117adda740c7e3b8366e9f10f14d3cb (diff)
parentcdce5197abcf0f5b0576e847a701d3a317384a65 (diff)
downloaddispatch-web-1285564f12238b22f6b39b9f3fbcecaca8456911.tar.gz
dispatch-web-1285564f12238b22f6b39b9f3fbcecaca8456911.zip
Merge branch 'feature/ssh-support' into dev
# Conflicts: # src/features/workspaces/ui/WorkspaceCard.svelte # src/features/workspaces/ui/WorkspaceCard.test.ts # src/features/workspaces/ui/WorkspacesHome.svelte
Diffstat (limited to '.dispatch')
-rw-r--r--.dispatch/transport-contract.reference.md100
-rw-r--r--.dispatch/wire.reference.md70
2 files changed, 167 insertions, 3 deletions
diff --git a/.dispatch/transport-contract.reference.md b/.dispatch/transport-contract.reference.md
index 8c6023a..48ba45d 100644
--- a/.dispatch/transport-contract.reference.md
+++ b/.dispatch/transport-contract.reference.md
@@ -5,9 +5,20 @@
> permission prompt). Your CODE still imports `@dispatch/transport-contract` normally — this file is for
> READING only.
>
-> **Orchestrator:** SNAPSHOT of `[email protected]` (MCP status). Regenerate whenever
+> **Orchestrator:** SNAPSHOT of `[email protected]` (MCP status + computers). Regenerate whenever
> it changes.
>
+> **2026-06-25 delta (SSH handoff #2 — ADDITIVE to `[email protected]`, NO version bump):** adds the
+> computer HTTP API types: `ComputerListResponse` (`GET /computers`), `ComputerResponse` (`GET /computers/:alias`),
+> `ComputerStatusResponse` (`GET /computers/:alias/status`), `TestComputerResponse` (`POST /computers/:alias/test`),
+> `SetConversationComputerRequest` + `ConversationComputerResponse`
+> (`GET`/`PUT`/`DELETE /conversations/:id/computer`), `SetWorkspaceDefaultComputerRequest`
+> (`PUT /workspaces/:id/default-computer`). Also `computerId?: string` on `ChatRequest`/`ChatSendMessage`/
+> `QueueRequest` (per-turn override; resolved server-side from the persisted per-conversation value in the MVP, so
+> `chat.send` need not send it). `Computer`/`ComputerEntry` themselves are `@dispatch/wire` types. See
+> `backend-handoff.md` §2e. (The `ssh` extension that provides the ComputerService is the last backend wave —
+> until it lands, `GET /computers` returns `[]` and statuses return `disconnected`.)
+>
> **2026-06-24 delta (MCP status handoff — package bumped `0.18.0` → `0.22.0`, ADDITIVE):** adds
> `McpServerState`, `McpServerInfo`, and `McpStatusResponse`; endpoint
> `GET /conversations/:id/mcp`. Mirrors the existing `GET /conversations/:id/lsp` shape (returns
@@ -117,6 +128,16 @@ export interface ChatRequest {
* defaultCwd = null).
*/
readonly workspaceId?: string;
+
+ /**
+ * The computer to run this turn's tools on — an SSH config `Host` alias
+ * (one of the `alias` values returned by `GET /computers`). Omit to inherit
+ * the resolved chain: per-conversation `computerId` → the workspace's
+ * `defaultComputerId` → `null`/local (today's behavior). Like `cwd`, this is
+ * a per-turn tool-execution target forwarded to tools and never part of the
+ * model prompt (so it does not affect prompt caching). Mirrors `cwd`.
+ */
+ readonly computerId?: string;
}
/**
@@ -843,5 +864,82 @@ export interface DeleteWorkspaceResponse {
/** Conversations that were closed (status → "closed") by this delete. */
readonly closedCount: number;
}
+
+// ─── Computers (SSH handoff #2) ─────────────────────────────────────────────
+
+/**
+ * Response of `GET /computers` — every remote computer discovered from the
+ * system's `~/.ssh/config`, sorted by `alias`. Parallel to
+ * `WorkspaceListResponse`: each entry is a `ComputerEntry` (a `Computer` plus a
+ * usage count). There is no Computer CRUD — to add one, the user adds a `Host`
+ * block to `~/.ssh/config` and Dispatch discovers it on the next read.
+ */
+export interface ComputerListResponse {
+ readonly computers: readonly ComputerEntry[];
+}
+
+/**
+ * Response of `GET /computers/:alias` — a single computer. Parallel to
+ * `WorkspaceResponse` (the entity itself). `alias` is the `computerId` users
+ * select; the remaining fields are resolved from the SSH config.
+ */
+export interface ComputerResponse extends Computer {}
+
+/**
+ * Response of `GET /computers/:alias/status` — the live connection state of a
+ * computer (whether Dispatch currently holds an open SSH session to it). Drives
+ * the frontend connection indicator. `error` is present only when
+ * `state === "error"`; `knownHost` mirrors the read-only `Computer` field.
+ */
+export interface ComputerStatusResponse {
+ readonly alias: string;
+ readonly state: "disconnected" | "connecting" | "connected" | "error";
+ readonly error?: string;
+ readonly knownHost: boolean;
+}
+
+/**
+ * Body of `PUT /conversations/:id/computer` — set or clear the conversation's
+ * persisted computer selection (the computer analog of `SetCwdRequest`). Pass
+ * `null` to clear → the conversation inherits the workspace's
+ * `defaultComputerId`, then `null`/local. An unknown alias is not validated here
+ * (the connection resolves at turn time; an unreachable host → turn error, not
+ * a 400). Mirrors the cwd/model PUT clear semantics.
+ */
+export interface SetConversationComputerRequest {
+ readonly computerId: string | null;
+}
+
+/**
+ * Response of `GET /conversations/:id/computer`. `computerId` is the persisted
+ * SSH `Host` alias, or `null` when never set (the conversation then inherits
+ * the workspace default → local). Parallel to `CwdResponse`.
+ */
+export interface ConversationComputerResponse {
+ readonly conversationId: string;
+ readonly computerId: string | null;
+}
+
+/**
+ * Body of `PUT /workspaces/:id/default-computer` — set or clear the workspace's
+ * default computer (the computer analog of `SetWorkspaceDefaultCwdRequest`).
+ * `null` means local (no SSH). Conversations in the workspace with no
+ * `computerId` of their own inherit this.
+ */
+export interface SetWorkspaceDefaultComputerRequest {
+ readonly computerId: string | null;
+}
+
+/**
+ * Response of `POST /computers/:alias/test` — the result of a one-shot
+ * connectivity probe (Dispatch opens an SSH connection to the alias, runs a
+ * trivial command, then closes). `ok` is true on success; `error` carries the
+ * failure reason (e.g. auth refused, host unreachable) when `ok` is false.
+ */
+export interface TestComputerResponse {
+ readonly alias: string;
+ readonly ok: boolean;
+ readonly error?: string;
+}
```
diff --git a/.dispatch/wire.reference.md b/.dispatch/wire.reference.md
index b7003ec..05ed40b 100644
--- a/.dispatch/wire.reference.md
+++ b/.dispatch/wire.reference.md
@@ -4,13 +4,30 @@
> types WITHOUT following the `file:` dep symlink out of this repo (which hangs on a permission
> prompt). Your CODE still imports `@dispatch/wire` normally — this file is for READING only.
>
-> **Orchestrator:** SNAPSHOT of `[email protected]` (workspaces). Regenerate whenever `@dispatch/wire` changes.
+> **Orchestrator:** SNAPSHOT of `[email protected]` (workspaces + computers). Regenerate whenever `@dispatch/wire` changes.
>
> **2026-06-23 delta (workspaces handoff — package bumped `0.11.0` → `0.12.0`, ADDITIVE):** adds
> `Workspace` + `WorkspaceEntry` (a list entry with a conversation count) and a required
> `workspaceId: string` on `ConversationMeta` (`"default"` for legacy/unspecified conversations). A
> workspace is a URL-driven grouping of conversations that owns a default cwd; conversations that
> haven't set their own cwd inherit `workspace.defaultCwd`. See `backend-handoff-workspaces-reply.md`.
+>
+> **2026-06-25 delta (SSH handoff #1 — ADDITIVE to `[email protected]`, NO version bump):** adds a REQUIRED
+> `defaultComputerId: string | null` on `Workspace` (null = local / no SSH; the computer analog of
+> `defaultCwd`) and two new read-only view types: `Computer` (a discovered `~/.ssh/config` `Host` alias)
+> and `ComputerEntry extends Computer` (a list entry with a `usageCount`). `alias` IS the `computerId`
+> users select (persisted per-conversation/per-workspace like cwd). The full HTTP API surface
+> (`GET /computers`, `PUT /conversations/:id/computer`, `PUT /workspaces/:id/default-computer`,
+> `GET /computers/:alias/status`, `chat.send computerId`) comes in a LATER handoff — NOT consumed yet.
+>
+> **⚠️ CROSS-REPO DIVERGENCE (2026-06-25, BLOCKING FE typecheck):** the backend `feature/ssh-support`
+> branch (where the SSH types landed) was cut from `8a74335` and is MISSING the `TurnProviderRetryEvent` /
+> `provider-retry` `AgentEvent` addition that is on `dev` (and which the FE already consumes — see §2c of
+> `backend-handoff.md`). The mirror below KEEPS `TurnProviderRetryEvent` (it is the FE's expected contract
+> and matches `dev`); it is marked where it appears. Until the backend merges `dev` into
+> `feature/ssh-support`, the FE pinned to the `feature/ssh-support` wire will NOT typecheck (11 errors,
+> all the missing `provider-retry` seam). The SSH `Computer`/`defaultComputerId` types ARE present on
+> `feature/ssh-support` and are consumed below.
```ts
/**
@@ -288,7 +305,7 @@ export type AgentEvent =
| TurnUsageEvent
| TurnStepCompleteEvent
| TurnErrorEvent
- | TurnProviderRetryEvent
+ | TurnProviderRetryEvent // ⚠️ divergent: present on `dev`, MISSING on the pinned `feature/ssh-support` wire (see header + backend-handoff.md §2c)
| TurnDoneEvent
| TurnSealedEvent
| TurnSteeringEvent;
@@ -611,6 +628,14 @@ export interface Workspace {
readonly title: string;
/** The workspace's default cwd, or `null` (fall through to server default). */
readonly defaultCwd: string | null;
+ /**
+ * The workspace's default computer — an SSH config `Host` alias that
+ * conversations in this workspace inherit when they set no `computerId` of
+ * their own. `null` means local (no SSH; today's behavior). The computer
+ * analog of `defaultCwd`. Resolved per-conversation by `getEffectiveComputer`
+ * (per-conv `computerId` → this → `null`/local).
+ */
+ readonly defaultComputerId: string | null;
/** Epoch-ms when the workspace was first created. */
readonly createdAt: number;
/** Epoch-ms of the most recent conversation activity in this workspace. */
@@ -625,4 +650,45 @@ export interface WorkspaceEntry extends Workspace {
/** Number of conversations assigned to this workspace. */
readonly conversationCount: number;
}
+
+// ─── Computers ───────────────────────────────────────────────────────────────
+
+/**
+ * A read-only view of a remote computer discovered from the system's
+ * `~/.ssh/config` — a "computer" is a `Host` alias, NOT an editable entity
+ * (there is no Computer CRUD store). To add a computer, the user adds a `Host`
+ * block to `~/.ssh/config`; Dispatch discovers it on the next `listComputers()`
+ * read. Every field below is resolved from the config (first-match-wins for
+ * `HostName`/`User`/`Port`/`IdentityFile`).
+ *
+ * `alias` is the `computerId` users select — the string persisted per
+ * conversation and per workspace (the computer analog of `cwd`). `knownHost`
+ * drives the frontend "known/new" indicator and is read-only.
+ */
+export interface Computer {
+ /** The SSH config `Host` alias — also the `computerId` users select. */
+ readonly alias: string;
+ /** Resolved `HostName`/IP from the config (falls back to the alias itself). */
+ readonly hostName: string;
+ /** Resolved port (config `Port`, default 22). */
+ readonly port: number;
+ /** Resolved user (config `User`, default the current user). */
+ readonly user: string;
+ /** Resolved `IdentityFile` path (from the config, or `null` = default `~/.ssh/id_*`). */
+ readonly identityFile: string | null;
+ /**
+ * Whether the host's key is already in `~/.ssh/known_hosts` (i.e. previously
+ * connected). Drives the frontend "known/new" indicator. Read-only.
+ */
+ readonly knownHost: boolean;
+}
+
+/**
+ * A computer entry in the list response (`GET /computers`) — a `Computer` plus
+ * a usage count. Parallel to `WorkspaceEntry`.
+ */
+export interface ComputerEntry extends Computer {
+ /** Number of conversations/workspaces whose `computerId` resolves to this alias. */
+ readonly usageCount: number;
+}
```