@miphamai/cli 0.83.0 → 0.85.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/bin/mipham.ts +20 -5
- 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 +4 -3
- package/src/core/improvement-track.ts +5 -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/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 +7 -1
- package/src/i18n-core/locales/zh-CN.json +7 -1
- 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 +255 -20
- 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 +26 -21
- package/src/ui/config-wizard.tsx +2 -2
- package/src/ui/input.tsx +15 -3
- package/src/workflow/journal.ts +2 -2
package/src/tools/agent/plan.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import type { ToolDefinition } from '../../shared/index.ts'
|
|
4
|
+
import { MIPHAM_DIR } from '../../shared/constants.ts'
|
|
4
5
|
|
|
5
6
|
export const planTool: ToolDefinition = {
|
|
6
7
|
name: 'Plan',
|
|
7
8
|
description:
|
|
8
9
|
'Enter plan mode — read-only analysis and design. Creates a structured plan file in .mipham/plans/.',
|
|
9
10
|
category: 'agent',
|
|
10
|
-
permission: '
|
|
11
|
+
permission: 'self',
|
|
11
12
|
parameters: {
|
|
12
13
|
type: 'object',
|
|
13
14
|
properties: {
|
|
@@ -25,7 +26,7 @@ export const planTool: ToolDefinition = {
|
|
|
25
26
|
const title = (params.title as string) || 'Implementation Plan'
|
|
26
27
|
const description = (params.description as string) || ''
|
|
27
28
|
|
|
28
|
-
const planDir = join(ctx.cwd,
|
|
29
|
+
const planDir = join(ctx.cwd, MIPHAM_DIR, 'plans')
|
|
29
30
|
mkdirSync(planDir, { recursive: true })
|
|
30
31
|
|
|
31
32
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
|
|
@@ -7,7 +7,7 @@ export const reportFindingsTool: ToolDefinition = {
|
|
|
7
7
|
'Use this to output structured review results with file, line, summary, ' +
|
|
8
8
|
'failure_scenario, and category. Findings are ranked most-severe first.',
|
|
9
9
|
category: 'agent',
|
|
10
|
-
permission: '
|
|
10
|
+
permission: 'self',
|
|
11
11
|
parameters: {
|
|
12
12
|
type: 'object',
|
|
13
13
|
properties: {
|
|
@@ -8,7 +8,7 @@ export const sendMessageTool: ToolDefinition = {
|
|
|
8
8
|
'Use "main" for the parent conversation, a background task ID for same-process agents, ' +
|
|
9
9
|
'or a session ID (or unique session name) for cross-session messaging (use ListAgents to discover sessions).',
|
|
10
10
|
category: 'agent',
|
|
11
|
-
permission: '
|
|
11
|
+
permission: 'self',
|
|
12
12
|
parameters: {
|
|
13
13
|
type: 'object',
|
|
14
14
|
properties: {
|
package/src/tools/agent/skill.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const skillTool: ToolDefinition = {
|
|
|
10
10
|
description:
|
|
11
11
|
'Execute a skill (.SKILL.md or .mipham-skill.md) by name. Skills extend AI capabilities with specialized instructions.',
|
|
12
12
|
category: 'agent',
|
|
13
|
-
permission: '
|
|
13
|
+
permission: 'self',
|
|
14
14
|
parameters: {
|
|
15
15
|
type: 'object',
|
|
16
16
|
properties: {
|
package/src/tools/exec/git.ts
CHANGED
|
@@ -205,7 +205,7 @@ export const gitTool: ToolDefinition = {
|
|
|
205
205
|
name: 'Git',
|
|
206
206
|
description: 'Execute git commands. Dangerous operations (force push, hard reset) are blocked.',
|
|
207
207
|
category: 'exec',
|
|
208
|
-
permission: '
|
|
208
|
+
permission: 'self',
|
|
209
209
|
parameters: {
|
|
210
210
|
type: 'object',
|
|
211
211
|
properties: {
|
|
@@ -240,7 +240,7 @@ export const gitTool: ToolDefinition = {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
-
// Git runs without an approval prompt (`permission: '
|
|
243
|
+
// Git runs without an approval prompt (`permission: 'self'`), so an option
|
|
244
244
|
// that names a program to execute is a code-execution path Bash would have
|
|
245
245
|
// had to ask for. Checked on argv, which is what git is handed below.
|
|
246
246
|
const argv = splitCommand(command)
|
package/src/tools/exec/task.ts
CHANGED
|
@@ -61,7 +61,7 @@ export const taskTool: ToolDefinition = {
|
|
|
61
61
|
'background task output/stop, and status workflow: pending → in_progress → completed. ' +
|
|
62
62
|
'Use for complex multi-step tasks, session tracking, and organizing work.',
|
|
63
63
|
category: 'exec',
|
|
64
|
-
permission: '
|
|
64
|
+
permission: 'self',
|
|
65
65
|
parameters: {
|
|
66
66
|
type: 'object',
|
|
67
67
|
properties: {
|
package/src/tools/file/glob.ts
CHANGED
|
@@ -15,7 +15,7 @@ export function createGlobTool(credentialConfig?: CredentialMaskingConfig): Tool
|
|
|
15
15
|
name: 'Glob',
|
|
16
16
|
description: 'Find files matching a glob pattern.',
|
|
17
17
|
category: 'file',
|
|
18
|
-
permission: '
|
|
18
|
+
permission: 'self',
|
|
19
19
|
parameters: {
|
|
20
20
|
type: 'object',
|
|
21
21
|
properties: {
|
package/src/tools/file/grep.ts
CHANGED
|
@@ -88,7 +88,7 @@ export function createGrepTool(credentialConfig?: CredentialMaskingConfig): Tool
|
|
|
88
88
|
'(glob pattern, e.g. "*.ts") before searching. ' +
|
|
89
89
|
'The `pattern` parameter accepts full regex syntax (e.g., "log.*Error", "\\bclass\\s+\\w+").',
|
|
90
90
|
category: 'file',
|
|
91
|
-
permission: '
|
|
91
|
+
permission: 'self',
|
|
92
92
|
parameters: {
|
|
93
93
|
type: 'object',
|
|
94
94
|
properties: {
|
package/src/tools/file/read.ts
CHANGED
|
@@ -121,7 +121,7 @@ export function createReadTool(credentialConfig?: CredentialMaskingConfig): Tool
|
|
|
121
121
|
description:
|
|
122
122
|
'Read a file from the local filesystem. Supports offset and limit for large files.',
|
|
123
123
|
category: 'file',
|
|
124
|
-
permission: '
|
|
124
|
+
permission: 'self',
|
|
125
125
|
parameters: {
|
|
126
126
|
type: 'object',
|
|
127
127
|
properties: {
|
|
@@ -148,7 +148,7 @@ export const webFetchTool: ToolDefinition = {
|
|
|
148
148
|
description:
|
|
149
149
|
'Fetches a URL, converts the page to markdown. HTTP is upgraded to HTTPS. Cross-host redirects are returned to the caller. Responses are cached for 15 minutes per URL.',
|
|
150
150
|
category: 'network',
|
|
151
|
-
permission: '
|
|
151
|
+
permission: 'self',
|
|
152
152
|
parameters: {
|
|
153
153
|
type: 'object',
|
|
154
154
|
properties: {
|
|
@@ -81,7 +81,7 @@ export const webSearchTool: ToolDefinition = {
|
|
|
81
81
|
description:
|
|
82
82
|
'Search the web via Brave Search API. Returns result blocks with titles, URLs, and descriptions. Set BRAVE_API_KEY to enable.',
|
|
83
83
|
category: 'network',
|
|
84
|
-
permission: '
|
|
84
|
+
permission: 'self',
|
|
85
85
|
parameters: {
|
|
86
86
|
type: 'object',
|
|
87
87
|
properties: {
|
|
@@ -8,11 +8,11 @@ import {
|
|
|
8
8
|
} from 'node:fs'
|
|
9
9
|
import { join } from 'node:path'
|
|
10
10
|
import { createHash } from 'node:crypto'
|
|
11
|
-
import { homedir } from 'node:os'
|
|
12
11
|
import type { ToolDefinition } from '../../shared/index.ts'
|
|
13
12
|
import { computeNextFire } from '../../core/cron'
|
|
13
|
+
import { miphamHome } from '../../core/paths.ts'
|
|
14
14
|
|
|
15
|
-
const CRON_DIR =
|
|
15
|
+
const CRON_DIR = miphamHome('cron')
|
|
16
16
|
|
|
17
17
|
function ensureCronDir(): void {
|
|
18
18
|
if (!existsSync(CRON_DIR)) mkdirSync(CRON_DIR, { recursive: true })
|
|
@@ -97,7 +97,7 @@ export const cronCreateTool: ToolDefinition = {
|
|
|
97
97
|
'For one-shot: set recurring:false with pinned minute/hour/day-of-month/month. ' +
|
|
98
98
|
'Durable — survives restarts, written to ~/.mipham/cron/.',
|
|
99
99
|
category: 'scheduling',
|
|
100
|
-
permission: '
|
|
100
|
+
permission: 'self',
|
|
101
101
|
parameters: {
|
|
102
102
|
type: 'object',
|
|
103
103
|
properties: {
|
|
@@ -157,7 +157,7 @@ export const cronDeleteTool: ToolDefinition = {
|
|
|
157
157
|
description:
|
|
158
158
|
'Cancel a cron job previously scheduled with CronCreate. Removes from ~/.mipham/cron/.',
|
|
159
159
|
category: 'scheduling',
|
|
160
|
-
permission: '
|
|
160
|
+
permission: 'self',
|
|
161
161
|
parameters: {
|
|
162
162
|
type: 'object',
|
|
163
163
|
properties: {
|
|
@@ -181,7 +181,7 @@ export const cronListTool: ToolDefinition = {
|
|
|
181
181
|
name: 'CronList',
|
|
182
182
|
description: 'List all cron jobs scheduled via CronCreate, both durable and session-only.',
|
|
183
183
|
category: 'scheduling',
|
|
184
|
-
permission: '
|
|
184
|
+
permission: 'self',
|
|
185
185
|
parameters: {
|
|
186
186
|
type: 'object',
|
|
187
187
|
properties: {},
|
|
@@ -20,7 +20,7 @@ export const scheduleWakeupTool: ToolDefinition = {
|
|
|
20
20
|
description:
|
|
21
21
|
'Schedule when to resume work in /loop dynamic mode — the user invoked /loop without an interval, asking you to self-pace iterations of a specific task. Do NOT schedule a short-interval wakeup to poll for background work you started — when harness-tracked work finishes, you are re-invoked automatically. The runtime clamps to [60, 3600].',
|
|
22
22
|
category: 'scheduling',
|
|
23
|
-
permission: '
|
|
23
|
+
permission: 'self',
|
|
24
24
|
parameters: {
|
|
25
25
|
type: 'object',
|
|
26
26
|
properties: {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { readFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
2
2
|
import { join } from 'node:path'
|
|
3
|
-
import { homedir } from 'node:os'
|
|
4
3
|
import { parse as parseYaml, stringify } from 'yaml'
|
|
5
4
|
import { atomicWriteFileSync } from '../../shared/atomic-write'
|
|
6
5
|
import type { ToolDefinition } from '../../shared/index.ts'
|
|
6
|
+
import { miphamHome } from '../../core/paths.ts'
|
|
7
7
|
|
|
8
|
-
const MIPHAM_HOME =
|
|
8
|
+
const MIPHAM_HOME = miphamHome()
|
|
9
9
|
const USER_CONFIG = join(MIPHAM_HOME, 'config.yml')
|
|
10
10
|
|
|
11
11
|
export const configTool: ToolDefinition = {
|
|
@@ -16,7 +16,7 @@ export const toolSearchTool: ToolDefinition = {
|
|
|
16
16
|
'Use this to discover tools on demand instead of loading all tool definitions into context. ' +
|
|
17
17
|
'Returns matching tool names with their server and description.',
|
|
18
18
|
category: 'system',
|
|
19
|
-
permission: '
|
|
19
|
+
permission: 'self',
|
|
20
20
|
parameters: {
|
|
21
21
|
type: 'object',
|
|
22
22
|
properties: {
|
package/src/ui/app.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import type { RemoteEngine } from '../daemon/remote-engine'
|
|
|
9
9
|
import type { MiphamConfig } from '../shared/index.ts'
|
|
10
10
|
import type { Llm } from '../providers/llm'
|
|
11
11
|
import { AUTOCOMPLETE_MAX_CONTEXT, type RecentMessage } from '../core/autocomplete'
|
|
12
|
+
import { MODE_CYCLE } from '../core/permission-config'
|
|
12
13
|
import { resolveGitPr, prColor, type GitPr } from '../core/git-pr'
|
|
13
14
|
import type { SkillsLoader } from '../skills/loader'
|
|
14
15
|
import type { PluginManager } from '../plugin/plugin-manager'
|
|
@@ -103,17 +104,43 @@ interface AgentProgress {
|
|
|
103
104
|
// Version is read fresh from package.json at startup via runApp prop
|
|
104
105
|
// (bypasses Bun module caching after npm update)
|
|
105
106
|
|
|
106
|
-
//
|
|
107
|
-
|
|
107
|
+
// The wheel is `MODE_CYCLE` — imported, not copied. This file used to hold its own
|
|
108
|
+
// array, which is how a help screen and a footer come to advertise a mode the wheel
|
|
109
|
+
// cannot reach: two lists that agree on the day they are written and drift after.
|
|
108
110
|
// Labels aligned with Claude Code terminology: describe behavior, not capability.
|
|
109
|
-
// Claude Code modes: manual mode → accept edits on → plan → bypass.
|
|
110
111
|
const PERMISSION_COLORS: Record<PermissionMode, string> = {
|
|
111
112
|
default: 'white',
|
|
112
113
|
acceptEdits: 'blue',
|
|
113
114
|
plan: 'yellow',
|
|
115
|
+
auto: 'magenta',
|
|
114
116
|
bypassPermissions: 'red',
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
// 页脚那一行的**字形**,取值与 Claude Code 二进制里那四个格逐字对齐(`$Pe="⏸"` /
|
|
120
|
+
// `Ije="⏵⏵"`):`default` **什么都不显示**、`acceptEdits` 与 `auto` 都是 `⏵⏵`、`plan` 是 `⏸`。
|
|
121
|
+
// 这本是设计文档决策 9 的一半,Step 6/7 只做了标签派生、漏了字形,此处补上。
|
|
122
|
+
// `bypassPermissions` 在 CC 的转盘上没有对应格(它的转盘 4 格、我们这张是 5 档),
|
|
123
|
+
// 无从对照 ⇒ **保留既有渲染** `⏵⏵`,是最小的选择而不是新决定。
|
|
124
|
+
// 穷尽 `Record` 与 `PERMISSION_COLORS` 同形:将来加档位忘了字形是**编译错**,不是静默空串。
|
|
125
|
+
const PERMISSION_GLYPHS: Record<PermissionMode, string> = {
|
|
126
|
+
default: '',
|
|
127
|
+
acceptEdits: '⏵⏵',
|
|
128
|
+
plan: '⏸',
|
|
129
|
+
auto: '⏵⏵',
|
|
130
|
+
bypassPermissions: '⏵⏵',
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 页脚前缀 = `<字形> `,**没有字形时连那个空格都不留**(否则 `default` 那一行会以空格起头)。
|
|
135
|
+
*
|
|
136
|
+
* 与 `PERMISSION_LABELS` 合成**单独一个**文本节点交给 Ink:分两处写时,中间那点缩进是否
|
|
137
|
+
* 落成空格取决于 JSX 的空白折叠规则 —— 一件与权限无关、却会改变用户读到的东西的巧合。
|
|
138
|
+
*/
|
|
139
|
+
export function permissionGlyphPrefix(mode: PermissionMode): string {
|
|
140
|
+
const glyph = PERMISSION_GLYPHS[mode]
|
|
141
|
+
return glyph ? `${glyph} ` : ''
|
|
142
|
+
}
|
|
143
|
+
|
|
117
144
|
/** 页脚读模式所需的最小面 —— `QueryEngine` 与 `RemoteEngine` 都满足。 */
|
|
118
145
|
export type PermissionSource = {
|
|
119
146
|
setMode(mode: PermissionMode): void
|
|
@@ -136,8 +163,17 @@ export function cyclePermissionMode(
|
|
|
136
163
|
permission: PermissionSource,
|
|
137
164
|
current: PermissionMode,
|
|
138
165
|
): PermissionMode {
|
|
139
|
-
|
|
140
|
-
|
|
166
|
+
// An off-wheel *current* mode is reachable, not hypothetical: `bypassPermissions`
|
|
167
|
+
// is legal (`ALL_MODES`) without being cyclable, so `permission: bypassPermissions`
|
|
168
|
+
// in config puts the user in a state the wheel has no slot for. `indexOf` then
|
|
169
|
+
// answers `-1`, and `(-1 + 1) % length` lands on slot 0 by arithmetic accident.
|
|
170
|
+
// Spelled out here so it is a decision rather than an accident: off-wheel goes to
|
|
171
|
+
// the wheel's first slot, the same answer `nextMode` gives. That slot is `default`,
|
|
172
|
+
// which is **not** the narrowest mode (`plan` is) — the wheel is not a permissiveness
|
|
173
|
+
// order — but it *is* narrower than the only off-wheel state you can reach,
|
|
174
|
+
// `bypassPermissions`, so the step still points away from wider.
|
|
175
|
+
const idx = MODE_CYCLE.indexOf(current)
|
|
176
|
+
const next = idx === -1 ? MODE_CYCLE[0]! : MODE_CYCLE[(idx + 1) % MODE_CYCLE.length]!
|
|
141
177
|
permission.setMode(next)
|
|
142
178
|
return permission.getMode()
|
|
143
179
|
}
|
|
@@ -204,6 +240,7 @@ export function App({
|
|
|
204
240
|
default: t('ui.permission.manual'),
|
|
205
241
|
acceptEdits: t('ui.permission.accept_edits'),
|
|
206
242
|
plan: t('ui.permission.plan_mode'),
|
|
243
|
+
auto: t('ui.permission.auto_mode'),
|
|
207
244
|
bypassPermissions: t('ui.permission.bypass'),
|
|
208
245
|
}),
|
|
209
246
|
[t],
|
|
@@ -231,6 +268,10 @@ export function App({
|
|
|
231
268
|
const [providerId, setProviderId] = useState(initialProvider || config.defaultProvider)
|
|
232
269
|
const [modelId, setModelId] = useState(initialModel || config.defaultModel)
|
|
233
270
|
const [pickerOpen, setPickerOpen] = useState(false)
|
|
271
|
+
// ↑/↓ 翻历史用的已提交输入。**必须住在这里而不是 InputBar 里** —— InputBar 会被
|
|
272
|
+
// 卸载(pickerOpen 三元 / apiKeyPrompt 早退 / Ctrl+G),组件内 state 随之清零,
|
|
273
|
+
// 于是「开一次模型选择器,历史就没了」(ROADMAP D7)。
|
|
274
|
+
const [inputHistory, setInputHistory] = useState<string[]>([])
|
|
234
275
|
const [updateStatus, setUpdateStatus] = useState<UpdateStatus | null>(null)
|
|
235
276
|
// Current git branch — read once at mount (not a git repo → null).
|
|
236
277
|
const [gitBranch] = useState<string | null>(() => {
|
|
@@ -1242,6 +1283,8 @@ export function App({
|
|
|
1242
1283
|
<InputBar
|
|
1243
1284
|
onSubmit={handleSubmit}
|
|
1244
1285
|
isLoading={isLoading}
|
|
1286
|
+
history={inputHistory}
|
|
1287
|
+
onHistoryAppend={(v) => setInputHistory((prev) => [...prev, v])}
|
|
1245
1288
|
llm={autocompleteLlm}
|
|
1246
1289
|
recentMessages={recentMessages}
|
|
1247
1290
|
autocompleteEnabled={
|
|
@@ -1319,13 +1362,12 @@ export function App({
|
|
|
1319
1362
|
<Box flexDirection="column">
|
|
1320
1363
|
<Box flexDirection="row">
|
|
1321
1364
|
<Text color={PERMISSION_COLORS[permissionMode]}>
|
|
1322
|
-
|
|
1365
|
+
{permissionGlyphPrefix(permissionMode) + PERMISSION_LABELS[permissionMode]}
|
|
1323
1366
|
</Text>
|
|
1324
1367
|
<Text dimColor>
|
|
1325
1368
|
{' '}
|
|
1326
|
-
({t('ui.status.shift_tab_cycle')}:
|
|
1327
|
-
{
|
|
1328
|
-
{PERMISSION_LABELS.bypassPermissions}){' · '}
|
|
1369
|
+
({t('ui.status.shift_tab_cycle')}:{' '}
|
|
1370
|
+
{MODE_CYCLE.map((m) => PERMISSION_LABELS[m]).join(' · ')}){' · '}
|
|
1329
1371
|
{t('ui.status.esc_to_interrupt')}
|
|
1330
1372
|
{' · '}
|
|
1331
1373
|
{t('ui.status.left_for_agents')}
|
package/src/ui/commands.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { unregisterMcpServerTools } from '../mcp/registry'
|
|
|
17
17
|
import { buildCapabilityReport } from '../core/capability-inventory'
|
|
18
18
|
import { InstructionsLoader } from '../core/instructions'
|
|
19
19
|
import { findDerivableSections, DERIVABLE_HINTS } from '../core/claude-md-audit'
|
|
20
|
-
import {
|
|
20
|
+
import { miphamHome, workflowScriptDir, workflowScriptDirs, worktreeRoot } from '../core/paths.ts'
|
|
21
21
|
import { fixDoctor, fixConfig, fixCache, selectRepoClaudeFiles } from '../core/fix'
|
|
22
22
|
import { fixCodeTarget } from '../core/fix-code'
|
|
23
23
|
import { homedir } from 'node:os'
|
|
@@ -108,7 +108,7 @@ import { keysCmd } from '../commands/keys'
|
|
|
108
108
|
import { workflowViewCmd, workflowWatchCmd } from '../commands/workflow-view.js'
|
|
109
109
|
import { listActiveAutoloops, formatLoopRows } from '../commands/autoloop-journal.js'
|
|
110
110
|
import { execSync } from 'node:child_process'
|
|
111
|
-
import { OLLAMA_PRESET_MODELS } from '../shared/constants'
|
|
111
|
+
import { MIPHAM_DIR, OLLAMA_PRESET_MODELS } from '../shared/constants'
|
|
112
112
|
import { renameActiveSession } from '../agent/cross-session/discovery'
|
|
113
113
|
|
|
114
114
|
export interface CommandContext {
|
|
@@ -3366,7 +3366,6 @@ const fixCmd: CommandHandler = async (ctx, args) => {
|
|
|
3366
3366
|
const t = resolveT(ctx)
|
|
3367
3367
|
const { readFileSync, writeFileSync } = await import('node:fs')
|
|
3368
3368
|
const { join } = await import('node:path')
|
|
3369
|
-
const { homedir } = await import('node:os')
|
|
3370
3369
|
const { parse: parseYaml } = await import('yaml')
|
|
3371
3370
|
|
|
3372
3371
|
const target = args.find((a) => a === 'doctor' || a === 'config' || a === 'cache' || a === 'test')
|
|
@@ -3406,11 +3405,7 @@ const fixCmd: CommandHandler = async (ctx, args) => {
|
|
|
3406
3405
|
}
|
|
3407
3406
|
|
|
3408
3407
|
if (!target || target === 'config') {
|
|
3409
|
-
const
|
|
3410
|
-
const configPaths = [
|
|
3411
|
-
join(process.cwd(), '.mipham', 'config.yml'),
|
|
3412
|
-
join(home, '.mipham', 'config.yml'),
|
|
3413
|
-
]
|
|
3408
|
+
const configPaths = [join(process.cwd(), MIPHAM_DIR, 'config.yml'), miphamHome('config.yml')]
|
|
3414
3409
|
const hookEngine = ctx.engine.getHookEngine?.()
|
|
3415
3410
|
const result = fixConfig({
|
|
3416
3411
|
configPaths,
|
|
@@ -3444,7 +3439,7 @@ const fixCmd: CommandHandler = async (ctx, args) => {
|
|
|
3444
3439
|
}
|
|
3445
3440
|
|
|
3446
3441
|
if (!target || target === 'cache') {
|
|
3447
|
-
const crsiDir =
|
|
3442
|
+
const crsiDir = miphamHome('crsi')
|
|
3448
3443
|
const cacheFiles = ['eval-scores.jsonl', 'improvements.jsonl', 'prose-proposals.jsonl'].map(
|
|
3449
3444
|
(f) => join(crsiDir, f),
|
|
3450
3445
|
)
|
|
@@ -3670,7 +3665,7 @@ const filesCmd: CommandHandler = async (ctx) => {
|
|
|
3670
3665
|
try {
|
|
3671
3666
|
const entries = readdirSync(cwd, { withFileTypes: true })
|
|
3672
3667
|
const items = entries
|
|
3673
|
-
.filter((e) => !e.name.startsWith('.') || e.name ===
|
|
3668
|
+
.filter((e) => !e.name.startsWith('.') || e.name === MIPHAM_DIR || e.name === '.mcp.json')
|
|
3674
3669
|
.slice(0, 40)
|
|
3675
3670
|
.map((e) => {
|
|
3676
3671
|
const icon = e.isDirectory() ? '📁' : '📄'
|
|
@@ -4316,8 +4311,7 @@ const memoryCmd: CommandHandler = async (ctx, args) => {
|
|
|
4316
4311
|
const { existsSync, readdirSync, readFileSync, statSync } = await import('node:fs')
|
|
4317
4312
|
const { join } = await import('node:path')
|
|
4318
4313
|
|
|
4319
|
-
const
|
|
4320
|
-
const memoryDir = join(home, '.mipham', 'memory')
|
|
4314
|
+
const memoryDir = miphamHome('memory')
|
|
4321
4315
|
|
|
4322
4316
|
// /memory gc — 记忆卫生:归档「0 召回 + 过期」的 auto-* 记忆(手写只报告)
|
|
4323
4317
|
if (args[0]?.toLowerCase() === 'gc') {
|
|
@@ -4467,15 +4461,20 @@ const upgradeCmd: CommandHandler = async (ctx) => {
|
|
|
4467
4461
|
lines.push(`Config backed up to: ${backupPath}`)
|
|
4468
4462
|
}
|
|
4469
4463
|
|
|
4470
|
-
const
|
|
4464
|
+
const result = performUpdate(update.latest)
|
|
4471
4465
|
|
|
4472
|
-
if (ok) {
|
|
4466
|
+
if (result.ok) {
|
|
4473
4467
|
const configPath = getConfigPath()
|
|
4474
4468
|
const { existsSync } = await import('node:fs')
|
|
4475
|
-
|
|
4469
|
+
// 只有自证通过才置「已装待重启」—— 装坏了还提示重启,是在骗用户。
|
|
4470
|
+
if (result.verified) ctx.setUpdateStatus({ state: 'installed', latest: update.latest })
|
|
4476
4471
|
lines.push('')
|
|
4477
4472
|
lines.push(t('commands.upgrade.updated', { version: update.latest }))
|
|
4478
4473
|
|
|
4474
|
+
if (!result.verified) {
|
|
4475
|
+
lines.push(t('commands.upgrade.unverified', { reason: result.reason ?? '' }))
|
|
4476
|
+
}
|
|
4477
|
+
|
|
4479
4478
|
if (existsSync(configPath)) {
|
|
4480
4479
|
lines.push(t('commands.upgrade.config_preserved', { path: configPath }))
|
|
4481
4480
|
} else if (backupPath) {
|
|
@@ -4488,8 +4487,17 @@ const upgradeCmd: CommandHandler = async (ctx) => {
|
|
|
4488
4487
|
lines.push(t('commands.upgrade.old_version_warning'))
|
|
4489
4488
|
} else {
|
|
4490
4489
|
lines.push('')
|
|
4490
|
+
// 失败信息必须回答两件事:为什么,以及**我手里还有没有 CLI**。
|
|
4491
|
+
const rollbackNote = t(
|
|
4492
|
+
result.rolledBack ? 'commands.upgrade.rolled_back' : 'commands.upgrade.no_rollback',
|
|
4493
|
+
)
|
|
4491
4494
|
lines.push(
|
|
4492
|
-
t('commands.upgrade.update_failed', {
|
|
4495
|
+
t('commands.upgrade.update_failed', {
|
|
4496
|
+
reason: result.reason ?? '',
|
|
4497
|
+
command: NPM_UPDATE_COMMAND,
|
|
4498
|
+
path: backupPath || '',
|
|
4499
|
+
rolledBack: rollbackNote,
|
|
4500
|
+
}),
|
|
4493
4501
|
)
|
|
4494
4502
|
}
|
|
4495
4503
|
|
|
@@ -4982,10 +4990,8 @@ const loginCmd: CommandHandler = (ctx) => {
|
|
|
4982
4990
|
|
|
4983
4991
|
const logoutCmd: CommandHandler = async () => {
|
|
4984
4992
|
const { existsSync } = await import('node:fs')
|
|
4985
|
-
const { join } = await import('node:path')
|
|
4986
4993
|
|
|
4987
|
-
const
|
|
4988
|
-
const userConfig = join(home, '.mipham', 'config.yml')
|
|
4994
|
+
const userConfig = miphamHome('config.yml')
|
|
4989
4995
|
const hasUserConfig = existsSync(userConfig)
|
|
4990
4996
|
|
|
4991
4997
|
return {
|
|
@@ -5027,9 +5033,8 @@ const feedbackCmd: CommandHandler = async (ctx, args) => {
|
|
|
5027
5033
|
try {
|
|
5028
5034
|
const { writeFileSync, mkdirSync, existsSync } = await import('node:fs')
|
|
5029
5035
|
const { join } = await import('node:path')
|
|
5030
|
-
const { homedir } = await import('node:os')
|
|
5031
5036
|
|
|
5032
|
-
const dir =
|
|
5037
|
+
const dir = miphamHome('feedback')
|
|
5033
5038
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
5034
5039
|
|
|
5035
5040
|
const ts = new Date().toISOString().replace(/[:.]/g, '-')
|
package/src/ui/config-wizard.tsx
CHANGED
|
@@ -17,10 +17,10 @@ import type { ModelInfo } from '../shared/types'
|
|
|
17
17
|
import { mkdirSync } from 'node:fs'
|
|
18
18
|
import { atomicWriteFileSync } from '../shared/atomic-write'
|
|
19
19
|
import { join } from 'node:path'
|
|
20
|
-
import { homedir } from 'node:os'
|
|
21
20
|
import { execSync } from 'node:child_process'
|
|
22
21
|
import { getCredentialKey, encryptApiKey } from '../config/credential-crypto'
|
|
23
22
|
import { CLOUD_PROVIDERS, getActiveModels, buildConfigYaml } from '../config/wizard-config'
|
|
23
|
+
import { miphamHome } from '../core/paths.ts'
|
|
24
24
|
|
|
25
25
|
// ── Types ──
|
|
26
26
|
|
|
@@ -38,7 +38,7 @@ const SELECTED_COLOR = 'cyan'
|
|
|
38
38
|
// ── Helpers ──
|
|
39
39
|
|
|
40
40
|
function writeConfigFile(providerId: string, modelId: string, apiKey: string): void {
|
|
41
|
-
const configDir =
|
|
41
|
+
const configDir = miphamHome()
|
|
42
42
|
mkdirSync(configDir, { recursive: true })
|
|
43
43
|
|
|
44
44
|
const models = providerId === 'ollama' ? getOllamaModelListForConfig() : []
|
package/src/ui/input.tsx
CHANGED
|
@@ -24,6 +24,14 @@ interface InputBarProps {
|
|
|
24
24
|
onCancel?: () => void
|
|
25
25
|
/** When false, don't auto-open the slash-command picker when typing `/`. */
|
|
26
26
|
showCommandPicker?: boolean
|
|
27
|
+
/**
|
|
28
|
+
* ↑/↓ 翻历史用的已提交输入。**由调用方持有,不放本组件内** —— app.tsx 有三条路径把
|
|
29
|
+
* InputBar 整个卸载(`pickerOpen` 三元 / `apiKeyPrompt` 早退 / Ctrl+G),
|
|
30
|
+
* 放在这里会被卸载清空(ROADMAP D7)。
|
|
31
|
+
*/
|
|
32
|
+
history: string[]
|
|
33
|
+
/** 提交时把该条追加进 history(由调用方落 state)。 */
|
|
34
|
+
onHistoryAppend: (value: string) => void
|
|
27
35
|
/** LLM 续写建议所需的模型(app.tsx 传;RemoteEngine 下 undefined → 补全禁用)。 */
|
|
28
36
|
llm?: Llm
|
|
29
37
|
/** 最近对话上下文(供续写贴合)。 */
|
|
@@ -288,6 +296,8 @@ export function InputBar({
|
|
|
288
296
|
onCyclePermission,
|
|
289
297
|
onCancel,
|
|
290
298
|
showCommandPicker = true,
|
|
299
|
+
history,
|
|
300
|
+
onHistoryAppend,
|
|
291
301
|
llm,
|
|
292
302
|
recentMessages,
|
|
293
303
|
autocompleteEnabled = true,
|
|
@@ -302,7 +312,9 @@ export function InputBar({
|
|
|
302
312
|
const prevLoading = useRef(isLoading)
|
|
303
313
|
|
|
304
314
|
// ── Message history for arrow-key navigation (Claude Code parity) ──
|
|
305
|
-
|
|
315
|
+
// `history` 由调用方持有(见 InputBarProps.history):本组件不拥有它,因为
|
|
316
|
+
// app.tsx 会卸载本组件,而卸载会清空组件内 state(ROADMAP D7)。
|
|
317
|
+
// 下面两个 ref 是**浏览游标**、不是历史本体,随卸载重置才是对的。
|
|
306
318
|
const historyIndexRef = useRef(-1) // -1 = not browsing history
|
|
307
319
|
const savedDraftRef = useRef('') // saved user draft before browsing history
|
|
308
320
|
|
|
@@ -434,7 +446,7 @@ export function InputBar({
|
|
|
434
446
|
|
|
435
447
|
const result = navigateHistory(
|
|
436
448
|
{
|
|
437
|
-
history
|
|
449
|
+
history,
|
|
438
450
|
index: historyIndexRef.current,
|
|
439
451
|
savedDraft: savedDraftRef.current,
|
|
440
452
|
},
|
|
@@ -480,7 +492,7 @@ export function InputBar({
|
|
|
480
492
|
onCancel?.()
|
|
481
493
|
}
|
|
482
494
|
// Save to message history for arrow-key navigation
|
|
483
|
-
|
|
495
|
+
onHistoryAppend(finalValue)
|
|
484
496
|
historyIndexRef.current = -1
|
|
485
497
|
savedDraftRef.current = ''
|
|
486
498
|
onSubmit(finalValue)
|
package/src/workflow/journal.ts
CHANGED
|
@@ -7,9 +7,9 @@ import {
|
|
|
7
7
|
readdirSync,
|
|
8
8
|
} from 'node:fs'
|
|
9
9
|
import { join } from 'node:path'
|
|
10
|
-
import {
|
|
10
|
+
import { miphamHome } from '../core/paths.ts'
|
|
11
11
|
|
|
12
|
-
const WORKFLOW_DIR =
|
|
12
|
+
const WORKFLOW_DIR = miphamHome('workflows')
|
|
13
13
|
|
|
14
14
|
export interface JournalEntry {
|
|
15
15
|
seq: number
|