@miphamai/cli 0.82.0 → 0.84.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/package.json +1 -1
- package/skills/standard/mipham-code-setup.SKILL.md +30 -8
- package/src/agent/agent-context.ts +5 -5
- package/src/agent/agent-experience.ts +2 -2
- package/src/agent/agent-registry.ts +4 -4
- package/src/agent/cross-session/discovery.ts +3 -2
- package/src/agent/cross-session/file-inbox.ts +2 -2
- package/src/agent/effectiveness-tracker.ts +2 -2
- package/src/agent/pattern-analyzer.ts +3 -4
- package/src/agent/sub-agent.ts +12 -2
- package/src/agent/types.ts +4 -1
- package/src/commands/autoloop-journal.ts +2 -2
- package/src/commands/environment.ts +2 -1
- package/src/commands/loop-scaffold.ts +2 -1
- package/src/commands/project.ts +86 -33
- package/src/config/keys-manager.ts +2 -2
- package/src/config/loader.ts +11 -10
- package/src/config/preferences.ts +3 -4
- package/src/core/auto-memory.ts +2 -3
- package/src/core/constitution-loader.ts +3 -4
- package/src/core/crsi-producer.ts +3 -3
- package/src/core/crsi-sandbox.ts +3 -2
- package/src/core/dream-engine.ts +2 -2
- package/src/core/engine.ts +38 -6
- package/src/core/error-signature-db.ts +2 -2
- package/src/core/eval-harness.ts +121 -4
- package/src/core/improvement-track.ts +35 -6
- package/src/core/instructions.ts +28 -4
- package/src/core/memory/memory-loader.ts +2 -3
- package/src/core/paths.ts +21 -1
- package/src/core/permission-audit.ts +120 -0
- package/src/core/permission-classifier.ts +449 -0
- package/src/core/permission-config.ts +106 -15
- package/src/core/permission.ts +369 -16
- package/src/core/rule-engine.ts +2 -2
- package/src/core/rules-loader.ts +3 -2
- package/src/core/session-log.ts +2 -3
- package/src/core/session-store.ts +2 -3
- package/src/core/task-performance.ts +22 -1
- package/src/core/workspace-trust.ts +4 -3
- package/src/daemon/database.ts +2 -2
- package/src/daemon/index.ts +2 -3
- package/src/daemon/launch.ts +3 -3
- package/src/daemon/server.ts +15 -0
- package/src/i18n-core/locales/en-US.json +3 -0
- package/src/i18n-core/locales/zh-CN.json +3 -0
- package/src/index.tsx +30 -6
- package/src/mcp/token-store.ts +2 -2
- package/src/plugin/plugin-manager.ts +2 -2
- package/src/shared/constants.ts +0 -1
- package/src/shared/package-info.ts +1 -1
- package/src/shared/types.ts +46 -6
- package/src/shared/update.ts +2 -3
- package/src/skills/bundled-skills.ts +1 -1
- package/src/skills/loader.ts +2 -3
- package/src/skills/marketplace.ts +2 -3
- package/src/skills/registry.ts +2 -2
- package/src/skills/skill-assets.ts +2 -2
- package/src/skills/usage.ts +2 -2
- package/src/telemetry/consent.ts +2 -3
- package/src/tools/agent/enter-plan.ts +3 -2
- package/src/tools/agent/exit-plan.ts +1 -1
- package/src/tools/agent/list-agents.ts +1 -1
- package/src/tools/agent/memory.ts +3 -3
- package/src/tools/agent/plan.ts +3 -2
- package/src/tools/agent/report-findings.ts +1 -1
- package/src/tools/agent/send-message.ts +1 -1
- package/src/tools/agent/skill.ts +1 -1
- package/src/tools/exec/git.ts +2 -2
- package/src/tools/exec/task.ts +1 -1
- package/src/tools/file/glob.ts +1 -1
- package/src/tools/file/grep.ts +1 -1
- package/src/tools/file/read.ts +1 -1
- package/src/tools/network/web-fetch.ts +1 -1
- package/src/tools/network/web-search.ts +1 -1
- package/src/tools/scheduling/cron.ts +5 -5
- package/src/tools/scheduling/schedule-wakeup.ts +1 -1
- package/src/tools/system/config.ts +2 -2
- package/src/tools/system/tool-search.ts +1 -1
- package/src/ui/app.tsx +51 -9
- package/src/ui/commands.ts +41 -19
- package/src/ui/config-wizard.tsx +2 -2
- package/src/ui/input.tsx +15 -3
- package/src/workflow/journal.ts +2 -2
package/src/core/rule-engine.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExperienceRule } from '../agent/experience-rules.js'
|
|
2
2
|
import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs'
|
|
3
3
|
import { join, dirname } from 'node:path'
|
|
4
|
-
import { homedir } from 'node:os'
|
|
5
4
|
import { MANAGED_RULES } from './crsi-managed-rules'
|
|
5
|
+
import { miphamHome } from './paths.ts'
|
|
6
6
|
|
|
7
7
|
export interface ToolRule {
|
|
8
8
|
id: string
|
|
@@ -62,7 +62,7 @@ export class ExperienceRuleEngine {
|
|
|
62
62
|
private rules: ToolRule[]
|
|
63
63
|
private storePath: string
|
|
64
64
|
|
|
65
|
-
constructor(storeDir: string =
|
|
65
|
+
constructor(storeDir: string = miphamHome('rule-engine')) {
|
|
66
66
|
this.rules = [...BUILTIN_RULES, ...MANAGED_RULES].map((r) => ({ ...r }))
|
|
67
67
|
this.storePath = join(storeDir, 'rules.json')
|
|
68
68
|
this.load()
|
package/src/core/rules-loader.ts
CHANGED
|
@@ -21,6 +21,7 @@ import { readdirSync, readFileSync, existsSync } from 'node:fs'
|
|
|
21
21
|
import { join } from 'node:path'
|
|
22
22
|
import { globToRegexSource } from './credential-masker/matcher'
|
|
23
23
|
import { findWorktreeMarker } from './paths.ts'
|
|
24
|
+
import { MIPHAM_DIR } from '../shared/constants.ts'
|
|
24
25
|
|
|
25
26
|
interface RuleFile {
|
|
26
27
|
name: string
|
|
@@ -49,9 +50,9 @@ export class RulesLoader {
|
|
|
49
50
|
private projectRulesDir: string | null
|
|
50
51
|
|
|
51
52
|
constructor(cwd: string) {
|
|
52
|
-
this.rulesDir = join(cwd,
|
|
53
|
+
this.rulesDir = join(cwd, MIPHAM_DIR, 'rules')
|
|
53
54
|
const marker = findWorktreeMarker(cwd)
|
|
54
|
-
const projectRulesDir = marker ? join(marker.root,
|
|
55
|
+
const projectRulesDir = marker ? join(marker.root, MIPHAM_DIR, 'rules') : null
|
|
55
56
|
this.projectRulesDir = projectRulesDir === this.rulesDir ? null : projectRulesDir
|
|
56
57
|
}
|
|
57
58
|
|
package/src/core/session-log.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { appendFileSync, readFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
2
|
-
import { homedir } from 'node:os'
|
|
3
2
|
import { join } from 'node:path'
|
|
4
3
|
import { createHash } from 'node:crypto'
|
|
5
4
|
import type { Message, ToolUseContent, ToolResultContent, ToolResult } from '../shared/types'
|
|
6
5
|
import type { CheckerDecision } from './post-flight-checker'
|
|
6
|
+
import { miphamHome } from './paths.ts'
|
|
7
7
|
|
|
8
8
|
export type SessionEvent =
|
|
9
9
|
| {
|
|
@@ -115,8 +115,7 @@ export function deriveMessages(events: SessionEvent[]): Message[] {
|
|
|
115
115
|
return out
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
const
|
|
119
|
-
const LOG_DIR = join(HOME, '.mipham', 'sessions')
|
|
118
|
+
const LOG_DIR = miphamHome('sessions')
|
|
120
119
|
|
|
121
120
|
/** 将会话名消毒为安全文件名(与 SessionStore 共用;防路径穿越)。 */
|
|
122
121
|
export function sanitizeSessionName(name: string): string {
|
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
existsSync,
|
|
9
9
|
statSync,
|
|
10
10
|
} from 'node:fs'
|
|
11
|
-
import { homedir } from 'node:os'
|
|
12
11
|
import { join } from 'node:path'
|
|
13
12
|
import { createHash } from 'node:crypto'
|
|
14
13
|
import type { Message } from '../shared/types'
|
|
15
14
|
import { sanitizeSessionName, SessionLog, deriveMessages, messageToEvents } from './session-log'
|
|
15
|
+
import { miphamHome } from './paths.ts'
|
|
16
16
|
|
|
17
17
|
export interface SessionMetadata {
|
|
18
18
|
name: string
|
|
@@ -29,8 +29,7 @@ export interface StoredSession {
|
|
|
29
29
|
messages: Message[]
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
const SESSIONS_DIR = join(HOME, '.mipham', 'sessions')
|
|
32
|
+
const SESSIONS_DIR = miphamHome('sessions')
|
|
34
33
|
const INDEX_FILE = join(SESSIONS_DIR, '.index.json')
|
|
35
34
|
const SUMMARIES_DIR = join(SESSIONS_DIR, '.summaries')
|
|
36
35
|
|
|
@@ -68,6 +68,8 @@ export interface TaskPerformanceReport {
|
|
|
68
68
|
score: number
|
|
69
69
|
results: TaskPerformanceResult[]
|
|
70
70
|
failures: string[]
|
|
71
|
+
/** B2 代价维:整轮(LLM 生成 + 冻结测试判定)的墙钟耗时。只记录,不参与任何判定。 */
|
|
72
|
+
durationMs: number
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/** 剥掉 LLM 可能包裹的 markdown 代码块(```ts ... ```),拿到裸代码。 */
|
|
@@ -101,6 +103,7 @@ export async function runTaskPerformance(
|
|
|
101
103
|
const wanted = opts?.skill?.name
|
|
102
104
|
const tasks = loadPerformanceTasks().filter((t) => (t.skill ?? undefined) === wanted)
|
|
103
105
|
const results: TaskPerformanceResult[] = []
|
|
106
|
+
const startedAt = Date.now()
|
|
104
107
|
for (const task of tasks) {
|
|
105
108
|
const code = await collectGeneratedCode(llm, task.prompt, opts?.skill?.text)
|
|
106
109
|
if (!code) {
|
|
@@ -127,6 +130,7 @@ export async function runTaskPerformance(
|
|
|
127
130
|
score: results.length > 0 ? Math.round((passed / results.length) * 100) : 100,
|
|
128
131
|
results,
|
|
129
132
|
failures: results.filter((r) => !r.passed).map((r) => r.id),
|
|
133
|
+
durationMs: Date.now() - startedAt,
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
|
|
@@ -210,6 +214,13 @@ export interface SkillDeltaSample {
|
|
|
210
214
|
skillName: string
|
|
211
215
|
baselineScores: number[]
|
|
212
216
|
postScores: number[]
|
|
217
|
+
/**
|
|
218
|
+
* B2 代价维:与分数数组**逐项对齐**的耗时(第 i 项就是产出第 i 个分数的那次采样)。
|
|
219
|
+
* 缺席 = 该样本未记代价(旧记录 / 旧调用点),读侧须按「缺席」处理、不得当成 0。
|
|
220
|
+
* 只记录,不参与改进判定 —— 样本量 k=3 时再加一个维度只会让统计问题更糟。
|
|
221
|
+
*/
|
|
222
|
+
baselineDurations?: number[]
|
|
223
|
+
postDurations?: number[]
|
|
213
224
|
}
|
|
214
225
|
|
|
215
226
|
/**
|
|
@@ -227,18 +238,28 @@ export async function measureSkillDeltaRepeated(
|
|
|
227
238
|
const k = opts?.k ?? 3
|
|
228
239
|
const baselineScores: number[] = []
|
|
229
240
|
const postScores: number[] = []
|
|
241
|
+
const baselineDurations: number[] = []
|
|
242
|
+
const postDurations: number[] = []
|
|
230
243
|
for (let i = 0; i < k; i++) {
|
|
231
244
|
const r = await runTaskPerformance(llm, {
|
|
232
245
|
skill: { name: resolved.skillName, text: resolved.baselineText },
|
|
233
246
|
})
|
|
234
247
|
baselineScores.push(r.score)
|
|
248
|
+
baselineDurations.push(r.durationMs)
|
|
235
249
|
}
|
|
236
250
|
for (let i = 0; i < k; i++) {
|
|
237
251
|
const r = await runTaskPerformance(llm, {
|
|
238
252
|
skill: { name: resolved.skillName, text: resolved.postText },
|
|
239
253
|
})
|
|
240
254
|
postScores.push(r.score)
|
|
255
|
+
postDurations.push(r.durationMs)
|
|
241
256
|
}
|
|
242
257
|
|
|
243
|
-
return {
|
|
258
|
+
return {
|
|
259
|
+
skillName: resolved.skillName,
|
|
260
|
+
baselineScores,
|
|
261
|
+
postScores,
|
|
262
|
+
baselineDurations,
|
|
263
|
+
postDurations,
|
|
264
|
+
}
|
|
244
265
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
2
2
|
import { join, dirname, resolve } from 'node:path'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
3
|
import { atomicWriteFileSync } from '../shared/atomic-write'
|
|
4
|
+
import { miphamHome } from './paths.ts'
|
|
5
|
+
import { MIPHAM_DIR } from '../shared/constants.ts'
|
|
5
6
|
|
|
6
|
-
const MIPHAM_HOME =
|
|
7
|
+
const MIPHAM_HOME = miphamHome()
|
|
7
8
|
const TRUST_STORE_PATH = join(MIPHAM_HOME, 'trusted-workspaces.json')
|
|
8
9
|
|
|
9
10
|
export interface TrustedWorkspaces {
|
|
@@ -198,7 +199,7 @@ export function resetWorkspaceTrust(): void {
|
|
|
198
199
|
*/
|
|
199
200
|
export function warnProjectHooksSkipped(cwd: string): void {
|
|
200
201
|
process.stderr.write(
|
|
201
|
-
`⚠️ Workspace not trusted: skipped hooks from ${join(cwd,
|
|
202
|
+
`⚠️ Workspace not trusted: skipped hooks from ${join(cwd, MIPHAM_DIR, 'settings.json')}\n` +
|
|
202
203
|
` (hooks run commands — repository-controlled). Trust this directory in an interactive\n` +
|
|
203
204
|
` session to enable them.\n`,
|
|
204
205
|
)
|
package/src/daemon/database.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// apps/cli/src/daemon/database.ts
|
|
2
2
|
import { Database } from 'bun:sqlite'
|
|
3
3
|
import { readdirSync, readFileSync, existsSync } from 'node:fs'
|
|
4
|
-
import { homedir } from 'node:os'
|
|
5
4
|
import { join } from 'node:path'
|
|
6
5
|
import type {
|
|
7
6
|
DaemonSession,
|
|
@@ -12,6 +11,7 @@ import type {
|
|
|
12
11
|
CreateSessionInput,
|
|
13
12
|
SessionStatus,
|
|
14
13
|
} from './types'
|
|
14
|
+
import { miphamHome } from '../core/paths.ts'
|
|
15
15
|
|
|
16
16
|
export class DaemonDatabase {
|
|
17
17
|
private db: Database
|
|
@@ -340,7 +340,7 @@ export class DaemonDatabase {
|
|
|
340
340
|
// ── Migration ──────────────────────────────────────────────
|
|
341
341
|
|
|
342
342
|
migrateFromJsonl(): number {
|
|
343
|
-
const sessionsDir =
|
|
343
|
+
const sessionsDir = miphamHome('sessions')
|
|
344
344
|
|
|
345
345
|
if (!existsSync(sessionsDir)) return 0
|
|
346
346
|
|
package/src/daemon/index.ts
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
import { join } from 'node:path'
|
|
9
9
|
import { existsSync, readFileSync, writeFileSync, unlinkSync, mkdirSync } from 'node:fs'
|
|
10
10
|
import { createServer as createNetServer } from 'node:net'
|
|
11
|
-
import { homedir } from 'node:os'
|
|
12
11
|
import type { Server } from 'bun'
|
|
13
12
|
import { DaemonDatabase } from './database'
|
|
14
13
|
import { SessionManager } from './session-manager'
|
|
@@ -32,9 +31,9 @@ import type { WecomConfig } from './wecom/types.js'
|
|
|
32
31
|
import { parseDingtalkEnv } from './dingtalk/env.js'
|
|
33
32
|
import type { DingtalkConfig } from './dingtalk/types.js'
|
|
34
33
|
import { loadConfig } from '../config/loader'
|
|
34
|
+
import { miphamHome } from '../core/paths.ts'
|
|
35
35
|
|
|
36
|
-
const
|
|
37
|
-
const MIPHAM_HOME = join(HOME, '.mipham')
|
|
36
|
+
const MIPHAM_HOME = miphamHome()
|
|
38
37
|
const DB_PATH = join(MIPHAM_HOME, 'daemon.db')
|
|
39
38
|
const TOKEN_PATH = join(MIPHAM_HOME, 'daemon.token')
|
|
40
39
|
const PID_FILE = join(MIPHAM_HOME, 'daemon.pid')
|
package/src/daemon/launch.ts
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
import { spawn, type SpawnOptions } from 'node:child_process'
|
|
15
15
|
import { closeSync, mkdirSync, openSync, readFileSync, statSync } from 'node:fs'
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { dirname, resolve } from 'node:path'
|
|
17
|
+
import { miphamHome } from '../core/paths.ts'
|
|
18
18
|
|
|
19
19
|
/** argv sentinel that re-enters this program as a daemon. Not user-facing. */
|
|
20
20
|
export const DAEMON_ENTRY = '__daemon'
|
|
21
21
|
|
|
22
|
-
const DEFAULT_LOG_FILE =
|
|
22
|
+
const DEFAULT_LOG_FILE = miphamHome('daemon.log')
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* argv prefix that re-runs *this* program.
|
package/src/daemon/server.ts
CHANGED
|
@@ -63,10 +63,25 @@ interface WsData {
|
|
|
63
63
|
sessionId: string
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Values `MIPHAM_DAEMON_PERMISSION` accepts. The **default stays `'default'`**;
|
|
68
|
+
* every widening is an explicit operator choice.
|
|
69
|
+
*
|
|
70
|
+
* `'auto'` is accepted, and today it means **every tool call is refused**: the
|
|
71
|
+
* mode's static baseline answers `'ask'` for all of them, and the daemon has no
|
|
72
|
+
* classifier to rule on the `'ask'` (the classifier is wired in the CLI's
|
|
73
|
+
* `index.tsx`, not here). Refusing with the mode named is the honest reading —
|
|
74
|
+
* `auto` means "there is no human to ask, let a classifier decide", and with no
|
|
75
|
+
* classifier there is nothing to decide with. The alternative (leaving it out of
|
|
76
|
+
* this set) would silently fall back to `'default'`, which is *more* permissive
|
|
77
|
+
* than what was asked for; a silent widening is the worse of the two ways to be
|
|
78
|
+
* wrong. This entry becomes useful the day the daemon builds a classifier.
|
|
79
|
+
*/
|
|
66
80
|
const DAEMON_PERMISSION_MODES: ReadonlySet<PermissionMode> = new Set<PermissionMode>([
|
|
67
81
|
'default',
|
|
68
82
|
'acceptEdits',
|
|
69
83
|
'plan',
|
|
84
|
+
'auto',
|
|
70
85
|
'bypassPermissions',
|
|
71
86
|
])
|
|
72
87
|
|
|
@@ -804,6 +804,7 @@
|
|
|
804
804
|
"manual": "manual mode",
|
|
805
805
|
"accept_edits": "accept edits on",
|
|
806
806
|
"plan_mode": "plan mode / read-only",
|
|
807
|
+
"auto_mode": "auto mode on",
|
|
807
808
|
"bypass": "bypass"
|
|
808
809
|
},
|
|
809
810
|
"loading": {
|
|
@@ -992,6 +993,8 @@
|
|
|
992
993
|
"tool_denied_deny_rule": "Tool \"{name}\" blocked by a deny rule (\"{pattern}\"). Deny rules override permission mode — try a different approach, or drop the rule with: /permissions remove \"{pattern}\"",
|
|
993
994
|
"tool_denied_ask_rule": "Tool \"{name}\" requires approval (ask rule: \"{pattern}\"). Approve when prompted, or stop being asked with: /permissions allow \"{pattern}\"",
|
|
994
995
|
"tool_denied_mode": "Tool \"{name}\" requires approval under \"{mode}\" mode. Press Shift+Tab to switch permission mode, or stop being asked with: /permissions allow \"{name}\" (or a narrower \"{name}(arg)\")",
|
|
996
|
+
"tool_denied_classifier": "Tool \"{name}\" was blocked in auto mode by the permission classifier ({reason}). This is a policy decision, not a failure — switching modes will not change it, and repeating the identical call returns the same ruling. Change the call so it no longer trips the rule, or allow it outright with: /permissions allow \"{name}\"",
|
|
997
|
+
"tool_denied_classifier_unavailable": "Tool \"{name}\" was held back because the auto-mode classifier could not be reached or its answer could not be read ({reason}). This is NOT a policy decision — the call was never judged, and retrying is appropriate. If this keeps happening, switch modes with Shift+Tab.",
|
|
995
998
|
"tool_blocked": "Tool \"{name}\" blocked by hook",
|
|
996
999
|
"user_input_blocked": "User input blocked by hook.",
|
|
997
1000
|
"dlp_blocked": "Request blocked by DLP policy.",
|
|
@@ -804,6 +804,7 @@
|
|
|
804
804
|
"manual": "手动模式",
|
|
805
805
|
"accept_edits": "接受编辑",
|
|
806
806
|
"plan_mode": "计划模式 / 只读",
|
|
807
|
+
"auto_mode": "自动模式",
|
|
807
808
|
"bypass": "绕过权限"
|
|
808
809
|
},
|
|
809
810
|
"loading": {
|
|
@@ -992,6 +993,8 @@
|
|
|
992
993
|
"tool_denied_deny_rule": "工具 \"{name}\" 被拒绝规则(\"{pattern}\")阻止。拒绝规则优先于权限模式 — 请改用其他方式,或移除该规则:/permissions remove \"{pattern}\"",
|
|
993
994
|
"tool_denied_ask_rule": "工具 \"{name}\" 需要批准(ask 规则:\"{pattern}\")。请在提示时批准,或不再询问:/permissions allow \"{pattern}\"",
|
|
994
995
|
"tool_denied_mode": "工具 \"{name}\" 在 \"{mode}\" 模式下需要批准。按 Shift+Tab 切换权限模式,或不再询问:/permissions allow \"{name}\"(更窄的 \"{name}(arg)\" 亦可)",
|
|
996
|
+
"tool_denied_classifier": "工具 \"{name}\" 在 auto 模式下被权限分类器拦下({reason})。这是策略裁决、不是故障 —— 切换模式不会改变它,把同一次调用原样重发也只会得到同一个裁决。请改变这次调用使其不再触犯该规则,或直接放行:/permissions allow \"{name}\"",
|
|
997
|
+
"tool_denied_classifier_unavailable": "工具 \"{name}\" 被暂缓:auto 模式的分类器不可达,或其回复无法解析({reason})。这**不是**策略裁决 —— 该调用从未被裁决,重试是恰当的。若持续发生,按 Shift+Tab 切换模式。",
|
|
995
998
|
"tool_blocked": "工具 \"{name}\" 被钩子拦截",
|
|
996
999
|
"user_input_blocked": "用户输入被钩子拦截。",
|
|
997
1000
|
"dlp_blocked": "请求被 DLP 策略阻止。",
|
package/src/index.tsx
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import { join, basename } from 'node:path'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
3
|
import { existsSync } from 'node:fs'
|
|
5
4
|
import { render } from 'ink'
|
|
6
5
|
import * as readline from 'node:readline'
|
|
@@ -33,6 +32,7 @@ import { SessionLog } from './core/session-log'
|
|
|
33
32
|
import { SessionStore } from './core/session-store'
|
|
34
33
|
import type { PermissionLevel, MiphamConfig, McpServerConfig } from './shared/types'
|
|
35
34
|
import { PermissionSystem } from './core/permission'
|
|
35
|
+
import { LlmPermissionClassifier } from './core/permission-classifier'
|
|
36
36
|
import { SkillsLoader } from './skills/loader'
|
|
37
37
|
import { PluginManager } from './plugin/plugin-manager'
|
|
38
38
|
import { loadPlugins } from './plugin/plugin-loader'
|
|
@@ -54,7 +54,7 @@ import { initTelemetry, enableTelemetryNow } from './telemetry/index'
|
|
|
54
54
|
import { wasPrompted, markPrompted, isInteractive, setTelemetryEnabled } from './telemetry/consent'
|
|
55
55
|
import { officialEndpointHost } from './telemetry/endpoint'
|
|
56
56
|
import { getWorkspaceTrust, warnProjectHooksSkipped } from './core/workspace-trust'
|
|
57
|
-
import { ARTIFACT_PORT } from './shared/constants'
|
|
57
|
+
import { ARTIFACT_PORT, MIPHAM_DIR } from './shared/constants'
|
|
58
58
|
import { artifactsRoot } from './artifacts/paths'
|
|
59
59
|
import { AgentViewManager } from './agent-view/agent-view-manager'
|
|
60
60
|
import { AgentViewDashboard } from './agent-view/dashboard'
|
|
@@ -65,6 +65,7 @@ import { ConfigWizard } from './ui/config-wizard'
|
|
|
65
65
|
import enUS from './i18n-core/locales/en-US.json' with { type: 'json' }
|
|
66
66
|
import zhCN from './i18n-core/locales/zh-CN.json' with { type: 'json' }
|
|
67
67
|
import type { TranslationMap } from './i18n-core/types'
|
|
68
|
+
import { miphamHome } from './core/paths.ts'
|
|
68
69
|
|
|
69
70
|
// Locale bundles — defined at module level so the remote-attach branch can
|
|
70
71
|
// reference them before the full bootstrap path runs.
|
|
@@ -401,6 +402,21 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
401
402
|
const defaultModel = options.model || config.defaultModel
|
|
402
403
|
const registry = bootstrapProviders(config.providers, defaultProvider, defaultModel)
|
|
403
404
|
|
|
405
|
+
// Attach `auto` mode's classifier here, where the provider registry exists —
|
|
406
|
+
// `PermissionSystem` itself imports no provider module, so the classifier is handed
|
|
407
|
+
// in rather than built inside. Doing it on the permission system instead of the
|
|
408
|
+
// engine is deliberate: an engine-side setter would be a new engine capability the
|
|
409
|
+
// daemon would then have to match or be exempted from
|
|
410
|
+
// (`test/integrity/daemon-capability-parity.test.ts`). Because that guard therefore
|
|
411
|
+
// cannot see this line, the wiring is asserted separately at the source level.
|
|
412
|
+
//
|
|
413
|
+
// The model is resolved per ruling through the thunk: capturing the active model
|
|
414
|
+
// here would freeze it at whatever was active at startup, so a user who switches to
|
|
415
|
+
// a cheaper model would keep paying for the old one with nothing on screen to say so.
|
|
416
|
+
permission.setClassifier(
|
|
417
|
+
new LlmPermissionClassifier(registry, { resolveModel: () => registry.getActiveModel() }),
|
|
418
|
+
)
|
|
419
|
+
|
|
404
420
|
// Load instructions
|
|
405
421
|
const instructions = new InstructionsLoader()
|
|
406
422
|
instructions.loadAll(process.cwd())
|
|
@@ -529,8 +545,8 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
529
545
|
// First-run setup detection: inject mipham-code-setup guidance when
|
|
530
546
|
// no project or user config exists. This mirrors Claude Code's first-run
|
|
531
547
|
// onboarding flow — the AI proactively offers to help configure the tool.
|
|
532
|
-
const hasProjectConfig = existsSync(join(process.cwd(),
|
|
533
|
-
const hasUserConfig = existsSync(
|
|
548
|
+
const hasProjectConfig = existsSync(join(process.cwd(), MIPHAM_DIR, 'config.yml'))
|
|
549
|
+
const hasUserConfig = existsSync(miphamHome('config.yml'))
|
|
534
550
|
const hasMiphamMd = existsSync(join(process.cwd(), 'MIPHAM.md'))
|
|
535
551
|
if (!hasProjectConfig && !hasUserConfig) {
|
|
536
552
|
const setupReminder = [
|
|
@@ -672,6 +688,14 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
672
688
|
process.stderr.write(`⚠ Mipham Code: ${msg}\n`)
|
|
673
689
|
}
|
|
674
690
|
|
|
691
|
+
// Third channel: an unrecognized `permission:` value in config.yml. Until
|
|
692
|
+
// `setDefaultLevel` honoured real mode names, *no* value except `bypass` could
|
|
693
|
+
// report anything — they all just became `default`. Warnings are collected on the
|
|
694
|
+
// instance, so this has to run after the config was applied above.
|
|
695
|
+
for (const msg of engine.getPermission().getInvalidPermissionMode()) {
|
|
696
|
+
process.stderr.write(`⚠ Mipham Code: ${msg}\n`)
|
|
697
|
+
}
|
|
698
|
+
|
|
675
699
|
// Initialize agent registry and load plugin agents/skills/MCP/hooks
|
|
676
700
|
const agentRegistry = new AgentRegistry()
|
|
677
701
|
agentRegistry.loadUserAgents()
|
|
@@ -815,8 +839,8 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
815
839
|
})
|
|
816
840
|
|
|
817
841
|
// ── First-run detection: show interactive ConfigWizard if no config exists ──
|
|
818
|
-
const hasUserConfig = existsSync(
|
|
819
|
-
const hasProjectConfig = existsSync(join(process.cwd(),
|
|
842
|
+
const hasUserConfig = existsSync(miphamHome('config.yml'))
|
|
843
|
+
const hasProjectConfig = existsSync(join(process.cwd(), MIPHAM_DIR, 'config.yml'))
|
|
820
844
|
const needsSetup = !hasUserConfig && !hasProjectConfig
|
|
821
845
|
|
|
822
846
|
// Only ask when the first-run wizard is *not* about to take over the
|
package/src/mcp/token-store.ts
CHANGED
|
@@ -8,8 +8,8 @@ import {
|
|
|
8
8
|
chmodSync,
|
|
9
9
|
} from 'node:fs'
|
|
10
10
|
import { join, dirname } from 'node:path'
|
|
11
|
-
import { homedir } from 'node:os'
|
|
12
11
|
import { encrypt, decrypt, getCredentialKey } from '../config/credential-crypto'
|
|
12
|
+
import { miphamHome } from '../core/paths.ts'
|
|
13
13
|
|
|
14
14
|
interface TokenData {
|
|
15
15
|
accessToken: string
|
|
@@ -24,7 +24,7 @@ export class TokenStore {
|
|
|
24
24
|
private storeDir: string
|
|
25
25
|
|
|
26
26
|
constructor(storeDir?: string) {
|
|
27
|
-
this.storeDir = storeDir ||
|
|
27
|
+
this.storeDir = storeDir || miphamHome('mcp-tokens')
|
|
28
28
|
this.key = getCredentialKey(dirname(this.storeDir))
|
|
29
29
|
}
|
|
30
30
|
|
|
@@ -8,12 +8,12 @@ import {
|
|
|
8
8
|
renameSync,
|
|
9
9
|
} from 'node:fs'
|
|
10
10
|
import { join } from 'node:path'
|
|
11
|
-
import { homedir } from 'node:os'
|
|
12
11
|
import { execFileSync } from 'node:child_process'
|
|
13
12
|
import { validatePlugin } from './plugin-validator'
|
|
14
13
|
import { atomicWriteFileSync } from '../shared/atomic-write'
|
|
14
|
+
import { miphamHome } from '../core/paths.ts'
|
|
15
15
|
|
|
16
|
-
const PLUGIN_DIR =
|
|
16
|
+
const PLUGIN_DIR = miphamHome('plugins')
|
|
17
17
|
|
|
18
18
|
export interface InstalledPlugin {
|
|
19
19
|
name: string
|
package/src/shared/constants.ts
CHANGED
|
@@ -539,7 +539,6 @@ export const TOOL_CATEGORIES = [
|
|
|
539
539
|
] as const
|
|
540
540
|
export const CONFIG_FILE_NAME = 'config.yml'
|
|
541
541
|
export const MIPHAM_DIR = '.mipham'
|
|
542
|
-
export const USER_CONFIG_DIR = '.mipham'
|
|
543
542
|
export const MEMORY_DIR = 'memory'
|
|
544
543
|
|
|
545
544
|
/** 预置 Ollama 模型 — 与 ollama list 结果合并去重后展示 */
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
export const PACKAGE_NAME = '@miphamai/cli' as const
|
|
10
10
|
|
|
11
11
|
/** 当前发布版本 */
|
|
12
|
-
export const PACKAGE_VERSION = '0.
|
|
12
|
+
export const PACKAGE_VERSION = '0.84.0' as const
|
|
13
13
|
|
|
14
14
|
/** npm install 全局安装命令 */
|
|
15
15
|
export const NPM_INSTALL_COMMAND = `npm install -g ${PACKAGE_NAME}` as const
|
package/src/shared/types.ts
CHANGED
|
@@ -67,7 +67,14 @@ export interface Message {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
// ── Tool Types ──
|
|
70
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Legacy 3-level tool permission (predates `PermissionMode`).
|
|
72
|
+
* `'self'` means "let the tool self-decide" — i.e. allowed, but not flagged as `'bypass'`.
|
|
73
|
+
* Renamed from `'auto'` (2026-09-22) so that `auto` can name the *classifier mode*
|
|
74
|
+
* without a second meaning living in `PermissionLevel` — see
|
|
75
|
+
* `docs/superpowers/specs/2026-09-22-permission-classifier-design.md` §3.1.
|
|
76
|
+
*/
|
|
77
|
+
export type ToolPermission = 'self' | 'ask' | 'bypass'
|
|
71
78
|
export type ToolCategory =
|
|
72
79
|
'file' | 'exec' | 'agent' | 'network' | 'system' | 'artifact' | 'scheduling'
|
|
73
80
|
|
|
@@ -326,11 +333,44 @@ export interface InstructionFile {
|
|
|
326
333
|
}
|
|
327
334
|
|
|
328
335
|
// ── Permission Types ──
|
|
329
|
-
/**
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Permission modes, matching Claude Code's permission architecture.
|
|
338
|
+
*
|
|
339
|
+
* `bypassPermissions` is legal but **off the Shift+Tab cycle** — the cycle is the
|
|
340
|
+
* four slots Claude Code's own cycle array lists (`default`, `acceptEdits`,
|
|
341
|
+
* `plan`, `auto`), while this union is the full set the config may request. See
|
|
342
|
+
* `ALL_MODES` / `MODE_CYCLE` in `core/permission-config.ts` for why those two
|
|
343
|
+
* arrays are separate, and why merging them silently demotes
|
|
344
|
+
* `bypassPermissions`.
|
|
345
|
+
*
|
|
346
|
+
* `auto` behaves unlike the other four: it does not decide anything statically.
|
|
347
|
+
* Its baseline answers `'ask'` for **every** call, and the ruling is delegated to
|
|
348
|
+
* the LLM permission classifier (`core/permission-classifier.ts`). Two
|
|
349
|
+
* consequences worth knowing before reading code that switches on this union:
|
|
350
|
+
*
|
|
351
|
+
* - `auto` has **no static width**, so a "which mode is narrower" measurement
|
|
352
|
+
* taken from the static chain reads it as narrower than `plan` — a
|
|
353
|
+
* measurement of the wrong object, not a fact about `auto`.
|
|
354
|
+
* - A mode whose static baseline is `'ask'` must not be *reached* by a caller
|
|
355
|
+
* that never consults the classifier, or it degrades into "refuse everything".
|
|
356
|
+
*
|
|
357
|
+
* This copy is kept in step with `packages/shared/src/types.ts` mechanically:
|
|
358
|
+
* `test/integrity/shared-types-parity.test.ts` asserts that every declaration
|
|
359
|
+
* the two files share has the same member set (and, for union aliases, the same
|
|
360
|
+
* literal set). Member *lists* are all it covers — a stale prose default is
|
|
361
|
+
* still a human's to catch.
|
|
362
|
+
*/
|
|
363
|
+
export type PermissionMode = 'default' | 'acceptEdits' | 'plan' | 'auto' | 'bypassPermissions'
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Backward-compatible alias: `PermissionMode` plus the legacy 3-level
|
|
367
|
+
* `'self'`/`'ask'`/`'bypass'`. The legacy level was named `'auto'` until 2026-09-22;
|
|
368
|
+
* that name now belongs to the classifier mode, so the two can no longer be
|
|
369
|
+
* confused for one another (the `fc5afd3a` incident was exactly that confusion:
|
|
370
|
+
* a config `permission: auto` meaning "tool self-decides" silently becoming
|
|
371
|
+
* "run everything").
|
|
372
|
+
*/
|
|
373
|
+
export type PermissionLevel = PermissionMode | 'self' | 'ask' | 'bypass'
|
|
334
374
|
|
|
335
375
|
/** Org-level restrictions that cap or forbid specific permission modes. */
|
|
336
376
|
export interface PermissionRestrictions {
|
package/src/shared/update.ts
CHANGED
|
@@ -9,12 +9,11 @@
|
|
|
9
9
|
import { readFileSync, existsSync, copyFileSync, mkdirSync, chmodSync } from 'node:fs'
|
|
10
10
|
import { join } from 'node:path'
|
|
11
11
|
import { execSync } from 'node:child_process'
|
|
12
|
-
import { homedir } from 'node:os'
|
|
13
12
|
import { PACKAGE_VERSION } from './package-info'
|
|
13
|
+
import { miphamHome } from '../core/paths.ts'
|
|
14
14
|
|
|
15
15
|
const PACKAGE = '@miphamai/cli'
|
|
16
|
-
const
|
|
17
|
-
const MIPHAM_HOME = join(HOME, '.mipham')
|
|
16
|
+
const MIPHAM_HOME = miphamHome()
|
|
18
17
|
const CONFIG_PATH = join(MIPHAM_HOME, 'config.yml')
|
|
19
18
|
|
|
20
19
|
// Registry fallback chain: npm default → npmmirror (China mirror)
|