evolcore 0.0.1 → 0.0.3
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.
- package/CHANGELOG.md +64 -0
- package/LICENSE +21 -0
- package/MIGRATION-0.5.0.md +378 -0
- package/README.md +318 -14
- package/ROLE_ACCESS_CONTROL.md +174 -0
- package/assets/.env.template +4 -0
- package/bin/ec-safe-output.js +161 -0
- package/bin/ec.js +29 -0
- package/dist/agents/baseagent.js +163 -0
- package/dist/agents/claude-runner.js +2565 -0
- package/dist/agents/codex-app-server-client.js +448 -0
- package/dist/agents/codex-runner.js +2682 -0
- package/dist/agents/gemini-runner.js +666 -0
- package/dist/agents/runner-types.js +75 -0
- package/dist/aun/aid/agentmd.js +216 -0
- package/dist/aun/aid/client.js +132 -0
- package/dist/aun/aid/control-aid.js +91 -0
- package/dist/aun/aid/identity.js +518 -0
- package/dist/aun/aid/index.js +4 -0
- package/dist/aun/aid/store.js +74 -0
- package/dist/aun/aid/types.js +1 -0
- package/dist/aun/aid/validation.js +21 -0
- package/dist/aun/group-identity.js +10 -0
- package/dist/aun/msg/group-index.js +6 -0
- package/dist/aun/msg/group.js +1231 -0
- package/dist/aun/msg/history.js +123 -0
- package/dist/aun/msg/index.js +5 -0
- package/dist/aun/msg/p2p.js +393 -0
- package/dist/aun/msg/payload-type.js +27 -0
- package/dist/aun/msg/upload.js +137 -0
- package/dist/aun/outbox.js +168 -0
- package/dist/aun/rpc/caller.js +42 -0
- package/dist/aun/rpc/connection.js +25 -0
- package/dist/aun/rpc/index.js +2 -0
- package/dist/aun/service-proxy.js +225 -0
- package/dist/aun/storage/download.js +29 -0
- package/dist/aun/storage/index.js +3 -0
- package/dist/aun/storage/manage.js +10 -0
- package/dist/aun/storage/upload.js +68 -0
- package/dist/channels/aun.js +4164 -0
- package/dist/channels/contact-bind-code.js +134 -0
- package/dist/channels/daemon.js +422 -0
- package/dist/channels/dingtalk.js +1479 -0
- package/dist/channels/feishu.js +1865 -0
- package/dist/channels/qqbot.js +409 -0
- package/dist/channels/wechat.js +817 -0
- package/dist/channels/wecom-card.js +101 -0
- package/dist/channels/wecom-onboarding.js +82 -0
- package/dist/channels/wecom-state.js +191 -0
- package/dist/channels/wecom.js +1157 -0
- package/dist/cli/agent-command.js +642 -0
- package/dist/cli/agent.js +1059 -0
- package/dist/cli/aun-commands.js +2003 -0
- package/dist/cli/bench.js +1228 -0
- package/dist/cli/cli-argv.js +66 -0
- package/dist/cli/code-stats.js +329 -0
- package/dist/cli/command-log.js +82 -0
- package/dist/cli/config-selector.js +69 -0
- package/dist/cli/config.js +261 -0
- package/dist/cli/contact.js +71 -0
- package/dist/cli/ctl-command.js +62 -0
- package/dist/cli/daemon-commands.js +2750 -0
- package/dist/cli/fs-command.js +1447 -0
- package/dist/cli/handoff-command.js +302 -0
- package/dist/cli/help.js +35 -0
- package/dist/cli/index.js +374 -0
- package/dist/cli/init-channel.js +1372 -0
- package/dist/cli/init.js +590 -0
- package/dist/cli/link-rules.js +240 -0
- package/dist/cli/model.js +591 -0
- package/dist/cli/net-check.js +723 -0
- package/dist/cli/queue-command.js +150 -0
- package/dist/cli/raw-key-input.js +25 -0
- package/dist/cli/response.js +344 -0
- package/dist/cli/restart-monitor.js +480 -0
- package/dist/cli/stats.js +609 -0
- package/dist/cli/task-context.js +80 -0
- package/dist/cli/trigger-command.js +545 -0
- package/dist/cli/version.js +93 -0
- package/dist/cli/watch-logs.js +33 -0
- package/dist/cli/watch-msg.js +673 -0
- package/dist/config/boot-log.js +266 -0
- package/dist/config/builtin-role-templates.js +42 -0
- package/dist/config/builtin-roles.js +91 -0
- package/dist/config/config-batch-get.js +11 -0
- package/dist/config/config-field-policy.js +261 -0
- package/dist/config/config-manager.js +1120 -0
- package/dist/config/config-operation-service.js +384 -0
- package/dist/config/contact-alias.js +68 -0
- package/dist/config/contact-book-store.js +454 -0
- package/dist/config/contact-book-v2-startup.js +35 -0
- package/dist/config/contact-book.js +224 -0
- package/dist/config/contact-operation-service.js +110 -0
- package/dist/config/gateway-config.js +858 -0
- package/dist/config/lifecycle.js +17 -0
- package/dist/config/mention-mode.js +27 -0
- package/dist/config/merge.js +161 -0
- package/dist/config/owner-policy.js +4 -0
- package/dist/config/peer-role-resolver.js +218 -0
- package/dist/config/resolved-config-op.js +483 -0
- package/dist/config/role-config-v4-startup.js +32 -0
- package/dist/config/role-config-v5-startup.js +27 -0
- package/dist/config/role-ranks.js +18 -0
- package/dist/config/role-schema.js +105 -0
- package/dist/config/role-service.js +156 -0
- package/dist/config/role-store.js +215 -0
- package/dist/config/roles.js +64 -0
- package/dist/config/schema-registry.js +154 -0
- package/dist/config/snapshot.js +598 -0
- package/dist/config-store.js +501 -0
- package/dist/core/auth/agent-delegation.js +111 -0
- package/dist/core/auth/auth-gateway.js +166 -0
- package/dist/core/auth/authenticated-actor.js +6 -0
- package/dist/core/auth/authorization-audit.js +119 -0
- package/dist/core/auth/operation-authorizer.js +720 -0
- package/dist/core/auth/operation-catalog.js +731 -0
- package/dist/core/baseagent-loader.js +54 -0
- package/dist/core/bootstrap-service.js +175 -0
- package/dist/core/capability/capability-manager.js +316 -0
- package/dist/core/capability/providers/claude-capability-provider.js +176 -0
- package/dist/core/capability/providers/codex-capability-provider.js +148 -0
- package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
- package/dist/core/capability/types.js +27 -0
- package/dist/core/causation/audit.js +103 -0
- package/dist/core/causation/aun-association.js +111 -0
- package/dist/core/causation/context.js +93 -0
- package/dist/core/causation/index.js +4 -0
- package/dist/core/causation/types.js +2 -0
- package/dist/core/channel-loader.js +277 -0
- package/dist/core/command/agent-control.js +616 -0
- package/dist/core/command/cli-intent-parser.js +225 -0
- package/dist/core/command/command-handler.js +1733 -0
- package/dist/core/command/connect-menu.js +374 -0
- package/dist/core/command/menu-handler.js +3452 -0
- package/dist/core/command/menu-protocol.js +247 -0
- package/dist/core/command/role-menu.js +1623 -0
- package/dist/core/command/slash-gate.js +148 -0
- package/dist/core/command/slash-handler.js +3066 -0
- package/dist/core/daemon-file-cache.js +222 -0
- package/dist/core/event-bus.js +32 -0
- package/dist/core/event-catalog.js +810 -0
- package/dist/core/evolagent-registry.js +545 -0
- package/dist/core/evolagent.js +342 -0
- package/dist/core/handoff/dispatcher.js +229 -0
- package/dist/core/handoff/mutex.js +46 -0
- package/dist/core/handoff/runtime.js +324 -0
- package/dist/core/handoff/store.js +537 -0
- package/dist/core/handoff/types.js +12 -0
- package/dist/core/inference/text-inference.js +173 -0
- package/dist/core/interaction-registration.js +10 -0
- package/dist/core/interaction-router.js +278 -0
- package/dist/core/message/create-status.js +67 -0
- package/dist/core/message/im-renderer.js +659 -0
- package/dist/core/message/items-formatter.js +76 -0
- package/dist/core/message/logical-queue-bridge.js +123 -0
- package/dist/core/message/message-bridge.js +874 -0
- package/dist/core/message/message-cache.js +56 -0
- package/dist/core/message/message-log.js +339 -0
- package/dist/core/message/message-processor.js +4 -0
- package/dist/core/message/message-queue.js +1446 -0
- package/dist/core/message/message-utils.js +76 -0
- package/dist/core/message/peer-mode.js +105 -0
- package/dist/core/message/pending-hints.js +232 -0
- package/dist/core/message/response-depth.js +33 -0
- package/dist/core/message/response-engine.js +4020 -0
- package/dist/core/message/response-snapshot.js +83 -0
- package/dist/core/message/send-receipt.js +24 -0
- package/dist/core/message/stream-debouncer.js +139 -0
- package/dist/core/message/stream-idle-monitor.js +124 -0
- package/dist/core/model/config-scope.js +162 -0
- package/dist/core/model/field-scope.js +78 -0
- package/dist/core/model/model-catalog.js +227 -0
- package/dist/core/model/model-diagnostics.js +182 -0
- package/dist/core/model/model-permission.js +90 -0
- package/dist/core/permission/approval-gateway.js +1017 -0
- package/dist/core/permission/ec-command-parser.js +347 -0
- package/dist/core/permission/execution-sandbox.js +16 -0
- package/dist/core/permission/index.js +6 -0
- package/dist/core/permission/mode.js +24 -0
- package/dist/core/permission/sandbox-runtime.js +265 -0
- package/dist/core/permission/tool-policy.js +1019 -0
- package/dist/core/permission/unix-socket-policy.js +99 -0
- package/dist/core/protected-paths.js +332 -0
- package/dist/core/relation/peer-identity.js +222 -0
- package/dist/core/relation/peer-key.js +1 -0
- package/dist/core/role/runtime-policy.js +141 -0
- package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
- package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
- package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
- package/dist/core/session/session-file-adapter.js +7 -0
- package/dist/core/session/session-file-health.js +45 -0
- package/dist/core/session/session-fs-store.js +273 -0
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +1643 -0
- package/dist/core/session/session-mapper.js +100 -0
- package/dist/core/session/session-renew.js +314 -0
- package/dist/core/session/session-title.js +128 -0
- package/dist/core/session/session-turn-coordinator.js +205 -0
- package/dist/core/session/session-turns.js +67 -0
- package/dist/core/system-channels.js +29 -0
- package/dist/eck/baseagent-caps.js +18 -0
- package/dist/eck/detect.js +47 -0
- package/dist/eck/group-rules-sync.js +345 -0
- package/dist/eck/init.js +77 -0
- package/dist/eck/kit-renderer.js +359 -0
- package/dist/eck/manifest-engine.js +446 -0
- package/dist/eck/message-renderer.js +199 -0
- package/dist/eck/rules-loader.js +28 -0
- package/dist/index.js +2926 -4
- package/dist/ipc.js +777 -0
- package/dist/paths.js +262 -0
- package/dist/product.js +18 -0
- package/dist/response-system/context-builder.js +71 -0
- package/dist/response-system/coordinator.js +117 -0
- package/dist/response-system/decision-executor.js +86 -0
- package/dist/response-system/engines/v1/index.js +21 -0
- package/dist/response-system/engines/v1/interactive-flow.js +27 -0
- package/dist/response-system/engines/v1/proactive-flow.js +137 -0
- package/dist/response-system/engines/v1/types.js +1 -0
- package/dist/response-system/extensions.js +41 -0
- package/dist/response-system/index.js +6 -0
- package/dist/response-system/modes/index.js +7 -0
- package/dist/response-system/modes/single-session/index.js +72 -0
- package/dist/response-system/queues/fifo-queue.js +44 -0
- package/dist/response-system/queues/index.js +6 -0
- package/dist/response-system/queues/lifo-queue.js +42 -0
- package/dist/response-system/queues/priority-queue.js +63 -0
- package/dist/response-system/registry.js +97 -0
- package/dist/response-system/resolver.js +37 -0
- package/dist/response-system/selector.js +23 -0
- package/dist/response-system/types.js +7 -0
- package/dist/stats/billing.js +163 -0
- package/dist/stats/budget.js +93 -0
- package/dist/stats/db.js +403 -0
- package/dist/stats/eck-vars.js +89 -0
- package/dist/stats/index.js +11 -0
- package/dist/stats/normalizer.js +80 -0
- package/dist/stats/price-resolver.js +138 -0
- package/dist/stats/query.js +763 -0
- package/dist/stats/role-budget.js +168 -0
- package/dist/stats/writer.js +151 -0
- package/dist/trigger/anomaly-store.js +258 -0
- package/dist/trigger/audit.js +152 -0
- package/dist/trigger/event-source.js +119 -0
- package/dist/trigger/feedback.js +685 -0
- package/dist/trigger/history.js +290 -0
- package/dist/trigger/manager.js +294 -0
- package/dist/trigger/parser.js +595 -0
- package/dist/trigger/patch.js +155 -0
- package/dist/trigger/scheduler.js +1602 -0
- package/dist/trigger/script-executor.js +155 -0
- package/dist/trigger/state.js +145 -0
- package/dist/trigger/types.js +1 -0
- package/dist/trigger/validation.js +634 -0
- package/dist/types.js +12 -0
- package/dist/utils/aid-bind.js +313 -0
- package/dist/utils/atomic-write.js +95 -0
- package/dist/utils/avatar-upload.js +123 -0
- package/dist/utils/cross-platform.js +297 -0
- package/dist/utils/ecweb-utils.js +73 -0
- package/dist/utils/error-dict.json +153 -0
- package/dist/utils/error-utils.js +349 -0
- package/dist/utils/instance-registry.js +444 -0
- package/dist/utils/locale.js +21 -0
- package/dist/utils/log-writer.js +270 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/markdown-to-plain-text.js +20 -0
- package/dist/utils/media-cache.js +274 -0
- package/dist/utils/model-prices.jsonl +20 -0
- package/dist/utils/npm-ops.js +210 -0
- package/dist/utils/process-introspect.js +133 -0
- package/dist/utils/process-tree-stats.js +271 -0
- package/dist/utils/project-path.js +74 -0
- package/dist/utils/restart-safety.js +31 -0
- package/dist/utils/stats.js +410 -0
- package/dist/utils/system-memory.js +62 -0
- package/dist/utils/tool-summary.js +284 -0
- package/dist/utils/welcome.js +268 -0
- package/kits/docs/GUIDE.md +20 -0
- package/kits/docs/INDEX.md +66 -0
- package/kits/docs/aun/CHEATSHEET.md +19 -0
- package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
- package/kits/docs/channels/aun.md +65 -0
- package/kits/docs/channels/feishu.md +56 -0
- package/kits/docs/context-assembly.md +366 -0
- package/kits/docs/eck_templates/GUIDE.template.md +22 -0
- package/kits/docs/eck_templates/INDEX.template.md +28 -0
- package/kits/docs/eck_templates/path-registry.template.md +33 -0
- package/kits/docs/eck_templates/runtime.template.md +19 -0
- package/kits/docs/evolcore/INDEX.md +68 -0
- package/kits/docs/evolcore/agent.md +77 -0
- package/kits/docs/evolcore/aid.md +52 -0
- package/kits/docs/evolcore/config.md +149 -0
- package/kits/docs/evolcore/contact.md +57 -0
- package/kits/docs/evolcore/ctl.md +46 -0
- package/kits/docs/evolcore/event.md +216 -0
- package/kits/docs/evolcore/fs-architecture.md +1215 -0
- package/kits/docs/evolcore/fs.md +110 -0
- package/kits/docs/evolcore/group-fs.md +17 -0
- package/kits/docs/evolcore/group-rules.md +226 -0
- package/kits/docs/evolcore/group.md +150 -0
- package/kits/docs/evolcore/model.md +50 -0
- package/kits/docs/evolcore/msg.md +136 -0
- package/kits/docs/evolcore/response.md +75 -0
- package/kits/docs/evolcore/rpc.md +37 -0
- package/kits/docs/evolcore/self-summary.md +29 -0
- package/kits/docs/evolcore/stats.md +83 -0
- package/kits/docs/evolcore/storage.md +50 -0
- package/kits/docs/evolcore/trigger.md +539 -0
- package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
- package/kits/docs/identity/PATH_OPS.md +16 -0
- package/kits/docs/identity/ROLE_DETAIL.md +23 -0
- package/kits/docs/identity/identity-tools.md +26 -0
- package/kits/docs/path-registry.md +43 -0
- package/kits/docs/prompt-loading-architecture.md +266 -0
- package/kits/docs/venues/aun-group.md +45 -0
- package/kits/docs/venues/aun-private.md +10 -0
- package/kits/docs/venues/client-desktop.md +10 -0
- package/kits/docs/venues/client-mobile.md +10 -0
- package/kits/docs/venues/feishu-group.md +13 -0
- package/kits/docs/venues/feishu-private.md +9 -0
- package/kits/docs/venues/group.md +25 -0
- package/kits/docs/venues/private.md +10 -0
- package/kits/eck_manifest.auxiliary.json +43 -0
- package/kits/eck_manifest.json +203 -0
- package/kits/eck_message_manifest.json +63 -0
- package/kits/migrations/README-role-config-v4.md +32 -0
- package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
- package/kits/migrations/migrate-role-config-v4.mjs +623 -0
- package/kits/migrations/migrate-role-config-v5.mjs +346 -0
- package/kits/migrations/rename-config-file.mjs +99 -0
- package/kits/rules/01-overview.md +142 -0
- package/kits/rules/02-navigation.md +76 -0
- package/kits/rules/03-identity.md +34 -0
- package/kits/rules/04-relation.md +59 -0
- package/kits/rules/05-venue.md +44 -0
- package/kits/rules/06-channel.md +59 -0
- package/kits/schemas/_meta.json +32 -0
- package/kits/schemas/agent-config.schema.1.json +177 -0
- package/kits/schemas/agent-config.schema.2.json +239 -0
- package/kits/schemas/agent-config.schema.3.json +119 -0
- package/kits/schemas/agent-config.schema.4.json +208 -0
- package/kits/schemas/agent-config.schema.5.json +326 -0
- package/kits/schemas/agent-config.schema.6.json +322 -0
- package/kits/schemas/contact-book.schema.1.json +36 -0
- package/kits/schemas/contact-book.schema.2.json +43 -0
- package/kits/schemas/daemon.schema.1.json +90 -0
- package/kits/schemas/defaults.schema.1.json +81 -0
- package/kits/schemas/menu-exec-schema-commands.md +208 -0
- package/kits/schemas/migrations/README.md +28 -0
- package/kits/schemas/relation-config.schema.1.json +158 -0
- package/kits/schemas/relation-config.schema.2.json +73 -0
- package/kits/schemas/relation-config.schema.3.json +50 -0
- package/kits/schemas/relation-config.schema.4.json +47 -0
- package/kits/schemas/relation-config.schema.5.json +47 -0
- package/kits/schemas/role-config.schema.1.json +201 -0
- package/kits/schemas/role-registry.schema.1.json +35 -0
- package/kits/schemas/single-session.schema.1.json +31 -0
- package/kits/templates/bootstrap-welcome.md +15 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
- package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
- package/kits/templates/message-fragments/inject-default.md +2 -0
- package/kits/templates/message-fragments/item.md +2 -0
- package/kits/templates/roles/admin.json +9 -0
- package/kits/templates/roles/member.json +41 -0
- package/kits/templates/roles/owner.json +9 -0
- package/kits/templates/roles/visitor.json +40 -0
- package/kits/templates/system-fragments/baseagent.md +14 -0
- package/kits/templates/system-fragments/bootstrap.md +16 -0
- package/kits/templates/system-fragments/channel.md +48 -0
- package/kits/templates/system-fragments/commands.md +28 -0
- package/kits/templates/system-fragments/identity.md +11 -0
- package/kits/templates/system-fragments/relation.md +19 -0
- package/kits/templates/system-fragments/session.md +53 -0
- package/kits/templates/system-fragments/venue.md +31 -0
- package/package.json +50 -15
- package/dist/index.d.ts +0 -7
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* npm operations
|
|
3
|
+
*
|
|
4
|
+
* 集中管理本仓库所有 `npm install -g` / `npm view` 相关的子进程调用:
|
|
5
|
+
* - tryUpgrade() — evolcore 自我升级
|
|
6
|
+
* - requireOptional() — 可选依赖动态加载 + 自动安装
|
|
7
|
+
* - npmInstallGlobal() — 全局安装(含 EACCES → sudo 回退、Windows npm.cmd)
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { execFile, execSync } from 'child_process';
|
|
12
|
+
import { promisify } from 'util';
|
|
13
|
+
import { getPackageRoot } from '../paths.js';
|
|
14
|
+
import { isWindows } from './cross-platform.js';
|
|
15
|
+
const execFileAsync = promisify(execFile);
|
|
16
|
+
// ── npm install -g (shared) ────────────────────────────────────────────────
|
|
17
|
+
export async function npmInstallGlobal(pkg) {
|
|
18
|
+
let cmd;
|
|
19
|
+
let args;
|
|
20
|
+
// Windows: run via cmd /c to avoid shell:true deprecation warning on Node 22.
|
|
21
|
+
// Unix: npm directly, no shell needed.
|
|
22
|
+
if (isWindows) {
|
|
23
|
+
cmd = 'cmd';
|
|
24
|
+
args = ['/c', 'npm', 'install', '-g', pkg];
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
cmd = 'npm';
|
|
28
|
+
args = ['install', '-g', pkg];
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
await execFileAsync(cmd, args, { timeout: 180000 });
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
if (e.stderr?.includes('EACCES') || e.message?.includes('EACCES')) {
|
|
35
|
+
if (isWindows) {
|
|
36
|
+
throw new Error('权限不足。请以管理员身份运行 PowerShell 或 CMD,然后重试');
|
|
37
|
+
}
|
|
38
|
+
await execFileAsync('sudo', ['npm', 'install', '-g', pkg], { timeout: 180000 });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** Dynamic import with auto-install fallback for optional dependencies */
|
|
46
|
+
export async function requireOptional(pkg, autoInstall = true) {
|
|
47
|
+
try {
|
|
48
|
+
return await import(pkg);
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
if (e.code !== 'ERR_MODULE_NOT_FOUND' && e.code !== 'MODULE_NOT_FOUND')
|
|
52
|
+
throw e;
|
|
53
|
+
if (!autoInstall)
|
|
54
|
+
throw new Error(`依赖 ${pkg} 未安装。请运行: npm install -g ${pkg}`);
|
|
55
|
+
const { logger } = await import('./logger.js');
|
|
56
|
+
logger.info(`正在安装可选依赖 ${pkg}...`);
|
|
57
|
+
await npmInstallGlobal(pkg);
|
|
58
|
+
return await import(pkg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* 比较两个 semver 版本号 (a.b.c 格式)
|
|
63
|
+
* 返回 -1 (a < b), 0 (a == b), 1 (a > b)
|
|
64
|
+
* 自动剥离 pre-release 标签 (e.g. 2.6.0-beta.1 → 2.6.0)
|
|
65
|
+
*/
|
|
66
|
+
export function compareVersions(a, b) {
|
|
67
|
+
const pa = a.split('-')[0].split('.').map(Number);
|
|
68
|
+
const pb = b.split('-')[0].split('.').map(Number);
|
|
69
|
+
const len = Math.max(pa.length, pb.length);
|
|
70
|
+
for (let i = 0; i < len; i++) {
|
|
71
|
+
const na = pa[i] ?? 0;
|
|
72
|
+
const nb = pb[i] ?? 0;
|
|
73
|
+
if (na < nb)
|
|
74
|
+
return -1;
|
|
75
|
+
if (na > nb)
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 检查当前安装是否为 npm link 开发模式。
|
|
82
|
+
* 正式全局安装的路径结构为 .../node_modules/evolcore,
|
|
83
|
+
* 而 npm link 指向项目源码目录,其父目录不是 node_modules。
|
|
84
|
+
*/
|
|
85
|
+
export function isLinkedInstall() {
|
|
86
|
+
const pkgRoot = getPackageRoot();
|
|
87
|
+
return path.basename(path.dirname(pkgRoot)) !== 'node_modules';
|
|
88
|
+
}
|
|
89
|
+
/** 获取本地 package.json 中的版本号 */
|
|
90
|
+
export function getLocalVersion() {
|
|
91
|
+
const pkgPath = path.join(getPackageRoot(), 'package.json');
|
|
92
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
93
|
+
return pkg.version;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* 解析全局安装包的已装版本(通过 `npm root -g` 定位全局 node_modules)。
|
|
97
|
+
* 用于 EC Web 这类独立全局命令包——它们不在主包的依赖树里。
|
|
98
|
+
* 未安装或查询失败返回 null。
|
|
99
|
+
*/
|
|
100
|
+
export function resolveGlobalPkg(pkgName) {
|
|
101
|
+
try {
|
|
102
|
+
// 命令完全静态(npm root -g,无用户输入),用 execSync 传完整命令字符串,
|
|
103
|
+
// 避免 "args 数组 + shell:true" 组合触发 Node DeprecationWarning,且无注入风险。
|
|
104
|
+
const npmCmd = isWindows ? 'npm.cmd' : 'npm';
|
|
105
|
+
const globalRoot = execSync(`${npmCmd} root -g`, {
|
|
106
|
+
encoding: 'utf-8', timeout: 10000,
|
|
107
|
+
}).trim();
|
|
108
|
+
const pkgPath = path.join(globalRoot, ...pkgName.split('/'), 'package.json');
|
|
109
|
+
if (fs.existsSync(pkgPath)) {
|
|
110
|
+
const data = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
111
|
+
return { version: data.version, path: pkgPath };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch { /* not found */ }
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 查询 npm registry 上指定包的最新版本。
|
|
119
|
+
* 使用 HTTP fetch 直接查 registry API,不依赖 npm CLI。
|
|
120
|
+
* 超时 10 秒,失败返回 null。
|
|
121
|
+
*/
|
|
122
|
+
export async function checkLatestVersion(pkg = 'evolcore') {
|
|
123
|
+
try {
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
const timer = setTimeout(() => controller.abort(), 10000);
|
|
126
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`, {
|
|
127
|
+
signal: controller.signal,
|
|
128
|
+
headers: { 'Accept': 'application/json' },
|
|
129
|
+
});
|
|
130
|
+
clearTimeout(timer);
|
|
131
|
+
if (!res.ok)
|
|
132
|
+
return null;
|
|
133
|
+
const data = await res.json();
|
|
134
|
+
return data.version || null;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 完整升级流程:检查 → 比较 → 安装(失败重试一次)
|
|
142
|
+
*/
|
|
143
|
+
export async function tryUpgrade() {
|
|
144
|
+
// 开发模式跳过
|
|
145
|
+
if (isLinkedInstall()) {
|
|
146
|
+
return { status: 'skipped' };
|
|
147
|
+
}
|
|
148
|
+
const localVer = getLocalVersion();
|
|
149
|
+
// 查询 registry
|
|
150
|
+
const remoteVer = await checkLatestVersion();
|
|
151
|
+
if (!remoteVer) {
|
|
152
|
+
return { status: 'skipped', error: 'Failed to check remote version' };
|
|
153
|
+
}
|
|
154
|
+
// 版本比较
|
|
155
|
+
if (compareVersions(localVer, remoteVer) >= 0) {
|
|
156
|
+
return { status: 'no-update', from: localVer };
|
|
157
|
+
}
|
|
158
|
+
// 有新版本,执行升级(失败重试一次)
|
|
159
|
+
let lastError;
|
|
160
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
161
|
+
try {
|
|
162
|
+
await npmInstallGlobal('evolcore@latest');
|
|
163
|
+
return { status: 'upgraded', from: localVer, to: remoteVer };
|
|
164
|
+
}
|
|
165
|
+
catch (e) {
|
|
166
|
+
lastError = e.stderr || e.message || String(e);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 通用全局包升级流程:检查 → 比较 → 安装(失败重试一次)。
|
|
173
|
+
* 仅在包已安装时检查升级,未安装则跳过(resolveFn 返回 null)。
|
|
174
|
+
* fastaun / EC Web 等独立全局包共用此逻辑。
|
|
175
|
+
*/
|
|
176
|
+
export async function tryUpgradeGlobalPkg(resolveInstalled, pkgName) {
|
|
177
|
+
if (isLinkedInstall()) {
|
|
178
|
+
return { status: 'skipped' };
|
|
179
|
+
}
|
|
180
|
+
const installed = resolveInstalled();
|
|
181
|
+
if (!installed) {
|
|
182
|
+
return { status: 'skipped' }; // not installed, skip
|
|
183
|
+
}
|
|
184
|
+
const localVer = installed.version;
|
|
185
|
+
const remoteVer = await checkLatestVersion(pkgName);
|
|
186
|
+
if (!remoteVer) {
|
|
187
|
+
return { status: 'skipped', error: 'Failed to check remote version' };
|
|
188
|
+
}
|
|
189
|
+
if (compareVersions(localVer, remoteVer) >= 0) {
|
|
190
|
+
return { status: 'no-update', from: localVer };
|
|
191
|
+
}
|
|
192
|
+
let lastError;
|
|
193
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
194
|
+
try {
|
|
195
|
+
await npmInstallGlobal(`${pkgName}@latest`);
|
|
196
|
+
return { status: 'upgraded', from: localVer, to: remoteVer };
|
|
197
|
+
}
|
|
198
|
+
catch (e) {
|
|
199
|
+
lastError = e.stderr || e.message || String(e);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return { status: 'failed', from: localVer, to: remoteVer, error: lastError };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* AUN SDK 升级流程:检查 → 比较 → 安装
|
|
206
|
+
* 仅在 SDK 已安装时检查升级,未安装则跳过。
|
|
207
|
+
*/
|
|
208
|
+
export async function tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG) {
|
|
209
|
+
return tryUpgradeGlobalPkg(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG);
|
|
210
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform process introspection.
|
|
3
|
+
*
|
|
4
|
+
* Provides process start time retrieval for PID reuse detection.
|
|
5
|
+
* Returns null when start time cannot be obtained — callers should treat
|
|
6
|
+
* this as "do not match" (conservative: prefer leaving an orphan over
|
|
7
|
+
* killing the wrong process).
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import { spawnSync } from 'child_process';
|
|
11
|
+
import { isWindows } from './cross-platform.js';
|
|
12
|
+
const isMacOS = process.platform === 'darwin';
|
|
13
|
+
/** 容差:2 秒(覆盖 macOS 秒级精度 + 时钟漂移) */
|
|
14
|
+
export const START_TIME_TOLERANCE_MS = 2000;
|
|
15
|
+
/**
|
|
16
|
+
* 获取进程启动时间(Unix 毫秒)。
|
|
17
|
+
* 拿不到时返回 null,调用方应保守处理(不杀该 PID)。
|
|
18
|
+
*/
|
|
19
|
+
export function getProcessStartTime(pid) {
|
|
20
|
+
try {
|
|
21
|
+
if (isWindows)
|
|
22
|
+
return getStartTimeWindows(pid);
|
|
23
|
+
if (isMacOS)
|
|
24
|
+
return getStartTimeMacOS(pid);
|
|
25
|
+
return getStartTimeLinux(pid);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 比对记录的启动时间与实际启动时间是否匹配(容差 2 秒)。
|
|
33
|
+
* actual === null 时返回 false(保守不匹配)。
|
|
34
|
+
*/
|
|
35
|
+
export function startTimeMatches(recorded, actual) {
|
|
36
|
+
if (actual === null)
|
|
37
|
+
return false;
|
|
38
|
+
return Math.abs(recorded - actual) < START_TIME_TOLERANCE_MS;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 只读关联进程校验:允许记录时间晚于进程实际启动时间,但拒绝实际进程明显更新。
|
|
42
|
+
* 部分启动器会在子进程就绪后才写 instance 记录;此时 recorded 比 actual 晚是正常的。
|
|
43
|
+
*/
|
|
44
|
+
export function isSameOrOlderProcess(recorded, actual) {
|
|
45
|
+
if (actual === null)
|
|
46
|
+
return true;
|
|
47
|
+
return actual - recorded < START_TIME_TOLERANCE_MS;
|
|
48
|
+
}
|
|
49
|
+
// ── Linux ──
|
|
50
|
+
function getStartTimeLinux(pid) {
|
|
51
|
+
let stat;
|
|
52
|
+
try {
|
|
53
|
+
stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const tail = stat.slice(stat.lastIndexOf(')') + 2);
|
|
59
|
+
const fields = tail.split(' ');
|
|
60
|
+
const starttimeJiffies = parseInt(fields[19], 10);
|
|
61
|
+
if (isNaN(starttimeJiffies))
|
|
62
|
+
return null;
|
|
63
|
+
let btimeSec;
|
|
64
|
+
try {
|
|
65
|
+
const m = fs.readFileSync('/proc/stat', 'utf-8').match(/^btime (\d+)/m);
|
|
66
|
+
if (!m)
|
|
67
|
+
return null;
|
|
68
|
+
btimeSec = parseInt(m[1], 10);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
if (isNaN(btimeSec))
|
|
74
|
+
return null;
|
|
75
|
+
const clkTck = 100;
|
|
76
|
+
return (btimeSec + starttimeJiffies / clkTck) * 1000;
|
|
77
|
+
}
|
|
78
|
+
// ── macOS ──
|
|
79
|
+
function getStartTimeMacOS(pid) {
|
|
80
|
+
const result = spawnSync('ps', ['-p', String(pid), '-o', 'lstart='], {
|
|
81
|
+
encoding: 'utf-8',
|
|
82
|
+
timeout: 5000,
|
|
83
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
84
|
+
});
|
|
85
|
+
const out = result.stdout?.trim();
|
|
86
|
+
if (!out)
|
|
87
|
+
return null;
|
|
88
|
+
const t = Date.parse(out);
|
|
89
|
+
return isNaN(t) ? null : t;
|
|
90
|
+
}
|
|
91
|
+
// ── Windows ──
|
|
92
|
+
function getStartTimeWindows(pid) {
|
|
93
|
+
const fromPwsh = winPowerShellCreationDate(pid);
|
|
94
|
+
if (fromPwsh !== null)
|
|
95
|
+
return fromPwsh;
|
|
96
|
+
return winWmicCreationDate(pid);
|
|
97
|
+
}
|
|
98
|
+
function winPowerShellCreationDate(pid) {
|
|
99
|
+
const result = spawnSync('powershell', [
|
|
100
|
+
'-NoProfile',
|
|
101
|
+
'-Command',
|
|
102
|
+
`(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').CreationDate`,
|
|
103
|
+
], { encoding: 'utf-8', timeout: 8000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
104
|
+
const out = result.stdout?.trim();
|
|
105
|
+
if (!out)
|
|
106
|
+
return null;
|
|
107
|
+
return parseCimDate(out);
|
|
108
|
+
}
|
|
109
|
+
function winWmicCreationDate(pid) {
|
|
110
|
+
const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CreationDate', '/value'], { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
111
|
+
const out = result.stdout || '';
|
|
112
|
+
const m = out.match(/CreationDate=([^\r\n]+)/);
|
|
113
|
+
if (!m)
|
|
114
|
+
return null;
|
|
115
|
+
return parseCimDate(m[1].trim());
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 解析 CIM/WMI 日期格式:yyyyMMddHHmmss.ffffff±TZZZ
|
|
119
|
+
* TZZZ 是相对 UTC 的分钟偏移(中国是 +480)
|
|
120
|
+
*/
|
|
121
|
+
export function parseCimDate(s) {
|
|
122
|
+
if (!s)
|
|
123
|
+
return null;
|
|
124
|
+
const m = s.match(/^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})\.(\d{6})([+-]\d+)$/);
|
|
125
|
+
if (!m)
|
|
126
|
+
return null;
|
|
127
|
+
const [, y, mo, d, h, mi, sec, us, tz] = m;
|
|
128
|
+
const tzMin = parseInt(tz, 10);
|
|
129
|
+
if (isNaN(tzMin))
|
|
130
|
+
return null;
|
|
131
|
+
const localUtcMs = Date.UTC(+y, +mo - 1, +d, +h, +mi, +sec, Math.floor(+us / 1000));
|
|
132
|
+
return localUtcMs - tzMin * 60 * 1000;
|
|
133
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
function runnerKind(name) {
|
|
4
|
+
const normalized = name.toLowerCase();
|
|
5
|
+
if (normalized.includes('codex'))
|
|
6
|
+
return 'codex-app-server';
|
|
7
|
+
if (normalized.includes('claude'))
|
|
8
|
+
return 'claude-runner';
|
|
9
|
+
if (normalized.includes('gemini'))
|
|
10
|
+
return 'gemini-runner';
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
export function classifyProcessTree(processes, rootPid, explicitKinds = new Map()) {
|
|
14
|
+
const byPid = new Map(processes.map(process => [process.pid, process]));
|
|
15
|
+
const children = new Map();
|
|
16
|
+
for (const process of processes) {
|
|
17
|
+
const list = children.get(process.ppid) ?? [];
|
|
18
|
+
list.push(process.pid);
|
|
19
|
+
children.set(process.ppid, list);
|
|
20
|
+
}
|
|
21
|
+
const branchKinds = new Map();
|
|
22
|
+
const resolveBranchKind = (pid, visiting = new Set()) => {
|
|
23
|
+
if (branchKinds.has(pid))
|
|
24
|
+
return branchKinds.get(pid) ?? null;
|
|
25
|
+
if (visiting.has(pid))
|
|
26
|
+
return null;
|
|
27
|
+
visiting.add(pid);
|
|
28
|
+
const process = byPid.get(pid);
|
|
29
|
+
let kind = process ? runnerKind(process.name) : null;
|
|
30
|
+
if (!kind) {
|
|
31
|
+
for (const childPid of children.get(pid) ?? []) {
|
|
32
|
+
kind = resolveBranchKind(childPid, visiting);
|
|
33
|
+
if (kind)
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
visiting.delete(pid);
|
|
38
|
+
branchKinds.set(pid, kind);
|
|
39
|
+
return kind;
|
|
40
|
+
};
|
|
41
|
+
const resolveAncestorKind = (process) => {
|
|
42
|
+
let parent = byPid.get(process.ppid);
|
|
43
|
+
const visited = new Set();
|
|
44
|
+
while (parent && !visited.has(parent.pid)) {
|
|
45
|
+
visited.add(parent.pid);
|
|
46
|
+
const kind = runnerKind(parent.name) ?? resolveBranchKind(parent.pid);
|
|
47
|
+
if (kind)
|
|
48
|
+
return kind;
|
|
49
|
+
parent = byPid.get(parent.ppid);
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
};
|
|
53
|
+
return processes.map(process => {
|
|
54
|
+
const explicit = explicitKinds.get(process.pid);
|
|
55
|
+
const normalized = process.name.toLowerCase();
|
|
56
|
+
let kind;
|
|
57
|
+
if (process.pid === rootPid)
|
|
58
|
+
kind = 'daemon';
|
|
59
|
+
else if (explicit)
|
|
60
|
+
kind = explicit;
|
|
61
|
+
else if (normalized === 'bwrap' || normalized.includes('bubblewrap'))
|
|
62
|
+
kind = 'sandbox';
|
|
63
|
+
else if (normalized === 'socat')
|
|
64
|
+
kind = 'runner-helper';
|
|
65
|
+
else
|
|
66
|
+
kind = runnerKind(process.name) ?? resolveBranchKind(process.pid) ?? resolveAncestorKind(process) ?? 'child';
|
|
67
|
+
return { ...process, kind };
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const linuxClockTicks = readPositiveInt('getconf', ['CLK_TCK'], 100);
|
|
71
|
+
const linuxPageSize = readPositiveInt('getconf', ['PAGESIZE'], 4096);
|
|
72
|
+
function readPositiveInt(command, args, fallback) {
|
|
73
|
+
if (process.platform !== 'linux')
|
|
74
|
+
return fallback;
|
|
75
|
+
try {
|
|
76
|
+
const result = spawnSync(command, args, { encoding: 'utf8', timeout: 1000, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
77
|
+
const value = Number.parseInt(result.stdout?.trim() || '', 10);
|
|
78
|
+
return value > 0 ? value : fallback;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return fallback;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function round1(value) {
|
|
85
|
+
return Math.round(value * 10) / 10;
|
|
86
|
+
}
|
|
87
|
+
export function parseLinuxProcStat(stat, pageSize = linuxPageSize, clockTicks = linuxClockTicks) {
|
|
88
|
+
const open = stat.indexOf('(');
|
|
89
|
+
const close = stat.lastIndexOf(')');
|
|
90
|
+
if (open < 1 || close <= open)
|
|
91
|
+
return null;
|
|
92
|
+
const pid = Number.parseInt(stat.slice(0, open).trim(), 10);
|
|
93
|
+
const fields = stat.slice(close + 2).trim().split(/\s+/);
|
|
94
|
+
const ppid = Number.parseInt(fields[1], 10);
|
|
95
|
+
const userTicks = Number.parseInt(fields[11], 10);
|
|
96
|
+
const systemTicks = Number.parseInt(fields[12], 10);
|
|
97
|
+
const startTicks = Number.parseInt(fields[19], 10);
|
|
98
|
+
const rssPages = Number.parseInt(fields[21], 10);
|
|
99
|
+
if (![pid, ppid, userTicks, systemTicks, rssPages].every(Number.isFinite))
|
|
100
|
+
return null;
|
|
101
|
+
return {
|
|
102
|
+
pid,
|
|
103
|
+
ppid,
|
|
104
|
+
name: stat.slice(open + 1, close),
|
|
105
|
+
cumulativeCpuMs: (userTicks + systemTicks) * 1000 / clockTicks,
|
|
106
|
+
rss: Math.max(0, rssPages) * pageSize,
|
|
107
|
+
startId: Number.isFinite(startTicks) ? startTicks : undefined,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
function readLinuxMetrics() {
|
|
111
|
+
const metrics = [];
|
|
112
|
+
let entries;
|
|
113
|
+
try {
|
|
114
|
+
entries = fs.readdirSync('/proc');
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return metrics;
|
|
118
|
+
}
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
if (!/^\d+$/.test(entry))
|
|
121
|
+
continue;
|
|
122
|
+
try {
|
|
123
|
+
const metric = parseLinuxProcStat(fs.readFileSync(`/proc/${entry}/stat`, 'utf8'));
|
|
124
|
+
if (metric)
|
|
125
|
+
metrics.push(metric);
|
|
126
|
+
}
|
|
127
|
+
catch { }
|
|
128
|
+
}
|
|
129
|
+
return metrics;
|
|
130
|
+
}
|
|
131
|
+
export function parseProcessTime(value) {
|
|
132
|
+
const match = value.trim().match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+(?:\.\d+)?)$/);
|
|
133
|
+
if (!match)
|
|
134
|
+
return 0;
|
|
135
|
+
const days = Number(match[1] || 0);
|
|
136
|
+
const hours = Number(match[2] || 0);
|
|
137
|
+
const minutes = Number(match[3] || 0);
|
|
138
|
+
const seconds = Number(match[4] || 0);
|
|
139
|
+
return (((days * 24 + hours) * 60 + minutes) * 60 + seconds) * 1000;
|
|
140
|
+
}
|
|
141
|
+
function readUnixPsMetrics() {
|
|
142
|
+
const result = spawnSync('ps', ['-axo', 'pid=,ppid=,rss=,time=,comm='], {
|
|
143
|
+
encoding: 'utf8',
|
|
144
|
+
timeout: 3000,
|
|
145
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
146
|
+
});
|
|
147
|
+
if (result.status !== 0)
|
|
148
|
+
return [];
|
|
149
|
+
const metrics = [];
|
|
150
|
+
for (const line of (result.stdout || '').split('\n')) {
|
|
151
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(.+?)\s*$/);
|
|
152
|
+
if (!match)
|
|
153
|
+
continue;
|
|
154
|
+
metrics.push({
|
|
155
|
+
pid: Number(match[1]),
|
|
156
|
+
ppid: Number(match[2]),
|
|
157
|
+
rss: Number(match[3]) * 1024,
|
|
158
|
+
cumulativeCpuMs: parseProcessTime(match[4]),
|
|
159
|
+
name: match[5],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return metrics;
|
|
163
|
+
}
|
|
164
|
+
function readWindowsMetrics() {
|
|
165
|
+
const command = [
|
|
166
|
+
'Get-CimInstance Win32_Process',
|
|
167
|
+
'Select-Object ProcessId,ParentProcessId,Name,WorkingSetSize,KernelModeTime,UserModeTime,CreationDate',
|
|
168
|
+
'ConvertTo-Json -Compress',
|
|
169
|
+
].join(' | ');
|
|
170
|
+
const result = spawnSync('powershell', ['-NoProfile', '-Command', command], {
|
|
171
|
+
encoding: 'utf8',
|
|
172
|
+
timeout: 5000,
|
|
173
|
+
windowsHide: true,
|
|
174
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
175
|
+
});
|
|
176
|
+
if (result.status !== 0 || !result.stdout?.trim())
|
|
177
|
+
return [];
|
|
178
|
+
try {
|
|
179
|
+
const parsed = JSON.parse(result.stdout);
|
|
180
|
+
const rows = Array.isArray(parsed) ? parsed : [parsed];
|
|
181
|
+
return rows.map((row) => ({
|
|
182
|
+
pid: Number(row.ProcessId),
|
|
183
|
+
ppid: Number(row.ParentProcessId),
|
|
184
|
+
name: String(row.Name || 'unknown'),
|
|
185
|
+
rss: Number(row.WorkingSetSize) || 0,
|
|
186
|
+
cumulativeCpuMs: ((Number(row.KernelModeTime) || 0) + (Number(row.UserModeTime) || 0)) / 10_000,
|
|
187
|
+
startId: row.CreationDate ? String(row.CreationDate) : undefined,
|
|
188
|
+
})).filter((row) => row.pid > 0);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
export function readProcessMetrics() {
|
|
195
|
+
if (process.platform === 'linux')
|
|
196
|
+
return readLinuxMetrics();
|
|
197
|
+
if (process.platform === 'win32')
|
|
198
|
+
return readWindowsMetrics();
|
|
199
|
+
return readUnixPsMetrics();
|
|
200
|
+
}
|
|
201
|
+
export class ProcessTreeSampler {
|
|
202
|
+
rootPid;
|
|
203
|
+
readMetrics;
|
|
204
|
+
now;
|
|
205
|
+
previous = new Map();
|
|
206
|
+
previousTs = null;
|
|
207
|
+
constructor(rootPid = process.pid, readMetrics = readProcessMetrics, now = Date.now) {
|
|
208
|
+
this.rootPid = rootPid;
|
|
209
|
+
this.readMetrics = readMetrics;
|
|
210
|
+
this.now = now;
|
|
211
|
+
}
|
|
212
|
+
sample(associatedRootPids = []) {
|
|
213
|
+
const timestamp = this.now();
|
|
214
|
+
const metrics = this.readMetrics();
|
|
215
|
+
const byPid = new Map(metrics.map(metric => [metric.pid, metric]));
|
|
216
|
+
const children = new Map();
|
|
217
|
+
for (const metric of metrics) {
|
|
218
|
+
const list = children.get(metric.ppid) ?? [];
|
|
219
|
+
list.push(metric.pid);
|
|
220
|
+
children.set(metric.ppid, list);
|
|
221
|
+
}
|
|
222
|
+
const rootPids = [...new Set([this.rootPid, ...associatedRootPids])].filter(pid => byPid.has(pid));
|
|
223
|
+
const selected = new Set();
|
|
224
|
+
const pending = [...rootPids];
|
|
225
|
+
while (pending.length > 0) {
|
|
226
|
+
const pid = pending.pop();
|
|
227
|
+
if (selected.has(pid))
|
|
228
|
+
continue;
|
|
229
|
+
selected.add(pid);
|
|
230
|
+
pending.push(...(children.get(pid) ?? []));
|
|
231
|
+
}
|
|
232
|
+
const elapsedMs = this.previousTs === null ? 0 : timestamp - this.previousTs;
|
|
233
|
+
const processes = [];
|
|
234
|
+
for (const pid of selected) {
|
|
235
|
+
const metric = byPid.get(pid);
|
|
236
|
+
if (!metric)
|
|
237
|
+
continue;
|
|
238
|
+
const previous = this.previous.get(pid);
|
|
239
|
+
const sameProcess = previous && (previous.startId === undefined || metric.startId === undefined || previous.startId === metric.startId);
|
|
240
|
+
const cpuDelta = sameProcess ? metric.cumulativeCpuMs - previous.cumulativeCpuMs : 0;
|
|
241
|
+
const cpuPercent = elapsedMs > 0 && cpuDelta > 0 ? cpuDelta * 100 / elapsedMs : 0;
|
|
242
|
+
processes.push({
|
|
243
|
+
pid: metric.pid,
|
|
244
|
+
ppid: metric.ppid,
|
|
245
|
+
name: metric.name,
|
|
246
|
+
cpuPercent: round1(cpuPercent),
|
|
247
|
+
rss: metric.rss,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
this.previous = new Map(metrics.map(metric => [metric.pid, {
|
|
251
|
+
cumulativeCpuMs: metric.cumulativeCpuMs,
|
|
252
|
+
startId: metric.startId,
|
|
253
|
+
}]));
|
|
254
|
+
this.previousTs = timestamp;
|
|
255
|
+
processes.sort((left, right) => {
|
|
256
|
+
if (left.pid === this.rootPid)
|
|
257
|
+
return -1;
|
|
258
|
+
if (right.pid === this.rootPid)
|
|
259
|
+
return 1;
|
|
260
|
+
return left.pid - right.pid;
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
rootPid: this.rootPid,
|
|
264
|
+
rootPids,
|
|
265
|
+
processCount: processes.length,
|
|
266
|
+
cpuPercent: round1(processes.reduce((sum, metric) => sum + metric.cpuPercent, 0)),
|
|
267
|
+
rss: processes.reduce((sum, metric) => sum + metric.rss, 0),
|
|
268
|
+
processes,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
function pathForPlatform(platform) {
|
|
5
|
+
return platform === 'win32' ? path.win32 : path.posix;
|
|
6
|
+
}
|
|
7
|
+
function trimTrailingSeparators(value, root) {
|
|
8
|
+
let out = value;
|
|
9
|
+
while (out.length > root.length && /[\\/]$/.test(out)) {
|
|
10
|
+
out = out.slice(0, -1);
|
|
11
|
+
}
|
|
12
|
+
return out;
|
|
13
|
+
}
|
|
14
|
+
function normalizeForCompare(value, platform) {
|
|
15
|
+
const pathImpl = pathForPlatform(platform);
|
|
16
|
+
const resolved = pathImpl.resolve(value);
|
|
17
|
+
const trimmed = trimTrailingSeparators(resolved, pathImpl.parse(resolved).root);
|
|
18
|
+
return platform === 'win32' ? trimmed.toLowerCase() : trimmed;
|
|
19
|
+
}
|
|
20
|
+
function isWindowsUnsafeProjectRoot(rootPath, homeDir) {
|
|
21
|
+
const win = path.win32;
|
|
22
|
+
const root = normalizeForCompare(rootPath, 'win32');
|
|
23
|
+
const parsed = win.parse(win.resolve(rootPath));
|
|
24
|
+
if (root === normalizeForCompare(parsed.root, 'win32'))
|
|
25
|
+
return true;
|
|
26
|
+
const userProfileRoot = win.dirname(win.resolve(homeDir));
|
|
27
|
+
if (win.basename(userProfileRoot).toLowerCase() !== 'users')
|
|
28
|
+
return false;
|
|
29
|
+
return root === normalizeForCompare(userProfileRoot, 'win32');
|
|
30
|
+
}
|
|
31
|
+
export function sanitizeProjectRoot(rootPath, options = {}) {
|
|
32
|
+
const platform = options.platform ?? process.platform;
|
|
33
|
+
if (platform !== 'win32')
|
|
34
|
+
return rootPath;
|
|
35
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
36
|
+
if (!homeDir)
|
|
37
|
+
return rootPath;
|
|
38
|
+
return isWindowsUnsafeProjectRoot(rootPath, homeDir)
|
|
39
|
+
? path.win32.join(homeDir, 'Projects')
|
|
40
|
+
: rootPath;
|
|
41
|
+
}
|
|
42
|
+
export function defaultProjectsRoot(evolcoreRoot, options = {}) {
|
|
43
|
+
const platform = options.platform ?? process.platform;
|
|
44
|
+
const pathImpl = pathForPlatform(platform);
|
|
45
|
+
if (platform === 'win32') {
|
|
46
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
47
|
+
if (homeDir && isWindowsUnsafeProjectRoot(evolcoreRoot, homeDir)) {
|
|
48
|
+
return path.win32.join(homeDir, 'Projects');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return pathImpl.join(evolcoreRoot, 'projects');
|
|
52
|
+
}
|
|
53
|
+
export function agentProjectRootFromDefaults(defaults, evolcoreRoot, options = {}) {
|
|
54
|
+
const platform = options.platform ?? process.platform;
|
|
55
|
+
const pathImpl = pathForPlatform(platform);
|
|
56
|
+
const configuredRoot = defaults?.projects?.rootPath
|
|
57
|
+
|| (defaults?.projects?.defaultPath && pathImpl.dirname(defaults.projects.defaultPath))
|
|
58
|
+
|| defaultProjectsRoot(evolcoreRoot, options);
|
|
59
|
+
return sanitizeProjectRoot(configuredRoot, options);
|
|
60
|
+
}
|
|
61
|
+
export function deriveAgentProjectPath(rootPath, aid, options = {}) {
|
|
62
|
+
const platform = options.platform ?? process.platform;
|
|
63
|
+
const pathImpl = pathForPlatform(platform);
|
|
64
|
+
const existsSync = options.existsSync ?? fs.existsSync;
|
|
65
|
+
const baseName = aid.split('.')[0];
|
|
66
|
+
const safeRootPath = sanitizeProjectRoot(rootPath, options);
|
|
67
|
+
let candidate = pathImpl.join(safeRootPath, baseName);
|
|
68
|
+
if (!existsSync(candidate))
|
|
69
|
+
return candidate;
|
|
70
|
+
let i = 1;
|
|
71
|
+
while (existsSync(`${candidate}~${i}`))
|
|
72
|
+
i++;
|
|
73
|
+
return `${candidate}~${i}`;
|
|
74
|
+
}
|