agim-cli 1.0.14 → 1.1.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 +126 -0
- package/README.md +8 -8
- package/README.zh-CN.md +12 -12
- package/dist/cli-ui/config-wizard.js +1 -1
- package/dist/cli-ui/config-wizard.js.map +1 -1
- package/dist/cli-ui/i18n.d.ts +5 -5
- package/dist/cli-ui/i18n.d.ts.map +1 -1
- package/dist/cli-ui/i18n.js +6 -6
- package/dist/cli-ui/i18n.js.map +1 -1
- package/dist/cli.js +3 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/acp-server.js +2 -2
- package/dist/core/acp-server.js.map +1 -1
- package/dist/core/admin-allowlist.js +1 -1
- package/dist/core/admin-allowlist.js.map +1 -1
- package/dist/core/agent-base.d.ts +6 -3
- package/dist/core/agent-base.d.ts.map +1 -1
- package/dist/core/agent-base.js +68 -24
- package/dist/core/agent-base.js.map +1 -1
- package/dist/core/agent-cwd.d.ts +2 -2
- package/dist/core/agent-cwd.js +11 -11
- package/dist/core/agent-cwd.js.map +1 -1
- package/dist/core/agent-helper.js +1 -1
- package/dist/core/agent-helper.js.map +1 -1
- package/dist/core/approval-bus.d.ts +42 -3
- package/dist/core/approval-bus.d.ts.map +1 -1
- package/dist/core/approval-bus.js +169 -38
- package/dist/core/approval-bus.js.map +1 -1
- package/dist/core/approval-router.d.ts.map +1 -1
- package/dist/core/approval-router.js +190 -87
- package/dist/core/approval-router.js.map +1 -1
- package/dist/core/commands/approval.d.ts.map +1 -1
- package/dist/core/commands/approval.js +7 -5
- package/dist/core/commands/approval.js.map +1 -1
- package/dist/core/commands/builtin.js +6 -6
- package/dist/core/commands/builtin.js.map +1 -1
- package/dist/core/event-bus.js +1 -1
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/job-board.js +3 -3
- package/dist/core/job-board.js.map +1 -1
- package/dist/core/logger.js +1 -1
- package/dist/core/logger.js.map +1 -1
- package/dist/core/memos.js +1 -1
- package/dist/core/memos.js.map +1 -1
- package/dist/core/onboarding.js +1 -1
- package/dist/core/onboarding.js.map +1 -1
- package/dist/core/reminders.js +2 -2
- package/dist/core/reminders.js.map +1 -1
- package/dist/core/self-protect.d.ts +3 -2
- package/dist/core/self-protect.d.ts.map +1 -1
- package/dist/core/self-protect.js +75 -41
- package/dist/core/self-protect.js.map +1 -1
- package/dist/core/session.js +2 -2
- package/dist/core/session.js.map +1 -1
- package/dist/core/thread-queue.js +1 -1
- package/dist/core/thread-queue.js.map +1 -1
- package/dist/core/types.d.ts +5 -5
- package/dist/core/types.js +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/agents/acp/types.d.ts +1 -1
- package/dist/plugins/agents/claude-code/index.js +4 -4
- package/dist/plugins/agents/claude-code/index.js.map +1 -1
- package/dist/plugins/agents/claude-code/mcp-approval-server.js +6 -6
- package/dist/plugins/agents/claude-code/mcp-approval-server.js.map +1 -1
- package/dist/plugins/agents/codex/index.d.ts +2 -2
- package/dist/plugins/agents/codex/index.js +5 -5
- package/dist/plugins/agents/codex/index.js.map +1 -1
- package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts +3 -3
- package/dist/plugins/agents/opencode/opencode-http-adapter.d.ts.map +1 -1
- package/dist/plugins/agents/opencode/opencode-http-adapter.js +53 -22
- package/dist/plugins/agents/opencode/opencode-http-adapter.js.map +1 -1
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.d.ts +2 -2
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.js +4 -4
- package/dist/plugins/agents/opencode/opencode-stdio-adapter.js.map +1 -1
- package/dist/plugins/agents/opencode/serve-manager.d.ts +1 -1
- package/dist/plugins/agents/opencode/serve-manager.d.ts.map +1 -1
- package/dist/plugins/agents/opencode/serve-manager.js +3 -3
- package/dist/plugins/agents/opencode/serve-manager.js.map +1 -1
- package/dist/plugins/messengers/dingtalk/dingtalk-adapter.d.ts +2 -2
- package/dist/plugins/messengers/dingtalk/dingtalk-adapter.js +2 -2
- package/dist/plugins/messengers/dingtalk/dingtalk-client.js +1 -1
- package/dist/plugins/messengers/dingtalk/dingtalk-client.js.map +1 -1
- package/dist/plugins/messengers/dingtalk/media-store.js +1 -1
- package/dist/plugins/messengers/dingtalk/media-store.js.map +1 -1
- package/dist/plugins/messengers/discord/discord-adapter.js +2 -2
- package/dist/plugins/messengers/discord/discord-adapter.js.map +1 -1
- package/dist/plugins/messengers/email/email-adapter.js +1 -1
- package/dist/plugins/messengers/email/email-adapter.js.map +1 -1
- package/dist/plugins/messengers/feishu/feishu-adapter.js +2 -2
- package/dist/plugins/messengers/feishu/feishu-adapter.js.map +1 -1
- package/dist/plugins/messengers/telegram/media-download.js +1 -1
- package/dist/plugins/messengers/telegram/media-download.js.map +1 -1
- package/dist/plugins/messengers/telegram/telegram-adapter.d.ts +1 -1
- package/dist/plugins/messengers/telegram/telegram-adapter.js +3 -3
- package/dist/plugins/messengers/telegram/telegram-adapter.js.map +1 -1
- package/dist/plugins/messengers/wechat/ilink-adapter.js +2 -2
- package/dist/plugins/messengers/wechat/ilink-adapter.js.map +1 -1
- package/dist/plugins/messengers/wechat/media-download.js +1 -1
- package/dist/plugins/messengers/wechat/media-download.js.map +1 -1
- package/dist/web/public/_app.js +1 -1
- package/dist/web/public/settings.html +89 -0
- package/dist/web/server.js +20 -4
- package/dist/web/server.js.map +1 -1
- package/package.json +2 -2
package/dist/core/agent-cwd.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Per-agent working directory resolution for the IM context.
|
|
2
2
|
//
|
|
3
3
|
// Both Claude Code and opencode key their per-project memory / history off
|
|
4
|
-
// the spawned process's cwd. When
|
|
4
|
+
// the spawned process's cwd. When agim runs as a systemd service, that cwd
|
|
5
5
|
// is "/" — so every IM thread, every direct-terminal session, and every
|
|
6
6
|
// background scheduler tick all share the SAME global memory bucket. This
|
|
7
7
|
// is the root cause of:
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
// Fix: when we detect we're in an IM call (threadId + platform both set),
|
|
14
14
|
// pin the agent into a stable per-agent workspace under
|
|
15
|
-
// `~/.
|
|
15
|
+
// `~/.agim-workspaces/<agent>/`. Non-IM calls (web UI, scheduler,
|
|
16
16
|
// intent-llm judge) keep the inherited cwd, preserving prior behavior.
|
|
17
17
|
//
|
|
18
18
|
// Override path: `IMHUB_<AGENT>_CWD` env var beats everything (e.g.
|
|
@@ -43,8 +43,8 @@ function envKeyFor(agentName) {
|
|
|
43
43
|
* Decision order:
|
|
44
44
|
* 1. Env override `IMHUB_<AGENT>_CWD` always wins.
|
|
45
45
|
* 2. If we're in an IM context (threadId + platform both present), pin to
|
|
46
|
-
* `~/.
|
|
47
|
-
* 3. Otherwise return undefined → adapter inherits
|
|
46
|
+
* `~/.agim-workspaces/<agent>/`.
|
|
47
|
+
* 3. Otherwise return undefined → adapter inherits agim's cwd. This
|
|
48
48
|
* keeps web/scheduler/intent-llm judge paths exactly as they were.
|
|
49
49
|
*/
|
|
50
50
|
export function resolveAgentCwd(agentName, opts) {
|
|
@@ -94,7 +94,7 @@ export async function bootstrapAgentWorkspaces() {
|
|
|
94
94
|
const result = [];
|
|
95
95
|
const claudeSeed = `# Claude Code — IM 入口工作区
|
|
96
96
|
|
|
97
|
-
> 这是
|
|
97
|
+
> 这是 agim 通过 IM 唤起 Claude Code 时的专用工作目录。
|
|
98
98
|
> 与你直接在终端里跑 \`claude\` 时使用的全局配置 (~/.claude/CLAUDE.md) 隔离。
|
|
99
99
|
>
|
|
100
100
|
> 文件位置:${defaultAgentCwd('claude-code')}/CLAUDE.md
|
|
@@ -113,7 +113,7 @@ export async function bootstrapAgentWorkspaces() {
|
|
|
113
113
|
## 注意
|
|
114
114
|
|
|
115
115
|
- 当前 cwd 不是真正的代码仓库;要查代码请去 /root/workspace/...
|
|
116
|
-
- IM 单条消息的硬超时是 30 分钟(
|
|
116
|
+
- IM 单条消息的硬超时是 30 分钟(agim 层),长任务必须走 bgjob
|
|
117
117
|
`;
|
|
118
118
|
result.push({
|
|
119
119
|
agent: 'claude-code',
|
|
@@ -121,7 +121,7 @@ export async function bootstrapAgentWorkspaces() {
|
|
|
121
121
|
});
|
|
122
122
|
const opencodeSeed = `# OpenCode — IM 入口工作区
|
|
123
123
|
|
|
124
|
-
> 这是
|
|
124
|
+
> 这是 agim 通过 IM 唤起 opencode 时的专用工作目录。
|
|
125
125
|
> 与你直接在终端里跑 \`opencode\` 时使用的全局配置 (~/.config/opencode/AGENTS.md) 隔离。
|
|
126
126
|
>
|
|
127
127
|
> 文件位置:${defaultAgentCwd('opencode')}/AGENTS.md
|
|
@@ -139,7 +139,7 @@ export async function bootstrapAgentWorkspaces() {
|
|
|
139
139
|
## 注意
|
|
140
140
|
|
|
141
141
|
- 当前 cwd 不是真正的代码仓库;要查代码请去 /root/workspace/...
|
|
142
|
-
- IM 单条消息的硬超时是 30 分钟(
|
|
142
|
+
- IM 单条消息的硬超时是 30 分钟(agim 层),长任务必须走 bgjob
|
|
143
143
|
`;
|
|
144
144
|
result.push({
|
|
145
145
|
agent: 'opencode',
|
|
@@ -147,7 +147,7 @@ export async function bootstrapAgentWorkspaces() {
|
|
|
147
147
|
});
|
|
148
148
|
const codexSeed = `# Codex — IM 入口工作区
|
|
149
149
|
|
|
150
|
-
> 这是
|
|
150
|
+
> 这是 agim 通过 IM 唤起 OpenAI Codex CLI 时的专用工作目录。
|
|
151
151
|
> 与你直接在终端里跑 \`codex\` 时使用的全局配置 (~/.codex/config.toml) 隔离。
|
|
152
152
|
>
|
|
153
153
|
> 文件位置:${defaultAgentCwd('codex')}/AGENTS.md
|
|
@@ -166,11 +166,11 @@ export async function bootstrapAgentWorkspaces() {
|
|
|
166
166
|
## 注意
|
|
167
167
|
|
|
168
168
|
- 当前 cwd 不是真正的代码仓库;要查代码请去 /root/workspace/...
|
|
169
|
-
- IM 单条消息的硬超时是 30 分钟(
|
|
169
|
+
- IM 单条消息的硬超时是 30 分钟(agim 层),长任务必须走 bgjob
|
|
170
170
|
- bgjob 数据目录:~/.codex/bgjobs/(与 claude / opencode 隔离)
|
|
171
171
|
使用方式:\`/root/.codex/scripts/bgjob start <name> -- <cmd...>\`
|
|
172
172
|
- AGENTS.md 与 opencode 同名但工作区分离 —— codex 只读本目录的 AGENTS.md,
|
|
173
|
-
不会读 ~/.
|
|
173
|
+
不会读 ~/.agim-workspaces/opencode/AGENTS.md
|
|
174
174
|
`;
|
|
175
175
|
result.push({
|
|
176
176
|
agent: 'codex',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-cwd.js","sourceRoot":"","sources":["../../src/core/agent-cwd.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"agent-cwd.js","sourceRoot":"","sources":["../../src/core/agent-cwd.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Why this layer exists: features that want a small LLM judgement
|
|
4
4
|
// (reminder text polish, reminder-intent detection, etc.) MUST NOT bake in
|
|
5
|
-
// a specific provider (DeepSeek, OpenAI, …).
|
|
5
|
+
// a specific provider (DeepSeek, OpenAI, …). agim is shipped as a
|
|
6
6
|
// pluggable framework — every user's LLM access goes through the agent
|
|
7
7
|
// they've already registered (claude-code / codex / opencode / copilot /
|
|
8
8
|
// any future ACP-discovered remote). So all helper calls funnel through
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-helper.js","sourceRoot":"","sources":["../../src/core/agent-helper.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,kEAAkE;AAClE,2EAA2E;AAC3E,
|
|
1
|
+
{"version":3,"file":"agent-helper.js","sourceRoot":"","sources":["../../src/core/agent-helper.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare function isTimeoutDefaultAllow(): boolean;
|
|
1
2
|
export interface RunContext {
|
|
2
3
|
threadId: string;
|
|
3
4
|
platform: string;
|
|
@@ -80,8 +81,10 @@ export interface ApprovalBusOptions {
|
|
|
80
81
|
export declare class ApprovalBus {
|
|
81
82
|
private server;
|
|
82
83
|
private socketPath;
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
/** Public so renderers (approval-router plain-text prompt, future card
|
|
85
|
+
* variants) can show the actual budget instead of hardcoded "5 分钟". */
|
|
86
|
+
readonly approvalTimeoutMs: number;
|
|
87
|
+
readonly autoAllowGraceMs: number;
|
|
85
88
|
private runContexts;
|
|
86
89
|
private pendingById;
|
|
87
90
|
private pendingByThread;
|
|
@@ -295,6 +298,42 @@ export declare class ApprovalBus {
|
|
|
295
298
|
private removePending;
|
|
296
299
|
private sendDecision;
|
|
297
300
|
}
|
|
298
|
-
/**
|
|
301
|
+
/**
|
|
302
|
+
* Translate a raw fingerprint string (e.g. `cmd:git+status`) into the
|
|
303
|
+
* human-readable form shown in IM receipts and `/approval` listings
|
|
304
|
+
* (e.g. `命令 git status`). The inverse of inputFingerprint()'s internal
|
|
305
|
+
* encoding — keep the scheme list in sync if you add new fingerprint
|
|
306
|
+
* variants.
|
|
307
|
+
*
|
|
308
|
+
* Unknown / legacy schemes pass through unchanged so the function is
|
|
309
|
+
* forward-compatible with future fingerprint additions and backward-
|
|
310
|
+
* compatible with rules created before the semantic split (no scheme).
|
|
311
|
+
*/
|
|
312
|
+
export declare function humanizeFingerprint(fingerprint: string): string;
|
|
313
|
+
/**
|
|
314
|
+
* Compute the auto-allow fingerprint — the "kind" of call that, when
|
|
315
|
+
* approved with `all`, covers future similar calls.
|
|
316
|
+
*
|
|
317
|
+
* Semantic per tool (rather than a uniform prefix slice) — fixes two
|
|
318
|
+
* footguns of the old prefix-based approach:
|
|
319
|
+
* 1. `Bash git status` and `git stash` collided at 5 chars but split at
|
|
320
|
+
* 10 — neither setting matched user intuition. We extract the first
|
|
321
|
+
* two tokens (`cmd:git+status` vs `cmd:git+stash`) so the family is
|
|
322
|
+
* narrow and predictable.
|
|
323
|
+
* 2. `Read /tmp/a.txt` and `Read /tmp/b.txt` got distinct fingerprints
|
|
324
|
+
* at any prefix length below the filename, forcing users to re-`all`
|
|
325
|
+
* every file. We key on dirname (`dir:/tmp`) so one approval covers
|
|
326
|
+
* the whole directory.
|
|
327
|
+
*
|
|
328
|
+
* Prefixes (`cmd:` / `dir:` / `host:` / `path:` / `pat:` / `query:` /
|
|
329
|
+
* `url:` / `raw:`) keep the key space disambiguated — rules from
|
|
330
|
+
* different schemes can never accidentally alias each other, and `raw:`
|
|
331
|
+
* preserves backward compatibility for unknown tools.
|
|
332
|
+
*
|
|
333
|
+
* EXPORTED for unit tests. Not part of the bus's public API; production
|
|
334
|
+
* call sites all live inside this module.
|
|
335
|
+
*/
|
|
336
|
+
export declare function inputFingerprint(toolName: string, input: Record<string, unknown>): string;
|
|
337
|
+
/** 进程级单例。agim 启动时 await approvalBus.start() 一次。 */
|
|
299
338
|
export declare const approvalBus: ApprovalBus;
|
|
300
339
|
//# sourceMappingURL=approval-bus.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approval-bus.d.ts","sourceRoot":"","sources":["../../src/core/approval-bus.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"approval-bus.d.ts","sourceRoot":"","sources":["../../src/core/approval-bus.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// approval-bus — IM 端人工审批的进程内总线
|
|
2
2
|
//
|
|
3
|
-
// 角色:在
|
|
3
|
+
// 角色:在 agim 主进程里跑一个 unix socket 服务,等 claude 子进程的
|
|
4
4
|
// MCP "approval sidecar" 通过 socket 连进来发审批请求。bus 自己不决策,
|
|
5
5
|
// 只做三件事:
|
|
6
6
|
// 1. 把请求转给 notifier(由 messenger 层注入:负责推 IM 卡片)
|
|
@@ -15,17 +15,35 @@
|
|
|
15
15
|
import { createServer } from 'node:net';
|
|
16
16
|
import { unlink, stat as fsStat, chmod } from 'node:fs/promises';
|
|
17
17
|
import { tmpdir } from 'node:os';
|
|
18
|
-
import { join } from 'node:path';
|
|
18
|
+
import { dirname, join } from 'node:path';
|
|
19
19
|
import { randomBytes } from 'node:crypto';
|
|
20
20
|
import { logger as rootLogger } from './logger.js';
|
|
21
21
|
import { eventBus } from './event-bus.js';
|
|
22
22
|
const log = rootLogger.child({ component: 'approval-bus' });
|
|
23
|
-
//
|
|
24
|
-
// notifications can land minutes after
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
|
|
23
|
+
// Default 15 min — long enough for slower IM channels (Telegram, where
|
|
24
|
+
// notifications can land minutes after bot.api.sendMessage logs success) to
|
|
25
|
+
// round-trip a y/n reply, short enough not to block downstream tools for
|
|
26
|
+
// hours. agent-base.ts now uses an idle-based timer (resets on every stdout
|
|
27
|
+
// chunk) instead of a fixed total-time cap, so this approval timeout is no
|
|
28
|
+
// longer constrained to "outlive the agent process" — the agent only dies
|
|
29
|
+
// after IMHUB_AGENT_IDLE_TIMEOUT_MS of complete silence.
|
|
30
|
+
const DEFAULT_APPROVAL_TIMEOUT_MS = parseEnvMs(process.env.IMHUB_APPROVAL_TIMEOUT_MS, 15 * 60 * 1000);
|
|
28
31
|
const DEFAULT_AUTO_ALLOW_GRACE_MS = 5 * 1000;
|
|
32
|
+
// IMHUB_TIMEOUT_DEFAULT=allow | deny (default deny). Decides what the bus
|
|
33
|
+
// returns when a normal-mode approval times out without a user response.
|
|
34
|
+
// Auto-allow grace expiry is unaffected — it always resolves to allow.
|
|
35
|
+
// Setting this to 'allow' is "user away" mode: the agent keeps moving even
|
|
36
|
+
// when nobody's at the IM, at the cost of weaker human oversight on
|
|
37
|
+
// write-side tool calls (Bash/Edit/Write/etc). Default deny preserves the
|
|
38
|
+
// original strict behavior.
|
|
39
|
+
//
|
|
40
|
+
// Read at every timer fire (not captured at module load) so the Web Settings
|
|
41
|
+
// "Approval Policy" card can hot-toggle the value — handlePutEnv mutates
|
|
42
|
+
// process.env after updating ~/.agim/env, and the next timer that expires
|
|
43
|
+
// picks up the new value with no restart.
|
|
44
|
+
export function isTimeoutDefaultAllow() {
|
|
45
|
+
return (process.env.IMHUB_TIMEOUT_DEFAULT ?? 'deny').toLowerCase() === 'allow';
|
|
46
|
+
}
|
|
29
47
|
/**
|
|
30
48
|
* Tools whose contract is "read filesystem state, return it" — no writes,
|
|
31
49
|
* no shell exec, no network. These short-circuit the approval flow with an
|
|
@@ -61,20 +79,11 @@ function parseEnvMs(raw, fallback) {
|
|
|
61
79
|
}
|
|
62
80
|
const MAX_LINE_BYTES = 256 * 1024;
|
|
63
81
|
const MAX_BUFFER_BYTES = MAX_LINE_BYTES * 4;
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* broader than users meant when they OK'd a single command. 10 keeps
|
|
70
|
-
* the three common families distinct (`git status` ≠ `git stash` ≠
|
|
71
|
-
* `git submo`) while still grouping benign variations of the same
|
|
72
|
-
* operation (e.g. `git status` ≈ `git status -s`).
|
|
73
|
-
*
|
|
74
|
-
* Why not longer? Most realistic commands are 6–14 chars, and pushing
|
|
75
|
-
* past 10 means even `git status` vs `git status -s` are treated as
|
|
76
|
-
* distinct, defeating the "approve once for variants" UX. 10 is the
|
|
77
|
-
* sweet spot per CR-2026-05-06. */
|
|
82
|
+
/** Fallback prefix length for tools without a semantic key (Glob/Grep
|
|
83
|
+
* patterns, WebSearch queries, unknown tools). The mainline path uses
|
|
84
|
+
* per-tool semantic fingerprints — see inputFingerprint() — so this only
|
|
85
|
+
* governs the long tail. Kept at 10 for parity with the pre-semantic
|
|
86
|
+
* behavior. */
|
|
78
87
|
const AUTO_ALLOW_PREFIX_LEN = 10;
|
|
79
88
|
/** Composite key that prevents cross-platform/channel thread collisions. */
|
|
80
89
|
export function threadKey(platform, channelId, threadId) {
|
|
@@ -83,6 +92,8 @@ export function threadKey(platform, channelId, threadId) {
|
|
|
83
92
|
export class ApprovalBus {
|
|
84
93
|
server = null;
|
|
85
94
|
socketPath = null;
|
|
95
|
+
/** Public so renderers (approval-router plain-text prompt, future card
|
|
96
|
+
* variants) can show the actual budget instead of hardcoded "5 分钟". */
|
|
86
97
|
approvalTimeoutMs;
|
|
87
98
|
autoAllowGraceMs;
|
|
88
99
|
runContexts = new Map();
|
|
@@ -721,7 +732,7 @@ export class ApprovalBus {
|
|
|
721
732
|
* notifier, and ensures the timer is cleared if the notifier itself throws.
|
|
722
733
|
*/
|
|
723
734
|
async _registerPending(args) {
|
|
724
|
-
const fingerprint = inputFingerprint(args.input);
|
|
735
|
+
const fingerprint = inputFingerprint(args.toolName, args.input);
|
|
725
736
|
const cKey = threadKey(args.ctx.platform, args.ctx.channelId, args.ctx.threadId);
|
|
726
737
|
// Readonly auto-allow short-circuit. Must come BEFORE pending registration
|
|
727
738
|
// / notifier so it costs zero IM round-trips. Counts toward totals so the
|
|
@@ -775,6 +786,9 @@ export class ApprovalBus {
|
|
|
775
786
|
if (isAutoAllow) {
|
|
776
787
|
this.cancelPending(pending, { behavior: 'allow' }, 'timeout');
|
|
777
788
|
}
|
|
789
|
+
else if (isTimeoutDefaultAllow()) {
|
|
790
|
+
this.cancelPending(pending, { behavior: 'allow' }, 'timeout');
|
|
791
|
+
}
|
|
778
792
|
else {
|
|
779
793
|
this.cancelPending(pending, { behavior: 'deny', message: 'approval timeout' }, 'timeout');
|
|
780
794
|
}
|
|
@@ -811,7 +825,12 @@ export class ApprovalBus {
|
|
|
811
825
|
}
|
|
812
826
|
catch (err) {
|
|
813
827
|
log.error({ event: 'approval.bus.notifier_error', reqId: args.reqId, err: String(err) });
|
|
814
|
-
|
|
828
|
+
// Forward the actual error message to the agent so users see meaningful
|
|
829
|
+
// text — most notably self-protect's "🛑 检测到尝试…请改用 /restart"
|
|
830
|
+
// redirect, which was previously swallowed and replaced with a useless
|
|
831
|
+
// "notifier error". For non-Error throws we keep the old fallback.
|
|
832
|
+
const errMsg = err instanceof Error && err.message ? err.message : 'notifier error';
|
|
833
|
+
this.cancelPending(pending, { behavior: 'deny', message: errMsg }, 'notifier-error');
|
|
815
834
|
}
|
|
816
835
|
}
|
|
817
836
|
cancelPending(p, decision, cause = 'sidecar-disconnect') {
|
|
@@ -930,8 +949,22 @@ export class ApprovalBus {
|
|
|
930
949
|
this.pendingByThread.delete(p.compositeKey);
|
|
931
950
|
}
|
|
932
951
|
sendDecision(socket, reqId, decision) {
|
|
933
|
-
if (!socket.writable)
|
|
952
|
+
if (!socket.writable) {
|
|
953
|
+
// Silent drop — agent never gets the decision and hangs until its own
|
|
954
|
+
// MCP-side timeout. Log loudly so users diagnosing "auto-allow showed
|
|
955
|
+
// the card but didn't fire" (and similar "approval landed but tool
|
|
956
|
+
// didn't run" reports) have a breadcrumb. Most often happens when the
|
|
957
|
+
// sidecar socket entered half-closed state between timer fire and
|
|
958
|
+
// write (e.g. claude process exited a few ms before grace timer
|
|
959
|
+
// expiry).
|
|
960
|
+
log.warn({
|
|
961
|
+
event: 'approval.bus.send_dropped',
|
|
962
|
+
reqId,
|
|
963
|
+
decision: decision.behavior,
|
|
964
|
+
reason: 'socket-not-writable',
|
|
965
|
+
}, 'cannot deliver decision: sidecar socket no longer writable');
|
|
934
966
|
return;
|
|
967
|
+
}
|
|
935
968
|
// Strip internal-only flags (e.g. autoAllowFurther) — sidecar only
|
|
936
969
|
// understands the wire schema.
|
|
937
970
|
const wire = { v: 1, type: 'decision', reqId, behavior: decision.behavior };
|
|
@@ -952,22 +985,120 @@ function autoAllowRuleKey(toolName, fingerprint) {
|
|
|
952
985
|
return `${toolName}::${fingerprint}`;
|
|
953
986
|
}
|
|
954
987
|
/**
|
|
955
|
-
*
|
|
988
|
+
* Translate a raw fingerprint string (e.g. `cmd:git+status`) into the
|
|
989
|
+
* human-readable form shown in IM receipts and `/approval` listings
|
|
990
|
+
* (e.g. `命令 git status`). The inverse of inputFingerprint()'s internal
|
|
991
|
+
* encoding — keep the scheme list in sync if you add new fingerprint
|
|
992
|
+
* variants.
|
|
956
993
|
*
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
* to AUTO_ALLOW_PREFIX_LEN so the rule covers small variations of the
|
|
961
|
-
* same operation but not unrelated ones.
|
|
994
|
+
* Unknown / legacy schemes pass through unchanged so the function is
|
|
995
|
+
* forward-compatible with future fingerprint additions and backward-
|
|
996
|
+
* compatible with rules created before the semantic split (no scheme).
|
|
962
997
|
*/
|
|
963
|
-
function
|
|
964
|
-
const
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
998
|
+
export function humanizeFingerprint(fingerprint) {
|
|
999
|
+
const sep = fingerprint.indexOf(':');
|
|
1000
|
+
if (sep < 0)
|
|
1001
|
+
return fingerprint;
|
|
1002
|
+
const scheme = fingerprint.slice(0, sep);
|
|
1003
|
+
const rest = fingerprint.slice(sep + 1);
|
|
1004
|
+
switch (scheme) {
|
|
1005
|
+
case 'cmd': return `命令 ${rest.replace(/\+/g, ' ')}`;
|
|
1006
|
+
case 'dir': return `目录 ${rest}`;
|
|
1007
|
+
case 'host': return `主机 ${rest}`;
|
|
1008
|
+
case 'path': return `路径 ${rest}`;
|
|
1009
|
+
case 'pat': return `模式 ${rest}`;
|
|
1010
|
+
case 'query': return `查询 ${rest}`;
|
|
1011
|
+
case 'url': return `URL ${rest}`;
|
|
1012
|
+
case 'raw': return `入参 ${rest}`;
|
|
1013
|
+
default: return fingerprint;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* Compute the auto-allow fingerprint — the "kind" of call that, when
|
|
1018
|
+
* approved with `all`, covers future similar calls.
|
|
1019
|
+
*
|
|
1020
|
+
* Semantic per tool (rather than a uniform prefix slice) — fixes two
|
|
1021
|
+
* footguns of the old prefix-based approach:
|
|
1022
|
+
* 1. `Bash git status` and `git stash` collided at 5 chars but split at
|
|
1023
|
+
* 10 — neither setting matched user intuition. We extract the first
|
|
1024
|
+
* two tokens (`cmd:git+status` vs `cmd:git+stash`) so the family is
|
|
1025
|
+
* narrow and predictable.
|
|
1026
|
+
* 2. `Read /tmp/a.txt` and `Read /tmp/b.txt` got distinct fingerprints
|
|
1027
|
+
* at any prefix length below the filename, forcing users to re-`all`
|
|
1028
|
+
* every file. We key on dirname (`dir:/tmp`) so one approval covers
|
|
1029
|
+
* the whole directory.
|
|
1030
|
+
*
|
|
1031
|
+
* Prefixes (`cmd:` / `dir:` / `host:` / `path:` / `pat:` / `query:` /
|
|
1032
|
+
* `url:` / `raw:`) keep the key space disambiguated — rules from
|
|
1033
|
+
* different schemes can never accidentally alias each other, and `raw:`
|
|
1034
|
+
* preserves backward compatibility for unknown tools.
|
|
1035
|
+
*
|
|
1036
|
+
* EXPORTED for unit tests. Not part of the bus's public API; production
|
|
1037
|
+
* call sites all live inside this module.
|
|
1038
|
+
*/
|
|
1039
|
+
export function inputFingerprint(toolName, input) {
|
|
1040
|
+
const getStr = (k) => {
|
|
1041
|
+
const v = input[k];
|
|
1042
|
+
return typeof v === 'string' && v.length > 0 ? v : null;
|
|
1043
|
+
};
|
|
1044
|
+
switch (toolName) {
|
|
1045
|
+
case 'Bash': {
|
|
1046
|
+
const cmd = getStr('command');
|
|
1047
|
+
if (!cmd)
|
|
1048
|
+
break;
|
|
1049
|
+
// First 1–2 whitespace-separated tokens. Quoted strings aren't
|
|
1050
|
+
// unwrapped — we want `bash -c "..."` to fingerprint as `bash+-c`,
|
|
1051
|
+
// not whatever's inside the quotes (the user is approving "running
|
|
1052
|
+
// bash -c", a meta-pattern).
|
|
1053
|
+
const tokens = cmd.trim().split(/\s+/).slice(0, 2);
|
|
1054
|
+
return `cmd:${tokens.join('+')}`;
|
|
1055
|
+
}
|
|
1056
|
+
case 'Read':
|
|
1057
|
+
case 'Write':
|
|
1058
|
+
case 'Edit':
|
|
1059
|
+
case 'NotebookEdit':
|
|
1060
|
+
case 'NotebookRead':
|
|
1061
|
+
case 'LS': {
|
|
1062
|
+
const fp = getStr('file_path') ?? getStr('path');
|
|
1063
|
+
if (!fp)
|
|
1064
|
+
break;
|
|
1065
|
+
// dirname('/tmp/x.txt') → '/tmp' (group all files in dir)
|
|
1066
|
+
// dirname('file.txt') → '.' (no dir — fall back to the path
|
|
1067
|
+
// itself so we don't lump every
|
|
1068
|
+
// relative-path call together)
|
|
1069
|
+
const d = dirname(fp);
|
|
1070
|
+
return d === '.' || d === '' ? `path:${fp.slice(0, AUTO_ALLOW_PREFIX_LEN)}` : `dir:${d}`;
|
|
1071
|
+
}
|
|
1072
|
+
case 'WebFetch': {
|
|
1073
|
+
const u = getStr('url');
|
|
1074
|
+
if (!u)
|
|
1075
|
+
break;
|
|
1076
|
+
// Hostname captures "calls to this API"; path / query string are
|
|
1077
|
+
// intentionally dropped — they vary per call but the user means
|
|
1078
|
+
// "trust this host" when they say `all`.
|
|
1079
|
+
try {
|
|
1080
|
+
return `host:${new URL(u).hostname}`;
|
|
1081
|
+
}
|
|
1082
|
+
catch {
|
|
1083
|
+
return `url:${u.slice(0, AUTO_ALLOW_PREFIX_LEN)}`;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
case 'WebSearch': {
|
|
1087
|
+
const q = getStr('query');
|
|
1088
|
+
if (!q)
|
|
1089
|
+
break;
|
|
1090
|
+
return `query:${q.slice(0, AUTO_ALLOW_PREFIX_LEN)}`;
|
|
1091
|
+
}
|
|
1092
|
+
case 'Glob':
|
|
1093
|
+
case 'Grep': {
|
|
1094
|
+
const p = getStr('pattern');
|
|
1095
|
+
if (!p)
|
|
1096
|
+
break;
|
|
1097
|
+
return `pat:${p.slice(0, AUTO_ALLOW_PREFIX_LEN)}`;
|
|
969
1098
|
}
|
|
970
1099
|
}
|
|
1100
|
+
// Unknown tool or known tool whose primary field was missing — fall
|
|
1101
|
+
// back to a stringified prefix so the dedup key remains stable.
|
|
971
1102
|
let s;
|
|
972
1103
|
try {
|
|
973
1104
|
s = JSON.stringify(input);
|
|
@@ -975,7 +1106,7 @@ function inputFingerprint(input) {
|
|
|
975
1106
|
catch {
|
|
976
1107
|
s = '';
|
|
977
1108
|
}
|
|
978
|
-
return s.slice(0, AUTO_ALLOW_PREFIX_LEN)
|
|
1109
|
+
return `raw:${s.slice(0, AUTO_ALLOW_PREFIX_LEN)}`;
|
|
979
1110
|
}
|
|
980
1111
|
function defaultSocketPath() {
|
|
981
1112
|
// 16 random bytes (32 hex chars) — defeats path prediction attacks that the
|
|
@@ -985,6 +1116,6 @@ function defaultSocketPath() {
|
|
|
985
1116
|
// statistically infeasible.
|
|
986
1117
|
return join(tmpdir(), `imhub-approval-${randomBytes(16).toString('hex')}.sock`);
|
|
987
1118
|
}
|
|
988
|
-
/** 进程级单例。
|
|
1119
|
+
/** 进程级单例。agim 启动时 await approvalBus.start() 一次。 */
|
|
989
1120
|
export const approvalBus = new ApprovalBus();
|
|
990
1121
|
//# sourceMappingURL=approval-bus.js.map
|