@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,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGPT 登录预设表(docs/wave6-plan.md §4.1、D13–D16;R6 §1.1、§1.2、§4.10)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 两条路径共用 PKCE / 回调 / 存储 / 锁 / 配额框架,差异全部在这张表里:
|
|
5
|
+
* - `siwc`(缺省,官方动态注册):`/api/accounts/*`,首次 `client_id=dynamic_agent_client`,回调带回签发的
|
|
6
|
+
* id 后存进条目;表单编码、带 `resource`;id_token 必须 JWKS 验签;推理走 `api.openai.com/v1`;
|
|
7
|
+
* - `codex`(显式开启的备用):Codex CLI 的公开客户端,`/oauth/*`,刷新 JSON 编码;id_token 只解码;
|
|
8
|
+
* 推理走 `chatgpt.com/backend-api/codex`,另发 `ChatGPT-Account-ID` 与 `originator`。
|
|
9
|
+
*
|
|
10
|
+
* 覆盖:`AMA_CHATGPT_CLIENT_ID` / `AMA_CHATGPT_ISSUER`(测试用)/ `AMA_CHATGPT_BASE_URL` > 配置
|
|
11
|
+
* `auth.chatgpt.*` > 条目里签发的 client id > 缺省。
|
|
12
|
+
*/
|
|
13
|
+
export const CHATGPT_PROVIDER_ID = "chatgpt";
|
|
14
|
+
export const DEFAULT_ISSUER = "https://auth.openai.com";
|
|
15
|
+
export const SIWC_REGISTRATION_CLIENT_ID = "dynamic_agent_client";
|
|
16
|
+
export const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
17
|
+
export const SIWC_RESOURCE = "https://api.openai.com/v1";
|
|
18
|
+
export const SIWC_REQUIRED_SCOPE = "chatgpt.tokens.use.direct";
|
|
19
|
+
export const AGENT_NAME_HINT = "ama";
|
|
20
|
+
export const DEFAULT_ORIGINATOR = "codex_cli_rs";
|
|
21
|
+
export const CHATGPT_BASE_URLS = {
|
|
22
|
+
siwc: "https://api.openai.com/v1",
|
|
23
|
+
codex: "https://chatgpt.com/backend-api/codex",
|
|
24
|
+
};
|
|
25
|
+
function nonEmpty(value) {
|
|
26
|
+
const trimmed = value?.trim();
|
|
27
|
+
return trimmed === undefined || trimmed === "" ? undefined : trimmed;
|
|
28
|
+
}
|
|
29
|
+
export function chatgptBaseUrl(flavor, env = process.env) {
|
|
30
|
+
return nonEmpty(env["AMA_CHATGPT_BASE_URL"]) ?? CHATGPT_BASE_URLS[flavor];
|
|
31
|
+
}
|
|
32
|
+
export function resolveChatGptPreset(flavor, overrides = {}) {
|
|
33
|
+
const env = overrides.env ?? process.env;
|
|
34
|
+
const config = overrides.config;
|
|
35
|
+
const issuer = (nonEmpty(env["AMA_CHATGPT_ISSUER"]) ??
|
|
36
|
+
nonEmpty(config?.issuer) ??
|
|
37
|
+
DEFAULT_ISSUER).replace(/\/+$/, "");
|
|
38
|
+
const sameFlavor = overrides.entry?.flavor === flavor ? overrides.entry : undefined;
|
|
39
|
+
const explicitClient = nonEmpty(env["AMA_CHATGPT_CLIENT_ID"]) ?? nonEmpty(config?.clientId);
|
|
40
|
+
const ports = overrides.port !== undefined
|
|
41
|
+
? [overrides.port]
|
|
42
|
+
: config?.redirectPorts !== undefined && config.redirectPorts.length > 0
|
|
43
|
+
? [...config.redirectPorts]
|
|
44
|
+
: undefined;
|
|
45
|
+
if (flavor === "siwc") {
|
|
46
|
+
const issued = sameFlavor?.clientId !== SIWC_REGISTRATION_CLIENT_ID ? sameFlavor?.clientId : undefined;
|
|
47
|
+
const clientId = explicitClient ?? issued ?? SIWC_REGISTRATION_CLIENT_ID;
|
|
48
|
+
return {
|
|
49
|
+
flavor,
|
|
50
|
+
issuer,
|
|
51
|
+
clientId,
|
|
52
|
+
registering: clientId === SIWC_REGISTRATION_CLIENT_ID,
|
|
53
|
+
authorizeUrl: `${issuer}/api/accounts/authorize`,
|
|
54
|
+
tokenUrl: `${issuer}/api/accounts/oauth/token`,
|
|
55
|
+
scopes: [
|
|
56
|
+
"openid",
|
|
57
|
+
"profile",
|
|
58
|
+
"email",
|
|
59
|
+
"offline_access",
|
|
60
|
+
"resource.invoke",
|
|
61
|
+
SIWC_REQUIRED_SCOPE,
|
|
62
|
+
],
|
|
63
|
+
// 端口可变(scheme / host / path 不变):1455 被占用就用任意空闲端口
|
|
64
|
+
redirectPorts: ports ?? [1455, 0],
|
|
65
|
+
resource: SIWC_RESOURCE,
|
|
66
|
+
refreshEncoding: "form",
|
|
67
|
+
baseUrl: chatgptBaseUrl(flavor, env),
|
|
68
|
+
verifyIdToken: true,
|
|
69
|
+
requiredScope: SIWC_REQUIRED_SCOPE,
|
|
70
|
+
device: false,
|
|
71
|
+
revoke: true,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
flavor,
|
|
76
|
+
issuer,
|
|
77
|
+
clientId: explicitClient ?? CODEX_CLIENT_ID,
|
|
78
|
+
registering: false,
|
|
79
|
+
authorizeUrl: `${issuer}/oauth/authorize`,
|
|
80
|
+
tokenUrl: `${issuer}/oauth/token`,
|
|
81
|
+
scopes: ["openid", "profile", "email", "offline_access"],
|
|
82
|
+
// 公开客户端只登记了这两个端口;被占用不抢占
|
|
83
|
+
redirectPorts: ports ?? [1455, 1457],
|
|
84
|
+
refreshEncoding: "json",
|
|
85
|
+
baseUrl: chatgptBaseUrl(flavor, env),
|
|
86
|
+
verifyIdToken: false,
|
|
87
|
+
device: true,
|
|
88
|
+
revoke: false,
|
|
89
|
+
originator: nonEmpty(config?.originator) ?? DEFAULT_ORIGINATOR,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** 授权 URL(参数顺序固定,便于测试)。 */
|
|
93
|
+
export function authorizeUrl(preset, input) {
|
|
94
|
+
const params = new URLSearchParams({
|
|
95
|
+
response_type: "code",
|
|
96
|
+
client_id: preset.clientId,
|
|
97
|
+
redirect_uri: input.redirectUri,
|
|
98
|
+
scope: preset.scopes.join(" "),
|
|
99
|
+
code_challenge: input.challenge,
|
|
100
|
+
code_challenge_method: "S256",
|
|
101
|
+
state: input.state,
|
|
102
|
+
});
|
|
103
|
+
if (preset.flavor === "siwc") {
|
|
104
|
+
params.set("nonce", input.nonce);
|
|
105
|
+
if (preset.resource !== undefined)
|
|
106
|
+
params.set("resource", preset.resource);
|
|
107
|
+
if (input.hostId !== undefined)
|
|
108
|
+
params.set("ext_agent_host_id", input.hostId);
|
|
109
|
+
// 只在首次动态注册时带
|
|
110
|
+
if (preset.registering)
|
|
111
|
+
params.set("agent_name_hint", AGENT_NAME_HINT);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
params.set("id_token_add_organizations", "true");
|
|
115
|
+
params.set("codex_cli_simplified_flow", "true");
|
|
116
|
+
if (preset.originator !== undefined)
|
|
117
|
+
params.set("originator", preset.originator);
|
|
118
|
+
}
|
|
119
|
+
return `${preset.authorizeUrl}?${params.toString()}`;
|
|
120
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配额的人读文本(`ama auth status`、`/session`、doctor 共用)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* `5 小时 42%(14:20 重置)· 周 18%(10-09 08:00 重置)`;重置时间按本地时区,24 小时内只给时分。
|
|
5
|
+
*/
|
|
6
|
+
import type { QuotaWindow } from "../../agent/types-w6.js";
|
|
7
|
+
export interface QuotaLike {
|
|
8
|
+
primary?: QuotaWindow | undefined;
|
|
9
|
+
secondary?: QuotaWindow | undefined;
|
|
10
|
+
}
|
|
11
|
+
export declare function resetClock(at: number, now?: number): string;
|
|
12
|
+
/** 没有任何窗口时 undefined。 */
|
|
13
|
+
export declare function quotaParts(quota: QuotaLike, now?: number): string | undefined;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配额的人读文本(`ama auth status`、`/session`、doctor 共用)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* `5 小时 42%(14:20 重置)· 周 18%(10-09 08:00 重置)`;重置时间按本地时区,24 小时内只给时分。
|
|
5
|
+
*/
|
|
6
|
+
import { formatDuration, msg } from "../../i18n/index.js";
|
|
7
|
+
function pad(n) {
|
|
8
|
+
return String(n).padStart(2, "0");
|
|
9
|
+
}
|
|
10
|
+
export function resetClock(at, now = Date.now()) {
|
|
11
|
+
const d = new Date(at);
|
|
12
|
+
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
|
13
|
+
return at - now < 24 * 3_600_000 ? time : `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${time}`;
|
|
14
|
+
}
|
|
15
|
+
function windowName(minutes, fallback) {
|
|
16
|
+
const m = msg().auth.status;
|
|
17
|
+
if (minutes === 300)
|
|
18
|
+
return m.window5h;
|
|
19
|
+
if (minutes === 10_080)
|
|
20
|
+
return m.windowWeek;
|
|
21
|
+
if (minutes !== undefined)
|
|
22
|
+
return m.windowOther(formatDuration(minutes * 60_000));
|
|
23
|
+
return fallback === "primary" ? m.window5h : m.windowWeek;
|
|
24
|
+
}
|
|
25
|
+
/** 没有任何窗口时 undefined。 */
|
|
26
|
+
export function quotaParts(quota, now = Date.now()) {
|
|
27
|
+
const m = msg().auth.status;
|
|
28
|
+
const parts = [];
|
|
29
|
+
for (const which of ["primary", "secondary"]) {
|
|
30
|
+
const w = quota[which];
|
|
31
|
+
if (w === undefined)
|
|
32
|
+
continue;
|
|
33
|
+
parts.push(m.quotaWindow(windowName(w.windowMinutes, which), Math.round(w.usedPercent), w.resetsAt === undefined ? undefined : resetClock(w.resetsAt, now)));
|
|
34
|
+
}
|
|
35
|
+
return parts.length === 0 ? undefined : parts.join(" · ");
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 打开系统浏览器(R6 §4.11)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* macOS `open`、Windows `cmd /c start ""`、其余 `xdg-open`;URL 作为 argv 传入,不拼 shell 字符串。
|
|
5
|
+
* 打不开(命令不存在、非零退出)返回 false,调用方改为只打印 URL。
|
|
6
|
+
*/
|
|
7
|
+
export type BrowserOpener = (url: string) => Promise<boolean>;
|
|
8
|
+
export declare function browserCommand(url: string, platform?: NodeJS.Platform): {
|
|
9
|
+
command: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
};
|
|
12
|
+
export declare const openBrowser: BrowserOpener;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 打开系统浏览器(R6 §4.11)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* macOS `open`、Windows `cmd /c start ""`、其余 `xdg-open`;URL 作为 argv 传入,不拼 shell 字符串。
|
|
5
|
+
* 打不开(命令不存在、非零退出)返回 false,调用方改为只打印 URL。
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from "node:child_process";
|
|
8
|
+
export function browserCommand(url, platform = process.platform) {
|
|
9
|
+
if (platform === "darwin")
|
|
10
|
+
return { command: "open", args: [url] };
|
|
11
|
+
// `start` 的第一个带引号参数是窗口标题;URL 里的 & 需要 cmd 转义
|
|
12
|
+
if (platform === "win32")
|
|
13
|
+
return { command: "cmd", args: ["/c", "start", '""', url.replace(/&/g, "^&")] };
|
|
14
|
+
return { command: "xdg-open", args: [url] };
|
|
15
|
+
}
|
|
16
|
+
export const openBrowser = (url) => new Promise((resolve) => {
|
|
17
|
+
const { command, args } = browserCommand(url);
|
|
18
|
+
try {
|
|
19
|
+
const child = spawn(command, args, { stdio: "ignore", detached: true, windowsHide: true });
|
|
20
|
+
child.once("error", () => resolve(false));
|
|
21
|
+
child.once("exit", (code) => resolve(code === 0 || code === null));
|
|
22
|
+
child.unref();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
resolve(false);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地 OAuth 回调服务(docs/wave6-plan.md §4.1 回调行;R6 §4.3)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* - 只监听 `127.0.0.1`;端口按 `ports` 依次尝试(0 = 任意空闲端口),被占用就试下一个;全被占用报
|
|
5
|
+
* `oauth_ports_busy`(**不**像别的客户端那样发 `/cancel` 抢占他人的监听);
|
|
6
|
+
* - 只认 `GET <path>`:`state` 不符 → `oauth_state_mismatch`;带 `error` → `oauth_denied`(只带 error 码,
|
|
7
|
+
* 不带描述原文);成功返回静态页,**页面不回显 code**;
|
|
8
|
+
* - 超时(缺省 5 分钟)→ `oauth_timeout`;signal 中止 → `aborted`。结束后自动关闭监听。
|
|
9
|
+
*
|
|
10
|
+
* `parseCallbackUrl` 给 `--paste` 用:校验粘回来的回调 URL 的 state 并取出 code。
|
|
11
|
+
* SIWC 首次动态注册时回调还带签发的 `client_id` 与授予的 `scope`(空格分隔),一并交出。
|
|
12
|
+
*/
|
|
13
|
+
export declare const CALLBACK_PATH = "/auth/callback";
|
|
14
|
+
export declare const CALLBACK_HOST = "127.0.0.1";
|
|
15
|
+
export declare const DEFAULT_LOGIN_TIMEOUT_MS: number;
|
|
16
|
+
export interface CallbackPages {
|
|
17
|
+
success: string;
|
|
18
|
+
failure: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CallbackServerOptions {
|
|
21
|
+
ports: readonly number[];
|
|
22
|
+
state: string;
|
|
23
|
+
path?: string | undefined;
|
|
24
|
+
timeoutMs?: number | undefined;
|
|
25
|
+
signal?: AbortSignal | undefined;
|
|
26
|
+
pages?: CallbackPages | undefined;
|
|
27
|
+
}
|
|
28
|
+
/** 回调里的有用参数(不含 state)。 */
|
|
29
|
+
export interface CallbackParams {
|
|
30
|
+
code: string;
|
|
31
|
+
/** SIWC 动态注册签发的 client id。 */
|
|
32
|
+
clientId?: string;
|
|
33
|
+
/** 授予的 scope(空格分隔)。 */
|
|
34
|
+
scope?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface CallbackServer {
|
|
37
|
+
port: number;
|
|
38
|
+
redirectUri: string;
|
|
39
|
+
/** 收到合法回调后 resolve;失败 reject AmaError。 */
|
|
40
|
+
result: Promise<CallbackParams>;
|
|
41
|
+
close(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/** 回调参数 → code;state 不符 / 带 error / 缺 code 抛 AmaError(不含 code 原文)。 */
|
|
44
|
+
export declare function codeFromParams(params: URLSearchParams, state: string): CallbackParams;
|
|
45
|
+
/** `--paste`:粘回来的整条回调 URL(或只有查询串)。 */
|
|
46
|
+
export declare function parseCallbackUrl(input: string, state: string): CallbackParams;
|
|
47
|
+
export declare function startCallbackServer(options: CallbackServerOptions): Promise<CallbackServer>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 本地 OAuth 回调服务(docs/wave6-plan.md §4.1 回调行;R6 §4.3)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* - 只监听 `127.0.0.1`;端口按 `ports` 依次尝试(0 = 任意空闲端口),被占用就试下一个;全被占用报
|
|
5
|
+
* `oauth_ports_busy`(**不**像别的客户端那样发 `/cancel` 抢占他人的监听);
|
|
6
|
+
* - 只认 `GET <path>`:`state` 不符 → `oauth_state_mismatch`;带 `error` → `oauth_denied`(只带 error 码,
|
|
7
|
+
* 不带描述原文);成功返回静态页,**页面不回显 code**;
|
|
8
|
+
* - 超时(缺省 5 分钟)→ `oauth_timeout`;signal 中止 → `aborted`。结束后自动关闭监听。
|
|
9
|
+
*
|
|
10
|
+
* `parseCallbackUrl` 给 `--paste` 用:校验粘回来的回调 URL 的 state 并取出 code。
|
|
11
|
+
* SIWC 首次动态注册时回调还带签发的 `client_id` 与授予的 `scope`(空格分隔),一并交出。
|
|
12
|
+
*/
|
|
13
|
+
import { createServer } from "node:http";
|
|
14
|
+
import { AmaError } from "../../errors.js";
|
|
15
|
+
export const CALLBACK_PATH = "/auth/callback";
|
|
16
|
+
export const CALLBACK_HOST = "127.0.0.1";
|
|
17
|
+
export const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60_000;
|
|
18
|
+
const DEFAULT_PAGES = {
|
|
19
|
+
success: "Signed in. You can close this page and return to the terminal.",
|
|
20
|
+
failure: "Sign-in failed. Return to the terminal for details.",
|
|
21
|
+
};
|
|
22
|
+
function page(text) {
|
|
23
|
+
const escaped = text.replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
|
|
24
|
+
return `<!doctype html><meta charset="utf-8"><title>ama</title><body style="font-family:sans-serif;padding:3em"><p>${escaped}</p></body>`;
|
|
25
|
+
}
|
|
26
|
+
/** 回调参数 → code;state 不符 / 带 error / 缺 code 抛 AmaError(不含 code 原文)。 */
|
|
27
|
+
export function codeFromParams(params, state) {
|
|
28
|
+
const error = params.get("error");
|
|
29
|
+
if (error !== null) {
|
|
30
|
+
throw new AmaError("oauth_denied", `authorization failed: ${error.slice(0, 64)}`, {
|
|
31
|
+
detail: { error: error.slice(0, 64) },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
if (params.get("state") !== state) {
|
|
35
|
+
throw new AmaError("oauth_state_mismatch", "OAuth state mismatch");
|
|
36
|
+
}
|
|
37
|
+
const code = params.get("code");
|
|
38
|
+
if (code === null || code === "")
|
|
39
|
+
throw new AmaError("oauth_no_code", "callback has no code");
|
|
40
|
+
const out = { code };
|
|
41
|
+
const clientId = params.get("client_id");
|
|
42
|
+
if (clientId)
|
|
43
|
+
out.clientId = clientId;
|
|
44
|
+
const scope = params.get("scope");
|
|
45
|
+
if (scope)
|
|
46
|
+
out.scope = scope;
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
/** `--paste`:粘回来的整条回调 URL(或只有查询串)。 */
|
|
50
|
+
export function parseCallbackUrl(input, state) {
|
|
51
|
+
const text = input.trim();
|
|
52
|
+
let params;
|
|
53
|
+
try {
|
|
54
|
+
params = new URL(text).searchParams;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
params = new URLSearchParams(text.startsWith("?") ? text.slice(1) : text);
|
|
58
|
+
}
|
|
59
|
+
return codeFromParams(params, state);
|
|
60
|
+
}
|
|
61
|
+
function listen(server, port) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const onError = (error) => {
|
|
64
|
+
server.off("listening", onListening);
|
|
65
|
+
reject(error);
|
|
66
|
+
};
|
|
67
|
+
const onListening = () => {
|
|
68
|
+
server.off("error", onError);
|
|
69
|
+
const address = server.address();
|
|
70
|
+
resolve(typeof address === "object" && address !== null ? address.port : port);
|
|
71
|
+
};
|
|
72
|
+
server.once("error", onError);
|
|
73
|
+
server.once("listening", onListening);
|
|
74
|
+
server.listen(port, CALLBACK_HOST);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function startCallbackServer(options) {
|
|
78
|
+
const path = options.path ?? CALLBACK_PATH;
|
|
79
|
+
const pages = options.pages ?? DEFAULT_PAGES;
|
|
80
|
+
let settle;
|
|
81
|
+
const result = new Promise((resolve, reject) => {
|
|
82
|
+
settle = { resolve, reject };
|
|
83
|
+
});
|
|
84
|
+
// 调用方可能只在失败时才 await
|
|
85
|
+
result.catch(() => { });
|
|
86
|
+
let done = false;
|
|
87
|
+
const server = createServer((req, res) => {
|
|
88
|
+
const url = new URL(req.url ?? "/", `http://${CALLBACK_HOST}`);
|
|
89
|
+
if (req.method !== "GET" || url.pathname !== path || done) {
|
|
90
|
+
res.writeHead(404, { "content-type": "text/plain" }).end("not found");
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const params = codeFromParams(url.searchParams, options.state);
|
|
95
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" }).end(page(pages.success));
|
|
96
|
+
finish(undefined, params);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
res.writeHead(400, { "content-type": "text/html; charset=utf-8" }).end(page(pages.failure));
|
|
100
|
+
// state 不符可能是别人的请求:只有 error 回调才结束等待
|
|
101
|
+
if (error.code !== "oauth_state_mismatch")
|
|
102
|
+
finish(error);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
let port;
|
|
106
|
+
for (const candidate of options.ports) {
|
|
107
|
+
try {
|
|
108
|
+
port = await listen(server, candidate);
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
if (error.code !== "EADDRINUSE")
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (port === undefined) {
|
|
117
|
+
throw new AmaError("oauth_ports_busy", `callback ports busy: ${options.ports.join(", ")}`, {
|
|
118
|
+
detail: { ports: [...options.ports] },
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const timer = setTimeout(() => finish(new AmaError("oauth_timeout", "login timed out")), options.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS);
|
|
122
|
+
const onAbort = () => finish(new AmaError("aborted", "login aborted"));
|
|
123
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
124
|
+
function finish(error, params) {
|
|
125
|
+
if (done)
|
|
126
|
+
return;
|
|
127
|
+
done = true;
|
|
128
|
+
clearTimeout(timer);
|
|
129
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
130
|
+
server.close();
|
|
131
|
+
server.closeAllConnections?.();
|
|
132
|
+
if (params !== undefined)
|
|
133
|
+
settle?.resolve(params);
|
|
134
|
+
else
|
|
135
|
+
settle?.reject(error);
|
|
136
|
+
}
|
|
137
|
+
if (options.signal?.aborted)
|
|
138
|
+
onAbort();
|
|
139
|
+
return {
|
|
140
|
+
port,
|
|
141
|
+
redirectUri: `http://${CALLBACK_HOST}:${port}${path}`,
|
|
142
|
+
result,
|
|
143
|
+
close: async () => {
|
|
144
|
+
finish(new AmaError("aborted", "login aborted"));
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -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;
|