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,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentials — 人面命令处理器(03 §10.9 写入面复合案·静态凭证人面唯写;
|
|
3
|
+
* c-5 落码批)。
|
|
4
|
+
*
|
|
5
|
+
* 双面共享底座:TUI `/credentials`(core-plugins 注册——argv 面)与 CLI
|
|
6
|
+
* `berry-agent credentials`(cli.ts 解析产物 CredentialsSub 直入)同源本件
|
|
7
|
+
* ——动词语义/值域执法/结算文本单源,两面只差投递形(notify vs stdout)。
|
|
8
|
+
* 形态律同 /tick /memory-export(命令件先例):argv → 人读文本;服务面守卫
|
|
9
|
+
* 错(BaseError)折文本不抛——命令面是用户面不是异常面。
|
|
10
|
+
*
|
|
11
|
+
* 铁律(03 §10.9 模型可见性铁律):
|
|
12
|
+
* - **值永不呈现在任何结算文本**——add/rm 只回执名与域,list 只列
|
|
13
|
+
* namespace/名/来源/更新时间(给值唯一路径 = 注入腿 env 引用形展开);
|
|
14
|
+
* - 人面命令恒可列示/撤销**一切域**(host + 全部 plugin:<id>——审计与
|
|
15
|
+
* 治理面不随 namespace 分权;与 ctx.secrets 读腿的自域隔离分立两律);
|
|
16
|
+
* - argv 传值不落 durable——channels 命令分派纯内存直达 handler(命令
|
|
17
|
+
* 输入不进 conversation/durable 管线),CLI 同理;值经 argv 即进程内
|
|
18
|
+
* 瞬态,写库即加密落盘,无明文留痕面。
|
|
19
|
+
*
|
|
20
|
+
* 窄面注入律(词面独立律——结构兼容 persist Store 凭证方法子集四法,
|
|
21
|
+
* compat 互证归本件测试):本件 DAG 无 host 边,宿主装配位传真身。
|
|
22
|
+
*/
|
|
23
|
+
import { BaseError } from '../contracts/index.js';
|
|
24
|
+
import { HOST_NAMESPACE, isPluginNamespace } from './types.js';
|
|
25
|
+
/** 用法文案(TUI 命令 description 位与用法错指路共用单源;oauth 动词 TUI 面承载——CLI 零装配无注册表不载) */
|
|
26
|
+
export const CREDENTIALS_USAGE = [
|
|
27
|
+
'用法:/credentials add <name> <value> [--namespace <ns>] —— 录入静态凭证(缺省 host 域;值含空格用引号包裹)',
|
|
28
|
+
' /credentials list —— 全域列示(namespace/名/来源/更新时间——永不呈值)',
|
|
29
|
+
' /credentials rm <name> [--namespace <ns>] —— 撤销凭证(删除唯一路径)',
|
|
30
|
+
' /credentials oauth <pluginId> [<name>] —— 发起插件 oauth 授权流(device-code;域随流主人)',
|
|
31
|
+
].join('\n');
|
|
32
|
+
/**
|
|
33
|
+
* TUI 面 argv 解析(CLI 面由 cli.ts 解析律执法——未知旗标退 2 等;本函数
|
|
34
|
+
* 只服务 TUI 通道的裸 argv:动词识别 + 位置参数 + --namespace 旗标)。
|
|
35
|
+
*/
|
|
36
|
+
export function parseCredentialsArgv(argv) {
|
|
37
|
+
const [verb, ...rest] = argv;
|
|
38
|
+
if (verb === undefined || verb.startsWith('--')) {
|
|
39
|
+
return { ok: false, message: `缺子命令。\n${CREDENTIALS_USAGE}` };
|
|
40
|
+
}
|
|
41
|
+
// 旗标扫描(本命令族唯一旗标 --namespace——宽容形:位置参数与旗标可交错)
|
|
42
|
+
const literals = [];
|
|
43
|
+
let namespace;
|
|
44
|
+
for (let i = 0; i < rest.length; i++) {
|
|
45
|
+
const tok = rest[i];
|
|
46
|
+
if (tok === '--namespace') {
|
|
47
|
+
const next = rest[i + 1];
|
|
48
|
+
if (next === undefined || next === '') {
|
|
49
|
+
return { ok: false, message: `--namespace 须带值。\n${CREDENTIALS_USAGE}` };
|
|
50
|
+
}
|
|
51
|
+
namespace = next;
|
|
52
|
+
i++;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
literals.push(tok);
|
|
56
|
+
}
|
|
57
|
+
if (verb === 'list') {
|
|
58
|
+
if (literals.length > 0)
|
|
59
|
+
return { ok: false, message: `list 不收位置参数。\n${CREDENTIALS_USAGE}` };
|
|
60
|
+
return { ok: true, sub: { sub: 'list' } };
|
|
61
|
+
}
|
|
62
|
+
if (verb === 'add') {
|
|
63
|
+
if (literals.length !== 2) {
|
|
64
|
+
return { ok: false, message: `add 须带 <name> <value> 两参数。\n${CREDENTIALS_USAGE}` };
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
ok: true,
|
|
68
|
+
sub: {
|
|
69
|
+
sub: 'add',
|
|
70
|
+
name: literals[0],
|
|
71
|
+
value: literals[1],
|
|
72
|
+
...(namespace !== undefined ? { namespace } : {}),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
if (verb === 'rm') {
|
|
77
|
+
if (literals.length !== 1) {
|
|
78
|
+
return { ok: false, message: `rm 须带 <name> 一参数。\n${CREDENTIALS_USAGE}` };
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
sub: { sub: 'rm', name: literals[0], ...(namespace !== undefined ? { namespace } : {}) },
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (verb === 'oauth') {
|
|
86
|
+
if (namespace !== undefined) {
|
|
87
|
+
// 域随流主人(token 落插件自域)——oauth 不收 --namespace
|
|
88
|
+
return { ok: false, message: `oauth 不收 --namespace(域随流主人——token 落插件自域)。\n${CREDENTIALS_USAGE}` };
|
|
89
|
+
}
|
|
90
|
+
if (literals.length < 1 || literals.length > 2) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
message: `oauth 须带 <pluginId> [<name>](name 可省 = 该插件唯一流)。\n${CREDENTIALS_USAGE}`,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
sub: {
|
|
99
|
+
sub: 'oauth',
|
|
100
|
+
pluginId: literals[0],
|
|
101
|
+
...(literals.length === 2 ? { name: literals[1] } : {}),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return { ok: false, message: `未知子命令:${verb}(合法:add/list/rm/oauth)。\n${CREDENTIALS_USAGE}` };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 命令处理器(同步纯逻辑——存储三法 + 审计 seam;守卫错折文本 ok:false)。
|
|
109
|
+
*/
|
|
110
|
+
export function runCredentialsCommand(sub, deps) {
|
|
111
|
+
try {
|
|
112
|
+
switch (sub.sub) {
|
|
113
|
+
case 'add':
|
|
114
|
+
return runAdd(sub.name, sub.value, sub.namespace, deps);
|
|
115
|
+
case 'list':
|
|
116
|
+
return runList(deps);
|
|
117
|
+
case 'rm':
|
|
118
|
+
return runRm(sub.name, sub.namespace, deps);
|
|
119
|
+
case 'oauth':
|
|
120
|
+
// 不可达退化档(CLI 解析律不产此形、TUI 面在 core-plugins 执行腿
|
|
121
|
+
// 分流先取)——直入仍诚实回执不炸
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
text: 'oauth 动词是运行时承载(需流注册表 + fetch)——TUI 内用 /credentials oauth;CLI 面不可用。',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
// 守卫错折文本(命令面是用户面——BaseError 码与人读原因直呈;密钥腐坏
|
|
130
|
+
// PERSIST_SECRET_UNREADABLE 等物理面错同折不炸通道)
|
|
131
|
+
if (err instanceof BaseError)
|
|
132
|
+
return { ok: false, text: `${err.code}:${err.message}` };
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** namespace 解析执法(缺省 host;值域 = 'host' | 好形 plugin:<id>——判据单源 isPluginNamespace) */
|
|
137
|
+
function resolveNamespace(namespace) {
|
|
138
|
+
if (namespace === undefined)
|
|
139
|
+
return HOST_NAMESPACE;
|
|
140
|
+
if (namespace !== HOST_NAMESPACE && !isPluginNamespace(namespace)) {
|
|
141
|
+
throw new BaseError('CREDENTIALS_NAMESPACE_DENIED', `namespace「${namespace}」坏形——值域 = 'host' | 'plugin:<id>'(03 §10.9 namespace 归属列值域单源)`);
|
|
142
|
+
}
|
|
143
|
+
return namespace;
|
|
144
|
+
}
|
|
145
|
+
/** add:录入(upsert——同名覆写整行含 meta;写 meta {source:'manual'};值不回显) */
|
|
146
|
+
function runAdd(name, value, namespace, deps) {
|
|
147
|
+
const ns = resolveNamespace(namespace);
|
|
148
|
+
if (value === '') {
|
|
149
|
+
// 空值 = 缺值形(CLI 空串经解析律已拦;TUI tokenize 可产空段——此处兜底)
|
|
150
|
+
return { ok: false, text: `值不得为空。\n${CREDENTIALS_USAGE}` };
|
|
151
|
+
}
|
|
152
|
+
const meta = { source: 'manual' };
|
|
153
|
+
deps.store.setCredential(ns, name, { apiKey: value, meta });
|
|
154
|
+
// credentials/changed 审计 seam(05 §1.1 值域单源:人面写 = action 'add' /
|
|
155
|
+
// origin 'human';值恒不入载荷;生产装配已接线 audit_events——assembly
|
|
156
|
+
// U3 批 U3-5,缺省 no-op = 测试形)
|
|
157
|
+
deps.onCredentialChanged?.({ namespace: ns, name, action: 'add', origin: 'human' });
|
|
158
|
+
return {
|
|
159
|
+
ok: true,
|
|
160
|
+
text: `已录入凭证 ${ns}/${name}(来源 manual)——值不回显;注入用 env 引用形 '@credentials:${name}'。`,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** rm:撤销(删除唯一路径——保留律的对面;缺席 = CREDENTIALS_NOT_FOUND 折文本) */
|
|
164
|
+
function runRm(name, namespace, deps) {
|
|
165
|
+
const ns = resolveNamespace(namespace);
|
|
166
|
+
if (!deps.store.deleteCredential(ns, name)) {
|
|
167
|
+
throw new BaseError('CREDENTIALS_NOT_FOUND', `凭证 ${name} 不在 ${ns} 域——用 list 查在册名。`);
|
|
168
|
+
}
|
|
169
|
+
deps.onCredentialChanged?.({ namespace: ns, name, action: 'remove', origin: 'human' });
|
|
170
|
+
return { ok: true, text: `已撤销凭证 ${ns}/${name}。` };
|
|
171
|
+
}
|
|
172
|
+
/** list:全域列示(namespace/名/来源/更新时间——永不呈值;expired 位随来源列标注) */
|
|
173
|
+
function runList(deps) {
|
|
174
|
+
const rows = deps.store.listCredentialProviders();
|
|
175
|
+
if (rows.length === 0) {
|
|
176
|
+
return { ok: true, text: '无凭证(数据目录凭证表空——/credentials add 录入首条)' };
|
|
177
|
+
}
|
|
178
|
+
const lines = [`共 ${rows.length} 条凭证(全域列示,永不呈值):`];
|
|
179
|
+
for (const row of rows) {
|
|
180
|
+
const meta = (row.meta ?? {});
|
|
181
|
+
const source = meta.source ?? '未记';
|
|
182
|
+
const expired = meta.expired === true ? '(已过期——保留上次有效值)' : '';
|
|
183
|
+
lines.push(` ${row.namespace} ${row.provider} 来源 ${source}${expired} 更新 ${new Date(row.updatedAt).toISOString()}`);
|
|
184
|
+
}
|
|
185
|
+
return { ok: true, text: lines.join('\n') };
|
|
186
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentials — env 引用形展开器工厂(03 §10.9 注入腿;c-4 落码批)。
|
|
3
|
+
*
|
|
4
|
+
* 消费链:用户在 MCP/LSP server config env 声明 `@credentials:<name>` →
|
|
5
|
+
* 配置原文(引用形)流入 exec EnvPolicy.set → spawn 时刻 buildChildEnv 单点
|
|
6
|
+
* 调用本工厂产物展开。本工厂只做两件事:
|
|
7
|
+
* - **host 域读**(namespace 恒 'host'——用户配置域 = 宿主域:server config
|
|
8
|
+
* 是用户手面 enabled.yaml,插件域凭证不进用户 server 配置注入面;
|
|
9
|
+
* 与 ctx.secrets.get 的插件域读腿分立两路);
|
|
10
|
+
* - **缺席响亮拒**(CREDENTIALS_NOT_FOUND——message 指路人面录入路径)。
|
|
11
|
+
*
|
|
12
|
+
* 不落审计:注入腿展开 = 宿主代用户配置展开,非插件越域读——无门无账
|
|
13
|
+
* (capability/used 只记插件越域命中,03 §10.9 读腿定形注同源)。
|
|
14
|
+
*
|
|
15
|
+
* 席位接线:plugin-boot 在 secretsSeatActive(与 ctx.secrets 席位门同一双
|
|
16
|
+
* 条件)时把本工厂产物供入共享根服务 'credentials-env-ref'——exec 件
|
|
17
|
+
* apply 期拾取;禁用/缺席 ⇒ 服务缺席 ⇒ 引用形 fail-loud(exec 侧执法)。
|
|
18
|
+
*/
|
|
19
|
+
import { BaseError } from '../contracts/index.js';
|
|
20
|
+
import { HOST_NAMESPACE } from './types.js';
|
|
21
|
+
/**
|
|
22
|
+
* 建 env 引用形展开器(host 域恒定——用户配置注入面单域)。
|
|
23
|
+
* @param store 凭证存储窄面(assembly 接 persistence.store 凭证投影)
|
|
24
|
+
* @returns 展开器:名命中返回明文;缺席抛 CREDENTIALS_NOT_FOUND(指路人面)
|
|
25
|
+
*/
|
|
26
|
+
export function createEnvRefResolver(store) {
|
|
27
|
+
return (name) => {
|
|
28
|
+
const entry = store.getCredential(HOST_NAMESPACE, name);
|
|
29
|
+
if (entry === undefined) {
|
|
30
|
+
throw new BaseError('CREDENTIALS_NOT_FOUND', `env 引用形 @credentials:${name} 在 host 域缺席——人面录入路径 /credentials add(03 §10.9 注入腿;模型可见性铁律:值恒不出现在配置/日志/工具结果)`);
|
|
31
|
+
}
|
|
32
|
+
return entry.apiKey;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentials — core:credentials 公开面(02 §4.1 #28 席;边 contracts+persist)。
|
|
3
|
+
*
|
|
4
|
+
* c-2 存储腿产物:契约词面件(types)+ 表扩容迁移(migration)。c-3 读腿
|
|
5
|
+
* 产物:错误码族(codes)+ 插件凭证面工厂(secrets——ctx.get("secrets")
|
|
6
|
+
* 消费面,host 装配序逐插件 fork provide 绑定版)。c-4 注入腿产物:env
|
|
7
|
+
* 引用形展开器工厂(env-ref——host 域,exec spawn 管道单点消费)。人面
|
|
8
|
+
* 命令/oauth 流随 c-5..c-6 落码批逐笔扩公开面(03 §10.9 件章为机制真源)。
|
|
9
|
+
* c-6 产物:oauth 流编舞 + 流注册表 + 刷新链三振(oauth.ts/refresh.ts);
|
|
10
|
+
* ctx.secrets 第三动词 registerOAuthFlow(secrets.ts)。
|
|
11
|
+
*/
|
|
12
|
+
import './codes.js';
|
|
13
|
+
export { HOST_NAMESPACE, pluginNamespace, isPluginNamespace, parsePluginNamespace, } from './types.js';
|
|
14
|
+
export { CREDENTIALS_MIGRATION } from './migration.js';
|
|
15
|
+
export { createSecretsFace, } from './secrets.js';
|
|
16
|
+
// oauth 流(c-6——03 §10.9 oauth 案):流编舞(device-code/refresh——四源
|
|
17
|
+
// 注入零真网络)+ 流注册表(host-owned)+ 人面流解析
|
|
18
|
+
export { runDeviceCodeFlow, refreshOAuthToken, createOAuthFlowRegistry, resolveOAuthFlow, } from './oauth.js';
|
|
19
|
+
// 刷新链(c-6):件内自持挂钟 + 保留上次有效值律三振(interval 0 = 测试
|
|
20
|
+
// 手动 tick 驱动)
|
|
21
|
+
export { createRefreshChain } from './refresh.js';
|
|
22
|
+
// 注入腿(c-4——03 §10.9):env 引用形展开器工厂(host 域)——plugin-boot
|
|
23
|
+
// 席位接线消费(共享根服务 'credentials-env-ref' → exec spawn 管道拾取)
|
|
24
|
+
export { createEnvRefResolver } from './env-ref.js';
|
|
25
|
+
// 人面命令(c-5——03 §10.9 写入面复合案):双面共享纯逻辑底座(TUI
|
|
26
|
+
// /credentials 与 CLI credentials 子命令族同源——动词语义/值域执法/结算
|
|
27
|
+
// 文本单源;host 侧 core-plugins 注册 + credentials-cmd CLI 入口两消费位)
|
|
28
|
+
export { CREDENTIALS_USAGE, parseCredentialsArgv, runCredentialsCommand, } from './commands.js';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const CREDENTIALS_MIGRATION = {
|
|
2
|
+
version: 7,
|
|
3
|
+
name: 'credentials-namespace',
|
|
4
|
+
sql: `
|
|
5
|
+
CREATE TABLE credentials_v7 (
|
|
6
|
+
namespace TEXT NOT NULL DEFAULT 'host', -- 归属列('host' | 'plugin:<id>'——词面单源 src/credentials/types.ts)
|
|
7
|
+
provider TEXT NOT NULL, -- 作用名(读腿 get(name) 的 name——物理列名承 v1)
|
|
8
|
+
api_key TEXT NOT NULL, -- v1:<base64(iv|tag|ct)>(secret-box 加密产物)
|
|
9
|
+
meta TEXT, -- JSON 附加元数据(source/expired 键约定见 types.ts)
|
|
10
|
+
updated_at INTEGER NOT NULL,
|
|
11
|
+
PRIMARY KEY (namespace, provider)
|
|
12
|
+
) STRICT;
|
|
13
|
+
INSERT INTO credentials_v7 (namespace, provider, api_key, meta, updated_at)
|
|
14
|
+
SELECT 'host', provider, api_key, meta, updated_at FROM credentials;
|
|
15
|
+
DROP TABLE credentials;
|
|
16
|
+
ALTER TABLE credentials_v7 RENAME TO credentials;
|
|
17
|
+
`,
|
|
18
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentials — oauth 授权流件(03 §10.9 oauth bullet;c-6 落码批)。
|
|
3
|
+
*
|
|
4
|
+
* 三段承载:
|
|
5
|
+
* 1. **device-code 流编舞**(RFC 8628——回调受理位挂账 U5 后,v1 只此流):
|
|
6
|
+
* `runDeviceCodeFlow` 纯舞步——发起(POST deviceAuthUrl form)→ present
|
|
7
|
+
* 用户(user_code/verification_uri 人读呈现)→ 按 interval 轮询 token
|
|
8
|
+
* 端点 → authorization_pending 续轮 / slow_down 提速降频(interval+5s)/
|
|
9
|
+
* access_denied → DENIED / expired_token·超窗 → EXPIRED / 200 → tokens。
|
|
10
|
+
* fetch/clock/present/sleep 四源全注入(测试零真网络铁律——loop 只认
|
|
11
|
+
* StreamFn 同律:本件无 web 边,fetchFn 由宿主装配位注入)。
|
|
12
|
+
* 2. **刷新腿** `refreshOAuthToken`:grant_type=refresh_token 换新——
|
|
13
|
+
* invalid_grant → EXPIRED(授权态坏,重试无益);其余非 200 → 兜底码。
|
|
14
|
+
* 3. **流注册表**(host-owned——createJobRegistry 同形先例):按
|
|
15
|
+
* (pluginId, name) 分键、同插件同名后写胜出(注册动词换装正道——
|
|
16
|
+
* channels 后写胜出同律,零新撞名码)、跨插件结构性不撞(分域)。
|
|
17
|
+
* invoke = 宿主回调窗包裹(enterHostCallback 开 → 插件 handler →
|
|
18
|
+
* finally 合窗——窗内 ctx.secrets.set 可达,c-3 受理窗机制的消费位)。
|
|
19
|
+
*
|
|
20
|
+
* 受理全景(03 §10.9 写入面复合案 oauth 案):用户人面 `/credentials oauth
|
|
21
|
+
* <pluginId> [<name>]` 发起 → 宿主解析流 → 开窗回调插件 handler(io 携
|
|
22
|
+
* runDeviceCode 舞步 + present 呈现面)→ 插件窗内 `ctx.secrets.set` 写
|
|
23
|
+
* 自域(token 经 io 返回值只在内存过手——宿主不落 token,永不持值面)。
|
|
24
|
+
*
|
|
25
|
+
* 回调受理位(redirect URI 形)挂账 U5 路由受限开放后定形——本批只钉
|
|
26
|
+
* 挂点归属(sdk.register-route 门制复用),不落形。
|
|
27
|
+
*/
|
|
28
|
+
import { BaseError } from '../contracts/index.js';
|
|
29
|
+
/** 注册表工厂(纯内存——零 IO,装载代内生命周期) */
|
|
30
|
+
export function createOAuthFlowRegistry() {
|
|
31
|
+
// 双层分键:外层 pluginId(跨插件结构性不撞)→ 内层流名(后写胜出)
|
|
32
|
+
const flows = new Map();
|
|
33
|
+
return {
|
|
34
|
+
register(pluginId, spec, openWriteWindow) {
|
|
35
|
+
let byName = flows.get(pluginId);
|
|
36
|
+
if (byName === undefined) {
|
|
37
|
+
byName = new Map();
|
|
38
|
+
flows.set(pluginId, byName);
|
|
39
|
+
}
|
|
40
|
+
byName.set(spec.def.name, {
|
|
41
|
+
pluginId,
|
|
42
|
+
def: spec.def,
|
|
43
|
+
// 宿主回调窗包裹:开窗 → handler → finally 合窗(异常路径同样收口——
|
|
44
|
+
// c-3 窗计数幂等恢复律;窗内该插件 ctx.secrets.set 可达)
|
|
45
|
+
invoke: async (io) => {
|
|
46
|
+
const exit = openWriteWindow();
|
|
47
|
+
try {
|
|
48
|
+
await spec.handler(io);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
exit();
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
get(pluginId, name) {
|
|
57
|
+
return flows.get(pluginId)?.get(name);
|
|
58
|
+
},
|
|
59
|
+
flowsOf(pluginId) {
|
|
60
|
+
return [...(flows.get(pluginId)?.values() ?? [])];
|
|
61
|
+
},
|
|
62
|
+
list() {
|
|
63
|
+
return [...flows.values()].flatMap((byName) => [...byName.values()]);
|
|
64
|
+
},
|
|
65
|
+
unregisterForPlugin(pluginId) {
|
|
66
|
+
return flows.delete(pluginId);
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 人面流解析(`/credentials oauth <pluginId> [<name>]` 的解析腿):
|
|
72
|
+
* name 给定 = 精确解析;缺省 = 该插件唯一流自动选中(0/多流各成指路文案)。
|
|
73
|
+
* 返回流或人读 message(不抛——命令面是用户面,文案单源在本函数)。
|
|
74
|
+
*/
|
|
75
|
+
export function resolveOAuthFlow(registry, pluginId, name) {
|
|
76
|
+
const listFlows = (rows) => rows.length === 0
|
|
77
|
+
? `(无——该插件未注册任何 oauth 流;装载期 ctx.secrets.registerOAuthFlow 注册)`
|
|
78
|
+
: rows.map((f) => `plugin:${f.pluginId}/${f.def.name}`).join('、');
|
|
79
|
+
if (name !== undefined) {
|
|
80
|
+
const flow = registry.get(pluginId, name);
|
|
81
|
+
if (flow === undefined) {
|
|
82
|
+
return {
|
|
83
|
+
flow: undefined,
|
|
84
|
+
message: `oauth 流 ${name} 不在插件 ${pluginId} 名下(在册流:${listFlows(registry.flowsOf(pluginId))})。`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return { flow };
|
|
88
|
+
}
|
|
89
|
+
const flows = registry.flowsOf(pluginId);
|
|
90
|
+
if (flows.length === 0) {
|
|
91
|
+
return { flow: undefined, message: `插件 ${pluginId} 未注册任何 oauth 流。` };
|
|
92
|
+
}
|
|
93
|
+
if (flows.length > 1) {
|
|
94
|
+
return { flow: undefined, message: `插件 ${pluginId} 在册多流,须指名其一:${listFlows(flows)}。` };
|
|
95
|
+
}
|
|
96
|
+
return { flow: flows[0] };
|
|
97
|
+
}
|
|
98
|
+
/** form 编码单源(URLSearchParams——百分号转义与空格 %20 形由标准库保证) */
|
|
99
|
+
function formUrlEncode(fields) {
|
|
100
|
+
return new URLSearchParams(fields).toString();
|
|
101
|
+
}
|
|
102
|
+
/** JSON 安全解析(非 JSON → null——调用面折 FLOW_FAILED 兜底码) */
|
|
103
|
+
function safeJsonParse(text) {
|
|
104
|
+
try {
|
|
105
|
+
const parsed = JSON.parse(text);
|
|
106
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : null;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/** 端点载荷字符串键提取(坏形/缺席 → undefined——调用面判形折错) */
|
|
113
|
+
function strField(parsed, key) {
|
|
114
|
+
return parsed !== null && typeof parsed[key] === 'string' ? parsed[key] : undefined;
|
|
115
|
+
}
|
|
116
|
+
/** 端点载荷数值键提取(同上——expires_in/interval 等) */
|
|
117
|
+
function numField(parsed, key) {
|
|
118
|
+
return parsed !== null && typeof parsed[key] === 'number' ? parsed[key] : undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* device-code 流编舞(纯舞步——存储/窗/注册表全不在场,消费位 =
|
|
122
|
+
* 人面动词的 io.runDeviceCode 实现)。
|
|
123
|
+
*/
|
|
124
|
+
export async function runDeviceCodeFlow(def, io) {
|
|
125
|
+
// 1. 发起设备授权(POST form:client_id + scope——scope 空数组/缺省不带)
|
|
126
|
+
let device;
|
|
127
|
+
try {
|
|
128
|
+
const body = formUrlEncode({
|
|
129
|
+
client_id: def.clientId,
|
|
130
|
+
...(def.scopes !== undefined && def.scopes.length > 0 ? { scope: def.scopes.join(' ') } : {}),
|
|
131
|
+
});
|
|
132
|
+
const res = await io.fetchFn(def.deviceAuthUrl, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
135
|
+
body,
|
|
136
|
+
});
|
|
137
|
+
const text = await res.text();
|
|
138
|
+
if (!res.ok) {
|
|
139
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `设备授权端点非 200(${res.status}):${text.slice(0, 200)}`);
|
|
140
|
+
}
|
|
141
|
+
const parsed = safeJsonParse(text);
|
|
142
|
+
const deviceCode = strField(parsed, 'device_code');
|
|
143
|
+
const userCode = strField(parsed, 'user_code');
|
|
144
|
+
const verificationUri = strField(parsed, 'verification_uri');
|
|
145
|
+
const expiresIn = numField(parsed, 'expires_in');
|
|
146
|
+
if (deviceCode === undefined ||
|
|
147
|
+
userCode === undefined ||
|
|
148
|
+
verificationUri === undefined ||
|
|
149
|
+
expiresIn === undefined) {
|
|
150
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `设备授权端点载荷坏形(device_code/user_code/verification_uri/expires_in 四键须齐——现得 ${text.slice(0, 200)})`);
|
|
151
|
+
}
|
|
152
|
+
device = {
|
|
153
|
+
deviceCode,
|
|
154
|
+
userCode,
|
|
155
|
+
verificationUri,
|
|
156
|
+
expiresIn,
|
|
157
|
+
interval: numField(parsed, 'interval'),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
if (err instanceof BaseError)
|
|
162
|
+
throw err;
|
|
163
|
+
// 传输面错(网络拒/丢包等)——兜底码包装
|
|
164
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `设备授权端点传输失败:${errText(err)}`);
|
|
165
|
+
}
|
|
166
|
+
// 2. present 用户(呈现面由宿主注入——TUI notify 直达)
|
|
167
|
+
io.present([
|
|
168
|
+
`oauth 授权流「${def.name}」已发起——请在浏览器完成授权:`,
|
|
169
|
+
` 打开:${device.verificationUri}`,
|
|
170
|
+
` 输入代码:${device.userCode}`,
|
|
171
|
+
` (${Math.ceil(device.expiresIn / 60)} 分钟内有效)`,
|
|
172
|
+
].join('\n'));
|
|
173
|
+
// 3. 轮询 token 端点(authorization_pending 续轮;slow_down 提 interval+5s)
|
|
174
|
+
const deadline = io.now() + device.expiresIn * 1000;
|
|
175
|
+
let intervalMs = (device.interval ?? 5) * 1000; // RFC 缺省 5s
|
|
176
|
+
for (;;) {
|
|
177
|
+
if (io.now() >= deadline) {
|
|
178
|
+
throw new BaseError('CREDENTIALS_OAUTH_EXPIRED', '设备码轮询窗已过期(expires_in 到限)——请重新发起 /credentials oauth。');
|
|
179
|
+
}
|
|
180
|
+
const res = await io.fetchFn(def.tokenUrl, {
|
|
181
|
+
method: 'POST',
|
|
182
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
183
|
+
body: formUrlEncode({
|
|
184
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
185
|
+
device_code: device.deviceCode,
|
|
186
|
+
client_id: def.clientId,
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
const text = await res.text();
|
|
190
|
+
const parsed = safeJsonParse(text);
|
|
191
|
+
if (res.ok) {
|
|
192
|
+
const accessToken = strField(parsed, 'access_token');
|
|
193
|
+
if (accessToken === undefined) {
|
|
194
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `token 端点 200 但缺 access_token(${text.slice(0, 200)})`);
|
|
195
|
+
}
|
|
196
|
+
const refreshToken = strField(parsed, 'refresh_token');
|
|
197
|
+
const expiresIn = numField(parsed, 'expires_in');
|
|
198
|
+
return {
|
|
199
|
+
accessToken,
|
|
200
|
+
...(refreshToken !== undefined ? { refreshToken } : {}),
|
|
201
|
+
...(expiresIn !== undefined ? { expiresAt: io.now() + expiresIn * 1000 } : {}),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// RFC 8628 §3.5 错误分流
|
|
205
|
+
const error = strField(parsed, 'error');
|
|
206
|
+
if (error === 'authorization_pending') {
|
|
207
|
+
// 授权未完成——续轮
|
|
208
|
+
}
|
|
209
|
+
else if (error === 'slow_down') {
|
|
210
|
+
intervalMs += 5000; // 提速降频(RFC:至少 +5s)
|
|
211
|
+
}
|
|
212
|
+
else if (error === 'access_denied') {
|
|
213
|
+
throw new BaseError('CREDENTIALS_OAUTH_DENIED', '用户在授权页拒绝了本次授权(access_denied)。');
|
|
214
|
+
}
|
|
215
|
+
else if (error === 'expired_token') {
|
|
216
|
+
throw new BaseError('CREDENTIALS_OAUTH_EXPIRED', '设备码已过期(expired_token)——请重新发起 /credentials oauth。');
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `token 端点非预期应答(${res.status}${error === undefined ? '' : ` error=${error}`}):${text.slice(0, 200)}`);
|
|
220
|
+
}
|
|
221
|
+
await io.sleep(intervalMs);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* refresh 换新(grant_type=refresh_token——刷新链巡检腿消费)。
|
|
226
|
+
* invalid_grant → EXPIRED(refresh 凭据失效/被撤销——授权态坏,重试无益,
|
|
227
|
+
* 唯一出路 = 重新发起授权流);其余非 200 → 兜底码(传输/端点抖动,重试可期)。
|
|
228
|
+
*/
|
|
229
|
+
export async function refreshOAuthToken(def, refreshToken, io) {
|
|
230
|
+
let res;
|
|
231
|
+
try {
|
|
232
|
+
res = await io.fetchFn(def.tokenUrl, {
|
|
233
|
+
method: 'POST',
|
|
234
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
235
|
+
body: formUrlEncode({ grant_type: 'refresh_token', refresh_token: refreshToken, client_id: def.clientId }),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `token 端点传输失败:${errText(err)}`);
|
|
240
|
+
}
|
|
241
|
+
const text = await res.text();
|
|
242
|
+
const parsed = safeJsonParse(text);
|
|
243
|
+
if (res.ok) {
|
|
244
|
+
const accessToken = strField(parsed, 'access_token');
|
|
245
|
+
if (accessToken === undefined) {
|
|
246
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `refresh 应答 200 但缺 access_token(${text.slice(0, 200)})`);
|
|
247
|
+
}
|
|
248
|
+
// RFC 6749 §6:新 refresh_token 可选——不下发即复用旧值(链不动刷新行)
|
|
249
|
+
const refreshToken = strField(parsed, 'refresh_token');
|
|
250
|
+
const expiresIn = numField(parsed, 'expires_in');
|
|
251
|
+
return {
|
|
252
|
+
accessToken,
|
|
253
|
+
...(refreshToken !== undefined ? { refreshToken } : {}),
|
|
254
|
+
...(expiresIn !== undefined ? { expiresAt: io.now() + expiresIn * 1000 } : {}),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
const error = strField(parsed, 'error');
|
|
258
|
+
if (error === 'invalid_grant') {
|
|
259
|
+
throw new BaseError('CREDENTIALS_OAUTH_EXPIRED', `refresh 凭据失效(invalid_grant——被撤销或过期)——重新发起授权流 /credentials oauth ${def.name}。`);
|
|
260
|
+
}
|
|
261
|
+
throw new BaseError('CREDENTIALS_OAUTH_FLOW_FAILED', `refresh 应答非预期(${res.status}${error === undefined ? '' : ` error=${error}`}):${text.slice(0, 200)}`);
|
|
262
|
+
}
|
|
263
|
+
/** 非 BaseError 错误折文案(兜底包装位共用——不出对象字面量防泄漏) */
|
|
264
|
+
function errText(err) {
|
|
265
|
+
return err instanceof Error ? err.message : String(err);
|
|
266
|
+
}
|