@kkelly-offical/kkcode 0.1.6 → 0.2.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 (163) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +452 -387
  3. package/package.json +50 -46
  4. package/src/agent/agent.mjs +19 -2
  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 +90 -0
  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/config/defaults.mjs +280 -260
  29. package/src/config/import-config.mjs +1 -1
  30. package/src/config/load-config.mjs +61 -4
  31. package/src/config/schema.mjs +591 -574
  32. package/src/context.mjs +4 -1
  33. package/src/core/constants.mjs +97 -91
  34. package/src/core/types.mjs +1 -1
  35. package/src/github/api.mjs +78 -78
  36. package/src/github/auth.mjs +294 -286
  37. package/src/github/flow.mjs +298 -298
  38. package/src/github/workspace.mjs +225 -212
  39. package/src/index.mjs +84 -82
  40. package/src/knowledge/frontend-aesthetics.txt +38 -38
  41. package/src/mcp/client-http.mjs +139 -141
  42. package/src/mcp/client-sse.mjs +297 -288
  43. package/src/mcp/client-stdio.mjs +534 -533
  44. package/src/mcp/constants.mjs +2 -2
  45. package/src/mcp/registry.mjs +498 -479
  46. package/src/mcp/stdio-framing.mjs +135 -133
  47. package/src/mcp/tool-result.mjs +24 -24
  48. package/src/observability/edit-diagnostics.mjs +449 -0
  49. package/src/observability/index.mjs +42 -42
  50. package/src/observability/metrics.mjs +165 -137
  51. package/src/observability/tracer.mjs +137 -137
  52. package/src/onboarding.mjs +209 -0
  53. package/src/orchestration/background-manager.mjs +567 -372
  54. package/src/orchestration/background-worker.mjs +419 -305
  55. package/src/orchestration/interruption-reason.mjs +21 -0
  56. package/src/orchestration/longagent-manager.mjs +197 -171
  57. package/src/orchestration/stage-scheduler.mjs +733 -728
  58. package/src/orchestration/subagent-router.mjs +7 -1
  59. package/src/orchestration/task-scheduler.mjs +219 -7
  60. package/src/permission/engine.mjs +1 -1
  61. package/src/permission/exec-policy.mjs +370 -370
  62. package/src/permission/file-edit-policy.mjs +108 -0
  63. package/src/permission/prompt.mjs +1 -1
  64. package/src/permission/rules.mjs +116 -7
  65. package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
  66. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
  67. package/src/plugin/hook-bus.mjs +19 -5
  68. package/src/plugin/manifest-loader.mjs +222 -0
  69. package/src/provider/anthropic.mjs +396 -390
  70. package/src/provider/ollama.mjs +7 -1
  71. package/src/provider/openai.mjs +382 -340
  72. package/src/provider/retry-policy.mjs +74 -68
  73. package/src/provider/router.mjs +242 -241
  74. package/src/provider/sse.mjs +104 -104
  75. package/src/provider/wizard.mjs +556 -0
  76. package/src/repl/capability-facade.mjs +30 -0
  77. package/src/repl/command-surface.mjs +23 -0
  78. package/src/repl/controller-entry.mjs +40 -0
  79. package/src/repl/core-shell.mjs +208 -0
  80. package/src/repl/dialog-router.mjs +87 -0
  81. package/src/repl/input-engine.mjs +76 -0
  82. package/src/repl/keymap.mjs +7 -0
  83. package/src/repl/operator-surface.mjs +15 -0
  84. package/src/repl/permission-flow.mjs +49 -0
  85. package/src/repl/runtime-facade.mjs +36 -0
  86. package/src/repl/slash-router.mjs +62 -0
  87. package/src/repl/state-store.mjs +29 -0
  88. package/src/repl/turn-controller.mjs +58 -0
  89. package/src/repl/verification.mjs +23 -0
  90. package/src/repl.mjs +3368 -2929
  91. package/src/rules/load-rules.mjs +3 -3
  92. package/src/runtime.mjs +1 -1
  93. package/src/session/agent-transaction.mjs +86 -0
  94. package/src/session/checkpoint.mjs +302 -302
  95. package/src/session/compaction.mjs +36 -14
  96. package/src/session/engine.mjs +417 -227
  97. package/src/session/longagent-4stage.mjs +467 -460
  98. package/src/session/longagent-hybrid.mjs +1344 -1081
  99. package/src/session/longagent-plan.mjs +376 -365
  100. package/src/session/longagent-project-memory.mjs +53 -53
  101. package/src/session/longagent-scaffold.mjs +291 -291
  102. package/src/session/longagent-task-bus.mjs +138 -54
  103. package/src/session/longagent-utils.mjs +828 -472
  104. package/src/session/longagent.mjs +911 -884
  105. package/src/session/loop.mjs +1005 -905
  106. package/src/session/prompt/agent.txt +25 -0
  107. package/src/session/prompt/anthropic.txt +150 -150
  108. package/src/session/prompt/beast.txt +1 -1
  109. package/src/session/prompt/plan.txt +28 -6
  110. package/src/session/prompt/qwen.txt +46 -46
  111. package/src/session/recovery.mjs +21 -0
  112. package/src/session/rollback.mjs +197 -0
  113. package/src/session/routing-observability.mjs +72 -0
  114. package/src/session/runtime-state.mjs +47 -0
  115. package/src/session/store.mjs +523 -510
  116. package/src/session/system-prompt.mjs +56 -8
  117. package/src/session/task-validator.mjs +267 -267
  118. package/src/session/usability-gates.mjs +2 -2
  119. package/src/skill/builtin/commit.mjs +64 -64
  120. package/src/skill/builtin/design.mjs +76 -76
  121. package/src/skill/generator.mjs +18 -2
  122. package/src/skill/registry.mjs +642 -390
  123. package/src/storage/audit-store.mjs +18 -11
  124. package/src/storage/event-log.mjs +7 -1
  125. package/src/storage/ghost-commit-store.mjs +243 -245
  126. package/src/storage/paths.mjs +13 -0
  127. package/src/theme/default-theme.mjs +1 -1
  128. package/src/theme/markdown.mjs +4 -0
  129. package/src/theme/schema.mjs +1 -1
  130. package/src/theme/status-bar.mjs +162 -158
  131. package/src/tool/audit-wrapper.mjs +18 -2
  132. package/src/tool/edit-transaction.mjs +23 -0
  133. package/src/tool/executor.mjs +26 -1
  134. package/src/tool/file-read-state.mjs +65 -0
  135. package/src/tool/git-auto.mjs +526 -526
  136. package/src/tool/git-full-auto.mjs +487 -478
  137. package/src/tool/mutation-guard.mjs +54 -0
  138. package/src/tool/prompt/edit.txt +3 -3
  139. package/src/tool/prompt/multiedit.txt +1 -0
  140. package/src/tool/prompt/notebookedit.txt +2 -1
  141. package/src/tool/prompt/patch.txt +25 -24
  142. package/src/tool/prompt/read.txt +3 -3
  143. package/src/tool/prompt/sysinfo.txt +29 -0
  144. package/src/tool/prompt/task.txt +66 -4
  145. package/src/tool/prompt/write.txt +2 -2
  146. package/src/tool/question-prompt.mjs +17 -4
  147. package/src/tool/registry.mjs +1701 -1343
  148. package/src/tool/task-tool.mjs +14 -6
  149. package/src/ui/activity-renderer.mjs +667 -664
  150. package/src/ui/repl-background-panel.mjs +7 -0
  151. package/src/ui/repl-capability-panel.mjs +9 -0
  152. package/src/ui/repl-dashboard.mjs +54 -4
  153. package/src/ui/repl-help.mjs +110 -0
  154. package/src/ui/repl-operator-panel.mjs +12 -0
  155. package/src/ui/repl-route-feedback.mjs +35 -0
  156. package/src/ui/repl-status-view.mjs +76 -0
  157. package/src/ui/repl-task-panel.mjs +5 -0
  158. package/src/ui/repl-transcript-panel.mjs +56 -0
  159. package/src/ui/repl-turn-summary.mjs +135 -0
  160. package/src/usage/pricing.mjs +122 -121
  161. package/src/usage/usage-meter.mjs +1 -0
  162. package/src/util/git.mjs +562 -519
  163. package/src/util/template.mjs +6 -1
