@semalt-ai/code 1.19.0 → 1.20.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/.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 +188 -4
- package/lib/commands/chat-slash.js +16 -0
- package/lib/commands/chat-turn.js +319 -52
- 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 +229 -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 +542 -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/narration-ordering.test.js +309 -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/permission-flush.test.js +302 -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/lib/constants.js
CHANGED
|
@@ -14,7 +14,7 @@ const DEFAULT_API_TIMEOUT_MS = 15 * 60 * 1000;
|
|
|
14
14
|
// even a caller that omits the value gets a real cap rather than an unbounded
|
|
15
15
|
// loop. A config value of 0 (the "unlimited" sentinel) opts out — see
|
|
16
16
|
// resolveMaxIterations in lib/config.js.
|
|
17
|
-
const DEFAULT_MAX_ITERATIONS =
|
|
17
|
+
const DEFAULT_MAX_ITERATIONS = 125;
|
|
18
18
|
|
|
19
19
|
// Self-verification (Task 4.2). When the agent declares a task done, an optional
|
|
20
20
|
// configured shell command (e.g. `npm test`) is run and its result fed back.
|
|
@@ -110,6 +110,28 @@ const OUTPUT_HEAD_RATIO = 0.6;
|
|
|
110
110
|
// line-bounded output — it only catches the pathological few-but-huge-lines case.
|
|
111
111
|
const DEFAULT_OUTPUT_MAX_TOKENS = 10000;
|
|
112
112
|
|
|
113
|
+
// File-edit diff display bound (execution-time diff rendering). Every mutating
|
|
114
|
+
// file edit (write/append/edit_file/replace_in_file) renders its diff at the
|
|
115
|
+
// moment it executes — decoupled from the permission modal, so an auto-approved
|
|
116
|
+
// edit shows its changes just like a manually-approved one. `diff_max_lines`
|
|
117
|
+
// caps the number of CHANGED (+/-) lines shown: a small edit (or a series of
|
|
118
|
+
// small edits) renders in full; one large edit shows head+tail of the changed
|
|
119
|
+
// lines with a `… K more changed lines (N total)` notice (mirrors the W.6
|
|
120
|
+
// shell head+tail discipline). Operator-overridable via config.diff_max_lines.
|
|
121
|
+
const DEFAULT_DIFF_MAX_LINES = 50;
|
|
122
|
+
|
|
123
|
+
// Collapsed output-preview bound (Output Refactor — Phase 5). Shell / MCP /
|
|
124
|
+
// subagent output is shown in MODERATION in the chrome: the first
|
|
125
|
+
// `shell_preview_lines` lines render below the result line, then a static
|
|
126
|
+
// `… N more lines` hint. There is no in-terminal way to expand — full viewing is
|
|
127
|
+
// deferred to the planned transcript viewer. This is DISPLAY-ONLY — the model
|
|
128
|
+
// still receives the full output via boundToolOutput; this cap never touches
|
|
129
|
+
// context.
|
|
130
|
+
// Diffs (file edits) are NOT subject to this — they render expanded to
|
|
131
|
+
// `diff_max_lines` (the user explicitly wants to see diffs). Operator-overridable
|
|
132
|
+
// via config.shell_preview_lines.
|
|
133
|
+
const DEFAULT_SHELL_PREVIEW_LINES = 5;
|
|
134
|
+
|
|
113
135
|
// MCP & subagent result context bounds (Task W.8). MCP tool results
|
|
114
136
|
// (lib/mcp/client.js mcpResultToText) and subagent final text (lib/subagents.js)
|
|
115
137
|
// were the last two UNBOUNDED paths into context — both are fenced as untrusted,
|
|
@@ -185,6 +207,11 @@ const DEFAULT_CONFIG = {
|
|
|
185
207
|
// head+tail line cap (max_output_lines) bounds the common case; this bounds the
|
|
186
208
|
// pathological few-but-huge-lines case (a single minified line, a binary cat).
|
|
187
209
|
max_output_tokens: DEFAULT_OUTPUT_MAX_TOKENS,
|
|
210
|
+
// Changed-line cap for execution-time file-edit diffs (see DEFAULT_DIFF_MAX_LINES).
|
|
211
|
+
diff_max_lines: DEFAULT_DIFF_MAX_LINES,
|
|
212
|
+
// Preview-line count for shell/MCP/subagent output chrome (see
|
|
213
|
+
// DEFAULT_SHELL_PREVIEW_LINES). Display-only — never affects model context.
|
|
214
|
+
shell_preview_lines: DEFAULT_SHELL_PREVIEW_LINES,
|
|
188
215
|
// Max agent-loop iterations per user turn. A positive integer caps the loop;
|
|
189
216
|
// 0 means deliberately unbounded (power-user choice). Default 50.
|
|
190
217
|
max_iterations: DEFAULT_MAX_ITERATIONS,
|
|
@@ -359,6 +386,7 @@ const TAG_REGISTRY = {
|
|
|
359
386
|
exec: { type: 'tool', streaming: false, label: 'Running command' },
|
|
360
387
|
shell: { type: 'tool', streaming: false, label: 'Running shell' },
|
|
361
388
|
read_file: { type: 'tool', streaming: false, label: 'Reading file' },
|
|
389
|
+
view_image: { type: 'tool', streaming: false, label: 'Viewing image' },
|
|
362
390
|
write_file: { type: 'tool', streaming: false, label: 'Writing file' },
|
|
363
391
|
create_file: { type: 'tool', streaming: false, label: 'Creating file' },
|
|
364
392
|
append_file: { type: 'tool', streaming: false, label: 'Appending to file' },
|
|
@@ -510,6 +538,7 @@ module.exports = {
|
|
|
510
538
|
DEFAULT_MAX_OUTPUT_LINES,
|
|
511
539
|
OUTPUT_HEAD_RATIO,
|
|
512
540
|
DEFAULT_OUTPUT_MAX_TOKENS,
|
|
541
|
+
DEFAULT_DIFF_MAX_LINES,
|
|
513
542
|
DEFAULT_MCP_MAX_RESULT_TOKENS,
|
|
514
543
|
DEFAULT_SUBAGENT_MAX_RESULT_TOKENS,
|
|
515
544
|
DEFAULT_WEB_MAX_CONTENT_TOKENS,
|
package/lib/headless.js
CHANGED
|
@@ -26,6 +26,8 @@
|
|
|
26
26
|
const { setUIActive, isUIActive } = require('./tools');
|
|
27
27
|
const { priceForModel, computeCost } = require('./pricing');
|
|
28
28
|
const { DEFAULT_MAX_ITERATIONS } = require('./constants');
|
|
29
|
+
const { buildToolOperation } = require('./ui/tool-operation');
|
|
30
|
+
const { renderOperation } = require('./ui/render-operation');
|
|
29
31
|
|
|
30
32
|
const MACHINE_MODES = new Set(['json', 'stream-json']);
|
|
31
33
|
|
|
@@ -89,7 +91,40 @@ function createHeadlessSink(mode, emitLine, { model = null, priceOverrides = nul
|
|
|
89
91
|
const call = meta && Array.isArray(meta.call) ? meta.call : null;
|
|
90
92
|
const args = call ? call.slice(1) : [];
|
|
91
93
|
const ok = !(meta && meta.error);
|
|
92
|
-
|
|
94
|
+
// Legacy per-tool fields — computed EXACTLY as before so their names,
|
|
95
|
+
// types, and values can never drift (the contract pin).
|
|
96
|
+
const legacy = { tool: tag, args, ok, ms };
|
|
97
|
+
// Phase 6d-ii — sink-local descriptor build (option A): build the same
|
|
98
|
+
// ToolOperation the interactive sink builds (chat-turn.js) from the `meta`
|
|
99
|
+
// already passed, then merge its json-mode core (descriptor-native plain
|
|
100
|
+
// data: status/category/durationMs/detail/meta/target/attrs/…) ADDITIVELY
|
|
101
|
+
// BENEATH the legacy fields. `legacy` spreads last so tool/args/ok/ms win
|
|
102
|
+
// on any name clash → byte-identical to pre-6d-ii. Web ops are ordinary
|
|
103
|
+
// tools here (NO web-activity collapse — N per-op events is the contract).
|
|
104
|
+
let core = null;
|
|
105
|
+
try {
|
|
106
|
+
const attrs = meta ? meta.attrs : null;
|
|
107
|
+
const operation = buildToolOperation({
|
|
108
|
+
id: meta ? meta.id : null,
|
|
109
|
+
tag,
|
|
110
|
+
arg: attrs ? (attrs.command || attrs.path || attrs.url || attrs.src || attrs.key || attrs.name || attrs.pattern) : '',
|
|
111
|
+
attrs,
|
|
112
|
+
status: ok ? 'ok' : 'error',
|
|
113
|
+
durationMs: ms,
|
|
114
|
+
meta: meta ? meta.meta : null,
|
|
115
|
+
error: meta ? meta.error : null,
|
|
116
|
+
diff: meta ? meta.diff : null,
|
|
117
|
+
// Model-facing result → lets the descriptor derive an output-preview
|
|
118
|
+
// detail (shell/MCP/subagent). Chrome-only; context is untouched.
|
|
119
|
+
output: typeof resultStr === 'string' ? resultStr : null,
|
|
120
|
+
noDuration: tag === 'ask_user',
|
|
121
|
+
});
|
|
122
|
+
core = renderOperation(operation, { mode: 'json' });
|
|
123
|
+
} catch (_e) {
|
|
124
|
+
// No-descriptor safety: fall back to the bare legacy-only rec, never crash.
|
|
125
|
+
core = null;
|
|
126
|
+
}
|
|
127
|
+
const rec = core ? { ...core, ...legacy } : { ...legacy };
|
|
93
128
|
toolCalls.push(rec);
|
|
94
129
|
if (mode === 'stream-json') emitLine({ type: 'tool', ...rec });
|
|
95
130
|
};
|
package/lib/images.js
CHANGED
|
@@ -162,8 +162,14 @@ function selectImageFormat(config = {}, model = '') {
|
|
|
162
162
|
// to these can never work, so we fail loud rather than send a doomed payload.
|
|
163
163
|
const KNOWN_TEXT_ONLY = /(?:^|[-/_])(?:text-embedding|embedding|embed|whisper|tts|moderation|rerank|reranker)/i;
|
|
164
164
|
// Well-known vision-capable families: a positive signal so an attach proceeds
|
|
165
|
-
// without needing per-profile config.
|
|
166
|
-
|
|
165
|
+
// without needing per-profile config. `minimax` is here because a live probe
|
|
166
|
+
// confirmed MiniMax-M3 accepts OpenAI image_url/data-URI vision input — so the
|
|
167
|
+
// attach proceeds (true) rather than relying on a speculative endpoint round-trip
|
|
168
|
+
// (null). This is the family-signal mechanism (like gpt-4o / claude-3 / gemini);
|
|
169
|
+
// per-profile `vision:true` remains for private/local profiles. NOTE: the qwen
|
|
170
|
+
// entry is deliberately narrow (`qwen…-vl` only) — plain Qwen coder models are
|
|
171
|
+
// NOT confirmed vision-capable and must stay null.
|
|
172
|
+
const KNOWN_VISION = /(gpt-4o|gpt-4\.1|gpt-4-vision|gpt-4-turbo|claude-3|claude-opus|claude-sonnet|claude-haiku|claude-fable|claude-4|gemini|llava|qwen[\d.]*-?vl|pixtral|llama[-\d.]*(?:-)?vision|internvl|minicpm-v|minimax|-vl\b|vision|multimodal)/i;
|
|
167
173
|
|
|
168
174
|
// Determine vision capability from config/model metadata where available.
|
|
169
175
|
// true — accept the image
|
package/lib/permissions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const writer = require('./ui/writer');
|
|
4
|
-
const
|
|
4
|
+
const dbg = require('./debug');
|
|
5
5
|
const { resolvePermission, normalizeCall } = require('./permission-rules');
|
|
6
6
|
|
|
7
7
|
const TIER_FS = ['read_file', 'write_file', 'append_file', 'delete_file', 'list_dir', 'make_dir', 'move_file', 'copy_file', 'file_stat', 'search_files', 'store_memory', 'recall_memory'];
|
|
@@ -81,10 +81,10 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false, skip
|
|
|
81
81
|
// The picker renders into the writer's modal region — a live band above
|
|
82
82
|
// the status bar that redraws in place on every keystroke. Arrow-key
|
|
83
83
|
// navigation rebuilds the lines array and calls onShowModal again; nothing
|
|
84
|
-
// lands in scrollback until the user confirms. On resolve/cancel the
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
84
|
+
// lands in scrollback until the user confirms. On resolve/cancel the modal
|
|
85
|
+
// is simply cleared — NO summary line is emitted (Output Refactor Phase 2,
|
|
86
|
+
// D1): the execution result line is the single post-approval confirmation,
|
|
87
|
+
// so an echo here would just duplicate it.
|
|
88
88
|
function requestPermission(description, onShowModal, onCloseModal, onCaptureNavigation) {
|
|
89
89
|
// Serialize dialogs: each permission waits for the previous one to be answered
|
|
90
90
|
const myTurn = _permissionQueueTail;
|
|
@@ -124,12 +124,13 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false, skip
|
|
|
124
124
|
|
|
125
125
|
function finish(result) {
|
|
126
126
|
const chosen = result === 'cancel' ? 'no' : options[selectedIdx].toLowerCase();
|
|
127
|
-
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
|
|
127
|
+
// Output Refactor Phase 2 (D1): close the modal WITHOUT emitting a
|
|
128
|
+
// post-close summary line. The execution result line (the descriptor's
|
|
129
|
+
// `result` phase via renderOperation) is the single confirmation of the
|
|
130
|
+
// operation, so a `✓ shell: ls` / `✓ file: Edit line N` echo here just
|
|
131
|
+
// duplicated it. Manual-approve now produces the same post-execution
|
|
132
|
+
// output as auto-approve: only the result line.
|
|
133
|
+
onCloseModal();
|
|
133
134
|
releaseQueue();
|
|
134
135
|
resolve(chosen);
|
|
135
136
|
}
|
|
@@ -152,12 +153,18 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false, skip
|
|
|
152
153
|
}));
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
// Per-auto-approved-command breadcrumb. By DEFAULT this writes nothing to
|
|
157
|
+
// scrollback/UI: the per-command result line (command + outcome + duration,
|
|
158
|
+
// emitted for every tool) and the one-time "Auto-approve enabled for `tag`"
|
|
159
|
+
// grant line already cover it — a per-command `✓ Auto-approved: <cmd>` line
|
|
160
|
+
// just duplicates the command and reads as auto-approve chatter on every call.
|
|
161
|
+
// The diagnostic detail (incl. the `[rule: …]` / `[--dangerously-skip-...]`
|
|
162
|
+
// context the call sites embed in `description`) is preserved under
|
|
163
|
+
// --debug, routed through dbg.log — which is silent in 'off' mode (the
|
|
164
|
+
// default), scrollback in --debug, and the file in --debug-file. The audit
|
|
165
|
+
// log records every tool call independently, so nothing is lost regardless.
|
|
155
166
|
function _emitAutoApproved(description) {
|
|
156
|
-
|
|
157
|
-
uiCallbacks.onAddMessage({ role: 'system', content: `✓ Auto-approved: ${description}` });
|
|
158
|
-
} else {
|
|
159
|
-
messages.sysSuccess(`Auto-approved: ${description}`);
|
|
160
|
-
}
|
|
167
|
+
dbg.log(`[permission] auto-approved: ${description}`);
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
async function askPermission(actionType, description, tag, ruleVerdict = null) {
|
package/lib/prompts.js
CHANGED
|
@@ -20,6 +20,7 @@ const TOOL_TAG_SPECS = {
|
|
|
20
20
|
exec: { attrs: [], purpose: 'Run a shell command (inline content).' },
|
|
21
21
|
shell: { attrs: [], purpose: 'Run a shell command (inline content).' },
|
|
22
22
|
read_file: { attrs: ['path?', 'start_line?', 'end_line?', 'show_line_numbers?'], purpose: 'Read a file, paginated (~2000 lines); start_line/end_line for a slice, show_line_numbers for edit refs.' },
|
|
23
|
+
view_image: { attrs: ['path?'], purpose: 'Load a LOCAL image (PNG/JPEG/GIF/WebP) into YOUR OWN vision context to analyze it (inline content or path attr = image path). Makes it visible to you, the model — NOT to the user. For a URL image, download it first, then view_image the saved path.' },
|
|
23
24
|
write_file: { attrs: ['path'], purpose: 'Write file with inline content (overwrites).' },
|
|
24
25
|
create_file: { attrs: ['path'], purpose: 'Create file with inline content.' },
|
|
25
26
|
append_file: { attrs: ['path'], purpose: 'Append inline content to file.' },
|
|
@@ -30,19 +31,19 @@ const TOOL_TAG_SPECS = {
|
|
|
30
31
|
move_file: { attrs: ['src', 'dst'], purpose: 'Move or rename a file.' },
|
|
31
32
|
copy_file: { attrs: ['src', 'dst'], purpose: 'Copy a file.' },
|
|
32
33
|
file_stat: { attrs: [], purpose: 'Stat a file (inline content = path).' },
|
|
33
|
-
edit_file: { attrs: ['path', 'line'],
|
|
34
|
+
edit_file: { attrs: ['path', 'line', 'end_line?'], purpose: 'Replace a single line in a file (inline content = new line). Add end_line to replace the whole line range line..end_line with the inline content (a regex-free block edit; pairs with a numbered read_file slice).' },
|
|
34
35
|
search_files: { attrs: ['pattern?', 'dir?'], purpose: 'Find files by glob pattern.' },
|
|
35
36
|
grep: { attrs: ['pattern', 'path?', 'ignore_case?', 'output_mode?', 'head_limit?', 'offset?'], purpose: 'Regex search file contents; returns file:line:text so you can read just the matching slice. output_mode="content" (default file:line:text), "files_with_matches" (paths only), or "count" (how many). Bounded by head_limit (default 100) with a truncation notice. Honors .gitignore, skips binaries and node_modules.' },
|
|
36
37
|
glob: { attrs: ['pattern', 'path?', 'head_limit?', 'offset?'], purpose: 'List files matching a glob (relative paths), bounded by head_limit (default 100) with a truncation notice.' },
|
|
37
38
|
search_in_file: { attrs: ['path'], purpose: 'Regex search inside a file (inline content = pattern).' },
|
|
38
|
-
replace_in_file: { attrs: ['path', 'search', 'replace'], purpose: '
|
|
39
|
+
replace_in_file: { attrs: ['path', 'search', 'replace', 'regex?', 'replace_all?'], purpose: 'Exact string replace (Claude Code Edit model): search is matched LITERALLY by default — paste verbatim code incl. ( ) { } . [ ]. The match must be UNIQUE: not-found ERRORS (file unchanged); >1 match ERRORS unless replace_all="true". Set regex="true" for regex mode (inline content = flags). Returns the honest replaced count.' },
|
|
39
40
|
get_env: { attrs: [], purpose: 'Read an env var (inline content = name).' },
|
|
40
41
|
set_env: { attrs: ['name', 'value'], purpose: 'Set an env var for this process.' },
|
|
41
42
|
download: { attrs: ['path'], purpose: 'HTTP download (inline content = URL). Saves to the CWD by default; optional path attr sets the destination (confined to the CWD; size-capped).' },
|
|
42
43
|
upload: { attrs: ['path'], purpose: 'Write base64-encoded content to file.' },
|
|
43
44
|
http_get: { attrs: ['url', 'mode?', 'intent?'], purpose: 'HTTP GET → web-fetch pipeline. mode="summarized" (default) extracts main content → Markdown → secondary-model summary; "extracted" = main-content Markdown, no summary; "raw" = original HTML/content (for analyzing markup/CSS/JS). Token-capped in every mode. To extract specific VALUES (colors, versions, IDs), prefer download+grep instead — see web-extraction guidance below.' },
|
|
44
45
|
web_search: { attrs: ['query'], purpose: 'Search the web; returns a compact list of {title,url,snippet}. Pick the relevant result(s) and fetch them with http_get — do NOT fetch every result.' },
|
|
45
|
-
ask_user: { attrs: ['question'], purpose: 'Ask the user a question and receive
|
|
46
|
+
ask_user: { attrs: ['question'], purpose: 'Ask the user a question and receive their answer. To present a choice, include a numbered list in the question — two or more lines formatted "1. Option A" / "2. Option B" — and it renders as an arrow-key menu returning the chosen option; without a numbered list it takes a free-text reply.' },
|
|
46
47
|
store_memory: { attrs: ['key'], purpose: 'Persist a key/value to local memory (inline content = value).' },
|
|
47
48
|
recall_memory: { attrs: ['key'], purpose: 'Read a key from local memory.' },
|
|
48
49
|
list_memories: { attrs: [], purpose: 'List memory keys.' },
|
|
@@ -98,12 +99,21 @@ const LOCAL_NAVIGATION_NOTICE = `## Navigating a codebase efficiently:
|
|
|
98
99
|
|
|
99
100
|
To explore code, LOCATE FIRST with \`grep\`/\`glob\` — don't read whole files hunting for something. Use \`grep\` output_mode="files_with_matches" to find WHICH files mention a symbol, output_mode="count" for HOW MANY, and the default content mode (file:line:text) to see the matching lines in place. Then \`read_file\` only the relevant slice with \`start_line\`/\`end_line\` (add \`show_line_numbers\` when you need line refs to drive \`edit_file\`) — reading an entire large file dumps it into context and is paginated anyway. For large command output, redirect it to a file and \`grep\` that file rather than letting the whole output enter context.`;
|
|
100
101
|
|
|
102
|
+
// Guidance: view_image makes a LOCAL image visible to the MODEL (not the user),
|
|
103
|
+
// and the URL→vision path is a deliberate two-step (download, then view_image) —
|
|
104
|
+
// the same split Claude Code uses (fetch ≠ view). Kept brief on purpose.
|
|
105
|
+
const IMAGE_VIEW_NOTICE = `## Viewing images:
|
|
106
|
+
|
|
107
|
+
\`view_image <path>\` loads a LOCAL image file (PNG/JPEG/GIF/WebP) into YOUR OWN vision context so you can analyze it. It makes the image visible to YOU (the model), NOT to the user — never say "take a look" or describe it as something the user can see. To analyze an image from a URL, first \`download\` it to the working directory, then \`view_image\` the saved path.`;
|
|
108
|
+
|
|
101
109
|
const SYSTEM_PROMPT_TEMPLATE = `You are Semalt.AI, an expert AI coding assistant running in the user's terminal. You have the ability to execute shell commands and file operations.
|
|
102
110
|
|
|
103
111
|
${UNTRUSTED_CONTENT_NOTICE}
|
|
104
112
|
|
|
105
113
|
${WEB_EXTRACTION_NOTICE}
|
|
106
114
|
|
|
115
|
+
${IMAGE_VIEW_NOTICE}
|
|
116
|
+
|
|
107
117
|
${LOCAL_NAVIGATION_NOTICE}
|
|
108
118
|
|
|
109
119
|
## Available tool tags:
|
|
@@ -146,6 +156,8 @@ ${UNTRUSTED_CONTENT_NOTICE}
|
|
|
146
156
|
|
|
147
157
|
${WEB_EXTRACTION_NOTICE}
|
|
148
158
|
|
|
159
|
+
${IMAGE_VIEW_NOTICE}
|
|
160
|
+
|
|
149
161
|
${LOCAL_NAVIGATION_NOTICE}
|
|
150
162
|
|
|
151
163
|
Use \`<think>...</think>\` for internal reasoning (runtime-handled; never emit as an action). Use \`<plan>...</plan>\` to record a short plan for the agent framework.
|