@semalt-ai/code 1.8.1 → 1.8.4

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.
@@ -2,7 +2,20 @@
2
2
  "permissions": {
3
3
  "allow": [
4
4
  "Bash(grep -oP '.{0,120}chat:stash.{0,120}' /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js)",
5
- "Bash(grep -oP '.{0,100}externalEditor.{0,100}' /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js)"
5
+ "Bash(grep -oP '.{0,100}externalEditor.{0,100}' /usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js)",
6
+ "Bash(node *)",
7
+ "Bash(git -C /srv/www/ai/semalt-code diff lib/constants.js lib/agent.js lib/prompts.js lib/tools.js)",
8
+ "Bash(git *)",
9
+ "WebFetch(domain:api.github.com)",
10
+ "WebFetch(domain:raw.githubusercontent.com)",
11
+ "WebFetch(domain:github.com)",
12
+ "Bash(xargs -I{} sh -c 'head -c 3000 \"$1\"' _ {})",
13
+ "Bash(xargs -I{} sh -c 'echo \"=== $1 ===\"; head -c 500 \"$1\"; echo' _ {})",
14
+ "Bash(xargs -I{} sh -c 'echo \"=== $1 ===\"; python3 -c \"import json,sys; d=json.load\\(open\\(\\\\\"$1\\\\\"\\)\\); print\\(len\\(d.get\\(\\\\\"messages\\\\\",[]\\)\\), \\\\\"msgs; roles:\\\\\", [m.get\\(\\\\\"role\\\\\"\\) for m in d.get\\(\\\\\"messages\\\\\",[]\\)[:20]]\\)\"' _ {})",
15
+ "Bash(python3 *)",
16
+ "Read(//tmp/**)",
17
+ "Bash(sed -i \"s/addMessage\\('>>> AI MSG 2'.*$/addMessage\\('>>> AI MSG 2', ['response body 2a', 'response body 2b']\\);\\\\nfor \\(let k = 3; k <= 8; k++\\) { addMessage\\('>>> USER MSG ' + k, ['line body ' + k]\\); addMessage\\('>>> AI MSG ' + k, ['response body ' + k + 'a', 'response body ' + k + 'b']\\); }/\" scroll-capture.js)",
18
+ "Bash(echo \"exit=$?\")"
6
19
  ]
7
20
  }
8
21
  }
package/CLAUDE.md CHANGED
@@ -299,7 +299,7 @@ Managed by `lib/config.js`. Normalized on every load. The config directory is cr
299
299
  - `max_output_lines` caps shell and HTTP response lines returned to the agent (default 50).
300
300
  - `show_token_count` controls whether token count is shown in the status bar.
301
301
  - `show_cost` reserved for future cost-display feature.
302
- - `context_length` / `models[].context_length` — token limit used for context-usage bar and warnings.
302
+ - `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.
303
303
  - Local `models[]` entries override dashboard models when selected.
304
304
 
305
305
  ---
@@ -311,6 +311,7 @@ Managed by `lib/config.js`. Normalized on every load. The config directory is cr
311
311
  - **Streaming**: `api.js` manually parses `text/event-stream`. The parser in `chatStream()` handles partial JSON lines — be careful editing it.
312
312
  - **Permissions are per-session**: `PermissionManager` resets on each CLI invocation. Approvals never persist to disk. In non-TTY mode all tool calls are auto-approved with a warning.
313
313
  - **Token counting is approximate**: `estimateTokens()` divides char count by 4. It is used only for the `/compact` display — do not rely on it for hard limits.
314
+ - **Context trimming is proactive when a limit is known**: `chatStream()` uses the in-process `_sessionInputLimits` learned from a prior 400 overflow first, then falls back to `config.context_length * 0.9`. When neither is set, no pre-flight trim runs and the client relies on the reactive 400/413 handler (which then persists the discovered window). `Metrics.tokenLimitStatus()` returns `{ used, limit: null }` until a limit is learned, so the status bar shows "N tok · limit unknown" instead of hiding the line.
314
315
  - **Tool output is truncated**: `tools.js` caps output at `max_output_lines` (default 50). Configurable via config.
315
316
  - **Max 10 agent iterations**: hard-coded in `agent.js`. Prevents runaway loops.
316
317
  - **Malformed tags are skipped**: each tool dispatch in the agent loop is wrapped in try/catch; errors emit a warning line and continue to the next tool call.
package/index.js CHANGED
@@ -8,6 +8,7 @@ const path = require('path');
8
8
  const { PACKAGE_JSON } = require('./lib/constants');
9
9
  const { loadConfig, saveConfig, configSet, configShow } = require('./lib/config');
10
10
  const ui = require('./lib/ui');
11
+ const { registerTerminalCleanup } = require('./lib/ui/terminal');
11
12
  const { createPermissionManager } = require('./lib/permissions');
12
13
  const { createToolExecutor, extractToolCalls } = require('./lib/tools');
13
14
  const { readFileContext } = require('./lib/context');
@@ -17,6 +18,12 @@ const { createCommands } = require('./lib/commands');
17
18
  const { parseArgs } = require('./lib/args');
18
19
  const { CONFIG_PATH } = require('./lib/constants');
19
20
  const { AUDIT_LOG } = require('./lib/audit');
21
+ const writer = require('./lib/ui/writer');
22
+
23
+ // Install process-wide signal handlers so every exit path (normal, SIGINT,
24
+ // SIGHUP, SIGTERM, uncaught exception) restores the terminal. Safe to call
25
+ // from multiple entrypoints — the function is internally idempotent.
26
+ registerTerminalCleanup();
20
27
 
21
28
  let config = loadConfig();
22
29
 
@@ -53,10 +60,14 @@ const apiClient = createApiClient({
53
60
  });
54
61
  const { runAgentLoop } = createAgentRunner({
55
62
  chatStream: apiClient.chatStream,
56
- extractToolCalls,
63
+ extractToolCalls: (reply, options = {}) => extractToolCalls(reply, {
64
+ repairMalformedXml: !!getConfig().repair_malformed_tool_xml,
65
+ ...options,
66
+ }),
57
67
  agentExecShell,
58
68
  agentExecFile,
59
69
  ui,
70
+ getConfig,
60
71
  });
61
72
  const commands = createCommands({
62
73
  getConfig,
@@ -80,7 +91,7 @@ async function main() {
80
91
  const command = rawArgs[0];
81
92
 
82
93
  if (command === '--help' || command === '-h') {
83
- console.log(`
94
+ writer.scrollback(`
84
95
  Semalt.AI — Self-hosted AI Coding Assistant
85
96
 
86
97
  Usage: semalt-code [command] [options]
@@ -113,17 +124,20 @@ Options:
113
124
  --allow-exec Auto-approve shell command execution
114
125
  --allow-net Auto-approve network operations
115
126
  --allow-all Auto-approve everything (use carefully)
127
+ --allow-anywhere Allow writes outside the project CWD and in sensitive dirs
116
128
  --readonly Block all write operations
117
129
  --new Skip session resume prompt
118
130
  -v, --version Show CLI version
119
131
 
120
132
  Config: ${CONFIG_PATH}
121
133
  `);
