naisys 1.5.0 → 1.6.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/README.md +20 -7
- package/dist/command/commandHandler.js +13 -2
- package/dist/command/commandLoop.js +4 -0
- package/dist/command/promptBuilder.js +41 -35
- package/dist/command/shellCommand.js +26 -18
- package/dist/command/shellWrapper.js +205 -47
- package/dist/config.js +4 -2
- package/dist/features/llmail.js +17 -9
- package/dist/features/subagent.js +11 -8
- package/dist/llm/llModels.js +4 -4
- package/dist/llm/llmService.js +5 -2
- package/dist/llm/systemMessage.js +5 -2
- package/dist/utils/logService.js +1 -1
- package/package.json +14 -11
package/README.md
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
## NAISYS (Node.js Autonomous Intelligence System)
|
|
2
2
|
|
|
3
|
-
NAISYS
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
NAISYS allows any LLM you want to operate a standard linux shell given your instructions. You can control how much
|
|
4
|
+
to spend, the maximum number of tokens to use per session, how long to wait between commands, etc.. Between each command
|
|
5
|
+
NAISYS will wait a few seconds to accept any input you want to put in yourself in case you want to colllaborate with the
|
|
6
|
+
LLM, give it hints, and/or diagnose the session. Once the LLM reaches the token max you specified for the sesssion it
|
|
7
|
+
will wrap things up, and start a fresh shell for the LLM to continue on its work.
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
NAISYS tries to be a minimal wrapper, just helping the LLM operate in the shell 'better'. Making commands 'context friendly'. For instace if a command is long running, NAISYS will interrupt it, show the LLM the current output, and ask the LLM what it wants to
|
|
10
|
+
do next - wait, kill, or send input. The custom command prompt helps the LLM keep track of its token usage during the session. The 'comment' command helps the LLM think outloud without putting invalid commands into the shell.
|
|
11
|
+
|
|
12
|
+
Some use cases are building websites, diagnosing a system for security concerns, mapping out the topology of the local
|
|
13
|
+
network, learning and performing arbitrary tasks, or just plain exploring the limits of autonomy. NAISYS has a built-in
|
|
14
|
+
system for inter-agent communiation. You can manually startup mulitple instances of NAISYS with different roles, or
|
|
15
|
+
you can allow agents to start their own sub-agents on demand with instructions defined by the LLM itself!
|
|
11
16
|
|
|
12
17
|
[NPM](https://www.npmjs.com/package/naisys) | [Website](https://naisys.org) | [Discord](https://discord.gg/JBUPWSbaEt) | [Demo Video](https://www.youtube.com/watch?v=Ttya3ixjumo)
|
|
13
18
|
|
|
@@ -194,9 +199,16 @@ initialCommands:
|
|
|
194
199
|
- To use NAISYS on Windows you need to run it locally from source (or from within WSL)
|
|
195
200
|
- Use the above instructions to install locally, and then continue with the instructions below
|
|
196
201
|
- Install WSL (Windows Subsystem for Linux)
|
|
202
|
+
- Install a Linux distribution, Ubuntu can easily be installed from the Microsoft Store
|
|
197
203
|
- The `NAISYS_FOLDER` and `WEBSITE_FOLDER` should be set to the WSL path
|
|
198
204
|
- So `C:\var\naisys` should be `/mnt/c/var/naisys` in the `.env` file
|
|
199
205
|
|
|
206
|
+
#### Notes for MacOS users
|
|
207
|
+
|
|
208
|
+
- The browser llmynx requires `timeout` and `lynx`. Run these commands to install them:
|
|
209
|
+
- `brew install coreutils`
|
|
210
|
+
- `brew install lynx`
|
|
211
|
+
|
|
200
212
|
#### Using NAISYS for a website
|
|
201
213
|
|
|
202
214
|
- Many frameworks come with their own dev server
|
|
@@ -205,6 +217,7 @@ initialCommands:
|
|
|
205
217
|
|
|
206
218
|
## Changelog
|
|
207
219
|
|
|
220
|
+
- 1.6: Support for long running shell commands and full screen terminal output
|
|
208
221
|
- 1.5: Allow agents to start their own parallel `subagents`
|
|
209
222
|
- 1.4: `genimg` command for generating images
|
|
210
223
|
- 1.3: Post-session 'dreaming' as well as a mail 'blackout' period
|
|
@@ -78,6 +78,10 @@ export async function processCommand(prompt, consoleInput) {
|
|
|
78
78
|
if (!config.endSessionEnabled) {
|
|
79
79
|
throw 'The "trimsession" command is not enabled in this environment.';
|
|
80
80
|
}
|
|
81
|
+
if (shellCommand.isShellSuspended()) {
|
|
82
|
+
await contextManager.append("Session cannot be ended while a shell command is active.");
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
81
85
|
// Don't need to check end line as this is the last command in the context, just read to the end
|
|
82
86
|
const endSessionNotes = utilities.trimChars(cmdArgs, '"');
|
|
83
87
|
if (!endSessionNotes) {
|
|
@@ -158,8 +162,8 @@ export async function processCommand(prompt, consoleInput) {
|
|
|
158
162
|
? NextCommandAction.ExitApplication
|
|
159
163
|
: NextCommandAction.Continue;
|
|
160
164
|
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
165
|
+
} // End switch
|
|
166
|
+
} // End loop processing LLM response
|
|
163
167
|
// display unprocessed lines to aid in debugging
|
|
164
168
|
if (consoleInput.trim()) {
|
|
165
169
|
await output.errorAndLog(`Unprocessed LLM response:\n${consoleInput}`);
|
|
@@ -227,6 +231,13 @@ async function splitMultipleInputCommands(nextInput) {
|
|
|
227
231
|
input = nextInput.slice(0, newLinePos);
|
|
228
232
|
nextInput = nextInput.slice(newLinePos).trim();
|
|
229
233
|
}
|
|
234
|
+
// If shell is suspended, the process can kill/wait the shell, and may run some commands after
|
|
235
|
+
else if (newLinePos > 0 &&
|
|
236
|
+
shellCommand.isShellSuspended() &&
|
|
237
|
+
(nextInput.startsWith("kill") || nextInput.startsWith("wait"))) {
|
|
238
|
+
input = nextInput.slice(0, newLinePos);
|
|
239
|
+
nextInput = nextInput.slice(newLinePos).trim();
|
|
240
|
+
}
|
|
230
241
|
// Else process the entire input now
|
|
231
242
|
else {
|
|
232
243
|
input = nextInput;
|
|
@@ -19,6 +19,7 @@ import * as utilities from "../utils/utilities.js";
|
|
|
19
19
|
import * as commandHandler from "./commandHandler.js";
|
|
20
20
|
import { NextCommandAction } from "./commandHandler.js";
|
|
21
21
|
import * as promptBuilder from "./promptBuilder.js";
|
|
22
|
+
import * as shellCommand from "./shellCommand.js";
|
|
22
23
|
const maxErrorCount = 5;
|
|
23
24
|
export async function run() {
|
|
24
25
|
// Show Agent Config exept the agent prompt
|
|
@@ -52,6 +53,9 @@ export async function run() {
|
|
|
52
53
|
let pauseSeconds = config.agent.debugPauseSeconds;
|
|
53
54
|
let wakeOnMessage = config.agent.wakeOnMessage;
|
|
54
55
|
while (nextCommandAction == NextCommandAction.Continue) {
|
|
56
|
+
if (shellCommand.isShellSuspended()) {
|
|
57
|
+
await contextManager.append(`Command still running. Enter 'wait' to continue waiting. 'kill' to terminate. Other input will be sent to the process.`, ContentSource.Console);
|
|
58
|
+
}
|
|
55
59
|
let prompt = await promptBuilder.getPrompt(pauseSeconds, wakeOnMessage);
|
|
56
60
|
let consoleInput = "";
|
|
57
61
|
// Debug command prompt
|
|
@@ -8,28 +8,35 @@ import * as inputMode from "../utils/inputMode.js";
|
|
|
8
8
|
import { InputMode } from "../utils/inputMode.js";
|
|
9
9
|
import * as output from "../utils/output.js";
|
|
10
10
|
import * as shellWrapper from "./shellWrapper.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/**
|
|
12
|
+
* When actual output is entered by the user we want to cancel any auto-continue timers and/or wake on message
|
|
13
|
+
* We don't want to cancel if the user is entering a chords like ctrl+b then down arrow, when using tmux
|
|
14
|
+
* This is why we can't put the event listener on the standard process.stdin/keypress event.
|
|
15
|
+
* There is no 'data entered' output event so this monkey patch does that
|
|
16
|
+
*/
|
|
17
|
+
const _writeEventEmitter = new events.EventEmitter();
|
|
15
18
|
const _writeEventName = "write";
|
|
16
|
-
const _outputEmitter = new events.EventEmitter();
|
|
17
19
|
const _originalWrite = process.stdout.write.bind(process.stdout);
|
|
18
20
|
process.stdout.write = (...args) => {
|
|
19
|
-
|
|
21
|
+
_writeEventEmitter.emit(_writeEventName, false, ...args);
|
|
20
22
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
23
|
return _originalWrite.apply(process.stdout, args);
|
|
22
24
|
};
|
|
23
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Tried to make this local and have it cleaned up with close() after using it, but
|
|
27
|
+
* due to the terminal settings below there are bugs with both terminal true and false
|
|
28
|
+
* pause() actually is nice in that it queues up the input, and doesn't allow the user
|
|
29
|
+
* to enter anything while the LLM is working
|
|
30
|
+
*/
|
|
31
|
+
const readlineInterface = readline.createInterface({
|
|
24
32
|
input: process.stdin,
|
|
25
33
|
output: process.stdout,
|
|
34
|
+
// With this set to ture, after an abort the second input will not be processed, see:
|
|
35
|
+
// https://gist.github.com/swax/964a2488494048c8e03d05493d9370f8
|
|
36
|
+
// With this set to false, the stdout.write event above will not be triggered
|
|
37
|
+
terminal: true,
|
|
26
38
|
});
|
|
27
|
-
|
|
28
|
-
let readlineInterfaceClosed = false;
|
|
29
|
-
_readlineInterface.on("close", () => {
|
|
30
|
-
readlineInterfaceClosed = true;
|
|
31
|
-
output.error("Readline interface closed");
|
|
32
|
-
});
|
|
39
|
+
readlineInterface.pause();
|
|
33
40
|
export async function getPrompt(pauseSeconds, wakeOnMessage) {
|
|
34
41
|
const promptSuffix = inputMode.current == InputMode.Debug ? "#" : "$";
|
|
35
42
|
const tokenMax = config.agent.tokenMax;
|
|
@@ -60,27 +67,24 @@ export function getInput(commandPrompt, pauseSeconds, wakeOnMessage) {
|
|
|
60
67
|
let timeout;
|
|
61
68
|
let interval;
|
|
62
69
|
let timeoutCancelled = false;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
70
|
+
function clearTimers() {
|
|
71
|
+
timeoutCancelled = true;
|
|
72
|
+
_writeEventEmitter.off(_writeEventName, cancelWaitingForUserInput);
|
|
73
|
+
clearTimeout(timeout);
|
|
74
|
+
clearInterval(interval);
|
|
66
75
|
}
|
|
67
76
|
/** Cancels waiting for user input */
|
|
68
|
-
|
|
77
|
+
const cancelWaitingForUserInput = (questionAborted, buffer) => {
|
|
69
78
|
// Don't allow console escape commands like \x1B[1G to cancel the timeout
|
|
70
79
|
if (timeoutCancelled || (buffer && !/^[a-zA-Z0-9 ]+$/.test(buffer))) {
|
|
71
80
|
return;
|
|
72
81
|
}
|
|
73
|
-
|
|
74
|
-
_outputEmitter.off(_writeEventName, onStdinWrite_cancelTimers);
|
|
75
|
-
clearTimeout(timeout);
|
|
76
|
-
clearInterval(interval);
|
|
77
|
-
timeout = undefined;
|
|
78
|
-
interval = undefined;
|
|
82
|
+
clearTimers();
|
|
79
83
|
if (questionAborted) {
|
|
80
84
|
return;
|
|
81
85
|
}
|
|
82
|
-
// Else timeout interrupted by user input
|
|
83
|
-
// to prevent the user from thinking the timeout still applies
|
|
86
|
+
// Else timeout interrupted by user input
|
|
87
|
+
// Clear out the timeout information from the prompt to prevent the user from thinking the timeout still applies
|
|
84
88
|
let pausePos = commandPrompt.indexOf("[Paused:");
|
|
85
89
|
pausePos =
|
|
86
90
|
pausePos == -1 ? commandPrompt.indexOf("[WakeOnMsg]") : pausePos;
|
|
@@ -92,21 +96,22 @@ export function getInput(commandPrompt, pauseSeconds, wakeOnMessage) {
|
|
|
92
96
|
process.stdout.write("-".repeat(charsBack - 3));
|
|
93
97
|
readline.moveCursor(process.stdout, 3, 0);
|
|
94
98
|
}
|
|
95
|
-
}
|
|
96
|
-
|
|
99
|
+
};
|
|
100
|
+
readlineInterface.question(chalk.greenBright(commandPrompt), { signal: questionController.signal }, (answer) => {
|
|
101
|
+
clearTimers();
|
|
102
|
+
readlineInterface.pause();
|
|
97
103
|
resolve(answer);
|
|
98
104
|
});
|
|
99
105
|
// If user starts typing in prompt, cancel any auto timeouts or wake on msg
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
106
|
+
_writeEventEmitter.on(_writeEventName, cancelWaitingForUserInput);
|
|
107
|
+
function abortQuestion() {
|
|
108
|
+
cancelWaitingForUserInput(true);
|
|
103
109
|
questionController.abort();
|
|
110
|
+
readlineInterface.pause();
|
|
104
111
|
resolve("");
|
|
105
|
-
}
|
|
112
|
+
}
|
|
106
113
|
if (pauseSeconds) {
|
|
107
|
-
timeout = setTimeout(
|
|
108
|
-
abortQuestion();
|
|
109
|
-
}, pauseSeconds * 1000);
|
|
114
|
+
timeout = setTimeout(abortQuestion, pauseSeconds * 1000);
|
|
110
115
|
}
|
|
111
116
|
if (wakeOnMessage) {
|
|
112
117
|
// Break timeout if new message is received
|
|
@@ -133,7 +138,8 @@ export function getInput(commandPrompt, pauseSeconds, wakeOnMessage) {
|
|
|
133
138
|
}
|
|
134
139
|
export function getCommandConfirmation() {
|
|
135
140
|
return new Promise((resolve) => {
|
|
136
|
-
|
|
141
|
+
readlineInterface.question(chalk.greenBright("Allow command to run? [y/n] "), (answer) => {
|
|
142
|
+
readlineInterface.pause();
|
|
137
143
|
resolve(answer);
|
|
138
144
|
});
|
|
139
145
|
});
|
|
@@ -4,27 +4,35 @@ import * as inputMode from "../utils/inputMode.js";
|
|
|
4
4
|
import { InputMode } from "../utils/inputMode.js";
|
|
5
5
|
import * as utilities from "../utils/utilities.js";
|
|
6
6
|
import * as shellWrapper from "./shellWrapper.js";
|
|
7
|
+
export const isShellSuspended = () => shellWrapper.isShellSuspended();
|
|
7
8
|
export async function handleCommand(input) {
|
|
8
9
|
const cmdParams = input.split(" ");
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
throw `Interactive mode with lynx is not supported. Use --dump with lynx to view a website`;
|
|
16
|
-
}
|
|
17
|
-
if (cmdParams[0] == "exit") {
|
|
18
|
-
if (inputMode.current == InputMode.LLM) {
|
|
19
|
-
throw "Use 'endsession' to end the session and clear the console log.";
|
|
10
|
+
let response;
|
|
11
|
+
if (!isShellSuspended()) {
|
|
12
|
+
if (["nano", "vi", "vim"].includes(cmdParams[0])) {
|
|
13
|
+
// Route user to context friendly edit commands that can read/write the entire file in one go
|
|
14
|
+
// Having EOF in quotes is important as it prevents the shell from replacing $variables with bash values
|
|
15
|
+
throw `${cmdParams[0]} not supported. Use \`cat\` to read a file and \`cat > filename << 'EOF'\` to write a file`;
|
|
20
16
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
await shellWrapper.terminate();
|
|
24
|
-
return true;
|
|
17
|
+
if (cmdParams[0] == "lynx" && cmdParams[1] != "--dump") {
|
|
18
|
+
throw `Interactive mode with lynx is not supported. Use --dump with lynx to view a website`;
|
|
25
19
|
}
|
|
20
|
+
if (cmdParams[0] == "exit") {
|
|
21
|
+
if (inputMode.current == InputMode.LLM) {
|
|
22
|
+
throw "Use 'endsession' to end the session and clear the console log.";
|
|
23
|
+
}
|
|
24
|
+
// Only the debug user is allowed to exit the shell
|
|
25
|
+
else if (inputMode.current == InputMode.Debug) {
|
|
26
|
+
await shellWrapper.terminate();
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
response = await shellWrapper.executeCommand(input);
|
|
31
|
+
}
|
|
32
|
+
// Else shell is suspended, continue
|
|
33
|
+
else {
|
|
34
|
+
response = await shellWrapper.continueCommand(input);
|
|
26
35
|
}
|
|
27
|
-
let response = await shellWrapper.executeCommand(input);
|
|
28
36
|
let outputLimitExceeded = false;
|
|
29
37
|
const tokenCount = utilities.getTokenCount(response);
|
|
30
38
|
// Prevent too much output from blowing up the context
|
|
@@ -41,9 +49,9 @@ export async function handleCommand(input) {
|
|
|
41
49
|
}
|
|
42
50
|
if (response.endsWith(": command not found")) {
|
|
43
51
|
response +=
|
|
44
|
-
"
|
|
52
|
+
"\nPlease enter a valid Linux or NAISYS command after the prompt. Use the 'comment' command for thoughts.";
|
|
45
53
|
}
|
|
46
|
-
//
|
|
54
|
+
// TODO: move this into the command handler to remove the context manager dependency
|
|
47
55
|
await contextManager.append(response);
|
|
48
56
|
return false;
|
|
49
57
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
+
import xterm from "@xterm/headless";
|
|
1
2
|
import { spawn } from "child_process";
|
|
2
3
|
import * as fs from "fs";
|
|
3
4
|
import * as os from "os";
|
|
5
|
+
import stripAnsi from "strip-ansi";
|
|
6
|
+
import treeKill from "tree-kill";
|
|
4
7
|
import * as config from "../config.js";
|
|
5
8
|
import * as output from "../utils/output.js";
|
|
9
|
+
import * as pathService from "../utils/pathService.js";
|
|
6
10
|
import { NaisysPath } from "../utils/pathService.js";
|
|
7
11
|
var ShellEvent;
|
|
8
12
|
(function (ShellEvent) {
|
|
@@ -14,31 +18,35 @@ let _process;
|
|
|
14
18
|
let _currentProcessId;
|
|
15
19
|
let _commandOutput = "";
|
|
16
20
|
let _currentPath;
|
|
21
|
+
let _terminal;
|
|
22
|
+
let _bufferChangeEvent;
|
|
23
|
+
let _currentBufferType = "normal";
|
|
17
24
|
let _resolveCurrentCommand;
|
|
18
25
|
let _currentCommandTimeout;
|
|
19
|
-
let _startTime;
|
|
20
26
|
/** How we know the command has completed when running the command inside a shell like bash or wsl */
|
|
21
27
|
const _commandDelimiter = "__COMMAND_END_X7YUTT__";
|
|
28
|
+
let _wrapperSuspended = false;
|
|
29
|
+
const _queuedOutput = [];
|
|
22
30
|
async function ensureOpen() {
|
|
23
31
|
if (_process) {
|
|
24
32
|
return;
|
|
25
33
|
}
|
|
26
34
|
resetCommand();
|
|
27
|
-
const
|
|
28
|
-
_process = spawn(
|
|
35
|
+
const spawnCmd = os.platform() === "win32" ? "wsl" : "bash";
|
|
36
|
+
_process = spawn(spawnCmd, [], { stdio: "pipe" });
|
|
29
37
|
const pid = _process.pid;
|
|
30
38
|
if (!pid) {
|
|
31
39
|
throw "Shell process failed to start";
|
|
32
40
|
}
|
|
33
41
|
_currentProcessId = pid;
|
|
34
42
|
_process.stdout.on("data", (data) => {
|
|
35
|
-
processOutput(data
|
|
43
|
+
processOutput(data, ShellEvent.Ouptput, pid);
|
|
36
44
|
});
|
|
37
45
|
_process.stderr.on("data", (data) => {
|
|
38
|
-
processOutput(data
|
|
46
|
+
processOutput(data, ShellEvent.Error, pid);
|
|
39
47
|
});
|
|
40
48
|
_process.on("close", (code) => {
|
|
41
|
-
processOutput(`${code}
|
|
49
|
+
processOutput(Buffer.from(`${code}`), ShellEvent.Exit, pid);
|
|
42
50
|
});
|
|
43
51
|
// Init users home dir on first run, on shell crash/rerun go back to the current path
|
|
44
52
|
if (!_currentPath) {
|
|
@@ -61,7 +69,12 @@ function errorIfNotEmpty(response) {
|
|
|
61
69
|
output.error(response);
|
|
62
70
|
}
|
|
63
71
|
}
|
|
64
|
-
function processOutput(
|
|
72
|
+
function processOutput(rawDataStr, eventType, pid) {
|
|
73
|
+
if (_wrapperSuspended) {
|
|
74
|
+
_queuedOutput.push({ rawDataStr, eventType, pid });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let dataStr = stripAnsi(rawDataStr.toString());
|
|
65
78
|
if (pid != _currentProcessId) {
|
|
66
79
|
output.comment(`Ignoring '${eventType}' from old shell process ${pid}: ` + dataStr);
|
|
67
80
|
return;
|
|
@@ -72,75 +85,170 @@ function processOutput(dataStr, eventType, pid) {
|
|
|
72
85
|
return;
|
|
73
86
|
}
|
|
74
87
|
if (eventType === ShellEvent.Exit) {
|
|
75
|
-
output.error(
|
|
76
|
-
|
|
77
|
-
?
|
|
78
|
-
:
|
|
79
|
-
|
|
80
|
-
`\nNAISYS: Command hit time out limit after ${elapsedSeconds} seconds. If possible figure out how to run the command faster or break it up into smaller parts.`;
|
|
88
|
+
output.error(`SHELL EXIT. PID: ${_process?.pid}, CODE: ${rawDataStr}`);
|
|
89
|
+
let finalOutput = _currentBufferType == "alternate"
|
|
90
|
+
? _getTerminalActiveBuffer()
|
|
91
|
+
: _commandOutput.trim();
|
|
92
|
+
finalOutput += `\nNAISYS: Command killed.`;
|
|
81
93
|
resetProcess();
|
|
82
|
-
|
|
94
|
+
_completeCommand(finalOutput);
|
|
83
95
|
return;
|
|
84
96
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
97
|
+
// Should only happen back in normal mode, so we don't need to modify the rawDataStr
|
|
98
|
+
let endDelimiterHit = false;
|
|
99
|
+
const endDelimiterPos = dataStr.indexOf(_commandDelimiter);
|
|
100
|
+
if (endDelimiterPos != -1 &&
|
|
101
|
+
// Quotes will only precede the delimiter if the echo command got in the output, so don't count it
|
|
102
|
+
// For example running nano or vi will cause this
|
|
103
|
+
dataStr[endDelimiterPos - 1] != '"') {
|
|
104
|
+
endDelimiterHit = true;
|
|
105
|
+
dataStr = dataStr.slice(0, endDelimiterPos);
|
|
106
|
+
// If it does happen somehow, log it so I can figure out why/how and what to do about it
|
|
107
|
+
if (_currentBufferType == "alternate") {
|
|
108
|
+
output.error("UNEXPECTED END DELIMITER IN ALTERNATE BUFFER: " + dataStr);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// If we're in alternate mode, just write the data to the terminal
|
|
112
|
+
// When the buffer changes back to normal, the output will be copied back to the command output
|
|
113
|
+
if (_currentBufferType == "normal") {
|
|
88
114
|
_commandOutput += dataStr;
|
|
89
115
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const response = _commandOutput.trim();
|
|
116
|
+
// TODO: get token size of buffer, if too big, switch it front/middle/back
|
|
117
|
+
_terminal?.write(rawDataStr); // Not synchronous, second param takes a call back, don't need to handle it AFAIK
|
|
118
|
+
if (endDelimiterHit) {
|
|
119
|
+
const finalOutput = _commandOutput.trim();
|
|
95
120
|
resetCommand();
|
|
96
|
-
|
|
121
|
+
_completeCommand(finalOutput);
|
|
97
122
|
}
|
|
98
123
|
}
|
|
99
124
|
export async function executeCommand(command) {
|
|
125
|
+
if (_wrapperSuspended) {
|
|
126
|
+
throw "Use continueCommand to send input to a shell command in process";
|
|
127
|
+
}
|
|
128
|
+
command = command.trim();
|
|
129
|
+
_lastCommand = command; // Set here before it gets reset by the multi line script below
|
|
100
130
|
await ensureOpen();
|
|
101
|
-
if (_currentPath && command.
|
|
131
|
+
if (_currentPath && command.split("\n").length > 1) {
|
|
102
132
|
command = await putMultilineCommandInAScript(command);
|
|
103
133
|
}
|
|
104
134
|
return new Promise((resolve, reject) => {
|
|
105
135
|
_resolveCurrentCommand = resolve;
|
|
106
|
-
const commandWithDelimiter = `${command.trim()}\necho "${_commandDelimiter} LINE:\${LINENO}"\n`;
|
|
107
136
|
if (!_process) {
|
|
108
137
|
reject("Shell process is not open");
|
|
109
138
|
return;
|
|
110
139
|
}
|
|
140
|
+
const commandWithDelimiter = `${command}\necho "${_commandDelimiter}"\n`;
|
|
111
141
|
_process.stdin.write(commandWithDelimiter);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
setOrExtendShellTimeout();
|
|
142
|
+
// Set timeout to wait for response from command
|
|
143
|
+
setCommandTimeout();
|
|
115
144
|
});
|
|
116
145
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
146
|
+
/** The LLM made its decision on how it wants to continue with the shell that previously timed out */
|
|
147
|
+
export function continueCommand(command) {
|
|
148
|
+
if (!_wrapperSuspended) {
|
|
149
|
+
throw "Shell is not suspended, use execute command";
|
|
150
|
+
}
|
|
151
|
+
command = command.trim();
|
|
152
|
+
_wrapperSuspended = false;
|
|
153
|
+
let choice;
|
|
154
|
+
if (command != "wait" && command != "kill") {
|
|
155
|
+
choice = "input";
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
choice = command;
|
|
159
|
+
}
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
_resolveCurrentCommand = resolve;
|
|
162
|
+
// If new output from the shell was queued while waiting for the LLM to decide what to do
|
|
163
|
+
if (_queuedOutput.length > 0) {
|
|
164
|
+
for (const output of _queuedOutput) {
|
|
165
|
+
processOutput(output.rawDataStr, output.eventType, output.pid);
|
|
166
|
+
}
|
|
167
|
+
_queuedOutput.length = 0;
|
|
168
|
+
// If processing queue resolved the command, then we're done
|
|
169
|
+
if (!_resolveCurrentCommand) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Used to return here if LLM was sending if output was generated while waiting for the LLM
|
|
173
|
+
// In normal mode this would make the log confusing and out of order
|
|
174
|
+
// But since we only use the terminal in alternate mode, this is fine and works
|
|
175
|
+
// with commands like `mtr` changing the display type
|
|
176
|
+
}
|
|
177
|
+
// LLM wants to wait for more output
|
|
178
|
+
if (choice == "wait") {
|
|
179
|
+
setCommandTimeout();
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Else LLM wants to kill the process
|
|
183
|
+
else if (choice == "kill") {
|
|
184
|
+
if (!_currentProcessId) {
|
|
185
|
+
reject("No process to kill");
|
|
186
|
+
}
|
|
187
|
+
else if (resetShell(_currentProcessId)) {
|
|
188
|
+
return; // Wait for exit event
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
reject("Unable to kill. Process not found");
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
// Else LLM wants to send input to the process
|
|
196
|
+
else {
|
|
197
|
+
if (!_process) {
|
|
198
|
+
reject("Shell process is not open");
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
_process.stdin.write(command + "\n");
|
|
202
|
+
_lastCommand = command;
|
|
203
|
+
setCommandTimeout();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
let _startCommandTime;
|
|
208
|
+
/** Pulled out because for commands like 'wait' we want to vary the run time based on the 'last command' */
|
|
209
|
+
let _lastCommand;
|
|
210
|
+
function setCommandTimeout() {
|
|
211
|
+
_startCommandTime = new Date();
|
|
212
|
+
let timeoutSeconds = config.shellCommand.timeoutSeconds;
|
|
213
|
+
if (config.shellCommand.longRunningCommands.some((cmd) => _lastCommand?.startsWith(cmd))) {
|
|
214
|
+
timeoutSeconds = config.shellCommand.longRunningTimeoutSeconds;
|
|
123
215
|
}
|
|
124
|
-
// Define the pid for use in the timeout closure, as _process.pid may change
|
|
125
|
-
const pid = _process.pid;
|
|
126
|
-
clearTimeout(_currentCommandTimeout);
|
|
127
216
|
_currentCommandTimeout = setTimeout(() => {
|
|
128
|
-
|
|
129
|
-
},
|
|
217
|
+
returnControlToNaisys();
|
|
218
|
+
}, timeoutSeconds * 1000);
|
|
219
|
+
}
|
|
220
|
+
function returnControlToNaisys() {
|
|
221
|
+
_wrapperSuspended = true;
|
|
222
|
+
_queuedOutput.length = 0;
|
|
223
|
+
// Flush the output to the consol, and give the LLM instructions of how it might continue
|
|
224
|
+
let outputWithInstruction = _currentBufferType == "alternate"
|
|
225
|
+
? _getTerminalActiveBuffer()
|
|
226
|
+
: _commandOutput.trim();
|
|
227
|
+
_commandOutput = "";
|
|
228
|
+
// Don't clear the alternate buffer, it's a special terminal full screen mode that the
|
|
229
|
+
// LLM might want to see updates too
|
|
230
|
+
if (_currentBufferType != "alternate") {
|
|
231
|
+
resetTerminal();
|
|
232
|
+
}
|
|
233
|
+
const waitSeconds = Math.round((new Date().getTime() - _startCommandTime.getTime()) / 1000);
|
|
234
|
+
outputWithInstruction += `\nNAISYS: Command interrupted after waiting ${waitSeconds} seconds.`;
|
|
235
|
+
_completeCommand(outputWithInstruction);
|
|
130
236
|
}
|
|
131
237
|
function resetShell(pid) {
|
|
132
238
|
if (!_process || _process.pid != pid) {
|
|
133
239
|
output.comment("Ignoring timeout for old shell process " + pid);
|
|
134
|
-
return;
|
|
240
|
+
return false;
|
|
135
241
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// A not fail proof workaround is to tell the LLM to prefix long running commands with 'timeout 10s' or similar
|
|
139
|
-
const killResponse = _process.kill();
|
|
140
|
-
output.error(`KILL SIGNAL SENT TO PID: ${_process.pid}, RESPONSE: ${killResponse ? "SUCCESS" : "FAILED"}`);
|
|
242
|
+
output.error(`KILL-TREE SIGNAL SENT TO PID: ${_process.pid}`);
|
|
243
|
+
treeKill(pid, "SIGKILL");
|
|
141
244
|
// Should trigger the process close event from here
|
|
245
|
+
return true;
|
|
142
246
|
}
|
|
143
247
|
export async function getCurrentPath() {
|
|
248
|
+
// If wrapper suspended just give the last known path
|
|
249
|
+
if (_wrapperSuspended) {
|
|
250
|
+
return _currentPath;
|
|
251
|
+
}
|
|
144
252
|
await ensureOpen();
|
|
145
253
|
_currentPath = await executeCommand("pwd");
|
|
146
254
|
return _currentPath;
|
|
@@ -152,18 +260,40 @@ export async function terminate() {
|
|
|
152
260
|
}
|
|
153
261
|
function resetCommand() {
|
|
154
262
|
_commandOutput = "";
|
|
155
|
-
|
|
263
|
+
resetTerminal();
|
|
156
264
|
clearTimeout(_currentCommandTimeout);
|
|
157
265
|
}
|
|
266
|
+
function resetTerminal() {
|
|
267
|
+
_bufferChangeEvent?.dispose();
|
|
268
|
+
_terminal?.dispose();
|
|
269
|
+
_terminal = new xterm.Terminal({
|
|
270
|
+
allowProposedApi: true,
|
|
271
|
+
rows: process.stdout.rows,
|
|
272
|
+
cols: process.stdout.columns,
|
|
273
|
+
});
|
|
274
|
+
_currentBufferType = "normal";
|
|
275
|
+
_bufferChangeEvent = _terminal.buffer.onBufferChange((buffer) => {
|
|
276
|
+
// If changing back to normal, copy the alternate buffer back to the output
|
|
277
|
+
// so it shows up when the command is resolved
|
|
278
|
+
if (_currentBufferType == "alternate" && buffer.type == "normal") {
|
|
279
|
+
output.comment("NAISYS: BUFFER CHANGE BACK TO NORMAL");
|
|
280
|
+
_commandOutput += "\n" + _getTerminalActiveBuffer() + "\n";
|
|
281
|
+
}
|
|
282
|
+
_currentBufferType = buffer.type;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
158
285
|
function resetProcess() {
|
|
159
286
|
resetCommand();
|
|
160
287
|
_process?.removeAllListeners();
|
|
161
288
|
_process = undefined;
|
|
289
|
+
_terminal?.dispose();
|
|
290
|
+
_terminal = undefined;
|
|
162
291
|
}
|
|
163
292
|
/** Wraps multi line commands in a script to make it easier to diagnose the source of errors based on line number
|
|
164
293
|
* May also help with common escaping errors */
|
|
165
294
|
function putMultilineCommandInAScript(command) {
|
|
166
|
-
const scriptPath = new NaisysPath(`${config.naisysFolder}/
|
|
295
|
+
const scriptPath = new NaisysPath(`${config.naisysFolder}/agent-data/${config.agent.username}/multiline-command.sh`);
|
|
296
|
+
pathService.ensureFileDirExists(scriptPath);
|
|
167
297
|
// set -e causes the script to exit on the first error
|
|
168
298
|
const scriptContent = `#!/bin/bash
|
|
169
299
|
set -e
|
|
@@ -176,4 +306,32 @@ ${command.trim()}`;
|
|
|
176
306
|
// `source` will run the script in the current shell, so any change directories in the script will persist in the current shell
|
|
177
307
|
return `PATH=${config.binPath}:$PATH source ${scriptPath.getNaisysPath()}`;
|
|
178
308
|
}
|
|
309
|
+
function _completeCommand(output) {
|
|
310
|
+
if (!_resolveCurrentCommand) {
|
|
311
|
+
throw "No command to resolve";
|
|
312
|
+
}
|
|
313
|
+
_resolveCurrentCommand(output);
|
|
314
|
+
_resolveCurrentCommand = undefined;
|
|
315
|
+
}
|
|
316
|
+
export function isShellSuspended() {
|
|
317
|
+
return _wrapperSuspended;
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* The alternate/active buffer is a special terminal mode that runs full screen
|
|
321
|
+
* independent of the 'normal' buffer that is more like a log
|
|
322
|
+
*/
|
|
323
|
+
function _getTerminalActiveBuffer() {
|
|
324
|
+
let output = "";
|
|
325
|
+
const bufferLineCount = _terminal?.buffer.normal?.length || 0;
|
|
326
|
+
for (let i = 0; i < bufferLineCount; i++) {
|
|
327
|
+
const line = _terminal?.buffer.alternate
|
|
328
|
+
?.getLine(i)
|
|
329
|
+
?.translateToString()
|
|
330
|
+
.trim();
|
|
331
|
+
if (line) {
|
|
332
|
+
output += line + "\n";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return output.trim();
|
|
336
|
+
}
|
|
179
337
|
//# sourceMappingURL=shellWrapper.js.map
|
package/dist/config.js
CHANGED
|
@@ -16,13 +16,15 @@ export const shellCommand = {
|
|
|
16
16
|
outputTokenMax: 3000,
|
|
17
17
|
/** The time NAISYS will wait for new shell output before giving up */
|
|
18
18
|
timeoutSeconds: 15,
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/** These commands have their own timeout so the LLM doesn't have to continually waste tokens on wait commands */
|
|
20
|
+
longRunningCommands: ["nmap", "traceroute", "tracepath", "mtr"],
|
|
21
|
+
longRunningTimeoutSeconds: 120,
|
|
21
22
|
};
|
|
22
23
|
/** Web pages loaded with llmynx will be reduced down to around this number of tokens */
|
|
23
24
|
export const webTokenMax = 2500;
|
|
24
25
|
export const endSessionEnabled = true;
|
|
25
26
|
export const mailEnabled = true;
|
|
27
|
+
export const webEnabled = true;
|
|
26
28
|
/** Experimental, live updating spot in the context for the LLM to put files, to avoid having to continually cat */
|
|
27
29
|
export const workspacesEnabled = false;
|
|
28
30
|
/** Experimental, allow LLM to trim prompts from it's own session context */
|
package/dist/features/llmail.js
CHANGED
|
@@ -19,6 +19,8 @@ async function init() {
|
|
|
19
19
|
const newDbCreated = await dbUtils.initDatabase(_dbFilePath);
|
|
20
20
|
await usingDatabase(async (db) => {
|
|
21
21
|
if (newDbCreated) {
|
|
22
|
+
// For llmail to work, the usernames need to be unique
|
|
23
|
+
// The agentPaths also need to be unique so we know what configuration each agent should use when we restart/reload naisys
|
|
22
24
|
const createTables = [
|
|
23
25
|
`CREATE TABLE Users (
|
|
24
26
|
id INTEGER PRIMARY KEY,
|
|
@@ -65,16 +67,22 @@ async function init() {
|
|
|
65
67
|
]);
|
|
66
68
|
// If user not in database, add them
|
|
67
69
|
if (!user) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
try {
|
|
71
|
+
const insertedUser = await db.run("INSERT INTO Users (username, title, agentPath, leadUsername) VALUES (?, ?, ?, ?)", [
|
|
72
|
+
config.agent.username,
|
|
73
|
+
config.agent.title,
|
|
74
|
+
config.agent.hostpath,
|
|
75
|
+
config.agent.leadAgent,
|
|
76
|
+
]);
|
|
77
|
+
if (!insertedUser.lastID) {
|
|
78
|
+
throw "Error adding local user to llmail database";
|
|
79
|
+
}
|
|
80
|
+
_myUserId = insertedUser.lastID;
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
throw (`A user already exists in the database with the agent path (${config.agent.hostpath})\n` +
|
|
84
|
+
`Either create a new agent config file, or delete the ${config.naisysFolder} folder to reset the database.`);
|
|
76
85
|
}
|
|
77
|
-
_myUserId = insertedUser.lastID;
|
|
78
86
|
}
|
|
79
87
|
// Else already exists, validate it's config path is correct
|
|
80
88
|
else {
|
|
@@ -46,6 +46,7 @@ export async function handleCommand(args) {
|
|
|
46
46
|
if (!argParams[0]) {
|
|
47
47
|
argParams[0] = "help";
|
|
48
48
|
}
|
|
49
|
+
let errorText = "";
|
|
49
50
|
switch (argParams[0]) {
|
|
50
51
|
case "help": {
|
|
51
52
|
let helpOutput = `subagent <command>
|
|
@@ -75,6 +76,11 @@ export async function handleCommand(args) {
|
|
|
75
76
|
const newParams = argParams.slice(1).join(" ").split('"');
|
|
76
77
|
const title = newParams[1];
|
|
77
78
|
const task = newParams[3];
|
|
79
|
+
// Validate title and task set
|
|
80
|
+
if (!title || !task) {
|
|
81
|
+
errorText = "See valid 'create' syntax below:\n";
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
78
84
|
return await _createAgent(title, task);
|
|
79
85
|
}
|
|
80
86
|
case "start": {
|
|
@@ -90,10 +96,11 @@ export async function handleCommand(args) {
|
|
|
90
96
|
_debugFlushContext(subagentId);
|
|
91
97
|
return "";
|
|
92
98
|
}
|
|
93
|
-
default:
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
default: {
|
|
100
|
+
errorText = "Error, unknown command. See valid commands below:\n";
|
|
101
|
+
}
|
|
96
102
|
}
|
|
103
|
+
return errorText + (await handleCommand("help"));
|
|
97
104
|
}
|
|
98
105
|
export function getRunningSubagentNames() {
|
|
99
106
|
return _subagents
|
|
@@ -112,10 +119,6 @@ export function unreadContextSummary() {
|
|
|
112
119
|
.join(" | "));
|
|
113
120
|
}
|
|
114
121
|
async function _createAgent(title, taskDescription) {
|
|
115
|
-
// Validate title and task set
|
|
116
|
-
if (!title || !taskDescription) {
|
|
117
|
-
throw "Title and task description must be set";
|
|
118
|
-
}
|
|
119
122
|
// Get available username
|
|
120
123
|
const usernames = await llmail.getAllUserNames();
|
|
121
124
|
let agentName = "";
|
|
@@ -218,6 +221,6 @@ function _debugFlushContext(subagentId) {
|
|
|
218
221
|
subagent.log = "";
|
|
219
222
|
}
|
|
220
223
|
function _getSubagentDir() {
|
|
221
|
-
return new NaisysPath(`${config.naisysFolder}/
|
|
224
|
+
return new NaisysPath(`${config.naisysFolder}/agent-data/${config.agent.username}/subagents`);
|
|
222
225
|
}
|
|
223
226
|
//# sourceMappingURL=subagent.js.map
|
package/dist/llm/llModels.js
CHANGED
|
@@ -8,7 +8,7 @@ export var LlmApiType;
|
|
|
8
8
|
const llmModels = [
|
|
9
9
|
{
|
|
10
10
|
key: "gpt4turbo",
|
|
11
|
-
name: "gpt-4-
|
|
11
|
+
name: "gpt-4-turbo",
|
|
12
12
|
apiType: LlmApiType.OpenAI,
|
|
13
13
|
maxTokens: 128000,
|
|
14
14
|
// Prices are per 1M tokens
|
|
@@ -17,7 +17,7 @@ const llmModels = [
|
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
key: "gpt3turbo",
|
|
20
|
-
name: "gpt-3.5-turbo
|
|
20
|
+
name: "gpt-3.5-turbo",
|
|
21
21
|
apiType: LlmApiType.OpenAI,
|
|
22
22
|
maxTokens: 16000,
|
|
23
23
|
// Prices are per 1M tokens
|
|
@@ -49,8 +49,8 @@ const llmModels = [
|
|
|
49
49
|
apiType: LlmApiType.Google,
|
|
50
50
|
maxTokens: 30720,
|
|
51
51
|
// 60 queries per minute free then the prices below are per 1000 characters
|
|
52
|
-
inputCost: 0.
|
|
53
|
-
outputCost: 1.
|
|
52
|
+
inputCost: 0.5,
|
|
53
|
+
outputCost: 1.5,
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
56
|
key: "claude3opus",
|
package/dist/llm/llmService.js
CHANGED
|
@@ -57,8 +57,11 @@ async function sendWithOpenAiCompatible(modelKey, systemMessage, context, source
|
|
|
57
57
|
})),
|
|
58
58
|
],
|
|
59
59
|
});
|
|
60
|
+
if (!model.inputCost && !model.outputCost) {
|
|
61
|
+
// Don't cost models with no costs
|
|
62
|
+
}
|
|
60
63
|
// Total up costs, prices are per 1M tokens
|
|
61
|
-
if (chatResponse.usage) {
|
|
64
|
+
else if (chatResponse.usage) {
|
|
62
65
|
const cost = chatResponse.usage.prompt_tokens * model.inputCost +
|
|
63
66
|
chatResponse.usage.completion_tokens * model.outputCost;
|
|
64
67
|
await costTracker.recordCost(cost / 1000000, source, model.name);
|
|
@@ -118,7 +121,7 @@ async function sendWithGoogle(modelKey, systemMessage, context, source) {
|
|
|
118
121
|
throw `Google API Request Blocked, ${result.response.promptFeedback.blockReason}`;
|
|
119
122
|
}
|
|
120
123
|
const responseText = result.response.text();
|
|
121
|
-
//
|
|
124
|
+
// TODO: take into account google allows 60 queries per minute for free for 1.0, 2 queries/min for 1.5
|
|
122
125
|
// AFAIK Google API doesn't provide usage data, so we have to estimate it ourselves
|
|
123
126
|
const inputTokenCount = getTokenCount(systemMessage) +
|
|
124
127
|
context
|
|
@@ -18,6 +18,10 @@ let llmailCmd = "";
|
|
|
18
18
|
if (config.mailEnabled) {
|
|
19
19
|
llmailCmd = `\n llmail: A local mail system for communicating with your team`;
|
|
20
20
|
}
|
|
21
|
+
let llmynxCmd = "";
|
|
22
|
+
if (config.webEnabled) {
|
|
23
|
+
llmynxCmd = `\n llmynx: A context optimized web browser. Enter 'llmynx help' to learn how to use it`;
|
|
24
|
+
}
|
|
21
25
|
let workspaces = "";
|
|
22
26
|
if (config.workspacesEnabled) {
|
|
23
27
|
workspaces = `\nWorkspaces:`;
|
|
@@ -71,8 +75,7 @@ LINUX Commands:
|
|
|
71
75
|
vi and nano are not supported
|
|
72
76
|
Read files with cat. Write files with \`cat > filename << 'EOF'\`
|
|
73
77
|
Do not input notes after the prompt. Only valid commands.
|
|
74
|
-
NAISYS Commands: (cannot be used with other commands on the same prompt)${llmailCmd}${subagentNote}
|
|
75
|
-
llmynx: A context optimized web browser. Enter 'llmynx help' to learn how to use it${genImgCmd}
|
|
78
|
+
NAISYS Commands: (cannot be used with other commands on the same prompt)${llmailCmd}${subagentNote}${llmynxCmd}${genImgCmd}
|
|
76
79
|
comment "<thought>": Any non-command output like thinking out loud, prefix with the 'comment' command
|
|
77
80
|
pause <seconds>: Pause for <seconds>${trimSession}${endsession}
|
|
78
81
|
Tokens:
|
package/dist/utils/logService.js
CHANGED
|
@@ -91,7 +91,7 @@ export function roleToSource(role) {
|
|
|
91
91
|
}
|
|
92
92
|
/** Write entire context to a file in the users home directory */
|
|
93
93
|
export function recordContext(contextLog) {
|
|
94
|
-
const filePath = new NaisysPath(`${config.naisysFolder}/
|
|
94
|
+
const filePath = new NaisysPath(`${config.naisysFolder}/agent-data/${config.agent.username}/current-context.txt`);
|
|
95
95
|
pathService.ensureFileDirExists(filePath);
|
|
96
96
|
fs.writeFileSync(filePath.toHostPath(), contextLog);
|
|
97
97
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naisys",
|
|
3
3
|
"description": "Node.js Autonomous Intelligence System",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/naisys.js",
|
|
7
7
|
"preferGlobal": true,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"naisys": "bin/naisys"
|
|
10
10
|
},
|
|
11
11
|
"scripts": {
|
|
12
|
-
"compile/run/attachable": "tsc && node --inspect dist/naisys.js ./agents/
|
|
12
|
+
"compile/run/attachable": "tsc && node --inspect dist/naisys.js ./agents/netmap.yaml",
|
|
13
13
|
"agent:assistant": "node dist/naisys.js ./agents/assistant.yaml",
|
|
14
14
|
"agent:nightwatch": "node dist/naisys.js ./agents/nightwatch.yaml",
|
|
15
15
|
"clean": "rm -rf dist",
|
|
@@ -42,29 +42,32 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/escape-html": "1.0.4",
|
|
44
44
|
"@types/js-yaml": "4.0.9",
|
|
45
|
-
"@types/node": "20.12.
|
|
45
|
+
"@types/node": "20.12.7",
|
|
46
46
|
"@types/text-table": "0.2.5",
|
|
47
|
-
"@typescript-eslint/eslint-plugin": "7.
|
|
48
|
-
"@typescript-eslint/parser": "7.
|
|
49
|
-
"eslint": "8.
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "7.7.0",
|
|
48
|
+
"@typescript-eslint/parser": "7.7.0",
|
|
49
|
+
"eslint": "8.56.0",
|
|
50
50
|
"jest": "29.7.0",
|
|
51
51
|
"prettier": "3.2.5",
|
|
52
52
|
"ts-node": "10.9.2",
|
|
53
|
-
"typescript": "5.4.
|
|
53
|
+
"typescript": "5.4.5"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@anthropic-ai/sdk": "0.20.
|
|
57
|
-
"@google/generative-ai": "0.
|
|
56
|
+
"@anthropic-ai/sdk": "0.20.5",
|
|
57
|
+
"@google/generative-ai": "0.7.1",
|
|
58
|
+
"@xterm/headless": "5.5.0",
|
|
58
59
|
"chalk": "5.3.0",
|
|
59
60
|
"commander": "12.0.0",
|
|
60
61
|
"dotenv": "16.4.5",
|
|
61
62
|
"escape-html": "1.0.3",
|
|
62
63
|
"js-yaml": "4.1.0",
|
|
63
|
-
"openai": "4.
|
|
64
|
+
"openai": "4.36.0",
|
|
64
65
|
"sharp": "0.33.3",
|
|
65
66
|
"sqlite": "5.1.1",
|
|
66
67
|
"sqlite3": "5.1.7",
|
|
68
|
+
"strip-ansi": "7.1.0",
|
|
67
69
|
"text-table": "0.2.0",
|
|
68
|
-
"tiktoken": "1.0.
|
|
70
|
+
"tiktoken": "1.0.14",
|
|
71
|
+
"tree-kill": "1.2.2"
|
|
69
72
|
}
|
|
70
73
|
}
|