@kbve/droid 0.0.6 → 0.1.2

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 (190) hide show
  1. package/droid.mjs +2312 -74
  2. package/index.d.ts +32 -0
  3. package/lib/agents/api-types.d.ts +121 -0
  4. package/lib/agents/api-types.d.ts.map +1 -0
  5. package/lib/agents/auth/init.d.ts +8 -0
  6. package/lib/agents/auth/init.d.ts.map +1 -0
  7. package/lib/agents/cache.d.ts +9 -0
  8. package/lib/agents/cache.d.ts.map +1 -0
  9. package/lib/agents/client/callGuildVault.d.ts +3 -0
  10. package/lib/agents/client/callGuildVault.d.ts.map +1 -0
  11. package/lib/agents/client/callJson.d.ts +3 -0
  12. package/lib/agents/client/callJson.d.ts.map +1 -0
  13. package/lib/agents/config.d.ts +36 -0
  14. package/lib/agents/config.d.ts.map +1 -0
  15. package/lib/agents/constants.d.ts +11 -0
  16. package/lib/agents/constants.d.ts.map +1 -0
  17. package/lib/agents/createAgents.d.ts +4 -0
  18. package/lib/agents/createAgents.d.ts.map +1 -0
  19. package/lib/agents/ctx.d.ts +46 -0
  20. package/lib/agents/ctx.d.ts.map +1 -0
  21. package/lib/agents/discord/bot.d.ts +21 -0
  22. package/lib/agents/discord/bot.d.ts.map +1 -0
  23. package/lib/agents/discord/guilds.d.ts +8 -0
  24. package/lib/agents/discord/guilds.d.ts.map +1 -0
  25. package/lib/agents/formDrafts.d.ts +5 -0
  26. package/lib/agents/formDrafts.d.ts.map +1 -0
  27. package/lib/agents/generated/agents-schema.d.ts +73 -0
  28. package/lib/agents/generated/agents-schema.d.ts.map +1 -0
  29. package/lib/agents/generated/discordsh-agents-schema.d.ts +71 -0
  30. package/lib/agents/generated/discordsh-agents-schema.d.ts.map +1 -0
  31. package/lib/agents/github/backfill.d.ts +23 -0
  32. package/lib/agents/github/backfill.d.ts.map +1 -0
  33. package/lib/agents/github/events.d.ts +10 -0
  34. package/lib/agents/github/events.d.ts.map +1 -0
  35. package/lib/agents/github/pat.d.ts +13 -0
  36. package/lib/agents/github/pat.d.ts.map +1 -0
  37. package/lib/agents/github/repoConfig.d.ts +25 -0
  38. package/lib/agents/github/repoConfig.d.ts.map +1 -0
  39. package/lib/agents/github/tokens.d.ts +22 -0
  40. package/lib/agents/github/tokens.d.ts.map +1 -0
  41. package/lib/agents/github/webhook.d.ts +37 -0
  42. package/lib/agents/github/webhook.d.ts.map +1 -0
  43. package/lib/agents/index.d.ts +8 -0
  44. package/lib/agents/index.d.ts.map +1 -0
  45. package/lib/agents/state/atoms.d.ts +122 -0
  46. package/lib/agents/state/atoms.d.ts.map +1 -0
  47. package/lib/agents/types.d.ts +65 -0
  48. package/lib/agents/types.d.ts.map +1 -0
  49. package/lib/gateway/SupabaseGateway.d.ts +38 -0
  50. package/lib/gateway/SupabaseGateway.d.ts.map +1 -0
  51. package/lib/gateway/WorkerCommunication.d.ts +39 -0
  52. package/lib/gateway/WorkerCommunication.d.ts.map +1 -0
  53. package/lib/gateway/WorkerPool.d.ts +55 -0
  54. package/lib/gateway/WorkerPool.d.ts.map +1 -0
  55. package/lib/gateway/capabilities.d.ts +23 -0
  56. package/lib/gateway/capabilities.d.ts.map +1 -0
  57. package/lib/gateway/index.d.ts +9 -0
  58. package/lib/gateway/index.d.ts.map +1 -0
  59. package/lib/gateway/strategies/DirectStrategy.d.ts +52 -0
  60. package/lib/gateway/strategies/DirectStrategy.d.ts.map +1 -0
  61. package/lib/gateway/strategies/SharedWorkerStrategy.d.ts +46 -0
  62. package/lib/gateway/strategies/SharedWorkerStrategy.d.ts.map +1 -0
  63. package/lib/gateway/strategies/WebWorkerStrategy.d.ts +45 -0
  64. package/lib/gateway/strategies/WebWorkerStrategy.d.ts.map +1 -0
  65. package/lib/gateway/types.d.ts +81 -0
  66. package/lib/gateway/types.d.ts.map +1 -0
  67. package/lib/workers/canvas-ui-renderer.d.ts +25 -0
  68. package/lib/workers/canvas-ui-renderer.d.ts.map +1 -0
  69. package/lib/workers/canvas-worker.d.ts +14 -0
  70. package/lib/workers/canvas-worker.d.ts.map +1 -0
  71. package/lib/workers/canvas-worker.js +5 -302
  72. package/lib/workers/data.d.ts +40 -0
  73. package/lib/workers/data.d.ts.map +1 -0
  74. package/lib/workers/db-worker.d.ts +69 -0
  75. package/lib/workers/db-worker.d.ts.map +1 -0
  76. package/lib/workers/db-worker.js +13 -3636
  77. package/lib/workers/events.d.ts +11 -0
  78. package/lib/workers/events.d.ts.map +1 -0
  79. package/lib/workers/flexbuilder.d.ts +4 -0
  80. package/lib/workers/flexbuilder.d.ts.map +1 -0
  81. package/lib/workers/init.d.ts +11 -0
  82. package/lib/workers/init.d.ts.map +1 -0
  83. package/lib/workers/main.d.ts +105 -0
  84. package/lib/workers/main.d.ts.map +1 -0
  85. package/lib/workers/supabase-db-worker.d.ts +2 -0
  86. package/lib/workers/supabase-db-worker.d.ts.map +1 -0
  87. package/lib/workers/supabase-db-worker.js +23 -0
  88. package/lib/workers/supabase-shared-worker.d.ts +2 -0
  89. package/lib/workers/supabase-shared-worker.d.ts.map +1 -0
  90. package/lib/workers/supabase-shared-worker.js +25 -0
  91. package/lib/workers/tools.d.ts +4 -0
  92. package/lib/workers/tools.d.ts.map +1 -0
  93. package/lib/workers/validate.d.ts +15 -0
  94. package/lib/workers/validate.d.ts.map +1 -0
  95. package/lib/workers/ws-worker.d.ts +10 -0
  96. package/lib/workers/ws-worker.d.ts.map +1 -0
  97. package/lib/workers/ws-worker.js +5 -273
  98. package/main.js +5711 -0
  99. package/package.json +18 -6
  100. package/src/index.ts +41 -13
  101. package/src/lib/agents/api-types.ts +170 -0
  102. package/src/lib/agents/auth/init.ts +200 -0
  103. package/src/lib/agents/cache.ts +127 -0
  104. package/src/lib/agents/client/callGuildVault.ts +71 -0
  105. package/src/lib/agents/client/callJson.ts +64 -0
  106. package/src/lib/agents/config.ts +83 -0
  107. package/src/lib/agents/constants.ts +11 -0
  108. package/src/lib/agents/createAgents.ts +62 -0
  109. package/src/lib/agents/ctx.ts +63 -0
  110. package/src/lib/agents/discord/bot.ts +169 -0
  111. package/src/lib/agents/discord/guilds.ts +106 -0
  112. package/src/lib/agents/formDrafts.ts +55 -0
  113. package/src/lib/agents/generated/agents-schema.ts +155 -0
  114. package/src/lib/agents/generated/discordsh-agents-schema.ts +96 -0
  115. package/src/lib/agents/github/backfill.ts +126 -0
  116. package/src/lib/agents/github/events.ts +131 -0
  117. package/src/lib/agents/github/pat.ts +150 -0
  118. package/src/lib/agents/github/repoConfig.ts +240 -0
  119. package/src/lib/agents/github/tokens.ts +222 -0
  120. package/src/lib/agents/github/webhook.ts +349 -0
  121. package/src/lib/agents/index.ts +35 -0
  122. package/src/lib/agents/state/atoms.ts +122 -0
  123. package/src/lib/agents/types.ts +83 -0
  124. package/src/lib/api.ts +71 -0
  125. package/src/lib/droid.spec.ts +14 -19
  126. package/src/lib/droid.ts +48 -4
  127. package/src/lib/gateway/SupabaseGateway.ts +236 -0
  128. package/src/lib/gateway/WorkerCommunication.ts +132 -0
  129. package/src/lib/gateway/WorkerPool.ts +304 -0
  130. package/src/lib/gateway/capabilities.spec.ts +144 -0
  131. package/src/lib/gateway/capabilities.ts +80 -0
  132. package/src/lib/gateway/index.ts +15 -0
  133. package/src/lib/gateway/strategies/DirectStrategy.ts +443 -0
  134. package/src/lib/gateway/strategies/SharedWorkerStrategy.ts +278 -0
  135. package/src/lib/gateway/strategies/WebWorkerStrategy.ts +278 -0
  136. package/src/lib/gateway/types.ts +139 -0
  137. package/src/lib/mod/mod-manager.ts +13 -8
  138. package/src/lib/mod/module/bento/mod-bento.worker.ts +1 -1
  139. package/src/lib/mod/module/supabase/mod-supabase.spec.ts +21 -25
  140. package/src/lib/mod/module/supabase/mod-supabase.ts +73 -0
  141. package/src/lib/mod/module/supabase/mod-supabase.worker.ts +1 -56
  142. package/src/lib/state/auth.spec.ts +58 -0
  143. package/src/lib/state/auth.ts +148 -0
  144. package/src/lib/state/bento.spec.ts +76 -0
  145. package/src/lib/state/bento.ts +51 -0
  146. package/src/lib/state/index.ts +98 -0
  147. package/src/lib/state/overlay-manager.ts +125 -0
  148. package/src/lib/state/profile-sync.ts +214 -0
  149. package/src/lib/state/profile.ts +155 -0
  150. package/src/lib/state/router.ts +24 -0
  151. package/src/lib/state/staff.ts +128 -0
  152. package/src/lib/state/sync-bus.ts +109 -0
  153. package/src/lib/state/theme-sync.ts +75 -0
  154. package/src/lib/state/toasts.spec.ts +106 -0
  155. package/src/lib/state/toasts.ts +34 -0
  156. package/src/lib/state/ui.spec.ts +148 -0
  157. package/src/lib/state/ui.ts +45 -0
  158. package/src/lib/state/welcome-toast.spec.ts +101 -0
  159. package/src/lib/state/welcome-toast.ts +76 -0
  160. package/src/lib/sw-recovery.spec.ts +165 -0
  161. package/src/lib/sw-recovery.ts +141 -0
  162. package/src/lib/types/bento.spec.ts +116 -0
  163. package/src/lib/types/bento.ts +94 -55
  164. package/src/lib/types/event-schemas.spec.ts +120 -0
  165. package/src/lib/types/event-types.spec.ts +96 -0
  166. package/src/lib/types/event-types.ts +113 -3
  167. package/src/lib/types/modules.ts +12 -5
  168. package/src/lib/types/ui-event-types.ts +66 -0
  169. package/src/lib/workers/canvas-ui-renderer.ts +266 -0
  170. package/src/lib/workers/canvas-worker.ts +94 -9
  171. package/src/lib/workers/data.ts +64 -37
  172. package/src/lib/workers/db-worker.ts +105 -32
  173. package/src/lib/workers/events.spec.ts +208 -0
  174. package/src/lib/workers/events.ts +27 -14
  175. package/src/lib/workers/flexbuilder.ts +2 -2
  176. package/src/lib/workers/init.ts +25 -17
  177. package/src/lib/workers/main.ts +506 -327
  178. package/src/lib/workers/supabase-db-worker.ts +243 -0
  179. package/src/lib/workers/supabase-shared-worker.ts +607 -0
  180. package/src/lib/workers/validate.ts +64 -0
  181. package/src/lib/workers/ws-worker.spec.ts +41 -0
  182. package/src/lib/workers/ws-worker.ts +255 -31
  183. package/src/setup-vitest.ts +4 -2
  184. package/src/types.ts +30 -0
  185. package/src/vite-env.d.ts +1 -0
  186. package/workers/main.js +2 -1125
  187. package/comlink.js +0 -253
  188. package/index.js +0 -2972
  189. package/reference.js +0 -259
  190. package/src/types.d.ts +0 -31
