@semalt-ai/code 1.8.4 → 1.8.5

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/lib/proc.js ADDED
@@ -0,0 +1,96 @@
1
+ 'use strict';
2
+
3
+ const dbg = require('./debug');
4
+
5
+ // Platform-aware subprocess spawn + tree-kill helpers.
6
+ //
7
+ // Why this module exists: when a child is started with `shell: true`, the
8
+ // PID Node hands back is the shell wrapper (`sh -c "..."` on POSIX, `cmd.exe
9
+ // /c "..."` on Windows). Calling `child.kill()` kills the wrapper, but its
10
+ // descendants (the actual `find`, `grep`, `bash` pipeline) become orphans
11
+ // and keep running. To abort cleanly we have to kill the whole process tree.
12
+ //
13
+ // Constraint from the project: no other file imports `process.kill` or
14
+ // `child.kill` directly — those calls live here. `tools.js` (and any future
15
+ // caller) only knows about `spawnWithGroup` and `killTreeEscalating`.
16
+
17
+ const isWindows = process.platform === 'win32';
18
+
19
+ // Wrap `child_process.spawn` so the resulting child is addressable as a
20
+ // process group. POSIX: `detached: true` makes the child a process-group
21
+ // leader, so `process.kill(-pid, sig)` reaches all descendants. Windows:
22
+ // taskkill /T walks the PID hierarchy itself, so `detached` is unnecessary
23
+ // and actively harmful — it would spawn the child in a new console window.
24
+ function spawnWithGroup(spawn, command, args, opts = {}) {
25
+ const finalOpts = { ...opts };
26
+ if (!isWindows) finalOpts.detached = true;
27
+ return spawn(command, args, finalOpts);
28
+ }
29
+
30
+ function killTree(child, signal) {
31
+ if (!child || child.killed || child.exitCode !== null || child.pid == null) return;
32
+ if (isWindows) {
33
+ // taskkill /T = traverse children, /F = force. windowsHide prevents the
34
+ // brief CMD window flash. Fire and forget — taskkill exits on its own
35
+ // and we don't care about its result code (the child's `exit` event is
36
+ // the authoritative signal).
37
+ const { spawn } = require('child_process');
38
+ try {
39
+ const args = ['/PID', String(child.pid), '/T'];
40
+ if (signal === 'SIGKILL') args.push('/F');
41
+ const tk = spawn('taskkill', args, { windowsHide: true, stdio: 'ignore' });
42
+ tk.on('error', () => {});
43
+ tk.unref();
44
+ } catch {
45
+ // taskkill failed to launch (PID already gone, or taskkill missing on
46
+ // a stripped-down Windows image). The child's exit event will still
47
+ // fire if the process is gone; nothing else to do here.
48
+ }
49
+ } else {
50
+ try {
51
+ // Negative PID = whole process group. Requires detached:true at spawn.
52
+ process.kill(-child.pid, signal || 'SIGTERM');
53
+ } catch (err) {
54
+ // ESRCH = process group already gone. Anything else is unexpected but
55
+ // not fatal — surface only when debug is active for triage.
56
+ if (err.code !== 'ESRCH') {
57
+ dbg.log(`[killTree] kill failed: ${err.code} ${err.message}`);
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ // Send SIGTERM (or taskkill graceful), wait 2s, escalate to SIGKILL (or
64
+ // taskkill /F) if the tree didn't exit. Hard-coded 2s grace per the abort
65
+ // requirements — long enough for well-behaved children to clean up, short
66
+ // enough that a stuck `trap "" TERM` process doesn't tie up the agent.
67
+ function killTreeEscalating(child) {
68
+ killTree(child, 'SIGTERM');
69
+ const escalation = setTimeout(() => {
70
+ if (child.exitCode === null && !child.killed) killTree(child, 'SIGKILL');
71
+ }, 2000);
72
+ // Don't keep the event loop alive solely for the escalation timer; if the
73
+ // process exits naturally first, the `once('exit')` listener clears it.
74
+ escalation.unref();
75
+ child.once('exit', () => clearTimeout(escalation));
76
+ }
77
+
78
+ // Future Windows-enablement notes:
79
+ // - Job objects (CreateJobObject API via a native binding) give stronger
80
+ // tree-kill guarantees than taskkill, especially for grandchild
81
+ // processes that detach themselves. Consider migrating if taskkill
82
+ // proves unreliable for nested children.
83
+ // - Windows has no SIGTERM/SIGKILL distinction at the OS level for
84
+ // spawned processes. taskkill (without /F) attempts WM_CLOSE-style
85
+ // graceful close; /F is a hard terminate. The 2s escalation here maps
86
+ // to "graceful taskkill, then forceful taskkill" — same shape as POSIX.
87
+ // - shell: true on Windows uses cmd.exe by default. Cross-platform
88
+ // command translation (find, grep, etc.) is the tool layer's problem,
89
+ // not this module's.
90
+
91
+ module.exports = {
92
+ spawnWithGroup,
93
+ killTree,
94
+ killTreeEscalating,
95
+ isWindows,
96
+ };
package/lib/prompts.js CHANGED
@@ -9,6 +9,7 @@ const WRAPPER_NAMES = new Set([
9
9
  'parameter',
10
10
  'tool_call',
11
11
  'function_call',
12
+ 'function',
12
13
  ]);
13
14
 
14
15
  // For each tool tag: required attributes and a one-line purpose.
@@ -32,7 +33,7 @@ const TOOL_TAG_SPECS = {
32
33
  edit_file: { attrs: ['path', 'line'], purpose: 'Replace a single line in a file (inline content = new line).' },
33
34
  search_files: { attrs: ['pattern?', 'dir?'], purpose: 'Find files by glob pattern.' },
34
35
  search_in_file: { attrs: ['path'], purpose: 'Regex search inside a file (inline content = pattern).' },
35
- replace_in_file: { attrs: ['path', 'search', 'replace'], purpose: 'Regex replace inside a file.' },
36
+ replace_in_file: { attrs: ['path', 'search', 'replace'], purpose: 'Regex replace inside a file; inline content is interpreted as regex flags (e.g. g, i, gi).' },
36
37
  get_env: { attrs: [], purpose: 'Read an env var (inline content = name).' },
37
38
  set_env: { attrs: ['name', 'value'], purpose: 'Set an env var for this process.' },
38
39
  download: { attrs: [], purpose: 'HTTP download to the CWD (inline content = URL).' },
@@ -80,8 +81,8 @@ ${TAG_INVENTORY}
80
81
  ## Reasoning vs planning — IMPORTANT:
81
82
 
82
83
  - Your internal chain-of-thought reasoning uses your native \`<think>...</think>\` block. Use it normally for deliberation. Do NOT treat \`<think>\` as a user-facing tool and do NOT try to emit \`<think>\` as an action — it is reserved for your own reasoning and is handled by the runtime.
83
- - When you need to explicitly record a short plan that the agent framework can see (for logging or hand-off between steps), use \`<plan>...</plan>\` instead. \`<plan>\` is a tool tag; \`<think>\` is not.
84
- - Never emit \`<think>\` as an action. The valid action tags are the ones listed above.
84
+ - When you need to explicitly record a short plan that the agent framework can see (for logging or hand-off between steps), use \`<plan>...</plan>\` instead. Both \`<think>\` and \`<plan>\` are display-only tags handled by the runtime — never emit either as an action.
85
+ - The valid action tags are the ones listed above.
85
86
 
86
87
  ## STRICT RULES — follow exactly:
87
88
 
package/lib/tool_specs.js CHANGED
@@ -75,9 +75,10 @@ const TOOL_SPECS = {
75
75
  content: {
76
76
  type: 'string',
77
77
  description: 'Full UTF-8 text to write as the new file contents',
78
+ default: '',
78
79
  },
79
80
  },
80
- required: ['path', 'content'],
81
+ required: ['path'],
81
82
  },
82
83
  },
83
84
 
@@ -93,9 +94,10 @@ const TOOL_SPECS = {
93
94
  content: {
94
95
  type: 'string',
95
96
  description: 'Full UTF-8 text to write as the initial file contents',
97
+ default: '',
96
98
  },
97
99
  },
98
- required: ['path', 'content'],
100
+ required: ['path'],
99
101
  },
100
102
  },
101
103
 
@@ -111,9 +113,10 @@ const TOOL_SPECS = {
111
113
  content: {
112
114
  type: 'string',
113
115
  description: 'UTF-8 text to append to the end of the file',
116
+ default: '',
114
117
  },
115
118
  },
116
- required: ['path', 'content'],
119
+ required: ['path'],
117
120
  },
118
121
  },
119
122
 
@@ -241,9 +244,10 @@ const TOOL_SPECS = {
241
244
  content: {
242
245
  type: 'string',
243
246
  description: 'New text for the target line; trailing newline is added automatically when the file is rejoined',
247
+ default: '',
244
248
  },
245
249
  },
246
- required: ['path', 'line', 'content'],
250
+ required: ['path', 'line'],
247
251
  },
248
252
  },
249
253
 
@@ -255,6 +259,7 @@ const TOOL_SPECS = {
255
259
  pattern: {
256
260
  type: 'string',
257
261
  description: 'Glob pattern such as "*.ts" or "src/**/*.js"; matches against the basename when the pattern contains no slash, otherwise against the relative path',
262
+ default: '*',
258
263
  },
259
264
  dir: {
260
265
  type: 'string',
@@ -262,7 +267,7 @@ const TOOL_SPECS = {
262
267
  default: '.',
263
268
  },
264
269
  },
265
- required: ['pattern'],
270
+ required: [],
266
271
  },
267
272
  },
268
273
 
@@ -300,6 +305,7 @@ const TOOL_SPECS = {
300
305
  replace: {
301
306
  type: 'string',
302
307
  description: 'Replacement string; supports the standard $1, $2, $& back-references',
308
+ default: '',
303
309
  },
304
310
  flags: {
305
311
  type: 'string',
@@ -307,7 +313,7 @@ const TOOL_SPECS = {
307
313
  default: '',
308
314
  },
309
315
  },
310
- required: ['path', 'search', 'replace'],
316
+ required: ['path', 'search'],
311
317
  },
312
318
  },
313
319
 
@@ -370,9 +376,10 @@ const TOOL_SPECS = {
370
376
  content: {
371
377
  type: 'string',
372
378
  description: 'Base64-encoded payload to decode and write as the file contents',
379
+ default: '',
373
380
  },
374
381
  },
375
- required: ['path', 'content'],
382
+ required: ['path'],
376
383
  },
377
384
  },
378
385