@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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises"
|
|
2
|
+
import { dirname } from "node:path"
|
|
3
|
+
import { paint } from "../theme/color.mjs"
|
|
4
|
+
import { resolveMode } from "../session/engine.mjs"
|
|
5
|
+
|
|
6
|
+
export function configuredProviders(config, listProvidersFn) {
|
|
7
|
+
const builtins = new Set(listProvidersFn())
|
|
8
|
+
const out = []
|
|
9
|
+
for (const [name, value] of Object.entries(config.provider || {})) {
|
|
10
|
+
if (name === "default") continue
|
|
11
|
+
if (name === "strict_mode") continue
|
|
12
|
+
if (name === "model_context") continue
|
|
13
|
+
if (!value || typeof value !== "object") continue
|
|
14
|
+
const type = value.type || name
|
|
15
|
+
if (builtins.has(type)) out.push(name)
|
|
16
|
+
}
|
|
17
|
+
return out
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function loadHistoryLines(filePath, size) {
|
|
21
|
+
try {
|
|
22
|
+
const raw = await readFile(filePath, "utf8")
|
|
23
|
+
return raw.split("\n").filter(Boolean).slice(-size)
|
|
24
|
+
} catch {
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function saveHistoryLines(filePath, size, lines) {
|
|
30
|
+
try {
|
|
31
|
+
await mkdir(dirname(filePath), { recursive: true })
|
|
32
|
+
const finalLines = [...lines].slice(-size)
|
|
33
|
+
await writeFile(filePath, finalLines.join("\n") + (finalLines.length ? "\n" : ""), "utf8")
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function clearScreen(output = process.stdout) {
|
|
38
|
+
if (!output?.isTTY) return
|
|
39
|
+
output.write("\x1Bc")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function resolveProviderDefaultModel(config, providerType, fallback = "") {
|
|
43
|
+
return (
|
|
44
|
+
config.provider?.[providerType]?.default_model ||
|
|
45
|
+
config.provider?.[config.provider?.default]?.default_model ||
|
|
46
|
+
fallback
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function createInitialReplState(config, { newSessionIdFn }) {
|
|
51
|
+
const providerType = config.provider.default
|
|
52
|
+
const state = {
|
|
53
|
+
sessionId: newSessionIdFn(),
|
|
54
|
+
mode: resolveMode(config.agent.default_mode),
|
|
55
|
+
providerType,
|
|
56
|
+
model: ""
|
|
57
|
+
}
|
|
58
|
+
state.model = resolveProviderDefaultModel(config, providerType)
|
|
59
|
+
return state
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function collectMcpStatusLines(theme, entries, tools) {
|
|
63
|
+
const lines = []
|
|
64
|
+
for (const entry of entries) {
|
|
65
|
+
if (entry.ok) {
|
|
66
|
+
const toolCount = tools.filter((tool) => tool.server === entry.name).length
|
|
67
|
+
lines.push(
|
|
68
|
+
paint(` mcp ✓ ${entry.name}`, theme.semantic.success) +
|
|
69
|
+
paint(` (${toolCount} tools, ${entry.transport})`, theme.base.muted)
|
|
70
|
+
)
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
const reason = entry.error || entry.reason || "unknown"
|
|
74
|
+
lines.push(
|
|
75
|
+
paint(` mcp ✗ ${entry.name}`, theme.semantic.error) +
|
|
76
|
+
paint(` ${reason}`, theme.base.muted)
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
return lines
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function startSplash({
|
|
83
|
+
paintFn = paint,
|
|
84
|
+
stdout = process.stdout,
|
|
85
|
+
version = "v0.1.27"
|
|
86
|
+
} = {}) {
|
|
87
|
+
if (!stdout?.isTTY) return { update() {}, stop() {} }
|
|
88
|
+
|
|
89
|
+
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
|
|
90
|
+
const logo = [
|
|
91
|
+
" ██╗ ██╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ ",
|
|
92
|
+
" ██║ ██╔╝ ██║ ██╔╝ ██╔════╝ ██╔═══██╗ ██╔══██╗ ██╔════╝ ",
|
|
93
|
+
" █████╔╝ █████╔╝ ██║ ██║ ██║ ██║ ██║ █████╗ ",
|
|
94
|
+
" ██╔═██╗ ██╔═██╗ ██║ ██║ ██║ ██║ ██║ ██╔══╝ ",
|
|
95
|
+
" ██║ ██╗ ██║ ██╗ ╚██████╗ ╚██████╔╝ ██████╔╝ ███████╗ ",
|
|
96
|
+
" ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ "
|
|
97
|
+
]
|
|
98
|
+
const tagline = "CLI Personal Assistant"
|
|
99
|
+
const wave = [
|
|
100
|
+
"#4af5f0", "#3de8f5", "#30dbfa", "#38c8ff", "#40b5ff",
|
|
101
|
+
"#58a0ff", "#708bff", "#8876ff", "#a061ff", "#b84cff",
|
|
102
|
+
"#d037ff", "#e828f0", "#f034d0", "#f040b0", "#f04c90",
|
|
103
|
+
"#f040b0", "#f034d0", "#e828f0", "#d037ff", "#b84cff",
|
|
104
|
+
"#a061ff", "#8876ff", "#708bff", "#58a0ff", "#40b5ff",
|
|
105
|
+
"#38c8ff", "#30dbfa", "#3de8f5"
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
function charColor(ch, hex) {
|
|
109
|
+
if (ch === " " || ch === "\n") return ch
|
|
110
|
+
const r = parseInt(hex.slice(1, 3), 16)
|
|
111
|
+
const g = parseInt(hex.slice(3, 5), 16)
|
|
112
|
+
const b = parseInt(hex.slice(5, 7), 16)
|
|
113
|
+
return `\x1b[1;38;2;${r};${g};${b}m${ch}\x1b[0m`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let tick = 0
|
|
117
|
+
let status = "loading config..."
|
|
118
|
+
let steps = []
|
|
119
|
+
let revealChars = 0
|
|
120
|
+
const totalChars = logo[0].length
|
|
121
|
+
const revealSpeed = 3
|
|
122
|
+
|
|
123
|
+
function render() {
|
|
124
|
+
const cols = stdout.columns || 80
|
|
125
|
+
const rows = stdout.rows || 24
|
|
126
|
+
const lines = []
|
|
127
|
+
const contentHeight = logo.length + 4 + steps.length + 2
|
|
128
|
+
const topPad = Math.max(0, Math.floor((rows - contentHeight) / 2))
|
|
129
|
+
for (let i = 0; i < topPad; i++) lines.push("")
|
|
130
|
+
|
|
131
|
+
const visible = Math.min(revealChars, totalChars)
|
|
132
|
+
for (let row = 0; row < logo.length; row++) {
|
|
133
|
+
const line = logo[row]
|
|
134
|
+
const pad = Math.max(0, Math.floor((cols - line.length) / 2))
|
|
135
|
+
let out = " ".repeat(pad)
|
|
136
|
+
for (let col = 0; col < line.length; col++) {
|
|
137
|
+
if (col >= visible) {
|
|
138
|
+
out += " "
|
|
139
|
+
continue
|
|
140
|
+
}
|
|
141
|
+
const ch = line[col]
|
|
142
|
+
const waveIdx = (col + tick * 2 + row * 3) % wave.length
|
|
143
|
+
out += charColor(ch, wave[waveIdx])
|
|
144
|
+
}
|
|
145
|
+
lines.push(out)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const tagFull = `${tagline} · ${version}`
|
|
149
|
+
if (visible >= totalChars) {
|
|
150
|
+
const tagPad = Math.max(0, Math.floor((cols - tagFull.length) / 2))
|
|
151
|
+
const tagAlpha = Math.min(1, (revealChars - totalChars) / 20)
|
|
152
|
+
const brightness = Math.round(100 + 155 * tagAlpha)
|
|
153
|
+
const hex = brightness.toString(16).padStart(2, "0")
|
|
154
|
+
const tagHex = `#${hex}${hex}${hex}`
|
|
155
|
+
lines.push(" ".repeat(tagPad) + paintFn(tagFull, tagHex, { dim: tagAlpha < 0.5 }))
|
|
156
|
+
} else {
|
|
157
|
+
lines.push("")
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (visible >= totalChars) {
|
|
161
|
+
const barWidth = Math.min(40, cols - 4)
|
|
162
|
+
const barPad = Math.max(0, Math.floor((cols - barWidth) / 2))
|
|
163
|
+
let bar = ""
|
|
164
|
+
for (let i = 0; i < barWidth; i++) {
|
|
165
|
+
const ci = (i + tick) % wave.length
|
|
166
|
+
bar += charColor("─", wave[ci])
|
|
167
|
+
}
|
|
168
|
+
lines.push(" ".repeat(barPad) + bar)
|
|
169
|
+
} else {
|
|
170
|
+
lines.push("")
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
lines.push("")
|
|
174
|
+
for (const step of steps) {
|
|
175
|
+
const pad = Math.max(0, Math.floor((cols - step.length - 4) / 2))
|
|
176
|
+
lines.push(" ".repeat(pad) + paintFn(` ✓ ${step}`, "#3fd487"))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const spinChar = frames[tick % frames.length]
|
|
180
|
+
const spinLine = `${spinChar} ${status}`
|
|
181
|
+
const spinPad = Math.max(0, Math.floor((cols - spinLine.length - 2) / 2))
|
|
182
|
+
lines.push(" ".repeat(spinPad) + paintFn(` ${spinLine}`, "#6ec1ff", { bold: true }))
|
|
183
|
+
|
|
184
|
+
stdout.write("\x1B[?25l")
|
|
185
|
+
stdout.write("\x1Bc")
|
|
186
|
+
stdout.write(lines.join("\n"))
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
render()
|
|
190
|
+
const timer = setInterval(() => {
|
|
191
|
+
tick += 1
|
|
192
|
+
if (revealChars < totalChars + 30) revealChars += revealSpeed
|
|
193
|
+
render()
|
|
194
|
+
}, 50)
|
|
195
|
+
|
|
196
|
+
return {
|
|
197
|
+
update(text) {
|
|
198
|
+
steps.push(status.replace("...", ""))
|
|
199
|
+
status = text
|
|
200
|
+
render()
|
|
201
|
+
},
|
|
202
|
+
stop() {
|
|
203
|
+
clearInterval(timer)
|
|
204
|
+
stdout.write("\x1B[?25h")
|
|
205
|
+
stdout.write("\x1Bc")
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
export const QUESTION_SKIPPED = "(skipped)"
|
|
2
|
+
|
|
3
|
+
export function activateNextQuestionState(queue = []) {
|
|
4
|
+
if (!queue.length) {
|
|
5
|
+
return {
|
|
6
|
+
pendingQuestion: null,
|
|
7
|
+
questionIndex: 0,
|
|
8
|
+
questionOptionSelected: 0,
|
|
9
|
+
questionMultiSelected: {},
|
|
10
|
+
questionCustomMode: false,
|
|
11
|
+
questionCustomInput: "",
|
|
12
|
+
questionCustomCursor: 0,
|
|
13
|
+
questionAnswers: {}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const [pendingQuestion, ...rest] = queue
|
|
18
|
+
return {
|
|
19
|
+
queue: rest,
|
|
20
|
+
pendingQuestion,
|
|
21
|
+
questionIndex: 0,
|
|
22
|
+
questionOptionSelected: 0,
|
|
23
|
+
questionMultiSelected: {},
|
|
24
|
+
questionCustomMode: false,
|
|
25
|
+
questionCustomInput: "",
|
|
26
|
+
questionCustomCursor: 0,
|
|
27
|
+
questionAnswers: {}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function commitQuestionAnswer(state) {
|
|
32
|
+
const questions = state.pendingQuestion?.questions || []
|
|
33
|
+
const current = questions[state.questionIndex]
|
|
34
|
+
if (!current) return state
|
|
35
|
+
|
|
36
|
+
const nextAnswers = { ...state.questionAnswers }
|
|
37
|
+
if (state.questionCustomMode) {
|
|
38
|
+
nextAnswers[current.id] = state.questionCustomInput || ""
|
|
39
|
+
return {
|
|
40
|
+
...state,
|
|
41
|
+
questionAnswers: nextAnswers,
|
|
42
|
+
questionCustomMode: false,
|
|
43
|
+
questionCustomInput: "",
|
|
44
|
+
questionCustomCursor: 0
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (current.multi) {
|
|
49
|
+
const selected = state.questionMultiSelected[current.id] || new Set()
|
|
50
|
+
const values = [...selected]
|
|
51
|
+
.map((index) => {
|
|
52
|
+
const opt = (current.options || [])[index]
|
|
53
|
+
return opt ? (opt.value || opt.label) : ""
|
|
54
|
+
})
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
nextAnswers[current.id] = values.join(", ")
|
|
57
|
+
return { ...state, questionAnswers: nextAnswers }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const option = (current.options || [])[state.questionOptionSelected]
|
|
61
|
+
if (option) nextAnswers[current.id] = option.value || option.label
|
|
62
|
+
return { ...state, questionAnswers: nextAnswers }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function advanceQuestionState(state) {
|
|
66
|
+
const questions = state.pendingQuestion?.questions || []
|
|
67
|
+
if (state.questionIndex < questions.length - 1) {
|
|
68
|
+
return {
|
|
69
|
+
...state,
|
|
70
|
+
questionIndex: state.questionIndex + 1,
|
|
71
|
+
questionOptionSelected: 0,
|
|
72
|
+
questionCustomMode: false,
|
|
73
|
+
questionCustomInput: "",
|
|
74
|
+
questionCustomCursor: 0
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { ...state, shouldSubmit: true }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function finalizeQuestionAnswers(pendingQuestion, questionAnswers = {}) {
|
|
81
|
+
const answers = { ...questionAnswers }
|
|
82
|
+
const questions = pendingQuestion?.questions || []
|
|
83
|
+
for (const question of questions) {
|
|
84
|
+
if (!(question.id in answers)) answers[question.id] = QUESTION_SKIPPED
|
|
85
|
+
}
|
|
86
|
+
return answers
|
|
87
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export async function collectInput(rl, promptStr) {
|
|
2
|
+
const first = (await rl.question(promptStr)).trim()
|
|
3
|
+
if (!first) return ""
|
|
4
|
+
|
|
5
|
+
if (first === '"""' || first.startsWith('"""')) {
|
|
6
|
+
const lines = []
|
|
7
|
+
if (first !== '"""') lines.push(first.slice(3))
|
|
8
|
+
while (true) {
|
|
9
|
+
const next = await rl.question("... ")
|
|
10
|
+
if (next.trim() === '"""') break
|
|
11
|
+
lines.push(next)
|
|
12
|
+
}
|
|
13
|
+
return lines.join("\n").trim()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (first.endsWith("\\")) {
|
|
17
|
+
const lines = [first.slice(0, -1)]
|
|
18
|
+
while (true) {
|
|
19
|
+
const next = await rl.question("... ")
|
|
20
|
+
if (next.endsWith("\\")) lines.push(next.slice(0, -1))
|
|
21
|
+
else {
|
|
22
|
+
lines.push(next)
|
|
23
|
+
break
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return lines.join("\n").trim()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return first
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function resolveHistoryNavigation(history, historyIndex, keyName) {
|
|
33
|
+
if (!Array.isArray(history) || history.length === 0) {
|
|
34
|
+
return { historyIndex, value: "", changed: false }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (keyName === "up") {
|
|
38
|
+
const nextIndex = Math.max(0, historyIndex - 1)
|
|
39
|
+
return {
|
|
40
|
+
historyIndex: nextIndex,
|
|
41
|
+
value: history[nextIndex] || "",
|
|
42
|
+
changed: nextIndex !== historyIndex
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (historyIndex < history.length - 1) {
|
|
47
|
+
const nextIndex = historyIndex + 1
|
|
48
|
+
return {
|
|
49
|
+
historyIndex: nextIndex,
|
|
50
|
+
value: history[nextIndex] || "",
|
|
51
|
+
changed: true
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
historyIndex: history.length,
|
|
57
|
+
value: "",
|
|
58
|
+
changed: historyIndex !== history.length
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function shouldApplySuggestionOnEnter(input, suggestions, selectedSuggestion) {
|
|
63
|
+
if (!Array.isArray(suggestions) || suggestions.length === 0) return false
|
|
64
|
+
if (!String(input || "").startsWith("/")) return false
|
|
65
|
+
|
|
66
|
+
const body = String(input || "").slice(1)
|
|
67
|
+
const firstSpace = body.indexOf(" ")
|
|
68
|
+
if (firstSpace >= 0) return false
|
|
69
|
+
|
|
70
|
+
const token = body.trim()
|
|
71
|
+
if (!token) return true
|
|
72
|
+
|
|
73
|
+
const idx = Math.max(0, Math.min(selectedSuggestion || 0, suggestions.length - 1))
|
|
74
|
+
const chosen = suggestions[idx]
|
|
75
|
+
return Boolean(chosen && chosen.name !== token)
|
|
76
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const MODE_CYCLE_ORDER = ["assistant", "agent", "longagent", "plan"]
|
|
2
|
+
|
|
3
|
+
export function nextMode(currentMode, order = MODE_CYCLE_ORDER) {
|
|
4
|
+
const idx = order.indexOf(currentMode)
|
|
5
|
+
const nextIdx = idx >= 0 ? (idx + 1) % order.length : 0
|
|
6
|
+
return order[nextIdx]
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function buildOperatorSnapshot({ runtimeSummary = null, backgroundSummary = null } = {}) {
|
|
2
|
+
const actions = []
|
|
3
|
+
if (runtimeSummary?.recoverableCount) {
|
|
4
|
+
actions.push(`recoverable sessions available: ${runtimeSummary.recoverableCount}`)
|
|
5
|
+
}
|
|
6
|
+
const recentTerminal = backgroundSummary?.recent_terminal || []
|
|
7
|
+
for (const item of recentTerminal.slice(0, 2)) {
|
|
8
|
+
if (item?.next_action) actions.push(`${item.id}: ${item.next_action}`)
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
recoverableCount: Number(runtimeSummary?.recoverableCount || 0),
|
|
12
|
+
activeBackground: Number(backgroundSummary?.active || 0),
|
|
13
|
+
actions
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export const POLICY_CHOICES = [
|
|
2
|
+
{ label: "Auto", value: "auto", desc: "auto-approve safe reads, review risky tools" },
|
|
3
|
+
{ label: "YOLO", value: "yolo", desc: "allow permission checks without prompts" },
|
|
4
|
+
{ label: "Ask", value: "ask", desc: "prompt before each tool call" },
|
|
5
|
+
{ label: "Allow", value: "allow", desc: "legacy allow all tool calls" },
|
|
6
|
+
{ label: "Deny", value: "deny", desc: "deny all tool calls" },
|
|
7
|
+
{ label: "Session Clear", value: "session-clear", desc: "clear cached grants" }
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
function currentPermissionValue(permissionConfigOrValue = "auto") {
|
|
11
|
+
if (typeof permissionConfigOrValue === "string") return permissionConfigOrValue
|
|
12
|
+
return permissionConfigOrValue.mode || permissionConfigOrValue.default_policy || "auto"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function createPolicyPickerState(current = "auto") {
|
|
16
|
+
const value = currentPermissionValue(current)
|
|
17
|
+
const idx = POLICY_CHOICES.findIndex((choice) => choice.value === value)
|
|
18
|
+
return { selected: Math.max(0, idx) }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function applyPolicyChoice(choice, { permissionConfig = {}, sessionId, clearSession } = {}) {
|
|
22
|
+
if (!choice) return { message: null, permissionConfig }
|
|
23
|
+
if (choice.value === "session-clear") {
|
|
24
|
+
clearSession?.(sessionId)
|
|
25
|
+
return {
|
|
26
|
+
message: "permission session cache cleared",
|
|
27
|
+
permissionConfig
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (["auto", "yolo"].includes(choice.value)) {
|
|
32
|
+
return {
|
|
33
|
+
message: `permission mode → ${choice.value}`,
|
|
34
|
+
permissionConfig: {
|
|
35
|
+
...permissionConfig,
|
|
36
|
+
mode: choice.value
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
message: `permission policy → ${choice.value}`,
|
|
43
|
+
permissionConfig: {
|
|
44
|
+
...permissionConfig,
|
|
45
|
+
mode: "manual",
|
|
46
|
+
default_policy: choice.value
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { listSessions } from "../session/store.mjs"
|
|
2
|
+
import { BackgroundManager } from "../orchestration/background-manager.mjs"
|
|
3
|
+
import { summarizeSessionRuntimeState } from "../session/runtime-state.mjs"
|
|
4
|
+
import { collectMcpSummary, collectSkillSummary } from "./state-store.mjs"
|
|
5
|
+
|
|
6
|
+
export async function buildReplRuntimeSnapshot({
|
|
7
|
+
cwd = process.cwd(),
|
|
8
|
+
state,
|
|
9
|
+
customCommands = [],
|
|
10
|
+
providers = [],
|
|
11
|
+
mcpRegistry,
|
|
12
|
+
skillRegistry,
|
|
13
|
+
recoveryEnabled = true
|
|
14
|
+
}) {
|
|
15
|
+
const recentSessions = await listSessions({ cwd, limit: 6, includeChildren: false }).catch(() => [])
|
|
16
|
+
const mcpSummary = collectMcpSummary(mcpRegistry)
|
|
17
|
+
const skillSummary = collectSkillSummary(skillRegistry)
|
|
18
|
+
const backgroundSummary = await BackgroundManager.summary().catch(() => null)
|
|
19
|
+
const runtimeSummary = await summarizeSessionRuntimeState({
|
|
20
|
+
sessionId: state?.sessionId || null,
|
|
21
|
+
cwd,
|
|
22
|
+
recoveryEnabled
|
|
23
|
+
}).catch(() => null)
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
state,
|
|
27
|
+
providers,
|
|
28
|
+
recentSessions,
|
|
29
|
+
mcpSummary,
|
|
30
|
+
skillSummary,
|
|
31
|
+
backgroundSummary,
|
|
32
|
+
runtimeSummary,
|
|
33
|
+
customCommandCount: customCommands.length,
|
|
34
|
+
cwd
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const DEFAULT_SLASH_ALIASES = {
|
|
2
|
+
"/h": "/help",
|
|
3
|
+
"/?": "/help",
|
|
4
|
+
"/n": "/new",
|
|
5
|
+
"/s": "/session",
|
|
6
|
+
"/k": "/keys",
|
|
7
|
+
"/r": "/resume",
|
|
8
|
+
"/m": "/mode",
|
|
9
|
+
"/p": "/provider",
|
|
10
|
+
"/q": "/exit"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildSlashCatalog({ builtinSlash = [], customCommands = [], skills = [] } = {}) {
|
|
14
|
+
const custom = customCommands.map((cmd) => ({
|
|
15
|
+
name: cmd.name,
|
|
16
|
+
desc: `custom (${cmd.scope || "project"})`
|
|
17
|
+
}))
|
|
18
|
+
const customNames = new Set(custom.map((item) => item.name))
|
|
19
|
+
const skillEntries = skills
|
|
20
|
+
.filter((skill) => !customNames.has(skill.name))
|
|
21
|
+
.map((skill) => ({ name: skill.name, desc: `skill (${skill.type})` }))
|
|
22
|
+
return [...builtinSlash, ...custom, ...skillEntries]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function slashQuery(inputLine) {
|
|
26
|
+
if (!String(inputLine || "").startsWith("/")) return null
|
|
27
|
+
const raw = String(inputLine).slice(1)
|
|
28
|
+
const firstSpace = raw.indexOf(" ")
|
|
29
|
+
return (firstSpace >= 0 ? raw.slice(0, firstSpace) : raw).trim()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function slashSuggestions(inputLine, options = {}) {
|
|
33
|
+
const token = slashQuery(inputLine)
|
|
34
|
+
if (token === null) return []
|
|
35
|
+
const all = buildSlashCatalog(options)
|
|
36
|
+
const q = token.toLowerCase()
|
|
37
|
+
return all
|
|
38
|
+
.map((item) => {
|
|
39
|
+
const name = item.name.toLowerCase()
|
|
40
|
+
let rank = 99
|
|
41
|
+
if (!q) rank = 0
|
|
42
|
+
else if (name === q) rank = 0
|
|
43
|
+
else if (name.startsWith(q)) rank = 1
|
|
44
|
+
else if (name.includes(q)) rank = 2
|
|
45
|
+
return { ...item, rank }
|
|
46
|
+
})
|
|
47
|
+
.filter((item) => item.rank < 99)
|
|
48
|
+
.sort((a, b) => (a.rank - b.rank) || a.name.localeCompare(b.name))
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function applySuggestionToInput(current, suggestionName) {
|
|
52
|
+
const raw = String(current || "")
|
|
53
|
+
if (!raw.startsWith("/")) return raw
|
|
54
|
+
const body = raw.slice(1)
|
|
55
|
+
const firstSpace = body.indexOf(" ")
|
|
56
|
+
if (firstSpace < 0) return `/${suggestionName} `
|
|
57
|
+
return `/${suggestionName}${body.slice(firstSpace)}`
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function normalizeSlashAlias(line, aliases = DEFAULT_SLASH_ALIASES) {
|
|
61
|
+
return aliases[String(line || "")] || line
|
|
62
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function collectMcpSummary(registry) {
|
|
2
|
+
const snapshot = registry.healthSnapshot()
|
|
3
|
+
const tools = registry.listTools()
|
|
4
|
+
const byServer = {}
|
|
5
|
+
for (const tool of tools) {
|
|
6
|
+
const server = tool.server || "unknown"
|
|
7
|
+
byServer[server] = (byServer[server] || 0) + 1
|
|
8
|
+
}
|
|
9
|
+
const healthy = snapshot.filter((item) => item.ok).length
|
|
10
|
+
return {
|
|
11
|
+
configured: snapshot.length,
|
|
12
|
+
healthy,
|
|
13
|
+
unhealthy: snapshot.length - healthy,
|
|
14
|
+
tools: tools.length,
|
|
15
|
+
byServer,
|
|
16
|
+
entries: snapshot
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function collectSkillSummary(registry) {
|
|
21
|
+
const list = registry.isReady() ? registry.list() : []
|
|
22
|
+
return {
|
|
23
|
+
total: list.length,
|
|
24
|
+
template: list.filter((s) => s.type === "template").length,
|
|
25
|
+
skillMd: list.filter((s) => s.type === "skill_md").length,
|
|
26
|
+
mcpPrompt: list.filter((s) => s.type === "mcp_prompt").length,
|
|
27
|
+
programmable: list.filter((s) => s.type === "mjs").length
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { executeTurn } from "../session/engine.mjs"
|
|
2
|
+
import { HookBus } from "../plugin/hook-bus.mjs"
|
|
3
|
+
import { extractImageRefs, buildContentBlocks } from "../tool/image-util.mjs"
|
|
4
|
+
|
|
5
|
+
export async function executePromptTurn({
|
|
6
|
+
prompt,
|
|
7
|
+
state,
|
|
8
|
+
ctx,
|
|
9
|
+
streamSink = null,
|
|
10
|
+
pendingImages = [],
|
|
11
|
+
signal = null,
|
|
12
|
+
deps = {}
|
|
13
|
+
}) {
|
|
14
|
+
const extractImageRefsFn = deps.extractImageRefs || extractImageRefs
|
|
15
|
+
const buildContentBlocksFn = deps.buildContentBlocks || buildContentBlocks
|
|
16
|
+
const chatParamsFn = deps.chatParams || HookBus.chatParams.bind(HookBus)
|
|
17
|
+
const executeTurnFn = deps.executeTurn || executeTurn
|
|
18
|
+
const cwd = deps.cwd || process.cwd()
|
|
19
|
+
|
|
20
|
+
const { text: cleanedPrompt, imagePaths, imageUrls = [] } = extractImageRefsFn(prompt, cwd)
|
|
21
|
+
const effectivePrompt = cleanedPrompt ?? prompt
|
|
22
|
+
let contentBlocks = null
|
|
23
|
+
|
|
24
|
+
if (imagePaths.length || imageUrls.length || pendingImages.length) {
|
|
25
|
+
contentBlocks = await buildContentBlocksFn(effectivePrompt, imagePaths, imageUrls)
|
|
26
|
+
if (typeof contentBlocks === "string") {
|
|
27
|
+
contentBlocks = [{ type: "text", text: contentBlocks }]
|
|
28
|
+
}
|
|
29
|
+
for (const img of pendingImages) {
|
|
30
|
+
if (img && img.type === "image") contentBlocks.push(img)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const chatParams = await chatParamsFn({
|
|
35
|
+
prompt: effectivePrompt,
|
|
36
|
+
mode: state.mode,
|
|
37
|
+
model: state.model,
|
|
38
|
+
providerType: state.providerType,
|
|
39
|
+
sessionId: state.sessionId
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
result: await executeTurnFn({
|
|
44
|
+
prompt: chatParams.prompt ?? effectivePrompt,
|
|
45
|
+
contentBlocks,
|
|
46
|
+
mode: chatParams.mode ?? state.mode,
|
|
47
|
+
model: chatParams.model ?? state.model,
|
|
48
|
+
sessionId: state.sessionId,
|
|
49
|
+
configState: ctx.configState,
|
|
50
|
+
providerType: chatParams.providerType ?? state.providerType,
|
|
51
|
+
longagentImpl: state.longagentImpl ?? null,
|
|
52
|
+
signal,
|
|
53
|
+
output: streamSink && typeof streamSink === "function"
|
|
54
|
+
? { write: streamSink }
|
|
55
|
+
: null
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function buildReplSmokeChecklist() {
|
|
2
|
+
return [
|
|
3
|
+
"start repl in tty mode",
|
|
4
|
+
"verify /help and /status output",
|
|
5
|
+
"verify slash suggestion and selection flow",
|
|
6
|
+
"submit one normal prompt turn",
|
|
7
|
+
"exercise one permission or question dialog",
|
|
8
|
+
"inspect background/task visibility",
|
|
9
|
+
"confirm --help and --version CLI outputs"
|
|
10
|
+
]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function summarizeVerificationResults(results = []) {
|
|
14
|
+
const total = results.length
|
|
15
|
+
const passed = results.filter((item) => item?.ok === true).length
|
|
16
|
+
const failed = total - passed
|
|
17
|
+
return {
|
|
18
|
+
total,
|
|
19
|
+
passed,
|
|
20
|
+
failed,
|
|
21
|
+
ok: failed === 0
|
|
22
|
+
}
|
|
23
|
+
}
|