@armadra/agent 0.3.0 → 0.4.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.
Files changed (198) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/README.md +93 -44
  3. package/dist/agent/retry.d.ts +1 -1
  4. package/dist/agent/retry.js +2 -1
  5. package/dist/agent/session-cache.js +1 -1
  6. package/dist/agent/session-classifier.d.ts +18 -0
  7. package/dist/agent/session-classifier.js +103 -0
  8. package/dist/agent/session-core.d.ts +13 -0
  9. package/dist/agent/session-tools.js +24 -3
  10. package/dist/agent/session.d.ts +3 -0
  11. package/dist/agent/session.js +12 -3
  12. package/dist/agent/tool-runner.js +15 -2
  13. package/dist/agent/types.d.ts +9 -1
  14. package/dist/ai/apis/anthropic-messages.js +3 -2
  15. package/dist/ai/apis/google-generative-ai.js +3 -2
  16. package/dist/ai/apis/openai-completions.js +3 -2
  17. package/dist/ai/apis/openai-responses.js +3 -2
  18. package/dist/ai/http.d.ts +28 -6
  19. package/dist/ai/http.js +41 -8
  20. package/dist/ai/providers/registry.d.ts +8 -1
  21. package/dist/ai/providers/registry.js +30 -22
  22. package/dist/ai/providers/suggest.d.ts +18 -0
  23. package/dist/ai/providers/suggest.js +72 -0
  24. package/dist/ai/sse.d.ts +6 -2
  25. package/dist/ai/sse.js +22 -2
  26. package/dist/ai/types.d.ts +9 -2
  27. package/dist/bundle/ama.cjs +15279 -10151
  28. package/dist/cli/args.d.ts +19 -4
  29. package/dist/cli/args.js +83 -10
  30. package/dist/cli/bootstrap.js +27 -3
  31. package/dist/cli/codemode-notice.d.ts +20 -0
  32. package/dist/cli/codemode-notice.js +55 -0
  33. package/dist/cli/compose-session.d.ts +5 -0
  34. package/dist/cli/compose-session.js +32 -1
  35. package/dist/cli/compose-store.d.ts +1 -1
  36. package/dist/cli/compose-store.js +3 -1
  37. package/dist/cli/compose.d.ts +14 -4
  38. package/dist/cli/compose.js +29 -10
  39. package/dist/cli/default-model.d.ts +38 -1
  40. package/dist/cli/default-model.js +96 -9
  41. package/dist/cli/deps.d.ts +36 -0
  42. package/dist/cli/exit-codes.d.ts +2 -0
  43. package/dist/cli/exit-codes.js +3 -0
  44. package/dist/cli/fake-visibility.d.ts +13 -0
  45. package/dist/cli/fake-visibility.js +26 -0
  46. package/dist/cli/from-prompt.d.ts +18 -0
  47. package/dist/cli/from-prompt.js +49 -0
  48. package/dist/cli/main.d.ts +9 -2
  49. package/dist/cli/main.js +77 -3
  50. package/dist/cli/proxy.d.ts +51 -0
  51. package/dist/cli/proxy.js +135 -0
  52. package/dist/cli/startup-screen.d.ts +29 -0
  53. package/dist/cli/startup-screen.js +47 -0
  54. package/dist/cli/startup-steps.d.ts +1 -1
  55. package/dist/cli/startup-steps.js +14 -12
  56. package/dist/cli/subcommands/config.d.ts +19 -3
  57. package/dist/cli/subcommands/config.js +108 -21
  58. package/dist/cli/subcommands/context.js +4 -1
  59. package/dist/cli/subcommands/doctor.js +12 -1
  60. package/dist/cli/subcommands/init.js +2 -1
  61. package/dist/cli/subcommands/models-discover.d.ts +15 -9
  62. package/dist/cli/subcommands/models-discover.js +53 -46
  63. package/dist/cli/subcommands/probe-runner.d.ts +96 -0
  64. package/dist/cli/subcommands/probe-runner.js +264 -0
  65. package/dist/cli/subcommands/providers-probe.d.ts +34 -0
  66. package/dist/cli/subcommands/providers-probe.js +87 -0
  67. package/dist/cli/subcommands/providers.d.ts +3 -2
  68. package/dist/cli/subcommands/providers.js +48 -45
  69. package/dist/cli/subcommands/sessions-export.d.ts +10 -0
  70. package/dist/cli/subcommands/sessions-export.js +59 -0
  71. package/dist/cli/subcommands/sessions-search.d.ts +13 -0
  72. package/dist/cli/subcommands/sessions-search.js +103 -0
  73. package/dist/cli/subcommands/sessions.d.ts +3 -2
  74. package/dist/cli/subcommands/sessions.js +22 -1
  75. package/dist/cli/subcommands/stats.d.ts +17 -0
  76. package/dist/cli/subcommands/stats.js +198 -0
  77. package/dist/cli/system-prompt-arg.d.ts +11 -0
  78. package/dist/cli/system-prompt-arg.js +34 -0
  79. package/dist/codemode/modes.d.ts +4 -11
  80. package/dist/codemode/modes.js +5 -27
  81. package/dist/codemode/tool.d.ts +18 -14
  82. package/dist/codemode/tool.js +71 -28
  83. package/dist/config/init.d.ts +6 -2
  84. package/dist/config/init.js +12 -5
  85. package/dist/config/json-schema.d.ts +1 -0
  86. package/dist/config/json-schema.js +36 -3
  87. package/dist/config/key-docs.d.ts +22 -0
  88. package/dist/config/key-docs.js +105 -0
  89. package/dist/config/merge.d.ts +9 -8
  90. package/dist/config/merge.js +22 -7
  91. package/dist/config/schema.d.ts +1 -1
  92. package/dist/config/schema.js +24 -5
  93. package/dist/config/types.d.ts +34 -6
  94. package/dist/config/types.js +17 -1
  95. package/dist/modes/commands-core.js +6 -5
  96. package/dist/modes/interactive/approval-dialog.d.ts +21 -5
  97. package/dist/modes/interactive/approval-dialog.js +106 -27
  98. package/dist/modes/interactive/commands.d.ts +11 -2
  99. package/dist/modes/interactive/commands.js +56 -16
  100. package/dist/modes/interactive/interactive-mode.d.ts +2 -1
  101. package/dist/modes/interactive/interactive-mode.js +65 -81
  102. package/dist/modes/interactive/key-dispatch.d.ts +3 -1
  103. package/dist/modes/interactive/key-dispatch.js +5 -6
  104. package/dist/modes/interactive/line/line-mode.d.ts +1 -0
  105. package/dist/modes/interactive/line/line-mode.js +6 -4
  106. package/dist/modes/interactive/line/line-render.d.ts +4 -0
  107. package/dist/modes/interactive/line/line-render.js +28 -6
  108. package/dist/modes/interactive/message-view.d.ts +48 -9
  109. package/dist/modes/interactive/message-view.js +238 -44
  110. package/dist/modes/interactive/panels.d.ts +18 -0
  111. package/dist/modes/interactive/panels.js +143 -0
  112. package/dist/modes/interactive/pickers.d.ts +23 -2
  113. package/dist/modes/interactive/pickers.js +48 -15
  114. package/dist/modes/interactive/run-indicator.d.ts +51 -0
  115. package/dist/modes/interactive/run-indicator.js +189 -0
  116. package/dist/modes/interactive/startup-header.d.ts +40 -0
  117. package/dist/modes/interactive/startup-header.js +169 -0
  118. package/dist/modes/interactive/status-bar.d.ts +18 -15
  119. package/dist/modes/interactive/status-bar.js +98 -56
  120. package/dist/modes/interactive/tool-summary.d.ts +46 -0
  121. package/dist/modes/interactive/tool-summary.js +218 -0
  122. package/dist/modes/interactive/tool-view.d.ts +48 -15
  123. package/dist/modes/interactive/tool-view.js +203 -145
  124. package/dist/modes/print/print-mode.d.ts +31 -4
  125. package/dist/modes/print/print-mode.js +116 -7
  126. package/dist/modes/rpc/commands.js +5 -0
  127. package/dist/permissions/auto-safe.d.ts +60 -0
  128. package/dist/permissions/auto-safe.js +529 -0
  129. package/dist/permissions/classifier.d.ts +64 -0
  130. package/dist/permissions/classifier.js +184 -0
  131. package/dist/permissions/dangerous.d.ts +5 -0
  132. package/dist/permissions/dangerous.js +1 -1
  133. package/dist/permissions/modes.d.ts +30 -0
  134. package/dist/permissions/modes.js +78 -0
  135. package/dist/permissions/pipeline.d.ts +31 -4
  136. package/dist/permissions/pipeline.js +196 -6
  137. package/dist/permissions/protected.d.ts +19 -0
  138. package/dist/permissions/protected.js +74 -0
  139. package/dist/permissions/rules.js +3 -0
  140. package/dist/permissions/types.d.ts +50 -3
  141. package/dist/permissions/types.js +3 -0
  142. package/dist/sdk.d.ts +9 -3
  143. package/dist/sdk.js +10 -2
  144. package/dist/session/export.d.ts +32 -0
  145. package/dist/session/export.js +187 -0
  146. package/dist/session/redact.d.ts +15 -0
  147. package/dist/session/redact.js +55 -0
  148. package/dist/session/reuse.d.ts +33 -0
  149. package/dist/session/reuse.js +86 -0
  150. package/dist/session/scan.d.ts +34 -0
  151. package/dist/session/scan.js +140 -0
  152. package/dist/session/search.d.ts +52 -0
  153. package/dist/session/search.js +211 -0
  154. package/dist/session/stats-aggregate.d.ts +63 -0
  155. package/dist/session/stats-aggregate.js +163 -0
  156. package/dist/session/stats-index.d.ts +26 -0
  157. package/dist/session/stats-index.js +91 -0
  158. package/dist/session/stats-scan.d.ts +54 -0
  159. package/dist/session/stats-scan.js +236 -0
  160. package/dist/tools/presets.d.ts +35 -8
  161. package/dist/tools/presets.js +56 -17
  162. package/dist/tui/component.d.ts +8 -2
  163. package/dist/tui/component.js +3 -1
  164. package/dist/tui/components/box.d.ts +6 -1
  165. package/dist/tui/components/box.js +16 -6
  166. package/dist/tui/components/card.d.ts +23 -0
  167. package/dist/tui/components/card.js +37 -0
  168. package/dist/tui/components/editor-history.d.ts +6 -0
  169. package/dist/tui/components/editor-history.js +45 -0
  170. package/dist/tui/components/editor-paste.d.ts +1 -1
  171. package/dist/tui/components/editor-paste.js +4 -4
  172. package/dist/tui/components/editor.d.ts +11 -5
  173. package/dist/tui/components/editor.js +52 -58
  174. package/dist/tui/components/key-value.d.ts +3 -0
  175. package/dist/tui/components/key-value.js +16 -6
  176. package/dist/tui/components/loader.d.ts +37 -7
  177. package/dist/tui/components/loader.js +84 -21
  178. package/dist/tui/components/markdown.d.ts +5 -1
  179. package/dist/tui/components/markdown.js +45 -21
  180. package/dist/tui/components/meter.d.ts +3 -3
  181. package/dist/tui/components/meter.js +13 -11
  182. package/dist/tui/components/select-list.d.ts +23 -1
  183. package/dist/tui/components/select-list.js +76 -13
  184. package/dist/tui/glyphs.d.ts +68 -0
  185. package/dist/tui/glyphs.js +114 -0
  186. package/dist/tui/theme.d.ts +30 -6
  187. package/dist/tui/theme.js +103 -17
  188. package/dist/tui.d.ts +4 -2
  189. package/dist/tui.js +3 -1
  190. package/docs/codemode.md +23 -9
  191. package/docs/hooks.md +10 -10
  192. package/docs/permissions.md +148 -0
  193. package/docs/providers.md +33 -10
  194. package/docs/rpc.md +36 -36
  195. package/docs/session-format.md +2 -1
  196. package/docs/sessions.md +134 -0
  197. package/docs/tui.md +139 -62
  198. package/package.json +3 -1
