diff --git a/README.md b/README.md index dfe5d0d7..da24d381 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,12 @@ The features below start from a real need — something a plain coding agent, op ## What You Can Do +### Bring your history over — switch tools without starting from zero + +**The need:** You've built up months of context in another agent — Codex or Claude Code — and switching tools normally means abandoning all of it: the conversations, the accumulated memory, the skills you tuned. That cost alone keeps people on tools they've outgrown. + +**What DeepAgent does:** One-click import of your existing history. Point it at a Codex or Claude Code installation and it hot-imports your chat sessions, memory, and skills straight into DeepAgent — reading each tool's on-disk format, normalizing it, and replaying it into the document graph so imported conversations behave like native ones. Secrets are redacted on the way in, imported projects stay isolated so nothing collides with your active work, and re-running an import converges instead of duplicating. Available from the Settings "Import history" panel, a History view in the sidebar, or the `import-history` CLI command. Migration is a few minutes, not a fresh start. + ### Keep one conversation going indefinitely **The need:** Long tasks overflow the context window. Most agents respond by truncating history or summarizing everything at a threshold — so mid-task the agent forgets a decision you made an hour ago, or the window fills with stale tool output and quality falls off a cliff. diff --git a/README.zh.md b/README.zh.md index b9d3d4e9..9d1618ab 100644 --- a/README.zh.md +++ b/README.zh.md @@ -21,6 +21,12 @@ DeepAgent Code 是一个构建在持久文档记忆之上的 AI 编程智能体 ## 你可以做什么 +### 把历史一起带过来——换工具不必从零开始 + +**需求:** 你已经在另一个智能体里——Codex 或 Claude Code——积累了几个月的上下文,而换工具通常意味着把这一切统统丢下:那些对话、沉淀下来的记忆、你调好的技能。单是这份代价,就足以把人留在早该告别的工具上。 + +**DeepAgent 的做法:** 一键导入你已有的历史。把它指向一个 Codex 或 Claude Code 的安装目录,它就会把你的会话、记忆和技能热导入 DeepAgent——读取各工具的本地格式,归一化后重放进文档图,让导入的对话表现得和原生对话一样。导入过程中会对密钥做脱敏,导入的项目彼此隔离、不会和你正在进行的工作冲突,重复导入会收敛而不是产生重复。入口有三处:设置里的"导入历史"面板、侧栏的历史视图,以及 `import-history` 命令行。迁移只需几分钟,而不是推倒重来。 + ### 一直对话下去,不必换新 **需求:** 长任务会撑爆上下文窗口。多数智能体的应对方式是截断历史,或在阈值处一刀切地整体摘要——于是任务进行到一半,智能体忘了你一小时前拍的板,或者窗口被陈旧的工具输出塞满,质量断崖式下跌。 diff --git a/packages/app/src/components/history/dialog-history-projects.tsx b/packages/app/src/components/history/dialog-history-projects.tsx new file mode 100644 index 00000000..699398e3 --- /dev/null +++ b/packages/app/src/components/history/dialog-history-projects.tsx @@ -0,0 +1,99 @@ +import { Component, createMemo, createResource, For, Show } from "solid-js" +import { Dialog } from "@deepagent-code/ui/v2/dialog-v2" +import { Icon } from "@deepagent-code/ui/icon" +import { getFilename } from "@deepagent-code/core/util/path" +import { useLanguage } from "@/context/language" +import "../settings-v2/settings-v2.css" + +type ProjectListItem = { + id: string + worktree: string + name?: string +} + +type RawSdkClient = { + client: { + request(options: { + method: string + url: string + body?: unknown + headers?: Record + }): Promise<{ data?: TData }> + } +} + +export const listAllProjects = async (client: RawSdkClient): Promise => { + try { + const response = await client.client.request<{ items?: ProjectListItem[] } | ProjectListItem[]>({ + method: "GET", + url: "/global/projects", + }) + const data = response.data + if (Array.isArray(data)) return data + return data?.items ?? [] + } catch { + // Endpoint missing (stale sidecar build) or transient error — degrade to an + // empty list instead of surfacing a scary error / crashing the dialog. + return [] + } +} + +export const DialogHistoryProjects: Component<{ + client: RawSdkClient + activeWorktrees: ReadonlySet + onOpen: (directory: string) => void +}> = (props) => { + const language = useLanguage() + const [items] = createResource(async () => listAllProjects(props.client)) + + const historical = createMemo(() => { + const all = items() ?? [] + // History = projects the user is NOT currently viewing as active. + return all + .filter((p) => p.worktree && !props.activeWorktrees.has(p.worktree)) + .sort((a, b) => (a.name ?? a.worktree).localeCompare(b.name ?? b.worktree)) + }) + + return ( + +
+
+

+ {language.t("sidebar.history.description")} +

