@open-everywhere/provider-github 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/NOTICE +21 -0
- package/README.md +50 -2
- package/agent/channels/github-catch-up.ts +134 -0
- package/agent/channels/github-engagement.ts +381 -0
- package/agent/channels/github-identity.ts +376 -0
- package/agent/channels/github-installations.ts +152 -0
- package/agent/channels/github-message-ops.ts +62 -0
- package/agent/channels/github-origin-codec.ts +42 -0
- package/agent/channels/github-post-arm.ts +422 -0
- package/agent/channels/github-reaction-feedback.ts +489 -0
- package/agent/channels/github-reaction-scope.ts +312 -0
- package/agent/channels/github-read.ts +471 -0
- package/agent/channels/github-stop.ts +175 -0
- package/agent/channels/github-supervisor.ts +645 -0
- package/agent/channels/github-webhook-verifier.ts +56 -0
- package/agent/channels/github.ts +1458 -0
- package/agent/contributions.ts +31 -0
- package/agent/docs-generated.ts +15 -0
- package/agent/memory/memory.ts +265 -0
- package/agent/ops.ts +17 -0
- package/agent/provider-events.ts +114 -0
- package/agent/register.ts +130 -0
- package/agent/surface-parity.ts +116 -0
- package/dist/agent/channels/github-catch-up.d.ts +29 -0
- package/dist/agent/channels/github-catch-up.d.ts.map +1 -0
- package/dist/agent/channels/github-catch-up.js +105 -0
- package/dist/agent/channels/github-catch-up.js.map +1 -0
- package/dist/agent/channels/github-engagement.d.ts +223 -0
- package/dist/agent/channels/github-engagement.d.ts.map +1 -0
- package/dist/agent/channels/github-engagement.js +317 -0
- package/dist/agent/channels/github-engagement.js.map +1 -0
- package/dist/agent/channels/github-identity.d.ts +164 -0
- package/dist/agent/channels/github-identity.d.ts.map +1 -0
- package/dist/agent/channels/github-identity.js +295 -0
- package/dist/agent/channels/github-identity.js.map +1 -0
- package/dist/agent/channels/github-installations.d.ts +49 -0
- package/dist/agent/channels/github-installations.d.ts.map +1 -0
- package/dist/agent/channels/github-installations.js +114 -0
- package/dist/agent/channels/github-installations.js.map +1 -0
- package/dist/agent/channels/github-message-ops.d.ts +3 -0
- package/dist/agent/channels/github-message-ops.d.ts.map +1 -0
- package/dist/agent/channels/github-message-ops.js +58 -0
- package/dist/agent/channels/github-message-ops.js.map +1 -0
- package/dist/agent/channels/github-origin-codec.d.ts +7 -0
- package/dist/agent/channels/github-origin-codec.d.ts.map +1 -0
- package/dist/agent/channels/github-origin-codec.js +36 -0
- package/dist/agent/channels/github-origin-codec.js.map +1 -0
- package/dist/agent/channels/github-post-arm.d.ts +54 -0
- package/dist/agent/channels/github-post-arm.d.ts.map +1 -0
- package/dist/agent/channels/github-post-arm.js +339 -0
- package/dist/agent/channels/github-post-arm.js.map +1 -0
- package/dist/agent/channels/github-reaction-feedback.d.ts +97 -0
- package/dist/agent/channels/github-reaction-feedback.d.ts.map +1 -0
- package/dist/agent/channels/github-reaction-feedback.js +380 -0
- package/dist/agent/channels/github-reaction-feedback.js.map +1 -0
- package/dist/agent/channels/github-reaction-scope.d.ts +102 -0
- package/dist/agent/channels/github-reaction-scope.d.ts.map +1 -0
- package/dist/agent/channels/github-reaction-scope.js +296 -0
- package/dist/agent/channels/github-reaction-scope.js.map +1 -0
- package/dist/agent/channels/github-read.d.ts +135 -0
- package/dist/agent/channels/github-read.d.ts.map +1 -0
- package/dist/agent/channels/github-read.js +371 -0
- package/dist/agent/channels/github-read.js.map +1 -0
- package/dist/agent/channels/github-stop.d.ts +52 -0
- package/dist/agent/channels/github-stop.d.ts.map +1 -0
- package/dist/agent/channels/github-stop.js +145 -0
- package/dist/agent/channels/github-stop.js.map +1 -0
- package/dist/agent/channels/github-supervisor.d.ts +308 -0
- package/dist/agent/channels/github-supervisor.d.ts.map +1 -0
- package/dist/agent/channels/github-supervisor.js +339 -0
- package/dist/agent/channels/github-supervisor.js.map +1 -0
- package/dist/agent/channels/github-webhook-verifier.d.ts +5 -0
- package/dist/agent/channels/github-webhook-verifier.d.ts.map +1 -0
- package/dist/agent/channels/github-webhook-verifier.js +55 -0
- package/dist/agent/channels/github-webhook-verifier.js.map +1 -0
- package/dist/agent/channels/github.d.ts +139 -0
- package/dist/agent/channels/github.d.ts.map +1 -0
- package/dist/agent/channels/github.js +1232 -0
- package/dist/agent/channels/github.js.map +1 -0
- package/dist/agent/contributions.d.ts +12 -0
- package/dist/agent/contributions.d.ts.map +1 -0
- package/dist/agent/contributions.js +31 -0
- package/dist/agent/contributions.js.map +1 -0
- package/dist/agent/docs-generated.d.ts +3 -0
- package/dist/agent/docs-generated.d.ts.map +1 -0
- package/dist/agent/docs-generated.js +13 -0
- package/dist/agent/docs-generated.js.map +1 -0
- package/dist/agent/memory/memory.d.ts +54 -0
- package/dist/agent/memory/memory.d.ts.map +1 -0
- package/dist/agent/memory/memory.js +224 -0
- package/dist/agent/memory/memory.js.map +1 -0
- package/dist/agent/ops.d.ts +3 -0
- package/dist/agent/ops.d.ts.map +1 -0
- package/dist/agent/ops.js +16 -0
- package/dist/agent/ops.js.map +1 -0
- package/dist/agent/provider-events.d.ts +3 -0
- package/dist/agent/provider-events.d.ts.map +1 -0
- package/dist/agent/provider-events.js +112 -0
- package/dist/agent/provider-events.js.map +1 -0
- package/dist/agent/register.d.ts +4 -0
- package/dist/agent/register.d.ts.map +1 -0
- package/dist/agent/register.js +116 -0
- package/dist/agent/register.js.map +1 -0
- package/dist/agent/surface-parity.d.ts +3 -0
- package/dist/agent/surface-parity.d.ts.map +1 -0
- package/dist/agent/surface-parity.js +110 -0
- package/dist/agent/surface-parity.js.map +1 -0
- package/package.json +156 -4
- package/tsconfig.build.json +22 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
// Pure GitHub→OpenEverywhere identity capture. The GitHub channel (github.ts) calls
|
|
2
|
+
// these from its `onComment` hook to stow RAW GitHub facts (repo owner/name + privacy +
|
|
3
|
+
// conversation anchor + actor) into the session auth context's `attributes`. The
|
|
4
|
+
// INTERPRETATION of those facts into visibility/trust lives here too (githubVisibility/
|
|
5
|
+
// githubTrust) — kept beside the capture so the mapper in agent/identity/binding.ts
|
|
6
|
+
// (bindGitHub) just calls them, exactly as Slack/Teams split capture from
|
|
7
|
+
// interpretation. This file imports no eve types, so it's trivially unit-testable.
|
|
8
|
+
//
|
|
9
|
+
// The two security-critical decisions are ONE tested place each and both FAIL CLOSED:
|
|
10
|
+
// - githubVisibility: "is this a world-readable (public-repo) surface whose learnings
|
|
11
|
+
// may flow up to shared memory?" — only an explicit public repo yields `public`.
|
|
12
|
+
// - githubTrust: "is this surface positively inside the org trust boundary?" — only a
|
|
13
|
+
// PRIVATE repo touched by an org OWNER/MEMBER yields `internal`; everything else
|
|
14
|
+
// (public repos, outside collaborators, missing signals) → no verdict, so bindFrom
|
|
15
|
+
// binds it `external`.
|
|
16
|
+
//
|
|
17
|
+
// GitHub has no 1:1 DM surface (every issue/PR/review thread is a shared conversation on
|
|
18
|
+
// a repo), so mode is always "channel" — bindGitHub inlines that, no helper needed.
|
|
19
|
+
//
|
|
20
|
+
// The IDENTITY MAPPER (bindGitHub) lives here too (moved out of core identity/binding.ts in
|
|
21
|
+
// OPE-114 stage 4, mirroring the notion bundle): registerGithubProvider() registers it via the
|
|
22
|
+
// registerIdentityMapper seam. The only imports are TYPE-ONLY barrel contracts (erased at
|
|
23
|
+
// compile), so the file stays trivially unit-testable.
|
|
24
|
+
/** Build the auth context OpenEverywhere dispatches with — raw facts as string attributes. */
|
|
25
|
+
export function buildGitHubAuth(input) {
|
|
26
|
+
const attributes = {
|
|
27
|
+
owner: input.owner,
|
|
28
|
+
repo: input.repo,
|
|
29
|
+
user_login: input.actorLogin,
|
|
30
|
+
};
|
|
31
|
+
if (input.repositoryFullName)
|
|
32
|
+
attributes.repository = input.repositoryFullName;
|
|
33
|
+
if (typeof input.repositoryId === "number")
|
|
34
|
+
attributes.repository_id = String(input.repositoryId);
|
|
35
|
+
if (typeof input.isPrivate === "boolean")
|
|
36
|
+
attributes.repo_private = String(input.isPrivate);
|
|
37
|
+
if (input.conversationKind)
|
|
38
|
+
attributes.conversation_kind = input.conversationKind;
|
|
39
|
+
if (typeof input.issueNumber === "number")
|
|
40
|
+
attributes.issue_number = String(input.issueNumber);
|
|
41
|
+
if (typeof input.pullRequestNumber === "number")
|
|
42
|
+
attributes.pull_request_number = String(input.pullRequestNumber);
|
|
43
|
+
if (typeof input.reviewThreadRootCommentId === "number")
|
|
44
|
+
attributes.review_thread_root_comment_id = String(input.reviewThreadRootCommentId);
|
|
45
|
+
if (typeof input.installationId === "number")
|
|
46
|
+
attributes.installation_id = String(input.installationId);
|
|
47
|
+
if (typeof input.actorId === "number")
|
|
48
|
+
attributes.user_id = String(input.actorId);
|
|
49
|
+
if (input.actorType)
|
|
50
|
+
attributes.user_type = input.actorType;
|
|
51
|
+
if (input.authorAssociation)
|
|
52
|
+
attributes.author_association = input.authorAssociation;
|
|
53
|
+
return {
|
|
54
|
+
authenticator: "github",
|
|
55
|
+
principalId: input.actorLogin,
|
|
56
|
+
principalType: input.actorType === "Bot" ? "service" : "github-user",
|
|
57
|
+
attributes,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A comment @mentions the bot and should wake a turn. Faithfully mirrors eve's
|
|
62
|
+
* internal gate (shouldDispatchGitHubComment: isIgnoredGitHubComment +
|
|
63
|
+
* extractGitHubCommentTrigger), which is NOT on eve's public export surface — so we
|
|
64
|
+
* replicate it here (small, deterministic, and unit-tested) rather than reach into a
|
|
65
|
+
* package-internal path. Kept byte-consistent with eve because the channel's message
|
|
66
|
+
* layer still strips the mention token via eve's own extractGitHubCommentTrigger; a
|
|
67
|
+
* divergent gate here would fire turns eve then can't strip. FAIL CLOSED: no botName,
|
|
68
|
+
* a bot author, the bot's own comments, or eve's marker → no wake.
|
|
69
|
+
*/
|
|
70
|
+
export function githubBotMentioned(body, botName, author) {
|
|
71
|
+
const name = botName?.trim();
|
|
72
|
+
if (!name || !body)
|
|
73
|
+
return false;
|
|
74
|
+
if (body.includes("<!-- eve:github:"))
|
|
75
|
+
return false; // eve's own comment marker — never self-trigger
|
|
76
|
+
if (author?.type === "Bot")
|
|
77
|
+
return false; // ignore bot actors (loop guard)
|
|
78
|
+
if (author?.login && author.login.toLowerCase() === `${name}[bot]`.toLowerCase())
|
|
79
|
+
return false; // the bot itself
|
|
80
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
81
|
+
// "@name" not followed by a word char — matches eve's `@${name}(?=$|[^A-Za-z0-9_-])`.
|
|
82
|
+
return new RegExp(`@${escaped}(?=$|[^A-Za-z0-9_-])`, "iu").test(body);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* GitHub repo privacy → visibility. FAIL CLOSED, mirroring slackVisibility/
|
|
86
|
+
* linearVisibility: only an explicitly PUBLIC repo (`repo_private="false"`) yields
|
|
87
|
+
* `public`; a private repo (`"true"`) or an unknown signal → `private`, so an
|
|
88
|
+
* under-identified session or a private repo never writes its learnings up to shared
|
|
89
|
+
* (workspace) memory. A public repo's issue/PR threads are world-readable, so treating
|
|
90
|
+
* them as a public surface (learnings may flow to the workspace brain across the org's
|
|
91
|
+
* repos) is correct; anything we can't confirm public stays surface-tier private.
|
|
92
|
+
*/
|
|
93
|
+
export function githubVisibility(repoPrivate) {
|
|
94
|
+
if (repoPrivate === "false")
|
|
95
|
+
return "public";
|
|
96
|
+
return "private"; // "true" or unknown → fail closed
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* GitHub actor + repo privacy → trust (the shared-memory gate). FAIL CLOSED — only a
|
|
100
|
+
* POSITIVE in-boundary fact yields a verdict:
|
|
101
|
+
* - A PRIVATE repo (only org members + invited collaborators can see it) touched by an
|
|
102
|
+
* org OWNER or MEMBER → `internal`. OWNER/MEMBER are GitHub's strong "part of the org
|
|
103
|
+
* that owns this repo" signals (`author_association`); on a private repo that means an
|
|
104
|
+
* insider acting inside the boundary, so shared/org memory is safe to read.
|
|
105
|
+
* - ANYTHING ELSE → `undefined`, so bindFrom FAILS CLOSED to `external`:
|
|
106
|
+
* · a PUBLIC repo — world-readable, anyone (external contributors) can comment, so it
|
|
107
|
+
* must never light up the org brain regardless of who this particular actor is;
|
|
108
|
+
* · a private repo touched by a COLLABORATOR (may be an OUTSIDE collaborator),
|
|
109
|
+
* CONTRIBUTOR, first-timer, mannequin, NONE, or a missing/unknown association;
|
|
110
|
+
* · a missing repo-privacy signal.
|
|
111
|
+
* The operator can still vouch for a deployment with OE_TRUST=internal — the same layer
|
|
112
|
+
* Slack-DM/Teams/Discord/Linear use. An ambiguous GitHub event must never reach
|
|
113
|
+
* the shared org brain. (Per-repo collaborator-membership enrichment — GET
|
|
114
|
+
* /repos/{o}/{r}/collaborators/{u}/permission — is the future layer-2 gate that could
|
|
115
|
+
* promote a verified internal collaborator; until then, only OWNER/MEMBER on a private
|
|
116
|
+
* repo is trusted.)
|
|
117
|
+
*/
|
|
118
|
+
export function githubTrust(repoPrivate, authorAssociation) {
|
|
119
|
+
if (repoPrivate !== "true")
|
|
120
|
+
return undefined; // public / unknown repo → no verdict (binds external)
|
|
121
|
+
if (authorAssociation === "OWNER" || authorAssociation === "MEMBER")
|
|
122
|
+
return "internal";
|
|
123
|
+
return undefined; // collaborator/contributor/outside/unknown on a private repo → fail closed
|
|
124
|
+
}
|
|
125
|
+
// --- the EXTERNAL-AUTHOR grain (OPE-533) --------------------------------------------------------
|
|
126
|
+
//
|
|
127
|
+
// THE EVIDENCE IS FREE. GitHub reports `author_association` on every `issue_comment` payload, and
|
|
128
|
+
// {@link buildGitHubAuth} already stows it. The author grain needs no new call and no new
|
|
129
|
+
// installation permission — the org-membership probe (`orgs/{org}/members/{user}`) would need the
|
|
130
|
+
// "Members" permission, which every installed org would have to re-approve.
|
|
131
|
+
//
|
|
132
|
+
// 🔴 THE SET IS A RULING, NOT AN INFERENCE (Tommy, 2026-08-14). INTERNAL is the three values that
|
|
133
|
+
// each carry a DELIBERATE ACCESS GRANT on the repo: `OWNER`, `MEMBER`, `COLLABORATOR`. Every other
|
|
134
|
+
// value is EXCLUDED. Tommy verbatim: "contributor shouldn't be trusted" — a past merged PR is not an
|
|
135
|
+
// access grant. `MANNEQUIN` is an import placeholder with no live person behind it, so it is
|
|
136
|
+
// excluded as hygiene.
|
|
137
|
+
//
|
|
138
|
+
// 🔴 THIS IS A DIFFERENT QUESTION FROM {@link githubTrust}, AND THE TWO MUST NOT BE MERGED.
|
|
139
|
+
// `githubTrust` asks "may this session read the shared org brain?" and fails CLOSED, so it demands
|
|
140
|
+
// a PRIVATE repo AND an OWNER/MEMBER author. This asks "may this person's bytes task the agent?"
|
|
141
|
+
// and fails OPEN. A COLLABORATOR is internal here and still not trusted there, which is correct:
|
|
142
|
+
// denying service is a different act from opening the org's memory.
|
|
143
|
+
/** `author_association` values that carry a deliberate access grant on the repo — the INTERNAL set. */
|
|
144
|
+
export const GITHUB_INTERNAL_ASSOCIATIONS = ["OWNER", "MEMBER", "COLLABORATOR"];
|
|
145
|
+
/** `author_association` values that carry NO access grant — the EXCLUDED (external) set. This is
|
|
146
|
+
* GitHub's whole documented enum minus {@link GITHUB_INTERNAL_ASSOCIATIONS}, written out rather
|
|
147
|
+
* than derived by negation: see {@link githubAssociationRefutes} for why the difference matters. */
|
|
148
|
+
export const GITHUB_EXTERNAL_ASSOCIATIONS = [
|
|
149
|
+
"CONTRIBUTOR",
|
|
150
|
+
"FIRST_TIME_CONTRIBUTOR",
|
|
151
|
+
"FIRST_TIMER",
|
|
152
|
+
"NONE",
|
|
153
|
+
"MANNEQUIN",
|
|
154
|
+
];
|
|
155
|
+
/**
|
|
156
|
+
* Does this `author_association` POSITIVELY refute the author's membership?
|
|
157
|
+
*
|
|
158
|
+
* 🔴 IT MATCHES THE EXCLUDED SET, IT DOES NOT NEGATE THE INTERNAL ONE, and the difference is the
|
|
159
|
+
* whole fail-open posture. An ABSENT association (a payload shape GitHub changed, a synthesised
|
|
160
|
+
* event) and an UNRECOGNISED one (a value GitHub adds later) both answer `false` — "not refuted",
|
|
161
|
+
* which is what `IngestedChatMessage.refuted` means by absence of evidence. Negating the internal
|
|
162
|
+
* set would read every unknown as external and take the agent off the air on a payload change.
|
|
163
|
+
*
|
|
164
|
+
* Case-sensitive on purpose: GitHub sends these values upper-case, and accepting a lower-case
|
|
165
|
+
* variant would be inventing a second spelling of a vendor enum.
|
|
166
|
+
*/
|
|
167
|
+
export function githubAssociationRefutes(authorAssociation) {
|
|
168
|
+
return authorAssociation !== undefined && GITHUB_EXTERNAL_ASSOCIATIONS.includes(authorAssociation);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* The author grain's evidence for one GitHub comment, in the core's own vocabulary
|
|
172
|
+
* (`IngestedChatMessage`) so access/external-policy.ts answers the exclusion question — this bundle
|
|
173
|
+
* never re-derives it.
|
|
174
|
+
*
|
|
175
|
+
* A `Bot` actor grades as the machine floor `relay` and is therefore NEVER excluded, which is the
|
|
176
|
+
* core's own ruling (access/taint-signal.ts `nonMemberChatTrust`): a bot is below peer standing but
|
|
177
|
+
* it is not a person outside the organisation, and withholding every app's comments would be an
|
|
178
|
+
* outage. Stating it as the tier rather than as a local `if` keeps ONE place deciding.
|
|
179
|
+
*
|
|
180
|
+
* A human author carries `refuted` only when {@link githubAssociationRefutes} says so. `trust` is
|
|
181
|
+
* left ABSENT for a human on purpose: this runs before the per-wake trust resolution, so naming a
|
|
182
|
+
* tier here would be a guess, and the refutation leg needs no tier.
|
|
183
|
+
*/
|
|
184
|
+
export function githubAuthorFacts(input) {
|
|
185
|
+
if (input.actorType === "Bot")
|
|
186
|
+
return { trust: "relay" };
|
|
187
|
+
return githubAssociationRefutes(input.authorAssociation) ? { refuted: true } : {};
|
|
188
|
+
}
|
|
189
|
+
function githubAuthOf(ctx) {
|
|
190
|
+
return ctx.session?.auth?.current ?? ctx.session?.auth?.initiator ?? null;
|
|
191
|
+
}
|
|
192
|
+
function githubAttr(a, keys) {
|
|
193
|
+
if (!a)
|
|
194
|
+
return undefined;
|
|
195
|
+
for (const k of keys) {
|
|
196
|
+
const v = a[k];
|
|
197
|
+
if (typeof v === "string" && v.length > 0)
|
|
198
|
+
return v;
|
|
199
|
+
if (Array.isArray(v) && typeof v[0] === "string" && v[0].length > 0)
|
|
200
|
+
return v[0];
|
|
201
|
+
}
|
|
202
|
+
return undefined;
|
|
203
|
+
}
|
|
204
|
+
function githubAuthPrincipalId(ctx) {
|
|
205
|
+
const pid = githubAuthOf(ctx)?.principalId;
|
|
206
|
+
return pid && pid.length > 0 ? pid : undefined;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The CONVERSATION key the stop registry keys a GitHub turn by (OPE-898) — `owner/repo#number`.
|
|
210
|
+
*
|
|
211
|
+
* ONE derivation, used by BOTH sides of the affordance: {@link bindGitHub} writes it onto the anchor
|
|
212
|
+
* row at turn start, and the stop-comment leg (channels/github-stop.ts) rebuilds it from the webhook
|
|
213
|
+
* that carries the directive. Two spellings of this key would make the whole arm silently miss.
|
|
214
|
+
*
|
|
215
|
+
* It is NOT a surface key and never becomes one — the GitHub surface grain is the REPO, and this
|
|
216
|
+
* value names one issue or PR inside it. Returns undefined on any missing part: a partial key would
|
|
217
|
+
* collide across repos.
|
|
218
|
+
*/
|
|
219
|
+
export function githubConversationKey(owner, repo, number) {
|
|
220
|
+
return owner && repo && Number.isFinite(number) ? `${owner}/${repo}#${number}` : undefined;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The ROOT comment id of the review thread this turn arrived in, or `undefined` (OPE-958). The read
|
|
224
|
+
* side of {@link GitHubIdentityInput.reviewThreadRootCommentId} — this file owns the attribute
|
|
225
|
+
* spelling on BOTH sides, so a rename cannot leave the reader looking for a key nobody writes.
|
|
226
|
+
*
|
|
227
|
+
* It reads the AUTH ATTRIBUTES rather than the composed binding on purpose. `RawIdentity.target` is
|
|
228
|
+
* eve's `GitHubReceiveTarget` and throws on a member it does not know, so the root cannot ride
|
|
229
|
+
* there, and no other binding field names a comment.
|
|
230
|
+
*
|
|
231
|
+
* Returns the id as a STRING of digits, which is what `ReadAdapter.readThread` takes as its thread
|
|
232
|
+
* id. A non-numeric value is treated as absent: a review thread id that is not a comment id would
|
|
233
|
+
* fetch nothing, and refusing honestly beats reading an empty thread.
|
|
234
|
+
*/
|
|
235
|
+
export function githubReviewThreadRoot(ctx) {
|
|
236
|
+
const raw = githubAttr(githubAuthOf(ctx)?.attributes, ["review_thread_root_comment_id", "reviewThreadRootCommentId"]);
|
|
237
|
+
return raw !== undefined && /^\d+$/u.test(raw) ? raw : undefined;
|
|
238
|
+
}
|
|
239
|
+
export const bindGitHub = (ctx) => {
|
|
240
|
+
const a = githubAuthOf(ctx)?.attributes;
|
|
241
|
+
const owner = githubAttr(a, ["owner"]);
|
|
242
|
+
const repo = githubAttr(a, ["repo"]);
|
|
243
|
+
const repoPrivate = githubAttr(a, ["repo_private", "repoPrivate"]);
|
|
244
|
+
const conversationKind = githubAttr(a, ["conversation_kind", "conversationKind"]);
|
|
245
|
+
const issueNumber = githubAttr(a, ["issue_number", "issueNumber"]);
|
|
246
|
+
const pullRequestNumber = githubAttr(a, ["pull_request_number", "pullRequestNumber"]);
|
|
247
|
+
const installationId = githubAttr(a, ["installation_id", "installationId"]);
|
|
248
|
+
// Proactive post-back uses eve's GitHubReceiveTarget: { owner, repo, and EXACTLY ONE of
|
|
249
|
+
// issueNumber | pullRequestNumber } (supplying both throws). Pick the PR number for a
|
|
250
|
+
// pull_request/review_thread conversation, else the issue number. Only emitted when we
|
|
251
|
+
// have owner + repo + a usable anchor number.
|
|
252
|
+
const isPr = conversationKind === "pull_request" || conversationKind === "review_thread";
|
|
253
|
+
const anchor = isPr && pullRequestNumber
|
|
254
|
+
? { pullRequestNumber: Number(pullRequestNumber) }
|
|
255
|
+
: issueNumber
|
|
256
|
+
? { issueNumber: Number(issueNumber) }
|
|
257
|
+
: pullRequestNumber
|
|
258
|
+
? { pullRequestNumber: Number(pullRequestNumber) }
|
|
259
|
+
: undefined;
|
|
260
|
+
return {
|
|
261
|
+
workspace: owner,
|
|
262
|
+
surface: repo,
|
|
263
|
+
mode: "channel", // GitHub has no DM analog — every issue/PR/review thread is shared
|
|
264
|
+
visibility: githubVisibility(repoPrivate),
|
|
265
|
+
trust: githubTrust(repoPrivate, githubAttr(a, ["author_association", "authorAssociation"])),
|
|
266
|
+
principalId: githubAuthPrincipalId(ctx) ?? githubAttr(a, ["user_login", "userLogin", "user_id", "userId"]),
|
|
267
|
+
// OPE-811: `installationId` rides the post-back target so the mid-turn `reply` arm mints its
|
|
268
|
+
// token against THIS wake's own installation instead of falling back to the owner→installation
|
|
269
|
+
// map. Absent when the webhook carried none — the arm's map lookup is the fallback, never a guess.
|
|
270
|
+
target: owner && repo && anchor ? { owner, repo, ...anchor, ...(installationId ? { installationId: Number(installationId) } : {}) } : undefined,
|
|
271
|
+
// THE STOP REGISTRY'S ADDRESS FOR THIS TURN (OPE-898). A GitHub conversation is ONE issue or PR
|
|
272
|
+
// thread, so that is what a stop directive posted into the thread can name. It cannot ride
|
|
273
|
+
// `target`: that object is eve's `GitHubReceiveTarget` and it throws on an unexpected member.
|
|
274
|
+
//
|
|
275
|
+
// GitHub sends NO webhook for a reaction (verified against the event list, 2026-08-20), so the
|
|
276
|
+
// trigger on this surface is a bare `stop` comment read at the ingress — see
|
|
277
|
+
// channels/github-stop.ts. The key it resolves by is this one.
|
|
278
|
+
...(anchor
|
|
279
|
+
? {
|
|
280
|
+
anchorChannelId: githubConversationKey(owner, repo, "pullRequestNumber" in anchor ? anchor.pullRequestNumber : anchor.issueNumber),
|
|
281
|
+
// THE ENGAGEMENT REGISTRY'S ADDRESS FOR THIS TURN (OPE-1501) — the SAME container key, and
|
|
282
|
+
// deliberately the same derivation rather than a second spelling of it. `stand_down` marks
|
|
283
|
+
// `binding.engagementKey ?? binding.threadTs`, and GitHub carries no `threadTs`, so
|
|
284
|
+
// without this field the verb found no anchor and refused on every GitHub turn.
|
|
285
|
+
//
|
|
286
|
+
// It is a SEPARATE field from `anchorChannelId` because the two answer different
|
|
287
|
+
// questions — which running turn a stop directive cancels, and which conversation row the
|
|
288
|
+
// agent was asked to stop following — and `channels/github-engagement.ts` reads this one
|
|
289
|
+
// back through `githubEngagementAnchor`, which calls the very same function.
|
|
290
|
+
engagementKey: githubConversationKey(owner, repo, "pullRequestNumber" in anchor ? anchor.pullRequestNumber : anchor.issueNumber),
|
|
291
|
+
}
|
|
292
|
+
: {}),
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
//# sourceMappingURL=github-identity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-identity.js","sourceRoot":"","sources":["../../../agent/channels/github-identity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** One installation delivery, normalized. `effect` is the whole decision; everything else is the
|
|
2
|
+
* row/audit payload. */
|
|
3
|
+
export interface GitHubInstallationEvent {
|
|
4
|
+
readonly effect: "learn" | "revoke";
|
|
5
|
+
/** The webhook's `action` — carried verbatim into the audit row (why the effect happened). */
|
|
6
|
+
readonly action: string;
|
|
7
|
+
/** `installation.account.login` — the SCM map's key. */
|
|
8
|
+
readonly owner: string;
|
|
9
|
+
readonly installationId: string;
|
|
10
|
+
/** `installation.account.type` — "Organization" | "User". */
|
|
11
|
+
readonly accountType?: string;
|
|
12
|
+
/** `installation.repository_selection` — "all" | "selected". */
|
|
13
|
+
readonly repositorySelection?: string;
|
|
14
|
+
/** How many repos the delivery named, when it named any (diagnostics in the audit row only —
|
|
15
|
+
* the repo LIST is never stored; the App's own grant is the source of truth). */
|
|
16
|
+
readonly repositoryCount?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Normalize one webhook delivery into a {@link GitHubInstallationEvent}, or null when it isn't an
|
|
20
|
+
* installation delivery we act on. PURE — no I/O, so every branch is unit-tested.
|
|
21
|
+
*
|
|
22
|
+
* FAIL-CLOSED on an unusable payload: an unknown action, a missing `installation.id`, or a missing
|
|
23
|
+
* `installation.account.login` all return null (nothing learned, nothing revoked) rather than
|
|
24
|
+
* writing a half-formed row. An installation with no resolvable owner is unroutable anyway.
|
|
25
|
+
*/
|
|
26
|
+
export declare function extractGitHubInstallationEvent(eventType: string, payload: Record<string, unknown>): GitHubInstallationEvent | null;
|
|
27
|
+
/** The side-effecting seams, injected so the dispatch core is testable without a store or a clock. */
|
|
28
|
+
export interface GitHubInstallationDeps {
|
|
29
|
+
readonly record: (rec: {
|
|
30
|
+
owner: string;
|
|
31
|
+
installationId: string;
|
|
32
|
+
accountType?: string;
|
|
33
|
+
repositorySelection?: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
}) => Promise<void>;
|
|
36
|
+
readonly forget: (owner: string) => Promise<boolean>;
|
|
37
|
+
readonly audit: (action: string, detail: Record<string, unknown>, workspace: string) => Promise<void>;
|
|
38
|
+
readonly now?: () => string;
|
|
39
|
+
readonly log?: (msg: string) => void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Apply one normalized delivery: persist-or-clear the owner→installation entry, then audit it.
|
|
43
|
+
*
|
|
44
|
+
* ORDER IS DELIBERATE — the durable write happens FIRST and its failure propagates (the route's
|
|
45
|
+
* catch logs it), so we never audit an install we didn't actually record. The audit row is written
|
|
46
|
+
* after, best-effort at the appendAudit layer.
|
|
47
|
+
*/
|
|
48
|
+
export declare function handleGitHubInstallationDelivery(ev: GitHubInstallationEvent, deps: GitHubInstallationDeps): Promise<void>;
|
|
49
|
+
//# sourceMappingURL=github-installations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-installations.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-installations.ts"],"names":[],"mappings":"AAwBA;yBACyB;AACzB,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,OAAO,GAAG,QAAQ,CAAC;IACpC,8FAA8F;IAC9F,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,6DAA6D;IAC7D,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC;sFACkF;IAClF,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;CACnC;AAoBD;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,uBAAuB,GAAG,IAAI,CAiClI;AAED,sGAAsG;AACtG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,cAAc,EAAE,MAAM,CAAC;QACvB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACrD,QAAQ,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtG,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAsB,gCAAgC,CAAC,EAAE,EAAE,uBAAuB,EAAE,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B/H"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// The GitHub App's INSTALLATION leg — the eve-free, tested half of "an org installing the App
|
|
2
|
+
// registers itself" (OPE-118 GitHub §3; github.ts is the thin wiring, mirroring github-supervisor.ts).
|
|
3
|
+
//
|
|
4
|
+
// GitHub's App-level webhook (ONE url + ONE secret, fanning in every installation of the App across
|
|
5
|
+
// every account) delivers:
|
|
6
|
+
// • `installation` — actions `created`, `deleted`, `suspend`, `unsuspend`, `new_permissions_accepted`
|
|
7
|
+
// • `installation_repositories` — actions `added`, `removed` (the repo-scope change)
|
|
8
|
+
// Verified against docs.github.com/en/webhooks/webhook-events-and-payloads (2026-07). We map those
|
|
9
|
+
// onto exactly two effects, LEARN and REVOKE:
|
|
10
|
+
// LEARN ← created / unsuspend / new_permissions_accepted / added / removed
|
|
11
|
+
// REVOKE ← deleted / suspend
|
|
12
|
+
// `suspend` revokes because a suspended installation's tokens are refused by GitHub — keeping the
|
|
13
|
+
// row would advertise an installation that cannot mint, and `unsuspend` re-learns it. The two
|
|
14
|
+
// metadata-only actions (`new_permissions_accepted`, and both `installation_repositories` actions)
|
|
15
|
+
// re-LEARN rather than no-op so `repository_selection` on the row can't drift from reality.
|
|
16
|
+
//
|
|
17
|
+
// SIGNATURE: this module never verifies anything — it runs INSIDE the route, AFTER the one
|
|
18
|
+
// existing `gateGithubDelivery` HMAC gate (GITHUB_WEBHOOK_SECRET) that already fail-closed 401s
|
|
19
|
+
// every unverified delivery for the whole route. There is deliberately no second verification path.
|
|
20
|
+
//
|
|
21
|
+
// AUDIT: install/uninstall is a surface-lifecycle fact, so it takes the cross-platform `surface.*`
|
|
22
|
+
// action namespace (the same one slack-front/teams-front's wake_failed/wake_dropped rows use),
|
|
23
|
+
// not a github-only verb — an operator asking "who installed us, and when?" wants one query.
|
|
24
|
+
const LEARN_ACTIONS = new Set(["created", "unsuspend", "new_permissions_accepted", "added", "removed"]);
|
|
25
|
+
const REVOKE_ACTIONS = new Set(["deleted", "suspend"]);
|
|
26
|
+
function str(v) {
|
|
27
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
28
|
+
}
|
|
29
|
+
/** GitHub sends `installation.id` as a NUMBER; the mint API wants a path segment. Accept both
|
|
30
|
+
* spellings and reject anything else (never coerce an object/array into "[object Object]"). */
|
|
31
|
+
function idOf(v) {
|
|
32
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
33
|
+
return String(v);
|
|
34
|
+
return str(v);
|
|
35
|
+
}
|
|
36
|
+
function countOf(v) {
|
|
37
|
+
return Array.isArray(v) ? v.length : undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Normalize one webhook delivery into a {@link GitHubInstallationEvent}, or null when it isn't an
|
|
41
|
+
* installation delivery we act on. PURE — no I/O, so every branch is unit-tested.
|
|
42
|
+
*
|
|
43
|
+
* FAIL-CLOSED on an unusable payload: an unknown action, a missing `installation.id`, or a missing
|
|
44
|
+
* `installation.account.login` all return null (nothing learned, nothing revoked) rather than
|
|
45
|
+
* writing a half-formed row. An installation with no resolvable owner is unroutable anyway.
|
|
46
|
+
*/
|
|
47
|
+
export function extractGitHubInstallationEvent(eventType, payload) {
|
|
48
|
+
if (eventType !== "installation" && eventType !== "installation_repositories")
|
|
49
|
+
return null;
|
|
50
|
+
const action = str(payload["action"]);
|
|
51
|
+
if (!action)
|
|
52
|
+
return null;
|
|
53
|
+
const effect = LEARN_ACTIONS.has(action) ? "learn" : REVOKE_ACTIONS.has(action) ? "revoke" : undefined;
|
|
54
|
+
if (!effect)
|
|
55
|
+
return null;
|
|
56
|
+
const installation = payload["installation"];
|
|
57
|
+
if (typeof installation !== "object" || installation === null)
|
|
58
|
+
return null;
|
|
59
|
+
const inst = installation;
|
|
60
|
+
const installationId = idOf(inst["id"]);
|
|
61
|
+
if (!installationId)
|
|
62
|
+
return null;
|
|
63
|
+
const account = typeof inst["account"] === "object" && inst["account"] !== null ? inst["account"] : undefined;
|
|
64
|
+
const owner = str(account?.["login"]);
|
|
65
|
+
if (!owner)
|
|
66
|
+
return null;
|
|
67
|
+
// `repository_selection` sits on the installation for `installation`, and is re-sent at the TOP
|
|
68
|
+
// level of an `installation_repositories` delivery (that event's whole point is the change) —
|
|
69
|
+
// prefer the top-level value so a scope change is reflected, not the pre-change installation copy.
|
|
70
|
+
const repositorySelection = str(payload["repository_selection"]) ?? str(inst["repository_selection"]);
|
|
71
|
+
const repositoryCount = countOf(payload["repositories"]) ?? countOf(payload["repositories_added"]) ?? countOf(payload["repositories_removed"]);
|
|
72
|
+
return {
|
|
73
|
+
effect,
|
|
74
|
+
action,
|
|
75
|
+
owner,
|
|
76
|
+
installationId,
|
|
77
|
+
...(str(account?.["type"]) ? { accountType: str(account?.["type"]) } : {}),
|
|
78
|
+
...(repositorySelection ? { repositorySelection } : {}),
|
|
79
|
+
...(repositoryCount !== undefined ? { repositoryCount } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Apply one normalized delivery: persist-or-clear the owner→installation entry, then audit it.
|
|
84
|
+
*
|
|
85
|
+
* ORDER IS DELIBERATE — the durable write happens FIRST and its failure propagates (the route's
|
|
86
|
+
* catch logs it), so we never audit an install we didn't actually record. The audit row is written
|
|
87
|
+
* after, best-effort at the appendAudit layer.
|
|
88
|
+
*/
|
|
89
|
+
export async function handleGitHubInstallationDelivery(ev, deps) {
|
|
90
|
+
const at = deps.now ? deps.now() : new Date().toISOString();
|
|
91
|
+
const detail = {
|
|
92
|
+
action: ev.action,
|
|
93
|
+
installationId: ev.installationId,
|
|
94
|
+
...(ev.accountType ? { accountType: ev.accountType } : {}),
|
|
95
|
+
...(ev.repositorySelection ? { repositorySelection: ev.repositorySelection } : {}),
|
|
96
|
+
...(ev.repositoryCount !== undefined ? { repositoryCount: ev.repositoryCount } : {}),
|
|
97
|
+
};
|
|
98
|
+
if (ev.effect === "revoke") {
|
|
99
|
+
const removed = await deps.forget(ev.owner);
|
|
100
|
+
deps.log?.(`installation ${ev.action} for ${ev.owner} — ${removed ? "discovered entry cleared" : "no discovered entry to clear"}`);
|
|
101
|
+
await deps.audit("surface.app_uninstalled", { ...detail, removed }, ev.owner);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await deps.record({
|
|
105
|
+
owner: ev.owner,
|
|
106
|
+
installationId: ev.installationId,
|
|
107
|
+
...(ev.accountType ? { accountType: ev.accountType } : {}),
|
|
108
|
+
...(ev.repositorySelection ? { repositorySelection: ev.repositorySelection } : {}),
|
|
109
|
+
updatedAt: at,
|
|
110
|
+
});
|
|
111
|
+
deps.log?.(`installation ${ev.action} for ${ev.owner} → installation ${ev.installationId} registered (no env edit needed)`);
|
|
112
|
+
await deps.audit("surface.app_installed", detail, ev.owner);
|
|
113
|
+
}
|
|
114
|
+
//# sourceMappingURL=github-installations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-installations.js","sourceRoot":"","sources":["../../../agent/channels/github-installations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-message-ops.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-message-ops.ts"],"names":[],"mappings":"AAkCA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,4CAA4C,CAAC;AAYpF,eAAO,MAAM,gBAAgB,EAAE,iBAe9B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// github-message-ops — GitHub's SurfaceMessageOps (OPE-899): the ancillary message ops core
|
|
2
|
+
// features dispatch through channels/surface-message-ops.ts. Before this file, GitHub registered
|
|
3
|
+
// none, so the stop acknowledgement (turn-anchor.ts's `postSurfaceText`) resolved nothing on a
|
|
4
|
+
// GitHub turn — the stop control worked (github-stop.ts, the bare `stop` comment), it was just
|
|
5
|
+
// invisible: a person who pressed it saw the agent go quiet and read nothing.
|
|
6
|
+
//
|
|
7
|
+
// - postText → the SAME issue/PR-timeline comment POST the reply spine uses
|
|
8
|
+
// (github-post-arm.ts's `liveGitHubPost`, exported for this file — one mint/fetch
|
|
9
|
+
// implementation, never a second copy of it).
|
|
10
|
+
// - editMessage → the SAME comment PATCH the `edit_reply` verb uses (`liveGitHubEditComment`).
|
|
11
|
+
// - NO `postFiles` (OPE-899's registry ruling): GitHub has no API for attaching a file to a
|
|
12
|
+
// comment — web upload only. The optional tier exists exactly for this shape.
|
|
13
|
+
// - NO `setReaction`: GitHub's reaction set is the fixed eight (+1, -1, laugh, confused, heart,
|
|
14
|
+
// hooray, rocket, eyes) — there is no raised hand. The bare `stop` comment (github-stop.ts) IS
|
|
15
|
+
// GitHub's stop affordance; there is no ✋ glyph to place beside it, so this module offers no
|
|
16
|
+
// `setReaction` rather than faking one against the wrong emoji.
|
|
17
|
+
//
|
|
18
|
+
// `channelId` IS THE CONVERSATION KEY `owner/repo#number`, not a GitHub-specific pair invented
|
|
19
|
+
// here. `turn-anchor.ts`'s `turnAnchorAddress` reads it off `binding.anchorChannelId`, which
|
|
20
|
+
// `bindGitHub` (github-identity.ts) mints through `githubConversationKey` — the SAME key
|
|
21
|
+
// github-read.ts's `parseGithubContainer` already parses for the read adapter's container id. This
|
|
22
|
+
// file reuses that parser rather than writing a second one: two derivations of the same address
|
|
23
|
+
// would be exactly the "silently miss" trap github-identity.ts's own header warns about.
|
|
24
|
+
//
|
|
25
|
+
// `messageId` on `editMessage` is the COMMENT id `postText`/`liveGitHubPost` returned — GitHub's
|
|
26
|
+
// comment ids are unique across the whole repo, so `editMessage` needs only the owner/repo half of
|
|
27
|
+
// the parsed container, never the issue number.
|
|
28
|
+
//
|
|
29
|
+
// NO THREADING ON `postText`. GitHub's issue/PR-timeline comment stream is flat — there is no
|
|
30
|
+
// "reply to this specific comment" primitive on the endpoint `liveGitHubPost` posts through (that
|
|
31
|
+
// exists only for PR review comments, a different family this registry does not address) — so
|
|
32
|
+
// `opts?.replyToMessageId` is accepted (the interface's contract) and ignored, exactly like a
|
|
33
|
+
// top-level Slack post with no thread.
|
|
34
|
+
import { liveGitHubEditComment, liveGitHubPost } from "./github-post-arm.js";
|
|
35
|
+
import { parseGithubContainer } from "./github-read.js";
|
|
36
|
+
/** Parse the `owner/repo#number` container id, or throw the same honest, naming refusal every
|
|
37
|
+
* other surface-message-ops call gives on a malformed address — never a guessed target. */
|
|
38
|
+
function requireContainer(channelId) {
|
|
39
|
+
const target = parseGithubContainer(channelId);
|
|
40
|
+
if (!target)
|
|
41
|
+
throw new Error(`GitHub surface message ops: "${channelId}" is not a conversation id (expected "owner/repo#number").`);
|
|
42
|
+
return target;
|
|
43
|
+
}
|
|
44
|
+
export const githubMessageOps = {
|
|
45
|
+
platform: "github",
|
|
46
|
+
/** Post to the issue/PR-timeline comment stream. `opts?.replyToMessageId` is accepted and
|
|
47
|
+
* ignored — see the header's "NO THREADING" note. */
|
|
48
|
+
async postText(channelId, text) {
|
|
49
|
+
const { owner, repo, number } = requireContainer(channelId);
|
|
50
|
+
return liveGitHubPost({ platform: "github", owner, repo, issueNumber: number }, text);
|
|
51
|
+
},
|
|
52
|
+
/** PATCH the comment's body in place — the same edit `edit_reply` performs on the reply spine. */
|
|
53
|
+
async editMessage(channelId, messageId, text) {
|
|
54
|
+
const { repoFull } = requireContainer(channelId);
|
|
55
|
+
await liveGitHubEditComment(repoFull, messageId, text);
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=github-message-ops.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-message-ops.js","sourceRoot":"","sources":["../../../agent/channels/github-message-ops.ts"],"names":[],"mappings":"AAAA,4FAA4F;AAC5F,iGAAiG;AACjG,+FAA+F;AAC/F,+FAA+F;AAC/F,8EAA8E;AAC9E,EAAE;AACF,iFAAiF;AACjF,oGAAoG;AACpG,gEAAgE;AAChE,iGAAiG;AACjG,8FAA8F;AAC9F,kFAAkF;AAClF,kGAAkG;AAClG,mGAAmG;AACnG,iGAAiG;AACjG,oEAAoE;AACpE,EAAE;AACF,+FAA+F;AAC/F,6FAA6F;AAC7F,yFAAyF;AACzF,mGAAmG;AACnG,gGAAgG;AAChG,yFAAyF;AACzF,EAAE;AACF,iGAAiG;AACjG,mGAAmG;AACnG,gDAAgD;AAChD,EAAE;AACF,8FAA8F;AAC9F,kGAAkG;AAClG,8FAA8F;AAC9F,8FAA8F;AAC9F,uCAAuC;AAGvC,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD;4FAC4F;AAC5F,SAAS,gBAAgB,CAAC,SAAiB;IACzC,MAAM,MAAM,GAAG,oBAAoB,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,gCAAgC,SAAS,4DAA4D,CAAC,CAAC;IACpI,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAsB;IACjD,QAAQ,EAAE,QAAQ;IAElB;0DACsD;IACtD,KAAK,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI;QAC5B,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QAC5D,OAAO,cAAc,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,CAAC;IACxF,CAAC;IAED,kGAAkG;IAClG,KAAK,CAAC,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI;QAC1C,MAAM,EAAE,QAAQ,EAAE,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACjD,MAAM,qBAAqB,CAAC,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { UnifiedWakeOrigin, WakeOriginCodec } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
type GitHubOrigin = Extract<UnifiedWakeOrigin, {
|
|
3
|
+
platform: "github";
|
|
4
|
+
}>;
|
|
5
|
+
export declare const githubWakeOriginCodec: WakeOriginCodec<GitHubOrigin>;
|
|
6
|
+
export {};
|
|
7
|
+
//# sourceMappingURL=github-origin-codec.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-origin-codec.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-origin-codec.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,4CAA4C,CAAC;AAErG,KAAK,YAAY,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAMvE,eAAO,MAAM,qBAAqB,EAAE,eAAe,CAAC,YAAY,CAqB/D,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// The GitHub UnifiedWakeOrigin codec — MOVED VERBATIM out of core's
|
|
2
|
+
// channels/wake-origin-codecs.ts (OPE-114 stage 4). STORED-DATA COMPATIBILITY IS THE CONTRACT:
|
|
3
|
+
// a GitHub origin is JSON-serialised into the durable operational store and fail-closed
|
|
4
|
+
// re-validated on read, so every behaviour here is frozen by core's
|
|
5
|
+
// scripts/wake-origin-fixtures-test.ts (run from the app root with this bundle's boot wiring —
|
|
6
|
+
// scripts/wake-origin-fixtures-boot.ts). Validator tolerances (typeof-only optional checks, extra
|
|
7
|
+
// unknown fields) are LOAD-BEARING for rows already on disk — never tighten them here.
|
|
8
|
+
//
|
|
9
|
+
// The `UnifiedWakeOrigin` TYPE union (including its github arm) stays in core — it is the wire
|
|
10
|
+
// schema for stored rows; only the codec BEHAVIOUR lives in the bundle, registered by
|
|
11
|
+
// registerGithubProvider() (../register.ts).
|
|
12
|
+
/** COLON-FREE by construction (a task windowId is `[^:]+` — core channels/window-key.ts): any colon
|
|
13
|
+
* in a raw surface id is folded to `_`, deterministically, so spawn + ingress agree on identity. */
|
|
14
|
+
const noColon = (s) => s.replace(/:/g, "_");
|
|
15
|
+
export const githubWakeOriginCodec = {
|
|
16
|
+
validate: (o) => typeof o.owner === "string" &&
|
|
17
|
+
o.owner.length > 0 &&
|
|
18
|
+
typeof o.repo === "string" &&
|
|
19
|
+
o.repo.length > 0 &&
|
|
20
|
+
typeof o.issueNumber === "number" &&
|
|
21
|
+
Number.isFinite(o.issueNumber) &&
|
|
22
|
+
o.issueNumber > 0 &&
|
|
23
|
+
(o.installationId === undefined || typeof o.installationId === "number"),
|
|
24
|
+
threadRef: (origin) => ({ channelKey: noColon(`github/${origin.owner}/${origin.repo}`), threadKey: String(origin.issueNumber) }),
|
|
25
|
+
// The container and the thread coincide (an issue IS its own thread) ⇒ always thread-scoped.
|
|
26
|
+
isThreadScoped: () => true,
|
|
27
|
+
containerId: (origin) => `${origin.owner}/${origin.repo}`,
|
|
28
|
+
// NO `placementRef` AND NO `carryPlacement`, AND THAT ABSENCE IS THE DECLARATION (OPE-1826). A
|
|
29
|
+
// GitHub reply is a comment appended to the issue timeline, and the issue IS the thread key above —
|
|
30
|
+
// there is no "where inside it" left to name, so two origins that pass `sameUnifiedConversation`
|
|
31
|
+
// cannot disagree about placement. `installationId` is not a counter-example: it selects the
|
|
32
|
+
// credential the post mints under, never where the post lands. The position is recorded, and held
|
|
33
|
+
// to account against the live registry, by the `origin-placement-carry` row in core's
|
|
34
|
+
// identity/surface-parity.ts.
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=github-origin-codec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-origin-codec.js","sourceRoot":"","sources":["../../../agent/channels/github-origin-codec.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,+FAA+F;AAC/F,wFAAwF;AACxF,oEAAoE;AACpE,+FAA+F;AAC/F,kGAAkG;AAClG,uFAAuF;AACvF,EAAE;AACF,+FAA+F;AAC/F,sFAAsF;AACtF,6CAA6C;AAM7C;qGACqG;AACrG,MAAM,OAAO,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AAE5D,MAAM,CAAC,MAAM,qBAAqB,GAAkC;IAClE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CACd,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;QAC3B,CAAC,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;QAClB,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ;QAC1B,CAAC,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACjB,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ;QACjC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;QAC9B,CAAC,CAAC,WAAW,GAAG,CAAC;QACjB,CAAC,CAAC,CAAC,cAAc,KAAK,SAAS,IAAI,OAAO,CAAC,CAAC,cAAc,KAAK,QAAQ,CAAC;IAC1E,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC;IAChI,6FAA6F;IAC7F,cAAc,EAAE,GAAG,EAAE,CAAC,IAAI;IAC1B,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE;IACzD,+FAA+F;IAC/F,oGAAoG;IACpG,iGAAiG;IACjG,6FAA6F;IAC7F,kGAAkG;IAClG,sFAAsF;IACtF,8BAA8B;CAC/B,CAAC"}
|