naisys 1.3.1 → 1.5.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 CHANGED
@@ -73,7 +73,11 @@ dreamModel: claude3opus
73
73
 
74
74
  # The model to use for llmynx, pre-processing websites to fit into a smaller context (use a cheaper model)
75
75
  # defaults to the shellModel if omitted
76
- webModel: gemini-pro
76
+ webModel: claude3haiku
77
+
78
+ # The model used by the 'genimg' command. If not defined then the genimg command is not available to the LLM
79
+ # Valid values: dalle2-256, dalle2-512, dalle2-1024, dalle3-1024, dalle3-1024-HD
80
+ imageModel: dalle3-1024
77
81
 
78
82
  # A system like prompt explaining the agent's role and responsibilities
79
83
  # You can use config variables in this string
@@ -108,6 +112,11 @@ spendLimitDollars: 2.00
108
112
  # Auto: All commands are run through the separate LLM instace that will check to see if the command is safe
109
113
  commandProtection: "none"
110
114
 
115
+ # The max number of subagents allowed to be started and managed. Leave out to disable.
116
+ # Costs by the subagent are applied to the host agent's spend limit
117
+ # Careful: Sub-agents can be chatty, slowing down progress.
118
+ subagentMax: 0
119
+
111
120
  # Run these commands on session start, in the example below the agent will see how to use mail and a list of other agents
112
121
  initialCommands:
113
122
  - llmail users
@@ -161,10 +170,12 @@ initialCommands:
161
170
  - `comment "<note>"` - The LLM is directed to use this for 'thinking out loud' which avoids 'invalid command' errors
162
171
  - `endsession "<note>"` - Clear the context and start a new session.
163
172
  - The LLM is directed to track it's context size and to end the session with a note before running over the context limit
164
- - `pause <seconds>` - Can be used by the debug agent or the LLM to pause execution indefinitely, or until a new message is received from another agent, or for a set number of seconds
173
+ - `pause <seconds>` - Can be used by the debug agent or the LLM to pause execution for a set number of seconds
165
174
  - NAISYS apps
166
175
  - `llmail` - A context friendly 'mail system' used for agent to agent communication
167
176
  - `llmynx` - A context friendly wrapping on the lynx browser that can use a separate LLM to reduce the size of a large webpage into something that can fit into the LLM's context
177
+ - `genimg "<description>" <filepath>` - Generates an image with the given description, save at the specified fully qualified path
178
+ - `subagent` - A way for LLMs to start/stop their own sub-agents. Communicating with each other with `llmail`. Set the `subagentMax` in the agent config to enable.
168
179
 
169
180
  ## Running NAISYS from Source
170
181
 
@@ -185,6 +196,18 @@ initialCommands:
185
196
  - Install WSL (Windows Subsystem for Linux)
186
197
  - The `NAISYS_FOLDER` and `WEBSITE_FOLDER` should be set to the WSL path
187
198
  - So `C:\var\naisys` should be `/mnt/c/var/naisys` in the `.env` file
