mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 19:04:40 +00:00
Integrate OpenHandoff factory workspace (#212)
This commit is contained in:
parent
3d9476ed0b
commit
bf282199b5
251 changed files with 42824 additions and 692 deletions
54
factory/packages/shared/src/config.ts
Normal file
54
factory/packages/shared/src/config.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const AgentEnumSchema = z.enum(["claude", "codex"]);
|
||||
|
||||
export const NotifyBackendSchema = z.enum([
|
||||
"openclaw",
|
||||
"macos-osascript",
|
||||
"linux-notify-send",
|
||||
"terminal"
|
||||
]);
|
||||
|
||||
export const ConfigSchema = z.object({
|
||||
theme: z.string().min(1).optional(),
|
||||
auto_submit: z.boolean().default(false),
|
||||
default_agent: AgentEnumSchema.default("codex"),
|
||||
model: z.object({
|
||||
provider: z.string(),
|
||||
model: z.string()
|
||||
}).optional(),
|
||||
notify: z.array(NotifyBackendSchema).default(["terminal"]),
|
||||
workspace: z.object({
|
||||
default: z.string().min(1).default("default")
|
||||
}).default({ default: "default" }),
|
||||
backend: z.object({
|
||||
host: z.string().default("127.0.0.1"),
|
||||
port: z.number().int().min(1).max(65535).default(7741),
|
||||
dbPath: z.string().default("~/.local/share/openhandoff/handoff.db"),
|
||||
opencode_poll_interval: z.number().default(2),
|
||||
github_poll_interval: z.number().default(30),
|
||||
backup_interval_secs: z.number().default(3600),
|
||||
backup_retention_days: z.number().default(7)
|
||||
}).default({
|
||||
host: "127.0.0.1",
|
||||
port: 7741,
|
||||
dbPath: "~/.local/share/openhandoff/handoff.db",
|
||||
opencode_poll_interval: 2,
|
||||
github_poll_interval: 30,
|
||||
backup_interval_secs: 3600,
|
||||
backup_retention_days: 7
|
||||
}),
|
||||
providers: z.object({
|
||||
local: z.object({
|
||||
rootDir: z.string().optional(),
|
||||
sandboxAgentPort: z.number().int().min(1).max(65535).optional(),
|
||||
}).default({}),
|
||||
daytona: z.object({
|
||||
endpoint: z.string().optional(),
|
||||
apiKey: z.string().optional(),
|
||||
image: z.string().default("ubuntu:24.04")
|
||||
}).default({ image: "ubuntu:24.04" })
|
||||
}).default({ local: {}, daytona: { image: "ubuntu:24.04" } })
|
||||
});
|
||||
|
||||
export type AppConfig = z.infer<typeof ConfigSchema>;
|
||||
252
factory/packages/shared/src/contracts.ts
Normal file
252
factory/packages/shared/src/contracts.ts
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const WorkspaceIdSchema = z.string().min(1).max(64).regex(/^[a-zA-Z0-9._-]+$/);
|
||||
export type WorkspaceId = z.infer<typeof WorkspaceIdSchema>;
|
||||
|
||||
export const ProviderIdSchema = z.enum(["daytona", "local"]);
|
||||
export type ProviderId = z.infer<typeof ProviderIdSchema>;
|
||||
|
||||
export const AgentTypeSchema = z.enum(["claude", "codex"]);
|
||||
export type AgentType = z.infer<typeof AgentTypeSchema>;
|
||||
|
||||
export const RepoIdSchema = z.string().min(1).max(128);
|
||||
export type RepoId = z.infer<typeof RepoIdSchema>;
|
||||
|
||||
export const RepoRemoteSchema = z.string().min(1).max(2048);
|
||||
export type RepoRemote = z.infer<typeof RepoRemoteSchema>;
|
||||
|
||||
export const HandoffStatusSchema = z.enum([
|
||||
"init_bootstrap_db",
|
||||
"init_enqueue_provision",
|
||||
"init_ensure_name",
|
||||
"init_assert_name",
|
||||
"init_create_sandbox",
|
||||
"init_ensure_agent",
|
||||
"init_start_sandbox_instance",
|
||||
"init_create_session",
|
||||
"init_write_db",
|
||||
"init_start_status_sync",
|
||||
"init_complete",
|
||||
"running",
|
||||
"idle",
|
||||
"archive_stop_status_sync",
|
||||
"archive_release_sandbox",
|
||||
"archive_finalize",
|
||||
"archived",
|
||||
"kill_destroy_sandbox",
|
||||
"kill_finalize",
|
||||
"killed",
|
||||
"error"
|
||||
]);
|
||||
export type HandoffStatus = z.infer<typeof HandoffStatusSchema>;
|
||||
|
||||
export const RepoRecordSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: RepoIdSchema,
|
||||
remoteUrl: RepoRemoteSchema,
|
||||
createdAt: z.number().int(),
|
||||
updatedAt: z.number().int(),
|
||||
});
|
||||
export type RepoRecord = z.infer<typeof RepoRecordSchema>;
|
||||
|
||||
export const AddRepoInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
remoteUrl: RepoRemoteSchema,
|
||||
});
|
||||
export type AddRepoInput = z.infer<typeof AddRepoInputSchema>;
|
||||
|
||||
export const CreateHandoffInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: RepoIdSchema,
|
||||
task: z.string().min(1),
|
||||
explicitTitle: z.string().trim().min(1).optional(),
|
||||
explicitBranchName: z.string().trim().min(1).optional(),
|
||||
providerId: ProviderIdSchema.optional(),
|
||||
agentType: AgentTypeSchema.optional(),
|
||||
onBranch: z.string().trim().min(1).optional()
|
||||
});
|
||||
export type CreateHandoffInput = z.infer<typeof CreateHandoffInputSchema>;
|
||||
|
||||
export const HandoffRecordSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: z.string().min(1),
|
||||
repoRemote: RepoRemoteSchema,
|
||||
handoffId: z.string().min(1),
|
||||
branchName: z.string().min(1).nullable(),
|
||||
title: z.string().min(1).nullable(),
|
||||
task: z.string().min(1),
|
||||
providerId: ProviderIdSchema,
|
||||
status: HandoffStatusSchema,
|
||||
statusMessage: z.string().nullable(),
|
||||
activeSandboxId: z.string().nullable(),
|
||||
activeSessionId: z.string().nullable(),
|
||||
sandboxes: z.array(
|
||||
z.object({
|
||||
sandboxId: z.string().min(1),
|
||||
providerId: ProviderIdSchema,
|
||||
sandboxActorId: z.string().nullable(),
|
||||
switchTarget: z.string().min(1),
|
||||
cwd: z.string().nullable(),
|
||||
createdAt: z.number().int(),
|
||||
updatedAt: z.number().int(),
|
||||
})
|
||||
),
|
||||
agentType: z.string().nullable(),
|
||||
prSubmitted: z.boolean(),
|
||||
diffStat: z.string().nullable(),
|
||||
prUrl: z.string().nullable(),
|
||||
prAuthor: z.string().nullable(),
|
||||
ciStatus: z.string().nullable(),
|
||||
reviewStatus: z.string().nullable(),
|
||||
reviewer: z.string().nullable(),
|
||||
conflictsWithMain: z.string().nullable(),
|
||||
hasUnpushed: z.string().nullable(),
|
||||
parentBranch: z.string().nullable(),
|
||||
createdAt: z.number().int(),
|
||||
updatedAt: z.number().int()
|
||||
});
|
||||
export type HandoffRecord = z.infer<typeof HandoffRecordSchema>;
|
||||
|
||||
export const HandoffSummarySchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: z.string().min(1),
|
||||
handoffId: z.string().min(1),
|
||||
branchName: z.string().min(1).nullable(),
|
||||
title: z.string().min(1).nullable(),
|
||||
status: HandoffStatusSchema,
|
||||
updatedAt: z.number().int()
|
||||
});
|
||||
export type HandoffSummary = z.infer<typeof HandoffSummarySchema>;
|
||||
|
||||
export const HandoffActionInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
handoffId: z.string().min(1)
|
||||
});
|
||||
export type HandoffActionInput = z.infer<typeof HandoffActionInputSchema>;
|
||||
|
||||
export const SwitchResultSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
handoffId: z.string().min(1),
|
||||
providerId: ProviderIdSchema,
|
||||
switchTarget: z.string().min(1)
|
||||
});
|
||||
export type SwitchResult = z.infer<typeof SwitchResultSchema>;
|
||||
|
||||
export const ListHandoffsInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: RepoIdSchema.optional()
|
||||
});
|
||||
export type ListHandoffsInput = z.infer<typeof ListHandoffsInputSchema>;
|
||||
|
||||
export const RepoBranchRecordSchema = z.object({
|
||||
branchName: z.string().min(1),
|
||||
commitSha: z.string().min(1),
|
||||
parentBranch: z.string().nullable(),
|
||||
trackedInStack: z.boolean(),
|
||||
diffStat: z.string().nullable(),
|
||||
hasUnpushed: z.boolean(),
|
||||
conflictsWithMain: z.boolean(),
|
||||
handoffId: z.string().nullable(),
|
||||
handoffTitle: z.string().nullable(),
|
||||
handoffStatus: HandoffStatusSchema.nullable(),
|
||||
prNumber: z.number().int().nullable(),
|
||||
prState: z.string().nullable(),
|
||||
prUrl: z.string().nullable(),
|
||||
ciStatus: z.string().nullable(),
|
||||
reviewStatus: z.string().nullable(),
|
||||
reviewer: z.string().nullable(),
|
||||
firstSeenAt: z.number().int().nullable(),
|
||||
lastSeenAt: z.number().int().nullable(),
|
||||
updatedAt: z.number().int()
|
||||
});
|
||||
export type RepoBranchRecord = z.infer<typeof RepoBranchRecordSchema>;
|
||||
|
||||
export const RepoOverviewSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: RepoIdSchema,
|
||||
remoteUrl: RepoRemoteSchema,
|
||||
baseRef: z.string().nullable(),
|
||||
stackAvailable: z.boolean(),
|
||||
fetchedAt: z.number().int(),
|
||||
branches: z.array(RepoBranchRecordSchema)
|
||||
});
|
||||
export type RepoOverview = z.infer<typeof RepoOverviewSchema>;
|
||||
|
||||
export const RepoStackActionSchema = z.enum([
|
||||
"sync_repo",
|
||||
"restack_repo",
|
||||
"restack_subtree",
|
||||
"rebase_branch",
|
||||
"reparent_branch"
|
||||
]);
|
||||
export type RepoStackAction = z.infer<typeof RepoStackActionSchema>;
|
||||
|
||||
export const RepoStackActionInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: RepoIdSchema,
|
||||
action: RepoStackActionSchema,
|
||||
branchName: z.string().trim().min(1).optional(),
|
||||
parentBranch: z.string().trim().min(1).optional()
|
||||
});
|
||||
export type RepoStackActionInput = z.infer<typeof RepoStackActionInputSchema>;
|
||||
|
||||
export const RepoStackActionResultSchema = z.object({
|
||||
action: RepoStackActionSchema,
|
||||
executed: z.boolean(),
|
||||
message: z.string().min(1),
|
||||
at: z.number().int()
|
||||
});
|
||||
export type RepoStackActionResult = z.infer<typeof RepoStackActionResultSchema>;
|
||||
|
||||
export const WorkspaceUseInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema
|
||||
});
|
||||
export type WorkspaceUseInput = z.infer<typeof WorkspaceUseInputSchema>;
|
||||
|
||||
export const HistoryQueryInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
limit: z.number().int().positive().max(500).optional(),
|
||||
branch: z.string().min(1).optional(),
|
||||
handoffId: z.string().min(1).optional()
|
||||
});
|
||||
export type HistoryQueryInput = z.infer<typeof HistoryQueryInputSchema>;
|
||||
|
||||
export const HistoryEventSchema = z.object({
|
||||
id: z.number().int(),
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
repoId: z.string().nullable(),
|
||||
handoffId: z.string().nullable(),
|
||||
branchName: z.string().nullable(),
|
||||
kind: z.string().min(1),
|
||||
payloadJson: z.string().min(1),
|
||||
createdAt: z.number().int()
|
||||
});
|
||||
export type HistoryEvent = z.infer<typeof HistoryEventSchema>;
|
||||
|
||||
export const PruneInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
dryRun: z.boolean(),
|
||||
yes: z.boolean()
|
||||
});
|
||||
export type PruneInput = z.infer<typeof PruneInputSchema>;
|
||||
|
||||
export const KillInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
handoffId: z.string().min(1),
|
||||
deleteBranch: z.boolean(),
|
||||
abandon: z.boolean()
|
||||
});
|
||||
export type KillInput = z.infer<typeof KillInputSchema>;
|
||||
|
||||
export const StatuslineInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
format: z.enum(["table", "claude-code"])
|
||||
});
|
||||
export type StatuslineInput = z.infer<typeof StatuslineInputSchema>;
|
||||
|
||||
export const ListInputSchema = z.object({
|
||||
workspaceId: WorkspaceIdSchema,
|
||||
format: z.enum(["table", "json"]),
|
||||
full: z.boolean()
|
||||
});
|
||||
export type ListInput = z.infer<typeof ListInputSchema>;
|
||||
4
factory/packages/shared/src/index.ts
Normal file
4
factory/packages/shared/src/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export * from "./contracts.js";
|
||||
export * from "./config.js";
|
||||
export * from "./workbench.js";
|
||||
export * from "./workspace.js";
|
||||
181
factory/packages/shared/src/workbench.ts
Normal file
181
factory/packages/shared/src/workbench.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
export type WorkbenchHandoffStatus = "running" | "idle" | "new" | "archived";
|
||||
export type WorkbenchAgentKind = "Claude" | "Codex" | "Cursor";
|
||||
export type WorkbenchModelId = "claude-sonnet-4" | "claude-opus-4" | "gpt-4o" | "o3";
|
||||
|
||||
export interface WorkbenchTranscriptEvent {
|
||||
id: string;
|
||||
eventIndex: number;
|
||||
sessionId: string;
|
||||
createdAt: number;
|
||||
connectionId: string;
|
||||
sender: "client" | "agent";
|
||||
payload: unknown;
|
||||
}
|
||||
|
||||
export interface WorkbenchComposerDraft {
|
||||
text: string;
|
||||
attachments: WorkbenchLineAttachment[];
|
||||
updatedAtMs: number | null;
|
||||
}
|
||||
|
||||
export interface WorkbenchAgentTab {
|
||||
id: string;
|
||||
sessionId: string | null;
|
||||
sessionName: string;
|
||||
agent: WorkbenchAgentKind;
|
||||
model: WorkbenchModelId;
|
||||
status: "running" | "idle" | "error";
|
||||
thinkingSinceMs: number | null;
|
||||
unread: boolean;
|
||||
created: boolean;
|
||||
draft: WorkbenchComposerDraft;
|
||||
transcript: WorkbenchTranscriptEvent[];
|
||||
}
|
||||
|
||||
export interface WorkbenchFileChange {
|
||||
path: string;
|
||||
added: number;
|
||||
removed: number;
|
||||
type: "M" | "A" | "D";
|
||||
}
|
||||
|
||||
export interface WorkbenchFileTreeNode {
|
||||
name: string;
|
||||
path: string;
|
||||
isDir: boolean;
|
||||
children?: WorkbenchFileTreeNode[];
|
||||
}
|
||||
|
||||
export interface WorkbenchLineAttachment {
|
||||
id: string;
|
||||
filePath: string;
|
||||
lineNumber: number;
|
||||
lineContent: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchHistoryEvent {
|
||||
id: string;
|
||||
messageId: string;
|
||||
preview: string;
|
||||
sessionName: string;
|
||||
tabId: string;
|
||||
createdAtMs: number;
|
||||
detail: string;
|
||||
}
|
||||
|
||||
export type WorkbenchDiffLineKind = "context" | "add" | "remove" | "hunk";
|
||||
|
||||
export interface WorkbenchParsedDiffLine {
|
||||
kind: WorkbenchDiffLineKind;
|
||||
lineNumber: number;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchPullRequestSummary {
|
||||
number: number;
|
||||
status: "draft" | "ready";
|
||||
}
|
||||
|
||||
export interface WorkbenchHandoff {
|
||||
id: string;
|
||||
repoId: string;
|
||||
title: string;
|
||||
status: WorkbenchHandoffStatus;
|
||||
repoName: string;
|
||||
updatedAtMs: number;
|
||||
branch: string | null;
|
||||
pullRequest: WorkbenchPullRequestSummary | null;
|
||||
tabs: WorkbenchAgentTab[];
|
||||
fileChanges: WorkbenchFileChange[];
|
||||
diffs: Record<string, string>;
|
||||
fileTree: WorkbenchFileTreeNode[];
|
||||
}
|
||||
|
||||
export interface WorkbenchRepo {
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchProjectSection {
|
||||
id: string;
|
||||
label: string;
|
||||
updatedAtMs: number;
|
||||
handoffs: WorkbenchHandoff[];
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchSnapshot {
|
||||
workspaceId: string;
|
||||
repos: WorkbenchRepo[];
|
||||
projects: WorkbenchProjectSection[];
|
||||
handoffs: WorkbenchHandoff[];
|
||||
}
|
||||
|
||||
export interface WorkbenchModelOption {
|
||||
id: WorkbenchModelId;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface WorkbenchModelGroup {
|
||||
provider: string;
|
||||
models: WorkbenchModelOption[];
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchSelectInput {
|
||||
handoffId: string;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchCreateHandoffInput {
|
||||
repoId: string;
|
||||
task: string;
|
||||
title?: string;
|
||||
branch?: string;
|
||||
model?: WorkbenchModelId;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchRenameInput {
|
||||
handoffId: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchSendMessageInput {
|
||||
handoffId: string;
|
||||
tabId: string;
|
||||
text: string;
|
||||
attachments: WorkbenchLineAttachment[];
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchTabInput {
|
||||
handoffId: string;
|
||||
tabId: string;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchRenameSessionInput extends HandoffWorkbenchTabInput {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchChangeModelInput extends HandoffWorkbenchTabInput {
|
||||
model: WorkbenchModelId;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchUpdateDraftInput extends HandoffWorkbenchTabInput {
|
||||
text: string;
|
||||
attachments: WorkbenchLineAttachment[];
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchSetSessionUnreadInput extends HandoffWorkbenchTabInput {
|
||||
unread: boolean;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchDiffInput {
|
||||
handoffId: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchCreateHandoffResponse {
|
||||
handoffId: string;
|
||||
tabId?: string;
|
||||
}
|
||||
|
||||
export interface HandoffWorkbenchAddTabResponse {
|
||||
tabId: string;
|
||||
}
|
||||
16
factory/packages/shared/src/workspace.ts
Normal file
16
factory/packages/shared/src/workspace.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import type { AppConfig } from "./config.js";
|
||||
|
||||
export function resolveWorkspaceId(
|
||||
flagWorkspace: string | undefined,
|
||||
config: AppConfig
|
||||
): string {
|
||||
if (flagWorkspace && flagWorkspace.trim().length > 0) {
|
||||
return flagWorkspace.trim();
|
||||
}
|
||||
|
||||
if (config.workspace.default.trim().length > 0) {
|
||||
return config.workspace.default.trim();
|
||||
}
|
||||
|
||||
return "default";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue