Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 61 additions & 30 deletions src/lib/api-checks-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,102 +15,133 @@ import { invalidateQueries, setQueryData } from "./swr-cache";

// Checks / logs API

export const listWorkflows = (repoPath: string): Promise<WorkflowInfo[]> =>
invoke("list_workflows", { repoPath });
const CHECKS_AND_LOGS = "Checks and logs";

export const runWorkflowJob = (
export const listWorkflows = async (
repoPath: string,
): Promise<WorkflowInfo[]> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("list_workflows", { repoPath });
};

export const runWorkflowJob = async (
repoPath: string,
filename: string,
jobId: string,
workspaceId: number,
workspacePath: string,
): Promise<JobResult> =>
invoke("run_workflow_job", {
): Promise<JobResult> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("run_workflow_job", {
repoPath,
filename,
jobId,
workspaceId,
workspacePath,
});
};

export const runWorkflow = (
export const runWorkflow = async (
repoPath: string,
filename: string,
workspaceId: number,
workspacePath: string,
): Promise<JobResult[]> =>
invoke("run_workflow", { repoPath, filename, workspaceId, workspacePath });
): Promise<JobResult[]> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("run_workflow", {
repoPath,
filename,
workspaceId,
workspacePath,
});
};

export const isRepoTrusted = (repoPath: string): Promise<boolean> =>
invoke("is_repo_trusted", { repoPath });
export const isRepoTrusted = async (repoPath: string): Promise<boolean> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("is_repo_trusted", { repoPath });
};

export const trustRepo = (repoPath: string): Promise<void> =>
invoke("trust_repo", { repoPath });
export const trustRepo = async (repoPath: string): Promise<void> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("trust_repo", { repoPath });
};

export const listWorkflowRuns = (
export const listWorkflowRuns = async (
repoPath: string,
workspaceId: number,
filename: string,
limit?: number,
): Promise<RunSummary[]> =>
invoke("list_workflow_runs", {
): Promise<RunSummary[]> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("list_workflow_runs", {
repoPath,
workspaceId,
filename,
limit: limit ?? null,
});
};

export const getWorkspaceSetupStatus = (
export const getWorkspaceSetupStatus = async (
repoPath: string,
workspaceId: number,
): Promise<SetupScriptStatus> =>
invoke("get_workspace_setup_status", { repoPath, workspaceId });
): Promise<SetupScriptStatus> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("get_workspace_setup_status", { repoPath, workspaceId });
};

