karnel-termux 4.15.2 → 4.16.0
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 +10 -4
- package/docs/CHANGELOG.md +19 -0
- package/docs/cli/README.md +0 -1
- package/karnel/RELEASE_COMMIT +1 -1
- package/karnel/cli/commands/agent.sh +841 -0
- package/karnel/cli/commands/ia.sh +24 -0
- package/karnel/cli/commands/list.sh +2 -0
- package/karnel/cli/commands/update.sh +1 -1
- package/karnel/cli/karnel.sh +29 -17
- package/karnel/tools/ai/all.sh +2 -0
- package/karnel/tools/ai/ampcode/bin/amp.proot +22 -0
- package/karnel/tools/ai/antigravity-cli/bin/agy.proot +22 -0
- package/karnel/tools/ai/cactus/bin/cactus +30 -0
- package/karnel/tools/ai/cactus/bin/cactus.glibc +31 -0
- package/karnel/tools/ai/cactus/bin/cactus.launcher.py +57 -0
- package/karnel/tools/ai/cactus/bin/cactus.proot +33 -0
- package/karnel/tools/ai/cactus/install.sh +288 -9
- package/karnel/tools/ai/cactus-needle/README.md +120 -0
- package/karnel/tools/ai/cactus-needle/bin/needle +30 -0
- package/karnel/tools/ai/cactus-needle/bin/needle.glibc +18 -0
- package/karnel/tools/ai/cactus-needle/bin/needle.proot +24 -0
- package/karnel/tools/ai/cactus-needle/install.sh +371 -0
- package/karnel/tools/ai/claude-code/bin/claude.proot +22 -0
- package/karnel/tools/ai/cline/bin/cline.proot +28 -0
- package/karnel/tools/ai/cline/install.sh +87 -34
- package/karnel/tools/ai/codegraph/bin/codegraph +1 -1
- package/karnel/tools/ai/command-code/install.sh +21 -3
- package/karnel/tools/ai/ctx7/install.sh +1 -1
- package/karnel/tools/ai/cursor-cli/bin/cursor.proot +33 -0
- package/karnel/tools/ai/cursor-cli/install.sh +107 -31
- package/karnel/tools/ai/droid/bin/droid +27 -0
- package/karnel/tools/ai/droid/bin/droid.proot +22 -0
- package/karnel/tools/ai/droid/helper/droid_helper.c +58 -0
- package/karnel/tools/ai/droid/install.sh +381 -79
- package/karnel/tools/ai/engram/install.sh +47 -13
- package/karnel/tools/ai/gentle-ai/install.sh +43 -5
- package/karnel/tools/ai/goose/bin/goose.proot +22 -0
- package/karnel/tools/ai/goose/install.sh +90 -8
- package/karnel/tools/ai/hermes-agent/install.sh +11 -2
- package/karnel/tools/ai/hugging-face/install.sh +1 -1
- package/karnel/tools/ai/keelcode/bin/keelcode.proot +23 -0
- package/karnel/tools/ai/keelcode/install.sh +95 -18
- package/karnel/tools/ai/kilocode-cli/bin/kilocode +4 -4
- package/karnel/tools/ai/kilocode-cli/bin/kilocode.proot +22 -0
- package/karnel/tools/ai/kilocode-cli/install.sh +19 -1
- package/karnel/tools/ai/kimchi-code/bin/kimchi.proot +22 -0
- package/karnel/tools/ai/kimchi-code/install.sh +399 -83
- package/karnel/tools/ai/kiro/install.sh +57 -14
- package/karnel/tools/ai/mimocode/bin/mimo.proot +22 -0
- package/karnel/tools/ai/mistral-vibe/install.sh +4 -1
- package/karnel/tools/ai/odysseus/install.sh +247 -55
- package/karnel/tools/ai/oh-my-pi/bin/omp.proot +22 -0
- package/karnel/tools/ai/omni-route/install.sh +63 -50
- package/karnel/tools/ai/openclaw/install.sh +4 -1
- package/karnel/tools/ai/opencode/bin/opencode.proot +22 -0
- package/karnel/tools/ai/openspec/install.sh +1 -1
- package/karnel/tools/ai/qoder/README.md +1 -1
- package/karnel/tools/ai/qoder/bin/qodercli.proot +22 -0
- package/karnel/tools/ai/walkie/README.md +84 -0
- package/karnel/tools/ai/walkie/bin/walkie +29 -0
- package/karnel/tools/ai/walkie/install.sh +660 -0
- package/karnel/tools/ai/walkie/patches/network-interfaces.js +71 -0
- package/karnel/tools/ai/walkie/patches/patch-agents.js +191 -0
- package/karnel/tools/ai/walkie/patches/patch-members.js +169 -0
- package/karnel/tools/ai/walkie/patches/patch-mention-only.js +89 -0
- package/karnel/tools/editor/nvchad/README.md +4 -4
- package/karnel/tools/editor/nvchad/install.sh +3 -3
- package/karnel/utils/agent_actions.sh +1421 -0
- package/karnel/utils/agent_llm.sh +304 -0
- package/karnel/utils/agent_markdown.sh +254 -0
- package/karnel/utils/bootstrap.sh +3 -2
- package/karnel/utils/log.sh +6 -0
- package/karnel/utils/walkie.sh +29 -0
- package/package.json +4 -1
- package/scripts/check-package.js +5 -2
- package/scripts/completion.bash +4 -4
- package/scripts/completion.zsh +4 -3
|
@@ -0,0 +1,841 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
# ============================================================
|
|
4
|
+
# karnel agent — AI assistant (ask) & bash-backed task agent (run)
|
|
5
|
+
#
|
|
6
|
+
# karnel agent ask "prompt" assistant, streamed (markdown TUI)
|
|
7
|
+
# karnel agent run "prompt" task agent: the LLM answers in
|
|
8
|
+
# markdown; bash parses the answer
|
|
9
|
+
# into files & commands and does the
|
|
10
|
+
# work (commands need y/N approval)
|
|
11
|
+
#
|
|
12
|
+
# Everything in this command is plain bash; the "AI" only DECIDES
|
|
13
|
+
# and DESCRIBES. bash EXECUTES. In the REPLs (and -p prompts on a
|
|
14
|
+
# TTY) typing `@query` opens an fzf picker to attach a file's
|
|
15
|
+
# contents to your message.
|
|
16
|
+
# ============================================================
|
|
17
|
+
|
|
18
|
+
import "@/utils/log"
|
|
19
|
+
import "@/utils/colors"
|
|
20
|
+
import "@/utils/agent_llm"
|
|
21
|
+
import "@/utils/agent_actions"
|
|
22
|
+
import "@/utils/agent_markdown"
|
|
23
|
+
|
|
24
|
+
# ------------------------------------------------------------
|
|
25
|
+
# agent_help
|
|
26
|
+
# ------------------------------------------------------------
|
|
27
|
+
agent_help() {
|
|
28
|
+
echo
|
|
29
|
+
box "Karnel Agent — Local AI Assistant & Task Agent"
|
|
30
|
+
echo
|
|
31
|
+
log_info "Usage: karnel agent <ask|run|config> [options]"
|
|
32
|
+
echo
|
|
33
|
+
separator_section "Subcommands"
|
|
34
|
+
echo
|
|
35
|
+
printf " ${D_CYAN}%-11s${D_NC} %s\n" "ask" "Assistant (prompt → LLM → colored markdown answer)"
|
|
36
|
+
printf " ${D_CYAN}%-11s${D_NC} %s\n" "run" "Agent (prompt → LLM markdown → bash creates files / runs commands)"
|
|
37
|
+
printf " ${D_CYAN}%-11s${D_NC} %s\n" "config" "Show or edit saved settings"
|
|
38
|
+
printf " ${D_CYAN}%-11s${D_NC} %s\n" "status" "Show endpoint/model status"
|
|
39
|
+
echo
|
|
40
|
+
separator_section "Options (ask & run)"
|
|
41
|
+
echo
|
|
42
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "-p, --prompt <text>" "Task/question (omit for interactive shell)"
|
|
43
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "-m, --model <name>" "Model id (default: $AGENT_MODEL)"
|
|
44
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "-u, --endpoint <url>" "OpenAI-compatible endpoint (default: $AGENT_ENDPOINT)"
|
|
45
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "-t, --temperature <n>" "Sampling temperature (default: $AGENT_TEMPERATURE)"
|
|
46
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "--max-tokens <n>" "Max output tokens (default: $AGENT_MAX_TOKENS)"
|
|
47
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "-w, --workspace <dir>" "Agent working dir (run mode, default: \$PWD)"
|
|
48
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "-n, --max-iterations <n>" "Agent loop limit (default: $AGENT_MAX_ITERATIONS)"
|
|
49
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "-y, --yes" "Auto-approve commands (skip y/N prompt)"
|
|
50
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "--plan" "Plan mode (read-only): no file writes, write commands blocked"
|
|
51
|
+
printf " ${D_CYAN}%-22s${D_NC} %s\n" "--build" "Build mode (default): files and commands applied"
|
|
52
|
+
echo
|
|
53
|
+
separator_section "Files & commands"
|
|
54
|
+
echo
|
|
55
|
+
list_item "Type ${D_CYAN}@name${D_NC} in a message to attach a file's contents (live fzf picker while typing)"
|
|
56
|
+
list_item "Start a message with ${D_CYAN}!${D_NC} for shell mode (e.g. ${D_CYAN}!git status${D_NC}) — the prompt turns green ${D_CYAN}Shell${D_NC} and the output is added to the agent's context"
|
|
57
|
+
list_item "Commands from the model run only after your ${D_CYAN}y/N${D_NC} confirmation (${D_CYAN}-y${D_NC} auto-approves)"
|
|
58
|
+
list_item "Press ${D_CYAN}ESC ESC${D_NC} at any prompt to cancel the agent"
|
|
59
|
+
list_item "The interactive REPL remembers the conversation and shows ${D_CYAN}[context % · elapsed]${D_NC} after each answer/task"
|
|
60
|
+
list_item "Dictate your prompt with ${D_CYAN}/voice${D_NC} (Termux:API)"
|
|
61
|
+
list_item "If the model server is down, the agent starts ${D_CYAN}cactus${D_NC} in the background (logs → ${D_CYAN}~/.cache/karnel/karnel-agent.log${D_NC}) and stops it when you leave (${D_CYAN}/exit${D_NC} or ${D_CYAN}Ctrl+C${D_NC})"
|
|
62
|
+
echo
|
|
63
|
+
separator_section "Interactive slash commands"
|
|
64
|
+
echo
|
|
65
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/help" "Show this help"
|
|
66
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/model <name>" "Switch model"
|
|
67
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/endpoint <url>" "Switch endpoint"
|
|
68
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/temp <n>" "Set temperature"
|
|
69
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/max <n>" "Set max tokens"
|
|
70
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/workspace <dir>" "Set agent workspace (run)"
|
|
71
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/voice" "Dictate with Termux:API"
|
|
72
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/clear" "Reset conversation"
|
|
73
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/history" "Show conversation history"
|
|
74
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/status" "Show current settings"
|
|
75
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/plan" "Plan mode (read-only) — next task only inspects and proposes"
|
|
76
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/build" "Build mode — next task applies files and commands"
|
|
77
|
+
printf " ${D_CYAN}%-16s${D_NC} %s\n" "/exit" "Leave the interactive shell"
|
|
78
|
+
echo
|
|
79
|
+
separator_section "Examples"
|
|
80
|
+
echo
|
|
81
|
+
printf " ${D_CYAN}karnel agent ask -p \"Explain rsync\"${D_NC}\n"
|
|
82
|
+
printf " ${D_CYAN}karnel agent run -p \"create a backup script\"${D_NC}\n"
|
|
83
|
+
printf " ${D_CYAN}karnel agent run -p \"...\" -m gemma-4-e2b-it-cq4 -u http://127.0.0.1:8000/v1${D_NC}\n"
|
|
84
|
+
printf " ${D_CYAN}karnel agent ask${D_NC} # interactive chat\n"
|
|
85
|
+
echo
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# ------------------------------------------------------------
|
|
89
|
+
# agent_main — dispatcher
|
|
90
|
+
# ------------------------------------------------------------
|
|
91
|
+
agent_main() {
|
|
92
|
+
local cmd="$1"
|
|
93
|
+
shift || true
|
|
94
|
+
|
|
95
|
+
agent_config_load
|
|
96
|
+
|
|
97
|
+
case "$cmd" in
|
|
98
|
+
ask | chat)
|
|
99
|
+
agent_ask "$@"
|
|
100
|
+
;;
|
|
101
|
+
run | exec)
|
|
102
|
+
agent_run "$@"
|
|
103
|
+
;;
|
|
104
|
+
config | settings)
|
|
105
|
+
agent_config_cmd "$@"
|
|
106
|
+
;;
|
|
107
|
+
status | info)
|
|
108
|
+
agent_status
|
|
109
|
+
;;
|
|
110
|
+
"" | --help | -h | help)
|
|
111
|
+
agent_help
|
|
112
|
+
;;
|
|
113
|
+
*)
|
|
114
|
+
log_error "Unknown subcommand: ${D_CYAN}$cmd${D_NC}"
|
|
115
|
+
echo
|
|
116
|
+
agent_help
|
|
117
|
+
exit 1
|
|
118
|
+
;;
|
|
119
|
+
esac
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
# ------------------------------------------------------------
|
|
123
|
+
# agent_status_line — compact one-liner of current settings
|
|
124
|
+
# ------------------------------------------------------------
|
|
125
|
+
agent_status_line() {
|
|
126
|
+
printf ' %b%-10s%b : %s\n' "$D_CYAN" "Model" "$NC" "$AGENT_MODEL"
|
|
127
|
+
printf ' %b%-10s%b : %s\n' "$D_CYAN" "Endpoint" "$NC" "$AGENT_ENDPOINT"
|
|
128
|
+
printf ' %b%-10s%b : %s\n' "$D_CYAN" "Temp" "$NC" "$AGENT_TEMPERATURE"
|
|
129
|
+
printf ' %b%-10s%b : %s\n' "$D_CYAN" "Max tokens" "$NC" "$AGENT_MAX_TOKENS"
|
|
130
|
+
printf ' %b%-10s%b : %s\n' "$D_CYAN" "Context" "$NC" "$AGENT_CONTEXT_WINDOW tokens"
|
|
131
|
+
# PLAN/BUILD modes are a run-mode concept; hide them in ask
|
|
132
|
+
[[ "$AGENT_REPL_MODE" != "ask" ]] && printf ' %b%-10s%b : %s\n' "$D_CYAN" "Mode" "$NC" "$([[ $AGENT_PLAN_MODE == 1 ]] && echo 'PLAN (read-only)' || echo 'BUILD (full access)')"
|
|
133
|
+
printf ' %b%-10s%b : %s\n' "$D_CYAN" "Workspace" "$NC" "$AGENT_WORKSPACE"
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
# ------------------------------------------------------------
|
|
137
|
+
# agent_status — full status with server check
|
|
138
|
+
# ------------------------------------------------------------
|
|
139
|
+
agent_status() {
|
|
140
|
+
separator_section "Karnel Agent — Status"
|
|
141
|
+
echo
|
|
142
|
+
agent_status_line
|
|
143
|
+
echo
|
|
144
|
+
separator_section "Server"
|
|
145
|
+
echo
|
|
146
|
+
if agent_check_server; then
|
|
147
|
+
log_success "Endpoint reachable: ${D_CYAN}$AGENT_ENDPOINT${D_NC}"
|
|
148
|
+
local ms
|
|
149
|
+
ms=$(agent_models_list 2>/dev/null)
|
|
150
|
+
echo
|
|
151
|
+
log_info "Available models:"
|
|
152
|
+
echo "$ms" | while IFS= read -r mid; do
|
|
153
|
+
list_item "$mid"
|
|
154
|
+
done
|
|
155
|
+
else
|
|
156
|
+
log_error "Endpoint unreachable"
|
|
157
|
+
fi
|
|
158
|
+
echo
|
|
159
|
+
separator_section "Config file"
|
|
160
|
+
echo
|
|
161
|
+
list_item "$AGENT_CONF_FILE"
|
|
162
|
+
echo
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
# ------------------------------------------------------------
|
|
166
|
+
# agent_config_help
|
|
167
|
+
# ------------------------------------------------------------
|
|
168
|
+
agent_config_help() {
|
|
169
|
+
log_info "Usage: karnel agent config [key] [value]"
|
|
170
|
+
echo
|
|
171
|
+
separator_section "Settings"
|
|
172
|
+
echo
|
|
173
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "model" "Model id (default: gemma-4-e2b-it-cq4)"
|
|
174
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "endpoint" "OpenAI-compatible URL"
|
|
175
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "temperature" "Sampling temperature"
|
|
176
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "max_tokens" "Max output tokens"
|
|
177
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "max_iterations" "Agent loop limit"
|
|
178
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "confirm_commands" "Ask y/N before running commands (1/0)"
|
|
179
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "context_window" "Model context window in tokens (default: 8192)"
|
|
180
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "server_command" "Command that starts the model server in the background"
|
|
181
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "workspace" "Default agent working dir"
|
|
182
|
+
echo
|
|
183
|
+
log_tip "Run ${D_CYAN}karnel agent config${D_NC} with no arguments to show current settings."
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# ------------------------------------------------------------
|
|
187
|
+
# agent_config_cmd
|
|
188
|
+
# ------------------------------------------------------------
|
|
189
|
+
agent_config_cmd() {
|
|
190
|
+
local key="$1"
|
|
191
|
+
shift || true
|
|
192
|
+
|
|
193
|
+
if [[ -z "$key" || "$key" == "--help" || "$key" == "-h" ]]; then
|
|
194
|
+
echo
|
|
195
|
+
separator_section "Current Config"
|
|
196
|
+
echo
|
|
197
|
+
agent_status_line
|
|
198
|
+
echo
|
|
199
|
+
separator_section "Config file"
|
|
200
|
+
echo
|
|
201
|
+
list_item "Saved at: ${D_CYAN}$AGENT_CONF_FILE${D_NC}"
|
|
202
|
+
echo
|
|
203
|
+
agent_config_help
|
|
204
|
+
echo
|
|
205
|
+
return 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
local value="$*"
|
|
209
|
+
if [[ -z "$value" ]]; then
|
|
210
|
+
log_error "Missing value for: $key"
|
|
211
|
+
echo
|
|
212
|
+
agent_config_help
|
|
213
|
+
return 1
|
|
214
|
+
fi
|
|
215
|
+
agent_config_set "$key" "$value"
|
|
216
|
+
log_success "Saved $key = ${D_CYAN}$value${D_NC}"
|
|
217
|
+
}
|
|
218
|
+
# ------------------------------------------------------------
|
|
219
|
+
# Slash-command help
|
|
220
|
+
# ------------------------------------------------------------
|
|
221
|
+
agent_repl_help() {
|
|
222
|
+
local mode="${1:-$AGENT_REPL_MODE}"
|
|
223
|
+
separator_section "Slash commands"
|
|
224
|
+
echo
|
|
225
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/model <name>" "Switch model"
|
|
226
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/endpoint <url>" "Switch endpoint"
|
|
227
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/temp <n>" "Set temperature"
|
|
228
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/max <n>" "Set max tokens"
|
|
229
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/workspace <dir>" "Set agent workspace (run)"
|
|
230
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/voice" "Dictate with Termux:API (editable prompt)"
|
|
231
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/clear" "Reset conversation"
|
|
232
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/history" "Show conversation history"
|
|
233
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/status" "Show current settings"
|
|
234
|
+
if [[ "$mode" != "ask" ]]; then
|
|
235
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/plan" "Plan mode (read-only) — next task only inspects and proposes"
|
|
236
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/build" "Build mode — next task applies files and commands"
|
|
237
|
+
fi
|
|
238
|
+
printf " ${D_CYAN}%-14s${D_NC} %s\n" "/exit" "Leave the interactive shell"
|
|
239
|
+
echo
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# ------------------------------------------------------------
|
|
243
|
+
# agent_parse_args — unified flag parser for ask/run
|
|
244
|
+
# -p|--prompt <text> -m|--model <id> -u|--endpoint <url>
|
|
245
|
+
# -t|--temperature <n> --max-tokens <n>
|
|
246
|
+
# -w|--workspace <dir> -n|--max-iterations <n>
|
|
247
|
+
# Any positional argument is treated as the prompt.
|
|
248
|
+
# ------------------------------------------------------------
|
|
249
|
+
agent_parse_args() {
|
|
250
|
+
AGENT_PROMPT=""
|
|
251
|
+
AGENT_SHOW_HELP=0
|
|
252
|
+
AGENT_YES=0
|
|
253
|
+
local prev=""
|
|
254
|
+
for arg in "$@"; do
|
|
255
|
+
case "$prev" in
|
|
256
|
+
-p | --prompt) AGENT_PROMPT="$arg"; prev=""; continue ;;
|
|
257
|
+
-m | --model) AGENT_MODEL="$arg"; prev=""; continue ;;
|
|
258
|
+
-u | --endpoint) AGENT_ENDPOINT="$arg"; prev=""; continue ;;
|
|
259
|
+
-t | --temperature) AGENT_TEMPERATURE="$arg"; prev=""; continue ;;
|
|
260
|
+
-w | --workspace) AGENT_WORKSPACE="$arg"; prev=""; continue ;;
|
|
261
|
+
-n | --max-iterations) AGENT_MAX_ITERATIONS="$arg"; prev=""; continue ;;
|
|
262
|
+
--max-tokens) AGENT_MAX_TOKENS="$arg"; prev=""; continue ;;
|
|
263
|
+
esac
|
|
264
|
+
case "$arg" in
|
|
265
|
+
-p=* | --prompt=*) AGENT_PROMPT="${arg#*=}" ;;
|
|
266
|
+
-m=* | --model=*) AGENT_MODEL="${arg#*=}" ;;
|
|
267
|
+
-u=* | --endpoint=*) AGENT_ENDPOINT="${arg#*=}" ;;
|
|
268
|
+
-t=* | --temperature=*) AGENT_TEMPERATURE="${arg#*=}" ;;
|
|
269
|
+
-w=* | --workspace=*) AGENT_WORKSPACE="${arg#*=}" ;;
|
|
270
|
+
-n=* | --max-iterations=*) AGENT_MAX_ITERATIONS="${arg#*=}" ;;
|
|
271
|
+
--max-tokens=*) AGENT_MAX_TOKENS="${arg#*=}" ;;
|
|
272
|
+
-p | --prompt | -m | --model | -u | --endpoint | -t | --temperature | -w | --workspace | -n | --max-iterations | --max-tokens) prev="$arg" ;;
|
|
273
|
+
--plan) AGENT_PLAN_MODE=1 ;;
|
|
274
|
+
--build) AGENT_PLAN_MODE=0 ;;
|
|
275
|
+
-y | --yes) AGENT_YES=1 ;;
|
|
276
|
+
-h | --help) AGENT_SHOW_HELP=1 ;;
|
|
277
|
+
-*) log_warn "Unknown option: $arg" ;;
|
|
278
|
+
*) [[ -z "$AGENT_PROMPT" ]] && AGENT_PROMPT="$arg" ;;
|
|
279
|
+
esac
|
|
280
|
+
done
|
|
281
|
+
}
|
|
282
|
+
readonly AGENT_ASK_SYSTEM="You are a concise chat assistant. Reply in the SAME LANGUAGE the user writes in. Be direct and practical; use markdown, and put code in fenced blocks. <attached_file path=\"...\">...</attached_file> blocks hold real file contents (action=\"no-read\" = path only). <compacted_summary>...</compacted_summary> blocks are condensed summaries of earlier turns — treat them as ground truth."
|
|
283
|
+
|
|
284
|
+
# ------------------------------------------------------------
|
|
285
|
+
# agent_show_answer <response> — render markdown
|
|
286
|
+
# ------------------------------------------------------------
|
|
287
|
+
agent_show_answer() {
|
|
288
|
+
local resp="$1"
|
|
289
|
+
md_render "$resp"
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
# ------------------------------------------------------------
|
|
293
|
+
# agent_strip_meta_blocks <text> — remove <compacted_summary>
|
|
294
|
+
# blocks that the model sometimes echoes back into its answer.
|
|
295
|
+
# ------------------------------------------------------------
|
|
296
|
+
agent_strip_meta_blocks() {
|
|
297
|
+
local text="$1"
|
|
298
|
+
printf '%s' "$text" | sed -e 's/<compacted_summary>.*<\/compacted_summary>//g' \
|
|
299
|
+
-e '/<compacted_summary>/,/<\/compacted_summary>/d'
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
# ------------------------------------------------------------
|
|
303
|
+
# agent_clean_output <text> — post-process a model answer with
|
|
304
|
+
# pure bash so the system prompts stay short: drop echoed
|
|
305
|
+
# <compacted_summary> blocks and translate the LaTeX math the
|
|
306
|
+
# model loves to emit ($\rightarrow$, \Rightarrow, ...) into
|
|
307
|
+
# unicode arrows so they render instead of showing as raw text.
|
|
308
|
+
# ------------------------------------------------------------
|
|
309
|
+
agent_clean_output() {
|
|
310
|
+
local text="$1"
|
|
311
|
+
text=$(agent_strip_meta_blocks "$text")
|
|
312
|
+
text=$(printf '%s' "$text" | sed \
|
|
313
|
+
-e 's/\$\\rightarrow\$/→/g' \
|
|
314
|
+
-e 's/\$\\Rightarrow\$/⇒/g' \
|
|
315
|
+
-e 's/\$\\leftarrow\$/←/g' \
|
|
316
|
+
-e 's/\$\\Leftarrow\$/⇐/g' \
|
|
317
|
+
-e 's/\$\\to\$/→/g' \
|
|
318
|
+
-e 's/\$\\leftarrow\$/←/g' \
|
|
319
|
+
-e 's/\\rightarrow/→/g' \
|
|
320
|
+
-e 's/\\Rightarrow/⇒/g' \
|
|
321
|
+
-e 's/\\leftarrow/←/g' \
|
|
322
|
+
-e 's/\\Leftarrow/⇐/g' \
|
|
323
|
+
-e 's/\\to\b/→/g')
|
|
324
|
+
printf '%s' "$text"
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# ------------------------------------------------------------
|
|
328
|
+
# agent_response_has_summary <text> — true if the model's answer
|
|
329
|
+
# already ends with a substantial prose summary AFTER its last
|
|
330
|
+
# action (file/command) block. Lets the run loop finish in one
|
|
331
|
+
# iteration when the model did the work AND wrote the summary,
|
|
332
|
+
# instead of forcing a redundant "final summary" iteration.
|
|
333
|
+
# ------------------------------------------------------------
|
|
334
|
+
agent_response_has_summary() {
|
|
335
|
+
local text="$1"
|
|
336
|
+
local trail prose
|
|
337
|
+
trail=$(printf '%s\n' "$text" | awk '
|
|
338
|
+
BEGIN { in_fence=0; last=0 }
|
|
339
|
+
/^[[:space:]]*```/ { in_fence = !in_fence; last=NR; next }
|
|
340
|
+
!in_fence && /^[[:space:]]*\$[[:space:]]/ { last=NR; next }
|
|
341
|
+
{ a[NR]=$0 }
|
|
342
|
+
END { for (i=last+1; i<=NR; i++) print a[i] }
|
|
343
|
+
')
|
|
344
|
+
prose=$(printf '%s\n' "$trail" | sed '/^[[:space:]]*#/d' | tr '\n' ' ')
|
|
345
|
+
prose=$(printf '%s\n' "$prose" | sed -E 's/[[:space:]]+/ /g; s/^[[:space:]]*//; s/[[:space:]]*$//')
|
|
346
|
+
[[ "${#prose}" -ge 40 ]]
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
# ------------------------------------------------------------
|
|
350
|
+
# agent_ask_once <prompt> — single-shot assistant answer
|
|
351
|
+
# ------------------------------------------------------------
|
|
352
|
+
agent_ask_once() {
|
|
353
|
+
local prompt="$1"
|
|
354
|
+
prompt=$(agent_prepare_prompt "$prompt")
|
|
355
|
+
if ! agent_check_server; then echo; return 1; fi
|
|
356
|
+
|
|
357
|
+
local history outfile resp
|
|
358
|
+
history=$(agent_history_new)
|
|
359
|
+
history=$(agent_history_system "$history" "$AGENT_ASK_SYSTEM")
|
|
360
|
+
history=$(agent_history_add "$history" user "$prompt")
|
|
361
|
+
|
|
362
|
+
echo
|
|
363
|
+
box "Ask"
|
|
364
|
+
echo
|
|
365
|
+
|
|
366
|
+
outfile=$(agent_md_dir)/ask_stream.txt
|
|
367
|
+
AGENT_ABORT=0
|
|
368
|
+
if agent_exec_loading "Asking ${D_CYAN}$AGENT_MODEL${D_NC}…" agent_chat_text_to "$history" "$outfile"; then
|
|
369
|
+
resp=$(agent_clean_output "$(cat "$outfile")")
|
|
370
|
+
echo
|
|
371
|
+
if [[ -z "$resp" ]]; then
|
|
372
|
+
log_warn "Empty response from the model"
|
|
373
|
+
else
|
|
374
|
+
agent_show_answer "$resp"
|
|
375
|
+
fi
|
|
376
|
+
elif (( AGENT_ABORT )); then
|
|
377
|
+
echo
|
|
378
|
+
else
|
|
379
|
+
log_error "Request failed (is the server running?)"
|
|
380
|
+
fi
|
|
381
|
+
echo
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
# ------------------------------------------------------------
|
|
385
|
+
# agent_repl_handle <line> — common slash-command handler.
|
|
386
|
+
# Returns 0 (continue), 1 (exit). Uses global AGENT_REPL_HISTORY.
|
|
387
|
+
# ------------------------------------------------------------
|
|
388
|
+
agent_repl_handle() {
|
|
389
|
+
local line="$1"
|
|
390
|
+
local mode="${2:-$AGENT_REPL_MODE}"
|
|
391
|
+
case "$line" in
|
|
392
|
+
/help | help)
|
|
393
|
+
agent_repl_help "$mode"
|
|
394
|
+
return 0
|
|
395
|
+
;;
|
|
396
|
+
/exit | /quit | /q | exit | quit)
|
|
397
|
+
return 1
|
|
398
|
+
;;
|
|
399
|
+
/clear)
|
|
400
|
+
AGENT_REPL_HISTORY=$(agent_history_new)
|
|
401
|
+
AGENT_REPL_STEPS=0
|
|
402
|
+
log_success "Conversation cleared"
|
|
403
|
+
echo
|
|
404
|
+
return 0
|
|
405
|
+
;;
|
|
406
|
+
/status)
|
|
407
|
+
echo
|
|
408
|
+
agent_status_line
|
|
409
|
+
echo
|
|
410
|
+
return 0
|
|
411
|
+
;;
|
|
412
|
+
/plan | /build)
|
|
413
|
+
if [[ "$mode" == "ask" ]]; then
|
|
414
|
+
echo
|
|
415
|
+
log_warn "Plan/Build modes only exist in ${D_CYAN}karnel agent run${D_NC} — ask is a plain chat"
|
|
416
|
+
echo
|
|
417
|
+
return 0
|
|
418
|
+
fi
|
|
419
|
+
if [[ "$line" == "/plan" ]]; then
|
|
420
|
+
AGENT_PLAN_MODE=1
|
|
421
|
+
echo
|
|
422
|
+
log_success "Mode → ${D_CYAN}PLAN (read-only)${D_NC} — file blocks and write commands will be ignored"
|
|
423
|
+
list_item "Next task will only run read-only commands and produce a plan"
|
|
424
|
+
else
|
|
425
|
+
AGENT_PLAN_MODE=0
|
|
426
|
+
echo
|
|
427
|
+
log_success "Mode → ${D_CYAN}BUILD (full access)${D_NC} — files and commands will be applied"
|
|
428
|
+
fi
|
|
429
|
+
echo
|
|
430
|
+
return 0
|
|
431
|
+
;;
|
|
432
|
+
/voice)
|
|
433
|
+
echo
|
|
434
|
+
printf ' %sListening… speak now, then stop the dialog when done%s\n' "$D_CYAN" "$NC"
|
|
435
|
+
echo
|
|
436
|
+
if agent_voice_capture; then
|
|
437
|
+
AGENT_REPL_PREFILL="$AGENT_VOICE_TEXT"
|
|
438
|
+
log_success "Transcribed — edit it below, then press Enter to send:"
|
|
439
|
+
printf ' %s%s%s\n' "$D_GREEN" "$AGENT_VOICE_TEXT" "$NC"
|
|
440
|
+
fi
|
|
441
|
+
echo
|
|
442
|
+
return 0
|
|
443
|
+
;;
|
|
444
|
+
/model*)
|
|
445
|
+
local m="${line#/model }"
|
|
446
|
+
m="${m#/model}"
|
|
447
|
+
m=$(echo "$m" | xargs)
|
|
448
|
+
if [[ -z "$m" ]]; then
|
|
449
|
+
log_info "Available models:"
|
|
450
|
+
agent_models_list 2>/dev/null | sed 's/^/ /' || log_warn "Cannot list models (check endpoint)"
|
|
451
|
+
agent_read_input "Model name" m
|
|
452
|
+
fi
|
|
453
|
+
if [[ -z "$m" ]]; then return 0; fi
|
|
454
|
+
AGENT_MODEL="$m"
|
|
455
|
+
log_success "Model → ${D_CYAN}$AGENT_MODEL${D_NC}"
|
|
456
|
+
return 0
|
|
457
|
+
;;
|
|
458
|
+
/endpoint*)
|
|
459
|
+
local e="${line#/endpoint }"
|
|
460
|
+
e="${e#/endpoint}"
|
|
461
|
+
e=$(echo "$e" | xargs)
|
|
462
|
+
if [[ -z "$e" ]]; then agent_read_input "Endpoint URL (e.g. http://127.0.0.1:8000/v1)" e; fi
|
|
463
|
+
if [[ -z "$e" ]]; then return 0; fi
|
|
464
|
+
AGENT_ENDPOINT="$e"
|
|
465
|
+
log_success "Endpoint → ${D_CYAN}$AGENT_ENDPOINT${D_NC}"
|
|
466
|
+
return 0
|
|
467
|
+
;;
|
|
468
|
+
/temp*)
|
|
469
|
+
local t="${line#/temp }"
|
|
470
|
+
t="${t#/temp}"
|
|
471
|
+
t=$(echo "$t" | xargs)
|
|
472
|
+
if [[ -z "$t" ]]; then agent_read_input "Temperature (0-2)" t; fi
|
|
473
|
+
if [[ -n "$t" ]]; then AGENT_TEMPERATURE="$t"; log_success "Temperature → ${D_CYAN}$AGENT_TEMPERATURE${D_NC}"; fi
|
|
474
|
+
return 0
|
|
475
|
+
;;
|
|
476
|
+
/max*)
|
|
477
|
+
local mx="${line#/max }"
|
|
478
|
+
mx="${mx#/max}"
|
|
479
|
+
mx=$(echo "$mx" | xargs)
|
|
480
|
+
if [[ -z "$mx" ]]; then agent_read_input "Max tokens" mx; fi
|
|
481
|
+
if [[ -n "$mx" ]]; then AGENT_MAX_TOKENS="$mx"; log_success "Max tokens → ${D_CYAN}$AGENT_MAX_TOKENS${D_NC}"; fi
|
|
482
|
+
return 0
|
|
483
|
+
;;
|
|
484
|
+
/workspace*)
|
|
485
|
+
local ws="${line#/workspace }"
|
|
486
|
+
ws="${ws#/workspace}"
|
|
487
|
+
ws=$(echo "$ws" | xargs)
|
|
488
|
+
if [[ -z "$ws" ]]; then agent_read_input "Workspace directory (absolute path)" ws; fi
|
|
489
|
+
if [[ -n "$ws" ]]; then
|
|
490
|
+
mkdir -p "$ws"
|
|
491
|
+
AGENT_WORKSPACE="$ws"
|
|
492
|
+
log_success "Workspace → ${D_CYAN}$AGENT_WORKSPACE${D_NC}"
|
|
493
|
+
fi
|
|
494
|
+
return 0
|
|
495
|
+
;;
|
|
496
|
+
/history)
|
|
497
|
+
echo
|
|
498
|
+
if [[ "$(echo "$AGENT_REPL_HISTORY" | jq 'length')" == "0" ]]; then
|
|
499
|
+
log_warn "No history yet"
|
|
500
|
+
else
|
|
501
|
+
echo "$AGENT_REPL_HISTORY" | jq -r '.[] | " [" + .role + "] " + (.content | .[0:120] | gsub("[\r\n]"; " "))' | sed 's/^/ /'
|
|
502
|
+
fi
|
|
503
|
+
echo
|
|
504
|
+
return 0
|
|
505
|
+
;;
|
|
506
|
+
*)
|
|
507
|
+
return 2
|
|
508
|
+
;;
|
|
509
|
+
esac
|
|
510
|
+
}
|
|
511
|
+
# ------------------------------------------------------------
|
|
512
|
+
# agent_ask — entry point
|
|
513
|
+
# ------------------------------------------------------------
|
|
514
|
+
agent_ask() {
|
|
515
|
+
agent_parse_args "$@"
|
|
516
|
+
if (( AGENT_SHOW_HELP )); then
|
|
517
|
+
agent_help
|
|
518
|
+
return 0
|
|
519
|
+
fi
|
|
520
|
+
if [[ -n "$AGENT_PROMPT" ]]; then
|
|
521
|
+
# traps before ensure: Ctrl+C during the startup wait also stops it
|
|
522
|
+
trap 'agent_server_stop; exit 130' INT
|
|
523
|
+
trap 'agent_server_stop' EXIT
|
|
524
|
+
agent_server_ensure
|
|
525
|
+
agent_ask_once "$AGENT_PROMPT"
|
|
526
|
+
agent_server_stop
|
|
527
|
+
trap - INT EXIT
|
|
528
|
+
else
|
|
529
|
+
agent_ask_repl
|
|
530
|
+
fi
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
# ------------------------------------------------------------
|
|
534
|
+
# agent_ask_repl — interactive chat with streaming
|
|
535
|
+
# ------------------------------------------------------------
|
|
536
|
+
agent_ask_repl() {
|
|
537
|
+
AGENT_REPL_HISTORY=$(agent_history_new)
|
|
538
|
+
AGENT_REPL_STEPS=0
|
|
539
|
+
AGENT_REPL_MODE=ask
|
|
540
|
+
|
|
541
|
+
# traps must exist before the server starts, so Ctrl+C during the
|
|
542
|
+
# startup wait also stops a server we just launched
|
|
543
|
+
trap 'agent_server_stop; exit 130' INT
|
|
544
|
+
trap 'agent_server_stop' EXIT
|
|
545
|
+
|
|
546
|
+
# start the model server first (if down): shows the starting message
|
|
547
|
+
# and waits for it with a loading spinner, then clear the screen so
|
|
548
|
+
# the welcome banner starts clean.
|
|
549
|
+
agent_server_ensure
|
|
550
|
+
clear
|
|
551
|
+
|
|
552
|
+
agent_banner
|
|
553
|
+
separator_section "Karnel Agent — Ask"
|
|
554
|
+
echo
|
|
555
|
+
agent_status_line
|
|
556
|
+
echo
|
|
557
|
+
list_item "Type ${D_CYAN}/help${D_NC} to see all commands"
|
|
558
|
+
echo
|
|
559
|
+
|
|
560
|
+
agent_repl_bind
|
|
561
|
+
|
|
562
|
+
local line outfile resp _ems
|
|
563
|
+
while true; do
|
|
564
|
+
if ! agent_repl_read; then
|
|
565
|
+
break
|
|
566
|
+
fi
|
|
567
|
+
line="$AGENT_REPL_LINE"
|
|
568
|
+
[[ -z "$line" ]] && continue
|
|
569
|
+
|
|
570
|
+
agent_repl_handle "$line" "$AGENT_REPL_MODE"
|
|
571
|
+
local hc=$?
|
|
572
|
+
if (( hc == 1 )); then
|
|
573
|
+
break
|
|
574
|
+
elif (( hc == 0 )); then
|
|
575
|
+
continue
|
|
576
|
+
fi
|
|
577
|
+
|
|
578
|
+
# shell mode: !command runs it and feeds the output to the agent
|
|
579
|
+
if [[ "$line" == '!'* ]]; then
|
|
580
|
+
agent_shell_cmd "$line"
|
|
581
|
+
continue
|
|
582
|
+
fi
|
|
583
|
+
|
|
584
|
+
# actual message — expand @file references
|
|
585
|
+
line=$(agent_prepare_prompt "$line")
|
|
586
|
+
if ! agent_check_server; then echo; continue; fi
|
|
587
|
+
AGENT_REPL_HISTORY=$(agent_history_add "$AGENT_REPL_HISTORY" user "$line")
|
|
588
|
+
AGENT_REPL_HISTORY=$(agent_history_ensure_system "$AGENT_REPL_HISTORY" "$AGENT_ASK_SYSTEM")
|
|
589
|
+
AGENT_REPL_HISTORY=$(agent_history_manage "$AGENT_REPL_HISTORY" "$AGENT_CONTEXT_WINDOW")
|
|
590
|
+
|
|
591
|
+
echo
|
|
592
|
+
outfile=$(agent_md_dir)/ask_stream.txt
|
|
593
|
+
agent_timer_start
|
|
594
|
+
AGENT_ABORT=0
|
|
595
|
+
if agent_exec_loading "Asking ${D_CYAN}$AGENT_MODEL${D_NC}…" agent_chat_text_to "$AGENT_REPL_HISTORY" "$outfile"; then
|
|
596
|
+
_ems=$(agent_timer_ms)
|
|
597
|
+
resp=$(agent_clean_output "$(cat "$outfile")")
|
|
598
|
+
echo
|
|
599
|
+
if [[ -n "$resp" ]]; then
|
|
600
|
+
agent_show_answer "$resp"
|
|
601
|
+
AGENT_REPL_HISTORY=$(agent_history_add "$AGENT_REPL_HISTORY" assistant "$resp")
|
|
602
|
+
printf ' %s[context %s]%s\n' "$D_GRAY" "$(agent_context_footer "$AGENT_REPL_HISTORY" "$_ems")" "$NC"
|
|
603
|
+
fi
|
|
604
|
+
elif (( AGENT_ABORT )); then
|
|
605
|
+
echo
|
|
606
|
+
continue
|
|
607
|
+
else
|
|
608
|
+
log_error "Request failed (is the server running?)"
|
|
609
|
+
echo
|
|
610
|
+
fi
|
|
611
|
+
done
|
|
612
|
+
agent_repl_unbind
|
|
613
|
+
agent_server_stop
|
|
614
|
+
trap - INT EXIT
|
|
615
|
+
echo
|
|
616
|
+
if (( AGENT_CANCEL )); then
|
|
617
|
+
log_warn "Aborted — you pressed ESC ESC"
|
|
618
|
+
echo
|
|
619
|
+
fi
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
# ------------------------------------------------------------
|
|
623
|
+
# agent_run — entry point
|
|
624
|
+
# ------------------------------------------------------------
|
|
625
|
+
agent_run() {
|
|
626
|
+
agent_parse_args "$@"
|
|
627
|
+
if (( AGENT_SHOW_HELP )); then
|
|
628
|
+
agent_help
|
|
629
|
+
return 0
|
|
630
|
+
fi
|
|
631
|
+
AGENT_ACTIONS_WORKSPACE="$AGENT_WORKSPACE"
|
|
632
|
+
mkdir -p "$AGENT_WORKSPACE" 2>/dev/null
|
|
633
|
+
|
|
634
|
+
if [[ -n "$AGENT_PROMPT" ]]; then
|
|
635
|
+
# traps before ensure: Ctrl+C during the startup wait also stops it
|
|
636
|
+
trap 'agent_server_stop; exit 130' INT
|
|
637
|
+
trap 'agent_server_stop' EXIT
|
|
638
|
+
agent_server_ensure
|
|
639
|
+
AGENT_PROMPT=$(agent_prepare_prompt "$AGENT_PROMPT")
|
|
640
|
+
agent_run_loop "$AGENT_PROMPT"
|
|
641
|
+
agent_server_stop
|
|
642
|
+
trap - INT EXIT
|
|
643
|
+
else
|
|
644
|
+
agent_run_repl
|
|
645
|
+
fi
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
# ------------------------------------------------------------
|
|
649
|
+
# agent_run_repl — interactive: every message is a new task
|
|
650
|
+
# ------------------------------------------------------------
|
|
651
|
+
agent_run_repl() {
|
|
652
|
+
AGENT_REPL_HISTORY=$(agent_history_new)
|
|
653
|
+
AGENT_REPL_STEPS=0
|
|
654
|
+
AGENT_REPL_MODE=run
|
|
655
|
+
|
|
656
|
+
# traps must exist before the server starts, so Ctrl+C during the
|
|
657
|
+
# startup wait also stops a server we just launched
|
|
658
|
+
trap 'agent_server_stop; exit 130' INT
|
|
659
|
+
trap 'agent_server_stop' EXIT
|
|
660
|
+
|
|
661
|
+
# start the model server first (if down): shows the starting message
|
|
662
|
+
# and waits for it with a loading spinner, then clear the screen so
|
|
663
|
+
# the welcome banner starts clean.
|
|
664
|
+
agent_server_ensure
|
|
665
|
+
clear
|
|
666
|
+
|
|
667
|
+
agent_banner
|
|
668
|
+
separator_section "Karnel Agent — Run ($([[ $AGENT_PLAN_MODE == 1 ]] && echo 'PLAN · read-only' || echo 'BUILD mode'))"
|
|
669
|
+
echo
|
|
670
|
+
agent_status_line
|
|
671
|
+
echo
|
|
672
|
+
list_item "Type ${D_CYAN}/help${D_NC} to see all commands"
|
|
673
|
+
echo
|
|
674
|
+
|
|
675
|
+
agent_repl_bind
|
|
676
|
+
|
|
677
|
+
local line
|
|
678
|
+
while true; do
|
|
679
|
+
if ! agent_repl_read; then
|
|
680
|
+
break
|
|
681
|
+
fi
|
|
682
|
+
line="$AGENT_REPL_LINE"
|
|
683
|
+
[[ -z "$line" ]] && continue
|
|
684
|
+
|
|
685
|
+
agent_repl_handle "$line" "$AGENT_REPL_MODE"
|
|
686
|
+
local hc=$?
|
|
687
|
+
if (( hc == 1 )); then
|
|
688
|
+
break
|
|
689
|
+
elif (( hc == 0 )); then
|
|
690
|
+
continue
|
|
691
|
+
fi
|
|
692
|
+
|
|
693
|
+
# shell mode: !command runs it and feeds the output to the agent
|
|
694
|
+
if [[ "$line" == '!'* ]]; then
|
|
695
|
+
agent_shell_cmd "$line"
|
|
696
|
+
continue
|
|
697
|
+
fi
|
|
698
|
+
|
|
699
|
+
# expand @file references
|
|
700
|
+
line=$(agent_prepare_prompt "$line")
|
|
701
|
+
if ! agent_check_server; then echo; continue; fi
|
|
702
|
+
AGENT_ACTIONS_WORKSPACE="$AGENT_WORKSPACE"
|
|
703
|
+
agent_run_loop "$line" "$AGENT_REPL_HISTORY"
|
|
704
|
+
done
|
|
705
|
+
agent_repl_unbind
|
|
706
|
+
agent_server_stop
|
|
707
|
+
trap - INT EXIT
|
|
708
|
+
echo
|
|
709
|
+
if (( AGENT_CANCEL )); then
|
|
710
|
+
log_warn "Aborted — you pressed ESC ESC"
|
|
711
|
+
echo
|
|
712
|
+
fi
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
# ------------------------------------------------------------
|
|
716
|
+
# agent_run_loop <prompt> [history] — the karnel agent loop:
|
|
717
|
+
# LLM answers in markdown → bash parses files & commands →
|
|
718
|
+
# bash creates the files / runs the approved commands →
|
|
719
|
+
# results go back to the LLM until it writes a final summary
|
|
720
|
+
# ------------------------------------------------------------
|
|
721
|
+
agent_run_loop() {
|
|
722
|
+
local user_prompt="$1"
|
|
723
|
+
local shared_history="${2:-}"
|
|
724
|
+
local system history
|
|
725
|
+
system=$(agent_system_prompt "$AGENT_WORKSPACE")
|
|
726
|
+
if [[ -n "$shared_history" && "$shared_history" != "[]" ]]; then
|
|
727
|
+
history="$shared_history"
|
|
728
|
+
else
|
|
729
|
+
history=$(agent_history_new)
|
|
730
|
+
fi
|
|
731
|
+
# always (re)set the front system prompt to the CURRENT mode's prompt
|
|
732
|
+
history=$(agent_history_ensure_system "$history" "$system")
|
|
733
|
+
history=$(agent_history_add "$history" user "$user_prompt")
|
|
734
|
+
|
|
735
|
+
AGENT_ACTIONS_WORKSPACE="$AGENT_WORKSPACE"
|
|
736
|
+
AGENT_ABORT=0
|
|
737
|
+
agent_timer_start
|
|
738
|
+
# fresh read-dedup registry per task
|
|
739
|
+
: >"$(agent_md_dir)/ran_commands.txt"
|
|
740
|
+
|
|
741
|
+
local iter=0 step=0 resp outfile parsed nfiles ncmds results rfile _ems
|
|
742
|
+
while (( iter < AGENT_MAX_ITERATIONS )); do
|
|
743
|
+
step=$((iter + 1))
|
|
744
|
+
printf ' %s[%s/%s]%s %sthinking…%s\n' "$D_GRAY" "$step" "$AGENT_MAX_ITERATIONS" "$NC" "$D_PURPLE" "$NC"
|
|
745
|
+
|
|
746
|
+
outfile=$(agent_md_dir)/run_stream.txt
|
|
747
|
+
if agent_exec_loading " model reasoning…" agent_chat_text_to "$history" "$outfile"; then
|
|
748
|
+
resp=$(agent_clean_output "$(cat "$outfile")")
|
|
749
|
+
elif (( AGENT_ABORT )); then
|
|
750
|
+
break
|
|
751
|
+
else
|
|
752
|
+
log_error "Request failed (is the server running?)"
|
|
753
|
+
break
|
|
754
|
+
fi
|
|
755
|
+
if [[ -z "$resp" ]]; then
|
|
756
|
+
log_error "Empty response from the model (is the server running?)"
|
|
757
|
+
break
|
|
758
|
+
fi
|
|
759
|
+
|
|
760
|
+
# visual feedback: render the full markdown answer on screen
|
|
761
|
+
echo
|
|
762
|
+
md_render "$resp"
|
|
763
|
+
|
|
764
|
+
parsed=$(agent_parse_response "$resp")
|
|
765
|
+
read -r nfiles ncmds <<<"$parsed"
|
|
766
|
+
|
|
767
|
+
# no files and no commands → this is the final answer
|
|
768
|
+
if (( nfiles == 0 && ncmds == 0 )); then
|
|
769
|
+
history=$(agent_history_add "$history" assistant "$resp")
|
|
770
|
+
break
|
|
771
|
+
fi
|
|
772
|
+
|
|
773
|
+
history=$(agent_history_add "$history" assistant "$resp")
|
|
774
|
+
|
|
775
|
+
AGENT_FILES_WRITTEN=0
|
|
776
|
+
if (( nfiles > 0 )); then
|
|
777
|
+
if (( AGENT_PLAN_MODE )); then
|
|
778
|
+
# PLAN mode: never write; just tell the model what was skipped
|
|
779
|
+
echo
|
|
780
|
+
separator_section "Plan mode — files skipped"
|
|
781
|
+
printf ' %s↳ %s ## File: block(s) NOT written (read-only mode)%s\n' "$D_GRAY" "$nfiles" "$NC"
|
|
782
|
+
AGENT_FILES_WRITTEN=0
|
|
783
|
+
else
|
|
784
|
+
agent_apply_files
|
|
785
|
+
# file writes invalidate earlier reads: allow re-reading to verify
|
|
786
|
+
(( AGENT_FILES_WRITTEN > 0 )) && : >"$(agent_md_dir)/ran_commands.txt"
|
|
787
|
+
fi
|
|
788
|
+
fi
|
|
789
|
+
|
|
790
|
+
results=""
|
|
791
|
+
if (( ncmds > 0 )); then
|
|
792
|
+
echo
|
|
793
|
+
separator_section "Running commands"
|
|
794
|
+
agent_execute_commands
|
|
795
|
+
if (( AGENT_ABORT )); then
|
|
796
|
+
break
|
|
797
|
+
fi
|
|
798
|
+
rfile="$(agent_md_dir)/command_results.txt"
|
|
799
|
+
[[ -s "$rfile" ]] && results=$(cat "$rfile")
|
|
800
|
+
fi
|
|
801
|
+
|
|
802
|
+
# The model already wrote its final summary in this response and
|
|
803
|
+
# there is no command output left to fold in — the task is done.
|
|
804
|
+
# Only files were written (or nothing at all), so forcing another
|
|
805
|
+
# iteration just to repeat the summary would be wasteful.
|
|
806
|
+
if (( ! AGENT_PLAN_MODE )) && [[ -z "$results" ]] && agent_response_has_summary "$resp"; then
|
|
807
|
+
break
|
|
808
|
+
fi
|
|
809
|
+
|
|
810
|
+
if [[ -n "$results" ]]; then
|
|
811
|
+
history=$(agent_history_add "$history" user "$results")
|
|
812
|
+
fi
|
|
813
|
+
|
|
814
|
+
if (( AGENT_PLAN_MODE )); then
|
|
815
|
+
# plan mode: keep the model from trying to apply changes
|
|
816
|
+
if (( nfiles > 0 )); then
|
|
817
|
+
history=$(agent_history_add "$history" user "<system>PLAN mode is active (read-only): your ## File: blocks were NOT written and write commands were blocked. Only read-only commands ran. Now present your plan (files to create or edit with their proposed content, commands to run). Do NOT emit more file blocks or write commands until the user switches to BUILD mode (type /build).</system>")
|
|
818
|
+
elif [[ -z "$results" ]]; then
|
|
819
|
+
history=$(agent_history_add "$history" user "<system>PLAN mode is active (read-only). Keep inspecting with read-only command blocks if needed; when ready, write your analysis and plan as a final summary with NO file or command blocks.</system>")
|
|
820
|
+
fi
|
|
821
|
+
elif [[ -z "$results" ]]; then
|
|
822
|
+
if (( AGENT_FILES_WRITTEN > 0 )); then
|
|
823
|
+
history=$(agent_history_add "$history" user "<system>Files were created or updated. Verify them with a command block if needed, then write your final summary (markdown) with no file or command blocks.</system>")
|
|
824
|
+
else
|
|
825
|
+
history=$(agent_history_add "$history" user "<system>No files were written and no commands ran (any file blocks the response contained targeted write-protected no-read files, or were identical to files already on disk, and were skipped). If the task needs a shell action (delete/move/rename/run/...), you MUST emit it as a \`$ \` or \`\`\`bash command block — a \`## File:\` block never runs anything and is ignored for write-protected files. Then finish with a short summary.</system>")
|
|
826
|
+
fi
|
|
827
|
+
fi
|
|
828
|
+
|
|
829
|
+
history=$(agent_history_manage "$history" "$AGENT_CONTEXT_WINDOW")
|
|
830
|
+
((iter++))
|
|
831
|
+
done
|
|
832
|
+
|
|
833
|
+
if (( iter >= AGENT_MAX_ITERATIONS )); then
|
|
834
|
+
echo
|
|
835
|
+
log_warn "Stopped after $AGENT_MAX_ITERATIONS iterations (task too complex?)"
|
|
836
|
+
fi
|
|
837
|
+
AGENT_REPL_HISTORY="$history"
|
|
838
|
+
_ems=$(agent_timer_ms)
|
|
839
|
+
echo
|
|
840
|
+
printf ' %s[context %s]%s\n' "$D_GRAY" "$(agent_context_footer "$AGENT_REPL_HISTORY" "$_ems")" "$NC"
|
|
841
|
+
}
|