[aidan] fix/app-selection: surface real app workspace path in selection so the builder edits in place

This commit is contained in:
abccodes
2026-06-26 03:36:58 -07:00
parent ab982afcea
commit 851330a5eb
5 changed files with 28 additions and 4 deletions
+13 -1
View File
@@ -1,8 +1,12 @@
from pydantic import BaseModel, Field, model_validator
import os
from pydantic import BaseModel, Field, computed_field, model_validator
from typing import Literal, Optional, Any
from uuid import uuid4
from datetime import datetime
from backend.config.paths import OUTPUTS_WORKSPACE_DIR
class Output(BaseModel):
id: str = Field(default_factory=lambda: uuid4().hex)
@@ -49,6 +53,14 @@ class Output(BaseModel):
data.pop("backend_code", None)
return data
@computed_field
@property
def workspace_path(self) -> str:
"""Absolute on-disk folder for this app, resolved from workspace_id. API-only (excluded from the saved JSON since it's machine-specific and re-derivable); lets the frontend show the real edit path when an App card is selected."""
if not self.workspace_id:
return ""
return os.path.abspath(os.path.join(OUTPUTS_WORKSPACE_DIR, self.workspace_id))
@property
def frontend_code(self) -> str:
return self.files.get("index.html", "")
+1 -1
View File
@@ -30,7 +30,7 @@ def load_all() -> list[Output]:
def save(output: Output):
atomic_write_json(os.path.join(DATA_DIR, f"{output.id}.json"), output.model_dump())
atomic_write_json(os.path.join(DATA_DIR, f"{output.id}.json"), output.model_dump(exclude={"workspace_path"}))
def load(output_id: str) -> Output:
@@ -79,13 +79,23 @@ export function appendSelectedElements(trimmed: string, selectedEls: SelectedEle
lines.push(` browser_id: ${el.semanticData.selectId}`);
if (url) lines.push(` URL: ${url}`);
lines.push(` (Use BrowserAgent with this browser_id to interact with it, or CreateBrowserAgent for a new browser)`);
} else if (el.semanticType === 'view-card') {
const appName = typeof el.semanticData?.name === 'string' ? el.semanticData.name : (el.semanticLabel || 'Untitled App');
const appPath = typeof el.semanticData?.path === 'string' ? el.semanticData.path : '';
lines.push(`${i + 1}. [App Card] ${appName}`);
if (appPath) {
lines.push(` Workspace path: ${appPath}`);
lines.push(` (Edit this app's files in place at the path above. Don't recreate it or write files elsewhere.)`);
} else {
// No resolved path means the app's folder is gone or unsaved; tell the agent rather than emit a confusing opaque id.
lines.push(` (This app has no on-disk workspace yet; ask the user to save or reopen it before editing.)`);
}
} else if (el.semanticType && el.semanticData) {
const typeLabel = {
'agent-card': 'Agent Card',
'message': 'Message',
'tool-call': 'Tool Call',
'tool-group': 'Tool Group',
'view-card': 'App Card',
'browser-card': 'Browser Card',
'workflow-card': 'Workflow Card',
'workflows-hub-card': 'Workflows Hub',
@@ -342,7 +342,7 @@ const DashboardViewCard: React.FC<Props> = ({
<Box
data-select-type="view-card"
data-select-id={output.id}
data-select-meta={JSON.stringify({ name: output.name, description: output.description })}
data-select-meta={JSON.stringify({ name: output.name, description: output.description, path: output.workspace_path })}
onPointerDownCapture={() => onBringToFront?.(output.id, 'view')}
onClick={(e: React.MouseEvent) => {
if (justDraggedRef.current) return;
@@ -20,6 +20,8 @@ export interface Output {
/** Linkage so reopening App Builder reattaches to the in-progress session and workspace. */
session_id?: string | null;
workspace_id?: string | null;
/** Backend-resolved absolute on-disk folder for this app; API-only, empty when no workspace_id. */
workspace_path?: string;
created_at: string;
updated_at: string;
/** App publishing to {slug}.openswarm.host. Server-managed; mirrored here after a publish/unpublish. */