From 6f1de8047e840bde751e0e211fad4fd95eecf91e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Mon, 30 Mar 2026 00:15:21 -0400 Subject: [PATCH] update --- .gitignore | 1 + build/release/builder-debug.yml | 46 +- .../win-arm64-unpacked/resources/app.asar | Bin 64516861 -> 64544272 bytes package.json | 2 +- src/main/database.ts | 21 +- src/main/ipc-core-admin-handlers.ts | 8 + src/main/ipc-core-read-handlers.ts | 9 + src/main/main-runtime-support.ts | 8 +- src/preload/index.ts | 2 + src/renderer/App.tsx | 50 +- .../ClaudeSettingsDialog.tsx | 15 +- .../CompanyOnboarding/CompanyStep.tsx | 2 +- .../CompanyOnboarding/ConnectorReadiness.tsx | 6 +- .../CompanyOnboarding/LaunchStep.tsx | 4 +- .../CompanyOnboarding/MissionStep.tsx | 2 +- .../CompanyOnboarding/NodeCheckStep.tsx | 4 +- .../CompanyOnboarding/OnboardingHero.tsx | 4 +- .../CompanyOnboarding/ProvidersStep.tsx | 14 +- src/renderer/components/Toast.tsx | 2 +- .../components/automation/AutomationPanel.tsx | 12 +- .../components/automation/RuleCard.tsx | 2 +- .../components/automation/RuleEditor.tsx | 10 +- .../components/automation/WorkflowEditor.tsx | 8 +- .../components/automation/WorkflowList.tsx | 6 +- .../communication/MessageComposer.tsx | 8 +- .../components/communication/MessageItem.tsx | 2 +- .../communication/SearchResults.tsx | 2 +- src/renderer/components/connectors.tsx | 2 +- .../components/dashboard/ActivityFeed.tsx | 4 +- .../dashboard/BudgetAlertBanner.tsx | 12 +- .../components/dashboard/Dashboard.tsx | 42 +- .../components/dashboard/KanbanBoard.tsx | 14 +- .../components/dashboard/MetricsGrid.tsx | 16 +- .../components/dashboard/RecoveryBanner.tsx | 6 +- .../dashboard/charts/BudgetUtilization.tsx | 6 +- .../components/execution/ActivityList.tsx | 2 +- .../execution/ApprovalInspector.tsx | 6 +- .../components/execution/ApprovalList.tsx | 4 +- .../components/execution/BudgetSummary.tsx | 16 +- .../components/execution/CostList.tsx | 2 +- .../components/execution/IncidentList.tsx | 8 +- .../components/execution/ModelManagement.tsx | 907 +++++++++++++++--- .../components/execution/PolicyList.tsx | 4 +- .../components/execution/SettingsPane.tsx | 17 + .../execution/SocialDraftPreview.tsx | 2 +- .../components/execution/TaskComments.tsx | 2 +- .../components/execution/TaskCreator.tsx | 23 +- .../components/execution/TaskInspector.tsx | 16 +- .../components/execution/execution-utils.tsx | 10 +- .../components/hiring/HiringPipeline.tsx | 10 +- .../components/hiring/hiring-helpers.ts | 10 +- .../components/inbox/PerformanceView.tsx | 8 +- src/renderer/components/inbox/StandupView.tsx | 18 +- .../components/inbox/inbox-helpers.tsx | 6 +- .../components/organization/AgentCreator.tsx | 72 +- .../components/organization/AgentEditor.tsx | 2 +- .../components/organization/OrgChart.tsx | 10 +- .../components/organization/helpers.tsx | 6 +- src/renderer/components/runs/RunDetail.tsx | 4 +- .../components/social/AccountCard.tsx | 14 +- .../components/social/ActionHistory.tsx | 10 +- src/renderer/components/ui.tsx | 157 +-- src/renderer/styles.css | 77 +- src/shared/contracts.ts | 9 + 64 files changed, 1269 insertions(+), 515 deletions(-) diff --git a/.gitignore b/.gitignore index 4fbdb4c1..e9bba417 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ win-unpacked .blockmap .claude build +.omc # Test artifacts test-results/ diff --git a/build/release/builder-debug.yml b/build/release/builder-debug.yml index 5027d739..1121a82e 100644 --- a/build/release/builder-debug.yml +++ b/build/release/builder-debug.yml @@ -1,25 +1,3 @@ -x64: - firstOrDefaultFilePatterns: - - '!**/node_modules/**' - - '!assets{,/**/*}' - - '!build/release{,/**/*}' - - out/** - - package.json - - assets/icon.png - - assets/tray-icon*.png - - '!**/*.{iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts,mk,a,o,obj,forge-meta,pdb}' - - '!**/._*' - - '!**/electron-builder.{yaml,yml,json,json5,toml,ts}' - - '!**/{.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore,.idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci,.yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log,pnpm-lock.yaml,bun.lock,bun.lockb,appveyor.yml,.travis.yml,circle.yml,.nyc_output,.husky,.github,electron-builder.env}' - - '!.yarn{,/**/*}' - - '!.editorconfig' - - '!.yarnrc.yml' - nodeModuleFilePatterns: - - '**/*' - - out/** - - package.json - - assets/icon.png - - assets/tray-icon*.png arm64: firstOrDefaultFilePatterns: - '!**/node_modules/**' @@ -42,5 +20,27 @@ arm64: - package.json - assets/icon.png - assets/tray-icon*.png +x64: + firstOrDefaultFilePatterns: + - '!**/node_modules/**' + - '!assets{,/**/*}' + - '!build/release{,/**/*}' + - out/** + - package.json + - assets/icon.png + - assets/tray-icon*.png + - '!**/*.{iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts,mk,a,o,obj,forge-meta,pdb}' + - '!**/._*' + - '!**/electron-builder.{yaml,yml,json,json5,toml,ts}' + - '!**/{.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore,.idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci,.yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log,pnpm-lock.yaml,bun.lock,bun.lockb,appveyor.yml,.travis.yml,circle.yml,.nyc_output,.husky,.github,electron-builder.env}' + - '!.yarn{,/**/*}' + - '!.editorconfig' + - '!.yarnrc.yml' + nodeModuleFilePatterns: + - '**/*' + - out/** + - package.json + - assets/icon.png + - assets/tray-icon*.png nsis: - script: "!include \"C:\\Users\\zhang\\Documents\\GitHub\\AgentCompany\\node_modules\\.pnpm\\app-builder-lib@26.8.1_dmg-_62d31da0b96a3ea7b7d51a6bbe3b3b6e\\node_modules\\app-builder-lib\\templates\\nsis\\include\\StdUtils.nsh\"\n!addincludedir \"C:\\Users\\zhang\\Documents\\GitHub\\AgentCompany\\node_modules\\.pnpm\\app-builder-lib@26.8.1_dmg-_62d31da0b96a3ea7b7d51a6bbe3b3b6e\\node_modules\\app-builder-lib\\templates\\nsis\\include\"\n!macro _isUpdated _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"updated\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isUpdated `\"\" isUpdated \"\"`\n\n!macro _isForceRun _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"force-run\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForceRun `\"\" isForceRun \"\"`\n\n!macro _isKeepShortcuts _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"keep-shortcuts\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isKeepShortcuts `\"\" isKeepShortcuts \"\"`\n\n!macro _isNoDesktopShortcut _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"no-desktop-shortcut\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isNoDesktopShortcut `\"\" isNoDesktopShortcut \"\"`\n\n!macro _isDeleteAppData _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"delete-app-data\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isDeleteAppData `\"\" isDeleteAppData \"\"`\n\n!macro _isForAllUsers _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"allusers\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForAllUsers `\"\" isForAllUsers \"\"`\n\n!macro _isForCurrentUser _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"currentuser\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForCurrentUser `\"\" isForCurrentUser \"\"`\n\n!macro addLangs\n !insertmacro MUI_LANGUAGE \"English\"\n !insertmacro MUI_LANGUAGE \"German\"\n !insertmacro MUI_LANGUAGE \"French\"\n !insertmacro MUI_LANGUAGE \"SpanishInternational\"\n !insertmacro MUI_LANGUAGE \"SimpChinese\"\n !insertmacro MUI_LANGUAGE \"TradChinese\"\n !insertmacro MUI_LANGUAGE \"Japanese\"\n !insertmacro MUI_LANGUAGE \"Korean\"\n !insertmacro MUI_LANGUAGE \"Italian\"\n !insertmacro MUI_LANGUAGE \"Dutch\"\n !insertmacro MUI_LANGUAGE \"Danish\"\n !insertmacro MUI_LANGUAGE \"Swedish\"\n !insertmacro MUI_LANGUAGE \"Norwegian\"\n !insertmacro MUI_LANGUAGE \"Finnish\"\n !insertmacro MUI_LANGUAGE \"Russian\"\n !insertmacro MUI_LANGUAGE \"Portuguese\"\n !insertmacro MUI_LANGUAGE \"PortugueseBR\"\n !insertmacro MUI_LANGUAGE \"Polish\"\n !insertmacro MUI_LANGUAGE \"Ukrainian\"\n !insertmacro MUI_LANGUAGE \"Czech\"\n !insertmacro MUI_LANGUAGE \"Slovak\"\n !insertmacro MUI_LANGUAGE \"Hungarian\"\n !insertmacro MUI_LANGUAGE \"Arabic\"\n !insertmacro MUI_LANGUAGE \"Turkish\"\n !insertmacro MUI_LANGUAGE \"Thai\"\n !insertmacro MUI_LANGUAGE \"Vietnamese\"\n!macroend\n\n!addplugindir /x86-unicode \"C:\\Users\\zhang\\AppData\\Local\\electron-builder\\Cache\\nsis\\nsis-resources-3.4.1-nsis-resources-3.4.1\\plugins\\x86-unicode\"\n!include \"C:\\Users\\zhang\\AppData\\Local\\Temp\\t-GU2sm0\\6-messages.nsh\"\n\n!include \"common.nsh\"\n!include \"extractAppPackage.nsh\"\n\n# https://github.com/electron-userland/electron-builder/issues/3972#issuecomment-505171582\nCRCCheck off\nWindowIcon Off\nAutoCloseWindow True\nRequestExecutionLevel ${REQUEST_EXECUTION_LEVEL}\n\nFunction .onInit\n !ifndef SPLASH_IMAGE\n SetSilent silent\n !endif\n\n !insertmacro check64BitAndSetRegView\nFunctionEnd\n\nFunction .onGUIInit\n InitPluginsDir\n\n !ifdef SPLASH_IMAGE\n File /oname=$PLUGINSDIR\\splash.bmp \"${SPLASH_IMAGE}\"\n BgImage::SetBg $PLUGINSDIR\\splash.bmp\n BgImage::Redraw\n !endif\nFunctionEnd\n\nSection\n !ifdef SPLASH_IMAGE\n HideWindow\n !endif\n\n StrCpy $INSTDIR \"$PLUGINSDIR\\app\"\n !ifdef UNPACK_DIR_NAME\n StrCpy $INSTDIR \"$TEMP\\${UNPACK_DIR_NAME}\"\n !endif\n\n RMDir /r $INSTDIR\n SetOutPath $INSTDIR\n\n !ifdef APP_DIR_64\n !ifdef APP_DIR_ARM64\n !ifdef APP_DIR_32\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${elseif} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_64}\\*.*\"\n {endIf}\n !endif\n !else\n !ifdef APP_DIR_32\n ${if} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n File /r \"${APP_DIR_64}\\*.*\"\n !endif\n !endif\n !else\n !ifdef APP_DIR_32\n File /r \"${APP_DIR_32}\\*.*\"\n !else\n !insertmacro extractEmbeddedAppPackage\n !endif\n !endif\n\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_DIR\", \"$EXEDIR\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_FILE\", \"$EXEPATH\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_APP_FILENAME\", \"${APP_FILENAME}\").r0'\n ${StdUtils.GetAllParameters} $R0 0\n\n !ifdef SPLASH_IMAGE\n BgImage::Destroy\n !endif\n\n\tExecWait \"$INSTDIR\\${APP_EXECUTABLE_FILENAME} $R0\" $0\n SetErrorLevel $0\n\n SetOutPath $EXEDIR\n\tRMDir /r $INSTDIR\nSectionEnd\n" + script: "!include \"C:\\Users\\zhang\\Documents\\GitHub\\AgentCompany\\node_modules\\.pnpm\\app-builder-lib@26.8.1_dmg-_62d31da0b96a3ea7b7d51a6bbe3b3b6e\\node_modules\\app-builder-lib\\templates\\nsis\\include\\StdUtils.nsh\"\n!addincludedir \"C:\\Users\\zhang\\Documents\\GitHub\\AgentCompany\\node_modules\\.pnpm\\app-builder-lib@26.8.1_dmg-_62d31da0b96a3ea7b7d51a6bbe3b3b6e\\node_modules\\app-builder-lib\\templates\\nsis\\include\"\n!macro _isUpdated _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"updated\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isUpdated `\"\" isUpdated \"\"`\n\n!macro _isForceRun _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"force-run\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForceRun `\"\" isForceRun \"\"`\n\n!macro _isKeepShortcuts _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"keep-shortcuts\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isKeepShortcuts `\"\" isKeepShortcuts \"\"`\n\n!macro _isNoDesktopShortcut _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"no-desktop-shortcut\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isNoDesktopShortcut `\"\" isNoDesktopShortcut \"\"`\n\n!macro _isDeleteAppData _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"delete-app-data\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isDeleteAppData `\"\" isDeleteAppData \"\"`\n\n!macro _isForAllUsers _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"allusers\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForAllUsers `\"\" isForAllUsers \"\"`\n\n!macro _isForCurrentUser _a _b _t _f\n ${StdUtils.TestParameter} $R9 \"currentuser\"\n StrCmp \"$R9\" \"true\" `${_t}` `${_f}`\n!macroend\n!define isForCurrentUser `\"\" isForCurrentUser \"\"`\n\n!macro addLangs\n !insertmacro MUI_LANGUAGE \"English\"\n !insertmacro MUI_LANGUAGE \"German\"\n !insertmacro MUI_LANGUAGE \"French\"\n !insertmacro MUI_LANGUAGE \"SpanishInternational\"\n !insertmacro MUI_LANGUAGE \"SimpChinese\"\n !insertmacro MUI_LANGUAGE \"TradChinese\"\n !insertmacro MUI_LANGUAGE \"Japanese\"\n !insertmacro MUI_LANGUAGE \"Korean\"\n !insertmacro MUI_LANGUAGE \"Italian\"\n !insertmacro MUI_LANGUAGE \"Dutch\"\n !insertmacro MUI_LANGUAGE \"Danish\"\n !insertmacro MUI_LANGUAGE \"Swedish\"\n !insertmacro MUI_LANGUAGE \"Norwegian\"\n !insertmacro MUI_LANGUAGE \"Finnish\"\n !insertmacro MUI_LANGUAGE \"Russian\"\n !insertmacro MUI_LANGUAGE \"Portuguese\"\n !insertmacro MUI_LANGUAGE \"PortugueseBR\"\n !insertmacro MUI_LANGUAGE \"Polish\"\n !insertmacro MUI_LANGUAGE \"Ukrainian\"\n !insertmacro MUI_LANGUAGE \"Czech\"\n !insertmacro MUI_LANGUAGE \"Slovak\"\n !insertmacro MUI_LANGUAGE \"Hungarian\"\n !insertmacro MUI_LANGUAGE \"Arabic\"\n !insertmacro MUI_LANGUAGE \"Turkish\"\n !insertmacro MUI_LANGUAGE \"Thai\"\n !insertmacro MUI_LANGUAGE \"Vietnamese\"\n!macroend\n\n!addplugindir /x86-unicode \"C:\\Users\\zhang\\AppData\\Local\\electron-builder\\Cache\\nsis\\nsis-resources-3.4.1-nsis-resources-3.4.1\\plugins\\x86-unicode\"\n!include \"C:\\Users\\zhang\\AppData\\Local\\Temp\\t-i79qg1\\6-messages.nsh\"\n\n!include \"common.nsh\"\n!include \"extractAppPackage.nsh\"\n\n# https://github.com/electron-userland/electron-builder/issues/3972#issuecomment-505171582\nCRCCheck off\nWindowIcon Off\nAutoCloseWindow True\nRequestExecutionLevel ${REQUEST_EXECUTION_LEVEL}\n\nFunction .onInit\n !ifndef SPLASH_IMAGE\n SetSilent silent\n !endif\n\n !insertmacro check64BitAndSetRegView\nFunctionEnd\n\nFunction .onGUIInit\n InitPluginsDir\n\n !ifdef SPLASH_IMAGE\n File /oname=$PLUGINSDIR\\splash.bmp \"${SPLASH_IMAGE}\"\n BgImage::SetBg $PLUGINSDIR\\splash.bmp\n BgImage::Redraw\n !endif\nFunctionEnd\n\nSection\n !ifdef SPLASH_IMAGE\n HideWindow\n !endif\n\n StrCpy $INSTDIR \"$PLUGINSDIR\\app\"\n !ifdef UNPACK_DIR_NAME\n StrCpy $INSTDIR \"$TEMP\\${UNPACK_DIR_NAME}\"\n !endif\n\n RMDir /r $INSTDIR\n SetOutPath $INSTDIR\n\n !ifdef APP_DIR_64\n !ifdef APP_DIR_ARM64\n !ifdef APP_DIR_32\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${elseif} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n ${if} ${IsNativeARM64}\n File /r \"${APP_DIR_ARM64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_64}\\*.*\"\n {endIf}\n !endif\n !else\n !ifdef APP_DIR_32\n ${if} ${RunningX64}\n File /r \"${APP_DIR_64}\\*.*\"\n ${else}\n File /r \"${APP_DIR_32}\\*.*\"\n ${endIf}\n !else\n File /r \"${APP_DIR_64}\\*.*\"\n !endif\n !endif\n !else\n !ifdef APP_DIR_32\n File /r \"${APP_DIR_32}\\*.*\"\n !else\n !insertmacro extractEmbeddedAppPackage\n !endif\n !endif\n\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_DIR\", \"$EXEDIR\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_FILE\", \"$EXEPATH\").r0'\n System::Call 'Kernel32::SetEnvironmentVariable(t, t)i (\"PORTABLE_EXECUTABLE_APP_FILENAME\", \"${APP_FILENAME}\").r0'\n ${StdUtils.GetAllParameters} $R0 0\n\n !ifdef SPLASH_IMAGE\n BgImage::Destroy\n !endif\n\n\tExecWait \"$INSTDIR\\${APP_EXECUTABLE_FILENAME} $R0\" $0\n SetErrorLevel $0\n\n SetOutPath $EXEDIR\n\tRMDir /r $INSTDIR\nSectionEnd\n" diff --git a/build/release/win-arm64-unpacked/resources/app.asar b/build/release/win-arm64-unpacked/resources/app.asar index 4a3fd83e86275e210f44c0443a357eb9269f1f3e..2bf206abebd6b5ac8db87e5a27012f6001c640e3 100644 GIT binary patch delta 29464 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 delta 23788 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 e#PXrwi2h-46}~>bSl3=|58JH5U { + const raw = this.getSetting(`disabled_models:${connectorId}`); + if (!raw) return []; + try { + return JSON.parse(raw); + } catch { + return raw.split(",").filter(Boolean).map((id) => ({ id, label: id })); + } + } + + setDisabledModels(connectorId: string, models: Array<{ id: string; label: string }>) { + if (models.length === 0) { + this.db.prepare("delete from settings where key = ?").run(`disabled_models:${connectorId}`); + } else { + this.setSetting(`disabled_models:${connectorId}`, JSON.stringify(models)); + } + } + listHeartbeatEligibleAgents(companyId: string): AgentRecord[] { return this.readAll( "select id, company_id as companyId, name, role, title, department, status, reports_to as reportsTo, connector_id as connectorId, workspace_id as workspaceId, model, capabilities, budget_monthly_usd as budgetMonthlyUsd, spent_monthly_usd as spentMonthlyUsd, metadata_json as metadataJson, heartbeat_enabled as heartbeatEnabled, heartbeat_interval_sec as heartbeatIntervalSec, last_heartbeat_at as lastHeartbeatAt, created_at as createdAt, updated_at as updatedAt from agents where company_id = ? and heartbeat_enabled = 1 and status not in ('terminated', 'paused', 'error', 'pending_approval')", @@ -2351,7 +2369,8 @@ export class AppDatabase { private seedConnectors() { // Remove connector rows for adapters that have been removed from the codebase. - this.db.prepare("delete from connectors where id not in ('codex_local', 'claude_local', 'gemini_local')").run(); + // Preserve native connectors and all claude_compat slots (user-configured API providers). + this.db.prepare("delete from connectors where id not in ('codex_local', 'claude_local', 'gemini_local') and id not like 'claude_compat%'").run(); const currentRows = this.readAll>( "select id, label, description, status, command, version, auth_state as authState, last_checked_at as lastCheckedAt, last_error as lastError, model, env_binding_text as envBindingText, notes from connectors", ); diff --git a/src/main/ipc-core-admin-handlers.ts b/src/main/ipc-core-admin-handlers.ts index 47553893..a58f9618 100644 --- a/src/main/ipc-core-admin-handlers.ts +++ b/src/main/ipc-core-admin-handlers.ts @@ -2,6 +2,7 @@ import { execSync } from "node:child_process"; import { IPC_CHANNELS, companyInputSchema, + connectorDisabledModelsSchema, connectorInputSchema, deleteEntitySchema, openTerminalCommandSchema, @@ -98,6 +99,13 @@ export function registerCoreAdminHandlers({ return ok(true); }); + registerHandle(IPC_CHANNELS.setConnectorDisabledModels, async (_event, payload) => { + const parsed = connectorDisabledModelsSchema.parse(payload); + db.setDisabledModels(parsed.connectorId, parsed.disabledModels); + publishDomainChanged(); + return ok(true); + }); + registerHandle(IPC_CHANNELS.saveSecret, async (_event, payload) => { const parsed = secretInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); diff --git a/src/main/ipc-core-read-handlers.ts b/src/main/ipc-core-read-handlers.ts index 14e2c367..defc4044 100644 --- a/src/main/ipc-core-read-handlers.ts +++ b/src/main/ipc-core-read-handlers.ts @@ -109,6 +109,15 @@ export function registerCoreReadHandlers({ } }); + registerHandle(IPC_CHANNELS.getConnectorDisabledModels, (_event, connectorId) => { + try { + const parsed = connectorInputSchema.shape.id.parse(connectorId); + return ok(db.getDisabledModels(parsed)); + } catch { + return ok([]); + } + }); + registerHandle(IPC_CHANNELS.getInbox, (_event, payload) => { const companyId = z.string().uuid().parse(payload); const err = ensureCompanyExists(companyId); diff --git a/src/main/main-runtime-support.ts b/src/main/main-runtime-support.ts index 84f7de29..ab845560 100644 --- a/src/main/main-runtime-support.ts +++ b/src/main/main-runtime-support.ts @@ -148,15 +148,19 @@ export function createMainRuntimeSupport(state: MainRuntimeState) { getApiPort: () => state.apiServer?.getPort() ?? 0, notify: (options) => requireIpcSupport().notify(options), listConnectorModels: async (connectorId) => { + const disabled = new Set(requireDb().getDisabledModels(connectorId).map((m) => m.id)); // For claude_compatible, return user-configured models from the DB // (stored as comma-separated values in the connector's model field) if (connectorId.startsWith("claude_compat")) { const connector = requireDb().getConnector(connectorId); const raw = connector.model ?? ""; - return raw.split(",").map((m) => m.trim()).filter(Boolean).map((m) => ({ id: m, label: m })); + return raw.split(",").map((m) => m.trim()).filter(Boolean) + .filter((m) => !disabled.has(m)) + .map((m) => ({ id: m, label: m })); } const definition = getConnectorDefinition(connectorId as Parameters[0]); - return definition.listModels ? definition.listModels() : []; + const models = definition.listModels ? await definition.listModels() : []; + return models.filter((m) => !disabled.has(m.id)); }, }); diff --git a/src/preload/index.ts b/src/preload/index.ts index 042a149c..05fa8d81 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -40,6 +40,8 @@ const api: DesktopApi = { deleteSecret: (input) => ipcRenderer.invoke(IPC_CHANNELS.deleteSecret, input), getSidebarBadges: (companyId) => ipcRenderer.invoke(IPC_CHANNELS.getSidebarBadges, companyId), listConnectorModels: (connectorId) => ipcRenderer.invoke(IPC_CHANNELS.listConnectorModels, connectorId), + getConnectorDisabledModels: (connectorId) => ipcRenderer.invoke(IPC_CHANNELS.getConnectorDisabledModels, connectorId), + setConnectorDisabledModels: (input) => ipcRenderer.invoke(IPC_CHANNELS.setConnectorDisabledModels, input), openConnectorAuthTerminal: (connectorId) => ipcRenderer.invoke(IPC_CHANNELS.openConnectorAuthTerminal, connectorId), addComment: (input) => ipcRenderer.invoke(IPC_CHANNELS.addComment, input), listComments: (input) => ipcRenderer.invoke(IPC_CHANNELS.listComments, input), diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 48924b61..0a0f5ee4 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; import { CaretUpDown, @@ -519,7 +519,7 @@ export function App() { const companyKnowledge = useMemo(() => snapshot?.knowledgeBase?.filter((k) => !currentCompany || k.companyId === currentCompany.id) ?? [], [snapshot, currentCompany]); const companyMeetings = useMemo(() => snapshot?.meetings?.filter((m) => !currentCompany || m.companyId === currentCompany.id) ?? [], [snapshot, currentCompany]); const companySprints = useMemo(() => snapshot?.sprints?.filter((s) => !currentCompany || s.companyId === currentCompany.id) ?? [], [snapshot, currentCompany]); - const paletteQuery = commandPaletteSearch.trim().toLowerCase(); + const paletteQuery = useDeferredValue(commandPaletteSearch.trim().toLowerCase()); const paletteResults = useMemo(() => { const filteredNavItems = paletteQuery ? navItems.filter((item) => item.label.toLowerCase().includes(paletteQuery) || item.id.toLowerCase().includes(paletteQuery)) @@ -581,6 +581,19 @@ export function App() { } }, [runs, tasks]); + // Global Cmd/Ctrl+K to open command palette + useEffect(() => { + function onGlobalKey(event: KeyboardEvent) { + if ((event.metaKey || event.ctrlKey) && event.key === "k") { + event.preventDefault(); + setCommandPaletteOpen((prev) => !prev); + if (!commandPaletteOpen) { setCommandPaletteSearch(""); setSelectedPaletteIndex(0); } + } + } + document.addEventListener("keydown", onGlobalKey); + return () => document.removeEventListener("keydown", onGlobalKey); + }, [commandPaletteOpen]); + useEffect(() => { if (!commandPaletteOpen) return; function onKey(event: KeyboardEvent) { @@ -741,13 +754,13 @@ export function App() {
{showCustomTitleBar && } -
+
)} @@ -899,7 +913,7 @@ export function App() {
{error ? ( -
+
{error}
diff --git a/src/renderer/components/CompanyOnboarding/ClaudeSettingsDialog.tsx b/src/renderer/components/CompanyOnboarding/ClaudeSettingsDialog.tsx index d55d2fa6..2b743754 100644 --- a/src/renderer/components/CompanyOnboarding/ClaudeSettingsDialog.tsx +++ b/src/renderer/components/CompanyOnboarding/ClaudeSettingsDialog.tsx @@ -1,5 +1,7 @@ +import { useEffect } from "react"; import { X } from "@phosphor-icons/react"; import type { AppLocale } from "@shared/locale"; +import { useFocusTrap } from "../ui"; interface ClaudeSettingsDialogProps { claudeApiKey: string; @@ -26,17 +28,20 @@ export function ClaudeSettingsDialog({ saving, currentLocale, }: ClaudeSettingsDialogProps) { + const trapRef = useFocusTrap(true); + useEffect(() => { const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose() }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey) }, [onClose]); return (
-
e.stopPropagation()}> +
e.stopPropagation()}>

Claude Code {currentLocale === "zh" ? "设置" : "Settings"}

- + {currentLocale === "zh" ? "留空使用订阅认证" : "Leave empty to use subscription auth"}
- + setClaudeBaseUrl(e.target.value)} @@ -56,8 +62,9 @@ export function ClaudeSettingsDialog({
{currentLocale === "zh" ? "留空使用默认端点" : "Leave empty for default endpoint"}
- + setClaudeModel(e.target.value)} diff --git a/src/renderer/components/CompanyOnboarding/CompanyStep.tsx b/src/renderer/components/CompanyOnboarding/CompanyStep.tsx index 5b3f331f..51dbe920 100644 --- a/src/renderer/components/CompanyOnboarding/CompanyStep.tsx +++ b/src/renderer/components/CompanyOnboarding/CompanyStep.tsx @@ -162,7 +162,7 @@ export function CompanyStep({
{error ? ( -
+
{error}
) : null} diff --git a/src/renderer/components/CompanyOnboarding/ConnectorReadiness.tsx b/src/renderer/components/CompanyOnboarding/ConnectorReadiness.tsx index 95fff1ae..b3b84565 100644 --- a/src/renderer/components/CompanyOnboarding/ConnectorReadiness.tsx +++ b/src/renderer/components/CompanyOnboarding/ConnectorReadiness.tsx @@ -39,11 +39,11 @@ export function ConnectorReadiness({ {isChecking ? ( ) : isReady ? ( - + ) : isMissing ? ( ) : ( - + )}
@@ -75,7 +75,7 @@ export function ConnectorReadiness({ })}
{!hasRunnableConnector ? ( -
+
{t("onboarding.connectorWarning")}
) : null} diff --git a/src/renderer/components/CompanyOnboarding/LaunchStep.tsx b/src/renderer/components/CompanyOnboarding/LaunchStep.tsx index 25c051e3..86c806a1 100644 --- a/src/renderer/components/CompanyOnboarding/LaunchStep.tsx +++ b/src/renderer/components/CompanyOnboarding/LaunchStep.tsx @@ -28,7 +28,7 @@ export function LaunchStep({ transition={{ duration: 0.36, ease: [0.16, 1, 0.3, 1] }} className="bg-card border border-border shadow-sm w-full rounded-lg p-7" > -
+

@@ -72,7 +72,7 @@ export function LaunchStep({

{error ? ( -
+
{error}
) : null} diff --git a/src/renderer/components/CompanyOnboarding/MissionStep.tsx b/src/renderer/components/CompanyOnboarding/MissionStep.tsx index 41af6da2..ba3a79ad 100644 --- a/src/renderer/components/CompanyOnboarding/MissionStep.tsx +++ b/src/renderer/components/CompanyOnboarding/MissionStep.tsx @@ -70,7 +70,7 @@ export function MissionStep({
{error ? ( -
+
{error}
) : null} diff --git a/src/renderer/components/CompanyOnboarding/NodeCheckStep.tsx b/src/renderer/components/CompanyOnboarding/NodeCheckStep.tsx index 15b1d4c4..f4b51ad3 100644 --- a/src/renderer/components/CompanyOnboarding/NodeCheckStep.tsx +++ b/src/renderer/components/CompanyOnboarding/NodeCheckStep.tsx @@ -80,7 +80,7 @@ export function NodeCheckStep({ {checking ? ( ) : nodeStatus?.installed ? ( - + ) : nodeStatus ? ( ) : null} @@ -88,7 +88,7 @@ export function NodeCheckStep({ {checking ? ( {t("onboarding.providerChecking")}... ) : nodeStatus?.installed ? ( - + {t("onboarding.nodeInstalled").replace("{version}", nodeStatus.version ?? "")} ) : nodeStatus ? ( diff --git a/src/renderer/components/CompanyOnboarding/OnboardingHero.tsx b/src/renderer/components/CompanyOnboarding/OnboardingHero.tsx index f60b8e9a..a05fc231 100644 --- a/src/renderer/components/CompanyOnboarding/OnboardingHero.tsx +++ b/src/renderer/components/CompanyOnboarding/OnboardingHero.tsx @@ -63,7 +63,7 @@ export function OnboardingHero({ initial={{ opacity: 0, y: 24 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.42, ease: [0.16, 1, 0.3, 1] }} - className="relative z-10 mx-auto w-full max-w-[460px]" + className="relative z-10 mx-auto w-full max-w-[min(460px,90vw)]" > {/* Signature line */} string }) { if (status === "ready") { return ( - - + + {t("onboarding.providerReady")} ); @@ -77,8 +77,8 @@ function StatusBadge({ status, t }: { status: ConnectorStatus; t: (key: string) } if (status === "auth_required") { return ( - - + + {t("onboarding.providerAuthRequired")} ); @@ -92,8 +92,8 @@ function StatusBadge({ status, t }: { status: ConnectorStatus; t: (key: string) ); } return ( - - + + {status.replaceAll("_", " ")} ); @@ -611,7 +611,7 @@ export function ProvidersStep({ {/* Error message */} {error ? ( -
+
{error}
) : null} diff --git a/src/renderer/components/Toast.tsx b/src/renderer/components/Toast.tsx index 7b07ac28..a308e216 100644 --- a/src/renderer/components/Toast.tsx +++ b/src/renderer/components/Toast.tsx @@ -6,7 +6,7 @@ import { useReducedMotion } from "./ui" const TONE_CONFIG = { success: { icon: CheckCircle, bg: "var(--muted-foreground)", border: "var(--primary)", text: "var(--primary)" }, danger: { icon: XCircle, bg: "var(--destructive)", border: "var(--destructive)", text: "var(--destructive)" }, - warn: { icon: Warning, bg: "var(--muted-foreground)", border: "var(--destructive)", text: "var(--destructive)" }, + warn: { icon: Warning, bg: "var(--muted-foreground)", border: "var(--warning)", text: "var(--warning)" }, } as const export default function ToastContainer() { diff --git a/src/renderer/components/automation/AutomationPanel.tsx b/src/renderer/components/automation/AutomationPanel.tsx index 200c5dfe..1dd33ff1 100644 --- a/src/renderer/components/automation/AutomationPanel.tsx +++ b/src/renderer/components/automation/AutomationPanel.tsx @@ -15,8 +15,8 @@ function DeleteConfirmDialog({ open, entityName, onConfirm, onCancel }: { open: if (!open) return null return ( - e.stopPropagation()}> -

Delete

+ e.stopPropagation()}> +

Delete

Are you sure you want to delete {entityName}? This action cannot be undone.

@@ -41,15 +41,15 @@ function RulesPanel({ rules, companyId, onRefresh }: { rules: AutomationRuleReco useEffect(() => { void window.agentCompany.getAutomationLog(companyId).then((r) => { if (r.ok) setActLog(r.data) }) }, [companyId, rules]) const filtered = useMemo(() => filterStatus === "all" ? rules : rules.filter((r) => r.status === filterStatus), [rules, filterStatus]) - const counts = useMemo(() => ({ total: rules.length, active: rules.filter((r) => r.status === "active").length, paused: rules.filter((r) => r.status === "paused").length, error: rules.filter((r) => r.status === "error").length }), [rules]) + const counts = useMemo(() => { let active = 0, paused = 0, error = 0; for (const r of rules) { if (r.status === "active") active++; else if (r.status === "paused") paused++; else if (r.status === "error") error++; } return { total: rules.length, active, paused, error } }, [rules]) const handleToggle = useCallback(async (rule: AutomationRuleRecord) => { setActionError(null); try { const r = await window.agentCompany.toggleAutomationRule({ id: rule.id, companyId, status: (rule.status === "active" ? "paused" : "active") as "active"|"paused" }); if (!r.ok) { setActionError(r.error.message); return }; onRefresh() } catch (e) { setActionError(e instanceof Error ? e.message : "Failed") } }, [companyId, onRefresh]) const handleDelete = useCallback(async () => { if (!delTarget) return; setActionError(null); try { const r = await window.agentCompany.deleteAutomationRule({ id: delTarget.id, companyId }); if (!r.ok) { setActionError(r.error.message); setDelTarget(null); return }; setDelTarget(null); onRefresh() } catch (e) { setActionError(e instanceof Error ? e.message : "Failed"); setDelTarget(null) } }, [delTarget, companyId, onRefresh]) return (
- {actionError ?
{actionError}
: null} + {actionError ?
{actionError}
: null}

Automation Rules

{counts.active} active, {counts.paused} paused{counts.error > 0 ? `, ${counts.error} errored` : ""}

setShowCreate(true)} icon={Plus} tone="accent" />
- {rules.length > 0 ?
{[{ l: "Total Rules", v: counts.total, c: undefined }, { l: "Total Executions", v: rules.reduce((s, r) => s + r.executionCount, 0), c: ACCENT }, { l: "Recent Activity", v: actLog.length, c: "var(--primary)" }].map((s) =>
{s.l}
{s.v}
)}
: null} + {rules.length > 0 ?
{[{ l: "Total Rules", v: counts.total, c: undefined }, { l: "Total Executions", v: rules.reduce((s, r) => s + r.executionCount, 0), c: ACCENT }, { l: "Recent Activity", v: actLog.length, c: "var(--primary)" }].map((s) =>
{s.l}
{s.v}
)}
: null}
{activeTab === "rules" ? <> {rules.length > 0 ?
{(["all","active","paused","error"] as const).map((s) => )}
: null} @@ -71,7 +71,7 @@ function WorkflowsPanel({ workflows, companyId, onRefresh }: { workflows: Workfl const counts = useMemo(() => ({ total: workflows.length, active: workflows.filter((w) => w.status === "active").length, draft: workflows.filter((w) => w.status === "draft").length }), [workflows]) return (
- {actionError ?
{actionError}
: null} + {actionError ?
{actionError}
: null}

Workflow Pipelines

{counts.active} active, {counts.draft} draft{counts.total > counts.active + counts.draft ? `, ${counts.total - counts.active - counts.draft} other` : ""}

setShowCreate(true)} icon={Plus} tone="accent" />
{workflows.length === 0 ?
No workflow pipelines
Workflows chain actions into multi-step pipelines.
setShowCreate(true)} icon={ArrowsClockwise} tone="accent" />
:
{workflows.map((wf) => setDelTarget(wf)} onEdit={() => setEditWf(wf)} onRun={() => void handleRun(wf)} />)}
} diff --git a/src/renderer/components/automation/RuleCard.tsx b/src/renderer/components/automation/RuleCard.tsx index c090651d..d10c61dd 100644 --- a/src/renderer/components/automation/RuleCard.tsx +++ b/src/renderer/components/automation/RuleCard.tsx @@ -27,7 +27,7 @@ export function RuleCard({ rule, onToggle, onDelete, onEdit }: { rule: Automatio
- +
diff --git a/src/renderer/components/automation/RuleEditor.tsx b/src/renderer/components/automation/RuleEditor.tsx index fb0be974..0b36748d 100644 --- a/src/renderer/components/automation/RuleEditor.tsx +++ b/src/renderer/components/automation/RuleEditor.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useState } from "react" import { motion } from "framer-motion" import { Gear, Lightning, X } from "@phosphor-icons/react" import type { AutomationAction, AutomationRuleRecord, AutomationTrigger, DepartmentId } from "@shared/types" -import { ActionButton, Select } from "../ui" +import { ActionButton, Select, useFocusTrap } from "../ui" import { ACCENT, LINE, triggerConfig, actionConfig, departmentOptions, safeJsonStringify } from "./automation-config" interface RuleFormState { name: string; description: string; trigger: AutomationTrigger; conditionsJson: string; action: AutomationAction; actionConfigJson: string; sourceDepartment: string; targetDepartment: string; priority: number } @@ -20,18 +20,20 @@ export function RuleFormModal({ open, editingRule, companyId, onClose, onSaved } setSaving(true); setError(null) try { const result = await window.agentCompany.saveAutomationRule({ id: editingRule?.id ?? null, companyId, name: form.name.trim(), description: form.description.trim(), trigger: form.trigger, conditionsJson: form.conditionsJson, action: form.action, actionConfigJson: form.actionConfigJson, sourceDepartment: (form.sourceDepartment || null) as DepartmentId | null, targetDepartment: (form.targetDepartment || null) as DepartmentId | null, priority: form.priority, status: editingRule?.status ?? "active" }); if (!result.ok) { setError(result.error.message); return }; onSaved(); onClose() } catch (err) { setError(err instanceof Error ? err.message : "Failed to save rule") } finally { setSaving(false) } }, [form, editingRule, companyId, onSaved, onClose]) + const trapRef = useFocusTrap(open) + useEffect(() => { if (!open) return; const onKey = (e: KeyboardEvent) => { if (e.key === "Escape") onClose() }; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey) }, [open, onClose]) if (!open) return null const triggerOptions = Object.entries(triggerConfig).map(([key, c]) => ({ value: key, label: c.label })); const actionOptions = Object.entries(actionConfig).map(([key, c]) => ({ value: key, label: c.label })) const selectedTriggerInfo = triggerConfig[form.trigger]; const selectedActionInfo = actionConfig[form.action] return ( - e.stopPropagation()}> + e.stopPropagation()}>

{isEditing ? "Edit Automation Rule" : "New Automation Rule"}

- {error ?
{error}
: null} + {error ?
{error}
: null}
updateField("name", e.target.value)} placeholder="e.g., Auto-assign blocked tasks" className="focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full rounded-lg border bg-card px-3.5 py-2.5 text-[14px] text-foreground placeholder:text-muted-foreground" style={{ borderColor: LINE }} />