@mattstack/rt-client 0.19.1 → 0.20.0

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
@@ -104,6 +104,7 @@ const stop = createRelay({ // one daemon sub
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
105
  | `paneList` / `panePeek` / `paneSpawn` / `paneAccounts` / `paneDirectories` | herdr panes: list with presence joined, peek a screen, start claude in a tab, cswap accounts, directory suggestions |
106
106
  | `chatInvite` | type `/chat:join <room>` into a pane; `accepted`, `queued` or `refused` |
107
+ | `paneSend` | type a line into a pane; `accepted`, `queued` or `refused`, plus an optional `continuation` the daemon types once the pane's turn has ended |
107
108
  | `createRelay` / `subscribe` | the event stream; `daemonHealth` the reachability probe |
108
109
 
109
110
  Pass `{ sockPath }` as the trailing options to reach a non-default daemon
package/dist/client.d.ts CHANGED
@@ -183,6 +183,9 @@ export declare function paneSend(a: Commands["pane:send"]["payload"], o?: RtClie
183
183
  /** Brings a herdr pane to the front. The daemon routes this to the tray, which
184
184
  owns the herdr focus and the native terminal-window raise. */
185
185
  export declare function paneFocus(a: Commands["pane:focus"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["pane:focus"]["data"]>>;
186
+ /** Never triggers a sweep; a plain read of the last one's snapshot. */
187
+ export declare function reconcilerStatus(o?: RtClientOptions): Promise<RtResponse<Commands["reconciler:status"]["data"]>>;
188
+ export declare function reconcilerClear(a: Commands["reconciler:clear"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["reconciler:clear"]["data"]>>;
186
189
  export declare function gateOpen(a: Commands["gate:open"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:open"]["data"]>>;
187
190
  export declare function gateAnswer(a: Commands["gate:answer"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:answer"]["data"]>>;
188
191
  /** Daemon clamps its own wait to 240s (gates-store.ts); the client abort
@@ -102,6 +102,8 @@ export interface EventsBusEvent {
102
102
  value gates-store.ts's release tracking (CAS winner or loser) reacts to. */
103
103
  export declare const GATE_BY_PANE = "pane";
104
104
  export type GateStatus = "open" | "answered" | "parked" | "closed";
105
+ /** Reconciler's view of an agent's liveness; also the value `GateRow.executor` is stamped with. */
106
+ export type ExecutorState = "live" | "blocked" | "hidden" | "gone" | "cleared" | "unknown";
105
107
  export type GateOption = string | {
106
108
  value: string;
107
109
  label: string;
@@ -143,7 +145,10 @@ export interface GateRow {
143
145
  openedAt: number;
144
146
  parkedAt: number | null;
145
147
  closedAt: number | null;
146
- closedReason: "abandoned" | "superseded" | "pruned" | null;
148
+ /** "resolved" is stamped only by the reconciler's auto-recovery close
149
+ (a blocked/gone pane came back); every other closer uses one of the
150
+ other three. */
151
+ closedReason: "abandoned" | "superseded" | "pruned" | "resolved" | null;
147
152
  /** Set only when `closedReason` is "superseded": the id of the gate that superseded this one. */
148
153
  supersededBy: string | null;
149
154
  agent: string | null;
@@ -152,12 +157,17 @@ export interface GateRow {
152
157
  session: string;
153
158
  } | null;
154
159
  delivery: {
155
- outcome: "delivered" | "dead-pane";
160
+ outcome: "delivered" | "dead-pane" | "confirmed" | "stuck";
156
161
  at: number;
157
162
  } | null;
158
163
  released: boolean;
159
164
  owner: string | null;
160
165
  escalatedAt: number | null;
166
+ /** Set by answer-time execution handling: an answered gate whose executor
167
+ couldn't be resumed. Cleared (absent) once resumption succeeds. */
168
+ execution?: "unassigned";
169
+ /** Stamped by the reconciler sweep for open/parked rows only. */
170
+ executor?: ExecutorState;
161
171
  }
162
172
  export interface GateSubscription {
163
173
  id: string;
@@ -213,7 +223,7 @@ export interface HerdStatusData {
213
223
  openGate: string | null;
214
224
  paneStatus: string | null;
215
225
  lastGateStatus: GateStatus | null;
216
- lastGateDelivery: "delivered" | "dead-pane" | null;
226
+ lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null;
217
227
  }>;
218
228
  unread: number;
219
229
  lifecycleConnected: boolean;
@@ -345,10 +355,14 @@ export interface InviteResult {
345
355
  }
346
356
  /** Duplicated shape on purpose: mirrors lib/daemon/inject.ts's InjectResult. */
347
357
  export type PaneDelivery = "accepted" | "queued" | "refused";
358
+ /** `continuation` is present only when the daemon scheduled a second line for after the target's current turn. */
348
359
  export interface PaneSendResult {
349
360
  paneId: string;
350
361
  delivered: PaneDelivery;
351
362
  reason?: string;
363
+ continuation?: {
364
+ delivered: "deferred";
365
+ };
352
366
  }
353
367
  /** `attendTab` is set only for a `bg:` ref: focus for a background pane IS
354
368
  the attend flow (a visible tab running a terminal attach), and this is
@@ -431,6 +445,23 @@ export interface RunDetail {
431
445
  schemaAhead: boolean;
432
446
  }
433
447
  export type AgentSurface = "herdr" | "headless";
448
+ /** The reconciler's per-agent snapshot: one row per agent it knows about, live or not. */
449
+ export interface ExecutorView {
450
+ agentId: string;
451
+ repo: string | null;
452
+ subject: string | null;
453
+ surface: AgentSurface;
454
+ sessionId: string;
455
+ paneRef: string | null;
456
+ state: ExecutorState;
457
+ since: number;
458
+ openGateIds: string[];
459
+ }
460
+ export interface ReconcilerStatus {
461
+ sweptAt: number;
462
+ herdrReachable: boolean;
463
+ executors: ExecutorView[];
464
+ }
434
465
  export interface AgentRecord {
435
466
  id: string;
436
467
  repo: string;
@@ -444,6 +475,12 @@ export interface AgentRecord {
444
475
  label?: string;
445
476
  caller?: string;
446
477
  handle?: string;
478
+ /** The gate-protocol subject stamped as RT_GATE_SUBJECT at launch. Left
479
+ undefined when the caller passed none (RT_GATE_SUBJECT still falls
480
+ back to "agent:<id>" at launch time); an explicit value is persisted
481
+ so a resume re-stamps the same one, AND gates whether the gate-fork
482
+ PreToolUse hook gets injected at all. */
483
+ subject?: string;
447
484
  paneId?: string;
448
485
  tabId?: string;
449
486
  workspaceId?: string;
@@ -1096,6 +1133,7 @@ export interface Commands {
1096
1133
  herdrSocket?: string;
1097
1134
  handle?: string;
1098
1135
  bg?: boolean;
1136
+ subject?: string;
1099
1137
  };
1100
1138
  data: AgentRecord;
1101
1139
  };
@@ -1182,6 +1220,7 @@ export interface Commands {
1182
1220
  paneId: string;
1183
1221
  text: string;
1184
1222
  callerPane?: string;
1223
+ continuation?: string;
1185
1224
  };
1186
1225
  data: PaneSendResult;
1187
1226
  };
@@ -1360,6 +1399,20 @@ export interface Commands {
1360
1399
  payload: Record<string, never>;
1361
1400
  data: unknown;
1362
1401
  };
1402
+ /** Read-only: the last sweep's snapshot. Never triggers a sweep itself. */
1403
+ "reconciler:status": {
1404
+ payload: Record<string, never>;
1405
+ data: ReconcilerStatus;
1406
+ };
1407
+ /** Manual override: marks the agent cleared and closes its open/parked gates (reconciler.ts's clear()). */
1408
+ "reconciler:clear": {
1409
+ payload: {
1410
+ agentId: string;
1411
+ };
1412
+ data: {
1413
+ cleared: true;
1414
+ };
1415
+ };
1363
1416
  "gate:open": {
1364
1417
  payload: {
1365
1418
  subject: string;
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, chatAck, chatClaim, chatRelease, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatSignIn, chatSignOut, chatAway, chatBack, chatBuddies, chatDm, chatArchive, chatDmOpen, eventsHead, eventsEmit, eventsWait, eventsList, agentStart, agentResume, agentGet, agentList, paneList, panePeek, paneSpawn, paneAccounts, paneDirectories, chatInvite, paneSend, paneFocus, gateOpen, gateAnswer, gateWait, gateList, gatePark, gateClose, gateSubscribe, gateUnsubscribe, gateSubscriptions, herdStart, herdSpawn, herdAsk, herdMilestone, herdAnswer, herdReport, herdGates, herdStatus, herdList, herdResume, herdClose, herdAttend, herdWrapUp, herdStopHidden, bgEnsure, bgStatus, bgStop, bgRelease, } from "./client.ts";
3
+ export { readProjectMRs, readDiscussions, readMrsByBranch, readBranchCache, resolveForgeToken, listRuns, getRun, abandonRun, chatJoin, chatLeave, chatAck, chatClaim, chatRelease, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatSignIn, chatSignOut, chatAway, chatBack, chatBuddies, chatDm, chatArchive, chatDmOpen, eventsHead, eventsEmit, eventsWait, eventsList, agentStart, agentResume, agentGet, agentList, paneList, panePeek, paneSpawn, paneAccounts, paneDirectories, chatInvite, paneSend, paneFocus, reconcilerStatus, reconcilerClear, gateOpen, gateAnswer, gateWait, gateList, gatePark, gateClose, gateSubscribe, gateUnsubscribe, gateSubscriptions, herdStart, herdSpawn, herdAsk, herdMilestone, herdAnswer, herdReport, herdGates, herdStatus, herdList, herdResume, herdClose, herdAttend, herdWrapUp, herdStopHidden, bgEnsure, bgStatus, bgStop, bgRelease, } from "./client.ts";
4
4
  export { COMMAND_NAMES, GATE_BY_PANE, gateOptionValue, gateOptionLabel } 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, ChatClaimOutcome, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, PaneFocusResult, GateStatus, GateOption, GateOrigin, GateQuestion, GateAnswer, GateRow, GateSubscription, HerdInfo, HerdListRow, HerdJobInfo, HerdStatusData, } 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, ChatClaimOutcome, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ExecutorState, ExecutorView, ReconcilerStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, PaneFocusResult, GateStatus, GateOption, GateOrigin, GateQuestion, GateAnswer, GateRow, GateSubscription, HerdInfo, HerdListRow, HerdJobInfo, HerdStatusData, } 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
@@ -5,7 +5,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
5
5
  import { homedir } from "os";
6
6
  import { join } from "path";
7
7
  function defaultSock() {
8
- return join(process.env.HOME ?? homedir(), ".mattstack", "rt", "rt.sock");
8
+ return process.env.RT_DAEMON_SOCK || join(process.env.HOME ?? homedir(), ".mattstack", "rt", "rt.sock");
9
9
  }
10
10
  var DEFAULT_SOCK = defaultSock();
11
11
  async function rtCommand(cmd, payload, opts = {}) {
@@ -205,7 +205,7 @@ function eventsList(payload, o = {}) {
205
205
  }
206
206
  function agentStart(a, o = {}) {
207
207
  const payload = { repo: a.repo, cwd: a.cwd };
208
- for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg"]) {
208
+ for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg", "subject"]) {
209
209
  if (a[k] !== undefined)
210
210
  payload[k] = a[k];
211
211
  }
@@ -265,6 +265,8 @@ function paneSend(a, o = {}) {
265
265
  const payload = { paneId: a.paneId, text: a.text };
266
266
  if (a.callerPane !== undefined)
267
267
  payload.callerPane = a.callerPane;
268
+ if (a.continuation !== undefined)
269
+ payload.continuation = a.continuation;
268
270
  return rtCommand("pane:send", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
269
271
  }
270
272
  function paneFocus(a, o = {}) {
@@ -273,6 +275,12 @@ function paneFocus(a, o = {}) {
273
275
  payload.callerWorkspace = a.callerWorkspace;
274
276
  return rtCommand("pane:focus", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
275
277
  }
278
+ function reconcilerStatus(o = {}) {
279
+ return rtCommand("reconciler:status", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
280
+ }
281
+ function reconcilerClear(a, o = {}) {
282
+ return rtCommand("reconciler:clear", { agentId: a.agentId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
283
+ }
276
284
  function gateOpen(a, o = {}) {
277
285
  const payload = { subject: a.subject, kind: a.kind, questions: a.questions };
278
286
  for (const k of ["meta", "agent", "pane", "nudge", "context", "origin"])
@@ -478,6 +486,8 @@ var COMMAND_NAMES = [
478
486
  "endpoint:status",
479
487
  "repos:locate",
480
488
  "freshness:reconcile",
489
+ "reconciler:status",
490
+ "reconciler:clear",
481
491
  "gate:open",
482
492
  "gate:answer",
483
493
  "gate:wait",
@@ -1031,6 +1041,14 @@ var REGISTRY = [
1031
1041
  merge: "replace",
1032
1042
  description: 'The machine\'s intended flavor, "dev" or "prod". Normally written by `rt settings dev-mode` after a successful handoff; a manual `rt settings set` is the blessed repair escape hatch — the daemon park loop converges on whatever this says. Unset ⇒ derived from the dev wrapper\'s presence.'
1033
1043
  },
1044
+ {
1045
+ key: "setup.waived",
1046
+ type: "array",
1047
+ scopes: ["machine"],
1048
+ default: [],
1049
+ merge: "replace",
1050
+ description: "Finish-gated setup rows the user skipped on this Mac through `rt setup waive` (today only tool.fast-browser-extension); the wizard's Finish no longer waits on them. Machine-only: a loaded Chrome extension is a per-profile fact and the choice is per machine, so it never travels with a team or user store."
1051
+ },
1034
1052
  {
1035
1053
  key: "rt.integrations",
1036
1054
  type: "object",
@@ -2253,6 +2271,8 @@ export {
2253
2271
  resolveNameToIdentity,
2254
2272
  resolveForgeToken,
2255
2273
  repoNameForPath,
2274
+ reconcilerStatus,
2275
+ reconcilerClear,
2256
2276
  readStore,
2257
2277
  readProjectMRs,
2258
2278
  readMrsByBranch,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mattstack/rt-client",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
package/src/client.ts CHANGED
@@ -356,7 +356,7 @@ export function agentStart(
356
356
  a: Commands["agent:start"]["payload"], o: RtClientOptions = {},
357
357
  ): Promise<RtResponse<AgentRecord>> {
358
358
  const payload: Record<string, unknown> = { repo: a.repo, cwd: a.cwd };
359
- for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg"] as const) {
359
+ for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg", "subject"] as const) {
360
360
  if (a[k] !== undefined) payload[k] = a[k];
361
361
  }
362
362
  return rtCommand<AgentRecord>("agent:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
@@ -432,6 +432,7 @@ export function paneSend(
432
432
  ): Promise<RtResponse<Commands["pane:send"]["data"]>> {
433
433
  const payload: Record<string, unknown> = { paneId: a.paneId, text: a.text };
434
434
  if (a.callerPane !== undefined) payload.callerPane = a.callerPane;
435
+ if (a.continuation !== undefined) payload.continuation = a.continuation;
435
436
  return rtCommand<Commands["pane:send"]["data"]>("pane:send", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
436
437
  }
437
438
 
@@ -446,6 +447,20 @@ export function paneFocus(
446
447
  return rtCommand<Commands["pane:focus"]["data"]>("pane:focus", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
447
448
  }
448
449
 
450
+ // ─── Reconciler (executor state; lib/daemon/reconciler.ts) ────────────────
451
+
452
+ /** Never triggers a sweep; a plain read of the last one's snapshot. */
453
+ export function reconcilerStatus(o: RtClientOptions = {}): Promise<RtResponse<Commands["reconciler:status"]["data"]>> {
454
+ return rtCommand<Commands["reconciler:status"]["data"]>("reconciler:status", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
455
+ }
456
+
457
+ export function reconcilerClear(
458
+ a: Commands["reconciler:clear"]["payload"],
459
+ o: RtClientOptions = {},
460
+ ): Promise<RtResponse<Commands["reconciler:clear"]["data"]>> {
461
+ return rtCommand<Commands["reconciler:clear"]["data"]>("reconciler:clear", { agentId: a.agentId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
462
+ }
463
+
449
464
  // ─── Gates (BOARD-20/21 gate facility) ─────────────────────────────────────
450
465
 
451
466
  export function gateOpen(
package/src/commands.ts CHANGED
@@ -95,6 +95,8 @@ export interface EventsBusEvent { id: number; topic: string; payload: unknown; e
95
95
  export const GATE_BY_PANE = "pane";
96
96
 
97
97
  export type GateStatus = "open" | "answered" | "parked" | "closed";
98
+ /** Reconciler's view of an agent's liveness; also the value `GateRow.executor` is stamped with. */
99
+ export type ExecutorState = "live" | "blocked" | "hidden" | "gone" | "cleared" | "unknown";
98
100
  export type GateOption = string | { value: string; label: string };
99
101
  export interface GateOrigin {
100
102
  paneId?: string;
@@ -118,15 +120,23 @@ export interface GateRow {
118
120
  origin?: GateOrigin | null;
119
121
  status: GateStatus; answer: GateAnswer | null;
120
122
  openedAt: number; parkedAt: number | null; closedAt: number | null;
121
- closedReason: "abandoned" | "superseded" | "pruned" | null;
123
+ /** "resolved" is stamped only by the reconciler's auto-recovery close
124
+ (a blocked/gone pane came back); every other closer uses one of the
125
+ other three. */
126
+ closedReason: "abandoned" | "superseded" | "pruned" | "resolved" | null;
122
127
  /** Set only when `closedReason` is "superseded": the id of the gate that superseded this one. */
123
128
  supersededBy: string | null;
124
129
  agent: string | null; pane: string | null;
125
130
  nudge: { session: string } | null;
126
- delivery: { outcome: "delivered" | "dead-pane"; at: number } | null;
131
+ delivery: { outcome: "delivered" | "dead-pane" | "confirmed" | "stuck"; at: number } | null;
127
132
  released: boolean;
128
133
  owner: string | null;
129
134
  escalatedAt: number | null;
135
+ /** Set by answer-time execution handling: an answered gate whose executor
136
+ couldn't be resumed. Cleared (absent) once resumption succeeds. */
137
+ execution?: "unassigned";
138
+ /** Stamped by the reconciler sweep for open/parked rows only. */
139
+ executor?: ExecutorState;
130
140
  }
131
141
 
132
142
  export interface GateSubscription {
@@ -147,7 +157,7 @@ export interface HerdJobInfo { herd: string; name: string; worktree: string; bra
147
157
  /** `lastGateStatus`/`lastGateDelivery` come from the job's `lastGate` row: an `answered` gate whose delivery is `dead-pane` is the "answered, worker not woken" case the shepherd must act on. */
148
158
  export interface HerdStatusData {
149
159
  herd: HerdInfo;
150
- jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | null; lastGateStatus: GateStatus | null; lastGateDelivery: "delivered" | "dead-pane" | null }>;
160
+ jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | null; lastGateStatus: GateStatus | null; lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null }>;
151
161
  unread: number;
152
162
  lifecycleConnected: boolean;
153
163
  hiddenUp: boolean | null;
@@ -251,7 +261,8 @@ export interface InviteResult { paneId: string; delivered: "accepted" | "queued"
251
261
 
252
262
  /** Duplicated shape on purpose: mirrors lib/daemon/inject.ts's InjectResult. */
253
263
  export type PaneDelivery = "accepted" | "queued" | "refused";
254
- export interface PaneSendResult { paneId: string; delivered: PaneDelivery; reason?: string }
264
+ /** `continuation` is present only when the daemon scheduled a second line for after the target's current turn. */
265
+ export interface PaneSendResult { paneId: string; delivered: PaneDelivery; reason?: string; continuation?: { delivered: "deferred" } }
255
266
  /** `attendTab` is set only for a `bg:` ref: focus for a background pane IS
256
267
  the attend flow (a visible tab running a terminal attach), and this is
257
268
  that tab's id. */
@@ -305,11 +316,36 @@ export interface RunDetail { run: RunSummary; stages: RunStageRow[]; fields: Run
305
316
 
306
317
  export type AgentSurface = "herdr" | "headless";
307
318
 
319
+ /** The reconciler's per-agent snapshot: one row per agent it knows about, live or not. */
320
+ export interface ExecutorView {
321
+ agentId: string;
322
+ repo: string | null;
323
+ subject: string | null;
324
+ surface: AgentSurface;
325
+ sessionId: string;
326
+ paneRef: string | null;
327
+ state: ExecutorState;
328
+ since: number;
329
+ openGateIds: string[];
330
+ }
331
+
332
+ export interface ReconcilerStatus {
333
+ sweptAt: number;
334
+ herdrReachable: boolean;
335
+ executors: ExecutorView[];
336
+ }
337
+
308
338
  export interface AgentRecord {
309
339
  id: string; repo: string; cwd: string; provider: string;
310
340
  surface: AgentSurface; sessionId: string;
311
341
  model?: string; effort?: string; account?: string;
312
342
  label?: string; caller?: string; handle?: string;
343
+ /** The gate-protocol subject stamped as RT_GATE_SUBJECT at launch. Left
344
+ undefined when the caller passed none (RT_GATE_SUBJECT still falls
345
+ back to "agent:<id>" at launch time); an explicit value is persisted
346
+ so a resume re-stamps the same one, AND gates whether the gate-fork
347
+ PreToolUse hook gets injected at all. */
348
+ subject?: string;
313
349
  paneId?: string; tabId?: string; workspaceId?: string;
314
350
  extraArgs?: string; exitCode?: number; resultPath?: string;
315
351
  createdAt: number; lastResumedAt?: number; finishedAt?: number;
@@ -568,7 +604,7 @@ export interface Commands {
568
604
  "chat:dm-open": { payload: { from: string; to: string; sessionId?: string }; data: { room: string; created: boolean } };
569
605
 
570
606
  // ─── Agent handoff (rt agent) ────────────────────────────────────────────
571
- "agent:start": { payload: { repo: string; cwd: string; prompt?: string; surface?: AgentSurface; model?: string; effort?: string; account?: string; label?: string; caller?: string; workspace?: string; tab?: string; extraArgs?: string; env?: Record<string, string>; herdrSocket?: string; handle?: string; bg?: boolean }; data: AgentRecord };
607
+ "agent:start": { payload: { repo: string; cwd: string; prompt?: string; surface?: AgentSurface; model?: string; effort?: string; account?: string; label?: string; caller?: string; workspace?: string; tab?: string; extraArgs?: string; env?: Record<string, string>; herdrSocket?: string; handle?: string; bg?: boolean; subject?: string }; data: AgentRecord };
572
608
  "agent:resume": { payload: { id: string; prompt?: string; surface?: AgentSurface; workspace?: string; tab?: string }; data: AgentRecord };
573
609
  "agent:get": { payload: { id: string }; data: AgentRecord };
574
610
  "agent:list": { payload: { repo?: string }; data: { agents: AgentRecord[] } };
@@ -581,7 +617,7 @@ export interface Commands {
581
617
  payload: { cwd: string; account?: string; model?: string; effort?: string; prompt?: string; workspace?: string };
582
618
  data: { pane: ChatPane; ready: boolean };
583
619
  };
584
- "pane:send": { payload: { paneId: string; text: string; callerPane?: string }; data: PaneSendResult };
620
+ "pane:send": { payload: { paneId: string; text: string; callerPane?: string; continuation?: string }; data: PaneSendResult };
585
621
  /** `callerWorkspace` (HERDR_WORKSPACE_ID) is required only for a `bg:`
586
622
  ref, whose focus opens an attend tab in the caller's own workspace. */
587
623
  "pane:focus": { payload: { paneId: string; callerWorkspace?: string }; data: PaneFocusResult };
@@ -619,6 +655,12 @@ export interface Commands {
619
655
  "repos:locate": { payload: { newPath: string; repo?: string; dryRun?: boolean }; data: unknown };
620
656
  "freshness:reconcile": { payload: Record<string, never>; data: unknown };
621
657
 
658
+ // ─── Reconciler (executor state; lib/daemon/reconciler.ts) ───────────────
659
+ /** Read-only: the last sweep's snapshot. Never triggers a sweep itself. */
660
+ "reconciler:status": { payload: Record<string, never>; data: ReconcilerStatus };
661
+ /** Manual override: marks the agent cleared and closes its open/parked gates (reconciler.ts's clear()). */
662
+ "reconciler:clear": { payload: { agentId: string }; data: { cleared: true } };
663
+
622
664
  // ─── Gate facility (BOARD-20/21) ─────────────────────────────────────────
623
665
  "gate:open": { payload: { subject: string; kind: string; questions: GateQuestion[]; meta?: Record<string, unknown>; agent?: string; pane?: string; nudge?: { session: string }; context?: string; origin?: GateOrigin }; data: { id: string; supersededId: string | null } };
624
666
  /**
@@ -769,6 +811,8 @@ export const COMMAND_NAMES: readonly CommandName[] = [
769
811
  "endpoint:status",
770
812
  "repos:locate",
771
813
  "freshness:reconcile",
814
+ "reconciler:status",
815
+ "reconciler:clear",
772
816
  "gate:open",
773
817
  "gate:answer",
774
818
  "gate:wait",
package/src/index.ts CHANGED
@@ -45,6 +45,8 @@ export {
45
45
  chatInvite,
46
46
  paneSend,
47
47
  paneFocus,
48
+ reconcilerStatus,
49
+ reconcilerClear,
48
50
  gateOpen,
49
51
  gateAnswer,
50
52
  gateWait,
@@ -104,6 +106,9 @@ export type {
104
106
  AgentRecord,
105
107
  AgentSurface,
106
108
  AgentStatus,
109
+ ExecutorState,
110
+ ExecutorView,
111
+ ReconcilerStatus,
107
112
  ChatPane,
108
113
  PaneAccount,
109
114
  PaneDirectory,
@@ -295,6 +295,15 @@ export const REGISTRY: readonly SettingDef[] = [
295
295
  merge: "replace",
296
296
  description: "The machine's intended flavor, \"dev\" or \"prod\". Normally written by `rt settings dev-mode` after a successful handoff; a manual `rt settings set` is the blessed repair escape hatch — the daemon park loop converges on whatever this says. Unset ⇒ derived from the dev wrapper's presence.",
297
297
  },
298
+ {
299
+ key: "setup.waived",
300
+ type: "array",
301
+ scopes: ["machine"],
302
+ default: [],
303
+ merge: "replace",
304
+ description:
305
+ "Finish-gated setup rows the user skipped on this Mac through `rt setup waive` (today only tool.fast-browser-extension); the wizard's Finish no longer waits on them. Machine-only: a loaded Chrome extension is a per-profile fact and the choice is per machine, so it never travels with a team or user store.",
306
+ },
298
307
  {
299
308
  key: "rt.integrations",
300
309
  type: "object",
package/src/transport.ts CHANGED
@@ -38,8 +38,13 @@ export interface RtClientOptions {
38
38
  // lib/, so this literal cannot import rtDir(). repo-tools/lib/rt-paths.ts is
39
39
  // the authority — change there first, mirror here (same convention as
40
40
  // settings/paths.ts's call-time `home()`).
41
+ //
42
+ // RT_DAEMON_SOCK wins over the HOME-derived default (same pattern as
43
+ // lib/daemon-client.ts's RT_APP_SOCKET for tray.sock): an isolated daemon
44
+ // (per-agent launch, an e2e run) points every rt-client caller, including
45
+ // the `rt` CLI's own subcommands, at its own socket without repointing HOME.
41
46
  function defaultSock(): string {
42
- return join(process.env.HOME ?? homedir(), ".mattstack", "rt", "rt.sock");
47
+ return process.env.RT_DAEMON_SOCK || join(process.env.HOME ?? homedir(), ".mattstack", "rt", "rt.sock");
43
48
  }
44
49
 
45
50
  /**