@kbve/droid 0.1.1 → 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.
- package/droid.mjs +2311 -203
- package/index.d.ts +3 -0
- package/lib/agents/api-types.d.ts +121 -0
- package/lib/agents/api-types.d.ts.map +1 -0
- package/lib/agents/auth/init.d.ts +8 -0
- package/lib/agents/auth/init.d.ts.map +1 -0
- package/lib/agents/cache.d.ts +9 -0
- package/lib/agents/cache.d.ts.map +1 -0
- package/lib/agents/client/callGuildVault.d.ts +3 -0
- package/lib/agents/client/callGuildVault.d.ts.map +1 -0
- package/lib/agents/client/callJson.d.ts +3 -0
- package/lib/agents/client/callJson.d.ts.map +1 -0
- package/lib/agents/config.d.ts +36 -0
- package/lib/agents/config.d.ts.map +1 -0
- package/lib/agents/constants.d.ts +11 -0
- package/lib/agents/constants.d.ts.map +1 -0
- package/lib/agents/createAgents.d.ts +4 -0
- package/lib/agents/createAgents.d.ts.map +1 -0
- package/lib/agents/ctx.d.ts +46 -0
- package/lib/agents/ctx.d.ts.map +1 -0
- package/lib/agents/discord/bot.d.ts +21 -0
- package/lib/agents/discord/bot.d.ts.map +1 -0
- package/lib/agents/discord/guilds.d.ts +8 -0
- package/lib/agents/discord/guilds.d.ts.map +1 -0
- package/lib/agents/formDrafts.d.ts +5 -0
- package/lib/agents/formDrafts.d.ts.map +1 -0
- package/lib/agents/generated/agents-schema.d.ts +73 -0
- package/lib/agents/generated/agents-schema.d.ts.map +1 -0
- package/lib/agents/generated/discordsh-agents-schema.d.ts +71 -0
- package/lib/agents/generated/discordsh-agents-schema.d.ts.map +1 -0
- package/lib/agents/github/backfill.d.ts +23 -0
- package/lib/agents/github/backfill.d.ts.map +1 -0
- package/lib/agents/github/events.d.ts +10 -0
- package/lib/agents/github/events.d.ts.map +1 -0
- package/lib/agents/github/pat.d.ts +13 -0
- package/lib/agents/github/pat.d.ts.map +1 -0
- package/lib/agents/github/repoConfig.d.ts +25 -0
- package/lib/agents/github/repoConfig.d.ts.map +1 -0
- package/lib/agents/github/tokens.d.ts +22 -0
- package/lib/agents/github/tokens.d.ts.map +1 -0
- package/lib/agents/github/webhook.d.ts +37 -0
- package/lib/agents/github/webhook.d.ts.map +1 -0
- package/lib/agents/index.d.ts +8 -0
- package/lib/agents/index.d.ts.map +1 -0
- package/lib/agents/state/atoms.d.ts +122 -0
- package/lib/agents/state/atoms.d.ts.map +1 -0
- package/lib/agents/types.d.ts +65 -0
- package/lib/agents/types.d.ts.map +1 -0
- package/lib/gateway/SupabaseGateway.d.ts.map +1 -1
- package/lib/gateway/types.d.ts +1 -1
- package/lib/gateway/types.d.ts.map +1 -1
- package/lib/workers/canvas-worker.js +6 -449
- package/lib/workers/data.d.ts.map +1 -1
- package/lib/workers/db-worker.d.ts +2 -2
- package/lib/workers/db-worker.d.ts.map +1 -1
- package/lib/workers/db-worker.js +14 -4170
- package/lib/workers/main.d.ts +0 -2
- package/lib/workers/main.d.ts.map +1 -1
- package/lib/workers/supabase-db-worker.js +11 -9325
- package/lib/workers/supabase-shared-worker.js +13 -12623
- package/lib/workers/ws-worker.d.ts +1 -2
- package/lib/workers/ws-worker.d.ts.map +1 -1
- package/lib/workers/ws-worker.js +6 -333
- package/main.js +5622 -4786
- package/package.json +2 -1
- package/src/index.ts +3 -0
- package/src/lib/agents/api-types.ts +170 -0
- package/src/lib/agents/auth/init.ts +200 -0
- package/src/lib/agents/cache.ts +127 -0
- package/src/lib/agents/client/callGuildVault.ts +71 -0
- package/src/lib/agents/client/callJson.ts +64 -0
- package/src/lib/agents/config.ts +83 -0
- package/src/lib/agents/constants.ts +11 -0
- package/src/lib/agents/createAgents.ts +62 -0
- package/src/lib/agents/ctx.ts +63 -0
- package/src/lib/agents/discord/bot.ts +169 -0
- package/src/lib/agents/discord/guilds.ts +106 -0
- package/src/lib/agents/formDrafts.ts +55 -0
- package/src/lib/agents/generated/agents-schema.ts +155 -0
- package/src/lib/agents/generated/discordsh-agents-schema.ts +96 -0
- package/src/lib/agents/github/backfill.ts +126 -0
- package/src/lib/agents/github/events.ts +131 -0
- package/src/lib/agents/github/pat.ts +150 -0
- package/src/lib/agents/github/repoConfig.ts +240 -0
- package/src/lib/agents/github/tokens.ts +222 -0
- package/src/lib/agents/github/webhook.ts +349 -0
- package/src/lib/agents/index.ts +35 -0
- package/src/lib/agents/state/atoms.ts +122 -0
- package/src/lib/agents/types.ts +83 -0
- package/src/lib/api.ts +71 -0
- package/src/lib/gateway/SupabaseGateway.ts +55 -2
- package/src/lib/gateway/WorkerCommunication.ts +1 -1
- package/src/lib/gateway/capabilities.spec.ts +144 -0
- package/src/lib/gateway/types.ts +97 -70
- package/src/lib/mod/module/supabase/mod-supabase.ts +10 -5
- package/src/lib/state/auth.ts +121 -3
- package/src/lib/state/bento.spec.ts +76 -0
- package/src/lib/state/bento.ts +51 -0
- package/src/lib/state/index.ts +64 -0
- package/src/lib/state/profile-sync.ts +214 -0
- package/src/lib/state/profile.ts +155 -0
- package/src/lib/state/staff.ts +128 -0
- package/src/lib/state/sync-bus.ts +109 -0
- package/src/lib/state/welcome-toast.spec.ts +101 -0
- package/src/lib/sw-recovery.spec.ts +165 -0
- package/src/lib/sw-recovery.ts +141 -0
- package/src/lib/types/event-schemas.spec.ts +120 -0
- package/src/lib/types/event-types.spec.ts +5 -2
- package/src/lib/types/event-types.ts +74 -0
- package/src/lib/workers/data.ts +5 -1
- package/src/lib/workers/db-worker.ts +34 -8
- package/src/lib/workers/main.ts +3 -18
- package/src/lib/workers/supabase-db-worker.ts +25 -3
- package/src/lib/workers/supabase-shared-worker.ts +43 -8
- package/src/lib/workers/ws-worker.spec.ts +41 -0
- package/src/lib/workers/ws-worker.ts +121 -28
- package/workers/main.js +2 -12
- package/comlink.js +0 -247
- package/index.js +0 -9263
- package/reference.js +0 -719
|
@@ -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
|
+
}
|