134
+ await writer.flush();
122
135
  return;
123
136
  }
124
137
 
125
138
  if (command === '--version' || command === '-v') {
126
- console.log(PACKAGE_JSON.version);
139
+ writer.scrollback(PACKAGE_JSON.version);
140
+ await writer.flush();
127
141
  return;
128
142
  }
129
143
 
@@ -159,20 +173,22 @@ Config: ${CONFIG_PATH}
159
173
  try {
160
174
  const entry = JSON.parse(line);
161
175
  const icon = entry.approved ? `${ui.FG_GREEN}✓${ui.RST}` : `${ui.FG_RED}✗${ui.RST}`;
162
- console.log(`${icon} ${line}`);
176
+ writer.scrollback(`${icon} ${line}`);
163
177
  } catch {
164
- console.log(line);
178
+ writer.scrollback(line);
165
179
  }
166
180
  }
167
181
  } catch {
168
- console.log('No audit log found.');
182
+ writer.scrollback('No audit log found.');
169
183
  }
184
+ await writer.flush();
170
185
  } else if (command === 'config') {
171
186
  const sub = rawArgs[1];
172
187
  if (sub === 'set') {
173
188
  const key = rawArgs[2];
174
189
  const value = rawArgs[3];
175
190
  if (!key || value === undefined) {
191
+ // audit: allowed — pre-UI argparse usage error to stderr; exits immediately.
176
192
  process.stderr.write(`Usage: semalt-code config set <key> <value>\n`);
177
193
  process.exit(1);
178
194
  }
@@ -183,11 +199,12 @@ Config: ${CONFIG_PATH}
183
199
  parsed = value;
184
200
  }
185
201
  configSet(key, parsed);
186
- console.log(`Set ${key} = ${JSON.stringify(parsed)}`);
202
+ writer.scrollback(`Set ${key} = ${JSON.stringify(parsed)}`);
187
203
  } else {
188
204
  // default: "show" or bare "config"
189
- console.log(configShow());
205
+ writer.scrollback(configShow());
190
206
  }
207
+ await writer.flush();
191
208
  } else {
192
209
  const { opts } = parseArgs(rawArgs);
193
210
  await commands.cmdChat(opts);
@@ -195,6 +212,10 @@ Config: ${CONFIG_PATH}
195
212
  }
196
213
 
197
214
  main().catch((error) => {
215
+ // Tear down the TUI synchronously so the error message lands below the
216
+ // last scrollback line, not on top of a still-rendered live region.
217
+ try { ui.teardownTerminal(); } catch {}
218
+ // audit: allowed — fatal error message to stderr after writer teardown.
198
219
  process.stderr.write(`\n ${ui.FG_RED}✗ Fatal: ${error.message}${ui.RST}\n\n`);
199
220
  process.exit(1);
200
221
  });