tabc 0.2.0__tar.gz → 0.2.1__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.
- {tabc-0.2.0 → tabc-0.2.1}/CHANGELOG.md +49 -1
- {tabc-0.2.0 → tabc-0.2.1}/GUIDE.md +45 -6
- {tabc-0.2.0 → tabc-0.2.1}/MCP.md +6 -0
- {tabc-0.2.0/tabc.egg-info → tabc-0.2.1}/PKG-INFO +32 -4
- {tabc-0.2.0 → tabc-0.2.1}/PYPI.md +31 -3
- {tabc-0.2.0 → tabc-0.2.1}/pyproject.toml +1 -1
- {tabc-0.2.0 → tabc-0.2.1/tabc.egg-info}/PKG-INFO +32 -4
- {tabc-0.2.0 → tabc-0.2.1}/tabc.egg-info/SOURCES.txt +1 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/bus.py +1 -1
- {tabc-0.2.0 → tabc-0.2.1}/tabus/cli.py +26 -1
- tabc-0.2.1/tabus/codex_queue.py +194 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/doorbell.py +7 -2
- {tabc-0.2.0 → tabc-0.2.1}/tabus/doorbell_poll.py +1 -1
- {tabc-0.2.0 → tabc-0.2.1}/LICENSE +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/MANIFEST.in +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/setup.cfg +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabc.egg-info/dependency_links.txt +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabc.egg-info/entry_points.txt +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabc.egg-info/requires.txt +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabc.egg-info/top_level.txt +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/__init__.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/admin.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/compat_bcn.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/compat_busd.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/daemon.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/lang.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/locales/en.json +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/mcp_server.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/nodekey.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/paths.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/process_argv.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/route_capture.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/send_refusal.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/sent.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/tac_identity.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/tac_migration.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/tac_refusal.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/tac_search.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/tac_store.py +0 -0
- {tabc-0.2.0 → tabc-0.2.1}/tabus/terminal_frontend.py +0 -0
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.2.
|
|
3
|
+
## 0.2.1 — 2026-10-15
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- A Codex queue notification route. Under the native Codex managed-daemon
|
|
8
|
+
app-server, the notifier submits notification metadata to the registered
|
|
9
|
+
Codex thread with `codex queue` instead of typing into a terminal.
|
|
10
|
+
- `register --codex-queue` requires that route with no terminal fallback. It
|
|
11
|
+
needs `--kind codex` and `--auto-enter on`.
|
|
12
|
+
- A local receipt database, `codex_queue_receipts.db`, that limits submission
|
|
13
|
+
to one attempt per thread UUID and notification ID. An uncertain result is
|
|
14
|
+
recorded as `UNKNOWN` and is not submitted again automatically.
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- `register` requires `--kind`. The previous default, `generic`, is removed.
|
|
19
|
+
- `register --kind codex --auto-enter on` selects the Codex queue route when
|
|
20
|
+
the current Codex thread verifies, then a terminal route verified by the same
|
|
21
|
+
command. With neither, registration is refused and existing routes are kept.
|
|
22
|
+
- When registration selects the Codex queue route, `--take-route` is refused.
|
|
23
|
+
|
|
24
|
+
### Upgrade notes
|
|
25
|
+
|
|
26
|
+
- The ledger schema is unchanged. Nodes, signing keys, messages, delivery
|
|
27
|
+
states, TACs, and routes are kept. No conversion is needed.
|
|
28
|
+
- Stop `tabus.doorbell`, install 0.2.1, restart `tabd`, then start one
|
|
29
|
+
`tabus.doorbell` from the same installation:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
python -m pip install --upgrade "tabc==0.2.1"
|
|
33
|
+
# Or, for desktop MCP support:
|
|
34
|
+
python -m pip install --upgrade "tabc[mcp]==0.2.1"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- Confirm the running notifier is 0.2.1 before registering Codex queue routes.
|
|
38
|
+
Run one notifier. An older notifier records `UNSUPPORTED` for these routes.
|
|
39
|
+
- Scripts that call `register` without `--kind` must add it.
|
|
40
|
+
- To use the Codex queue route, register again from each intended Codex
|
|
41
|
+
conversation after the notifier is replaced.
|
|
42
|
+
- The notifier creates `codex_queue_receipts.db` in the tabc home directory on
|
|
43
|
+
its first Codex queue submission. It is separate from message read state.
|
|
44
|
+
- The Codex queue route is a notifier path. It is not part of the MCP adapter,
|
|
45
|
+
which still does not wake an agent.
|
|
46
|
+
- If a desktop client runs `tabc-mcp`, restart that MCP process from the client
|
|
47
|
+
after the upgrade so it loads the new installation.
|
|
48
|
+
- Checked with codex-cli 0.162.1 on macOS only.
|
|
49
|
+
- After restart, `tabc --version` and `tabd --version` report 0.2.1.
|
|
50
|
+
|
|
51
|
+
## 0.2.0 — 2026-09-23
|
|
4
52
|
|
|
5
53
|
### Added
|
|
6
54
|
|
|
@@ -78,6 +78,33 @@ Read the two together:
|
|
|
78
78
|
A row existing is not the same as a row pointing at you. Check the string, not
|
|
79
79
|
the count.
|
|
80
80
|
|
|
81
|
+
### Codex queue routes
|
|
82
|
+
|
|
83
|
+
This applies to 0.2.1 and later, which add the Codex queue adapter. For a
|
|
84
|
+
Codex queue route, `adapter` is `codex-queue` and `target` is a Codex thread
|
|
85
|
+
UUID, not a terminal. Compare it with the conversation you are in:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
echo "$CODEX_THREAD_ID"
|
|
89
|
+
sqlite3 "file:$HOME/.tabc/tabus.db?mode=ro" \
|
|
90
|
+
"SELECT adapter, target, auto_enter FROM tab_routes
|
|
91
|
+
WHERE node_id='<your-node>' AND revoked_at IS NULL"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
| Outcome | What it means |
|
|
95
|
+
|---|---|
|
|
96
|
+
| `QUEUED` | Codex accepted the notification input for that thread. It is not `READ`, and the text can appear after the current reply ends |
|
|
97
|
+
| `UNKNOWN` | The submission result is uncertain. The same notification is not submitted again automatically. Check the inbox with `dm`. If every notification ends here, check that the notifier's own `PATH` has a native `codex` and that its Codex environment reaches the same local daemon |
|
|
98
|
+
| `DISABLED` | Automatic submission is off for the route, so nothing was submitted. Register again with `--auto-enter on` |
|
|
99
|
+
| `UNSUPPORTED` | The running notifier predates the queue adapter. Replace it with the matching release, then register again |
|
|
100
|
+
|
|
101
|
+
If the target is another conversation's UUID, register again from your own
|
|
102
|
+
conversation. Do not copy a UUID into the route.
|
|
103
|
+
|
|
104
|
+
After replacing the notifier and registering, confirm one real round trip: a
|
|
105
|
+
message arrives in the conversation and a reply comes back. A `QUEUED` record
|
|
106
|
+
does not show that.
|
|
107
|
+
|
|
81
108
|
---
|
|
82
109
|
|
|
83
110
|
## What a success record does not prove
|
|
@@ -189,9 +216,12 @@ ACCEPTED → CLAIMED → INJECTED → READ → PROCESSED
|
|
|
189
216
|
A state cannot be skipped. `ACCEPTED` will not go straight to `READ`: pass
|
|
190
217
|
through `INJECTED` with `open` or `pull` first, then `ack`.
|
|
191
218
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
`
|
|
219
|
+
CLI `open` and `ack` take the full UUID or a unique prefix, such as the
|
|
220
|
+
shortened id that `dm` prints. A prefix that matches more than one message is
|
|
221
|
+
refused as ambiguous. `sent --id` and every MCP `message_id` take the full UUID.
|
|
222
|
+
|
|
223
|
+
A message that `pull` already claimed does not come back on a second `pull`.
|
|
224
|
+
It stays listed in `dm`; open it from there.
|
|
195
225
|
|
|
196
226
|
---
|
|
197
227
|
|
|
@@ -275,11 +305,20 @@ The same visible text can therefore use different byte counts in NFC and NFD.
|
|
|
275
305
|
- Agents sharing one operating-system account can read each other's key files.
|
|
276
306
|
This is not a security boundary between them.
|
|
277
307
|
- Message bodies are not encrypted at rest.
|
|
278
|
-
-
|
|
279
|
-
routes, and turning it on is a decision about that
|
|
280
|
-
|
|
308
|
+
- On a terminal route, automatic Enter types into an execution-capable input
|
|
309
|
+
field. It is off for new routes, and turning it on is a decision about that
|
|
310
|
+
terminal, not a convenience. A Codex queue route does not type into a
|
|
311
|
+
terminal; it submits input to the conversation.
|
|
312
|
+
- Message delivery is at-least-once, not exactly-once. A recipient absorbs
|
|
313
|
+
duplicates. Codex queue notifications are separate: the local receipt
|
|
314
|
+
database allows one submission attempt per thread UUID and notification ID,
|
|
315
|
+
and an `UNKNOWN` result is not submitted again automatically.
|
|
281
316
|
- An alarm depends on a route. A node without one receives by `pull`, and no
|
|
282
317
|
amount of sending will make it ring.
|
|
318
|
+
- A Codex queue route submits notification input to a Codex thread. It was
|
|
319
|
+
checked with codex-cli 0.162.1 on macOS only. The notification text is a
|
|
320
|
+
command-line argument that local process tools can see. It carries sender
|
|
321
|
+
names, unread counts and TAC names or UUIDs, never a body.
|
|
283
322
|
|
|
284
323
|
`SECURITY.md` states the five clauses and how far each is currently kept.
|
|
285
324
|
|
{tabc-0.2.0 → tabc-0.2.1}/MCP.md
RENAMED
|
@@ -30,6 +30,7 @@ normal node for each agent:
|
|
|
30
30
|
Do not use `--program`: it creates a send-only node that cannot receive messages.
|
|
31
31
|
Registration from iTerm2 or tmux can capture that terminal's notification route.
|
|
32
32
|
Inspect the registration output; a terminal route does not notify the desktop app.
|
|
33
|
+
The command above omits `--auto-enter`, so it does not select a Codex queue route.
|
|
33
34
|
Do not share one node between independent agent conversations: they would share
|
|
34
35
|
the inbox and delivery states.
|
|
35
36
|
|
|
@@ -121,6 +122,11 @@ There are no node deletion, daemon administration, shell execution or automatic
|
|
|
121
122
|
polling tools. Arrival does not trigger an agent turn. The client must call tools
|
|
122
123
|
on user request or through separately configured scheduling.
|
|
123
124
|
|
|
125
|
+
The Codex queue adapter, added in 0.2.1, is a separate notification
|
|
126
|
+
path run by the notifier. It is not an MCP tool and does not change this
|
|
127
|
+
section: the MCP adapter still does not wake an agent. See
|
|
128
|
+
[Codex queue notifications](README.md#how-do-i-get-notifications-in-a-codex-thread).
|
|
129
|
+
|
|
124
130
|
## Errors and retries
|
|
125
131
|
|
|
126
132
|
No request is automatically retried. A timeout, connection loss, server error or
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tabc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A shared inbox for AI agents and programs on one machine
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -102,12 +102,30 @@ tabc sent --node alice
|
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
The last line is what matters. It shows the message reached the other side,
|
|
105
|
-
rather than showing that sending did not fail.
|
|
106
|
-
the `pull --mode full` detail block,
|
|
105
|
+
rather than showing that sending did not fail. `ack --id` takes the full UUID
|
|
106
|
+
from the `pull --mode full` detail block, or a unique prefix of it.
|
|
107
107
|
|
|
108
108
|
Terminal notification is a separate process and does not start with the
|
|
109
109
|
install; see the repository's guide before turning it on.
|
|
110
110
|
|
|
111
|
+
## Updating from 0.2.0
|
|
112
|
+
|
|
113
|
+
The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
|
|
114
|
+
TACs, and routes are kept. Stop `tabus.doorbell`, install, restart `tabd`, then
|
|
115
|
+
start one `tabus.doorbell` from the same installation:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python -m pip install --upgrade "tabc==0.2.1"
|
|
119
|
+
# Or, when the local desktop MCP adapter is required:
|
|
120
|
+
python -m pip install --upgrade "tabc[mcp]==0.2.1"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`register` now requires `--kind`. Update scripts that omit it. Codex agents
|
|
124
|
+
register again from each conversation after the notifier is replaced. The
|
|
125
|
+
notifier creates `codex_queue_receipts.db` in the tabc home directory on its
|
|
126
|
+
first Codex queue submission. See the
|
|
127
|
+
[changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
|
|
128
|
+
|
|
111
129
|
## Updating from 0.1.6
|
|
112
130
|
|
|
113
131
|
The upgrade preserves nodes, messages, delivery states, TAC membership, names,
|
|
@@ -125,6 +143,7 @@ python -m pip install --upgrade "tabc[mcp]==0.2.0"
|
|
|
125
143
|
Existing string TAC IDs are not converted during installation. Follow the
|
|
126
144
|
repository README's 0.2.0 ledger conversion procedure when converting TAC IDs
|
|
127
145
|
to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
|
|
146
|
+
Then follow "Updating from 0.2.0".
|
|
128
147
|
|
|
129
148
|
## Commands
|
|
130
149
|
|
|
@@ -136,7 +155,7 @@ to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
|
|
|
136
155
|
- `tabc tac search --node alice --query text`: search joined topics without changing read state.
|
|
137
156
|
|
|
138
157
|
Register each recipient before sending. tac creators must also join their topic.
|
|
139
|
-
|
|
158
|
+
Since 0.2.0, the server mints a UUID for each new tac. Use that UUID for membership,
|
|
140
159
|
sending, reading, renaming and closing; use the unique name for display and search.
|
|
141
160
|
Run `tabc --help` for available commands. The local server is `tabd`; management
|
|
142
161
|
commands are provided separately by `tabm`.
|
|
@@ -163,6 +182,15 @@ Installation alone does not enable notifications. Automatic Enter defaults to of
|
|
|
163
182
|
for new routes. Omitting the setting during registration preserves the same active
|
|
164
183
|
route; `--auto-enter off` disables it explicitly.
|
|
165
184
|
|
|
185
|
+
`register` requires `--kind`. A Codex agent running under the native Codex
|
|
186
|
+
managed-daemon app-server can receive notifications in its conversation instead
|
|
187
|
+
of a terminal. Replace the notifier with this release first, then run
|
|
188
|
+
`tabc register --node <node> --kind codex --auto-enter on` from that
|
|
189
|
+
conversation. `--codex-queue` requires that route with no terminal fallback.
|
|
190
|
+
`QUEUED` means Codex accepted the notification input, not that it was read.
|
|
191
|
+
This path was checked with codex-cli 0.162.1 on macOS only. See the repository
|
|
192
|
+
README for conditions and limits.
|
|
193
|
+
|
|
166
194
|
Do not connect notifications to an unreviewed command prompt.
|
|
167
195
|
Automatic Enter does not guarantee a reply.
|
|
168
196
|
|
|
@@ -57,12 +57,30 @@ tabc sent --node alice
|
|
|
57
57
|
```
|
|
58
58
|
|
|
59
59
|
The last line is what matters. It shows the message reached the other side,
|
|
60
|
-
rather than showing that sending did not fail.
|
|
61
|
-
the `pull --mode full` detail block,
|
|
60
|
+
rather than showing that sending did not fail. `ack --id` takes the full UUID
|
|
61
|
+
from the `pull --mode full` detail block, or a unique prefix of it.
|
|
62
62
|
|
|
63
63
|
Terminal notification is a separate process and does not start with the
|
|
64
64
|
install; see the repository's guide before turning it on.
|
|
65
65
|
|
|
66
|
+
## Updating from 0.2.0
|
|
67
|
+
|
|
68
|
+
The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
|
|
69
|
+
TACs, and routes are kept. Stop `tabus.doorbell`, install, restart `tabd`, then
|
|
70
|
+
start one `tabus.doorbell` from the same installation:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m pip install --upgrade "tabc==0.2.1"
|
|
74
|
+
# Or, when the local desktop MCP adapter is required:
|
|
75
|
+
python -m pip install --upgrade "tabc[mcp]==0.2.1"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`register` now requires `--kind`. Update scripts that omit it. Codex agents
|
|
79
|
+
register again from each conversation after the notifier is replaced. The
|
|
80
|
+
notifier creates `codex_queue_receipts.db` in the tabc home directory on its
|
|
81
|
+
first Codex queue submission. See the
|
|
82
|
+
[changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
|
|
83
|
+
|
|
66
84
|
## Updating from 0.1.6
|
|
67
85
|
|
|
68
86
|
The upgrade preserves nodes, messages, delivery states, TAC membership, names,
|
|
@@ -80,6 +98,7 @@ python -m pip install --upgrade "tabc[mcp]==0.2.0"
|
|
|
80
98
|
Existing string TAC IDs are not converted during installation. Follow the
|
|
81
99
|
repository README's 0.2.0 ledger conversion procedure when converting TAC IDs
|
|
82
100
|
to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
|
|
101
|
+
Then follow "Updating from 0.2.0".
|
|
83
102
|
|
|
84
103
|
## Commands
|
|
85
104
|
|
|
@@ -91,7 +110,7 @@ to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
|
|
|
91
110
|
- `tabc tac search --node alice --query text`: search joined topics without changing read state.
|
|
92
111
|
|
|
93
112
|
Register each recipient before sending. tac creators must also join their topic.
|
|
94
|
-
|
|
113
|
+
Since 0.2.0, the server mints a UUID for each new tac. Use that UUID for membership,
|
|
95
114
|
sending, reading, renaming and closing; use the unique name for display and search.
|
|
96
115
|
Run `tabc --help` for available commands. The local server is `tabd`; management
|
|
97
116
|
commands are provided separately by `tabm`.
|
|
@@ -118,6 +137,15 @@ Installation alone does not enable notifications. Automatic Enter defaults to of
|
|
|
118
137
|
for new routes. Omitting the setting during registration preserves the same active
|
|
119
138
|
route; `--auto-enter off` disables it explicitly.
|
|
120
139
|
|
|
140
|
+
`register` requires `--kind`. A Codex agent running under the native Codex
|
|
141
|
+
managed-daemon app-server can receive notifications in its conversation instead
|
|
142
|
+
of a terminal. Replace the notifier with this release first, then run
|
|
143
|
+
`tabc register --node <node> --kind codex --auto-enter on` from that
|
|
144
|
+
conversation. `--codex-queue` requires that route with no terminal fallback.
|
|
145
|
+
`QUEUED` means Codex accepted the notification input, not that it was read.
|
|
146
|
+
This path was checked with codex-cli 0.162.1 on macOS only. See the repository
|
|
147
|
+
README for conditions and limits.
|
|
148
|
+
|
|
121
149
|
Do not connect notifications to an unreviewed command prompt.
|
|
122
150
|
Automatic Enter does not guarantee a reply.
|
|
123
151
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tabc
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A shared inbox for AI agents and programs on one machine
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -102,12 +102,30 @@ tabc sent --node alice
|
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
The last line is what matters. It shows the message reached the other side,
|
|
105
|
-
rather than showing that sending did not fail.
|
|
106
|
-
the `pull --mode full` detail block,
|
|
105
|
+
rather than showing that sending did not fail. `ack --id` takes the full UUID
|
|
106
|
+
from the `pull --mode full` detail block, or a unique prefix of it.
|
|
107
107
|
|
|
108
108
|
Terminal notification is a separate process and does not start with the
|
|
109
109
|
install; see the repository's guide before turning it on.
|
|
110
110
|
|
|
111
|
+
## Updating from 0.2.0
|
|
112
|
+
|
|
113
|
+
The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
|
|
114
|
+
TACs, and routes are kept. Stop `tabus.doorbell`, install, restart `tabd`, then
|
|
115
|
+
start one `tabus.doorbell` from the same installation:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
python -m pip install --upgrade "tabc==0.2.1"
|
|
119
|
+
# Or, when the local desktop MCP adapter is required:
|
|
120
|
+
python -m pip install --upgrade "tabc[mcp]==0.2.1"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`register` now requires `--kind`. Update scripts that omit it. Codex agents
|
|
124
|
+
register again from each conversation after the notifier is replaced. The
|
|
125
|
+
notifier creates `codex_queue_receipts.db` in the tabc home directory on its
|
|
126
|
+
first Codex queue submission. See the
|
|
127
|
+
[changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
|
|
128
|
+
|
|
111
129
|
## Updating from 0.1.6
|
|
112
130
|
|
|
113
131
|
The upgrade preserves nodes, messages, delivery states, TAC membership, names,
|
|
@@ -125,6 +143,7 @@ python -m pip install --upgrade "tabc[mcp]==0.2.0"
|
|
|
125
143
|
Existing string TAC IDs are not converted during installation. Follow the
|
|
126
144
|
repository README's 0.2.0 ledger conversion procedure when converting TAC IDs
|
|
127
145
|
to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
|
|
146
|
+
Then follow "Updating from 0.2.0".
|
|
128
147
|
|
|
129
148
|
## Commands
|
|
130
149
|
|
|
@@ -136,7 +155,7 @@ to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
|
|
|
136
155
|
- `tabc tac search --node alice --query text`: search joined topics without changing read state.
|
|
137
156
|
|
|
138
157
|
Register each recipient before sending. tac creators must also join their topic.
|
|
139
|
-
|
|
158
|
+
Since 0.2.0, the server mints a UUID for each new tac. Use that UUID for membership,
|
|
140
159
|
sending, reading, renaming and closing; use the unique name for display and search.
|
|
141
160
|
Run `tabc --help` for available commands. The local server is `tabd`; management
|
|
142
161
|
commands are provided separately by `tabm`.
|
|
@@ -163,6 +182,15 @@ Installation alone does not enable notifications. Automatic Enter defaults to of
|
|
|
163
182
|
for new routes. Omitting the setting during registration preserves the same active
|
|
164
183
|
route; `--auto-enter off` disables it explicitly.
|
|
165
184
|
|
|
185
|
+
`register` requires `--kind`. A Codex agent running under the native Codex
|
|
186
|
+
managed-daemon app-server can receive notifications in its conversation instead
|
|
187
|
+
of a terminal. Replace the notifier with this release first, then run
|
|
188
|
+
`tabc register --node <node> --kind codex --auto-enter on` from that
|
|
189
|
+
conversation. `--codex-queue` requires that route with no terminal fallback.
|
|
190
|
+
`QUEUED` means Codex accepted the notification input, not that it was read.
|
|
191
|
+
This path was checked with codex-cli 0.162.1 on macOS only. See the repository
|
|
192
|
+
README for conditions and limits.
|
|
193
|
+
|
|
166
194
|
Do not connect notifications to an unreviewed command prompt.
|
|
167
195
|
Automatic Enter does not guarantee a reply.
|
|
168
196
|
|
|
@@ -74,7 +74,7 @@ DB_PATH = os.environ.get(
|
|
|
74
74
|
# drifted once already — 0.1.0.post1 shipped while this said 0.1.0. Bump
|
|
75
75
|
# both together; tests/test_version_agreement.py fails the build otherwise.
|
|
76
76
|
# Do not copy the number into documentation.
|
|
77
|
-
__version__ = "0.2.
|
|
77
|
+
__version__ = "0.2.1"
|
|
78
78
|
PROTOCOL_VERSION = "tabus.v1"
|
|
79
79
|
|
|
80
80
|
# TTLs. Past these, a derived status counts as old.
|
|
@@ -255,6 +255,12 @@ def fn_register(a):
|
|
|
255
255
|
take_route = bool(getattr(a, "take_route", False))
|
|
256
256
|
setting = getattr(a, "auto_enter", None)
|
|
257
257
|
auto_enter = setting == "on"
|
|
258
|
+
if getattr(a, "codex_queue", False) and take_route:
|
|
259
|
+
print("--codex-queue does not support --take-route")
|
|
260
|
+
sys.exit(2)
|
|
261
|
+
if getattr(a, "codex_queue", False) and getattr(a, "program", None) is not None:
|
|
262
|
+
print("--codex-queue and --program cannot be used together")
|
|
263
|
+
sys.exit(2)
|
|
258
264
|
if setting is not None:
|
|
259
265
|
payload["auto_enter"] = auto_enter
|
|
260
266
|
if getattr(a, "program", None) is not None and take_route:
|
|
@@ -290,6 +296,23 @@ def fn_register(a):
|
|
|
290
296
|
else:
|
|
291
297
|
captured = _capture_route()
|
|
292
298
|
adapter, target = captured
|
|
299
|
+
# Codex shared-daemon commands have a thread identity, not a TTY owner.
|
|
300
|
+
# Automatic queue selection still requires explicit submission consent.
|
|
301
|
+
force_queue = getattr(a, "codex_queue", False)
|
|
302
|
+
if force_queue or (a.kind == "codex" and auto_enter):
|
|
303
|
+
if a.kind != "codex" or setting != "on":
|
|
304
|
+
print("--codex-queue requires --kind codex --auto-enter on")
|
|
305
|
+
sys.exit(2)
|
|
306
|
+
from .codex_queue import capture_current_thread
|
|
307
|
+
queue_target = capture_current_thread()
|
|
308
|
+
if queue_target is None and force_queue:
|
|
309
|
+
print("cannot verify the current Codex managed-daemon thread; registration not sent")
|
|
310
|
+
sys.exit(2)
|
|
311
|
+
if queue_target is not None:
|
|
312
|
+
if take_route:
|
|
313
|
+
print("Codex queue delivery does not support --take-route; registration not sent")
|
|
314
|
+
sys.exit(2)
|
|
315
|
+
adapter, target = "codex-queue", queue_target
|
|
293
316
|
# A failed local capture cannot invalidate a previously stored route.
|
|
294
317
|
# Do not send legacy auto-revocation fields, even to an older daemon.
|
|
295
318
|
if not getattr(captured, "rejected", False) and take_route:
|
|
@@ -1061,7 +1084,9 @@ COMMANDS = {
|
|
|
1061
1084
|
"register a node",
|
|
1062
1085
|
[
|
|
1063
1086
|
(("--node",), dict(required=True)),
|
|
1064
|
-
(("--kind",), dict(
|
|
1087
|
+
(("--kind",), dict(required=True)),
|
|
1088
|
+
(("--codex-queue",), dict(action="store_true",
|
|
1089
|
+
help="require Codex queue delivery without terminal fallback; requires kind codex and auto-enter on")),
|
|
1065
1090
|
(
|
|
1066
1091
|
("--take-route",),
|
|
1067
1092
|
dict(
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""Deliver notifications to an explicit Codex thread, without terminal input.
|
|
2
|
+
|
|
3
|
+
This module does not register routes or mark messages read. The receipt journal
|
|
4
|
+
records delivery attempts, not messages. An uncertain attempt is never retried
|
|
5
|
+
automatically: the queue command has no caller-supplied idempotency key.
|
|
6
|
+
|
|
7
|
+
Notification text is passed in argv and can be visible to local process tools.
|
|
8
|
+
Callers must pass notification metadata only, never message bodies or secrets.
|
|
9
|
+
Receipt parsing requires exactly one success line on stdout. Additional stdout
|
|
10
|
+
is treated as UNKNOWN even if submission may have succeeded.
|
|
11
|
+
|
|
12
|
+
Rollout order: deploy and verify the updated doorbell daemon first, then
|
|
13
|
+
register each intended Codex node with the documented options; an older daemon
|
|
14
|
+
returns DELIVER_UNSUPPORTED.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import os
|
|
18
|
+
import re
|
|
19
|
+
import sqlite3
|
|
20
|
+
import subprocess
|
|
21
|
+
import uuid
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def deliver_notification(target, notification_id, message, auto_enter):
|
|
25
|
+
"""Doorbell boundary: use only a recognized local native Codex binary."""
|
|
26
|
+
import pwd
|
|
27
|
+
import shutil
|
|
28
|
+
from . import paths
|
|
29
|
+
from .terminal_frontend import executable_kind
|
|
30
|
+
executable = shutil.which("codex")
|
|
31
|
+
if not executable or not notification_id:
|
|
32
|
+
return "UNKNOWN", -1
|
|
33
|
+
executable = os.path.realpath(executable)
|
|
34
|
+
if executable_kind(executable, pwd.getpwuid(os.getuid()).pw_dir) != "codex":
|
|
35
|
+
return "UNKNOWN", -1
|
|
36
|
+
try:
|
|
37
|
+
queue = QueueDelivery(paths.state("codex_queue_receipts.db"), executable)
|
|
38
|
+
try:
|
|
39
|
+
state, _ = queue.deliver(target, notification_id, message, auto_enter=auto_enter)
|
|
40
|
+
return state, 1 if state == "QUEUED" else 0
|
|
41
|
+
finally:
|
|
42
|
+
queue.close()
|
|
43
|
+
except (OSError, sqlite3.Error, ValueError):
|
|
44
|
+
return "UNKNOWN", -1
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def capture_current_thread():
|
|
48
|
+
"""Accept per-request thread identity only below a native managed daemon.
|
|
49
|
+
|
|
50
|
+
No terminal ownership is inferred. Shared daemon ancestry is used only to
|
|
51
|
+
identify the execution product. Same-OS-user environment forgery is outside
|
|
52
|
+
this local routing check, as for existing terminal route capture.
|
|
53
|
+
"""
|
|
54
|
+
from .terminal_frontend import process_info, native_executable, executable_kind
|
|
55
|
+
import pwd
|
|
56
|
+
try:
|
|
57
|
+
target = current_thread()
|
|
58
|
+
uid = os.getuid()
|
|
59
|
+
home = pwd.getpwuid(uid).pw_dir
|
|
60
|
+
session = os.getsid(0)
|
|
61
|
+
pid = os.getpid()
|
|
62
|
+
seen = set()
|
|
63
|
+
for _ in range(64):
|
|
64
|
+
if pid <= 1 or pid in seen:
|
|
65
|
+
return None
|
|
66
|
+
seen.add(pid)
|
|
67
|
+
info = process_info(pid)
|
|
68
|
+
if info is None or info[0] != uid:
|
|
69
|
+
return None
|
|
70
|
+
if os.getsid(pid) != session:
|
|
71
|
+
executable = native_executable(pid)
|
|
72
|
+
if not executable or executable_kind(executable, home) != "codex":
|
|
73
|
+
return None
|
|
74
|
+
if info[2] != pid or info[4] not in ("??", "?"):
|
|
75
|
+
return None
|
|
76
|
+
args = info[5]
|
|
77
|
+
if os.path.basename(args[0]) != "codex" or args[1:] != [
|
|
78
|
+
"app-server", "--listen", "unix://",
|
|
79
|
+
"--analytics-default-enabled", "--managed-daemon",
|
|
80
|
+
]:
|
|
81
|
+
return None
|
|
82
|
+
if process_info(pid) != info or native_executable(pid) != executable:
|
|
83
|
+
return None
|
|
84
|
+
return target
|
|
85
|
+
pid = info[1]
|
|
86
|
+
except (OSError, ValueError, TypeError, IndexError, subprocess.SubprocessError):
|
|
87
|
+
return None
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def thread_id(value):
|
|
92
|
+
"""Require a canonical UUID; session names are not routing identities."""
|
|
93
|
+
if not isinstance(value, str):
|
|
94
|
+
raise ValueError("Codex thread must be a canonical UUID")
|
|
95
|
+
try:
|
|
96
|
+
valid = str(uuid.UUID(value)) == value
|
|
97
|
+
except ValueError:
|
|
98
|
+
valid = False
|
|
99
|
+
if not valid:
|
|
100
|
+
raise ValueError("Codex thread must be a canonical UUID")
|
|
101
|
+
return value
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def current_thread(environ=None):
|
|
105
|
+
"""Read the execution context, not the shared daemon's inherited tab ID.
|
|
106
|
+
|
|
107
|
+
This is a candidate identity only, not proof of terminal ownership.
|
|
108
|
+
Registration must separately validate the Codex execution context.
|
|
109
|
+
"""
|
|
110
|
+
env = os.environ if environ is None else environ
|
|
111
|
+
return thread_id(env.get("CODEX_THREAD_ID"))
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class QueueDelivery:
|
|
115
|
+
"""One attempted submission per (thread UUID, notification identity).
|
|
116
|
+
|
|
117
|
+
Use a dedicated local receipt database. The caller supplies an absolute,
|
|
118
|
+
trusted Codex executable path; no route may supply executable arguments.
|
|
119
|
+
The caller must reuse the same delivery_id after restart, timeout, or any
|
|
120
|
+
exception, including a receipt UPDATE/commit failure after queue acceptance.
|
|
121
|
+
Never allocate a new identity merely to retry an uncertain submission.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, receipt_db, executable, run=None):
|
|
125
|
+
if not os.path.isabs(executable):
|
|
126
|
+
raise ValueError("Codex executable must be an absolute path")
|
|
127
|
+
self.executable = executable
|
|
128
|
+
self.run = run or subprocess.run
|
|
129
|
+
self.db = sqlite3.connect(receipt_db, timeout=5)
|
|
130
|
+
self.db.execute("""CREATE TABLE IF NOT EXISTS codex_queue_receipts (
|
|
131
|
+
thread_id TEXT NOT NULL,
|
|
132
|
+
delivery_id TEXT NOT NULL,
|
|
133
|
+
state TEXT NOT NULL CHECK(state IN ('PENDING','QUEUED','UNKNOWN')),
|
|
134
|
+
submission_id TEXT,
|
|
135
|
+
PRIMARY KEY(thread_id, delivery_id)
|
|
136
|
+
)""")
|
|
137
|
+
self.db.commit()
|
|
138
|
+
|
|
139
|
+
def close(self):
|
|
140
|
+
self.db.close()
|
|
141
|
+
|
|
142
|
+
def deliver(self, target, delivery_id, message, *, auto_enter=False):
|
|
143
|
+
target = thread_id(target)
|
|
144
|
+
if not isinstance(delivery_id, str) or not delivery_id:
|
|
145
|
+
raise ValueError("stable delivery_id required")
|
|
146
|
+
if not isinstance(message, str) or not message or "\x00" in message:
|
|
147
|
+
raise ValueError("nonempty notification text required")
|
|
148
|
+
# Queue submits input; it has no equivalent to typing without Enter.
|
|
149
|
+
if auto_enter is not True:
|
|
150
|
+
return "DISABLED", None
|
|
151
|
+
|
|
152
|
+
self.db.execute("BEGIN IMMEDIATE")
|
|
153
|
+
try:
|
|
154
|
+
row = self.db.execute(
|
|
155
|
+
"SELECT state, submission_id FROM codex_queue_receipts "
|
|
156
|
+
"WHERE thread_id=? AND delivery_id=?", (target, delivery_id),
|
|
157
|
+
).fetchone()
|
|
158
|
+
if row:
|
|
159
|
+
self.db.commit()
|
|
160
|
+
return ("UNKNOWN" if row[0] == "PENDING" else row[0]), row[1]
|
|
161
|
+
self.db.execute(
|
|
162
|
+
"INSERT INTO codex_queue_receipts VALUES (?, ?, 'PENDING', NULL)",
|
|
163
|
+
(target, delivery_id),
|
|
164
|
+
)
|
|
165
|
+
self.db.commit()
|
|
166
|
+
except BaseException:
|
|
167
|
+
self.db.rollback()
|
|
168
|
+
raise
|
|
169
|
+
|
|
170
|
+
state, submission = "UNKNOWN", None
|
|
171
|
+
try:
|
|
172
|
+
result = self.run(
|
|
173
|
+
[self.executable, "queue", "--remote=unix://",
|
|
174
|
+
f"--thread={target}", f"--message={message}"],
|
|
175
|
+
stdin=subprocess.DEVNULL, capture_output=True, text=True,
|
|
176
|
+
timeout=15, check=False,
|
|
177
|
+
)
|
|
178
|
+
receipt = re.fullmatch(
|
|
179
|
+
r"Queued message ([0-9a-f-]{36}) for thread " + re.escape(target) + r"\.",
|
|
180
|
+
result.stdout.strip(),
|
|
181
|
+
)
|
|
182
|
+
if result.returncode == 0 and receipt:
|
|
183
|
+
submission = thread_id(receipt.group(1))
|
|
184
|
+
state = "QUEUED"
|
|
185
|
+
except (OSError, subprocess.SubprocessError, ValueError):
|
|
186
|
+
# Failure can follow acceptance. Do not translate it to safe retry.
|
|
187
|
+
pass
|
|
188
|
+
self.db.execute(
|
|
189
|
+
"UPDATE codex_queue_receipts SET state=?, submission_id=? "
|
|
190
|
+
"WHERE thread_id=? AND delivery_id=?",
|
|
191
|
+
(state, submission, target, delivery_id),
|
|
192
|
+
)
|
|
193
|
+
self.db.commit()
|
|
194
|
+
return state, submission
|
|
@@ -307,7 +307,7 @@ def format_doorbell_who(senders):
|
|
|
307
307
|
return f"{senders[0]['sender']}"
|
|
308
308
|
|
|
309
309
|
|
|
310
|
-
def deliver_doorbell(node_id, unread):
|
|
310
|
+
def deliver_doorbell(node_id, unread, notification_id=None):
|
|
311
311
|
"""Attempt one delivery. Returns (outcome, adapter, match_count).
|
|
312
312
|
|
|
313
313
|
🔴 It writes no ledger row; that belongs to the caller.
|
|
@@ -364,6 +364,10 @@ def deliver_doorbell(node_id, unread):
|
|
|
364
364
|
return DELIVER_NOT_MINE, "-", 0
|
|
365
365
|
adapter, target, *settings = route
|
|
366
366
|
auto_enter = bool(settings[0]) if settings else False
|
|
367
|
+
if adapter == "codex-queue":
|
|
368
|
+
from .codex_queue import deliver_notification
|
|
369
|
+
result, matched = deliver_notification(target, notification_id, msg, auto_enter)
|
|
370
|
+
return result, adapter, matched
|
|
367
371
|
# 🔴 The adapter dispatch stays an if-chain. It was briefly a name-to-function
|
|
368
372
|
# table and was reverted: the table freezes function references at import
|
|
369
373
|
# time, which stopped tests from substituting them — three of sixteen failed
|
|
@@ -470,7 +474,8 @@ def main():
|
|
|
470
474
|
flush=True,
|
|
471
475
|
)
|
|
472
476
|
continue
|
|
473
|
-
result, adapter, matched = deliver_doorbell(
|
|
477
|
+
result, adapter, matched = deliver_doorbell(
|
|
478
|
+
rcpt, d["unread"], notification_id=f"{rcpt}:{d['high_id']}")
|
|
474
479
|
# 🔴 An announcement overriding observation mode lands here too, and
|
|
475
480
|
# the recorded mode stays as the mode it actually ran in — that is
|
|
476
481
|
# the axis the watermark is measured on. Only the log tag differs.
|
|
@@ -61,7 +61,7 @@ LEDGER_DB = os.environ.get(
|
|
|
61
61
|
# The legacy entry covers rings that went out through the old path. Those did
|
|
62
62
|
# ring, so they suppress. It has its own name so it can be counted; when it
|
|
63
63
|
# reaches zero, that path can be deleted.
|
|
64
|
-
SUPPRESSING = ("SUCCESS", "SUCCESS_LEGACY", "SHADOW_ONLY", "TEXT_ONLY")
|
|
64
|
+
SUPPRESSING = ("SUCCESS", "SUCCESS_LEGACY", "SHADOW_ONLY", "TEXT_ONLY", "QUEUED")
|
|
65
65
|
UNKNOWN_RETRY_SECONDS = 30
|
|
66
66
|
_unknown_attempts = {}
|
|
67
67
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|