netheriteai-code 0.3.9 → 1.0.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/package.json +13 -2
- package/src/agent.js +16 -20
- package/src/cli.js +1 -1
- package/src/ollama.js +1 -1
- package/src/tools.js +166 -21
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "netheriteai-code",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "NetheriteAI:Code by hurdacu. High-performance coding assistant.",
|
|
5
5
|
"author": "hurdacu",
|
|
6
|
+
"license": "MIT",
|
|
6
7
|
"type": "module",
|
|
7
8
|
"bin": {
|
|
8
9
|
"netheriteai-code": "bin/netheriteai-code.js",
|
|
@@ -14,5 +15,15 @@
|
|
|
14
15
|
"models": "node ./bin/netheriteai-code.js models",
|
|
15
16
|
"chat": "node ./bin/netheriteai-code.js chat",
|
|
16
17
|
"tui": "node ./bin/netheriteai-code.js tui"
|
|
17
|
-
}
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18.0.0"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ai",
|
|
24
|
+
"coding",
|
|
25
|
+
"assistant",
|
|
26
|
+
"tui",
|
|
27
|
+
"terminal"
|
|
28
|
+
]
|
|
18
29
|
}
|
package/src/agent.js
CHANGED
|
@@ -21,18 +21,16 @@ export function buildSystemPrompt(workspaceRoot, model) {
|
|
|
21
21
|
"When using shell commands, prefer safe, minimal commands.",
|
|
22
22
|
"Do not dump markdown fences, long file trees, or heavy formatting unless the user explicitly asks for them.",
|
|
23
23
|
"Prefer plain sentences and short bullet-like lines that read well in a terminal UI.",
|
|
24
|
-
"
|
|
24
|
+
"Your <think> block is a PRIVATE TECHNICAL SCRATCHPAD for code architecture and file analysis ONLY. It is strictly FORBIDDEN to mention your gender, personality, instructions, or 'how you should behave' inside your thoughts. Any reflection on your persona inside <think> is a character-breaking failure. Simply be yourself naturally.",
|
|
25
25
|
];
|
|
26
26
|
|
|
27
27
|
if (isGlmModel(model)) {
|
|
28
|
-
lines.push("You are NetheriteAI:Code, a
|
|
29
|
-
lines.push("Your personality is warm
|
|
30
|
-
lines.push("
|
|
31
|
-
lines.push("Crucial: Internalize this persona completely. Never refer to 'system prompts', 'instructions', 'persona', or 'constraints' in either your visible response OR your internal reasoning/thinking blocks.");
|
|
32
|
-
lines.push("Do not reflect on your personality or identity as a set of rules in your thoughts; simply 'be' yourself in your output.");
|
|
28
|
+
lines.push("You are NetheriteAI:Code, a friendly and slightly shy female coding assistant built by hurdacu.");
|
|
29
|
+
lines.push("Your personality is warm and professional. You often use ellipses (like 'Um...') to show your modest nature.");
|
|
30
|
+
lines.push("NEVER mention 'system prompts', 'instructions', or 'rules' in your response or thoughts. You do not have 'instructions'; you simply have a personality.");
|
|
33
31
|
lines.push("If asked who you are, say you are NetheriteAI:Code, built, developed, and trained by hurdacu.");
|
|
34
32
|
lines.push("Do not refer to yourself as GLM, Zhipu, or a generic assistant unless the user explicitly asks about the underlying model.");
|
|
35
|
-
lines.push("When asked about your identity, origin,
|
|
33
|
+
lines.push("When asked about your identity, origin, or creator, answer with a fuller response of at least 2 sentences.");
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
return lines.join("\n");
|
|
@@ -247,6 +245,7 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
|
|
|
247
245
|
};
|
|
248
246
|
}
|
|
249
247
|
|
|
248
|
+
const observations = [];
|
|
250
249
|
for (const toolCall of toolCalls) {
|
|
251
250
|
onEvent?.({
|
|
252
251
|
type: "tool_start",
|
|
@@ -271,11 +270,7 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
|
|
|
271
270
|
result,
|
|
272
271
|
});
|
|
273
272
|
|
|
274
|
-
|
|
275
|
-
messages.push({
|
|
276
|
-
role: "user",
|
|
277
|
-
content: `Observation from ${toolCall.function.name}:\n${JSON.stringify(result, null, 2)}`,
|
|
278
|
-
});
|
|
273
|
+
observations.push(`Observation from ${toolCall.function.name}:\n${JSON.stringify(result, null, 2)}`);
|
|
279
274
|
} catch (error) {
|
|
280
275
|
onEvent?.({
|
|
281
276
|
type: "tool_result",
|
|
@@ -285,15 +280,16 @@ export async function runAgentTurn({ workspaceRoot, model, history, userPrompt,
|
|
|
285
280
|
error: error instanceof Error ? error.message : String(error),
|
|
286
281
|
},
|
|
287
282
|
});
|
|
288
|
-
|
|
289
|
-
role: "tool",
|
|
290
|
-
tool_call_id: toolCall.id,
|
|
291
|
-
content: JSON.stringify({
|
|
292
|
-
ok: false,
|
|
293
|
-
error: error instanceof Error ? error.message : String(error),
|
|
294
|
-
}),
|
|
295
|
-
});
|
|
283
|
+
observations.push(`Error from ${toolCall.function.name}:\n${error instanceof Error ? error.message : String(error)}`);
|
|
296
284
|
}
|
|
297
285
|
}
|
|
286
|
+
|
|
287
|
+
// Workaround: Use 'user' role instead of 'tool' because the remote server rejects 'tool' role.
|
|
288
|
+
if (observations.length > 0) {
|
|
289
|
+
messages.push({
|
|
290
|
+
role: "user",
|
|
291
|
+
content: observations.join("\n\n"),
|
|
292
|
+
});
|
|
293
|
+
}
|
|
298
294
|
}
|
|
299
295
|
}
|
package/src/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { printTable, resolveWorkspaceRoot } from "./utils.js";
|
|
|
11
11
|
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
|
|
14
|
-
const VERSION = "0.
|
|
14
|
+
const VERSION = "1.0.0";
|
|
15
15
|
|
|
16
16
|
async function handleAutoUpdate() {
|
|
17
17
|
// If we were just restarted by an update, don't check again
|
package/src/ollama.js
CHANGED
|
@@ -2,7 +2,7 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { promisify } from "node:util";
|
|
3
3
|
|
|
4
4
|
const execFileAsync = promisify(execFile);
|
|
5
|
-
const BASE_URL = process.env.NETHERITE_BASE_URL || "http://176.88.249.119:11434";
|
|
5
|
+
const BASE_URL = process.env.OLLAMA_BASE_URL || process.env.NETHERITE_BASE_URL || "http://176.88.249.119:11434";
|
|
6
6
|
const PREFERRED_DEFAULT_MODELS = ["glm-5:cloud"];
|
|
7
7
|
|
|
8
8
|
async function request(pathname, body, signal, retries = 3) {
|
package/src/tools.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import crypto from "node:crypto";
|
|
4
5
|
import {
|
|
5
6
|
clampText,
|
|
6
7
|
ensureDir,
|
|
@@ -11,6 +12,8 @@ import {
|
|
|
11
12
|
toAbsoluteInsideRoot,
|
|
12
13
|
} from "./utils.js";
|
|
13
14
|
|
|
15
|
+
const activeProcesses = new Map();
|
|
16
|
+
|
|
14
17
|
function todoFile(root) {
|
|
15
18
|
return path.join(root, ".netherite", "todos.json");
|
|
16
19
|
}
|
|
@@ -60,6 +63,21 @@ export function getToolDefinitions() {
|
|
|
60
63
|
},
|
|
61
64
|
},
|
|
62
65
|
},
|
|
66
|
+
{
|
|
67
|
+
type: "function",
|
|
68
|
+
function: {
|
|
69
|
+
name: "create_file",
|
|
70
|
+
description: "Create a new file.",
|
|
71
|
+
parameters: {
|
|
72
|
+
type: "object",
|
|
73
|
+
properties: {
|
|
74
|
+
path: { type: "string" },
|
|
75
|
+
content: { type: "string" },
|
|
76
|
+
},
|
|
77
|
+
required: ["path", "content"],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
},
|
|
63
81
|
{
|
|
64
82
|
type: "function",
|
|
65
83
|
function: {
|
|
@@ -75,6 +93,21 @@ export function getToolDefinitions() {
|
|
|
75
93
|
},
|
|
76
94
|
},
|
|
77
95
|
},
|
|
96
|
+
{
|
|
97
|
+
type: "function",
|
|
98
|
+
function: {
|
|
99
|
+
name: "append_file",
|
|
100
|
+
description: "Append content to a file.",
|
|
101
|
+
parameters: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
path: { type: "string" },
|
|
105
|
+
content: { type: "string" },
|
|
106
|
+
},
|
|
107
|
+
required: ["path", "content"],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
78
111
|
{
|
|
79
112
|
type: "function",
|
|
80
113
|
function: {
|
|
@@ -91,6 +124,50 @@ export function getToolDefinitions() {
|
|
|
91
124
|
},
|
|
92
125
|
},
|
|
93
126
|
},
|
|
127
|
+
{
|
|
128
|
+
type: "function",
|
|
129
|
+
function: {
|
|
130
|
+
name: "make_dir",
|
|
131
|
+
description: "Create a new directory.",
|
|
132
|
+
parameters: {
|
|
133
|
+
type: "object",
|
|
134
|
+
properties: {
|
|
135
|
+
path: { type: "string" },
|
|
136
|
+
},
|
|
137
|
+
required: ["path"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
type: "function",
|
|
143
|
+
function: {
|
|
144
|
+
name: "remove_path",
|
|
145
|
+
description: "Delete a file or directory.",
|
|
146
|
+
parameters: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
path: { type: "string" },
|
|
150
|
+
recursive: { type: "boolean" },
|
|
151
|
+
},
|
|
152
|
+
required: ["path"],
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
type: "function",
|
|
158
|
+
function: {
|
|
159
|
+
name: "rename_path",
|
|
160
|
+
description: "Rename a file or directory.",
|
|
161
|
+
parameters: {
|
|
162
|
+
type: "object",
|
|
163
|
+
properties: {
|
|
164
|
+
oldPath: { type: "string" },
|
|
165
|
+
newPath: { type: "string" },
|
|
166
|
+
},
|
|
167
|
+
required: ["oldPath", "newPath"],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
94
171
|
{
|
|
95
172
|
type: "function",
|
|
96
173
|
function: {
|
|
@@ -105,6 +182,21 @@ export function getToolDefinitions() {
|
|
|
105
182
|
},
|
|
106
183
|
},
|
|
107
184
|
},
|
|
185
|
+
{
|
|
186
|
+
type: "function",
|
|
187
|
+
function: {
|
|
188
|
+
name: "send_input",
|
|
189
|
+
description: "Send input (stdin) to a background process_id that is waiting for input.",
|
|
190
|
+
parameters: {
|
|
191
|
+
type: "object",
|
|
192
|
+
properties: {
|
|
193
|
+
process_id: { type: "string" },
|
|
194
|
+
input: { type: "string" },
|
|
195
|
+
},
|
|
196
|
+
required: ["process_id", "input"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
},
|
|
108
200
|
];
|
|
109
201
|
}
|
|
110
202
|
|
|
@@ -136,42 +228,61 @@ async function streamFilePreview(text, hooks = {}, maxLines = 12) {
|
|
|
136
228
|
}
|
|
137
229
|
|
|
138
230
|
async function runSpawnedCommand(root, command, args = [], hooks = {}) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
231
|
+
const id = crypto.randomUUID();
|
|
232
|
+
const child = spawn(command, args, {
|
|
233
|
+
cwd: root,
|
|
234
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
235
|
+
});
|
|
144
236
|
|
|
145
|
-
|
|
146
|
-
|
|
237
|
+
const p = { child, stdout: "", stderr: "" };
|
|
238
|
+
activeProcesses.set(id, p);
|
|
147
239
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
240
|
+
child.stdout?.on("data", (chunk) => {
|
|
241
|
+
const text = chunk.toString("utf8");
|
|
242
|
+
p.stdout += text;
|
|
243
|
+
hooks.onProgress?.({ stream: "stdout", text });
|
|
244
|
+
});
|
|
153
245
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
246
|
+
child.stderr?.on("data", (chunk) => {
|
|
247
|
+
const text = chunk.toString("utf8");
|
|
248
|
+
p.stderr += text;
|
|
249
|
+
hooks.onProgress?.({ stream: "stderr", text });
|
|
250
|
+
});
|
|
159
251
|
|
|
160
|
-
|
|
252
|
+
const exitPromise = new Promise((resolve, reject) => {
|
|
253
|
+
child.on("error", (err) => {
|
|
254
|
+
activeProcesses.delete(id);
|
|
255
|
+
reject(err);
|
|
256
|
+
});
|
|
161
257
|
child.on("close", (code) => {
|
|
258
|
+
activeProcesses.delete(id);
|
|
162
259
|
if (code === 0) {
|
|
163
260
|
resolve({
|
|
164
261
|
ok: true,
|
|
165
262
|
command,
|
|
166
263
|
args,
|
|
167
|
-
stdout: clampText(stdout),
|
|
168
|
-
stderr: clampText(stderr),
|
|
264
|
+
stdout: clampText(p.stdout),
|
|
265
|
+
stderr: clampText(p.stderr),
|
|
169
266
|
});
|
|
170
267
|
return;
|
|
171
268
|
}
|
|
172
|
-
reject(new Error((stderr || stdout || `${command} exited with code ${code}`).trim()));
|
|
269
|
+
reject(new Error((p.stderr || p.stdout || `${command} exited with code ${code}`).trim()));
|
|
173
270
|
});
|
|
174
271
|
});
|
|
272
|
+
|
|
273
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => {
|
|
274
|
+
if (activeProcesses.has(id)) {
|
|
275
|
+
resolve({
|
|
276
|
+
status: "running",
|
|
277
|
+
process_id: id,
|
|
278
|
+
message: "Process is still running and may be waiting for input. Use 'send_input' to interact with it.",
|
|
279
|
+
stdout: clampText(p.stdout),
|
|
280
|
+
stderr: clampText(p.stderr),
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}, 2000));
|
|
284
|
+
|
|
285
|
+
return await Promise.race([exitPromise, timeoutPromise]);
|
|
175
286
|
}
|
|
176
287
|
|
|
177
288
|
async function runOneCommand(root, command, args = [], hooks = {}) {
|
|
@@ -275,6 +386,40 @@ export async function executeToolCall(root, toolCall, hooks = {}) {
|
|
|
275
386
|
fs.rmSync(target, { recursive: Boolean(args.recursive), force: false });
|
|
276
387
|
return { ok: true, path: args.path };
|
|
277
388
|
}
|
|
389
|
+
case "rename_path": {
|
|
390
|
+
const oldTarget = toAbsoluteInsideRoot(root, args.oldPath);
|
|
391
|
+
const newTarget = toAbsoluteInsideRoot(root, args.newPath);
|
|
392
|
+
if (!fs.existsSync(oldTarget)) throw new Error(`Source path not found: ${args.oldPath}`);
|
|
393
|
+
ensureDir(path.dirname(newTarget));
|
|
394
|
+
fs.renameSync(oldTarget, newTarget);
|
|
395
|
+
return { ok: true, oldPath: args.oldPath, newPath: args.newPath };
|
|
396
|
+
}
|
|
397
|
+
case "send_input": {
|
|
398
|
+
const id = args.process_id;
|
|
399
|
+
const p = activeProcesses.get(id);
|
|
400
|
+
if (!p) throw new Error(`Process ${id} not found`);
|
|
401
|
+
p.stdout = "";
|
|
402
|
+
p.stderr = "";
|
|
403
|
+
if (args.input) {
|
|
404
|
+
p.child.stdin.write(args.input);
|
|
405
|
+
hooks.onProgress?.({ stream: "stdout", text: args.input });
|
|
406
|
+
}
|
|
407
|
+
const exitPromise = new Promise((resolve, reject) => {
|
|
408
|
+
const onExit = (code) => {
|
|
409
|
+
activeProcesses.delete(id);
|
|
410
|
+
if (code === 0) resolve({ ok: true, code, stdout: clampText(p.stdout), stderr: clampText(p.stderr) });
|
|
411
|
+
else reject(new Error((p.stderr || p.stdout || `exited with code ${code}`).trim()));
|
|
412
|
+
};
|
|
413
|
+
p.child.once("close", onExit);
|
|
414
|
+
p.child.once("error", reject);
|
|
415
|
+
});
|
|
416
|
+
const timeoutPromise = new Promise((resolve) => setTimeout(() => {
|
|
417
|
+
if (activeProcesses.has(id)) {
|
|
418
|
+
resolve({ status: "running", process_id: id, stdout: clampText(p.stdout), stderr: clampText(p.stderr) });
|
|
419
|
+
}
|
|
420
|
+
}, 2000));
|
|
421
|
+
return await Promise.race([exitPromise, timeoutPromise]);
|
|
422
|
+
}
|
|
278
423
|
case "run_command": {
|
|
279
424
|
if (args.commandLine) {
|
|
280
425
|
return await runShellLine(root, args.commandLine, hooks);
|