tabc 0.2.2__tar.gz → 0.2.4__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.
Files changed (41) hide show
  1. {tabc-0.2.2 → tabc-0.2.4}/CHANGELOG.md +69 -0
  2. {tabc-0.2.2 → tabc-0.2.4}/GUIDE.md +9 -2
  3. {tabc-0.2.2 → tabc-0.2.4}/MCP.md +2 -1
  4. {tabc-0.2.2/tabc.egg-info → tabc-0.2.4}/PKG-INFO +15 -59
  5. {tabc-0.2.2 → tabc-0.2.4}/PYPI.md +13 -58
  6. {tabc-0.2.2 → tabc-0.2.4}/pyproject.toml +2 -1
  7. {tabc-0.2.2 → tabc-0.2.4/tabc.egg-info}/PKG-INFO +15 -59
  8. {tabc-0.2.2 → tabc-0.2.4}/tabc.egg-info/SOURCES.txt +1 -0
  9. {tabc-0.2.2 → tabc-0.2.4}/tabus/bus.py +1 -1
  10. {tabc-0.2.2 → tabc-0.2.4}/tabus/cli.py +93 -4
  11. {tabc-0.2.2 → tabc-0.2.4}/tabus/daemon.py +21 -0
  12. tabc-0.2.4/tabus/tac_lookup.py +42 -0
  13. {tabc-0.2.2 → tabc-0.2.4}/LICENSE +0 -0
  14. {tabc-0.2.2 → tabc-0.2.4}/MANIFEST.in +0 -0
  15. {tabc-0.2.2 → tabc-0.2.4}/setup.cfg +0 -0
  16. {tabc-0.2.2 → tabc-0.2.4}/tabc.egg-info/dependency_links.txt +0 -0
  17. {tabc-0.2.2 → tabc-0.2.4}/tabc.egg-info/entry_points.txt +0 -0
  18. {tabc-0.2.2 → tabc-0.2.4}/tabc.egg-info/requires.txt +0 -0
  19. {tabc-0.2.2 → tabc-0.2.4}/tabc.egg-info/top_level.txt +0 -0
  20. {tabc-0.2.2 → tabc-0.2.4}/tabus/__init__.py +0 -0
  21. {tabc-0.2.2 → tabc-0.2.4}/tabus/admin.py +0 -0
  22. {tabc-0.2.2 → tabc-0.2.4}/tabus/codex_queue.py +0 -0
  23. {tabc-0.2.2 → tabc-0.2.4}/tabus/compat_bcn.py +0 -0
  24. {tabc-0.2.2 → tabc-0.2.4}/tabus/compat_busd.py +0 -0
  25. {tabc-0.2.2 → tabc-0.2.4}/tabus/doorbell.py +0 -0
  26. {tabc-0.2.2 → tabc-0.2.4}/tabus/doorbell_poll.py +0 -0
  27. {tabc-0.2.2 → tabc-0.2.4}/tabus/lang.py +0 -0
  28. {tabc-0.2.2 → tabc-0.2.4}/tabus/locales/en.json +0 -0
  29. {tabc-0.2.2 → tabc-0.2.4}/tabus/mcp_server.py +0 -0
  30. {tabc-0.2.2 → tabc-0.2.4}/tabus/nodekey.py +0 -0
  31. {tabc-0.2.2 → tabc-0.2.4}/tabus/paths.py +0 -0
  32. {tabc-0.2.2 → tabc-0.2.4}/tabus/process_argv.py +0 -0
  33. {tabc-0.2.2 → tabc-0.2.4}/tabus/route_capture.py +0 -0
  34. {tabc-0.2.2 → tabc-0.2.4}/tabus/send_refusal.py +0 -0
  35. {tabc-0.2.2 → tabc-0.2.4}/tabus/sent.py +0 -0
  36. {tabc-0.2.2 → tabc-0.2.4}/tabus/tac_identity.py +0 -0
  37. {tabc-0.2.2 → tabc-0.2.4}/tabus/tac_migration.py +0 -0
  38. {tabc-0.2.2 → tabc-0.2.4}/tabus/tac_refusal.py +0 -0
  39. {tabc-0.2.2 → tabc-0.2.4}/tabus/tac_search.py +0 -0
  40. {tabc-0.2.2 → tabc-0.2.4}/tabus/tac_store.py +0 -0
  41. {tabc-0.2.2 → tabc-0.2.4}/tabus/terminal_frontend.py +0 -0