@@ -0,0 +1,51 @@
1
+ /**
2
+ * 环境变量代理(W4-C):Node 的全局 fetch 缺省不读 `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`。
3
+ *
4
+ * - 没设代理变量 → 什么都不做。
5
+ * - 运行时已接管(`NODE_USE_ENV_PROXY=1` 或 `--use-env-proxy`,且 Node 支持)→ 不重复设置。
6
+ * - Node 有 `http.setGlobalProxyFromEnv()`(24.x / 26.x 等)→ 进程启动时调用一次,等价于
7
+ * `NODE_USE_ENV_PROXY=1`(fetch 与 http/https 全局 agent 都走代理,`NO_PROXY` 生效)。零依赖。
8
+ * - 不支持的 Node 版本 → 只在会联网的命令里于 stderr 提示一次(不中断)。
9
+ *
10
+ * `ama doctor` 用 `describeProxy()` 显示同一份状态;代理 URL 里的账号密码打码。
11
+ */
12
+ export interface ProxyEnv {
13
+ httpsProxy?: string;
14
+ httpProxy?: string;
15
+ noProxy?: string;
16
+ }
17
+ export type ProxyState = "none"
18
+ /** 运行时已按环境变量接管(NODE_USE_ENV_PROXY / --use-env-proxy)。 */
19
+ | "runtime"
20
+ /** ama 启动时调用 http.setGlobalProxyFromEnv() 启用。 */
21
+ | "enabled"
22
+ /** 设了代理变量,但当前 Node 不支持内置代理。 */
23
+ | "unsupported"
24
+ /** 代理 URL 无法解析。 */
25
+ | "invalid";
26
+ export interface ProxyStatus {
27
+ env: ProxyEnv;
28
+ state: ProxyState;
29
+ error?: string;
30
+ }
31
+ type SetGlobalProxy = (env?: NodeJS.ProcessEnv) => () => void;
32
+ export interface ProxyRuntime {
33
+ setGlobalProxyFromEnv?: SetGlobalProxy | undefined;
34
+ execArgv: readonly string[];
35
+ version: string;
36
+ }
37
+ export declare function readProxyEnv(env: Readonly<Record<string, string | undefined>>): ProxyEnv;
38
+ export declare function defaultProxyRuntime(): ProxyRuntime;
39
+ /** 只判断不启用:没设代理 / 运行时已接管 / 能启用 / 不支持 / URL 无法解析。 */
40
+ export declare function inspectProxy(env: Readonly<Record<string, string | undefined>>, runtime?: ProxyRuntime): ProxyStatus;
41
+ /** 按上面的规则启用代理(进程级副作用);同一进程只做一次,之后返回同一份状态。 */
42
+ export declare function enableEnvProxy(env: Readonly<Record<string, string | undefined>>, runtime?: ProxyRuntime): ProxyStatus;
43
+ /** 测试用:清掉进程内记住的状态。 */
44
+ export declare function resetProxyForTest(): void;
45
+ /** 代理 URL 里的账号密码打码。 */
46
+ export declare function redactProxyUrl(value: string): string;
47
+ /** 不能启用时给一次 stderr 提示(每个进程至多一次);能用或没设代理返回 undefined。 */
48
+ export declare function proxyHint(status: ProxyStatus, nodeVersion?: string): string | undefined;
49
+ /** `ama doctor` 的代理段落。 */
50
+ export declare function describeProxy(status: ProxyStatus): string[];
51
+ export {};
@@ -0,0 +1,135 @@
1
+ /**
2
+ * 环境变量代理(W4-C):Node 的全局 fetch 缺省不读 `HTTPS_PROXY` / `HTTP_PROXY` / `NO_PROXY`。
3
+ *
4
+ * - 没设代理变量 → 什么都不做。
5
+ * - 运行时已接管(`NODE_USE_ENV_PROXY=1` 或 `--use-env-proxy`,且 Node 支持)→ 不重复设置。
6
+ * - Node 有 `http.setGlobalProxyFromEnv()`(24.x / 26.x 等)→ 进程启动时调用一次,等价于
7
+ * `NODE_USE_ENV_PROXY=1`(fetch 与 http/https 全局 agent 都走代理,`NO_PROXY` 生效)。零依赖。
8
+ * - 不支持的 Node 版本 → 只在会联网的命令里于 stderr 提示一次(不中断)。
9
+ *
10
+ * `ama doctor` 用 `describeProxy()` 显示同一份状态;代理 URL 里的账号密码打码。
11
+ */
12
+ import * as http from "node:http";
13
+ function pick(env, name) {
14
+ const value = env[name.toLowerCase()] ?? env[name.toUpperCase()];
15
+ return value !== undefined && value.trim() !== "" ? value.trim() : undefined;
16
+ }
17
+ export function readProxyEnv(env) {
18
+ const out = {};
19
+ const https = pick(env, "https_proxy");
20
+ const plain = pick(env, "http_proxy");
21
+ const no = pick(env, "no_proxy");
22
+ if (https !== undefined)
23
+ out.httpsProxy = https;
24
+ if (plain !== undefined)
25
+ out.httpProxy = plain;
26
+ if (no !== undefined)
27
+ out.noProxy = no;
28
+ return out;
29
+ }
30
+ export function defaultProxyRuntime() {
31
+ const fn = http.setGlobalProxyFromEnv;
32
+ return {
33
+ setGlobalProxyFromEnv: typeof fn === "function" ? fn.bind(http) : undefined,
34
+ execArgv: process.execArgv,
35
+ version: process.versions.node,
36
+ };
37
+ }
38
+ /** Node ≥ 24,或 22.21+:认 NODE_USE_ENV_PROXY / --use-env-proxy。 */
39
+ function runtimeReadsEnvProxy(version) {
40
+ const [major = 0, minor = 0] = version.split(".").map(Number);
41
+ return major >= 24 || (major === 22 && minor >= 21);
42
+ }
43
+ let current;
44
+ let hinted = false;
45
+ /** 只判断不启用:没设代理 / 运行时已接管 / 能启用 / 不支持 / URL 无法解析。 */
46
+ export function inspectProxy(env, runtime = defaultProxyRuntime()) {
47
+ if (current !== undefined)
48
+ return current;
49
+ const proxy = readProxyEnv(env);
50
+ if (proxy.httpsProxy === undefined && proxy.httpProxy === undefined)
51
+ return { env: proxy, state: "none" };
52
+ const flagged = env["NODE_USE_ENV_PROXY"] === "1" ||
53
+ runtime.execArgv.includes("--use-env-proxy") ||
54
+ (env["NODE_OPTIONS"] ?? "").includes("--use-env-proxy");
55
+ if (flagged && runtimeReadsEnvProxy(runtime.version))
56
+ return { env: proxy, state: "runtime" };
57
+ if (runtime.setGlobalProxyFromEnv === undefined)
58
+ return { env: proxy, state: "unsupported" };
59
+ for (const value of [proxy.httpsProxy, proxy.httpProxy]) {
60
+ if (value !== undefined && !URL.canParse(value))
61
+ return { env: proxy, state: "invalid", error: `无法解析 ${redactProxyUrl(value)}` };
62
+ }
63
+ return { env: proxy, state: "enabled" };
64
+ }
65
+ /** 按上面的规则启用代理(进程级副作用);同一进程只做一次,之后返回同一份状态。 */
66
+ export function enableEnvProxy(env, runtime = defaultProxyRuntime()) {
67
+ if (current !== undefined)
68
+ return current;
69
+ const status = inspectProxy(env, runtime);
70
+ if (status.state === "enabled") {
71
+ try {
72
+ runtime.setGlobalProxyFromEnv?.({ ...env });
73
+ }
74
+ catch (error) {
75
+ current = { env: status.env, state: "invalid", error: error.message };
76
+ return current;
77
+ }
78
+ }
79
+ current = status;
80
+ return current;
81
+ }
82
+ /** 测试用:清掉进程内记住的状态。 */
83
+ export function resetProxyForTest() {
84
+ current = undefined;
85
+ hinted = false;
86
+ }
87
+ /** 代理 URL 里的账号密码打码。 */
88
+ export function redactProxyUrl(value) {
89
+ try {
90
+ const url = new URL(value);
91
+ if (url.username === "" && url.password === "")
92
+ return value;
93
+ url.username = url.username === "" ? "" : "***";
94
+ url.password = url.password === "" ? "" : "***";
95
+ return url.toString();
96
+ }
97
+ catch {
98
+ return value.replace(/\/\/[^@/]*@/, "//***@");
99
+ }
100
+ }
101
+ /** 不能启用时给一次 stderr 提示(每个进程至多一次);能用或没设代理返回 undefined。 */
102
+ export function proxyHint(status, nodeVersion = process.versions.node) {
103
+ if (hinted)
104
+ return undefined;
105
+ if (status.state === "unsupported") {
106
+ hinted = true;
107
+ return (`ama: 检测到 HTTPS_PROXY / HTTP_PROXY,但 Node ${nodeVersion} 的 fetch 不读代理变量,请求将直连;` +
108
+ "升级到 Node 24+,或在 Node 22.21+ 上设 NODE_USE_ENV_PROXY=1\n");
109
+ }
110
+ if (status.state === "invalid") {
111
+ hinted = true;
112
+ return `ama: 代理变量无法解析,请求将直连:${status.error ?? ""}\n`;
113
+ }
114
+ return undefined;
115
+ }
116
+ /** `ama doctor` 的代理段落。 */
117
+ export function describeProxy(status) {
118
+ const { env } = status;
119
+ const lines = [];
120
+ if (env.httpsProxy !== undefined)
121
+ lines.push(`HTTPS_PROXY=${redactProxyUrl(env.httpsProxy)}`);
122
+ if (env.httpProxy !== undefined)
123
+ lines.push(`HTTP_PROXY=${redactProxyUrl(env.httpProxy)}`);
124
+ if (env.noProxy !== undefined)
125
+ lines.push(`NO_PROXY=${env.noProxy}`);
126
+ const state = {
127
+ none: "未设置代理变量:直连",
128
+ runtime: "已启用(Node 按 NODE_USE_ENV_PROXY / --use-env-proxy 接管)",
129
+ enabled: "已启用(ama 启动时调用 Node 内置的 setGlobalProxyFromEnv)",
130
+ unsupported: `✗ 当前 Node ${process.versions.node} 不支持内置代理,请求直连;升级到 Node 24+`,
131
+ invalid: `✗ 代理变量无法解析:${status.error ?? ""}`,
132
+ };
133
+ lines.push(state[status.state]);
134
+ return lines;
135
+ }
@@ -6,10 +6,39 @@
6
6
  * - normal:标题行 + 模型 / 信任 / 已加载资源清单 / 警告数