export const rerunWorkspaceSetupScript = (
export const rerunWorkspaceSetupScript = async (
repoPath: string,
workspaceId: number,
workspacePath: string,
): Promise<JobResult> =>
invoke("rerun_workspace_setup_script", {
): Promise<JobResult> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("rerun_workspace_setup_script", {
repoPath,
workspaceId,
workspacePath,
});
};

export const getRepoLogs = (
export const getRepoLogs = async (
repoPath: string,
options?: {
levels?: string[];
search?: string;
limit?: number;
offset?: number;
},
): Promise<LogRecordView[]> =>
invoke("get_repo_logs", {
): Promise<LogRecordView[]> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("get_repo_logs", {
repoPath,
levels: options?.levels ?? null,
search: options?.search ?? null,
limit: options?.limit ?? null,
offset: options?.offset ?? null,
});
};

export const getLogTimeseries = (
export const getLogTimeseries = async (
repoPath: string,
options?: { levels?: string[]; search?: string; bucketSeconds?: number },
): Promise<LogBucket[]> =>
invoke("get_log_timeseries", {
): Promise<LogBucket[]> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("get_log_timeseries", {
repoPath,
levels: options?.levels ?? null,
search: options?.search ?? null,
bucketSeconds: options?.bucketSeconds ?? null,
});
};

export const runLogsSql = (
export const runLogsSql = async (
repoPath: string,
sql: string,
maxRows?: number,
): Promise<SqlResult> =>
invoke("run_logs_sql", { repoPath, sql, maxRows: maxRows ?? null });
): Promise<SqlResult> => {
assertLocalOperation(repoPath, CHECKS_AND_LOGS);
return invoke("run_logs_sql", { repoPath, sql, maxRows: maxRows ?? null });
};

// Agent chat logs are files under the repository's `.treq` directory.
const AGENT_CHAT_LOGS = "Agent chat logs";
Expand Down
22 changes: 14 additions & 8 deletions src/lib/api-extra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,19 @@ import {
transportMoveCommit,
transportResolveCommit,
} from "./repository-adapter-mutations";
import { localReadOr, localRepoPathOrNull } from "./local-only";
import { currentWindowLabel } from "./window-label";

export * from "./api-pr-status";

export const setGitSubmoduleSynced = (
export const setGitSubmoduleSynced = async (
repoPath: string,
path: string,
enabled: boolean,
): Promise<void> =>
invoke("set_git_submodule_synced", { repoPath, path, enabled });
): Promise<void> => {
assertLocalOperation(repoPath, "Syncing a git submodule");
return invoke("set_git_submodule_synced", { repoPath, path, enabled });
};

export const startResolveConflicts = async (
repoPath: string,
Expand Down Expand Up @@ -252,16 +255,18 @@ export const remotePtyReattach = (
export const readFile = (path: string): Promise<string> =>
transportReadFile(path, () => invoke("read_file", { path }));

export const writeSendReviewImage = (
export const writeSendReviewImage = async (
repoPath: string,
suggestedName: string,
contentsBase64: string,
): Promise<string> =>
invoke("write_send_review_image", {
): Promise<string> => {
assertLocalOperation(repoPath, "Sending review images");
return invoke("write_send_review_image", {
repoPath,
suggestedName,
contentsBase64,
});
};

export const writeAgentCliFiles = (
prompt: string,
Expand Down Expand Up @@ -344,7 +349,8 @@ export interface SendArtifactRecord {

export const listSendArtifacts = (
repoPath: string,
): Promise<SendArtifactRecord[]> => invoke("list_send_artifacts", { repoPath });
): Promise<SendArtifactRecord[]> =>
localReadOr(repoPath, [], () => invoke("list_send_artifacts", { repoPath }));

export const searchWorkspaceFiles = (
repoPath: string,
Expand Down Expand Up @@ -948,7 +954,7 @@ export const listSkillCatalog = (
catalogUrl?: string | null,
): Promise<SkillCatalogView> =>
invoke("list_skill_catalog", {
repoPath: repoPath ?? null,
repoPath: localRepoPathOrNull(repoPath),
catalogUrl: catalogUrl ?? null,
});

Expand Down
9 changes: 6 additions & 3 deletions src/lib/api-github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
GhReviewThread,
} from "./api-types";
import { invoke } from "@tauri-apps/api/core";
import { assertLocalOperation } from "./repository-adapter";

export const GH_LIST_PAGE_SIZE = 30;

Expand All @@ -24,18 +25,20 @@ export const ghViewIssue = (
): Promise<GhIssue> => invoke("gh_view_issue", { repoFullName, issueNumber });

/** Opens (or creates) the `github-<number>-<slug>` workspace for an issue. */
export const githubOpenOrCreateWorkspaceFromIssue = (
export const githubOpenOrCreateWorkspaceFromIssue = async (
repoPath: string,
number: number,
title: string,
url: string,
): Promise<{ workspace_id: number; created: boolean }> =>
invoke("github_open_or_create_workspace_from_issue", {
): Promise<{ workspace_id: number; created: boolean }> => {
assertLocalOperation(repoPath, "Opening a GitHub issue");
return invoke("github_open_or_create_workspace_from_issue", {
repoPath,
number,
title,
url,
});
};

export const ghCreateIssue = (
repoFullName: string,
Expand Down
40 changes: 30 additions & 10 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import type {
WorkspaceStatus,
} from "./api-types";
import { enqueueJjExclusive } from "./enqueue-jj-exclusive";
import { localReadOr } from "./local-only";
import { localReadOr, localRepoPathOrNull } from "./local-only";
import {
assertLocalOperation,
remoteRepositoryContaining,
Expand Down Expand Up @@ -73,8 +73,10 @@ export * from "./api-github";
export * from "./api-remote-ssh";
export * from "./api-types";

export const initRepo = (repoPath: string): Promise<void> =>
invoke("init_repo", { repoPath });
export const initRepo = async (repoPath: string): Promise<void> => {
assertLocalOperation(repoPath, "Initializing a repository");
return invoke("init_repo", { repoPath });
};

// Database API
export const getWorkspaces = (repoPath: string): Promise<Workspace[]> =>
Expand Down Expand Up @@ -206,14 +208,30 @@ export const applyAgentReviewSuggestion = async (
return invoke("apply_agent_review_suggestion", { repoPath, commentId });
};

// A remote repository's `.treq/config.yaml` lives on the remote host.
const NO_REPO_YAML: RepoYamlConfig = {
branch_name_pattern: null,
default_model: null,
default_agent: null,
target_branch: null,
included_copy_files: null,
review_prompt: null,
review_agent: null,
auto_review_trigger: null,
};

export const loadRepoYamlConfig = (repoPath: string): Promise<RepoYamlConfig> =>
invoke("load_repo_yaml_config", { repoPath });
localReadOr(repoPath, NO_REPO_YAML, () =>
invoke("load_repo_yaml_config", { repoPath }),
);

export const setWindowRepoPath = (repoPath: string): Promise<void> =>
invoke("set_window_repo_path", {
repoPath,
windowLabel: currentWindowLabel(),
});
localReadOr(repoPath, undefined, () =>
invoke("set_window_repo_path", {
repoPath,
windowLabel: currentWindowLabel(),
}),
);

export const detectEditorApps = (): Promise<EditorAppsResponse> =>
invoke("detect_editor_apps");
Expand Down Expand Up @@ -252,7 +270,9 @@ export const getWorkspaceChangedFiles = (
export const listGitignoredPathSuggestions = (
repoPath: string,
): Promise<string[]> =>
invoke("list_gitignored_path_suggestions", { repoPath });
localReadOr(repoPath, [], () =>
invoke("list_gitignored_path_suggestions", { repoPath }),
);

export const getWorkspaceReadme = async (
repoPath: string,
Expand Down Expand Up @@ -732,4 +752,4 @@ export const ensureMobileDeviceKey = (): Promise<DeviceKeyInfo> =>
export const listInstalledSkills = (
repoPath?: string | null,
): Promise<InstalledSkill[]> =>
invoke("list_installed_skills", { repoPath: repoPath ?? null });
invoke("list_installed_skills", { repoPath: localRepoPathOrNull(repoPath) });
5 changes: 5 additions & 0 deletions src/lib/local-only.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ export function localReadOr<T>(
): Promise<T> {
return remoteRepositoryContaining(repoPath) ? Promise.resolve(empty) : read();
}

/** `repoPath` for a local repository; `null` (application scope) for a remote one. */
export function localRepoPathOrNull(repoPath?: string | null): string | null {
return repoPath && !remoteRepositoryContaining(repoPath) ? repoPath : null;
}
24 changes: 1 addition & 23 deletions src/lib/remote-path-guards.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,7 @@ const NOT_REPOSITORY_PATHS = new Set(["cleanupAgentCliFiles"]);

// Local commands that still receive a remote path. Each entry is a gap to
// close: guard it, route it, or scope it, then remove it from this list.
const KNOWN_UNGUARDED: string[] = [
"get_log_timeseries",
"get_repo_logs",
"get_workspace_setup_status",
"github_open_or_create_workspace_from_issue",
"init_repo",
"is_repo_trusted",
"list_gitignored_path_suggestions",
"list_installed_skills",
"list_send_artifacts",
"list_skill_catalog",
"list_workflow_runs",
"list_workflows",
"load_repo_yaml_config",
"rerun_workspace_setup_script",
"run_logs_sql",
"run_workflow",
"run_workflow_job",
"set_git_submodule_synced",
"set_window_repo_path",
"trust_repo",
"write_send_review_image",
];
const KNOWN_UNGUARDED: string[] = [];

function mentionsRemotePath(value: unknown): boolean {
if (typeof value === "string") {
Expand Down
Loading