@kkelly-offical/kkcode 0.1.7 → 0.2.3-preview.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/LICENSE +674 -674
- package/README.md +474 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +228 -220
- package/src/agent/custom-agent-loader.mjs +6 -3
- package/src/agent/generator.mjs +2 -2
- package/src/agent/prompt/assistant.txt +12 -0
- package/src/agent/prompt/bug-hunter.txt +89 -89
- package/src/agent/prompt/frontend-designer.txt +58 -58
- package/src/agent/prompt/guide.txt +1 -1
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
- package/src/agent/prompt/longagent-coding-agent.txt +37 -37
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
- package/src/agent/prompt/longagent-preview-agent.txt +63 -63
- package/src/command/custom-commands.mjs +2 -2
- package/src/commands/agent.mjs +1 -1
- package/src/commands/background.mjs +145 -4
- package/src/commands/chat.mjs +117 -76
- package/src/commands/config.mjs +148 -1
- package/src/commands/doctor.mjs +30 -6
- package/src/commands/init.mjs +32 -6
- package/src/commands/longagent.mjs +117 -0
- package/src/commands/mcp.mjs +275 -43
- package/src/commands/permission.mjs +1 -1
- package/src/commands/session.mjs +195 -140
- package/src/commands/skill.mjs +63 -0
- package/src/commands/theme.mjs +1 -1
- package/src/commands/update.mjs +32 -0
- package/src/config/defaults.mjs +289 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +604 -574
- package/src/context.mjs +4 -1
- package/src/core/constants.mjs +97 -91
- package/src/core/types.mjs +1 -1
- package/src/github/api.mjs +78 -78
- package/src/github/auth.mjs +294 -286
- package/src/github/flow.mjs +298 -298
- package/src/github/workspace.mjs +225 -212
- package/src/index.mjs +87 -82
- package/src/knowledge/frontend-aesthetics.txt +38 -38
- package/src/mcp/client-http.mjs +139 -141
- package/src/mcp/client-sse.mjs +297 -288
- package/src/mcp/client-stdio.mjs +534 -533
- package/src/mcp/constants.mjs +4 -2
- package/src/mcp/registry.mjs +498 -479
- package/src/mcp/stdio-framing.mjs +135 -133
- package/src/mcp/tool-result.mjs +24 -24
- package/src/observability/edit-diagnostics.mjs +449 -0
- package/src/observability/index.mjs +42 -42
- package/src/observability/metrics.mjs +165 -137
- package/src/observability/tracer.mjs +137 -137
- package/src/onboarding.mjs +209 -0
- package/src/orchestration/background-manager.mjs +567 -372
- package/src/orchestration/background-worker.mjs +419 -305
- package/src/orchestration/interruption-reason.mjs +21 -0
- package/src/orchestration/longagent-manager.mjs +197 -171
- package/src/orchestration/stage-scheduler.mjs +733 -728
- package/src/orchestration/subagent-router.mjs +7 -1
- package/src/orchestration/task-scheduler.mjs +219 -7
- package/src/permission/engine.mjs +1 -1
- package/src/permission/exec-policy.mjs +370 -370
- package/src/permission/file-edit-policy.mjs +108 -0
- package/src/permission/prompt.mjs +1 -1
- package/src/permission/rules.mjs +116 -7
- package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
- package/src/plugin/hook-bus.mjs +19 -5
- package/src/plugin/manifest-loader.mjs +222 -0
- package/src/provider/anthropic.mjs +396 -390
- package/src/provider/ollama.mjs +7 -1
- package/src/provider/openai.mjs +382 -340
- package/src/provider/retry-policy.mjs +74 -68
- package/src/provider/router.mjs +242 -241
- package/src/provider/sse.mjs +104 -104
- package/src/provider/wizard.mjs +556 -0
- package/src/repl/capability-facade.mjs +30 -0
- package/src/repl/command-surface.mjs +23 -0
- package/src/repl/controller-entry.mjs +40 -0
- package/src/repl/core-shell.mjs +208 -0
- package/src/repl/dialog-router.mjs +87 -0
- package/src/repl/input-engine.mjs +76 -0
- package/src/repl/keymap.mjs +7 -0
- package/src/repl/operator-surface.mjs +15 -0
- package/src/repl/permission-flow.mjs +49 -0
- package/src/repl/runtime-facade.mjs +36 -0
- package/src/repl/slash-router.mjs +62 -0
- package/src/repl/state-store.mjs +29 -0
- package/src/repl/turn-controller.mjs +58 -0
- package/src/repl/verification.mjs +23 -0
- package/src/repl.mjs +3371 -2981
- package/src/rules/load-rules.mjs +3 -3
- package/src/runtime.mjs +1 -1
- package/src/session/agent-transaction.mjs +86 -0
- package/src/session/checkpoint.mjs +302 -302
- package/src/session/compaction.mjs +298 -298
- package/src/session/engine.mjs +417 -232
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1097
- package/src/session/longagent-plan.mjs +376 -365
- package/src/session/longagent-project-memory.mjs +53 -53
- package/src/session/longagent-scaffold.mjs +291 -291
- package/src/session/longagent-task-bus.mjs +138 -54
- package/src/session/longagent-utils.mjs +828 -472
- package/src/session/longagent.mjs +911 -900
- package/src/session/loop.mjs +1005 -930
- package/src/session/prompt/agent.txt +25 -25
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +31 -31
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +196 -195
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -519
- package/src/session/system-prompt.mjs +308 -273
- package/src/session/task-validator.mjs +267 -267
- package/src/session/usability-gates.mjs +2 -2
- package/src/skill/builtin/commit.mjs +64 -64
- package/src/skill/builtin/design.mjs +76 -76
- package/src/skill/generator.mjs +18 -2
- package/src/skill/registry.mjs +642 -390
- package/src/storage/audit-store.mjs +18 -11
- package/src/storage/event-log.mjs +7 -1
- package/src/storage/ghost-commit-store.mjs +243 -245
- package/src/storage/paths.mjs +17 -0
- package/src/theme/default-theme.mjs +1 -1
- package/src/theme/markdown.mjs +4 -0
- package/src/theme/schema.mjs +1 -1
- package/src/theme/status-bar.mjs +162 -158
- package/src/tool/audit-wrapper.mjs +18 -2
- package/src/tool/edit-transaction.mjs +23 -0
- package/src/tool/executor.mjs +26 -1
- package/src/tool/file-read-state.mjs +65 -0
- package/src/tool/git-auto.mjs +526 -526
- package/src/tool/git-full-auto.mjs +487 -478
- package/src/tool/mutation-guard.mjs +54 -0
- package/src/tool/prompt/edit.txt +3 -3
- package/src/tool/prompt/multiedit.txt +1 -0
- package/src/tool/prompt/notebookedit.txt +2 -1
- package/src/tool/prompt/patch.txt +25 -24
- package/src/tool/prompt/read.txt +3 -3
- package/src/tool/prompt/sysinfo.txt +29 -0
- package/src/tool/prompt/task.txt +66 -4
- package/src/tool/prompt/write.txt +2 -2
- package/src/tool/question-prompt.mjs +99 -93
- package/src/tool/registry.mjs +1701 -1343
- package/src/tool/task-tool.mjs +14 -6
- package/src/ui/activity-renderer.mjs +667 -664
- package/src/ui/repl-background-panel.mjs +7 -0
- package/src/ui/repl-capability-panel.mjs +9 -0
- package/src/ui/repl-dashboard.mjs +54 -4
- package/src/ui/repl-help.mjs +110 -0
- package/src/ui/repl-operator-panel.mjs +12 -0
- package/src/ui/repl-route-feedback.mjs +35 -0
- package/src/ui/repl-status-view.mjs +76 -0
- package/src/ui/repl-task-panel.mjs +5 -0
- package/src/ui/repl-transcript-panel.mjs +56 -0
- package/src/ui/repl-turn-summary.mjs +135 -0
- package/src/update/checker.mjs +184 -0
- package/src/usage/pricing.mjs +122 -121
- package/src/usage/usage-meter.mjs +1 -0
- package/src/util/git.mjs +562 -519
- package/src/util/template.mjs +6 -1
- package/src/version.mjs +3 -0
package/src/session/rollback.mjs
CHANGED
|
@@ -1,196 +1,197 @@
|
|
|
1
|
-
import { isGitRepo } from "../util/git.mjs"
|
|
2
|
-
import { getLatestGhostCommit, listGhostCommits } from "../storage/ghost-commit-store.mjs"
|
|
3
|
-
import { restoreGhostCommit } from "../util/git.mjs"
|
|
4
|
-
import { askQuestionInteractive } from "../tool/question-prompt.mjs"
|
|
5
|
-
import { EventBus } from "../core/events.mjs"
|
|
6
|
-
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* 回溯意图检测关键词
|
|
10
|
-
* 分为中文和英文两组,按置信度排序
|
|
11
|
-
*/
|
|
12
|
-
const ROLLBACK_PATTERNS = [
|
|
13
|
-
// 高置信度 — 明确的回退指令(中文不用 \b,英文保留)
|
|
14
|
-
{ pattern: /(回退|撤销|撤回|回滚|还原)/i, confidence: 0.9 },
|
|
15
|
-
{ pattern: /\b(undo|rollback|revert)\b/i, confidence: 0.9 },
|
|
16
|
-
// 中置信度 — 需要上下文
|
|
17
|
-
{ pattern: /(恢复到|恢复之前|回到之前|退回|取消(刚才|上次|之前)的(修改|更改|变更|操作))/i, confidence: 0.8 },
|
|
18
|
-
{ pattern: /\b(restore previous|go back|undo (last|previous|recent))\b/i, confidence: 0.8 },
|
|
19
|
-
// 低置信度 — 可能是回退也可能不是
|
|
20
|
-
{ pattern: /(不要了|算了不改了|改回去|恢复原样)/i, confidence: 0.7 }
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 检测用户消息中的回溯意图
|
|
25
|
-
* @param {string} text - 用户输入文本
|
|
26
|
-
* @returns {{ isRollback: boolean, confidence: number, matchedPattern: string }}
|
|
27
|
-
*/
|
|
28
|
-
export function detectRollbackIntent(text) {
|
|
29
|
-
if (!text || typeof text !== "string") {
|
|
30
|
-
return { isRollback: false, confidence: 0, matchedPattern: "" }
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const normalized = text.trim().toLowerCase()
|
|
34
|
-
// 过短的消息不太可能是回退指令(除非就是 "undo" 这样的单词)
|
|
35
|
-
if (normalized.length > 200) {
|
|
36
|
-
return { isRollback: false, confidence: 0, matchedPattern: "" }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
for (const { pattern, confidence } of ROLLBACK_PATTERNS) {
|
|
40
|
-
const match = normalized.match(pattern)
|
|
41
|
-
if (match) {
|
|
42
|
-
return { isRollback: true, confidence, matchedPattern: match[0] }
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return { isRollback: false, confidence: 0, matchedPattern: "" }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* 向用户确认是否执行回滚,并展示可用快照
|
|
51
|
-
* @returns {{ confirmed: boolean, snapshotId: string|null, message: string }}
|
|
52
|
-
*/
|
|
53
|
-
export async function confirmRollback({ cwd, language = "en" }) {
|
|
54
|
-
const inGit = await isGitRepo(cwd)
|
|
55
|
-
if (!inGit) {
|
|
56
|
-
return {
|
|
57
|
-
confirmed: false,
|
|
58
|
-
snapshotId: null,
|
|
59
|
-
message: language === "zh"
|
|
60
|
-
? "当前目录不是 Git 仓库,无法执行代码回滚。"
|
|
61
|
-
: "Not a git repository — cannot rollback code changes."
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const latest = await getLatestGhostCommit(cwd)
|
|
66
|
-
if (!latest) {
|
|
67
|
-
return {
|
|
68
|
-
confirmed: false,
|
|
69
|
-
snapshotId: null,
|
|
70
|
-
message: language === "zh"
|
|
71
|
-
? "没有找到可用的快照。本次会话尚未创建任何代码快照,无法回滚。"
|
|
72
|
-
: "No snapshots found. No code snapshots were created in this session."
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const snapDate = new Date(latest.createdAt).toLocaleString()
|
|
77
|
-
const fileCount = latest.files?.length || 0
|
|
78
|
-
const shortHash = latest.commitHash?.slice(0, 8) || "unknown"
|
|
79
|
-
|
|
80
|
-
const zhWarning = [
|
|
81
|
-
`找到最近的快照: ${shortHash} (${snapDate})`,
|
|
82
|
-
`包含 ${fileCount} 个文件: ${(latest.files || []).slice(0, 5).join(", ")}${fileCount > 5 ? " ..." : ""}`,
|
|
83
|
-
"",
|
|
84
|
-
"⚠ 注意: 回滚只能恢复文件变更。已执行的 bash 命令(如安装依赖、删除文件等)无法自动撤销。"
|
|
85
|
-
].join("\n")
|
|
86
|
-
|
|
87
|
-
const enWarning = [
|
|
88
|
-
`Latest snapshot: ${shortHash} (${snapDate})`,
|
|
89
|
-
`Contains ${fileCount} file(s): ${(latest.files || []).slice(0, 5).join(", ")}${fileCount > 5 ? " ..." : ""}`,
|
|
90
|
-
"",
|
|
91
|
-
"Warning: Rollback only restores file changes. Bash commands (installs, deletions, etc.) cannot be undone."
|
|
92
|
-
].join("\n")
|
|
93
|
-
|
|
94
|
-
const answers = await askQuestionInteractive({
|
|
95
|
-
questions: [{
|
|
96
|
-
id: "rollback_confirm",
|
|
97
|
-
text: language === "zh" ? "确认回滚代码?" : "Confirm code rollback?",
|
|
98
|
-
description: language === "zh" ? zhWarning : enWarning,
|
|
99
|
-
options: [
|
|
100
|
-
{
|
|
101
|
-
label: language === "zh" ? "确认回滚" : "Confirm rollback",
|
|
102
|
-
value: "yes",
|
|
103
|
-
description: language === "zh"
|
|
104
|
-
? "恢复文件到快照状态"
|
|
105
|
-
: "Restore files to snapshot state"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
label: language === "zh" ? "取消" : "Cancel",
|
|
109
|
-
value: "no",
|
|
110
|
-
description: language === "zh"
|
|
111
|
-
? "不执行回滚,继续当前对话"
|
|
112
|
-
: "Skip rollback, continue conversation"
|
|
113
|
-
}
|
|
114
|
-
],
|
|
115
|
-
allowCustom: false
|
|
116
|
-
}]
|
|
117
|
-
})
|
|
118
|
-
|
|
119
|
-
const answer = String(answers.rollback_confirm || "").toLowerCase().trim()
|
|
120
|
-
const confirmed = ["yes", "confirm", "确认回滚", "1"].includes(answer)
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
confirmed,
|
|
124
|
-
snapshotId: confirmed ? latest.id : null,
|
|
125
|
-
commitHash: confirmed ? latest.commitHash : null,
|
|
126
|
-
message: confirmed
|
|
127
|
-
? (language === "zh" ? `正在回滚到快照 ${shortHash}...` : `Rolling back to snapshot ${shortHash}...`)
|
|
128
|
-
: (language === "zh" ? "已取消回滚。" : "Rollback cancelled.")
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* 执行代码回滚
|
|
134
|
-
* @returns {{ ok: boolean, message: string }}
|
|
135
|
-
*/
|
|
136
|
-
export async function executeRollback({ cwd, commitHash, sessionId, language = "en" }) {
|
|
137
|
-
try {
|
|
138
|
-
const result = await restoreGhostCommit(cwd, commitHash, false)
|
|
139
|
-
if (!result.ok) {
|
|
140
|
-
return {
|
|
141
|
-
ok: false,
|
|
142
|
-
message: language === "zh"
|
|
143
|
-
? `回滚失败: ${result.error}`
|
|
144
|
-
: `Rollback failed: ${result.error}`
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
await EventBus.emit({
|
|
149
|
-
type: EVENT_TYPES.TURN_STEP_FINISH,
|
|
150
|
-
sessionId,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
1
|
+
import { isGitRepo } from "../util/git.mjs"
|
|
2
|
+
import { getLatestGhostCommit, listGhostCommits } from "../storage/ghost-commit-store.mjs"
|
|
3
|
+
import { restoreGhostCommit } from "../util/git.mjs"
|
|
4
|
+
import { askQuestionInteractive } from "../tool/question-prompt.mjs"
|
|
5
|
+
import { EventBus } from "../core/events.mjs"
|
|
6
|
+
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 回溯意图检测关键词
|
|
10
|
+
* 分为中文和英文两组,按置信度排序
|
|
11
|
+
*/
|
|
12
|
+
const ROLLBACK_PATTERNS = [
|
|
13
|
+
// 高置信度 — 明确的回退指令(中文不用 \b,英文保留)
|
|
14
|
+
{ pattern: /(回退|撤销|撤回|回滚|还原)/i, confidence: 0.9 },
|
|
15
|
+
{ pattern: /\b(undo|rollback|revert)\b/i, confidence: 0.9 },
|
|
16
|
+
// 中置信度 — 需要上下文
|
|
17
|
+
{ pattern: /(恢复到|恢复之前|回到之前|退回|取消(刚才|上次|之前)的(修改|更改|变更|操作))/i, confidence: 0.8 },
|
|
18
|
+
{ pattern: /\b(restore previous|go back|undo (last|previous|recent))\b/i, confidence: 0.8 },
|
|
19
|
+
// 低置信度 — 可能是回退也可能不是
|
|
20
|
+
{ pattern: /(不要了|算了不改了|改回去|恢复原样)/i, confidence: 0.7 }
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 检测用户消息中的回溯意图
|
|
25
|
+
* @param {string} text - 用户输入文本
|
|
26
|
+
* @returns {{ isRollback: boolean, confidence: number, matchedPattern: string }}
|
|
27
|
+
*/
|
|
28
|
+
export function detectRollbackIntent(text) {
|
|
29
|
+
if (!text || typeof text !== "string") {
|
|
30
|
+
return { isRollback: false, confidence: 0, matchedPattern: "" }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const normalized = text.trim().toLowerCase()
|
|
34
|
+
// 过短的消息不太可能是回退指令(除非就是 "undo" 这样的单词)
|
|
35
|
+
if (normalized.length > 200) {
|
|
36
|
+
return { isRollback: false, confidence: 0, matchedPattern: "" }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const { pattern, confidence } of ROLLBACK_PATTERNS) {
|
|
40
|
+
const match = normalized.match(pattern)
|
|
41
|
+
if (match) {
|
|
42
|
+
return { isRollback: true, confidence, matchedPattern: match[0] }
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return { isRollback: false, confidence: 0, matchedPattern: "" }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 向用户确认是否执行回滚,并展示可用快照
|
|
51
|
+
* @returns {{ confirmed: boolean, snapshotId: string|null, message: string }}
|
|
52
|
+
*/
|
|
53
|
+
export async function confirmRollback({ cwd, language = "en" }) {
|
|
54
|
+
const inGit = await isGitRepo(cwd)
|
|
55
|
+
if (!inGit) {
|
|
56
|
+
return {
|
|
57
|
+
confirmed: false,
|
|
58
|
+
snapshotId: null,
|
|
59
|
+
message: language === "zh"
|
|
60
|
+
? "当前目录不是 Git 仓库,无法执行代码回滚。"
|
|
61
|
+
: "Not a git repository — cannot rollback code changes."
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const latest = await getLatestGhostCommit(cwd)
|
|
66
|
+
if (!latest) {
|
|
67
|
+
return {
|
|
68
|
+
confirmed: false,
|
|
69
|
+
snapshotId: null,
|
|
70
|
+
message: language === "zh"
|
|
71
|
+
? "没有找到可用的快照。本次会话尚未创建任何代码快照,无法回滚。"
|
|
72
|
+
: "No snapshots found. No code snapshots were created in this session."
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const snapDate = new Date(latest.createdAt).toLocaleString()
|
|
77
|
+
const fileCount = latest.files?.length || 0
|
|
78
|
+
const shortHash = latest.commitHash?.slice(0, 8) || "unknown"
|
|
79
|
+
|
|
80
|
+
const zhWarning = [
|
|
81
|
+
`找到最近的快照: ${shortHash} (${snapDate})`,
|
|
82
|
+
`包含 ${fileCount} 个文件: ${(latest.files || []).slice(0, 5).join(", ")}${fileCount > 5 ? " ..." : ""}`,
|
|
83
|
+
"",
|
|
84
|
+
"⚠ 注意: 回滚只能恢复文件变更。已执行的 bash 命令(如安装依赖、删除文件等)无法自动撤销。"
|
|
85
|
+
].join("\n")
|
|
86
|
+
|
|
87
|
+
const enWarning = [
|
|
88
|
+
`Latest snapshot: ${shortHash} (${snapDate})`,
|
|
89
|
+
`Contains ${fileCount} file(s): ${(latest.files || []).slice(0, 5).join(", ")}${fileCount > 5 ? " ..." : ""}`,
|
|
90
|
+
"",
|
|
91
|
+
"Warning: Rollback only restores file changes. Bash commands (installs, deletions, etc.) cannot be undone."
|
|
92
|
+
].join("\n")
|
|
93
|
+
|
|
94
|
+
const answers = await askQuestionInteractive({
|
|
95
|
+
questions: [{
|
|
96
|
+
id: "rollback_confirm",
|
|
97
|
+
text: language === "zh" ? "确认回滚代码?" : "Confirm code rollback?",
|
|
98
|
+
description: language === "zh" ? zhWarning : enWarning,
|
|
99
|
+
options: [
|
|
100
|
+
{
|
|
101
|
+
label: language === "zh" ? "确认回滚" : "Confirm rollback",
|
|
102
|
+
value: "yes",
|
|
103
|
+
description: language === "zh"
|
|
104
|
+
? "恢复文件到快照状态"
|
|
105
|
+
: "Restore files to snapshot state"
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
label: language === "zh" ? "取消" : "Cancel",
|
|
109
|
+
value: "no",
|
|
110
|
+
description: language === "zh"
|
|
111
|
+
? "不执行回滚,继续当前对话"
|
|
112
|
+
: "Skip rollback, continue conversation"
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
allowCustom: false
|
|
116
|
+
}]
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const answer = String(answers.rollback_confirm || "").toLowerCase().trim()
|
|
120
|
+
const confirmed = ["yes", "confirm", "确认回滚", "1"].includes(answer)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
confirmed,
|
|
124
|
+
snapshotId: confirmed ? latest.id : null,
|
|
125
|
+
commitHash: confirmed ? latest.commitHash : null,
|
|
126
|
+
message: confirmed
|
|
127
|
+
? (language === "zh" ? `正在回滚到快照 ${shortHash}...` : `Rolling back to snapshot ${shortHash}...`)
|
|
128
|
+
: (language === "zh" ? "已取消回滚。" : "Rollback cancelled.")
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 执行代码回滚
|
|
134
|
+
* @returns {{ ok: boolean, message: string }}
|
|
135
|
+
*/
|
|
136
|
+
export async function executeRollback({ cwd, commitHash, sessionId, language = "en" }) {
|
|
137
|
+
try {
|
|
138
|
+
const result = await restoreGhostCommit(cwd, commitHash, false)
|
|
139
|
+
if (!result.ok) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
message: language === "zh"
|
|
143
|
+
? `回滚失败: ${result.error}`
|
|
144
|
+
: `Rollback failed: ${result.error}`
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
await EventBus.emit({
|
|
149
|
+
type: EVENT_TYPES.TURN_STEP_FINISH,
|
|
150
|
+
sessionId,
|
|
151
|
+
turnId: null,
|
|
152
|
+
payload: { action: "rollback", commitHash }
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
ok: true,
|
|
157
|
+
message: language === "zh"
|
|
158
|
+
? `已成功回滚到快照 ${commitHash.slice(0, 8)}。文件已恢复,但已执行的 bash 命令无法撤销。`
|
|
159
|
+
: `Rolled back to snapshot ${commitHash.slice(0, 8)}. Files restored, but executed bash commands cannot be undone.`
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
return {
|
|
163
|
+
ok: false,
|
|
164
|
+
message: language === "zh"
|
|
165
|
+
? `回滚异常: ${err.message}`
|
|
166
|
+
: `Rollback error: ${err.message}`
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* 完整的回溯流程:检测 → 确认 → 执行
|
|
173
|
+
* 在 loop.mjs 的 processTurnLoop 入口调用
|
|
174
|
+
*
|
|
175
|
+
* @returns {{ handled: boolean, reply: string }}
|
|
176
|
+
* handled=true 表示消息已被回溯流程处理,不需要再发给模型
|
|
177
|
+
*/
|
|
178
|
+
export async function handleRollbackIfNeeded({ prompt, cwd, sessionId, language = "en" }) {
|
|
179
|
+
const intent = detectRollbackIntent(prompt)
|
|
180
|
+
if (!intent.isRollback) {
|
|
181
|
+
return { handled: false, reply: "" }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const confirmation = await confirmRollback({ cwd, language })
|
|
185
|
+
if (!confirmation.confirmed) {
|
|
186
|
+
return { handled: true, reply: confirmation.message }
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const result = await executeRollback({
|
|
190
|
+
cwd,
|
|
191
|
+
commitHash: confirmation.commitHash,
|
|
192
|
+
sessionId,
|
|
193
|
+
language
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return { handled: true, reply: result.message }
|
|
196
197
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { EventBus } from "../core/events.mjs"
|
|
2
|
+
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
3
|
+
|
|
4
|
+
export async function emitRouteDecisionEvent({
|
|
5
|
+
sessionId,
|
|
6
|
+
source = "repl",
|
|
7
|
+
requestedMode,
|
|
8
|
+
route,
|
|
9
|
+
prompt,
|
|
10
|
+
continuedTransaction = false
|
|
11
|
+
}) {
|
|
12
|
+
if (!route) return null
|
|
13
|
+
return EventBus.emit({
|
|
14
|
+
type: EVENT_TYPES.ROUTE_DECISION,
|
|
15
|
+
sessionId,
|
|
16
|
+
payload: {
|
|
17
|
+
source,
|
|
18
|
+
requestedMode,
|
|
19
|
+
selectedMode: route.mode,
|
|
20
|
+
changed: route.changed === true,
|
|
21
|
+
forced: route.forced === true,
|
|
22
|
+
suggestion: route.suggestion || null,
|
|
23
|
+
reason: route.reason || null,
|
|
24
|
+
explanation: route.explanation || null,
|
|
25
|
+
confidence: route.confidence || null,
|
|
26
|
+
topology: route.topology || null,
|
|
27
|
+
continuity: route.continuity || null,
|
|
28
|
+
evidenceSummary: route.evidenceSummary || null,
|
|
29
|
+
topologySummary: route.topologySummary || null,
|
|
30
|
+
upgradePath: route.upgradePath || null,
|
|
31
|
+
evidence: Array.isArray(route.evidence) ? route.evidence : [],
|
|
32
|
+
promptLength: String(prompt || "").trim().length,
|
|
33
|
+
continuedTransaction
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function emitAgentContinuationInterrupted({
|
|
39
|
+
sessionId,
|
|
40
|
+
summary
|
|
41
|
+
}) {
|
|
42
|
+
return EventBus.emit({
|
|
43
|
+
type: EVENT_TYPES.AGENT_CONTINUATION_INTERRUPTED,
|
|
44
|
+
sessionId,
|
|
45
|
+
payload: {
|
|
46
|
+
objective: summary?.objective || null,
|
|
47
|
+
paths: Array.isArray(summary?.paths) ? summary.paths : [],
|
|
48
|
+
commands: Array.isArray(summary?.commands) ? summary.commands : [],
|
|
49
|
+
routeReason: summary?.routeReason || null,
|
|
50
|
+
evidence: Array.isArray(summary?.evidence) ? summary.evidence : []
|
|
51
|
+
}
|
|
52
|
+
})
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function emitAgentContinuationResumed({
|
|
56
|
+
sessionId,
|
|
57
|
+
summary,
|
|
58
|
+
continuation
|
|
59
|
+
}) {
|
|
60
|
+
return EventBus.emit({
|
|
61
|
+
type: EVENT_TYPES.AGENT_CONTINUATION_RESUMED,
|
|
62
|
+
sessionId,
|
|
63
|
+
payload: {
|
|
64
|
+
objective: summary?.objective || null,
|
|
65
|
+
paths: Array.isArray(summary?.paths) ? summary.paths : [],
|
|
66
|
+
commands: Array.isArray(summary?.commands) ? summary.commands : [],
|
|
67
|
+
routeReason: summary?.routeReason || null,
|
|
68
|
+
evidence: Array.isArray(summary?.evidence) ? summary.evidence : [],
|
|
69
|
+
continuationLength: String(continuation || "").trim().length
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { getSession, listSessions } from "./store.mjs"
|
|
2
|
+
import { listRecoverableSessions } from "./recovery.mjs"
|
|
3
|
+
import { auditStats } from "../storage/audit-store.mjs"
|
|
4
|
+
import { BackgroundManager } from "../orchestration/background-manager.mjs"
|
|
5
|
+
|
|
6
|
+
function summarizeBackgroundCounts(tasks = []) {
|
|
7
|
+
const counts = {
|
|
8
|
+
total: tasks.length,
|
|
9
|
+
pending: 0,
|
|
10
|
+
running: 0,
|
|
11
|
+
completed: 0,
|
|
12
|
+
interrupted: 0,
|
|
13
|
+
error: 0,
|
|
14
|
+
cancelled: 0
|
|
15
|
+
}
|
|
16
|
+
for (const task of tasks) {
|
|
17
|
+
if (counts[task.status] !== undefined) counts[task.status] += 1
|
|
18
|
+
}
|
|
19
|
+
return counts
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function summarizeSessionRuntimeState({ sessionId = null, cwd = process.cwd(), recoveryEnabled = true } = {}) {
|
|
23
|
+
let resolvedSessionId = sessionId
|
|
24
|
+
if (!resolvedSessionId) {
|
|
25
|
+
const sessions = await listSessions({ cwd, limit: 1, includeChildren: true })
|
|
26
|
+
resolvedSessionId = sessions[0]?.id || null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const data = resolvedSessionId ? await getSession(resolvedSessionId) : null
|
|
30
|
+
const recoverable = recoveryEnabled
|
|
31
|
+
? await listRecoverableSessions({ cwd, limit: 20, enabled: true })
|
|
32
|
+
: []
|
|
33
|
+
const backgroundTasks = await BackgroundManager.list()
|
|
34
|
+
const audit = await auditStats()
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
session: data?.session || null,
|
|
38
|
+
messageCount: data?.messages?.length || 0,
|
|
39
|
+
partCount: data?.parts?.length || 0,
|
|
40
|
+
retryMeta: data?.session?.retryMeta || null,
|
|
41
|
+
budgetState: data?.session?.budgetState || null,
|
|
42
|
+
recoverableCount: recoverable.length,
|
|
43
|
+
recoverableSessionIds: recoverable.map((item) => item.id),
|
|
44
|
+
background: summarizeBackgroundCounts(backgroundTasks),
|
|
45
|
+
audit
|
|
46
|
+
}
|
|
47
|
+
}
|