@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
@@ -1,15 +1,24 @@
1
1
  /**
2
2
  * print 模式(`ama -p`,设计 §11.1 第 15–16 步)。[B6]
3
3
  *
4
- * - 提示 = 位置参数 + stdin 管道(stdin 非 TTY 时读到 EOF),两者都有时空一行拼接;都没有 → 用法错误 2。
4
+ * - 提示 = 位置参数 + stdin,两者都有时空一行拼接;都没有 → 用法错误 2。stdin 的读法:
5
+ * - 没有提示参数,或显式写了位置参数 `-`:读到 EOF(超过 3 s 时 stderr 提示一次正在等待);
6
+ * - 有提示参数且 stdin 是管道(shell 管道或父进程留的管道):等首字节,`AMA_STDIN_WAIT_MS`
7
+ * (缺省 2000,0 = 不等)内一个字节都没有 → 忽略 stdin 并在 stderr 提示;收到首字节后读到 EOF;
8
+ * - stdin 是普通文件 / 空设备:直接读(不会卡);TTY 或 `--no-stdin`:不读。
5
9
  * - 图片:`--image`(可重复)与提示里的 `@图片路径` / 图片文件路径作为附件(modes/image-input.ts);
6
10
  * 显式附件遇到当前模型不收图片、文件不存在或超限 → 用法错误 2,不发请求。
7
11
  * - `--output-format text`(缺省):运行结束后输出最后一条助手文本;`json`:一个结果对象(文本、
8
12
  * 停止原因、用量、缓存命中率、[W3-C2] `cache` 统计(同 `get_session_stats.cache`)、全部条目);
9
13
  * `stream-json`:每个会话事件一行(线上形状同 RPC,含 `cache_miss` / `cache_warm` /
10
14
  * `context_pressure`)。
11
- * - 无人值守:ask → deny(bootstrap 已按 print 设 unattended)。
12
- * - 退出码:最终助手消息 `error / aborted` 或提示被拒 → 1;SIGINT 130、SIGTERM 143(先 abort)。
15
+ * - 重试:text / json 格式在 stderr 打一行 `↻ 重试 n/m`(stream-json 里本来就有事件),等待期间不再无声。
16
+ * - 无人值守:ask → deny(bootstrap 已按 print 设 unattended)。被拒的调用(`tool_execution_end`
17
+ * 带 `denied`)在 stderr 汇总一行(工具 ×次数、首个原因、放行办法),json 结果带 `deniedTools`。
18
+ * - `--max-turns N`:到达上限时若最后一条助手消息停在 toolUse(还有活没做完)→ stderr 一行、
19
+ * json 带 `maxTurnsReached`、退出码 1。
20
+ * - 退出码:最终助手消息 `error / aborted` 或提示被拒 → 1;有工具调用被拒 → 7;SIGINT 130、
21
+ * SIGTERM 143(先 abort)。
13
22
  */
14
23
  import { currentSession } from "../../cli/compose-session.js";
15
24
  import { ExitCode } from "../../cli/exit-codes.js";
@@ -20,10 +29,70 @@ export function joinPrompt(argument, piped) {
20
29
  const parts = [argument ?? "", piped.replace(/\s+$/, "")].filter((p) => p.trim() !== "");
21
30
  return parts.join("\n\n");
22
31
  }
32
+ /** 等 stdin 多久后提示一次「正在等待」。 */
33
+ export const STDIN_WAIT_HINT_MS = 3_000;
34
+ /** 有提示参数时等管道首字节的缺省上限;`AMA_STDIN_WAIT_MS` 覆盖,0 = 不等待。 */
35
+ export const STDIN_FIRST_BYTE_MS = 2_000;
36
+ export function stdinWaitMs(env) {
37
+ const raw = env["AMA_STDIN_WAIT_MS"];
38
+ if (raw === undefined || raw.trim() === "")
39
+ return STDIN_FIRST_BYTE_MS;
40
+ const value = Number(raw);
41
+ return Number.isFinite(value) && value >= 0 ? value : STDIN_FIRST_BYTE_MS;
42
+ }
43
+ /** io 没有首字节读取时的退化:整段读取与超时赛跑。 */
44
+ function readWithin(io, ms) {
45
+ if (io.readStdinFirstByte !== undefined)
46
+ return io.readStdinFirstByte(ms);
47
+ return new Promise((resolve, reject) => {
48
+ const timer = setTimeout(() => resolve(undefined), ms);
49
+ io.readStdin().then((text) => {
50
+ clearTimeout(timer);
51
+ resolve(text);
52
+ }, (error) => {
53
+ clearTimeout(timer);
54
+ reject(error);
55
+ });
56
+ });
57
+ }
58
+ /**
59
+ * 决定读不读 stdin(规则见文件头)。`mode`:`auto` 按提示参数与 stdin 类型决定,`explicit`
60
+ * (位置参数 `-`)一直等到 EOF,`off`(`--no-stdin`)不读。
61
+ */
62
+ export async function readPromptStdin(io, prompt, mode, hintMs = STDIN_WAIT_HINT_MS) {
63
+ if (io.stdinIsTTY || mode === "off")
64
+ return "";
65
+ const kind = io.stdinKind?.() ?? "other";
66
+ const safe = kind === "file" || kind === "null";
67
+ if (prompt !== undefined && prompt !== "" && mode === "auto" && !safe) {
68
+ const waitMs = stdinWaitMs(io.env);
69
+ if (waitMs === 0)
70
+ return "";
71
+ const text = await readWithin(io, waitMs);
72
+ if (text === undefined) {
73
+ io.stderr(`ama: 未在 ${formatSeconds(waitMs)}内收到管道输入,已忽略;需要等待请在末尾加 -\n`);
74
+ return "";
75
+ }
76
+ return text;
77
+ }
78
+ const timer = safe
79
+ ? undefined
80
+ : setTimeout(() => io.stderr("ama: 正在等待 stdin 输入结束(Ctrl+D 结束;提示也可以直接写成参数)…\n"), hintMs);
81
+ try {
82
+ return await io.readStdin();
83
+ }
84
+ finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
88
+ function formatSeconds(ms) {
89
+ return ms % 1000 === 0 ? `${ms / 1000} 秒` : `${ms} 毫秒`;
90
+ }
23
91
  export async function runPrintMode(runtime, context) {
24
92
  const { io } = context;
25
93
  const format = context.args.outputFormat ?? "text";
26
- const piped = io.stdinIsTTY ? "" : await io.readStdin();
94
+ const stdinMode = context.args.noStdin ? "off" : context.args.stdin ? "explicit" : "auto";
95
+ const piped = await readPromptStdin(io, context.prompt, stdinMode);
27
96
  const prompt = joinPrompt(context.prompt, piped);
28
97
  if (prompt === "") {
29
98
  io.stderr("ama: -p 需要提示(位置参数或 stdin 管道)\n");
@@ -38,9 +107,19 @@ export async function runPrintMode(runtime, context) {
38
107
  io.stderr(`ama: ${errorText(error)}\n`);
39
108
  return ExitCode.Usage;
40
109
  }
41
- const unsubscribe = format === "stream-json"
42
- ? session.subscribe((event) => io.stdout(`${toJsonLine(toWireEvent(event))}\n`))
43
- : () => undefined;
110
+ const denied = [];
111
+ const unsubscribe = session.subscribe((event) => {
112
+ if (event.type === "tool_execution_end" && event.denied === true)
113
+ denied.push({
114
+ toolCallId: event.toolCallId,
115
+ toolName: event.toolName,
116
+ reason: textOf(event),
117
+ });
118
+ if (format === "stream-json")
119
+ io.stdout(`${toJsonLine(toWireEvent(event))}\n`);
120
+ else if (event.type === "auto_retry_start")
121
+ io.stderr(`ama: ↻ 重试 ${event.attempt}/${event.maxAttempts}(${Math.round(event.delayMs / 1000)}s 后):${event.errorMessage}\n`);
122
+ });
44
123
  let signalled;
45
124
  const offSignals = onTerminationSignals((code) => {
46
125
  signalled ??= code;
@@ -65,6 +144,9 @@ export async function runPrintMode(runtime, context) {
65
144
  }
66
145
  const last = lastAssistant(session);
67
146
  const text = session.getLastAssistantText() ?? "";
147
+ // 回合上限在工具执行之后结束运行:最后一条助手消息停在 toolUse 即「没做完」
148
+ const maxTurns = context.args.maxTurns;
149
+ const turnsExhausted = maxTurns !== undefined && failure === undefined && last?.stopReason === "toolUse";
68
150
  const stopReason = failure !== undefined ? "error" : (last?.stopReason ?? "stop");
69
151
  if (format === "text") {
70
152
  // 部分模型在正文前多发空行:只去前导空行,保留首行缩进;json / stream-json 原样。
@@ -88,6 +170,8 @@ export async function runPrintMode(runtime, context) {
88
170
  cost: stats.cost,
89
171
  cacheHitRate: stats.cacheHitRate,
90
172
  ...(stats.cache !== undefined ? { cache: stats.cache } : {}),
173
+ ...(denied.length > 0 ? { deniedTools: denied } : {}),
174
+ ...(turnsExhausted ? { maxTurnsReached: true } : {}),
91
175
  entries: session.entries,
92
176
  })}\n`);
93
177
  }
@@ -103,5 +187,30 @@ export async function runPrintMode(runtime, context) {
103
187
  io.stderr(`ama: ${last.errorMessage ?? "模型调用失败"}\n`);
104
188
  return ExitCode.RuntimeError;
105
189
  }
190
+ if (denied.length > 0)
191
+ io.stderr(`${describeDenied(denied)}\n`);
192
+ if (turnsExhausted) {
193
+ io.stderr(`ama: 已达到 --max-turns ${maxTurns},运行在完成前结束\n`);
194
+ return ExitCode.RuntimeError;
195
+ }
196
+ if (denied.length > 0)
197
+ return ExitCode.ToolDenied;
106
198
  return ExitCode.Ok;
107
199
  }
200
+ function textOf(event) {
201
+ const content = event.result.content;
202
+ const text = typeof content === "string"
203
+ ? content
204
+ : content.map((b) => (b.type === "text" ? b.text : "")).join("");
205
+ return text.split("\n")[0]?.trim().slice(0, 200) ?? "";
206
+ }
207
+ /** stderr 一行:被拒的工具 ×次数、首个原因、放行办法。 */
208
+ export function describeDenied(denied) {
209
+ const counts = new Map();
210
+ for (const item of denied)
211
+ counts.set(item.toolName, (counts.get(item.toolName) ?? 0) + 1);
212
+ const tools = [...counts].map(([name, n]) => `${name} ×${n}`).join("、");
213
+ const reason = denied[0]?.reason ?? "";
214
+ return (`ama: ${denied.length} 次工具调用被拒:${tools}${reason !== "" ? `(${reason})` : ""};` +
215
+ "-p 没有人审批,需要放行时用 --permission-mode auto-edit|auto 或 --allow <规则>");
216
+ }
@@ -14,6 +14,8 @@ import { AgentSessionImpl } from "../../agent/session.js";
14
14
  import { getSupportedLevels } from "../../ai/thinking.js";
15
15
  import { switchSession } from "../../cli/compose-session.js";
16
16
  import { AmaError } from "../../errors.js";
17
+ import { isPermissionMode } from "../../permissions/modes.js";
18
+ import { PERMISSION_MODES_STRICT_FIRST } from "../../permissions/types.js";
17
19
  import { BUILTIN_COMMANDS } from "../commands-core.js";
18
20
  /** 审批 broker:等客户端 `permission_response`。 */
19
21
  export class RpcApprovals {
@@ -254,6 +256,9 @@ export const handlers = {
254
256
  };
255
257
  },
256
258
  set_permission_mode: async (p, ctx) => {
259
+ if (!isPermissionMode(p.mode)) {
260
+ throw new AmaError("invalid_arguments", `mode 应为 ${PERMISSION_MODES_STRICT_FIRST.join(" | ")}`);
261
+ }
257
262
  ctx.session().setPermissionMode(p.mode);
258
263
  return { mode: p.mode };
259
264
  },
@@ -0,0 +1,60 @@
1
+ /**
2
+ * auto 模式下 bash 的规则层与静态判定(§7.4,docs/permissions.md)。
3
+ *
4
+ * 规则层(命中即询问,不调模型)——外层与每层嵌套命令(`sh -c`、`eval`、`xargs`、`find -exec`,
5
+ * 见 dangerous.ts)的每一段都查:
6
+ * - 网络命令({@link networkReason}):curl / wget / ssh / git push|pull|fetch|clone / npm install / npx …
7
+ * - 删除与回退({@link destructiveReason}):rm -r|-f、find -delete、git clean / restore / checkout -- …
8
+ * - 写入目标:重定向(`>`、`>>`、`&>`)、tee、cp / mv / mkdir / touch / ln / chmod 等的目标在项目外或受保护;
9
+ * - 参数里出现机密路径(`cat .env`、`< ~/.ssh/id_rsa`、`--env-file=.env`)。
10
+ *
11
+ * 静态判定(全部满足即放行):每一段都命中安全名单({@link AUTO_SAFE_COMMANDS} + 用户追加),整条命令
12
+ * 没有命令替换、参数没有变量展开 `$X`、没有可能匹配点文件的通配、没有超深嵌套。
13
+ *
14
+ * 分段与分词复用 rules.ts / dangerous.ts(引号内不切、剥环境赋值与 env / nohup / time 等前缀)。
15
+ */
16
+ export interface SafeCommandSpec {
17
+ /** 词前缀:`["git", "status"]` 命中 `git status -s`。git 的全局选项只允许 `-C <dir>` 与 `--no-pager`。 */
18
+ words: readonly string[];
19
+ /** 前缀之后任一参数命中即不安全。 */
20
+ forbid?: RegExp;
21
+ /** 前缀之后必须出现的参数。 */
22
+ require?: string;
23
+ }
24
+ /** 初始安全名单(每条在 auto-safe.test.ts 有正反例)。 */
25
+ export declare const AUTO_SAFE_COMMANDS: readonly SafeCommandSpec[];
26
+ /**
27
+ * 用户追加的安全命令(`permission.autoSafeCommands`):不含 `*` 按词前缀;含 `*` 按通配匹配整段
28
+ * (剥过前缀、重定向之后的命令文本)。
29
+ */
30
+ export declare function extraSafeMatches(argv: readonly string[], entry: string): boolean;
31
+ /** 网络命令:返回说明;不是返回 undefined。 */
32
+ export declare function networkReason(argv: readonly string[]): string | undefined;
33
+ /** 删除与丢弃改动的命令:返回说明;不是返回 undefined。 */
34
+ export declare function destructiveReason(argv: readonly string[]): string | undefined;
35
+ export interface Redirects {
36
+ /** 输出重定向目标(`>`、`>>`、`>|`、`&>`)。 */
37
+ outputs: string[];
38
+ /** 输入重定向来源(`<`)。 */
39
+ inputs: string[];
40
+ /** 去掉重定向之后的段文本。 */
41
+ rest: string;
42
+ }
43
+ /** 从一段命令里取出重定向(引号内不算);`2>&1`、`>&2` 是复制描述符,没有目标。 */
44
+ export declare function extractRedirects(segment: string): Redirects;
45
+ export interface BashAutoOptions {
46
+ cwd: string;
47
+ projectRoot: string;
48
+ /** `permission.autoSafeCommands`。 */
49
+ extraSafe?: readonly string[];
50
+ }
51
+ export interface BashAutoAnalysis {
52
+ /** 规则层要询问的原因;undefined = 规则层不管。 */
53
+ ask?: string;
54
+ /** 静态判定放行。 */
55
+ safe: boolean;
56
+ /** 不安全(交给分类器)的原因,审计用。 */
57
+ reason: string;
58
+ }
59
+ /** auto 模式下一条 bash 命令的规则层与静态判定结论。 */
60
+ export declare function analyzeBashForAuto(command: string, options: BashAutoOptions): BashAutoAnalysis;