hyacinth-ai 0.9.6
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/LICENSE +21 -0
- package/README.md +205 -0
- package/commands.json +25 -0
- package/dist/agents/builtins.d.ts +8 -0
- package/dist/agents/builtins.js +50 -0
- package/dist/agents/config-loader.d.ts +15 -0
- package/dist/agents/config-loader.js +75 -0
- package/dist/agents/delegate-tool.d.ts +109 -0
- package/dist/agents/delegate-tool.js +407 -0
- package/dist/agents/index.d.ts +5 -0
- package/dist/agents/index.js +5 -0
- package/dist/agents/registry.d.ts +2 -0
- package/dist/agents/registry.js +2 -0
- package/dist/bypass/agents/companion/index.d.ts +57 -0
- package/dist/bypass/agents/companion/index.js +487 -0
- package/dist/bypass/agents/orchestrator/index.d.ts +17 -0
- package/dist/bypass/agents/orchestrator/index.js +414 -0
- package/dist/bypass/agents/orchestrator.d.ts +16 -0
- package/dist/bypass/agents/orchestrator.js +377 -0
- package/dist/bypass/agents/world-engine/agent.d.ts +41 -0
- package/dist/bypass/agents/world-engine/agent.js +385 -0
- package/dist/bypass/agents/world-engine/index.d.ts +52 -0
- package/dist/bypass/agents/world-engine/index.js +198 -0
- package/dist/bypass/agents/world-engine/sim.d.ts +14 -0
- package/dist/bypass/agents/world-engine/sim.js +120 -0
- package/dist/bypass/agents/world-engine/store.d.ts +110 -0
- package/dist/bypass/agents/world-engine/store.js +519 -0
- package/dist/bypass/agents/world-engine/ticker.d.ts +37 -0
- package/dist/bypass/agents/world-engine/ticker.js +214 -0
- package/dist/bypass/agents/world-engine/tools.d.ts +6 -0
- package/dist/bypass/agents/world-engine/tools.js +380 -0
- package/dist/bypass/agents/world-engine/types.d.ts +202 -0
- package/dist/bypass/agents/world-engine/types.js +56 -0
- package/dist/bypass/agents/world-engine.d.ts +20 -0
- package/dist/bypass/agents/world-engine.js +63 -0
- package/dist/bypass/base.d.ts +48 -0
- package/dist/bypass/base.js +126 -0
- package/dist/bypass/index.d.ts +5 -0
- package/dist/bypass/index.js +6 -0
- package/dist/bypass/manager.d.ts +41 -0
- package/dist/bypass/manager.js +179 -0
- package/dist/bypass/types.d.ts +63 -0
- package/dist/bypass/types.js +14 -0
- package/dist/channels/auto-detect.d.ts +25 -0
- package/dist/channels/auto-detect.js +77 -0
- package/dist/channels/builtin/http-webhook.d.ts +25 -0
- package/dist/channels/builtin/http-webhook.js +287 -0
- package/dist/channels/builtin/tui-channel.d.ts +33 -0
- package/dist/channels/builtin/tui-channel.js +61 -0
- package/dist/channels/builtin/tui-ws-session.d.ts +16 -0
- package/dist/channels/builtin/tui-ws-session.js +90 -0
- package/dist/channels/builtin/webui-output-handler.d.ts +30 -0
- package/dist/channels/builtin/webui-output-handler.js +93 -0
- package/dist/channels/index.d.ts +5 -0
- package/dist/channels/index.js +3 -0
- package/dist/channels/interface.d.ts +148 -0
- package/dist/channels/interface.js +15 -0
- package/dist/channels/manager.d.ts +43 -0
- package/dist/channels/manager.js +156 -0
- package/dist/channels/message-queue.d.ts +36 -0
- package/dist/channels/message-queue.js +91 -0
- package/dist/channels/plugins/clawbot/clawbot-auth.d.ts +50 -0
- package/dist/channels/plugins/clawbot/clawbot-auth.js +235 -0
- package/dist/channels/plugins/clawbot/clawbot-channel.d.ts +76 -0
- package/dist/channels/plugins/clawbot/clawbot-channel.js +566 -0
- package/dist/channels/plugins/clawbot/clawbot-client.d.ts +180 -0
- package/dist/channels/plugins/clawbot/clawbot-client.js +312 -0
- package/dist/channels/plugins/clawbot/clawbot-config.d.ts +30 -0
- package/dist/channels/plugins/clawbot/clawbot-config.js +28 -0
- package/dist/channels/plugins/clawbot/clawbot-message-queue.d.ts +27 -0
- package/dist/channels/plugins/clawbot/clawbot-message-queue.js +114 -0
- package/dist/channels/plugins/clawbot/clawbot-session.d.ts +10 -0
- package/dist/channels/plugins/clawbot/clawbot-session.js +26 -0
- package/dist/channels/plugins/clawbot/index.d.ts +17 -0
- package/dist/channels/plugins/clawbot/index.js +58 -0
- package/dist/channels/plugins/feishu/feishu-channel.d.ts +57 -0
- package/dist/channels/plugins/feishu/feishu-channel.js +602 -0
- package/dist/channels/plugins/feishu/feishu-client.d.ts +23 -0
- package/dist/channels/plugins/feishu/feishu-client.js +86 -0
- package/dist/channels/plugins/feishu/feishu-config.d.ts +53 -0
- package/dist/channels/plugins/feishu/feishu-config.js +35 -0
- package/dist/channels/plugins/feishu/feishu-event.d.ts +102 -0
- package/dist/channels/plugins/feishu/feishu-event.js +257 -0
- package/dist/channels/plugins/feishu/feishu-log-suppressor.d.ts +7 -0
- package/dist/channels/plugins/feishu/feishu-log-suppressor.js +34 -0
- package/dist/channels/plugins/feishu/feishu-message-queue.d.ts +27 -0
- package/dist/channels/plugins/feishu/feishu-message-queue.js +112 -0
- package/dist/channels/plugins/feishu/feishu-send.d.ts +44 -0
- package/dist/channels/plugins/feishu/feishu-send.js +232 -0
- package/dist/channels/plugins/feishu/feishu-session.d.ts +44 -0
- package/dist/channels/plugins/feishu/feishu-session.js +84 -0
- package/dist/channels/plugins/feishu/feishu-streaming.d.ts +49 -0
- package/dist/channels/plugins/feishu/feishu-streaming.js +185 -0
- package/dist/channels/plugins/feishu/feishu-transport.d.ts +29 -0
- package/dist/channels/plugins/feishu/feishu-transport.js +99 -0
- package/dist/channels/plugins/feishu/index.d.ts +22 -0
- package/dist/channels/plugins/feishu/index.js +68 -0
- package/dist/cli/doctor.d.ts +20 -0
- package/dist/cli/doctor.js +317 -0
- package/dist/context/cache-strategy.d.ts +76 -0
- package/dist/context/cache-strategy.js +123 -0
- package/dist/context/composer.d.ts +77 -0
- package/dist/context/composer.js +318 -0
- package/dist/context/compressor.d.ts +218 -0
- package/dist/context/compressor.js +909 -0
- package/dist/context/index.d.ts +14 -0
- package/dist/context/index.js +8 -0
- package/dist/context/interface.d.ts +33 -0
- package/dist/context/interface.js +2 -0
- package/dist/context/manifest-defaults.d.ts +51 -0
- package/dist/context/manifest-defaults.js +133 -0
- package/dist/context/manifest-loader.d.ts +16 -0
- package/dist/context/manifest-loader.js +103 -0
- package/dist/context/manifest-types.d.ts +26 -0
- package/dist/context/manifest-types.js +2 -0
- package/dist/context/precision/analyzer.d.ts +31 -0
- package/dist/context/precision/analyzer.js +131 -0
- package/dist/context/precision/companion.d.ts +27 -0
- package/dist/context/precision/companion.js +144 -0
- package/dist/context/precision/default.d.ts +14 -0
- package/dist/context/precision/default.js +17 -0
- package/dist/context/precision/index.d.ts +9 -0
- package/dist/context/precision/index.js +7 -0
- package/dist/context/precision/keyword-pool.d.ts +27 -0
- package/dist/context/precision/keyword-pool.js +66 -0
- package/dist/context/precision/strategy.d.ts +18 -0
- package/dist/context/precision/strategy.js +51 -0
- package/dist/context/precision/summary-store.d.ts +18 -0
- package/dist/context/precision/summary-store.js +67 -0
- package/dist/context/precision/types.d.ts +23 -0
- package/dist/context/precision/types.js +2 -0
- package/dist/context/profiles.d.ts +45 -0
- package/dist/context/profiles.js +92 -0
- package/dist/context/prompt-builder.d.ts +76 -0
- package/dist/context/prompt-builder.js +133 -0
- package/dist/context/retriever.d.ts +44 -0
- package/dist/context/retriever.js +203 -0
- package/dist/context/router.d.ts +122 -0
- package/dist/context/router.js +291 -0
- package/dist/context/section-resolver.d.ts +38 -0
- package/dist/context/section-resolver.js +398 -0
- package/dist/context/tokenizer.d.ts +23 -0
- package/dist/context/tokenizer.js +78 -0
- package/dist/dependency/analyzer.d.ts +33 -0
- package/dist/dependency/analyzer.js +242 -0
- package/dist/dependency/data-flow-tracker.d.ts +12 -0
- package/dist/dependency/data-flow-tracker.js +97 -0
- package/dist/dependency/function-parser.d.ts +34 -0
- package/dist/dependency/function-parser.js +295 -0
- package/dist/dependency/index.d.ts +14 -0
- package/dist/dependency/index.js +20 -0
- package/dist/dependency/parser.d.ts +10 -0
- package/dist/dependency/parser.js +93 -0
- package/dist/dependency/types.d.ts +57 -0
- package/dist/dependency/types.js +2 -0
- package/dist/env/env-collector.d.ts +45 -0
- package/dist/env/env-collector.js +221 -0
- package/dist/env/index.d.ts +3 -0
- package/dist/env/index.js +2 -0
- package/dist/event-store.d.ts +27 -0
- package/dist/event-store.js +71 -0
- package/dist/evolution/git-manager.d.ts +46 -0
- package/dist/evolution/git-manager.js +276 -0
- package/dist/evolution/index.d.ts +3 -0
- package/dist/evolution/index.js +2 -0
- package/dist/flow/bootstrap-flow.d.ts +19 -0
- package/dist/flow/bootstrap-flow.js +72 -0
- package/dist/flow/flow-registry.d.ts +12 -0
- package/dist/flow/flow-registry.js +74 -0
- package/dist/flow/flow-tools.d.ts +8 -0
- package/dist/flow/flow-tools.js +106 -0
- package/dist/flow/index.d.ts +6 -0
- package/dist/flow/index.js +8 -0
- package/dist/flow/todo-flow.d.ts +25 -0
- package/dist/flow/todo-flow.js +107 -0
- package/dist/flow/types.d.ts +78 -0
- package/dist/flow/types.js +12 -0
- package/dist/flows/bootstrap.d.ts +18 -0
- package/dist/flows/bootstrap.js +119 -0
- package/dist/flows/index.d.ts +6 -0
- package/dist/flows/index.js +11 -0
- package/dist/flows/todo.d.ts +26 -0
- package/dist/flows/todo.js +174 -0
- package/dist/flows/tools.d.ts +8 -0
- package/dist/flows/tools.js +119 -0
- package/dist/flows/types.d.ts +27 -0
- package/dist/flows/types.js +10 -0
- package/dist/gateway/cli.d.ts +5 -0
- package/dist/gateway/cli.js +1027 -0
- package/dist/gateway/factory.d.ts +80 -0
- package/dist/gateway/factory.js +832 -0
- package/dist/gateway/guardian.d.ts +8 -0
- package/dist/gateway/guardian.js +30 -0
- package/dist/gateway/index.d.ts +3 -0
- package/dist/gateway/index.js +3 -0
- package/dist/gateway/server.d.ts +17 -0
- package/dist/gateway/server.js +96 -0
- package/dist/gateway/tui.d.ts +16 -0
- package/dist/gateway/tui.js +3273 -0
- package/dist/hot-reload/agent-watcher.d.ts +24 -0
- package/dist/hot-reload/agent-watcher.js +175 -0
- package/dist/hot-reload/bundle-watcher.d.ts +12 -0
- package/dist/hot-reload/bundle-watcher.js +35 -0
- package/dist/hot-reload/channel-watcher.d.ts +19 -0
- package/dist/hot-reload/channel-watcher.js +54 -0
- package/dist/hot-reload/command-watcher.d.ts +14 -0
- package/dist/hot-reload/command-watcher.js +54 -0
- package/dist/hot-reload/config-watcher.d.ts +20 -0
- package/dist/hot-reload/config-watcher.js +58 -0
- package/dist/hot-reload/index.d.ts +12 -0
- package/dist/hot-reload/index.js +12 -0
- package/dist/hot-reload/manager.d.ts +58 -0
- package/dist/hot-reload/manager.js +243 -0
- package/dist/hot-reload/manifest-watcher.d.ts +9 -0
- package/dist/hot-reload/manifest-watcher.js +42 -0
- package/dist/hot-reload/mcp-watcher.d.ts +18 -0
- package/dist/hot-reload/mcp-watcher.js +64 -0
- package/dist/hot-reload/model-catalog-watcher.d.ts +18 -0
- package/dist/hot-reload/model-catalog-watcher.js +62 -0
- package/dist/hot-reload/plugin-watcher.d.ts +20 -0
- package/dist/hot-reload/plugin-watcher.js +56 -0
- package/dist/hot-reload/prompt-watcher.d.ts +6 -0
- package/dist/hot-reload/prompt-watcher.js +38 -0
- package/dist/hot-reload/provider-watcher.d.ts +16 -0
- package/dist/hot-reload/provider-watcher.js +45 -0
- package/dist/hot-reload/skill-watcher.d.ts +19 -0
- package/dist/hot-reload/skill-watcher.js +82 -0
- package/dist/hot-reload/tool-watcher.d.ts +16 -0
- package/dist/hot-reload/tool-watcher.js +168 -0
- package/dist/hot-reload/workflow-watcher.d.ts +19 -0
- package/dist/hot-reload/workflow-watcher.js +84 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/knowledge/fts5-retriever.d.ts +30 -0
- package/dist/knowledge/fts5-retriever.js +223 -0
- package/dist/knowledge/index.d.ts +11 -0
- package/dist/knowledge/index.js +14 -0
- package/dist/knowledge/indexer.d.ts +41 -0
- package/dist/knowledge/indexer.js +186 -0
- package/dist/knowledge/query-preprocessor.d.ts +13 -0
- package/dist/knowledge/query-preprocessor.js +63 -0
- package/dist/knowledge/retriever.d.ts +45 -0
- package/dist/knowledge/retriever.js +9 -0
- package/dist/knowledge/store.d.ts +27 -0
- package/dist/knowledge/store.js +61 -0
- package/dist/knowledge/structured-store.d.ts +78 -0
- package/dist/knowledge/structured-store.js +280 -0
- package/dist/knowledge/structured-tools.d.ts +12 -0
- package/dist/knowledge/structured-tools.js +151 -0
- package/dist/knowledge/tools.d.ts +20 -0
- package/dist/knowledge/tools.js +238 -0
- package/dist/knowledge/watcher.d.ts +28 -0
- package/dist/knowledge/watcher.js +113 -0
- package/dist/lifecycle/index.d.ts +12 -0
- package/dist/lifecycle/index.js +14 -0
- package/dist/lifecycle/interface.d.ts +94 -0
- package/dist/lifecycle/interface.js +2 -0
- package/dist/lifecycle/local-model.d.ts +62 -0
- package/dist/lifecycle/local-model.js +331 -0
- package/dist/lifecycle/manager.d.ts +70 -0
- package/dist/lifecycle/manager.js +374 -0
- package/dist/lifecycle/supervisor.d.ts +89 -0
- package/dist/lifecycle/supervisor.js +277 -0
- package/dist/lifecycle-integration-test.d.ts +2 -0
- package/dist/lifecycle-integration-test.js +72 -0
- package/dist/local-model/download-manager.d.ts +59 -0
- package/dist/local-model/download-manager.js +517 -0
- package/dist/local-model/index.d.ts +109 -0
- package/dist/local-model/index.js +233 -0
- package/dist/local-model/llamacpp-provider.d.ts +95 -0
- package/dist/local-model/llamacpp-provider.js +379 -0
- package/dist/local-model/model-bridge.d.ts +48 -0
- package/dist/local-model/model-bridge.js +256 -0
- package/dist/local-model/model-registry.d.ts +47 -0
- package/dist/local-model/model-registry.js +188 -0
- package/dist/local-model/types.d.ts +49 -0
- package/dist/local-model/types.js +2 -0
- package/dist/logging/index.d.ts +3 -0
- package/dist/logging/index.js +3 -0
- package/dist/logging/logger.d.ts +56 -0
- package/dist/logging/logger.js +94 -0
- package/dist/machine/flows/bootstrap.d.ts +18 -0
- package/dist/machine/flows/bootstrap.js +119 -0
- package/dist/machine/flows/index.d.ts +6 -0
- package/dist/machine/flows/index.js +11 -0
- package/dist/machine/flows/spec.d.ts +17 -0
- package/dist/machine/flows/spec.js +176 -0
- package/dist/machine/flows/todo.d.ts +26 -0
- package/dist/machine/flows/todo.js +175 -0
- package/dist/machine/flows/tools.d.ts +8 -0
- package/dist/machine/flows/tools.js +119 -0
- package/dist/machine/flows/types.d.ts +33 -0
- package/dist/machine/flows/types.js +10 -0
- package/dist/machine/index.d.ts +8 -0
- package/dist/machine/index.js +9 -0
- package/dist/machine/registry.d.ts +34 -0
- package/dist/machine/registry.js +98 -0
- package/dist/machine/runner.d.ts +41 -0
- package/dist/machine/runner.js +177 -0
- package/dist/machine/types.d.ts +121 -0
- package/dist/machine/types.js +9 -0
- package/dist/mcp/bridge.d.ts +33 -0
- package/dist/mcp/bridge.js +99 -0
- package/dist/mcp/client.d.ts +37 -0
- package/dist/mcp/client.js +161 -0
- package/dist/mcp/config.d.ts +15 -0
- package/dist/mcp/config.js +85 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/install-manager.d.ts +31 -0
- package/dist/mcp/install-manager.js +158 -0
- package/dist/mcp/lifecycle.d.ts +41 -0
- package/dist/mcp/lifecycle.js +175 -0
- package/dist/mcp/process-transport.d.ts +21 -0
- package/dist/mcp/process-transport.js +59 -0
- package/dist/mcp/side-effect.d.ts +5 -0
- package/dist/mcp/side-effect.js +21 -0
- package/dist/mcp/system.d.ts +95 -0
- package/dist/mcp/system.js +279 -0
- package/dist/memory/companion-session.d.ts +22 -0
- package/dist/memory/companion-session.js +77 -0
- package/dist/memory/compression-boundary.d.ts +20 -0
- package/dist/memory/compression-boundary.js +40 -0
- package/dist/memory/conversation.d.ts +34 -0
- package/dist/memory/conversation.js +107 -0
- package/dist/memory/events.d.ts +42 -0
- package/dist/memory/events.js +56 -0
- package/dist/memory/index.d.ts +7 -0
- package/dist/memory/index.js +6 -0
- package/dist/memory/memory-store.d.ts +11 -0
- package/dist/memory/memory-store.js +51 -0
- package/dist/memory/session-allowlist.d.ts +11 -0
- package/dist/memory/session-allowlist.js +50 -0
- package/dist/memory/session.d.ts +48 -0
- package/dist/memory/session.js +275 -0
- package/dist/memory/stats.d.ts +23 -0
- package/dist/memory/stats.js +59 -0
- package/dist/memory/summary.d.ts +7 -0
- package/dist/memory/summary.js +23 -0
- package/dist/multimodal/index.d.ts +66 -0
- package/dist/multimodal/index.js +210 -0
- package/dist/orchestrator/index.d.ts +5 -0
- package/dist/orchestrator/index.js +4 -0
- package/dist/orchestrator/loop.d.ts +331 -0
- package/dist/orchestrator/loop.js +2443 -0
- package/dist/orchestrator/plan-store.d.ts +81 -0
- package/dist/orchestrator/plan-store.js +167 -0
- package/dist/orchestrator/planner.d.ts +20 -0
- package/dist/orchestrator/planner.js +34 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +2 -0
- package/dist/parser/router.d.ts +30 -0
- package/dist/parser/router.js +61 -0
- package/dist/plugins/api.d.ts +39 -0
- package/dist/plugins/api.js +85 -0
- package/dist/plugins/define.d.ts +27 -0
- package/dist/plugins/define.js +32 -0
- package/dist/plugins/index.d.ts +9 -0
- package/dist/plugins/index.js +5 -0
- package/dist/plugins/loader.d.ts +39 -0
- package/dist/plugins/loader.js +128 -0
- package/dist/plugins/manager.d.ts +61 -0
- package/dist/plugins/manager.js +257 -0
- package/dist/plugins/types.d.ts +82 -0
- package/dist/plugins/types.js +2 -0
- package/dist/prompts/agents/code-reviewer.md +16 -0
- package/dist/prompts/agents/security-auditor.md +17 -0
- package/dist/prompts/agents/test-writer.md +15 -0
- package/dist/prompts/attention/attention.md +12 -0
- package/dist/prompts/environment/environment.md +18 -0
- package/dist/prompts/environment/feishu.md +107 -0
- package/dist/prompts/flows/bootstrap-confirm.md +14 -0
- package/dist/prompts/flows/bootstrap-identity.md +11 -0
- package/dist/prompts/flows/bootstrap-soul.md +19 -0
- package/dist/prompts/flows/bootstrap-user.md +11 -0
- package/dist/prompts/flows/spec-checklist.md +27 -0
- package/dist/prompts/flows/spec-define.md +64 -0
- package/dist/prompts/flows/spec-executing-checklist.md +15 -0
- package/dist/prompts/flows/spec-executing-tasks.md +15 -0
- package/dist/prompts/flows/spec-spec.md +51 -0
- package/dist/prompts/flows/spec-tasks.md +32 -0
- package/dist/prompts/flows/todo-execution.md +7 -0
- package/dist/prompts/flows/todo-planning.md +18 -0
- package/dist/prompts/framework/architecture.md +131 -0
- package/dist/prompts/identity/identity.md +2 -0
- package/dist/prompts/loader.d.ts +24 -0
- package/dist/prompts/loader.js +102 -0
- package/dist/prompts/loader.ts +119 -0
- package/dist/prompts/orchestration/summary.md +14 -0
- package/dist/prompts/persona/BOOTSTRAP.md +5 -0
- package/dist/prompts/persona/IDENTITY.md +16 -0
- package/dist/prompts/persona/PartnerMemory.md +5 -0
- package/dist/prompts/persona/PartnerSoul.md +7 -0
- package/dist/prompts/persona/SOUL.md +30 -0
- package/dist/prompts/persona/USER.md +16 -0
- package/dist/prompts/precise/analyze.md +23 -0
- package/dist/prompts/precise/persona.md +18 -0
- package/dist/prompts/safety/coding-standards.md +5 -0
- package/dist/prompts/safety/safety.md +4 -0
- package/dist/prompts/skills/code-review.md +4 -0
- package/dist/prompts/skills/debug.md +7 -0
- package/dist/prompts/skills/framework-reference.md +131 -0
- package/dist/prompts/skills/refactor.md +6 -0
- package/dist/prompts/summary.md +22 -0
- package/dist/prompts/tools/framework-capabilities.md +241 -0
- package/dist/prompts/tools/tool-rules.md +15 -0
- package/dist/provider/anthropic.d.ts +62 -0
- package/dist/provider/anthropic.js +361 -0
- package/dist/provider/catalog.d.ts +58 -0
- package/dist/provider/catalog.js +97 -0
- package/dist/provider/compatible.d.ts +89 -0
- package/dist/provider/compatible.js +328 -0
- package/dist/provider/config.d.ts +23 -0
- package/dist/provider/config.js +158 -0
- package/dist/provider/deepseek.d.ts +25 -0
- package/dist/provider/deepseek.js +37 -0
- package/dist/provider/fallback.d.ts +51 -0
- package/dist/provider/fallback.js +113 -0
- package/dist/provider/gemini.d.ts +27 -0
- package/dist/provider/gemini.js +170 -0
- package/dist/provider/index.d.ts +36 -0
- package/dist/provider/index.js +42 -0
- package/dist/provider/interface.d.ts +62 -0
- package/dist/provider/interface.js +2 -0
- package/dist/provider/local-config.d.ts +37 -0
- package/dist/provider/local-config.js +146 -0
- package/dist/provider/local.d.ts +45 -0
- package/dist/provider/local.js +248 -0
- package/dist/provider/manager.d.ts +67 -0
- package/dist/provider/manager.js +355 -0
- package/dist/provider/mimo.d.ts +19 -0
- package/dist/provider/mimo.js +30 -0
- package/dist/provider/minimax.d.ts +22 -0
- package/dist/provider/minimax.js +33 -0
- package/dist/provider/model-catalog-loader.d.ts +42 -0
- package/dist/provider/model-catalog-loader.js +89 -0
- package/dist/provider/model-channel-registry.d.ts +130 -0
- package/dist/provider/model-channel-registry.js +488 -0
- package/dist/provider/model-router.d.ts +60 -0
- package/dist/provider/model-router.js +87 -0
- package/dist/provider/openai.d.ts +54 -0
- package/dist/provider/openai.js +274 -0
- package/dist/provider/qwen.d.ts +19 -0
- package/dist/provider/qwen.js +30 -0
- package/dist/provider/resilient.d.ts +46 -0
- package/dist/provider/resilient.js +181 -0
- package/dist/provider/retry.d.ts +16 -0
- package/dist/provider/retry.js +78 -0
- package/dist/provider/router.d.ts +68 -0
- package/dist/provider/router.js +145 -0
- package/dist/provider/tool-args-recovery.d.ts +36 -0
- package/dist/provider/tool-args-recovery.js +91 -0
- package/dist/provider/user-id.d.ts +13 -0
- package/dist/provider/user-id.js +53 -0
- package/dist/provider/zhipu.d.ts +19 -0
- package/dist/provider/zhipu.js +32 -0
- package/dist/registry/agent.registry.d.ts +57 -0
- package/dist/registry/agent.registry.js +224 -0
- package/dist/registry/base.d.ts +54 -0
- package/dist/registry/base.js +99 -0
- package/dist/registry/channel.registry.d.ts +53 -0
- package/dist/registry/channel.registry.js +87 -0
- package/dist/registry/index.d.ts +9 -0
- package/dist/registry/index.js +10 -0
- package/dist/registry/mcp.registry.d.ts +2 -0
- package/dist/registry/mcp.registry.js +2 -0
- package/dist/registry/plugin.registry.d.ts +33 -0
- package/dist/registry/plugin.registry.js +66 -0
- package/dist/registry/provider.registry.d.ts +51 -0
- package/dist/registry/provider.registry.js +104 -0
- package/dist/registry/skill.registry.d.ts +33 -0
- package/dist/registry/skill.registry.js +95 -0
- package/dist/registry/tool.registry.d.ts +68 -0
- package/dist/registry/tool.registry.js +185 -0
- package/dist/repair/loop-guard.d.ts +98 -0
- package/dist/repair/loop-guard.js +217 -0
- package/dist/repair/scavenge.d.ts +19 -0
- package/dist/repair/scavenge.js +81 -0
- package/dist/repair/storm.d.ts +7 -0
- package/dist/repair/storm.js +7 -0
- package/dist/rollback/index.d.ts +5 -0
- package/dist/rollback/index.js +4 -0
- package/dist/rollback/rollback-tool.d.ts +13 -0
- package/dist/rollback/rollback-tool.js +167 -0
- package/dist/rollback/turn-recorder.d.ts +37 -0
- package/dist/rollback/turn-recorder.js +183 -0
- package/dist/rollback/turn-store.d.ts +29 -0
- package/dist/rollback/turn-store.js +114 -0
- package/dist/rollback/types.d.ts +51 -0
- package/dist/rollback/types.js +9 -0
- package/dist/runtime/config-center.d.ts +92 -0
- package/dist/runtime/config-center.js +467 -0
- package/dist/runtime/config-schema.d.ts +201 -0
- package/dist/runtime/config-schema.js +2 -0
- package/dist/runtime/defaults.d.ts +3 -0
- package/dist/runtime/defaults.js +146 -0
- package/dist/schedule/cron.d.ts +24 -0
- package/dist/schedule/cron.js +145 -0
- package/dist/schedule/index.d.ts +6 -0
- package/dist/schedule/index.js +4 -0
- package/dist/schedule/persistence.d.ts +33 -0
- package/dist/schedule/persistence.js +106 -0
- package/dist/schedule/scheduler.d.ts +85 -0
- package/dist/schedule/scheduler.js +630 -0
- package/dist/schedule/types.d.ts +185 -0
- package/dist/schedule/types.js +2 -0
- package/dist/setup/config.d.ts +189 -0
- package/dist/setup/config.js +253 -0
- package/dist/setup/index.d.ts +3 -0
- package/dist/setup/index.js +3 -0
- package/dist/setup/model-defaults.d.ts +13 -0
- package/dist/setup/model-defaults.js +87 -0
- package/dist/setup/persona-bootstrap.d.ts +42 -0
- package/dist/setup/persona-bootstrap.js +379 -0
- package/dist/setup/wizard.d.ts +32 -0
- package/dist/setup/wizard.js +384 -0
- package/dist/skills/index.d.ts +4 -0
- package/dist/skills/index.js +4 -0
- package/dist/skills/loader.d.ts +12 -0
- package/dist/skills/loader.js +83 -0
- package/dist/skills/registry.d.ts +2 -0
- package/dist/skills/registry.js +2 -0
- package/dist/skills/skill-tool.d.ts +20 -0
- package/dist/skills/skill-tool.js +49 -0
- package/dist/tools/archive.d.ts +17 -0
- package/dist/tools/archive.js +131 -0
- package/dist/tools/ask-user.d.ts +9 -0
- package/dist/tools/ask-user.js +62 -0
- package/dist/tools/background-registry.d.ts +45 -0
- package/dist/tools/background-registry.js +164 -0
- package/dist/tools/bash.d.ts +56 -0
- package/dist/tools/bash.js +391 -0
- package/dist/tools/bundle-registry.d.ts +42 -0
- package/dist/tools/bundle-registry.js +239 -0
- package/dist/tools/bundle-tools.d.ts +11 -0
- package/dist/tools/bundle-tools.js +168 -0
- package/dist/tools/channel-info.d.ts +5 -0
- package/dist/tools/channel-info.js +38 -0
- package/dist/tools/compression.d.ts +7 -0
- package/dist/tools/compression.js +21 -0
- package/dist/tools/config.d.ts +23 -0
- package/dist/tools/config.js +255 -0
- package/dist/tools/db-query.d.ts +12 -0
- package/dist/tools/db-query.js +107 -0
- package/dist/tools/diagnostics.d.ts +30 -0
- package/dist/tools/diagnostics.js +111 -0
- package/dist/tools/diff-channel.d.ts +9 -0
- package/dist/tools/diff-channel.js +17 -0
- package/dist/tools/diff-files.d.ts +17 -0
- package/dist/tools/diff-files.js +88 -0
- package/dist/tools/disk-usage.d.ts +34 -0
- package/dist/tools/disk-usage.js +258 -0
- package/dist/tools/edit.d.ts +30 -0
- package/dist/tools/edit.js +235 -0
- package/dist/tools/executor.d.ts +32 -0
- package/dist/tools/executor.js +66 -0
- package/dist/tools/file-tracker.d.ts +20 -0
- package/dist/tools/file-tracker.js +44 -0
- package/dist/tools/filtered-registry.d.ts +29 -0
- package/dist/tools/filtered-registry.js +50 -0
- package/dist/tools/flow.d.ts +10 -0
- package/dist/tools/flow.js +180 -0
- package/dist/tools/git-tool.d.ts +24 -0
- package/dist/tools/git-tool.js +113 -0
- package/dist/tools/glob.d.ts +29 -0
- package/dist/tools/glob.js +152 -0
- package/dist/tools/grep.d.ts +14 -0
- package/dist/tools/grep.js +325 -0
- package/dist/tools/http-request.d.ts +14 -0
- package/dist/tools/http-request.js +126 -0
- package/dist/tools/index.d.ts +43 -0
- package/dist/tools/index.js +76 -0
- package/dist/tools/injection-filter.d.ts +6 -0
- package/dist/tools/injection-filter.js +71 -0
- package/dist/tools/insert.d.ts +13 -0
- package/dist/tools/insert.js +61 -0
- package/dist/tools/interface.d.ts +30 -0
- package/dist/tools/interface.js +2 -0
- package/dist/tools/json-edit.d.ts +15 -0
- package/dist/tools/json-edit.js +122 -0
- package/dist/tools/multi-edit.d.ts +9 -0
- package/dist/tools/multi-edit.js +146 -0
- package/dist/tools/path-sandbox.d.ts +14 -0
- package/dist/tools/path-sandbox.js +60 -0
- package/dist/tools/process-tools.d.ts +15 -0
- package/dist/tools/process-tools.js +95 -0
- package/dist/tools/python-bridge/bridge.d.ts +24 -0
- package/dist/tools/python-bridge/bridge.js +39 -0
- package/dist/tools/python-bridge/executor.d.ts +6 -0
- package/dist/tools/python-bridge/executor.js +69 -0
- package/dist/tools/python-bridge/index.d.ts +5 -0
- package/dist/tools/python-bridge/index.js +4 -0
- package/dist/tools/python-bridge/parser.d.ts +21 -0
- package/dist/tools/python-bridge/parser.js +258 -0
- package/dist/tools/read.d.ts +16 -0
- package/dist/tools/read.js +276 -0
- package/dist/tools/registry.d.ts +2 -0
- package/dist/tools/registry.js +2 -0
- package/dist/tools/restart.d.ts +10 -0
- package/dist/tools/restart.js +46 -0
- package/dist/tools/result-buffer.d.ts +66 -0
- package/dist/tools/result-buffer.js +146 -0
- package/dist/tools/runtime-control.d.ts +163 -0
- package/dist/tools/runtime-control.js +1780 -0
- package/dist/tools/symbol-references.d.ts +5 -0
- package/dist/tools/symbol-references.js +331 -0
- package/dist/tools/system-info.d.ts +3 -0
- package/dist/tools/system-info.js +55 -0
- package/dist/tools/write.d.ts +18 -0
- package/dist/tools/write.js +118 -0
- package/dist/tools/xref/index.d.ts +25 -0
- package/dist/tools/xref/index.js +25 -0
- package/dist/tools/xref/manager.d.ts +85 -0
- package/dist/tools/xref/manager.js +948 -0
- package/dist/tools/xref/parser.d.ts +34 -0
- package/dist/tools/xref/parser.js +64 -0
- package/dist/tools/xref/regex-parser.d.ts +26 -0
- package/dist/tools/xref/regex-parser.js +446 -0
- package/dist/tools/xref/schema.d.ts +104 -0
- package/dist/tools/xref/schema.js +68 -0
- package/dist/tools/xref/ts-parser.d.ts +20 -0
- package/dist/tools/xref/ts-parser.js +403 -0
- package/dist/tools/xref/xref-build.d.ts +20 -0
- package/dist/tools/xref/xref-build.js +128 -0
- package/dist/tools/xref/xref-graph.d.ts +24 -0
- package/dist/tools/xref/xref-graph.js +88 -0
- package/dist/tools/xref/xref-query.d.ts +30 -0
- package/dist/tools/xref/xref-query.js +123 -0
- package/dist/types.d.ts +217 -0
- package/dist/types.js +2 -0
- package/dist/ui/assistant-message.d.ts +7 -0
- package/dist/ui/assistant-message.js +18 -0
- package/dist/ui/chat-log.d.ts +52 -0
- package/dist/ui/chat-log.js +246 -0
- package/dist/ui/command-registry.d.ts +73 -0
- package/dist/ui/command-registry.js +838 -0
- package/dist/ui/diff-component.d.ts +14 -0
- package/dist/ui/diff-component.js +42 -0
- package/dist/ui/hyperlink-markdown.d.ts +10 -0
- package/dist/ui/hyperlink-markdown.js +21 -0
- package/dist/ui/markdown-message.d.ts +10 -0
- package/dist/ui/markdown-message.js +16 -0
- package/dist/ui/osc8-hyperlinks.d.ts +4 -0
- package/dist/ui/osc8-hyperlinks.js +142 -0
- package/dist/ui/pi-tui-editor.d.ts +35 -0
- package/dist/ui/pi-tui-editor.js +79 -0
- package/dist/ui/slash-commands.d.ts +16 -0
- package/dist/ui/slash-commands.js +29 -0
- package/dist/ui/slash-panel.d.ts +54 -0
- package/dist/ui/slash-panel.js +199 -0
- package/dist/ui/theme.d.ts +120 -0
- package/dist/ui/theme.js +154 -0
- package/dist/ui/tool-display.d.ts +13 -0
- package/dist/ui/tool-display.js +85 -0
- package/dist/ui/tool-execution.d.ts +22 -0
- package/dist/ui/tool-execution.js +87 -0
- package/dist/ui/tui-formatters.d.ts +2 -0
- package/dist/ui/tui-formatters.js +145 -0
- package/dist/ui/user-message.d.ts +5 -0
- package/dist/ui/user-message.js +11 -0
- package/dist/update/check.d.ts +8 -0
- package/dist/update/check.js +28 -0
- package/dist/update/config.d.ts +6 -0
- package/dist/update/config.js +18 -0
- package/dist/update/download.d.ts +3 -0
- package/dist/update/download.js +27 -0
- package/dist/update/index.d.ts +6 -0
- package/dist/update/index.js +5 -0
- package/dist/update/install.d.ts +3 -0
- package/dist/update/install.js +33 -0
- package/dist/update/types.d.ts +16 -0
- package/dist/update/types.js +2 -0
- package/dist/utils/diff.d.ts +9 -0
- package/dist/utils/diff.js +45 -0
- package/dist/utils/misc.d.ts +12 -0
- package/dist/utils/misc.js +34 -0
- package/dist/world-engine/agent.d.ts +41 -0
- package/dist/world-engine/agent.js +385 -0
- package/dist/world-engine/index.d.ts +45 -0
- package/dist/world-engine/index.js +160 -0
- package/dist/world-engine/sim.d.ts +14 -0
- package/dist/world-engine/sim.js +120 -0
- package/dist/world-engine/store.d.ts +110 -0
- package/dist/world-engine/store.js +519 -0
- package/dist/world-engine/ticker.d.ts +37 -0
- package/dist/world-engine/ticker.js +214 -0
- package/dist/world-engine/tools.d.ts +6 -0
- package/dist/world-engine/tools.js +380 -0
- package/dist/world-engine/types.d.ts +202 -0
- package/dist/world-engine/types.js +56 -0
- package/docs/api.md +180 -0
- package/docs/architecture.md +206 -0
- package/docs/diagnostics/2026-06-08-config-prompt-guidance.md +67 -0
- package/docs/superpowers/plans/2026-06-08-global-bootstrap-mode.md +89 -0
- package/docs/superpowers/specs/2026-06-08-global-bootstrap-mode-design.md +174 -0
- package/docs/user-guide.md +226 -0
- package/package.json +63 -0
- package/plugins/example-greeter/index.js +55 -0
- package/plugins/example-greeter/plugin.json +8 -0
- package/pnpm-workspace.yaml +5 -0
- package/provider_arch.md +179 -0
- package/start-webui.sh +27 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/webui-channel-design.md +789 -0
|
@@ -0,0 +1,909 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 差分压缩引擎 — Agent 框架的上下文压缩模块
|
|
3
|
+
*
|
|
4
|
+
* Phase 1: ToolOutputTrimmer — 无 LLM 调用的轻量压缩(规则裁剪)
|
|
5
|
+
* Phase 2: StructuredSummarizer — 首次结构化摘要(LLM 调用)
|
|
6
|
+
* Phase 3: StructuredSummarizer — 增量摘要更新(LLM 调用)
|
|
7
|
+
* Phase 4: 保护区规则裁剪 — 最终兜底(无 LLM 调用)
|
|
8
|
+
*
|
|
9
|
+
* 多轮迭代压缩:每轮按 token 比例动态分层,LLM 压缩最旧部分,
|
|
10
|
+
* 检查收敛后决定是否继续下一轮,最多 3 轮。
|
|
11
|
+
*/
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import fs from 'node:fs/promises';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import os from 'node:os';
|
|
16
|
+
import { loadPrompt } from '../prompts/loader.js';
|
|
17
|
+
// ─── 工具函数 ────────────────────────────────────────────────────────
|
|
18
|
+
/** 判断 MessageContent 是否为 ToolResultContent */
|
|
19
|
+
function isToolResult(content) {
|
|
20
|
+
return content.type === 'tool_result';
|
|
21
|
+
}
|
|
22
|
+
/** 判断 MessageContent 是否为 ToolUseContent */
|
|
23
|
+
function isToolUse(content) {
|
|
24
|
+
return content.type === 'tool_use';
|
|
25
|
+
}
|
|
26
|
+
/** 判断 MessageContent 是否为 TextContent */
|
|
27
|
+
function isText(content) {
|
|
28
|
+
return content.type === 'text';
|
|
29
|
+
}
|
|
30
|
+
/** 计算 MD5 哈希 */
|
|
31
|
+
function md5(input) {
|
|
32
|
+
return createHash('md5').update(input).digest('hex');
|
|
33
|
+
}
|
|
34
|
+
/** 将 Message.content 统一为数组形式 */
|
|
35
|
+
function contentToArray(content) {
|
|
36
|
+
return Array.isArray(content) ? content : [content];
|
|
37
|
+
}
|
|
38
|
+
/** 将 MessageContent 数组还原为原始形式(单元素时解包) */
|
|
39
|
+
function contentFromArray(items) {
|
|
40
|
+
return items.length === 1 ? items[0] : items;
|
|
41
|
+
}
|
|
42
|
+
/** 将文本截断到指定长度,超出部分用 ... 表示 */
|
|
43
|
+
function truncateText(text, maxLen) {
|
|
44
|
+
if (text.length <= maxLen)
|
|
45
|
+
return text;
|
|
46
|
+
return text.slice(0, maxLen - 3) + '...';
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 从消息列表中提取涉及的文件路径。
|
|
50
|
+
* 匹配模式:file_path、file、绝对路径(/ 或 \ 分隔)、.ts/.js/.py 等扩展名。
|
|
51
|
+
*/
|
|
52
|
+
function extractFilePathsFromMessages(messages) {
|
|
53
|
+
const paths = new Set();
|
|
54
|
+
const filePathRegex = /(?:file_path|file|path)\s*[:=]\s*["']?([^\s"',;}\]]+)/gi;
|
|
55
|
+
const absolutePathRegex = /(?:^|\s)([a-zA-Z]:[\\/][^\s"',;}\]]+|(?:\/[^\s"',;}\]]+)+)/gm;
|
|
56
|
+
const extPathRegex = /([^\s"',;}\]]+\.(?:ts|js|py|go|rs|java|cpp|c|h|hpp|css|html|json|yaml|yml|md|txt))/gi;
|
|
57
|
+
for (const msg of messages) {
|
|
58
|
+
const items = contentToArray(msg.content);
|
|
59
|
+
for (const item of items) {
|
|
60
|
+
let text = '';
|
|
61
|
+
if (isText(item)) {
|
|
62
|
+
text = item.text;
|
|
63
|
+
}
|
|
64
|
+
else if (isToolResult(item)) {
|
|
65
|
+
text = typeof item.content === 'string' ? item.content : JSON.stringify(item.content);
|
|
66
|
+
}
|
|
67
|
+
else if (isToolUse(item)) {
|
|
68
|
+
text = JSON.stringify(item.input);
|
|
69
|
+
}
|
|
70
|
+
for (const regex of [filePathRegex, absolutePathRegex, extPathRegex]) {
|
|
71
|
+
let m;
|
|
72
|
+
regex.lastIndex = 0;
|
|
73
|
+
while ((m = regex.exec(text)) !== null) {
|
|
74
|
+
const p = (m[1] || m[0]).trim();
|
|
75
|
+
if (p.length > 0)
|
|
76
|
+
paths.add(p);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return [...paths];
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* 通过 git log 判断文件是否为高频修改文件(hot)。
|
|
85
|
+
* 对每个文件调用 git log,如果最近 10 条 commit 中 >= 3 条涉及该文件,则视为 "hot"。
|
|
86
|
+
* 失败时返回空 Set。
|
|
87
|
+
*/
|
|
88
|
+
async function getGitHotness(filePaths, gitManager) {
|
|
89
|
+
const hotFiles = new Set();
|
|
90
|
+
for (const file of filePaths) {
|
|
91
|
+
try {
|
|
92
|
+
const commits = await gitManager.logFile(file, 10);
|
|
93
|
+
if (commits.length >= 3) {
|
|
94
|
+
hotFiles.add(file);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// 单个文件查询失败不影响整体,跳过
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return hotFiles;
|
|
102
|
+
}
|
|
103
|
+
// ─── ToolOutputTrimmer ───────────────────────────────────────────────
|
|
104
|
+
/**
|
|
105
|
+
* Phase 1 压缩器 — 无需 LLM 调用,纯规则驱动的工具输出裁剪。
|
|
106
|
+
*/
|
|
107
|
+
export class ToolOutputTrimmer {
|
|
108
|
+
/** 最近 N 条消息中的工具结果保持原样 */
|
|
109
|
+
trimWindow;
|
|
110
|
+
constructor(trimWindow = 6) {
|
|
111
|
+
this.trimWindow = trimWindow;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 裁剪旧工具结果:不在最近 N 条消息中的 ToolResultContent 替换为一行摘要。
|
|
115
|
+
*/
|
|
116
|
+
trimToolResults(messages) {
|
|
117
|
+
// 收集最近 N 条消息的索引集合
|
|
118
|
+
const recentIndices = new Set();
|
|
119
|
+
const start = Math.max(0, messages.length - this.trimWindow);
|
|
120
|
+
for (let i = start; i < messages.length; i++) {
|
|
121
|
+
recentIndices.add(i);
|
|
122
|
+
}
|
|
123
|
+
return messages.map((msg, idx) => {
|
|
124
|
+
if (msg.role !== 'user')
|
|
125
|
+
return msg;
|
|
126
|
+
if (recentIndices.has(idx))
|
|
127
|
+
return msg;
|
|
128
|
+
const items = contentToArray(msg.content);
|
|
129
|
+
let changed = false;
|
|
130
|
+
const newItems = items.map((item) => {
|
|
131
|
+
if (!isToolResult(item))
|
|
132
|
+
return item;
|
|
133
|
+
changed = true;
|
|
134
|
+
return this.summarizeToolResult(item);
|
|
135
|
+
});
|
|
136
|
+
if (!changed)
|
|
137
|
+
return msg;
|
|
138
|
+
return { ...msg, content: contentFromArray(newItems) };
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 去重连续的相同工具结果(MD5 匹配),仅保留最新,旧结果替换为引用。
|
|
143
|
+
*/
|
|
144
|
+
deduplicateToolResults(messages) {
|
|
145
|
+
// 先收集所有 ToolResultContent 及其位置,按 tool_use_id 索引
|
|
146
|
+
const resultMap = new Map();
|
|
147
|
+
// 第一遍:记录每个 tool_use_id 对应的最新结果
|
|
148
|
+
for (let mi = 0; mi < messages.length; mi++) {
|
|
149
|
+
const msg = messages[mi];
|
|
150
|
+
if (msg.role !== 'user')
|
|
151
|
+
continue;
|
|
152
|
+
const items = contentToArray(msg.content);
|
|
153
|
+
for (let ci = 0; ci < items.length; ci++) {
|
|
154
|
+
const item = items[ci];
|
|
155
|
+
if (!isToolResult(item))
|
|
156
|
+
continue;
|
|
157
|
+
const hash = md5(item.content);
|
|
158
|
+
resultMap.set(item.tool_use_id, { msgIdx: mi, itemIdx: ci, hash });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// 第二遍:查找重复内容(相同 hash 的不同 tool_use_id)
|
|
162
|
+
const hashToLatestId = new Map();
|
|
163
|
+
// 按出现顺序遍历,后出现的覆盖前面的
|
|
164
|
+
for (const [id, info] of resultMap) {
|
|
165
|
+
hashToLatestId.set(info.hash, id);
|
|
166
|
+
}
|
|
167
|
+
// 第三遍:替换重复
|
|
168
|
+
const result = messages.map((msg, mi) => {
|
|
169
|
+
if (msg.role !== 'user')
|
|
170
|
+
return msg;
|
|
171
|
+
const items = contentToArray(msg.content);
|
|
172
|
+
let changed = false;
|
|
173
|
+
const newItems = items.map((item, ci) => {
|
|
174
|
+
if (!isToolResult(item))
|
|
175
|
+
return item;
|
|
176
|
+
const hash = md5(item.content);
|
|
177
|
+
const latestId = hashToLatestId.get(hash);
|
|
178
|
+
// 如果当前 id 不是最新的(即存在另一个同 hash 的不同 id),则替换
|
|
179
|
+
if (latestId && latestId !== item.tool_use_id) {
|
|
180
|
+
changed = true;
|
|
181
|
+
const replacement = {
|
|
182
|
+
type: 'tool_result',
|
|
183
|
+
tool_use_id: item.tool_use_id,
|
|
184
|
+
content: `[Duplicate] same result as tool_use_id=${latestId}`,
|
|
185
|
+
};
|
|
186
|
+
return replacement;
|
|
187
|
+
}
|
|
188
|
+
return item;
|
|
189
|
+
});
|
|
190
|
+
if (!changed)
|
|
191
|
+
return msg;
|
|
192
|
+
return { ...msg, content: contentFromArray(newItems) };
|
|
193
|
+
});
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 截断过大的 tool_call JSON:当 input 序列化后超过 maxJsonLength 时,
|
|
198
|
+
* 生成智能摘要(提取关键参数)而非一刀切截断字符串。
|
|
199
|
+
*/
|
|
200
|
+
truncateLargeToolCalls(messages, maxJsonLength = 2000) {
|
|
201
|
+
return messages.map((msg) => {
|
|
202
|
+
if (msg.role !== 'assistant')
|
|
203
|
+
return msg;
|
|
204
|
+
const items = contentToArray(msg.content);
|
|
205
|
+
let changed = false;
|
|
206
|
+
const newItems = items.map((item) => {
|
|
207
|
+
if (!isToolUse(item))
|
|
208
|
+
return item;
|
|
209
|
+
const json = JSON.stringify(item.input);
|
|
210
|
+
if (json.length <= maxJsonLength)
|
|
211
|
+
return item;
|
|
212
|
+
changed = true;
|
|
213
|
+
// 智能摘要:提取关键参数
|
|
214
|
+
const summary = this.summarizeToolInput(item.name, item.input);
|
|
215
|
+
const newItem = {
|
|
216
|
+
type: 'tool_use',
|
|
217
|
+
id: item.id,
|
|
218
|
+
name: item.name,
|
|
219
|
+
input: { _summarized: true, summary },
|
|
220
|
+
};
|
|
221
|
+
return newItem;
|
|
222
|
+
});
|
|
223
|
+
if (!changed)
|
|
224
|
+
return msg;
|
|
225
|
+
return { ...msg, content: contentFromArray(newItems) };
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
// ─── 私有方法 ──────────────────────────────────────────────────────
|
|
229
|
+
// 工具结果缓存目录
|
|
230
|
+
static getCacheDir() {
|
|
231
|
+
return path.join(os.homedir(), '.agent', 'cache', 'tool-results');
|
|
232
|
+
}
|
|
233
|
+
/** 将工具结果内容保存到缓存,返回引用 ID */
|
|
234
|
+
saveToCache(label, content) {
|
|
235
|
+
const hash = createHash('md5').update(content).digest('hex').slice(0, 10);
|
|
236
|
+
const cacheDir = ToolOutputTrimmer.getCacheDir();
|
|
237
|
+
const cacheFile = path.join(cacheDir, `${hash}.txt`);
|
|
238
|
+
fs.mkdir(path.dirname(cacheFile), { recursive: true })
|
|
239
|
+
.then(() => fs.writeFile(cacheFile, content, 'utf-8'))
|
|
240
|
+
.catch(() => { }); // 写入失败不阻塞
|
|
241
|
+
return `📦 ${label} → ${hash} (${content.length} chars, 用 read 找回: ${cacheFile})`;
|
|
242
|
+
}
|
|
243
|
+
/** 为 ToolResultContent 生成一行摘要并缓存原始内容 */
|
|
244
|
+
summarizeToolResult(item) {
|
|
245
|
+
const summary = this.generateSummary(item);
|
|
246
|
+
return {
|
|
247
|
+
type: 'tool_result',
|
|
248
|
+
tool_use_id: item.tool_use_id,
|
|
249
|
+
content: summary,
|
|
250
|
+
is_error: item.is_error,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/** 根据工具结果内容推断工具名并生成摘要 + 缓存 */
|
|
254
|
+
generateSummary(item) {
|
|
255
|
+
const content = item.content;
|
|
256
|
+
// Read 工具:输出通常包含行号格式 " 1→content"
|
|
257
|
+
const lineMatch = content.match(/^\s*\d+[→|]/m);
|
|
258
|
+
if (lineMatch) {
|
|
259
|
+
const lineCount = (content.match(/^\s*\d+[→|]/gm) || []).length;
|
|
260
|
+
const filePathMatch = content.match(/(?:^|\n)([^\s\n]+\.\w+)/);
|
|
261
|
+
const filePath = filePathMatch ? filePathMatch[1] : 'unknown';
|
|
262
|
+
return this.saveToCache(`[Read] ${filePath} (${lineCount} lines)`, content);
|
|
263
|
+
}
|
|
264
|
+
// Bash 工具:包含 exit code
|
|
265
|
+
const exitMatch = content.match(/exit\s+code[:\s]+(\d+)/i);
|
|
266
|
+
if (exitMatch) {
|
|
267
|
+
const cmdMatch = content.match(/(?:^|\n)([$>]\s*)([^\n]+)/);
|
|
268
|
+
const cmd = cmdMatch ? cmdMatch[2].substring(0, 50) : 'command';
|
|
269
|
+
return this.saveToCache(`[Bash] ${cmd} → exit ${exitMatch[1]}`, content);
|
|
270
|
+
}
|
|
271
|
+
// Glob 工具:输出是文件路径列表
|
|
272
|
+
const lines = content.split('\n').filter((l) => l.trim().length > 0);
|
|
273
|
+
if (lines.length > 0 && lines.every((l) => l.trim().includes('/') || l.trim().includes('\\') || l.trim().includes('.'))) {
|
|
274
|
+
return this.saveToCache(`[Glob] (${lines.length} matches)`, content);
|
|
275
|
+
}
|
|
276
|
+
// 通用:内容超过 200 字符才缓存,短文直接保留
|
|
277
|
+
if (content.length > 200) {
|
|
278
|
+
return this.saveToCache(`[ToolResult]`, content);
|
|
279
|
+
}
|
|
280
|
+
return `[ToolResult] ${content.slice(0, 200)}`;
|
|
281
|
+
}
|
|
282
|
+
/** 为大型 ToolUse input 生成智能摘要,提取关键参数。
|
|
283
|
+
*
|
|
284
|
+
* Write 工具特殊处理:提取函数/类签名、import、行数
|
|
285
|
+
* Edit 工具特殊处理:提取 old/new_string 首尾行、行数变化
|
|
286
|
+
* 其他工具:提取关键参数名
|
|
287
|
+
*/
|
|
288
|
+
summarizeToolInput(toolName, input) {
|
|
289
|
+
// ── Write 工具语义压缩 ──
|
|
290
|
+
if (toolName === 'write') {
|
|
291
|
+
return this.summarizeWriteInput(input);
|
|
292
|
+
}
|
|
293
|
+
// ── Edit 工具语义压缩 ──
|
|
294
|
+
if (toolName === 'edit') {
|
|
295
|
+
return this.summarizeEditInput(input);
|
|
296
|
+
}
|
|
297
|
+
// ── 其他工具:通用参数提取 ──
|
|
298
|
+
const keys = Object.keys(input).filter((k) => !k.startsWith('_'));
|
|
299
|
+
const parts = keys.map((key) => {
|
|
300
|
+
const val = input[key];
|
|
301
|
+
if (typeof val === 'string') {
|
|
302
|
+
if (val.includes('/') || val.includes('\\')) {
|
|
303
|
+
const segments = val.replace(/\\/g, '/').split('/');
|
|
304
|
+
return `${key}=${segments[segments.length - 1]}`;
|
|
305
|
+
}
|
|
306
|
+
return `${key}="${val.length > 60 ? val.slice(0, 57) + '...' : val}"`;
|
|
307
|
+
}
|
|
308
|
+
if (typeof val === 'object') {
|
|
309
|
+
return `${key}={...}`;
|
|
310
|
+
}
|
|
311
|
+
return `${key}=${val}`;
|
|
312
|
+
});
|
|
313
|
+
return `[${toolName}] ${parts.join(', ')}`;
|
|
314
|
+
}
|
|
315
|
+
/** Write 工具:提取函数/类签名、import、行数 */
|
|
316
|
+
summarizeWriteInput(input) {
|
|
317
|
+
const filePath = String(input.file_path ?? 'unknown');
|
|
318
|
+
const content = String(input.content ?? '');
|
|
319
|
+
const lines = content.split('\n');
|
|
320
|
+
const lineCount = lines.length;
|
|
321
|
+
// 提取函数签名
|
|
322
|
+
const signatures = [];
|
|
323
|
+
const sigRegex = /export\s+(async\s+)?(function|class|const|let|var|interface|type|enum)\s+(\w+)/g;
|
|
324
|
+
let m;
|
|
325
|
+
while ((m = sigRegex.exec(content)) !== null) {
|
|
326
|
+
signatures.push(`${m[3]}(${m[2]})`);
|
|
327
|
+
}
|
|
328
|
+
if (signatures.length === 0) {
|
|
329
|
+
// 回退:查找任何顶层的 function/class 声明
|
|
330
|
+
const fallbackRegex = /^(?:export\s+)?(async\s+)?(function|class)\s+(\w+)/gm;
|
|
331
|
+
let fm;
|
|
332
|
+
while ((fm = fallbackRegex.exec(content)) !== null) {
|
|
333
|
+
signatures.push(fm[3]);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// 提取 import 来源
|
|
337
|
+
const imports = [];
|
|
338
|
+
const importRegex = /import\s+.*?\bfrom\s+['"](.+?)['"]/g;
|
|
339
|
+
let im;
|
|
340
|
+
while ((im = importRegex.exec(content)) !== null) {
|
|
341
|
+
const p = im[1].split('/');
|
|
342
|
+
imports.push(p[p.length - 1]);
|
|
343
|
+
}
|
|
344
|
+
// 提取文件名
|
|
345
|
+
const p = filePath.replace(/\\/g, '/').split('/');
|
|
346
|
+
const fileName = p[p.length - 1];
|
|
347
|
+
const sigStr = signatures.length > 0 ? signatures.join(', ') : '';
|
|
348
|
+
const impStr = imports.length > 0 ? `; imports: ${imports.join(', ')}` : '';
|
|
349
|
+
return `[Write] ${fileName} (+${lineCount} lines)${sigStr ? ': ' + sigStr : ''}${impStr}`;
|
|
350
|
+
}
|
|
351
|
+
/** Edit 工具:提取 old/new_string 首尾行、行数变化 */
|
|
352
|
+
summarizeEditInput(input) {
|
|
353
|
+
const filePath = String(input.file_path ?? 'unknown');
|
|
354
|
+
const oldStr = String(input.old_string ?? '');
|
|
355
|
+
const newStr = String(input.new_string ?? '');
|
|
356
|
+
const oldLines = oldStr.split('\n');
|
|
357
|
+
const newLines = newStr.split('\n');
|
|
358
|
+
const oldFirst = oldLines[0] ? truncateText(oldLines[0], 40) : '';
|
|
359
|
+
const oldLast = oldLines.length > 1 && oldLines[oldLines.length - 1]
|
|
360
|
+
? truncateText(oldLines[oldLines.length - 1], 40) : '';
|
|
361
|
+
const newFirst = newLines[0] ? truncateText(newLines[0], 40) : '';
|
|
362
|
+
const newLast = newLines.length > 1 && newLines[newLines.length - 1]
|
|
363
|
+
? truncateText(newLines[newLines.length - 1], 40) : '';
|
|
364
|
+
const p = filePath.replace(/\\/g, '/').split('/');
|
|
365
|
+
const fileName = p[p.length - 1];
|
|
366
|
+
const oldDesc = oldFirst + (oldLines.length > 1 ? `...${oldLast}` : '');
|
|
367
|
+
const newDesc = newFirst + (newLines.length > 1 ? `...${newLast}` : '');
|
|
368
|
+
return `[Edit] ${fileName}: ${oldDesc} → ${newDesc} (${oldLines.length}→${newLines.length} lines)`;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
// ─── StructuredSummaryTemplate ───────────────────────────────────────
|
|
372
|
+
/**
|
|
373
|
+
* 12 字段结构化摘要模板 — 用于 Phase 2/3 的 LLM 摘要生成。
|
|
374
|
+
*/
|
|
375
|
+
export const STRUCTURED_SUMMARY_TEMPLATE = loadPrompt('summary');
|
|
376
|
+
// ─── StructuredSummarizer ────────────────────────────────────────────
|
|
377
|
+
/**
|
|
378
|
+
* Phase 2-3 压缩器 — 使用 LLM 生成结构化摘要。
|
|
379
|
+
*/
|
|
380
|
+
export class StructuredSummarizer {
|
|
381
|
+
modelRouter;
|
|
382
|
+
compressDepth;
|
|
383
|
+
constructor(modelRouter, options) {
|
|
384
|
+
this.modelRouter = modelRouter;
|
|
385
|
+
this.compressDepth = options?.compressDepth ?? 0.5;
|
|
386
|
+
}
|
|
387
|
+
/** 运行时更新压缩深度 */
|
|
388
|
+
setCompressDepth(depth) {
|
|
389
|
+
this.compressDepth = Math.max(0, Math.min(1, depth));
|
|
390
|
+
}
|
|
391
|
+
/** 根据 compressDepth 生成压缩策略提示词 */
|
|
392
|
+
getDepthInstruction() {
|
|
393
|
+
if (this.compressDepth <= 0.3) {
|
|
394
|
+
return '激进压缩。仅保留任务完成状态和关键决策,丢弃所有实现细节、工具输出和过程描述。';
|
|
395
|
+
}
|
|
396
|
+
if (this.compressDepth <= 0.7) {
|
|
397
|
+
return '平衡压缩。优先保留相关信息和关键步骤,无关内容可适度压缩。';
|
|
398
|
+
}
|
|
399
|
+
return '保守压缩。尽可能保留完整上下文,保留决策过程和重要细节,只去除明显冗余和重复。';
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* 生成结构化摘要。
|
|
403
|
+
*
|
|
404
|
+
* - 无 existingSummary → Phase 2(首次摘要),使用完整模板
|
|
405
|
+
* - 有 existingSummary → Phase 3(增量更新),基于前次摘要增量更新
|
|
406
|
+
*
|
|
407
|
+
* @param messages - 待压缩的历史消息
|
|
408
|
+
* @param existingSummary - 已有的摘要(增量压缩时传入)
|
|
409
|
+
* @param recentContext - 近期对话(不压缩,仅供 LLM 判断相关性,优先保留相关信息)
|
|
410
|
+
*/
|
|
411
|
+
async summarize(messages, existingSummary, recentContext) {
|
|
412
|
+
let prompt;
|
|
413
|
+
const depthInstruction = this.getDepthInstruction();
|
|
414
|
+
// 近期对话作为 LLM 判断相关性的"锚"
|
|
415
|
+
const taskFocus = recentContext && recentContext.length > 0
|
|
416
|
+
? `## 近期对话(仅供参考,不需压缩)\n${this.serializeMessages(recentContext)}\n\n` +
|
|
417
|
+
`---\n\n` +
|
|
418
|
+
`## 需压缩的历史对话\n` +
|
|
419
|
+
`${depthInstruction}\n` +
|
|
420
|
+
`已完成的任务记入"✅ 已完成",实现细节可压缩,但完成状态不能丢。\n\n`
|
|
421
|
+
: '';
|
|
422
|
+
if (existingSummary) {
|
|
423
|
+
// Phase 3: 增量更新 — 已完成的移到 ✅,新内容补充到对应章节
|
|
424
|
+
prompt =
|
|
425
|
+
`前一次摘要:\n${existingSummary}\n\n` +
|
|
426
|
+
taskFocus +
|
|
427
|
+
`增量更新前一次摘要:已完成的任务移到"✅ 已完成",新内容补到对应章节,不要重复已完成的项。\n\n` +
|
|
428
|
+
`对话历史:\n${this.serializeMessages(messages)}\n\n` +
|
|
429
|
+
STRUCTURED_SUMMARY_TEMPLATE;
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
// Phase 2: 首次摘要
|
|
433
|
+
prompt =
|
|
434
|
+
taskFocus +
|
|
435
|
+
`对话历史:\n${this.serializeMessages(messages)}\n\n` +
|
|
436
|
+
STRUCTURED_SUMMARY_TEMPLATE;
|
|
437
|
+
}
|
|
438
|
+
const summaryMessages = [
|
|
439
|
+
{
|
|
440
|
+
role: 'user',
|
|
441
|
+
content: { type: 'text', text: prompt },
|
|
442
|
+
},
|
|
443
|
+
];
|
|
444
|
+
let summaryText = '';
|
|
445
|
+
const provider = this.modelRouter.getProvider('compression');
|
|
446
|
+
const stream = provider.createStream(summaryMessages);
|
|
447
|
+
for await (const event of stream) {
|
|
448
|
+
if (event.type === 'TEXT') {
|
|
449
|
+
summaryText += event.content;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return summaryText;
|
|
453
|
+
}
|
|
454
|
+
/** 将消息序列化为可读文本 */
|
|
455
|
+
serializeMessages(messages) {
|
|
456
|
+
return messages
|
|
457
|
+
.map((msg) => {
|
|
458
|
+
const items = contentToArray(msg.content);
|
|
459
|
+
const body = items
|
|
460
|
+
.map((item) => {
|
|
461
|
+
if (isText(item))
|
|
462
|
+
return item.text;
|
|
463
|
+
if (isToolUse(item))
|
|
464
|
+
return `[ToolUse: ${item.name}] ${JSON.stringify(item.input)}`;
|
|
465
|
+
if (isToolResult(item))
|
|
466
|
+
return `[ToolResult: ${item.tool_use_id}] ${item.content}`;
|
|
467
|
+
if (item.type === 'image') {
|
|
468
|
+
const img = item;
|
|
469
|
+
return `[Image: ${img.source.type === 'base64' ? img.source.media_type : img.source.url}]`;
|
|
470
|
+
}
|
|
471
|
+
if (item.type === 'thinking')
|
|
472
|
+
return `[Thinking: ${item.thinking?.slice(0, 200) ?? ''}]`;
|
|
473
|
+
return '';
|
|
474
|
+
})
|
|
475
|
+
.join('\n');
|
|
476
|
+
return `[${msg.role}]: ${body}`;
|
|
477
|
+
})
|
|
478
|
+
.join('\n\n');
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* 压缩编排器 — 协调所有压缩阶段。
|
|
483
|
+
*/
|
|
484
|
+
export class CompressorOrchestrator {
|
|
485
|
+
tokenizer;
|
|
486
|
+
summarizer;
|
|
487
|
+
maxContextTokens;
|
|
488
|
+
/** ToolOutputTrimmer 的窗口大小(规则裁剪时最近 N 条不处理) */
|
|
489
|
+
trimWindow;
|
|
490
|
+
safetyThreshold;
|
|
491
|
+
targetRatio;
|
|
492
|
+
compressThreshold;
|
|
493
|
+
/** 最大迭代轮数 */
|
|
494
|
+
maxRounds;
|
|
495
|
+
/** 压缩激进程度 0.0~1.0 */
|
|
496
|
+
compressDepth;
|
|
497
|
+
/** 压缩预算基准:当调用方传入 historyBudget 时使用该值,否则回退到 maxContextTokens */
|
|
498
|
+
#historyBudget = null;
|
|
499
|
+
/** 当前压缩摘要内容 */
|
|
500
|
+
_currentSummary;
|
|
501
|
+
constructor(tokenizer, summarizer, maxContextTokens, options) {
|
|
502
|
+
this.tokenizer = tokenizer;
|
|
503
|
+
this.summarizer = summarizer;
|
|
504
|
+
this.maxContextTokens = maxContextTokens;
|
|
505
|
+
this.trimWindow = options?.trimWindow ?? 6;
|
|
506
|
+
this.safetyThreshold = options?.safetyThreshold ?? 0.95;
|
|
507
|
+
this.targetRatio = options?.targetRatio ?? 0.15;
|
|
508
|
+
this.compressThreshold = options?.compressThreshold ?? 0.75;
|
|
509
|
+
this.maxRounds = options?.maxRounds ?? 3;
|
|
510
|
+
this.compressDepth = options?.compressDepth ?? 0.5;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* 执行多轮迭代压缩。
|
|
514
|
+
*
|
|
515
|
+
* 每轮按 token 比例动态分层,LLM 压缩最旧部分(Layer 3),
|
|
516
|
+
* 规则裁剪中间部分(Layer 2,仅第 1 轮),保留最新部分(Layer 1)。
|
|
517
|
+
* 每轮后检查是否收敛,未收敛则缩小保护区继续下一轮,最多 maxRounds 轮。
|
|
518
|
+
*
|
|
519
|
+
* @param protectLast - 保护最后 N 条消息不被压缩(用于 Zone 5 live tail),默认 0
|
|
520
|
+
* @param historyBudget - 压缩预算基准(token 数)。调用方应传入 maxContextTokens
|
|
521
|
+
* 或计算后的 history 可用空间。未传入时回退到 maxContextTokens。
|
|
522
|
+
*/
|
|
523
|
+
async compress(messages, currentSummary, protectLast = 0, historyBudget, options) {
|
|
524
|
+
this.#historyBudget = historyBudget;
|
|
525
|
+
try {
|
|
526
|
+
const phasesUsed = [];
|
|
527
|
+
const inputMsgsLength = messages.length;
|
|
528
|
+
let summary = currentSummary;
|
|
529
|
+
let currentProtectCount = protectLast;
|
|
530
|
+
let liveMessages = [...messages];
|
|
531
|
+
let round = 0;
|
|
532
|
+
while (round < this.maxRounds && liveMessages.length > 0) {
|
|
533
|
+
round++;
|
|
534
|
+
// ── 分离保护区 ──
|
|
535
|
+
let protectedMessages = [];
|
|
536
|
+
let workingSet = liveMessages;
|
|
537
|
+
if (currentProtectCount > 0 && currentProtectCount < workingSet.length) {
|
|
538
|
+
protectedMessages = workingSet.slice(-currentProtectCount);
|
|
539
|
+
workingSet = workingSet.slice(0, -currentProtectCount);
|
|
540
|
+
}
|
|
541
|
+
if (workingSet.length === 0)
|
|
542
|
+
break;
|
|
543
|
+
// ── 动态分层 ──
|
|
544
|
+
// Round 1: 激进策略 — 70% LLM压缩, 20% 规则裁剪, 10% 保留
|
|
545
|
+
// Round 2+: 对半策略 — 剩余内容 50% LLM压缩, 50% 保留(规则层已用完)
|
|
546
|
+
const ratios = round === 1
|
|
547
|
+
? { layer1: 0.10, layer2: 0.20, layer3: 0.70 }
|
|
548
|
+
: { layer1: 0.50, layer2: 0, layer3: 0.50 };
|
|
549
|
+
let { layer3, layer2, layer1 } = this.splitByTokenRatio(workingSet, ratios);
|
|
550
|
+
({ layer3, layer2, layer1 } = this.fixOrphanedToolResults(layer3, layer2, layer1));
|
|
551
|
+
// ── Phase 1: 规则裁剪(仅第 1 轮)──
|
|
552
|
+
if (round === 1 && layer2.length > 0) {
|
|
553
|
+
const trimmer = new ToolOutputTrimmer(this.trimWindow);
|
|
554
|
+
layer2 = trimmer.trimToolResults(layer2);
|
|
555
|
+
layer2 = trimmer.deduplicateToolResults(layer2);
|
|
556
|
+
layer2 = trimmer.truncateLargeToolCalls(layer2);
|
|
557
|
+
phasesUsed.push(1);
|
|
558
|
+
}
|
|
559
|
+
// ── Phase 2/3: LLM 结构化摘要(作用于 Layer 3)──
|
|
560
|
+
if (layer3.length > 0) {
|
|
561
|
+
try {
|
|
562
|
+
const hadSummary = !!summary;
|
|
563
|
+
const recentContext = [...layer2, ...layer1];
|
|
564
|
+
summary = await this.summarizer.summarize(layer3, summary, recentContext);
|
|
565
|
+
this._currentSummary = summary;
|
|
566
|
+
if (!phasesUsed.includes(2) && !phasesUsed.includes(3)) {
|
|
567
|
+
phasesUsed.push(hadSummary ? 3 : 2);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
catch (err) {
|
|
571
|
+
console.warn('[Compressor] LLM summary failed (round %d), keeping previous result:', round, err instanceof Error ? err.message : String(err));
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// ── 重组:Layer 3 已被摘要替代,Layer 2 + Layer 1 + 保护区保留 ──
|
|
575
|
+
liveMessages = [...layer2, ...layer1, ...protectedMessages];
|
|
576
|
+
liveMessages = this.ensureNoConsecutiveUserMessages(liveMessages);
|
|
577
|
+
// ── 收敛检查 ──
|
|
578
|
+
// targetRatio 是理想压缩目标(默认 0.15),达到即停
|
|
579
|
+
// 0.5 是"够用就停"阈值:不必压到理想目标,只要低于 50% 即可接受
|
|
580
|
+
if (this.getUsageRatio(liveMessages) <= this.targetRatio)
|
|
581
|
+
break;
|
|
582
|
+
if (this.getUsageRatio(liveMessages) <= 0.5)
|
|
583
|
+
break;
|
|
584
|
+
// 未收敛 → 缩小保护区,下一轮压缩更多
|
|
585
|
+
currentProtectCount = Math.max(2, Math.floor(currentProtectCount / 2));
|
|
586
|
+
}
|
|
587
|
+
// ── Phase 4: 保护区规则裁剪兜底 ──
|
|
588
|
+
if (this.getUsageRatio(liveMessages) > this.safetyThreshold) {
|
|
589
|
+
const trimmer = new ToolOutputTrimmer(2);
|
|
590
|
+
liveMessages = trimmer.trimToolResults(liveMessages);
|
|
591
|
+
liveMessages = trimmer.deduplicateToolResults(liveMessages);
|
|
592
|
+
liveMessages = trimmer.truncateLargeToolCalls(liveMessages);
|
|
593
|
+
liveMessages = this.ensureNoConsecutiveUserMessages(liveMessages);
|
|
594
|
+
phasesUsed.push(4);
|
|
595
|
+
}
|
|
596
|
+
// ── Phase 5: 全局 tool 消息完整性校验 ──
|
|
597
|
+
// 5a: 孤立 tool_result(tool_result 没有配对的 tool_use)→ 降级为文本
|
|
598
|
+
// 5b: 孤立 tool_calls(assistant 有 tool_calls 但没有对应的 tool_result)→ 移除 tool_calls
|
|
599
|
+
liveMessages = this.fixGlobalOrphanedToolResults(liveMessages);
|
|
600
|
+
liveMessages = this.fixGlobalOrphanedToolCalls(liveMessages);
|
|
601
|
+
const compressedCount = inputMsgsLength - liveMessages.length;
|
|
602
|
+
return { messages: liveMessages, summary, phasesUsed, compressedCount };
|
|
603
|
+
}
|
|
604
|
+
finally {
|
|
605
|
+
this.#historyBudget = null;
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
/** 检查消息是否超过压缩阈值的最大上下文 token */
|
|
609
|
+
needsCompression(messages) {
|
|
610
|
+
return this.getUsageRatio(messages) > this.compressThreshold;
|
|
611
|
+
}
|
|
612
|
+
/** 获取压缩统计信息 */
|
|
613
|
+
getCompressionStats(messages) {
|
|
614
|
+
const totalTokens = this.tokenizer.countMessagesTokens(messages);
|
|
615
|
+
const budget = Math.max(1, this.#historyBudget ?? this.maxContextTokens);
|
|
616
|
+
return {
|
|
617
|
+
totalTokens,
|
|
618
|
+
maxTokens: budget,
|
|
619
|
+
usageRatio: totalTokens / budget,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Update the compression threshold at runtime.
|
|
624
|
+
* This allows config changes to take effect without reconstruction.
|
|
625
|
+
*/
|
|
626
|
+
setCompressThreshold(threshold) {
|
|
627
|
+
this.compressThreshold = threshold;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Update the compression depth at runtime.
|
|
631
|
+
* Delegates to StructuredSummarizer for prompt modulation.
|
|
632
|
+
*/
|
|
633
|
+
setCompressDepth(depth) {
|
|
634
|
+
this.compressDepth = depth;
|
|
635
|
+
this.summarizer.setCompressDepth(depth);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Get current compressDepth.
|
|
639
|
+
*/
|
|
640
|
+
getCompressDepth() {
|
|
641
|
+
return this.compressDepth;
|
|
642
|
+
}
|
|
643
|
+
/** 获取当前压缩摘要 */
|
|
644
|
+
get currentSummary() {
|
|
645
|
+
return this._currentSummary;
|
|
646
|
+
}
|
|
647
|
+
// ─── 私有方法 ──────────────────────────────────────────────────────
|
|
648
|
+
/** 获取当前使用比例 */
|
|
649
|
+
getUsageRatio(messages) {
|
|
650
|
+
const tokens = this.tokenizer.countMessagesTokens(messages);
|
|
651
|
+
const budget = Math.max(1, this.#historyBudget ?? this.maxContextTokens);
|
|
652
|
+
return tokens / budget;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* 按 token 比例动态分层:从消息末尾向前累加 token 数,切分为三层。
|
|
656
|
+
*
|
|
657
|
+
* @param ratios - 分层比例 { layer1: 最新保留比例, layer2: 中间规则层比例, layer3: 最旧 LLM 层比例 }
|
|
658
|
+
* 三者之和应为 1.0。layer2 为 0 表示跳过规则裁剪层。
|
|
659
|
+
* @returns { layer3, layer2, layer1 } 从最旧到最新排列
|
|
660
|
+
*/
|
|
661
|
+
splitByTokenRatio(messages, ratios) {
|
|
662
|
+
if (messages.length === 0)
|
|
663
|
+
return { layer3: [], layer2: [], layer1: [] };
|
|
664
|
+
const totalTokens = this.tokenizer.countMessagesTokens(messages);
|
|
665
|
+
// 从末尾向前累加
|
|
666
|
+
let accumulatedTokens = 0;
|
|
667
|
+
let l1Idx = messages.length;
|
|
668
|
+
let l2Idx = messages.length;
|
|
669
|
+
// Layer 1: 最新保留部分
|
|
670
|
+
const l1TargetTokens = totalTokens * ratios.layer1;
|
|
671
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
672
|
+
const msgTokens = this.tokenizer.countMessagesTokens([messages[i]]);
|
|
673
|
+
accumulatedTokens += msgTokens;
|
|
674
|
+
l1Idx = i;
|
|
675
|
+
if (accumulatedTokens >= l1TargetTokens)
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
// Layer 2: 中间规则裁剪层(仅当 ratios.layer2 > 0)
|
|
679
|
+
if (ratios.layer2 > 0) {
|
|
680
|
+
const l2TargetTokens = totalTokens * (ratios.layer1 + ratios.layer2);
|
|
681
|
+
for (let i = l1Idx - 1; i >= 0; i--) {
|
|
682
|
+
const msgTokens = this.tokenizer.countMessagesTokens([messages[i]]);
|
|
683
|
+
accumulatedTokens += msgTokens;
|
|
684
|
+
l2Idx = i;
|
|
685
|
+
if (accumulatedTokens >= l2TargetTokens)
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
l2Idx = l1Idx;
|
|
691
|
+
}
|
|
692
|
+
const layer1 = messages.slice(l1Idx);
|
|
693
|
+
const layer2 = ratios.layer2 > 0 ? messages.slice(l2Idx, l1Idx) : [];
|
|
694
|
+
let layer3 = messages.slice(0, l2Idx);
|
|
695
|
+
// 确保 layer3 至少保留 1 条(若有消息可压缩)
|
|
696
|
+
// 当消息很少但每条很长时,单条消息就可能超过 l1TargetTokens,
|
|
697
|
+
// 导致 l1Idx/l2Idx 跳到 0,layer3 为空,LLM 压缩无法触发。
|
|
698
|
+
// 放宽条件:只要有多于 1 条消息,就应确保至少 1 条进入 layer3。
|
|
699
|
+
if (layer3.length === 0 && messages.length > 1) {
|
|
700
|
+
// 所有消息都太长,无法按比例分层
|
|
701
|
+
// 策略:将最旧的 1 条放入 layer3(LLM 压缩),其余放入 layer1(保留)
|
|
702
|
+
return {
|
|
703
|
+
layer3: messages.slice(0, 1),
|
|
704
|
+
layer2: [],
|
|
705
|
+
layer1: messages.slice(1),
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
return { layer3, layer2, layer1 };
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* 确保消息数组中不存在连续的 user 角色消息。
|
|
712
|
+
*
|
|
713
|
+
* 策略:如果连续两条 user 消息中,后一条含 tool_result,
|
|
714
|
+
* 则将后一条的 tool_result 合并到前一条 user 消息中(保持与 assistant(tool_calls) 的配对),
|
|
715
|
+
* 否则插入空 assistant 消息分隔。
|
|
716
|
+
*/
|
|
717
|
+
ensureNoConsecutiveUserMessages(messages) {
|
|
718
|
+
const result = [];
|
|
719
|
+
for (let i = 0; i < messages.length; i++) {
|
|
720
|
+
const msg = messages[i];
|
|
721
|
+
// 检查是否与前一条都是 user 且需要合并
|
|
722
|
+
if (msg.role === 'user' &&
|
|
723
|
+
result.length > 0 &&
|
|
724
|
+
result[result.length - 1].role === 'user') {
|
|
725
|
+
const prevMsg = result[result.length - 1];
|
|
726
|
+
const curContents = contentToArray(msg.content);
|
|
727
|
+
const hasToolResult = curContents.some(isToolResult);
|
|
728
|
+
if (hasToolResult) {
|
|
729
|
+
// 后一条含 tool_result → 合并到前一条 user 消息中
|
|
730
|
+
// 这样 tool_result 仍紧跟在 assistant(tool_calls) 之后
|
|
731
|
+
const prevContents = contentToArray(prevMsg.content);
|
|
732
|
+
const merged = [...prevContents, ...curContents];
|
|
733
|
+
result[result.length - 1] = { ...prevMsg, content: contentFromArray(merged) };
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
// 后一条不含 tool_result → 插入空 assistant 分隔
|
|
737
|
+
result.push({
|
|
738
|
+
role: 'assistant',
|
|
739
|
+
content: { type: 'text', text: '' },
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
result.push(msg);
|
|
743
|
+
}
|
|
744
|
+
return result;
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* 修复分层后 tool 消息的配对完整性。
|
|
748
|
+
* 如果 assistant 消息含 tool_use 被分到 layer3(将被摘要替代),
|
|
749
|
+
* 而对应的 tool_result 消息被分到 layer2/layer1,则会产生孤立 tool_result,
|
|
750
|
+
* 导致 API 报错 "Messages with role 'tool' must be a response to a preceding message with 'tool_calls'"。
|
|
751
|
+
*
|
|
752
|
+
* 修复策略:将孤立的 tool_result 消息降级为普通 user 文本消息,
|
|
753
|
+
* 保留其内容但移除 tool_result 结构。
|
|
754
|
+
*/
|
|
755
|
+
fixOrphanedToolResults(layer3, layer2, layer1) {
|
|
756
|
+
const collectToolUseIds = (msgs) => {
|
|
757
|
+
const ids = new Set();
|
|
758
|
+
for (const msg of msgs) {
|
|
759
|
+
const contents = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
760
|
+
for (const c of contents) {
|
|
761
|
+
if (isToolUse(c))
|
|
762
|
+
ids.add(c.id);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return ids;
|
|
766
|
+
};
|
|
767
|
+
const l3ToolUseIds = collectToolUseIds(layer3);
|
|
768
|
+
const l2ToolUseIds = collectToolUseIds(layer2);
|
|
769
|
+
const fixLayer = (msgs, higherLayerToolUseIds) => {
|
|
770
|
+
return msgs.map((msg) => {
|
|
771
|
+
if (msg.role !== 'user')
|
|
772
|
+
return msg;
|
|
773
|
+
const contents = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
774
|
+
const hasOrphanResult = contents.some((c) => isToolResult(c) && higherLayerToolUseIds.has(c.tool_use_id));
|
|
775
|
+
if (!hasOrphanResult)
|
|
776
|
+
return msg;
|
|
777
|
+
const fixedContents = [];
|
|
778
|
+
for (const c of contents) {
|
|
779
|
+
if (isToolResult(c) && higherLayerToolUseIds.has(c.tool_use_id)) {
|
|
780
|
+
fixedContents.push({
|
|
781
|
+
type: 'text',
|
|
782
|
+
text: `[ToolResult: ${c.tool_use_id}] ${c.content}`,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
fixedContents.push(c);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
return { ...msg, content: fixedContents };
|
|
790
|
+
});
|
|
791
|
+
};
|
|
792
|
+
return {
|
|
793
|
+
layer3,
|
|
794
|
+
layer2: fixLayer(layer2, l3ToolUseIds),
|
|
795
|
+
layer1: fixLayer(layer1, new Set([...l3ToolUseIds, ...l2ToolUseIds])),
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* 全局 tool_result 完整性校验:确保最终输出中所有 tool_result
|
|
800
|
+
* 都有对应的 tool_use(assistant 消息中的 tool_calls)。
|
|
801
|
+
*
|
|
802
|
+
* 压缩后可能出现以下孤立场景:
|
|
803
|
+
* - layer3 整体被 LLM 摘要替代后,layer2/layer1 中残留的 tool_result
|
|
804
|
+
* 其对应的 tool_use 已随 layer3 一起消失
|
|
805
|
+
* - ensureNoConsecutiveUserMessages 插入空 assistant 后,
|
|
806
|
+
* tool_result 与 tool_use 之间可能被隔开
|
|
807
|
+
* - 多轮压缩迭代中,早期修复可能引入新的孤立
|
|
808
|
+
*
|
|
809
|
+
* 修复策略:将孤立 tool_result 降级为普通 user 文本消息
|
|
810
|
+
*/
|
|
811
|
+
fixGlobalOrphanedToolResults(messages) {
|
|
812
|
+
// 收集所有 tool_use 的 id
|
|
813
|
+
const allToolUseIds = new Set();
|
|
814
|
+
for (const msg of messages) {
|
|
815
|
+
if (msg.role !== 'assistant')
|
|
816
|
+
continue;
|
|
817
|
+
const contents = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
818
|
+
for (const c of contents) {
|
|
819
|
+
if (isToolUse(c))
|
|
820
|
+
allToolUseIds.add(c.id);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
// 检查每条 user 消息中的 tool_result 是否有配对
|
|
824
|
+
let hasOrphan = false;
|
|
825
|
+
const result = messages.map((msg) => {
|
|
826
|
+
if (msg.role !== 'user')
|
|
827
|
+
return msg;
|
|
828
|
+
const contents = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
829
|
+
const hasOrphanResult = contents.some((c) => isToolResult(c) && !allToolUseIds.has(c.tool_use_id));
|
|
830
|
+
if (!hasOrphanResult)
|
|
831
|
+
return msg;
|
|
832
|
+
hasOrphan = true;
|
|
833
|
+
const fixedContents = [];
|
|
834
|
+
for (const c of contents) {
|
|
835
|
+
if (isToolResult(c) && !allToolUseIds.has(c.tool_use_id)) {
|
|
836
|
+
fixedContents.push({
|
|
837
|
+
type: 'text',
|
|
838
|
+
text: `[OrphanedToolResult: ${c.tool_use_id}] ${c.content}`,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
fixedContents.push(c);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return { ...msg, content: fixedContents };
|
|
846
|
+
});
|
|
847
|
+
if (hasOrphan) {
|
|
848
|
+
// 降级后可能产生连续 user 消息,需要重新修复
|
|
849
|
+
return this.ensureNoConsecutiveUserMessages(result);
|
|
850
|
+
}
|
|
851
|
+
return result;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* 全局 tool_calls 完整性校验:确保每个 assistant 消息中的 tool_call
|
|
855
|
+
* 都有对应的 tool_result 消息紧跟其后。
|
|
856
|
+
*
|
|
857
|
+
* 压缩后可能出现的孤立场景:
|
|
858
|
+
* - assistant(tool_calls) 消息保留,但对应的 user(tool_result) 被压缩删掉
|
|
859
|
+
* - ensureNoConsecutiveUserMessages 合并消息时,tool_result 被移到其他位置
|
|
860
|
+
*
|
|
861
|
+
* 修复策略:从 assistant 消息中移除没有配对 tool_result 的 tool_calls。
|
|
862
|
+
* 如果移除后 assistant 消息只剩 tool_calls(无文本),则整体转为文本消息。
|
|
863
|
+
*/
|
|
864
|
+
fixGlobalOrphanedToolCalls(messages) {
|
|
865
|
+
// 收集所有 tool_result 的 tool_use_id
|
|
866
|
+
const allToolResultIds = new Set();
|
|
867
|
+
for (const msg of messages) {
|
|
868
|
+
if (msg.role !== 'user')
|
|
869
|
+
continue;
|
|
870
|
+
const contents = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
871
|
+
for (const c of contents) {
|
|
872
|
+
if (isToolResult(c))
|
|
873
|
+
allToolResultIds.add(c.tool_use_id);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
let hasOrphan = false;
|
|
877
|
+
const result = messages.map((msg) => {
|
|
878
|
+
if (msg.role !== 'assistant')
|
|
879
|
+
return msg;
|
|
880
|
+
const contents = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
881
|
+
const toolUseBlocks = contents.filter(isToolUse);
|
|
882
|
+
if (toolUseBlocks.length === 0)
|
|
883
|
+
return msg;
|
|
884
|
+
// 找出没有配对 tool_result 的 tool_calls
|
|
885
|
+
const orphanToolUseIds = toolUseBlocks
|
|
886
|
+
.filter((b) => !allToolResultIds.has(b.id))
|
|
887
|
+
.map((b) => b.id);
|
|
888
|
+
if (orphanToolUseIds.length === 0)
|
|
889
|
+
return msg;
|
|
890
|
+
hasOrphan = true;
|
|
891
|
+
const orphanSet = new Set(orphanToolUseIds);
|
|
892
|
+
const remaining = contents.filter((c) => !(isToolUse(c) && orphanSet.has(c.id)));
|
|
893
|
+
if (remaining.length === 0) {
|
|
894
|
+
// assistant 消息只剩孤立的 tool_calls,无文本 → 转为文本消息
|
|
895
|
+
return {
|
|
896
|
+
...msg,
|
|
897
|
+
content: [{
|
|
898
|
+
type: 'text',
|
|
899
|
+
text: `[Assistant called tools: ${orphanToolUseIds.join(', ')} — results removed during compression]`,
|
|
900
|
+
}],
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
// 移除孤立的 tool_calls,保留其他内容
|
|
904
|
+
return { ...msg, content: remaining };
|
|
905
|
+
});
|
|
906
|
+
return result;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
//# sourceMappingURL=compressor.js.map
|