feat: persist permission auto-accept on server (#2158)
Move per-session permission auto-accept policy ownership from the UI to the OpenChamber server so enabled sessions continue running when clients disconnect or the server restarts. - persist explicit per-session policies in OpenChamber settings - inherit the nearest explicit policy across subagent session hierarchies - allow child sessions to opt out of an inherited parent policy - immediately accept matching global and directory-scoped pending requests - process future requests without requiring a connected UI client - reconcile pending permissions after startup and event-stream reconnects - deduplicate concurrent requests and retry transient reply failures - synchronize policy updates across connected clients - migrate existing browser-persisted policies to server storage - suppress auto-accepted permission cards before they enter UI state - show deduplicated permission toasts for inactive sessions - preserve foreground-only permission handling in VS Code - integrate directory-aware notification routing from main - add coverage for persistence, inheritance, retries, reconciliation, pending requests, client hydration, and inactive-session toasts
This commit is contained in:
committed by
GitHub
parent
3d90eddcaf
commit
d738d41574
@@ -166,6 +166,7 @@ This module provides OpenCode server integration utilities for the web server ru
|
||||
- `readSettingsFromDiskMigrated()`
|
||||
- `writeSettingsToDisk(settings)`
|
||||
- `persistSettings(changes)`
|
||||
- Persistent permission auto-accept policy is stored under `permissionAutoAccept`; execution ownership lives in `lib/permission-auto-accept/`.
|
||||
|
||||
## Public exports (settings-helpers.js)
|
||||
- `createSettingsHelpers(dependencies)`: creates settings helper runtime for settings request/response shaping.
|
||||
|
||||
@@ -998,6 +998,7 @@ export const registerCommonRequestMiddleware = (app, dependencies) => {
|
||||
req.path.startsWith('/api/opencode') ||
|
||||
req.path.startsWith('/api/push') ||
|
||||
req.path.startsWith('/api/notifications') ||
|
||||
req.path.startsWith('/api/permission-auto-accept') ||
|
||||
req.path.startsWith('/api/session-folders') ||
|
||||
req.path.startsWith('/api/small-model') ||
|
||||
req.path.startsWith('/api/goals') ||
|
||||
|
||||
@@ -6,6 +6,7 @@ import { registerGitHubRoutes } from '../github/routes.js';
|
||||
import { registerGitRoutes } from '../git/routes.js';
|
||||
import { registerMagicPromptRoutes } from '../magic-prompts/routes.js';
|
||||
import { registerSessionFoldersRoutes } from '../session-folders/routes.js';
|
||||
import { registerPermissionAutoAcceptRoutes } from '../permission-auto-accept/runtime.js';
|
||||
import { registerConfigEntityRoutes } from './config-entity-routes.js';
|
||||
import { registerSettingsUtilityRoutes } from './core-routes.js';
|
||||
import { registerProjectIconRoutes } from './project-icon-routes.js';
|
||||
@@ -98,6 +99,7 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
||||
scheduledTasksRuntime,
|
||||
getOpenChamberEventClients,
|
||||
writeSseEvent,
|
||||
permissionAutoAcceptRuntime,
|
||||
} = routeDependencies;
|
||||
|
||||
registerSettingsUtilityRoutes(app, {
|
||||
@@ -106,6 +108,8 @@ export const createFeatureRoutesRuntime = (dependencies) => {
|
||||
clientReloadDelayMs,
|
||||
});
|
||||
|
||||
registerPermissionAutoAcceptRoutes(app, permissionAutoAcceptRuntime);
|
||||
|
||||
registerOpenCodeRoutes(app, {
|
||||
crypto,
|
||||
clientReloadDelayMs,
|
||||
|
||||
@@ -184,6 +184,18 @@ export const createSettingsHelpers = (dependencies) => {
|
||||
if (typeof candidate.desktopMinimizeToTrayEnabled === 'boolean') {
|
||||
result.desktopMinimizeToTrayEnabled = candidate.desktopMinimizeToTrayEnabled;
|
||||
}
|
||||
if (candidate.permissionAutoAccept && typeof candidate.permissionAutoAccept === 'object' && !Array.isArray(candidate.permissionAutoAccept)) {
|
||||
const sessions = {};
|
||||
const sourceSessions = candidate.permissionAutoAccept.sessions;
|
||||
if (sourceSessions && typeof sourceSessions === 'object' && !Array.isArray(sourceSessions)) {
|
||||
for (const [sessionId, enabled] of Object.entries(sourceSessions)) {
|
||||
if (sessionId && typeof enabled === 'boolean') sessions[sessionId] = enabled;
|
||||
}
|
||||
}
|
||||
result.permissionAutoAccept = {
|
||||
sessions,
|
||||
};
|
||||
}
|
||||
if (typeof candidate.desktopUiPassword === 'string') {
|
||||
result.desktopUiPassword = candidate.desktopUiPassword.trim();
|
||||
}
|
||||
|
||||
@@ -111,6 +111,20 @@ describe('settings helpers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('sanitizes the persisted permission auto-accept policy', () => {
|
||||
const helpers = createTestHelpers();
|
||||
|
||||
expect(helpers.sanitizeSettingsUpdate({
|
||||
permissionAutoAccept: {
|
||||
sessions: { root: true, child: false, invalid: 'true' },
|
||||
},
|
||||
})).toEqual({
|
||||
permissionAutoAccept: {
|
||||
sessions: { root: true, child: false },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('accepts desktopUiPassword as a persisted shared setting', () => {
|
||||
const helpers = createTestHelpers();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user