@korso/shepherd-ui 0.1.1 → 0.2.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.
Files changed (100) hide show
  1. package/README.md +47 -47
  2. package/dist/ShepherdProvider.d.ts +12 -0
  3. package/dist/ShepherdProvider.js +33 -0
  4. package/dist/ShepherdProvider.test.d.ts +1 -0
  5. package/dist/ShepherdProvider.test.js +47 -0
  6. package/dist/ShepherdRoot.d.ts +9 -0
  7. package/dist/ShepherdRoot.js +88 -0
  8. package/dist/ShepherdRoot.routing.test.d.ts +1 -0
  9. package/dist/ShepherdRoot.routing.test.js +61 -0
  10. package/dist/ShepherdRoot.test.d.ts +1 -0
  11. package/dist/ShepherdRoot.test.js +37 -0
  12. package/dist/app/assets/index-CDOCIg6s.js +11 -0
  13. package/dist/app/index.html +12 -0
  14. package/dist/client.d.ts +107 -0
  15. package/dist/client.js +238 -0
  16. package/dist/client.test.d.ts +1 -0
  17. package/dist/client.test.js +298 -0
  18. package/dist/components/ActiveList.d.ts +22 -0
  19. package/dist/components/ActiveList.js +59 -0
  20. package/dist/components/Chat.d.ts +30 -0
  21. package/dist/components/Chat.js +61 -0
  22. package/dist/components/Composer.d.ts +37 -0
  23. package/dist/components/Composer.js +146 -0
  24. package/dist/components/Crew.d.ts +23 -0
  25. package/dist/components/Crew.js +31 -0
  26. package/dist/components/Dashboard.d.ts +34 -0
  27. package/dist/components/Dashboard.js +209 -0
  28. package/dist/components/DoneList.d.ts +29 -0
  29. package/dist/components/DoneList.js +50 -0
  30. package/dist/components/RepoSelect.d.ts +38 -0
  31. package/dist/components/RepoSelect.js +56 -0
  32. package/dist/components/Territory.d.ts +21 -0
  33. package/dist/components/Territory.js +21 -0
  34. package/dist/config/ConnectAgent.d.ts +10 -0
  35. package/dist/config/ConnectAgent.js +119 -0
  36. package/dist/config/ConnectAgent.test.d.ts +1 -0
  37. package/dist/config/ConnectAgent.test.js +92 -0
  38. package/dist/config/EmptyState.d.ts +12 -0
  39. package/dist/config/EmptyState.js +4 -0
  40. package/dist/config/EmptyState.test.d.ts +1 -0
  41. package/dist/config/EmptyState.test.js +32 -0
  42. package/dist/config/Members.d.ts +10 -0
  43. package/dist/config/Members.js +83 -0
  44. package/dist/config/Members.test.d.ts +1 -0
  45. package/dist/config/Members.test.js +66 -0
  46. package/dist/config/Workspaces.d.ts +12 -0
  47. package/dist/config/Workspaces.js +96 -0
  48. package/dist/config/Workspaces.test.d.ts +1 -0
  49. package/dist/config/Workspaces.test.js +90 -0
  50. package/dist/config/index.d.ts +8 -0
  51. package/dist/config/index.js +6 -0
  52. package/dist/context.d.ts +28 -0
  53. package/dist/context.js +36 -0
  54. package/dist/index.cjs +5289 -0
  55. package/dist/index.cjs.map +1 -0
  56. package/dist/index.d.cts +149 -0
  57. package/dist/index.d.ts +18 -0
  58. package/dist/index.js +15 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/lib/Dashboard-CG6KeFQ3.js +1214 -0
  61. package/dist/lib/index.d.ts +438 -16
  62. package/dist/lib/index.js +455 -6
  63. package/dist/lib/selfhost.js +9 -9
  64. package/dist/lib/styles.css +209 -182
  65. package/dist/logic.d.ts +208 -0
  66. package/dist/logic.js +372 -0
  67. package/dist/main.d.ts +1 -0
  68. package/dist/main.js +11 -0
  69. package/dist/selfhost/assets/{index-tSyzirZa.css → index-BZmImfYZ.css} +1 -1
  70. package/dist/selfhost/assets/index-CS268KSw.js +40 -0
  71. package/dist/selfhost/index.html +13 -13
  72. package/dist/selfhost.d.ts +22 -0
  73. package/dist/selfhost.js +97 -0
  74. package/dist/test/mockClient.d.ts +6 -0
  75. package/dist/test/mockClient.js +45 -0
  76. package/dist/test/setup.d.ts +1 -0
  77. package/dist/test/setup.js +2 -0
  78. package/dist/useLandscapePolling.d.ts +63 -0
  79. package/dist/useLandscapePolling.js +129 -0
  80. package/dist/views/Chat.d.ts +5 -0
  81. package/dist/views/Chat.js +60 -0
  82. package/dist/views/Chat.test.d.ts +1 -0
  83. package/dist/views/Chat.test.js +92 -0
  84. package/dist/views/Config.d.ts +12 -0
  85. package/dist/views/Config.js +6 -0
  86. package/dist/views/Tasks.d.ts +5 -0
  87. package/dist/views/Tasks.js +75 -0
  88. package/dist/views/Tasks.test.d.ts +1 -0
  89. package/dist/views/Tasks.test.js +115 -0
  90. package/dist/views/useLandscape.d.ts +14 -0
  91. package/dist/views/useLandscape.js +74 -0
  92. package/dist/views/useLandscape.test.d.ts +1 -0
  93. package/dist/views/useLandscape.test.js +101 -0
  94. package/dist/views/wallboard.d.ts +46 -0
  95. package/dist/views/wallboard.js +209 -0
  96. package/dist/views/wallboard.test.d.ts +1 -0
  97. package/dist/views/wallboard.test.js +143 -0
  98. package/package.json +67 -67
  99. package/dist/lib/Dashboard-CB6SL-J2.js +0 -1064
  100. package/dist/selfhost/assets/index-BBx3vo2Y.js +0 -40
