aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ai/workflows/triage-intake.signal.org77
-rw-r--r--claude-templates/.ai/workflows/triage-intake.signal.org77
2 files changed, 64 insertions, 90 deletions
diff --git a/.ai/workflows/triage-intake.signal.org b/.ai/workflows/triage-intake.signal.org
index 97f7fcd..978241d 100644
--- a/.ai/workflows/triage-intake.signal.org
+++ b/.ai/workflows/triage-intake.signal.org
@@ -5,33 +5,32 @@
# Source plugin for the triage-intake engine. See triage-intake.org for the
# contract and the Phase A-D orchestration. This file declares ONE source.
#
-# General (personal) source: Signal Private Messenger. TWO access paths exist
-# on Craig's machines, and picking the right one is the whole game:
+# General (personal) source: Signal Private Messenger. Emacs is the ONLY way
+# to check Signal (Craig's ruling, 2026-06-10). His Emacs daemon runs
+# signal-cli in jsonRpc mode via the signel package (~/code/signel, wired in
+# ~/.emacs.d/modules/signal-config.el), and that daemon OWNS the account: it
+# drains the server queue continuously, writes messages into *Signel: <id>*
+# buffers, and fires desktop notifications.
#
-# - signel (Emacs): Craig's Emacs daemon runs signal-cli in jsonRpc mode via
-# the signel package (~/code/signel, wired in ~/.emacs.d/modules/signal-config.el).
-# When that daemon is live, it OWNS the Signal account: it drains the
-# server queue continuously, writes messages into *Signel: <id>* buffers,
-# and fires desktop notifications.
-# - standalone signal-cli: works ONLY when signel's daemon is not running.
-#
-# ⚠ NEVER run a standalone `signal-cli receive` while signel's daemon is
-# live. signal-cli locks the account database, so the standalone call hangs
-# on the lock until killed, and the scan silently stalls (observed
-# 2026-06-10: a backgrounded receive blocked for 3+ minutes and the sweep
-# shipped without Signal). Detect first, then branch.
+# ⚠ NEVER run a standalone `signal-cli receive`. signel and standalone
+# signal-cli share the same account config and device queue, so a standalone
+# drain STEALS messages the Emacs client would otherwise show Craig — and if
+# the daemon is live it hangs on the account lock besides (observed
+# 2026-06-10: a backgrounded receive blocked 3+ minutes and the sweep shipped
+# without Signal). signal-cli's only legitimate direct uses are
+# queue-untouching reads like `signal-cli listAccounts`.
* Source: signal
:PROPERTIES:
:ORDER: 22
-:ENABLED: command -v signal-cli || command -v emacsclient
+:ENABLED: command -v emacsclient
:ANCHOR: none
:SUBAGENT_OVER: 40
:END:
** Scan
-*** Step 0 — detect which path owns the account (mandatory, every run)
+*** Step 0 — ensure signel is running (start it if down, leave it running)
#+begin_src bash
SIGNEL_LIVE=$(emacsclient -e "(and (featurep 'signel) (process-live-p (get-process \"signal-rpc\")) t)" 2>/dev/null)
@@ -41,11 +40,17 @@ pgrep -f "org.asamk.signal.Main.*jsonRpc" >/dev/null && SIGNEL_LIVE=t
echo "signel daemon live: ${SIGNEL_LIVE:-nil}"
#+end_src
-- =t= → *Path A* (query through Emacs). Standalone receive is FORBIDDEN.
-- =nil= → *Path B* (standalone draining receive).
-- emacsclient unreachable AND signal-cli absent → SCAN FAILED. Surface loudly per the engine's failure rule; never report Signal as "quiet."
+- =t= → proceed straight to the buffer query below.
+- =nil= → *start signel through Emacs* and leave it running — a live signel is the desired steady state (unlike telega's leave-no-trace lifecycle, there is no teardown step):
+
+ #+begin_src bash
+ emacsclient -e "(progn (require 'signel) (cj/signel--ensure-started))"
+ sleep 5 # let queued envelopes land in the chat buffers
+ #+end_src
+
+- emacsclient unreachable, or signel fails to start → SCAN FAILED. Surface loudly per the engine's failure rule; never report Signal as "quiet."
-*** Path A — query through Emacs (signel daemon live)
+*** Buffer query
signel keeps three sources of truth: =signel--active-chats= (hash of chat-ids seen this Emacs session), =signel--contact-map= (number → display name), and the =*Signel: <id>*= chat buffers (message lines stamped =[HH:MM]=, senders as =<Name>=, Craig's own as =<Me>=).
@@ -67,24 +72,11 @@ emacsclient -e "(progn (require 'signel)
Phase B reads the =[HH:MM]= stamps against the anchor to find what's new, and reads who spoke last: a thread whose last line is =<Me>= (or a closing acknowledgment from the contact) carries no reply owed; a thread ending on the contact's question does.
-Path A caveats, stated honestly in the render when they bite:
+Caveats, stated honestly in the render when they bite:
- =signel--active-chats= covers only chats with traffic since the Emacs daemon (or signel) last started. It is not a full history.
-- Buffer content is in-memory; an Emacs restart empties it. The phone and Signal Desktop still hold everything.
+- A freshly-started signel shows only what arrives after the start plus the queued envelopes that land on connect. The phone and Signal Desktop still hold everything.
- Timestamps are =[HH:MM]= with no date. Treat stamps as today's unless buffer position says otherwise.
-*** Path B — standalone draining receive (signel NOT running)
-
-#+begin_src bash
-acct=$(signal-cli listAccounts | awk 'NR==1{print $2}')
-signal-cli -o json -a "$acct" receive --timeout 10 \
- | jq -c 'select(.envelope.dataMessage.message != null
- or .envelope.syncMessage.sentMessage.message != null)'
-#+end_src
-
-⚠ *DRAINING READ.* =signal-cli receive= is destructive on this device's server queue: once an envelope is ACKed the server drops it and a second receive will NOT return it. Capture everything from the one scan. The messages are not lost to Craig (phone and Desktop are independent linked devices), but the triage's own view is one-shot. Never fire a throwaway receive.
-
-⚠ Run Path B in the foreground, never backgrounded with stderr discarded — a lock conflict must fail loudly, not hang silently.
-
** Classify
Bias: Signal is personal, conversational, and time-sensitive — it leans *Action*, because a direct message usually carries an implicit "respond." Volume is low, signal-to-noise high (the opposite of personal Gmail).
@@ -102,28 +94,23 @@ Flag a message from a contact Craig is mid-thread with prominently — a dropped
- Action: <items, sender + gist, reply owed called out>
- FYI: <threads with new traffic Craig already handled, terse>
- Noise: <tally>
-- (Path A caveat line, only when relevant: "covers chats since Emacs started <when>")
+- (Caveat line, only when relevant: "covers chats since signel started <when>")
#+end_example
Omit the block ONLY when the scan genuinely ran and found nothing. A scan that could not run is never "quiet" — it renders as a loud SCAN FAILED line at the top of the whole summary (engine rule).
** Actions
-*Path A (signel live)* — all through the daemon:
+All through the signel daemon:
- reply (programmatic) :: =emacsclient -e "(progn (require 'signel) (signel--send-rpc \"send\" '((message . \"<body>\") (recipient . [\"+1555...\"]))))"= — for a group, replace =recipient= with =(groupId . \"<id>\")=. Public-facing (goes out under Craig's name): run =/voice personal= first.
- reply (interactive) :: =emacsclient -e "(cj/signel-message)"= pops Craig's contact picker + chat buffer for a considered reply by hand.
- attach :: open the chat buffer, =signel-attach-file=.
-- start signel if Craig wants it up :: =emacsclient -e "(cj/signel-connect)"= (auto-starts the daemon and pre-warms contacts).
-
-*Path B (standalone)*:
-
-- reply :: =signal-cli -a <acct> send -m "<body>" <recipient-number>= — =/voice personal= first. Group: =-g <groupId>=.
-- react :: =signal-cli -a <acct> sendReaction -e <emoji> -t <target-timestamp> -a <target-author> <recipient>=.
-No mark-read step on either path: Path A's daemon already consumed the queue; Path B's draining receive did the same.
+No mark-read step: the daemon already consumed the queue.
** History
- 2026-06-08: initial standalone-receive plugin.
-- 2026-06-10: rewrote around the signel discovery. Craig's Emacs daemon runs signal-cli jsonRpc via signel, which owns the account lock; a standalone receive hung a sweep silently. Added Step 0 detection, the Path A emacsclient query (verified live: caught two real threads the standalone path was blind to), the foreground-only rule for Path B, and the never-quiet-on-failure rule. Craig: Signal carries important communication; failures surface loudly.
+- 2026-06-10: rewrote around the signel discovery. Craig's Emacs daemon runs signal-cli jsonRpc via signel, which owns the account lock; a standalone receive hung a sweep silently. Added Step 0 detection, the dual-path split, and the never-quiet-on-failure rule. Craig: Signal carries important communication; failures surface loudly.
+- 2026-06-10 (correction, same day): Craig's ruling — Emacs is the ONLY way to check Signal. Path B (standalone draining receive) removed entirely: signel and standalone signal-cli share the same account config and device queue, so a standalone drain steals messages from the Emacs client. Step 0 now starts signel via =cj/signel--ensure-started= when it's down and leaves it running (running signel is the steady state; no teardown). signal-cli survives only for queue-untouching reads like =listAccounts=.
diff --git a/claude-templates/.ai/workflows/triage-intake.signal.org b/claude-templates/.ai/workflows/triage-intake.signal.org
index 97f7fcd..978241d 100644
--- a/claude-templates/.ai/workflows/triage-intake.signal.org
+++ b/claude-templates/.ai/workflows/triage-intake.signal.org
@@ -5,33 +5,32 @@
# Source plugin for the triage-intake engine. See triage-intake.org for the
# contract and the Phase A-D orchestration. This file declares ONE source.
#
-# General (personal) source: Signal Private Messenger. TWO access paths exist
-# on Craig's machines, and picking the right one is the whole game:
+# General (personal) source: Signal Private Messenger. Emacs is the ONLY way
+# to check Signal (Craig's ruling, 2026-06-10). His Emacs daemon runs
+# signal-cli in jsonRpc mode via the signel package (~/code/signel, wired in
+# ~/.emacs.d/modules/signal-config.el), and that daemon OWNS the account: it
+# drains the server queue continuously, writes messages into *Signel: <id>*
+# buffers, and fires desktop notifications.
#
-# - signel (Emacs): Craig's Emacs daemon runs signal-cli in jsonRpc mode via
-# the signel package (~/code/signel, wired in ~/.emacs.d/modules/signal-config.el).
-# When that daemon is live, it OWNS the Signal account: it drains the
-# server queue continuously, writes messages into *Signel: <id>* buffers,
-# and fires desktop notifications.
-# - standalone signal-cli: works ONLY when signel's daemon is not running.
-#
-# ⚠ NEVER run a standalone `signal-cli receive` while signel's daemon is
-# live. signal-cli locks the account database, so the standalone call hangs
-# on the lock until killed, and the scan silently stalls (observed
-# 2026-06-10: a backgrounded receive blocked for 3+ minutes and the sweep
-# shipped without Signal). Detect first, then branch.
+# ⚠ NEVER run a standalone `signal-cli receive`. signel and standalone
+# signal-cli share the same account config and device queue, so a standalone
+# drain STEALS messages the Emacs client would otherwise show Craig — and if
+# the daemon is live it hangs on the account lock besides (observed
+# 2026-06-10: a backgrounded receive blocked 3+ minutes and the sweep shipped
+# without Signal). signal-cli's only legitimate direct uses are
+# queue-untouching reads like `signal-cli listAccounts`.
* Source: signal
:PROPERTIES:
:ORDER: 22
-:ENABLED: command -v signal-cli || command -v emacsclient
+:ENABLED: command -v emacsclient
:ANCHOR: none
:SUBAGENT_OVER: 40
:END:
** Scan
-*** Step 0 — detect which path owns the account (mandatory, every run)
+*** Step 0 — ensure signel is running (start it if down, leave it running)
#+begin_src bash
SIGNEL_LIVE=$(emacsclient -e "(and (featurep 'signel) (process-live-p (get-process \"signal-rpc\")) t)" 2>/dev/null)
@@ -41,11 +40,17 @@ pgrep -f "org.asamk.signal.Main.*jsonRpc" >/dev/null && SIGNEL_LIVE=t
echo "signel daemon live: ${SIGNEL_LIVE:-nil}"
#+end_src
-- =t= → *Path A* (query through Emacs). Standalone receive is FORBIDDEN.
-- =nil= → *Path B* (standalone draining receive).
-- emacsclient unreachable AND signal-cli absent → SCAN FAILED. Surface loudly per the engine's failure rule; never report Signal as "quiet."
+- =t= → proceed straight to the buffer query below.
+- =nil= → *start signel through Emacs* and leave it running — a live signel is the desired steady state (unlike telega's leave-no-trace lifecycle, there is no teardown step):
+
+ #+begin_src bash
+ emacsclient -e "(progn (require 'signel) (cj/signel--ensure-started))"
+ sleep 5 # let queued envelopes land in the chat buffers
+ #+end_src
+
+- emacsclient unreachable, or signel fails to start → SCAN FAILED. Surface loudly per the engine's failure rule; never report Signal as "quiet."
-*** Path A — query through Emacs (signel daemon live)
+*** Buffer query
signel keeps three sources of truth: =signel--active-chats= (hash of chat-ids seen this Emacs session), =signel--contact-map= (number → display name), and the =*Signel: <id>*= chat buffers (message lines stamped =[HH:MM]=, senders as =<Name>=, Craig's own as =<Me>=).
@@ -67,24 +72,11 @@ emacsclient -e "(progn (require 'signel)
Phase B reads the =[HH:MM]= stamps against the anchor to find what's new, and reads who spoke last: a thread whose last line is =<Me>= (or a closing acknowledgment from the contact) carries no reply owed; a thread ending on the contact's question does.
-Path A caveats, stated honestly in the render when they bite:
+Caveats, stated honestly in the render when they bite:
- =signel--active-chats= covers only chats with traffic since the Emacs daemon (or signel) last started. It is not a full history.
-- Buffer content is in-memory; an Emacs restart empties it. The phone and Signal Desktop still hold everything.
+- A freshly-started signel shows only what arrives after the start plus the queued envelopes that land on connect. The phone and Signal Desktop still hold everything.
- Timestamps are =[HH:MM]= with no date. Treat stamps as today's unless buffer position says otherwise.
-*** Path B — standalone draining receive (signel NOT running)
-
-#+begin_src bash
-acct=$(signal-cli listAccounts | awk 'NR==1{print $2}')
-signal-cli -o json -a "$acct" receive --timeout 10 \
- | jq -c 'select(.envelope.dataMessage.message != null
- or .envelope.syncMessage.sentMessage.message != null)'
-#+end_src
-
-⚠ *DRAINING READ.* =signal-cli receive= is destructive on this device's server queue: once an envelope is ACKed the server drops it and a second receive will NOT return it. Capture everything from the one scan. The messages are not lost to Craig (phone and Desktop are independent linked devices), but the triage's own view is one-shot. Never fire a throwaway receive.
-
-⚠ Run Path B in the foreground, never backgrounded with stderr discarded — a lock conflict must fail loudly, not hang silently.
-
** Classify
Bias: Signal is personal, conversational, and time-sensitive — it leans *Action*, because a direct message usually carries an implicit "respond." Volume is low, signal-to-noise high (the opposite of personal Gmail).
@@ -102,28 +94,23 @@ Flag a message from a contact Craig is mid-thread with prominently — a dropped
- Action: <items, sender + gist, reply owed called out>
- FYI: <threads with new traffic Craig already handled, terse>
- Noise: <tally>
-- (Path A caveat line, only when relevant: "covers chats since Emacs started <when>")
+- (Caveat line, only when relevant: "covers chats since signel started <when>")
#+end_example
Omit the block ONLY when the scan genuinely ran and found nothing. A scan that could not run is never "quiet" — it renders as a loud SCAN FAILED line at the top of the whole summary (engine rule).
** Actions
-*Path A (signel live)* — all through the daemon:
+All through the signel daemon:
- reply (programmatic) :: =emacsclient -e "(progn (require 'signel) (signel--send-rpc \"send\" '((message . \"<body>\") (recipient . [\"+1555...\"]))))"= — for a group, replace =recipient= with =(groupId . \"<id>\")=. Public-facing (goes out under Craig's name): run =/voice personal= first.
- reply (interactive) :: =emacsclient -e "(cj/signel-message)"= pops Craig's contact picker + chat buffer for a considered reply by hand.
- attach :: open the chat buffer, =signel-attach-file=.
-- start signel if Craig wants it up :: =emacsclient -e "(cj/signel-connect)"= (auto-starts the daemon and pre-warms contacts).
-
-*Path B (standalone)*:
-
-- reply :: =signal-cli -a <acct> send -m "<body>" <recipient-number>= — =/voice personal= first. Group: =-g <groupId>=.
-- react :: =signal-cli -a <acct> sendReaction -e <emoji> -t <target-timestamp> -a <target-author> <recipient>=.
-No mark-read step on either path: Path A's daemon already consumed the queue; Path B's draining receive did the same.
+No mark-read step: the daemon already consumed the queue.
** History
- 2026-06-08: initial standalone-receive plugin.
-- 2026-06-10: rewrote around the signel discovery. Craig's Emacs daemon runs signal-cli jsonRpc via signel, which owns the account lock; a standalone receive hung a sweep silently. Added Step 0 detection, the Path A emacsclient query (verified live: caught two real threads the standalone path was blind to), the foreground-only rule for Path B, and the never-quiet-on-failure rule. Craig: Signal carries important communication; failures surface loudly.
+- 2026-06-10: rewrote around the signel discovery. Craig's Emacs daemon runs signal-cli jsonRpc via signel, which owns the account lock; a standalone receive hung a sweep silently. Added Step 0 detection, the dual-path split, and the never-quiet-on-failure rule. Craig: Signal carries important communication; failures surface loudly.
+- 2026-06-10 (correction, same day): Craig's ruling — Emacs is the ONLY way to check Signal. Path B (standalone draining receive) removed entirely: signel and standalone signal-cli share the same account config and device queue, so a standalone drain steals messages from the Emacs client. Step 0 now starts signel via =cj/signel--ensure-started= when it's down and leaves it running (running signel is the steady state; no teardown). signal-cli survives only for queue-untouching reads like =listAccounts=.