@armadra/agent 0.2.1 → 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.
- package/CHANGELOG.md +99 -0
- package/README.md +142 -49
- package/dist/agent/retry.d.ts +1 -1
- package/dist/agent/retry.js +2 -1
- package/dist/agent/session-cache.js +6 -3
- package/dist/agent/session-classifier.d.ts +18 -0
- package/dist/agent/session-classifier.js +103 -0
- package/dist/agent/session-core.d.ts +13 -0
- package/dist/agent/session-state.js +2 -1
- package/dist/agent/session-sync.js +9 -2
- package/dist/agent/session-tools.js +24 -3
- package/dist/agent/session.d.ts +3 -0
- package/dist/agent/session.js +20 -10
- package/dist/agent/tool-runner.js +15 -2
- package/dist/agent/types.d.ts +9 -1
- package/dist/ai/apis/anthropic-messages.js +3 -2
- package/dist/ai/apis/google-generative-ai.js +3 -2
- package/dist/ai/apis/openai-completions.js +3 -2
- package/dist/ai/apis/openai-responses.js +5 -3
- package/dist/ai/cache/fingerprint.d.ts +1 -1
- package/dist/ai/cache/fingerprint.js +1 -1
- package/dist/ai/cache/reporting.js +2 -1
- package/dist/ai/http.d.ts +28 -6
- package/dist/ai/http.js +41 -8
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +42 -4
- package/dist/ai/providers/registry.js +195 -34
- package/dist/ai/providers/suggest.d.ts +18 -0
- package/dist/ai/providers/suggest.js +72 -0
- package/dist/ai/sse.d.ts +6 -2
- package/dist/ai/sse.js +22 -2
- package/dist/ai/types.d.ts +30 -3
- package/dist/bundle/ama.cjs +17893 -10698
- package/dist/cli/args.d.ts +23 -5
- package/dist/cli/args.js +113 -13
- package/dist/cli/bootstrap.js +28 -3
- package/dist/cli/codemode-notice.d.ts +20 -0
- package/dist/cli/codemode-notice.js +55 -0
- package/dist/cli/compose-providers.js +3 -0
- package/dist/cli/compose-session.d.ts +8 -0
- package/dist/cli/compose-session.js +32 -1
- package/dist/cli/compose-store.d.ts +1 -1
- package/dist/cli/compose-store.js +3 -1
- package/dist/cli/compose.d.ts +17 -5
- package/dist/cli/compose.js +53 -19
- package/dist/cli/default-model.d.ts +38 -1
- package/dist/cli/default-model.js +96 -9
- package/dist/cli/deps.d.ts +38 -0
- package/dist/cli/exit-codes.d.ts +2 -0
- package/dist/cli/exit-codes.js +3 -0
- package/dist/cli/fake-visibility.d.ts +13 -0
- package/dist/cli/fake-visibility.js +26 -0
- package/dist/cli/from-prompt.d.ts +18 -0
- package/dist/cli/from-prompt.js +49 -0
- package/dist/cli/main.d.ts +10 -2
- package/dist/cli/main.js +88 -2
- package/dist/cli/proxy.d.ts +51 -0
- package/dist/cli/proxy.js +135 -0
- package/dist/cli/startup-screen.d.ts +29 -0
- package/dist/cli/startup-screen.js +47 -0
- package/dist/cli/startup-steps.d.ts +1 -1
- package/dist/cli/startup-steps.js +20 -13
- package/dist/cli/subcommands/config.d.ts +29 -4
- package/dist/cli/subcommands/config.js +188 -25
- package/dist/cli/subcommands/context.js +5 -1
- package/dist/cli/subcommands/doctor.js +12 -1
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +23 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.js +1 -1
- package/dist/cli/subcommands/models-discover.d.ts +18 -6
- package/dist/cli/subcommands/models-discover.js +87 -52
- package/dist/cli/subcommands/models.js +26 -18
- package/dist/cli/subcommands/probe-runner.d.ts +96 -0
- package/dist/cli/subcommands/probe-runner.js +264 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -0
- package/dist/cli/subcommands/providers-probe.d.ts +34 -0
- package/dist/cli/subcommands/providers-probe.js +87 -0
- package/dist/cli/subcommands/providers.d.ts +28 -0
- package/dist/cli/subcommands/providers.js +436 -0
- package/dist/cli/subcommands/sessions-export.d.ts +10 -0
- package/dist/cli/subcommands/sessions-export.js +59 -0
- package/dist/cli/subcommands/sessions-search.d.ts +13 -0
- package/dist/cli/subcommands/sessions-search.js +103 -0
- package/dist/cli/subcommands/sessions.d.ts +3 -2
- package/dist/cli/subcommands/sessions.js +22 -1
- package/dist/cli/subcommands/stats.d.ts +17 -0
- package/dist/cli/subcommands/stats.js +198 -0
- package/dist/cli/system-prompt-arg.d.ts +11 -0
- package/dist/cli/system-prompt-arg.js +34 -0
- package/dist/codemode/modes.d.ts +4 -11
- package/dist/codemode/modes.js +5 -27
- package/dist/codemode/tool.d.ts +18 -14
- package/dist/codemode/tool.js +71 -28
- package/dist/config/init.d.ts +34 -0
- package/dist/config/init.js +99 -0
- package/dist/config/json-schema.d.ts +15 -0
- package/dist/config/json-schema.js +215 -0
- package/dist/config/key-docs.d.ts +22 -0
- package/dist/config/key-docs.js +105 -0
- package/dist/config/merge.d.ts +9 -8
- package/dist/config/merge.js +22 -7
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +97 -15
- package/dist/config/types.d.ts +57 -11
- package/dist/config/types.js +19 -1
- package/dist/modes/commands-core.js +6 -5
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- package/dist/modes/interactive/approval-dialog.d.ts +21 -5
- package/dist/modes/interactive/approval-dialog.js +106 -27
- package/dist/modes/interactive/commands.d.ts +11 -2
- package/dist/modes/interactive/commands.js +58 -19
- package/dist/modes/interactive/interactive-mode.d.ts +2 -1
- package/dist/modes/interactive/interactive-mode.js +70 -82
- package/dist/modes/interactive/key-dispatch.d.ts +3 -1
- package/dist/modes/interactive/key-dispatch.js +5 -6
- package/dist/modes/interactive/line/line-mode.d.ts +1 -0
- package/dist/modes/interactive/line/line-mode.js +9 -5
- package/dist/modes/interactive/line/line-render.d.ts +4 -0
- package/dist/modes/interactive/line/line-render.js +28 -6
- package/dist/modes/interactive/message-view.d.ts +48 -9
- package/dist/modes/interactive/message-view.js +238 -44
- package/dist/modes/interactive/panels.d.ts +18 -0
- package/dist/modes/interactive/panels.js +143 -0
- package/dist/modes/interactive/pickers.d.ts +23 -2
- package/dist/modes/interactive/pickers.js +48 -15
- package/dist/modes/interactive/run-indicator.d.ts +51 -0
- package/dist/modes/interactive/run-indicator.js +189 -0
- package/dist/modes/interactive/startup-header.d.ts +40 -0
- package/dist/modes/interactive/startup-header.js +169 -0
- package/dist/modes/interactive/startup-ui.d.ts +7 -2
- package/dist/modes/interactive/startup-ui.js +41 -11
- package/dist/modes/interactive/status-bar.d.ts +18 -15
- package/dist/modes/interactive/status-bar.js +98 -56
- package/dist/modes/interactive/tool-summary.d.ts +46 -0
- package/dist/modes/interactive/tool-summary.js +218 -0
- package/dist/modes/interactive/tool-view.d.ts +48 -15
- package/dist/modes/interactive/tool-view.js +203 -145
- package/dist/modes/print/print-mode.d.ts +33 -4
- package/dist/modes/print/print-mode.js +128 -8
- package/dist/modes/rpc/commands.js +12 -2
- package/dist/permissions/auto-safe.d.ts +60 -0
- package/dist/permissions/auto-safe.js +529 -0
- package/dist/permissions/classifier.d.ts +64 -0
- package/dist/permissions/classifier.js +184 -0
- package/dist/permissions/dangerous.d.ts +5 -0
- package/dist/permissions/dangerous.js +1 -1
- package/dist/permissions/modes.d.ts +30 -0
- package/dist/permissions/modes.js +78 -0
- package/dist/permissions/pipeline.d.ts +31 -4
- package/dist/permissions/pipeline.js +196 -6
- package/dist/permissions/protected.d.ts +19 -0
- package/dist/permissions/protected.js +74 -0
- package/dist/permissions/rules.js +3 -0
- package/dist/permissions/types.d.ts +50 -3
- package/dist/permissions/types.js +3 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/sdk.d.ts +9 -3
- package/dist/sdk.js +10 -2
- package/dist/session/export.d.ts +32 -0
- package/dist/session/export.js +187 -0
- package/dist/session/projection.js +5 -1
- package/dist/session/redact.d.ts +15 -0
- package/dist/session/redact.js +55 -0
- package/dist/session/reuse.d.ts +33 -0
- package/dist/session/reuse.js +86 -0
- package/dist/session/scan.d.ts +34 -0
- package/dist/session/scan.js +140 -0
- package/dist/session/search.d.ts +52 -0
- package/dist/session/search.js +211 -0
- package/dist/session/stats-aggregate.d.ts +63 -0
- package/dist/session/stats-aggregate.js +163 -0
- package/dist/session/stats-index.d.ts +26 -0
- package/dist/session/stats-index.js +91 -0
- package/dist/session/stats-scan.d.ts +54 -0
- package/dist/session/stats-scan.js +236 -0
- package/dist/session/types.d.ts +2 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/presets.d.ts +35 -8
- package/dist/tools/presets.js +56 -17
- package/dist/tools/read.d.ts +4 -8
- package/dist/tools/read.js +15 -64
- package/dist/tui/component.d.ts +8 -2
- package/dist/tui/component.js +3 -1
- package/dist/tui/components/box.d.ts +6 -1
- package/dist/tui/components/box.js +16 -6
- package/dist/tui/components/card.d.ts +23 -0
- package/dist/tui/components/card.js +37 -0
- package/dist/tui/components/editor-history.d.ts +6 -0
- package/dist/tui/components/editor-history.js +45 -0
- package/dist/tui/components/editor-paste.d.ts +1 -1
- package/dist/tui/components/editor-paste.js +4 -4
- package/dist/tui/components/editor.d.ts +11 -5
- package/dist/tui/components/editor.js +52 -58
- package/dist/tui/components/key-value.d.ts +3 -0
- package/dist/tui/components/key-value.js +16 -6
- package/dist/tui/components/loader.d.ts +37 -7
- package/dist/tui/components/loader.js +84 -21
- package/dist/tui/components/markdown.d.ts +5 -1
- package/dist/tui/components/markdown.js +45 -21
- package/dist/tui/components/meter.d.ts +3 -3
- package/dist/tui/components/meter.js +13 -11
- package/dist/tui/components/select-list.d.ts +23 -1
- package/dist/tui/components/select-list.js +76 -13
- package/dist/tui/glyphs.d.ts +68 -0
- package/dist/tui/glyphs.js +114 -0
- package/dist/tui/theme.d.ts +30 -6
- package/dist/tui/theme.js +103 -17
- package/dist/tui.d.ts +4 -2
- package/dist/tui.js +3 -1
- package/docs/codemode.md +23 -9
- package/docs/hooks.md +10 -10
- package/docs/permissions.md +148 -0
- package/docs/providers.md +241 -3
- package/docs/rpc.md +38 -38
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +134 -0
- package/docs/tui.md +140 -61
- package/package.json +3 -1
|
@@ -1,35 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* print 模式(`ama -p`,设计 §11.1 第 15–16 步)。[B6]
|
|
3
3
|
*
|
|
4
|
-
* - 提示 = 位置参数 + stdin
|
|
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`:不读。
|
|
9
|
+
* - 图片:`--image`(可重复)与提示里的 `@图片路径` / 图片文件路径作为附件(modes/image-input.ts);
|
|
10
|
+
* 显式附件遇到当前模型不收图片、文件不存在或超限 → 用法错误 2,不发请求。
|
|
5
11
|
* - `--output-format text`(缺省):运行结束后输出最后一条助手文本;`json`:一个结果对象(文本、
|
|
6
12
|
* 停止原因、用量、缓存命中率、[W3-C2] `cache` 统计(同 `get_session_stats.cache`)、全部条目);
|
|
7
13
|
* `stream-json`:每个会话事件一行(线上形状同 RPC,含 `cache_miss` / `cache_warm` /
|
|
8
14
|
* `context_pressure`)。
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
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)。
|
|
11
22
|
*/
|
|
12
23
|
import { currentSession } from "../../cli/compose-session.js";
|
|
13
24
|
import { ExitCode } from "../../cli/exit-codes.js";
|
|
14
25
|
import { errorText, lastAssistant, onStdoutClosed, onTerminationSignals } from "../shared.js";
|
|
15
26
|
import { toJsonLine, toWireEvent } from "./json-event.js";
|
|
27
|
+
import { promptImages, sessionModel } from "../image-input.js";
|
|
16
28
|
export function joinPrompt(argument, piped) {
|
|
17
29
|
const parts = [argument ?? "", piped.replace(/\s+$/, "")].filter((p) => p.trim() !== "");
|
|
18
30
|
return parts.join("\n\n");
|
|
19
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
|
+
}
|
|
20
91
|
export async function runPrintMode(runtime, context) {
|
|
21
92
|
const { io } = context;
|
|
22
93
|
const format = context.args.outputFormat ?? "text";
|
|
23
|
-
const
|
|
94
|
+
const stdinMode = context.args.noStdin ? "off" : context.args.stdin ? "explicit" : "auto";
|
|
95
|
+
const piped = await readPromptStdin(io, context.prompt, stdinMode);
|
|
24
96
|
const prompt = joinPrompt(context.prompt, piped);
|
|
25
97
|
if (prompt === "") {
|
|
26
98
|
io.stderr("ama: -p 需要提示(位置参数或 stdin 管道)\n");
|
|
27
99
|
return ExitCode.Usage;
|
|
28
100
|
}
|
|
29
101
|
const session = currentSession(runtime);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
102
|
+
let images;
|
|
103
|
+
try {
|
|
104
|
+
images = await promptImages(prompt, context.args.images, io.cwd, sessionModel(runtime.providers, session));
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
io.stderr(`ama: ${errorText(error)}\n`);
|
|
108
|
+
return ExitCode.Usage;
|
|
109
|
+
}
|
|
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
|
+
});
|
|
33
123
|
let signalled;
|
|
34
124
|
const offSignals = onTerminationSignals((code) => {
|
|
35
125
|
signalled ??= code;
|
|
@@ -42,7 +132,7 @@ export async function runPrintMode(runtime, context) {
|
|
|
42
132
|
});
|
|
43
133
|
let failure;
|
|
44
134
|
try {
|
|
45
|
-
await session.prompt(prompt);
|
|
135
|
+
await session.prompt(prompt, images.length > 0 ? { images } : {});
|
|
46
136
|
}
|
|
47
137
|
catch (error) {
|
|
48
138
|
failure = errorText(error);
|
|
@@ -54,6 +144,9 @@ export async function runPrintMode(runtime, context) {
|
|
|
54
144
|
}
|
|
55
145
|
const last = lastAssistant(session);
|
|
56
146
|
const text = session.getLastAssistantText() ?? "";
|
|
147
|
+
// 回合上限在工具执行之后结束运行:最后一条助手消息停在 toolUse 即「没做完」
|
|
148
|
+
const maxTurns = context.args.maxTurns;
|
|
149
|
+
const turnsExhausted = maxTurns !== undefined && failure === undefined && last?.stopReason === "toolUse";
|
|
57
150
|
const stopReason = failure !== undefined ? "error" : (last?.stopReason ?? "stop");
|
|
58
151
|
if (format === "text") {
|
|
59
152
|
// 部分模型在正文前多发空行:只去前导空行,保留首行缩进;json / stream-json 原样。
|
|
@@ -77,6 +170,8 @@ export async function runPrintMode(runtime, context) {
|
|
|
77
170
|
cost: stats.cost,
|
|
78
171
|
cacheHitRate: stats.cacheHitRate,
|
|
79
172
|
...(stats.cache !== undefined ? { cache: stats.cache } : {}),
|
|
173
|
+
...(denied.length > 0 ? { deniedTools: denied } : {}),
|
|
174
|
+
...(turnsExhausted ? { maxTurnsReached: true } : {}),
|
|
80
175
|
entries: session.entries,
|
|
81
176
|
})}\n`);
|
|
82
177
|
}
|
|
@@ -92,5 +187,30 @@ export async function runPrintMode(runtime, context) {
|
|
|
92
187
|
io.stderr(`ama: ${last.errorMessage ?? "模型调用失败"}\n`);
|
|
93
188
|
return ExitCode.RuntimeError;
|
|
94
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;
|
|
95
198
|
return ExitCode.Ok;
|
|
96
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
|
+
}
|
|
@@ -9,10 +9,13 @@
|
|
|
9
9
|
* `permission_response` 按 requestId 作答;先到的回答暂存,等 broker 被问到时取用。
|
|
10
10
|
* - 密钥不离开进程:`get_available_models` 只回 `hasKey` 与 `keySource`。
|
|
11
11
|
*/
|
|
12
|
+
import { formatModelRef } from "../../ai/providers/channels.js";
|
|
12
13
|
import { AgentSessionImpl } from "../../agent/session.js";
|
|
13
14
|
import { getSupportedLevels } from "../../ai/thinking.js";
|
|
14
15
|
import { switchSession } from "../../cli/compose-session.js";
|
|
15
16
|
import { AmaError } from "../../errors.js";
|
|
17
|
+
import { isPermissionMode } from "../../permissions/modes.js";
|
|
18
|
+
import { PERMISSION_MODES_STRICT_FIRST } from "../../permissions/types.js";
|
|
16
19
|
import { BUILTIN_COMMANDS } from "../commands-core.js";
|
|
17
20
|
/** 审批 broker:等客户端 `permission_response`。 */
|
|
18
21
|
export class RpcApprovals {
|
|
@@ -146,7 +149,11 @@ export const handlers = {
|
|
|
146
149
|
// [W3-C2] 含 `cache`(三态、最近 / 会话命中率、未命中、保温、余量;第三波 §1.10)。
|
|
147
150
|
get_session_stats: async (_p, ctx) => ctx.session().getStats(),
|
|
148
151
|
set_model: async (p, ctx) => {
|
|
149
|
-
await ctx.session().setModel(
|
|
152
|
+
await ctx.session().setModel(formatModelRef({
|
|
153
|
+
provider: p.provider,
|
|
154
|
+
id: p.modelId,
|
|
155
|
+
...(p.channel !== undefined ? { channel: p.channel } : {}),
|
|
156
|
+
}));
|
|
150
157
|
return { model: ctx.session().state.model };
|
|
151
158
|
},
|
|
152
159
|
get_available_models: async (_p, ctx) => ({ models: await availableModels(ctx.runtime) }),
|
|
@@ -156,7 +163,7 @@ export const handlers = {
|
|
|
156
163
|
},
|
|
157
164
|
get_available_thinking_levels: async (_p, ctx) => {
|
|
158
165
|
const ref = ctx.session().state.model;
|
|
159
|
-
const found = ref === undefined ? undefined : ctx.runtime.providers.findModel(
|
|
166
|
+
const found = ref === undefined ? undefined : ctx.runtime.providers.findModel(formatModelRef(ref));
|
|
160
167
|
return { levels: found?.ok === true ? getSupportedLevels(found.model) : ["off"] };
|
|
161
168
|
},
|
|
162
169
|
set_steering_mode: async (p, ctx) => {
|
|
@@ -249,6 +256,9 @@ export const handlers = {
|
|
|
249
256
|
};
|
|
250
257
|
},
|
|
251
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
|
+
}
|
|
252
262
|
ctx.session().setPermissionMode(p.mode);
|
|
253
263
|
return { mode: p.mode };
|
|
254
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;
|