@theowlops/boombot 0.0.0-stage → 1.1.1
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/README.md +69 -3
- package/dist/LICENSE +21 -0
- package/dist/bin/boombot.js +16 -0
- package/dist/cli/api-key.js +123 -0
- package/dist/cli/bin-bun.js +17 -0
- package/dist/cli/bin.js +34 -0
- package/dist/cli/cli/cli-error.js +61 -0
- package/dist/cli/cli/dispatch.js +138 -0
- package/dist/cli/cli/help.js +83 -0
- package/dist/cli/cli/parser.js +89 -0
- package/dist/cli/cli/registry.js +185 -0
- package/dist/cli/cli/spec.js +41 -0
- package/dist/cli/cli/suggest.js +125 -0
- package/dist/cli/cli/version.js +30 -0
- package/dist/cli/cli/vocabulary.js +87 -0
- package/dist/cli/commands/doctor.js +365 -0
- package/dist/cli/commands/gateway.js +290 -0
- package/dist/cli/commands/migrate.js +270 -0
- package/dist/cli/commands/plugin.js +97 -0
- package/dist/cli/commands/setup.js +244 -0
- package/dist/cli/config-file.js +133 -0
- package/dist/cli/master-key.js +78 -0
- package/dist/cli/open-browser.js +59 -0
- package/dist/cli/web-assets.js +58 -0
- package/dist/gateway/auth.js +46 -0
- package/dist/gateway/config-format.js +202 -0
- package/dist/gateway/config.js +427 -0
- package/dist/gateway/context.js +1 -0
- package/dist/gateway/env-file.js +129 -0
- package/dist/gateway/events.js +172 -0
- package/dist/gateway/guard/tagging.js +505 -0
- package/dist/gateway/handlers/index.js +66 -0
- package/dist/gateway/handlers/memory.js +54 -0
- package/dist/gateway/handlers/providers.js +73 -0
- package/dist/gateway/handlers/registry.js +39 -0
- package/dist/gateway/handlers/sessions.js +188 -0
- package/dist/gateway/handlers/system.js +12 -0
- package/dist/gateway/handlers/tasks.js +191 -0
- package/dist/gateway/handlers/turn.js +100 -0
- package/dist/gateway/index.js +126 -0
- package/dist/gateway/main.js +23 -0
- package/dist/gateway/server.js +354 -0
- package/dist/gateway/services/context-limits.js +75 -0
- package/dist/gateway/services/history-normalizer.js +42 -0
- package/dist/gateway/services/image-pipeline.js +56 -0
- package/dist/gateway/services/key-roulette-client.js +43 -0
- package/dist/gateway/services/llm-client.js +593 -0
- package/dist/gateway/services/llm-decoder.js +200 -0
- package/dist/gateway/services/local-fallback.js +135 -0
- package/dist/gateway/services/memory.js +511 -0
- package/dist/gateway/services/persistent-memory.js +196 -0
- package/dist/gateway/services/providers.js +44 -0
- package/dist/gateway/services/tasks.js +237 -0
- package/dist/gateway/services/verification.js +124 -0
- package/dist/gateway/services/vision-payload.js +48 -0
- package/dist/gateway/services.js +257 -0
- package/dist/gateway/session-snapshot.js +19 -0
- package/dist/gateway/sessions.js +701 -0
- package/dist/gateway/static.js +239 -0
- package/dist/gateway/turns/clarify-tool.js +57 -0
- package/dist/gateway/turns/cronjob-tool.js +96 -0
- package/dist/gateway/turns/deferred-tools.js +154 -0
- package/dist/gateway/turns/execute-code-tool.js +84 -0
- package/dist/gateway/turns/file-tool-defs.js +178 -0
- package/dist/gateway/turns/file-tool-ops.js +178 -0
- package/dist/gateway/turns/file-tool-scan.js +182 -0
- package/dist/gateway/turns/file-tool.js +302 -0
- package/dist/gateway/turns/index.js +416 -0
- package/dist/gateway/turns/jev-middleware.js +91 -0
- package/dist/gateway/turns/local-reply.js +156 -0
- package/dist/gateway/turns/mcp-client.js +643 -0
- package/dist/gateway/turns/memory-tool.js +171 -0
- package/dist/gateway/turns/middleware.js +96 -0
- package/dist/gateway/turns/python-core.js +397 -0
- package/dist/gateway/turns/routing/index.js +3 -0
- package/dist/gateway/turns/routing/intent-classifier.js +60 -0
- package/dist/gateway/turns/routing/planner-adapter.js +91 -0
- package/dist/gateway/turns/routing/types.js +1 -0
- package/dist/gateway/turns/runtime-deps.js +228 -0
- package/dist/gateway/turns/self-config-tool.js +277 -0
- package/dist/gateway/turns/service-types.js +11 -0
- package/dist/gateway/turns/service.js +820 -0
- package/dist/gateway/turns/session-adapter.js +158 -0
- package/dist/gateway/turns/session-tool.js +49 -0
- package/dist/gateway/turns/shell-tool.js +190 -0
- package/dist/gateway/turns/skill-tool.js +239 -0
- package/dist/gateway/turns/spillover.js +201 -0
- package/dist/gateway/turns/subagent-tool.js +140 -0
- package/dist/gateway/turns/system-prompt.js +121 -0
- package/dist/gateway/turns/tool-schema.js +90 -0
- package/dist/gateway/turns/turn-utils.js +58 -0
- package/dist/gateway/turns/vision-tool.js +120 -0
- package/dist/gateway/types.js +1 -0
- package/dist/native/win32-x64/boombot-native-host.exe +0 -0
- package/dist/node_modules/@boombot/channels/handlers.d.ts +9 -0
- package/dist/node_modules/@boombot/channels/handlers.js +109 -0
- package/dist/node_modules/@boombot/channels/index.d.ts +12 -0
- package/dist/node_modules/@boombot/channels/index.js +12 -0
- package/dist/node_modules/@boombot/channels/package.json +9 -0
- package/dist/node_modules/@boombot/channels/service.d.ts +62 -0
- package/dist/node_modules/@boombot/channels/service.js +608 -0
- package/dist/node_modules/@boombot/channels/tool.d.ts +10 -0
- package/dist/node_modules/@boombot/channels/tool.js +46 -0
- package/dist/node_modules/@boombot/channels/transports/common/dedup.d.ts +17 -0
- package/dist/node_modules/@boombot/channels/transports/common/dedup.js +42 -0
- package/dist/node_modules/@boombot/channels/transports/common/handoff.d.ts +19 -0
- package/dist/node_modules/@boombot/channels/transports/common/handoff.js +32 -0
- package/dist/node_modules/@boombot/channels/transports/common/index.d.ts +3 -0
- package/dist/node_modules/@boombot/channels/transports/common/index.js +3 -0
- package/dist/node_modules/@boombot/channels/transports/common/types.d.ts +99 -0
- package/dist/node_modules/@boombot/channels/transports/common/types.js +54 -0
- package/dist/node_modules/@boombot/channels/transports/common.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/common.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/dedup/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/dedup/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/dedup.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/dedup.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/discord/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/discord/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/discord/transport.d.ts +36 -0
- package/dist/node_modules/@boombot/channels/transports/discord/transport.js +199 -0
- package/dist/node_modules/@boombot/channels/transports/discord.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/discord.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/handoff.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/handoff.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/transport.d.ts +34 -0
- package/dist/node_modules/@boombot/channels/transports/messenger/transport.js +180 -0
- package/dist/node_modules/@boombot/channels/transports/messenger.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/messenger.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/slack/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/slack/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/slack/transport.d.ts +35 -0
- package/dist/node_modules/@boombot/channels/transports/slack/transport.js +183 -0
- package/dist/node_modules/@boombot/channels/transports/slack.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/slack.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/streamer.d.ts +31 -0
- package/dist/node_modules/@boombot/channels/transports/streamer/streamer.js +141 -0
- package/dist/node_modules/@boombot/channels/transports/streamer.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/streamer.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/transport.d.ts +75 -0
- package/dist/node_modules/@boombot/channels/transports/telegram/transport.js +395 -0
- package/dist/node_modules/@boombot/channels/transports/telegram.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/telegram.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/transport.d.ts +27 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok/transport.js +97 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/tiktok.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/index.d.ts +1 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/index.js +1 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.d.ts +38 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp/transport.js +189 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/whatsapp.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/index.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/index.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/oa.d.ts +28 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/oa.js +94 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/personal.d.ts +106 -0
- package/dist/node_modules/@boombot/channels/transports/zalo/personal.js +171 -0
- package/dist/node_modules/@boombot/channels/transports/zalo-personal.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo-personal.js +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo.d.ts +2 -0
- package/dist/node_modules/@boombot/channels/transports/zalo.js +2 -0
- package/dist/node_modules/@boombot/core/agent-core/index.d.ts +12 -0
- package/dist/node_modules/@boombot/core/agent-core/index.js +12 -0
- package/dist/node_modules/@boombot/core/agent-core/middleware.d.ts +60 -0
- package/dist/node_modules/@boombot/core/agent-core/middleware.js +152 -0
- package/dist/node_modules/@boombot/core/agent-core/routing.d.ts +128 -0
- package/dist/node_modules/@boombot/core/agent-core/routing.js +437 -0
- package/dist/node_modules/@boombot/core/agent-core/runtime-deps.d.ts +88 -0
- package/dist/node_modules/@boombot/core/agent-core/runtime-deps.js +8 -0
- package/dist/node_modules/@boombot/core/agent-core/tool-errors.d.ts +44 -0
- package/dist/node_modules/@boombot/core/agent-core/tool-errors.js +60 -0
- package/dist/node_modules/@boombot/core/agent-core/turn-loop.d.ts +21 -0
- package/dist/node_modules/@boombot/core/agent-core/turn-loop.js +561 -0
- package/dist/node_modules/@boombot/core/agent-core/types.d.ts +189 -0
- package/dist/node_modules/@boombot/core/agent-core/types.js +7 -0
- package/dist/node_modules/@boombot/core/compaction/compactor.d.ts +19 -0
- package/dist/node_modules/@boombot/core/compaction/compactor.js +127 -0
- package/dist/node_modules/@boombot/core/compaction/estimator.d.ts +10 -0
- package/dist/node_modules/@boombot/core/compaction/estimator.js +48 -0
- package/dist/node_modules/@boombot/core/compaction/index.d.ts +3 -0
- package/dist/node_modules/@boombot/core/compaction/index.js +3 -0
- package/dist/node_modules/@boombot/core/compaction/types.d.ts +31 -0
- package/dist/node_modules/@boombot/core/compaction/types.js +7 -0
- package/dist/node_modules/@boombot/core/index.d.ts +9 -0
- package/dist/node_modules/@boombot/core/index.js +9 -0
- package/dist/node_modules/@boombot/core/jev/index.d.ts +2 -0
- package/dist/node_modules/@boombot/core/jev/index.js +2 -0
- package/dist/node_modules/@boombot/core/jev/jev.d.ts +30 -0
- package/dist/node_modules/@boombot/core/jev/jev.js +164 -0
- package/dist/node_modules/@boombot/core/jev/types.d.ts +22 -0
- package/dist/node_modules/@boombot/core/jev/types.js +1 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/budget.d.ts +63 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/budget.js +94 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.d.ts +152 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/consolidator.js +318 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/index.d.ts +12 -0
- package/dist/node_modules/@boombot/core/memory/consolidation/index.js +12 -0
- package/dist/node_modules/@boombot/core/memory/curation.d.ts +86 -0
- package/dist/node_modules/@boombot/core/memory/curation.js +120 -0
- package/dist/node_modules/@boombot/core/memory/errors.d.ts +12 -0
- package/dist/node_modules/@boombot/core/memory/errors.js +20 -0
- package/dist/node_modules/@boombot/core/memory/hybrid-search.d.ts +29 -0
- package/dist/node_modules/@boombot/core/memory/hybrid-search.js +52 -0
- package/dist/node_modules/@boombot/core/memory/index.d.ts +23 -0
- package/dist/node_modules/@boombot/core/memory/index.js +23 -0
- package/dist/node_modules/@boombot/core/memory/indexer.d.ts +47 -0
- package/dist/node_modules/@boombot/core/memory/indexer.js +138 -0
- package/dist/node_modules/@boombot/core/memory/lifecycle.d.ts +12 -0
- package/dist/node_modules/@boombot/core/memory/lifecycle.js +66 -0
- package/dist/node_modules/@boombot/core/memory/provider.d.ts +84 -0
- package/dist/node_modules/@boombot/core/memory/provider.js +117 -0
- package/dist/node_modules/@boombot/core/memory/recall.d.ts +7 -0
- package/dist/node_modules/@boombot/core/memory/recall.js +111 -0
- package/dist/node_modules/@boombot/core/memory/scope-resolver.d.ts +27 -0
- package/dist/node_modules/@boombot/core/memory/scope-resolver.js +60 -0
- package/dist/node_modules/@boombot/core/memory/search.d.ts +37 -0
- package/dist/node_modules/@boombot/core/memory/search.js +158 -0
- package/dist/node_modules/@boombot/core/memory/store.d.ts +60 -0
- package/dist/node_modules/@boombot/core/memory/store.js +289 -0
- package/dist/node_modules/@boombot/core/memory/types.d.ts +121 -0
- package/dist/node_modules/@boombot/core/memory/types.js +21 -0
- package/dist/node_modules/@boombot/core/memory/vector-index.d.ts +34 -0
- package/dist/node_modules/@boombot/core/memory/vector-index.js +65 -0
- package/dist/node_modules/@boombot/core/package.json +9 -0
- package/dist/node_modules/@boombot/core/reflection/classifier.d.ts +13 -0
- package/dist/node_modules/@boombot/core/reflection/classifier.js +77 -0
- package/dist/node_modules/@boombot/core/reflection/index.d.ts +2 -0
- package/dist/node_modules/@boombot/core/reflection/index.js +2 -0
- package/dist/node_modules/@boombot/core/reflection/types.d.ts +13 -0
- package/dist/node_modules/@boombot/core/reflection/types.js +4 -0
- package/dist/node_modules/@boombot/core/research/config.d.ts +89 -0
- package/dist/node_modules/@boombot/core/research/config.js +206 -0
- package/dist/node_modules/@boombot/core/research/crawl.d.ts +36 -0
- package/dist/node_modules/@boombot/core/research/crawl.js +179 -0
- package/dist/node_modules/@boombot/core/research/factory.d.ts +26 -0
- package/dist/node_modules/@boombot/core/research/factory.js +46 -0
- package/dist/node_modules/@boombot/core/research/http.d.ts +87 -0
- package/dist/node_modules/@boombot/core/research/http.js +250 -0
- package/dist/node_modules/@boombot/core/research/index.d.ts +17 -0
- package/dist/node_modules/@boombot/core/research/index.js +10 -0
- package/dist/node_modules/@boombot/core/research/orchestrator.d.ts +113 -0
- package/dist/node_modules/@boombot/core/research/orchestrator.js +252 -0
- package/dist/node_modules/@boombot/core/research/parse/docx.d.ts +24 -0
- package/dist/node_modules/@boombot/core/research/parse/docx.js +183 -0
- package/dist/node_modules/@boombot/core/research/parse/guards.d.ts +89 -0
- package/dist/node_modules/@boombot/core/research/parse/guards.js +171 -0
- package/dist/node_modules/@boombot/core/research/parse/index.d.ts +68 -0
- package/dist/node_modules/@boombot/core/research/parse/index.js +219 -0
- package/dist/node_modules/@boombot/core/research/parse/pdf.d.ts +38 -0
- package/dist/node_modules/@boombot/core/research/parse/pdf.js +567 -0
- package/dist/node_modules/@boombot/core/research/providers/arxiv.d.ts +9 -0
- package/dist/node_modules/@boombot/core/research/providers/arxiv.js +72 -0
- package/dist/node_modules/@boombot/core/research/providers/brave.d.ts +11 -0
- package/dist/node_modules/@boombot/core/research/providers/brave.js +36 -0
- package/dist/node_modules/@boombot/core/research/providers/duckduckgo.d.ts +9 -0
- package/dist/node_modules/@boombot/core/research/providers/duckduckgo.js +78 -0
- package/dist/node_modules/@boombot/core/research/providers/exa.d.ts +11 -0
- package/dist/node_modules/@boombot/core/research/providers/exa.js +34 -0
- package/dist/node_modules/@boombot/core/research/providers/firecrawl.d.ts +16 -0
- package/dist/node_modules/@boombot/core/research/providers/firecrawl.js +47 -0
- package/dist/node_modules/@boombot/core/research/providers/index.d.ts +17 -0
- package/dist/node_modules/@boombot/core/research/providers/index.js +11 -0
- package/dist/node_modules/@boombot/core/research/providers/jina.d.ts +13 -0
- package/dist/node_modules/@boombot/core/research/providers/jina.js +114 -0
- package/dist/node_modules/@boombot/core/research/providers/openalex.d.ts +14 -0
- package/dist/node_modules/@boombot/core/research/providers/openalex.js +66 -0
- package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.d.ts +9 -0
- package/dist/node_modules/@boombot/core/research/providers/semantic-scholar.js +56 -0
- package/dist/node_modules/@boombot/core/research/providers/tavily.d.ts +11 -0
- package/dist/node_modules/@boombot/core/research/providers/tavily.js +35 -0
- package/dist/node_modules/@boombot/core/research/providers/wikipedia.d.ts +12 -0
- package/dist/node_modules/@boombot/core/research/providers/wikipedia.js +47 -0
- package/dist/node_modules/@boombot/core/research/tools.d.ts +27 -0
- package/dist/node_modules/@boombot/core/research/tools.js +251 -0
- package/dist/node_modules/@boombot/core/research/types.d.ts +133 -0
- package/dist/node_modules/@boombot/core/research/types.js +8 -0
- package/dist/node_modules/@boombot/core/research/wiring.d.ts +25 -0
- package/dist/node_modules/@boombot/core/research/wiring.js +51 -0
- package/dist/node_modules/@boombot/core/scheduler/cron.d.ts +34 -0
- package/dist/node_modules/@boombot/core/scheduler/cron.js +147 -0
- package/dist/node_modules/@boombot/core/scheduler/evaluator.d.ts +42 -0
- package/dist/node_modules/@boombot/core/scheduler/evaluator.js +148 -0
- package/dist/node_modules/@boombot/core/scheduler/index.d.ts +15 -0
- package/dist/node_modules/@boombot/core/scheduler/index.js +15 -0
- package/dist/node_modules/@boombot/core/scheduler/registry.d.ts +81 -0
- package/dist/node_modules/@boombot/core/scheduler/registry.js +263 -0
- package/dist/node_modules/@boombot/core/scheduler/runner.d.ts +59 -0
- package/dist/node_modules/@boombot/core/scheduler/runner.js +257 -0
- package/dist/node_modules/@boombot/core/scheduler/store.d.ts +150 -0
- package/dist/node_modules/@boombot/core/scheduler/store.js +526 -0
- package/dist/node_modules/@boombot/core/scheduler/timezone.d.ts +35 -0
- package/dist/node_modules/@boombot/core/scheduler/timezone.js +96 -0
- package/dist/node_modules/@boombot/core/scheduler/types.d.ts +157 -0
- package/dist/node_modules/@boombot/core/scheduler/types.js +89 -0
- package/dist/node_modules/@boombot/core/task-runner/index.d.ts +7 -0
- package/dist/node_modules/@boombot/core/task-runner/index.js +5 -0
- package/dist/node_modules/@boombot/core/task-runner/planner.d.ts +37 -0
- package/dist/node_modules/@boombot/core/task-runner/planner.js +154 -0
- package/dist/node_modules/@boombot/core/task-runner/runner.d.ts +8 -0
- package/dist/node_modules/@boombot/core/task-runner/runner.js +564 -0
- package/dist/node_modules/@boombot/core/task-runner/store.d.ts +28 -0
- package/dist/node_modules/@boombot/core/task-runner/store.js +126 -0
- package/dist/node_modules/@boombot/core/task-runner/types.d.ts +138 -0
- package/dist/node_modules/@boombot/core/task-runner/types.js +11 -0
- package/dist/node_modules/@boombot/core/workflow/dag.d.ts +16 -0
- package/dist/node_modules/@boombot/core/workflow/dag.js +109 -0
- package/dist/node_modules/@boombot/core/workflow/engine.d.ts +34 -0
- package/dist/node_modules/@boombot/core/workflow/engine.js +304 -0
- package/dist/node_modules/@boombot/core/workflow/index.d.ts +3 -0
- package/dist/node_modules/@boombot/core/workflow/index.js +3 -0
- package/dist/node_modules/@boombot/core/workflow/types.d.ts +98 -0
- package/dist/node_modules/@boombot/core/workflow/types.js +21 -0
- package/dist/node_modules/@boombot/cronjobs/handlers.d.ts +11 -0
- package/dist/node_modules/@boombot/cronjobs/handlers.js +174 -0
- package/dist/node_modules/@boombot/cronjobs/index.d.ts +2 -0
- package/dist/node_modules/@boombot/cronjobs/index.js +2 -0
- package/dist/node_modules/@boombot/cronjobs/package.json +9 -0
- package/dist/node_modules/@boombot/cronjobs/service.d.ts +53 -0
- package/dist/node_modules/@boombot/cronjobs/service.js +200 -0
- package/dist/node_modules/@boombot/plugins/index.d.ts +2 -0
- package/dist/node_modules/@boombot/plugins/index.js +2 -0
- package/dist/node_modules/@boombot/plugins/installer.d.ts +45 -0
- package/dist/node_modules/@boombot/plugins/installer.js +487 -0
- package/dist/node_modules/@boombot/plugins/manager.d.ts +23 -0
- package/dist/node_modules/@boombot/plugins/manager.js +243 -0
- package/dist/node_modules/@boombot/plugins/package.json +9 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.d.ts +125 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-channels.js +76 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.d.ts +220 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-plugins.js +60 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.d.ts +33 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands-scheduler.js +25 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.d.ts +4349 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/commands.js +563 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.d.ts +32 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/envelope.js +61 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.d.ts +13 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/errors.js +16 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/events.d.ts +567 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/events.js +395 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/index.d.ts +20 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/index.js +13 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/types.d.ts +91 -0
- package/dist/node_modules/@boombot/protocol/gateway-protocol/types.js +7 -0
- package/dist/node_modules/@boombot/protocol/index.d.ts +4 -0
- package/dist/node_modules/@boombot/protocol/index.js +3 -0
- package/dist/node_modules/@boombot/protocol/package.json +9 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.d.ts +41 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/channel.js +17 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/index.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/index.js +10 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.d.ts +58 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/manifest.js +116 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.d.ts +25 -0
- package/dist/node_modules/@boombot/protocol/plugin-sdk/plugin.js +1 -0
- package/dist/node_modules/@boombot/protocol/providers/cipher.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/cipher.js +52 -0
- package/dist/node_modules/@boombot/protocol/providers/index.d.ts +19 -0
- package/dist/node_modules/@boombot/protocol/providers/index.js +19 -0
- package/dist/node_modules/@boombot/protocol/providers/key-roulette.d.ts +79 -0
- package/dist/node_modules/@boombot/protocol/providers/key-roulette.js +81 -0
- package/dist/node_modules/@boombot/protocol/providers/probe.d.ts +29 -0
- package/dist/node_modules/@boombot/protocol/providers/probe.js +96 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.d.ts +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/anthropic.js +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/custom.d.ts +22 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/custom.js +222 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/deepseek.js +9 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/google.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/google.js +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/index.d.ts +31 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/index.js +56 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/local.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/local.js +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/mistral.js +9 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.d.ts +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai-compatible.js +10 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/openai.js +11 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/types.d.ts +58 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/types.js +1 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/xai.d.ts +7 -0
- package/dist/node_modules/@boombot/protocol/providers/profiles/xai.js +10 -0
- package/dist/node_modules/@boombot/protocol/providers/request-profile.d.ts +102 -0
- package/dist/node_modules/@boombot/protocol/providers/request-profile.js +118 -0
- package/dist/node_modules/@boombot/protocol/providers/service.d.ts +2 -0
- package/dist/node_modules/@boombot/protocol/providers/service.js +418 -0
- package/dist/node_modules/@boombot/protocol/providers/types.d.ts +141 -0
- package/dist/node_modules/@boombot/protocol/providers/types.js +16 -0
- package/dist/node_modules/@boombot/sandbox/fs-path.d.ts +13 -0
- package/dist/node_modules/@boombot/sandbox/fs-path.js +79 -0
- package/dist/node_modules/@boombot/sandbox/index.d.ts +13 -0
- package/dist/node_modules/@boombot/sandbox/index.js +13 -0
- package/dist/node_modules/@boombot/sandbox/native.d.ts +39 -0
- package/dist/node_modules/@boombot/sandbox/native.js +224 -0
- package/dist/node_modules/@boombot/sandbox/package.json +9 -0
- package/dist/node_modules/@boombot/sandbox/sandbox.d.ts +92 -0
- package/dist/node_modules/@boombot/sandbox/sandbox.js +229 -0
- package/dist/node_modules/@boombot/security/harness-guard/boundary.d.ts +22 -0
- package/dist/node_modules/@boombot/security/harness-guard/boundary.js +105 -0
- package/dist/node_modules/@boombot/security/harness-guard/gates.d.ts +90 -0
- package/dist/node_modules/@boombot/security/harness-guard/gates.js +791 -0
- package/dist/node_modules/@boombot/security/harness-guard/index.d.ts +23 -0
- package/dist/node_modules/@boombot/security/harness-guard/index.js +23 -0
- package/dist/node_modules/@boombot/security/harness-guard/policy.d.ts +20 -0
- package/dist/node_modules/@boombot/security/harness-guard/policy.js +70 -0
- package/dist/node_modules/@boombot/security/harness-guard/sanitizer.d.ts +53 -0
- package/dist/node_modules/@boombot/security/harness-guard/sanitizer.js +145 -0
- package/dist/node_modules/@boombot/security/harness-guard/types.d.ts +71 -0
- package/dist/node_modules/@boombot/security/harness-guard/types.js +10 -0
- package/dist/node_modules/@boombot/security/harness-guard/verifier.d.ts +59 -0
- package/dist/node_modules/@boombot/security/harness-guard/verifier.js +169 -0
- package/dist/node_modules/@boombot/security/index.d.ts +5 -0
- package/dist/node_modules/@boombot/security/index.js +9 -0
- package/dist/node_modules/@boombot/security/package.json +9 -0
- package/dist/node_modules/@boombot/security/policy/approval.d.ts +117 -0
- package/dist/node_modules/@boombot/security/policy/approval.js +223 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.d.ts +10 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/claim-extractor.js +124 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.d.ts +25 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/evidence-ledger.js +70 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/gate.d.ts +38 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/gate.js +61 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.d.ts +12 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/grounding-check.js +234 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/index.d.ts +17 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/index.js +13 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/policy.d.ts +15 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/policy.js +39 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/types.d.ts +46 -0
- package/dist/node_modules/@boombot/security/reasoning-guard/types.js +7 -0
- package/dist/node_modules/@boombot/security/security/approval.d.ts +63 -0
- package/dist/node_modules/@boombot/security/security/approval.js +140 -0
- package/dist/node_modules/@boombot/security/security/audit.d.ts +39 -0
- package/dist/node_modules/@boombot/security/security/audit.js +70 -0
- package/dist/node_modules/@boombot/security/security/index.d.ts +12 -0
- package/dist/node_modules/@boombot/security/security/index.js +12 -0
- package/dist/node_modules/@boombot/security/security/trust.d.ts +79 -0
- package/dist/node_modules/@boombot/security/security/trust.js +87 -0
- package/dist/package.json +5 -0
- package/dist/proto/agent_core.proto +56 -0
- package/dist/python/agent-core-python/pyproject.toml +40 -0
- package/dist/python/agent-core-python/src/boombot_agent/__init__.py +5 -0
- package/dist/python/agent-core-python/src/boombot_agent/generated/__init__.py +0 -0
- package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2.py +54 -0
- package/dist/python/agent-core-python/src/boombot_agent/generated/agent_core_pb2_grpc.py +140 -0
- package/dist/python/agent-core-python/src/boombot_agent/rpc.py +265 -0
- package/dist/python/agent-core-python/src/boombot_agent/turn.py +577 -0
- package/dist/python/agent-core-python/src/boombot_ml/__init__.py +12 -0
- package/dist/python/agent-core-python/src/boombot_ml/embeddings.py +58 -0
- package/dist/python/agent-core-python/src/boombot_ml/training.py +156 -0
- package/dist/python/agent-core-python/uv.lock +2856 -0
- package/dist/web/assets/ChatView-BZrSD-64.js +262 -0
- package/dist/web/assets/ChatView-Cahbjez6.css +1 -0
- package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/web/assets/MemoryView-BJbXqqs7.css +1 -0
- package/dist/web/assets/MemoryView-BfMq_6CD.js +1 -0
- package/dist/web/assets/SchedulesView-B22QmPR-.js +1 -0
- package/dist/web/assets/SchedulesView-C6XK7A8a.css +1 -0
- package/dist/web/assets/SessionsView-C1dqdhxI.css +1 -0
- package/dist/web/assets/SessionsView-DS0-aH2a.js +1 -0
- package/dist/web/assets/TasksView-Dtasg4hd.css +1 -0
- package/dist/web/assets/TasksView-ey0FLsW8.js +2 -0
- package/dist/web/assets/Textarea-DN3Zybl1.js +1 -0
- package/dist/web/assets/Tooltip-Bn2cqFPz.css +1 -0
- package/dist/web/assets/Tooltip-MzV2Gqnc.js +1 -0
- package/dist/web/assets/browser-Bnt73-Ub.js +8 -0
- package/dist/web/assets/index-BL8bi7Yt.css +1 -0
- package/dist/web/assets/index-CuPR73Tn.js +73 -0
- package/dist/web/favicon.svg +4 -0
- package/dist/web/index.html +26 -0
- package/dist/web/logo.jpg +0 -0
- package/dist/web/logo.png +0 -0
- package/package.json +51 -4
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-hop research orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* `research.deep` without an LLM in the loop: hop 1 searches the original
|
|
5
|
+
* query, later hops search follow-up queries derived from the previous hop's
|
|
6
|
+
* top titles (titles are keyword-dense), then the top URLs are extracted.
|
|
7
|
+
* Every hop's results carry their provider id and URL, so the
|
|
8
|
+
* reasoning-guard can attribute claims to sources (`sources`).
|
|
9
|
+
*/
|
|
10
|
+
import type { CrawlOpts, CrawlResult, ExtractOpts, ExtractProvider, ExtractResult, ResearchProvider, SearchOpts, SearchResult } from './types.js';
|
|
11
|
+
/** One citable source for reasoning-guard claim grounding. */
|
|
12
|
+
export interface SourceAttribution {
|
|
13
|
+
readonly url: string;
|
|
14
|
+
readonly title: string;
|
|
15
|
+
/** Provider id that produced this source, e.g. `'arxiv'`. */
|
|
16
|
+
readonly provider: string;
|
|
17
|
+
}
|
|
18
|
+
export interface DeepResearchOptions {
|
|
19
|
+
/** Total search hops (1–3). Default 2. */
|
|
20
|
+
readonly maxHops?: number;
|
|
21
|
+
/** Results per individual query. Default 5. */
|
|
22
|
+
readonly perQueryLimit?: number;
|
|
23
|
+
/** Follow-up queries spawned per hop. Default 3. */
|
|
24
|
+
readonly followUpsPerHop?: number;
|
|
25
|
+
/** URLs to extract after the search hops. Default 3. */
|
|
26
|
+
readonly maxExtracts?: number;
|
|
27
|
+
readonly signal?: AbortSignal;
|
|
28
|
+
}
|
|
29
|
+
export interface DeepResearchResult {
|
|
30
|
+
readonly query: string;
|
|
31
|
+
readonly results: readonly SearchResult[];
|
|
32
|
+
readonly extracts: readonly ExtractResult[];
|
|
33
|
+
/** Every unique source seen across hops, for reasoning-guard grounding. */
|
|
34
|
+
readonly sources: readonly SourceAttribution[];
|
|
35
|
+
readonly hopsCompleted: number;
|
|
36
|
+
readonly followUpQueries: readonly string[];
|
|
37
|
+
}
|
|
38
|
+
export interface OrchestratorDeps {
|
|
39
|
+
/** Resolved search chain (see `resolveSearchProviders`). */
|
|
40
|
+
readonly searchChain: readonly ResearchProvider[];
|
|
41
|
+
/**
|
|
42
|
+
* Engines `academic()` may search. Optional: absent means `searchChain`,
|
|
43
|
+
* which is right for a caller that passes the whole chain in. The gateway
|
|
44
|
+
* passes the wider free pool instead, because its default chain omits the
|
|
45
|
+
* scholarly engines for latency — asking for them by name must still work.
|
|
46
|
+
*/
|
|
47
|
+
readonly academicChain?: readonly ResearchProvider[];
|
|
48
|
+
/** Resolved extract backend (see `resolveExtractProvider`). */
|
|
49
|
+
readonly extractProvider: ExtractProvider;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Run the search chain for one query.
|
|
53
|
+
*
|
|
54
|
+
* Paid providers (contiguous leading segment of the chain) run as a
|
|
55
|
+
* sequential fallback chain — the first non-empty success WINS ALONE: the
|
|
56
|
+
* free fan-out is skipped, because merging it in buys nothing and costs the
|
|
57
|
+
* fan-out's latency on top of an answer that is already better ranked. Free
|
|
58
|
+
* providers run in parallel under one shared budget (`fanOutSignal`) as the
|
|
59
|
+
* fallback path — the way this chain is configured by default.
|
|
60
|
+
*
|
|
61
|
+
* Everything is deduped by URL and clamped to `limit`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function searchChain(query: string, chain: readonly ResearchProvider[], opts?: SearchOpts): Promise<SearchResult[]>;
|
|
64
|
+
/**
|
|
65
|
+
* Search only the scholarly providers in the chain (`arxiv`, `openalex`,
|
|
66
|
+
* `semanticScholar`), in parallel under the same shared budget as the
|
|
67
|
+
* free fan-out. Returns [] when none are present.
|
|
68
|
+
*/
|
|
69
|
+
export declare function academicSearch(query: string, chain: readonly ResearchProvider[], opts?: SearchOpts): Promise<SearchResult[]>;
|
|
70
|
+
export declare class ResearchOrchestrator {
|
|
71
|
+
#private;
|
|
72
|
+
constructor(deps: OrchestratorDeps);
|
|
73
|
+
/** Single-shot search through the chain (used by `research.search`). */
|
|
74
|
+
search(query: string, opts?: SearchOpts): Promise<SearchResult[]>;
|
|
75
|
+
/** Scholarly-only search (used by `research.academic`). */
|
|
76
|
+
academic(query: string, opts?: SearchOpts): Promise<SearchResult[]>;
|
|
77
|
+
/**
|
|
78
|
+
* Extract one URL to markdown (used by `research.extract`).
|
|
79
|
+
*
|
|
80
|
+
* Security choke point: the URL is user-supplied, so it is validated
|
|
81
|
+
* and sanitized (`sanitizeExtractUrl`) BEFORE reaching the extract
|
|
82
|
+
* backend — non-http(s) schemes are rejected and embedded credentials
|
|
83
|
+
* are stripped so they can never leak to a third-party reader.
|
|
84
|
+
*
|
|
85
|
+
* P1-8: URLs that look like documents (`.pdf`/`.docx`) are parsed
|
|
86
|
+
* locally by `research.parse` (zero-dep, quota-guarded) instead of being
|
|
87
|
+
* sent to a third-party reader — the bytes never leave the gateway.
|
|
88
|
+
* `research.deep` routes through here, so deep research gets document
|
|
89
|
+
* parsing for free.
|
|
90
|
+
*
|
|
91
|
+
* `opts.offset` pages through long documents: the provider re-fetches
|
|
92
|
+
* and serves the next window (see `ExtractOpts`).
|
|
93
|
+
*/
|
|
94
|
+
extract(url: string, signal?: AbortSignal, opts?: ExtractOpts): Promise<ExtractResult>;
|
|
95
|
+
/**
|
|
96
|
+
* Multi-page same-site crawl (used by `research.crawl`).
|
|
97
|
+
*
|
|
98
|
+
* BFS over the resolved extract backend; caps are clamped here so the
|
|
99
|
+
* engine's hard limits (10 pages, depth 2) can never be bypassed by a
|
|
100
|
+
* larger tool argument. The start URL passes the same
|
|
101
|
+
* `sanitizeExtractUrl` choke point as `extract()`.
|
|
102
|
+
*/
|
|
103
|
+
crawl(url: string, opts?: CrawlOpts): Promise<CrawlResult>;
|
|
104
|
+
/**
|
|
105
|
+
* Multi-hop deep research (used by `research.deep`).
|
|
106
|
+
*
|
|
107
|
+
* Hop 1 searches the original query; each further hop searches follow-up
|
|
108
|
+
* queries derived from the previous hop's top titles. After the search
|
|
109
|
+
* hops, the top `maxExtracts` unique URLs are extracted. Sources are
|
|
110
|
+
* attributed per provider for reasoning-guard grounding.
|
|
111
|
+
*/
|
|
112
|
+
deep(query: string, opts?: DeepResearchOptions): Promise<DeepResearchResult>;
|
|
113
|
+
}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-hop research orchestrator.
|
|
3
|
+
*
|
|
4
|
+
* `research.deep` without an LLM in the loop: hop 1 searches the original
|
|
5
|
+
* query, later hops search follow-up queries derived from the previous hop's
|
|
6
|
+
* top titles (titles are keyword-dense), then the top URLs are extracted.
|
|
7
|
+
* Every hop's results carry their provider id and URL, so the
|
|
8
|
+
* reasoning-guard can attribute claims to sources (`sources`).
|
|
9
|
+
*/
|
|
10
|
+
import { dedupeByUrl } from './config.js';
|
|
11
|
+
import { CRAWL_HARD_MAX_DEPTH, CRAWL_HARD_MAX_PAGES, crawlViaExtract } from './crawl.js';
|
|
12
|
+
import { sanitizeExtractUrl, isHttpUrl } from './http.js';
|
|
13
|
+
import { detectDocumentKindFromUrl, extractDocument } from './parse/index.js';
|
|
14
|
+
const DEFAULT_MAX_HOPS = 2;
|
|
15
|
+
const HARD_MAX_HOPS = 3;
|
|
16
|
+
const DEFAULT_PER_QUERY_LIMIT = 5;
|
|
17
|
+
const DEFAULT_FOLLOW_UPS = 3;
|
|
18
|
+
const DEFAULT_MAX_EXTRACTS = 3;
|
|
19
|
+
/**
|
|
20
|
+
* Ceiling on one round of parallel provider calls, measured across ALL of
|
|
21
|
+
* them together rather than per provider. Without it the round costs as much
|
|
22
|
+
* as its slowest engine (plus that engine's retry), and this is the call the
|
|
23
|
+
* user sits and waits on. Engines that miss the budget are dropped; whatever
|
|
24
|
+
* finished in time is still returned.
|
|
25
|
+
*/
|
|
26
|
+
// ponytail: a 4s free-engine budget favors responsiveness; raise it if missing sources outweigh latency.
|
|
27
|
+
const SEARCH_FANOUT_BUDGET_MS = 2_500;
|
|
28
|
+
/** The caller's signal plus the fan-out budget, whichever fires first. */
|
|
29
|
+
function fanOutSignal(signal) {
|
|
30
|
+
const budget = AbortSignal.timeout(SEARCH_FANOUT_BUDGET_MS);
|
|
31
|
+
return signal === undefined ? budget : AbortSignal.any([signal, budget]);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run the search chain for one query.
|
|
35
|
+
*
|
|
36
|
+
* Paid providers (contiguous leading segment of the chain) run as a
|
|
37
|
+
* sequential fallback chain — the first non-empty success WINS ALONE: the
|
|
38
|
+
* free fan-out is skipped, because merging it in buys nothing and costs the
|
|
39
|
+
* fan-out's latency on top of an answer that is already better ranked. Free
|
|
40
|
+
* providers run in parallel under one shared budget (`fanOutSignal`) as the
|
|
41
|
+
* fallback path — the way this chain is configured by default.
|
|
42
|
+
*
|
|
43
|
+
* Everything is deduped by URL and clamped to `limit`.
|
|
44
|
+
*/
|
|
45
|
+
export async function searchChain(query, chain, opts = {}) {
|
|
46
|
+
opts.signal?.throwIfAborted();
|
|
47
|
+
const limit = Math.min(Math.max(opts.limit ?? 8, 1), 20);
|
|
48
|
+
const paid = [];
|
|
49
|
+
const free = [];
|
|
50
|
+
let seenFree = false;
|
|
51
|
+
for (const provider of chain) {
|
|
52
|
+
if (provider.kind === 'paid' && !seenFree)
|
|
53
|
+
paid.push(provider);
|
|
54
|
+
else {
|
|
55
|
+
seenFree = true;
|
|
56
|
+
if (provider.kind === 'free')
|
|
57
|
+
free.push(provider);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const provider of paid) {
|
|
61
|
+
try {
|
|
62
|
+
opts.signal?.throwIfAborted();
|
|
63
|
+
const paidResults = await provider.search(query, { limit, signal: opts.signal });
|
|
64
|
+
opts.signal?.throwIfAborted();
|
|
65
|
+
// An empty success is not an answer: fall through to the free
|
|
66
|
+
// fan-out instead of returning nothing at all.
|
|
67
|
+
if (paidResults.length > 0)
|
|
68
|
+
return dedupeByUrl(paidResults).slice(0, limit);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// fall through to the next paid provider
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
opts.signal?.throwIfAborted();
|
|
75
|
+
const enough = new AbortController();
|
|
76
|
+
const signal = AbortSignal.any([fanOutSignal(opts.signal), enough.signal]);
|
|
77
|
+
const freeResults = [];
|
|
78
|
+
await Promise.allSettled(free.map(async (provider) => {
|
|
79
|
+
const results = await provider.search(query, { limit, signal });
|
|
80
|
+
freeResults.push(...results);
|
|
81
|
+
// Do not pay the slowest provider's latency once the requested result count is met.
|
|
82
|
+
if (dedupeByUrl(freeResults).length >= limit)
|
|
83
|
+
enough.abort();
|
|
84
|
+
}));
|
|
85
|
+
opts.signal?.throwIfAborted();
|
|
86
|
+
return dedupeByUrl(freeResults).slice(0, limit);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Search only the scholarly providers in the chain (`arxiv`, `openalex`,
|
|
90
|
+
* `semanticScholar`), in parallel under the same shared budget as the
|
|
91
|
+
* free fan-out. Returns [] when none are present.
|
|
92
|
+
*/
|
|
93
|
+
export async function academicSearch(query, chain, opts = {}) {
|
|
94
|
+
const limit = Math.min(Math.max(opts.limit ?? 8, 1), 20);
|
|
95
|
+
const academicIds = new Set(['arxiv', 'openalex', 'semanticScholar']);
|
|
96
|
+
const academic = chain.filter((p) => p.kind === 'free' && academicIds.has(p.id));
|
|
97
|
+
const settled = await Promise.allSettled(academic.map((provider) => provider.search(query, { limit, signal: fanOutSignal(opts.signal) })));
|
|
98
|
+
const results = [];
|
|
99
|
+
for (const outcome of settled) {
|
|
100
|
+
if (outcome.status === 'fulfilled')
|
|
101
|
+
results.push(...outcome.value);
|
|
102
|
+
}
|
|
103
|
+
return dedupeByUrl(results).slice(0, limit);
|
|
104
|
+
}
|
|
105
|
+
/** Derive follow-up queries from hop results: top titles, trimmed to keywords. */
|
|
106
|
+
function deriveFollowUps(query, results, count) {
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const r of results) {
|
|
109
|
+
if (out.length >= count)
|
|
110
|
+
break;
|
|
111
|
+
if (r.title === '' || r.title === query)
|
|
112
|
+
continue;
|
|
113
|
+
const title = r.title.length > 80 ? r.title.slice(0, 80) : r.title;
|
|
114
|
+
out.push(`${query} — ${title}`);
|
|
115
|
+
}
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
function toAttribution(r) {
|
|
119
|
+
return { url: r.url, title: r.title, provider: r.source };
|
|
120
|
+
}
|
|
121
|
+
export class ResearchOrchestrator {
|
|
122
|
+
#deps;
|
|
123
|
+
constructor(deps) {
|
|
124
|
+
this.#deps = deps;
|
|
125
|
+
}
|
|
126
|
+
/** Single-shot search through the chain (used by `research.search`). */
|
|
127
|
+
search(query, opts = {}) {
|
|
128
|
+
return searchChain(query, this.#deps.searchChain, opts);
|
|
129
|
+
}
|
|
130
|
+
/** Scholarly-only search (used by `research.academic`). */
|
|
131
|
+
academic(query, opts = {}) {
|
|
132
|
+
return academicSearch(query, this.#deps.academicChain ?? this.#deps.searchChain, opts);
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Extract one URL to markdown (used by `research.extract`).
|
|
136
|
+
*
|
|
137
|
+
* Security choke point: the URL is user-supplied, so it is validated
|
|
138
|
+
* and sanitized (`sanitizeExtractUrl`) BEFORE reaching the extract
|
|
139
|
+
* backend — non-http(s) schemes are rejected and embedded credentials
|
|
140
|
+
* are stripped so they can never leak to a third-party reader.
|
|
141
|
+
*
|
|
142
|
+
* P1-8: URLs that look like documents (`.pdf`/`.docx`) are parsed
|
|
143
|
+
* locally by `research.parse` (zero-dep, quota-guarded) instead of being
|
|
144
|
+
* sent to a third-party reader — the bytes never leave the gateway.
|
|
145
|
+
* `research.deep` routes through here, so deep research gets document
|
|
146
|
+
* parsing for free.
|
|
147
|
+
*
|
|
148
|
+
* `opts.offset` pages through long documents: the provider re-fetches
|
|
149
|
+
* and serves the next window (see `ExtractOpts`).
|
|
150
|
+
*/
|
|
151
|
+
extract(url, signal, opts) {
|
|
152
|
+
let safeUrl;
|
|
153
|
+
try {
|
|
154
|
+
safeUrl = sanitizeExtractUrl(url);
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
return Promise.reject(err);
|
|
158
|
+
}
|
|
159
|
+
if (detectDocumentKindFromUrl(safeUrl) !== undefined) {
|
|
160
|
+
return extractDocument(safeUrl, this.#deps.extractProvider, signal, opts);
|
|
161
|
+
}
|
|
162
|
+
return this.#deps.extractProvider.extract(safeUrl, signal, opts);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Multi-page same-site crawl (used by `research.crawl`).
|
|
166
|
+
*
|
|
167
|
+
* BFS over the resolved extract backend; caps are clamped here so the
|
|
168
|
+
* engine's hard limits (10 pages, depth 2) can never be bypassed by a
|
|
169
|
+
* larger tool argument. The start URL passes the same
|
|
170
|
+
* `sanitizeExtractUrl` choke point as `extract()`.
|
|
171
|
+
*/
|
|
172
|
+
crawl(url, opts = {}) {
|
|
173
|
+
const clamped = {
|
|
174
|
+
maxPages: Math.min(Math.max(opts.maxPages ?? 5, 1), CRAWL_HARD_MAX_PAGES),
|
|
175
|
+
depth: Math.min(Math.max(opts.depth ?? 2, 1), CRAWL_HARD_MAX_DEPTH),
|
|
176
|
+
perPageChars: opts.perPageChars,
|
|
177
|
+
signal: opts.signal,
|
|
178
|
+
};
|
|
179
|
+
return crawlViaExtract(url, this.#deps.extractProvider, clamped);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Multi-hop deep research (used by `research.deep`).
|
|
183
|
+
*
|
|
184
|
+
* Hop 1 searches the original query; each further hop searches follow-up
|
|
185
|
+
* queries derived from the previous hop's top titles. After the search
|
|
186
|
+
* hops, the top `maxExtracts` unique URLs are extracted. Sources are
|
|
187
|
+
* attributed per provider for reasoning-guard grounding.
|
|
188
|
+
*/
|
|
189
|
+
async deep(query, opts = {}) {
|
|
190
|
+
const maxHops = Math.min(Math.max(opts.maxHops ?? DEFAULT_MAX_HOPS, 1), HARD_MAX_HOPS);
|
|
191
|
+
const perQueryLimit = Math.min(Math.max(opts.perQueryLimit ?? DEFAULT_PER_QUERY_LIMIT, 1), 10);
|
|
192
|
+
const followUpsPerHop = Math.min(Math.max(opts.followUpsPerHop ?? DEFAULT_FOLLOW_UPS, 1), 5);
|
|
193
|
+
const maxExtracts = Math.min(Math.max(opts.maxExtracts ?? DEFAULT_MAX_EXTRACTS, 0), 5);
|
|
194
|
+
const signal = opts.signal;
|
|
195
|
+
let all = await this.search(query, { limit: perQueryLimit * 2, signal });
|
|
196
|
+
let previousHop = all;
|
|
197
|
+
const searched = new Set([query]);
|
|
198
|
+
const followUpQueries = [];
|
|
199
|
+
let hopsCompleted = 1;
|
|
200
|
+
for (let hop = 2; hop <= maxHops; hop += 1) {
|
|
201
|
+
if (signal?.aborted === true)
|
|
202
|
+
break;
|
|
203
|
+
const followUps = deriveFollowUps(query, previousHop, previousHop.length)
|
|
204
|
+
.filter((q) => !searched.has(q))
|
|
205
|
+
.filter((q, i, queries) => queries.indexOf(q) === i)
|
|
206
|
+
.slice(0, followUpsPerHop);
|
|
207
|
+
if (followUps.length === 0)
|
|
208
|
+
break;
|
|
209
|
+
for (const q of followUps)
|
|
210
|
+
searched.add(q);
|
|
211
|
+
followUpQueries.push(...followUps);
|
|
212
|
+
const settled = await Promise.allSettled(followUps.map((q) => searchChain(q, this.#deps.searchChain, {
|
|
213
|
+
limit: perQueryLimit,
|
|
214
|
+
signal,
|
|
215
|
+
})));
|
|
216
|
+
previousHop = dedupeByUrl(settled.flatMap((outcome) => outcome.status === 'fulfilled' ? outcome.value : []));
|
|
217
|
+
all = dedupeByUrl([...all, ...previousHop]);
|
|
218
|
+
hopsCompleted = hop;
|
|
219
|
+
}
|
|
220
|
+
const urls = dedupeByUrl(all)
|
|
221
|
+
.filter((r) => isHttpUrl(r.url))
|
|
222
|
+
.slice(0, maxExtracts)
|
|
223
|
+
.map((r) => r.url);
|
|
224
|
+
const extracted = await Promise.allSettled(
|
|
225
|
+
// Route through this.extract() so every URL — including ones that
|
|
226
|
+
// came from search results — passes the sanitizeExtractUrl choke
|
|
227
|
+
// point. A rejected URL settles as a rejection, never an extract.
|
|
228
|
+
urls.map((u) => this.extract(u, signal)));
|
|
229
|
+
const extracts = [];
|
|
230
|
+
for (const outcome of extracted) {
|
|
231
|
+
if (outcome.status === 'fulfilled')
|
|
232
|
+
extracts.push(outcome.value);
|
|
233
|
+
}
|
|
234
|
+
const results = dedupeByUrl(all);
|
|
235
|
+
const seen = new Set();
|
|
236
|
+
const sources = [];
|
|
237
|
+
for (const r of results) {
|
|
238
|
+
if (seen.has(r.url))
|
|
239
|
+
continue;
|
|
240
|
+
seen.add(r.url);
|
|
241
|
+
sources.push(toAttribution(r));
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
query,
|
|
245
|
+
results,
|
|
246
|
+
extracts,
|
|
247
|
+
sources,
|
|
248
|
+
hopsCompleted,
|
|
249
|
+
followUpQueries,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal DOCX text extractor — hand-written, zero npm dependencies.
|
|
3
|
+
*
|
|
4
|
+
* A `.docx` is a ZIP (OOXML) whose prose lives in `word/document.xml` as
|
|
5
|
+
* `<w:t>` runs inside `<w:p>` paragraphs. This module ships its own tiny
|
|
6
|
+
* ZIP reader (central-directory walk + `node:zlib` inflateRaw) instead of
|
|
7
|
+
* taking an npm zip library — one less supply-chain dependency, and every
|
|
8
|
+
* decompression goes through `./guards.ts` quotas (the declared-size check
|
|
9
|
+
* first, then the streaming actual-bytes check).
|
|
10
|
+
*
|
|
11
|
+
* Honestly scoped: body paragraphs (+ tables as tab/newline-separated text),
|
|
12
|
+
* headers/footers/footnotes are NOT read, complex fields are flattened to
|
|
13
|
+
* their visible text. That's the text a researcher needs from a document.
|
|
14
|
+
*/
|
|
15
|
+
export interface DocxExtractedText {
|
|
16
|
+
readonly text: string;
|
|
17
|
+
readonly title: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Extract text from DOCX `bytes`. Throws {@link DocumentParseError} with
|
|
21
|
+
* `CORRUPT_DOCUMENT` (not a zip / no document.xml), `ARCHIVE_LIMIT`
|
|
22
|
+
* (zip bomb), `DOCUMENT_TOO_LARGE`, or `UNSUPPORTED_TYPE` (encrypted/zip64).
|
|
23
|
+
*/
|
|
24
|
+
export declare function extractDocxText(bytes: Uint8Array): Promise<DocxExtractedText>;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal DOCX text extractor — hand-written, zero npm dependencies.
|
|
3
|
+
*
|
|
4
|
+
* A `.docx` is a ZIP (OOXML) whose prose lives in `word/document.xml` as
|
|
5
|
+
* `<w:t>` runs inside `<w:p>` paragraphs. This module ships its own tiny
|
|
6
|
+
* ZIP reader (central-directory walk + `node:zlib` inflateRaw) instead of
|
|
7
|
+
* taking an npm zip library — one less supply-chain dependency, and every
|
|
8
|
+
* decompression goes through `./guards.ts` quotas (the declared-size check
|
|
9
|
+
* first, then the streaming actual-bytes check).
|
|
10
|
+
*
|
|
11
|
+
* Honestly scoped: body paragraphs (+ tables as tab/newline-separated text),
|
|
12
|
+
* headers/footers/footnotes are NOT read, complex fields are flattened to
|
|
13
|
+
* their visible text. That's the text a researcher needs from a document.
|
|
14
|
+
*/
|
|
15
|
+
import { Buffer } from 'node:buffer';
|
|
16
|
+
import { DecompressionQuota, DocumentParseError, MAX_ZIP_ENTRIES, MAX_ZIP_ENTRY_BYTES, assertDeclaredEntrySizes, assertDocumentSize, } from './guards.js';
|
|
17
|
+
const ZIP_LOCAL_SIG = 0x04034b50;
|
|
18
|
+
const ZIP_CENTRAL_SIG = 0x02014b50;
|
|
19
|
+
const ZIP_EOCD_SIG = 0x06054b50;
|
|
20
|
+
function parseCentralDirectory(bytes) {
|
|
21
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
22
|
+
// The end-of-central-directory record sits in the last 64KiB + 22 bytes.
|
|
23
|
+
const minPos = Math.max(0, bytes.length - 65557);
|
|
24
|
+
let eocd = -1;
|
|
25
|
+
for (let i = bytes.length - 22; i >= minPos; i -= 1) {
|
|
26
|
+
if (view.getUint32(i, true) === ZIP_EOCD_SIG) {
|
|
27
|
+
eocd = i;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (eocd < 0) {
|
|
32
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', 'ZIP end-of-central-directory not found.');
|
|
33
|
+
}
|
|
34
|
+
const count = view.getUint16(eocd + 10, true);
|
|
35
|
+
const cdOffset = view.getUint32(eocd + 16, true);
|
|
36
|
+
if (count > MAX_ZIP_ENTRIES) {
|
|
37
|
+
throw new DocumentParseError('ARCHIVE_LIMIT', `ZIP declares ${count} entries (cap ${MAX_ZIP_ENTRIES}, possible zip bomb).`);
|
|
38
|
+
}
|
|
39
|
+
if (cdOffset > bytes.length) {
|
|
40
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', 'ZIP central directory offset is out of bounds.');
|
|
41
|
+
}
|
|
42
|
+
const entries = [];
|
|
43
|
+
let pos = cdOffset;
|
|
44
|
+
for (let e = 0; e < count; e += 1) {
|
|
45
|
+
if (pos + 46 > bytes.length) {
|
|
46
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', 'ZIP central directory is truncated.');
|
|
47
|
+
}
|
|
48
|
+
if (view.getUint32(pos, true) !== ZIP_CENTRAL_SIG) {
|
|
49
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', `Bad central directory signature at entry ${e}.`);
|
|
50
|
+
}
|
|
51
|
+
const flags = view.getUint16(pos + 8, true);
|
|
52
|
+
const method = view.getUint16(pos + 10, true);
|
|
53
|
+
const compressedSize = view.getUint32(pos + 20, true);
|
|
54
|
+
const uncompressedSize = view.getUint32(pos + 24, true);
|
|
55
|
+
const nameLen = view.getUint16(pos + 28, true);
|
|
56
|
+
const extraLen = view.getUint16(pos + 30, true);
|
|
57
|
+
const commentLen = view.getUint16(pos + 32, true);
|
|
58
|
+
const localOffset = view.getUint32(pos + 42, true);
|
|
59
|
+
if (compressedSize === 0xffffffff ||
|
|
60
|
+
uncompressedSize === 0xffffffff) {
|
|
61
|
+
throw new DocumentParseError('UNSUPPORTED_TYPE', 'ZIP64 archives are not supported.');
|
|
62
|
+
}
|
|
63
|
+
if ((flags & 0x1) !== 0) {
|
|
64
|
+
throw new DocumentParseError('UNSUPPORTED_TYPE', 'Encrypted ZIP entries are not supported.');
|
|
65
|
+
}
|
|
66
|
+
if ((flags & 0x8) !== 0) {
|
|
67
|
+
// Bit 3 = sizes live in a data descriptor after the data; a
|
|
68
|
+
// central-directory walk cannot locate the data safely.
|
|
69
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', 'ZIP entry uses a data descriptor (streaming layout not supported).');
|
|
70
|
+
}
|
|
71
|
+
const name = Buffer.from(bytes.slice(pos + 46, pos + 46 + nameLen)).toString('utf8');
|
|
72
|
+
entries.push({ name, method, compressedSize, uncompressedSize, localOffset });
|
|
73
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
74
|
+
}
|
|
75
|
+
return entries;
|
|
76
|
+
}
|
|
77
|
+
async function readEntry(bytes, entry, quota) {
|
|
78
|
+
// Declared-size check FIRST (cheap zip-bomb reject)...
|
|
79
|
+
assertDeclaredEntrySizes({
|
|
80
|
+
compressedSize: entry.compressedSize,
|
|
81
|
+
uncompressedSize: entry.uncompressedSize,
|
|
82
|
+
method: entry.method,
|
|
83
|
+
label: entry.name,
|
|
84
|
+
});
|
|
85
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
86
|
+
const lo = entry.localOffset;
|
|
87
|
+
if (lo + 30 > bytes.length || view.getUint32(lo, true) !== ZIP_LOCAL_SIG) {
|
|
88
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', `Bad local header for ZIP entry '${entry.name}'.`);
|
|
89
|
+
}
|
|
90
|
+
const nameLen = view.getUint16(lo + 26, true);
|
|
91
|
+
const extraLen = view.getUint16(lo + 28, true);
|
|
92
|
+
const dataStart = lo + 30 + nameLen + extraLen;
|
|
93
|
+
const dataEnd = dataStart + entry.compressedSize;
|
|
94
|
+
if (dataEnd > bytes.length) {
|
|
95
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', `ZIP entry '${entry.name}' data extends past end of file.`);
|
|
96
|
+
}
|
|
97
|
+
const data = bytes.slice(dataStart, dataEnd);
|
|
98
|
+
if (entry.method === 0) {
|
|
99
|
+
// ...then actual-bytes accounting even for stored entries.
|
|
100
|
+
quota.trackRaw(data.length, entry.name);
|
|
101
|
+
return data;
|
|
102
|
+
}
|
|
103
|
+
// ...then the streaming actual-bytes check inside inflateRaw.
|
|
104
|
+
return quota.inflateRaw(data, MAX_ZIP_ENTRY_BYTES, entry.name);
|
|
105
|
+
}
|
|
106
|
+
function decodeXmlEntities(s) {
|
|
107
|
+
return s
|
|
108
|
+
.replace(/&(lt|gt|amp|quot|apos);/g, (m, name) => {
|
|
109
|
+
switch (name) {
|
|
110
|
+
case 'lt': return '<';
|
|
111
|
+
case 'gt': return '>';
|
|
112
|
+
case 'amp': return '&';
|
|
113
|
+
case 'quot': return '"';
|
|
114
|
+
case 'apos': return "'";
|
|
115
|
+
default: return m;
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
.replace(/&#(\d+);/g, (_m, dec) => String.fromCodePoint(parseInt(dec, 10)))
|
|
119
|
+
.replace(/&#x([0-9a-fA-F]+);/g, (_m, hex) => String.fromCodePoint(parseInt(hex, 16)));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Flatten `word/document.xml` to text: `<w:t>` runs are the prose,
|
|
123
|
+
* `</w:p>` ends a paragraph, `<w:br/>` a line break, `<w:tab/>` a tab,
|
|
124
|
+
* table cells/rows get tab/newline separators so tables stay readable.
|
|
125
|
+
*/
|
|
126
|
+
function docxXmlToText(xml) {
|
|
127
|
+
const parts = [];
|
|
128
|
+
const re = /<\/w:p\s*>|<\/w:tr\s*>|<\/w:tc\s*>|<w:br\s*\/>|<w:tab\s*\/>|<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t\s*>/g;
|
|
129
|
+
for (;;) {
|
|
130
|
+
const m = re.exec(xml);
|
|
131
|
+
if (m === null)
|
|
132
|
+
break;
|
|
133
|
+
const tok = m[0];
|
|
134
|
+
if (tok.startsWith('</w:p') || tok.startsWith('</w:tr') || tok.startsWith('<w:br')) {
|
|
135
|
+
parts.push('\n');
|
|
136
|
+
}
|
|
137
|
+
else if (tok.startsWith('</w:tc') || tok.startsWith('<w:tab')) {
|
|
138
|
+
parts.push('\t');
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
parts.push(decodeXmlEntities(m[1] ?? ''));
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return parts.join('');
|
|
145
|
+
}
|
|
146
|
+
function findDcTitle(coreXml) {
|
|
147
|
+
const m = /<dc:title[^>]*>([\s\S]*?)<\/dc:title>/.exec(coreXml);
|
|
148
|
+
return m !== null ? decodeXmlEntities(m[1] ?? '').trim() : '';
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Extract text from DOCX `bytes`. Throws {@link DocumentParseError} with
|
|
152
|
+
* `CORRUPT_DOCUMENT` (not a zip / no document.xml), `ARCHIVE_LIMIT`
|
|
153
|
+
* (zip bomb), `DOCUMENT_TOO_LARGE`, or `UNSUPPORTED_TYPE` (encrypted/zip64).
|
|
154
|
+
*/
|
|
155
|
+
export async function extractDocxText(bytes) {
|
|
156
|
+
assertDocumentSize(bytes.length, 'DOCX');
|
|
157
|
+
if (bytes.length < 4 ||
|
|
158
|
+
bytes[0] !== 0x50 ||
|
|
159
|
+
bytes[1] !== 0x4b ||
|
|
160
|
+
bytes[2] !== 0x03 ||
|
|
161
|
+
bytes[3] !== 0x04) {
|
|
162
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', 'Not a DOCX file (missing ZIP magic).');
|
|
163
|
+
}
|
|
164
|
+
const entries = parseCentralDirectory(bytes);
|
|
165
|
+
const quota = new DecompressionQuota();
|
|
166
|
+
const documentEntry = entries.find((e) => e.name === 'word/document.xml');
|
|
167
|
+
if (documentEntry === undefined) {
|
|
168
|
+
throw new DocumentParseError('CORRUPT_DOCUMENT', "ZIP has no 'word/document.xml' — not a Word document.");
|
|
169
|
+
}
|
|
170
|
+
const documentXml = Buffer.from(await readEntry(bytes, documentEntry, quota)).toString('utf8');
|
|
171
|
+
const text = docxXmlToText(documentXml);
|
|
172
|
+
let title = '';
|
|
173
|
+
const coreEntry = entries.find((e) => e.name === 'docProps/core.xml');
|
|
174
|
+
if (coreEntry !== undefined) {
|
|
175
|
+
try {
|
|
176
|
+
title = findDcTitle(Buffer.from(await readEntry(bytes, coreEntry, quota)).toString('utf8'));
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// A broken core.xml must not fail the whole document.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return { text, title };
|
|
183
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security guards for document parsing (`research.parse`).
|
|
3
|
+
*
|
|
4
|
+
* Parsing attacker-influenced bytes — a URL the model or user asked
|
|
5
|
+
* `research.extract` to read — is an attack surface: zip bombs, decompression
|
|
6
|
+
* bombs hidden in PDF content streams, and absurd entry counts. This module
|
|
7
|
+
* is the choke point every parser runs through: the same role
|
|
8
|
+
* `sanitizeExtractUrl` plays for URL inputs in `orchestrator.extract()`.
|
|
9
|
+
*
|
|
10
|
+
* Two lines of defense, because declared sizes are attacker-controlled:
|
|
11
|
+
* 1. Declared sizes are checked BEFORE inflating — cheaply rejects 42.zip-style
|
|
12
|
+
* entries whose headers claim gigabytes.
|
|
13
|
+
* 2. Actual output bytes are counted DURING streaming inflation — closes the
|
|
14
|
+
* understated/zero-size hole (the adm-zip CVE-2026-77301 class of bug, where
|
|
15
|
+
* a streaming path never checked accumulated output).
|
|
16
|
+
*
|
|
17
|
+
* Fail-closed: any cap breach throws `DocumentParseError`; a parser never
|
|
18
|
+
* returns a partial buffer.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Machine-readable failure codes. Additive-first: new codes are added,
|
|
22
|
+
* never renamed.
|
|
23
|
+
*/
|
|
24
|
+
export type DocumentParseErrorCode = 'UNSUPPORTED_TYPE' | 'DOCUMENT_TOO_LARGE' | 'ARCHIVE_LIMIT' | 'CORRUPT_DOCUMENT';
|
|
25
|
+
export declare class DocumentParseError extends Error {
|
|
26
|
+
readonly code: DocumentParseErrorCode;
|
|
27
|
+
constructor(code: DocumentParseErrorCode, message: string, options?: ErrorOptions);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hard cap on raw document bytes accepted for parsing. Matches
|
|
31
|
+
* `DEFAULT_MAX_RESPONSE_BYTES` in `../http.ts` so a document fetch can never
|
|
32
|
+
* exceed what a normal extract response may already hold.
|
|
33
|
+
*/
|
|
34
|
+
export declare const MAX_DOCUMENT_BYTES = 5242880;
|
|
35
|
+
/** Max entries in a ZIP central directory — a docx rarely has a few hundred. */
|
|
36
|
+
export declare const MAX_ZIP_ENTRIES = 2048;
|
|
37
|
+
/** Max declared uncompressed size of a single ZIP entry. */
|
|
38
|
+
export declare const MAX_ZIP_ENTRY_BYTES = 20971520;
|
|
39
|
+
/** Max total decompressed bytes across every inflated entry/stream of one document. */
|
|
40
|
+
export declare const MAX_TOTAL_DECOMPRESSED_BYTES = 52428800;
|
|
41
|
+
/**
|
|
42
|
+
* Declared compression ratio above this is treated as a bomb. Legitimate
|
|
43
|
+
* docx XML compresses ~5–20:1; real zip bombs start around 100,000:1, so
|
|
44
|
+
* 100:1 rejects every known bomb shape with wide margin for legit files.
|
|
45
|
+
*/
|
|
46
|
+
export declare const MAX_COMPRESSION_RATIO = 100;
|
|
47
|
+
/** Max content streams inflated from a single PDF. */
|
|
48
|
+
export declare const MAX_PDF_STREAMS = 20000;
|
|
49
|
+
/**
|
|
50
|
+
* Ceiling on extracted plain-text characters — defense-in-depth past the
|
|
51
|
+
* byte quotas, so parser output can never blow up turn context on its own.
|
|
52
|
+
*/
|
|
53
|
+
export declare const MAX_EXTRACTED_CHARS = 200000;
|
|
54
|
+
/** Reject an input buffer that exceeds {@link MAX_DOCUMENT_BYTES}. */
|
|
55
|
+
export declare function assertDocumentSize(byteLength: number, label?: string): void;
|
|
56
|
+
/**
|
|
57
|
+
* Check a ZIP entry's DECLARED sizes before touching its data.
|
|
58
|
+
* Cheap first line of defense — rejects 42.zip-style headers outright.
|
|
59
|
+
*/
|
|
60
|
+
export declare function assertDeclaredEntrySizes(args: {
|
|
61
|
+
readonly compressedSize: number;
|
|
62
|
+
readonly uncompressedSize: number;
|
|
63
|
+
readonly method: number;
|
|
64
|
+
readonly label: string;
|
|
65
|
+
}): void;
|
|
66
|
+
/**
|
|
67
|
+
* Tracks the running total of decompressed bytes for one document and
|
|
68
|
+
* inflates raw-deflate blocks with both per-entry and total caps enforced
|
|
69
|
+
* DURING decompression (streaming). Declared sizes are attacker-controlled,
|
|
70
|
+
* so this second line of defense measures actual output — an entry that
|
|
71
|
+
* understates its size still trips the cap mid-inflate instead of landing
|
|
72
|
+
* a huge buffer in memory first.
|
|
73
|
+
*/
|
|
74
|
+
export declare class DecompressionQuota {
|
|
75
|
+
#private;
|
|
76
|
+
/** Bytes inflated so far across all entries/streams of this document. */
|
|
77
|
+
get usedTotal(): number;
|
|
78
|
+
/** Account `byteLength` raw (stored, not inflated) bytes against the total quota. */
|
|
79
|
+
trackRaw(byteLength: number, label: string): void;
|
|
80
|
+
/**
|
|
81
|
+
* Inflate one zlib-wrapped deflate block (PDF `/FlateDecode` is zlib /
|
|
82
|
+
* RFC 1950 per the PDF spec, NOT raw deflate).
|
|
83
|
+
*/
|
|
84
|
+
inflate(compressed: Uint8Array, maxEntryBytes: number, label: string): Promise<Uint8Array>;
|
|
85
|
+
/**
|
|
86
|
+
* Inflate one raw deflate block (ZIP entry data has no zlib wrapper).
|
|
87
|
+
*/
|
|
88
|
+
inflateRaw(compressed: Uint8Array, maxEntryBytes: number, label: string): Promise<Uint8Array>;
|
|
89
|
+
}
|