evolcore 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/bin/codex-managed-hook.mjs +16 -7
  3. package/bin/install-codex-managed-hooks.mjs +4 -2
  4. package/dist/agents/claude-runner.js +132 -14
  5. package/dist/agents/codex-app-server-client.js +6 -1
  6. package/dist/agents/codex-runner.js +21 -6
  7. package/dist/agents/ecagent-runner.js +39 -10
  8. package/dist/agents/gemini-runner.js +90 -19
  9. package/dist/aun/aid/store.js +36 -0
  10. package/dist/aun/msg/group.js +3 -1
  11. package/dist/aun/msg/p2p.js +23 -9
  12. package/dist/channels/aun.js +159 -21
  13. package/dist/cli/agent-command.js +67 -6
  14. package/dist/cli/agent.js +26 -0
  15. package/dist/cli/command-log.js +23 -4
  16. package/dist/cli/daemon-commands.js +53 -12
  17. package/dist/cli/init.js +21 -5
  18. package/dist/cli/restart-monitor.js +13 -6
  19. package/dist/cli/task-context.js +46 -1
  20. package/dist/cli/watch-logs.js +2 -2
  21. package/dist/config/builtin-roles.js +5 -1
  22. package/dist/config/role-ranks.js +4 -0
  23. package/dist/core/audit/event-key.js +29 -0
  24. package/dist/core/audit/log-integrity.js +13 -3
  25. package/dist/core/auth/auth-gateway.js +14 -18
  26. package/dist/core/auth/authorization-audit.js +110 -3
  27. package/dist/core/auth/authorization-denial.js +17 -0
  28. package/dist/core/auth/operation-authorizer.js +143 -18
  29. package/dist/core/auth/operation-catalog.js +21 -5
  30. package/dist/core/bootstrap-messages.js +11 -6
  31. package/dist/core/bootstrap-service.js +26 -4
  32. package/dist/core/causation/aun-association.js +7 -4
  33. package/dist/core/command/agent-control.js +25 -16
  34. package/dist/core/command/command-handler.js +50 -4
  35. package/dist/core/command/group-menu.js +1 -1
  36. package/dist/core/command/menu-catalog.js +32 -7
  37. package/dist/core/command/menu-handler.js +59 -23
  38. package/dist/core/command/menu-protocol.js +196 -0
  39. package/dist/core/command/slash-gate.js +14 -5
  40. package/dist/core/command/slash-handler.js +81 -99
  41. package/dist/core/event-catalog.js +18 -0
  42. package/dist/core/message/message-bridge.js +72 -9
  43. package/dist/core/message/pause-controller.js +53 -0
  44. package/dist/core/message/response-engine.js +98 -11
  45. package/dist/core/permission/sandbox-runtime.js +79 -13
  46. package/dist/core/permission/tool-policy.js +1 -1
  47. package/dist/index.js +357 -48
  48. package/dist/ipc.js +75 -4
  49. package/dist/utils/atomic-write.js +45 -11
  50. package/dist/utils/error-utils.js +38 -0
  51. package/dist/utils/logger.js +27 -0
  52. package/dist/utils/windows-autostart.js +740 -83
  53. package/ecagent/dist/harness/agent-harness.d.ts +1 -1
  54. package/ecagent/dist/harness/agent-harness.js +6 -4
  55. package/kits/docs/evolcore/config.md +1 -1
  56. package/kits/docs/evolcore/group-rules.md +2 -1
  57. package/kits/docs/identity/ROLE_DETAIL.md +3 -1
  58. package/kits/eck_manifest.json +25 -16
  59. package/kits/rules/01-overview.md +5 -5
  60. package/kits/rules/03-identity.md +1 -1
  61. package/kits/rules/04-relation.md +4 -4
  62. package/kits/rules/05-venue.md +5 -5
  63. package/kits/templates/bootstrap-welcome.md +3 -1
  64. package/kits/templates/system-fragments/bootstrap.md +17 -9
  65. package/package.json +1 -1
@@ -1,9 +1,14 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { spawnSync } from 'child_process';
4
- import { getPackageRoot } from '../paths.js';
4
+ import { getPackageRoot, resolvePaths } from '../paths.js';
5
+ import { atomicReadJson, atomicWriteJson } from './atomic-write.js';
5
6
  import { decodeWindowsOutput } from './windows-output.js';
6
7
  export const WINDOWS_AUTOSTART_TASK_NAME = 'EvolCore';
8
+ export const WINDOWS_AUTOSTART_RUN_VALUE_NAME = 'EvolCore';
9
+ export const WINDOWS_AUTOSTART_STATE_VERSION = 1;
10
+ const WINDOWS_RUN_KEY = 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';
11
+ const WRAPPER_MARKER = '# EvolCore managed login autostart wrapper v1';
7
12
  function isWindows() {
8
13
  return process.platform === 'win32';
9
14
  }
@@ -14,23 +19,39 @@ function runSchtasks(args) {
14
19
  timeout: 15_000,
15
20
  });
16
21
  }
22
+ function runReg(args) {
23
+ return spawnSync('reg.exe', args, {
24
+ encoding: 'buffer',
25
+ windowsHide: true,
26
+ timeout: 15_000,
27
+ });
28
+ }
29
+ function resultOutput(result) {
30
+ return decodeWindowsOutput(result.stderr) || decodeWindowsOutput(result.stdout);
31
+ }
17
32
  function commandError(result, fallback) {
18
- const output = decodeWindowsOutput(result.stderr) || decodeWindowsOutput(result.stdout);
33
+ const output = resultOutput(result);
19
34
  const status = result.status == null ? '' : `(退出码 ${result.status})`;
20
35
  const signal = result.signal ? `(信号 ${result.signal})` : '';
21
36
  return `${result.error?.message || output || fallback}${status}${signal}`.trim();
22
37
  }
38
+ function isAccessDenied(result) {
39
+ return result.status === 5
40
+ || /access is denied|access denied|拒绝访问|访问被拒绝/i.test(resultOutput(result));
41
+ }
42
+ function accessDeniedError(action, result) {
43
+ const retry = action === '启用' ? 'ec init --auto-start' : 'ec init --no-auto-start';
44
+ const exitDetail = result.status == null ? '' : `;schtasks 退出码 ${result.status}`;
45
+ return `Windows 任务计划程序拒绝访问(通常为系统错误码 5${exitDetail})。`
46
+ + `当前 PowerShell/Windows Terminal 未获得管理员权限,或现有 \\${WINDOWS_AUTOSTART_TASK_NAME} 任务由提升权限创建。`
47
+ + `请右键终端选择“以管理员身份运行”,然后重新执行:${retry}。`
48
+ + '若自启动已由管理员配置且只需确认服务,请执行:ec status。';
49
+ }
23
50
  function isTaskNotFoundMessage(message) {
24
51
  return /cannot find the file specified|系统找不到指定的文件|找不到指定的文件|指定的任务不存在|任务不存在/i.test(message);
25
52
  }
26
- function probeWindowsTask() {
27
- const result = runSchtasks(['/Query', '/TN', WINDOWS_AUTOSTART_TASK_NAME]);
28
- if (result.status === 0)
29
- return { state: 'installed' };
30
- const message = decodeWindowsOutput(result.stderr) || decodeWindowsOutput(result.stdout);
31
- if (isTaskNotFoundMessage(message))
32
- return { state: 'not-found' };
33
- return { state: 'error', error: commandError(result, '查询 Windows 登录自启任务失败') };
53
+ function isRegistryValueNotFoundMessage(message) {
54
+ return /unable to find|cannot find|系统找不到|找不到指定的注册表项|找不到指定的文件/i.test(message);
34
55
  }
35
56
  function powershellLiteral(value) {
36
57
  return `'${value.replace(/'/g, "''")}'`;
@@ -41,28 +62,40 @@ function windowsCommandLiteral(value) {
41
62
  function wrapperPath(runtimeRoot) {
42
63
  return path.join(runtimeRoot, 'data', 'evolcore-autostart.ps1');
43
64
  }
44
- export function windowsAutostartInstalled() {
45
- if (!isWindows())
46
- return false;
47
- return probeWindowsTask().state === 'installed';
65
+ function statePath(runtimeRoot) {
66
+ return path.join(runtimeRoot, 'data', 'autostart-state.json');
48
67
  }
49
- function createWrapper(runtimeRoot) {
50
- const scriptPath = wrapperPath(runtimeRoot);
68
+ function powershellPath() {
69
+ const systemRoot = process.env.SystemRoot || 'C:\\Windows';
70
+ return path.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
71
+ }
72
+ function taskArguments(scriptPath) {
73
+ return `-NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ${windowsCommandLiteral(scriptPath)}`;
74
+ }
75
+ function taskCommand(scriptPath) {
76
+ return `${windowsCommandLiteral(powershellPath())} ${taskArguments(scriptPath)}`;
77
+ }
78
+ function userRunCommand(scriptPath) {
79
+ return `${windowsCommandLiteral(powershellPath())} -NoLogo -NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File ${windowsCommandLiteral(scriptPath)}`;
80
+ }
81
+ function createWrapperContent(runtimeRoot) {
51
82
  const cliEntry = path.join(getPackageRoot(), 'dist', 'cli', 'index.js');
52
83
  if (!fs.existsSync(cliEntry)) {
53
84
  throw new Error(`找不到 EvolCore CLI 入口: ${cliEntry}`);
54
85
  }
55
86
  const pathValue = process.env.PATH ?? '';
56
- const script = [
87
+ return [
88
+ WRAPPER_MARKER,
57
89
  `$env:EVOLCORE_HOME = ${powershellLiteral(runtimeRoot)}`,
58
90
  `$env:Path = ${powershellLiteral(pathValue)}`,
59
91
  `& ${powershellLiteral(process.execPath)} --no-warnings=ExperimentalWarning ${powershellLiteral(cliEntry)} start`,
60
92
  'exit $LASTEXITCODE',
61
93
  '',
62
94
  ].join('\r\n');
95
+ }
96
+ function writeWrapper(scriptPath, content) {
63
97
  fs.mkdirSync(path.dirname(scriptPath), { recursive: true });
64
- fs.writeFileSync(scriptPath, script, 'utf8');
65
- return scriptPath;
98
+ fs.writeFileSync(scriptPath, content, 'utf8');
66
99
  }
67
100
  function restoreWrapper(file, previous) {
68
101
  try {
@@ -76,6 +109,430 @@ function restoreWrapper(file, previous) {
76
109
  return error instanceof Error ? error.message : String(error);
77
110
  }
78
111
  }
112
+ function restoreWrapperUnlessNeeded(file, previous, keepCurrent) {
113
+ return keepCurrent ? undefined : restoreWrapper(file, previous);
114
+ }
115
+ function writeState(runtimeRoot, backend) {
116
+ const scriptPath = wrapperPath(runtimeRoot);
117
+ const state = {
118
+ $schema_version: WINDOWS_AUTOSTART_STATE_VERSION,
119
+ backend,
120
+ wrapperPath: scriptPath,
121
+ ...(backend === 'task'
122
+ ? { taskName: WINDOWS_AUTOSTART_TASK_NAME }
123
+ : { runValueName: WINDOWS_AUTOSTART_RUN_VALUE_NAME }),
124
+ };
125
+ try {
126
+ atomicWriteJson(statePath(runtimeRoot), state);
127
+ return undefined;
128
+ }
129
+ catch (error) {
130
+ return error instanceof Error ? error.message : String(error);
131
+ }
132
+ }
133
+ function readState(runtimeRoot) {
134
+ try {
135
+ const state = atomicReadJson(statePath(runtimeRoot));
136
+ if (!state || state.$schema_version !== WINDOWS_AUTOSTART_STATE_VERSION)
137
+ return undefined;
138
+ if (state.backend !== 'task' && state.backend !== 'user-run')
139
+ return undefined;
140
+ if (typeof state.wrapperPath !== 'string' || state.wrapperPath.trim().length === 0)
141
+ return undefined;
142
+ if (state.backend === 'task' && state.taskName !== WINDOWS_AUTOSTART_TASK_NAME)
143
+ return undefined;
144
+ if (state.backend === 'user-run' && state.runValueName !== WINDOWS_AUTOSTART_RUN_VALUE_NAME)
145
+ return undefined;
146
+ return state;
147
+ }
148
+ catch {
149
+ return undefined;
150
+ }
151
+ }
152
+ function removeState(runtimeRoot) {
153
+ const file = statePath(runtimeRoot);
154
+ for (const suffix of ['', '_', '__']) {
155
+ try {
156
+ fs.rmSync(file + suffix, { force: true });
157
+ }
158
+ catch { }
159
+ }
160
+ }
161
+ function removeOwnedWrapper(runtimeRoot) {
162
+ if (wrapperOwnedByEvolCore(runtimeRoot)) {
163
+ try {
164
+ fs.rmSync(wrapperPath(runtimeRoot), { force: true });
165
+ }
166
+ catch { }
167
+ }
168
+ }
169
+ function decodeXml(value) {
170
+ return value
171
+ .replace(/"/gi, '"')
172
+ .replace(/'/gi, "'")
173
+ .replace(/&lt;/gi, '<')
174
+ .replace(/&gt;/gi, '>')
175
+ .replace(/&amp;/gi, '&');
176
+ }
177
+ function xmlElement(xml, name) {
178
+ const match = xml.match(new RegExp(`<${name}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${name}>`, 'i'));
179
+ return match ? decodeXml(match[1].trim()) : undefined;
180
+ }
181
+ function xmlBlocks(xml, name) {
182
+ return [...xml.matchAll(new RegExp(`<${name}(?:\\s[^>]*)?>([\\s\\S]*?)<\\/${name}>`, 'gi'))]
183
+ .map(match => match[1]);
184
+ }
185
+ function normalizeWindowsPath(value) {
186
+ const trimmed = value.trim().replace(/^"|"$/g, '');
187
+ return path.win32.normalize(trimmed).toLowerCase();
188
+ }
189
+ function normalizeCommandLine(value) {
190
+ // Keep whitespace inside quoted paths significant. Collapsing it could make
191
+ // two different wrapper paths (for example "A B" and "A B") look equal
192
+ // and weaken the ownership check used before deletion.
193
+ return value.trim().toLowerCase();
194
+ }
195
+ function wrapperOwnedByEvolCore(runtimeRoot) {
196
+ const scriptPath = wrapperPath(runtimeRoot);
197
+ try {
198
+ const content = fs.readFileSync(scriptPath, 'utf8');
199
+ if (content.split(/\r?\n/, 1)[0] === WRAPPER_MARKER)
200
+ return true;
201
+ // Recognize wrappers created before the ownership marker was introduced.
202
+ return content.includes(`$env:EVOLCORE_HOME = ${powershellLiteral(runtimeRoot)}`)
203
+ && content.includes('--no-warnings=ExperimentalWarning')
204
+ && content.includes(' start\r\n');
205
+ }
206
+ catch {
207
+ return false;
208
+ }
209
+ }
210
+ function taskOwnership(xml, runtimeRoot) {
211
+ const execBlocks = xmlBlocks(xml, 'Exec');
212
+ if (execBlocks.length !== 1)
213
+ return { managed: false, current: false };
214
+ const command = xmlElement(execBlocks[0], 'Command');
215
+ const args = xmlElement(execBlocks[0], 'Arguments');
216
+ if (!command || !args)
217
+ return { managed: false, current: false };
218
+ const expectedScript = wrapperPath(runtimeRoot);
219
+ const expectedArgs = taskArguments(expectedScript);
220
+ const actionMatches = normalizeWindowsPath(command) === normalizeWindowsPath(powershellPath())
221
+ && normalizeCommandLine(args).includes(normalizeCommandLine(`-File ${windowsCommandLiteral(expectedScript)}`));
222
+ const state = readState(runtimeRoot);
223
+ const stateMatches = state?.backend === 'task'
224
+ && normalizeWindowsPath(state.wrapperPath) === normalizeWindowsPath(expectedScript)
225
+ && state.taskName === WINDOWS_AUTOSTART_TASK_NAME;
226
+ const managed = actionMatches && (wrapperOwnedByEvolCore(runtimeRoot) || stateMatches);
227
+ const principalBlocks = xmlBlocks(xml, 'Principal');
228
+ const triggerTypes = [...xml.matchAll(/<([A-Za-z]+Trigger)(?:\s[^>]*)?>/gi)]
229
+ .map(match => match[1].toLowerCase());
230
+ const runLevel = principalBlocks.length === 1
231
+ ? xmlElement(principalBlocks[0], 'RunLevel')
232
+ : undefined;
233
+ const principalCurrent = principalBlocks.length === 1
234
+ && /<LogonType>\s*InteractiveToken\s*<\/LogonType>/i.test(principalBlocks[0])
235
+ // schtasks /RL LIMITED can omit RunLevel entirely because
236
+ // LeastPrivilege is the schema default.
237
+ && (runLevel === undefined || /^LeastPrivilege$/i.test(runLevel));
238
+ const triggerCurrent = triggerTypes.length === 1 && triggerTypes[0] === 'logontrigger';
239
+ const current = managed
240
+ && normalizeCommandLine(args) === normalizeCommandLine(expectedArgs)
241
+ && principalCurrent
242
+ && triggerCurrent
243
+ && !/<Enabled>\s*false\s*<\/Enabled>/i.test(xml);
244
+ return { managed, current };
245
+ }
246
+ function probeWindowsTask(runtimeRoot, action = '启用') {
247
+ const result = runSchtasks(['/Query', '/TN', WINDOWS_AUTOSTART_TASK_NAME, '/XML']);
248
+ if (result.status === 0) {
249
+ const xml = decodeWindowsOutput(result.stdout);
250
+ const ownership = taskOwnership(xml, runtimeRoot);
251
+ return { state: 'installed', xml, ...ownership };
252
+ }
253
+ const message = resultOutput(result);
254
+ if (isTaskNotFoundMessage(message))
255
+ return { state: 'not-found' };
256
+ return {
257
+ state: 'error',
258
+ error: isAccessDenied(result)
259
+ ? accessDeniedError(action, result)
260
+ : commandError(result, '查询 Windows 登录自启任务失败'),
261
+ };
262
+ }
263
+ function parseRegistryValue(output) {
264
+ const line = output.split(/\r?\n/).find(candidate => (candidate.includes(WINDOWS_AUTOSTART_RUN_VALUE_NAME)
265
+ && /\bREG_(?:EXPAND_)?SZ\b/i.test(candidate)));
266
+ if (!line)
267
+ return undefined;
268
+ const match = line.match(/\bREG_(?:EXPAND_)?SZ\b\s+([\s\S]+)$/i);
269
+ return match?.[1]?.trim();
270
+ }
271
+ function probeWindowsRun(runtimeRoot) {
272
+ const result = runReg(['query', WINDOWS_RUN_KEY, '/v', WINDOWS_AUTOSTART_RUN_VALUE_NAME]);
273
+ if (result.status === 0) {
274
+ const command = parseRegistryValue(decodeWindowsOutput(result.stdout));
275
+ if (!command)
276
+ return { state: 'error', error: '无法解析 Windows 用户级 Run 自启动项' };
277
+ const expectedTask = taskCommand(wrapperPath(runtimeRoot));
278
+ const expectedRun = userRunCommand(wrapperPath(runtimeRoot));
279
+ const normalizedCommand = normalizeCommandLine(command);
280
+ const actionMatches = normalizedCommand === normalizeCommandLine(expectedTask)
281
+ || normalizedCommand === normalizeCommandLine(expectedRun);
282
+ const state = readState(runtimeRoot);
283
+ const stateMatches = state?.backend === 'user-run'
284
+ && normalizeWindowsPath(state.wrapperPath) === normalizeWindowsPath(wrapperPath(runtimeRoot))
285
+ && state.runValueName === WINDOWS_AUTOSTART_RUN_VALUE_NAME;
286
+ const managed = actionMatches && (wrapperOwnedByEvolCore(runtimeRoot) || stateMatches);
287
+ const current = normalizedCommand === normalizeCommandLine(expectedRun);
288
+ return { state: 'installed', command, managed, current };
289
+ }
290
+ const message = resultOutput(result);
291
+ if (isRegistryValueNotFoundMessage(message))
292
+ return { state: 'not-found' };
293
+ return { state: 'error', error: commandError(result, '查询 Windows 用户级 Run 自启动项失败') };
294
+ }
295
+ function deleteManagedTask(runtimeRoot) {
296
+ // Re-query immediately before deletion so a same-named task that was
297
+ // replaced after an earlier probe is not deleted based on stale ownership.
298
+ const task = probeWindowsTask(runtimeRoot, '禁用');
299
+ if (task.state === 'error')
300
+ return task;
301
+ if (task.state === 'not-found')
302
+ return { state: 'not-found' };
303
+ if (!task.managed)
304
+ return { state: 'preserved' };
305
+ const result = runSchtasks(['/Delete', '/TN', WINDOWS_AUTOSTART_TASK_NAME, '/F']);
306
+ if (result.status === 0)
307
+ return { state: 'deleted' };
308
+ return {
309
+ state: 'error',
310
+ error: isAccessDenied(result)
311
+ ? accessDeniedError('禁用', result)
312
+ : commandError(result, '删除 Windows 登录自启任务失败'),
313
+ };
314
+ }
315
+ function deleteManagedRun(runtimeRoot) {
316
+ // reg delete operates only by value name. Re-query the command first to
317
+ // avoid deleting a value another program installed after our earlier probe.
318
+ const run = probeWindowsRun(runtimeRoot);
319
+ if (run.state === 'error')
320
+ return run;
321
+ if (run.state === 'not-found')
322
+ return { state: 'not-found' };
323
+ if (!run.managed)
324
+ return { state: 'preserved' };
325
+ const result = runReg(['delete', WINDOWS_RUN_KEY, '/v', WINDOWS_AUTOSTART_RUN_VALUE_NAME, '/f']);
326
+ if (result.status === 0)
327
+ return { state: 'deleted' };
328
+ return { state: 'error', error: commandError(result, '删除 Windows 用户级 Run 自启动项失败') };
329
+ }
330
+ function createUserRunAutostart(runtimeRoot, scriptPath, wrapperWasCurrent) {
331
+ // Query again immediately before the /f write. This cannot make the
332
+ // registry update fully atomic, but it narrows the window in which a
333
+ // same-named value owned by another program could be overwritten.
334
+ const existing = probeWindowsRun(runtimeRoot);
335
+ if (existing.state === 'error')
336
+ return { ok: false, enabled: false, error: existing.error };
337
+ if (existing.state === 'installed' && !existing.managed) {
338
+ return {
339
+ ok: false,
340
+ enabled: false,
341
+ error: `HKCU Run 中已存在同名值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME},但其命令不属于 EvolCore;为避免覆盖已停止配置。`,
342
+ };
343
+ }
344
+ if (existing.state === 'installed' && existing.managed && existing.current) {
345
+ const stateError = writeState(runtimeRoot, 'user-run');
346
+ if (stateError)
347
+ return { ok: false, enabled: true, backend: 'user-run', error: `记录自启动后端失败: ${stateError}` };
348
+ return {
349
+ ok: true,
350
+ enabled: true,
351
+ backend: 'user-run',
352
+ ...(wrapperWasCurrent ? { unchanged: true } : {}),
353
+ };
354
+ }
355
+ const result = runReg([
356
+ 'add', WINDOWS_RUN_KEY,
357
+ '/v', WINDOWS_AUTOSTART_RUN_VALUE_NAME,
358
+ '/t', 'REG_SZ',
359
+ '/d', userRunCommand(scriptPath),
360
+ '/f',
361
+ ]);
362
+ if (result.status !== 0) {
363
+ return {
364
+ ok: false,
365
+ enabled: existing.state === 'installed' && existing.managed,
366
+ ...(existing.state === 'installed' && existing.managed ? { backend: 'user-run' } : {}),
367
+ error: commandError(result, '创建 Windows 用户级 Run 自启动项失败'),
368
+ };
369
+ }
370
+ const verified = probeWindowsRun(runtimeRoot);
371
+ if (verified.state === 'error') {
372
+ return {
373
+ ok: false,
374
+ enabled: true,
375
+ backend: 'user-run',
376
+ error: `HKCU Run 写入命令已成功,但验证查询失败,当前状态不确定;已保留 EvolCore 启动脚本,请重试 ec init --auto-start。${verified.error}`,
377
+ };
378
+ }
379
+ if (verified.state === 'not-found') {
380
+ return { ok: false, enabled: false, error: 'HKCU Run 写入命令已成功,但验证时未找到该自启动项' };
381
+ }
382
+ if (!verified.managed) {
383
+ return {
384
+ ok: false,
385
+ enabled: false,
386
+ error: `HKCU Run 写入后同名值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME} 已变为非 EvolCore 命令;为避免误删已保留该值。`,
387
+ };
388
+ }
389
+ if (!verified.current) {
390
+ return {
391
+ ok: false,
392
+ enabled: true,
393
+ backend: 'user-run',
394
+ error: 'HKCU Run 自启动项已写入,但验证到的命令与当前 EvolCore 配置不一致;已保留启动脚本,请重试 ec init --auto-start。',
395
+ };
396
+ }
397
+ const stateError = writeState(runtimeRoot, 'user-run');
398
+ if (stateError)
399
+ return { ok: false, enabled: true, backend: 'user-run', error: `记录自启动后端失败: ${stateError}` };
400
+ return { ok: true, enabled: true, backend: 'user-run' };
401
+ }
402
+ function currentBackend(runtimeRoot) {
403
+ const savedState = readState(runtimeRoot);
404
+ const task = probeWindowsTask(runtimeRoot);
405
+ if (task.state === 'installed' && task.current)
406
+ return 'task';
407
+ if (task.state === 'error' && savedState?.backend === 'task')
408
+ return 'task';
409
+ const run = probeWindowsRun(runtimeRoot);
410
+ if (run.state === 'installed' && run.managed && run.current)
411
+ return 'user-run';
412
+ if (run.state === 'error' && savedState?.backend === 'user-run')
413
+ return 'user-run';
414
+ return undefined;
415
+ }
416
+ export function windowsAutostartInstalled(runtimeRoot = resolvePaths().root) {
417
+ if (!isWindows())
418
+ return false;
419
+ return currentBackend(path.resolve(runtimeRoot)) !== undefined;
420
+ }
421
+ function disableWindowsAutostart(runtimeRoot) {
422
+ const savedState = readState(runtimeRoot);
423
+ const task = probeWindowsTask(runtimeRoot, '禁用');
424
+ if (task.state === 'error') {
425
+ // HKCU remains writable from a normal terminal. Clean a Run value whose
426
+ // ownership can still be verified, but keep the wrapper and state because
427
+ // the task backend is unknown until an elevated retry succeeds.
428
+ const run = probeWindowsRun(runtimeRoot);
429
+ if (run.state === 'error') {
430
+ return {
431
+ ok: false,
432
+ enabled: true,
433
+ ...(savedState?.backend ? { backend: savedState.backend } : {}),
434
+ error: `${task.error};${run.error}`,
435
+ };
436
+ }
437
+ if (run.state === 'installed' && !run.managed) {
438
+ return { ok: false, enabled: true, error: `HKCU Run 中的同名值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME} 已被其他命令占用;为避免误删已保留。${task.error}` };
439
+ }
440
+ if (run.state === 'installed' && run.managed) {
441
+ const removeRun = deleteManagedRun(runtimeRoot);
442
+ if (removeRun.state === 'error')
443
+ return { ok: false, enabled: true, backend: 'user-run', error: removeRun.error };
444
+ if (removeRun.state === 'preserved') {
445
+ return {
446
+ ok: false,
447
+ enabled: true,
448
+ error: `HKCU Run 中的同名值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME} 在删除前已被其他命令替换;为避免误删已保留。${task.error}`,
449
+ };
450
+ }
451
+ }
452
+ return { ok: false, enabled: true, error: `用户级 Run 自启动已删除或不存在,但无法确认是否存在计划任务。${task.error}` };
453
+ }
454
+ const run = probeWindowsRun(runtimeRoot);
455
+ if (run.state === 'error') {
456
+ return {
457
+ ok: false,
458
+ enabled: true,
459
+ ...(task.state === 'installed' && task.managed ? { backend: 'task' } : {}),
460
+ error: run.error,
461
+ };
462
+ }
463
+ const preserved = [];
464
+ if (task.state === 'installed' && !task.managed)
465
+ preserved.push(`计划任务 \\${WINDOWS_AUTOSTART_TASK_NAME}`);
466
+ if (run.state === 'installed' && !run.managed)
467
+ preserved.push(`HKCU Run 值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME}`);
468
+ if (task.state === 'installed' && !task.managed && run.state === 'installed' && run.managed) {
469
+ const removeRun = deleteManagedRun(runtimeRoot);
470
+ if (removeRun.state === 'error') {
471
+ return { ok: false, enabled: true, backend: 'user-run', error: removeRun.error };
472
+ }
473
+ if (removeRun.state === 'preserved')
474
+ preserved.push(`删除前被替换的 HKCU Run 值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME}`);
475
+ if (removeRun.state !== 'preserved')
476
+ removeOwnedWrapper(runtimeRoot);
477
+ removeState(runtimeRoot);
478
+ return {
479
+ ok: true,
480
+ enabled: false,
481
+ warning: `已禁用 EvolCore 管理的自启动;检测到同名但并非 EvolCore 管理的 ${preserved.join('、')},为避免误删已保留。`,
482
+ };
483
+ }
484
+ if (run.state === 'installed' && !run.managed && task.state === 'installed' && task.managed) {
485
+ const removeTask = deleteManagedTask(runtimeRoot);
486
+ if (removeTask.state === 'error') {
487
+ return { ok: false, enabled: true, backend: 'task', error: removeTask.error };
488
+ }
489
+ if (removeTask.state === 'preserved')
490
+ preserved.push(`删除前被替换的计划任务 \\${WINDOWS_AUTOSTART_TASK_NAME}`);
491
+ if (removeTask.state !== 'preserved')
492
+ removeOwnedWrapper(runtimeRoot);
493
+ removeState(runtimeRoot);
494
+ return {
495
+ ok: true,
496
+ enabled: false,
497
+ warning: `已禁用 EvolCore 管理的自启动;检测到同名但并非 EvolCore 管理的 ${preserved.join('、')},为避免误删已保留。`,
498
+ };
499
+ }
500
+ if (task.state === 'installed' && task.managed) {
501
+ const result = deleteManagedTask(runtimeRoot);
502
+ if (result.state === 'error') {
503
+ return {
504
+ ok: false,
505
+ enabled: true,
506
+ backend: 'task',
507
+ error: result.error,
508
+ };
509
+ }
510
+ if (result.state === 'preserved')
511
+ preserved.push(`删除前被替换的计划任务 \\${WINDOWS_AUTOSTART_TASK_NAME}`);
512
+ }
513
+ if (run.state === 'installed' && run.managed) {
514
+ const result = deleteManagedRun(runtimeRoot);
515
+ if (result.state === 'error') {
516
+ return {
517
+ ok: false,
518
+ enabled: true,
519
+ backend: 'user-run',
520
+ error: result.error,
521
+ };
522
+ }
523
+ if (result.state === 'preserved')
524
+ preserved.push(`删除前被替换的 HKCU Run 值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME}`);
525
+ }
526
+ removeOwnedWrapper(runtimeRoot);
527
+ removeState(runtimeRoot);
528
+ return {
529
+ ok: true,
530
+ enabled: false,
531
+ ...(preserved.length > 0
532
+ ? { warning: `检测到同名但并非 EvolCore 管理的 ${preserved.join('、')},为避免误删已保留。` }
533
+ : {}),
534
+ };
535
+ }
79
536
  export function configureWindowsAutostart(enabled, runtimeRoot) {
80
537
  if (!isWindows()) {
81
538
  return {
@@ -85,87 +542,287 @@ export function configureWindowsAutostart(enabled, runtimeRoot) {
85
542
  };
86
543
  }
87
544
  const absoluteRoot = path.resolve(runtimeRoot);
545
+ if (!enabled)
546
+ return disableWindowsAutostart(absoluteRoot);
88
547
  const scriptPath = wrapperPath(absoluteRoot);
89
- if (!enabled) {
90
- const result = runSchtasks(['/Delete', '/TN', WINDOWS_AUTOSTART_TASK_NAME, '/F']);
91
- if (result.status !== 0) {
92
- const probe = probeWindowsTask();
93
- if (probe.state === 'not-found') {
94
- try {
95
- fs.rmSync(scriptPath, { force: true });
96
- }
97
- catch { }
98
- return { ok: true, enabled: false };
548
+ let desiredContent;
549
+ try {
550
+ desiredContent = createWrapperContent(absoluteRoot);
551
+ }
552
+ catch (error) {
553
+ return { ok: false, enabled: false, error: error instanceof Error ? error.message : String(error) };
554
+ }
555
+ const previousTask = probeWindowsTask(absoluteRoot);
556
+ if (previousTask.state === 'error') {
557
+ const savedState = readState(absoluteRoot);
558
+ return {
559
+ ok: false,
560
+ enabled: savedState !== undefined,
561
+ ...(savedState ? { backend: savedState.backend } : {}),
562
+ error: previousTask.error,
563
+ };
564
+ }
565
+ if (previousTask.state === 'installed' && !previousTask.managed) {
566
+ return {
567
+ ok: false,
568
+ enabled: false,
569
+ error: `已存在同名计划任务 \\${WINDOWS_AUTOSTART_TASK_NAME},但其启动命令不属于 EvolCore;为避免覆盖已停止配置。`,
570
+ };
571
+ }
572
+ let previousWrapper;
573
+ try {
574
+ if (fs.existsSync(scriptPath))
575
+ previousWrapper = fs.readFileSync(scriptPath, 'utf8');
576
+ }
577
+ catch (error) {
578
+ return {
579
+ ok: false,
580
+ enabled: previousTask.state === 'installed' && previousTask.managed,
581
+ ...(previousTask.state === 'installed' && previousTask.managed ? { backend: 'task' } : {}),
582
+ error: `读取 Windows 登录自启脚本失败:${error instanceof Error ? error.message : String(error)}`,
583
+ };
584
+ }
585
+ const wrapperCurrent = previousWrapper === desiredContent;
586
+ const existingRun = probeWindowsRun(absoluteRoot);
587
+ if (existingRun.state === 'error') {
588
+ const savedState = readState(absoluteRoot);
589
+ const knownBackend = previousTask.state === 'installed' && previousTask.managed
590
+ ? 'task'
591
+ : savedState?.backend;
592
+ return {
593
+ ok: false,
594
+ enabled: knownBackend !== undefined,
595
+ ...(knownBackend ? { backend: knownBackend } : {}),
596
+ error: existingRun.error,
597
+ };
598
+ }
599
+ if (previousTask.state === 'installed' && previousTask.current) {
600
+ if (!wrapperCurrent) {
601
+ try {
602
+ writeWrapper(scriptPath, desiredContent);
603
+ }
604
+ catch (error) {
605
+ const restoreError = restoreWrapper(scriptPath, previousWrapper);
606
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
607
+ return {
608
+ ok: false,
609
+ enabled: true,
610
+ backend: 'task',
611
+ error: `更新 Windows 登录自启脚本失败:${error instanceof Error ? error.message : String(error)}${rollbackSuffix}`,
612
+ };
613
+ }
614
+ }
615
+ let removedManagedRun = false;
616
+ let warning;
617
+ if (existingRun.state === 'installed' && existingRun.managed) {
618
+ const removeRun = deleteManagedRun(absoluteRoot);
619
+ if (removeRun.state === 'error') {
620
+ const stateError = writeState(absoluteRoot, 'task');
621
+ const stateSuffix = stateError ? `;记录当前 task 后端失败:${stateError}` : '';
622
+ return {
623
+ ok: false,
624
+ enabled: true,
625
+ backend: 'task',
626
+ error: `计划任务配置未变化,但无法清理残留的用户级 Run 自启动项:${removeRun.error}${stateSuffix}`,
627
+ };
628
+ }
629
+ removedManagedRun = removeRun.state === 'deleted';
630
+ if (removeRun.state === 'preserved') {
631
+ warning = `HKCU Run 值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME} 在清理前已被其他命令替换;为避免误删已保留。`;
632
+ }
633
+ }
634
+ else if (existingRun.state === 'installed') {
635
+ warning = `检测到同名但并非 EvolCore 管理的 HKCU Run 值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME},为避免误删已保留。`;
636
+ }
637
+ const stateError = writeState(absoluteRoot, 'task');
638
+ if (stateError)
639
+ return { ok: false, enabled: true, backend: 'task', error: `记录自启动后端失败: ${stateError}` };
640
+ return {
641
+ ok: true,
642
+ enabled: true,
643
+ backend: 'task',
644
+ ...(!wrapperCurrent || removedManagedRun ? {} : { unchanged: true }),
645
+ ...(warning ? { warning } : {}),
646
+ };
647
+ }
648
+ if (previousWrapper !== undefined
649
+ && !wrapperOwnedByEvolCore(absoluteRoot)
650
+ && !(previousTask.state === 'installed' && previousTask.managed)
651
+ && !(existingRun.state === 'installed' && existingRun.managed)) {
652
+ return {
653
+ ok: false,
654
+ enabled: false,
655
+ error: `启动脚本 ${scriptPath} 已存在但不属于 EvolCore;为避免覆盖已停止配置。`,
656
+ };
657
+ }
658
+ try {
659
+ writeWrapper(scriptPath, desiredContent);
660
+ }
661
+ catch (error) {
662
+ const restoreError = restoreWrapper(scriptPath, previousWrapper);
663
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
664
+ return {
665
+ ok: false,
666
+ enabled: previousTask.state === 'installed' && previousTask.managed,
667
+ ...(previousTask.state === 'installed' && previousTask.managed ? { backend: 'task' } : {}),
668
+ error: `写入 Windows 登录自启脚本失败:${error instanceof Error ? error.message : String(error)}${rollbackSuffix}`,
669
+ };
670
+ }
671
+ const taskBeforeCreate = probeWindowsTask(absoluteRoot);
672
+ if (taskBeforeCreate.state === 'error') {
673
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, previousTask.state === 'installed' || (existingRun.state === 'installed' && existingRun.managed));
674
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
675
+ return {
676
+ ok: false,
677
+ enabled: previousTask.state === 'installed' && previousTask.managed,
678
+ ...(previousTask.state === 'installed' && previousTask.managed ? { backend: 'task' } : {}),
679
+ error: `创建计划任务前的复核失败:${taskBeforeCreate.error}${rollbackSuffix}`,
680
+ };
681
+ }
682
+ if (taskBeforeCreate.state === 'installed' && !taskBeforeCreate.managed) {
683
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, existingRun.state === 'installed' && existingRun.managed);
684
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
685
+ return {
686
+ ok: false,
687
+ enabled: false,
688
+ error: `计划任务 \\${WINDOWS_AUTOSTART_TASK_NAME} 在创建前已被其他配置占用;为避免覆盖已停止配置${rollbackSuffix}`,
689
+ };
690
+ }
691
+ const args = [
692
+ '/Create',
693
+ '/TN', WINDOWS_AUTOSTART_TASK_NAME,
694
+ '/SC', 'ONLOGON',
695
+ '/TR', taskCommand(scriptPath),
696
+ '/RL', 'LIMITED',
697
+ '/F',
698
+ ];
699
+ const result = runSchtasks(args);
700
+ if (result.status !== 0) {
701
+ if (isAccessDenied(result) && taskBeforeCreate.state === 'not-found') {
702
+ const fallback = createUserRunAutostart(absoluteRoot, scriptPath, wrapperCurrent);
703
+ const fallbackMayBeInstalled = fallback.enabled && fallback.backend === 'user-run';
704
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, fallback.ok || fallbackMayBeInstalled || (existingRun.state === 'installed' && existingRun.managed));
705
+ if (fallback.ok) {
706
+ return {
707
+ ...fallback,
708
+ warning: '任务计划程序拒绝访问,已回退到当前用户 HKCU Run 自启动(无需管理员权限)。如需改用计划任务,请以管理员身份再次运行 ec init --auto-start。',
709
+ };
99
710
  }
711
+ const fallbackError = fallback.error ? `;用户级 Run 回退失败:${fallback.error}` : '';
712
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
100
713
  return {
101
714
  ok: false,
102
- enabled: probe.state === 'installed',
103
- error: commandError(result, '删除 Windows 开机自启任务失败'),
715
+ enabled: fallback.enabled,
716
+ ...(fallback.backend ? { backend: fallback.backend } : {}),
717
+ error: `${accessDeniedError('启用', result)}${fallbackError}${rollbackSuffix}`,
104
718
  };
105
719
  }
106
- try {
107
- fs.rmSync(scriptPath, { force: true });
108
- }
109
- catch { }
110
- return { ok: true, enabled: false };
720
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, taskBeforeCreate.state === 'installed' || (existingRun.state === 'installed' && existingRun.managed));
721
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
722
+ const previousBackend = taskBeforeCreate.state === 'installed'
723
+ ? 'task'
724
+ : existingRun.state === 'installed' && existingRun.managed
725
+ ? 'user-run'
726
+ : undefined;
727
+ return {
728
+ ok: false,
729
+ enabled: previousBackend !== undefined,
730
+ ...(previousBackend ? { backend: previousBackend } : {}),
731
+ error: `${isAccessDenied(result)
732
+ ? accessDeniedError('启用', result)
733
+ : commandError(result, '创建 Windows 登录自启任务失败')}${rollbackSuffix}`,
734
+ };
111
735
  }
112
- let previousWrapper;
113
- let previouslyInstalled = false;
114
- try {
115
- if (fs.existsSync(scriptPath))
116
- previousWrapper = fs.readFileSync(scriptPath, 'utf8');
117
- const previousTask = probeWindowsTask();
118
- if (previousTask.state === 'error') {
119
- return { ok: false, enabled: false, error: previousTask.error };
120
- }
121
- previouslyInstalled = previousTask.state === 'installed';
122
- const taskScript = createWrapper(absoluteRoot);
123
- const systemRoot = process.env.SystemRoot || 'C:\\Windows';
124
- const powershell = path.join(systemRoot, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
125
- const taskCommand = `${windowsCommandLiteral(powershell)} -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File ${windowsCommandLiteral(taskScript)}`;
126
- // Omitting /RU keeps the task attached to the user creating it and avoids a password prompt.
127
- const args = [
128
- '/Create',
129
- '/TN', WINDOWS_AUTOSTART_TASK_NAME,
130
- '/SC', 'ONLOGON',
131
- '/TR', taskCommand,
132
- '/RL', 'LIMITED',
133
- '/F',
134
- ];
135
- const result = runSchtasks(args);
136
- if (result.status !== 0) {
137
- const restoreError = restoreWrapper(scriptPath, previousWrapper);
138
- const probe = probeWindowsTask();
139
- const rollbackSuffix = restoreError ? `; 恢复原启动脚本失败: ${restoreError}` : '';
736
+ const verified = probeWindowsTask(absoluteRoot);
737
+ if (verified.state === 'error') {
738
+ return {
739
+ ok: false,
740
+ enabled: true,
741
+ backend: 'task',
742
+ error: `schtasks /Create 已成功,但验证查询失败,当前状态不确定;已保留 EvolCore 启动脚本,请以管理员身份重试 ec init --auto-start。${verified.error}`,
743
+ };
744
+ }
745
+ if (verified.state === 'not-found') {
746
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, existingRun.state === 'installed' && existingRun.managed);
747
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
748
+ const runStillEnabled = existingRun.state === 'installed' && existingRun.managed;
749
+ return {
750
+ ok: false,
751
+ enabled: runStillEnabled,
752
+ ...(runStillEnabled ? { backend: 'user-run' } : {}),
753
+ error: `schtasks /Create 已成功,但验证时未找到 EvolCore 计划任务${rollbackSuffix}`,
754
+ };
755
+ }
756
+ if (!verified.managed) {
757
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, existingRun.state === 'installed' && existingRun.managed);
758
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
759
+ const runStillEnabled = existingRun.state === 'installed' && existingRun.managed;
760
+ return {
761
+ ok: false,
762
+ enabled: runStillEnabled,
763
+ ...(runStillEnabled ? { backend: 'user-run' } : {}),
764
+ error: `计划任务 \\${WINDOWS_AUTOSTART_TASK_NAME} 在创建后已变为非 EvolCore 配置;为避免误删已保留该任务${rollbackSuffix}`,
765
+ };
766
+ }
767
+ if (!verified.current) {
768
+ if (previousTask.state === 'installed') {
140
769
  return {
141
770
  ok: false,
142
- enabled: probe.state === 'installed',
143
- error: `${commandError(result, '创建 Windows 开机自启任务失败')}${rollbackSuffix}`,
771
+ enabled: true,
772
+ backend: 'task',
773
+ error: '更新后的 EvolCore 计划任务仍与当前配置不一致;为避免破坏原任务,已保留任务和启动脚本,请以管理员身份重试 ec init --auto-start。',
144
774
  };
145
775
  }
146
- const probe = probeWindowsTask();
147
- if (probe.state !== 'installed') {
148
- const cleanup = runSchtasks(['/Delete', '/TN', WINDOWS_AUTOSTART_TASK_NAME, '/F']);
149
- const afterCleanup = cleanup.status === 0 ? probeWindowsTask() : probe;
150
- const restoreError = restoreWrapper(scriptPath, previousWrapper);
151
- const details = probe.state === 'error' ? probe.error : '创建后无法查询到任务';
152
- const cleanupError = cleanup.status === 0 ? '' : `; 清理任务失败: ${commandError(cleanup, '未知错误')}`;
153
- const rollbackSuffix = restoreError ? `; 恢复原启动脚本失败: ${restoreError}` : '';
776
+ const cleanup = deleteManagedTask(absoluteRoot);
777
+ if (cleanup.state === 'error') {
154
778
  return {
155
779
  ok: false,
156
- enabled: afterCleanup.state === 'installed',
157
- error: `${details}${cleanupError}${rollbackSuffix}`,
780
+ enabled: true,
781
+ backend: 'task',
782
+ error: `创建后的 EvolCore 计划任务与当前配置不一致,且清理失败:${cleanup.error};已保留启动脚本。`,
158
783
  };
159
784
  }
160
- return { ok: true, enabled: true };
161
- }
162
- catch (error) {
163
- const restoreError = restoreWrapper(scriptPath, previousWrapper);
164
- const rollbackSuffix = restoreError ? `; 恢复原启动脚本失败: ${restoreError}` : '';
785
+ if (cleanup.state === 'preserved') {
786
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, existingRun.state === 'installed' && existingRun.managed);
787
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
788
+ return {
789
+ ok: false,
790
+ enabled: false,
791
+ error: `计划任务 \\${WINDOWS_AUTOSTART_TASK_NAME} 在回滚前已被其他配置替换;为避免误删已保留该任务${rollbackSuffix}`,
792
+ };
793
+ }
794
+ const restoreError = restoreWrapperUnlessNeeded(scriptPath, previousWrapper, existingRun.state === 'installed' && existingRun.managed);
795
+ const rollbackSuffix = restoreError ? `;恢复原启动脚本失败:${restoreError}` : '';
796
+ const runStillEnabled = existingRun.state === 'installed' && existingRun.managed;
165
797
  return {
166
798
  ok: false,
167
- enabled: previouslyInstalled,
168
- error: `${error instanceof Error ? error.message : String(error)}${rollbackSuffix}`,
799
+ enabled: runStillEnabled,
800
+ ...(runStillEnabled ? { backend: 'user-run' } : {}),
801
+ error: `创建后的 EvolCore 计划任务与当前配置不一致,已安全清理${rollbackSuffix}`,
169
802
  };
170
803
  }
804
+ let warning;
805
+ if (existingRun.state === 'installed' && existingRun.managed) {
806
+ const removeRun = deleteManagedRun(absoluteRoot);
807
+ if (removeRun.state === 'error') {
808
+ const stateError = writeState(absoluteRoot, 'task');
809
+ const stateSuffix = stateError ? `;记录当前 task 后端失败:${stateError}` : '';
810
+ return {
811
+ ok: false,
812
+ enabled: true,
813
+ backend: 'task',
814
+ error: `计划任务已创建并验证成功,但无法清理原用户级 Run 自启动项:${removeRun.error};已保留任务和启动脚本,请重试 ec init --auto-start。${stateSuffix}`,
815
+ };
816
+ }
817
+ if (removeRun.state === 'preserved') {
818
+ warning = `HKCU Run 值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME} 在清理前已被其他命令替换;为避免误删已保留。`;
819
+ }
820
+ }
821
+ else if (existingRun.state === 'installed') {
822
+ warning = `检测到同名但并非 EvolCore 管理的 HKCU Run 值 ${WINDOWS_AUTOSTART_RUN_VALUE_NAME},为避免误删已保留。`;
823
+ }
824
+ const stateError = writeState(absoluteRoot, 'task');
825
+ if (stateError)
826
+ return { ok: false, enabled: true, backend: 'task', error: `记录自启动后端失败: ${stateError}` };
827
+ return { ok: true, enabled: true, backend: 'task', ...(warning ? { warning } : {}) };
171
828
  }