@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.
- package/LICENSE +674 -674
- package/README.md +474 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +228 -220
- package/src/agent/custom-agent-loader.mjs +6 -3
- package/src/agent/generator.mjs +2 -2
- package/src/agent/prompt/assistant.txt +12 -0
- package/src/agent/prompt/bug-hunter.txt +89 -89
- package/src/agent/prompt/frontend-designer.txt +58 -58
- package/src/agent/prompt/guide.txt +1 -1
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
- package/src/agent/prompt/longagent-coding-agent.txt +37 -37
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
- package/src/agent/prompt/longagent-preview-agent.txt +63 -63
- package/src/command/custom-commands.mjs +2 -2
- package/src/commands/agent.mjs +1 -1
- package/src/commands/background.mjs +145 -4
- package/src/commands/chat.mjs +117 -76
- package/src/commands/config.mjs +148 -1
- package/src/commands/doctor.mjs +30 -6
- package/src/commands/init.mjs +32 -6
- package/src/commands/longagent.mjs +117 -0
- package/src/commands/mcp.mjs +275 -43
- package/src/commands/permission.mjs +1 -1
- package/src/commands/session.mjs +195 -140
- package/src/commands/skill.mjs +63 -0
- package/src/commands/theme.mjs +1 -1
- package/src/commands/update.mjs +32 -0
- package/src/config/defaults.mjs +289 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +604 -574
- package/src/context.mjs +4 -1
- package/src/core/constants.mjs +97 -91
- package/src/core/types.mjs +1 -1
- package/src/github/api.mjs +78 -78
- package/src/github/auth.mjs +294 -286
- package/src/github/flow.mjs +298 -298
- package/src/github/workspace.mjs +225 -212
- package/src/index.mjs +87 -82
- package/src/knowledge/frontend-aesthetics.txt +38 -38
- package/src/mcp/client-http.mjs +139 -141
- package/src/mcp/client-sse.mjs +297 -288
- package/src/mcp/client-stdio.mjs +534 -533
- package/src/mcp/constants.mjs +4 -2
- package/src/mcp/registry.mjs +498 -479
- package/src/mcp/stdio-framing.mjs +135 -133
- package/src/mcp/tool-result.mjs +24 -24
- package/src/observability/edit-diagnostics.mjs +449 -0
- package/src/observability/index.mjs +42 -42
- package/src/observability/metrics.mjs +165 -137
- package/src/observability/tracer.mjs +137 -137
- package/src/onboarding.mjs +209 -0
- package/src/orchestration/background-manager.mjs +567 -372
- package/src/orchestration/background-worker.mjs +419 -305
- package/src/orchestration/interruption-reason.mjs +21 -0
- package/src/orchestration/longagent-manager.mjs +197 -171
- package/src/orchestration/stage-scheduler.mjs +733 -728
- package/src/orchestration/subagent-router.mjs +7 -1
- package/src/orchestration/task-scheduler.mjs +219 -7
- package/src/permission/engine.mjs +1 -1
- package/src/permission/exec-policy.mjs +370 -370
- package/src/permission/file-edit-policy.mjs +108 -0
- package/src/permission/prompt.mjs +1 -1
- package/src/permission/rules.mjs +116 -7
- package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
- package/src/plugin/hook-bus.mjs +19 -5
- package/src/plugin/manifest-loader.mjs +222 -0
- package/src/provider/anthropic.mjs +396 -390
- package/src/provider/ollama.mjs +7 -1
- package/src/provider/openai.mjs +382 -340
- package/src/provider/retry-policy.mjs +74 -68
- package/src/provider/router.mjs +242 -241
- package/src/provider/sse.mjs +104 -104
- package/src/provider/wizard.mjs +556 -0
- package/src/repl/capability-facade.mjs +30 -0
- package/src/repl/command-surface.mjs +23 -0
- package/src/repl/controller-entry.mjs +40 -0
- package/src/repl/core-shell.mjs +208 -0
- package/src/repl/dialog-router.mjs +87 -0
- package/src/repl/input-engine.mjs +76 -0
- package/src/repl/keymap.mjs +7 -0
- package/src/repl/operator-surface.mjs +15 -0
- package/src/repl/permission-flow.mjs +49 -0
- package/src/repl/runtime-facade.mjs +36 -0
- package/src/repl/slash-router.mjs +62 -0
- package/src/repl/state-store.mjs +29 -0
- package/src/repl/turn-controller.mjs +58 -0
- package/src/repl/verification.mjs +23 -0
- package/src/repl.mjs +3371 -2981
- package/src/rules/load-rules.mjs +3 -3
- package/src/runtime.mjs +1 -1
- package/src/session/agent-transaction.mjs +86 -0
- package/src/session/checkpoint.mjs +302 -302
- package/src/session/compaction.mjs +298 -298
- package/src/session/engine.mjs +417 -232
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1097
- package/src/session/longagent-plan.mjs +376 -365
- package/src/session/longagent-project-memory.mjs +53 -53
- package/src/session/longagent-scaffold.mjs +291 -291
- package/src/session/longagent-task-bus.mjs +138 -54
- package/src/session/longagent-utils.mjs +828 -472
- package/src/session/longagent.mjs +911 -900
- package/src/session/loop.mjs +1005 -930
- package/src/session/prompt/agent.txt +25 -25
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +31 -31
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +196 -195
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -519
- package/src/session/system-prompt.mjs +308 -273
- package/src/session/task-validator.mjs +267 -267
- package/src/session/usability-gates.mjs +2 -2
- package/src/skill/builtin/commit.mjs +64 -64
- package/src/skill/builtin/design.mjs +76 -76
- package/src/skill/generator.mjs +18 -2
- package/src/skill/registry.mjs +642 -390
- package/src/storage/audit-store.mjs +18 -11
- package/src/storage/event-log.mjs +7 -1
- package/src/storage/ghost-commit-store.mjs +243 -245
- package/src/storage/paths.mjs +17 -0
- package/src/theme/default-theme.mjs +1 -1
- package/src/theme/markdown.mjs +4 -0
- package/src/theme/schema.mjs +1 -1
- package/src/theme/status-bar.mjs +162 -158
- package/src/tool/audit-wrapper.mjs +18 -2
- package/src/tool/edit-transaction.mjs +23 -0
- package/src/tool/executor.mjs +26 -1
- package/src/tool/file-read-state.mjs +65 -0
- package/src/tool/git-auto.mjs +526 -526
- package/src/tool/git-full-auto.mjs +487 -478
- package/src/tool/mutation-guard.mjs +54 -0
- package/src/tool/prompt/edit.txt +3 -3
- package/src/tool/prompt/multiedit.txt +1 -0
- package/src/tool/prompt/notebookedit.txt +2 -1
- package/src/tool/prompt/patch.txt +25 -24
- package/src/tool/prompt/read.txt +3 -3
- package/src/tool/prompt/sysinfo.txt +29 -0
- package/src/tool/prompt/task.txt +66 -4
- package/src/tool/prompt/write.txt +2 -2
- package/src/tool/question-prompt.mjs +99 -93
- package/src/tool/registry.mjs +1701 -1343
- package/src/tool/task-tool.mjs +14 -6
- package/src/ui/activity-renderer.mjs +667 -664
- package/src/ui/repl-background-panel.mjs +7 -0
- package/src/ui/repl-capability-panel.mjs +9 -0
- package/src/ui/repl-dashboard.mjs +54 -4
- package/src/ui/repl-help.mjs +110 -0
- package/src/ui/repl-operator-panel.mjs +12 -0
- package/src/ui/repl-route-feedback.mjs +35 -0
- package/src/ui/repl-status-view.mjs +76 -0
- package/src/ui/repl-task-panel.mjs +5 -0
- package/src/ui/repl-transcript-panel.mjs +56 -0
- package/src/ui/repl-turn-summary.mjs +135 -0
- package/src/update/checker.mjs +184 -0
- package/src/usage/pricing.mjs +122 -121
- package/src/usage/usage-meter.mjs +1 -0
- package/src/util/git.mjs +562 -519
- package/src/util/template.mjs +6 -1
- package/src/version.mjs +3 -0
package/src/theme/status-bar.mjs
CHANGED
|
@@ -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":
|
|
15
|
-
case "
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
if (
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
|
|
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 })
|
package/src/tool/executor.mjs
CHANGED
|
@@ -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
|
+
}
|