Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a03397-4d51-7fd1-8ff2-946eb22ea2bc
This commit is contained in:
parent
00f04da5f6
commit
e284c9f63c
16 changed files with 233 additions and 49 deletions
46
README.md
46
README.md
|
|
@ -71,19 +71,36 @@ input. It is intended for coding-agent interfaces that queue user prompts:
|
|||
tamq --mode pushy --command codex flex-auth audit-core
|
||||
```
|
||||
|
||||
The submitted line is sanitized and sender-labelled, then followed by exactly
|
||||
one Enter key:
|
||||
For newly created windows with an explicit command, pushy mode automatically
|
||||
places that command behind tamq's transparent PTY tap. Inside the coding-agent
|
||||
input, use `#repo:` to avoid interfaces that reserve `@` for file mentions or
|
||||
templates:
|
||||
|
||||
```text
|
||||
#flex-auth: please review the auth boundary [m-...]
|
||||
#audit-core: Please review the authentication change.
|
||||
```
|
||||
|
||||
This leading `#` makes an empty ordinary shell prompt treat the line as a
|
||||
comment. Pushy mode cannot determine whether a pane is an agent, a shell, or
|
||||
whether someone is already editing input: it can append to that input and
|
||||
submit the combined line. Use it only for panes whose occupant is known to
|
||||
accept or queue asynchronous prompts. Switching an existing endpoint to pushy
|
||||
may also submit pending messages that have never been displayed.
|
||||
The tap observes this complete line and queues the same durable route as
|
||||
`@audit-core: ...`; it forwards the source input unchanged to its occupant.
|
||||
`#repo:` is therefore an agent/tap convention, not a shell command—at an
|
||||
ordinary shell it remains a comment. Existing windows are never respawned, so
|
||||
recreate a session that was originally started without pushy observation.
|
||||
|
||||
The submitted target line is sanitized, sender-labelled, deliberately
|
||||
non-routable, and then followed by exactly one Enter key:
|
||||
|
||||
```text
|
||||
# from flex-auth: please review the auth boundary [m-...]
|
||||
```
|
||||
|
||||
The `# from` shape cannot match the outbound `#repo:` grammar, preventing a
|
||||
tapped recipient from bouncing the message back. Its leading `#` also makes an
|
||||
empty ordinary shell prompt treat the line as a comment. Pushy mode cannot
|
||||
determine whether a pane is an agent, a shell, or whether someone is already
|
||||
editing input: it can append to that input and submit the combined line. Use it
|
||||
only for panes whose occupant is known to accept or queue asynchronous prompts.
|
||||
Switching an existing endpoint to pushy may also submit pending messages that
|
||||
have never been displayed.
|
||||
|
||||
## Exchange messages manually
|
||||
|
||||
|
|
@ -227,7 +244,7 @@ later phase.
|
|||
2. the local broker assigns endpoint/source identity and durably queues intent;
|
||||
3. Explicit `tamq tap` mode is a full-duplex PTY proxy around a command. It forwards
|
||||
bytes unchanged in raw terminal mode, propagates terminal resize and lifecycle
|
||||
signals, and observes complete input lines for `@repo:` routing.
|
||||
signals, and observes complete input lines for `@repo:`/`#repo:` routing.
|
||||
|
||||
This keeps tmux-specific topology concerns separate from reusable terminal I/O
|
||||
observation and message identity.
|
||||
|
|
@ -237,10 +254,11 @@ the target pane's PTY output and stays pending in the durable inbox until
|
|||
acknowledged. `--mode inbox` selects inbox-only manual mode. Neither becomes
|
||||
pane input. Experimental `--mode pushy` sends one checked tmux command list
|
||||
containing sanitized literal input followed by Enter, then records the message
|
||||
as `injected`. The older full-duplex pane-input path remains available only
|
||||
with the explicit `tamq start --tap --command ...` opt-in. Both input paths
|
||||
remain subject to the retry and acknowledgement limitations tracked by
|
||||
`TAMQ-WP-0003`.
|
||||
as `injected`. When pushy mode creates explicitly commanded windows, the same
|
||||
PTY tap observes outbound `@repo:` and `#repo:` lines without rewriting them.
|
||||
The older pane-delivery experiment remains available only with the explicit
|
||||
`tamq start --tap --command ...` opt-in. Both input paths remain subject to the
|
||||
retry and acknowledgement limitations tracked by `TAMQ-WP-0003`.
|
||||
The visible endpoint label is `tmux-amq-<PID>`; each boot also receives an
|
||||
instance nonce so PID reuse cannot collide with prior leases or receipts.
|
||||
|
||||
|
|
|
|||
17
SCOPE.md
17
SCOPE.md
|
|
@ -22,10 +22,11 @@ tamq does not choose or infer them.
|
|||
- Sanitized one-time output notifications through target tmux pane PTYs, with
|
||||
inbox-only delivery as an explicit option and no foreground-process input.
|
||||
- Explicit experimental pushy delivery that submits a sanitized comment and
|
||||
Enter to target pane input for known queue-capable interactive programs.
|
||||
Enter to target pane input for known queue-capable interactive programs,
|
||||
with `#repo:` routing observed through the PTY tap.
|
||||
- Explicit opt-in control-mode pane delivery and the full-duplex `tamq tap` PTY
|
||||
broker for integration experiments.
|
||||
- Exact `gita` repository validation and direct `@repo: message` routing.
|
||||
- Exact `gita` repository validation and direct `@repo:`/`#repo:` routing.
|
||||
- Unix-socket operations for local clients and a future coordination-engine
|
||||
adapter.
|
||||
- Policy profiles, safety-gated retries, local diagnostics, tests, packaging,
|
||||
|
|
@ -51,12 +52,12 @@ transport.
|
|||
|
||||
| Intent capability | State | Evidence and remaining gap |
|
||||
| --- | --- | --- |
|
||||
| Direct repository addressing | Implemented for local alpha | Exact `gita` validation, per-session `@repo`/`@repo:` commands, a bare `@` composer with latest-counterparty default and Tab recipient cycling, and long-form parsing are covered without modifying shell configuration. |
|
||||
| Direct repository addressing | Implemented for local alpha | Exact `gita` validation, per-session `@repo`/`@repo:` shell commands, tapped `#repo:` agent-input routing, a bare `@` composer with latest-counterparty default and Tab recipient cycling, and long-form parsing are covered. |
|
||||
| Durable, inspectable local queue | Implemented | SQLite history, manual inbox, leases, endpoint records, inspect/history, JSONL export/replay, acknowledgement, and purge are present. |
|
||||
| Local socket service | Implemented | Peer-credential checks and structured ping/register/send/history/ack/endpoints/disconnect operations are tested. |
|
||||
| Neutral tmux session lifecycle | Implemented for local alpha | Repository-first startup opens untouched shells at exact gita paths, exports per-window identity, and runs no initial command unless `--command` is explicit. Stable reuse, service restart, and cleanup are covered by the installed-package test. |
|
||||
| Safe manual messaging | Implemented for local alpha | Normal endpoints write one sanitized comment above a stable shell input row without injecting stdin; conservative fallback handles the first row and alternate screens. Messages remain pending until acknowledgement. Inbox-only manual mode is explicit with `--no-display`. |
|
||||
| Experimental pushy delivery | Explicit opt-in | `--mode pushy` submits one sanitized sender-labelled comment plus Enter through a checked tmux command. It marks successful delivery `injected`, but cannot identify pane occupants or protect input already being edited. |
|
||||
| Experimental pushy delivery | Explicit opt-in | `--mode pushy --command ...` observes outbound `#repo:`/`@repo:` lines and submits a non-routable `# from sender:` envelope plus Enter through a checked tmux command. It marks successful delivery `injected`, but cannot identify pane occupants or protect input already being edited. |
|
||||
| Full-duplex input observation | Explicit opt-in | `--tap --command ...` enables the PTY integration path. It is absent from neutral startup and remains covered for geometry, resize, raw mouse input, and lifecycle behavior. |
|
||||
| Bounded retry behavior | Not enforced | Failed output or injection remains pending and becomes claimable after lease expiry, but no attempt counter or terminal failure state applies the configured cap. |
|
||||
| Acknowledgement policy | Partially implemented | Terminal output remains pending until explicit acknowledgement, while legacy pane injection becomes `injected`; the configured `delivery_ack_mode` does not yet govern both paths. |
|
||||
|
|
@ -70,8 +71,10 @@ shells at exact gita paths without sending initial keystrokes. The test proved
|
|||
per-window repository identity, stable reuse, shell-native addressing,
|
||||
latest-counterparty replies, comment-safe target output above an unchanged
|
||||
partial input line and cursor, inbox/filter/ack exchange, zero target-input
|
||||
mutation, service restart, endpoint disappearance, explicit initial-command
|
||||
startup, and cleanup.
|
||||
mutation in safe modes, service restart, endpoint disappearance, explicit
|
||||
initial-command startup, and cleanup. Isolated pushy-mode coverage additionally
|
||||
proves one `#repo:` route, one non-routable target submission, and no feedback
|
||||
message.
|
||||
|
||||
Suitable today:
|
||||
|
||||
|
|
@ -95,7 +98,7 @@ Not yet suitable:
|
|||
and stronger process-supervision evidence.
|
||||
- Cross-host messaging or use as a general-purpose broker.
|
||||
|
||||
The suite currently has 117 passing tests and 77% statement coverage. Coverage
|
||||
The suite currently has 120 passing tests and 77% statement coverage. Coverage
|
||||
is strongest in durable storage and registry handling, and weakest in the PTY
|
||||
tap and CLI orchestration; PTY statement coverage increased from 23% to 33%,
|
||||
while subprocess behavior is primarily proven by the real-tmux test. The
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
| workplan | TAMQ-WP-0008 | finished | — | workplans/TAMQ-WP-0008-reply-shorthand-stable-output.md |
|
||||
| workplan | TAMQ-WP-0009 | finished | — | workplans/TAMQ-WP-0009-interactive-recipient-composer.md |
|
||||
| workplan | TAMQ-WP-0010 | finished | — | workplans/TAMQ-WP-0010-experimental-pushy-delivery.md |
|
||||
| workplan | TAMQ-WP-0011 | active | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
| workplan | TAMQ-WP-0011 | finished | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
| task | TAMQ-WP-ADHOC-2026-08-24-T01 | done | — | workplans/ADHOC-2026-08-24.md |
|
||||
| task | TAMQ-WP-0001-T01 | done | — | workplans/TAMQ-WP-0001-statehub-bootstrap.md |
|
||||
| task | TAMQ-WP-0001-T02 | done | — | workplans/TAMQ-WP-0001-statehub-bootstrap.md |
|
||||
|
|
@ -60,6 +60,6 @@
|
|||
| task | TAMQ-WP-0010-T01 | done | — | workplans/TAMQ-WP-0010-experimental-pushy-delivery.md |
|
||||
| task | TAMQ-WP-0010-T02 | done | — | workplans/TAMQ-WP-0010-experimental-pushy-delivery.md |
|
||||
| task | TAMQ-WP-0010-T03 | done | — | workplans/TAMQ-WP-0010-experimental-pushy-delivery.md |
|
||||
| task | TAMQ-WP-0011-T01 | progress | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
| task | TAMQ-WP-0011-T02 | todo | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
| task | TAMQ-WP-0011-T03 | todo | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
| task | TAMQ-WP-0011-T01 | done | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
| task | TAMQ-WP-0011-T02 | done | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
| task | TAMQ-WP-0011-T03 | done | — | workplans/TAMQ-WP-0011-hash-routing-for-pushy-agents.md |
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ manual messaging from a managed shell:
|
|||
@TARGET: MESSAGE...
|
||||
@ compose with a visible, Tab-selectable recipient
|
||||
@ MESSAGE... fast reply (ordinary shell quoting applies)
|
||||
#TARGET: MESSAGE... agent-input alias in a tapped pushy session
|
||||
tamq inbox [--filter COMMAND]
|
||||
""",
|
||||
)
|
||||
|
|
@ -336,6 +337,9 @@ def main(argv: list[str] | None = None) -> int:
|
|||
if args.no_service and args.mode is not None:
|
||||
print("tamq: --mode cannot be combined with --no-service", file=sys.stderr)
|
||||
return 2
|
||||
tap_enabled = args.tap or (
|
||||
selected_mode == "pushy" and args.initial_command is not None
|
||||
)
|
||||
manager = TmuxManager()
|
||||
try:
|
||||
if not args.no_service:
|
||||
|
|
@ -356,7 +360,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
print("tamq service failed to start with the required delivery capability; use --no-service to open repos without messaging", file=sys.stderr)
|
||||
return 1
|
||||
try:
|
||||
endpoint = manager.ensure_plan(launch_plan, tap=args.tap)
|
||||
endpoint = manager.ensure_plan(launch_plan, tap=tap_enabled)
|
||||
except (TmuxError, OSError) as exc:
|
||||
print(f"tamq: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
|
|
@ -393,6 +397,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
"service": not args.no_service,
|
||||
"messaging": registered,
|
||||
"mode": "none" if args.no_service else ("tap" if args.tap else selected_mode),
|
||||
"input_observation_requested": tap_enabled,
|
||||
"delivery_mode": "none" if args.no_service else (
|
||||
"pane" if args.tap else endpoint_delivery_mode
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ from __future__ import annotations
|
|||
import re
|
||||
from dataclasses import dataclass
|
||||
|
||||
ADDRESS = re.compile(r"^@([a-z0-9][a-z0-9._-]*):(?:[ \t]*)(.+)$", re.IGNORECASE)
|
||||
ADDRESS = re.compile(r"^[@#]([a-z0-9][a-z0-9._-]*):(?:[ \t]*)(.+)$", re.IGNORECASE)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ def format_comment(sender: str, body: str, message_id: str) -> str:
|
|||
|
||||
|
||||
def format_pushy_input(sender: str, body: str, message_id: str) -> str:
|
||||
"""Render one shell-safe input line for explicit pushy delivery."""
|
||||
"""Render one shell-safe, non-routable line for pushy delivery."""
|
||||
return (
|
||||
f"#{terminal_safe(sender)}: {terminal_safe(body)} "
|
||||
f"# from {terminal_safe(sender)}: {terminal_safe(body)} "
|
||||
f"[{terminal_safe(message_id)}]"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -246,7 +246,7 @@ class TmuxManager:
|
|||
command = (
|
||||
[
|
||||
*self.tamq_command,
|
||||
"tap", "--repo", repo, "--endpoint", endpoint_id, "--", *plan.command,
|
||||
"tap", "--repo", repo, "--endpoint", instance_id, "--", *plan.command,
|
||||
]
|
||||
if tap
|
||||
else list(plan.command)
|
||||
|
|
|
|||
|
|
@ -9,3 +9,13 @@ def test_broker_preserves_identity(tmp_path):
|
|||
row = store.list()[0]
|
||||
assert row["sender_repo"] == "net-kingdom"
|
||||
assert row["endpoint_id"] == "tmux-amq-42"
|
||||
|
||||
|
||||
def test_broker_accepts_hash_address_alias(tmp_path):
|
||||
store = Store(tmp_path / "queue.sqlite3")
|
||||
broker = InputBroker(store, BrokerIdentity("tmux-amq-42-boot", "net-kingdom"))
|
||||
assert broker.inspect_line("#railiance-platform: hello") is not None
|
||||
row = store.list()[0]
|
||||
assert row["sender_repo"] == "net-kingdom"
|
||||
assert row["target_repo"] == "railiance-platform"
|
||||
assert row["endpoint_id"] == "tmux-amq-42-boot"
|
||||
|
|
|
|||
|
|
@ -209,10 +209,11 @@ def test_pushy_mode_registers_explicit_delivery_mode(monkeypatch, capsys):
|
|||
class Manager:
|
||||
def preflight(self, repos, command):
|
||||
assert repos == ["a", "b"]
|
||||
assert command == "codex"
|
||||
return "plan"
|
||||
|
||||
def ensure_plan(self, plan, *, tap=True):
|
||||
assert tap is False
|
||||
assert tap is True
|
||||
return endpoint
|
||||
|
||||
def rollback(self, value):
|
||||
|
|
@ -231,10 +232,13 @@ def test_pushy_mode_registers_explicit_delivery_mode(monkeypatch, capsys):
|
|||
)
|
||||
monkeypatch.setattr("tamq.cli.request", register)
|
||||
|
||||
assert main(["start", "--detach", "--mode", "pushy", "a", "b"]) == 0
|
||||
assert main([
|
||||
"start", "--detach", "--mode", "pushy", "--command", "codex", "a", "b"
|
||||
]) == 0
|
||||
summary = json.loads(capsys.readouterr().out)
|
||||
assert summary["mode"] == "pushy"
|
||||
assert summary["delivery_mode"] == "pushy"
|
||||
assert summary["input_observation_requested"] is True
|
||||
assert requests[0]["delivery_mode"] == "pushy"
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class RecordingBroker:
|
|||
|
||||
def inspect_line(self, line):
|
||||
self.lines.append(line)
|
||||
return line.startswith("@")
|
||||
return line.startswith(("@", "#"))
|
||||
|
||||
|
||||
def test_input_observer_accepts_raw_terminal_carriage_returns():
|
||||
|
|
@ -22,11 +22,11 @@ def test_input_observer_accepts_raw_terminal_carriage_returns():
|
|||
tap = PtyTap(["true"], broker, on_line=observed.append)
|
||||
buffer = bytearray()
|
||||
|
||||
tap._observe_input(buffer, b"@activity-core: hel")
|
||||
tap._observe_input(buffer, b"#activity-core: hel")
|
||||
tap._observe_input(buffer, b"lo\rplain line\n")
|
||||
|
||||
assert broker.lines == ["@activity-core: hello", "plain line"]
|
||||
assert observed == ["@activity-core: hello"]
|
||||
assert broker.lines == ["#activity-core: hello", "plain line"]
|
||||
assert observed == ["#activity-core: hello"]
|
||||
|
||||
|
||||
def test_copy_winsize_preserves_rows_columns_and_pixels():
|
||||
|
|
|
|||
|
|
@ -1,11 +1,17 @@
|
|||
import pytest
|
||||
|
||||
from tamq.routing import parse_address_line
|
||||
|
||||
|
||||
def test_direct_address():
|
||||
assert parse_address_line("@railiance-platform: do something!").body == "do something!"
|
||||
assert parse_address_line("@railiance-platform: do something!").target_repo == "railiance-platform"
|
||||
@pytest.mark.parametrize("prefix", ["@", "#"])
|
||||
def test_direct_address(prefix):
|
||||
routed = parse_address_line(f"{prefix}railiance-platform: do something!")
|
||||
assert routed.body == "do something!"
|
||||
assert routed.target_repo == "railiance-platform"
|
||||
|
||||
|
||||
def test_ordinary_input_is_unchanged():
|
||||
assert parse_address_line("hello @repo: not at start") is None
|
||||
assert parse_address_line("@repo:") is None
|
||||
assert parse_address_line("# from repo: inbound envelope") is None
|
||||
assert parse_address_line("##repo: not an address") is None
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ def test_service_pushy_mode_submits_once_and_marks_injected(tmp_path, monkeypatc
|
|||
assert control.submitted == [
|
||||
(
|
||||
"tamq:repo-b",
|
||||
f"#repo-a: first\\x0asecond [{message_id}]",
|
||||
f"# from repo-a: first\\x0asecond [{message_id}]",
|
||||
)
|
||||
]
|
||||
assert store.list()[0]["state"] == "injected"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ def test_comment_format_escapes_controls_and_prefixes_every_line():
|
|||
|
||||
def test_pushy_input_is_one_sanitized_shell_comment():
|
||||
assert format_pushy_input("repo-a", "first\nsecond\x1b[31m", "m-1") == (
|
||||
"#repo-a: first\\x0asecond\\x1b[31m [m-1]"
|
||||
"# from repo-a: first\\x0asecond\\x1b[31m [m-1]"
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -242,10 +242,10 @@ def test_real_tmux_pushy_mode_submits_one_shell_safe_input(tmp_path, monkeypatch
|
|||
service = Service(store=store)
|
||||
service._deliver_once()
|
||||
|
||||
expected = f"#flex-auth: What's next?\\x0asecond [{message_id}]"
|
||||
expected = f"# from flex-auth: What's next?\\x0asecond [{message_id}]"
|
||||
deadline = time.monotonic() + 5
|
||||
while time.monotonic() < deadline:
|
||||
capture = manager._run("capture-pane", "-p", "-t", target)
|
||||
capture = manager._run("capture-pane", "-p", "-J", "-t", target)
|
||||
if expected in capture:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
|
|
@ -254,8 +254,128 @@ def test_real_tmux_pushy_mode_submits_one_shell_safe_input(tmp_path, monkeypatch
|
|||
assert store.db.execute("SELECT COUNT(*) FROM leases").fetchone()[0] == 0
|
||||
|
||||
service._deliver_once()
|
||||
repeated_capture = manager._run("capture-pane", "-p", "-t", target)
|
||||
repeated_capture = manager._run("capture-pane", "-p", "-J", "-t", target)
|
||||
assert repeated_capture.count(expected) == 1
|
||||
finally:
|
||||
store.close()
|
||||
manager._run("kill-server", check=False)
|
||||
|
||||
|
||||
@pytest.mark.skipif(shutil.which("tmux") is None, reason="tmux is not installed")
|
||||
def test_real_tmux_hash_route_pushes_once_without_feedback(tmp_path, monkeypatch):
|
||||
repo_a = tmp_path / "railiance-platform"
|
||||
repo_b = tmp_path / "activity-core"
|
||||
bin_dir = tmp_path / "bin"
|
||||
for path in (repo_a, repo_b, bin_dir):
|
||||
path.mkdir()
|
||||
gita = bin_dir / "gita"
|
||||
gita.write_text(
|
||||
"#!/bin/sh\n"
|
||||
"if [ \"$1\" = ls ]; then printf 'railiance-platform activity-core\\n'; exit 0; fi\n"
|
||||
"exit 2\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
gita.chmod(0o755)
|
||||
fixture = tmp_path / "agent_fixture.py"
|
||||
fixture.write_text(
|
||||
"import sys\n"
|
||||
"print('AGENT-READY', flush=True)\n"
|
||||
"for line in sys.stdin:\n"
|
||||
" print('AGENT:' + line.rstrip('\\r\\n'), flush=True)\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
project_src = str(Path(__file__).resolve().parents[1] / "src")
|
||||
existing_pythonpath = os.environ.get("PYTHONPATH")
|
||||
monkeypatch.setenv(
|
||||
"PYTHONPATH",
|
||||
project_src if not existing_pythonpath else f"{project_src}:{existing_pythonpath}",
|
||||
)
|
||||
monkeypatch.setenv("PATH", f"{bin_dir}:{os.environ['PATH']}")
|
||||
monkeypatch.setenv("TAMQ_STATE_DIR", str(tmp_path / "state"))
|
||||
socket_name = f"tamq-hash-{os.getpid()}-{uuid4().hex[:8]}"
|
||||
session = f"tamq-hash-{uuid4().hex[:8]}"
|
||||
monkeypatch.setenv("TAMQ_TMUX_SOCKET", socket_name)
|
||||
manager = TmuxManager(
|
||||
session,
|
||||
tmux_command=("tmux", "-L", socket_name),
|
||||
tamq_command=(sys.executable, "-m", "tamq.cli"),
|
||||
command_dir=tmp_path / "commands",
|
||||
)
|
||||
plan = LaunchPlan(
|
||||
("railiance-platform", "activity-core"),
|
||||
{
|
||||
"railiance-platform": str(repo_a),
|
||||
"activity-core": str(repo_b),
|
||||
},
|
||||
(sys.executable, str(fixture)),
|
||||
)
|
||||
store = None
|
||||
|
||||
try:
|
||||
endpoint = manager.ensure_plan(plan, tap=True)
|
||||
deadline = time.monotonic() + 5
|
||||
while time.monotonic() < deadline:
|
||||
captures = {
|
||||
repo: manager._run("capture-pane", "-p", "-t", f"{session}:{repo}")
|
||||
for repo in plan.repos
|
||||
}
|
||||
if all("AGENT-READY" in output for output in captures.values()):
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert all("AGENT-READY" in output for output in captures.values())
|
||||
|
||||
store = Store(tmp_path / "state" / "tamq.sqlite3")
|
||||
store.register_endpoint(
|
||||
endpoint.instance_key,
|
||||
endpoint.pid,
|
||||
endpoint.session,
|
||||
endpoint.repos,
|
||||
"pushy",
|
||||
)
|
||||
manager._run(
|
||||
"send-keys",
|
||||
"-t",
|
||||
f"{session}:railiance-platform",
|
||||
"-l",
|
||||
"--",
|
||||
"#activity-core: Hello!",
|
||||
)
|
||||
manager._run("send-keys", "-t", f"{session}:railiance-platform", "Enter")
|
||||
|
||||
deadline = time.monotonic() + 5
|
||||
while time.monotonic() < deadline:
|
||||
rows = store.list()
|
||||
if rows:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert len(rows) == 1
|
||||
row = rows[0]
|
||||
assert row["sender_repo"] == "railiance-platform"
|
||||
assert row["target_repo"] == "activity-core"
|
||||
assert row["body"] == "Hello!"
|
||||
assert row["endpoint_id"] == endpoint.instance_key
|
||||
|
||||
service = Service(store=store)
|
||||
service._deliver_once()
|
||||
expected = f"# from railiance-platform: Hello! [{row['message_id']}]"
|
||||
deadline = time.monotonic() + 5
|
||||
while time.monotonic() < deadline:
|
||||
target_capture = manager._run(
|
||||
"capture-pane", "-p", "-J", "-t", f"{session}:activity-core"
|
||||
)
|
||||
if f"AGENT:{expected}" in target_capture:
|
||||
break
|
||||
time.sleep(0.05)
|
||||
assert f"AGENT:{expected}" in target_capture
|
||||
assert store.list()[0]["state"] == "injected"
|
||||
|
||||
service._deliver_once()
|
||||
time.sleep(0.1)
|
||||
assert len(store.list()) == 1
|
||||
assert manager._run(
|
||||
"capture-pane", "-p", "-J", "-t", f"{session}:activity-core"
|
||||
).count(f"AGENT:{expected}") == 1
|
||||
finally:
|
||||
if store is not None:
|
||||
store.close()
|
||||
manager._run("kill-server", check=False)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ def test_tmux_manager_builds_tap_windows(tmp_path, monkeypatch):
|
|||
tap_calls = [call for call in calls if call and call[0] == "send-keys"]
|
||||
assert len(tap_calls) == 2
|
||||
assert all("tamq tap" in " ".join(call) for call in tap_calls)
|
||||
assert all(endpoint.instance_id in " ".join(call) for call in tap_calls)
|
||||
assert all("codex --quiet" in " ".join(call) for call in tap_calls)
|
||||
assert ("select-window", "-t", "tamq-test:a") in calls
|
||||
assert any(call and call[0] == "set-option" for call in calls)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ type: workplan
|
|||
title: "Hash-prefixed routing for pushy agent sessions"
|
||||
domain: communication
|
||||
repo: tmux-amq
|
||||
status: active
|
||||
status: finished
|
||||
owner: codex
|
||||
topic_slug: coulomb-social
|
||||
planning_priority: P0
|
||||
planning_order: 16
|
||||
created: "2026-08-24"
|
||||
updated: "2026-08-24"
|
||||
updated: "2026-08-25"
|
||||
state_hub_workstream_id: "9a38f0c3-bd98-51ae-a6ad-502bef94a38f"
|
||||
---
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ comment and a clear coding-agent prompt.
|
|||
|
||||
```task
|
||||
id: TAMQ-WP-0011-T01
|
||||
status: progress
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "d50b09b2-e4ad-5eb4-b833-2d9a62b5ff1f"
|
||||
```
|
||||
|
|
@ -48,7 +48,7 @@ validation, sender identity, and ordinary-input pass-through.
|
|||
|
||||
```task
|
||||
id: TAMQ-WP-0011-T02
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "1170ae19-a2ce-5f50-8a43-41b9625621e7"
|
||||
```
|
||||
|
|
@ -61,7 +61,7 @@ command is supplied and keep legacy `--tap` behavior distinct.
|
|||
|
||||
```task
|
||||
id: TAMQ-WP-0011-T03
|
||||
status: todo
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "2a355d88-5272-58c2-a4f8-82df6cac7c3e"
|
||||
```
|
||||
|
|
@ -71,3 +71,20 @@ CLI, PTY, and real two-window tmux coverage proving one `#repo:` source line
|
|||
creates one durable message, one target submission, and no bounced message.
|
||||
Document the interaction, run full checks, install, and exercise a controlled
|
||||
live commanded pushy session if it can be isolated from user input.
|
||||
|
||||
## Completion evidence
|
||||
|
||||
- `make check`: 120 tests passed, including a real isolated two-window tmux
|
||||
test proving one `#activity-core:` source line creates one durable message,
|
||||
one submitted target prompt, an `injected` transition, and no feedback or
|
||||
duplicate delivery on a second service pass.
|
||||
- Coverage: 77% overall; routing is 100%, tmux management is 82%, terminal
|
||||
framing is 85%, and the service is 77%.
|
||||
- `make install`: installed `tmux-amq==0.1.0` with hash routing and automatic
|
||||
PTY observation for newly created commanded pushy windows.
|
||||
- The inbound `# from sender:` envelope is deliberately outside the outbound
|
||||
route grammar, preventing tapped targets from bouncing injected messages.
|
||||
- The operator's existing session was not recreated or modified. Existing
|
||||
windows must be recreated to gain PTY observation; this preserves live input.
|
||||
- Bounded retry and acknowledgement-policy residuals remain owned by
|
||||
`TAMQ-WP-0003`; this workplan creates no additional residual record.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue