@armadra/agent 0.5.1 → 0.6.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/CHANGELOG.md +203 -304
- package/CHANGELOG.zh-CN.md +462 -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/discovered-cache.d.ts +48 -0
- package/dist/ai/providers/discovered-cache.js +102 -0
- package/dist/ai/providers/enrich.js +2 -7
- package/dist/ai/providers/model-visibility.d.ts +33 -0
- package/dist/ai/providers/model-visibility.js +70 -0
- 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/registry.d.ts +2 -0
- package/dist/ai/providers/registry.js +6 -1
- 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 +35 -0
- package/dist/auth/chatgpt/cli.js +279 -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 +26532 -11045
- 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.js +8 -6
- 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 +6 -0
- package/dist/cli/compose-providers.js +38 -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 +145 -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.js +23 -20
- package/dist/cli/subcommands/models-discover.d.ts +4 -0
- package/dist/cli/subcommands/models-discover.js +60 -21
- package/dist/cli/subcommands/models-enable.d.ts +13 -0
- package/dist/cli/subcommands/models-enable.js +145 -0
- package/dist/cli/subcommands/models.d.ts +3 -1
- package/dist/cli/subcommands/models.js +39 -24
- 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 +1 -1
- package/dist/cli/subcommands/providers.js +45 -50
- 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 +102 -69
- 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 +12 -4
- 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 +81 -0
- package/dist/config/settings-registry.js +185 -0
- package/dist/config/types-w5.d.ts +7 -0
- package/dist/config/types-w5.js +2 -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 +2209 -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 +189 -0
- package/dist/i18n/messages/auth.js +201 -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 +248 -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 +293 -0
- package/dist/i18n/messages/config-keys.js +296 -0
- package/dist/i18n/messages/config.d.ts +508 -0
- package/dist/i18n/messages/config.js +241 -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 +99 -0
- package/dist/i18n/messages/interactive-startup.js +100 -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 +489 -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 +233 -0
- package/dist/i18n/messages/panels.js +232 -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 +355 -0
- package/dist/i18n/messages/settings.js +343 -0
- package/dist/i18n/messages/subcommands-config.d.ts +113 -0
- package/dist/i18n/messages/subcommands-config.js +129 -0
- package/dist/i18n/messages/subcommands-models.d.ts +34 -0
- package/dist/i18n/messages/subcommands-models.js +34 -0
- package/dist/i18n/messages/subcommands.d.ts +576 -0
- package/dist/i18n/messages/subcommands.js +477 -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 +11 -0
- package/dist/modes/commands-core.js +93 -62
- 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 +100 -49
- 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.js +6 -7
- 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 +21 -22
- package/dist/modes/interactive/key-dispatch.d.ts +12 -0
- package/dist/modes/interactive/key-dispatch.js +24 -9
- package/dist/modes/interactive/line/line-editor.js +2 -1
- package/dist/modes/interactive/line/line-mode.js +18 -7
- 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/model-items.d.ts +45 -0
- package/dist/modes/interactive/model-items.js +134 -0
- package/dist/modes/interactive/model-picker.d.ts +57 -0
- package/dist/modes/interactive/model-picker.js +191 -0
- 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.d.ts +3 -9
- package/dist/modes/interactive/startup-ui.js +44 -88
- 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 +8 -10
- package/dist/permissions/bypass.js +19 -10
- 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/select-list.d.ts +2 -0
- package/dist/tui/components/select-list.js +7 -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 +5 -0
- package/dist/tui/keybindings.js +17 -5
- 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 +627 -0
- package/docs/en/rpc.md +361 -0
- package/docs/en/sessions.md +219 -0
- package/docs/en/tui.md +534 -0
- package/docs/host-api.md +18 -17
- package/docs/memory.md +172 -0
- package/docs/permissions.md +2 -2
- package/docs/providers.md +73 -1
- package/docs/rpc.md +31 -3
- package/docs/session-format.md +8 -2
- package/docs/sessions.md +29 -1
- package/docs/tui.md +140 -4
- package/package.json +8 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 授权流程(docs/wave6-plan.md §4.2、D16;R6 §4.2、§4.3、§1.2 设备码行)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 三种拿授权码的方式,产出同一个 `AuthorizationResult`,由各预设的登录模块去换 token:
|
|
5
|
+
* - `browser`:本地回调服务 + 打开浏览器(打不开就只给 URL);
|
|
6
|
+
* - `paste`:只给 URL,用户把浏览器地址栏里的回调 URL 粘回来(SSH / 宿主场景);
|
|
7
|
+
* - `device`:设备码(只有 codex 预设),轮询 `/api/accounts/deviceauth/token`,15 分钟上限。
|
|
8
|
+
*
|
|
9
|
+
* 给人看的东西经 `onStep` 交给界面层渲染(i18n 在那里);本模块不打印、不记日志,code 与 verifier
|
|
10
|
+
* 只出现在返回值里。
|
|
11
|
+
*/
|
|
12
|
+
import type { BrowserOpener } from "./browser.js";
|
|
13
|
+
import { type CallbackPages, type CallbackParams } from "./callback-server.js";
|
|
14
|
+
import { type FetchLike } from "./token-client.js";
|
|
15
|
+
export type LoginStep = {
|
|
16
|
+
kind: "open_url";
|
|
17
|
+
url: string;
|
|
18
|
+
opened: boolean;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "paste_prompt";
|
|
21
|
+
url: string;
|
|
22
|
+
} | {
|
|
23
|
+
kind: "device_code";
|
|
24
|
+
url: string;
|
|
25
|
+
userCode: string;
|
|
26
|
+
};
|
|
27
|
+
export interface FlowDeps {
|
|
28
|
+
fetch: FetchLike;
|
|
29
|
+
openBrowser: BrowserOpener;
|
|
30
|
+
onStep(step: LoginStep): void;
|
|
31
|
+
/** `--paste`:读一行(回调 URL)。 */
|
|
32
|
+
readLine(): Promise<string>;
|
|
33
|
+
signal?: AbortSignal | undefined;
|
|
34
|
+
sleep?: ((ms: number) => Promise<void>) | undefined;
|
|
35
|
+
now?: (() => number) | undefined;
|
|
36
|
+
pages?: CallbackPages | undefined;
|
|
37
|
+
timeoutMs?: number | undefined;
|
|
38
|
+
}
|
|
39
|
+
export interface FlowPreset {
|
|
40
|
+
issuer: string;
|
|
41
|
+
clientId: string;
|
|
42
|
+
redirectPorts: readonly number[];
|
|
43
|
+
/** 给定回调地址与随机串,产出授权 URL。 */
|
|
44
|
+
buildAuthorizeUrl(input: {
|
|
45
|
+
redirectUri: string;
|
|
46
|
+
state: string;
|
|
47
|
+
challenge: string;
|
|
48
|
+
nonce: string;
|
|
49
|
+
}): string;
|
|
50
|
+
}
|
|
51
|
+
export interface AuthorizationResult extends CallbackParams {
|
|
52
|
+
redirectUri: string;
|
|
53
|
+
verifier: string;
|
|
54
|
+
nonce: string;
|
|
55
|
+
}
|
|
56
|
+
export declare function loginBrowser(preset: FlowPreset, deps: FlowDeps, options?: {
|
|
57
|
+
noBrowser?: boolean;
|
|
58
|
+
}): Promise<AuthorizationResult>;
|
|
59
|
+
export declare function loginPaste(preset: FlowPreset, deps: FlowDeps): Promise<AuthorizationResult>;
|
|
60
|
+
export declare const DEVICE_MAX_WAIT_MS: number;
|
|
61
|
+
/** 设备码:`{issuer}/api/accounts/deviceauth/usercode` → 用户在 `{issuer}/codex/device` 输入 → 轮询。 */
|
|
62
|
+
export declare function loginDevice(preset: FlowPreset, deps: FlowDeps): Promise<AuthorizationResult>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 授权流程(docs/wave6-plan.md §4.2、D16;R6 §4.2、§4.3、§1.2 设备码行)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 三种拿授权码的方式,产出同一个 `AuthorizationResult`,由各预设的登录模块去换 token:
|
|
5
|
+
* - `browser`:本地回调服务 + 打开浏览器(打不开就只给 URL);
|
|
6
|
+
* - `paste`:只给 URL,用户把浏览器地址栏里的回调 URL 粘回来(SSH / 宿主场景);
|
|
7
|
+
* - `device`:设备码(只有 codex 预设),轮询 `/api/accounts/deviceauth/token`,15 分钟上限。
|
|
8
|
+
*
|
|
9
|
+
* 给人看的东西经 `onStep` 交给界面层渲染(i18n 在那里);本模块不打印、不记日志,code 与 verifier
|
|
10
|
+
* 只出现在返回值里。
|
|
11
|
+
*/
|
|
12
|
+
import { AmaError } from "../../errors.js";
|
|
13
|
+
import { CALLBACK_HOST, CALLBACK_PATH, parseCallbackUrl, startCallbackServer, } from "./callback-server.js";
|
|
14
|
+
import { generatePkce, randomToken } from "./pkce.js";
|
|
15
|
+
import { OAuthHttpError, postOAuth } from "./token-client.js";
|
|
16
|
+
export async function loginBrowser(preset, deps, options = {}) {
|
|
17
|
+
const pkce = generatePkce();
|
|
18
|
+
const state = randomToken();
|
|
19
|
+
const nonce = randomToken();
|
|
20
|
+
const server = await startCallbackServer({
|
|
21
|
+
ports: preset.redirectPorts,
|
|
22
|
+
state,
|
|
23
|
+
signal: deps.signal,
|
|
24
|
+
pages: deps.pages,
|
|
25
|
+
timeoutMs: deps.timeoutMs,
|
|
26
|
+
});
|
|
27
|
+
try {
|
|
28
|
+
const url = preset.buildAuthorizeUrl({
|
|
29
|
+
redirectUri: server.redirectUri,
|
|
30
|
+
state,
|
|
31
|
+
challenge: pkce.challenge,
|
|
32
|
+
nonce,
|
|
33
|
+
});
|
|
34
|
+
const opened = options.noBrowser === true ? false : await deps.openBrowser(url);
|
|
35
|
+
deps.onStep({ kind: "open_url", url, opened });
|
|
36
|
+
const params = await server.result;
|
|
37
|
+
return { ...params, redirectUri: server.redirectUri, verifier: pkce.verifier, nonce };
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await server.close();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function loginPaste(preset, deps) {
|
|
44
|
+
const pkce = generatePkce();
|
|
45
|
+
const state = randomToken();
|
|
46
|
+
const nonce = randomToken();
|
|
47
|
+
const port = preset.redirectPorts.find((p) => p > 0) ?? 1455;
|
|
48
|
+
const redirectUri = `http://${CALLBACK_HOST}:${port}${CALLBACK_PATH}`;
|
|
49
|
+
const url = preset.buildAuthorizeUrl({ redirectUri, state, challenge: pkce.challenge, nonce });
|
|
50
|
+
deps.onStep({ kind: "paste_prompt", url });
|
|
51
|
+
const line = await deps.readLine();
|
|
52
|
+
if (line.trim() === "")
|
|
53
|
+
throw new AmaError("aborted", "login aborted");
|
|
54
|
+
const params = parseCallbackUrl(line, state);
|
|
55
|
+
return { ...params, redirectUri, verifier: pkce.verifier, nonce };
|
|
56
|
+
}
|
|
57
|
+
export const DEVICE_MAX_WAIT_MS = 15 * 60_000;
|
|
58
|
+
function defaultSleep(ms, signal) {
|
|
59
|
+
return new Promise((resolve, reject) => {
|
|
60
|
+
if (signal?.aborted)
|
|
61
|
+
return reject(new AmaError("aborted", "login aborted"));
|
|
62
|
+
const timer = setTimeout(resolve, ms);
|
|
63
|
+
signal?.addEventListener("abort", () => {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
reject(new AmaError("aborted", "login aborted"));
|
|
66
|
+
}, { once: true });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** 设备码:`{issuer}/api/accounts/deviceauth/usercode` → 用户在 `{issuer}/codex/device` 输入 → 轮询。 */
|
|
70
|
+
export async function loginDevice(preset, deps) {
|
|
71
|
+
const api = `${preset.issuer}/api/accounts`;
|
|
72
|
+
let start;
|
|
73
|
+
try {
|
|
74
|
+
start = await postOAuth(deps.fetch, `${api}/deviceauth/usercode`, { client_id: preset.clientId }, { encoding: "json", what: "device code request", signal: deps.signal });
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof OAuthHttpError && error.status === 404)
|
|
78
|
+
throw new AmaError("oauth_device_unavailable", "device code login is not enabled");
|
|
79
|
+
throw error;
|
|
80
|
+
}
|
|
81
|
+
const deviceAuthId = start["device_auth_id"];
|
|
82
|
+
const userCode = start["user_code"] ?? start["usercode"];
|
|
83
|
+
if (typeof deviceAuthId !== "string" || typeof userCode !== "string")
|
|
84
|
+
throw new AmaError("oauth_bad_response", "device code request: unexpected response");
|
|
85
|
+
const interval = Math.max(1, Number(start["interval"] ?? 5) || 5) * 1000;
|
|
86
|
+
deps.onStep({ kind: "device_code", url: `${preset.issuer}/codex/device`, userCode });
|
|
87
|
+
const now = deps.now ?? Date.now;
|
|
88
|
+
const sleep = deps.sleep ?? ((ms) => defaultSleep(ms, deps.signal));
|
|
89
|
+
const began = now();
|
|
90
|
+
for (;;) {
|
|
91
|
+
try {
|
|
92
|
+
const done = await postOAuth(deps.fetch, `${api}/deviceauth/token`, { device_auth_id: deviceAuthId, user_code: userCode }, { encoding: "json", what: "device code poll", signal: deps.signal });
|
|
93
|
+
const code = done["authorization_code"];
|
|
94
|
+
const verifier = done["code_verifier"];
|
|
95
|
+
if (typeof code !== "string" || typeof verifier !== "string")
|
|
96
|
+
throw new AmaError("oauth_bad_response", "device code poll: unexpected response");
|
|
97
|
+
return {
|
|
98
|
+
code,
|
|
99
|
+
verifier,
|
|
100
|
+
nonce: "",
|
|
101
|
+
redirectUri: `${preset.issuer}/deviceauth/callback`,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
const pending = error instanceof OAuthHttpError && (error.status === 403 || error.status === 404);
|
|
106
|
+
if (!pending)
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
if (now() - began >= DEVICE_MAX_WAIT_MS)
|
|
110
|
+
throw new AmaError("oauth_timeout", "device code login timed out");
|
|
111
|
+
await sleep(interval);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 { type JsonWebKey } from "node:crypto";
|
|
8
|
+
export type JwtPayload = Record<string, unknown>;
|
|
9
|
+
export interface DecodedJwt {
|
|
10
|
+
header: Record<string, unknown>;
|
|
11
|
+
payload: JwtPayload;
|
|
12
|
+
signingInput: string;
|
|
13
|
+
signature: Buffer;
|
|
14
|
+
}
|
|
15
|
+
export declare function decodeJwt(token: string): DecodedJwt;
|
|
16
|
+
/** 只解码 payload;解不开返回 undefined(codex flavor 取声明用,不验签)。 */
|
|
17
|
+
export declare function jwtClaims(token: string | undefined): JwtPayload | undefined;
|
|
18
|
+
export interface Jwk extends JsonWebKey {
|
|
19
|
+
kid?: string;
|
|
20
|
+
alg?: string;
|
|
21
|
+
use?: string;
|
|
22
|
+
}
|
|
23
|
+
/** 按 header 的 alg / kid 在 JWKS 里找钥并验签;不通过抛 `oauth_invalid_token`。 */
|
|
24
|
+
export declare function verifyJwtSignature(decoded: DecodedJwt, keys: readonly Jwk[]): void;
|
|
@@ -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>>;
|