@mercury-fw/cli 0.72.0 → 0.73.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/CHANGELOG.md +61 -0
- package/README.md +11 -9
- package/dist/src/app/commands.d.ts +11 -5
- package/dist/src/render.d.ts +3 -1
- package/package.json +8 -7
- package/src/app/commands.ts +7 -5
- package/src/program.ts +3 -3
- package/src/render.ts +63 -14
- package/src/versions.ts +7 -1
- package/template/Dockerfile.tpl +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# @mercury-fw/cli
|
|
2
2
|
|
|
3
|
+
## 0.73.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- a8e2fe9: - the work nobody waits on (titles, memory captures, corrections, the archive's index, the nightly review) waits for the model: it runs only in the requests the turns leave free of the model's `capacity`, one model call at a time, titles, corrections and the archive's index first
|
|
8
|
+
- `ollamaModel(name, { backgroundSlots })`: how many of the model's requests that work may take at once; 1 when left out, below `capacity` when it's above 1
|
|
9
|
+
- a turn's corrections, its threshold capture and its message's indexing no longer hold the conversation's next turn
|
|
10
|
+
- a model of a plugin's own (`ctx.modelFor`) goes through the same queue
|
|
11
|
+
- `background.job` in the traces: each piece of that work that called a model, its models, model calls, time waited, time taken, and whether it was done, failed or dropped at stop
|
|
12
|
+
- `mfw create`: `backgroundSlots` read from `OLLAMA_BACKGROUND_SLOTS`, in the env example
|
|
13
|
+
- **Migration:** to set it from the environment, add `backgroundSlots: envNumber("OLLAMA_BACKGROUND_SLOTS"),` to the turn model's `ollamaModel(...)` in `mercury.config.ts`
|
|
14
|
+
- fb1f30a: - conversation-store contract: `ConversationStorePlugin`, `ConversationStore`, `StoredMessage`, `StoredTitle`, `CONVERSATION_STORE_API_VERSION`
|
|
15
|
+
- `conversationStore` in `mercury.config.ts`: where every conversation's messages and title are kept
|
|
16
|
+
- **Breaking:** without `conversationStore` nothing is archived: no reopened or listed conversations, no titles, no `recall_verbatim`
|
|
17
|
+
- `qdrantConversations()` in `@mercury-fw/store-qdrant`: messages in `conversation_messages` (`QDRANT_MESSAGES_COLLECTION`), titles in `conversation_titles` (`QDRANT_TITLES_COLLECTION`); on start, copies the messages of `verbatim_archive` into `conversation_messages` once, marking the copy done only when all are in
|
|
18
|
+
- `@mercury-fw/core` no longer depends on `@qdrant/js-client-rest` and no longer reads `QDRANT_URL`
|
|
19
|
+
- **Breaking:** `GET /health` (HTTP): `conversationStoreReachable` and `vectorIndexReachable` (`null` when not declared) instead of `qdrantReachable`
|
|
20
|
+
- **Breaking:** channel contract 7: `ChannelHostReads.conversation`, `conversations` and `memoryScroll` optional, given only with a conversation store / a vector index; channels written for contract 6 are refused
|
|
21
|
+
- `channel-http`, `channel-google-chat`: channel contract 7, framework 0.73 or later
|
|
22
|
+
- `channel-http`: `/conversation`, `/conversations` (`GET`, `PATCH`) only with a conversation store, `/memory/scroll` only with a vector index; `404` otherwise
|
|
23
|
+
- `system.start` trace event: `conversationStore` (`declared`, `active`)
|
|
24
|
+
- `mfw create`: new apps declare `conversationStore: qdrantConversations()`
|
|
25
|
+
- **Migration:** in `mercury.config.ts`, import `qdrantConversations` from `@mercury-fw/store-qdrant` and add `conversationStore: qdrantConversations(),`; upgrade `@mercury-fw/channel-http` and `@mercury-fw/channel-google-chat` with the framework; a UI reading `qdrantReachable` from `/health` reads the two new fields
|
|
26
|
+
- 4a00b0c: - **Breaking:** new required `MERCURY_DATA_PATH`, the folder for Mercury's own state (account links, profiles, plugins' `dataDir`, traces, `trace-secret`), no longer in the vault's `.mercury/`; the start stops without it
|
|
27
|
+
- first start with an empty data folder copies the vault's `.mercury/` into it, leaving the original in place; a failed copy is undone and stops the start
|
|
28
|
+
- `system.migrated` trace event when that copy happens
|
|
29
|
+
- `mfw identity` reads the links from `MERCURY_DATA_PATH`
|
|
30
|
+
- `mfw reset wiki` no longer deletes links, profiles and traces
|
|
31
|
+
- `mfw reset data` deletes the `mercury-data` volume
|
|
32
|
+
- `mfw create`: new apps get a `mercury-data` volume mounted at `/app/data` and `MERCURY_DATA_PATH=/app/data`
|
|
33
|
+
- **Migration:** in `Dockerfile`, add `/app/data` to the `mkdir -p` and the `chown mercury:mercury` of the volumes' mount points; in `docker-compose.yml`, add `- mercury-data:/app/data` to the `mercury` service's `volumes` and `mercury-data:` with `name: <app>_mercury-data` under the top-level `volumes`; in `.env`, add `MERCURY_DATA_PATH=/app/data`; then `mfw start`
|
|
34
|
+
- 40fde6b: - `ollamaModel(name, settings?)` and `ollamaEmbedding(name, { capacity }?)` declare a model; `capacity`, how many requests its server answers at once, 1 when left out
|
|
35
|
+
- `models` in `mercury.config.ts`: `turn` (required), `background` (titles, memory captures, corrections, nightly review; the turn's when left out), `embedding`
|
|
36
|
+
- a `vectorIndex` without `models.embedding` stops the start
|
|
37
|
+
- `ctx.modelFor(handle)` in a plugin's `build()`: the model of a handle the plugin was given in its configuration
|
|
38
|
+
- `system.start` carries `models` (each role's name and capacity) instead of `model` and `modelSettings`
|
|
39
|
+
- the startup log names each role's model
|
|
40
|
+
- `mfw create`: the config declares `main` from `OLLAMA_MODEL` and its settings, and `models` with `turn` and `embedding`; `OLLAMA_CAPACITY` in the env example
|
|
41
|
+
- **Breaking:** `model` is gone from `mercury.config.ts`, and the core no longer reads `OLLAMA_MODEL` or `OLLAMA_EMBEDDING_MODEL`: declare the models in `models`
|
|
42
|
+
- **Migration:** in `mercury.config.ts`, import `ollamaEmbedding` and `ollamaModel` from `@mercury-fw/core`, add above the config `const main = ollamaModel(process.env.OLLAMA_MODEL, { think: envBoolean("OLLAMA_THINK", true), temperature: envNumber("OLLAMA_TEMPERATURE"), topP: envNumber("OLLAMA_TOP_P"), topK: envNumber("OLLAMA_TOP_K"), capacity: envNumber("OLLAMA_CAPACITY") });`, and replace the `model: { ... }` section with `models: { turn: main, embedding: ollamaEmbedding(process.env.OLLAMA_EMBEDDING_MODEL ?? "nomic-embed-text") },`; `OLLAMA_CAPACITY` is optional
|
|
43
|
+
- b634bd1: - vector-index contract: `VectorIndexPlugin`, `VectorIndex`, `VectorPoint`, `VectorSpaceSpec`, `VectorWhere`, `VECTOR_INDEX_API_VERSION`
|
|
44
|
+
- `vectorIndex` in `mercury.config.ts`: where episodes, semantic facts, procedural corrections and `recall_verbatim`'s search are kept
|
|
45
|
+
- **Breaking:** without `vectorIndex` there is no long-term memory: no conversation summaries, no corrections, no `recall_verbatim` (tool and prompt), idle conversations closed without a summary
|
|
46
|
+
- new package `@mercury-fw/store-qdrant`: `qdrantIndex()`, Qdrant at `QDRANT_URL`, each space the collection of the same name or the one its `QDRANT_*_COLLECTION` names
|
|
47
|
+
- the memory's vector length comes from the embedding model at startup; `QDRANT_*_VECTOR_SIZE` no longer read
|
|
48
|
+
- `GET /memory/scroll` (HTTP) takes the index's space name (`episodic_memory`, `semantic_facts`, `verbatim_archive`), not the `QDRANT_*_COLLECTION` name
|
|
49
|
+
- `system.start` trace event: `vectorIndex` (`declared`, `active`)
|
|
50
|
+
- `mfw memory` runs the memory CLI of `@mercury-fw/store-qdrant`
|
|
51
|
+
- `mfw create`: new apps declare `vectorIndex: qdrantIndex()` and depend on `@mercury-fw/store-qdrant`
|
|
52
|
+
- **Migration:** add `@mercury-fw/store-qdrant` to the app's dependencies, at the framework's version; in `mercury.config.ts`, `import { qdrantIndex } from "@mercury-fw/store-qdrant";` and `vectorIndex: qdrantIndex(),`; `QDRANT_*_VECTOR_SIZE` can go from `.env`
|
|
53
|
+
|
|
54
|
+
### Patch Changes
|
|
55
|
+
|
|
56
|
+
- Updated dependencies [a8e2fe9]
|
|
57
|
+
- Updated dependencies [fb1f30a]
|
|
58
|
+
- Updated dependencies [4a00b0c]
|
|
59
|
+
- Updated dependencies [40fde6b]
|
|
60
|
+
- Updated dependencies [b634bd1]
|
|
61
|
+
- @mercury-fw/core@0.73.0
|
|
62
|
+
- @mercury-fw/utils@0.73.0
|
|
63
|
+
|
|
3
64
|
## 0.72.0
|
|
4
65
|
|
|
5
66
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ On Mercury's documentation site: [the `mfw` reference](https://lucabro81.github.
|
|
|
19
19
|
- [`mfw memory list`](#mfw-memory-list)
|
|
20
20
|
- [`mfw memory read <collection> [--limit N]`](#mfw-memory-read-collection---limit-n)
|
|
21
21
|
- [`mfw identity links`, `link`, `unlink`](#mfw-identity-links-link-unlink)
|
|
22
|
-
- [`mfw reset <memory|wiki>`](#mfw-reset-
|
|
22
|
+
- [`mfw reset <memory|wiki|data>`](#mfw-reset-memorywikidata)
|
|
23
23
|
- [`mfw credentials setup <plugin>`](#mfw-credentials-setup-plugin)
|
|
24
24
|
- [`mfw credentials check <plugin>`](#mfw-credentials-check-plugin)
|
|
25
25
|
- [`mfw credentials reset <plugin> [--user <key>]`](#mfw-credentials-reset-plugin---user-key)
|
|
@@ -53,7 +53,7 @@ Writes a new Mercury app into `<folder>`, which has to be missing or empty; the
|
|
|
53
53
|
- which channels and which tool plugins to include (none is fine: the REPL always works);
|
|
54
54
|
- the git remote for `origin`, which you can leave empty (most of the time it doesn't exist yet when you scaffold).
|
|
55
55
|
|
|
56
|
-
It writes the `mercury.config.ts` for that selection, the persona, the traces (`@mercury-fw/trace-jsonl`, at level `meta`, in
|
|
56
|
+
It writes the `mercury.config.ts` for that selection, the persona, the traces (`@mercury-fw/trace-jsonl`, at level `meta`, in `traces/` in the data folder), a `locales/` folder with an `en.json` for the texts people see (empty, or the starting formatter rules' own texts: `app.jira.no-issues` with Jira), the service and REPL entrypoints, a Dockerfile, a compose file with Qdrant (the app's vector index and conversation store, `vectorIndex: qdrantIndex()` and `conversationStore: qdrantConversations()`) and a volume for the wiki and one for Mercury's own state, and an env example listing every variable the chosen pieces read. Its `package.json` declares `plugins/*` as workspaces, the place for the app's own plugins (each a package in `plugins/<name>`, which the image copies before installing), and a `test` script (`bun test`, the plugins' tests included); the workspaces make Bun install with its isolated linker, and the Dockerfile's PATH step follows the links it makes. A plugin that hands over lists comes wrapped in the formatter with a starting rule, yours to change.
|
|
57
57
|
|
|
58
58
|
Then it runs `bun install` in the app and creates a git repository on `main` with a first commit (`bun.lock` included), adding `origin` when you gave one; nothing is pushed. Each step is best effort and never undoes the ones before it: an install that fails still leaves the app committed, without the lockfile, and the closing message says what to run. The repository is skipped when the folder is already inside one (an app created in a monorepo belongs to it). Whether git can commit is git's call: when it refuses (no identity, a hook) or isn't installed, the message reports why and lists the commands to finish by hand. `bun install` prints its own output as it runs.
|
|
59
59
|
|
|
@@ -92,7 +92,7 @@ mfw start --no-cache
|
|
|
92
92
|
|
|
93
93
|
### `mfw stop`
|
|
94
94
|
|
|
95
|
-
Stops the app and Qdrant and removes their containers (`docker compose down`). The volumes stay, so memory, the wiki and the CLI credentials are all there on the next start.
|
|
95
|
+
Stops the app and Qdrant and removes their containers (`docker compose down`). The volumes stay, so memory, the wiki, Mercury's own state (the data folder) and the CLI credentials are all there on the next start.
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
mfw stop
|
|
@@ -155,23 +155,24 @@ There's no command writing inferred notes on purpose: those are the agent's own,
|
|
|
155
155
|
|
|
156
156
|
### `mfw memory list`
|
|
157
157
|
|
|
158
|
-
Lists the collections of the memory on Qdrant with how many points each holds, in a one-off container that reaches Qdrant the way the app does. Read-only.
|
|
158
|
+
Lists the collections of the memory on Qdrant with how many points each holds, in a one-off container that reaches Qdrant the way the app does. Read-only. It runs the memory CLI of `@mercury-fw/store-qdrant`, which an app made with `mfw create` has: one without it has no `mfw memory`.
|
|
159
159
|
|
|
160
160
|
```bash
|
|
161
161
|
mfw memory list
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
```
|
|
165
|
+
conversation_messages 113 points
|
|
165
166
|
conversation_titles 4 points
|
|
166
167
|
episodic_memory 25 points
|
|
167
168
|
semantic_facts 20 points
|
|
168
169
|
tool_corrections 37 points
|
|
169
|
-
verbatim_archive
|
|
170
|
+
verbatim_archive 112 points
|
|
170
171
|
```
|
|
171
172
|
|
|
172
173
|
### `mfw memory read <collection> [--limit N]`
|
|
173
174
|
|
|
174
|
-
Prints a collection's points, each as its id followed by one line per payload field. Newest first where the collection has a timestamp index (episodic memory and the verbatim archive do); in Qdrant's own order otherwise, and it says so. `--limit` defaults to 20. Read-only.
|
|
175
|
+
Prints a collection's points, each as its id followed by one line per payload field. Newest first where the collection has a timestamp index (the episodic memory, the conversation messages and the verbatim archive do; `conversation_messages` also holds one point with no person, marking the copy of an older archive done); in Qdrant's own order otherwise, and it says so. `--limit` defaults to 20. Read-only.
|
|
175
176
|
|
|
176
177
|
```bash
|
|
177
178
|
mfw memory read episodic_memory
|
|
@@ -188,18 +189,19 @@ mfw identity link google-chat:users/123 oidc:3123
|
|
|
188
189
|
mfw identity unlink google-chat:users/123
|
|
189
190
|
```
|
|
190
191
|
|
|
191
|
-
### `mfw reset <memory|wiki>`
|
|
192
|
+
### `mfw reset <memory|wiki|data>`
|
|
192
193
|
|
|
193
|
-
Deletes for good what the assistant remembers: `memory` is every collection on Qdrant, `wiki` the whole vault. It reads the volume's real name from the compose file, tells you which one is about to go, and asks you to type the app's name (the `name` in `package.json`); anything else, an empty answer, a `y` or closing the input included, deletes nothing and exits 1. Once confirmed it stops the service using the volume, removes its container and the volume, and starts the service again on an empty one (`docker compose stop`, `rm -f`, `docker volume rm`, `up -d`). After `memory`, a running app is restarted too (`docker compose restart mercury`), since it sets up its collections only when it starts.
|
|
194
|
+
Deletes for good what the assistant remembers: `memory` is every collection on Qdrant, `wiki` the whole vault, `data` Mercury's own state (the linked accounts, the profiles, the plugins' data, the traces), each on a volume of its own. An app whose compose file has no `mercury-data` volume yet has nothing for `data` to delete: the command says so before asking anything. It reads the volume's real name from the compose file, tells you which one is about to go, and asks you to type the app's name (the `name` in `package.json`); anything else, an empty answer, a `y` or closing the input included, deletes nothing and exits 1. Once confirmed it stops the service using the volume, removes its container and the volume, and starts the service again on an empty one (`docker compose stop`, `rm -f`, `docker volume rm`, `up -d`). After `memory`, a running app is restarted too (`docker compose restart mercury`), since it sets up its collections only when it starts.
|
|
194
195
|
|
|
195
196
|
If a step fails once the service is stopped (the volume still in use by a one-off container, say), it stops there and says the service is down: `mfw start` brings it back.
|
|
196
197
|
|
|
197
198
|
```bash
|
|
198
199
|
mfw reset memory
|
|
199
200
|
mfw reset wiki
|
|
201
|
+
mfw reset data
|
|
200
202
|
```
|
|
201
203
|
|
|
202
|
-
Useful for clearing out test data; the other
|
|
204
|
+
Useful for clearing out test data; the other volumes aren't touched.
|
|
203
205
|
|
|
204
206
|
### `mfw credentials setup <plugin>`
|
|
205
207
|
|
|
@@ -15,12 +15,13 @@ export type AppDeps = {
|
|
|
15
15
|
/** The user's home folder, where a CLI keeps its config (`~/.config/<cli>`). */
|
|
16
16
|
home: string;
|
|
17
17
|
};
|
|
18
|
-
/** The
|
|
19
|
-
* app's folder, where node_modules/@mercury-fw
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
/** The framework's maintenance CLIs, from the container's working directory
|
|
19
|
+
* (the app's folder, where node_modules/@mercury-fw/* is): the core's, and the
|
|
20
|
+
* memory's in its Qdrant package. A path and not a bin: a monorepo image
|
|
21
|
+
* installs before copying the sources, and Bun doesn't link a bin whose file
|
|
22
|
+
* isn't there yet. Both packages move in lockstep with this CLI. */
|
|
22
23
|
export declare const VAULT_CLI = "node_modules/@mercury-fw/core/src/wiki/vault-cli.ts";
|
|
23
|
-
export declare const MEMORY_CLI = "node_modules/@mercury-fw/
|
|
24
|
+
export declare const MEMORY_CLI = "node_modules/@mercury-fw/store-qdrant/memory-cli.ts";
|
|
24
25
|
export declare const IDENTITY_CLI = "node_modules/@mercury-fw/core/src/identity/links-cli.ts";
|
|
25
26
|
/** What `mfw reset` can wipe: the compose service using the volume, the
|
|
26
27
|
* volume's key in the compose file, and how to say what's lost. */
|
|
@@ -35,6 +36,11 @@ export declare const RESET_TARGETS: {
|
|
|
35
36
|
readonly volume: "wiki-vault";
|
|
36
37
|
readonly what: "the wiki vault (every note)";
|
|
37
38
|
};
|
|
39
|
+
readonly data: {
|
|
40
|
+
readonly service: "mercury";
|
|
41
|
+
readonly volume: "mercury-data";
|
|
42
|
+
readonly what: "Mercury's own state (account links, profiles, plugins' data, traces)";
|
|
43
|
+
};
|
|
38
44
|
};
|
|
39
45
|
export type ResetTarget = keyof typeof RESET_TARGETS;
|
|
40
46
|
/** The commands bound to `app`, each returning its exit code. */
|
package/dist/src/render.d.ts
CHANGED
|
@@ -13,8 +13,10 @@ export type RenderInput = {
|
|
|
13
13
|
directory?: string;
|
|
14
14
|
versions: Record<string, string>;
|
|
15
15
|
};
|
|
16
|
-
/** The trace sink every new app declares, writing the traces to files
|
|
16
|
+
/** The trace sink every new app declares, writing the traces to files in Mercury's data folder. */
|
|
17
17
|
export declare const TRACE_SINK_PACKAGE = "@mercury-fw/trace-jsonl";
|
|
18
|
+
/** The vector index and the conversation store every new app declares: Qdrant, the compose service. */
|
|
19
|
+
export declare const VECTOR_INDEX_PACKAGE = "@mercury-fw/store-qdrant";
|
|
18
20
|
/** Builds every file of the new app. Throws on an invalid app name, an empty
|
|
19
21
|
* assistant name or role, an id the catalog doesn't have, or a package with no
|
|
20
22
|
* version in `input.versions`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mercury-fw/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.73.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -41,23 +41,24 @@
|
|
|
41
41
|
"comment:shape": "The Mercury CLI (`mfw`). `mfw create <dir>` writes a new Mercury app from the template. The framework packages move in lockstep with it, so a new app gets them at the CLI's own version; plugins and channels at the registry's latest. The plugin and channel packages are devDependencies only for the catalog test. `main(argv)` is exported for create-mercury-agent.",
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@clack/prompts": "^1.8.1",
|
|
44
|
-
"@mercury-fw/core": "0.
|
|
45
|
-
"@mercury-fw/utils": "0.
|
|
44
|
+
"@mercury-fw/core": "0.73.0",
|
|
45
|
+
"@mercury-fw/utils": "0.73.0",
|
|
46
46
|
"commander": "^15.0.0"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@mercury-fw/auth-oidc": "0.2.1",
|
|
50
50
|
"@mercury-fw/auth-static": "0.2.2",
|
|
51
|
-
"@mercury-fw/channel-google-chat": "0.
|
|
52
|
-
"@mercury-fw/channel-http": "0.
|
|
51
|
+
"@mercury-fw/channel-google-chat": "0.13.0",
|
|
52
|
+
"@mercury-fw/channel-http": "0.17.0",
|
|
53
53
|
"@mercury-fw/directory-static": "0.1.2",
|
|
54
|
-
"@mercury-fw/formatter": "0.
|
|
54
|
+
"@mercury-fw/formatter": "0.73.0",
|
|
55
55
|
"@mercury-fw/plugin-atlassian-admin": "0.5.1",
|
|
56
56
|
"@mercury-fw/plugin-bitbucket": "0.6.1",
|
|
57
57
|
"@mercury-fw/plugin-confluence": "0.1.0",
|
|
58
58
|
"@mercury-fw/plugin-jira": "0.8.1",
|
|
59
|
-
"@mercury-fw/plugin-web-search": "0.4.
|
|
59
|
+
"@mercury-fw/plugin-web-search": "0.4.2",
|
|
60
60
|
"@mercury-fw/plugin-zitadel": "0.5.1",
|
|
61
|
+
"@mercury-fw/store-qdrant": "0.73.0",
|
|
61
62
|
"@mercury-fw/typescript-config": "*",
|
|
62
63
|
"@mercury-fw/web-search-searxng": "0.1.1",
|
|
63
64
|
"@types/bun": "^1.4.2",
|
package/src/app/commands.ts
CHANGED
|
@@ -38,12 +38,13 @@ const GOOGLE_CHAT_PACKAGE = "@mercury-fw/channel-google-chat";
|
|
|
38
38
|
/** The app's service, the one the image builds. */
|
|
39
39
|
const SERVICE = "mercury";
|
|
40
40
|
|
|
41
|
-
/** The
|
|
42
|
-
* app's folder, where node_modules/@mercury-fw
|
|
43
|
-
*
|
|
44
|
-
*
|
|
41
|
+
/** The framework's maintenance CLIs, from the container's working directory
|
|
42
|
+
* (the app's folder, where node_modules/@mercury-fw/* is): the core's, and the
|
|
43
|
+
* memory's in its Qdrant package. A path and not a bin: a monorepo image
|
|
44
|
+
* installs before copying the sources, and Bun doesn't link a bin whose file
|
|
45
|
+
* isn't there yet. Both packages move in lockstep with this CLI. */
|
|
45
46
|
export const VAULT_CLI = "node_modules/@mercury-fw/core/src/wiki/vault-cli.ts";
|
|
46
|
-
export const MEMORY_CLI = "node_modules/@mercury-fw/
|
|
47
|
+
export const MEMORY_CLI = "node_modules/@mercury-fw/store-qdrant/memory-cli.ts";
|
|
47
48
|
export const IDENTITY_CLI = "node_modules/@mercury-fw/core/src/identity/links-cli.ts";
|
|
48
49
|
|
|
49
50
|
/** What `mfw reset` can wipe: the compose service using the volume, the
|
|
@@ -51,6 +52,7 @@ export const IDENTITY_CLI = "node_modules/@mercury-fw/core/src/identity/links-cl
|
|
|
51
52
|
export const RESET_TARGETS = {
|
|
52
53
|
memory: { service: "qdrant", volume: "qdrant-data", what: "Layer-3 memory (every Qdrant collection)" },
|
|
53
54
|
wiki: { service: SERVICE, volume: "wiki-vault", what: "the wiki vault (every note)" },
|
|
55
|
+
data: { service: SERVICE, volume: "mercury-data", what: "Mercury's own state (account links, profiles, plugins' data, traces)" },
|
|
54
56
|
} as const;
|
|
55
57
|
export type ResetTarget = keyof typeof RESET_TARGETS;
|
|
56
58
|
|
package/src/program.ts
CHANGED
|
@@ -119,7 +119,7 @@ Examples:
|
|
|
119
119
|
program
|
|
120
120
|
.command("stop")
|
|
121
121
|
.summary("stops the app")
|
|
122
|
-
.description("Stops the app and Qdrant and removes their containers. The volumes (memory, wiki, CLI credentials) stay.")
|
|
122
|
+
.description("Stops the app and Qdrant and removes their containers. The volumes (memory, wiki, Mercury's own state, CLI credentials) stay.")
|
|
123
123
|
.addHelpText("after", INSIDE_AN_APP)
|
|
124
124
|
.action(inApp((app) => app.stop()));
|
|
125
125
|
|
|
@@ -249,9 +249,9 @@ Examples:
|
|
|
249
249
|
|
|
250
250
|
program
|
|
251
251
|
.command("reset")
|
|
252
|
-
.summary("deletes memory or
|
|
252
|
+
.summary("deletes memory, the wiki or Mercury's state, after confirmation")
|
|
253
253
|
.description(
|
|
254
|
-
"Deletes for good what the assistant remembers: memory is every Qdrant collection, wiki the whole vault. It asks you to type the app's name first (anything else deletes nothing), then brings the service back up empty.",
|
|
254
|
+
"Deletes for good what the assistant remembers: memory is every Qdrant collection, wiki the whole vault, data Mercury's own state (account links, profiles, plugins' data, traces). It asks you to type the app's name first (anything else deletes nothing), then brings the service back up empty.",
|
|
255
255
|
)
|
|
256
256
|
.addArgument(new Argument("<target>", "what to delete").choices(Object.keys(RESET_TARGETS)))
|
|
257
257
|
.addHelpText("after", INSIDE_AN_APP)
|
package/src/render.ts
CHANGED
|
@@ -49,9 +49,19 @@ const CORE_ENV: EnvVar[] = [
|
|
|
49
49
|
{ name: "OLLAMA_TEMPERATURE", comment: "The chat model's temperature, from 0 up (empty: the model's own)" },
|
|
50
50
|
{ name: "OLLAMA_TOP_P", comment: "The chat model's top_p, above 0 and at most 1 (empty: the model's own)" },
|
|
51
51
|
{ name: "OLLAMA_TOP_K", comment: "The chat model's top_k, a whole number from 1 up (empty: the model's own)" },
|
|
52
|
+
{ name: "OLLAMA_CAPACITY", comment: "How many requests the chat model's runtime serves at once (empty: 1)" },
|
|
53
|
+
{
|
|
54
|
+
name: "OLLAMA_BACKGROUND_SLOTS",
|
|
55
|
+
comment: "How many of those the work nobody waits on may take, below the capacity when it's above 1 (empty: 1)",
|
|
56
|
+
},
|
|
52
57
|
{ name: "OLLAMA_EMBEDDING_MODEL", comment: "Embedding model for the episodic memory", value: "nomic-embed-text" },
|
|
53
58
|
{ name: "QDRANT_URL", comment: "Qdrant, the compose service", value: "http://qdrant:6333" },
|
|
54
59
|
{ name: "WIKI_VAULT_PATH", comment: "Wiki vault, the named volume's mount point", value: "/app/wiki-vault" },
|
|
60
|
+
{
|
|
61
|
+
name: "MERCURY_DATA_PATH",
|
|
62
|
+
comment: "Mercury's own state (account links, profiles, plugins' data, traces), the named volume's mount point",
|
|
63
|
+
value: "/app/data",
|
|
64
|
+
},
|
|
55
65
|
{
|
|
56
66
|
name: "TURN_STALL_TIMEOUT_MS",
|
|
57
67
|
comment: "How long a streaming turn waits for the model's next token before it's stopped with an error (0 turns it off)",
|
|
@@ -59,15 +69,32 @@ const CORE_ENV: EnvVar[] = [
|
|
|
59
69
|
},
|
|
60
70
|
];
|
|
61
71
|
|
|
62
|
-
/**
|
|
63
|
-
const
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
/** Above the config: the turn model, its name, settings and capacity each from its env variable (#355, #383). */
|
|
73
|
+
const MAIN_MODEL = [
|
|
74
|
+
"/**",
|
|
75
|
+
" * The model the assistant runs on, from .env: a setting left empty is the",
|
|
76
|
+
" * model's own (thinking stays on); capacity, how many requests its runtime",
|
|
77
|
+
" * serves at once, is 1, and so is backgroundSlots, how many of them the work",
|
|
78
|
+
" * nobody waits on may take.",
|
|
79
|
+
" */",
|
|
80
|
+
"const main = ollamaModel(process.env.OLLAMA_MODEL, {",
|
|
81
|
+
' think: envBoolean("OLLAMA_THINK", true),',
|
|
82
|
+
' temperature: envNumber("OLLAMA_TEMPERATURE"),',
|
|
83
|
+
' topP: envNumber("OLLAMA_TOP_P"),',
|
|
84
|
+
' topK: envNumber("OLLAMA_TOP_K"),',
|
|
85
|
+
' capacity: envNumber("OLLAMA_CAPACITY"),',
|
|
86
|
+
' backgroundSlots: envNumber("OLLAMA_BACKGROUND_SLOTS"),',
|
|
87
|
+
"});",
|
|
88
|
+
].join("\n");
|
|
89
|
+
|
|
90
|
+
/** The config's `models` section: the model of each of the core's roles (#383). */
|
|
91
|
+
const MODELS_SECTION = [
|
|
92
|
+
" // The models it runs on, by role: the turns on `main`, and the work no one",
|
|
93
|
+
" // waits on too unless a `background` model is added here; the memory",
|
|
94
|
+
" // found by meaning is embedded with `embedding`.",
|
|
95
|
+
" models: {",
|
|
96
|
+
" turn: main,",
|
|
97
|
+
' embedding: ollamaEmbedding(process.env.OLLAMA_EMBEDDING_MODEL ?? "nomic-embed-text"),',
|
|
71
98
|
" },",
|
|
72
99
|
].join("\n");
|
|
73
100
|
|
|
@@ -90,12 +117,27 @@ const CONFIG_HEADER = `/**
|
|
|
90
117
|
* Every tool plugin and channel declared here is active.
|
|
91
118
|
*/`;
|
|
92
119
|
|
|
93
|
-
/** The trace sink every new app declares, writing the traces to files
|
|
120
|
+
/** The trace sink every new app declares, writing the traces to files in Mercury's data folder. */
|
|
94
121
|
export const TRACE_SINK_PACKAGE = "@mercury-fw/trace-jsonl";
|
|
95
122
|
|
|
123
|
+
/** The vector index and the conversation store every new app declares: Qdrant, the compose service. */
|
|
124
|
+
export const VECTOR_INDEX_PACKAGE = "@mercury-fw/store-qdrant";
|
|
125
|
+
|
|
126
|
+
/** The config's `vectorIndex` and `conversationStore` entries, with what each holds. */
|
|
127
|
+
const VECTOR_INDEX_SECTION = [
|
|
128
|
+
" // Where the memory is found by meaning (what past conversations were about,",
|
|
129
|
+
" // what the agent learned about each person, the corrections to how a tool",
|
|
130
|
+
" // is used, and searching past messages): Qdrant, the compose service.",
|
|
131
|
+
" // Remove to keep no memory.",
|
|
132
|
+
" vectorIndex: qdrantIndex(),",
|
|
133
|
+
" // Where what was said is kept: every conversation, to reopen and list it,",
|
|
134
|
+
" // and its title. Qdrant, the compose service. Remove to archive nothing.",
|
|
135
|
+
" conversationStore: qdrantConversations(),",
|
|
136
|
+
].join("\n");
|
|
137
|
+
|
|
96
138
|
/** The config's `traces` entry, with what it records and where. */
|
|
97
139
|
const TRACES_SECTION = [
|
|
98
|
-
" // What the agent does, as events in
|
|
140
|
+
" // What the agent does, as events in traces/ in Mercury's data folder:",
|
|
99
141
|
" // a file per day, the last 7 days with activity kept. \"meta\" records names,",
|
|
100
142
|
" // outcomes and times; \"args\" also what the tools were given. Remove to record nothing.",
|
|
101
143
|
' traces: { sink: jsonlTraces(), level: "meta" },',
|
|
@@ -251,7 +293,7 @@ function renderConfig(
|
|
|
251
293
|
): string {
|
|
252
294
|
const withRules = tools.filter((t) => t.formatter);
|
|
253
295
|
const imports = [
|
|
254
|
-
'import { defineMercuryConfig, envBoolean, envNumber } from "@mercury-fw/core";',
|
|
296
|
+
'import { defineMercuryConfig, envBoolean, envNumber, ollamaEmbedding, ollamaModel } from "@mercury-fw/core";',
|
|
255
297
|
'import en from "./locales/en.json";',
|
|
256
298
|
'import pkg from "./package.json";',
|
|
257
299
|
];
|
|
@@ -267,6 +309,7 @@ function renderConfig(
|
|
|
267
309
|
imports.push(`import { ${c.exportName} } from "${c.package}";`);
|
|
268
310
|
}
|
|
269
311
|
imports.push(`import { jsonlTraces } from "${TRACE_SINK_PACKAGE}";`);
|
|
312
|
+
imports.push(`import { qdrantConversations, qdrantIndex } from "${VECTOR_INDEX_PACKAGE}";`);
|
|
270
313
|
|
|
271
314
|
const helpers = withRules.map((t) => `${t.formatter?.helpers}\n\n`).join("");
|
|
272
315
|
|
|
@@ -283,13 +326,16 @@ function renderConfig(
|
|
|
283
326
|
"/** The assistant's name: what the persona calls it, and every text the model reads. */",
|
|
284
327
|
`const name = ${JSON.stringify(assistantName)};`,
|
|
285
328
|
"",
|
|
329
|
+
MAIN_MODEL,
|
|
330
|
+
"",
|
|
286
331
|
`${helpers}export default defineMercuryConfig({`,
|
|
287
332
|
...renderPersona(role),
|
|
288
333
|
plugins,
|
|
289
334
|
channelList,
|
|
290
335
|
...(auth ? [` auth: ${auth.exportName},`] : []),
|
|
291
336
|
...(directory ? [` directory: ${directory.exportName},`] : []),
|
|
292
|
-
|
|
337
|
+
VECTOR_INDEX_SECTION,
|
|
338
|
+
MODELS_SECTION,
|
|
293
339
|
" i18n: { locales: { en } },",
|
|
294
340
|
TRACES_SECTION,
|
|
295
341
|
VERSION_COMMENT,
|
|
@@ -332,7 +378,7 @@ function renderPackageJson(
|
|
|
332
378
|
directory: CatalogEntry | undefined,
|
|
333
379
|
): string {
|
|
334
380
|
const chosen = [...channels, ...(auth ? [auth] : []), ...tools, ...(directory ? [directory] : [])];
|
|
335
|
-
const packages = [...new Set(["@mercury-fw/core", TRACE_SINK_PACKAGE, ...chosen.flatMap(entryPackages)])];
|
|
381
|
+
const packages = [...new Set(["@mercury-fw/core", TRACE_SINK_PACKAGE, VECTOR_INDEX_PACKAGE, ...chosen.flatMap(entryPackages)])];
|
|
336
382
|
if (tools.some((t) => t.formatter)) {
|
|
337
383
|
packages.push("@mercury-fw/formatter");
|
|
338
384
|
}
|
|
@@ -410,6 +456,7 @@ function renderCompose(name: string, hasHttp: boolean, services: Array<{ name: s
|
|
|
410
456
|
" required: false",
|
|
411
457
|
" volumes:",
|
|
412
458
|
" - wiki-vault:/app/wiki-vault",
|
|
459
|
+
" - mercury-data:/app/data",
|
|
413
460
|
" # The login of a plugin's CLI, on a volume so what the CLI writes back",
|
|
414
461
|
" # (refreshed tokens) survives a redeploy: see README.md.",
|
|
415
462
|
" - cli-credentials:/home/mercury/.config",
|
|
@@ -429,6 +476,8 @@ function renderCompose(name: string, hasHttp: boolean, services: Array<{ name: s
|
|
|
429
476
|
"volumes:",
|
|
430
477
|
" wiki-vault:",
|
|
431
478
|
` name: ${name}_wiki-vault`,
|
|
479
|
+
" mercury-data:",
|
|
480
|
+
` name: ${name}_mercury-data`,
|
|
432
481
|
" qdrant-data:",
|
|
433
482
|
` name: ${name}_qdrant-data`,
|
|
434
483
|
" cli-credentials:",
|
package/src/versions.ts
CHANGED
|
@@ -7,7 +7,13 @@
|
|
|
7
7
|
import pkg from "../package.json";
|
|
8
8
|
|
|
9
9
|
/** The framework packages a new app can depend on: always at the CLI's version. */
|
|
10
|
-
export const FRAMEWORK_PACKAGES = [
|
|
10
|
+
export const FRAMEWORK_PACKAGES = [
|
|
11
|
+
"@mercury-fw/cli",
|
|
12
|
+
"@mercury-fw/core",
|
|
13
|
+
"@mercury-fw/formatter",
|
|
14
|
+
"@mercury-fw/store-qdrant",
|
|
15
|
+
"@mercury-fw/trace-jsonl",
|
|
16
|
+
];
|
|
11
17
|
|
|
12
18
|
/** The registry asked when `MFW_REGISTRY` doesn't name another. */
|
|
13
19
|
export const DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
package/template/Dockerfile.tpl
CHANGED
|
@@ -32,8 +32,8 @@ COPY --chown=mercury:mercury src ./src
|
|
|
32
32
|
|
|
33
33
|
# Mount points of the named volumes, owned by the runtime user before a fresh
|
|
34
34
|
# volume attaches (a new volume takes the ownership it finds here).
|
|
35
|
-
RUN mkdir -p /app/wiki-vault /home/mercury/.config \
|
|
36
|
-
&& chown mercury:mercury /app/wiki-vault \
|
|
35
|
+
RUN mkdir -p /app/wiki-vault /app/data /home/mercury/.config \
|
|
36
|
+
&& chown mercury:mercury /app/wiki-vault /app/data \
|
|
37
37
|
&& chown -R mercury:mercury /home/mercury
|
|
38
38
|
|
|
39
39
|
USER mercury
|