sessionmail 0.3.1__tar.gz → 0.3.3__tar.gz
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.
- {sessionmail-0.3.1 → sessionmail-0.3.3}/.claude-plugin/plugin.json +1 -1
- {sessionmail-0.3.1 → sessionmail-0.3.3}/PKG-INFO +41 -17
- {sessionmail-0.3.1 → sessionmail-0.3.3}/README.md +39 -15
- {sessionmail-0.3.1 → sessionmail-0.3.3}/hooks/mail-guard/README.md +64 -8
- {sessionmail-0.3.1 → sessionmail-0.3.3}/pyproject.toml +3 -3
- {sessionmail-0.3.1 → sessionmail-0.3.3}/scripts/seat-agent/README.md +26 -8
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_archive.py +16 -11
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_boxes.py +197 -3
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_detect.py +36 -7
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_drop.py +6 -13
- sessionmail-0.3.3/src/sessionmail/_fresh.py +158 -0
- sessionmail-0.3.3/src/sessionmail/_frozen.py +899 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_index.py +74 -9
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_launchd.py +61 -20
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_layer.py +17 -3
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_maildir.py +183 -22
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_mhs.py +19 -1
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_migrate.py +17 -13
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_pull_agent.py +9 -3
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_pull_imap.py +19 -2
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_puller.py +121 -26
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_reach.py +32 -10
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_root.py +15 -3
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_seat.py +231 -21
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_send.py +8 -6
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_sender.py +48 -16
- sessionmail-0.3.3/src/sessionmail/_settings.py +1905 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_setup.py +295 -39
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_shorten.py +9 -2
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_subscribe.py +17 -5
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_subscription.py +21 -8
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_views.py +14 -1
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/hook_lines.toml +30 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/humans +1 -1
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mail.schema.json +45 -4
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/memory.py +38 -10
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/message.py +294 -47
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/mail-box.lib.sh +48 -5
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/mhs-thread +25 -5
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/session-mail-wake +331 -17
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/notmuch/notmuch-config.sessionkit.template +5 -3
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/notmuch/post-new +101 -12
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/notmuch/store-index +41 -6
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/pager.py +893 -104
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/postfix/delivery-check +76 -11
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/postfix/nm-deliver +29 -7
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/postfix/vmailbox-gen +185 -32
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/rules.py +172 -30
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/seat/seat-agent +31 -8
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/seat/seat-agent.plist.template +8 -2
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/seat_init.py +17 -5
- sessionmail-0.3.1/src/sessionmail/_settings.py +0 -825
- {sessionmail-0.3.1 → sessionmail-0.3.3}/.gitignore +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/LICENSE +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/plugins/pager-monitor/README.md +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/__init__.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_arm.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_birth.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_detect_imap.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_generator.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_headers.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_help.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_host.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_init.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_injectlog.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_magicdns.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_mcp.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_metrics.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_notmuch.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_operator.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_parsers.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_record.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_render.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_say.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_sig.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_stub.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_tokens.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_voice.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/_why.py +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/arm.txt +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/mail-ext.lib.sh +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/session-mail-cat +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/session-mail-ls +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/session-mail-run +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/session-mail-tree +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/mhs/session-mail-verify +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/notmuch/notmuch-config.store.template +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/postfix/magicdns-suffix +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/src/sessionmail/settings.template.json +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/tests/fixtures/sig_v2/README.md +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/tests/fixtures/sm827_hint_root/README.md +0 -0
- {sessionmail-0.3.1 → sessionmail-0.3.3}/tests/fixtures/sm827_honest/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: sessionmail
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: sessionmail - mail for coding agents: message, pager and memory over one maildir.
|
|
5
5
|
Project-URL: Homepage, https://github.com/sessionkit/sessionmail
|
|
6
6
|
Project-URL: Repository, https://github.com/sessionkit/sessionmail
|
|
@@ -14,7 +14,7 @@ Requires-Dist: cryptography>=42
|
|
|
14
14
|
Requires-Dist: markdown>=3.6
|
|
15
15
|
Requires-Dist: mcp>=2.2.0
|
|
16
16
|
Requires-Dist: pymdown-extensions>=10.0
|
|
17
|
-
Requires-Dist: sessiontools>=0.3.
|
|
17
|
+
Requires-Dist: sessiontools>=0.3.3
|
|
18
18
|
Description-Content-Type: text/markdown
|
|
19
19
|
|
|
20
20
|
# sessionmail
|
|
@@ -91,8 +91,7 @@ hook says so once per session rather than leaving you to guess.
|
|
|
91
91
|
name.
|
|
92
92
|
- Reading needs no server either: `message list`, `tree` and `read`, and
|
|
93
93
|
`pager`, read the maildir directly. `message mark` and `memory` read the
|
|
94
|
-
[notmuch][] index,
|
|
95
|
-
(below).
|
|
94
|
+
[notmuch][] index, and refresh it themselves before they read (below).
|
|
96
95
|
- **Mail across machines** goes through postfix, which is opt-in:
|
|
97
96
|
[postfix configuration][postfix-configuration].
|
|
98
97
|
|
|
@@ -112,9 +111,12 @@ message setup --box NAME # the mail root, the boxes, the notmuch profile
|
|
|
112
111
|
- It never touches your own notmuch config, never edits postfix, and
|
|
113
112
|
writes no settings key.
|
|
114
113
|
- **Setup runs `notmuch new --no-hooks` once**, on its own empty profile,
|
|
115
|
-
to build the index the hook needs.
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
to build the index the hook needs. It schedules nothing and installs no
|
|
115
|
+
job.
|
|
116
|
+
- **The readers keep the index fresh.** `memory` runs a bounded
|
|
117
|
+
`notmuch new --no-hooks` before it reads, and `message mark` runs one
|
|
118
|
+
when an id is missing. The bound is `notmuch.refresh`: 2 seconds by
|
|
119
|
+
default, and 0 turns it off: [notmuch][].
|
|
118
120
|
|
|
119
121
|
`message`, `pager` and `memory` all run; the specs, in the git repository
|
|
120
122
|
(the release archive leaves them out), say what each verb is meant to do
|
|
@@ -125,11 +127,25 @@ and `make progress` counts how much of it is built.
|
|
|
125
127
|
```sh
|
|
126
128
|
claude plugin uninstall sessionmail # skills, hooks, MCP entries
|
|
127
129
|
uv tool uninstall sessionmail # the three commands
|
|
128
|
-
rm
|
|
129
|
-
notmuch --config="$HOME/.config/notmuch/sessionkit/config" config set sessionmail.post_new_command # the optional command, off
|
|
130
|
-
rm ~/.config/notmuch/sessionkit/config # sessionmail's notmuch profile (its index stays in ~/.mail/.notmuch)
|
|
130
|
+
R="$(message path --root)"; rm "$R/.notmuch/hooks/post-new" "$R/.notmuch/hooks/post-insert" # the linked hook (in notmuch's database.hook_dir)
|
|
131
131
|
```
|
|
132
132
|
|
|
133
|
+
- sessionmail's notmuch profile (its index stays in the mail root's
|
|
134
|
+
`.notmuch`). A fresh host:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
notmuch --config="$HOME/.sessionkit/mail/settings/notmuch/sessionkit/config" config set sessionmail.post_new_command # the optional command, off
|
|
138
|
+
rm ~/.sessionkit/mail/settings/notmuch/sessionkit/config
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- A host that kept its older profile (or `~/.notmuch-config.sessionkit`,
|
|
142
|
+
if that is the file it has):
|
|
143
|
+
|
|
144
|
+
```sh
|
|
145
|
+
notmuch --config="$HOME/.config/notmuch/sessionkit/config" config set sessionmail.post_new_command # the optional command, off
|
|
146
|
+
rm ~/.config/notmuch/sessionkit/config
|
|
147
|
+
```
|
|
148
|
+
|
|
133
149
|
## Configuration
|
|
134
150
|
|
|
135
151
|
Which files, which environment variables, and where each one lives, one
|
|
@@ -141,8 +157,10 @@ component at a time: [configuration][configuration].
|
|
|
141
157
|
and the two maps the generator writes as you, in
|
|
142
158
|
`/usr/local/etc/postfix-agents/`.
|
|
143
159
|
- **notmuch**, the index: sessionmail's own profile,
|
|
144
|
-
`~/.
|
|
145
|
-
|
|
160
|
+
`~/.sessionkit/mail/settings/notmuch/sessionkit/config` on a fresh host.
|
|
161
|
+
An older `~/.config/notmuch/sessionkit/config` (or
|
|
162
|
+
`~/.notmuch-config.sessionkit`) is still read while it exists, and is
|
|
163
|
+
never moved. Your own `~/.notmuch-config` is never read.
|
|
146
164
|
- **Environment variables**: each settings key has one (the `sendmail`
|
|
147
165
|
key's is `SESSIONMAIL_SENDMAIL`). When it is set and not empty, it wins
|
|
148
166
|
over the file.
|
|
@@ -160,13 +178,14 @@ to record:
|
|
|
160
178
|
under `~/.session/seats/<seat>/`: each settings change for that session
|
|
161
179
|
or seat, such as a subscription or its address, with the command that
|
|
162
180
|
made it.
|
|
163
|
-
-
|
|
181
|
+
- `<mail root>/.state/regen-failures.log` (`message path --root`): each time the map generator
|
|
164
182
|
refused to rewrite the alias map, and why.
|
|
165
|
-
- `~/.
|
|
183
|
+
- `~/.sessionkit/mail/state/post-new/log` (`~/.sessionmail/post-new/log` on
|
|
184
|
+
an existing host where that folder is still real): the [post-new command][post-new-command]'s
|
|
166
185
|
runs and output, only once you set one. Past 1 MiB it becomes `log.1`.
|
|
167
186
|
|
|
168
187
|
Follow one as it grows with `tail -F`, for example
|
|
169
|
-
`tail -F
|
|
188
|
+
`tail -F "$(message path --root)/.state/regen-failures.log"`. `pager status` ends its
|
|
170
189
|
`today:` line with the path of this session's `pager.jsonl`.
|
|
171
190
|
|
|
172
191
|
- **Health:** `pager status`, run by the agent. `monitor: armed (pid N)`,
|
|
@@ -181,12 +200,15 @@ Follow one as it grows with `tail -F`, for example
|
|
|
181
200
|
Everything else is in `docs/`, one subject per file:
|
|
182
201
|
|
|
183
202
|
- [The pager][pager], the model in one page; [how `pager listen`
|
|
184
|
-
works][pager-listen-internals]; [the pager monitor][pager-monitor]
|
|
185
|
-
|
|
203
|
+
works][pager-listen-internals]; [the pager monitor][pager-monitor] and
|
|
204
|
+
its [frozen-turn watch][frozen-watch], off by default; [not running
|
|
205
|
+
under Claude Code][pager-outside-claude]; [what the pager
|
|
186
206
|
injects][pager-inject-log].
|
|
187
207
|
- Three hidden verbs: [`message seat`][message-seat], a seat with no Claude
|
|
188
208
|
session; [`message receipt`][message-receipt], what another session
|
|
189
209
|
recorded; and [`message setup`][message-setup], making a host ready.
|
|
210
|
+
- [Your first memory root, by hand][memory-first-root]: `memory` has no
|
|
211
|
+
automated first step yet.
|
|
190
212
|
- [Gating on `message verify --json`][verify-json-verdict]: the verdict as
|
|
191
213
|
one JSON object.
|
|
192
214
|
- [The writing model][session-model]: the signed `Session-Model:` header,
|
|
@@ -207,12 +229,14 @@ Everything else is in `docs/`, one subject per file:
|
|
|
207
229
|
[pager]: docs/pager.md
|
|
208
230
|
[pager-listen-internals]: docs/pager-listen-internals.md
|
|
209
231
|
[pager-monitor]: docs/pager-monitor.md
|
|
232
|
+
[frozen-watch]: docs/frozen-watch.md
|
|
210
233
|
[pager-outside-claude]: docs/pager-outside-claude.md
|
|
211
234
|
[pager-inject-log]: docs/pager-inject-log.md
|
|
212
235
|
[message-seat]: docs/message-seat.md
|
|
213
236
|
[session-model]: docs/session-model.md
|
|
214
237
|
[message-receipt]: docs/message-receipt.md
|
|
215
238
|
[message-setup]: docs/message-setup.md
|
|
239
|
+
[memory-first-root]: docs/memory-first-root.md
|
|
216
240
|
[verify-json-verdict]: docs/verify-json-verdict.md
|
|
217
241
|
[mail-delivery-paths]: docs/mail-delivery-paths.md
|
|
218
242
|
[postfix-configuration]: docs/postfix-configuration.md
|
|
@@ -72,8 +72,7 @@ hook says so once per session rather than leaving you to guess.
|
|
|
72
72
|
name.
|
|
73
73
|
- Reading needs no server either: `message list`, `tree` and `read`, and
|
|
74
74
|
`pager`, read the maildir directly. `message mark` and `memory` read the
|
|
75
|
-
[notmuch][] index,
|
|
76
|
-
(below).
|
|
75
|
+
[notmuch][] index, and refresh it themselves before they read (below).
|
|
77
76
|
- **Mail across machines** goes through postfix, which is opt-in:
|
|
78
77
|
[postfix configuration][postfix-configuration].
|
|
79
78
|
|
|
@@ -93,9 +92,12 @@ message setup --box NAME # the mail root, the boxes, the notmuch profile
|
|
|
93
92
|
- It never touches your own notmuch config, never edits postfix, and
|
|
94
93
|
writes no settings key.
|
|
95
94
|
- **Setup runs `notmuch new --no-hooks` once**, on its own empty profile,
|
|
96
|
-
to build the index the hook needs.
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
to build the index the hook needs. It schedules nothing and installs no
|
|
96
|
+
job.
|
|
97
|
+
- **The readers keep the index fresh.** `memory` runs a bounded
|
|
98
|
+
`notmuch new --no-hooks` before it reads, and `message mark` runs one
|
|
99
|
+
when an id is missing. The bound is `notmuch.refresh`: 2 seconds by
|
|
100
|
+
default, and 0 turns it off: [notmuch][].
|
|
99
101
|
|
|
100
102
|
`message`, `pager` and `memory` all run; the specs, in the git repository
|
|
101
103
|
(the release archive leaves them out), say what each verb is meant to do
|
|
@@ -106,11 +108,25 @@ and `make progress` counts how much of it is built.
|
|
|
106
108
|
```sh
|
|
107
109
|
claude plugin uninstall sessionmail # skills, hooks, MCP entries
|
|
108
110
|
uv tool uninstall sessionmail # the three commands
|
|
109
|
-
rm
|
|
110
|
-
notmuch --config="$HOME/.config/notmuch/sessionkit/config" config set sessionmail.post_new_command # the optional command, off
|
|
111
|
-
rm ~/.config/notmuch/sessionkit/config # sessionmail's notmuch profile (its index stays in ~/.mail/.notmuch)
|
|
111
|
+
R="$(message path --root)"; rm "$R/.notmuch/hooks/post-new" "$R/.notmuch/hooks/post-insert" # the linked hook (in notmuch's database.hook_dir)
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
- sessionmail's notmuch profile (its index stays in the mail root's
|
|
115
|
+
`.notmuch`). A fresh host:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
notmuch --config="$HOME/.sessionkit/mail/settings/notmuch/sessionkit/config" config set sessionmail.post_new_command # the optional command, off
|
|
119
|
+
rm ~/.sessionkit/mail/settings/notmuch/sessionkit/config
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
- A host that kept its older profile (or `~/.notmuch-config.sessionkit`,
|
|
123
|
+
if that is the file it has):
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
notmuch --config="$HOME/.config/notmuch/sessionkit/config" config set sessionmail.post_new_command # the optional command, off
|
|
127
|
+
rm ~/.config/notmuch/sessionkit/config
|
|
128
|
+
```
|
|
129
|
+
|
|
114
130
|
## Configuration
|
|
115
131
|
|
|
116
132
|
Which files, which environment variables, and where each one lives, one
|
|
@@ -122,8 +138,10 @@ component at a time: [configuration][configuration].
|
|
|
122
138
|
and the two maps the generator writes as you, in
|
|
123
139
|
`/usr/local/etc/postfix-agents/`.
|
|
124
140
|
- **notmuch**, the index: sessionmail's own profile,
|
|
125
|
-
`~/.
|
|
126
|
-
|
|
141
|
+
`~/.sessionkit/mail/settings/notmuch/sessionkit/config` on a fresh host.
|
|
142
|
+
An older `~/.config/notmuch/sessionkit/config` (or
|
|
143
|
+
`~/.notmuch-config.sessionkit`) is still read while it exists, and is
|
|
144
|
+
never moved. Your own `~/.notmuch-config` is never read.
|
|
127
145
|
- **Environment variables**: each settings key has one (the `sendmail`
|
|
128
146
|
key's is `SESSIONMAIL_SENDMAIL`). When it is set and not empty, it wins
|
|
129
147
|
over the file.
|
|
@@ -141,13 +159,14 @@ to record:
|
|
|
141
159
|
under `~/.session/seats/<seat>/`: each settings change for that session
|
|
142
160
|
or seat, such as a subscription or its address, with the command that
|
|
143
161
|
made it.
|
|
144
|
-
-
|
|
162
|
+
- `<mail root>/.state/regen-failures.log` (`message path --root`): each time the map generator
|
|
145
163
|
refused to rewrite the alias map, and why.
|
|
146
|
-
- `~/.
|
|
164
|
+
- `~/.sessionkit/mail/state/post-new/log` (`~/.sessionmail/post-new/log` on
|
|
165
|
+
an existing host where that folder is still real): the [post-new command][post-new-command]'s
|
|
147
166
|
runs and output, only once you set one. Past 1 MiB it becomes `log.1`.
|
|
148
167
|
|
|
149
168
|
Follow one as it grows with `tail -F`, for example
|
|
150
|
-
`tail -F
|
|
169
|
+
`tail -F "$(message path --root)/.state/regen-failures.log"`. `pager status` ends its
|
|
151
170
|
`today:` line with the path of this session's `pager.jsonl`.
|
|
152
171
|
|
|
153
172
|
- **Health:** `pager status`, run by the agent. `monitor: armed (pid N)`,
|
|
@@ -162,12 +181,15 @@ Follow one as it grows with `tail -F`, for example
|
|
|
162
181
|
Everything else is in `docs/`, one subject per file:
|
|
163
182
|
|
|
164
183
|
- [The pager][pager], the model in one page; [how `pager listen`
|
|
165
|
-
works][pager-listen-internals]; [the pager monitor][pager-monitor]
|
|
166
|
-
|
|
184
|
+
works][pager-listen-internals]; [the pager monitor][pager-monitor] and
|
|
185
|
+
its [frozen-turn watch][frozen-watch], off by default; [not running
|
|
186
|
+
under Claude Code][pager-outside-claude]; [what the pager
|
|
167
187
|
injects][pager-inject-log].
|
|
168
188
|
- Three hidden verbs: [`message seat`][message-seat], a seat with no Claude
|
|
169
189
|
session; [`message receipt`][message-receipt], what another session
|
|
170
190
|
recorded; and [`message setup`][message-setup], making a host ready.
|
|
191
|
+
- [Your first memory root, by hand][memory-first-root]: `memory` has no
|
|
192
|
+
automated first step yet.
|
|
171
193
|
- [Gating on `message verify --json`][verify-json-verdict]: the verdict as
|
|
172
194
|
one JSON object.
|
|
173
195
|
- [The writing model][session-model]: the signed `Session-Model:` header,
|
|
@@ -188,12 +210,14 @@ Everything else is in `docs/`, one subject per file:
|
|
|
188
210
|
[pager]: docs/pager.md
|
|
189
211
|
[pager-listen-internals]: docs/pager-listen-internals.md
|
|
190
212
|
[pager-monitor]: docs/pager-monitor.md
|
|
213
|
+
[frozen-watch]: docs/frozen-watch.md
|
|
191
214
|
[pager-outside-claude]: docs/pager-outside-claude.md
|
|
192
215
|
[pager-inject-log]: docs/pager-inject-log.md
|
|
193
216
|
[message-seat]: docs/message-seat.md
|
|
194
217
|
[session-model]: docs/session-model.md
|
|
195
218
|
[message-receipt]: docs/message-receipt.md
|
|
196
219
|
[message-setup]: docs/message-setup.md
|
|
220
|
+
[memory-first-root]: docs/memory-first-root.md
|
|
197
221
|
[verify-json-verdict]: docs/verify-json-verdict.md
|
|
198
222
|
[mail-delivery-paths]: docs/mail-delivery-paths.md
|
|
199
223
|
[postfix-configuration]: docs/postfix-configuration.md
|
|
@@ -131,6 +131,41 @@ backtick or `$(` somewhere). One false positive was found and fixed before
|
|
|
131
131
|
this landed — arithmetic `$(( … ))` in a subject. ~18µs per command; the hook
|
|
132
132
|
process through its shim, ~27ms.
|
|
133
133
|
|
|
134
|
+
## Turning it off — `"mail_guard": false`
|
|
135
|
+
|
|
136
|
+
The guard has one switch, in the host settings file
|
|
137
|
+
`~/.sessionkit/mail/settings/mail.json`: `"mail_guard": false` turns off its
|
|
138
|
+
subject and body checks for every seat on the host. It is the lever for a
|
|
139
|
+
guard that is broken. A person turns it off with `pager settings hooks
|
|
140
|
+
mail_guard off`, and back on with `pager settings hooks mail_guard on`;
|
|
141
|
+
`pager settings hooks mail_guard` says which.
|
|
142
|
+
|
|
143
|
+
- **Missing means on.** Only the boolean `false` turns the checks off. Any
|
|
144
|
+
other value (`"no"`, `0`), and a file that is missing, cannot be read, is
|
|
145
|
+
not JSON or is not a JSON object, leaves them on. An odd or broken
|
|
146
|
+
settings file never turns the guard off.
|
|
147
|
+
- **The subagent rules stay on.** Inside a subagent, the guard still denies
|
|
148
|
+
what would take or mark the seat's mail, write its settings or switch its
|
|
149
|
+
hooks, through both doors, whatever the switch says. The switch is read
|
|
150
|
+
only when the subject and body checks would run, so an MCP call and a
|
|
151
|
+
call those rules deny never read the file.
|
|
152
|
+
- **`"hooks"` does not touch it.** `"hooks": false` silences the hooks that
|
|
153
|
+
speak; the guard still judges.
|
|
154
|
+
- **Off, nothing stands before the shell.** The CLI refuses a subject that
|
|
155
|
+
*arrives* carrying a backtick or `$(`, but the shell has already expanded
|
|
156
|
+
and run a double-quoted one by then. With the guard off, that command runs.
|
|
157
|
+
- **It is a person's switch, by convention.** Every seat runs as the user
|
|
158
|
+
who owns the file, so nothing refuses a main-thread agent that writes it.
|
|
159
|
+
Each change the verb makes is logged, with who made it
|
|
160
|
+
([the settings doc][settings-hooks]); turn it off only when a person
|
|
161
|
+
asked for it.
|
|
162
|
+
|
|
163
|
+
The guard reads the file itself, stdlib only (`guard_switched_off` in
|
|
164
|
+
`hook.py`): it cannot import sessionmail. A test keeps its reading in step
|
|
165
|
+
with sessionmail's own reader.
|
|
166
|
+
|
|
167
|
+
[settings-hooks]: ../../docs/settings.md#the-per-hook-switches
|
|
168
|
+
|
|
134
169
|
## Wiring it — the plugin registers it
|
|
135
170
|
|
|
136
171
|
This hook lived in the `session` repo until that project was retired
|
|
@@ -143,6 +178,7 @@ it, like its other hooks:
|
|
|
143
178
|
```
|
|
144
179
|
hooks/hooks.json PreToolUse, matcher Bash -> hooks/mail-guard-hook (timeout 5)
|
|
145
180
|
PreToolUse, matcher mcp__plugin_sessionmail_(message|pager)__.*
|
|
181
|
+
|mcp__plugin_sessionmail_memory__memory_init
|
|
146
182
|
-> the same shim (a subagent's MCP door)
|
|
147
183
|
hooks/mail-guard-hook the shim: python3 hooks/mail-guard/hook.py, and exit 0
|
|
148
184
|
whatever goes wrong
|
|
@@ -151,13 +187,28 @@ hooks/mail-guard-hook the shim: python3 hooks/mail-guard/hook.py, and exit 0
|
|
|
151
187
|
So a seat that loaded sessionmail is behind the guard, and one that did not
|
|
152
188
|
is not.
|
|
153
189
|
|
|
154
|
-
**The MCP group** routes the message and pager servers' tools
|
|
155
|
-
memory's
|
|
156
|
-
`agent_id` (a subagent's): there it denies what the Bash door
|
|
157
|
-
subagent (`message_read` without `peek`, `message_mark`,
|
|
158
|
-
`new_only`, `pager_stop|off|mute|unmute|listen
|
|
159
|
-
|
|
160
|
-
|
|
190
|
+
**The MCP group** routes the message and pager servers' tools, and of
|
|
191
|
+
memory's only `memory_init`, to the same guard. It acts only on a payload
|
|
192
|
+
that carries `agent_id` (a subagent's): there it denies what the Bash door
|
|
193
|
+
denies a subagent (`message_read` without `peek`, `message_mark`,
|
|
194
|
+
`pager_status` with `new_only`, `pager_stop|off|mute|unmute|listen`,
|
|
195
|
+
`pager_focus`, `message_subscription_add|remove` and
|
|
196
|
+
`memory_init`), judged by the same table (`subagent_verb`). A main thread's
|
|
197
|
+
MCP call is allowed at once, exit 0 and no output. Cost: one python3 run per
|
|
198
|
+
such call, about 23 ms on one Mac. The rest of memory's tools are not
|
|
199
|
+
routed, so they cost nothing.
|
|
200
|
+
|
|
201
|
+
**Inside a subagent, through either door**, the table also denies
|
|
202
|
+
`memory init` (the whole verb), `memory run` without `--list`, `message
|
|
203
|
+
subscription add|remove`, `pager rule add|remove`, `pager focus` and
|
|
204
|
+
`pager settings hooks on|off` or `pager settings hooks KEY on|off`: each
|
|
205
|
+
writes the seat's memory, runs its script-mail, or changes its state, as the
|
|
206
|
+
seat (the last two, the HOST's switches, for every seat on it). Their reads
|
|
207
|
+
stay allowed (`memory ls|tree|cat|wake`, `memory run --list`, `message
|
|
208
|
+
subscription list`, `pager rule list`, `pager status --stopped`, a bare
|
|
209
|
+
`pager settings hooks`, and `pager settings hooks KEY` alone). A word that would allow (`--peek`, `--list`, `--help`) counts
|
|
210
|
+
only before a `--`, where click still reads options: `message read 1 --
|
|
211
|
+
--peek` reads, and is denied. Hook configuration is read at **session start**: loading the plugin
|
|
161
212
|
does not guard agents that are already running.
|
|
162
213
|
|
|
163
214
|
**It never blocks by failing.** The only way it speaks is decision JSON on
|
|
@@ -182,7 +233,12 @@ group, and the hook process contract. `tests/test_mail_guard_wiring.py` —
|
|
|
182
233
|
the registration in `hooks/hooks.json`, and the shim allowing when python3
|
|
183
234
|
is missing, `hook.py` is missing, or it crashes. Both run under `make test`.
|
|
184
235
|
`tests/test_mail_guard_subagent.py` and `tests/test_mail_guard_subagent_mcp.py`
|
|
185
|
-
— the subagent rule through the Bash door and the MCP door
|
|
236
|
+
— the subagent rule through the Bash door and the MCP door;
|
|
237
|
+
`tests/test_mail_guard_subagent_sm837.py` — the seat-state rows and the `--` cut,
|
|
238
|
+
both doors, with the main thread pinned byte for byte to the master it was
|
|
239
|
+
built on. `tests/test_hook_switches_mail_guard.py` — the off switch, both
|
|
240
|
+
doors, and the guard's reader in step with sessionmail's over every file
|
|
241
|
+
shape and value kind.
|
|
186
242
|
|
|
187
243
|
Written in python3, stdlib only, on purpose: a quoting-aware guard written in
|
|
188
244
|
the language whose quoting is the hazard is how you get a guard with its own
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "sessionmail"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.3"
|
|
8
8
|
description = "sessionmail - mail for coding agents: message, pager and memory over one maildir."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11,<3.15"
|
|
@@ -20,7 +20,7 @@ dependencies = [
|
|
|
20
20
|
"markdown>=3.6",
|
|
21
21
|
"mcp>=2.2.0",
|
|
22
22
|
"pymdown-extensions>=10.0",
|
|
23
|
-
"sessiontools>=0.3.
|
|
23
|
+
"sessiontools>=0.3.3",
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
# One package, three commands (openspec/specs/package/spec.md):
|
|
@@ -31,7 +31,7 @@ dependencies = [
|
|
|
31
31
|
#
|
|
32
32
|
# `pager listen` regenerates the LIVE postfix maps on a seat birth
|
|
33
33
|
# (pager.py:486-489), and it resolves which generator to run via
|
|
34
|
-
# shutil.which("vmailbox-gen") BEFORE
|
|
34
|
+
# shutil.which("vmailbox-gen") BEFORE <mail root>/.postfix/vmailbox-gen
|
|
35
35
|
# (_seat.py:994), with no --no-postmap. So an entry point here does not merely
|
|
36
36
|
# put a command on the PATH: at the next seat birth it COMPILES AND SERVES the
|
|
37
37
|
# packaged generator's map to postfix, for every agent address on this host.
|
|
@@ -24,8 +24,12 @@ seat --help` answers when typed.
|
|
|
24
24
|
|
|
25
25
|
## The wrapper
|
|
26
26
|
|
|
27
|
-
- **The key** is
|
|
28
|
-
|
|
27
|
+
- **The key** is `KEYS/ADDRESS/id_ed25519`, which is `message seat init`'s layout.
|
|
28
|
+
The pub the adapter names is `seat.pub` in the same dir. KEYS is
|
|
29
|
+
`$SESSIONMAIL_HOME/seat-keys` when that is set; else `~/.sessionmail/seat-keys`
|
|
30
|
+
while that is a real folder (an existing host's keys never move); else
|
|
31
|
+
`~/.sessionkit/mail/state/seat-keys`, where a fresh host mints. See "Where the
|
|
32
|
+
keys are" in [`docs/message-seat.md`](../../docs/message-seat.md).
|
|
29
33
|
- **It refuses before starting anything (exit 2)** when:
|
|
30
34
|
- the sessionmail install is broken: the packaged wrapper is missing or not
|
|
31
35
|
executable (said by `message` itself);
|
|
@@ -43,7 +47,9 @@ seat --help` answers when typed.
|
|
|
43
47
|
- **A TERM** (launchd stopping the job) stops the agent and removes the socket, exit 0.
|
|
44
48
|
- **The socket cap is 103 bytes.** macOS's `sun_path` is 104 bytes including the NUL.
|
|
45
49
|
Measured: ssh-agent binds a 103-byte path and refuses a 104-byte one. The default,
|
|
46
|
-
|
|
50
|
+
`KEYS/<address>/agent.sock`, is under the cap for a usual home and address
|
|
51
|
+
(`/Users/<name>/.sessionkit/mail/state/seat-keys/` is 45 bytes for a 4-letter
|
|
52
|
+
name); a longer one is refused before anything starts.
|
|
47
53
|
- **An orphan agent.** If the wrapper is SIGKILLed outside launchd, its agent can outlive it,
|
|
48
54
|
and the next start then refuses ("a live agent already answers"). Kill the orphan by
|
|
49
55
|
hand. Under launchd this does not arise: a job's process group goes with the job.
|
|
@@ -57,15 +63,27 @@ answers. `--uninstall ADDRESS` boots it out and removes the plist. Their contrac
|
|
|
57
63
|
[`docs/message-seat.md`](../../docs/message-seat.md).
|
|
58
64
|
|
|
59
65
|
`message seat agent --template` prints it, and nothing else: it never renders it, loads
|
|
60
|
-
it or runs launchctl. To render it by hand,
|
|
66
|
+
it or runs launchctl. To render it by hand, set KEYS to the seat-keys folder (above),
|
|
67
|
+
replace each `@PLACEHOLDER@`, then lint it. With no `SESSIONMAIL_HOME` of your own,
|
|
68
|
+
drop its entry, as `--install` does; the wrapper then finds KEYS under the job's HOME:
|
|
61
69
|
|
|
70
|
+
KEYS=$HOME/.sessionkit/mail/state/seat-keys # or $HOME/.sessionmail/seat-keys where the keys are
|
|
62
71
|
message seat agent --template |
|
|
63
72
|
sed -e 's|@ADDRESS@|bot.alice|g' -e "s|@MESSAGE@|$(command -v message)|g" \
|
|
64
|
-
-e
|
|
65
|
-
-e "s|@
|
|
66
|
-
-e "s|@
|
|
73
|
+
-e '/<key>SESSIONMAIL_HOME<\/key>/{N;d;}' \
|
|
74
|
+
-e "s|@SEAT_PUB@|$KEYS/bot.alice/seat.pub|g" \
|
|
75
|
+
-e "s|@SOCKET@|$KEYS/bot.alice/agent.sock|g" \
|
|
76
|
+
-e "s|@LOG_DIR@|$HOME/.sessionkit/mail/log|g" > /some/scratch/dir/dev.sessionkit.mail.seat-agent.bot.alice.plist
|
|
67
77
|
plutil -lint /some/scratch/dir/dev.sessionkit.mail.seat-agent.bot.alice.plist
|
|
68
78
|
|
|
79
|
+
With a `SESSIONMAIL_HOME` of your own, keep the entry and fill it instead
|
|
80
|
+
(`-e "s|@SESSIONMAIL_HOME@|$SESSIONMAIL_HOME|g"`, absolute), with KEYS its `seat-keys`.
|
|
81
|
+
|
|
82
|
+
`@LOG_DIR@` must be a folder that exists before launchd loads the job; `--install` makes
|
|
83
|
+
`~/.sessionkit/mail/log` mode 0700 when it is missing. An agent installed before 0.3.2
|
|
84
|
+
logs to `~/Library/Logs` and keeps doing so until it is uninstalled and installed again
|
|
85
|
+
(see "The log" in [`docs/message-seat.md`](../../docs/message-seat.md)).
|
|
86
|
+
|
|
69
87
|
`@MESSAGE@` is the path `command -v message` prints, not resolved: `~/.local/bin/message`
|
|
70
88
|
under uv tool or pipx, `<venv>/bin/message` in a venv. It stays the same across upgrades,
|
|
71
89
|
and its launcher is absolute, so launchd's `PATH=/usr/bin:/bin` is enough.
|
|
@@ -89,7 +107,7 @@ them as a plist dict.
|
|
|
89
107
|
|---|---|
|
|
90
108
|
| `TM_AGENT_ADDRESS` | the seat, `bot.alice` |
|
|
91
109
|
| `DESK_SIGNER_SOCK` | this job's `SOCKET` |
|
|
92
|
-
| `DESK_SIGNER_PUB` |
|
|
110
|
+
| `DESK_SIGNER_PUB` | `KEYS/<address>/seat.pub`, apart from the private key |
|
|
93
111
|
| `SESSIONMAIL_REQUIRE_SIGNATURE` | `1`: a send that cannot sign is refused |
|
|
94
112
|
| `SESSION_REGISTRY` | the registry that `message seat init` registered into, for `message verify --trust` |
|
|
95
113
|
|
|
@@ -197,11 +197,13 @@ def routed_names(text: str) -> set[str]:
|
|
|
197
197
|
return names
|
|
198
198
|
|
|
199
199
|
|
|
200
|
-
def mailbox_keys(text: str, rel: str) -> list[str]:
|
|
200
|
+
def mailbox_keys(text: str, rel: str, boxes: str) -> list[str]:
|
|
201
201
|
"""The keys of REL's rows in a mailbox map's text: the rows whose
|
|
202
|
-
SECOND field is exactly
|
|
203
|
-
found by one test and never by a substring.
|
|
204
|
-
|
|
202
|
+
SECOND field is exactly `<boxes>/<rel>/` (BN1), so both host forms are
|
|
203
|
+
found by one test and never by a substring. BOXES is the boxes folder's
|
|
204
|
+
name on this tree (`Ctx.root.name`: `stores`, or `agents` on an old
|
|
205
|
+
tree), which the generator writes into every value."""
|
|
206
|
+
want = f"{boxes}/{rel.lower()}/"
|
|
205
207
|
keys = []
|
|
206
208
|
for line in text.splitlines():
|
|
207
209
|
parts = line.split()
|
|
@@ -214,7 +216,7 @@ def mailbox_keys(text: str, rel: str) -> list[str]:
|
|
|
214
216
|
class Ctx:
|
|
215
217
|
"""One run: where things are, and what it may ask."""
|
|
216
218
|
|
|
217
|
-
root: Path # the
|
|
219
|
+
root: Path # the boxes dir (`_maildir.root()`); its name is the map values' folder
|
|
218
220
|
archive: Path
|
|
219
221
|
days: float = DEFAULT_DAYS
|
|
220
222
|
maps: Path | None = None # --maps DIR; None: ask postconf
|
|
@@ -411,14 +413,17 @@ def release_lock(archive: Path) -> None:
|
|
|
411
413
|
|
|
412
414
|
def notmuch_configs(env: dict[str, str]) -> list[Path]:
|
|
413
415
|
"""Every notmuch config file this host could use: `$NOTMUCH_CONFIG`
|
|
414
|
-
(else `~/.notmuch-config`), each `~/.notmuch-config.<profile>`,
|
|
415
|
-
|
|
416
|
+
(else `~/.notmuch-config`), each `~/.notmuch-config.<profile>`, each
|
|
417
|
+
`$XDG_CONFIG_HOME/notmuch/<profile>/config`, and each kit profile,
|
|
418
|
+
`~/.sessionkit/mail/settings/notmuch/<profile>/config` (`_index`)."""
|
|
416
419
|
home = Path(env.get("HOME") or Path.home())
|
|
417
420
|
found = [Path(env["NOTMUCH_CONFIG"]).expanduser()] if env.get("NOTMUCH_CONFIG") \
|
|
418
421
|
else [home / ".notmuch-config"]
|
|
419
422
|
found += sorted(home.glob(".notmuch-config.*"))
|
|
420
423
|
xdg = Path(env.get("XDG_CONFIG_HOME") or home / ".config")
|
|
421
424
|
found += sorted(xdg.glob("notmuch/*/config"))
|
|
425
|
+
from . import _index # here, not at import: _index reaches _notmuch
|
|
426
|
+
found += sorted(_index.kit_profiles(env).glob("*/config"))
|
|
422
427
|
out: list[Path] = []
|
|
423
428
|
for p in found:
|
|
424
429
|
if p.is_file() and p not in out:
|
|
@@ -853,7 +858,7 @@ def verify_gone(ctx: Ctx, text: str | None, m: Moved) -> tuple[str, str]:
|
|
|
853
858
|
resolves against, so TEXT is the print (`Ctx.mailbox_text`)."""
|
|
854
859
|
if text is None:
|
|
855
860
|
return "UNKNOWN", "map unreadable"
|
|
856
|
-
if mailbox_keys(text, m.rel):
|
|
861
|
+
if mailbox_keys(text, m.rel, ctx.root.name):
|
|
857
862
|
return "PRESENT", "regenerate did not take"
|
|
858
863
|
if not m.keys:
|
|
859
864
|
# BN6: a `moving` line with no keys (or a box with no row) cannot ask
|
|
@@ -873,7 +878,7 @@ def routed_now(ctx: Ctx, text: str | None, rel: str) -> bool:
|
|
|
873
878
|
"""REL has its row back, in the text and (when postconf answers) the .db."""
|
|
874
879
|
if text is None:
|
|
875
880
|
return False
|
|
876
|
-
keys = mailbox_keys(text, rel)
|
|
881
|
+
keys = mailbox_keys(text, rel, ctx.root.name)
|
|
877
882
|
if not keys:
|
|
878
883
|
return False
|
|
879
884
|
if ctx.use_db():
|
|
@@ -893,7 +898,7 @@ def alias_db_hit(ctx: Ctx, rel: str) -> str | None:
|
|
|
893
898
|
maps = _seat._postfix_maps("virtual_alias_maps")
|
|
894
899
|
if not maps:
|
|
895
900
|
raise Unknown("postconf could not say which alias .db postfix reads")
|
|
896
|
-
for key in mailbox_keys(ctx.mailbox_text() or "", rel):
|
|
901
|
+
for key in mailbox_keys(ctx.mailbox_text() or "", rel, ctx.root.name):
|
|
897
902
|
if _seat._postmap_lookup(key, maps):
|
|
898
903
|
return key
|
|
899
904
|
return None
|
|
@@ -1020,7 +1025,7 @@ def _apply_locked(ctx: Ctx, c: Census, pf: Preflight, candidates: list[Row],
|
|
|
1020
1025
|
warn(f"refused {rel}: archive/{rel} already exists; message box unarchive {rel} --merge --apply")
|
|
1021
1026
|
continue
|
|
1022
1027
|
text = ctx.mailbox_text()
|
|
1023
|
-
keys = mailbox_keys(text or "", rel)
|
|
1028
|
+
keys = mailbox_keys(text or "", rel, ctx.root.name)
|
|
1024
1029
|
if not keys:
|
|
1025
1030
|
warn(f"not moved {rel}: no mailbox row for it before the move (BN2)")
|
|
1026
1031
|
continue
|