Skip to content

Commit 25a679d

Browse files
authored
Merge pull request #2284 from netease-youdao/fisherdaddy/chore-settings-and-cowork-cleanup
chore: settings and cowork cleanup
2 parents 5e4cfa3 + b8f6ea3 commit 25a679d

9 files changed

Lines changed: 389 additions & 196 deletions

File tree

src/main/libs/openclawCronLegacyMigration.test.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,4 +157,95 @@ describe('openclawCronLegacyMigration', () => {
157157

158158
expect(result).toEqual({ status: 'failed', code: 2 });
159159
});
160+
161+
test('archives legacy cron files after successful doctor run', async () => {
162+
const storePath = resolveLegacyCronStorePath(stateDir);
163+
const statePath = path.join(stateDir, 'cron', 'jobs-state.json');
164+
const runLogPath = path.join(stateDir, 'cron', 'runs', 'job-1.jsonl');
165+
writeFile(storePath, '{"version":1,"jobs":[{"id":"job-1"}]}\n');
166+
writeFile(statePath, '{"job-1":{}}\n');
167+
writeFile(runLogPath, '{"status":"ok"}\n');
168+
const runner = vi.fn<LegacyCronMigrationRunner>().mockResolvedValue({
169+
code: 0,
170+
stdout: '',
171+
stderr: '',
172+
});
173+
174+
const result = await migrateLegacyCronStorageWithDoctor({
175+
stateDir,
176+
runtimeRoot,
177+
electronNodeRuntimePath,
178+
env: {},
179+
runner,
180+
});
181+
182+
expect(result).toEqual({ status: 'migrated', code: 0 });
183+
expect(fs.existsSync(storePath)).toBe(false);
184+
expect(fs.existsSync(statePath)).toBe(false);
185+
expect(fs.existsSync(runLogPath)).toBe(false);
186+
expect(fs.readFileSync(`${storePath}.migrated`, 'utf8')).toBe('{"version":1,"jobs":[{"id":"job-1"}]}\n');
187+
expect(fs.readFileSync(`${statePath}.migrated`, 'utf8')).toBe('{"job-1":{}}\n');
188+
expect(fs.readFileSync(`${runLogPath}.migrated`, 'utf8')).toBe('{"status":"ok"}\n');
189+
expect(hasLegacyCronStorage(stateDir)).toBe(false);
190+
191+
const rerun = await migrateLegacyCronStorageWithDoctor({
192+
stateDir,
193+
runtimeRoot,
194+
electronNodeRuntimePath,
195+
env: {},
196+
runner,
197+
});
198+
199+
expect(rerun).toEqual({ status: 'skipped', reason: 'no-legacy-cron-files' });
200+
expect(runner).toHaveBeenCalledTimes(1);
201+
});
202+
203+
test('keeps legacy cron files when doctor fails', async () => {
204+
const storePath = resolveLegacyCronStorePath(stateDir);
205+
writeFile(storePath, '{"version":1,"jobs":[]}\n');
206+
const runner = vi.fn<LegacyCronMigrationRunner>().mockResolvedValue({
207+
code: 2,
208+
stdout: '',
209+
stderr: 'failed',
210+
});
211+
212+
await migrateLegacyCronStorageWithDoctor({
213+
stateDir,
214+
runtimeRoot,
215+
electronNodeRuntimePath,
216+
env: {},
217+
runner,
218+
});
219+
220+
expect(fs.existsSync(storePath)).toBe(true);
221+
expect(hasLegacyCronStorage(stateDir)).toBe(true);
222+
});
223+
224+
test('does not clobber an existing archive from a previous migration', async () => {
225+
const storePath = resolveLegacyCronStorePath(stateDir);
226+
writeFile(`${storePath}.migrated`, 'earlier archive\n');
227+
writeFile(storePath, '{"version":1,"jobs":[{"id":"job-2"}]}\n');
228+
const runner = vi.fn<LegacyCronMigrationRunner>().mockResolvedValue({
229+
code: 0,
230+
stdout: '',
231+
stderr: '',
232+
});
233+
234+
const result = await migrateLegacyCronStorageWithDoctor({
235+
stateDir,
236+
runtimeRoot,
237+
electronNodeRuntimePath,
238+
env: {},
239+
runner,
240+
});
241+
242+
expect(result).toEqual({ status: 'migrated', code: 0 });
243+
expect(fs.existsSync(storePath)).toBe(false);
244+
expect(fs.readFileSync(`${storePath}.migrated`, 'utf8')).toBe('earlier archive\n');
245+
const fallbackArchives = fs.readdirSync(path.join(stateDir, 'cron'))
246+
.filter((name) => name.startsWith('jobs.json.migrated-'));
247+
expect(fallbackArchives).toHaveLength(1);
248+
expect(fs.readFileSync(path.join(stateDir, 'cron', fallbackArchives[0]), 'utf8'))
249+
.toBe('{"version":1,"jobs":[{"id":"job-2"}]}\n');
250+
});
160251
});

