mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 21:03:46 +00:00
Configure lefthook formatter checks (#231)
* Add lefthook formatter checks * Fix SDK mode hydration * Stabilize SDK mode integration test
This commit is contained in:
parent
0471214d65
commit
d2346bafb3
282 changed files with 5840 additions and 8399 deletions
|
|
@ -25,7 +25,7 @@ import type {
|
|||
RepoStackActionInput,
|
||||
RepoStackActionResult,
|
||||
RepoRecord,
|
||||
SwitchResult
|
||||
SwitchResult,
|
||||
} from "@openhandoff/shared";
|
||||
import { sandboxInstanceKey, workspaceKey } from "./keys.js";
|
||||
|
||||
|
|
@ -94,7 +94,11 @@ interface WorkspaceHandle {
|
|||
}
|
||||
|
||||
interface SandboxInstanceHandle {
|
||||
createSession(input: { prompt: string; cwd?: string; agent?: AgentType | "opencode" }): Promise<{ id: string | null; status: "running" | "idle" | "error"; error?: string }>;
|
||||
createSession(input: {
|
||||
prompt: string;
|
||||
cwd?: string;
|
||||
agent?: AgentType | "opencode";
|
||||
}): Promise<{ id: string | null; status: "running" | "idle" | "error"; error?: string }>;
|
||||
listSessions(input?: { cursor?: string; limit?: number }): Promise<{ items: SandboxSessionRecord[]; nextCursor?: string }>;
|
||||
listSessionEvents(input: { sessionId: string; cursor?: string; limit?: number }): Promise<{ items: SandboxSessionEventRecord[]; nextCursor?: string }>;
|
||||
sendPrompt(input: { sessionId: string; prompt: string; notification?: boolean }): Promise<void>;
|
||||
|
|
@ -148,13 +152,13 @@ export interface BackendClient {
|
|||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string,
|
||||
input?: { cursor?: string; limit?: number }
|
||||
input?: { cursor?: string; limit?: number },
|
||||
): Promise<{ items: SandboxSessionRecord[]; nextCursor?: string }>;
|
||||
listSandboxSessionEvents(
|
||||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string,
|
||||
input: { sessionId: string; cursor?: string; limit?: number }
|
||||
input: { sessionId: string; cursor?: string; limit?: number },
|
||||
): Promise<{ items: SandboxSessionEventRecord[]; nextCursor?: string }>;
|
||||
sendSandboxPrompt(input: {
|
||||
workspaceId: string;
|
||||
|
|
@ -168,31 +172,22 @@ export interface BackendClient {
|
|||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string,
|
||||
sessionId: string
|
||||
sessionId: string,
|
||||
): Promise<{ id: string; status: "running" | "idle" | "error" }>;
|
||||
sandboxProviderState(
|
||||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string
|
||||
sandboxId: string,
|
||||
): Promise<{ providerId: ProviderId; sandboxId: string; state: string; at: number }>;
|
||||
getWorkbench(workspaceId: string): Promise<HandoffWorkbenchSnapshot>;
|
||||
subscribeWorkbench(workspaceId: string, listener: () => void): () => void;
|
||||
createWorkbenchHandoff(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchCreateHandoffInput
|
||||
): Promise<HandoffWorkbenchCreateHandoffResponse>;
|
||||
createWorkbenchHandoff(workspaceId: string, input: HandoffWorkbenchCreateHandoffInput): Promise<HandoffWorkbenchCreateHandoffResponse>;
|
||||
markWorkbenchUnread(workspaceId: string, input: HandoffWorkbenchSelectInput): Promise<void>;
|
||||
renameWorkbenchHandoff(workspaceId: string, input: HandoffWorkbenchRenameInput): Promise<void>;
|
||||
renameWorkbenchBranch(workspaceId: string, input: HandoffWorkbenchRenameInput): Promise<void>;
|
||||
createWorkbenchSession(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchSelectInput & { model?: string }
|
||||
): Promise<{ tabId: string }>;
|
||||
createWorkbenchSession(workspaceId: string, input: HandoffWorkbenchSelectInput & { model?: string }): Promise<{ tabId: string }>;
|
||||
renameWorkbenchSession(workspaceId: string, input: HandoffWorkbenchRenameSessionInput): Promise<void>;
|
||||
setWorkbenchSessionUnread(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchSetSessionUnreadInput
|
||||
): Promise<void>;
|
||||
setWorkbenchSessionUnread(workspaceId: string, input: HandoffWorkbenchSetSessionUnreadInput): Promise<void>;
|
||||
updateWorkbenchDraft(workspaceId: string, input: HandoffWorkbenchUpdateDraftInput): Promise<void>;
|
||||
changeWorkbenchModel(workspaceId: string, input: HandoffWorkbenchChangeModelInput): Promise<void>;
|
||||
sendWorkbenchMessage(workspaceId: string, input: HandoffWorkbenchSendMessageInput): Promise<void>;
|
||||
|
|
@ -211,7 +206,7 @@ export function rivetEndpoint(config: AppConfig): string {
|
|||
export function createBackendClientFromConfig(config: AppConfig): BackendClient {
|
||||
return createBackendClient({
|
||||
endpoint: rivetEndpoint(config),
|
||||
defaultWorkspaceId: config.workspace.default
|
||||
defaultWorkspaceId: config.workspace.default,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +245,7 @@ async function fetchJsonWithTimeout(url: string, timeoutMs: number): Promise<unk
|
|||
async function fetchMetadataWithRetry(
|
||||
endpoint: string,
|
||||
namespace: string | undefined,
|
||||
opts: { timeoutMs: number; requestTimeoutMs: number }
|
||||
opts: { timeoutMs: number; requestTimeoutMs: number },
|
||||
): Promise<RivetMetadataResponse> {
|
||||
const base = new URL(endpoint);
|
||||
base.pathname = base.pathname.replace(/\/$/, "") + "/metadata";
|
||||
|
|
@ -268,10 +263,7 @@ async function fetchMetadataWithRetry(
|
|||
const data = json as Record<string, unknown>;
|
||||
return {
|
||||
runtime: typeof data.runtime === "string" ? data.runtime : undefined,
|
||||
actorNames:
|
||||
data.actorNames && typeof data.actorNames === "object"
|
||||
? (data.actorNames as Record<string, unknown>)
|
||||
: undefined,
|
||||
actorNames: data.actorNames && typeof data.actorNames === "object" ? (data.actorNames as Record<string, unknown>) : undefined,
|
||||
clientEndpoint: typeof data.clientEndpoint === "string" ? data.clientEndpoint : undefined,
|
||||
clientNamespace: typeof data.clientNamespace === "string" ? data.clientNamespace : undefined,
|
||||
clientToken: typeof data.clientToken === "string" ? data.clientToken : undefined,
|
||||
|
|
@ -286,11 +278,7 @@ async function fetchMetadataWithRetry(
|
|||
}
|
||||
}
|
||||
|
||||
export async function readBackendMetadata(input: {
|
||||
endpoint: string;
|
||||
namespace?: string;
|
||||
timeoutMs?: number;
|
||||
}): Promise<BackendMetadata> {
|
||||
export async function readBackendMetadata(input: { endpoint: string; namespace?: string; timeoutMs?: number }): Promise<BackendMetadata> {
|
||||
const base = new URL(input.endpoint);
|
||||
base.pathname = base.pathname.replace(/\/$/, "") + "/metadata";
|
||||
if (input.namespace) {
|
||||
|
|
@ -304,21 +292,14 @@ export async function readBackendMetadata(input: {
|
|||
const data = json as Record<string, unknown>;
|
||||
return {
|
||||
runtime: typeof data.runtime === "string" ? data.runtime : undefined,
|
||||
actorNames:
|
||||
data.actorNames && typeof data.actorNames === "object"
|
||||
? (data.actorNames as Record<string, unknown>)
|
||||
: undefined,
|
||||
actorNames: data.actorNames && typeof data.actorNames === "object" ? (data.actorNames as Record<string, unknown>) : undefined,
|
||||
clientEndpoint: typeof data.clientEndpoint === "string" ? data.clientEndpoint : undefined,
|
||||
clientNamespace: typeof data.clientNamespace === "string" ? data.clientNamespace : undefined,
|
||||
clientToken: typeof data.clientToken === "string" ? data.clientToken : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export async function checkBackendHealth(input: {
|
||||
endpoint: string;
|
||||
namespace?: string;
|
||||
timeoutMs?: number;
|
||||
}): Promise<boolean> {
|
||||
export async function checkBackendHealth(input: { endpoint: string; namespace?: string; timeoutMs?: number }): Promise<boolean> {
|
||||
try {
|
||||
const metadata = await readBackendMetadata(input);
|
||||
return metadata.runtime === "rivetkit" && Boolean(metadata.actorNames);
|
||||
|
|
@ -327,11 +308,7 @@ export async function checkBackendHealth(input: {
|
|||
}
|
||||
}
|
||||
|
||||
async function probeMetadataEndpoint(
|
||||
endpoint: string,
|
||||
namespace: string | undefined,
|
||||
timeoutMs: number
|
||||
): Promise<boolean> {
|
||||
async function probeMetadataEndpoint(endpoint: string, namespace: string | undefined, timeoutMs: number): Promise<boolean> {
|
||||
try {
|
||||
const base = new URL(endpoint);
|
||||
base.pathname = base.pathname.replace(/\/$/, "") + "/metadata";
|
||||
|
|
@ -370,19 +347,15 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
const initialNamespace = undefined;
|
||||
const metadata = await fetchMetadataWithRetry(options.endpoint, initialNamespace, {
|
||||
timeoutMs: 30_000,
|
||||
requestTimeoutMs: 8_000
|
||||
requestTimeoutMs: 8_000,
|
||||
});
|
||||
|
||||
// Candidate endpoint: manager endpoint if provided, otherwise stick to the configured endpoint.
|
||||
const candidateEndpoint = metadata.clientEndpoint
|
||||
? rewriteLoopbackClientEndpoint(metadata.clientEndpoint, configuredOrigin)
|
||||
: options.endpoint;
|
||||
const candidateEndpoint = metadata.clientEndpoint ? rewriteLoopbackClientEndpoint(metadata.clientEndpoint, configuredOrigin) : options.endpoint;
|
||||
|
||||
// If the manager port isn't reachable from this client (common behind reverse proxies),
|
||||
// fall back to the configured serverless endpoint to avoid hanging requests.
|
||||
const shouldUseCandidate = metadata.clientEndpoint
|
||||
? await probeMetadataEndpoint(candidateEndpoint, metadata.clientNamespace, 1_500)
|
||||
: true;
|
||||
const shouldUseCandidate = metadata.clientEndpoint ? await probeMetadataEndpoint(candidateEndpoint, metadata.clientNamespace, 1_500) : true;
|
||||
const resolvedEndpoint = shouldUseCandidate ? candidateEndpoint : options.endpoint;
|
||||
|
||||
return createClient({
|
||||
|
|
@ -399,14 +372,10 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
|
||||
const workspace = async (workspaceId: string): Promise<WorkspaceHandle> =>
|
||||
(await getClient()).workspace.getOrCreate(workspaceKey(workspaceId), {
|
||||
createWithInput: workspaceId
|
||||
createWithInput: workspaceId,
|
||||
});
|
||||
|
||||
const sandboxByKey = async (
|
||||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string
|
||||
): Promise<SandboxInstanceHandle> => {
|
||||
const sandboxByKey = async (workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<SandboxInstanceHandle> => {
|
||||
const client = await getClient();
|
||||
return (client as any).sandboxInstance.get(sandboxInstanceKey(workspaceId, providerId, sandboxId));
|
||||
};
|
||||
|
|
@ -416,11 +385,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
return message.includes("Actor not found");
|
||||
}
|
||||
|
||||
const sandboxByActorIdFromHandoff = async (
|
||||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string
|
||||
): Promise<SandboxInstanceHandle | null> => {
|
||||
const sandboxByActorIdFromHandoff = async (workspaceId: string, providerId: ProviderId, sandboxId: string): Promise<SandboxInstanceHandle | null> => {
|
||||
const ws = await workspace(workspaceId);
|
||||
const rows = await ws.listHandoffs({ workspaceId });
|
||||
const candidates = [...rows].sort((a, b) => b.updatedAt - a.updatedAt);
|
||||
|
|
@ -431,12 +396,13 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
if (detail.providerId !== providerId) {
|
||||
continue;
|
||||
}
|
||||
const sandbox = detail.sandboxes.find((sb) =>
|
||||
sb.sandboxId === sandboxId &&
|
||||
sb.providerId === providerId &&
|
||||
typeof (sb as any).sandboxActorId === "string" &&
|
||||
(sb as any).sandboxActorId.length > 0
|
||||
) as ({ sandboxActorId?: string } | undefined);
|
||||
const sandbox = detail.sandboxes.find(
|
||||
(sb) =>
|
||||
sb.sandboxId === sandboxId &&
|
||||
sb.providerId === providerId &&
|
||||
typeof (sb as any).sandboxActorId === "string" &&
|
||||
(sb as any).sandboxActorId.length > 0,
|
||||
) as { sandboxActorId?: string } | undefined;
|
||||
if (sandbox?.sandboxActorId) {
|
||||
const client = await getClient();
|
||||
return (client as any).sandboxInstance.getForId(sandbox.sandboxActorId);
|
||||
|
|
@ -457,7 +423,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string,
|
||||
run: (handle: SandboxInstanceHandle) => Promise<T>
|
||||
run: (handle: SandboxInstanceHandle) => Promise<T>,
|
||||
): Promise<T> => {
|
||||
const handle = await sandboxByKey(workspaceId, providerId, sandboxId);
|
||||
try {
|
||||
|
|
@ -553,7 +519,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
async getHandoff(workspaceId: string, handoffId: string): Promise<HandoffRecord> {
|
||||
return (await workspace(workspaceId)).getHandoff({
|
||||
workspaceId,
|
||||
handoffId
|
||||
handoffId,
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -569,7 +535,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
return (await workspace(workspaceId)).attachHandoff({
|
||||
workspaceId,
|
||||
handoffId,
|
||||
reason: "cli.attach"
|
||||
reason: "cli.attach",
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -578,7 +544,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
await (await workspace(workspaceId)).pushHandoff({
|
||||
workspaceId,
|
||||
handoffId,
|
||||
reason: "cli.push"
|
||||
reason: "cli.push",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -586,7 +552,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
await (await workspace(workspaceId)).syncHandoff({
|
||||
workspaceId,
|
||||
handoffId,
|
||||
reason: "cli.sync"
|
||||
reason: "cli.sync",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -594,7 +560,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
await (await workspace(workspaceId)).mergeHandoff({
|
||||
workspaceId,
|
||||
handoffId,
|
||||
reason: "cli.merge"
|
||||
reason: "cli.merge",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
|
@ -602,14 +568,14 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
await (await workspace(workspaceId)).archiveHandoff({
|
||||
workspaceId,
|
||||
handoffId,
|
||||
reason: "cli.archive"
|
||||
reason: "cli.archive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
await (await workspace(workspaceId)).killHandoff({
|
||||
workspaceId,
|
||||
handoffId,
|
||||
reason: "cli.kill"
|
||||
reason: "cli.kill",
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -621,23 +587,19 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
cwd?: string;
|
||||
agent?: AgentType | "opencode";
|
||||
}): Promise<{ id: string; status: "running" | "idle" | "error" }> {
|
||||
const created = await withSandboxHandle(
|
||||
input.workspaceId,
|
||||
input.providerId,
|
||||
input.sandboxId,
|
||||
async (handle) =>
|
||||
handle.createSession({
|
||||
prompt: input.prompt,
|
||||
cwd: input.cwd,
|
||||
agent: input.agent
|
||||
})
|
||||
const created = await withSandboxHandle(input.workspaceId, input.providerId, input.sandboxId, async (handle) =>
|
||||
handle.createSession({
|
||||
prompt: input.prompt,
|
||||
cwd: input.cwd,
|
||||
agent: input.agent,
|
||||
}),
|
||||
);
|
||||
if (!created.id) {
|
||||
throw new Error(created.error ?? "sandbox session creation failed");
|
||||
}
|
||||
return {
|
||||
id: created.id,
|
||||
status: created.status
|
||||
status: created.status,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -645,28 +607,18 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string,
|
||||
input?: { cursor?: string; limit?: number }
|
||||
input?: { cursor?: string; limit?: number },
|
||||
): Promise<{ items: SandboxSessionRecord[]; nextCursor?: string }> {
|
||||
return await withSandboxHandle(
|
||||
workspaceId,
|
||||
providerId,
|
||||
sandboxId,
|
||||
async (handle) => handle.listSessions(input ?? {})
|
||||
);
|
||||
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.listSessions(input ?? {}));
|
||||
},
|
||||
|
||||
async listSandboxSessionEvents(
|
||||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string,
|
||||
input: { sessionId: string; cursor?: string; limit?: number }
|
||||
input: { sessionId: string; cursor?: string; limit?: number },
|
||||
): Promise<{ items: SandboxSessionEventRecord[]; nextCursor?: string }> {
|
||||
return await withSandboxHandle(
|
||||
workspaceId,
|
||||
providerId,
|
||||
sandboxId,
|
||||
async (handle) => handle.listSessionEvents(input)
|
||||
);
|
||||
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.listSessionEvents(input));
|
||||
},
|
||||
|
||||
async sendSandboxPrompt(input: {
|
||||
|
|
@ -677,16 +629,12 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
prompt: string;
|
||||
notification?: boolean;
|
||||
}): Promise<void> {
|
||||
await withSandboxHandle(
|
||||
input.workspaceId,
|
||||
input.providerId,
|
||||
input.sandboxId,
|
||||
async (handle) =>
|
||||
handle.sendPrompt({
|
||||
sessionId: input.sessionId,
|
||||
prompt: input.prompt,
|
||||
notification: input.notification
|
||||
})
|
||||
await withSandboxHandle(input.workspaceId, input.providerId, input.sandboxId, async (handle) =>
|
||||
handle.sendPrompt({
|
||||
sessionId: input.sessionId,
|
||||
prompt: input.prompt,
|
||||
notification: input.notification,
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
|
|
@ -694,27 +642,17 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string,
|
||||
sessionId: string
|
||||
sessionId: string,
|
||||
): Promise<{ id: string; status: "running" | "idle" | "error" }> {
|
||||
return await withSandboxHandle(
|
||||
workspaceId,
|
||||
providerId,
|
||||
sandboxId,
|
||||
async (handle) => handle.sessionStatus({ sessionId })
|
||||
);
|
||||
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.sessionStatus({ sessionId }));
|
||||
},
|
||||
|
||||
async sandboxProviderState(
|
||||
workspaceId: string,
|
||||
providerId: ProviderId,
|
||||
sandboxId: string
|
||||
sandboxId: string,
|
||||
): Promise<{ providerId: ProviderId; sandboxId: string; state: string; at: number }> {
|
||||
return await withSandboxHandle(
|
||||
workspaceId,
|
||||
providerId,
|
||||
sandboxId,
|
||||
async (handle) => handle.providerState()
|
||||
);
|
||||
return await withSandboxHandle(workspaceId, providerId, sandboxId, async (handle) => handle.providerState());
|
||||
},
|
||||
|
||||
async getWorkbench(workspaceId: string): Promise<HandoffWorkbenchSnapshot> {
|
||||
|
|
@ -725,10 +663,7 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
return subscribeWorkbench(workspaceId, listener);
|
||||
},
|
||||
|
||||
async createWorkbenchHandoff(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchCreateHandoffInput
|
||||
): Promise<HandoffWorkbenchCreateHandoffResponse> {
|
||||
async createWorkbenchHandoff(workspaceId: string, input: HandoffWorkbenchCreateHandoffInput): Promise<HandoffWorkbenchCreateHandoffResponse> {
|
||||
return (await workspace(workspaceId)).createWorkbenchHandoff(input);
|
||||
},
|
||||
|
||||
|
|
@ -744,45 +679,27 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
await (await workspace(workspaceId)).renameWorkbenchBranch(input);
|
||||
},
|
||||
|
||||
async createWorkbenchSession(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchSelectInput & { model?: string }
|
||||
): Promise<{ tabId: string }> {
|
||||
async createWorkbenchSession(workspaceId: string, input: HandoffWorkbenchSelectInput & { model?: string }): Promise<{ tabId: string }> {
|
||||
return await (await workspace(workspaceId)).createWorkbenchSession(input);
|
||||
},
|
||||
|
||||
async renameWorkbenchSession(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchRenameSessionInput
|
||||
): Promise<void> {
|
||||
async renameWorkbenchSession(workspaceId: string, input: HandoffWorkbenchRenameSessionInput): Promise<void> {
|
||||
await (await workspace(workspaceId)).renameWorkbenchSession(input);
|
||||
},
|
||||
|
||||
async setWorkbenchSessionUnread(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchSetSessionUnreadInput
|
||||
): Promise<void> {
|
||||
async setWorkbenchSessionUnread(workspaceId: string, input: HandoffWorkbenchSetSessionUnreadInput): Promise<void> {
|
||||
await (await workspace(workspaceId)).setWorkbenchSessionUnread(input);
|
||||
},
|
||||
|
||||
async updateWorkbenchDraft(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchUpdateDraftInput
|
||||
): Promise<void> {
|
||||
async updateWorkbenchDraft(workspaceId: string, input: HandoffWorkbenchUpdateDraftInput): Promise<void> {
|
||||
await (await workspace(workspaceId)).updateWorkbenchDraft(input);
|
||||
},
|
||||
|
||||
async changeWorkbenchModel(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchChangeModelInput
|
||||
): Promise<void> {
|
||||
async changeWorkbenchModel(workspaceId: string, input: HandoffWorkbenchChangeModelInput): Promise<void> {
|
||||
await (await workspace(workspaceId)).changeWorkbenchModel(input);
|
||||
},
|
||||
|
||||
async sendWorkbenchMessage(
|
||||
workspaceId: string,
|
||||
input: HandoffWorkbenchSendMessageInput
|
||||
): Promise<void> {
|
||||
async sendWorkbenchMessage(workspaceId: string, input: HandoffWorkbenchSendMessageInput): Promise<void> {
|
||||
await (await workspace(workspaceId)).sendWorkbenchMessage(input);
|
||||
},
|
||||
|
||||
|
|
@ -809,13 +726,13 @@ export function createBackendClient(options: BackendClientOptions): BackendClien
|
|||
}
|
||||
|
||||
await (await workspace(workspaceId)).useWorkspace({
|
||||
workspaceId
|
||||
workspaceId,
|
||||
});
|
||||
return { ok: true };
|
||||
},
|
||||
|
||||
async useWorkspace(workspaceId: string): Promise<{ workspaceId: string }> {
|
||||
return (await workspace(workspaceId)).useWorkspace({ workspaceId });
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,7 @@ export function handoffKey(workspaceId: string, repoId: string, handoffId: strin
|
|||
return ["ws", workspaceId, "project", repoId, "handoff", handoffId];
|
||||
}
|
||||
|
||||
export function sandboxInstanceKey(
|
||||
workspaceId: string,
|
||||
providerId: string,
|
||||
sandboxId: string
|
||||
): ActorKey {
|
||||
export function sandboxInstanceKey(workspaceId: string, providerId: string, sandboxId: string): ActorKey {
|
||||
return ["ws", workspaceId, "provider", providerId, "sandbox", sandboxId];
|
||||
}
|
||||
|
||||
|
|
@ -32,13 +28,7 @@ export function projectBranchSyncKey(workspaceId: string, repoId: string): Actor
|
|||
return ["ws", workspaceId, "project", repoId, "branch-sync"];
|
||||
}
|
||||
|
||||
export function handoffStatusSyncKey(
|
||||
workspaceId: string,
|
||||
repoId: string,
|
||||
handoffId: string,
|
||||
sandboxId: string,
|
||||
sessionId: string
|
||||
): ActorKey {
|
||||
export function handoffStatusSyncKey(workspaceId: string, repoId: string, handoffId: string, sandboxId: string, sessionId: string): ActorKey {
|
||||
// Include sandbox + session so multiple sandboxes/sessions can be tracked per handoff.
|
||||
return ["ws", workspaceId, "project", repoId, "handoff", handoffId, "status-sync", sandboxId, sessionId];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,9 @@ class MockWorkbenchStore implements HandoffWorkbenchClient {
|
|||
id: tabId,
|
||||
sessionId: tabId,
|
||||
sessionName: "Session 1",
|
||||
agent: providerAgent(MODEL_GROUPS.find((group) => group.models.some((model) => model.id === (input.model ?? "claude-sonnet-4")))?.provider ?? "Claude"),
|
||||
agent: providerAgent(
|
||||
MODEL_GROUPS.find((group) => group.models.some((model) => model.id === (input.model ?? "claude-sonnet-4")))?.provider ?? "Claude",
|
||||
),
|
||||
model: input.model ?? "claude-sonnet-4",
|
||||
status: "idle",
|
||||
thinkingSinceMs: null,
|
||||
|
|
@ -311,9 +313,7 @@ class MockWorkbenchStore implements HandoffWorkbenchClient {
|
|||
async setSessionUnread(input: HandoffWorkbenchSetSessionUnreadInput): Promise<void> {
|
||||
this.updateHandoff(input.handoffId, (currentHandoff) => ({
|
||||
...currentHandoff,
|
||||
tabs: currentHandoff.tabs.map((candidate) =>
|
||||
candidate.id === input.tabId ? { ...candidate, unread: input.unread } : candidate,
|
||||
),
|
||||
tabs: currentHandoff.tabs.map((candidate) => (candidate.id === input.tabId ? { ...candidate, unread: input.unread } : candidate)),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -324,9 +324,7 @@ class MockWorkbenchStore implements HandoffWorkbenchClient {
|
|||
}
|
||||
this.updateHandoff(input.handoffId, (currentHandoff) => ({
|
||||
...currentHandoff,
|
||||
tabs: currentHandoff.tabs.map((candidate) =>
|
||||
candidate.id === input.tabId ? { ...candidate, sessionName: title } : candidate,
|
||||
),
|
||||
tabs: currentHandoff.tabs.map((candidate) => (candidate.id === input.tabId ? { ...candidate, sessionName: title } : candidate)),
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,8 +192,6 @@ class RemoteWorkbenchStore implements HandoffWorkbenchClient {
|
|||
}
|
||||
}
|
||||
|
||||
export function createRemoteWorkbenchClient(
|
||||
options: RemoteWorkbenchClientOptions,
|
||||
): HandoffWorkbenchClient {
|
||||
export function createRemoteWorkbenchClient(options: RemoteWorkbenchClientOptions): HandoffWorkbenchClient {
|
||||
return new RemoteWorkbenchStore(options);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,6 @@
|
|||
import type { HandoffRecord, HandoffStatus } from "@openhandoff/shared";
|
||||
|
||||
export const HANDOFF_STATUS_GROUPS = [
|
||||
"queued",
|
||||
"running",
|
||||
"idle",
|
||||
"archived",
|
||||
"killed",
|
||||
"error"
|
||||
] as const;
|
||||
export const HANDOFF_STATUS_GROUPS = ["queued", "running", "idle", "archived", "killed", "error"] as const;
|
||||
|
||||
export type HandoffStatusGroup = (typeof HANDOFF_STATUS_GROUPS)[number];
|
||||
|
||||
|
|
@ -27,7 +20,7 @@ const QUEUED_STATUSES = new Set<HandoffStatus>([
|
|||
"archive_release_sandbox",
|
||||
"archive_finalize",
|
||||
"kill_destroy_sandbox",
|
||||
"kill_finalize"
|
||||
"kill_finalize",
|
||||
]);
|
||||
|
||||
export function groupHandoffStatus(status: HandoffStatus): HandoffStatusGroup {
|
||||
|
|
@ -47,7 +40,7 @@ function emptyStatusCounts(): Record<HandoffStatusGroup, number> {
|
|||
idle: 0,
|
||||
archived: 0,
|
||||
killed: 0,
|
||||
error: 0
|
||||
error: 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -78,14 +71,7 @@ export function filterHandoffs(rows: HandoffRecord[], query: string): HandoffRec
|
|||
}
|
||||
|
||||
return rows.filter((row) => {
|
||||
const fields = [
|
||||
row.branchName ?? "",
|
||||
row.title ?? "",
|
||||
row.handoffId,
|
||||
row.task,
|
||||
row.prAuthor ?? "",
|
||||
row.reviewer ?? ""
|
||||
];
|
||||
const fields = [row.branchName ?? "", row.title ?? "", row.handoffId, row.task, row.prAuthor ?? "", row.reviewer ?? ""];
|
||||
return fields.some((field) => fuzzyMatch(field, q));
|
||||
});
|
||||
}
|
||||
|
|
@ -113,6 +99,6 @@ export function summarizeHandoffs(rows: HandoffRecord[]): HandoffSummary {
|
|||
return {
|
||||
total: rows.length,
|
||||
byStatus,
|
||||
byProvider
|
||||
byProvider,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ export interface HandoffWorkbenchClient {
|
|||
changeModel(input: HandoffWorkbenchChangeModelInput): Promise<void>;
|
||||
}
|
||||
|
||||
export function createHandoffWorkbenchClient(
|
||||
options: CreateHandoffWorkbenchClientOptions,
|
||||
): HandoffWorkbenchClient {
|
||||
export function createHandoffWorkbenchClient(options: CreateHandoffWorkbenchClientOptions): HandoffWorkbenchClient {
|
||||
if (options.mode === "mock") {
|
||||
return getSharedMockWorkbenchClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,11 @@ export function providerAgent(provider: string): AgentKind {
|
|||
}
|
||||
|
||||
export function slugify(text: string): string {
|
||||
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "")
|
||||
.slice(0, 40);
|
||||
}
|
||||
|
||||
export function randomReply(): string {
|
||||
|
|
@ -707,13 +711,13 @@ export function buildInitialHandoffs(): Handoff[] {
|
|||
"+ const [isDark, setIsDark] = useState(theme === 'dark');",
|
||||
" ",
|
||||
" return (",
|
||||
" <div className=\"settings-page\">",
|
||||
' <div className="settings-page">',
|
||||
"+ <Card>",
|
||||
"+ <h3>Appearance</h3>",
|
||||
"+ <div className=\"setting-row\">",
|
||||
"+ <Label htmlFor=\"dark-mode\">Dark Mode</Label>",
|
||||
'+ <div className="setting-row">',
|
||||
'+ <Label htmlFor="dark-mode">Dark Mode</Label>',
|
||||
"+ <Toggle",
|
||||
"+ id=\"dark-mode\"",
|
||||
'+ id="dark-mode"',
|
||||
"+ checked={isDark}",
|
||||
"+ onCheckedChange={(checked) => {",
|
||||
"+ setIsDark(checked);",
|
||||
|
|
@ -725,7 +729,7 @@ export function buildInitialHandoffs(): Handoff[] {
|
|||
"+ }}",
|
||||
"+ />",
|
||||
"+ </div>",
|
||||
"+ <p className=\"setting-description\">",
|
||||
'+ <p className="setting-description">',
|
||||
"+ Toggle between light and dark color schemes.",
|
||||
"+ Your preference is saved to localStorage.",
|
||||
"+ </p>",
|
||||
|
|
@ -733,11 +737,11 @@ export function buildInitialHandoffs(): Handoff[] {
|
|||
"+",
|
||||
" <Card>",
|
||||
" <h3>Notifications</h3>",
|
||||
" <div className=\"setting-row\">",
|
||||
' <div className="setting-row">',
|
||||
"- <Label>Email notifications</Label>",
|
||||
"- <Toggle checked={notifications} onCheckedChange={setNotifications} />",
|
||||
"+ <Label htmlFor=\"notifs\">Email notifications</Label>",
|
||||
"+ <Toggle id=\"notifs\" checked={notifications} onCheckedChange={setNotifications} />",
|
||||
'+ <Label htmlFor="notifs">Email notifications</Label>',
|
||||
'+ <Toggle id="notifs" checked={notifications} onCheckedChange={setNotifications} />',
|
||||
" </div>",
|
||||
" </Card>",
|
||||
].join("\n"),
|
||||
|
|
@ -957,8 +961,7 @@ export function groupWorkbenchProjects(repos: WorkbenchRepo[], handoffs: Handoff
|
|||
.map((project) => ({
|
||||
...project,
|
||||
handoffs: [...project.handoffs].sort((a, b) => b.updatedAtMs - a.updatedAtMs),
|
||||
updatedAtMs:
|
||||
project.handoffs.length > 0 ? Math.max(...project.handoffs.map((handoff) => handoff.updatedAtMs)) : project.updatedAtMs,
|
||||
updatedAtMs: project.handoffs.length > 0 ? Math.max(...project.handoffs.map((handoff) => handoff.updatedAtMs)) : project.updatedAtMs,
|
||||
}))
|
||||
.filter((project) => project.handoffs.length > 0)
|
||||
.sort((a, b) => b.updatedAtMs - a.updatedAtMs);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue