hippo-memory 1.2.0 → 1.3.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.
- package/README.md +15 -0
- package/dist/api.d.ts +1 -0
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +30 -10
- package/dist/api.js.map +1 -1
- package/dist/cli.js +15 -3
- package/dist/cli.js.map +1 -1
- package/dist/connectors/github/backfill.d.ts +48 -0
- package/dist/connectors/github/backfill.d.ts.map +1 -0
- package/dist/connectors/github/backfill.js +257 -0
- package/dist/connectors/github/backfill.js.map +1 -0
- package/dist/connectors/github/cli-impl.d.ts +24 -0
- package/dist/connectors/github/cli-impl.d.ts.map +1 -0
- package/dist/connectors/github/cli-impl.js +152 -0
- package/dist/connectors/github/cli-impl.js.map +1 -0
- package/dist/connectors/github/deletion.d.ts +38 -0
- package/dist/connectors/github/deletion.d.ts.map +1 -0
- package/dist/connectors/github/deletion.js +78 -0
- package/dist/connectors/github/deletion.js.map +1 -0
- package/dist/connectors/github/dlq.d.ts +101 -0
- package/dist/connectors/github/dlq.d.ts.map +1 -0
- package/dist/connectors/github/dlq.js +181 -0
- package/dist/connectors/github/dlq.js.map +1 -0
- package/dist/connectors/github/idempotency.d.ts +19 -0
- package/dist/connectors/github/idempotency.d.ts.map +1 -0
- package/dist/connectors/github/idempotency.js +25 -0
- package/dist/connectors/github/idempotency.js.map +1 -0
- package/dist/connectors/github/ingest.d.ts +67 -0
- package/dist/connectors/github/ingest.d.ts.map +1 -0
- package/dist/connectors/github/ingest.js +107 -0
- package/dist/connectors/github/ingest.js.map +1 -0
- package/dist/connectors/github/octokit-client.d.ts +36 -0
- package/dist/connectors/github/octokit-client.d.ts.map +1 -0
- package/dist/connectors/github/octokit-client.js +65 -0
- package/dist/connectors/github/octokit-client.js.map +1 -0
- package/dist/connectors/github/ratelimit.d.ts +20 -0
- package/dist/connectors/github/ratelimit.d.ts.map +1 -0
- package/dist/connectors/github/ratelimit.js +31 -0
- package/dist/connectors/github/ratelimit.js.map +1 -0
- package/dist/connectors/github/scope.d.ts +8 -0
- package/dist/connectors/github/scope.d.ts.map +1 -0
- package/dist/connectors/github/scope.js +13 -0
- package/dist/connectors/github/scope.js.map +1 -0
- package/dist/connectors/github/signature.d.ts +24 -0
- package/dist/connectors/github/signature.d.ts.map +1 -0
- package/dist/connectors/github/signature.js +35 -0
- package/dist/connectors/github/signature.js.map +1 -0
- package/dist/connectors/github/tenant-routing.d.ts +33 -0
- package/dist/connectors/github/tenant-routing.d.ts.map +1 -0
- package/dist/connectors/github/tenant-routing.js +61 -0
- package/dist/connectors/github/tenant-routing.js.map +1 -0
- package/dist/connectors/github/transform.d.ts +7 -0
- package/dist/connectors/github/transform.d.ts.map +1 -0
- package/dist/connectors/github/transform.js +103 -0
- package/dist/connectors/github/transform.js.map +1 -0
- package/dist/connectors/github/types.d.ts +87 -0
- package/dist/connectors/github/types.d.ts.map +1 -0
- package/dist/connectors/github/types.js +94 -0
- package/dist/connectors/github/types.js.map +1 -0
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +72 -1
- package/dist/db.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +6 -4
- package/dist/mcp/server.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +310 -1
- package/dist/server.js.map +1 -1
- package/dist/src/api.js +30 -10
- package/dist/src/api.js.map +1 -1
- package/dist/src/cli.js +15 -3
- package/dist/src/cli.js.map +1 -1
- package/dist/src/connectors/github/backfill.js +257 -0
- package/dist/src/connectors/github/backfill.js.map +1 -0
- package/dist/src/connectors/github/cli-impl.js +152 -0
- package/dist/src/connectors/github/cli-impl.js.map +1 -0
- package/dist/src/connectors/github/deletion.js +78 -0
- package/dist/src/connectors/github/deletion.js.map +1 -0
- package/dist/src/connectors/github/dlq.js +181 -0
- package/dist/src/connectors/github/dlq.js.map +1 -0
- package/dist/src/connectors/github/idempotency.js +25 -0
- package/dist/src/connectors/github/idempotency.js.map +1 -0
- package/dist/src/connectors/github/ingest.js +107 -0
- package/dist/src/connectors/github/ingest.js.map +1 -0
- package/dist/src/connectors/github/octokit-client.js +65 -0
- package/dist/src/connectors/github/octokit-client.js.map +1 -0
- package/dist/src/connectors/github/ratelimit.js +31 -0
- package/dist/src/connectors/github/ratelimit.js.map +1 -0
- package/dist/src/connectors/github/scope.js +13 -0
- package/dist/src/connectors/github/scope.js.map +1 -0
- package/dist/src/connectors/github/signature.js +35 -0
- package/dist/src/connectors/github/signature.js.map +1 -0
- package/dist/src/connectors/github/tenant-routing.js +61 -0
- package/dist/src/connectors/github/tenant-routing.js.map +1 -0
- package/dist/src/connectors/github/transform.js +103 -0
- package/dist/src/connectors/github/transform.js.map +1 -0
- package/dist/src/connectors/github/types.js +94 -0
- package/dist/src/connectors/github/types.js.map +1 -0
- package/dist/src/db.js +72 -1
- package/dist/src/db.js.map +1 -1
- package/dist/src/mcp/server.js +6 -4
- package/dist/src/mcp/server.js.map +1 -1
- package/dist/src/server.js +310 -1
- package/dist/src/server.js.map +1 -1
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Paginated backfill of three GitHub REST streams with per-stream
|
|
3
|
+
* high-water marks (HWMs):
|
|
4
|
+
* - /repos/{repo}/issues -> issues_hwm
|
|
5
|
+
* - /repos/{repo}/issues/comments -> issue_comments_hwm
|
|
6
|
+
* - /repos/{repo}/pulls/comments -> pr_review_comments_hwm
|
|
7
|
+
*
|
|
8
|
+
* Crash safety (codex P1 #3): each stream's HWM is persisted ONLY after
|
|
9
|
+
* the stream fully drains. If stream 2 throws mid-flight, stream 1's HWM
|
|
10
|
+
* is committed and stream 2's stays NULL (or its prior value). Rerun
|
|
11
|
+
* picks up from stream 1's saved HWM and re-fetches stream 2 from its
|
|
12
|
+
* last committed point.
|
|
13
|
+
*
|
|
14
|
+
* Codex P1 #2: the /issues endpoint returns BOTH issues and PRs (a PR is
|
|
15
|
+
* an issue with a `pull_request` field). We skip PRs here so they don't
|
|
16
|
+
* get ingested under the issues schema. PRs are handled via webhook in
|
|
17
|
+
* V1 (no /pulls backfill stream — review comments cover the discussion
|
|
18
|
+
* surface).
|
|
19
|
+
*
|
|
20
|
+
* Privacy (V1 limitation): the REST list endpoints don't reliably set
|
|
21
|
+
* `repository.private`, and resolving it requires an extra API call per
|
|
22
|
+
* repo. Backfill leaves the field undefined so scopeFromRepository falls
|
|
23
|
+
* through to private. Callers who know the repo is public can re-tag
|
|
24
|
+
* downstream; the fail-safe default protects private orgs.
|
|
25
|
+
*/
|
|
26
|
+
import { openHippoDb, closeHippoDb } from '../../db.js';
|
|
27
|
+
import { ingestEvent } from './ingest.js';
|
|
28
|
+
const API = 'https://api.github.com';
|
|
29
|
+
function readCursor(root, tenantId, repo) {
|
|
30
|
+
const db = openHippoDb(root);
|
|
31
|
+
try {
|
|
32
|
+
const row = db
|
|
33
|
+
.prepare(`SELECT issues_hwm, issue_comments_hwm, pr_review_comments_hwm
|
|
34
|
+
FROM github_cursors WHERE tenant_id = ? AND repo_full_name = ?`)
|
|
35
|
+
.get(tenantId, repo);
|
|
36
|
+
return {
|
|
37
|
+
issues: row?.issues_hwm ?? null,
|
|
38
|
+
issueComments: row?.issue_comments_hwm ?? null,
|
|
39
|
+
prReviewComments: row?.pr_review_comments_hwm ?? null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
closeHippoDb(db);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function writeOneHwm(root, tenantId, repo, column, value) {
|
|
47
|
+
const db = openHippoDb(root);
|
|
48
|
+
try {
|
|
49
|
+
db.prepare(`INSERT INTO github_cursors (tenant_id, repo_full_name, ${column}, updated_at)
|
|
50
|
+
VALUES (?, ?, ?, ?)
|
|
51
|
+
ON CONFLICT(tenant_id, repo_full_name)
|
|
52
|
+
DO UPDATE SET ${column} = excluded.${column}, updated_at = excluded.updated_at`).run(tenantId, repo, value, new Date().toISOString());
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
closeHippoDb(db);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Build a synthetic `repository` object from the known repo full name.
|
|
60
|
+
* REST list endpoints often omit the full repository object on each item;
|
|
61
|
+
* we already know the repo since the caller passed it. `private` left
|
|
62
|
+
* undefined so scopeFromRepository falls through to private.
|
|
63
|
+
*/
|
|
64
|
+
function syntheticRepository(repoFullName) {
|
|
65
|
+
const [owner, name] = repoFullName.split('/');
|
|
66
|
+
return {
|
|
67
|
+
full_name: repoFullName,
|
|
68
|
+
name: name ?? repoFullName,
|
|
69
|
+
owner: { login: owner ?? repoFullName },
|
|
70
|
+
// private intentionally omitted — fail-safe to private scope.
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Parse the trailing issue/PR number from a REST API URL.
|
|
75
|
+
* e.g. https://api.github.com/repos/o/r/issues/42 -> 42
|
|
76
|
+
* https://api.github.com/repos/o/r/pulls/7 -> 7
|
|
77
|
+
*/
|
|
78
|
+
function parseTrailingNumber(url) {
|
|
79
|
+
if (!url)
|
|
80
|
+
return null;
|
|
81
|
+
const m = url.match(/\/(\d+)(?:\?.*)?$/);
|
|
82
|
+
if (!m)
|
|
83
|
+
return null;
|
|
84
|
+
const n = Number(m[1]);
|
|
85
|
+
return Number.isFinite(n) ? n : null;
|
|
86
|
+
}
|
|
87
|
+
function isIssuesItem(x) {
|
|
88
|
+
if (!x || typeof x !== 'object')
|
|
89
|
+
return false;
|
|
90
|
+
const o = x;
|
|
91
|
+
if (typeof o.number !== 'number')
|
|
92
|
+
return false;
|
|
93
|
+
if (typeof o.title !== 'string')
|
|
94
|
+
return false;
|
|
95
|
+
const u = o.user;
|
|
96
|
+
if (!u || typeof u.login !== 'string' || typeof u.id !== 'number')
|
|
97
|
+
return false;
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
function isCommentItem(x) {
|
|
101
|
+
if (!x || typeof x !== 'object')
|
|
102
|
+
return false;
|
|
103
|
+
const o = x;
|
|
104
|
+
if (typeof o.id !== 'number')
|
|
105
|
+
return false;
|
|
106
|
+
const u = o.user;
|
|
107
|
+
if (!u || typeof u.login !== 'string' || typeof u.id !== 'number')
|
|
108
|
+
return false;
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Drain one stream end-to-end. Pauses and retries on rate-limit. Throws
|
|
113
|
+
* on any other fetch error so the caller leaves the HWM unchanged
|
|
114
|
+
* (codex P1 #3 crash safety). Returns max(updated_at) seen so the caller
|
|
115
|
+
* can advance the HWM only if the whole stream drained.
|
|
116
|
+
*/
|
|
117
|
+
async function drainStream(ctx, url0, toIngestEvent, fetcher, token, sleep, maxItems) {
|
|
118
|
+
let url = url0;
|
|
119
|
+
let ingested = 0;
|
|
120
|
+
let pages = 0;
|
|
121
|
+
let maxUpdatedAt = null;
|
|
122
|
+
while (url) {
|
|
123
|
+
// Fetch with rate-limit retry loop.
|
|
124
|
+
let page;
|
|
125
|
+
while (true) {
|
|
126
|
+
page = await fetcher({ url, token });
|
|
127
|
+
if (page.rateLimit.reason !== 'none') {
|
|
128
|
+
await sleep(page.rateLimit.sleepSeconds * 1000);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
pages++;
|
|
134
|
+
for (const item of page.items) {
|
|
135
|
+
const evt = toIngestEvent(item);
|
|
136
|
+
if (!evt)
|
|
137
|
+
continue; // PRs returned by /issues, malformed shape, etc.
|
|
138
|
+
const updatedAt = item && typeof item === 'object'
|
|
139
|
+
? (item.updated_at ?? null)
|
|
140
|
+
: null;
|
|
141
|
+
const r = ingestEvent(ctx, {
|
|
142
|
+
event: evt,
|
|
143
|
+
rawBody: JSON.stringify(item),
|
|
144
|
+
deliveryId: `backfill:${ctx.tenantId}:${updatedAt ?? ''}`,
|
|
145
|
+
});
|
|
146
|
+
// Count anything we successfully processed (new ingest OR known dup).
|
|
147
|
+
if (r.status === 'ingested' || r.status === 'skipped')
|
|
148
|
+
ingested++;
|
|
149
|
+
if (updatedAt && (!maxUpdatedAt || updatedAt > maxUpdatedAt)) {
|
|
150
|
+
maxUpdatedAt = updatedAt;
|
|
151
|
+
}
|
|
152
|
+
if (maxItems && ingested >= maxItems) {
|
|
153
|
+
return { ingested, pages, maxUpdatedAt };
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
url = page.next;
|
|
157
|
+
}
|
|
158
|
+
return { ingested, pages, maxUpdatedAt };
|
|
159
|
+
}
|
|
160
|
+
export async function backfillRepo(ctx, opts) {
|
|
161
|
+
const sleep = opts.sleepMs ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
162
|
+
const cursors = readCursor(ctx.hippoRoot, ctx.tenantId, opts.repoFullName);
|
|
163
|
+
const repository = syntheticRepository(opts.repoFullName);
|
|
164
|
+
const result = {
|
|
165
|
+
ingested: { issues: 0, issueComments: 0, prReviewComments: 0 },
|
|
166
|
+
pages: { issues: 0, issueComments: 0, prReviewComments: 0 },
|
|
167
|
+
};
|
|
168
|
+
// ---------- Stream 1: issues (skip PRs) ----------
|
|
169
|
+
const issuesUrl = `${API}/repos/${opts.repoFullName}/issues?state=all&per_page=100` +
|
|
170
|
+
(cursors.issues ? `&since=${encodeURIComponent(cursors.issues)}` : '');
|
|
171
|
+
const issuesRes = await drainStream(ctx, issuesUrl, (item) => {
|
|
172
|
+
if (!isIssuesItem(item))
|
|
173
|
+
return null;
|
|
174
|
+
// Codex P1 #2: /issues returns PRs too — skip them.
|
|
175
|
+
if (item.pull_request)
|
|
176
|
+
return null;
|
|
177
|
+
const payload = {
|
|
178
|
+
action: 'opened',
|
|
179
|
+
repository,
|
|
180
|
+
issue: {
|
|
181
|
+
number: item.number,
|
|
182
|
+
title: item.title,
|
|
183
|
+
body: item.body ?? null,
|
|
184
|
+
user: { login: item.user.login, id: item.user.id },
|
|
185
|
+
updated_at: item.updated_at,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
return { eventName: 'issues', payload };
|
|
189
|
+
}, opts.fetcher, opts.token, sleep, opts.maxPerStream);
|
|
190
|
+
result.ingested.issues = issuesRes.ingested;
|
|
191
|
+
result.pages.issues = issuesRes.pages;
|
|
192
|
+
if (issuesRes.maxUpdatedAt) {
|
|
193
|
+
writeOneHwm(ctx.hippoRoot, ctx.tenantId, opts.repoFullName, 'issues_hwm', issuesRes.maxUpdatedAt);
|
|
194
|
+
}
|
|
195
|
+
// ---------- Stream 2: repo-level issue comments ----------
|
|
196
|
+
const commentsUrl = `${API}/repos/${opts.repoFullName}/issues/comments?per_page=100` +
|
|
197
|
+
(cursors.issueComments
|
|
198
|
+
? `&since=${encodeURIComponent(cursors.issueComments)}`
|
|
199
|
+
: '');
|
|
200
|
+
const commentsRes = await drainStream(ctx, commentsUrl, (item) => {
|
|
201
|
+
if (!isCommentItem(item))
|
|
202
|
+
return null;
|
|
203
|
+
const c = item;
|
|
204
|
+
const issueNumber = parseTrailingNumber(c.issue_url);
|
|
205
|
+
if (issueNumber === null)
|
|
206
|
+
return null;
|
|
207
|
+
const payload = {
|
|
208
|
+
action: 'created',
|
|
209
|
+
repository,
|
|
210
|
+
issue: { number: issueNumber },
|
|
211
|
+
comment: {
|
|
212
|
+
id: c.id,
|
|
213
|
+
body: c.body ?? null,
|
|
214
|
+
user: { login: c.user.login, id: c.user.id },
|
|
215
|
+
updated_at: c.updated_at,
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
return { eventName: 'issue_comment', payload };
|
|
219
|
+
}, opts.fetcher, opts.token, sleep, opts.maxPerStream);
|
|
220
|
+
result.ingested.issueComments = commentsRes.ingested;
|
|
221
|
+
result.pages.issueComments = commentsRes.pages;
|
|
222
|
+
if (commentsRes.maxUpdatedAt) {
|
|
223
|
+
writeOneHwm(ctx.hippoRoot, ctx.tenantId, opts.repoFullName, 'issue_comments_hwm', commentsRes.maxUpdatedAt);
|
|
224
|
+
}
|
|
225
|
+
// ---------- Stream 3: repo-level PR review comments ----------
|
|
226
|
+
const prCommentsUrl = `${API}/repos/${opts.repoFullName}/pulls/comments?per_page=100` +
|
|
227
|
+
(cursors.prReviewComments
|
|
228
|
+
? `&since=${encodeURIComponent(cursors.prReviewComments)}`
|
|
229
|
+
: '');
|
|
230
|
+
const prCommentsRes = await drainStream(ctx, prCommentsUrl, (item) => {
|
|
231
|
+
if (!isCommentItem(item))
|
|
232
|
+
return null;
|
|
233
|
+
const c = item;
|
|
234
|
+
const prNumber = parseTrailingNumber(c.pull_request_url);
|
|
235
|
+
if (prNumber === null)
|
|
236
|
+
return null;
|
|
237
|
+
const payload = {
|
|
238
|
+
action: 'created',
|
|
239
|
+
repository,
|
|
240
|
+
pull_request: { number: prNumber },
|
|
241
|
+
comment: {
|
|
242
|
+
id: c.id,
|
|
243
|
+
body: c.body ?? null,
|
|
244
|
+
user: { login: c.user.login, id: c.user.id },
|
|
245
|
+
updated_at: c.updated_at,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
return { eventName: 'pull_request_review_comment', payload };
|
|
249
|
+
}, opts.fetcher, opts.token, sleep, opts.maxPerStream);
|
|
250
|
+
result.ingested.prReviewComments = prCommentsRes.ingested;
|
|
251
|
+
result.pages.prReviewComments = prCommentsRes.pages;
|
|
252
|
+
if (prCommentsRes.maxUpdatedAt) {
|
|
253
|
+
writeOneHwm(ctx.hippoRoot, ctx.tenantId, opts.repoFullName, 'pr_review_comments_hwm', prCommentsRes.maxUpdatedAt);
|
|
254
|
+
}
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=backfill.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backfill.js","sourceRoot":"","sources":["../../../../src/connectors/github/backfill.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Implementation of `hippo github` CLI subcommands. Extracted from the main
|
|
3
|
+
* cli.ts so unit tests can import these functions directly without triggering
|
|
4
|
+
* the cli.ts main() side effects. The cli.ts dispatcher re-exports the
|
|
5
|
+
* top-level cmdGithub.
|
|
6
|
+
*
|
|
7
|
+
* Subcommands mirror the Slack connector shape (cli.ts §Slack subcommands):
|
|
8
|
+
* - hippo github backfill --repo <owner/name> [--since ISO] [--max <N>]
|
|
9
|
+
* - hippo github dlq list
|
|
10
|
+
* - hippo github dlq replay <id> [--force]
|
|
11
|
+
*/
|
|
12
|
+
import { openHippoDb, closeHippoDb } from '../../db.js';
|
|
13
|
+
import { resolveTenantId } from '../../tenant.js';
|
|
14
|
+
import { backfillRepo } from './backfill.js';
|
|
15
|
+
import { realGitHubFetcher } from './octokit-client.js';
|
|
16
|
+
import { listDlq, replayDlqEntry } from './dlq.js';
|
|
17
|
+
export function printGithubBackfillUsage() {
|
|
18
|
+
console.log('hippo github backfill --repo <owner/name> [--since ISO] [--max <N>]');
|
|
19
|
+
console.log(' --repo GitHub repository in owner/name format (required, e.g. acme/widgets)');
|
|
20
|
+
console.log(' --since Initial high-water-mark for first run (optional, ISO 8601)');
|
|
21
|
+
console.log(' --max Cap items per stream (optional, integer)');
|
|
22
|
+
console.log(' Requires GITHUB_TOKEN env var with repo read scope.');
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* `hippo github backfill`. The fetcher is injectable so tests can drive the
|
|
26
|
+
* code path without hitting the network. Defaults to `realGitHubFetcher`.
|
|
27
|
+
*/
|
|
28
|
+
export async function cmdGithubBackfill(hippoRoot, flags, fetcher = realGitHubFetcher) {
|
|
29
|
+
if (flags['help']) {
|
|
30
|
+
printGithubBackfillUsage();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const repo = flags['repo'];
|
|
34
|
+
if (typeof repo !== 'string' || !repo.includes('/')) {
|
|
35
|
+
printGithubBackfillUsage();
|
|
36
|
+
process.exit(2);
|
|
37
|
+
}
|
|
38
|
+
const token = process.env.GITHUB_TOKEN;
|
|
39
|
+
if (!token) {
|
|
40
|
+
console.error('GITHUB_TOKEN is not set. Backfill requires a personal access token with repo read scope.');
|
|
41
|
+
process.exit(2);
|
|
42
|
+
}
|
|
43
|
+
const maxRaw = flags['max'];
|
|
44
|
+
let maxPerStream;
|
|
45
|
+
if (typeof maxRaw === 'string' || typeof maxRaw === 'number') {
|
|
46
|
+
const parsed = Number(maxRaw);
|
|
47
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
48
|
+
maxPerStream = Math.floor(parsed);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const sinceIso = typeof flags['since'] === 'string' ? flags['since'] : undefined;
|
|
52
|
+
const tenantId = resolveTenantId({});
|
|
53
|
+
// Seed the github_cursors row so all 3 streams use --since on a fresh run.
|
|
54
|
+
// COALESCE preserves any existing HWM (subsequent runs ignore --since for
|
|
55
|
+
// streams that already drained at least once — same idempotency story as
|
|
56
|
+
// Slack's slack_cursors).
|
|
57
|
+
if (sinceIso) {
|
|
58
|
+
const db = openHippoDb(hippoRoot);
|
|
59
|
+
try {
|
|
60
|
+
db.prepare(`INSERT INTO github_cursors (tenant_id, repo_full_name, issues_hwm, issue_comments_hwm, pr_review_comments_hwm, updated_at)
|
|
61
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
62
|
+
ON CONFLICT(tenant_id, repo_full_name) DO UPDATE SET
|
|
63
|
+
issues_hwm = COALESCE(github_cursors.issues_hwm, excluded.issues_hwm),
|
|
64
|
+
issue_comments_hwm = COALESCE(github_cursors.issue_comments_hwm, excluded.issue_comments_hwm),
|
|
65
|
+
pr_review_comments_hwm = COALESCE(github_cursors.pr_review_comments_hwm, excluded.pr_review_comments_hwm),
|
|
66
|
+
updated_at = excluded.updated_at`).run(tenantId, repo, sinceIso, sinceIso, sinceIso, new Date().toISOString());
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
closeHippoDb(db);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const ctx = {
|
|
73
|
+
hippoRoot,
|
|
74
|
+
tenantId,
|
|
75
|
+
actor: 'cli:github-backfill',
|
|
76
|
+
};
|
|
77
|
+
try {
|
|
78
|
+
const result = await backfillRepo(ctx, {
|
|
79
|
+
repoFullName: repo,
|
|
80
|
+
fetcher,
|
|
81
|
+
token: token,
|
|
82
|
+
maxPerStream,
|
|
83
|
+
});
|
|
84
|
+
console.log(JSON.stringify(result, null, 2));
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
console.error('backfill failed:', e.message);
|
|
88
|
+
process.exit(3);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export function cmdGithubDlqList(hippoRoot, _flags) {
|
|
92
|
+
const db = openHippoDb(hippoRoot);
|
|
93
|
+
try {
|
|
94
|
+
const tenantId = resolveTenantId({});
|
|
95
|
+
const items = listDlq(db, { tenantId });
|
|
96
|
+
if (items.length === 0) {
|
|
97
|
+
console.log('no entries');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
for (const it of items) {
|
|
101
|
+
console.log(`${it.id}\t${it.bucket}\t${it.tenantId}\t${it.eventName ?? '-'}\t${it.receivedAt}\t${it.error}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
closeHippoDb(db);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export async function cmdGithubDlqReplay(hippoRoot, args, flags) {
|
|
109
|
+
const idArg = args[0];
|
|
110
|
+
if (!idArg) {
|
|
111
|
+
console.error('Usage: hippo github dlq replay <id> [--force]');
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
const id = Number(idArg);
|
|
115
|
+
if (!Number.isFinite(id) || !Number.isInteger(id) || id < 1) {
|
|
116
|
+
console.error(`replay: invalid id ${idArg}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
const force = flags['force'] === true;
|
|
120
|
+
const ctx = {
|
|
121
|
+
hippoRoot,
|
|
122
|
+
tenantId: resolveTenantId({}),
|
|
123
|
+
actor: 'cli:github-dlq-replay',
|
|
124
|
+
};
|
|
125
|
+
const result = await replayDlqEntry(ctx, id, {
|
|
126
|
+
force,
|
|
127
|
+
webhookSecret: process.env.GITHUB_WEBHOOK_SECRET,
|
|
128
|
+
});
|
|
129
|
+
if (!result.ok) {
|
|
130
|
+
console.error(`replay failed: status=${result.status} retry_count=${result.retryCount}${result.reason ? ` reason=${result.reason}` : ''}`);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
console.log(`replay ok: status=${result.status} memory_id=${result.memoryId ?? '(none)'} retry_count=${result.retryCount}`);
|
|
134
|
+
}
|
|
135
|
+
export async function cmdGithub(hippoRoot, args, flags) {
|
|
136
|
+
const sub = args[0];
|
|
137
|
+
if (sub === 'backfill') {
|
|
138
|
+
await cmdGithubBackfill(hippoRoot, flags);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (sub === 'dlq' && args[1] === 'list') {
|
|
142
|
+
cmdGithubDlqList(hippoRoot, flags);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (sub === 'dlq' && args[1] === 'replay') {
|
|
146
|
+
await cmdGithubDlqReplay(hippoRoot, args.slice(2), flags);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
console.error('Usage: hippo github <backfill|dlq list|dlq replay <id> [--force]> [...]');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=cli-impl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-impl.js","sourceRoot":"","sources":["../../../../src/connectors/github/cli-impl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,iBAAiB,EAAsB,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAInD,MAAM,UAAU,wBAAwB;IACtC,OAAO,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC;IACnF,OAAO,CAAC,GAAG,CAAC,iFAAiF,CAAC,CAAC;IAC/F,OAAO,CAAC,GAAG,CAAC,uEAAuE,CAAC,CAAC;IACrF,OAAO,CAAC,GAAG,CAAC,qDAAqD,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,uDAAuD,CAAC,CAAC;AACvE,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,SAAiB,EACjB,KAAY,EACZ,UAAyB,iBAAiB;IAE1C,IAAI,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;QAClB,wBAAwB,EAAE,CAAC;QAC3B,OAAO;IACT,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,wBAAwB,EAAE,CAAC;QAC3B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACvC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CACX,0FAA0F,CAC3F,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,YAAgC,CAAC;IACrC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1C,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IACD,MAAM,QAAQ,GAAG,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,KAAK,CAAC,OAAO,CAAY,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7F,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;IAErC,2EAA2E;IAC3E,0EAA0E;IAC1E,yEAAyE;IACzE,0BAA0B;IAC1B,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,EAAE,CAAC,OAAO,CACR;;;;;;4CAMoC,CACrC,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QAChF,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,EAAE,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;IAED,MAAM,GAAG,GAAY;QACnB,SAAS;QACT,QAAQ;QACR,KAAK,EAAE,qBAAqB;KAC7B,CAAC;IACF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,GAAG,EAAE;YACrC,YAAY,EAAE,IAAI;YAClB,OAAO;YACP,KAAK,EAAE,KAAe;YACtB,YAAY;SACb,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAG,CAAW,CAAC,OAAO,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,SAAiB,EAAE,MAAa;IAC/D,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,KAAK,GAAG,OAAO,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC;QACxC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QACD,KAAK,MAAM,EAAE,IAAI,KAAK,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CACT,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,KAAK,EAAE,CAAC,QAAQ,KAAK,EAAE,CAAC,SAAS,IAAI,GAAG,KAAK,EAAE,CAAC,UAAU,KAAK,EAAE,CAAC,KAAK,EAAE,CAChG,CAAC;QACJ,CAAC;IACH,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,EAAE,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,SAAiB,EACjB,IAAc,EACd,KAAY;IAEZ,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACzB,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;QAC5D,OAAO,CAAC,KAAK,CAAC,sBAAsB,KAAK,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IACtC,MAAM,GAAG,GAAY;QACnB,SAAS;QACT,QAAQ,EAAE,eAAe,CAAC,EAAE,CAAC;QAC7B,KAAK,EAAE,uBAAuB;KAC/B,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE,EAAE,EAAE;QAC3C,KAAK;QACL,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,qBAAqB;KACjD,CAAC,CAAC;IACH,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CACX,yBAAyB,MAAM,CAAC,MAAM,gBAAgB,MAAM,CAAC,UAAU,GACrE,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAC/C,EAAE,CACH,CAAC;QACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,CAAC,GAAG,CACT,qBAAqB,MAAM,CAAC,MAAM,cAAc,MAAM,CAAC,QAAQ,IAAI,QAAQ,gBAAgB,MAAM,CAAC,UAAU,EAAE,CAC/G,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,SAAiB,EACjB,IAAc,EACd,KAAY;IAEZ,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,IAAI,GAAG,KAAK,UAAU,EAAE,CAAC;QACvB,MAAM,iBAAiB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QAC1C,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,EAAE,CAAC;QACxC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnC,OAAO;IACT,CAAC;IACD,IAAI,GAAG,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1C,MAAM,kBAAkB,CAAC,SAAS,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO;IACT,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,yEAAyE,CAAC,CAAC;IACzF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { archiveRaw } from '../../api.js';
|
|
2
|
+
import { openHippoDb, closeHippoDb } from '../../db.js';
|
|
3
|
+
import { hasSeenKey, markKeySeen } from './idempotency.js';
|
|
4
|
+
/**
|
|
5
|
+
* Handle GitHub `issue_comment.deleted` and `pull_request_review_comment.deleted`.
|
|
6
|
+
*
|
|
7
|
+
* Codex P0 #5: filter by tenant_id + kind='raw'. Multi-row archive: GitHub edits
|
|
8
|
+
* keep the same artifact_ref, so multiple active raw rows can match a single
|
|
9
|
+
* deletion event (each edit produces a fresh raw memory id sharing the same
|
|
10
|
+
* artifact_ref). Archive ALL of them, not just the most recent.
|
|
11
|
+
*
|
|
12
|
+
* Crash-safety: the afterArchive hook on archiveRaw runs inside the SAVEPOINT,
|
|
13
|
+
* so the idempotency mark commits with the FIRST archive — a crash mid-archive
|
|
14
|
+
* cannot leave the deletion event un-acked. A retry sees the key as 'seen' and
|
|
15
|
+
* returns 'duplicate' instead of attempting a partial re-archive.
|
|
16
|
+
*
|
|
17
|
+
* Tenant scope is load-bearing: without `tenant_id = ?` a deletion event from
|
|
18
|
+
* tenant A could archive tenant B's raw row sharing the same artifact_ref. The
|
|
19
|
+
* `kind = 'raw'` filter prevents accidentally targeting distilled rows that
|
|
20
|
+
* may share artifact_ref via downstream extraction.
|
|
21
|
+
*/
|
|
22
|
+
export function handleCommentDeleted(ctx, input) {
|
|
23
|
+
// Fast-path duplicate check + look up all matching raw rows.
|
|
24
|
+
const dbCheck = openHippoDb(ctx.hippoRoot);
|
|
25
|
+
let memoryIds = [];
|
|
26
|
+
try {
|
|
27
|
+
if (hasSeenKey(dbCheck, input.idempotencyKey)) {
|
|
28
|
+
return { status: 'duplicate', archivedCount: 0 };
|
|
29
|
+
}
|
|
30
|
+
const rows = dbCheck
|
|
31
|
+
.prepare(`SELECT id FROM memories WHERE artifact_ref = ? AND tenant_id = ? AND kind = 'raw'`)
|
|
32
|
+
.all(input.artifactRef, ctx.tenantId);
|
|
33
|
+
memoryIds = rows.map((r) => r.id);
|
|
34
|
+
}
|
|
35
|
+
finally {
|
|
36
|
+
closeHippoDb(dbCheck);
|
|
37
|
+
}
|
|
38
|
+
if (memoryIds.length === 0) {
|
|
39
|
+
// Nothing to archive — but still mark idempotency so a retry returns 'duplicate'.
|
|
40
|
+
// No row to roll back, so the second-handle pattern is safe here.
|
|
41
|
+
const dbMark = openHippoDb(ctx.hippoRoot);
|
|
42
|
+
try {
|
|
43
|
+
markKeySeen(dbMark, {
|
|
44
|
+
idempotencyKey: input.idempotencyKey,
|
|
45
|
+
deliveryId: input.deliveryId,
|
|
46
|
+
eventName: input.eventName,
|
|
47
|
+
memoryId: null,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
closeHippoDb(dbMark);
|
|
52
|
+
}
|
|
53
|
+
return { status: 'archive_skipped_not_found', archivedCount: 0 };
|
|
54
|
+
}
|
|
55
|
+
// Archive each matching row. Mark idempotency on the first archive's
|
|
56
|
+
// afterArchive hook so the mark lands inside the same SAVEPOINT as the first
|
|
57
|
+
// archive (crash-safe). markKeySeen is INSERT OR IGNORE so subsequent calls
|
|
58
|
+
// are no-ops, but we only need the first one to commit-or-rollback with the
|
|
59
|
+
// first archive — that's enough to keep the source-of-truth lock-step.
|
|
60
|
+
let firstMarked = false;
|
|
61
|
+
for (const id of memoryIds) {
|
|
62
|
+
archiveRaw(ctx, id, `source_deleted:github:${input.eventName}:${input.deliveryId}`, {
|
|
63
|
+
afterArchive: (sameDb, archivedId) => {
|
|
64
|
+
if (!firstMarked) {
|
|
65
|
+
markKeySeen(sameDb, {
|
|
66
|
+
idempotencyKey: input.idempotencyKey,
|
|
67
|
+
deliveryId: input.deliveryId,
|
|
68
|
+
eventName: input.eventName,
|
|
69
|
+
memoryId: archivedId,
|
|
70
|
+
});
|
|
71
|
+
firstMarked = true;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return { status: 'archived', archivedCount: memoryIds.length };
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=deletion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deletion.js","sourceRoot":"","sources":["../../../../src/connectors/github/deletion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAgB,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAsB3D;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAY,EAAE,KAAoB;IACrE,6DAA6D;IAC7D,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3C,IAAI,SAAS,GAAa,EAAE,CAAC;IAC7B,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC,cAAc,CAAC,EAAE,CAAC;YAC9C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;QACnD,CAAC;QACD,MAAM,IAAI,GAAG,OAAO;aACjB,OAAO,CACN,mFAAmF,CACpF;aACA,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,GAAG,CAAC,QAAQ,CAA0B,CAAC;QACjE,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACpC,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,OAAO,CAAC,CAAC;IACxB,CAAC;IAED,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,kFAAkF;QAClF,kEAAkE;QAClE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC;YACH,WAAW,CAAC,MAAM,EAAE;gBAClB,cAAc,EAAE,KAAK,CAAC,cAAc;gBACpC,UAAU,EAAE,KAAK,CAAC,UAAU;gBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;gBAC1B,QAAQ,EAAE,IAAI;aACf,CAAC,CAAC;QACL,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,2BAA2B,EAAE,aAAa,EAAE,CAAC,EAAE,CAAC;IACnE,CAAC;IAED,qEAAqE;IACrE,6EAA6E;IAC7E,4EAA4E;IAC5E,4EAA4E;IAC5E,uEAAuE;IACvE,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,UAAU,CACR,GAAG,EACH,EAAE,EACF,yBAAyB,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,UAAU,EAAE,EAC9D;YACE,YAAY,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE;gBACnC,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,WAAW,CAAC,MAAM,EAAE;wBAClB,cAAc,EAAE,KAAK,CAAC,cAAc;wBACpC,UAAU,EAAE,KAAK,CAAC,UAAU;wBAC5B,SAAS,EAAE,KAAK,CAAC,SAAS;wBAC1B,QAAQ,EAAE,UAAU;qBACrB,CAAC,CAAC;oBACH,WAAW,GAAG,IAAI,CAAC;gBACrB,CAAC;YACH,CAAC;SACF,CACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;AACjE,CAAC"}
|