Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
e5c1e1c
fix: validate edit_file selection modes before file access
Oct 19, 2026
cc4ee53
docs: clarify edit_file replace_all true-only restrictions
Oct 19, 2026
3a5f5c4
Merge pull request #674 from Orsted290/fix-edit-file-selection-valida…
origami-chen Oct 20, 2026
57a3c82
feat: require MemMate authorization before managed tool execution
ZongYue99 Oct 21, 2026
c3a2038
feat: consume managed web results without native network fallback
ZongYue99 Oct 21, 2026
fa175f0
fix: declare fzstd for standalone Memmy runtime packaging
ZongYue99 Oct 21, 2026
f83aeb0
fix: stream managed Memmy tool outcomes with approval correlation
ZongYue99 Oct 21, 2026
2ddf657
chore: merge release/v1.2.4 into unified approval
ZongYue99 Oct 22, 2026
05891ff
feat: check MemMate permissions during directory traversal and search
ZongYue99 Oct 22, 2026
a32f768
feat: search approved PDF and DOCX text in managed Memmy runs
ZongYue99 Oct 22, 2026
2e688df
fix: resolve packaged Memmy version inside its sandbox boundary
ZongYue99 Oct 22, 2026
8d725fe
fix: isolate managed Memmy state from unapproved project files
ZongYue99 Oct 22, 2026
b4098c1
fix: report outcomes for individual MemMate file approvals
ZongYue99 Oct 22, 2026
8d791ac
fix: continue authorized Memmy traversal after individual denials
ZongYue99 Oct 22, 2026
210f60b
Apply eMudhra runtime upgrade to release v1.2.4
Oct 22, 2026
870a646
fix: bundle ignore into the Memory runtime package
Oct 22, 2026
b7654fd
Merge pull request #713 from memory-lee/fix/memory-runtime-ignore
ZongYue99 Oct 22, 2026
41b1a64
Merge pull request #712 from memory-lee/codex/emudhra-runtime-release…
ZongYue99 Oct 22, 2026
34d0ee1
Merge pull request #718 from MemTensor/main
syzsunshine219 Oct 22, 2026
1157c37
release: prepare Memmy v1.2.401
Oct 22, 2026
1ed0136
test: cover v1.2.401 release regressions
Oct 22, 2026
70d9978
test: pass silent vitest flag explicitly
Oct 22, 2026
8ed73b7
fix: check the better-sqlite3 13 prebuild in the Memory runtime package
Oct 23, 2026
f4834aa
Merge pull request #723 from memory-lee/fix/memory-runtime-sqlite13-c…
ZongYue99 Oct 23, 2026
ba56773
fix memory panel pagination and skill usage analytics
hijzy Oct 23, 2026
ffe9f35
Merge pull request #726 from hijzy/1.2.401
hijzy Oct 23, 2026
3a15e64
fix(desktop): accept Mac update packages during architecture checks
Oct 23, 2026
8afcf9c
Merge remote-tracking branch 'upstream/1.2.401' into release/v1.2.401
Oct 23, 2026
afcf4f5
Merge remote-tracking branch 'upstream/main' into release/v1.2.401
Oct 23, 2026
8043c75
release: refresh v1.2.401 changelog and regression readiness
Oct 23, 2026
04da7f0
docs: align Linux installer Node requirements for v1.2.401
Oct 23, 2026
29b9bc0
fix(release): pin patched Node runtime for standalone Memory
Oct 23, 2026
acdec66
test: align release regression with N-API runtimes and save completion
Oct 23, 2026
9c9d562
test: await catalog persistence and scan I/O completion
Oct 23, 2026
4020519
fix: avoid native dependency rebuild during Windows packaging
Oct 23, 2026
628831c
fix: delegate managed Memmy tools to Host execution
ZongYue99 Oct 24, 2026
5a638c2
memmate-permission
ZongYue99 Oct 24, 2026
d27d975
chore: merge upstream 1.2.401
ZongYue99 Oct 24, 2026
5d5b670
Merge pull request #735 from ZongYue99/zy_memmate-unified-approval
ZongYue99 Oct 24, 2026
424d999
fix: recover transient Windows signing failures
Oct 25, 2026
a02dccf
test(release): track Windows packaging invocation order
Oct 26, 2026
fd4ae6e
fix: compare update versions in character order
ZongYue99 Oct 26, 2026
580413e
fix(desktop): pass a literal Developer ID requirement when staging Ma…
Oct 26, 2026
a7f9aab
Merge pull request #743 from ZongYue99/zy_1.2.401_update
ZongYue99 Oct 26, 2026
3a5cf29
Merge pull request #744 from MemTensor/1.2.401
syzsunshine219 Oct 26, 2026
9ff0787
test(agent): keep managed state assertions type-safe for v1.2.401
Oct 26, 2026
8302997
fix: decouple managed execution interfaces from product names
ZongYue99 Oct 26, 2026
e9d9551
chore: merge release/v1.2.401 and resolve managed state conflict
ZongYue99 Oct 26, 2026
f194351
Merge pull request #749 from ZongYue99/zy_1.2.401_update
ZongYue99 Oct 26, 2026
005768e
docs(release): document managed Agent protocol migration for v1.2.401
Oct 26, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/release-notes/v1.2.401.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Memmy v1.2.401