7
7
  * - header:只有标题行(版本与按键提示)
8
8
  * - silent:不输出
9
+ *
10
+ * 交互模式(终端界面视觉设计 v1 §3.1)用结构化的 `startupInfo()` 自己排版(框 / 无框两态);
11
+ * `buildStartupScreen` 的纯文本行保留给 line 模式与测试。
9
12
  */
13
+ import type { PermissionMode } from "../permissions/types.js";
10
14
  import type { Runtime } from "./runtime.js";
11
15
  export type StartupScreenLevel = "normal" | "header" | "silent";
12
16
  export declare function startupScreenLevel(runtime: Pick<Runtime, "config">): StartupScreenLevel;
13
17
  export declare function headerLine(): string;
14
18
  export type StartupScreenRuntime = Pick<Runtime, "config" | "trust" | "resources" | "model" | "thinkingLevel" | "hooks" | "host" | "paths" | "warnings">;
15
19
  export declare function buildStartupScreen(runtime: StartupScreenRuntime, level?: StartupScreenLevel): string[];
20
+ /** 交互模式启动头的结构化字段(§3.1)。 */
21
+ export interface StartupInfo {
22
+ version: string;
23
+ /** `provider/id[@渠道]`。 */
24
+ model: string;
25
+ thinking: string;
26
+ /** 会话目录(家目录缩写为 `~`)。 */
27
+ cwd: string;
28
+ trusted: boolean;
29
+ /** 信任来源的短名(trust.json / 命令行 / 本次确认 / profile / 缺省)。 */
30
+ trustSource: string;
31
+ permissionMode: PermissionMode;
32
+ preset: string;
33
+ codemode: "off" | "on" | "only";
34
+ /** 上下文文件的文件名(外层在前)。 */
35
+ contextFiles: string[];
36
+ skills: number;
37
+ prompts: number;
38
+ hooks: number;
39
+ host?: string;
40
+ warnings: number;
41
+ }
42
+ /** 家目录前缀缩写为 `~`(`/` 与 `\` 两种分隔都认,余下部分原样保留)。 */
43
+ export declare function tildePath(path: string, home: string | undefined): string;
44
+ export declare function startupInfo(runtime: StartupScreenRuntime & Pick<Runtime, "session">, home?: string): StartupInfo;
@@ -6,7 +6,13 @@
6
6
  * - normal:标题行 + 模型 / 信任 / 已加载资源清单 / 警告数
