@kkelly-offical/kkcode 0.1.2 → 0.1.6

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 (58) hide show
  1. package/README.md +120 -178
  2. package/package.json +46 -46
  3. package/src/agent/agent.mjs +41 -0
  4. package/src/agent/prompt/frontend-designer.txt +58 -0
  5. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -0
  6. package/src/agent/prompt/longagent-coding-agent.txt +37 -0
  7. package/src/agent/prompt/longagent-debugging-agent.txt +46 -0
  8. package/src/agent/prompt/longagent-preview-agent.txt +63 -0
  9. package/src/config/defaults.mjs +260 -195
  10. package/src/config/schema.mjs +71 -6
  11. package/src/core/constants.mjs +91 -46
  12. package/src/index.mjs +1 -1
  13. package/src/knowledge/frontend-aesthetics.txt +39 -0
  14. package/src/knowledge/loader.mjs +2 -1
  15. package/src/knowledge/tailwind.txt +12 -3
  16. package/src/mcp/client-http.mjs +141 -157
  17. package/src/mcp/client-sse.mjs +288 -286
  18. package/src/mcp/client-stdio.mjs +533 -451
  19. package/src/mcp/constants.mjs +2 -0
  20. package/src/mcp/registry.mjs +479 -394
  21. package/src/mcp/stdio-framing.mjs +133 -127
  22. package/src/mcp/tool-result.mjs +24 -0
  23. package/src/observability/index.mjs +42 -0
  24. package/src/observability/metrics.mjs +137 -0
  25. package/src/observability/tracer.mjs +137 -0
  26. package/src/orchestration/background-manager.mjs +372 -358
  27. package/src/orchestration/background-worker.mjs +305 -245
  28. package/src/orchestration/longagent-manager.mjs +171 -116
  29. package/src/orchestration/stage-scheduler.mjs +728 -489
  30. package/src/permission/exec-policy.mjs +9 -11
  31. package/src/provider/anthropic.mjs +1 -0
  32. package/src/provider/openai.mjs +340 -339
  33. package/src/provider/retry-policy.mjs +68 -68
  34. package/src/provider/router.mjs +241 -228
  35. package/src/provider/sse.mjs +104 -91
  36. package/src/repl.mjs +1 -1
  37. package/src/session/checkpoint.mjs +66 -3
  38. package/src/session/engine.mjs +227 -225
  39. package/src/session/longagent-4stage.mjs +460 -0
  40. package/src/session/longagent-hybrid.mjs +1081 -0
  41. package/src/session/longagent-plan.mjs +365 -329
  42. package/src/session/longagent-project-memory.mjs +53 -0
  43. package/src/session/longagent-scaffold.mjs +291 -100
  44. package/src/session/longagent-task-bus.mjs +54 -0
  45. package/src/session/longagent-utils.mjs +472 -0
  46. package/src/session/longagent.mjs +884 -1462
  47. package/src/session/project-context.mjs +30 -0
  48. package/src/session/store.mjs +510 -503
  49. package/src/session/task-validator.mjs +4 -3
  50. package/src/skill/builtin/design.mjs +76 -0
  51. package/src/skill/builtin/frontend.mjs +8 -0
  52. package/src/skill/registry.mjs +390 -336
  53. package/src/storage/ghost-commit-store.mjs +18 -8
  54. package/src/tool/executor.mjs +11 -0
  55. package/src/tool/git-auto.mjs +0 -19
  56. package/src/tool/registry.mjs +71 -37
  57. package/src/ui/activity-renderer.mjs +664 -410
  58. package/src/util/git.mjs +23 -0