## Highlights

- Fixed macOS in-app updates being rejected during CPU architecture validation.
- Host-managed Agent file tools now use approvals for file access and keep runtime state separate from unapproved projects.
- Corrected the seven-day skill-use metric to count recalls and invocations of activated skills instead of recently updated skill records.

## Agent and Desktop improvements

- Hide pagination controls when the User Memories list is empty.
- Fixed packaged Memory startup by including the `ignore` dependency.
- Updated Electron and SQLite runtime dependencies, aligned Memory packaging with SQLite 13 N-API prebuilds, and pinned standalone builds to Node.js 24.22.1.
- Retry transient HTTP 503 responses while the Linux systemd Gateway is starting.
- Improve Windows installer build reliability with prepared native dependencies, isolated edition outputs, and bounded recovery for transient signing and file-lock errors.
- Preserve the Developer ID requirement when staging macOS updates and compare update version characters consistently.
- Apply managed approvals to directory traversal, file search, edits, and PDF/DOCX text reads; prevent project-controlled bootstrap files and plugins from being read before approval.

## Upgrade notes

- Applications embedding the Agent in managed mode must migrate to `AGENT_MANAGED_EXECUTION_REQUIRED`, `AGENT_RUNTIME_TOKEN`, and `AGENT_MACOS_SANDBOX`, the `x-agent-runtime-token` request header, and the `managed_execution` request field. The former `MEMMATE_*` settings, header, and `memmate_permission` field are no longer recognized.
- Managed integrations must use `authorize_read_path` for descendant-path authorization and the health fields `permissionProtocol`, `managedExecutionProtocol`, `managedExecutionRequired`, and `macosSandbox`. Update the embedding application together with the Agent and verify the protocol before rollout. Standalone Agent operation does not require these managed integration settings.
- The Linux CLI installer requires Node.js 22.23.4+ within 22.x, 24.22.1+ within 24.x, or 26.12.1+ within 26.x. Other major versions are rejected.

