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.
Files changed (48) hide show
  1. package/.env.example +15 -0
  2. package/LICENSE.md +7 -0
  3. package/README.md +125 -0
  4. package/agents/example.yaml +34 -0
  5. package/dist/__tests__/utils/output.test.js +22 -0
  6. package/dist/__tests__/utils/output.test.js.map +1 -0
  7. package/dist/__tests__/utils/utilities.test.js +42 -0
  8. package/dist/__tests__/utils/utilities.test.js.map +1 -0
  9. package/dist/apps/llmail.js +350 -0
  10. package/dist/apps/llmail.js.map +1 -0
  11. package/dist/apps/llmynx.js +229 -0
  12. package/dist/apps/llmynx.js.map +1 -0
  13. package/dist/command/commandHandler.js +151 -0
  14. package/dist/command/commandHandler.js.map +1 -0
  15. package/dist/command/commandLoop.js +176 -0
  16. package/dist/command/commandLoop.js.map +1 -0
  17. package/dist/command/promptBuilder.js +133 -0
  18. package/dist/command/promptBuilder.js.map +1 -0
  19. package/dist/command/shellCommand.js +36 -0
  20. package/dist/command/shellCommand.js.map +1 -0
  21. package/dist/command/shellWrapper.js +179 -0
  22. package/dist/command/shellWrapper.js.map +1 -0
  23. package/dist/config.js +46 -0
  24. package/dist/config.js.map +1 -0
  25. package/dist/llm/contextManager.js +127 -0
  26. package/dist/llm/contextManager.js.map +1 -0
  27. package/dist/llm/costTracker.js +43 -0
  28. package/dist/llm/costTracker.js.map +1 -0
  29. package/dist/llm/llModels.js +52 -0
  30. package/dist/llm/llModels.js.map +1 -0
  31. package/dist/llm/llmDtos.js +8 -0
  32. package/dist/llm/llmDtos.js.map +1 -0
  33. package/dist/llm/llmService.js +113 -0
  34. package/dist/llm/llmService.js.map +1 -0
  35. package/dist/naisys.js +7 -0
  36. package/dist/naisys.js.map +1 -0
  37. package/dist/utils/dbUtils.js +37 -0
  38. package/dist/utils/dbUtils.js.map +1 -0
  39. package/dist/utils/inputMode.js +18 -0
  40. package/dist/utils/inputMode.js.map +1 -0
  41. package/dist/utils/logService.js +116 -0
  42. package/dist/utils/logService.js.map +1 -0
  43. package/dist/utils/output.js +38 -0
  44. package/dist/utils/output.js.map +1 -0
  45. package/dist/utils/utilities.js +40 -0
  46. package/dist/utils/utilities.js.map +1 -0
  47. package/naisys.sh +56 -0
  48. 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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"}