@@ -1,225 +1,227 @@
1
- import { randomUUID } from "node:crypto"
2
- import { loadPricing, calculateCost } from "../usage/pricing.mjs"
3
- import { recordTurn } from "../usage/usage-meter.mjs"
4
- import { processTurnLoop } from "./loop.mjs"
5
- import { runLongAgent } from "./longagent.mjs"
6
- import { touchSession, setBudgetState } from "./store.mjs"
7
- import { appendEventLog } from "../storage/event-log.mjs"
8
- import { EventBus } from "../core/events.mjs"
9
- import { ToolRegistry } from "../tool/registry.mjs"
10
- import { resolveAgentForMode } from "../agent/agent.mjs"
11
- import { estimateStringTokens } from "./compaction.mjs"
12
-
13
- let sinkReady = false
14
-
15
- function estimateTokens(text) {
16
- return Math.max(1, estimateStringTokens(text || ""))
17
- }
18
-
19
- export function resolveMode(inputMode = "agent") {
20
- const mode = String(inputMode || "agent").toLowerCase()
21
- if (["ask", "plan", "agent", "longagent"].includes(mode)) return mode
22
- return "agent"
23
- }
24
-
25
- export function newSessionId() {
26
- return `ses_${randomUUID().slice(0, 12)}`
27
- }
28
-
29
- function maybeRegisterSink() {
30
- if (sinkReady) return
31
- EventBus.registerSink(async (event) => {
32
- await appendEventLog(event)
33
- })
34
- sinkReady = true
35
- }
36
-
37
- function evaluateBudget(config, meter) {
38
- const budget = config.usage?.budget || {}
39
- const warnings = []
40
- const strategy = budget.strategy || "warn"
41
- const warnAt = Number(budget.warn_at_percent || 80)
42
- let exceeded = false
43
-
44
- if (budget.session_usd && meter.session.cost > 0) {
45
- const ratio = (meter.session.cost / budget.session_usd) * 100
46
- if (ratio >= 100) exceeded = true
47
- if (ratio >= warnAt) warnings.push(`session budget ${ratio.toFixed(1)}% (${meter.session.cost.toFixed(4)}/${budget.session_usd})`)
48
- }
49
- if (budget.global_usd && meter.global.cost > 0) {
50
- const ratio = (meter.global.cost / budget.global_usd) * 100
51
- if (ratio >= 100) exceeded = true
52
- if (ratio >= warnAt) warnings.push(`global budget ${ratio.toFixed(1)}% (${meter.global.cost.toFixed(4)}/${budget.global_usd})`)
53
- }
54
- return { warnings, exceeded, strategy }
55
- }
56
-
57
- export async function executeTurn({
58
- prompt,
59
- contentBlocks = null,
60
- mode,
61
- model,
62
- sessionId,
63
- configState,
64
- providerType = null,
65
- baseUrl = null,
66
- apiKeyEnv = null,
67
- maxIterations = null,
68
- signal = null,
69
- output = null,
70
- allowQuestion = true,
71
- toolContext = {}
72
- }) {
73
- maybeRegisterSink()
74
-
75
- const resolvedProviderType = providerType || configState.config.provider.default
76
- const agent = resolveAgentForMode(mode)
77
- await ToolRegistry.initialize({
78
- config: configState.config,
79
- cwd: process.cwd()
80
- })
81
- await touchSession({
82
- sessionId,
83
- mode,
84
- model,
85
- providerType: resolvedProviderType,
86
- cwd: process.cwd(),
87
- status: mode === "longagent" ? "running-longagent" : "active"
88
- })
89
-
90
- const turn =
91
- mode === "longagent"
92
- ? await runLongAgent({
93
- prompt,
94
- model,
95
- providerType: resolvedProviderType,
96
- sessionId,
97
- configState,
98
- baseUrl,
99
- apiKeyEnv,
100
- agent,
101
- maxIterations:
102
- maxIterations === null
103
- ? Number(configState.config.agent.longagent.max_iterations || 0)
104
- : Number(maxIterations),
105
- signal,
106
- output,
107
- allowQuestion,
108
- toolContext
109
- })
110
- : await processTurnLoop({
111
- prompt,
112
- contentBlocks,
113
- mode,
114
- model,
115
- providerType: resolvedProviderType,
116
- sessionId,
117
- configState,
118
- baseUrl,
119
- apiKeyEnv,
120
- agent,
121
- output,
122
- signal,
123
- allowQuestion,
124
- toolContext
125
- })
126
-
127
- const usage = { ...turn.usage }
128
- let estimated = false
129
- if ((usage.input || 0) === 0 && (usage.output || 0) === 0) {
130
- usage.input = estimateTokens(prompt)
131
- usage.output = estimateTokens(turn.reply)
132
- estimated = true
133
- }
134
-
135
- const pricingInfo = await loadPricing(configState)
136
- const costInfo = calculateCost(pricingInfo.pricing, model, usage)
137
- const meter = await recordTurn({ sessionId, usage, cost: costInfo.amount })
138
- const budgetResult = evaluateBudget(configState.config, meter)
139
-
140
- await setBudgetState(sessionId, {
141
- lastTurnCost: costInfo.amount,
142
- warnings: budgetResult.warnings,
143
- exceeded: budgetResult.exceeded,
144
- updatedAt: Date.now()
145
- })
146
-
147
- if (budgetResult.exceeded && budgetResult.strategy === "block") {
148
- const msg = `budget exceeded — ${budgetResult.warnings.join("; ")}. strategy=block, stopping execution.`
149
- return {
150
- reply: msg,
151
- mode,
152
- model,
153
- sessionId,
154
- turnId: turn.turnId,
155
- emittedText: turn.emittedText,
156
- context: turn.context,
157
- tokenMeter: { ...meter, estimated: estimated || costInfo.unknown },
158
- cost: costInfo.amount,
159
- costSavings: costInfo.savings,
160
- pricingWarnings: pricingInfo.errors,
161
- budgetWarnings: budgetResult.warnings,
162
- budgetExceeded: true,
163
- toolEvents: turn.toolEvents,
164
- longagent: mode === "longagent"
165
- ? {
166
- status: turn.status,
167
- phase: turn.phase,
168
- gateStatus: turn.gateStatus,
169
- currentGate: turn.currentGate,
170
- lastGateFailures: turn.lastGateFailures || [],
171
- iterations: turn.iterations,
172
- recoveryCount: turn.recoveryCount,
173
- progress: turn.progress,
174
- elapsed: turn.elapsed,
175
- stageIndex: turn.stageIndex,
176
- stageCount: turn.stageCount,
177
- currentStageId: turn.currentStageId,
178
- planFrozen: turn.planFrozen,
179
- taskProgress: turn.taskProgress,
180
- stageProgress: turn.stageProgress,
181
- remainingFilesCount: turn.remainingFilesCount,
182
- fileChanges: turn.fileChanges || []
183
- }
184
- : null
185
- }
186
- }
187
-
188
- return {
189
- reply: turn.reply,
190
- mode,
191
- model,
192
- sessionId,
193
- turnId: turn.turnId,
194
- emittedText: turn.emittedText,
195
- context: turn.context,
196
- tokenMeter: { ...meter, estimated: estimated || costInfo.unknown },
197
- cost: costInfo.amount,
198
- costSavings: costInfo.savings,
199
- pricingWarnings: pricingInfo.errors,
200
- budgetWarnings: budgetResult.warnings,
201
- budgetExceeded: false,
202
- toolEvents: turn.toolEvents,
203
- longagent: mode === "longagent"
204
- ? {
205
- status: turn.status,
206
- phase: turn.phase,
207
- gateStatus: turn.gateStatus,
208
- currentGate: turn.currentGate,
209
- lastGateFailures: turn.lastGateFailures || [],
210
- iterations: turn.iterations,
211
- recoveryCount: turn.recoveryCount,
212
- progress: turn.progress,
213
- elapsed: turn.elapsed,
214
- stageIndex: turn.stageIndex,
215
- stageCount: turn.stageCount,
216
- currentStageId: turn.currentStageId,
217
- planFrozen: turn.planFrozen,
218
- taskProgress: turn.taskProgress,
219
- stageProgress: turn.stageProgress,
220
- remainingFilesCount: turn.remainingFilesCount,
221
- fileChanges: turn.fileChanges || []
222
- }
223
- : null
224
- }
225
- }
1
+ import { randomUUID } from "node:crypto"
2
+ import { loadPricing, calculateCost } from "../usage/pricing.mjs"
3
+ import { recordTurn } from "../usage/usage-meter.mjs"
4
+ import { processTurnLoop } from "./loop.mjs"
5
+ import { runLongAgent } from "./longagent.mjs"
6
+ import { touchSession, setBudgetState } from "./store.mjs"
7
+ import { appendEventLog } from "../storage/event-log.mjs"
8
+ import { EventBus } from "../core/events.mjs"
9
+ import { initialize as initObservability } from "../observability/index.mjs"
10
+ import { ToolRegistry } from "../tool/registry.mjs"
11
+ import { resolveAgentForMode } from "../agent/agent.mjs"
12
+ import { estimateStringTokens } from "./compaction.mjs"
13
+
14
+ let sinkReady = false
15
+
16
+ function estimateTokens(text) {
17
+ return Math.max(1, estimateStringTokens(text || ""))
18
+ }
19
+
20
+ export function resolveMode(inputMode = "agent") {
21
+ const mode = String(inputMode || "agent").toLowerCase()
22
+ if (["ask", "plan", "agent", "longagent"].includes(mode)) return mode
23
+ return "agent"
24
+ }
25
+
26
+ export function newSessionId() {
27
+ return `ses_${randomUUID().slice(0, 12)}`
28
+ }
29
+
30
+ function maybeRegisterSink() {
31
+ if (sinkReady) return
32
+ EventBus.registerSink(async (event) => {
33
+ await appendEventLog(event)
34
+ })
35
+ initObservability(EventBus)
36
+ sinkReady = true
37
+ }
38
+
39
+ function evaluateBudget(config, meter) {
40
+ const budget = config.usage?.budget || {}
41
+ const warnings = []
42
+ const strategy = budget.strategy || "warn"
43
+ const warnAt = Number(budget.warn_at_percent || 80)
44
+ let exceeded = false
45
+
46
+ if (budget.session_usd && meter.session.cost > 0) {
47
+ const ratio = (meter.session.cost / budget.session_usd) * 100
48
+ if (ratio >= 100) exceeded = true
49
+ if (ratio >= warnAt) warnings.push(`session budget ${ratio.toFixed(1)}% (${meter.session.cost.toFixed(4)}/${budget.session_usd})`)
50
+ }
51
+ if (budget.global_usd && meter.global.cost > 0) {
52
+ const ratio = (meter.global.cost / budget.global_usd) * 100
53
+ if (ratio >= 100) exceeded = true
54
+ if (ratio >= warnAt) warnings.push(`global budget ${ratio.toFixed(1)}% (${meter.global.cost.toFixed(4)}/${budget.global_usd})`)
55
+ }
56
+ return { warnings, exceeded, strategy }
57
+ }
58
+
59
+ export async function executeTurn({
60
+ prompt,
61
+ contentBlocks = null,
62
+ mode,
63
+ model,
64
+ sessionId,
65
+ configState,
66
+ providerType = null,
67
+ baseUrl = null,
68
+ apiKeyEnv = null,
69
+ maxIterations = null,
70
+ signal = null,
71
+ output = null,
72
+ allowQuestion = true,
73
+ toolContext = {}
74
+ }) {
75
+ maybeRegisterSink()
76
+
77
+ const resolvedProviderType = providerType || configState.config.provider.default
78
+ const agent = resolveAgentForMode(mode)
79
+ await ToolRegistry.initialize({
80
+ config: configState.config,
81
+ cwd: process.cwd()
82
+ })
83
+ await touchSession({
84
+ sessionId,
85
+ mode,
86
+ model,
87
+ providerType: resolvedProviderType,
88
+ cwd: process.cwd(),
89
+ status: mode === "longagent" ? "running-longagent" : "active"
90
+ })
91
+
92
+ const turn =
93
+ mode === "longagent"
94
+ ? await runLongAgent({
95
+ prompt,
96
+ model,
97
+ providerType: resolvedProviderType,
98
+ sessionId,
99
+ configState,
100
+ baseUrl,
101
+ apiKeyEnv,
102
+ agent,
103
+ maxIterations:
104
+ maxIterations === null
105
+ ? Number(configState.config.agent.longagent.max_iterations || 0)
106
+ : Number(maxIterations),
107
+ signal,
108
+ output,
109
+ allowQuestion,
110
+ toolContext
111
+ })
112
+ : await processTurnLoop({
113
+ prompt,
114
+ contentBlocks,
115
+ mode,
116
+ model,
117
+ providerType: resolvedProviderType,
118
+ sessionId,
119
+ configState,
120
+ baseUrl,
121
+ apiKeyEnv,
122
+ agent,
123
+ output,
124
+ signal,
125
+ allowQuestion,
126
+ toolContext
127
+ })
128
+
129
+ const usage = { ...turn.usage }
130
+ let estimated = false
131
+ if ((usage.input || 0) === 0 && (usage.output || 0) === 0) {
132
+ usage.input = estimateTokens(prompt)
133
+ usage.output = estimateTokens(turn.reply)
134
+ estimated = true
135
+ }
136
+
137
+ const pricingInfo = await loadPricing(configState)
138
+ const costInfo = calculateCost(pricingInfo.pricing, model, usage)
139
+ const meter = await recordTurn({ sessionId, usage, cost: costInfo.amount })
140
+ const budgetResult = evaluateBudget(configState.config, meter)
141
+
142
+ await setBudgetState(sessionId, {
143
+ lastTurnCost: costInfo.amount,
144
+ warnings: budgetResult.warnings,
145
+ exceeded: budgetResult.exceeded,
146
+ updatedAt: Date.now()
147
+ })
148
+
149
+ if (budgetResult.exceeded && budgetResult.strategy === "block") {
150
+ const msg = `budget exceeded — ${budgetResult.warnings.join("; ")}. strategy=block, stopping execution.`
151
+ return {
152
+ reply: msg,
153
+ mode,
154
+ model,
155
+ sessionId,
156
+ turnId: turn.turnId,
157
+ emittedText: turn.emittedText,
158
+ context: turn.context,
159
+ tokenMeter: { ...meter, estimated: estimated || costInfo.unknown },
160
+ cost: costInfo.amount,
161
+ costSavings: costInfo.savings,
162
+ pricingWarnings: pricingInfo.errors,
163
+ budgetWarnings: budgetResult.warnings,
164
+ budgetExceeded: true,
165
+ toolEvents: turn.toolEvents,
166
+ longagent: mode === "longagent"
167
+ ? {
168
+ status: turn.status,
169
+ phase: turn.phase,
170
+ gateStatus: turn.gateStatus,
171
+ currentGate: turn.currentGate,
172
+ lastGateFailures: turn.lastGateFailures || [],
173
+ iterations: turn.iterations,
174
+ recoveryCount: turn.recoveryCount,
175
+ progress: turn.progress,
176
+ elapsed: turn.elapsed,
177
+ stageIndex: turn.stageIndex,
178
+ stageCount: turn.stageCount,
179
+ currentStageId: turn.currentStageId,
180
+ planFrozen: turn.planFrozen,
181
+ taskProgress: turn.taskProgress,
182
+ stageProgress: turn.stageProgress,
183
+ remainingFilesCount: turn.remainingFilesCount,
184
+ fileChanges: turn.fileChanges || []
185
+ }
186
+ : null
187
+ }
188
+ }
189
+
190
+ return {
191
+ reply: turn.reply,
192
+ mode,
193
+ model,
194
+ sessionId,
195
+ turnId: turn.turnId,
196
+ emittedText: turn.emittedText,
197
+ context: turn.context,
198
+ tokenMeter: { ...meter, estimated: estimated || costInfo.unknown },
199
+ cost: costInfo.amount,
200
+ costSavings: costInfo.savings,
201
+ pricingWarnings: pricingInfo.errors,
202
+ budgetWarnings: budgetResult.warnings,
203
+ budgetExceeded: false,
204
+ toolEvents: turn.toolEvents,
205
+ longagent: mode === "longagent"
206
+ ? {
207
+ status: turn.status,
208
+ phase: turn.phase,
209
+ gateStatus: turn.gateStatus,
210
+ currentGate: turn.currentGate,
211
+ lastGateFailures: turn.lastGateFailures || [],
212
+ iterations: turn.iterations,
213
+ recoveryCount: turn.recoveryCount,
214
+ progress: turn.progress,
215
+ elapsed: turn.elapsed,
216
+ stageIndex: turn.stageIndex,
217
+ stageCount: turn.stageCount,
218
+ currentStageId: turn.currentStageId,
219
+ planFrozen: turn.planFrozen,
220
+ taskProgress: turn.taskProgress,
221
+ stageProgress: turn.stageProgress,
222
+ remainingFilesCount: turn.remainingFilesCount,
223
+ fileChanges: turn.fileChanges || []
224
+ }
225
+ : null
226
+ }
227
+ }