- Memmy Desktop and Agent are version `1.2.401`; the bundled Memory service, viewer, and CLI remain independently versioned at `2.1.7`.
- The skill-use total reports usage events, not the number of distinct skills. Updating a skill without recalling or invoking it no longer increases this total.
- Installation, in-place upgrade, signed-package validation, and publication remain separate release checks.
4 changes: 2 additions & 2 deletions .github/workflows/linux-cli-installer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
ref: ${{ github.event_name == 'release' && format('refs/tags/{0}', github.event.release.tag_name) || github.sha }}
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 22.23.4
cache: npm
- uses: actions/setup-go@v5
with:
Expand Down Expand Up @@ -124,7 +124,7 @@ jobs:
path: release-assets
- uses: actions/setup-node@v4
with:
node-version: 22
node-version: 22.23.4
- name: Install twice and exercise the CLI
env:
VERSION: ${{ needs.build.outputs.version }}
Expand Down
2 changes: 1 addition & 1 deletion App/backend/local-api-contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
},
"scripts": {
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json",
"test": "npm run typecheck && vitest run tests/desktop-runtime-manifest.test.ts tests/language.test.ts tests/model-catalog-resolver.test.ts tests/model-thinking-levels.test.ts",
"test": "npm run typecheck && vitest run --silent=true tests/desktop-runtime-manifest.test.ts tests/language.test.ts tests/model-catalog-resolver.test.ts tests/model-thinking-levels.test.ts",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion App/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"build": "npm run build -w @memmy/knowledge && npm run build -w @memmy/agent-source-core && npm run build -w @memmy/migrations && npm run build -w @memmy/local-api-contracts && node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node -e \"const fs=require('node:fs'); fs.cpSync('src/infrastructure/app-state-store/migrations', 'dist/src/infrastructure/app-state-store/migrations', { recursive: true }); fs.cpSync('src/infrastructure/agent-source-store/migrations', 'dist/src/infrastructure/agent-source-store/migrations', { recursive: true }); fs.cpSync('src/infrastructure/idempotency-store/migrations', 'dist/src/infrastructure/idempotency-store/migrations', { recursive: true }); if (fs.existsSync('src/adapters/outbound/agent-adapter/plugins')) fs.cpSync('src/adapters/outbound/agent-adapter/plugins', 'dist/src/adapters/outbound/agent-adapter/plugins', { recursive: true });\" && npm run workspace-bridge:build:dist",
"lint": "eslint \"src/**/*.ts\" \"vitest.config.ts\"",
"typecheck": "npm run build -w @memmy/knowledge && npm run build -w @memmy/agent-source-core && npm run build -w @memmy/migrations && npm run build -w @memmy/local-api-contracts && tsc -p tsconfig.json --noEmit",
"test": "npm run build -w @memmy/knowledge && npm run build -w @memmy/agent-source-core && npm run build -w @memmy/migrations && npm run build -w @memmy/local-api-contracts && npm run workspace-bridge:build && vitest run",
"test": "npm run build -w @memmy/knowledge && npm run build -w @memmy/agent-source-core && npm run build -w @memmy/migrations && npm run build -w @memmy/local-api-contracts && npm run workspace-bridge:build && vitest run --reporter=dot --silent=true",
"test:agent-adapter:coverage": "npm run build -w @memmy/local-api-contracts && vitest run src/adapters/outbound/agent-adapter/tests --coverage",
"db:migrate": "tsx src/infrastructure/app-state-store/cli/migrate.ts"
},
Expand Down
2 changes: 1 addition & 1 deletion App/backend/src/project-version.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/** Generated from the root package.json by scripts/sync-project-version.mjs. */
export const MEMMY_VERSION = "1.2.4";
export const MEMMY_VERSION = "1.2.401";
1 change: 1 addition & 0 deletions App/backend/src/tests/project-version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe("project version", () => {
);

expect(MEMMY_VERSION).toBe(rootManifest.version);
expect(MEMMY_VERSION).toBe("1.2.401");
expect(MEMMY_VERSION).toMatch(/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/);
expect(readFileSync(resolve(repoRoot, "App/backend/src/project-version.ts"), "utf8"))
.toContain(`MEMMY_VERSION = ${JSON.stringify(rootManifest.version)}`);
Expand Down
2 changes: 1 addition & 1 deletion App/frontend/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"dev": "vite",
"build": "npm run build -w @memmy/knowledge && npm run build -w @memmy/desktop-interface && tsc -p tsconfig.json --noEmit && vite build",
"typecheck": "npm run build -w @memmy/knowledge && npm run build -w @memmy/desktop-interface && tsc -p tsconfig.json --noEmit",
"test": "npm run build -w @memmy/knowledge && npm run build -w @memmy/desktop-interface && vitest run"
"test": "npm run build -w @memmy/knowledge && npm run build -w @memmy/desktop-interface && vitest run --reporter=dot --silent=true"
},
"dependencies": {
"@memmy/desktop-interface": "0.0.0",
Expand Down
17 changes: 2 additions & 15 deletions App/frontend/desktop/src/app/browser-update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,18 +89,9 @@ export function openUpdateUrlInBrowser(downloadUrl: string): void {
window.open(downloadUrl, "_blank", "noopener,noreferrer");
}

/** Compares numeric version segments. */
/** Match Desktop's character ordering, including release suffixes. */
export function compareUpdateVersions(left: string, right: string): number {
const leftParts = extractUpdateVersionSegments(left);
const rightParts = extractUpdateVersionSegments(right);
const length = Math.max(leftParts.length, rightParts.length);
for (let index = 0; index < length; index += 1) {
const diff = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
if (diff !== 0) {
return diff;
}
}
return 0;
return left === right ? 0 : left < right ? -1 : 1;
}

function resolveBrowserUpdateEdition(): "cn" | "intl" {
Expand Down Expand Up @@ -176,10 +167,6 @@ function buildBrowserUpdateDownloadKeys(): string[] {
return [resolveBrowserUpdatePlatformType()];
}

function extractUpdateVersionSegments(version: string): number[] {
return (version.match(/\d+/gu) ?? []).map((part) => Number(part));
}

function readUpdateManifestString(value: unknown, key: string): string | null {
if (!isUpdateManifestRecord(value)) {
return null;
Expand Down
19 changes: 18 additions & 1 deletion App/frontend/desktop/src/app/tests/browser-update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,25 @@ describe("browser update fallback", () => {
currentVersion: "1.4.2",
latestVersion: "1.4.2"
});
expect(compareUpdateVersions("1.10.0", "1.9.9")).toBeGreaterThan(0);
expect(compareUpdateVersions("1.10.0", "1.9.9")).toBeLessThan(0);
expect(() => parseBrowserUpdateManifest({ code: 500, data: {} }, "1.4.2"))
.toThrow("update manifest response code is not ok");
});

it.each([
["1.2.4", "1.2.401"],
["1.2.401", "1.2.402"],
["1.2.402", "1.2.5"],
["1.2.5", "1.2.6"],
["1.2.6", "1.3.0"],
["1.2.10", "1.2.9"],
])("offers %s to %s without offering the reverse update", (current, latest) => {
expect(parseBrowserUpdateManifest({ code: 0, data: { version: latest } }, current).status).toBe("available");
expect(parseBrowserUpdateManifest({ code: 0, data: { version: current } }, latest).status).toBe("latest");
});

it("uses character ordering for the minimum supported version", () => {
expect(parseBrowserUpdateManifest({ code: 0, data: { version: "1.2.6", minSupportedVersion: "1.2.5" } }, "1.2.401"))
.toMatchObject({ status: "available", force: true });
});
});
4 changes: 2 additions & 2 deletions App/frontend/desktop/src/i18n/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1500,7 +1500,7 @@ export const zhCNMessages = {
"memory.analytics.toolAverageLatency": "工具平均耗时",
"memory.analytics.toolP95Latency": "工具 P95 耗时",
"memory.analytics.recallEvents": "{count} 次召回事件",
"memory.analytics.recentlyUsedSkills": "近 7 天使用 {count} 个技能",
"memory.analytics.recentlyUsedSkills": "近 7 天使用技能 {count} 次",
"memory.analytics.toolAverageLatencyHint": "按全部工具调用统计",
"memory.analytics.slowCallWatch": "用于观察慢调用",
"memory.analytics.activeMemories": "活跃记忆",
Expand Down Expand Up @@ -3728,7 +3728,7 @@ export const enUSMessages: Record<keyof typeof zhCNMessages, string> = {
"memory.analytics.toolAverageLatency": "Avg tool latency",
"memory.analytics.toolP95Latency": "P95 tool latency",
"memory.analytics.recallEvents": "{count} recall events",
"memory.analytics.recentlyUsedSkills": "{count} skills used in the last 7 days",
"memory.analytics.recentlyUsedSkills": "{count} skill uses in the last 7 days",
"memory.analytics.toolAverageLatencyHint": "Across all tool calls",
"memory.analytics.slowCallWatch": "Slow-call watch",
"memory.analytics.activeMemories": "Active memories",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ describe("AnalyticsSubPage", () => {
expect(html).toContain('data-icon="bar-chart-3"');
expect(html).toContain("平均召回得分");
expect(html).toContain("活跃技能数量");
expect(html).toContain("近 7 天使用技能 18 次");
expect(html).toContain("工具平均耗时");
expect(html).toContain("工具 P95 耗时");
expect(html).toContain("repeat(auto-fit, minmax(min(100%, 144px), 1fr))");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,23 @@ describe("UserMemoriesSubPage interaction", () => {
container.remove();
});

it("does not render pagination when there are no user memories", async () => {
const client = createMemoryRuntimeClientStub({
listPanelItems: vi.fn(async () => panelItemsOutput([]))
});

await act(async () => {
root.render(
<I18nProvider language="zh-CN">
<UserMemoriesSubPage client={client} />
</I18nProvider>
);
});

expect(container.textContent).toContain("暂无用户记忆");
expect(container.querySelector(".memory-pagination")).toBeNull();
});

it("loads the UserMemory layer and deletes the selected record", async () => {
const item = {
id: "user_memory_1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,12 @@ export function UserMemoriesSubPage(props: UserMemoriesSubPageProps) {
</button>
))}
</div>
<MemoryPagination
data={state.data}
onPageChange={(nextPage) => void refresh(nextPage)}
/>
{state.data.items.length > 0 ? (
<MemoryPagination
data={state.data}
onPageChange={(nextPage) => void refresh(nextPage)}
/>
) : null}
</>
) : null}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,16 +140,29 @@ describe("BYOK displayed, tested and saved credentials", () => {
input.dispatchEvent(new Event("input", { bubbles: true }));
});
}
async function saveAndWait(button: HTMLButtonElement, navigates = false) {
const saveIndex = server.saveCatalog.mock.results.length;
const writes = server.write.mock.calls.length;
const dispatchIndex = mocks.dispatch.mock.calls.length;
await click(button);
await vi.waitFor(() => expect(server.saveCatalog.mock.results.length).toBeGreaterThan(saveIndex));
// A new preset is created before its assignment is saved. Wait for this
// complete catalog mutation, rather than the first backend write or an old navigation.
await act(async () => { await server.saveCatalog.mock.results[saveIndex]!.value; });
expect(server.write.mock.calls.length).toBeGreaterThan(writes);
if (navigates) {
await vi.waitFor(() => expect(mocks.dispatch.mock.calls.slice(dispatchIndex).map(([action]) => action))
.toContainEqual(expect.objectContaining({ type: "navigation/changed" })));
}
}
async function saveOnboarding() { await saveAndWait(next(), true); }
async function testAndSave(scenario: Scenario) {
const count = server.probe.mock.calls.length;
await click(test(scenario.index));
await vi.waitFor(() => expect(server.probe).toHaveBeenCalledTimes(count + 1));
await vi.waitFor(() => expect(test(scenario.index).textContent).toBe("apiKey.testSuccess"));
const tested = server.probe.mock.calls.at(-1)![0];
const writes = server.write.mock.calls.length;
await click(next());
await vi.waitFor(() => expect(server.write.mock.calls.length).toBeGreaterThan(writes));
await vi.waitFor(() => expect(mocks.dispatch).toHaveBeenCalledWith(expect.objectContaining({ type: "navigation/changed" })));
await saveOnboarding();
expect(await server.key(scenario.role)).toBe(tested.apiKey);
return tested;
}
Expand Down Expand Up @@ -250,8 +263,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
it.each([0, 1])("role %i: enabling reuse saves the inherited credential instead of the independent credential", async (index) => {
await render(<ModelPage />);
await click([...container.querySelectorAll<HTMLButtonElement>('button[role="checkbox"]')][index]!);
await click(next());
await vi.waitFor(() => expect(mocks.dispatch).toHaveBeenCalledWith(expect.objectContaining({ type: "navigation/changed" })));
await saveOnboarding();
expect(await server.key(index === 0 ? "memory_summary" : "memory_evolution")).toBe(index === 0 ? "sk-team-1111" : "sk-personal-1111");
});

Expand Down Expand Up @@ -348,8 +360,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
await vi.waitFor(() => expect(container.textContent).toContain("assignment-write-failed"));
expect(mocks.dispatch).not.toHaveBeenCalled();
const endpointsAfterCreate = (await server.read()).catalog!.providers.flatMap((provider) => provider.endpoints).length;
await click(next());
await vi.waitFor(() => expect(mocks.dispatch).toHaveBeenCalledWith(expect.objectContaining({ type: "navigation/changed" })));
await saveOnboarding();
expect(await server.key("agent")).toBe("sk-retry-1111");
expect((await server.read()).catalog!.providers.flatMap((provider) => provider.endpoints)).toHaveLength(endpointsAfterCreate);
});
Expand Down Expand Up @@ -396,8 +407,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
await vi.waitFor(() => expect(document.body.textContent).toContain("settings.modelWorkspace.testSuccess"));
expect(server.probe.mock.calls.at(-1)![0]).toMatchObject({ apiKey: scenario.expected, apiBase: "https://changed.example/v2" });
expect(settingsSave().disabled).toBe(false);
await click(settingsSave());
await vi.waitFor(() => expect(server.write).toHaveBeenCalled());
await saveAndWait(settingsSave());
await vi.waitFor(async () => {
const config = await server.read();
expect(config.catalog!.providers.flatMap((p) => p.endpoints).find((e) => e.endpointId === oldId)?.apiBase).toBe("https://changed.example/v2");
Expand Down Expand Up @@ -429,8 +439,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
await vi.waitFor(() => expect(server.probe.mock.calls.length).toBeGreaterThan(beforeTest4));
await vi.waitFor(() => expect(document.body.textContent).toContain("settings.modelWorkspace.testSuccess"));
expect(server.probe.mock.calls.at(-1)![0]).toMatchObject({ apiKey: "sk-personal-1111", modelId: "changed-model", apiBase: "https://provider.example/v1" });
await click(settingsSave());
await vi.waitFor(() => expect(server.write).toHaveBeenCalled());
await saveAndWait(settingsSave());
expect(await server.key("agent")).toBe("sk-personal-1111");
});

