@armadra/agent 0.0.0-stage → 0.2.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 +22 -0
- package/LICENSE +21 -0
- package/README.md +426 -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 +511 -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 +118 -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 +104 -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 +484 -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 +303 -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 +102 -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/registry.d.ts +83 -0
- package/dist/ai/providers/registry.js +305 -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 +428 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +25770 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +82 -0
- package/dist/cli/args.js +381 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +398 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +104 -0
- package/dist/cli/compose-session.d.ts +69 -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 +106 -0
- package/dist/cli/compose.js +206 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +205 -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 +26 -0
- package/dist/cli/main.js +165 -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 +197 -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 +44 -0
- package/dist/cli/subcommands/config.js +201 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +41 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -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 +40 -0
- package/dist/cli/subcommands/models-discover.js +204 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +144 -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/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 +413 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +172 -0
- package/dist/config/types.js +27 -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/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 +202 -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 +487 -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 +216 -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 +28 -0
- package/dist/modes/interactive/startup-ui.js +238 -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 +15 -0
- package/dist/modes/print/print-mode.js +96 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +258 -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 +171 -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 +180 -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 +199 -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/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 +28 -0
- package/dist/tools/read.js +181 -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 +316 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +168 -0
- package/package.json +101 -4
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 读配置文件 + 校验 + 诊断(设计 §11.1 第 6 / 9 / 10 步)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* JSON 语法错误给出行列号;字段错误给出字段路径。任何 error 级诊断都抛
|
|
5
|
+
* `StartupError{exitCode: 3}`(code 依文件种类为 config_invalid / profile_invalid),
|
|
6
|
+
* warning 级诊断(未知字段)原样返回给调用方汇入 Runtime.warnings。
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { StartupError } from "../errors.js";
|
|
10
|
+
import { hasErrors, validateAuthFile, validateConfig, validateHookConfig, validateProfile, validateTrustFile, } from "./schema.js";
|
|
11
|
+
const EXIT_CONFIG = 3;
|
|
12
|
+
const VALIDATORS = {
|
|
13
|
+
config: validateConfig,
|
|
14
|
+
auth: validateAuthFile,
|
|
15
|
+
profile: validateProfile,
|
|
16
|
+
hooks: validateHookConfig,
|
|
17
|
+
trust: validateTrustFile,
|
|
18
|
+
};
|
|
19
|
+
/** 字符偏移 → 1 起的行列。 */
|
|
20
|
+
export function lineColumnAt(text, offset) {
|
|
21
|
+
const before = text.slice(0, Math.max(0, Math.min(offset, text.length)));
|
|
22
|
+
const lines = before.split("\n");
|
|
23
|
+
return { line: lines.length, column: (lines[lines.length - 1]?.length ?? 0) + 1 };
|
|
24
|
+
}
|
|
25
|
+
/** 解析 JSON;语法错误抛带行列号的 Error。容忍 UTF-8 BOM。 */
|
|
26
|
+
export function parseJsonText(text) {
|
|
27
|
+
const source = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
28
|
+
try {
|
|
29
|
+
return JSON.parse(source);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
33
|
+
const lineCol = /line (\d+) column (\d+)/.exec(message);
|
|
34
|
+
if (lineCol !== null)
|
|
35
|
+
throw new Error(`JSON 语法错误(第 ${lineCol[1]} 行第 ${lineCol[2]} 列):${message}`);
|
|
36
|
+
const position = /position (\d+)/.exec(message);
|
|
37
|
+
if (position?.[1] !== undefined) {
|
|
38
|
+
const { line, column } = lineColumnAt(source, Number(position[1]));
|
|
39
|
+
throw new Error(`JSON 语法错误(第 ${line} 行第 ${column} 列):${message}`);
|
|
40
|
+
}
|
|
41
|
+
if (source.trim() === "")
|
|
42
|
+
throw new Error("JSON 语法错误:文件为空");
|
|
43
|
+
const end = lineColumnAt(source, source.length);
|
|
44
|
+
throw new Error(`JSON 语法错误(第 ${end.line} 行第 ${end.column} 列附近):${message}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
export function formatDiagnostic(path, d) {
|
|
48
|
+
return `${path}: ${d.path === "" ? "" : `${d.path}: `}${d.message}`;
|
|
49
|
+
}
|
|
50
|
+
function failure(kind, message, detail) {
|
|
51
|
+
return new StartupError(kind === "profile" ? "profile_invalid" : "config_invalid", message, EXIT_CONFIG, detail === undefined ? {} : { detail });
|
|
52
|
+
}
|
|
53
|
+
/** 读并校验一个配置文件;不存在且非必需时返回 undefined。 */
|
|
54
|
+
export function loadConfigFile(kind, path, options = {}) {
|
|
55
|
+
let text;
|
|
56
|
+
try {
|
|
57
|
+
text = readFileSync(path, "utf8");
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
const code = error.code;
|
|
61
|
+
if (code === "ENOENT" && options.required !== true)
|
|
62
|
+
return undefined;
|
|
63
|
+
const reason = code === "ENOENT" ? "文件不存在" : error.message;
|
|
64
|
+
throw failure(kind, `${path}: ${reason}`, { path });
|
|
65
|
+
}
|
|
66
|
+
let value;
|
|
67
|
+
try {
|
|
68
|
+
value = parseJsonText(text);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
throw failure(kind, `${path}: ${error.message}`, { path });
|
|
72
|
+
}
|
|
73
|
+
const diagnostics = VALIDATORS[kind](value);
|
|
74
|
+
if (hasErrors(diagnostics)) {
|
|
75
|
+
const lines = diagnostics
|
|
76
|
+
.filter((d) => d.severity === "error")
|
|
77
|
+
.map((d) => formatDiagnostic(path, d));
|
|
78
|
+
throw failure(kind, `配置文件无效:\n ${lines.join("\n ")}`, { path, diagnostics });
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
path,
|
|
82
|
+
value: value,
|
|
83
|
+
warnings: diagnostics.map((d) => formatDiagnostic(path, d)),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
/** 读失败不抛:返回诊断(doctor 用)。 */
|
|
87
|
+
export function probeConfigFile(kind, path) {
|
|
88
|
+
try {
|
|
89
|
+
const loaded = loadConfigFile(kind, path);
|
|
90
|
+
if (loaded === undefined)
|
|
91
|
+
return { exists: false, ok: true, messages: [] };
|
|
92
|
+
return { exists: true, ok: true, messages: loaded.warnings };
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return { exists: true, ok: false, messages: [error.message] };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置层级合并(设计 §7.2、§10.2)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 顺序:内置缺省 ← 用户级 ← profile.config ← 项目级(受限字段,只能收紧)← 命令行。
|
|
5
|
+
* - 对象深合并;数组整体替换,例外是「累加型」列表:`permission.allow / deny`、
|
|
6
|
+
* `tools.disabled`、`skills.dirs` 跨层拼接去重。
|
|
7
|
+
* - 项目级 `.ama/config.json` 只接受:`permission.deny`(追加)、`permission.mode`(只能更严)、
|
|
8
|
+
* `compaction`、`tools.disabled`、`tools.preset`(只能更严)、`codemode.mode: "off"`、`ui`;
|
|
9
|
+
* 其它字段与放宽项(含 `permission.builtinDeny`)被忽略并记 warning。
|
|
10
|
+
* - 同时产出带来源的权限规则清单(`ruleSpecs`),交给权限管线(B3 的 rules.ts 解析)。
|
|
11
|
+
*/
|
|
12
|
+
import type { AmaConfig, CodemodeMode, ToolsPreset } from "./types.js";
|
|
13
|
+
import type { PermissionMode, RuleSource } from "../permissions/types.js";
|
|
14
|
+
import type { ModelThinkingLevel } from "../ai/types.js";
|
|
15
|
+
export declare const DEFAULT_CONFIG: Readonly<AmaConfig>;
|
|
16
|
+
/** 嵌入宿主(有 profile)时的缺省覆盖(§12.10)。 */
|
|
17
|
+
export declare const PROFILE_DEFAULTS: Readonly<Partial<AmaConfig>>;
|
|
18
|
+
export type ConfigLayerName = "default" | "user" | "profile" | "project" | "cli";
|
|
19
|
+
export interface PermissionRuleSpec {
|
|
20
|
+
effect: "allow" | "deny";
|
|
21
|
+
raw: string;
|
|
22
|
+
source: RuleSource;
|
|
23
|
+
}
|
|
24
|
+
/** 深合并两层配置(不改入参)。 */
|
|
25
|
+
export declare function mergeConfig(base: AmaConfig, over: Partial<AmaConfig> | undefined): AmaConfig;
|
|
26
|
+
export interface RestrictResult {
|
|
27
|
+
/** 只含被接受的字段。 */
|
|
28
|
+
accepted: Partial<AmaConfig>;
|
|
29
|
+
warnings: string[];
|
|
30
|
+
}
|
|
31
|
+
/** 预设 a 是否比 b 更严或相同(coordinator 最严,codemode 最宽)。 */
|
|
32
|
+
export declare function isPresetStricterOrEqual(a: ToolsPreset, b: ToolsPreset): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* 把项目级配置裁剪为受限字段(§7.2);`currentMode` / `currentPreset` 是合并到此为止的
|
|
35
|
+
* 权限模式与工具预设。
|
|
36
|
+
*/
|
|
37
|
+
export declare function restrictProjectConfig(project: AmaConfig, currentMode: PermissionMode, label?: string, currentPreset?: ToolsPreset): RestrictResult;
|
|
38
|
+
/** 命令行对配置的覆盖(只含与 config 同名的那部分参数)。 */
|
|
39
|
+
export interface CliConfigOverrides {
|
|
40
|
+
thinkingLevel?: ModelThinkingLevel | undefined;
|
|
41
|
+
permissionMode?: PermissionMode | undefined;
|
|
42
|
+
allow?: readonly string[] | undefined;
|
|
43
|
+
deny?: readonly string[] | undefined;
|
|
44
|
+
quietStartup?: "normal" | "header" | "silent" | undefined;
|
|
45
|
+
tuiMode?: "regular" | undefined;
|
|
46
|
+
skillDirs?: readonly string[] | undefined;
|
|
47
|
+
/** `--tools-preset`。 */
|
|
48
|
+
toolsPreset?: ToolsPreset | undefined;
|
|
49
|
+
/** `--codemode`。 */
|
|
50
|
+
codemode?: CodemodeMode | undefined;
|
|
51
|
+
}
|
|
52
|
+
export declare function cliOverridesToConfig(cli: CliConfigOverrides): Partial<AmaConfig>;
|
|
53
|
+
export interface ConfigLayers {
|
|
54
|
+
user?: AmaConfig | undefined;
|
|
55
|
+
/** profile.config 指向的文件内容;`hasProfile` 为 true 时还会叠加 PROFILE_DEFAULTS。 */
|
|
56
|
+
profile?: AmaConfig | undefined;
|
|
57
|
+
hasProfile?: boolean;
|
|
58
|
+
project?: AmaConfig | undefined;
|
|
59
|
+
cli?: CliConfigOverrides | undefined;
|
|
60
|
+
}
|
|
61
|
+
export interface MergeResult {
|
|
62
|
+
config: AmaConfig;
|
|
63
|
+
warnings: string[];
|
|
64
|
+
ruleSpecs: PermissionRuleSpec[];
|
|
65
|
+
/** 各层是否存在(doctor 显示)。 */
|
|
66
|
+
layers: ConfigLayerName[];
|
|
67
|
+
}
|
|
68
|
+
/** 第 6 步:缺省 ← 用户级 ← profile。 */
|
|
69
|
+
export declare function mergeBaseLayers(layers: Pick<ConfigLayers, "user" | "profile" | "hasProfile">): MergeResult;
|
|
70
|
+
/** 第 9 步:叠加项目级(受限)与命令行。 */
|
|
71
|
+
export declare function mergeProjectAndCli(base: MergeResult, project: AmaConfig | undefined, cli: CliConfigOverrides | undefined, projectLabel?: string): MergeResult;
|
|
72
|
+
/** 一次合并全部层(SDK / 测试用)。 */
|
|
73
|
+
export declare function mergeConfigLayers(layers: ConfigLayers): MergeResult;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 配置层级合并(设计 §7.2、§10.2)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* 顺序:内置缺省 ← 用户级 ← profile.config ← 项目级(受限字段,只能收紧)← 命令行。
|
|
5
|
+
* - 对象深合并;数组整体替换,例外是「累加型」列表:`permission.allow / deny`、
|
|
6
|
+
* `tools.disabled`、`skills.dirs` 跨层拼接去重。
|
|
7
|
+
* - 项目级 `.ama/config.json` 只接受:`permission.deny`(追加)、`permission.mode`(只能更严)、
|
|
8
|
+
* `compaction`、`tools.disabled`、`tools.preset`(只能更严)、`codemode.mode: "off"`、`ui`;
|
|
9
|
+
* 其它字段与放宽项(含 `permission.builtinDeny`)被忽略并记 warning。
|
|
10
|
+
* - 同时产出带来源的权限规则清单(`ruleSpecs`),交给权限管线(B3 的 rules.ts 解析)。
|
|
11
|
+
*/
|
|
12
|
+
import { CONFIG_FILE_VERSION, TOOLS_PRESETS_STRICT_FIRST } from "./types.js";
|
|
13
|
+
import { isAtLeastAsStrict } from "../permissions/rules.js";
|
|
14
|
+
export const DEFAULT_CONFIG = Object.freeze({
|
|
15
|
+
version: CONFIG_FILE_VERSION,
|
|
16
|
+
thinkingLevel: "medium",
|
|
17
|
+
permission: { mode: "default", allow: [], deny: [] },
|
|
18
|
+
compaction: { enabled: true, reserveTokens: 16_384, keepRecentTokens: 20_000 },
|
|
19
|
+
retry: { enabled: true, maxRetries: 3, baseDelayMs: 2_000, maxDelayMs: 60_000 },
|
|
20
|
+
tools: { preset: "default", maxToolResultChars: 30_000, bashTimeoutMs: 120_000, disabled: [] },
|
|
21
|
+
hooks: { timeoutMs: 60_000 },
|
|
22
|
+
ui: {
|
|
23
|
+
theme: "dark",
|
|
24
|
+
markdown: true,
|
|
25
|
+
showThinking: "collapsed",
|
|
26
|
+
tuiMode: "regular",
|
|
27
|
+
quietStartup: "normal",
|
|
28
|
+
},
|
|
29
|
+
skills: { dirs: [] },
|
|
30
|
+
});
|
|
31
|
+
/** 嵌入宿主(有 profile)时的缺省覆盖(§12.10)。 */
|
|
32
|
+
export const PROFILE_DEFAULTS = Object.freeze({
|
|
33
|
+
ui: { quietStartup: "header" },
|
|
34
|
+
});
|
|
35
|
+
/** 层名 → 规则来源(RuleSource)。 */
|
|
36
|
+
const RULE_SOURCE = {
|
|
37
|
+
default: "builtin",
|
|
38
|
+
user: "user",
|
|
39
|
+
profile: "profile",
|
|
40
|
+
project: "project",
|
|
41
|
+
cli: "cli",
|
|
42
|
+
};
|
|
43
|
+
const ACCUMULATING = new Set([
|
|
44
|
+
"permission.allow",
|
|
45
|
+
"permission.deny",
|
|
46
|
+
"tools.disabled",
|
|
47
|
+
"skills.dirs",
|
|
48
|
+
]);
|
|
49
|
+
function isPlainObject(value) {
|
|
50
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
51
|
+
}
|
|
52
|
+
function unique(values) {
|
|
53
|
+
return [...new Set(values)];
|
|
54
|
+
}
|
|
55
|
+
function mergeValue(base, over, path) {
|
|
56
|
+
if (over === undefined)
|
|
57
|
+
return structuredCloneSafe(base);
|
|
58
|
+
if (Array.isArray(over)) {
|
|
59
|
+
if (ACCUMULATING.has(path) && Array.isArray(base))
|
|
60
|
+
return unique([...base, ...over]);
|
|
61
|
+
return [...over];
|
|
62
|
+
}
|
|
63
|
+
if (isPlainObject(over)) {
|
|
64
|
+
const result = isPlainObject(base) ? { ...structuredCloneSafe(base) } : {};
|
|
65
|
+
for (const [key, value] of Object.entries(over)) {
|
|
66
|
+
result[key] = mergeValue(result[key], value, path === "" ? key : `${path}.${key}`);
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
return over;
|
|
71
|
+
}
|
|
72
|
+
function structuredCloneSafe(value) {
|
|
73
|
+
return value === undefined ? value : structuredClone(value);
|
|
74
|
+
}
|
|
75
|
+
/** 深合并两层配置(不改入参)。 */
|
|
76
|
+
export function mergeConfig(base, over) {
|
|
77
|
+
if (over === undefined)
|
|
78
|
+
return structuredCloneSafe(base);
|
|
79
|
+
const merged = mergeValue(base, over, "");
|
|
80
|
+
merged.version = CONFIG_FILE_VERSION;
|
|
81
|
+
return merged;
|
|
82
|
+
}
|
|
83
|
+
/** 预设 a 是否比 b 更严或相同(coordinator 最严,codemode 最宽)。 */
|
|
84
|
+
export function isPresetStricterOrEqual(a, b) {
|
|
85
|
+
return TOOLS_PRESETS_STRICT_FIRST.indexOf(a) <= TOOLS_PRESETS_STRICT_FIRST.indexOf(b);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 把项目级配置裁剪为受限字段(§7.2);`currentMode` / `currentPreset` 是合并到此为止的
|
|
89
|
+
* 权限模式与工具预设。
|
|
90
|
+
*/
|
|
91
|
+
export function restrictProjectConfig(project, currentMode, label = ".ama/config.json", currentPreset = "default") {
|
|
92
|
+
const warnings = [];
|
|
93
|
+
const accepted = {};
|
|
94
|
+
for (const [key, value] of Object.entries(project)) {
|
|
95
|
+
if (key === "version" || key === "$schema" || value === undefined)
|
|
96
|
+
continue;
|
|
97
|
+
switch (key) {
|
|
98
|
+
case "compaction":
|
|
99
|
+
if (project.compaction !== undefined)
|
|
100
|
+
accepted.compaction = structuredClone(project.compaction);
|
|
101
|
+
break;
|
|
102
|
+
case "ui":
|
|
103
|
+
if (project.ui !== undefined)
|
|
104
|
+
accepted.ui = structuredClone(project.ui);
|
|
105
|
+
break;
|
|
106
|
+
case "tools": {
|
|
107
|
+
const tools = project.tools ?? {};
|
|
108
|
+
const result = {};
|
|
109
|
+
for (const sub of Object.keys(tools)) {
|
|
110
|
+
if (sub !== "disabled" && sub !== "preset")
|
|
111
|
+
warnings.push(`${label}: 项目级只能设 tools.disabled / tools.preset,忽略 tools.${sub}`);
|
|
112
|
+
}
|
|
113
|
+
if (tools.disabled !== undefined)
|
|
114
|
+
result.disabled = [...tools.disabled];
|
|
115
|
+
if (tools.preset !== undefined) {
|
|
116
|
+
if (isPresetStricterOrEqual(tools.preset, currentPreset))
|
|
117
|
+
result.preset = tools.preset;
|
|
118
|
+
else
|
|
119
|
+
warnings.push(`${label}: 项目级不能放宽工具预设,忽略 tools.preset ${tools.preset}(当前 ${currentPreset})`);
|
|
120
|
+
}
|
|
121
|
+
if (Object.keys(result).length > 0)
|
|
122
|
+
accepted.tools = result;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
case "codemode": {
|
|
126
|
+
const codemode = project.codemode ?? {};
|
|
127
|
+
for (const [sub, v] of Object.entries(codemode)) {
|
|
128
|
+
if (sub === "mode" && v === "off")
|
|
129
|
+
continue;
|
|
130
|
+
warnings.push(`${label}: 项目级只接受 codemode.mode "off",忽略 codemode.${sub}${sub === "mode" ? ` ${String(v)}` : ""}`);
|
|
131
|
+
}
|
|
132
|
+
if (codemode.mode === "off")
|
|
133
|
+
accepted.codemode = { mode: "off" };
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "permission": {
|
|
137
|
+
const permission = restrictPermission(project.permission ?? {}, currentMode, label, warnings);
|
|
138
|
+
if (permission !== undefined)
|
|
139
|
+
accepted.permission = permission;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
warnings.push(`${label}: 项目级不能设 ${key},已忽略`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { accepted, warnings };
|
|
147
|
+
}
|
|
148
|
+
function restrictPermission(permission, currentMode, label, warnings) {
|
|
149
|
+
const result = {};
|
|
150
|
+
if (permission.allow !== undefined && permission.allow.length > 0) {
|
|
151
|
+
warnings.push(`${label}: 项目级不能加 allow 规则,忽略 ${permission.allow.join(", ")}`);
|
|
152
|
+
}
|
|
153
|
+
if (permission.deny !== undefined && permission.deny.length > 0)
|
|
154
|
+
result.deny = [...permission.deny];
|
|
155
|
+
if (permission.builtinDeny !== undefined) {
|
|
156
|
+
warnings.push(`${label}: 项目级不能改内置 deny 表,忽略 permission.builtinDeny`);
|
|
157
|
+
}
|
|
158
|
+
if (permission.mode !== undefined) {
|
|
159
|
+
if (isAtLeastAsStrict(permission.mode, currentMode)) {
|
|
160
|
+
result.mode = permission.mode;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
warnings.push(`${label}: 项目级只能收紧权限模式,忽略 ${permission.mode}(当前 ${currentMode})`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
167
|
+
}
|
|
168
|
+
export function cliOverridesToConfig(cli) {
|
|
169
|
+
const out = {};
|
|
170
|
+
if (cli.thinkingLevel !== undefined)
|
|
171
|
+
out.thinkingLevel = cli.thinkingLevel;
|
|
172
|
+
const permission = {};
|
|
173
|
+
if (cli.permissionMode !== undefined)
|
|
174
|
+
permission.mode = cli.permissionMode;
|
|
175
|
+
if (cli.allow !== undefined && cli.allow.length > 0)
|
|
176
|
+
permission.allow = [...cli.allow];
|
|
177
|
+
if (cli.deny !== undefined && cli.deny.length > 0)
|
|
178
|
+
permission.deny = [...cli.deny];
|
|
179
|
+
if (Object.keys(permission).length > 0)
|
|
180
|
+
out.permission = permission;
|
|
181
|
+
const ui = {};
|
|
182
|
+
if (cli.quietStartup !== undefined)
|
|
183
|
+
ui.quietStartup = cli.quietStartup;
|
|
184
|
+
if (cli.tuiMode !== undefined)
|
|
185
|
+
ui.tuiMode = cli.tuiMode;
|
|
186
|
+
if (Object.keys(ui).length > 0)
|
|
187
|
+
out.ui = ui;
|
|
188
|
+
if (cli.toolsPreset !== undefined)
|
|
189
|
+
out.tools = { preset: cli.toolsPreset };
|
|
190
|
+
if (cli.codemode !== undefined)
|
|
191
|
+
out.codemode = { mode: cli.codemode };
|
|
192
|
+
return out;
|
|
193
|
+
}
|
|
194
|
+
function collectRules(layer, permission) {
|
|
195
|
+
const source = RULE_SOURCE[layer];
|
|
196
|
+
const specs = [];
|
|
197
|
+
for (const raw of permission?.allow ?? [])
|
|
198
|
+
specs.push({ effect: "allow", raw, source });
|
|
199
|
+
for (const raw of permission?.deny ?? [])
|
|
200
|
+
specs.push({ effect: "deny", raw, source });
|
|
201
|
+
return specs;
|
|
202
|
+
}
|
|
203
|
+
/** 第 6 步:缺省 ← 用户级 ← profile。 */
|
|
204
|
+
export function mergeBaseLayers(layers) {
|
|
205
|
+
let config = mergeConfig(DEFAULT_CONFIG, undefined);
|
|
206
|
+
const present = ["default"];
|
|
207
|
+
const ruleSpecs = [];
|
|
208
|
+
if (layers.user !== undefined) {
|
|
209
|
+
config = mergeConfig(config, layers.user);
|
|
210
|
+
present.push("user");
|
|
211
|
+
ruleSpecs.push(...collectRules("user", layers.user.permission));
|
|
212
|
+
}
|
|
213
|
+
if (layers.hasProfile === true)
|
|
214
|
+
config = mergeConfig(config, PROFILE_DEFAULTS);
|
|
215
|
+
if (layers.profile !== undefined) {
|
|
216
|
+
config = mergeConfig(config, layers.profile);
|
|
217
|
+
present.push("profile");
|
|
218
|
+
ruleSpecs.push(...collectRules("profile", layers.profile.permission));
|
|
219
|
+
}
|
|
220
|
+
return { config, warnings: [], ruleSpecs, layers: present };
|
|
221
|
+
}
|
|
222
|
+
/** 第 9 步:叠加项目级(受限)与命令行。 */
|
|
223
|
+
export function mergeProjectAndCli(base, project, cli, projectLabel) {
|
|
224
|
+
let config = base.config;
|
|
225
|
+
const warnings = [...base.warnings];
|
|
226
|
+
const ruleSpecs = [...base.ruleSpecs];
|
|
227
|
+
const present = [...base.layers];
|
|
228
|
+
if (project !== undefined) {
|
|
229
|
+
const baseline = config.permission?.mode ?? "default";
|
|
230
|
+
const restricted = restrictProjectConfig(project, baseline, projectLabel, config.tools?.preset ?? "default");
|
|
231
|
+
warnings.push(...restricted.warnings);
|
|
232
|
+
config = mergeConfig(config, restricted.accepted);
|
|
233
|
+
present.push("project");
|
|
234
|
+
ruleSpecs.push(...collectRules("project", restricted.accepted.permission));
|
|
235
|
+
}
|
|
236
|
+
// 命令行是用户的显式选择,最后叠加(可以放宽项目级收紧过的 mode,§7.2「放宽只认用户级 / 命令行 / profile」)。
|
|
237
|
+
const cliConfig = cli === undefined ? undefined : cliOverridesToConfig(cli);
|
|
238
|
+
if (cliConfig !== undefined && Object.keys(cliConfig).length > 0) {
|
|
239
|
+
config = mergeConfig(config, cliConfig);
|
|
240
|
+
present.push("cli");
|
|
241
|
+
ruleSpecs.push(...collectRules("cli", cliConfig.permission));
|
|
242
|
+
}
|
|
243
|
+
return { config, warnings, ruleSpecs, layers: present };
|
|
244
|
+
}
|
|
245
|
+
/** 一次合并全部层(SDK / 测试用)。 */
|
|
246
|
+
export function mergeConfigLayers(layers) {
|
|
247
|
+
return mergeProjectAndCli(mergeBaseLayers(layers), layers.project, layers.cli);
|
|
248
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 目录解析(设计 §10.1、§11.1 第 5 步)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 用户级配置目录:`AMA_CONFIG_DIR` > Windows `%APPDATA%\ama` > `$XDG_CONFIG_HOME/ama` > `~/.config/ama`。
|
|
5
|
+
* - 数据目录:`AMA_DATA_DIR` > Windows `%LOCALAPPDATA%\ama` > `$XDG_DATA_HOME/ama` > `~/.local/share/ama`。
|
|
6
|
+
* - 会话目录:`--session-dir` > profile.sessionDir > `<dataDir>/sessions`。
|
|
7
|
+
*/
|
|
8
|
+
import type { ResolvedPaths } from "../cli/runtime.js";
|
|
9
|
+
export declare const CONFIG_FILE = "config.json";
|
|
10
|
+
export declare const AUTH_FILE = "auth.json";
|
|
11
|
+
export declare const HOOKS_FILE = "hooks.json";
|
|
12
|
+
export declare const TRUST_FILE = "trust.json";
|
|
13
|
+
export declare const KEYBINDINGS_FILE = "keybindings.json";
|
|
14
|
+
/** 项目级目录名(`<cwd>/.ama/`)。 */
|
|
15
|
+
export declare const PROJECT_DIR = ".ama";
|
|
16
|
+
export interface PathEnv {
|
|
17
|
+
readonly env?: Readonly<Record<string, string | undefined>>;
|
|
18
|
+
readonly platform?: NodeJS.Platform;
|
|
19
|
+
readonly home?: string;
|
|
20
|
+
}
|
|
21
|
+
/** 展开开头的 `~`。 */
|
|
22
|
+
export declare function expandHome(path: string, options?: PathEnv): string;
|
|
23
|
+
export declare function resolveConfigDir(options?: PathEnv): string;
|
|
24
|
+
export declare function resolveDataDir(options?: PathEnv): string;
|
|
25
|
+
export interface ResolvePathsInput extends PathEnv {
|
|
26
|
+
cwd: string;
|
|
27
|
+
/** `--session-dir`(相对 cwd)。 */
|
|
28
|
+
sessionDirFlag?: string | undefined;
|
|
29
|
+
/** profile.sessionDir(已校验为绝对路径)。 */
|
|
30
|
+
profileSessionDir?: string | undefined;
|
|
31
|
+
}
|
|
32
|
+
export declare function resolvePaths(input: ResolvePathsInput): ResolvedPaths;
|
|
33
|
+
/** 建目录(0700)并确认可写;失败抛 StartupError(退出码 3)。 */
|
|
34
|
+
export declare function ensureDir(dir: string, label: string): void;
|
|
35
|
+
/** 第 5 步:建 configDir / dataDir / sessionDir。 */
|
|
36
|
+
export declare function ensurePaths(paths: ResolvedPaths): void;
|
|
37
|
+
export declare function userFile(paths: Pick<ResolvedPaths, "configDir">, name: string): string;
|
|
38
|
+
export declare function projectFile(cwd: string, name: string): string;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 目录解析(设计 §10.1、§11.1 第 5 步)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* - 用户级配置目录:`AMA_CONFIG_DIR` > Windows `%APPDATA%\ama` > `$XDG_CONFIG_HOME/ama` > `~/.config/ama`。
|
|
5
|
+
* - 数据目录:`AMA_DATA_DIR` > Windows `%LOCALAPPDATA%\ama` > `$XDG_DATA_HOME/ama` > `~/.local/share/ama`。
|
|
6
|
+
* - 会话目录:`--session-dir` > profile.sessionDir > `<dataDir>/sessions`。
|
|
7
|
+
*/
|
|
8
|
+
import { accessSync, constants, mkdirSync, statSync } from "node:fs";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { isAbsolute, join, resolve } from "node:path";
|
|
11
|
+
import { StartupError } from "../errors.js";
|
|
12
|
+
/** 与 cli/exit-codes.ts 的 Config 相同;config 层不反向依赖 cli 的值。 */
|
|
13
|
+
const EXIT_CONFIG = 3;
|
|
14
|
+
export const CONFIG_FILE = "config.json";
|
|
15
|
+
export const AUTH_FILE = "auth.json";
|
|
16
|
+
export const HOOKS_FILE = "hooks.json";
|
|
17
|
+
export const TRUST_FILE = "trust.json";
|
|
18
|
+
export const KEYBINDINGS_FILE = "keybindings.json";
|
|
19
|
+
/** 项目级目录名(`<cwd>/.ama/`)。 */
|
|
20
|
+
export const PROJECT_DIR = ".ama";
|
|
21
|
+
function envOf(options) {
|
|
22
|
+
return options.env ?? process.env;
|
|
23
|
+
}
|
|
24
|
+
function nonEmpty(value) {
|
|
25
|
+
return value !== undefined && value.trim() !== "" ? value : undefined;
|
|
26
|
+
}
|
|
27
|
+
function homeOf(options) {
|
|
28
|
+
const env = envOf(options);
|
|
29
|
+
return options.home ?? nonEmpty(env["HOME"]) ?? nonEmpty(env["USERPROFILE"]) ?? homedir();
|
|
30
|
+
}
|
|
31
|
+
/** 展开开头的 `~`。 */
|
|
32
|
+
export function expandHome(path, options = {}) {
|
|
33
|
+
if (path === "~")
|
|
34
|
+
return homeOf(options);
|
|
35
|
+
if (path.startsWith("~/") || path.startsWith("~\\"))
|
|
36
|
+
return join(homeOf(options), path.slice(2));
|
|
37
|
+
return path;
|
|
38
|
+
}
|
|
39
|
+
export function resolveConfigDir(options = {}) {
|
|
40
|
+
const env = envOf(options);
|
|
41
|
+
const explicit = nonEmpty(env["AMA_CONFIG_DIR"]);
|
|
42
|
+
if (explicit !== undefined)
|
|
43
|
+
return resolve(expandHome(explicit, options));
|
|
44
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
45
|
+
const appData = nonEmpty(env["APPDATA"]);
|
|
46
|
+
if (appData !== undefined)
|
|
47
|
+
return join(appData, "ama");
|
|
48
|
+
}
|
|
49
|
+
const xdg = nonEmpty(env["XDG_CONFIG_HOME"]);
|
|
50
|
+
if (xdg !== undefined && isAbsolute(xdg))
|
|
51
|
+
return join(xdg, "ama");
|
|
52
|
+
return join(homeOf(options), ".config", "ama");
|
|
53
|
+
}
|
|
54
|
+
export function resolveDataDir(options = {}) {
|
|
55
|
+
const env = envOf(options);
|
|
56
|
+
const explicit = nonEmpty(env["AMA_DATA_DIR"]);
|
|
57
|
+
if (explicit !== undefined)
|
|
58
|
+
return resolve(expandHome(explicit, options));
|
|
59
|
+
if ((options.platform ?? process.platform) === "win32") {
|
|
60
|
+
const local = nonEmpty(env["LOCALAPPDATA"]);
|
|
61
|
+
if (local !== undefined)
|
|
62
|
+
return join(local, "ama");
|
|
63
|
+
}
|
|
64
|
+
const xdg = nonEmpty(env["XDG_DATA_HOME"]);
|
|
65
|
+
if (xdg !== undefined && isAbsolute(xdg))
|
|
66
|
+
return join(xdg, "ama");
|
|
67
|
+
return join(homeOf(options), ".local", "share", "ama");
|
|
68
|
+
}
|
|
69
|
+
export function resolvePaths(input) {
|
|
70
|
+
const configDir = resolveConfigDir(input);
|
|
71
|
+
const dataDir = resolveDataDir(input);
|
|
72
|
+
const cwd = resolve(input.cwd);
|
|
73
|
+
let sessionDir;
|
|
74
|
+
if (input.sessionDirFlag !== undefined) {
|
|
75
|
+
sessionDir = resolve(cwd, expandHome(input.sessionDirFlag, input));
|
|
76
|
+
}
|
|
77
|
+
else if (input.profileSessionDir !== undefined) {
|
|
78
|
+
sessionDir = resolve(input.profileSessionDir);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
sessionDir = join(dataDir, "sessions");
|
|
82
|
+
}
|
|
83
|
+
return { configDir, dataDir, sessionDir, cwd };
|
|
84
|
+
}
|
|
85
|
+
/** 建目录(0700)并确认可写;失败抛 StartupError(退出码 3)。 */
|
|
86
|
+
export function ensureDir(dir, label) {
|
|
87
|
+
try {
|
|
88
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
89
|
+
if (!statSync(dir).isDirectory())
|
|
90
|
+
throw new Error("不是目录");
|
|
91
|
+
accessSync(dir, constants.W_OK);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
throw new StartupError("config_invalid", `${label}不可写:${dir}(${error instanceof Error ? error.message : String(error)})`, EXIT_CONFIG, { cause: error });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/** 第 5 步:建 configDir / dataDir / sessionDir。 */
|
|
98
|
+
export function ensurePaths(paths) {
|
|
99
|
+
ensureDir(paths.configDir, "配置目录");
|
|
100
|
+
ensureDir(paths.dataDir, "数据目录");
|
|
101
|
+
ensureDir(paths.sessionDir, "会话目录");
|
|
102
|
+
}
|
|
103
|
+
export function userFile(paths, name) {
|
|
104
|
+
return join(paths.configDir, name);
|
|
105
|
+
}
|
|
106
|
+
export function projectFile(cwd, name) {
|
|
107
|
+
return join(cwd, PROJECT_DIR, name);
|
|
108
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* profile.json(宿主用,设计 §10.3、§11.1 第 3 步、§11.2)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* profile 的字段等价于对应命令行参数;命令行显式参数优先(合并在 cli/bootstrap.ts)。
|
|
5
|
+
* 文件不存在 / 版本不符 / 路径不是绝对路径 → StartupError(退出码 3)。profile 不含密钥。
|
|
6
|
+
*/
|
|
7
|
+
import type { AmaConfig } from "./types.js";
|
|
8
|
+
/** profile 展开后的等价参数(全部为绝对路径)。 */
|
|
9
|
+
export interface ProfileOptions {
|
|
10
|
+
path: string;
|
|
11
|
+
host?: string;
|
|
12
|
+
instructions: string[];
|
|
13
|
+
skillDirs: string[];
|
|
14
|
+
promptDirs: string[];
|
|
15
|
+
hooksFile?: string;
|
|
16
|
+
authFile?: string;
|
|
17
|
+
/** false:key 只来自 authFile。 */
|
|
18
|
+
authEnv: boolean;
|
|
19
|
+
sessionDir?: string;
|
|
20
|
+
/** profile.config 文件路径。 */
|
|
21
|
+
configFile?: string;
|
|
22
|
+
/** profile.config 的内容(已校验)。 */
|
|
23
|
+
config?: AmaConfig;
|
|
24
|
+
trustProject: boolean;
|
|
25
|
+
warnings: string[];
|
|
26
|
+
}
|
|
27
|
+
export declare function loadProfile(path: string, cwd?: string): ProfileOptions;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* profile.json(宿主用,设计 §10.3、§11.1 第 3 步、§11.2)。[B5]
|
|
3
|
+
*
|
|
4
|
+
* profile 的字段等价于对应命令行参数;命令行显式参数优先(合并在 cli/bootstrap.ts)。
|
|
5
|
+
* 文件不存在 / 版本不符 / 路径不是绝对路径 → StartupError(退出码 3)。profile 不含密钥。
|
|
6
|
+
*/
|
|
7
|
+
import { isAbsolute, resolve } from "node:path";
|
|
8
|
+
import { StartupError } from "../errors.js";
|
|
9
|
+
import { loadConfigFile } from "./load.js";
|
|
10
|
+
import { PROFILE_PATH_FIELDS, PROFILE_PATH_LIST_FIELDS } from "./schema.js";
|
|
11
|
+
const EXIT_CONFIG = 3;
|
|
12
|
+
export function loadProfile(path, cwd = process.cwd()) {
|
|
13
|
+
const abs = resolve(cwd, path);
|
|
14
|
+
const loaded = loadConfigFile("profile", abs, { required: true });
|
|
15
|
+
if (loaded === undefined) {
|
|
16
|
+
throw new StartupError("profile_invalid", `${abs}: 文件不存在`, EXIT_CONFIG);
|
|
17
|
+
}
|
|
18
|
+
const profile = loaded.value;
|
|
19
|
+
const relative = [];
|
|
20
|
+
for (const key of PROFILE_PATH_FIELDS) {
|
|
21
|
+
const value = profile[key];
|
|
22
|
+
if (value !== undefined && !isAbsolute(value))
|
|
23
|
+
relative.push(`${key}: ${value}`);
|
|
24
|
+
}
|
|
25
|
+
for (const key of PROFILE_PATH_LIST_FIELDS) {
|
|
26
|
+
for (const value of profile[key] ?? []) {
|
|
27
|
+
if (!isAbsolute(value))
|
|
28
|
+
relative.push(`${key}: ${value}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (relative.length > 0) {
|
|
32
|
+
throw new StartupError("profile_invalid", `${abs}: profile 中的路径必须是绝对路径:\n ${relative.join("\n ")}`, EXIT_CONFIG);
|
|
33
|
+
}
|
|
34
|
+
const options = profileToOptions(abs, profile, loaded.warnings);
|
|
35
|
+
if (profile.config !== undefined) {
|
|
36
|
+
const config = loadConfigFile("config", profile.config, { required: true });
|
|
37
|
+
if (config !== undefined) {
|
|
38
|
+
options.config = config.value;
|
|
39
|
+
options.warnings.push(...config.warnings);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return options;
|
|
43
|
+
}
|
|
44
|
+
function profileToOptions(path, profile, warnings) {
|
|
45
|
+
const options = {
|
|
46
|
+
path,
|
|
47
|
+
instructions: [...(profile.instructions ?? [])],
|
|
48
|
+
skillDirs: [...(profile.skillDirs ?? [])],
|
|
49
|
+
promptDirs: [...(profile.promptDirs ?? [])],
|
|
50
|
+
authEnv: profile.authEnv !== false,
|
|
51
|
+
trustProject: profile.trustProject === true,
|
|
52
|
+
warnings: [...warnings],
|
|
53
|
+
};
|
|
54
|
+
if (profile.host !== undefined)
|
|
55
|
+
options.host = profile.host;
|
|
56
|
+
if (profile.hooksFile !== undefined)
|
|
57
|
+
options.hooksFile = profile.hooksFile;
|
|
58
|
+
if (profile.authFile !== undefined)
|
|
59
|
+
options.authFile = profile.authFile;
|
|
60
|
+
if (profile.sessionDir !== undefined)
|
|
61
|
+
options.sessionDir = profile.sessionDir;
|
|
62
|
+
if (profile.config !== undefined)
|
|
63
|
+
options.configFile = profile.config;
|
|
64
|
+
return options;
|
|
65
|
+
}
|