@@ -1,5 +1,74 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.4 — 2026-10-25
4
+
5
+ ### Added
6
+
7
+ - `tabc send --id <UUID>` sends with a caller-chosen message id, so a retry
8
+ after a lost response is not stored twice. A resend goes through the
9
+ usual send checks first, so unread mail can refuse it with
10
+ `UNREAD_BLOCKED`. After those checks, the same id with the same
11
+ envelope prints `stored id=<id>` and, below it,
12
+ `identical envelope resent — not stored twice`, and exits 0. With a
13
+ changed envelope it fails with `failed: conflict: …` and exits 1. The id
14
+ is checked before any request and written in canonical form. A value
15
+ that is not a UUID fails with
16
+ `failed: --id must be a full message UUID` and exits 1.
17
+
18
+ ### Unchanged
19
+
20
+ - Without `--id`, `send` uses a new random id as before. The daemon is
21
+ unchanged. The MCP `tabc_send` tool already took a caller-chosen id as
22
+ `message_id`; the CLI now offers the same.
23
+
24
+ ## 0.2.3 — 2026-10-17
25
+
26
+ ### Added
27
+
28
+ - The CLI accepts the exact name of an open tac, compared after case folding,
29
+ wherever it takes a tac:
30
+ `tac show`, `add`, `rm`, `rename` (the target, not the new name), `close`,
31
+ `link` (child and parent), `search --tac`, and `send --tac`. It prints
32
+ `tac <name> = <uuid>` on stderr and runs with that UUID.
33
+ - A closed tac, a missing name, or names that only resemble the input run
34
+ nothing. The CLI lists the candidates with UUID and `open` or `closed` date,
35
+ prints `code TAC_NAME_UNRESOLVED`, and exits 2. A partial name is never
36
+ chosen.
37
+ - A read-only, signed server endpoint, `GET /tac_lookup?name=`, returns the
38
+ exact match and up to 20 similar names within the same scope as `/tacs`.
39
+
40
+ ### Unchanged
41
+
42
+ - `tac create` does not look up its name.
43
+ - A canonical lowercase UUID is used as given, without a lookup. Any other
44
+ string is looked up as a name first. Only when no tac has that exact name is
45
+ another spelling of a UUID (upper case, braces, 32 hex digits, `urn:uuid:`)
46
+ sent as that identifier.
47
+ - Every other server endpoint and the MCP adapter take only the UUID. An older
48
+ `tabd` without the lookup endpoint receives the typed value unchanged,
49
+ including another UUID spelling, and answers `TAC_ID_INVALID` as before.
50
+ - The ledger schema, registration, the notifier, and the MCP adapter are
51
+ unchanged.
52
+
53
+ ### Upgrade notes (from 0.2.2)
54
+
55
+ - Nodes, signing keys, messages, delivery states, TACs, routes, and Codex
56
+ queue receipts are kept. No conversion and no re-registration are needed.
57
+ - Stop `tabus.doorbell`, install 0.2.3, restart `tabd`, then start one
58
+ `tabus.doorbell` from the same installation:
59
+
60
+ ```bash
61
+ python -m pip install --upgrade "tabc==0.2.3"
62
+ # Or, for desktop MCP support:
63
+ python -m pip install --upgrade "tabc[mcp]==0.2.3"
64
+ ```
65
+
66
+ - Name lookup needs the restarted `tabd`. Before the restart it answers 404 for
67
+ `/tac_lookup`, the CLI sends the typed name, and the server answers
68
+ `TAC_ID_INVALID`. UUIDs work either way.
69
+ - A running `tabc-mcp` process does not need a restart for this change.
70
+ - After restart, `tabc --version` and `tabd --version` report 0.2.3.
71
+
3
72
  ## 0.2.2 — 2026-10-17
4
73
 
5
74
  ### Changed
@@ -269,12 +269,19 @@ tabd returns a stable `code`, a plain `message`, structured `details`, and a
269
269
  | `REQUEST_TIMEOUT` | the declared request body did not arrive within 30 seconds |
270
270
  | `REQUEST_INCOMPLETE` | the connection ended before the declared body length arrived |
271
271
  | `UNREAD_BLOCKED` | read-before-send is blocking this DM or TAC send |
272
- | `TAC_ID_INVALID` | a TAC operation requires a canonical lowercase UUID |
272
+ | `TAC_ID_INVALID` | a TAC operation requires a canonical lowercase UUID; the server does not resolve names |
273
273
  | `TAC_NOT_FOUND` | no TAC matches the supplied identifier |
274
274
  | `TAC_NAME_TAKEN` | the folded name or a reserved legacy identifier is already used |
275
275
  | `TAC_NAME_INVALID` | the TAC name violates the name rules |
276
276
  | `TAC_NOT_CONVERTED` | a partially converted ledger still contains string TAC IDs |
277
277
 
278
+ From 0.2.3, the CLI adds one code of its own before the requested TAC operation is sent.
279
+ `TAC_NAME_UNRESOLVED` means the TAC name was not exactly one open tac: it is
280
+ closed, missing, or only similar names exist. Nothing was done. The CLI lists the
281
+ candidates with UUID and state on stderr and exits 2. Use a listed UUID, or check
282
+ the name with `tabc tac ls`. The lookup accepts names of 1 to 200 characters. A
283
+ longer value fails the lookup, runs nothing, and exits 1; use the UUID instead.
284
+
278
285
  `retry=never` means change the request. `retry=after_condition` means satisfy the
279
286
  reported condition first. `retry=as_is` means the same request may be tried again,
280
287
  but a write that lost its response must first be checked by message ID. A timeout,
@@ -295,7 +302,7 @@ The same visible text can therefore use different byte counts in NFC and NFD.
295
302
  | dm | a message addressed to named recipients (`send --to`) |
296
303
  | tac | a named topic. Members receive everything posted to it |
297
304
  | tac name | a unique display and search name. It can change |
298
- | tac id | a canonical UUID minted by the server. It addresses the tac and does not change when the name changes |
305
+ | tac id | a canonical UUID minted by the server. It addresses the tac and does not change when the name changes. From 0.2.3 the CLI also resolves an exact open tac name to it |
299
306
  | program | a send-only node. It claims no terminal and is refused as a recipient |
300
307
 
301
308
  ---
@@ -114,7 +114,8 @@ titles without that wait, including messages claimed by an earlier call. Use
114
114
 
115
115
  Use the CLI to create TACs and manage membership. Use the `tac_id` returned by
116
116
  `tabc_tacs`, not its display name. A TAC name can be renamed; its canonical UUID
117
- does not change. TAC history inspection does not change delivery state. Before
117
+ does not change. The CLI resolves an exact open tac name from 0.2.3; the MCP
118
+ adapter does not. TAC history inspection does not change delivery state. Before
118
119
  sending, open and acknowledge the relevant pending deliveries using the inbox
119
120
  tools. tabd enforces the same read-before-send rules as it does for the CLI.
120
121
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tabc
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A shared inbox for AI agents and programs on one machine
5
5
  License: MIT License
6
6
 
@@ -27,6 +27,7 @@ License: MIT License
27
27
  Project-URL: Homepage, https://tabc.sh
28
28
  Project-URL: Source, https://github.com/jultabc/tabc
29
29
  Project-URL: Documentation, https://github.com/jultabc/tabc/blob/main/GUIDE.md
30
+ Project-URL: Changelog, https://github.com/jultabc/tabc/blob/main/CHANGELOG.md
30
31
  Project-URL: Issues, https://github.com/jultabc/tabc/issues
31
32
  Keywords: agents,messaging,ipc,cli
32
33
  Classifier: Development Status :: 3 - Alpha
@@ -51,6 +52,16 @@ This is an experimental package. It is not a security boundary between agents
51
52
  using the same operating-system account. Message delivery does not imply that
52
53
  an agent read, understood, or completed a request.
53
54
 
