Skip to content

OmaQ 1.2.0: queued direct messages, and replies and identity actions in omaq-connect - #64

Merged
HANCORE-linux merged 7 commits into
mainfrom
feat/queued-send-and-cli-reply
Oct 14, 2026
Merged

HANCORE-linux merged 7 commits into
mainfrom
feat/queued-send-and-cli-reply

Conversation

@HANCORE-linux

@HANCORE-linux HANCORE-linux commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

OmaQ 1.2.0 holds direct messages until the secure session exists, and adds replies, edits and deletes, and identity actions to omaq-connect. The protocol stays at 16; the helper's snapshot now also announces identityExport: 2.

Changes

  • Queued direct messages: a direct message sent before the Ratchet session existed failed at once with ratchet_pending, and every such send asked for a new session. The helper now keeps these messages in a bounded queue (64 messages, 16 per contact), asks for one session per waiting phase and sends them in order once the session exists. A message still waiting after 5 minutes fails with the new code ratchet_timeout ("No secure session yet; message not sent" in the bar and in omaq-connect). A contact that goes offline, is removed or changes key, an identity change and a helper shutdown fail queued messages at once; the messages of a client that disconnects are dropped.
  • Replies, edits and deletes in omaq-connect: ctrl+r or :reply marks the newest message that can be answered, and the next message carries its id. Quotes look as in the bar. message.updated shows edits and deletes at once, and a reply to a message that gets deleted ends with a notice, also when the delete first shows in a history reload. Only ids that fit the helper's 79-byte reply field can be answered.
  • Identity actions in omaq-connect: :identity protect, unprotect, export [path], validate <path> and import <path> run the helper's identity ops with the bar's wording. Passphrases go into a masked line, never into the command line, drafts, recall or history, and every change asks y/n first. The helper now refuses identity.protect while a passphrase is set.
  • Hardening: passphrase lines cope with split escape sequences and pastes, and in a terminal without bracketed paste, input after a passphrase line waits for esc. identity.export takes replace:false and creates the file exclusively. After an identity import or a guarded restore, the helper writes its new instance to omaq.protocol, so :quit and the uninstaller still recognize it; if that write fails, it reports runtime_marker_failed and retries.
  • Tests: cli-two-homes.py and cli-split.py accept the presence dot in the pane header; they failed on main since 1.1.0.
  • Release metadata: manifest 1.2.0, docs/CURRENT.md, the stage index and the terminal-client stage.

Verification

  • make test, including the terminal client's 163 unit tests, and tests/cli_arch_test.py.
  • ./tests/phase8.sh: two messages sent before the session exists arrive in order without a failure. The 1.1.2 helper fails both with ratchet_pending.
  • tests/cli-pty.py with the real helper: export (mode 0600), validate, unprotect, protect and import; no passphrase appears in the output or in any file of the test's home.
  • tests/cli-two-homes.py over the Tox network, including a ctrl+r reply that bob's helper receives with its id.
  • omarchy plugin validate ., shellcheck on all *.sh and git diff --check.
  • A live test between two machines over the Tox network, with fresh identities and this client on both sides (32 of 32 checks, run before the last commit; the helper has not changed since):
    • a message typed right after the accept waited for the secure session and arrived;
    • a ctrl+r reply arrived with its quote, and an edit and a delete showed on the other side;
    • a file arrived byte for byte;
    • protect, unlock, unprotect, export, validate and import worked, and a multi-line paste into a passphrase line never reached the other side.

Since 1.1.0 a pane header shows the friend's presence dot, "❯ bob ●  online",
but cli-two-homes.py and cli-split.py still waited for "❯ bob  online". They
matched only during the short connecting state, so on main both failed once
the header showed the dot. The patterns now accept the header with and
without the dot, as cli-invite.py already does.
A direct message sent before the Ratchet session existed failed at once
with ratchet_pending, and every such send asked for a new session. Each
OQB2 request rotates the bootstrap prekey, so a burst of sends right after
pairing could also delay the handshake.

The helper now keeps such messages in a bounded queue (send_queue.c: 64
messages, 16 per contact), bound to the contact's stable key, the own
identity and the IPC client that sent them. It asks for one session per
waiting phase and sends the queue in order once the session exists. A
message that still waits after 5 minutes fails with the new code
ratchet_timeout, "No secure session yet; message not sent" in the bar and
in omaq-connect. A contact that goes offline, is removed or changes key, an
identity change and a helper shutdown fail queued messages at once. When
the sending client disconnects, the helper drops its messages, because that
client already shows them as delivery_unknown. Sent messages are never sent
again, and a full queue still answers ratchet_pending.

tests/phase8.sh sends two messages before the session exists and requires
both in order without a failure; the 1.1.2 helper fails both with
ratchet_pending. The PlainText gate pins the new ChatPage source hashes.
omaq-connect always sent an empty reply and ignored reply ids, edits and
deletes. ctrl+r (or :reply) now marks the newest message that can be
answered with an accent ▌; up/down, pgup/pgdn and home/end move the mark,
the chat keeps it in view, and enter or r picks it. "↩ Reply: <preview>"
above the input line shows the choice, and the next message carries its id.
esc ends the choice, then the reply, then clears the draft. Pasted text and
other keys end the choice and go into the draft, so a paste never picks a
message. Only messages with a helper id can be answered: system lines,
unsent, failed and local items, deleted messages, files and images are
skipped, because file ids differ between the two sides.

