@mattstack/rt-client 0.26.0 → 0.28.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/commands.d.ts +24 -6
- package/dist/gate-options.d.ts +11 -3
- package/dist/gate.js +9 -2
- package/dist/index.js +87 -3
- package/package.json +1 -1
- package/src/client.ts +4 -1
- package/src/commands.ts +17 -9
- package/src/gate-options.ts +19 -5
- package/src/settings/registry-defs.ts +74 -0
package/dist/commands.d.ts
CHANGED
|
@@ -108,6 +108,7 @@ export type GateOption = string | {
|
|
|
108
108
|
value: string;
|
|
109
109
|
label: string;
|
|
110
110
|
recommended?: boolean;
|
|
111
|
+
description?: string;
|
|
111
112
|
};
|
|
112
113
|
export interface GateOrigin {
|
|
113
114
|
paneId?: string;
|
|
@@ -117,11 +118,14 @@ export interface GateOrigin {
|
|
|
117
118
|
surface?: string;
|
|
118
119
|
presentation?: "form" | "wait";
|
|
119
120
|
}
|
|
121
|
+
/** `context` is per-question material (what this one choice turns on);
|
|
122
|
+
the gate-level `context` on the open/ask payload is the whole ask's. */
|
|
120
123
|
export interface GateQuestion {
|
|
121
124
|
id: string;
|
|
122
125
|
label: string;
|
|
123
126
|
multi: boolean;
|
|
124
127
|
options: GateOption[];
|
|
128
|
+
context?: string;
|
|
125
129
|
}
|
|
126
130
|
/** Implementations live in gate-options.ts (the browser-safe ./gate
|
|
127
131
|
subpath); re-exported here so existing commands.ts/index.ts consumers
|
|
@@ -243,7 +247,11 @@ export interface HerdStatusData {
|
|
|
243
247
|
sessionDead: boolean | null;
|
|
244
248
|
lastGateStatus: GateStatus | null;
|
|
245
249
|
lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null;
|
|
246
|
-
lastGateConsumed: boolean | null;
|
|
250
|
+
lastGateConsumed: boolean | null; /** The daemon watchdog's escalation ladder for this job: strikes so far and when it last acted. Absent when the job is not on the ladder (healthy, no watchdog wired, or the daemon restarted since). */
|
|
251
|
+
watchdog?: {
|
|
252
|
+
strikes: number;
|
|
253
|
+
lastPokeAt: number | null;
|
|
254
|
+
};
|
|
247
255
|
}>;
|
|
248
256
|
unread: number;
|
|
249
257
|
lifecycleConnected: boolean;
|
|
@@ -482,6 +490,10 @@ export interface ReconcilerStatus {
|
|
|
482
490
|
herdrReachable: boolean;
|
|
483
491
|
executors: ExecutorView[];
|
|
484
492
|
}
|
|
493
|
+
/** What a claude spawn's folder-trust check established, when one ran:
|
|
494
|
+
`none` = no dialog was up, `accepted` = one was answered, `stuck` = one is
|
|
495
|
+
still up and needs a human, `unchecked` = nothing could be established. */
|
|
496
|
+
export type TrustOutcome = "none" | "accepted" | "stuck" | "unchecked";
|
|
485
497
|
export interface AgentRecord {
|
|
486
498
|
id: string;
|
|
487
499
|
repo: string;
|
|
@@ -1156,9 +1168,12 @@ export interface Commands {
|
|
|
1156
1168
|
handle?: string;
|
|
1157
1169
|
bg?: boolean;
|
|
1158
1170
|
subject?: string;
|
|
1159
|
-
yolo?: boolean;
|
|
1171
|
+
yolo?: boolean; /** How long the folder-trust check lets the dialog paint before reading the screen; interactive launches keep the short default, herd:spawn passes its own longer budget. */
|
|
1172
|
+
trustWaitMs?: number;
|
|
1173
|
+
};
|
|
1174
|
+
data: AgentRecord & {
|
|
1175
|
+
trust?: TrustOutcome;
|
|
1160
1176
|
};
|
|
1161
|
-
data: AgentRecord;
|
|
1162
1177
|
};
|
|
1163
1178
|
"agent:resume": {
|
|
1164
1179
|
payload: {
|
|
@@ -1506,9 +1521,10 @@ export interface Commands {
|
|
|
1506
1521
|
worktree?: string;
|
|
1507
1522
|
};
|
|
1508
1523
|
};
|
|
1509
|
-
/** `contextOmitted` appears only when the
|
|
1510
|
-
8192-byte
|
|
1511
|
-
|
|
1524
|
+
/** `contextOmitted` appears only when the gate context plus every
|
|
1525
|
+
question's `context` exceeded their shared 8192-byte budget: the
|
|
1526
|
+
gate still opened, but question contexts were dropped, and the gate
|
|
1527
|
+
context too when it was over the budget on its own. */
|
|
1512
1528
|
data: {
|
|
1513
1529
|
id: string;
|
|
1514
1530
|
presentation: "form" | "wait";
|
|
@@ -1629,6 +1645,7 @@ export interface Commands {
|
|
|
1629
1645
|
repo: string;
|
|
1630
1646
|
session: string;
|
|
1631
1647
|
hidden?: boolean;
|
|
1648
|
+
callerPane?: string;
|
|
1632
1649
|
};
|
|
1633
1650
|
data: {
|
|
1634
1651
|
herd: string;
|
|
@@ -1643,6 +1660,7 @@ export interface Commands {
|
|
|
1643
1660
|
payload: {
|
|
1644
1661
|
herd: string;
|
|
1645
1662
|
session: string;
|
|
1663
|
+
callerPane?: string;
|
|
1646
1664
|
};
|
|
1647
1665
|
data: {
|
|
1648
1666
|
subscription: string;
|
package/dist/gate-options.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ import type { GateOption, GateQuestion } from "./commands.ts";
|
|
|
5
5
|
export interface GateOptionObject {
|
|
6
6
|
value: string;
|
|
7
7
|
label: string;
|
|
8
|
+
/** Per-option explanation, carried verbatim: never capitalized, never
|
|
9
|
+
suffixed, never filled in from value/label. */
|
|
10
|
+
description?: string;
|
|
8
11
|
}
|
|
9
12
|
export declare function gateOptionValue(o: GateOption): string;
|
|
10
13
|
export declare function gateOptionLabel(o: GateOption): string;
|
|
@@ -17,8 +20,13 @@ export declare function gateOptionLabel(o: GateOption): string;
|
|
|
17
20
|
word-like (see isWordLikeLabel), and an object form's `recommended:
|
|
18
21
|
true` lifts into a " (Recommended)" label suffix -- guarded against
|
|
19
22
|
double-appending -- since `recommended` itself does not survive into
|
|
20
|
-
the returned object; the suffix IS its wire representation.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
the returned object; the suffix IS its wire representation. An object
|
|
24
|
+
form's non-empty string `description` rides along untouched (no
|
|
25
|
+
capitalization, no suffix); any other description shape is dropped.
|
|
26
|
+
Every returned entry is a full {value,label} pair -- callers may trust
|
|
27
|
+
the return type without re-checking it. Pure and order-preserving. */
|
|
23
28
|
export declare function normalizeGateOptions(options: GateOption[]): GateOptionObject[];
|
|
29
|
+
/** Options normalize per normalizeGateOptions; a question's `context` is
|
|
30
|
+
kept verbatim when it is a non-blank string and dropped otherwise, so a
|
|
31
|
+
stored row never carries an empty or non-string context key. */
|
|
24
32
|
export declare function normalizeGateQuestions(questions: GateQuestion[]): GateQuestion[];
|
package/dist/gate.js
CHANGED
|
@@ -33,11 +33,18 @@ function normalizeGateOptions(options) {
|
|
|
33
33
|
label = capitalize(label);
|
|
34
34
|
if (recommended && label && !HAS_RECOMMENDED_SUFFIX.test(label))
|
|
35
35
|
label += RECOMMENDED_SUFFIX;
|
|
36
|
-
|
|
36
|
+
const d = o !== null && typeof o === "object" ? o.description : undefined;
|
|
37
|
+
return typeof d === "string" && d ? { value, label, description: d } : { value, label };
|
|
37
38
|
});
|
|
38
39
|
}
|
|
39
40
|
function normalizeGateQuestions(questions) {
|
|
40
|
-
return questions.map((q) =>
|
|
41
|
+
return questions.map((q) => {
|
|
42
|
+
const { context, ...rest } = q;
|
|
43
|
+
const out = { ...rest, options: normalizeGateOptions(q.options) };
|
|
44
|
+
if (typeof context === "string" && context.trim())
|
|
45
|
+
out.context = context;
|
|
46
|
+
return out;
|
|
47
|
+
});
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
// src/gate-answers.ts
|
package/dist/index.js
CHANGED
|
@@ -382,6 +382,8 @@ function herdStart(a, o = {}) {
|
|
|
382
382
|
const payload = { name: a.name, repo: a.repo, session: a.session };
|
|
383
383
|
if (a.hidden !== undefined)
|
|
384
384
|
payload.hidden = a.hidden;
|
|
385
|
+
if (a.callerPane !== undefined)
|
|
386
|
+
payload.callerPane = a.callerPane;
|
|
385
387
|
return rtCommand("herd:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60000 });
|
|
386
388
|
}
|
|
387
389
|
function herdSpawn(a, o = {}) {
|
|
@@ -425,7 +427,10 @@ function herdList(a = {}, o = {}) {
|
|
|
425
427
|
return rtCommand("herd:list", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
426
428
|
}
|
|
427
429
|
function herdResume(a, o = {}) {
|
|
428
|
-
|
|
430
|
+
const payload = { herd: a.herd, session: a.session };
|
|
431
|
+
if (a.callerPane !== undefined)
|
|
432
|
+
payload.callerPane = a.callerPane;
|
|
433
|
+
return rtCommand("herd:resume", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
429
434
|
}
|
|
430
435
|
function herdClose(a, o = {}) {
|
|
431
436
|
return rtCommand("herd:close", { herd: a.herd, job: a.job }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 30000 });
|
|
@@ -493,11 +498,18 @@ function normalizeGateOptions(options) {
|
|
|
493
498
|
label = capitalize(label);
|
|
494
499
|
if (recommended && label && !HAS_RECOMMENDED_SUFFIX.test(label))
|
|
495
500
|
label += RECOMMENDED_SUFFIX;
|
|
496
|
-
|
|
501
|
+
const d = o !== null && typeof o === "object" ? o.description : undefined;
|
|
502
|
+
return typeof d === "string" && d ? { value, label, description: d } : { value, label };
|
|
497
503
|
});
|
|
498
504
|
}
|
|
499
505
|
function normalizeGateQuestions(questions) {
|
|
500
|
-
return questions.map((q) =>
|
|
506
|
+
return questions.map((q) => {
|
|
507
|
+
const { context, ...rest } = q;
|
|
508
|
+
const out = { ...rest, options: normalizeGateOptions(q.options) };
|
|
509
|
+
if (typeof context === "string" && context.trim())
|
|
510
|
+
out.context = context;
|
|
511
|
+
return out;
|
|
512
|
+
});
|
|
501
513
|
}
|
|
502
514
|
|
|
503
515
|
// src/commands.ts
|
|
@@ -1598,6 +1610,78 @@ var REGISTRY = [
|
|
|
1598
1610
|
default: 10,
|
|
1599
1611
|
merge: "replace",
|
|
1600
1612
|
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.`
|
|
1613
|
+
},
|
|
1614
|
+
{
|
|
1615
|
+
key: "herd.watchdog.enabled",
|
|
1616
|
+
type: "boolean",
|
|
1617
|
+
scopes: ["machine"],
|
|
1618
|
+
default: true,
|
|
1619
|
+
merge: "replace",
|
|
1620
|
+
description: "Turns the herd watchdog sweep on."
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
key: "herd.watchdog.fastMins",
|
|
1624
|
+
type: "number",
|
|
1625
|
+
scopes: ["machine"],
|
|
1626
|
+
default: 2,
|
|
1627
|
+
merge: "replace",
|
|
1628
|
+
description: "Idle minutes before a worker with a pending DM, mention, or answered gate is poked."
|
|
1629
|
+
},
|
|
1630
|
+
{
|
|
1631
|
+
key: "herd.watchdog.shepherdFastMins",
|
|
1632
|
+
type: "number",
|
|
1633
|
+
scopes: ["machine"],
|
|
1634
|
+
default: 5,
|
|
1635
|
+
merge: "replace",
|
|
1636
|
+
description: "Minutes an open human-owned gate in the herd may stay unanswered before the shepherd is poked. Unread DMs and mentions for the shepherd wedge immediately and are not delayed by this key."
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
key: "herd.watchdog.backstopMins",
|
|
1640
|
+
type: "number",
|
|
1641
|
+
scopes: ["machine"],
|
|
1642
|
+
default: 15,
|
|
1643
|
+
merge: "replace",
|
|
1644
|
+
description: "Idle minutes before any non-gated active worker is poked."
|
|
1645
|
+
},
|
|
1646
|
+
{
|
|
1647
|
+
key: "herd.watchdog.retryMins",
|
|
1648
|
+
type: "number",
|
|
1649
|
+
scopes: ["machine"],
|
|
1650
|
+
default: 5,
|
|
1651
|
+
merge: "replace",
|
|
1652
|
+
description: "Minutes between escalation strikes while the watchdog keeps poking an unresponsive worker."
|
|
1653
|
+
},
|
|
1654
|
+
{
|
|
1655
|
+
key: "herd.watchdog.notifyQuietMins",
|
|
1656
|
+
type: "number",
|
|
1657
|
+
scopes: ["machine"],
|
|
1658
|
+
default: 30,
|
|
1659
|
+
merge: "replace",
|
|
1660
|
+
description: "Minimum minutes between macOS notifications for the same herd."
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
key: "herd.watchdog.nagMins",
|
|
1664
|
+
type: "number",
|
|
1665
|
+
scopes: ["machine"],
|
|
1666
|
+
default: 30,
|
|
1667
|
+
merge: "replace",
|
|
1668
|
+
description: "Minutes a done job may hold its pane before the shepherd is nagged to reclaim it."
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
key: "herd.watchdog.notifyHuman",
|
|
1672
|
+
type: "boolean",
|
|
1673
|
+
scopes: ["machine"],
|
|
1674
|
+
default: true,
|
|
1675
|
+
merge: "replace",
|
|
1676
|
+
description: "Whether the watchdog's top escalation rung sends a macOS notification to the human."
|
|
1677
|
+
},
|
|
1678
|
+
{
|
|
1679
|
+
key: "herd.watchdog.midRunTrustAccept",
|
|
1680
|
+
type: "boolean",
|
|
1681
|
+
scopes: ["machine"],
|
|
1682
|
+
default: false,
|
|
1683
|
+
merge: "replace",
|
|
1684
|
+
description: "Whether the watchdog may drive a mid-run folder-trust dialog on a daemon-provisioned tree itself, rather than only parking the job and notifying (RT-196). Off by default: the screen match cannot yet confirm the dialog's folder matches the job's worktree, so a working session showing an unrelated permission prompt with the same shape is a real risk."
|
|
1601
1685
|
}
|
|
1602
1686
|
];
|
|
1603
1687
|
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -561,6 +561,7 @@ export function herdStart(
|
|
|
561
561
|
): Promise<RtResponse<Commands["herd:start"]["data"]>> {
|
|
562
562
|
const payload: Record<string, unknown> = { name: a.name, repo: a.repo, session: a.session };
|
|
563
563
|
if (a.hidden !== undefined) payload.hidden = a.hidden;
|
|
564
|
+
if (a.callerPane !== undefined) payload.callerPane = a.callerPane;
|
|
564
565
|
return rtCommand<Commands["herd:start"]["data"]>("herd:start", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 60_000 });
|
|
565
566
|
}
|
|
566
567
|
|
|
@@ -635,7 +636,9 @@ export function herdResume(
|
|
|
635
636
|
a: Commands["herd:resume"]["payload"],
|
|
636
637
|
o: RtClientOptions = {},
|
|
637
638
|
): Promise<RtResponse<Commands["herd:resume"]["data"]>> {
|
|
638
|
-
|
|
639
|
+
const payload: Record<string, unknown> = { herd: a.herd, session: a.session };
|
|
640
|
+
if (a.callerPane !== undefined) payload.callerPane = a.callerPane;
|
|
641
|
+
return rtCommand<Commands["herd:resume"]["data"]>("herd:resume", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
639
642
|
}
|
|
640
643
|
|
|
641
644
|
/** Closes a herdr pane, one CLI call under the runner's own 15s budget. */
|
package/src/commands.ts
CHANGED
|
@@ -97,7 +97,7 @@ export const GATE_BY_PANE = "pane";
|
|
|
97
97
|
export type GateStatus = "open" | "answered" | "parked" | "closed";
|
|
98
98
|
/** Reconciler's view of an agent's liveness; also the value `GateRow.executor` is stamped with. */
|
|
99
99
|
export type ExecutorState = "live" | "blocked" | "hidden" | "gone" | "cleared" | "unknown";
|
|
100
|
-
export type GateOption = string | { value: string; label: string; recommended?: boolean };
|
|
100
|
+
export type GateOption = string | { value: string; label: string; recommended?: boolean; description?: string };
|
|
101
101
|
export interface GateOrigin {
|
|
102
102
|
paneId?: string;
|
|
103
103
|
tabId?: string;
|
|
@@ -106,7 +106,9 @@ export interface GateOrigin {
|
|
|
106
106
|
surface?: string;
|
|
107
107
|
presentation?: "form" | "wait";
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
/** `context` is per-question material (what this one choice turns on);
|
|
110
|
+
the gate-level `context` on the open/ask payload is the whole ask's. */
|
|
111
|
+
export interface GateQuestion { id: string; label: string; multi: boolean; options: GateOption[]; context?: string }
|
|
110
112
|
/** Implementations live in gate-options.ts (the browser-safe ./gate
|
|
111
113
|
subpath); re-exported here so existing commands.ts/index.ts consumers
|
|
112
114
|
are unaffected. */
|
|
@@ -169,7 +171,7 @@ export interface HerdJobInfo { herd: string; name: string; worktree: string; bra
|
|
|
169
171
|
/** `lastGateStatus`/`lastGateDelivery` come from the job's `lastGate` row: a TERMINAL gate (answered or closed) whose delivery is `dead-pane` is the "worker not woken" case the shepherd must act on. `lastGateConsumed` is `null` when there is nothing to consume (no last gate, not answered, or not nudged), and otherwise reports whether the nudged pane has read its answer. */
|
|
170
172
|
export interface HerdStatusData {
|
|
171
173
|
herd: HerdInfo;
|
|
172
|
-
jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | null; /** The worker session behind this job's pane is gone while the job still reads in-flight: herdr lists the pane but no claude is on it. Null when herdr does not list the pane at all (it closed, or herdr is unreachable), which proves nothing either way. */ sessionDead: boolean | null; lastGateStatus: GateStatus | null; lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null; lastGateConsumed: boolean | null }>;
|
|
174
|
+
jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | null; /** The worker session behind this job's pane is gone while the job still reads in-flight: herdr lists the pane but no claude is on it. Null when herdr does not list the pane at all (it closed, or herdr is unreachable), which proves nothing either way. */ sessionDead: boolean | null; lastGateStatus: GateStatus | null; lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null; lastGateConsumed: boolean | null; /** The daemon watchdog's escalation ladder for this job: strikes so far and when it last acted. Absent when the job is not on the ladder (healthy, no watchdog wired, or the daemon restarted since). */ watchdog?: { strikes: number; lastPokeAt: number | null } }>;
|
|
173
175
|
unread: number;
|
|
174
176
|
lifecycleConnected: boolean;
|
|
175
177
|
hiddenUp: boolean | null;
|
|
@@ -347,6 +349,11 @@ export interface ReconcilerStatus {
|
|
|
347
349
|
executors: ExecutorView[];
|
|
348
350
|
}
|
|
349
351
|
|
|
352
|
+
/** What a claude spawn's folder-trust check established, when one ran:
|
|
353
|
+
`none` = no dialog was up, `accepted` = one was answered, `stuck` = one is
|
|
354
|
+
still up and needs a human, `unchecked` = nothing could be established. */
|
|
355
|
+
export type TrustOutcome = "none" | "accepted" | "stuck" | "unchecked";
|
|
356
|
+
|
|
350
357
|
export interface AgentRecord {
|
|
351
358
|
id: string; repo: string; cwd: string; provider: string;
|
|
352
359
|
surface: AgentSurface; sessionId: string;
|
|
@@ -616,7 +623,7 @@ export interface Commands {
|
|
|
616
623
|
"chat:dm-open": { payload: { from: string; to: string; sessionId?: string }; data: { room: string; created: boolean } };
|
|
617
624
|
|
|
618
625
|
// ─── Agent handoff (rt agent) ────────────────────────────────────────────
|
|
619
|
-
"agent:start": { payload: { repo: string; cwd: string; prompt?: string; surface?: AgentSurface; provider?: string; model?: string; effort?: string; account?: string; label?: string; caller?: string; workspace?: string; tab?: string; extraArgs?: string; env?: Record<string, string>; herdrSocket?: string; handle?: string; bg?: boolean; subject?: string; yolo?: boolean }; data: AgentRecord };
|
|
626
|
+
"agent:start": { payload: { repo: string; cwd: string; prompt?: string; surface?: AgentSurface; provider?: string; model?: string; effort?: string; account?: string; label?: string; caller?: string; workspace?: string; tab?: string; extraArgs?: string; env?: Record<string, string>; herdrSocket?: string; handle?: string; bg?: boolean; subject?: string; yolo?: boolean; /** How long the folder-trust check lets the dialog paint before reading the screen; interactive launches keep the short default, herd:spawn passes its own longer budget. */ trustWaitMs?: number }; data: AgentRecord & { trust?: TrustOutcome } };
|
|
620
627
|
"agent:resume": { payload: { id: string; prompt?: string; surface?: AgentSurface; workspace?: string; tab?: string }; data: AgentRecord };
|
|
621
628
|
"agent:get": { payload: { id: string }; data: AgentRecord };
|
|
622
629
|
"agent:list": { payload: { repo?: string }; data: { agents: AgentRecord[] } };
|
|
@@ -711,9 +718,10 @@ export interface Commands {
|
|
|
711
718
|
agent?: string;
|
|
712
719
|
origin?: { surface?: string; tabId?: string; worktree?: string };
|
|
713
720
|
};
|
|
714
|
-
/** `contextOmitted` appears only when the
|
|
715
|
-
8192-byte
|
|
716
|
-
|
|
721
|
+
/** `contextOmitted` appears only when the gate context plus every
|
|
722
|
+
question's `context` exceeded their shared 8192-byte budget: the
|
|
723
|
+
gate still opened, but question contexts were dropped, and the gate
|
|
724
|
+
context too when it was over the budget on its own. */
|
|
717
725
|
data: { id: string; presentation: "form" | "wait"; subject: string; supersededId: string | null; contextOmitted?: true };
|
|
718
726
|
};
|
|
719
727
|
/**
|
|
@@ -748,8 +756,8 @@ export interface Commands {
|
|
|
748
756
|
"gate:subscriptions": { payload: { session?: string; live?: boolean }; data: { subscriptions: GateSubscription[] } };
|
|
749
757
|
|
|
750
758
|
// ─── Herd (shepherd run registry) ────────────────────────────────────────
|
|
751
|
-
"herd:start": { payload: { name: string; repo: string; session: string; hidden?: boolean }; data: { herd: string; room: string; workspace: string; subscription: string; handle: string; hidden: boolean } };
|
|
752
|
-
"herd:resume": { payload: { herd: string; session: string }; data: { subscription: string; gates: GateRow[]; unread: number; status: HerdStatusData; handle: string } };
|
|
759
|
+
"herd:start": { payload: { name: string; repo: string; session: string; hidden?: boolean; callerPane?: string }; data: { herd: string; room: string; workspace: string; subscription: string; handle: string; hidden: boolean } };
|
|
760
|
+
"herd:resume": { payload: { herd: string; session: string; callerPane?: string }; data: { subscription: string; gates: GateRow[]; unread: number; status: HerdStatusData; handle: string } };
|
|
753
761
|
"herd:status": { payload: { herd: string }; data: HerdStatusData };
|
|
754
762
|
/** Active herds only unless `all`, so a shepherd's "which herd am I on" question has one answer. */
|
|
755
763
|
"herd:list": { payload: { all?: boolean }; data: { herds: HerdListRow[] } };
|
package/src/gate-options.ts
CHANGED
|
@@ -6,6 +6,9 @@ import type { GateOption, GateQuestion } from "./commands.ts";
|
|
|
6
6
|
export interface GateOptionObject {
|
|
7
7
|
value: string;
|
|
8
8
|
label: string;
|
|
9
|
+
/** Per-option explanation, carried verbatim: never capitalized, never
|
|
10
|
+
suffixed, never filled in from value/label. */
|
|
11
|
+
description?: string;
|
|
9
12
|
}
|
|
10
13
|
|
|
11
14
|
export function gateOptionValue(o: GateOption): string {
|
|
@@ -45,9 +48,11 @@ function capitalize(label: string): string {
|
|
|
45
48
|
word-like (see isWordLikeLabel), and an object form's `recommended:
|
|
46
49
|
true` lifts into a " (Recommended)" label suffix -- guarded against
|
|
47
50
|
double-appending -- since `recommended` itself does not survive into
|
|
48
|
-
the returned object; the suffix IS its wire representation.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
+
the returned object; the suffix IS its wire representation. An object
|
|
52
|
+
form's non-empty string `description` rides along untouched (no
|
|
53
|
+
capitalization, no suffix); any other description shape is dropped.
|
|
54
|
+
Every returned entry is a full {value,label} pair -- callers may trust
|
|
55
|
+
the return type without re-checking it. Pure and order-preserving. */
|
|
51
56
|
export function normalizeGateOptions(options: GateOption[]): GateOptionObject[] {
|
|
52
57
|
return options.map((o) => {
|
|
53
58
|
const recommended = o !== null && typeof o === "object" && (o as { recommended?: unknown }).recommended === true;
|
|
@@ -70,10 +75,19 @@ export function normalizeGateOptions(options: GateOption[]): GateOptionObject[]
|
|
|
70
75
|
// downstream `label || value` fallback (gate-kit) must still see label
|
|
71
76
|
// as absent, not as a non-empty "(Recommended)" that hides the value.
|
|
72
77
|
if (recommended && label && !HAS_RECOMMENDED_SUFFIX.test(label)) label += RECOMMENDED_SUFFIX;
|
|
73
|
-
|
|
78
|
+
const d = o !== null && typeof o === "object" ? (o as { description?: unknown }).description : undefined;
|
|
79
|
+
return typeof d === "string" && d ? { value, label, description: d } : { value, label };
|
|
74
80
|
});
|
|
75
81
|
}
|
|
76
82
|
|
|
83
|
+
/** Options normalize per normalizeGateOptions; a question's `context` is
|
|
84
|
+
kept verbatim when it is a non-blank string and dropped otherwise, so a
|
|
85
|
+
stored row never carries an empty or non-string context key. */
|
|
77
86
|
export function normalizeGateQuestions(questions: GateQuestion[]): GateQuestion[] {
|
|
78
|
-
return questions.map((q) =>
|
|
87
|
+
return questions.map((q) => {
|
|
88
|
+
const { context, ...rest } = q;
|
|
89
|
+
const out: GateQuestion = { ...rest, options: normalizeGateOptions(q.options) };
|
|
90
|
+
if (typeof context === "string" && context.trim()) out.context = context;
|
|
91
|
+
return out;
|
|
92
|
+
});
|
|
79
93
|
}
|
|
@@ -751,4 +751,78 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
751
751
|
merge: "replace",
|
|
752
752
|
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.",
|
|
753
753
|
},
|
|
754
|
+
|
|
755
|
+
// --- herd (watchdog) ---------------------------------------------------
|
|
756
|
+
{
|
|
757
|
+
key: "herd.watchdog.enabled",
|
|
758
|
+
type: "boolean",
|
|
759
|
+
scopes: ["machine"],
|
|
760
|
+
default: true,
|
|
761
|
+
merge: "replace",
|
|
762
|
+
description: "Turns the herd watchdog sweep on.",
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
key: "herd.watchdog.fastMins",
|
|
766
|
+
type: "number",
|
|
767
|
+
scopes: ["machine"],
|
|
768
|
+
default: 2,
|
|
769
|
+
merge: "replace",
|
|
770
|
+
description: "Idle minutes before a worker with a pending DM, mention, or answered gate is poked.",
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
key: "herd.watchdog.shepherdFastMins",
|
|
774
|
+
type: "number",
|
|
775
|
+
scopes: ["machine"],
|
|
776
|
+
default: 5,
|
|
777
|
+
merge: "replace",
|
|
778
|
+
description: "Minutes an open human-owned gate in the herd may stay unanswered before the shepherd is poked. Unread DMs and mentions for the shepherd wedge immediately and are not delayed by this key.",
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
key: "herd.watchdog.backstopMins",
|
|
782
|
+
type: "number",
|
|
783
|
+
scopes: ["machine"],
|
|
784
|
+
default: 15,
|
|
785
|
+
merge: "replace",
|
|
786
|
+
description: "Idle minutes before any non-gated active worker is poked.",
|
|
787
|
+
},
|
|
788
|
+
{
|
|
789
|
+
key: "herd.watchdog.retryMins",
|
|
790
|
+
type: "number",
|
|
791
|
+
scopes: ["machine"],
|
|
792
|
+
default: 5,
|
|
793
|
+
merge: "replace",
|
|
794
|
+
description: "Minutes between escalation strikes while the watchdog keeps poking an unresponsive worker.",
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
key: "herd.watchdog.notifyQuietMins",
|
|
798
|
+
type: "number",
|
|
799
|
+
scopes: ["machine"],
|
|
800
|
+
default: 30,
|
|
801
|
+
merge: "replace",
|
|
802
|
+
description: "Minimum minutes between macOS notifications for the same herd.",
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
key: "herd.watchdog.nagMins",
|
|
806
|
+
type: "number",
|
|
807
|
+
scopes: ["machine"],
|
|
808
|
+
default: 30,
|
|
809
|
+
merge: "replace",
|
|
810
|
+
description: "Minutes a done job may hold its pane before the shepherd is nagged to reclaim it.",
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
key: "herd.watchdog.notifyHuman",
|
|
814
|
+
type: "boolean",
|
|
815
|
+
scopes: ["machine"],
|
|
816
|
+
default: true,
|
|
817
|
+
merge: "replace",
|
|
818
|
+
description: "Whether the watchdog's top escalation rung sends a macOS notification to the human.",
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
key: "herd.watchdog.midRunTrustAccept",
|
|
822
|
+
type: "boolean",
|
|
823
|
+
scopes: ["machine"],
|
|
824
|
+
default: false,
|
|
825
|
+
merge: "replace",
|
|
826
|
+
description: "Whether the watchdog may drive a mid-run folder-trust dialog on a daemon-provisioned tree itself, rather than only parking the job and notifying (RT-196). Off by default: the screen match cannot yet confirm the dialog's folder matches the job's worktree, so a working session showing an unrelated permission prompt with the same shape is a real risk.",
|
|
827
|
+
},
|
|
754
828
|
];
|