Integrate OpenHandoff factory workspace (#212)

This commit is contained in:
Nathan Flurry 2026-03-09 14:00:20 -07:00 committed by GitHub
parent 3d9476ed0b
commit bf282199b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
251 changed files with 42824 additions and 692 deletions

View file

@ -0,0 +1,84 @@
import { describe, expect, it } from "vitest";
import type { HandoffRecord } from "@openhandoff/shared";
import { formatDiffStat, groupHandoffsByRepo } from "./model";
const base: HandoffRecord = {
workspaceId: "default",
repoId: "repo-a",
repoRemote: "https://example.com/repo-a.git",
handoffId: "handoff-1",
branchName: "feature/one",
title: "Feature one",
task: "Ship one",
providerId: "daytona",
status: "running",
statusMessage: null,
activeSandboxId: "sandbox-1",
activeSessionId: "session-1",
sandboxes: [
{
sandboxId: "sandbox-1",
providerId: "daytona",
sandboxActorId: null,
switchTarget: "daytona://sandbox-1",
cwd: null,
createdAt: 10,
updatedAt: 10,
}
],
agentType: null,
prSubmitted: false,
diffStat: null,
prUrl: null,
prAuthor: null,
ciStatus: null,
reviewStatus: null,
reviewer: null,
conflictsWithMain: null,
hasUnpushed: null,
parentBranch: null,
createdAt: 10,
updatedAt: 10,
};
describe("groupHandoffsByRepo", () => {
it("groups by repo and sorts by recency", () => {
const rows: HandoffRecord[] = [
{ ...base, handoffId: "h1", repoId: "repo-a", repoRemote: "https://example.com/repo-a.git", updatedAt: 10 },
{ ...base, handoffId: "h2", repoId: "repo-a", repoRemote: "https://example.com/repo-a.git", updatedAt: 50 },
{ ...base, handoffId: "h3", repoId: "repo-b", repoRemote: "https://example.com/repo-b.git", updatedAt: 30 },
];
const groups = groupHandoffsByRepo(rows);
expect(groups).toHaveLength(2);
expect(groups[0]?.repoId).toBe("repo-a");
expect(groups[0]?.handoffs[0]?.handoffId).toBe("h2");
});
it("sorts repo groups by latest handoff activity first", () => {
const rows: HandoffRecord[] = [
{ ...base, handoffId: "h1", repoId: "repo-z", repoRemote: "https://example.com/repo-z.git", updatedAt: 200 },
{ ...base, handoffId: "h2", repoId: "repo-a", repoRemote: "https://example.com/repo-a.git", updatedAt: 100 },
];
const groups = groupHandoffsByRepo(rows);
expect(groups[0]?.repoId).toBe("repo-z");
expect(groups[1]?.repoId).toBe("repo-a");
});
});
describe("formatDiffStat", () => {
it("returns No changes for zero-diff values", () => {
expect(formatDiffStat("+0/-0")).toBe("No changes");
expect(formatDiffStat("+0 -0")).toBe("No changes");
});
it("returns dash for empty values", () => {
expect(formatDiffStat(null)).toBe("-");
expect(formatDiffStat("")).toBe("-");
});
it("keeps non-empty non-zero diff stats", () => {
expect(formatDiffStat("+12/-4")).toBe("+12/-4");
});
});

View file

@ -0,0 +1,50 @@
import type { HandoffRecord } from "@openhandoff/shared";
export interface RepoGroup {
repoId: string;
repoRemote: string;
handoffs: HandoffRecord[];
}
export function groupHandoffsByRepo(handoffs: HandoffRecord[]): RepoGroup[] {
const groups = new Map<string, RepoGroup>();
for (const handoff of handoffs) {
const group = groups.get(handoff.repoId);
if (group) {
group.handoffs.push(handoff);
continue;
}
groups.set(handoff.repoId, {
repoId: handoff.repoId,
repoRemote: handoff.repoRemote,
handoffs: [handoff],
});
}
return Array.from(groups.values())
.map((group) => ({
...group,
handoffs: [...group.handoffs].sort((a, b) => b.updatedAt - a.updatedAt),
}))
.sort((a, b) => {
const aLatest = a.handoffs[0]?.updatedAt ?? 0;
const bLatest = b.handoffs[0]?.updatedAt ?? 0;
if (aLatest !== bLatest) {
return bLatest - aLatest;
}
return a.repoRemote.localeCompare(b.repoRemote);
});
}
export function formatDiffStat(diffStat: string | null | undefined): string {
const normalized = diffStat?.trim();
if (!normalized) {
return "-";
}
if (normalized === "+0/-0" || normalized === "+0 -0" || normalized === "0 files changed") {
return "No changes";
}
return normalized;
}

View file

@ -0,0 +1,143 @@
import { describe, expect, it } from "vitest";
import type { SandboxSessionRecord } from "@openhandoff/client";
import { buildTranscript, extractEventText, resolveSessionSelection } from "./model";
describe("extractEventText", () => {
it("extracts prompt text arrays", () => {
expect(
extractEventText({ params: { prompt: [{ type: "text", text: "hello" }] } })
).toBe("hello");
});
it("falls back to method name", () => {
expect(extractEventText({ method: "session/started" })).toBe("session/started");
});
it("extracts agent result text when present", () => {
expect(
extractEventText({
result: {
text: "agent output"
}
})
).toBe("agent output");
});
it("extracts text from chunked session updates", () => {
expect(
extractEventText({
params: {
update: {
sessionUpdate: "agent_message_chunk",
content: {
type: "text",
text: "chunk"
}
}
}
})
).toBe("chunk");
});
});
describe("buildTranscript", () => {
it("maps sender/text/timestamp for UI transcript rendering", () => {
const rows = buildTranscript([
{
id: "evt-1",
eventIndex: 1,
sessionId: "sess-1",
createdAt: 1000,
connectionId: "conn-1",
sender: "client",
payload: { params: { prompt: [{ type: "text", text: "hello" }] } }
},
{
id: "evt-2",
eventIndex: 2,
sessionId: "sess-1",
createdAt: 2000,
connectionId: "conn-1",
sender: "agent",
payload: { params: { text: "world" } }
}
]);
expect(rows).toEqual([
{
id: "evt-1",
sender: "client",
text: "hello",
createdAt: 1000
},
{
id: "evt-2",
sender: "agent",
text: "world",
createdAt: 2000
}
]);
});
});
describe("resolveSessionSelection", () => {
const session = (id: string, status: "running" | "idle" | "error" = "running"): SandboxSessionRecord => ({
id,
agentSessionId: `agent-${id}`,
lastConnectionId: `conn-${id}`,
createdAt: 1,
status
} as SandboxSessionRecord);
it("prefers explicit selection when present in session list", () => {
const resolved = resolveSessionSelection({
explicitSessionId: "session-2",
handoffSessionId: "session-1",
sessions: [session("session-1"), session("session-2")]
});
expect(resolved).toEqual({
sessionId: "session-2",
staleSessionId: null
});
});
it("falls back to handoff session when explicit selection is missing", () => {
const resolved = resolveSessionSelection({
explicitSessionId: null,
handoffSessionId: "session-1",
sessions: [session("session-1")]
});
expect(resolved).toEqual({
sessionId: "session-1",
staleSessionId: null
});
});
it("falls back to the newest available session when configured session IDs are stale", () => {
const resolved = resolveSessionSelection({
explicitSessionId: null,
handoffSessionId: "session-stale",
sessions: [session("session-fresh")]
});
expect(resolved).toEqual({
sessionId: "session-fresh",
staleSessionId: null
});
});
it("marks stale session when no sessions are available", () => {
const resolved = resolveSessionSelection({
explicitSessionId: null,
handoffSessionId: "session-stale",
sessions: []
});
expect(resolved).toEqual({
sessionId: null,
staleSessionId: "session-stale"
});
});
});

View file

@ -0,0 +1,141 @@
import type { SandboxSessionEventRecord } from "@openhandoff/client";
import type { SandboxSessionRecord } from "@openhandoff/client";
function fromPromptArray(value: unknown): string | null {
if (!Array.isArray(value)) {
return null;
}
const parts: string[] = [];
for (const item of value) {
if (!item || typeof item !== "object") {
continue;
}
const text = (item as { text?: unknown }).text;
if (typeof text === "string" && text.trim().length > 0) {
parts.push(text.trim());
}
}
return parts.length > 0 ? parts.join("\n") : null;
}
function fromSessionUpdate(value: unknown): string | null {
if (!value || typeof value !== "object") {
return null;
}
const update = value as {
content?: unknown;
sessionUpdate?: unknown;
};
if (update.sessionUpdate !== "agent_message_chunk") {
return null;
}
const content = update.content;
if (!content || typeof content !== "object") {
return null;
}
const text = (content as { text?: unknown }).text;
return typeof text === "string" ? text : null;
}
export function extractEventText(payload: unknown): string {
if (!payload || typeof payload !== "object") {
return String(payload ?? "");
}
const envelope = payload as {
method?: unknown;
params?: unknown;
result?: unknown;
error?: unknown;
};
const params = envelope.params;
if (params && typeof params === "object") {
const updateText = fromSessionUpdate((params as { update?: unknown }).update);
if (typeof updateText === "string") {
return updateText;
}
const text = (params as { text?: unknown }).text;
if (typeof text === "string" && text.trim().length > 0) {
return text.trim();
}
const prompt = fromPromptArray((params as { prompt?: unknown }).prompt);
if (prompt) {
return prompt;
}
}
const result = envelope.result;
if (result && typeof result === "object") {
const text = (result as { text?: unknown }).text;
if (typeof text === "string" && text.trim().length > 0) {
return text.trim();
}
}
if (envelope.error) {
return JSON.stringify(envelope.error, null, 2);
}
if (typeof envelope.method === "string") {
return envelope.method;
}
return JSON.stringify(payload, null, 2);
}
export function buildTranscript(events: SandboxSessionEventRecord[]): Array<{
id: string;
sender: "client" | "agent";
text: string;
createdAt: number;
}> {
return events.map((event) => ({
id: event.id,
sender: event.sender,
text: extractEventText(event.payload),
createdAt: event.createdAt,
}));
}
export function resolveSessionSelection(input: {
explicitSessionId: string | null;
handoffSessionId: string | null;
sessions: SandboxSessionRecord[];
}): {
sessionId: string | null;
staleSessionId: string | null;
} {
const sessionIds = new Set(input.sessions.map((session) => session.id));
const hasSession = (id: string | null): id is string => Boolean(id && sessionIds.has(id));
if (hasSession(input.explicitSessionId)) {
return { sessionId: input.explicitSessionId, staleSessionId: null };
}
if (hasSession(input.handoffSessionId)) {
return { sessionId: input.handoffSessionId, staleSessionId: null };
}
const fallbackSessionId = input.sessions[0]?.id ?? null;
if (fallbackSessionId) {
return { sessionId: fallbackSessionId, staleSessionId: null };
}
if (input.explicitSessionId) {
return { sessionId: null, staleSessionId: input.explicitSessionId };
}
if (input.handoffSessionId) {
return { sessionId: null, staleSessionId: input.handoffSessionId };
}
return { sessionId: null, staleSessionId: null };
}