@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/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'
|
|
@@ -37,7 +37,13 @@ import {
|
|
|
37
37
|
MANAGED_RULES_FILE,
|
|
38
38
|
} from '../core/crsi-producer'
|
|
39
39
|
import { prefilterProposal } from '../core/proposal-guard'
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
runEval,
|
|
42
|
+
appendEvalScore,
|
|
43
|
+
getContractHistory,
|
|
44
|
+
diffContractHistory,
|
|
45
|
+
renderContractDiff,
|
|
46
|
+
} from '../core/eval-harness'
|
|
41
47
|
import { listRewardFns } from '../core/reward-fn'
|
|
42
48
|
import {
|
|
43
49
|
runTaskPerformance,
|
|
@@ -47,6 +53,7 @@ import {
|
|
|
47
53
|
import { randomUUID } from 'node:crypto'
|
|
48
54
|
import {
|
|
49
55
|
buildImprovementReport,
|
|
56
|
+
formatCostLine,
|
|
50
57
|
appendImprovement,
|
|
51
58
|
readImprovements,
|
|
52
59
|
improvementRate,
|
|
@@ -101,7 +108,7 @@ import { keysCmd } from '../commands/keys'
|
|
|
101
108
|
import { workflowViewCmd, workflowWatchCmd } from '../commands/workflow-view.js'
|
|
102
109
|
import { listActiveAutoloops, formatLoopRows } from '../commands/autoloop-journal.js'
|
|
103
110
|
import { execSync } from 'node:child_process'
|
|
104
|
-
import { OLLAMA_PRESET_MODELS } from '../shared/constants'
|
|
111
|
+
import { MIPHAM_DIR, OLLAMA_PRESET_MODELS } from '../shared/constants'
|
|
105
112
|
import { renameActiveSession } from '../agent/cross-session/discovery'
|
|
106
113
|
|
|
107
114
|
export interface CommandContext {
|
|
@@ -901,9 +908,12 @@ const crsiModifyCmd: CommandHandler = async (ctx, args) => {
|
|
|
901
908
|
? 'regressed ⚠️'
|
|
902
909
|
: 'inconclusive'
|
|
903
910
|
const sign = report.deltaMean >= 0 ? '+' : ''
|
|
911
|
+
// B2 代价维:只展示、不进判定(`formatCostLine` 缺席时返回 null ⇒ 整行不打)。
|
|
912
|
+
const costLine = formatCostLine(report)
|
|
904
913
|
improvementLine =
|
|
905
914
|
`\n📊 改进判定: ${label} (delta ${sign}${report.deltaMean.toFixed(1)}, 噪声 ${report.noise.toFixed(1)}, 阈值 ${report.minEffect.toFixed(1)})` +
|
|
906
915
|
`\n 改进率: ${rate.improved}/${rate.total} (${(rate.rate * 100).toFixed(0)}%, Wilson 95% [${(rate.lo * 100).toFixed(0)}%, ${(rate.hi * 100).toFixed(0)}%])` +
|
|
916
|
+
(costLine ? `\n${costLine}` : '') +
|
|
907
917
|
(report.verdict === 'regressed' ? '\n ⚠️ 任务表现倒退:--approve 将被拒绝。' : '')
|
|
908
918
|
}
|
|
909
919
|
} catch {
|
|
@@ -1009,6 +1019,10 @@ const crsiProposeCmd: CommandHandler = async (ctx, args) => {
|
|
|
1009
1019
|
`\n🎯 ε 预测命中: ${report.predictionHit ? '命中 ✅' : '未命中 ⚠️'}` +
|
|
1010
1020
|
`(预测 ${report.predictedDelta},实际 delta ${report.deltaMean.toFixed(1)})`
|
|
1011
1021
|
}
|
|
1022
|
+
// B2 代价维:**与手工路径同一行读数**,两条渲染路径都接(只接一条即本仓库记过的
|
|
1023
|
+
// 「局部正确全局遗漏」)。ε 行可以有、代价行可以没有,故各自独立追加。
|
|
1024
|
+
const costLine = formatCostLine(report)
|
|
1025
|
+
if (costLine) predictionLine += `\n${costLine}`
|
|
1012
1026
|
}
|
|
1013
1027
|
} catch {
|
|
1014
1028
|
// 测量失败(LLM 不可用等)不阻断提案流程 —— 与手工路径 :889 的处置一致。
|
|
@@ -1132,14 +1146,26 @@ const crsiEvalCmd: CommandHandler = async (ctx, args) => {
|
|
|
1132
1146
|
content: `❌ 未知 reward: ${rewardName}。可用: ${fns.map((f) => f.name).join(', ')}`,
|
|
1133
1147
|
}
|
|
1134
1148
|
}
|
|
1149
|
+
// 先读后写:比较基准是「上一次已落盘的态」,不依赖刚写进去那条排第几。
|
|
1150
|
+
const prev = getContractHistory(fn.name)
|
|
1135
1151
|
const report = await fn.evaluate()
|
|
1152
|
+
const deltaLines = report.results
|
|
1153
|
+
? renderContractDiff(diffContractHistory(report.results, prev))
|
|
1154
|
+
: []
|
|
1136
1155
|
appendEvalScore(fn.name, report)
|
|
1137
1156
|
return {
|
|
1138
|
-
content:
|
|
1157
|
+
content: [
|
|
1158
|
+
`得分 **${report.score}/100** (${report.passed}/${report.total})`,
|
|
1159
|
+
`失败: ${report.failures.join(', ') || '无'}`,
|
|
1160
|
+
...(deltaLines.length > 0 ? ['', '### 与上次相比', ...deltaLines] : []),
|
|
1161
|
+
].join('\n'),
|
|
1139
1162
|
}
|
|
1140
1163
|
}
|
|
1141
1164
|
|
|
1165
|
+
// 先读后写(同上):基准是上一次落盘的态。
|
|
1166
|
+
const prevSnapshot = getContractHistory('mechanism-sentinel')
|
|
1142
1167
|
const report = runEval()
|
|
1168
|
+
const deltaLines = renderContractDiff(diffContractHistory(report.results, prevSnapshot))
|
|
1143
1169
|
appendEvalScore('mechanism-sentinel', report)
|
|
1144
1170
|
|
|
1145
1171
|
const lines: string[] = ['## 🧪 CRSI Eval Harness', '']
|
|
@@ -1154,6 +1180,11 @@ const crsiEvalCmd: CommandHandler = async (ctx, args) => {
|
|
|
1154
1180
|
if (report.failures.length > 0) {
|
|
1155
1181
|
lines.push('', `❌ 失败任务: ${report.failures.join(', ')}`)
|
|
1156
1182
|
}
|
|
1183
|
+
// 只读展示:账本按契约粒度落盘后,才能回答「是哪条契约翻的」。
|
|
1184
|
+
// 这里不做任何自动决策——闸门仍只看 regressedAnchors / score。
|
|
1185
|
+
if (deltaLines.length > 0) {
|
|
1186
|
+
lines.push('', '### 与上次相比', ...deltaLines)
|
|
1187
|
+
}
|
|
1157
1188
|
|
|
1158
1189
|
// 奖励函数注册表(reward function = policy→feedback 抽象可见)
|
|
1159
1190
|
// 传 llm 列出完整注册表(task-performance 需 llm 才能跑,但构造它零 LLM 调用)。
|
|
@@ -3335,7 +3366,6 @@ const fixCmd: CommandHandler = async (ctx, args) => {
|
|
|
3335
3366
|
const t = resolveT(ctx)
|
|
3336
3367
|
const { readFileSync, writeFileSync } = await import('node:fs')
|
|
3337
3368
|
const { join } = await import('node:path')
|
|
3338
|
-
const { homedir } = await import('node:os')
|
|
3339
3369
|
const { parse: parseYaml } = await import('yaml')
|
|
3340
3370
|
|
|
3341
3371
|
const target = args.find((a) => a === 'doctor' || a === 'config' || a === 'cache' || a === 'test')
|
|
@@ -3375,11 +3405,7 @@ const fixCmd: CommandHandler = async (ctx, args) => {
|
|
|
3375
3405
|
}
|
|
3376
3406
|
|
|
3377
3407
|
if (!target || target === 'config') {
|
|
3378
|
-
const
|
|
3379
|
-
const configPaths = [
|
|
3380
|
-
join(process.cwd(), '.mipham', 'config.yml'),
|
|
3381
|
-
join(home, '.mipham', 'config.yml'),
|
|
3382
|
-
]
|
|
3408
|
+
const configPaths = [join(process.cwd(), MIPHAM_DIR, 'config.yml'), miphamHome('config.yml')]
|
|
3383
3409
|
const hookEngine = ctx.engine.getHookEngine?.()
|
|
3384
3410
|
const result = fixConfig({
|
|
3385
3411
|
configPaths,
|
|
@@ -3413,7 +3439,7 @@ const fixCmd: CommandHandler = async (ctx, args) => {
|
|
|
3413
3439
|
}
|
|
3414
3440
|
|
|
3415
3441
|
if (!target || target === 'cache') {
|
|
3416
|
-
const crsiDir =
|
|
3442
|
+
const crsiDir = miphamHome('crsi')
|
|
3417
3443
|
const cacheFiles = ['eval-scores.jsonl', 'improvements.jsonl', 'prose-proposals.jsonl'].map(
|
|
3418
3444
|
(f) => join(crsiDir, f),
|
|
3419
3445
|
)
|
|
@@ -3639,7 +3665,7 @@ const filesCmd: CommandHandler = async (ctx) => {
|
|
|
3639
3665
|
try {
|
|
3640
3666
|
const entries = readdirSync(cwd, { withFileTypes: true })
|
|
3641
3667
|
const items = entries
|
|
3642
|
-
.filter((e) => !e.name.startsWith('.') || e.name ===
|
|
3668
|
+
.filter((e) => !e.name.startsWith('.') || e.name === MIPHAM_DIR || e.name === '.mcp.json')
|
|
3643
3669
|
.slice(0, 40)
|
|
3644
3670
|
.map((e) => {
|
|
3645
3671
|
const icon = e.isDirectory() ? '📁' : '📄'
|
|
@@ -4285,8 +4311,7 @@ const memoryCmd: CommandHandler = async (ctx, args) => {
|
|
|
4285
4311
|
const { existsSync, readdirSync, readFileSync, statSync } = await import('node:fs')
|
|
4286
4312
|
const { join } = await import('node:path')
|
|
4287
4313
|
|
|
4288
|
-
const
|
|
4289
|
-
const memoryDir = join(home, '.mipham', 'memory')
|
|
4314
|
+
const memoryDir = miphamHome('memory')
|
|
4290
4315
|
|
|
4291
4316
|
// /memory gc — 记忆卫生:归档「0 召回 + 过期」的 auto-* 记忆(手写只报告)
|
|
4292
4317
|
if (args[0]?.toLowerCase() === 'gc') {
|
|
@@ -4951,10 +4976,8 @@ const loginCmd: CommandHandler = (ctx) => {
|
|
|
4951
4976
|
|
|
4952
4977
|
const logoutCmd: CommandHandler = async () => {
|
|
4953
4978
|
const { existsSync } = await import('node:fs')
|
|
4954
|
-
const { join } = await import('node:path')
|
|
4955
4979
|
|
|
4956
|
-
const
|
|
4957
|
-
const userConfig = join(home, '.mipham', 'config.yml')
|
|
4980
|
+
const userConfig = miphamHome('config.yml')
|
|
4958
4981
|
const hasUserConfig = existsSync(userConfig)
|
|
4959
4982
|
|
|
4960
4983
|
return {
|
|
@@ -4996,9 +5019,8 @@ const feedbackCmd: CommandHandler = async (ctx, args) => {
|
|
|
4996
5019
|
try {
|
|
4997
5020
|
const { writeFileSync, mkdirSync, existsSync } = await import('node:fs')
|
|
4998
5021
|
const { join } = await import('node:path')
|
|
4999
|
-
const { homedir } = await import('node:os')
|
|
5000
5022
|
|
|
5001
|
-
const dir =
|
|
5023
|
+
const dir = miphamHome('feedback')
|
|
5002
5024
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
5003
5025
|
|
|
5004
5026
|
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
|