@mattstack/rt-client 0.25.0 → 0.27.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 +29 -12
- package/dist/gate-options.d.ts +11 -3
- package/dist/gate.js +9 -2
- package/dist/index.js +63 -11
- package/package.json +1 -1
- package/src/client.ts +2 -1
- package/src/commands.ts +26 -18
- package/src/gate-options.ts +19 -5
- package/src/settings/registry-defs.ts +55 -10
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
|
|
@@ -171,14 +175,13 @@ export interface GateRow {
|
|
|
171
175
|
at: number;
|
|
172
176
|
} | null;
|
|
173
177
|
released: boolean;
|
|
174
|
-
/** Set once the nudged pane has provably read the answer:
|
|
175
|
-
|
|
176
|
-
later `markConsumed` call recorded that it acted on a push
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
reconciles. */
|
|
178
|
+
/** Set once the nudged pane has provably read the answer: it self-answered
|
|
179
|
+
or lost the CAS to one (both stamped in the answer's own transaction),
|
|
180
|
+
or a later `markConsumed` call recorded that it acted on a push
|
|
181
|
+
(`rt herd answer` and `rt gate wait`, each on the nudged session's own
|
|
182
|
+
read). `null` until then, so a sweep can tell an answered-but-unread row
|
|
183
|
+
from a settled one. Every nudge-bearing subject has a stamping read;
|
|
184
|
+
a gate with no nudge never carries one, having no pane to read it. */
|
|
182
185
|
consumedAt: number | null;
|
|
183
186
|
owner: string | null;
|
|
184
187
|
escalatedAt: number | null;
|
|
@@ -228,19 +231,20 @@ export interface HerdJobInfo {
|
|
|
228
231
|
agentSession: string | null;
|
|
229
232
|
agentId: string | null;
|
|
230
233
|
handle: string;
|
|
231
|
-
status: "spawning" | "active" | "at-gate" | "at-milestone" | "done" | "closed" | "crashed";
|
|
234
|
+
status: "spawning" | "active" | "at-gate" | "at-milestone" | "done" | "closed" | "crashed" | "stuck-at-modal";
|
|
232
235
|
disposable: boolean;
|
|
233
236
|
lastGate: string | null;
|
|
234
237
|
lastReport: number | null;
|
|
235
238
|
createdAt: number;
|
|
236
239
|
updatedAt: number;
|
|
237
240
|
}
|
|
238
|
-
/** `lastGateStatus`/`lastGateDelivery` come from the job's `lastGate` row:
|
|
241
|
+
/** `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. */
|
|
239
242
|
export interface HerdStatusData {
|
|
240
243
|
herd: HerdInfo;
|
|
241
244
|
jobs: Array<HerdJobInfo & {
|
|
242
245
|
openGate: string | null;
|
|
243
|
-
paneStatus: string | null;
|
|
246
|
+
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. */
|
|
247
|
+
sessionDead: boolean | null;
|
|
244
248
|
lastGateStatus: GateStatus | null;
|
|
245
249
|
lastGateDelivery: "delivered" | "dead-pane" | "confirmed" | "stuck" | null;
|
|
246
250
|
lastGateConsumed: boolean | null;
|
|
@@ -507,6 +511,7 @@ export interface AgentRecord {
|
|
|
507
511
|
extraArgs?: string;
|
|
508
512
|
exitCode?: number;
|
|
509
513
|
resultPath?: string;
|
|
514
|
+
yolo?: boolean;
|
|
510
515
|
createdAt: number;
|
|
511
516
|
lastResumedAt?: number;
|
|
512
517
|
finishedAt?: number;
|
|
@@ -1141,6 +1146,7 @@ export interface Commands {
|
|
|
1141
1146
|
cwd: string;
|
|
1142
1147
|
prompt?: string;
|
|
1143
1148
|
surface?: AgentSurface;
|
|
1149
|
+
provider?: string;
|
|
1144
1150
|
model?: string;
|
|
1145
1151
|
effort?: string;
|
|
1146
1152
|
account?: string;
|
|
@@ -1154,6 +1160,7 @@ export interface Commands {
|
|
|
1154
1160
|
handle?: string;
|
|
1155
1161
|
bg?: boolean;
|
|
1156
1162
|
subject?: string;
|
|
1163
|
+
yolo?: boolean;
|
|
1157
1164
|
};
|
|
1158
1165
|
data: AgentRecord;
|
|
1159
1166
|
};
|
|
@@ -1503,11 +1510,16 @@ export interface Commands {
|
|
|
1503
1510
|
worktree?: string;
|
|
1504
1511
|
};
|
|
1505
1512
|
};
|
|
1513
|
+
/** `contextOmitted` appears only when the gate context plus every
|
|
1514
|
+
question's `context` exceeded their shared 8192-byte budget: the
|
|
1515
|
+
gate still opened, but question contexts were dropped, and the gate
|
|
1516
|
+
context too when it was over the budget on its own. */
|
|
1506
1517
|
data: {
|
|
1507
1518
|
id: string;
|
|
1508
1519
|
presentation: "form" | "wait";
|
|
1509
1520
|
subject: string;
|
|
1510
1521
|
supersededId: string | null;
|
|
1522
|
+
contextOmitted?: true;
|
|
1511
1523
|
};
|
|
1512
1524
|
};
|
|
1513
1525
|
/**
|
|
@@ -1538,10 +1550,14 @@ export interface Commands {
|
|
|
1538
1550
|
};
|
|
1539
1551
|
/** `ok:false "not-found"` on an unknown id is terminal; the CLI loop must not re-enter on it.
|
|
1540
1552
|
* `timeout` carries no row (nothing settled); `answered`/`closed` always carry the settled row. */
|
|
1553
|
+
/** `sessionId` is the caller's own session: an answered result returned to
|
|
1554
|
+
* the gate's nudged session is a recorded read and stamps `consumedAt`.
|
|
1555
|
+
* Omitting it reads without consuming. */
|
|
1541
1556
|
"gate:wait": {
|
|
1542
1557
|
payload: {
|
|
1543
1558
|
id: string;
|
|
1544
1559
|
waitMs?: number;
|
|
1560
|
+
sessionId?: string;
|
|
1545
1561
|
};
|
|
1546
1562
|
data: {
|
|
1547
1563
|
status: "timeout";
|
|
@@ -1689,7 +1705,8 @@ export interface Commands {
|
|
|
1689
1705
|
wasOnDeck: boolean | null;
|
|
1690
1706
|
agentId: string;
|
|
1691
1707
|
sessionId: string;
|
|
1692
|
-
handle: string;
|
|
1708
|
+
handle: string; /** What the folder-trust check established: no modal was up, one was accepted and verified gone, one is still up (the job reads `stuck-at-modal`), or herdr could not be read. */
|
|
1709
|
+
trust: "none" | "accepted" | "stuck" | "unchecked";
|
|
1693
1710
|
};
|
|
1694
1711
|
};
|
|
1695
1712
|
"herd:gates": {
|
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
|
@@ -243,7 +243,7 @@ function eventsList(payload, o = {}) {
|
|
|
243
243
|
}
|
|
244
244
|
function agentStart(a, o = {}) {
|
|
245
245
|
const payload = { repo: a.repo, cwd: a.cwd };
|
|
246
|
-
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg", "subject"]) {
|
|
246
|
+
for (const k of ["prompt", "surface", "provider", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg", "subject", "yolo"]) {
|
|
247
247
|
if (a[k] !== undefined)
|
|
248
248
|
payload[k] = a[k];
|
|
249
249
|
}
|
|
@@ -344,6 +344,8 @@ function gateWait(a, o = {}) {
|
|
|
344
344
|
const payload = { id: a.id };
|
|
345
345
|
if (a.waitMs !== undefined)
|
|
346
346
|
payload.waitMs = a.waitMs;
|
|
347
|
+
if (a.sessionId !== undefined)
|
|
348
|
+
payload.sessionId = a.sessionId;
|
|
347
349
|
return rtCommand("gate:wait", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 250000 });
|
|
348
350
|
}
|
|
349
351
|
function gateList(a, o = {}) {
|
|
@@ -491,11 +493,18 @@ function normalizeGateOptions(options) {
|
|
|
491
493
|
label = capitalize(label);
|
|
492
494
|
if (recommended && label && !HAS_RECOMMENDED_SUFFIX.test(label))
|
|
493
495
|
label += RECOMMENDED_SUFFIX;
|
|
494
|
-
|
|
496
|
+
const d = o !== null && typeof o === "object" ? o.description : undefined;
|
|
497
|
+
return typeof d === "string" && d ? { value, label, description: d } : { value, label };
|
|
495
498
|
});
|
|
496
499
|
}
|
|
497
500
|
function normalizeGateQuestions(questions) {
|
|
498
|
-
return questions.map((q) =>
|
|
501
|
+
return questions.map((q) => {
|
|
502
|
+
const { context, ...rest } = q;
|
|
503
|
+
const out = { ...rest, options: normalizeGateOptions(q.options) };
|
|
504
|
+
if (typeof context === "string" && context.trim())
|
|
505
|
+
out.context = context;
|
|
506
|
+
return out;
|
|
507
|
+
});
|
|
499
508
|
}
|
|
500
509
|
|
|
501
510
|
// src/commands.ts
|
|
@@ -1519,32 +1528,75 @@ var REGISTRY = [
|
|
|
1519
1528
|
description: "Destination (topic/URL/token) the configured chat.push.provider sends to."
|
|
1520
1529
|
},
|
|
1521
1530
|
{
|
|
1522
|
-
key: "agent.
|
|
1531
|
+
key: "agent.provider",
|
|
1532
|
+
type: "string",
|
|
1533
|
+
scopes: ["user", "machine"],
|
|
1534
|
+
default: "claude",
|
|
1535
|
+
merge: "replace",
|
|
1536
|
+
description: 'Which provider rt agent start uses when --provider is not given: "claude" or "codex".'
|
|
1537
|
+
},
|
|
1538
|
+
{
|
|
1539
|
+
key: "agent.claude.model",
|
|
1540
|
+
type: "string",
|
|
1541
|
+
scopes: ["user", "machine"],
|
|
1542
|
+
merge: "replace",
|
|
1543
|
+
description: "Default --model for claude rt agent launches; unset omits the flag."
|
|
1544
|
+
},
|
|
1545
|
+
{
|
|
1546
|
+
key: "agent.claude.effort",
|
|
1547
|
+
type: "string",
|
|
1548
|
+
scopes: ["user", "machine"],
|
|
1549
|
+
merge: "replace",
|
|
1550
|
+
description: "Default --effort for claude rt agent launches; unset omits the flag."
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
key: "agent.claude.account",
|
|
1554
|
+
type: "string",
|
|
1555
|
+
scopes: ["user", "machine"],
|
|
1556
|
+
merge: "replace",
|
|
1557
|
+
description: "cswap account email claude rt agent launches under; unset uses the default claude profile."
|
|
1558
|
+
},
|
|
1559
|
+
{
|
|
1560
|
+
key: "agent.claude.extraArgs",
|
|
1523
1561
|
type: "string",
|
|
1524
1562
|
scopes: ["user", "machine"],
|
|
1525
1563
|
merge: "replace",
|
|
1526
|
-
description: "
|
|
1564
|
+
description: "Opaque extra claude arguments appended to every claude rt agent launch (escape hatch)."
|
|
1527
1565
|
},
|
|
1528
1566
|
{
|
|
1529
|
-
key: "agent.
|
|
1567
|
+
key: "agent.claude.yolo",
|
|
1568
|
+
type: "boolean",
|
|
1569
|
+
scopes: ["user", "machine"],
|
|
1570
|
+
merge: "replace",
|
|
1571
|
+
description: "Default --yolo (--dangerously-skip-permissions) for claude rt agent launches; unset behaves as false."
|
|
1572
|
+
},
|
|
1573
|
+
{
|
|
1574
|
+
key: "agent.codex.model",
|
|
1530
1575
|
type: "string",
|
|
1531
1576
|
scopes: ["user", "machine"],
|
|
1532
1577
|
merge: "replace",
|
|
1533
|
-
description: "Default
|
|
1578
|
+
description: "Default -m/--model for codex rt agent launches; unset omits the flag."
|
|
1534
1579
|
},
|
|
1535
1580
|
{
|
|
1536
|
-
key: "agent.
|
|
1581
|
+
key: "agent.codex.effort",
|
|
1537
1582
|
type: "string",
|
|
1538
1583
|
scopes: ["user", "machine"],
|
|
1539
1584
|
merge: "replace",
|
|
1540
|
-
description: "
|
|
1585
|
+
description: "Default reasoning effort for codex rt agent launches, passed as -c model_reasoning_effort=<value>; unset omits the override."
|
|
1541
1586
|
},
|
|
1542
1587
|
{
|
|
1543
|
-
key: "agent.extraArgs",
|
|
1588
|
+
key: "agent.codex.extraArgs",
|
|
1544
1589
|
type: "string",
|
|
1545
1590
|
scopes: ["user", "machine"],
|
|
1546
1591
|
merge: "replace",
|
|
1547
|
-
description: "Opaque extra
|
|
1592
|
+
description: "Opaque extra codex arguments appended to every codex rt agent launch (escape hatch)."
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
key: "agent.codex.yolo",
|
|
1596
|
+
type: "boolean",
|
|
1597
|
+
scopes: ["user", "machine"],
|
|
1598
|
+
merge: "replace",
|
|
1599
|
+
description: "Default --yolo (--dangerously-bypass-approvals-and-sandbox) for codex rt agent launches; unset behaves as false. codex has no per-agent account setting (see spec's Non-goals)."
|
|
1548
1600
|
},
|
|
1549
1601
|
{
|
|
1550
1602
|
key: "rt.gates.escalationTtlMinutes",
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -356,7 +356,7 @@ export function agentStart(
|
|
|
356
356
|
a: Commands["agent:start"]["payload"], o: RtClientOptions = {},
|
|
357
357
|
): Promise<RtResponse<AgentRecord>> {
|
|
358
358
|
const payload: Record<string, unknown> = { repo: a.repo, cwd: a.cwd };
|
|
359
|
-
for (const k of ["prompt", "surface", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg", "subject"] as const) {
|
|
359
|
+
for (const k of ["prompt", "surface", "provider", "model", "effort", "account", "label", "caller", "workspace", "tab", "extraArgs", "env", "herdrSocket", "handle", "bg", "subject", "yolo"] 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 });
|
|
@@ -499,6 +499,7 @@ export function gateWait(
|
|
|
499
499
|
): Promise<RtResponse<Commands["gate:wait"]["data"]>> {
|
|
500
500
|
const payload: Record<string, unknown> = { id: a.id };
|
|
501
501
|
if (a.waitMs !== undefined) payload.waitMs = a.waitMs;
|
|
502
|
+
if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
|
|
502
503
|
return rtCommand<Commands["gate:wait"]["data"]>("gate:wait", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 250_000 });
|
|
503
504
|
}
|
|
504
505
|
|
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. */
|
|
@@ -134,14 +136,13 @@ export interface GateRow {
|
|
|
134
136
|
nudge: { session: string } | null;
|
|
135
137
|
delivery: { outcome: "delivered" | "dead-pane" | "confirmed" | "stuck"; at: number } | null;
|
|
136
138
|
released: boolean;
|
|
137
|
-
/** Set once the nudged pane has provably read the answer:
|
|
138
|
-
|
|
139
|
-
later `markConsumed` call recorded that it acted on a push
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
reconciles. */
|
|
139
|
+
/** Set once the nudged pane has provably read the answer: it self-answered
|
|
140
|
+
or lost the CAS to one (both stamped in the answer's own transaction),
|
|
141
|
+
or a later `markConsumed` call recorded that it acted on a push
|
|
142
|
+
(`rt herd answer` and `rt gate wait`, each on the nudged session's own
|
|
143
|
+
read). `null` until then, so a sweep can tell an answered-but-unread row
|
|
144
|
+
from a settled one. Every nudge-bearing subject has a stamping read;
|
|
145
|
+
a gate with no nudge never carries one, having no pane to read it. */
|
|
145
146
|
consumedAt: number | null;
|
|
146
147
|
owner: string | null;
|
|
147
148
|
escalatedAt: number | null;
|
|
@@ -166,11 +167,11 @@ export interface GateSubscription {
|
|
|
166
167
|
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 }
|
|
167
168
|
/** A herd row as `herd:list` reports it: the registry row plus how many jobs hang off it. */
|
|
168
169
|
export interface HerdListRow extends HerdInfo { jobs: number }
|
|
169
|
-
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 }
|
|
170
|
-
/** `lastGateStatus`/`lastGateDelivery` come from the job's `lastGate` row:
|
|
170
|
+
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" | "stuck-at-modal"; disposable: boolean; lastGate: string | null; lastReport: number | null; createdAt: number; updatedAt: number }
|
|
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. */
|
|
171
172
|
export interface HerdStatusData {
|
|
172
173
|
herd: HerdInfo;
|
|
173
|
-
jobs: Array<HerdJobInfo & { openGate: string | null; paneStatus: string | 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 }>;
|
|
174
175
|
unread: number;
|
|
175
176
|
lifecycleConnected: boolean;
|
|
176
177
|
hiddenUp: boolean | null;
|
|
@@ -360,7 +361,7 @@ export interface AgentRecord {
|
|
|
360
361
|
PreToolUse hook gets injected at all. */
|
|
361
362
|
subject?: string;
|
|
362
363
|
paneId?: string; tabId?: string; workspaceId?: string;
|
|
363
|
-
extraArgs?: string; exitCode?: number; resultPath?: string;
|
|
364
|
+
extraArgs?: string; exitCode?: number; resultPath?: string; yolo?: boolean;
|
|
364
365
|
createdAt: number; lastResumedAt?: number; finishedAt?: number;
|
|
365
366
|
}
|
|
366
367
|
|
|
@@ -617,7 +618,7 @@ export interface Commands {
|
|
|
617
618
|
"chat:dm-open": { payload: { from: string; to: string; sessionId?: string }; data: { room: string; created: boolean } };
|
|
618
619
|
|
|
619
620
|
// ─── Agent handoff (rt agent) ────────────────────────────────────────────
|
|
620
|
-
"agent:start": { payload: { repo: string; cwd: string; prompt?: string; surface?: AgentSurface; model?: string; effort?: string; account?: string; label?: string; caller?: string; workspace?: string; tab?: string; extraArgs?: string; env?: Record<string, string>; herdrSocket?: string; handle?: string; bg?: boolean; subject?: string }; data: AgentRecord };
|
|
621
|
+
"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 };
|
|
621
622
|
"agent:resume": { payload: { id: string; prompt?: string; surface?: AgentSurface; workspace?: string; tab?: string }; data: AgentRecord };
|
|
622
623
|
"agent:get": { payload: { id: string }; data: AgentRecord };
|
|
623
624
|
"agent:list": { payload: { repo?: string }; data: { agents: AgentRecord[] } };
|
|
@@ -712,7 +713,11 @@ export interface Commands {
|
|
|
712
713
|
agent?: string;
|
|
713
714
|
origin?: { surface?: string; tabId?: string; worktree?: string };
|
|
714
715
|
};
|
|
715
|
-
|
|
716
|
+
/** `contextOmitted` appears only when the gate context plus every
|
|
717
|
+
question's `context` exceeded their shared 8192-byte budget: the
|
|
718
|
+
gate still opened, but question contexts were dropped, and the gate
|
|
719
|
+
context too when it was over the budget on its own. */
|
|
720
|
+
data: { id: string; presentation: "form" | "wait"; subject: string; supersededId: string | null; contextOmitted?: true };
|
|
716
721
|
};
|
|
717
722
|
/**
|
|
718
723
|
* A CAS loss is a DEFINED OUTCOME, not an error: `ok:true` with
|
|
@@ -730,7 +735,10 @@ export interface Commands {
|
|
|
730
735
|
"gate:answer": { payload: { id: string; answers: GateAnswer["answers"]; by: string; session?: string; override?: boolean }; data: { row: GateRow; conflict?: true } };
|
|
731
736
|
/** `ok:false "not-found"` on an unknown id is terminal; the CLI loop must not re-enter on it.
|
|
732
737
|
* `timeout` carries no row (nothing settled); `answered`/`closed` always carry the settled row. */
|
|
733
|
-
|
|
738
|
+
/** `sessionId` is the caller's own session: an answered result returned to
|
|
739
|
+
* the gate's nudged session is a recorded read and stamps `consumedAt`.
|
|
740
|
+
* Omitting it reads without consuming. */
|
|
741
|
+
"gate:wait": { payload: { id: string; waitMs?: number; sessionId?: string }; data: { status: "timeout" } | { status: "answered" | "closed"; row: GateRow } };
|
|
734
742
|
/** Paged like events:list: an omitted `limit` clamps daemon-side rather than
|
|
735
743
|
* forcing a full-table read; `cursor` is the paging rowid to resume from. */
|
|
736
744
|
"gate:list": { payload: { open?: boolean; subjectPrefix?: string; kind?: string; limit?: number; cursor?: number }; data: { gates: GateRow[]; cursor: number } };
|
|
@@ -750,7 +758,7 @@ export interface Commands {
|
|
|
750
758
|
"herd:list": { payload: { all?: boolean }; data: { herds: HerdListRow[] } };
|
|
751
759
|
"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 } };
|
|
752
760
|
/** `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. */
|
|
753
|
-
"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 } };
|
|
761
|
+
"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; /** What the folder-trust check established: no modal was up, one was accepted and verified gone, one is still up (the job reads `stuck-at-modal`), or herdr could not be read. */ trust: "none" | "accepted" | "stuck" | "unchecked" } };
|
|
754
762
|
"herd:gates": { payload: { herd: string }; data: { gates: GateRow[] } };
|
|
755
763
|
"herd:ask": { payload: { herd: string; job: string; session: string; pane?: string; questions: GateQuestion[]; context?: string }; data: { gate: string } };
|
|
756
764
|
"herd:milestone": { payload: { herd: string; job: string; session: string; pane?: string; artifact: string; summary?: string }; data: { gate: string; message: number } };
|
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
|
}
|
|
@@ -666,35 +666,80 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
666
666
|
},
|
|
667
667
|
|
|
668
668
|
// --- agent (rt agent handoff) --------------------------------------------
|
|
669
|
-
// No defaults by design: an unset key means the
|
|
670
|
-
//
|
|
669
|
+
// No defaults on any per-provider row, by design: an unset key means the
|
|
670
|
+
// flag is omitted from the launch entirely (spec "Settings"). agent.provider
|
|
671
|
+
// is the one exception -- it needs a concrete fallback to preserve
|
|
672
|
+
// claude-only behavior with zero code changes for callers who never set it.
|
|
671
673
|
{
|
|
672
|
-
key: "agent.
|
|
674
|
+
key: "agent.provider",
|
|
673
675
|
type: "string",
|
|
674
676
|
scopes: ["user", "machine"],
|
|
677
|
+
default: "claude",
|
|
675
678
|
merge: "replace",
|
|
676
|
-
description: "
|
|
679
|
+
description: "Which provider rt agent start uses when --provider is not given: \"claude\" or \"codex\".",
|
|
677
680
|
},
|
|
678
681
|
{
|
|
679
|
-
key: "agent.
|
|
682
|
+
key: "agent.claude.model",
|
|
680
683
|
type: "string",
|
|
681
684
|
scopes: ["user", "machine"],
|
|
682
685
|
merge: "replace",
|
|
683
|
-
description: "Default --
|
|
686
|
+
description: "Default --model for claude rt agent launches; unset omits the flag.",
|
|
684
687
|
},
|
|
685
688
|
{
|
|
686
|
-
key: "agent.
|
|
689
|
+
key: "agent.claude.effort",
|
|
687
690
|
type: "string",
|
|
688
691
|
scopes: ["user", "machine"],
|
|
689
692
|
merge: "replace",
|
|
690
|
-
description: "
|
|
693
|
+
description: "Default --effort for claude rt agent launches; unset omits the flag.",
|
|
691
694
|
},
|
|
692
695
|
{
|
|
693
|
-
key: "agent.
|
|
696
|
+
key: "agent.claude.account",
|
|
694
697
|
type: "string",
|
|
695
698
|
scopes: ["user", "machine"],
|
|
696
699
|
merge: "replace",
|
|
697
|
-
description: "
|
|
700
|
+
description: "cswap account email claude rt agent launches under; unset uses the default claude profile.",
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
key: "agent.claude.extraArgs",
|
|
704
|
+
type: "string",
|
|
705
|
+
scopes: ["user", "machine"],
|
|
706
|
+
merge: "replace",
|
|
707
|
+
description: "Opaque extra claude arguments appended to every claude rt agent launch (escape hatch).",
|
|
708
|
+
},
|
|
709
|
+
{
|
|
710
|
+
key: "agent.claude.yolo",
|
|
711
|
+
type: "boolean",
|
|
712
|
+
scopes: ["user", "machine"],
|
|
713
|
+
merge: "replace",
|
|
714
|
+
description: "Default --yolo (--dangerously-skip-permissions) for claude rt agent launches; unset behaves as false.",
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
key: "agent.codex.model",
|
|
718
|
+
type: "string",
|
|
719
|
+
scopes: ["user", "machine"],
|
|
720
|
+
merge: "replace",
|
|
721
|
+
description: "Default -m/--model for codex rt agent launches; unset omits the flag.",
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
key: "agent.codex.effort",
|
|
725
|
+
type: "string",
|
|
726
|
+
scopes: ["user", "machine"],
|
|
727
|
+
merge: "replace",
|
|
728
|
+
description: "Default reasoning effort for codex rt agent launches, passed as -c model_reasoning_effort=<value>; unset omits the override.",
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
key: "agent.codex.extraArgs",
|
|
732
|
+
type: "string",
|
|
733
|
+
scopes: ["user", "machine"],
|
|
734
|
+
merge: "replace",
|
|
735
|
+
description: "Opaque extra codex arguments appended to every codex rt agent launch (escape hatch).",
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
key: "agent.codex.yolo",
|
|
739
|
+
type: "boolean",
|
|
740
|
+
scopes: ["user", "machine"],
|
|
741
|
+
merge: "replace",
|
|
742
|
+
description: "Default --yolo (--dangerously-bypass-approvals-and-sandbox) for codex rt agent launches; unset behaves as false. codex has no per-agent account setting (see spec's Non-goals).",
|
|
698
743
|
},
|
|
699
744
|
|
|
700
745
|
// --- gates (escalation) ----------------------------------------------------
|