@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.
- package/README.md +120 -178
- package/package.json +46 -46
- package/src/agent/agent.mjs +41 -0
- package/src/agent/prompt/frontend-designer.txt +58 -0
- package/src/agent/prompt/longagent-blueprint-agent.txt +83 -0
- package/src/agent/prompt/longagent-coding-agent.txt +37 -0
- package/src/agent/prompt/longagent-debugging-agent.txt +46 -0
- package/src/agent/prompt/longagent-preview-agent.txt +63 -0
- package/src/config/defaults.mjs +260 -195
- package/src/config/schema.mjs +71 -6
- package/src/core/constants.mjs +91 -46
- package/src/index.mjs +1 -1
- package/src/knowledge/frontend-aesthetics.txt +39 -0
- package/src/knowledge/loader.mjs +2 -1
- package/src/knowledge/tailwind.txt +12 -3
- package/src/mcp/client-http.mjs +141 -157
- package/src/mcp/client-sse.mjs +288 -286
- package/src/mcp/client-stdio.mjs +533 -451
- package/src/mcp/constants.mjs +2 -0
- package/src/mcp/registry.mjs +479 -394
- package/src/mcp/stdio-framing.mjs +133 -127
- package/src/mcp/tool-result.mjs +24 -0
- package/src/observability/index.mjs +42 -0
- package/src/observability/metrics.mjs +137 -0
- package/src/observability/tracer.mjs +137 -0
- package/src/orchestration/background-manager.mjs +372 -358
- package/src/orchestration/background-worker.mjs +305 -245
- package/src/orchestration/longagent-manager.mjs +171 -116
- package/src/orchestration/stage-scheduler.mjs +728 -489
- package/src/permission/exec-policy.mjs +9 -11
- package/src/provider/anthropic.mjs +1 -0
- package/src/provider/openai.mjs +340 -339
- package/src/provider/retry-policy.mjs +68 -68
- package/src/provider/router.mjs +241 -228
- package/src/provider/sse.mjs +104 -91
- package/src/repl.mjs +1 -1
- package/src/session/checkpoint.mjs +66 -3
- package/src/session/engine.mjs +227 -225
- package/src/session/longagent-4stage.mjs +460 -0
- package/src/session/longagent-hybrid.mjs +1081 -0
- package/src/session/longagent-plan.mjs +365 -329
- package/src/session/longagent-project-memory.mjs +53 -0
- package/src/session/longagent-scaffold.mjs +291 -100
- package/src/session/longagent-task-bus.mjs +54 -0
- package/src/session/longagent-utils.mjs +472 -0
- package/src/session/longagent.mjs +884 -1462
- package/src/session/project-context.mjs +30 -0
- package/src/session/store.mjs +510 -503
- package/src/session/task-validator.mjs +4 -3
- package/src/skill/builtin/design.mjs +76 -0
- package/src/skill/builtin/frontend.mjs +8 -0
- package/src/skill/registry.mjs +390 -336
- package/src/storage/ghost-commit-store.mjs +18 -8
- package/src/tool/executor.mjs +11 -0
- package/src/tool/git-auto.mjs +0 -19
- package/src/tool/registry.mjs +71 -37
- package/src/ui/activity-renderer.mjs +664 -410
- package/src/util/git.mjs +23 -0
package/src/provider/sse.mjs
CHANGED
|
@@ -1,91 +1,104 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 将 fetch response.body (ReadableStream) 解析为 SSE 事件的 AsyncIterator。
|
|
3
|
-
* 同时支持 OpenAI(纯 data: 行)和 Anthropic(event: + data: 对)格式。
|
|
4
|
-
*
|
|
5
|
-
* @param {ReadableStream} body
|
|
6
|
-
* @param {AbortSignal} [signal]
|
|
7
|
-
* @param {object} [options]
|
|
8
|
-
* @param {number} [options.idleTimeoutMs] - per-chunk idle timeout (resets on each chunk)
|
|
9
|
-
* @yields {{ event: string|null, data: string }}
|
|
10
|
-
*/
|
|
11
|
-
export async function* parseSSE(body, signal, options = {}) {
|
|
12
|
-
const reader = body.getReader()
|
|
13
|
-
const decoder = new TextDecoder()
|
|
14
|
-
let buffer = ""
|
|
15
|
-
const idleMs = options.idleTimeoutMs || 0
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 将 fetch response.body (ReadableStream) 解析为 SSE 事件的 AsyncIterator。
|
|
3
|
+
* 同时支持 OpenAI(纯 data: 行)和 Anthropic(event: + data: 对)格式。
|
|
4
|
+
*
|
|
5
|
+
* @param {ReadableStream} body
|
|
6
|
+
* @param {AbortSignal} [signal]
|
|
7
|
+
* @param {object} [options]
|
|
8
|
+
* @param {number} [options.idleTimeoutMs] - per-chunk idle timeout (resets on each chunk)
|
|
9
|
+
* @yields {{ event: string|null, data: string }}
|
|
10
|
+
*/
|
|
11
|
+
export async function* parseSSE(body, signal, options = {}) {
|
|
12
|
+
const reader = body.getReader()
|
|
13
|
+
const decoder = new TextDecoder()
|
|
14
|
+
let buffer = ""
|
|
15
|
+
const idleMs = options.idleTimeoutMs || 0
|
|
16
|
+
let currentTimeout = null
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
while (true) {
|
|
20
|
+
if (signal?.aborted) break
|
|
21
|
+
|
|
22
|
+
let readResult
|
|
23
|
+
if (idleMs > 0) {
|
|
24
|
+
if (currentTimeout) currentTimeout.cancel()
|
|
25
|
+
currentTimeout = idleTimeout(idleMs, signal)
|
|
26
|
+
readResult = await Promise.race([
|
|
27
|
+
reader.read(),
|
|
28
|
+
currentTimeout.promise
|
|
29
|
+
])
|
|
30
|
+
} else {
|
|
31
|
+
readResult = await reader.read()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const { done, value } = readResult
|
|
35
|
+
if (done) break
|
|
36
|
+
buffer += decoder.decode(value, { stream: true })
|
|
37
|
+
|
|
38
|
+
const parts = buffer.split("\n\n")
|
|
39
|
+
buffer = parts.pop()
|
|
40
|
+
|
|
41
|
+
for (const part of parts) {
|
|
42
|
+
const result = parsePart(part)
|
|
43
|
+
if (result === null) return // [DONE]
|
|
44
|
+
if (result) yield result
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// flush remaining buffer
|
|
48
|
+
if (buffer.trim()) {
|
|
49
|
+
const result = parsePart(buffer)
|
|
50
|
+
if (result && result !== null) yield result
|
|
51
|
+
}
|
|
52
|
+
} finally {
|
|
53
|
+
if (currentTimeout) currentTimeout.cancel()
|
|
54
|
+
try { reader.releaseLock() } catch { /* reader may have pending read if generator was force-closed */ }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function idleTimeout(ms, signal) {
|
|
59
|
+
let timer = null
|
|
60
|
+
let onAbort = null
|
|
61
|
+
const promise = new Promise((resolve, reject) => {
|
|
62
|
+
timer = setTimeout(() => {
|
|
63
|
+
const err = new Error(`stream idle timeout: no data received for ${ms}ms`)
|
|
64
|
+
err.code = "STREAM_IDLE_TIMEOUT"
|
|
65
|
+
reject(err)
|
|
66
|
+
}, ms)
|
|
67
|
+
if (signal) {
|
|
68
|
+
onAbort = () => {
|
|
69
|
+
clearTimeout(timer)
|
|
70
|
+
const err = new Error("aborted")
|
|
71
|
+
err.code = "ABORT_ERR"
|
|
72
|
+
reject(err)
|
|
73
|
+
}
|
|
74
|
+
if (signal.aborted) { clearTimeout(timer); onAbort(); return }
|
|
75
|
+
signal.addEventListener("abort", onAbort, { once: true })
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
function cancel() {
|
|
79
|
+
if (timer !== null) { clearTimeout(timer); timer = null }
|
|
80
|
+
if (onAbort && signal) {
|
|
81
|
+
signal.removeEventListener("abort", onAbort)
|
|
82
|
+
onAbort = null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { promise, cancel }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function parsePart(part) {
|
|
89
|
+
const trimmed = part.trim()
|
|
90
|
+
if (!trimmed) return undefined
|
|
91
|
+
let event = null
|
|
92
|
+
let data = ""
|
|
93
|
+
for (const line of trimmed.split("\n")) {
|
|
94
|
+
if (line.startsWith("event:")) {
|
|
95
|
+
event = line.slice(6).trim()
|
|
96
|
+
} else if (line.startsWith("data:")) {
|
|
97
|
+
const payload = line.slice(5).trim()
|
|
98
|
+
if (payload === "[DONE]") return null
|
|
99
|
+
data = payload
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!data) return undefined
|
|
103
|
+
return { event, data }
|
|
104
|
+
}
|
package/src/repl.mjs
CHANGED
|
@@ -2720,7 +2720,7 @@ function startSplash() {
|
|
|
2720
2720
|
" ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ "
|
|
2721
2721
|
]
|
|
2722
2722
|
const tagline = "AI Coding Agent"
|
|
2723
|
-
const version = "v0.1.
|
|
2723
|
+
const version = "v0.1.6"
|
|
2724
2724
|
|
|
2725
2725
|
// Gradient colors for the wave animation (cyan → blue → purple → pink → back)
|
|
2726
2726
|
const wave = [
|
|
@@ -46,6 +46,71 @@ export async function listCheckpoints(sessionId) {
|
|
|
46
46
|
.sort()
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
// ========== Phase 7: Task 级 Checkpoint ==========
|
|
50
|
+
|
|
51
|
+
export async function saveTaskCheckpoint(sessionId, stageId, taskId, data) {
|
|
52
|
+
const dir = checkpointDir(sessionId)
|
|
53
|
+
await mkdir(dir, { recursive: true })
|
|
54
|
+
const name = `task_${stageId}_${taskId}`
|
|
55
|
+
const checkpoint = {
|
|
56
|
+
sessionId,
|
|
57
|
+
stageId,
|
|
58
|
+
taskId,
|
|
59
|
+
savedAt: Date.now(),
|
|
60
|
+
...data
|
|
61
|
+
}
|
|
62
|
+
await writeJson(checkpointFile(sessionId, name), checkpoint)
|
|
63
|
+
return checkpoint
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function loadTaskCheckpoints(sessionId, stageId) {
|
|
67
|
+
const dir = checkpointDir(sessionId)
|
|
68
|
+
const files = await readdir(dir, { withFileTypes: true }).catch(() => [])
|
|
69
|
+
const prefix = `task_${stageId}_`
|
|
70
|
+
const results = {}
|
|
71
|
+
for (const entry of files) {
|
|
72
|
+
if (entry.isFile() && entry.name.startsWith(prefix) && entry.name.endsWith(".json")) {
|
|
73
|
+
const data = await readJson(path.join(dir, entry.name), null)
|
|
74
|
+
if (data?.taskId) results[data.taskId] = data
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return results
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ========== Phase 10: Checkpoint 清理策略 ==========
|
|
81
|
+
|
|
82
|
+
export async function cleanupCheckpoints(sessionId, options = {}) {
|
|
83
|
+
const maxKeep = options.maxKeep || 10
|
|
84
|
+
const keepStageCheckpoints = options.keepStageCheckpoints !== false
|
|
85
|
+
const dir = checkpointDir(sessionId)
|
|
86
|
+
const all = await listCheckpoints(sessionId)
|
|
87
|
+
if (all.length <= maxKeep + 1) return { removed: 0 }
|
|
88
|
+
|
|
89
|
+
const toKeep = new Set(["latest"])
|
|
90
|
+
// 保留 stage 级和 task 级 checkpoint
|
|
91
|
+
if (keepStageCheckpoints) {
|
|
92
|
+
for (const name of all) {
|
|
93
|
+
if (name.startsWith("hybrid_stage_") || name.startsWith("task_")) {
|
|
94
|
+
toKeep.add(name)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// 保留最近 maxKeep 个编号 checkpoint
|
|
99
|
+
const numbered = all.filter(n => n.startsWith("cp_")).sort()
|
|
100
|
+
for (const n of numbered.slice(-maxKeep)) toKeep.add(n)
|
|
101
|
+
|
|
102
|
+
let removed = 0
|
|
103
|
+
for (const name of all) {
|
|
104
|
+
if (toKeep.has(name)) continue
|
|
105
|
+
try {
|
|
106
|
+
const { unlink: unlinkFile } = await import("node:fs/promises")
|
|
107
|
+
await unlinkFile(checkpointFile(sessionId, name)).catch(() => {})
|
|
108
|
+
removed++
|
|
109
|
+
} catch { /* ignore */ }
|
|
110
|
+
}
|
|
111
|
+
return { removed }
|
|
112
|
+
}
|
|
113
|
+
|
|
49
114
|
// ============================================================================
|
|
50
115
|
// Git Snapshot Integration - AI Agent 自动 Git 快照功能
|
|
51
116
|
// ============================================================================
|
|
@@ -61,12 +126,10 @@ export async function listCheckpoints(sessionId) {
|
|
|
61
126
|
* @returns {Promise<{ok: boolean, snapshot?: Object, skipped?: boolean, reason?: string}>}
|
|
62
127
|
*/
|
|
63
128
|
export async function autoSnapshotBeforeEdit(sessionId, cwd, config = {}, options = {}) {
|
|
64
|
-
// 检查 Git
|
|
129
|
+
// 检查 Git 自动化是否启用(默认启用,只有显式关闭才跳过)
|
|
65
130
|
if (config.git_auto?.enabled === false) {
|
|
66
131
|
return { ok: true, skipped: true, reason: "git_auto_disabled" }
|
|
67
132
|
}
|
|
68
|
-
|
|
69
|
-
// 检查自动快照是否启用
|
|
70
133
|
if (config.git_auto?.auto_snapshot === false) {
|
|
71
134
|
return { ok: true, skipped: true, reason: "auto_snapshot_disabled" }
|
|
72
135
|
}
|