@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.
Files changed (166) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +474 -387
  3. package/package.json +50 -46
  4. package/src/agent/agent.mjs +228 -220
  5. package/src/agent/custom-agent-loader.mjs +6 -3
  6. package/src/agent/generator.mjs +2 -2
  7. package/src/agent/prompt/assistant.txt +12 -0
  8. package/src/agent/prompt/bug-hunter.txt +89 -89
  9. package/src/agent/prompt/frontend-designer.txt +58 -58
  10. package/src/agent/prompt/guide.txt +1 -1
  11. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
  12. package/src/agent/prompt/longagent-coding-agent.txt +37 -37
  13. package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
  14. package/src/agent/prompt/longagent-preview-agent.txt +63 -63
  15. package/src/command/custom-commands.mjs +2 -2
  16. package/src/commands/agent.mjs +1 -1
  17. package/src/commands/background.mjs +145 -4
  18. package/src/commands/chat.mjs +117 -76
  19. package/src/commands/config.mjs +148 -1
  20. package/src/commands/doctor.mjs +30 -6
  21. package/src/commands/init.mjs +32 -6
  22. package/src/commands/longagent.mjs +117 -0
  23. package/src/commands/mcp.mjs +275 -43
  24. package/src/commands/permission.mjs +1 -1
  25. package/src/commands/session.mjs +195 -140
  26. package/src/commands/skill.mjs +63 -0
  27. package/src/commands/theme.mjs +1 -1
  28. package/src/commands/update.mjs +32 -0
  29. package/src/config/defaults.mjs +289 -260
  30. package/src/config/import-config.mjs +1 -1
  31. package/src/config/load-config.mjs +61 -4
  32. package/src/config/schema.mjs +604 -574
  33. package/src/context.mjs +4 -1
  34. package/src/core/constants.mjs +97 -91
  35. package/src/core/types.mjs +1 -1
  36. package/src/github/api.mjs +78 -78
  37. package/src/github/auth.mjs +294 -286
  38. package/src/github/flow.mjs +298 -298
  39. package/src/github/workspace.mjs +225 -212
  40. package/src/index.mjs +87 -82
  41. package/src/knowledge/frontend-aesthetics.txt +38 -38
  42. package/src/mcp/client-http.mjs +139 -141
  43. package/src/mcp/client-sse.mjs +297 -288
  44. package/src/mcp/client-stdio.mjs +534 -533
  45. package/src/mcp/constants.mjs +4 -2
  46. package/src/mcp/registry.mjs +498 -479
  47. package/src/mcp/stdio-framing.mjs +135 -133
  48. package/src/mcp/tool-result.mjs +24 -24
  49. package/src/observability/edit-diagnostics.mjs +449 -0
  50. package/src/observability/index.mjs +42 -42
  51. package/src/observability/metrics.mjs +165 -137
  52. package/src/observability/tracer.mjs +137 -137
  53. package/src/onboarding.mjs +209 -0
  54. package/src/orchestration/background-manager.mjs +567 -372
  55. package/src/orchestration/background-worker.mjs +419 -305
  56. package/src/orchestration/interruption-reason.mjs +21 -0
  57. package/src/orchestration/longagent-manager.mjs +197 -171
  58. package/src/orchestration/stage-scheduler.mjs +733 -728
  59. package/src/orchestration/subagent-router.mjs +7 -1
  60. package/src/orchestration/task-scheduler.mjs +219 -7
  61. package/src/permission/engine.mjs +1 -1
  62. package/src/permission/exec-policy.mjs +370 -370
  63. package/src/permission/file-edit-policy.mjs +108 -0
  64. package/src/permission/prompt.mjs +1 -1
  65. package/src/permission/rules.mjs +116 -7
  66. package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
  67. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
  68. package/src/plugin/hook-bus.mjs +19 -5
  69. package/src/plugin/manifest-loader.mjs +222 -0
  70. package/src/provider/anthropic.mjs +396 -390
  71. package/src/provider/ollama.mjs +7 -1
  72. package/src/provider/openai.mjs +382 -340
  73. package/src/provider/retry-policy.mjs +74 -68
  74. package/src/provider/router.mjs +242 -241
  75. package/src/provider/sse.mjs +104 -104
  76. package/src/provider/wizard.mjs +556 -0
  77. package/src/repl/capability-facade.mjs +30 -0
  78. package/src/repl/command-surface.mjs +23 -0
  79. package/src/repl/controller-entry.mjs +40 -0
  80. package/src/repl/core-shell.mjs +208 -0
  81. package/src/repl/dialog-router.mjs +87 -0
  82. package/src/repl/input-engine.mjs +76 -0
  83. package/src/repl/keymap.mjs +7 -0
  84. package/src/repl/operator-surface.mjs +15 -0
  85. package/src/repl/permission-flow.mjs +49 -0
  86. package/src/repl/runtime-facade.mjs +36 -0
  87. package/src/repl/slash-router.mjs +62 -0
  88. package/src/repl/state-store.mjs +29 -0
  89. package/src/repl/turn-controller.mjs +58 -0
  90. package/src/repl/verification.mjs +23 -0
  91. package/src/repl.mjs +3371 -2981
  92. package/src/rules/load-rules.mjs +3 -3
  93. package/src/runtime.mjs +1 -1
  94. package/src/session/agent-transaction.mjs +86 -0
  95. package/src/session/checkpoint.mjs +302 -302
  96. package/src/session/compaction.mjs +298 -298
  97. package/src/session/engine.mjs +417 -232
  98. package/src/session/longagent-4stage.mjs +467 -460
  99. package/src/session/longagent-hybrid.mjs +1344 -1097
  100. package/src/session/longagent-plan.mjs +376 -365
  101. package/src/session/longagent-project-memory.mjs +53 -53
  102. package/src/session/longagent-scaffold.mjs +291 -291
  103. package/src/session/longagent-task-bus.mjs +138 -54
  104. package/src/session/longagent-utils.mjs +828 -472
  105. package/src/session/longagent.mjs +911 -900
  106. package/src/session/loop.mjs +1005 -930
  107. package/src/session/prompt/agent.txt +25 -25
  108. package/src/session/prompt/anthropic.txt +150 -150
  109. package/src/session/prompt/beast.txt +1 -1
  110. package/src/session/prompt/plan.txt +31 -31
  111. package/src/session/prompt/qwen.txt +46 -46
  112. package/src/session/recovery.mjs +21 -0
  113. package/src/session/rollback.mjs +196 -195
  114. package/src/session/routing-observability.mjs +72 -0
  115. package/src/session/runtime-state.mjs +47 -0
  116. package/src/session/store.mjs +523 -519
  117. package/src/session/system-prompt.mjs +308 -273
  118. package/src/session/task-validator.mjs +267 -267
  119. package/src/session/usability-gates.mjs +2 -2
  120. package/src/skill/builtin/commit.mjs +64 -64
  121. package/src/skill/builtin/design.mjs +76 -76
  122. package/src/skill/generator.mjs +18 -2
  123. package/src/skill/registry.mjs +642 -390
  124. package/src/storage/audit-store.mjs +18 -11
  125. package/src/storage/event-log.mjs +7 -1
  126. package/src/storage/ghost-commit-store.mjs +243 -245
  127. package/src/storage/paths.mjs +17 -0
  128. package/src/theme/default-theme.mjs +1 -1
  129. package/src/theme/markdown.mjs +4 -0
  130. package/src/theme/schema.mjs +1 -1
  131. package/src/theme/status-bar.mjs +162 -158
  132. package/src/tool/audit-wrapper.mjs +18 -2
  133. package/src/tool/edit-transaction.mjs +23 -0
  134. package/src/tool/executor.mjs +26 -1
  135. package/src/tool/file-read-state.mjs +65 -0
  136. package/src/tool/git-auto.mjs +526 -526
  137. package/src/tool/git-full-auto.mjs +487 -478
  138. package/src/tool/mutation-guard.mjs +54 -0
  139. package/src/tool/prompt/edit.txt +3 -3
  140. package/src/tool/prompt/multiedit.txt +1 -0
  141. package/src/tool/prompt/notebookedit.txt +2 -1
  142. package/src/tool/prompt/patch.txt +25 -24
  143. package/src/tool/prompt/read.txt +3 -3
  144. package/src/tool/prompt/sysinfo.txt +29 -0
  145. package/src/tool/prompt/task.txt +66 -4
  146. package/src/tool/prompt/write.txt +2 -2
  147. package/src/tool/question-prompt.mjs +99 -93
  148. package/src/tool/registry.mjs +1701 -1343
  149. package/src/tool/task-tool.mjs +14 -6
  150. package/src/ui/activity-renderer.mjs +667 -664
  151. package/src/ui/repl-background-panel.mjs +7 -0
  152. package/src/ui/repl-capability-panel.mjs +9 -0
  153. package/src/ui/repl-dashboard.mjs +54 -4
  154. package/src/ui/repl-help.mjs +110 -0
  155. package/src/ui/repl-operator-panel.mjs +12 -0
  156. package/src/ui/repl-route-feedback.mjs +35 -0
  157. package/src/ui/repl-status-view.mjs +76 -0
  158. package/src/ui/repl-task-panel.mjs +5 -0
  159. package/src/ui/repl-transcript-panel.mjs +56 -0
  160. package/src/ui/repl-turn-summary.mjs +135 -0
  161. package/src/update/checker.mjs +184 -0
  162. package/src/usage/pricing.mjs +122 -121
  163. package/src/usage/usage-meter.mjs +1 -0
  164. package/src/util/git.mjs +562 -519
  165. package/src/util/template.mjs +6 -1
  166. package/src/version.mjs +3 -0
