@@ -110,6 +110,13 @@ import {
110110 type SourceConnectorId ,
111111 type SourceSetupState ,
112112} from "./source-setup" ;
113+ import {
114+ invokePortableWorkspace ,
115+ portableWorkspaceSuccessMessage ,
116+ validatePortableWorkspaceForm ,
117+ workspaceWorkflowCommand ,
118+ type PortableWorkspaceReport ,
119+ } from "./portable-workspace" ;
113120import gmailIconUrl from "./assets/connectors/gmail.svg" ;
114121import googleCalendarIconUrl from "./assets/connectors/google-calendar.svg" ;
115122import googleDocsIconUrl from "./assets/connectors/google-docs.svg" ;
@@ -131,7 +138,7 @@ type OnboardingConnectorId = SourceConnectorId;
131138type ReviewFilter = "all" | "approvals" | "problems" ;
132139type FileStatusFilter = "all" | "review" | "conflict" | "synced" ;
133140type DestructiveSettingsAction = "reset" | "uninstall" ;
134- type SettingsSection = "general" | "sources" | "sync" | "activity" | "agents" | "advanced" | "about" ;
141+ type SettingsSection = "general" | "sources" | "hosted" | " sync" | "activity" | "agents" | "advanced" | "about" ;
135142type SourceListViewMode = "list" | "tiles" ;
136143type AppTheme = "system" | "light" | "dark" ;
137144
@@ -3108,7 +3115,7 @@ function HomeView({
31083115 async function createMount ( ) {
31093116 setActionError ( "" ) ;
31103117 const report = await callCommand < ActionReport > (
3111- "create_workspace_mount" ,
3118+ workspaceWorkflowCommand ( "local" ) ,
31123119 { path : snapshot . mount . localPath } ,
31133120 { ok : true , message : "Created demo mount." } ,
31143121 ) ;
@@ -5954,6 +5961,13 @@ function SettingsView({
59545961 const [ settingsSection , setSettingsSection ] = useState < SettingsSection > ( "general" ) ;
59555962 const [ busySetting , setBusySetting ] = useState ( "" ) ;
59565963 const [ localSettings , setLocalSettings ] = useState ( snapshot . settings ) ;
5964+ const [ portableApiUrl , setPortableApiUrl ] = useState ( "" ) ;
5965+ const [ portableRoot , setPortableRoot ] = useState ( "" ) ;
5966+ const [ portableProfileKey , setPortableProfileKey ] = useState ( "" ) ;
5967+ const [ portableWorkspaceState , setPortableWorkspaceState ] = useState <
5968+ "idle" | "materializing" | "success" | "error"
5969+ > ( "idle" ) ;
5970+ const [ portableWorkspaceMessage , setPortableWorkspaceMessage ] = useState ( "" ) ;
59575971 const daemonStopped = snapshot . health . state === "stopped" ;
59585972 const runtimeStopped = snapshot . health . state === "runtime_stopped" ;
59595973 const runtimeNeedsRepair = daemonStopped || runtimeStopped ;
@@ -6131,9 +6145,51 @@ function SettingsView({
61316145 }
61326146 }
61336147
6148+ async function materializePortableWorkspace ( ) {
6149+ if ( portableWorkspaceState === "materializing" ) {
6150+ return ;
6151+ }
6152+ const validation = validatePortableWorkspaceForm ( {
6153+ apiUrl : portableApiUrl ,
6154+ root : portableRoot ,
6155+ profileKey : portableProfileKey ,
6156+ } ) ;
6157+ if ( ! validation . ok ) {
6158+ setPortableWorkspaceState ( "error" ) ;
6159+ setPortableWorkspaceMessage ( validation . message ) ;
6160+ return ;
6161+ }
6162+ setPortableWorkspaceState ( "materializing" ) ;
6163+ setPortableWorkspaceMessage ( "Negotiating and materializing the hosted workspace…" ) ;
6164+ try {
6165+ const fallback : PortableWorkspaceReport = {
6166+ ok : true ,
6167+ root : validation . request . root ,
6168+ session_id : "demo-session" ,
6169+ content_encoding : "identity" ,
6170+ entries : 0 ,
6171+ files : 0 ,
6172+ directories : 0 ,
6173+ materialized_bytes : 0 ,
6174+ decoded_bytes : 0 ,
6175+ } ;
6176+ const report = await invokePortableWorkspace (
6177+ ( command , args ) => callCommand < PortableWorkspaceReport > ( command , args , fallback ) ,
6178+ validation . request ,
6179+ ) ;
6180+ setPortableProfileKey ( "" ) ;
6181+ setPortableWorkspaceState ( "success" ) ;
6182+ setPortableWorkspaceMessage ( portableWorkspaceSuccessMessage ( report ) ) ;
6183+ } catch ( error ) {
6184+ setPortableWorkspaceState ( "error" ) ;
6185+ setPortableWorkspaceMessage ( errorMessage ( error ) ) ;
6186+ }
6187+ }
6188+
61346189 const settingsSections : Array < { id : SettingsSection ; label : string ; description : string } > = [
61356190 { id : "general" , label : "General" , description : "Startup and desktop behavior" } ,
61366191 { id : "sources" , label : "Sources" , description : "Connected workspaces and local folders" } ,
6192+ { id : "hosted" , label : "Hosted (preview)" , description : "Manual portable materializer" } ,
61376193 { id : "sync" , label : "Sync" , description : "Live Mode and review policy" } ,
61386194 { id : "activity" , label : "Activity" , description : "Recent events and debug queue" } ,
61396195 { id : "agents" , label : "Agents" , description : "Local agent instructions" } ,
@@ -6217,6 +6273,68 @@ function SettingsView({
62176273 </ section >
62186274 ) }
62196275
6276+ { settingsSection === "hosted" && (
6277+ < section className = "panel settings-section-panel portable-workspace-panel" >
6278+ < PanelTitle title = "Hosted materializer preview" />
6279+ < p className = "quiet-note" >
6280+ Manually materialize or recover a generation-2 workspace. This preview is not yet bound to Desktop sources, mounts, or stored credentials.
6281+ </ p >
6282+ < div className = "portable-workspace-fields" >
6283+ < label className = "source-inline-field" >
6284+ < span > Workspace API URL</ span >
6285+ < input
6286+ type = "url"
6287+ value = { portableApiUrl }
6288+ placeholder = "https://workspace.example.com"
6289+ disabled = { portableWorkspaceState === "materializing" }
6290+ onChange = { ( event ) => setPortableApiUrl ( event . target . value ) }
6291+ />
6292+ </ label >
6293+ < label className = "source-inline-field" >
6294+ < span > Local workspace root</ span >
6295+ < input
6296+ value = { portableRoot }
6297+ placeholder = "/mnt/locality"
6298+ disabled = { portableWorkspaceState === "materializing" }
6299+ onChange = { ( event ) => setPortableRoot ( event . target . value ) }
6300+ />
6301+ </ label >
6302+ < label className = "source-inline-field portable-workspace-key-field" >
6303+ < span > Workspace Profile key</ span >
6304+ < input
6305+ type = "password"
6306+ autoComplete = "off"
6307+ value = { portableProfileKey }
6308+ placeholder = "64-character key"
6309+ disabled = { portableWorkspaceState === "materializing" }
6310+ onChange = { ( event ) => setPortableProfileKey ( event . target . value ) }
6311+ onKeyDown = { ( event ) => {
6312+ if ( event . key === "Enter" ) {
6313+ void materializePortableWorkspace ( ) ;
6314+ }
6315+ } }
6316+ />
6317+ </ label >
6318+ </ div >
6319+ < PrimaryButton
6320+ compact
6321+ icon = { portableWorkspaceState === "materializing" ? < Loader2 className = "spin-icon" /> : < Download /> }
6322+ disabled = { portableWorkspaceState === "materializing" }
6323+ onClick = { ( ) => void materializePortableWorkspace ( ) }
6324+ >
6325+ { portableWorkspaceState === "materializing" ? "Materializing" : "Materialize Workspace" }
6326+ </ PrimaryButton >
6327+ { portableWorkspaceMessage && (
6328+ < p
6329+ className = { portableWorkspaceState === "error" ? "field-error" : "quiet-note inline-note" }
6330+ role = { portableWorkspaceState === "error" ? "alert" : "status" }
6331+ >
6332+ { portableWorkspaceMessage }
6333+ </ p >
6334+ ) }
6335+ </ section >
6336+ ) }
6337+
62206338 { settingsSection === "sync" && (
62216339 < section className = "panel settings-section-panel" >
62226340 < PanelTitle title = "Sync and review policy" />
0 commit comments