@kkelly-offical/kkcode 0.1.3 → 0.1.7

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 (66) hide show
  1. package/README.md +110 -172
  2. package/package.json +46 -46
  3. package/src/agent/agent.mjs +220 -170
  4. package/src/agent/prompt/bug-hunter.txt +90 -0
  5. package/src/agent/prompt/frontend-designer.txt +58 -0
  6. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -0
  7. package/src/agent/prompt/longagent-coding-agent.txt +37 -0
  8. package/src/agent/prompt/longagent-debugging-agent.txt +46 -0
  9. package/src/agent/prompt/longagent-preview-agent.txt +63 -0
  10. package/src/config/defaults.mjs +260 -195
  11. package/src/config/schema.mjs +71 -6
  12. package/src/core/constants.mjs +91 -46
  13. package/src/index.mjs +1 -1
  14. package/src/knowledge/frontend-aesthetics.txt +39 -0
  15. package/src/knowledge/loader.mjs +2 -1
  16. package/src/knowledge/tailwind.txt +12 -3
  17. package/src/mcp/client-http.mjs +141 -157
  18. package/src/mcp/client-sse.mjs +288 -286
  19. package/src/mcp/client-stdio.mjs +533 -451
  20. package/src/mcp/constants.mjs +2 -0
  21. package/src/mcp/registry.mjs +479 -394
  22. package/src/mcp/stdio-framing.mjs +133 -127
  23. package/src/mcp/tool-result.mjs +24 -0
  24. package/src/observability/index.mjs +42 -0
  25. package/src/observability/metrics.mjs +137 -0
  26. package/src/observability/tracer.mjs +137 -0
  27. package/src/orchestration/background-manager.mjs +372 -358
  28. package/src/orchestration/background-worker.mjs +305 -245
  29. package/src/orchestration/longagent-manager.mjs +171 -116
  30. package/src/orchestration/stage-scheduler.mjs +728 -489
  31. package/src/permission/exec-policy.mjs +9 -11
  32. package/src/provider/anthropic.mjs +1 -0
  33. package/src/provider/openai.mjs +340 -339
  34. package/src/provider/retry-policy.mjs +68 -68
  35. package/src/provider/router.mjs +241 -228
  36. package/src/provider/sse.mjs +104 -91
  37. package/src/repl.mjs +59 -7
  38. package/src/session/checkpoint.mjs +66 -3
  39. package/src/session/compaction.mjs +298 -276
  40. package/src/session/engine.mjs +232 -225
  41. package/src/session/longagent-4stage.mjs +460 -0
  42. package/src/session/longagent-hybrid.mjs +1097 -0
  43. package/src/session/longagent-plan.mjs +365 -329
  44. package/src/session/longagent-project-memory.mjs +53 -0
  45. package/src/session/longagent-scaffold.mjs +291 -100
  46. package/src/session/longagent-task-bus.mjs +54 -0
  47. package/src/session/longagent-utils.mjs +472 -0
  48. package/src/session/longagent.mjs +900 -1462
  49. package/src/session/loop.mjs +65 -40
  50. package/src/session/project-context.mjs +30 -0
  51. package/src/session/prompt/agent.txt +25 -0
  52. package/src/session/prompt/plan.txt +31 -9
  53. package/src/session/rollback.mjs +196 -0
  54. package/src/session/store.mjs +519 -503
  55. package/src/session/system-prompt.mjs +273 -260
  56. package/src/session/task-validator.mjs +4 -3
  57. package/src/skill/builtin/design.mjs +76 -0
  58. package/src/skill/builtin/frontend.mjs +8 -0
  59. package/src/skill/registry.mjs +390 -336
  60. package/src/storage/ghost-commit-store.mjs +18 -8
  61. package/src/tool/executor.mjs +11 -0
  62. package/src/tool/git-auto.mjs +0 -19
  63. package/src/tool/question-prompt.mjs +93 -86
  64. package/src/tool/registry.mjs +71 -37
  65. package/src/ui/activity-renderer.mjs +664 -410
  66. package/src/util/git.mjs +23 -0
@@ -29,8 +29,11 @@ import { saveCheckpoint } from "./checkpoint.mjs"
29
29
  import { askPlanApproval } from "../tool/question-prompt.mjs"
30
30
  import { createValidator } from "./task-validator.mjs"
31
31
 
