mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 09:07:46 +02:00
Automatically import and update project repository skills (#16066)
Automatically import skills from new GitHub project repositories and update enabled sources in the background. Preserve project-derived access, saved selections, immutable execution versions and explicit pins; add legacy adoption, refresh and connection repair in Sources. Include durable scheduling and bounded work credits, production Storybook journeys, real GitHub-to-routine acceptance receipts and final-head CI/security/Greptile qualification. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
de335b42d4
commit
473c80d13f
101 files changed
+5991
-186
No files matched your search
@@ -15,7 +15,7 @@ function providerStatus(error: unknown): number | undefined {
|
||||
}
|
||||
|
||||
/** Automatic access considers only this caller's grants, never connection management visibility. */
|
||||
export function skillSourceGitHubReader(db: Db, companyId: string, actor: Request['actor'], connectionId: string | null): GitHubRead {
|
||||
export function skillSourceGitHubReader(db: Db, companyId: string, actor: Request['actor'], connectionId: string | null, options: { grantId?: string | null } = {}): GitHubRead {
|
||||
let chosen: Authorization | undefined;
|
||||
const access = toolAccessService(db);
|
||||
const localTrusted = actor.type === 'board' && actor.source === 'local_implicit';
|
||||
@@ -44,7 +44,10 @@ export function skillSourceGitHubReader(db: Db, companyId: string, actor: Reques
|
||||
const ids = connectionId ? [connectionId] : await access.githubReadConnectionIds(companyId, actor.userId ?? null, localTrusted);
|
||||
const result: Authorization[] = [];
|
||||
for (const id of ids) {
|
||||
for (const grantId of await access.githubReadGrantIds(companyId, id, actor.userId ?? null, localTrusted)) result.push({ connectionId: id, grantId });
|
||||
for (const grantId of await access.githubReadGrantIds(companyId, id, actor.userId ?? null, localTrusted)) {
|
||||
if (options.grantId && options.grantId !== grantId) continue;
|
||||
result.push({ connectionId: id, grantId });
|
||||
}
|
||||
}
|
||||
result.sort((a, b) => Number(b.connectionId === chosen?.connectionId && b.grantId === chosen?.grantId) - Number(a.connectionId === chosen?.connectionId && a.grantId === chosen?.grantId));
|
||||
if (!connectionId) result.push({ connectionId: null });
|
||||
@@ -100,7 +103,13 @@ export function skillSourceGitHubReader(db: Db, companyId: string, actor: Reques
|
||||
: response.status === 404 ? 'Repository, branch, or file is unavailable. Check the URL and GitHub repository access.'
|
||||
: [401, 403].includes(response.status) ? 'GitHub denied repository access. Reconnect GitHub or choose an authorized connection.'
|
||||
: 'GitHub could not complete the read. Try again.';
|
||||
throw unprocessable(message, { code: limited ? 'skill_source_github_rate_limited' : 'skill_source_github_read_failed', status: response.status });
|
||||
const retryAfter = response.headers.get('retry-after');
|
||||
const resetAt = Number(response.headers.get('x-ratelimit-reset')) * 1_000;
|
||||
const retryAfterMs = retryAfter && /^\d+$/.test(retryAfter)
|
||||
? Number(retryAfter) * 1_000
|
||||
: retryAfter ? Date.parse(retryAfter) - Date.now() : resetAt - Date.now();
|
||||
throw unprocessable(message, { code: limited ? 'skill_source_github_rate_limited' : 'skill_source_github_read_failed', status: response.status,
|
||||
...(limited && Number.isFinite(retryAfterMs) && retryAfterMs > 0 ? { retryAfterMs } : {}) });
|
||||
}
|
||||
return response.json();
|
||||
}, signal);
|
||||
@@ -129,6 +138,7 @@ export function skillSourceGitHubReader(db: Db, companyId: string, actor: Reques
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
Object.defineProperty(read, 'grantId', { get: () => chosen?.grantId ?? null });
|
||||
Object.defineProperty(read, 'connectionId', { get: () => chosen?.connectionId ?? null });
|
||||
return read;
|
||||
}
|
||||
Reference in new issue
Block a user