@@ -12,7 +12,7 @@ import {launchBrowser, loadPlaywright, waitForHttp} from "../dashboard-browser-s
1212import { openWorkspacePage } from "./scenario-context.mjs" ;
1313import { 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 ( / \/ a p i \/ c h a t \/ g o a l - ( s t o r a g e | o w n e r s h i p ) (?: [ / ? ] | $ ) / , 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+
69132const 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