chore: v2.8.12 — clone queue and progress, session defaults, chat agents, drops as @ mentions
Whole tree from parallel sessions, plus the version bump: - Clone: a queue with live progress (Add workspace and extra folders), clones run under nice/ionice so the machine stays responsive. - Settings → AI tools → Session defaults: a new session's default permission mode / bypass per account. - Chat lists the background subagents that have not reported back yet. - A file pasted or dropped into a claude pane goes in as an @ mention. - Changes panel: say why amend/undo is refused on a pushed commit. - openapi.yaml regenerated (Artifacts, Host, resumeById).
This commit is contained in:
@@ -3576,11 +3576,22 @@ export const api = {
|
||||
title?: string;
|
||||
tool?: LaneTool;
|
||||
credentials?: { username: string; password: string; remember?: boolean };
|
||||
/** Blobless clone (`--filter=blob:none`): file contents fetched on checkout. */
|
||||
partial?: boolean;
|
||||
/** This clone's name for {@link lanes.cloneProgress}. */
|
||||
progressToken?: string;
|
||||
}) =>
|
||||
request<{ lane: Lane; created: boolean; path: string }>("/lanes/clone", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(body),
|
||||
}),
|
||||
/**
|
||||
* GET /api/lanes/clone/progress/:token — a clone's place in the
|
||||
* machine-wide queue, then git's own progress; 404 until the POST
|
||||
* carrying `progressToken` arrives, and a minute after it ends.
|
||||
*/
|
||||
cloneProgress: (token: string) =>
|
||||
request<CloneProgressView>(`/lanes/clone/progress/${encodeURIComponent(token)}`),
|
||||
/**
|
||||
* GET /api/lanes/pipelines — every pipeline template the server can render
|
||||
* a lane against, built-in plus any `DASHBOARD_PIPELINES_DIR` override.
|
||||
@@ -3977,6 +3988,9 @@ export const api = {
|
||||
url: string;
|
||||
name?: string;
|
||||
credentials?: { username: string; password: string; remember?: boolean };
|
||||
/** Blobless clone, like {@link lanes.clone}'s `partial`. */
|
||||
partial?: boolean;
|
||||
progressToken?: string;
|
||||
}
|
||||
) =>
|
||||
request<LaneSourcesView & { path: string }>(`/lanes/${id}/sources/clone`, {
|
||||
@@ -5290,6 +5304,23 @@ export interface LaneSource {
|
||||
}
|
||||
|
||||
/** Response shape of GET /api/lanes/:id/sources (and of every sources write). */
|
||||
/** `GET /api/lanes/clone/progress/:token` — see server/lib/clone-queue.js. */
|
||||
export interface CloneProgressView {
|
||||
state: "queued" | "running" | "done" | "failed";
|
||||
/** 1-based place among the waiting clones while `queued`. */
|
||||
position: number | null;
|
||||
/** Clones running / waiting machine-wide, and how many may run at once. */
|
||||
running: number;
|
||||
waiting: number;
|
||||
limit: number;
|
||||
phase: "remote" | "receiving" | "resolving" | "checkout" | "other" | null;
|
||||
/** git's own words for the phase (shown for `other`). */
|
||||
label: string | null;
|
||||
percent: number | null;
|
||||
/** e.g. "450/1000, 120.50 MiB | 5.20 MiB/s". */
|
||||
detail: string | null;
|
||||
}
|
||||
|
||||
export interface LaneSourcesView {
|
||||
instructions: string;
|
||||
/** The workspace's tool can take folders and instructions (claude, codex). */
|
||||
|
||||
@@ -14,9 +14,10 @@
|
||||
* terminal moves the unsent prompt across (`takeRunInput` reads the TUI's
|
||||
* input box off the screen and empties it; the chat's draft is pasted back
|
||||
* without Enter), so the half-typed text is never left behind on the other side.
|
||||
* Attached images' paths go after the text, each in a paste of its own: `claude`
|
||||
* turns a paste that is only an image's path into `[Image #n]`, but left a
|
||||
* Windows path (`C:\…\drops\…png`) inside a paste of text as a plain path.
|
||||
* Attached images' paths go after the text, each in a paste of its own — how
|
||||
* `codex` attaches one; a `claude` pane gets every file as an `@` mention in
|
||||
* the text instead (`promptForPane`), since a pasted path stayed a plain
|
||||
* `C:\…\drops\…png` there on Windows.
|
||||
* The Chat view also answers a menu the CLI asks with (`parsePaneMenu` reads
|
||||
* it off the screen; `chooseRunOption`/`answerRunMenu` press its digit, the
|
||||
* arrows and Enter, or type into its "Type something" row).
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
* file or folder dragged from the Files panel (`ATTACH_MIME`, read back by
|
||||
* `PromptEditor`'s drop) or from a Chat pane's activity panel
|
||||
* (`dragAttachments`, `dragSkill`: a skill becomes the prompt's `/command`) and the files uploaded through a prompt box in the
|
||||
* last day (`recentUploads`, kept in this browser). An attachment is a path
|
||||
* sent with the prompt, so nothing here touches the server.
|
||||
* last day (`recentUploads`, kept in this browser), and `claudeMention`, the
|
||||
* `@` form a file is handed to `claude` in. An attachment is a path sent with
|
||||
* the prompt, so nothing here touches the server.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -101,6 +102,19 @@ export function droppedText(
|
||||
}
|
||||
}
|
||||
|
||||
/** An attached file as Claude Code's `@` mention, which `claude` reads at
|
||||
* submit — an image as an image — whether it came on the command line or in
|
||||
* a paste. A bare pasted PATH becomes `[Image #n]` only when the TUI catches
|
||||
* the paste on its own and finishes before Enter: on Windows (psmux) it
|
||||
* stayed a plain `C:\…\drops\…png` in the prompt, and on the command line
|
||||
* a path is never an image at all. A Windows path's backslashes become `/`
|
||||
* (Windows takes either); a path with a space is quoted. */
|
||||
export function claudeMention(path: string): string {
|
||||
if (path.startsWith("@")) return path;
|
||||
const p = /^[A-Za-z]:\\/.test(path) ? path.replace(/\\/g, "/") : path;
|
||||
return /\s/.test(p) ? `@"${p}"` : `@${p}`;
|
||||
}
|
||||
|
||||
export const SKILL_MIME = "application/x-agenthub-skill";
|
||||
// A skill's name as Claude Code types it: `name` or `plugin:name`.
|
||||
const SKILL_RE = /^[\w.-]+(?::[\w.-]+)?$/;
|
||||
|
||||
@@ -1,22 +1,37 @@
|
||||
/**
|
||||
* @file sessionDefaults.ts
|
||||
* @description The account's own starting choices for new work, picked in the
|
||||
* first-run setup (docs/plans/onboarding) and synced per account like every
|
||||
* other preference (`lib/userPrefs.ts`): the AI a new workspace starts on, and
|
||||
* the permission mode a new Claude session starts in. Bypass is NEVER one of
|
||||
* them — it stays the per-session switch (CLAUDE.md), so `bypassPermissions`
|
||||
* is refused on read and on write.
|
||||
* first-run setup (docs/plans/onboarding) or Settings → AI tools → Session
|
||||
* defaults, and synced per account like every other preference
|
||||
* (`lib/userPrefs.ts`): the AI a new workspace starts on, the permission mode
|
||||
* a new Claude session starts in, and a new Codex session's sandbox, approval
|
||||
* and bypass. Bypass (Claude's `bypassPermissions`, Codex's bypass switch) may
|
||||
* be a default only because the user turned it on in Settings behind a
|
||||
* confirm — the first-run setup never offers it, and the pet chat never
|
||||
* starts in it (`readDefaultMode({ safe: true })`).
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import type { PermissionMode } from "./api";
|
||||
import type { CodexApproval, CodexSandbox, PermissionMode } from "./api";
|
||||
|
||||
const TOOL_KEY = "agenthub.defaultTool";
|
||||
const MODE_KEY = "agenthub.permissionMode";
|
||||
const CODEX_SANDBOX_KEY = "agenthub.codex.sandbox";
|
||||
const CODEX_APPROVAL_KEY = "agenthub.codex.approval";
|
||||
const CODEX_BYPASS_KEY = "agenthub.codex.bypass";
|
||||
|
||||
/** The permission modes a default may be. */
|
||||
export const DEFAULT_MODES = ["auto", "acceptEdits", "default", "plan"] as const;
|
||||
export const DEFAULT_MODES = ["auto", "acceptEdits", "default", "plan", "bypassPermissions"] as const;
|
||||
export type DefaultMode = (typeof DEFAULT_MODES)[number];
|
||||
/** The ones the first-run setup offers: never bypass. */
|
||||
export const SAFE_MODES = DEFAULT_MODES.filter((m) => m !== "bypassPermissions");
|
||||
|
||||
export const CODEX_SANDBOXES: readonly CodexSandbox[] = [
|
||||
"read-only",
|
||||
"workspace-write",
|
||||
"danger-full-access",
|
||||
];
|
||||
export const CODEX_APPROVALS: readonly CodexApproval[] = ["on-request", "never"];
|
||||
|
||||
function read(key: string): string | null {
|
||||
try {
|
||||
@@ -37,11 +52,36 @@ function write(key: string, value: string): void {
|
||||
export const readDefaultTool = (): string | null => read(TOOL_KEY);
|
||||
export const writeDefaultTool = (id: string): void => write(TOOL_KEY, id);
|
||||
|
||||
/** The permission mode a new Claude session starts in ("auto" when none was picked). */
|
||||
export function readDefaultMode(): PermissionMode {
|
||||
/** The permission mode a new Claude session starts in ("auto" when none was
|
||||
* picked); `safe` turns a bypass default into "auto". */
|
||||
export function readDefaultMode({ safe = false }: { safe?: boolean } = {}): PermissionMode {
|
||||
const v = read(MODE_KEY);
|
||||
if (safe && v === "bypassPermissions") return "auto";
|
||||
return (DEFAULT_MODES as readonly string[]).includes(v ?? "") ? (v as DefaultMode) : "auto";
|
||||
}
|
||||
export function writeDefaultMode(mode: DefaultMode): void {
|
||||
if ((DEFAULT_MODES as readonly string[]).includes(mode)) write(MODE_KEY, mode);
|
||||
}
|
||||
|
||||
export interface CodexDefaults {
|
||||
sandbox: CodexSandbox;
|
||||
approval: CodexApproval;
|
||||
bypass: boolean;
|
||||
}
|
||||
|
||||
/** A new Codex session's sandbox, approval and bypass (Codex's own defaults when none was picked). */
|
||||
export function readCodexDefaults(): CodexDefaults {
|
||||
const sandbox = read(CODEX_SANDBOX_KEY) as CodexSandbox | null;
|
||||
const approval = read(CODEX_APPROVAL_KEY) as CodexApproval | null;
|
||||
return {
|
||||
sandbox: sandbox && CODEX_SANDBOXES.includes(sandbox) ? sandbox : "workspace-write",
|
||||
approval: approval && CODEX_APPROVALS.includes(approval) ? approval : "on-request",
|
||||
bypass: read(CODEX_BYPASS_KEY) === "true",
|
||||
};
|
||||
}
|
||||
export function writeCodexDefaults(patch: Partial<CodexDefaults>): void {
|
||||
if (patch.sandbox && CODEX_SANDBOXES.includes(patch.sandbox)) write(CODEX_SANDBOX_KEY, patch.sandbox);
|
||||
if (patch.approval && CODEX_APPROVALS.includes(patch.approval))
|
||||
write(CODEX_APPROVAL_KEY, patch.approval);
|
||||
if (patch.bypass !== undefined) write(CODEX_BYPASS_KEY, String(patch.bypass === true));
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@ export const PREF_KEYS = [
|
||||
"agent-monitor-notifications",
|
||||
"agenthub.defaultTool",
|
||||
"agenthub.permissionMode",
|
||||
"agenthub.codex.sandbox",
|
||||
"agenthub.codex.approval",
|
||||
"agenthub.codex.bypass",
|
||||
"agenthub.tour",
|
||||
"agenthub.workspace.allTabs",
|
||||
] as const;
|
||||
|
||||
Reference in New Issue
Block a user