@mattstack/rt-client 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +12 -0
- package/dist/commands.d.ts +167 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +71 -0
- package/package.json +2 -2
- package/src/client.ts +76 -0
- package/src/commands.ts +67 -0
- package/src/index.ts +15 -1
- package/src/settings/registry-defs.ts +9 -0
package/dist/client.d.ts
CHANGED
|
@@ -186,3 +186,15 @@ export declare function paneSend(a: Commands["pane:send"]["payload"], o?: RtClie
|
|
|
186
186
|
/** Brings a herdr pane to the front. The daemon routes this to the tray, which
|
|
187
187
|
owns the herdr focus and the native terminal-window raise. */
|
|
188
188
|
export declare function paneFocus(a: Commands["pane:focus"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["pane:focus"]["data"]>>;
|
|
189
|
+
export declare function gateOpen(a: Commands["gate:open"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:open"]["data"]>>;
|
|
190
|
+
export declare function gateAnswer(a: Commands["gate:answer"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:answer"]["data"]>>;
|
|
191
|
+
/** Daemon clamps its own wait to 240s (gates-store.ts); the client abort
|
|
192
|
+
must outlive that cap, same +10s buffer as commands/events.ts's
|
|
193
|
+
IPC_TIMEOUT_MS over DAEMON_WAIT_MS. */
|
|
194
|
+
export declare function gateWait(a: Commands["gate:wait"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:wait"]["data"]>>;
|
|
195
|
+
export declare function gateList(a: Commands["gate:list"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:list"]["data"]>>;
|
|
196
|
+
export declare function gatePark(a: Commands["gate:park"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:park"]["data"]>>;
|
|
197
|
+
export declare function gateClose(a: Commands["gate:close"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:close"]["data"]>>;
|
|
198
|
+
export declare function gateSubscribe(a: Commands["gate:subscribe"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:subscribe"]["data"]>>;
|
|
199
|
+
export declare function gateUnsubscribe(a: Commands["gate:unsubscribe"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:unsubscribe"]["data"]>>;
|
|
200
|
+
export declare function gateSubscriptions(a: Commands["gate:subscriptions"]["payload"], o?: RtClientOptions): Promise<RtResponse<Commands["gate:subscriptions"]["data"]>>;
|
package/dist/commands.d.ts
CHANGED
|
@@ -91,6 +91,65 @@ export interface EventsBusEvent {
|
|
|
91
91
|
payload: unknown;
|
|
92
92
|
emittedAt: number;
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Duplicated shape on purpose, same reasoning as EventsBusEvent above:
|
|
96
|
+
* these mirror lib/daemon/gates-store.ts's types, which rt-client cannot
|
|
97
|
+
* import. gates-store.ts imports them back FROM this package (Commands
|
|
98
|
+
* already flows daemon -> rt-client, e.g. handlers/events.ts), so this is
|
|
99
|
+
* the single source of truth for the wire shape.
|
|
100
|
+
*/
|
|
101
|
+
/** The `by` value a pane spells when it answers its own gate: the only
|
|
102
|
+
value gates-store.ts's release tracking (CAS winner or loser) reacts to. */
|
|
103
|
+
export declare const GATE_BY_PANE = "pane";
|
|
104
|
+
export type GateStatus = "open" | "answered" | "parked" | "closed";
|
|
105
|
+
export interface GateQuestion {
|
|
106
|
+
id: string;
|
|
107
|
+
label: string;
|
|
108
|
+
multi: boolean;
|
|
109
|
+
options: string[];
|
|
110
|
+
}
|
|
111
|
+
export interface GateAnswer {
|
|
112
|
+
answers: Record<string, string | string[] | {
|
|
113
|
+
value: string | string[];
|
|
114
|
+
note?: string;
|
|
115
|
+
}>;
|
|
116
|
+
by: string;
|
|
117
|
+
answeredAt: number;
|
|
118
|
+
}
|
|
119
|
+
export interface GateRow {
|
|
120
|
+
id: string;
|
|
121
|
+
subject: string;
|
|
122
|
+
kind: string;
|
|
123
|
+
questions: GateQuestion[];
|
|
124
|
+
meta: Record<string, unknown> | null;
|
|
125
|
+
status: GateStatus;
|
|
126
|
+
answer: GateAnswer | null;
|
|
127
|
+
openedAt: number;
|
|
128
|
+
parkedAt: number | null;
|
|
129
|
+
closedAt: number | null;
|
|
130
|
+
closedReason: "abandoned" | "superseded" | "pruned" | null;
|
|
131
|
+
agent: string | null;
|
|
132
|
+
pane: string | null;
|
|
133
|
+
nudge: {
|
|
134
|
+
session: string;
|
|
135
|
+
} | null;
|
|
136
|
+
delivery: {
|
|
137
|
+
outcome: "delivered" | "dead-pane";
|
|
138
|
+
at: number;
|
|
139
|
+
} | null;
|
|
140
|
+
released: boolean;
|
|
141
|
+
}
|
|
142
|
+
export interface GateSubscription {
|
|
143
|
+
id: string;
|
|
144
|
+
subjectPrefix: string;
|
|
145
|
+
session: string;
|
|
146
|
+
createdAt: number;
|
|
147
|
+
lastDelivery: {
|
|
148
|
+
outcome: "delivered" | "failed";
|
|
149
|
+
at: number;
|
|
150
|
+
} | null;
|
|
151
|
+
dead: boolean;
|
|
152
|
+
}
|
|
94
153
|
/**
|
|
95
154
|
* Duplicated shape on purpose, same reasoning as EventsBusEvent above:
|
|
96
155
|
* these mirror lib/state/chat-store.ts's types, which rt-client cannot
|
|
@@ -1205,6 +1264,114 @@ export interface Commands {
|
|
|
1205
1264
|
payload: Record<string, never>;
|
|
1206
1265
|
data: unknown;
|
|
1207
1266
|
};
|
|
1267
|
+
"gate:open": {
|
|
1268
|
+
payload: {
|
|
1269
|
+
subject: string;
|
|
1270
|
+
kind: string;
|
|
1271
|
+
questions: GateQuestion[];
|
|
1272
|
+
meta?: Record<string, unknown>;
|
|
1273
|
+
agent?: string;
|
|
1274
|
+
pane?: string;
|
|
1275
|
+
nudge?: {
|
|
1276
|
+
session: string;
|
|
1277
|
+
};
|
|
1278
|
+
};
|
|
1279
|
+
data: {
|
|
1280
|
+
id: string;
|
|
1281
|
+
supersededId: string | null;
|
|
1282
|
+
};
|
|
1283
|
+
};
|
|
1284
|
+
/**
|
|
1285
|
+
* A CAS loss is a DEFINED OUTCOME, not an error: `ok:true` with
|
|
1286
|
+
* `conflict:true` and the WINNING row, so every consumer gets the winner
|
|
1287
|
+
* typed with no envelope hacks. `ok:false` is reserved for
|
|
1288
|
+
* not-found/closed/validation failures.
|
|
1289
|
+
*/
|
|
1290
|
+
"gate:answer": {
|
|
1291
|
+
payload: {
|
|
1292
|
+
id: string;
|
|
1293
|
+
answers: GateAnswer["answers"];
|
|
1294
|
+
by: string;
|
|
1295
|
+
};
|
|
1296
|
+
data: {
|
|
1297
|
+
row: GateRow;
|
|
1298
|
+
conflict?: true;
|
|
1299
|
+
};
|
|
1300
|
+
};
|
|
1301
|
+
/** `ok:false "not-found"` on an unknown id is terminal; the CLI loop must not re-enter on it.
|
|
1302
|
+
* `timeout` carries no row (nothing settled); `answered`/`closed` always carry the settled row. */
|
|
1303
|
+
"gate:wait": {
|
|
1304
|
+
payload: {
|
|
1305
|
+
id: string;
|
|
1306
|
+
waitMs?: number;
|
|
1307
|
+
};
|
|
1308
|
+
data: {
|
|
1309
|
+
status: "timeout";
|
|
1310
|
+
} | {
|
|
1311
|
+
status: "answered" | "closed";
|
|
1312
|
+
row: GateRow;
|
|
1313
|
+
};
|
|
1314
|
+
};
|
|
1315
|
+
/** Paged like events:list: an omitted `limit` clamps daemon-side rather than
|
|
1316
|
+
* forcing a full-table read; `cursor` is the paging rowid to resume from. */
|
|
1317
|
+
"gate:list": {
|
|
1318
|
+
payload: {
|
|
1319
|
+
open?: boolean;
|
|
1320
|
+
subjectPrefix?: string;
|
|
1321
|
+
kind?: string;
|
|
1322
|
+
limit?: number;
|
|
1323
|
+
cursor?: number;
|
|
1324
|
+
};
|
|
1325
|
+
data: {
|
|
1326
|
+
gates: GateRow[];
|
|
1327
|
+
cursor: number;
|
|
1328
|
+
};
|
|
1329
|
+
};
|
|
1330
|
+
"gate:park": {
|
|
1331
|
+
payload: {
|
|
1332
|
+
id: string;
|
|
1333
|
+
};
|
|
1334
|
+
data: {
|
|
1335
|
+
ok: true;
|
|
1336
|
+
};
|
|
1337
|
+
};
|
|
1338
|
+
"gate:close": {
|
|
1339
|
+
payload: {
|
|
1340
|
+
id: string;
|
|
1341
|
+
reason: "abandoned" | "superseded" | "pruned";
|
|
1342
|
+
};
|
|
1343
|
+
data: {
|
|
1344
|
+
ok: true;
|
|
1345
|
+
};
|
|
1346
|
+
};
|
|
1347
|
+
"gate:subscribe": {
|
|
1348
|
+
payload: {
|
|
1349
|
+
subjectPrefix: string;
|
|
1350
|
+
session: string;
|
|
1351
|
+
};
|
|
1352
|
+
data: {
|
|
1353
|
+
id: string;
|
|
1354
|
+
};
|
|
1355
|
+
};
|
|
1356
|
+
"gate:unsubscribe": {
|
|
1357
|
+
payload: {
|
|
1358
|
+
id: string;
|
|
1359
|
+
};
|
|
1360
|
+
data: {
|
|
1361
|
+
removed: boolean;
|
|
1362
|
+
};
|
|
1363
|
+
};
|
|
1364
|
+
/** The shepherd's gap-recovery liveness check and the observability window
|
|
1365
|
+
* onto delivery outcomes (dead marks included). */
|
|
1366
|
+
"gate:subscriptions": {
|
|
1367
|
+
payload: {
|
|
1368
|
+
session?: string;
|
|
1369
|
+
live?: boolean;
|
|
1370
|
+
};
|
|
1371
|
+
data: {
|
|
1372
|
+
subscriptions: GateSubscription[];
|
|
1373
|
+
};
|
|
1374
|
+
};
|
|
1208
1375
|
/** Wire reply on success is always `{ok:true, repaired}` (no `data`
|
|
1209
1376
|
* wrapper) — `data` here documents the extra field the same way PingData
|
|
1210
1377
|
* does for `ping`, not the literal wire nesting (R3). */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export { rtCommand, DEFAULT_SOCK } from "./transport.ts";
|
|
2
2
|
export type { RtResponse, RtClientOptions } from "./transport.ts";
|
|
3
|
-
export { readProjectMRs, readDiscussions, readMrsByBranch, readBranchCache, resolveForgeToken, listRuns, getRun, abandonRun, chatJoin, chatLeave, chatAck, chatClaim, chatRelease, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatSignIn, chatSignOut, chatAway, chatBack, chatBuddies, chatDm, chatArchive, chatDmOpen, eventsHead, eventsEmit, eventsWait, eventsList, agentStart, agentResume, agentGet, agentList, paneList, panePeek, paneSpawn, paneAccounts, paneDirectories, chatInvite, paneSend, paneFocus, } from "./client.ts";
|
|
4
|
-
export { COMMAND_NAMES } from "./commands.ts";
|
|
5
|
-
export type { Discussion, DemandDecl, ProjectMRsScope, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, BranchEnrichment, Commands, CommandName, ForgeSlug, ForgeTokenData, Attention, RunSummary, RunStageRow, RunFieldRow, RunDecisionRow, RunDetail, WakeMode, ChatMember, ChatMessage, ChatClaimOutcome, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, PaneFocusResult, } from "./commands.ts";
|
|
3
|
+
export { readProjectMRs, readDiscussions, readMrsByBranch, readBranchCache, resolveForgeToken, listRuns, getRun, abandonRun, chatJoin, chatLeave, chatAck, chatClaim, chatRelease, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatSignIn, chatSignOut, chatAway, chatBack, chatBuddies, chatDm, chatArchive, chatDmOpen, eventsHead, eventsEmit, eventsWait, eventsList, agentStart, agentResume, agentGet, agentList, paneList, panePeek, paneSpawn, paneAccounts, paneDirectories, chatInvite, paneSend, paneFocus, gateOpen, gateAnswer, gateWait, gateList, gatePark, gateClose, gateSubscribe, gateUnsubscribe, gateSubscriptions, } from "./client.ts";
|
|
4
|
+
export { COMMAND_NAMES, GATE_BY_PANE } from "./commands.ts";
|
|
5
|
+
export type { Discussion, DemandDecl, ProjectMRsScope, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, BranchEnrichment, Commands, CommandName, ForgeSlug, ForgeTokenData, Attention, RunSummary, RunStageRow, RunFieldRow, RunDecisionRow, RunDetail, WakeMode, ChatMember, ChatMessage, ChatClaimOutcome, RoomSummary, BuddyStatus, PresenceRow, AgentRecord, AgentSurface, AgentStatus, ChatPane, PaneAccount, PaneDirectory, InviteResult, PaneDelivery, PaneSendResult, PaneFocusResult, GateStatus, GateQuestion, GateAnswer, GateRow, GateSubscription, } from "./commands.ts";
|
|
6
6
|
export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";
|
|
7
7
|
export type { RelayEventType } from "./relay.ts";
|
|
8
8
|
export { daemonHealth } from "./health.ts";
|
package/dist/index.js
CHANGED
|
@@ -268,7 +268,50 @@ function paneSend(a, o = {}) {
|
|
|
268
268
|
function paneFocus(a, o = {}) {
|
|
269
269
|
return rtCommand("pane:focus", { paneId: a.paneId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
270
270
|
}
|
|
271
|
+
function gateOpen(a, o = {}) {
|
|
272
|
+
const payload = { subject: a.subject, kind: a.kind, questions: a.questions };
|
|
273
|
+
for (const k of ["meta", "agent", "pane", "nudge"])
|
|
274
|
+
if (a[k] !== undefined)
|
|
275
|
+
payload[k] = a[k];
|
|
276
|
+
return rtCommand("gate:open", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
277
|
+
}
|
|
278
|
+
function gateAnswer(a, o = {}) {
|
|
279
|
+
return rtCommand("gate:answer", { id: a.id, answers: a.answers, by: a.by }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
280
|
+
}
|
|
281
|
+
function gateWait(a, o = {}) {
|
|
282
|
+
const payload = { id: a.id };
|
|
283
|
+
if (a.waitMs !== undefined)
|
|
284
|
+
payload.waitMs = a.waitMs;
|
|
285
|
+
return rtCommand("gate:wait", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 250000 });
|
|
286
|
+
}
|
|
287
|
+
function gateList(a, o = {}) {
|
|
288
|
+
const payload = {};
|
|
289
|
+
for (const k of ["open", "subjectPrefix", "kind", "limit", "cursor"])
|
|
290
|
+
if (a[k] !== undefined)
|
|
291
|
+
payload[k] = a[k];
|
|
292
|
+
return rtCommand("gate:list", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
293
|
+
}
|
|
294
|
+
function gatePark(a, o = {}) {
|
|
295
|
+
return rtCommand("gate:park", { id: a.id }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
296
|
+
}
|
|
297
|
+
function gateClose(a, o = {}) {
|
|
298
|
+
return rtCommand("gate:close", { id: a.id, reason: a.reason }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
299
|
+
}
|
|
300
|
+
function gateSubscribe(a, o = {}) {
|
|
301
|
+
return rtCommand("gate:subscribe", { subjectPrefix: a.subjectPrefix, session: a.session }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
302
|
+
}
|
|
303
|
+
function gateUnsubscribe(a, o = {}) {
|
|
304
|
+
return rtCommand("gate:unsubscribe", { id: a.id }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
305
|
+
}
|
|
306
|
+
function gateSubscriptions(a, o = {}) {
|
|
307
|
+
const payload = {};
|
|
308
|
+
for (const k of ["session", "live"])
|
|
309
|
+
if (a[k] !== undefined)
|
|
310
|
+
payload[k] = a[k];
|
|
311
|
+
return rtCommand("gate:subscriptions", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
312
|
+
}
|
|
271
313
|
// src/commands.ts
|
|
314
|
+
var GATE_BY_PANE = "pane";
|
|
272
315
|
var COMMAND_NAMES = [
|
|
273
316
|
"project-mrs:read",
|
|
274
317
|
"discussions:read",
|
|
@@ -337,6 +380,15 @@ var COMMAND_NAMES = [
|
|
|
337
380
|
"endpoint:status",
|
|
338
381
|
"repos:locate",
|
|
339
382
|
"freshness:reconcile",
|
|
383
|
+
"gate:open",
|
|
384
|
+
"gate:answer",
|
|
385
|
+
"gate:wait",
|
|
386
|
+
"gate:list",
|
|
387
|
+
"gate:park",
|
|
388
|
+
"gate:close",
|
|
389
|
+
"gate:subscribe",
|
|
390
|
+
"gate:unsubscribe",
|
|
391
|
+
"gate:subscriptions",
|
|
340
392
|
"hooks:repair",
|
|
341
393
|
"hooks:watch",
|
|
342
394
|
"sdm:catalog",
|
|
@@ -688,6 +740,15 @@ var REGISTRY = [
|
|
|
688
740
|
migrated: true,
|
|
689
741
|
description: "Home-repo snapshot daemon config: enabled, debounce/push delays, and the janitor threshold/interval for zones left dirty too long."
|
|
690
742
|
},
|
|
743
|
+
{
|
|
744
|
+
key: "rt.teamSnapshot",
|
|
745
|
+
type: "object",
|
|
746
|
+
scopes: ["machine"],
|
|
747
|
+
default: { enabled: true, debounceSec: 20, pushDelaySec: 60, janitorThresholdHours: 6, janitorIntervalMin: 30, pullIntervalSec: 300 },
|
|
748
|
+
merge: "deep",
|
|
749
|
+
migrated: true,
|
|
750
|
+
description: "Team-clone snapshot daemon config: the home snapshot's fields plus pullIntervalSec, the fast-forward/rebase pull cadence for every clone under ~/.mattstack/teams."
|
|
751
|
+
},
|
|
691
752
|
{
|
|
692
753
|
key: "rt.sync",
|
|
693
754
|
type: "object",
|
|
@@ -2031,6 +2092,15 @@ export {
|
|
|
2031
2092
|
getSetting,
|
|
2032
2093
|
getRun,
|
|
2033
2094
|
getDef,
|
|
2095
|
+
gateWait,
|
|
2096
|
+
gateUnsubscribe,
|
|
2097
|
+
gateSubscriptions,
|
|
2098
|
+
gateSubscribe,
|
|
2099
|
+
gatePark,
|
|
2100
|
+
gateOpen,
|
|
2101
|
+
gateList,
|
|
2102
|
+
gateClose,
|
|
2103
|
+
gateAnswer,
|
|
2034
2104
|
explainSetting,
|
|
2035
2105
|
expandVariables,
|
|
2036
2106
|
eventsWait,
|
|
@@ -2070,6 +2140,7 @@ export {
|
|
|
2070
2140
|
abandonRun,
|
|
2071
2141
|
SCOPE_ORDER,
|
|
2072
2142
|
REGISTRY,
|
|
2143
|
+
GATE_BY_PANE,
|
|
2073
2144
|
DEFAULT_WS_URL,
|
|
2074
2145
|
DEFAULT_SOCK,
|
|
2075
2146
|
COMMAND_NAMES
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mattstack/rt-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"./test/fake-daemon.ts": "./test/fake-daemon.ts"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@mattstack/glance": ">=0.
|
|
21
|
+
"@mattstack/glance": ">=0.24.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"jsonc-parser": "^3.3.1"
|
package/src/client.ts
CHANGED
|
@@ -442,3 +442,79 @@ export function paneFocus(
|
|
|
442
442
|
): Promise<RtResponse<Commands["pane:focus"]["data"]>> {
|
|
443
443
|
return rtCommand<Commands["pane:focus"]["data"]>("pane:focus", { paneId: a.paneId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
444
444
|
}
|
|
445
|
+
|
|
446
|
+
// ─── Gates (BOARD-20/21 gate facility) ─────────────────────────────────────
|
|
447
|
+
|
|
448
|
+
export function gateOpen(
|
|
449
|
+
a: Commands["gate:open"]["payload"],
|
|
450
|
+
o: RtClientOptions = {},
|
|
451
|
+
): Promise<RtResponse<Commands["gate:open"]["data"]>> {
|
|
452
|
+
const payload: Record<string, unknown> = { subject: a.subject, kind: a.kind, questions: a.questions };
|
|
453
|
+
for (const k of ["meta", "agent", "pane", "nudge"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
454
|
+
return rtCommand<Commands["gate:open"]["data"]>("gate:open", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function gateAnswer(
|
|
458
|
+
a: Commands["gate:answer"]["payload"],
|
|
459
|
+
o: RtClientOptions = {},
|
|
460
|
+
): Promise<RtResponse<Commands["gate:answer"]["data"]>> {
|
|
461
|
+
return rtCommand<Commands["gate:answer"]["data"]>("gate:answer", { id: a.id, answers: a.answers, by: a.by }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/** Daemon clamps its own wait to 240s (gates-store.ts); the client abort
|
|
465
|
+
must outlive that cap, same +10s buffer as commands/events.ts's
|
|
466
|
+
IPC_TIMEOUT_MS over DAEMON_WAIT_MS. */
|
|
467
|
+
export function gateWait(
|
|
468
|
+
a: Commands["gate:wait"]["payload"],
|
|
469
|
+
o: RtClientOptions = {},
|
|
470
|
+
): Promise<RtResponse<Commands["gate:wait"]["data"]>> {
|
|
471
|
+
const payload: Record<string, unknown> = { id: a.id };
|
|
472
|
+
if (a.waitMs !== undefined) payload.waitMs = a.waitMs;
|
|
473
|
+
return rtCommand<Commands["gate:wait"]["data"]>("gate:wait", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 250_000 });
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export function gateList(
|
|
477
|
+
a: Commands["gate:list"]["payload"],
|
|
478
|
+
o: RtClientOptions = {},
|
|
479
|
+
): Promise<RtResponse<Commands["gate:list"]["data"]>> {
|
|
480
|
+
const payload: Record<string, unknown> = {};
|
|
481
|
+
for (const k of ["open", "subjectPrefix", "kind", "limit", "cursor"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
482
|
+
return rtCommand<Commands["gate:list"]["data"]>("gate:list", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
export function gatePark(
|
|
486
|
+
a: Commands["gate:park"]["payload"],
|
|
487
|
+
o: RtClientOptions = {},
|
|
488
|
+
): Promise<RtResponse<Commands["gate:park"]["data"]>> {
|
|
489
|
+
return rtCommand<Commands["gate:park"]["data"]>("gate:park", { id: a.id }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
export function gateClose(
|
|
493
|
+
a: Commands["gate:close"]["payload"],
|
|
494
|
+
o: RtClientOptions = {},
|
|
495
|
+
): Promise<RtResponse<Commands["gate:close"]["data"]>> {
|
|
496
|
+
return rtCommand<Commands["gate:close"]["data"]>("gate:close", { id: a.id, reason: a.reason }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
export function gateSubscribe(
|
|
500
|
+
a: Commands["gate:subscribe"]["payload"],
|
|
501
|
+
o: RtClientOptions = {},
|
|
502
|
+
): Promise<RtResponse<Commands["gate:subscribe"]["data"]>> {
|
|
503
|
+
return rtCommand<Commands["gate:subscribe"]["data"]>("gate:subscribe", { subjectPrefix: a.subjectPrefix, session: a.session }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
export function gateUnsubscribe(
|
|
507
|
+
a: Commands["gate:unsubscribe"]["payload"],
|
|
508
|
+
o: RtClientOptions = {},
|
|
509
|
+
): Promise<RtResponse<Commands["gate:unsubscribe"]["data"]>> {
|
|
510
|
+
return rtCommand<Commands["gate:unsubscribe"]["data"]>("gate:unsubscribe", { id: a.id }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export function gateSubscriptions(
|
|
514
|
+
a: Commands["gate:subscriptions"]["payload"],
|
|
515
|
+
o: RtClientOptions = {},
|
|
516
|
+
): Promise<RtResponse<Commands["gate:subscriptions"]["data"]>> {
|
|
517
|
+
const payload: Record<string, unknown> = {};
|
|
518
|
+
for (const k of ["session", "live"] as const) if (a[k] !== undefined) payload[k] = a[k];
|
|
519
|
+
return rtCommand<Commands["gate:subscriptions"]["data"]>("gate:subscriptions", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
520
|
+
}
|
package/src/commands.ts
CHANGED
|
@@ -83,6 +83,41 @@ export interface ForgeTokenData {
|
|
|
83
83
|
*/
|
|
84
84
|
export interface EventsBusEvent { id: number; topic: string; payload: unknown; emittedAt: number }
|
|
85
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Duplicated shape on purpose, same reasoning as EventsBusEvent above:
|
|
88
|
+
* these mirror lib/daemon/gates-store.ts's types, which rt-client cannot
|
|
89
|
+
* import. gates-store.ts imports them back FROM this package (Commands
|
|
90
|
+
* already flows daemon -> rt-client, e.g. handlers/events.ts), so this is
|
|
91
|
+
* the single source of truth for the wire shape.
|
|
92
|
+
*/
|
|
93
|
+
/** The `by` value a pane spells when it answers its own gate: the only
|
|
94
|
+
value gates-store.ts's release tracking (CAS winner or loser) reacts to. */
|
|
95
|
+
export const GATE_BY_PANE = "pane";
|
|
96
|
+
|
|
97
|
+
export type GateStatus = "open" | "answered" | "parked" | "closed";
|
|
98
|
+
export interface GateQuestion { id: string; label: string; multi: boolean; options: string[] }
|
|
99
|
+
export interface GateAnswer { answers: Record<string, string | string[] | { value: string | string[]; note?: string }>; by: string; answeredAt: number }
|
|
100
|
+
export interface GateRow {
|
|
101
|
+
id: string; subject: string; kind: string;
|
|
102
|
+
questions: GateQuestion[]; meta: Record<string, unknown> | null;
|
|
103
|
+
status: GateStatus; answer: GateAnswer | null;
|
|
104
|
+
openedAt: number; parkedAt: number | null; closedAt: number | null;
|
|
105
|
+
closedReason: "abandoned" | "superseded" | "pruned" | null;
|
|
106
|
+
agent: string | null; pane: string | null;
|
|
107
|
+
nudge: { session: string } | null;
|
|
108
|
+
delivery: { outcome: "delivered" | "dead-pane"; at: number } | null;
|
|
109
|
+
released: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface GateSubscription {
|
|
113
|
+
id: string;
|
|
114
|
+
subjectPrefix: string;
|
|
115
|
+
session: string;
|
|
116
|
+
createdAt: number;
|
|
117
|
+
lastDelivery: { outcome: "delivered" | "failed"; at: number } | null;
|
|
118
|
+
dead: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
86
121
|
/**
|
|
87
122
|
* Duplicated shape on purpose, same reasoning as EventsBusEvent above:
|
|
88
123
|
* these mirror lib/state/chat-store.ts's types, which rt-client cannot
|
|
@@ -538,6 +573,29 @@ export interface Commands {
|
|
|
538
573
|
"repos:locate": { payload: { newPath: string; repo?: string; dryRun?: boolean }; data: unknown };
|
|
539
574
|
"freshness:reconcile": { payload: Record<string, never>; data: unknown };
|
|
540
575
|
|
|
576
|
+
// ─── Gate facility (BOARD-20/21) ─────────────────────────────────────────
|
|
577
|
+
"gate:open": { payload: { subject: string; kind: string; questions: GateQuestion[]; meta?: Record<string, unknown>; agent?: string; pane?: string; nudge?: { session: string } }; data: { id: string; supersededId: string | null } };
|
|
578
|
+
/**
|
|
579
|
+
* A CAS loss is a DEFINED OUTCOME, not an error: `ok:true` with
|
|
580
|
+
* `conflict:true` and the WINNING row, so every consumer gets the winner
|
|
581
|
+
* typed with no envelope hacks. `ok:false` is reserved for
|
|
582
|
+
* not-found/closed/validation failures.
|
|
583
|
+
*/
|
|
584
|
+
"gate:answer": { payload: { id: string; answers: GateAnswer["answers"]; by: string }; data: { row: GateRow; conflict?: true } };
|
|
585
|
+
/** `ok:false "not-found"` on an unknown id is terminal; the CLI loop must not re-enter on it.
|
|
586
|
+
* `timeout` carries no row (nothing settled); `answered`/`closed` always carry the settled row. */
|
|
587
|
+
"gate:wait": { payload: { id: string; waitMs?: number }; data: { status: "timeout" } | { status: "answered" | "closed"; row: GateRow } };
|
|
588
|
+
/** Paged like events:list: an omitted `limit` clamps daemon-side rather than
|
|
589
|
+
* forcing a full-table read; `cursor` is the paging rowid to resume from. */
|
|
590
|
+
"gate:list": { payload: { open?: boolean; subjectPrefix?: string; kind?: string; limit?: number; cursor?: number }; data: { gates: GateRow[]; cursor: number } };
|
|
591
|
+
"gate:park": { payload: { id: string }; data: { ok: true } };
|
|
592
|
+
"gate:close": { payload: { id: string; reason: "abandoned" | "superseded" | "pruned" }; data: { ok: true } };
|
|
593
|
+
"gate:subscribe": { payload: { subjectPrefix: string; session: string }; data: { id: string } };
|
|
594
|
+
"gate:unsubscribe": { payload: { id: string }; data: { removed: boolean } };
|
|
595
|
+
/** The shepherd's gap-recovery liveness check and the observability window
|
|
596
|
+
* onto delivery outcomes (dead marks included). */
|
|
597
|
+
"gate:subscriptions": { payload: { session?: string; live?: boolean }; data: { subscriptions: GateSubscription[] } };
|
|
598
|
+
|
|
541
599
|
/** Wire reply on success is always `{ok:true, repaired}` (no `data`
|
|
542
600
|
* wrapper) — `data` here documents the extra field the same way PingData
|
|
543
601
|
* does for `ping`, not the literal wire nesting (R3). */
|
|
@@ -632,6 +690,15 @@ export const COMMAND_NAMES: readonly CommandName[] = [
|
|
|
632
690
|
"endpoint:status",
|
|
633
691
|
"repos:locate",
|
|
634
692
|
"freshness:reconcile",
|
|
693
|
+
"gate:open",
|
|
694
|
+
"gate:answer",
|
|
695
|
+
"gate:wait",
|
|
696
|
+
"gate:list",
|
|
697
|
+
"gate:park",
|
|
698
|
+
"gate:close",
|
|
699
|
+
"gate:subscribe",
|
|
700
|
+
"gate:unsubscribe",
|
|
701
|
+
"gate:subscriptions",
|
|
635
702
|
"hooks:repair",
|
|
636
703
|
"hooks:watch",
|
|
637
704
|
"sdm:catalog",
|
package/src/index.ts
CHANGED
|
@@ -45,9 +45,18 @@ export {
|
|
|
45
45
|
chatInvite,
|
|
46
46
|
paneSend,
|
|
47
47
|
paneFocus,
|
|
48
|
+
gateOpen,
|
|
49
|
+
gateAnswer,
|
|
50
|
+
gateWait,
|
|
51
|
+
gateList,
|
|
52
|
+
gatePark,
|
|
53
|
+
gateClose,
|
|
54
|
+
gateSubscribe,
|
|
55
|
+
gateUnsubscribe,
|
|
56
|
+
gateSubscriptions,
|
|
48
57
|
} from "./client.ts";
|
|
49
58
|
|
|
50
|
-
export { COMMAND_NAMES } from "./commands.ts";
|
|
59
|
+
export { COMMAND_NAMES, GATE_BY_PANE } from "./commands.ts";
|
|
51
60
|
export type {
|
|
52
61
|
Discussion,
|
|
53
62
|
DemandDecl,
|
|
@@ -84,6 +93,11 @@ export type {
|
|
|
84
93
|
PaneDelivery,
|
|
85
94
|
PaneSendResult,
|
|
86
95
|
PaneFocusResult,
|
|
96
|
+
GateStatus,
|
|
97
|
+
GateQuestion,
|
|
98
|
+
GateAnswer,
|
|
99
|
+
GateRow,
|
|
100
|
+
GateSubscription,
|
|
87
101
|
} from "./commands.ts";
|
|
88
102
|
|
|
89
103
|
export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";
|
|
@@ -137,6 +137,15 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
137
137
|
migrated: true,
|
|
138
138
|
description: "Home-repo snapshot daemon config: enabled, debounce/push delays, and the janitor threshold/interval for zones left dirty too long.",
|
|
139
139
|
},
|
|
140
|
+
{
|
|
141
|
+
key: "rt.teamSnapshot",
|
|
142
|
+
type: "object",
|
|
143
|
+
scopes: ["machine"],
|
|
144
|
+
default: { enabled: true, debounceSec: 20, pushDelaySec: 60, janitorThresholdHours: 6, janitorIntervalMin: 30, pullIntervalSec: 300 },
|
|
145
|
+
merge: "deep",
|
|
146
|
+
migrated: true,
|
|
147
|
+
description: "Team-clone snapshot daemon config: the home snapshot's fields plus pullIntervalSec, the fast-forward/rebase pull cadence for every clone under ~/.mattstack/teams.",
|
|
148
|
+
},
|
|
140
149
|
{
|
|
141
150
|
key: "rt.sync",
|
|
142
151
|
type: "object",
|