@@ -0,0 +1,1214 @@
1
+ var _e = Object.defineProperty;
2
+ var Ne = (n, t, i) => t in n ? _e(n, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : n[t] = i;
3
+ var ee = (n, t, i) => Ne(n, typeof t != "symbol" ? t + "" : t, i);
4
+ import { jsx as o, jsxs as m, Fragment as ye } from "react/jsx-runtime";
5
+ import { createContext as ke, useContext as we, useState as k, useRef as x, useCallback as S, useEffect as X, Fragment as Ae, useLayoutEffect as Se } from "react";
6
+ import { z as e } from "zod";
7
+ const A = e.string(), H = e.number(), Te = e.object({
8
+ agentName: e.string(),
9
+ human: e.string(),
10
+ branch: e.string(),
11
+ kind: e.enum(["committed", "uncommitted"]),
12
+ commitSha: e.string().nullable(),
13
+ message: e.string().nullable(),
14
+ paths: e.array(e.string()).min(1),
15
+ authorIsLive: e.boolean(),
16
+ authorLastActiveAt: A,
17
+ updatedAt: A
18
+ }), $e = e.object({
19
+ kind: e.enum(["committed", "uncommitted"]),
20
+ // A git object id (lowercase hex, 4–64 chars) for `committed` entries, or null
21
+ // for `uncommitted`. This value is forwarded by the hub to OTHER clients, which
22
+ // feed it straight into local `git` argument vectors (isAncestor/hasCommit/
23
+ // changedLineRanges). Validating the shape at the wire boundary stops an
24
+ // attacker-controlled, flag-like value (e.g. "--output=...") from being parsed
25
+ // by git as an option on a teammate's machine (argument injection). gitContext
26
+ // re-validates defensively as well.
27
+ sha: e.string().regex(/^[0-9a-f]{4,64}$/).nullable(),
28
+ message: e.string().nullable(),
29
+ paths: e.array(e.string()).min(1).max(500)
30
+ }), Z = e.object({
31
+ branch: e.string(),
32
+ baseBranch: e.string(),
33
+ head: e.string(),
34
+ truncated: e.boolean().default(!1),
35
+ // The only producer (gitContext.unlandedCommits) emits at most MAX_COMMITS
36
+ // (100) committed entries + 1 uncommitted, so this ceiling is generous. If
37
+ // MAX_COMMITS is ever raised above ~599, raise this in lockstep or the hub
38
+ // will start 400-rejecting otherwise-valid reports.
39
+ entries: e.array($e).max(600)
40
+ }), q = e.object({
41
+ workItemId: e.string().uuid(),
42
+ agentName: e.string(),
43
+ human: e.string().min(1),
44
+ intent: e.string().min(1).max(2048),
45
+ pathGlobs: e.array(e.string().min(1).max(512)).min(1).max(64),
46
+ // ISO timestamp string; see IsoTimestamp note above
47
+ expiresAt: A
48
+ }), W = e.object({
49
+ // bigint PK serialised as number; see DbId note above
50
+ id: H,
51
+ fromAgentName: e.string(),
52
+ fromHuman: e.string().min(1),
53
+ body: e.string().min(1).max(8192),
54
+ targetAgentName: e.string().nullable(),
55
+ // ISO timestamp string; see IsoTimestamp note above
56
+ createdAt: A
57
+ }), ue = e.object({
58
+ conflicts: e.array(q),
59
+ activeClaims: e.array(q),
60
+ // The caller's OWN active claims. `activeClaims` deliberately excludes the
61
+ // caller's session, so without this an agent has no way to confirm its own
62
+ // claim is live. Optional with a default so an older client talking to a
63
+ // newer hub (or vice-versa) never fails validation on its absence.
64
+ yourClaims: e.array(q).default([]),
65
+ announcements: e.array(W),
66
+ // Per-agent change records for the workspace. Defaulted for version-skew safety.
67
+ changeRecords: e.array(Te).default([])
68
+ }), Ce = e.object({
69
+ name: e.string(),
70
+ human: e.string(),
71
+ program: e.string(),
72
+ // model is nullable in the DB (may be unknown when an agent first joins).
73
+ model: e.string().nullable(),
74
+ // repo/branch/lastHeartbeatAt come from the agent's most-recent session and
75
+ // are null when the agent has no session yet.
76
+ repo: e.string().nullable(),
77
+ branch: e.string().nullable(),
78
+ lastHeartbeatAt: A.nullable(),
79
+ presence: e.enum(["live", "offline"])
80
+ }), Re = e.enum(["active", "done", "dropped"]), Ee = e.object({
81
+ agentName: e.string(),
82
+ program: e.string(),
83
+ model: e.string().nullable(),
84
+ repo: e.string(),
85
+ intent: e.string(),
86
+ pathGlobs: e.array(e.string()),
87
+ status: Re,
88
+ createdAt: A,
89
+ endedAt: A.nullable()
90
+ }), De = e.object({
91
+ fromAgentName: e.string(),
92
+ fromHuman: e.string(),
93
+ body: e.string(),
94
+ targetAgentName: e.string().nullable(),
95
+ repo: e.string(),
96
+ // True when the message was sent by the human operator from the dashboard
97
+ // (no agent session). The dashboard renders these as "me" (right-aligned).
98
+ // Defaulted for version-skew safety with older hubs.
99
+ fromAdmin: e.boolean().default(!1),
100
+ // True when an agent addressed the message TO the operator (the mirror of
101
+ // fromAdmin). The dashboard renders these as "<agent> → admin". Not delivered
102
+ // to other agents. Defaulted for version-skew safety with older hubs.
103
+ toAdmin: e.boolean().default(!1),
104
+ createdAt: A
105
+ }), te = e.object({
106
+ agents: e.array(Ce),
107
+ tasks: e.array(Ee),
108
+ announcements: e.array(De),
109
+ // The server's clock, so the client computes "expires in / last seen" against
110
+ // the hub rather than the (possibly skewed) browser clock.
111
+ serverTime: A
112
+ });
113
+ e.object({
114
+ body: e.string().min(1).max(8192),
115
+ // Direct-message a single agent (by the exact name shown in the landscape).
116
+ // Absent/null => broadcast. The hub resolves the target's repo server-side.
117
+ targetAgentName: e.string().min(1).nullable().optional(),
118
+ // For a broadcast, the repo to scope the message to (matches the dashboard's
119
+ // selected repo). Absent/null => fan out to every repo in the workspace.
120
+ // Ignored for a DM (the target's own repo is used).
121
+ repo: e.string().min(1).nullable().optional()
122
+ });
123
+ const ne = e.object({
124
+ ok: e.literal(!0),
125
+ // One id per inserted row: a single id for a DM or repo-scoped broadcast, or
126
+ // several when an all-repos broadcast fans out across repos.
127
+ announcementIds: e.array(H)
128
+ });
129
+ e.object({
130
+ workspace: e.string().min(1),
131
+ repo: e.string().min(1),
132
+ branch: e.string().min(1),
133
+ human: e.string().min(1),
134
+ program: e.string().min(1),
135
+ model: e.string().min(1).optional()
136
+ });
137
+ e.object({
138
+ agentName: e.string(),
139
+ sessionId: e.string().uuid()
140
+ });
141
+ const je = e.object({
142
+ sessionId: e.string().uuid(),
143
+ intent: e.string().min(1).max(2048),
144
+ pathGlobs: e.array(e.string().min(1).max(512)).min(1).max(64),
145
+ ttlSeconds: e.number().int().positive().optional(),
146
+ changeReport: Z.optional()
147
+ });
148
+ e.object({
149
+ workItemId: e.string().uuid(),
150
+ landscape: ue
151
+ });
152
+ const Ie = e.object({
153
+ sessionId: e.string().uuid(),
154
+ workItemId: e.string().uuid()
155
+ });
156
+ e.object({
157
+ ok: e.literal(!0),
158
+ // Pending announcements for the caller, delivered as a side effect of done so
159
+ // a message lands the moment a teammate finishes a unit of work (not only on
160
+ // their next work/sync). Defaulted for version-skew safety with older hubs.
161
+ announcements: e.array(W).default([])
162
+ });
163
+ const xe = e.object({
164
+ sessionId: e.string().uuid(),
165
+ body: e.string().min(1).max(8192),
166
+ // absent or null => broadcast to all agents in the workspace
167
+ targetAgentName: e.string().nullable().optional(),
168
+ // true => address the human operator (the dashboard) instead of agents. The
169
+ // mirror of the operator's admin → agent DM: it shows in the workspace feed as
170
+ // "<agent> → admin" and is NOT delivered to other agents. Mutually exclusive
171
+ // with targetAgentName (the hub rejects setting both). Defaulted/optional for
172
+ // version skew with older clients.
173
+ toAdmin: e.boolean().optional()
174
+ });
175
+ e.object({
176
+ ok: e.literal(!0),
177
+ // bigint PK serialised as number; see DbId note above
178
+ announcementId: H,
179
+ // Pending announcements for the caller, delivered as a side effect of announce
180
+ // (a turn where the agent is already reading hub output) so inbound messages
181
+ // surface promptly. Excludes the just-sent one. Defaulted for version skew.
182
+ announcements: e.array(W).default([])
183
+ });
184
+ e.object({
185
+ sessionId: e.string().uuid(),
186
+ changeReport: Z.optional()
187
+ });
188
+ e.object({
189
+ landscape: ue
190
+ });
191
+ je.omit({ sessionId: !0, changeReport: !0 });
192
+ xe.omit({ sessionId: !0 });
193
+ Ie.omit({ sessionId: !0 });
194
+ e.object({});
195
+ e.object({});
196
+ e.object({
197
+ sessionId: e.string().uuid(),
198
+ // Optional change report so the BACKGROUND heartbeat keeps an agent's durable
199
+ // change records fresh (commits surface within ~one heartbeat interval, not
200
+ // only when it next calls work/sync). Processed presence-style: it refreshes
201
+ // change records but, like the rest of heartbeat, does NOT renew claim TTLs.
202
+ changeReport: Z.optional(),
203
+ // Opt-in: when set, the heartbeat returns any pending announcements for the
204
+ // caller in the response. Delivery is now TWO-PHASE and crash-safe: this fetch
205
+ // phase does NOT mark them delivered — the client persists them to its
206
+ // model-visible sink (the local inbox file drained by a hook) FIRST, then acks
207
+ // via `ackAnnouncementIds` so the hub records the delivery only after the local
208
+ // write is confirmed. The MCP client only sets this when it actually has such a
209
+ // sink. Absent for older clients, so default behaviour (no delivery) is
210
+ // unchanged.
211
+ deliverAnnouncements: e.boolean().optional(),
212
+ // Phase-two ack of a previous `deliverAnnouncements` fetch: the ids the client
213
+ // has now durably written to its model-visible sink. The hub marks exactly
214
+ // these delivered to the caller's session. Decoupling the mark from the fetch
215
+ // guarantees a message is never recorded delivered before the client holds it
216
+ // (a lost response or a failed local append simply leaves it pending for the
217
+ // next beat). Absent on a plain presence/fetch beat.
218
+ ackAnnouncementIds: e.array(H).optional()
219
+ });
220
+ e.object({
221
+ ok: e.literal(!0),
222
+ // Pending announcements for the caller, delivered only when the request set
223
+ // `deliverAnnouncements`. Defaulted to [] for version-skew safety with older
224
+ // hubs (which return just { ok: true }).
225
+ announcements: e.array(W).default([])
226
+ });
227
+ e.object({
228
+ sessionId: e.string().uuid()
229
+ });
230
+ e.object({
231
+ ok: e.literal(!0)
232
+ });
233
+ const me = e.enum(["admin", "member"]), J = e.object({
234
+ id: e.string(),
235
+ slug: e.string(),
236
+ name: e.string(),
237
+ role: me
238
+ });
239
+ e.object({
240
+ name: e.string().min(1)
241
+ });
242
+ const Me = J, Le = e.object({
243
+ workspaces: e.array(J)
244
+ });
245
+ e.object({
246
+ name: e.string().min(1).optional()
247
+ });
248
+ const Pe = e.object({
249
+ // The raw shp_ token, shown once at creation and never returned again.
250
+ token: e.string(),
251
+ id: e.string()
252
+ }), Oe = e.object({
253
+ id: e.string(),
254
+ name: e.string().nullable(),
255
+ // ISO timestamp string (see IsoTimestamp note above) or null when unused / not revoked.
256
+ lastUsedAt: A.nullable(),
257
+ createdAt: A,
258
+ revokedAt: A.nullable()
259
+ }), Ge = e.object({
260
+ tokens: e.array(Oe)
261
+ });
262
+ e.object({
263
+ expiresInDays: e.number().int().positive().optional(),
264
+ maxUses: e.number().int().positive().optional()
265
+ });
266
+ const Ue = e.object({
267
+ code: e.string(),
268
+ // ISO timestamp string, or null when the invite never expires.
269
+ expiresAt: A.nullable(),
270
+ maxUses: e.number().int().positive(),
271
+ useCount: e.number().int().nonnegative()
272
+ }), He = e.object({
273
+ // The workspace the caller just joined.
274
+ workspace: J
275
+ }), We = e.object({
276
+ accountId: e.string(),
277
+ displayName: e.string().nullable(),
278
+ githubLogin: e.string().nullable(),
279
+ avatarUrl: e.string().nullable(),
280
+ role: me
281
+ }), Fe = e.object({
282
+ members: e.array(We)
283
+ }), qe = 5e3;
284
+ class P extends Error {
285
+ /**
286
+ * @param message - Human-readable failure description.
287
+ * @param status - Upstream HTTP status, omitted for network/abort failures.
288
+ */
289
+ constructor(i, r) {
290
+ super(i);
291
+ /** Upstream HTTP status for a non-2xx response; absent for transport errors. */
292
+ ee(this, "status");
293
+ this.name = "ShepherdClientError", r !== void 0 && (this.status = r);
294
+ }
295
+ }
296
+ function wt(n) {
297
+ return n instanceof Error ? n.message : "Something went wrong.";
298
+ }
299
+ async function ze(n) {
300
+ try {
301
+ const t = await n.json();
302
+ if (t !== null && typeof t == "object" && "error" in t && typeof t.error == "string")
303
+ return t.error;
304
+ } catch {
305
+ }
306
+ return "";
307
+ }
308
+ function At(n) {
309
+ const t = n.baseUrl.replace(/\/$/, ""), i = n.timeoutMs ?? qe, r = encodeURIComponent;
310
+ async function a(s, l, c = {}) {
311
+ var v, N;
312
+ const p = new AbortController(), u = setTimeout(() => p.abort(), i);
313
+ let d;
314
+ try {
315
+ const y = {
316
+ "Content-Type": "application/json"
317
+ }, f = await ((v = n.getAuthHeader) == null ? void 0 : v.call(n)), $ = {
318
+ method: s,
319
+ headers: { ...y, ...typeof f == "string" ? { Authorization: f } : f ?? {} },
320
+ signal: p.signal
321
+ };
322
+ c.body !== void 0 && ($.body = JSON.stringify(c.body)), d = await fetch(`${t}${l}`, $);
323
+ } catch (y) {
324
+ throw new P(
325
+ y instanceof Error ? y.message : String(y)
326
+ );
327
+ } finally {
328
+ clearTimeout(u);
329
+ }
330
+ if (d.status === 401)
331
+ throw (N = n.onUnauthorized) == null || N.call(n), new P("Unauthorized", 401);
332
+ if (!d.ok) {
333
+ const y = await ze(d);
334
+ throw new P(
335
+ y !== "" ? `HTTP ${d.status}: ${y}` : `HTTP ${d.status}`,
336
+ d.status
337
+ );
338
+ }
339
+ if (!c.schema) {
340
+ try {
341
+ await d.json();
342
+ } catch {
343
+ }
344
+ return;
345
+ }
346
+ const b = c.schema.safeParse(await d.json());
347
+ if (!b.success)
348
+ throw new P("Invalid response schema");
349
+ return b.data;
350
+ }
351
+ return {
352
+ baseUrl: t,
353
+ // --- multi-workspace management surface ---------------------------------
354
+ listWorkspaces() {
355
+ return a("GET", "/workspaces", {
356
+ schema: Le
357
+ });
358
+ },
359
+ createWorkspace(s) {
360
+ return a("POST", "/workspaces", {
361
+ body: s,
362
+ schema: Me
363
+ });
364
+ },
365
+ mintToken(s, l) {
366
+ return a("POST", `/workspaces/${r(s)}/tokens`, {
367
+ body: l,
368
+ schema: Pe
369
+ });
370
+ },
371
+ listTokens(s) {
372
+ return a("GET", `/workspaces/${r(s)}/tokens`, {
373
+ schema: Ge
374
+ });
375
+ },
376
+ revokeToken(s, l) {
377
+ return a(
378
+ "DELETE",
379
+ `/workspaces/${r(s)}/tokens/${r(l)}`
380
+ );
381
+ },
382
+ createInvite(s, l) {
383
+ return a("POST", `/workspaces/${r(s)}/invites`, {
384
+ body: l,
385
+ schema: Ue
386
+ });
387
+ },
388
+ revokeInvite(s, l) {
389
+ return a(
390
+ "POST",
391
+ `/workspaces/${r(s)}/invites/${r(l)}/revoke`
392
+ );
393
+ },
394
+ redeemInvite(s) {
395
+ return a("POST", `/invites/${r(s)}/redeem`, {
396
+ schema: He
397
+ });
398
+ },
399
+ listMembers(s) {
400
+ return a("GET", `/workspaces/${r(s)}/members`, {
401
+ schema: Fe
402
+ });
403
+ },
404
+ removeMember(s, l) {
405
+ return a(
406
+ "DELETE",
407
+ `/workspaces/${r(s)}/members/${r(l)}`
408
+ );
409
+ },
410
+ leave(s) {
411
+ return a("POST", `/workspaces/${r(s)}/leave`);
412
+ },
413
+ landscape(s) {
414
+ return a("GET", `/workspaces/${r(s)}/landscape`, {
415
+ schema: te
416
+ });
417
+ },
418
+ announceTo(s, l) {
419
+ return a("POST", `/workspaces/${r(s)}/announce`, {
420
+ body: l,
421
+ schema: ne
422
+ });
423
+ },
424
+ // --- self-host singular aliases (implicit single workspace) -------------
425
+ getLandscape() {
426
+ return a("GET", "/workspace/landscape", {
427
+ schema: te
428
+ });
429
+ },
430
+ announce(s) {
431
+ return a("POST", "/workspace/announce", {
432
+ body: s,
433
+ schema: ne
434
+ });
435
+ }
436
+ };
437
+ }
438
+ const he = ke(null);
439
+ function St({
440
+ client: n,
441
+ children: t
442
+ }) {
443
+ return /* @__PURE__ */ o(he.Provider, { value: n, children: t });
444
+ }
445
+ function pe() {
446
+ const n = we(he);
447
+ if (n === null)
448
+ throw new Error(
449
+ "useShepherdClient must be used within <ShepherdClientProvider>"
450
+ );
451
+ return n;
452
+ }
453
+ const Ke = 5e3, Be = 1e3;
454
+ function Ye(n) {
455
+ const t = pe(), i = n == null ? void 0 : n.workspaceId, r = (n == null ? void 0 : n.pollMs) ?? Ke, [a, s] = k(
456
+ null
457
+ ), [l, c] = k("live"), [p, u] = k(null), [, d] = k(0), b = x(t);
458
+ b.current = t;
459
+ const v = x(0), N = S(
460
+ async (f) => {
461
+ try {
462
+ const w = i ? await b.current.landscape(i) : await b.current.getLandscape();
463
+ if (f !== v.current) return;
464
+ s(w), u(Date.now()), c("live");
465
+ } catch (w) {
466
+ if (f !== v.current) return;
467
+ w instanceof P && w.status === 401 ? c("unauthorized") : c("reconnecting");
468
+ }
469
+ },
470
+ [i]
471
+ );
472
+ X(() => {
473
+ const f = ++v.current;
474
+ N(f);
475
+ const w = setInterval(() => {
476
+ document.visibilityState !== "hidden" && N(f);
477
+ }, r), $ = setInterval(() => {
478
+ d((R) => R + 1);
479
+ }, Be), C = () => {
480
+ document.visibilityState === "visible" && N(f);
481
+ };
482
+ return document.addEventListener("visibilitychange", C), () => {
483
+ v.current++, clearInterval(w), clearInterval($), document.removeEventListener("visibilitychange", C);
484
+ };
485
+ }, [N, r]);
486
+ const y = S(() => N(v.current), [N]);
487
+ return { snapshot: a, status: l, lastUpdatedMs: p, refresh: y };
488
+ }
489
+ function G(n, t) {
490
+ const i = Math.floor((t - Date.parse(n)) / 1e3);
491
+ if (i < 5) return "just now";
492
+ if (i < 60) return `${i}s ago`;
493
+ const r = Math.floor(i / 60);
494
+ if (r < 60) return `${r}m ago`;
495
+ const a = Math.floor(r / 60);
496
+ return a < 24 ? `${a}h ago` : `${Math.floor(a / 24)}d ago`;
497
+ }
498
+ function U(n) {
499
+ let t = 0;
500
+ for (let i = 0; i < n.length; i++) t = (t * 31 + n.charCodeAt(i)) % 360;
501
+ return `hsl(${t}, 38%, 42%)`;
502
+ }
503
+ function Q(n) {
504
+ if (!n) return "?";
505
+ const t = n.match(/[A-Z]/g);
506
+ return t && t.length >= 2 ? t[0] + t[1] : n.slice(0, 2).toUpperCase();
507
+ }
508
+ const ge = "A-Za-z0-9_-";
509
+ function Ve(n, t) {
510
+ const r = n.slice(0, t).match(new RegExp(`(?:^|\\s)@([${ge}]*)$`));
511
+ if (!r) return null;
512
+ const a = r[1];
513
+ return { start: t - a.length - 1, end: t, query: a };
514
+ }
515
+ function Xe(n, t) {
516
+ const i = new RegExp(`(?:^|\\s)@([${ge}]+)`, "g");
517
+ let r;
518
+ for (; (r = i.exec(n)) !== null; ) {
519
+ const a = r[1].toLowerCase(), s = t.find((l) => l.toLowerCase() === a);
520
+ if (s) return s;
521
+ }
522
+ return null;
523
+ }
524
+ function fe(n) {
525
+ return [...new Set(n.map((t) => t.repo))].sort();
526
+ }
527
+ function D(n, t) {
528
+ return t === null || t === "__all__" || n.repo === t;
529
+ }
530
+ function Ze(n, t) {
531
+ return [
532
+ ...new Set(
533
+ n.filter((i) => i.presence === "live" && D(i, t)).map((i) => i.name)
534
+ )
535
+ ].sort();
536
+ }
537
+ function se(n) {
538
+ const t = n.find((i) => i.status === "active");
539
+ return t ? t.repo : n.length ? n[0].repo : null;
540
+ }
541
+ function Je(n) {
542
+ return n === "dropped" ? "dropped" : "done";
543
+ }
544
+ function ae(n, t) {
545
+ if (!t) return "";
546
+ const i = Math.floor((Date.parse(t) - Date.parse(n)) / 1e3), r = Math.floor(i / 60);
547
+ return r < 60 ? `active ${r}m` : `active ${Math.floor(r / 60)}h`;
548
+ }
549
+ function Qe(n, t) {
550
+ const i = new Date(Date.parse(n)), r = new Date(t), a = (l) => new Date(l.getFullYear(), l.getMonth(), l.getDate()).getTime(), s = Math.round((a(r) - a(i)) / 864e5);
551
+ return s <= 0 ? "Today" : s === 1 ? "Yesterday" : i.toLocaleDateString(void 0, { month: "short", day: "numeric" });
552
+ }
553
+ function re(n) {
554
+ let t = n.toLowerCase().replace(/\\/g, "/");
555
+ t.endsWith("/") && (t = t.replace(/\/+$/, "") + "/**");
556
+ const i = [];
557
+ for (const r of t.split("/"))
558
+ if (!(r === "" || r === ".")) {
559
+ if (r === "..") {
560
+ i.length && i.pop();
561
+ continue;
562
+ }
563
+ i.push(r);
564
+ }
565
+ return i;
566
+ }
567
+ const oe = /[*?{}[\]]/;
568
+ function et(n, t) {
569
+ return n === "*" ? !0 : (oe.test(n) || !oe.test(t)) && n === t;
570
+ }
571
+ function tt(n, t) {
572
+ const i = /* @__PURE__ */ new Map();
573
+ function r(a, s) {
574
+ if (t.length - s === 0) {
575
+ for (let b = a; b < n.length; b++) if (n[b] !== "**") return !1;
576
+ return !0;
577
+ }
578
+ if (n.length - a === 0) return !1;
579
+ const l = a * (t.length + 1) + s, c = i.get(l);
580
+ if (c !== void 0) return c;
581
+ const p = n[a], u = t[s];
582
+ let d;
583
+ return p === "**" ? d = r(a + 1, s) || r(a, s + 1) : u === "**" ? d = !1 : d = et(p, u) && r(a + 1, s + 1), i.set(l, d), d;
584
+ }
585
+ return r(0, 0);
586
+ }
587
+ function ie(n, t) {
588
+ if (t.length === 0) return !0;
589
+ const i = n.map(re);
590
+ return t.every((r) => {
591
+ const a = re(r);
592
+ return i.some((s) => tt(s, a));
593
+ });
594
+ }
595
+ function nt(n) {
596
+ const t = /* @__PURE__ */ new Map();
597
+ for (const a of n)
598
+ t.has(a.agentName) || t.set(a.agentName, []), t.get(a.agentName).push(a);
599
+ const i = (a, s) => s.createdAt.localeCompare(a.createdAt), r = [];
600
+ for (const [a, s] of t) {
601
+ const l = [...s].sort(i), c = l.map(
602
+ (u) => l.some(
603
+ (d) => d !== u && ie(d.pathGlobs, u.pathGlobs) && !ie(u.pathGlobs, d.pathGlobs)
604
+ )
605
+ ), p = l[0];
606
+ r.push({
607
+ agentName: a,
608
+ model: p.model,
609
+ program: p.program,
610
+ repo: p.repo,
611
+ primaries: l.filter((u, d) => !c[d]),
612
+ narrower: l.filter((u, d) => c[d])
613
+ });
614
+ }
615
+ return r.sort((a, s) => {
616
+ const l = a.primaries.concat(a.narrower)[0].createdAt;
617
+ return s.primaries.concat(s.narrower)[0].createdAt.localeCompare(l);
618
+ }), r;
619
+ }
620
+ const ce = { active: 0, done: 0 };
621
+ function st({
622
+ repos: n,
623
+ counts: t,
624
+ selected: i,
625
+ onSelect: r
626
+ }) {
627
+ const [a, s] = k(!1), l = x(null);
628
+ if (X(() => {
629
+ var v;
630
+ if (!a) return;
631
+ const d = (v = l.current) == null ? void 0 : v.querySelector(".repo-menu");
632
+ if (!d) return;
633
+ const b = d.querySelector('[aria-selected="true"]') ?? d.querySelector("button");
634
+ b == null || b.focus();
635
+ }, [a]), n.length < 2) return null;
636
+ const c = i === null || i === "__all__", p = c ? "All repos" : i, u = (d, b) => {
637
+ const v = d === null ? t.__all__ ?? ce : t[d] ?? ce, N = d === null ? c : i === d, y = "repo-mi" + (N ? " on" : "") + (d === null ? " all" : "");
638
+ return /* @__PURE__ */ m(
639
+ "button",
640
+ {
641
+ type: "button",
642
+ className: y,
643
+ role: "option",
644
+ "aria-selected": N,
645
+ onClick: (f) => {
646
+ f.stopPropagation(), s(!1), r(d);
647
+ },
648
+ children: [
649
+ /* @__PURE__ */ o("span", { children: b }),
650
+ /* @__PURE__ */ o("span", { className: "ct", children: `${v.active} active · ${v.done} done` })
651
+ ]
652
+ },
653
+ d ?? "__all__"
654
+ );
655
+ };
656
+ return /* @__PURE__ */ m(
657
+ "span",
658
+ {
659
+ ref: l,
660
+ className: "repo",
661
+ onKeyDown: (d) => {
662
+ d.key === "Escape" && s(!1);
663
+ },
664
+ children: [
665
+ /* @__PURE__ */ m(
666
+ "button",
667
+ {
668
+ type: "button",
669
+ className: "repo-trig",
670
+ "aria-haspopup": "listbox",
671
+ "aria-expanded": a,
672
+ "aria-label": `Filter by repo (current: ${p})`,
673
+ onClick: (d) => {
674
+ d.stopPropagation(), s((b) => !b);
675
+ },
676
+ children: [
677
+ /* @__PURE__ */ o("span", { className: "slash", children: "/" }),
678
+ " " + p + " ",
679
+ /* @__PURE__ */ o("span", { children: "▼" })
680
+ ]
681
+ }
682
+ ),
683
+ a && /* @__PURE__ */ m("div", { className: "repo-menu", role: "listbox", children: [
684
+ n.map((d) => u(d, d)),
685
+ u(null, "All repos")
686
+ ] })
687
+ ]
688
+ }
689
+ );
690
+ }
691
+ function at({ agents: n, tasks: t, selectedRepo: i }) {
692
+ const r = n.filter(
693
+ (c) => c.presence === "live" && D({ repo: c.repo ?? "" }, i)
694
+ ), a = /* @__PURE__ */ new Set();
695
+ for (const c of t) c.status === "active" && a.add(c.agentName);
696
+ const s = (c) => a.has(c.name), l = [...r].sort((c, p) => {
697
+ const u = s(c), d = s(p);
698
+ return u !== d ? u ? -1 : 1 : c.name.localeCompare(p.name);
699
+ });
700
+ return /* @__PURE__ */ o("div", { className: "crew", id: "crew", children: l.map((c) => /* @__PURE__ */ m(
701
+ "div",
702
+ {
703
+ className: "person" + (s(c) ? "" : " person--idle"),
704
+ children: [
705
+ /* @__PURE__ */ o("div", { className: "avatar", style: { background: U(c.name) }, children: Q(c.name) }),
706
+ /* @__PURE__ */ o("span", { className: "person__name", children: c.name })
707
+ ]
708
+ },
709
+ c.name
710
+ )) });
711
+ }
712
+ function be({ globs: n }) {
713
+ return n.length <= 1 ? /* @__PURE__ */ m("div", { className: "terr", children: [
714
+ /* @__PURE__ */ o("span", { className: "terr__lbl", children: "territory" }),
715
+ n.map((t) => /* @__PURE__ */ o("span", { className: "glob", children: t }, t))
716
+ ] }) : /* @__PURE__ */ m("details", { className: "terrx", children: [
717
+ /* @__PURE__ */ m("summary", { children: [
718
+ /* @__PURE__ */ o("span", { className: "terr__lbl", children: "territory" }),
719
+ /* @__PURE__ */ m("span", { className: "glob glob--more", children: [
720
+ /* @__PURE__ */ o("span", { className: "tw", children: "▶" }),
721
+ ` ${n.length} paths`
722
+ ] })
723
+ ] }),
724
+ /* @__PURE__ */ o("div", { className: "terr terr--full", children: n.map((t) => /* @__PURE__ */ o("span", { className: "glob", children: t }, t)) })
725
+ ] });
726
+ }
727
+ const Y = (n) => `${n.agentName}|${n.repo}|${n.createdAt}`, rt = (n) => n === null || n === "__all__";
728
+ function V({
729
+ task: n,
730
+ nowMs: t
731
+ }) {
732
+ return /* @__PURE__ */ m(ye, { children: [
733
+ /* @__PURE__ */ o("div", { className: "task__intent", children: n.intent }),
734
+ /* @__PURE__ */ o(be, { globs: n.pathGlobs }),
735
+ /* @__PURE__ */ o("div", { className: "task__meta", children: `started ${G(n.createdAt, t)}` })
736
+ ] });
737
+ }
738
+ function ot({
739
+ task: n,
740
+ nowMs: t,
741
+ allRepos: i
742
+ }) {
743
+ const r = n.model || n.program;
744
+ return /* @__PURE__ */ m("div", { className: "task", children: [
745
+ /* @__PURE__ */ m("div", { className: "task__r1", children: [
746
+ /* @__PURE__ */ o("span", { className: "task__who", children: n.agentName }),
747
+ i && /* @__PURE__ */ o("span", { className: "task__repo", children: n.repo }),
748
+ r && /* @__PURE__ */ o("span", { className: "task__tag", children: r }),
749
+ /* @__PURE__ */ o("span", { className: "livedot", title: "active" })
750
+ ] }),
751
+ /* @__PURE__ */ o(V, { task: n, nowMs: t })
752
+ ] });
753
+ }
754
+ function it({
755
+ group: n,
756
+ nowMs: t,
757
+ allRepos: i
758
+ }) {
759
+ const r = n.model || n.program, a = n.primaries.length + n.narrower.length, s = n.narrower.length;
760
+ return /* @__PURE__ */ m("div", { className: "grp", children: [
761
+ /* @__PURE__ */ m("div", { className: "grp__head", children: [
762
+ /* @__PURE__ */ o("span", { className: "grp__who", children: n.agentName }),
763
+ i && /* @__PURE__ */ o("span", { className: "task__repo", children: n.repo }),
764
+ r && /* @__PURE__ */ o("span", { className: "grp__tag", children: r }),
765
+ /* @__PURE__ */ o("span", { className: "grp__count", children: `· ${a} active` }),
766
+ /* @__PURE__ */ o("span", { className: "grp__dot", title: "live" })
767
+ ] }),
768
+ /* @__PURE__ */ o("div", { className: "claims", children: n.primaries.map((l) => /* @__PURE__ */ o("div", { className: "claim", children: /* @__PURE__ */ o(V, { task: l, nowMs: t }) }, Y(l))) }),
769
+ s > 0 && /* @__PURE__ */ m("details", { className: "fold", children: [
770
+ /* @__PURE__ */ m("summary", { children: [
771
+ /* @__PURE__ */ o("span", { className: "tw", children: "▶" }),
772
+ ` +${s} narrower claim${s > 1 ? "s" : ""}`
773
+ ] }),
774
+ /* @__PURE__ */ o("div", { className: "fold__body", children: n.narrower.map((l) => /* @__PURE__ */ m("div", { className: "claim", children: [
775
+ /* @__PURE__ */ o(V, { task: l, nowMs: t }),
776
+ /* @__PURE__ */ o("div", { className: "covered", children: "⊂ covered by a claim above" })
777
+ ] }, Y(l))) })
778
+ ] })
779
+ ] });
780
+ }
781
+ function ct({
782
+ tasks: n,
783
+ nowMs: t,
784
+ selectedRepo: i
785
+ }) {
786
+ const r = rt(i), a = n.filter(
787
+ (s) => s.status === "active" && D(s, i)
788
+ );
789
+ if (a.length === 0) {
790
+ const s = r ? "Nothing active right now." : `Nothing active in ${i}.`;
791
+ return /* @__PURE__ */ o("div", { id: "active-list", children: /* @__PURE__ */ o("div", { className: "empty", children: s }) });
792
+ }
793
+ return /* @__PURE__ */ o("div", { id: "active-list", children: nt(a).map((s) => {
794
+ const l = s.primaries.length === 1 && s.narrower.length === 0, c = Y(s.primaries.concat(s.narrower)[0]);
795
+ return l ? /* @__PURE__ */ o(
796
+ ot,
797
+ {
798
+ task: s.primaries[0],
799
+ nowMs: t,
800
+ allRepos: r
801
+ },
802
+ c
803
+ ) : /* @__PURE__ */ o(it, { group: s, nowMs: t, allRepos: r }, c);
804
+ }) });
805
+ }
806
+ const lt = (n) => `${n.agentName}|${n.repo}|${n.createdAt}`, dt = (n) => n === null || n === "__all__";
807
+ function ut({
808
+ tasks: n,
809
+ nowMs: t,
810
+ selectedRepo: i,
811
+ doneShown: r,
812
+ onLoadMore: a
813
+ }) {
814
+ const s = dt(i), l = n.filter(
815
+ (u) => u.status !== "active" && D(u, i)
816
+ );
817
+ if (l.length === 0) {
818
+ const u = s ? "No finished tasks yet." : `No finished tasks in ${i} yet.`;
819
+ return /* @__PURE__ */ o("div", { id: "done-list", children: /* @__PURE__ */ o("div", { className: "empty", children: u }) });
820
+ }
821
+ const c = l.slice(0, r);
822
+ let p = null;
823
+ return /* @__PURE__ */ m("div", { id: "done-list", children: [
824
+ c.map((u) => {
825
+ const d = Qe(u.endedAt ?? u.createdAt, t), b = d !== p ? d : null;
826
+ p = d;
827
+ const v = u.status === "dropped", N = v ? `went offline ${G(u.endedAt ?? u.createdAt, t)} — no done signal` : `finished ${G(u.endedAt ?? u.createdAt, t)}${ae(u.createdAt, u.endedAt) ? " · " + ae(u.createdAt, u.endedAt) : ""}`;
828
+ return /* @__PURE__ */ m(Ae, { children: [
829
+ b !== null && /* @__PURE__ */ o("div", { className: "day", children: b }),
830
+ /* @__PURE__ */ m("div", { className: "task", children: [
831
+ /* @__PURE__ */ m("div", { className: "task__r1", children: [
832
+ /* @__PURE__ */ o("span", { className: "task__who", children: u.agentName }),
833
+ s && /* @__PURE__ */ o("span", { className: "task__repo", children: u.repo }),
834
+ /* @__PURE__ */ o(
835
+ "span",
836
+ {
837
+ className: "task__stat" + (v ? " task__stat--drop" : ""),
838
+ children: Je(u.status)
839
+ }
840
+ )
841
+ ] }),
842
+ /* @__PURE__ */ o("div", { className: "task__intent", children: u.intent }),
843
+ /* @__PURE__ */ o(be, { globs: u.pathGlobs }),
844
+ /* @__PURE__ */ o("div", { className: "task__meta", children: N })
845
+ ] })
846
+ ] }, lt(u));
847
+ }),
848
+ l.length > r && /* @__PURE__ */ o(
849
+ "div",
850
+ {
851
+ className: "more",
852
+ role: "button",
853
+ tabIndex: 0,
854
+ onClick: a,
855
+ onKeyDown: (u) => {
856
+ (u.key === "Enter" || u.key === " ") && (u.preventDefault(), a());
857
+ },
858
+ children: `Load older · ${r} of ${l.length}`
859
+ }
860
+ )
861
+ ] });
862
+ }
863
+ const mt = 80;
864
+ function ht({ announcements: n, selectedRepo: t, nowMs: i }) {
865
+ const r = x(null), a = x(!0), s = r.current;
866
+ s && (a.current = s.scrollHeight - s.scrollTop - s.clientHeight < mt);
867
+ const l = [...n].filter((c) => D(c, t)).reverse();
868
+ return Se(() => {
869
+ a.current && r.current && (r.current.scrollTop = r.current.scrollHeight);
870
+ }, [l.length]), /* @__PURE__ */ o("div", { id: "chat", className: "chat", ref: r, children: l.length === 0 ? /* @__PURE__ */ o("div", { className: "empty", children: "No announcements yet — agents will post here as they coordinate." }) : l.map((c, p) => {
871
+ const d = "msg" + (c.targetAgentName !== null || c.toAdmin ? " msg--targeted" : "") + (c.fromAdmin ? " msg--me" : "");
872
+ return (
873
+ // The feed has no stable id; index is acceptable because the list is
874
+ // append-only oldest->newest and rows are never reordered in place.
875
+ /* @__PURE__ */ m("div", { className: d, children: [
876
+ /* @__PURE__ */ o(
877
+ "div",
878
+ {
879
+ className: "msg__avatar",
880
+ style: { background: U(c.fromAgentName) },
881
+ children: Q(c.fromAgentName)
882
+ }
883
+ ),
884
+ /* @__PURE__ */ m("div", { className: "msg__body", children: [
885
+ /* @__PURE__ */ m("div", { className: "msg__head", children: [
886
+ /* @__PURE__ */ o(
887
+ "span",
888
+ {
889
+ className: "msg__who",
890
+ style: { color: U(c.fromAgentName) },
891
+ children: c.fromAgentName
892
+ }
893
+ ),
894
+ c.fromHuman ? /* @__PURE__ */ o("span", { className: "msg__human", children: c.fromHuman }) : null,
895
+ c.targetAgentName !== null ? /* @__PURE__ */ o("span", { className: "msg__to", children: `→ @${c.targetAgentName}` }) : c.toAdmin ? /* @__PURE__ */ o("span", { className: "msg__to", children: "→ admin" }) : null,
896
+ /* @__PURE__ */ o("span", { className: "msg__time", children: G(c.createdAt, i) })
897
+ ] }),
898
+ /* @__PURE__ */ o("div", { className: "msg__text", children: c.body })
899
+ ] })
900
+ ] }, p)
901
+ );
902
+ }) });
903
+ }
904
+ const pt = 8;
905
+ function gt({ agents: n, selectedRepo: t, workspaceId: i, onSent: r }) {
906
+ const a = pe(), s = x(null), [l, c] = k(""), [p, u] = k([]), [d, b] = k(0), [v, N] = k(null), [y, f] = k(!1), [w, $] = k(!1), C = Ze(
907
+ n.map((h) => ({ name: h.name, presence: h.presence, repo: h.repo ?? "" })),
908
+ t
909
+ ), R = p.length > 0, T = S(() => {
910
+ u([]), N(null);
911
+ }, []), M = S(
912
+ (h, _) => {
913
+ const g = Ve(h, _);
914
+ if (!g) {
915
+ T();
916
+ return;
917
+ }
918
+ const E = g.query.toLowerCase(), j = C.filter((ve) => ve.toLowerCase().startsWith(E)).slice(0, pt);
919
+ u(j), N({ start: g.start, end: g.end }), b(0);
920
+ },
921
+ [C, T]
922
+ ), L = S(
923
+ (h) => {
924
+ if (!v) return;
925
+ const _ = "@" + h + " ", g = l.slice(0, v.start) + _ + l.slice(v.end), E = v.start + _.length;
926
+ c(g), T(), requestAnimationFrame(() => {
927
+ const j = s.current;
928
+ j && (j.focus(), j.setSelectionRange(E, E));
929
+ });
930
+ },
931
+ [v, l, T]
932
+ ), O = S(
933
+ (h) => {
934
+ var g;
935
+ c(h);
936
+ const _ = ((g = s.current) == null ? void 0 : g.selectionStart) ?? h.length;
937
+ M(h, _);
938
+ },
939
+ [M]
940
+ ), I = S(
941
+ (h) => {
942
+ R && (h.key === "ArrowDown" ? (h.preventDefault(), b((_) => (_ + 1) % p.length)) : h.key === "ArrowUp" ? (h.preventDefault(), b((_) => (_ - 1 + p.length) % p.length)) : h.key === "Enter" || h.key === "Tab" ? (h.preventDefault(), L(p[d])) : h.key === "Escape" && (h.preventDefault(), T()));
943
+ },
944
+ [R, p, d, L, T]
945
+ ), F = S(
946
+ async (h) => {
947
+ h.preventDefault();
948
+ const _ = l.trim();
949
+ if (!_) return;
950
+ const g = Xe(_, C), E = t === null || t === "__all__" ? null : t;
951
+ f(!0), $(!1);
952
+ try {
953
+ const j = { body: _, targetAgentName: g, repo: E };
954
+ await (i !== void 0 ? a.announceTo(i, j) : a.announce(j)), c(""), T(), await r();
955
+ } catch {
956
+ $(!0);
957
+ } finally {
958
+ f(!1);
959
+ }
960
+ },
961
+ [l, C, t, i, a, T, r]
962
+ );
963
+ return /* @__PURE__ */ m("div", { className: "composer", children: [
964
+ /* @__PURE__ */ o(
965
+ "div",
966
+ {
967
+ id: "mention-pop",
968
+ className: "mention-pop",
969
+ role: "listbox",
970
+ "aria-label": "Agents to mention",
971
+ hidden: !R,
972
+ children: p.map((h, _) => /* @__PURE__ */ m(
973
+ "button",
974
+ {
975
+ id: `mention-opt-${_}`,
976
+ type: "button",
977
+ role: "option",
978
+ "aria-selected": _ === d,
979
+ className: "mention-mi" + (_ === d ? " on" : ""),
980
+ onMouseDown: (g) => {
981
+ g.preventDefault(), L(h);
982
+ },
983
+ children: [
984
+ /* @__PURE__ */ o("div", { className: "ma", style: { background: U(h) }, children: Q(h) }),
985
+ /* @__PURE__ */ o("span", { children: h })
986
+ ]
987
+ },
988
+ h
989
+ ))
990
+ }
991
+ ),
992
+ /* @__PURE__ */ m(
993
+ "form",
994
+ {
995
+ id: "chat-form",
996
+ className: "chat-form",
997
+ onSubmit: (h) => {
998
+ F(h);
999
+ },
1000
+ children: [
1001
+ /* @__PURE__ */ o(
1002
+ "input",
1003
+ {
1004
+ id: "chat-input",
1005
+ className: "chat-input",
1006
+ type: "text",
1007
+ autoComplete: "off",
1008
+ "aria-label": "Message the team",
1009
+ placeholder: "Message the team… use @name to direct it",
1010
+ role: "combobox",
1011
+ "aria-expanded": R,
1012
+ "aria-controls": "mention-pop",
1013
+ "aria-autocomplete": "list",
1014
+ "aria-activedescendant": R ? `mention-opt-${d}` : void 0,
1015
+ ref: s,
1016
+ value: l,
1017
+ onChange: (h) => O(h.target.value),
1018
+ onKeyDown: I
1019
+ }
1020
+ ),
1021
+ /* @__PURE__ */ o("button", { id: "chat-send", className: "chat-send", type: "submit", disabled: y, children: "Send" })
1022
+ ]
1023
+ }
1024
+ ),
1025
+ w ? /* @__PURE__ */ o("div", { className: "chat__note", role: "status", children: "send failed — retry" }) : null
1026
+ ] });
1027
+ }
1028
+ const le = "shepherd.tab", z = "shepherd.repo", K = 10, ft = {
1029
+ live: { text: "live", kind: "ok" },
1030
+ reconnecting: { text: "reconnecting…", kind: "warn" },
1031
+ // app.js cleared the token and re-prompted on 401; in the auth-agnostic port
1032
+ // the injected client's onUnauthorized owns any token handling, so the board
1033
+ // only surfaces the rejected state.
1034
+ unauthorized: { text: "token rejected", kind: "error" }
1035
+ };
1036
+ function de(n) {
1037
+ try {
1038
+ return localStorage.getItem(n);
1039
+ } catch {
1040
+ return null;
1041
+ }
1042
+ }
1043
+ function B(n, t) {
1044
+ try {
1045
+ localStorage.setItem(n, t);
1046
+ } catch {
1047
+ }
1048
+ }
1049
+ function bt(n, t, i) {
1050
+ const r = fe(t.tasks);
1051
+ let a = n;
1052
+ if (a === null && r.length >= 2 && (a = se(t.tasks)), a !== null && a !== "__all__" && !r.includes(a) && (a = "__all__"), i && a !== null && a !== "__all__") {
1053
+ const s = t.tasks.some(
1054
+ (c) => c.status === "active" && c.repo === a
1055
+ ), l = t.tasks.some((c) => c.status === "active");
1056
+ !s && l && (a = se(t.tasks));
1057
+ }
1058
+ return a;
1059
+ }
1060
+ function vt(n) {
1061
+ const t = {}, i = (r, a) => {
1062
+ const s = t[r] ?? (t[r] = { active: 0, done: 0 });
1063
+ s[a]++;
1064
+ };
1065
+ for (const r of n.tasks) {
1066
+ const a = r.status === "active" ? "active" : "done";
1067
+ i(r.repo, a), i("__all__", a);
1068
+ }
1069
+ return t;
1070
+ }
1071
+ function Tt({ workspaceId: n } = {}) {
1072
+ const { snapshot: t, status: i, lastUpdatedMs: r, refresh: a } = Ye({ workspaceId: n }), [s, l] = k(
1073
+ () => de(le) === "chat" ? "chat" : "tasks"
1074
+ ), [c, p] = k(
1075
+ () => de(z)
1076
+ ), [u, d] = k(K), b = x(!0), v = S((g) => {
1077
+ l(g), B(le, g);
1078
+ }, []), N = S((g) => {
1079
+ const E = g === null ? "__all__" : g;
1080
+ p(E), B(z, E), d(K);
1081
+ }, []), y = S(() => {
1082
+ d((g) => g + K);
1083
+ }, []), f = t ? bt(c, t, b.current) : c;
1084
+ X(() => {
1085
+ t && (b.current = !1, f !== c && (p(f), f !== null && f !== "__all__" && B(z, f)));
1086
+ }, [t, f, c]);
1087
+ const w = t ? Date.parse(t.serverTime) : Date.now(), $ = t ? fe(t.tasks) : [], C = t ? vt(t) : {}, R = t ? t.agents.filter(
1088
+ (g) => g.presence === "live" && D({ repo: g.repo ?? "" }, f)
1089
+ ).length : 0, T = t ? t.tasks.filter((g) => g.status === "active" && D(g, f)).length : 0, M = ft[i], L = r === null ? "" : `updated ${Math.floor((Date.now() - r) / 1e3)}s ago`, O = (t == null ? void 0 : t.agents) ?? [], I = (t == null ? void 0 : t.tasks) ?? [], F = (t == null ? void 0 : t.announcements) ?? [], h = I.filter(
1090
+ (g) => g.status === "active" && D(g, f)
1091
+ ).length, _ = I.filter(
1092
+ (g) => g.status !== "active" && D(g, f)
1093
+ ).length;
1094
+ return /* @__PURE__ */ m("div", { id: "board", children: [
1095
+ /* @__PURE__ */ m("header", { children: [
1096
+ /* @__PURE__ */ o("span", { className: "brand", children: "Shepherd" }),
1097
+ /* @__PURE__ */ o(
1098
+ st,
1099
+ {
1100
+ repos: $,
1101
+ counts: C,
1102
+ selected: f,
1103
+ onSelect: N
1104
+ }
1105
+ ),
1106
+ /* @__PURE__ */ m("span", { className: "vitals", children: [
1107
+ /* @__PURE__ */ o("b", { id: "vitals-online", children: R }),
1108
+ " online · ",
1109
+ /* @__PURE__ */ o("b", { id: "vitals-active", children: T }),
1110
+ " active"
1111
+ ] }),
1112
+ /* @__PURE__ */ o("span", { className: "grow" }),
1113
+ /* @__PURE__ */ o("span", { id: "status", className: "status" + (M.kind ? ` status--${M.kind}` : ""), children: M.text }),
1114
+ /* @__PURE__ */ o("span", { id: "freshness", className: "freshness", children: L }),
1115
+ /* @__PURE__ */ m("nav", { className: "tabs", role: "tablist", "aria-label": "Dashboard views", children: [
1116
+ /* @__PURE__ */ o(
1117
+ "button",
1118
+ {
1119
+ className: "tab" + (s === "tasks" ? " tab--active" : ""),
1120
+ "data-tab": "tasks",
1121
+ type: "button",
1122
+ role: "tab",
1123
+ id: "tab-tasks",
1124
+ "aria-controls": "panel-tasks",
1125
+ "aria-selected": s === "tasks",
1126
+ onClick: () => v("tasks"),
1127
+ children: "Tasks"
1128
+ }
1129
+ ),
1130
+ /* @__PURE__ */ o(
1131
+ "button",
1132
+ {
1133
+ className: "tab" + (s === "chat" ? " tab--active" : ""),
1134
+ "data-tab": "chat",
1135
+ type: "button",
1136
+ role: "tab",
1137
+ id: "tab-chat",
1138
+ "aria-controls": "panel-chat",
1139
+ "aria-selected": s === "chat",
1140
+ onClick: () => v("chat"),
1141
+ children: "Chat"
1142
+ }
1143
+ )
1144
+ ] })
1145
+ ] }),
1146
+ /* @__PURE__ */ m(
1147
+ "section",
1148
+ {
1149
+ id: "panel-tasks",
1150
+ role: "tabpanel",
1151
+ "aria-labelledby": "tab-tasks",
1152
+ hidden: s !== "tasks",
1153
+ children: [
1154
+ /* @__PURE__ */ o(at, { agents: O, tasks: I, selectedRepo: f }),
1155
+ /* @__PURE__ */ m("div", { className: "board", children: [
1156
+ /* @__PURE__ */ m("div", { className: "col", children: [
1157
+ /* @__PURE__ */ m("div", { className: "colhead", children: [
1158
+ /* @__PURE__ */ o("h2", { children: "Active" }),
1159
+ /* @__PURE__ */ o("span", { className: "n", id: "active-count", children: h })
1160
+ ] }),
1161
+ /* @__PURE__ */ o(ct, { tasks: I, nowMs: w, selectedRepo: f })
1162
+ ] }),
1163
+ /* @__PURE__ */ o("div", { className: "board__rule" }),
1164
+ /* @__PURE__ */ m("div", { className: "col", children: [
1165
+ /* @__PURE__ */ m("div", { className: "colhead", children: [
1166
+ /* @__PURE__ */ o("h2", { children: "Done" }),
1167
+ /* @__PURE__ */ o("span", { className: "n", id: "done-count", children: _ })
1168
+ ] }),
1169
+ /* @__PURE__ */ o(
1170
+ ut,
1171
+ {
1172
+ tasks: I,
1173
+ nowMs: w,
1174
+ selectedRepo: f,
1175
+ doneShown: u,
1176
+ onLoadMore: y
1177
+ }
1178
+ )
1179
+ ] })
1180
+ ] })
1181
+ ]
1182
+ }
1183
+ ),
1184
+ /* @__PURE__ */ o(
1185
+ "section",
1186
+ {
1187
+ id: "panel-chat",
1188
+ role: "tabpanel",
1189
+ "aria-labelledby": "tab-chat",
1190
+ hidden: s !== "chat",
1191
+ children: /* @__PURE__ */ m("div", { className: "chat-wrap", children: [
1192
+ /* @__PURE__ */ o(ht, { announcements: F, selectedRepo: f, nowMs: w }),
1193
+ /* @__PURE__ */ o(
1194
+ gt,
1195
+ {
1196
+ agents: O,
1197
+ selectedRepo: f,
1198
+ workspaceId: n,
1199
+ onSent: a
1200
+ }
1201
+ )
1202
+ ] })
1203
+ }
1204
+ )
1205
+ ] });
1206
+ }
1207
+ export {
1208
+ Tt as D,
1209
+ P as S,
1210
+ St as a,
1211
+ At as c,
1212
+ wt as d,
1213
+ pe as u
1214
+ };