@@ -0,0 +1,155 @@
1
+ /**
2
+ * AUTO-GENERATED by proto-to-zod — DO NOT EDIT
3
+ *
4
+ * Source: ../descriptors/agents.binpb
5
+ * Config: ../agents-zod-config.json
6
+ * Generated: 2026-07-20T00:37:21.617Z
7
+ */
8
+
9
+ import { z } from 'zod';
10
+
11
+ export const TokenServices = [
12
+ 'github_webhook',
13
+ 'github',
14
+ 'github_repos',
15
+ 'discordsh_config',
16
+ ] as const;
17
+
18
+ export type TokenServiceValue = (typeof TokenServices)[number];
19
+
20
+ export const TokenServiceSchema = z.enum(TokenServices);
21
+
22
+ // PeekTokenResponse
23
+ export const PeekTokenResponseSchema = z.object({
24
+ value: z.string().optional(),
25
+ });
26
+
27
+ export type PeekTokenResponse = z.infer<typeof PeekTokenResponseSchema>;
28
+
29
+ // PeekTokenRequest
30
+ export const PeekTokenRequestSchema = z.object({
31
+ server_id: z
32
+ .string()
33
+ .regex(
34
+ /^\d{17,20}$/,
35
+ 'Must be a valid Discord snowflake (17-20 digits)',
36
+ ),
37
+ service: z
38
+ .string()
39
+ .regex(
40
+ /^[a-z0-9_]{2,32}$/,
41
+ 'Service must be 2-32 chars: lowercase a-z, 0-9, underscore',
42
+ ),
43
+ });
44
+
45
+ export type PeekTokenRequest = z.infer<typeof PeekTokenRequestSchema>;
46
+
47
+ // ToggleTokenRequest
48
+ export const ToggleTokenRequestSchema = z.object({
49
+ server_id: z
50
+ .string()
51
+ .regex(
52
+ /^\d{17,20}$/,
53
+ 'Must be a valid Discord snowflake (17-20 digits)',
54
+ ),
55
+ token_id: z
56
+ .string()
57
+ .regex(
58
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
59
+ 'token_id must be a UUID',
60
+ ),
61
+ is_active: z.boolean(),
62
+ });
63
+
64
+ export type ToggleTokenRequest = z.infer<typeof ToggleTokenRequestSchema>;
65
+
66
+ // DeleteTokenRequest
67
+ export const DeleteTokenRequestSchema = z.object({
68
+ server_id: z
69
+ .string()
70
+ .regex(
71
+ /^\d{17,20}$/,
72
+ 'Must be a valid Discord snowflake (17-20 digits)',
73
+ ),
74
+ token_id: z
75
+ .string()
76
+ .regex(
77
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
78
+ 'token_id must be a UUID',
79
+ ),
80
+ });
81
+
82
+ export type DeleteTokenRequest = z.infer<typeof DeleteTokenRequestSchema>;
83
+
84
+ // AgentTokenRow
85
+ export const AgentTokenRowSchema = z.object({
86
+ token_id: z.string(),
87
+ token_name: z.string(),
88
+ service: z.string(),
89
+ description: z.string().nullish(),
90
+ is_active: z.boolean(),
91
+ created_at: z.string(),
92
+ updated_at: z.string().nullish(),
93
+ });
94
+
95
+ export type AgentTokenRow = z.infer<typeof AgentTokenRowSchema>;
96
+
97
+ // ListTokensResponse
98
+ export const ListTokensResponseSchema = z.object({
99
+ tokens: z.array(AgentTokenRowSchema).optional(),
100
+ });
101
+
102
+ export type ListTokensResponse = z.infer<typeof ListTokensResponseSchema>;
103
+
104
+ // ListTokensRequest
105
+ export const ListTokensRequestSchema = z.object({
106
+ server_id: z
107
+ .string()
108
+ .regex(
109
+ /^\d{17,20}$/,
110
+ 'Must be a valid Discord snowflake (17-20 digits)',
111
+ ),
112
+ });
113
+
114
+ export type ListTokensRequest = z.infer<typeof ListTokensRequestSchema>;
115
+
116
+ // SetTokenRequest
117
+ export const SetTokenRequestSchema = z.object({
118
+ server_id: z
119
+ .string()
120
+ .regex(
121
+ /^\d{17,20}$/,
122
+ 'Must be a valid Discord snowflake (17-20 digits)',
123
+ ),
124
+ service: z
125
+ .string()
126
+ .regex(
127
+ /^[a-z0-9_]{2,32}$/,
128
+ 'Service must be 2-32 chars: lowercase a-z, 0-9, underscore',
129
+ ),
130
+ token_name: z
131
+ .string()
132
+ .regex(
133
+ /^[a-z0-9_-]{3,64}$/,
134
+ 'Token name must be 3-64 lowercase chars: a-z, 0-9, underscore, dash',
135
+ ),
136
+ token_value: z
137
+ .string()
138
+ .min(10, 'Token value must be 10-8000 characters')
139
+ .max(8000, 'Token value must be 10-8000 characters'),
140
+ description: z.string().optional(),
141
+ });
142
+
143
+ export type SetTokenRequest = z.infer<typeof SetTokenRequestSchema>;
144
+
145
+ // AgentError
146
+ export const AgentErrorSchema = z.object({
147
+ success: z.boolean(),
148
+ message: z.string(),
149
+ code: z.number(),
150
+ sqlstate: z.string().optional(),
151
+ hint: z.string().optional(),
152
+ context: z.string().optional(),
153
+ });
154
+
155
+ export type AgentError = z.infer<typeof AgentErrorSchema>;
@@ -0,0 +1,96 @@
1
+ /**
2
+ * AUTO-GENERATED by proto-to-zod — DO NOT EDIT
3
+ *
4
+ * Source: ../descriptors/discordsh_agents.binpb
5
+ * Config: ../discordsh_agents-zod-config.json
6
+ * Generated: 2026-07-20T00:37:21.620Z
7
+ */
8
+
9
+ import { z } from 'zod';
10
+
11
+ // PendingEvent
12
+ export const PendingEventSchema = z.object({
13
+ id: z.number(),
14
+ owner: z.string(),
15
+ repo: z.string(),
16
+ number: z.number(),
17
+ event_type: z.string(),
18
+ actor: z.string().nullish(),
19
+ delivery_state: z.number(),
20
+ delivery_attempts: z.number(),
21
+ created_at: z.string(),
22
+ claimed_at: z.string().nullish(),
23
+ });
24
+
25
+ export type PendingEvent = z.infer<typeof PendingEventSchema>;
26
+
27
+ // FailedEvent
28
+ export const FailedEventSchema = z.object({
29
+ id: z.number(),
30
+ owner: z.string(),
31
+ repo: z.string(),
32
+ number: z.number(),
33
+ event_type: z.string(),
34
+ actor: z.string().nullish(),
35
+ delivery_attempts: z.number(),
36
+ last_error: z.string().nullish(),
37
+ created_at: z.string(),
38
+ updated_at: z.string(),
39
+ });
40
+
41
+ export type FailedEvent = z.infer<typeof FailedEventSchema>;
42
+
43
+ // EventQueueStats
44
+ export const EventQueueStatsSchema = z.object({
45
+ last_delivered_at: z.string().nullish(),
46
+ last_recorded_at: z.string().nullish(),
47
+ pending_count: z.number(),
48
+ in_flight_count: z.number(),
49
+ delivered_count: z.number(),
50
+ failed_count: z.number(),
51
+ oldest_pending_at: z.string().nullish(),
52
+ });
53
+
54
+ export type EventQueueStats = z.infer<typeof EventQueueStatsSchema>;
55
+
56
+ // WebhookDelivery
57
+ export const WebhookDeliverySchema = z.object({
58
+ id: z.number(),
59
+ guid: z.string(),
60
+ delivered_at: z.string(),
61
+ redelivery: z.boolean(),
62
+ duration: z.number(),
63
+ status: z.string(),
64
+ status_code: z.number(),
65
+ event: z.string(),
66
+ action: z.string().nullish(),
67
+ });
68
+
69
+ export type WebhookDelivery = z.infer<typeof WebhookDeliverySchema>;
70
+
71
+ // GithubRepoHook
72
+ export const GithubRepoHookSchema = z.object({
73
+ id: z.number(),
74
+ name: z.string(),
75
+ active: z.boolean(),
76
+ events: z.array(z.string()),
77
+ url: z.string().nullish(),
78
+ is_kbve: z.boolean(),
79
+ updated_at: z.string(),
80
+ });
81
+
82
+ export type GithubRepoHook = z.infer<typeof GithubRepoHookSchema>;
83
+
84
+ // DiscordshConfig
85
+ export const DiscordshConfigSchema = z.object({
86
+ default_repo: z.string().optional(),
87
+ claim_channel_id: z.string().optional(),
88
+ forum_channel_id: z.string().optional(),
89
+ noticeboard_channel_id: z.string().optional(),
90
+ taskboard_channel_id: z.string().optional(),
91
+ max_assignees: z.number().optional(),
92
+ mirror_pr_events: z.boolean().optional(),
93
+ active: z.boolean().optional(),
94
+ });
95
+
96
+ export type DiscordshConfig = z.infer<typeof DiscordshConfigSchema>;
@@ -0,0 +1,126 @@
1
+ import type { AgentsCtx } from '../ctx';
2
+ import type { BackfillResult } from '../types';
3
+
4
+ type Fail = { ok: false; error: string };
5
+
6
+ export function makeBackfill(ctx: AgentsCtx) {
7
+ const { store, endpoints } = ctx;
8
+
9
+ function patchBackfillDraft(
10
+ guildId: string,
11
+ partial: Partial<{ owner: string; repo: string }>,
12
+ ): void {
13
+ const drafts = store.$backfillDrafts.get();
14
+ const cur = drafts[guildId] ?? { owner: '', repo: '' };
15
+ store.$backfillDrafts.set({
16
+ ...drafts,
17
+ [guildId]: { ...cur, ...partial },
18
+ });
19
+ }
20
+
21
+ function clearBackfillResult(guildId: string): void {
22
+ const m = { ...store.$backfillResults.get() };
23
+ delete m[guildId];
24
+ store.$backfillResults.set(m);
25
+ }
26
+
27
+ async function runBackfill(input: {
28
+ owner: string;
29
+ repo: string;
30
+ state?: 'open' | 'closed' | 'all';
31
+ maxPages?: number;
32
+ perPage?: number;
33
+ }): Promise<BackfillResult | Fail> {
34
+ const accessToken = store.$accessToken.get();
35
+ const guildId = store.$selectedGuildId.get();
36
+ if (!accessToken || !guildId) {
37
+ return { ok: false, error: 'No guild selected or session missing' };
38
+ }
39
+
40
+ try {
41
+ const resp = await fetch(endpoints.ghBackfill, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ Authorization: `Bearer ${accessToken}`,
46
+ },
47
+ body: JSON.stringify({
48
+ owner: input.owner,
49
+ repo: input.repo,
50
+ guild_id: guildId,
51
+ state: input.state ?? 'open',
52
+ max_pages: input.maxPages ?? 1,
53
+ per_page: input.perPage ?? 30,
54
+ }),
55
+ });
56
+ const text = await resp.text();
57
+ let body: unknown;
58
+ try {
59
+ body = JSON.parse(text);
60
+ } catch {
61
+ return {
62
+ ok: false,
63
+ error: `HTTP ${resp.status}: ${text.slice(0, 200)}`,
64
+ };
65
+ }
66
+ if (!resp.ok) {
67
+ const errMsg =
68
+ (body as { error?: string } | null)?.error ??
69
+ `HTTP ${resp.status}`;
70
+ return { ok: false, error: errMsg };
71
+ }
72
+ const b = body as {
73
+ upserted?: number;
74
+ pages_walked?: number;
75
+ rate_limit_remaining?: number | null;
76
+ };
77
+ return {
78
+ ok: true,
79
+ upserted: b.upserted ?? 0,
80
+ pages: b.pages_walked ?? 0,
81
+ rateLimitRemaining: b.rate_limit_remaining ?? null,
82
+ };
83
+ } catch (e) {
84
+ return {
85
+ ok: false,
86
+ error: e instanceof Error ? e.message : 'Network error',
87
+ };
88
+ }
89
+ }
90
+
91
+ async function runBackfillForGuild(guildId: string): Promise<void> {
92
+ const draft = store.$backfillDrafts.get()[guildId] ?? {
93
+ owner: '',
94
+ repo: '',
95
+ };
96
+ if (!draft.owner || !draft.repo) return;
97
+ store.$backfillBusyFor.set({
98
+ ...store.$backfillBusyFor.get(),
99
+ [guildId]: true,
100
+ });
101
+ const resultsClear = { ...store.$backfillResults.get() };
102
+ delete resultsClear[guildId];
103
+ store.$backfillResults.set(resultsClear);
104
+ const r = await runBackfill({
105
+ owner: draft.owner,
106
+ repo: draft.repo,
107
+ state: 'open',
108
+ maxPages: 1,
109
+ perPage: 30,
110
+ });
111
+ const busyDone = { ...store.$backfillBusyFor.get() };
112
+ delete busyDone[guildId];
113
+ store.$backfillBusyFor.set(busyDone);
114
+ store.$backfillResults.set({
115
+ ...store.$backfillResults.get(),
116
+ [guildId]: r,
117
+ });
118
+ }
119
+
120
+ return {
121
+ patchBackfillDraft,
122
+ clearBackfillResult,
123
+ runBackfill,
124
+ runBackfillForGuild,
125
+ };
126
+ }
@@ -0,0 +1,131 @@
1
+ import type { AgentsCtx } from '../ctx';
2
+ import type { AgentsApi } from '../api-types';
3
+ import {
4
+ EventQueueStatsSchema,
5
+ FailedEventSchema,
6
+ PendingEventSchema,
7
+ } from '../generated/discordsh-agents-schema';
8
+ import type {
9
+ EventQueueStats,
10
+ FailedEvent,
11
+ PendingEvent,
12
+ Result,
13
+ } from '../types';
14
+
15
+ export function makeEvents(ctx: AgentsCtx, api: AgentsApi) {
16
+ const { store, endpoints } = ctx;
17
+
18
+ async function loadEventStats(guildId: string): Promise<void> {
19
+ store.$eventStatsLoading.set({
20
+ ...store.$eventStatsLoading.get(),
21
+ [guildId]: true,
22
+ });
23
+ const r = await ctx.callJson<EventQueueStats>(endpoints.ghAdmin, {
24
+ command: 'events.stats',
25
+ server_id: guildId,
26
+ });
27
+ const loading = { ...store.$eventStatsLoading.get() };
28
+ delete loading[guildId];
29
+ store.$eventStatsLoading.set(loading);
30
+ if (!r.ok) return;
31
+ const parsed = EventQueueStatsSchema.safeParse(r.data);
32
+ if (!parsed.success) return;
33
+ store.$eventStats.set({
34
+ ...store.$eventStats.get(),
35
+ [guildId]: parsed.data,
36
+ });
37
+ }
38
+
39
+ async function loadFailedEvents(
40
+ guildId: string,
41
+ limit = 10,
42
+ ): Promise<void> {
43
+ store.$failedEventsLoading.set({
44
+ ...store.$failedEventsLoading.get(),
45
+ [guildId]: true,
46
+ });
47
+ const r = await ctx.callJson<{ events: FailedEvent[] }>(
48
+ endpoints.ghAdmin,
49
+ { command: 'events.failed', server_id: guildId, limit },
50
+ );
51
+ const loading = { ...store.$failedEventsLoading.get() };
52
+ delete loading[guildId];
53
+ store.$failedEventsLoading.set(loading);
54
+ if (!r.ok) return;
55
+ const parsed = FailedEventSchema.array().safeParse(r.data.events ?? []);
56
+ if (!parsed.success) return;
57
+ store.$failedEvents.set({
58
+ ...store.$failedEvents.get(),
59
+ [guildId]: parsed.data,
60
+ });
61
+ }
62
+
63
+ async function loadPendingEvents(
64
+ guildId: string,
65
+ limit = 10,
66
+ ): Promise<void> {
67
+ store.$pendingEventsLoading.set({
68
+ ...store.$pendingEventsLoading.get(),
69
+ [guildId]: true,
70
+ });
71
+ const r = await ctx.callJson<{ events: PendingEvent[] }>(
72
+ endpoints.ghAdmin,
73
+ { command: 'events.pending', server_id: guildId, limit },
74
+ );
75
+ const loading = { ...store.$pendingEventsLoading.get() };
76
+ delete loading[guildId];
77
+ store.$pendingEventsLoading.set(loading);
78
+ if (!r.ok) return;
79
+ const parsed = PendingEventSchema.array().safeParse(
80
+ r.data.events ?? [],
81
+ );
82
+ if (!parsed.success) return;
83
+ store.$pendingEvents.set({
84
+ ...store.$pendingEvents.get(),
85
+ [guildId]: parsed.data,
86
+ });
87
+ }
88
+
89
+ async function requeueEvent(
90
+ guildId: string,
91
+ eventId: number,
92
+ reason?: string,
93
+ ): Promise<Result> {
94
+ const key = `${guildId}:${eventId}`;
95
+ store.$eventRequeueBusyFor.set({
96
+ ...store.$eventRequeueBusyFor.get(),
97
+ [key]: true,
98
+ });
99
+ const trimmedReason =
100
+ typeof reason === 'string' && reason.trim().length > 0
101
+ ? reason.trim().slice(0, 512)
102
+ : undefined;
103
+ const r = await ctx.callJson<{ requeued: boolean; event_id: number }>(
104
+ endpoints.ghAdmin,
105
+ {
106
+ command: 'events.requeue',
107
+ server_id: guildId,
108
+ event_id: eventId,
109
+ ...(trimmedReason ? { reason: trimmedReason } : {}),
110
+ },
111
+ );
112
+ const busy = { ...store.$eventRequeueBusyFor.get() };
113
+ delete busy[key];
114
+ store.$eventRequeueBusyFor.set(busy);
115
+ if (!r.ok) return { ok: false, error: r.error };
116
+ const cur = store.$failedEvents.get()[guildId] ?? [];
117
+ store.$failedEvents.set({
118
+ ...store.$failedEvents.get(),
119
+ [guildId]: cur.filter((e) => e.id !== eventId),
120
+ });
121
+ void api.loadEventStats(guildId);
122
+ return { ok: true };
123
+ }
124
+
125
+ return {
126
+ loadEventStats,
127
+ loadFailedEvents,
128
+ loadPendingEvents,
129
+ requeueEvent,
130
+ };
131
+ }
@@ -0,0 +1,150 @@
1
+ import type { AgentsCtx } from '../ctx';
2
+ import type { AgentsApi } from '../api-types';
3
+ import type { PatValidation, Result } from '../types';
4
+
5
+ export function makePat(ctx: AgentsCtx, api: AgentsApi) {
6
+ const { store } = ctx;
7
+
8
+ function setPatDraft(guildId: string, pat: string): void {
9
+ store.$patDrafts.set({ ...store.$patDrafts.get(), [guildId]: pat });
10
+ const validated = { ...store.$patValidatedFor.get() };
11
+ if (validated[guildId]) {
12
+ delete validated[guildId];
13
+ store.$patValidatedFor.set(validated);
14
+ }
15
+ }
16
+
17
+ function clearPatState(guildId: string): void {
18
+ const drafts = { ...store.$patDrafts.get() };
19
+ delete drafts[guildId];
20
+ store.$patDrafts.set(drafts);
21
+ const validated = { ...store.$patValidatedFor.get() };
22
+ delete validated[guildId];
23
+ store.$patValidatedFor.set(validated);
24
+ const errs = { ...store.$patErrors.get() };
25
+ delete errs[guildId];
26
+ store.$patErrors.set(errs);
27
+ }
28
+
29
+ async function validateGithubPat(
30
+ pat: string,
31
+ ): Promise<Result<PatValidation>> {
32
+ try {
33
+ const resp = await fetch('https://api.github.com/user', {
34
+ headers: {
35
+ Authorization: `Bearer ${pat}`,
36
+ Accept: 'application/vnd.github+json',
37
+ 'X-GitHub-Api-Version': '2022-11-28',
38
+ },
39
+ });
40
+ if (resp.status === 401) {
41
+ return { ok: false, error: 'GitHub rejected the token (401).' };
42
+ }
43
+ if (!resp.ok) {
44
+ return {
45
+ ok: false,
46
+ error: `GitHub /user returned ${resp.status}.`,
47
+ };
48
+ }
49
+ const user = (await resp.json()) as { login?: string };
50
+ if (!user.login) {
51
+ return { ok: false, error: 'GitHub response missing login.' };
52
+ }
53
+ const scopesHeader = resp.headers.get('x-oauth-scopes') ?? '';
54
+ const tokenTypeHeader =
55
+ resp.headers.get('x-github-authentication-token-type') ??
56
+ resp.headers.get('x-github-token-type') ??
57
+ 'unknown';
58
+ const scopes = scopesHeader
59
+ .split(',')
60
+ .map((s) => s.trim())
61
+ .filter(Boolean);
62
+ return {
63
+ ok: true,
64
+ login: user.login,
65
+ scopes,
66
+ tokenType: tokenTypeHeader,
67
+ };
68
+ } catch (e) {
69
+ return {
70
+ ok: false,
71
+ error: e instanceof Error ? e.message : 'Network error',
72
+ };
73
+ }
74
+ }
75
+
76
+ async function validatePatForGuild(guildId: string): Promise<Result> {
77
+ const pat = store.$patDrafts.get()[guildId] ?? '';
78
+ if (!pat) return { ok: false, error: 'No PAT entered' };
79
+ store.$patValidatingFor.set({
80
+ ...store.$patValidatingFor.get(),
81
+ [guildId]: true,
82
+ });
83
+ store.$patErrors.set({ ...store.$patErrors.get(), [guildId]: null });
84
+ const validatedClear = { ...store.$patValidatedFor.get() };
85
+ delete validatedClear[guildId];
86
+ store.$patValidatedFor.set(validatedClear);
87
+ const r = await validateGithubPat(pat);
88
+ const validatingDone = { ...store.$patValidatingFor.get() };
89
+ delete validatingDone[guildId];
90
+ store.$patValidatingFor.set(validatingDone);
91
+ if (!r.ok) {
92
+ store.$patErrors.set({
93
+ ...store.$patErrors.get(),
94
+ [guildId]: r.error,
95
+ });
96
+ return r;
97
+ }
98
+ store.$patValidatedFor.set({
99
+ ...store.$patValidatedFor.get(),
100
+ [guildId]: {
101
+ login: r.login,
102
+ scopes: r.scopes,
103
+ tokenType: r.tokenType,
104
+ },
105
+ });
106
+ return { ok: true };
107
+ }
108
+
109
+ async function savePatForGuild(
110
+ guildId: string,
111
+ tokenName: string,
112
+ ): Promise<Result<{ tokenId: string }>> {
113
+ const pat = store.$patDrafts.get()[guildId] ?? '';
114
+ if (!pat) return { ok: false, error: 'No PAT entered' };
115
+ const validated = store.$patValidatedFor.get()[guildId] ?? null;
116
+ store.$patSavingFor.set({
117
+ ...store.$patSavingFor.get(),
118
+ [guildId]: true,
119
+ });
120
+ store.$patErrors.set({ ...store.$patErrors.get(), [guildId]: null });
121
+ const r = await api.addToken({
122
+ tokenName,
123
+ service: 'github',
124
+ tokenValue: pat,
125
+ description: validated
126
+ ? `GitHub PAT for ${validated.login}`
127
+ : 'GitHub PAT',
128
+ });
129
+ const savingDone = { ...store.$patSavingFor.get() };
130
+ delete savingDone[guildId];
131
+ store.$patSavingFor.set(savingDone);
132
+ if (r.ok) {
133
+ clearPatState(guildId);
134
+ } else {
135
+ store.$patErrors.set({
136
+ ...store.$patErrors.get(),
137
+ [guildId]: r.error,
138
+ });
139
+ }
140
+ return r;
141
+ }
142
+
143
+ return {
144
+ setPatDraft,
145
+ clearPatState,
146
+ validateGithubPat,
147
+ validatePatForGuild,
148
+ savePatForGuild,
149
+ };
150
+ }