evolcore 0.0.7 → 0.0.9

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 (230) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +38 -4
  3. package/dist/agents/baseagent.js +117 -24
  4. package/dist/agents/claude-runner.js +31 -2
  5. package/dist/agents/codex-app-server-client.js +37 -2
  6. package/dist/agents/codex-runner.js +11 -3
  7. package/dist/aun/aid/control-aid.js +40 -27
  8. package/dist/aun/aid/domain.js +23 -0
  9. package/dist/aun/msg/group.js +9 -9
  10. package/dist/aun/msg/p2p.js +11 -6
  11. package/dist/aun/msg/upload.js +43 -29
  12. package/dist/aun/outbox.js +48 -5
  13. package/dist/channels/aun.js +225 -71
  14. package/dist/cli/aun-commands.js +10 -7
  15. package/dist/cli/bench.js +4 -3
  16. package/dist/cli/daemon-commands.js +208 -68
  17. package/dist/cli/data-command.js +62 -35
  18. package/dist/cli/index.js +11 -1
  19. package/dist/cli/init-channel.js +1 -1
  20. package/dist/cli/init.js +112 -17
  21. package/dist/cli/model.js +5 -5
  22. package/dist/cli/restart-monitor.js +102 -22
  23. package/dist/config/builtin-role-templates.js +4 -3
  24. package/dist/config/config-field-policy.js +6 -2
  25. package/dist/config/config-manager.js +79 -11
  26. package/dist/config/gateway-config.js +80 -35
  27. package/dist/config/role-schema.js +18 -3
  28. package/dist/config/role-service.js +3 -3
  29. package/dist/config/role-store.js +4 -3
  30. package/dist/config-store.js +19 -3
  31. package/dist/core/auth/operation-authorizer.js +3 -1
  32. package/dist/core/baseagent-loader.js +5 -3
  33. package/dist/core/bootstrap-service.js +0 -12
  34. package/dist/core/capability/providers/codex-capability-provider.js +2 -2
  35. package/dist/core/channel-loader.js +15 -2
  36. package/dist/core/command/command-handler.js +1 -1
  37. package/dist/core/command/evol-menu-version-gate.js +3 -2
  38. package/dist/core/command/menu-handler.js +50 -28
  39. package/dist/core/command/menu-protocol.js +7 -4
  40. package/dist/core/command/slash-handler.js +4 -4
  41. package/dist/core/data-migration.js +517 -24
  42. package/dist/core/inference/text-inference.js +18 -5
  43. package/dist/core/message/message-bridge.js +17 -6
  44. package/dist/core/message/response-engine.js +22 -3
  45. package/dist/core/model/config-scope.js +3 -3
  46. package/dist/core/permission/tool-policy.js +18 -0
  47. package/dist/core/protected-paths.js +12 -1
  48. package/dist/index.js +794 -725
  49. package/dist/ipc.js +3 -1
  50. package/dist/product.js +1 -0
  51. package/dist/utils/autostart.js +27 -0
  52. package/dist/utils/codex-cli.js +39 -0
  53. package/dist/utils/cross-platform.js +147 -18
  54. package/dist/utils/ecweb-utils.js +15 -2
  55. package/dist/utils/instance-registry.js +43 -6
  56. package/dist/utils/macos-autostart.js +179 -0
  57. package/dist/utils/process-introspect.js +7 -3
  58. package/dist/utils/windows-autostart.js +130 -0
  59. package/ecagent/dist/agent-loop.d.ts +1 -1
  60. package/ecagent/dist/agent-loop.js +1 -1
  61. package/ecagent/dist/agent.d.ts +1 -1
  62. package/ecagent/dist/agent.js +1 -1
  63. package/ecagent/dist/harness/agent-harness.d.ts +1 -1
  64. package/ecagent/dist/harness/compaction/branch-summarization.d.ts +1 -1
  65. package/ecagent/dist/harness/compaction/compaction.d.ts +1 -1
  66. package/ecagent/dist/harness/compaction/utils.d.ts +1 -1
  67. package/ecagent/dist/harness/messages.d.ts +1 -1
  68. package/ecagent/dist/harness/session/session.d.ts +1 -1
  69. package/ecagent/dist/harness/types.d.ts +1 -1
  70. package/ecagent/dist/proxy.d.ts +1 -1
  71. package/ecagent/dist/proxy.js +1 -1
  72. package/ecagent/dist/types.d.ts +1 -1
  73. package/kits/migrations/migrate-role-config-v5.mjs +27 -2
  74. package/kits/rules/01-overview.md +3 -2
  75. package/kits/schemas/_meta.json +2 -1
  76. package/kits/schemas/agent-config.schema.10.json +19 -1
  77. package/kits/schemas/daemon.schema.4.json +136 -0
  78. package/kits/schemas/defaults.schema.4.json +15 -1
  79. package/kits/schemas/relation-config.schema.8.json +22 -1
  80. package/kits/schemas/role-config.schema.1.json +2 -2
  81. package/package.json +2 -4
  82. package/ecagent/dist/agent-loop.d.ts.map +0 -1
  83. package/ecagent/dist/agent-loop.js.map +0 -1
  84. package/ecagent/dist/agent.d.ts.map +0 -1
  85. package/ecagent/dist/agent.js.map +0 -1
  86. package/ecagent/dist/harness/agent-harness.d.ts.map +0 -1
  87. package/ecagent/dist/harness/agent-harness.js.map +0 -1
  88. package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +0 -1
  89. package/ecagent/dist/harness/compaction/branch-summarization.js.map +0 -1
  90. package/ecagent/dist/harness/compaction/compaction.d.ts.map +0 -1
  91. package/ecagent/dist/harness/compaction/compaction.js.map +0 -1
  92. package/ecagent/dist/harness/compaction/utils.d.ts.map +0 -1
  93. package/ecagent/dist/harness/compaction/utils.js.map +0 -1
  94. package/ecagent/dist/harness/env/nodejs.d.ts.map +0 -1
  95. package/ecagent/dist/harness/env/nodejs.js.map +0 -1
  96. package/ecagent/dist/harness/messages.d.ts.map +0 -1
  97. package/ecagent/dist/harness/messages.js.map +0 -1
  98. package/ecagent/dist/harness/prompt-templates.d.ts.map +0 -1
  99. package/ecagent/dist/harness/prompt-templates.js.map +0 -1
  100. package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +0 -1
  101. package/ecagent/dist/harness/session/jsonl-repo.js.map +0 -1
  102. package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +0 -1
  103. package/ecagent/dist/harness/session/jsonl-storage.js.map +0 -1
  104. package/ecagent/dist/harness/session/memory-repo.d.ts.map +0 -1
  105. package/ecagent/dist/harness/session/memory-repo.js.map +0 -1
  106. package/ecagent/dist/harness/session/memory-storage.d.ts.map +0 -1
  107. package/ecagent/dist/harness/session/memory-storage.js.map +0 -1
  108. package/ecagent/dist/harness/session/repo-utils.d.ts.map +0 -1
  109. package/ecagent/dist/harness/session/repo-utils.js.map +0 -1
  110. package/ecagent/dist/harness/session/session.d.ts.map +0 -1
  111. package/ecagent/dist/harness/session/session.js.map +0 -1
  112. package/ecagent/dist/harness/session/uuid.d.ts.map +0 -1
  113. package/ecagent/dist/harness/session/uuid.js.map +0 -1
  114. package/ecagent/dist/harness/skills.d.ts.map +0 -1
  115. package/ecagent/dist/harness/skills.js.map +0 -1
  116. package/ecagent/dist/harness/system-prompt.d.ts.map +0 -1
  117. package/ecagent/dist/harness/system-prompt.js.map +0 -1
  118. package/ecagent/dist/harness/types.d.ts.map +0 -1
  119. package/ecagent/dist/harness/types.js.map +0 -1
  120. package/ecagent/dist/harness/utils/shell-output.d.ts.map +0 -1
  121. package/ecagent/dist/harness/utils/shell-output.js.map +0 -1
  122. package/ecagent/dist/harness/utils/truncate.d.ts.map +0 -1
  123. package/ecagent/dist/harness/utils/truncate.js.map +0 -1
  124. package/ecagent/dist/index.d.ts.map +0 -1
  125. package/ecagent/dist/index.js.map +0 -1
  126. package/ecagent/dist/node.d.ts.map +0 -1
  127. package/ecagent/dist/node.js.map +0 -1
  128. package/ecagent/dist/proxy.d.ts.map +0 -1
  129. package/ecagent/dist/proxy.js.map +0 -1
  130. package/ecagent/dist/runtime/api/lazy.d.ts.map +0 -1
  131. package/ecagent/dist/runtime/api/lazy.js.map +0 -1
  132. package/ecagent/dist/runtime/api/pi-messages.d.ts.map +0 -1
  133. package/ecagent/dist/runtime/api/pi-messages.js.map +0 -1
  134. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +0 -1
  135. package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +0 -1
  136. package/ecagent/dist/runtime/auth/context.d.ts.map +0 -1
  137. package/ecagent/dist/runtime/auth/context.js.map +0 -1
  138. package/ecagent/dist/runtime/auth/credential-store.d.ts.map +0 -1
  139. package/ecagent/dist/runtime/auth/credential-store.js.map +0 -1
  140. package/ecagent/dist/runtime/auth/helpers.d.ts.map +0 -1
  141. package/ecagent/dist/runtime/auth/helpers.js.map +0 -1
  142. package/ecagent/dist/runtime/auth/resolve.d.ts.map +0 -1
  143. package/ecagent/dist/runtime/auth/resolve.js.map +0 -1
  144. package/ecagent/dist/runtime/auth/types.d.ts.map +0 -1
  145. package/ecagent/dist/runtime/auth/types.js.map +0 -1
  146. package/ecagent/dist/runtime/compat.d.ts.map +0 -1
  147. package/ecagent/dist/runtime/compat.js.map +0 -1
  148. package/ecagent/dist/runtime/index.d.ts.map +0 -1
  149. package/ecagent/dist/runtime/index.js.map +0 -1
  150. package/ecagent/dist/runtime/models.d.ts.map +0 -1
  151. package/ecagent/dist/runtime/models.js.map +0 -1
  152. package/ecagent/dist/runtime/oauth.d.ts.map +0 -1
  153. package/ecagent/dist/runtime/oauth.js.map +0 -1
  154. package/ecagent/dist/runtime/providers/faux.d.ts.map +0 -1
  155. package/ecagent/dist/runtime/providers/faux.js.map +0 -1
  156. package/ecagent/dist/runtime/types.d.ts.map +0 -1
  157. package/ecagent/dist/runtime/types.js.map +0 -1
  158. package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +0 -1
  159. package/ecagent/dist/runtime/utils/diagnostics.js.map +0 -1
  160. package/ecagent/dist/runtime/utils/event-stream.d.ts.map +0 -1
  161. package/ecagent/dist/runtime/utils/event-stream.js.map +0 -1
  162. package/ecagent/dist/runtime/utils/headers.d.ts.map +0 -1
  163. package/ecagent/dist/runtime/utils/headers.js.map +0 -1
  164. package/ecagent/dist/runtime/utils/json-parse.d.ts.map +0 -1
  165. package/ecagent/dist/runtime/utils/json-parse.js.map +0 -1
  166. package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +0 -1
  167. package/ecagent/dist/runtime/utils/oauth/types.js.map +0 -1
  168. package/ecagent/dist/runtime/utils/overflow.d.ts.map +0 -1
  169. package/ecagent/dist/runtime/utils/overflow.js.map +0 -1
  170. package/ecagent/dist/runtime/utils/provider-env.d.ts.map +0 -1
  171. package/ecagent/dist/runtime/utils/provider-env.js.map +0 -1
  172. package/ecagent/dist/runtime/utils/retry.d.ts.map +0 -1
  173. package/ecagent/dist/runtime/utils/retry.js.map +0 -1
  174. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +0 -1
  175. package/ecagent/dist/runtime/utils/typebox-helpers.js.map +0 -1
  176. package/ecagent/dist/runtime/utils/validation.d.ts.map +0 -1
  177. package/ecagent/dist/runtime/utils/validation.js.map +0 -1
  178. package/ecagent/dist/types.d.ts.map +0 -1
  179. package/ecagent/dist/types.js.map +0 -1
  180. package/ecagent/package.json +0 -93
  181. package/ecagent/src/agent-loop.ts +0 -792
  182. package/ecagent/src/agent.ts +0 -575
  183. package/ecagent/src/harness/agent-harness.ts +0 -1029
  184. package/ecagent/src/harness/compaction/branch-summarization.ts +0 -261
  185. package/ecagent/src/harness/compaction/compaction.ts +0 -753
  186. package/ecagent/src/harness/compaction/utils.ts +0 -144
  187. package/ecagent/src/harness/env/nodejs.ts +0 -569
  188. package/ecagent/src/harness/messages.ts +0 -164
  189. package/ecagent/src/harness/prompt-templates.ts +0 -267
  190. package/ecagent/src/harness/session/jsonl-repo.ts +0 -179
  191. package/ecagent/src/harness/session/jsonl-storage.ts +0 -314
  192. package/ecagent/src/harness/session/memory-repo.ts +0 -50
  193. package/ecagent/src/harness/session/memory-storage.ts +0 -133
  194. package/ecagent/src/harness/session/repo-utils.ts +0 -51
  195. package/ecagent/src/harness/session/session.ts +0 -338
  196. package/ecagent/src/harness/session/uuid.ts +0 -54
  197. package/ecagent/src/harness/skills.ts +0 -375
  198. package/ecagent/src/harness/system-prompt.ts +0 -34
  199. package/ecagent/src/harness/types.ts +0 -838
  200. package/ecagent/src/harness/utils/shell-output.ts +0 -135
  201. package/ecagent/src/harness/utils/truncate.ts +0 -344
  202. package/ecagent/src/index.ts +0 -46
  203. package/ecagent/src/node.ts +0 -2
  204. package/ecagent/src/proxy.ts +0 -367
  205. package/ecagent/src/runtime/api/lazy.ts +0 -70
  206. package/ecagent/src/runtime/api/pi-messages.lazy.ts +0 -4
  207. package/ecagent/src/runtime/api/pi-messages.ts +0 -436
  208. package/ecagent/src/runtime/auth/context.ts +0 -45
  209. package/ecagent/src/runtime/auth/credential-store.ts +0 -47
  210. package/ecagent/src/runtime/auth/helpers.ts +0 -46
  211. package/ecagent/src/runtime/auth/resolve.ts +0 -141
  212. package/ecagent/src/runtime/auth/types.ts +0 -182
  213. package/ecagent/src/runtime/compat.ts +0 -158
  214. package/ecagent/src/runtime/index.ts +0 -31
  215. package/ecagent/src/runtime/models.ts +0 -452
  216. package/ecagent/src/runtime/oauth.ts +0 -1
  217. package/ecagent/src/runtime/providers/faux.ts +0 -538
  218. package/ecagent/src/runtime/types.ts +0 -718
  219. package/ecagent/src/runtime/utils/diagnostics.ts +0 -45
  220. package/ecagent/src/runtime/utils/event-stream.ts +0 -88
  221. package/ecagent/src/runtime/utils/headers.ts +0 -18
  222. package/ecagent/src/runtime/utils/json-parse.ts +0 -124
  223. package/ecagent/src/runtime/utils/oauth/types.ts +0 -79
  224. package/ecagent/src/runtime/utils/overflow.ts +0 -165
  225. package/ecagent/src/runtime/utils/provider-env.ts +0 -52
  226. package/ecagent/src/runtime/utils/retry.ts +0 -101
  227. package/ecagent/src/runtime/utils/typebox-helpers.ts +0 -24
  228. package/ecagent/src/runtime/utils/validation.ts +0 -310
  229. package/ecagent/src/types.ts +0 -430
  230. package/ecagent/tsconfig.build.json +0 -32
