Add worktrees, per-hunk review, effort per message, regenerate, voice input
- Isolated git worktree per session (create, status, merge, discard) - Permission card: accept file-edit hunks individually; agent learns what was rejected - Reasoning effort per message; regenerate the last answer with another model/effort - Voice input via the Web Speech API - Folder-up button in the file browser; ssh, rsync and mc in the image Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
df1924b0a1
commit
fca1c41087
22 files changed
+821
-32
No files matched your search
@@ -5,7 +5,7 @@ import { useT } from "../i18n";
|
||||
import { navigate } from "../router";
|
||||
import { useStore } from "../store";
|
||||
import type { PermissionRequest, QuestionRequest } from "../types";
|
||||
import { Diff } from "./Diff";
|
||||
import { Diff, HunkDiff, splitHunks } from "./Diff";
|
||||
|
||||
function SubagentHint({ sessionId, currentId }: { sessionId: string; currentId: string }) {
|
||||
const t = useT();
|
||||
@@ -25,11 +25,17 @@ export function PermissionCard({ req, currentId }: { req: PermissionRequest; cur
|
||||
const t = useT();
|
||||
const [feedback, setFeedback] = useState("");
|
||||
const [busy, setBusy] = useState(false);
|
||||
// File edits with several hunks can be accepted piece by piece.
|
||||
const hunkCount = req.diff && (req.tool === "edit" || req.tool === "write") ? splitHunks(req.diff).hunks.length : 0;
|
||||
const [selected, setSelected] = useState<boolean[]>(() => Array(hunkCount).fill(true));
|
||||
const chosen = selected.filter(Boolean).length;
|
||||
const partial = hunkCount > 1 && chosen < hunkCount;
|
||||
|
||||
const reply = async (r: "once" | "always" | "reject") => {
|
||||
setBusy(true);
|
||||
try {
|
||||
await post(`/permissions/${req.id}`, { reply: r, message: r === "reject" ? feedback : undefined });
|
||||
const hunks = r === "once" && partial ? selected.flatMap((on, i) => (on ? [i] : [])) : undefined;
|
||||
await post(`/permissions/${req.id}`, { reply: r, message: r === "reject" || partial ? feedback || undefined : undefined, hunks });
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
@@ -49,7 +55,12 @@ export function PermissionCard({ req, currentId }: { req: PermissionRequest; cur
|
||||
<SubagentHint sessionId={req.session_id} currentId={currentId} />
|
||||
<div style={{ margin: "6px 0" }}>{req.title}</div>
|
||||
{command && <pre className="mono" style={{ whiteSpace: "pre-wrap", margin: "6px 0" }}>$ {command}</pre>}
|
||||
{req.diff ? (
|
||||
{req.diff && hunkCount > 1 ? (
|
||||
<>
|
||||
<div className="hint">{t("permission.hunksHint", { n: hunkCount })}</div>
|
||||
<HunkDiff diff={req.diff} selected={selected} onToggle={(i) => setSelected((s) => s.map((v, j) => (j === i ? !v : v)))} />
|
||||
</>
|
||||
) : req.diff ? (
|
||||
<Diff diff={req.diff} />
|
||||
) : (
|
||||
!command && (
|
||||
@@ -69,10 +80,10 @@ export function PermissionCard({ req, currentId }: { req: PermissionRequest; cur
|
||||
onKeyDown={(e) => e.key === "Enter" && reply("reject")}
|
||||
/>
|
||||
<div className="actions">
|
||||
<button className="primary" disabled={busy} onClick={() => reply("once")} autoFocus>
|
||||
{t("permission.allowOnce")}
|
||||
<button className="primary" disabled={busy || (hunkCount > 1 && chosen === 0)} onClick={() => reply("once")} autoFocus>
|
||||
{partial ? t("permission.applySelected", { k: chosen, n: hunkCount }) : t("permission.allowOnce")}
|
||||
</button>
|
||||
<button disabled={busy} onClick={() => reply("always")}>
|
||||
<button disabled={busy || partial} onClick={() => reply("always")}>
|
||||
{alwaysLabel}
|
||||
</button>
|
||||
<button className="danger" disabled={busy} onClick={() => reply("reject")}>
|
||||
|
||||
Reference in new issue
Block a user