@mattstack/rt-client 0.19.0 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +4 -1
- package/dist/commands.d.ts +29 -1
- package/dist/index.js +25 -6
- package/package.json +1 -1
- package/src/client.ts +11 -4
- package/src/commands.ts +23 -6
- package/src/pane-ref.ts +5 -0
- package/src/settings/registry-defs.ts +13 -3
package/dist/client.d.ts
CHANGED
|
@@ -218,7 +218,10 @@ export declare function herdAttend(a: Commands["herd:attend"]["payload"], o?: Rt
|
|
|
218
218
|
export declare function herdWrapUp(a: Commands["herd:wrap-up"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:wrap-up"]["data"]>>;
|
|
219
219
|
/** Runs `herdr session stop`, one CLI call under the runner's own 15s budget. */
|
|
220
220
|
export declare function herdStopHidden(_a: Commands["herd:stop-hidden"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["herd:stop-hidden"]["data"]>>;
|
|
221
|
-
/**
|
|
221
|
+
/** A cold ensure spawns `herdr server`, waits out bg-service's own 10s
|
|
222
|
+
readyTimeoutMs, then runs its parity probes sequentially -- the 15s
|
|
223
|
+
default budget every other rt-client call inherits can lose that race,
|
|
224
|
+
so this is the one wrapper that widens its own. */
|
|
222
225
|
export declare function bgEnsure(a?: Commands["bg:ensure"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["bg:ensure"]["data"]>>;
|
|
223
226
|
/** Never ensures/spawns; a plain read of the current state. */
|
|
224
227
|
export declare function bgStatus(o?: RtClientOptions): Promise<RtResponse<Commands["bg:status"]["data"]>>;
|
package/dist/commands.d.ts
CHANGED
|
@@ -128,6 +128,7 @@ export interface GateAnswer {
|
|
|
128
128
|
}>;
|
|
129
129
|
by: string;
|
|
130
130
|
answeredAt: number;
|
|
131
|
+
overridden?: boolean;
|
|
131
132
|
}
|
|
132
133
|
export interface GateRow {
|
|
133
134
|
id: string;
|
|
@@ -143,6 +144,8 @@ export interface GateRow {
|
|
|
143
144
|
parkedAt: number | null;
|
|
144
145
|
closedAt: number | null;
|
|
145
146
|
closedReason: "abandoned" | "superseded" | "pruned" | null;
|
|
147
|
+
/** Set only when `closedReason` is "superseded": the id of the gate that superseded this one. */
|
|
148
|
+
supersededBy: string | null;
|
|
146
149
|
agent: string | null;
|
|
147
150
|
pane: string | null;
|
|
148
151
|
nudge: {
|
|
@@ -153,6 +156,8 @@ export interface GateRow {
|
|
|
153
156
|
at: number;
|
|
154
157
|
} | null;
|
|
155
158
|
released: boolean;
|
|
159
|
+
owner: string | null;
|
|
160
|
+
escalatedAt: number | null;
|
|
156
161
|
}
|
|
157
162
|
export interface GateSubscription {
|
|
158
163
|
id: string;
|
|
@@ -164,6 +169,8 @@ export interface GateSubscription {
|
|
|
164
169
|
at: number;
|
|
165
170
|
} | null;
|
|
166
171
|
dead: boolean;
|
|
172
|
+
scope: "prefix" | "owner";
|
|
173
|
+
ownerRef: string | null;
|
|
167
174
|
}
|
|
168
175
|
export interface HerdInfo {
|
|
169
176
|
id: string;
|
|
@@ -217,6 +224,11 @@ export interface HerdStatusData {
|
|
|
217
224
|
dead: boolean;
|
|
218
225
|
lastDelivery: GateSubscription["lastDelivery"];
|
|
219
226
|
} | null;
|
|
227
|
+
/** Whether the shepherd session's own inbox socket is reachable right now, probed fresh on every status call -- honest liveness, not the subscription row's bookkeeping. */
|
|
228
|
+
push: {
|
|
229
|
+
state: "reachable" | "unreachable";
|
|
230
|
+
lastDelivery: GateSubscription["lastDelivery"];
|
|
231
|
+
};
|
|
220
232
|
}
|
|
221
233
|
/**
|
|
222
234
|
* Duplicated shape on purpose, same reasoning as EventsBusEvent above:
|
|
@@ -616,9 +628,12 @@ export interface WorktreeCreateData {
|
|
|
616
628
|
}
|
|
617
629
|
export interface WorktreeDisposeData {
|
|
618
630
|
disposed: string[];
|
|
631
|
+
/** `detail` is set only for a refusal whose bare `reason` code can't name
|
|
632
|
+
what a human needs to act on it (the run id and stage for `running-run`). */
|
|
619
633
|
refused: Array<{
|
|
620
634
|
tree: string;
|
|
621
635
|
reason: string;
|
|
636
|
+
detail?: string;
|
|
622
637
|
}>;
|
|
623
638
|
recoverable: Array<{
|
|
624
639
|
tree: string;
|
|
@@ -644,6 +659,7 @@ export interface WorktreeAdoptData {
|
|
|
644
659
|
refused: Array<{
|
|
645
660
|
tree: string;
|
|
646
661
|
reason: string;
|
|
662
|
+
detail?: string;
|
|
647
663
|
}>;
|
|
648
664
|
}
|
|
649
665
|
/** Duplicated shape on purpose: mirrors lib/endpoint/store.ts's EndpointClaim. */
|
|
@@ -1368,12 +1384,21 @@ export interface Commands {
|
|
|
1368
1384
|
* `conflict:true` and the WINNING row, so every consumer gets the winner
|
|
1369
1385
|
* typed with no envelope hacks. `ok:false` is reserved for
|
|
1370
1386
|
* not-found/closed/validation failures.
|
|
1387
|
+
*
|
|
1388
|
+
* Owner enforcement adds two structured rejections beyond the plain
|
|
1389
|
+
* `{ok:false, error:string}` shape (see `GateAnswerResult` in
|
|
1390
|
+
* `lib/daemon/handlers/gate.ts`): a herd-owned gate answered by anyone but
|
|
1391
|
+
* the owning shepherd's session, the answering pane, or an explicit human
|
|
1392
|
+
* `override` returns `{ok:false, error:"owned-by", owner}`; a closed gate
|
|
1393
|
+
* returns `{ok:false, error:"gate-closed", reason, supersededBy?}`.
|
|
1371
1394
|
*/
|
|
1372
1395
|
"gate:answer": {
|
|
1373
1396
|
payload: {
|
|
1374
1397
|
id: string;
|
|
1375
1398
|
answers: GateAnswer["answers"];
|
|
1376
1399
|
by: string;
|
|
1400
|
+
session?: string;
|
|
1401
|
+
override?: boolean;
|
|
1377
1402
|
};
|
|
1378
1403
|
data: {
|
|
1379
1404
|
row: GateRow;
|
|
@@ -1430,6 +1455,8 @@ export interface Commands {
|
|
|
1430
1455
|
payload: {
|
|
1431
1456
|
subjectPrefix: string;
|
|
1432
1457
|
session: string;
|
|
1458
|
+
scope?: "owner";
|
|
1459
|
+
ownerRef?: string;
|
|
1433
1460
|
};
|
|
1434
1461
|
data: {
|
|
1435
1462
|
id: string;
|
|
@@ -1505,7 +1532,8 @@ export interface Commands {
|
|
|
1505
1532
|
};
|
|
1506
1533
|
data: {
|
|
1507
1534
|
job: string;
|
|
1508
|
-
status: "closed";
|
|
1535
|
+
status: "closed"; /** Advisory: a resumable run can still write into this job's worktree after close, so this warns rather than blocking. */
|
|
1536
|
+
warning?: string;
|
|
1509
1537
|
};
|
|
1510
1538
|
};
|
|
1511
1539
|
/** `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. */
|
package/dist/index.js
CHANGED
|
@@ -281,7 +281,11 @@ function gateOpen(a, o = {}) {
|
|
|
281
281
|
return rtCommand("gate:open", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
282
282
|
}
|
|
283
283
|
function gateAnswer(a, o = {}) {
|
|
284
|
-
|
|
284
|
+
const payload = { id: a.id, answers: a.answers, by: a.by };
|
|
285
|
+
for (const k of ["session", "override"])
|
|
286
|
+
if (a[k] !== undefined)
|
|
287
|
+
payload[k] = a[k];
|
|
288
|
+
return rtCommand("gate:answer", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
285
289
|
}
|
|
286
290
|
function gateWait(a, o = {}) {
|
|
287
291
|
const payload = { id: a.id };
|
|
@@ -303,7 +307,11 @@ function gateClose(a, o = {}) {
|
|
|
303
307
|
return rtCommand("gate:close", { id: a.id, reason: a.reason }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
304
308
|
}
|
|
305
309
|
function gateSubscribe(a, o = {}) {
|
|
306
|
-
|
|
310
|
+
const payload = { subjectPrefix: a.subjectPrefix, session: a.session };
|
|
311
|
+
for (const k of ["scope", "ownerRef"])
|
|
312
|
+
if (a[k] !== undefined)
|
|
313
|
+
payload[k] = a[k];
|
|
314
|
+
return rtCommand("gate:subscribe", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
307
315
|
}
|
|
308
316
|
function gateUnsubscribe(a, o = {}) {
|
|
309
317
|
return rtCommand("gate:unsubscribe", { id: a.id }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
@@ -383,7 +391,7 @@ function bgEnsure(a = {}, o = {}) {
|
|
|
383
391
|
const payload = {};
|
|
384
392
|
if (a.claim !== undefined)
|
|
385
393
|
payload.claim = a.claim;
|
|
386
|
-
return rtCommand("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ??
|
|
394
|
+
return rtCommand("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
387
395
|
}
|
|
388
396
|
function bgStatus(o = {}) {
|
|
389
397
|
return rtCommand("bg:status", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
@@ -705,6 +713,9 @@ function formatPaneRef(paneId, server) {
|
|
|
705
713
|
if (server === "visible") {
|
|
706
714
|
return paneId;
|
|
707
715
|
}
|
|
716
|
+
if (paneId.startsWith(BG_PREFIX)) {
|
|
717
|
+
return paneId;
|
|
718
|
+
}
|
|
708
719
|
return BG_PREFIX + paneId;
|
|
709
720
|
}
|
|
710
721
|
// src/settings/resolve.ts
|
|
@@ -777,7 +788,7 @@ var REGISTRY = [
|
|
|
777
788
|
merge: "deep",
|
|
778
789
|
repoScoped: true,
|
|
779
790
|
migrated: true,
|
|
780
|
-
description: "Per-repo worktree pool config (onDeck size, ready steps, name pool); root/branchFormat/ready computed-or-empty in the reader."
|
|
791
|
+
description: "Per-repo worktree pool config (onDeck size, ready steps, name pool, staleClaimDays); root/branchFormat/ready computed-or-empty in the reader."
|
|
781
792
|
},
|
|
782
793
|
{
|
|
783
794
|
key: "rt.worktreeReadyApproval",
|
|
@@ -819,7 +830,7 @@ var REGISTRY = [
|
|
|
819
830
|
scopes: ["user"],
|
|
820
831
|
default: [],
|
|
821
832
|
merge: "replace",
|
|
822
|
-
description:
|
|
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.'
|
|
823
834
|
},
|
|
824
835
|
{
|
|
825
836
|
key: "rt.cron",
|
|
@@ -1138,7 +1149,7 @@ var REGISTRY = [
|
|
|
1138
1149
|
type: "array",
|
|
1139
1150
|
scopes: ["team"],
|
|
1140
1151
|
merge: "replace",
|
|
1141
|
-
description: "Board tabs ({id, label, source, slackChannel?, reviewSkill?}), editable from the board's settings modal. source.kind 'authors' is the classic roster board; 'codeowners' lists MRs
|
|
1152
|
+
description: "Board tabs ({id, label, source, slackChannel?, reviewSkill?}), editable from the board's settings modal. source.kind 'authors' is the classic roster board; 'codeowners' lists any author's open MRs carrying a CODE_OWNER rule for the section, until merge/close. Absent = one implicit authors tab (fallback lives in the board reader, never here)."
|
|
1142
1153
|
},
|
|
1143
1154
|
{
|
|
1144
1155
|
key: "board.staleAfterDays",
|
|
@@ -1381,6 +1392,14 @@ var REGISTRY = [
|
|
|
1381
1392
|
scopes: ["user", "machine"],
|
|
1382
1393
|
merge: "replace",
|
|
1383
1394
|
description: "Opaque extra claude arguments appended to every rt agent launch (escape hatch)."
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
key: "rt.gates.escalationTtlMinutes",
|
|
1398
|
+
type: "number",
|
|
1399
|
+
scopes: ["user"],
|
|
1400
|
+
default: 10,
|
|
1401
|
+
merge: "replace",
|
|
1402
|
+
description: `Minutes an open herd-owned gate waits before the escalation sweep surfaces it to the human (topic gate/escalated/<id>). Fires on either trigger: the TTL elapses (reason "ttl"), or the owning herd's shepherd subscription is gone or dead before the TTL (reason "owner-dead"). 0 escalates any eligible gate on the first sweep after it opens. A fresh key, not an ownership-latch port, so a default is fine here.`
|
|
1384
1403
|
}
|
|
1385
1404
|
];
|
|
1386
1405
|
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -461,7 +461,9 @@ export function gateAnswer(
|
|
|
461
461
|
a: Commands["gate:answer"]["payload"],
|
|
462
462
|
o: RtClientOptions = {},
|
|
463
463
|
): Promise<RtResponse<Commands["gate:answer"]["data"]>> {
|
|
464
|
-
|
|
464
|
+
const payload: Record<string, unknown> = { id: a.id, answers: a.answers, by: a.by };
|
|
465
|
+
for (const k of ["session", "override"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
466
|
+
return rtCommand<Commands["gate:answer"]["data"]>("gate:answer", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
465
467
|
}
|
|
466
468
|
|
|
467
469
|
/** Daemon clamps its own wait to 240s (gates-store.ts); the client abort
|
|
@@ -503,7 +505,9 @@ export function gateSubscribe(
|
|
|
503
505
|
a: Commands["gate:subscribe"]["payload"],
|
|
504
506
|
o: RtClientOptions = {},
|
|
505
507
|
): Promise<RtResponse<Commands["gate:subscribe"]["data"]>> {
|
|
506
|
-
|
|
508
|
+
const payload: Record<string, unknown> = { subjectPrefix: a.subjectPrefix, session: a.session };
|
|
509
|
+
for (const k of ["scope", "ownerRef"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
510
|
+
return rtCommand<Commands["gate:subscribe"]["data"]>("gate:subscribe", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
507
511
|
}
|
|
508
512
|
|
|
509
513
|
export function gateUnsubscribe(
|
|
@@ -645,14 +649,17 @@ export function herdStopHidden(
|
|
|
645
649
|
|
|
646
650
|
// ─── Background server (daemon-owned background herdr session) ────────────
|
|
647
651
|
|
|
648
|
-
/**
|
|
652
|
+
/** A cold ensure spawns `herdr server`, waits out bg-service's own 10s
|
|
653
|
+
readyTimeoutMs, then runs its parity probes sequentially -- the 15s
|
|
654
|
+
default budget every other rt-client call inherits can lose that race,
|
|
655
|
+
so this is the one wrapper that widens its own. */
|
|
649
656
|
export function bgEnsure(
|
|
650
657
|
a: Commands["bg:ensure"]["payload"] = {},
|
|
651
658
|
o: RtClientOptions = {},
|
|
652
659
|
): Promise<RtResponse<Commands["bg:ensure"]["data"]>> {
|
|
653
660
|
const payload: Record<string, unknown> = {};
|
|
654
661
|
if (a.claim !== undefined) payload.claim = a.claim;
|
|
655
|
-
return rtCommand<Commands["bg:ensure"]["data"]>("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ??
|
|
662
|
+
return rtCommand<Commands["bg:ensure"]["data"]>("bg:ensure", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30_000 });
|
|
656
663
|
}
|
|
657
664
|
|
|
658
665
|
/** Never ensures/spawns; a plain read of the current state. */
|
package/src/commands.ts
CHANGED
|
@@ -110,7 +110,7 @@ export function gateOptionValue(o: GateOption): string {
|
|
|
110
110
|
export function gateOptionLabel(o: GateOption): string {
|
|
111
111
|
return typeof o === "string" ? o : (o.label || o.value);
|
|
112
112
|
}
|
|
113
|
-
export interface GateAnswer { answers: Record<string, string | string[] | { value: string | string[]; note?: string }>; by: string; answeredAt: number }
|
|
113
|
+
export interface GateAnswer { answers: Record<string, string | string[] | { value: string | string[]; note?: string }>; by: string; answeredAt: number; overridden?: boolean }
|
|
114
114
|
export interface GateRow {
|
|
115
115
|
id: string; subject: string; kind: string;
|
|
116
116
|
questions: GateQuestion[]; meta: Record<string, unknown> | null;
|
|
@@ -119,10 +119,14 @@ export interface GateRow {
|
|
|
119
119
|
status: GateStatus; answer: GateAnswer | null;
|
|
120
120
|
openedAt: number; parkedAt: number | null; closedAt: number | null;
|
|
121
121
|
closedReason: "abandoned" | "superseded" | "pruned" | null;
|
|
122
|
+
/** Set only when `closedReason` is "superseded": the id of the gate that superseded this one. */
|
|
123
|
+
supersededBy: string | null;
|
|
122
124
|
agent: string | null; pane: string | null;
|
|
123
125
|
nudge: { session: string } | null;
|
|
124
126
|
delivery: { outcome: "delivered" | "dead-pane"; at: number } | null;
|
|
125
127
|
released: boolean;
|
|
128
|
+
owner: string | null;
|
|
129
|
+
escalatedAt: number | null;
|
|
126
130
|
}
|
|
127
131
|
|
|
128
132
|
export interface GateSubscription {
|
|
@@ -132,6 +136,8 @@ export interface GateSubscription {
|
|
|
132
136
|
createdAt: number;
|
|
133
137
|
lastDelivery: { outcome: "delivered" | "failed"; at: number } | null;
|
|
134
138
|
dead: boolean;
|
|
139
|
+
scope: "prefix" | "owner";
|
|
140
|
+
ownerRef: string | null;
|
|
135
141
|
}
|
|
136
142
|
|
|
137
143
|
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 }
|
|
@@ -147,6 +153,8 @@ export interface HerdStatusData {
|
|
|
147
153
|
hiddenUp: boolean | null;
|
|
148
154
|
/** The shepherd session's own `herd:<id>/` subscription row, or null when none is live. */
|
|
149
155
|
subscription: { id: string; dead: boolean; lastDelivery: GateSubscription["lastDelivery"] } | null;
|
|
156
|
+
/** Whether the shepherd session's own inbox socket is reachable right now, probed fresh on every status call -- honest liveness, not the subscription row's bookkeeping. */
|
|
157
|
+
push: { state: "reachable" | "unreachable"; lastDelivery: GateSubscription["lastDelivery"] };
|
|
150
158
|
}
|
|
151
159
|
|
|
152
160
|
/**
|
|
@@ -405,7 +413,9 @@ export interface WorktreeProvisionData {
|
|
|
405
413
|
export interface WorktreeCreateData { tree: string; path: string }
|
|
406
414
|
export interface WorktreeDisposeData {
|
|
407
415
|
disposed: string[];
|
|
408
|
-
|
|
416
|
+
/** `detail` is set only for a refusal whose bare `reason` code can't name
|
|
417
|
+
what a human needs to act on it (the run id and stage for `running-run`). */
|
|
418
|
+
refused: Array<{ tree: string; reason: string; detail?: string }>;
|
|
409
419
|
recoverable: Array<{ tree: string; path: string; until: string }>;
|
|
410
420
|
}
|
|
411
421
|
export interface WorktreeRestoreData {
|
|
@@ -414,7 +424,7 @@ export interface WorktreeRestoreData {
|
|
|
414
424
|
export interface WorktreeFreshenData { ran: string[] }
|
|
415
425
|
export interface WorktreeAdoptData {
|
|
416
426
|
main: string; claimed: string[]; unmanaged: string[]; disposed: string[];
|
|
417
|
-
refused: Array<{ tree: string; reason: string }>;
|
|
427
|
+
refused: Array<{ tree: string; reason: string; detail?: string }>;
|
|
418
428
|
}
|
|
419
429
|
|
|
420
430
|
/** Duplicated shape on purpose: mirrors lib/endpoint/store.ts's EndpointClaim. */
|
|
@@ -616,8 +626,15 @@ export interface Commands {
|
|
|
616
626
|
* `conflict:true` and the WINNING row, so every consumer gets the winner
|
|
617
627
|
* typed with no envelope hacks. `ok:false` is reserved for
|
|
618
628
|
* not-found/closed/validation failures.
|
|
629
|
+
*
|
|
630
|
+
* Owner enforcement adds two structured rejections beyond the plain
|
|
631
|
+
* `{ok:false, error:string}` shape (see `GateAnswerResult` in
|
|
632
|
+
* `lib/daemon/handlers/gate.ts`): a herd-owned gate answered by anyone but
|
|
633
|
+
* the owning shepherd's session, the answering pane, or an explicit human
|
|
634
|
+
* `override` returns `{ok:false, error:"owned-by", owner}`; a closed gate
|
|
635
|
+
* returns `{ok:false, error:"gate-closed", reason, supersededBy?}`.
|
|
619
636
|
*/
|
|
620
|
-
"gate:answer": { payload: { id: string; answers: GateAnswer["answers"]; by: string }; data: { row: GateRow; conflict?: true } };
|
|
637
|
+
"gate:answer": { payload: { id: string; answers: GateAnswer["answers"]; by: string; session?: string; override?: boolean }; data: { row: GateRow; conflict?: true } };
|
|
621
638
|
/** `ok:false "not-found"` on an unknown id is terminal; the CLI loop must not re-enter on it.
|
|
622
639
|
* `timeout` carries no row (nothing settled); `answered`/`closed` always carry the settled row. */
|
|
623
640
|
"gate:wait": { payload: { id: string; waitMs?: number }; data: { status: "timeout" } | { status: "answered" | "closed"; row: GateRow } };
|
|
@@ -626,7 +643,7 @@ export interface Commands {
|
|
|
626
643
|
"gate:list": { payload: { open?: boolean; subjectPrefix?: string; kind?: string; limit?: number; cursor?: number }; data: { gates: GateRow[]; cursor: number } };
|
|
627
644
|
"gate:park": { payload: { id: string }; data: { ok: true } };
|
|
628
645
|
"gate:close": { payload: { id: string; reason: "abandoned" | "superseded" | "pruned" }; data: { ok: true } };
|
|
629
|
-
"gate:subscribe": { payload: { subjectPrefix: string; session: string }; data: { id: string } };
|
|
646
|
+
"gate:subscribe": { payload: { subjectPrefix: string; session: string; scope?: "owner"; ownerRef?: string }; data: { id: string } };
|
|
630
647
|
"gate:unsubscribe": { payload: { id: string }; data: { removed: boolean } };
|
|
631
648
|
/** The shepherd's gap-recovery liveness check and the observability window
|
|
632
649
|
* onto delivery outcomes (dead marks included). */
|
|
@@ -638,7 +655,7 @@ export interface Commands {
|
|
|
638
655
|
"herd:status": { payload: { herd: string }; data: HerdStatusData };
|
|
639
656
|
/** Active herds only unless `all`, so a shepherd's "which herd am I on" question has one answer. */
|
|
640
657
|
"herd:list": { payload: { all?: boolean }; data: { herds: HerdListRow[] } };
|
|
641
|
-
"herd:close": { payload: { herd: string; job: string }; data: { job: string; status: "closed" } };
|
|
658
|
+
"herd:close": { payload: { herd: string; job: string }; data: { job: string; status: "closed"; /** Advisory: a resumable run can still write into this job's worktree after close, so this warns rather than blocking. */ warning?: string } };
|
|
642
659
|
/** `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
660
|
"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
661
|
"herd:gates": { payload: { herd: string }; data: { gates: GateRow[] } };
|
package/src/pane-ref.ts
CHANGED
|
@@ -24,5 +24,10 @@ export function formatPaneRef(paneId: string, server: PaneServer): string {
|
|
|
24
24
|
if (server === "visible") {
|
|
25
25
|
return paneId;
|
|
26
26
|
}
|
|
27
|
+
// Idempotent: herdr pane ids (w<N>:p<N>) can never start with "bg:", so an
|
|
28
|
+
// already-formatted ref passes through unchanged instead of nesting.
|
|
29
|
+
if (paneId.startsWith(BG_PREFIX)) {
|
|
30
|
+
return paneId;
|
|
31
|
+
}
|
|
27
32
|
return BG_PREFIX + paneId;
|
|
28
33
|
}
|
|
@@ -42,7 +42,7 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
42
42
|
merge: "deep",
|
|
43
43
|
repoScoped: true,
|
|
44
44
|
migrated: true,
|
|
45
|
-
description: "Per-repo worktree pool config (onDeck size, ready steps, name pool); root/branchFormat/ready computed-or-empty in the reader.",
|
|
45
|
+
description: "Per-repo worktree pool config (onDeck size, ready steps, name pool, staleClaimDays); root/branchFormat/ready computed-or-empty in the reader.",
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
key: "rt.worktreeReadyApproval",
|
|
@@ -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?}]. 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.",
|
|
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.",
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
key: "rt.cron",
|
|
@@ -427,7 +427,7 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
427
427
|
type: "array",
|
|
428
428
|
scopes: ["team"],
|
|
429
429
|
merge: "replace",
|
|
430
|
-
description: "Board tabs ({id, label, source, slackChannel?, reviewSkill?}), editable from the board's settings modal. source.kind 'authors' is the classic roster board; 'codeowners' lists MRs
|
|
430
|
+
description: "Board tabs ({id, label, source, slackChannel?, reviewSkill?}), editable from the board's settings modal. source.kind 'authors' is the classic roster board; 'codeowners' lists any author's open MRs carrying a CODE_OWNER rule for the section, until merge/close. Absent = one implicit authors tab (fallback lives in the board reader, never here).",
|
|
431
431
|
},
|
|
432
432
|
|
|
433
433
|
// --- board (user) ----------------------------------------------------------
|
|
@@ -687,4 +687,14 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
687
687
|
merge: "replace",
|
|
688
688
|
description: "Opaque extra claude arguments appended to every rt agent launch (escape hatch).",
|
|
689
689
|
},
|
|
690
|
+
|
|
691
|
+
// --- gates (escalation) ----------------------------------------------------
|
|
692
|
+
{
|
|
693
|
+
key: "rt.gates.escalationTtlMinutes",
|
|
694
|
+
type: "number",
|
|
695
|
+
scopes: ["user"],
|
|
696
|
+
default: 10,
|
|
697
|
+
merge: "replace",
|
|
698
|
+
description: "Minutes an open herd-owned gate waits before the escalation sweep surfaces it to the human (topic gate/escalated/<id>). Fires on either trigger: the TTL elapses (reason \"ttl\"), or the owning herd's shepherd subscription is gone or dead before the TTL (reason \"owner-dead\"). 0 escalates any eligible gate on the first sweep after it opens. A fresh key, not an ownership-latch port, so a default is fine here.",
|
|
699
|
+
},
|
|
690
700
|
];
|