@mattstack/rt-client 0.19.1 → 0.21.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 +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/commands.d.ts +57 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +23 -3
- package/package.json +1 -1
- package/src/client.ts +16 -1
- package/src/commands.ts +51 -6
- package/src/index.ts +5 -0
- package/src/settings/registry-defs.ts +10 -1
- package/src/transport.ts +6 -1
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
|
package/dist/commands.d.ts
CHANGED
|
@@ -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;
|
|
@@ -111,6 +113,7 @@ export interface GateOrigin {
|
|
|
111
113
|
tabId?: string;
|
|
112
114
|
runId?: string;
|
|
113
115
|
worktree?: string;
|
|
116
|
+
surface?: string;
|
|
114
117
|
presentation?: "form" | "wait";
|
|
115
118
|
}
|
|
116
119
|
export interface GateQuestion {
|
|
@@ -143,7 +146,10 @@ export interface GateRow {
|
|
|
143
146
|
openedAt: number;
|
|
144
147
|
parkedAt: number | null;
|
|
145
148
|
closedAt: number | null;
|
|
146
|
-
|
|
149
|
+
/** "resolved" is stamped only by the reconciler's auto-recovery close
|
|
150
|
+
(a blocked/gone pane came back); every other closer uses one of the
|
|
151
|
+
other three. */
|
|
152
|
+
closedReason: "abandoned" | "superseded" | "pruned" | "resolved" | null;
|
|
147
153
|
/** Set only when `closedReason` is "superseded": the id of the gate that superseded this one. */
|
|
148
154
|
supersededBy: string | null;
|
|
149
155
|
agent: string | null;
|
|
@@ -152,12 +158,17 @@ export interface GateRow {
|
|
|
152
158
|
session: string;
|
|
153
159
|
} | null;
|
|
154
160
|
delivery: {
|
|
155
|
-
outcome: "delivered" | "dead-pane";
|
|
161
|
+
outcome: "delivered" | "dead-pane" | "confirmed" | "stuck";
|
|
156
162
|
at: number;
|
|
157
163
|
} | null;
|
|
158
164
|
released: boolean;
|
|
159
165
|
owner: string | null;
|
|
160
166
|
escalatedAt: number | null;
|
|
167
|
+
/** Set by answer-time execution handling: an answered gate whose executor
|
|
168
|
+
couldn't be resumed. Cleared (absent) once resumption succeeds. */
|
|
169
|
+
execution?: "unassigned";
|
|
170
|
+
/** Stamped by the reconciler sweep for open/parked rows only. */
|
|
171
|
+
executor?: ExecutorState;
|
|
161
172
|
}
|
|
162
173
|
export interface GateSubscription {
|
|
163
174
|
id: string;
|
|
@@ -213,7 +224,7 @@ export interface HerdStatusData {
|
|
|
213
224
|
openGate: string | null;
|
|
214
225
|
paneStatus: string | null;
|
|
215
226
|
lastGateStatus: GateStatus | null;
|
|
216
|
-
lastGateDelivery: "delivered" | "dead-pane" | null;
|
|
227
|
+
lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null;
|
|
217
228
|
}>;
|
|
218
229
|
unread: number;
|
|
219
230
|
lifecycleConnected: boolean;
|
|
@@ -345,10 +356,14 @@ export interface InviteResult {
|
|
|
345
356
|
}
|
|
346
357
|
/** Duplicated shape on purpose: mirrors lib/daemon/inject.ts's InjectResult. */
|
|
347
358
|
export type PaneDelivery = "accepted" | "queued" | "refused";
|
|
359
|
+
/** `continuation` is present only when the daemon scheduled a second line for after the target's current turn. */
|
|
348
360
|
export interface PaneSendResult {
|
|
349
361
|
paneId: string;
|
|
350
362
|
delivered: PaneDelivery;
|
|
351
363
|
reason?: string;
|
|
364
|
+
continuation?: {
|
|
365
|
+
delivered: "deferred";
|
|
366
|
+
};
|
|
352
367
|
}
|
|
353
368
|
/** `attendTab` is set only for a `bg:` ref: focus for a background pane IS
|
|
354
369
|
the attend flow (a visible tab running a terminal attach), and this is
|
|
@@ -431,6 +446,23 @@ export interface RunDetail {
|
|
|
431
446
|
schemaAhead: boolean;
|
|
432
447
|
}
|
|
433
448
|
export type AgentSurface = "herdr" | "headless";
|
|
449
|
+
/** The reconciler's per-agent snapshot: one row per agent it knows about, live or not. */
|
|
450
|
+
export interface ExecutorView {
|
|
451
|
+
agentId: string;
|
|
452
|
+
repo: string | null;
|
|
453
|
+
subject: string | null;
|
|
454
|
+
surface: AgentSurface;
|
|
455
|
+
sessionId: string;
|
|
456
|
+
paneRef: string | null;
|
|
457
|
+
state: ExecutorState;
|
|
458
|
+
since: number;
|
|
459
|
+
openGateIds: string[];
|
|
460
|
+
}
|
|
461
|
+
export interface ReconcilerStatus {
|
|
462
|
+
sweptAt: number;
|
|
463
|
+
herdrReachable: boolean;
|
|
464
|
+
executors: ExecutorView[];
|
|
465
|
+
}
|
|
434
466
|
export interface AgentRecord {
|
|
435
467
|
id: string;
|
|
436
468
|
repo: string;
|
|
@@ -444,6 +476,12 @@ export interface AgentRecord {
|
|
|
444
476
|
label?: string;
|
|
445
477
|
caller?: string;
|
|
446
478
|
handle?: string;
|
|
479
|
+
/** The gate-protocol subject stamped as RT_GATE_SUBJECT at launch. Left
|
|
480
|
+
undefined when the caller passed none (RT_GATE_SUBJECT still falls
|
|
481
|
+
back to "agent:<id>" at launch time); an explicit value is persisted
|
|
482
|
+
so a resume re-stamps the same one, AND gates whether the gate-fork
|
|
483
|
+
PreToolUse hook gets injected at all. */
|
|
484
|
+
subject?: string;
|
|
447
485
|
paneId?: string;
|
|
448
486
|
tabId?: string;
|
|
449
487
|
workspaceId?: string;
|
|
@@ -1096,6 +1134,7 @@ export interface Commands {
|
|
|
1096
1134
|
herdrSocket?: string;
|
|
1097
1135
|
handle?: string;
|
|
1098
1136
|
bg?: boolean;
|
|
1137
|
+
subject?: string;
|
|
1099
1138
|
};
|
|
1100
1139
|
data: AgentRecord;
|
|
1101
1140
|
};
|
|
@@ -1182,6 +1221,7 @@ export interface Commands {
|
|
|
1182
1221
|
paneId: string;
|
|
1183
1222
|
text: string;
|
|
1184
1223
|
callerPane?: string;
|
|
1224
|
+
continuation?: string;
|
|
1185
1225
|
};
|
|
1186
1226
|
data: PaneSendResult;
|
|
1187
1227
|
};
|
|
@@ -1360,6 +1400,20 @@ export interface Commands {
|
|
|
1360
1400
|
payload: Record<string, never>;
|
|
1361
1401
|
data: unknown;
|
|
1362
1402
|
};
|
|
1403
|
+
/** Read-only: the last sweep's snapshot. Never triggers a sweep itself. */
|
|
1404
|
+
"reconciler:status": {
|
|
1405
|
+
payload: Record<string, never>;
|
|
1406
|
+
data: ReconcilerStatus;
|
|
1407
|
+
};
|
|
1408
|
+
/** Manual override: marks the agent cleared and closes its open/parked gates (reconciler.ts's clear()). */
|
|
1409
|
+
"reconciler:clear": {
|
|
1410
|
+
payload: {
|
|
1411
|
+
agentId: string;
|
|
1412
|
+
};
|
|
1413
|
+
data: {
|
|
1414
|
+
cleared: true;
|
|
1415
|
+
};
|
|
1416
|
+
};
|
|
1363
1417
|
"gate:open": {
|
|
1364
1418
|
payload: {
|
|
1365
1419
|
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",
|
|
@@ -830,7 +840,7 @@ var REGISTRY = [
|
|
|
830
840
|
scopes: ["user"],
|
|
831
841
|
default: [],
|
|
832
842
|
merge: "replace",
|
|
833
|
-
description: 'Event-bus glob rules that raise a desktop notification: [{pattern, category, title, message, subjectPrefix?, url?, owner?}]. pattern is matched against the events-bus topic (Bun.Glob semantics); title/message may interpolate `{field}` from the event payload, plus the computed `{question}` field (the event payload\'s first question label, `payload.questions[0].label`, empty string when absent); optional subjectPrefix matches the event payload\'s subject as a prefix. The optional url is interpolated the same way as title/message and becomes the notification\'s Open target; a gate rule should set it. The optional owner field (only literal "human" is valid) suppresses events whose payload.owner starts with "herd:", allowing gate rules to skip herd-owned events. A typical setup pairs a gate/opened rule with owner: "human" (human-owned gates notify) and a gate/escalated rule without owner (all escalations notify, whether human or herd). A fresh key, not an ownership-latch port, so a default is fine here.'
|
|
843
|
+
description: 'Event-bus glob rules that raise a desktop notification: [{pattern, category, title, message, subjectPrefix?, url?, owner?, surface?}]. pattern is matched against the events-bus topic (Bun.Glob semantics); title/message may interpolate `{field}` from the event payload, plus the computed `{question}` field (the event payload\'s first question label, `payload.questions[0].label`, empty string when absent); optional subjectPrefix matches the event payload\'s subject as a prefix. The optional url is interpolated the same way as title/message and becomes the notification\'s Open target; a gate rule should set it. The optional owner field (only literal "human" is valid) suppresses events whose payload.owner starts with "herd:", allowing gate rules to skip herd-owned events. The optional surface field (a string) matches only events whose payload.origin.surface equals the given value, allowing a rule to fire only for gates opened by a specific surface (e.g. surface: "board" matches only board-originated gates). A typical setup pairs a gate/opened rule with owner: "human" (human-owned gates notify) and a gate/escalated rule without owner (all escalations notify, whether human or herd). A board-specific rule can combine surface: "board" with a board URL to open the decision queue directly. A fresh key, not an ownership-latch port, so a default is fine here.'
|
|
834
844
|
},
|
|
835
845
|
{
|
|
836
846
|
key: "rt.cron",
|
|
@@ -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
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,12 +95,15 @@ 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;
|
|
101
103
|
tabId?: string;
|
|
102
104
|
runId?: string;
|
|
103
105
|
worktree?: string;
|
|
106
|
+
surface?: string;
|
|
104
107
|
presentation?: "form" | "wait";
|
|
105
108
|
}
|
|
106
109
|
export interface GateQuestion { id: string; label: string; multi: boolean; options: GateOption[] }
|
|
@@ -118,15 +121,23 @@ export interface GateRow {
|
|
|
118
121
|
origin?: GateOrigin | null;
|
|
119
122
|
status: GateStatus; answer: GateAnswer | null;
|
|
120
123
|
openedAt: number; parkedAt: number | null; closedAt: number | null;
|
|
121
|
-
|
|
124
|
+
/** "resolved" is stamped only by the reconciler's auto-recovery close
|
|
125
|
+
(a blocked/gone pane came back); every other closer uses one of the
|
|
126
|
+
other three. */
|
|
127
|
+
closedReason: "abandoned" | "superseded" | "pruned" | "resolved" | null;
|
|
122
128
|
/** Set only when `closedReason` is "superseded": the id of the gate that superseded this one. */
|
|
123
129
|
supersededBy: string | null;
|
|
124
130
|
agent: string | null; pane: string | null;
|
|
125
131
|
nudge: { session: string } | null;
|
|
126
|
-
delivery: { outcome: "delivered" | "dead-pane"; at: number } | null;
|
|
132
|
+
delivery: { outcome: "delivered" | "dead-pane" | "confirmed" | "stuck"; at: number } | null;
|
|
127
133
|
released: boolean;
|
|
128
134
|
owner: string | null;
|
|
129
135
|
escalatedAt: number | null;
|
|
136
|
+
/** Set by answer-time execution handling: an answered gate whose executor
|
|
137
|
+
couldn't be resumed. Cleared (absent) once resumption succeeds. */
|
|
138
|
+
execution?: "unassigned";
|
|
139
|
+
/** Stamped by the reconciler sweep for open/parked rows only. */
|
|
140
|
+
executor?: ExecutorState;
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
export interface GateSubscription {
|
|
@@ -147,7 +158,7 @@ export interface HerdJobInfo { herd: string; name: string; worktree: string; bra
|
|
|
147
158
|
/** `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
159
|
export interface HerdStatusData {
|
|
149
160
|
herd: HerdInfo;
|
|
150
|
-
jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | null; lastGateStatus: GateStatus | null; lastGateDelivery: "delivered" | "dead-pane" | null }>;
|
|
161
|
+
jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | null; lastGateStatus: GateStatus | null; lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null }>;
|
|
151
162
|
unread: number;
|
|
152
163
|
lifecycleConnected: boolean;
|
|
153
164
|
hiddenUp: boolean | null;
|
|
@@ -251,7 +262,8 @@ export interface InviteResult { paneId: string; delivered: "accepted" | "queued"
|
|
|
251
262
|
|
|
252
263
|
/** Duplicated shape on purpose: mirrors lib/daemon/inject.ts's InjectResult. */
|
|
253
264
|
export type PaneDelivery = "accepted" | "queued" | "refused";
|
|
254
|
-
|
|
265
|
+
/** `continuation` is present only when the daemon scheduled a second line for after the target's current turn. */
|
|
266
|
+
export interface PaneSendResult { paneId: string; delivered: PaneDelivery; reason?: string; continuation?: { delivered: "deferred" } }
|
|
255
267
|
/** `attendTab` is set only for a `bg:` ref: focus for a background pane IS
|
|
256
268
|
the attend flow (a visible tab running a terminal attach), and this is
|
|
257
269
|
that tab's id. */
|
|
@@ -305,11 +317,36 @@ export interface RunDetail { run: RunSummary; stages: RunStageRow[]; fields: Run
|
|
|
305
317
|
|
|
306
318
|
export type AgentSurface = "herdr" | "headless";
|
|
307
319
|
|
|
320
|
+
/** The reconciler's per-agent snapshot: one row per agent it knows about, live or not. */
|
|
321
|
+
export interface ExecutorView {
|
|
322
|
+
agentId: string;
|
|
323
|
+
repo: string | null;
|
|
324
|
+
subject: string | null;
|
|
325
|
+
surface: AgentSurface;
|
|
326
|
+
sessionId: string;
|
|
327
|
+
paneRef: string | null;
|
|
328
|
+
state: ExecutorState;
|
|
329
|
+
since: number;
|
|
330
|
+
openGateIds: string[];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
export interface ReconcilerStatus {
|
|
334
|
+
sweptAt: number;
|
|
335
|
+
herdrReachable: boolean;
|
|
336
|
+
executors: ExecutorView[];
|
|
337
|
+
}
|
|
338
|
+
|
|
308
339
|
export interface AgentRecord {
|
|
309
340
|
id: string; repo: string; cwd: string; provider: string;
|
|
310
341
|
surface: AgentSurface; sessionId: string;
|
|
311
342
|
model?: string; effort?: string; account?: string;
|
|
312
343
|
label?: string; caller?: string; handle?: string;
|
|
344
|
+
/** The gate-protocol subject stamped as RT_GATE_SUBJECT at launch. Left
|
|
345
|
+
undefined when the caller passed none (RT_GATE_SUBJECT still falls
|
|
346
|
+
back to "agent:<id>" at launch time); an explicit value is persisted
|
|
347
|
+
so a resume re-stamps the same one, AND gates whether the gate-fork
|
|
348
|
+
PreToolUse hook gets injected at all. */
|
|
349
|
+
subject?: string;
|
|
313
350
|
paneId?: string; tabId?: string; workspaceId?: string;
|
|
314
351
|
extraArgs?: string; exitCode?: number; resultPath?: string;
|
|
315
352
|
createdAt: number; lastResumedAt?: number; finishedAt?: number;
|
|
@@ -568,7 +605,7 @@ export interface Commands {
|
|
|
568
605
|
"chat:dm-open": { payload: { from: string; to: string; sessionId?: string }; data: { room: string; created: boolean } };
|
|
569
606
|
|
|
570
607
|
// ─── 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 };
|
|
608
|
+
"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
609
|
"agent:resume": { payload: { id: string; prompt?: string; surface?: AgentSurface; workspace?: string; tab?: string }; data: AgentRecord };
|
|
573
610
|
"agent:get": { payload: { id: string }; data: AgentRecord };
|
|
574
611
|
"agent:list": { payload: { repo?: string }; data: { agents: AgentRecord[] } };
|
|
@@ -581,7 +618,7 @@ export interface Commands {
|
|
|
581
618
|
payload: { cwd: string; account?: string; model?: string; effort?: string; prompt?: string; workspace?: string };
|
|
582
619
|
data: { pane: ChatPane; ready: boolean };
|
|
583
620
|
};
|
|
584
|
-
"pane:send": { payload: { paneId: string; text: string; callerPane?: string }; data: PaneSendResult };
|
|
621
|
+
"pane:send": { payload: { paneId: string; text: string; callerPane?: string; continuation?: string }; data: PaneSendResult };
|
|
585
622
|
/** `callerWorkspace` (HERDR_WORKSPACE_ID) is required only for a `bg:`
|
|
586
623
|
ref, whose focus opens an attend tab in the caller's own workspace. */
|
|
587
624
|
"pane:focus": { payload: { paneId: string; callerWorkspace?: string }; data: PaneFocusResult };
|
|
@@ -619,6 +656,12 @@ export interface Commands {
|
|
|
619
656
|
"repos:locate": { payload: { newPath: string; repo?: string; dryRun?: boolean }; data: unknown };
|
|
620
657
|
"freshness:reconcile": { payload: Record<string, never>; data: unknown };
|
|
621
658
|
|
|
659
|
+
// ─── Reconciler (executor state; lib/daemon/reconciler.ts) ───────────────
|
|
660
|
+
/** Read-only: the last sweep's snapshot. Never triggers a sweep itself. */
|
|
661
|
+
"reconciler:status": { payload: Record<string, never>; data: ReconcilerStatus };
|
|
662
|
+
/** Manual override: marks the agent cleared and closes its open/parked gates (reconciler.ts's clear()). */
|
|
663
|
+
"reconciler:clear": { payload: { agentId: string }; data: { cleared: true } };
|
|
664
|
+
|
|
622
665
|
// ─── Gate facility (BOARD-20/21) ─────────────────────────────────────────
|
|
623
666
|
"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
667
|
/**
|
|
@@ -769,6 +812,8 @@ export const COMMAND_NAMES: readonly CommandName[] = [
|
|
|
769
812
|
"endpoint:status",
|
|
770
813
|
"repos:locate",
|
|
771
814
|
"freshness:reconcile",
|
|
815
|
+
"reconciler:status",
|
|
816
|
+
"reconciler:clear",
|
|
772
817
|
"gate:open",
|
|
773
818
|
"gate:answer",
|
|
774
819
|
"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,
|
|
@@ -85,7 +85,7 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
85
85
|
scopes: ["user"],
|
|
86
86
|
default: [],
|
|
87
87
|
merge: "replace",
|
|
88
|
-
description: "Event-bus glob rules that raise a desktop notification: [{pattern, category, title, message, subjectPrefix?, url?, owner?}]. pattern is matched against the events-bus topic (Bun.Glob semantics); title/message may interpolate `{field}` from the event payload, plus the computed `{question}` field (the event payload's first question label, `payload.questions[0].label`, empty string when absent); optional subjectPrefix matches the event payload's subject as a prefix. The optional url is interpolated the same way as title/message and becomes the notification's Open target; a gate rule should set it. The optional owner field (only literal \"human\" is valid) suppresses events whose payload.owner starts with \"herd:\", allowing gate rules to skip herd-owned events. A typical setup pairs a gate/opened rule with owner: \"human\" (human-owned gates notify) and a gate/escalated rule without owner (all escalations notify, whether human or herd). A fresh key, not an ownership-latch port, so a default is fine here.",
|
|
88
|
+
description: "Event-bus glob rules that raise a desktop notification: [{pattern, category, title, message, subjectPrefix?, url?, owner?, surface?}]. pattern is matched against the events-bus topic (Bun.Glob semantics); title/message may interpolate `{field}` from the event payload, plus the computed `{question}` field (the event payload's first question label, `payload.questions[0].label`, empty string when absent); optional subjectPrefix matches the event payload's subject as a prefix. The optional url is interpolated the same way as title/message and becomes the notification's Open target; a gate rule should set it. The optional owner field (only literal \"human\" is valid) suppresses events whose payload.owner starts with \"herd:\", allowing gate rules to skip herd-owned events. The optional surface field (a string) matches only events whose payload.origin.surface equals the given value, allowing a rule to fire only for gates opened by a specific surface (e.g. surface: \"board\" matches only board-originated gates). A typical setup pairs a gate/opened rule with owner: \"human\" (human-owned gates notify) and a gate/escalated rule without owner (all escalations notify, whether human or herd). A board-specific rule can combine surface: \"board\" with a board URL to open the decision queue directly. A fresh key, not an ownership-latch port, so a default is fine here.",
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
key: "rt.cron",
|
|
@@ -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
|
/**
|