@armadra/agent 0.0.0-stage → 0.3.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 +37 -0
- package/LICENSE +21 -0
- package/README.md +470 -2
- package/dist/agent/agent.d.ts +56 -0
- package/dist/agent/agent.js +129 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +158 -0
- package/dist/agent/queue.d.ts +25 -0
- package/dist/agent/queue.js +55 -0
- package/dist/agent/retry.d.ts +22 -0
- package/dist/agent/retry.js +92 -0
- package/dist/agent/schema.d.ts +27 -0
- package/dist/agent/schema.js +186 -0
- package/dist/agent/session-cache.d.ts +100 -0
- package/dist/agent/session-cache.js +514 -0
- package/dist/agent/session-compaction.d.ts +49 -0
- package/dist/agent/session-compaction.js +251 -0
- package/dist/agent/session-core.d.ts +103 -0
- package/dist/agent/session-core.js +7 -0
- package/dist/agent/session-run.d.ts +55 -0
- package/dist/agent/session-run.js +246 -0
- package/dist/agent/session-state.d.ts +39 -0
- package/dist/agent/session-state.js +119 -0
- package/dist/agent/session-subagent.d.ts +54 -0
- package/dist/agent/session-subagent.js +157 -0
- package/dist/agent/session-sync.d.ts +20 -0
- package/dist/agent/session-sync.js +111 -0
- package/dist/agent/session-tools.d.ts +24 -0
- package/dist/agent/session-tools.js +277 -0
- package/dist/agent/session.d.ts +117 -0
- package/dist/agent/session.js +485 -0
- package/dist/agent/system-prompt.d.ts +49 -0
- package/dist/agent/system-prompt.js +121 -0
- package/dist/agent/tool-runner.d.ts +64 -0
- package/dist/agent/tool-runner.js +309 -0
- package/dist/agent/transform.d.ts +26 -0
- package/dist/agent/transform.js +171 -0
- package/dist/agent/types.d.ts +378 -0
- package/dist/agent/types.js +21 -0
- package/dist/ai/apis/anthropic-messages.d.ts +26 -0
- package/dist/ai/apis/anthropic-messages.js +214 -0
- package/dist/ai/apis/anthropic-request.d.ts +42 -0
- package/dist/ai/apis/anthropic-request.js +282 -0
- package/dist/ai/apis/api.d.ts +28 -0
- package/dist/ai/apis/api.js +102 -0
- package/dist/ai/apis/cache-params.d.ts +48 -0
- package/dist/ai/apis/cache-params.js +163 -0
- package/dist/ai/apis/google-generative-ai.d.ts +32 -0
- package/dist/ai/apis/google-generative-ai.js +249 -0
- package/dist/ai/apis/google-request.d.ts +40 -0
- package/dist/ai/apis/google-request.js +255 -0
- package/dist/ai/apis/openai-compat.d.ts +35 -0
- package/dist/ai/apis/openai-compat.js +142 -0
- package/dist/ai/apis/openai-completions.d.ts +28 -0
- package/dist/ai/apis/openai-completions.js +246 -0
- package/dist/ai/apis/openai-request.d.ts +35 -0
- package/dist/ai/apis/openai-request.js +303 -0
- package/dist/ai/apis/openai-responses-request.d.ts +52 -0
- package/dist/ai/apis/openai-responses-request.js +266 -0
- package/dist/ai/apis/openai-responses.d.ts +28 -0
- package/dist/ai/apis/openai-responses.js +304 -0
- package/dist/ai/apis/shared.d.ts +56 -0
- package/dist/ai/apis/shared.js +179 -0
- package/dist/ai/cache/economics.d.ts +24 -0
- package/dist/ai/cache/economics.js +69 -0
- package/dist/ai/cache/fingerprint.d.ts +17 -0
- package/dist/ai/cache/fingerprint.js +39 -0
- package/dist/ai/cache/miss.d.ts +49 -0
- package/dist/ai/cache/miss.js +96 -0
- package/dist/ai/cache/reporting.d.ts +39 -0
- package/dist/ai/cache/reporting.js +103 -0
- package/dist/ai/cache/types.d.ts +94 -0
- package/dist/ai/cache/types.js +19 -0
- package/dist/ai/cache/warmer.d.ts +94 -0
- package/dist/ai/cache/warmer.js +224 -0
- package/dist/ai/context.d.ts +35 -0
- package/dist/ai/context.js +114 -0
- package/dist/ai/cost.d.ts +21 -0
- package/dist/ai/cost.js +60 -0
- package/dist/ai/event-stream.d.ts +33 -0
- package/dist/ai/event-stream.js +101 -0
- package/dist/ai/fake/fake-provider.d.ts +68 -0
- package/dist/ai/fake/fake-provider.js +278 -0
- package/dist/ai/fake/fake-script.d.ts +69 -0
- package/dist/ai/fake/fake-script.js +142 -0
- package/dist/ai/http.d.ts +54 -0
- package/dist/ai/http.js +212 -0
- package/dist/ai/json-partial.d.ts +17 -0
- package/dist/ai/json-partial.js +243 -0
- package/dist/ai/overflow.d.ts +22 -0
- package/dist/ai/overflow.js +61 -0
- package/dist/ai/providers/auth.d.ts +71 -0
- package/dist/ai/providers/auth.js +202 -0
- package/dist/ai/providers/builtin.d.ts +23 -0
- package/dist/ai/providers/builtin.js +148 -0
- package/dist/ai/providers/catalog-data.d.ts +5 -0
- package/dist/ai/providers/catalog-data.js +19 -0
- package/dist/ai/providers/catalog.d.ts +28 -0
- package/dist/ai/providers/catalog.js +166 -0
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +114 -0
- package/dist/ai/providers/registry.js +458 -0
- package/dist/ai/sse.d.ts +32 -0
- package/dist/ai/sse.js +140 -0
- package/dist/ai/thinking.d.ts +42 -0
- package/dist/ai/thinking.js +101 -0
- package/dist/ai/types.d.ts +448 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +27837 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +85 -0
- package/dist/cli/args.js +408 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +399 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +107 -0
- package/dist/cli/compose-session.d.ts +72 -0
- package/dist/cli/compose-session.js +370 -0
- package/dist/cli/compose-store.d.ts +24 -0
- package/dist/cli/compose-store.js +125 -0
- package/dist/cli/compose.d.ts +108 -0
- package/dist/cli/compose.js +221 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +207 -0
- package/dist/cli/deps.js +12 -0
- package/dist/cli/exit-codes.d.ts +29 -0
- package/dist/cli/exit-codes.js +40 -0
- package/dist/cli/main.d.ts +27 -0
- package/dist/cli/main.js +177 -0
- package/dist/cli/runtime.d.ts +90 -0
- package/dist/cli/runtime.js +8 -0
- package/dist/cli/startup-screen.d.ts +15 -0
- package/dist/cli/startup-screen.js +56 -0
- package/dist/cli/startup-steps.d.ts +35 -0
- package/dist/cli/startup-steps.js +202 -0
- package/dist/cli/subcommands/auth.d.ts +12 -0
- package/dist/cli/subcommands/auth.js +91 -0
- package/dist/cli/subcommands/config.d.ts +53 -0
- package/dist/cli/subcommands/config.js +277 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +42 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -0
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +22 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
- package/dist/cli/subcommands/models-cache-probe.js +190 -0
- package/dist/cli/subcommands/models-discover.d.ts +46 -0
- package/dist/cli/subcommands/models-discover.js +232 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +152 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -0
- package/dist/cli/subcommands/providers.d.ts +27 -0
- package/dist/cli/subcommands/providers.js +433 -0
- package/dist/cli/subcommands/sessions.d.ts +11 -0
- package/dist/cli/subcommands/sessions.js +100 -0
- package/dist/codemode/capability.d.ts +37 -0
- package/dist/codemode/capability.js +55 -0
- package/dist/codemode/declarations.d.ts +44 -0
- package/dist/codemode/declarations.js +111 -0
- package/dist/codemode/host-side.d.ts +59 -0
- package/dist/codemode/host-side.js +253 -0
- package/dist/codemode/modes.d.ts +23 -0
- package/dist/codemode/modes.js +40 -0
- package/dist/codemode/protocol.d.ts +91 -0
- package/dist/codemode/protocol.js +114 -0
- package/dist/codemode/sandbox-entry.d.ts +25 -0
- package/dist/codemode/sandbox-entry.js +323 -0
- package/dist/codemode/store.d.ts +26 -0
- package/dist/codemode/store.js +53 -0
- package/dist/codemode/tool.d.ts +105 -0
- package/dist/codemode/tool.js +283 -0
- package/dist/compaction/branch-summary.d.ts +30 -0
- package/dist/compaction/branch-summary.js +79 -0
- package/dist/compaction/breaker.d.ts +32 -0
- package/dist/compaction/breaker.js +65 -0
- package/dist/compaction/cut-point.d.ts +23 -0
- package/dist/compaction/cut-point.js +70 -0
- package/dist/compaction/estimate.d.ts +30 -0
- package/dist/compaction/estimate.js +114 -0
- package/dist/compaction/prune-tier.d.ts +31 -0
- package/dist/compaction/prune-tier.js +83 -0
- package/dist/compaction/serialize.d.ts +20 -0
- package/dist/compaction/serialize.js +115 -0
- package/dist/compaction/summarize-tier.d.ts +111 -0
- package/dist/compaction/summarize-tier.js +305 -0
- package/dist/config/auth-file.d.ts +39 -0
- package/dist/config/auth-file.js +92 -0
- package/dist/config/context-files.d.ts +30 -0
- package/dist/config/context-files.js +77 -0
- package/dist/config/init.d.ts +30 -0
- package/dist/config/init.js +92 -0
- package/dist/config/json-schema.d.ts +14 -0
- package/dist/config/json-schema.js +182 -0
- package/dist/config/load.d.ts +45 -0
- package/dist/config/load.js +97 -0
- package/dist/config/merge.d.ts +73 -0
- package/dist/config/merge.js +248 -0
- package/dist/config/paths.d.ts +38 -0
- package/dist/config/paths.js +108 -0
- package/dist/config/profile.d.ts +27 -0
- package/dist/config/profile.js +65 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.js +476 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +190 -0
- package/dist/config/types.js +29 -0
- package/dist/config/write.d.ts +10 -0
- package/dist/config/write.js +25 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +30 -0
- package/dist/hooks/config.d.ts +49 -0
- package/dist/hooks/config.js +102 -0
- package/dist/hooks/dispatcher.d.ts +41 -0
- package/dist/hooks/dispatcher.js +104 -0
- package/dist/hooks/matcher.d.ts +22 -0
- package/dist/hooks/matcher.js +108 -0
- package/dist/hooks/protocol.d.ts +33 -0
- package/dist/hooks/protocol.js +240 -0
- package/dist/hooks/runner.d.ts +30 -0
- package/dist/hooks/runner.js +143 -0
- package/dist/hooks/types.d.ts +147 -0
- package/dist/hooks/types.js +20 -0
- package/dist/host/api-impl.d.ts +75 -0
- package/dist/host/api-impl.js +173 -0
- package/dist/host/loader.d.ts +31 -0
- package/dist/host/loader.js +147 -0
- package/dist/host/types.d.ts +169 -0
- package/dist/host/types.js +14 -0
- package/dist/host.d.ts +5 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +28 -0
- package/dist/modes/commands-core.d.ts +44 -0
- package/dist/modes/commands-core.js +171 -0
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- package/dist/modes/interactive/approval-dialog.d.ts +48 -0
- package/dist/modes/interactive/approval-dialog.js +202 -0
- package/dist/modes/interactive/commands.d.ts +41 -0
- package/dist/modes/interactive/commands.js +201 -0
- package/dist/modes/interactive/completion.d.ts +53 -0
- package/dist/modes/interactive/completion.js +157 -0
- package/dist/modes/interactive/interactive-mode.d.ts +46 -0
- package/dist/modes/interactive/interactive-mode.js +491 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -0
- package/dist/modes/interactive/key-dispatch.js +107 -0
- package/dist/modes/interactive/line/line-editor.d.ts +50 -0
- package/dist/modes/interactive/line/line-editor.js +232 -0
- package/dist/modes/interactive/line/line-mode.d.ts +20 -0
- package/dist/modes/interactive/line/line-mode.js +218 -0
- package/dist/modes/interactive/line/line-render.d.ts +35 -0
- package/dist/modes/interactive/line/line-render.js +171 -0
- package/dist/modes/interactive/line/paste-state.d.ts +26 -0
- package/dist/modes/interactive/line/paste-state.js +72 -0
- package/dist/modes/interactive/message-view.d.ts +77 -0
- package/dist/modes/interactive/message-view.js +269 -0
- package/dist/modes/interactive/pickers.d.ts +42 -0
- package/dist/modes/interactive/pickers.js +115 -0
- package/dist/modes/interactive/startup-ui.d.ts +33 -0
- package/dist/modes/interactive/startup-ui.js +268 -0
- package/dist/modes/interactive/status-bar.d.ts +48 -0
- package/dist/modes/interactive/status-bar.js +139 -0
- package/dist/modes/interactive/tool-view.d.ts +95 -0
- package/dist/modes/interactive/tool-view.js +344 -0
- package/dist/modes/print/json-event.d.ts +15 -0
- package/dist/modes/print/json-event.js +39 -0
- package/dist/modes/print/print-mode.d.ts +17 -0
- package/dist/modes/print/print-mode.js +107 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +263 -0
- package/dist/modes/rpc/jsonl.d.ts +16 -0
- package/dist/modes/rpc/jsonl.js +55 -0
- package/dist/modes/rpc/rpc-mode.d.ts +22 -0
- package/dist/modes/rpc/rpc-mode.js +153 -0
- package/dist/modes/session-report.d.ts +55 -0
- package/dist/modes/session-report.js +278 -0
- package/dist/modes/shared.d.ts +13 -0
- package/dist/modes/shared.js +52 -0
- package/dist/modes/startup-ui-text.d.ts +16 -0
- package/dist/modes/startup-ui-text.js +103 -0
- package/dist/permissions/broker.d.ts +47 -0
- package/dist/permissions/broker.js +107 -0
- package/dist/permissions/dangerous.d.ts +47 -0
- package/dist/permissions/dangerous.js +370 -0
- package/dist/permissions/pipeline.d.ts +51 -0
- package/dist/permissions/pipeline.js +181 -0
- package/dist/permissions/preview.d.ts +43 -0
- package/dist/permissions/preview.js +463 -0
- package/dist/permissions/rules.d.ts +58 -0
- package/dist/permissions/rules.js +249 -0
- package/dist/permissions/types.d.ts +100 -0
- package/dist/permissions/types.js +18 -0
- package/dist/rpc.d.ts +173 -0
- package/dist/rpc.js +13 -0
- package/dist/sdk.d.ts +98 -0
- package/dist/sdk.js +195 -0
- package/dist/session/manager.d.ts +70 -0
- package/dist/session/manager.js +278 -0
- package/dist/session/migrate.d.ts +19 -0
- package/dist/session/migrate.js +53 -0
- package/dist/session/projection.d.ts +50 -0
- package/dist/session/projection.js +184 -0
- package/dist/session/store.d.ts +47 -0
- package/dist/session/store.js +210 -0
- package/dist/session/tree.d.ts +21 -0
- package/dist/session/tree.js +88 -0
- package/dist/session/types.d.ts +201 -0
- package/dist/session/types.js +18 -0
- package/dist/skills/discover.d.ts +60 -0
- package/dist/skills/discover.js +160 -0
- package/dist/skills/expand.d.ts +35 -0
- package/dist/skills/expand.js +46 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +157 -0
- package/dist/skills/index-prompt.d.ts +13 -0
- package/dist/skills/index-prompt.js +42 -0
- package/dist/skills/templates.d.ts +52 -0
- package/dist/skills/templates.js +177 -0
- package/dist/tools/bash.d.ts +49 -0
- package/dist/tools/bash.js +222 -0
- package/dist/tools/edit-fuzzy.d.ts +36 -0
- package/dist/tools/edit-fuzzy.js +129 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +241 -0
- package/dist/tools/file-mutex.d.ts +8 -0
- package/dist/tools/file-mutex.js +29 -0
- package/dist/tools/glob.d.ts +44 -0
- package/dist/tools/glob.js +217 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.js +179 -0
- package/dist/tools/ignore.d.ts +40 -0
- package/dist/tools/ignore.js +174 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/ls.d.ts +21 -0
- package/dist/tools/ls.js +96 -0
- package/dist/tools/output-accumulator.d.ts +48 -0
- package/dist/tools/output-accumulator.js +106 -0
- package/dist/tools/paths.d.ts +20 -0
- package/dist/tools/paths.js +58 -0
- package/dist/tools/presets.d.ts +71 -0
- package/dist/tools/presets.js +128 -0
- package/dist/tools/process-tree.d.ts +35 -0
- package/dist/tools/process-tree.js +125 -0
- package/dist/tools/read.d.ts +24 -0
- package/dist/tools/read.js +132 -0
- package/dist/tools/registry.d.ts +43 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/shell.d.ts +38 -0
- package/dist/tools/shell.js +91 -0
- package/dist/tools/task.d.ts +29 -0
- package/dist/tools/task.js +107 -0
- package/dist/tools/todo.d.ts +28 -0
- package/dist/tools/todo.js +107 -0
- package/dist/tools/truncate.d.ts +44 -0
- package/dist/tools/truncate.js +175 -0
- package/dist/tools/types.d.ts +135 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/write.d.ts +20 -0
- package/dist/tools/write.js +84 -0
- package/dist/tui/ansi.d.ts +29 -0
- package/dist/tui/ansi.js +420 -0
- package/dist/tui/component.d.ts +38 -0
- package/dist/tui/component.js +13 -0
- package/dist/tui/components/box.d.ts +26 -0
- package/dist/tui/components/box.js +65 -0
- package/dist/tui/components/container.d.ts +14 -0
- package/dist/tui/components/container.js +39 -0
- package/dist/tui/components/editor-buffer.d.ts +67 -0
- package/dist/tui/components/editor-buffer.js +300 -0
- package/dist/tui/components/editor-paste.d.ts +29 -0
- package/dist/tui/components/editor-paste.js +64 -0
- package/dist/tui/components/editor.d.ts +114 -0
- package/dist/tui/components/editor.js +530 -0
- package/dist/tui/components/key-value.d.ts +29 -0
- package/dist/tui/components/key-value.js +48 -0
- package/dist/tui/components/loader.d.ts +35 -0
- package/dist/tui/components/loader.js +71 -0
- package/dist/tui/components/markdown.d.ts +90 -0
- package/dist/tui/components/markdown.js +462 -0
- package/dist/tui/components/meter.d.ts +29 -0
- package/dist/tui/components/meter.js +47 -0
- package/dist/tui/components/overlay.d.ts +24 -0
- package/dist/tui/components/overlay.js +52 -0
- package/dist/tui/components/select-list.d.ts +57 -0
- package/dist/tui/components/select-list.js +179 -0
- package/dist/tui/components/spacer.d.ts +11 -0
- package/dist/tui/components/spacer.js +16 -0
- package/dist/tui/components/text.d.ts +30 -0
- package/dist/tui/components/text.js +65 -0
- package/dist/tui/keybindings.d.ts +66 -0
- package/dist/tui/keybindings.js +120 -0
- package/dist/tui/keys.d.ts +38 -0
- package/dist/tui/keys.js +275 -0
- package/dist/tui/stdin-buffer.d.ts +41 -0
- package/dist/tui/stdin-buffer.js +206 -0
- package/dist/tui/terminal.d.ts +79 -0
- package/dist/tui/terminal.js +153 -0
- package/dist/tui/theme.d.ts +31 -0
- package/dist/tui/theme.js +199 -0
- package/dist/tui/tui.d.ts +103 -0
- package/dist/tui/tui.js +363 -0
- package/dist/tui/vt-screen.d.ts +46 -0
- package/dist/tui/vt-screen.js +251 -0
- package/dist/tui.d.ts +30 -0
- package/dist/tui.js +31 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +24 -0
- package/docs/codemode.md +102 -0
- package/docs/hooks.md +196 -0
- package/docs/host-api.md +161 -0
- package/docs/providers.md +531 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +170 -0
- package/package.json +101 -4
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 档二摘要(设计 §9「档二」「模板」)。[B2]
|
|
3
|
+
*
|
|
4
|
+
* `prepareCompaction()` 在投影上找切点,得到「要摘要的消息」与 `firstKeptEntryId`;
|
|
5
|
+
* `runCompaction()` 请模型按模板写摘要,split turn 时历史与回合前缀各一份再合并;
|
|
6
|
+
* `<read-files>` / `<modified-files>` 与上一份 compaction 的 details 累计。失败抛
|
|
7
|
+
* `AmaError{code:"compaction_failed"}`,abort 抛 `aborted`。
|
|
8
|
+
*
|
|
9
|
+
* [W3-C1b] 会话前缀续写(第三波 §1.8):调用方给出 `continuation.prefix`(与上一次真实请求
|
|
10
|
+
* 逐字节同前缀的完整转录)时,请求 = 前缀 + 一条摘要指令(「只摘要第 1–N 条,第 N+1 条起
|
|
11
|
+
* 保留原文」+ 首行引文),`cacheRetention: "short"`、`purpose: "summary"`,按读价计费。
|
|
12
|
+
* **不发 `toolChoice`**:实测(docs/benchmarks/cache-2026-10-02.md E3)中转与 Kimi 在
|
|
13
|
+
* `tool_choice: "none"` 时渲染的提示不带工具定义,前缀在工具段断开、cacheRead 为 0;Anthropic
|
|
14
|
+
* 文档也写明改动 tool_choice 会让消息缓存失效。所以 system + tools + 消息前缀与上一次真实请求
|
|
15
|
+
* 逐字节相同,「不调用工具、只输出摘要」只写在末尾指令里;响应为空 / 被截断 / 含工具调用 / 请求出错 → 回落现行独立请求(序列化转录、
|
|
16
|
+
* `cacheRetention: "none"`)并经 `onFallback` 记 warning。
|
|
17
|
+
*/
|
|
18
|
+
import { AmaError } from "../errors.js";
|
|
19
|
+
import { findCutPoint, summarizableStart } from "./cut-point.js";
|
|
20
|
+
import { collectFileOps, createFileOps, fileOpsDetails, formatFileOps, serializeConversation, } from "./serialize.js";
|
|
21
|
+
export const SUMMARY_MAX_TOKENS = 4096;
|
|
22
|
+
export const SUMMARIZATION_SYSTEM_PROMPT = "You are a context summarization assistant. You read a record of a coding session between a " +
|
|
23
|
+
"user and an AI coding agent and write a structured checkpoint that another instance of the " +
|
|
24
|
+
"agent will use to continue the work. Do not continue the conversation and do not answer " +
|
|
25
|
+
"questions found in the record; only output the summary.";
|
|
26
|
+
export const SUMMARY_TEMPLATE = `Write the checkpoint summary using EXACTLY this format:
|
|
27
|
+
|
|
28
|
+
## Goal
|
|
29
|
+
[What the user is trying to accomplish; several items if the session covers several tasks.]
|
|
30
|
+
|
|
31
|
+
## Constraints & Preferences
|
|
32
|
+
- [Constraints, preferences, or requirements stated by the user, or "(none)"]
|
|
33
|
+
|
|
34
|
+
## Progress
|
|
35
|
+
### Done
|
|
36
|
+
- [x] [Completed tasks and changes]
|
|
37
|
+
|
|
38
|
+
### In Progress
|
|
39
|
+
- [ ] [Current work]
|
|
40
|
+
|
|
41
|
+
### Blocked
|
|
42
|
+
- [Issues preventing progress, if any]
|
|
43
|
+
|
|
44
|
+
## Key Decisions
|
|
45
|
+
- **[Decision]**: [Short rationale]
|
|
46
|
+
|
|
47
|
+
## Next Steps
|
|
48
|
+
1. [Ordered list of what should happen next]
|
|
49
|
+
|
|
50
|
+
## Critical Context
|
|
51
|
+
- [Data, file paths, identifiers, error messages, or references needed to continue, or "(none)"]
|
|
52
|
+
|
|
53
|
+
Keep every section concise. Preserve exact file paths, function names, and error messages.`;
|
|
54
|
+
/** 续写指令的首句(测试据此识别摘要请求)。 */
|
|
55
|
+
export const SUMMARY_CONTINUATION_PREAMBLE = "Stop here: do not continue the conversation, do not answer questions from it and do not call any tools.";
|
|
56
|
+
/** 续写指令的末句:工具仍在请求里(为了缓存前缀),只靠指令禁止调用。 */
|
|
57
|
+
export const SUMMARY_CONTINUATION_TAIL = "Tools are still listed above, but you must not call any of them now. Reply with the summary text only.";
|
|
58
|
+
const TURN_PREFIX_PROMPT = `The record above is the BEGINNING of the current turn; the rest of the turn is kept verbatim after this summary. Summarize only what the agent was asked to do in this turn and what it has done so far, in a short "## Turn So Far" section followed by "## Critical Context".`;
|
|
59
|
+
const TURN_PREFIX_CONTINUATION_PROMPT = `These messages are the BEGINNING of the current turn; the rest of the turn is kept verbatim after this summary. Summarize only what the agent was asked to do in this turn and what it has done so far, in a short "## Turn So Far" section followed by "## Critical Context".`;
|
|
60
|
+
/** 转录里(不计 system)实际发给模型的消息数:失败 / 中断的助手消息连同其工具结果不回放。 */
|
|
61
|
+
export function countLlmMessages(messages) {
|
|
62
|
+
let count = 0;
|
|
63
|
+
let skipping = false;
|
|
64
|
+
for (const message of messages) {
|
|
65
|
+
if (message.role === "system")
|
|
66
|
+
continue;
|
|
67
|
+
if (message.role === "toolResult" && skipping)
|
|
68
|
+
continue;
|
|
69
|
+
skipping =
|
|
70
|
+
message.role === "assistant" &&
|
|
71
|
+
(message.stopReason === "error" || message.stopReason === "aborted");
|
|
72
|
+
if (!skipping)
|
|
73
|
+
count++;
|
|
74
|
+
}
|
|
75
|
+
return count;
|
|
76
|
+
}
|
|
77
|
+
/** 消息首行引文(≤ 80 字符)。 */
|
|
78
|
+
export function excerptOf(message) {
|
|
79
|
+
if (message === undefined || message.role === "system")
|
|
80
|
+
return "";
|
|
81
|
+
let text;
|
|
82
|
+
if (message.role === "compactionSummary" || message.role === "branchSummary")
|
|
83
|
+
text = message.summary;
|
|
84
|
+
else if (message.role === "assistant")
|
|
85
|
+
text = message.content
|
|
86
|
+
.map((block) => block.type === "text" ? block.text : block.type === "toolCall" ? `[${block.name}]` : "")
|
|
87
|
+
.join(" ");
|
|
88
|
+
else if (typeof message.content === "string")
|
|
89
|
+
text = message.content;
|
|
90
|
+
else
|
|
91
|
+
text = message.content.map((block) => (block.type === "text" ? block.text : "")).join(" ");
|
|
92
|
+
const line = text.trim().split("\n")[0] ?? "";
|
|
93
|
+
return line.length > 80 ? `${line.slice(0, 79)}…` : line;
|
|
94
|
+
}
|
|
95
|
+
/** 无可摘要内容时返回 undefined。 */
|
|
96
|
+
export function prepareCompaction(projection, keepRecentTokens) {
|
|
97
|
+
const items = projection.items;
|
|
98
|
+
const start = summarizableStart(items);
|
|
99
|
+
const cut = findCutPoint(items, start, keepRecentTokens);
|
|
100
|
+
if (cut.firstKeptIndex <= start)
|
|
101
|
+
return undefined;
|
|
102
|
+
const keptItem = items[cut.firstKeptIndex];
|
|
103
|
+
if (keptItem === undefined)
|
|
104
|
+
return undefined;
|
|
105
|
+
const historyEnd = cut.isSplitTurn ? cut.turnStartIndex : cut.firstKeptIndex;
|
|
106
|
+
const pick = (from, to) => items
|
|
107
|
+
.slice(from, to)
|
|
108
|
+
.map((item) => item.message)
|
|
109
|
+
.filter((message) => message.role !== "system");
|
|
110
|
+
const previous = projection.compaction;
|
|
111
|
+
const messages = items.map((item) => item.message);
|
|
112
|
+
const anchors = {
|
|
113
|
+
historyEnd: countLlmMessages(messages.slice(0, historyEnd)),
|
|
114
|
+
keptStart: countLlmMessages(messages.slice(0, cut.firstKeptIndex)),
|
|
115
|
+
historyExcerpt: excerptOf(messages[historyEnd]),
|
|
116
|
+
keptExcerpt: excerptOf(keptItem.message),
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
anchors,
|
|
120
|
+
toSummarize: pick(start, historyEnd),
|
|
121
|
+
turnPrefix: cut.isSplitTurn ? pick(cut.turnStartIndex, cut.firstKeptIndex) : [],
|
|
122
|
+
firstKeptEntryId: keptItem.entry.id,
|
|
123
|
+
cut,
|
|
124
|
+
previousSummary: previous?.summary,
|
|
125
|
+
previousDetails: previous?.details,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function addUsage(a, b) {
|
|
129
|
+
if (a === undefined)
|
|
130
|
+
return { ...b };
|
|
131
|
+
return {
|
|
132
|
+
input: a.input + b.input,
|
|
133
|
+
output: a.output + b.output,
|
|
134
|
+
cacheRead: a.cacheRead + b.cacheRead,
|
|
135
|
+
cacheWrite: a.cacheWrite + b.cacheWrite,
|
|
136
|
+
totalTokens: a.totalTokens + b.totalTokens,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function assistantText(message) {
|
|
140
|
+
return message.content
|
|
141
|
+
.filter((block) => block.type === "text")
|
|
142
|
+
.map((block) => block.text)
|
|
143
|
+
.join("")
|
|
144
|
+
.trim();
|
|
145
|
+
}
|
|
146
|
+
/** 一次无工具的文本补全;错误 / 取消 / 空输出抛错。 */
|
|
147
|
+
export async function completeText(options, userText) {
|
|
148
|
+
const context = {
|
|
149
|
+
messages: [
|
|
150
|
+
{
|
|
151
|
+
role: "system",
|
|
152
|
+
sections: { preamble: SUMMARIZATION_SYSTEM_PROMPT },
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
},
|
|
155
|
+
{ role: "user", content: userText, timestamp: Date.now() },
|
|
156
|
+
],
|
|
157
|
+
};
|
|
158
|
+
const maxTokens = Math.min(options.maxTokens ?? SUMMARY_MAX_TOKENS, options.model.maxTokens);
|
|
159
|
+
let message;
|
|
160
|
+
try {
|
|
161
|
+
const streamOptions = {
|
|
162
|
+
signal: options.signal,
|
|
163
|
+
maxTokens,
|
|
164
|
+
cacheRetention: "none",
|
|
165
|
+
purpose: "summary",
|
|
166
|
+
};
|
|
167
|
+
if (options.apiKey !== undefined)
|
|
168
|
+
streamOptions.apiKey = options.apiKey;
|
|
169
|
+
const stream = options.stream(options.model, context, streamOptions);
|
|
170
|
+
message = await stream.result();
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (options.signal.aborted)
|
|
174
|
+
throw new AmaError("aborted", "compaction aborted");
|
|
175
|
+
throw new AmaError("compaction_failed", `summary request failed: ${String(error)}`, {
|
|
176
|
+
cause: error,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return checkSummary(message, options.signal);
|
|
180
|
+
}
|
|
181
|
+
function checkSummary(message, signal) {
|
|
182
|
+
if (message.stopReason === "aborted" || signal.aborted) {
|
|
183
|
+
throw new AmaError("aborted", "compaction aborted");
|
|
184
|
+
}
|
|
185
|
+
if (message.stopReason === "error") {
|
|
186
|
+
throw new AmaError("compaction_failed", message.errorMessage ?? "summary request failed");
|
|
187
|
+
}
|
|
188
|
+
const text = assistantText(message);
|
|
189
|
+
if (text === "")
|
|
190
|
+
throw new AmaError("compaction_failed", "summary model returned no text");
|
|
191
|
+
return { text, usage: message.usage };
|
|
192
|
+
}
|
|
193
|
+
function continuationPrompt(input, total, instructions) {
|
|
194
|
+
const { index, excerpt } = input.keepFrom;
|
|
195
|
+
const from = input.from ?? 0;
|
|
196
|
+
const parts = [SUMMARY_CONTINUATION_PREAMBLE];
|
|
197
|
+
const range = from + 1 === index ? `message ${index}` : `messages ${from + 1}–${index}`;
|
|
198
|
+
parts.push(`Write a summary of ${range} of the conversation above ` +
|
|
199
|
+
"(count user, assistant and tool-result messages from the start of the conversation; " +
|
|
200
|
+
"a summary of earlier history at the start counts as a message and must be merged in).");
|
|
201
|
+
if (index < total && excerpt !== "") {
|
|
202
|
+
parts.push(`Message ${index + 1} begins with: «${excerpt}». It and everything after it stay in the context verbatim, so do not summarize them.`);
|
|
203
|
+
}
|
|
204
|
+
parts.push(input.instruction);
|
|
205
|
+
if (instructions !== undefined && instructions.trim() !== "") {
|
|
206
|
+
parts.push(`Additional instructions:\n${instructions.trim()}`);
|
|
207
|
+
}
|
|
208
|
+
parts.push(SUMMARY_CONTINUATION_TAIL);
|
|
209
|
+
return parts.join("\n\n");
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* 会话前缀续写:前缀 + 一条摘要指令,不发 `toolChoice`(保住含工具段的缓存前缀)。空 / 截断 /
|
|
213
|
+
* 含工具调用 / 出错抛 `compaction_failed`(调用方回落),abort 抛 `aborted`。
|
|
214
|
+
*/
|
|
215
|
+
export async function completeByContinuation(options, input) {
|
|
216
|
+
const total = input.prefix.messages.filter((message) => message.role !== "system").length;
|
|
217
|
+
const prompt = continuationPrompt(input, total, options.customInstructions);
|
|
218
|
+
const context = {
|
|
219
|
+
messages: [...input.prefix.messages, { role: "user", content: prompt, timestamp: Date.now() }],
|
|
220
|
+
};
|
|
221
|
+
// 改动 tool_choice 会让缓存前缀失效:即使调用方传了也不带。
|
|
222
|
+
const { toolChoice: _toolChoice, ...base } = options.continuation?.streamOptions ?? {};
|
|
223
|
+
const streamOptions = {
|
|
224
|
+
...base,
|
|
225
|
+
signal: options.signal,
|
|
226
|
+
maxTokens: base.maxTokens ?? Math.min(options.maxTokens ?? SUMMARY_MAX_TOKENS, options.model.maxTokens),
|
|
227
|
+
cacheRetention: "short",
|
|
228
|
+
purpose: "summary",
|
|
229
|
+
};
|
|
230
|
+
if (options.apiKey !== undefined)
|
|
231
|
+
streamOptions.apiKey = options.apiKey;
|
|
232
|
+
let message;
|
|
233
|
+
try {
|
|
234
|
+
message = await options.stream(options.model, context, streamOptions).result();
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
if (options.signal.aborted)
|
|
238
|
+
throw new AmaError("aborted", "compaction aborted");
|
|
239
|
+
throw new AmaError("compaction_failed", `summary request failed: ${String(error)}`);
|
|
240
|
+
}
|
|
241
|
+
if (message.stopReason === "length")
|
|
242
|
+
throw new AmaError("compaction_failed", "summary was truncated");
|
|
243
|
+
if (message.content.some((block) => block.type === "toolCall"))
|
|
244
|
+
throw new AmaError("compaction_failed", "summary response called a tool");
|
|
245
|
+
return checkSummary(message, options.signal);
|
|
246
|
+
}
|
|
247
|
+
/** 先试续写,失败(非 abort)回落独立请求。 */
|
|
248
|
+
export async function summarizeWithFallback(options, continuation, independentPrompt) {
|
|
249
|
+
const prefix = options.continuation?.prefix;
|
|
250
|
+
if (prefix !== undefined && continuation !== undefined) {
|
|
251
|
+
try {
|
|
252
|
+
return await completeByContinuation(options, { ...continuation, prefix });
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (options.signal.aborted || (error instanceof AmaError && error.code === "aborted"))
|
|
256
|
+
throw error;
|
|
257
|
+
options.onFallback?.(error instanceof Error ? error.message : String(error));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
return completeText(options, independentPrompt());
|
|
261
|
+
}
|
|
262
|
+
function buildPrompt(conversation, previousSummary, instructions, tail) {
|
|
263
|
+
const parts = [];
|
|
264
|
+
if (previousSummary !== undefined) {
|
|
265
|
+
parts.push(`<previous-summary>\n${previousSummary}\n</previous-summary>`);
|
|
266
|
+
parts.push("The previous summary covers the session before the record below. Merge it with the new record into ONE updated summary.");
|
|
267
|
+
}
|
|
268
|
+
parts.push(`<conversation>\n${conversation}\n</conversation>`);
|
|
269
|
+
parts.push(tail);
|
|
270
|
+
if (instructions !== undefined && instructions.trim() !== "") {
|
|
271
|
+
parts.push(`Additional instructions:\n${instructions.trim()}`);
|
|
272
|
+
}
|
|
273
|
+
return parts.join("\n\n");
|
|
274
|
+
}
|
|
275
|
+
export async function runCompaction(plan, options) {
|
|
276
|
+
let usage;
|
|
277
|
+
let summary;
|
|
278
|
+
const { anchors } = plan;
|
|
279
|
+
if (plan.toSummarize.length > 0 || plan.previousSummary !== undefined) {
|
|
280
|
+
const history = await summarizeWithFallback(options, {
|
|
281
|
+
keepFrom: { index: anchors.historyEnd, excerpt: anchors.historyExcerpt },
|
|
282
|
+
instruction: SUMMARY_TEMPLATE,
|
|
283
|
+
}, () => buildPrompt(serializeConversation(plan.toSummarize), plan.previousSummary, options.customInstructions, SUMMARY_TEMPLATE));
|
|
284
|
+
usage = addUsage(usage, history.usage);
|
|
285
|
+
summary = history.text;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
summary = "## Goal\n(no earlier history)";
|
|
289
|
+
}
|
|
290
|
+
if (plan.turnPrefix.length > 0) {
|
|
291
|
+
const prefix = await summarizeWithFallback({ ...options, customInstructions: undefined }, {
|
|
292
|
+
from: anchors.historyEnd,
|
|
293
|
+
keepFrom: { index: anchors.keptStart, excerpt: anchors.keptExcerpt },
|
|
294
|
+
instruction: TURN_PREFIX_CONTINUATION_PROMPT,
|
|
295
|
+
}, () => buildPrompt(serializeConversation(plan.turnPrefix), undefined, undefined, TURN_PREFIX_PROMPT));
|
|
296
|
+
usage = addUsage(usage, prefix.usage);
|
|
297
|
+
summary = `${summary}\n\n---\n\n${prefix.text}`;
|
|
298
|
+
}
|
|
299
|
+
const ops = collectFileOps([...plan.toSummarize, ...plan.turnPrefix], createFileOps(plan.previousDetails));
|
|
300
|
+
const details = fileOpsDetails(ops);
|
|
301
|
+
const files = formatFileOps(details);
|
|
302
|
+
if (files !== "")
|
|
303
|
+
summary = `${summary}\n\n${files}`;
|
|
304
|
+
return { summary, firstKeptEntryId: plan.firstKeptEntryId, usage, details };
|
|
305
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.json 读写(设计 §3.5 ②③、§10.1)与 `ama auth` 后端。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 文件权限 0600;读到非 0600(POSIX)→ warning 并照用,doctor 提示。
|
|
5
|
+
* - 写入:同目录临时文件(0600)→ rename,再 chmod 一次兜底。
|
|
6
|
+
* - 本模块只搬运字符串,不解析 `!command` / `$ENV`(那是 ai/providers/auth.ts 的事),
|
|
7
|
+
* 也不把 key 写进任何日志或返回给展示层(`describeAuthFile` 只给来源与形态)。
|
|
8
|
+
*/
|
|
9
|
+
import type { AuthFile } from "./types.js";
|
|
10
|
+
export declare function defaultAuthFilePath(configDir: string): string;
|
|
11
|
+
export declare const PROVIDER_ID_PATTERN: RegExp;
|
|
12
|
+
export interface ReadAuthResult {
|
|
13
|
+
path: string;
|
|
14
|
+
exists: boolean;
|
|
15
|
+
file: AuthFile;
|
|
16
|
+
warnings: string[];
|
|
17
|
+
/** POSIX 下的权限位(八进制数),Windows 为 undefined。 */
|
|
18
|
+
mode?: number;
|
|
19
|
+
}
|
|
20
|
+
/** 当前权限位;文件不存在为 undefined。 */
|
|
21
|
+
export declare function fileMode(path: string): number | undefined;
|
|
22
|
+
export declare function isModeTooOpen(mode: number | undefined, platform?: NodeJS.Platform): boolean;
|
|
23
|
+
/** 读 auth.json;不存在 → 空文件。语法 / 字段错误抛 StartupError(3)。 */
|
|
24
|
+
export declare function readAuthFile(path: string): ReadAuthResult;
|
|
25
|
+
export declare function writeAuthFile(path: string, file: AuthFile): void;
|
|
26
|
+
/** `ama auth set`:设置 / 替换一家的 key(保留该条目的 env / baseUrl)。 */
|
|
27
|
+
export declare function setAuthKey(path: string, provider: string, apiKey: string): void;
|
|
28
|
+
/** `ama auth remove`:返回是否删除了条目。 */
|
|
29
|
+
export declare function removeAuthKey(path: string, provider: string): boolean;
|
|
30
|
+
export type AuthValueKind = "literal" | "command" | "env-ref";
|
|
31
|
+
/** 只描述 key 的形态,不暴露值。 */
|
|
32
|
+
export declare function classifyKeyValue(value: string): AuthValueKind;
|
|
33
|
+
export interface AuthEntrySummary {
|
|
34
|
+
provider: string;
|
|
35
|
+
kind: AuthValueKind;
|
|
36
|
+
hasBaseUrl: boolean;
|
|
37
|
+
envNames: string[];
|
|
38
|
+
}
|
|
39
|
+
export declare function describeAuthFile(file: AuthFile): AuthEntrySummary[];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auth.json 读写(设计 §3.5 ②③、§10.1)与 `ama auth` 后端。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 文件权限 0600;读到非 0600(POSIX)→ warning 并照用,doctor 提示。
|
|
5
|
+
* - 写入:同目录临时文件(0600)→ rename,再 chmod 一次兜底。
|
|
6
|
+
* - 本模块只搬运字符串,不解析 `!command` / `$ENV`(那是 ai/providers/auth.ts 的事),
|
|
7
|
+
* 也不把 key 写进任何日志或返回给展示层(`describeAuthFile` 只给来源与形态)。
|
|
8
|
+
*/
|
|
9
|
+
import { chmodSync, mkdirSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { loadConfigFile } from "./load.js";
|
|
12
|
+
import { AUTH_FILE } from "./paths.js";
|
|
13
|
+
import { CONFIG_FILE_VERSION } from "./types.js";
|
|
14
|
+
export function defaultAuthFilePath(configDir) {
|
|
15
|
+
return join(configDir, AUTH_FILE);
|
|
16
|
+
}
|
|
17
|
+
export const PROVIDER_ID_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
|
|
18
|
+
/** 当前权限位;文件不存在为 undefined。 */
|
|
19
|
+
export function fileMode(path) {
|
|
20
|
+
try {
|
|
21
|
+
return statSync(path).mode & 0o777;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function isModeTooOpen(mode, platform = process.platform) {
|
|
28
|
+
return platform !== "win32" && mode !== undefined && (mode & 0o077) !== 0;
|
|
29
|
+
}
|
|
30
|
+
/** 读 auth.json;不存在 → 空文件。语法 / 字段错误抛 StartupError(3)。 */
|
|
31
|
+
export function readAuthFile(path) {
|
|
32
|
+
const loaded = loadConfigFile("auth", path);
|
|
33
|
+
if (loaded === undefined) {
|
|
34
|
+
return {
|
|
35
|
+
path,
|
|
36
|
+
exists: false,
|
|
37
|
+
file: { version: CONFIG_FILE_VERSION, providers: {} },
|
|
38
|
+
warnings: [],
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const warnings = [...loaded.warnings];
|
|
42
|
+
const mode = fileMode(path);
|
|
43
|
+
if (isModeTooOpen(mode)) {
|
|
44
|
+
warnings.push(`${path}: 权限为 ${mode?.toString(8).padStart(4, "0")},应为 0600(chmod 600 ${path})`);
|
|
45
|
+
}
|
|
46
|
+
const result = { path, exists: true, file: loaded.value, warnings };
|
|
47
|
+
if (mode !== undefined && process.platform !== "win32")
|
|
48
|
+
result.mode = mode;
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
export function writeAuthFile(path, file) {
|
|
52
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
53
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
54
|
+
writeFileSync(tmp, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
|
|
55
|
+
renameSync(tmp, path);
|
|
56
|
+
if (process.platform !== "win32")
|
|
57
|
+
chmodSync(path, 0o600);
|
|
58
|
+
}
|
|
59
|
+
/** `ama auth set`:设置 / 替换一家的 key(保留该条目的 env / baseUrl)。 */
|
|
60
|
+
export function setAuthKey(path, provider, apiKey) {
|
|
61
|
+
const { file } = readAuthFile(path);
|
|
62
|
+
const previous = file.providers[provider];
|
|
63
|
+
file.providers[provider] = { ...previous, apiKey };
|
|
64
|
+
writeAuthFile(path, { version: CONFIG_FILE_VERSION, providers: file.providers });
|
|
65
|
+
}
|
|
66
|
+
/** `ama auth remove`:返回是否删除了条目。 */
|
|
67
|
+
export function removeAuthKey(path, provider) {
|
|
68
|
+
const { file, exists } = readAuthFile(path);
|
|
69
|
+
if (!exists || file.providers[provider] === undefined)
|
|
70
|
+
return false;
|
|
71
|
+
delete file.providers[provider];
|
|
72
|
+
writeAuthFile(path, { version: CONFIG_FILE_VERSION, providers: file.providers });
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
/** 只描述 key 的形态,不暴露值。 */
|
|
76
|
+
export function classifyKeyValue(value) {
|
|
77
|
+
if (value.startsWith("!"))
|
|
78
|
+
return "command";
|
|
79
|
+
if (/^\$(\{[A-Za-z_][A-Za-z0-9_]*\}|[A-Za-z_][A-Za-z0-9_]*)$/.test(value))
|
|
80
|
+
return "env-ref";
|
|
81
|
+
return "literal";
|
|
82
|
+
}
|
|
83
|
+
export function describeAuthFile(file) {
|
|
84
|
+
return Object.entries(file.providers)
|
|
85
|
+
.map(([provider, entry]) => ({
|
|
86
|
+
provider,
|
|
87
|
+
kind: classifyKeyValue(entry.apiKey),
|
|
88
|
+
hasBaseUrl: entry.baseUrl !== undefined,
|
|
89
|
+
envNames: Object.keys(entry.env ?? {}).sort(),
|
|
90
|
+
}))
|
|
91
|
+
.sort((a, b) => a.provider.localeCompare(b.provider));
|
|
92
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目上下文文件(AGENTS.md)向上查找(设计 §1.2、§10.1、§11.1 第 10 步)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 每个目录取第一个存在的:`AGENTS.override.md` > `AGENTS.md` > `AGENTS.MD`。
|
|
5
|
+
* - 顺序:用户级 `<configDir>/AGENTS.md`(全局约定)在最前,其后祖先由外向内,cwd 最后。
|
|
6
|
+
* - 去重:同一真实路径只取一次(符号链接);内容与已收集文件完全相同的跳过——
|
|
7
|
+
* git worktree 放在主仓库目录内(如 `.worktrees/x`)时,向上会再碰到主仓库的同一份
|
|
8
|
+
* AGENTS.md,它不带来新信息。
|
|
9
|
+
* - 不需要信任(§7.3)。读错记 warning 继续。
|
|
10
|
+
*/
|
|
11
|
+
export declare const CONTEXT_FILE_NAMES: readonly ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD"];
|
|
12
|
+
export interface ContextFile {
|
|
13
|
+
path: string;
|
|
14
|
+
content: string;
|
|
15
|
+
scope: "user" | "project";
|
|
16
|
+
}
|
|
17
|
+
export interface FindContextFilesInput {
|
|
18
|
+
cwd: string;
|
|
19
|
+
/** 用户级配置目录;undefined 则不读全局 AGENTS.md。 */
|
|
20
|
+
configDir?: string | undefined;
|
|
21
|
+
/** 单文件上限(字节),超出截断并 warning;缺省 256 KiB。 */
|
|
22
|
+
maxBytes?: number;
|
|
23
|
+
}
|
|
24
|
+
export interface FindContextFilesResult {
|
|
25
|
+
files: ContextFile[];
|
|
26
|
+
warnings: string[];
|
|
27
|
+
}
|
|
28
|
+
/** 目录里按优先级取第一个存在的上下文文件。 */
|
|
29
|
+
export declare function pickContextFile(dir: string): string | undefined;
|
|
30
|
+
export declare function findContextFiles(input: FindContextFilesInput): FindContextFilesResult;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 项目上下文文件(AGENTS.md)向上查找(设计 §1.2、§10.1、§11.1 第 10 步)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 每个目录取第一个存在的:`AGENTS.override.md` > `AGENTS.md` > `AGENTS.MD`。
|
|
5
|
+
* - 顺序:用户级 `<configDir>/AGENTS.md`(全局约定)在最前,其后祖先由外向内,cwd 最后。
|
|
6
|
+
* - 去重:同一真实路径只取一次(符号链接);内容与已收集文件完全相同的跳过——
|
|
7
|
+
* git worktree 放在主仓库目录内(如 `.worktrees/x`)时,向上会再碰到主仓库的同一份
|
|
8
|
+
* AGENTS.md,它不带来新信息。
|
|
9
|
+
* - 不需要信任(§7.3)。读错记 warning 继续。
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync, realpathSync, statSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { ancestorsOf } from "./trust.js";
|
|
14
|
+
export const CONTEXT_FILE_NAMES = ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD"];
|
|
15
|
+
function isFile(path) {
|
|
16
|
+
try {
|
|
17
|
+
return statSync(path).isFile();
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/** 目录里按优先级取第一个存在的上下文文件。 */
|
|
24
|
+
export function pickContextFile(dir) {
|
|
25
|
+
for (const name of CONTEXT_FILE_NAMES) {
|
|
26
|
+
const candidate = join(dir, name);
|
|
27
|
+
if (isFile(candidate))
|
|
28
|
+
return candidate;
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
export function findContextFiles(input) {
|
|
33
|
+
const maxBytes = input.maxBytes ?? 256 * 1024;
|
|
34
|
+
const files = [];
|
|
35
|
+
const warnings = [];
|
|
36
|
+
const seenPaths = new Set();
|
|
37
|
+
const seenContent = new Set();
|
|
38
|
+
const add = (path, scope) => {
|
|
39
|
+
let real;
|
|
40
|
+
try {
|
|
41
|
+
real = realpathSync(path);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
real = path;
|
|
45
|
+
}
|
|
46
|
+
if (seenPaths.has(real))
|
|
47
|
+
return;
|
|
48
|
+
seenPaths.add(real);
|
|
49
|
+
let content;
|
|
50
|
+
try {
|
|
51
|
+
content = readFileSync(path, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
warnings.push(`${path}: 读取失败(${error.message})`);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (Buffer.byteLength(content) > maxBytes) {
|
|
58
|
+
content = Buffer.from(content).subarray(0, maxBytes).toString("utf8");
|
|
59
|
+
warnings.push(`${path}: 超过 ${maxBytes} 字节,已截断`);
|
|
60
|
+
}
|
|
61
|
+
if (seenContent.has(content))
|
|
62
|
+
return;
|
|
63
|
+
seenContent.add(content);
|
|
64
|
+
files.push({ path, content, scope });
|
|
65
|
+
};
|
|
66
|
+
if (input.configDir !== undefined) {
|
|
67
|
+
const global = pickContextFile(input.configDir);
|
|
68
|
+
if (global !== undefined)
|
|
69
|
+
add(global, "user");
|
|
70
|
+
}
|
|
71
|
+
for (const dir of ancestorsOf(input.cwd).reverse()) {
|
|
72
|
+
const found = pickContextFile(dir);
|
|
73
|
+
if (found !== undefined)
|
|
74
|
+
add(found, "project");
|
|
75
|
+
}
|
|
76
|
+
return { files, warnings };
|
|
77
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置目录初始化(docs/providers.md「配置目录」):`ama init` 与 CLI 首次运行的自动初始化。
|
|
3
|
+
*
|
|
4
|
+
* - 目录 0700;`config.json` 只在不存在时写最小内容(`--force` 时先备份再重写);
|
|
5
|
+
* - `config.schema.json` 不是用户文件,每次 init 都按当前版本重写(内容相同则不动);
|
|
6
|
+
* - 不创建 auth.json(只有 `ama auth set` / `ama providers add` 才写,0600)。
|
|
7
|
+
* - 自动初始化只在 CLI 里、配置目录不存在时触发,`AMA_NO_INIT=1` 关闭;SDK 与测试不走这里。
|
|
8
|
+
*/
|
|
9
|
+
import { type AmaConfig } from "./types.js";
|
|
10
|
+
export declare const NO_INIT_ENV = "AMA_NO_INIT";
|
|
11
|
+
/** 最小 config.json:常用键的缺省值(与 DEFAULT_CONFIG 相同,写出来方便直接改)。 */
|
|
12
|
+
export declare function minimalConfig(): AmaConfig & {
|
|
13
|
+
$schema: string;
|
|
14
|
+
};
|
|
15
|
+
export type InitStatus = "created" | "exists" | "updated" | "unchanged" | "overwritten";
|
|
16
|
+
export interface InitResult {
|
|
17
|
+
dir: string;
|
|
18
|
+
dirCreated: boolean;
|
|
19
|
+
files: {
|
|
20
|
+
path: string;
|
|
21
|
+
status: InitStatus;
|
|
22
|
+
}[];
|
|
23
|
+
}
|
|
24
|
+
/** 建目录并补齐缺失文件;已有的 config.json 不覆盖(`force` 时备份为 .bak 再重写)。 */
|
|
25
|
+
export declare function initConfigDir(dir: string, options?: {
|
|
26
|
+
force?: boolean;
|
|
27
|
+
}): InitResult;
|
|
28
|
+
/** CLI 启动时:配置目录不存在就静默初始化;返回是否初始化了。失败不影响启动。 */
|
|
29
|
+
export declare function autoInitConfigDir(dir: string, env: Readonly<Record<string, string | undefined>>): boolean;
|
|
30
|
+
export declare function describeInit(result: InitResult): string;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置目录初始化(docs/providers.md「配置目录」):`ama init` 与 CLI 首次运行的自动初始化。
|
|
3
|
+
*
|
|
4
|
+
* - 目录 0700;`config.json` 只在不存在时写最小内容(`--force` 时先备份再重写);
|
|
5
|
+
* - `config.schema.json` 不是用户文件,每次 init 都按当前版本重写(内容相同则不动);
|
|
6
|
+
* - 不创建 auth.json(只有 `ama auth set` / `ama providers add` 才写,0600)。
|
|
7
|
+
* - 自动初始化只在 CLI 里、配置目录不存在时触发,`AMA_NO_INIT=1` 关闭;SDK 与测试不走这里。
|
|
8
|
+
*/
|
|
9
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { CONFIG_SCHEMA_FILE, configSchemaText } from "./json-schema.js";
|
|
12
|
+
import { AUTH_FILE, CONFIG_FILE } from "./paths.js";
|
|
13
|
+
import { CONFIG_FILE_VERSION } from "./types.js";
|
|
14
|
+
export const NO_INIT_ENV = "AMA_NO_INIT";
|
|
15
|
+
/** 最小 config.json:常用键的缺省值(与 DEFAULT_CONFIG 相同,写出来方便直接改)。 */
|
|
16
|
+
export function minimalConfig() {
|
|
17
|
+
return {
|
|
18
|
+
$schema: `./${CONFIG_SCHEMA_FILE}`,
|
|
19
|
+
version: CONFIG_FILE_VERSION,
|
|
20
|
+
thinkingLevel: "medium",
|
|
21
|
+
permission: { mode: "default" },
|
|
22
|
+
tools: { preset: "default" },
|
|
23
|
+
providers: {},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function writeAtomic(path, text, mode) {
|
|
27
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
28
|
+
writeFileSync(tmp, text, { mode });
|
|
29
|
+
renameSync(tmp, path);
|
|
30
|
+
}
|
|
31
|
+
/** 建目录并补齐缺失文件;已有的 config.json 不覆盖(`force` 时备份为 .bak 再重写)。 */
|
|
32
|
+
export function initConfigDir(dir, options = {}) {
|
|
33
|
+
const dirCreated = !existsSync(dir);
|
|
34
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
35
|
+
if (dirCreated && process.platform !== "win32")
|
|
36
|
+
chmodSync(dir, 0o700);
|
|
37
|
+
const files = [];
|
|
38
|
+
const config = join(dir, CONFIG_FILE);
|
|
39
|
+
const text = `${JSON.stringify(minimalConfig(), null, 2)}\n`;
|
|
40
|
+
if (!existsSync(config)) {
|
|
41
|
+
writeAtomic(config, text, 0o644);
|
|
42
|
+
files.push({ path: config, status: "created" });
|
|
43
|
+
}
|
|
44
|
+
else if (options.force) {
|
|
45
|
+
copyFileSync(config, `${config}.bak`);
|
|
46
|
+
writeAtomic(config, text, 0o644);
|
|
47
|
+
files.push({ path: config, status: "overwritten" });
|
|
48
|
+
}
|
|
49
|
+
else
|
|
50
|
+
files.push({ path: config, status: "exists" });
|
|
51
|
+
const schema = join(dir, CONFIG_SCHEMA_FILE);
|
|
52
|
+
const schemaText = configSchemaText();
|
|
53
|
+
if (!existsSync(schema)) {
|
|
54
|
+
writeAtomic(schema, schemaText, 0o644);
|
|
55
|
+
files.push({ path: schema, status: "created" });
|
|
56
|
+
}
|
|
57
|
+
else if (readFileSync(schema, "utf8") !== schemaText) {
|
|
58
|
+
writeAtomic(schema, schemaText, 0o644);
|
|
59
|
+
files.push({ path: schema, status: "updated" });
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
files.push({ path: schema, status: "unchanged" });
|
|
63
|
+
const auth = join(dir, AUTH_FILE);
|
|
64
|
+
if (existsSync(auth))
|
|
65
|
+
files.push({ path: auth, status: "exists" });
|
|
66
|
+
return { dir, dirCreated, files };
|
|
67
|
+
}
|
|
68
|
+
/** CLI 启动时:配置目录不存在就静默初始化;返回是否初始化了。失败不影响启动。 */
|
|
69
|
+
export function autoInitConfigDir(dir, env) {
|
|
70
|
+
if (env[NO_INIT_ENV] === "1" || existsSync(dir))
|
|
71
|
+
return false;
|
|
72
|
+
try {
|
|
73
|
+
initConfigDir(dir);
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
const STATUS_TEXT = {
|
|
81
|
+
created: "已创建",
|
|
82
|
+
exists: "已存在,未改动",
|
|
83
|
+
updated: "已更新为当前版本",
|
|
84
|
+
unchanged: "已是当前版本",
|
|
85
|
+
overwritten: "已重写(原文件备份为 .bak)",
|
|
86
|
+
};
|
|
87
|
+
export function describeInit(result) {
|
|
88
|
+
const lines = [`${result.dir}${result.dirCreated ? " 已创建(0700)" : " 已存在"}`];
|
|
89
|
+
for (const file of result.files)
|
|
90
|
+
lines.push(` ${file.path} ${STATUS_TEXT[file.status]}`);
|
|
91
|
+
return `${lines.join("\n")}\n`;
|
|
92
|
+
}
|