Skip to content
Open
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
63 changes: 62 additions & 1 deletion apps/app/src/app/lib/desktop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ import type {
WorkspaceList,
} from "./desktop-types";
import type { BrowserPanelTab } from "./desktop-types";
import type {
ScheduledTask,
ScheduledTaskCreateInput,
ScheduledTaskLogEntry,
ScheduledTaskUpdatePatch,
} from "@/react-app/domains/session/scheduled-tasks/scheduled-task";

export const LOCAL_IMAGE_FILE_EXTENSIONS = ["avif", "bmp", "gif", "ico", "jpeg", "jpg", "png", "svg", "webp"];
export const LOCAL_IMAGE_FILE_FILTERS = [{ name: "图片文件", extensions: LOCAL_IMAGE_FILE_EXTENSIONS }];
Expand All @@ -53,6 +59,17 @@ export type BrowserProxyState = {
proxy: { rules: string; authenticated: boolean } | null;
};

export type LanPreviewState = {
enabled: boolean;
port: number;
addresses: string[];
code: string | null;
codeExpiresAt: number;
sessionCount: number;
pendingChallengeCount?: number;
error?: string;
};

// ---------------------------------------------------------------------------
// Electron bridge surface
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -154,13 +171,51 @@ declare global {
onPanelClosed?: (callback: () => void) => () => void;
};
terminal?: {
create?: (options: { cwd: string; cols: number; rows: number }) => Promise<{ terminalId: string }>;
create?: (options: {
cwd: string;
cols: number;
rows: number;
/** Optional argv for the shell. When present the shell runs this
command instead of opening an interactive prompt, e.g.
`["ssh", "user@host"]` for the ops panel. */
command?: string[];
/** Optional explicit shell/executable path. */
shell?: string;
}) => Promise<{ terminalId: string }>;
write?: (terminalId: string, data: string) => Promise<void>;
resize?: (terminalId: string, cols: number, rows: number) => Promise<void>;
kill?: (terminalId: string) => Promise<void>;
onData?: (callback: (payload: { terminalId: string; data: string }) => void) => () => void;
onExit?: (callback: (payload: { terminalId: string; exitCode: number | null; signal?: number }) => void) => () => void;
};
ssh?: {
listHosts?: () => Promise<{ hosts: string[]; configPath: string }>;
};
git?: {
graph?: (options: { cwd: string; maxCommits?: number }) => Promise<
| { ok: true; repoRoot: string; count: number; totalCount: number | null; truncated: boolean; isRepo: true; commits: { sha: string; parents: string[] }[]; refs: { sha: string; refname: string; head: boolean }[]; headShas: string[] }
| { ok: false; isRepo: boolean; error: string }
>;
};
scheduledTasks?: {
list?: () => Promise<ScheduledTask[]>;
create?: (input: ScheduledTaskCreateInput) => Promise<ScheduledTask>;
update?: (id: string, patch: ScheduledTaskUpdatePatch) => Promise<ScheduledTask | null>;
setEnabled?: (id: string, enabled: boolean) => Promise<ScheduledTask | null>;
remove?: (id: string) => Promise<boolean>;
runNow?: (id: string) => Promise<ScheduledTask | null>;
logs?: (id: string) => Promise<ScheduledTaskLogEntry[]>;
preview?: (cron: string) => Promise<{ valid: boolean; nextRunAt: number | null }>;
onChanged?: (callback: (payload: { type: string; taskId?: string }) => void) => () => void;
};
lanPreview?: {
getState?: () => Promise<LanPreviewState>;
setEnabled?: (enabled: boolean) => Promise<LanPreviewState>;
regenerateCode?: () => Promise<LanPreviewState>;
disconnectAll?: () => Promise<LanPreviewState>;
pushToIm?: (options: { mcpUrl: string }) => Promise<{ ok: boolean; tool?: string; error?: string }>;
onStateChanged?: (callback: (state: LanPreviewState) => void) => () => void;
};
hyperframes?: {
start?: (options: { workspaceRoot: string; sessionId: string; projectDirectory: string; port: number }) => Promise<{ ok: boolean; port?: number; reused?: boolean }>;
stop?: (sessionId: string, options?: { keepWarm?: boolean }) => Promise<{ ok: boolean }>;
Expand Down Expand Up @@ -576,6 +631,9 @@ const {
updaterEnvironment,
readOpencodeConfig,
writeOpencodeConfig,
routerGatewayGetConfig,
routerGatewayWriteConfig,
routerGatewayStatus,
resetiPolloWorkState,
resetOpencodeCache,
opencodeMcpAuth,
Expand Down Expand Up @@ -644,6 +702,9 @@ export {
updaterEnvironment,
readOpencodeConfig,
writeOpencodeConfig,
routerGatewayGetConfig,
routerGatewayWriteConfig,
routerGatewayStatus,
resetiPolloWorkState,
resetOpencodeCache,
opencodeMcpAuth,
Expand Down
1 change: 1 addition & 0 deletions apps/app/src/app/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ export const SETTINGS_TAB_VALUES = [
"updates",
"recovery",
"debug",
"remote-preview",
] as const;

export type SettingsTab = (typeof SETTINGS_TAB_VALUES)[number];
Expand Down
66 changes: 63 additions & 3 deletions apps/app/src/i18n/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -752,6 +752,7 @@ export default {
"find.no_matches": "No matches",
"find.previous_match": "Previous match",
"find.previous_match_hint": "Previous match (⇧↵)",
"git.title": "Git",
"help.all_categories": "All categories",
"help.back": "Back to Work",
"help.clear_search": "Clear search",
Expand Down Expand Up @@ -855,7 +856,7 @@ export default {
"mcp.enable_app": "Enable",
"mcp.env_modal_cancel": "Cancel",
"mcp.env_modal_desc": "These credentials are stored in this device's encrypted env store and never uploaded. Connection continues automatically after saving.",
"mcp.env_modal_open_console": "Open platform console (create app)",
"mcp.env_modal_open_console": "Open platform console (create app)",
"mcp.env_modal_placeholder": "Paste the value",
"mcp.env_modal_save": "Save & connect",
"mcp.env_modal_save_failed": "Save failed. Check the values and try again.",
Expand All @@ -878,7 +879,7 @@ export default {
"mcp.logout_modal_title": "Log out of this app?",
"mcp.logout_success": "Logged out of {server}.",
"mcp.logout_working": "Logging out...",
"mcp.missing_env": "Set these environment variables first: {vars}. Add them in Settings → Environment Variables, then retry.",
"mcp.missing_env": "Set these environment variables first: {vars}. Add them in Settings → Environment Variables, then retry.",
"mcp.name_required": "Enter a server name.",
"mcp.no_apps_hint": "Connect one above to get started.",
"mcp.no_apps_yet": "No apps connected yet",
Expand Down Expand Up @@ -1208,6 +1209,7 @@ export default {
"notifications.view": "View",
"onboarding.authorize_folder": "Authorize folder",
"onboarding.installing_resources": "Installing required resources\u2026",
"ops.title": "Ops",
"plugin_library.add": "Add",
"plugin_library.category.ai-agents": "AI Agents & Automation",
"plugin_library.category.business-operations": "Business & Operations",
Expand Down Expand Up @@ -1375,6 +1377,7 @@ export default {
"remote_workspace.optional_placeholder": "Optional",
"remote_workspace.worker_url": "Worker URL",
"remote_workspace.worker_url_hint": "Paste the iPolloWork worker URL you want to connect to.",
"scheduled_tasks.title": "Scheduled Tasks",
"session.add_provider_hint": "Add a provider to run tasks.",
"session.allow_for_session": "Always allow",
"session.allow_once": "Allow once",
Expand Down Expand Up @@ -2183,6 +2186,35 @@ export default {
"settings.recovery_reset_config_unavailable": "Reset config is not yet available.",
"settings.recovery_section_desc": "Roll back state without quitting iPolloWork.",
"settings.recovery_section_title": "Reset & recovery",
"settings.remote_preview_address_desc": "Open this address in a phone or tablet browser on the same network.",
"settings.remote_preview_address_title": "Access address",
"settings.remote_preview_alert_desc": "Lets phones and tablets on your local network view the current workbench state through a browser. Read-only: devices can watch, not control.",
"settings.remote_preview_alert_title": "Remote preview (LAN read-only)",
"settings.remote_preview_code_desc": "Valid for 10 minutes, single use",
"settings.remote_preview_code_hint": "Enter this code on the device to pair it with this workbench.",
"settings.remote_preview_code_title": "Pair code",
"settings.remote_preview_copy": "Copy",
"settings.remote_preview_desktop_desc": "Remote preview is only available in the desktop app.",
"settings.remote_preview_desktop_title": "Desktop app required",
"settings.remote_preview_disconnect_all": "Disconnect all",
"settings.remote_preview_enable_desc": "Defaults to off. Turning it on exposes a read-only snapshot to your local network.",
"settings.remote_preview_enable_title": "Enable LAN preview",
"settings.remote_preview_error_title": "Could not start remote preview",
"settings.remote_preview_im_desc": "Send the current workbench status to a DingTalk/Feishu group via its MCP endpoint.",
"settings.remote_preview_im_endpoint_label": "DingTalk MCP endpoint URL",
"settings.remote_preview_im_endpoint_placeholder": "https://… DingTalk MCP endpoint (dws mcp url get <mcpId>)",
"settings.remote_preview_im_failed": "Could not send the preview summary.",
"settings.remote_preview_im_hint": "Get the endpoint from `dws mcp url get <mcpId>`. Only a redacted status summary is sent; no session tokens.",
"settings.remote_preview_im_need_endpoint": "Enter a DingTalk MCP endpoint first.",
"settings.remote_preview_im_push": "Push summary",
"settings.remote_preview_im_sent": "Preview summary sent.",
"settings.remote_preview_im_title": "Push preview to IM",
"settings.remote_preview_im_unavailable": "IM push is only available in the desktop app.",
"settings.remote_preview_no_address": "No LAN addresses found.",
"settings.remote_preview_regenerate": "Regenerate",
"settings.remote_preview_sessions_connected": "devices connected",
"settings.remote_preview_sessions_none": "No devices paired.",
"settings.remote_preview_sessions_title": "Paired devices",
"settings.removing_containers": "Removing containers...",
"settings.removing_local_state": "Removing local state...",
"settings.repair_cache": "Repair cache",
Expand Down Expand Up @@ -2223,6 +2255,32 @@ export default {
"settings.restarting": "Restarting…",
"settings.reveal_config": "Show file",
"settings.reveal_config_requires_desktop": "Only available in the desktop app",
"settings.router_gateway.add_model": "Add escalation model",
"settings.router_gateway.api_key": "API key",
"settings.router_gateway.api_key_env": "API key env var name",
"settings.router_gateway.api_key_optional": "Optional; leave empty to use the env var",
"settings.router_gateway.base_url": "Base URL",
"settings.router_gateway.config_path": "Config file: {path}",
"settings.router_gateway.context_limit": "Context limit (tokens)",
"settings.router_gateway.debug": "Debug logging",
"settings.router_gateway.description": "Local smart routing gateway: requests go to the primary model by default, and switch to an escalation model after the context is repeatedly compacted past the limit. Saving takes effect immediately.",
"settings.router_gateway.desktop_only": "Smart routing gateway config is only available on desktop.",
"settings.router_gateway.escalation_desc": "After the session context is compacted (compaction) up to the limit, subsequent requests switch to the first escalation model in the list.",
"settings.router_gateway.escalation_label": "Enable auto escalation",
"settings.router_gateway.escalation_model": "Escalation model #{index}",
"settings.router_gateway.loading": "Loading gateway config...",
"settings.router_gateway.max_compactions": "Compaction limit",
"settings.router_gateway.merge_system": "Merge system messages to front",
"settings.router_gateway.model_name": "Model name",
"settings.router_gateway.not_running": "Gateway not running",
"settings.router_gateway.primary_label": "Primary model",
"settings.router_gateway.primary_model": "Primary model: {model}",
"settings.router_gateway.refresh": "Refresh",
"settings.router_gateway.remove_model": "Remove",
"settings.router_gateway.running": "Running",
"settings.router_gateway.saved": "Routing gateway config saved",
"settings.router_gateway.saving": "Saving...",
"settings.router_gateway.title": "Smart Routing Gateway",
"settings.run_sandbox_probe": "Running probe...",
"settings.running_probe": "Running probe...",
"settings.runtime_config.agents_plugins": "Agents / plugins",
Expand Down Expand Up @@ -2393,6 +2451,7 @@ export default {
"settings.tab_description_pet": "An always-on desktop companion: progress reminders, pending decisions, and chat.",
"settings.tab_description_preferences": "Default model, reasoning, and compaction",
"settings.tab_description_recovery": "Fix common issues, reset workspace settings, or clean up leftover data.",
"settings.tab_description_remote_preview": "Allow LAN devices to view this workbench (read-only).",
"settings.tab_description_shell": "Branding, visibility, and shell controls",
"settings.tab_description_skills": "Browse, edit, and install skills without leaving settings.",
"settings.tab_description_updates": "Keep the app current with quiet background checks and install controls.",
Expand All @@ -2403,6 +2462,7 @@ export default {
"settings.tab_pet": "Desktop Pet",
"settings.tab_preferences": "Preferences",
"settings.tab_recovery": "Recovery",
"settings.tab_remote_preview": "Remote Preview",
"settings.tab_shell": "Customization",
"settings.tab_skills": "Skills",
"settings.tab_updates": "Updates",
Expand Down Expand Up @@ -3074,4 +3134,4 @@ export default {
"workspace_list.test_connection": "Test connection",
"workspace_list.unavailable": "Unavailable",
"workspace_list.workspace_fallback": "Workspace",
} as const;
} as const;
Loading
Loading