mirror of
https://github.com/harivansh-afk/sandbox-agent.git
synced 2026-04-16 21:03:46 +00:00
Stabilize SDK mode integration test
This commit is contained in:
parent
24e99ac5e7
commit
ec8b6afea9
274 changed files with 5412 additions and 7893 deletions
|
|
@ -1,13 +1,5 @@
|
|||
import * as childProcess from "node:child_process";
|
||||
import {
|
||||
closeSync,
|
||||
existsSync,
|
||||
mkdirSync,
|
||||
openSync,
|
||||
readFileSync,
|
||||
rmSync,
|
||||
writeFileSync
|
||||
} from "node:fs";
|
||||
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { dirname, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
|
@ -141,7 +133,7 @@ function removeStateFiles(host: string, port: number): void {
|
|||
async function checkHealth(host: string, port: number): Promise<boolean> {
|
||||
return await checkBackendHealth({
|
||||
endpoint: `http://${host}:${port}/api/rivet`,
|
||||
timeoutMs: HEALTH_TIMEOUT_MS
|
||||
timeoutMs: HEALTH_TIMEOUT_MS,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -206,25 +198,14 @@ function resolveLaunchSpec(host: string, port: number): LaunchSpec {
|
|||
return {
|
||||
command: resolveBunCommand(),
|
||||
args: [backendEntry, "start", "--host", host, "--port", String(port)],
|
||||
cwd: repoRoot
|
||||
cwd: repoRoot,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
command: "pnpm",
|
||||
args: [
|
||||
"--filter",
|
||||
"@openhandoff/backend",
|
||||
"exec",
|
||||
"bun",
|
||||
"src/index.ts",
|
||||
"start",
|
||||
"--host",
|
||||
host,
|
||||
"--port",
|
||||
String(port)
|
||||
],
|
||||
cwd: repoRoot
|
||||
args: ["--filter", "@openhandoff/backend", "exec", "bun", "src/index.ts", "start", "--host", host, "--port", String(port)],
|
||||
cwd: repoRoot,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -252,7 +233,7 @@ async function startBackend(host: string, port: number): Promise<void> {
|
|||
cwd: launch.cwd,
|
||||
detached: true,
|
||||
stdio: ["ignore", fd, fd],
|
||||
env: process.env
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
child.on("error", (error) => {
|
||||
|
|
@ -298,7 +279,7 @@ function findProcessOnPort(port: number): number | null {
|
|||
const out = childProcess
|
||||
.execFileSync("lsof", ["-i", `:${port}`, "-t", "-sTCP:LISTEN"], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"]
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
})
|
||||
.trim();
|
||||
|
||||
|
|
@ -372,7 +353,7 @@ export async function getBackendStatus(host: string, port: number): Promise<Back
|
|||
pid,
|
||||
version: readBackendVersion(host, port),
|
||||
versionCurrent: isVersionCurrent(host, port),
|
||||
logPath
|
||||
logPath,
|
||||
};
|
||||
}
|
||||
removeStateFiles(host, port);
|
||||
|
|
@ -384,7 +365,7 @@ export async function getBackendStatus(host: string, port: number): Promise<Back
|
|||
pid: null,
|
||||
version: readBackendVersion(host, port),
|
||||
versionCurrent: isVersionCurrent(host, port),
|
||||
logPath
|
||||
logPath,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -393,7 +374,7 @@ export async function getBackendStatus(host: string, port: number): Promise<Back
|
|||
pid: null,
|
||||
version: readBackendVersion(host, port),
|
||||
versionCurrent: false,
|
||||
logPath
|
||||
logPath,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,3 @@
|
|||
declare const __HF_BUILD_ID__: string | undefined;
|
||||
|
||||
export const CLI_BUILD_ID =
|
||||
typeof __HF_BUILD_ID__ === "string" && __HF_BUILD_ID__.trim().length > 0
|
||||
? __HF_BUILD_ID__.trim()
|
||||
: "dev";
|
||||
|
||||
export const CLI_BUILD_ID = typeof __HF_BUILD_ID__ === "string" && __HF_BUILD_ID__.trim().length > 0 ? __HF_BUILD_ID__.trim() : "dev";
|
||||
|
|
|
|||
|
|
@ -3,19 +3,8 @@ import { spawnSync } from "node:child_process";
|
|||
import { existsSync } from "node:fs";
|
||||
import { homedir } from "node:os";
|
||||
import { AgentTypeSchema, CreateHandoffInputSchema, type HandoffRecord } from "@openhandoff/shared";
|
||||
import {
|
||||
readBackendMetadata,
|
||||
createBackendClientFromConfig,
|
||||
formatRelativeAge,
|
||||
groupHandoffStatus,
|
||||
summarizeHandoffs
|
||||
} from "@openhandoff/client";
|
||||
import {
|
||||
ensureBackendRunning,
|
||||
getBackendStatus,
|
||||
parseBackendPort,
|
||||
stopBackend
|
||||
} from "./backend/manager.js";
|
||||
import { readBackendMetadata, createBackendClientFromConfig, formatRelativeAge, groupHandoffStatus, summarizeHandoffs } from "@openhandoff/client";
|
||||
import { ensureBackendRunning, getBackendStatus, parseBackendPort, stopBackend } from "./backend/manager.js";
|
||||
import { openEditorForTask } from "./task-editor.js";
|
||||
import { spawnCreateTmuxWindow } from "./tmux.js";
|
||||
import { loadConfig, resolveWorkspace, saveConfig } from "./workspace/config.js";
|
||||
|
|
@ -26,11 +15,7 @@ async function ensureBunRuntime(): Promise<void> {
|
|||
}
|
||||
|
||||
const preferred = process.env.HF_BUN?.trim();
|
||||
const candidates = [
|
||||
preferred,
|
||||
`${homedir()}/.bun/bin/bun`,
|
||||
"bun"
|
||||
].filter((item): item is string => Boolean(item && item.length > 0));
|
||||
const candidates = [preferred, `${homedir()}/.bun/bin/bun`, "bun"].filter((item): item is string => Boolean(item && item.length > 0));
|
||||
|
||||
for (const candidate of candidates) {
|
||||
const command = candidate;
|
||||
|
|
@ -41,7 +26,7 @@ async function ensureBunRuntime(): Promise<void> {
|
|||
|
||||
const child = spawnSync(command, [process.argv[1] ?? "", ...process.argv.slice(2)], {
|
||||
stdio: "inherit",
|
||||
env: process.env
|
||||
env: process.env,
|
||||
});
|
||||
|
||||
if (child.error) {
|
||||
|
|
@ -70,11 +55,7 @@ function hasFlag(args: string[], flag: string): boolean {
|
|||
return args.includes(flag);
|
||||
}
|
||||
|
||||
function parseIntOption(
|
||||
value: string | undefined,
|
||||
fallback: number,
|
||||
label: string
|
||||
): number {
|
||||
function parseIntOption(value: string | undefined, fallback: number, label: string): number {
|
||||
if (!value) {
|
||||
return fallback;
|
||||
}
|
||||
|
|
@ -204,8 +185,8 @@ async function handleBackend(args: string[]): Promise<void> {
|
|||
backend: {
|
||||
...config.backend,
|
||||
host,
|
||||
port
|
||||
}
|
||||
port,
|
||||
},
|
||||
};
|
||||
|
||||
if (sub === "start") {
|
||||
|
|
@ -229,9 +210,7 @@ async function handleBackend(args: string[]): Promise<void> {
|
|||
const pid = status.pid ?? "unknown";
|
||||
const version = status.version ?? "unknown";
|
||||
const stale = status.running && !status.versionCurrent ? " [outdated]" : "";
|
||||
console.log(
|
||||
`running=${status.running} pid=${pid} version=${version}${stale} host=${host} port=${port} log=${status.logPath}`
|
||||
);
|
||||
console.log(`running=${status.running} pid=${pid} version=${version}${stale} host=${host} port=${port} log=${status.logPath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +218,7 @@ async function handleBackend(args: string[]): Promise<void> {
|
|||
await ensureBackendRunning(backendConfig);
|
||||
const metadata = await readBackendMetadata({
|
||||
endpoint: `http://${host}:${port}/api/rivet`,
|
||||
timeoutMs: 4_000
|
||||
timeoutMs: 4_000,
|
||||
});
|
||||
const managerEndpoint = metadata.clientEndpoint ?? `http://${host}:${port}`;
|
||||
const inspectorUrl = `https://inspect.rivet.dev?u=${encodeURIComponent(managerEndpoint)}`;
|
||||
|
|
@ -424,7 +403,7 @@ async function waitForHandoffReady(
|
|||
client: ReturnType<typeof createBackendClientFromConfig>,
|
||||
workspaceId: string,
|
||||
handoffId: string,
|
||||
timeoutMs: number
|
||||
timeoutMs: number,
|
||||
): Promise<HandoffRecord> {
|
||||
const start = Date.now();
|
||||
let delayMs = 250;
|
||||
|
|
@ -478,7 +457,7 @@ async function handleCreate(args: string[]): Promise<void> {
|
|||
explicitTitle: explicitTitle || undefined,
|
||||
explicitBranchName: explicitBranchName || undefined,
|
||||
agentType,
|
||||
onBranch
|
||||
onBranch,
|
||||
});
|
||||
|
||||
const created = await client.createHandoff(payload);
|
||||
|
|
@ -496,7 +475,7 @@ async function handleCreate(args: string[]): Promise<void> {
|
|||
const tmuxResult = spawnCreateTmuxWindow({
|
||||
branchName: handoff.branchName ?? handoff.handoffId,
|
||||
targetPath: switched.switchTarget || attached.target,
|
||||
sessionId: attached.sessionId
|
||||
sessionId: attached.sessionId,
|
||||
});
|
||||
|
||||
if (tmuxResult.created) {
|
||||
|
|
@ -507,7 +486,7 @@ async function handleCreate(args: string[]): Promise<void> {
|
|||
console.log("");
|
||||
console.log(`Run: hf switch ${handoff.handoffId}`);
|
||||
if ((switched.switchTarget || attached.target).startsWith("/")) {
|
||||
console.log(`cd ${(switched.switchTarget || attached.target)}`);
|
||||
console.log(`cd ${switched.switchTarget || attached.target}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -539,23 +518,21 @@ async function handleStatus(args: string[]): Promise<void> {
|
|||
handoffs: {
|
||||
total: summary.total,
|
||||
byStatus: summary.byStatus,
|
||||
byProvider: summary.byProvider
|
||||
}
|
||||
byProvider: summary.byProvider,
|
||||
},
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
2,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`workspace=${workspaceId}`);
|
||||
console.log(
|
||||
`backend running=${backendStatus.running} pid=${backendStatus.pid ?? "unknown"} version=${backendStatus.version ?? "unknown"}`
|
||||
);
|
||||
console.log(`backend running=${backendStatus.running} pid=${backendStatus.pid ?? "unknown"} version=${backendStatus.version ?? "unknown"}`);
|
||||
console.log(`handoffs total=${summary.total}`);
|
||||
console.log(
|
||||
`status queued=${summary.byStatus.queued} running=${summary.byStatus.running} idle=${summary.byStatus.idle} archived=${summary.byStatus.archived} killed=${summary.byStatus.killed} error=${summary.byStatus.error}`
|
||||
`status queued=${summary.byStatus.queued} running=${summary.byStatus.running} idle=${summary.byStatus.idle} archived=${summary.byStatus.archived} killed=${summary.byStatus.killed} error=${summary.byStatus.error}`,
|
||||
);
|
||||
const providerSummary = Object.entries(summary.byProvider)
|
||||
.map(([provider, count]) => `${provider}=${count}`)
|
||||
|
|
@ -579,7 +556,7 @@ async function handleHistory(args: string[]): Promise<void> {
|
|||
workspaceId,
|
||||
limit,
|
||||
branch: branch || undefined,
|
||||
handoffId: handoffId || undefined
|
||||
handoffId: handoffId || undefined,
|
||||
});
|
||||
|
||||
if (hasFlag(args, "--json")) {
|
||||
|
|
@ -748,7 +725,7 @@ async function main(): Promise<void> {
|
|||
}
|
||||
|
||||
main().catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.stack ?? err.message : String(err);
|
||||
const msg = err instanceof Error ? (err.stack ?? err.message) : String(err);
|
||||
console.error(msg);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,11 +3,7 @@ import { tmpdir } from "node:os";
|
|||
import { join } from "node:path";
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const DEFAULT_EDITOR_TEMPLATE = [
|
||||
"# Enter handoff task details below.",
|
||||
"# Lines starting with # are ignored.",
|
||||
""
|
||||
].join("\n");
|
||||
const DEFAULT_EDITOR_TEMPLATE = ["# Enter handoff task details below.", "# Lines starting with # are ignored.", ""].join("\n");
|
||||
|
||||
export function sanitizeEditorTask(input: string): string {
|
||||
return input
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ const DEFAULT_THEME: TuiTheme = {
|
|||
reviewApproved: "#22c55e",
|
||||
reviewChanges: "#ef4444",
|
||||
reviewPending: "#eab308",
|
||||
reviewNone: "#6b7280"
|
||||
reviewNone: "#6b7280",
|
||||
};
|
||||
|
||||
const OPENCODE_THEME_PACK = opencodeThemePackJson as Record<string, unknown>;
|
||||
|
|
@ -102,7 +102,7 @@ export function resolveTuiTheme(config: AppConfig, baseDir = cwd()): TuiThemeRes
|
|||
theme: candidate.theme,
|
||||
name: candidate.name,
|
||||
source: "openhandoff config",
|
||||
mode
|
||||
mode,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ export function resolveTuiTheme(config: AppConfig, baseDir = cwd()): TuiThemeRes
|
|||
theme: DEFAULT_THEME,
|
||||
name: "opencode-default",
|
||||
source: "default",
|
||||
mode
|
||||
mode,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +150,7 @@ function loadOpencodeThemeFromConfig(mode: ThemeMode, baseDir: string): TuiTheme
|
|||
theme: candidate.theme,
|
||||
name: candidate.name,
|
||||
source: `opencode config (${path})`,
|
||||
mode
|
||||
mode,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -182,20 +182,15 @@ function loadOpencodeThemeFromState(mode: ThemeMode, baseDir: string): TuiThemeR
|
|||
theme: candidate.theme,
|
||||
name: candidate.name,
|
||||
source: `opencode state (${path})`,
|
||||
mode
|
||||
mode,
|
||||
};
|
||||
}
|
||||
|
||||
function loadFromSpec(
|
||||
spec: string,
|
||||
searchDirs: string[],
|
||||
mode: ThemeMode,
|
||||
baseDir: string
|
||||
): ThemeCandidate | null {
|
||||
function loadFromSpec(spec: string, searchDirs: string[], mode: ThemeMode, baseDir: string): ThemeCandidate | null {
|
||||
if (isDefaultThemeName(spec)) {
|
||||
return {
|
||||
theme: DEFAULT_THEME,
|
||||
name: "opencode-default"
|
||||
name: "opencode-default",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -229,7 +224,7 @@ function loadFromSpec(
|
|||
if (theme) {
|
||||
return {
|
||||
theme,
|
||||
name: spec
|
||||
name: spec,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -253,7 +248,7 @@ function loadThemeFromPath(path: string, mode: ThemeMode): ThemeCandidate | null
|
|||
}
|
||||
return {
|
||||
theme,
|
||||
name: themeNameFromPath(path)
|
||||
name: themeNameFromPath(path),
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
|
|
@ -269,7 +264,7 @@ function loadThemeFromPath(path: string, mode: ThemeMode): ThemeCandidate | null
|
|||
if (opencodeTheme) {
|
||||
return {
|
||||
theme: opencodeTheme,
|
||||
name: themeNameFromPath(path)
|
||||
name: themeNameFromPath(path),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -280,7 +275,7 @@ function loadThemeFromPath(path: string, mode: ThemeMode): ThemeCandidate | null
|
|||
|
||||
return {
|
||||
theme: paletteTheme,
|
||||
name: themeNameFromPath(path)
|
||||
name: themeNameFromPath(path),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -292,12 +287,7 @@ function themeNameFromPath(path: string): string {
|
|||
return base;
|
||||
}
|
||||
|
||||
function themeFromOpencodeValue(
|
||||
value: unknown,
|
||||
searchDirs: string[],
|
||||
mode: ThemeMode,
|
||||
baseDir: string
|
||||
): ThemeCandidate | null {
|
||||
function themeFromOpencodeValue(value: unknown, searchDirs: string[], mode: ThemeMode, baseDir: string): ThemeCandidate | null {
|
||||
if (typeof value === "string") {
|
||||
return loadFromSpec(value, searchDirs, mode, baseDir);
|
||||
}
|
||||
|
|
@ -311,7 +301,7 @@ function themeFromOpencodeValue(
|
|||
if (theme) {
|
||||
return {
|
||||
theme,
|
||||
name: typeof value.name === "string" ? value.name : "inline"
|
||||
name: typeof value.name === "string" ? value.name : "inline",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -320,7 +310,7 @@ function themeFromOpencodeValue(
|
|||
if (paletteTheme) {
|
||||
return {
|
||||
theme: paletteTheme,
|
||||
name: typeof value.name === "string" ? value.name : "inline"
|
||||
name: typeof value.name === "string" ? value.name : "inline",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -382,10 +372,7 @@ function themeFromOpencodeJson(value: unknown, mode: ThemeMode): TuiTheme | null
|
|||
const info = opencodeColor(themeMap, defs, mode, "info") ?? DEFAULT_THEME.info;
|
||||
const diffAdd = opencodeColor(themeMap, defs, mode, "diffAdded") ?? success;
|
||||
const diffDel = opencodeColor(themeMap, defs, mode, "diffRemoved") ?? error;
|
||||
const diffSep =
|
||||
opencodeColor(themeMap, defs, mode, "diffContext") ??
|
||||
opencodeColor(themeMap, defs, mode, "diffHunkHeader") ??
|
||||
muted;
|
||||
const diffSep = opencodeColor(themeMap, defs, mode, "diffContext") ?? opencodeColor(themeMap, defs, mode, "diffHunkHeader") ?? muted;
|
||||
|
||||
return {
|
||||
background,
|
||||
|
|
@ -416,7 +403,7 @@ function themeFromOpencodeJson(value: unknown, mode: ThemeMode): TuiTheme | null
|
|||
reviewApproved: success,
|
||||
reviewChanges: error,
|
||||
reviewPending: warning,
|
||||
reviewNone: muted
|
||||
reviewNone: muted,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -428,13 +415,7 @@ function opencodeColor(themeMap: JsonObject, defs: JsonObject, mode: ThemeMode,
|
|||
return resolveOpencodeColor(raw, themeMap, defs, mode, 0);
|
||||
}
|
||||
|
||||
function resolveOpencodeColor(
|
||||
value: unknown,
|
||||
themeMap: JsonObject,
|
||||
defs: JsonObject,
|
||||
mode: ThemeMode,
|
||||
depth: number
|
||||
): string | null {
|
||||
function resolveOpencodeColor(value: unknown, themeMap: JsonObject, defs: JsonObject, mode: ThemeMode, depth: number): string | null {
|
||||
if (depth > 12) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -533,7 +514,7 @@ function themeFromAny(value: unknown): TuiTheme | null {
|
|||
reviewApproved: pick(["review_approved", "approved"], success),
|
||||
reviewChanges: pick(["review_changes", "changes"], error),
|
||||
reviewPending: pick(["review_pending", "pending"], warning),
|
||||
reviewNone: pick(["review_none", "review_unknown"], muted)
|
||||
reviewNone: pick(["review_none", "review_unknown"], muted),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,12 +20,7 @@ export interface SpawnCreateTmuxWindowInput {
|
|||
|
||||
export interface SpawnCreateTmuxWindowResult {
|
||||
created: boolean;
|
||||
reason:
|
||||
| "created"
|
||||
| "not-in-tmux"
|
||||
| "not-local-path"
|
||||
| "window-exists"
|
||||
| "tmux-new-window-failed";
|
||||
reason: "created" | "not-in-tmux" | "not-local-path" | "window-exists" | "tmux-new-window-failed";
|
||||
}
|
||||
|
||||
function isTmuxSession(): boolean {
|
||||
|
|
@ -63,10 +58,7 @@ function resolveOpencodeBinary(): string {
|
|||
return "opencode";
|
||||
}
|
||||
|
||||
const bundledCandidates = [
|
||||
`${homedir()}/.local/share/sandbox-agent/bin/opencode`,
|
||||
`${homedir()}/.opencode/bin/opencode`
|
||||
];
|
||||
const bundledCandidates = [`${homedir()}/.local/share/sandbox-agent/bin/opencode`, `${homedir()}/.opencode/bin/opencode`];
|
||||
|
||||
for (const candidate of bundledCandidates) {
|
||||
if (existsSync(candidate)) {
|
||||
|
|
@ -79,15 +71,7 @@ function resolveOpencodeBinary(): string {
|
|||
|
||||
function attachCommand(sessionId: string, targetPath: string, endpoint: string): string {
|
||||
const opencode = resolveOpencodeBinary();
|
||||
return [
|
||||
shellEscape(opencode),
|
||||
"attach",
|
||||
shellEscape(endpoint),
|
||||
"--session",
|
||||
shellEscape(sessionId),
|
||||
"--dir",
|
||||
shellEscape(targetPath)
|
||||
].join(" ");
|
||||
return [shellEscape(opencode), "attach", shellEscape(endpoint), "--session", shellEscape(sessionId), "--dir", shellEscape(targetPath)].join(" ");
|
||||
}
|
||||
|
||||
export function stripStatusPrefix(windowName: string): string {
|
||||
|
|
@ -99,11 +83,7 @@ export function stripStatusPrefix(windowName: string): string {
|
|||
}
|
||||
|
||||
export function findTmuxWindowsByBranch(branchName: string): TmuxWindowMatch[] {
|
||||
const output = spawnSync(
|
||||
"tmux",
|
||||
["list-windows", "-a", "-F", "#{session_name}:#{window_id}:#{window_name}"],
|
||||
{ encoding: "utf8" }
|
||||
);
|
||||
const output = spawnSync("tmux", ["list-windows", "-a", "-F", "#{session_name}:#{window_id}:#{window_name}"], { encoding: "utf8" });
|
||||
|
||||
if (output.error || output.status !== 0 || !output.stdout) {
|
||||
return [];
|
||||
|
|
@ -128,16 +108,14 @@ export function findTmuxWindowsByBranch(branchName: string): TmuxWindowMatch[] {
|
|||
|
||||
matches.push({
|
||||
target: `${sessionName}:${windowId}`,
|
||||
windowName
|
||||
windowName,
|
||||
});
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
export function spawnCreateTmuxWindow(
|
||||
input: SpawnCreateTmuxWindowInput
|
||||
): SpawnCreateTmuxWindowResult {
|
||||
export function spawnCreateTmuxWindow(input: SpawnCreateTmuxWindowInput): SpawnCreateTmuxWindowResult {
|
||||
if (!isTmuxSession()) {
|
||||
return { created: false, reason: "not-in-tmux" };
|
||||
}
|
||||
|
|
@ -154,21 +132,10 @@ export function spawnCreateTmuxWindow(
|
|||
const endpoint = input.opencodeEndpoint ?? DEFAULT_OPENCODE_ENDPOINT;
|
||||
let output = "";
|
||||
try {
|
||||
output = execFileSync(
|
||||
"tmux",
|
||||
[
|
||||
"new-window",
|
||||
"-d",
|
||||
"-P",
|
||||
"-F",
|
||||
"#{window_id}",
|
||||
"-n",
|
||||
windowName,
|
||||
"-c",
|
||||
input.targetPath
|
||||
],
|
||||
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
||||
);
|
||||
output = execFileSync("tmux", ["new-window", "-d", "-P", "-F", "#{window_id}", "-n", windowName, "-c", input.targetPath], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
} catch {
|
||||
return { created: false, reason: "tmux-new-window-failed" };
|
||||
}
|
||||
|
|
@ -184,11 +151,10 @@ export function spawnCreateTmuxWindow(
|
|||
// Split left pane horizontally → creates right pane; capture its pane ID
|
||||
let rightPane: string;
|
||||
try {
|
||||
rightPane = execFileSync(
|
||||
"tmux",
|
||||
["split-window", "-h", "-P", "-F", "#{pane_id}", "-t", leftPane, "-c", input.targetPath],
|
||||
{ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
|
||||
).trim();
|
||||
rightPane = execFileSync("tmux", ["split-window", "-h", "-P", "-F", "#{pane_id}", "-t", leftPane, "-c", input.targetPath], {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
}).trim();
|
||||
} catch {
|
||||
return { created: true, reason: "created" };
|
||||
}
|
||||
|
|
@ -206,13 +172,7 @@ export function spawnCreateTmuxWindow(
|
|||
|
||||
// Editor in left pane, agent attach in top-right pane
|
||||
runTmux(["send-keys", "-t", leftPane, "nvim .", "Enter"]);
|
||||
runTmux([
|
||||
"send-keys",
|
||||
"-t",
|
||||
rightPane,
|
||||
attachCommand(input.sessionId, input.targetPath, endpoint),
|
||||
"Enter"
|
||||
]);
|
||||
runTmux(["send-keys", "-t", rightPane, attachCommand(input.sessionId, input.targetPath, endpoint), "Enter"]);
|
||||
runTmux(["select-pane", "-t", rightPane]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,6 @@
|
|||
import type { AppConfig, HandoffRecord } from "@openhandoff/shared";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import {
|
||||
createBackendClientFromConfig,
|
||||
filterHandoffs,
|
||||
formatRelativeAge,
|
||||
groupHandoffStatus
|
||||
} from "@openhandoff/client";
|
||||
import { createBackendClientFromConfig, filterHandoffs, formatRelativeAge, groupHandoffStatus } from "@openhandoff/client";
|
||||
import { CLI_BUILD_ID } from "./build-id.js";
|
||||
import { resolveTuiTheme, type TuiTheme } from "./theme.js";
|
||||
|
||||
|
|
@ -31,7 +26,7 @@ const HELP_LINES = [
|
|||
"Esc / Ctrl-C cancel",
|
||||
"",
|
||||
"Legend",
|
||||
"Agent: \u{1F916} running \u{1F4AC} idle \u25CC queued"
|
||||
"Agent: \u{1F916} running \u{1F4AC} idle \u25CC queued",
|
||||
];
|
||||
|
||||
const COLUMN_WIDTHS = {
|
||||
|
|
@ -41,7 +36,7 @@ const COLUMN_WIDTHS = {
|
|||
author: 10,
|
||||
ci: 7,
|
||||
review: 8,
|
||||
age: 5
|
||||
age: 5,
|
||||
} as const;
|
||||
|
||||
interface DisplayRow {
|
||||
|
|
@ -145,15 +140,17 @@ function agentSymbol(status: HandoffRecord["status"]): string {
|
|||
function toDisplayRow(row: HandoffRecord): DisplayRow {
|
||||
const conflictPrefix = row.conflictsWithMain === "true" ? "\u26A0 " : "";
|
||||
|
||||
const prLabel = row.prUrl
|
||||
? `#${row.prUrl.match(/\/pull\/(\d+)/)?.[1] ?? "?"}`
|
||||
: row.prSubmitted ? "sub" : "-";
|
||||
const prLabel = row.prUrl ? `#${row.prUrl.match(/\/pull\/(\d+)/)?.[1] ?? "?"}` : row.prSubmitted ? "sub" : "-";
|
||||
|
||||
const ciLabel = row.ciStatus ?? "-";
|
||||
const reviewLabel = row.reviewStatus
|
||||
? row.reviewStatus === "approved" ? "ok"
|
||||
: row.reviewStatus === "changes_requested" ? "chg"
|
||||
: row.reviewStatus === "pending" ? "..." : row.reviewStatus
|
||||
? row.reviewStatus === "approved"
|
||||
? "ok"
|
||||
: row.reviewStatus === "changes_requested"
|
||||
? "chg"
|
||||
: row.reviewStatus === "pending"
|
||||
? "..."
|
||||
: row.reviewStatus
|
||||
: "-";
|
||||
|
||||
return {
|
||||
|
|
@ -164,7 +161,7 @@ function toDisplayRow(row: HandoffRecord): DisplayRow {
|
|||
author: row.prAuthor ?? "-",
|
||||
ci: ciLabel,
|
||||
review: reviewLabel,
|
||||
age: formatRelativeAge(row.updatedAt)
|
||||
age: formatRelativeAge(row.updatedAt),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -189,18 +186,12 @@ export function formatRows(
|
|||
status: string,
|
||||
searchQuery = "",
|
||||
showHelp = false,
|
||||
options: RenderOptions = {}
|
||||
options: RenderOptions = {},
|
||||
): string {
|
||||
const totalWidth = options.width ?? process.stdout.columns ?? 120;
|
||||
const totalHeight = Math.max(6, options.height ?? process.stdout.rows ?? 24);
|
||||
const fixedWidth =
|
||||
COLUMN_WIDTHS.diff +
|
||||
COLUMN_WIDTHS.agent +
|
||||
COLUMN_WIDTHS.pr +
|
||||
COLUMN_WIDTHS.author +
|
||||
COLUMN_WIDTHS.ci +
|
||||
COLUMN_WIDTHS.review +
|
||||
COLUMN_WIDTHS.age;
|
||||
COLUMN_WIDTHS.diff + COLUMN_WIDTHS.agent + COLUMN_WIDTHS.pr + COLUMN_WIDTHS.author + COLUMN_WIDTHS.ci + COLUMN_WIDTHS.review + COLUMN_WIDTHS.age;
|
||||
const separators = 7;
|
||||
const prefixWidth = 2;
|
||||
const branchWidth = Math.max(20, totalWidth - (fixedWidth + separators + prefixWidth));
|
||||
|
|
@ -208,7 +199,7 @@ export function formatRows(
|
|||
const branchHeader = searchQuery ? `Branch/PR: ${searchQuery}_` : "Branch/PR (type to filter)";
|
||||
const header = [
|
||||
` ${pad(branchHeader, branchWidth)} ${pad("Diff", COLUMN_WIDTHS.diff)} ${pad("Agent", COLUMN_WIDTHS.agent)} ${pad("PR", COLUMN_WIDTHS.pr)} ${pad("Author", COLUMN_WIDTHS.author)} ${pad("CI", COLUMN_WIDTHS.ci)} ${pad("Review", COLUMN_WIDTHS.review)} ${pad("Age", COLUMN_WIDTHS.age)}`,
|
||||
"-".repeat(Math.max(24, Math.min(totalWidth, 180)))
|
||||
"-".repeat(Math.max(24, Math.min(totalWidth, 180))),
|
||||
];
|
||||
|
||||
const body =
|
||||
|
|
@ -220,14 +211,7 @@ export function formatRows(
|
|||
return `${marker}${pad(display.name, branchWidth)} ${pad(display.diff, COLUMN_WIDTHS.diff)} ${pad(display.agent, COLUMN_WIDTHS.agent)} ${pad(display.pr, COLUMN_WIDTHS.pr)} ${pad(display.author, COLUMN_WIDTHS.author)} ${pad(display.ci, COLUMN_WIDTHS.ci)} ${pad(display.review, COLUMN_WIDTHS.review)} ${pad(display.age, COLUMN_WIDTHS.age)}`;
|
||||
});
|
||||
|
||||
const footer = fitLine(
|
||||
buildFooterLine(
|
||||
totalWidth,
|
||||
["Ctrl-H:cheatsheet", `workspace:${workspaceId}`, status],
|
||||
`v${CLI_BUILD_ID}`
|
||||
),
|
||||
totalWidth,
|
||||
);
|
||||
const footer = fitLine(buildFooterLine(totalWidth, ["Ctrl-H:cheatsheet", `workspace:${workspaceId}`, status], `v${CLI_BUILD_ID}`), totalWidth);
|
||||
|
||||
const contentHeight = totalHeight - 1;
|
||||
const lines = [...header, ...body].map((line) => fitLine(line, totalWidth));
|
||||
|
|
@ -256,7 +240,10 @@ export function formatRows(
|
|||
|
||||
interface OpenTuiLike {
|
||||
createCliRenderer?: (options?: Record<string, unknown>) => Promise<any>;
|
||||
TextRenderable?: new (ctx: any, options: { id: string; content: string }) => {
|
||||
TextRenderable?: new (
|
||||
ctx: any,
|
||||
options: { id: string; content: string },
|
||||
) => {
|
||||
content: unknown;
|
||||
fg?: string;
|
||||
bg?: string;
|
||||
|
|
@ -325,10 +312,7 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
const core = (await import("@opentui/core")) as OpenTuiLike;
|
||||
const createCliRenderer = core.createCliRenderer;
|
||||
const TextRenderable = core.TextRenderable;
|
||||
const styleApi =
|
||||
core.fg && core.bg && core.StyledText
|
||||
? { fg: core.fg, bg: core.bg, StyledText: core.StyledText }
|
||||
: null;
|
||||
const styleApi = core.fg && core.bg && core.StyledText ? { fg: core.fg, bg: core.bg, StyledText: core.StyledText } : null;
|
||||
|
||||
if (!createCliRenderer || !TextRenderable) {
|
||||
throw new Error("OpenTUI runtime missing createCliRenderer/TextRenderable exports");
|
||||
|
|
@ -339,7 +323,7 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
const renderer = await createCliRenderer({ exitOnCtrlC: false });
|
||||
const text = new TextRenderable(renderer, {
|
||||
id: "openhandoff-switch",
|
||||
content: "Loading..."
|
||||
content: "Loading...",
|
||||
});
|
||||
text.fg = themeResolution.theme.text;
|
||||
text.bg = themeResolution.theme.background;
|
||||
|
|
@ -376,11 +360,9 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
}
|
||||
const output = formatRows(filteredRows, selected, workspaceId, status, searchQuery, showHelp, {
|
||||
width: renderer.width ?? process.stdout.columns,
|
||||
height: renderer.height ?? process.stdout.rows
|
||||
height: renderer.height ?? process.stdout.rows,
|
||||
});
|
||||
text.content = styleApi
|
||||
? buildStyledContent(output, themeResolution.theme, styleApi)
|
||||
: output;
|
||||
text.content = styleApi ? buildStyledContent(output, themeResolution.theme, styleApi) : output;
|
||||
renderer.requestRender();
|
||||
};
|
||||
|
||||
|
|
@ -439,11 +421,7 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
close();
|
||||
};
|
||||
|
||||
const runActionWithRefresh = async (
|
||||
label: string,
|
||||
fn: () => Promise<void>,
|
||||
success: string
|
||||
): Promise<void> => {
|
||||
const runActionWithRefresh = async (label: string, fn: () => Promise<void>, success: string): Promise<void> => {
|
||||
if (busy) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -469,9 +447,7 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
process.once("SIGINT", handleSignal);
|
||||
process.once("SIGTERM", handleSignal);
|
||||
|
||||
const keyInput = (renderer.keyInput ?? renderer.keyHandler) as
|
||||
| { on: (name: string, cb: (event: KeyEventLike) => void) => void }
|
||||
| undefined;
|
||||
const keyInput = (renderer.keyInput ?? renderer.keyHandler) as { on: (name: string, cb: (event: KeyEventLike) => void) => void } | undefined;
|
||||
|
||||
if (!keyInput) {
|
||||
clearInterval(timer);
|
||||
|
|
@ -577,11 +553,7 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
if (!row) {
|
||||
return;
|
||||
}
|
||||
void runActionWithRefresh(
|
||||
`archiving ${row.handoffId}`,
|
||||
async () => client.runAction(workspaceId, row.handoffId, "archive"),
|
||||
`archived ${row.handoffId}`
|
||||
);
|
||||
void runActionWithRefresh(`archiving ${row.handoffId}`, async () => client.runAction(workspaceId, row.handoffId, "archive"), `archived ${row.handoffId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -590,11 +562,7 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
if (!row) {
|
||||
return;
|
||||
}
|
||||
void runActionWithRefresh(
|
||||
`syncing ${row.handoffId}`,
|
||||
async () => client.runAction(workspaceId, row.handoffId, "sync"),
|
||||
`synced ${row.handoffId}`
|
||||
);
|
||||
void runActionWithRefresh(`syncing ${row.handoffId}`, async () => client.runAction(workspaceId, row.handoffId, "sync"), `synced ${row.handoffId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -609,7 +577,7 @@ export async function runTui(config: AppConfig, workspaceId: string): Promise<vo
|
|||
await client.runAction(workspaceId, row.handoffId, "merge");
|
||||
await client.runAction(workspaceId, row.handoffId, "archive");
|
||||
},
|
||||
`merged+archived ${row.handoffId}`
|
||||
`merged+archived ${row.handoffId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type { ChildProcess } from "node:child_process";
|
|||
|
||||
const { spawnMock, execFileSyncMock } = vi.hoisted(() => ({
|
||||
spawnMock: vi.fn(),
|
||||
execFileSyncMock: vi.fn()
|
||||
execFileSyncMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("node:child_process", async () => {
|
||||
|
|
@ -15,7 +15,7 @@ vi.mock("node:child_process", async () => {
|
|||
return {
|
||||
...actual,
|
||||
spawn: spawnMock,
|
||||
execFileSync: execFileSyncMock
|
||||
execFileSync: execFileSyncMock,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -37,16 +37,16 @@ function healthyMetadataResponse(): { ok: boolean; json: () => Promise<unknown>
|
|||
json: async () => ({
|
||||
runtime: "rivetkit",
|
||||
actorNames: {
|
||||
workspace: {}
|
||||
}
|
||||
})
|
||||
workspace: {},
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function unhealthyMetadataResponse(): { ok: boolean; json: () => Promise<unknown> } {
|
||||
return {
|
||||
ok: false,
|
||||
json: async () => ({})
|
||||
json: async () => ({}),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -66,11 +66,11 @@ describe("backend manager", () => {
|
|||
opencode_poll_interval: 2,
|
||||
github_poll_interval: 30,
|
||||
backup_interval_secs: 3600,
|
||||
backup_retention_days: 7
|
||||
backup_retention_days: 7,
|
||||
},
|
||||
providers: {
|
||||
daytona: { image: "ubuntu:24.04" }
|
||||
}
|
||||
daytona: { image: "ubuntu:24.04" },
|
||||
},
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
@ -116,7 +116,7 @@ describe("backend manager", () => {
|
|||
|
||||
const fakeChild = Object.assign(new EventEmitter(), {
|
||||
pid: process.pid,
|
||||
unref: vi.fn()
|
||||
unref: vi.fn(),
|
||||
}) as unknown as ChildProcess;
|
||||
spawnMock.mockReturnValue(fakeChild);
|
||||
|
||||
|
|
@ -125,14 +125,8 @@ describe("backend manager", () => {
|
|||
expect(spawnMock).toHaveBeenCalledTimes(1);
|
||||
const launchCommand = spawnMock.mock.calls[0]?.[0];
|
||||
const launchArgs = spawnMock.mock.calls[0]?.[1] as string[] | undefined;
|
||||
expect(
|
||||
launchCommand === "pnpm" ||
|
||||
launchCommand === "bun" ||
|
||||
(typeof launchCommand === "string" && launchCommand.endsWith("/bun"))
|
||||
).toBe(true);
|
||||
expect(launchArgs).toEqual(
|
||||
expect.arrayContaining(["start", "--host", config.backend.host, "--port", String(config.backend.port)])
|
||||
);
|
||||
expect(launchCommand === "pnpm" || launchCommand === "bun" || (typeof launchCommand === "string" && launchCommand.endsWith("/bun"))).toBe(true);
|
||||
expect(launchArgs).toEqual(expect.arrayContaining(["start", "--host", config.backend.host, "--port", String(config.backend.port)]));
|
||||
if (launchCommand === "pnpm") {
|
||||
expect(launchArgs).toEqual(expect.arrayContaining(["exec", "bun", "src/index.ts"]));
|
||||
}
|
||||
|
|
@ -148,9 +142,7 @@ describe("backend manager", () => {
|
|||
mkdirSync(stateDir, { recursive: true });
|
||||
writeFileSync(versionPath, "test-build", "utf8");
|
||||
|
||||
const fetchMock = vi
|
||||
.fn<() => Promise<{ ok: boolean; json: () => Promise<unknown> }>>()
|
||||
.mockResolvedValue(healthyMetadataResponse());
|
||||
const fetchMock = vi.fn<() => Promise<{ ok: boolean; json: () => Promise<unknown> }>>().mockResolvedValue(healthyMetadataResponse());
|
||||
globalThis.fetch = fetchMock as unknown as typeof fetch;
|
||||
|
||||
await ensureBackendRunning(config);
|
||||
|
|
|
|||
|
|
@ -23,4 +23,3 @@ with more detail
|
|||
expect(value).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -29,11 +29,11 @@ describe("resolveTuiTheme", () => {
|
|||
opencode_poll_interval: 2,
|
||||
github_poll_interval: 30,
|
||||
backup_interval_secs: 3600,
|
||||
backup_retention_days: 7
|
||||
backup_retention_days: 7,
|
||||
},
|
||||
providers: {
|
||||
daytona: { image: "ubuntu:24.04" }
|
||||
}
|
||||
daytona: { image: "ubuntu:24.04" },
|
||||
},
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -64,11 +64,7 @@ describe("resolveTuiTheme", () => {
|
|||
withEnv("XDG_STATE_HOME", stateHome);
|
||||
withEnv("XDG_CONFIG_HOME", configHome);
|
||||
mkdirSync(join(stateHome, "opencode"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(stateHome, "opencode", "kv.json"),
|
||||
JSON.stringify({ theme: "gruvbox", theme_mode: "dark" }),
|
||||
"utf8"
|
||||
);
|
||||
writeFileSync(join(stateHome, "opencode", "kv.json"), JSON.stringify({ theme: "gruvbox", theme_mode: "dark" }), "utf8");
|
||||
|
||||
const resolution = resolveTuiTheme(baseConfig, tempDir);
|
||||
|
||||
|
|
@ -85,11 +81,7 @@ describe("resolveTuiTheme", () => {
|
|||
withEnv("XDG_STATE_HOME", stateHome);
|
||||
withEnv("XDG_CONFIG_HOME", configHome);
|
||||
mkdirSync(join(stateHome, "opencode"), { recursive: true });
|
||||
writeFileSync(
|
||||
join(stateHome, "opencode", "kv.json"),
|
||||
JSON.stringify({ theme: "orng", theme_mode: "dark" }),
|
||||
"utf8"
|
||||
);
|
||||
writeFileSync(join(stateHome, "opencode", "kv.json"), JSON.stringify({ theme: "orng", theme_mode: "dark" }), "utf8");
|
||||
|
||||
const resolution = resolveTuiTheme(baseConfig, tempDir);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ const sample: HandoffRecord = {
|
|||
switchTarget: "daytona://sandbox-1",
|
||||
cwd: null,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
}
|
||||
updatedAt: 1,
|
||||
},
|
||||
],
|
||||
agentType: null,
|
||||
prSubmitted: false,
|
||||
|
|
@ -38,7 +38,7 @@ const sample: HandoffRecord = {
|
|||
hasUnpushed: null,
|
||||
parentBranch: null,
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
updatedAt: 1,
|
||||
};
|
||||
|
||||
describe("formatRows", () => {
|
||||
|
|
@ -60,7 +60,7 @@ describe("formatRows", () => {
|
|||
it("pins footer to the last terminal row", () => {
|
||||
const output = formatRows([sample], 0, "default", "ready", "", false, {
|
||||
width: 80,
|
||||
height: 12
|
||||
height: 12,
|
||||
});
|
||||
const lines = output.split("\n");
|
||||
expect(lines).toHaveLength(12);
|
||||
|
|
@ -83,8 +83,8 @@ describe("search", () => {
|
|||
handoffId: "handoff-2",
|
||||
branchName: "docs/update-intro",
|
||||
title: "Docs Intro Refresh",
|
||||
status: "idle"
|
||||
}
|
||||
status: "idle",
|
||||
},
|
||||
];
|
||||
expect(filterHandoffs(rows, "doc")).toHaveLength(1);
|
||||
expect(filterHandoffs(rows, "h2")).toHaveLength(1);
|
||||
|
|
|
|||
|
|
@ -15,11 +15,11 @@ describe("cli workspace resolution", () => {
|
|||
opencode_poll_interval: 2,
|
||||
github_poll_interval: 30,
|
||||
backup_interval_secs: 3600,
|
||||
backup_retention_days: 7
|
||||
backup_retention_days: 7,
|
||||
},
|
||||
providers: {
|
||||
daytona: { image: "ubuntu:24.04" }
|
||||
}
|
||||
daytona: { image: "ubuntu:24.04" },
|
||||
},
|
||||
});
|
||||
|
||||
expect(resolveWorkspace(undefined, config)).toBe("team");
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ function sourceId(): string {
|
|||
try {
|
||||
const raw = execSync("git rev-parse --short HEAD", {
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "ignore"]
|
||||
stdio: ["ignore", "pipe", "ignore"],
|
||||
}).trim();
|
||||
if (raw.length > 0) {
|
||||
return raw;
|
||||
|
|
@ -48,7 +48,6 @@ export default defineConfig({
|
|||
format: ["esm"],
|
||||
dts: true,
|
||||
define: {
|
||||
__HF_BUILD_ID__: JSON.stringify(buildId)
|
||||
}
|
||||
__HF_BUILD_ID__: JSON.stringify(buildId),
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue