berry-agent 0.1.0-alpha.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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebFetchService——三消费面共用的唯一 execute 路径(02 §4.1 席 18「两消费
|
|
3
|
+
* 面同一 execute」+ 03 §10.3 browser 件章第三消费位)。
|
|
4
|
+
*
|
|
5
|
+
* 五卫生件编排序(缺一不可、逐跳复跑):
|
|
6
|
+
* 限流(在飞门)→ URL 解析 + 协议白名单 → 私网判定(字面 + DNS)→
|
|
7
|
+
* 重定向跟随(手动逐跳——每跳目标重跑解析/协议/私网三查,跳数触帽拒)→
|
|
8
|
+
* 字节上限(流式读取,触帽截断置 truncated 非拒)→ 归因落账(sink 注入,
|
|
9
|
+
* ok/blocked/error 三结局全落——运行账「谁在何时取了什么」)。
|
|
10
|
+
*
|
|
11
|
+
* 超时不在本件:signal 归调用方(工具面 = 管道执行段预算;服务面 = 调用方
|
|
12
|
+
* 自带)——服务内不设时钟(可测性 + 预算单源)。
|
|
13
|
+
*
|
|
14
|
+
* 已知边界(v1 注记):redirect:'manual' 逐跳拦截 + 请求前 DNS 全查已覆盖
|
|
15
|
+
* 模型可控的全部 URL 面;DNS 校验与实际连接的解析漂移窗(rebinding)与
|
|
16
|
+
* undici 连接级钉死挂账后续批。
|
|
17
|
+
*/
|
|
18
|
+
import { BaseError } from '../contracts/index.js';
|
|
19
|
+
import { createInFlightGate } from './gate.js';
|
|
20
|
+
import { assertPublicHost, defaultDnsResolver, parseWebUrl } from './hygiene.js';
|
|
21
|
+
/** 缺省数值帽(随实机校准回填——规范不收数字,07 §1.1 只列五卫生件名目) */
|
|
22
|
+
export const DEFAULT_WEB_LIMITS = {
|
|
23
|
+
maxRedirects: 5,
|
|
24
|
+
maxBytes: 1024 * 1024,
|
|
25
|
+
maxConcurrent: 4,
|
|
26
|
+
};
|
|
27
|
+
/** 重定向状态码集(其余 3xx 按终态返回不跟随) */
|
|
28
|
+
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
29
|
+
/**
|
|
30
|
+
* 流式读响应体到字节帽:触帽即 cancel 流(不再拉数据)、截到恰好帽值、
|
|
31
|
+
* truncated 置真。只产原始字节——UTF-8 严格解码由调用方在快照 bytes 之后
|
|
32
|
+
* 做(解码失败也是「已读了这么多字节」的失败,归因记账不归零)。
|
|
33
|
+
*/
|
|
34
|
+
async function readBodyCapped(response, maxBytes) {
|
|
35
|
+
if (response.body === null) {
|
|
36
|
+
// HEAD/204 等空体形——零字节非截断
|
|
37
|
+
return { raw: new Uint8Array(0), bytes: 0, truncated: false };
|
|
38
|
+
}
|
|
39
|
+
const reader = response.body.getReader();
|
|
40
|
+
const chunks = [];
|
|
41
|
+
let total = 0;
|
|
42
|
+
let truncated = false;
|
|
43
|
+
while (true) {
|
|
44
|
+
const { done, value } = await reader.read();
|
|
45
|
+
if (done)
|
|
46
|
+
break;
|
|
47
|
+
if (total + value.byteLength > maxBytes) {
|
|
48
|
+
// 触帽:只收帽内余量,弃流
|
|
49
|
+
const remainder = maxBytes - total;
|
|
50
|
+
if (remainder > 0)
|
|
51
|
+
chunks.push(value.slice(0, remainder));
|
|
52
|
+
total = maxBytes;
|
|
53
|
+
truncated = true;
|
|
54
|
+
await reader.cancel().catch(() => {
|
|
55
|
+
// cancel 失败不阻断——连接由 undici 自理,数据已不再被读
|
|
56
|
+
});
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
chunks.push(value);
|
|
60
|
+
total += value.byteLength;
|
|
61
|
+
}
|
|
62
|
+
const merged = new Uint8Array(total);
|
|
63
|
+
let offset = 0;
|
|
64
|
+
for (const chunk of chunks) {
|
|
65
|
+
merged.set(chunk, offset);
|
|
66
|
+
offset += chunk.byteLength;
|
|
67
|
+
}
|
|
68
|
+
return { raw: merged, bytes: total, truncated };
|
|
69
|
+
}
|
|
70
|
+
/** UTF-8 严格解码(截断劈尾回退剥 ≤3 字节——UTF-8 最长序列 4 字节;真非 UTF-8 各试皆红抛普通 Error——二进制内容不适用文本 fetch 面,属普通失败非卫生拦截) */
|
|
71
|
+
function decodeUtf8Strict(bytes, truncated) {
|
|
72
|
+
const decoder = new TextDecoder('utf-8', { fatal: true });
|
|
73
|
+
// 未截断时全长一次即判(劈尾剥除只对截断形有意义);截断形全长 → 剥 1..3
|
|
74
|
+
const candidates = truncated ? [0, 1, 2, 3] : [0];
|
|
75
|
+
for (const trim of candidates) {
|
|
76
|
+
try {
|
|
77
|
+
return decoder.decode(bytes.subarray(0, bytes.byteLength - trim));
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// 继续下一候选
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
throw new Error('响应体非 UTF-8 文本——二进制内容不适用 fetch 工具(文本面只认 UTF-8)');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 服务构造:三消费面(fetch 工具 / ctx.fetch 插件消费 / browser 导航)共用
|
|
87
|
+
* 同一 service 实例即共用同一 execute 与同一在飞门。
|
|
88
|
+
*/
|
|
89
|
+
export function createWebFetchService(deps = {}) {
|
|
90
|
+
const fetchImpl = deps.fetchImpl ?? ((url, init) => fetch(url, init));
|
|
91
|
+
const resolveDns = deps.resolveDns ?? defaultDnsResolver;
|
|
92
|
+
const now = deps.now ?? (() => Date.now());
|
|
93
|
+
const sink = deps.sink ?? (() => { });
|
|
94
|
+
const limits = { ...DEFAULT_WEB_LIMITS, ...deps.limits };
|
|
95
|
+
const gate = deps.gate ?? createInFlightGate(limits.maxConcurrent);
|
|
96
|
+
// 归因落账统一出口:sink 自身异常不吞业务结果也不中断——归因属观测面
|
|
97
|
+
const record = (entry) => {
|
|
98
|
+
try {
|
|
99
|
+
sink(entry);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
/* 归因 sink 异常静默——观测面不绑架数据面 */
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
return {
|
|
106
|
+
async fetch(rawUrl, init = {}) {
|
|
107
|
+
const consumer = init.consumer ?? 'service';
|
|
108
|
+
const method = init.method ?? 'GET';
|
|
109
|
+
const startedAt = now();
|
|
110
|
+
// 进度快照——ok 结局在 task 内记全量;失败结局在外层 catch 记(记到哪
|
|
111
|
+
// 步算哪步:finalUrl/status 到达即带,早拒即缺席)
|
|
112
|
+
let finalUrl;
|
|
113
|
+
let status;
|
|
114
|
+
let bytes = 0;
|
|
115
|
+
let redirects = 0;
|
|
116
|
+
try {
|
|
117
|
+
return await gate.run(async () => {
|
|
118
|
+
// 卫生件 1/5:在飞门(满拒 WEB_RATE_LIMITED——由 gate.run 抛出,外层
|
|
119
|
+
// catch 统一落账)
|
|
120
|
+
// 卫生件 2/5:URL 解析 + 协议白名单
|
|
121
|
+
let url = parseWebUrl(rawUrl);
|
|
122
|
+
// 卫生件 3/5:私网判定(字面 + DNS)
|
|
123
|
+
await assertPublicHost(url, resolveDns);
|
|
124
|
+
// 卫生件 4/5:重定向逐跳跟随(手动——每跳目标重跑三查,跳数触帽拒)
|
|
125
|
+
let requestMethod = method;
|
|
126
|
+
let requestBody = init.body;
|
|
127
|
+
let response;
|
|
128
|
+
while (true) {
|
|
129
|
+
response = await fetchImpl(url.toString(), {
|
|
130
|
+
method: requestMethod,
|
|
131
|
+
...(init.headers ? { headers: init.headers } : {}),
|
|
132
|
+
...(requestBody !== undefined ? { body: requestBody } : {}),
|
|
133
|
+
redirect: 'manual',
|
|
134
|
+
...(init.signal ? { signal: init.signal } : {}),
|
|
135
|
+
});
|
|
136
|
+
if (!REDIRECT_STATUSES.has(response.status))
|
|
137
|
+
break;
|
|
138
|
+
const location = response.headers.get('location');
|
|
139
|
+
if (!location)
|
|
140
|
+
break; // 3xx 无 Location——按终态返回(宽容不发明跳转)
|
|
141
|
+
if (redirects >= limits.maxRedirects) {
|
|
142
|
+
throw new BaseError('WEB_REDIRECT_LIMIT', `重定向跟随触帽(${limits.maxRedirects} 跳):${rawUrl} → … → ${url.toString()}`);
|
|
143
|
+
}
|
|
144
|
+
// 跳目标经同一卫生单源复检(协议/私网/DNS——重定向是 SSRF 主载体)
|
|
145
|
+
const next = parseWebUrl(new URL(location, url.toString()).toString());
|
|
146
|
+
await assertPublicHost(next, resolveDns);
|
|
147
|
+
redirects += 1;
|
|
148
|
+
url = next;
|
|
149
|
+
// 方法改写语义:303 恒转 GET 弃体;301/302 携 POST 按浏览器兼容转
|
|
150
|
+
// GET(RFC 保留方法但现实生态如此);307/308 保方法保体
|
|
151
|
+
if (response.status === 303 ||
|
|
152
|
+
((response.status === 301 || response.status === 302) && requestMethod === 'POST')) {
|
|
153
|
+
requestMethod = 'GET';
|
|
154
|
+
requestBody = undefined;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// 卫生件 5/5:字节上限(截断非拒)——bytes 先快照(解码失败也记
|
|
158
|
+
// 已读字节数),UTF-8 严格解码在后
|
|
159
|
+
const read = await readBodyCapped(response, limits.maxBytes);
|
|
160
|
+
bytes = read.bytes;
|
|
161
|
+
const body = decodeUtf8Strict(read.raw, read.truncated);
|
|
162
|
+
finalUrl = url.toString();
|
|
163
|
+
status = response.status;
|
|
164
|
+
record({
|
|
165
|
+
consumer,
|
|
166
|
+
method: requestMethod,
|
|
167
|
+
url: rawUrl,
|
|
168
|
+
finalUrl,
|
|
169
|
+
status,
|
|
170
|
+
bytes,
|
|
171
|
+
redirects,
|
|
172
|
+
outcome: 'ok',
|
|
173
|
+
at: startedAt,
|
|
174
|
+
});
|
|
175
|
+
return {
|
|
176
|
+
url: rawUrl,
|
|
177
|
+
finalUrl,
|
|
178
|
+
status,
|
|
179
|
+
contentType: response.headers.get('content-type') ?? '',
|
|
180
|
+
body,
|
|
181
|
+
truncated: read.truncated,
|
|
182
|
+
bytes,
|
|
183
|
+
redirects,
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
// 失败结局统一落账:BaseError = 卫生拦截(blocked + 码);其余 = 普通失败
|
|
189
|
+
// (DNS/网络/中止/解码——error 档无码)。在飞门拒也经此路(gate.run 内
|
|
190
|
+
// 先抛,快照全空即早拒形)
|
|
191
|
+
record({
|
|
192
|
+
consumer,
|
|
193
|
+
method,
|
|
194
|
+
url: rawUrl,
|
|
195
|
+
...(finalUrl !== undefined ? { finalUrl } : {}),
|
|
196
|
+
...(status !== undefined ? { status } : {}),
|
|
197
|
+
bytes,
|
|
198
|
+
redirects,
|
|
199
|
+
outcome: error instanceof BaseError ? 'blocked' : 'error',
|
|
200
|
+
...(error instanceof BaseError ? { errorCode: error.code } : {}),
|
|
201
|
+
at: startedAt,
|
|
202
|
+
});
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSRF 守卫 fetch 包裹件(web 卫生单源的第三消费位——03 §10.9 oauth 案
|
|
3
|
+
* 「SSRF 守卫」挂账收口件,2026-09-09)。
|
|
4
|
+
*
|
|
5
|
+
* 承载:oauth 外联腿(credentials 域——人面发起腿与刷新链腿)的 fetch
|
|
6
|
+
* 预检包裹。插件声明的 deviceAuthUrl/tokenUrl 是任意 URL——宿主外联前
|
|
7
|
+
* 经 web 卫生单源复用两查(协议白名单 + 私网/保留段拒,字面 + DNS)。
|
|
8
|
+
*
|
|
9
|
+
* 定形要点(03 §10.9 定形注同源):
|
|
10
|
+
* - **拦截码复用 `WEB_` 族不自造**(browser 件章同律——守卫错即 BaseError
|
|
11
|
+
* 直传,消费面按码分流);
|
|
12
|
+
* - **`redirect: 'manual'` 钉死不跟随**——oauth 端点恒直答 200 形态,3xx
|
|
13
|
+
* 应答按非 200 由 oauth 层折 FLOW_FAILED 兜底 fail-closed;自动跟随是
|
|
14
|
+
* SSRF 首跳绕过主载体(公网 URL 302 → 内网地址),钉死即闭合。重定向
|
|
15
|
+
* 逐跳跟随编舞只在 web service 唯一实现(service.ts),本件不复制第二份;
|
|
16
|
+
* - **守卫件住 web 域**——credentials 无 web 边(「本件无 web 边,fetchFn
|
|
17
|
+
* 宿主装配位注入」),装配位(host 已有 web 边)包裹注入,credentials
|
|
18
|
+
* 模块零改动。
|
|
19
|
+
*
|
|
20
|
+
* 与 web service 的分职:service = 模型可控任意 URL 的全卫生编排(在飞门/
|
|
21
|
+
* 逐跳重定向/字节帽/归因落账);本件 = 宿主外联固定端点的窄面包裹(无门
|
|
22
|
+
* 无帽无归因——端点是插件声明非模型生成,额度面不适用)。
|
|
23
|
+
*/
|
|
24
|
+
import { assertPublicHost, defaultDnsResolver, parseWebUrl } from './hygiene.js';
|
|
25
|
+
/**
|
|
26
|
+
* 构造 SSRF 守卫 fetch:每次调用先卫生两查(解析 + 协议白名单 → 字面 +
|
|
27
|
+
* DNS 私网拒),过了才透传底层 fetchImpl(init 原样 + redirect 钉 manual)。
|
|
28
|
+
*
|
|
29
|
+
* 返回形 = FetchLike(结构兼容全局 fetch 窄面;credentials OAuthFetchLike
|
|
30
|
+
* 只消费 ok/status/text 三面——Response 超集,装配位直传即合法注入)。
|
|
31
|
+
*
|
|
32
|
+
* @param fetchImpl 底层 fetch(生产 = globalThis.fetch;测试注入桩)
|
|
33
|
+
* @param resolveDns DNS 解析器(缺省 node:dns/promises lookup all——测试注入)
|
|
34
|
+
*/
|
|
35
|
+
export function createSsrfGuardedFetch(fetchImpl, resolveDns = defaultDnsResolver) {
|
|
36
|
+
return async (url, init) => {
|
|
37
|
+
// 卫生两查(卫生单源复用——解析/协议白名单在 parseWebUrl,字面 + DNS
|
|
38
|
+
// 私网拒在 assertPublicHost;拒即抛 WEB_ 族 BaseError 不透传底层)
|
|
39
|
+
const parsed = parseWebUrl(url);
|
|
40
|
+
await assertPublicHost(parsed, resolveDns);
|
|
41
|
+
// 透传(redirect 钉 manual 不跟随——3xx 按非 200 由消费面折兜底码)
|
|
42
|
+
return fetchImpl(url, { ...init, redirect: 'manual' });
|
|
43
|
+
};
|
|
44
|
+
}
|
package/dist/web/tool.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fetch 工具件(消费面一——模型消费;02 §4.1 席 18「fetch 工具」)。
|
|
3
|
+
*
|
|
4
|
+
* 工具面 = WebFetchService 的薄包装:参数 schema 收紧(方法词表/头表/体)
|
|
5
|
+
* + 卫生拦截码转 isError 数据面(一切失败编码为结果——03 §2.3 工具契约)
|
|
6
|
+
* + 应答拼模型可见文本(元信息头 + 正文——承 berry fetch 头形思路:URL/
|
|
7
|
+
* Content-Type/跳转/截断记全披露,正文分隔线下置)。effect 'read':GET/
|
|
8
|
+
* POST 皆模型读世界动作,不触发审批对与可写根(写盘另有 fs 族——fetch
|
|
9
|
+
* 不落盘)。
|
|
10
|
+
*
|
|
11
|
+
* consumer 标注 'tool'——归因落账与 ctx.fetch 服务消费('service')、browser
|
|
12
|
+
* 导航('navigate')三面可区分(同一 execute 路径)。
|
|
13
|
+
*/
|
|
14
|
+
import { BaseError } from '../contracts/index.js';
|
|
15
|
+
import { Type } from 'typebox';
|
|
16
|
+
/** 工具面方法词表(服务面不收紧——插件经 ctx.fetch 可用任意标准方法) */
|
|
17
|
+
const TOOL_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'];
|
|
18
|
+
/**
|
|
19
|
+
* fetch 工具定义工厂(装载批由装配根经插件注册面挂入工具注册表)。
|
|
20
|
+
* @param service 三消费面共用的 execute 载体(与 ctx.fetch 服务同一实例)
|
|
21
|
+
*/
|
|
22
|
+
export function createFetchTool(service) {
|
|
23
|
+
return {
|
|
24
|
+
name: 'fetch',
|
|
25
|
+
description: '发起 HTTP(S) 请求取回文本响应(GET 缺省;可 POST/PUT/PATCH/DELETE/HEAD)。' +
|
|
26
|
+
'自动跟随重定向(上限 5 跳);响应体上限 1 MiB、超限截断并在回执标注;' +
|
|
27
|
+
'仅接受 UTF-8 文本(二进制/非 UTF-8 拒)。内网地址(私网/环回段)被' +
|
|
28
|
+
'安全卫生件拒绝。回执含最终 URL、状态码、Content-Type 与字节数。',
|
|
29
|
+
parameters: Type.Object({
|
|
30
|
+
url: Type.String({ description: '完整 URL(须含 http:// 或 https:// 协议)' }),
|
|
31
|
+
method: Type.Optional(Type.Union(TOOL_METHODS.map((m) => Type.Literal(m)))),
|
|
32
|
+
headers: Type.Optional(Type.Record(Type.String(), Type.String(), { description: '请求头(可选)' })),
|
|
33
|
+
body: Type.Optional(Type.String({ description: '请求体(POST/PUT/PATCH 用;字符串形)' })),
|
|
34
|
+
}, { additionalProperties: false }),
|
|
35
|
+
effect: 'read',
|
|
36
|
+
execute: async (args, toolCtx) => {
|
|
37
|
+
// 管道已按 schema 校验;此处窄化取参(缺省腿全在场判定)
|
|
38
|
+
const url = String(args.url);
|
|
39
|
+
const method = typeof args.method === 'string' ? args.method : 'GET';
|
|
40
|
+
const headers = args.headers && typeof args.headers === 'object' && !Array.isArray(args.headers)
|
|
41
|
+
? Object.fromEntries(Object.entries(args.headers).filter((entry) => typeof entry[1] === 'string'))
|
|
42
|
+
: undefined;
|
|
43
|
+
const body = typeof args.body === 'string' ? args.body : undefined;
|
|
44
|
+
try {
|
|
45
|
+
const response = await service.fetch(url, {
|
|
46
|
+
method,
|
|
47
|
+
...(headers ? { headers } : {}),
|
|
48
|
+
...(body !== undefined ? { body } : {}),
|
|
49
|
+
...(toolCtx.signal ? { signal: toolCtx.signal } : {}),
|
|
50
|
+
consumer: 'tool',
|
|
51
|
+
});
|
|
52
|
+
// 元信息头 + 分隔线 + 正文(截断记显式披露——模型自知内容不完整)
|
|
53
|
+
const meta = [
|
|
54
|
+
`URL: ${response.finalUrl}`,
|
|
55
|
+
`Status: ${response.status}`,
|
|
56
|
+
`Content-Type: ${response.contentType || '未知'}`,
|
|
57
|
+
`Bytes: ${response.bytes}${response.truncated ? `(超上限截断——原文更长)` : ''}`,
|
|
58
|
+
...(response.redirects > 0 ? [`Redirects: ${response.redirects} 跳(入口 ${response.url})`] : []),
|
|
59
|
+
];
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: 'text', text: `${meta.join('\n')}\n\n---\n\n${response.body}` }],
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// 一切失败编码为 isError 数据面(03 §2.3)——BaseError 携码前置披露
|
|
66
|
+
// (判别收紧:Node 系统错误也带 code 属性,只认注册码体系成员)
|
|
67
|
+
const code = error instanceof BaseError ? error.code : undefined;
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
return {
|
|
70
|
+
content: [{ type: 'text', text: code ? `[${code}] ${message}` : message }],
|
|
71
|
+
isError: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* web 件公开类型(02 §4.1 席 18:ctx.fetch + fetch 工具 + SSRF 五卫生件——
|
|
3
|
+
* 两消费面同一 execute)。
|
|
4
|
+
*
|
|
5
|
+
* 件身份:core: 官方插件 15 之 #6(07 §1.1 core:web 行)——fetch 工具(模型
|
|
6
|
+
* 消费面)/ ctx.fetch 服务(插件消费面)/ browser 导航(第三消费位,03 §10.3
|
|
7
|
+
* 安全卫生条)三面共用同一 execute 路径与同一在飞门实例。装载态集成(apply
|
|
8
|
+
* 注册工具 + provide 服务)归批 12 后装载面;本批纯逻辑腿。
|
|
9
|
+
*/
|
|
10
|
+
export {};
|