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,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instance registry — manages {HOME}/data/instance/ directory.
|
|
3
|
+
*
|
|
4
|
+
* 真相源:每个进程写一份带 PID 的 record 文件。判定"是否已有实例运行"时,
|
|
5
|
+
* 遍历所有 record,根据 (pid, startedAt) 对每个 record 做存活校验。
|
|
6
|
+
*
|
|
7
|
+
* 不使用 lock 文件——record 文件本身就是登记簿,"互斥"由 post-write 自检
|
|
8
|
+
* (写完 record 立刻扫一遍,比自己早的赢)保证。这样 6 个并发进程同时启动
|
|
9
|
+
* 也不会互相覆盖(PID 不同名字也不同),最终通过 startedAt 比较选出唯一赢家。
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { spawnSync } from 'child_process';
|
|
14
|
+
import { resolvePaths } from '../paths.js';
|
|
15
|
+
import { isProcessRunning, killProcess, isWindows, findProcesses } from './cross-platform.js';
|
|
16
|
+
import { getProcessStartTime, startTimeMatches } from './process-introspect.js';
|
|
17
|
+
import { isConfirmedLeakedTestDaemon, runtimeHomeFromEnv } from './restart-safety.js';
|
|
18
|
+
// ── Helpers ──
|
|
19
|
+
function instanceDir() {
|
|
20
|
+
return resolvePaths().instanceDir;
|
|
21
|
+
}
|
|
22
|
+
function writeAtomic(filePath, data) {
|
|
23
|
+
const tmp = filePath + '.tmp';
|
|
24
|
+
fs.writeFileSync(tmp, data);
|
|
25
|
+
fs.renameSync(tmp, filePath);
|
|
26
|
+
}
|
|
27
|
+
function safeParseJson(filePath) {
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function isAlive(pid, recordedStartedAt) {
|
|
36
|
+
if (!isProcessRunning(pid))
|
|
37
|
+
return false;
|
|
38
|
+
const actual = getProcessStartTime(pid);
|
|
39
|
+
// 拿不到启动时间时保守认为是我们的进程(宁可误判活着,不误杀)
|
|
40
|
+
if (actual === null)
|
|
41
|
+
return true;
|
|
42
|
+
return startTimeMatches(recordedStartedAt, actual);
|
|
43
|
+
}
|
|
44
|
+
// ── Main record ──
|
|
45
|
+
function mainFileName(pid) {
|
|
46
|
+
return `main-${pid}.json`;
|
|
47
|
+
}
|
|
48
|
+
export function writeMain(launchedBy) {
|
|
49
|
+
const dir = instanceDir();
|
|
50
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
51
|
+
const startedAt = getProcessStartTime(process.pid) ?? Date.now();
|
|
52
|
+
const record = {
|
|
53
|
+
pid: process.pid,
|
|
54
|
+
startedAt,
|
|
55
|
+
startedAtIso: new Date(startedAt).toISOString(),
|
|
56
|
+
launchedBy,
|
|
57
|
+
};
|
|
58
|
+
const filePath = path.join(dir, mainFileName(process.pid));
|
|
59
|
+
writeAtomic(filePath, JSON.stringify(record, null, 2));
|
|
60
|
+
return filePath;
|
|
61
|
+
}
|
|
62
|
+
export function removeMain(pid) {
|
|
63
|
+
const target = pid ?? process.pid;
|
|
64
|
+
const filePath = path.join(instanceDir(), mainFileName(target));
|
|
65
|
+
try {
|
|
66
|
+
fs.unlinkSync(filePath);
|
|
67
|
+
}
|
|
68
|
+
catch { }
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Post-write 自检:写完 main record 后立刻扫一次目录。
|
|
72
|
+
* 如果发现别的活 main,按 (startedAt, pid) 选最早的赢家。
|
|
73
|
+
*
|
|
74
|
+
* @returns 当前进程是否赢家。false 表示应该让出(删自己的 record + exit)。
|
|
75
|
+
*/
|
|
76
|
+
export function isMainWinner() {
|
|
77
|
+
const status = scanInstances();
|
|
78
|
+
const aliveMains = status.mains.filter(m => m.alive);
|
|
79
|
+
if (aliveMains.length <= 1)
|
|
80
|
+
return { winner: true };
|
|
81
|
+
const self = aliveMains.find(m => m.record.pid === process.pid);
|
|
82
|
+
if (!self)
|
|
83
|
+
return { winner: true }; // 自己的记录都没了,让别人去争吧
|
|
84
|
+
// (startedAt, pid) 字典序最小者赢
|
|
85
|
+
const winnerEntry = aliveMains.reduce((best, cur) => {
|
|
86
|
+
if (cur.record.startedAt < best.record.startedAt)
|
|
87
|
+
return cur;
|
|
88
|
+
if (cur.record.startedAt > best.record.startedAt)
|
|
89
|
+
return best;
|
|
90
|
+
return cur.record.pid < best.record.pid ? cur : best;
|
|
91
|
+
});
|
|
92
|
+
if (winnerEntry.record.pid === process.pid)
|
|
93
|
+
return { winner: true };
|
|
94
|
+
return { winner: false, conflictingPid: winnerEntry.record.pid };
|
|
95
|
+
}
|
|
96
|
+
// ── Restart monitor record ──
|
|
97
|
+
function restartMonitorFileName(pid) {
|
|
98
|
+
return `restart-monitor-${pid}.json`;
|
|
99
|
+
}
|
|
100
|
+
export function writeRestartMonitor() {
|
|
101
|
+
const dir = instanceDir();
|
|
102
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
103
|
+
const startedAt = getProcessStartTime(process.pid) ?? Date.now();
|
|
104
|
+
const record = {
|
|
105
|
+
pid: process.pid,
|
|
106
|
+
startedAt,
|
|
107
|
+
startedAtIso: new Date(startedAt).toISOString(),
|
|
108
|
+
launchedBy: 'restart-monitor',
|
|
109
|
+
};
|
|
110
|
+
const filePath = path.join(dir, restartMonitorFileName(process.pid));
|
|
111
|
+
writeAtomic(filePath, JSON.stringify(record, null, 2));
|
|
112
|
+
return filePath;
|
|
113
|
+
}
|
|
114
|
+
export function removeRestartMonitor(pid) {
|
|
115
|
+
const target = pid ?? process.pid;
|
|
116
|
+
const filePath = path.join(instanceDir(), restartMonitorFileName(target));
|
|
117
|
+
try {
|
|
118
|
+
fs.unlinkSync(filePath);
|
|
119
|
+
}
|
|
120
|
+
catch { }
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Post-write 自检:与 isMainWinner 同语义,用于 restart-monitor。
|
|
124
|
+
*/
|
|
125
|
+
export function isRestartMonitorWinner() {
|
|
126
|
+
const status = scanInstances();
|
|
127
|
+
const alive = status.restartMonitors.filter(m => m.alive);
|
|
128
|
+
if (alive.length <= 1)
|
|
129
|
+
return { winner: true };
|
|
130
|
+
const self = alive.find(m => m.record.pid === process.pid);
|
|
131
|
+
if (!self)
|
|
132
|
+
return { winner: true };
|
|
133
|
+
const winnerEntry = alive.reduce((best, cur) => {
|
|
134
|
+
if (cur.record.startedAt < best.record.startedAt)
|
|
135
|
+
return cur;
|
|
136
|
+
if (cur.record.startedAt > best.record.startedAt)
|
|
137
|
+
return best;
|
|
138
|
+
return cur.record.pid < best.record.pid ? cur : best;
|
|
139
|
+
});
|
|
140
|
+
if (winnerEntry.record.pid === process.pid)
|
|
141
|
+
return { winner: true };
|
|
142
|
+
return { winner: false, conflictingPid: winnerEntry.record.pid };
|
|
143
|
+
}
|
|
144
|
+
// ─ AID event log ──
|
|
145
|
+
function aidFileName(pid) {
|
|
146
|
+
return `aid-${pid}.jsonl`;
|
|
147
|
+
}
|
|
148
|
+
export function appendAidEvent(event, pid) {
|
|
149
|
+
const target = pid ?? process.pid;
|
|
150
|
+
const dir = instanceDir();
|
|
151
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
152
|
+
const filePath = path.join(dir, aidFileName(target));
|
|
153
|
+
const line = JSON.stringify(event) + '\n';
|
|
154
|
+
fs.appendFileSync(filePath, line);
|
|
155
|
+
}
|
|
156
|
+
export function removeAidLog(pid) {
|
|
157
|
+
const target = pid ?? process.pid;
|
|
158
|
+
const filePath = path.join(instanceDir(), aidFileName(target));
|
|
159
|
+
try {
|
|
160
|
+
fs.unlinkSync(filePath);
|
|
161
|
+
}
|
|
162
|
+
catch { }
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* 读取 aid jsonl 文件尾部,提取每个 AID 的最后活动时间和事件类型。
|
|
166
|
+
*/
|
|
167
|
+
export function readAidLastActivity(pid) {
|
|
168
|
+
const filePath = path.join(instanceDir(), aidFileName(pid));
|
|
169
|
+
const result = new Map();
|
|
170
|
+
if (!fs.existsSync(filePath))
|
|
171
|
+
return result;
|
|
172
|
+
try {
|
|
173
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
174
|
+
const lines = content.trim().split('\n');
|
|
175
|
+
for (const line of lines) {
|
|
176
|
+
if (!line)
|
|
177
|
+
continue;
|
|
178
|
+
try {
|
|
179
|
+
const ev = JSON.parse(line);
|
|
180
|
+
if (ev.aid && ev.ts) {
|
|
181
|
+
result.set(ev.aid, { ts: ev.ts, event: ev.event });
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch { }
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch { }
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
// ── Scan & cleanup ──
|
|
191
|
+
/**
|
|
192
|
+
* 扫描 instance/ 目录,返回所有实例记录(含死活状态)。
|
|
193
|
+
*
|
|
194
|
+
* 损坏的 JSON 文件直接删除。aid-*.jsonl 在 scan 时不解析,由调用方按需读取。
|
|
195
|
+
*/
|
|
196
|
+
export function scanInstances() {
|
|
197
|
+
const status = {
|
|
198
|
+
mains: [],
|
|
199
|
+
restartMonitors: [],
|
|
200
|
+
aidLastActivity: new Map(),
|
|
201
|
+
};
|
|
202
|
+
const dir = instanceDir();
|
|
203
|
+
if (!fs.existsSync(dir))
|
|
204
|
+
return status;
|
|
205
|
+
let files;
|
|
206
|
+
try {
|
|
207
|
+
files = fs.readdirSync(dir);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return status;
|
|
211
|
+
}
|
|
212
|
+
for (const file of files) {
|
|
213
|
+
const filePath = path.join(dir, file);
|
|
214
|
+
if (file.startsWith('main-') && file.endsWith('.json')) {
|
|
215
|
+
const record = safeParseJson(filePath);
|
|
216
|
+
if (record && record.pid && record.startedAt) {
|
|
217
|
+
const alive = isAlive(record.pid, record.startedAt);
|
|
218
|
+
status.mains.push({ record, alive });
|
|
219
|
+
if (alive) {
|
|
220
|
+
// 合并所有活 main 的 AID 活动记录(按 ts 取最新)
|
|
221
|
+
for (const [aid, info] of readAidLastActivity(record.pid)) {
|
|
222
|
+
const prev = status.aidLastActivity.get(aid);
|
|
223
|
+
if (!prev || info.ts > prev.ts) {
|
|
224
|
+
status.aidLastActivity.set(aid, info);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
try {
|
|
231
|
+
fs.unlinkSync(filePath);
|
|
232
|
+
}
|
|
233
|
+
catch { }
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
else if (file.startsWith('restart-monitor-') && file.endsWith('.json')) {
|
|
237
|
+
const record = safeParseJson(filePath);
|
|
238
|
+
if (record && record.pid && record.startedAt) {
|
|
239
|
+
const alive = isAlive(record.pid, record.startedAt);
|
|
240
|
+
status.restartMonitors.push({ record, alive });
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
try {
|
|
244
|
+
fs.unlinkSync(filePath);
|
|
245
|
+
}
|
|
246
|
+
catch { }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return status;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* 清理所有非自己 PID 的残留文件。仍活着的旧实例进程会被 SIGKILL。
|
|
254
|
+
* 返回被杀掉的 PID 列表。
|
|
255
|
+
*/
|
|
256
|
+
export function cleanupInstances() {
|
|
257
|
+
const dir = instanceDir();
|
|
258
|
+
if (!fs.existsSync(dir))
|
|
259
|
+
return [];
|
|
260
|
+
let files;
|
|
261
|
+
try {
|
|
262
|
+
files = fs.readdirSync(dir);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return [];
|
|
266
|
+
}
|
|
267
|
+
const killed = [];
|
|
268
|
+
for (const file of files) {
|
|
269
|
+
const filePath = path.join(dir, file);
|
|
270
|
+
if (file.startsWith('main-') && file.endsWith('.json')) {
|
|
271
|
+
const record = safeParseJson(filePath);
|
|
272
|
+
if (record?.pid) {
|
|
273
|
+
if (record.pid === process.pid)
|
|
274
|
+
continue;
|
|
275
|
+
if (isProcessRunning(record.pid)) {
|
|
276
|
+
const actual = getProcessStartTime(record.pid);
|
|
277
|
+
if (startTimeMatches(record.startedAt, actual)) {
|
|
278
|
+
killPid(record.pid);
|
|
279
|
+
killed.push(record.pid);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
try {
|
|
284
|
+
fs.unlinkSync(filePath);
|
|
285
|
+
}
|
|
286
|
+
catch { }
|
|
287
|
+
}
|
|
288
|
+
else if (file.startsWith('restart-monitor-') && file.endsWith('.json')) {
|
|
289
|
+
const record = safeParseJson(filePath);
|
|
290
|
+
if (record?.pid) {
|
|
291
|
+
if (record.pid === process.pid)
|
|
292
|
+
continue;
|
|
293
|
+
if (isProcessRunning(record.pid)) {
|
|
294
|
+
const actual = getProcessStartTime(record.pid);
|
|
295
|
+
if (startTimeMatches(record.startedAt, actual)) {
|
|
296
|
+
killPid(record.pid);
|
|
297
|
+
killed.push(record.pid);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
try {
|
|
302
|
+
fs.unlinkSync(filePath);
|
|
303
|
+
}
|
|
304
|
+
catch { }
|
|
305
|
+
}
|
|
306
|
+
else if (file.startsWith('aid-') && file.endsWith('.jsonl')) {
|
|
307
|
+
// aid-<pid>.jsonl:自己的不动,其他的清掉
|
|
308
|
+
const m = file.match(/^aid-(\d+)\.jsonl$/);
|
|
309
|
+
if (m && parseInt(m[1], 10) === process.pid)
|
|
310
|
+
continue;
|
|
311
|
+
try {
|
|
312
|
+
fs.unlinkSync(filePath);
|
|
313
|
+
}
|
|
314
|
+
catch { }
|
|
315
|
+
}
|
|
316
|
+
else if (file.endsWith('.tmp')) {
|
|
317
|
+
try {
|
|
318
|
+
fs.unlinkSync(filePath);
|
|
319
|
+
}
|
|
320
|
+
catch { }
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return killed;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* 删除当前进程拥有的所有 instance 文件(正常关闭时调用)。
|
|
327
|
+
*/
|
|
328
|
+
export function removeAll(pid) {
|
|
329
|
+
const target = pid ?? process.pid;
|
|
330
|
+
removeMain(target);
|
|
331
|
+
removeAidLog(target);
|
|
332
|
+
}
|
|
333
|
+
// ── Internal ──
|
|
334
|
+
function killPid(pid) {
|
|
335
|
+
killProcess(pid, true);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 扫所有 node 进程中跑 dist/index.js 的 PID,减去当前 HOME 已登记的 main PID。
|
|
339
|
+
*
|
|
340
|
+
* 用途:检测跨 HOME 残留的 evolcore 主进程(例如测试套件 spawn 后未清理、
|
|
341
|
+
* 旧版本 pidfile 模式遗留等),由 cmdStart/cmdRestart 在启动前提示用户。
|
|
342
|
+
*
|
|
343
|
+
* Linux 下额外读取 /proc/<pid>/environ 提取 EVOLCORE_HOME,
|
|
344
|
+
* 并对 Vitest 泄漏的 restart-monitor daemon 做严格标记。
|
|
345
|
+
* Windows / macOS 取不到环境变量时 evolcoreHome 为 null。
|
|
346
|
+
*
|
|
347
|
+
* 不会主动 kill——清理由调用方决定。
|
|
348
|
+
*/
|
|
349
|
+
export function findOrphanProcesses() {
|
|
350
|
+
// 1. 已登记 PID(自己 HOME 下的 main + 自己进程)
|
|
351
|
+
const known = new Set([process.pid]);
|
|
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);
|
|
357
|
+
// 2. 系统中所有跑 dist/index.js 的 node 进程
|
|
358
|
+
const candidates = findProcesses('node.*dist/index.js');
|
|
359
|
+
const orphans = [];
|
|
360
|
+
for (const pid of candidates) {
|
|
361
|
+
if (known.has(pid))
|
|
362
|
+
continue;
|
|
363
|
+
if (!isProcessRunning(pid))
|
|
364
|
+
continue;
|
|
365
|
+
const cmdline = readCmdline(pid);
|
|
366
|
+
// 二次验证:确实是 evolcore 的 dist/index.js
|
|
367
|
+
if (!/dist[\\/]index\.js/.test(cmdline))
|
|
368
|
+
continue;
|
|
369
|
+
// 三次验证:排除 ecweb(/ecweb/dist/index.js)
|
|
370
|
+
if (/[\\/]ecweb[\\/]dist[\\/]index\.js/.test(cmdline))
|
|
371
|
+
continue;
|
|
372
|
+
const processEnv = readProcessEnvironment(pid);
|
|
373
|
+
orphans.push({
|
|
374
|
+
pid,
|
|
375
|
+
evolcoreHome: processEnv ? runtimeHomeFromEnv(processEnv) : null,
|
|
376
|
+
cmdline,
|
|
377
|
+
confirmedTestDaemon: processEnv ? isConfirmedLeakedTestDaemon(processEnv) : false,
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return orphans;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* SIGKILL 所有传入的孤儿 PID。返回成功杀掉的 PID 列表(即调用后已停的)。
|
|
384
|
+
*/
|
|
385
|
+
export function killOrphans(orphans) {
|
|
386
|
+
const killed = [];
|
|
387
|
+
for (const o of orphans) {
|
|
388
|
+
try {
|
|
389
|
+
killProcess(o.pid, true);
|
|
390
|
+
killed.push(o.pid);
|
|
391
|
+
}
|
|
392
|
+
catch { }
|
|
393
|
+
}
|
|
394
|
+
return killed;
|
|
395
|
+
}
|
|
396
|
+
function readCmdline(pid) {
|
|
397
|
+
if (isWindows) {
|
|
398
|
+
try {
|
|
399
|
+
const result = spawnSync('wmic', ['process', 'where', `ProcessId=${pid}`, 'get', 'CommandLine', '/value'], { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true });
|
|
400
|
+
const out = result.stdout || '';
|
|
401
|
+
const m = out.match(/CommandLine=([^\r\n]+)/);
|
|
402
|
+
return m ? m[1].trim() : '';
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return '';
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
try {
|
|
409
|
+
return fs.readFileSync(`/proc/${pid}/cmdline`, 'utf-8').replace(/\0/g, ' ').trim();
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
try {
|
|
413
|
+
const result = spawnSync('ps', ['-p', String(pid), '-o', 'args='], {
|
|
414
|
+
encoding: 'utf-8',
|
|
415
|
+
timeout: 3000,
|
|
416
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
417
|
+
});
|
|
418
|
+
return result.stdout?.trim() || '';
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
return '';
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function readProcessEnvironment(pid) {
|
|
426
|
+
// Linux: /proc/<pid>/environ
|
|
427
|
+
if (!isWindows && process.platform !== 'darwin') {
|
|
428
|
+
try {
|
|
429
|
+
const env = fs.readFileSync(`/proc/${pid}/environ`, 'utf-8');
|
|
430
|
+
const result = {};
|
|
431
|
+
for (const entry of env.split('\0')) {
|
|
432
|
+
const separator = entry.indexOf('=');
|
|
433
|
+
if (separator <= 0)
|
|
434
|
+
continue;
|
|
435
|
+
result[entry.slice(0, separator)] = entry.slice(separator + 1);
|
|
436
|
+
}
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* locale.ts — 宿主机系统语言判定(单一事实来源)。
|
|
3
|
+
*
|
|
4
|
+
* 依据进程环境的 locale 变量(LANG / LC_ALL / LANGUAGE),
|
|
5
|
+
* 回退到 Intl 解析出的运行时 locale。用于面向用户的系统文案在中/英之间切换。
|
|
6
|
+
*
|
|
7
|
+
* 注意:这是「宿主机/守护进程」语言,不是「对端用户」语言——
|
|
8
|
+
* 系统当前没有 per-peer 语言偏好;若将来引入,应在调用点覆盖此判定。
|
|
9
|
+
*/
|
|
10
|
+
/** 解析宿主机 locale 字符串(小写),无则返回空串。 */
|
|
11
|
+
export function resolveHostLocale() {
|
|
12
|
+
return (process.env.LANG ||
|
|
13
|
+
process.env.LC_ALL ||
|
|
14
|
+
process.env.LANGUAGE ||
|
|
15
|
+
Intl.DateTimeFormat().resolvedOptions().locale ||
|
|
16
|
+
'').toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
/** 宿主机系统语言是否为中文(zh / zh-CN / zh_CN.UTF-8 等)。 */
|
|
19
|
+
export function isHostChinese() {
|
|
20
|
+
return resolveHostLocale().includes('zh');
|
|
21
|
+
}
|