Skip to content
This repository was archived by the owner on Jun 8, 2026. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "desktop",
"version": "0.15.102",
"version": "0.15.103",
"description": "ClosedLoop Desktop",
"author": "ClosedLoop AI <support@closedloop.ai>",
"private": true,
Expand Down
79 changes: 78 additions & 1 deletion apps/desktop/src/main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { SettingsStore, shouldShowManagedKeyHint, type SavedConfigManagedPatch }
import { DesktopTray } from "./tray.js";
import { DesktopWindow } from "./window.js";
import { AgentMonitorSidecar } from "./agent-monitor-sidecar.js";
import { SymphonyWebPocRuntime } from "./symphony-web-poc-runtime.js";
import { AgentSessionSyncService } from "./agent-session-sync-service.js";
import {
isAgentMonitorHooksEnabled,
Expand Down Expand Up @@ -204,6 +205,7 @@ export class DesktopApplication {
private readonly cloudSocket: CloudSocketService;
private readonly commandExecutor: CloudCommandExecutor;
private readonly agentMonitor: AgentMonitorSidecar;
private readonly symphonyWebPoc: SymphonyWebPocRuntime;
private readonly agentSessionSync: AgentSessionSyncService;
private readonly activityLog: ActivityLogStore;
private readonly approvalStore: ApprovalStore;
Expand Down Expand Up @@ -317,6 +319,10 @@ export class DesktopApplication {
this.agentMonitor.setSandboxBaseDirectory(
this.settingsStore.getSandboxBaseDirectory(),
);
this.symphonyWebPoc = new SymphonyWebPocRuntime({
dataDir: path.join(app.getPath("userData"), "symphony-web-poc"),
appDirCandidates: getSymphonyWebPocAppDirCandidates(),
});
this.activityLog = new ActivityLogStore();
this.jobStore = new JobStore();
this.approvalStore = new ApprovalStore({
Expand Down Expand Up @@ -670,6 +676,9 @@ export class DesktopApplication {
syncAgentMonitorHooksOnBoot();
this.agentSessionSync.start();
}
if (this.settingsStore.getSymphonyWebPocEnabled()) {
void this.symphonyWebPoc.start();
}

try {
await this.server.start();
Expand Down Expand Up @@ -1266,6 +1275,10 @@ export class DesktopApplication {
return this.settingsStore.getPlanExtractionEnabled();
}

private isSymphonyWebPocEnabled(): boolean {
return this.settingsStore.getSymphonyWebPocEnabled();
}

private async applyAgentMonitorSetting(enabled: boolean): Promise<void> {
this.tray.setAgentMonitorEnabled(enabled);

Expand Down Expand Up @@ -1299,6 +1312,20 @@ export class DesktopApplication {
?.webContents.send("desktop:navigate-settings-tab", "relay-gateway");
}

private async applySymphonyWebPocSetting(enabled: boolean): Promise<void> {
if (enabled) {
void this.symphonyWebPoc.start();
return;
}
await this.symphonyWebPoc.stop();
this.desktopWindow
.getWindow()
?.webContents.send("desktop:navigate-tab", "settings");
this.desktopWindow
.getWindow()
?.webContents.send("desktop:navigate-settings-tab", "feature-flags");
}

openClaudeDashboard(): void {
this.desktopWindow.show();
if (!this.isAgentMonitorEnabled()) {
Expand Down Expand Up @@ -1745,7 +1772,12 @@ export class DesktopApplication {
},
cloudSocket: this.cloudSocket,
commandExecutor: this.commandExecutor,
agentMonitor: this.agentMonitor,
agentMonitor: {
stop: async () => {
await this.symphonyWebPoc.stop();
await this.agentMonitor.stop();
},
},
server: this.server,
desktopWindow: this.desktopWindow,
tray: this.tray,
Expand Down Expand Up @@ -2462,6 +2494,20 @@ export class DesktopApplication {
enabled: this.isAgentMonitorEnabled(),
planExtractionEnabled: this.isPlanExtractionEnabled(),
}));
ipcMain.handle("desktop:get-symphony-web-poc-status", () =>
this.symphonyWebPoc.getStatus(this.isSymphonyWebPocEnabled()),
);
ipcMain.handle("desktop:restart-symphony-web-poc", async () => {
if (!this.isSymphonyWebPocEnabled()) {
return {
ok: false,
error: "Symphony Web POC is disabled in Settings.",
};
}
await this.symphonyWebPoc.stop();
void this.symphonyWebPoc.start();
return { ok: true };
});
// FEA-1334: proxy the Agent Monitor cold-start ingest progress so the renderer
// can drive the floating progress card without a cross-origin fetch.
// Returns null whenever the Agent Monitor runtime is not reachable — the renderer treats
Expand Down Expand Up @@ -2554,8 +2600,15 @@ export class DesktopApplication {
if (activeAlwaysAllowRules.length !== settings.alwaysAllowRules.length) {
this.settingsStore.setAlwaysAllowRules(activeAlwaysAllowRules);
}
const effectiveFlags = Object.fromEntries(
FEATURE_FLAGS.map((flag) => [
flag.key,
this.settingsStore.getFlag(flag.key as FlagKey),
]),
);
return {
...settings,
...effectiveFlags,
alwaysAllowRules: activeAlwaysAllowRules,
};
});
Expand Down Expand Up @@ -2663,6 +2716,12 @@ export class DesktopApplication {
) {
await this.applyAgentMonitorSetting(nextPartial.agentMonitorEnabled);
}
if (
typeof nextPartial.symphonyWebPocEnabled === "boolean" &&
nextPartial.symphonyWebPocEnabled !== currentSettings.symphonyWebPocEnabled
) {
await this.applySymphonyWebPocSetting(nextPartial.symphonyWebPocEnabled);
}
if (
typeof nextPartial.cloudCommandsPaused === "boolean" &&
nextPartial.cloudCommandsPaused !== this.cloudCommandsPaused
Expand Down Expand Up @@ -2714,6 +2773,9 @@ export class DesktopApplication {
sandboxBaseDirectory: this.settingsStore.getSandboxBaseDirectory(),
commandsPaused: this.cloudCommandsPaused,
connectionEnabled: this.cloudConnectionEnabled,
symphonyWebPoc: this.symphonyWebPoc.getStatus(
this.isSymphonyWebPocEnabled(),
),
connectionSecurity: this.getConnectionSecurityStatus(),
commandSigning: {
serverSupported: this.serverCommandSigningSupported,
Expand Down Expand Up @@ -3616,6 +3678,21 @@ function maybeString(value: unknown): string | null {
return value.trim();
}

function getSymphonyWebPocAppDirCandidates(): string[] {
const roots = [
process.cwd(),
app.getAppPath(),
path.join(os.homedir(), "ClaudeCode"),
];
const candidates = roots.flatMap((root) => [
path.resolve(root, "symphony-alpha/apps/app"),
path.resolve(root, "../symphony-alpha/apps/app"),
path.resolve(root, "../../symphony-alpha/apps/app"),
path.resolve(root, "../../../symphony-alpha/apps/app"),
]);
return Array.from(new Set(candidates));
}

/**
* Builds the state to persist when the user dismisses the managed-key hint.
* Extracted as a standalone function to keep the ipcMain callback thin and
Expand Down
22 changes: 22 additions & 0 deletions apps/desktop/src/main/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,28 @@ const desktopApi = {
enabled: boolean;
planExtractionEnabled: boolean;
}>,
getSymphonyWebPocStatus: () =>
ipcRenderer.invoke("desktop:get-symphony-web-poc-status") as Promise<{
enabled: boolean;
ready: boolean;
mode: string | null;
url: string | null;
apiUrl: string | null;
apiToken: string | null;
dbPath: string | null;
error: string | null;
source: string | null;
counts: {
projects: number;
workstreams: number;
documents: number;
};
}>,
restartSymphonyWebPoc: () =>
ipcRenderer.invoke("desktop:restart-symphony-web-poc") as Promise<{
ok: boolean;
error?: string;
}>,
openAgentMonitor: () =>
ipcRenderer.invoke("desktop:open-agent-monitor") as Promise<unknown>,
getAgentMonitorHooksEnabled: () =>
Expand Down
8 changes: 8 additions & 0 deletions apps/desktop/src/main/settings-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,10 @@ export class SettingsStore {
return this.getFlag("planExtractionEnabled");
}

getSymphonyWebPocEnabled(): boolean {
return this.getFlag("symphonyWebPocEnabled");
}

getCommandSigningEnforcementEnabled(): boolean {
return this.getFlag("commandSigningEnforcementEnabled");
}
Expand Down Expand Up @@ -313,6 +317,10 @@ export class SettingsStore {
this.setFlag("planExtractionEnabled", planExtractionEnabled);
}

setSymphonyWebPocEnabled(symphonyWebPocEnabled: boolean): void {
this.setFlag("symphonyWebPocEnabled", symphonyWebPocEnabled);
}

setCommandSigningEnforcementEnabled(commandSigningEnforcementEnabled: boolean): void {
this.setFlag("commandSigningEnforcementEnabled", commandSigningEnforcementEnabled);
}
Expand Down
Loading
Loading