7
7
  * - header:只有标题行(版本与按键提示)
8
8
  * - silent:不输出
9
+ *
10
+ * 交互模式(终端界面视觉设计 v1 §3.1)用结构化的 `startupInfo()` 自己排版(框 / 无框两态);
11
+ * `buildStartupScreen` 的纯文本行保留给 line 模式与测试。
9
12
  */
13
+ import { basename } from "node:path";
14
+ import { formatModelRef } from "../ai/providers/channels.js";
15
+ import { effectiveCodemodeMode } from "../tools/presets.js";
10
16
  import { AMA_VERSION } from "../version.js";
11
17
  export function startupScreenLevel(runtime) {
12
18
  return runtime.config.ui?.quietStartup ?? "normal";
@@ -54,3 +60,44 @@ export function buildStartupScreen(runtime, level = startupScreenLevel(runtime))
54
60
  lines.push(`警告:${runtime.warnings.length} 条(ama doctor 查看)`);
55
61
  return lines;
56
62
  }
63
+ const TRUST_SOURCE = {
64
+ flag: "命令行",
65
+ "trust-file": "trust.json",
66
+ prompt: "本次确认",
67
+ profile: "profile",
68
+ default: "缺省",
69
+ };
70
+ /** 家目录前缀缩写为 `~`(`/` 与 `\` 两种分隔都认,余下部分原样保留)。 */
71
+ export function tildePath(path, home) {
72
+ if (home === undefined || home === "")
73
+ return path;
74
+ const trimmed = /[\\/]$/.test(home) ? home.slice(0, -1) : home;
75
+ if (path === trimmed)
76
+ return "~";
77
+ const next = path[trimmed.length];
78
+ return path.startsWith(trimmed) && (next === "/" || next === "\\")
79
+ ? `~${path.slice(trimmed.length)}`
80
+ : path;
81
+ }
82
+ export function startupInfo(runtime, home) {
83
+ const { resources } = runtime;
84
+ const info = {
85
+ version: AMA_VERSION,
86
+ model: formatModelRef(runtime.model),
87
+ thinking: runtime.thinkingLevel,
88
+ cwd: tildePath(runtime.paths.cwd, home),
89
+ trusted: runtime.trust.trusted,
90
+ trustSource: TRUST_SOURCE[runtime.trust.source],
91
+ permissionMode: runtime.session.state.permissionMode,
92
+ preset: runtime.config.tools?.preset ?? "default",
93
+ codemode: effectiveCodemodeMode(runtime.config),
94
+ contextFiles: resources.contextFiles.map((f) => basename(f.path)),
95
+ skills: resources.skills.length,
96
+ prompts: resources.prompts.length,
97
+ hooks: runtime.hooks.list().length,
98
+ warnings: runtime.warnings.length,
99
+ };
100
+ if (runtime.host !== undefined)
101
+ info.host = runtime.host.adapter.id;
102
+ return info;
103
+ }
@@ -26,7 +26,7 @@ export interface ModelChoice {
26
26
  provider: ProviderData;
27
27
  }
