@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,158 +1,162 @@
1
- import { paint } from "./color.mjs"
2
-
3
- function formatNumber(value) {
4
- return Intl.NumberFormat("en-US").format(Math.round(value))
5
- }
6
-
7
- function formatCost(amount) {
8
- if (amount === null || amount === undefined) return "unknown"
9
- return `$${amount.toFixed(4)}`
10
- }
11
-
12
- function permissionColor(permission, theme) {
13
- switch (permission) {
14
- case "allow": return theme.semantic.success || theme.semantic.info
15
- case "deny": return theme.semantic.error || theme.semantic.warn
16
- case "ask":
17
- default:
18
- return theme.semantic.info
19
- }
20
- }
21
-
22
- function contrastText(hex, dark = "#111111", light = "#f7f7f7") {
23
- if (!/^#([A-Fa-f0-9]{6})$/.test(String(hex || ""))) return light
24
- const raw = hex.replace("#", "")
25
- const r = parseInt(raw.slice(0, 2), 16)
26
- const g = parseInt(raw.slice(2, 4), 16)
27
- const b = parseInt(raw.slice(4, 6), 16)
28
- const y = 0.2126 * r + 0.7152 * g + 0.0722 * b
29
- return y > 150 ? dark : light
30
- }
31
-
32
- function badge(text, fg, bg, options = {}) {
33
- return paint(` ${text} `, fg, { bg, bold: options.bold !== false })
34
- }
35
-
36
- function clipModel(model, maxLen) {
37
- const value = String(model || "")
38
- if (value.length <= maxLen) return value
39
- if (maxLen < 10) return value.slice(0, maxLen)
40
- return `${value.slice(0, Math.max(4, maxLen - 4))}...`
41
- }
42
-
43
- export function renderStatusBar({
44
- mode,
45
- model,
46
- permission,
47
- tokenMeter,
48
- aggregation = ["turn", "session", "global"],
49
- cost,
50
- savings = 0,
51
- showCost = true,
52
- showTokenMeter = true,
53
- contextMeter = null,
54
- theme,
55
- layout = "compact",
56
- longagentState = null,
57
- memoryLoaded = false
58
- }) {
59
- const width = Number(process.stdout.columns || 120)
60
- const dense = width < 110
61
- const tight = width < 86
62
- const modelLabel = clipModel(model, tight ? 18 : dense ? 28 : 44)
63
-
64
- const segments = []
65
- const modeBg = theme.modes[mode] || theme.base.accent
66
- segments.push(badge(mode.toUpperCase(), contrastText(modeBg), modeBg))
67
- segments.push(badge(`MODEL ${modelLabel}`, theme.base.fg, theme.components.panel || theme.base.border, { bold: false }))
68
-
69
- if (showTokenMeter && tokenMeter) {
70
- const t = tokenMeter.turn
71
- const s = tokenMeter.session
72
- const g = tokenMeter.global
73
- const tokenSegments = []
74
- if (aggregation.includes("turn")) tokenSegments.push(`T:${formatNumber(t.input + t.output)}`)
75
- if (!tight && aggregation.includes("session")) tokenSegments.push(`S:${formatNumber(s.input + s.output)}`)
76
- if (!dense && aggregation.includes("global")) tokenSegments.push(`G:${formatNumber(g.input + g.output)}`)
77
- const tokenText = `TOKENS ${tokenSegments.join(" ")}${tokenMeter.estimated ? " ~" : ""}`
78
- segments.push(
79
- badge(tokenText, theme.base.fg, "#2d3748", { bold: false })
80
- )
81
- }
82
- if (showCost) {
83
- const savingsStr = savings > 0 ? ` ↓${formatCost(savings)}` : ""
84
- segments.push(badge(`COST ${formatCost(cost)}${savingsStr}`, contrastText(theme.semantic.warn), theme.semantic.warn, { bold: false }))
85
- }
86
- if (contextMeter && Number.isFinite(contextMeter.percent)) {
87
- const pct = Math.max(0, Math.min(100, Math.round(contextMeter.percent)))
88
- const ctxBg = pct >= 85
89
- ? theme.semantic.error
90
- : pct >= 70
91
- ? theme.semantic.warn
92
- : theme.semantic.info
93
- let suffix = ""
94
- if (contextMeter.cacheRead > 0 || contextMeter.cacheWrite > 0) {
95
- const total = (contextMeter.cacheRead || 0) + (contextMeter.cacheWrite || 0) + (contextMeter.inputUncached || 0)
96
- const hitPct = total > 0 ? Math.round((contextMeter.cacheRead || 0) / total * 100) : 0
97
- suffix = ` Cache:${hitPct}%`
98
- }
99
- const text = tight ? `CTX ${pct}%` : `CONTEXT ${pct}%${suffix}`
100
- segments.push(badge(text, contrastText(ctxBg), ctxBg, { bold: false }))
101
- }
102
- if (memoryLoaded && !tight) {
103
- segments.push(badge("MEM", contrastText(theme.semantic.info), theme.semantic.info, { bold: false }))
104
- }
105
- const permBg = permissionColor(permission, theme)
106
- segments.push(badge(`PERMISSION ${permission.toUpperCase()}`, contrastText(permBg), permBg, { bold: false }))
107
- if (longagentState && mode === "longagent") {
108
- const parts = []
109
- if (longagentState.currentStageId) {
110
- parts.push(`STG:${longagentState.currentStageId}`)
111
- } else if (Number.isFinite(longagentState.stageIndex) && Number.isFinite(longagentState.stageCount) && longagentState.stageCount > 0) {
112
- parts.push(`STG:${longagentState.stageIndex + 1}/${longagentState.stageCount}`)
113
- }
114
- if (longagentState.stageProgress?.total) {
115
- parts.push(`TSK:${longagentState.stageProgress.done || 0}/${longagentState.stageProgress.total}`)
116
- }
117
- if (Number.isFinite(longagentState.remainingFilesCount)) {
118
- parts.push(`REM:${longagentState.remainingFilesCount}`)
119
- }
120
- if (longagentState.phase) {
121
- parts.push(`P:${longagentState.phase}`)
122
- }
123
- if (longagentState.currentGate) {
124
- parts.push(`G:${longagentState.currentGate}`)
125
- }
126
- if (longagentState.iterations !== undefined) {
127
- const iter = longagentState.maxIterations
128
- ? `${longagentState.iterations}/${longagentState.maxIterations}`
129
- : String(longagentState.iterations)
130
- parts.push(`I:${iter}`)
131
- }
132
- if (!tight && longagentState.progress?.percentage !== null && longagentState.progress?.percentage !== undefined) {
133
- const pct = longagentState.progress.percentage
134
- const barW = dense ? 8 : 14
135
- const filled = Math.round(barW * pct / 100)
136
- parts.push(`${"█".repeat(filled)}${"░".repeat(barW - filled)} ${pct}%`)
137
- }
138
- if (!dense && longagentState.elapsed !== undefined) {
139
- const m = Math.floor(longagentState.elapsed / 60)
140
- const s = longagentState.elapsed % 60
141
- parts.push(`${m}m${s}s`)
142
- }
143
- if (!tight && Array.isArray(longagentState.lastGateFailures) && longagentState.lastGateFailures.length) {
144
- parts.push(`Fail`)
145
- }
146
- if (!tight && typeof longagentState.recoveryCount === "number" && longagentState.recoveryCount > 0) {
147
- parts.push(`R:${longagentState.recoveryCount}`)
148
- }
149
- if (parts.length) {
150
- segments.push(badge(`LONG ${parts.join(" ")}`, contrastText(theme.semantic.success), theme.semantic.success, { bold: false }))
151
- }
152
- }
153
-
154
- if (layout === "comfortable") {
155
- return segments.join(" ")
156
- }
157
- return segments.join(" ")
158
- }
1
+ import { paint } from "./color.mjs"
2
+
3
+ function formatNumber(value) {
4
+ return Intl.NumberFormat("en-US").format(Math.round(value))
5
+ }
6
+
7
+ function formatCost(amount) {
8
+ if (amount === null || amount === undefined) return "unknown"
9
+ return `$${amount.toFixed(4)}`
10
+ }
11
+
12
+ function permissionColor(permission, theme) {
13
+ switch (permission) {
14
+ case "allow":
15
+ case "yolo":
16
+ return theme.semantic.success || theme.semantic.info
17
+ case "deny": return theme.semantic.error || theme.semantic.warn
18
+ case "auto": return theme.semantic.warn || theme.semantic.info
19
+ case "manual":
20
+ case "ask":
21
+ default:
22
+ return theme.semantic.info
23
+ }
24
+ }
25
+
26
+ function contrastText(hex, dark = "#111111", light = "#f7f7f7") {
27
+ if (!/^#([A-Fa-f0-9]{6})$/.test(String(hex || ""))) return light
28
+ const raw = hex.replace("#", "")
29
+ const r = parseInt(raw.slice(0, 2), 16)
30
+ const g = parseInt(raw.slice(2, 4), 16)
31
+ const b = parseInt(raw.slice(4, 6), 16)
32
+ const y = 0.2126 * r + 0.7152 * g + 0.0722 * b
33
+ return y > 150 ? dark : light
34
+ }
35
+
36
+ function badge(text, fg, bg, options = {}) {
37
+ return paint(` ${text} `, fg, { bg, bold: options.bold !== false })
38
+ }
39
+
40
+ function clipModel(model, maxLen) {
41
+ const value = String(model || "")
42
+ if (value.length <= maxLen) return value
43
+ if (maxLen < 10) return value.slice(0, maxLen)
44
+ return `${value.slice(0, Math.max(4, maxLen - 4))}...`
45
+ }
46
+
47
+ export function renderStatusBar({
48
+ mode,
49
+ model,
50
+ permission,
51
+ tokenMeter,
52
+ aggregation = ["turn", "session", "global"],
53
+ cost,
54
+ savings = 0,
55
+ showCost = true,
56
+ showTokenMeter = true,
57
+ contextMeter = null,
58
+ theme,
59
+ layout = "compact",
60
+ longagentState = null,
61
+ memoryLoaded = false
62
+ }) {
63
+ const width = Number(process.stdout.columns || 120)
64
+ const dense = width < 110
65
+ const tight = width < 86
66
+ const modelLabel = clipModel(model, tight ? 18 : dense ? 28 : 44)
67
+
68
+ const segments = []
69
+ const modeBg = theme.modes[mode] || theme.base.accent
70
+ segments.push(badge(mode.toUpperCase(), contrastText(modeBg), modeBg))
71
+ segments.push(badge(`MODEL ${modelLabel}`, theme.base.fg, theme.components.panel || theme.base.border, { bold: false }))
72
+
73
+ if (showTokenMeter && tokenMeter) {
74
+ const t = tokenMeter.turn
75
+ const s = tokenMeter.session
76
+ const g = tokenMeter.global
77
+ const tokenSegments = []
78
+ if (aggregation.includes("turn")) tokenSegments.push(`T:${formatNumber(t.input + t.output)}`)
79
+ if (!tight && aggregation.includes("session")) tokenSegments.push(`S:${formatNumber(s.input + s.output)}`)
80
+ if (!dense && aggregation.includes("global")) tokenSegments.push(`G:${formatNumber(g.input + g.output)}`)
81
+ const tokenText = `TOKENS ${tokenSegments.join(" ")}${tokenMeter.estimated ? " ~" : ""}`
82
+ segments.push(
83
+ badge(tokenText, theme.base.fg, "#2d3748", { bold: false })
84
+ )
85
+ }
86
+ if (showCost) {
87
+ const savingsStr = savings > 0 ? ` ↓${formatCost(savings)}` : ""
88
+ segments.push(badge(`COST ${formatCost(cost)}${savingsStr}`, contrastText(theme.semantic.warn), theme.semantic.warn, { bold: false }))
89
+ }
90
+ if (contextMeter && Number.isFinite(contextMeter.percent)) {
91
+ const pct = Math.max(0, Math.min(100, Math.round(contextMeter.percent)))
92
+ const ctxBg = pct >= 85
93
+ ? theme.semantic.error
94
+ : pct >= 70
95
+ ? theme.semantic.warn
96
+ : theme.semantic.info
97
+ let suffix = ""
98
+ if (contextMeter.cacheRead > 0 || contextMeter.cacheWrite > 0) {
99
+ const total = (contextMeter.cacheRead || 0) + (contextMeter.cacheWrite || 0) + (contextMeter.inputUncached || 0)
100
+ const hitPct = total > 0 ? Math.round((contextMeter.cacheRead || 0) / total * 100) : 0
101
+ suffix = ` Cache:${hitPct}%`
102
+ }
103
+ const text = tight ? `CTX ${pct}%` : `CONTEXT ${pct}%${suffix}`
104
+ segments.push(badge(text, contrastText(ctxBg), ctxBg, { bold: false }))
105
+ }
106
+ if (memoryLoaded && !tight) {
107
+ segments.push(badge("MEM", contrastText(theme.semantic.info), theme.semantic.info, { bold: false }))
108
+ }
109
+ const permBg = permissionColor(permission, theme)
110
+ segments.push(badge(`PERMISSION ${permission.toUpperCase()}`, contrastText(permBg), permBg, { bold: false }))
111
+ if (longagentState && mode === "longagent") {
112
+ const parts = []
113
+ if (longagentState.currentStageId) {
114
+ parts.push(`STG:${longagentState.currentStageId}`)
115
+ } else if (Number.isFinite(longagentState.stageIndex) && Number.isFinite(longagentState.stageCount) && longagentState.stageCount > 0) {
116
+ parts.push(`STG:${longagentState.stageIndex + 1}/${longagentState.stageCount}`)
117
+ }
118
+ if (longagentState.stageProgress?.total) {
119
+ parts.push(`TSK:${longagentState.stageProgress.done || 0}/${longagentState.stageProgress.total}`)
120
+ }
121
+ if (Number.isFinite(longagentState.remainingFilesCount)) {
122
+ parts.push(`REM:${longagentState.remainingFilesCount}`)
123
+ }
124
+ if (longagentState.phase) {
125
+ parts.push(`P:${longagentState.phase}`)
126
+ }
127
+ if (longagentState.currentGate) {
128
+ parts.push(`G:${longagentState.currentGate}`)
129
+ }
130
+ if (longagentState.iterations !== undefined) {
131
+ const iter = longagentState.maxIterations
132
+ ? `${longagentState.iterations}/${longagentState.maxIterations}`
133
+ : String(longagentState.iterations)
134
+ parts.push(`I:${iter}`)
135
+ }
136
+ if (!tight && longagentState.progress?.percentage !== null && longagentState.progress?.percentage !== undefined) {
137
+ const pct = longagentState.progress.percentage
138
+ const barW = dense ? 8 : 14
139
+ const filled = Math.round(barW * pct / 100)
140
+ parts.push(`${"█".repeat(filled)}${"░".repeat(barW - filled)} ${pct}%`)
141
+ }
142
+ if (!dense && longagentState.elapsed !== undefined) {
143
+ const m = Math.floor(longagentState.elapsed / 60)
144
+ const s = longagentState.elapsed % 60
145
+ parts.push(`${m}m${s}s`)
146
+ }
147
+ if (!tight && Array.isArray(longagentState.lastGateFailures) && longagentState.lastGateFailures.length) {
148
+ parts.push(`Fail`)
149
+ }
150
+ if (!tight && typeof longagentState.recoveryCount === "number" && longagentState.recoveryCount > 0) {
151
+ parts.push(`R:${longagentState.recoveryCount}`)
152
+ }
153
+ if (parts.length) {
154
+ segments.push(badge(`LONG ${parts.join(" ")}`, contrastText(theme.semantic.success), theme.semantic.success, { bold: false }))
155
+ }
156
+ }
157
+
158
+ if (layout === "comfortable") {
159
+ return segments.join(" ")
160
+ }
161
+ return segments.join(" ")
162
+ }
@@ -1,5 +1,21 @@
1
1
  import { appendAuditEntry } from "../storage/audit-store.mjs"
2
2
 
3
+ const REDACT_KEYS = new Set(["api_key", "apiKey", "token", "password", "secret", "credential", "authorization"])
4
+ const TRUNCATE_KEYS = new Set(["content", "new_string", "old_string"])
5
+ const TRUNCATE_LIMIT = 200
6
+
7
+ function redactArgs(args) {
8
+ if (!args || typeof args !== "object") return args
9
+ const out = {}
10
+ for (const [k, v] of Object.entries(args)) {
11
+ if (REDACT_KEYS.has(k)) { out[k] = "[REDACTED]" }
12
+ else if (TRUNCATE_KEYS.has(k) && typeof v === "string" && v.length > TRUNCATE_LIMIT) {
13
+ out[k] = v.slice(0, TRUNCATE_LIMIT) + `... (${v.length} chars)`
14
+ } else { out[k] = v }
15
+ }
16
+ return out
17
+ }
18
+
3
19
  export async function withAudit({ sessionId, turnId, toolName, args, run }) {
4
20
  const startedAt = Date.now()
5
21
  await appendAuditEntry({
@@ -7,7 +23,7 @@ export async function withAudit({ sessionId, turnId, toolName, args, run }) {
7
23
  sessionId,
8
24
  turnId,
9
25
  tool: toolName,
10
- args
26
+ args: redactArgs(args)
11
27
  })
12
28
  try {
13
29
  const result = await run()
@@ -35,4 +51,4 @@ export async function withAudit({ sessionId, turnId, toolName, args, run }) {
35
51
  })
36
52
  throw error
37
53
  }
38
- }
54
+ }
@@ -9,6 +9,11 @@ function backupPath(target) {
9
9
  return `${target}.kkcode.bak`
10
10
  }
11
11
 
12
+ function linesForPatch(text) {
13
+ const value = String(text ?? "")
14
+ return value === "" ? [] : value.split("\n")
15
+ }
16
+
12
17
  /**
13
18
  * Count added/removed lines between two text snippets using LCS.
14
19
  * For snippets under 500 lines, uses O(m*n) DP. For larger texts, falls back to simple line-count diff.
@@ -51,6 +56,24 @@ export function diffLineCount(oldText, newText) {
51
56
  return { added: n - common, removed: m - common }
52
57
  }
53
58
 
59
+ export function buildStructuredPatch(oldText, newText, {
60
+ oldStart = 1,
61
+ newStart = 1
62
+ } = {}) {
63
+ const removed = linesForPatch(oldText)
64
+ const added = linesForPatch(newText)
65
+ return [{
66
+ oldStart,
67
+ oldLineCount: removed.length,
68
+ newStart,
69
+ newLineCount: added.length,
70
+ lines: [
71
+ ...removed.map((text) => ({ type: "remove", text })),
72
+ ...added.map((text) => ({ type: "add", text }))
73
+ ]
74
+ }]
75
+ }
76
+
54
77
  export async function atomicWriteFile(target, content) {
55
78
  const dir = path.dirname(target)
56
79
  await mkdir(dir, { recursive: true })
@@ -3,10 +3,34 @@ import { EventBus } from "../core/events.mjs"
3
3
  import { EVENT_TYPES } from "../core/constants.mjs"
4
4
  import { withAudit } from "./audit-wrapper.mjs"
5
5
  import { autoSnapshotBeforeEdit } from "../session/checkpoint.mjs"
6
+ import { buildMutationObservability } from "../observability/edit-diagnostics.mjs"
6
7
 
7
8
  const FILE_EDIT_TOOLS = new Set(["write", "edit", "multiedit", "patch", "notebookedit"])
8
9
  const snapshotted = new Set()
9
10
 
11
+ function eventMetadataSummary(metadata = {}) {
12
+ const fileChanges = Array.isArray(metadata.fileChanges) ? metadata.fileChanges : []
13
+ const observability = metadata.observability?.contract
14
+ ? metadata.observability
15
+ : buildMutationObservability(metadata)
16
+ const diagnostics = metadata.diagnostics?.contract
17
+ ? {
18
+ summary: metadata.diagnostics.summary || null,
19
+ currentCount: metadata.diagnostics.current?.count || 0,
20
+ delta: metadata.diagnostics.delta
21
+ ? {
22
+ added: metadata.diagnostics.delta.added?.length || 0,
23
+ resolved: metadata.diagnostics.delta.resolved?.length || 0,
24
+ persisted: metadata.diagnostics.delta.persisted?.length || 0
25
+ }
26
+ : null
27
+ }
28
+ : null
29
+
30
+ if (!fileChanges.length && !observability?.changes?.length && !diagnostics) return null
31
+ return { fileChanges, observability, diagnostics }
32
+ }
33
+
10
34
  export async function executeTool({ tool, args, sessionId, turnId, context, signal = null }) {
11
35
  return withAudit({
12
36
  sessionId,
@@ -88,7 +112,8 @@ export async function executeTool({ tool, args, sessionId, turnId, context, sign
88
112
  status: result.status,
89
113
  args,
90
114
  output: String(output || "").slice(0, 500),
91
- durationMs: result.durationMs
115
+ durationMs: result.durationMs,
116
+ metadata: eventMetadataSummary(metadata)
92
117
  }
93
118
  })
94
119
  return result
@@ -0,0 +1,65 @@
1
+ import path from "node:path"
2
+ import { readFile, stat } from "node:fs/promises"
3
+
4
+ const fileReadState = new Map()
5
+
6
+ function normalizeFilePath(filePath) {
7
+ return path.resolve(String(filePath || ""))
8
+ }
9
+
10
+ function normalizeTimestamp(timestamp) {
11
+ const value = Number(timestamp)
12
+ return Number.isFinite(value) ? Math.floor(value) : Date.now()
13
+ }
14
+
15
+ export function markFileRead(filePath, {
16
+ content = "",
17
+ timestamp = Date.now(),
18
+ offset = undefined,
19
+ limit = undefined,
20
+ isPartialView = false
21
+ } = {}) {
22
+ const normalized = normalizeFilePath(filePath)
23
+ fileReadState.set(normalized, {
24
+ content: String(content ?? ""),
25
+ timestamp: normalizeTimestamp(timestamp),
26
+ offset: Number.isInteger(offset) ? offset : undefined,
27
+ limit: Number.isInteger(limit) ? limit : undefined,
28
+ isPartialView: Boolean(isPartialView)
29
+ })
30
+ }
31
+
32
+ export function getFileReadState(filePath) {
33
+ return fileReadState.get(normalizeFilePath(filePath)) || null
34
+ }
35
+
36
+ export function wasFileRead(filePath) {
37
+ return fileReadState.has(normalizeFilePath(filePath))
38
+ }
39
+
40
+ export function clearFileReadState() {
41
+ fileReadState.clear()
42
+ }
43
+
44
+ export function extractTrackedView(content, readState) {
45
+ const text = String(content ?? "")
46
+ if (!readState?.isPartialView) return text
47
+ const startLine = Math.max(1, Number(readState.offset) || 1)
48
+ const lines = text.split("\n")
49
+ const sliceLength = Math.max(1, Number(readState.limit) || lines.length)
50
+ return lines.slice(startLine - 1, startLine - 1 + sliceLength).join("\n")
51
+ }
52
+
53
+ export async function refreshFileReadStateFromDisk(filePath, {
54
+ content = undefined
55
+ } = {}) {
56
+ const normalized = normalizeFilePath(filePath)
57
+ const nextContent = content === undefined ? await readFile(normalized, "utf8") : String(content)
58
+ const fileStat = await stat(normalized)
59
+ markFileRead(normalized, {
60
+ content: nextContent,
61
+ timestamp: fileStat.mtimeMs,
62
+ isPartialView: false
63
+ })
64
+ return getFileReadState(normalized)
65
+ }