@semalt-ai/code 1.19.0 → 1.20.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/.claude/settings.local.json +2 -1
- package/ARCHITECTURE.md +6 -95
- package/CLAUDE.md +196 -1874
- package/README.md +1 -1
- package/docs/ARCHITECTURE.md +1321 -0
- package/docs/CONFIG.md +340 -0
- package/docs/HISTORY.md +245 -0
- package/index.js +1 -1
- package/lib/agent.js +145 -16
- package/lib/api.js +28 -3
- package/lib/commands/chat-session.js +187 -4
- package/lib/commands/chat-slash.js +16 -0
- package/lib/commands/chat-turn.js +272 -49
- package/lib/commands/chat.js +12 -8
- package/lib/config.js +27 -0
- package/lib/constants.js +30 -1
- package/lib/headless.js +36 -1
- package/lib/images.js +8 -2
- package/lib/permissions.js +23 -16
- package/lib/prompts.js +15 -3
- package/lib/tool_registry.js +357 -53
- package/lib/tool_specs.js +42 -8
- package/lib/tools.js +80 -19
- package/lib/ui/anim.js +86 -0
- package/lib/ui/ansi.js +17 -27
- package/lib/ui/chat-history.js +253 -71
- package/lib/ui/create-ui.js +67 -24
- package/lib/ui/diff.js +90 -25
- package/lib/ui/file-activity.js +236 -0
- package/lib/ui/format.js +173 -28
- package/lib/ui/input-field.js +5 -4
- package/lib/ui/md-stream.js +234 -0
- package/lib/ui/render-operation.js +113 -0
- package/lib/ui/select.js +1 -4
- package/lib/ui/status-bar.js +99 -57
- package/lib/ui/stream.js +20 -13
- package/lib/ui/theme.js +190 -45
- package/lib/ui/tool-operation.js +190 -0
- package/lib/ui/utils.js +9 -5
- package/lib/ui/web-activity.js +58 -6
- package/lib/ui/writer.js +159 -45
- package/lib/ui.js +1 -1
- package/package.json +1 -1
- package/test/anim-driver.test.js +153 -0
- package/test/ask-user-display.test.js +226 -0
- package/test/ask-user-gate.test.js +231 -0
- package/test/chat-history-nocolor.test.js +155 -0
- package/test/chat-relogin.test.js +207 -0
- package/test/defer-detail-band.test.js +403 -0
- package/test/detail-band-tab-flatten.test.js +242 -0
- package/test/exec-diff.test.js +268 -0
- package/test/executors.test.js +250 -13
- package/test/extract-tool-calls.test.js +37 -3
- package/test/file-activity.test.js +522 -0
- package/test/grep-path-target.test.js +227 -0
- package/test/harness/chat-harness.js +2 -1
- package/test/headless.test.js +146 -1
- package/test/input-field-ctrl-o.test.js +37 -0
- package/test/live-height-physical.test.js +281 -0
- package/test/max-iterations.test.js +9 -7
- package/test/md-stream.test.js +183 -0
- package/test/native-dispatch.test.js +53 -0
- package/test/native-live-narration.test.js +254 -0
- package/test/output-heredoc-leak.test.js +195 -0
- package/test/output-preview.test.js +245 -0
- package/test/permissions.test.js +199 -0
- package/test/read-paginate.test.js +1 -1
- package/test/render-operation.test.js +317 -0
- package/test/replay-descriptor-xml.test.js +216 -0
- package/test/replay-descriptor.test.js +189 -0
- package/test/replay-web-aggregate.test.js +291 -0
- package/test/replay-web-persist.test.js +241 -0
- package/test/running-glyph-anim.test.js +111 -0
- package/test/status-bar-driver.test.js +93 -0
- package/test/status-bar-resync.test.js +188 -0
- package/test/stream-parser.test.js +24 -0
- package/test/theme-palette.test.js +166 -0
- package/test/truncate-visible.test.js +78 -0
- package/test/view-image.test.js +199 -0
- package/test/web-activity-ordering.test.js +12 -3
- package/path +0 -1
package/docs/CONFIG.md
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
# semalt-code — Config & CLI Reference
|
|
2
|
+
|
|
3
|
+
> Full per-key config reference, the CLI command/flag reference, in-chat slash
|
|
4
|
+
> commands, tool tags, tool operations, and dashboard API endpoints. **Not
|
|
5
|
+
> auto-loaded** as project memory. The authoritative runtime sources are
|
|
6
|
+
> `--help`, `lib/tool_specs.js`, `lib/prompts.js`, `lib/config.js`, and
|
|
7
|
+
> `lib/api.js`; this file mirrors them for humans.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## CLI Commands
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
semalt-code # interactive chat (default)
|
|
15
|
+
semalt-code chat # interactive chat (explicit)
|
|
16
|
+
semalt-code code <prompt> # one-shot task with optional file context
|
|
17
|
+
semalt-code edit <file> <instruction> # targeted file edit
|
|
18
|
+
semalt-code shell <command> # run shell, optionally ask LLM to analyze output
|
|
19
|
+
semalt-code run --background <prompt> # launch a detached background agent task (Task 5.3)
|
|
20
|
+
semalt-code tasks list|status|result|kill|prune # manage background tasks (Task 5.3)
|
|
21
|
+
semalt-code login # browser-based device auth against dashboard
|
|
22
|
+
semalt-code logout # clear stored auth_token
|
|
23
|
+
semalt-code whoami # show authenticated user
|
|
24
|
+
semalt-code models # interactive model selector (fetches from dashboard)
|
|
25
|
+
semalt-code init [options] # create/update ~/.semalt-ai/config.json
|
|
26
|
+
semalt-code audit # print last 50 audit log entries
|
|
27
|
+
semalt-code rewind [seq] [code|conversation|both] # list checkpoints / restore files and/or conversation (latest session; default both)
|
|
28
|
+
semalt-code sandbox # show OS sandbox status (mode, tool, availability, install hint)
|
|
29
|
+
semalt-code doctor # self-diagnostics (config, dashboard, model, audit, key, memory)
|
|
30
|
+
semalt-code config [set <key> <val>] # show or update config keys
|
|
31
|
+
semalt-code auth set-key [key] # store API key in the OS keychain (not plaintext)
|
|
32
|
+
semalt-code mcp list|status|add|remove|auth # manage MCP servers (Task 3.3)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Common Flags
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
-m, --model <name> override model for this invocation
|
|
39
|
+
-p, --print headless one-shot mode (no interactive chat)
|
|
40
|
+
--output-format <fmt> text | json | stream-json (implies -p). Per-tool recs
|
|
41
|
+
(json toolCalls[] / stream-json tool events) carry the
|
|
42
|
+
legacy { tool, args, ok, ms } PLUS additive descriptor
|
|
43
|
+
fields (status, category, durationMs, target, attrs,
|
|
44
|
+
meta, error, noDuration, detail). See ARCHITECTURE.md.
|
|
45
|
+
-r, --resume <chat-id> resume a dashboard chat by ID
|
|
46
|
+
-f, --file <path> load file or directory as context
|
|
47
|
+
--image <path> attach an image (PNG/JPEG/WebP/GIF) to the turn;
|
|
48
|
+
repeatable. Read through isPathSafe, size-capped,
|
|
49
|
+
base64-encoded. Sent to a vision model only — a
|
|
50
|
+
text-only model errors loudly (Task 5.4)
|
|
51
|
+
-a, --analyze have LLM analyze shell output (used with `shell`)
|
|
52
|
+
--dry-run preview file edits without writing
|
|
53
|
+
--api-base <url> LLM API base URL (overrides config)
|
|
54
|
+
--api-key <key> API key (overrides config)
|
|
55
|
+
--dashboard-url <url> dashboard base URL (overrides config)
|
|
56
|
+
--default-model <name> set default model in config
|
|
57
|
+
--show-think display model reasoning (thinking) content
|
|
58
|
+
--debug inline debug: per-iteration debug block in chat history (TUI-safe)
|
|
59
|
+
--debug-file <path> extended debug: per-iteration block + raw SSE chunks
|
|
60
|
+
+ request body dumps written to <path>, nothing to stdout.
|
|
61
|
+
Mutually exclusive with --debug.
|
|
62
|
+
--allow-fs auto-approve all filesystem operations
|
|
63
|
+
--allow-exec auto-approve shell command execution
|
|
64
|
+
--allow-net auto-approve network operations
|
|
65
|
+
--allow-all auto-approve everything (use carefully)
|
|
66
|
+
--allow-anywhere allow writes outside CWD / sensitive dirs (NOT secret-file reads)
|
|
67
|
+
--no-network kernel-level no-network for sandboxed shell commands
|
|
68
|
+
(bwrap --unshare-net / Seatbelt deny network*). Binary
|
|
69
|
+
on/off — no host proxy, no allowlist, no TLS interception.
|
|
70
|
+
Same as sandbox.network "off" in config. Human-only.
|
|
71
|
+
--dangerously-skip-permissions the ONLY full opt-out: auto-approve all, disable deny-list
|
|
72
|
+
and secret-file guard. Required to auto-approve in non-TTY mode.
|
|
73
|
+
--readonly block all file-mutating tools (write_file, append_file,
|
|
74
|
+
edit_file, replace_in_file, delete_file, make_dir,
|
|
75
|
+
remove_dir, move_file, copy_file, upload, download).
|
|
76
|
+
File TOOLS only — shell side effects are NOT constrained
|
|
77
|
+
by --readonly (so read-only commands like `ls`/`git status`
|
|
78
|
+
still run); shell writes are confined by the OS sandbox +
|
|
79
|
+
deny-list, the correct layer for that.
|
|
80
|
+
--plan plan mode: propose a plan, withhold mutating tools until approved
|
|
81
|
+
--reasoning-effort <lvl> minimal|low|medium|high — sent only for reasoning models
|
|
82
|
+
--prompt-caching send cache_control markers on the stable prefix (opt-in)
|
|
83
|
+
--max-iterations <n> cap agent-loop iterations per turn (default 125); 0 or
|
|
84
|
+
"unlimited" removes the cap (power-user choice)
|
|
85
|
+
--no-verify skip self-verification (config.verify) for this run,
|
|
86
|
+
in both advisory and enforcing modes (Task 4.2)
|
|
87
|
+
-v, --version print version
|
|
88
|
+
-h, --help print help
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### In-Chat Slash Commands
|
|
92
|
+
|
|
93
|
+
| Command | Effect |
|
|
94
|
+
|---------|--------|
|
|
95
|
+
| `/help` | List slash commands |
|
|
96
|
+
| `/file <path>` | Attach file or directory to context |
|
|
97
|
+
| `/image <path>` | Stage an image (PNG/JPEG/WebP/GIF) for your next message (Task 5.4) |
|
|
98
|
+
| `/history` | Browse and load a local saved session |
|
|
99
|
+
| `/chats` | Browse and resume a saved chat from the dashboard |
|
|
100
|
+
| `/new` | Start a fresh conversation (detach from current saved chat) |
|
|
101
|
+
| `/model [name]` | Show or switch model |
|
|
102
|
+
| `/models` | Interactive model picker from dashboard |
|
|
103
|
+
| `/shell <cmd>` or `!<cmd>` | Execute shell command |
|
|
104
|
+
| `/compact` | Summarize older turns into a compact summary (preserving recent/pinned), shrinking the context; shows before/after token counts |
|
|
105
|
+
| `/memory` | Show which AGENTS.md/CLAUDE.md project-memory files are loaded and their paths |
|
|
106
|
+
| `/mcp` | Show MCP server connection status and the tools each exposes |
|
|
107
|
+
| `/skills` | List available skills (metadata only; each skill's body loads on invocation) |
|
|
108
|
+
| `/<skill-name>` | Invoke a skill — loads its SKILL.md body into context and submits it to the agent |
|
|
109
|
+
| `/plan` | Toggle plan mode — agent proposes a plan and withholds mutating tools until you run `/plan` again to approve |
|
|
110
|
+
| `/rewind` | List file checkpoints, or `/rewind <seq>` / `/rewind last` to restore one. Optional mode `code` \| `conversation` \| `both` (default **both**) restores files, history, or the linked state; append `force` to override out-of-band edits (force does NOT bypass the restore-path guards). **File-tool changes only — shell side effects are not reversible.** |
|
|
111
|
+
| `/doctor` | Run self-diagnostics: config + resolved layers, dashboard reachability, model/context, audit writability, key source, memory |
|
|
112
|
+
| `/sandbox` | Show OS sandbox status: mode (auto/off), the detected tool (Seatbelt/bubblewrap), whether it's available, the **network mode** (on / kernel-level none), the effective posture (`ON (net:on\|off)`), and an install hint when unavailable |
|
|
113
|
+
| `/clear` | Reset conversation history |
|
|
114
|
+
| `/approve` | Toggle auto-approval of tool calls |
|
|
115
|
+
| `/config` | Print current config |
|
|
116
|
+
| `/login` | Start device auth flow |
|
|
117
|
+
| `/whoami` | Show current user |
|
|
118
|
+
| `/logout` | Clear auth token |
|
|
119
|
+
| `exit` / `quit` | Exit |
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
### Tool Tags (parsed from LLM text)
|
|
125
|
+
|
|
126
|
+
```xml
|
|
127
|
+
<exec>shell command here</exec>
|
|
128
|
+
<shell>shell command here</shell>
|
|
129
|
+
<read_file>/absolute/or/relative/path</read_file>
|
|
130
|
+
<read_file path="/path/to/file"/>
|
|
131
|
+
<read_file path="/path/to/file" start_line="100" end_line="200" show_line_numbers="true"/>
|
|
132
|
+
<write_file path="/path/to/file">file content here</write_file>
|
|
133
|
+
<create_file path="/path/to/file">file content here</create_file>
|
|
134
|
+
<append_file path="/path/to/file">content to append</append_file>
|
|
135
|
+
<list_dir>/path/to/dir</list_dir>
|
|
136
|
+
<search_files pattern="*.ts" dir="src"/>
|
|
137
|
+
<grep pattern="TODO" path="*.js" ignore_case="true"/> <!-- path may also be a specific file or a directory; non-path values are treated as a glob -->
|
|
138
|
+
<grep pattern="T.PICKUP" path="/abs/path/to/file.js"/> <!-- search one explicit file (like search_in_file, but regex+ripgrep) -->
|
|
139
|
+
<glob pattern="src/**/*.ts"/>
|
|
140
|
+
<delete_file>/path/to/file</delete_file>
|
|
141
|
+
<make_dir>/path/to/dir</make_dir>
|
|
142
|
+
<remove_dir>/path/to/dir</remove_dir>
|
|
143
|
+
<get_env>ENV_VAR_NAME</get_env>
|
|
144
|
+
<set_env name="VAR" value="value"/>
|
|
145
|
+
<move_file src="/old/path" dst="/new/path"/>
|
|
146
|
+
<copy_file src="/src/path" dst="/dst/path"/>
|
|
147
|
+
<edit_file path="/file" line="42">replacement line content</edit_file>
|
|
148
|
+
<edit_file path="/file" line="42" end_line="48">multi-line\nblock replacing lines 42–48</edit_file>
|
|
149
|
+
<search_in_file path="/file">regex pattern</search_in_file>
|
|
150
|
+
<replace_in_file path="/file" search="foo(x)" replace="bar(y)"></replace_in_file>
|
|
151
|
+
<replace_in_file path="/file" search="old" replace="new" replace_all="true"></replace_in_file>
|
|
152
|
+
<replace_in_file path="/file" search="id=\d+" replace="id=0" regex="true">g</replace_in_file>
|
|
153
|
+
<download>https://example.com/file.zip</download>
|
|
154
|
+
<download path="dist/file.zip">https://example.com/file.zip</download>
|
|
155
|
+
<upload path="/local/path">base64encodedcontent</upload>
|
|
156
|
+
<file_stat>/path/to/file</file_stat>
|
|
157
|
+
<http_get url="https://example.com/api"/>
|
|
158
|
+
<web_search query="how do tariffs work" count="5"/>
|
|
159
|
+
<ask_user question="What is your preferred language?"/>
|
|
160
|
+
<spawn_agent agent="reviewer">Review the diff in src/ for correctness bugs</spawn_agent>
|
|
161
|
+
<git_status/>
|
|
162
|
+
<git_diff staged="true" path="src"/>
|
|
163
|
+
<git_log count="10"/>
|
|
164
|
+
<git_add paths="a.txt b.txt"/>
|
|
165
|
+
<git_commit message="Fix the parser" all="true"/>
|
|
166
|
+
<git_branch name="feature-x"/>
|
|
167
|
+
<git_checkout name="main" create="true"/>
|
|
168
|
+
<git_worktree op="add" path="../wt" branch="feature"/>
|
|
169
|
+
<store_memory key="project_lang">TypeScript</store_memory>
|
|
170
|
+
<recall_memory key="project_lang"/>
|
|
171
|
+
<list_memories/>
|
|
172
|
+
<system_info/>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The system prompt (`lib/prompts.js`) instructs the LLM to use exactly these tags. Do not change tag names without updating both `prompts.js` and the parser in `agent.js`.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
## Tool Operations (`lib/tools.js`)
|
|
181
|
+
|
|
182
|
+
All operations request permission before execution unless auto-approved.
|
|
183
|
+
**Shell/exec output entering the model context is bounded** by a head+tail line
|
|
184
|
+
cap (`config.max_output_lines`, default 50) plus a token safety net
|
|
185
|
+
(`config.max_output_tokens`, default 10000) — Task W.6, `capShellOutput` in
|
|
186
|
+
`lib/agent.js`; see the shell-output-bounding note under **Key Patterns &
|
|
187
|
+
Invariants**. Other tools cap their own output as documented per-action.
|
|
188
|
+
|
|
189
|
+
| Action | Description |
|
|
190
|
+
|--------|-------------|
|
|
191
|
+
| `read` | Read file content, **paginated** (Task W.7): default returns the first `read_line_cap` (~2000) lines; over the cap the model-facing result ends with a `[PARTIAL]` notice giving the total and the `start_line` for the next page. `start_line`/`end_line` read an explicit slice (also line-capped). `show_line_numbers` (default off) prefixes absolute 1-based numbers for driving `edit_file`. A token safety net (`read_max_tokens`) bounds pathological long lines. Byte cap (`max_file_size_kb`) is now a backstop, not the primary bound |
|
|
192
|
+
| `write` | Write file (creates parent dirs) |
|
|
193
|
+
| `append` | Append to file |
|
|
194
|
+
| `list_dir` | List directory contents |
|
|
195
|
+
| `delete_file` | Delete file |
|
|
196
|
+
| `make_dir` | Create directory (recursive) |
|
|
197
|
+
| `remove_dir` | Remove directory (recursive) |
|
|
198
|
+
| `move_file` | Move/rename file |
|
|
199
|
+
| `copy_file` | Copy file |
|
|
200
|
+
| `search_files` | Find files matching glob pattern |
|
|
201
|
+
| `grep` | Regex search file contents across the tree; **serializes the structured matches (`file:line:text`) into context** so the agent can navigate to a slice instead of reading whole files (Task W.5 — previously the result was dropped and the model got `"grep: done"`). `output_mode`: `content` (default, `file:line:text`), `files_with_matches` (unique paths), `count` (per-file + total). Bounded by `head_limit` (default 100, `lib/constants.js`) + optional `offset`, with a truncation notice when more matched. Honors `.gitignore`, skips binaries + `node_modules`/`.git`; uses ripgrep when present with an identical pure-Node fallback |
|
|
202
|
+
| `glob` | List files matching a glob; **serializes the relative-path list into context** (Task W.5 — previously `"glob: done"`), bounded by `head_limit` (default 100) + `offset` with a truncation notice |
|
|
203
|
+
| `search_in_file` | Regex search within file |
|
|
204
|
+
| `replace_in_file` | Exact string replacement (Claude Code Edit model). The search is matched **literally by default** — verbatim, no regex, at any length — so paste exact code incl. `( ) { } . [ ]`. The match must be **unique**: not-found → **error** (file unchanged); >1 match → **error** unless `replace_all="true"`. Returns the honest replaced count, plus a `warning` if the search string still remains after replacing. `regex="true"` opts into regex mode (inline content = `flags`; bounded by a nested-quantifier guard + 1000-char cap) — the uniqueness guard still applies (`g` flag or `replace_all` to replace many) |
|
|
205
|
+
| `edit_file` | Replace a line, or a contiguous line range, in a file by 1-based number. Optional `end_line` replaces lines `line..end_line` wholesale with the content (a regex-free block edit; pairs with a numbered `read_file` slice) |
|
|
206
|
+
| `get_env` / `set_env` | Read/write environment variables |
|
|
207
|
+
| `download` | HTTP GET → save to file. Confined like every other write path: optional `path` destination defaults to the CWD basename, routed through `isPathSafe` + the secret-file guard, refused under `--readonly`, and size-capped (`download_max_bytes`) — exceeding the cap aborts the stream and removes the partial file. Sends the fixed browser User-Agent (`config.web.user_agent`, Task W.3) |
|
|
208
|
+
| `upload` | Write base64-encoded content to file |
|
|
209
|
+
| `file_stat` | Stat a file (size, mtime, type, mode) |
|
|
210
|
+
| `http_get` | HTTP GET → **web-fetch pipeline** (Task W.1 / W.1b): a three-level `mode` enum — `summarized` (default: Readability extract → Turndown Markdown → secondary-LLM summary, only the compact result enters context), `extracted` (extracted Markdown verbatim, no summary), `raw` (the **original** fetched HTML/content, token-capped — for analyzing markup/CSS/JS/structure). Deprecated `summarize="false"`/`raw="true"` ≡ `mode="extracted"`; `intent="…"` focuses the summary. JSON/plain-text pass through. Sends the fixed browser User-Agent (`config.web.user_agent`, Task W.3 — operator-overridable, never model-selectable). See **Web Fetch Pipeline** |
|
|
211
|
+
| `web_search` | Search the web via the backend `POST /api/search` (SearXNG, Task W.2b): returns a **compact** `{title,url,snippet}` list so the agent picks relevant results and fetches them with `http_get` instead of guessing URLs / fetching every page. Backend-unavailable (down/unreachable/timeout/non-2xx/`{error}`/no-auth/no-config) degrades to a clean tool error — never a crash. Results are fenced as untrusted. `count` is optional + bounded. **Interactive chat / SDK only** (needs the api client; no-op clean error in headless/oneshot wiring without one) |
|
|
212
|
+
| `ask_user` | Prompt user for input; auto-answers 'y' in non-TTY mode |
|
|
213
|
+
| `store_memory` | Persist a key/value pair to `~/.semalt-ai/memory.json` |
|
|
214
|
+
| `recall_memory` | Read a key from `~/.semalt-ai/memory.json` |
|
|
215
|
+
| `list_memories` | List all stored memory keys |
|
|
216
|
+
| `system_info` | Return platform, arch, hostname, memory, Node version, cwd |
|
|
217
|
+
| `spawn_agent` | Launch an isolated child agent loop (optionally a named `.semalt/agents` def, model override, or parallel `tasks[]`); returns only the child's final result, fenced as untrusted (Task 3.6) |
|
|
218
|
+
| `git_status` | Structured working-tree status (staged/unstaged/untracked + branch). Read-only (Task 5.1) |
|
|
219
|
+
| `git_diff` | Structured diff (files, hunks, +/- counts); `staged` for the index diff, optional `path`. Read-only |
|
|
220
|
+
| `git_log` | Recent commits as structured records (hash/short/author/email/date/subject); `count`, optional `path`. Read-only |
|
|
221
|
+
| `git_add` | Stage changes (`paths` or `all`). Mutating |
|
|
222
|
+
| `git_commit` | Commit with a **required non-empty** `message` (empty → error, never a placeholder); returns the new hash + branch. Mutating |
|
|
223
|
+
| `git_branch` | List branches (no `name`, read-only) or create/delete one (`name`, with `delete`/`force`). Create/delete is mutating |
|
|
224
|
+
| `git_checkout` | Switch to a branch/ref (`create` for `-b`, `force` for `-f`). Mutating. **Can discard uncommitted changes — NOT recoverable via `/rewind`** |
|
|
225
|
+
| `git_worktree` | `op: list` (read-only) / `add` (optional new `branch`) / `remove` (`force`) linked worktrees for parallel agents. add/remove mutating |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
## API Client (`lib/api.js`)
|
|
231
|
+
|
|
232
|
+
Handles two distinct concerns:
|
|
233
|
+
|
|
234
|
+
**Inference** (OpenAI-compatible):
|
|
235
|
+
- `chatStream(messages, model, opts)` → streams tokens, calls `onToken`, returns `{ content, usage }`
|
|
236
|
+
- URL: `config.api_base` normalized to include `/v1` if missing
|
|
237
|
+
- Supports `reasoning_content` field for extended-thinking models
|
|
238
|
+
|
|
239
|
+
**Dashboard** (cli.semalt.ai backend):
|
|
240
|
+
- `requestCliLogin()` → `POST /api/auth/cli/request`
|
|
241
|
+
- `getCliLoginStatus(id, token)` → `POST /api/auth/cli/status`
|
|
242
|
+
- `dashboardWhoAmI()` → `GET /api/auth/me`
|
|
243
|
+
- `dashboardLogout()` → `POST /api/auth/logout`
|
|
244
|
+
- `dashboardListModels()` → `GET /api/models`
|
|
245
|
+
- `dashboardGetModelForCli(id)` → `GET /api/models/{id}/cli`
|
|
246
|
+
- `dashboardCreateChat(title, modelDbId)` → `POST /api/chats`
|
|
247
|
+
- `dashboardListChats()` → `GET /api/chats`
|
|
248
|
+
- `dashboardGetChat(id)` → `GET /api/chats/{id}`
|
|
249
|
+
- `dashboardSaveMessages(chatId, messages)` → `POST /api/chats/{id}/messages/batch`
|
|
250
|
+
- `dashboardSearch(query, { count })` → `POST /api/search` (SearXNG-backed web search, Task W.2b; backs the `web_search` tool)
|
|
251
|
+
|
|
252
|
+
All dashboard calls send `Authorization: Bearer <auth_token>` from config.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
## Config File (`~/.semalt-ai/config.json`)
|
|
257
|
+
|
|
258
|
+
Managed by `lib/config.js`. Normalized on every load. The config directory is created automatically if it does not exist.
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"api_base": "http://127.0.0.1:8800",
|
|
263
|
+
"api_key": "any",
|
|
264
|
+
"dashboard_url": "https://cli.semalt.ai",
|
|
265
|
+
"auth_token": "",
|
|
266
|
+
"default_model": "default",
|
|
267
|
+
"dashboard_model_id": null,
|
|
268
|
+
"temperature": 0.7,
|
|
269
|
+
"request_timeout_ms": 900000,
|
|
270
|
+
"stream": true,
|
|
271
|
+
"theme": "dark",
|
|
272
|
+
"max_file_size_kb": 51200,
|
|
273
|
+
"read_line_cap": 2000,
|
|
274
|
+
"read_max_tokens": 25000,
|
|
275
|
+
"command_timeout_ms": 30000,
|
|
276
|
+
"max_output_lines": 50,
|
|
277
|
+
"max_output_tokens": 10000,
|
|
278
|
+
"diff_max_lines": 50,
|
|
279
|
+
"shell_preview_lines": 5,
|
|
280
|
+
"max_iterations": 125,
|
|
281
|
+
"show_token_count": true,
|
|
282
|
+
"show_cost": false,
|
|
283
|
+
"context_length": null,
|
|
284
|
+
"models": [
|
|
285
|
+
{
|
|
286
|
+
"name": "local-llama",
|
|
287
|
+
"api_base": "http://127.0.0.1:11434",
|
|
288
|
+
"api_key": "any",
|
|
289
|
+
"model": "llama3",
|
|
290
|
+
"context_length": 8192
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
- `api_base` is normalized to always include `/v1`.
|
|
297
|
+
- Legacy key `semalt_base_url` is migrated to `api_base` on load.
|
|
298
|
+
- `auth_token` is written by `semalt-code login` and cleared by `logout`.
|
|
299
|
+
- `dashboard_model_id` is the integer PK of the active model in `available_models`; written when a model is selected via `/models`. Required for chat history sync — if null, history sync is silently skipped.
|
|
300
|
+
- `max_file_size_kb` is the `read_file` **byte backstop** (Task W.7; default raised to **50 MB** = 51200 KB). It is **no longer the primary bound** — a large line-readable file **paginates** (`read_line_cap`) rather than hard-refusing; this ceiling only rules out slurping a multi-GB file whole into memory. Lower it to hard-refuse smaller files.
|
|
301
|
+
- `read_line_cap` (Task W.7) caps the lines `read_file` returns per page and the width of an explicit `start_line` window (default 2000). Over the cap, the result carries a `[PARTIAL]` notice with the total and the next `start_line`.
|
|
302
|
+
- `read_max_tokens` (Task W.7) is the token safety net on a `read_file` page (default 25000) — bounds the pathological few-but-enormous-lines case the line cap misses, reusing the web pipeline's `capToTokens`.
|
|
303
|
+
- `command_timeout_ms` caps shell command execution time (default 30 s).
|
|
304
|
+
- `max_output_lines` caps the lines of shell/exec output that enter the model context (default 50), applied as a **head+tail** split (Task W.6 — first ~60% + last ~40%, middle elided) at the context boundary, not just in the UI. Also caps the UI render and HTTP response lines.
|
|
305
|
+
- `max_output_tokens` is the token safety net on shell/exec output entering context (default 10000; Task W.6) — bounds the few-but-huge-lines case the line cap misses. Applied after the line cap via the web pipeline's `capToTokens`.
|
|
306
|
+
- `diff_max_lines` caps the **changed** (`+`/`-`) lines shown in a file-edit diff (default 50). Every mutating file edit (`write_file`/`append_file`/`edit_file`/`replace_in_file`) renders its diff **at execution time** — decoupled from the permission modal, so an **auto-approved** edit shows its changes exactly like a manually-approved one, and the diff is shown **exactly once** per edit (the modal carries only a compact description, never the full diff). A small edit (or a series of small edits, each short) renders in full; one large edit shows **head+tail** of the changed lines (first ~60% + last ~40%, mirroring `max_output_lines`) with a `… K more changed lines (N total)` notice. Loaded history (`--resume` / `/history` / `/chats`) is **not** replayed — only new edits render diffs. UI-only (`renderDiff` / `buildExecutionDiff` in `lib/ui/diff.js`); the model-facing tool result is unchanged.
|
|
307
|
+
- `shell_preview_lines` caps the **chrome preview** of `shell`/MCP/subagent output (default 5; Output Refactor Phase 5). A successful tool's output renders **in moderation** below the result line: the first `shell_preview_lines` lines (each fitted to one physical row) followed by a **static** `… N more lines` hint, where **N is exact** (total − previewed). The preview is collapsed and **non-interactive** — there is no in-terminal way to expand it (full viewing of large output is deferred to the planned full-screen transcript viewer). Output of `≤ shell_preview_lines` lines shows fully with no hint. **Diffs are exempt** — file edits render expanded to `diff_max_lines`, and **errors render expanded** (full body), since both are things the user explicitly wants to see. **UI-only** (`formatOutputPreview` in `lib/ui/format.js`, rendered via `lib/ui/render-operation.js` / committed via `lib/ui/chat-history.js`); the model still receives the **full** output via `boundToolOutput` — this preview never touches model context.
|
|
308
|
+
- `download_max_bytes` caps how many bytes the `download` tool may stream to disk (default 100 MB). Exceeding it aborts the request and removes the partial file, so no truncated artifact is left behind.
|
|
309
|
+
- `web` — normalized to `{ summarize, summary_model, max_content_tokens, user_agent }` (Task W.1 / W.1b / W.3). The `http_get` web-fetch pipeline: `summarize` (default **true**) sets the default `mode` (`summarized` when true, `extracted` when false) — a secondary cheap-LLM summary of the extracted Markdown so only the compact result enters context. Override per-fetch with `mode="extracted"` (verbatim Markdown; deprecated aliases `summarize="false"`/`raw="true"`) or `mode="raw"` (original token-capped HTML/content, for markup/CSS/JS analysis). `summary_model` (`''` → current model) is the cheap model for that call. `max_content_tokens` (default 6000) caps the content fed to the summarizer/context **in every mode incl. raw** — the token-budget that **replaces** the blind `http_fetch_max_bytes` cut as context protection (the byte cap is now only a transfer guard). `user_agent` (Task W.3 Part 2; `''` → the fixed `DEFAULT_USER_AGENT`, a current mainstream-browser string) is the **operator override** for the `http_get`/`download` User-Agent — a **human-only** setting (there is **no UA parameter in the tool spec**, so the agent can never set a per-call UA, an impersonation/evasion surface we deliberately don't expose). A realistic UA defeats only **simple** UA-based bot-blocking (sites that 403/406 an empty/curl-like UA); Cloudflare / JS-challenges / IP-rate-limits still 403 — full coverage would need headless rendering (deferred). See **Web Fetch Pipeline** above.
|
|
310
|
+
- `image_max_bytes` caps the **raw** bytes of an attached image before base64-encoding (default 5 MB; base64 inflates ~33%). Over the cap is a clear error, not an opaque endpoint rejection. `image_format` (`''`|`anthropic`|`openai`) forces the provider content-part shape; `''` selects it heuristically per endpoint. Per-`models[]`-profile `vision` (bool) and `image_format` override for that profile. See **Multimodal Image Input** above (Task 5.4).
|
|
311
|
+
- `max_iterations` caps agent-loop iterations per user turn (default 125; `DEFAULT_MAX_ITERATIONS` in `constants.js`). A positive integer caps the loop; `0` (the stored "unlimited" sentinel — config.json can't hold `Infinity`) removes the cap. `--max-iterations <n>` overrides it (accepts `0`/`unlimited`); entry points resolve the value via `resolveMaxIterations()`. Reaching the cap stops the loop gracefully (warning + `stopReason: "max_iterations"`).
|
|
312
|
+
- `show_token_count` controls whether token count is shown in the status bar.
|
|
313
|
+
- `show_cost` reserved for future cost-display feature.
|
|
314
|
+
- `context_length` / `models[].context_length` — token limit used for context-usage bar, warnings, and proactive trimming. Self-calibrating: when a request triggers a context-overflow 400 (`"context length is only N"`), `api.js` parses the real window, persists it to `config.context_length` (and to the matching `models[]` entry), and trims to ~90% of it on subsequent calls. The value is never cached in memory only — a restart keeps the learned limit.
|
|
315
|
+
- Local `models[]` entries override dashboard models when selected.
|
|
316
|
+
- `models[].native_tools` (bool, default **true**) — only an explicit `false`/`0`/`"false"`/`"0"` opts the profile out of native function-calling (the XML tool-tag rail). Resolved by `isNativeToolsActive()`.
|
|
317
|
+
- `models[].inline_reasoning` (bool, **optional**, default unset) — live-narration safety assertion for the **native rail only**. Some models inline their reasoning into `delta.content` (Qwen3-style bare text terminated by an orphan `</think>`), indistinguishable from narration until the boundary arrives; to avoid leaking that hidden reasoning, leading content is buffered until a positive boundary. Setting `inline_reasoning: false` asserts **this model never inlines reasoning** — so on the native rail its narration streams **live, token-by-token, from token 1**. Leave it **unset** (or `true`) when unsure: the agent then keeps the safe buffered-until-boundary behavior. Independently, if the model emits a structured `reasoning_content` delta this turn, the gate opens live regardless of this flag (positive evidence the reasoning channel is in use). Only an explicit boolean is persisted (`getInlineReasoning()`); the XML rail ignores this key entirely.
|
|
318
|
+
- `mcp` — normalized to `{ servers: {}, max_result_tokens }`. `servers` maps a server name → its launch/connection spec (transport, command/args/env/cwd or url/headers/oauth, allow/allowAll, disabled). Empty by default; no MCP server is connected until a user adds an entry. `max_result_tokens` (Task W.8, default **10000**) is the **stricter** token cap applied to an MCP tool result before it enters context (third-party / untrusted) — applied inside the untrusted fence. Consumed by the MCP client (`lib/mcp/client.js`, Task 3.3) and `formatMcpResult` (`lib/agent.js`) — see **MCP Client** above.
|
|
319
|
+
- `hooks` — normalized (`normalizeHooks` in `lib/hooks.js`) to a map with one array per known event (`PreToolUse`, `PostToolUse`, `UserPromptSubmit`, `Stop`, `PreCompact`). Each entry is `{ type: "command"|"prompt", command|prompt, matcher?, timeout_ms? }`. Empty by default. Consumed by the agent loop — see **Lifecycle Hooks** above. **NOTE (Pre-Task 5.0a):** `loadConfig` re-resolves hooks from the user/project layers SEPARATELY (`loadHookLayers`) and quarantines project-layer **command** hooks (a cloned repo can only add **prompt** hooks) — this shallow-merged value is not the executable security path.
|
|
320
|
+
- `subagents` — normalized to `{ max_concurrency, max_result_tokens }` (defaults 3 (clamped 1–16) / 20000). `max_concurrency` bounds the parallel-execution pool for the `spawn_agent` tool; `max_result_tokens` (Task W.8, default **20000**) is the **generous** token cap on a subagent's final text before it enters the parent context (a safety net against a verbose child, strictly larger than the MCP cap). See **Subagents** above.
|
|
321
|
+
- `permissions` — normalized (shape-only) to `{ rules: [] }`. Per-pattern permission rules (`{ tool, action, and one of pattern|path|url|match }`). **Enforcement reads the user and project layers SEPARATELY** via `loadRuleLayers` (`lib/permission-rules.js`) — the merged `config.permissions` here is display/normalization only — because the project layer can only **narrow** the user posture, never widen it. See **Per-Pattern Permissions** above.
|
|
322
|
+
- `checkpoints` — normalized (`normalizeCheckpoints` in `lib/checkpoints.js`) to `{ enabled, max_file_bytes, max_per_session }`. Per-write file snapshots under `~/.semalt-ai/checkpoints/<session>/` powering `/rewind`. Enabled by default; `max_file_bytes` (5 MB) is the per-file snapshot cap (oversize → rewind unavailable, not disk exhaustion); `max_per_session` (100) is the retention cap (oldest pruned). File-tool changes only — shell side effects are not reversible. See **Checkpoints & Rewind** above.
|
|
323
|
+
- `sandbox` — normalized (`normalizeSandbox` in `lib/sandbox.js`) to `{ mode, failIfUnavailable, network }`. OS-level filesystem **+ binary network** sandbox for shell commands (Seatbelt on macOS, bubblewrap on Linux/WSL2). `mode` `auto` (default — jail when available) or `off` (a **human-only** opt-out the agent can never set); `failIfUnavailable` makes a missing/unusable sandbox a hard error instead of a human-approval fallback; `network` `on` (default — sandboxed commands keep normal egress) or `off` (kernel-level no-network: `--unshare-net` / Seatbelt `(deny network*)`; also via the `--no-network` flag). **Binary on/off — no host proxy, no domain allowlist, no TLS interception.** Anti-fail-open: a present-but-malformed `network` value resolves to `off`, never silently to network. See **OS Sandbox** above.
|
|
324
|
+
- `verify` — normalized (`normalizeVerify` in `lib/verify.js`) to `{ mode, command, timeout_ms, expected_exit_code, max_attempts }`. Self-verification: when the agent declares a task done, optionally run `command` and feed the result back. `mode` advisory (default) never blocks; `enforcing` returns the agent to the loop on a failing verify, bounded by `max_attempts` (default 3) then `stopReason: "verify_failed"`. Empty `command` → no-op; `--no-verify` skips for one run. Success is exit-code based (`expected_exit_code`, default 0). See **Self-Verification** above. **NOTE (Pre-Task 5.0a):** `loadConfig` re-resolves verify from the user/project layers SEPARATELY (`loadVerifyLayers`) and quarantines a project-layer `verify.command` — the effective command can only come from the trusted user layer.
|
|
325
|
+
|
|
326
|
+
### Config hierarchy (Task 2.2)
|
|
327
|
+
|
|
328
|
+
`loadConfig()` merges four layers, lowest to highest precedence:
|
|
329
|
+
|
|
330
|
+
1. **User** — `~/.semalt-ai/config.json`
|
|
331
|
+
2. **Project** — `.semalt/config.json`, the nearest one found by walking up from the CWD to the repo root (the directory holding `.git` is the last checked)
|
|
332
|
+
3. **Environment** — `SEMALT_API_BASE` → `api_base`, `SEMALT_MODEL` → `default_model`, `HTTPS_PROXY`/`HTTP_PROXY` → `https_proxy`/`http_proxy`. **Proxy intent is parsed and exposed in config, but not yet consumed:** `api.js` does **not** route requests through a proxy agent, so setting `HTTPS_PROXY`/`HTTP_PROXY` currently has **no effect on outbound HTTP** (relevant on corporate networks). Proxy consumption is a **deferred** item — see **Deferred / Not Yet Implemented**.
|
|
333
|
+
4. **CLI flags** — `--api-base`, `--api-key`, `--dashboard-url`, `--default-model`
|
|
334
|
+
|
|
335
|
+
The merge is a pure function (`mergeConfigLayers`) with each layer produced by a pure extractor (`envConfigLayer`, `flagsConfigLayer`, `loadProjectConfig`), so every combination is unit-testable. **API-key sourcing is NOT part of this merge** — it stays in `lib/secrets.js` (`SEMALT_API_KEY` env → OS keychain → `config.api_key`), preserving the Phase 0 precedence.
|
|
336
|
+
|
|
337
|
+
**Persistence is user-file-only.** `configSet` writes against the user file, and the runtime `setConfig`/learned-context-length persistence rebases through `userLayerForPersist` — only keys a caller actually changed land in `config.json`, so a project/env/flag override is never baked into the user's global config.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|