@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,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SIWC 的 `ext_agent_host_id`(docs/wave6-plan.md D16):每个安装稳定不变,存
|
|
3
|
+
* `<dataDir>/chatgpt-host.json`(0600,按安装、不随 dotfiles 同步),形如 `urn:uuid:…`。[W6-O]
|
|
4
|
+
*/
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
export const HOST_ID_FILE = "chatgpt-host.json";
|
|
9
|
+
export function hostIdPath(dataDir) {
|
|
10
|
+
return join(dataDir, HOST_ID_FILE);
|
|
11
|
+
}
|
|
12
|
+
export function readOrCreateHostId(dataDir) {
|
|
13
|
+
const path = hostIdPath(dataDir);
|
|
14
|
+
try {
|
|
15
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
16
|
+
if (typeof value.hostId === "string" && /^urn:uuid:[0-9a-f-]{36}$/i.test(value.hostId))
|
|
17
|
+
return value.hostId;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// 不存在或损坏:重建
|
|
21
|
+
}
|
|
22
|
+
const hostId = `urn:uuid:${randomUUID()}`;
|
|
23
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
24
|
+
writeFileSync(path, `${JSON.stringify({ version: 1, hostId }, null, 2)}\n`, { mode: 0o600 });
|
|
25
|
+
return hostId;
|
|
26
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGPT 登录 / 登出编排(docs/wave6-plan.md §4.1–§4.3、D13–D16)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 登录:预设 → 授权码(browser / paste / device)→ 换 token(表单编码;SIWC 带 `resource`,client id 用
|
|
5
|
+
* 回调带回的签发 id)→ 校验(SIWC:JWKS 验签 iss / aud / nonce / exp + 授予 scope 含
|
|
6
|
+
* `chatgpt.tokens.use.direct`;codex:只解码取账户)→ `OAuthAuthEntry`。写文件由调用方经 token-store 做。
|
|
7
|
+
*
|
|
8
|
+
* 登出:SIWC 调 discovery 里的 `revocation_endpoint`(`token=<refresh>`、`token_type_hint=refresh_token`、
|
|
9
|
+
* `client_id`),失败不阻止删除本地条目;codex 只删本地。
|
|
10
|
+
*/
|
|
11
|
+
import type { ChatGptFlavor, OAuthAuthEntry } from "../../config/types-w6.js";
|
|
12
|
+
import { type AuthorizationResult, type FlowDeps } from "../oauth/flows.js";
|
|
13
|
+
import { type FetchLike } from "../oauth/token-client.js";
|
|
14
|
+
import { type ChatGptPreset, type PresetOverrides } from "./presets.js";
|
|
15
|
+
export type LoginMethod = "browser" | "paste" | "device";
|
|
16
|
+
export interface ChatGptLoginOptions extends PresetOverrides {
|
|
17
|
+
flavor: ChatGptFlavor;
|
|
18
|
+
method: LoginMethod;
|
|
19
|
+
noBrowser?: boolean;
|
|
20
|
+
/** SIWC 的 `ext_agent_host_id` 存放目录(`<dataDir>`)。 */
|
|
21
|
+
dataDir: string;
|
|
22
|
+
/** codex 的一次性确认时刻(ISO);调用方负责问。 */
|
|
23
|
+
acknowledgedAt?: string | undefined;
|
|
24
|
+
deps: FlowDeps;
|
|
25
|
+
}
|
|
26
|
+
export declare const DEFAULT_ACCESS_TTL_S = 3600;
|
|
27
|
+
export declare function exchangeCode(fetchFn: FetchLike, preset: ChatGptPreset, auth: AuthorizationResult, signal?: AbortSignal): Promise<OAuthAuthEntry>;
|
|
28
|
+
export declare function loginChatGpt(options: ChatGptLoginOptions): Promise<OAuthAuthEntry>;
|
|
29
|
+
/** SIWC 登出:撤销 refresh token。返回是否确认撤销(失败 / 不支持为 false,不抛)。 */
|
|
30
|
+
export declare function revokeChatGpt(fetchFn: FetchLike, entry: OAuthAuthEntry, overrides?: PresetOverrides): Promise<boolean>;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChatGPT 登录 / 登出编排(docs/wave6-plan.md §4.1–§4.3、D13–D16)。[W6-O]
|
|
3
|
+
*
|
|
4
|
+
* 登录:预设 → 授权码(browser / paste / device)→ 换 token(表单编码;SIWC 带 `resource`,client id 用
|
|
5
|
+
* 回调带回的签发 id)→ 校验(SIWC:JWKS 验签 iss / aud / nonce / exp + 授予 scope 含
|
|
6
|
+
* `chatgpt.tokens.use.direct`;codex:只解码取账户)→ `OAuthAuthEntry`。写文件由调用方经 token-store 做。
|
|
7
|
+
*
|
|
8
|
+
* 登出:SIWC 调 discovery 里的 `revocation_endpoint`(`token=<refresh>`、`token_type_hint=refresh_token`、
|
|
9
|
+
* `client_id`),失败不阻止删除本地条目;codex 只删本地。
|
|
10
|
+
*/
|
|
11
|
+
import { AmaError } from "../../errors.js";
|
|
12
|
+
import { loginBrowser, loginDevice, loginPaste, } from "../oauth/flows.js";
|
|
13
|
+
import { jwtClaims } from "../oauth/jwt.js";
|
|
14
|
+
import { fetchDiscovery, validateIdToken } from "../oauth/oidc.js";
|
|
15
|
+
import { parseTokenResponse, postOAuth } from "../oauth/token-client.js";
|
|
16
|
+
import { chatgptAccount, hasScope } from "./claims.js";
|
|
17
|
+
import { readOrCreateHostId } from "./host-id.js";
|
|
18
|
+
import { SIWC_REGISTRATION_CLIENT_ID, authorizeUrl, resolveChatGptPreset, } from "./presets.js";
|
|
19
|
+
export const DEFAULT_ACCESS_TTL_S = 3600;
|
|
20
|
+
function flowPreset(preset, hostId) {
|
|
21
|
+
return {
|
|
22
|
+
issuer: preset.issuer,
|
|
23
|
+
clientId: preset.clientId,
|
|
24
|
+
redirectPorts: preset.redirectPorts,
|
|
25
|
+
buildAuthorizeUrl: (input) => authorizeUrl(preset, { ...input, hostId }),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function issuedClientId(preset, auth, tokenClientId, idToken) {
|
|
29
|
+
if (!preset.registering)
|
|
30
|
+
return preset.clientId;
|
|
31
|
+
const fromAud = () => {
|
|
32
|
+
const aud = jwtClaims(idToken)?.["aud"];
|
|
33
|
+
const list = Array.isArray(aud) ? aud : [aud];
|
|
34
|
+
return list.find((a) => typeof a === "string" && a !== SIWC_REGISTRATION_CLIENT_ID);
|
|
35
|
+
};
|
|
36
|
+
const issued = auth.clientId ?? tokenClientId ?? fromAud();
|
|
37
|
+
if (issued === undefined || issued === SIWC_REGISTRATION_CLIENT_ID)
|
|
38
|
+
throw new AmaError("oauth_no_client_id", "dynamic registration returned no client id");
|
|
39
|
+
return issued;
|
|
40
|
+
}
|
|
41
|
+
export async function exchangeCode(fetchFn, preset, auth, signal) {
|
|
42
|
+
// 新注册:交换要用签发的 id(回调带回);回调没带时先用注册入口换,再从 id_token 的 aud 取
|
|
43
|
+
const exchangeClient = preset.registering ? (auth.clientId ?? preset.clientId) : preset.clientId;
|
|
44
|
+
const params = {
|
|
45
|
+
grant_type: "authorization_code",
|
|
46
|
+
code: auth.code,
|
|
47
|
+
redirect_uri: auth.redirectUri,
|
|
48
|
+
client_id: exchangeClient,
|
|
49
|
+
code_verifier: auth.verifier,
|
|
50
|
+
};
|
|
51
|
+
if (preset.resource !== undefined)
|
|
52
|
+
params["resource"] = preset.resource;
|
|
53
|
+
const token = parseTokenResponse(await postOAuth(fetchFn, preset.tokenUrl, params, {
|
|
54
|
+
encoding: "form",
|
|
55
|
+
what: "token exchange",
|
|
56
|
+
signal,
|
|
57
|
+
}), "token exchange");
|
|
58
|
+
if (token.refreshToken === undefined)
|
|
59
|
+
throw new AmaError("oauth_bad_response", "token exchange: no refresh_token");
|
|
60
|
+
const clientId = issuedClientId(preset, auth, token.clientId, token.idToken);
|
|
61
|
+
let claims = jwtClaims(token.idToken);
|
|
62
|
+
if (preset.verifyIdToken) {
|
|
63
|
+
if (token.idToken === undefined)
|
|
64
|
+
throw new AmaError("oauth_invalid_token", "invalid id_token: missing");
|
|
65
|
+
claims = await validateIdToken(fetchFn, token.idToken, {
|
|
66
|
+
issuer: preset.issuer,
|
|
67
|
+
audience: clientId,
|
|
68
|
+
nonce: auth.nonce,
|
|
69
|
+
});
|
|
70
|
+
const scope = auth.scope ?? token.scope;
|
|
71
|
+
if (preset.requiredScope !== undefined && !hasScope(scope, preset.requiredScope))
|
|
72
|
+
throw new AmaError("oauth_scope_missing", `granted scope lacks ${preset.requiredScope}`, {
|
|
73
|
+
detail: { scope: preset.requiredScope },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const account = chatgptAccount(claims, preset.flavor);
|
|
77
|
+
if (preset.flavor === "codex" && account.accountId === undefined)
|
|
78
|
+
throw new AmaError("oauth_no_account", "id_token has no ChatGPT account id");
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const entry = {
|
|
81
|
+
type: "oauth",
|
|
82
|
+
flavor: preset.flavor,
|
|
83
|
+
clientId,
|
|
84
|
+
issuer: preset.issuer,
|
|
85
|
+
...account,
|
|
86
|
+
accessToken: token.accessToken,
|
|
87
|
+
refreshToken: token.refreshToken,
|
|
88
|
+
...(token.idToken !== undefined ? { idToken: token.idToken } : {}),
|
|
89
|
+
expiresAt: now + (token.expiresIn ?? DEFAULT_ACCESS_TTL_S) * 1000,
|
|
90
|
+
lastRefresh: new Date(now).toISOString(),
|
|
91
|
+
};
|
|
92
|
+
return entry;
|
|
93
|
+
}
|
|
94
|
+
export async function loginChatGpt(options) {
|
|
95
|
+
const preset = resolveChatGptPreset(options.flavor, options);
|
|
96
|
+
if (options.method === "device" && !preset.device)
|
|
97
|
+
throw new AmaError("oauth_device_unsupported", "device code login needs --flavor codex");
|
|
98
|
+
const hostId = preset.flavor === "siwc" ? readOrCreateHostId(options.dataDir) : undefined;
|
|
99
|
+
const flow = flowPreset(preset, hostId);
|
|
100
|
+
const auth = options.method === "device"
|
|
101
|
+
? await loginDevice(flow, options.deps)
|
|
102
|
+
: options.method === "paste"
|
|
103
|
+
? await loginPaste(flow, options.deps)
|
|
104
|
+
: await loginBrowser(flow, options.deps, { noBrowser: options.noBrowser === true });
|
|
105
|
+
const entry = await exchangeCode(options.deps.fetch, preset, auth, options.deps.signal);
|
|
106
|
+
if (options.acknowledgedAt !== undefined)
|
|
107
|
+
entry.acknowledgedAt = options.acknowledgedAt;
|
|
108
|
+
return entry;
|
|
109
|
+
}
|
|
110
|
+
/** SIWC 登出:撤销 refresh token。返回是否确认撤销(失败 / 不支持为 false,不抛)。 */
|
|
111
|
+
export async function revokeChatGpt(fetchFn, entry, overrides = {}) {
|
|
112
|
+
const preset = resolveChatGptPreset(entry.flavor, { ...overrides, entry });
|
|
113
|
+
if (!preset.revoke)
|
|
114
|
+
return false;
|
|
115
|
+
const issuer = entry.issuer ?? preset.issuer;
|
|
116
|
+
try {
|
|
117
|
+
const discovery = await fetchDiscovery(fetchFn, issuer);
|
|
118
|
+
if (discovery.revocationEndpoint === undefined)
|
|
119
|
+
return false;
|
|
120
|
+
await postOAuth(fetchFn, discovery.revocationEndpoint, {
|
|
121
|
+
token: entry.refreshToken,
|
|
122
|
+
token_type_hint: "refresh_token",
|
|
123
|
+
client_id: entry.clientId ?? preset.clientId,
|
|
124
|
+
}, { encoding: "form", what: "token revocation" });
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
import type { ChatGptAuthConfig, ChatGptFlavor, OAuthAuthEntry } from "../../config/types-w6.js";
|
|
14
|
+
export declare const CHATGPT_PROVIDER_ID = "chatgpt";
|
|
15
|
+
export declare const DEFAULT_ISSUER = "https://auth.openai.com";
|
|
16
|
+
export declare const SIWC_REGISTRATION_CLIENT_ID = "dynamic_agent_client";
|
|
17
|
+
export declare const CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
18
|
+
export declare const SIWC_RESOURCE = "https://api.openai.com/v1";
|
|
19
|
+
export declare const SIWC_REQUIRED_SCOPE = "chatgpt.tokens.use.direct";
|
|
20
|
+
export declare const AGENT_NAME_HINT = "ama";
|
|
21
|
+
export declare const DEFAULT_ORIGINATOR = "codex_cli_rs";
|
|
22
|
+
export declare const CHATGPT_BASE_URLS: Readonly<Record<ChatGptFlavor, string>>;
|
|
23
|
+
export interface AuthorizeInput {
|
|
24
|
+
redirectUri: string;
|
|
25
|
+
state: string;
|
|
26
|
+
challenge: string;
|
|
27
|
+
nonce: string;
|
|
28
|
+
/** SIWC:`<dataDir>/chatgpt-host.json` 的安装 id。 */
|
|
29
|
+
hostId?: string | undefined;
|
|
30
|
+
}
|
|
31
|
+
export interface ChatGptPreset {
|
|
32
|
+
flavor: ChatGptFlavor;
|
|
33
|
+
issuer: string;
|
|
34
|
+
clientId: string;
|
|
35
|
+
/** SIWC 还没有签发 id(用注册入口登录)。 */
|
|
36
|
+
registering: boolean;
|
|
37
|
+
authorizeUrl: string;
|
|
38
|
+
tokenUrl: string;
|
|
39
|
+
scopes: readonly string[];
|
|
40
|
+
redirectPorts: readonly number[];
|
|
41
|
+
/** SIWC 的 `resource`(交换与刷新都带)。 */
|
|
42
|
+
resource?: string;
|
|
43
|
+
refreshEncoding: "form" | "json";
|
|
44
|
+
/** 推理端点(渠道 baseUrl)。 */
|
|
45
|
+
baseUrl: string;
|
|
46
|
+
/** SIWC:id_token 必须 JWKS 验签,且授予 scope 含 `chatgpt.tokens.use.direct`。 */
|
|
47
|
+
verifyIdToken: boolean;
|
|
48
|
+
requiredScope?: string;
|
|
49
|
+
/** 设备码(只有 codex)。 */
|
|
50
|
+
device: boolean;
|
|
51
|
+
/** 登出调 revocation_endpoint(只有 SIWC)。 */
|
|
52
|
+
revoke: boolean;
|
|
53
|
+
originator?: string;
|
|
54
|
+
}
|
|
55
|
+
export interface PresetOverrides {
|
|
56
|
+
config?: ChatGptAuthConfig | undefined;
|
|
57
|
+
env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
58
|
+
/** 已有条目:SIWC 复用签发的 client id。 */
|
|
59
|
+
entry?: OAuthAuthEntry | undefined;
|
|
60
|
+
/** `--port N`。 */
|
|
61
|
+
port?: number | undefined;
|
|
62
|
+
}
|
|
63
|
+
export declare function chatgptBaseUrl(flavor: ChatGptFlavor, env?: Readonly<Record<string, string | undefined>>): string;
|
|
64
|
+
export declare function resolveChatGptPreset(flavor: ChatGptFlavor, overrides?: PresetOverrides): ChatGptPreset;
|
|
65
|
+
/** 授权 URL(参数顺序固定,便于测试)。 */
|
|
66
|
+
export declare function authorizeUrl(preset: ChatGptPreset, input: AuthorizeInput): string;
|
|
@@ -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
|
+
}
|