@elizaos/plugin-github 2.0.0-alpha.5 → 2.0.0-beta.1
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/dist/chunk-KU2MGW2W.js +6 -0
- package/dist/index.d.ts +294 -0
- package/dist/index.js +2212 -1914
- package/dist/register-routes.d.ts +2 -0
- package/dist/register-routes.js +1 -0
- package/package.json +20 -99
- package/dist/index.js.map +0 -27
package/dist/index.js
CHANGED
|
@@ -1,2064 +1,2362 @@
|
|
|
1
|
-
|
|
2
|
-
import { logger as logger9 } from "@elizaos/core";
|
|
1
|
+
import "./chunk-KU2MGW2W.js";
|
|
3
2
|
|
|
4
|
-
//
|
|
3
|
+
// src/index.ts
|
|
5
4
|
import {
|
|
6
|
-
|
|
5
|
+
getConnectorAccountManager as getConnectorAccountManager2,
|
|
6
|
+
logger as logger7,
|
|
7
|
+
promoteSubactionsToActions
|
|
7
8
|
} from "@elizaos/core";
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
version: "1.0.0",
|
|
12
|
-
actions: [
|
|
13
|
-
{
|
|
14
|
-
name: "CREATE_GITHUB_BRANCH",
|
|
15
|
-
description: "",
|
|
16
|
-
parameters: []
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: "CREATE_GITHUB_COMMENT",
|
|
20
|
-
description: "",
|
|
21
|
-
parameters: []
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
name: "CREATE_GITHUB_ISSUE",
|
|
25
|
-
description: "",
|
|
26
|
-
parameters: []
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
name: "CREATE_GITHUB_PULL_REQUEST",
|
|
30
|
-
description: "",
|
|
31
|
-
parameters: []
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
name: "MERGE_GITHUB_PULL_REQUEST",
|
|
35
|
-
description: "",
|
|
36
|
-
parameters: []
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: "PUSH_GITHUB_CODE",
|
|
40
|
-
description: "",
|
|
41
|
-
parameters: []
|
|
42
|
-
},
|
|
43
|
-
{
|
|
44
|
-
name: "REVIEW_GITHUB_PULL_REQUEST",
|
|
45
|
-
description: "",
|
|
46
|
-
parameters: []
|
|
47
|
-
}
|
|
48
|
-
]
|
|
49
|
-
};
|
|
50
|
-
var allActionsSpec = {
|
|
51
|
-
version: "1.0.0",
|
|
52
|
-
actions: [
|
|
53
|
-
{
|
|
54
|
-
name: "CREATE_GITHUB_BRANCH",
|
|
55
|
-
description: "",
|
|
56
|
-
parameters: []
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: "CREATE_GITHUB_COMMENT",
|
|
60
|
-
description: "",
|
|
61
|
-
parameters: []
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
name: "CREATE_GITHUB_ISSUE",
|
|
65
|
-
description: "",
|
|
66
|
-
parameters: []
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
name: "CREATE_GITHUB_PULL_REQUEST",
|
|
70
|
-
description: "",
|
|
71
|
-
parameters: []
|
|
72
|
-
},
|
|
73
|
-
{
|
|
74
|
-
name: "MERGE_GITHUB_PULL_REQUEST",
|
|
75
|
-
description: "",
|
|
76
|
-
parameters: []
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "PUSH_GITHUB_CODE",
|
|
80
|
-
description: "",
|
|
81
|
-
parameters: []
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: "REVIEW_GITHUB_PULL_REQUEST",
|
|
85
|
-
description: "",
|
|
86
|
-
parameters: []
|
|
87
|
-
}
|
|
88
|
-
]
|
|
89
|
-
};
|
|
90
|
-
var coreProvidersSpec = {
|
|
91
|
-
version: "1.0.0",
|
|
92
|
-
providers: [
|
|
93
|
-
{
|
|
94
|
-
name: "GITHUB_ISSUE_CONTEXT",
|
|
95
|
-
description: "Provides detailed context about a specific GitHub issue or pull request when referenced",
|
|
96
|
-
dynamic: true
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
name: "GITHUB_REPOSITORY_STATE",
|
|
100
|
-
description: "Provides context about the current GitHub repository including recent activity",
|
|
101
|
-
dynamic: true
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
};
|
|
105
|
-
var allProvidersSpec = {
|
|
106
|
-
version: "1.0.0",
|
|
107
|
-
providers: [
|
|
108
|
-
{
|
|
109
|
-
name: "GITHUB_ISSUE_CONTEXT",
|
|
110
|
-
description: "Provides detailed context about a specific GitHub issue or pull request when referenced",
|
|
111
|
-
dynamic: true
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
name: "GITHUB_REPOSITORY_STATE",
|
|
115
|
-
description: "Provides context about the current GitHub repository including recent activity",
|
|
116
|
-
dynamic: true
|
|
117
|
-
}
|
|
118
|
-
]
|
|
119
|
-
};
|
|
120
|
-
var coreEvaluatorsSpec = {
|
|
121
|
-
version: "1.0.0",
|
|
122
|
-
evaluators: []
|
|
123
|
-
};
|
|
124
|
-
var allEvaluatorsSpec = {
|
|
125
|
-
version: "1.0.0",
|
|
126
|
-
evaluators: []
|
|
127
|
-
};
|
|
128
|
-
var coreActionDocs = coreActionsSpec.actions;
|
|
129
|
-
var allActionDocs = allActionsSpec.actions;
|
|
130
|
-
var coreProviderDocs = coreProvidersSpec.providers;
|
|
131
|
-
var allProviderDocs = allProvidersSpec.providers;
|
|
132
|
-
var coreEvaluatorDocs = coreEvaluatorsSpec.evaluators;
|
|
133
|
-
var allEvaluatorDocs = allEvaluatorsSpec.evaluators;
|
|
10
|
+
// src/actions/issue-op.ts
|
|
11
|
+
import { logger } from "@elizaos/core";
|
|
134
12
|
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
var
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
13
|
+
// src/connector-credential-refs.ts
|
|
14
|
+
import {
|
|
15
|
+
CONNECTOR_ACCOUNT_STORAGE_SERVICE_TYPE,
|
|
16
|
+
getConnectorAccountManager
|
|
17
|
+
} from "@elizaos/core";
|
|
18
|
+
var OAUTH_TOKENS_CREDENTIAL_TYPE = "oauth.tokens";
|
|
19
|
+
async function persistConnectorCredentialRefs(params) {
|
|
20
|
+
const refs = [];
|
|
21
|
+
const vaultWriters = resolveVaultWriters(params.runtime, {
|
|
22
|
+
provider: params.provider,
|
|
23
|
+
accountId: params.accountIdForRef,
|
|
24
|
+
caller: params.caller
|
|
25
|
+
});
|
|
26
|
+
if (vaultWriters.length === 0) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`No durable connector credential store or vault writer is available for ${params.provider} account ${params.accountIdForRef}. Refusing to mark OAuth account connected without persisted credentials.`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
if (!params.storageAccountId) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`No durable connector account id is available for ${params.provider} account ${params.accountIdForRef}. Refusing to mark OAuth account connected without persisted credential refs.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
const storageWriters = resolveCredentialRefWriters(
|
|
37
|
+
params.runtime,
|
|
38
|
+
params.manager,
|
|
39
|
+
params.storageAccountId
|
|
40
|
+
);
|
|
41
|
+
if (storageWriters.length === 0) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`No durable connector credential ref writer is available for ${params.provider} account ${params.storageAccountId}. Refusing to mark OAuth account connected without persisted credential refs.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
for (const credential of params.credentials) {
|
|
47
|
+
const plannedRef = buildConnectorCredentialVaultRef({
|
|
48
|
+
agentId: nonEmptyString(params.runtime.agentId) ?? "agent",
|
|
49
|
+
provider: params.provider,
|
|
50
|
+
accountId: params.accountIdForRef,
|
|
51
|
+
credentialType: credential.credentialType
|
|
52
|
+
});
|
|
53
|
+
const vaultRef = await writeWithFirstAvailableVault(
|
|
54
|
+
vaultWriters,
|
|
55
|
+
plannedRef,
|
|
56
|
+
credential
|
|
57
|
+
);
|
|
58
|
+
refs.push({
|
|
59
|
+
credentialType: credential.credentialType,
|
|
60
|
+
vaultRef,
|
|
61
|
+
...credential.expiresAt !== void 0 ? { expiresAt: credential.expiresAt } : {},
|
|
62
|
+
...credential.metadata ? { metadata: credential.metadata } : {}
|
|
63
|
+
});
|
|
149
64
|
}
|
|
150
|
-
|
|
65
|
+
if (refs.length > 0) {
|
|
66
|
+
await writeRefsToStorage(storageWriters, refs);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
refs,
|
|
70
|
+
vaultAvailable: vaultWriters.length > 0,
|
|
71
|
+
storageAvailable: storageWriters.length > 0
|
|
72
|
+
};
|
|
151
73
|
}
|
|
152
|
-
function
|
|
153
|
-
|
|
74
|
+
async function loadConnectorOAuthAccessToken(params) {
|
|
75
|
+
const { records } = await loadConnectorCredentialRecords(params);
|
|
76
|
+
const tokenRecord = records.find(
|
|
77
|
+
(record) => sameCredentialType(record.credentialType, OAUTH_TOKENS_CREDENTIAL_TYPE)
|
|
78
|
+
);
|
|
79
|
+
if (!tokenRecord) return null;
|
|
80
|
+
const raw = nonEmptyString(tokenRecord.value) ?? (tokenRecord.vaultRef ? await readCredentialSecret(
|
|
81
|
+
params.runtime,
|
|
82
|
+
tokenRecord.vaultRef,
|
|
83
|
+
params.caller
|
|
84
|
+
) : void 0);
|
|
85
|
+
const parsed = raw ? parseMaybeJson(raw) : void 0;
|
|
86
|
+
const tokenSet = asRecord(parsed);
|
|
87
|
+
return nonEmptyString(tokenSet?.access_token) ?? null;
|
|
154
88
|
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
89
|
+
async function listConnectorAccounts(runtime, provider) {
|
|
90
|
+
try {
|
|
91
|
+
return await getConnectorAccountManager(runtime).listAccounts(provider);
|
|
92
|
+
} catch {
|
|
93
|
+
const storage = getService(
|
|
94
|
+
runtime,
|
|
95
|
+
CONNECTOR_ACCOUNT_STORAGE_SERVICE_TYPE
|
|
96
|
+
);
|
|
97
|
+
if (typeof storage?.listAccounts === "function") {
|
|
98
|
+
return storage.listAccounts(provider);
|
|
99
|
+
}
|
|
159
100
|
}
|
|
160
|
-
return
|
|
101
|
+
return [];
|
|
161
102
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
103
|
+
function credentialRefRecordsFromMetadata(metadata) {
|
|
104
|
+
const record = asRecord(metadata);
|
|
105
|
+
if (!record) return [];
|
|
106
|
+
const oauth = asRecord(record.oauth);
|
|
107
|
+
return [
|
|
108
|
+
...credentialRefsFromUnknown(record.credentialRefs),
|
|
109
|
+
...credentialRefsFromUnknown(record.oauthCredentialRefs),
|
|
110
|
+
...credentialRefsFromUnknown(oauth?.credentialRefs)
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
async function loadConnectorCredentialRecords(params) {
|
|
114
|
+
const accounts = await listConnectorAccounts(params.runtime, params.provider);
|
|
115
|
+
const account = accounts.find(
|
|
116
|
+
(candidate) => candidate.id === params.accountId || candidate.externalId === params.accountId || candidate.displayHandle === params.accountId
|
|
117
|
+
);
|
|
118
|
+
if (!account) return { records: [] };
|
|
119
|
+
const records = [...credentialRefRecordsFromMetadata(account.metadata)];
|
|
120
|
+
for (const source of [
|
|
121
|
+
getService(params.runtime, CONNECTOR_ACCOUNT_STORAGE_SERVICE_TYPE),
|
|
122
|
+
params.runtime.adapter
|
|
123
|
+
]) {
|
|
124
|
+
const reader = source;
|
|
125
|
+
if (typeof reader?.listConnectorAccountCredentialRefs === "function") {
|
|
126
|
+
records.push(
|
|
127
|
+
...await reader.listConnectorAccountCredentialRefs({
|
|
128
|
+
accountId: account.id
|
|
129
|
+
})
|
|
130
|
+
);
|
|
131
|
+
} else if (typeof reader?.getConnectorAccountCredentialRef === "function") {
|
|
132
|
+
const ref = await reader.getConnectorAccountCredentialRef({
|
|
133
|
+
accountId: account.id,
|
|
134
|
+
credentialType: OAUTH_TOKENS_CREDENTIAL_TYPE
|
|
135
|
+
});
|
|
136
|
+
if (ref) records.push(ref);
|
|
137
|
+
}
|
|
182
138
|
}
|
|
139
|
+
return { records };
|
|
183
140
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
141
|
+
function credentialRefsFromUnknown(value) {
|
|
142
|
+
if (Array.isArray(value)) {
|
|
143
|
+
return value.flatMap((entry) => {
|
|
144
|
+
const ref = credentialRefFromRecord(asRecord(entry));
|
|
145
|
+
return ref ? [ref] : [];
|
|
146
|
+
});
|
|
189
147
|
}
|
|
148
|
+
const record = asRecord(value);
|
|
149
|
+
if (!record) return [];
|
|
150
|
+
return Object.entries(record).flatMap(([credentialType, entry]) => {
|
|
151
|
+
const entryRecord = asRecord(entry);
|
|
152
|
+
if (entryRecord) {
|
|
153
|
+
const ref = credentialRefFromRecord({ credentialType, ...entryRecord });
|
|
154
|
+
return ref ? [ref] : [];
|
|
155
|
+
}
|
|
156
|
+
const vaultRef = nonEmptyString(entry);
|
|
157
|
+
return vaultRef ? [{ credentialType, vaultRef }] : [];
|
|
158
|
+
});
|
|
190
159
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
160
|
+
function credentialRefFromRecord(record) {
|
|
161
|
+
if (!record) return null;
|
|
162
|
+
const credentialType = nonEmptyString(
|
|
163
|
+
record.credentialType ?? record.type ?? record.name
|
|
164
|
+
);
|
|
165
|
+
const vaultRef = nonEmptyString(record.vaultRef ?? record.ref);
|
|
166
|
+
if (!credentialType || !vaultRef) return null;
|
|
167
|
+
return {
|
|
168
|
+
credentialType,
|
|
169
|
+
vaultRef,
|
|
170
|
+
metadata: asRecord(record.metadata) ?? null,
|
|
171
|
+
expiresAt: record.expiresAt,
|
|
172
|
+
updatedAt: record.updatedAt,
|
|
173
|
+
version: record.version ?? record.credentialVersion
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
async function readCredentialSecret(runtime, vaultRef, caller) {
|
|
177
|
+
for (const reader of resolveSecretReaders(runtime)) {
|
|
178
|
+
try {
|
|
179
|
+
const value = await readSecret(reader, vaultRef, caller, runtime);
|
|
180
|
+
const trimmed = nonEmptyString(value);
|
|
181
|
+
if (trimmed) return trimmed;
|
|
182
|
+
} catch {
|
|
183
|
+
}
|
|
199
184
|
}
|
|
185
|
+
return void 0;
|
|
200
186
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
187
|
+
function resolveVaultWriters(runtime, context) {
|
|
188
|
+
const writers = [];
|
|
189
|
+
const credentialStore = getFirstService(runtime, [
|
|
190
|
+
"connector_credential_store",
|
|
191
|
+
"CONNECTOR_CREDENTIAL_STORE",
|
|
192
|
+
"connectorCredentialStore",
|
|
193
|
+
"credential_store"
|
|
194
|
+
]);
|
|
195
|
+
if (typeof credentialStore?.putSecret === "function") {
|
|
196
|
+
writers.push({
|
|
197
|
+
name: "connector_credential_store",
|
|
198
|
+
write: async (vaultRef, credential) => credentialStore.putSecret?.({
|
|
199
|
+
vaultRef,
|
|
200
|
+
agentId: nonEmptyString(runtime.agentId) ?? "agent",
|
|
201
|
+
provider: context.provider,
|
|
202
|
+
accountId: context.accountId,
|
|
203
|
+
credentialType: credential.credentialType,
|
|
204
|
+
value: credential.value,
|
|
205
|
+
caller: context.caller
|
|
206
|
+
}) ?? vaultRef
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const vault = getFirstService(runtime, ["vault", "VAULT"]);
|
|
210
|
+
if (typeof vault?.set === "function") {
|
|
211
|
+
writers.push({
|
|
212
|
+
name: "vault",
|
|
213
|
+
write: async (vaultRef, credential) => {
|
|
214
|
+
await vault.set?.(vaultRef, credential.value, {
|
|
215
|
+
sensitive: true,
|
|
216
|
+
caller: context.caller
|
|
217
|
+
});
|
|
218
|
+
return vaultRef;
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
const secrets = getService(runtime, "SECRETS");
|
|
223
|
+
if (typeof secrets?.setGlobal === "function" || typeof secrets?.set === "function") {
|
|
224
|
+
writers.push({
|
|
225
|
+
name: "SECRETS",
|
|
226
|
+
write: async (vaultRef, credential) => {
|
|
227
|
+
if (typeof secrets.setGlobal === "function") {
|
|
228
|
+
await secrets.setGlobal(vaultRef, credential.value, {
|
|
229
|
+
sensitive: true
|
|
230
|
+
});
|
|
231
|
+
return vaultRef;
|
|
232
|
+
}
|
|
233
|
+
await secrets.set?.(
|
|
234
|
+
vaultRef,
|
|
235
|
+
credential.value,
|
|
236
|
+
{ level: "global", agentId: runtime.agentId },
|
|
237
|
+
{ sensitive: true }
|
|
238
|
+
);
|
|
239
|
+
return vaultRef;
|
|
240
|
+
}
|
|
241
|
+
});
|
|
208
242
|
}
|
|
243
|
+
return writers;
|
|
209
244
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
var createIssueSchema = repositoryRefSchema.extend({
|
|
222
|
-
title: z.string().min(1, "Title is required"),
|
|
223
|
-
body: z.string().optional(),
|
|
224
|
-
assignees: z.array(z.string()).optional(),
|
|
225
|
-
labels: z.array(z.string()).optional(),
|
|
226
|
-
milestone: z.number().optional()
|
|
227
|
-
});
|
|
228
|
-
var updateIssueSchema = repositoryRefSchema.extend({
|
|
229
|
-
issueNumber: z.number().min(1, "Issue number is required"),
|
|
230
|
-
title: z.string().optional(),
|
|
231
|
-
body: z.string().optional(),
|
|
232
|
-
state: z.enum(["open", "closed"]).optional(),
|
|
233
|
-
stateReason: z.enum(["completed", "not_planned", "reopened"]).optional(),
|
|
234
|
-
assignees: z.array(z.string()).optional(),
|
|
235
|
-
labels: z.array(z.string()).optional(),
|
|
236
|
-
milestone: z.number().nullable().optional()
|
|
237
|
-
});
|
|
238
|
-
var createPullRequestSchema = repositoryRefSchema.extend({
|
|
239
|
-
title: z.string().min(1, "Title is required"),
|
|
240
|
-
body: z.string().optional(),
|
|
241
|
-
head: z.string().min(1, "Head branch is required"),
|
|
242
|
-
base: z.string().min(1, "Base branch is required"),
|
|
243
|
-
draft: z.boolean().optional(),
|
|
244
|
-
maintainerCanModify: z.boolean().optional()
|
|
245
|
-
});
|
|
246
|
-
var createReviewSchema = repositoryRefSchema.extend({
|
|
247
|
-
pullNumber: z.number().min(1, "Pull request number is required"),
|
|
248
|
-
body: z.string().optional(),
|
|
249
|
-
event: z.enum(["APPROVE", "REQUEST_CHANGES", "COMMENT"]),
|
|
250
|
-
commitId: z.string().optional(),
|
|
251
|
-
comments: z.array(z.object({
|
|
252
|
-
path: z.string(),
|
|
253
|
-
line: z.number(),
|
|
254
|
-
body: z.string(),
|
|
255
|
-
side: z.enum(["LEFT", "RIGHT"]).optional(),
|
|
256
|
-
startLine: z.number().optional(),
|
|
257
|
-
startSide: z.enum(["LEFT", "RIGHT"]).optional()
|
|
258
|
-
})).optional()
|
|
259
|
-
});
|
|
260
|
-
var createCommentSchema = repositoryRefSchema.extend({
|
|
261
|
-
issueNumber: z.number().min(1, "Issue number is required"),
|
|
262
|
-
body: z.string().min(1, "Comment body is required")
|
|
263
|
-
});
|
|
264
|
-
var createBranchSchema = repositoryRefSchema.extend({
|
|
265
|
-
branchName: z.string().min(1, "Branch name is required"),
|
|
266
|
-
fromRef: z.string().min(1, "Source ref is required")
|
|
267
|
-
});
|
|
268
|
-
var createCommitSchema = repositoryRefSchema.extend({
|
|
269
|
-
message: z.string().min(1, "Commit message is required"),
|
|
270
|
-
files: z.array(z.object({
|
|
271
|
-
path: z.string().min(1, "File path is required"),
|
|
272
|
-
content: z.string(),
|
|
273
|
-
encoding: z.enum(["utf-8", "base64"]).optional(),
|
|
274
|
-
operation: z.enum(["add", "modify", "delete"]).optional()
|
|
275
|
-
})),
|
|
276
|
-
branch: z.string().min(1, "Branch is required"),
|
|
277
|
-
parentSha: z.string().optional(),
|
|
278
|
-
authorName: z.string().optional(),
|
|
279
|
-
authorEmail: z.string().optional()
|
|
280
|
-
});
|
|
281
|
-
var mergePullRequestSchema = repositoryRefSchema.extend({
|
|
282
|
-
pullNumber: z.number().min(1, "Pull request number is required"),
|
|
283
|
-
commitTitle: z.string().optional(),
|
|
284
|
-
commitMessage: z.string().optional(),
|
|
285
|
-
mergeMethod: z.enum(["merge", "squash", "rebase"]).optional(),
|
|
286
|
-
sha: z.string().optional()
|
|
287
|
-
});
|
|
288
|
-
var gitHubSettingsSchema = z.object({
|
|
289
|
-
apiToken: z.string().min(1, "API token is required"),
|
|
290
|
-
owner: z.string().optional(),
|
|
291
|
-
repo: z.string().optional(),
|
|
292
|
-
branch: z.string().optional(),
|
|
293
|
-
webhookSecret: z.string().optional(),
|
|
294
|
-
appId: z.string().optional(),
|
|
295
|
-
appPrivateKey: z.string().optional(),
|
|
296
|
-
installationId: z.string().optional()
|
|
297
|
-
});
|
|
298
|
-
function formatZodErrors(error) {
|
|
299
|
-
return z.prettifyError(error);
|
|
245
|
+
function resolveSecretReaders(runtime) {
|
|
246
|
+
return [
|
|
247
|
+
getFirstService(runtime, [
|
|
248
|
+
"connector_credential_store",
|
|
249
|
+
"CONNECTOR_CREDENTIAL_STORE",
|
|
250
|
+
"connectorCredentialStore",
|
|
251
|
+
"credential_store"
|
|
252
|
+
]),
|
|
253
|
+
getFirstService(runtime, ["vault", "VAULT"]),
|
|
254
|
+
getService(runtime, "SECRETS")
|
|
255
|
+
].filter(Boolean);
|
|
300
256
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
owner: z2.string().optional(),
|
|
306
|
-
repo: z2.string().optional(),
|
|
307
|
-
branch: z2.string().default("main"),
|
|
308
|
-
webhookSecret: z2.string().optional(),
|
|
309
|
-
appId: z2.string().optional(),
|
|
310
|
-
appPrivateKey: z2.string().optional(),
|
|
311
|
-
installationId: z2.string().optional()
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
class GitHubPluginConfig {
|
|
315
|
-
apiToken;
|
|
316
|
-
owner;
|
|
317
|
-
repo;
|
|
318
|
-
branch;
|
|
319
|
-
webhookSecret;
|
|
320
|
-
appId;
|
|
321
|
-
appPrivateKey;
|
|
322
|
-
installationId;
|
|
323
|
-
constructor(config) {
|
|
324
|
-
this.apiToken = config.apiToken;
|
|
325
|
-
this.owner = config.owner;
|
|
326
|
-
this.repo = config.repo;
|
|
327
|
-
this.branch = config.branch;
|
|
328
|
-
this.webhookSecret = config.webhookSecret;
|
|
329
|
-
this.appId = config.appId;
|
|
330
|
-
this.appPrivateKey = config.appPrivateKey;
|
|
331
|
-
this.installationId = config.installationId;
|
|
332
|
-
}
|
|
333
|
-
static fromRuntime(runtime) {
|
|
334
|
-
const apiToken = runtime.getSetting("GITHUB_API_TOKEN");
|
|
335
|
-
if (!apiToken) {
|
|
336
|
-
throw new MissingSettingError("GITHUB_API_TOKEN");
|
|
337
|
-
}
|
|
338
|
-
const rawConfig = {
|
|
339
|
-
apiToken,
|
|
340
|
-
owner: runtime.getSetting("GITHUB_OWNER") ?? undefined,
|
|
341
|
-
repo: runtime.getSetting("GITHUB_REPO") ?? undefined,
|
|
342
|
-
branch: runtime.getSetting("GITHUB_BRANCH") ?? "main",
|
|
343
|
-
webhookSecret: runtime.getSetting("GITHUB_WEBHOOK_SECRET") ?? undefined,
|
|
344
|
-
appId: runtime.getSetting("GITHUB_APP_ID") ?? undefined,
|
|
345
|
-
appPrivateKey: runtime.getSetting("GITHUB_APP_PRIVATE_KEY") ?? undefined,
|
|
346
|
-
installationId: runtime.getSetting("GITHUB_INSTALLATION_ID") ?? undefined
|
|
347
|
-
};
|
|
348
|
-
const result = configSchema.safeParse(rawConfig);
|
|
349
|
-
if (!result.success) {
|
|
350
|
-
throw new ConfigError(formatZodErrors(result.error));
|
|
351
|
-
}
|
|
352
|
-
return new GitHubPluginConfig(result.data);
|
|
257
|
+
async function readSecret(reader, vaultRef, caller, runtime) {
|
|
258
|
+
const candidate = reader;
|
|
259
|
+
if (typeof candidate.reveal === "function") {
|
|
260
|
+
return candidate.reveal(vaultRef, caller);
|
|
353
261
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
262
|
+
if (typeof candidate.get !== "function") return null;
|
|
263
|
+
if (reader && reader.constructor?.name === "SecretsService") {
|
|
264
|
+
return candidate.get(vaultRef, {
|
|
265
|
+
level: "global",
|
|
266
|
+
agentId: runtime.agentId
|
|
358
267
|
});
|
|
359
|
-
if (!result.success) {
|
|
360
|
-
throw new ConfigError(formatZodErrors(result.error));
|
|
361
|
-
}
|
|
362
|
-
return new GitHubPluginConfig(result.data);
|
|
363
268
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
269
|
+
return candidate.get(vaultRef, { reveal: true, caller });
|
|
270
|
+
}
|
|
271
|
+
function resolveCredentialRefWriters(runtime, manager, accountId) {
|
|
272
|
+
const candidates = [
|
|
273
|
+
manager?.getStorage?.(),
|
|
274
|
+
getService(runtime, CONNECTOR_ACCOUNT_STORAGE_SERVICE_TYPE),
|
|
275
|
+
runtime.adapter
|
|
276
|
+
].filter(Boolean);
|
|
277
|
+
const writers = [];
|
|
278
|
+
for (const candidate of candidates) {
|
|
279
|
+
const writer = candidate;
|
|
280
|
+
if (typeof writer.setConnectorAccountCredentialRef === "function") {
|
|
281
|
+
writers.push({
|
|
282
|
+
name: "setConnectorAccountCredentialRef",
|
|
283
|
+
write: async (ref) => {
|
|
284
|
+
await writer.setConnectorAccountCredentialRef?.({
|
|
285
|
+
accountId,
|
|
286
|
+
credentialType: ref.credentialType,
|
|
287
|
+
vaultRef: ref.vaultRef,
|
|
288
|
+
...ref.metadata ? { metadata: ref.metadata } : {},
|
|
289
|
+
...ref.expiresAt !== void 0 ? { expiresAt: ref.expiresAt } : {}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
} else if (typeof writer.setCredentialRef === "function") {
|
|
294
|
+
writers.push({
|
|
295
|
+
name: "setCredentialRef",
|
|
296
|
+
write: async (ref) => {
|
|
297
|
+
await writer.setCredentialRef?.({
|
|
298
|
+
accountId,
|
|
299
|
+
credentialType: ref.credentialType,
|
|
300
|
+
vaultRef: ref.vaultRef,
|
|
301
|
+
...ref.metadata ? { metadata: ref.metadata } : {},
|
|
302
|
+
...ref.expiresAt !== void 0 ? { expiresAt: ref.expiresAt } : {}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
});
|
|
382
306
|
}
|
|
383
|
-
return new GitHubPluginConfig(result.data);
|
|
384
307
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
308
|
+
return writers;
|
|
309
|
+
}
|
|
310
|
+
async function writeWithFirstAvailableVault(writers, plannedRef, credential) {
|
|
311
|
+
const errors = [];
|
|
312
|
+
for (const writer of writers) {
|
|
313
|
+
try {
|
|
314
|
+
return await writer.write(plannedRef, credential);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
errors.push(
|
|
317
|
+
`${writer.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
318
|
+
);
|
|
390
319
|
}
|
|
391
|
-
|
|
392
|
-
|
|
320
|
+
}
|
|
321
|
+
throw new Error(
|
|
322
|
+
`Failed to persist connector credential ref ${plannedRef}: ${errors.join("; ")}`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
async function writeRefsToStorage(writers, refs) {
|
|
326
|
+
const errors = [];
|
|
327
|
+
for (const writer of writers) {
|
|
328
|
+
try {
|
|
329
|
+
for (const ref of refs) {
|
|
330
|
+
await writer.write(ref);
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
} catch (error) {
|
|
334
|
+
errors.push(
|
|
335
|
+
`${writer.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
336
|
+
);
|
|
393
337
|
}
|
|
394
|
-
return { owner: resolvedOwner, repo: resolvedRepo };
|
|
395
338
|
}
|
|
396
|
-
|
|
397
|
-
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Failed to persist connector credential refs: ${errors.join("; ")}`
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
function buildConnectorCredentialVaultRef(params) {
|
|
344
|
+
return [
|
|
345
|
+
"connector",
|
|
346
|
+
normalizeVaultSegment(params.agentId),
|
|
347
|
+
normalizeVaultSegment(params.provider),
|
|
348
|
+
normalizeVaultSegment(params.accountId),
|
|
349
|
+
normalizeVaultSegment(params.credentialType)
|
|
350
|
+
].join(".");
|
|
351
|
+
}
|
|
352
|
+
function normalizeVaultSegment(value) {
|
|
353
|
+
const normalized = value.trim().replace(/[^a-zA-Z0-9_-]+/g, "_").replace(/^_+|_+$/g, "");
|
|
354
|
+
return (normalized || "unknown").slice(0, 64);
|
|
355
|
+
}
|
|
356
|
+
function sameCredentialType(left, right) {
|
|
357
|
+
return left.toLowerCase() === right.toLowerCase();
|
|
358
|
+
}
|
|
359
|
+
function parseMaybeJson(value) {
|
|
360
|
+
const trimmed = value.trim();
|
|
361
|
+
if (!trimmed.startsWith("{") && !trimmed.startsWith("[")) return void 0;
|
|
362
|
+
try {
|
|
363
|
+
return JSON.parse(trimmed);
|
|
364
|
+
} catch {
|
|
365
|
+
return void 0;
|
|
398
366
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (
|
|
404
|
-
throw new ConfigError("GITHUB_INSTALLATION_ID is required when using GitHub App authentication");
|
|
405
|
-
}
|
|
367
|
+
}
|
|
368
|
+
function getFirstService(runtime, serviceTypes) {
|
|
369
|
+
for (const serviceType of serviceTypes) {
|
|
370
|
+
const service = getService(runtime, serviceType);
|
|
371
|
+
if (service) return service;
|
|
406
372
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
appId: this.appId,
|
|
415
|
-
appPrivateKey: this.appPrivateKey,
|
|
416
|
-
installationId: this.installationId
|
|
417
|
-
};
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
function getService(runtime, serviceType) {
|
|
376
|
+
try {
|
|
377
|
+
return runtime.getService?.(serviceType) ?? null;
|
|
378
|
+
} catch {
|
|
379
|
+
return null;
|
|
418
380
|
}
|
|
419
381
|
}
|
|
420
|
-
function
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
382
|
+
function asRecord(value) {
|
|
383
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
384
|
+
}
|
|
385
|
+
function nonEmptyString(value) {
|
|
386
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
424
387
|
}
|
|
425
388
|
|
|
426
|
-
//
|
|
427
|
-
var
|
|
389
|
+
// src/types.ts
|
|
390
|
+
var GITHUB_SERVICE_TYPE = "github";
|
|
391
|
+
var GitHubActions = {
|
|
392
|
+
GITHUB_ISSUE_OP: "GITHUB_ISSUE",
|
|
393
|
+
GITHUB_PR_OP: "GITHUB_PR",
|
|
394
|
+
GITHUB_NOTIFICATION_TRIAGE: "GITHUB_NOTIFICATION_TRIAGE"
|
|
395
|
+
};
|
|
428
396
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
397
|
+
// src/accounts.ts
|
|
398
|
+
var DEFAULT_GITHUB_USER_ACCOUNT_ID = "user";
|
|
399
|
+
var DEFAULT_GITHUB_AGENT_ACCOUNT_ID = "agent";
|
|
400
|
+
function nonEmptyString2(value) {
|
|
401
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
402
|
+
}
|
|
403
|
+
function readSetting(runtime, key) {
|
|
404
|
+
return nonEmptyString2(runtime.getSetting(key));
|
|
405
|
+
}
|
|
406
|
+
function normalizeRole(value) {
|
|
407
|
+
return value === "user" || value === "agent" ? value : void 0;
|
|
408
|
+
}
|
|
409
|
+
function defaultGitHubAccountIdForRole(role) {
|
|
410
|
+
return role === "user" ? DEFAULT_GITHUB_USER_ACCOUNT_ID : DEFAULT_GITHUB_AGENT_ACCOUNT_ID;
|
|
411
|
+
}
|
|
412
|
+
function normalizeGitHubAccountId(value) {
|
|
413
|
+
return nonEmptyString2(value);
|
|
414
|
+
}
|
|
415
|
+
function resolveGitHubAccountSelection(options, defaultRole) {
|
|
416
|
+
const requestedAccountId = normalizeGitHubAccountId(options?.accountId);
|
|
417
|
+
const requestedRole = normalizeRole(options?.as);
|
|
418
|
+
return {
|
|
419
|
+
accountId: requestedAccountId,
|
|
420
|
+
role: requestedRole ?? normalizeRole(requestedAccountId) ?? defaultRole
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
function parseAccountsJson(raw) {
|
|
424
|
+
if (!raw) return [];
|
|
425
|
+
try {
|
|
426
|
+
const parsed = JSON.parse(raw);
|
|
427
|
+
if (Array.isArray(parsed)) {
|
|
428
|
+
return parsed.filter(
|
|
429
|
+
(item) => Boolean(item) && typeof item === "object" && !Array.isArray(item)
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
if (parsed && typeof parsed === "object") {
|
|
433
|
+
return Object.entries(parsed).filter(([, value]) => value && typeof value === "object").map(([id, value]) => ({
|
|
434
|
+
...value,
|
|
435
|
+
accountId: value.accountId ?? id
|
|
436
|
+
}));
|
|
437
|
+
}
|
|
438
|
+
} catch {
|
|
439
|
+
return [];
|
|
442
440
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
441
|
+
return [];
|
|
442
|
+
}
|
|
443
|
+
function readRawField(record, keys) {
|
|
444
|
+
const credentials = record.credentials && typeof record.credentials === "object" ? record.credentials : {};
|
|
445
|
+
const settings = record.settings && typeof record.settings === "object" ? record.settings : {};
|
|
446
|
+
for (const source of [record, credentials, settings]) {
|
|
447
|
+
for (const key of keys) {
|
|
448
|
+
const value = nonEmptyString2(source[key]);
|
|
449
|
+
if (value) return value;
|
|
446
450
|
}
|
|
447
|
-
return this.octokit;
|
|
448
|
-
}
|
|
449
|
-
async start(runtime) {
|
|
450
|
-
logger.info("Starting GitHub service...");
|
|
451
|
-
this._config = validateGitHubConfig(runtime);
|
|
452
|
-
const settings = this._config.toSettings();
|
|
453
|
-
this.config = {
|
|
454
|
-
apiToken: settings.apiToken ?? "",
|
|
455
|
-
owner: settings.owner ?? undefined,
|
|
456
|
-
repo: settings.repo ?? undefined,
|
|
457
|
-
branch: settings.branch ?? "main",
|
|
458
|
-
webhookSecret: settings.webhookSecret ?? undefined,
|
|
459
|
-
appId: settings.appId ?? undefined,
|
|
460
|
-
appPrivateKey: settings.appPrivateKey ?? undefined,
|
|
461
|
-
installationId: settings.installationId ?? undefined
|
|
462
|
-
};
|
|
463
|
-
this.octokit = new Octokit({
|
|
464
|
-
auth: this._config.apiToken,
|
|
465
|
-
userAgent: "elizaos-plugin-github/1.0.0"
|
|
466
|
-
});
|
|
467
|
-
const { data: user } = await this.octokit.users.getAuthenticated();
|
|
468
|
-
logger.info(`GitHub service started - authenticated as ${user.login}`);
|
|
469
451
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
per_page: options?.perPage ?? 30,
|
|
489
|
-
page: options?.page ?? 1
|
|
490
|
-
}) : await client.repos.listForAuthenticatedUser({
|
|
491
|
-
type: options?.type ?? "all",
|
|
492
|
-
per_page: options?.perPage ?? 30,
|
|
493
|
-
page: options?.page ?? 1
|
|
494
|
-
});
|
|
495
|
-
return data.map((r) => this.mapRepository(r));
|
|
496
|
-
}
|
|
497
|
-
async createIssue(params) {
|
|
498
|
-
const client = this.getClient();
|
|
499
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
500
|
-
const { data } = await client.issues.create({
|
|
501
|
-
owner,
|
|
502
|
-
repo,
|
|
503
|
-
title: params.title,
|
|
504
|
-
body: params.body,
|
|
505
|
-
assignees: params.assignees,
|
|
506
|
-
labels: params.labels,
|
|
507
|
-
milestone: params.milestone
|
|
508
|
-
});
|
|
509
|
-
return this.mapIssue(data);
|
|
510
|
-
}
|
|
511
|
-
async getIssue(params) {
|
|
512
|
-
const client = this.getClient();
|
|
513
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
514
|
-
const { data } = await client.issues.get({
|
|
515
|
-
owner,
|
|
516
|
-
repo,
|
|
517
|
-
issue_number: params.issueNumber
|
|
518
|
-
});
|
|
519
|
-
return this.mapIssue(data);
|
|
520
|
-
}
|
|
521
|
-
async updateIssue(params) {
|
|
522
|
-
const client = this.getClient();
|
|
523
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
524
|
-
const { data } = await client.issues.update({
|
|
525
|
-
owner,
|
|
526
|
-
repo,
|
|
527
|
-
issue_number: params.issueNumber,
|
|
528
|
-
title: params.title,
|
|
529
|
-
body: params.body,
|
|
530
|
-
state: params.state,
|
|
531
|
-
state_reason: params.stateReason,
|
|
532
|
-
assignees: params.assignees,
|
|
533
|
-
labels: params.labels,
|
|
534
|
-
milestone: params.milestone ?? undefined
|
|
535
|
-
});
|
|
536
|
-
return this.mapIssue(data);
|
|
537
|
-
}
|
|
538
|
-
async listIssues(params) {
|
|
539
|
-
const client = this.getClient();
|
|
540
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
541
|
-
const { data } = await client.issues.listForRepo({
|
|
542
|
-
owner,
|
|
543
|
-
repo,
|
|
544
|
-
state: params.state ?? "open",
|
|
545
|
-
labels: params.labels,
|
|
546
|
-
sort: params.sort ?? "created",
|
|
547
|
-
direction: params.direction ?? "desc",
|
|
548
|
-
assignee: params.assignee,
|
|
549
|
-
creator: params.creator,
|
|
550
|
-
mentioned: params.mentioned,
|
|
551
|
-
per_page: params.perPage ?? 30,
|
|
552
|
-
page: params.page ?? 1
|
|
553
|
-
});
|
|
554
|
-
return data.filter((issue) => !issue.pull_request).map((issue) => this.mapIssue(issue));
|
|
452
|
+
return void 0;
|
|
453
|
+
}
|
|
454
|
+
function accountFromRecord(record) {
|
|
455
|
+
const accountId = normalizeGitHubAccountId(
|
|
456
|
+
record.accountId ?? record.id ?? record.name
|
|
457
|
+
);
|
|
458
|
+
const role = normalizeRole(record.role) ?? normalizeRole(record.as) ?? normalizeRole(accountId);
|
|
459
|
+
const token = readRawField(record, [
|
|
460
|
+
"GITHUB_PAT",
|
|
461
|
+
"GITHUB_TOKEN",
|
|
462
|
+
"GITHUB_ACCESS_TOKEN",
|
|
463
|
+
"token",
|
|
464
|
+
"pat",
|
|
465
|
+
"accessToken",
|
|
466
|
+
"access"
|
|
467
|
+
]);
|
|
468
|
+
if (!accountId || !role || !token) {
|
|
469
|
+
return null;
|
|
555
470
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
471
|
+
return {
|
|
472
|
+
accountId,
|
|
473
|
+
role,
|
|
474
|
+
token,
|
|
475
|
+
label: nonEmptyString2(record.label ?? record.displayName)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
function addAccount(accounts, account) {
|
|
479
|
+
if (account) {
|
|
480
|
+
accounts.set(account.accountId, account);
|
|
562
481
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
482
|
+
}
|
|
483
|
+
function readGitHubAccounts(runtime) {
|
|
484
|
+
const accounts = /* @__PURE__ */ new Map();
|
|
485
|
+
const characterConfig = runtime.character?.settings?.github;
|
|
486
|
+
const characterAccounts = characterConfig?.accounts;
|
|
487
|
+
if (Array.isArray(characterAccounts)) {
|
|
488
|
+
for (const item of characterAccounts) {
|
|
489
|
+
if (item && typeof item === "object") {
|
|
490
|
+
addAccount(accounts, accountFromRecord(item));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
} else if (characterAccounts && typeof characterAccounts === "object") {
|
|
494
|
+
for (const [id, value] of Object.entries(
|
|
495
|
+
characterAccounts
|
|
496
|
+
)) {
|
|
497
|
+
if (value && typeof value === "object") {
|
|
498
|
+
addAccount(
|
|
499
|
+
accounts,
|
|
500
|
+
accountFromRecord({
|
|
501
|
+
...value,
|
|
502
|
+
accountId: value.accountId ?? id
|
|
503
|
+
})
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
569
507
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
owner,
|
|
575
|
-
repo,
|
|
576
|
-
title: params.title,
|
|
577
|
-
body: params.body,
|
|
578
|
-
head: params.head,
|
|
579
|
-
base: params.base,
|
|
580
|
-
draft: params.draft,
|
|
581
|
-
maintainer_can_modify: params.maintainerCanModify
|
|
582
|
-
});
|
|
583
|
-
return this.mapPullRequest(data);
|
|
584
|
-
}
|
|
585
|
-
async getPullRequest(params) {
|
|
586
|
-
const client = this.getClient();
|
|
587
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
588
|
-
const { data } = await client.pulls.get({
|
|
589
|
-
owner,
|
|
590
|
-
repo,
|
|
591
|
-
pull_number: params.pullNumber
|
|
592
|
-
});
|
|
593
|
-
return this.mapPullRequest(data);
|
|
594
|
-
}
|
|
595
|
-
async updatePullRequest(params) {
|
|
596
|
-
const client = this.getClient();
|
|
597
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
598
|
-
const { data } = await client.pulls.update({
|
|
599
|
-
owner,
|
|
600
|
-
repo,
|
|
601
|
-
pull_number: params.pullNumber,
|
|
602
|
-
title: params.title,
|
|
603
|
-
body: params.body,
|
|
604
|
-
state: params.state,
|
|
605
|
-
base: params.base,
|
|
606
|
-
maintainer_can_modify: params.maintainerCanModify
|
|
607
|
-
});
|
|
608
|
-
return this.mapPullRequest(data);
|
|
609
|
-
}
|
|
610
|
-
async listPullRequests(params) {
|
|
611
|
-
const client = this.getClient();
|
|
612
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
613
|
-
const { data } = await client.pulls.list({
|
|
614
|
-
owner,
|
|
615
|
-
repo,
|
|
616
|
-
state: params.state ?? "open",
|
|
617
|
-
head: params.head,
|
|
618
|
-
base: params.base,
|
|
619
|
-
sort: params.sort ?? "created",
|
|
620
|
-
direction: params.direction ?? "desc",
|
|
621
|
-
per_page: params.perPage ?? 30,
|
|
622
|
-
page: params.page ?? 1
|
|
623
|
-
});
|
|
624
|
-
return data.map((pr) => this.mapPullRequest(pr));
|
|
625
|
-
}
|
|
626
|
-
async mergePullRequest(params) {
|
|
627
|
-
const client = this.getClient();
|
|
628
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
629
|
-
const { data } = await client.pulls.merge({
|
|
630
|
-
owner,
|
|
631
|
-
repo,
|
|
632
|
-
pull_number: params.pullNumber,
|
|
633
|
-
commit_title: params.commitTitle,
|
|
634
|
-
commit_message: params.commitMessage,
|
|
635
|
-
merge_method: params.mergeMethod ?? "merge",
|
|
636
|
-
sha: params.sha
|
|
637
|
-
});
|
|
638
|
-
return {
|
|
639
|
-
sha: data.sha,
|
|
640
|
-
merged: data.merged,
|
|
641
|
-
message: data.message
|
|
642
|
-
};
|
|
508
|
+
for (const record of parseAccountsJson(
|
|
509
|
+
readSetting(runtime, "GITHUB_ACCOUNTS")
|
|
510
|
+
)) {
|
|
511
|
+
addAccount(accounts, accountFromRecord(record));
|
|
643
512
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
513
|
+
addAccount(
|
|
514
|
+
accounts,
|
|
515
|
+
legacyAccount(
|
|
516
|
+
runtime,
|
|
517
|
+
"user",
|
|
518
|
+
readSetting(runtime, "GITHUB_USER_ACCOUNT_ID") ?? DEFAULT_GITHUB_USER_ACCOUNT_ID,
|
|
519
|
+
"GITHUB_USER_PAT",
|
|
520
|
+
"ELIZA_E2E_GITHUB_USER_PAT"
|
|
521
|
+
)
|
|
522
|
+
);
|
|
523
|
+
addAccount(
|
|
524
|
+
accounts,
|
|
525
|
+
legacyAccount(
|
|
526
|
+
runtime,
|
|
527
|
+
"agent",
|
|
528
|
+
readSetting(runtime, "GITHUB_AGENT_ACCOUNT_ID") ?? DEFAULT_GITHUB_AGENT_ACCOUNT_ID,
|
|
529
|
+
"GITHUB_AGENT_PAT",
|
|
530
|
+
"ELIZA_E2E_GITHUB_AGENT_PAT"
|
|
531
|
+
)
|
|
532
|
+
);
|
|
533
|
+
return Array.from(accounts.values());
|
|
534
|
+
}
|
|
535
|
+
async function readGitHubAccountsWithConnectorCredentials(runtime) {
|
|
536
|
+
const accounts = /* @__PURE__ */ new Map();
|
|
537
|
+
for (const account of readGitHubAccounts(runtime)) {
|
|
538
|
+
accounts.set(account.accountId, account);
|
|
649
539
|
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
path: c.path,
|
|
662
|
-
line: c.line,
|
|
663
|
-
body: c.body,
|
|
664
|
-
side: c.side,
|
|
665
|
-
start_line: c.startLine,
|
|
666
|
-
start_side: c.startSide
|
|
667
|
-
}))
|
|
540
|
+
const connectorAccounts = await listConnectorAccounts(
|
|
541
|
+
runtime,
|
|
542
|
+
GITHUB_SERVICE_TYPE
|
|
543
|
+
);
|
|
544
|
+
for (const account of connectorAccounts) {
|
|
545
|
+
if (account.status !== "connected") continue;
|
|
546
|
+
const token = await loadConnectorOAuthAccessToken({
|
|
547
|
+
runtime,
|
|
548
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
549
|
+
accountId: account.id,
|
|
550
|
+
caller: "plugin-github"
|
|
668
551
|
});
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
owner,
|
|
676
|
-
repo,
|
|
677
|
-
pull_number: params.pullNumber
|
|
552
|
+
if (!token) continue;
|
|
553
|
+
accounts.set(account.id, {
|
|
554
|
+
accountId: account.id,
|
|
555
|
+
role: connectorRoleToIdentity(account.role),
|
|
556
|
+
token,
|
|
557
|
+
label: account.label ?? account.displayHandle
|
|
678
558
|
});
|
|
679
|
-
return data.map((r) => this.mapReview(r));
|
|
680
|
-
}
|
|
681
|
-
async createComment(params) {
|
|
682
|
-
const client = this.getClient();
|
|
683
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
684
|
-
const { data } = await client.issues.createComment({
|
|
685
|
-
owner,
|
|
686
|
-
repo,
|
|
687
|
-
issue_number: params.issueNumber,
|
|
688
|
-
body: params.body
|
|
689
|
-
});
|
|
690
|
-
return this.mapComment(data);
|
|
691
|
-
}
|
|
692
|
-
async listComments(params) {
|
|
693
|
-
const client = this.getClient();
|
|
694
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
695
|
-
const { data } = await client.issues.listComments({
|
|
696
|
-
owner,
|
|
697
|
-
repo,
|
|
698
|
-
issue_number: params.issueNumber,
|
|
699
|
-
per_page: params.perPage ?? 30,
|
|
700
|
-
page: params.page ?? 1
|
|
701
|
-
});
|
|
702
|
-
return data.map((c) => this.mapComment(c));
|
|
703
|
-
}
|
|
704
|
-
async createBranch(params) {
|
|
705
|
-
const client = this.getClient();
|
|
706
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
707
|
-
let sha;
|
|
708
|
-
if (params.fromRef.match(/^[0-9a-f]{40}$/i)) {
|
|
709
|
-
sha = params.fromRef;
|
|
710
|
-
} else {
|
|
711
|
-
const { data: refData } = await client.git.getRef({
|
|
712
|
-
owner,
|
|
713
|
-
repo,
|
|
714
|
-
ref: `heads/${params.fromRef}`
|
|
715
|
-
});
|
|
716
|
-
sha = refData.object.sha;
|
|
717
|
-
}
|
|
718
|
-
await client.git.createRef({
|
|
719
|
-
owner,
|
|
720
|
-
repo,
|
|
721
|
-
ref: `refs/heads/${params.branchName}`,
|
|
722
|
-
sha
|
|
723
|
-
});
|
|
724
|
-
return {
|
|
725
|
-
name: params.branchName,
|
|
726
|
-
sha,
|
|
727
|
-
protected: false
|
|
728
|
-
};
|
|
729
559
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
560
|
+
return Array.from(accounts.values());
|
|
561
|
+
}
|
|
562
|
+
function legacyAccount(runtime, role, accountId, primaryKey, fallbackKey) {
|
|
563
|
+
const token = readSetting(runtime, primaryKey) ?? readSetting(runtime, fallbackKey);
|
|
564
|
+
if (!token) return null;
|
|
565
|
+
return { accountId, role, token };
|
|
566
|
+
}
|
|
567
|
+
function connectorRoleToIdentity(role) {
|
|
568
|
+
return typeof role === "string" && role.toUpperCase() === "AGENT" ? "agent" : "user";
|
|
569
|
+
}
|
|
570
|
+
function resolveGitHubAccount(accounts, selection) {
|
|
571
|
+
if (selection.accountId) {
|
|
572
|
+
const exact = accounts.find(
|
|
573
|
+
(account) => account.accountId === selection.accountId
|
|
574
|
+
);
|
|
575
|
+
if (exact) return exact;
|
|
576
|
+
return null;
|
|
738
577
|
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return data.map((b) => ({
|
|
749
|
-
name: b.name,
|
|
750
|
-
sha: b.commit.sha,
|
|
751
|
-
protected: b.protected
|
|
752
|
-
}));
|
|
753
|
-
}
|
|
754
|
-
async getFile(params) {
|
|
755
|
-
const client = this.getClient();
|
|
756
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
757
|
-
const { data } = await client.repos.getContent({
|
|
758
|
-
owner,
|
|
759
|
-
repo,
|
|
760
|
-
path: params.path,
|
|
761
|
-
ref: params.branch
|
|
762
|
-
});
|
|
763
|
-
if (Array.isArray(data)) {
|
|
764
|
-
throw new FileNotFoundError(`${params.path} is a directory, not a file`, owner, repo);
|
|
765
|
-
}
|
|
766
|
-
if (data.type !== "file") {
|
|
767
|
-
throw new FileNotFoundError(`${params.path} is not a file`, owner, repo);
|
|
768
|
-
}
|
|
769
|
-
let content = "";
|
|
770
|
-
if ("content" in data && data.content) {
|
|
771
|
-
content = Buffer.from(data.content, "base64").toString("utf-8");
|
|
772
|
-
}
|
|
773
|
-
return {
|
|
774
|
-
name: data.name,
|
|
775
|
-
path: data.path,
|
|
776
|
-
content,
|
|
777
|
-
sha: data.sha,
|
|
778
|
-
size: data.size,
|
|
779
|
-
type: data.type,
|
|
780
|
-
encoding: "encoding" in data ? data.encoding ?? "base64" : "base64",
|
|
781
|
-
htmlUrl: data.html_url ?? "",
|
|
782
|
-
downloadUrl: data.download_url
|
|
783
|
-
};
|
|
578
|
+
const legacyId = defaultGitHubAccountIdForRole(selection.role);
|
|
579
|
+
return accounts.find((account) => account.accountId === legacyId) ?? accounts.find((account) => account.role === selection.role) ?? null;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// src/action-helpers.ts
|
|
583
|
+
function getClient(runtime, selection) {
|
|
584
|
+
const service = runtime.getService(GITHUB_SERVICE_TYPE);
|
|
585
|
+
if (!service) {
|
|
586
|
+
return null;
|
|
784
587
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
throw new FileNotFoundError(`${params.path} is a file, not a directory`, owner, repo);
|
|
796
|
-
}
|
|
797
|
-
return data.map((entry) => ({
|
|
798
|
-
name: entry.name,
|
|
799
|
-
path: entry.path,
|
|
800
|
-
sha: entry.sha,
|
|
801
|
-
size: entry.size,
|
|
802
|
-
type: entry.type,
|
|
803
|
-
htmlUrl: entry.html_url ?? "",
|
|
804
|
-
downloadUrl: entry.download_url
|
|
805
|
-
}));
|
|
806
|
-
}
|
|
807
|
-
async createCommit(params) {
|
|
808
|
-
const client = this.getClient();
|
|
809
|
-
const { owner, repo } = this.resolveRepoRef(params);
|
|
810
|
-
let parentSha = params.parentSha;
|
|
811
|
-
if (!parentSha) {
|
|
812
|
-
const { data: refData } = await client.git.getRef({
|
|
813
|
-
owner,
|
|
814
|
-
repo,
|
|
815
|
-
ref: `heads/${params.branch}`
|
|
816
|
-
});
|
|
817
|
-
parentSha = refData.object.sha;
|
|
818
|
-
}
|
|
819
|
-
const { data: parentCommit } = await client.git.getCommit({
|
|
820
|
-
owner,
|
|
821
|
-
repo,
|
|
822
|
-
commit_sha: parentSha
|
|
823
|
-
});
|
|
824
|
-
const treeItems = [];
|
|
825
|
-
for (const file of params.files) {
|
|
826
|
-
if (file.operation === "delete") {
|
|
827
|
-
continue;
|
|
828
|
-
}
|
|
829
|
-
const { data: blob } = await client.git.createBlob({
|
|
830
|
-
owner,
|
|
831
|
-
repo,
|
|
832
|
-
content: file.content,
|
|
833
|
-
encoding: file.encoding ?? "utf-8"
|
|
834
|
-
});
|
|
835
|
-
treeItems.push({
|
|
836
|
-
path: file.path,
|
|
837
|
-
mode: "100644",
|
|
838
|
-
type: "blob",
|
|
839
|
-
sha: blob.sha
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
const { data: newTree } = await client.git.createTree({
|
|
843
|
-
owner,
|
|
844
|
-
repo,
|
|
845
|
-
base_tree: parentCommit.tree.sha,
|
|
846
|
-
tree: treeItems
|
|
847
|
-
});
|
|
848
|
-
const { data: commit } = await client.git.createCommit({
|
|
849
|
-
owner,
|
|
850
|
-
repo,
|
|
851
|
-
message: params.message,
|
|
852
|
-
tree: newTree.sha,
|
|
853
|
-
parents: [parentSha],
|
|
854
|
-
author: params.authorName ? {
|
|
855
|
-
name: params.authorName,
|
|
856
|
-
email: params.authorEmail ?? `${params.authorName}@users.noreply.github.com`
|
|
857
|
-
} : undefined
|
|
858
|
-
});
|
|
859
|
-
await client.git.updateRef({
|
|
860
|
-
owner,
|
|
861
|
-
repo,
|
|
862
|
-
ref: `heads/${params.branch}`,
|
|
863
|
-
sha: commit.sha
|
|
864
|
-
});
|
|
865
|
-
return {
|
|
866
|
-
sha: commit.sha,
|
|
867
|
-
message: commit.message,
|
|
868
|
-
author: {
|
|
869
|
-
name: commit.author?.name ?? "Unknown",
|
|
870
|
-
email: commit.author?.email ?? "",
|
|
871
|
-
date: commit.author?.date ?? new Date().toISOString()
|
|
872
|
-
},
|
|
873
|
-
committer: {
|
|
874
|
-
name: commit.committer?.name ?? "Unknown",
|
|
875
|
-
email: commit.committer?.email ?? "",
|
|
876
|
-
date: commit.committer?.date ?? new Date().toISOString()
|
|
877
|
-
},
|
|
878
|
-
timestamp: commit.author?.date ?? new Date().toISOString(),
|
|
879
|
-
htmlUrl: commit.html_url,
|
|
880
|
-
parents: commit.parents.map((p) => p.sha)
|
|
881
|
-
};
|
|
588
|
+
return service.getOctokit(selection);
|
|
589
|
+
}
|
|
590
|
+
function requireString(options, key) {
|
|
591
|
+
const v = options?.[key];
|
|
592
|
+
return typeof v === "string" && v.length > 0 ? v : null;
|
|
593
|
+
}
|
|
594
|
+
function requireNumber(options, key) {
|
|
595
|
+
const v = options?.[key];
|
|
596
|
+
if (typeof v === "number" && Number.isInteger(v)) {
|
|
597
|
+
return v;
|
|
882
598
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
message: c.commit.message,
|
|
898
|
-
author: {
|
|
899
|
-
name: c.commit.author?.name ?? "Unknown",
|
|
900
|
-
email: c.commit.author?.email ?? "",
|
|
901
|
-
date: c.commit.author?.date ?? ""
|
|
902
|
-
},
|
|
903
|
-
committer: {
|
|
904
|
-
name: c.commit.committer?.name ?? "Unknown",
|
|
905
|
-
email: c.commit.committer?.email ?? "",
|
|
906
|
-
date: c.commit.committer?.date ?? ""
|
|
907
|
-
},
|
|
908
|
-
timestamp: c.commit.author?.date ?? "",
|
|
909
|
-
htmlUrl: c.html_url,
|
|
910
|
-
parents: c.parents.map((p) => p.sha)
|
|
911
|
-
}));
|
|
912
|
-
}
|
|
913
|
-
async getAuthenticatedUser() {
|
|
914
|
-
const client = this.getClient();
|
|
915
|
-
const { data } = await client.users.getAuthenticated();
|
|
916
|
-
return this.mapUser(data);
|
|
917
|
-
}
|
|
918
|
-
async getUser(username) {
|
|
919
|
-
const client = this.getClient();
|
|
920
|
-
const { data } = await client.users.getByUsername({ username });
|
|
921
|
-
return this.mapUser(data);
|
|
922
|
-
}
|
|
923
|
-
resolveRepoRef(params) {
|
|
924
|
-
const owner = params.owner ?? this._config?.owner;
|
|
925
|
-
const repo = params.repo ?? this._config?.repo;
|
|
926
|
-
if (!owner || !repo) {
|
|
927
|
-
throw new Error("Repository owner and name are required. Configure defaults or provide them explicitly.");
|
|
599
|
+
if (typeof v === "string" && /^\d+$/.test(v)) {
|
|
600
|
+
return Number(v);
|
|
601
|
+
}
|
|
602
|
+
return null;
|
|
603
|
+
}
|
|
604
|
+
function requireStringArray(options, key) {
|
|
605
|
+
const v = options?.[key];
|
|
606
|
+
if (!Array.isArray(v)) {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
const result = [];
|
|
610
|
+
for (const item of v) {
|
|
611
|
+
if (typeof item !== "string" || item.length === 0) {
|
|
612
|
+
return null;
|
|
928
613
|
}
|
|
929
|
-
|
|
614
|
+
result.push(item);
|
|
930
615
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
fullName: d.full_name,
|
|
938
|
-
owner: this.mapUser(d.owner),
|
|
939
|
-
description: d.description,
|
|
940
|
-
private: d.private,
|
|
941
|
-
fork: d.fork,
|
|
942
|
-
defaultBranch: d.default_branch,
|
|
943
|
-
language: d.language,
|
|
944
|
-
stargazersCount: d.stargazers_count,
|
|
945
|
-
forksCount: d.forks_count,
|
|
946
|
-
openIssuesCount: d.open_issues_count,
|
|
947
|
-
watchersCount: d.watchers_count,
|
|
948
|
-
htmlUrl: d.html_url,
|
|
949
|
-
cloneUrl: d.clone_url,
|
|
950
|
-
sshUrl: d.ssh_url,
|
|
951
|
-
createdAt: d.created_at,
|
|
952
|
-
updatedAt: d.updated_at,
|
|
953
|
-
pushedAt: d.pushed_at,
|
|
954
|
-
topics: d.topics ?? [],
|
|
955
|
-
license: license ? {
|
|
956
|
-
key: license.key,
|
|
957
|
-
name: license.name,
|
|
958
|
-
spdxId: license.spdx_id,
|
|
959
|
-
url: license.url
|
|
960
|
-
} : null
|
|
961
|
-
};
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
function optionalStringArray(options, key) {
|
|
619
|
+
const v = options?.[key];
|
|
620
|
+
if (v === void 0) {
|
|
621
|
+
return void 0;
|
|
962
622
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
avatarUrl: d.avatar_url,
|
|
970
|
-
htmlUrl: d.html_url,
|
|
971
|
-
type: d.type
|
|
972
|
-
};
|
|
623
|
+
return requireStringArray(options, key) ?? void 0;
|
|
624
|
+
}
|
|
625
|
+
function splitRepo(repo) {
|
|
626
|
+
const parts = repo.split("/");
|
|
627
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) {
|
|
628
|
+
return null;
|
|
973
629
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
htmlUrl: d.html_url,
|
|
990
|
-
comments: d.comments,
|
|
991
|
-
isPullRequest: !!d.pull_request
|
|
992
|
-
};
|
|
630
|
+
return { owner: parts[0], name: parts[1] };
|
|
631
|
+
}
|
|
632
|
+
function isConfirmed(options) {
|
|
633
|
+
return options?.confirmed === true;
|
|
634
|
+
}
|
|
635
|
+
function needsClientError(selection) {
|
|
636
|
+
const accountSuffix = selection.accountId ? ` accountId "${selection.accountId}"` : ` ${selection.role} account`;
|
|
637
|
+
return `GitHub${accountSuffix} token not configured (set GITHUB_ACCOUNTS or ${selection.role === "user" ? "GITHUB_USER_PAT" : "GITHUB_AGENT_PAT"})`;
|
|
638
|
+
}
|
|
639
|
+
function getServiceOrNull(runtime) {
|
|
640
|
+
return runtime.getService(GITHUB_SERVICE_TYPE);
|
|
641
|
+
}
|
|
642
|
+
function buildResolvedClient(runtime, selection) {
|
|
643
|
+
if (!getServiceOrNull(runtime)) {
|
|
644
|
+
return { error: "GitHub service not available" };
|
|
993
645
|
}
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
name: data,
|
|
999
|
-
color: "",
|
|
1000
|
-
description: null,
|
|
1001
|
-
default: false
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
const d = data;
|
|
1005
|
-
return {
|
|
1006
|
-
id: d.id,
|
|
1007
|
-
name: d.name,
|
|
1008
|
-
color: d.color,
|
|
1009
|
-
description: d.description,
|
|
1010
|
-
default: d.default
|
|
1011
|
-
};
|
|
646
|
+
const resolvedSelection = typeof selection === "string" ? { role: selection } : selection;
|
|
647
|
+
const client = getClient(runtime, resolvedSelection);
|
|
648
|
+
if (!client) {
|
|
649
|
+
return { error: needsClientError(resolvedSelection) };
|
|
1012
650
|
}
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
651
|
+
return {
|
|
652
|
+
client,
|
|
653
|
+
identity: resolvedSelection.role,
|
|
654
|
+
accountId: resolvedSelection.accountId
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function resolveAccountSelection(options, defaultIdentity) {
|
|
658
|
+
return resolveGitHubAccountSelection(options, defaultIdentity);
|
|
659
|
+
}
|
|
660
|
+
function describeSelection(selection) {
|
|
661
|
+
return selection.accountId ? `${selection.role} (${selection.accountId})` : selection.role;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/rate-limit.ts
|
|
665
|
+
function toErrorLike(value) {
|
|
666
|
+
if (typeof value !== "object" || value === null) {
|
|
667
|
+
return { message: String(value) };
|
|
1027
668
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
const baseRepo = base.repo;
|
|
1034
|
-
const headRepoOwner = headRepo?.owner;
|
|
1035
|
-
const baseRepoOwner = baseRepo?.owner;
|
|
1036
|
-
return {
|
|
1037
|
-
number: d.number,
|
|
1038
|
-
title: d.title,
|
|
1039
|
-
body: d.body,
|
|
1040
|
-
state: d.state,
|
|
1041
|
-
draft: d.draft ?? false,
|
|
1042
|
-
merged: d.merged ?? false,
|
|
1043
|
-
mergeable: d.mergeable,
|
|
1044
|
-
mergeableState: d.mergeable_state ?? "unknown",
|
|
1045
|
-
user: this.mapUser(d.user),
|
|
1046
|
-
head: {
|
|
1047
|
-
ref: head.ref,
|
|
1048
|
-
label: head.label,
|
|
1049
|
-
sha: head.sha,
|
|
1050
|
-
repo: headRepo ? { owner: headRepoOwner?.login, repo: headRepo.name } : null
|
|
1051
|
-
},
|
|
1052
|
-
base: {
|
|
1053
|
-
ref: base.ref,
|
|
1054
|
-
label: base.label,
|
|
1055
|
-
sha: base.sha,
|
|
1056
|
-
repo: baseRepo ? { owner: baseRepoOwner?.login, repo: baseRepo.name } : null
|
|
1057
|
-
},
|
|
1058
|
-
assignees: (d.assignees ?? []).map((a) => this.mapUser(a)),
|
|
1059
|
-
requestedReviewers: (d.requested_reviewers ?? []).map((r) => this.mapUser(r)),
|
|
1060
|
-
labels: (d.labels ?? []).map((l) => this.mapLabel(l)),
|
|
1061
|
-
milestone: d.milestone ? this.mapMilestone(d.milestone) : null,
|
|
1062
|
-
createdAt: d.created_at,
|
|
1063
|
-
updatedAt: d.updated_at,
|
|
1064
|
-
closedAt: d.closed_at,
|
|
1065
|
-
mergedAt: d.merged_at,
|
|
1066
|
-
htmlUrl: d.html_url,
|
|
1067
|
-
commits: d.commits ?? 0,
|
|
1068
|
-
additions: d.additions ?? 0,
|
|
1069
|
-
deletions: d.deletions ?? 0,
|
|
1070
|
-
changedFiles: d.changed_files ?? 0
|
|
1071
|
-
};
|
|
669
|
+
return value;
|
|
670
|
+
}
|
|
671
|
+
function headerNumber(headers, name) {
|
|
672
|
+
if (!headers) {
|
|
673
|
+
return null;
|
|
1072
674
|
}
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
return
|
|
1076
|
-
id: d.id,
|
|
1077
|
-
user: this.mapUser(d.user),
|
|
1078
|
-
body: d.body,
|
|
1079
|
-
state: d.state,
|
|
1080
|
-
commitId: d.commit_id,
|
|
1081
|
-
htmlUrl: d.html_url,
|
|
1082
|
-
submittedAt: d.submitted_at
|
|
1083
|
-
};
|
|
675
|
+
const raw = headers[name] ?? headers[name.toLowerCase()];
|
|
676
|
+
if (raw === void 0) {
|
|
677
|
+
return null;
|
|
1084
678
|
}
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
679
|
+
const num = typeof raw === "number" ? raw : Number(raw);
|
|
680
|
+
return Number.isFinite(num) ? num : null;
|
|
681
|
+
}
|
|
682
|
+
function inspectRateLimit(err) {
|
|
683
|
+
const e = toErrorLike(err);
|
|
684
|
+
const headers = e.response?.headers;
|
|
685
|
+
const remaining = headerNumber(headers, "x-ratelimit-remaining");
|
|
686
|
+
const resetSeconds = headerNumber(headers, "x-ratelimit-reset");
|
|
687
|
+
const isRateLimited = e.status === 403 && remaining === 0;
|
|
688
|
+
return {
|
|
689
|
+
isRateLimited,
|
|
690
|
+
remaining,
|
|
691
|
+
resetAtMs: resetSeconds === null ? null : resetSeconds * 1e3
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
function formatRateLimitMessage(details) {
|
|
695
|
+
if (!details.isRateLimited) {
|
|
696
|
+
return "GitHub request failed";
|
|
697
|
+
}
|
|
698
|
+
if (details.resetAtMs === null) {
|
|
699
|
+
return "GitHub rate limit exhausted";
|
|
700
|
+
}
|
|
701
|
+
const reset = new Date(details.resetAtMs).toISOString();
|
|
702
|
+
return `GitHub rate limit exhausted; resets at ${reset}`;
|
|
703
|
+
}
|
|
704
|
+
function errorMessage(err) {
|
|
705
|
+
if (err instanceof Error) {
|
|
706
|
+
return err.message;
|
|
1095
707
|
}
|
|
708
|
+
if (typeof err === "string") {
|
|
709
|
+
return err;
|
|
710
|
+
}
|
|
711
|
+
const e = toErrorLike(err);
|
|
712
|
+
return e.message ?? "unknown error";
|
|
1096
713
|
}
|
|
1097
714
|
|
|
1098
|
-
// actions/
|
|
1099
|
-
var
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
715
|
+
// src/actions/issue-op.ts
|
|
716
|
+
var SUPPORTED_OPS = /* @__PURE__ */ new Set([
|
|
717
|
+
"create",
|
|
718
|
+
"assign",
|
|
719
|
+
"close",
|
|
720
|
+
"reopen",
|
|
721
|
+
"comment",
|
|
722
|
+
"label"
|
|
723
|
+
]);
|
|
724
|
+
function parseOp(value) {
|
|
725
|
+
if (typeof value !== "string") return null;
|
|
726
|
+
return SUPPORTED_OPS.has(value) ? value : null;
|
|
727
|
+
}
|
|
728
|
+
function describeOp(op) {
|
|
729
|
+
switch (op) {
|
|
730
|
+
case "create":
|
|
731
|
+
return "create";
|
|
732
|
+
case "assign":
|
|
733
|
+
return "assign";
|
|
734
|
+
case "close":
|
|
735
|
+
return "close";
|
|
736
|
+
case "reopen":
|
|
737
|
+
return "reopen";
|
|
738
|
+
case "comment":
|
|
739
|
+
return "comment on";
|
|
740
|
+
case "label":
|
|
741
|
+
return "label";
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
async function runCreate(resolved, parts, repo, options, callback) {
|
|
745
|
+
const title = requireString(options, "title");
|
|
746
|
+
const body = requireString(options, "body");
|
|
747
|
+
const labels = optionalStringArray(options, "labels");
|
|
748
|
+
const assignees = optionalStringArray(options, "assignees");
|
|
749
|
+
if (!title) {
|
|
750
|
+
const err = "GITHUB_ISSUE_OP create requires title";
|
|
751
|
+
await callback?.({ text: err });
|
|
752
|
+
return { success: false, error: err };
|
|
753
|
+
}
|
|
754
|
+
const resp = await resolved.client.issues.create({
|
|
755
|
+
owner: parts.owner,
|
|
756
|
+
repo: parts.name,
|
|
757
|
+
title,
|
|
758
|
+
body: body ?? void 0,
|
|
759
|
+
labels,
|
|
760
|
+
assignees
|
|
761
|
+
});
|
|
762
|
+
await callback?.({
|
|
763
|
+
text: `Created issue ${repo}#${resp.data.number}: ${resp.data.html_url}`
|
|
764
|
+
});
|
|
765
|
+
return {
|
|
766
|
+
success: true,
|
|
767
|
+
data: {
|
|
768
|
+
op: "create",
|
|
769
|
+
number: resp.data.number,
|
|
770
|
+
url: resp.data.html_url
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
async function runAssign(resolved, parts, repo, options, callback) {
|
|
775
|
+
const number = requireNumber(options, "number");
|
|
776
|
+
const assignees = requireStringArray(options, "assignees");
|
|
777
|
+
if (!number || !assignees || assignees.length === 0) {
|
|
778
|
+
const err = "GITHUB_ISSUE_OP assign requires number (integer) and assignees (non-empty string[])";
|
|
779
|
+
await callback?.({ text: err });
|
|
780
|
+
return { success: false, error: err };
|
|
781
|
+
}
|
|
782
|
+
const resp = await resolved.client.issues.addAssignees({
|
|
783
|
+
owner: parts.owner,
|
|
784
|
+
repo: parts.name,
|
|
785
|
+
issue_number: number,
|
|
786
|
+
assignees
|
|
787
|
+
});
|
|
788
|
+
const actual = (resp.data.assignees ?? []).map((a) => a?.login).filter((x) => typeof x === "string");
|
|
789
|
+
await callback?.({
|
|
790
|
+
text: `Assigned [${actual.join(", ")}] to ${repo}#${number}`
|
|
791
|
+
});
|
|
792
|
+
return {
|
|
793
|
+
success: true,
|
|
794
|
+
data: { op: "assign", number, assignees: actual }
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
async function runStateChange(resolved, parts, repo, options, callback, target) {
|
|
798
|
+
const number = requireNumber(options, "number");
|
|
799
|
+
if (!number) {
|
|
800
|
+
const err = `GITHUB_ISSUE_OP ${target === "closed" ? "close" : "reopen"} requires number (integer)`;
|
|
801
|
+
await callback?.({ text: err });
|
|
802
|
+
return { success: false, error: err };
|
|
803
|
+
}
|
|
804
|
+
const resp = await resolved.client.issues.update({
|
|
805
|
+
owner: parts.owner,
|
|
806
|
+
repo: parts.name,
|
|
807
|
+
issue_number: number,
|
|
808
|
+
state: target
|
|
809
|
+
});
|
|
810
|
+
const verb = target === "closed" ? "Closed" : "Reopened";
|
|
811
|
+
await callback?.({ text: `${verb} ${repo}#${number}: ${resp.data.title}` });
|
|
812
|
+
return {
|
|
813
|
+
success: true,
|
|
814
|
+
data: {
|
|
815
|
+
op: target === "closed" ? "close" : "reopen",
|
|
816
|
+
number,
|
|
817
|
+
title: resp.data.title
|
|
818
|
+
}
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
async function runComment(resolved, parts, repo, options, callback) {
|
|
822
|
+
const number = requireNumber(options, "number");
|
|
823
|
+
const body = requireString(options, "body");
|
|
824
|
+
if (!number || !body) {
|
|
825
|
+
const err = "GITHUB_ISSUE_OP comment requires number (integer) and body";
|
|
826
|
+
await callback?.({ text: err });
|
|
827
|
+
return { success: false, error: err };
|
|
828
|
+
}
|
|
829
|
+
const resp = await resolved.client.issues.createComment({
|
|
830
|
+
owner: parts.owner,
|
|
831
|
+
repo: parts.name,
|
|
832
|
+
issue_number: number,
|
|
833
|
+
body
|
|
834
|
+
});
|
|
835
|
+
await callback?.({
|
|
836
|
+
text: `Commented on ${repo}#${number}: ${resp.data.html_url}`
|
|
837
|
+
});
|
|
838
|
+
return {
|
|
839
|
+
success: true,
|
|
840
|
+
data: {
|
|
841
|
+
op: "comment",
|
|
842
|
+
number,
|
|
843
|
+
commentId: resp.data.id,
|
|
844
|
+
url: resp.data.html_url
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
async function runLabel(resolved, parts, repo, options, callback) {
|
|
849
|
+
const number = requireNumber(options, "number");
|
|
850
|
+
const labels = requireStringArray(options, "labels");
|
|
851
|
+
if (!number || !labels || labels.length === 0) {
|
|
852
|
+
const err = "GITHUB_ISSUE_OP label requires number (integer) and labels (non-empty string[])";
|
|
853
|
+
await callback?.({ text: err });
|
|
854
|
+
return { success: false, error: err };
|
|
855
|
+
}
|
|
856
|
+
const resp = await resolved.client.issues.addLabels({
|
|
857
|
+
owner: parts.owner,
|
|
858
|
+
repo: parts.name,
|
|
859
|
+
issue_number: number,
|
|
860
|
+
labels
|
|
861
|
+
});
|
|
862
|
+
const applied = (resp.data ?? []).map((label) => typeof label === "string" ? label : label?.name ?? null).filter((x) => typeof x === "string");
|
|
863
|
+
await callback?.({
|
|
864
|
+
text: `Applied labels [${applied.join(", ")}] to ${repo}#${number}`
|
|
865
|
+
});
|
|
866
|
+
return {
|
|
867
|
+
success: true,
|
|
868
|
+
data: { op: "label", number, labels: applied }
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function buildPreview(op, repo, identity, options) {
|
|
872
|
+
const number = requireNumber(options, "number");
|
|
873
|
+
const title = requireString(options, "title");
|
|
874
|
+
const body = requireString(options, "body");
|
|
875
|
+
const assignees = optionalStringArray(options, "assignees");
|
|
876
|
+
const labels = optionalStringArray(options, "labels");
|
|
877
|
+
const head = `About to ${describeOp(op)} ${repo}`;
|
|
878
|
+
const target = number ? `#${number}` : "";
|
|
879
|
+
const detail = (() => {
|
|
880
|
+
switch (op) {
|
|
881
|
+
case "create":
|
|
882
|
+
return ` issue: "${title ?? "(no title)"}"${labels ? ` [labels: ${labels.join(", ")}]` : ""}${assignees ? ` [assignees: ${assignees.join(", ")}]` : ""}`;
|
|
883
|
+
case "assign":
|
|
884
|
+
return ` with [${assignees?.join(", ") ?? ""}]`;
|
|
885
|
+
case "label":
|
|
886
|
+
return ` with [${labels?.join(", ") ?? ""}]`;
|
|
887
|
+
case "comment":
|
|
888
|
+
return body ? ` body: "${body.slice(0, 120)}"` : "";
|
|
889
|
+
default:
|
|
890
|
+
return "";
|
|
891
|
+
}
|
|
892
|
+
})();
|
|
893
|
+
return `${head}${target}${detail} as ${identity}. Re-invoke with confirmed: true to proceed.`;
|
|
894
|
+
}
|
|
895
|
+
var issueOpAction = {
|
|
896
|
+
name: GitHubActions.GITHUB_ISSUE_OP,
|
|
897
|
+
contexts: ["code", "tasks", "connectors", "automation"],
|
|
898
|
+
contextGate: { anyOf: ["code", "tasks", "connectors", "automation"] },
|
|
899
|
+
roleGate: { minRole: "USER" },
|
|
900
|
+
similes: [
|
|
901
|
+
"CREATE_ISSUE",
|
|
902
|
+
"OPEN_ISSUE",
|
|
903
|
+
"FILE_ISSUE",
|
|
904
|
+
"GITHUB_CREATE_ISSUE",
|
|
905
|
+
"ASSIGN_ISSUE",
|
|
906
|
+
"ASSIGN_GITHUB_ISSUE",
|
|
907
|
+
"ADD_ASSIGNEE",
|
|
908
|
+
"CLOSE_ISSUE",
|
|
909
|
+
"REOPEN_ISSUE",
|
|
910
|
+
"COMMENT_ISSUE",
|
|
911
|
+
"ADD_ISSUE_COMMENT",
|
|
912
|
+
"LABEL_ISSUE",
|
|
913
|
+
"ADD_ISSUE_LABEL",
|
|
914
|
+
"MANAGE_ISSUES"
|
|
915
|
+
],
|
|
916
|
+
description: "Single router for GitHub issue ops: create, assign, close, reopen, comment, label. Requires confirmed:true.",
|
|
917
|
+
descriptionCompressed: "GitHub issue ops: create, assign, close, reopen, comment, label.",
|
|
918
|
+
parameters: [
|
|
1203
919
|
{
|
|
1204
|
-
name: "
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
}
|
|
920
|
+
name: "subaction",
|
|
921
|
+
description: "Issue operation: create, assign, close, reopen, comment, or label.",
|
|
922
|
+
required: true,
|
|
923
|
+
schema: { type: "string", enum: [...SUPPORTED_OPS] }
|
|
1208
924
|
},
|
|
1209
925
|
{
|
|
1210
|
-
name: "
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
}
|
|
1216
|
-
],
|
|
1217
|
-
[
|
|
926
|
+
name: "repo",
|
|
927
|
+
description: "Repository in owner/name form.",
|
|
928
|
+
required: true,
|
|
929
|
+
schema: { type: "string" }
|
|
930
|
+
},
|
|
1218
931
|
{
|
|
1219
|
-
name: "
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
}
|
|
932
|
+
name: "number",
|
|
933
|
+
description: "Issue number for existing-issue operations.",
|
|
934
|
+
required: false,
|
|
935
|
+
schema: { type: "number" }
|
|
1223
936
|
},
|
|
1224
937
|
{
|
|
1225
|
-
name: "
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
]
|
|
1232
|
-
];
|
|
1233
|
-
var createCommentAction = {
|
|
1234
|
-
name: "CREATE_GITHUB_COMMENT",
|
|
1235
|
-
similes: spec2.similes ? [...spec2.similes] : [],
|
|
1236
|
-
description: spec2.description,
|
|
1237
|
-
validate: async (runtime, message, _state) => {
|
|
1238
|
-
const service = runtime.getService(GITHUB_SERVICE_NAME);
|
|
1239
|
-
if (!service) {
|
|
1240
|
-
return false;
|
|
1241
|
-
}
|
|
1242
|
-
const text = message.content.text?.toLowerCase() ?? "";
|
|
1243
|
-
return text.includes("comment") || text.includes("reply") || text.includes("respond");
|
|
1244
|
-
},
|
|
1245
|
-
handler: async (runtime, message, state, _options, callback) => {
|
|
1246
|
-
const service = runtime.getService(GITHUB_SERVICE_NAME);
|
|
1247
|
-
if (!service) {
|
|
1248
|
-
logger3.error("GitHub service not available");
|
|
1249
|
-
if (callback) {
|
|
1250
|
-
await callback({
|
|
1251
|
-
text: "GitHub service is not available. Please ensure the plugin is properly configured."
|
|
1252
|
-
});
|
|
1253
|
-
}
|
|
1254
|
-
return { success: false };
|
|
1255
|
-
}
|
|
1256
|
-
try {
|
|
1257
|
-
const content = message.content;
|
|
1258
|
-
const text = content.text ?? "";
|
|
1259
|
-
const params = {
|
|
1260
|
-
owner: state?.owner ?? service.getConfig().owner ?? "",
|
|
1261
|
-
repo: state?.repo ?? service.getConfig().repo ?? "",
|
|
1262
|
-
issueNumber: state?.issueNumber ?? 0,
|
|
1263
|
-
body: state?.body ?? text
|
|
1264
|
-
};
|
|
1265
|
-
const validation = createCommentSchema.safeParse(params);
|
|
1266
|
-
if (!validation.success) {
|
|
1267
|
-
const errors = formatZodErrors(validation.error);
|
|
1268
|
-
logger3.error(`Invalid comment parameters: ${errors}`);
|
|
1269
|
-
if (callback) {
|
|
1270
|
-
await callback({
|
|
1271
|
-
text: `I couldn't create the comment due to missing information: ${errors}`
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
return { success: false };
|
|
1275
|
-
}
|
|
1276
|
-
const comment = await service.createComment(params);
|
|
1277
|
-
logger3.info(`Created comment on #${params.issueNumber}`);
|
|
1278
|
-
if (callback) {
|
|
1279
|
-
await callback({
|
|
1280
|
-
text: `Added comment to #${params.issueNumber}.
|
|
1281
|
-
|
|
1282
|
-
View it at: ${comment.htmlUrl}`
|
|
1283
|
-
});
|
|
1284
|
-
}
|
|
1285
|
-
return { success: true };
|
|
1286
|
-
} catch (error) {
|
|
1287
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1288
|
-
logger3.error(`Failed to create comment: ${errorMessage}`);
|
|
1289
|
-
if (callback) {
|
|
1290
|
-
await callback({
|
|
1291
|
-
text: `Failed to create the comment: ${errorMessage}`
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
return { success: false };
|
|
1295
|
-
}
|
|
1296
|
-
},
|
|
1297
|
-
examples: examples2
|
|
1298
|
-
};
|
|
1299
|
-
// actions/createIssue.ts
|
|
1300
|
-
import {
|
|
1301
|
-
logger as logger4
|
|
1302
|
-
} from "@elizaos/core";
|
|
1303
|
-
var spec3 = requireActionSpec("CREATE_ISSUE");
|
|
1304
|
-
var examples3 = [
|
|
1305
|
-
[
|
|
938
|
+
name: "title",
|
|
939
|
+
description: "Issue title for create.",
|
|
940
|
+
required: false,
|
|
941
|
+
schema: { type: "string" }
|
|
942
|
+
},
|
|
1306
943
|
{
|
|
1307
|
-
name: "
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
944
|
+
name: "body",
|
|
945
|
+
description: "Issue body or comment body.",
|
|
946
|
+
required: false,
|
|
947
|
+
schema: { type: "string" }
|
|
1311
948
|
},
|
|
1312
949
|
{
|
|
1313
|
-
name: "
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
}
|
|
1319
|
-
],
|
|
1320
|
-
[
|
|
950
|
+
name: "assignees",
|
|
951
|
+
description: "GitHub usernames to assign.",
|
|
952
|
+
required: false,
|
|
953
|
+
schema: { type: "array", items: { type: "string" } }
|
|
954
|
+
},
|
|
1321
955
|
{
|
|
1322
|
-
name: "
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
}
|
|
956
|
+
name: "labels",
|
|
957
|
+
description: "Labels to apply on create or label.",
|
|
958
|
+
required: false,
|
|
959
|
+
schema: { type: "array", items: { type: "string" } }
|
|
1326
960
|
},
|
|
1327
961
|
{
|
|
1328
|
-
name: "
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
962
|
+
name: "as",
|
|
963
|
+
description: "Identity to use: agent or user.",
|
|
964
|
+
required: false,
|
|
965
|
+
schema: { type: "string", enum: ["agent", "user"], default: "agent" }
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: "accountId",
|
|
969
|
+
description: "Optional GitHub account id from GITHUB_ACCOUNTS. Defaults by role.",
|
|
970
|
+
required: false,
|
|
971
|
+
schema: { type: "string" }
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
name: "confirmed",
|
|
975
|
+
description: "Must be true to perform the write operation.",
|
|
976
|
+
required: false,
|
|
977
|
+
schema: { type: "boolean", default: false }
|
|
1344
978
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
979
|
+
],
|
|
980
|
+
validate: async (runtime, _message) => {
|
|
981
|
+
const r = buildResolvedClient(runtime, "agent");
|
|
982
|
+
return !("error" in r);
|
|
1347
983
|
},
|
|
1348
|
-
handler: async (runtime,
|
|
1349
|
-
const
|
|
1350
|
-
if (!
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
984
|
+
handler: async (runtime, _message, _state, options, callback) => {
|
|
985
|
+
const op = parseOp(options?.op);
|
|
986
|
+
if (!op) {
|
|
987
|
+
const err = "GITHUB_ISSUE_OP requires op (create|assign|close|reopen|comment|label)";
|
|
988
|
+
await callback?.({ text: err });
|
|
989
|
+
return { success: false, error: err };
|
|
990
|
+
}
|
|
991
|
+
const selection = resolveAccountSelection(options, "agent");
|
|
992
|
+
const repo = requireString(options, "repo");
|
|
993
|
+
if (!repo) {
|
|
994
|
+
const err = "GITHUB_ISSUE_OP requires repo (owner/name)";
|
|
995
|
+
await callback?.({ text: err });
|
|
996
|
+
return { success: false, error: err };
|
|
997
|
+
}
|
|
998
|
+
const parts = splitRepo(repo);
|
|
999
|
+
if (!parts) {
|
|
1000
|
+
const err = `Invalid repo "${repo}" \u2014 expected "owner/name"`;
|
|
1001
|
+
await callback?.({ text: err });
|
|
1002
|
+
return { success: false, error: err };
|
|
1003
|
+
}
|
|
1004
|
+
if (!isConfirmed(options)) {
|
|
1005
|
+
const preview = buildPreview(op, repo, describeSelection(selection), options);
|
|
1006
|
+
await callback?.({ text: preview });
|
|
1007
|
+
return { success: false, requiresConfirmation: true, preview };
|
|
1008
|
+
}
|
|
1009
|
+
const resolved = buildResolvedClient(runtime, selection);
|
|
1010
|
+
if ("error" in resolved) {
|
|
1011
|
+
await callback?.({ text: resolved.error });
|
|
1012
|
+
return { success: false, error: resolved.error };
|
|
1358
1013
|
}
|
|
1359
1014
|
try {
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
await
|
|
1376
|
-
|
|
1377
|
-
|
|
1015
|
+
switch (op) {
|
|
1016
|
+
case "create":
|
|
1017
|
+
return await runCreate(resolved, parts, repo, options, callback);
|
|
1018
|
+
case "assign":
|
|
1019
|
+
return await runAssign(resolved, parts, repo, options, callback);
|
|
1020
|
+
case "close":
|
|
1021
|
+
return await runStateChange(
|
|
1022
|
+
resolved,
|
|
1023
|
+
parts,
|
|
1024
|
+
repo,
|
|
1025
|
+
options,
|
|
1026
|
+
callback,
|
|
1027
|
+
"closed"
|
|
1028
|
+
);
|
|
1029
|
+
case "reopen":
|
|
1030
|
+
return await runStateChange(
|
|
1031
|
+
resolved,
|
|
1032
|
+
parts,
|
|
1033
|
+
repo,
|
|
1034
|
+
options,
|
|
1035
|
+
callback,
|
|
1036
|
+
"open"
|
|
1037
|
+
);
|
|
1038
|
+
case "comment":
|
|
1039
|
+
return await runComment(resolved, parts, repo, options, callback);
|
|
1040
|
+
case "label":
|
|
1041
|
+
return await runLabel(resolved, parts, repo, options, callback);
|
|
1042
|
+
}
|
|
1043
|
+
} catch (err) {
|
|
1044
|
+
const rl = inspectRateLimit(err);
|
|
1045
|
+
const message = rl.isRateLimited ? formatRateLimitMessage(rl) : `GITHUB_ISSUE_OP ${op} failed: ${errorMessage(err)}`;
|
|
1046
|
+
logger.warn({ message }, "[GitHub:GITHUB_ISSUE_OP]");
|
|
1047
|
+
await callback?.({ text: message });
|
|
1048
|
+
return { success: false, error: message };
|
|
1049
|
+
}
|
|
1050
|
+
},
|
|
1051
|
+
examples: [
|
|
1052
|
+
[
|
|
1053
|
+
{
|
|
1054
|
+
name: "{{user1}}",
|
|
1055
|
+
content: {
|
|
1056
|
+
text: "Open an issue in elizaOS/eliza titled 'Docs gap'"
|
|
1057
|
+
}
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
name: "{{agentName}}",
|
|
1061
|
+
content: {
|
|
1062
|
+
text: "Created issue elizaOS/eliza#101"
|
|
1378
1063
|
}
|
|
1379
|
-
return { success: false };
|
|
1380
|
-
}
|
|
1381
|
-
const issue = await service.createIssue(params);
|
|
1382
|
-
logger4.info(`Created issue #${issue.number}: ${issue.title}`);
|
|
1383
|
-
if (callback) {
|
|
1384
|
-
await callback({
|
|
1385
|
-
text: `Created issue #${issue.number}: "${issue.title}"
|
|
1386
|
-
|
|
1387
|
-
View it at: ${issue.htmlUrl}`
|
|
1388
|
-
});
|
|
1389
1064
|
}
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1065
|
+
],
|
|
1066
|
+
[
|
|
1067
|
+
{
|
|
1068
|
+
name: "{{user1}}",
|
|
1069
|
+
content: {
|
|
1070
|
+
text: "Close issue elizaOS/eliza#42"
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
{
|
|
1074
|
+
name: "{{agentName}}",
|
|
1075
|
+
content: {
|
|
1076
|
+
text: "Closed elizaOS/eliza#42"
|
|
1077
|
+
}
|
|
1398
1078
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
},
|
|
1402
|
-
examples: examples3
|
|
1079
|
+
]
|
|
1080
|
+
]
|
|
1403
1081
|
};
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1082
|
+
|
|
1083
|
+
// src/actions/notification-triage.ts
|
|
1084
|
+
import { logger as logger2 } from "@elizaos/core";
|
|
1085
|
+
var REASON_SCORES = {
|
|
1086
|
+
security_advisory: 100,
|
|
1087
|
+
team_mention: 70,
|
|
1088
|
+
author: 60,
|
|
1089
|
+
mention: 55,
|
|
1090
|
+
assign: 50,
|
|
1091
|
+
review_requested: 80,
|
|
1092
|
+
state_change: 20,
|
|
1093
|
+
comment: 30,
|
|
1094
|
+
subscribed: 10,
|
|
1095
|
+
manual: 15,
|
|
1096
|
+
invitation: 40,
|
|
1097
|
+
ci_activity: 25
|
|
1098
|
+
};
|
|
1099
|
+
var SUBJECT_TYPE_SCORES = {
|
|
1100
|
+
PullRequest: 20,
|
|
1101
|
+
Issue: 15,
|
|
1102
|
+
Release: 10,
|
|
1103
|
+
Commit: 5,
|
|
1104
|
+
Discussion: 8
|
|
1105
|
+
};
|
|
1106
|
+
var NOTIFICATION_TRIAGE_LIMIT = 25;
|
|
1107
|
+
function scoreNotification(params) {
|
|
1108
|
+
const base = REASON_SCORES[params.reason] ?? 10;
|
|
1109
|
+
const subject = SUBJECT_TYPE_SCORES[params.subjectType] ?? 0;
|
|
1110
|
+
let freshness = 0;
|
|
1111
|
+
if (params.repoPushedAtMs !== null) {
|
|
1112
|
+
const ageHours = (params.nowMs - params.repoPushedAtMs) / (1e3 * 60 * 60);
|
|
1113
|
+
if (ageHours < 1) freshness = 20;
|
|
1114
|
+
else if (ageHours < 6) freshness = 15;
|
|
1115
|
+
else if (ageHours < 24) freshness = 10;
|
|
1116
|
+
else if (ageHours < 24 * 7) freshness = 5;
|
|
1117
|
+
}
|
|
1118
|
+
return base + subject + freshness;
|
|
1119
|
+
}
|
|
1120
|
+
var notificationTriageAction = {
|
|
1121
|
+
name: GitHubActions.GITHUB_NOTIFICATION_TRIAGE,
|
|
1122
|
+
contexts: ["code", "tasks", "connectors", "automation"],
|
|
1123
|
+
contextGate: { anyOf: ["code", "tasks", "connectors", "automation"] },
|
|
1124
|
+
roleGate: { minRole: "USER" },
|
|
1125
|
+
similes: ["TRIAGE_GITHUB_NOTIFICATIONS", "GITHUB_INBOX"],
|
|
1126
|
+
description: "Returns unread GitHub notifications sorted by a priority score derived from reason, subject type, and repo freshness.",
|
|
1127
|
+
descriptionCompressed: "return unread GitHub notification sort priority score derive reason, subject type, repo freshness",
|
|
1128
|
+
parameters: [
|
|
1411
1129
|
{
|
|
1412
|
-
name:
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
}
|
|
1130
|
+
name: "as",
|
|
1131
|
+
description: "Identity to use when reading notifications: user or agent.",
|
|
1132
|
+
required: false,
|
|
1133
|
+
schema: { type: "string", enum: ["user", "agent"], default: "user" }
|
|
1416
1134
|
},
|
|
1417
1135
|
{
|
|
1418
|
-
name: "
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
}
|
|
1136
|
+
name: "accountId",
|
|
1137
|
+
description: "Optional GitHub account id from GITHUB_ACCOUNTS. Defaults by role.",
|
|
1138
|
+
required: false,
|
|
1139
|
+
schema: { type: "string" }
|
|
1423
1140
|
}
|
|
1424
1141
|
],
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
content: {
|
|
1429
|
-
text: "Open a PR to merge my-branch into develop"
|
|
1430
|
-
}
|
|
1431
|
-
},
|
|
1432
|
-
{
|
|
1433
|
-
name: "{{agent}}",
|
|
1434
|
-
content: {
|
|
1435
|
-
text: "Creating a pull request to merge my-branch into develop.",
|
|
1436
|
-
actions: ["CREATE_GITHUB_PULL_REQUEST"]
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
]
|
|
1440
|
-
];
|
|
1441
|
-
var createPullRequestAction = {
|
|
1442
|
-
name: "CREATE_GITHUB_PULL_REQUEST",
|
|
1443
|
-
similes: spec4.similes ? [...spec4.similes] : [],
|
|
1444
|
-
description: spec4.description,
|
|
1445
|
-
validate: async (runtime, message, _state) => {
|
|
1446
|
-
const service = runtime.getService(GITHUB_SERVICE_NAME);
|
|
1447
|
-
if (!service) {
|
|
1448
|
-
return false;
|
|
1449
|
-
}
|
|
1450
|
-
const text = message.content.text?.toLowerCase() ?? "";
|
|
1451
|
-
return text.includes("pull request") || text.includes("pr") || text.includes("merge");
|
|
1142
|
+
validate: async (runtime, _message) => {
|
|
1143
|
+
const r = buildResolvedClient(runtime, "user");
|
|
1144
|
+
return !("error" in r);
|
|
1452
1145
|
},
|
|
1453
|
-
handler: async (runtime,
|
|
1454
|
-
const
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
text: "GitHub service is not available. Please ensure the plugin is properly configured."
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
return { success: false };
|
|
1146
|
+
handler: async (runtime, _message, _state, options, callback) => {
|
|
1147
|
+
const selection = resolveAccountSelection(options, "user");
|
|
1148
|
+
const resolved = buildResolvedClient(runtime, selection);
|
|
1149
|
+
if ("error" in resolved) {
|
|
1150
|
+
await callback?.({ text: resolved.error });
|
|
1151
|
+
return { success: false, error: resolved.error };
|
|
1463
1152
|
}
|
|
1464
1153
|
try {
|
|
1465
|
-
const
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1154
|
+
const resp = await resolved.client.activity.listNotificationsForAuthenticatedUser({
|
|
1155
|
+
all: false,
|
|
1156
|
+
per_page: 50
|
|
1157
|
+
});
|
|
1158
|
+
const notifications = resp.data;
|
|
1159
|
+
const nowMs = Date.now();
|
|
1160
|
+
const triaged = notifications.map((n) => {
|
|
1161
|
+
const repoPushedAt = n.repository?.pushed_at ?? null;
|
|
1162
|
+
const repoPushedAtMs = typeof repoPushedAt === "string" ? Date.parse(repoPushedAt) : null;
|
|
1163
|
+
const reason = typeof n.reason === "string" ? n.reason : "unknown";
|
|
1164
|
+
const subjectType = typeof n.subject?.type === "string" ? n.subject.type : "Unknown";
|
|
1165
|
+
return {
|
|
1166
|
+
id: n.id,
|
|
1167
|
+
reason,
|
|
1168
|
+
repo: n.repository?.full_name ?? "unknown",
|
|
1169
|
+
title: n.subject?.title ?? "(untitled)",
|
|
1170
|
+
subjectType,
|
|
1171
|
+
url: n.subject?.url ?? null,
|
|
1172
|
+
updatedAt: n.updated_at,
|
|
1173
|
+
score: scoreNotification({
|
|
1174
|
+
reason,
|
|
1175
|
+
subjectType,
|
|
1176
|
+
repoPushedAtMs: repoPushedAtMs !== null && Number.isFinite(repoPushedAtMs) ? repoPushedAtMs : null,
|
|
1177
|
+
nowMs
|
|
1178
|
+
})
|
|
1179
|
+
};
|
|
1180
|
+
});
|
|
1181
|
+
triaged.sort((a, b) => b.score - a.score);
|
|
1182
|
+
const boundedTriaged = triaged.slice(0, NOTIFICATION_TRIAGE_LIMIT);
|
|
1183
|
+
await callback?.({
|
|
1184
|
+
text: `Triaged ${boundedTriaged.length} unread notification(s)`
|
|
1185
|
+
});
|
|
1186
|
+
return {
|
|
1187
|
+
success: true,
|
|
1188
|
+
data: {
|
|
1189
|
+
notifications: boundedTriaged,
|
|
1190
|
+
notificationLimit: NOTIFICATION_TRIAGE_LIMIT,
|
|
1191
|
+
totalUnread: triaged.length
|
|
1484
1192
|
}
|
|
1485
|
-
|
|
1193
|
+
};
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
const rl = inspectRateLimit(err);
|
|
1196
|
+
const message = rl.isRateLimited ? formatRateLimitMessage(rl) : `GITHUB_NOTIFICATION_TRIAGE failed: ${errorMessage(err)}`;
|
|
1197
|
+
logger2.warn({ message }, "[GitHub:GITHUB_NOTIFICATION_TRIAGE]");
|
|
1198
|
+
await callback?.({ text: message });
|
|
1199
|
+
return { success: false, error: message };
|
|
1200
|
+
}
|
|
1201
|
+
},
|
|
1202
|
+
examples: [
|
|
1203
|
+
[
|
|
1204
|
+
{
|
|
1205
|
+
name: "{{user1}}",
|
|
1206
|
+
content: { text: "What's in my GitHub inbox?" }
|
|
1207
|
+
},
|
|
1208
|
+
{
|
|
1209
|
+
name: "{{agentName}}",
|
|
1210
|
+
content: { text: "Triaged 7 unread notification(s)" }
|
|
1486
1211
|
}
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
await callback({
|
|
1491
|
-
text: `Created pull request #${pr.number}: "${pr.title}"
|
|
1492
|
-
|
|
1493
|
-
From: ${pr.head.ref}
|
|
1494
|
-
To: ${pr.base.ref}
|
|
1212
|
+
]
|
|
1213
|
+
]
|
|
1214
|
+
};
|
|
1495
1215
|
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1216
|
+
// src/actions/pr-op.ts
|
|
1217
|
+
import { logger as logger3 } from "@elizaos/core";
|
|
1218
|
+
var SUPPORTED_OPS2 = /* @__PURE__ */ new Set(["list", "review"]);
|
|
1219
|
+
var EVENT_BY_ACTION = {
|
|
1220
|
+
approve: "APPROVE",
|
|
1221
|
+
"request-changes": "REQUEST_CHANGES",
|
|
1222
|
+
comment: "COMMENT"
|
|
1223
|
+
};
|
|
1224
|
+
function parseOp2(value) {
|
|
1225
|
+
if (typeof value !== "string") return null;
|
|
1226
|
+
return SUPPORTED_OPS2.has(value) ? value : null;
|
|
1227
|
+
}
|
|
1228
|
+
function parseState(value) {
|
|
1229
|
+
return value === "closed" || value === "all" ? value : "open";
|
|
1230
|
+
}
|
|
1231
|
+
function parseReviewAction(value) {
|
|
1232
|
+
return value === "approve" || value === "request-changes" || value === "comment" ? value : null;
|
|
1233
|
+
}
|
|
1234
|
+
async function runList(runtime, options, callback) {
|
|
1235
|
+
const selection = resolveAccountSelection(options, "agent");
|
|
1236
|
+
const resolved = buildResolvedClient(runtime, selection);
|
|
1237
|
+
if ("error" in resolved) {
|
|
1238
|
+
await callback?.({ text: resolved.error });
|
|
1239
|
+
return { success: false, error: resolved.error };
|
|
1240
|
+
}
|
|
1241
|
+
const state = parseState(options?.state);
|
|
1242
|
+
const author = requireString(options, "author");
|
|
1243
|
+
const repo = requireString(options, "repo");
|
|
1244
|
+
const prs = [];
|
|
1245
|
+
if (repo) {
|
|
1246
|
+
const parts = splitRepo(repo);
|
|
1247
|
+
if (!parts) {
|
|
1248
|
+
const err = `Invalid repo "${repo}" \u2014 expected "owner/name"`;
|
|
1249
|
+
await callback?.({ text: err });
|
|
1250
|
+
return { success: false, error: err };
|
|
1251
|
+
}
|
|
1252
|
+
const resp = await resolved.client.pulls.list({
|
|
1253
|
+
owner: parts.owner,
|
|
1254
|
+
repo: parts.name,
|
|
1255
|
+
state,
|
|
1256
|
+
per_page: 100
|
|
1257
|
+
});
|
|
1258
|
+
for (const pr of resp.data) {
|
|
1259
|
+
if (author && pr.user?.login !== author) {
|
|
1260
|
+
continue;
|
|
1507
1261
|
}
|
|
1508
|
-
|
|
1262
|
+
prs.push({
|
|
1263
|
+
repo,
|
|
1264
|
+
number: pr.number,
|
|
1265
|
+
title: pr.title,
|
|
1266
|
+
author: pr.user?.login ?? null,
|
|
1267
|
+
state: pr.state,
|
|
1268
|
+
url: pr.html_url
|
|
1269
|
+
});
|
|
1509
1270
|
}
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1271
|
+
} else {
|
|
1272
|
+
const q = [
|
|
1273
|
+
"is:pr",
|
|
1274
|
+
state === "all" ? "" : `is:${state}`,
|
|
1275
|
+
author ? `author:${author}` : ""
|
|
1276
|
+
].filter(Boolean).join(" ");
|
|
1277
|
+
const resp = await resolved.client.search.issuesAndPullRequests({
|
|
1278
|
+
q,
|
|
1279
|
+
per_page: 50
|
|
1280
|
+
});
|
|
1281
|
+
for (const item of resp.data.items) {
|
|
1282
|
+
const match = /\/repos\/([^/]+\/[^/]+)(?:\/|$)/.exec(item.repository_url);
|
|
1283
|
+
const repoName = match?.[1] ?? item.repository_url;
|
|
1284
|
+
prs.push({
|
|
1285
|
+
repo: repoName,
|
|
1286
|
+
number: item.number,
|
|
1287
|
+
title: item.title,
|
|
1288
|
+
author: item.user?.login ?? null,
|
|
1289
|
+
state: item.state,
|
|
1290
|
+
url: item.html_url
|
|
1291
|
+
});
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
await callback?.({ text: `Found ${prs.length} pull request(s)` });
|
|
1295
|
+
return { success: true, data: { op: "list", prs } };
|
|
1296
|
+
}
|
|
1297
|
+
async function runReview(runtime, options, callback) {
|
|
1298
|
+
const selection = resolveAccountSelection(options, "user");
|
|
1299
|
+
const repo = requireString(options, "repo");
|
|
1300
|
+
const number = requireNumber(options, "number");
|
|
1301
|
+
const action = parseReviewAction(options?.action);
|
|
1302
|
+
const body = requireString(options, "body");
|
|
1303
|
+
if (!repo || !number || !action) {
|
|
1304
|
+
const err = "GITHUB_PR_OP review requires repo (owner/name), number (integer), and action (approve|request-changes|comment)";
|
|
1305
|
+
await callback?.({ text: err });
|
|
1306
|
+
return { success: false, error: err };
|
|
1307
|
+
}
|
|
1308
|
+
const parts = splitRepo(repo);
|
|
1309
|
+
if (!parts) {
|
|
1310
|
+
const err = `Invalid repo "${repo}" \u2014 expected "owner/name"`;
|
|
1311
|
+
await callback?.({ text: err });
|
|
1312
|
+
return { success: false, error: err };
|
|
1313
|
+
}
|
|
1314
|
+
if (!isConfirmed(options)) {
|
|
1315
|
+
const preview = `About to ${action.replace("-", " ")} PR ${repo}#${number}` + (body ? ` with body: "${body.slice(0, 120)}"` : "") + ` as ${describeSelection(selection)}. Re-invoke with confirmed: true to proceed.`;
|
|
1316
|
+
await callback?.({ text: preview });
|
|
1317
|
+
return { success: false, requiresConfirmation: true, preview };
|
|
1318
|
+
}
|
|
1319
|
+
if (action === "request-changes" && !body) {
|
|
1320
|
+
const err = "request-changes review requires a body explaining the changes";
|
|
1321
|
+
await callback?.({ text: err });
|
|
1322
|
+
return { success: false, error: err };
|
|
1323
|
+
}
|
|
1324
|
+
const resolved = buildResolvedClient(runtime, selection);
|
|
1325
|
+
if ("error" in resolved) {
|
|
1326
|
+
await callback?.({ text: resolved.error });
|
|
1327
|
+
return { success: false, error: resolved.error };
|
|
1328
|
+
}
|
|
1329
|
+
const resp = await resolved.client.pulls.createReview({
|
|
1330
|
+
owner: parts.owner,
|
|
1331
|
+
repo: parts.name,
|
|
1332
|
+
pull_number: number,
|
|
1333
|
+
event: EVENT_BY_ACTION[action],
|
|
1334
|
+
body: body ?? void 0
|
|
1335
|
+
});
|
|
1336
|
+
await callback?.({ text: `Submitted ${action} review on ${repo}#${number}` });
|
|
1337
|
+
return { success: true, data: { op: "review", id: resp.data.id } };
|
|
1338
|
+
}
|
|
1339
|
+
var prOpAction = {
|
|
1340
|
+
name: GitHubActions.GITHUB_PR_OP,
|
|
1341
|
+
contexts: ["code", "tasks", "connectors", "automation"],
|
|
1342
|
+
contextGate: { anyOf: ["code", "tasks", "connectors", "automation"] },
|
|
1343
|
+
roleGate: { minRole: "USER" },
|
|
1344
|
+
similes: [
|
|
1345
|
+
"LIST_PRS",
|
|
1346
|
+
"LIST_PULL_REQUESTS",
|
|
1347
|
+
"SHOW_PRS",
|
|
1348
|
+
"GITHUB_LIST_PRS",
|
|
1349
|
+
"REVIEW_PR",
|
|
1350
|
+
"APPROVE_PR",
|
|
1351
|
+
"REQUEST_CHANGES",
|
|
1352
|
+
"COMMENT_ON_PR"
|
|
1353
|
+
],
|
|
1354
|
+
description: "Single router for GitHub PR ops: list and review. Review requires confirmed:true.",
|
|
1355
|
+
descriptionCompressed: "GitHub PR ops: list pull requests, submit review with confirmation.",
|
|
1356
|
+
parameters: [
|
|
1520
1357
|
{
|
|
1521
|
-
name:
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
}
|
|
1358
|
+
name: "subaction",
|
|
1359
|
+
description: "PR operation: list or review.",
|
|
1360
|
+
required: true,
|
|
1361
|
+
schema: { type: "string", enum: [...SUPPORTED_OPS2] }
|
|
1525
1362
|
},
|
|
1526
1363
|
{
|
|
1527
|
-
name: "
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
}
|
|
1533
|
-
],
|
|
1534
|
-
[
|
|
1364
|
+
name: "repo",
|
|
1365
|
+
description: "Repository in owner/name form.",
|
|
1366
|
+
required: false,
|
|
1367
|
+
schema: { type: "string" }
|
|
1368
|
+
},
|
|
1535
1369
|
{
|
|
1536
|
-
name: "
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
}
|
|
1370
|
+
name: "number",
|
|
1371
|
+
description: "Pull request number for review.",
|
|
1372
|
+
required: false,
|
|
1373
|
+
schema: { type: "number" }
|
|
1540
1374
|
},
|
|
1541
1375
|
{
|
|
1542
|
-
name: "
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
];
|
|
1550
|
-
var mergePullRequestAction = {
|
|
1551
|
-
name: "MERGE_GITHUB_PULL_REQUEST",
|
|
1552
|
-
similes: spec5.similes ? [...spec5.similes] : [],
|
|
1553
|
-
description: spec5.description,
|
|
1554
|
-
validate: async (runtime, message, _state) => {
|
|
1555
|
-
const service = runtime.getService(GITHUB_SERVICE_NAME);
|
|
1556
|
-
if (!service) {
|
|
1557
|
-
return false;
|
|
1558
|
-
}
|
|
1559
|
-
const text = message.content.text?.toLowerCase() ?? "";
|
|
1560
|
-
return text.includes("merge");
|
|
1561
|
-
},
|
|
1562
|
-
handler: async (runtime, message, state, _options, callback) => {
|
|
1563
|
-
const service = runtime.getService(GITHUB_SERVICE_NAME);
|
|
1564
|
-
if (!service) {
|
|
1565
|
-
logger6.error("GitHub service not available");
|
|
1566
|
-
if (callback) {
|
|
1567
|
-
await callback({
|
|
1568
|
-
text: "GitHub service is not available. Please ensure the plugin is properly configured."
|
|
1569
|
-
});
|
|
1570
|
-
}
|
|
1571
|
-
return { success: false };
|
|
1572
|
-
}
|
|
1573
|
-
try {
|
|
1574
|
-
const content = message.content;
|
|
1575
|
-
const text = content.text?.toLowerCase() ?? "";
|
|
1576
|
-
let mergeMethod = "merge";
|
|
1577
|
-
if (text.includes("squash")) {
|
|
1578
|
-
mergeMethod = "squash";
|
|
1579
|
-
} else if (text.includes("rebase")) {
|
|
1580
|
-
mergeMethod = "rebase";
|
|
1581
|
-
}
|
|
1582
|
-
const params = {
|
|
1583
|
-
owner: state?.owner ?? service.getConfig().owner ?? "",
|
|
1584
|
-
repo: state?.repo ?? service.getConfig().repo ?? "",
|
|
1585
|
-
pullNumber: state?.pullNumber ?? 0,
|
|
1586
|
-
commitTitle: state?.commitTitle,
|
|
1587
|
-
commitMessage: state?.commitMessage,
|
|
1588
|
-
mergeMethod: state?.mergeMethod ?? mergeMethod
|
|
1589
|
-
};
|
|
1590
|
-
const validation = mergePullRequestSchema.safeParse(params);
|
|
1591
|
-
if (!validation.success) {
|
|
1592
|
-
const errors = formatZodErrors(validation.error);
|
|
1593
|
-
logger6.error(`Invalid merge parameters: ${errors}`);
|
|
1594
|
-
if (callback) {
|
|
1595
|
-
await callback({
|
|
1596
|
-
text: `I couldn't merge the pull request due to missing information: ${errors}`
|
|
1597
|
-
});
|
|
1598
|
-
}
|
|
1599
|
-
return { success: false };
|
|
1600
|
-
}
|
|
1601
|
-
const result = await service.mergePullRequest(params);
|
|
1602
|
-
if (result.merged) {
|
|
1603
|
-
logger6.info(`Merged pull request #${params.pullNumber}`);
|
|
1604
|
-
if (callback) {
|
|
1605
|
-
await callback({
|
|
1606
|
-
text: `Successfully merged pull request #${params.pullNumber}.
|
|
1607
|
-
|
|
1608
|
-
Merge commit: ${result.sha.slice(0, 7)}
|
|
1609
|
-
Method: ${params.mergeMethod}`
|
|
1610
|
-
});
|
|
1611
|
-
}
|
|
1612
|
-
return { success: true };
|
|
1613
|
-
} else {
|
|
1614
|
-
logger6.warn(`Pull request #${params.pullNumber} was not merged: ${result.message}`);
|
|
1615
|
-
if (callback) {
|
|
1616
|
-
await callback({
|
|
1617
|
-
text: `Could not merge pull request #${params.pullNumber}: ${result.message}`
|
|
1618
|
-
});
|
|
1619
|
-
}
|
|
1620
|
-
return { success: false };
|
|
1621
|
-
}
|
|
1622
|
-
} catch (error) {
|
|
1623
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1624
|
-
logger6.error(`Failed to merge pull request: ${errorMessage}`);
|
|
1625
|
-
if (callback) {
|
|
1626
|
-
await callback({
|
|
1627
|
-
text: `Failed to merge the pull request: ${errorMessage}`
|
|
1628
|
-
});
|
|
1376
|
+
name: "state",
|
|
1377
|
+
description: "PR state for list.",
|
|
1378
|
+
required: false,
|
|
1379
|
+
schema: {
|
|
1380
|
+
type: "string",
|
|
1381
|
+
enum: ["open", "closed", "all"],
|
|
1382
|
+
default: "open"
|
|
1629
1383
|
}
|
|
1630
|
-
|
|
1631
|
-
}
|
|
1632
|
-
},
|
|
1633
|
-
examples: examples5
|
|
1634
|
-
};
|
|
1635
|
-
// actions/pushCode.ts
|
|
1636
|
-
import {
|
|
1637
|
-
logger as logger7
|
|
1638
|
-
} from "@elizaos/core";
|
|
1639
|
-
var spec6 = requireActionSpec("PUSH_CODE");
|
|
1640
|
-
var examples6 = [
|
|
1641
|
-
[
|
|
1384
|
+
},
|
|
1642
1385
|
{
|
|
1643
|
-
name:
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
1386
|
+
name: "author",
|
|
1387
|
+
description: "Optional PR author username filter for list.",
|
|
1388
|
+
required: false,
|
|
1389
|
+
schema: { type: "string" }
|
|
1647
1390
|
},
|
|
1648
1391
|
{
|
|
1649
|
-
name: "
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1392
|
+
name: "action",
|
|
1393
|
+
description: "Review action: approve, request-changes, or comment.",
|
|
1394
|
+
required: false,
|
|
1395
|
+
schema: {
|
|
1396
|
+
type: "string",
|
|
1397
|
+
enum: ["approve", "request-changes", "comment"]
|
|
1653
1398
|
}
|
|
1654
|
-
}
|
|
1655
|
-
],
|
|
1656
|
-
[
|
|
1399
|
+
},
|
|
1657
1400
|
{
|
|
1658
|
-
name: "
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
}
|
|
1401
|
+
name: "body",
|
|
1402
|
+
description: "Review body for comment or request-changes.",
|
|
1403
|
+
required: false,
|
|
1404
|
+
schema: { type: "string" }
|
|
1662
1405
|
},
|
|
1663
1406
|
{
|
|
1664
|
-
name: "
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1407
|
+
name: "as",
|
|
1408
|
+
description: "Identity to use: agent or user.",
|
|
1409
|
+
required: false,
|
|
1410
|
+
schema: { type: "string", enum: ["agent", "user"], default: "agent" }
|
|
1411
|
+
},
|
|
1412
|
+
{
|
|
1413
|
+
name: "accountId",
|
|
1414
|
+
description: "Optional GitHub account id from GITHUB_ACCOUNTS. Defaults by role.",
|
|
1415
|
+
required: false,
|
|
1416
|
+
schema: { type: "string" }
|
|
1417
|
+
},
|
|
1418
|
+
{
|
|
1419
|
+
name: "confirmed",
|
|
1420
|
+
description: "Must be true to submit a review.",
|
|
1421
|
+
required: false,
|
|
1422
|
+
schema: { type: "boolean", default: false }
|
|
1680
1423
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
1424
|
+
],
|
|
1425
|
+
validate: async (runtime, _message) => {
|
|
1426
|
+
const r = buildResolvedClient(runtime, "agent");
|
|
1427
|
+
return !("error" in r);
|
|
1683
1428
|
},
|
|
1684
|
-
handler: async (runtime,
|
|
1685
|
-
const
|
|
1686
|
-
if (!
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
text: "GitHub service is not available. Please ensure the plugin is properly configured."
|
|
1691
|
-
});
|
|
1692
|
-
}
|
|
1693
|
-
return { success: false };
|
|
1429
|
+
handler: async (runtime, _message, _state, options, callback) => {
|
|
1430
|
+
const op = parseOp2(options?.op);
|
|
1431
|
+
if (!op) {
|
|
1432
|
+
const err = "GITHUB_PR_OP requires op (list|review)";
|
|
1433
|
+
await callback?.({ text: err });
|
|
1434
|
+
return { success: false, error: err };
|
|
1694
1435
|
}
|
|
1695
1436
|
try {
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
const
|
|
1699
|
-
const
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1437
|
+
return op === "list" ? await runList(runtime, options, callback) : await runReview(runtime, options, callback);
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
const rl = inspectRateLimit(err);
|
|
1440
|
+
const message = rl.isRateLimited ? formatRateLimitMessage(rl) : `GITHUB_PR_OP ${op} failed: ${errorMessage(err)}`;
|
|
1441
|
+
logger3.warn({ message }, "[GitHub:GITHUB_PR_OP]");
|
|
1442
|
+
await callback?.({ text: message });
|
|
1443
|
+
return { success: false, error: message };
|
|
1444
|
+
}
|
|
1445
|
+
},
|
|
1446
|
+
examples: [
|
|
1447
|
+
[
|
|
1448
|
+
{
|
|
1449
|
+
name: "{{user1}}",
|
|
1450
|
+
content: { text: "Show me open PRs on elizaOS/eliza" }
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
name: "{{agentName}}",
|
|
1454
|
+
content: { text: "Found 3 pull request(s)" }
|
|
1455
|
+
}
|
|
1456
|
+
],
|
|
1457
|
+
[
|
|
1458
|
+
{
|
|
1459
|
+
name: "{{user1}}",
|
|
1460
|
+
content: { text: "Approve PR #42 on elizaOS/eliza" }
|
|
1461
|
+
},
|
|
1462
|
+
{
|
|
1463
|
+
name: "{{agentName}}",
|
|
1464
|
+
content: {
|
|
1465
|
+
text: "Submitted approve review on elizaOS/eliza#42"
|
|
1716
1466
|
}
|
|
1717
|
-
return { success: false };
|
|
1718
1467
|
}
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
await callback({
|
|
1723
|
-
text: `Pushed ${files.length} file(s) to ${params.branch}.
|
|
1724
|
-
|
|
1725
|
-
Commit: ${commit.sha.slice(0, 7)}
|
|
1726
|
-
Message: ${commit.message}
|
|
1468
|
+
]
|
|
1469
|
+
]
|
|
1470
|
+
};
|
|
1727
1471
|
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1472
|
+
// src/actions/github.ts
|
|
1473
|
+
var GITHUB_ACTIONS = [
|
|
1474
|
+
"pr_list",
|
|
1475
|
+
"pr_review",
|
|
1476
|
+
"issue_create",
|
|
1477
|
+
"issue_assign",
|
|
1478
|
+
"issue_close",
|
|
1479
|
+
"issue_reopen",
|
|
1480
|
+
"issue_comment",
|
|
1481
|
+
"issue_label",
|
|
1482
|
+
"notification_triage"
|
|
1483
|
+
];
|
|
1484
|
+
var ISSUE_OP_BY_ACTION = {
|
|
1485
|
+
issue_create: "create",
|
|
1486
|
+
issue_assign: "assign",
|
|
1487
|
+
issue_close: "close",
|
|
1488
|
+
issue_reopen: "reopen",
|
|
1489
|
+
issue_comment: "comment",
|
|
1490
|
+
issue_label: "label"
|
|
1744
1491
|
};
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1492
|
+
function readParameters(options) {
|
|
1493
|
+
if (!options || typeof options !== "object") return {};
|
|
1494
|
+
const record = options;
|
|
1495
|
+
const params = record.parameters;
|
|
1496
|
+
return params && typeof params === "object" && !Array.isArray(params) ? { ...params } : { ...record };
|
|
1497
|
+
}
|
|
1498
|
+
function readAction(options) {
|
|
1499
|
+
const params = readParameters(options);
|
|
1500
|
+
const raw = params.action ?? params.subaction ?? params.op ?? params.operation ?? params.verb;
|
|
1501
|
+
if (typeof raw !== "string") return void 0;
|
|
1502
|
+
const normalized = raw.trim().toLowerCase().replace(/[-\s]+/g, "_");
|
|
1503
|
+
return GITHUB_ACTIONS.includes(normalized) ? normalized : void 0;
|
|
1504
|
+
}
|
|
1505
|
+
function withParameters(options, parameters) {
|
|
1506
|
+
if (!options || typeof options !== "object") return { parameters };
|
|
1507
|
+
return { ...options, parameters };
|
|
1508
|
+
}
|
|
1509
|
+
function delegate(target, runtime, message, state, options, callback) {
|
|
1510
|
+
return target.handler(
|
|
1511
|
+
runtime,
|
|
1512
|
+
message,
|
|
1513
|
+
state,
|
|
1514
|
+
options,
|
|
1515
|
+
callback
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
var githubAction = {
|
|
1519
|
+
name: "GITHUB",
|
|
1520
|
+
contexts: ["code", "tasks", "connectors", "automation"],
|
|
1521
|
+
contextGate: { anyOf: ["code", "tasks", "connectors", "automation"] },
|
|
1522
|
+
roleGate: { minRole: "USER" },
|
|
1523
|
+
similes: [
|
|
1524
|
+
"GITHUB_PR_OP",
|
|
1525
|
+
"GITHUB_ISSUE_OP",
|
|
1526
|
+
"GITHUB_NOTIFICATION_TRIAGE",
|
|
1527
|
+
"GITHUB_PULL_REQUEST",
|
|
1528
|
+
"GITHUB_ISSUE",
|
|
1529
|
+
"GITHUB_NOTIFICATIONS"
|
|
1530
|
+
],
|
|
1531
|
+
description: "GitHub umbrella for pull requests, issues, and notification triage. Use action=pr_list/pr_review/issue_create/issue_assign/issue_close/issue_reopen/issue_comment/issue_label/notification_triage.",
|
|
1532
|
+
descriptionCompressed: "GitHub: pr_list|pr_review|issue_create|issue_assign|issue_close|issue_reopen|issue_comment|issue_label|notification_triage",
|
|
1533
|
+
parameters: [
|
|
1752
1534
|
{
|
|
1753
|
-
name:
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
}
|
|
1535
|
+
name: "action",
|
|
1536
|
+
description: "GitHub operation to run.",
|
|
1537
|
+
required: true,
|
|
1538
|
+
schema: { type: "string", enum: [...GITHUB_ACTIONS] }
|
|
1757
1539
|
},
|
|
1758
1540
|
{
|
|
1759
|
-
name: "
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
}
|
|
1765
|
-
],
|
|
1766
|
-
[
|
|
1541
|
+
name: "repo",
|
|
1542
|
+
description: "Repository in owner/name form.",
|
|
1543
|
+
required: false,
|
|
1544
|
+
schema: { type: "string" }
|
|
1545
|
+
},
|
|
1767
1546
|
{
|
|
1768
|
-
name: "
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
}
|
|
1547
|
+
name: "number",
|
|
1548
|
+
description: "Pull request or issue number.",
|
|
1549
|
+
required: false,
|
|
1550
|
+
schema: { type: "number" }
|
|
1772
1551
|
},
|
|
1773
1552
|
{
|
|
1774
|
-
name: "
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1553
|
+
name: "state",
|
|
1554
|
+
description: "PR state for pr_list: open, closed, or all.",
|
|
1555
|
+
required: false,
|
|
1556
|
+
schema: { type: "string", enum: ["open", "closed", "all"], default: "open" }
|
|
1557
|
+
},
|
|
1558
|
+
{
|
|
1559
|
+
name: "author",
|
|
1560
|
+
description: "Optional PR author username filter for pr_list.",
|
|
1561
|
+
required: false,
|
|
1562
|
+
schema: { type: "string" }
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
name: "review_action",
|
|
1566
|
+
description: "For action=pr_review: approve, request-changes, or comment.",
|
|
1567
|
+
required: false,
|
|
1568
|
+
schema: { type: "string", enum: ["approve", "request-changes", "comment"] }
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
name: "title",
|
|
1572
|
+
description: "Issue title for action=issue_create.",
|
|
1573
|
+
required: false,
|
|
1574
|
+
schema: { type: "string" }
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
name: "body",
|
|
1578
|
+
description: "Issue body, issue comment body, or PR review body.",
|
|
1579
|
+
required: false,
|
|
1580
|
+
schema: { type: "string" }
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
name: "assignees",
|
|
1584
|
+
description: "GitHub usernames to assign.",
|
|
1585
|
+
required: false,
|
|
1586
|
+
schema: { type: "array", items: { type: "string" } }
|
|
1587
|
+
},
|
|
1588
|
+
{
|
|
1589
|
+
name: "labels",
|
|
1590
|
+
description: "Labels to apply on issue create or issue_label.",
|
|
1591
|
+
required: false,
|
|
1592
|
+
schema: { type: "array", items: { type: "string" } }
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
name: "as",
|
|
1596
|
+
description: "Identity to use: agent or user.",
|
|
1597
|
+
required: false,
|
|
1598
|
+
schema: { type: "string", enum: ["agent", "user"], default: "agent" }
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
name: "accountId",
|
|
1602
|
+
description: "Optional GitHub account id from GITHUB_ACCOUNTS. Defaults by role.",
|
|
1603
|
+
required: false,
|
|
1604
|
+
schema: { type: "string" }
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
name: "confirmed",
|
|
1608
|
+
description: "Must be true for GitHub write operations.",
|
|
1609
|
+
required: false,
|
|
1610
|
+
schema: { type: "boolean", default: false }
|
|
1790
1611
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
const
|
|
1796
|
-
if (!
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
}
|
|
1803
|
-
return { success: false };
|
|
1612
|
+
],
|
|
1613
|
+
validate: async (runtime, message, state) => await prOpAction.validate(runtime, message, state) || await issueOpAction.validate(runtime, message, state) || await notificationTriageAction.validate(runtime, message, state),
|
|
1614
|
+
handler: async (runtime, message, state, options, callback) => {
|
|
1615
|
+
const action = readAction(options);
|
|
1616
|
+
const params = readParameters(options);
|
|
1617
|
+
if (!action) {
|
|
1618
|
+
return {
|
|
1619
|
+
success: false,
|
|
1620
|
+
text: "GITHUB requires action=pr_list/pr_review/issue_create/issue_assign/issue_close/issue_reopen/issue_comment/issue_label/notification_triage.",
|
|
1621
|
+
data: { error: "MISSING_ACTION" }
|
|
1622
|
+
};
|
|
1804
1623
|
}
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
event: state?.event ?? event
|
|
1624
|
+
if (action === "notification_triage") {
|
|
1625
|
+
return delegate(
|
|
1626
|
+
notificationTriageAction,
|
|
1627
|
+
runtime,
|
|
1628
|
+
message,
|
|
1629
|
+
state,
|
|
1630
|
+
withParameters(options, params),
|
|
1631
|
+
callback
|
|
1632
|
+
);
|
|
1633
|
+
}
|
|
1634
|
+
if (action === "pr_list" || action === "pr_review") {
|
|
1635
|
+
const childParams = {
|
|
1636
|
+
...params,
|
|
1637
|
+
op: action === "pr_list" ? "list" : "review",
|
|
1638
|
+
...action === "pr_review" && params.review_action ? { action: params.review_action } : {}
|
|
1821
1639
|
};
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
});
|
|
1842
|
-
}
|
|
1843
|
-
return { success: true };
|
|
1844
|
-
} catch (error) {
|
|
1845
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1846
|
-
logger8.error(`Failed to create review: ${errorMessage}`);
|
|
1847
|
-
if (callback) {
|
|
1848
|
-
await callback({
|
|
1849
|
-
text: `Failed to create the review: ${errorMessage}`
|
|
1850
|
-
});
|
|
1851
|
-
}
|
|
1852
|
-
return { success: false };
|
|
1640
|
+
return delegate(
|
|
1641
|
+
prOpAction,
|
|
1642
|
+
runtime,
|
|
1643
|
+
message,
|
|
1644
|
+
state,
|
|
1645
|
+
withParameters(options, childParams),
|
|
1646
|
+
callback
|
|
1647
|
+
);
|
|
1648
|
+
}
|
|
1649
|
+
const issueOp = ISSUE_OP_BY_ACTION[action];
|
|
1650
|
+
if (issueOp) {
|
|
1651
|
+
return delegate(
|
|
1652
|
+
issueOpAction,
|
|
1653
|
+
runtime,
|
|
1654
|
+
message,
|
|
1655
|
+
state,
|
|
1656
|
+
withParameters(options, { ...params, op: issueOp }),
|
|
1657
|
+
callback
|
|
1658
|
+
);
|
|
1853
1659
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1660
|
+
return {
|
|
1661
|
+
success: false,
|
|
1662
|
+
text: `Unsupported GITHUB action: ${action}`,
|
|
1663
|
+
data: { error: "UNSUPPORTED_ACTION", action }
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1856
1666
|
};
|
|
1857
|
-
// actions/index.ts
|
|
1858
|
-
var allActions = [
|
|
1859
|
-
createIssueAction,
|
|
1860
|
-
createPullRequestAction,
|
|
1861
|
-
reviewPullRequestAction,
|
|
1862
|
-
createCommentAction,
|
|
1863
|
-
createBranchAction,
|
|
1864
|
-
pushCodeAction,
|
|
1865
|
-
mergePullRequestAction
|
|
1866
|
-
];
|
|
1867
1667
|
|
|
1868
|
-
//
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1668
|
+
// src/connector-account-provider.ts
|
|
1669
|
+
import {
|
|
1670
|
+
logger as logger4
|
|
1671
|
+
} from "@elizaos/core";
|
|
1672
|
+
var GITHUB_AUTHORIZATION_ENDPOINT = "https://github.com/login/oauth/authorize";
|
|
1673
|
+
var GITHUB_TOKEN_ENDPOINT = "https://github.com/login/oauth/access_token";
|
|
1674
|
+
var GITHUB_USER_ENDPOINT = "https://api.github.com/user";
|
|
1675
|
+
var DEFAULT_PURPOSES = [
|
|
1676
|
+
"posting",
|
|
1677
|
+
"reading",
|
|
1678
|
+
"admin"
|
|
1679
|
+
];
|
|
1680
|
+
function nonEmptyString3(value) {
|
|
1681
|
+
if (typeof value !== "string") return void 0;
|
|
1682
|
+
const trimmed = value.trim();
|
|
1683
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
1684
|
+
}
|
|
1685
|
+
function readSetting2(runtime, key) {
|
|
1686
|
+
return nonEmptyString3(runtime.getSetting?.(key));
|
|
1687
|
+
}
|
|
1688
|
+
function readClientConfig(runtime) {
|
|
1689
|
+
const clientId = readSetting2(runtime, "GITHUB_OAUTH_CLIENT_ID");
|
|
1690
|
+
const clientSecret = readSetting2(runtime, "GITHUB_OAUTH_CLIENT_SECRET");
|
|
1691
|
+
const redirectUri = readSetting2(runtime, "GITHUB_OAUTH_REDIRECT_URI");
|
|
1692
|
+
if (!clientId || !clientSecret || !redirectUri) {
|
|
1693
|
+
throw new Error(
|
|
1694
|
+
"GitHub OAuth requires GITHUB_OAUTH_CLIENT_ID, GITHUB_OAUTH_CLIENT_SECRET, and GITHUB_OAUTH_REDIRECT_URI to be configured."
|
|
1695
|
+
);
|
|
1876
1696
|
}
|
|
1877
|
-
return
|
|
1697
|
+
return { clientId, clientSecret, redirectUri };
|
|
1878
1698
|
}
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1699
|
+
function parseScopes(value) {
|
|
1700
|
+
if (!value) return [];
|
|
1701
|
+
return value.split(/[,\s]+/).map((scope) => scope.trim()).filter(Boolean);
|
|
1702
|
+
}
|
|
1703
|
+
function defaultRoleFromAccountId(accountId) {
|
|
1704
|
+
if (accountId === DEFAULT_GITHUB_USER_ACCOUNT_ID) return "OWNER";
|
|
1705
|
+
if (accountId === DEFAULT_GITHUB_AGENT_ACCOUNT_ID) return "AGENT";
|
|
1706
|
+
return "OWNER";
|
|
1707
|
+
}
|
|
1708
|
+
function roleFromMetadata(metadata, accountId) {
|
|
1709
|
+
const record = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : {};
|
|
1710
|
+
const raw = nonEmptyString3(record.role ?? record.accountRole);
|
|
1711
|
+
const normalized = raw?.toUpperCase();
|
|
1712
|
+
if (normalized === "OWNER" || normalized === "AGENT" || normalized === "TEAM") {
|
|
1713
|
+
return normalized;
|
|
1714
|
+
}
|
|
1715
|
+
return defaultRoleFromAccountId(accountId);
|
|
1716
|
+
}
|
|
1717
|
+
async function exchangeCodeForToken(args) {
|
|
1718
|
+
const response = await fetch(GITHUB_TOKEN_ENDPOINT, {
|
|
1719
|
+
method: "POST",
|
|
1720
|
+
headers: {
|
|
1721
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
1722
|
+
Accept: "application/json"
|
|
1723
|
+
},
|
|
1724
|
+
body: new URLSearchParams({
|
|
1725
|
+
client_id: args.clientId,
|
|
1726
|
+
client_secret: args.clientSecret,
|
|
1727
|
+
code: args.code,
|
|
1728
|
+
redirect_uri: args.redirectUri
|
|
1729
|
+
}).toString()
|
|
1730
|
+
});
|
|
1731
|
+
if (!response.ok) {
|
|
1732
|
+
const body = await response.text();
|
|
1733
|
+
throw new Error(
|
|
1734
|
+
`GitHub token exchange failed with ${response.status}: ${body}`
|
|
1735
|
+
);
|
|
1736
|
+
}
|
|
1737
|
+
const parsed = await response.json();
|
|
1738
|
+
if (parsed.error) {
|
|
1739
|
+
throw new Error(
|
|
1740
|
+
`GitHub token exchange returned error ${parsed.error}: ${parsed.error_description ?? "no description"}`
|
|
1741
|
+
);
|
|
1742
|
+
}
|
|
1743
|
+
if (!parsed.access_token) {
|
|
1744
|
+
throw new Error("GitHub token exchange returned no access_token.");
|
|
1745
|
+
}
|
|
1746
|
+
return parsed;
|
|
1747
|
+
}
|
|
1748
|
+
async function fetchGitHubUser(accessToken) {
|
|
1749
|
+
const response = await fetch(GITHUB_USER_ENDPOINT, {
|
|
1750
|
+
headers: {
|
|
1751
|
+
Authorization: `Bearer ${accessToken}`,
|
|
1752
|
+
Accept: "application/vnd.github+json",
|
|
1753
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1756
|
+
if (!response.ok) {
|
|
1757
|
+
throw new Error(`GitHub /user request failed with ${response.status}`);
|
|
1758
|
+
}
|
|
1759
|
+
const parsed = await response.json();
|
|
1760
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1761
|
+
throw new Error("GitHub /user returned an invalid payload.");
|
|
1762
|
+
}
|
|
1763
|
+
return parsed;
|
|
1764
|
+
}
|
|
1765
|
+
function synthesizeEnvAccounts(runtime) {
|
|
1766
|
+
const now = Date.now();
|
|
1767
|
+
return readGitHubAccounts(runtime).map((account) => ({
|
|
1768
|
+
id: account.accountId,
|
|
1769
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
1770
|
+
label: account.label ?? `GitHub ${account.role} (${account.accountId})`,
|
|
1771
|
+
role: account.role === "user" ? "OWNER" : "AGENT",
|
|
1772
|
+
purpose: DEFAULT_PURPOSES,
|
|
1773
|
+
accessGate: "open",
|
|
1774
|
+
status: "connected",
|
|
1775
|
+
displayHandle: account.accountId,
|
|
1776
|
+
createdAt: now,
|
|
1777
|
+
updatedAt: now,
|
|
1778
|
+
metadata: { authMethod: "pat", source: "env" }
|
|
1779
|
+
}));
|
|
1780
|
+
}
|
|
1781
|
+
function createGitHubConnectorAccountProvider(runtime) {
|
|
1782
|
+
return {
|
|
1783
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
1784
|
+
label: "GitHub",
|
|
1785
|
+
listAccounts: async (manager) => {
|
|
1786
|
+
const stored = await manager.getStorage().listAccounts(GITHUB_SERVICE_TYPE);
|
|
1787
|
+
if (stored.length > 0) return stored;
|
|
1788
|
+
return synthesizeEnvAccounts(runtime);
|
|
1789
|
+
},
|
|
1790
|
+
createAccount: async (input, _manager) => {
|
|
1791
|
+
return {
|
|
1792
|
+
...input,
|
|
1793
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
1794
|
+
role: input.role ?? "OWNER",
|
|
1795
|
+
purpose: input.purpose ?? DEFAULT_PURPOSES,
|
|
1796
|
+
accessGate: input.accessGate ?? "open",
|
|
1797
|
+
status: input.status ?? "pending"
|
|
1798
|
+
};
|
|
1799
|
+
},
|
|
1800
|
+
patchAccount: async (_accountId, patch, _manager) => {
|
|
1801
|
+
return { ...patch, provider: GITHUB_SERVICE_TYPE };
|
|
1802
|
+
},
|
|
1803
|
+
deleteAccount: async (_accountId, _manager) => {
|
|
1804
|
+
},
|
|
1805
|
+
startOAuth: async (request, _manager) => {
|
|
1806
|
+
const config = readClientConfig(runtime);
|
|
1807
|
+
const redirectUri = request.redirectUri ?? config.redirectUri;
|
|
1808
|
+
const scopes = request.scopes && request.scopes.length > 0 ? request.scopes : ["repo", "read:user", "user:email", "notifications"];
|
|
1809
|
+
const params = new URLSearchParams({
|
|
1810
|
+
client_id: config.clientId,
|
|
1811
|
+
redirect_uri: redirectUri,
|
|
1812
|
+
state: request.flow.state,
|
|
1813
|
+
scope: scopes.join(" "),
|
|
1814
|
+
allow_signup: "false"
|
|
1815
|
+
});
|
|
1816
|
+
return {
|
|
1817
|
+
authUrl: `${GITHUB_AUTHORIZATION_ENDPOINT}?${params.toString()}`,
|
|
1818
|
+
metadata: {
|
|
1819
|
+
...request.metadata,
|
|
1820
|
+
requestedScopes: scopes,
|
|
1821
|
+
redirectUri
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
},
|
|
1825
|
+
completeOAuth: async (request, manager) => {
|
|
1826
|
+
const code = nonEmptyString3(request.code);
|
|
1827
|
+
if (!code) {
|
|
1828
|
+
throw new Error(
|
|
1829
|
+
"GitHub OAuth callback is missing an authorization code."
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
const config = readClientConfig(runtime);
|
|
1833
|
+
const redirectUri = nonEmptyString3(request.flow.redirectUri) ?? config.redirectUri;
|
|
1834
|
+
const tokens = await exchangeCodeForToken({
|
|
1835
|
+
clientId: config.clientId,
|
|
1836
|
+
clientSecret: config.clientSecret,
|
|
1837
|
+
redirectUri,
|
|
1838
|
+
code
|
|
1839
|
+
});
|
|
1840
|
+
if (!tokens.access_token) {
|
|
1841
|
+
throw new Error("GitHub token exchange returned no access_token.");
|
|
1842
|
+
}
|
|
1843
|
+
const user = await fetchGitHubUser(tokens.access_token);
|
|
1844
|
+
const externalId = nonEmptyString3(user.id ? String(user.id) : void 0);
|
|
1845
|
+
const login = nonEmptyString3(user.login);
|
|
1846
|
+
if (!login) {
|
|
1847
|
+
throw new Error("GitHub /user payload did not include a login.");
|
|
1848
|
+
}
|
|
1849
|
+
const expiresAt = typeof tokens.expires_in === "number" ? Date.now() + tokens.expires_in * 1e3 : void 0;
|
|
1850
|
+
const oauthCredentialVersion = String(Date.now());
|
|
1851
|
+
const accountMetadata = {
|
|
1852
|
+
authMethod: "oauth",
|
|
1853
|
+
login,
|
|
1854
|
+
githubUserId: user.id ?? null,
|
|
1855
|
+
email: nonEmptyString3(user.email) ?? null,
|
|
1856
|
+
type: nonEmptyString3(user.type) ?? null,
|
|
1857
|
+
tokenType: nonEmptyString3(tokens.token_type) ?? "bearer",
|
|
1858
|
+
grantedScopes: parseScopes(tokens.scope),
|
|
1859
|
+
hasRefreshToken: Boolean(tokens.refresh_token),
|
|
1860
|
+
expiresAt,
|
|
1861
|
+
oauthCredentialVersion
|
|
1862
|
+
};
|
|
1863
|
+
const pendingAccount = await manager.upsertAccount(
|
|
1864
|
+
GITHUB_SERVICE_TYPE,
|
|
1865
|
+
{
|
|
1866
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
1867
|
+
role: roleFromMetadata(request.flow.metadata, request.flow.accountId),
|
|
1868
|
+
purpose: DEFAULT_PURPOSES,
|
|
1869
|
+
accessGate: "open",
|
|
1870
|
+
status: "pending",
|
|
1871
|
+
externalId: externalId ?? login,
|
|
1872
|
+
displayHandle: login,
|
|
1873
|
+
label: nonEmptyString3(user.name) ?? login,
|
|
1874
|
+
metadata: accountMetadata
|
|
1875
|
+
},
|
|
1876
|
+
request.flow.accountId
|
|
1877
|
+
);
|
|
1878
|
+
const credentialPersist = await persistConnectorCredentialRefs({
|
|
1879
|
+
runtime,
|
|
1880
|
+
manager,
|
|
1881
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
1882
|
+
accountIdForRef: pendingAccount.id,
|
|
1883
|
+
storageAccountId: pendingAccount.id,
|
|
1884
|
+
caller: "plugin-github",
|
|
1885
|
+
credentials: [
|
|
1886
|
+
{
|
|
1887
|
+
credentialType: "oauth.tokens",
|
|
1888
|
+
value: JSON.stringify({
|
|
1889
|
+
access_token: tokens.access_token,
|
|
1890
|
+
...tokens.refresh_token ? { refresh_token: tokens.refresh_token } : {},
|
|
1891
|
+
...expiresAt !== void 0 ? { expires_at: expiresAt } : {},
|
|
1892
|
+
token_type: nonEmptyString3(tokens.token_type) ?? "bearer",
|
|
1893
|
+
scope: tokens.scope ?? ""
|
|
1894
|
+
}),
|
|
1895
|
+
...expiresAt !== void 0 ? { expiresAt } : {},
|
|
1896
|
+
metadata: {
|
|
1897
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
1898
|
+
hasRefreshToken: Boolean(tokens.refresh_token)
|
|
1899
|
+
}
|
|
1927
1900
|
}
|
|
1928
|
-
|
|
1929
|
-
|
|
1901
|
+
]
|
|
1902
|
+
});
|
|
1903
|
+
const accountPatch = {
|
|
1904
|
+
...pendingAccount,
|
|
1905
|
+
id: pendingAccount.id,
|
|
1906
|
+
provider: GITHUB_SERVICE_TYPE,
|
|
1907
|
+
status: "connected",
|
|
1908
|
+
metadata: {
|
|
1909
|
+
...accountMetadata,
|
|
1910
|
+
credentialRefs: credentialPersist.refs,
|
|
1911
|
+
credentialRefStorage: {
|
|
1912
|
+
vaultAvailable: credentialPersist.vaultAvailable,
|
|
1913
|
+
storageAvailable: credentialPersist.storageAvailable
|
|
1930
1914
|
}
|
|
1931
|
-
parts2.push("", `**Changes:** +${pr.additions} / -${pr.deletions} (${pr.changedFiles} files)`, "", "### Description", pr.body ?? "_No description provided_", "", `**URL:** ${pr.htmlUrl}`);
|
|
1932
|
-
return { text: parts2.join(`
|
|
1933
|
-
`) };
|
|
1934
|
-
}
|
|
1935
|
-
const labels = issue.labels.map((l) => l.name).join(", ");
|
|
1936
|
-
const assignees = issue.assignees.map((a) => a.login).join(", ");
|
|
1937
|
-
const parts = [
|
|
1938
|
-
`## Issue #${issue.number}: ${issue.title}`,
|
|
1939
|
-
"",
|
|
1940
|
-
`**State:** ${issue.state}${issue.stateReason ? ` (${issue.stateReason})` : ""}`,
|
|
1941
|
-
`**Author:** ${issue.user.login}`,
|
|
1942
|
-
`**Created:** ${issue.createdAt}`,
|
|
1943
|
-
`**Updated:** ${issue.updatedAt}`,
|
|
1944
|
-
`**Comments:** ${issue.comments}`
|
|
1945
|
-
];
|
|
1946
|
-
if (labels) {
|
|
1947
|
-
parts.push(`**Labels:** ${labels}`);
|
|
1948
1915
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
};
|
|
1962
|
-
}
|
|
1963
|
-
} catch (error) {
|
|
1964
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
1965
|
-
return { text: `Unable to fetch issue context: ${errorMessage}` };
|
|
1916
|
+
};
|
|
1917
|
+
logger4.info(
|
|
1918
|
+
{
|
|
1919
|
+
src: "plugin:github:connector",
|
|
1920
|
+
login
|
|
1921
|
+
},
|
|
1922
|
+
"GitHub OAuth completed"
|
|
1923
|
+
);
|
|
1924
|
+
return {
|
|
1925
|
+
account: accountPatch,
|
|
1926
|
+
flow: { status: "completed" }
|
|
1927
|
+
};
|
|
1966
1928
|
}
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
// src/routes/github-routes.ts
|
|
1933
|
+
import { logger as logger5 } from "@elizaos/core";
|
|
1934
|
+
|
|
1935
|
+
// src/github-credentials.ts
|
|
1936
|
+
import fs from "fs/promises";
|
|
1937
|
+
import path from "path";
|
|
1938
|
+
import { resolveStateDir } from "@elizaos/core";
|
|
1939
|
+
function getCredentialFilePath() {
|
|
1940
|
+
return path.join(resolveStateDir(), "credentials", "github.json");
|
|
1941
|
+
}
|
|
1942
|
+
function isGitHubCredentials(value) {
|
|
1943
|
+
if (!value || typeof value !== "object") return false;
|
|
1944
|
+
const v = value;
|
|
1945
|
+
return typeof v.token === "string" && typeof v.username === "string" && Array.isArray(v.scopes) && v.scopes.every((s) => typeof s === "string") && typeof v.savedAt === "number";
|
|
1946
|
+
}
|
|
1947
|
+
async function loadCredentials() {
|
|
1948
|
+
const filePath = getCredentialFilePath();
|
|
1949
|
+
let raw;
|
|
1950
|
+
try {
|
|
1951
|
+
raw = await fs.readFile(filePath, "utf-8");
|
|
1952
|
+
} catch {
|
|
1953
|
+
return null;
|
|
1967
1954
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1955
|
+
let parsed;
|
|
1956
|
+
try {
|
|
1957
|
+
parsed = JSON.parse(raw);
|
|
1958
|
+
} catch {
|
|
1959
|
+
return null;
|
|
1960
|
+
}
|
|
1961
|
+
return isGitHubCredentials(parsed) ? parsed : null;
|
|
1962
|
+
}
|
|
1963
|
+
async function loadMetadata() {
|
|
1964
|
+
const creds = await loadCredentials();
|
|
1965
|
+
if (!creds) return null;
|
|
1966
|
+
const { token: _token, ...metadata } = creds;
|
|
1967
|
+
return metadata;
|
|
1968
|
+
}
|
|
1969
|
+
async function saveCredentials(creds) {
|
|
1970
|
+
const filePath = getCredentialFilePath();
|
|
1971
|
+
const directory = path.dirname(filePath);
|
|
1972
|
+
await fs.mkdir(directory, { recursive: true, mode: 448 });
|
|
1973
|
+
await fs.chmod(directory, 448);
|
|
1974
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
1975
|
+
await fs.writeFile(tmpPath, JSON.stringify(creds, null, 2), {
|
|
1976
|
+
mode: 384
|
|
1977
|
+
});
|
|
1978
|
+
await fs.rename(tmpPath, filePath);
|
|
1979
|
+
}
|
|
1980
|
+
async function clearCredentials() {
|
|
1981
|
+
const filePath = getCredentialFilePath();
|
|
1982
|
+
try {
|
|
1983
|
+
await fs.unlink(filePath);
|
|
1984
|
+
} catch (err) {
|
|
1985
|
+
if (err.code === "ENOENT") return;
|
|
1986
|
+
throw err;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
function buildCredentialsFromUserResponse(token, user, scopes, now = Date.now()) {
|
|
1990
|
+
return {
|
|
1991
|
+
token,
|
|
1992
|
+
username: user.login,
|
|
1993
|
+
scopes,
|
|
1994
|
+
savedAt: now
|
|
1995
|
+
};
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
// src/routes/github-routes.ts
|
|
1999
|
+
var GITHUB_USER_URL = "https://api.github.com/user";
|
|
2000
|
+
var VALIDATION_TIMEOUT_MS = 1e4;
|
|
2001
|
+
var MAX_BODY_BYTES = 8 * 1024;
|
|
2002
|
+
async function readJsonBody(req) {
|
|
2003
|
+
const chunks = [];
|
|
2004
|
+
let total = 0;
|
|
2005
|
+
for await (const chunk of req) {
|
|
2006
|
+
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2007
|
+
total += buf.length;
|
|
2008
|
+
if (total > MAX_BODY_BYTES) return null;
|
|
2009
|
+
chunks.push(buf);
|
|
2010
|
+
}
|
|
2011
|
+
if (chunks.length === 0) return null;
|
|
2012
|
+
try {
|
|
2013
|
+
const parsed = JSON.parse(Buffer.concat(chunks).toString("utf-8"));
|
|
2014
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
|
|
2015
|
+
} catch {
|
|
2016
|
+
return null;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
function sendJson(ctx, status, body) {
|
|
2020
|
+
if (ctx.json) {
|
|
2021
|
+
ctx.json(status, body);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
ctx.res.statusCode = status;
|
|
2025
|
+
ctx.res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
2026
|
+
ctx.res.end(JSON.stringify(body));
|
|
2027
|
+
}
|
|
2028
|
+
function metadataToStatus(metadata) {
|
|
2029
|
+
if (!metadata) return { connected: false };
|
|
2030
|
+
return {
|
|
2031
|
+
connected: true,
|
|
2032
|
+
username: metadata.username,
|
|
2033
|
+
scopes: metadata.scopes,
|
|
2034
|
+
savedAt: metadata.savedAt
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
async function validateToken(token, fetchImpl) {
|
|
2038
|
+
const controller = new AbortController();
|
|
2039
|
+
const timer = setTimeout(() => controller.abort(), VALIDATION_TIMEOUT_MS);
|
|
2040
|
+
let response;
|
|
2041
|
+
try {
|
|
2042
|
+
response = await fetchImpl(GITHUB_USER_URL, {
|
|
2043
|
+
headers: {
|
|
2044
|
+
Authorization: `Bearer ${token}`,
|
|
2045
|
+
Accept: "application/vnd.github+json",
|
|
2046
|
+
"User-Agent": "eliza-github-connection"
|
|
2047
|
+
},
|
|
2048
|
+
signal: controller.signal
|
|
2049
|
+
});
|
|
2050
|
+
} finally {
|
|
2051
|
+
clearTimeout(timer);
|
|
2052
|
+
}
|
|
2053
|
+
if (response.status === 401) {
|
|
2054
|
+
throw new Error("Token rejected by GitHub: bad credentials.");
|
|
2055
|
+
}
|
|
2056
|
+
if (response.status === 403) {
|
|
2057
|
+
throw new Error(
|
|
2058
|
+
"Token rejected by GitHub: forbidden. Check the token has at least `read:user` scope."
|
|
2059
|
+
);
|
|
2060
|
+
}
|
|
2061
|
+
if (!response.ok) {
|
|
2062
|
+
throw new Error(
|
|
2063
|
+
`GitHub returned ${response.status} validating the token. Try again or generate a new token.`
|
|
2064
|
+
);
|
|
2065
|
+
}
|
|
2066
|
+
const body = await response.json();
|
|
2067
|
+
if (typeof body?.login !== "string" || body.login.length === 0) {
|
|
2068
|
+
throw new Error("GitHub /user response was missing the login field.");
|
|
2069
|
+
}
|
|
2070
|
+
const scopesHeader = response.headers.get("x-oauth-scopes") ?? "";
|
|
2071
|
+
const scopes = scopesHeader.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2072
|
+
return { user: body, scopes };
|
|
2073
|
+
}
|
|
2074
|
+
async function handleGetToken(ctx) {
|
|
2075
|
+
const metadata = await loadMetadata();
|
|
2076
|
+
sendJson(ctx, 200, metadataToStatus(metadata));
|
|
2077
|
+
return true;
|
|
2078
|
+
}
|
|
2079
|
+
async function handlePostToken(ctx) {
|
|
2080
|
+
const body = await readJsonBody(ctx.req);
|
|
2081
|
+
const token = body && typeof body.token === "string" ? body.token.trim() : "";
|
|
2082
|
+
if (token.length === 0) {
|
|
2083
|
+
sendJson(ctx, 400, { error: "Missing `token` in request body." });
|
|
2084
|
+
return true;
|
|
2085
|
+
}
|
|
2086
|
+
const fetchImpl = ctx.fetch ?? fetch;
|
|
2087
|
+
let validated;
|
|
2088
|
+
try {
|
|
2089
|
+
validated = await validateToken(token, fetchImpl);
|
|
2090
|
+
} catch (err) {
|
|
2091
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2092
|
+
logger5.warn(`[github-routes] token validation failed: ${message}`);
|
|
2093
|
+
sendJson(ctx, 400, { error: message });
|
|
2094
|
+
return true;
|
|
2095
|
+
}
|
|
2096
|
+
const credentials = buildCredentialsFromUserResponse(
|
|
2097
|
+
token,
|
|
2098
|
+
validated.user,
|
|
2099
|
+
validated.scopes
|
|
2100
|
+
);
|
|
2101
|
+
await saveCredentials(credentials);
|
|
2102
|
+
logger5.info(
|
|
2103
|
+
`[github-routes] saved github token for @${validated.user.login} (scopes=${validated.scopes.join(",") || "(none)"})`
|
|
2104
|
+
);
|
|
2105
|
+
sendJson(ctx, 200, metadataToStatus(credentials));
|
|
2106
|
+
return true;
|
|
2107
|
+
}
|
|
2108
|
+
async function handleDeleteToken(ctx) {
|
|
2109
|
+
await clearCredentials();
|
|
2110
|
+
logger5.info("[github-routes] cleared saved github token");
|
|
2111
|
+
sendJson(ctx, 200, { connected: false });
|
|
2112
|
+
return true;
|
|
2113
|
+
}
|
|
2114
|
+
async function handleGitHubRoutes(ctx) {
|
|
2115
|
+
if (ctx.pathname !== "/api/github/token") return false;
|
|
2116
|
+
switch (ctx.method) {
|
|
2117
|
+
case "GET":
|
|
2118
|
+
return handleGetToken(ctx);
|
|
2119
|
+
case "POST":
|
|
2120
|
+
return handlePostToken(ctx);
|
|
2121
|
+
case "DELETE":
|
|
2122
|
+
return handleDeleteToken(ctx);
|
|
2123
|
+
default:
|
|
2124
|
+
sendJson(ctx, 405, { error: "Method not allowed" });
|
|
2125
|
+
return true;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
|
|
2129
|
+
// src/search-category.ts
|
|
2130
|
+
var GITHUB_PULL_REQUESTS_SEARCH_CATEGORY = {
|
|
2131
|
+
category: "github_pull_requests",
|
|
2132
|
+
label: "GitHub pull requests",
|
|
2133
|
+
description: "Search GitHub pull requests in a repo or across accessible repositories.",
|
|
2134
|
+
contexts: ["code", "automation"],
|
|
2135
|
+
filters: [
|
|
2136
|
+
{ name: "query", label: "Query", type: "string" },
|
|
2137
|
+
{
|
|
2138
|
+
name: "repo",
|
|
2139
|
+
label: "Repository",
|
|
2140
|
+
description: "Repository in owner/name format. Omit to search accessible repositories.",
|
|
2141
|
+
type: "string"
|
|
2142
|
+
},
|
|
2143
|
+
{
|
|
2144
|
+
name: "state",
|
|
2145
|
+
label: "State",
|
|
2146
|
+
description: "Pull request state.",
|
|
2147
|
+
type: "enum",
|
|
2148
|
+
default: "open",
|
|
2149
|
+
options: [
|
|
2150
|
+
{ label: "Open", value: "open" },
|
|
2151
|
+
{ label: "Closed", value: "closed" },
|
|
2152
|
+
{ label: "All", value: "all" }
|
|
2153
|
+
]
|
|
2154
|
+
},
|
|
2155
|
+
{
|
|
2156
|
+
name: "author",
|
|
2157
|
+
label: "Author",
|
|
2158
|
+
description: "GitHub login to filter by.",
|
|
2159
|
+
type: "string"
|
|
2160
|
+
},
|
|
2161
|
+
{
|
|
2162
|
+
name: "as",
|
|
2163
|
+
label: "Identity",
|
|
2164
|
+
description: "Configured GitHub identity token to use.",
|
|
2165
|
+
type: "enum",
|
|
2166
|
+
default: "agent",
|
|
2167
|
+
options: [
|
|
2168
|
+
{ label: "Agent", value: "agent" },
|
|
2169
|
+
{ label: "User", value: "user" }
|
|
2170
|
+
]
|
|
2171
|
+
},
|
|
2172
|
+
{
|
|
2173
|
+
name: "accountId",
|
|
2174
|
+
label: "Account",
|
|
2175
|
+
description: "Optional configured GitHub account id. Defaults by identity role.",
|
|
2176
|
+
type: "string"
|
|
2177
|
+
},
|
|
2178
|
+
{
|
|
2179
|
+
name: "limit",
|
|
2180
|
+
label: "Limit",
|
|
2181
|
+
description: "Maximum pull requests to return.",
|
|
2182
|
+
type: "number",
|
|
2183
|
+
default: 50
|
|
1978
2184
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2185
|
+
],
|
|
2186
|
+
resultSchemaSummary: "PRSummary[] with repo, number, title, author, state, and url.",
|
|
2187
|
+
capabilities: ["pull-requests", "issues-search", "repositories"],
|
|
2188
|
+
source: "plugin:github",
|
|
2189
|
+
serviceType: "github"
|
|
2190
|
+
};
|
|
2191
|
+
function hasSearchCategory(runtime, category) {
|
|
2192
|
+
try {
|
|
2193
|
+
runtime.getSearchCategory(category, { includeDisabled: true });
|
|
2194
|
+
return true;
|
|
2195
|
+
} catch {
|
|
2196
|
+
return false;
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
function registerGitHubSearchCategory(runtime) {
|
|
2200
|
+
if (!hasSearchCategory(runtime, GITHUB_PULL_REQUESTS_SEARCH_CATEGORY.category)) {
|
|
2201
|
+
runtime.registerSearchCategory(GITHUB_PULL_REQUESTS_SEARCH_CATEGORY);
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
// src/services/github-service.ts
|
|
2206
|
+
import { logger as logger6, Service } from "@elizaos/core";
|
|
2207
|
+
import { Octokit } from "@octokit/rest";
|
|
2208
|
+
function normalizeSelector(selector) {
|
|
2209
|
+
if (selector === "user" || selector === "agent") {
|
|
2210
|
+
return { role: selector };
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
accountId: typeof selector.accountId === "string" && selector.accountId.trim() ? selector.accountId.trim() : void 0,
|
|
2214
|
+
role: selector.role ?? selector.as ?? "agent"
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
2217
|
+
var GitHubService = class _GitHubService extends Service {
|
|
2218
|
+
constructor(runtime, createClient = (auth) => new Octokit({ auth })) {
|
|
2219
|
+
super(runtime);
|
|
2220
|
+
this.createClient = createClient;
|
|
2221
|
+
}
|
|
2222
|
+
createClient;
|
|
2223
|
+
static serviceType = GITHUB_SERVICE_TYPE;
|
|
2224
|
+
capabilityDescription = "GitHub REST API integration for PRs, issues, and notifications";
|
|
2225
|
+
clients = /* @__PURE__ */ new Map();
|
|
2226
|
+
static async start(runtime, createClient) {
|
|
2227
|
+
const service = new _GitHubService(runtime, createClient);
|
|
2228
|
+
await service.initialize();
|
|
2229
|
+
return service;
|
|
2230
|
+
}
|
|
2231
|
+
async initialize() {
|
|
2232
|
+
if (!this.runtime) {
|
|
2233
|
+
return;
|
|
2234
|
+
}
|
|
2235
|
+
const accounts = await readGitHubAccountsWithConnectorCredentials(this.runtime);
|
|
2236
|
+
this.clients.clear();
|
|
2237
|
+
for (const account of accounts) {
|
|
2238
|
+
this.clients.set(account.accountId, {
|
|
2239
|
+
account,
|
|
2240
|
+
client: this.createClient(account.token)
|
|
2001
2241
|
});
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
`**Stars:** ${repo.stargazersCount} | **Forks:** ${repo.forksCount}`,
|
|
2009
|
-
`**Open Issues:** ${repo.openIssuesCount}`,
|
|
2010
|
-
""
|
|
2011
|
-
];
|
|
2012
|
-
if (issues.length > 0) {
|
|
2013
|
-
parts.push("### Recent Open Issues");
|
|
2014
|
-
for (const issue of issues) {
|
|
2015
|
-
const labels = issue.labels.map((l) => l.name).join(", ");
|
|
2016
|
-
parts.push(`- #${issue.number}: ${issue.title}${labels ? ` [${labels}]` : ""}`);
|
|
2017
|
-
}
|
|
2018
|
-
parts.push("");
|
|
2019
|
-
}
|
|
2020
|
-
if (pullRequests.length > 0) {
|
|
2021
|
-
parts.push("### Recent Open Pull Requests");
|
|
2022
|
-
for (const pr of pullRequests) {
|
|
2023
|
-
const status = pr.draft ? "[DRAFT] " : "";
|
|
2024
|
-
parts.push(`- #${pr.number}: ${status}${pr.title} (${pr.head.ref} → ${pr.base.ref})`);
|
|
2025
|
-
}
|
|
2026
|
-
parts.push("");
|
|
2242
|
+
}
|
|
2243
|
+
for (const role of ["user", "agent"]) {
|
|
2244
|
+
if (!resolveGitHubAccount(accounts, { role })) {
|
|
2245
|
+
logger6.info(
|
|
2246
|
+
`[GitHubService] no GitHub ${role} account configured \u2014 ${role}-acting calls will be rejected`
|
|
2247
|
+
);
|
|
2027
2248
|
}
|
|
2028
|
-
return { text: parts.join(`
|
|
2029
|
-
`) };
|
|
2030
|
-
} catch (error) {
|
|
2031
|
-
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
2032
|
-
return {
|
|
2033
|
-
text: `Unable to fetch GitHub repository state: ${errorMessage}`
|
|
2034
|
-
};
|
|
2035
2249
|
}
|
|
2250
|
+
logger6.info(
|
|
2251
|
+
`[GitHubService] configured ${this.clients.size} GitHub account(s)`
|
|
2252
|
+
);
|
|
2253
|
+
}
|
|
2254
|
+
getOctokit(selector) {
|
|
2255
|
+
const selection = normalizeSelector(selector);
|
|
2256
|
+
const account = resolveGitHubAccount(
|
|
2257
|
+
Array.from(this.clients.values()).map((record) => record.account),
|
|
2258
|
+
selection
|
|
2259
|
+
);
|
|
2260
|
+
if (!account) {
|
|
2261
|
+
return null;
|
|
2262
|
+
}
|
|
2263
|
+
return this.clients.get(account.accountId)?.client ?? null;
|
|
2264
|
+
}
|
|
2265
|
+
/**
|
|
2266
|
+
* Allows tests to inject an Octokit-shaped mock without going through
|
|
2267
|
+
* environment variables. Not part of the public runtime contract.
|
|
2268
|
+
*/
|
|
2269
|
+
setClientForTesting(as, client, accountId = defaultGitHubAccountIdForRole(as)) {
|
|
2270
|
+
if (!client) {
|
|
2271
|
+
this.clients.delete(accountId);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2274
|
+
this.clients.set(accountId, {
|
|
2275
|
+
account: { accountId, role: as, token: "test" },
|
|
2276
|
+
client
|
|
2277
|
+
});
|
|
2278
|
+
}
|
|
2279
|
+
async stop() {
|
|
2280
|
+
this.clients.clear();
|
|
2036
2281
|
}
|
|
2037
2282
|
};
|
|
2038
|
-
// providers/index.ts
|
|
2039
|
-
var allProviders = [repositoryStateProvider, issueContextProvider];
|
|
2040
2283
|
|
|
2041
|
-
// index.ts
|
|
2284
|
+
// src/index.ts
|
|
2285
|
+
function createGitHubRouteHandler(method) {
|
|
2286
|
+
return async (req, res, runtime) => {
|
|
2287
|
+
const httpReq = req;
|
|
2288
|
+
const httpRes = res;
|
|
2289
|
+
const url = new URL(httpReq.url ?? "/api/github/token", "http://localhost");
|
|
2290
|
+
void runtime;
|
|
2291
|
+
await handleGitHubRoutes({
|
|
2292
|
+
req: httpReq,
|
|
2293
|
+
res: httpRes,
|
|
2294
|
+
method,
|
|
2295
|
+
pathname: url.pathname
|
|
2296
|
+
});
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
var githubRoutes = [
|
|
2300
|
+
{
|
|
2301
|
+
type: "GET",
|
|
2302
|
+
path: "/api/github/token",
|
|
2303
|
+
rawPath: true,
|
|
2304
|
+
handler: createGitHubRouteHandler("GET")
|
|
2305
|
+
},
|
|
2306
|
+
{
|
|
2307
|
+
type: "POST",
|
|
2308
|
+
path: "/api/github/token",
|
|
2309
|
+
rawPath: true,
|
|
2310
|
+
handler: createGitHubRouteHandler("POST")
|
|
2311
|
+
},
|
|
2312
|
+
{
|
|
2313
|
+
type: "DELETE",
|
|
2314
|
+
path: "/api/github/token",
|
|
2315
|
+
rawPath: true,
|
|
2316
|
+
handler: createGitHubRouteHandler("DELETE")
|
|
2317
|
+
}
|
|
2318
|
+
];
|
|
2042
2319
|
var githubPlugin = {
|
|
2043
2320
|
name: "github",
|
|
2044
|
-
description: "GitHub integration for
|
|
2321
|
+
description: "GitHub integration for pull requests, issues, and notification triage",
|
|
2045
2322
|
services: [GitHubService],
|
|
2046
|
-
actions:
|
|
2047
|
-
|
|
2323
|
+
actions: [...promoteSubactionsToActions(githubAction)],
|
|
2324
|
+
routes: githubRoutes,
|
|
2048
2325
|
init: async (_config, runtime) => {
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2326
|
+
registerGitHubSearchCategory(runtime);
|
|
2327
|
+
try {
|
|
2328
|
+
const manager = getConnectorAccountManager2(runtime);
|
|
2329
|
+
manager.registerProvider(createGitHubConnectorAccountProvider(runtime));
|
|
2330
|
+
} catch (err) {
|
|
2331
|
+
logger7.warn(
|
|
2332
|
+
{
|
|
2333
|
+
src: "plugin:github",
|
|
2334
|
+
err: err instanceof Error ? err.message : String(err)
|
|
2335
|
+
},
|
|
2336
|
+
"Failed to register GitHub provider with ConnectorAccountManager"
|
|
2337
|
+
);
|
|
2056
2338
|
}
|
|
2057
2339
|
}
|
|
2058
2340
|
};
|
|
2059
|
-
var
|
|
2341
|
+
var index_default = githubPlugin;
|
|
2060
2342
|
export {
|
|
2061
|
-
|
|
2343
|
+
DEFAULT_GITHUB_AGENT_ACCOUNT_ID,
|
|
2344
|
+
DEFAULT_GITHUB_USER_ACCOUNT_ID,
|
|
2345
|
+
GITHUB_SERVICE_TYPE,
|
|
2346
|
+
GitHubActions,
|
|
2347
|
+
GitHubService,
|
|
2348
|
+
createGitHubConnectorAccountProvider,
|
|
2349
|
+
index_default as default,
|
|
2350
|
+
defaultGitHubAccountIdForRole,
|
|
2351
|
+
githubAction,
|
|
2352
|
+
githubPlugin,
|
|
2353
|
+
issueOpAction,
|
|
2354
|
+
normalizeGitHubAccountId,
|
|
2355
|
+
notificationTriageAction,
|
|
2356
|
+
prOpAction,
|
|
2357
|
+
readGitHubAccounts,
|
|
2358
|
+
readGitHubAccountsWithConnectorCredentials,
|
|
2359
|
+
resolveGitHubAccount,
|
|
2360
|
+
resolveGitHubAccountSelection,
|
|
2361
|
+
scoreNotification
|
|
2062
2362
|
};
|
|
2063
|
-
|
|
2064
|
-
//# debugId=D24EE671934108D364756E2164756E21
|