@armadra/agent 0.5.0 → 0.6.0
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/CHANGELOG.md +177 -294
- package/CHANGELOG.zh-CN.md +442 -0
- package/README.md +408 -364
- package/README.zh-CN.md +705 -0
- package/dist/agent/session-plan.js +2 -2
- package/dist/agent/session-state.d.ts +3 -0
- package/dist/agent/session-state.js +20 -0
- package/dist/agent/session-subagent.d.ts +15 -1
- package/dist/agent/session-subagent.js +26 -3
- package/dist/agent/session-telemetry.d.ts +16 -2
- package/dist/agent/session-telemetry.js +29 -7
- package/dist/agent/session-tools.js +5 -2
- package/dist/agent/session-trace-writer.d.ts +27 -0
- package/dist/agent/session-trace-writer.js +253 -0
- package/dist/agent/session.d.ts +2 -0
- package/dist/agent/session.js +12 -0
- package/dist/agent/subagent-direct.d.ts +20 -0
- package/dist/agent/subagent-direct.js +76 -0
- package/dist/agent/subagent-registry.d.ts +8 -1
- package/dist/agent/subagent-registry.js +28 -1
- package/dist/agent/system-prompt.d.ts +3 -1
- package/dist/agent/system-prompt.js +3 -0
- package/dist/agent/tool-runner.d.ts +2 -1
- package/dist/agent/tool-runner.js +5 -4
- package/dist/agent/types-w6.d.ts +39 -0
- package/dist/agent/types-w6.js +4 -0
- package/dist/agent/types.d.ts +5 -2
- package/dist/agents/external.js +1 -1
- package/dist/agents/task-record.d.ts +74 -1
- package/dist/agents/task-record.js +100 -0
- package/dist/ai/apis/cache-params.js +2 -1
- package/dist/ai/apis/chatgpt-backend.d.ts +52 -0
- package/dist/ai/apis/chatgpt-backend.js +224 -0
- package/dist/ai/apis/chatgpt-rate-limits.d.ts +26 -0
- package/dist/ai/apis/chatgpt-rate-limits.js +91 -0
- package/dist/ai/apis/openai-responses-request.d.ts +2 -1
- package/dist/ai/apis/openai-responses-request.js +8 -2
- package/dist/ai/apis/openai-responses.d.ts +2 -0
- package/dist/ai/apis/openai-responses.js +51 -16
- package/dist/ai/overflow.d.ts +1 -1
- package/dist/ai/overflow.js +10 -7
- package/dist/ai/providers/auth.d.ts +15 -2
- package/dist/ai/providers/auth.js +69 -2
- package/dist/ai/providers/builtin.js +27 -0
- package/dist/ai/providers/catalog-data.js +1 -0
- package/dist/ai/providers/enrich.js +2 -7
- package/dist/ai/providers/models-dev-cache.js +15 -19
- package/dist/ai/providers/models-dev-snapshot.js +8 -7
- package/dist/ai/providers/models-dev.js +5 -15
- package/dist/ai/providers/suggest.js +2 -16
- package/dist/ai/types.d.ts +27 -1
- package/dist/auth/chatgpt/backend-client.d.ts +26 -0
- package/dist/auth/chatgpt/backend-client.js +74 -0
- package/dist/auth/chatgpt/claims.d.ts +17 -0
- package/dist/auth/chatgpt/claims.js +46 -0
- package/dist/auth/chatgpt/cli.d.ts +33 -0
- package/dist/auth/chatgpt/cli.js +252 -0
- package/dist/auth/chatgpt/doctor.d.ts +17 -0
- package/dist/auth/chatgpt/doctor.js +36 -0
- package/dist/auth/chatgpt/host-id.d.ts +7 -0
- package/dist/auth/chatgpt/host-id.js +26 -0
- package/dist/auth/chatgpt/login.d.ts +30 -0
- package/dist/auth/chatgpt/login.js +130 -0
- package/dist/auth/chatgpt/presets.d.ts +66 -0
- package/dist/auth/chatgpt/presets.js +120 -0
- package/dist/auth/chatgpt/quota-text.d.ts +13 -0
- package/dist/auth/chatgpt/quota-text.js +36 -0
- package/dist/auth/oauth/browser.d.ts +12 -0
- package/dist/auth/oauth/browser.js +27 -0
- package/dist/auth/oauth/callback-server.d.ts +47 -0
- package/dist/auth/oauth/callback-server.js +147 -0
- package/dist/auth/oauth/flows.d.ts +62 -0
- package/dist/auth/oauth/flows.js +113 -0
- package/dist/auth/oauth/jwt.d.ts +24 -0
- package/dist/auth/oauth/jwt.js +81 -0
- package/dist/auth/oauth/live.d.ts +21 -0
- package/dist/auth/oauth/live.js +25 -0
- package/dist/auth/oauth/oidc.d.ts +26 -0
- package/dist/auth/oauth/oidc.js +72 -0
- package/dist/auth/oauth/pkce.d.ts +15 -0
- package/dist/auth/oauth/pkce.js +20 -0
- package/dist/auth/oauth/refresh.d.ts +35 -0
- package/dist/auth/oauth/refresh.js +129 -0
- package/dist/auth/oauth/token-client.d.ts +33 -0
- package/dist/auth/oauth/token-client.js +121 -0
- package/dist/auth/oauth/token-store.d.ts +23 -0
- package/dist/auth/oauth/token-store.js +125 -0
- package/dist/auth/testing/fake-oauth.d.ts +73 -0
- package/dist/auth/testing/fake-oauth.js +271 -0
- package/dist/auth/testing/refresh-child.d.ts +5 -0
- package/dist/auth/testing/refresh-child.js +16 -0
- package/dist/bundle/ama.cjs +28307 -13241
- package/dist/checkpoints/backend.js +6 -5
- package/dist/checkpoints/restore.js +3 -2
- package/dist/checkpoints/settings.js +2 -1
- package/dist/checkpoints/shadow-git.js +7 -6
- package/dist/checkpoints/shadow-restore.js +3 -2
- package/dist/checkpoints/tracker.js +4 -3
- package/dist/cli/args.d.ts +9 -2
- package/dist/cli/args.js +74 -27
- package/dist/cli/bootstrap.js +53 -31
- package/dist/cli/choice-prompt.d.ts +58 -0
- package/dist/cli/choice-prompt.js +142 -0
- package/dist/cli/codemode-notice.js +2 -2
- package/dist/cli/compose-agents.js +2 -1
- package/dist/cli/compose-extensions.js +2 -0
- package/dist/cli/compose-memory.d.ts +53 -0
- package/dist/cli/compose-memory.js +127 -0
- package/dist/cli/compose-providers.d.ts +5 -0
- package/dist/cli/compose-providers.js +32 -2
- package/dist/cli/compose-session.d.ts +5 -0
- package/dist/cli/compose-session.js +27 -13
- package/dist/cli/compose-store.d.ts +2 -0
- package/dist/cli/compose-store.js +9 -5
- package/dist/cli/compose.d.ts +2 -0
- package/dist/cli/compose.js +13 -5
- package/dist/cli/default-model.d.ts +0 -2
- package/dist/cli/default-model.js +5 -8
- package/dist/cli/deps.d.ts +5 -1
- package/dist/cli/exit-codes.d.ts +1 -1
- package/dist/cli/exit-codes.js +7 -16
- package/dist/cli/from-prompt.js +3 -2
- package/dist/cli/help-text.d.ts +2 -1
- package/dist/cli/help-text.js +5 -99
- package/dist/cli/main.d.ts +5 -0
- package/dist/cli/main.js +21 -2
- package/dist/cli/proxy.js +14 -9
- package/dist/cli/runtime.d.ts +5 -0
- package/dist/cli/startup-screen.js +20 -27
- package/dist/cli/startup-steps.js +6 -8
- package/dist/cli/subcommands/auth.d.ts +6 -3
- package/dist/cli/subcommands/auth.js +52 -22
- package/dist/cli/subcommands/config-set.d.ts +21 -0
- package/dist/cli/subcommands/config-set.js +137 -0
- package/dist/cli/subcommands/config.d.ts +2 -1
- package/dist/cli/subcommands/config.js +51 -37
- package/dist/cli/subcommands/doctor.d.ts +2 -1
- package/dist/cli/subcommands/doctor.js +97 -58
- package/dist/cli/subcommands/init.d.ts +1 -1
- package/dist/cli/subcommands/init.js +8 -7
- package/dist/cli/subcommands/memory.d.ts +18 -0
- package/dist/cli/subcommands/memory.js +189 -0
- package/dist/cli/subcommands/model-meta.js +11 -9
- package/dist/cli/subcommands/models-cache-probe.d.ts +1 -1
- package/dist/cli/subcommands/models-cache-probe.js +26 -32
- package/dist/cli/subcommands/models-discover.d.ts +3 -0
- package/dist/cli/subcommands/models-discover.js +44 -21
- package/dist/cli/subcommands/models.d.ts +1 -1
- package/dist/cli/subcommands/models.js +29 -22
- package/dist/cli/subcommands/probe-runner.js +8 -7
- package/dist/cli/subcommands/providers-list.js +32 -21
- package/dist/cli/subcommands/providers-plan.js +24 -25
- package/dist/cli/subcommands/providers-probe.js +5 -6
- package/dist/cli/subcommands/providers.d.ts +2 -2
- package/dist/cli/subcommands/providers.js +48 -59
- package/dist/cli/subcommands/sessions-export.d.ts +1 -1
- package/dist/cli/subcommands/sessions-export.js +10 -9
- package/dist/cli/subcommands/sessions-search.d.ts +1 -1
- package/dist/cli/subcommands/sessions-search.js +11 -10
- package/dist/cli/subcommands/sessions-trace.d.ts +30 -0
- package/dist/cli/subcommands/sessions-trace.js +156 -0
- package/dist/cli/subcommands/sessions.d.ts +4 -3
- package/dist/cli/subcommands/sessions.js +40 -37
- package/dist/cli/subcommands/stats.d.ts +1 -1
- package/dist/cli/subcommands/stats.js +49 -30
- package/dist/cli/system-prompt-arg.js +3 -2
- package/dist/codemode/capability.js +3 -2
- package/dist/compaction/post-compact.d.ts +1 -0
- package/dist/compaction/post-compact.js +15 -1
- package/dist/compaction/prune-tier.d.ts +1 -1
- package/dist/compaction/prune-tier.js +3 -3
- package/dist/compaction/serialize.js +2 -1
- package/dist/config/auth-file.d.ts +12 -2
- package/dist/config/auth-file.js +32 -10
- package/dist/config/checker.js +12 -11
- package/dist/config/context-files.js +3 -2
- package/dist/config/edit.d.ts +106 -0
- package/dist/config/edit.js +350 -0
- package/dist/config/init.d.ts +4 -3
- package/dist/config/init.js +10 -18
- package/dist/config/json-schema.d.ts +2 -1
- package/dist/config/json-schema.js +101 -68
- package/dist/config/key-docs.d.ts +18 -7
- package/dist/config/key-docs.js +43 -128
- package/dist/config/load.js +7 -6
- package/dist/config/merge.d.ts +4 -1
- package/dist/config/merge.js +42 -22
- package/dist/config/paths.js +6 -5
- package/dist/config/profile.d.ts +5 -1
- package/dist/config/profile.js +7 -2
- package/dist/config/schema-w5.js +3 -2
- package/dist/config/schema-w6.d.ts +19 -0
- package/dist/config/schema-w6.js +115 -0
- package/dist/config/schema.js +34 -20
- package/dist/config/settings-registry.d.ts +78 -0
- package/dist/config/settings-registry.js +184 -0
- package/dist/config/types-w6.d.ts +109 -0
- package/dist/config/types-w6.js +19 -0
- package/dist/config/types.d.ts +11 -8
- package/dist/config/types.js +1 -0
- package/dist/drivers/acp/client.js +1 -1
- package/dist/drivers/acp/driver.js +13 -8
- package/dist/drivers/agents.js +4 -3
- package/dist/drivers/base.js +3 -2
- package/dist/drivers/host-runners.js +2 -1
- package/dist/drivers/native/claude-stream.js +12 -11
- package/dist/drivers/native/codex-app-server.js +17 -12
- package/dist/drivers/native/oneshot.js +8 -7
- package/dist/drivers/pool.js +1 -1
- package/dist/drivers/runner.d.ts +3 -1
- package/dist/drivers/runner.js +68 -20
- package/dist/drivers/turn.js +1 -1
- package/dist/hooks/config.js +3 -2
- package/dist/hooks/protocol.js +18 -12
- package/dist/host/api-impl.js +11 -10
- package/dist/host/loader.js +9 -8
- package/dist/host/types.d.ts +3 -0
- package/dist/i18n/catalog.d.ts +2169 -0
- package/dist/i18n/catalog.js +72 -0
- package/dist/i18n/format.d.ts +21 -0
- package/dist/i18n/format.js +58 -0
- package/dist/i18n/index.d.ts +58 -0
- package/dist/i18n/index.js +72 -0
- package/dist/i18n/messages/agents.d.ts +84 -0
- package/dist/i18n/messages/agents.js +85 -0
- package/dist/i18n/messages/approval.d.ts +99 -0
- package/dist/i18n/messages/approval.js +100 -0
- package/dist/i18n/messages/auth.d.ts +187 -0
- package/dist/i18n/messages/auth.js +189 -0
- package/dist/i18n/messages/cli-args.d.ts +46 -0
- package/dist/i18n/messages/cli-args.js +46 -0
- package/dist/i18n/messages/cli-help.d.ts +12 -0
- package/dist/i18n/messages/cli-help.js +244 -0
- package/dist/i18n/messages/cli.d.ts +336 -0
- package/dist/i18n/messages/cli.js +311 -0
- package/dist/i18n/messages/config-keys.d.ts +289 -0
- package/dist/i18n/messages/config-keys.js +292 -0
- package/dist/i18n/messages/config.d.ts +502 -0
- package/dist/i18n/messages/config.js +239 -0
- package/dist/i18n/messages/drivers.d.ts +123 -0
- package/dist/i18n/messages/drivers.js +124 -0
- package/dist/i18n/messages/errors.d.ts +83 -0
- package/dist/i18n/messages/errors.js +171 -0
- package/dist/i18n/messages/interactive-line.d.ts +61 -0
- package/dist/i18n/messages/interactive-line.js +69 -0
- package/dist/i18n/messages/interactive-startup.d.ts +103 -0
- package/dist/i18n/messages/interactive-startup.js +104 -0
- package/dist/i18n/messages/interactive-view.d.ts +140 -0
- package/dist/i18n/messages/interactive-view.js +141 -0
- package/dist/i18n/messages/interactive.d.ts +493 -0
- package/dist/i18n/messages/interactive.js +248 -0
- package/dist/i18n/messages/memory.d.ts +123 -0
- package/dist/i18n/messages/memory.js +128 -0
- package/dist/i18n/messages/panels.d.ts +190 -0
- package/dist/i18n/messages/panels.js +189 -0
- package/dist/i18n/messages/permissions.d.ts +126 -0
- package/dist/i18n/messages/permissions.js +151 -0
- package/dist/i18n/messages/plan.d.ts +123 -0
- package/dist/i18n/messages/plan.js +124 -0
- package/dist/i18n/messages/print.d.ts +86 -0
- package/dist/i18n/messages/print.js +91 -0
- package/dist/i18n/messages/report.d.ts +391 -0
- package/dist/i18n/messages/report.js +490 -0
- package/dist/i18n/messages/rewind.d.ts +198 -0
- package/dist/i18n/messages/rewind.js +217 -0
- package/dist/i18n/messages/session.d.ts +230 -0
- package/dist/i18n/messages/session.js +258 -0
- package/dist/i18n/messages/settings.d.ts +348 -0
- package/dist/i18n/messages/settings.js +336 -0
- package/dist/i18n/messages/subcommands-config.d.ts +111 -0
- package/dist/i18n/messages/subcommands-config.js +127 -0
- package/dist/i18n/messages/subcommands.d.ts +548 -0
- package/dist/i18n/messages/subcommands.js +474 -0
- package/dist/i18n/messages/trace.d.ts +243 -0
- package/dist/i18n/messages/trace.js +238 -0
- package/dist/i18n/types.d.ts +15 -0
- package/dist/i18n/types.js +7 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2 -0
- package/dist/memory/edit.d.ts +24 -0
- package/dist/memory/edit.js +63 -0
- package/dist/memory/frontmatter.d.ts +37 -0
- package/dist/memory/frontmatter.js +79 -0
- package/dist/memory/index.d.ts +28 -0
- package/dist/memory/index.js +126 -0
- package/dist/memory/lock.d.ts +17 -0
- package/dist/memory/lock.js +62 -0
- package/dist/memory/paths.d.ts +57 -0
- package/dist/memory/paths.js +175 -0
- package/dist/memory/report.d.ts +36 -0
- package/dist/memory/report.js +86 -0
- package/dist/memory/runtime.d.ts +42 -0
- package/dist/memory/runtime.js +36 -0
- package/dist/memory/secrets.d.ts +8 -0
- package/dist/memory/secrets.js +27 -0
- package/dist/memory/section.d.ts +28 -0
- package/dist/memory/section.js +59 -0
- package/dist/memory/store.d.ts +70 -0
- package/dist/memory/store.js +284 -0
- package/dist/memory/tool.d.ts +31 -0
- package/dist/memory/tool.js +94 -0
- package/dist/modes/acp/acp-events.js +2 -1
- package/dist/modes/acp/acp-server.js +13 -12
- package/dist/modes/commands-core.d.ts +17 -0
- package/dist/modes/commands-core.js +101 -61
- package/dist/modes/image-input.js +20 -6
- package/dist/modes/interactive/agent-bar.d.ts +95 -0
- package/dist/modes/interactive/agent-bar.js +248 -0
- package/dist/modes/interactive/agent-panels.js +27 -26
- package/dist/modes/interactive/agent-transcript.d.ts +47 -0
- package/dist/modes/interactive/agent-transcript.js +191 -0
- package/dist/modes/interactive/agent-ui.d.ts +36 -1
- package/dist/modes/interactive/agent-ui.js +180 -14
- package/dist/modes/interactive/agent-view.d.ts +72 -0
- package/dist/modes/interactive/agent-view.js +281 -0
- package/dist/modes/interactive/approval-dialog.js +46 -34
- package/dist/modes/interactive/clipboard-paste.d.ts +2 -2
- package/dist/modes/interactive/clipboard-paste.js +9 -4
- package/dist/modes/interactive/commands.d.ts +22 -1
- package/dist/modes/interactive/commands.js +93 -38
- package/dist/modes/interactive/completion.js +2 -1
- package/dist/modes/interactive/config-panel.d.ts +70 -0
- package/dist/modes/interactive/config-panel.js +319 -0
- package/dist/modes/interactive/config-ui.d.ts +96 -0
- package/dist/modes/interactive/config-ui.js +397 -0
- package/dist/modes/interactive/confirm-dialog.d.ts +49 -0
- package/dist/modes/interactive/confirm-dialog.js +120 -0
- package/dist/modes/interactive/event-notices.js +11 -9
- package/dist/modes/interactive/interactive-mode.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode.js +25 -22
- package/dist/modes/interactive/key-dispatch.d.ts +18 -0
- package/dist/modes/interactive/key-dispatch.js +39 -13
- package/dist/modes/interactive/line/line-editor.js +2 -1
- package/dist/modes/interactive/line/line-mode.d.ts +2 -0
- package/dist/modes/interactive/line/line-mode.js +24 -4
- package/dist/modes/interactive/line/line-render.js +25 -24
- package/dist/modes/interactive/memory-panel.d.ts +64 -0
- package/dist/modes/interactive/memory-panel.js +221 -0
- package/dist/modes/interactive/message-view.d.ts +4 -2
- package/dist/modes/interactive/message-view.js +42 -33
- package/dist/modes/interactive/panels.js +39 -31
- package/dist/modes/interactive/pickers.d.ts +2 -1
- package/dist/modes/interactive/pickers.js +10 -6
- package/dist/modes/interactive/plan-command.d.ts +1 -1
- package/dist/modes/interactive/plan-command.js +24 -27
- package/dist/modes/interactive/plan-dialog.js +18 -17
- package/dist/modes/interactive/plan-flow.js +6 -5
- package/dist/modes/interactive/rewind-command.d.ts +1 -1
- package/dist/modes/interactive/rewind-command.js +18 -16
- package/dist/modes/interactive/rewind-flow.d.ts +0 -1
- package/dist/modes/interactive/rewind-flow.js +13 -14
- package/dist/modes/interactive/rewind-list.js +7 -6
- package/dist/modes/interactive/rewind-panel.js +40 -40
- package/dist/modes/interactive/rewind-text.d.ts +5 -3
- package/dist/modes/interactive/rewind-text.js +48 -38
- package/dist/modes/interactive/run-indicator.js +15 -12
- package/dist/modes/interactive/session-events.js +3 -2
- package/dist/modes/interactive/startup-header.js +22 -23
- package/dist/modes/interactive/startup-ui.js +41 -29
- package/dist/modes/interactive/status-area.d.ts +2 -0
- package/dist/modes/interactive/status-area.js +8 -2
- package/dist/modes/interactive/status-bar.js +4 -3
- package/dist/modes/interactive/subagent-view.js +9 -7
- package/dist/modes/interactive/tasks-report.d.ts +6 -0
- package/dist/modes/interactive/tasks-report.js +28 -31
- package/dist/modes/interactive/terminal-setup.d.ts +9 -0
- package/dist/modes/interactive/terminal-setup.js +23 -0
- package/dist/modes/interactive/tool-summary.js +19 -17
- package/dist/modes/interactive/tool-view.js +7 -4
- package/dist/modes/interactive/trace-view.d.ts +89 -0
- package/dist/modes/interactive/trace-view.js +332 -0
- package/dist/modes/print/print-mode.js +14 -15
- package/dist/modes/rpc/commands.js +7 -3
- package/dist/modes/rpc/rpc-mode.js +9 -3
- package/dist/modes/session-report.d.ts +2 -0
- package/dist/modes/session-report.js +101 -105
- package/dist/modes/startup-ui-text.js +11 -6
- package/dist/permissions/bypass.d.ts +30 -0
- package/dist/permissions/bypass.js +45 -0
- package/dist/permissions/memory-class.d.ts +23 -0
- package/dist/permissions/memory-class.js +54 -0
- package/dist/permissions/modes.d.ts +3 -3
- package/dist/permissions/modes.js +21 -16
- package/dist/permissions/pipeline.d.ts +1 -1
- package/dist/permissions/pipeline.js +9 -2
- package/dist/permissions/preview.js +49 -42
- package/dist/permissions/rules.js +5 -2
- package/dist/permissions/types.d.ts +4 -0
- package/dist/plan/store.js +2 -1
- package/dist/rpc.d.ts +39 -1
- package/dist/rpc.js +3 -0
- package/dist/sandbox/bash.js +11 -8
- package/dist/sandbox/detect.js +14 -11
- package/dist/sdk.d.ts +17 -1
- package/dist/sdk.js +29 -7
- package/dist/session/export.js +31 -30
- package/dist/session/manager.d.ts +1 -1
- package/dist/session/manager.js +5 -3
- package/dist/session/reuse.js +5 -4
- package/dist/session/scan.js +3 -2
- package/dist/session/stats-aggregate.d.ts +3 -1
- package/dist/session/stats-aggregate.js +5 -1
- package/dist/session/stats-index.js +2 -1
- package/dist/session/stats-scan.d.ts +4 -1
- package/dist/session/stats-scan.js +6 -2
- package/dist/session/store.d.ts +11 -0
- package/dist/session/store.js +48 -1
- package/dist/session/types.d.ts +2 -0
- package/dist/skills/builtin.js +2 -1
- package/dist/tools/image-file.d.ts +26 -1
- package/dist/tools/image-file.js +55 -13
- package/dist/tools/presets.d.ts +14 -1
- package/dist/tools/presets.js +3 -3
- package/dist/tools/registry.js +1 -1
- package/dist/tools/truncate.d.ts +1 -1
- package/dist/tools/truncate.js +2 -2
- package/dist/tools/types.d.ts +17 -1
- package/dist/trace/build-index.d.ts +91 -0
- package/dist/trace/build-index.js +127 -0
- package/dist/trace/build-nodes.d.ts +24 -0
- package/dist/trace/build-nodes.js +279 -0
- package/dist/trace/build-util.d.ts +43 -0
- package/dist/trace/build-util.js +120 -0
- package/dist/trace/build.d.ts +82 -0
- package/dist/trace/build.js +382 -0
- package/dist/trace/detail.d.ts +35 -0
- package/dist/trace/detail.js +205 -0
- package/dist/trace/flatten.d.ts +69 -0
- package/dist/trace/flatten.js +173 -0
- package/dist/trace/format.d.ts +47 -0
- package/dist/trace/format.js +329 -0
- package/dist/trace/html-template.d.ts +19 -0
- package/dist/trace/html-template.js +150 -0
- package/dist/trace/html.d.ts +104 -0
- package/dist/trace/html.js +292 -0
- package/dist/trace/preview.d.ts +29 -0
- package/dist/trace/preview.js +59 -0
- package/dist/trace/query-session.d.ts +14 -0
- package/dist/trace/query-session.js +38 -0
- package/dist/trace/query.d.ts +55 -0
- package/dist/trace/query.js +188 -0
- package/dist/trace/session.d.ts +40 -0
- package/dist/trace/session.js +168 -0
- package/dist/trace/types.d.ts +243 -0
- package/dist/trace/types.js +13 -0
- package/dist/tui/components/editor-paste.d.ts +2 -0
- package/dist/tui/components/editor-paste.js +6 -3
- package/dist/tui/components/editor.js +2 -2
- package/dist/tui/components/loader.d.ts +3 -0
- package/dist/tui/components/loader.js +14 -0
- package/dist/tui/components/settings-list.d.ts +61 -0
- package/dist/tui/components/settings-list.js +135 -0
- package/dist/tui/keybindings.d.ts +6 -1
- package/dist/tui/keybindings.js +18 -6
- package/dist/tui.d.ts +1 -0
- package/dist/tui.js +2 -0
- package/docs/en/host-api.md +167 -0
- package/docs/en/permissions.md +214 -0
- package/docs/en/providers.md +625 -0
- package/docs/en/rpc.md +361 -0
- package/docs/en/sessions.md +219 -0
- package/docs/en/tui.md +527 -0
- package/docs/host-api.md +18 -17
- package/docs/memory.md +172 -0
- package/docs/permissions.md +3 -2
- package/docs/providers.md +67 -1
- package/docs/rpc.md +31 -3
- package/docs/session-format.md +8 -2
- package/docs/sessions.md +29 -1
- package/docs/tui.md +189 -21
- package/package.json +8 -3
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT 解码与 JWKS 验签(R6 §4.11:`createPublicKey` + `verify`,零依赖)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 支持 RS256 / RS384 / RS512 / PS256 / ES256 / ES384;其它 alg(含 `none`)一律拒绝。
|
|
5
|
+
* 错误只带原因码,不带 token 原文。
|
|
6
|
+
*/
|
|
7
|
+
import { createPublicKey, verify, constants } from "node:crypto";
|
|
8
|
+
import { AmaError } from "../../errors.js";
|
|
9
|
+
function invalid(reason) {
|
|
10
|
+
return new AmaError("oauth_invalid_token", `invalid id_token: ${reason}`, { detail: { reason } });
|
|
11
|
+
}
|
|
12
|
+
function json(segment, what) {
|
|
13
|
+
try {
|
|
14
|
+
const value = JSON.parse(Buffer.from(segment ?? "", "base64url").toString("utf8"));
|
|
15
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value))
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
// 落到下面
|
|
20
|
+
}
|
|
21
|
+
throw invalid(`malformed ${what}`);
|
|
22
|
+
}
|
|
23
|
+
export function decodeJwt(token) {
|
|
24
|
+
const parts = token.split(".");
|
|
25
|
+
if (parts.length !== 3)
|
|
26
|
+
throw invalid("malformed");
|
|
27
|
+
return {
|
|
28
|
+
header: json(parts[0], "header"),
|
|
29
|
+
payload: json(parts[1], "payload"),
|
|
30
|
+
signingInput: `${parts[0]}.${parts[1]}`,
|
|
31
|
+
signature: Buffer.from(parts[2] ?? "", "base64url"),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** 只解码 payload;解不开返回 undefined(codex flavor 取声明用,不验签)。 */
|
|
35
|
+
export function jwtClaims(token) {
|
|
36
|
+
if (token === undefined)
|
|
37
|
+
return undefined;
|
|
38
|
+
try {
|
|
39
|
+
return decodeJwt(token).payload;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const ALGS = {
|
|
46
|
+
RS256: { hash: "sha256" },
|
|
47
|
+
RS384: { hash: "sha384" },
|
|
48
|
+
RS512: { hash: "sha512" },
|
|
49
|
+
PS256: { hash: "sha256", pss: true },
|
|
50
|
+
ES256: { hash: "sha256", ec: true },
|
|
51
|
+
ES384: { hash: "sha384", ec: true },
|
|
52
|
+
};
|
|
53
|
+
/** 按 header 的 alg / kid 在 JWKS 里找钥并验签;不通过抛 `oauth_invalid_token`。 */
|
|
54
|
+
export function verifyJwtSignature(decoded, keys) {
|
|
55
|
+
const alg = typeof decoded.header["alg"] === "string" ? decoded.header["alg"] : "";
|
|
56
|
+
const spec = ALGS[alg];
|
|
57
|
+
if (spec === undefined)
|
|
58
|
+
throw invalid(`unsupported alg ${alg.slice(0, 16) || "(none)"}`);
|
|
59
|
+
const kid = decoded.header["kid"];
|
|
60
|
+
const candidates = keys.filter((key) => (kid === undefined || key.kid === kid) &&
|
|
61
|
+
(key.use === undefined || key.use === "sig") &&
|
|
62
|
+
(key.alg === undefined || key.alg === alg));
|
|
63
|
+
if (candidates.length === 0)
|
|
64
|
+
throw invalid("no matching key");
|
|
65
|
+
for (const jwk of candidates) {
|
|
66
|
+
try {
|
|
67
|
+
const key = createPublicKey({ key: jwk, format: "jwk" });
|
|
68
|
+
const ok = verify(spec.hash, Buffer.from(decoded.signingInput), spec.pss
|
|
69
|
+
? { key, padding: constants.RSA_PKCS1_PSS_PADDING, saltLength: 32 }
|
|
70
|
+
: spec.ec
|
|
71
|
+
? { key, dsaEncoding: "ieee-p1363" }
|
|
72
|
+
: key, decoded.signature);
|
|
73
|
+
if (ok)
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// 这把钥不合用,试下一把
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
throw invalid("bad signature");
|
|
81
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 进程内「活 token」登记([W6-O]):`ApiKeyResolver` 解析出 OAuth access token 时登记它的上下文,协议层
|
|
3
|
+
* (openai-responses 的 ChatGPT 后端)凭手里的 token 取回账户 id、flavor,并在 401 时强制刷新一次。
|
|
4
|
+
*
|
|
5
|
+
* 只存在内存里,不落盘、不进日志;最多保留 32 条(新的挤掉旧的)。
|
|
6
|
+
*/
|
|
7
|
+
import type { ChatGptFlavor } from "../../config/types-w6.js";
|
|
8
|
+
export interface LiveToken {
|
|
9
|
+
provider: string;
|
|
10
|
+
flavor: ChatGptFlavor;
|
|
11
|
+
accountId?: string | undefined;
|
|
12
|
+
planType?: string | undefined;
|
|
13
|
+
/** 刷新永久失败、等用户重新登录:协议层不发请求,直接报 `auth_expired`。 */
|
|
14
|
+
needsLogin?: boolean;
|
|
15
|
+
/** 强制刷新(取锁、重读、必要时调 token 端点);返回新 token,失败抛 `auth_expired`。 */
|
|
16
|
+
refresh(): Promise<string>;
|
|
17
|
+
}
|
|
18
|
+
export declare function registerLiveToken(token: string, context: LiveToken): void;
|
|
19
|
+
export declare function liveToken(token: string | undefined): LiveToken | undefined;
|
|
20
|
+
/** 测试用。 */
|
|
21
|
+
export declare function clearLiveTokens(): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 进程内「活 token」登记([W6-O]):`ApiKeyResolver` 解析出 OAuth access token 时登记它的上下文,协议层
|
|
3
|
+
* (openai-responses 的 ChatGPT 后端)凭手里的 token 取回账户 id、flavor,并在 401 时强制刷新一次。
|
|
4
|
+
*
|
|
5
|
+
* 只存在内存里,不落盘、不进日志;最多保留 32 条(新的挤掉旧的)。
|
|
6
|
+
*/
|
|
7
|
+
const MAX_LIVE = 32;
|
|
8
|
+
const live = new Map();
|
|
9
|
+
export function registerLiveToken(token, context) {
|
|
10
|
+
live.delete(token);
|
|
11
|
+
live.set(token, context);
|
|
12
|
+
while (live.size > MAX_LIVE) {
|
|
13
|
+
const oldest = live.keys().next().value;
|
|
14
|
+
if (oldest === undefined)
|
|
15
|
+
break;
|
|
16
|
+
live.delete(oldest);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export function liveToken(token) {
|
|
20
|
+
return token === undefined ? undefined : live.get(token);
|
|
21
|
+
}
|
|
22
|
+
/** 测试用。 */
|
|
23
|
+
export function clearLiveTokens() {
|
|
24
|
+
live.clear();
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC discovery、JWKS 与 id_token 校验(docs/wave6-plan.md §4.1 id_token 行;R6 §1.1)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* SIWC 必须:JWKS 验签 + `iss` = issuer、`aud` 含签发的 client id、`nonce` = 本次登录的、`exp` 未过
|
|
5
|
+
* (允许 60 s 时钟偏差)。discovery 与 JWKS 每进程按 issuer 缓存一次。
|
|
6
|
+
*/
|
|
7
|
+
import { type Jwk, type JwtPayload } from "./jwt.js";
|
|
8
|
+
import { type FetchLike } from "./token-client.js";
|
|
9
|
+
export interface OidcDiscovery {
|
|
10
|
+
issuer?: string;
|
|
11
|
+
jwksUri?: string;
|
|
12
|
+
revocationEndpoint?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function fetchDiscovery(fetchFn: FetchLike, issuer: string): Promise<OidcDiscovery>;
|
|
15
|
+
export declare function fetchJwks(fetchFn: FetchLike, uri: string): Promise<Jwk[]>;
|
|
16
|
+
/** 测试用:清掉 discovery 缓存。 */
|
|
17
|
+
export declare function clearOidcCache(): void;
|
|
18
|
+
export interface IdTokenExpectations {
|
|
19
|
+
issuer: string;
|
|
20
|
+
/** 签发的 client id;`aud` 必须含它。 */
|
|
21
|
+
audience: string;
|
|
22
|
+
nonce: string;
|
|
23
|
+
now?: number;
|
|
24
|
+
}
|
|
25
|
+
/** 验签并校验声明;返回 payload。 */
|
|
26
|
+
export declare function validateIdToken(fetchFn: FetchLike, idToken: string, expect: IdTokenExpectations): Promise<JwtPayload>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OIDC discovery、JWKS 与 id_token 校验(docs/wave6-plan.md §4.1 id_token 行;R6 §1.1)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* SIWC 必须:JWKS 验签 + `iss` = issuer、`aud` 含签发的 client id、`nonce` = 本次登录的、`exp` 未过
|
|
5
|
+
* (允许 60 s 时钟偏差)。discovery 与 JWKS 每进程按 issuer 缓存一次。
|
|
6
|
+
*/
|
|
7
|
+
import { AmaError } from "../../errors.js";
|
|
8
|
+
import { decodeJwt, verifyJwtSignature } from "./jwt.js";
|
|
9
|
+
import { getJson } from "./token-client.js";
|
|
10
|
+
const discoveryCache = new Map();
|
|
11
|
+
function trimSlash(url) {
|
|
12
|
+
return url.replace(/\/+$/, "");
|
|
13
|
+
}
|
|
14
|
+
export function fetchDiscovery(fetchFn, issuer) {
|
|
15
|
+
const key = trimSlash(issuer);
|
|
16
|
+
let pending = discoveryCache.get(key);
|
|
17
|
+
if (pending === undefined) {
|
|
18
|
+
pending = getJson(fetchFn, `${key}/.well-known/openid-configuration`, "OIDC discovery").then((value) => {
|
|
19
|
+
const str = (k) => typeof value[k] === "string" ? value[k] : undefined;
|
|
20
|
+
const out = {};
|
|
21
|
+
const iss = str("issuer");
|
|
22
|
+
if (iss !== undefined)
|
|
23
|
+
out.issuer = iss;
|
|
24
|
+
const jwks = str("jwks_uri");
|
|
25
|
+
if (jwks !== undefined)
|
|
26
|
+
out.jwksUri = jwks;
|
|
27
|
+
const revoke = str("revocation_endpoint");
|
|
28
|
+
if (revoke !== undefined)
|
|
29
|
+
out.revocationEndpoint = revoke;
|
|
30
|
+
return out;
|
|
31
|
+
});
|
|
32
|
+
pending.catch(() => discoveryCache.delete(key));
|
|
33
|
+
discoveryCache.set(key, pending);
|
|
34
|
+
}
|
|
35
|
+
return pending;
|
|
36
|
+
}
|
|
37
|
+
export async function fetchJwks(fetchFn, uri) {
|
|
38
|
+
const value = await getJson(fetchFn, uri, "JWKS");
|
|
39
|
+
const keys = value["keys"];
|
|
40
|
+
return Array.isArray(keys)
|
|
41
|
+
? keys.filter((k) => typeof k === "object" && k !== null)
|
|
42
|
+
: [];
|
|
43
|
+
}
|
|
44
|
+
/** 测试用:清掉 discovery 缓存。 */
|
|
45
|
+
export function clearOidcCache() {
|
|
46
|
+
discoveryCache.clear();
|
|
47
|
+
}
|
|
48
|
+
function reject(reason) {
|
|
49
|
+
return new AmaError("oauth_invalid_token", `invalid id_token: ${reason}`, { detail: { reason } });
|
|
50
|
+
}
|
|
51
|
+
/** 验签并校验声明;返回 payload。 */
|
|
52
|
+
export async function validateIdToken(fetchFn, idToken, expect) {
|
|
53
|
+
const decoded = decodeJwt(idToken);
|
|
54
|
+
const discovery = await fetchDiscovery(fetchFn, expect.issuer);
|
|
55
|
+
if (discovery.jwksUri === undefined)
|
|
56
|
+
throw reject("issuer has no jwks_uri");
|
|
57
|
+
verifyJwtSignature(decoded, await fetchJwks(fetchFn, discovery.jwksUri));
|
|
58
|
+
const claims = decoded.payload;
|
|
59
|
+
if (trimSlash(String(claims["iss"] ?? "")) !== trimSlash(expect.issuer))
|
|
60
|
+
throw reject("iss");
|
|
61
|
+
const aud = claims["aud"];
|
|
62
|
+
const audiences = Array.isArray(aud) ? aud : [aud];
|
|
63
|
+
if (!audiences.includes(expect.audience))
|
|
64
|
+
throw reject("aud");
|
|
65
|
+
if (claims["nonce"] !== expect.nonce)
|
|
66
|
+
throw reject("nonce");
|
|
67
|
+
const exp = claims["exp"];
|
|
68
|
+
const now = (expect.now ?? Date.now()) / 1000;
|
|
69
|
+
if (typeof exp !== "number" || exp + 60 < now)
|
|
70
|
+
throw reject("exp");
|
|
71
|
+
return claims;
|
|
72
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE(RFC 7636,S256)与随机串(docs/wave6-plan.md §4.1;R6 §4.3)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* verifier 32 字节随机数的 base64url(43 字符),challenge = base64url(SHA-256(verifier)) 无填充;
|
|
5
|
+
* `state` / `nonce` 同样 32 字节随机。只用 `node:crypto`。
|
|
6
|
+
*/
|
|
7
|
+
export interface PkcePair {
|
|
8
|
+
verifier: string;
|
|
9
|
+
challenge: string;
|
|
10
|
+
method: "S256";
|
|
11
|
+
}
|
|
12
|
+
export declare function base64url(input: Buffer | Uint8Array): string;
|
|
13
|
+
export declare function randomToken(bytes?: number): string;
|
|
14
|
+
export declare function s256(verifier: string): string;
|
|
15
|
+
export declare function generatePkce(): PkcePair;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PKCE(RFC 7636,S256)与随机串(docs/wave6-plan.md §4.1;R6 §4.3)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* verifier 32 字节随机数的 base64url(43 字符),challenge = base64url(SHA-256(verifier)) 无填充;
|
|
5
|
+
* `state` / `nonce` 同样 32 字节随机。只用 `node:crypto`。
|
|
6
|
+
*/
|
|
7
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
8
|
+
export function base64url(input) {
|
|
9
|
+
return Buffer.from(input).toString("base64url");
|
|
10
|
+
}
|
|
11
|
+
export function randomToken(bytes = 32) {
|
|
12
|
+
return base64url(randomBytes(bytes));
|
|
13
|
+
}
|
|
14
|
+
export function s256(verifier) {
|
|
15
|
+
return base64url(createHash("sha256").update(verifier, "ascii").digest());
|
|
16
|
+
}
|
|
17
|
+
export function generatePkce() {
|
|
18
|
+
const verifier = randomToken(32);
|
|
19
|
+
return { verifier, challenge: s256(verifier), method: "S256" };
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth access token 的取用与刷新(docs/wave6-plan.md §4.3、D15)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* - 触发:`expiresAt − now < 5 min`,或协议层 401 后 `force`;
|
|
5
|
+
* - 进程内:同一 (auth.json, 供应商) 共用一个刷新 Promise;
|
|
6
|
+
* - 跨进程:`withRefreshLock` 取锁后**重读**文件——`refreshToken` 已变(别的进程刷过)且 token 仍新鲜就直接用;
|
|
7
|
+
* 否则调 token 端点(SIWC 表单 + `resource`;codex JSON),原子写回后释放;
|
|
8
|
+
* - 永久失败(`refresh_token_expired` / `_reused` / `_invalidated` / `invalid_grant` / `invalid_refresh_token` /
|
|
9
|
+
* `token_expired` / `invalid_client` / 401):条目标 `needsLogin: true`(不删 token),抛 `auth_expired`;
|
|
10
|
+
* 暂时失败(网络、5xx、429)退避重试 2 次,仍失败抛原错误。
|
|
11
|
+
*/
|
|
12
|
+
import type { ChatGptAuthConfig, OAuthAuthEntry } from "../../config/types-w6.js";
|
|
13
|
+
import { AmaError } from "../../errors.js";
|
|
14
|
+
import { type FetchLike } from "./token-client.js";
|
|
15
|
+
export declare const REFRESH_MARGIN_MS: number;
|
|
16
|
+
export declare const RETRY_DELAYS_MS: readonly [500, 1500];
|
|
17
|
+
export interface RefreshDeps {
|
|
18
|
+
fetch?: FetchLike | undefined;
|
|
19
|
+
now?: (() => number) | undefined;
|
|
20
|
+
sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
21
|
+
env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
22
|
+
config?: ChatGptAuthConfig | undefined;
|
|
23
|
+
/** 锁等待(测试缩短)。 */
|
|
24
|
+
lockWaitMs?: number | undefined;
|
|
25
|
+
}
|
|
26
|
+
export declare function authExpiredError(provider: string): AmaError;
|
|
27
|
+
export declare function isFresh(entry: OAuthAuthEntry, now: number): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* 取可用的条目:新鲜就直接返回,否则刷新。`force`:协议层 401 后强制刷新(`staleToken` 是被拒的那个,别的
|
|
30
|
+
* 进程已换掉它就直接用新的)。条目不存在返回 undefined;`needsLogin` 抛 `auth_expired`。
|
|
31
|
+
*/
|
|
32
|
+
export declare function freshOAuthEntry(authFile: string, provider: string, deps?: RefreshDeps, options?: {
|
|
33
|
+
force?: boolean;
|
|
34
|
+
staleToken?: string;
|
|
35
|
+
}): Promise<OAuthAuthEntry | undefined>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth access token 的取用与刷新(docs/wave6-plan.md §4.3、D15)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* - 触发:`expiresAt − now < 5 min`,或协议层 401 后 `force`;
|
|
5
|
+
* - 进程内:同一 (auth.json, 供应商) 共用一个刷新 Promise;
|
|
6
|
+
* - 跨进程:`withRefreshLock` 取锁后**重读**文件——`refreshToken` 已变(别的进程刷过)且 token 仍新鲜就直接用;
|
|
7
|
+
* 否则调 token 端点(SIWC 表单 + `resource`;codex JSON),原子写回后释放;
|
|
8
|
+
* - 永久失败(`refresh_token_expired` / `_reused` / `_invalidated` / `invalid_grant` / `invalid_refresh_token` /
|
|
9
|
+
* `token_expired` / `invalid_client` / 401):条目标 `needsLogin: true`(不删 token),抛 `auth_expired`;
|
|
10
|
+
* 暂时失败(网络、5xx、429)退避重试 2 次,仍失败抛原错误。
|
|
11
|
+
*/
|
|
12
|
+
import { AmaError } from "../../errors.js";
|
|
13
|
+
import { resolveChatGptPreset } from "../chatgpt/presets.js";
|
|
14
|
+
import { OAuthHttpError, parseTokenResponse, postOAuth } from "./token-client.js";
|
|
15
|
+
import { readOAuthEntry, withRefreshLock, writeOAuthEntry } from "./token-store.js";
|
|
16
|
+
export const REFRESH_MARGIN_MS = 5 * 60_000;
|
|
17
|
+
export const RETRY_DELAYS_MS = [500, 1500];
|
|
18
|
+
const PERMANENT = new Set([
|
|
19
|
+
"refresh_token_expired",
|
|
20
|
+
"refresh_token_reused",
|
|
21
|
+
"refresh_token_invalidated",
|
|
22
|
+
"invalid_grant",
|
|
23
|
+
"invalid_refresh_token",
|
|
24
|
+
"token_expired",
|
|
25
|
+
"invalid_client",
|
|
26
|
+
]);
|
|
27
|
+
export function authExpiredError(provider) {
|
|
28
|
+
return new AmaError("auth_expired", `auth_expired: ${provider} login expired; run \`ama auth login ${provider}\``, { detail: { provider } });
|
|
29
|
+
}
|
|
30
|
+
export function isFresh(entry, now) {
|
|
31
|
+
return entry.expiresAt - now > REFRESH_MARGIN_MS;
|
|
32
|
+
}
|
|
33
|
+
const inflight = new Map();
|
|
34
|
+
async function callTokenEndpoint(entry, deps) {
|
|
35
|
+
const preset = resolveChatGptPreset(entry.flavor, { entry, env: deps.env, config: deps.config });
|
|
36
|
+
// token 属于签发它的 issuer:条目里记的优先
|
|
37
|
+
const issuer = (entry.issuer ?? preset.issuer).replace(/\/+$/, "");
|
|
38
|
+
const tokenUrl = entry.flavor === "siwc" ? `${issuer}/api/accounts/oauth/token` : `${issuer}/oauth/token`;
|
|
39
|
+
const params = {
|
|
40
|
+
grant_type: "refresh_token",
|
|
41
|
+
client_id: entry.clientId ?? preset.clientId,
|
|
42
|
+
refresh_token: entry.refreshToken,
|
|
43
|
+
};
|
|
44
|
+
if (preset.resource !== undefined)
|
|
45
|
+
params["resource"] = preset.resource;
|
|
46
|
+
const fetchFn = deps.fetch ?? fetch;
|
|
47
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
48
|
+
for (let attempt = 0;; attempt++) {
|
|
49
|
+
try {
|
|
50
|
+
return await postOAuth(fetchFn, tokenUrl, params, {
|
|
51
|
+
encoding: preset.refreshEncoding,
|
|
52
|
+
what: "token refresh",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
const permanent = error instanceof OAuthHttpError &&
|
|
57
|
+
(error.status === 401 || (error.error !== undefined && PERMANENT.has(error.error)));
|
|
58
|
+
if (permanent)
|
|
59
|
+
throw error;
|
|
60
|
+
const temporary = !(error instanceof OAuthHttpError) || error.status >= 500 || error.status === 429;
|
|
61
|
+
const delay = RETRY_DELAYS_MS[attempt];
|
|
62
|
+
if (!temporary || delay === undefined)
|
|
63
|
+
throw error;
|
|
64
|
+
await sleep(delay);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async function refreshLocked(authFile, provider, seen, force, deps) {
|
|
69
|
+
const now = deps.now ?? Date.now;
|
|
70
|
+
return withRefreshLock(authFile, async () => {
|
|
71
|
+
const current = readOAuthEntry(authFile, provider);
|
|
72
|
+
if (current === undefined)
|
|
73
|
+
throw authExpiredError(provider);
|
|
74
|
+
if (current.needsLogin === true)
|
|
75
|
+
throw authExpiredError(provider);
|
|
76
|
+
// 别的进程已经刷过:直接用
|
|
77
|
+
const rotated = current.refreshToken !== seen.refreshToken || current.accessToken !== seen.accessToken;
|
|
78
|
+
if ((rotated || !force) && isFresh(current, now()))
|
|
79
|
+
return current;
|
|
80
|
+
let raw;
|
|
81
|
+
try {
|
|
82
|
+
raw = await callTokenEndpoint(current, deps);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (error instanceof OAuthHttpError &&
|
|
86
|
+
(error.status === 401 || (error.error !== undefined && PERMANENT.has(error.error)))) {
|
|
87
|
+
writeOAuthEntry(authFile, provider, { ...current, needsLogin: true });
|
|
88
|
+
throw authExpiredError(provider);
|
|
89
|
+
}
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
const token = parseTokenResponse(raw, "token refresh");
|
|
93
|
+
const at = now();
|
|
94
|
+
const next = {
|
|
95
|
+
...current,
|
|
96
|
+
accessToken: token.accessToken,
|
|
97
|
+
refreshToken: token.refreshToken ?? current.refreshToken,
|
|
98
|
+
expiresAt: at + (token.expiresIn ?? 3600) * 1000,
|
|
99
|
+
lastRefresh: new Date(at).toISOString(),
|
|
100
|
+
};
|
|
101
|
+
if (token.idToken !== undefined)
|
|
102
|
+
next.idToken = token.idToken;
|
|
103
|
+
delete next.needsLogin;
|
|
104
|
+
writeOAuthEntry(authFile, provider, next);
|
|
105
|
+
return next;
|
|
106
|
+
}, deps.lockWaitMs === undefined ? {} : { waitMs: deps.lockWaitMs });
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 取可用的条目:新鲜就直接返回,否则刷新。`force`:协议层 401 后强制刷新(`staleToken` 是被拒的那个,别的
|
|
110
|
+
* 进程已换掉它就直接用新的)。条目不存在返回 undefined;`needsLogin` 抛 `auth_expired`。
|
|
111
|
+
*/
|
|
112
|
+
export async function freshOAuthEntry(authFile, provider, deps = {}, options = {}) {
|
|
113
|
+
const now = deps.now ?? Date.now;
|
|
114
|
+
const entry = readOAuthEntry(authFile, provider);
|
|
115
|
+
if (entry === undefined)
|
|
116
|
+
return undefined;
|
|
117
|
+
if (entry.needsLogin === true)
|
|
118
|
+
throw authExpiredError(provider);
|
|
119
|
+
const force = options.force === true && entry.accessToken === (options.staleToken ?? entry.accessToken);
|
|
120
|
+
if (!force && isFresh(entry, now()))
|
|
121
|
+
return entry;
|
|
122
|
+
const key = `${authFile}\0${provider}`;
|
|
123
|
+
let pending = inflight.get(key);
|
|
124
|
+
if (pending === undefined) {
|
|
125
|
+
pending = refreshLocked(authFile, provider, entry, force, deps).finally(() => inflight.delete(key));
|
|
126
|
+
inflight.set(key, pending);
|
|
127
|
+
}
|
|
128
|
+
return pending;
|
|
129
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 端点的 HTTP 调用(token 交换 / 刷新 / 撤销 / discovery)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 日志红线(docs/wave6-plan.md §4.3):错误只带 HTTP 状态与 `error` 码(截断到 64 字符),从不带响应
|
|
5
|
+
* 原文、请求参数、code 或任何 token。
|
|
6
|
+
*/
|
|
7
|
+
import { AmaError } from "../../errors.js";
|
|
8
|
+
export type FetchLike = (input: string | URL, init?: RequestInit) => Promise<Response>;
|
|
9
|
+
export declare const OAUTH_TIMEOUT_MS = 30000;
|
|
10
|
+
export interface TokenResponse {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
refreshToken?: string;
|
|
13
|
+
idToken?: string;
|
|
14
|
+
/** 秒。 */
|
|
15
|
+
expiresIn?: number;
|
|
16
|
+
scope?: string;
|
|
17
|
+
clientId?: string;
|
|
18
|
+
}
|
|
19
|
+
/** OAuth 端点返回非 2xx:`status` 与 `error` 码(不含描述原文)。 */
|
|
20
|
+
export declare class OAuthHttpError extends AmaError {
|
|
21
|
+
readonly status: number;
|
|
22
|
+
readonly error: string | undefined;
|
|
23
|
+
constructor(status: number, error: string | undefined, what: string);
|
|
24
|
+
}
|
|
25
|
+
/** 响应体里的错误码:`error`(字符串或 `{code|type}`)、`code`、`error_code`。 */
|
|
26
|
+
export declare function oauthErrorCode(body: string): string | undefined;
|
|
27
|
+
export declare function postOAuth(fetchFn: FetchLike, url: string, params: Record<string, string>, options: {
|
|
28
|
+
encoding: "form" | "json";
|
|
29
|
+
what: string;
|
|
30
|
+
signal?: AbortSignal | undefined;
|
|
31
|
+
}): Promise<Record<string, unknown>>;
|
|
32
|
+
export declare function parseTokenResponse(value: Record<string, unknown>, what: string): TokenResponse;
|
|
33
|
+
export declare function getJson(fetchFn: FetchLike, url: string, what: string, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 端点的 HTTP 调用(token 交换 / 刷新 / 撤销 / discovery)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 日志红线(docs/wave6-plan.md §4.3):错误只带 HTTP 状态与 `error` 码(截断到 64 字符),从不带响应
|
|
5
|
+
* 原文、请求参数、code 或任何 token。
|
|
6
|
+
*/
|
|
7
|
+
import { AmaError } from "../../errors.js";
|
|
8
|
+
export const OAUTH_TIMEOUT_MS = 30_000;
|
|
9
|
+
/** OAuth 端点返回非 2xx:`status` 与 `error` 码(不含描述原文)。 */
|
|
10
|
+
export class OAuthHttpError extends AmaError {
|
|
11
|
+
status;
|
|
12
|
+
error;
|
|
13
|
+
constructor(status, error, what) {
|
|
14
|
+
super("oauth_http", `${what} failed: HTTP ${status}${error ? ` ${error}` : ""}`, {
|
|
15
|
+
detail: { status, error },
|
|
16
|
+
});
|
|
17
|
+
this.status = status;
|
|
18
|
+
this.error = error;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** 响应体里的错误码:`error`(字符串或 `{code|type}`)、`code`、`error_code`。 */
|
|
22
|
+
export function oauthErrorCode(body) {
|
|
23
|
+
try {
|
|
24
|
+
const value = JSON.parse(body);
|
|
25
|
+
const error = value["error"];
|
|
26
|
+
const nested = typeof error === "object" && error !== null
|
|
27
|
+
? (error["code"] ?? error["type"])
|
|
28
|
+
: error;
|
|
29
|
+
const code = nested ?? value["code"] ?? value["error_code"];
|
|
30
|
+
return typeof code === "string" && code !== "" ? code.slice(0, 64) : undefined;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function signalOf(signal) {
|
|
37
|
+
const timeout = AbortSignal.timeout(OAUTH_TIMEOUT_MS);
|
|
38
|
+
return signal === undefined ? timeout : AbortSignal.any([signal, timeout]);
|
|
39
|
+
}
|
|
40
|
+
export async function postOAuth(fetchFn, url, params, options) {
|
|
41
|
+
let response;
|
|
42
|
+
try {
|
|
43
|
+
response = await fetchFn(url, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: options.encoding === "form"
|
|
46
|
+
? { "content-type": "application/x-www-form-urlencoded", accept: "application/json" }
|
|
47
|
+
: { "content-type": "application/json", accept: "application/json" },
|
|
48
|
+
body: options.encoding === "form"
|
|
49
|
+
? new URLSearchParams(params).toString()
|
|
50
|
+
: JSON.stringify(params),
|
|
51
|
+
signal: signalOf(options.signal),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new AmaError("oauth_network", `${options.what} failed: ${error.name}`, {
|
|
56
|
+
cause: undefined,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const text = await response.text().catch(() => "");
|
|
60
|
+
if (!response.ok)
|
|
61
|
+
throw new OAuthHttpError(response.status, oauthErrorCode(text), options.what);
|
|
62
|
+
if (text.trim() === "")
|
|
63
|
+
return {};
|
|
64
|
+
try {
|
|
65
|
+
const value = JSON.parse(text);
|
|
66
|
+
if (typeof value === "object" && value !== null)
|
|
67
|
+
return value;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// 落到下面
|
|
71
|
+
}
|
|
72
|
+
throw new AmaError("oauth_bad_response", `${options.what}: response is not JSON`);
|
|
73
|
+
}
|
|
74
|
+
export function parseTokenResponse(value, what) {
|
|
75
|
+
const access = value["access_token"];
|
|
76
|
+
if (typeof access !== "string" || access === "")
|
|
77
|
+
throw new AmaError("oauth_bad_response", `${what}: no access_token`);
|
|
78
|
+
const out = { accessToken: access };
|
|
79
|
+
const str = (key) => typeof value[key] === "string" && value[key] !== "" ? value[key] : undefined;
|
|
80
|
+
const refresh = str("refresh_token");
|
|
81
|
+
if (refresh !== undefined)
|
|
82
|
+
out.refreshToken = refresh;
|
|
83
|
+
const id = str("id_token");
|
|
84
|
+
if (id !== undefined)
|
|
85
|
+
out.idToken = id;
|
|
86
|
+
const scope = str("scope");
|
|
87
|
+
if (scope !== undefined)
|
|
88
|
+
out.scope = scope;
|
|
89
|
+
const clientId = str("client_id");
|
|
90
|
+
if (clientId !== undefined)
|
|
91
|
+
out.clientId = clientId;
|
|
92
|
+
const expires = value["expires_in"];
|
|
93
|
+
const seconds = typeof expires === "string" ? Number(expires) : expires;
|
|
94
|
+
if (typeof seconds === "number" && Number.isFinite(seconds) && seconds > 0)
|
|
95
|
+
out.expiresIn = seconds;
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
export async function getJson(fetchFn, url, what, signal) {
|
|
99
|
+
let response;
|
|
100
|
+
try {
|
|
101
|
+
response = await fetchFn(url, {
|
|
102
|
+
headers: { accept: "application/json" },
|
|
103
|
+
signal: signalOf(signal),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
throw new AmaError("oauth_network", `${what} failed: ${error.name}`);
|
|
108
|
+
}
|
|
109
|
+
const text = await response.text().catch(() => "");
|
|
110
|
+
if (!response.ok)
|
|
111
|
+
throw new OAuthHttpError(response.status, oauthErrorCode(text), what);
|
|
112
|
+
try {
|
|
113
|
+
const value = JSON.parse(text);
|
|
114
|
+
if (typeof value === "object" && value !== null)
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
// 落到下面
|
|
119
|
+
}
|
|
120
|
+
throw new AmaError("oauth_bad_response", `${what}: response is not JSON`);
|
|
121
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 条目的存取与跨进程刷新锁(docs/wave6-plan.md §4.3、D15)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* - 读:每次都从磁盘读(绕过 `ApiKeyResolver.fileCache`,长会话才能看到别的进程刷新后的 token);文件不存在 /
|
|
5
|
+
* 坏掉 / 不是 oauth 条目 → undefined;
|
|
6
|
+
* - 写:读-改-写 `auth.json` 整个文件,经 `writeAuthFile`(同目录临时文件 0600 → rename → chmod),其它条目原样保留;
|
|
7
|
+
* - 锁:`<auth.json>.lock`,`open(O_CREAT|O_EXCL)`(flag `wx`)写 `{ pid, at }`;等待 ≤ 15 s;锁文件超过 60 s 且
|
|
8
|
+
* pid 已死视为陈旧,删除后重试。取锁后由调用方**重读文件**再决定要不要刷新。
|
|
9
|
+
*/
|
|
10
|
+
import { type OAuthAuthEntry } from "../../config/types-w6.js";
|
|
11
|
+
export declare const LOCK_WAIT_MS = 15000;
|
|
12
|
+
export declare const LOCK_STALE_MS = 60000;
|
|
13
|
+
export declare function readOAuthEntry(path: string, provider: string): OAuthAuthEntry | undefined;
|
|
14
|
+
/** 写入(`undefined` 删除)该供应商的条目,其余条目不动。 */
|
|
15
|
+
export declare function writeOAuthEntry(path: string, provider: string, entry: OAuthAuthEntry | undefined): void;
|
|
16
|
+
export declare function lockPath(authFile: string): string;
|
|
17
|
+
/** 锁文件陈旧:超过 60 s 且写锁的进程已不在。 */
|
|
18
|
+
export declare function isStaleLock(path: string, now?: number): boolean;
|
|
19
|
+
export interface LockOptions {
|
|
20
|
+
waitMs?: number;
|
|
21
|
+
}
|
|
22
|
+
/** 在 `auth.json.lock` 下执行 `fn`;等不到锁抛 `auth_lock_timeout`。 */
|
|
23
|
+
export declare function withRefreshLock<T>(authFile: string, fn: () => Promise<T>, options?: LockOptions): Promise<T>;
|