28
28
  /** 第 11 步:解析模型与 key。 */
29
- export declare function resolveModel(args: ParsedArgs, registry: ProviderRegistryApi, manager: SessionManagerApi, defaultModel: string | undefined, deps: RuntimeDeps, interactive: boolean): Promise<ModelChoice>;
29
+ export declare function resolveModel(args: ParsedArgs, registry: ProviderRegistryApi, manager: SessionManagerApi, defaultModel: string | undefined, deps: RuntimeDeps, interactive: boolean, env?: Readonly<Record<string, string | undefined>>): Promise<ModelChoice>;
30
30
  export declare function thinkingOf(args: ParsedArgs, manager: SessionManagerApi, fallback: ModelThinkingLevel | undefined): ModelThinkingLevel;
31
31
  /** 第 12 步之后:`--tools` / `--exclude-tools`(宿主工具注册后再校验,宿主工具名也可用)。 */
32
32
  export declare function applyToolFilters(args: ParsedArgs, runtimeTools: Runtime["tools"]): void;
@@ -2,12 +2,14 @@
2
2
  * 启动序列的单步辅助(设计 §11.1):错误映射、模式判定、profile 合并、会话请求、
3
3
  * 模型与思考级别解析、工具过滤、指令文件。[B5] 由 cli/bootstrap.ts 编排。
4
4
  */
5
+ import { describeLookupFailure } from "../ai/providers/suggest.js";
5
6
  import { formatModelRef } from "../ai/providers/channels.js";
6
7
  import { statSync } from "node:fs";
7
8
  import { isAbsolute, resolve } from "node:path";
8
9
  import { AmaError, StartupError, isAmaError } from "../errors.js";
9
10
  import { UsageError } from "./args.js";
10
- import { pickDefaultModel } from "./default-model.js";
11
+ import { noModelGuidance, pickDefaultModel } from "./default-model.js";
12
+ import { hideFakeProvider } from "./fake-visibility.js";
11
13
  import { ExitCode } from "./exit-codes.js";
12
14
  /** 把非 StartupError 包成指定退出码;StartupError / UsageError 原样透传。 */
