@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
@@ -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
+ }