@mattstack/rt-client 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,6 +26,54 @@ Bun-only: the settings exec path (`src/settings/exec.ts`) shells out via
26
26
  `@mattstack/glance` is a peer dependency: rt-client returns glance's forge types
27
27
  so merge request shapes stay identical across rt, gitq, and mr-board.
28
28
 
29
+ ## Repo identity
30
+
31
+ Every per-repo key in the rt estate is a stable serialized identity, not a
32
+ repo name. Whatever you store per-repo, send to the daemon, or put in a REST
33
+ path is keyed by the wire form this package emits. The one exception:
34
+ settings-store sections (`repos.<identity>`) key on the RAW `host/path` form
35
+ (`RepoIdentity.id` for a remote-kind identity) — the settings resolver never
36
+ sees the wire form, and a serialized key there misses silently.
37
+
38
+ ```text
39
+ remote:gitlab.com%2Facme%2Facme-dev path:%2FUsers%2Fdev%2Fscratch
40
+ └─┬──┘ └──────────┬─────────────┘
41
+ kind the id, encodeURIComponent'd — slash-free, fits one URL segment
42
+ ```
43
+
44
+ `kind` is `remote` (the repo has an origin: id is normalized `host/path`) or
45
+ `path` (no usable remote: id is the main worktree's realpath). The `:` is a
46
+ literal delimiter.
47
+
48
+ ```ts
49
+ import {
50
+ deriveRepoIdentity, // (repoPath: string) => Promise<RepoIdentity> — never null
51
+ serializeIdentity, // (id: RepoIdentity) => string — the wire form above
52
+ parseIdentity, // (wire: string) => RepoIdentity | null — THE validity check
53
+ identityFromRemote, // (remoteUrl: string) => RepoIdentity | null — sync
54
+ type RepoIdentity, // { kind: "remote" | "path"; id: string }
55
+ } from '@mattstack/rt-client';
56
+
57
+ const identity = serializeIdentity(await deriveRepoIdentity(repoPath));
58
+
59
+ await rtCommand(['worktree', 'list', '--repo', identity]); // daemon key
60
+ const url = `/api/runs/${encodeURIComponent(identity)}/${runId}`; // URL segment
61
+ ```
62
+
63
+ | Do | Don't |
64
+ |---|---|
65
+ | Get identities from these functions, once, at the boundary | Re-derive with your own git calls (`git remote get-url` diverges under `insteadOf`) |
66
+ | Key stores and daemon payloads on the serialized form | Key anything on a folder basename or a remote's last segment |
67
+ | Key settings sections (`repos.<identity>`) on the raw `host/path` id | Put the serialized form in a settings lookup, or the raw form in a daemon payload |
68
+ | `encodeURIComponent(identity)` in URL path segments | Ship the wire form raw in a URL — its `%` signs decode into slashes |
69
+ | Decode for display: `parseIdentity(wire)`, then the id's last path segment (remote) or basename (path) — the returned `id` is already decoded | `decodeURIComponent` the id again, show the wire form to a human, or build a chat handle from it |
70
+ | Treat the `repo` field from `runs:list` as an opaque key, passed back verbatim | Validate or re-derive `runs:*` repo keys — pre-cutover runs keep their original keys |
71
+
72
+ Repo-keyed daemon verbs accept serialized identities only; a bare repo name
73
+ doesn't error, it resolves empty. `parseIdentity` is strict — only strings
74
+ `serializeIdentity` emitted parse — so validate payloads with it, and never
75
+ hand-assemble or string-split a wire.
76
+
29
77
  ## License
30
78
 
31
79
  MIT
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, ForgeSlug, ForgeTokenData, RunSummary, RunDetail } from "./commands.ts";
2
+ import type { DemandDecl, ProjectMRsData, DiscussionsData, MrByBranchData, ForgeSlug, ForgeTokenData, RunSummary, RunDetail, WakeMode, ChatMember, ChatMessage, RoomSummary } 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
@@ -25,3 +25,90 @@ export declare function listRuns(repo?: string, opts?: RtClientOptions): Promise
25
25
  runs: RunSummary[];
26
26
  }>>;
