Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b2154cc
fix(deepagent-code): pass release gate and consolidate verified batch
deepagent-ai Aug 19, 2026
47b6d55
feat(deepagent-code): retire legacy activity authority, durable is th…
deepagent-ai Aug 19, 2026
3b76c18
fix(deepagent-code): widen load-sensitive test ceilings
deepagent-ai Aug 19, 2026
1ae2255
fix(app): order session timeline by (time, id) instead of wrapped opa…
deepagent-ai Aug 19, 2026
6406686
fix(deepagent-code): rotate provider owner to a successor generation …
deepagent-ai Aug 19, 2026
bfe2e1f
fix(core): fold awaiting_confirmation/selected into the session_inten…
deepagent-ai Aug 19, 2026
b0a2fa3
fix(deepagent-code): stamp the real workspace id on goal tick event rows
deepagent-ai Aug 19, 2026
204217c
test(deepagent-code): lock continue_loop_on_deny terminalization (BUG…
deepagent-ai Aug 19, 2026
5f465eb
feat(deepagent-code): close UPD-005 gaps — persist encrypted_content …
deepagent-ai Aug 19, 2026
796a251
fix(deepagent-code): durable turn stage evidence and pre-dispatch wat…
deepagent-ai Aug 19, 2026
b824bb0
test(deepagent-code): lock REL-002 release evidence (wire bytes, disc…
deepagent-ai Aug 19, 2026
5804f81
feat(deepagent-code): RISK-003 code items — diff artifact capture, ca…
deepagent-ai Aug 19, 2026
841e988
feat(deepagent-code): release-evidence manifest infrastructure (BUG-4…
deepagent-ai Aug 19, 2026
bd4dff8
fix(deepagent-code): stage diff-artifact capture behind a flag and re…
deepagent-ai Aug 19, 2026
20e2d4a
chore(deepagent-code): archive the 2026-08-20 release gate evidence
deepagent-ai Aug 20, 2026
650dac9
fix(core): successor registration fences pre-successor binaries via p…
deepagent-ai Aug 20, 2026
f2d513a
feat(deepagent-code): RISK-003 maintenance drill script (gate-ordered…
deepagent-ai Aug 20, 2026
5d5318d
fix(core): desktop startup crash — session_intent rebuild rename fail…
deepagent-ai Aug 20, 2026
f5b1693
fix(deepagent-code): stop the infinite continuation recovery retry lo…
deepagent-ai Aug 20, 2026
3d8b305
fix(deepagent-code): serialize continuation recovery and downgrade th…
deepagent-ai Aug 20, 2026
c4352e0
chore: bump versions for the 1.4.7 release line
deepagent-ai Aug 20, 2026
d9a3cb5
docs: align release docs and action tags with Desktop 1.4.7 / Core 4.0.8
deepagent-ai Aug 20, 2026
915989b
docs: use the native-language label for the cross-language readme link
deepagent-ai Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
2 changes: 1 addition & 1 deletion .github/workflows/deepagent-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
- uses: ./.github/actions/setup-bun

- name: Run deepagent-code
uses: deepagent-ltd/deepagent-code/github@github-v1.4.5
uses: deepagent-ltd/deepagent-code/github@github-v1.4.7
env:
DEEPAGENT_CODE_API_KEY: ${{ secrets.DEEPAGENT_CODE_API_KEY }}
DEEPAGENT_CODE_PERMISSION: '{"bash": "deny"}'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:

- name: Run deepagent-code
if: steps.commits.outputs.has_commits == 'true'
uses: deepagent-ltd/deepagent-code/github@github-v1.4.5
uses: deepagent-ltd/deepagent-code/github@github-v1.4.7
env:
DEEPAGENT_CODE_API_KEY: ${{ secrets.DEEPAGENT_CODE_API_KEY }}
with:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,26 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
test-fixture-gate:
name: test-fixture-gate
# QUAL-003: forbid test fixtures from bypassing the durable session state
# machine via direct inserts into authority tables. Pure grep gate, no deps.
runs-on: blacksmith-4vcpu-ubuntu-2404
defaults:
run:
shell: bash
steps:
- name: Checkout repository
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
token: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Bun
uses: ./.github/actions/setup-bun

- name: Run test fixture gate
run: bun script/check-test-fixtures.ts

unit:
name: unit (${{ matrix.settings.name }})
strategy:
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/typecheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ name: typecheck
on:
push:
branches: [dev]
# Gate every PR regardless of target branch: the typecheck baseline is easily polluted by stale
# worktrees/generated files, so keep it required and branch-agnostic.
pull_request:
branches: [dev]
workflow_dispatch:

jobs:
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Changelog

## Desktop 1.4.7 / DeepAgent Core V4.0.8 - Release hardening and data governance

- Recover the two incident sessions through the formal abandoned path with a zero-provider-invocation oracle and fork smoke on production-snapshot copies; fence pre-successor binaries via a protocol-3 database capability so older builds refuse to open databases carrying successor ownership.
- Fix the desktop startup crash in the session_intent rebuild migration on SQLite 3.51 (node runtime) and retry transient schema-lock failures in the migration runner.
- Stop the infinite compaction-continuation recovery loop on startup: recovery failures now land fail-closed (pending → failed) through the widened continuation state machine, serialized per session, with warning-level telemetry.
- Add durable turn stage evidence and a pre-dispatch watchdog for hung activities; order app timelines by (time, id) so wrapped opaque IDs never reorder messages.
- Persist encrypted remote-compaction content per session and commit remote compaction runs with an opaque blob pointer instead of a text summary, exempt from information-hole validation.
- Land RISK-003 data governance: runtime diff-artifact capture (staged behind a flag), a legacy event canonicalizer daemon, portable aggregate snapshot bundles, and a gate-ordered maintenance drill that reclaimed hundreds of MiB on production-snapshot copies (33,168 events deleted, VACUUM 5075 → 4311 MiB).
- Add the release-evidence manifest infrastructure and archive the 2026-08-19/20 release gates; lock REL-002 wire-budget and discovery-budget regressions; retire the legacy activity authority in favor of the durable path.
- Align Desktop 1.4.7, DeepAgent Core V4.0.8, and GitHub Action `github-v1.4.7` across package metadata, updater tags, and workflow references.

## Desktop 1.4.5 / DeepAgent Core V4.0.6 - Bounded planning and runtime hardening

- Make compaction durable through explicit run/attempt lifecycle records and Prompt Epoch authority, with bounded text-only summary retries and typed terminal failures.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<p align="center">
<a href="README.md">English</a> |
<a href="README.zh.md">简体中文</a> |
<a href="README.zh.md">中文</a> |
<a href="https://github.com/deepagent-ltd/deepagent-code-enterprise">Enterprise</a>
</p>

Expand Down
2 changes: 1 addition & 1 deletion github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ This will walk you through installing the GitHub app, creating the workflow, and
persist-credentials: false

- name: Run deepagent-code
uses: deepagent-ltd/deepagent-code/github@github-v1.4.5
uses: deepagent-ltd/deepagent-code/github@github-v1.4.7
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion github/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.4.5
1.4.7
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@deepagent-code/app",
"version": "1.4.6",
"version": "1.4.7",
"description": "",
"type": "module",
"exports": {
Expand Down
105 changes: 105 additions & 0 deletions packages/app/src/components/dialog-console-account.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { createResource, createSignal, type Component, For, Show } from "solid-js"
import { useDialog } from "@deepagent-code/ui/context/dialog"
import { Dialog } from "@deepagent-code/ui/dialog"
import { Spinner } from "@deepagent-code/ui/spinner"
import { showToast } from "@/utils/toast"
import { useLanguage } from "@/context/language"
import { useServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"

// PARITY-004 / GUI 批 — Console 账号接线:消费 /experimental/console/orgs 与 /switch,
// 列出可切换的 Console 组织并切换活跃组织(server-side truth,见 worklist GUI 批)。
type Org = {
accountID: string
accountEmail: string
accountUrl: string
orgID: string
orgName: string
active: boolean
}

export const DialogConsoleAccount: Component = () => {
const dialog = useDialog()
const language = useLanguage()
const serverSDK = useServerSDK()
const serverSync = useServerSync()
const [switching, setSwitching] = createSignal<string | undefined>()

const [orgs] = createResource(async () => {
const result = await serverSDK.client.experimental.console.listOrgs(undefined, { throwOnError: true })
return (result.data?.orgs ?? []) as Org[]
})

const switchOrg = async (org: Org) => {
if (switching()) return
setSwitching(`${org.accountID}:${org.orgID}`)
await serverSDK.client.experimental.console
.switchOrg({ accountID: org.accountID, orgID: org.orgID }, { throwOnError: true })
.then(() => {
serverSync.refreshProviders()
showToast({
variant: "success",
icon: "circle-check",
title: language.t("settings.providers.console.switch.toast.title"),
description: language.t("settings.providers.console.switch.toast.description", { org: org.orgName }),
})
dialog.close()
})
.catch((err: unknown) => {
const message = err instanceof Error ? err.message : String(err)
showToast({ title: language.t("common.requestFailed"), description: message })
})
.finally(() => setSwitching(undefined))
}

return (
<Dialog title={language.t("settings.providers.console.switchOrg")} transition>
<Show
when={!orgs.loading}
fallback={
<div class="flex items-center justify-center py-8">
<Spinner />
</div>
}
>
<Show
when={(orgs() ?? []).length > 0}
fallback={
<div class="px-4 py-6 text-14-regular text-text-weak">
{language.t("settings.providers.console.empty")}
</div>
}
>
<div class="flex flex-col px-2 py-1">
<For each={orgs() ?? []}>
{(org) => {
const key = `${org.accountID}:${org.orgID}`
return (
<button
type="button"
disabled={!!switching()}
class="flex w-full items-center justify-between gap-3 rounded-md px-3 py-2 text-left hover:bg-fill-weak-base disabled:opacity-60"
onClick={() => void switchOrg(org)}
>
<span class="flex min-w-0 flex-col">
<span class="truncate text-14-medium text-text-strong">{org.orgName}</span>
<span class="truncate text-12-regular text-text-weak">{org.accountEmail}</span>
</span>
<Show when={switching() === key}>
<Spinner />
</Show>
<Show when={org.active && switching() !== key}>
<span class="text-12-medium text-text-interactive-base">
{language.t("common.active")}
</span>
</Show>
</button>
)
}}
</For>
</div>
</Show>
</Show>
</Dialog>
)
}
42 changes: 41 additions & 1 deletion packages/app/src/components/settings-providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { ProviderIcon } from "@deepagent-code/ui/provider-icon"
import { Tag } from "@deepagent-code/ui/tag"
import { showToast } from "@/utils/toast"
import { popularProviders, useProviders } from "@/hooks/use-providers"
import { createMemo, type Component, For, Show } from "solid-js"
import { createMemo, createResource, type Component, For, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { useLanguage } from "@/context/language"
import { useServerSDK } from "@/context/server-sdk"
import { useServerSync } from "@/context/server-sync"
import { DialogConnectProvider } from "./dialog-connect-provider"
import { DialogSelectProvider } from "./dialog-select-provider"
import { DialogCustomProvider } from "./dialog-custom-provider"
import { DialogConsoleAccount } from "./dialog-console-account"
import { SettingsList } from "./settings-list"
import { SettingsServerPicker, SettingsServerScope } from "./settings-server-picker"
import { canRefreshProviderModels } from "./provider-model-refresh"
Expand Down Expand Up @@ -50,6 +51,12 @@ const SettingsProvidersContent: Component = () => {
const providers = useProviders()
const [refreshing, setRefreshing] = createStore<Record<string, boolean>>({})

// PARITY-004 / GUI 批 — Console 账号:消费 /experimental/console,显示活跃组织与可切换数量。
const [consoleState] = createResource(async () => {
const result = await serverSDK.client.experimental.console.get().catch(() => undefined)
return result?.data
})

const isConfigCustom = (providerID: string) => {
const provider = serverSync.data.config.provider?.[providerID]
if (!provider) return false
Expand Down Expand Up @@ -180,6 +187,39 @@ const SettingsProvidersContent: Component = () => {
</div>

<div class="flex flex-col gap-8 max-w-[720px]">
<Show
when={(consoleState()?.switchableOrgCount ?? 0) > 0 || (consoleState()?.consoleManagedProviders.length ?? 0) > 0}
>
<div class="flex flex-col gap-1" data-component="console-account-section">
<h3 class="text-14-medium text-text-strong pb-2">
{language.t("settings.providers.section.consoleAccount")}
</h3>
<SettingsList>
<div class="flex flex-wrap items-center justify-between gap-4 min-h-16 py-3 border-b border-border-weak-base last:border-none">
<div class="flex flex-col min-w-0">
<span class="text-14-medium text-text-strong truncate">
{consoleState()?.activeOrgName ?? language.t("settings.providers.console.noOrg")}
</span>
<span class="text-12-regular text-text-weak">
{language.t("settings.providers.console.managedCount", {
count: consoleState()?.consoleManagedProviders.length ?? 0,
})}
</span>
</div>
<Button
size="large"
variant="ghost"
onClick={() => {
dialog.show(() => <DialogConsoleAccount />)
}}
>
{language.t("settings.providers.console.switchOrg")}
</Button>
</div>
</SettingsList>
</div>
</Show>

<div class="flex flex-col gap-1" data-component="connected-providers-section">
<h3 class="text-14-medium text-text-strong pb-2">{language.t("settings.providers.section.connected")}</h3>
<SettingsList>
Expand Down
67 changes: 66 additions & 1 deletion packages/app/src/components/settings-v2/import-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { SettingsListV2 } from "./parts/list"
import { SettingsRowV2 } from "./parts/row"
import "./settings-v2.css"

type SourceFormat = "codex" | "claude"
type SourceFormat = "codex" | "claude" | "deepagent"
type SourceMode = SourceFormat | "custom"
type Scope = "session" | "memory" | "skill"
type ProgressEvent =
Expand Down Expand Up @@ -90,6 +90,7 @@ export const ImportSection: Component = () => {
const [dryRun, setDryRun] = createSignal(false)
const [copyLiveDb, setCopyLiveDb] = createSignal(true)
const [cwdFilter, setCwdFilter] = createSignal("")
const [snapshotFile, setSnapshotFile] = createSignal<File | undefined>(undefined)
const [running, setRunning] = createSignal(false)
const [logs, setLogs] = createSignal<string[]>([])
const [summary, setSummary] = createSignal<string>("")
Expand All @@ -113,6 +114,7 @@ export const ImportSection: Component = () => {
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: "deepagent" as const, label: t("settings.import.preset.deepagent", "DeepAgent Code (snapshot)") },
{ value: "custom" as const, label: t("settings.import.preset.custom", "Custom") },
])
const formatOptions = createMemo(() => [
Expand All @@ -125,6 +127,42 @@ export const ImportSection: Component = () => {
{ id: "skill", icon: "bookmark" },
]

// DeepAgent Code snapshot import: read the chosen bundle file and POST it to
// /session/import-snapshot (server rebuilds a fresh, continuable session).
const runSnapshotImport = async (h: HttpBase | undefined) => {
try {
const file = snapshotFile()
if (!file) {
push(` [error] ${t("settings.import.snapshot.noFile", "Choose a snapshot bundle file first")}`)
return
}
const bundle = await file.text()
const res = await fetch(`${h?.url ?? ""}/session/import-snapshot`, {
method: "POST",
headers: { "content-type": "application/json", ...authHeader(h) },
body: JSON.stringify({ bundle }),
signal: abort?.signal,
})
if (!res.ok) {
const text = await res.text().catch(() => res.statusText)
push(` [error] HTTP ${res.status}: ${text.slice(0, 200)}`)
return
}
const result = (await res.json().catch(() => undefined)) as
| { sessionID?: string; messages?: number; parts?: number }
| undefined
push(
` [session] imported ${result?.sessionID?.slice(0, 16) ?? ""}… · ${result?.messages ?? 0} messages · ${result?.parts ?? 0} parts`,
)
setSummary(t("settings.import.snapshot.done", "Snapshot imported as a new continuable session"))
} catch (err) {
const message = err instanceof Error ? err.message : String(err)
push(` [error] ${message}`)
} finally {
setRunning(false)
}
}

const run = async () => {
if (running()) return
setLogs([])
Expand All @@ -134,6 +172,10 @@ export const ImportSection: Component = () => {
const h = http()
const src = format()
push(`→ ${src}${mode() === "custom" ? ` (custom: ${sourcePath() || "default"})` : ` (default)`} · scopes: ${scopes().join(",")}${dryRun() ? " · dry-run" : ""}`)
if (src === "deepagent") {
await runSnapshotImport(h)
return
}
try {
await streamImport(
h?.url ?? "",
Expand Down Expand Up @@ -218,6 +260,29 @@ export const ImportSection: Component = () => {
/>
</SettingsRowV2>

<Show when={mode() === "deepagent"}>
<SettingsRowV2
title={t("settings.import.snapshot.file", "Snapshot bundle")}
description={t(
"settings.import.snapshot.file.hint",
"Choose an exported session snapshot bundle (.json). It imports as a new continuable session.",
)}
>
<div class="flex flex-col gap-2 w-full sm:w-[260px]">
<input
data-action="settings-import-snapshot-file"
type="file"
accept=".json,application/json"
disabled={running()}
onChange={(e) => setSnapshotFile(e.currentTarget.files?.[0])}
/>
<Show when={snapshotFile()}>
{(file) => <span class="text-12-regular text-text-weak truncate">{file().name}</span>}
</Show>
</div>
</SettingsRowV2>
</Show>

<Show when={mode() === "custom"}>
<SettingsRowV2 title={t("settings.import.customPath", "Path")} description={t("settings.import.customPath.hint", "Absolute path to the agent data directory.")}>
<div class="flex flex-col gap-2 w-full sm:w-[260px]">
Expand Down
Loading
Loading