@mattstack/rt-client 0.16.0 → 0.19.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/dist/client.d.ts +30 -0
- package/dist/commands.d.ts +277 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +183 -13
- package/dist/pane-ref.d.ts +8 -0
- package/dist/settings/paths.d.ts +8 -0
- package/dist/settings/team-local-read.d.ts +7 -0
- package/package.json +1 -1
- package/src/client.ts +154 -2
- package/src/commands.ts +68 -3
- package/src/index.ts +25 -0
- package/src/pane-ref.ts +28 -0
- package/src/settings/paths.ts +11 -0
- package/src/settings/registry-defs.ts +1 -1
- package/src/settings/team-local-read.ts +18 -0
- package/src/settings/write.ts +24 -3
package/dist/client.d.ts
CHANGED
|
@@ -195,3 +195,33 @@ export declare function gateClose(a: Commands["gate:close"]["payload"], o?: RtCl
|
|
|
195
195
|
export declare function gateSubscribe(a: Commands["gate:subscribe"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:subscribe"]["data"]>>;
|
|
196
196
|
export declare function gateUnsubscribe(a: Commands["gate:unsubscribe"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:unsubscribe"]["data"]>>;
|
|
197
197
|
export declare function gateSubscriptions(a: Commands["gate:subscriptions"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:subscriptions"]["data"]>>;
|
|
198
|
+
/** Provisions the room, workspace and subscription before it answers, so it
|
|
199
|
+
gets the same 60s budget as a spawn rather than the 10s default. */
|
|
200
|
+
export declare function herdStart(a: Commands["herd:start"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:start"]["data"]>>;
|
|
201
|
+
/** Worktree provision plus agent launch; the 10s default cannot cover it. */
|
|
202
|
+
export declare function herdSpawn(a: Commands["herd:spawn"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:spawn"]["data"]>>;
|
|
203
|
+
export declare function herdAsk(a: Commands["herd:ask"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:ask"]["data"]>>;
|
|
204
|
+
export declare function herdMilestone(a: Commands["herd:milestone"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:milestone"]["data"]>>;
|
|
205
|
+
export declare function herdAnswer(a: Commands["herd:answer"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:answer"]["data"]>>;
|
|
206
|
+
/** A disposable job's report also closes its pane, one herdr CLI call under the runner's own 15s budget. */
|
|
207
|
+
export declare function herdReport(a: Commands["herd:report"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:report"]["data"]>>;
|
|
208
|
+
export declare function herdGates(a: Commands["herd:gates"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:gates"]["data"]>>;
|
|
209
|
+
export declare function herdStatus(a: Commands["herd:status"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:status"]["data"]>>;
|
|
210
|
+
export declare function herdList(a?: Commands["herd:list"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:list"]["data"]>>;
|
|
211
|
+
export declare function herdResume(a: Commands["herd:resume"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:resume"]["data"]>>;
|
|
212
|
+
/** Closes a herdr pane, one CLI call under the runner's own 15s budget. */
|
|
213
|
+
export declare function herdClose(a: Commands["herd:close"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:close"]["data"]>>;
|
|
214
|
+
/** Three sequential herdr CLI calls (pane get, tab create, pane run), each under the runner's own 15s budget. */
|
|
215
|
+
export declare function herdAttend(a: Commands["herd:attend"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:attend"]["data"]>>;
|
|
216
|
+
/** Closes panes, disposes worktrees and archives the room in one pass; the
|
|
217
|
+
worktree disposals alone can outrun a spawn's budget. */
|
|
218
|
+
export declare function herdWrapUp(a: Commands["herd:wrap-up"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:wrap-up"]["data"]>>;
|
|
219
|
+
/** Runs `herdr session stop`, one CLI call under the runner's own 15s budget. */
|
|
220
|
+
export declare function herdStopHidden(_a: Commands["herd:stop-hidden"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:stop-hidden"]["data"]>>;
|
|
221
|
+
/** May spawn `herdr server` and wait for it to bind; budget matches bg-service's own 10s readyTimeoutMs plus margin. */
|
|
222
|
+
export declare function bgEnsure(a?: Commands["bg:ensure"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["bg:ensure"]["data"]>>;
|
|
223
|
+
/** Never ensures/spawns; a plain read of the current state. */
|
|
224
|
+
export declare function bgStatus(o?: RtClientOptions): Promise<RtResponse<Commands["bg:status"]["data"]>>;
|
|
225
|
+
/** Asks the daemon to stop the background server; refuses (ok:false) while any claim is live, naming the owners. */
|
|
226
|
+
export declare function bgStop(o?: RtClientOptions): Promise<RtResponse<Commands["bg:stop"]["data"]>>;
|
|
227
|
+
export declare function bgRelease(a: Commands["bg:release"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["bg:release"]["data"]>>;
|
package/dist/commands.d.ts
CHANGED
|
@@ -165,6 +165,59 @@ export interface GateSubscription {
|
|
|
165
165
|
} | null;
|
|
166
166
|
dead: boolean;
|
|
167
167
|
}
|
|
168
|
+
export interface HerdInfo {
|
|
169
|
+
id: string;
|
|
170
|
+
repo: string;
|
|
171
|
+
room: string;
|
|
172
|
+
workspace: string;
|
|
173
|
+
shepherdSession: string;
|
|
174
|
+
shepherdHandle: string;
|
|
175
|
+
herdrSocket: string | null;
|
|
176
|
+
hidden: boolean;
|
|
177
|
+
status: "active" | "wrapped";
|
|
178
|
+
createdAt: number;
|
|
179
|
+
wrappedAt: number | null;
|
|
180
|
+
}
|
|
181
|
+
/** A herd row as `herd:list` reports it: the registry row plus how many jobs hang off it. */
|
|
182
|
+
export interface HerdListRow extends HerdInfo {
|
|
183
|
+
jobs: number;
|
|
184
|
+
}
|
|
185
|
+
export interface HerdJobInfo {
|
|
186
|
+
herd: string;
|
|
187
|
+
name: string;
|
|
188
|
+
worktree: string;
|
|
189
|
+
branch: string | null;
|
|
190
|
+
tree: string | null;
|
|
191
|
+
pane: string | null;
|
|
192
|
+
agentSession: string | null;
|
|
193
|
+
agentId: string | null;
|
|
194
|
+
handle: string;
|
|
195
|
+
status: "spawning" | "active" | "at-gate" | "at-milestone" | "done" | "closed" | "crashed";
|
|
196
|
+
disposable: boolean;
|
|
197
|
+
lastGate: string | null;
|
|
198
|
+
lastReport: number | null;
|
|
199
|
+
createdAt: number;
|
|
200
|
+
updatedAt: number;
|
|
201
|
+
}
|
|
202
|
+
/** `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. */
|
|
203
|
+
export interface HerdStatusData {
|
|
204
|
+
herd: HerdInfo;
|
|
205
|
+
jobs: Array<HerdJobInfo & {
|
|
206
|
+
openGate: string | null;
|
|
207
|
+
paneStatus: string | null;
|
|
208
|
+
lastGateStatus: GateStatus | null;
|
|
209
|
+
lastGateDelivery: "delivered" | "dead-pane" | null;
|
|
210
|
+
}>;
|
|
211
|
+
unread: number;
|
|
212
|
+
lifecycleConnected: boolean;
|
|
213
|
+
hiddenUp: boolean | null;
|
|
214
|
+
/** The shepherd session's own `herd:<id>/` subscription row, or null when none is live. */
|
|
215
|
+
subscription: {
|
|
216
|
+
id: string;
|
|
217
|
+
dead: boolean;
|
|
218
|
+
lastDelivery: GateSubscription["lastDelivery"];
|
|
219
|
+
} | null;
|
|
220
|
+
}
|
|
168
221
|
/**
|
|
169
222
|
* Duplicated shape on purpose, same reasoning as EventsBusEvent above:
|
|
170
223
|
* these mirror lib/state/chat-store.ts's types, which rt-client cannot
|
|
@@ -285,9 +338,13 @@ export interface PaneSendResult {
|
|
|
285
338
|
delivered: PaneDelivery;
|
|
286
339
|
reason?: string;
|
|
287
340
|
}
|
|
341
|
+
/** `attendTab` is set only for a `bg:` ref: focus for a background pane IS
|
|
342
|
+
the attend flow (a visible tab running a terminal attach), and this is
|
|
343
|
+
that tab's id. */
|
|
288
344
|
export interface PaneFocusResult {
|
|
289
345
|
paneId: string;
|
|
290
346
|
focused: boolean;
|
|
347
|
+
attendTab?: string;
|
|
291
348
|
}
|
|
292
349
|
export type Attention = {
|
|
293
350
|
needs: boolean;
|
|
@@ -1019,6 +1076,10 @@ export interface Commands {
|
|
|
1019
1076
|
workspace?: string;
|
|
1020
1077
|
tab?: string;
|
|
1021
1078
|
extraArgs?: string;
|
|
1079
|
+
env?: Record<string, string>;
|
|
1080
|
+
herdrSocket?: string;
|
|
1081
|
+
handle?: string;
|
|
1082
|
+
bg?: boolean;
|
|
1022
1083
|
};
|
|
1023
1084
|
data: AgentRecord;
|
|
1024
1085
|
};
|
|
@@ -1108,9 +1169,12 @@ export interface Commands {
|
|
|
1108
1169
|
};
|
|
1109
1170
|
data: PaneSendResult;
|
|
1110
1171
|
};
|
|
1172
|
+
/** `callerWorkspace` (HERDR_WORKSPACE_ID) is required only for a `bg:`
|
|
1173
|
+
ref, whose focus opens an attend tab in the caller's own workspace. */
|
|
1111
1174
|
"pane:focus": {
|
|
1112
1175
|
payload: {
|
|
1113
1176
|
paneId: string;
|
|
1177
|
+
callerWorkspace?: string;
|
|
1114
1178
|
};
|
|
1115
1179
|
data: PaneFocusResult;
|
|
1116
1180
|
};
|
|
@@ -1390,6 +1454,179 @@ export interface Commands {
|
|
|
1390
1454
|
subscriptions: GateSubscription[];
|
|
1391
1455
|
};
|
|
1392
1456
|
};
|
|
1457
|
+
"herd:start": {
|
|
1458
|
+
payload: {
|
|
1459
|
+
name: string;
|
|
1460
|
+
repo: string;
|
|
1461
|
+
session: string;
|
|
1462
|
+
hidden?: boolean;
|
|
1463
|
+
};
|
|
1464
|
+
data: {
|
|
1465
|
+
herd: string;
|
|
1466
|
+
room: string;
|
|
1467
|
+
workspace: string;
|
|
1468
|
+
subscription: string;
|
|
1469
|
+
handle: string;
|
|
1470
|
+
hidden: boolean;
|
|
1471
|
+
};
|
|
1472
|
+
};
|
|
1473
|
+
"herd:resume": {
|
|
1474
|
+
payload: {
|
|
1475
|
+
herd: string;
|
|
1476
|
+
session: string;
|
|
1477
|
+
};
|
|
1478
|
+
data: {
|
|
1479
|
+
subscription: string;
|
|
1480
|
+
gates: GateRow[];
|
|
1481
|
+
unread: number;
|
|
1482
|
+
status: HerdStatusData;
|
|
1483
|
+
handle: string;
|
|
1484
|
+
};
|
|
1485
|
+
};
|
|
1486
|
+
"herd:status": {
|
|
1487
|
+
payload: {
|
|
1488
|
+
herd: string;
|
|
1489
|
+
};
|
|
1490
|
+
data: HerdStatusData;
|
|
1491
|
+
};
|
|
1492
|
+
/** Active herds only unless `all`, so a shepherd's "which herd am I on" question has one answer. */
|
|
1493
|
+
"herd:list": {
|
|
1494
|
+
payload: {
|
|
1495
|
+
all?: boolean;
|
|
1496
|
+
};
|
|
1497
|
+
data: {
|
|
1498
|
+
herds: HerdListRow[];
|
|
1499
|
+
};
|
|
1500
|
+
};
|
|
1501
|
+
"herd:close": {
|
|
1502
|
+
payload: {
|
|
1503
|
+
herd: string;
|
|
1504
|
+
job: string;
|
|
1505
|
+
};
|
|
1506
|
+
data: {
|
|
1507
|
+
job: string;
|
|
1508
|
+
status: "closed";
|
|
1509
|
+
};
|
|
1510
|
+
};
|
|
1511
|
+
/** `brief` is the brief TEXT, not a path: the CLI reads the file. It is stored at `<jobsRoot>/<herd>/<job>/job.md`, so a respawn with `dir` and no `brief` reads it back. */
|
|
1512
|
+
"herd:spawn": {
|
|
1513
|
+
payload: {
|
|
1514
|
+
herd: string;
|
|
1515
|
+
job: string;
|
|
1516
|
+
brief?: string;
|
|
1517
|
+
dir?: string;
|
|
1518
|
+
model?: string;
|
|
1519
|
+
effort?: string;
|
|
1520
|
+
account?: string;
|
|
1521
|
+
disposable?: boolean;
|
|
1522
|
+
};
|
|
1523
|
+
data: {
|
|
1524
|
+
herd: string;
|
|
1525
|
+
job: string;
|
|
1526
|
+
pane: string;
|
|
1527
|
+
worktree: string;
|
|
1528
|
+
branch: string | null;
|
|
1529
|
+
tree: string | null; /** null = no provisioning ran (--dir); false = cold create, worth announcing. */
|
|
1530
|
+
wasOnDeck: boolean | null;
|
|
1531
|
+
agentId: string;
|
|
1532
|
+
sessionId: string;
|
|
1533
|
+
handle: string;
|
|
1534
|
+
};
|
|
1535
|
+
};
|
|
1536
|
+
"herd:gates": {
|
|
1537
|
+
payload: {
|
|
1538
|
+
herd: string;
|
|
1539
|
+
};
|
|
1540
|
+
data: {
|
|
1541
|
+
gates: GateRow[];
|
|
1542
|
+
};
|
|
1543
|
+
};
|
|
1544
|
+
"herd:ask": {
|
|
1545
|
+
payload: {
|
|
1546
|
+
herd: string;
|
|
1547
|
+
job: string;
|
|
1548
|
+
session: string;
|
|
1549
|
+
pane?: string;
|
|
1550
|
+
questions: GateQuestion[];
|
|
1551
|
+
context?: string;
|
|
1552
|
+
};
|
|
1553
|
+
data: {
|
|
1554
|
+
gate: string;
|
|
1555
|
+
};
|
|
1556
|
+
};
|
|
1557
|
+
"herd:milestone": {
|
|
1558
|
+
payload: {
|
|
1559
|
+
herd: string;
|
|
1560
|
+
job: string;
|
|
1561
|
+
session: string;
|
|
1562
|
+
pane?: string;
|
|
1563
|
+
artifact: string;
|
|
1564
|
+
summary?: string;
|
|
1565
|
+
};
|
|
1566
|
+
data: {
|
|
1567
|
+
gate: string;
|
|
1568
|
+
message: number;
|
|
1569
|
+
};
|
|
1570
|
+
};
|
|
1571
|
+
"herd:answer": {
|
|
1572
|
+
payload: {
|
|
1573
|
+
gate: string;
|
|
1574
|
+
};
|
|
1575
|
+
data: {
|
|
1576
|
+
gate: string;
|
|
1577
|
+
status: GateStatus;
|
|
1578
|
+
answer: GateAnswer | null;
|
|
1579
|
+
closedReason: GateRow["closedReason"];
|
|
1580
|
+
};
|
|
1581
|
+
};
|
|
1582
|
+
"herd:report": {
|
|
1583
|
+
payload: {
|
|
1584
|
+
herd: string;
|
|
1585
|
+
job: string;
|
|
1586
|
+
body: string;
|
|
1587
|
+
};
|
|
1588
|
+
data: {
|
|
1589
|
+
message: number;
|
|
1590
|
+
};
|
|
1591
|
+
};
|
|
1592
|
+
/** `callerWorkspace` is the attending session's own HERDR_WORKSPACE_ID: the attached tab opens there, not in the herd's workspace. */
|
|
1593
|
+
"herd:attend": {
|
|
1594
|
+
payload: {
|
|
1595
|
+
herd: string;
|
|
1596
|
+
job: string;
|
|
1597
|
+
callerWorkspace: string;
|
|
1598
|
+
};
|
|
1599
|
+
data: {
|
|
1600
|
+
tab: string;
|
|
1601
|
+
pane: string;
|
|
1602
|
+
};
|
|
1603
|
+
};
|
|
1604
|
+
"herd:stop-hidden": {
|
|
1605
|
+
payload: Record<string, never>;
|
|
1606
|
+
data: {
|
|
1607
|
+
stopped: boolean;
|
|
1608
|
+
};
|
|
1609
|
+
};
|
|
1610
|
+
"herd:wrap-up": {
|
|
1611
|
+
payload: {
|
|
1612
|
+
herd: string;
|
|
1613
|
+
closePanes?: boolean;
|
|
1614
|
+
dispose?: string[];
|
|
1615
|
+
deleteJobDirs?: boolean;
|
|
1616
|
+
archiveRoom?: boolean;
|
|
1617
|
+
};
|
|
1618
|
+
data: {
|
|
1619
|
+
closed: string[];
|
|
1620
|
+
workspaceClosed: boolean;
|
|
1621
|
+
disposed: string[];
|
|
1622
|
+
refused: Array<{
|
|
1623
|
+
tree: string;
|
|
1624
|
+
reason: string;
|
|
1625
|
+
}>;
|
|
1626
|
+
deletedJobDirs: boolean;
|
|
1627
|
+
archived: boolean;
|
|
1628
|
+
};
|
|
1629
|
+
};
|
|
1393
1630
|
/** Wire reply on success is always `{ok:true, repaired}` (no `data`
|
|
1394
1631
|
* wrapper) — `data` here documents the extra field the same way PingData
|
|
1395
1632
|
* does for `ping`, not the literal wire nesting (R3). */
|
|
@@ -1488,6 +1725,46 @@ export interface Commands {
|
|
|
1488
1725
|
};
|
|
1489
1726
|
data: WorktreeAdoptData;
|
|
1490
1727
|
};
|
|
1728
|
+
"bg:ensure": {
|
|
1729
|
+
payload: {
|
|
1730
|
+
claim?: string;
|
|
1731
|
+
};
|
|
1732
|
+
data: {
|
|
1733
|
+
socket: string;
|
|
1734
|
+
started: boolean;
|
|
1735
|
+
parity: {
|
|
1736
|
+
ok: boolean;
|
|
1737
|
+
drift: string[];
|
|
1738
|
+
} | null;
|
|
1739
|
+
};
|
|
1740
|
+
};
|
|
1741
|
+
"bg:status": {
|
|
1742
|
+
payload: Record<string, never>;
|
|
1743
|
+
data: {
|
|
1744
|
+
up: boolean;
|
|
1745
|
+
socket: string;
|
|
1746
|
+
claims: Array<{
|
|
1747
|
+
owner: string;
|
|
1748
|
+
pane: string | null;
|
|
1749
|
+
createdAt: number;
|
|
1750
|
+
}>;
|
|
1751
|
+
};
|
|
1752
|
+
};
|
|
1753
|
+
/** Rejects (`ok:false`) naming every live claim owner while any claim is held. */
|
|
1754
|
+
"bg:stop": {
|
|
1755
|
+
payload: Record<string, never>;
|
|
1756
|
+
data: {
|
|
1757
|
+
stopped: boolean;
|
|
1758
|
+
};
|
|
1759
|
+
};
|
|
1760
|
+
"bg:release": {
|
|
1761
|
+
payload: {
|
|
1762
|
+
claim: string;
|
|
1763
|
+
};
|
|
1764
|
+
data: {
|
|
1765
|
+
released: boolean;
|
|
1766
|
+
};
|
|
1767
|
+
};
|
|
1491
1768
|
}
|
|
1492
1769
|
export type CommandName = keyof Commands;
|
|
1493
1770
|
export declare const COMMAND_NAMES: readonly CommandName[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
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, } 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, 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, } 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";
|
|
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";
|
|
9
9
|
export { repoNameForPath } from "./repos.ts";
|
|
10
10
|
export { decidePlacement, openSmartPane } from "./smart-pane.ts";
|
|
11
11
|
export type { Placement, PlacementOpts, HerdrCall } from "./smart-pane.ts";
|
|
12
|
+
export { BG_PREFIX, parsePaneRef, formatPaneRef } from "./pane-ref.ts";
|
|
13
|
+
export type { PaneServer, PaneRef } from "./pane-ref.ts";
|
|
12
14
|
export { getSetting, listSettings, explainSetting, expandVariables, SCOPE_ORDER, setSettingsWarnSink } from "./settings/resolve.ts";
|
|
13
15
|
export type { Scope, Provenance, ResolveOpts, Resolved, InvalidScope, ListedSetting, ExplainRow, ExpandCtx, } from "./settings/resolve.ts";
|
|
14
16
|
export { setSetting, unsetSetting } from "./settings/write.ts";
|
package/dist/index.js
CHANGED
|
@@ -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"]) {
|
|
208
|
+
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg"]) {
|
|
209
209
|
if (a[k] !== undefined)
|
|
210
210
|
payload[k] = a[k];
|
|
211
211
|
}
|
|
@@ -268,7 +268,10 @@ function paneSend(a, o = {}) {
|
|
|
268
268
|
return rtCommand("pane:send", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
269
269
|
}
|
|
270
270
|
function paneFocus(a, o = {}) {
|
|
271
|
-
|
|
271
|
+
const payload = { paneId: a.paneId };
|
|
272
|
+
if (a.callerWorkspace !== undefined)
|
|
273
|
+
payload.callerWorkspace = a.callerWorkspace;
|
|
274
|
+
return rtCommand("pane:focus", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
272
275
|
}
|
|
273
276
|
function gateOpen(a, o = {}) {
|
|
274
277
|
const payload = { subject: a.subject, kind: a.kind, questions: a.questions };
|
|
@@ -312,6 +315,85 @@ function gateSubscriptions(a, o = {}) {
|
|
|
312
315
|
payload[k] = a[k];
|
|
313
316
|
return rtCommand("gate:subscriptions", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
314
317
|
}
|
|
318
|
+
function herdStart(a, o = {}) {
|
|
319
|
+
const payload = { name: a.name, repo: a.repo, session: a.session };
|
|
320
|
+
if (a.hidden !== undefined)
|
|
321
|
+
payload.hidden = a.hidden;
|
|
322
|
+
return rtCommand("herd:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60000 });
|
|
323
|
+
}
|
|
324
|
+
function herdSpawn(a, o = {}) {
|
|
325
|
+
const payload = { herd: a.herd, job: a.job };
|
|
326
|
+
for (const k of ["brief", "dir", "model", "effort", "account", "disposable"])
|
|
327
|
+
if (a[k] !== undefined)
|
|
328
|
+
payload[k] = a[k];
|
|
329
|
+
return rtCommand("herd:spawn", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60000 });
|
|
330
|
+
}
|
|
331
|
+
function herdAsk(a, o = {}) {
|
|
332
|
+
const payload = { herd: a.herd, job: a.job, session: a.session, questions: a.questions };
|
|
333
|
+
for (const k of ["pane", "context"])
|
|
334
|
+
if (a[k] !== undefined)
|
|
335
|
+
payload[k] = a[k];
|
|
336
|
+
return rtCommand("herd:ask", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
337
|
+
}
|
|
338
|
+
function herdMilestone(a, o = {}) {
|
|
339
|
+
const payload = { herd: a.herd, job: a.job, session: a.session, artifact: a.artifact };
|
|
340
|
+
for (const k of ["pane", "summary"])
|
|
341
|
+
if (a[k] !== undefined)
|
|
342
|
+
payload[k] = a[k];
|
|
343
|
+
return rtCommand("herd:milestone", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
344
|
+
}
|
|
345
|
+
function herdAnswer(a, o = {}) {
|
|
346
|
+
return rtCommand("herd:answer", { gate: a.gate }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
347
|
+
}
|
|
348
|
+
function herdReport(a, o = {}) {
|
|
349
|
+
return rtCommand("herd:report", { herd: a.herd, job: a.job, body: a.body }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
350
|
+
}
|
|
351
|
+
function herdGates(a, o = {}) {
|
|
352
|
+
return rtCommand("herd:gates", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
353
|
+
}
|
|
354
|
+
function herdStatus(a, o = {}) {
|
|
355
|
+
return rtCommand("herd:status", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
356
|
+
}
|
|
357
|
+
function herdList(a = {}, o = {}) {
|
|
358
|
+
const payload = {};
|
|
359
|
+
if (a.all !== undefined)
|
|
360
|
+
payload.all = a.all;
|
|
361
|
+
return rtCommand("herd:list", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
362
|
+
}
|
|
363
|
+
function herdResume(a, o = {}) {
|
|
364
|
+
return rtCommand("herd:resume", { herd: a.herd, session: a.session }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
365
|
+
}
|
|
366
|
+
function herdClose(a, o = {}) {
|
|
367
|
+
return rtCommand("herd:close", { herd: a.herd, job: a.job }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
368
|
+
}
|
|
369
|
+
function herdAttend(a, o = {}) {
|
|
370
|
+
return rtCommand("herd:attend", { herd: a.herd, job: a.job, callerWorkspace: a.callerWorkspace }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60000 });
|
|
371
|
+
}
|
|
372
|
+
function herdWrapUp(a, o = {}) {
|
|
373
|
+
const payload = { herd: a.herd };
|
|
374
|
+
for (const k of ["closePanes", "dispose", "deleteJobDirs", "archiveRoom"])
|
|
375
|
+
if (a[k] !== undefined)
|
|
376
|
+
payload[k] = a[k];
|
|
377
|
+
return rtCommand("herd:wrap-up", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 120000 });
|
|
378
|
+
}
|
|
379
|
+
function herdStopHidden(_a, o = {}) {
|
|
380
|
+
return rtCommand("herd:stop-hidden", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
381
|
+
}
|
|
382
|
+
function bgEnsure(a = {}, o = {}) {
|
|
383
|
+
const payload = {};
|
|
384
|
+
if (a.claim !== undefined)
|
|
385
|
+
payload.claim = a.claim;
|
|
386
|
+
return rtCommand("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 15000 });
|
|
387
|
+
}
|
|
388
|
+
function bgStatus(o = {}) {
|
|
389
|
+
return rtCommand("bg:status", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
390
|
+
}
|
|
391
|
+
function bgStop(o = {}) {
|
|
392
|
+
return rtCommand("bg:stop", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
393
|
+
}
|
|
394
|
+
function bgRelease(a, o = {}) {
|
|
395
|
+
return rtCommand("bg:release", { claim: a.claim }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
396
|
+
}
|
|
315
397
|
// src/commands.ts
|
|
316
398
|
var GATE_BY_PANE = "pane";
|
|
317
399
|
function gateOptionValue(o) {
|
|
@@ -397,6 +479,20 @@ var COMMAND_NAMES = [
|
|
|
397
479
|
"gate:subscribe",
|
|
398
480
|
"gate:unsubscribe",
|
|
399
481
|
"gate:subscriptions",
|
|
482
|
+
"herd:start",
|
|
483
|
+
"herd:resume",
|
|
484
|
+
"herd:status",
|
|
485
|
+
"herd:list",
|
|
486
|
+
"herd:close",
|
|
487
|
+
"herd:spawn",
|
|
488
|
+
"herd:gates",
|
|
489
|
+
"herd:ask",
|
|
490
|
+
"herd:milestone",
|
|
491
|
+
"herd:answer",
|
|
492
|
+
"herd:report",
|
|
493
|
+
"herd:attend",
|
|
494
|
+
"herd:stop-hidden",
|
|
495
|
+
"herd:wrap-up",
|
|
400
496
|
"hooks:repair",
|
|
401
497
|
"hooks:watch",
|
|
402
498
|
"sdm:catalog",
|
|
@@ -410,7 +506,11 @@ var COMMAND_NAMES = [
|
|
|
410
506
|
"worktree:list",
|
|
411
507
|
"worktree:restore",
|
|
412
508
|
"worktree:freshen",
|
|
413
|
-
"worktree:adopt"
|
|
509
|
+
"worktree:adopt",
|
|
510
|
+
"bg:ensure",
|
|
511
|
+
"bg:status",
|
|
512
|
+
"bg:stop",
|
|
513
|
+
"bg:release"
|
|
414
514
|
];
|
|
415
515
|
// src/relay.ts
|
|
416
516
|
var DEFAULT_WS_URL = "ws://127.0.0.1:9401/ws";
|
|
@@ -587,6 +687,26 @@ async function openSmartPane(herdr, anchorPaneId, opts = {}) {
|
|
|
587
687
|
}
|
|
588
688
|
return { paneId, placement };
|
|
589
689
|
}
|
|
690
|
+
// src/pane-ref.ts
|
|
691
|
+
var BG_PREFIX = "bg:";
|
|
692
|
+
function parsePaneRef(ref) {
|
|
693
|
+
if (ref.startsWith(BG_PREFIX)) {
|
|
694
|
+
return {
|
|
695
|
+
server: "bg",
|
|
696
|
+
paneId: ref.slice(BG_PREFIX.length)
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
server: "visible",
|
|
701
|
+
paneId: ref
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function formatPaneRef(paneId, server) {
|
|
705
|
+
if (server === "visible") {
|
|
706
|
+
return paneId;
|
|
707
|
+
}
|
|
708
|
+
return BG_PREFIX + paneId;
|
|
709
|
+
}
|
|
590
710
|
// src/settings/resolve.ts
|
|
591
711
|
import { homedir as homedir4 } from "os";
|
|
592
712
|
import { join as join5 } from "path";
|
|
@@ -604,6 +724,9 @@ function userSettingsPath() {
|
|
|
604
724
|
function teamSettingsPath(team) {
|
|
605
725
|
return join3(teamsDir(), team, "mattstack", "settings.team.jsonc");
|
|
606
726
|
}
|
|
727
|
+
function teamLocalPath(team) {
|
|
728
|
+
return join3(home(), ".mattstack", "rt", "teams", `${team}.json`);
|
|
729
|
+
}
|
|
607
730
|
function machineSettingsPath() {
|
|
608
731
|
return join3(home(), ".mattstack", "user", "local", machineKey(), "settings.local.jsonc");
|
|
609
732
|
}
|
|
@@ -696,7 +819,7 @@ var REGISTRY = [
|
|
|
696
819
|
scopes: ["user"],
|
|
697
820
|
default: [],
|
|
698
821
|
merge: "replace",
|
|
699
|
-
description: "Event-bus glob rules that raise a desktop notification: [{pattern, category, title, message}]. pattern is matched against the events-bus topic (Bun.Glob semantics); title/message may interpolate `{field}` from the event payload. A fresh key, not an ownership-latch port, so a default is fine here."
|
|
822
|
+
description: "Event-bus glob rules that raise a desktop notification: [{pattern, category, title, message, subjectPrefix?, url?}]. 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. A fresh key, not an ownership-latch port, so a default is fine here."
|
|
700
823
|
},
|
|
701
824
|
{
|
|
702
825
|
key: "rt.cron",
|
|
@@ -1712,10 +1835,23 @@ function explainSetting(key, opts = {}) {
|
|
|
1712
1835
|
return resolveDef(def, readStores(), opts).rows;
|
|
1713
1836
|
}
|
|
1714
1837
|
// src/settings/write.ts
|
|
1715
|
-
import { existsSync as existsSync3, mkdirSync, readFileSync as
|
|
1838
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync5, renameSync, unlinkSync, writeFileSync } from "fs";
|
|
1716
1839
|
import { applyEdits, modify, parseTree } from "jsonc-parser";
|
|
1717
1840
|
import { randomBytes } from "crypto";
|
|
1718
1841
|
import { dirname as dirname2 } from "path";
|
|
1842
|
+
|
|
1843
|
+
// src/settings/team-local-read.ts
|
|
1844
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1845
|
+
function isJoinedTeam(team) {
|
|
1846
|
+
try {
|
|
1847
|
+
const parsed = JSON.parse(readFileSync4(teamLocalPath(team), "utf8"));
|
|
1848
|
+
return typeof parsed === "object" && parsed !== null && parsed.joinedByRt === true;
|
|
1849
|
+
} catch {
|
|
1850
|
+
return false;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/settings/write.ts
|
|
1719
1855
|
var FORMAT = { tabSize: 2, insertSpaces: true, eol: `
|
|
1720
1856
|
` };
|
|
1721
1857
|
function refuse(message) {
|
|
@@ -1773,6 +1909,11 @@ function migratedFalseMessage(key, def) {
|
|
|
1773
1909
|
const legacyPart = def.legacyFile ? ` — it is still read from ${def.legacyFile}` : "";
|
|
1774
1910
|
return `"${key}" is not writable through the settings resolver yet${legacyPart}`;
|
|
1775
1911
|
}
|
|
1912
|
+
function refuseIfJoined(team) {
|
|
1913
|
+
if (isJoinedTeam(team)) {
|
|
1914
|
+
refuse(`this machine joined "${team}" by invite, so its clone is pull-only and team settings cannot be written here. Ask the team's owner to make this change. Member-proposed changes are tracked in MAT-415.`);
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1776
1917
|
function resolveStorePath(scope, opts) {
|
|
1777
1918
|
if (scope === "user")
|
|
1778
1919
|
return userSettingsPath();
|
|
@@ -1783,6 +1924,7 @@ function resolveStorePath(scope, opts) {
|
|
|
1783
1924
|
if (!existsSync3(path)) {
|
|
1784
1925
|
refuse(`team store for "${opts.team}" does not exist (${path}) — clone/seed it before writing to it`);
|
|
1785
1926
|
}
|
|
1927
|
+
refuseIfJoined(opts.team);
|
|
1786
1928
|
return path;
|
|
1787
1929
|
}
|
|
1788
1930
|
const teams = listTeams();
|
|
@@ -1792,7 +1934,9 @@ function resolveStorePath(scope, opts) {
|
|
|
1792
1934
|
if (teams.length > 1) {
|
|
1793
1935
|
refuse(`multiple local team stores found (${teams.join(", ")}) — pass opts.team to choose one`);
|
|
1794
1936
|
}
|
|
1795
|
-
|
|
1937
|
+
const team = teams[0];
|
|
1938
|
+
refuseIfJoined(team);
|
|
1939
|
+
return teamSettingsPath(team);
|
|
1796
1940
|
}
|
|
1797
1941
|
function resolveStorePathForUnset(scope, opts) {
|
|
1798
1942
|
if (scope === "user")
|
|
@@ -1801,7 +1945,10 @@ function resolveStorePathForUnset(scope, opts) {
|
|
|
1801
1945
|
return machineSettingsPath();
|
|
1802
1946
|
if (opts.team !== undefined) {
|
|
1803
1947
|
const path = teamSettingsPath(opts.team);
|
|
1804
|
-
|
|
1948
|
+
if (!existsSync3(path))
|
|
1949
|
+
return null;
|
|
1950
|
+
refuseIfJoined(opts.team);
|
|
1951
|
+
return path;
|
|
1805
1952
|
}
|
|
1806
1953
|
const teams = listTeams();
|
|
1807
1954
|
if (teams.length === 0)
|
|
@@ -1809,7 +1956,9 @@ function resolveStorePathForUnset(scope, opts) {
|
|
|
1809
1956
|
if (teams.length > 1) {
|
|
1810
1957
|
refuse(`multiple local team stores found (${teams.join(", ")}) — pass opts.team to choose one`);
|
|
1811
1958
|
}
|
|
1812
|
-
|
|
1959
|
+
const team = teams[0];
|
|
1960
|
+
refuseIfJoined(team);
|
|
1961
|
+
return teamSettingsPath(team);
|
|
1813
1962
|
}
|
|
1814
1963
|
function seedHeader() {
|
|
1815
1964
|
return `// rt settings — created by \`rt settings set\`. JSONC: comments and trailing commas are fine.
|
|
@@ -1855,7 +2004,7 @@ function findDuplicateKey(node) {
|
|
|
1855
2004
|
function writeIntoStore(storePath, jsonPath, value, createIfMissing) {
|
|
1856
2005
|
let content;
|
|
1857
2006
|
if (existsSync3(storePath)) {
|
|
1858
|
-
content =
|
|
2007
|
+
content = readFileSync5(storePath, "utf8");
|
|
1859
2008
|
if (content.trim() === "") {
|
|
1860
2009
|
content = seedHeader();
|
|
1861
2010
|
} else {
|
|
@@ -1876,7 +2025,7 @@ function writeIntoStore(storePath, jsonPath, value, createIfMissing) {
|
|
|
1876
2025
|
writeTempThenRename(storePath, finalText);
|
|
1877
2026
|
}
|
|
1878
2027
|
function removeFromStore(storePath, jsonPath) {
|
|
1879
|
-
const content =
|
|
2028
|
+
const content = readFileSync5(storePath, "utf8");
|
|
1880
2029
|
if (content.trim() === "")
|
|
1881
2030
|
return false;
|
|
1882
2031
|
assertEditableJsonc(storePath, content);
|
|
@@ -1903,7 +2052,7 @@ function writeTempThenRename(storePath, finalText) {
|
|
|
1903
2052
|
}
|
|
1904
2053
|
}
|
|
1905
2054
|
// src/settings/identity.ts
|
|
1906
|
-
import { existsSync as existsSync4, readFileSync as
|
|
2055
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, realpathSync } from "fs";
|
|
1907
2056
|
|
|
1908
2057
|
// src/settings/exec.ts
|
|
1909
2058
|
async function runCapture(argv, opts = {}) {
|
|
@@ -2065,7 +2214,7 @@ async function resolveNameToIdentity(name, reposJsonPath) {
|
|
|
2065
2214
|
if (!existsSync4(reposJsonPath))
|
|
2066
2215
|
return null;
|
|
2067
2216
|
try {
|
|
2068
|
-
const index = JSON.parse(
|
|
2217
|
+
const index = JSON.parse(readFileSync6(reposJsonPath, "utf8"));
|
|
2069
2218
|
const path = index[name];
|
|
2070
2219
|
if (typeof path !== "string")
|
|
2071
2220
|
return null;
|
|
@@ -2090,6 +2239,7 @@ export {
|
|
|
2090
2239
|
readMrsByBranch,
|
|
2091
2240
|
readDiscussions,
|
|
2092
2241
|
readBranchCache,
|
|
2242
|
+
parsePaneRef,
|
|
2093
2243
|
parseIdentity,
|
|
2094
2244
|
paneSpawn,
|
|
2095
2245
|
paneSend,
|
|
@@ -2105,6 +2255,20 @@ export {
|
|
|
2105
2255
|
listRuns,
|
|
2106
2256
|
isMigrated,
|
|
2107
2257
|
identityFromRemote,
|
|
2258
|
+
herdWrapUp,
|
|
2259
|
+
herdStopHidden,
|
|
2260
|
+
herdStatus,
|
|
2261
|
+
herdStart,
|
|
2262
|
+
herdSpawn,
|
|
2263
|
+
herdResume,
|
|
2264
|
+
herdReport,
|
|
2265
|
+
herdMilestone,
|
|
2266
|
+
herdList,
|
|
2267
|
+
herdGates,
|
|
2268
|
+
herdClose,
|
|
2269
|
+
herdAttend,
|
|
2270
|
+
herdAsk,
|
|
2271
|
+
herdAnswer,
|
|
2108
2272
|
getSetting,
|
|
2109
2273
|
getRun,
|
|
2110
2274
|
getDef,
|
|
@@ -2119,6 +2283,7 @@ export {
|
|
|
2119
2283
|
gateList,
|
|
2120
2284
|
gateClose,
|
|
2121
2285
|
gateAnswer,
|
|
2286
|
+
formatPaneRef,
|
|
2122
2287
|
explainSetting,
|
|
2123
2288
|
expandVariables,
|
|
2124
2289
|
eventsWait,
|
|
@@ -2150,6 +2315,10 @@ export {
|
|
|
2150
2315
|
chatAway,
|
|
2151
2316
|
chatArchive,
|
|
2152
2317
|
chatAck,
|
|
2318
|
+
bgStop,
|
|
2319
|
+
bgStatus,
|
|
2320
|
+
bgRelease,
|
|
2321
|
+
bgEnsure,
|
|
2153
2322
|
allDefs,
|
|
2154
2323
|
agentStart,
|
|
2155
2324
|
agentResume,
|
|
@@ -2161,5 +2330,6 @@ export {
|
|
|
2161
2330
|
GATE_BY_PANE,
|
|
2162
2331
|
DEFAULT_WS_URL,
|
|
2163
2332
|
DEFAULT_SOCK,
|
|
2164
|
-
COMMAND_NAMES
|
|
2333
|
+
COMMAND_NAMES,
|
|
2334
|
+
BG_PREFIX
|
|
2165
2335
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const BG_PREFIX = "bg:";
|
|
2
|
+
export type PaneServer = "visible" | "bg";
|
|
3
|
+
export interface PaneRef {
|
|
4
|
+
server: PaneServer;
|
|
5
|
+
paneId: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function parsePaneRef(ref: string): PaneRef;
|
|
8
|
+
export declare function formatPaneRef(paneId: string, server: PaneServer): string;
|
package/dist/settings/paths.d.ts
CHANGED
|
@@ -12,6 +12,14 @@
|
|
|
12
12
|
export declare function userSettingsPath(): string;
|
|
13
13
|
/** ~/.mattstack/teams/<team>/mattstack/settings.team.jsonc — the team store. */
|
|
14
14
|
export declare function teamSettingsPath(team: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* ~/.mattstack/rt/teams/<team>.json: the machine-local team record. Mirrored
|
|
17
|
+
* from repo-tools/lib/team/team-local.ts's teamLocalPath, which is the
|
|
18
|
+
* authority: rt-client has no dependency on rt's lib/, so this literal is
|
|
19
|
+
* duplicated here rather than imported (same convention as `teamSettingsPath`
|
|
20
|
+
* and `userSettingsPath` above).
|
|
21
|
+
*/
|
|
22
|
+
export declare function teamLocalPath(team: string): string;
|
|
15
23
|
/**
|
|
16
24
|
* ~/.mattstack/user/local/<machineKey()>/settings.local.jsonc — the machine
|
|
17
25
|
* store, TRACKED and keyed per machine (path literals legal here only).
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One field of the machine-local team record, for the write guard. The record
|
|
3
|
+
* itself is owned by repo-tools/lib/team/team-local.ts; this reads only what
|
|
4
|
+
* the guard needs and never writes.
|
|
5
|
+
*/
|
|
6
|
+
/** Unreadable, absent or malformed all read as false, so nothing that predates the field is refused. */
|
|
7
|
+
export declare function isJoinedTeam(team: string): boolean;
|
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"] as const) {
|
|
359
|
+
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg"] 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 });
|
|
@@ -441,7 +441,9 @@ export function paneFocus(
|
|
|
441
441
|
a: Commands["pane:focus"]["payload"],
|
|
442
442
|
o: RtClientOptions = {},
|
|
443
443
|
): Promise<RtResponse<Commands["pane:focus"]["data"]>> {
|
|
444
|
-
|
|
444
|
+
const payload: Record<string, unknown> = { paneId: a.paneId };
|
|
445
|
+
if (a.callerWorkspace !== undefined) payload.callerWorkspace = a.callerWorkspace;
|
|
446
|
+
return rtCommand<Commands["pane:focus"]["data"]>("pane:focus", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
445
447
|
}
|
|
446
448
|
|
|
447
449
|
// ─── Gates (BOARD-20/21 gate facility) ─────────────────────────────────────
|
|
@@ -519,3 +521,153 @@ export function gateSubscriptions(
|
|
|
519
521
|
for (const k of ["session", "live"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
520
522
|
return rtCommand<Commands["gate:subscriptions"]["data"]>("gate:subscriptions", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
521
523
|
}
|
|
524
|
+
|
|
525
|
+
// ─── Herd (shepherd run registry) ───────────────────────────────────────────
|
|
526
|
+
|
|
527
|
+
/** Provisions the room, workspace and subscription before it answers, so it
|
|
528
|
+
gets the same 60s budget as a spawn rather than the 10s default. */
|
|
529
|
+
export function herdStart(
|
|
530
|
+
a: Commands["herd:start"]["payload"],
|
|
531
|
+
o: RtClientOptions = {},
|
|
532
|
+
): Promise<RtResponse<Commands["herd:start"]["data"]>> {
|
|
533
|
+
const payload: Record<string, unknown> = { name: a.name, repo: a.repo, session: a.session };
|
|
534
|
+
if (a.hidden !== undefined) payload.hidden = a.hidden;
|
|
535
|
+
return rtCommand<Commands["herd:start"]["data"]>("herd:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60_000 });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Worktree provision plus agent launch; the 10s default cannot cover it. */
|
|
539
|
+
export function herdSpawn(
|
|
540
|
+
a: Commands["herd:spawn"]["payload"],
|
|
541
|
+
o: RtClientOptions = {},
|
|
542
|
+
): Promise<RtResponse<Commands["herd:spawn"]["data"]>> {
|
|
543
|
+
const payload: Record<string, unknown> = { herd: a.herd, job: a.job };
|
|
544
|
+
for (const k of ["brief", "dir", "model", "effort", "account", "disposable"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
545
|
+
return rtCommand<Commands["herd:spawn"]["data"]>("herd:spawn", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60_000 });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
export function herdAsk(
|
|
549
|
+
a: Commands["herd:ask"]["payload"],
|
|
550
|
+
o: RtClientOptions = {},
|
|
551
|
+
): Promise<RtResponse<Commands["herd:ask"]["data"]>> {
|
|
552
|
+
const payload: Record<string, unknown> = { herd: a.herd, job: a.job, session: a.session, questions: a.questions };
|
|
553
|
+
for (const k of ["pane", "context"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
554
|
+
return rtCommand<Commands["herd:ask"]["data"]>("herd:ask", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function herdMilestone(
|
|
558
|
+
a: Commands["herd:milestone"]["payload"],
|
|
559
|
+
o: RtClientOptions = {},
|
|
560
|
+
): Promise<RtResponse<Commands["herd:milestone"]["data"]>> {
|
|
561
|
+
const payload: Record<string, unknown> = { herd: a.herd, job: a.job, session: a.session, artifact: a.artifact };
|
|
562
|
+
for (const k of ["pane", "summary"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
563
|
+
return rtCommand<Commands["herd:milestone"]["data"]>("herd:milestone", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function herdAnswer(
|
|
567
|
+
a: Commands["herd:answer"]["payload"],
|
|
568
|
+
o: RtClientOptions = {},
|
|
569
|
+
): Promise<RtResponse<Commands["herd:answer"]["data"]>> {
|
|
570
|
+
return rtCommand<Commands["herd:answer"]["data"]>("herd:answer", { gate: a.gate }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** A disposable job's report also closes its pane, one herdr CLI call under the runner's own 15s budget. */
|
|
574
|
+
export function herdReport(
|
|
575
|
+
a: Commands["herd:report"]["payload"],
|
|
576
|
+
o: RtClientOptions = {},
|
|
577
|
+
): Promise<RtResponse<Commands["herd:report"]["data"]>> {
|
|
578
|
+
return rtCommand<Commands["herd:report"]["data"]>("herd:report", { herd: a.herd, job: a.job, body: a.body }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
export function herdGates(
|
|
582
|
+
a: Commands["herd:gates"]["payload"],
|
|
583
|
+
o: RtClientOptions = {},
|
|
584
|
+
): Promise<RtResponse<Commands["herd:gates"]["data"]>> {
|
|
585
|
+
return rtCommand<Commands["herd:gates"]["data"]>("herd:gates", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
export function herdStatus(
|
|
589
|
+
a: Commands["herd:status"]["payload"],
|
|
590
|
+
o: RtClientOptions = {},
|
|
591
|
+
): Promise<RtResponse<Commands["herd:status"]["data"]>> {
|
|
592
|
+
return rtCommand<Commands["herd:status"]["data"]>("herd:status", { herd: a.herd }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
export function herdList(
|
|
596
|
+
a: Commands["herd:list"]["payload"] = {},
|
|
597
|
+
o: RtClientOptions = {},
|
|
598
|
+
): Promise<RtResponse<Commands["herd:list"]["data"]>> {
|
|
599
|
+
const payload: Record<string, unknown> = {};
|
|
600
|
+
if (a.all !== undefined) payload.all = a.all;
|
|
601
|
+
return rtCommand<Commands["herd:list"]["data"]>("herd:list", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
export function herdResume(
|
|
605
|
+
a: Commands["herd:resume"]["payload"],
|
|
606
|
+
o: RtClientOptions = {},
|
|
607
|
+
): Promise<RtResponse<Commands["herd:resume"]["data"]>> {
|
|
608
|
+
return rtCommand<Commands["herd:resume"]["data"]>("herd:resume", { herd: a.herd, session: a.session }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** Closes a herdr pane, one CLI call under the runner's own 15s budget. */
|
|
612
|
+
export function herdClose(
|
|
613
|
+
a: Commands["herd:close"]["payload"],
|
|
614
|
+
o: RtClientOptions = {},
|
|
615
|
+
): Promise<RtResponse<Commands["herd:close"]["data"]>> {
|
|
616
|
+
return rtCommand<Commands["herd:close"]["data"]>("herd:close", { herd: a.herd, job: a.job }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** Three sequential herdr CLI calls (pane get, tab create, pane run), each under the runner's own 15s budget. */
|
|
620
|
+
export function herdAttend(
|
|
621
|
+
a: Commands["herd:attend"]["payload"],
|
|
622
|
+
o: RtClientOptions = {},
|
|
623
|
+
): Promise<RtResponse<Commands["herd:attend"]["data"]>> {
|
|
624
|
+
return rtCommand<Commands["herd:attend"]["data"]>("herd:attend", { herd: a.herd, job: a.job, callerWorkspace: a.callerWorkspace }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60_000 });
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Closes panes, disposes worktrees and archives the room in one pass; the
|
|
628
|
+
worktree disposals alone can outrun a spawn's budget. */
|
|
629
|
+
export function herdWrapUp(
|
|
630
|
+
a: Commands["herd:wrap-up"]["payload"],
|
|
631
|
+
o: RtClientOptions = {},
|
|
632
|
+
): Promise<RtResponse<Commands["herd:wrap-up"]["data"]>> {
|
|
633
|
+
const payload: Record<string, unknown> = { herd: a.herd };
|
|
634
|
+
for (const k of ["closePanes", "dispose", "deleteJobDirs", "archiveRoom"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
635
|
+
return rtCommand<Commands["herd:wrap-up"]["data"]>("herd:wrap-up", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 120_000 });
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
/** Runs `herdr session stop`, one CLI call under the runner's own 15s budget. */
|
|
639
|
+
export function herdStopHidden(
|
|
640
|
+
_a: Commands["herd:stop-hidden"]["payload"],
|
|
641
|
+
o: RtClientOptions = {},
|
|
642
|
+
): Promise<RtResponse<Commands["herd:stop-hidden"]["data"]>> {
|
|
643
|
+
return rtCommand<Commands["herd:stop-hidden"]["data"]>("herd:stop-hidden", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// ─── Background server (daemon-owned background herdr session) ────────────
|
|
647
|
+
|
|
648
|
+
/** May spawn `herdr server` and wait for it to bind; budget matches bg-service's own 10s readyTimeoutMs plus margin. */
|
|
649
|
+
export function bgEnsure(
|
|
650
|
+
a: Commands["bg:ensure"]["payload"] = {},
|
|
651
|
+
o: RtClientOptions = {},
|
|
652
|
+
): Promise<RtResponse<Commands["bg:ensure"]["data"]>> {
|
|
653
|
+
const payload: Record<string, unknown> = {};
|
|
654
|
+
if (a.claim !== undefined) payload.claim = a.claim;
|
|
655
|
+
return rtCommand<Commands["bg:ensure"]["data"]>("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 15_000 });
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/** Never ensures/spawns; a plain read of the current state. */
|
|
659
|
+
export function bgStatus(o: RtClientOptions = {}): Promise<RtResponse<Commands["bg:status"]["data"]>> {
|
|
660
|
+
return rtCommand<Commands["bg:status"]["data"]>("bg:status", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/** Asks the daemon to stop the background server; refuses (ok:false) while any claim is live, naming the owners. */
|
|
664
|
+
export function bgStop(o: RtClientOptions = {}): Promise<RtResponse<Commands["bg:stop"]["data"]>> {
|
|
665
|
+
return rtCommand<Commands["bg:stop"]["data"]>("bg:stop", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export function bgRelease(
|
|
669
|
+
a: Commands["bg:release"]["payload"],
|
|
670
|
+
o: RtClientOptions = {},
|
|
671
|
+
): Promise<RtResponse<Commands["bg:release"]["data"]>> {
|
|
672
|
+
return rtCommand<Commands["bg:release"]["data"]>("bg:release", { claim: a.claim }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
673
|
+
}
|
package/src/commands.ts
CHANGED
|
@@ -134,6 +134,21 @@ export interface GateSubscription {
|
|
|
134
134
|
dead: boolean;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
export interface HerdInfo { id: string; repo: string; room: string; workspace: string; shepherdSession: string; shepherdHandle: string; herdrSocket: string | null; hidden: boolean; status: "active" | "wrapped"; createdAt: number; wrappedAt: number | null }
|
|
138
|
+
/** A herd row as `herd:list` reports it: the registry row plus how many jobs hang off it. */
|
|
139
|
+
export interface HerdListRow extends HerdInfo { jobs: number }
|
|
140
|
+
export interface HerdJobInfo { herd: string; name: string; worktree: string; branch: string | null; tree: string | null; pane: string | null; agentSession: string | null; agentId: string | null; handle: string; status: "spawning" | "active" | "at-gate" | "at-milestone" | "done" | "closed" | "crashed"; disposable: boolean; lastGate: string | null; lastReport: number | null; createdAt: number; updatedAt: number }
|
|
141
|
+
/** `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. */
|
|
142
|
+
export interface HerdStatusData {
|
|
143
|
+
herd: HerdInfo;
|
|
144
|
+
jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | null; lastGateStatus: GateStatus | null; lastGateDelivery: "delivered" | "dead-pane" | null }>;
|
|
145
|
+
unread: number;
|
|
146
|
+
lifecycleConnected: boolean;
|
|
147
|
+
hiddenUp: boolean | null;
|
|
148
|
+
/** The shepherd session's own `herd:<id>/` subscription row, or null when none is live. */
|
|
149
|
+
subscription: { id: string; dead: boolean; lastDelivery: GateSubscription["lastDelivery"] } | null;
|
|
150
|
+
}
|
|
151
|
+
|
|
137
152
|
/**
|
|
138
153
|
* Duplicated shape on purpose, same reasoning as EventsBusEvent above:
|
|
139
154
|
* these mirror lib/state/chat-store.ts's types, which rt-client cannot
|
|
@@ -229,7 +244,10 @@ export interface InviteResult { paneId: string; delivered: "accepted" | "queued"
|
|
|
229
244
|
/** Duplicated shape on purpose: mirrors lib/daemon/inject.ts's InjectResult. */
|
|
230
245
|
export type PaneDelivery = "accepted" | "queued" | "refused";
|
|
231
246
|
export interface PaneSendResult { paneId: string; delivered: PaneDelivery; reason?: string }
|
|
232
|
-
|
|
247
|
+
/** `attendTab` is set only for a `bg:` ref: focus for a background pane IS
|
|
248
|
+
the attend flow (a visible tab running a terminal attach), and this is
|
|
249
|
+
that tab's id. */
|
|
250
|
+
export interface PaneFocusResult { paneId: string; focused: boolean; attendTab?: string }
|
|
233
251
|
|
|
234
252
|
// SKILLS-53: one judgment, computed once in rt, so the console and the tray
|
|
235
253
|
// never derive two verdicts that can disagree.
|
|
@@ -540,7 +558,7 @@ export interface Commands {
|
|
|
540
558
|
"chat:dm-open": { payload: { from: string; to: string; sessionId?: string }; data: { room: string; created: boolean } };
|
|
541
559
|
|
|
542
560
|
// ─── Agent handoff (rt agent) ────────────────────────────────────────────
|
|
543
|
-
"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 }; data: AgentRecord };
|
|
561
|
+
"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 };
|
|
544
562
|
"agent:resume": { payload: { id: string; prompt?: string; surface?: AgentSurface; workspace?: string; tab?: string }; data: AgentRecord };
|
|
545
563
|
"agent:get": { payload: { id: string }; data: AgentRecord };
|
|
546
564
|
"agent:list": { payload: { repo?: string }; data: { agents: AgentRecord[] } };
|
|
@@ -554,7 +572,9 @@ export interface Commands {
|
|
|
554
572
|
data: { pane: ChatPane; ready: boolean };
|
|
555
573
|
};
|
|
556
574
|
"pane:send": { payload: { paneId: string; text: string; callerPane?: string }; data: PaneSendResult };
|
|
557
|
-
|
|
575
|
+
/** `callerWorkspace` (HERDR_WORKSPACE_ID) is required only for a `bg:`
|
|
576
|
+
ref, whose focus opens an attend tab in the caller's own workspace. */
|
|
577
|
+
"pane:focus": { payload: { paneId: string; callerWorkspace?: string }; data: PaneFocusResult };
|
|
558
578
|
|
|
559
579
|
// ─── R013/R016 ────────────────────────────────────────────────
|
|
560
580
|
"cache:read": { payload: { branches?: string[]; maxAgeMs?: number; repoIdentity?: string }; data: Record<string, BranchEnrichment> };
|
|
@@ -612,6 +632,25 @@ export interface Commands {
|
|
|
612
632
|
* onto delivery outcomes (dead marks included). */
|
|
613
633
|
"gate:subscriptions": { payload: { session?: string; live?: boolean }; data: { subscriptions: GateSubscription[] } };
|
|
614
634
|
|
|
635
|
+
// ─── Herd (shepherd run registry) ────────────────────────────────────────
|
|
636
|
+
"herd:start": { payload: { name: string; repo: string; session: string; hidden?: boolean }; data: { herd: string; room: string; workspace: string; subscription: string; handle: string; hidden: boolean } };
|
|
637
|
+
"herd:resume": { payload: { herd: string; session: string }; data: { subscription: string; gates: GateRow[]; unread: number; status: HerdStatusData; handle: string } };
|
|
638
|
+
"herd:status": { payload: { herd: string }; data: HerdStatusData };
|
|
639
|
+
/** Active herds only unless `all`, so a shepherd's "which herd am I on" question has one answer. */
|
|
640
|
+
"herd:list": { payload: { all?: boolean }; data: { herds: HerdListRow[] } };
|
|
641
|
+
"herd:close": { payload: { herd: string; job: string }; data: { job: string; status: "closed" } };
|
|
642
|
+
/** `brief` is the brief TEXT, not a path: the CLI reads the file. It is stored at `<jobsRoot>/<herd>/<job>/job.md`, so a respawn with `dir` and no `brief` reads it back. */
|
|
643
|
+
"herd:spawn": { payload: { herd: string; job: string; brief?: string; dir?: string; model?: string; effort?: string; account?: string; disposable?: boolean }; data: { herd: string; job: string; pane: string; worktree: string; branch: string | null; tree: string | null; /** null = no provisioning ran (--dir); false = cold create, worth announcing. */ wasOnDeck: boolean | null; agentId: string; sessionId: string; handle: string } };
|
|
644
|
+
"herd:gates": { payload: { herd: string }; data: { gates: GateRow[] } };
|
|
645
|
+
"herd:ask": { payload: { herd: string; job: string; session: string; pane?: string; questions: GateQuestion[]; context?: string }; data: { gate: string } };
|
|
646
|
+
"herd:milestone": { payload: { herd: string; job: string; session: string; pane?: string; artifact: string; summary?: string }; data: { gate: string; message: number } };
|
|
647
|
+
"herd:answer": { payload: { gate: string }; data: { gate: string; status: GateStatus; answer: GateAnswer | null; closedReason: GateRow["closedReason"] } };
|
|
648
|
+
"herd:report": { payload: { herd: string; job: string; body: string }; data: { message: number } };
|
|
649
|
+
/** `callerWorkspace` is the attending session's own HERDR_WORKSPACE_ID: the attached tab opens there, not in the herd's workspace. */
|
|
650
|
+
"herd:attend": { payload: { herd: string; job: string; callerWorkspace: string }; data: { tab: string; pane: string } };
|
|
651
|
+
"herd:stop-hidden": { payload: Record<string, never>; data: { stopped: boolean } };
|
|
652
|
+
"herd:wrap-up": { payload: { herd: string; closePanes?: boolean; dispose?: string[]; deleteJobDirs?: boolean; archiveRoom?: boolean }; data: { closed: string[]; workspaceClosed: boolean; disposed: string[]; refused: Array<{ tree: string; reason: string }>; deletedJobDirs: boolean; archived: boolean } };
|
|
653
|
+
|
|
615
654
|
/** Wire reply on success is always `{ok:true, repaired}` (no `data`
|
|
616
655
|
* wrapper) — `data` here documents the extra field the same way PingData
|
|
617
656
|
* does for `ping`, not the literal wire nesting (R3). */
|
|
@@ -632,6 +671,13 @@ export interface Commands {
|
|
|
632
671
|
"worktree:restore": { payload: { repoName: string; tree: string }; data: WorktreeRestoreData };
|
|
633
672
|
"worktree:freshen": { payload: { repoName?: string; tree?: string }; data: WorktreeFreshenData };
|
|
634
673
|
"worktree:adopt": { payload: { repoName: string; claim?: boolean }; data: WorktreeAdoptData };
|
|
674
|
+
|
|
675
|
+
// ─── Background server (daemon-owned background herdr session) ──────────
|
|
676
|
+
"bg:ensure": { payload: { claim?: string }; data: { socket: string; started: boolean; parity: { ok: boolean; drift: string[] } | null } };
|
|
677
|
+
"bg:status": { payload: Record<string, never>; data: { up: boolean; socket: string; claims: Array<{ owner: string; pane: string | null; createdAt: number }> } };
|
|
678
|
+
/** Rejects (`ok:false`) naming every live claim owner while any claim is held. */
|
|
679
|
+
"bg:stop": { payload: Record<string, never>; data: { stopped: boolean } };
|
|
680
|
+
"bg:release": { payload: { claim: string }; data: { released: boolean } };
|
|
635
681
|
}
|
|
636
682
|
|
|
637
683
|
export type CommandName = keyof Commands;
|
|
@@ -715,6 +761,20 @@ export const COMMAND_NAMES: readonly CommandName[] = [
|
|
|
715
761
|
"gate:subscribe",
|
|
716
762
|
"gate:unsubscribe",
|
|
717
763
|
"gate:subscriptions",
|
|
764
|
+
"herd:start",
|
|
765
|
+
"herd:resume",
|
|
766
|
+
"herd:status",
|
|
767
|
+
"herd:list",
|
|
768
|
+
"herd:close",
|
|
769
|
+
"herd:spawn",
|
|
770
|
+
"herd:gates",
|
|
771
|
+
"herd:ask",
|
|
772
|
+
"herd:milestone",
|
|
773
|
+
"herd:answer",
|
|
774
|
+
"herd:report",
|
|
775
|
+
"herd:attend",
|
|
776
|
+
"herd:stop-hidden",
|
|
777
|
+
"herd:wrap-up",
|
|
718
778
|
"hooks:repair",
|
|
719
779
|
"hooks:watch",
|
|
720
780
|
"sdm:catalog",
|
|
@@ -729,4 +789,9 @@ export const COMMAND_NAMES: readonly CommandName[] = [
|
|
|
729
789
|
"worktree:restore",
|
|
730
790
|
"worktree:freshen",
|
|
731
791
|
"worktree:adopt",
|
|
792
|
+
|
|
793
|
+
"bg:ensure",
|
|
794
|
+
"bg:status",
|
|
795
|
+
"bg:stop",
|
|
796
|
+
"bg:release",
|
|
732
797
|
];
|
package/src/index.ts
CHANGED
|
@@ -54,6 +54,24 @@ export {
|
|
|
54
54
|
gateSubscribe,
|
|
55
55
|
gateUnsubscribe,
|
|
56
56
|
gateSubscriptions,
|
|
57
|
+
herdStart,
|
|
58
|
+
herdSpawn,
|
|
59
|
+
herdAsk,
|
|
60
|
+
herdMilestone,
|
|
61
|
+
herdAnswer,
|
|
62
|
+
herdReport,
|
|
63
|
+
herdGates,
|
|
64
|
+
herdStatus,
|
|
65
|
+
herdList,
|
|
66
|
+
herdResume,
|
|
67
|
+
herdClose,
|
|
68
|
+
herdAttend,
|
|
69
|
+
herdWrapUp,
|
|
70
|
+
herdStopHidden,
|
|
71
|
+
bgEnsure,
|
|
72
|
+
bgStatus,
|
|
73
|
+
bgStop,
|
|
74
|
+
bgRelease,
|
|
57
75
|
} from "./client.ts";
|
|
58
76
|
|
|
59
77
|
export { COMMAND_NAMES, GATE_BY_PANE, gateOptionValue, gateOptionLabel } from "./commands.ts";
|
|
@@ -100,6 +118,10 @@ export type {
|
|
|
100
118
|
GateAnswer,
|
|
101
119
|
GateRow,
|
|
102
120
|
GateSubscription,
|
|
121
|
+
HerdInfo,
|
|
122
|
+
HerdListRow,
|
|
123
|
+
HerdJobInfo,
|
|
124
|
+
HerdStatusData,
|
|
103
125
|
} from "./commands.ts";
|
|
104
126
|
|
|
105
127
|
export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";
|
|
@@ -112,6 +134,9 @@ export { repoNameForPath } from "./repos.ts";
|
|
|
112
134
|
export { decidePlacement, openSmartPane } from "./smart-pane.ts";
|
|
113
135
|
export type { Placement, PlacementOpts, HerdrCall } from "./smart-pane.ts";
|
|
114
136
|
|
|
137
|
+
export { BG_PREFIX, parsePaneRef, formatPaneRef } from "./pane-ref.ts";
|
|
138
|
+
export type { PaneServer, PaneRef } from "./pane-ref.ts";
|
|
139
|
+
|
|
115
140
|
// ─── Settings (RT-50) ────────────────────────────────────────────────────────
|
|
116
141
|
|
|
117
142
|
export { getSetting, listSettings, explainSetting, expandVariables, SCOPE_ORDER, setSettingsWarnSink } from "./settings/resolve.ts";
|
package/src/pane-ref.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export const BG_PREFIX = "bg:";
|
|
2
|
+
|
|
3
|
+
export type PaneServer = "visible" | "bg";
|
|
4
|
+
|
|
5
|
+
export interface PaneRef {
|
|
6
|
+
server: PaneServer;
|
|
7
|
+
paneId: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function parsePaneRef(ref: string): PaneRef {
|
|
11
|
+
if (ref.startsWith(BG_PREFIX)) {
|
|
12
|
+
return {
|
|
13
|
+
server: "bg",
|
|
14
|
+
paneId: ref.slice(BG_PREFIX.length),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
server: "visible",
|
|
19
|
+
paneId: ref,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function formatPaneRef(paneId: string, server: PaneServer): string {
|
|
24
|
+
if (server === "visible") {
|
|
25
|
+
return paneId;
|
|
26
|
+
}
|
|
27
|
+
return BG_PREFIX + paneId;
|
|
28
|
+
}
|
package/src/settings/paths.ts
CHANGED
|
@@ -27,6 +27,17 @@ export function teamSettingsPath(team: string): string {
|
|
|
27
27
|
return join(teamsDir(), team, "mattstack", "settings.team.jsonc");
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* ~/.mattstack/rt/teams/<team>.json: the machine-local team record. Mirrored
|
|
32
|
+
* from repo-tools/lib/team/team-local.ts's teamLocalPath, which is the
|
|
33
|
+
* authority: rt-client has no dependency on rt's lib/, so this literal is
|
|
34
|
+
* duplicated here rather than imported (same convention as `teamSettingsPath`
|
|
35
|
+
* and `userSettingsPath` above).
|
|
36
|
+
*/
|
|
37
|
+
export function teamLocalPath(team: string): string {
|
|
38
|
+
return join(home(), ".mattstack", "rt", "teams", `${team}.json`);
|
|
39
|
+
}
|
|
40
|
+
|
|
30
41
|
/**
|
|
31
42
|
* ~/.mattstack/user/local/<machineKey()>/settings.local.jsonc — the machine
|
|
32
43
|
* store, TRACKED and keyed per machine (path literals legal here only).
|
|
@@ -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}]. pattern is matched against the events-bus topic (Bun.Glob semantics); title/message may interpolate `{field}` from the event payload. 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?}]. 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. A fresh key, not an ownership-latch port, so a default is fine here.",
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
key: "rt.cron",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One field of the machine-local team record, for the write guard. The record
|
|
3
|
+
* itself is owned by repo-tools/lib/team/team-local.ts; this reads only what
|
|
4
|
+
* the guard needs and never writes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "fs";
|
|
8
|
+
import { teamLocalPath } from "./paths.ts";
|
|
9
|
+
|
|
10
|
+
/** Unreadable, absent or malformed all read as false, so nothing that predates the field is refused. */
|
|
11
|
+
export function isJoinedTeam(team: string): boolean {
|
|
12
|
+
try {
|
|
13
|
+
const parsed: unknown = JSON.parse(readFileSync(teamLocalPath(team), "utf8"));
|
|
14
|
+
return typeof parsed === "object" && parsed !== null && (parsed as { joinedByRt?: unknown }).joinedByRt === true;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
}
|
package/src/settings/write.ts
CHANGED
|
@@ -100,6 +100,7 @@ import { dirname } from "path";
|
|
|
100
100
|
import { machineSettingsPath, teamSettingsPath, userSettingsPath } from "./paths.ts";
|
|
101
101
|
import { getDef, isMigrated, validateValue, type SettingDef, type SettingScope } from "./registry-machinery.ts";
|
|
102
102
|
import { listTeams } from "./stores.ts";
|
|
103
|
+
import { isJoinedTeam } from "./team-local-read.ts";
|
|
103
104
|
|
|
104
105
|
export interface SetSettingOpts {
|
|
105
106
|
/** Normalized repo identity — required to target a repoScoped key's `repos.<identity>` section. */
|
|
@@ -208,6 +209,19 @@ function migratedFalseMessage(key: string, def: SettingDef): string {
|
|
|
208
209
|
return `"${key}" is not writable through the settings resolver yet${legacyPart}`;
|
|
209
210
|
}
|
|
210
211
|
|
|
212
|
+
/**
|
|
213
|
+
* A clone that arrived by redeeming an invite is pull-only, so a write here
|
|
214
|
+
* would never reach the team AND would leave a tracked file dirty, which is
|
|
215
|
+
* enough on its own to make the daemon's fast-forward pull fail.
|
|
216
|
+
*/
|
|
217
|
+
function refuseIfJoined(team: string): void {
|
|
218
|
+
if (isJoinedTeam(team)) {
|
|
219
|
+
refuse(
|
|
220
|
+
`this machine joined "${team}" by invite, so its clone is pull-only and team settings cannot be written here. Ask the team's owner to make this change. Member-proposed changes are tracked in MAT-415.`,
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
211
225
|
/** Resolves which store file a write targets, applying the team-selection rule for `scope: "team"`. */
|
|
212
226
|
function resolveStorePath(scope: SettingScope, opts: SetSettingOpts): string {
|
|
213
227
|
if (scope === "user") return userSettingsPath();
|
|
@@ -218,6 +232,7 @@ function resolveStorePath(scope: SettingScope, opts: SetSettingOpts): string {
|
|
|
218
232
|
if (!existsSync(path)) {
|
|
219
233
|
refuse(`team store for "${opts.team}" does not exist (${path}) — clone/seed it before writing to it`);
|
|
220
234
|
}
|
|
235
|
+
refuseIfJoined(opts.team);
|
|
221
236
|
return path;
|
|
222
237
|
}
|
|
223
238
|
|
|
@@ -228,7 +243,9 @@ function resolveStorePath(scope: SettingScope, opts: SetSettingOpts): string {
|
|
|
228
243
|
if (teams.length > 1) {
|
|
229
244
|
refuse(`multiple local team stores found (${teams.join(", ")}) — pass opts.team to choose one`);
|
|
230
245
|
}
|
|
231
|
-
|
|
246
|
+
const team = teams[0] as string;
|
|
247
|
+
refuseIfJoined(team);
|
|
248
|
+
return teamSettingsPath(team);
|
|
232
249
|
}
|
|
233
250
|
|
|
234
251
|
/**
|
|
@@ -243,7 +260,9 @@ function resolveStorePathForUnset(scope: SettingScope, opts: SetSettingOpts): st
|
|
|
243
260
|
|
|
244
261
|
if (opts.team !== undefined) {
|
|
245
262
|
const path = teamSettingsPath(opts.team);
|
|
246
|
-
|
|
263
|
+
if (!existsSync(path)) return null;
|
|
264
|
+
refuseIfJoined(opts.team);
|
|
265
|
+
return path;
|
|
247
266
|
}
|
|
248
267
|
|
|
249
268
|
const teams = listTeams();
|
|
@@ -251,7 +270,9 @@ function resolveStorePathForUnset(scope: SettingScope, opts: SetSettingOpts): st
|
|
|
251
270
|
if (teams.length > 1) {
|
|
252
271
|
refuse(`multiple local team stores found (${teams.join(", ")}) — pass opts.team to choose one`);
|
|
253
272
|
}
|
|
254
|
-
|
|
273
|
+
const team = teams[0] as string;
|
|
274
|
+
refuseIfJoined(team);
|
|
275
|
+
return teamSettingsPath(team);
|
|
255
276
|
}
|
|
256
277
|
|
|
257
278
|
/** `// header comment\n{}\n` — see module doc for why the object must be seeded before the first `modify`. */
|