@kkelly-offical/kkcode 0.1.6 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kkelly-offical/kkcode",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Team-first terminal AI coding agent CLI with LongAgent orchestration, GitHub integration and themed UX.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.5.2",
@@ -1,211 +1,220 @@
1
- import { readFile } from "node:fs/promises"
2
- import path from "node:path"
3
- import { fileURLToPath } from "node:url"
4
-
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url))
6
-
7
- const registry = new Map()
8
-
9
- function promptPath(name) {
10
- return path.join(__dirname, "prompt", `${name}.txt`)
11
- }
12
-
13
- async function loadPrompt(name) {
14
- try {
15
- return (await readFile(promptPath(name), "utf8")).trim()
16
- } catch {
17
- return ""
18
- }
19
- }
20
-
21
- export function defineAgent(spec) {
22
- const agent = {
23
- name: spec.name,
24
- description: spec.description || "",
25
- mode: spec.mode || "primary",
26
- permission: spec.permission || "default",
27
- tools: spec.tools || null,
28
- model: spec.model || null,
29
- temperature: spec.temperature ?? null,
30
- maxTurns: spec.maxTurns || null,
31
- hidden: spec.hidden || false,
32
- promptFile: spec.promptFile || spec.name,
33
- _promptCache: spec._promptCache ?? null,
34
- _customAgent: spec._customAgent || false,
35
- _scope: spec._scope || null,
36
- _source: spec._source || null
37
- }
38
- registry.set(agent.name, agent)
39
- return agent
40
- }
41
-
42
- export async function getAgentPrompt(name) {
43
- const agent = registry.get(name)
44
- if (!agent) return ""
45
- if (agent._promptCache !== null) return agent._promptCache
46
- agent._promptCache = await loadPrompt(agent.promptFile)
47
- return agent._promptCache
48
- }
49
-
50
- export function getAgent(name) {
51
- return registry.get(name) || null
52
- }
53
-
54
- export function listAgents({ includeHidden = false } = {}) {
55
- const agents = [...registry.values()]
56
- return includeHidden ? agents : agents.filter((a) => !a.hidden)
57
- }
58
-
59
- export function resolveAgentForMode(mode) {
60
- if (registry.has(mode)) return registry.get(mode)
61
- const modeMap = { ask: "build", plan: "plan", agent: "build", longagent: "longagent" }
62
- const mapped = modeMap[mode]
63
- return mapped ? registry.get(mapped) || null : null
64
- }
65
-
66
- defineAgent({
67
- name: "build",
68
- description: "Default agent with full tool access for code development",
69
- mode: "primary",
70
- permission: "full",
71
- tools: null
72
- })
73
-
74
- defineAgent({
75
- name: "plan",
76
- description: "Read-only analysis agent, no file editing allowed",
77
- mode: "primary",
78
- permission: "readonly",
79
- tools: ["read", "glob", "grep", "list", "bash"]
80
- })
81
-
82
- defineAgent({
83
- name: "explore",
84
- description: "Fast file search subagent for codebase exploration",
85
- mode: "subagent",
86
- permission: "readonly",
87
- tools: ["read", "glob", "grep", "list", "bash"]
88
- })
89
-
90
- defineAgent({
91
- name: "longagent",
92
- description: "Persistent iterative execution agent for complex multi-step tasks",
93
- mode: "primary",
94
- permission: "full",
95
- tools: null
96
- })
97
-
98
- defineAgent({
99
- name: "reviewer",
100
- description: "Code review specialist for analyzing code quality, bugs, and security issues",
101
- mode: "subagent",
102
- permission: "readonly",
103
- tools: ["read", "glob", "grep", "list", "bash"]
104
- })
105
-
106
- defineAgent({
107
- name: "researcher",
108
- description: "Deep codebase research and web-augmented exploration agent",
109
- mode: "subagent",
110
- permission: "readonly",
111
- tools: ["read", "glob", "grep", "list", "bash", "websearch", "codesearch", "webfetch"]
112
- })
113
-
114
- defineAgent({
115
- name: "architect",
116
- description: "Feature architecture designer. Analyzes codebase patterns, designs implementation blueprints with specific files, component designs, data flows.",
117
- mode: "subagent",
118
- permission: "readonly",
119
- tools: ["read", "glob", "grep", "list", "bash"]
120
- })
121
-
122
- defineAgent({
123
- name: "guide",
124
- description: "kkcode self-help guide. Answers questions about kkcode features, tools, configuration, modes, skills, hooks, MCP servers, and usage patterns by searching the kkcode source code.",
125
- mode: "subagent",
126
- permission: "readonly",
127
- tools: ["read", "glob", "grep", "list", "webfetch", "websearch"]
128
- })
129
-
130
- defineAgent({
131
- name: "security-reviewer",
132
- description: "Security audit specialist. Performs OWASP Top 10 checks, hardcoded secret scans, dependency audits, and authentication/authorization reviews.",
133
- mode: "subagent",
134
- permission: "readonly",
135
- tools: ["read", "glob", "grep", "list", "bash"]
136
- })
137
-
138
- defineAgent({
139
- name: "tdd-guide",
140
- description: "TDD specialist. Guides and executes test-driven development: scaffold interfaces, write failing tests (RED), implement minimum code (GREEN), refactor (IMPROVE). Targets 80%+ coverage.",
141
- mode: "subagent",
142
- permission: "full",
143
- tools: ["read", "write", "edit", "bash", "glob", "grep", "list"]
144
- })
145
-
146
- defineAgent({
147
- name: "build-fixer",
148
- description: "Build error diagnosis and repair. Analyzes build failures, identifies root causes, applies fixes, and verifies the build succeeds. Supports TypeScript, Python, Go, Rust, Java.",
149
- mode: "subagent",
150
- permission: "full",
151
- tools: ["read", "write", "edit", "bash", "glob", "grep", "list"]
152
- })
153
-
154
- defineAgent({
155
- name: "frontend-designer",
156
- description: "Frontend design specialist. Creates polished, distinctive UIs with strong aesthetics — typography, color, motion, layout. Avoids generic AI-style designs. Reads project design system (Tailwind, CSS vars, component libraries) and produces production-grade frontend code.",
157
- mode: "subagent",
158
- permission: "full",
159
- tools: ["read", "write", "edit", "bash", "glob", "grep", "list"]
160
- })
161
-
162
- defineAgent({
163
- name: "compaction",
164
- description: "Conversation summarizer for context compression",
165
- mode: "subagent",
166
- permission: "none",
167
- tools: [],
168
- hidden: true
169
- })
170
-
171
- defineAgent({
172
- name: "title",
173
- description: "Session title generator",
174
- mode: "subagent",
175
- permission: "none",
176
- tools: [],
177
- hidden: true
178
- })
179
-
180
- // 4-Stage LongAgent agents
181
- defineAgent({
182
- name: "preview-agent",
183
- description: "4-Stage LongAgent: Stage 1 - Previewing Agent. Explores codebase, extracts requirements, no editing allowed.",
184
- mode: "subagent",
185
- permission: "readonly",
186
- tools: ["read", "glob", "grep", "list", "bash", "question", "todowrite"]
187
- })
188
-
189
- defineAgent({
190
- name: "blueprint-agent",
191
- description: "4-Stage LongAgent: Stage 2 - Blueprint Agent. Creates detailed implementation plan, function designs, architecture.",
192
- mode: "subagent",
193
- permission: "readonly",
194
- tools: ["read", "glob", "grep", "list", "bash", "question", "todowrite"]
195
- })
196
-
197
- defineAgent({
198
- name: "coding-agent",
199
- description: "4-Stage LongAgent: Stage 3 - Coding Agent. Implements code strictly according to blueprint.",
200
- mode: "subagent",
201
- permission: "full",
202
- tools: null
203
- })
204
-
205
- defineAgent({
206
- name: "debugging-agent",
207
- description: "4-Stage LongAgent: Stage 4 - Debugging Agent. Verifies implementation, runs tests, finds and fixes bugs.",
208
- mode: "subagent",
209
- permission: "full",
210
- tools: null
211
- })
1
+ import { readFile } from "node:fs/promises"
2
+ import path from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
+
5
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
6
+
7
+ const registry = new Map()
8
+
9
+ function promptPath(name) {
10
+ return path.join(__dirname, "prompt", `${name}.txt`)
11
+ }
12
+
13
+ async function loadPrompt(name) {
14
+ try {
15
+ return (await readFile(promptPath(name), "utf8")).trim()
16
+ } catch {
17
+ return ""
18
+ }
19
+ }
20
+
21
+ export function defineAgent(spec) {
22
+ const agent = {
23
+ name: spec.name,
24
+ description: spec.description || "",
25
+ mode: spec.mode || "primary",
26
+ permission: spec.permission || "default",
27
+ tools: spec.tools || null,
28
+ model: spec.model || null,
29
+ temperature: spec.temperature ?? null,
30
+ maxTurns: spec.maxTurns || null,
31
+ hidden: spec.hidden || false,
32
+ promptFile: spec.promptFile || spec.name,
33
+ _promptCache: spec._promptCache ?? null,
34
+ _customAgent: spec._customAgent || false,
35
+ _scope: spec._scope || null,
36
+ _source: spec._source || null
37
+ }
38
+ registry.set(agent.name, agent)
39
+ return agent
40
+ }
41
+
42
+ export async function getAgentPrompt(name) {
43
+ const agent = registry.get(name)
44
+ if (!agent) return ""
45
+ if (agent._promptCache !== null) return agent._promptCache
46
+ agent._promptCache = await loadPrompt(agent.promptFile)
47
+ return agent._promptCache
48
+ }
49
+
50
+ export function getAgent(name) {
51
+ return registry.get(name) || null
52
+ }
53
+
54
+ export function listAgents({ includeHidden = false } = {}) {
55
+ const agents = [...registry.values()]
56
+ return includeHidden ? agents : agents.filter((a) => !a.hidden)
57
+ }
58
+
59
+ export function resolveAgentForMode(mode) {
60
+ if (registry.has(mode)) return registry.get(mode)
61
+ const modeMap = { ask: "build", plan: "plan", agent: "build", longagent: "longagent" }
62
+ const mapped = modeMap[mode]
63
+ return mapped ? registry.get(mapped) || null : null
64
+ }
65
+
66
+ defineAgent({
67
+ name: "build",
68
+ description: "Default agent with full tool access for code development",
69
+ mode: "primary",
70
+ permission: "full",
71
+ tools: null
72
+ })
73
+
74
+ defineAgent({
75
+ name: "plan",
76
+ description: "Read-only analysis agent, no file editing allowed",
77
+ mode: "primary",
78
+ permission: "readonly",
79
+ tools: ["read", "glob", "grep", "list", "bash"]
80
+ })
81
+
82
+ defineAgent({
83
+ name: "explore",
84
+ description: "Fast file search subagent for codebase exploration",
85
+ mode: "subagent",
86
+ permission: "readonly",
87
+ tools: ["read", "glob", "grep", "list", "bash"]
88
+ })
89
+
90
+ defineAgent({
91
+ name: "longagent",
92
+ description: "Persistent iterative execution agent for complex multi-step tasks",
93
+ mode: "primary",
94
+ permission: "full",
95
+ tools: null
96
+ })
97
+
98
+ defineAgent({
99
+ name: "reviewer",
100
+ description: "Code review specialist for analyzing code quality, bugs, and security issues",
101
+ mode: "subagent",
102
+ permission: "readonly",
103
+ tools: ["read", "glob", "grep", "list", "bash"]
104
+ })
105
+
106
+ defineAgent({
107
+ name: "researcher",
108
+ description: "Deep codebase research and web-augmented exploration agent",
109
+ mode: "subagent",
110
+ permission: "readonly",
111
+ tools: ["read", "glob", "grep", "list", "bash", "websearch", "codesearch", "webfetch"]
112
+ })
113
+
114
+ defineAgent({
115
+ name: "architect",
116
+ description: "Feature architecture designer. Analyzes codebase patterns, designs implementation blueprints with specific files, component designs, data flows.",
117
+ mode: "subagent",
118
+ permission: "readonly",
119
+ tools: ["read", "glob", "grep", "list", "bash"]
120
+ })
121
+
122
+ defineAgent({
123
+ name: "guide",
124
+ description: "kkcode self-help guide. Answers questions about kkcode features, tools, configuration, modes, skills, hooks, MCP servers, and usage patterns by searching the kkcode source code.",
125
+ mode: "subagent",
126
+ permission: "readonly",
127
+ tools: ["read", "glob", "grep", "list", "webfetch", "websearch"]
128
+ })
129
+
130
+ defineAgent({
131
+ name: "security-reviewer",
132
+ description: "Security audit specialist. Performs OWASP Top 10 checks, hardcoded secret scans, dependency audits, and authentication/authorization reviews.",
133
+ mode: "subagent",
134
+ permission: "readonly",
135
+ tools: ["read", "glob", "grep", "list", "bash"]
136
+ })
137
+
138
+ defineAgent({
139
+ name: "tdd-guide",
140
+ description: "TDD specialist. Guides and executes test-driven development: scaffold interfaces, write failing tests (RED), implement minimum code (GREEN), refactor (IMPROVE). Targets 80%+ coverage.",
141
+ mode: "subagent",
142
+ permission: "full",
143
+ tools: ["read", "write", "edit", "bash", "glob", "grep", "list"]
144
+ })
145
+
146
+ defineAgent({
147
+ name: "build-fixer",
148
+ description: "Build error diagnosis and repair. Analyzes build failures, identifies root causes, applies fixes, and verifies the build succeeds. Supports TypeScript, Python, Go, Rust, Java.",
149
+ mode: "subagent",
150
+ permission: "full",
151
+ tools: ["read", "write", "edit", "bash", "glob", "grep", "list"]
152
+ })
153
+
154
+ defineAgent({
155
+ name: "frontend-designer",
156
+ description: "Frontend design specialist. Creates polished, distinctive UIs with strong aesthetics — typography, color, motion, layout. Avoids generic AI-style designs. Reads project design system (Tailwind, CSS vars, component libraries) and produces production-grade frontend code.",
157
+ mode: "subagent",
158
+ permission: "full",
159
+ tools: ["read", "write", "edit", "bash", "glob", "grep", "list"]
160
+ })
161
+
162
+ defineAgent({
163
+ name: "compaction",
164
+ description: "Conversation summarizer for context compression",
165
+ mode: "subagent",
166
+ permission: "none",
167
+ tools: [],
168
+ hidden: true
169
+ })
170
+
171
+ defineAgent({
172
+ name: "title",
173
+ description: "Session title generator",
174
+ mode: "subagent",
175
+ permission: "none",
176
+ tools: [],
177
+ hidden: true
178
+ })
179
+
180
+ // 4-Stage LongAgent agents
181
+ defineAgent({
182
+ name: "preview-agent",
183
+ description: "4-Stage LongAgent: Stage 1 - Previewing Agent. Explores codebase, extracts requirements, no editing allowed.",
184
+ mode: "subagent",
185
+ permission: "readonly",
186
+ tools: ["read", "glob", "grep", "list", "bash", "question", "todowrite"]
187
+ })
188
+
189
+ defineAgent({
190
+ name: "blueprint-agent",
191
+ description: "4-Stage LongAgent: Stage 2 - Blueprint Agent. Creates detailed implementation plan, function designs, architecture.",
192
+ mode: "subagent",
193
+ permission: "readonly",
194
+ tools: ["read", "glob", "grep", "list", "bash", "question", "todowrite"]
195
+ })
196
+
197
+ defineAgent({
198
+ name: "coding-agent",
199
+ description: "4-Stage LongAgent: Stage 3 - Coding Agent. Implements code strictly according to blueprint.",
200
+ mode: "subagent",
201
+ permission: "full",
202
+ tools: null
203
+ })
204
+
205
+ defineAgent({
206
+ name: "debugging-agent",
207
+ description: "4-Stage LongAgent: Stage 4 - Debugging Agent. Verifies implementation, runs tests, finds and fixes bugs.",
208
+ mode: "subagent",
209
+ permission: "full",
210
+ tools: null
211
+ })
212
+
213
+ defineAgent({
214
+ name: "bug-hunter",
215
+ description: "Deep bug detection specialist. Systematically hunts logic errors, boundary conditions, race conditions, resource leaks, error handling gaps, and state corruption. Reports only HIGH/MEDIUM confidence bugs with concrete trigger paths.",
216
+ mode: "subagent",
217
+ permission: "full",
218
+ maxTurns: 30,
219
+ tools: ["read", "glob", "grep", "list", "bash"]
220
+ })
@@ -0,0 +1,90 @@
1
+ You are a deep bug detection specialist. Your job is to systematically hunt for real, exploitable bugs in codebases — not style issues or theoretical concerns.
2
+
3
+ Available tools: Read, Glob, Grep, List, Bash
4
+
5
+ # Bug Categories (Priority Order)
6
+
7
+ ## 1. Logic Errors
8
+ - Off-by-one errors in loops and array indexing
9
+ - Incorrect boolean logic (De Morgan violations, short-circuit misuse)
10
+ - Wrong comparison operators (== vs ===, < vs <=)
11
+ - Unreachable code paths, dead branches
12
+ - Variable shadowing that changes behavior
13
+ - Missing return statements in conditional branches
14
+
15
+ ## 2. Boundary Conditions
16
+ - Empty arrays/objects/strings not handled
17
+ - null/undefined propagation without guards
18
+ - NaN comparisons (NaN !== NaN)
19
+ - Integer overflow / floating point precision
20
+ - Empty string vs null vs undefined confusion
21
+ - Array index out of bounds
22
+
23
+ ## 3. Race Conditions & Concurrency
24
+ - Async state mutations without synchronization
25
+ - TOCTOU (time-of-check-time-of-use) patterns
26
+ - Promise chains with shared mutable state
27
+ - Event handler ordering assumptions
28
+ - Missing await on async calls
29
+ - Concurrent Map/Set modifications
30
+
31
+ ## 4. Resource Leaks
32
+ - Unclosed file handles, streams, sockets
33
+ - setTimeout/setInterval without cleanup
34
+ - Event listeners added but never removed
35
+ - Database connections not released
36
+ - Child processes not terminated
37
+ - AbortController signals not wired
38
+
39
+ ## 5. Error Handling Gaps
40
+ - Silent catch blocks that swallow errors
41
+ - Unhandled promise rejections
42
+ - try/catch that catches too broadly
43
+ - Error objects losing stack trace (re-throw without cause)
44
+ - finally blocks that override return values
45
+ - Missing error propagation in callbacks
46
+
47
+ ## 6. State Corruption
48
+ - Shared mutable objects passed by reference
49
+ - Stale closures capturing outdated values
50
+ - Cache invalidation misses
51
+ - Partial updates leaving inconsistent state
52
+ - Constructor/init order dependencies
53
+
54
+ # Hunt Process
55
+
56
+ 1. **Map attack surface**: Use `glob` to identify entry points, handlers, and critical paths
57
+ 2. **Trace data flow**: Use `grep` to follow variables from input to output
58
+ 3. **Read critical code**: Use `read` to examine complex functions, error handlers, and state management
59
+ 4. **Verify assumptions**: Use `bash` to run tests, check types, or validate behavior
60
+ 5. **Cross-reference**: Check if the same pattern appears elsewhere (copy-paste bugs)
61
+
62
+ # Confidence Scoring
63
+
64
+ For each bug found, assign confidence:
65
+ - **HIGH** (8-10): Clear bug with reproducible trigger path
66
+ - **MEDIUM** (5-7): Suspicious pattern, needs specific conditions to trigger
67
+ - **LOW** (1-4): Theoretical concern, unlikely in practice — do NOT report these
68
+
69
+ Only report HIGH and MEDIUM confidence bugs.
70
+
71
+ # Output Format
72
+
73
+ For each bug:
74
+ - **Confidence**: HIGH / MEDIUM (with score 1-10)
75
+ - **Category**: From the categories above
76
+ - **File**: file path and line number(s)
77
+ - **Bug**: Clear description of what's wrong
78
+ - **Trigger**: How to reproduce or what conditions cause it
79
+ - **Fix**: Concrete fix with code snippet
80
+
81
+ End with: total bugs by confidence, most critical fix to prioritize.
82
+
83
+ # Anti-Patterns to Avoid
84
+
85
+ - Do NOT report missing documentation or comments
86
+ - Do NOT report style/formatting issues
87
+ - Do NOT report "could be improved" suggestions
88
+ - Do NOT report theoretical vulnerabilities without trigger paths
89
+ - Do NOT flag intentional design decisions as bugs
90
+ - Focus on bugs that WILL cause incorrect behavior in production
package/src/repl.mjs CHANGED
@@ -29,6 +29,7 @@ import { extractImageRefs, buildContentBlocks, readClipboardImage, readClipboard
29
29
  import { generateSkill, saveSkillGlobal } from "./skill/generator.mjs"
30
30
  import { userConfigCandidates, projectConfigCandidates, memoryFilePath } from "./storage/paths.mjs"
31
31
  import { persistTrust, revokeTrust } from "./permission/workspace-trust.mjs"
32
+ import { confirmRollback, executeRollback } from "./session/rollback.mjs"
32
33
 
33
34
  const HIST_DIR = join(homedir(), ".kkcode")
34
35
  const HIST_FILE = join(HIST_DIR, "repl_history")
@@ -88,6 +89,7 @@ const BUILTIN_SLASH = [
88
89
  { name: "longagent", desc: "switch to longagent mode" },
89
90
  { name: "create-skill", desc: "generate a new skill via AI" },
90
91
  { name: "create-agent", desc: "generate a new sub-agent via AI" },
92
+ { name: "undo", desc: "undo last code changes" },
91
93
  { name: "trust", desc: "trust this workspace" },
92
94
  { name: "untrust", desc: "revoke workspace trust" },
93
95
  { name: "exit", desc: "quit" }
@@ -747,7 +749,9 @@ async function processInputLine({
747
749
  else {
748
750
  for (const s of sessions) {
749
751
  const age = ageLabel(Date.now() - s.updatedAt)
750
- print(` ${s.id.slice(0, 12)} ${padRight(s.mode, 9)} ${padRight(s.model || "?", 20)} ${padRight(s.status || "-", 14)} ${age}`)
752
+ const title = s.title || `${s.mode}:${s.model || "?"}`
753
+ const titleClipped = title.length > 35 ? title.slice(0, 32) + "..." : title
754
+ print(` ${s.id.slice(0, 12)} ${padRight(titleClipped, 36)} ${padRight(s.mode, 9)} ${padRight(s.status || "-", 10)} ${age}`)
751
755
  }
752
756
  }
753
757
  return { exit: false }
@@ -756,12 +760,42 @@ async function processInputLine({
756
760
  if (normalized === "/resume" || normalized.startsWith("/resume ") || normalized === "/r" || normalized.startsWith("/r ")) {
757
761
  const arg = normalized.replace(/^\/(resume|r)/, "").trim()
758
762
  const sessions = await listSessions({ cwd: process.cwd(), limit: 20, includeChildren: false })
763
+
764
+ if (!sessions.length) {
765
+ print("no sessions found in current directory")
766
+ return { exit: false }
767
+ }
768
+
759
769
  let target = null
760
- if (!arg) target = sessions[0] || null
761
- else target = sessions.find((s) => s.id === arg || s.id.startsWith(arg)) || null
770
+
771
+ if (!arg) {
772
+ // Show interactive numbered list
773
+ print(`\n Sessions in ${paint(process.cwd(), "cyan")}:\n`)
774
+ for (let i = 0; i < sessions.length; i++) {
775
+ const s = sessions[i]
776
+ const num = paint(` ${String(i + 1).padStart(2)}.`, "yellow")
777
+ const title = s.title || `${s.mode}:${s.model || "?"}`
778
+ const titleClipped = title.length > 45 ? title.slice(0, 42) + "..." : title
779
+ const age = ageLabel(Date.now() - s.updatedAt)
780
+ const mode = paint(padRight(s.mode, 9), "cyan")
781
+ const status = s.status === "active" ? paint("active", "green") : paint(s.status || "-", null, { dim: true })
782
+ print(`${num} ${padRight(titleClipped, 46)} ${mode} ${padRight(status, 14)} ${paint(age, null, { dim: true })}`)
783
+ }
784
+ print(`\n usage: ${paint("/resume <number>", "yellow")} or ${paint("/resume <session-id>", "yellow")}`)
785
+ return { exit: false }
786
+ }
787
+
788
+ // Try numeric index first (1-based)
789
+ const idx = parseInt(arg, 10)
790
+ if (!Number.isNaN(idx) && idx >= 1 && idx <= sessions.length) {
791
+ target = sessions[idx - 1]
792
+ } else {
793
+ // Fallback to ID prefix match
794
+ target = sessions.find((s) => s.id === arg || s.id.startsWith(arg)) || null
795
+ }
762
796
 
763
797
  if (!target) {
764
- print(arg ? `no session matching "${arg}"` : "no sessions to resume")
798
+ print(`no session matching "${arg}"`)
765
799
  return { exit: false }
766
800
  }
767
801
 
@@ -769,15 +803,33 @@ async function processInputLine({
769
803
  state.mode = target.mode || state.mode
770
804
  state.providerType = target.providerType || state.providerType
771
805
  state.model = target.model || state.model
772
- print(`resumed session: ${target.id} (${target.mode}, ${target.model || "?"})`)
806
+ const title = target.title || `${target.mode}:${target.model || "?"}`
807
+ print(`resumed: ${paint(title, "cyan")} (${target.mode}, ${target.model || "?"})`)
773
808
  const msgs = await getConversationHistory(target.id, 3)
774
809
  for (const m of msgs) {
775
- const preview = m.content.length > 84 ? `${m.content.slice(0, 84)}...` : m.content
810
+ const text = typeof m.content === "string" ? m.content : JSON.stringify(m.content)
811
+ const preview = text.length > 84 ? `${text.slice(0, 84)}...` : text
776
812
  print(` [${m.role}] ${preview}`)
777
813
  }
778
814
  return { exit: false }
779
815
  }
780
816
 
817
+ if (normalized === "/undo") {
818
+ const language = ctx.configState.config.language || "en"
819
+ const cwd = process.cwd()
820
+ const confirmation = await confirmRollback({ cwd, language })
821
+ print(confirmation.message)
822
+ if (!confirmation.confirmed) return { exit: false }
823
+ const result = await executeRollback({
824
+ cwd,
825
+ commitHash: confirmation.commitHash,
826
+ sessionId: state.sessionId,
827
+ language
828
+ })
829
+ print(result.message)
830
+ return { exit: false }
831
+ }
832
+
781
833
  if (["/ask", "/plan", "/agent", "/longagent"].includes(normalized)) {
782
834
  state.mode = resolveMode(normalized.slice(1))
783
835
  print(`mode switched: ${state.mode}`)