@mattstack/rt-client 0.5.0 → 0.6.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 +56 -1
- package/dist/commands.d.ts +109 -7
- package/dist/health.d.ts +11 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +111 -21
- package/dist/relay.d.ts +15 -0
- package/dist/transport.d.ts +9 -0
- package/package.json +1 -1
- package/src/client.ts +93 -17
- package/src/commands.ts +60 -12
- package/src/health.ts +15 -0
- package/src/index.ts +12 -1
- package/src/relay.ts +31 -0
- package/src/settings/registry-defs.ts +7 -7
- package/src/transport.ts +9 -0
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RtResponse, RtClientOptions } from "./transport.ts";
|
|
2
|
-
import type { DemandDecl, ProjectMRsData, DiscussionsData, MrByBranchData, BranchEnrichment, ForgeSlug, ForgeTokenData, RunSummary, RunDetail, WakeMode, ChatMember, ChatMessage, RoomSummary } from "./commands.ts";
|
|
2
|
+
import type { DemandDecl, ProjectMRsData, DiscussionsData, MrByBranchData, BranchEnrichment, ForgeSlug, ForgeTokenData, RunSummary, RunDetail, WakeMode, ChatMember, ChatMessage, RoomSummary, BuddyStatus, PresenceRow } from "./commands.ts";
|
|
3
3
|
/**
|
|
4
4
|
* One repo's project open-MR store. A cold repo forces a full paginated sync
|
|
5
5
|
* on the daemon side when maxAgeMs demands it, which can run tens of seconds
|
|
@@ -58,6 +58,7 @@ export declare function chatPost(a: {
|
|
|
58
58
|
room: string;
|
|
59
59
|
handle: string;
|
|
60
60
|
body: string;
|
|
61
|
+
mentions?: string[];
|
|
61
62
|
}, o?: RtClientOptions): Promise<RtResponse<{
|
|
62
63
|
id: number;
|
|
63
64
|
recipients: string[];
|
|
@@ -97,12 +98,15 @@ export declare function chatMessages(a: {
|
|
|
97
98
|
export declare function chatArm(a: {
|
|
98
99
|
handle: string;
|
|
99
100
|
room?: string;
|
|
101
|
+
sessionId?: string;
|
|
100
102
|
}, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
|
|
101
103
|
export declare function chatTouch(a: {
|
|
102
104
|
handle: string;
|
|
105
|
+
sessionId?: string;
|
|
103
106
|
}, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
|
|
104
107
|
export declare function chatDisarm(a: {
|
|
105
108
|
handle: string;
|
|
109
|
+
sessionId?: string;
|
|
106
110
|
}, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
|
|
107
111
|
export declare function chatUnreadWaking(a: {
|
|
108
112
|
handle: string;
|
|
@@ -115,6 +119,57 @@ export declare function chatUnreadWaking(a: {
|
|
|
115
119
|
maxId: number;
|
|
116
120
|
}[];
|
|
117
121
|
}>>;
|
|
122
|
+
export declare function chatSignIn(a: {
|
|
123
|
+
sessionId: string;
|
|
124
|
+
baseHandle: string;
|
|
125
|
+
cwd?: string;
|
|
126
|
+
repo?: string;
|
|
127
|
+
branch?: string;
|
|
128
|
+
pane?: string;
|
|
129
|
+
statusText?: string;
|
|
130
|
+
}, o?: RtClientOptions): Promise<RtResponse<{
|
|
131
|
+
handle: string;
|
|
132
|
+
reclaimed: boolean;
|
|
133
|
+
}>>;
|
|
134
|
+
export declare function chatSignOut(a: {
|
|
135
|
+
sessionId: string;
|
|
136
|
+
}, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
|
|
137
|
+
export declare function chatAway(a: {
|
|
138
|
+
sessionId: string;
|
|
139
|
+
text: string;
|
|
140
|
+
}, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
|
|
141
|
+
export declare function chatBack(a: {
|
|
142
|
+
sessionId: string;
|
|
143
|
+
}, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
|
|
144
|
+
export declare function chatBuddies(o?: RtClientOptions): Promise<RtResponse<{
|
|
145
|
+
buddies: Array<PresenceRow & {
|
|
146
|
+
status: BuddyStatus;
|
|
147
|
+
}>;
|
|
148
|
+
}>>;
|
|
149
|
+
export declare function chatPulse(a: {
|
|
150
|
+
sessionId: string;
|
|
151
|
+
cwd?: string;
|
|
152
|
+
repo?: string;
|
|
153
|
+
branch?: string;
|
|
154
|
+
pane?: string;
|
|
155
|
+
}, o?: RtClientOptions): Promise<RtResponse<{
|
|
156
|
+
unread: {
|
|
157
|
+
dms: number;
|
|
158
|
+
mentions: number;
|
|
159
|
+
rooms: number;
|
|
160
|
+
};
|
|
161
|
+
status: BuddyStatus;
|
|
162
|
+
}>>;
|
|
163
|
+
export declare function chatDm(a: {
|
|
164
|
+
from: string;
|
|
165
|
+
to: string;
|
|
166
|
+
body: string;
|
|
167
|
+
sessionId?: string;
|
|
168
|
+
}, o?: RtClientOptions): Promise<RtResponse<{
|
|
169
|
+
room: string;
|
|
170
|
+
id: number;
|
|
171
|
+
recipients: string[];
|
|
172
|
+
}>>;
|
|
118
173
|
export declare function eventsHead(o?: RtClientOptions): Promise<RtResponse<{
|
|
119
174
|
cursor: number;
|
|
120
175
|
}>>;
|
package/dist/commands.d.ts
CHANGED
|
@@ -10,24 +10,17 @@ export type Discussion = MRDetail["discussions"][number];
|
|
|
10
10
|
export interface DemandDecl {
|
|
11
11
|
client: string;
|
|
12
12
|
authors: string[];
|
|
13
|
-
/** Codeowner sections this client needs covered (spec: second demand axis). */
|
|
14
|
-
codeownerSections?: string[];
|
|
15
13
|
declaredAt: number;
|
|
16
14
|
}
|
|
17
15
|
export interface ProjectMRsScope {
|
|
18
16
|
authors: string[];
|
|
19
17
|
windowDays: number;
|
|
20
18
|
uncovered: string[];
|
|
21
|
-
/** Effective synced section union; absent from a pre-sections daemon. */
|
|
22
|
-
sections?: string[];
|
|
23
|
-
/** Demanded sections not yet swept for this client. */
|
|
24
|
-
uncoveredSections?: string[];
|
|
25
19
|
}
|
|
26
20
|
export interface ProjectMRsData {
|
|
27
21
|
mrs: Record<string, {
|
|
28
22
|
pr: PullRequest;
|
|
29
23
|
fetchedAt: number;
|
|
30
|
-
codeownerSections?: string[];
|
|
31
24
|
}>;
|
|
32
25
|
listSyncedAt: number;
|
|
33
26
|
source: "poll" | "events" | "mutation";
|
|
@@ -103,6 +96,8 @@ export interface ChatMember {
|
|
|
103
96
|
armedAt?: number;
|
|
104
97
|
cwd?: string;
|
|
105
98
|
pane?: string;
|
|
99
|
+
/** Presence-joined by chat:who's handler — the only place this type is ever returned, and it always attaches one. */
|
|
100
|
+
status: BuddyStatus;
|
|
106
101
|
}
|
|
107
102
|
export interface ChatMessage {
|
|
108
103
|
id: number;
|
|
@@ -119,6 +114,35 @@ export interface RoomSummary {
|
|
|
119
114
|
unread: number;
|
|
120
115
|
mentions: number;
|
|
121
116
|
lastPostedAt?: number;
|
|
117
|
+
/** Set only by chat:rooms's left join against chat_dms. */
|
|
118
|
+
kind?: "dm";
|
|
119
|
+
participants?: {
|
|
120
|
+
a: string;
|
|
121
|
+
b: string;
|
|
122
|
+
};
|
|
123
|
+
/** Set only by chat:rooms's left join against chat_room_defaults; undefined for a room never stamped a default (every DM room included). */
|
|
124
|
+
defaultWake?: WakeMode;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Duplicated shape on purpose, same reasoning as ChatMember/ChatMessage
|
|
128
|
+
* above: mirrors lib/state/presence-store.ts's types, which rt-client
|
|
129
|
+
* cannot import.
|
|
130
|
+
*/
|
|
131
|
+
export type BuddyStatus = "live" | "idle" | "deaf" | "offline";
|
|
132
|
+
export interface PresenceRow {
|
|
133
|
+
sessionId: string;
|
|
134
|
+
handle: string;
|
|
135
|
+
baseHandle: string;
|
|
136
|
+
cwd?: string;
|
|
137
|
+
repo?: string;
|
|
138
|
+
branch?: string;
|
|
139
|
+
pane?: string;
|
|
140
|
+
statusText?: string;
|
|
141
|
+
signedInAt: number;
|
|
142
|
+
lastSeenAt: number;
|
|
143
|
+
tailSeenAt?: number;
|
|
144
|
+
armedAt?: number;
|
|
145
|
+
signedOutAt?: number;
|
|
122
146
|
}
|
|
123
147
|
export type Attention = {
|
|
124
148
|
needs: boolean;
|
|
@@ -156,6 +180,7 @@ export interface RunSummary {
|
|
|
156
180
|
stages?: {
|
|
157
181
|
name: string;
|
|
158
182
|
status: string;
|
|
183
|
+
started_at: number | null;
|
|
159
184
|
}[];
|
|
160
185
|
}
|
|
161
186
|
export interface RunAgent {
|
|
@@ -359,6 +384,7 @@ export interface Commands {
|
|
|
359
384
|
room: string;
|
|
360
385
|
handle: string;
|
|
361
386
|
body: string;
|
|
387
|
+
mentions?: string[];
|
|
362
388
|
};
|
|
363
389
|
data: {
|
|
364
390
|
id: number;
|
|
@@ -416,18 +442,21 @@ export interface Commands {
|
|
|
416
442
|
payload: {
|
|
417
443
|
handle: string;
|
|
418
444
|
room?: string;
|
|
445
|
+
sessionId?: string;
|
|
419
446
|
};
|
|
420
447
|
data: Record<string, never>;
|
|
421
448
|
};
|
|
422
449
|
"chat:touch": {
|
|
423
450
|
payload: {
|
|
424
451
|
handle: string;
|
|
452
|
+
sessionId?: string;
|
|
425
453
|
};
|
|
426
454
|
data: Record<string, never>;
|
|
427
455
|
};
|
|
428
456
|
"chat:disarm": {
|
|
429
457
|
payload: {
|
|
430
458
|
handle: string;
|
|
459
|
+
sessionId?: string;
|
|
431
460
|
};
|
|
432
461
|
data: Record<string, never>;
|
|
433
462
|
};
|
|
@@ -445,6 +474,79 @@ export interface Commands {
|
|
|
445
474
|
}[];
|
|
446
475
|
};
|
|
447
476
|
};
|
|
477
|
+
"chat:sign-in": {
|
|
478
|
+
payload: {
|
|
479
|
+
sessionId: string;
|
|
480
|
+
baseHandle: string;
|
|
481
|
+
cwd?: string;
|
|
482
|
+
repo?: string;
|
|
483
|
+
branch?: string;
|
|
484
|
+
pane?: string;
|
|
485
|
+
statusText?: string;
|
|
486
|
+
};
|
|
487
|
+
data: {
|
|
488
|
+
handle: string;
|
|
489
|
+
reclaimed: boolean;
|
|
490
|
+
};
|
|
491
|
+
};
|
|
492
|
+
"chat:sign-out": {
|
|
493
|
+
payload: {
|
|
494
|
+
sessionId: string;
|
|
495
|
+
};
|
|
496
|
+
data: Record<string, never>;
|
|
497
|
+
};
|
|
498
|
+
"chat:away": {
|
|
499
|
+
payload: {
|
|
500
|
+
sessionId: string;
|
|
501
|
+
text: string;
|
|
502
|
+
};
|
|
503
|
+
data: Record<string, never>;
|
|
504
|
+
};
|
|
505
|
+
"chat:back": {
|
|
506
|
+
payload: {
|
|
507
|
+
sessionId: string;
|
|
508
|
+
};
|
|
509
|
+
data: Record<string, never>;
|
|
510
|
+
};
|
|
511
|
+
"chat:buddies": {
|
|
512
|
+
payload: Record<string, never>;
|
|
513
|
+
data: {
|
|
514
|
+
buddies: Array<PresenceRow & {
|
|
515
|
+
status: BuddyStatus;
|
|
516
|
+
}>;
|
|
517
|
+
};
|
|
518
|
+
};
|
|
519
|
+
/** `unread`'s three fields are disjoint and sum to the true total: `dms` is DM-room waking count; `mentions` is non-DM waking mentions; `rooms` is non-DM waking count minus those mentions (never negative). */
|
|
520
|
+
"chat:pulse": {
|
|
521
|
+
payload: {
|
|
522
|
+
sessionId: string;
|
|
523
|
+
cwd?: string;
|
|
524
|
+
repo?: string;
|
|
525
|
+
branch?: string;
|
|
526
|
+
pane?: string;
|
|
527
|
+
};
|
|
528
|
+
data: {
|
|
529
|
+
unread: {
|
|
530
|
+
dms: number;
|
|
531
|
+
mentions: number;
|
|
532
|
+
rooms: number;
|
|
533
|
+
};
|
|
534
|
+
status: BuddyStatus;
|
|
535
|
+
};
|
|
536
|
+
};
|
|
537
|
+
"chat:dm": {
|
|
538
|
+
payload: {
|
|
539
|
+
from: string;
|
|
540
|
+
to: string;
|
|
541
|
+
body: string;
|
|
542
|
+
sessionId?: string;
|
|
543
|
+
};
|
|
544
|
+
data: {
|
|
545
|
+
room: string;
|
|
546
|
+
id: number;
|
|
547
|
+
recipients: string[];
|
|
548
|
+
};
|
|
549
|
+
};
|
|
448
550
|
}
|
|
449
551
|
export type CommandName = keyof Commands;
|
|
450
552
|
export declare const COMMAND_NAMES: readonly CommandName[];
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RtClientOptions } from "./transport.ts";
|
|
2
|
+
/**
|
|
3
|
+
* A daemon-down result is a successful probe, not a failure of this call —
|
|
4
|
+
* eventsHead already never throws (transport.ts degrades every fetch to
|
|
5
|
+
* `{ ok: false, error }`), so this only reshapes that envelope for callers
|
|
6
|
+
* who want a boolean, not `{ ok, data, error }`.
|
|
7
|
+
*/
|
|
8
|
+
export declare function daemonHealth(opts?: RtClientOptions): Promise<{
|
|
9
|
+
reachable: boolean;
|
|
10
|
+
error?: string;
|
|
11
|
+
}>;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
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, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatArm, chatTouch, chatDisarm, chatUnreadWaking, eventsHead, } from "./client.ts";
|
|
3
|
+
export { readProjectMRs, readDiscussions, readMrsByBranch, readBranchCache, resolveForgeToken, listRuns, getRun, abandonRun, chatJoin, chatLeave, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatArm, chatTouch, chatDisarm, chatUnreadWaking, chatSignIn, chatSignOut, chatAway, chatBack, chatBuddies, chatPulse, chatDm, eventsHead, } from "./client.ts";
|
|
4
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, RoomSummary, } from "./commands.ts";
|
|
6
|
-
export { subscribe, DEFAULT_WS_URL } from "./relay.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, RoomSummary, BuddyStatus, PresenceRow, } from "./commands.ts";
|
|
6
|
+
export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";
|
|
7
7
|
export type { RelayEventType } from "./relay.ts";
|
|
8
|
+
export { daemonHealth } from "./health.ts";
|
|
8
9
|
export { repoNameForPath } from "./repos.ts";
|
|
9
10
|
export { getSetting, listSettings, explainSetting, expandVariables, SCOPE_ORDER } from "./settings/resolve.ts";
|
|
10
11
|
export type { Scope, Provenance, ResolveOpts, Resolved, InvalidScope, ListedSetting, ExplainRow, ExpandCtx, } from "./settings/resolve.ts";
|
package/dist/index.js
CHANGED
|
@@ -73,13 +73,16 @@ function chatJoin(a, o = {}) {
|
|
|
73
73
|
payload.cwd = a.cwd;
|
|
74
74
|
if (a.pane !== undefined)
|
|
75
75
|
payload.pane = a.pane;
|
|
76
|
-
return rtCommand("chat:join", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
76
|
+
return rtCommand("chat:join", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
77
77
|
}
|
|
78
78
|
function chatLeave(a, o = {}) {
|
|
79
|
-
return rtCommand("chat:leave", { room: a.room, handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
79
|
+
return rtCommand("chat:leave", { room: a.room, handle: a.handle }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
80
80
|
}
|
|
81
81
|
function chatPost(a, o = {}) {
|
|
82
|
-
|
|
82
|
+
const payload = { room: a.room, handle: a.handle, body: a.body };
|
|
83
|
+
if (a.mentions !== undefined)
|
|
84
|
+
payload.mentions = a.mentions;
|
|
85
|
+
return rtCommand("chat:post", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
83
86
|
}
|
|
84
87
|
function chatRead(a, o = {}) {
|
|
85
88
|
const payload = { handle: a.handle };
|
|
@@ -89,19 +92,19 @@ function chatRead(a, o = {}) {
|
|
|
89
92
|
payload.limit = a.limit;
|
|
90
93
|
if (a.sinceMs !== undefined)
|
|
91
94
|
payload.sinceMs = a.sinceMs;
|
|
92
|
-
return rtCommand("chat:read", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
95
|
+
return rtCommand("chat:read", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
93
96
|
}
|
|
94
97
|
function chatRooms(a, o = {}) {
|
|
95
|
-
return rtCommand("chat:rooms", { handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
98
|
+
return rtCommand("chat:rooms", { handle: a.handle }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
96
99
|
}
|
|
97
100
|
function chatWho(a, o = {}) {
|
|
98
|
-
return rtCommand("chat:who", { room: a.room }, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
101
|
+
return rtCommand("chat:who", { room: a.room }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
99
102
|
}
|
|
100
103
|
function chatMark(a, o = {}) {
|
|
101
104
|
const payload = { handle: a.handle };
|
|
102
105
|
if (a.room !== undefined)
|
|
103
106
|
payload.room = a.room;
|
|
104
|
-
return rtCommand("chat:mark", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
107
|
+
return rtCommand("chat:mark", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
105
108
|
}
|
|
106
109
|
function chatMessages(a, o = {}) {
|
|
107
110
|
const payload = { room: a.room };
|
|
@@ -109,28 +112,80 @@ function chatMessages(a, o = {}) {
|
|
|
109
112
|
payload.before = a.before;
|
|
110
113
|
if (a.limit !== undefined)
|
|
111
114
|
payload.limit = a.limit;
|
|
112
|
-
return rtCommand("chat:messages", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
115
|
+
return rtCommand("chat:messages", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
113
116
|
}
|
|
114
117
|
function chatArm(a, o = {}) {
|
|
115
118
|
const payload = { handle: a.handle };
|
|
116
119
|
if (a.room !== undefined)
|
|
117
120
|
payload.room = a.room;
|
|
118
|
-
|
|
121
|
+
if (a.sessionId !== undefined)
|
|
122
|
+
payload.sessionId = a.sessionId;
|
|
123
|
+
return rtCommand("chat:arm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
119
124
|
}
|
|
120
125
|
function chatTouch(a, o = {}) {
|
|
121
|
-
|
|
126
|
+
const payload = { handle: a.handle };
|
|
127
|
+
if (a.sessionId !== undefined)
|
|
128
|
+
payload.sessionId = a.sessionId;
|
|
129
|
+
return rtCommand("chat:touch", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
122
130
|
}
|
|
123
131
|
function chatDisarm(a, o = {}) {
|
|
124
|
-
|
|
132
|
+
const payload = { handle: a.handle };
|
|
133
|
+
if (a.sessionId !== undefined)
|
|
134
|
+
payload.sessionId = a.sessionId;
|
|
135
|
+
return rtCommand("chat:disarm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
125
136
|
}
|
|
126
137
|
function chatUnreadWaking(a, o = {}) {
|
|
127
138
|
const payload = { handle: a.handle };
|
|
128
139
|
if (a.room !== undefined)
|
|
129
140
|
payload.room = a.room;
|
|
130
|
-
return rtCommand("chat:unread-waking", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
141
|
+
return rtCommand("chat:unread-waking", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
142
|
+
}
|
|
143
|
+
function chatSignIn(a, o = {}) {
|
|
144
|
+
const payload = { sessionId: a.sessionId, baseHandle: a.baseHandle };
|
|
145
|
+
if (a.cwd !== undefined)
|
|
146
|
+
payload.cwd = a.cwd;
|
|
147
|
+
if (a.repo !== undefined)
|
|
148
|
+
payload.repo = a.repo;
|
|
149
|
+
if (a.branch !== undefined)
|
|
150
|
+
payload.branch = a.branch;
|
|
151
|
+
if (a.pane !== undefined)
|
|
152
|
+
payload.pane = a.pane;
|
|
153
|
+
if (a.statusText !== undefined)
|
|
154
|
+
payload.statusText = a.statusText;
|
|
155
|
+
return rtCommand("chat:sign-in", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
156
|
+
}
|
|
157
|
+
function chatSignOut(a, o = {}) {
|
|
158
|
+
return rtCommand("chat:sign-out", { sessionId: a.sessionId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
159
|
+
}
|
|
160
|
+
function chatAway(a, o = {}) {
|
|
161
|
+
return rtCommand("chat:away", { sessionId: a.sessionId, text: a.text }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
162
|
+
}
|
|
163
|
+
function chatBack(a, o = {}) {
|
|
164
|
+
return rtCommand("chat:back", { sessionId: a.sessionId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
165
|
+
}
|
|
166
|
+
function chatBuddies(o = {}) {
|
|
167
|
+
return rtCommand("chat:buddies", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
168
|
+
}
|
|
169
|
+
function chatPulse(a, o = {}) {
|
|
170
|
+
const payload = { sessionId: a.sessionId };
|
|
171
|
+
if (a.cwd !== undefined)
|
|
172
|
+
payload.cwd = a.cwd;
|
|
173
|
+
if (a.repo !== undefined)
|
|
174
|
+
payload.repo = a.repo;
|
|
175
|
+
if (a.branch !== undefined)
|
|
176
|
+
payload.branch = a.branch;
|
|
177
|
+
if (a.pane !== undefined)
|
|
178
|
+
payload.pane = a.pane;
|
|
179
|
+
return rtCommand("chat:pulse", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
180
|
+
}
|
|
181
|
+
function chatDm(a, o = {}) {
|
|
182
|
+
const payload = { from: a.from, to: a.to, body: a.body };
|
|
183
|
+
if (a.sessionId !== undefined)
|
|
184
|
+
payload.sessionId = a.sessionId;
|
|
185
|
+
return rtCommand("chat:dm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
131
186
|
}
|
|
132
187
|
function eventsHead(o = {}) {
|
|
133
|
-
return rtCommand("events:head", {}, { sockPath: o.sockPath, timeoutMs: 1e4 });
|
|
188
|
+
return rtCommand("events:head", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 1e4 });
|
|
134
189
|
}
|
|
135
190
|
// src/commands.ts
|
|
136
191
|
var COMMAND_NAMES = [
|
|
@@ -157,7 +212,14 @@ var COMMAND_NAMES = [
|
|
|
157
212
|
"chat:arm",
|
|
158
213
|
"chat:touch",
|
|
159
214
|
"chat:disarm",
|
|
160
|
-
"chat:unread-waking"
|
|
215
|
+
"chat:unread-waking",
|
|
216
|
+
"chat:sign-in",
|
|
217
|
+
"chat:sign-out",
|
|
218
|
+
"chat:away",
|
|
219
|
+
"chat:back",
|
|
220
|
+
"chat:buddies",
|
|
221
|
+
"chat:pulse",
|
|
222
|
+
"chat:dm"
|
|
161
223
|
];
|
|
162
224
|
// src/relay.ts
|
|
163
225
|
var DEFAULT_WS_URL = "ws://127.0.0.1:9401/ws";
|
|
@@ -202,6 +264,25 @@ function subscribe(onEvent, opts = {}) {
|
|
|
202
264
|
} catch {}
|
|
203
265
|
};
|
|
204
266
|
}
|
|
267
|
+
function createRelay(cfg, opts = {}) {
|
|
268
|
+
const doSubscribe = opts.subscribeImpl ?? subscribe;
|
|
269
|
+
return doSubscribe((type, data) => {
|
|
270
|
+
if (type !== "event")
|
|
271
|
+
return;
|
|
272
|
+
const frame = data;
|
|
273
|
+
if (typeof frame?.topic !== "string" || !cfg.match(frame.topic))
|
|
274
|
+
return;
|
|
275
|
+
const payload = JSON.stringify(data);
|
|
276
|
+
try {
|
|
277
|
+
cfg.publish(cfg.topic, payload);
|
|
278
|
+
} catch {}
|
|
279
|
+
}, opts);
|
|
280
|
+
}
|
|
281
|
+
// src/health.ts
|
|
282
|
+
async function daemonHealth(opts = {}) {
|
|
283
|
+
const res = await eventsHead(opts);
|
|
284
|
+
return { reachable: res.ok, error: res.ok ? undefined : res.error };
|
|
285
|
+
}
|
|
205
286
|
// src/repos.ts
|
|
206
287
|
import { existsSync, readFileSync } from "fs";
|
|
207
288
|
import { homedir as homedir2 } from "os";
|
|
@@ -509,6 +590,13 @@ var REGISTRY = [
|
|
|
509
590
|
merge: "replace",
|
|
510
591
|
description: "Absolute path to the installed mattstack.app bundle, written by the app at launch so rt stops hardcoding ~/Applications."
|
|
511
592
|
},
|
|
593
|
+
{
|
|
594
|
+
key: "mattstack.mode",
|
|
595
|
+
type: "string",
|
|
596
|
+
scopes: ["machine"],
|
|
597
|
+
merge: "replace",
|
|
598
|
+
description: 'The machine\'s intended flavor, "dev" or "prod". Normally written by `rt settings dev-mode` after a successful handoff; a manual `rt settings set` is the blessed repair escape hatch — the daemon park loop converges on whatever this says. Unset ⇒ derived from the dev wrapper\'s presence.'
|
|
599
|
+
},
|
|
512
600
|
{
|
|
513
601
|
key: "rt.integrations",
|
|
514
602
|
type: "object",
|
|
@@ -615,13 +703,6 @@ var REGISTRY = [
|
|
|
615
703
|
merge: "replace",
|
|
616
704
|
description: "Doctor skill the board's own API-tier triage sweep runs on your MRs; deliberately never resolved through a repo's skills.jsonc manifest. A sibling flat key of board.triage, not a field inside it — the board reader assembles the two independently."
|
|
617
705
|
},
|
|
618
|
-
{
|
|
619
|
-
key: "board.tabs",
|
|
620
|
-
type: "array",
|
|
621
|
-
scopes: ["team"],
|
|
622
|
-
merge: "replace",
|
|
623
|
-
description: "Board tab definitions ({id, label, source, slackChannel?, reviewSkill?}); source.kind 'authors' is the classic roster board, 'codeowners' lists MRs blocked on an unapproved CODEOWNERS section. Absent = one implicit authors tab (fallback lives in the board reader, never here)."
|
|
624
|
-
},
|
|
625
706
|
{
|
|
626
707
|
key: "board.staleAfterDays",
|
|
627
708
|
type: "number",
|
|
@@ -1549,18 +1630,27 @@ export {
|
|
|
1549
1630
|
expandVariables,
|
|
1550
1631
|
eventsHead,
|
|
1551
1632
|
deriveRepoIdentity,
|
|
1633
|
+
daemonHealth,
|
|
1634
|
+
createRelay,
|
|
1552
1635
|
clearIdentityMemo,
|
|
1553
1636
|
chatWho,
|
|
1554
1637
|
chatUnreadWaking,
|
|
1555
1638
|
chatTouch,
|
|
1639
|
+
chatSignOut,
|
|
1640
|
+
chatSignIn,
|
|
1556
1641
|
chatRooms,
|
|
1557
1642
|
chatRead,
|
|
1643
|
+
chatPulse,
|
|
1558
1644
|
chatPost,
|
|
1559
1645
|
chatMessages,
|
|
1560
1646
|
chatMark,
|
|
1561
1647
|
chatLeave,
|
|
1562
1648
|
chatJoin,
|
|
1649
|
+
chatDm,
|
|
1563
1650
|
chatDisarm,
|
|
1651
|
+
chatBuddies,
|
|
1652
|
+
chatBack,
|
|
1653
|
+
chatAway,
|
|
1564
1654
|
chatArm,
|
|
1565
1655
|
allDefs,
|
|
1566
1656
|
abandonRun,
|
package/dist/relay.d.ts
CHANGED
|
@@ -13,3 +13,18 @@ export declare const DEFAULT_WS_URL = "ws://127.0.0.1:9401/ws";
|
|
|
13
13
|
* the daemon being down just means silence, never a crash.
|
|
14
14
|
*/
|
|
15
15
|
export declare function subscribe(onEvent: (type: RelayEventType, data: unknown) => void, opts?: RtClientOptions): () => void;
|
|
16
|
+
/**
|
|
17
|
+
* One daemon subscription for the whole process, republished onto a
|
|
18
|
+
* caller-chosen pub/sub topic. Every subscriber to that topic then shares
|
|
19
|
+
* one relay connection instead of each opening its own — filtering here
|
|
20
|
+
* (rather than at each subscriber) is what keeps an unrelated event from
|
|
21
|
+
* making every subscriber re-render.
|
|
22
|
+
*
|
|
23
|
+
* Ported from console's `startRelay` (src/server/ws.ts) with the match
|
|
24
|
+
* predicate and target topic lifted to arguments.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createRelay(cfg: {
|
|
27
|
+
match: (topic: string) => boolean;
|
|
28
|
+
topic: string;
|
|
29
|
+
publish: (topic: string, data: string) => void;
|
|
30
|
+
}, opts?: RtClientOptions): () => void;
|
package/dist/transport.d.ts
CHANGED
|
@@ -6,6 +6,15 @@ export interface RtResponse<T = unknown> {
|
|
|
6
6
|
export interface RtClientOptions {
|
|
7
7
|
sockPath?: string;
|
|
8
8
|
wsUrl?: string;
|
|
9
|
+
/** Per-call override of rtCommand's own default (15s); chat's pulse wrapper needs an 800ms hook budget. */
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Test seam for createRelay (relay.ts): swaps the daemon subscription for
|
|
13
|
+
* a fake without a live WebSocket server. Typed structurally against
|
|
14
|
+
* relay.ts's `subscribe` rather than importing its RelayEventType, which
|
|
15
|
+
* would make this module depend on the one that already depends on it.
|
|
16
|
+
*/
|
|
17
|
+
subscribeImpl?: (onEvent: (type: string, data: unknown) => void, opts?: RtClientOptions) => () => void;
|
|
9
18
|
}
|
|
10
19
|
/**
|
|
11
20
|
* Display-only: a module-load snapshot for callers that just want to show
|
package/package.json
CHANGED
package/src/client.ts
CHANGED
|
@@ -19,6 +19,8 @@ import type {
|
|
|
19
19
|
ChatMember,
|
|
20
20
|
ChatMessage,
|
|
21
21
|
RoomSummary,
|
|
22
|
+
BuddyStatus,
|
|
23
|
+
PresenceRow,
|
|
22
24
|
} from "./commands.ts";
|
|
23
25
|
|
|
24
26
|
/**
|
|
@@ -151,21 +153,23 @@ export function chatJoin(
|
|
|
151
153
|
if (a.wakeOn !== undefined) payload.wakeOn = a.wakeOn;
|
|
152
154
|
if (a.cwd !== undefined) payload.cwd = a.cwd;
|
|
153
155
|
if (a.pane !== undefined) payload.pane = a.pane;
|
|
154
|
-
return rtCommand<{ handle: string; memberCount: number; unread: number }>("chat:join", payload, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
156
|
+
return rtCommand<{ handle: string; memberCount: number; unread: number }>("chat:join", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
export function chatLeave(
|
|
158
160
|
a: { room: string; handle: string },
|
|
159
161
|
o: RtClientOptions = {},
|
|
160
162
|
): Promise<RtResponse<Record<string, never>>> {
|
|
161
|
-
return rtCommand<Record<string, never>>("chat:leave", { room: a.room, handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
163
|
+
return rtCommand<Record<string, never>>("chat:leave", { room: a.room, handle: a.handle }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
162
164
|
}
|
|
163
165
|
|
|
164
166
|
export function chatPost(
|
|
165
|
-
a: { room: string; handle: string; body: string },
|
|
167
|
+
a: { room: string; handle: string; body: string; mentions?: string[] },
|
|
166
168
|
o: RtClientOptions = {},
|
|
167
169
|
): Promise<RtResponse<{ id: number; recipients: string[] }>> {
|
|
168
|
-
|
|
170
|
+
const payload: Record<string, unknown> = { room: a.room, handle: a.handle, body: a.body };
|
|
171
|
+
if (a.mentions !== undefined) payload.mentions = a.mentions;
|
|
172
|
+
return rtCommand<{ id: number; recipients: string[] }>("chat:post", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
169
173
|
}
|
|
170
174
|
|
|
171
175
|
export function chatRead(
|
|
@@ -176,21 +180,21 @@ export function chatRead(
|
|
|
176
180
|
if (a.room !== undefined) payload.room = a.room;
|
|
177
181
|
if (a.limit !== undefined) payload.limit = a.limit;
|
|
178
182
|
if (a.sinceMs !== undefined) payload.sinceMs = a.sinceMs;
|
|
179
|
-
return rtCommand<{ rooms: { room: string; messages: ChatMessage[] }[] }>("chat:read", payload, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
183
|
+
return rtCommand<{ rooms: { room: string; messages: ChatMessage[] }[] }>("chat:read", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
180
184
|
}
|
|
181
185
|
|
|
182
186
|
export function chatRooms(
|
|
183
187
|
a: { handle: string },
|
|
184
188
|
o: RtClientOptions = {},
|
|
185
189
|
): Promise<RtResponse<{ rooms: RoomSummary[] }>> {
|
|
186
|
-
return rtCommand<{ rooms: RoomSummary[] }>("chat:rooms", { handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
190
|
+
return rtCommand<{ rooms: RoomSummary[] }>("chat:rooms", { handle: a.handle }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
export function chatWho(
|
|
190
194
|
a: { room: string },
|
|
191
195
|
o: RtClientOptions = {},
|
|
192
196
|
): Promise<RtResponse<{ members: ChatMember[] }>> {
|
|
193
|
-
return rtCommand<{ members: ChatMember[] }>("chat:who", { room: a.room }, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
197
|
+
return rtCommand<{ members: ChatMember[] }>("chat:who", { room: a.room }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
194
198
|
}
|
|
195
199
|
|
|
196
200
|
export function chatMark(
|
|
@@ -199,7 +203,7 @@ export function chatMark(
|
|
|
199
203
|
): Promise<RtResponse<Record<string, never>>> {
|
|
200
204
|
const payload: Record<string, unknown> = { handle: a.handle };
|
|
201
205
|
if (a.room !== undefined) payload.room = a.room;
|
|
202
|
-
return rtCommand<Record<string, never>>("chat:mark", payload, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
206
|
+
return rtCommand<Record<string, never>>("chat:mark", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
203
207
|
}
|
|
204
208
|
|
|
205
209
|
export function chatMessages(
|
|
@@ -209,30 +213,35 @@ export function chatMessages(
|
|
|
209
213
|
const payload: Record<string, unknown> = { room: a.room };
|
|
210
214
|
if (a.before !== undefined) payload.before = a.before;
|
|
211
215
|
if (a.limit !== undefined) payload.limit = a.limit;
|
|
212
|
-
return rtCommand<{ messages: ChatMessage[] }>("chat:messages", payload, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
216
|
+
return rtCommand<{ messages: ChatMessage[] }>("chat:messages", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
export function chatArm(
|
|
216
|
-
a: { handle: string; room?: string },
|
|
220
|
+
a: { handle: string; room?: string; sessionId?: string },
|
|
217
221
|
o: RtClientOptions = {},
|
|
218
222
|
): Promise<RtResponse<Record<string, never>>> {
|
|
219
223
|
const payload: Record<string, unknown> = { handle: a.handle };
|
|
220
224
|
if (a.room !== undefined) payload.room = a.room;
|
|
221
|
-
|
|
225
|
+
if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
|
|
226
|
+
return rtCommand<Record<string, never>>("chat:arm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
222
227
|
}
|
|
223
228
|
|
|
224
229
|
export function chatTouch(
|
|
225
|
-
a: { handle: string },
|
|
230
|
+
a: { handle: string; sessionId?: string },
|
|
226
231
|
o: RtClientOptions = {},
|
|
227
232
|
): Promise<RtResponse<Record<string, never>>> {
|
|
228
|
-
|
|
233
|
+
const payload: Record<string, unknown> = { handle: a.handle };
|
|
234
|
+
if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
|
|
235
|
+
return rtCommand<Record<string, never>>("chat:touch", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
229
236
|
}
|
|
230
237
|
|
|
231
238
|
export function chatDisarm(
|
|
232
|
-
a: { handle: string },
|
|
239
|
+
a: { handle: string; sessionId?: string },
|
|
233
240
|
o: RtClientOptions = {},
|
|
234
241
|
): Promise<RtResponse<Record<string, never>>> {
|
|
235
|
-
|
|
242
|
+
const payload: Record<string, unknown> = { handle: a.handle };
|
|
243
|
+
if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
|
|
244
|
+
return rtCommand<Record<string, never>>("chat:disarm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
export function chatUnreadWaking(
|
|
@@ -241,9 +250,76 @@ export function chatUnreadWaking(
|
|
|
241
250
|
): Promise<RtResponse<{ rooms: { room: string; count: number; mentions: number; maxId: number }[] }>> {
|
|
242
251
|
const payload: Record<string, unknown> = { handle: a.handle };
|
|
243
252
|
if (a.room !== undefined) payload.room = a.room;
|
|
244
|
-
return rtCommand<{ rooms: { room: string; count: number; mentions: number; maxId: number }[] }>("chat:unread-waking", payload, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
253
|
+
return rtCommand<{ rooms: { room: string; count: number; mentions: number; maxId: number }[] }>("chat:unread-waking", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ─── Presence ──────────────────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
export function chatSignIn(
|
|
259
|
+
a: { sessionId: string; baseHandle: string; cwd?: string; repo?: string; branch?: string; pane?: string; statusText?: string },
|
|
260
|
+
o: RtClientOptions = {},
|
|
261
|
+
): Promise<RtResponse<{ handle: string; reclaimed: boolean }>> {
|
|
262
|
+
const payload: Record<string, unknown> = { sessionId: a.sessionId, baseHandle: a.baseHandle };
|
|
263
|
+
if (a.cwd !== undefined) payload.cwd = a.cwd;
|
|
264
|
+
if (a.repo !== undefined) payload.repo = a.repo;
|
|
265
|
+
if (a.branch !== undefined) payload.branch = a.branch;
|
|
266
|
+
if (a.pane !== undefined) payload.pane = a.pane;
|
|
267
|
+
if (a.statusText !== undefined) payload.statusText = a.statusText;
|
|
268
|
+
return rtCommand<{ handle: string; reclaimed: boolean }>("chat:sign-in", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function chatSignOut(
|
|
272
|
+
a: { sessionId: string },
|
|
273
|
+
o: RtClientOptions = {},
|
|
274
|
+
): Promise<RtResponse<Record<string, never>>> {
|
|
275
|
+
return rtCommand<Record<string, never>>("chat:sign-out", { sessionId: a.sessionId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export function chatAway(
|
|
279
|
+
a: { sessionId: string; text: string },
|
|
280
|
+
o: RtClientOptions = {},
|
|
281
|
+
): Promise<RtResponse<Record<string, never>>> {
|
|
282
|
+
return rtCommand<Record<string, never>>("chat:away", { sessionId: a.sessionId, text: a.text }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function chatBack(
|
|
286
|
+
a: { sessionId: string },
|
|
287
|
+
o: RtClientOptions = {},
|
|
288
|
+
): Promise<RtResponse<Record<string, never>>> {
|
|
289
|
+
return rtCommand<Record<string, never>>("chat:back", { sessionId: a.sessionId }, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function chatBuddies(
|
|
293
|
+
o: RtClientOptions = {},
|
|
294
|
+
): Promise<RtResponse<{ buddies: Array<PresenceRow & { status: BuddyStatus }> }>> {
|
|
295
|
+
return rtCommand<{ buddies: Array<PresenceRow & { status: BuddyStatus }> }>("chat:buddies", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export function chatPulse(
|
|
299
|
+
a: { sessionId: string; cwd?: string; repo?: string; branch?: string; pane?: string },
|
|
300
|
+
o: RtClientOptions = {},
|
|
301
|
+
): Promise<RtResponse<{ unread: { dms: number; mentions: number; rooms: number }; status: BuddyStatus }>> {
|
|
302
|
+
const payload: Record<string, unknown> = { sessionId: a.sessionId };
|
|
303
|
+
if (a.cwd !== undefined) payload.cwd = a.cwd;
|
|
304
|
+
if (a.repo !== undefined) payload.repo = a.repo;
|
|
305
|
+
if (a.branch !== undefined) payload.branch = a.branch;
|
|
306
|
+
if (a.pane !== undefined) payload.pane = a.pane;
|
|
307
|
+
return rtCommand<{ unread: { dms: number; mentions: number; rooms: number }; status: BuddyStatus }>(
|
|
308
|
+
"chat:pulse",
|
|
309
|
+
payload,
|
|
310
|
+
{ sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 },
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export function chatDm(
|
|
315
|
+
a: { from: string; to: string; body: string; sessionId?: string },
|
|
316
|
+
o: RtClientOptions = {},
|
|
317
|
+
): Promise<RtResponse<{ room: string; id: number; recipients: string[] }>> {
|
|
318
|
+
const payload: Record<string, unknown> = { from: a.from, to: a.to, body: a.body };
|
|
319
|
+
if (a.sessionId !== undefined) payload.sessionId = a.sessionId;
|
|
320
|
+
return rtCommand<{ room: string; id: number; recipients: string[] }>("chat:dm", payload, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
245
321
|
}
|
|
246
322
|
|
|
247
323
|
export function eventsHead(o: RtClientOptions = {}): Promise<RtResponse<{ cursor: number }>> {
|
|
248
|
-
return rtCommand<{ cursor: number }>("events:head", {}, { sockPath: o.sockPath, timeoutMs: 10_000 });
|
|
324
|
+
return rtCommand<{ cursor: number }>("events:head", {}, { sockPath: o.sockPath, timeoutMs: o.timeoutMs ?? 10_000 });
|
|
249
325
|
}
|
package/src/commands.ts
CHANGED
|
@@ -12,8 +12,6 @@ export type Discussion = MRDetail["discussions"][number];
|
|
|
12
12
|
export interface DemandDecl {
|
|
13
13
|
client: string;
|
|
14
14
|
authors: string[];
|
|
15
|
-
/** Codeowner sections this client needs covered (spec: second demand axis). */
|
|
16
|
-
codeownerSections?: string[];
|
|
17
15
|
declaredAt: number;
|
|
18
16
|
}
|
|
19
17
|
|
|
@@ -21,14 +19,10 @@ export interface ProjectMRsScope {
|
|
|
21
19
|
authors: string[];
|
|
22
20
|
windowDays: number;
|
|
23
21
|
uncovered: string[];
|
|
24
|
-
/** Effective synced section union; absent from a pre-sections daemon. */
|
|
25
|
-
sections?: string[];
|
|
26
|
-
/** Demanded sections not yet swept for this client. */
|
|
27
|
-
uncoveredSections?: string[];
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
export interface ProjectMRsData {
|
|
31
|
-
mrs: Record<string, { pr: PullRequest; fetchedAt: number
|
|
25
|
+
mrs: Record<string, { pr: PullRequest; fetchedAt: number }>;
|
|
32
26
|
listSyncedAt: number;
|
|
33
27
|
source: "poll" | "events" | "mutation";
|
|
34
28
|
syncedAt: number;
|
|
@@ -96,6 +90,8 @@ export interface ChatMember {
|
|
|
96
90
|
armedAt?: number;
|
|
97
91
|
cwd?: string;
|
|
98
92
|
pane?: string;
|
|
93
|
+
/** Presence-joined by chat:who's handler — the only place this type is ever returned, and it always attaches one. */
|
|
94
|
+
status: BuddyStatus;
|
|
99
95
|
}
|
|
100
96
|
|
|
101
97
|
export interface ChatMessage {
|
|
@@ -114,6 +110,34 @@ export interface RoomSummary {
|
|
|
114
110
|
unread: number;
|
|
115
111
|
mentions: number;
|
|
116
112
|
lastPostedAt?: number;
|
|
113
|
+
/** Set only by chat:rooms's left join against chat_dms. */
|
|
114
|
+
kind?: "dm";
|
|
115
|
+
participants?: { a: string; b: string };
|
|
116
|
+
/** Set only by chat:rooms's left join against chat_room_defaults; undefined for a room never stamped a default (every DM room included). */
|
|
117
|
+
defaultWake?: WakeMode;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Duplicated shape on purpose, same reasoning as ChatMember/ChatMessage
|
|
122
|
+
* above: mirrors lib/state/presence-store.ts's types, which rt-client
|
|
123
|
+
* cannot import.
|
|
124
|
+
*/
|
|
125
|
+
export type BuddyStatus = "live" | "idle" | "deaf" | "offline";
|
|
126
|
+
|
|
127
|
+
export interface PresenceRow {
|
|
128
|
+
sessionId: string;
|
|
129
|
+
handle: string;
|
|
130
|
+
baseHandle: string;
|
|
131
|
+
cwd?: string;
|
|
132
|
+
repo?: string;
|
|
133
|
+
branch?: string;
|
|
134
|
+
pane?: string;
|
|
135
|
+
statusText?: string;
|
|
136
|
+
signedInAt: number;
|
|
137
|
+
lastSeenAt: number;
|
|
138
|
+
tailSeenAt?: number;
|
|
139
|
+
armedAt?: number;
|
|
140
|
+
signedOutAt?: number;
|
|
117
141
|
}
|
|
118
142
|
|
|
119
143
|
// SKILLS-53: one judgment, computed once in rt, so the console and the tray
|
|
@@ -147,7 +171,7 @@ export interface RunSummary {
|
|
|
147
171
|
pre-mirror daemons. */
|
|
148
172
|
agent?: RunAgent | null;
|
|
149
173
|
/** Executed stages only, in run order — the pipeline may define more that have not started. */
|
|
150
|
-
stages?: { name: string; status: string }[];
|
|
174
|
+
stages?: { name: string; status: string; started_at: number | null }[];
|
|
151
175
|
}
|
|
152
176
|
export interface RunAgent {
|
|
153
177
|
status: "working" | "idle" | "blocked" | "done" | "unknown";
|
|
@@ -220,16 +244,33 @@ export interface Commands {
|
|
|
220
244
|
"runs:abandon": { payload: { runId: string; repo?: string; reason?: string }; data: { ok: boolean } };
|
|
221
245
|
"chat:join": { payload: { room: string; handle: string; wakeOn?: WakeMode; cwd?: string; pane?: string }; data: { handle: string; memberCount: number; unread: number } };
|
|
222
246
|
"chat:leave": { payload: { room: string; handle: string }; data: Record<string, never> };
|
|
223
|
-
"chat:post": { payload: { room: string; handle: string; body: string }; data: { id: number; recipients: string[] } };
|
|
247
|
+
"chat:post": { payload: { room: string; handle: string; body: string; mentions?: string[] }; data: { id: number; recipients: string[] } };
|
|
224
248
|
"chat:read": { payload: { handle: string; room?: string; limit?: number; sinceMs?: number }; data: { rooms: { room: string; messages: ChatMessage[] }[] } };
|
|
225
249
|
"chat:rooms": { payload: { handle: string }; data: { rooms: RoomSummary[] } };
|
|
226
250
|
"chat:who": { payload: { room: string }; data: { members: ChatMember[] } };
|
|
227
251
|
"chat:mark": { payload: { handle: string; room?: string }; data: Record<string, never> };
|
|
228
252
|
"chat:messages": { payload: { room: string; before?: number; limit?: number }; data: { messages: ChatMessage[] } };
|
|
229
|
-
"chat:arm": { payload: { handle: string; room?: string }; data: Record<string, never> };
|
|
230
|
-
"chat:touch": { payload: { handle: string }; data: Record<string, never> };
|
|
231
|
-
"chat:disarm": { payload: { handle: string }; data: Record<string, never> };
|
|
253
|
+
"chat:arm": { payload: { handle: string; room?: string; sessionId?: string }; data: Record<string, never> };
|
|
254
|
+
"chat:touch": { payload: { handle: string; sessionId?: string }; data: Record<string, never> };
|
|
255
|
+
"chat:disarm": { payload: { handle: string; sessionId?: string }; data: Record<string, never> };
|
|
232
256
|
"chat:unread-waking": { payload: { handle: string; room?: string }; data: { rooms: { room: string; count: number; mentions: number; maxId: number }[] } };
|
|
257
|
+
|
|
258
|
+
// A session id keys these to one signed-in handle, not a room-membership
|
|
259
|
+
// handle string.
|
|
260
|
+
"chat:sign-in": {
|
|
261
|
+
payload: { sessionId: string; baseHandle: string; cwd?: string; repo?: string; branch?: string; pane?: string; statusText?: string };
|
|
262
|
+
data: { handle: string; reclaimed: boolean };
|
|
263
|
+
};
|
|
264
|
+
"chat:sign-out": { payload: { sessionId: string }; data: Record<string, never> };
|
|
265
|
+
"chat:away": { payload: { sessionId: string; text: string }; data: Record<string, never> };
|
|
266
|
+
"chat:back": { payload: { sessionId: string }; data: Record<string, never> };
|
|
267
|
+
"chat:buddies": { payload: Record<string, never>; data: { buddies: Array<PresenceRow & { status: BuddyStatus }> } };
|
|
268
|
+
/** `unread`'s three fields are disjoint and sum to the true total: `dms` is DM-room waking count; `mentions` is non-DM waking mentions; `rooms` is non-DM waking count minus those mentions (never negative). */
|
|
269
|
+
"chat:pulse": {
|
|
270
|
+
payload: { sessionId: string; cwd?: string; repo?: string; branch?: string; pane?: string };
|
|
271
|
+
data: { unread: { dms: number; mentions: number; rooms: number }; status: BuddyStatus };
|
|
272
|
+
};
|
|
273
|
+
"chat:dm": { payload: { from: string; to: string; body: string; sessionId?: string }; data: { room: string; id: number; recipients: string[] } };
|
|
233
274
|
}
|
|
234
275
|
|
|
235
276
|
export type CommandName = keyof Commands;
|
|
@@ -259,4 +300,11 @@ export const COMMAND_NAMES: readonly CommandName[] = [
|
|
|
259
300
|
"chat:touch",
|
|
260
301
|
"chat:disarm",
|
|
261
302
|
"chat:unread-waking",
|
|
303
|
+
"chat:sign-in",
|
|
304
|
+
"chat:sign-out",
|
|
305
|
+
"chat:away",
|
|
306
|
+
"chat:back",
|
|
307
|
+
"chat:buddies",
|
|
308
|
+
"chat:pulse",
|
|
309
|
+
"chat:dm",
|
|
262
310
|
];
|
package/src/health.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { eventsHead } from "./client.ts";
|
|
2
|
+
import type { RtClientOptions } from "./transport.ts";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A daemon-down result is a successful probe, not a failure of this call —
|
|
6
|
+
* eventsHead already never throws (transport.ts degrades every fetch to
|
|
7
|
+
* `{ ok: false, error }`), so this only reshapes that envelope for callers
|
|
8
|
+
* who want a boolean, not `{ ok, data, error }`.
|
|
9
|
+
*/
|
|
10
|
+
export async function daemonHealth(
|
|
11
|
+
opts: RtClientOptions = {},
|
|
12
|
+
): Promise<{ reachable: boolean; error?: string }> {
|
|
13
|
+
const res = await eventsHead(opts);
|
|
14
|
+
return { reachable: res.ok, error: res.ok ? undefined : res.error };
|
|
15
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,13 @@ export {
|
|
|
22
22
|
chatTouch,
|
|
23
23
|
chatDisarm,
|
|
24
24
|
chatUnreadWaking,
|
|
25
|
+
chatSignIn,
|
|
26
|
+
chatSignOut,
|
|
27
|
+
chatAway,
|
|
28
|
+
chatBack,
|
|
29
|
+
chatBuddies,
|
|
30
|
+
chatPulse,
|
|
31
|
+
chatDm,
|
|
25
32
|
eventsHead,
|
|
26
33
|
} from "./client.ts";
|
|
27
34
|
|
|
@@ -49,11 +56,15 @@ export type {
|
|
|
49
56
|
ChatMember,
|
|
50
57
|
ChatMessage,
|
|
51
58
|
RoomSummary,
|
|
59
|
+
BuddyStatus,
|
|
60
|
+
PresenceRow,
|
|
52
61
|
} from "./commands.ts";
|
|
53
62
|
|
|
54
|
-
export { subscribe, DEFAULT_WS_URL } from "./relay.ts";
|
|
63
|
+
export { subscribe, createRelay, DEFAULT_WS_URL } from "./relay.ts";
|
|
55
64
|
export type { RelayEventType } from "./relay.ts";
|
|
56
65
|
|
|
66
|
+
export { daemonHealth } from "./health.ts";
|
|
67
|
+
|
|
57
68
|
export { repoNameForPath } from "./repos.ts";
|
|
58
69
|
|
|
59
70
|
// ─── Settings (RT-50) ────────────────────────────────────────────────────────
|
package/src/relay.ts
CHANGED
|
@@ -59,3 +59,34 @@ export function subscribe(
|
|
|
59
59
|
try { ws?.close(); } catch { /* already closed */ }
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One daemon subscription for the whole process, republished onto a
|
|
65
|
+
* caller-chosen pub/sub topic. Every subscriber to that topic then shares
|
|
66
|
+
* one relay connection instead of each opening its own — filtering here
|
|
67
|
+
* (rather than at each subscriber) is what keeps an unrelated event from
|
|
68
|
+
* making every subscriber re-render.
|
|
69
|
+
*
|
|
70
|
+
* Ported from console's `startRelay` (src/server/ws.ts) with the match
|
|
71
|
+
* predicate and target topic lifted to arguments.
|
|
72
|
+
*/
|
|
73
|
+
export function createRelay(
|
|
74
|
+
cfg: { match: (topic: string) => boolean; topic: string; publish: (topic: string, data: string) => void },
|
|
75
|
+
opts: RtClientOptions = {},
|
|
76
|
+
): () => void {
|
|
77
|
+
const doSubscribe = opts.subscribeImpl ?? subscribe;
|
|
78
|
+
return doSubscribe((type, data) => {
|
|
79
|
+
if (type !== "event") return;
|
|
80
|
+
const frame = data as { topic?: unknown };
|
|
81
|
+
if (typeof frame?.topic !== "string" || !cfg.match(frame.topic)) return;
|
|
82
|
+
// Serializing outside the catch keeps the suppression narrow: only a
|
|
83
|
+
// publish that rejects its own frame is expected here, and one
|
|
84
|
+
// subscriber's broken publish must not tear down the shared relay.
|
|
85
|
+
const payload = JSON.stringify(data);
|
|
86
|
+
try {
|
|
87
|
+
cfg.publish(cfg.topic, payload);
|
|
88
|
+
} catch {
|
|
89
|
+
/* the subscriber went away */
|
|
90
|
+
}
|
|
91
|
+
}, opts);
|
|
92
|
+
}
|
|
@@ -228,6 +228,13 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
228
228
|
merge: "replace",
|
|
229
229
|
description: "Absolute path to the installed mattstack.app bundle, written by the app at launch so rt stops hardcoding ~/Applications.",
|
|
230
230
|
},
|
|
231
|
+
{
|
|
232
|
+
key: "mattstack.mode",
|
|
233
|
+
type: "string",
|
|
234
|
+
scopes: ["machine"],
|
|
235
|
+
merge: "replace",
|
|
236
|
+
description: "The machine's intended flavor, \"dev\" or \"prod\". Normally written by `rt settings dev-mode` after a successful handoff; a manual `rt settings set` is the blessed repair escape hatch — the daemon park loop converges on whatever this says. Unset ⇒ derived from the dev wrapper's presence.",
|
|
237
|
+
},
|
|
231
238
|
{
|
|
232
239
|
key: "rt.integrations",
|
|
233
240
|
type: "object",
|
|
@@ -345,13 +352,6 @@ export const REGISTRY: readonly SettingDef[] = [
|
|
|
345
352
|
merge: "replace",
|
|
346
353
|
description: "Doctor skill the board's own API-tier triage sweep runs on your MRs; deliberately never resolved through a repo's skills.jsonc manifest. A sibling flat key of board.triage, not a field inside it — the board reader assembles the two independently.",
|
|
347
354
|
},
|
|
348
|
-
{
|
|
349
|
-
key: "board.tabs",
|
|
350
|
-
type: "array",
|
|
351
|
-
scopes: ["team"],
|
|
352
|
-
merge: "replace",
|
|
353
|
-
description: "Board tab definitions ({id, label, source, slackChannel?, reviewSkill?}); source.kind 'authors' is the classic roster board, 'codeowners' lists MRs blocked on an unapproved CODEOWNERS section. Absent = one implicit authors tab (fallback lives in the board reader, never here).",
|
|
354
|
-
},
|
|
355
355
|
|
|
356
356
|
// --- board (user) ----------------------------------------------------------
|
|
357
357
|
{
|
package/src/transport.ts
CHANGED
|
@@ -19,6 +19,15 @@ export interface RtResponse<T = unknown> {
|
|
|
19
19
|
export interface RtClientOptions {
|
|
20
20
|
sockPath?: string;
|
|
21
21
|
wsUrl?: string;
|
|
22
|
+
/** Per-call override of rtCommand's own default (15s); chat's pulse wrapper needs an 800ms hook budget. */
|
|
23
|
+
timeoutMs?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Test seam for createRelay (relay.ts): swaps the daemon subscription for
|
|
26
|
+
* a fake without a live WebSocket server. Typed structurally against
|
|
27
|
+
* relay.ts's `subscribe` rather than importing its RelayEventType, which
|
|
28
|
+
* would make this module depend on the one that already depends on it.
|
|
29
|
+
*/
|
|
30
|
+
subscribeImpl?: (onEvent: (type: string, data: unknown) => void, opts?: RtClientOptions) => () => void;
|
|
22
31
|
}
|
|
23
32
|
|
|
24
33
|
// Duplicates the ~/.mattstack/rt layout: rt-client has no dependency on rt's
|