Skip to content

Commit 9a94b2e

Browse files
committed
Validate packaged cold-import confirmation and lost-response recovery
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
1 parent 0dcdd61 commit 9a94b2e

1 file changed

Lines changed: 76 additions & 8 deletions

File tree

‎examples/personal-workspace-browser/goal-storage.mjs‎

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {launchBrowser, loadPlaywright, waitForHttp} from "../dashboard-browser-s
1212
import {openWorkspacePage} from "./scenario-context.mjs";
1313
import {outputDir, packaged, port, repoRoot, startServer} from "./fixture.mjs";
1414

15-
async function startAuthority() {
15+
async function startAuthority({unsettled = true} = {}) {
1616
const root = await mkdtemp(resolve(tmpdir(), "loopx-old-storage-browser-"));
1717
const source = "---\ngoal_id: multi-agent-projection\nhandoff_mode: soft_claim\n---\n" +
1818
"## Agent Todo\n- [ ] Private current requirement\n" +
@@ -26,7 +26,7 @@ async function startAuthority() {
2626
}));
2727
const leases = resolve(root, "runtime/goals/multi-agent-projection/task-leases");
2828
await mkdir(leases, {recursive: true});
29-
await writeFile(resolve(leases, "removed.json"), JSON.stringify({schema_version: "task_lease_v0",
29+
if (unsettled) await writeFile(resolve(leases, "removed.json"), JSON.stringify({schema_version: "task_lease_v0",
3030
goal_id: "multi-agent-projection", todo_id: "removed", owner: "agent-a", status: "active",
3131
idempotency_key: "original-private-key", version: 4, lease_epoch: 2,
3232
expires_at: "2000-01-01T00:00:00Z", write_scopes: []}));
@@ -66,6 +66,69 @@ server.serve_forever()
6666
}
6767
}
6868

69+
async function importSettled(browser, url, provider) {
70+
const authority = await startAuthority({unsettled: false});
71+
let context;
72+
let applyCount = 0;
73+
try {
74+
context = await openWorkspacePage(browser, url, {beforeGoto: async (_api, page) => {
75+
await page.route(/\/api\/chat\/goal-(storage|ownership)(?:[/?]|$)/, async route => {
76+
const parsed = new URL(route.request().url());
77+
const response = await route.fetch({url: authority.url + parsed.pathname + parsed.search});
78+
if (parsed.pathname.endsWith("/import/apply") && ++applyCount === 1) {
79+
assert.equal(response.status(), 200); // Commit succeeded; only its response is lost.
80+
await route.abort("failed");
81+
} else await route.fulfill({response});
82+
});
83+
}});
84+
const {page} = context;
85+
async function open() {
86+
const navigation = page.getByRole("button", {name: "打开 Goal 导航", exact: true});
87+
if (await navigation.isVisible()) await navigation.click();
88+
await page.locator(".personal-goal-link", {hasText: "Multi Agent Projection"}).click();
89+
await page.getByRole("button", {name: "Goal 设置", exact: true}).click();
90+
await page.getByRole("button", {name: "任务所有权", exact: true}).click();
91+
return page.getByRole("region", {name: "Goal 数据存储"});
92+
}
93+
let panel = await open();
94+
await panel.getByText("1 项当前任务 · 1 项归档任务 · 0 项未结算 lease", {exact: true}).waitFor();
95+
await panel.getByRole("combobox", {name: "目标存储", exact: true}).selectOption(provider);
96+
await panel.getByRole("combobox", {name: "新策略", exact: true}).selectOption("hard_lease");
97+
await panel.getByRole("button", {name: "备份并预览导入", exact: true}).click();
98+
const apply = panel.getByRole("button", {name: "导入已审核的 Markdown 来源", exact: true});
99+
try { await panel.getByRole("checkbox").waitFor(); }
100+
catch (error) { throw new Error(`${error.message}; panel=${await panel.innerText()}`); }
101+
assert.ok(await apply.isDisabled());
102+
const saved = await page.evaluate(() => localStorage.getItem("loopx-storage-preview:multi-agent-projection"));
103+
assert.ok(saved && !saved.includes(authority.root) && !saved.includes("Private"));
104+
await page.reload({waitUntil: "networkidle"});
105+
panel = await open();
106+
await panel.getByRole("checkbox").waitFor();
107+
await panel.getByRole("status").filter({hasText: "已准备"}).waitFor();
108+
assert.ok(await panel.getByRole("button", {name: "导入已审核的 Markdown 来源", exact: true}).isDisabled());
109+
assert.equal(applyCount, 0, "reload observes the original prepared operation without applying");
110+
await page.setViewportSize({width: 390, height: 844});
111+
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth > innerWidth + 1), false);
112+
await panel.getByRole("checkbox").focus();
113+
await page.keyboard.press("Space");
114+
assert.equal(await panel.getByRole("checkbox").isChecked(), true);
115+
await panel.getByRole("button", {name: "导入已审核的 Markdown 来源", exact: true}).click();
116+
await panel.getByRole("alert").waitFor();
117+
assert.equal(applyCount, 1);
118+
assert.equal(await page.evaluate(() => localStorage.getItem("loopx-storage-preview:multi-agent-projection")), saved);
119+
await page.reload({waitUntil: "networkidle"});
120+
panel = await open();
121+
await panel.getByRole("status").filter({hasText: "原导入回执已核验"}).waitFor();
122+
assert.equal(applyCount, 1, "completed readback does not apply again");
123+
await panel.getByText(provider, {exact: true}).waitFor();
124+
assert.equal(await panel.getByRole("checkbox").count(), 0);
125+
assert.equal(await readFile(resolve(authority.root, "state.md"), "utf8"), authority.source);
126+
await panel.getByRole("status").filter({hasText: "原导入回执已核验"}).scrollIntoViewIfNeeded();
127+
await page.screenshot({path: resolve(outputDir, `goal-storage-import-${provider}-mobile.png`), animations: "disabled"});
128+
assert.equal(context.errors.filter(e => !e.includes("ERR_FAILED")).length, 0, context.errors.join("; "));
129+
} finally { await context?.close(); await authority.close(); }
130+
}
131+
69132
const goalStorageScenario = {
70133
id: "goal-storage",
71134
async run({browser, url}) {
@@ -90,8 +153,12 @@ const goalStorageScenario = {
90153
let panel = await open();
91154
await panel.getByText("1 项当前任务 · 1 项归档任务 · 1 项未结算 lease", {exact: true}).waitFor();
92155
assert.equal(await panel.getByRole("checkbox").count(), 0);
93-
assert.equal(await panel.getByRole("combobox").count(), 0);
94-
assert.ok((await panel.innerText()).includes("尚不能导入"));
156+
assert.equal(await panel.getByRole("combobox").count(), 2);
157+
assert.ok((await panel.innerText()).includes("此操作不会替你停止 Host"));
158+
await panel.getByRole("combobox", {name: "新策略", exact: true}).selectOption("soft_claim");
159+
await panel.getByRole("button", {name: "备份并预览导入", exact: true}).click();
160+
await panel.getByText("cold_import_lease_requires_settlement", {exact: true}).waitFor();
161+
assert.equal(await panel.getByRole("checkbox").count(), 0);
95162
assert.ok(!(await panel.innerText()).includes("Private"));
96163
await page.screenshot({path: resolve(outputDir, "goal-storage-cold-desktop.png"), animations: "disabled"});
97164
const outbox = resolve(authority.root, "runtime/authority-shadow/outbox/multi-agent-projection/todos");
@@ -116,13 +183,14 @@ const goalStorageScenario = {
116183
await page.reload({waitUntil: "networkidle"});
117184
panel = await open("en");
118185
await panel.getByText("1 active tasks · 1 archived tasks · 1 unsettled leases", {exact: true}).waitFor();
119-
assert.ok((await panel.innerText()).includes("Import is not available here yet"));
120-
assert.equal(writes, 0);
186+
assert.ok((await panel.innerText()).includes("This does not stop Hosts"));
187+
assert.equal(writes, 1, "only the explicit refused preview posts");
121188
assert.equal(await readFile(resolve(authority.root, "state.md"), "utf8"), authority.source);
122189
assert.equal(await readFile(residue, "utf8"), "{unrecognized original bytes");
123190
// Deliberately induced HTTP failures may be logged by the browser.
124-
assert.equal(context.errors.filter(e => !e.includes("503")).length, 0, context.errors.join("; "));
125-
return {note: "Cold/archived tasks, expired orphan lease, original outbox, unavailable source and fresh recovery; no import or write; packaged Chinese/English desktop/mobile."};
191+
assert.equal(context.errors.filter(e => !e.includes("503") && !e.includes("409")).length, 0, context.errors.join("; "));
192+
for (const provider of ["file", "sqlite"]) await importSettled(browser, url, provider);
193+
return {note: "Cold/archived tasks, expired orphan lease, original outbox, unavailable source and fresh recovery; refused expired lease; confirmed File/SQLite import, readonly reload and lost-response original receipt recovery; packaged Chinese/English desktop/mobile."};
126194
} finally { await context?.close(); await authority.close(); }
127195
},
128196
};

0 commit comments

Comments
 (0)