A reply starts with "↩ <preview>" in the accent color when the quoted
message is loaded, as in the bar. compact_preview() ports the bar's
compactReplyPreview and matched it on all 3005 texts of a one-off
comparison. message.updated now changes a loaded message at once: an edit
shows "(edited)", a delete "Message deleted", and history items carry the
same flags. A reply to a message that gets deleted ends with a notice.

cli-two-homes.py answers bob's message with ctrl+r and checks the reply id
on bob's helper and the quote in the CLI.
omaq-connect could only unlock a protected identity. :identity now shows
whether the identity has a passphrase, and :identity protect, unprotect,
export [path], validate <path> and import <path> run the helper's identity
ops with the bar's wording, one at a time, each with its own request id
and the bar's 20 second timeout. Passphrases go into a masked line, never
into the command line, drafts, recall or history; a new one is entered
twice. Every change asks y/n right before it is sent, and the passphrase
waits only inside that question. Export writes omaq-identity.save to
$XDG_DOWNLOAD_DIR or ~/Downloads by default and asks before it replaces a
file. A protected bundle asks for its passphrase, and import asks again
before it sends it. tab after export, validate and import opens the
:sendfile file list.

The helper now refuses identity.protect while a passphrase is set, so no
client can replace it without the current one; encryptsave.sh checks this.

Passphrase lines, the unlock prompt included, accept a tab and refuse a
pasted line break. In a terminal without bracketed paste, an enter, esc,
ctrl+c or ctrl+d with more input in the same read refuses the line, and
after a passphrase line closes the client discards input until the
terminal is quiet for 0.3 s; an incomplete escape sequence or a running
paste is then dropped whole. The key parser no longer flushes during a
paste, so a split end marker still ends it, and a sequence broken by a
byte that cannot continue it restarts parsing at that byte.

IdentityActions in cli_unit_test.py has 28 tests, and cli-pty.py exports,
validates, unprotects, protects and imports with the real helper.
While a passphrase line is open, an incomplete ESC [ sequence that stays
alone for 30 ms is kept, and what follows decides. A known key, a focus
report or the paste start counts as such, and a split cursor position,
mode or device report is dropped. Anything else counts as typed text
with the digits typed after the piece, so alt-[ no longer swallows the
next key. A lock from another client drops a started paste together with
the passphrase line it belonged to. A running paste no longer waits
30 ms at the drain.

The startup query now also asks whether bracketed paste is on. A
terminal that does not confirm it holds input after a passphrase line
until esc ("press esc to type again").

identity.export takes replace:false and then creates the file
exclusively. omaq-connect sends it unless the user agreed to replace the
file. If a file appeared in the meantime, it reports "<path> already
exists. Export again to replace it." An export to a new path needs a
helper whose snapshot announces identityExport 2.

Long questions and notices wrap onto up to three lines, and :help
indents subcommands under their command. When the helper refuses the
bar's Protect because a passphrase is already set, the bar now says so.

After an identity import or a guarded restore, the helper writes its new
instance to omaq.protocol. The client that started it and the
uninstaller therefore still recognize it, and :quit stops the helper
again. If that write fails, the helper keeps running. Once the change is
saved, it reports runtime_marker_failed, which the bar and omaq-connect
show, and it retries the file with every status.

cli_unit_test.py now has 161 tests. tests/identity-feedback.sh and
tests/runtime-marker.sh are new. phase5.sh, reinvite-recovery.sh,
cli-pty.py, ipc-regression.py and omaq_test.c cover the export flag, the
capability and the marker, and qml_plaintext_test.py carries the
reviewed Panel.qml hashes.
…connect

The helper accepts message ids of up to 96 characters from a peer but
reads the reply id of msg.send into an 80-byte field. A reply to a
longer id made it reject the whole request with an error that names no
request, and omaq-connect kept the message pending. Helpers create
32-character ids, so only a modified peer sends such ids. Only messages
whose id fits in 79 bytes can now be answered, and a test ties
REPLY_ID_MAX to the reply field in helper/json_io.h.

A message deleted while omaq-connect had no link to the helper first
shows as deleted in the history reload after the reconnect. That reload
now ends a reply to it with "Reply canceled: the message was deleted",
as a live delete does, and moves a choice resting on it to the newest
message that can still be answered.

phase8.sh now also fails when a queued message that arrived is reported
as failed.

cli_unit_test.py now has 163 tests.
The manifest moves to 1.2.0, still Protocol 16. CURRENT.md, the stage
index and the terminal-client stage now name 1.2.0 for the terminal
client's replies and identity actions, and the snapshot date moves to
2026-10-14.
@HANCORE-linux HANCORE-linux changed the title Queue early direct messages and add replies and identity actions to omaq-connect OmaQ 1.2.0: queued direct messages, and replies and identity actions in omaq-connect Oct 14, 2026
@HANCORE-linux
HANCORE-linux merged commit 008f7c8 into main Oct 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant