mirror-mind 0.38.0 → 0.39.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/bin/loader.js +41 -20
- package/docs/releases/index.md +2 -0
- package/docs/releases/v0.33.3.md +9 -1
- package/docs/releases/v0.38.1.md +152 -0
- package/docs/releases/v0.39.0.md +110 -0
- package/lib/desktop.js +72 -0
- package/package.json +6 -1
- package/plugins/mirror-mind/.claude-plugin/plugin.json +1 -1
- package/plugins/mirror-mind/plugin.json +1 -1
- package/ts/src/conversation/extraction.ts +2 -2
- package/ts/src/conversation/hostCalls.ts +204 -0
- package/ts/src/conversation/hostReads.ts +126 -0
- package/ts/src/conversation/learnFromConversations.ts +405 -0
- package/ts/src/db/database.ts +79 -0
- package/ts/src/db/migrateHome.ts +153 -0
- package/ts/src/db/migrateOnOpen.ts +7 -3
- package/ts/src/db/migrationReport.ts +82 -0
- package/ts/src/frontDoor/cli.ts +32 -103
- package/ts/src/frontDoor/consultRoute.ts +4 -2
- package/ts/src/frontDoor/dbPath.ts +80 -7
- package/ts/src/frontDoor/initRoute.ts +190 -0
- package/ts/src/frontDoor/mirrorHomes.ts +95 -0
- package/ts/src/frontDoor/namedRefusal.ts +29 -1
- package/ts/src/frontDoor/runtimeRoute.ts +11 -6
- package/ts/src/frontDoor/searchRoute.ts +31 -3
- package/ts/src/frontDoor/usage.ts +17 -3
- package/ts/src/guards/packContents.ts +3 -0
- package/ts/src/init/createHome.ts +136 -0
- package/ts/src/init/init.ts +2 -1
- package/ts/src/journey/journeyCalls.ts +516 -0
- package/ts/src/journey/journeyTree.ts +191 -0
- package/ts/src/mirror/deliverContext.ts +239 -0
- package/ts/src/providers/config.ts +88 -1
- package/ts/src/providers/embedding.ts +26 -3
- package/ts/src/providers/familyProviders.ts +43 -0
- package/ts/src/runtime/diagnose.ts +28 -1
- package/ts/src/runtime/git.ts +153 -42
- package/ts/src/runtime/status.ts +27 -17
- package/ts/src/runtime/strategies/clone.ts +76 -55
- package/ts/src/runtime/strategies/package.ts +26 -7
- package/ts/src/runtime/update.ts +2 -1
- package/ts/src/search/memorySearch.ts +4 -2
package/bin/loader.js
CHANGED
|
@@ -24,34 +24,52 @@
|
|
|
24
24
|
// stands on; `scripts/smoke_npm_package.sh` runs both bins from a real global
|
|
25
25
|
// install on CI's Node 24.
|
|
26
26
|
//
|
|
27
|
-
// The warning filter
|
|
27
|
+
// The warning filter is the shim's own copy, because the hook emits its
|
|
28
28
|
// ExperimentalWarning before the core's filter can be imported through it.
|
|
29
|
+
// `boot` installs it; `registerTypeStripping` alone does not (CV24.DS1).
|
|
29
30
|
|
|
30
31
|
import { readFileSync } from "node:fs";
|
|
31
32
|
import { registerHooks, stripTypeScriptTypes } from "node:module";
|
|
32
33
|
import { fileURLToPath } from "node:url";
|
|
33
34
|
|
|
34
|
-
process.removeAllListeners("warning");
|
|
35
|
-
process.on("warning", (warning) => {
|
|
36
|
-
if (warning.name === "ExperimentalWarning") return;
|
|
37
|
-
process.stderr.write(`${warning.name}: ${warning.message}\n`);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
35
|
const packageRoot = new URL("../", import.meta.url).href;
|
|
36
|
+
const REGISTERED = Symbol.for("mirror-mind.typeStripping");
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Register the load hook that strips types for `.ts` files under this package
|
|
40
|
+
* root. Idempotent per package root, so the bins and the library entry
|
|
41
|
+
* (`lib/desktop.js`, CV24.DS1) can both call it in one process.
|
|
42
|
+
*
|
|
43
|
+
* It does nothing to the process's warnings: a library entry runs inside a
|
|
44
|
+
* host that owns them. Only `boot`, which owns its process, filters them.
|
|
45
|
+
*/
|
|
46
|
+
export function registerTypeStripping() {
|
|
47
|
+
if (!globalThis[REGISTERED]) globalThis[REGISTERED] = new Set();
|
|
48
|
+
const roots = globalThis[REGISTERED];
|
|
49
|
+
if (roots.has(packageRoot)) return;
|
|
50
|
+
roots.add(packageRoot);
|
|
51
|
+
registerHooks({
|
|
52
|
+
load(url, context, nextLoad) {
|
|
53
|
+
if (url.startsWith(packageRoot) && url.endsWith(".ts")) {
|
|
54
|
+
const source = readFileSync(fileURLToPath(url), "utf8");
|
|
55
|
+
return {
|
|
56
|
+
format: "module",
|
|
57
|
+
source: stripTypeScriptTypes(source, { mode: "strip" }),
|
|
58
|
+
shortCircuit: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return nextLoad(url, context);
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function filterExperimentalWarnings() {
|
|
67
|
+
process.removeAllListeners("warning");
|
|
68
|
+
process.on("warning", (warning) => {
|
|
69
|
+
if (warning.name === "ExperimentalWarning") return;
|
|
70
|
+
process.stderr.write(`${warning.name}: ${warning.message}\n`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
55
73
|
|
|
56
74
|
/**
|
|
57
75
|
* Import the entry at `entryPath` (relative to the package root) and run its
|
|
@@ -62,6 +80,9 @@ registerHooks({
|
|
|
62
80
|
* @returns {Promise<number>}
|
|
63
81
|
*/
|
|
64
82
|
export async function boot(entryPath) {
|
|
83
|
+
// The filter first: registering the hook emits its own ExperimentalWarning.
|
|
84
|
+
filterExperimentalWarnings();
|
|
85
|
+
registerTypeStripping();
|
|
65
86
|
const { runAsEntry } = await import(new URL(entryPath, packageRoot).href);
|
|
66
87
|
return runAsEntry();
|
|
67
88
|
}
|
package/docs/releases/index.md
CHANGED
|
@@ -12,6 +12,8 @@ Future release notes should use the structure defined in [Release Notes](../proc
|
|
|
12
12
|
|
|
13
13
|
## Releases
|
|
14
14
|
|
|
15
|
+
- [v0.39.0 — The Core as a Library](v0.39.0.md) — minor release of CV24's first eight Delivery Stories: `mirror-mind/desktop`, a library entry through which a host such as Mirror Desktop 0.3 embeds this core; calls for a home's migration state, Conversations, sidebar reads, home creation, migration on request, Mirror's context, learning from open Conversations, and Journey administration, each with an explicit path and no environment. The new `exports` field closes every other deep import. No migration.
|
|
16
|
+
- [v0.38.1 — The Refusal Names the Way Out](v0.38.1.md) — patch release of three Change Requests, CR185, CR186, and CR187: a package with no home configured names the homes it finds and the `mirror init <name>` that selects one, and `init` adopts a home that already exists; a missing or refused OpenRouter key is one line that names the file, before anything is touched; a `stable` clone ahead only by `main`'s commits is told that `stable` is behind it, not to push or reset. No migration.
|
|
15
17
|
- [v0.38.0 — The Model Behind the Memories, Chosen by Measurement](v0.38.0.md) — minor release closing CV9.DS9: the model behind memories, tasks, titles, summaries, tags, reception, journal, shadow, and consolidation is Claude Haiku 5.5 at reasoning off, chosen on the eval suite with reasoning on and off and on 24 of the owner's own conversations ranked blind, where it came first on 16 of 26 surfaces. Every extraction-pin call can carry a reasoning effort, and a pin carries its own default. With CR180, summaries open with their subject, every lifecycle prompt sees the owner's name, and a framed opening is written once more. Release gate 70 of 70 probes. Rollback is one env line. No migration.
|
|
16
18
|
- [v0.37.0 — The Mirror Stays in the Conversation](v0.37.0.md) — minor release closing CV9.DS8 and, with CV21.DS11, CV21: on Pi, Claude Code, Codex, and Antigravity one doorman in the core puts Mirror's context before the model on every turn of Mirror Mode, all of it on the turn Mirror Mode starts and then the persona and memories each question calls for; an explicit turn loads once; one signature, `◇ persona-name`; Pi reaches L4, measured live. One parity matrix in the runtime interface contract, sourced and held by a test. Codex, Antigravity, and Grok Build need their plugin copy brought level. Migration 018, applied on its own.
|
|
17
19
|
- [v0.36.3 — A Skill You Invoke Is Not Something You Said](v0.36.3.md) — patch release of one Change Request, CR160, with CR159's docs: on Pi, a skill picked from the menu or typed is no longer stored as the person's words, and neither is its reply; a question asked with `/mm-mirror` is stored as the question. Pi's turns go through the rule the other runtimes have. `mirror conversations skill-turns` repairs what earlier versions stored, only on `--apply` and after a verified backup. No migration.
|
package/docs/releases/v0.33.3.md
CHANGED
|
@@ -111,7 +111,8 @@ hand:
|
|
|
111
111
|
|
|
112
112
|
```bash
|
|
113
113
|
# a clone: in the checkout
|
|
114
|
-
git
|
|
114
|
+
git fetch origin
|
|
115
|
+
git merge --ff-only origin/stable # origin/main if the clone follows main
|
|
115
116
|
# a package
|
|
116
117
|
npm install -g mirror-mind@latest
|
|
117
118
|
```
|
|
@@ -124,6 +125,13 @@ mirror runtime update # or: npm install -g mirror-mind
|
|
|
124
125
|
|
|
125
126
|
There is no database migration and no runtime wiring change.
|
|
126
127
|
|
|
128
|
+
*Corrected on 2026-10-29:* the clone line first read `git pull --ff-only`. A clone follows
|
|
129
|
+
`stable` unless you chose `main`. On `main`, that command pulls past the release, so the
|
|
130
|
+
next `runtime update` refused with `plan: local commits present` (CR186). If that happened
|
|
131
|
+
to you, your checkout is fine. Every later release contains the commits it pulled, so
|
|
132
|
+
`runtime update` works again from the next release on. `mirror runtime channel main`
|
|
133
|
+
follows `main` instead.
|
|
134
|
+
|
|
127
135
|
## Conscious Exclusions
|
|
128
136
|
|
|
129
137
|
- **A Windows product.** REFERENCE.md's platform envelope stands: POSIX. The Windows
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
---
|
|
2
|
+
digest: >
|
|
3
|
+
v0.38.1 is a patch release of three Change Requests, CR185, CR186, and CR187, two of
|
|
4
|
+
them from Leandro Garcia's report on PR #42. Where Mirror refused to go on, it now
|
|
5
|
+
says what to do next. A package with no home configured names the homes it finds and
|
|
6
|
+
the `mirror init <name>` that selects one, and `init` adopts a home that already
|
|
7
|
+
exists. A missing or refused OpenRouter key is one line that names the file the key
|
|
8
|
+
goes in, said before anything is touched. A `stable` clone that took `main`'s commits
|
|
9
|
+
early is told that `stable` is behind it, not to push or reset. No migration.
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# v0.38.1 — The Refusal Names the Way Out
|
|
13
|
+
|
|
14
|
+
**Date:** 2026-10-30
|
|
15
|
+
|
|
16
|
+
## Highlights
|
|
17
|
+
|
|
18
|
+
- **A package with no home configured says where it looks and what to run**
|
|
19
|
+
([CR185](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr185-a-package-with-no-home-configured-does-not-say-where-it-looks.md)).
|
|
20
|
+
The error names the homes it finds under `~/.mirror-minds`, the `mirror init <name>`
|
|
21
|
+
that selects one, and the file that command writes. `mirror init <name>` now adopts a
|
|
22
|
+
home that already exists, changing nothing in it, so moving from a clone to the
|
|
23
|
+
package is one command. The bare `mirror` names the home too.
|
|
24
|
+
- **A missing OpenRouter key stops the command before it touches anything**
|
|
25
|
+
([CR187](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr187-a-missing-openrouter-key-crashes-three-commands-with-a-stack-trace-that-names-no-file.md)).
|
|
26
|
+
The six commands that cannot work without it (`consult`, `journal`, `week plan`,
|
|
27
|
+
`descriptor generate`, `consolidate scan`, and `shadow scan`) refuse in one line that
|
|
28
|
+
says what was not done and where the key goes, and exit 2. `journal` says the entry
|
|
29
|
+
was not saved, instead of crashing after it had copied the database into a snapshot.
|
|
30
|
+
A key that OpenRouter refuses is one line too. `runtime status`, `runtime diagnose`,
|
|
31
|
+
and a degraded search say when the key is missing.
|
|
32
|
+
- **A `stable` clone ahead only by `main`'s commits is told that `stable` is behind it**
|
|
33
|
+
([CR186](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr186-runtime-update-reads-main-s-commits-as-local-work.md)).
|
|
34
|
+
The ways out are `runtime channel main` or the next release, in the words a package
|
|
35
|
+
install already used. It is no longer told to push commits the remote already has, or
|
|
36
|
+
to reset its code below a database that may be newer than `stable`'s. Real local work
|
|
37
|
+
keeps the push-or-reset refusal.
|
|
38
|
+
- No migration, and no model or prompt change.
|
|
39
|
+
|
|
40
|
+
## Where We Started
|
|
41
|
+
|
|
42
|
+
v0.33.3 fixed installs under a non-ASCII path, which Leandro Garcia reproduced on PR #42.
|
|
43
|
+
On 2026-10-17 he came back with two bumps he met while following the update
|
|
44
|
+
instructions:
|
|
45
|
+
|
|
46
|
+
- Moving to the package, `runtime update` stopped with "Mirror home is not configured"
|
|
47
|
+
and did not say where the package looks.
|
|
48
|
+
- On a clone, the release notes' manual step, `git pull --ff-only`, pulled `main` past
|
|
49
|
+
the release. The next `runtime update` refused with `local commits present` and
|
|
50
|
+
"push or reset", for commits that were not his.
|
|
51
|
+
|
|
52
|
+
The answer came on 2026-10-29, twelve days late. The v0.33.3 notes were corrected to
|
|
53
|
+
`git fetch origin` and `git merge --ff-only origin/stable`. CR185's review turned up a
|
|
54
|
+
third refusal of the same kind: with no `OPENROUTER_API_KEY`, `consult`, `journal`, and
|
|
55
|
+
`week plan` crashed with a stack trace that named no file, and `journal` lost the entry.
|
|
56
|
+
|
|
57
|
+
## What Changed
|
|
58
|
+
|
|
59
|
+
**A home the error can name (CR185).** One module now owns the homes root,
|
|
60
|
+
`~/.mirror-minds`, and decides what counts as a home there: a directory with a non-empty
|
|
61
|
+
`identity/` or a `memory*.db` file. The not-configured error is one line everywhere it
|
|
62
|
+
appears. It ends with the command that finishes the job:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
Mirror home is not configured. To use the home at <root>/<name>, run: mirror init <name> (it writes MIRROR_USER=<name> to <config>).
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
There are forms for several homes, for a home whose name `init` would refuse, and for
|
|
69
|
+
none. `init <name>` on an existing home copies nothing and changes nothing in it. It
|
|
70
|
+
writes `MIRROR_USER` to an owner-only config file when no home is selected, and prints
|
|
71
|
+
only what is left to do: the key, `mirror seed` when there is no database, and the
|
|
72
|
+
runtime wiring. Selecting the only home automatically was considered and refused. A
|
|
73
|
+
silent fallback once caused the wrong-session export incident, so one explicit command,
|
|
74
|
+
whose effect is kept in a file, is the honest minimum.
|
|
75
|
+
|
|
76
|
+
**A key checked before anything is touched (CR187).** The key used to be read at the
|
|
77
|
+
first model call, so a failing command could already have done work. A keyless
|
|
78
|
+
`journal` copied the whole database into its pre-write snapshot, then crashed. Now the
|
|
79
|
+
six commands that cannot work without the key check it before the database opens. Any
|
|
80
|
+
other command that meets a missing key is answered by the front door's handler: one
|
|
81
|
+
line, no stack trace. All the sentences come from one place and name the file the
|
|
82
|
+
install reads its configuration from. When that file does not exist yet, the line says
|
|
83
|
+
to create it readable only by its owner. A key that OpenRouter refuses (HTTP 401 or 403)
|
|
84
|
+
gets its own line, whether a chat call or an embedding call met it. `consolidate apply`
|
|
85
|
+
is not checked up front, because only a merge proposal embeds.
|
|
86
|
+
|
|
87
|
+
**The channel behind the clone (CR186).** The plan stage of `runtime update` counted
|
|
88
|
+
every commit in `origin/stable..HEAD` as the person's own. Releases only fast-forward
|
|
89
|
+
`stable` to `main`, so one more local count tells the two cases apart. When every commit
|
|
90
|
+
past `stable` is already on `origin/main`, the clone's channel is behind it. The update
|
|
91
|
+
then refuses the way the package lane refuses a channel behind the installed version,
|
|
92
|
+
and in its words: "A downgrade is refused: migrations do not run backwards.", then
|
|
93
|
+
`runtime channel main`, or staying until the next release. The captured Change Request
|
|
94
|
+
also offered a reset to `origin/stable`; it was dropped. `main`'s code can migrate the
|
|
95
|
+
database when it opens it, and `stable`'s core refuses a database newer than itself. One
|
|
96
|
+
function now reads where a clone stands, for both the update plan and the welcome card,
|
|
97
|
+
which had counted two different ways. A count that cannot run reads as local work, so
|
|
98
|
+
real work is never mistaken for published work.
|
|
99
|
+
|
|
100
|
+
**Reviewed before code, validated by hand.** Each plan was reviewed through persona
|
|
101
|
+
lenses before any code was written: five for CR185, four each for CR186 and CR187. The
|
|
102
|
+
Navigator validated each change on a natural route: CR185 and CR187 as a package user,
|
|
103
|
+
the lane Leandro was on, and CR186 in a scratch clone ahead of `stable`. CR186's reviews
|
|
104
|
+
turned its plan around. It had proposed reporting the clone as up to date, until the
|
|
105
|
+
engineer found the package lane already refusing the same state.
|
|
106
|
+
|
|
107
|
+
## Upgrading
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
mirror runtime update # or: npm install -g mirror-mind@latest
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
No migration, and nothing to configure. Moving from a clone to the package with a home
|
|
114
|
+
that already exists: `mirror init <your-name>` now selects it without changing anything
|
|
115
|
+
in it, and says what is left, such as the key. No skill, plugin, or extension changed in
|
|
116
|
+
this release, so a reinstall line the updater prints for Codex, Antigravity, or Grok
|
|
117
|
+
Build only moves the version number.
|
|
118
|
+
|
|
119
|
+
## Conscious Exclusions
|
|
120
|
+
|
|
121
|
+
- Transport failures other than a refused key (a timeout, an outage, a rate limit, a
|
|
122
|
+
server error) still crash the commands that call a model with a stack trace, and
|
|
123
|
+
`journal` still loses the entry
|
|
124
|
+
([CR188](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr188-a-provider-outage-crashes-the-commands-that-call-a-model-with-a-stack-trace.md)).
|
|
125
|
+
- Extraction at session end with no key is unchanged, and a configuration error still
|
|
126
|
+
counts as an attempt, which can quarantine a conversation
|
|
127
|
+
([CR178](../project/refinement/rs010-cv22-oracle-and-port-hygiene/cr178-a-configuration-error-counts-as-an-extraction-attempt-and-nothing-releases-a-quarantine.md)).
|
|
128
|
+
- With a wrong key, `descriptor generate` still prints "skipped (empty response)" for each
|
|
129
|
+
persona. That line is golden-graded.
|
|
130
|
+
- `runtime update --check` still reports `local_ahead` for a clone ahead of `stable` by
|
|
131
|
+
`main`'s commits, where a package reports `channel_behind`.
|
|
132
|
+
- A `main`-channel clone ahead by commits on another pushed branch keeps the
|
|
133
|
+
push-or-reset refusal.
|
|
134
|
+
|
|
135
|
+
## What We Learned
|
|
136
|
+
|
|
137
|
+
A validation route is code too, and it has to be run before it is handed over. CR186's
|
|
138
|
+
route, run first by the agent, never reached the plan: a `node_modules` symlink made the
|
|
139
|
+
scratch tree dirty, because `.gitignore`'s `node_modules/` matches only a directory.
|
|
140
|
+
CR187's first route could not prove that no snapshot was taken, because `ls -l` shows
|
|
141
|
+
minutes. Both were caught before the Navigator ran them.
|
|
142
|
+
|
|
143
|
+
CI runs the suite under `MEMORY_ENV=test`. CR185's first push failed a test that had
|
|
144
|
+
assumed `memory.db`. The suite is now run that way before every push.
|
|
145
|
+
|
|
146
|
+
The same state deserves the same words. CR186's answer was already written, in the
|
|
147
|
+
package lane's refusal of a channel behind the installed version.
|
|
148
|
+
|
|
149
|
+
## Next Horizon
|
|
150
|
+
|
|
151
|
+
CR188, the transport failures, is the next refusal of this kind. CV9, Mirror Mind 1.0,
|
|
152
|
+
continues, and when its done condition is met the release is `v1.0.0`.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
digest: >
|
|
3
|
+
v0.39.0 is a minor release that ships CV24's first eight Delivery Stories: a library
|
|
4
|
+
entry, `mirror-mind/desktop`, through which a host such as Mirror Desktop 0.3 embeds
|
|
5
|
+
this core in its own Node process instead of spawning a CLI. Twenty-five calls and refusal
|
|
6
|
+
classes, beside `DESKTOP_API_VERSION`, cover a
|
|
7
|
+
home's migration state, Conversations, sidebar reads, home creation, migration on
|
|
8
|
+
request, Mirror's context for a turn, learning from open Conversations, and Journey
|
|
9
|
+
administration, each taking an explicit path and reading no environment. The package
|
|
10
|
+
gains an `exports` field, which closes every deep import other than the new entry. The
|
|
11
|
+
terminal, the bins and the hooks behave as before. No migration, and no model or prompt
|
|
12
|
+
change.
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# v0.39.0 — The Core as a Library
|
|
16
|
+
|
|
17
|
+
**Date:** 2026-10-31
|
|
18
|
+
|
|
19
|
+
## Highlights
|
|
20
|
+
|
|
21
|
+
- **`mirror-mind/desktop`, a library entry for a host**
|
|
22
|
+
([CV24](../project/roadmap/cv24-embeddable-core-for-mirror-desktop/index.md)). Imported by
|
|
23
|
+
package name from a host's `node_modules`, with no flags: the entry registers the bins'
|
|
24
|
+
type-stripping hook itself. Everything a host may call is re-exported there by name, with
|
|
25
|
+
`DESKTOP_API_VERSION = 1`; a name not in that file is not part of the contract.
|
|
26
|
+
- **Eight stories, one rule.** Every call takes an explicit database or home path, never reads
|
|
27
|
+
`MIRROR_HOME`, `MIRROR_USER`, `DB_PATH` or a key from the environment, never migrates as a side
|
|
28
|
+
effect, and refuses a database that is not `current` before opening it for writing.
|
|
29
|
+
- **Custody chosen per write, with a decision for each.** Conversation writes go through a
|
|
30
|
+
narrowed handle instead of a per-write snapshot; a home is created beside its target and renamed
|
|
31
|
+
into place; a home migrates only behind a uniquely named, verified backup the core never deletes.
|
|
32
|
+
- **Breaking for deep imports only.** The package now declares `exports` with `./desktop` and
|
|
33
|
+
`./package.json`. Any other `import "mirror-mind/…"` path that resolved before no longer does.
|
|
34
|
+
The `mirror` and `mirror-hook` bins are unaffected.
|
|
35
|
+
- No migration, and no model or prompt change, so the model-behavior release gate does not apply.
|
|
36
|
+
|
|
37
|
+
## Where We Started
|
|
38
|
+
|
|
39
|
+
Decision CV22.DS10.TS1 left Mirror Desktop outside the TypeScript migration: it kept calling the
|
|
40
|
+
Python core through the retired Journey administration and a catalog script. On 2026-10-28 Mirror
|
|
41
|
+
Desktop decided to follow this core instead. It froze its 0.2 line on Python and opened a 0.3 line
|
|
42
|
+
that hosts the core in its own Node process, on its own home.
|
|
43
|
+
|
|
44
|
+
That host could not use the CLI's way in. It runs from `node_modules`, where Node refuses to strip
|
|
45
|
+
TypeScript types. A shell that starts it may export another home's `MIRROR_HOME`. It writes on every
|
|
46
|
+
turn of a Conversation that never ends. And it has to ask the person before a home is migrated,
|
|
47
|
+
while every open through the front door migrates first.
|
|
48
|
+
|
|
49
|
+
## What Changed
|
|
50
|
+
|
|
51
|
+
The contract grew one story at a time on `desktop/library-seam-1a`, each with its spec first, and
|
|
52
|
+
merged as PR #52.
|
|
53
|
+
|
|
54
|
+
- **DS1, inspect a home without migrating.** `inspectMigrationState` reports `missing`,
|
|
55
|
+
`not_mirror`, `current`, `pending` or `ahead`, with the pending list. It shares its three
|
|
56
|
+
ledger readers with `ensureMigratedOnOpen`, so what it calls pending is exactly what an open
|
|
57
|
+
would apply. The loader became `boot` plus an idempotent `registerTypeStripping`; only `boot`,
|
|
58
|
+
which owns its process, filters warnings.
|
|
59
|
+
- **DS2, Conversations as calls.** `ensureConversation`, `appendConversationMessages` (the
|
|
60
|
+
`conversations append` contract, byte for byte), `renameConversation` and
|
|
61
|
+
`rebindConversationJourney`, written through `openDatabaseForConversationWrites`. That handle
|
|
62
|
+
prepares only a single SELECT, INSERT … VALUES into three tables, and an UPDATE of four
|
|
63
|
+
`conversations` columns by id. The per-write `VACUUM INTO` measured about 0.9 s on a 108 MB
|
|
64
|
+
home, too much for every turn.
|
|
65
|
+
- **DS3, Journey reads.** `readJourneyTree` (a new contract in the stored sibling order, not a
|
|
66
|
+
revival of the retired registry dump), `listJourneyConversations` and `inspectConversation`,
|
|
67
|
+
read-only.
|
|
68
|
+
- **DS4, create a home.** `createHome` copies the templates with the person's name, bootstraps,
|
|
69
|
+
and seeds in a staging directory, renaming it into place only when the home reads `current`.
|
|
70
|
+
It never writes `~/.config/mirror/env`.
|
|
71
|
+
- **DS5, migrate on request.** `migrateHome` applies exactly the pending list the person was
|
|
72
|
+
shown, under the bootstrap lock, behind a backup that is quick-checked, hashed, and read back as
|
|
73
|
+
the same pre-image.
|
|
74
|
+
- **DS6, Mirror's context as a call.** `deliverMirrorContext` is the doorman for a host's turn,
|
|
75
|
+
with the key as an argument and Mirror Mode from the first turn; each provider call is tried
|
|
76
|
+
once within its own bound. Its write is `mirror doorman`'s, behind the front door's snapshot.
|
|
77
|
+
- **DS7, learning from an open Conversation.** `learnFromConversations` extracts the stretch an
|
|
78
|
+
idle, unended Conversation gained since its `learned_through` watermark, in bounded pieces,
|
|
79
|
+
with the terminal's prompts, and never ends it.
|
|
80
|
+
- **DS8, Journey administration as calls.** `addJourney`, `setJourneyProjectPath`, `moveJourney`
|
|
81
|
+
and `deleteJourney` port Python 0.31.14's administration, so the requests Desktop 0.2 sent keep
|
|
82
|
+
their meaning. `updateJourney` is new: it edits a Journey's name, description and status, and
|
|
83
|
+
refuses `journey_changed` when the row changed since the host read it.
|
|
84
|
+
|
|
85
|
+
Existing code changed only where the entry needed it: the loader, three exported readers, two
|
|
86
|
+
`export` keywords in `extraction.ts`, the tarball guard (`lib/` allowed, `lib/desktop.js`
|
|
87
|
+
required), and `package.json`. No existing test changed. The npm smoke installs the package and
|
|
88
|
+
imports the entry from a host project for DS1 to DS5.
|
|
89
|
+
|
|
90
|
+
## Conscious Exclusions
|
|
91
|
+
|
|
92
|
+
- **Renaming a Journey's slug.** The slug keys Conversations, memories and tasks; renaming it is a
|
|
93
|
+
migration, not a call.
|
|
94
|
+
- **A Journey's icon and color, and a receipt ledger for Journey calls.**
|
|
95
|
+
- **The front door's own migration custody.** `ensureMigratedOnOpen` still overwrites one fixed
|
|
96
|
+
backup; giving it DS5's custody is a separate decision.
|
|
97
|
+
- **Installed-package smoke for DS6 to DS8.** DS6 and DS7 need a provider key. DS8 is exercised
|
|
98
|
+
end to end by Mirror Desktop's real-host test; a keyless smoke probe is a cheap follow-up.
|
|
99
|
+
|
|
100
|
+
## What We Learned
|
|
101
|
+
|
|
102
|
+
A library entry is a stricter consumer than a CLI. Every assumption the bins could make about
|
|
103
|
+
their own process — environment, warnings, migrate-on-open, a snapshot per write — had to become
|
|
104
|
+
an explicit argument or a decision. The retired-surfaces sweep also caught the port twice naming a
|
|
105
|
+
retired verb in comments, which says the sweep is doing its job on new files.
|
|
106
|
+
|
|
107
|
+
## Next Horizon
|
|
108
|
+
|
|
109
|
+
Mirror Desktop moves its pin from a branch tarball to this release, then its first 0.3 alpha. CV24
|
|
110
|
+
stays open for the pieces that alpha's use asks for.
|
package/lib/desktop.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// `mirror-mind/desktop` — the core's library entry for Mirror Desktop. CV24.DS1.
|
|
2
|
+
//
|
|
3
|
+
// Mirror Desktop hosts this core in its own Node process and imports it from
|
|
4
|
+
// `node_modules`, where Node refuses to strip TypeScript types. So this entry
|
|
5
|
+
// registers the same type-stripping hook the bins use, and only then imports
|
|
6
|
+
// the TypeScript modules -- dynamically, because a static `export ... from
|
|
7
|
+
// "./x.ts"` would be loaded before the hook exists.
|
|
8
|
+
//
|
|
9
|
+
// Everything Desktop may call is re-exported here BY NAME. A name missing from
|
|
10
|
+
// this file is not part of the contract, whatever the TypeScript tree holds.
|
|
11
|
+
// `DESKTOP_API_VERSION` changes when a name is removed or changes meaning.
|
|
12
|
+
|
|
13
|
+
import { registerTypeStripping } from "../bin/loader.js";
|
|
14
|
+
|
|
15
|
+
registerTypeStripping();
|
|
16
|
+
|
|
17
|
+
const migrationReport = await import("../ts/src/db/migrationReport.ts");
|
|
18
|
+
const conversations = await import("../ts/src/conversation/hostCalls.ts");
|
|
19
|
+
const reads = await import("../ts/src/conversation/hostReads.ts");
|
|
20
|
+
const homes = await import("../ts/src/init/createHome.ts");
|
|
21
|
+
const migration = await import("../ts/src/db/migrateHome.ts");
|
|
22
|
+
const context = await import("../ts/src/mirror/deliverContext.ts");
|
|
23
|
+
const learning = await import("../ts/src/conversation/learnFromConversations.ts");
|
|
24
|
+
const journeys = await import("../ts/src/journey/journeyCalls.ts");
|
|
25
|
+
|
|
26
|
+
export const DESKTOP_API_VERSION = 1;
|
|
27
|
+
export const inspectMigrationState = migrationReport.inspectMigrationState;
|
|
28
|
+
|
|
29
|
+
// CV24.DS2: Conversations as calls. Each opens only a `current` database,
|
|
30
|
+
// never migrates, and writes through the narrowed Conversation handle.
|
|
31
|
+
export const ensureConversation = conversations.ensureConversation;
|
|
32
|
+
export const appendConversationMessages = conversations.appendConversationMessages;
|
|
33
|
+
export const renameConversation = conversations.renameConversation;
|
|
34
|
+
export const rebindConversationJourney = conversations.rebindConversationJourney;
|
|
35
|
+
export const DatabaseNotCurrentError = conversations.DatabaseNotCurrentError;
|
|
36
|
+
export const ConversationCallRejectedError = conversations.ConversationCallRejectedError;
|
|
37
|
+
|
|
38
|
+
// CV24.DS3: the reads a sidebar is built from. Read-only, `current` homes only.
|
|
39
|
+
export const readJourneyTree = reads.readJourneyTree;
|
|
40
|
+
export const listJourneyConversations = reads.listJourneyConversations;
|
|
41
|
+
export const inspectConversation = reads.inspectConversation;
|
|
42
|
+
|
|
43
|
+
// CV24.DS4: create a complete, seeded home at an explicit path, all or nothing.
|
|
44
|
+
// Writes nothing outside it, never the machine-wide ~/.config/mirror/env.
|
|
45
|
+
export const createHome = homes.createHome;
|
|
46
|
+
export const CreateHomeRejectedError = homes.CreateHomeRejectedError;
|
|
47
|
+
|
|
48
|
+
// CV24.DS5: migrate a home on request, applying exactly the pending list the
|
|
49
|
+
// person was shown, behind a uniquely named, verified, retained backup.
|
|
50
|
+
export const migrateHome = migration.migrateHome;
|
|
51
|
+
export const MigrateHomeRejectedError = migration.MigrateHomeRejectedError;
|
|
52
|
+
|
|
53
|
+
// CV24.DS6: Mirror's context for a host's turn -- the doorman as a call. The
|
|
54
|
+
// key is an argument; nothing is read from the environment. A Conversation is
|
|
55
|
+
// in Mirror Mode from its first turn.
|
|
56
|
+
export const deliverMirrorContext = context.deliverMirrorContext;
|
|
57
|
+
export const forgetMirrorContext = context.forgetMirrorContext;
|
|
58
|
+
export const MirrorContextRejectedError = context.MirrorContextRejectedError;
|
|
59
|
+
|
|
60
|
+
// CV24.DS7: learn from a host's idle Conversations without ending them -- the
|
|
61
|
+
// stretch each gained since it was last learned from. The key is an argument.
|
|
62
|
+
export const learnFromConversations = learning.learnFromConversations;
|
|
63
|
+
export const LearningRejectedError = learning.LearningRejectedError;
|
|
64
|
+
|
|
65
|
+
// CV24.DS8: Journeys as calls -- create, edit, move, set a project path,
|
|
66
|
+
// delete -- each answering with the tree `readJourneyTree` reads.
|
|
67
|
+
export const addJourney = journeys.addJourney;
|
|
68
|
+
export const updateJourney = journeys.updateJourney;
|
|
69
|
+
export const setJourneyProjectPath = journeys.setJourneyProjectPath;
|
|
70
|
+
export const moveJourney = journeys.moveJourney;
|
|
71
|
+
export const deleteJourney = journeys.deleteJourney;
|
|
72
|
+
export const JourneyCallRejectedError = journeys.JourneyCallRejectedError;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mirror-mind",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.39.0",
|
|
4
4
|
"description": "Mirror Mind: a local-first memory and identity framework for agentic AI runtimes (Pi, Claude Code, Codex, Antigravity)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,8 +27,13 @@
|
|
|
27
27
|
"mirror": "bin/mirror.js",
|
|
28
28
|
"mirror-hook": "bin/mirror-hook.js"
|
|
29
29
|
},
|
|
30
|
+
"exports": {
|
|
31
|
+
"./desktop": "./lib/desktop.js",
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
30
34
|
"files": [
|
|
31
35
|
"bin",
|
|
36
|
+
"lib",
|
|
32
37
|
"ts/src",
|
|
33
38
|
"templates",
|
|
34
39
|
"docs/releases",
|
|
@@ -179,7 +179,7 @@ export async function runConversationExtraction(
|
|
|
179
179
|
return { memoryIds, taskIds, extracted: memoryIds.length };
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
async function persistExtractedTasks(
|
|
182
|
+
export async function persistExtractedTasks(
|
|
183
183
|
db: WritableDatabase,
|
|
184
184
|
llm: LlmProvider,
|
|
185
185
|
messages: readonly ExtractionMessage[],
|
|
@@ -295,7 +295,7 @@ function logEmbeddingAttempt(
|
|
|
295
295
|
return embeddingLedgerHook(db, { conversationId, now });
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
function insertMemory(
|
|
298
|
+
export function insertMemory(
|
|
299
299
|
db: WritableDatabase,
|
|
300
300
|
id: string,
|
|
301
301
|
conversationId: string,
|