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,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* host/webui-bridge — `--port` webui 统一 HTTP 面装配桥(批 12f-2c 落、
|
|
3
|
+
* 18a-3' 三入口咬合归一定形;03 §10.4 host 接线义务:`--port <n>` 开面 +
|
|
4
|
+
* token 一次性披露 + 生命周期收口)。18a-2' 起件零自持监听——路由经 sdk
|
|
5
|
+
* HTTP 面承載(mountWebui 注册 face.register——面注册器直注)。
|
|
6
|
+
*
|
|
7
|
+
* **三入口两段形(18a-3' 兑现——12f-2c 期「过渡期桥自持面」挂账注销)**:
|
|
8
|
+
* - mountWebuiOnFace:**共用挂载段**——桥真身映射 + 路由注册进任意 sdk 面 +
|
|
9
|
+
* channels backend 挂接(信封扇出与审批腿接线——12f-2c 期注记「addBackend
|
|
10
|
+
* 归调用方」而调用方缺席 = TUI 形真缺陷〔SSE 死流〕,本段收编修前必红
|
|
11
|
+
* 例锁死)+ detach 摘挂(幂等——closer 归调用方接线);
|
|
12
|
+
* - openWebuiFace:**前台自持面开面**(TUI / serve 前台 `--port` 形)——
|
|
13
|
+
* 自起统一 HTTP 面 TCP 人面(恒回环;SPA + /api/* + /v1/* 三族同面;
|
|
14
|
+
* bridge 走 createServeBridge 零第二套映射)+ 披露两行 + closer 整体
|
|
15
|
+
* 收口;daemon 常驻面 `--port` 侧不起本面——serve-daemon 件内组态人面
|
|
16
|
+
* 监听后走共用挂载段(面归 daemon 单源)。
|
|
17
|
+
*
|
|
18
|
+
* 批 19e 件在场分档:openWebuiFace 挂载改经 mountKit(core:webui 件
|
|
19
|
+
* provide 'webui-face-mount' 的 kit——入口装配根 tryGet 后注入);kit
|
|
20
|
+
* 缺席 = 件禁用/零装载——面仍开(/v1/* 程序调用族在场)而 /api/* 与
|
|
21
|
+
* SPA 404,披露分档诚实不虚报(sdk/webui 两件禁用语义族——03 §10.4)。
|
|
22
|
+
*
|
|
23
|
+
* 职责(装配桥五面):conversation 栈五动词 → WebuiDeps 三窄面映射(词面
|
|
24
|
+
* 独立律的装配侧互证——compat 互证 18a-3' 两方向例锁死,见测试件)+
|
|
25
|
+
* staticDir 探测(dist/webui 共生形;缺席诚实 API-only 不虚报)+ token
|
|
26
|
+
* 一次性披露(stderr 缺省——「监听 ⇒ 鉴权」面本体保证恒在场,令牌只此
|
|
27
|
+
* 一次显示;token 生成归面)+ backend 通道挂接(UiBackend 第四实装 claim
|
|
28
|
+
* 桥——共用挂载段接线)+ stop 进运行时退出序 closer(注册序在
|
|
29
|
+
* plugin-unload 之后、tui-backend 之前——网络面先关再出屏)。
|
|
30
|
+
*
|
|
31
|
+
* 桥映射注记:createSession 走 manager.create 零 I/O(行随首事件落库——
|
|
32
|
+
* 浏览器会话无 cwd 锚,workspaceRoot 缺省全局态);sessionStateOf 三档
|
|
33
|
+
* 判据 = isOpen 内存册(open)∪ 持久 list(closed)∪ 余(missing);
|
|
34
|
+
* submitPrompt 受理门前置(服务端已判 open——isOpen 真则驱动在册,
|
|
35
|
+
* submitText 必达);todoOf 无驱动回 undefined(服务端 `items ?? null`
|
|
36
|
+
* 诚实空)。completion 面 v1 不接(诚实缺席——补全族接线挂账后续批)。
|
|
37
|
+
*/
|
|
38
|
+
import { existsSync } from 'node:fs';
|
|
39
|
+
import { dirname, join } from 'node:path';
|
|
40
|
+
import { fileURLToPath } from 'node:url';
|
|
41
|
+
import { foldTodoTable } from '../conversation/index.js';
|
|
42
|
+
import { createSdkHttpFace } from '../sdk/index.js';
|
|
43
|
+
import { WEBUI_DEFAULT_HOST, WEBUI_DEFAULT_PORT, mountWebui } from '../webui/index.js';
|
|
44
|
+
import { createServeBridge } from './serve-entry.js';
|
|
45
|
+
/**
|
|
46
|
+
* 前台自持面开面(async——监听绑定;TUI / serve 前台 `--port` 形)。
|
|
47
|
+
* 开面即披露(token 只此一次)+ stop 挂退出序;开面失败(如端口占用
|
|
48
|
+
* EADDRINUSE)时先摘挂载再抛——不留半挂 backend。
|
|
49
|
+
*/
|
|
50
|
+
export async function openWebuiFace(options) {
|
|
51
|
+
const face = createSdkHttpFace({
|
|
52
|
+
config: { tcp: { host: WEBUI_DEFAULT_HOST, port: options.port ?? WEBUI_DEFAULT_PORT } },
|
|
53
|
+
bridge: createServeBridge(options.stack, options.runtime, { cwd: process.cwd() }),
|
|
54
|
+
// U5-2 构造期 replay:装载序已受理的插件道路由快照注入 routes 位
|
|
55
|
+
// (受理与挂载两时点解耦——03 §10.6 时序缝定形)
|
|
56
|
+
...(options.pluginRoutes !== undefined ? { routes: options.pluginRoutes.snapshot() } : {}),
|
|
57
|
+
});
|
|
58
|
+
// 件在场分档(批 19e):kit 在场 → 挂载走件 kit(真身 = 共用挂载段);
|
|
59
|
+
// 缺席 → 面开而无 webui 路由(/api/* 404——件禁用语义族,诚实披露)
|
|
60
|
+
const mount = options.mountKit !== undefined
|
|
61
|
+
? options.mountKit.mountOnFace(face, options.staticDir !== undefined ? { staticDir: options.staticDir } : undefined)
|
|
62
|
+
: undefined;
|
|
63
|
+
let info;
|
|
64
|
+
try {
|
|
65
|
+
info = await face.start();
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
mount?.detach(); // 监听未成——backend 与路由不留半挂
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
const { host, port } = info.tcp[0];
|
|
72
|
+
// U5-2 晚注册路挂接:此后受理(/reload 换代重注册)走 face.register——
|
|
73
|
+
// 构造期已注入 snapshot,此处不重放(双注册 throw 防线)
|
|
74
|
+
options.pluginRoutes?.attachFace(face);
|
|
75
|
+
const stop = async () => {
|
|
76
|
+
mount?.detach(); // 幂等(backend 摘除 + 全路由摘除 + 全流收口 + 审批清槽丢弃性)
|
|
77
|
+
options.pluginRoutes?.detachFace(); // 受理账回 pending 态(活面解挂——收口对称)
|
|
78
|
+
await face.stop(); // 幂等(全流收口 + 关监听)
|
|
79
|
+
};
|
|
80
|
+
const disclose = options.disclose ?? ((line) => process.stderr.write(`${line}\n`));
|
|
81
|
+
if (mount !== undefined) {
|
|
82
|
+
disclose(`Web 界面已开面:http://${host}:${port}/`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
disclose(`HTTP 面已开面(webui 件未装载——Web 界面与 /api/* 缺席,/v1/* 程序调用面仍在场):http://${host}:${port}/`);
|
|
86
|
+
}
|
|
87
|
+
disclose(`访问令牌(仅此一次显示):${face.token}`);
|
|
88
|
+
options.onOpen?.({ host, port, token: face.token });
|
|
89
|
+
options.runtime.registerCloser({ label: 'webui-server', fn: () => stop() });
|
|
90
|
+
return { webui: mount?.webui, face, deps: mount?.deps, stop };
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* 共用挂载段(18a-3' 三入口咬合真源):桥真身映射 + webui 路由族注册进
|
|
94
|
+
* 承载面 + channels backend 挂接。TUI/serve 前台经 openWebuiFace 间接受益;
|
|
95
|
+
* daemon 常驻面直调本段(面归 daemon 单源,零第二套映射)。挂载即活——
|
|
96
|
+
* 信封扇出(SSE display/session 族)与审批腿(claim 桥)由 backend 挂接
|
|
97
|
+
* 接线;closer 注册与 token 披露归调用方按入口形各自编排。
|
|
98
|
+
*/
|
|
99
|
+
export function mountWebuiOnFace(options) {
|
|
100
|
+
const deps = bridgeDeps(options.stack, options.staticDir);
|
|
101
|
+
// 面注册器直注(WebuiRouteDescriptor → SdkRouteDescriptor 方向性结构兼容)
|
|
102
|
+
const webui = mountWebui({ ...deps, register: options.face.register });
|
|
103
|
+
// 通道挂接(UiBackend 第四实装 claim 桥)——12f-2c 期注记「addBackend 归
|
|
104
|
+
// 调用方」而 TUI 调用方缺席 = SSE 死流真缺陷,本段收编(修前必红例锁死)
|
|
105
|
+
options.stack.channels.addBackend(webui.backend);
|
|
106
|
+
let detached = false;
|
|
107
|
+
return {
|
|
108
|
+
webui,
|
|
109
|
+
deps,
|
|
110
|
+
detach: () => {
|
|
111
|
+
if (detached)
|
|
112
|
+
return;
|
|
113
|
+
detached = true;
|
|
114
|
+
options.stack.channels.removeBackend(webui.backend.id); // 未注册位 no-op(件内幂等)
|
|
115
|
+
webui.detach();
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* 桥真身:conversation 栈五动词 → WebuiDeps 三窄面(词面独立律——本侧
|
|
121
|
+
* 只做映射不造新词;结构兼容由 face.register 直注与 e2e 双向互证)。
|
|
122
|
+
*/
|
|
123
|
+
function bridgeDeps(stack, staticDirOverride) {
|
|
124
|
+
return {
|
|
125
|
+
sessions: {
|
|
126
|
+
createSession: () => stack.manager.create().sessionId,
|
|
127
|
+
listSessions: () => stack.manager.list().map((row) => ({
|
|
128
|
+
id: row.id,
|
|
129
|
+
title: row.title ?? null, // 无标题会话 null(不造占位串)
|
|
130
|
+
lastActivityAt: row.updatedAt,
|
|
131
|
+
})),
|
|
132
|
+
sessionStateOf: (sessionId) => {
|
|
133
|
+
if (stack.manager.isOpen(sessionId))
|
|
134
|
+
return 'open';
|
|
135
|
+
// 持久册可见 = 已闭(open 态由内存册先判——零 I/O 承诺下新建未落库行
|
|
136
|
+
// 也在内存册,不漏判)
|
|
137
|
+
return stack.manager.list().some((row) => row.id === sessionId) ? 'closed' : 'missing';
|
|
138
|
+
},
|
|
139
|
+
submitPrompt: (input) => {
|
|
140
|
+
void stack.submitText(input.sessionId, input.content); // fire-and-forget——回执经信封回流
|
|
141
|
+
return { sessionId: input.sessionId };
|
|
142
|
+
},
|
|
143
|
+
interruptSession: (sessionId) => stack.interrupt(sessionId), // 未知 id 静默幂等(栈内建)
|
|
144
|
+
},
|
|
145
|
+
read: {
|
|
146
|
+
fetchMessages: (sessionId) => stack.projectionOf(sessionId),
|
|
147
|
+
// 无驱动(closed 会话)回 undefined——服务端 null 诚实空;fold 空事件得 []
|
|
148
|
+
todoOf: (sessionId) => {
|
|
149
|
+
const driver = stack.driverOf(sessionId);
|
|
150
|
+
return driver === undefined ? undefined : foldTodoTable(driver.session.events());
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
// completion 面 v1 不接(可选面缺席合法——服务端诚实回空)
|
|
154
|
+
...(staticDirOverride !== undefined ? { staticDir: staticDirOverride } : { staticDir: resolveStaticDir() }),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 静态面目录探测:构建共生形 dist/host → dist/webui(SPA index.html 在场
|
|
159
|
+
* 才启用;缺席 = API-only 形——/ 与未知路径 404,诚实不虚报)。测试态
|
|
160
|
+
* (tsx 直跑 src)src/webui 无 index.html 自动落 API-only。
|
|
161
|
+
*/
|
|
162
|
+
function resolveStaticDir() {
|
|
163
|
+
const candidate = join(dirname(fileURLToPath(import.meta.url)), '..', 'webui');
|
|
164
|
+
return existsSync(join(candidate, 'index.html')) ? candidate : undefined;
|
|
165
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* issue 域错误码注册(03 §10.7——ISSUE_ 前缀族首批)。
|
|
3
|
+
*
|
|
4
|
+
* 前缀族明列见 contracts/errors.ts ERROR_CODE_PREFIXES(02 §5.3 #1);码名清
|
|
5
|
+
* 单与语义真源 = 03 篇 §10.7 入队/触发/交付条 ISSUE_ 四码。本文件由模块公开
|
|
6
|
+
* 面 index.ts 引入(注册纪律:写入点文件必须实际 import 本文件注册才发
|
|
7
|
+
* 生——与 llm/session/persist/tools 的 codes.ts 同款)。
|
|
8
|
+
*/
|
|
9
|
+
import { registerErrorCodes } from '../contracts/index.js';
|
|
10
|
+
registerErrorCodes([
|
|
11
|
+
{
|
|
12
|
+
code: 'ISSUE_SOURCE_UNREACHABLE',
|
|
13
|
+
module: 'issue',
|
|
14
|
+
description: 'issue 源不可达(轮询/webhook 取数网络折——DNS/连接/5xx 等非限额类失败)',
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: 'ISSUE_SOURCE_RATE_LIMITED',
|
|
18
|
+
module: 'issue',
|
|
19
|
+
description: 'issue 源限额(GitHub 403/429——detail 携 retryAfter 秒;轮询下轮自退避,入队不重试风暴)',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
code: 'ISSUE_WEBHOOK_INVALID',
|
|
23
|
+
module: 'issue',
|
|
24
|
+
description: 'webhook 请求坏形(签名不符/JSON 坏/载荷非 issue 域形——响亮拒不静默吞)',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
code: 'ISSUE_JOB_DUPLICATE',
|
|
28
|
+
module: 'issue',
|
|
29
|
+
description: '同 dedupeKey(repo#issue)在飞互斥撞锁(03 §10.7 入队条——进程内语义,双触发源竞速护栏)',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
// 批 19e 装载批补(03 §10.7 错误码族同笔 + 02 §5.3 随册)——装载期
|
|
33
|
+
// mount config 坏形响亮拒(行级装载失败 /reload 可修,MCP_CONFIG_INVALID 同律)
|
|
34
|
+
code: 'ISSUE_CONFIG_INVALID',
|
|
35
|
+
module: 'issue',
|
|
36
|
+
description: 'mount config 坏形(repos 缺席/非数组/glob 坏形等 normalizeIssueConfig 拒形——装载期响亮拒行级失败)',
|
|
37
|
+
},
|
|
38
|
+
]);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { ISSUE_DEFAULT_SCHEDULE, ISSUE_MAX_DELIVERIES_PER_DAY_DEFAULT, ISSUE_PER_ISSUE_MESSAGES_DEFAULT, } from './types.js';
|
|
2
|
+
/** repo 串词法(精确形 `owner/name`——与 github.ts REPO_RE 同形,此处独立持有:filter 不依赖取数层) */
|
|
3
|
+
const EXACT_REPO_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
4
|
+
/** schedule 串词法(前缀形粗校验——精校归 scheduler parseSchedule 守卫) */
|
|
5
|
+
const SCHEDULE_PREFIX_RE = /^(every:|daily:|weekly:|once:)/;
|
|
6
|
+
/** glob → 正则:`*` 通配段内任意(不含 `/`——段级通配);锚定全配 */
|
|
7
|
+
export function repoMatchesGlob(pattern, repo) {
|
|
8
|
+
const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '[^/]*');
|
|
9
|
+
return new RegExp(`^${escaped}$`).test(repo);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 归一配置(坏形 {ok:false, message} 响亮拒)。输入来自 mount config——
|
|
13
|
+
* 用户可配域,一切缺省在此落定(缺省值单源 = types.ts 常量)。
|
|
14
|
+
*/
|
|
15
|
+
export function normalizeIssueConfig(raw) {
|
|
16
|
+
if (raw === null || typeof raw !== 'object') {
|
|
17
|
+
return { ok: false, message: `issue 配置须对象形(得 ${typeof raw})` };
|
|
18
|
+
}
|
|
19
|
+
const obj = raw;
|
|
20
|
+
// repos:必填非空数组;元素 owner/name 精确形或含 * 的 glob 形
|
|
21
|
+
if (!Array.isArray(obj.repos) || obj.repos.length === 0) {
|
|
22
|
+
return { ok: false, message: 'issue 配置 repos 须非空数组(监听仓库域)' };
|
|
23
|
+
}
|
|
24
|
+
const repos = [];
|
|
25
|
+
for (const r of obj.repos) {
|
|
26
|
+
if (typeof r !== 'string' || r === '') {
|
|
27
|
+
return { ok: false, message: `issue 配置 repos 元素须非空串(得 ${JSON.stringify(r)})` };
|
|
28
|
+
}
|
|
29
|
+
if (r.includes('*')) {
|
|
30
|
+
// glob 形:`*` 不越段(段级通配)——形如 owner/* 或 */name 或 */* 皆合法
|
|
31
|
+
const globOk = r.split('/').length === 2 && r.split('/').every((seg) => seg.length > 0);
|
|
32
|
+
if (!globOk)
|
|
33
|
+
return { ok: false, message: `issue 配置 repos glob 坏形:${r}(须 owner/name 两段、* 为段级通配)` };
|
|
34
|
+
}
|
|
35
|
+
else if (!EXACT_REPO_RE.test(r)) {
|
|
36
|
+
return { ok: false, message: `issue 配置 repos 元素坏形:${r}(须 owner/name 或含 * 的 glob)` };
|
|
37
|
+
}
|
|
38
|
+
repos.push(r);
|
|
39
|
+
}
|
|
40
|
+
// labels/assignees:可选数组;元素非空串;空数组 = 该维不约束
|
|
41
|
+
const normalizeList = (key) => {
|
|
42
|
+
const v = obj[key];
|
|
43
|
+
if (v === undefined)
|
|
44
|
+
return { ok: true };
|
|
45
|
+
if (!Array.isArray(v))
|
|
46
|
+
return { ok: false, message: `issue 配置 ${key} 须数组(得 ${typeof v})` };
|
|
47
|
+
for (const item of v) {
|
|
48
|
+
if (typeof item !== 'string' || item === '') {
|
|
49
|
+
return { ok: false, message: `issue 配置 ${key} 元素须非空串(得 ${JSON.stringify(item)})` };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, value: v };
|
|
53
|
+
};
|
|
54
|
+
const labels = normalizeList('labels');
|
|
55
|
+
if (!labels.ok)
|
|
56
|
+
return labels;
|
|
57
|
+
const assignees = normalizeList('assignees');
|
|
58
|
+
if (!assignees.ok)
|
|
59
|
+
return assignees;
|
|
60
|
+
// mode:缺省 'draft'(fail-closed 缺省律——全自动显式 opt-in)
|
|
61
|
+
let mode = 'draft';
|
|
62
|
+
if (obj.mode !== undefined) {
|
|
63
|
+
if (obj.mode !== 'draft' && obj.mode !== 'auto') {
|
|
64
|
+
return { ok: false, message: `issue 配置 mode 须 'draft'|'auto'(得 ${JSON.stringify(obj.mode)})` };
|
|
65
|
+
}
|
|
66
|
+
mode = obj.mode;
|
|
67
|
+
}
|
|
68
|
+
// schedule:缺省 every:120s;前缀粗校验(精校归 scheduler 守卫)
|
|
69
|
+
let schedule = ISSUE_DEFAULT_SCHEDULE;
|
|
70
|
+
if (obj.schedule !== undefined) {
|
|
71
|
+
if (typeof obj.schedule !== 'string' || !SCHEDULE_PREFIX_RE.test(obj.schedule)) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
message: `issue 配置 schedule 坏形:${JSON.stringify(obj.schedule)}(every:/daily:/weekly:/once: 前缀四形)`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
schedule = obj.schedule;
|
|
78
|
+
}
|
|
79
|
+
// perIssueBudgetMessages:缺省 400;正整数帽
|
|
80
|
+
let perIssueBudgetMessages = ISSUE_PER_ISSUE_MESSAGES_DEFAULT;
|
|
81
|
+
if (obj.perIssueBudgetMessages !== undefined) {
|
|
82
|
+
const n = obj.perIssueBudgetMessages;
|
|
83
|
+
if (typeof n !== 'number' || !Number.isInteger(n) || n < 1 || n > 100_000) {
|
|
84
|
+
return { ok: false, message: `issue 配置 perIssueBudgetMessages 须 1..100000 整数(得 ${JSON.stringify(n)})` };
|
|
85
|
+
}
|
|
86
|
+
perIssueBudgetMessages = n;
|
|
87
|
+
}
|
|
88
|
+
// baseBranch:缺省 'main';非空串
|
|
89
|
+
let baseBranch = 'main';
|
|
90
|
+
if (obj.baseBranch !== undefined) {
|
|
91
|
+
if (typeof obj.baseBranch !== 'string' || obj.baseBranch === '') {
|
|
92
|
+
return { ok: false, message: `issue 配置 baseBranch 须非空串(得 ${JSON.stringify(obj.baseBranch)})` };
|
|
93
|
+
}
|
|
94
|
+
baseBranch = obj.baseBranch;
|
|
95
|
+
}
|
|
96
|
+
// maxDeliveriesPerDay:缺省 10(04 §13 mandate maxPerDay 原料);正整数律
|
|
97
|
+
// ——空帽即坏形(0/负数/坏形拒;彻底关停走 HALT 哨兵或撤 consent,不走空帽)
|
|
98
|
+
let maxDeliveriesPerDay = ISSUE_MAX_DELIVERIES_PER_DAY_DEFAULT;
|
|
99
|
+
if (obj.maxDeliveriesPerDay !== undefined) {
|
|
100
|
+
const n = obj.maxDeliveriesPerDay;
|
|
101
|
+
if (typeof n !== 'number' || !Number.isInteger(n) || n < 1 || n > 100_000) {
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
message: `issue 配置 maxDeliveriesPerDay 须 1..100000 正整数(得 ${JSON.stringify(n)})——彻底关停走 HALT 哨兵或撤 consent,不走空帽`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
maxDeliveriesPerDay = n;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
config: {
|
|
112
|
+
mode,
|
|
113
|
+
schedule,
|
|
114
|
+
repos,
|
|
115
|
+
labels: labels.value,
|
|
116
|
+
assignees: assignees.value,
|
|
117
|
+
perIssueBudgetMessages,
|
|
118
|
+
baseBranch,
|
|
119
|
+
maxDeliveriesPerDay,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* issue 是否命中过滤(白名单语义:命中任一 repo 即过;labels/assignees 维
|
|
125
|
+
* 缺席不约束、在场须至少一命中)。closed 一律不匹配(终态 issue 不再入队)。
|
|
126
|
+
*/
|
|
127
|
+
export function issueMatchesFilter(issue, config) {
|
|
128
|
+
if (issue.state !== 'open')
|
|
129
|
+
return false;
|
|
130
|
+
const repoHit = config.repos.some((p) => (p.includes('*') ? repoMatchesGlob(p, issue.repo) : p === issue.repo));
|
|
131
|
+
if (!repoHit)
|
|
132
|
+
return false;
|
|
133
|
+
if (config.labels !== undefined && config.labels.length > 0) {
|
|
134
|
+
if (!config.labels.some((l) => issue.labels.includes(l)))
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
if (config.assignees !== undefined && config.assignees.length > 0) {
|
|
138
|
+
if (!config.assignees.some((a) => issue.assignees.includes(a)))
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
/** 精确 repo 集(轮询面消费——glob 形不入轮询域,调用方计数 globSkipped) */
|
|
144
|
+
export function exactRepos(config) {
|
|
145
|
+
return config.repos.filter((r) => !r.includes('*'));
|
|
146
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub 源后端(03 §10.7 触发面——轮询/webhook 两源共用的取数腿)。
|
|
3
|
+
*
|
|
4
|
+
* 只走 REST v3(议题/评论面够用——GraphQL 引入零必要);fetch 注入式
|
|
5
|
+
* (`fetchImpl` 缺省全局 fetch——测试注假件,零真网络纪律);分页单页帽
|
|
6
|
+
* 100、不翻页(轮询 since 增量窗内单页 100 通常覆盖;溢出诚实注记于
|
|
7
|
+
* PollReport——不静默丢)。PR 混列:GitHub issues API 把 PR 计入 issues 列
|
|
8
|
+
* 表,`pull_request` 字段在场即过滤。
|
|
9
|
+
*
|
|
10
|
+
* 不可信外部文本:title/body/comments 进上下文一律经调用方帽(issue_get 面
|
|
11
|
+
* ISSUE_CONTEXT_CAP_BYTES);本层只取数不渲染。
|
|
12
|
+
*/
|
|
13
|
+
import { BaseError } from '../contracts/index.js';
|
|
14
|
+
/** repo 串词法(`owner/name`——防注入:两段、字符域限字母数字连字符下划线点) */
|
|
15
|
+
const REPO_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
16
|
+
/** repo 串守卫(坏形 = 参数面问题——TOOL_INVALID_ARGS 语义域) */
|
|
17
|
+
export function assertRepoValid(repo) {
|
|
18
|
+
if (!REPO_RE.test(repo)) {
|
|
19
|
+
throw new BaseError('TOOL_INVALID_ARGS', `[TOOL_INVALID_ARGS] repo 串坏形:${repo}(须 owner/name 两段、字符域限字母数字与 ._-')`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 组 GitHub 后端。错误折码:
|
|
24
|
+
* - 403/429 → ISSUE_SOURCE_RATE_LIMITED(detail 携 retryAfter 秒——
|
|
25
|
+
* x-ratelimit-reset 头优先,缺席 60s 缺省);
|
|
26
|
+
* - 网络异常/其余非 2xx → ISSUE_SOURCE_UNREACHABLE。
|
|
27
|
+
*/
|
|
28
|
+
export function createGithubBackend(opts) {
|
|
29
|
+
const apiBase = (opts.apiBase ?? 'https://api.github.com').replace(/\/+$/, '');
|
|
30
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
31
|
+
/** 单请求(通用头 + JSON 解析;非 2xx/网络错折码上抛;POST 形携 body) */
|
|
32
|
+
async function requestJson(path, init) {
|
|
33
|
+
const hasBody = init?.body !== undefined;
|
|
34
|
+
let response;
|
|
35
|
+
try {
|
|
36
|
+
response = await doFetch(`${apiBase}${path}`, {
|
|
37
|
+
method: init?.method ?? (hasBody ? 'POST' : 'GET'),
|
|
38
|
+
headers: {
|
|
39
|
+
accept: 'application/vnd.github+json',
|
|
40
|
+
authorization: `Bearer ${opts.token}`,
|
|
41
|
+
'x-github-api-version': '2022-11-28',
|
|
42
|
+
...(hasBody ? { 'content-type': 'application/json' } : {}),
|
|
43
|
+
},
|
|
44
|
+
...(hasBody ? { body: JSON.stringify(init?.body) } : {}),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
// fetch 抛 = 网络层折(DNS/连接/超时)——非 HTTP 状态域
|
|
49
|
+
throw new BaseError('ISSUE_SOURCE_UNREACHABLE', `[ISSUE_SOURCE_UNREACHABLE] GitHub 不可达(${path}):${err instanceof Error ? err.message : String(err)}`);
|
|
50
|
+
}
|
|
51
|
+
if (response.status === 403 || response.status === 429) {
|
|
52
|
+
const reset = Number(response.headers.get('x-ratelimit-reset'));
|
|
53
|
+
// reset 头是 epoch 秒;缺席/坏值回 60s 缺省退避
|
|
54
|
+
const retryAfter = Number.isFinite(reset) && reset > 0 ? Math.max(1, reset - Math.floor(Date.now() / 1000)) : 60;
|
|
55
|
+
throw new BaseError('ISSUE_SOURCE_RATE_LIMITED', `[ISSUE_SOURCE_RATE_LIMITED] GitHub 限额(${path},HTTP ${response.status})——${retryAfter}s 后退避`);
|
|
56
|
+
}
|
|
57
|
+
if (!response.ok) {
|
|
58
|
+
throw new BaseError('ISSUE_SOURCE_UNREACHABLE', `[ISSUE_SOURCE_UNREACHABLE] GitHub 非预期状态(${path},HTTP ${response.status})`);
|
|
59
|
+
}
|
|
60
|
+
return (await response.json());
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
async listIssues(req) {
|
|
64
|
+
assertRepoValid(req.repo);
|
|
65
|
+
const params = new URLSearchParams({ state: 'open', per_page: '100' });
|
|
66
|
+
if (req.since !== undefined)
|
|
67
|
+
params.set('since', req.since);
|
|
68
|
+
const rows = await requestJson(`/repos/${req.repo}/issues?${params.toString()}`);
|
|
69
|
+
// PR 混列过滤(pull_request 字段在场即 PR——issues API 语义)
|
|
70
|
+
return rows
|
|
71
|
+
.filter((r) => r.pull_request === undefined)
|
|
72
|
+
.map((r) => ({
|
|
73
|
+
repo: req.repo,
|
|
74
|
+
number: r.number,
|
|
75
|
+
title: r.title ?? '',
|
|
76
|
+
body: r.body ?? '',
|
|
77
|
+
labels: (r.labels ?? []).map((l) => l.name ?? '').filter((n) => n !== ''),
|
|
78
|
+
assignees: (r.assignees ?? []).map((a) => a.login ?? '').filter((n) => n !== ''),
|
|
79
|
+
state: r.state === 'closed' ? 'closed' : 'open',
|
|
80
|
+
updatedAt: r.updated_at,
|
|
81
|
+
htmlUrl: r.html_url,
|
|
82
|
+
}));
|
|
83
|
+
},
|
|
84
|
+
async listComments(req) {
|
|
85
|
+
assertRepoValid(req.repo);
|
|
86
|
+
const rows = await requestJson(`/repos/${req.repo}/issues/${req.number}/comments?per_page=100&sort=created&direction=asc`);
|
|
87
|
+
return rows.map((r) => ({
|
|
88
|
+
id: r.id,
|
|
89
|
+
author: r.user?.login ?? '(unknown)',
|
|
90
|
+
body: r.body ?? '',
|
|
91
|
+
createdAt: r.created_at,
|
|
92
|
+
}));
|
|
93
|
+
},
|
|
94
|
+
async postComment(req) {
|
|
95
|
+
assertRepoValid(req.repo);
|
|
96
|
+
// POST 回执 201——非 2xx 已在 requestJson 折码;body 帽由调用方执(ISSUE_RECEIPT_PATCH_CHARS)
|
|
97
|
+
const created = await requestJson(`/repos/${req.repo}/issues/${req.number}/comments`, {
|
|
98
|
+
body: { body: req.body },
|
|
99
|
+
});
|
|
100
|
+
return { id: typeof created.id === 'number' ? created.id : 0 };
|
|
101
|
+
},
|
|
102
|
+
async createPullRequest(req) {
|
|
103
|
+
assertRepoValid(req.repo);
|
|
104
|
+
// POST 开 PR(201/200——非 2xx 已在 requestJson 折码;422「PR 已在/无
|
|
105
|
+
// 差异」折 ISSUE_SOURCE_UNREACHABLE 族携状态码——与 postComment 同形
|
|
106
|
+
// 不特判,调用方按错误面分流)
|
|
107
|
+
const row = await requestJson(`/repos/${req.repo}/pulls`, {
|
|
108
|
+
body: { title: req.title, body: req.body, head: req.head, base: req.base },
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
number: typeof row.number === 'number' ? row.number : 0,
|
|
112
|
+
htmlUrl: typeof row.html_url === 'string' ? row.html_url : '',
|
|
113
|
+
};
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* issue 件公开面(03 §10.7——core:issue 无人值守编排件;批 16)。
|
|
3
|
+
*
|
|
4
|
+
* 单向 DAG:issue → contracts + context(02 §4.1 #27 席)。兄弟件
|
|
5
|
+
* (scheduler/subagent/goal/tools/exec/checkpoint/persist)零 import——
|
|
6
|
+
* 全部经窄面注入(types.ts 词面独立律注)。错误码注册(codes.ts)随本面
|
|
7
|
+
* 引入生效——与 llm/session/persist/tools 的 codes.ts 同款纪律。
|
|
8
|
+
*/
|
|
9
|
+
import './codes.js';
|
|
10
|
+
export { ISSUE_POLL_JOB_NAME, ISSUE_DEFAULT_SCHEDULE, ISSUE_PARALLEL_LIMIT_DEFAULT, ISSUE_PER_ISSUE_MESSAGES_DEFAULT, ISSUE_RECEIPT_PATCH_CHARS, ISSUE_CONTEXT_CAP_BYTES, ISSUE_WEBHOOK_ENDPOINT, ISSUE_WEBHOOK_BODY_LIMIT_BYTES, ISSUE_GITHUB_TOKEN_NAME, ISSUE_WEBHOOK_SECRET_NAME, ISSUE_MAX_DELIVERIES_PER_DAY_DEFAULT, ISSUE_WORKTREE_NAME_RE, issueDedupeKey, } from './types.js';
|
|
11
|
+
export { createGithubBackend, assertRepoValid } from './github.js';
|
|
12
|
+
export { normalizeIssueConfig, issueMatchesFilter, repoMatchesGlob, exactRepos } from './filter.js';
|
|
13
|
+
export { createIssuePoller, watermarkKey } from './poll.js';
|
|
14
|
+
export { computeSignature, signatureMatches, parseWebhookPayload, handleWebhookRequest } from './webhook.js';
|
|
15
|
+
export { mountIssueWebhook, WEBHOOK_EVENT_HEADER, WEBHOOK_SIGNATURE_HEADER } from './mount.js';
|
|
16
|
+
export { createIssueTools } from './tools.js';
|
|
17
|
+
export { createIssueService } from './service.js';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { BaseError } from '../contracts/index.js';
|
|
2
|
+
import { ISSUE_WEBHOOK_BODY_LIMIT_BYTES, ISSUE_WEBHOOK_ENDPOINT } from './types.js';
|
|
3
|
+
import { handleWebhookRequest } from './webhook.js';
|
|
4
|
+
/** webhook 事件名头(GitHub 官方词面 X-GitHub-Event——node 头表恒小写读位) */
|
|
5
|
+
export const WEBHOOK_EVENT_HEADER = 'x-github-event';
|
|
6
|
+
/** 验签头(GitHub 官方词面 X-Hub-Signature-256——03 §10.7 批 18a-4' 勘正后单源) */
|
|
7
|
+
export const WEBHOOK_SIGNATURE_HEADER = 'x-hub-signature-256';
|
|
8
|
+
/** JSON 应答速记(webhook 回执/拒收形——面级无共享 sendJson 故件内自持) */
|
|
9
|
+
function sendJson(res, status, payload) {
|
|
10
|
+
res.writeHead(status, { 'content-type': 'application/json; charset=utf-8' });
|
|
11
|
+
res.end(JSON.stringify(payload));
|
|
12
|
+
}
|
|
13
|
+
/** 单值头读取(node 头表恒小写;数组形取首——GitHub 恒单值) */
|
|
14
|
+
function headerOf(req, name) {
|
|
15
|
+
const raw = req.headers[name];
|
|
16
|
+
const value = Array.isArray(raw) ? raw[0] : raw;
|
|
17
|
+
return typeof value === 'string' ? value : '';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 注册 issue webhook 端点(03 §10.7 ②——路由扩展位第二消费方)。
|
|
21
|
+
*
|
|
22
|
+
* 验签与拒收语义件侧执法:receipt 族 200 / `ISSUE_WEBHOOK_INVALID` 400 /
|
|
23
|
+
* 未预期异常上抛面级 catch 500(GitHub 侧按 5xx 重试)。
|
|
24
|
+
*/
|
|
25
|
+
export function mountIssueWebhook(deps, face, options = {}) {
|
|
26
|
+
let detacher = face.register({
|
|
27
|
+
method: 'POST',
|
|
28
|
+
path: options.path ?? ISSUE_WEBHOOK_ENDPOINT,
|
|
29
|
+
auth: 'self',
|
|
30
|
+
// loopbackOnly 不设(有意)——入站投递面须挂非回环监听器
|
|
31
|
+
bodyLimitBytes: options.bodyLimitBytes ?? ISSUE_WEBHOOK_BODY_LIMIT_BYTES,
|
|
32
|
+
handler: async (req, res, ctx) => {
|
|
33
|
+
// 空 secret = 面未开启(空密钥 HMAC 确定性可伪造——禁值守卫先于读体)
|
|
34
|
+
if (deps.secret === '') {
|
|
35
|
+
sendJson(res, 400, {
|
|
36
|
+
error: 'ISSUE_WEBHOOK_INVALID',
|
|
37
|
+
message: '[ISSUE_WEBHOOK_INVALID] webhook 面未开启(缺 secret 配置)',
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const body = await ctx.readBody(req);
|
|
42
|
+
if (!body.ok) {
|
|
43
|
+
sendJson(res, body.status, { error: 'body_too_large', message: body.message });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const receipt = await handleWebhookRequest(deps, {
|
|
48
|
+
event: headerOf(req, WEBHOOK_EVENT_HEADER),
|
|
49
|
+
signatureHeader: headerOf(req, WEBHOOK_SIGNATURE_HEADER),
|
|
50
|
+
rawBody: body.body,
|
|
51
|
+
});
|
|
52
|
+
sendJson(res, 200, receipt);
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// 响亮拒(运维可见)——坏签/坏载荷 400;未预期异常上抛(面级 catch → 500)
|
|
56
|
+
if (err instanceof BaseError && err.code === 'ISSUE_WEBHOOK_INVALID') {
|
|
57
|
+
sendJson(res, 400, { error: err.code, message: err.message });
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
throw err;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
return {
|
|
65
|
+
dispose() {
|
|
66
|
+
detacher?.();
|
|
67
|
+
detacher = undefined;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { exactRepos, issueMatchesFilter } from './filter.js';
|
|
2
|
+
/** 水位键(store_state 承载——kind 'issue-poll') */
|
|
3
|
+
export function watermarkKey(repo) {
|
|
4
|
+
return `issue:poll:watermark:${repo}`;
|
|
5
|
+
}
|
|
6
|
+
/** 组轮询器(单轮 = pollOnce 一次调用;无自驱时钟——挂钟归 scheduler 行) */
|
|
7
|
+
export function createIssuePoller(deps) {
|
|
8
|
+
const { backend, state, config } = deps;
|
|
9
|
+
return {
|
|
10
|
+
async pollOnce() {
|
|
11
|
+
const repos = exactRepos(config);
|
|
12
|
+
const globSkipped = config.repos.length - repos.length;
|
|
13
|
+
let seen = 0;
|
|
14
|
+
let candidates = 0;
|
|
15
|
+
let enqueued = 0;
|
|
16
|
+
let duplicates = 0;
|
|
17
|
+
let rejected = 0;
|
|
18
|
+
const errors = [];
|
|
19
|
+
for (const repo of repos) {
|
|
20
|
+
// 水位读取(缺席 = 首轮全量窗;值坏形视同缺席——防御旧数据)
|
|
21
|
+
const stored = state.getStoreState(watermarkKey(repo));
|
|
22
|
+
const since = typeof stored?.value === 'string' ? stored.value : undefined;
|
|
23
|
+
let issues;
|
|
24
|
+
try {
|
|
25
|
+
issues = await backend.listIssues({ repo, since });
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
// 单仓失败不炸整轮——记录进报告继续下一仓
|
|
29
|
+
errors.push({ repo, message: err instanceof Error ? err.message : String(err) });
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// 水位推进 = 本轮见过的 max(updatedAt)(含被弃的——见 types 头注纪律)
|
|
33
|
+
let maxUpdated = since ?? '';
|
|
34
|
+
for (const issue of issues) {
|
|
35
|
+
if (issue.updatedAt > maxUpdated)
|
|
36
|
+
maxUpdated = issue.updatedAt;
|
|
37
|
+
}
|
|
38
|
+
if (issues.length > 0 && maxUpdated !== since) {
|
|
39
|
+
state.setStoreState(watermarkKey(repo), maxUpdated, { kind: 'issue-poll' });
|
|
40
|
+
}
|
|
41
|
+
seen += issues.length;
|
|
42
|
+
for (const issue of issues) {
|
|
43
|
+
if (!issueMatchesFilter(issue, config))
|
|
44
|
+
continue;
|
|
45
|
+
candidates += 1;
|
|
46
|
+
const result = await deps.enqueue(issue);
|
|
47
|
+
if (result.status === 'started')
|
|
48
|
+
enqueued += 1;
|
|
49
|
+
else if (result.status === 'duplicate')
|
|
50
|
+
duplicates += 1;
|
|
51
|
+
else
|
|
52
|
+
rejected += 1;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { repos, seen, candidates, enqueued, duplicates, rejected, globSkipped, errors };
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|