Conversation
…1229) A bare plain:- target (no emulator/tty) has no addressable pane to type into or read a screen from. For a claude-code target, this adds two substitutes: - peek: read-only, off the target's own session transcript on disk, resolved from this session's own live actas lock for that member (never guessed, never written to). Output leads with an AGMSG-NATIVE-RECORD marker line so a caller always knows this was a transcript read, not a screen read. No lock, no live owner, or no transcript falls straight through to the driver's existing unsupported message, unchanged. - poke: when both the caller and the target are claude-code, Claude Code's own local session messaging (ListAgents/SendMessage) is a strictly better substitute than anything this script can do — but there is no shell path to that channel (its wire protocol is internal and undocumented; reverse-engineering it against a live session was considered and rejected). poke.sh names the route (scripts/drivers/terminals/plain/README.md's new poke section, including the <team>-<name> session-naming convention to match against) and refuses rather than attempting anything. Otherwise (the caller is not claude-code, or the target's type has no native channel of its own), poke.sh instead delivers the text as an ordinary agmsg message — the same store send.sh writes to — so it still reaches the target on its own delivery terms rather than failing outright. `from` is resolved the same way whoami.sh/identities.sh already resolve identity: (project, type) -> registered (team, agent), scoped to the target's own team. When that alone is ambiguous (several same-type registrations sharing one project, this fleet's own actual shape), it narrows using this session's own actas lock among the ambiguous candidates — checked per candidate through the existing forward (team, agent) -> lock lookup, never by scanning locks and reverse-parsing a team/agent back out of a lock's filename (a separate, pre-existing defect for an id-keyed lock, kept out of this change's scope; see linked issue). Fewer or more than one candidate refuses rather than guessing. Also fixes the transcript-path resolver (spawn's resume-uuid check, and this peek substitute) to prefer CLAUDE_CONFIG_DIR over $HOME/.claude when set, so a multi-account/profile install resolves against its own real transcripts instead of one directory too shallow. Scope: claude-code only, per the tracking issue. What codex/antigravity would need for the same substitutes is one paragraph in the linked issue, not implemented here.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A bare
plain:-target (no emulator/tty) has no addressable pane to typeinto or read a screen from. Per #1229, this adds two claude-code-specific
substitutes.
peek — read-only, off the target's own session transcript on disk,
resolved from this session's own live actas lock for that member (never
guessed, never written to). Output leads with an
AGMSG-NATIVE-RECORDmarker line so a caller always knows this was a transcript read, not a
screen read. No lock, no live owner, or no transcript falls straight
through to the driver's existing unsupported message, unchanged.
poke — when both the caller and the target are claude-code, Claude
Code's own local session messaging (ListAgents/SendMessage) is a strictly
better substitute than anything this script can do — but there is no shell
path to that channel: it is an authenticated, undocumented local socket
(
CLAUDE_CODE_MESSAGING_SOCKET/CLAUDE_CODE_MESSAGING_TOKEN), andreverse-engineering its wire protocol against a live session was
considered and rejected as unsafe for this change.
poke.shinstead namesthe route (
scripts/drivers/terminals/plain/README.md's new poke section,including the
<team>-<name>session-naming convention spawned memberscarry, to match against in ListAgents' output) and refuses rather than
attempting anything.
Otherwise (the caller is not claude-code, or the target's type has no
native channel of its own),
poke.shinstead delivers the text as anordinary agmsg message — the same store
send.shwrites to — so it stillreaches the target on its own delivery terms rather than failing outright.
fromis resolved the same waywhoami.sh/identities.shalreadyresolve identity:
(project, type)→ registered(team, agent), scopedto the target's own team. When that alone is ambiguous (several same-type
registrations sharing one project — this fleet's own actual shape), it
narrows using this session's own actas lock among the ambiguous
candidates, checked per candidate through the existing forward
(team, agent)→ lock lookup (actas_lock_read), never by scanning locksand reverse-parsing a team/agent back out of a lock's filename. Fewer or
more than one candidate refuses rather than guessing.
Also fixes the transcript-path resolver (spawn's resume-uuid check, and
this peek substitute) to prefer
CLAUDE_CONFIG_DIRover$HOME/.claudewhen set, so a multi-account/profile install resolves against its own
real transcripts instead of one directory too shallow — a real,
independently-confirmed bug found while building the peek substitute
(reproduced against a live multi-account session), not specific to this
feature.
What codex/antigravity would need (out of scope here)
Per #1229's own scope cut ("Implement it for claude-code first; list what
codex and antigravity would need"): both would need (1) a documented,
shell-callable way to identify and message a specific local session of
that CLI (codex and antigravity have no equivalent of Claude Code's
ListAgents/SendMessagesurfaced here), and (2) for peek, a known,stable on-disk transcript location and format for that CLI's sessions
(this PR's
agmsg_transcript_path/agmsg_transcript_tailareclaude-code's own
.jsonllayout specifically, not something togeneralize speculatively).
A related defect found, not fixed here
Building the several-identities disambiguation,
self-fix.sh's_fix_seats_ofwas found to mis-resolve an id-keyed actas lock'steam/agent (it reverse-parses the lock's filename, which only works for a
legacy-keyed lock; #1023 introduced a second, opaque id-keyed filename
shape). Filed with a concrete repro: #1255. This PR's own
fromresolution avoids the same trap by going through the existingforward
actas_lock_read(team, agent)for each ofidentities.sh's own(already small, already known) candidates, never by scanning and
reverse-parsing.
Test plan
tests/test_transcript_exists.bats— 7/7 (1 new:CLAUDE_CONFIG_DIRoverride)
tests/test_peek_poke.bats— 50/50 (4 new/widened: the claude-codetranscript-read substitute; the claude-code+claude-code poke refusal
naming ListAgents/SendMessage; the message-fallback delivery, widened to
cover several-identities disambiguation via actas lock and refusal when
that also can't narrow to exactly one)
tests/test_self_fix.bats,tests/test_actas_lock.bats— untouched,still green (an earlier revision of this PR touched
scripts/lib/actas-lock.sh/self-fix.sh; reverted in favor of reusingidentities.sh, so neither file is touched in the final diff)CLAUDE_CODE_SESSION_ID/AGMSG_SESSION_ID/CODEX_THREAD_ID(
test_codex_resume,test_claude_template,test_dispatch,test_delivery,test_team,test_spawn,test_terminal_registry,test_type_registry) after adding those totest_helper.bash's defaultunset — 458/458 + 213/213, all green
All run with HERDR/TMUX/CLAUDE_CODE_SESSION_ID/CLAUDE_CONFIG_DIR/
AGMSG_SESSION_ID/CODEX_THREAD_ID environment variables stripped.
tests/test_helper.bashnow also unsetsCLAUDE_CONFIG_DIR/AGMSG_SESSION_ID/CLAUDE_CODE_SESSION_ID/CODEX_THREAD_IDby default (same place as the existing TMUX/HERDRunsets): found live, mid-PR, when this session's own real
CLAUDE_CODE_SESSION_ID/CLAUDE_CONFIG_DIRleaked into a local test runand silently changed which branch two of the new tests exercised.
tests/test_peek_poke.bats's_terminal_less_pathfixture (a PATH builtfrom scratch to simulate "no tmux/herdr on PATH") also gained
paste: areal, already-relied-upon dependency of
type-registry.sh(used bywhoami.sh/identities.sh's callers today), not one this fixture shouldhide by omission.
Every new/changed branch was mutation-tested (temporarily broken,
confirmed red, restored) — including the several-identities disambiguation
and its actas-lock owner comparison specifically.
Static checks:
check-enforced-assertions.sh testsat baseline (626);merge-base-scoped diff grepped clean for internal seat names;
bash -nclean on every touched shell file.
Base is
main(f9061f0, 1.3.0's own tip); per 1.3.1 policy, merging tomainwaits on maintainer GO.