mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-09 17:32:41 -10:00
feat(daemon): CmuxAdapter layout RPC method extensions (slice 24 Checkpoint A)
Adds four new RPC method wrappers to CmuxAdapter powering the slice 24
layout flow per OPT-B (extend existing RPC adapter; chosen after
pre-scaffold spike confirmed cmux RPC exposes all required primitives):
- splitSurface(surfaceId, direction, workspaceId?): CmuxResult<string>
Calls surface.split RPC; returns new surface handle. Direction is
"left" | "right" | "up" | "down". Defensive handling of multiple
response field name variants (created_surface_ref / created_surface_id
/ surface_ref / surface_id / id) via shared normalizeHandle helper.
- createWorkspace(name, cwd?): CmuxResult<string>
Calls workspace.create RPC; returns new workspace handle. Optional
cwd is omitted from params when not provided (cmux daemon picks a
default).
- closeWorkspace(workspaceId): CmuxResult<void>
Calls workspace.close RPC for cleanup paths (e.g., conflict-resolution
retry, test teardown).
- listPaneSurfaces(paneId, workspaceId?): CmuxResult<CmuxSurface[]>
Calls pane.surfaces RPC; returns surfaces in a pane. Used by the
layout flow to discover the pane ID a new surface belongs to after
surface.split (cmux returns surface handle; pane lookup is a separate
call per the SOP).
Parameter convention: snake_case (surface_id, workspace_id, pane_id,
direction, name, cwd). Verified via slice 24 pre-scaffold spike against
live cmux daemon — `cmux rpc workspace.close { "workspace_id": ... }`
succeeds; camelCase `workspaceId` returns "Missing or invalid
workspace_id". Existing CmuxAdapter methods (sendText, focus, etc.)
use camelCase params and are unchanged at this checkpoint; orthogonal
to slice 24 contract.
Tests: 15/15 new layout-method tests green; 33/33 existing CmuxAdapter
tests still green (zero regression).
SC-29 EXCEPTION declared: NONE at this checkpoint
(adapter extension only; no new ConfigStore key, env var, daemon
endpoint, schema migration, or event type. SC-29 will land at slice 24
Checkpoint C when the new POST /api/rigs/:rigId/cmux/launch route is
added; declared in that commit body + slice ACK.)
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
2835a283b3
commit
cd3383b2df
1 file changed
+91
@@ -186,6 +186,97 @@ export class CmuxAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Slice 24 layout primitives — see slice 24 pre-scaffold spike result.
|
||||
// cmux RPC accepts these methods with snake_case params (verified via
|
||||
// `cmux rpc workspace.close` probe). New methods adopt snake_case
|
||||
// explicitly; existing camelCase callers (sendText, focus, etc.) are
|
||||
// unchanged at this checkpoint.
|
||||
|
||||
async splitSurface(
|
||||
surfaceId: string,
|
||||
direction: "left" | "right" | "up" | "down",
|
||||
workspaceId?: string,
|
||||
): Promise<CmuxResult<string>> {
|
||||
if (!this.transport) {
|
||||
return { ok: false, code: "unavailable", message: "cmux is not connected" };
|
||||
}
|
||||
try {
|
||||
const params: Record<string, unknown> = {
|
||||
surface_id: surfaceId,
|
||||
direction,
|
||||
};
|
||||
if (workspaceId != null) params["workspace_id"] = workspaceId;
|
||||
const raw = (await this.transport.request("surface.split", params)) as Record<string, unknown>;
|
||||
const handle = [
|
||||
raw["created_surface_ref"],
|
||||
raw["created_surface_id"],
|
||||
raw["surface_ref"],
|
||||
raw["surface_id"],
|
||||
raw["id"],
|
||||
]
|
||||
.map((value) => normalizeHandle("surface", value))
|
||||
.find((value): value is string => Boolean(value));
|
||||
if (!handle) {
|
||||
return { ok: false, code: "request_failed", message: "cmux surface.split returned no surface handle" };
|
||||
}
|
||||
return { ok: true, data: handle };
|
||||
} catch (err) {
|
||||
return { ok: false, code: "request_failed", message: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
async createWorkspace(name: string, cwd?: string): Promise<CmuxResult<string>> {
|
||||
if (!this.transport) {
|
||||
return { ok: false, code: "unavailable", message: "cmux is not connected" };
|
||||
}
|
||||
try {
|
||||
const params: Record<string, unknown> = { name };
|
||||
if (cwd != null) params["cwd"] = cwd;
|
||||
const raw = (await this.transport.request("workspace.create", params)) as Record<string, unknown>;
|
||||
const handle = [
|
||||
raw["workspace_ref"],
|
||||
raw["workspace_id"],
|
||||
raw["id"],
|
||||
]
|
||||
.map((value) => normalizeHandle("workspace", value))
|
||||
.find((value): value is string => Boolean(value));
|
||||
if (!handle) {
|
||||
return { ok: false, code: "request_failed", message: "cmux workspace.create returned no workspace handle" };
|
||||
}
|
||||
return { ok: true, data: handle };
|
||||
} catch (err) {
|
||||
return { ok: false, code: "request_failed", message: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
async closeWorkspace(workspaceId: string): Promise<CmuxResult<void>> {
|
||||
if (!this.transport) {
|
||||
return { ok: false, code: "unavailable", message: "cmux is not connected" };
|
||||
}
|
||||
try {
|
||||
await this.transport.request("workspace.close", { workspace_id: workspaceId });
|
||||
return { ok: true, data: undefined };
|
||||
} catch (err) {
|
||||
return { ok: false, code: "request_failed", message: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
async listPaneSurfaces(paneId: string, workspaceId?: string): Promise<CmuxResult<CmuxSurface[]>> {
|
||||
if (!this.transport) {
|
||||
return { ok: false, code: "unavailable", message: "cmux is not connected" };
|
||||
}
|
||||
try {
|
||||
const params: Record<string, unknown> = { pane_id: paneId };
|
||||
if (workspaceId != null) params["workspace_id"] = workspaceId;
|
||||
const result = (await this.transport.request("pane.surfaces", params)) as {
|
||||
surfaces?: CmuxSurface[];
|
||||
};
|
||||
return { ok: true, data: result.surfaces ?? [] };
|
||||
} catch (err) {
|
||||
return { ok: false, code: "request_failed", message: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
/** Query cmux for agent PIDs (sidebar metadata). Returns Map<pid, { runtime, pid }>. */
|
||||
async queryAgentPIDs(): Promise<CmuxResult<Map<number, { runtime: string; pid: number }>>> {
|
||||
if (!this.transport) {
|
||||
|
||||
Reference in new issue
Block a user