32
+ // Max chars kept in active context per tool_result — process output beyond this is truncated
33
+ const TOOL_RESULT_ACTIVE_LIMIT = 3000
34
+
32
35
  const READ_ONLY_TOOLS = new Set([
33
- "read", "glob", "grep", "list", "webfetch", "websearch", "codesearch", "background_output", "todowrite", "enter_plan", "exit_plan"
36
+ "read", "glob", "grep", "list", "webfetch", "websearch", "codesearch", "background_output", "todowrite", "enter_plan"
34
37
  ])
35
38
 
36
39
  function addUsage(target, delta) {
@@ -534,9 +537,16 @@ export async function processTurnLoop({
534
537
  ? `\n被截断的工具调用: ${truncatedToolNames}。请完整重新发起这些工具调用。如果是创建大文件,使用 write(mode="append") 分段追加;如果是修改已有文件的局部内容,使用 patch 按行号范围替换。`
535
538
  : `\nTruncated tool calls: ${truncatedToolNames}. Re-issue these tool calls completely. For large file creation, use write(mode="append") to append in chunks. For modifying sections of existing files, use patch to replace by line range.`)
536
539
  : ""
540
+ // Anchor: last 200 chars of truncated text so model knows exactly where to resume
541
+ const textTail = response.text ? response.text.slice(-200) : ""
542
+ const anchorHint = textTail
543
+ ? (language === "zh"
544
+ ? `\n[锚点] 上次输出末尾:...${textTail}`
545
+ : `\n[Anchor] Last output ended with: ...${textTail}`)
546
+ : ""
537
547
  const continuePrompt = language === "zh"
538
- ? `[输出被截断 ${continueCount}/${MAX_CONTINUES}] 你的上一条回复在输出 token 上限处被截断。请从你停止的地方精确继续,不要重复已经写过的内容。如果你正在执行工具调用,请完整重新发起。${toolHint}`
539
- : `[OUTPUT TRUNCATED ${continueCount}/${MAX_CONTINUES}] Your previous response was cut off at the output token limit. Continue EXACTLY from where you stopped. Do not repeat any content you already wrote. If you were in the middle of a tool call, re-issue it completely.${toolHint}`
548
+ ? `[输出被截断 ${continueCount}/${MAX_CONTINUES}] 你的上一条回复在输出 token 上限处被截断。请从你停止的地方精确继续,不要重复已经写过的内容。如果你正在执行工具调用,请完整重新发起。${toolHint}${anchorHint}`
549
+ : `[OUTPUT TRUNCATED ${continueCount}/${MAX_CONTINUES}] Your previous response was cut off at the output token limit. Continue EXACTLY from where you stopped. Do not repeat any content you already wrote. If you were in the middle of a tool call, re-issue it completely.${toolHint}${anchorHint}`
540
550
  await appendMessage(sessionId, "user", continuePrompt,
541
551
  { mode, model, providerType, step, turnId, synthetic: true }
542
552
  )
@@ -642,42 +652,51 @@ export async function processTurnLoop({
642
652
  }
643
653
  }
644
654
 
645
- await PermissionEngine.check({
646
- config: configState.config,
647
- sessionId,
648
- tool: call.name,
649
- mode,
650
- pattern,
651
- command,
652
- risk,
653
- reason: `tool call from model at step ${step}`
654
- })
655
+ // Plan mode enforcement: block write tools when _planMode is active
656
+ if (toolContext._planMode && !READ_ONLY_TOOLS.has(call.name) && call.name !== "exit_plan") {
657
+ result = {
658
+ name: call.name,
659
+ status: "error",
660
+ output: `[PLAN MODE] Cannot execute '${call.name}' in plan mode. Finish your plan outline and call exit_plan to present it for user approval.`
661
+ }
662
+ } else {
663
+ await PermissionEngine.check({
664
+ config: configState.config,
665
+ sessionId,
666
+ tool: call.name,
667
+ mode,
668
+ pattern,
669
+ command,
670
+ risk,
671
+ reason: `tool call from model at step ${step}`
672
+ })
655
673
 
656
- const tool = await ToolRegistry.get(call.name)
657
- result = !tool
658
- ? {
659
- name: call.name,
660
- status: "error",
661
- output: `unknown tool: ${call.name}`,
662
- error: `unknown tool: ${call.name}`
663
- }
664
- : await executeTool({
665
- tool,
666
- args: call.args,
667
- sessionId,
668
- turnId,
669
- context: {
670
- cwd,
671
- mode,
672
- delegateTask,
673
- signal,
674
+ const tool = await ToolRegistry.get(call.name)
675
+ result = !tool
676
+ ? {
677
+ name: call.name,
678
+ status: "error",
679
+ output: `unknown tool: ${call.name}`,
680
+ error: `unknown tool: ${call.name}`
681
+ }
682
+ : await executeTool({
683
+ tool,
684
+ args: call.args,
674
685
  sessionId,
675
686
  turnId,
676
- config: configState.config,
677
- ...toolContext
678
- },
679
- signal
680
- })
687
+ context: {
688
+ cwd,
689
+ mode,
690
+ delegateTask,
691
+ signal,
692
+ sessionId,
693
+ turnId,
694
+ config: configState.config,
695
+ ...toolContext
696
+ },
697
+ signal
698
+ })
699
+ }
681
700
  } catch (error) {
682
701
  result = {
683
702
  name: call.name,
@@ -700,8 +719,10 @@ export async function processTurnLoop({
700
719
  result = {
701
720
  ...result,
702
721
  output: approval.approved
703
- ? "User APPROVED the plan. Proceed with implementation."
704
- : `User REJECTED the plan. Feedback: ${approval.feedback || "no feedback provided"}`,
722
+ ? "User APPROVED the plan. Proceed with implementation immediately."
723
+ : approval.requestChanges
724
+ ? `User requested changes to the plan. Feedback: ${approval.feedback || "no specific feedback"}. Revise your plan and call exit_plan again with the updated plan.`
725
+ : `User REJECTED the plan. Feedback: ${approval.feedback || "no feedback provided"}. Do not proceed — the plan has been cancelled.`,
705
726
  metadata: { ...result.metadata, planApprovalResult: approval }
706
727
  }
707
728
  }
@@ -798,15 +819,19 @@ export async function processTurnLoop({
798
819
  })
799
820
 
800
821
  // User message: tool_result blocks (one per tool call, in order)
822
+ // Process output beyond TOOL_RESULT_ACTIVE_LIMIT is truncated to keep context lean
801
823
  const resultContent = []
802
824
  for (const call of response.toolCalls) {
803
825
  const entry = callResults.get(call.id)
804
- const output = entry?.result?.output || ""
826
+ const rawOutput = entry?.result?.output || ""
805
827
  const isError = entry?.result?.status === "error"
828
+ const content = rawOutput.length > TOOL_RESULT_ACTIVE_LIMIT
829
+ ? `${rawOutput.slice(0, TOOL_RESULT_ACTIVE_LIMIT)}\n[...过程输出已截断,共 ${rawOutput.length} 字符,仅保留前 ${TOOL_RESULT_ACTIVE_LIMIT} 字符]`
830
+ : rawOutput
806
831
  resultContent.push({
807
832
  type: "tool_result",
808
833
  tool_use_id: call.id,
809
- content: output,
834
+ content,
810
835
  is_error: isError
811
836
  })
812
837
  }
@@ -79,6 +79,32 @@ function detectFeatures(allDeps) {
79
79
  return features
80
80
  }
81
81
 
82
+ /** Detect CSS framework used in the project */
83
+ function detectCssFramework(allDeps) {
84
+ if (allDeps.tailwindcss) return "tailwind"
85
+ if (allDeps.unocss || allDeps["@unocss/core"]) return "unocss"
86
+ if (allDeps["styled-components"]) return "styled-components"
87
+ if (allDeps["@emotion/react"]) return "emotion"
88
+ if (allDeps.sass || allDeps["sass-loader"]) return "sass"
89
+ return null
90
+ }
91
+
92
+ /** Detect UI component library */
93
+ function detectComponentLib(allDeps) {
94
+ if (allDeps["@shadcn/ui"] || allDeps["shadcn-ui"]) return "shadcn/ui"
95
+ if (allDeps["antd"]) return "antd"
96
+ if (allDeps["element-plus"]) return "element-plus"
97
+ if (allDeps["@mui/material"]) return "mui"
98
+ if (allDeps["@chakra-ui/react"]) return "chakra-ui"
99
+ if (allDeps["@radix-ui/react-dialog"] || allDeps["@radix-ui/themes"]) return "radix"
100
+ if (allDeps["@headlessui/react"]) return "headless-ui"
101
+ if (allDeps["@mantine/core"]) return "mantine"
102
+ if (allDeps["naive-ui"]) return "naive-ui"
103
+ if (allDeps["vuetify"]) return "vuetify"
104
+ if (allDeps["@arco-design/web-react"] || allDeps["@arco-design/web-vue"]) return "arco-design"
105
+ return null
106
+ }
107
+
82
108
  async function detectStructure(cwd) {
83
109
  const dirs = []
84
110
  try {
@@ -341,6 +367,10 @@ export async function detectProjectContext(cwd) {
341
367
  if (structure.length) lines.push(` structure: ${structure.join(", ")}`)
342
368
  if (projectType) lines.push(` type: ${projectType}`)
343
369
  if (features.length) lines.push(` features: ${features.join(", ")}`)
370
+ const cssFramework = detectCssFramework(allDeps)
371
+ if (cssFramework) lines.push(` css_framework: ${cssFramework}`)
372
+ const componentLib = detectComponentLib(allDeps)
373
+ if (componentLib) lines.push(` component_lib: ${componentLib}`)
344
374
  const hasDocker = await exists(path.join(cwd, "Dockerfile"))
345
375
  if (hasDocker) lines.push(` docker: true`)
346
376
  lines.push("</project>")
@@ -0,0 +1,25 @@
1
+ Agent mode active. Full tool access enabled.
2
+
3
+ ## When to use plan mode first
4
+
5
+ Call enter_plan PROACTIVELY before making changes when ANY of these apply:
6
+ - Task requires changes to 3+ files
7
+ - Multiple valid implementation approaches exist
8
+ - Architectural decisions need user input
9
+ - The user hasn't specified implementation details
10
+ - The task involves refactoring, new features, or system-level changes
11
+
12
+ Simple tasks can proceed directly without planning:
13
+ - Single-file bug fix with clear solution
14
+ - Typo or minor text correction
15
+ - Adding one small function with clear requirements
16
+
17
+ ## Plan → Execute flow
18
+
19
+ 1. Call enter_plan (reason: why planning is needed)
20
+ 2. Explore the codebase to understand the context (read, glob, grep)
21
+ 3. Outline your complete plan in your response text
22
+ 4. Call exit_plan(plan, files) to present it for user approval
23
+ 5. If approved: proceed with implementation immediately
24
+ 6. If changes requested: revise and call exit_plan again
25
+ 7. If rejected: stop and ask the user what they want instead
@@ -1,9 +1,31 @@
1
- Plan mode active.
2
-
3
- Return:
4
- 1. goal summary
5
- 2. implementation steps
6
- 3. files to edit
7
- 4. validation checklist
8
-
9
- Do not execute mutation actions in this mode.
1
+ Plan mode active.
2
+
3
+ ## Your task
4
+ Create a detailed implementation plan. Do NOT execute any file mutations, bash commands, or write operations in this mode.
5
+
6
+ ## Required plan format
7
+
8
+ 1. **Goal**: Clear statement of what will be accomplished
9
+ 2. **Approach**: High-level strategy and key architectural decisions
10
+ 3. **Implementation steps**: Numbered, specific, actionable steps
11
+ 4. **Files to create/modify**: List each file with the specific change description
12
+ 5. **Validation checklist**: How to verify the implementation is correct (tests, syntax checks, manual steps)
13
+
14
+ ## Quality criteria
15
+ - Each step must be specific enough to execute without ambiguity
16
+ - Include file paths, function names, and line numbers where relevant
17
+ - Identify dependencies between steps (step N must complete before step M)
18
+ - Flag risks, trade-offs, or alternative approaches considered
19
+ - Estimate complexity: simple / medium / complex
20
+
21
+ ## Allowed actions in plan mode
22
+ - Read files (read, glob, grep)
23
+ - Search the web (websearch, codesearch)
24
+ - Explore the codebase to gather information
25
+
26
+ ## After creating your plan
27
+ Call exit_plan with the complete plan text and the list of files to modify.
28
+ The user will then choose:
29
+ - **Approve** → you proceed with implementation immediately
30
+ - **Request Changes** → you revise the plan and call exit_plan again
31
+ - **Reject** → the plan is cancelled, do not proceed
@@ -0,0 +1,196 @@
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 }
196
+ }