@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.
- package/LICENSE +674 -674
- package/README.md +452 -387
- package/package.json +50 -46
- package/src/agent/agent.mjs +19 -2
- 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 +90 -0
- 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/config/defaults.mjs +280 -260
- package/src/config/import-config.mjs +1 -1
- package/src/config/load-config.mjs +61 -4
- package/src/config/schema.mjs +591 -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 +84 -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 +2 -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 +3368 -2929
- 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 +36 -14
- package/src/session/engine.mjs +417 -227
- package/src/session/longagent-4stage.mjs +467 -460
- package/src/session/longagent-hybrid.mjs +1344 -1081
- 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 -884
- package/src/session/loop.mjs +1005 -905
- package/src/session/prompt/agent.txt +25 -0
- package/src/session/prompt/anthropic.txt +150 -150
- package/src/session/prompt/beast.txt +1 -1
- package/src/session/prompt/plan.txt +28 -6
- package/src/session/prompt/qwen.txt +46 -46
- package/src/session/recovery.mjs +21 -0
- package/src/session/rollback.mjs +197 -0
- package/src/session/routing-observability.mjs +72 -0
- package/src/session/runtime-state.mjs +47 -0
- package/src/session/store.mjs +523 -510
- package/src/session/system-prompt.mjs +56 -8
- 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 +13 -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 +17 -4
- 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/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/commands/chat.mjs
CHANGED
|
@@ -1,68 +1,109 @@
|
|
|
1
|
-
import { Command } from "commander"
|
|
2
|
-
import { buildContext, printContextWarnings } from "../context.mjs"
|
|
3
|
-
import { executeTurn, newSessionId, resolveMode } from "../session/engine.mjs"
|
|
4
|
-
import {
|
|
1
|
+
import { Command } from "commander"
|
|
2
|
+
import { buildContext, printContextWarnings } from "../context.mjs"
|
|
3
|
+
import { ensureEventSinks, executeTurn, formatPublicModeSummary, getPublicModeContract, newSessionId, resolveMode, resolvePromptMode, summarizeRouteDecision } from "../session/engine.mjs"
|
|
4
|
+
import { emitRouteDecisionEvent } from "../session/routing-observability.mjs"
|
|
5
|
+
import { renderStatusBar } from "../theme/status-bar.mjs"
|
|
5
6
|
import { applyCommandTemplate, loadCustomCommands } from "../command/custom-commands.mjs"
|
|
6
7
|
import { ToolRegistry } from "../tool/registry.mjs"
|
|
8
|
+
import { SkillRegistry } from "../skill/registry.mjs"
|
|
9
|
+
import { PermissionEngine } from "../permission/engine.mjs"
|
|
7
10
|
import { HookBus, initHookBus } from "../plugin/hook-bus.mjs"
|
|
8
11
|
import { listProviders } from "../provider/router.mjs"
|
|
12
|
+
import { EventBus } from "../core/events.mjs"
|
|
13
|
+
import { EVENT_TYPES } from "../core/constants.mjs"
|
|
14
|
+
|
|
15
|
+
export function resolveChatExecutionMode(prompt, requestedMode) {
|
|
16
|
+
return resolvePromptMode(prompt, requestedMode)
|
|
17
|
+
}
|
|
9
18
|
|
|
10
19
|
export function createChatCommand() {
|
|
11
20
|
const providers = listProviders()
|
|
12
21
|
return new Command("chat")
|
|
13
|
-
.description("run one prompt in
|
|
22
|
+
.description("run one prompt in assistant/plan/agent/code/longagent mode (assistant = default personal lane)")
|
|
14
23
|
.argument("<prompt...>", "prompt text")
|
|
15
|
-
.option("--mode <mode>", "
|
|
24
|
+
.option("--mode <mode>", "assistant|plan|agent|code|coding|longagent", "assistant")
|
|
16
25
|
.option("--model <model>", "model id")
|
|
17
26
|
.option("--provider-type <type>", `provider type (${providers.join("|")})`)
|
|
18
|
-
.option("--base-url <url>", "provider base url override")
|
|
19
|
-
.option("--api-key-env <name>", "api key env override")
|
|
20
|
-
.option("--max-iterations <n>", "longagent max iterations (0 = unlimited)")
|
|
21
|
-
.option("--session <id>", "session id")
|
|
22
|
-
.action(async (promptParts, options) => {
|
|
23
|
-
const ctx = await buildContext()
|
|
24
|
-
printContextWarnings(ctx)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
27
|
+
.option("--base-url <url>", "provider base url override")
|
|
28
|
+
.option("--api-key-env <name>", "api key env override")
|
|
29
|
+
.option("--max-iterations <n>", "longagent max iterations (0 = unlimited)")
|
|
30
|
+
.option("--session <id>", "session id")
|
|
31
|
+
.action(async (promptParts, options) => {
|
|
32
|
+
const ctx = await buildContext()
|
|
33
|
+
printContextWarnings(ctx)
|
|
34
|
+
PermissionEngine.setTrusted(ctx.trustState?.trusted !== false)
|
|
35
|
+
let prompt = promptParts.join(" ").trim()
|
|
36
|
+
if (prompt.startsWith("/")) {
|
|
37
|
+
const commands = await loadCustomCommands(process.cwd())
|
|
38
|
+
const [name, ...argTokens] = prompt.slice(1).split(/\s+/)
|
|
39
|
+
const custom = commands.find((item) => item.name === name)
|
|
40
|
+
if (custom) {
|
|
41
|
+
const args = argTokens.join(" ").trim()
|
|
42
|
+
prompt = applyCommandTemplate(custom.template, args, {
|
|
43
|
+
path: process.cwd()
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const mode = resolveMode(options.mode)
|
|
49
|
+
const providerType = options.providerType ?? ctx.configState.config.provider.default
|
|
50
|
+
const providerDefaults = ctx.configState.config.provider[providerType]
|
|
51
|
+
if (!providerDefaults) {
|
|
52
|
+
throw new Error(`unknown provider type: ${providerType}`)
|
|
53
|
+
}
|
|
54
|
+
const model = options.model ?? providerDefaults.default_model
|
|
55
|
+
const sessionId = options.session || newSessionId()
|
|
56
|
+
|
|
57
|
+
await ToolRegistry.initialize({
|
|
58
|
+
config: ctx.configState.config,
|
|
59
|
+
cwd: process.cwd()
|
|
60
|
+
})
|
|
61
|
+
await SkillRegistry.initialize(ctx.configState.config, process.cwd())
|
|
62
|
+
|
|
63
|
+
await initHookBus()
|
|
64
|
+
const chatParams = await HookBus.chatParams({
|
|
65
|
+
prompt,
|
|
66
|
+
mode,
|
|
67
|
+
model,
|
|
68
|
+
providerType,
|
|
69
|
+
sessionId,
|
|
70
|
+
baseUrl: options.baseUrl ?? null,
|
|
71
|
+
apiKeyEnv: options.apiKeyEnv ?? null
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const routedMode = resolveChatExecutionMode(chatParams.prompt ?? prompt, chatParams.mode ?? mode)
|
|
75
|
+
const effectiveMode = routedMode.effectiveMode
|
|
76
|
+
const effectiveExplanation = routedMode.route.explanation || routedMode.route.reason
|
|
77
|
+
const requestedContract = getPublicModeContract(routedMode.requestedMode)
|
|
78
|
+
const effectiveContract = routedMode.effectiveContract || getPublicModeContract(effectiveMode)
|
|
79
|
+
ensureEventSinks()
|
|
80
|
+
await emitRouteDecisionEvent({
|
|
81
|
+
sessionId,
|
|
82
|
+
source: "chat",
|
|
83
|
+
requestedMode: routedMode.requestedMode,
|
|
84
|
+
route: routedMode.route,
|
|
85
|
+
prompt: chatParams.prompt ?? prompt
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
if (routedMode.route.changed) {
|
|
89
|
+
console.log(`mode routed: ${routedMode.requestedMode} -> ${effectiveMode} (${effectiveExplanation})`)
|
|
90
|
+
} else if (routedMode.route.forced && routedMode.route.suggestion) {
|
|
91
|
+
console.log(`mode kept: ${effectiveMode} (${effectiveExplanation}; suggested ${routedMode.route.suggestion})`)
|
|
92
|
+
} else if (routedMode.route.suggestion === "longagent" && (routedMode.requestedMode === "assistant" || routedMode.requestedMode === "agent")) {
|
|
93
|
+
console.log(`mode note: ${effectiveMode} (${effectiveExplanation}; consider --mode longagent)`)
|
|
94
|
+
} else {
|
|
95
|
+
console.log(`mode: ${effectiveMode} (${effectiveExplanation})`)
|
|
96
|
+
}
|
|
97
|
+
if (routedMode.requestedMode !== effectiveMode) {
|
|
98
|
+
console.log(`requested lane: ${requestedContract.summary}`)
|
|
99
|
+
}
|
|
100
|
+
console.log(`effective lane: ${formatPublicModeSummary(effectiveMode)}`)
|
|
101
|
+
console.log(`lane guarantee: ${effectiveContract.guarantee}`)
|
|
102
|
+
console.log(`route summary: ${summarizeRouteDecision(routedMode.route)}`)
|
|
103
|
+
|
|
63
104
|
const result = await executeTurn({
|
|
64
105
|
prompt: chatParams.prompt ?? prompt,
|
|
65
|
-
mode:
|
|
106
|
+
mode: effectiveMode,
|
|
66
107
|
model: chatParams.model ?? model,
|
|
67
108
|
sessionId,
|
|
68
109
|
configState: ctx.configState,
|
|
@@ -74,41 +115,41 @@ export function createChatCommand() {
|
|
|
74
115
|
? { write: (chunk) => process.stdout.write(String(chunk || "")) }
|
|
75
116
|
: null
|
|
76
117
|
})
|
|
77
|
-
|
|
118
|
+
|
|
78
119
|
const status = renderStatusBar({
|
|
79
|
-
mode,
|
|
120
|
+
mode: effectiveMode,
|
|
80
121
|
model: result.model,
|
|
81
|
-
permission: ctx.configState.config.permission.default_policy,
|
|
122
|
+
permission: ctx.configState.config.permission.mode || ctx.configState.config.permission.default_policy,
|
|
82
123
|
tokenMeter: result.tokenMeter,
|
|
83
|
-
aggregation: ctx.configState.config.usage.aggregation,
|
|
84
|
-
cost: result.cost,
|
|
124
|
+
aggregation: ctx.configState.config.usage.aggregation,
|
|
125
|
+
cost: result.cost,
|
|
85
126
|
showCost: ctx.configState.config.ui.status.show_cost,
|
|
86
127
|
showTokenMeter: ctx.configState.config.ui.status.show_token_meter,
|
|
87
128
|
theme: ctx.themeState.theme,
|
|
88
129
|
layout: ctx.configState.config.ui.layout,
|
|
89
|
-
longagentState:
|
|
130
|
+
longagentState: effectiveMode === "longagent" ? result.longagent : null
|
|
90
131
|
})
|
|
91
|
-
|
|
132
|
+
|
|
92
133
|
console.log(status)
|
|
93
134
|
console.log("")
|
|
94
135
|
const streamEnabled = ctx.configState.config.provider[chatParams.providerType ?? providerType]?.stream !== false
|
|
95
136
|
if (!streamEnabled || !result.emittedText) {
|
|
96
137
|
console.log(result.reply)
|
|
97
138
|
}
|
|
98
|
-
console.log("")
|
|
99
|
-
if (result.toolEvents.length) {
|
|
100
|
-
console.log(`tool events: ${result.toolEvents.length}`)
|
|
101
|
-
}
|
|
102
|
-
if (result.pricingWarnings.length) {
|
|
103
|
-
for (const warning of result.pricingWarnings) {
|
|
104
|
-
console.log(`pricing warning: ${warning}`)
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
if (result.budgetWarnings.length) {
|
|
108
|
-
for (const warning of result.budgetWarnings) {
|
|
109
|
-
console.log(`budget warning: ${warning}`)
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
console.log(`session: ${sessionId}`)
|
|
113
|
-
})
|
|
114
|
-
}
|
|
139
|
+
console.log("")
|
|
140
|
+
if (result.toolEvents.length) {
|
|
141
|
+
console.log(`tool events: ${result.toolEvents.length}`)
|
|
142
|
+
}
|
|
143
|
+
if (result.pricingWarnings.length) {
|
|
144
|
+
for (const warning of result.pricingWarnings) {
|
|
145
|
+
console.log(`pricing warning: ${warning}`)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (result.budgetWarnings.length) {
|
|
149
|
+
for (const warning of result.budgetWarnings) {
|
|
150
|
+
console.log(`budget warning: ${warning}`)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
console.log(`session: ${sessionId}`)
|
|
154
|
+
})
|
|
155
|
+
}
|
package/src/commands/config.mjs
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises"
|
|
2
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises"
|
|
3
3
|
import { Command } from "commander"
|
|
4
4
|
import YAML from "yaml"
|
|
5
5
|
import { importConfig } from "../config/import-config.mjs"
|
|
6
6
|
import { validateConfig } from "../config/schema.mjs"
|
|
7
|
+
import { loadConfig } from "../config/load-config.mjs"
|
|
8
|
+
import { DEFAULT_CONFIG } from "../config/defaults.mjs"
|
|
9
|
+
import { projectConfigCandidates } from "../storage/paths.mjs"
|
|
7
10
|
|
|
8
11
|
function parseInput(file, raw) {
|
|
9
12
|
if (file.endsWith(".json")) return JSON.parse(raw)
|
|
@@ -15,6 +18,72 @@ function stringifyOutput(file, data) {
|
|
|
15
18
|
return YAML.stringify(data)
|
|
16
19
|
}
|
|
17
20
|
|
|
21
|
+
function getByPath(obj, keyPath) {
|
|
22
|
+
const keys = keyPath.split(".")
|
|
23
|
+
let current = obj
|
|
24
|
+
for (const key of keys) {
|
|
25
|
+
if (current == null || typeof current !== "object") return undefined
|
|
26
|
+
current = current[key]
|
|
27
|
+
}
|
|
28
|
+
return current
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"])
|
|
32
|
+
|
|
33
|
+
function setByPath(obj, keyPath, value) {
|
|
34
|
+
const keys = keyPath.split(".")
|
|
35
|
+
if (keys.some(k => FORBIDDEN_KEYS.has(k))) {
|
|
36
|
+
throw new Error(`forbidden key in path: ${keyPath}`)
|
|
37
|
+
}
|
|
38
|
+
let current = obj
|
|
39
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
40
|
+
const key = keys[i]
|
|
41
|
+
if (current[key] == null || typeof current[key] !== "object") {
|
|
42
|
+
current[key] = {}
|
|
43
|
+
}
|
|
44
|
+
current = current[key]
|
|
45
|
+
}
|
|
46
|
+
current[keys[keys.length - 1]] = value
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function coerceValue(raw) {
|
|
50
|
+
if (raw === "true") return true
|
|
51
|
+
if (raw === "false") return false
|
|
52
|
+
if (raw === "null") return null
|
|
53
|
+
if (raw !== "" && /^-?\d+(\.\d+)?$/.test(raw)) return Number(raw)
|
|
54
|
+
return raw
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isNonDefault(merged, defaults, keyPath) {
|
|
58
|
+
const mergedVal = getByPath(merged, keyPath)
|
|
59
|
+
const defaultVal = getByPath(defaults, keyPath)
|
|
60
|
+
return JSON.stringify(mergedVal) !== JSON.stringify(defaultVal)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function printTree(obj, defaults, prefix = "", indent = "") {
|
|
64
|
+
if (obj == null || typeof obj !== "object") {
|
|
65
|
+
const marker = isNonDefault(obj, defaults, prefix) ? " *" : ""
|
|
66
|
+
console.log(`${indent}${formatValue(obj)}${marker}`)
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
70
|
+
const fullPath = prefix ? `${prefix}.${key}` : key
|
|
71
|
+
if (value != null && typeof value === "object" && !Array.isArray(value)) {
|
|
72
|
+
console.log(`${indent}${key}:`)
|
|
73
|
+
printTree(value, defaults, fullPath, indent + " ")
|
|
74
|
+
} else {
|
|
75
|
+
const marker = isNonDefault(value, getByPath(defaults, fullPath), "") ? " *" : ""
|
|
76
|
+
console.log(`${indent}${key}: ${formatValue(value)}${marker}`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatValue(v) {
|
|
82
|
+
if (v === null) return "null"
|
|
83
|
+
if (Array.isArray(v)) return JSON.stringify(v)
|
|
84
|
+
return String(v)
|
|
85
|
+
}
|
|
86
|
+
|
|
18
87
|
export function createConfigCommand() {
|
|
19
88
|
const cmd = new Command("config").description("manage kkcode config")
|
|
20
89
|
|
|
@@ -40,5 +109,83 @@ export function createConfigCommand() {
|
|
|
40
109
|
console.log(`imported config written: ${to}`)
|
|
41
110
|
})
|
|
42
111
|
|
|
112
|
+
cmd
|
|
113
|
+
.command("show")
|
|
114
|
+
.description("show current effective config (merged)")
|
|
115
|
+
.option("--section <name>", "show only a specific top-level section")
|
|
116
|
+
.action(async (options) => {
|
|
117
|
+
const { config, source } = await loadConfig(process.cwd())
|
|
118
|
+
console.log("# effective config (user + project + defaults)")
|
|
119
|
+
if (source.userPath) console.log(`# user: ${source.userPath}`)
|
|
120
|
+
if (source.projectPath) console.log(`# project: ${source.projectPath}`)
|
|
121
|
+
console.log("# entries marked with * differ from defaults\n")
|
|
122
|
+
const target = options.section ? { [options.section]: config[options.section] } : config
|
|
123
|
+
if (options.section && config[options.section] === undefined) {
|
|
124
|
+
console.error(`unknown section: ${options.section}`)
|
|
125
|
+
process.exitCode = 1
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
printTree(target, DEFAULT_CONFIG)
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
cmd
|
|
132
|
+
.command("get <key>")
|
|
133
|
+
.description("get a config value by dot-path (e.g. provider.default)")
|
|
134
|
+
.action(async (key) => {
|
|
135
|
+
const { config } = await loadConfig(process.cwd())
|
|
136
|
+
const value = getByPath(config, key)
|
|
137
|
+
if (value === undefined) {
|
|
138
|
+
console.error(`key not found: ${key}`)
|
|
139
|
+
process.exitCode = 1
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
if (value != null && typeof value === "object") {
|
|
143
|
+
console.log(YAML.stringify(value).trimEnd())
|
|
144
|
+
} else {
|
|
145
|
+
console.log(formatValue(value))
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
cmd
|
|
150
|
+
.command("set <key> <value>")
|
|
151
|
+
.description("set a config value in project config (e.g. provider.default anthropic)")
|
|
152
|
+
.action(async (key, rawValue) => {
|
|
153
|
+
const cwd = process.cwd()
|
|
154
|
+
const candidates = projectConfigCandidates(cwd)
|
|
155
|
+
let configPath = null
|
|
156
|
+
for (const c of candidates) {
|
|
157
|
+
try {
|
|
158
|
+
await readFile(c, "utf8")
|
|
159
|
+
configPath = c
|
|
160
|
+
break
|
|
161
|
+
} catch { /* not found */ }
|
|
162
|
+
}
|
|
163
|
+
if (!configPath) {
|
|
164
|
+
configPath = candidates[0]
|
|
165
|
+
await mkdir(path.dirname(configPath), { recursive: true })
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
let existing = {}
|
|
169
|
+
try {
|
|
170
|
+
const raw = await readFile(configPath, "utf8")
|
|
171
|
+
existing = parseInput(configPath, raw) || {}
|
|
172
|
+
} catch { /* new file */ }
|
|
173
|
+
|
|
174
|
+
const value = coerceValue(rawValue)
|
|
175
|
+
setByPath(existing, key, value)
|
|
176
|
+
|
|
177
|
+
const check = validateConfig(existing)
|
|
178
|
+
if (!check.valid) {
|
|
179
|
+
console.error("resulting config is invalid:")
|
|
180
|
+
for (const e of check.errors) console.error(` - ${e}`)
|
|
181
|
+
process.exitCode = 1
|
|
182
|
+
return
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await writeFile(configPath, stringifyOutput(configPath, existing), "utf8")
|
|
186
|
+
console.log(`${key} = ${formatValue(value)}`)
|
|
187
|
+
console.log(`written: ${configPath}`)
|
|
188
|
+
})
|
|
189
|
+
|
|
43
190
|
return cmd
|
|
44
191
|
}
|
package/src/commands/doctor.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { auditStats } from "../storage/audit-store.mjs"
|
|
|
8
8
|
import { fsckSessionStore, flushNow } from "../session/store.mjs"
|
|
9
9
|
import { BackgroundManager } from "../orchestration/background-manager.mjs"
|
|
10
10
|
import { McpRegistry } from "../mcp/registry.mjs"
|
|
11
|
+
import { SkillRegistry } from "../skill/registry.mjs"
|
|
11
12
|
|
|
12
13
|
const exec = promisify(execCb)
|
|
13
14
|
|
|
@@ -69,8 +70,19 @@ async function buildDoctorReport() {
|
|
|
69
70
|
const storage = await fsckSessionStore()
|
|
70
71
|
const backgroundTasks = await BackgroundManager.list()
|
|
71
72
|
await McpRegistry.initialize(config)
|
|
73
|
+
await SkillRegistry.initialize({ ...config, skills: { ...(config.skills || {}), auto_seed: false } }, process.cwd())
|
|
72
74
|
const mcpSnapshot = McpRegistry.healthSnapshot()
|
|
73
75
|
const mcpHealthy = mcpSnapshot.filter((item) => item.ok).length
|
|
76
|
+
const skillList = SkillRegistry.list()
|
|
77
|
+
const skillSummary = {
|
|
78
|
+
enabled: config.skills?.enabled !== false,
|
|
79
|
+
autoSeed: config.skills?.auto_seed !== false,
|
|
80
|
+
total: skillList.length,
|
|
81
|
+
template: skillList.filter((s) => s.type === "template").length,
|
|
82
|
+
skillMd: skillList.filter((s) => s.type === "skill_md").length,
|
|
83
|
+
mcpPrompt: skillList.filter((s) => s.type === "mcp_prompt").length,
|
|
84
|
+
programmable: skillList.filter((s) => s.type === "mjs").length
|
|
85
|
+
}
|
|
74
86
|
|
|
75
87
|
return {
|
|
76
88
|
ok: storage.ok,
|
|
@@ -94,6 +106,7 @@ async function buildDoctorReport() {
|
|
|
94
106
|
unhealthy: mcpSnapshot.length - mcpHealthy,
|
|
95
107
|
servers: mcpSnapshot
|
|
96
108
|
},
|
|
109
|
+
skills: skillSummary,
|
|
97
110
|
storage: {
|
|
98
111
|
sessions: storage,
|
|
99
112
|
eventLog: events,
|
|
@@ -120,11 +133,18 @@ function printTextReport(report, themeWarnings = []) {
|
|
|
120
133
|
}
|
|
121
134
|
for (const p of report.runtime.providersConfigured) {
|
|
122
135
|
console.log(
|
|
123
|
-
`provider:${p.name} type=${p.type} model=${p.model || "?"}
|
|
136
|
+
`provider:${p.name} type=${p.type} model=${p.model || "?"} env=${p.apiKeyEnv || "-"} (${p.apiKeyConfigured ? "set" : "missing"})`
|
|
124
137
|
)
|
|
125
138
|
}
|
|
126
139
|
console.log(`check node=${report.checks.node ? "ok" : "missing"} rg=${report.checks.rg ? "ok" : "missing"} git=${report.checks.git ? "ok" : "missing"}`)
|
|
127
140
|
console.log(`mcp: configured=${report.mcp.configured} healthy=${report.mcp.healthy} unhealthy=${report.mcp.unhealthy}`)
|
|
141
|
+
console.log(`skills: total=${report.skills.total} template=${report.skills.template + report.skills.skillMd} mcp=${report.skills.mcpPrompt} programmable=${report.skills.programmable}`)
|
|
142
|
+
if (report.mcp.configured === 0) {
|
|
143
|
+
console.log(" mcp quickstart: kkcode mcp init --project --with-skills")
|
|
144
|
+
}
|
|
145
|
+
if (report.skills.total === 0) {
|
|
146
|
+
console.log(" skills quickstart: kkcode skill init --project")
|
|
147
|
+
}
|
|
128
148
|
console.log(`sessions: ok=${report.storage.sessions.ok} index=${report.storage.sessions.sessionsInIndex} files=${report.storage.sessions.filesOnDisk}`)
|
|
129
149
|
console.log(`events: active=${report.storage.eventLog.activeBytes} rotated=${report.storage.eventLog.rotatedFiles}`)
|
|
130
150
|
console.log(`audit: total=${report.storage.audit.total} error1h=${report.storage.audit.error1h} error24h=${report.storage.audit.error24h}`)
|
|
@@ -138,11 +158,15 @@ export function createDoctorCommand() {
|
|
|
138
158
|
.description("run environment diagnostics")
|
|
139
159
|
.option("--json", "print structured diagnostics", false)
|
|
140
160
|
.action(async (options) => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
161
|
+
try {
|
|
162
|
+
const report = await buildDoctorReport()
|
|
163
|
+
if (options.json) {
|
|
164
|
+
console.log(JSON.stringify(report, null, 2))
|
|
165
|
+
return
|
|
166
|
+
}
|
|
167
|
+
printTextReport(report, report.themeWarnings || [])
|
|
168
|
+
} finally {
|
|
169
|
+
McpRegistry.shutdown()
|
|
145
170
|
}
|
|
146
|
-
printTextReport(report, report.themeWarnings || [])
|
|
147
171
|
})
|
|
148
172
|
}
|
package/src/commands/init.mjs
CHANGED
|
@@ -35,6 +35,23 @@ const PROVIDER_DEFAULTS = {
|
|
|
35
35
|
"openai-compatible": { base_url: "", api_key_env: "", default_model: "" }
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
// Auto-detect available providers by checking environment variables
|
|
39
|
+
function detectProvider() {
|
|
40
|
+
const registered = new Set(listProviders())
|
|
41
|
+
const checks = [
|
|
42
|
+
{ provider: "anthropic", env: "ANTHROPIC_API_KEY" },
|
|
43
|
+
{ provider: "openai", env: "OPENAI_API_KEY" }
|
|
44
|
+
]
|
|
45
|
+
const available = []
|
|
46
|
+
for (const { provider, env } of checks) {
|
|
47
|
+
if (process.env[env] && registered.has(provider)) available.push(provider)
|
|
48
|
+
}
|
|
49
|
+
// Prefer anthropic if both are set, otherwise first available, fallback to openai
|
|
50
|
+
if (available.includes("anthropic")) return { provider: "anthropic", detected: available }
|
|
51
|
+
if (available.length) return { provider: available[0], detected: available }
|
|
52
|
+
return { provider: "openai", detected: [] }
|
|
53
|
+
}
|
|
54
|
+
|
|
38
55
|
function buildConfig(answers) {
|
|
39
56
|
const config = {
|
|
40
57
|
provider: {
|
|
@@ -56,7 +73,11 @@ function buildConfig(answers) {
|
|
|
56
73
|
|
|
57
74
|
async function runInteractive(rl) {
|
|
58
75
|
const providers = listProviders()
|
|
59
|
-
const provider
|
|
76
|
+
const { provider: detectedProvider, detected } = detectProvider()
|
|
77
|
+
if (detected.length) {
|
|
78
|
+
console.log(`\nauto-detected API keys: ${detected.join(", ")}`)
|
|
79
|
+
}
|
|
80
|
+
const provider = await askChoice(rl, "select default provider:", providers, detectedProvider)
|
|
60
81
|
const defaults = PROVIDER_DEFAULTS[provider] || {}
|
|
61
82
|
|
|
62
83
|
let baseUrl = ""
|
|
@@ -102,11 +123,16 @@ export function createInitCommand() {
|
|
|
102
123
|
|
|
103
124
|
let answers
|
|
104
125
|
if (options.yes) {
|
|
126
|
+
const { provider, detected } = detectProvider()
|
|
127
|
+
const defaults = PROVIDER_DEFAULTS[provider] || {}
|
|
128
|
+
if (detected.length) {
|
|
129
|
+
console.log(`auto-detected: ${detected.join(", ")} → using ${provider}`)
|
|
130
|
+
}
|
|
105
131
|
answers = {
|
|
106
|
-
provider
|
|
107
|
-
baseUrl: "",
|
|
108
|
-
apiKeyEnv: "
|
|
109
|
-
model:
|
|
132
|
+
provider,
|
|
133
|
+
baseUrl: defaults.base_url || "",
|
|
134
|
+
apiKeyEnv: defaults.api_key_env || "",
|
|
135
|
+
model: defaults.default_model || "",
|
|
110
136
|
permissionPolicy: "ask"
|
|
111
137
|
}
|
|
112
138
|
} else {
|
|
@@ -134,7 +160,7 @@ export function createInitCommand() {
|
|
|
134
160
|
const defaultProvider = config.provider.default
|
|
135
161
|
const providerCfg = config.provider[defaultProvider] || {}
|
|
136
162
|
if (providerCfg.api_key_env && !process.env[providerCfg.api_key_env]) {
|
|
137
|
-
console.log(`note: ${providerCfg.api_key_env} is not set
|
|
163
|
+
console.log(`note: environment variable ${providerCfg.api_key_env} is not set`)
|
|
138
164
|
}
|
|
139
165
|
console.log("run 'kkcode doctor' for full diagnostics")
|
|
140
166
|
})
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from "commander"
|
|
2
|
+
import { readFile } from "node:fs/promises"
|
|
2
3
|
import { LongAgentManager } from "../orchestration/longagent-manager.mjs"
|
|
4
|
+
import { loadConfig } from "../config/load-config.mjs"
|
|
5
|
+
import { eventLogPath } from "../storage/paths.mjs"
|
|
3
6
|
|
|
4
7
|
export function createLongagentCommand() {
|
|
5
8
|
const cmd = new Command("longagent").description("manage longagent sessions")
|
|
@@ -96,5 +99,119 @@ export function createLongagentCommand() {
|
|
|
96
99
|
console.log(`stage retry requested: ${options.stage} (session=${out.sessionId})`)
|
|
97
100
|
})
|
|
98
101
|
|
|
102
|
+
cmd
|
|
103
|
+
.command("logs")
|
|
104
|
+
.description("view longagent event logs")
|
|
105
|
+
.option("--session <id>", "filter by session id")
|
|
106
|
+
.option("-n, --lines <n>", "number of recent lines", "50")
|
|
107
|
+
.option("--json", "output raw JSON lines")
|
|
108
|
+
.action(async (options) => {
|
|
109
|
+
const logFile = eventLogPath()
|
|
110
|
+
let raw
|
|
111
|
+
try {
|
|
112
|
+
raw = await readFile(logFile, "utf8")
|
|
113
|
+
} catch {
|
|
114
|
+
console.error(`no event log found at ${logFile}`)
|
|
115
|
+
process.exitCode = 1
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
const allLines = raw.trim().split("\n").filter(Boolean)
|
|
119
|
+
let events = allLines.map((line) => {
|
|
120
|
+
try { return JSON.parse(line) } catch { return null }
|
|
121
|
+
}).filter(Boolean)
|
|
122
|
+
|
|
123
|
+
if (options.session) {
|
|
124
|
+
events = events.filter((e) => e.sessionId === options.session)
|
|
125
|
+
}
|
|
126
|
+
// filter longagent-related events
|
|
127
|
+
events = events.filter((e) =>
|
|
128
|
+
String(e.type || "").includes("longagent") ||
|
|
129
|
+
String(e.type || "").includes("stage") ||
|
|
130
|
+
String(e.type || "").includes("task")
|
|
131
|
+
)
|
|
132
|
+
const limit = Math.max(1, Number(options.lines) || 50)
|
|
133
|
+
events = events.slice(-limit)
|
|
134
|
+
|
|
135
|
+
if (options.json) {
|
|
136
|
+
for (const e of events) console.log(JSON.stringify(e))
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
if (!events.length) {
|
|
140
|
+
console.log("no longagent events found")
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
for (const e of events) {
|
|
144
|
+
const ts = e.timestamp ? new Date(e.timestamp).toISOString().slice(11, 19) : "??:??:??"
|
|
145
|
+
const sid = e.sessionId ? e.sessionId.slice(0, 12) : "????????????"
|
|
146
|
+
const payload = e.payload ? JSON.stringify(e.payload).slice(0, 120) : ""
|
|
147
|
+
console.log(`${ts} [${sid}] ${e.type || "unknown"}${payload ? " " + payload : ""}`)
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
cmd
|
|
152
|
+
.command("config")
|
|
153
|
+
.description("show effective longagent configuration")
|
|
154
|
+
.option("--full", "show full merged config (not just longagent section)")
|
|
155
|
+
.action(async (options) => {
|
|
156
|
+
const configState = await loadConfig()
|
|
157
|
+
if (options.full) {
|
|
158
|
+
console.log(JSON.stringify(configState.config, null, 2))
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
const la = configState.config.agent?.longagent || {}
|
|
162
|
+
console.log("## longagent config")
|
|
163
|
+
console.log(JSON.stringify(la, null, 2))
|
|
164
|
+
console.log("\n## sources")
|
|
165
|
+
console.log(` user: ${configState.source.userPath || "(none)"}`)
|
|
166
|
+
console.log(` project: ${configState.source.projectPath || "(none)"}`)
|
|
167
|
+
console.log(` env: ${configState.source.envPath || "(none)"}`)
|
|
168
|
+
if (configState.errors.length) {
|
|
169
|
+
console.log("\n## errors")
|
|
170
|
+
for (const e of configState.errors) console.log(` - ${e}`)
|
|
171
|
+
}
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
cmd
|
|
175
|
+
.command("start")
|
|
176
|
+
.description("launch a longagent session with a prompt")
|
|
177
|
+
.argument("<prompt>", "task description for longagent")
|
|
178
|
+
.option("--model <model>", "override model")
|
|
179
|
+
.option("--provider <type>", "override provider type")
|
|
180
|
+
.option("--max-iterations <n>", "max iterations (0=unlimited)", "0")
|
|
181
|
+
.action(async (prompt, options) => {
|
|
182
|
+
const configState = await loadConfig()
|
|
183
|
+
const providerKey = options.provider || configState.config.provider.default
|
|
184
|
+
const providerConf = configState.config.provider[providerKey] || {}
|
|
185
|
+
const model = options.model || providerConf.default_model
|
|
186
|
+
if (!model) {
|
|
187
|
+
console.error(`no model configured for provider "${providerKey}"`)
|
|
188
|
+
process.exitCode = 1
|
|
189
|
+
return
|
|
190
|
+
}
|
|
191
|
+
const { executeTurn } = await import("../session/engine.mjs")
|
|
192
|
+
const { newSessionId } = await import("../session/engine.mjs")
|
|
193
|
+
const sessionId = newSessionId()
|
|
194
|
+
console.log(`starting longagent session: ${sessionId}`)
|
|
195
|
+
console.log(`model: ${model}, provider: ${providerKey}`)
|
|
196
|
+
try {
|
|
197
|
+
const result = await executeTurn({
|
|
198
|
+
prompt,
|
|
199
|
+
mode: "longagent",
|
|
200
|
+
model,
|
|
201
|
+
sessionId,
|
|
202
|
+
configState: { config: configState.config, source: configState.source },
|
|
203
|
+
providerType: providerKey,
|
|
204
|
+
baseUrl: providerConf.base_url || null,
|
|
205
|
+
apiKeyEnv: providerConf.api_key_env || null,
|
|
206
|
+
maxIterations: Number(options.maxIterations) || 0,
|
|
207
|
+
output: { write: (t) => process.stdout.write(t) }
|
|
208
|
+
})
|
|
209
|
+
console.log(`\nsession ${sessionId} finished (status: ${result.status || "done"})`)
|
|
210
|
+
} catch (err) {
|
|
211
|
+
console.error(`longagent error: ${err.message}`)
|
|
212
|
+
process.exitCode = 1
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
99
216
|
return cmd
|
|
100
217
|
}
|