@@ -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
- payload: { action: "rollback", commitHash }
152
- })
153
-
154
- return {
155
- ok: true,
156
- message: language === "zh"
157
- ? `已成功回滚到快照 ${commitHash.slice(0, 8)}。文件已恢复,但已执行的 bash 命令无法撤销。`
158
- : `Rolled back to snapshot ${commitHash.slice(0, 8)}. Files restored, but executed bash commands cannot be undone.`
159
- }
160
- } catch (err) {
161
- return {
162
- ok: false,
163
- message: language === "zh"
164
- ? `回滚异常: ${err.message}`
165
- : `Rollback error: ${err.message}`
166
- }
167
- }
168
- }
169
-
170
- /**
171
- * 完整的回溯流程:检测 → 确认 → 执行
172
- * loop.mjs processTurnLoop 入口调用
173
- *
174
- * @returns {{ handled: boolean, reply: string }}
175
- * handled=true 表示消息已被回溯流程处理,不需要再发给模型
176
- */
177
- export async function handleRollbackIfNeeded({ prompt, cwd, sessionId, language = "en" }) {
178
- const intent = detectRollbackIntent(prompt)
179
- if (!intent.isRollback) {
180
- return { handled: false, reply: "" }
181
- }
182
-
183
- const confirmation = await confirmRollback({ cwd, language })
184
- if (!confirmation.confirmed) {
185
- return { handled: true, reply: confirmation.message }
186
- }
187
-
188
- const result = await executeRollback({
189
- cwd,
190
- commitHash: confirmation.commitHash,
191
- sessionId,
192
- language
193
- })
194
-
195
- return { handled: true, reply: result.message }
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
+ }