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,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-200:oklch(88.5% .062 18.334);--color-red-400:oklch(70.4% .191 22.216);--color-red-700:oklch(50.5% .213 27.518);--color-red-800:oklch(44.4% .177 26.899);--color-red-900:oklch(39.6% .141 25.723);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-900:oklch(37.8% .077 168.94);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-900:oklch(39.1% .09 240.876);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-zinc-950:oklch(14.1% .005 285.823);--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-medium:500;--font-weight-semibold:600;--tracking-wide:.025em;--radius-lg:.5rem;--animate-pulse:pulse 2s cubic-bezier(.4, 0, .6, 1) infinite;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.static{position:static}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mr-16{margin-right:calc(var(--spacing) * 16)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-16{margin-left:calc(var(--spacing) * 16)}.block{display:block}.flex{display:flex}.h-screen{height:100vh}.max-h-40{max-height:calc(var(--spacing) * 40)}.max-h-48{max-height:calc(var(--spacing) * 48)}.min-h-0{min-height:0}.min-h-\[2\.5rem\]{min-height:2.5rem}.w-64{width:calc(var(--spacing) * 64)}.w-72{width:calc(var(--spacing) * 72)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-none{max-width:none}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.animate-pulse{animation:var(--animate-pulse)}.resize-y{resize:vertical}.flex-col{flex-direction:column}.items-center{align-items:center}.items-end{align-items:flex-end}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-zinc-700{border-color:var(--color-zinc-700)}.border-zinc-800{border-color:var(--color-zinc-800)}.bg-amber-900\/60{background-color:#7b330699}@supports (color:color-mix(in lab, red, red)){.bg-amber-900\/60{background-color:color-mix(in oklab, var(--color-amber-900) 60%, transparent)}}.bg-emerald-700\/80{background-color:#007956cc}@supports (color:color-mix(in lab, red, red)){.bg-emerald-700\/80{background-color:color-mix(in oklab, var(--color-emerald-700) 80%, transparent)}}.bg-emerald-900\/60{background-color:#004e3b99}@supports (color:color-mix(in lab, red, red)){.bg-emerald-900\/60{background-color:color-mix(in oklab, var(--color-emerald-900) 60%, transparent)}}.bg-red-800\/80{background-color:#9f0712cc}@supports (color:color-mix(in lab, red, red)){.bg-red-800\/80{background-color:color-mix(in oklab, var(--color-red-800) 80%, transparent)}}.bg-red-900\/60{background-color:#82181a99}@supports (color:color-mix(in lab, red, red)){.bg-red-900\/60{background-color:color-mix(in oklab, var(--color-red-900) 60%, transparent)}}.bg-sky-900\/60{background-color:#024a7099}@supports (color:color-mix(in lab, red, red)){.bg-sky-900\/60{background-color:color-mix(in oklab, var(--color-sky-900) 60%, transparent)}}.bg-zinc-200{background-color:var(--color-zinc-200)}.bg-zinc-800{background-color:var(--color-zinc-800)}.bg-zinc-900{background-color:var(--color-zinc-900)}.bg-zinc-950{background-color:var(--color-zinc-950)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pb-2{padding-bottom:calc(var(--spacing) * 2)}.text-left{text-align:left}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[13px\]{font-size:13px}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-200{color:var(--color-amber-200)}.text-emerald-200{color:var(--color-emerald-200)}.text-red-200{color:var(--color-red-200)}.text-red-400{color:var(--color-red-400)}.text-sky-200{color:var(--color-sky-200)}.text-white{color:var(--color-white)}.text-zinc-100{color:var(--color-zinc-100)}.text-zinc-200{color:var(--color-zinc-200)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-900{color:var(--color-zinc-900)}.uppercase{text-transform:uppercase}.opacity-60{opacity:.6}.outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:bg-emerald-600:hover{background-color:var(--color-emerald-600)}.hover\:bg-red-700:hover{background-color:var(--color-red-700)}.hover\:bg-white:hover{background-color:var(--color-white)}.hover\:bg-zinc-700:hover{background-color:var(--color-zinc-700)}.hover\:bg-zinc-800:hover{background-color:var(--color-zinc-800)}.hover\:bg-zinc-900:hover{background-color:var(--color-zinc-900)}}.focus\:border-zinc-500:focus{border-color:var(--color-zinc-500)}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}.\[\&_code\]\:rounded code{border-radius:.25rem}.\[\&_code\]\:bg-zinc-800 code{background-color:var(--color-zinc-800)}.\[\&_code\]\:px-1 code{padding-inline:var(--spacing)}.\[\&_pre\]\:rounded pre{border-radius:.25rem}.\[\&_pre\]\:bg-zinc-950 pre{background-color:var(--color-zinc-950)}.\[\&_pre\]\:p-2 pre{padding:calc(var(--spacing) * 2)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@keyframes pulse{50%{opacity:.5}}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta name="color-scheme" content="dark" />
|
|
7
|
+
<title>berry-agent</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-B-t2O8gh.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DOlgSZ9Q.css">
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
<div id="root"></div>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* webui 模块公开面(L4 core: 官方插件 15 之 #13——02 §4.1 席 25 / 03
|
|
3
|
+
* §10.4 webui 件章 + 批 18a-2' 改形注记:SSE + 微路由 + 跨入口审批,
|
|
4
|
+
* 承载位改注册 sdk 路由扩展位——零自持监听)。
|
|
5
|
+
*
|
|
6
|
+
* 批 18a-1 服务端核心 → 18a-2' 改形:backend(UiBackend 第四实装——claim
|
|
7
|
+
* 桥)+ 微路由五撮 + SSE 信封三族全维持,防线/token/体帽/SSE 基建归面级
|
|
8
|
+
* 单源;本面出 mountWebui(路由族注册形)。SPA 客户端腿(dist/webui/
|
|
9
|
+
* 静态面)已落(6881b38);host `--port` 归一已落(18a-3' 三入口咬合——
|
|
10
|
+
* TUI/serve/daemon 共用统一 HTTP 面端口)。
|
|
11
|
+
*/
|
|
12
|
+
import './types.js';
|
|
13
|
+
export { WEBUI_DEFAULT_PORT, WEBUI_DEFAULT_HOST, WEBUI_MAX_CONNECTIONS, WEBUI_BODY_LIMIT_BYTES, WEBUI_COOKIE_NAME, WEBUI_ENDPOINTS, } from './types.js';
|
|
14
|
+
export { mountWebui } from './server.js';
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* webui/server — Web 通道路由实装(03 §10.4 批 18a-2' 改形;批 18a-1 传输
|
|
3
|
+
* 实装 → 承载位改注册)。
|
|
4
|
+
*
|
|
5
|
+
* **承载位改注册(18a-2'——03 §10.4 改形注记 + §10.6 路由扩展位段)**:
|
|
6
|
+
* 件零自持 node:http 监听——mountWebui(deps) 把五撮 13 端点 + SPA fallback
|
|
7
|
+
* 逐条注册进注入的注册器(sdk 面注册器结构兼容)。归面级的四块(原自持
|
|
8
|
+
* 已删):三防线(Host 白名单/Origin 硬防线——面级先行适用于一切路由,仅
|
|
9
|
+
* TCP)/ token 鉴权执法(token-or-cookie 档 Bearer ∪ cookie 双通道、恒时
|
|
10
|
+
* 比对——面 token 唯一验换位 ctx.verifyToken)/ POST 体帽与排空纪律
|
|
11
|
+
* (ctx.readBody——超帽 413 永不早于体收完)/ SSE 单流基建(帧形/ping/
|
|
12
|
+
* 看门狗/shedding——ctx.openSse 注入件侧可丢判据)。
|
|
13
|
+
*
|
|
14
|
+
* 件内维持位(§10.4 改形注②-⑦语义全保):
|
|
15
|
+
* - **backend**:UiBackend 第四实装(claim 桥)。审批腿**进程作用域语义**
|
|
16
|
+
* (定形注④——与 SDK 腿「连接作用域无订阅即 cancel」分立):开面在场即
|
|
17
|
+
* 持应答能力、不采无连接即时 cancel;败腿/撤销经 signal abort 清槽
|
|
18
|
+
* (abort 恒后于竞速落定——UiCore finish 内序,不构成抢答)。
|
|
19
|
+
* - **SSE 信封三族**:分档判据注②(终结型两型 + asked 镜像 → session 族,
|
|
20
|
+
* 其余活体 → display 族);连接即当下(v1 无重放游标——正确性层 = 客户端
|
|
21
|
+
* onopen 恒重拉投影);全局连接帽 16 件侧自记账(面级 openStreams 不暴露
|
|
22
|
+
* 计数)超帽 503;背压 shedding 判据 = display 的 update 两型(session/
|
|
23
|
+
* notify/status 镜像帧不弃)。
|
|
24
|
+
* - **微路由语义**:会话存在性分账(missing submit/events 404 not_found /
|
|
25
|
+
* closed submit 404 closed;messages/todo 不受闭态拦;closed events 放行
|
|
26
|
+
* 空流);cookie 桥注③(体 {token} 经面级 verifyToken 验换 → Set-Cookie
|
|
27
|
+
* HttpOnly SameSite=Strict——EventSource 无头位,浏览器侧唯一凭证通道);
|
|
28
|
+
* SPA 静态位注⑦(路径穿越防线 + 未知深路径 fallback index.html;缺席
|
|
29
|
+
* API-only 404 no_spa);JSON 均 typebox 校验后消费。
|
|
30
|
+
* - **decide 只 resolve pending resolver**(绝不直接写 durable——decided
|
|
31
|
+
* durable 写唯一保留在 ask() 汇流点;unknown/已决 → superseded 幂等回执)。
|
|
32
|
+
*
|
|
33
|
+
* 收场语义:detach() = 全路由摘除 + 全流收口 + 审批清槽(**丢弃性结算——
|
|
34
|
+
* 不 resolve**:未决条目不凭空造值抢答;败腿 promise 悬挂由核 finish/队列
|
|
35
|
+
* 收口吸收)——行回卷语义与原 stop() 同律;监听关停归面(宿主 face.stop)。
|
|
36
|
+
*/
|
|
37
|
+
import { createReadStream } from 'node:fs';
|
|
38
|
+
import { stat } from 'node:fs/promises';
|
|
39
|
+
import { join, resolve, sep } from 'node:path';
|
|
40
|
+
import { Type } from 'typebox';
|
|
41
|
+
import { Value } from 'typebox/value';
|
|
42
|
+
import { WEBUI_BODY_LIMIT_BYTES, WEBUI_COOKIE_NAME, WEBUI_ENDPOINTS, WEBUI_MAX_CONNECTIONS, } from './types.js';
|
|
43
|
+
/* ---------------- typebox 校验(03 §10.4「JSON 均 typebox 校验后消费」) ---------------- */
|
|
44
|
+
/** 公共字段词面:未知字段拒收(收窄律——请求面载荷即全集) */
|
|
45
|
+
const strict = { additionalProperties: false };
|
|
46
|
+
/** auth 体(cookie 桥——token 必填) */
|
|
47
|
+
const AuthSchema = Type.Object({ token: Type.String() }, strict);
|
|
48
|
+
/** submit 体(text 必填;messageId 选填 = SPA 重试幂等位,缺席服务端生成) */
|
|
49
|
+
const SubmitSchema = Type.Object({ text: Type.String(), messageId: Type.Optional(Type.String()) }, strict);
|
|
50
|
+
/** decide 体(answer 四值闭集 = ApprovalAskAnswer;note 选填) */
|
|
51
|
+
const DecideSchema = Type.Object({
|
|
52
|
+
answer: Type.Union([
|
|
53
|
+
Type.Literal('approve'),
|
|
54
|
+
Type.Literal('reject'),
|
|
55
|
+
Type.Literal('cancel'),
|
|
56
|
+
Type.Literal('always'),
|
|
57
|
+
]),
|
|
58
|
+
note: Type.Optional(Type.String()),
|
|
59
|
+
}, strict);
|
|
60
|
+
/** 深校验非抛型(fail-loud 可行动报因——首错定位) */
|
|
61
|
+
function validate(schema, value, what) {
|
|
62
|
+
if (!Value.Check(schema, value)) {
|
|
63
|
+
const errors = [...Value.Errors(schema, value)];
|
|
64
|
+
const first = errors[0];
|
|
65
|
+
const at = first === undefined ? '' : `(${first.instancePath || '(root)'}:${first.message})`;
|
|
66
|
+
return { ok: false, reason: `${what} 不合 schema${at}` };
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, value: value };
|
|
69
|
+
}
|
|
70
|
+
/* ---------------- 应答与解析 ---------------- */
|
|
71
|
+
/** JSON 应答(content-type 单源) */
|
|
72
|
+
function sendJson(res, status, body) {
|
|
73
|
+
if (res.writableEnded)
|
|
74
|
+
return;
|
|
75
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
76
|
+
res.end(JSON.stringify(body));
|
|
77
|
+
}
|
|
78
|
+
/** 错误应答(machine word + 人读消息——SPA 面无错误码族,HTTP 状态即词面) */
|
|
79
|
+
function sendError(res, status, error, message) {
|
|
80
|
+
sendJson(res, status, { error, ...(message !== undefined ? { message } : {}) });
|
|
81
|
+
}
|
|
82
|
+
/** 解析 JSON 体(坏形 → 可行动报因) */
|
|
83
|
+
function parseJson(raw) {
|
|
84
|
+
try {
|
|
85
|
+
return { ok: true, value: JSON.parse(raw) };
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return { ok: false, reason: '请求体非 JSON' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** query 解析(ctx 窄面无 query 位——handler 侧自 req.url 取;基座仅为相对形合法) */
|
|
92
|
+
function queryOf(req) {
|
|
93
|
+
return new URL(req.url ?? '/', 'http://webui.internal').searchParams;
|
|
94
|
+
}
|
|
95
|
+
/** 判信封是否纯活体可丢(背压 shedding 面——display 族的 update 两型;session
|
|
96
|
+
* 镜像/notify/status 不弃) */
|
|
97
|
+
function isDroppableEnvelope(env) {
|
|
98
|
+
return (env.kind === 'display' && (env.payload.type === 'message_update' || env.payload.type === 'tool_execution_update'));
|
|
99
|
+
}
|
|
100
|
+
/* ---------------- 静态面 ---------------- */
|
|
101
|
+
/** 静态面内容型表(扩展名 → content-type;缺席 application/octet-stream) */
|
|
102
|
+
const CONTENT_TYPES = {
|
|
103
|
+
'.html': 'text/html; charset=utf-8',
|
|
104
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
105
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
106
|
+
'.css': 'text/css; charset=utf-8',
|
|
107
|
+
'.svg': 'image/svg+xml',
|
|
108
|
+
'.png': 'image/png',
|
|
109
|
+
'.ico': 'image/x-icon',
|
|
110
|
+
'.json': 'application/json; charset=utf-8',
|
|
111
|
+
'.txt': 'text/plain; charset=utf-8',
|
|
112
|
+
'.map': 'application/json',
|
|
113
|
+
'.woff2': 'font/woff2',
|
|
114
|
+
'.webmanifest': 'application/manifest+json',
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* 造 webui 路由件并注册进注入的注册器(承载位改注册——零自持监听)。
|
|
118
|
+
*
|
|
119
|
+
* @param deps 装配根闭包注入窄面 + 路由注册器(sdk 面注册器结构兼容直传)
|
|
120
|
+
* @param options 件侧帽参(SSE 连接帽 / POST 体帽——测试确定性注入位)
|
|
121
|
+
*/
|
|
122
|
+
export function mountWebui(deps, options = {}) {
|
|
123
|
+
const maxConnections = options.maxConnections ?? WEBUI_MAX_CONNECTIONS;
|
|
124
|
+
const bodyLimitBytes = options.bodyLimitBytes ?? WEBUI_BODY_LIMIT_BYTES;
|
|
125
|
+
const warn = options.warn ?? (() => { });
|
|
126
|
+
/** 活体流注册表(全局帽计数 + 按会话扇出索引) */
|
|
127
|
+
const streams = new Set();
|
|
128
|
+
const bySession = new Map();
|
|
129
|
+
/** 未决审批账(approvalId → 账项——decide 幂等判据 + 清单投影) */
|
|
130
|
+
const pending = new Map();
|
|
131
|
+
/** 后端内 ask 计数(调用方未指派 approvalId 时生成 `webui-N`) */
|
|
132
|
+
let askSeq = 0;
|
|
133
|
+
/** 件内 submit 计数(SPA 未携 messageId 时生成 `webui-N`——无幂等) */
|
|
134
|
+
let submitSeq = 0;
|
|
135
|
+
/** 流退订(终结回调——双索引摘除;挂点位 = res close〔连接 close/看门狗/
|
|
136
|
+
* 面收场三路共用的真连接终结信号〕,与面级流账自摘并存不冲突) */
|
|
137
|
+
const deregister = (entry) => {
|
|
138
|
+
streams.delete(entry);
|
|
139
|
+
const set = bySession.get(entry.sessionId);
|
|
140
|
+
if (set === undefined)
|
|
141
|
+
return;
|
|
142
|
+
set.delete(entry);
|
|
143
|
+
if (set.size === 0)
|
|
144
|
+
bySession.delete(entry.sessionId);
|
|
145
|
+
};
|
|
146
|
+
/** 会话路由扇出(display/session/status 三族——按订阅索引) */
|
|
147
|
+
const pushToSession = (sessionId, env) => {
|
|
148
|
+
const set = bySession.get(sessionId);
|
|
149
|
+
if (set === undefined)
|
|
150
|
+
return;
|
|
151
|
+
for (const entry of set)
|
|
152
|
+
entry.stream.write(env);
|
|
153
|
+
};
|
|
154
|
+
/** notify 广播(非阻塞原语不分会话呈现位——全流扇出) */
|
|
155
|
+
const broadcast = (env) => {
|
|
156
|
+
for (const entry of streams)
|
|
157
|
+
entry.stream.write(env);
|
|
158
|
+
};
|
|
159
|
+
/* ---- UiBackend 第四实装(claim 桥——通道核 addBackend 注册) ---- */
|
|
160
|
+
const backend = {
|
|
161
|
+
id: 'webui',
|
|
162
|
+
// 三位能力面对应信封三族(notify·status·approval);confirm/select/input
|
|
163
|
+
// 缺席(微路由 v1 无应答端点——降级判定归核);setWidget 是 TUI 呈现概念
|
|
164
|
+
capabilities: {
|
|
165
|
+
notify: true,
|
|
166
|
+
confirm: false,
|
|
167
|
+
select: false,
|
|
168
|
+
input: false,
|
|
169
|
+
approval: true,
|
|
170
|
+
setStatus: true,
|
|
171
|
+
setWidget: false,
|
|
172
|
+
},
|
|
173
|
+
// 观众探针:在线 SSE 连接数(零连接 = 无人接帧 = 无观众)
|
|
174
|
+
hasAudience: () => streams.size > 0,
|
|
175
|
+
notify: (message, opts) => {
|
|
176
|
+
broadcast({
|
|
177
|
+
kind: 'notify',
|
|
178
|
+
payload: { message, ...(opts?.level !== undefined ? { level: opts.level } : {}) },
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
// 状态行更新(last-writer-wins 天然——多写者扇出覆盖)
|
|
182
|
+
setStatus: (sessionId, status) => {
|
|
183
|
+
pushToSession(sessionId, { kind: 'status', sessionId, payload: { status } });
|
|
184
|
+
},
|
|
185
|
+
// 活体信封呈现(focused 位无 webui 语义——SPA 自选视图,无聚焦降档)
|
|
186
|
+
onEnvelope: (env) => {
|
|
187
|
+
// 分档判据(批 18a 定形注②):终结型两型落 durable → session 镜像族;
|
|
188
|
+
// 其余活体 → display 族
|
|
189
|
+
const terminal = env.event.type === 'message_end' || env.event.type === 'tool_execution_end';
|
|
190
|
+
pushToSession(env.sessionId, terminal
|
|
191
|
+
? { kind: 'session', sessionId: env.sessionId, payload: env.event }
|
|
192
|
+
: { kind: 'display', sessionId: env.sessionId, payload: env.event });
|
|
193
|
+
},
|
|
194
|
+
askApproval: (sessionId, request, opts) => new Promise((resolve) => {
|
|
195
|
+
// 进程作用域语义(定形注④):不采无连接即时 cancel——开面在场即持
|
|
196
|
+
// 应答能力,浏览器迟到也可应答;纯 webui 无浏览器时 ask 挂起至
|
|
197
|
+
// run 打断/会话收口(核保守值兜底)。
|
|
198
|
+
const approvalId = request.approvalId ?? `webui-${++askSeq}`;
|
|
199
|
+
const entry = {
|
|
200
|
+
sessionId,
|
|
201
|
+
summary: request.summary,
|
|
202
|
+
...(request.reason !== undefined ? { reason: request.reason } : {}),
|
|
203
|
+
...(request.toolName !== undefined ? { toolName: request.toolName } : {}),
|
|
204
|
+
...(request.suggestedEntry !== undefined ? { suggestedEntry: request.suggestedEntry } : {}),
|
|
205
|
+
settled: false,
|
|
206
|
+
resolve,
|
|
207
|
+
};
|
|
208
|
+
pending.set(approvalId, entry);
|
|
209
|
+
// asked 镜像外推(session 族承载——零新词汇:payload 形复用 durable
|
|
210
|
+
// approval/asked 词汇;可见性半边经此镜像,无第二套 ask 帧词面;
|
|
211
|
+
// approvalId 显式置尾防 request 内 undefined 值覆写生成位)
|
|
212
|
+
pushToSession(sessionId, {
|
|
213
|
+
kind: 'session',
|
|
214
|
+
sessionId,
|
|
215
|
+
payload: { type: 'approval/asked', ...request, approvalId },
|
|
216
|
+
});
|
|
217
|
+
// 败腿撤销传播位(UiCore 竞速先答后 abort——abort 恒后于竞速落定,
|
|
218
|
+
// 本 resolve 是无害清槽不构成抢答);已决后迟到 abort 是 no-op
|
|
219
|
+
opts?.signal?.addEventListener('abort', () => {
|
|
220
|
+
const e = pending.get(approvalId);
|
|
221
|
+
if (e === undefined || e.settled)
|
|
222
|
+
return;
|
|
223
|
+
e.settled = true;
|
|
224
|
+
pending.delete(approvalId);
|
|
225
|
+
resolve('cancel');
|
|
226
|
+
}, { once: true });
|
|
227
|
+
}),
|
|
228
|
+
};
|
|
229
|
+
/** decide 应答(跨入口竞速回执——只 resolve pending resolver,绝不直接写
|
|
230
|
+
* durable:decided durable 写唯一保留在 ask() 汇流点;unknown/已决 →
|
|
231
|
+
* superseded 幂等回执) */
|
|
232
|
+
const decideApproval = (approvalId, answer) => {
|
|
233
|
+
const entry = pending.get(approvalId);
|
|
234
|
+
if (entry === undefined || entry.settled)
|
|
235
|
+
return 'superseded';
|
|
236
|
+
entry.settled = true;
|
|
237
|
+
pending.delete(approvalId);
|
|
238
|
+
entry.resolve(answer);
|
|
239
|
+
return 'applied';
|
|
240
|
+
};
|
|
241
|
+
/* ---- 静态面(SPA 壳——open/static-shell 三位之一;缺席 = API-only 形) ---- */
|
|
242
|
+
async function serveStatic(res, wildcard) {
|
|
243
|
+
if (deps.staticDir === undefined) {
|
|
244
|
+
sendError(res, 404, 'no_spa', 'SPA 静态面未装配(API-only 形态)');
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
const root = resolve(deps.staticDir);
|
|
248
|
+
// wildcard 无前导斜杠(面级匹配器以段拼回)——根路径空串归 index.html
|
|
249
|
+
let rel = wildcard === '' ? 'index.html' : decodeURIComponent(wildcard);
|
|
250
|
+
let target = resolve(join(root, rel));
|
|
251
|
+
// 路径穿越防线:归一后须仍在根内(.. 段与编码形出根即拒)
|
|
252
|
+
if (target !== root && !target.startsWith(root + sep)) {
|
|
253
|
+
sendError(res, 403, 'forbidden', '路径出静态面根');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const s = await stat(target);
|
|
258
|
+
if (!s.isFile())
|
|
259
|
+
throw new Error('not file');
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// SPA fallback 路由:未知/非文件路径回 index.html(前端路由接管)
|
|
263
|
+
rel = 'index.html';
|
|
264
|
+
target = join(root, rel);
|
|
265
|
+
try {
|
|
266
|
+
if (!(await stat(target)).isFile())
|
|
267
|
+
throw new Error('no index');
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
sendError(res, 404, 'no_spa', '静态面缺 index.html');
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const dot = target.lastIndexOf('.');
|
|
275
|
+
const ext = dot === -1 ? '' : target.slice(dot).toLowerCase();
|
|
276
|
+
res.writeHead(200, { 'content-type': CONTENT_TYPES[ext] ?? 'application/octet-stream' });
|
|
277
|
+
createReadStream(target)
|
|
278
|
+
.on('error', () => res.destroy())
|
|
279
|
+
.pipe(res);
|
|
280
|
+
}
|
|
281
|
+
/* ---- 路由族注册(五撮 13 端点 + /api 兜底 + SPA fallback——全族 loopbackOnly) ---- */
|
|
282
|
+
/** API 族鉴权档(Bearer ∪ cookie 双通道——cookie 名单源) */
|
|
283
|
+
const tokenOrCookie = { mode: 'token-or-cookie', cookie: WEBUI_COOKIE_NAME };
|
|
284
|
+
/** 摘除 fn 账(detach 全路由摘除用) */
|
|
285
|
+
const detachers = [];
|
|
286
|
+
/** 注册速记(loopbackOnly 全族恒真——07 E1 恒回环可达) */
|
|
287
|
+
const add = (descriptor) => {
|
|
288
|
+
detachers.push(deps.register({ loopbackOnly: true, ...descriptor }));
|
|
289
|
+
};
|
|
290
|
+
// —— 探活(open/liveness——只回 ok 零敏感面)——
|
|
291
|
+
add({
|
|
292
|
+
method: 'GET',
|
|
293
|
+
path: WEBUI_ENDPOINTS.health,
|
|
294
|
+
auth: { mode: 'open', purpose: 'liveness' },
|
|
295
|
+
handler: (_req, res) => sendJson(res, 200, { ok: true }),
|
|
296
|
+
});
|
|
297
|
+
// —— auth cookie 桥(open/auth-exchange——本位即鉴权:体 token 经面级
|
|
298
|
+
// verifyToken 验换,落定 Set-Cookie;cookie 值 = 验换通过的那枚 token)——
|
|
299
|
+
add({
|
|
300
|
+
method: 'POST',
|
|
301
|
+
path: WEBUI_ENDPOINTS.auth,
|
|
302
|
+
auth: { mode: 'open', purpose: 'auth-exchange' },
|
|
303
|
+
bodyLimitBytes,
|
|
304
|
+
handler: async (req, res, ctx) => {
|
|
305
|
+
const body = await ctx.readBody(req);
|
|
306
|
+
if (!body.ok)
|
|
307
|
+
return sendError(res, body.status, 'too_large', body.message);
|
|
308
|
+
const parsed = parseJson(body.body);
|
|
309
|
+
if (!parsed.ok)
|
|
310
|
+
return sendError(res, 400, 'bad_request', parsed.reason);
|
|
311
|
+
const auth = validate(AuthSchema, parsed.value, 'auth 体');
|
|
312
|
+
if (!auth.ok)
|
|
313
|
+
return sendError(res, 400, 'bad_request', auth.reason);
|
|
314
|
+
if (!ctx.verifyToken(auth.value.token))
|
|
315
|
+
return sendError(res, 401, 'unauthorized', 'token 不符');
|
|
316
|
+
// cookie 桥落定(HttpOnly + SameSite=Strict——EventSource 不能携头,本桥
|
|
317
|
+
// 是浏览器侧唯一凭证通道;Path=/ 覆盖全 API 面;token-or-cookie 档由面
|
|
318
|
+
// 级用同一 token 验 cookie——闭环成立)
|
|
319
|
+
res.writeHead(204, {
|
|
320
|
+
'set-cookie': `${WEBUI_COOKIE_NAME}=${auth.value.token}; HttpOnly; SameSite=Strict; Path=/`,
|
|
321
|
+
});
|
|
322
|
+
res.end();
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
// —— 会话族根:GET 清单 / POST 开新 ——
|
|
326
|
+
add({
|
|
327
|
+
method: 'GET',
|
|
328
|
+
path: WEBUI_ENDPOINTS.sessions,
|
|
329
|
+
auth: tokenOrCookie,
|
|
330
|
+
handler: (_req, res) => sendJson(res, 200, { sessions: deps.sessions.listSessions() }),
|
|
331
|
+
});
|
|
332
|
+
add({
|
|
333
|
+
method: 'POST',
|
|
334
|
+
path: WEBUI_ENDPOINTS.sessions,
|
|
335
|
+
auth: tokenOrCookie,
|
|
336
|
+
handler: (_req, res) => sendJson(res, 200, { sessionId: deps.sessions.createSession() }),
|
|
337
|
+
});
|
|
338
|
+
// —— 会话族子路由(存在性分账注⑥:messages/todo 不受闭态拦;events 前
|
|
339
|
+
// missing 拒、closed 放行空流;submit/interrupt 受闭态拦)——
|
|
340
|
+
add({
|
|
341
|
+
method: 'GET',
|
|
342
|
+
path: WEBUI_ENDPOINTS.sessionMessages,
|
|
343
|
+
auth: tokenOrCookie,
|
|
344
|
+
handler: async (_req, res, ctx) => {
|
|
345
|
+
// 近史投影兜底可拉(closed 会话同样可拉——只读腿)
|
|
346
|
+
const messages = await deps.read.fetchMessages(ctx.params.id);
|
|
347
|
+
sendJson(res, 200, { messages });
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
add({
|
|
351
|
+
method: 'GET',
|
|
352
|
+
path: WEBUI_ENDPOINTS.sessionTodo,
|
|
353
|
+
auth: tokenOrCookie,
|
|
354
|
+
handler: (_req, res, ctx) => {
|
|
355
|
+
const items = deps.read.todoOf?.(ctx.params.id);
|
|
356
|
+
sendJson(res, 200, { items: items ?? null }); // null = 无数据源(诚实不虚报)
|
|
357
|
+
},
|
|
358
|
+
});
|
|
359
|
+
add({
|
|
360
|
+
method: 'GET',
|
|
361
|
+
path: WEBUI_ENDPOINTS.sessionEvents,
|
|
362
|
+
auth: tokenOrCookie,
|
|
363
|
+
handler: (_req, res, ctx) => {
|
|
364
|
+
const sessionId = ctx.params.id;
|
|
365
|
+
if (deps.sessions.sessionStateOf(sessionId) === 'missing') {
|
|
366
|
+
sendError(res, 404, 'not_found', '会话缺席');
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
// closed 会话放行(空流形——近史走 messages 兜底,流恒静默无假帧)
|
|
370
|
+
if (streams.size >= maxConnections) {
|
|
371
|
+
sendError(res, 503, 'overloaded', `SSE 连接帽(${maxConnections})已满`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
// 面级开流(帧形/ping/看门狗面单源;shedding 判据 = display 的 update
|
|
375
|
+
// 两型可丢——unknown 域收敛转型 = 本面流载荷恒 WebuiEnvelope 的件内不变式)
|
|
376
|
+
const stream = ctx.openSse(res, {
|
|
377
|
+
droppable: (data) => isDroppableEnvelope(data),
|
|
378
|
+
});
|
|
379
|
+
const entry = { stream, sessionId };
|
|
380
|
+
streams.add(entry);
|
|
381
|
+
const set = bySession.get(sessionId) ?? new Set();
|
|
382
|
+
set.add(entry);
|
|
383
|
+
bySession.set(sessionId, set);
|
|
384
|
+
// 件侧销账位 = res close(连接 close/面级看门狗 destroy/宿主收场三路
|
|
385
|
+
// 共用的真连接终结信号——与面级流账自摘并存)
|
|
386
|
+
res.on('close', () => deregister(entry));
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
add({
|
|
390
|
+
method: 'POST',
|
|
391
|
+
path: WEBUI_ENDPOINTS.sessionSubmit,
|
|
392
|
+
auth: tokenOrCookie,
|
|
393
|
+
bodyLimitBytes,
|
|
394
|
+
handler: async (req, res, ctx) => {
|
|
395
|
+
const sessionId = ctx.params.id;
|
|
396
|
+
const state = deps.sessions.sessionStateOf(sessionId);
|
|
397
|
+
if (state === 'missing')
|
|
398
|
+
return sendError(res, 404, 'not_found', '会话缺席');
|
|
399
|
+
if (state === 'closed')
|
|
400
|
+
return sendError(res, 404, 'closed', '会话已闭(只读兜底)');
|
|
401
|
+
const body = await ctx.readBody(req);
|
|
402
|
+
if (!body.ok)
|
|
403
|
+
return sendError(res, body.status, 'too_large', body.message);
|
|
404
|
+
const parsed = parseJson(body.body);
|
|
405
|
+
if (!parsed.ok)
|
|
406
|
+
return sendError(res, 400, 'bad_request', parsed.reason);
|
|
407
|
+
const submit = validate(SubmitSchema, parsed.value, 'submit 体');
|
|
408
|
+
if (!submit.ok)
|
|
409
|
+
return sendError(res, 400, 'bad_request', submit.reason);
|
|
410
|
+
const outcome = deps.sessions.submitPrompt({
|
|
411
|
+
sessionId,
|
|
412
|
+
content: submit.value.text,
|
|
413
|
+
messageId: submit.value.messageId ?? `webui-${++submitSeq}`,
|
|
414
|
+
});
|
|
415
|
+
sendJson(res, 200, { sessionId: outcome.sessionId });
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
add({
|
|
419
|
+
method: 'POST',
|
|
420
|
+
path: WEBUI_ENDPOINTS.sessionInterrupt,
|
|
421
|
+
auth: tokenOrCookie,
|
|
422
|
+
handler: (_req, res, ctx) => {
|
|
423
|
+
const sessionId = ctx.params.id;
|
|
424
|
+
const state = deps.sessions.sessionStateOf(sessionId);
|
|
425
|
+
if (state === 'missing')
|
|
426
|
+
return sendError(res, 404, 'not_found', '会话缺席');
|
|
427
|
+
if (state === 'closed')
|
|
428
|
+
return sendError(res, 404, 'closed', '会话已闭(只读兜底)');
|
|
429
|
+
deps.sessions.interruptSession(sessionId);
|
|
430
|
+
res.writeHead(204).end();
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
// —— 审批族 ——
|
|
434
|
+
add({
|
|
435
|
+
method: 'GET',
|
|
436
|
+
path: WEBUI_ENDPOINTS.approvals,
|
|
437
|
+
auth: tokenOrCookie,
|
|
438
|
+
handler: (req, res) => {
|
|
439
|
+
const filter = queryOf(req).get('sessionId');
|
|
440
|
+
const list = [];
|
|
441
|
+
for (const [approvalId, entry] of pending) {
|
|
442
|
+
if (entry.settled || (filter !== null && entry.sessionId !== filter))
|
|
443
|
+
continue;
|
|
444
|
+
list.push({
|
|
445
|
+
approvalId,
|
|
446
|
+
sessionId: entry.sessionId,
|
|
447
|
+
summary: entry.summary,
|
|
448
|
+
...(entry.reason !== undefined ? { reason: entry.reason } : {}),
|
|
449
|
+
...(entry.toolName !== undefined ? { toolName: entry.toolName } : {}),
|
|
450
|
+
...(entry.suggestedEntry !== undefined ? { suggestedEntry: entry.suggestedEntry } : {}),
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
sendJson(res, 200, { approvals: list });
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
add({
|
|
457
|
+
method: 'POST',
|
|
458
|
+
path: WEBUI_ENDPOINTS.approvalsDecide,
|
|
459
|
+
auth: tokenOrCookie,
|
|
460
|
+
bodyLimitBytes,
|
|
461
|
+
handler: async (req, res, ctx) => {
|
|
462
|
+
const body = await ctx.readBody(req);
|
|
463
|
+
if (!body.ok)
|
|
464
|
+
return sendError(res, body.status, 'too_large', body.message);
|
|
465
|
+
const parsed = parseJson(body.body);
|
|
466
|
+
if (!parsed.ok)
|
|
467
|
+
return sendError(res, 400, 'bad_request', parsed.reason);
|
|
468
|
+
const decide = validate(DecideSchema, parsed.value, 'decide 体');
|
|
469
|
+
if (!decide.ok)
|
|
470
|
+
return sendError(res, 400, 'bad_request', decide.reason);
|
|
471
|
+
// 只 resolve pending resolver(绝不直接写 durable——decided durable 写
|
|
472
|
+
// 唯一保留在 ask() 汇流点;后到/未知 = superseded 幂等回执)
|
|
473
|
+
const outcome = decideApproval(ctx.params.approvalId, decide.value.answer);
|
|
474
|
+
sendJson(res, 200, { outcome });
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
// —— 补全族(两段——注入面缺席诚实空)——
|
|
478
|
+
add({
|
|
479
|
+
method: 'GET',
|
|
480
|
+
path: WEBUI_ENDPOINTS.workspaceFiles,
|
|
481
|
+
auth: tokenOrCookie,
|
|
482
|
+
handler: (req, res) => {
|
|
483
|
+
const q = queryOf(req).get('q') ?? '';
|
|
484
|
+
sendJson(res, 200, { items: deps.completion?.workspaceFiles?.(q) ?? [] });
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
add({
|
|
488
|
+
method: 'GET',
|
|
489
|
+
path: WEBUI_ENDPOINTS.workspaceSymbols,
|
|
490
|
+
auth: tokenOrCookie,
|
|
491
|
+
handler: (req, res) => {
|
|
492
|
+
const q = queryOf(req).get('q') ?? '';
|
|
493
|
+
sendJson(res, 200, { items: deps.completion?.workspaceSymbols?.(q) ?? [] });
|
|
494
|
+
},
|
|
495
|
+
});
|
|
496
|
+
// —— /api 兜底(鉴权先行语义维持:未知 /api 路径过鉴权门后 404——防 SPA
|
|
497
|
+
// fallback 吞程序面打错路径;GET/POST 双形,其余 method 落面级 404)——
|
|
498
|
+
add({
|
|
499
|
+
method: 'GET',
|
|
500
|
+
path: '/api/*',
|
|
501
|
+
auth: tokenOrCookie,
|
|
502
|
+
handler: (_req, res) => sendError(res, 404, 'not_found', '未知 API 路由'),
|
|
503
|
+
});
|
|
504
|
+
add({
|
|
505
|
+
method: 'POST',
|
|
506
|
+
path: '/api/*',
|
|
507
|
+
auth: tokenOrCookie,
|
|
508
|
+
handler: (_req, res) => sendError(res, 404, 'not_found', '未知 API 路由'),
|
|
509
|
+
});
|
|
510
|
+
// —— SPA 静态面(open/static-shell——壳先于鉴权必须可载,壳无 API 即惰性;
|
|
511
|
+
// GET/HEAD 双形;注册序在 /api/* 之后 = /api 射界先由兜底圈占)——
|
|
512
|
+
add({
|
|
513
|
+
method: 'GET',
|
|
514
|
+
path: '*',
|
|
515
|
+
auth: { mode: 'open', purpose: 'static-shell' },
|
|
516
|
+
handler: (_req, res, ctx) => {
|
|
517
|
+
void serveStatic(res, ctx.wildcard ?? '').catch((err) => {
|
|
518
|
+
warn(`webui 静态面处理异常:${err instanceof Error ? err.message : String(err)}`);
|
|
519
|
+
if (!res.writableEnded)
|
|
520
|
+
sendError(res, 500, 'internal');
|
|
521
|
+
});
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
add({
|
|
525
|
+
method: 'HEAD',
|
|
526
|
+
path: '*',
|
|
527
|
+
auth: { mode: 'open', purpose: 'static-shell' },
|
|
528
|
+
handler: (_req, res, ctx) => {
|
|
529
|
+
void serveStatic(res, ctx.wildcard ?? '').catch((err) => {
|
|
530
|
+
warn(`webui 静态面处理异常:${err instanceof Error ? err.message : String(err)}`);
|
|
531
|
+
if (!res.writableEnded)
|
|
532
|
+
sendError(res, 500, 'internal');
|
|
533
|
+
});
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
return {
|
|
537
|
+
backend,
|
|
538
|
+
detach: () => {
|
|
539
|
+
// 收场序:全路由摘除 → 全流收口(面级流随 res 终结)→ 审批清槽
|
|
540
|
+
// (**丢弃性结算——不 resolve**:未决条目不凭空造值抢答;败腿 promise
|
|
541
|
+
// 悬挂由核 finish/队列收口吸收——行回卷语义)
|
|
542
|
+
for (const detach of detachers.splice(0))
|
|
543
|
+
detach();
|
|
544
|
+
for (const entry of [...streams])
|
|
545
|
+
entry.stream.close();
|
|
546
|
+
pending.clear();
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* ---------------- 常量(缺省值单源——03 §10.4 各条款钉死值) ---------------- */
|
|
2
|
+
/** 缺省端口(`--port <n>` 一次性开面——缺省 7860;装配位消费) */
|
|
3
|
+
export const WEBUI_DEFAULT_PORT = 7860;
|
|
4
|
+
/** 缺省回环绑定地址(恒回环钉死——非回环须配凭证 fail-closed 由面级 judgeListenConfig 执法) */
|
|
5
|
+
export const WEBUI_DEFAULT_HOST = '127.0.0.1';
|
|
6
|
+
/** 全局 SSE 连接帽(超帽新连接 503——件侧自记账:面级 openStreams 不暴露计数) */
|
|
7
|
+
export const WEBUI_MAX_CONNECTIONS = 16;
|
|
8
|
+
/** POST 族字节帽(256KiB——per-route bodyLimitBytes 位;超帽 413 且应答永不早于请求体收完〔面级排空纪律〕) */
|
|
9
|
+
export const WEBUI_BODY_LIMIT_BYTES = 256 * 1024;
|
|
10
|
+
/** auth cookie 桥 cookie 名(HttpOnly SameSite=Strict——EventSource 无头位;token-or-cookie 档位) */
|
|
11
|
+
export const WEBUI_COOKIE_NAME = 'webui_token';
|
|
12
|
+
/** 微路由端点表(五撮——03 §10.4 批 18a 落码定形注①定形;SPA fallback 承载位 = 单 `*` catch-all) */
|
|
13
|
+
export const WEBUI_ENDPOINTS = {
|
|
14
|
+
/** GET——探活(open/liveness;只回 ok 零敏感面) */
|
|
15
|
+
health: '/api/health',
|
|
16
|
+
/** POST——auth cookie 桥(open/auth-exchange;体 {token} → Set-Cookie) */
|
|
17
|
+
auth: '/api/auth',
|
|
18
|
+
/** GET——会话清单 / POST——开新 */
|
|
19
|
+
sessions: '/api/sessions',
|
|
20
|
+
/** GET——投影拉取(近史正文——正确性层真源) */
|
|
21
|
+
sessionMessages: '/api/sessions/:id/messages',
|
|
22
|
+
/** GET SSE——活体流(连接即当下,v1 无重放游标) */
|
|
23
|
+
sessionEvents: '/api/sessions/:id/events',
|
|
24
|
+
/** POST——提交(体 {text, messageId?}) */
|
|
25
|
+
sessionSubmit: '/api/sessions/:id/submit',
|
|
26
|
+
/** POST——打断在飞 run */
|
|
27
|
+
sessionInterrupt: '/api/sessions/:id/interrupt',
|
|
28
|
+
/** GET——todo 数据源(goal 计划态呈现投影) */
|
|
29
|
+
sessionTodo: '/api/sessions/:id/todo',
|
|
30
|
+
/** GET——审批清单(?sessionId= 过滤,缺省全量) */
|
|
31
|
+
approvals: '/api/approvals',
|
|
32
|
+
/** POST——审批应答(体 {answer, note?}——跨入口竞速回执) */
|
|
33
|
+
approvalsDecide: '/api/approvals/:approvalId/decide',
|
|
34
|
+
/** GET——补全族两段之一(?q= 前缀查询) */
|
|
35
|
+
workspaceFiles: '/api/workspace/files',
|
|
36
|
+
/** GET——补全族两段之二(?q= 前缀查询) */
|
|
37
|
+
workspaceSymbols: '/api/workspace/symbols',
|
|
38
|
+
};
|