diff --git a/frontend/src/stores/product-assistant.js b/frontend/src/stores/product-assistant.js index 4e642335c7..0ad509d61d 100644 --- a/frontend/src/stores/product-assistant.js +++ b/frontend/src/stores/product-assistant.js @@ -11,10 +11,19 @@ const MAX_DEBUG_LOG_ENTRIES = 100 // maximum number of debug log entries to keep const TOOL_POLICIES = ['allow', 'ask', 'deny'] const isToolPolicy = (p) => TOOL_POLICIES.includes(p) const TOOL_CLASSES = ['read', 'write', 'destructive'] -// Fail-safe default when a class has no configured default: read allows, the rest ask. -const fallbackForToolClass = (cls) => (cls === 'read' ? 'allow' : 'ask') +// Flow-building changes only touch the editor and nothing runs until the user deploys, +// so every class starts allowed there. Platform actions act on the account straight +// away: read allows, the rest ask. +const fallbackForToolClass = (cls, group = TOOL_GROUPS.FLOW_BUILDING) => { + if (group === TOOL_GROUPS.FLOW_BUILDING || cls === 'read') return 'allow' + return 'ask' +} // The class defaults a team starts with before the user changes anything. -const defaultToolDefaults = () => ({ read: 'allow', write: 'ask', destructive: 'ask' }) +const defaultToolDefaults = (group = TOOL_GROUPS.FLOW_BUILDING) => ({ + read: fallbackForToolClass('read', group), + write: fallbackForToolClass('write', group), + destructive: fallbackForToolClass('destructive', group) +}) // The team whose saved permissions are in effect. Permissions are per team, so every // read/write of defaults or preferences is scoped by this id. const currentTeamId = () => useContextStore().team?.id || null @@ -336,7 +345,7 @@ export const useProductAssistantStore = defineStore('product-assistant', { const migrated = saved.destructive === undefined && legacyDelete !== undefined ? { ...saved, destructive: legacyDelete } : saved - return { ...defaultToolDefaults(), ...migrated } + return { ...defaultToolDefaults(group), ...migrated } }, /** The current team's saved per-tool preferences ({ [key]: policy }). */ teamToolPreferences: (state) => { @@ -346,7 +355,7 @@ export const useProductAssistantStore = defineStore('product-assistant', { defaultForToolClass () { return (cls, group = TOOL_GROUPS.FLOW_BUILDING) => { const d = this.teamGroupDefaults(group)[cls] - return isToolPolicy(d) ? d : fallbackForToolClass(cls) + return isToolPolicy(d) ? d : fallbackForToolClass(cls, group) } }, /** This chat session's grant for a tool key, or null if none ('allow'|'deny'). */ @@ -719,7 +728,7 @@ export const useProductAssistantStore = defineStore('product-assistant', { const teamId = currentTeamId() if (!teamId) return const teamDefaults = this.toolDefaultsByTeam[teamId] || {} - const groupDefaults = { ...defaultToolDefaults(), ...(teamDefaults[group] || {}), [cls]: policy } + const groupDefaults = { ...defaultToolDefaults(group), ...(teamDefaults[group] || {}), [cls]: policy } this.toolDefaultsByTeam = { ...this.toolDefaultsByTeam, [teamId]: { ...teamDefaults, [group]: groupDefaults } diff --git a/test/unit/frontend/stores/product-assistant.spec.js b/test/unit/frontend/stores/product-assistant.spec.js index 6d32d83863..a270dfb945 100644 --- a/test/unit/frontend/stores/product-assistant.spec.js +++ b/test/unit/frontend/stores/product-assistant.spec.js @@ -508,9 +508,23 @@ describe('product-assistant store', () => { }) describe('teamGroupDefaults / defaultForToolClass', () => { - it('returns the fail-safe defaults when the team has none saved', () => { + it('allows every class in flow-building when the team has none saved', () => { const store = useProductAssistantStore() - expect(store.teamGroupDefaults(TOOL_GROUPS.FLOW_BUILDING)).toEqual({ read: 'allow', write: 'ask', destructive: 'ask' }) + expect(store.teamGroupDefaults(TOOL_GROUPS.FLOW_BUILDING)).toEqual({ read: 'allow', write: 'allow', destructive: 'allow' }) + }) + + it('keeps asking for platform writes and destructive actions by default', () => { + const store = useProductAssistantStore() + expect(store.teamGroupDefaults(TOOL_GROUPS.PLATFORM)).toEqual({ read: 'allow', write: 'ask', destructive: 'ask' }) + expect(store.defaultForToolClass('write', TOOL_GROUPS.PLATFORM)).toBe('ask') + expect(store.defaultForToolClass('destructive', TOOL_GROUPS.PLATFORM)).toBe('ask') + }) + + it('keeps a class default the user saved as ask', () => { + const store = useProductAssistantStore() + store.setToolClassDefault(TOOL_GROUPS.FLOW_BUILDING, 'write', 'ask') + expect(store.defaultForToolClass('write')).toBe('ask') + expect(store.defaultForToolClass('destructive')).toBe('allow') }) it('defaultForToolClass reflects a saved class default', () => { @@ -522,15 +536,15 @@ describe('product-assistant store', () => { it('defaultForToolClass falls back per class when nothing valid is stored', () => { const store = useProductAssistantStore() expect(store.defaultForToolClass('read')).toBe('allow') - expect(store.defaultForToolClass('write')).toBe('ask') - expect(store.defaultForToolClass('destructive')).toBe('ask') + expect(store.defaultForToolClass('write')).toBe('allow') + expect(store.defaultForToolClass('destructive')).toBe('allow') }) it('migrates a pre-rename "delete" default saved before the destructive rename', () => { const store = useProductAssistantStore() - store.toolDefaultsByTeam = { [TEAM]: { [TOOL_GROUPS.FLOW_BUILDING]: { delete: 'allow' } } } - expect(store.teamGroupDefaults(TOOL_GROUPS.FLOW_BUILDING)).toEqual({ read: 'allow', write: 'ask', destructive: 'allow' }) - expect(store.defaultForToolClass('destructive')).toBe('allow') + store.toolDefaultsByTeam = { [TEAM]: { [TOOL_GROUPS.FLOW_BUILDING]: { delete: 'ask' } } } + expect(store.teamGroupDefaults(TOOL_GROUPS.FLOW_BUILDING)).toEqual({ read: 'allow', write: 'allow', destructive: 'ask' }) + expect(store.defaultForToolClass('destructive')).toBe('ask') }) it('prefers an already-migrated "destructive" default over a stale "delete" one', () => { @@ -572,13 +586,13 @@ describe('product-assistant store', () => { { key: 'write-flow', toolClass: 'write' }, { key: 'destructive-flow', toolClass: 'destructive' } ]) - store.setSessionToolOverride('write-flow', 'allow') + store.setSessionToolOverride('write-flow', 'deny') const resolved = store.resolvedToolPermissions - expect(resolved.defaults).toEqual({ read: 'allow', write: 'ask', destructive: 'ask' }) + expect(resolved.defaults).toEqual({ read: 'allow', write: 'allow', destructive: 'allow' }) expect(resolved.tools).toEqual({ 'read-flow': 'allow', - 'write-flow': 'allow', // session grant folded in - 'destructive-flow': 'ask' + 'write-flow': 'deny', // session grant folded in + 'destructive-flow': 'allow' }) }) })