naisys 1.0.1
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/.env.example +15 -0
- package/LICENSE.md +7 -0
- package/README.md +125 -0
- package/agents/example.yaml +34 -0
- package/dist/__tests__/utils/output.test.js +22 -0
- package/dist/__tests__/utils/output.test.js.map +1 -0
- package/dist/__tests__/utils/utilities.test.js +42 -0
- package/dist/__tests__/utils/utilities.test.js.map +1 -0
- package/dist/apps/llmail.js +350 -0
- package/dist/apps/llmail.js.map +1 -0
- package/dist/apps/llmynx.js +229 -0
- package/dist/apps/llmynx.js.map +1 -0
- package/dist/command/commandHandler.js +151 -0
- package/dist/command/commandHandler.js.map +1 -0
- package/dist/command/commandLoop.js +176 -0
- package/dist/command/commandLoop.js.map +1 -0
- package/dist/command/promptBuilder.js +133 -0
- package/dist/command/promptBuilder.js.map +1 -0
- package/dist/command/shellCommand.js +36 -0
- package/dist/command/shellCommand.js.map +1 -0
- package/dist/command/shellWrapper.js +179 -0
- package/dist/command/shellWrapper.js.map +1 -0
- package/dist/config.js +46 -0
- package/dist/config.js.map +1 -0
- package/dist/llm/contextManager.js +127 -0
- package/dist/llm/contextManager.js.map +1 -0
- package/dist/llm/costTracker.js +43 -0
- package/dist/llm/costTracker.js.map +1 -0
- package/dist/llm/llModels.js +52 -0
- package/dist/llm/llModels.js.map +1 -0
- package/dist/llm/llmDtos.js +8 -0
- package/dist/llm/llmDtos.js.map +1 -0
- package/dist/llm/llmService.js +113 -0
- package/dist/llm/llmService.js.map +1 -0
- package/dist/naisys.js +7 -0
- package/dist/naisys.js.map +1 -0
- package/dist/utils/dbUtils.js +37 -0
- package/dist/utils/dbUtils.js.map +1 -0
- package/dist/utils/inputMode.js +18 -0
- package/dist/utils/inputMode.js.map +1 -0
- package/dist/utils/logService.js +116 -0
- package/dist/utils/logService.js.map +1 -0
- package/dist/utils/output.js +38 -0
- package/dist/utils/output.js.map +1 -0
- package/dist/utils/utilities.js +40 -0
- package/dist/utils/utilities.js.map +1 -0
- package/naisys.sh +56 -0
- package/package.json +70 -0
package/.env.example
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Agent home files and NAISYS specific databases will be stored here
|
|
2
|
+
NAISYS_FOLDER="/var/naisys"
|
|
3
|
+
|
|
4
|
+
# The folder where the website and logs are served from (if not defined then logs put in the naisys folder)
|
|
5
|
+
WEBSITE_FOLDER="/var/www"
|
|
6
|
+
|
|
7
|
+
# Leave api keys/url blank if not using the service
|
|
8
|
+
OPENAI_API_KEY="xxx"
|
|
9
|
+
GOOGLE_API_KEY="yyy"
|
|
10
|
+
|
|
11
|
+
LOCAL_LLM_URL="http://localhost:1234/v1"
|
|
12
|
+
LOCAL_LLM_NAME="minstral instruct v0.2"
|
|
13
|
+
|
|
14
|
+
# Custom global vars for use in agent configurations here
|
|
15
|
+
WEBSITE_URL="http://localhost:8080/"
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2024 John Marshall
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
## NAISYS (Node.js Autonomous Intelligence System)
|
|
2
|
+
|
|
3
|
+
NAISYS is acts as a proxy shell between a LLM and a real shell. The goal is to see how far a LLM can
|
|
4
|
+
get into writing a website from scratch as well as work with other LLM agents on the same project. Trying to figure
|
|
5
|
+
out what works and what doesn't when it comes to 'cognitive architectures'. NAISYS isn't
|
|
6
|
+
limited to websites, but it seemed like a good place to start.
|
|
7
|
+
|
|
8
|
+
Since the LLM has a limited context, NAISYS should take this into account and help the LLM
|
|
9
|
+
perform 'context friendly' operations. For example reading/writing a file can't use a typical editor like
|
|
10
|
+
vim or nano so point the LLM to use cat to read/write files in a single operation.
|
|
11
|
+
|
|
12
|
+
#### Node.js is used to create a simple proxy shell environment for the LLM that
|
|
13
|
+
|
|
14
|
+
- NAISYS helps the LLM keep track of its current context size
|
|
15
|
+
- Gives the LLM the ability to 'reset' the context and carry over information to a new session/context
|
|
16
|
+
- Proxy commands to a real shell, and help guide the LLM to use context friendly commands
|
|
17
|
+
- Prevent the context from being polluted by catching common errors like output that includes the command prompt itself
|
|
18
|
+
- Allows communication with the LLM by way of a 'debug' prompt after each run of the LLM
|
|
19
|
+
- A custom 'mail' system for context friendly inter-agent communication
|
|
20
|
+
- A 'lynx' browser wrapper called 'llmynx' that uses a separate LLM to reduce the size of web pages to fit in the context
|
|
21
|
+
- Cost tracking built in, and cost limits must be set in the config to run NAISYS
|
|
22
|
+
- Supports multiple LLM backends, configurable per agent - Google, OpenAI, and self-hosted LLMs
|
|
23
|
+
|
|
24
|
+
## Resources
|
|
25
|
+
|
|
26
|
+
- [Website](https://naisys.org)
|
|
27
|
+
- [Discord](https://discord.gg/JBUPWSbaEt)
|
|
28
|
+
- [NPM Package](https://www.npmjs.com/package/naisys)
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
#### Getting started locally
|
|
33
|
+
|
|
34
|
+
- Install Node.js, NAISYS has been tested with version 20
|
|
35
|
+
- Clone this repository
|
|
36
|
+
- Run `npm install` to install dependencies
|
|
37
|
+
- Create a `.env` from the `.env.example` file, and configure
|
|
38
|
+
- Run `npm run compile`
|
|
39
|
+
- Configure your agent using the examples in the `./agents` folder
|
|
40
|
+
- Run `node dist/naisys.js <path to agent yaml file>`
|
|
41
|
+
|
|
42
|
+
#### Notes for Windows users
|
|
43
|
+
|
|
44
|
+
- Install WSL (Windows Subsystem for Linux)
|
|
45
|
+
- The naisys/website folder should be set to the WSL path
|
|
46
|
+
- So `C:\var\naisys` should be `/mnt/c/var/naisys` in the `.env` file
|
|
47
|
+
- If you want to use NAISYS for a website
|
|
48
|
+
- Install a local web server, for example [XAMPP](https://www.apachefriends.org/) on Windows
|
|
49
|
+
- Start the server and put the URL in the `.env` file
|
|
50
|
+
|
|
51
|
+
#### Getting started on a VM (Digital Ocean for example)
|
|
52
|
+
|
|
53
|
+
- Create new VM using the [LAMP stack droplet template](https://marketplace.digitalocean.com/apps/lamp)
|
|
54
|
+
- Login to the droplet using the web console
|
|
55
|
+
- Clone this repo using the [instructions from GitHub](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent)
|
|
56
|
+
- Run `apt install lynx`
|
|
57
|
+
- Run `apt install npm`
|
|
58
|
+
- Install `nvm` using the `curl` url from these [instructions](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating)
|
|
59
|
+
- Run `nvm install/use 20` to set node version to 20
|
|
60
|
+
- Create a `.env` from the `.env.example` file
|
|
61
|
+
- Set the api keys for the LLMs you need to access
|
|
62
|
+
- Set `NAISYS_FOLDER` to `/var/naisys`
|
|
63
|
+
- If you plan to use NAISYS for a website
|
|
64
|
+
- Set `WEBSITE_FOLDER` to `/var/www/html`
|
|
65
|
+
- Set `WEBSITE_URL` to the `http://<IP address of the droplet>`
|
|
66
|
+
- Follow the instructions for getting started locally above
|
|
67
|
+
|
|
68
|
+
#### Getting started from NPM (Still in development)
|
|
69
|
+
|
|
70
|
+
- Run `npm install -g naisys`
|
|
71
|
+
- Create a `.env` from the `.env.example` file, and configure
|
|
72
|
+
- Run `naisys <path to directory or yaml>`
|
|
73
|
+
- If a directory is passed, naisys will start a tmux session with the agents in the directory
|
|
74
|
+
- If a yaml file is passed, naisys will start a single agent
|
|
75
|
+
|
|
76
|
+
## Using NAISYS
|
|
77
|
+
|
|
78
|
+
#### The Basics
|
|
79
|
+
|
|
80
|
+
- NAISYS will start with a debug prompt, this is where you can use and run commands in NAISYS just like the LLM will
|
|
81
|
+
- If you hit `Enter` without typing anything, the LLM will run against the prompt
|
|
82
|
+
- Afterwards NAISYS will return to the debug prompt
|
|
83
|
+
- Depending on how the agents' `debugPauseSeconds` is configured NAISYS will
|
|
84
|
+
- Pause on the debug prompt for that many seconds
|
|
85
|
+
- Pause indefinitely
|
|
86
|
+
- Pause until a new message is received from another agent
|
|
87
|
+
- Combined logs across all agents are written to the `{WEBSITE_FOLDER}/logs` folder as html
|
|
88
|
+
|
|
89
|
+
#### Console Colors Legend
|
|
90
|
+
|
|
91
|
+
- Purple: Response from LLM, added to context
|
|
92
|
+
- White: Generated locally or from a real shell, added to context
|
|
93
|
+
- Green: Debug prompt and debug command reponses. Not added to context. Used for diagnostics between calls to LLM
|
|
94
|
+
- Red: High level NAISYS errors, not added to the context
|
|
95
|
+
|
|
96
|
+
#### Commands
|
|
97
|
+
|
|
98
|
+
- NAISYS tries to be light, acting as a helpful proxy between the LLM and a real shell, most commands should pass right though to the shell
|
|
99
|
+
- Debug Commands
|
|
100
|
+
- `cost` - Prints the current total LLM cost
|
|
101
|
+
- `context` - Prints the current context
|
|
102
|
+
- `exit` - Exits NAISYS. If the LLM tries to use `exit`, it is directed to use `endsession` instead
|
|
103
|
+
- `talk` - Communicate with the local agent to give hints or ask questions (the agent itself does not know about talk and is directed to use `comment` or `llmail` for communication)
|
|
104
|
+
- Special Commands usable by the LLM as well as by the debug prompt
|
|
105
|
+
- `comment <notes>` - The LLM is directed to use this for 'thinking out loud' which avoid 'invalid command' errors
|
|
106
|
+
- `endsession <notes>` - Clear the context and start a new session.
|
|
107
|
+
- The LLM is directed to track it's context size and to end the session with a note before running over the context limit
|
|
108
|
+
- `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
|
|
109
|
+
- NAISYS apps
|
|
110
|
+
- `llmail` - A context friendly 'mail system' used for agent to agent communication
|
|
111
|
+
- `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
|
|
112
|
+
|
|
113
|
+
## Code Design Notes
|
|
114
|
+
|
|
115
|
+
- The entry point is in `./naisys.ts`
|
|
116
|
+
- LLM configurations are in the `src/llm/llmModels.ts` file
|
|
117
|
+
- A helpful `dependency-graph.png` is included to get an idea of the overall architecture
|
|
118
|
+
- This also doubles as a way to prevent cyclic dependencies as a DI library is not used currently
|
|
119
|
+
- The code is organzied into module based services
|
|
120
|
+
- Think poor mans singleton dependency injection
|
|
121
|
+
- A previous version had class based services using real DI, but made the code a soup of `this.` statements
|
|
122
|
+
- Code from these services are imported with \* so it's clear when you're calling out to a service like llmService.send()
|
|
123
|
+
- There is a command loop that first checks for internally handled NAISYS commands, unhandled commands fall through to an actual shell
|
|
124
|
+
- Multiline commands are added to a temporary shell script and then executed so it's easier to pinpoint where a command failed by line number in the script versus the entire shell log
|
|
125
|
+
- Various sqlite databases are used for logging, cost tracking and mail. All stored in the `{NAISYS_FOLDER}/lib` folder
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Used to identify the agent on the prompt, logs, home dir, mail, etc..
|
|
2
|
+
username: jill
|
|
3
|
+
|
|
4
|
+
# How other agents will understand the role of this agent
|
|
5
|
+
title: Software Engineer
|
|
6
|
+
|
|
7
|
+
# The model to use for console interactions
|
|
8
|
+
consoleModel: gpt4turbo
|
|
9
|
+
|
|
10
|
+
# The model to use for llmynx, pre-processing websites to fit into a smaller context
|
|
11
|
+
webModel: gpt3turbo
|
|
12
|
+
|
|
13
|
+
# A system like prompt explaining the agent's role and responsibilities
|
|
14
|
+
# You can use config variables in this string
|
|
15
|
+
agentPrompt: |
|
|
16
|
+
You are ${agent.username} a ${agent.title} with the job of creating website on the latest Winter weather conditions across the USA.
|
|
17
|
+
The website should be very simple html, able to be used from a text based browser like lynx. Pages should be relatively short.
|
|
18
|
+
The location of the website files should be in ${env.WEBSITE_FOLDER}
|
|
19
|
+
The website can be tested with 'llmynx open ${env.WEBSITE_URL}' to see how it looks in a text based browser.
|
|
20
|
+
You can use PHP as a way to share layout across pages and reduce duplication.
|
|
21
|
+
Careful when creating new files that what you are creating is not already there.
|
|
22
|
+
|
|
23
|
+
# The number of seconds to pause after each console interaction for debugging and rate limiting
|
|
24
|
+
# No value or zero means wait indefinitely (debug driven)
|
|
25
|
+
debugPauseSeconds: 5
|
|
26
|
+
|
|
27
|
+
# If true, regardless of the debugPauseSeconds, the agent will not wake up on messages
|
|
28
|
+
# With lots of agents this could be costly if they all end up mailing/replying each other in quick succession
|
|
29
|
+
wakeOnMessage: false
|
|
30
|
+
|
|
31
|
+
# The maximum amount to spend on LLM interactions
|
|
32
|
+
# Once reached the agent will stop and this value will need to be increased to continue
|
|
33
|
+
spendLimitDollars: 2.00
|
|
34
|
+
# Additional custom variables can be defined here and/or in the .env file to be loaded into the agent prompt
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it, jest } from "@jest/globals";
|
|
2
|
+
jest.unstable_mockModule("../../config.js", () => ({}));
|
|
3
|
+
const mockLogServiceWrite = jest
|
|
4
|
+
.fn()
|
|
5
|
+
.mockResolvedValue(1);
|
|
6
|
+
jest.unstable_mockModule("../../utils/logService.js", () => ({
|
|
7
|
+
write: mockLogServiceWrite,
|
|
8
|
+
}));
|
|
9
|
+
const output = await import("../../utils/output.js");
|
|
10
|
+
describe("commentAndLog function", () => {
|
|
11
|
+
it("should call writeDbLog with the correct arguments", async () => {
|
|
12
|
+
// Assuming you've refactored commentAndLog to take logService or its functionality as a parameter
|
|
13
|
+
await output.commentAndLog("Test message");
|
|
14
|
+
// Verify the mock was called correctly
|
|
15
|
+
expect(mockLogServiceWrite).toHaveBeenCalledWith({
|
|
16
|
+
content: "Test message",
|
|
17
|
+
role: "user",
|
|
18
|
+
type: "comment",
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
//# sourceMappingURL=output.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.test.js","sourceRoot":"","sources":["../../../src/__tests__/utils/output.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAG3D,IAAI,CAAC,mBAAmB,CAAC,iBAAiB,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AAExD,MAAM,mBAAmB,GAAG,IAAI;KAC7B,EAAE,EAAwD;KAC1D,iBAAiB,CAAC,CAAC,CAAC,CAAC;AAExB,IAAI,CAAC,mBAAmB,CAAC,2BAA2B,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3D,KAAK,EAAE,mBAAmB;CAC3B,CAAC,CAAC,CAAC;AAEJ,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAC;AAErD,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,kGAAkG;QAClG,MAAM,MAAM,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC;QAE3C,uCAAuC;QACvC,MAAM,CAAC,mBAAmB,CAAC,CAAC,oBAAoB,CAAC;YAC/C,OAAO,EAAE,cAAc;YACvB,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,SAAS;SAChB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
|
+
import { valueFromString } from "../../utils/utilities.js";
|
|
3
|
+
describe("valueFromString", () => {
|
|
4
|
+
const obj = {
|
|
5
|
+
user: {
|
|
6
|
+
name: "John Doe",
|
|
7
|
+
contact: {
|
|
8
|
+
email: "john@example.com",
|
|
9
|
+
phone: {
|
|
10
|
+
home: "123456",
|
|
11
|
+
work: "789101",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
test("retrieves a nested value successfully", () => {
|
|
17
|
+
expect(valueFromString(obj, "user.name")).toBe("John Doe");
|
|
18
|
+
expect(valueFromString(obj, "user.contact.email")).toBe("john@example.com");
|
|
19
|
+
expect(valueFromString(obj, "user.contact.phone.home")).toBe("123456");
|
|
20
|
+
});
|
|
21
|
+
test("returns undefined for non-existent path", () => {
|
|
22
|
+
expect(valueFromString(obj, "user.address")).toBeUndefined();
|
|
23
|
+
});
|
|
24
|
+
test("returns default value for non-existent path when specified", () => {
|
|
25
|
+
const defaultValue = "N/A";
|
|
26
|
+
expect(valueFromString(obj, "user.age", defaultValue)).toBe(defaultValue);
|
|
27
|
+
});
|
|
28
|
+
test("handles non-object inputs gracefully", () => {
|
|
29
|
+
expect(valueFromString(null, "user.name")).toBeUndefined();
|
|
30
|
+
expect(valueFromString(undefined, "user.name")).toBeUndefined();
|
|
31
|
+
expect(valueFromString("not-an-object", "user.name")).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
test("deals with edge cases for paths", () => {
|
|
34
|
+
expect(valueFromString(obj, "")).toEqual(obj);
|
|
35
|
+
expect(valueFromString(obj, ".", "default")).toBe("default");
|
|
36
|
+
});
|
|
37
|
+
test("handles empty object and non-matching paths", () => {
|
|
38
|
+
expect(valueFromString({}, "user.name")).toBeUndefined();
|
|
39
|
+
expect(valueFromString(obj, "user.nonexistent.prop", "default")).toBe("default");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
//# sourceMappingURL=utilities.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utilities.test.js","sourceRoot":"","sources":["../../../src/__tests__/utils/utilities.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAE3D,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,MAAM,GAAG,GAAG;QACV,IAAI,EAAE;YACJ,IAAI,EAAE,UAAU;YAChB,OAAO,EAAE;gBACP,KAAK,EAAE,kBAAkB;gBACzB,KAAK,EAAE;oBACL,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,QAAQ;iBACf;aACF;SACF;KACF,CAAC;IAEF,IAAI,CAAC,uCAAuC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC3D,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC5E,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACnD,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACtE,MAAM,YAAY,GAAG,KAAK,CAAC;QAC3B,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,eAAe,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAC3D,MAAM,CAAC,eAAe,CAAC,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QAChE,MAAM,CAAC,eAAe,CAAC,eAAe,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,iCAAiC,EAAE,GAAG,EAAE;QAC3C,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,6CAA6C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,eAAe,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC,CAAC,aAAa,EAAE,CAAC;QACzD,MAAM,CAAC,eAAe,CAAC,GAAG,EAAE,uBAAuB,EAAE,SAAS,CAAC,CAAC,CAAC,IAAI,CACnE,SAAS,CACV,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import table from "text-table";
|
|
3
|
+
import * as config from "../config.js";
|
|
4
|
+
import * as dbUtils from "../utils/dbUtils.js";
|
|
5
|
+
import * as utilities from "../utils/utilities.js";
|
|
6
|
+
import { naisysToHostPath } from "../utils/utilities.js";
|
|
7
|
+
const _dbFilePath = naisysToHostPath(`${config.naisysFolder}/lib/llmail.db`);
|
|
8
|
+
let _myUserId = -1;
|
|
9
|
+
// Implement maxes so that LLMs actively manage threads, archive, and create new ones
|
|
10
|
+
const _threadTokenMax = config.tokenMax / 2; // So 4000, would be 2000 thread max
|
|
11
|
+
const _messageTokenMax = _threadTokenMax / 5; // Given the above a 400 token max, and 5 big messages per thread
|
|
12
|
+
/** The 'non-simple' version of this is a thread first mail system. Where agents can create threads, add users, and reply to threads, etc..
|
|
13
|
+
* The problem with this was the agents were too chatty with so many mail commands, wasting context replying, reading threads, etc..
|
|
14
|
+
* Simple mode only has two commands. It still requires db persistance to support offline agents. */
|
|
15
|
+
export const simpleMode = true;
|
|
16
|
+
await init();
|
|
17
|
+
async function init() {
|
|
18
|
+
const newDbCreated = await dbUtils.initDatabase(_dbFilePath);
|
|
19
|
+
await usingDatabase(async (db) => {
|
|
20
|
+
if (newDbCreated) {
|
|
21
|
+
const createTables = [
|
|
22
|
+
`CREATE TABLE Users (
|
|
23
|
+
id INTEGER PRIMARY KEY,
|
|
24
|
+
username TEXT NOT NULL,
|
|
25
|
+
title TEXT NOT NULL
|
|
26
|
+
)`,
|
|
27
|
+
`CREATE TABLE Threads (
|
|
28
|
+
id INTEGER PRIMARY KEY,
|
|
29
|
+
subject TEXT NOT NULL,
|
|
30
|
+
tokenCount INTEGER NOT NULL DEFAULT 0
|
|
31
|
+
)`,
|
|
32
|
+
`CREATE TABLE ThreadMembers (
|
|
33
|
+
id INTEGER PRIMARY KEY,
|
|
34
|
+
threadId INTEGER NOT NULL,
|
|
35
|
+
userId INTEGER NOT NULL,
|
|
36
|
+
newMsgId INTEGER NOT NULL DEFAULT -1,
|
|
37
|
+
archived INTEGER NOT NULL DEFAULT 0,
|
|
38
|
+
UNIQUE(threadId,userId),
|
|
39
|
+
FOREIGN KEY(threadId) REFERENCES Threads(id),
|
|
40
|
+
FOREIGN KEY(userId) REFERENCES Users(id)
|
|
41
|
+
|
|
42
|
+
)`,
|
|
43
|
+
`CREATE TABLE ThreadMessages (
|
|
44
|
+
id INTEGER PRIMARY KEY,
|
|
45
|
+
threadId INTEGER NOT NULL,
|
|
46
|
+
userId INTEGER NOT NULL,
|
|
47
|
+
message TEXT NOT NULL,
|
|
48
|
+
date TEXT NOT NULL,
|
|
49
|
+
FOREIGN KEY(threadId) REFERENCES Threads(id),
|
|
50
|
+
FOREIGN KEY(userId) REFERENCES Users(id)
|
|
51
|
+
)`,
|
|
52
|
+
];
|
|
53
|
+
for (const createTable of createTables) {
|
|
54
|
+
await db.exec(createTable);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// If user is not in the db, add them
|
|
58
|
+
const user = await db.get("SELECT * FROM Users WHERE username = ?", [
|
|
59
|
+
config.agent.username,
|
|
60
|
+
]);
|
|
61
|
+
if (!user) {
|
|
62
|
+
const insertedUser = await db.run("INSERT INTO Users (username, title) VALUES (?, ?)", [config.agent.username, config.agent.title]);
|
|
63
|
+
if (!insertedUser.lastID) {
|
|
64
|
+
throw "Error adding local user to llmail database";
|
|
65
|
+
}
|
|
66
|
+
_myUserId = insertedUser.lastID;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
_myUserId = user.id;
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
export async function handleCommand(args) {
|
|
74
|
+
const argParams = args.split(" ");
|
|
75
|
+
if (!argParams[0]) {
|
|
76
|
+
if (simpleMode) {
|
|
77
|
+
argParams[0] = "help";
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return await listThreads();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
switch (argParams[0]) {
|
|
84
|
+
case "help": {
|
|
85
|
+
if (simpleMode) {
|
|
86
|
+
return `llmail <command>
|
|
87
|
+
users: Get list of users on the system
|
|
88
|
+
send "<users>" "subject" "message": Send a message. ${_messageTokenMax} token max.`;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
return `llmail <command>
|
|
92
|
+
no params: List all active threads
|
|
93
|
+
users: Get list of users on the system
|
|
94
|
+
send "<users>" "subject" "message": Send a new mail, starting a new thread
|
|
95
|
+
read <id>: Read a thread
|
|
96
|
+
reply <id> <message>: Reply to a thread
|
|
97
|
+
adduser <id> <username>: Add a user to thread with id
|
|
98
|
+
archive <ids>: Archives a comma separated list of threads`;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
case "send": {
|
|
102
|
+
const newParams = argParams.slice(1).join(" ").split('"');
|
|
103
|
+
const usernames = newParams[1].split(",").map((u) => u.trim());
|
|
104
|
+
const subject = newParams[3];
|
|
105
|
+
const message = newParams[5];
|
|
106
|
+
return await newThread(usernames, subject, message);
|
|
107
|
+
}
|
|
108
|
+
case "read": {
|
|
109
|
+
const threadId = parseInt(argParams[1]);
|
|
110
|
+
return await readThread(threadId);
|
|
111
|
+
}
|
|
112
|
+
case "users": {
|
|
113
|
+
return await listUsers();
|
|
114
|
+
}
|
|
115
|
+
case "reply": {
|
|
116
|
+
const threadId = parseInt(argParams[1]);
|
|
117
|
+
const message = argParams.slice(2).join(" ");
|
|
118
|
+
return await replyThread(threadId, message);
|
|
119
|
+
}
|
|
120
|
+
case "adduser": {
|
|
121
|
+
const threadId = parseInt(argParams[1]);
|
|
122
|
+
const username = argParams[2];
|
|
123
|
+
return await addUser(threadId, username);
|
|
124
|
+
}
|
|
125
|
+
case "archive": {
|
|
126
|
+
const threadIds = argParams
|
|
127
|
+
.slice(1)
|
|
128
|
+
.join(" ")
|
|
129
|
+
.split(",")
|
|
130
|
+
.map((id) => parseInt(id));
|
|
131
|
+
return await archiveThreads(threadIds);
|
|
132
|
+
}
|
|
133
|
+
// Debug level 'secret command'. Don't let the LLM know about this
|
|
134
|
+
case "reset":
|
|
135
|
+
if (fs.existsSync(_dbFilePath)) {
|
|
136
|
+
fs.unlinkSync(_dbFilePath);
|
|
137
|
+
}
|
|
138
|
+
await init();
|
|
139
|
+
return "llmail database reset";
|
|
140
|
+
default:
|
|
141
|
+
return "Unknown llmail command: " + argParams[0];
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
export async function getUnreadThreads() {
|
|
145
|
+
return await usingDatabase(async (db) => {
|
|
146
|
+
const updatedThreads = await db.all(`SELECT tm.threadId, tm.newMsgId
|
|
147
|
+
FROM ThreadMembers tm
|
|
148
|
+
WHERE tm.userId = ? AND tm.newMsgId >= 0 AND tm.archived = 0`, [_myUserId]);
|
|
149
|
+
return updatedThreads;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
async function listThreads() {
|
|
153
|
+
return await usingDatabase(async (db) => {
|
|
154
|
+
const threads = await db.all(`SELECT t.id, t.subject, max(msg.date) as date, t.tokenCount,
|
|
155
|
+
(
|
|
156
|
+
SELECT GROUP_CONCAT(u.username, ', ')
|
|
157
|
+
FROM ThreadMembers tm
|
|
158
|
+
JOIN Users u ON tm.userId = u.id
|
|
159
|
+
WHERE tm.threadId = t.id
|
|
160
|
+
GROUP BY tm.threadId
|
|
161
|
+
) AS members
|
|
162
|
+
FROM Threads t
|
|
163
|
+
JOIN ThreadMessages msg ON t.id = msg.threadId
|
|
164
|
+
JOIN ThreadMembers member ON t.id = member.threadId
|
|
165
|
+
WHERE member.userId = ? AND member.archived = 0
|
|
166
|
+
GROUP BY t.id, t.subject
|
|
167
|
+
ORDER BY max(msg.date)`, [_myUserId]);
|
|
168
|
+
// Show threads as a table
|
|
169
|
+
return table([
|
|
170
|
+
["ID", "Subject", "Date", "Members", "Token Count"],
|
|
171
|
+
...threads.map((t) => [
|
|
172
|
+
t.id,
|
|
173
|
+
t.subject,
|
|
174
|
+
t.date,
|
|
175
|
+
t.members,
|
|
176
|
+
`${t.tokenCount}/${_threadTokenMax}`,
|
|
177
|
+
]),
|
|
178
|
+
], { hsep: " | " });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async function newThread(usernames, subject, message) {
|
|
182
|
+
// Ensure user itself is in the list
|
|
183
|
+
if (!usernames.includes(config.agent.username)) {
|
|
184
|
+
usernames.push(config.agent.username);
|
|
185
|
+
}
|
|
186
|
+
message = message.replace(/\\n/g, "\n");
|
|
187
|
+
const msgTokenCount = validateMsgTokenCount(message);
|
|
188
|
+
return await usingDatabase(async (db) => {
|
|
189
|
+
await db.run("BEGIN TRANSACTION");
|
|
190
|
+
// Create thread
|
|
191
|
+
const thread = await db.run("INSERT INTO Threads (subject, tokenCount) VALUES (?, ?)", [subject, msgTokenCount]);
|
|
192
|
+
// Add users
|
|
193
|
+
for (const username of usernames) {
|
|
194
|
+
const user = await db.get("SELECT * FROM Users WHERE username = ?", [
|
|
195
|
+
username,
|
|
196
|
+
]);
|
|
197
|
+
if (user) {
|
|
198
|
+
await db.run("INSERT INTO ThreadMembers (threadId, userId, newMsgId) VALUES (?, ?, ?)", [thread.lastID, user.id, user.id === _myUserId ? -1 : 0]);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
await db.run("ROLLBACK");
|
|
202
|
+
throw `Error: User ${username} not found`;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Add message
|
|
206
|
+
await db.run("INSERT INTO ThreadMessages (threadId, userId, message, date) VALUES (?, ?, ?, ?)", [thread.lastID, _myUserId, message, new Date().toISOString()]);
|
|
207
|
+
await db.run("COMMIT");
|
|
208
|
+
return simpleMode ? "Mail sent" : `Thread created with id ${thread.lastID}`;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
export async function readThread(threadId, newMsgId,
|
|
212
|
+
/** For checking new messages and getting a token count, while not showing the user */
|
|
213
|
+
peek) {
|
|
214
|
+
return await usingDatabase(async (db) => {
|
|
215
|
+
const thread = await getThread(db, threadId);
|
|
216
|
+
const threadMembers = await db.all(`SELECT u.id, u.username
|
|
217
|
+
FROM ThreadMembers tm
|
|
218
|
+
JOIN Users u ON tm.userId = u.id
|
|
219
|
+
WHERE tm.threadId = ?`, [threadId]);
|
|
220
|
+
let unreadFilter = "";
|
|
221
|
+
if (newMsgId != undefined) {
|
|
222
|
+
unreadFilter = `AND tm.id >= ${newMsgId}`;
|
|
223
|
+
}
|
|
224
|
+
const messages = await db.all(`SELECT u.username, tm.date, tm.message
|
|
225
|
+
FROM ThreadMessages tm
|
|
226
|
+
JOIN Users u ON tm.userId = u.id
|
|
227
|
+
WHERE tm.threadId = ? ${unreadFilter}
|
|
228
|
+
ORDER BY tm.date`, [threadId]);
|
|
229
|
+
let threadMessages = "";
|
|
230
|
+
// If simple mode just show subject/from/to
|
|
231
|
+
// Buildings strings with \n here because otherwise this code is super hard to read
|
|
232
|
+
if (simpleMode) {
|
|
233
|
+
for (const message of messages) {
|
|
234
|
+
const toMembers = threadMembers
|
|
235
|
+
.filter((m) => m.username !== message.username)
|
|
236
|
+
.map((m) => m.username)
|
|
237
|
+
.join(", ");
|
|
238
|
+
threadMessages +=
|
|
239
|
+
`Subject: ${thread.subject}\n` +
|
|
240
|
+
`From: ${message.username}\n` +
|
|
241
|
+
`To: ${toMembers}\n` +
|
|
242
|
+
`Date: ${new Date(message.date).toLocaleString()}\n` +
|
|
243
|
+
`Message:\n` +
|
|
244
|
+
`${message.message}\n`;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Else threaded version
|
|
248
|
+
else {
|
|
249
|
+
const toMembers = threadMembers.map((m) => m.username).join(", ");
|
|
250
|
+
threadMessages =
|
|
251
|
+
`Thread ${thread.id}: ${thread.subject}\n` + `Members: ${toMembers}\n`;
|
|
252
|
+
for (const message of messages) {
|
|
253
|
+
threadMessages +=
|
|
254
|
+
`\n` +
|
|
255
|
+
`From: ${message.username}\n` +
|
|
256
|
+
`Date: ${new Date(message.date).toLocaleString()}\n` +
|
|
257
|
+
`Message:\n` +
|
|
258
|
+
`${message.message}\n`;
|
|
259
|
+
}
|
|
260
|
+
threadMessages += `\nUse 'llmail reply ${threadId}' to reply.`;
|
|
261
|
+
}
|
|
262
|
+
if (!peek) {
|
|
263
|
+
await markAsRead(threadId);
|
|
264
|
+
}
|
|
265
|
+
return threadMessages;
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
export async function markAsRead(threadId) {
|
|
269
|
+
await usingDatabase(async (db) => {
|
|
270
|
+
await db.run(`UPDATE ThreadMembers
|
|
271
|
+
SET newMsgId = -1
|
|
272
|
+
WHERE threadId = ? AND userId = ?`, [threadId, _myUserId]);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
async function listUsers() {
|
|
276
|
+
return await usingDatabase(async (db) => {
|
|
277
|
+
const usersList = await db.all("SELECT * FROM Users");
|
|
278
|
+
return table([
|
|
279
|
+
["Username", "Title"],
|
|
280
|
+
...usersList.map((ul) => [ul.username, ul.title]),
|
|
281
|
+
], { hsep: " | " });
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
async function replyThread(threadId, message) {
|
|
285
|
+
message = message.replace(/\\n/g, "\n");
|
|
286
|
+
// Validate message does not exceed token limit
|
|
287
|
+
const msgTokenCount = validateMsgTokenCount(message);
|
|
288
|
+
return await usingDatabase(async (db) => {
|
|
289
|
+
const thread = await getThread(db, threadId);
|
|
290
|
+
const newThreadTokenTotal = thread.tokenCount + msgTokenCount;
|
|
291
|
+
if (newThreadTokenTotal > _threadTokenMax) {
|
|
292
|
+
throw `Error: Reply is ${msgTokenCount} tokens and thread is ${thread.tokenCount} tokens.
|
|
293
|
+
Reply would cause thread to exceed total thread token limit of ${_threadTokenMax} tokens.
|
|
294
|
+
Consider archiving this thread and starting a new one.`;
|
|
295
|
+
}
|
|
296
|
+
const insertedMessage = await db.run("INSERT INTO ThreadMessages (threadId, userId, message, date) VALUES (?, ?, ?, ?)", [thread.id, _myUserId, message, new Date().toISOString()]);
|
|
297
|
+
// Mark thread has new message only if it hasnt already been marked
|
|
298
|
+
await db.run(`UPDATE ThreadMembers
|
|
299
|
+
SET newMsgId = ?, archived = 0
|
|
300
|
+
WHERE newMsgId = -1 AND threadId = ? AND userId != ?`, [insertedMessage.lastID, thread.id, _myUserId]);
|
|
301
|
+
// Update token total
|
|
302
|
+
await db.run(`UPDATE Threads
|
|
303
|
+
SET tokenCount = ?
|
|
304
|
+
WHERE id = ?`, [newThreadTokenTotal, thread.id]);
|
|
305
|
+
return `Message added to thread ${threadId}`;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async function addUser(threadId, username) {
|
|
309
|
+
return await usingDatabase(async (db) => {
|
|
310
|
+
const thread = await getThread(db, threadId);
|
|
311
|
+
const user = await getUser(db, username);
|
|
312
|
+
await db.run("INSERT INTO ThreadMembers (threadId, userId, newMsgId) VALUES (?, ?, 0)", [thread.id, user.id]);
|
|
313
|
+
return `User ${username} added to thread ${threadId}`;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
async function archiveThreads(threadIds) {
|
|
317
|
+
return await usingDatabase(async (db) => {
|
|
318
|
+
await db.run(`UPDATE ThreadMembers
|
|
319
|
+
SET archived = 1
|
|
320
|
+
WHERE threadId IN (${threadIds.join(",")}) AND userId = ?`, [_myUserId]);
|
|
321
|
+
return `Threads ${threadIds.join(",")} archived`;
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
async function getThread(db, threadId) {
|
|
325
|
+
const thread = await db.get(`SELECT * FROM Threads WHERE id = ?`, [threadId]);
|
|
326
|
+
if (!thread) {
|
|
327
|
+
throw `Error: Thread ${threadId} not found`;
|
|
328
|
+
}
|
|
329
|
+
return thread;
|
|
330
|
+
}
|
|
331
|
+
async function getUser(db, username) {
|
|
332
|
+
const user = await db.get(`SELECT * FROM Users WHERE username = ?`, [
|
|
333
|
+
username,
|
|
334
|
+
]);
|
|
335
|
+
if (!user) {
|
|
336
|
+
throw `Error: User ${username} not found`;
|
|
337
|
+
}
|
|
338
|
+
return user;
|
|
339
|
+
}
|
|
340
|
+
function validateMsgTokenCount(message) {
|
|
341
|
+
const msgTokenCount = utilities.getTokenCount(message);
|
|
342
|
+
if (msgTokenCount > _messageTokenMax) {
|
|
343
|
+
throw `Error: Message is ${msgTokenCount} tokens, exceeding the limit of ${_messageTokenMax} tokens`;
|
|
344
|
+
}
|
|
345
|
+
return msgTokenCount;
|
|
346
|
+
}
|
|
347
|
+
async function usingDatabase(run) {
|
|
348
|
+
return dbUtils.usingDatabase(_dbFilePath, run);
|
|
349
|
+
}
|
|
350
|
+
//# sourceMappingURL=llmail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"llmail.js","sourceRoot":"","sources":["../../src/apps/llmail.ts"],"names":[],"mappings":"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"}
|