188
- - If you want to use NAISYS for a website
189
- - Install a local web server, for example [XAMPP](https://www.apachefriends.org/) on Windows
190
- - Start the server and put the URL in the `.env` file
199
+
200
+ #### Using NAISYS for a website
201
+
202
+ - Many frameworks come with their own dev server
203
+ - PHP for example can start a server with `php -S localhost:8000 -d display_errors=On -d error_reporting=E_ALL`
204
+ - Start the server and put the URL in the `.env` file
205
+
206
+ ## Changelog
207
+
208
+ - 1.5: Allow agents to start their own parallel `subagents`
209
+ - 1.4: `genimg` command for generating images
210
+ - 1.3: Post-session 'dreaming' as well as a mail 'blackout' period
211
+ - 1.2: Created stand-in shell commands for custom Naisys commands
212
+ - 1.1: Added command protection settings to prevent unwanted writes
213
+ - 1.0: Initial release
package/bin/genimg ADDED
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ echo "'genimg' cannot be used with other commands on the same prompt."
package/bin/naisys CHANGED
@@ -1,19 +1,22 @@
1
1
  #!/bin/bash
2
2
 
3
- # Make sure to enable this script for execution with `chmod +x runteam.sh`
3
+ # Make sure to enable this script for execution with `chmod +x naisys`
4
+
5
+ # Resolves the location of naisys from the bin directory
6
+ SCRIPT=$(readlink -f "$0" || echo "$0")
7
+ SCRIPT_DIR=$(dirname "$SCRIPT")/..
4
8
 
5
9
  # Check if an argument is provided
6
10
  if [ $# -eq 0 ]; then
11
+ # get version from package.json
12
+ VERSION=$(node -e "console.log(require('${SCRIPT_DIR}/package.json').version)")
7
13
  echo "NAISYS: Node.js Autonomous Intelligence System"
14
+ echo " Version: $VERSION"
8
15
  echo " Usage: naisys <path to agent config yaml, or directory>"
9
16
  echo " Note: If a folder is passed then all agents will be started in a tmux session"
10
17
  exit 1
11
18
  fi
12
19
 
13
- # Resolves the location of naisys from the bin directory
14
- SCRIPT=$(readlink -f "$0" || echo "$0")
15
- SCRIPT_DIR=$(dirname "$SCRIPT")/..
16
-
17
20
  # if path is a yaml file then start a single agent
18
21
  if [ -f "$1" ]; then
19
22
  if [[ "$1" == *".yaml" ]]; then
@@ -0,0 +1,3 @@
1
+ #!/bin/bash
2
+
3
+ echo "'trimsession' cannot be used with other commands on the same prompt."
@@ -1,11 +1,13 @@
1
1
  import chalk from "chalk";
2
- import * as llmail from "../apps/llmail.js";
3
- import * as llmynx from "../apps/llmynx.js";
4
2
  import * as config from "../config.js";
3
+ import * as genimg from "../features/genimg.js";
4
+ import * as llmail from "../features/llmail.js";
5
+ import * as llmynx from "../features/llmynx.js";
6
+ import * as subagent from "../features/subagent.js";
5
7
  import * as contextManager from "../llm/contextManager.js";
6
- import { ContentSource } from "../llm/contextManager.js";
7
8
  import * as costTracker from "../llm/costTracker.js";
8
9
  import * as dreamMaker from "../llm/dreamMaker.js";
10
+ import { ContentSource } from "../llm/llmDtos.js";
9
11
  import * as inputMode from "../utils/inputMode.js";
10
12
  import { InputMode } from "../utils/inputMode.js";
11
13
  import * as output from "../utils/output.js";
@@ -64,7 +66,18 @@ export async function processCommand(prompt, consoleInput) {
64
66
  await contextManager.append("Comment noted. Try running commands now to achieve your goal.");
65
67
  break;
66
68
  }
69
+ case "trimsession": {
70
+ if (!config.trimSessionEnabled) {
71
+ throw 'The "trimsession" command is not enabled in this environment.';
72
+ }
73
+ const trimSummary = contextManager.trim(cmdArgs);
74
+ await contextManager.append(trimSummary);
75
+ break;
76
+ }
67
77
  case "endsession": {
78
+ if (!config.endSessionEnabled) {
79
+ throw 'The "trimsession" command is not enabled in this environment.';
80
+ }
68
81
  // Don't need to check end line as this is the last command in the context, just read to the end
69
82
  const endSessionNotes = utilities.trimChars(cmdArgs, '"');
70
83
  if (!endSessionNotes) {
@@ -104,8 +117,7 @@ export async function processCommand(prompt, consoleInput) {
104
117
  };
105
118
  }
106
119
  case "cost": {
107
- const totalCost = await costTracker.getTotalCosts();
108
- output.comment(`Total cost so far $${totalCost.toFixed(2)} of $${config.agent.spendLimitDollars} limit`);
120
+ await costTracker.printCosts();
109
121
  break;
110
122
  }
111
123
  case "llmynx": {
@@ -125,16 +137,24 @@ export async function processCommand(prompt, consoleInput) {
125
137
  }
126
138
  break;
127
139
  }
140
+ case "genimg": {
141
+ const genimgResponse = await genimg.handleCommand(cmdArgs);
142
+ await contextManager.append(genimgResponse);
143
+ break;
144
+ }
128
145
  case "context":
129
- contextManager.printContext();
146
+ output.comment("#####################");
147
+ output.comment(contextManager.printContext());
148
+ output.comment("#####################");
149
+ break;
150
+ case "subagent": {
151
+ const subagentResponse = await subagent.handleCommand(cmdArgs);
152
+ await contextManager.append(subagentResponse);
130
153
  break;
154
+ }
131
155
  default: {
132
- const shellResponse = await shellCommand.handleCommand(input);
133
- if (shellResponse.hasErrors && nextInput) {
134
- await output.errorAndLog(`Error detected processing shell command:`);
135
- processNextLLMpromptBlock = false;
136
- }
137
- nextCommandAction = shellResponse.terminate
156
+ const exitApp = await shellCommand.handleCommand(input);
157
+ nextCommandAction = exitApp
138
158
  ? NextCommandAction.ExitApplication
139
159
  : NextCommandAction.Continue;
140
160
  }
@@ -199,7 +219,11 @@ async function splitMultipleInputCommands(nextInput) {
199
219
  }
200
220
  }
201
221
  // If the LLM forgets the quote on the comment, treat it as a single line comment
202
- else if (newLinePos > 0 && nextInput.startsWith("comment ")) {
222
+ // Not something we want to use for multi-line commands like llmail and subagent
223
+ else if (newLinePos > 0 &&
224
+ (nextInput.startsWith("comment ") ||
225
+ nextInput.startsWith("genimg ") ||
226
+ nextInput.startsWith("trimsession "))) {
203
227
  input = nextInput.slice(0, newLinePos);
204
228
  nextInput = nextInput.slice(newLinePos).trim();
205
229
  }
@@ -1,17 +1,20 @@
1
1
  import chalk from "chalk";
2
2
  import * as readline from "readline";
3
- import * as llmail from "../apps/llmail.js";
4
- import * as llmynx from "../apps/llmynx.js";
5
3
  import * as config from "../config.js";
4
+ import * as llmail from "../features/llmail.js";
5
+ import * as llmynx from "../features/llmynx.js";
6
+ import * as subagent from "../features/subagent.js";
7
+ import * as workspaces from "../features/workspaces.js";
6
8
  import * as contextManager from "../llm/contextManager.js";
7
- import { ContentSource } from "../llm/contextManager.js";
8
9
  import * as dreamMaker from "../llm/dreamMaker.js";
9
- import { LlmRole } from "../llm/llmDtos.js";
10
+ import { ContentSource, LlmRole } from "../llm/llmDtos.js";
10
11
  import * as llmService from "../llm/llmService.js";
12
+ import { systemMessage } from "../llm/systemMessage.js";
11
13
  import * as inputMode from "../utils/inputMode.js";
12
14
  import { InputMode } from "../utils/inputMode.js";
13
15
  import * as logService from "../utils/logService.js";
14
16
  import * as output from "../utils/output.js";
17
+ import { OutputColor } from "../utils/output.js";
15
18
  import * as utilities from "../utils/utilities.js";
16
19
  import * as commandHandler from "./commandHandler.js";
17
20
  import { NextCommandAction } from "./commandHandler.js";
@@ -22,7 +25,6 @@ export async function run() {
22
25
  await output.commentAndLog(`Agent configured to use ${config.agent.shellModel} model`);
23
26
  // Show System Message
24
27
  await output.commentAndLog("System Message:");
25
- const systemMessage = contextManager.getSystemMessage();
26
28
  output.write(systemMessage);
27
29
  await logService.write({
28
30
  role: LlmRole.System,
@@ -31,6 +33,7 @@ export async function run() {
31
33
  });
32
34
  let nextCommandAction = NextCommandAction.Continue;
33
35
  let llmErrorCount = 0;
36
+ let nextPromptIndex = 0;
34
37
  while (nextCommandAction != NextCommandAction.ExitApplication) {
35
38
  inputMode.toggle(InputMode.LLM);
36
39
  await output.commentAndLog("Starting Context:");
@@ -40,30 +43,34 @@ export async function run() {
40
43
  await contextManager.append(latestDream);
41
44
  }
42
45
  for (const initialCommand of config.agent.initialCommands) {
43
- const prompt = await promptBuilder.getPrompt(0, false);
44
- await contextManager.append(prompt, ContentSource.ConsolePrompt);
46
+ let prompt = await promptBuilder.getPrompt(0, false);
47
+ prompt = setPromptIndex(prompt, ++nextPromptIndex);
48
+ await contextManager.append(prompt, ContentSource.ConsolePrompt, nextPromptIndex);
45
49
  await commandHandler.processCommand(prompt, config.resolveConfigVars(initialCommand));
46
50
  }
47
51
  inputMode.toggle(InputMode.Debug);
48
52
  let pauseSeconds = config.agent.debugPauseSeconds;
49
53
  let wakeOnMessage = config.agent.wakeOnMessage;
50
54
  while (nextCommandAction == NextCommandAction.Continue) {
51
- const prompt = await promptBuilder.getPrompt(pauseSeconds, wakeOnMessage);
55
+ let prompt = await promptBuilder.getPrompt(pauseSeconds, wakeOnMessage);
52
56
  let consoleInput = "";
53
57
  // Debug command prompt
54
58
  if (inputMode.current === InputMode.Debug) {
59
+ subagent.unreadContextSummary();
55
60
  consoleInput = await promptBuilder.getInput(`${prompt}`, pauseSeconds, wakeOnMessage);
56
61
  }
57
62
  // LLM command prompt
58
63
  else if (inputMode.current === InputMode.LLM) {
64
+ prompt = setPromptIndex(prompt, ++nextPromptIndex);
59
65
  const workingMsg = prompt +
60
- chalk[output.OutputColor.loading](`LLM (${config.agent.shellModel}) Working...`);
66
+ chalk[OutputColor.loading](`LLM (${config.agent.shellModel}) Working...`);
61
67
  try {
62
68
  await checkNewMailNotification();
63
69
  await checkContextLimitWarning();
64
- await contextManager.append(prompt, ContentSource.ConsolePrompt);
70
+ await workspaces.displayActive();
71
+ await contextManager.append(prompt, ContentSource.ConsolePrompt, nextPromptIndex);
65
72
  process.stdout.write(workingMsg);
66
- consoleInput = await llmService.query(config.agent.shellModel, contextManager.getSystemMessage(), contextManager.messages, "console");
73
+ consoleInput = await llmService.query(config.agent.shellModel, systemMessage, contextManager.getCombinedMessages(), "console");
67
74
  clearPromptMessage(workingMsg);
68
75
  }
69
76
  catch (e) {
@@ -101,6 +108,7 @@ export async function run() {
101
108
  llmynx.clear();
102
109
  contextManager.clear();
103
110
  nextCommandAction = NextCommandAction.Continue;
111
+ nextPromptIndex = 0;
104
112
  }
105
113
  }
106
114
  }
@@ -178,7 +186,7 @@ async function checkNewMailNotification() {
178
186
  for (const unreadThread of unreadThreads) {
179
187
  await llmail.markAsRead(unreadThread.threadId);
180
188
  }
181
- mailBlackoutCountdown = config.mailBlackoutCycles;
189
+ mailBlackoutCountdown = config.agent.mailBlackoutCycles || 0;
182
190
  }
183
191
  else if (llmail.simpleMode) {
184
192
  await contextManager.append(`You have new mail, but not enough context to read them.\n` +
@@ -196,11 +204,34 @@ async function checkContextLimitWarning() {
196
204
  const tokenCount = contextManager.getTokenCount();
197
205
  const tokenMax = config.agent.tokenMax;
198
206
  if (tokenCount > tokenMax) {
199
- await contextManager.append(`The token limit for this session has been exceeded.
200
- Use \`endsession <note>\` to clear the console and reset the session.
207
+ let tokenNote = "";
208
+ if (config.endSessionEnabled) {
209
+ tokenNote += `\nUse 'endsession <note>' to clear the console and reset the session.
201
210
  The note should help you find your bearings in the next session.
202
- The note should contain your next goal, and important things should you remember.
203
- Try to keep the note around 400 tokens.`, ContentSource.Console);
211
+ The note should contain your next goal, and important things should you remember.`;
212
+ }
213
+ if (config.trimSessionEnabled) {
214
+ tokenNote += `\nUse 'trimsession' to reduce the size of the session.
215
+ Use comments to remember important things from trimmed prompts.`;
216
+ }
217
+ await contextManager.append(`The token limit for this session has been exceeded.${tokenNote}`, ContentSource.Console);
218
+ }
219
+ }
220
+ /** Insert prompt index [Index: 1] before the $.
221
+ * Insert at the end of the prompt so that 'prompt splitting' still works in the command handler
222
+ */
223
+ function setPromptIndex(prompt, index) {
224
+ if (!config.trimSessionEnabled) {
225
+ return prompt;
226
+ }
227
+ let newPrompt = prompt;
228
+ const endPromptPos = prompt.lastIndexOf("$");
229
+ if (endPromptPos != -1) {
230
+ newPrompt =
231
+ prompt.slice(0, endPromptPos) +
232
+ ` [Index: ${index}]` +
233
+ prompt.slice(endPromptPos);
204
234
  }
235
+ return newPrompt;
205
236
  }
206
237
  //# sourceMappingURL=commandLoop.js.map
@@ -1,8 +1,8 @@
1
1
  import chalk from "chalk";
2
2
  import * as events from "events";
3
3
  import * as readline from "readline";
4
- import * as llmail from "../apps/llmail.js";
5
4
  import * as config from "../config.js";
5
+ import * as llmail from "../features/llmail.js";
6
6
  import * as contextManager from "../llm/contextManager.js";
7
7
  import * as inputMode from "../utils/inputMode.js";
8
8
  import { InputMode } from "../utils/inputMode.js";
@@ -17,6 +17,7 @@ const _outputEmitter = new events.EventEmitter();
17
17
  const _originalWrite = process.stdout.write.bind(process.stdout);
18
18
  process.stdout.write = (...args) => {
19
19
  _outputEmitter.emit(_writeEventName, false, ...args);
20
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
21
  return _originalWrite.apply(process.stdout, args);
21
22
  };
22
23
  const _readlineInterface = readline.createInterface({
@@ -6,49 +6,45 @@ import * as utilities from "../utils/utilities.js";
6
6
  import * as shellWrapper from "./shellWrapper.js";
7
7
  export async function handleCommand(input) {
8
8
  const cmdParams = input.split(" ");
9
- const response = {
10
- hasErrors: true,
11
- };
12
9
  // Route user to context friendly edit commands that can read/write the entire file in one go
13
10
  // Having EOF in quotes is important as it prevents the shell from replacing $variables with bash values
14
11
  if (["nano", "vi", "vim"].includes(cmdParams[0])) {
15
- await contextManager.append(`${cmdParams[0]} not supported. Use \`cat\` to read a file and \`cat > filename << 'EOF'\` to write a file`);
16
- return response;
12
+ throw `${cmdParams[0]} not supported. Use \`cat\` to read a file and \`cat > filename << 'EOF'\` to write a file`;
17
13
  }
18
14
  if (cmdParams[0] == "lynx" && cmdParams[1] != "--dump") {
19
- await contextManager.append(`Interactive mode with lynx is not supported. Use --dump with lynx to view a website`);
20
- return response;
15
+ throw `Interactive mode with lynx is not supported. Use --dump with lynx to view a website`;
21
16
  }
22
17
  if (cmdParams[0] == "exit") {
23
18
  if (inputMode.current == InputMode.LLM) {
24
- await contextManager.append("Use 'endsession' to end the session and clear the console log.");
19
+ throw "Use 'endsession' to end the session and clear the console log.";
25
20
  }
21
+ // Only the debug user is allowed to exit the shell
26
22
  else if (inputMode.current == InputMode.Debug) {
27
23
  await shellWrapper.terminate();
28
- response.terminate = true;
24
+ return true;
29
25
  }
30
- return response;
31
26
  }
32
- const output = await shellWrapper.executeCommand(input);
33
- if (output.value) {
34
- let text = output.value;
35
- let outputLimitExceeded = false;
36
- const tokenCount = utilities.getTokenCount(text);
37
- // Prevent too much output from blowing up the context
38
- if (tokenCount > config.shellOutputTokenMax) {
39
- outputLimitExceeded = true;
40
- const trimLength = (text.length * config.shellOutputTokenMax) / tokenCount;
41
- text =
42
- text.slice(0, trimLength / 2) +
43
- "\n\n...\n\n" +
44
- text.slice(-trimLength / 2);
45
- }
46
- await contextManager.append(text);
47
- if (outputLimitExceeded) {
48
- await contextManager.append(`\nThe shell command generated too much output (${tokenCount} tokens). Only 2,000 tokens worth are shown above.`);
49
- }
27
+ let response = await shellWrapper.executeCommand(input);
28
+ let outputLimitExceeded = false;
29
+ const tokenCount = utilities.getTokenCount(response);
30
+ // Prevent too much output from blowing up the context
31
+ if (tokenCount > config.shellCommand.outputTokenMax) {
32
+ outputLimitExceeded = true;
33
+ const trimLength = (response.length * config.shellCommand.outputTokenMax) / tokenCount;
34
+ response =
35
+ response.slice(0, trimLength / 2) +
36
+ "\n\n...\n\n" +
37
+ response.slice(-trimLength / 2);
38
+ }
39
+ if (outputLimitExceeded) {
40
+ response += `\nThe shell command generated too much output (${tokenCount} tokens). Only 2,000 tokens worth are shown above.`;
41
+ }
42
+ if (response.endsWith(": command not found")) {
43
+ response +=
44
+ "Please enter a valid Linux or NAISYS command after the prompt. Use the 'comment' command for thoughts.";
50
45
  }
51
- response.hasErrors = output.hasErrors;
52
- return response;
46
+ // todo move this into the command handler to remove the context manager dependency
47
+ await contextManager.append(response);
48
+ return false;
53
49
  }
54
50
  //# sourceMappingURL=shellCommand.js.map