27
27
  export declare function getRun(runId: string, repo?: string, opts?: RtClientOptions): Promise<RtResponse<RunDetail>>;
28
+ /**
29
+ * SKILLS-54. rt's only write path into run state, so a wedged run can be
30
+ * resolved by a person instead of lying in the data forever. The write happens
31
+ * in the daemon and is attributed there; consumers never touch the run DB.
32
+ */
33
+ export declare function abandonRun(runId: string, repo?: string, reason?: string, opts?: RtClientOptions): Promise<RtResponse<{
34
+ ok: boolean;
35
+ }>>;
36
+ export declare function chatJoin(a: {
37
+ room: string;
38
+ handle: string;
39
+ wakeOn?: WakeMode;
40
+ cwd?: string;
41
+ pane?: string;
42
+ }, o?: RtClientOptions): Promise<RtResponse<{
43
+ handle: string;
44
+ memberCount: number;
45
+ unread: number;
46
+ }>>;
47
+ export declare function chatLeave(a: {
48
+ room: string;
49
+ handle: string;
50
+ }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
51
+ export declare function chatPost(a: {
52
+ room: string;
53
+ handle: string;
54
+ body: string;
55
+ }, o?: RtClientOptions): Promise<RtResponse<{
56
+ id: number;
57
+ recipients: string[];
58
+ }>>;
59
+ export declare function chatRead(a: {
60
+ handle: string;
61
+ room?: string;
62
+ limit?: number;
63
+ sinceMs?: number;
64
+ }, o?: RtClientOptions): Promise<RtResponse<{
65
+ rooms: {
66
+ room: string;
67
+ messages: ChatMessage[];
68
+ }[];
69
+ }>>;
70
+ export declare function chatRooms(a: {
71
+ handle: string;
72
+ }, o?: RtClientOptions): Promise<RtResponse<{
73
+ rooms: RoomSummary[];
74
+ }>>;
75
+ export declare function chatWho(a: {
76
+ room: string;
77
+ }, o?: RtClientOptions): Promise<RtResponse<{
78
+ members: ChatMember[];
79
+ }>>;
80
+ export declare function chatMark(a: {
81
+ handle: string;
82
+ room?: string;
83
+ }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
84
+ export declare function chatMessages(a: {
85
+ room: string;
86
+ before?: number;
87
+ limit?: number;
88
+ }, o?: RtClientOptions): Promise<RtResponse<{
89
+ messages: ChatMessage[];
90
+ }>>;
91
+ export declare function chatArm(a: {
92
+ handle: string;
93
+ room?: string;
94
+ }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
95
+ export declare function chatTouch(a: {
96
+ handle: string;
97
+ }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
98
+ export declare function chatDisarm(a: {
99
+ handle: string;
100
+ }, o?: RtClientOptions): Promise<RtResponse<Record<string, never>>>;
101
+ export declare function chatUnreadWaking(a: {
102
+ handle: string;
103
+ room?: string;
104
+ }, o?: RtClientOptions): Promise<RtResponse<{
105
+ rooms: {
106
+ room: string;
107
+ count: number;
108
+ mentions: number;
109
+ maxId: number;
110
+ }[];
111
+ }>>;
112
+ export declare function eventsHead(o?: RtClientOptions): Promise<RtResponse<{
113
+ cursor: number;
114
+ }>>;
@@ -55,6 +55,44 @@ export interface EventsBusEvent {
55
55
  payload: unknown;
56
56
  emittedAt: number;
57
57
  }
58
+ /**
59
+ * Duplicated shape on purpose, same reasoning as EventsBusEvent above:
60
+ * these mirror lib/state/chat-store.ts's types, which rt-client cannot
61
+ * import (it's outside lib/state/ and outside this package entirely).
62
+ */
63
+ export type WakeMode = "mention" | "all" | "none";
64
+ export interface ChatMember {
65
+ room: string;
66
+ handle: string;
67
+ joinedAt: number;
68
+ lastReadId: number;
69
+ wakeOn: WakeMode;
70
+ lastSeenAt?: number;
71
+ armedAt?: number;
72
+ cwd?: string;
73
+ pane?: string;
74
+ }
75
+ export interface ChatMessage {
76
+ id: number;
77
+ room: string;
78
+ handle: string;
79
+ body: string;
80
+ mentions: string[];
81
+ replyTo?: number;
82
+ postedAt: number;
83
+ }
84
+ export interface RoomSummary {
85
+ room: string;
86
+ memberCount: number;
87
+ unread: number;
88
+ mentions: number;
89
+ lastPostedAt?: number;
90
+ }
91
+ export type Attention = {
92
+ needs: boolean;
93
+ reason: "failed" | "stale" | "stranded" | null;
94
+ evidence: string;
95
+ };
58
96
  export interface RunSummary {
59
97
  id: string;
60
98
  repo: string;
@@ -65,6 +103,18 @@ export interface RunSummary {
65
103
  spawned_by: string | null;
66
104
  started_at: number;
67
105
  ended_at: number | null;
106
+ pack_commits: string | null;
107
+ pack_dirty: number;
108
+ attention: Attention;
109
+ /** Max over stage, field, and decision timestamps; falls back to
110
+ `started_at` when the run has produced no events yet. The board orders
111
+ by silence, so this — not `started_at` — is its sort key. */
112
+ last_event_at: number;
113
+ /** Denormalized from the run's `ticket` / `branch` fields so the LIST view
114
+ can render and search them without a detail fetch per row. Null when
115
+ the run has not produced that field yet. */
116
+ ticket: string | null;
117
+ branch: string | null;
68
118
  }
69
119
  export interface RunStageRow {
70
120
  name: string;
@@ -72,6 +122,8 @@ export interface RunStageRow {
72
122
  attempt: number;
73
123
  started_at: number | null;
74
124
  ended_at: number | null;
125
+ reason: string | null;
126
+ detail_path: string | null;
75
127
  }
76
128
  export interface RunFieldRow {
77
129
  key: string;
@@ -204,6 +256,12 @@ export interface Commands {
204
256
  cursor: number;
205
257
  };
206
258
  };
259
+ "events:head": {
260
+ payload: Record<string, never>;
261
+ data: {
262
+ cursor: number;
263
+ };
264
+ };
207
265
  "runs:list": {
208
266
  payload: {
209
267
  repo?: string;
@@ -219,6 +277,128 @@ export interface Commands {
219
277
  };
220
278
  data: RunDetail;
221
279
  };
280
+ "runs:abandon": {
281
+ payload: {
282
+ runId: string;
283
+ repo?: string;
284
+ reason?: string;
285
+ };
286
+ data: {
287
+ ok: boolean;
288
+ };
289
+ };
290
+ "chat:join": {
291
+ payload: {
292
+ room: string;
293
+ handle: string;
294
+ wakeOn?: WakeMode;
295
+ cwd?: string;
296
+ pane?: string;
297
+ };
298
+ data: {
299
+ handle: string;
300
+ memberCount: number;
301
+ unread: number;
302
+ };
303
+ };
304
+ "chat:leave": {
305
+ payload: {
306
+ room: string;
307
+ handle: string;
308
+ };
309
+ data: Record<string, never>;
310
+ };
311
+ "chat:post": {
312
+ payload: {
313
+ room: string;
314
+ handle: string;
315
+ body: string;
316
+ };
317
+ data: {
318
+ id: number;
319
+ recipients: string[];
320
+ };
321
+ };
322
+ "chat:read": {
323
+ payload: {
324
+ handle: string;
325
+ room?: string;
326
+ limit?: number;
327
+ sinceMs?: number;
328
+ };
329
+ data: {
330
+ rooms: {
331
+ room: string;
332
+ messages: ChatMessage[];
333
+ }[];
334
+ };
335
+ };
336
+ "chat:rooms": {
337
+ payload: {
338
+ handle: string;
339
+ };
340
+ data: {
341
+ rooms: RoomSummary[];
342
+ };
343
+ };
344
+ "chat:who": {
345
+ payload: {
346
+ room: string;
347
+ };
348
+ data: {
349
+ members: ChatMember[];
350
+ };
351
+ };
352
+ "chat:mark": {
353
+ payload: {
354
+ handle: string;
355
+ room?: string;
356
+ };
357
+ data: Record<string, never>;
358
+ };
359
+ "chat:messages": {
360
+ payload: {
361
+ room: string;
362
+ before?: number;
363
+ limit?: number;
364
+ };
365
+ data: {
366
+ messages: ChatMessage[];
367
+ };
368
+ };
369
+ "chat:arm": {
370
+ payload: {
371
+ handle: string;
372
+ room?: string;
373
+ };
374
+ data: Record<string, never>;
375
+ };
376
+ "chat:touch": {
377
+ payload: {
378
+ handle: string;
379
+ };
380
+ data: Record<string, never>;
381
+ };
382
+ "chat:disarm": {
383
+ payload: {
384
+ handle: string;
385
+ };
386
+ data: Record<string, never>;
387
+ };
388
+ "chat:unread-waking": {
389
+ payload: {
390
+ handle: string;
391
+ room?: string;
392
+ };
393
+ data: {
394
+ rooms: {
395
+ room: string;
396
+ count: number;
397
+ mentions: number;
398
+ maxId: number;
399
+ }[];
400
+ };
401
+ };
222
402
  }
223
403
  export type CommandName = keyof Commands;
224
404
  export declare const COMMAND_NAMES: readonly CommandName[];
package/dist/index.d.ts CHANGED
@@ -1,18 +1,18 @@
1
1
  export { rtCommand, DEFAULT_SOCK } from "./transport.ts";
2
2
  export type { RtResponse, RtClientOptions } from "./transport.ts";
3
- export { readProjectMRs, readDiscussions, readMrsByBranch, resolveForgeToken } from "./client.ts";
3
+ export { readProjectMRs, readDiscussions, readMrsByBranch, resolveForgeToken, listRuns, getRun, abandonRun, chatJoin, chatLeave, chatPost, chatRead, chatRooms, chatWho, chatMark, chatMessages, chatArm, chatTouch, chatDisarm, chatUnreadWaking, eventsHead, } from "./client.ts";
4
4
  export { COMMAND_NAMES } from "./commands.ts";
5
- export type { Discussion, DemandDecl, ProjectMRsScope, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, Commands, CommandName, ForgeSlug, ForgeTokenData, } from "./commands.ts";
5
+ export type { Discussion, DemandDecl, ProjectMRsScope, ProjectMRsData, DiscussionsData, MrByBranchEntry, MrByBranchData, Commands, CommandName, ForgeSlug, ForgeTokenData, Attention, RunSummary, RunStageRow, RunFieldRow, RunDecisionRow, RunDetail, WakeMode, ChatMember, ChatMessage, RoomSummary, } from "./commands.ts";
6
6
  export { subscribe, DEFAULT_WS_URL } from "./relay.ts";
7
7
  export type { RelayEventType } from "./relay.ts";
8
8
  export { repoNameForPath } from "./repos.ts";
9
9
  export { getSetting, listSettings, explainSetting, expandVariables, SCOPE_ORDER } from "./settings/resolve.ts";
10
10
  export type { Scope, Provenance, ResolveOpts, Resolved, InvalidScope, ListedSetting, ExplainRow, ExpandCtx, } from "./settings/resolve.ts";
11
- export { setSetting } from "./settings/write.ts";
11
+ export { setSetting, unsetSetting } from "./settings/write.ts";
12
12
  export type { SetSettingOpts } from "./settings/write.ts";
13
13
  export { getDef, allDefs, validateValue, isMigrated } from "./settings/registry-machinery.ts";
14
14
  export type { SettingDef, SettingScope } from "./settings/registry-machinery.ts";
15
15
  export { REGISTRY } from "./settings/registry-defs.ts";
16
16
  export { readStore, listTeams } from "./settings/stores.ts";
17
17
  export type { StoreFile } from "./settings/stores.ts";
18
- export { normalizeRemote, identityFromRemote, deriveRepoIdentity, clearIdentityMemo } from "./settings/identity.ts";
18
+ export { normalizeRemote, identityFromRemote, deriveRepoIdentity, clearIdentityMemo, serializeIdentity, parseIdentity, resolveNameToIdentity, type RepoIdentity, } from "./settings/identity.ts";