@@ -0,0 +1,7 @@
1
+ export function renderBackgroundSummaryPanel(backgroundSummary) {
2
+ if (!backgroundSummary) return []
3
+ return [
4
+ `background=${backgroundSummary.active} active (pending:${backgroundSummary.counts.pending}, running:${backgroundSummary.counts.running})`,
5
+ `background.terminal=completed:${backgroundSummary.counts.completed} interrupted:${backgroundSummary.counts.interrupted} error:${backgroundSummary.counts.error}`
6
+ ]
7
+ }
@@ -0,0 +1,9 @@
1
+ export function renderCapabilityPanel(snapshot) {
2
+ if (!snapshot) return []
3
+ return [
4
+ "capability surface:",
5
+ ` mode=${snapshot.mode}`,
6
+ ` commands=${snapshot.customCommands} skills=${snapshot.skills} tools=${snapshot.tools}`,
7
+ ` mcp=${snapshot.healthyMcp}/${snapshot.mcpServers} healthy agents=${snapshot.agents}`
8
+ ]
9
+ }
@@ -1,4 +1,5 @@
1
1
  import { homedir } from "node:os"
2
+ import { resolve } from "node:path"
2
3
  import { paint } from "../theme/color.mjs"
3
4
 
4
5
  function stripAnsi(text) {
@@ -101,11 +102,50 @@ function centerLine(text, width) {
101
102
  function shortenPath(path) {
102
103
  const home = homedir()
103
104
  if (!path) return ""
104
- const replaced = path.startsWith(home) ? `~${path.slice(home.length)}` : path
105
+ const normalizedPath = resolve(path).replace(/\\/g, "/")
106
+ const homeNorm = home ? resolve(home).replace(/\\/g, "/") : ""
107
+ const replaced = (home && normalizedPath.startsWith(`${homeNorm}/`)) || normalizedPath === homeNorm
108
+ ? `~${normalizedPath.slice(homeNorm.length)}`
109
+ : path
105
110
  if (replaced.length <= 72) return replaced
106
111
  return `...${replaced.slice(-69)}`
107
112
  }
108
113
 
114
+ function formatMcpStatus(summary) {
115
+ if (!summary) return []
116
+ const lines = [
117
+ `MCP: configured ${summary.configured}, healthy ${summary.healthy}, tools ${summary.tools || 0}`
118
+ ]
119
+ if (!summary.configured) {
120
+ lines.push(" quickstart: kkcode mcp init --project --with-skills")
121
+ return lines
122
+ }
123
+ for (const entry of summary.entries || []) {
124
+ const byServer = summary.byServer?.[entry.name]
125
+ const toolCount = byServer !== undefined ? ` (${byServer} tools)` : ""
126
+ lines.push(` ${entry.ok ? "✓" : "×"} ${entry.name}${toolCount}`)
127
+ }
128
+ return lines
129
+ }
130
+
131
+ function formatSkillStatus(summary) {
132
+ if (!summary) return []
133
+ const mdTotal = summary.template + summary.skillMd
134
+ const lines = [
135
+ `Skills: ${summary.total} loaded`,
136
+ ` markdown: ${mdTotal} (templates: ${summary.template}, dirs: ${summary.skillMd})`,
137
+ ` mcp prompts: ${summary.mcpPrompt}`,
138
+ ` programmable: ${summary.programmable}`
139
+ ]
140
+ if (summary.total === 0) {
141
+ lines.push(" quickstart: kkcode skill init --project")
142
+ }
143
+ if (summary.total > 0) {
144
+ lines.push(" try: /code-review /test-plan")
145
+ }
146
+ return lines
147
+ }
148
+
109
149
  function renderTag(theme, label, fg = "#0b0b0b", bg = theme.base.accent) {
110
150
  return paint(` ${label} `, fg, { bg, bold: true })
111
151
  }
@@ -179,6 +219,8 @@ export function renderReplDashboard({
179
219
  providers,
180
220
  recentSessions,
181
221
  customCommandCount,
222
+ mcpSummary = null,
223
+ skillSummary = null,
182
224
  cwd,
183
225
  columns = null
184
226
  }) {
@@ -204,7 +246,9 @@ export function renderReplDashboard({
204
246
  `Mode: ${state.mode}`,
205
247
  `Provider: ${state.providerType}`,
206
248
  `Model: ${state.model}`,
207
- `Custom commands: ${customCommandCount}`
249
+ `Custom commands: ${customCommandCount}`,
250
+ ...formatMcpStatus(mcpSummary),
251
+ ...formatSkillStatus(skillSummary)
208
252
  ]
209
253
  },
210
254
  {
@@ -226,6 +270,12 @@ export function renderReplDashboard({
226
270
  title: "Tips",
227
271
  color: theme.semantic.warn,
228
272
  items: [
273
+ ...[
274
+ "Commands for bootstrap:",
275
+ " kkcode mcp discover",
276
+ " kkcode mcp init --project",
277
+ " kkcode skill init"
278
+ ],
229
279
  "Use /dash to redraw this panel",
230
280
  "Use /clear to clear screen",
231
281
  "Use /model <id> to override model",
@@ -239,7 +289,7 @@ export function renderReplDashboard({
239
289
  },
240
290
  {
241
291
  title: "Providers",
242
- color: theme.modes.ask,
292
+ color: theme.modes.assistant,
243
293
  items: [providers.length ? providers.join(" | ") : "(none configured)"]
244
294
  },
245
295
  {
@@ -247,7 +297,7 @@ export function renderReplDashboard({
247
297
  color: theme.modes.longagent,
248
298
  items: [
249
299
  "/history /resume /commands /reload",
250
- "/ask /plan /agent /longagent"
300
+ "/assistant /plan /agent /longagent"
251
301
  ]
252
302
  }
253
303
  ]
@@ -0,0 +1,110 @@
1
+ function padRight(text, width) {
2
+ const value = String(text || "")
3
+ return value.length >= width ? value : value + " ".repeat(width - value.length)
4
+ }
5
+
6
+ export function buildHelpText({ providers = [], userRootPath = "" } = {}) {
7
+ const W = 30
8
+ const row = (cmd, desc) => ` ${padRight(cmd, W)} ${desc}`
9
+ const lines = ["", "Commands:"]
10
+
11
+ lines.push("")
12
+ lines.push(" Session")
13
+ lines.push(row("/new,/n", "start a new session"))
14
+ lines.push(row("/resume [id],/r [id]", "resume a previous session"))
15
+ lines.push(row("/history", "list recent sessions"))
16
+ lines.push(row("/session,/s", "print current session id"))
17
+ lines.push(row("/compact", "summarize conversation to free context"))
18
+ lines.push(row("/undo", "undo last code changes"))
19
+
20
+ lines.push("")
21
+ lines.push(" Mode & Provider")
22
+ lines.push(row("/assistant /plan /agent /code /longagent", "quick mode switch to the public execution lanes"))
23
+ lines.push(row("/mode <name>,/m <name>", "switch mode (assistant|plan|agent|code|coding|longagent)"))
24
+ lines.push(row("/longagent 4stage|hybrid", "switch longagent impl"))
25
+ lines.push(row("/provider <type>,/p <type>", `switch provider (${providers.join("|") || "configured"})`))
26
+ lines.push(row("/provider edit <name>", "edit existing provider config"))
27
+ lines.push(row("/model <id>", "set active model"))
28
+ lines.push(row("", "assistant = personal CLI + Q&A lane · plan = spec only · agent/code = coding · longagent = staged"))
29
+
30
+ lines.push("")
31
+ lines.push(" Profile & Workspace")
32
+ lines.push(row("/profile", "view or edit your user profile"))
33
+ lines.push(row("/like", "show welcome screen / re-run onboarding"))
34
+ lines.push(row("/trust", "trust this workspace"))
35
+ lines.push(row("/untrust", "revoke workspace trust"))
36
+
37
+ lines.push("")
38
+ lines.push(" Tools & Display")
39
+ lines.push(row("/permission [...]", "adjust permission mode/policy"))
40
+ lines.push(row("/paste [text]", "paste clipboard image"))
41
+ lines.push(row("/status", "show current runtime state"))
42
+ lines.push(row("/dash,/home", "redraw dashboard"))
43
+ lines.push(row("/clear,/cls", "clear terminal"))
44
+ lines.push(row("/keys,/k", "show key map"))
45
+
46
+ lines.push("")
47
+ lines.push(" Custom Extensions")
48
+ lines.push(row("/commands", "list custom slash commands"))
49
+ lines.push(row("/create-skill <desc>", "generate a new skill via AI"))
50
+ lines.push(row("/create-agent <desc>", "generate a new sub-agent via AI"))
51
+ lines.push(row("/reload", "reload commands, skills, agents"))
52
+
53
+ lines.push("")
54
+ lines.push(row("/help,/h,/?", "show this help"))
55
+ lines.push(row("/exit,/quit,/q", "quit"))
56
+
57
+ lines.push("")
58
+ lines.push("Configuration:")
59
+ lines.push(` Global config ${userRootPath}/config.yaml`)
60
+ lines.push(" Project config kkcode.config.yaml / .kkcode/config.yaml")
61
+ lines.push(" Custom commands .kkcode/commands/ (project-level slash commands)")
62
+ lines.push(` Custom skills ${userRootPath}/skills/ or .kkcode/skills/`)
63
+ lines.push(` Custom agents ${userRootPath}/agents/ or .kkcode/agents/`)
64
+ lines.push(" Custom tools .kkcode/tools/ (project-level tool definitions)")
65
+ lines.push(" Hooks .kkcode/hooks/ (project-level hook scripts)")
66
+ lines.push(" Plugin packages .kkcode-plugin/ or .kkcode/plugins/<name>/")
67
+ lines.push(" Rules .kkcode/rules/ (project-level prompt rules)")
68
+ lines.push(" Instructions .kkcode/instructions.md or KKCODE.md")
69
+ lines.push(" MCP servers config.* -> mcp.servers")
70
+ lines.push("")
71
+ lines.push("Key config settings:")
72
+ lines.push(" provider.default default provider name")
73
+ lines.push(" provider.<name>.api_key_env env var for API key")
74
+ lines.push(" provider.<name>.default_model default model id")
75
+ lines.push(" agent.default_mode startup mode (assistant|plan|agent|code|longagent)")
76
+ lines.push(" agent.longagent.git.enabled git branch mgmt (true|false|\"ask\")")
77
+ lines.push(" permission.mode tool approvals (auto|manual|yolo)")
78
+ lines.push(" permission.default_policy manual policy (ask|allow|deny)")
79
+ lines.push(" usage.budget.session_usd per-session cost limit")
80
+ lines.push("")
81
+ lines.push("See notice.md in project root for full configuration guide.")
82
+ return lines.join("\n")
83
+ }
84
+
85
+ export function buildShortcutLegend() {
86
+ return [
87
+ "",
88
+ "Shortcut Map:",
89
+ " /h Help",
90
+ " /n New session",
91
+ " /r Resume latest session",
92
+ " /m Switch mode",
93
+ " /p Switch provider",
94
+ " /k Show this key map",
95
+ " /permission [show|auto|yolo|ask|allow|deny|non-tty <allow_once|deny>|save [project|user]|session-clear]",
96
+ " /dash Redraw dashboard",
97
+ " /clear Clear screen",
98
+ " /assistant /plan /agent /code /longagent Quick lane switch",
99
+ "",
100
+ "TUI keys:",
101
+ " Enter choose slash suggestion / submit prompt",
102
+ " Ctrl+J insert newline (Shift+Enter if terminal supports)",
103
+ " /paste paste image from clipboard (Ctrl+V if terminal supports)",
104
+ " Up/Down navigate suggestion/history",
105
+ " Left/Right/Home/End edit cursor",
106
+ " Ctrl+Up/Down scroll log Ctrl+Home/End oldest/latest",
107
+ " Tab cycle lane (assistant -> agent -> longagent -> plan)",
108
+ " Esc interrupt turn Ctrl+C×2 exit"
109
+ ].join("\n")
110
+ }
@@ -0,0 +1,12 @@
1
+ export function renderOperatorPanel(snapshot) {
2
+ if (!snapshot) return []
3
+ const lines = [
4
+ "operator surface:",
5
+ ` recoverable=${snapshot.recoverableCount}`,
6
+ ` background.active=${snapshot.activeBackground}`
7
+ ]
8
+ for (const action of snapshot.actions || []) {
9
+ lines.push(` next: ${action}`)
10
+ }
11
+ return lines
12
+ }
@@ -0,0 +1,35 @@
1
+ function formatModeLabel(mode) {
2
+ if (mode === "assistant") return "assistant(个人助手)"
3
+ if (mode === "agent") return "agent/code(编码)"
4
+ return String(mode || "assistant")
5
+ }
6
+
7
+ export function buildRouteFeedback({ route, currentMode, routeSummary = "" } = {}) {
8
+ if (!route) {
9
+ return {
10
+ changedMessage: null,
11
+ forcedMessage: null,
12
+ suggestionMessage: null,
13
+ stayedMessage: null,
14
+ summaryMessage: null
15
+ }
16
+ }
17
+
18
+ const routeExplanation = route.explanation || route.reason || ""
19
+
20
+ return {
21
+ changedMessage: route.changed
22
+ ? `⟳ 自动切换到 ${formatModeLabel(route.mode)} 模式(${routeExplanation})`
23
+ : null,
24
+ forcedMessage: route.forced && route.suggestion
25
+ ? `⚠ 这看起来是个简单任务(${routeExplanation}),建议用 ${route.suggestion} 模式。输入 y 继续用 longagent,或 n 切换到 ${route.suggestion}。`
26
+ : null,
27
+ suggestionMessage: route.suggestion === "longagent" && (currentMode === "assistant" || currentMode === "agent")
28
+ ? `💡 这看起来是个复杂任务(${routeExplanation}),可以用 /longagent 切换到 longagent 模式获得更好效果。`
29
+ : null,
30
+ stayedMessage: !route.changed && !route.suggestion && !(route.forced && route.suggestion) && route.reason !== "low_confidence"
31
+ ? `↳ 保持 ${formatModeLabel(currentMode)} 模式(${routeExplanation})`
32
+ : null,
33
+ summaryMessage: routeSummary ? ` ${routeSummary}` : null
34
+ }
35
+ }
@@ -0,0 +1,76 @@
1
+ import { renderStatusBar } from "../theme/status-bar.mjs"
2
+ import { renderReplDashboard, renderReplLogo, renderStartupHint } from "./repl-dashboard.mjs"
3
+ import { formatRuntimeStateText } from "./repl-turn-summary.mjs"
4
+ import { renderOperatorPanel } from "./repl-operator-panel.mjs"
5
+
6
+ export function renderReplStatusLine({
7
+ state,
8
+ configState,
9
+ theme,
10
+ tokenMeter,
11
+ cost,
12
+ costSavings = 0,
13
+ contextMeter = null,
14
+ longagentState = null
15
+ }) {
16
+ return renderStatusBar({
17
+ mode: state.mode,
18
+ model: state.model,
19
+ permission: configState.config.permission.mode || configState.config.permission.default_policy,
20
+ tokenMeter,
21
+ aggregation: configState.config.usage.aggregation,
22
+ cost,
23
+ savings: costSavings,
24
+ contextMeter,
25
+ showCost: configState.config.ui.status.show_cost,
26
+ showTokenMeter: configState.config.ui.status.show_token_meter,
27
+ theme,
28
+ layout: configState.config.ui.layout,
29
+ longagentState: state.mode === "longagent" ? longagentState : null,
30
+ memoryLoaded: state.memoryLoaded
31
+ })
32
+ }
33
+
34
+ export function renderRuntimeDashboardView({
35
+ theme,
36
+ state,
37
+ providers,
38
+ recentSessions,
39
+ mcpSummary,
40
+ skillSummary,
41
+ backgroundSummary,
42
+ runtimeSummary,
43
+ operatorSnapshot = null,
44
+ customCommandCount,
45
+ cwd,
46
+ columns = null
47
+ }) {
48
+ return [
49
+ renderReplDashboard({
50
+ theme,
51
+ state,
52
+ providers,
53
+ recentSessions,
54
+ mcpSummary,
55
+ skillSummary,
56
+ backgroundSummary,
57
+ customCommandCount,
58
+ cwd,
59
+ columns
60
+ }),
61
+ "",
62
+ formatRuntimeStateText(state, mcpSummary, skillSummary, backgroundSummary, runtimeSummary),
63
+ ...(operatorSnapshot ? ["", ...renderOperatorPanel(operatorSnapshot)] : [])
64
+ ].join("\n")
65
+ }
66
+
67
+ export function renderStartupScreen({ theme, recentSessions, columns = null }) {
68
+ const logo = renderReplLogo({ theme, columns })
69
+ const hint = renderStartupHint(recentSessions)
70
+ return hint ? `${logo}\n${hint}\n` : logo
71
+ }
72
+
73
+ export function renderFrameDashboardHeader({ showDashboard, theme, columns = null }) {
74
+ if (!showDashboard) return []
75
+ return renderReplLogo({ theme, columns }).split("\n")
76
+ }
@@ -0,0 +1,5 @@
1
+ import { formatPlanProgress } from "./activity-renderer.mjs"
2
+
3
+ export function renderTaskProgressPanel(taskProgress, formatter = formatPlanProgress) {
4
+ return formatter(taskProgress)
5
+ }
@@ -0,0 +1,56 @@
1
+ export function buildTranscriptViewport({
2
+ logs = [],
3
+ width,
4
+ logRows,
5
+ scrollOffset,
6
+ wrapLogLines,
7
+ clipAnsiLine,
8
+ paint,
9
+ theme
10
+ }) {
11
+ const wrappedAllLogs = wrapLogLines(logs, width)
12
+ const maxOffset = Math.max(0, wrappedAllLogs.length - logRows)
13
+ const clampedOffset = Math.max(0, Math.min(maxOffset, scrollOffset))
14
+ const end = Math.max(0, wrappedAllLogs.length - clampedOffset)
15
+ const start = Math.max(0, end - logRows)
16
+ const wrappedLogs = wrappedAllLogs.slice(start, end)
17
+ const scrollMeta = {
18
+ logRows,
19
+ totalRows: wrappedAllLogs.length,
20
+ maxOffset
21
+ }
22
+
23
+ const scrollHint = clampedOffset > 0
24
+ ? paint(` Ctrl+Up/Down scroll | +${clampedOffset} lines`, theme.semantic.warn)
25
+ : paint(" Ctrl+Up/Down scroll | Ctrl+Home oldest | Ctrl+End latest", theme.base.muted, { dim: true })
26
+
27
+ const totalLog = wrappedAllLogs.length
28
+ const showScrollbar = totalLog > logRows
29
+ let thumbStart = 0
30
+ let thumbEnd = 0
31
+ if (showScrollbar) {
32
+ thumbStart = Math.floor((start / totalLog) * logRows)
33
+ thumbEnd = Math.min(logRows, thumbStart + Math.max(1, Math.round((logRows / totalLog) * logRows)))
34
+ }
35
+
36
+ const lines = []
37
+ for (let i = 0; i < logRows; i++) {
38
+ const content = wrappedLogs[i] || ""
39
+ if (showScrollbar) {
40
+ const bar = i >= thumbStart && i < thumbEnd
41
+ ? paint("┃", theme.semantic.warn)
42
+ : paint("│", theme.base.border, { dim: true })
43
+ lines.push(clipAnsiLine(content, width - 2) + " " + bar)
44
+ } else {
45
+ lines.push(clipAnsiLine(content, width))
46
+ }
47
+ }
48
+
49
+ return {
50
+ lines,
51
+ scrollHint,
52
+ scrollMeta,
53
+ scrollOffset: clampedOffset,
54
+ wrappedLogs
55
+ }
56
+ }
@@ -0,0 +1,135 @@
1
+ import { paint } from "../theme/color.mjs"
2
+ import { renderBackgroundSummaryPanel } from "./repl-background-panel.mjs"
3
+
4
+ export function formatRuntimeStateText(state, mcpSummary = null, skillSummary = null, backgroundSummary = null, runtimeSummary = null) {
5
+ const lines = [
6
+ `session=${state.sessionId}`,
7
+ `mode=${state.mode}`,
8
+ `provider=${state.providerType}`,
9
+ `model=${state.model}`
10
+ ]
11
+ if (state.mode === "longagent" && state.longagentImpl) {
12
+ lines.push(`longagent.impl=${state.longagentImpl}`)
13
+ }
14
+ if (mcpSummary) {
15
+ lines.push(`mcp=${mcpSummary.healthy}/${mcpSummary.configured} healthy, ${mcpSummary.tools} tools`)
16
+ if (mcpSummary.configured === 0) {
17
+ lines.push("mcp.quickstart=kkcode mcp init --project")
18
+ }
19
+ }
20
+ if (skillSummary) {
21
+ const mdCount = skillSummary.template + skillSummary.skillMd
22
+ lines.push(`skills=${skillSummary.total} loaded (md:${mdCount}, mcp:${skillSummary.mcpPrompt}, mjs:${skillSummary.programmable})`)
23
+ if (skillSummary.total === 0) {
24
+ lines.push("skills.quickstart=kkcode skill init --project")
25
+ }
26
+ }
27
+ if (backgroundSummary) {
28
+ lines.push(...renderBackgroundSummaryPanel(backgroundSummary))
29
+ }
30
+ if (runtimeSummary) {
31
+ lines.push(`session.messages=${runtimeSummary.messageCount}`)
32
+ lines.push(`session.parts=${runtimeSummary.partCount}`)
33
+ lines.push(`session.recoverable=${runtimeSummary.recoverableCount}`)
34
+ if (runtimeSummary.audit) {
35
+ lines.push(`audit.total=${runtimeSummary.audit.total} error=${runtimeSummary.audit.errorCount}`)
36
+ }
37
+ }
38
+ return lines.join("\n")
39
+ }
40
+
41
+ export function normalizeFileChanges(toolEvents = []) {
42
+ const rows = []
43
+ for (const event of toolEvents || []) {
44
+ if (!event || !["write", "edit"].includes(event.name)) continue
45
+ const changes = Array.isArray(event?.metadata?.fileChanges) ? event.metadata.fileChanges : []
46
+ for (const item of changes) {
47
+ const path = String(item?.path || event.args?.path || "").trim()
48
+ if (!path) continue
49
+ rows.push({
50
+ path,
51
+ addedLines: Number(item?.addedLines || 0),
52
+ removedLines: Number(item?.removedLines || 0),
53
+ stageId: item?.stageId ? String(item.stageId) : "",
54
+ taskId: item?.taskId ? String(item.taskId) : ""
55
+ })
56
+ }
57
+ }
58
+
59
+ const grouped = new Map()
60
+ for (const row of rows) {
61
+ const key = `${row.path}::${row.stageId}::${row.taskId}`
62
+ const prev = grouped.get(key) || {
63
+ path: row.path,
64
+ addedLines: 0,
65
+ removedLines: 0,
66
+ stageId: row.stageId,
67
+ taskId: row.taskId
68
+ }
69
+ prev.addedLines += row.addedLines
70
+ prev.removedLines += row.removedLines
71
+ grouped.set(key, prev)
72
+ }
73
+ return [...grouped.values()]
74
+ }
75
+
76
+ export function renderFileChangeLines(fileChanges = [], limit = 20) {
77
+ const lines = []
78
+ const rows = fileChanges.slice(0, limit)
79
+ for (const item of rows) {
80
+ const scope = [item.stageId, item.taskId].filter(Boolean).join("/")
81
+ const suffix = scope ? paint(` (${scope})`, null, { dim: true }) : ""
82
+ const add = item.addedLines > 0
83
+ ? paint(`+${item.addedLines}`, "#00ff00", { bold: true })
84
+ : paint("+0", null, { dim: true })
85
+ const del = item.removedLines > 0
86
+ ? paint(`-${item.removedLines}`, "#ff4444", { bold: true })
87
+ : paint("-0", null, { dim: true })
88
+ lines.push(` ${paint(item.path, "white")} ${add} ${del}${suffix}`)
89
+ }
90
+ if (fileChanges.length > rows.length) {
91
+ lines.push(paint(` ... +${fileChanges.length - rows.length} more file(s)`, null, { dim: true }))
92
+ }
93
+ return lines
94
+ }
95
+
96
+ export function normalizeDiagnostics(toolEvents = []) {
97
+ const summaries = []
98
+ for (const event of toolEvents || []) {
99
+ const diagnostics = event?.metadata?.diagnostics
100
+ if (!diagnostics) continue
101
+ const summary = diagnostics.summary || {}
102
+ const currentSummary = diagnostics.after?.summary || diagnostics.current?.summary || {}
103
+ const delta = diagnostics.delta || {}
104
+ summaries.push({
105
+ tool: String(event?.name || ""),
106
+ path: String(event?.args?.path || diagnostics.current?.diagnostics?.[0]?.file || diagnostics.baseline?.diagnostics?.[0]?.file || "").trim(),
107
+ introduced: Number(summary.introduced || delta.added?.length || 0),
108
+ persistent: Number(summary.persistent || delta.persisted?.length || 0),
109
+ resolved: Number(summary.resolved || delta.resolved?.length || 0),
110
+ unchanged: Boolean(summary.unchanged || delta.unchanged),
111
+ errorCount: Number(currentSummary.errorCount || currentSummary.errors || 0),
112
+ warningCount: Number(currentSummary.warningCount || currentSummary.warnings || 0),
113
+ status: String(summary.status || diagnostics.after?.status || diagnostics.current?.status || diagnostics.status || "")
114
+ })
115
+ }
116
+ return summaries
117
+ }
118
+
119
+ export function renderDiagnosticsLines(rows = [], limit = 10) {
120
+ const lines = []
121
+ const visible = rows.slice(0, limit)
122
+ for (const item of visible) {
123
+ const label = item.path || item.tool || "diagnostics"
124
+ const summary = item.unchanged
125
+ ? "no change"
126
+ : `+${item.introduced} / ${item.persistent} / -${item.resolved}`
127
+ const totals = `${item.errorCount} error(s), ${item.warningCount} warning(s)`
128
+ const status = item.status ? paint(` ${item.status}`, null, { dim: true }) : ""
129
+ lines.push(` ${paint(label, "white")} ${paint(summary, "yellow", { bold: true })} ${paint(totals, null, { dim: true })}${status}`)
130
+ }
131
+ if (rows.length > visible.length) {
132
+ lines.push(paint(` ... +${rows.length - visible.length} more diagnostics result(s)`, null, { dim: true }))
133
+ }
134
+ return lines
135
+ }