Expand Down Expand Up @@ -534,9 +543,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
expect(test(scenario.index).textContent).toBe("apiKey.testSuccess");
expect(next().disabled).toBe(false);
expect(server.write).not.toHaveBeenCalled();
await click(next());
await vi.waitFor(() => expect(server.write).toHaveBeenCalled());
await vi.waitFor(() => expect(mocks.dispatch).toHaveBeenCalledWith(expect.objectContaining({ type: "navigation/changed" })));
await saveOnboarding();
expect(await server.key(scenario.role)).toBe("sk-after-reentry-9999");
});

Expand All @@ -557,8 +564,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
expect(test(scenario.index).textContent).toBe("apiKey.testSuccess");
await finishOld(false);
expect(test(scenario.index).textContent).toBe("apiKey.testSuccess");
await click(next());
await vi.waitFor(() => expect(server.write).toHaveBeenCalled());
await saveOnboarding();
expect(await server.key(scenario.role)).toBe("sk-new-onboarding-9999");
});

Expand Down Expand Up @@ -599,8 +605,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
await testAndSave(scenario);
} else {
expect(test(scenario.index).textContent).toBe("apiKey.testSuccess");
await click(next());
await vi.waitFor(() => expect(server.write).toHaveBeenCalled());
await saveOnboarding();
}
expect(await server.key(scenario.role)).toBe(order === "return-to-original" ? scenario.expected : "sk-new-onboarding-9999");
});
Expand Down Expand Up @@ -701,8 +706,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
await finish();
expect(test(index).textContent).toBe("apiKey.testSuccess");
expect(next().disabled).toBe(false);
await click(next());
await vi.waitFor(() => expect(server.write).toHaveBeenCalled());
await saveOnboarding();
});

it.each([0, 1])("role %i: editing the other independent card leaves this request valid", async (index) => {
Expand Down Expand Up @@ -740,8 +744,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
}
expect(test(1).textContent).toBe("apiKey.testSuccess");
expect(next().disabled).toBe(false);
await click(next());
await vi.waitFor(() => expect(server.write).toHaveBeenCalled());
await saveOnboarding();
expect(await server.key("memory_evolution")).toBe("sk-team-1111");
});

Expand All @@ -764,8 +767,7 @@ describe("BYOK displayed, tested and saved credentials", () => {
await render(<ModelPage />);
await click(container.querySelectorAll<HTMLButtonElement>('button[role="checkbox"]')[0]!);
await click(container.querySelectorAll<HTMLButtonElement>('button[role="checkbox"]')[1]!);
await click(next());
await vi.waitFor(() => expect(mocks.dispatch).toHaveBeenCalledWith(expect.objectContaining({ type: "navigation/changed" })));
await saveOnboarding();
expect(await server.key("memory_evolution")).toBe("sk-personal-1111");
expect(await server.key("memory_summary")).toBe("sk-personal-1111");
await render(null);
Expand Down
Loading
Loading