@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,240 @@
1
+ import {
2
+ botConfigFromFormDraft,
3
+ botConfigToFormDraft,
4
+ emptyBotConfigFormDraft,
5
+ } from '../formDrafts';
6
+ import type { AgentsCtx } from '../ctx';
7
+ import type { AgentsApi } from '../api-types';
8
+ import type { BotConfigFormDraft, DiscordshConfig, Result } from '../types';
9
+
10
+ export function makeRepoConfig(ctx: AgentsCtx, api: AgentsApi) {
11
+ const { store } = ctx;
12
+
13
+ async function getRepoAllowlist(): Promise<
14
+ Result<{ repos: string[]; raw: string | null }>
15
+ > {
16
+ const r = await api.peekToken('github_repos');
17
+ if (!r.ok) return { ok: false, error: r.error };
18
+ if (!r.value) return { ok: true, repos: [], raw: null };
19
+ try {
20
+ const parsed = JSON.parse(r.value) as { repos?: unknown };
21
+ const repos = Array.isArray(parsed.repos)
22
+ ? parsed.repos.filter((x): x is string => typeof x === 'string')
23
+ : [];
24
+ return { ok: true, repos, raw: r.value };
25
+ } catch {
26
+ return { ok: true, repos: [], raw: r.value };
27
+ }
28
+ }
29
+
30
+ async function setRepoAllowlist(repos: string[]): Promise<Result> {
31
+ const r = await api.addToken({
32
+ tokenName: 'github-repos',
33
+ service: 'github_repos',
34
+ tokenValue: JSON.stringify({ repos }),
35
+ description:
36
+ 'Per-guild repo allowlist consumed by gh-webhook and gh-backfill',
37
+ });
38
+ if (!r.ok) return { ok: false, error: r.error };
39
+ return { ok: true };
40
+ }
41
+
42
+ function hydrateRepoAllowlistDraft(guildId: string, repos: string[]): void {
43
+ store.$repoAllowlistDrafts.set({
44
+ ...store.$repoAllowlistDrafts.get(),
45
+ [guildId]: repos,
46
+ });
47
+ store.$repoAllowlistLoadedFor.set({
48
+ ...store.$repoAllowlistLoadedFor.get(),
49
+ [guildId]: true,
50
+ });
51
+ }
52
+
53
+ function patchRepoAllowlistDraft(guildId: string, repos: string[]): void {
54
+ store.$repoAllowlistDrafts.set({
55
+ ...store.$repoAllowlistDrafts.get(),
56
+ [guildId]: repos,
57
+ });
58
+ }
59
+
60
+ function clearRepoAllowlistDraft(guildId: string): void {
61
+ const drafts = { ...store.$repoAllowlistDrafts.get() };
62
+ delete drafts[guildId];
63
+ store.$repoAllowlistDrafts.set(drafts);
64
+ const loaded = { ...store.$repoAllowlistLoadedFor.get() };
65
+ delete loaded[guildId];
66
+ store.$repoAllowlistLoadedFor.set(loaded);
67
+ }
68
+
69
+ async function ensureRepoAllowlistLoaded(
70
+ guildId: string,
71
+ force = false,
72
+ ): Promise<Result> {
73
+ if (!force && store.$repoAllowlistLoadedFor.get()[guildId]) {
74
+ return { ok: true };
75
+ }
76
+ const r = await getRepoAllowlist();
77
+ if (!r.ok) {
78
+ store.$repoAllowlistErrors.set({
79
+ ...store.$repoAllowlistErrors.get(),
80
+ [guildId]: r.error,
81
+ });
82
+ return r;
83
+ }
84
+ hydrateRepoAllowlistDraft(guildId, r.repos);
85
+ store.$repoAllowlistErrors.set({
86
+ ...store.$repoAllowlistErrors.get(),
87
+ [guildId]: null,
88
+ });
89
+ return { ok: true };
90
+ }
91
+
92
+ async function saveRepoAllowlistDraft(guildId: string): Promise<Result> {
93
+ const repos = store.$repoAllowlistDrafts.get()[guildId];
94
+ if (!repos) return { ok: false, error: 'No draft loaded yet' };
95
+ store.$repoAllowlistSavingFor.set({
96
+ ...store.$repoAllowlistSavingFor.get(),
97
+ [guildId]: true,
98
+ });
99
+ store.$repoAllowlistErrors.set({
100
+ ...store.$repoAllowlistErrors.get(),
101
+ [guildId]: null,
102
+ });
103
+ const r = await setRepoAllowlist(repos);
104
+ const savingDone = { ...store.$repoAllowlistSavingFor.get() };
105
+ delete savingDone[guildId];
106
+ store.$repoAllowlistSavingFor.set(savingDone);
107
+ if (!r.ok) {
108
+ store.$repoAllowlistErrors.set({
109
+ ...store.$repoAllowlistErrors.get(),
110
+ [guildId]: r.error,
111
+ });
112
+ }
113
+ return r;
114
+ }
115
+
116
+ async function getBotConfig(): Promise<
117
+ Result<{ config: DiscordshConfig }>
118
+ > {
119
+ const r = await api.peekToken('discordsh_config');
120
+ if (!r.ok) return { ok: false, error: r.error };
121
+ if (!r.value) return { ok: true, config: {} };
122
+ try {
123
+ const parsed = JSON.parse(r.value) as Record<string, unknown>;
124
+ return { ok: true, config: parsed as DiscordshConfig };
125
+ } catch {
126
+ return { ok: true, config: {} };
127
+ }
128
+ }
129
+
130
+ async function setBotConfig(config: DiscordshConfig): Promise<Result> {
131
+ const r = await api.addToken({
132
+ tokenName: 'discordsh-config',
133
+ service: 'discordsh_config',
134
+ tokenValue: JSON.stringify(config),
135
+ description:
136
+ 'Per-guild DiscordSH bot config (channels, defaults, toggles)',
137
+ });
138
+ if (!r.ok) return { ok: false, error: r.error };
139
+ return { ok: true };
140
+ }
141
+
142
+ function hydrateBotConfigDraft(
143
+ guildId: string,
144
+ cfg: DiscordshConfig,
145
+ ): void {
146
+ store.$botConfigDrafts.set({
147
+ ...store.$botConfigDrafts.get(),
148
+ [guildId]: botConfigToFormDraft(cfg),
149
+ });
150
+ store.$botConfigLoadedFor.set({
151
+ ...store.$botConfigLoadedFor.get(),
152
+ [guildId]: true,
153
+ });
154
+ }
155
+
156
+ function patchBotConfigDraft(
157
+ guildId: string,
158
+ patch: Partial<BotConfigFormDraft>,
159
+ ): void {
160
+ const drafts = store.$botConfigDrafts.get();
161
+ const cur = drafts[guildId] ?? emptyBotConfigFormDraft();
162
+ store.$botConfigDrafts.set({
163
+ ...drafts,
164
+ [guildId]: { ...cur, ...patch },
165
+ });
166
+ }
167
+
168
+ function clearBotConfigDraft(guildId: string): void {
169
+ const drafts = { ...store.$botConfigDrafts.get() };
170
+ delete drafts[guildId];
171
+ store.$botConfigDrafts.set(drafts);
172
+ const loaded = { ...store.$botConfigLoadedFor.get() };
173
+ delete loaded[guildId];
174
+ store.$botConfigLoadedFor.set(loaded);
175
+ }
176
+
177
+ async function ensureBotConfigLoaded(
178
+ guildId: string,
179
+ force = false,
180
+ ): Promise<Result> {
181
+ if (!force && store.$botConfigLoadedFor.get()[guildId]) {
182
+ return { ok: true };
183
+ }
184
+ const r = await getBotConfig();
185
+ if (!r.ok) {
186
+ store.$botConfigErrors.set({
187
+ ...store.$botConfigErrors.get(),
188
+ [guildId]: r.error,
189
+ });
190
+ return r;
191
+ }
192
+ hydrateBotConfigDraft(guildId, r.config);
193
+ store.$botConfigErrors.set({
194
+ ...store.$botConfigErrors.get(),
195
+ [guildId]: null,
196
+ });
197
+ return { ok: true };
198
+ }
199
+
200
+ async function saveBotConfigDraft(guildId: string): Promise<Result> {
201
+ const draft = store.$botConfigDrafts.get()[guildId];
202
+ if (!draft) return { ok: false, error: 'No draft loaded yet' };
203
+ store.$botConfigSavingFor.set({
204
+ ...store.$botConfigSavingFor.get(),
205
+ [guildId]: true,
206
+ });
207
+ store.$botConfigErrors.set({
208
+ ...store.$botConfigErrors.get(),
209
+ [guildId]: null,
210
+ });
211
+ const r = await setBotConfig(botConfigFromFormDraft(draft));
212
+ const savingDone = { ...store.$botConfigSavingFor.get() };
213
+ delete savingDone[guildId];
214
+ store.$botConfigSavingFor.set(savingDone);
215
+ if (!r.ok) {
216
+ store.$botConfigErrors.set({
217
+ ...store.$botConfigErrors.get(),
218
+ [guildId]: r.error,
219
+ });
220
+ }
221
+ return r;
222
+ }
223
+
224
+ return {
225
+ getRepoAllowlist,
226
+ setRepoAllowlist,
227
+ hydrateRepoAllowlistDraft,
228
+ patchRepoAllowlistDraft,
229
+ clearRepoAllowlistDraft,
230
+ ensureRepoAllowlistLoaded,
231
+ saveRepoAllowlistDraft,
232
+ getBotConfig,
233
+ setBotConfig,
234
+ hydrateBotConfigDraft,
235
+ patchBotConfigDraft,
236
+ clearBotConfigDraft,
237
+ ensureBotConfigLoaded,
238
+ saveBotConfigDraft,
239
+ };
240
+ }
@@ -0,0 +1,222 @@
1
+ import { loadCachedTokens, saveCachedTokens } from '../cache';
2
+ import type { AgentsCtx } from '../ctx';
3
+ import type { AgentsApi } from '../api-types';
4
+ import {
5
+ AgentTokenRowSchema,
6
+ type TokenServiceValue,
7
+ } from '../generated/agents-schema';
8
+ import type { AgentTokenRow, Result } from '../types';
9
+
10
+ export function makeTokens(ctx: AgentsCtx, api: AgentsApi) {
11
+ const { store, config, endpoints, runtime } = ctx;
12
+
13
+ async function loadTokens(guildId: string, force = false): Promise<void> {
14
+ const accessToken = store.$accessToken.get();
15
+ if (!accessToken) return;
16
+
17
+ const userId = store.$userId.get();
18
+
19
+ if (!force && userId) {
20
+ const cached = loadCachedTokens(userId, guildId);
21
+ if (cached) {
22
+ store.$tokens.set(cached);
23
+ store.$tokensError.set(null);
24
+ return;
25
+ }
26
+ }
27
+
28
+ const existing = runtime.tokensInflight.get(guildId);
29
+ if (!force && existing) return existing;
30
+
31
+ const prevAbort = runtime.tokensAbort.get(guildId);
32
+ if (prevAbort) prevAbort.abort();
33
+ const abort = new AbortController();
34
+ runtime.tokensAbort.set(guildId, abort);
35
+
36
+ store.$tokensLoading.set(true);
37
+ store.$tokensError.set(null);
38
+
39
+ const promise = (async () => {
40
+ try {
41
+ const resp = await fetch(endpoints.guildVault, {
42
+ method: 'POST',
43
+ headers: {
44
+ 'Content-Type': 'application/json',
45
+ Authorization: `Bearer ${accessToken}`,
46
+ },
47
+ body: JSON.stringify({
48
+ command: 'tokens.list_tokens',
49
+ server_id: guildId,
50
+ }),
51
+ signal: abort.signal,
52
+ });
53
+
54
+ const text = await resp.text();
55
+ let body: unknown;
56
+ try {
57
+ body = JSON.parse(text);
58
+ } catch {
59
+ store.$tokensError.set(
60
+ `HTTP ${resp.status}: ${text.slice(0, 200)}`,
61
+ );
62
+ return;
63
+ }
64
+
65
+ if (!resp.ok) {
66
+ const errMsg =
67
+ (body as { error?: string } | null)?.error ??
68
+ `HTTP ${resp.status}`;
69
+ store.$tokensError.set(errMsg);
70
+ return;
71
+ }
72
+
73
+ const rawRows =
74
+ (body as { tokens?: unknown } | null)?.tokens ?? [];
75
+ const parsed = AgentTokenRowSchema.array().safeParse(rawRows);
76
+ if (!parsed.success) {
77
+ store.$tokensError.set(
78
+ 'Token list failed schema validation',
79
+ );
80
+ return;
81
+ }
82
+ const rows = parsed.data;
83
+ store.$tokens.set(rows);
84
+ if (userId) saveCachedTokens(userId, guildId, rows);
85
+ } catch (e) {
86
+ if (e instanceof DOMException && e.name === 'AbortError') {
87
+ return;
88
+ }
89
+ store.$tokensError.set(
90
+ e instanceof Error ? e.message : 'Failed to load tokens',
91
+ );
92
+ } finally {
93
+ store.$tokensLoading.set(false);
94
+ runtime.tokensInflight.delete(guildId);
95
+ if (runtime.tokensAbort.get(guildId) === abort) {
96
+ runtime.tokensAbort.delete(guildId);
97
+ }
98
+ }
99
+ })();
100
+
101
+ runtime.tokensInflight.set(guildId, promise);
102
+ return promise;
103
+ }
104
+
105
+ async function addToken(input: {
106
+ tokenName: string;
107
+ service: TokenServiceValue;
108
+ tokenValue: string;
109
+ description?: string | null;
110
+ }): Promise<Result<{ tokenId: string }>> {
111
+ const guildId = store.$selectedGuildId.get();
112
+ const accessToken = store.$accessToken.get();
113
+ if (!guildId || !accessToken) {
114
+ return { ok: false, error: 'No guild selected or session missing' };
115
+ }
116
+
117
+ const resp = await ctx.callGuildVault('tokens.set_token', accessToken, {
118
+ server_id: guildId,
119
+ token_name: input.tokenName,
120
+ service: input.service,
121
+ token_value: input.tokenValue,
122
+ description: input.description ?? null,
123
+ });
124
+
125
+ if (!resp.ok) return { ok: false, error: resp.error };
126
+ const tokenId =
127
+ (resp.body as { token_id?: string } | null)?.token_id ?? '';
128
+ await api.refreshSelectedGuild();
129
+ config.notify({
130
+ message: `Token "${input.tokenName}" registered.`,
131
+ severity: 'success',
132
+ duration: 3500,
133
+ });
134
+ return { ok: true, tokenId };
135
+ }
136
+
137
+ async function deleteToken(tokenId: string): Promise<Result> {
138
+ const guildId = store.$selectedGuildId.get();
139
+ const accessToken = store.$accessToken.get();
140
+ if (!guildId || !accessToken) {
141
+ return { ok: false, error: 'No guild selected or session missing' };
142
+ }
143
+
144
+ const resp = await ctx.callGuildVault(
145
+ 'tokens.delete_token',
146
+ accessToken,
147
+ { server_id: guildId, token_id: tokenId },
148
+ );
149
+
150
+ if (!resp.ok) return { ok: false, error: resp.error };
151
+ await api.refreshSelectedGuild();
152
+ config.notify({
153
+ message: 'Token deleted.',
154
+ severity: 'success',
155
+ duration: 3500,
156
+ });
157
+ return { ok: true };
158
+ }
159
+
160
+ async function peekToken(
161
+ service: TokenServiceValue,
162
+ ): Promise<Result<{ value: string | null }>> {
163
+ const guildId = store.$selectedGuildId.get();
164
+ const accessToken = store.$accessToken.get();
165
+ if (!guildId || !accessToken) {
166
+ return { ok: false, error: 'No guild selected or session missing' };
167
+ }
168
+
169
+ const resp = await ctx.callGuildVault(
170
+ 'tokens.peek_token',
171
+ accessToken,
172
+ {
173
+ server_id: guildId,
174
+ service,
175
+ },
176
+ );
177
+
178
+ if (!resp.ok) return { ok: false, error: resp.error };
179
+ const value =
180
+ (resp.body as { value?: string | null } | null)?.value ?? null;
181
+ return { ok: true, value };
182
+ }
183
+
184
+ async function toggleToken(
185
+ tokenId: string,
186
+ isActive: boolean,
187
+ ): Promise<Result> {
188
+ const guildId = store.$selectedGuildId.get();
189
+ const accessToken = store.$accessToken.get();
190
+ if (!guildId || !accessToken) {
191
+ return { ok: false, error: 'No guild selected or session missing' };
192
+ }
193
+
194
+ const resp = await ctx.callGuildVault(
195
+ 'tokens.toggle_token',
196
+ accessToken,
197
+ { server_id: guildId, token_id: tokenId, is_active: isActive },
198
+ );
199
+
200
+ if (!resp.ok) return { ok: false, error: resp.error };
201
+ await api.refreshSelectedGuild();
202
+ return { ok: true };
203
+ }
204
+
205
+ function hasService(service: TokenServiceValue): AgentTokenRow | null {
206
+ const tokens = store.$tokens.get();
207
+ return (
208
+ tokens.find((t) => t.service === service && t.is_active) ??
209
+ tokens.find((t) => t.service === service) ??
210
+ null
211
+ );
212
+ }
213
+
214
+ return {
215
+ loadTokens,
216
+ addToken,
217
+ deleteToken,
218
+ peekToken,
219
+ toggleToken,
220
+ hasService,
221
+ };
222
+ }