evolcore 0.0.1 → 0.0.2
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 +813 -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/assets/brand/evolcore/README.md +19 -0
- package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
- package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
- package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
- package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
- package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
- package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
- package/assets/brand/evolcore/evolcore-logo.png +0 -0
- package/assets/brand/evolcore/evolcore-logo.svg +14 -0
- package/assets/brand/evolcore/evolcore-mark.png +0 -0
- package/assets/brand/evolcore/evolcore-mark.svg +10 -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 +2385 -0
- package/dist/agents/codex-app-server-client.js +448 -0
- package/dist/agents/codex-runner.js +2639 -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 +160 -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 +4147 -0
- package/dist/channels/daemon.js +422 -0
- package/dist/channels/dingtalk.js +649 -0
- package/dist/channels/feishu.js +1789 -0
- package/dist/channels/qqbot.js +409 -0
- package/dist/channels/wechat.js +817 -0
- package/dist/channels/wecom.js +565 -0
- package/dist/cli/agent-command.js +641 -0
- package/dist/cli/agent.js +1059 -0
- package/dist/cli/aun-commands.js +1948 -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/ctl-command.js +62 -0
- package/dist/cli/daemon-commands.js +2887 -0
- package/dist/cli/fs-command.js +1447 -0
- package/dist/cli/handoff-command.js +302 -0
- package/dist/cli/help.js +31 -0
- package/dist/cli/index.js +358 -0
- package/dist/cli/init-channel.js +1377 -0
- package/dist/cli/init.js +553 -0
- package/dist/cli/link-rules.js +240 -0
- package/dist/cli/model.js +590 -0
- package/dist/cli/net-check.js +723 -0
- package/dist/cli/queue-command.js +126 -0
- package/dist/cli/response.js +345 -0
- package/dist/cli/restart-monitor.js +456 -0
- package/dist/cli/stats.js +607 -0
- package/dist/cli/task-context.js +80 -0
- package/dist/cli/trigger-command.js +505 -0
- package/dist/cli/version.js +88 -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 +30 -0
- package/dist/config/builtin-roles.js +85 -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 +916 -0
- package/dist/config/config-operation-service.js +384 -0
- package/dist/config/contact-book.js +371 -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 +139 -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-schema.js +105 -0
- package/dist/config/role-service.js +159 -0
- package/dist/config/role-store.js +204 -0
- package/dist/config/roles.js +55 -0
- package/dist/config/schema-registry.js +154 -0
- package/dist/config/snapshot.js +598 -0
- package/dist/config-store.js +503 -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 +110 -0
- package/dist/core/auth/operation-authorizer.js +718 -0
- package/dist/core/auth/operation-catalog.js +675 -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 +1638 -0
- package/dist/core/command/menu-handler.js +3354 -0
- package/dist/core/command/menu-protocol.js +247 -0
- package/dist/core/command/role-menu.js +1524 -0
- package/dist/core/command/slash-gate.js +148 -0
- package/dist/core/command/slash-handler.js +3056 -0
- package/dist/core/daemon-file-cache.js +216 -0
- package/dist/core/event-bus.js +32 -0
- package/dist/core/event-catalog.js +740 -0
- package/dist/core/evolagent-registry.js +546 -0
- package/dist/core/evolagent.js +331 -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 +657 -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 +803 -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 +1436 -0
- package/dist/core/message/message-utils.js +70 -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 +3776 -0
- package/dist/core/message/response-snapshot.js +83 -0
- package/dist/core/message/stream-debouncer.js +130 -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 +987 -0
- package/dist/core/permission/unix-socket-policy.js +99 -0
- package/dist/core/protected-paths.js +330 -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 +232 -0
- package/dist/core/session/session-key.js +24 -0
- package/dist/core/session/session-manager.js +1587 -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 +2870 -4
- package/dist/ipc.js +748 -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 +151 -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 +291 -0
- package/dist/trigger/parser.js +520 -0
- package/dist/trigger/patch.js +148 -0
- package/dist/trigger/scheduler.js +1600 -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 +621 -0
- package/dist/types.js +12 -0
- package/dist/utils/aid-bind.js +299 -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 +437 -0
- package/dist/utils/locale.js +21 -0
- package/dist/utils/log-writer.js +224 -0
- package/dist/utils/logger.js +89 -0
- package/dist/utils/markdown-to-plain-text.js +20 -0
- package/dist/utils/media-cache.js +271 -0
- package/dist/utils/model-prices.jsonl +17 -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/stats.js +410 -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 +65 -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 +66 -0
- package/kits/docs/evolcore/agent.md +69 -0
- package/kits/docs/evolcore/aid.md +49 -0
- package/kits/docs/evolcore/config.md +149 -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 +101 -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 +141 -0
- package/kits/docs/evolcore/model.md +47 -0
- package/kits/docs/evolcore/msg.md +130 -0
- package/kits/docs/evolcore/response.md +80 -0
- package/kits/docs/evolcore/rpc.md +35 -0
- package/kits/docs/evolcore/self-summary.md +29 -0
- package/kits/docs/evolcore/stats.md +70 -0
- package/kits/docs/evolcore/storage.md +49 -0
- package/kits/docs/evolcore/trigger.md +524 -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 +191 -0
- package/kits/eck_message_manifest.json +63 -0
- package/kits/migrations/README-role-config-v4.md +32 -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 +29 -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/contact-book.schema.1.json +36 -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/role-config.schema.1.json +200 -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 +8 -0
- package/kits/templates/roles/member.json +40 -0
- package/kits/templates/roles/owner.json +8 -0
- package/kits/templates/roles/visitor.json +39 -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 +26 -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,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini SessionFileAdapter
|
|
3
|
+
*
|
|
4
|
+
* Reads Gemini CLI session files from ~/.gemini/tmp/{project}/chats/.
|
|
5
|
+
* Gemini stores sessions as JSON files with naming convention:
|
|
6
|
+
* session-{YYYY-MM-DDTHH-mm}-{uuid-prefix}.json
|
|
7
|
+
*
|
|
8
|
+
* Each file contains:
|
|
9
|
+
* { sessionId, projectHash, startTime, lastUpdated, messages, kind }
|
|
10
|
+
*
|
|
11
|
+
* Messages have type: 'user' | 'gemini' (not 'role').
|
|
12
|
+
*/
|
|
13
|
+
import { logger } from '../../../utils/logger.js';
|
|
14
|
+
import { buildCliSessionPreview } from '../session-title.js';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import os from 'os';
|
|
18
|
+
export class GeminiSessionFileAdapter {
|
|
19
|
+
baseagent = 'gemini';
|
|
20
|
+
getGeminiHome() {
|
|
21
|
+
return path.join(os.homedir(), '.gemini');
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve Gemini project key for a project path.
|
|
25
|
+
* Priority:
|
|
26
|
+
* 1. ~/.gemini/projects.json explicit mapping
|
|
27
|
+
* 2. .project_root file content match under ~/.gemini/tmp/<project-key>/
|
|
28
|
+
* 3. basename(projectPath) fallback
|
|
29
|
+
*/
|
|
30
|
+
resolveProjectKey(projectPath) {
|
|
31
|
+
const geminiHome = this.getGeminiHome();
|
|
32
|
+
const projectsPath = path.join(geminiHome, 'projects.json');
|
|
33
|
+
try {
|
|
34
|
+
if (fs.existsSync(projectsPath)) {
|
|
35
|
+
const data = JSON.parse(fs.readFileSync(projectsPath, 'utf-8'));
|
|
36
|
+
const mapped = data?.projects?.[projectPath];
|
|
37
|
+
if (typeof mapped === 'string' && mapped.trim())
|
|
38
|
+
return mapped;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
logger.debug('[GeminiAdapter] Failed to read projects.json:', error);
|
|
43
|
+
}
|
|
44
|
+
const tmpDir = path.join(geminiHome, 'tmp');
|
|
45
|
+
if (fs.existsSync(tmpDir)) {
|
|
46
|
+
try {
|
|
47
|
+
for (const entry of fs.readdirSync(tmpDir, { withFileTypes: true })) {
|
|
48
|
+
if (!entry.isDirectory())
|
|
49
|
+
continue;
|
|
50
|
+
const rootFile = path.join(tmpDir, entry.name, '.project_root');
|
|
51
|
+
if (!fs.existsSync(rootFile))
|
|
52
|
+
continue;
|
|
53
|
+
try {
|
|
54
|
+
const content = fs.readFileSync(rootFile, 'utf-8').trim();
|
|
55
|
+
if (content === projectPath)
|
|
56
|
+
return entry.name;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// ignore broken .project_root
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.debug('[GeminiAdapter] Failed to scan tmp project roots:', error);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return path.basename(projectPath);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the Gemini chats directory for a given project path.
|
|
71
|
+
*/
|
|
72
|
+
resolveChatsDir(projectPath) {
|
|
73
|
+
const geminiHome = this.getGeminiHome();
|
|
74
|
+
const projectKey = this.resolveProjectKey(projectPath);
|
|
75
|
+
return path.join(geminiHome, 'tmp', projectKey, 'chats');
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Find the session file matching a given session UUID.
|
|
79
|
+
* Gemini files are named session-{date}-{uuid-prefix}.json where
|
|
80
|
+
* uuid-prefix is the first 8 chars of the session UUID.
|
|
81
|
+
*/
|
|
82
|
+
findSessionFile(projectPath, agentSessionId) {
|
|
83
|
+
const chatsDir = this.resolveChatsDir(projectPath);
|
|
84
|
+
if (!fs.existsSync(chatsDir))
|
|
85
|
+
return null;
|
|
86
|
+
const uuidPrefix = agentSessionId.substring(0, 8);
|
|
87
|
+
try {
|
|
88
|
+
const files = fs.readdirSync(chatsDir);
|
|
89
|
+
const match = files.find(f => f.includes(uuidPrefix) && f.endsWith('.json'));
|
|
90
|
+
return match ? path.join(chatsDir, match) : null;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
readSessionData(projectPath, agentSessionId) {
|
|
97
|
+
const filePath = this.findSessionFile(projectPath, agentSessionId);
|
|
98
|
+
if (!filePath)
|
|
99
|
+
return null;
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
logger.warn(`[GeminiAdapter] Failed to read session file: ${filePath}`, error);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
checkExists(projectPath, agentSessionId) {
|
|
109
|
+
return this.findSessionFile(projectPath, agentSessionId) !== null;
|
|
110
|
+
}
|
|
111
|
+
getFileInfo(projectPath, agentSessionId) {
|
|
112
|
+
const data = this.readSessionData(projectPath, agentSessionId);
|
|
113
|
+
if (!data)
|
|
114
|
+
return { turns: 0 };
|
|
115
|
+
const msgs = data.messages || [];
|
|
116
|
+
const userTurns = msgs.filter((m) => m.type === 'user').length;
|
|
117
|
+
const preview = this.buildSessionPreview(data, false, false, 80);
|
|
118
|
+
return { turns: userTurns, title: preview.title || undefined };
|
|
119
|
+
}
|
|
120
|
+
readFirstMessage(projectPath, agentSessionId) {
|
|
121
|
+
return this.readUserMessage(projectPath, agentSessionId, 'first');
|
|
122
|
+
}
|
|
123
|
+
readLastUserMessage(projectPath, agentSessionId) {
|
|
124
|
+
return this.readUserMessage(projectPath, agentSessionId, 'last');
|
|
125
|
+
}
|
|
126
|
+
scanCliSessions(projectPath, limit = 10) {
|
|
127
|
+
const chatsDir = this.resolveChatsDir(projectPath);
|
|
128
|
+
if (!fs.existsSync(chatsDir))
|
|
129
|
+
return [];
|
|
130
|
+
try {
|
|
131
|
+
const files = fs.readdirSync(chatsDir)
|
|
132
|
+
.filter(f => f.startsWith('session-') && f.endsWith('.json'))
|
|
133
|
+
.map((f) => {
|
|
134
|
+
const filePath = path.join(chatsDir, f);
|
|
135
|
+
const stat = fs.statSync(filePath);
|
|
136
|
+
// Extract UUID from filename: session-{date}-{uuid-prefix}.json
|
|
137
|
+
// Read the file to get full UUID
|
|
138
|
+
try {
|
|
139
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
140
|
+
const preview = this.buildSessionPreview(data, false, false, 80);
|
|
141
|
+
if (!preview.hasReal)
|
|
142
|
+
return null;
|
|
143
|
+
return { uuid: data.sessionId, mtime: stat.mtimeMs, title: preview.title || undefined };
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
})
|
|
149
|
+
.filter((e) => e !== null)
|
|
150
|
+
.sort((a, b) => b.mtime - a.mtime)
|
|
151
|
+
.slice(0, limit);
|
|
152
|
+
return files;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
logger.warn('[GeminiAdapter] scanCliSessions failed:', error);
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
readUserMessage(projectPath, agentSessionId, which) {
|
|
160
|
+
const data = this.readSessionData(projectPath, agentSessionId);
|
|
161
|
+
if (!data)
|
|
162
|
+
return null;
|
|
163
|
+
return this.buildSessionPreview(data, which === 'last').title;
|
|
164
|
+
}
|
|
165
|
+
buildSessionPreview(data, reverse = false, includeSyntheticFallback = true, maxLength = 50) {
|
|
166
|
+
const messages = (data.messages || [])
|
|
167
|
+
.filter((message) => message.type === 'user')
|
|
168
|
+
.map((message) => this.extractRawUserText(message.content))
|
|
169
|
+
.filter((text) => !!text);
|
|
170
|
+
if (reverse)
|
|
171
|
+
messages.reverse();
|
|
172
|
+
return buildCliSessionPreview(messages, 'gemini', { includeSyntheticFallback, maxLength });
|
|
173
|
+
}
|
|
174
|
+
extractRawUserText(content) {
|
|
175
|
+
const text = Array.isArray(content)
|
|
176
|
+
? content.find((part) => part?.text)?.text || ''
|
|
177
|
+
: String(content || '');
|
|
178
|
+
const normalized = text.trim().replace(/\s+/g, ' ');
|
|
179
|
+
return normalized || null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fsPromises from 'fs/promises';
|
|
2
|
+
import { logger } from '../../utils/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* 检查会话文件健康度(接收完整文件路径)
|
|
5
|
+
*/
|
|
6
|
+
export async function checkSessionFile(sessionFile) {
|
|
7
|
+
const issues = [];
|
|
8
|
+
try {
|
|
9
|
+
const stats = await fsPromises.stat(sessionFile);
|
|
10
|
+
const sizeMB = stats.size / (1024 * 1024);
|
|
11
|
+
if (stats.size > 50 * 1024 * 1024) {
|
|
12
|
+
issues.push(`会话文件过大: ${sizeMB.toFixed(1)}MB`);
|
|
13
|
+
}
|
|
14
|
+
const content = await fsPromises.readFile(sessionFile, 'utf-8');
|
|
15
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
16
|
+
for (let i = 0; i < lines.length; i++) {
|
|
17
|
+
try {
|
|
18
|
+
JSON.parse(lines[i]);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
issues.push(`会话文件格式损坏(第 ${i + 1} 行)`);
|
|
22
|
+
return { healthy: false, issues, corrupt: true, fileSize: stats.size };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
healthy: issues.length === 0,
|
|
27
|
+
issues,
|
|
28
|
+
fileSize: stats.size
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.error('[SessionFileHealth] Check failed:', error);
|
|
33
|
+
issues.push(`文件读取失败: ${error.message}`);
|
|
34
|
+
return { healthy: false, issues, corrupt: true };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 备份单个会话文件(在同目录下创建 .bak 副本)
|
|
39
|
+
*/
|
|
40
|
+
export async function backupSessionFile(sessionFile) {
|
|
41
|
+
const backupPath = `${sessionFile}.bak-${Date.now()}`;
|
|
42
|
+
await fsPromises.copyFile(sessionFile, backupPath);
|
|
43
|
+
logger.info(`[SessionFileHealth] Backup created: ${backupPath}`);
|
|
44
|
+
return backupPath;
|
|
45
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
// 文件系统非法字符(Windows 最严格):< > : " / \ | ? *
|
|
4
|
+
// 还有控制字符 0x00-0x1F。我们把这些字符编码为 %XX(hex 大写)。
|
|
5
|
+
// `%` 本身也要转义为 %25,保证可逆。
|
|
6
|
+
const UNSAFE_CHARS_RE = /[<>:"/\\|?*\x00-\x1F%]/g;
|
|
7
|
+
function encodeSegment(s) {
|
|
8
|
+
return s.replace(UNSAFE_CHARS_RE, ch => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'));
|
|
9
|
+
}
|
|
10
|
+
function decodeSegment(s) {
|
|
11
|
+
return s.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 计算 chat 目录的完整路径。
|
|
15
|
+
* - aun: sessionsDir/aun/<urlEncode(selfAID|'_unknown')>/<urlEncode(channelId)>/
|
|
16
|
+
* - 其它: sessionsDir/<channelType>/<urlEncode(channelId)>/
|
|
17
|
+
*/
|
|
18
|
+
export function chatDirPath(sessionsDir, channelType, channelId, selfAID) {
|
|
19
|
+
if (channelType === 'aun') {
|
|
20
|
+
return path.join(sessionsDir, channelType, encodeSegment(selfAID || '_unknown'), encodeSegment(channelId));
|
|
21
|
+
}
|
|
22
|
+
return path.join(sessionsDir, channelType, encodeSegment(channelId));
|
|
23
|
+
}
|
|
24
|
+
/** 解码目录段(用于扫描时把目录名还原为原始 channelId/selfAID) */
|
|
25
|
+
export function decodeDirSegment(seg) {
|
|
26
|
+
return decodeSegment(seg);
|
|
27
|
+
}
|
|
28
|
+
export function generateSessionId(now) {
|
|
29
|
+
const ts = now ?? Date.now();
|
|
30
|
+
const d = new Date(ts);
|
|
31
|
+
const yyyy = d.getFullYear();
|
|
32
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
33
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
34
|
+
return `meta_${yyyy}${mm}${dd}_${ts}`;
|
|
35
|
+
}
|
|
36
|
+
export function formatTimestamp(epochMs) {
|
|
37
|
+
const d = new Date(epochMs);
|
|
38
|
+
const yyyy = d.getFullYear();
|
|
39
|
+
const mo = String(d.getMonth() + 1).padStart(2, '0');
|
|
40
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
41
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
42
|
+
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
43
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
44
|
+
return `${yyyy}-${mo}-${dd} ${hh}:${mi}:${ss}`;
|
|
45
|
+
}
|
|
46
|
+
export function atomicWriteJson(filePath, data) {
|
|
47
|
+
const tmpPath = filePath + '.tmp';
|
|
48
|
+
const content = JSON.stringify(data, null, 2) + '\n';
|
|
49
|
+
const fd = fs.openSync(tmpPath, 'w');
|
|
50
|
+
fs.writeSync(fd, content);
|
|
51
|
+
fs.fsyncSync(fd);
|
|
52
|
+
fs.closeSync(fd);
|
|
53
|
+
try {
|
|
54
|
+
fs.unlinkSync(filePath);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
if (e.code !== 'ENOENT')
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
fs.renameSync(tmpPath, filePath);
|
|
61
|
+
}
|
|
62
|
+
export function appendJsonl(filePath, record) {
|
|
63
|
+
const line = JSON.stringify(record) + '\n';
|
|
64
|
+
const fd = fs.openSync(filePath, 'a');
|
|
65
|
+
fs.writeSync(fd, line);
|
|
66
|
+
fs.fsyncSync(fd);
|
|
67
|
+
fs.closeSync(fd);
|
|
68
|
+
}
|
|
69
|
+
export function readJsonFile(filePath) {
|
|
70
|
+
try {
|
|
71
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
72
|
+
return JSON.parse(content);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
if (e.code === 'ENOENT')
|
|
76
|
+
return undefined;
|
|
77
|
+
if (e instanceof SyntaxError)
|
|
78
|
+
return undefined;
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function readLastJsonlLine(filePath) {
|
|
83
|
+
try {
|
|
84
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
85
|
+
const lines = content.trimEnd().split('\n');
|
|
86
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
87
|
+
const line = lines[i].trim();
|
|
88
|
+
if (!line)
|
|
89
|
+
continue;
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(line);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
if (e.code === 'ENOENT')
|
|
101
|
+
return undefined;
|
|
102
|
+
throw e;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function readAllJsonlLines(filePath) {
|
|
106
|
+
try {
|
|
107
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
108
|
+
const results = [];
|
|
109
|
+
for (const line of content.split('\n')) {
|
|
110
|
+
const trimmed = line.trim();
|
|
111
|
+
if (!trimmed)
|
|
112
|
+
continue;
|
|
113
|
+
try {
|
|
114
|
+
results.push(JSON.parse(trimmed));
|
|
115
|
+
}
|
|
116
|
+
catch { /* skip corrupt line */ }
|
|
117
|
+
}
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
catch (e) {
|
|
121
|
+
if (e.code === 'ENOENT')
|
|
122
|
+
return [];
|
|
123
|
+
throw e;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* 扫描所有 chat 目录。
|
|
128
|
+
* - aun: channelType / selfAID / channelId (3层)
|
|
129
|
+
* - 其它: channelType / channelId (2层)
|
|
130
|
+
*/
|
|
131
|
+
export function scanChatDirs(sessionsDir) {
|
|
132
|
+
const results = [];
|
|
133
|
+
let typeEntries;
|
|
134
|
+
try {
|
|
135
|
+
typeEntries = fs.readdirSync(sessionsDir, { withFileTypes: true });
|
|
136
|
+
}
|
|
137
|
+
catch (e) {
|
|
138
|
+
if (e.code === 'ENOENT')
|
|
139
|
+
return [];
|
|
140
|
+
throw e;
|
|
141
|
+
}
|
|
142
|
+
for (const typeEntry of typeEntries) {
|
|
143
|
+
if (!typeEntry.isDirectory())
|
|
144
|
+
continue;
|
|
145
|
+
const channelType = typeEntry.name;
|
|
146
|
+
const typeDir = path.join(sessionsDir, channelType);
|
|
147
|
+
let level2Entries;
|
|
148
|
+
try {
|
|
149
|
+
level2Entries = fs.readdirSync(typeDir, { withFileTypes: true });
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (channelType === 'aun') {
|
|
155
|
+
// 3-layer: aun/selfAID/channelId
|
|
156
|
+
for (const selfEntry of level2Entries) {
|
|
157
|
+
if (!selfEntry.isDirectory())
|
|
158
|
+
continue;
|
|
159
|
+
const selfDir = path.join(typeDir, selfEntry.name);
|
|
160
|
+
let chatEntries;
|
|
161
|
+
try {
|
|
162
|
+
chatEntries = fs.readdirSync(selfDir, { withFileTypes: true });
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
for (const chatEntry of chatEntries) {
|
|
168
|
+
if (!chatEntry.isDirectory())
|
|
169
|
+
continue;
|
|
170
|
+
results.push({
|
|
171
|
+
channelType,
|
|
172
|
+
selfAID: decodeSegment(selfEntry.name),
|
|
173
|
+
channelId: decodeSegment(chatEntry.name),
|
|
174
|
+
dirPath: path.join(selfDir, chatEntry.name),
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
// 2-layer: channelType/channelId
|
|
181
|
+
for (const chatEntry of level2Entries) {
|
|
182
|
+
if (!chatEntry.isDirectory())
|
|
183
|
+
continue;
|
|
184
|
+
results.push({
|
|
185
|
+
channelType,
|
|
186
|
+
selfAID: '',
|
|
187
|
+
channelId: decodeSegment(chatEntry.name),
|
|
188
|
+
dirPath: path.join(typeDir, chatEntry.name),
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return results;
|
|
194
|
+
}
|
|
195
|
+
export function scanMetaFiles(chatDir) {
|
|
196
|
+
try {
|
|
197
|
+
const entries = fs.readdirSync(chatDir);
|
|
198
|
+
return entries
|
|
199
|
+
.filter(f => f.startsWith('meta_') && f.endsWith('.jsonl'))
|
|
200
|
+
.sort();
|
|
201
|
+
}
|
|
202
|
+
catch (e) {
|
|
203
|
+
if (e.code === 'ENOENT')
|
|
204
|
+
return [];
|
|
205
|
+
throw e;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
export function ensureChatDir(sessionsDir, channelType, channelId, selfAID) {
|
|
209
|
+
const dir = chatDirPath(sessionsDir, channelType, channelId, selfAID);
|
|
210
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
211
|
+
fs.mkdirSync(path.join(dir, '_threads'), { recursive: true });
|
|
212
|
+
fs.mkdirSync(path.join(dir, '_trash'), { recursive: true });
|
|
213
|
+
return dir;
|
|
214
|
+
}
|
|
215
|
+
export function readThreadIndex(chatDir) {
|
|
216
|
+
const raw = readJsonFile(path.join(chatDir, '_threads', 'thread-index.json')) || {};
|
|
217
|
+
// Migrate legacy format: { threadId: sessionId } → { threadId: { sessionId, sessionKey, metaFile } }
|
|
218
|
+
const result = {};
|
|
219
|
+
for (const [tid, val] of Object.entries(raw)) {
|
|
220
|
+
if (typeof val === 'string') {
|
|
221
|
+
// Legacy: val is sessionId
|
|
222
|
+
result[tid] = { sessionId: val, sessionKey: '', metaFile: `${val}.jsonl` };
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
result[tid] = val;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}
|
|
230
|
+
export function writeThreadIndex(chatDir, index) {
|
|
231
|
+
atomicWriteJson(path.join(chatDir, '_threads', 'thread-index.json'), index);
|
|
232
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sessionKey: agent 内部会话路由键。
|
|
3
|
+
* 格式: channelType#urlEncode(channelId)#urlEncode(threadId)
|
|
4
|
+
* 无话题时 threadId 固定为 'main'。
|
|
5
|
+
*/
|
|
6
|
+
export const DEFAULT_THREAD_ID = 'main';
|
|
7
|
+
export function formatSessionKey(channelType, channelId, threadId) {
|
|
8
|
+
const tid = threadId || DEFAULT_THREAD_ID;
|
|
9
|
+
return `${channelType}#${encodeURIComponent(channelId)}#${encodeURIComponent(tid)}`;
|
|
10
|
+
}
|
|
11
|
+
export function parseSessionKey(key) {
|
|
12
|
+
const first = key.indexOf('#');
|
|
13
|
+
if (first <= 0)
|
|
14
|
+
throw new Error(`Invalid session key: ${key}`);
|
|
15
|
+
const rest = key.slice(first + 1);
|
|
16
|
+
const second = rest.indexOf('#');
|
|
17
|
+
if (second <= 0)
|
|
18
|
+
throw new Error(`Invalid session key (missing threadId): ${key}`);
|
|
19
|
+
return {
|
|
20
|
+
channelType: key.slice(0, first),
|
|
21
|
+
channelId: decodeURIComponent(rest.slice(0, second)),
|
|
22
|
+
threadId: decodeURIComponent(rest.slice(second + 1)),
|
|
23
|
+
};
|
|
24
|
+
}
|