Skip to content
Merged
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
23 changes: 23 additions & 0 deletions docs/issues/feishu-pairing-save-race/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Plan

## Implementation Approach
- Inspect `RemoteControlPresenter.saveFeishuSettings` and binding store ownership of Feishu paired users.
- Update Feishu settings save to persist editable settings while preserving `config.pairedUserOpenIds`.
- Keep runtime rebuild behavior unchanged after saving credentials or enabled state.

## Affected Interfaces
- `saveFeishuSettings(input: FeishuRemoteSettings)` behavior changes to ignore `input.pairedUserOpenIds` for persistence.
- Returned settings remain `FeishuRemoteSettings` and include the current paired users from storage.

## Data Flow
- Frontend sends normalized Feishu settings, possibly from stale form state.
- Main process updates editable Feishu config fields.
- Existing `pairedUserOpenIds` is copied from the current config, so pairing state created by command handling is preserved.

## Compatibility
- No IPC or renderer contract changes.
- Old frontend payloads with `pairedUserOpenIds` continue to be accepted but cannot mutate authorization state.

## Test Strategy
- Add a main presenter unit test that seeds a paired Feishu user, saves settings with `pairedUserOpenIds: []`, and expects the paired user to remain.
- Run focused remote control presenter tests, then required format/i18n/lint commands if feasible.
21 changes: 21 additions & 0 deletions docs/issues/feishu-pairing-save-race/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Feishu Pairing Save Race

## User Need
Feishu/Lark Remote Control users who complete `/pair <code>` must stay authorized after the settings page auto-saves stale form state.

## Goal
Prevent general Feishu settings saves from overwriting the runtime-managed `pairedUserOpenIds` list.

## Acceptance Criteria
- Saving Feishu settings preserves the existing paired user open IDs when the input contains an older or empty list.
- Pair/unpair operations remain the only path that changes Feishu paired users.
- Existing Feishu settings fields such as brand, credentials, enabled state, default agent, and workdir still save normally.
- Regression coverage verifies stale frontend settings cannot erase a paired Feishu user.

## Constraints
- Keep public settings shape compatible with the current renderer and IPC contracts.
- Avoid broad changes to unrelated remote channels unless a matching save-race path is confirmed in code.

## Non-Goals
- Redesign remote settings state management.
- Remove `pairedUserOpenIds` from shared types in this fix.
6 changes: 6 additions & 0 deletions docs/issues/feishu-pairing-save-race/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Tasks

- [x] Capture issue requirements and SDD artifacts.
- [x] Change Feishu settings save to preserve paired users.
- [x] Add regression test for stale Feishu settings save.
- [x] Run formatting, i18n generation, lint, and focused tests.
2 changes: 1 addition & 1 deletion src/main/presenter/remoteControlPresenter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -532,7 +532,7 @@ export class RemoteControlPresenter {
enabled: normalized.remoteEnabled,
defaultAgentId,
defaultWorkdir: normalized.defaultWorkdir,
pairedUserOpenIds: normalized.pairedUserOpenIds,
pairedUserOpenIds: config.pairedUserOpenIds,
lastFatalError: shouldClearFatalError ? null : config.lastFatalError,
pairing: config.pairing
}))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,61 @@ describe('RemoteControlPresenter', () => {
)
})

it('preserves paired Feishu users when saving stale settings input', async () => {
const configPresenter = createConfigPresenter()
configPresenter.setSetting('remoteControl', {
feishu: {
brand: 'feishu',
appId: 'cli_old',
appSecret: 'secret',
verificationToken: 'verify',
encryptKey: '',
enabled: true,
defaultAgentId: 'deepchat',
defaultWorkdir: '',
pairedUserOpenIds: ['ou_paired'],
lastFatalError: null,
pairing: {
code: null,
expiresAt: null,
failedAttempts: 0
},
bindings: {}
}
})

const presenter = new RemoteControlPresenter({
configPresenter: configPresenter as any,
agentSessionPresenter: {} as any,
agentRuntimePresenter: {} as any,
windowPresenter: {} as any,
tabPresenter: {} as any
})

const saved = await presenter.saveFeishuSettings({
brand: 'lark',
appId: 'cli_new',
appSecret: 'secret',
verificationToken: 'verify',
encryptKey: '',
remoteEnabled: true,
defaultAgentId: 'deepchat',
defaultWorkdir: '',
pairedUserOpenIds: []
})

expect(saved.pairedUserOpenIds).toEqual(['ou_paired'])
expect(configPresenter.setSetting).toHaveBeenCalledWith(
'remoteControl',
expect.objectContaining({
feishu: expect.objectContaining({
appId: 'cli_new',
pairedUserOpenIds: ['ou_paired']
})
})
)
})

it('persists the lark brand inside feishu remote settings', async () => {
const configPresenter = createConfigPresenter()

Expand Down