+ +
+ {language.t("review.loading")}
} + > + 0} + fallback={
{language.t("review.empty")}
} + > + + {(project) => ( + + )} + +
+ +
+
+ +
+ ) +} diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx index daeba752..a2d21b1d 100644 --- a/packages/app/src/components/prompt-input.tsx +++ b/packages/app/src/components/prompt-input.tsx @@ -330,6 +330,13 @@ export const PromptInput: Component = (props) => { }) const info = createMemo(() => (params.id ? sync.session.get(params.id) : undefined)) const working = createMemo(() => sync.data.session_working(params.id ?? "")) + // A finished subagent is read-only: it did its one turn and was marked done by the task tool + // (metadata.deepagent.subagent.finished). The panel still lists it so its history is viewable, + // but new prompts must be refused — the parent orchestrates work, you don't chat with a done child. + const subagentFinished = createMemo(() => { + const meta = info()?.metadata as { deepagent?: { subagent?: { finished?: boolean } } } | undefined + return meta?.deepagent?.subagent?.finished === true + }) const imageAttachments = createMemo(() => prompt.current().filter((part): part is ImageAttachmentPart => part.type === "image"), ) @@ -1264,6 +1271,11 @@ export const PromptInput: Component = (props) => { }) const handlePromptSubmit = (event: Event) => { + // Read-only finished subagent: swallow the submit so no new turn is started. + if (subagentFinished()) { + event.preventDefault() + return + } if (draftPreparing() || composing()) { event.preventDefault() return @@ -1778,7 +1790,7 @@ export const PromptInput: Component = (props) => { (options: { @@ -29,13 +26,8 @@ type RawSdkClient = { } } -// The dialog mounts outside SDKProvider (DialogProvider sits above it), so the dir-scoped sdk -// client is passed in by the opener instead of read from useSDK. Shape mirrors submit.ts: the -// generated client exposes the low-level request fn at `.client.request`. type ReviewClient = RawSdkClient -// Exported for the route-contract test (review-dialog-contract.test.ts): these are the live V3.1 -// self-learning Review routes. The test asserts method/url/body so a backend rename breaks CI. export const listPending = async (client: ReviewClient): Promise => { const response = await client.client.request<{ items: KnowledgeItem[] }>({ method: "GET", @@ -61,21 +53,27 @@ export const DialogReview: Component<{ client: ReviewClient }> = (props) => { const language = useLanguage() const [selected, setSelected] = createSignal>(new Set()) const [busy, setBusy] = createSignal(false) + const [showApproved, setShowApproved] = createSignal(false) const [items, { refetch }] = createResource(async () => listPending(props.client)) - const allItems = createMemo(() => items() ?? []) + // Rejected (and superseded, which the backend already excludes) are noise — + // hide them. Approved collapses into a single expandable row so the list is + // dominated by what actually needs attention (pending). + const pending = createMemo(() => (items() ?? []).filter((i) => i.approval_status === "pending")) + const approved = createMemo(() => (items() ?? []).filter((i) => i.approval_status === "approved")) + const toggle = (id: string) => { const next = new Set(selected()) if (next.has(id)) next.delete(id) else next.add(id) setSelected(next) } - const selectAll = () => setSelected(new Set(allItems().map((i) => i.id))) + const selectAll = () => setSelected(new Set(pending().map((i) => i.id))) const invert = () => { const cur = selected() setSelected( new Set( - allItems() + pending() .map((i) => i.id) .filter((id) => !cur.has(id)), ), @@ -101,6 +99,31 @@ export const DialogReview: Component<{ client: ReviewClient }> = (props) => { } } + const Row = (item: KnowledgeItem) => { + const checked = createMemo(() => selected().has(item.id)) + return ( + + ) + } + return (
@@ -113,60 +136,39 @@ export const DialogReview: Component<{ client: ReviewClient }> = (props) => { fallback={
{language.t("review.loading")}
} > 0} + when={pending().length > 0 || approved().length > 0} fallback={
{language.t("review.empty")}
} > - - {(item) => { - const checked = createMemo(() => selected().has(item.id)) - // P2-J: render all THREE states. The backend returns approved entries too so a - // reviewer can REVOKE a prior approval (select it, then Reject). Collapsing - // approved into "pending" made Approve look like a no-op and hid revoke entirely. - const statusKey = createMemo(() => - item.approval_status === "rejected" - ? "review.status.rejected" - : item.approval_status === "approved" - ? "review.status.approved" - : "review.status.pending", - ) - return ( - - ) - }} - + {/* Pending: laid out flat — this is what needs attention. */} + {(item) => Row(item)} + + {/* Approved: collapsed into one expandable row. */} + 0}> + + + {(item) => Row(item)} + +
- - 0}> diff --git a/packages/app/src/components/settings-v2/general.tsx b/packages/app/src/components/settings-v2/general.tsx index 2c076f06..c5fc27df 100644 --- a/packages/app/src/components/settings-v2/general.tsx +++ b/packages/app/src/components/settings-v2/general.tsx @@ -31,6 +31,7 @@ import { playSoundById, SOUND_OPTIONS } from "@/utils/sound" import { Link } from "../link" import { SettingsListV2 } from "./parts/list" import { SettingsRowV2 } from "./parts/row" +import { ImportSection } from "./import-history" import "./settings-v2.css" import { deepAgentModeFromConfig, @@ -923,6 +924,8 @@ export const SettingsGeneralV2: Component = () => { + +
) diff --git a/packages/app/src/components/settings-v2/import-history.tsx b/packages/app/src/components/settings-v2/import-history.tsx new file mode 100644 index 00000000..918c30d8 --- /dev/null +++ b/packages/app/src/components/settings-v2/import-history.tsx @@ -0,0 +1,337 @@ +import { Component, For, Show, createMemo, createSignal } from "solid-js" +import { ButtonV2 } from "@deepagent-code/ui/v2/button-v2" +import { Icon } from "@deepagent-code/ui/icon" +import { SelectV2 } from "@deepagent-code/ui/v2/select-v2" +import { Switch } from "@deepagent-code/ui/v2/switch-v2" +import { TextInputV2 } from "@deepagent-code/ui/v2/text-input-v2" +import { useLanguage } from "@/context/language" +import { useServer } from "@/context/server" +import { SettingsListV2 } from "./parts/list" +import { SettingsRowV2 } from "./parts/row" +import "./settings-v2.css" + +type SourceFormat = "codex" | "claude" +type SourceMode = SourceFormat | "custom" +type Scope = "session" | "memory" | "skill" +type ProgressEvent = + | { phase: "discover"; source: SourceFormat; count: number } + | { phase: "write-session"; sessionId: string; turns: number; reimport: boolean } + | { phase: "write-memory"; staged: number } + | { phase: "write-skill"; written: number } + | { phase: "warn"; message: string; label?: string } + | { phase: "done"; report: any } + | { phase: "error"; message: string } + +interface HttpBase { + url: string + username?: string + password?: string + bearer?: string +} + +function authHeader(http: HttpBase | undefined): Record { + if (!http) return {} + if (http.bearer) return { Authorization: `Bearer ${http.bearer}` } + if (http.password) { + const user = http.username ?? "deepagent-code" + return { Authorization: `Basic ${btoa(`${user}:${http.password}`)}` } + } + return {} +} + +async function streamImport( + baseUrl: string, + headers: Record, + payload: Record, + onEvent: (e: ProgressEvent) => void, + signal: AbortSignal, +): Promise { + const res = await fetch(`${baseUrl}/global/import`, { + method: "POST", + headers: { "content-type": "application/json", ...headers }, + body: JSON.stringify(payload), + signal, + }) + if (!res.ok || !res.body) { + const text = await res.text().catch(() => res.statusText) + onEvent({ phase: "error", message: `HTTP ${res.status}: ${text.slice(0, 200)}` }) + return + } + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buffer = "" + while (true) { + const { value, done } = await reader.read() + if (done) break + buffer += decoder.decode(value, { stream: true }) + let idx: number + while ((idx = buffer.indexOf("\n\n")) >= 0) { + const block = buffer.slice(0, idx) + buffer = buffer.slice(idx + 2) + const dataLine = block.split("\n").find((l) => l.startsWith("data:")) + if (!dataLine) continue + try { + onEvent(JSON.parse(dataLine.slice(5).trim()) as ProgressEvent) + } catch { + /* skip malformed */ + } + } + } +} + +export const ImportSection: Component = () => { + const language = useLanguage() + const server = useServer() + + const [mode, setMode] = createSignal("codex") + const [customPath, setCustomPath] = createSignal("") + const [customFormat, setCustomFormat] = createSignal("codex") + const [scopes, setScopes] = createSignal(["session", "memory", "skill"]) + const [dryRun, setDryRun] = createSignal(false) + const [copyLiveDb, setCopyLiveDb] = createSignal(true) + const [cwdFilter, setCwdFilter] = createSignal("") + const [running, setRunning] = createSignal(false) + const [logs, setLogs] = createSignal([]) + const [summary, setSummary] = createSignal("") + + let abort: AbortController | undefined + + const http = (): HttpBase | undefined => (server.current as { http?: HttpBase } | undefined)?.http + const t = (k: string, fallback: string) => { + const v = language.t(k) + return v === k ? fallback : v + } + const push = (line: string) => setLogs((l) => [...l, line]) + const toggleScope = (s: Scope) => { + const cur = scopes() + setScopes(cur.includes(s) ? cur.filter((x) => x !== s) : [...cur, s]) + } + + const format = createMemo(() => (mode() === "custom" ? customFormat() : (mode() as SourceFormat))) + const sourcePath = createMemo(() => (mode() === "custom" ? customPath().trim() : "")) + + const modeOptions = createMemo(() => [ + { value: "codex" as const, label: t("settings.import.preset.codex", "Codex") }, + { value: "claude" as const, label: t("settings.import.preset.claude", "Claude Code") }, + { value: "custom" as const, label: t("settings.import.preset.custom", "Custom") }, + ]) + const formatOptions = createMemo(() => [ + { value: "codex" as const, label: "Codex" }, + { value: "claude" as const, label: "Claude Code" }, + ]) + const scopeOptions: { id: Scope; icon: string }[] = [ + { id: "session", icon: "message" }, + { id: "memory", icon: "bookmark" }, + { id: "skill", icon: "bookmark" }, + ] + + const run = async () => { + if (running()) return + setLogs([]) + setSummary("") + setRunning(true) + abort = new AbortController() + const h = http() + const src = format() + push(`→ ${src}${mode() === "custom" ? ` (custom: ${sourcePath() || "default"})` : ` (default)`} · scopes: ${scopes().join(",")}${dryRun() ? " · dry-run" : ""}`) + try { + await streamImport( + h?.url ?? "", + authHeader(h), + { + source: src, + sourcePath: sourcePath() || undefined, + scopes: scopes(), + dryRun: dryRun(), + copyLiveDb: copyLiveDb(), + cwdFilter: cwdFilter().trim() || undefined, + }, + (e) => { + switch (e.phase) { + case "discover": + push(` discovered ${e.count} session(s)`) + break + case "write-session": + push(` [session] ${e.sessionId.slice(0, 16)}… · ${e.turns} turns${e.reimport ? " · re-imported" : ""}`) + break + case "write-memory": + push(` [memory] staged ${e.staged} candidate(s) for review`) + break + case "write-skill": + push(` [skill] wrote ${e.written}`) + break + case "warn": + push(` [warn] ${e.label ?? ""} ${e.message}`) + break + case "error": + push(` [error] ${e.message}`) + break + case "done": { + const r = e.report + setSummary( + [ + `Done in ${r.elapsedMs}ms`, + `sessions=${r.sessions?.length ?? 0}`, + r.memory ? `memories_staged=${r.memory.staged}` : "", + r.skills ? `skills=${r.skills.written}` : "", + r.warnings?.length ? `warnings=${r.warnings.length}` : "", + r.dryRun ? "(dry-run)" : "", + ] + .filter(Boolean) + .join(" · "), + ) + break + } + } + }, + abort.signal, + ) + } catch (err) { + push(` [error] ${err instanceof Error ? err.message : String(err)}`) + } finally { + setRunning(false) + } + } + + const cancel = () => { + abort?.abort() + setRunning(false) + push(" (cancelled)") + } + + return ( +
+

{t("settings.import.section", "Import history")}

+ + + + o.value === mode())} + placement="bottom-end" + gutter={6} + value={(o) => o.value} + label={(o) => o.label} + onSelect={(option) => option && setMode(option.value)} + /> + + + + +
+ setCustomPath(e.currentTarget.value)} + disabled={running()} + placeholder="/Users/you/.codex_backup" + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + /> + o.value === customFormat())} + placement="bottom-end" + gutter={6} + value={(o) => o.value} + label={(o) => o.label} + onSelect={(option) => option && setCustomFormat(option.value)} + /> +
+
+
+ + +
+ + {(opt) => { + const active = () => scopes().includes(opt.id) + return ( + + ) + }} + +
+
+ + +
+ setDryRun(c)} /> +
+
+ + +
+ setCopyLiveDb(c)} /> +
+
+ + +
+ setCwdFilter(e.currentTarget.value)} + disabled={running()} + placeholder="/Users/you/projects/..." + spellcheck={false} + autocorrect="off" + autocomplete="off" + autocapitalize="off" + /> +
+
+ + +
+ + {running() ? t("settings.import.running", "Importing…") : t("settings.import.run.btn", "Import")} + + + + {t("settings.import.cancel", "Cancel")} + + +
+
+ + + +
{summary()}
+
+
+ + 0}> + +
+              {(line) => line + "\n"}
+            
+
+
+
+
+ ) +} diff --git a/packages/app/src/components/settings-v2/settings-v2.css b/packages/app/src/components/settings-v2/settings-v2.css index 87223afc..ca6d0919 100644 --- a/packages/app/src/components/settings-v2/settings-v2.css +++ b/packages/app/src/components/settings-v2/settings-v2.css @@ -729,3 +729,81 @@ word-break: break-word; color: var(--v2-text-text-faint); } + +.settings-v2-import-log { + margin: 0; + padding: 10px 12px; + max-height: 220px; + min-width: 240px; + width: 100%; + overflow: auto; + border: 1px solid var(--v2-border-border-muted); + border-radius: 8px; + background: var(--v2-background-bg-layer-01); + font-family: var(--font-mono, monospace); + font-size: 11px; + line-height: 1.5; + white-space: pre-wrap; + word-break: break-word; + color: var(--v2-text-text-faint); +} + +.settings-v2-scope-row { + display: flex; + flex-wrap: wrap; + gap: 8px; + width: 100%; +} + +.settings-v2-scope-toggle { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 8px 12px; + border-radius: 8px; + border: 1px solid var(--v2-border-border-strong, var(--v2-border-border-muted)); + background: var(--v2-background-bg-layer-01); + color: var(--v2-text-text-base); + font-size: 13px; + font-weight: 500; + cursor: pointer; + user-select: none; + transition: + background 0.12s ease, + border-color 0.12s ease, + color 0.12s ease; +} + +.settings-v2-scope-toggle:hover:not(:disabled) { + border-color: var(--v2-border-border-hover, var(--v2-border-border-strong)); + background: var(--v2-background-bg-layer-02, var(--v2-background-bg-layer-01)); +} + +.settings-v2-scope-toggle:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.settings-v2-scope-toggle--active { + border-color: var(--v2-border-border-strong, currentColor); + background: var(--v2-background-bg-strong, var(--v2-background-bg-layer-02)); + color: var(--v2-text-text-base); +} + +.settings-v2-scope-toggle-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + border-radius: 4px; + border: 1px solid var(--v2-border-border-muted); + background: var(--v2-background-bg-base, transparent); + color: var(--v2-text-text-invert, currentColor); +} + +.settings-v2-scope-toggle--active .settings-v2-scope-toggle-mark { + border-color: var(--v2-accent-accent, var(--v2-border-border-strong)); + background: var(--v2-accent-accent, var(--v2-background-bg-strong)); + color: #fff; +} diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx index a9287465..adf1569c 100644 --- a/packages/app/src/context/terminal.tsx +++ b/packages/app/src/context/terminal.tsx @@ -644,6 +644,22 @@ function createWorkspaceTerminalSession( size: pty.cols && pty.rows ? { rows: pty.rows, cols: pty.cols } : undefined, }) .catch((error: unknown) => { + // Benign races that must NOT surface as a scary error (or, in dev, kick + // the user out of their flow): + // - "PTY session not found": a resize/title update racing with the PTY + // exiting server-side. `pty.exited` + removeExited already retire it. + // - HTTP 503 with empty body: the instance scope is tearing down during + // a project switch / reload (see handlers/pty.ts). The terminal will + // re-establish on the new instance; this is expected, not an error. + const message = error instanceof Error ? error.message : String(error) + const benign = /PTY session not found/.test(message) || /\b503\b/.test(message) + if (benign) { + if (previous) { + const currentIndex = store.all.findIndex((item) => item.id === pty.id) + if (currentIndex >= 0) setStore("all", currentIndex, previous) + } + return + } if (previous) { const currentIndex = store.all.findIndex((item) => item.id === pty.id) if (currentIndex >= 0) setStore("all", currentIndex, previous) diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index ab9107c6..1acf716d 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -842,6 +842,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "تم إغلاق WebSocket بشكل غير طبيعي: {{code}}", "settings.section.about": "حول", "settings.tab.about": "حول", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "المنتج", "settings.about.product.description": "اسم التطبيق وإصداره.", "settings.about.attribution.title": "مبني على deepagent-code", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index 95f636d3..83b330fc 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -856,6 +856,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket fechado anormalmente: {{code}}", "settings.section.about": "Sobre", "settings.tab.about": "Sobre", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Produto", "settings.about.product.description": "O nome e a versão do aplicativo.", "settings.about.attribution.title": "Criado sobre deepagent-code", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 72217dfa..06994c6e 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -933,6 +933,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket zatvoren nenormalno: {{code}}", "settings.section.about": "O aplikaciji", "settings.tab.about": "O aplikaciji", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Produkt", "settings.about.product.description": "Naziv i verzija aplikacije.", "settings.about.attribution.title": "Zbudowane na deepagent-code", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 49597235..404f6209 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -926,6 +926,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket lukkede unormalt: {{code}}", "settings.section.about": "Om", "settings.tab.about": "Om", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Produkt", "settings.about.product.description": "Programmets navn og version.", "settings.about.attribution.title": "Bygget på deepagent-code", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index 70a5fe6a..3dc2a534 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -872,6 +872,30 @@ export const dict = { "settings.general.deepagent.mode.ultra": "ultra", "settings.section.about": "Info", "settings.tab.about": "Info", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Produkt", "settings.about.product.description": "Name und Version der Anwendung.", "settings.about.attribution.title": "Auf deepagent-code aufgebaut", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 0da53ec3..02b7b05a 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -717,6 +717,8 @@ export const dict = { "session.subagents.empty": "No subagents for this session", "session.subagents.running": "running", "session.subagents.idle": "idle", + "session.subagents.finished": "finished", + "session.subagents.readonly": "This subagent has finished. Its history is read-only.", "session.fork.derivedFrom": "Derived from {title}", "session.fork.derivedFromUnknown": "Derived from another session", "browser.title": "Browser", @@ -869,6 +871,8 @@ export const dict = { "sidebar.settings": "Settings", "sidebar.help": "Help", "sidebar.review": "Knowledge Governance", + "sidebar.history": "History projects", + "sidebar.history.description": "Projects with imported or past sessions that are not currently active. Click one to open it.", "sidebar.workspaces.enable": "Enable workspaces", "sidebar.workspaces.disable": "Disable workspaces", "sidebar.gettingStarted.title": "Getting started", @@ -912,6 +916,30 @@ export const dict = { "settings.tab.general": "General", "settings.tab.shortcuts": "Shortcuts", "settings.tab.about": "About", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Product", "settings.about.product.description": "The application name and version.", "settings.about.attribution.title": "Built on deepagent-code", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 45453dd4..20c9d71c 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -939,6 +939,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket cerrado anormalmente: {{code}}", "settings.section.about": "Acerca de", "settings.tab.about": "Acerca de", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Producto", "settings.about.product.description": "El nombre y la versión de la aplicación.", "settings.about.attribution.title": "Basado en deepagent-code", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 27c1c913..4fa45f73 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -866,6 +866,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket fermé anormalement : {{code}}", "settings.section.about": "À propos", "settings.tab.about": "À propos", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Produit", "settings.about.product.description": "Le nom et la version de l’application.", "settings.about.attribution.title": "Basé sur deepagent-code", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index c5902ac7..24b6a134 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -852,6 +852,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket が異常終了しました: {{code}}", "settings.section.about": "情報", "settings.tab.about": "情報", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "製品", "settings.about.product.description": "アプリケーション名とバージョン。", "settings.about.attribution.title": "deepagent-codeを基盤に構築", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 9f05a929..4ba90185 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -845,6 +845,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket이 비정상적으로 닫힘: {{code}}", "settings.section.about": "정보", "settings.tab.about": "정보", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "제품", "settings.about.product.description": "애플리케이션 이름과 버전입니다.", "settings.about.attribution.title": "deepagent-code 기반", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index fd095d24..1d2964aa 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -938,6 +938,30 @@ export const dict = { "settings.general.deepagent.mode.ultra": "ultra", "settings.section.about": "Om", "settings.tab.about": "Om", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Produkt", "settings.about.product.description": "Applikasjonens navn og versjon.", "settings.about.attribution.title": "Bygget på deepagent-code", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index a7c17d28..6bcde90b 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -855,6 +855,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket zamknięty nieprawidłowo: {{code}}", "settings.section.about": "O programie", "settings.tab.about": "O programie", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Produkt", "settings.about.product.description": "Nazwa i wersja aplikacji.", "settings.about.attribution.title": "Zbudowane na deepagent-code", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index f226ad73..0fa459df 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -935,6 +935,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket закрыт аварийно: {{code}}", "settings.section.about": "О программе", "settings.tab.about": "О программе", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Продукт", "settings.about.product.description": "Название и версия приложения.", "settings.about.attribution.title": "Создано на базе deepagent-code", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 9b480c26..58a0217a 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -922,6 +922,30 @@ export const dict = { "terminal.connectionLost.abnormalClose": "WebSocket ปิดอย่างผิดปกติ: {{code}}", "settings.section.about": "เกี่ยวกับ", "settings.tab.about": "เกี่ยวกับ", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "ผลิตภัณฑ์", "settings.about.product.description": "ชื่อและเวอร์ชันของแอปพลิเคชัน", "settings.about.attribution.title": "สร้างบน deepagent-code", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index 5494ead0..a29c1a08 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -945,6 +945,30 @@ export const dict = { "settings.general.deepagent.mode.ultra": "ultra", "settings.section.about": "Hakkında", "settings.tab.about": "Hakkında", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Ürün", "settings.about.product.description": "Uygulama adı ve sürümü.", "settings.about.attribution.title": "deepagent-code üzerine kurulu", diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index 607a0520..c1573940 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -966,6 +966,30 @@ export const dict = { "workspace.reset.note": "Це скине робочу область, щоб вона відповідала гілці за замовчуванням.", "settings.section.about": "Про застосунок", "settings.tab.about": "Про застосунок", + "settings.import.section": "Import history", + "settings.import.source": "Source", + "settings.import.source.hint": "Pick a preset, or choose Custom to type a path.", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "Custom", + "settings.import.customPath": "Path", + "settings.import.customPath.hint": "Absolute path to the agent data directory.", + "settings.import.scope": "What to import", + "settings.import.scope.hint": "Select one or more categories.", + "settings.import.scope.session": "Chat history", + "settings.import.scope.memory": "Memory", + "settings.import.scope.skill": "Skills", + "settings.import.dryRun": "Dry-run", + "settings.import.dryRun.hint": "Parse and map only; write nothing.", + "settings.import.copyLiveDb": "Snapshot live DB", + "settings.import.copyLiveDb.hint": "Write into a copy of the live DB (recommended) instead of the live DB.", + "settings.import.cwdFilter": "Directory filter", + "settings.import.cwdFilter.hint": "Only import sessions whose directory starts with this prefix.", + "settings.import.run.btn": "Import", + "settings.import.running": "Importing…", + "settings.import.cancel": "Cancel", + "settings.import.result": "Result", + "settings.import.log": "Progress", "settings.about.product.title": "Продукт", "settings.about.product.description": "Назва і версія застосунку.", "settings.about.attribution.title": "Побудовано на deepagent-code", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 4b217456..2a2f8dd8 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -629,6 +629,8 @@ export const dict = { "session.subagents.empty": "此会话没有子 Agent", "session.subagents.running": "运行中", "session.subagents.idle": "空闲", + "session.subagents.finished": "已完成", + "session.subagents.readonly": "该子 Agent 已完成,历史记录为只读。", "session.fork.derivedFrom": "从《{title}》派生", "session.fork.derivedFromUnknown": "从其他对话派生", "browser.title": "浏览器", @@ -733,6 +735,8 @@ export const dict = { "sidebar.settings": "设置", "sidebar.help": "帮助", "sidebar.review": "知识治理", + "sidebar.history": "历史项目", + "sidebar.history.description": "包含已导入或历史会话、但当前未激活的项目。点击可打开。", "sidebar.workspaces.enable": "启用工作区", "sidebar.workspaces.disable": "禁用工作区", "sidebar.gettingStarted.title": "入门", @@ -1121,6 +1125,30 @@ export const dict = { "settings.general.deepagent.mode.xhigh": "xhigh", "settings.section.about": "关于", "settings.tab.about": "关于", + "settings.import.section": "导入历史", + "settings.import.source": "数据来源", + "settings.import.source.hint": "选择预设来源,或选择“自定义”手动输入路径。", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "自定义", + "settings.import.customPath": "路径", + "settings.import.customPath.hint": "Agent 数据目录的绝对路径。", + "settings.import.scope": "导入内容", + "settings.import.scope.hint": "选择一个或多个类别。", + "settings.import.scope.session": "聊天历史", + "settings.import.scope.memory": "记忆", + "settings.import.scope.skill": "技能", + "settings.import.dryRun": "试运行", + "settings.import.dryRun.hint": "仅解析与映射,不写入任何内容。", + "settings.import.copyLiveDb": "快照主库", + "settings.import.copyLiveDb.hint": "写入主库的副本(推荐),而不是直接写主库。", + "settings.import.cwdFilter": "目录过滤", + "settings.import.cwdFilter.hint": "仅导入目录以该前缀开头的会话。", + "settings.import.run.btn": "导入", + "settings.import.running": "导入中…", + "settings.import.cancel": "取消", + "settings.import.result": "结果", + "settings.import.log": "进度", "settings.about.product.title": "产品", "settings.about.product.description": "应用名称和版本。", "settings.about.attribution.title": "上游来源", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index f8345e57..4e9e4878 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -928,6 +928,30 @@ export const dict = { "settings.general.deepagent.mode.ultra": "極限", "settings.section.about": "關於", "settings.tab.about": "關於", + "settings.import.section": "匯入歷史", + "settings.import.source": "資料來源", + "settings.import.source.hint": "選擇預設來源,或選擇「自訂」手動輸入路徑。", + "settings.import.preset.codex": "Codex", + "settings.import.preset.claude": "Claude Code", + "settings.import.preset.custom": "自訂", + "settings.import.customPath": "路徑", + "settings.import.customPath.hint": "Agent 資料目錄的絕對路徑。", + "settings.import.scope": "匯入內容", + "settings.import.scope.hint": "選擇一個或多個類別。", + "settings.import.scope.session": "聊天歷史", + "settings.import.scope.memory": "記憶", + "settings.import.scope.skill": "技能", + "settings.import.dryRun": "試執行", + "settings.import.dryRun.hint": "僅解析與映射,不寫入任何內容。", + "settings.import.copyLiveDb": "快照主庫", + "settings.import.copyLiveDb.hint": "寫入主庫的副本(推薦),而非直接寫主庫。", + "settings.import.cwdFilter": "目錄過濾", + "settings.import.cwdFilter.hint": "僅匯入目錄以該前綴開頭的工作階段。", + "settings.import.run.btn": "匯入", + "settings.import.running": "匯入中…", + "settings.import.cancel": "取消", + "settings.import.result": "結果", + "settings.import.log": "進度", "settings.about.product.title": "產品", "settings.about.product.description": "應用程式名稱與版本。", "settings.about.attribution.title": "基於 deepagent-code 建置", diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index c596397b..4123de0d 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -63,6 +63,7 @@ import { useTheme, type ColorScheme } from "@deepagent-code/ui/theme/context" import { useCommand, type CommandOption } from "@/context/command" import { ConstrainDragXAxis, getDraggableId } from "@/utils/solid-dnd" import { DebugBar } from "@/components/debug-bar" +import { listPending } from "@/components/review/dialog-review" import { Titlebar, type TitlebarUpdate } from "@/components/titlebar" import { useDirectoryPicker } from "@/components/directory-picker" import { ServerConnection, useServer } from "@/context/server" @@ -156,6 +157,22 @@ export default function Layout(props: ParentProps) { const colorSchemeLabel = (scheme: ColorScheme) => language.t(colorSchemeKey[scheme]) const currentDir = createMemo(() => route().dir) + // Pending knowledge-candidate badge on the Review sidebar icon. Re-fetches on + // workspace change and on a slow interval so it stays fresh after imports / + // reviews without coupling to the dialog lifecycle. + const [reviewPending, { refetch: refetchReviewPending }] = createResource(currentDir, async (dir) => { + if (!dir) return false + try { + const sdk = serverSDK.createDirSdkContext(dir) + const items = await listPending(sdk.client as never) + return items.some((item) => item.approval_status === "pending") + } catch { + return false + } + }) + const reviewBadgeTimer = setInterval(() => void refetchReviewPending(), 60_000) + onCleanup(() => clearInterval(reviewBadgeTimer)) + const [state, setState] = createStore({ autoselect: !initialDirectory, busyWorkspaces: {} as Record, @@ -1233,6 +1250,23 @@ export default function Layout(props: ParentProps) { if (dialogDead || dialogRun !== run) return dialog.show(() => ) }) + // Refresh the sidebar badge once the reviewer closes. + setTimeout(() => void refetchReviewPending(), 1500) + } + + function openHistoryProjects() { + const run = ++dialogRun + const activeWorktrees = new Set(server.projects.list().map((p) => p.worktree)) + void import("@/components/history/dialog-history-projects").then((x) => { + if (dialogDead || dialogRun !== run) return + dialog.show(() => ( + openProject(directory)} + /> + )) + }) } function openPacks() { @@ -2362,6 +2396,9 @@ export default function Layout(props: ParentProps) { onOpenSettings={openSettings} reviewLabel={() => language.t("sidebar.review")} onOpenReview={openReview} + reviewPending={() => !!reviewPending.latest} + historyLabel={() => language.t("sidebar.history")} + onOpenHistory={openHistoryProjects} packsLabel={() => language.t("packs.title")} onOpenPacks={openPacks} helpLabel={() => language.t("sidebar.help")} diff --git a/packages/app/src/pages/layout/sidebar-shell.tsx b/packages/app/src/pages/layout/sidebar-shell.tsx index 5692bbd0..1a343b8e 100644 --- a/packages/app/src/pages/layout/sidebar-shell.tsx +++ b/packages/app/src/pages/layout/sidebar-shell.tsx @@ -28,8 +28,11 @@ export const SidebarContent = (props: { settingsLabel: Accessor settingsKeybind: Accessor onOpenSettings: () => void + historyLabel: Accessor + onOpenHistory: () => void reviewLabel: Accessor onOpenReview: () => void + reviewPending?: Accessor packsLabel?: Accessor onOpenPacks?: () => void helpLabel: Accessor @@ -94,24 +97,38 @@ export const SidebarContent = (props: {
- + - + +
+ + + + +
+
+ - + void }> = (props) => .sort((a, b) => (b.time?.updated ?? 0) - (a.time?.updated ?? 0)) }) - const statusOf = (id: string): "running" | "idle" => (sync.data.session_working(id) ? "running" : "idle") + // Three states. A subagent does one turn then finishes: the task tool persists a terminal marker + // in the child session's metadata (`deepagent.subagent.finished`) so a completed subagent reads as + // "finished" (read-only) instead of "idle" (looks available). `session_working` is the live signal + // for the brief window it's actually running; the persisted marker wins once set. + const isFinished = (child: { metadata?: Record }): boolean => { + const sub = (child.metadata?.["deepagent"] as { subagent?: { finished?: boolean } } | undefined)?.subagent + return sub?.finished === true + } + const statusOf = (child: { id: string; metadata?: Record }): "running" | "finished" | "idle" => { + if (sync.data.session_working(child.id)) return "running" + if (isFinished(child)) return "finished" + return "idle" + } + const statusLabel = (state: "running" | "finished" | "idle") => + language.t( + state === "running" + ? "session.subagents.running" + : state === "finished" + ? "session.subagents.finished" + : "session.subagents.idle", + ) return (
@@ -56,17 +76,18 @@ export const SidePanelSubagents: Component<{ onClose: () => void }> = (props) =>