src/main/libs/openclawCronLegacyMigration.ts

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,21 @@ function resolveLegacyCronStatePath(stateDir: string): string {
3434
return path.join(stateDir, 'cron', 'jobs-state.json');
3535
}
3636

37-
function legacyCronRunLogsExist(stateDir: string): boolean {
37+
function listLegacyCronRunLogPaths(stateDir: string): string[] {
3838
const runsDir = path.join(stateDir, 'cron', 'runs');
3939
try {
4040
return fs.readdirSync(runsDir, { withFileTypes: true })
41-
.some((entry) => entry.isFile() && entry.name.endsWith('.jsonl'));
41+
.filter((entry) => entry.isFile() && entry.name.endsWith('.jsonl'))
42+
.map((entry) => path.join(runsDir, entry.name));
4243
} catch {
43-
return false;
44+
return [];
4445
}
4546
}
4647

48+
function legacyCronRunLogsExist(stateDir: string): boolean {
49+
return listLegacyCronRunLogPaths(stateDir).length > 0;
50+
}
51+
4752
export function hasLegacyCronStorage(stateDir: string): boolean {
4853
return (
4954
fs.existsSync(resolveLegacyCronStorePath(stateDir)) ||
@@ -52,6 +57,43 @@ export function hasLegacyCronStorage(stateDir: string): boolean {
5257
);
5358
}
5459

60+
const LEGACY_CRON_ARCHIVE_SUFFIX = '.migrated';
61+
62+
function resolveLegacyCronArchivePath(filePath: string): string {
63+
const preferred = `${filePath}${LEGACY_CRON_ARCHIVE_SUFFIX}`;
64+
if (!fs.existsSync(preferred)) {
65+
return preferred;
66+
}
67+
return `${preferred}-${Date.now()}`;
68+
}
69+
70+
// The pinned OpenClaw runtime keeps cron jobs in its shared SQLite state
71+
// (the configured cron.store path only acts as the store key); the legacy
72+
// JSON/JSONL files are read solely by the doctor migration. Rename them after
73+
// a successful doctor run so hasLegacyCronStorage() stops re-triggering the
74+
// doctor on every startup.
75+
export function archiveLegacyCronStorage(stateDir: string): string[] {
76+
const candidates = [
77+
resolveLegacyCronStorePath(stateDir),
78+
resolveLegacyCronStatePath(stateDir),
79+
...listLegacyCronRunLogPaths(stateDir),
80+
];
81+
const archived: string[] = [];
82+
for (const filePath of candidates) {
83+
try {
84+
if (!fs.existsSync(filePath)) {
85+
continue;
86+
}
87+
const archivePath = resolveLegacyCronArchivePath(filePath);
88+
fs.renameSync(filePath, archivePath);
89+
archived.push(archivePath);
90+
} catch (error) {
91+
console.warn(`[OpenClaw] Failed to archive legacy cron file: ${filePath}`, error);
92+
}
93+
}
94+
return archived;
95+
}
96+
5597
function tailLog(text: string): string {
5698
if (text.length <= LOG_TAIL_LIMIT) {
5799
return text;
@@ -159,7 +201,15 @@ export async function migrateLegacyCronStorageWithDoctor(params: {
159201
});
160202

161203
if (result.code === 0) {
162-
console.log('[OpenClaw] Legacy cron doctor migration completed.');
204+
const archived = archiveLegacyCronStorage(params.stateDir);
205+
if (archived.length > 0) {
206+
console.log(
207+
'[OpenClaw] Legacy cron doctor migration completed; archived: '
208+
+ archived.map((filePath) => path.basename(filePath)).join(', '),
209+
);
210+
} else {
211+
console.log('[OpenClaw] Legacy cron doctor migration completed.');
212+
}
163213
return { status: 'migrated', code: result.code };
164214
}
165215

src/main/libs/pythonRuntime.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,7 @@ function runPythonCommand(
325325
stdio: 'pipe',
326326
timeout: 60_000,
327327
env,
328+
windowsHide: true,
328329
});
329330
if (result.status === 0) {
330331
return { ok: true };

src/renderer/components/Settings.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import {
7272
shouldUseMaxCompletionTokensForOpenAI,
7373
shouldUseOpenAIResponsesForProvider,
7474
} from './settings/modelProviderUtils';
75-
import ModelSettingsSection, { ModelEditorDialog } from './settings/ModelSettingsSection';
75+
import ModelSettingsSection, { DeleteProviderConfirmDialog, ModelEditorDialog } from './settings/ModelSettingsSection';
7676
import EmailSkillConfig from './skills/EmailSkillConfig';
7777
import ThemedSelect from './ui/ThemedSelect';
7878

@@ -5167,8 +5167,6 @@ const Settings: React.FC<SettingsProps> = ({
51675167
testResult={testResult}
51685168
isTestResultModalOpen={isTestResultModalOpen}
51695169
setIsTestResultModalOpen={setIsTestResultModalOpen}
5170-
pendingDeleteProvider={pendingDeleteProvider}
5171-
setPendingDeleteProvider={setPendingDeleteProvider}
51725170
importInputRef={importInputRef}
51735171
handleImportProvidersClick={handleImportProvidersClick}
51745172
handleExportProviders={handleExportProviders}
@@ -5177,7 +5175,6 @@ const Settings: React.FC<SettingsProps> = ({
51775175
toggleProviderEnabled={toggleProviderEnabled}
51785176
handleAddCustomProvider={handleAddCustomProvider}
51795177
handleDeleteCustomProvider={handleDeleteCustomProvider}
5180-
confirmDeleteCustomProvider={confirmDeleteCustomProvider}
51815178
handleProviderConfigChange={handleProviderConfigChange}
51825179
setProviders={setProviders}
51835180
handleMiniMaxDeviceLogin={handleMiniMaxDeviceLogin}
@@ -5583,6 +5580,13 @@ const Settings: React.FC<SettingsProps> = ({
55835580
handleModelDialogKeyDown={handleModelDialogKeyDown}
55845581
/>
55855582

5583+
<DeleteProviderConfirmDialog
5584+
pendingDeleteProvider={pendingDeleteProvider}
5585+
providers={providers}
5586+
onCancel={() => setPendingDeleteProvider(null)}
5587+
onConfirm={confirmDeleteCustomProvider}
5588+
/>
5589+
55865590
{showOpenClawRepairConfirm && (
55875591
<div
55885592
className="absolute inset-0 z-30 flex items-center justify-center bg-black/35 px-4 rounded-2xl"

src/renderer/components/cowork/CoworkView.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import { useAgentSelectedModel } from './agentModelSelection';
3838
import { CoworkUiEvent } from './constants';
3939
import CoworkPromptInput, { type CoworkPromptInputRef } from './CoworkPromptInput';
4040
import CoworkSessionDetail from './CoworkSessionDetail';
41-
import HomeRecentTasks from './HomeRecentTasks';
4241
import { reportPromptTemplateAction } from './promptAnalytics';
4342
import { buildCoworkContinuationSystemPrompt, buildCoworkSystemPrompt } from './skillSystemPrompt';
4443

@@ -858,8 +857,6 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
858857
<CreditsResetCampaignFloat />
859858
</div>
860859

861-
{/* Recent tasks - one-click resume for returning users */}
862-
<HomeRecentTasks />
863860
<div aria-hidden="true" className="w-full min-h-[24px] flex-[3_0_0px]" />
864861
</div>
865862
</div>

src/renderer/components/cowork/HomeRecentTasks.tsx

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)