55
+ ## Updates
56
+
57
+ See the [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md)
58
+ for changes and compatibility notes, or
59
+ [GitHub releases](https://github.com/jultabc/tabc/releases) for published versions.
60
+
61
+ Before updating an existing installation, follow the
62
+ [installation and upgrade instructions](https://github.com/jultabc/tabc#readme).
63
+ Keep a backup and follow the stop, install, and restart sequence for your version.
64
+
54
65
  ## Install
55
66
 
56
67
  ```bash
@@ -105,64 +116,6 @@ from the `pull --mode full` detail block, or a unique prefix of it.
105
116
  Terminal notification is a separate process and does not start with the
106
117
  install; see the repository's guide before turning it on.
107
118
 
108
- ## Updating from 0.2.1
109
-
110
- The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
111
- TACs, routes, and Codex queue receipts are kept. Stop `tabus.doorbell`, install,
112
- restart `tabd`, then start one `tabus.doorbell` from the same installation:
113
-
114
- ```bash
115
- python -m pip install --upgrade "tabc==0.2.2"
116
- # Or, when the local desktop MCP adapter is required:
117
- python -m pip install --upgrade "tabc[mcp]==0.2.2"
118
- ```
119
-
120
- `--kind` is now case-insensitive; `--kind Codex` selects the same Codex queue
121
- route as `--kind codex`. A detached command no longer needs a recognized agent
122
- product: its immediate session-boundary parent qualifies when it owns the
123
- terminal foreground and reads standard input from it. The check proves terminal
124
- ownership, not that the program is an agent. Register again from the intended
125
- terminal and confirm one notification arrives. Codex queue rules are otherwise
126
- unchanged. See the
127
- [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
128
-
129
- ## Updating from 0.2.0
130
-
131
- The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
132
- TACs, and routes are kept. Stop `tabus.doorbell`, install, restart `tabd`, then
133
- start one `tabus.doorbell` from the same installation:
134
-
135
- ```bash
136
- python -m pip install --upgrade "tabc==0.2.1"
137
- # Or, when the local desktop MCP adapter is required:
138
- python -m pip install --upgrade "tabc[mcp]==0.2.1"
139
- ```
140
-
141
- `register` now requires `--kind`. Update scripts that omit it. Codex agents
142
- register again from each conversation after the notifier is replaced. The
143
- notifier creates `codex_queue_receipts.db` in the tabc home directory on its
144
- first Codex queue submission. See the
145
- [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
146
-
147
- ## Updating from 0.1.6
148
-
149
- The upgrade preserves nodes, messages, delivery states, TAC membership, names,
150
- and labels. Stop `tabus.doorbell` and `tabd`. Confirm that the ledger's `-wal`
151
- file is absent or empty (0 bytes), then keep a backup before installing. A
152
- remaining empty WAL or SHM file is safe. Do not continue while the WAL is
153
- non-empty:
154
-
155
- ```bash
156
- python -m pip install --upgrade "tabc==0.2.0"
157
- # Or, when the local desktop MCP adapter is required:
158
- python -m pip install --upgrade "tabc[mcp]==0.2.0"
159
- ```
160
-
161
- Existing string TAC IDs are not converted during installation. Follow the
162
- repository README's 0.2.0 ledger conversion procedure when converting TAC IDs
163
- to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
164
- Then follow "Updating from 0.2.0" and "Updating from 0.2.1".
165
-
166
119
  ## Commands
167
120
 
168
121
  - `tabc register --node alice --kind codex`: register an agent.
@@ -175,6 +128,9 @@ Then follow "Updating from 0.2.0" and "Updating from 0.2.1".
175
128
  Register each recipient before sending. tac creators must also join their topic.
176
129
  Since 0.2.0, the server mints a UUID for each new tac. Use that UUID for membership,
177
130
  sending, reading, renaming and closing; use the unique name for display and search.
131
+ From 0.2.3, the CLI also accepts the exact name of an open tac and prints the UUID
132
+ it used. A closed, missing or only similar name runs nothing and lists candidates
133
+ with `TAC_NAME_UNRESOLVED`. The MCP adapter still takes only the UUID.
178
134
  Run `tabc --help` for available commands. The local server is `tabd`; management
179
135
  commands are provided separately by `tabm`.
180
136
  The source repository carries the operating guide and the security notes.
@@ -9,6 +9,16 @@ This is an experimental package. It is not a security boundary between agents
9
9
  using the same operating-system account. Message delivery does not imply that
10
10
  an agent read, understood, or completed a request.
11
11
 
12
+ ## Updates
13
+
14
+ See the [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md)
15
+ for changes and compatibility notes, or
16
+ [GitHub releases](https://github.com/jultabc/tabc/releases) for published versions.
17
+
18
+ Before updating an existing installation, follow the
19
+ [installation and upgrade instructions](https://github.com/jultabc/tabc#readme).
20
+ Keep a backup and follow the stop, install, and restart sequence for your version.
21
+
12
22
  ## Install
13
23
 
14
24
  ```bash
@@ -63,64 +73,6 @@ from the `pull --mode full` detail block, or a unique prefix of it.
63
73
  Terminal notification is a separate process and does not start with the
64
74
  install; see the repository's guide before turning it on.
65
75
 
66
- ## Updating from 0.2.1
67
-
68
- The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
69
- TACs, routes, and Codex queue receipts are kept. Stop `tabus.doorbell`, install,
70
- restart `tabd`, then start one `tabus.doorbell` from the same installation:
71
-
72
- ```bash
73
- python -m pip install --upgrade "tabc==0.2.2"
74
- # Or, when the local desktop MCP adapter is required:
75
- python -m pip install --upgrade "tabc[mcp]==0.2.2"
76
- ```
77
-
78
- `--kind` is now case-insensitive; `--kind Codex` selects the same Codex queue
79
- route as `--kind codex`. A detached command no longer needs a recognized agent
80
- product: its immediate session-boundary parent qualifies when it owns the
81
- terminal foreground and reads standard input from it. The check proves terminal
82
- ownership, not that the program is an agent. Register again from the intended
83
- terminal and confirm one notification arrives. Codex queue rules are otherwise
84
- unchanged. See the
85
- [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
86
-
87
- ## Updating from 0.2.0
88
-
89
- The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
90
- TACs, and routes are kept. Stop `tabus.doorbell`, install, restart `tabd`, then
91
- start one `tabus.doorbell` from the same installation:
92
-
93
- ```bash
94
- python -m pip install --upgrade "tabc==0.2.1"
95
- # Or, when the local desktop MCP adapter is required:
96
- python -m pip install --upgrade "tabc[mcp]==0.2.1"
97
- ```
98
-
99
- `register` now requires `--kind`. Update scripts that omit it. Codex agents
100
- register again from each conversation after the notifier is replaced. The
101
- notifier creates `codex_queue_receipts.db` in the tabc home directory on its
102
- first Codex queue submission. See the
103
- [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
104
-
105
- ## Updating from 0.1.6
106
-
107
- The upgrade preserves nodes, messages, delivery states, TAC membership, names,
108
- and labels. Stop `tabus.doorbell` and `tabd`. Confirm that the ledger's `-wal`
109
- file is absent or empty (0 bytes), then keep a backup before installing. A
110
- remaining empty WAL or SHM file is safe. Do not continue while the WAL is
111
- non-empty:
112
-
113
- ```bash
114
- python -m pip install --upgrade "tabc==0.2.0"
115
- # Or, when the local desktop MCP adapter is required:
116
- python -m pip install --upgrade "tabc[mcp]==0.2.0"
117
- ```
118
-
119
- Existing string TAC IDs are not converted during installation. Follow the
120
- repository README's 0.2.0 ledger conversion procedure when converting TAC IDs
121
- to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
122
- Then follow "Updating from 0.2.0" and "Updating from 0.2.1".
123
-
124
76
  ## Commands
125
77
 
126
78
  - `tabc register --node alice --kind codex`: register an agent.
@@ -133,6 +85,9 @@ Then follow "Updating from 0.2.0" and "Updating from 0.2.1".
133
85
  Register each recipient before sending. tac creators must also join their topic.
134
86
  Since 0.2.0, the server mints a UUID for each new tac. Use that UUID for membership,
135
87
  sending, reading, renaming and closing; use the unique name for display and search.
88
+ From 0.2.3, the CLI also accepts the exact name of an open tac and prints the UUID
89
+ it used. A closed, missing or only similar name runs nothing and lists candidates
90
+ with `TAC_NAME_UNRESOLVED`. The MCP adapter still takes only the UUID.
136
91
  Run `tabc --help` for available commands. The local server is `tabd`; management
137
92
  commands are provided separately by `tabm`.
138
93
  The source repository carries the operating guide and the security notes.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tabc"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "A shared inbox for AI agents and programs on one machine"
9
9
  readme = "PYPI.md"
10
10
  requires-python = ">=3.9"
@@ -40,6 +40,7 @@ mcp = ["mcp>=1.27.1,<2; python_version >= '3.10'"]
40
40
  Homepage = "https://tabc.sh"
41
41
  Source = "https://github.com/jultabc/tabc"
42
42
  Documentation = "https://github.com/jultabc/tabc/blob/main/GUIDE.md"
43
+ Changelog = "https://github.com/jultabc/tabc/blob/main/CHANGELOG.md"
43
44
  Issues = "https://github.com/jultabc/tabc/issues"
44
45
 
45
46
  [project.scripts]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: tabc
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: A shared inbox for AI agents and programs on one machine
5
5
  License: MIT License
6
6
 
@@ -27,6 +27,7 @@ License: MIT License
27
27
  Project-URL: Homepage, https://tabc.sh
28
28
  Project-URL: Source, https://github.com/jultabc/tabc
29
29
  Project-URL: Documentation, https://github.com/jultabc/tabc/blob/main/GUIDE.md
30
+ Project-URL: Changelog, https://github.com/jultabc/tabc/blob/main/CHANGELOG.md
30
31
  Project-URL: Issues, https://github.com/jultabc/tabc/issues
31
32
  Keywords: agents,messaging,ipc,cli
32
33
  Classifier: Development Status :: 3 - Alpha
@@ -51,6 +52,16 @@ This is an experimental package. It is not a security boundary between agents
51
52
  using the same operating-system account. Message delivery does not imply that
52
53
  an agent read, understood, or completed a request.
53
54
 
55
+ ## Updates
56
+
57
+ See the [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md)
58
+ for changes and compatibility notes, or
59
+ [GitHub releases](https://github.com/jultabc/tabc/releases) for published versions.
60
+
61
+ Before updating an existing installation, follow the
62
+ [installation and upgrade instructions](https://github.com/jultabc/tabc#readme).
63
+ Keep a backup and follow the stop, install, and restart sequence for your version.
64
+
54
65
  ## Install
55
66
 
56
67
  ```bash
@@ -105,64 +116,6 @@ from the `pull --mode full` detail block, or a unique prefix of it.
105
116
  Terminal notification is a separate process and does not start with the
106
117
  install; see the repository's guide before turning it on.
107
118
 
108
- ## Updating from 0.2.1
109
-
110
- The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
111
- TACs, routes, and Codex queue receipts are kept. Stop `tabus.doorbell`, install,
112
- restart `tabd`, then start one `tabus.doorbell` from the same installation:
113
-
114
- ```bash
115
- python -m pip install --upgrade "tabc==0.2.2"
116
- # Or, when the local desktop MCP adapter is required:
117
- python -m pip install --upgrade "tabc[mcp]==0.2.2"
118
- ```
119
-
120
- `--kind` is now case-insensitive; `--kind Codex` selects the same Codex queue
121
- route as `--kind codex`. A detached command no longer needs a recognized agent
122
- product: its immediate session-boundary parent qualifies when it owns the
123
- terminal foreground and reads standard input from it. The check proves terminal
124
- ownership, not that the program is an agent. Register again from the intended
125
- terminal and confirm one notification arrives. Codex queue rules are otherwise
126
- unchanged. See the
127
- [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
128
-
129
- ## Updating from 0.2.0
130
-
131
- The ledger schema is unchanged. Nodes, signing keys, messages, delivery states,
132
- TACs, and routes are kept. Stop `tabus.doorbell`, install, restart `tabd`, then
133
- start one `tabus.doorbell` from the same installation:
134
-
135
- ```bash
136
- python -m pip install --upgrade "tabc==0.2.1"
137
- # Or, when the local desktop MCP adapter is required:
138
- python -m pip install --upgrade "tabc[mcp]==0.2.1"
139
- ```
140
-
141
- `register` now requires `--kind`. Update scripts that omit it. Codex agents
142
- register again from each conversation after the notifier is replaced. The
143
- notifier creates `codex_queue_receipts.db` in the tabc home directory on its
144
- first Codex queue submission. See the
145
- [changelog](https://github.com/jultabc/tabc/blob/main/CHANGELOG.md).
146
-
147
- ## Updating from 0.1.6
148
-
149
- The upgrade preserves nodes, messages, delivery states, TAC membership, names,
150
- and labels. Stop `tabus.doorbell` and `tabd`. Confirm that the ledger's `-wal`
151
- file is absent or empty (0 bytes), then keep a backup before installing. A
152
- remaining empty WAL or SHM file is safe. Do not continue while the WAL is
153
- non-empty:
154
-
155
- ```bash
156
- python -m pip install --upgrade "tabc==0.2.0"
157
- # Or, when the local desktop MCP adapter is required:
158
- python -m pip install --upgrade "tabc[mcp]==0.2.0"
159
- ```
160
-
161
- Existing string TAC IDs are not converted during installation. Follow the
162
- repository README's 0.2.0 ledger conversion procedure when converting TAC IDs
163
- to UUIDs. Without conversion, restart `tabd` first, then `tabus.doorbell`.
164
- Then follow "Updating from 0.2.0" and "Updating from 0.2.1".
165
-
166
119
  ## Commands
167
120
 
168
121
  - `tabc register --node alice --kind codex`: register an agent.
@@ -175,6 +128,9 @@ Then follow "Updating from 0.2.0" and "Updating from 0.2.1".
175
128
  Register each recipient before sending. tac creators must also join their topic.
176
129
  Since 0.2.0, the server mints a UUID for each new tac. Use that UUID for membership,
177
130
  sending, reading, renaming and closing; use the unique name for display and search.
131
+ From 0.2.3, the CLI also accepts the exact name of an open tac and prints the UUID
132
+ it used. A closed, missing or only similar name runs nothing and lists candidates
133
+ with `TAC_NAME_UNRESOLVED`. The MCP adapter still takes only the UUID.
178
134
  Run `tabc --help` for available commands. The local server is `tabd`; management
179
135
  commands are provided separately by `tabm`.
180
136
  The source repository carries the operating guide and the security notes.
@@ -30,6 +30,7 @@ tabus/route_capture.py
30
30
  tabus/send_refusal.py
31
31
  tabus/sent.py
32
32
  tabus/tac_identity.py
33
+ tabus/tac_lookup.py
33
34
  tabus/tac_migration.py
34
35
  tabus/tac_refusal.py
35
36
  tabus/tac_search.py
@@ -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.2"
77
+ __version__ = "0.2.4"
78
78
  PROTOCOL_VERSION = "tabus.v1"
79
79
 
80
80
  # TTLs. Past these, a derived status counts as old.
@@ -94,6 +94,7 @@ MAPPING = {
94
94
  "tac_link": "/tac_link",
95
95
  "tac_rename": "/tac_rename",
96
96
  "tac_check": "/tac_check",
97
+ "tac_lookup": "/tac_lookup",
97
98
  # name uses only the register endpoint; setting the session title is local
98
99
  }
99
100
 
@@ -436,10 +437,27 @@ def _refusal_line(r):
436
437
 
437
438
 
438
439
  def fn_send(a):
440
+ # 🔴 --id lets a caller retry without storing twice. The daemon treats the same id
441
+ # with the same envelope as a resend and the same id with a changed envelope as
442
+ # a conflict, so the caller keeps the id and the whole envelope from before the
443
+ # first attempt. The daemon does not compare the sending mode itself (tac or
444
+ # broadcast), only the resolved recipients, so the caller keeps that fixed too.
445
+ # Full UUIDs only, written in canonical form, so that one id typed two ways
446
+ # cannot become two messages. Checked first, before the tac lookup or any
447
+ # other request.
448
+ mid = str(uuid.uuid4())
449
+ if getattr(a, "id", None) is not None:
450
+ try:
451
+ mid = str(uuid.UUID(a.id))
452
+ except ValueError:
453
+ print("failed: --id must be a full message UUID")
454
+ sys.exit(1)
439
455
  body = a.body
440
456
  if a.body_file:
441
457
  body = open(a.body_file, encoding="utf-8").read()
442
458
  group = getattr(a, "tac", None)
459
+ if group:
460
+ group = _designate_tac(group, a.sender)
443
461
  bcast = getattr(a, "broadcast", False)
444
462
  requested = _recipients(a.to)
445
463
  # 🔴 Recipients come from --to, --tac, or --broadcast. With none of the three
@@ -451,7 +469,6 @@ def fn_send(a):
451
469
  # send is authenticated by the request signature (call() signs every request),
452
470
  # and the daemon requires `from` to equal the acting node, so no message-level
453
471
  # signature is sent — a forged sender cannot get past the request auth.
454
- mid = str(uuid.uuid4())
455
472
  payload = {
456
473
  "from": a.sender,
457
474
  "to": requested,
@@ -806,6 +823,64 @@ def fn_restore(a):
806
823
  sys.exit(1)
807
824
 
808
825
 
826
+ def _tac_state(row):
827
+ closed = row.get("closed_at")
828
+ return f"closed {closed[:10]}" if closed else "open"
829
+
830
+
831
+ def _designate_tac(given, node):
832
+ """A tac argument → the identifier to send. A name is looked up here, in the client.
833
+
834
+ 🔴 The server still takes only the identifier. A name is turned into one before the
835
+ request, and the identifier used is printed on stderr, so a later rename cannot
836
+ make the record ambiguous. Only an exact name of an open tac is used. A closed tac,
837
+ or no exact match, prints the candidates with identifier and open/closed and
838
+ exits 2 without doing anything. A partial name is never chosen for the caller.
839
+ """
840
+ from .tac_identity import is_uuid_id
841
+
842
+ if not isinstance(given, str) or not given.strip() or is_uuid_id(given):
843
+ return given
844
+ try:
845
+ # Another UUID spelling (uppercase, braces, 32 hex digits) of an identifier.
846
+ spelled = str(uuid.UUID(given.strip()))
847
+ except ValueError:
848
+ spelled = None
849
+ from urllib.parse import urlencode
850
+
851
+ # 🔴 The name is asked first, even for a string uuid.UUID can read: a tac may be
852
+ # named "0123456789abcdef0123456789abcdef", and that name must not turn into
853
+ # some other tac's identifier (jack 4c84df2c). Only with no tac of that exact
854
+ # name is the string read as an identifier spelling.
855
+ code, r = call("GET", MAPPING["tac_lookup"] + "?" + urlencode({"name": given}), node=node)
856
+ if code == 404:
857
+ # 🔴 A server before this lookup existed: send the string as typed, and the
858
+ # server answers TAC_ID_INVALID as it always did.
859
+ return given
860
+ if code != 200 or r.get("error"):
861
+ print(f"failed: tac lookup: {r.get('error') or r}", file=sys.stderr)
862
+ sys.exit(1)
863
+ exact, similar = r.get("exact"), r.get("similar") or []
864
+ if exact is None and spelled is not None:
865
+ return spelled
866
+ if exact and not exact.get("closed_at"):
867
+ print(f"tac {exact['name']} = {exact['tac_id']}", file=sys.stderr)
868
+ return exact["tac_id"]
869
+ rows = ([exact] if exact else []) + similar
870
+ if exact:
871
+ print(f"tac {exact['name']} is closed; nothing was done. Use its identifier to read it:",
872
+ file=sys.stderr)
873
+ elif rows:
874
+ print(f"no tac is named {given!r}; nothing was done. Similar names:", file=sys.stderr)
875
+ else:
876
+ print(f"no tac is named {given!r}; nothing was done (list them with: tabc tac ls)",
877
+ file=sys.stderr)
878
+ for row in rows:
879
+ print(f" {row['name']:<24} {row['tac_id']} {_tac_state(row)}", file=sys.stderr)
880
+ print(" code TAC_NAME_UNRESOLVED", file=sys.stderr)
881
+ sys.exit(2)
882
+
883
+
809
884
  def fn_tac(a):
810
885
  """tac — a named topic for group delivery.
811
886
 
@@ -818,6 +893,12 @@ def fn_tac(a):
818
893
  from another, which covers direction, forks, and merges.
819
894
  """
820
895
  action = a.action
896
+ # 🔴 Every subcommand that takes an existing tac accepts a name here and sends the
897
+ # identifier. create takes a new name and is not looked up.
898
+ if action in ("show", "add", "rm", "rename", "close", "link") or (action == "search" and a.tac):
899
+ a.tac = _designate_tac(a.tac, a.node)
900
+ if action == "link":
901
+ a.member = _designate_tac(a.member, a.node)
821
902
  if action == "search":
822
903
  from urllib.parse import urlencode
823
904
 
@@ -1135,7 +1216,7 @@ COMMANDS = {
1135
1216
  ("--tac",),
1136
1217
  dict(
1137
1218
  default=None,
1138
- help="send to a tac, fanning out to every member (instead of --to)",
1219
+ help="send to a tac, by identifier or exact name, fanning out to every member (instead of --to)",
1139
1220
  ),
1140
1221
  ),
1141
1222
  (
@@ -1149,6 +1230,13 @@ COMMANDS = {
1149
1230
  (("--body",), dict()),
1150
1231
  (("--body-file",), dict()),
1151
1232
  (("--priority",), dict(default="next", choices=["now", "next", "batch"])),
1233
+ (
1234
+ ("--id",),
1235
+ dict(
1236
+ default=None,
1237
+ help="message UUID for a retry-safe send. The same id with the same envelope is not stored twice; with a changed envelope it is refused as a conflict",
1238
+ ),
1239
+ ),
1152
1240
  ],
1153
1241
  ),
1154
1242
  "who": (fn_who, "who is present, and how much is pending", [(("--node",), dict(default=None, help="acting node; falls back to TABC_NODE"))]),
@@ -1235,8 +1323,9 @@ COMMANDS = {
1235
1323
  "close <tac> --summary | link <child> <parent>",
1236
1324
  ),
1237
1325
  ),
1238
- (("tac",), dict(nargs="?", help="the tac, by its identifier (not needed for ls); "
1239
- "find it with ls or search; for link this is the child")),
1326
+ (("tac",), dict(nargs="?", help="the tac, by its identifier or its exact name (not needed for ls); "
1327
+ "a name is looked up and the identifier used is printed; "
1328
+ "for create this is the new name; for link this is the child")),
1240
1329
  (("member",), dict(nargs="?", help="node for add and rm; for link this is the parent; "
1241
1330
  "for rename this is the new name")),
1242
1331
  (("--description",), dict(default=None, help="what the tac is for, for create")),
@@ -498,6 +498,27 @@ class BusHandler(BaseHTTPRequestHandler):
498
498
  con.close()
499
499
  return self._json(200, {"hits": hits})
500
500
 
501
+ if path == "/tac_lookup":
502
+ # 🔴 Read-only: which tac carries this name now. Same scope as /tacs. The
503
+ # client sends the identifier this returns; every other endpoint still
504
+ # takes only the identifier.
505
+ if not self._authorized():
506
+ return self._json(401, {"error": "token"})
507
+ from urllib.parse import parse_qs
508
+ from .tac_lookup import lookup
509
+
510
+ params = parse_qs(query, keep_blank_values=True)
511
+ if any(len(values) != 1 for values in params.values()):
512
+ return self._json(400, {"error": "duplicate query parameters"})
513
+ con = self._connect()
514
+ try:
515
+ exact, similar = lookup(con, params.get("name", [None])[0])
516
+ except (ValueError, TypeError) as exc:
517
+ return self._json(400, {"error": str(exc)})
518
+ finally:
519
+ con.close()
520
+ return self._json(200, {"exact": exact, "similar": similar})
521
+
501
522
  if path == "/tac_messages":
502
523
  # 🔴 tac contents, read-only, and member-only: a tac is a conversation you
503
524
  # read by joining it, so only its members see the body. A non-member is
@@ -0,0 +1,42 @@
1
+ """Find a tac by name, read-only. The client turns a typed name into an identifier here.
2
+
3
+ 🔴 Designation stays by identifier. This endpoint does not make a name a designation:
4
+ every other endpoint still refuses a name with TAC_ID_INVALID. It answers one question,
5
+ "which tac carries this name now", so the client can print the identifier it is about
6
+ to use and then send that identifier. A rename moves the name; the identifier the
7
+ client printed is what the request used.
8
+
9
+ Names are unique on the folded key (ux_tacs_name_key), closed tacs included, so an
10
+ exact match is at most one row. `similar` lists the rows whose folded name contains
11
+ the folded query, for a caller who typed part of a name. Neither changes state.
12
+ """
13
+
14
+ from .tac_identity import name_key
15
+
16
+ SIMILAR_LIMIT = 20
17
+
18
+
19
+ def _row(r):
20
+ return {"tac_id": r["tac_id"], "name": r["name"], "label": r["label"],
21
+ "closed_at": r["closed_at"]}
22
+
23
+
24
+ def lookup(con, name, limit=SIMILAR_LIMIT):
25
+ """(exact row or None, similar rows). Raises ValueError on a bad query."""
26
+ if not isinstance(name, str) or not name.strip() or len(name) > 200:
27
+ raise ValueError("name must contain 1..200 characters")
28
+ columns = {row[1] for row in con.execute("PRAGMA table_info(tacs)")}
29
+ # 🔴 A ledger before the identity migration has no name column. There is nothing
30
+ # to look up there: the identifier is the string a caller types.
31
+ if not {"name", "name_key"} <= columns:
32
+ return None, []
33
+ key = name_key(name.strip())
34
+ rows = con.execute(
35
+ "SELECT tac_id, name, label, closed_at, name_key FROM tacs ORDER BY name"
36
+ ).fetchall()
37
+ exact = next((r for r in rows if r["name_key"] == key), None)
38
+ # 🔴 Compared on name_key in Python, as tac_store.search_name does: sqlite lower()
39
+ # folds ASCII only.
40
+ similar = [r for r in rows if r["name_key"] and key in r["name_key"]
41
+ and (exact is None or r["tac_id"] != exact["tac_id"])][:limit]
42
+ return (_row(exact) if exact else None), [_row(r) for r in similar]
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