13
15
  export async function step(exitCode, label, fn) {
@@ -67,6 +69,8 @@ export function applyProfile(args, profile) {
67
69
  return merged;
68
70
  }
69
71
  export function sessionRequestOf(args) {
72
+ if (args.noSession)
73
+ return { kind: "memory" };
70
74
  if (args.continue)
71
75
  return { kind: "continue" };
72
76
  if (args.resume)
@@ -80,7 +84,7 @@ export function sessionRequestOf(args) {
80
84
  export function sourceOf(request, manager) {
81
85
  if (request.kind === "fork")
82
86
  return "fork";
83
- if (request.kind === "new")
87
+ if (request.kind === "new" || request.kind === "memory")
84
88
  return "startup";
85
89
  return manager.entries().length > 0 ? "resume" : "startup";
86
90
  }
@@ -94,7 +98,7 @@ export function lastEntry(manager, predicate) {
94
98
  return undefined;
95
99
  }
96
100
  /** 第 11 步:解析模型与 key。 */
97
- export async function resolveModel(args, registry, manager, defaultModel, deps, interactive) {
101
+ export async function resolveModel(args, registry, manager, defaultModel, deps, interactive, env = {}) {
98
102
  if (args.provider !== undefined && args.model === undefined) {
99
103
  throw new UsageError(`--provider ${args.provider} 需要同时给出 --model(不回退到该供应商的缺省模型)`);
100
104
  }
@@ -102,13 +106,8 @@ export async function resolveModel(args, registry, manager, defaultModel, deps,
102
106
  const found = registry.findModel(ref);
103
107
  if (found.ok)
104
108
  return { model: found.model, provider: found.provider };
105
- const hint = found.candidates.length > 0 ? `;候选:${found.candidates.slice(0, 20).join(", ")}` : "";
106
- const what = found.reason === "provider_not_found"
107
- ? "供应商不存在"
108
- : found.reason === "ambiguous"
109
- ? "模型名有歧义"
110
- : "模型不存在";
111
- throw new StartupError("model_not_found", `${label}${what}:${ref}${hint}`, ExitCode.NoModel);
109
+ const code = found.reason === "provider_not_found" ? "provider_not_found" : "model_not_found";
110
+ throw new StartupError(code, `${label}${describeLookupFailure(ref, found)}`, ExitCode.NoModel);
112
111
  };
113
112
  let choice;
114
113
  if (args.model !== undefined) {
@@ -137,13 +136,16 @@ export async function resolveModel(args, registry, manager, defaultModel, deps,
137
136
  }
138
137
  }
139
138
  const pick = async (reason) => {
140
- const picked = interactive ? await deps.ui?.pickModel?.(registry, reason) : undefined;
139
+ // 选择器不列测试供应商 fake(AMA_SHOW_FAKE=1 或 AMA_FAKE_SCRIPT 时照列)
140
+ const picked = interactive
141
+ ? await deps.ui?.pickModel?.(hideFakeProvider(registry, env), reason)
142
+ : undefined;
141
143
  if (picked === undefined)
142
144
  throw new StartupError("no_api_key", reason, ExitCode.NoModel);
143
145
  return lookup(picked, "");
144
146
  };
145
147
  if (choice === undefined) {
146
- return pick("没有可用模型:用 `ama auth set <provider>` 保存 key,或设置对应环境变量,或 --model 指定");
148
+ return pick(noModelGuidance(registry));
147
149
  }
148
150
  if (choice.provider.requiresApiKey) {
149
151
  const key = await registry.resolveApiKey(choice.provider.id);
@@ -3,16 +3,20 @@
3
3
  * 每一项标出来自哪一层(default / user / profile / project),并给出将使用的模型与原因
4
4
  * (`config.defaultModel` 或零配置选择)。只读;config 里的字面量 apiKey 只显示种类。[B6]
5
5
  *
6
+ * 缺省值来自 config/key-docs.ts 的 DISPLAY_DEFAULTS(含 DEFAULT_CONFIG 不写的 cache / codemode 等段),
7
+ * `codemode.mode` 不写时显示跟随预设的结果与原因;`--tools-preset` / `--codemode` 作为 cli 层叠加;
8
+ * 预设写的是旧名(codemode)时显示规范名并提示。
9
+ *
6
10
  * [W3-B12] 「供应商」节:config 里出现的供应商与 baseUrl 来自环境变量(`OPENAI_BASE_URL` /
7
11
  * `ANTHROPIC_BASE_URL`)的内置供应商,列出协议、生效 baseUrl 与来源、config 里每个模型的协议
8
12
  * (模型级 `api` 生效后的值)。
9
13
  */
10
- import type { AmaConfig } from "../../config/types.js";
14
+ import { type AmaConfig, type CodemodeMode } from "../../config/types.js";
11
15
  import type { Api, ProviderRegistryApi } from "../../ai/types.js";
12
16
  import type { CliIo, RuntimeDeps } from "../deps.js";
13
- export declare const CONFIG_USAGE = "\u7528\u6CD5\uFF1Aama config show [--json] [--profile <\u6587\u4EF6>] [--auth-file <\u6587\u4EF6>]\n ama config path \u914D\u7F6E\u76EE\u5F55\u3001\u6570\u636E\u76EE\u5F55\u4E0E\u5404\u6587\u4EF6\u8DEF\u5F84\n ama config edit \u7528 $VISUAL / $EDITOR \u6253\u5F00 config.json\uFF08\u6CA1\u6709\u7F16\u8F91\u5668\u65F6\u6253\u5370\u8DEF\u5F84\uFF09\n";
17
+ export declare const CONFIG_USAGE = "\u7528\u6CD5\uFF1Aama config show [--json] [--profile <\u6587\u4EF6>] [--auth-file <\u6587\u4EF6>]\n [--tools-preset <\u540D>] [--codemode off|on|only]\n ama config path \u914D\u7F6E\u76EE\u5F55\u3001\u6570\u636E\u76EE\u5F55\u4E0E\u5404\u6587\u4EF6\u8DEF\u5F84\n ama config edit \u7528 $VISUAL / $EDITOR \u6253\u5F00 config.json\uFF08\u6CA1\u6709\u7F16\u8F91\u5668\u65F6\u6253\u5370\u8DEF\u5F84\uFF09\n";
14
18
  type Layer = {
15
- name: "default" | "user" | "profile" | "project";
19
+ name: "default" | "user" | "profile" | "project" | "cli";
16
20
  label: string;
17
21
  value: unknown;
18
22
  };
@@ -49,5 +53,17 @@ export interface ProviderDescription {
49
53
  export declare function describeProviders(config: AmaConfig, registry: ProviderRegistryApi | undefined): ProviderDescription[];
50
54
  /** 每个生效叶子的来源:累加型列表列出所有贡献层,其余取最后设置它的层。 */
51
55
  export declare function sourcesOf(effective: AmaConfig, layers: readonly Layer[]): Map<string, string>;
56
+ export interface CodemodeDescription {
57
+ mode: CodemodeMode;
58
+ /** config:显式 codemode.mode;preset:跟随预设。 */
59
+ source: "config" | "preset";
60
+ /** 人读原因。 */
61
+ reason: string;
62
+ strict: boolean;
63
+ /** 生效模式不是 off 但 codemode 工具不可用(requireStrict)时的说明。 */
64
+ unavailable?: string;
65
+ }
66
+ /** codemode 生效模式与原因(跟随预设时写明预设与运行时 Node 是否隔离网络)。 */
67
+ export declare function describeCodemode(config: AmaConfig, nodeVersion?: string): CodemodeDescription;
52
68
  export declare function runConfig(argv: readonly string[], io: CliIo, deps: Pick<RuntimeDeps, "providers"> | undefined): Promise<number>;
53
69
  export {};
@@ -3,13 +3,18 @@
3
3
  * 每一项标出来自哪一层(default / user / profile / project),并给出将使用的模型与原因
4
4
  * (`config.defaultModel` 或零配置选择)。只读;config 里的字面量 apiKey 只显示种类。[B6]
5
5
  *
6
+ * 缺省值来自 config/key-docs.ts 的 DISPLAY_DEFAULTS(含 DEFAULT_CONFIG 不写的 cache / codemode 等段),
7
+ * `codemode.mode` 不写时显示跟随预设的结果与原因;`--tools-preset` / `--codemode` 作为 cli 层叠加;
8
+ * 预设写的是旧名(codemode)时显示规范名并提示。
9
+ *
6
10
  * [W3-B12] 「供应商」节:config 里出现的供应商与 baseUrl 来自环境变量(`OPENAI_BASE_URL` /
7
11
  * `ANTHROPIC_BASE_URL`)的内置供应商,列出协议、生效 baseUrl 与来源、config 里每个模型的协议
8
12
  * (模型级 `api` 生效后的值)。
9
13
  */
10
14
  import { metadataOf, modelFlags, sourcesLine } from "./model-meta.js";
11
15
  import { loadConfigFile } from "../../config/load.js";
12
- import { DEFAULT_CONFIG, PROFILE_DEFAULTS, mergeProjectAndCli } from "../../config/merge.js";
16
+ import { PROFILE_DEFAULTS, cliOverridesToConfig, mergeConfig, mergeProjectAndCli, } from "../../config/merge.js";
17
+ import { DISPLAY_DEFAULTS } from "../../config/key-docs.js";
13
18
  import { AUTH_FILE, CONFIG_FILE, HOOKS_FILE, projectFile, resolveConfigDir, resolveDataDir, } from "../../config/paths.js";
14
19
  import { spawnSync } from "node:child_process";
15
20
  import { existsSync } from "node:fs";
@@ -17,15 +22,18 @@ import { join } from "node:path";
17
22
  import { modelsDevCachePath } from "../../ai/providers/models-dev-cache.js";
18
23
  import { initConfigDir } from "../../config/init.js";
19
24
  import { CONFIG_SCHEMA_FILE } from "../../config/json-schema.js";
25
+ import { CODEMODE_MODES, TOOLS_PRESET_ALIASES, TOOLS_PRESET_INPUTS, canonicalPreset, } from "../../config/types.js";
26
+ import { codemodeAvailability, detectSandboxCapability } from "../../codemode/capability.js";
20
27
  import { baseUrlEnvOf } from "../../ai/providers/registry.js";
21
28
  import { classifyKeyValue } from "../../config/auth-file.js";
22
- import { effectiveCodemodeMode, resolvePreset } from "../../tools/presets.js";
29
+ import { CODEMODE_TOOL, resolveCodemodeMode, resolvePreset } from "../../tools/presets.js";
23
30
  import { builtinTools } from "../../tools/registry.js";
24
31
  import { parseSubArgs, UsageError } from "../args.js";
25
- import { pickDefaultModel } from "../default-model.js";
32
+ import { noModelGuidance, pickDefaultModel } from "../default-model.js";
26
33
  import { ExitCode } from "../exit-codes.js";
27
34
  import { buildRegistry, loadUserLevel } from "./context.js";
28
35
  export const CONFIG_USAGE = `用法:ama config show [--json] [--profile <文件>] [--auth-file <文件>]
36
+ [--tools-preset <名>] [--codemode off|on|only]
29
37
  ama config path 配置目录、数据目录与各文件路径
30
38
  ama config edit 用 $VISUAL / $EDITOR 打开 config.json(没有编辑器时打印路径)
31
39
  `;
@@ -105,13 +113,16 @@ export async function describeModel(config, registry) {
105
113
  return { ref: undefined, reason: "注册表未装配" };
106
114
  const picked = await pickDefaultModel(registry);
107
115
  if (picked === undefined)
108
- return { ref: undefined, reason: "没有可用模型:ama auth set <provider> 或设置对应环境变量" };
116
+ return { ref: undefined, reason: noModelGuidance(registry) };
109
117
  const ref = `${picked.provider.id}/${picked.model.id}`;
110
118
  if (picked.via === "local")
111
- return { ref, reason: `零配置:本地 ${picked.provider.id} 可达` };
119
+ return { ref, reason: `零配置:本地 ${picked.provider.id} 可达;${picked.rule}` };
112
120
  const key = await registry.resolveApiKey(picked.provider.id);
113
121
  const origin = key.origin !== undefined ? ` ${key.origin}` : "";
114
- return { ref, reason: `零配置:${picked.provider.id} 有 key(${key.source}${origin})` };
122
+ return {
123
+ ref,
124
+ reason: `零配置:${picked.provider.id} 有 key(${key.source}${origin});${picked.rule}`,
125
+ };
115
126
  }
116
127
  /** config 里出现的供应商 + baseUrl 来自环境变量的内置供应商。 */
117
128
  export function describeProviders(config, registry) {
@@ -171,8 +182,8 @@ function providerLines(providers) {
171
182
  }
172
183
  return lines;
173
184
  }
174
- function layersOf(level, project, effective) {
175
- const layers = [{ name: "default", label: "内置缺省", value: DEFAULT_CONFIG }];
185
+ function layersOf(level, project, effective, cli) {
186
+ const layers = [{ name: "default", label: "内置缺省", value: DISPLAY_DEFAULTS }];
176
187
  const user = loadConfigFile("config", level.userConfigPath)?.value;
177
188
  if (user !== undefined)
178
189
  layers.push({ name: "user", label: level.userConfigPath, value: user });
@@ -195,6 +206,8 @@ function layersOf(level, project, effective) {
195
206
  }
196
207
  layers.push({ name: "project", label: ".ama/config.json", value: unflatten(accepted) });
197
208
  }
209
+ if (cli !== undefined && Object.keys(cli).length > 0)
210
+ layers.push({ name: "cli", label: "命令行", value: cli });
198
211
  return layers;
199
212
  }
200
213
  function unflatten(flat) {
@@ -221,8 +234,48 @@ export function sourcesOf(effective, layers) {
221
234
  }
222
235
  return sources;
223
236
  }
237
+ /** codemode 生效模式与原因(跟随预设时写明预设与运行时 Node 是否隔离网络)。 */
238
+ export function describeCodemode(config, nodeVersion) {
239
+ const capability = detectSandboxCapability(nodeVersion);
240
+ const resolved = resolveCodemodeMode(config, capability.strict);
241
+ const sandbox = capability.strict
242
+ ? `Node ${capability.nodeMajor} 网络已隔离`
243
+ : `Node ${capability.nodeMajor} < 25 网络未隔离`;
244
+ const reason = resolved.source === "config"
245
+ ? `codemode.mode 显式设置(${sandbox})`
246
+ : `跟随预设 ${resolved.preset}(${sandbox})`;
247
+ const out = {
248
+ mode: resolved.mode,
249
+ source: resolved.source,
250
+ reason,
251
+ strict: capability.strict,
252
+ };
253
+ if (resolved.mode !== "off") {
254
+ const availability = codemodeAvailability(config.codemode?.requireStrict, capability);
255
+ if (!availability.available)
256
+ out.unavailable = availability.warning;
257
+ }
258
+ return out;
259
+ }
260
+ /** 各层里把 tools.preset 写成旧名(别名)的:`user(写的是旧名 codemode)`。 */
261
+ function presetAliasNotes(layers) {
262
+ const notes = [];
263
+ for (const layer of layers) {
264
+ const preset = layer.value?.tools?.preset;
265
+ if (preset !== undefined && Object.hasOwn(TOOLS_PRESET_ALIASES, preset))
266
+ notes.push(`${layer.name}(${layer.label})的 tools.preset 写的是旧名 ${preset},规范名 ${canonicalPreset(preset)}`);
267
+ }
268
+ return notes;
269
+ }
270
+ function choice(name, value, all) {
271
+ if (value === undefined)
272
+ return undefined;
273
+ if (all.includes(value))
274
+ return value;
275
+ throw new UsageError(`--${name} 的取值应为 ${all.join(" | ")}(收到 ${value})`);
276
+ }
224
277
  export async function runConfig(argv, io, deps) {
225
- const { positionals, values, flags } = parseSubArgs(argv, ["profile", "auth-file"], ["json"]);
278
+ const { positionals, values, flags } = parseSubArgs(argv, ["profile", "auth-file", "tools-preset", "codemode"], ["json"]);
226
279
  const action = positionals[0] ?? "show";
227
280
  if (flags.has("help")) {
228
281
  io.stdout(CONFIG_USAGE);
@@ -234,42 +287,76 @@ export async function runConfig(argv, io, deps) {
234
287
  return editConfig(io);
235
288
  if (action !== "show")
236
289
  throw new UsageError(`未知的 config 子命令:${action}`);
290
+ const cli = {};
291
+ const presetFlag = choice("tools-preset", values.get("tools-preset"), TOOLS_PRESET_INPUTS);
292
+ if (presetFlag !== undefined)
293
+ cli.toolsPreset = presetFlag;
294
+ const codemodeFlag = choice("codemode", values.get("codemode"), CODEMODE_MODES);
295
+ if (codemodeFlag !== undefined)
296
+ cli.codemode = codemodeFlag;
237
297
  const level = loadUserLevel(io, {
238
298
  profile: values.get("profile"),
239
299
  authFile: values.get("auth-file"),
240
300
  });
241
301
  const project = loadConfigFile("config", projectFile(io.cwd, CONFIG_FILE))?.value;
242
- const merged = mergeProjectAndCli(level.merged, project, undefined);
302
+ const merged = mergeProjectAndCli(level.merged, project, cli);
243
303
  const config = merged.config;
244
- const layers = layersOf(level, project, config);
245
- const sources = sourcesOf(config, layers);
304
+ const layers = layersOf(level, project, config, cliOverridesToConfig(cli));
305
+ // 展示:生效配置补上 DEFAULT_CONFIG 不写的缺省(cache、codemode.inlineBudget 等),来源 default
306
+ const shown = mergeConfig(DISPLAY_DEFAULTS, config);
307
+ const sources = sourcesOf(shown, layers);
246
308
  const registry = deps === undefined ? undefined : await buildRegistry(level, io, deps);
247
309
  const model = await describeModel(config, registry);
248
310
  const providers = describeProviders(config, registry);
311
+ const codemode = describeCodemode(config);
249
312
  const builtin = new Set(builtinTools().map((tool) => tool.name));
250
- const preset = resolvePreset({ config, available: (name) => builtin.has(name) });
313
+ if (codemode.mode !== "off" && codemode.unavailable === undefined)
314
+ builtin.add(CODEMODE_TOOL);
315
+ const preset = resolvePreset({
316
+ config,
317
+ available: (name) => builtin.has(name),
318
+ strict: codemode.strict,
319
+ });
320
+ const notes = presetAliasNotes(layers);
251
321
  const warnings = [...level.warnings, ...merged.warnings];
322
+ const rows = [...flatten(shown)].map(([path, value]) => ({
323
+ path,
324
+ value,
325
+ source: sources.get(path) ?? "default",
326
+ }));
327
+ if (config.codemode?.mode === undefined)
328
+ rows.push({
329
+ path: "codemode.mode",
330
+ value: codemode.mode,
331
+ source: `default(${codemode.reason})`,
332
+ });
333
+ rows.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
252
334
  if (flags.has("json")) {
253
- const entries = [...flatten(config)].map(([path, value]) => ({
335
+ const entries = rows.map(({ path, value, source }) => ({
254
336
  path,
255
337
  value: path.endsWith(".apiKey") ? display(path, value) : value,
256
- source: sources.get(path),
338
+ source,
257
339
  }));
258
- io.stdout(`${JSON.stringify({ model, providers, tools: preset.builtin, codemode: effectiveCodemodeMode(config), entries, layers: layers.map((l) => ({ name: l.name, file: l.label })), warnings }, null, 2)}\n`);
340
+ io.stdout(`${JSON.stringify({ model, providers, tools: preset.builtin, preset: preset.preset, codemode, entries, layers: layers.map((l) => ({ name: l.name, file: l.label })), notes, warnings }, null, 2)}\n`);
259
341
  return ExitCode.Ok;
260
342
  }
261
- const lines = ["生效配置(来源:default 内置 ← user ← profile ← project 只能收紧)"];
343
+ const lines = ["生效配置(来源:default 内置 ← user ← profile ← project 只能收紧 ← cli)"];
262
344
  for (const layer of layers.slice(1))
263
345
  lines.push(` ${layer.name}:${layer.label}`);
264
346
  lines.push("");
265
- const rows = [...flatten(config)].map(([path, value]) => [`${path} = ${display(path, value)}`, sources.get(path) ?? "default"]);
266
- const width = Math.min(60, Math.max(...rows.map(([text]) => text.length)));
267
- for (const [text, source] of rows)
268
- lines.push(` ${text.padEnd(width)} ${source}`);
347
+ const texts = rows.map((row) => [`${row.path} = ${display(row.path, row.value)}`, row.source]);
348
+ const width = Math.min(60, Math.max(...texts.map(([text]) => (text ?? "").length)));
349
+ for (const [text, source] of texts)
350
+ lines.push(` ${(text ?? "").padEnd(width)} ${source}`);
269
351
  lines.push(...providerLines(providers));
270
352
  lines.push("");
271
353
  lines.push(`模型:${model.ref ?? "(无)"} ${model.reason}`);
272
354
  lines.push(`工具:${preset.builtin.join(", ")}(预设 ${preset.preset},codemode ${preset.codemode})`);
355
+ lines.push(`codemode:${codemode.mode} ${codemode.reason}`);
356
+ if (codemode.unavailable !== undefined)
357
+ lines.push(` ${codemode.unavailable}`);
358
+ for (const note of notes)
359
+ lines.push(`提示:${note}`);
273
360
  for (const warning of [...warnings, ...preset.warnings])
274
361
  lines.push(`警告:${warning}`);
275
362
  io.stdout(`${lines.join("\n")}\n`);