@mattstack/rt-client 0.9.0 → 0.10.1

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.
package/README.md CHANGED
@@ -90,7 +90,7 @@ await chatPost({ handle: 'matt', room: 'build', body: 'on it' });
90
90
 
91
91
  const health = await daemonHealth(); // { reachable, error? }, never throws
92
92
  const stop = createRelay({ // one daemon subscription, republished
93
- match: t => t.startsWith('chat/'), // `chat/<room>/msg`, `chat/wake/<handle>`
93
+ match: t => t.startsWith('chat/'), // `chat/<room>/msg`
94
94
  topic: 'chat',
95
95
  publish: (topic, data) => server.publish(topic, data),
96
96
  });
@@ -98,20 +98,18 @@ const stop = createRelay({ // one daemon sub
98
98
 
99
99
  | Function | Daemon verb |
100
100
  | --- | --- |
101
- | `chatSignIn` / `chatSignOut` / `chatAway` / `chatBack` / `chatPulse` | presence: the buddy-list row and its heartbeats |
101
+ | `chatSignIn` / `chatSignOut` / `chatAway` / `chatBack` | presence: the buddy-list row and its session heartbeat |
102
102
  | `chatBuddies` / `chatWho` / `chatRooms` | the roster, one room's members, a handle's rooms |
103
103
  | `chatJoin` / `chatLeave` / `chatArchive` | membership (`wakeOn: mention \| all \| none`); archive parks a room for everyone until a post revives it |
104
104
  | `chatPost` / `chatDm` / `chatDmOpen` / `chatRead` / `chatMessages` / `chatMark` | messages: post, DM, open a DM room without posting, read-and-advance, page, advance the cursor |
105
- | `chatArm` / `chatTouch` / `chatDisarm` / `chatUnreadWaking` | the tail's wake protocol; the CLI's `rt chat tail` uses these |
106
105
  | `paneList` / `panePeek` / `paneSpawn` / `paneAccounts` / `paneDirectories` | herdr panes: list with presence joined, peek a screen, start claude in a tab, cswap accounts, directory suggestions |
107
106
  | `chatInvite` | type `/chat:join <room>` into a pane; `accepted`, `queued` or `refused` |
108
107
  | `createRelay` / `subscribe` | the event stream; `daemonHealth` the reachability probe |
109
108
 
110
109
  Pass `{ sockPath }` as the trailing options to reach a non-default daemon
111
- socket. The verbs, their payloads and the wake protocol are specified in
112
- repo-tools `docs/superpowers/specs/2026-08-23-rt-chat-design.md` and
113
- `2026-08-24-rt-chat-presence-design.md`; the agent-facing rules are
114
- `skills/rt-chat/SKILL.md`.
110
+ socket. The verbs and their payloads are specified in repo-tools
111
+ `docs/superpowers/specs/2026-08-28-rt-chat-delivery-v2-design.md`; the
112
+ agent-facing rules are `skills/rt-chat/SKILL.md`.
115
113
 
116
114
  ## License
117
115
 
package/dist/client.d.ts CHANGED
@@ -96,35 +96,8 @@ export declare function chatMessages(a: {
96
96
  }, o?: RtClientOptions): Promise<RtResponse<{
97
97
  messages: ChatMessage[];
98
98
  }>>;
99
- export declare function chatArm(a: {
100
- handle: string;
101
- room?: string;
102
- sessionId?: string;
103
- }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
104
- export declare function chatTouch(a: {
105
- handle: string;
106
- room?: string;
107
- sessionId?: string;
108
- }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
109
- export declare function chatDisarm(a: {
110
- handle: string;
111
- sessionId?: string;
112
- }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
113
- export declare function chatUnreadWaking(a: {
114
- handle: string;
115
- room?: string;
116
- }, o?: RtClientOptions): Promise<RtResponse<{
117
- rooms: {
118
- room: string;
119
- count: number;
120
- mentions: number;
121
- maxId: number;
122
- }[];
123
- }>>;
124
99
  export declare function chatSignIn(a: Commands["chat:sign-in"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["chat:sign-in"]["data"]>>;
125
- export declare function chatSignOut(a: {
126
- sessionId: string;
127
- }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
100
+ export declare function chatSignOut(a: Commands["chat:sign-out"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["chat:sign-out"]["data"]>>;
128
101
  export declare function chatAway(a: {
129
102
  sessionId: string;
130
103
  text: string;
@@ -137,20 +110,6 @@ export declare function chatBuddies(o?: RtClientOptions): Promise<RtResponse<{
137
110
  status: BuddyStatus;
138
111
  }>;
139
112
  }>>;
140
- export declare function chatPulse(a: {
141
- sessionId: string;
142
- cwd?: string;
143
- repo?: string;
144
- branch?: string;
145
- pane?: string;
146
- }, o?: RtClientOptions): Promise<RtResponse<{
147
- unread: {
148
- dms: number;
149
- mentions: number;
150
- rooms: number;
151
- };
152
- status: BuddyStatus;
153
- }>>;
154
113
  export declare function chatDm(a: {
155
114
  from: string;
156
115
  to: string;
@@ -199,3 +158,6 @@ export declare function paneDirectories(a: Commands["pane:directories"]["payload
199
158
  export declare function chatInvite(a: Commands["chat:invite"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["chat:invite"]["data"]>>;
200
159
  /** A working target holds the connection through its prompt wait, so the budget matches chatInvite's 30s. */
201
160
  export declare function paneSend(a: Commands["pane:send"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["pane:send"]["data"]>>;
161
+ /** Brings a herdr pane to the front. The daemon routes this to the tray, which
162
+ owns the herdr focus and the native terminal-window raise. */
163
+ export declare function paneFocus(a: Commands["pane:focus"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["pane:focus"]["data"]>>;
@@ -99,8 +99,6 @@ export interface ChatMember {
99
99
  joinedAt: number;
100
100
  lastReadId: number;
101
101
  wakeOn: WakeMode;
102
- lastSeenAt?: number;
103
- armedAt?: number;
104
102
  cwd?: string;
105
103
  pane?: string;
106
104
  /** Presence-joined by chat:who's handler — the only place this type is ever returned, and it always attaches one. */
@@ -137,7 +135,7 @@ export interface RoomSummary {
137
135
  * above: mirrors lib/state/presence-store.ts's types, which rt-client
138
136
  * cannot import.
139
137
  */
140
- export type BuddyStatus = "live" | "idle" | "deaf" | "offline";
138
+ export type BuddyStatus = "live" | "idle" | "offline";
141
139
  export interface PresenceRow {
142
140
  sessionId: string;
143
141
  handle: string;
@@ -149,8 +147,6 @@ export interface PresenceRow {
149
147
  statusText?: string;
150
148
  signedInAt: number;
151
149
  lastSeenAt: number;
152
- tailSeenAt?: number;
153
- armedAt?: number;
154
150
  signedOutAt?: number;
155
151
  }
156
152
  export type AgentStatus = "idle" | "working" | "blocked" | "done" | "unknown";
@@ -193,6 +189,10 @@ export interface PaneSendResult {
193
189
  delivered: PaneDelivery;
194
190
  reason?: string;
195
191
  }
192
+ export interface PaneFocusResult {
193
+ paneId: string;
194
+ focused: boolean;
195
+ }
196
196
  export type Attention = {
197
197
  needs: boolean;
198
198
  reason: "failed" | "stale" | "stranded" | "blocked" | null;
@@ -278,6 +278,7 @@ export interface AgentRecord {
278
278
  account?: string;
279
279
  label?: string;
280
280
  caller?: string;
281
+ handle?: string;
281
282
  paneId?: string;
282
283
  tabId?: string;
283
284
  workspaceId?: string;
@@ -511,65 +512,56 @@ export interface Commands {
511
512
  messages: ChatMessage[];
512
513
  };
513
514
  };
514
- "chat:arm": {
515
- payload: {
516
- handle: string;
517
- room?: string;
518
- sessionId?: string;
519
- };
520
- data: Record<string, never>;
521
- };
522
- "chat:touch": {
523
- payload: {
524
- handle: string;
525
- room?: string;
526
- sessionId?: string;
527
- };
528
- data: Record<string, never>;
529
- };
530
- "chat:disarm": {
531
- payload: {
532
- handle: string;
533
- sessionId?: string;
534
- };
535
- data: Record<string, never>;
536
- };
537
- "chat:unread-waking": {
538
- payload: {
539
- handle: string;
540
- room?: string;
541
- };
542
- data: {
543
- rooms: {
544
- room: string;
545
- count: number;
546
- mentions: number;
547
- maxId: number;
548
- }[];
549
- };
550
- };
551
- /** No `baseHandle` means "draw me a first name": the daemon picks the least recently used pool name no live session holds. */
515
+ /**
516
+ * No `baseHandle` means "draw me a first name": the daemon picks the least
517
+ * recently used pool name no live session holds. `viaPane` resolves
518
+ * `sessionId` daemon-side from herdr's `session.snapshot` for the pane id
519
+ * in `pane`, rather than trusting a caller-supplied id -- the caller may
520
+ * have none of its own (a human or another agent signing a target pane in
521
+ * on its behalf); the response's `sessionId` and `room` are then the only
522
+ * way that caller learns what got signed in and joined, since it has
523
+ * nothing local to derive either from.
524
+ */
552
525
  "chat:sign-in": {
553
526
  payload: {
554
- sessionId: string;
527
+ sessionId?: string;
555
528
  baseHandle?: string;
556
529
  cwd?: string;
557
530
  repo?: string;
558
531
  branch?: string;
559
532
  pane?: string;
560
533
  statusText?: string;
534
+ viaPane?: boolean;
535
+ /** `viaPane` only: overrides the pane-cwd-derived room outright. */
536
+ room?: string;
537
+ /** `viaPane` only: skip room derivation/join entirely, same as --no-room on the non-pane path. */
538
+ noRoom?: boolean;
561
539
  };
562
540
  data: {
563
541
  handle: string;
564
542
  baseHandle: string;
565
543
  reclaimed: boolean;
544
+ sessionId: string;
545
+ room: string | null;
566
546
  };
567
547
  };
548
+ /**
549
+ * `viaPane` mirrors `chat:sign-in`'s: the daemon resolves `pane` to a
550
+ * session id via herdr's `session.snapshot` rather than trusting a
551
+ * caller-supplied one, so a process signing another pane out (herdr-chat,
552
+ * or a human invoking a target pane) needs neither that pane's session id
553
+ * nor its own. The response's `sessionId` is the RESOLVED id, the only way
554
+ * such a caller learns which session file to delete locally.
555
+ */
568
556
  "chat:sign-out": {
569
557
  payload: {
558
+ sessionId?: string;
559
+ pane?: string;
560
+ viaPane?: boolean;
561
+ };
562
+ data: {
570
563
  sessionId: string;
571
564
  };
572
- data: Record<string, never>;
573
565
  };
574
566
  "chat:away": {
575
567
  payload: {
@@ -592,24 +584,6 @@ export interface Commands {
592
584
  }>;
593
585
  };
594
586
  };
595
- /** `unread`'s three fields are disjoint and sum to the true total: `dms` is DM-room waking count; `mentions` is non-DM waking mentions; `rooms` is non-DM waking count minus those mentions (never negative). */
596
- "chat:pulse": {
597
- payload: {
598
- sessionId: string;
599
- cwd?: string;
600
- repo?: string;
601
- branch?: string;
602
- pane?: string;
603
- };
604
- data: {
605
- unread: {
606
- dms: number;
607
- mentions: number;
608
- rooms: number;
609
- };
610
- status: BuddyStatus;
611
- };
612
- };
613
587
  "chat:dm": {
614
588
  payload: {
615
589
  from: string;
@@ -748,6 +722,12 @@ export interface Commands {
748
722
  };
749
723
  data: PaneSendResult;
750
724
  };
725
+ "pane:focus": {
726
+ payload: {
727
+ paneId: string;
728
+ };
729
+ data: PaneFocusResult;
730
+ };
751
731
  }
752
732
  export type CommandName = keyof Commands;
753
733
  export declare const COMMAND_NAMES: readonly CommandName[];
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  export { rtCommand, DEFAULT_SOCK } from "./transport.ts";
2
2
  export type { RtResponse, RtClientOptions } from "./transport.ts";
3
- export { readProjectMRs, readDiscussions, readMrsByBranch, readBranchCache, resolveForgeToken, listRuns, getRun, abandonRun, chatJoin, chatLeave, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatArm, chatTouch, chatDisarm, chatUnreadWaking, chatSignIn, chatSignOut, chatAway, chatBack, chatBuddies, chatPulse, chatDm, chatArchive, chatDmOpen, eventsHead, agentStart, agentResume, agentGet, agentList, paneList, panePeek, paneSpawn, paneAccounts, paneDirectories, chatInvite, paneSend, } from "./client.ts";
3
+ export { readProjectMRs, readDiscussions, readMrsByBranch, readBranchCache, resolveForgeToken, listRuns, getRun, abandonRun, chatJoin, chatLeave, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatSignIn, chatSignOut, chatAway, chatBack, chatBuddies, chatDm, chatArchive, chatDmOpen, eventsHead, agentStart, agentResume, agentGet, agentList, paneList, panePeek, paneSpawn, paneAccounts, paneDirectories, chatInvite, paneSend, paneFocus, } from "./client.ts";
4
4
  export { COMMAND_NAMES } from "./commands.ts";
5
- export type { Discussion, DemandDecl, ProjectMRsScope, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, BranchEnrichment, Commands, CommandName, ForgeSlug, ForgeTokenData, Attention, RunSummary, RunStageRow, RunFieldRow, RunDecisionRow, RunDetail, WakeMode, ChatMember, ChatMessage, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, } from "./commands.ts";
5
+ export type { Discussion, DemandDecl, ProjectMRsScope, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, BranchEnrichment, Commands, CommandName, ForgeSlug, ForgeTokenData, Attention, RunSummary, RunStageRow, RunFieldRow, RunDecisionRow, RunDetail, WakeMode, ChatMember, ChatMessage, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, PaneFocusResult, } from "./commands.ts";
6
6
  export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";
7
7
  export type { RelayEventType } from "./relay.ts";
8
8
  export { daemonHealth } from "./health.ts";
package/dist/index.js CHANGED
@@ -117,36 +117,10 @@ function chatMessages(a, o = {}) {
117
117
  payload.limit = a.limit;
118
118
  return rtCommand("chat:messages", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
119
119
  }
120
- function chatArm(a, o = {}) {
121
- const payload = { handle: a.handle };
122
- if (a.room !== undefined)
123
- payload.room = a.room;
124
- if (a.sessionId !== undefined)
125
- payload.sessionId = a.sessionId;
126
- return rtCommand("chat:arm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
127
- }
128
- function chatTouch(a, o = {}) {
129
- const payload = { handle: a.handle };
130
- if (a.room !== undefined)
131
- payload.room = a.room;
132
- if (a.sessionId !== undefined)
133
- payload.sessionId = a.sessionId;
134
- return rtCommand("chat:touch", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
135
- }
136
- function chatDisarm(a, o = {}) {
137
- const payload = { handle: a.handle };
120
+ function chatSignIn(a, o = {}) {
121
+ const payload = {};
138
122
  if (a.sessionId !== undefined)
139
123
  payload.sessionId = a.sessionId;
140
- return rtCommand("chat:disarm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
141
- }
142
- function chatUnreadWaking(a, o = {}) {
143
- const payload = { handle: a.handle };
144
- if (a.room !== undefined)
145
- payload.room = a.room;
146
- return rtCommand("chat:unread-waking", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
147
- }
148
- function chatSignIn(a, o = {}) {
149
- const payload = { sessionId: a.sessionId };
150
124
  if (a.baseHandle !== undefined)
151
125
  payload.baseHandle = a.baseHandle;
152
126
  if (a.cwd !== undefined)
@@ -159,10 +133,23 @@ function chatSignIn(a, o = {}) {
159
133
  payload.pane = a.pane;
160
134
  if (a.statusText !== undefined)
161
135
  payload.statusText = a.statusText;
136
+ if (a.viaPane !== undefined)
137
+ payload.viaPane = a.viaPane;
138
+ if (a.room !== undefined)
139
+ payload.room = a.room;
140
+ if (a.noRoom !== undefined)
141
+ payload.noRoom = a.noRoom;
162
142
  return rtCommand("chat:sign-in", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
163
143
  }
164
144
  function chatSignOut(a, o = {}) {
165
- return rtCommand("chat:sign-out", { sessionId: a.sessionId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
145
+ const payload = {};
146
+ if (a.sessionId !== undefined)
147
+ payload.sessionId = a.sessionId;
148
+ if (a.pane !== undefined)
149
+ payload.pane = a.pane;
150
+ if (a.viaPane !== undefined)
151
+ payload.viaPane = a.viaPane;
152
+ return rtCommand("chat:sign-out", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
166
153
  }
167
154
  function chatAway(a, o = {}) {
168
155
  return rtCommand("chat:away", { sessionId: a.sessionId, text: a.text }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
@@ -173,18 +160,6 @@ function chatBack(a, o = {}) {
173
160
  function chatBuddies(o = {}) {
174
161
  return rtCommand("chat:buddies", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
175
162
  }
176
- function chatPulse(a, o = {}) {
177
- const payload = { sessionId: a.sessionId };
178
- if (a.cwd !== undefined)
179
- payload.cwd = a.cwd;
180
- if (a.repo !== undefined)
181
- payload.repo = a.repo;
182
- if (a.branch !== undefined)
183
- payload.branch = a.branch;
184
- if (a.pane !== undefined)
185
- payload.pane = a.pane;
186
- return rtCommand("chat:pulse", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
187
- }
188
163
  function chatDm(a, o = {}) {
189
164
  const payload = { from: a.from, to: a.to, body: a.body };
190
165
  if (a.sessionId !== undefined)
@@ -267,6 +242,9 @@ function paneSend(a, o = {}) {
267
242
  payload.callerPane = a.callerPane;
268
243
  return rtCommand("pane:send", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
269
244
  }
245
+ function paneFocus(a, o = {}) {
246
+ return rtCommand("pane:focus", { paneId: a.paneId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
247
+ }
270
248
  // src/commands.ts
271
249
  var COMMAND_NAMES = [
272
250
  "project-mrs:read",
@@ -289,16 +267,11 @@ var COMMAND_NAMES = [
289
267
  "chat:who",
290
268
  "chat:mark",
291
269
  "chat:messages",
292
- "chat:arm",
293
- "chat:touch",
294
- "chat:disarm",
295
- "chat:unread-waking",
296
270
  "chat:sign-in",
297
271
  "chat:sign-out",
298
272
  "chat:away",
299
273
  "chat:back",
300
274
  "chat:buddies",
301
- "chat:pulse",
302
275
  "chat:dm",
303
276
  "chat:archive",
304
277
  "chat:dm-open",
@@ -312,7 +285,8 @@ var COMMAND_NAMES = [
312
285
  "pane:accounts",
313
286
  "pane:directories",
314
287
  "pane:spawn",
315
- "pane:send"
288
+ "pane:send",
289
+ "pane:focus"
316
290
  ];
317
291
  // src/relay.ts
318
292
  var DEFAULT_WS_URL = "ws://127.0.0.1:9401/ws";
@@ -1764,6 +1738,7 @@ export {
1764
1738
  paneSend,
1765
1739
  panePeek,
1766
1740
  paneList,
1741
+ paneFocus,
1767
1742
  paneDirectories,
1768
1743
  paneAccounts,
1769
1744
  normalizeRemote,
@@ -1783,13 +1758,10 @@ export {
1783
1758
  createRelay,
1784
1759
  clearIdentityMemo,
1785
1760
  chatWho,
1786
- chatUnreadWaking,
1787
- chatTouch,
1788
1761
  chatSignOut,
1789
1762
  chatSignIn,
1790
1763
  chatRooms,
1791
1764
  chatRead,
1792
- chatPulse,
1793
1765
  chatPost,
1794
1766
  chatMessages,
1795
1767
  chatMark,
@@ -1798,11 +1770,9 @@ export {
1798
1770
  chatInvite,
1799
1771
  chatDmOpen,
1800
1772
  chatDm,
1801
- chatDisarm,
1802
1773
  chatBuddies,
1803
1774
  chatBack,
1804
1775
  chatAway,
1805
- chatArm,
1806
1776
  chatArchive,
1807
1777
  allDefs,
1808
1778
  agentStart,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mattstack/rt-client",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/client.ts CHANGED
@@ -220,65 +220,35 @@ export function chatMessages(
220
220
  return rtCommand<{ messages: ChatMessage[] }>("chat:messages", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
221
221
  }
222
222
 
223
- export function chatArm(
224
- a: { handle: string; room?: string; sessionId?: string },
225
- o: RtClientOptions = {},
226
- ): Promise<RtResponse<Record<string, never>>> {
227
- const payload: Record<string, unknown> = { handle: a.handle };
228
- if (a.room !== undefined) payload.room = a.room;
229
- if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
230
- return rtCommand<Record<string, never>>("chat:arm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
231
- }
232
-
233
- export function chatTouch(
234
- a: { handle: string; room?: string; sessionId?: string },
235
- o: RtClientOptions = {},
236
- ): Promise<RtResponse<Record<string, never>>> {
237
- const payload: Record<string, unknown> = { handle: a.handle };
238
- if (a.room !== undefined) payload.room = a.room;
239
- if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
240
- return rtCommand<Record<string, never>>("chat:touch", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
241
- }
242
-
243
- export function chatDisarm(
244
- a: { handle: string; sessionId?: string },
245
- o: RtClientOptions = {},
246
- ): Promise<RtResponse<Record<string, never>>> {
247
- const payload: Record<string, unknown> = { handle: a.handle };
248
- if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
249
- return rtCommand<Record<string, never>>("chat:disarm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
250
- }
251
-
252
- export function chatUnreadWaking(
253
- a: { handle: string; room?: string },
254
- o: RtClientOptions = {},
255
- ): Promise<RtResponse<{ rooms: { room: string; count: number; mentions: number; maxId: number }[] }>> {
256
- const payload: Record<string, unknown> = { handle: a.handle };
257
- if (a.room !== undefined) payload.room = a.room;
258
- return rtCommand<{ rooms: { room: string; count: number; mentions: number; maxId: number }[] }>("chat:unread-waking", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
259
- }
260
-
261
223
  // ─── Presence ──────────────────────────────────────────────────────────
262
224
 
263
225
  export function chatSignIn(
264
226
  a: Commands["chat:sign-in"]["payload"],
265
227
  o: RtClientOptions = {},
266
228
  ): Promise<RtResponse<Commands["chat:sign-in"]["data"]>> {
267
- const payload: Record<string, unknown> = { sessionId: a.sessionId };
229
+ const payload: Record<string, unknown> = {};
230
+ if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
268
231
  if (a.baseHandle !== undefined) payload.baseHandle = a.baseHandle;
269
232
  if (a.cwd !== undefined) payload.cwd = a.cwd;
270
233
  if (a.repo !== undefined) payload.repo = a.repo;
271
234
  if (a.branch !== undefined) payload.branch = a.branch;
272
235
  if (a.pane !== undefined) payload.pane = a.pane;
273
236
  if (a.statusText !== undefined) payload.statusText = a.statusText;
237
+ if (a.viaPane !== undefined) payload.viaPane = a.viaPane;
238
+ if (a.room !== undefined) payload.room = a.room;
239
+ if (a.noRoom !== undefined) payload.noRoom = a.noRoom;
274
240
  return rtCommand<Commands["chat:sign-in"]["data"]>("chat:sign-in", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
275
241
  }
276
242
 
277
243
  export function chatSignOut(
278
- a: { sessionId: string },
244
+ a: Commands["chat:sign-out"]["payload"],
279
245
  o: RtClientOptions = {},
280
- ): Promise<RtResponse<Record<string, never>>> {
281
- return rtCommand<Record<string, never>>("chat:sign-out", { sessionId: a.sessionId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
246
+ ): Promise<RtResponse<Commands["chat:sign-out"]["data"]>> {
247
+ const payload: Record<string, unknown> = {};
248
+ if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
249
+ if (a.pane !== undefined) payload.pane = a.pane;
250
+ if (a.viaPane !== undefined) payload.viaPane = a.viaPane;
251
+ return rtCommand<Commands["chat:sign-out"]["data"]>("chat:sign-out", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
282
252
  }
283
253
 
284
254
  export function chatAway(
@@ -301,22 +271,6 @@ export function chatBuddies(
301
271
  return rtCommand<{ buddies: Array<PresenceRow & { status: BuddyStatus }> }>("chat:buddies", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
302
272
  }
303
273
 
304
- export function chatPulse(
305
- a: { sessionId: string; cwd?: string; repo?: string; branch?: string; pane?: string },
306
- o: RtClientOptions = {},
307
- ): Promise<RtResponse<{ unread: { dms: number; mentions: number; rooms: number }; status: BuddyStatus }>> {
308
- const payload: Record<string, unknown> = { sessionId: a.sessionId };
309
- if (a.cwd !== undefined) payload.cwd = a.cwd;
310
- if (a.repo !== undefined) payload.repo = a.repo;
311
- if (a.branch !== undefined) payload.branch = a.branch;
312
- if (a.pane !== undefined) payload.pane = a.pane;
313
- return rtCommand<{ unread: { dms: number; mentions: number; rooms: number }; status: BuddyStatus }>(
314
- "chat:pulse",
315
- payload,
316
- { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 },
317
- );
318
- }
319
-
320
274
  export function chatDm(
321
275
  a: { from: string; to: string; body: string; sessionId?: string },
322
276
  o: RtClientOptions = {},
@@ -434,3 +388,12 @@ export function paneSend(
434
388
  if (a.callerPane !== undefined) payload.callerPane = a.callerPane;
435
389
  return rtCommand<Commands["pane:send"]["data"]>("pane:send", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
436
390
  }
391
+
392
+ /** Brings a herdr pane to the front. The daemon routes this to the tray, which
393
+ owns the herdr focus and the native terminal-window raise. */
394
+ export function paneFocus(
395
+ a: Commands["pane:focus"]["payload"],
396
+ o: RtClientOptions = {},
397
+ ): Promise<RtResponse<Commands["pane:focus"]["data"]>> {
398
+ return rtCommand<Commands["pane:focus"]["data"]>("pane:focus", { paneId: a.paneId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
399
+ }
package/src/commands.ts CHANGED
@@ -92,8 +92,6 @@ export interface ChatMember {
92
92
  joinedAt: number;
93
93
  lastReadId: number;
94
94
  wakeOn: WakeMode;
95
- lastSeenAt?: number;
96
- armedAt?: number;
97
95
  cwd?: string;
98
96
  pane?: string;
99
97
  /** Presence-joined by chat:who's handler — the only place this type is ever returned, and it always attaches one. */
@@ -130,7 +128,7 @@ export interface RoomSummary {
130
128
  * above: mirrors lib/state/presence-store.ts's types, which rt-client
131
129
  * cannot import.
132
130
  */
133
- export type BuddyStatus = "live" | "idle" | "deaf" | "offline";
131
+ export type BuddyStatus = "live" | "idle" | "offline";
134
132
 
135
133
  export interface PresenceRow {
136
134
  sessionId: string;
@@ -143,8 +141,6 @@ export interface PresenceRow {
143
141
  statusText?: string;
144
142
  signedInAt: number;
145
143
  lastSeenAt: number;
146
- tailSeenAt?: number;
147
- armedAt?: number;
148
144
  signedOutAt?: number;
149
145
  }
150
146
 
@@ -170,6 +166,7 @@ export interface InviteResult { paneId: string; delivered: "accepted" | "queued"
170
166
  /** Duplicated shape on purpose: mirrors lib/daemon/inject.ts's InjectResult. */
171
167
  export type PaneDelivery = "accepted" | "queued" | "refused";
172
168
  export interface PaneSendResult { paneId: string; delivered: PaneDelivery; reason?: string }
169
+ export interface PaneFocusResult { paneId: string; focused: boolean }
173
170
 
174
171
  // SKILLS-53: one judgment, computed once in rt, so the console and the tray
175
172
  // never derive two verdicts that can disagree.
@@ -223,7 +220,7 @@ export interface AgentRecord {
223
220
  id: string; repo: string; cwd: string; provider: string;
224
221
  surface: AgentSurface; sessionId: string;
225
222
  model?: string; effort?: string; account?: string;
226
- label?: string; caller?: string;
223
+ label?: string; caller?: string; handle?: string;
227
224
  paneId?: string; tabId?: string; workspaceId?: string;
228
225
  extraArgs?: string; exitCode?: number; resultPath?: string;
229
226
  createdAt: number; lastResumedAt?: number; finishedAt?: number;
@@ -293,27 +290,51 @@ export interface Commands {
293
290
  "chat:who": { payload: { room: string }; data: { members: ChatMember[] } };
294
291
  "chat:mark": { payload: { handle: string; room?: string }; data: Record<string, never> };
295
292
  "chat:messages": { payload: { room: string; before?: number; limit?: number }; data: { messages: ChatMessage[] } };
296
- "chat:arm": { payload: { handle: string; room?: string; sessionId?: string }; data: Record<string, never> };
297
- "chat:touch": { payload: { handle: string; room?: string; sessionId?: string }; data: Record<string, never> };
298
- "chat:disarm": { payload: { handle: string; sessionId?: string }; data: Record<string, never> };
299
- "chat:unread-waking": { payload: { handle: string; room?: string }; data: { rooms: { room: string; count: number; mentions: number; maxId: number }[] } };
300
293
 
301
294
  // A session id keys these to one signed-in handle, not a room-membership
302
295
  // handle string.
303
- /** No `baseHandle` means "draw me a first name": the daemon picks the least recently used pool name no live session holds. */
296
+ /**
297
+ * No `baseHandle` means "draw me a first name": the daemon picks the least
298
+ * recently used pool name no live session holds. `viaPane` resolves
299
+ * `sessionId` daemon-side from herdr's `session.snapshot` for the pane id
300
+ * in `pane`, rather than trusting a caller-supplied id -- the caller may
301
+ * have none of its own (a human or another agent signing a target pane in
302
+ * on its behalf); the response's `sessionId` and `room` are then the only
303
+ * way that caller learns what got signed in and joined, since it has
304
+ * nothing local to derive either from.
305
+ */
304
306
  "chat:sign-in": {
305
- payload: { sessionId: string; baseHandle?: string; cwd?: string; repo?: string; branch?: string; pane?: string; statusText?: string };
306
- data: { handle: string; baseHandle: string; reclaimed: boolean };
307
+ payload: {
308
+ sessionId?: string;
309
+ baseHandle?: string;
310
+ cwd?: string;
311
+ repo?: string;
312
+ branch?: string;
313
+ pane?: string;
314
+ statusText?: string;
315
+ viaPane?: boolean;
316
+ /** `viaPane` only: overrides the pane-cwd-derived room outright. */
317
+ room?: string;
318
+ /** `viaPane` only: skip room derivation/join entirely, same as --no-room on the non-pane path. */
319
+ noRoom?: boolean;
320
+ };
321
+ data: { handle: string; baseHandle: string; reclaimed: boolean; sessionId: string; room: string | null };
322
+ };
323
+ /**
324
+ * `viaPane` mirrors `chat:sign-in`'s: the daemon resolves `pane` to a
325
+ * session id via herdr's `session.snapshot` rather than trusting a
326
+ * caller-supplied one, so a process signing another pane out (herdr-chat,
327
+ * or a human invoking a target pane) needs neither that pane's session id
328
+ * nor its own. The response's `sessionId` is the RESOLVED id, the only way
329
+ * such a caller learns which session file to delete locally.
330
+ */
331
+ "chat:sign-out": {
332
+ payload: { sessionId?: string; pane?: string; viaPane?: boolean };
333
+ data: { sessionId: string };
307
334
  };
308
- "chat:sign-out": { payload: { sessionId: string }; data: Record<string, never> };
309
335
  "chat:away": { payload: { sessionId: string; text: string }; data: Record<string, never> };
310
336
  "chat:back": { payload: { sessionId: string }; data: Record<string, never> };
311
337
  "chat:buddies": { payload: Record<string, never>; data: { buddies: Array<PresenceRow & { status: BuddyStatus }> } };
312
- /** `unread`'s three fields are disjoint and sum to the true total: `dms` is DM-room waking count; `mentions` is non-DM waking mentions; `rooms` is non-DM waking count minus those mentions (never negative). */
313
- "chat:pulse": {
314
- payload: { sessionId: string; cwd?: string; repo?: string; branch?: string; pane?: string };
315
- data: { unread: { dms: number; mentions: number; rooms: number }; status: BuddyStatus };
316
- };
317
338
  "chat:dm": { payload: { from: string; to: string; body: string; sessionId?: string }; data: { room: string; id: number; recipients: string[] } };
318
339
  "chat:archive": { payload: { room: string; handle: string; archived: boolean }; data: { room: string; archivedAt: number | null } };
319
340
  "chat:dm-open": { payload: { from: string; to: string; sessionId?: string }; data: { room: string; created: boolean } };
@@ -333,6 +354,7 @@ export interface Commands {
333
354
  data: { pane: ChatPane; ready: boolean };
334
355
  };
335
356
  "pane:send": { payload: { paneId: string; text: string; callerPane?: string }; data: PaneSendResult };
357
+ "pane:focus": { payload: { paneId: string }; data: PaneFocusResult };
336
358
  }
337
359
 
338
360
  export type CommandName = keyof Commands;
@@ -358,16 +380,11 @@ export const COMMAND_NAMES: readonly CommandName[] = [
358
380
  "chat:who",
359
381
  "chat:mark",
360
382
  "chat:messages",
361
- "chat:arm",
362
- "chat:touch",
363
- "chat:disarm",
364
- "chat:unread-waking",
365
383
  "chat:sign-in",
366
384
  "chat:sign-out",
367
385
  "chat:away",
368
386
  "chat:back",
369
387
  "chat:buddies",
370
- "chat:pulse",
371
388
  "chat:dm",
372
389
  "chat:archive",
373
390
  "chat:dm-open",
@@ -382,4 +399,5 @@ export const COMMAND_NAMES: readonly CommandName[] = [
382
399
  "pane:directories",
383
400
  "pane:spawn",
384
401
  "pane:send",
402
+ "pane:focus",
385
403
  ];
package/src/index.ts CHANGED
@@ -18,16 +18,11 @@ export {
18
18
  chatWho,
19
19
  chatMark,
20
20
  chatMessages,
21
- chatArm,
22
- chatTouch,
23
- chatDisarm,
24
- chatUnreadWaking,
25
21
  chatSignIn,
26
22
  chatSignOut,
27
23
  chatAway,
28
24
  chatBack,
29
25
  chatBuddies,
30
- chatPulse,
31
26
  chatDm,
32
27
  chatArchive,
33
28
  chatDmOpen,
@@ -43,6 +38,7 @@ export {
43
38
  paneDirectories,
44
39
  chatInvite,
45
40
  paneSend,
41
+ paneFocus,
46
42
  } from "./client.ts";
47
43
 
48
44
  export { COMMAND_NAMES } from "./commands.ts";
@@ -80,6 +76,7 @@ export type {
80
76
  InviteResult,
81
77
  PaneDelivery,
82
78
  PaneSendResult,
79
+ PaneFocusResult,
83
80
  } from "./commands.ts";
84
81
 
85
82
  export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";