package/dist/ipc.js CHANGED
@@ -247,6 +247,8 @@ export class IpcServer {
247
247
  return roots;
248
248
  }
249
249
  start() {
250
+ if (this.server)
251
+ return Promise.resolve();
250
252
  // Remove stale socket file (Unix only — named pipes auto-cleanup on process exit)
251
253
  if (!isNamedPipe(this.socketPath)) {
252
254
  try {
@@ -517,7 +519,7 @@ export class IpcServer {
517
519
  mentions: Array.isArray(cmd.mentions)
518
520
  ? cmd.mentions.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry))
519
521
  : undefined,
520
- encrypt: cmd.encrypt === true,
522
+ encrypt: typeof cmd.encrypt === 'boolean' ? cmd.encrypt : undefined,
521
523
  thread: typeof cmd.thread === 'string' ? cmd.thread : undefined,
522
524
  returnPolicy: cmd.returnPolicy === 'required' || cmd.returnPolicy === 'none' ? cmd.returnPolicy : undefined,
523
525
  originSessionId: typeof cmd.originSessionId === 'string' ? cmd.originSessionId : undefined,
package/dist/product.js CHANGED
@@ -10,6 +10,7 @@ export const MAIN_CLI_BIN = 'ec';
10
10
  export const WEB_PACKAGE_NAME = 'ec-web';
11
11
  export const WEB_CLI_BIN = 'ec-web';
12
12
  export const WEB_PACKAGE_LATEST = `${WEB_PACKAGE_NAME}@latest`;
13
+ export const MIN_ECWEB_VERSION = '0.0.8';
13
14
  export const DAEMON_CONFIG_FILENAME = 'daemon.json';
14
15
  export const DAEMON_SCHEMA_NAME = 'daemon';
15
16
  export const RUNTIME_HOME_ENV = 'EVOLCORE_HOME';
@@ -0,0 +1,27 @@
1
+ import { configureMacosAutostart, macosAutostartInstalled } from './macos-autostart.js';
2
+ import { configureWindowsAutostart, windowsAutostartInstalled } from './windows-autostart.js';
3
+ export function autostartInstalled() {
4
+ if (process.platform === 'win32')
5
+ return windowsAutostartInstalled();
6
+ if (process.platform === 'darwin')
7
+ return macosAutostartInstalled();
8
+ return false;
9
+ }
10
+ export function configureAutostart(enabled, runtimeRoot) {
11
+ if (process.platform === 'win32')
12
+ return configureWindowsAutostart(enabled, runtimeRoot);
13
+ if (process.platform === 'darwin')
14
+ return configureMacosAutostart(enabled, runtimeRoot);
15
+ return {
16
+ ok: !enabled,
17
+ enabled: false,
18
+ ...(!enabled ? {} : { error: '登录自启仅支持 Windows 和 macOS 主机' }),
19
+ };
20
+ }
21
+ export function autostartPlatformLabel() {
22
+ if (process.platform === 'win32')
23
+ return 'Windows';
24
+ if (process.platform === 'darwin')
25
+ return 'macOS';
26
+ return undefined;
27
+ }
@@ -0,0 +1,39 @@
1
+ import { execFileSync } from 'child_process';
2
+ import { isWindows, resolveCommandPath } from './cross-platform.js';
3
+ /**
4
+ * Resolve the installed Codex CLI entrypoint. npm creates a `.cmd` shim on
5
+ * Windows, which is not the same executable as the extensionless Unix shim.
6
+ */
7
+ export function resolveCodexCliPath() {
8
+ return resolveCommandPath('codex');
9
+ }
10
+ /**
11
+ * Build a child-process invocation that works for npm's Windows command shim.
12
+ * The options are injectable so platform-specific behavior can be tested on
13
+ * non-Windows CI hosts.
14
+ */
15
+ export function resolveCodexLaunchCommand(args, options = {}) {
16
+ const windows = options.isWindows ?? isWindows;
17
+ const commandPath = options.commandPath !== undefined
18
+ ? options.commandPath
19
+ : resolveCodexCliPath();
20
+ if (!commandPath)
21
+ return null;
22
+ if (windows && /\.(cmd|bat)$/i.test(commandPath)) {
23
+ return {
24
+ command: options.comSpec ?? process.env.ComSpec ?? 'cmd.exe',
25
+ args: ['/d', '/s', '/c', commandPath, ...args],
26
+ };
27
+ }
28
+ return { command: commandPath, args };
29
+ }
30
+ /** Execute a Codex CLI command using the resolved npm shim/path. */
31
+ export function execCodexCliSync(args, options) {
32
+ const commandPath = resolveCodexCliPath();
33
+ if (!commandPath)
34
+ throw new Error('Codex CLI not found');
35
+ const launchCommand = resolveCodexLaunchCommand(args, { commandPath });
36
+ if (!launchCommand)
37
+ throw new Error('Codex CLI not found');
38
+ return execFileSync(launchCommand.command, launchCommand.args, options);
39
+ }
@@ -3,7 +3,7 @@ import { fileURLToPath } from 'url';
3
3
  import { execFileSync, execFile, spawn, spawnSync } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import fs from 'fs';
6
- import { getProcessStartTime } from './process-introspect.js';
6
+ import { getProcessStartTime, parseCimDate } from './process-introspect.js';
7
7
  const execFileAsync = promisify(execFile);
8
8
  export const isWindows = process.platform === 'win32';
9
9
  const ENCODE_PATH_MAX = 200;
@@ -38,6 +38,10 @@ export function encodePath(projectPath) {
38
38
  * Cross-platform process liveness check.
39
39
  */
40
40
  export function isProcessRunning(pid) {
41
+ if (!Number.isInteger(pid) || pid <= 0)
42
+ return false;
43
+ if (isWindows)
44
+ return isWindowsProcessRunning(pid);
41
45
  try {
42
46
  process.kill(pid, 0);
43
47
  return true;
@@ -47,23 +51,65 @@ export function isProcessRunning(pid) {
47
51
  return e.code === 'EPERM';
48
52
  }
49
53
  }
54
+ function isWindowsProcessRunning(pid) {
55
+ try {
56
+ const result = spawnSync('tasklist', ['/FI', `PID eq ${pid}`, '/FO', 'CSV', '/NH'], { encoding: 'utf-8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
57
+ if (result.status === 0 && new RegExp(`,"${pid}",`, 'i').test(result.stdout || ''))
58
+ return true;
59
+ }
60
+ catch { }
61
+ // Keep a conservative fallback for restricted Windows environments where
62
+ // tasklist is unavailable. A failed probe must never be treated as proof of
63
+ // process exit by lifecycle code.
64
+ try {
65
+ process.kill(pid, 0);
66
+ return true;
67
+ }
68
+ catch (e) {
69
+ return e?.code === 'EPERM';
70
+ }
71
+ }
50
72
  /**
51
73
  * Cross-platform process termination.
52
74
  */
53
75
  export function killProcess(pid, force = false) {
54
- if (isWindows && force) {
76
+ if (!Number.isInteger(pid) || pid <= 0)
77
+ return false;
78
+ if (isWindows) {
55
79
  try {
56
- spawnSync('taskkill', ['/PID', String(pid), '/F'], { windowsHide: true });
80
+ const args = ['/PID', String(pid), '/T'];
81
+ if (force)
82
+ args.push('/F');
83
+ const result = spawnSync('taskkill', args, {
84
+ encoding: 'utf-8',
85
+ timeout: 10_000,
86
+ stdio: ['ignore', 'pipe', 'pipe'],
87
+ windowsHide: true,
88
+ });
89
+ return result.status === 0 || !isProcessRunning(pid);
57
90
  }
58
- catch { }
59
- }
60
- else {
61
- try {
62
- process.kill(pid, force ? 'SIGKILL' : 'SIGTERM');
91
+ catch {
92
+ return !isProcessRunning(pid);
63
93
  }
64
- catch { }
94
+ }
95
+ try {
96
+ process.kill(pid, force ? 'SIGKILL' : 'SIGTERM');
97
+ return true;
98
+ }
99
+ catch {
100
+ return !isProcessRunning(pid);
65
101
  }
66
102
  }
103
+ /** Wait until a process has really disappeared from the host process table. */
104
+ export async function waitForProcessExit(pid, timeoutMs = 10_000, intervalMs = 250) {
105
+ const deadline = Date.now() + timeoutMs;
106
+ while (Date.now() < deadline) {
107
+ if (!isProcessRunning(pid))
108
+ return true;
109
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
110
+ }
111
+ return !isProcessRunning(pid);
112
+ }
67
113
  /**
68
114
  * Cross-platform process search by command line pattern.
69
115
  * Returns list of matching PIDs.
@@ -71,11 +117,37 @@ export function killProcess(pid, force = false) {
71
117
  export function findProcesses(pattern) {
72
118
  try {
73
119
  if (isWindows) {
74
- const result = spawnSync('wmic', ['process', 'where', `CommandLine like '%${pattern}%'`, 'get', 'ProcessId'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
75
- const output = result.stdout || '';
76
- return output.split('\n')
77
- .map(line => parseInt(line.trim(), 10))
78
- .filter(pid => !isNaN(pid) && pid !== process.pid);
120
+ // WMI LIKE is wildcard matching, not JavaScript regex matching. Query a
121
+ // stable literal anchor first, then apply the requested regex locally.
122
+ const anchor = windowsProcessSearchAnchor(pattern);
123
+ const escapedAnchor = anchor.replace(/'/g, "''");
124
+ const script = `$a='${escapedAnchor}'; Get-CimInstance Win32_Process | Where-Object { $_.CommandLine -and $_.CommandLine -like ('*' + $a + '*') } | Select-Object ProcessId,CommandLine | ConvertTo-Json -Compress`;
125
+ const result = spawnSync('powershell', ['-NoProfile', '-Command', script], { encoding: null, timeout: 8000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
126
+ const raw = decodeWindowsOutput(result.stdout).trim();
127
+ if (raw) {
128
+ const parsed = JSON.parse(raw);
129
+ const rows = !parsed ? [] : Array.isArray(parsed) ? parsed : [parsed];
130
+ const matcherCandidates = [pattern, pattern.replace(/\\\\/g, '\\')]
131
+ .filter((value, index, values) => values.indexOf(value) === index)
132
+ .map((value) => {
133
+ try {
134
+ return new RegExp(value, 'i');
135
+ }
136
+ catch {
137
+ return null;
138
+ }
139
+ })
140
+ .filter((value) => value !== null);
141
+ return rows
142
+ .filter(row => Number.isInteger(row.ProcessId) && row.ProcessId !== process.pid)
143
+ .filter(row => {
144
+ const commandLine = row.CommandLine || '';
145
+ const normalized = commandLine.replace(/\\/g, '/');
146
+ return matcherCandidates.some(matcher => matcher.test(commandLine) || matcher.test(normalized));
147
+ })
148
+ .map(row => row.ProcessId);
149
+ }
150
+ return [];
79
151
  }
80
152
  else {
81
153
  const output = execFileSync('pgrep', ['-f', pattern], { encoding: 'utf-8' }).trim();
@@ -86,6 +158,10 @@ export function findProcesses(pattern) {
86
158
  return [];
87
159
  }
88
160
  }
161
+ function windowsProcessSearchAnchor(pattern) {
162
+ const tokens = pattern.match(/[A-Za-z0-9][A-Za-z0-9._-]{2,}/g) || [];
163
+ return tokens.sort((a, b) => b.length - a.length)[0] || pattern;
164
+ }
89
165
  /**
90
166
  * Cross-platform: find PIDs listening on a TCP port.
91
167
  * Used to clean up stale/orphaned listeners (e.g. manually-spawned ecweb
@@ -124,13 +200,35 @@ export function findProcessByPort(port) {
124
200
  export function getProcessInfo(pid) {
125
201
  try {
126
202
  if (isWindows) {
203
+ // WMIC was removed from recent Windows installations. Prefer the
204
+ // supported CIM API and keep WMIC as a compatibility fallback.
205
+ const psScript = `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}' | Select-Object WorkingSetSize,CreationDate | ConvertTo-Json -Compress)`;
206
+ const ps = spawnSync('powershell', ['-NoProfile', '-Command', psScript], {
207
+ encoding: null, timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
208
+ });
209
+ const raw = decodeWindowsOutput(ps.stdout).trim();
210
+ if (raw) {
211
+ const processInfo = JSON.parse(raw);
212
+ const memory = Number(processInfo.WorkingSetSize);
213
+ const createdAt = processInfo.CreationDate
214
+ ? parseCimDate(processInfo.CreationDate) ?? parseDateString(processInfo.CreationDate)
215
+ : null;
216
+ return {
217
+ ...(Number.isFinite(memory) && memory > 0 ? { memory: String(Math.round(memory / 1024)) } : {}),
218
+ ...(createdAt !== null ? { uptime: formatUptime(Math.max(0, Math.floor((Date.now() - createdAt) / 1000))) } : {}),
219
+ };
220
+ }
127
221
  const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'WorkingSetSize,CreationDate'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
128
222
  const output = result.stdout || '';
129
223
  const lines = output.trim().split('\n').filter(l => l.trim());
130
224
  if (lines.length >= 2) {
131
225
  const parts = lines[1].trim().split(/\s+/);
132
226
  const memKB = parts[1] ? Math.round(parseInt(parts[1], 10) / 1024) : undefined;
133
- return { memory: memKB ? `${memKB}` : undefined };
227
+ const createdAt = parts[0] ? parseCimDate(parts[0]) ?? parseDateString(parts[0]) : null;
228
+ return {
229
+ ...(memKB ? { memory: `${memKB}` } : {}),
230
+ ...(createdAt !== null ? { uptime: formatUptime(Math.max(0, Math.floor((Date.now() - createdAt) / 1000))) } : {}),
231
+ };
134
232
  }
135
233
  }
136
234
  else {
@@ -167,6 +265,37 @@ function formatUptime(totalSeconds) {
167
265
  parts.push(`${seconds}s`);
168
266
  return parts.join(' ');
169
267
  }
268
+ function parseDateString(value) {
269
+ const microsoftJsonDate = value.match(/^\\?\/Date\(([-+]?\d+)\)\\?\/$/);
270
+ if (microsoftJsonDate)
271
+ return Number(microsoftJsonDate[1]);
272
+ const parsed = Date.parse(value);
273
+ return Number.isNaN(parsed) ? null : parsed;
274
+ }
275
+ /** Decode PowerShell output consistently across Windows 5.1 and pwsh. */
276
+ function decodeWindowsOutput(value) {
277
+ if (!value)
278
+ return '';
279
+ const bytes = Buffer.isBuffer(value) ? value : Buffer.from(value);
280
+ if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe) {
281
+ return bytes.subarray(2).toString('utf16le');
282
+ }
283
+ if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) {
284
+ const swapped = Buffer.allocUnsafe(bytes.length - 2);
285
+ for (let i = 2; i + 1 < bytes.length; i += 2) {
286
+ swapped[i - 2] = bytes[i + 1];
287
+ swapped[i - 1] = bytes[i];
288
+ }
289
+ return swapped.toString('utf16le');
290
+ }
291
+ const utf8 = bytes.toString('utf8').replace(/^\uFEFF/, '');
292
+ if (utf8.includes('\u0000')) {
293
+ const utf16 = bytes.toString('utf16le').replace(/^\uFEFF/, '');
294
+ if (utf16.includes('{') || utf16.includes('[') || utf16.includes('CommandLine'))
295
+ return utf16;
296
+ }
297
+ return utf8;
298
+ }
170
299
  /**
171
300
  * Cross-platform command existence check.
172
301
  */
@@ -178,7 +307,7 @@ export function commandExists(cmd) {
178
307
  let exists = false;
179
308
  try {
180
309
  if (isWindows) {
181
- const r = spawnSync('where', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
310
+ const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
182
311
  exists = r.status === 0;
183
312
  }
184
313
  else {
@@ -194,7 +323,7 @@ export function commandExists(cmd) {
194
323
  }
195
324
  /**
196
325
  * 解析命令的真实可执行文件绝对路径。
197
- * Windows: `where` 会列出全部同名文件(npm 全局 bin 同时生成无后缀 sh 包装、.cmd、.ps1),
326
+ * Windows: `where.exe` 会列出全部同名文件(npm 全局 bin 同时生成无后缀 sh 包装、.cmd、.ps1),
198
327
  * 其中无后缀的那个是 Unix sh 脚本,Windows 无法直接 spawn(ENOENT)。
199
328
  * 因此优先选 PATHEXT 可执行后缀(.cmd/.exe/.bat/.com),都没有才退回首行。
200
329
  * 失败返回 null。不缓存——刚安装的命令需要重新探测。
@@ -202,7 +331,7 @@ export function commandExists(cmd) {
202
331
  export function resolveCommandPath(cmd) {
203
332
  try {
204
333
  if (isWindows) {
205
- const r = spawnSync('where', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
334
+ const r = spawnSync('where.exe', [cmd], { encoding: 'utf-8', stdio: 'pipe', windowsHide: true });
206
335
  if (r.status !== 0 || !r.stdout)
207
336
  return null;
208
337
  const candidates = r.stdout.split(/\r?\n/).map(s => s.trim()).filter(Boolean);
@@ -1,8 +1,21 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
- import { resolveGlobalPkg } from './npm-ops.js';
3
+ import { compareVersions, resolveGlobalPkg } from './npm-ops.js';
4
4
  import { isWindows as platformIsWindows, resolveCommandPath } from './cross-platform.js';
5
- import { WEB_CLI_BIN, WEB_PACKAGE_NAME } from '../product.js';
5
+ import { MIN_ECWEB_VERSION, WEB_CLI_BIN, WEB_PACKAGE_NAME } from '../product.js';
6
+ /** EC Web owns the session-log reader, so older builds cannot safely serve a newer daemon. */
7
+ export function isEcwebVersionSupported(version) {
8
+ const normalized = version?.trim();
9
+ if (!normalized || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(normalized))
10
+ return false;
11
+ return compareVersions(normalized, MIN_ECWEB_VERSION) >= 0;
12
+ }
13
+ export function ecwebVersionRequirementError(version) {
14
+ if (isEcwebVersionSupported(version))
15
+ return null;
16
+ const detected = version?.trim() || 'unknown';
17
+ return `EC Web ${detected} 低于最低要求 ${MIN_ECWEB_VERSION},拒绝启动。请运行: npm install -g ${WEB_PACKAGE_NAME}@latest`;
18
+ }
6
19
  function readEcwebPackageEntry(pkgJsonPath) {
7
20
  try {
8
21
  const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf-8'));
@@ -350,12 +350,20 @@ export function findOrphanProcesses() {
350
350
  // 1. 已登记 PID(自己 HOME 下的 main + 自己进程)
351
351
  const known = new Set([process.pid]);
352
352
  const status = scanInstances();
353
- for (const m of status.mains)
354
- known.add(m.record.pid);
355
- for (const m of status.restartMonitors)
356
- known.add(m.record.pid);
353
+ for (const m of status.mains) {
354
+ if (m.alive)
355
+ known.add(m.record.pid);
356
+ }
357
+ for (const m of status.restartMonitors) {
358
+ if (m.alive)
359
+ known.add(m.record.pid);
360
+ }
357
361
  // 2. 系统中所有跑 dist/index.js 的 node 进程
358
- const candidates = findProcesses('node.*dist/index.js');
362
+ // Use a stable filename anchor for the Windows CIM query. The full path
363
+ // regex contains escaped separators and is applied after command lines have
364
+ // been fetched; embedding it in the CIM pre-filter can silently miss a
365
+ // backslash-delimited `dist\\index.js` command on some PowerShell builds.
366
+ const candidates = findProcesses('index\\.js');
359
367
  const orphans = [];
360
368
  for (const pid of candidates) {
361
369
  if (known.has(pid))
@@ -395,11 +403,21 @@ export function killOrphans(orphans) {
395
403
  }
396
404
  function readCmdline(pid) {
397
405
  if (isWindows) {
406
+ // WMIC is removed from recent Windows installations. Keep it as the
407
+ // first probe for older hosts, then use the supported CIM API when it is
408
+ // unavailable or returns no row.
398
409
  try {
399
410
  const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CommandLine', '/value'], { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
400
411
  const out = result.stdout || '';
401
412
  const m = out.match(/CommandLine=([^\r\n]+)/);
402
- return m ? m[1].trim() : '';
413
+ if (m?.[1]?.trim())
414
+ return m[1].trim();
415
+ }
416
+ catch { }
417
+ try {
418
+ const script = `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CommandLine`;
419
+ const result = spawnSync('powershell', ['-NoProfile', '-Command', script], { encoding: null, timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true });
420
+ return decodeWindowsOutput(result.stdout).trim();
403
421
  }
404
422
  catch {
405
423
  return '';
@@ -422,6 +440,25 @@ function readCmdline(pid) {
422
440
  }
423
441
  }
424
442
  }
443
+ function decodeWindowsOutput(value) {
444
+ if (!value)
445
+ return '';
446
+ const bytes = Buffer.isBuffer(value) ? value : Buffer.from(value);
447
+ if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe)
448
+ return bytes.subarray(2).toString('utf16le');
449
+ if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) {
450
+ const swapped = Buffer.allocUnsafe(bytes.length - 2);
451
+ for (let i = 2; i + 1 < bytes.length; i += 2) {
452
+ swapped[i - 2] = bytes[i + 1];
453
+ swapped[i - 1] = bytes[i];
454
+ }
455
+ return swapped.toString('utf16le');
456
+ }
457
+ const utf8 = bytes.toString('utf8').replace(/^\uFEFF/, '');
458
+ if (utf8.includes('\u0000'))
459
+ return bytes.toString('utf16le').replace(/^\uFEFF/, '');
460
+ return utf8;
461
+ }
425
462
  function readProcessEnvironment(pid) {
426
463
  // Linux: /proc/<pid>/environ
427
464
  if (!isWindows && process.platform !== 'darwin') {
@@ -0,0 +1,179 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { spawnSync } from 'child_process';
5
+ import { getPackageRoot } from '../paths.js';
6
+ export const MACOS_AUTOSTART_LABEL = 'cn.evolai.evolcore';
7
+ function isMacos() {
8
+ return process.platform === 'darwin';
9
+ }
10
+ function plistPath() {
11
+ return path.join(os.homedir(), 'Library', 'LaunchAgents', `${MACOS_AUTOSTART_LABEL}.plist`);
12
+ }
13
+ function launchctl(args) {
14
+ return spawnSync('/bin/launchctl', args, {
15
+ encoding: 'utf8',
16
+ timeout: 15_000,
17
+ });
18
+ }
19
+ function commandError(result, fallback) {
20
+ return String(result.error?.message || result.stderr || result.stdout || fallback).trim();
21
+ }
22
+ function guiDomain() {
23
+ const uid = process.getuid?.();
24
+ return uid === undefined ? undefined : `gui/${uid}`;
25
+ }
26
+ function xmlLiteral(value) {
27
+ return value
28
+ .replace(/&/g, '&amp;')
29
+ .replace(/</g, '&lt;')
30
+ .replace(/>/g, '&gt;')
31
+ .replace(/"/g, '&quot;')
32
+ .replace(/'/g, '&apos;');
33
+ }
34
+ function createPlist(runtimeRoot) {
35
+ const absoluteRoot = path.resolve(runtimeRoot);
36
+ const cliEntry = path.join(getPackageRoot(), 'dist', 'cli', 'index.js');
37
+ if (!fs.existsSync(cliEntry)) {
38
+ throw new Error(`找不到 EvolCore CLI 入口: ${cliEntry}`);
39
+ }
40
+ const environment = [
41
+ ['EVOLCORE_HOME', absoluteRoot],
42
+ ['HOME', os.homedir()],
43
+ ['PATH', Array.from(new Set([
44
+ path.dirname(process.execPath),
45
+ ...(process.env.PATH ?? '').split(path.delimiter).filter(Boolean),
46
+ ])).join(path.delimiter)],
47
+ ];
48
+ const environmentXml = environment
49
+ .map(([key, value]) => ` <key>${xmlLiteral(key)}</key>\n <string>${xmlLiteral(value)}</string>`)
50
+ .join('\n');
51
+ const args = [process.execPath, '--no-warnings=ExperimentalWarning', cliEntry, 'start']
52
+ .map(value => ` <string>${xmlLiteral(value)}</string>`)
53
+ .join('\n');
54
+ return `<?xml version="1.0" encoding="UTF-8"?>
55
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
56
+ <plist version="1.0">
57
+ <dict>
58
+ <key>Label</key>
59
+ <string>${xmlLiteral(MACOS_AUTOSTART_LABEL)}</string>
60
+ <key>ProgramArguments</key>
61
+ <array>
62
+ ${args}
63
+ </array>
64
+ <key>EnvironmentVariables</key>
65
+ <dict>
66
+ ${environmentXml}
67
+ </dict>
68
+ <key>RunAtLoad</key>
69
+ <true/>
70
+ <key>StandardOutPath</key>
71
+ <string>${xmlLiteral(path.join(absoluteRoot, 'logs', 'autostart.stdout.log'))}</string>
72
+ <key>StandardErrorPath</key>
73
+ <string>${xmlLiteral(path.join(absoluteRoot, 'logs', 'autostart.stderr.log'))}</string>
74
+ </dict>
75
+ </plist>
76
+ `;
77
+ }
78
+ function restorePlist(file, previous) {
79
+ try {
80
+ if (previous === undefined)
81
+ fs.rmSync(file, { force: true });
82
+ else
83
+ fs.writeFileSync(file, previous, { encoding: 'utf8', mode: 0o600 });
84
+ return undefined;
85
+ }
86
+ catch (error) {
87
+ return error instanceof Error ? error.message : String(error);
88
+ }
89
+ }
90
+ export function macosAutostartInstalled() {
91
+ if (!isMacos())
92
+ return false;
93
+ const domain = guiDomain();
94
+ if (!domain)
95
+ return false;
96
+ const result = launchctl(['print', `${domain}/${MACOS_AUTOSTART_LABEL}`]);
97
+ return result.status === 0;
98
+ }
99
+ export function configureMacosAutostart(enabled, runtimeRoot) {
100
+ if (!isMacos()) {
101
+ return {
102
+ ok: !enabled,
103
+ enabled: false,
104
+ ...(!enabled ? {} : { error: 'macOS 登录自启仅支持 macOS 主机' }),
105
+ };
106
+ }
107
+ const domain = guiDomain();
108
+ if (!domain) {
109
+ return { ok: false, enabled: false, error: '无法确定当前 macOS 用户 UID' };
110
+ }
111
+ const file = plistPath();
112
+ if (!enabled) {
113
+ const result = launchctl(['bootout', `${domain}/${MACOS_AUTOSTART_LABEL}`]);
114
+ if (result.status !== 0 && macosAutostartInstalled()) {
115
+ return {
116
+ ok: false,
117
+ enabled: true,
118
+ error: commandError(result, '删除 macOS 登录自启失败'),
119
+ };
120
+ }
121
+ try {
122
+ fs.rmSync(file, { force: true });
123
+ }
124
+ catch (error) {
125
+ // Keep the previous state coherent: a LaunchAgents plist that cannot be
126
+ // removed would otherwise silently re-enable the job at the next login.
127
+ const restored = launchctl(['bootstrap', domain, file]);
128
+ return {
129
+ ok: false,
130
+ enabled: restored.status === 0 || macosAutostartInstalled(),
131
+ error: `删除 macOS 登录自启配置失败: ${error instanceof Error ? error.message : String(error)}`,
132
+ };
133
+ }
134
+ return { ok: true, enabled: false };
135
+ }
136
+ let previousPlist;
137
+ let previouslyInstalled = false;
138
+ try {
139
+ const absoluteRoot = path.resolve(runtimeRoot);
140
+ if (fs.existsSync(file)) {
141
+ previousPlist = fs.readFileSync(file, 'utf8');
142
+ previouslyInstalled = macosAutostartInstalled();
143
+ }
144
+ fs.mkdirSync(path.join(absoluteRoot, 'logs'), { recursive: true });
145
+ fs.mkdirSync(path.dirname(file), { recursive: true });
146
+ fs.writeFileSync(file, createPlist(absoluteRoot), { encoding: 'utf8', mode: 0o600 });
147
+ // Re-bootstrap so repeated `ec init --auto-start` updates the command and paths.
148
+ launchctl(['bootout', `${domain}/${MACOS_AUTOSTART_LABEL}`]);
149
+ const result = launchctl(['bootstrap', domain, file]);
150
+ if (result.status !== 0) {
151
+ const restoreError = restorePlist(file, previousPlist);
152
+ let restored = false;
153
+ if (!restoreError && previouslyInstalled && previousPlist !== undefined) {
154
+ const restoreResult = launchctl(['bootstrap', domain, file]);
155
+ restored = restoreResult.status === 0 || macosAutostartInstalled();
156
+ }
157
+ else if (!previouslyInstalled) {
158
+ // A failed bootstrap may still have partially loaded the job.
159
+ launchctl(['bootout', `${domain}/${MACOS_AUTOSTART_LABEL}`]);
160
+ }
161
+ const rollbackSuffix = restoreError ? `; 恢复原配置失败: ${restoreError}` : '';
162
+ return {
163
+ ok: false,
164
+ enabled: restored,
165
+ error: `${commandError(result, '创建 macOS 登录自启失败')}${rollbackSuffix}`,
166
+ };
167
+ }
168
+ return { ok: true, enabled: true };
169
+ }
170
+ catch (error) {
171
+ const restoreError = restorePlist(file, previousPlist);
172
+ const rollbackSuffix = restoreError ? `; 恢复原配置失败: ${restoreError}` : '';
173
+ return {
174
+ ok: false,
175
+ enabled: previouslyInstalled,
176
+ error: `${error instanceof Error ? error.message : String(error)}${rollbackSuffix}`,
177
+ };
178
+ }
179
+ }
@@ -115,12 +115,12 @@ function winPowerShellCreationDate(pid) {
115
115
  const result = spawnSync('powershell', [
116
116
  '-NoProfile',
117
117
  '-Command',
118
- `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CreationDate`,
118
+ `(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CreationDate.ToUniversalTime().ToString('o')`,
119
119
  ], { encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
120
120
  const out = result.stdout?.trim();
121
121
  if (!out)
122
122
  return null;
123
- return parseCimDate(out);
123
+ return parseCimDate(out) ?? parseDateString(out);
124
124
  }
125
125
  function winWmicCreationDate(pid) {
126
126
  const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CreationDate', '/value'], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
@@ -128,7 +128,11 @@ function winWmicCreationDate(pid) {
128
128
  const m = out.match(/CreationDate=([^\r\n]+)/);
129
129
  if (!m)
130
130
  return null;
131
- return parseCimDate(m[1].trim());
131
+ return parseCimDate(m[1].trim()) ?? parseDateString(m[1].trim());
132
+ }
133
+ function parseDateString(value) {
134
+ const parsed = Date.parse(value);
135
+ return Number.isNaN(parsed) ? null : parsed;
132
136
  }
133
137
  /**
134
138
  * 解析 CIM/WMI 日期格式:yyyyMMddHHmmss.ffffff±TZZZ