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:
2026-10-19 17:19:41 +07:00
parent 1a6b550e89
commit 49f87c4da3
61 changed files with 2824 additions and 114 deletions
@@ -0,0 +1,154 @@
/**
* @file SessionDefaultsSettings — Settings → AI tools → Session defaults: the
* account's own starting choices for a new Claude Code session (permission
* mode, Bypass included) and a new Codex session (sandbox, approval, bypass),
* which the workspace home's prompt box starts from (`lib/sessionDefaults.ts`,
* synced per account). Turning bypass on as a default asks first; a pick in
* the prompt box still changes only that one session.
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
*/
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { ShieldAlert } from "lucide-react";
import type { CodexApproval, CodexSandbox } from "../../lib/api";
import { useAiTools } from "../../hooks/useAiTools";
import { confirmDialog } from "../../lib/confirm";
import {
CODEX_APPROVALS,
CODEX_SANDBOXES,
DEFAULT_MODES,
readCodexDefaults,
readDefaultMode,
writeCodexDefaults,
writeDefaultMode,
type DefaultMode,
} from "../../lib/sessionDefaults";
import { SettingsRow, SettingsSection, SettingsSegmented, SettingsSwitchRow } from "./SettingsRow";
const MODE_KEYS: Record<DefaultMode, string> = {
auto: "Auto",
acceptEdits: "AcceptEdits",
default: "Default",
plan: "Plan",
bypassPermissions: "Bypass",
};
const SANDBOX_KEYS: Record<CodexSandbox, string> = {
"read-only": "sandboxReadOnly",
"workspace-write": "sandboxWorkspaceWrite",
"danger-full-access": "sandboxFullAccess",
};
const APPROVAL_KEYS: Record<CodexApproval, string> = {
"on-request": "approvalOnRequest",
never: "approvalNever",
};
function Danger({ children }: { children: string }) {
return (
<p className="flex items-start gap-2 py-2 text-xs text-status-danger">
<ShieldAlert className="mt-0.5 h-3.5 w-3.5 shrink-0" />
{children}
</p>
);
}
export function SessionDefaultsSettings() {
const { t } = useTranslation("settings");
const aiTools = useAiTools();
const shown = (id: string) => aiTools.tools.some((x) => x.id === id && x.enabled);
const [mode, setMode] = useState<DefaultMode>(() => readDefaultMode() as DefaultMode);
const [codex, setCodex] = useState(readCodexDefaults);
const askBypass = (tool: string) =>
confirmDialog({
title:
tool === "Codex"
? t("sessionDefaults.codexConfirmTitle")
: t("sessionDefaults.confirmTitle", { tool }),
message: t("sessionDefaults.confirmBody", { tool }),
confirmLabel: t("sessionDefaults.confirmLabel"),
});
const pickMode = async (m: DefaultMode) => {
if (m === "bypassPermissions" && !(await askBypass("Claude Code"))) return;
writeDefaultMode(m);
setMode(m);
};
const patchCodex = (patch: Partial<typeof codex>) => {
writeCodexDefaults(patch);
setCodex((c) => ({ ...c, ...patch }));
};
const pickCodexBypass = async (on: boolean) => {
if (on && !(await askBypass("Codex"))) return;
patchCodex({ bypass: on });
};
if (!shown("claude") && !shown("codex")) return null;
return (
<SettingsSection
id="session-defaults"
title={t("sessionDefaults.title")}
description={t("sessionDefaults.description")}
>
{shown("claude") && (
<>
<SettingsRow
label={t("sessionDefaults.claudeMode")}
description={t("sessionDefaults.claudeModeDesc")}
control={
<SettingsSegmented
ariaLabel={t("sessionDefaults.claudeMode")}
value={mode}
onChange={(m) => void pickMode(m)}
options={DEFAULT_MODES.map((m) => ({
value: m,
label: t(`lanes:home.permission${MODE_KEYS[m]}`),
}))}
/>
}
/>
{mode === "bypassPermissions" && <Danger>{t("run:hint.permissionWarning")}</Danger>}
</>
)}
{shown("codex") && (
<>
<SettingsRow
label={t("sessionDefaults.codexSandbox")}
control={
<SettingsSegmented
ariaLabel={t("sessionDefaults.codexSandbox")}
value={codex.sandbox}
onChange={(sandbox) => patchCodex({ sandbox })}
options={CODEX_SANDBOXES.map((v) => ({
value: v,
label: t(`run:fields.${SANDBOX_KEYS[v]}`),
}))}
/>
}
/>
<SettingsRow
label={t("sessionDefaults.codexApproval")}
control={
<SettingsSegmented
ariaLabel={t("sessionDefaults.codexApproval")}
value={codex.approval}
onChange={(approval) => patchCodex({ approval })}
options={CODEX_APPROVALS.map((v) => ({
value: v,
label: t(`run:fields.${APPROVAL_KEYS[v]}`),
}))}
/>
}
/>
<SettingsSwitchRow
label={t("sessionDefaults.codexBypass")}
description={t("sessionDefaults.codexBypassDesc")}
checked={codex.bypass}
onChange={(on) => void pickCodexBypass(on)}
/>
{codex.bypass && <Danger>{t("run:hint.bypassWarning", { tool: "Codex" })}</Danger>}
</>
)}
</SettingsSection>
);
}