@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,317 @@
|
|
|
1
|
+
// THE GITHUB AMBIENT ENGAGEMENT SEAM (chat-surface contract §2.5) — the GitHub wiring over core's
|
|
2
|
+
// pure engagement policy (`packages/core/agent/channels/engagement.ts`). It gives GitHub the middle
|
|
3
|
+
// posture Slack, Discord and Teams already have: an @mention marks the issue or pull-request thread
|
|
4
|
+
// ENGAGED, a later PLAIN comment in that thread wakes the agent, and `stand_down` stores the stop.
|
|
5
|
+
//
|
|
6
|
+
// THE ANCHOR is `githubConversationKey(owner, repo, number)` — `owner/repo#N`, the same string the
|
|
7
|
+
// mapper already publishes as `anchorChannelId` for the stop registry. One issue or pull request IS
|
|
8
|
+
// the GitHub conversation, so that is the grain a person can address a stop to, and it is the grain
|
|
9
|
+
// engagement is kept per. The surface partition stays the REPO (`github/<owner>/<repo>`), so the
|
|
10
|
+
// anchor distinguishes the containers inside it.
|
|
11
|
+
//
|
|
12
|
+
// 🔴 WHICH LEG OWNS AN ENGAGED FOLLOW-UP IS THE SAME QUESTION AS WHICH LEG OWNS A MENTION, AND IT
|
|
13
|
+
// GETS THE SAME ANSWER. GitHub runs two legs over one webhook: the NATIVE mention session
|
|
14
|
+
// (`githubOnComment`) and the ambient supervisor (`ambientDelivery`). Today a mention goes to
|
|
15
|
+
// exactly one of them — the native leg, unless the repo is single-window and operator-vouched, where
|
|
16
|
+
// the native session is suppressed and the unified window answers instead. This seam follows that
|
|
17
|
+
// split rather than inventing a second one: on a repo where the native leg owns mentions it also
|
|
18
|
+
// owns engaged follow-ups, and the ambient leg drops them; on a single-window vouched repo the
|
|
19
|
+
// ambient leg owns them, because the native handler stands down for EVERY comment there
|
|
20
|
+
// (`shouldSuppressNativeMention` asks `singleWindowMentionPolicy` with `isMention` pinned true).
|
|
21
|
+
// Two legs answering one comment is the failure this ordering exists to prevent.
|
|
22
|
+
//
|
|
23
|
+
// 🔴 "THE WINDOW ALREADY ANSWERS EVERY COMMENT" WAS THE CLAIM THAT BROKE IT (OPE-1740/OPE-1745). It
|
|
24
|
+
// held only while a seat REQUIRED `ambientSupervisor`. Seats are universal since OPE-1695, so a
|
|
25
|
+
// seated, vouched repo on an ADDRESSED-ONLY domain answers addressed comments and nothing else — and
|
|
26
|
+
// an engaged follow-up there reached neither leg: the native handler stood down, and the ambient
|
|
27
|
+
// leg's chattiness gate refused the comment as `domain-refused` before this seam was read. Deploy
|
|
28
|
+
// #201 measured exactly that on `oe-e2e-addressed-github`. The ambient leg now reads the seam FIRST
|
|
29
|
+
// and feeds its verdict to the gate (`githubAmbientLegAdmission`, github.ts): enrolment is an
|
|
30
|
+
// address, and the domain opt-in governs UNINVITED talk only.
|
|
31
|
+
//
|
|
32
|
+
// 🔴 HOW FAR THE NON-MENTION ARM REACHES — PRIVATE REPOS, AND NEVER AN INLINE REVIEW COMMENT.
|
|
33
|
+
// `githubEngagedFollowUpReach` is the one predicate for it, and BOTH legs ask it. A plain follow-up
|
|
34
|
+
// wakes a native turn (a sandbox checkout, a judged spend), so it is refused on a public or
|
|
35
|
+
// unknown-privacy repo, and refused inside a review thread because the anchor is the pull request
|
|
36
|
+
// rather than the thread. An @mention is unaffected by either floor.
|
|
37
|
+
//
|
|
38
|
+
// 🔴 IT IS A STRICTER FLOOR THAN THE AMBIENT LEG'S, NOT THE SAME ONE. `classifyGitHubAmbientComment`
|
|
39
|
+
// step 5 admits a VOUCHED public repo (`repoPrivate !== true && !vouched ⇒ drop`); this one is
|
|
40
|
+
// vouch-blind and refuses every public repo. Do not "align" the two by adding the vouch here — that
|
|
41
|
+
// re-opens an uninvited native turn on a public repo, which is the thing this floor exists to close.
|
|
42
|
+
//
|
|
43
|
+
// 🔴 WHAT NO TEST REACHES, STATED RATHER THAN IMPLIED, AND ONE OF THE THREE IS COVERED BY NOTHING.
|
|
44
|
+
// `github.ts`'s `ambientDelivery` is module-private and only runs behind the route's `waitUntil`, so
|
|
45
|
+
// no in-repo test drives the LEG itself. Obligation by obligation:
|
|
46
|
+
// • THE CHATTINESS GATE reading the enrolment fact IS driven offline, over the real store and a
|
|
47
|
+
// real declared addressed-only domain, because `githubAmbientLegAdmission` is exported for it
|
|
48
|
+
// (`scripts/engaged-seam-wiring-test.ts`, the addressed-only block) — reach floors included.
|
|
49
|
+
// That the leg CALLS it is `J-ENGAGED-github`'s to prove: step (b)'s follow-up reaches a turn on
|
|
50
|
+
// that journey's repo only through this gate.
|
|
51
|
+
// • THE STOOD-DOWN SUPPRESSION is covered by NOTHING, and the gate above it is why. On an
|
|
52
|
+
// ADDRESSED-ONLY repo a stopped row resolves `hold`, never `deliver`, so the gate refuses the
|
|
53
|
+
// comment first and this branch never runs — `J-ENGAGED-github`'s stand-down step passes on the
|
|
54
|
+
// domain flag, not on the stop. The branch is reachable only on an ambient-ON repo, and no
|
|
55
|
+
// journey drives a `stand_down` on one. Deleting it would red nothing.
|
|
56
|
+
// • THE `!shouldSuppressNativeMention(singleWindow, vouched)` DROP GUARD is covered on its
|
|
57
|
+
// NON-FIRING side only: `J-ENGAGED-github`'s repo stands the native leg down, so a guard that
|
|
58
|
+
// wrongly dropped there reds the journey. Its FIRING side — a repo the native leg owns, where
|
|
59
|
+
// the guard is what stops two legs answering one comment — is covered by nothing.
|
|
60
|
+
// `scripts/github-external-policy-room-test.ts` records the same limit for its own leg.
|
|
61
|
+
// What IS pinned: this module's decision table
|
|
62
|
+
// (`packages/providers/github/scripts/github-engagement-test.ts`), the NATIVE leg's whole loop over
|
|
63
|
+
// the real store — mark, deliver, stop, the way back in, and the redelivery guard
|
|
64
|
+
// (`scripts/engaged-seam-wiring-test.ts`), and the anchor round trip through `stand_down`
|
|
65
|
+
// (`scripts/stand-down-anchor-test.ts` LEG 17). On the AMBIENT leg, the `J-ENGAGED-github` e2e
|
|
66
|
+
// journey covers exactly what the three bullets above say it covers, and no more.
|
|
67
|
+
import { getOperationalStore, getThreadEngagement, markThreadEngaged, resolvePartition, shouldDeliverReply, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
68
|
+
import { buildGitHubAuth, githubConversationKey } from "./github-identity.js";
|
|
69
|
+
/**
|
|
70
|
+
* The engagement-registry anchor for one GitHub container, or `undefined` when the delivery names
|
|
71
|
+
* no container number.
|
|
72
|
+
*
|
|
73
|
+
* 🔴 IT IS THE ONE PREDICATE THE MAPPER AND THE SEAM SHARE. `bindGitHub` publishes exactly this as
|
|
74
|
+
* `engagementKey`, so `stand_down`'s mark and this seam's read address the same row. Two spellings
|
|
75
|
+
* of "which container this is" is how a stop comes to mark a row no gate reads.
|
|
76
|
+
*/
|
|
77
|
+
export function githubEngagementAnchor(owner, repo, num) {
|
|
78
|
+
return githubConversationKey(owner, repo, num);
|
|
79
|
+
}
|
|
80
|
+
/** The partition one container's engagement row lives in — the REPO grain
|
|
81
|
+
* (`github/<owner>/<repo>`), which is what `resolvePartition` gives a bound GitHub session and
|
|
82
|
+
* therefore what `stand_down` writes under. It is built from THIS delivery's own facts, through the
|
|
83
|
+
* same `buildGitHubAuth` the wake dispatches, so the seam can never key a row the turn would not.
|
|
84
|
+
* Null on an unresolvable identity: the caller then keeps today's path, the fail-open posture every
|
|
85
|
+
* carriage leg on this surface takes. */
|
|
86
|
+
export function githubSeamPartition(f) {
|
|
87
|
+
try {
|
|
88
|
+
const auth = buildGitHubAuth({
|
|
89
|
+
owner: f.owner,
|
|
90
|
+
repo: f.repo,
|
|
91
|
+
isPrivate: f.isPrivate,
|
|
92
|
+
actorLogin: f.actorLogin,
|
|
93
|
+
});
|
|
94
|
+
return resolvePartition({ channel: { kind: "github" }, session: { auth: { current: auth } } });
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* May the NON-MENTION arm of the seam reach this delivery at all? Read BEFORE any registry read, by
|
|
102
|
+
* both legs, so the two never disagree about which comments the native leg owns.
|
|
103
|
+
*
|
|
104
|
+
* TWO FLOORS. Each refuses on an unknown rather than admitting, and each is about REACH — whether
|
|
105
|
+
* this kind of delivery may wake at all — never about whether the container is engaged.
|
|
106
|
+
*
|
|
107
|
+
* • 🔴 THE REPO MUST BE POSITIVELY PRIVATE. An engaged container's plain comment wakes a FULL
|
|
108
|
+
* native turn: a sandbox checkout and a judged spend. On a public repo that lets ANY GitHub
|
|
109
|
+
* account buy one, by writing in an issue somebody once @mentioned the app in — the same
|
|
110
|
+
* drive-by spend and prompt-injection surface `classifyGitHubAmbientComment` step 5 refuses for,
|
|
111
|
+
* on the louder of the two legs. 🔴 THIS FLOOR IS STRICTER THAN THAT ONE, deliberately: step 5
|
|
112
|
+
* admits a VOUCHED public repo, and this one refuses every public repo. A vouch widens the
|
|
113
|
+
* ambient supervisor's judged reply; widening an uninvited NATIVE turn to a public repo is a
|
|
114
|
+
* bigger step that needs its own ruling.
|
|
115
|
+
* • 🔴 A REVIEW THREAD IS NOT A CONTAINER OF ITS OWN. {@link githubEngagementAnchor} collapses to
|
|
116
|
+
* `owner/repo#N`, so every INLINE review comment on a pull request reads the row that one
|
|
117
|
+
* @mention anywhere on that pull request engaged — and one review leaves dozens of them.
|
|
118
|
+
*
|
|
119
|
+
* AN @MENTION IS UNAFFECTED BY BOTH, on a public repo and inside a review thread alike: it is
|
|
120
|
+
* answered by the caller's own mention gate, which this seam never narrows.
|
|
121
|
+
*/
|
|
122
|
+
export function githubEngagedFollowUpReach(f) {
|
|
123
|
+
if (f.isPrivate !== true) {
|
|
124
|
+
return { reachable: false, reason: "the repo is public or of unknown privacy — an engaged follow-up wakes a native turn, which is private-repo only" };
|
|
125
|
+
}
|
|
126
|
+
if (f.conversationKind === "review_thread") {
|
|
127
|
+
return { reachable: false, reason: "an inline review comment shares its pull request's anchor — a review thread is not a container of its own" };
|
|
128
|
+
}
|
|
129
|
+
return { reachable: true, reason: "a private repo, and not an inline review comment" };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Resolve the seam for one comment — READ-ONLY. The mark is a separate call
|
|
133
|
+
* ({@link markGitHubEngaged}) so exactly one caller writes the row: two writers is how one comment
|
|
134
|
+
* comes to engage a container twice, once on each leg.
|
|
135
|
+
*
|
|
136
|
+
* ORDERING. A mention is settled before any read: it is always answered by the caller's own path, so
|
|
137
|
+
* its verdict costs no store round-trip. Only a plain comment pays for a read, and it pays for
|
|
138
|
+
* exactly one — the ROW, not a boolean, because the row carries both facts the decision needs (is
|
|
139
|
+
* this engaged, and was the agent asked to stand down here) and two reads could disagree.
|
|
140
|
+
*
|
|
141
|
+
* THE DECISION ITSELF IS CORE'S. `shouldDeliverReply` owns the disqualifier order and the reasons;
|
|
142
|
+
* this function only translates GitHub facts into its input. Nothing here re-states the policy.
|
|
143
|
+
*
|
|
144
|
+
* FAIL-CLOSED on a store error: `getThreadEngagement` reads a failure as `null`, so an unreadable
|
|
145
|
+
* registry holds rather than delivers — the agent does not barge into a conversation it cannot prove
|
|
146
|
+
* it was invited to.
|
|
147
|
+
*/
|
|
148
|
+
export async function resolveGitHubEngagement(f, deps = {}) {
|
|
149
|
+
const anchor = githubEngagementAnchor(f.owner, f.repo, f.number);
|
|
150
|
+
if (!anchor)
|
|
151
|
+
return { kind: "unanchored", reason: "the delivery names no issue or pull-request number — no row to address" };
|
|
152
|
+
const partition = githubSeamPartition(f);
|
|
153
|
+
if (!partition)
|
|
154
|
+
return { kind: "unanchored", reason: "the delivery has no resolvable partition — no row to address" };
|
|
155
|
+
if (f.isMention)
|
|
156
|
+
return { kind: "engage", anchor };
|
|
157
|
+
const read = deps.getEngagement ?? ((p, key) => getThreadEngagement(p, key));
|
|
158
|
+
const row = await read(partition, anchor);
|
|
159
|
+
const decision = shouldDeliverReply({
|
|
160
|
+
isEngaged: row !== null,
|
|
161
|
+
stopped: row?.stopped ?? false,
|
|
162
|
+
authorIsBot: f.authorIsBot,
|
|
163
|
+
isAppMention: false, // settled above — a mention never reaches this branch
|
|
164
|
+
inThread: true, // an anchored GitHub container IS the thread engagement is kept per
|
|
165
|
+
});
|
|
166
|
+
if (decision.deliver)
|
|
167
|
+
return { kind: "deliver", anchor, reason: decision.reason };
|
|
168
|
+
return { kind: "hold", anchor, reason: decision.reason, stopped: row?.stopped === true };
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Mark one container ENGAGED on an @mention, ONCE PER COMMENT.
|
|
172
|
+
*
|
|
173
|
+
* `participating: true` is the re-invitation: it promotes the row and CLEARS a previous stand-down,
|
|
174
|
+
* which is what "an @mention brings the agent back" means on every surface that wires this seam.
|
|
175
|
+
*
|
|
176
|
+
* 🔴 THE MARK IS DEDUPED, AND THE STOP IS WHY. Clearing the flag is right for a person's second
|
|
177
|
+
* @mention and WRONG for GitHub redelivering the first one — a redelivered webhook would quietly
|
|
178
|
+
* lift a stand-down nobody asked to lift. `commentId` is the comment's own id, so the guard keys on
|
|
179
|
+
* the thing that must happen once. It is the DURABLE layer only, unlike Teams' front: this route
|
|
180
|
+
* holds no in-process dedupe object, and a serverless invocation is a fresh process, so an
|
|
181
|
+
* in-memory layer would catch almost nothing here. FAIL-OPEN on a store error, the same posture
|
|
182
|
+
* every dedupe in this repository takes — a dedupe prevents a double write, not a security hole.
|
|
183
|
+
*
|
|
184
|
+
* Best-effort throughout — `markThreadEngaged` swallows a store failure, because engagement
|
|
185
|
+
* bookkeeping must never fail a turn. Returns the anchor it marked, or null when it marked nothing.
|
|
186
|
+
*/
|
|
187
|
+
export async function markGitHubEngaged(f, commentId, deps = {}) {
|
|
188
|
+
const anchor = githubEngagementAnchor(f.owner, f.repo, f.number);
|
|
189
|
+
if (!anchor)
|
|
190
|
+
return null;
|
|
191
|
+
const partition = githubSeamPartition(f);
|
|
192
|
+
if (!partition)
|
|
193
|
+
return null;
|
|
194
|
+
const now = deps.now ?? (() => new Date().toISOString());
|
|
195
|
+
const atIso = now();
|
|
196
|
+
if (commentId !== undefined) {
|
|
197
|
+
const seen = deps.markEventSeen ?? liveMarkEngageSeen;
|
|
198
|
+
if (!(await seen(partition.org, `github-engage:${anchor}:${String(commentId)}`, atIso)))
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
const mark = deps.markEngaged ?? ((p, key, at) => markThreadEngaged(p, key, at, { participating: true }));
|
|
202
|
+
await mark(partition, anchor, atIso);
|
|
203
|
+
return anchor;
|
|
204
|
+
}
|
|
205
|
+
/** The durable half of the mark dedupe. FAIL-OPEN: an unreachable store answers "not seen", so a
|
|
206
|
+
* store outage costs a redundant mark rather than a lost engagement. */
|
|
207
|
+
const ENGAGE_DEDUPE_TTL_MS = 10 * 60_000;
|
|
208
|
+
let warnedEngageDedupe = false;
|
|
209
|
+
async function liveMarkEngageSeen(org, key, atIso) {
|
|
210
|
+
try {
|
|
211
|
+
return await getOperationalStore().markEventSeen(org, key, atIso, ENGAGE_DEDUPE_TTL_MS);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
if (!warnedEngageDedupe) {
|
|
215
|
+
warnedEngageDedupe = true;
|
|
216
|
+
console.warn(`[open-everywhere:github-engagement] engage dedupe unavailable — a redelivered @mention may re-mark: ${String(err)}`);
|
|
217
|
+
}
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Is this comment MACHINE-AUTHORED — a GitHub App, a bot account, or this deployment's own reply?
|
|
223
|
+
*
|
|
224
|
+
* 🔴 ONE PREDICATE, AND IT MUST FAIL CLOSED ON AN UNRESOLVED SLUG. `githubBotMentioned` needs the
|
|
225
|
+
* app slug to recognise a mention at all, so before this seam a deployment with no `GITHUB_APP_SLUG`
|
|
226
|
+
* simply never woke natively. A plain comment now reaches the seam, and without the slug the app
|
|
227
|
+
* cannot tell its OWN reply from a person's — so "unknown" answers `true` and nothing is delivered,
|
|
228
|
+
* the same posture `classifyGitHubAmbientComment` takes with its own no-slug drop.
|
|
229
|
+
*/
|
|
230
|
+
export function githubMachineAuthored(authorLogin, authorType, botSlug) {
|
|
231
|
+
const slug = botSlug?.trim();
|
|
232
|
+
if (!slug)
|
|
233
|
+
return true; // cannot identify self ⇒ cannot prove a human wrote this
|
|
234
|
+
if (authorType === "Bot")
|
|
235
|
+
return true;
|
|
236
|
+
return authorLogin !== undefined && authorLogin.toLowerCase() === `${slug}[bot]`.toLowerCase();
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* The NATIVE leg's one-line admission (`github.ts`'s mention gate): may this plain, non-mention
|
|
240
|
+
* comment run a turn?
|
|
241
|
+
*
|
|
242
|
+
* It exists because the native channel is where a GitHub turn is minted — the wake is the webhook
|
|
243
|
+
* itself, so admitting the comment through the mention gate IS the wake. The ambient leg owns the
|
|
244
|
+
* complementary halves above it (the STOP suppression, and dropping a comment this leg now owns), so
|
|
245
|
+
* this call only ever answers the delivery question, and it never writes.
|
|
246
|
+
*
|
|
247
|
+
* 🔴 THE REACH FLOORS RUN FIRST, above the registry read: a public repo and an inline review comment
|
|
248
|
+
* are refused whatever the row says ({@link githubEngagedFollowUpReach}). Putting them here rather
|
|
249
|
+
* than in {@link resolveGitHubEngagement} is deliberate — the ambient leg reads that function for the
|
|
250
|
+
* STOP, and a stood-down container must stay suppressed on a public repo too.
|
|
251
|
+
*/
|
|
252
|
+
export async function githubEngagedAdmits(f, deps = {}) {
|
|
253
|
+
if (f.isMention)
|
|
254
|
+
return true; // the caller's own mention gate already admitted it
|
|
255
|
+
if (!githubEngagedFollowUpReach(f).reachable)
|
|
256
|
+
return false;
|
|
257
|
+
const verdict = await resolveGitHubEngagement(f, deps);
|
|
258
|
+
return verdict.kind === "deliver";
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* WHAT WOKE ONE AMBIENT-LEG DELIVERY, for the `github.dispatched` audit row (OPE-1752).
|
|
262
|
+
*
|
|
263
|
+
* 🔴 THE CLASSIFIER'S OWN WORD CANNOT ANSWER THIS, AND WRITING IT WAS THE DEFECT.
|
|
264
|
+
* `classifyGitHubAmbientComment` answers a narrower question — did this comment ADDRESS the agent —
|
|
265
|
+
* and an engaged follow-up did not, so it stays `channel-watch`. That is the right answer to its own
|
|
266
|
+
* question, and `channel-watch` is what makes the turn a JUDGED one. But `channel-watch` ALSO names
|
|
267
|
+
* unowned chatter in an un-enrolled issue, and the audit row is the one place those two must not
|
|
268
|
+
* read alike. This is the same promotion OPE-1510 made on Notion (`notionWakeAddressing`) and
|
|
269
|
+
* OPE-1578 made on Linear (`linearWakeAddressing`) — one concept, one spelling, across three
|
|
270
|
+
* surfaces.
|
|
271
|
+
*
|
|
272
|
+
* 🔴 THE MAPPING IS THE ROW'S ALONE, and nothing else has to be re-derived from it. The classifier's
|
|
273
|
+
* value reaches five places in `github.ts`, and TWO of them are behavioural: the `drop` return, the
|
|
274
|
+
* `githubAmbientLegAdmission` gate input, the seam facts' `isMention`, `directMention` — which is
|
|
275
|
+
* what the wake envelope and `singleWindowMentionPolicy` are built from — and this row. The gate
|
|
276
|
+
* input and `directMention` are the behavioural two, and NEITHER reads the PROMOTED value: both take
|
|
277
|
+
* the classifier's word directly, above this mapping. `github-supervisor.ts` derives the wake
|
|
278
|
+
* envelope's own `addressing`
|
|
279
|
+
* from `directMention` (`directMention ? "direct-mention" : "channel-watch"`), and the
|
|
280
|
+
* unified-window wake takes `singleWindowMentionPolicy(singleWindow, directMention, vouched)`.
|
|
281
|
+
* Neither reads this value. So the turn is judged the same, and the SILENCE BAR is what it always
|
|
282
|
+
* was, whatever this function returns.
|
|
283
|
+
*
|
|
284
|
+
* 🔴 A ROW THAT CANNOT TELL THEM APART REDS `J-ENGAGED-github` STEP (b) AGAINST A HEALTHY
|
|
285
|
+
* DEPLOYMENT. `gateEngagedWake` (packages/core/scripts/e2e/scenarios.ts) skips on an EMPTY dispatch
|
|
286
|
+
* read only, so a non-empty read carrying `channel-watch` is a hard gate failure, not an ignored
|
|
287
|
+
* value. Measured on deploy #203: the follow-up woke, dispatched and was ANSWERED (`reply.post`,
|
|
288
|
+
* 2026-09-12T20:27:25Z), and only the label disagreed. What the promotion DOES oblige is every
|
|
289
|
+
* reader that filtered the ambient leg by the literal `"channel-watch"` — `AMBIENT_LEG_ADDRESSINGS`
|
|
290
|
+
* (packages/core/scripts/e2e/engaged-rows.ts) is the pair such a reader takes.
|
|
291
|
+
*
|
|
292
|
+
* 🔴 IT REPORTS THE ENGAGED DOOR, AND THAT DOOR CARRIES A REACH FLOOR LINEAR'S DOES NOT. Linear's
|
|
293
|
+
* input is the bare seam verdict; GitHub's is `admission.engagedFollowUp`, which is
|
|
294
|
+
* `seam.kind === "deliver" && githubEngagedFollowUpReach(f).reachable`. So the promotion needs BOTH
|
|
295
|
+
* an enrolled container and a reachable one, and {@link githubEngagedFollowUpReach} refuses a public
|
|
296
|
+
* or unknown-privacy repo and an inline review comment. An engaged issue on a VOUCHED PUBLIC repo
|
|
297
|
+
* with ambient ON therefore still writes `channel-watch`: the classifier keeps that comment, but the
|
|
298
|
+
* engaged door is shut and the native leg would not have woken a turn for it either. That is the
|
|
299
|
+
* right answer — the row reports the door the delivery actually came through — and it is a narrower
|
|
300
|
+
* claim than Linear's, so do not read the two mappings as identical in reach.
|
|
301
|
+
*
|
|
302
|
+
* 🔴 IT REPORTS THE REGISTRY, NOT THE AMBIENT OPT-IN. `githubAmbientLegAdmission` resolves the seam
|
|
303
|
+
* for every kept comment, ABOVE the `githubAmbientEnabled` gate and independently of it, so on an
|
|
304
|
+
* ambient-ENABLED private repo an engaged issue's plain comment also reads `thread-follow` — the
|
|
305
|
+
* opt-in would have admitted it either way. The value therefore separates an engaged wake from an
|
|
306
|
+
* ambient one only where the domain has ambient OFF, which is the addressed-only posture the
|
|
307
|
+
* journeys drive.
|
|
308
|
+
*
|
|
309
|
+
* `engagedFollowUp` is the caller's own admission verdict, and it is the only door a plain comment
|
|
310
|
+
* has on a repo that never opted in. The `channel-watch` guard is belt-and-braces:
|
|
311
|
+
* `drop` returns before the row is written and can never reach here, and `direct` is an address
|
|
312
|
+
* rather than a follow-up.
|
|
313
|
+
*/
|
|
314
|
+
export function githubWakeAddressing(addressing, engagedFollowUp) {
|
|
315
|
+
return addressing === "channel-watch" && engagedFollowUp === true ? "thread-follow" : addressing;
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=github-engagement.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-engagement.js","sourceRoot":"","sources":["../../../agent/channels/github-engagement.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { BindableContext, IdentityMapper, IngestedChatMessage } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
/** Structural match for eve's SessionAuthContext (attributes + principal). */
|
|
3
|
+
export interface OpenEverywhereGitHubAuth {
|
|
4
|
+
readonly authenticator: string;
|
|
5
|
+
readonly principalId: string;
|
|
6
|
+
readonly principalType: string;
|
|
7
|
+
readonly attributes: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export interface GitHubIdentityInput {
|
|
10
|
+
/** Repo owner (org/user login) — becomes the OpenEverywhere `workspace`, the org boundary. */
|
|
11
|
+
readonly owner: string;
|
|
12
|
+
/** Repo name — becomes the OpenEverywhere `surface` (the silo grain: one repo, many issues/PRs). */
|
|
13
|
+
readonly repo: string;
|
|
14
|
+
/** "owner/name" — carried for reference/logging (not a partition key on its own). */
|
|
15
|
+
readonly repositoryFullName?: string;
|
|
16
|
+
readonly repositoryId?: number;
|
|
17
|
+
/** Repo visibility, when positively known (off ctx.repository.private); absent → fail closed. */
|
|
18
|
+
readonly isPrivate?: boolean;
|
|
19
|
+
/** "issue" | "pull_request" | "review_thread" — picks the post-back anchor (issue vs PR number). */
|
|
20
|
+
readonly conversationKind?: string;
|
|
21
|
+
/** Issue number for the conversation, when it's an issue thread (post-back anchor). */
|
|
22
|
+
readonly issueNumber?: number;
|
|
23
|
+
/** PR number for the conversation, when it's a PR/review thread (post-back anchor). */
|
|
24
|
+
readonly pullRequestNumber?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The ROOT comment id of the review thread this turn arrived in (OPE-958) — present only on a
|
|
27
|
+
* `review_thread` conversation. It is the id `read_thread` fetches the thread by, and the binding
|
|
28
|
+
* is the only place it can ride: `ReadAdapter.wakeAddress` is pure and synchronous and reads the
|
|
29
|
+
* binding, while eve carries the root on its own channel STATE, which the mapper never sees.
|
|
30
|
+
*
|
|
31
|
+
* GitHub sends `in_reply_to_id` on a REPLY and omits it on the comment that OPENED the thread, so
|
|
32
|
+
* the root is `in_reply_to_id ?? comment.id` and github.ts derives it there, once.
|
|
33
|
+
*/
|
|
34
|
+
readonly reviewThreadRootCommentId?: number;
|
|
35
|
+
/** GitHub App installation id (needed to mint tokens for proactive post-back). */
|
|
36
|
+
readonly installationId?: number;
|
|
37
|
+
/** The acting GitHub user (comment author) — the OpenEverywhere principal. */
|
|
38
|
+
readonly actorLogin: string;
|
|
39
|
+
readonly actorId?: number;
|
|
40
|
+
/** "User" | "Bot" | "Organization" — a Bot actor maps to a `service` principal. */
|
|
41
|
+
readonly actorType?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The actor's relationship to the repo, as GitHub reports it on the comment payload
|
|
44
|
+
* (`author_association`): OWNER | MEMBER | COLLABORATOR | CONTRIBUTOR |
|
|
45
|
+
* FIRST_TIME_CONTRIBUTOR | FIRST_TIMER | MANNEQUIN | NONE. A verifiable trust signal.
|
|
46
|
+
*/
|
|
47
|
+
readonly authorAssociation?: string;
|
|
48
|
+
}
|
|
49
|
+
/** Build the auth context OpenEverywhere dispatches with — raw facts as string attributes. */
|
|
50
|
+
export declare function buildGitHubAuth(input: GitHubIdentityInput): OpenEverywhereGitHubAuth;
|
|
51
|
+
/**
|
|
52
|
+
* A comment @mentions the bot and should wake a turn. Faithfully mirrors eve's
|
|
53
|
+
* internal gate (shouldDispatchGitHubComment: isIgnoredGitHubComment +
|
|
54
|
+
* extractGitHubCommentTrigger), which is NOT on eve's public export surface — so we
|
|
55
|
+
* replicate it here (small, deterministic, and unit-tested) rather than reach into a
|
|
56
|
+
* package-internal path. Kept byte-consistent with eve because the channel's message
|
|
57
|
+
* layer still strips the mention token via eve's own extractGitHubCommentTrigger; a
|
|
58
|
+
* divergent gate here would fire turns eve then can't strip. FAIL CLOSED: no botName,
|
|
59
|
+
* a bot author, the bot's own comments, or eve's marker → no wake.
|
|
60
|
+
*/
|
|
61
|
+
export declare function githubBotMentioned(body: string | undefined, botName: string | undefined, author: {
|
|
62
|
+
readonly login?: string;
|
|
63
|
+
readonly type?: string;
|
|
64
|
+
} | undefined): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* GitHub repo privacy → visibility. FAIL CLOSED, mirroring slackVisibility/
|
|
67
|
+
* linearVisibility: only an explicitly PUBLIC repo (`repo_private="false"`) yields
|
|
68
|
+
* `public`; a private repo (`"true"`) or an unknown signal → `private`, so an
|
|
69
|
+
* under-identified session or a private repo never writes its learnings up to shared
|
|
70
|
+
* (workspace) memory. A public repo's issue/PR threads are world-readable, so treating
|
|
71
|
+
* them as a public surface (learnings may flow to the workspace brain across the org's
|
|
72
|
+
* repos) is correct; anything we can't confirm public stays surface-tier private.
|
|
73
|
+
*/
|
|
74
|
+
export declare function githubVisibility(repoPrivate: string | undefined): Visibility;
|
|
75
|
+
/**
|
|
76
|
+
* GitHub actor + repo privacy → trust (the shared-memory gate). FAIL CLOSED — only a
|
|
77
|
+
* POSITIVE in-boundary fact yields a verdict:
|
|
78
|
+
* - A PRIVATE repo (only org members + invited collaborators can see it) touched by an
|
|
79
|
+
* org OWNER or MEMBER → `internal`. OWNER/MEMBER are GitHub's strong "part of the org
|
|
80
|
+
* that owns this repo" signals (`author_association`); on a private repo that means an
|
|
81
|
+
* insider acting inside the boundary, so shared/org memory is safe to read.
|
|
82
|
+
* - ANYTHING ELSE → `undefined`, so bindFrom FAILS CLOSED to `external`:
|
|
83
|
+
* · a PUBLIC repo — world-readable, anyone (external contributors) can comment, so it
|
|
84
|
+
* must never light up the org brain regardless of who this particular actor is;
|
|
85
|
+
* · a private repo touched by a COLLABORATOR (may be an OUTSIDE collaborator),
|
|
86
|
+
* CONTRIBUTOR, first-timer, mannequin, NONE, or a missing/unknown association;
|
|
87
|
+
* · a missing repo-privacy signal.
|
|
88
|
+
* The operator can still vouch for a deployment with OE_TRUST=internal — the same layer
|
|
89
|
+
* Slack-DM/Teams/Discord/Linear use. An ambiguous GitHub event must never reach
|
|
90
|
+
* the shared org brain. (Per-repo collaborator-membership enrichment — GET
|
|
91
|
+
* /repos/{o}/{r}/collaborators/{u}/permission — is the future layer-2 gate that could
|
|
92
|
+
* promote a verified internal collaborator; until then, only OWNER/MEMBER on a private
|
|
93
|
+
* repo is trusted.)
|
|
94
|
+
*/
|
|
95
|
+
export declare function githubTrust(repoPrivate: string | undefined, authorAssociation: string | undefined): Trust | undefined;
|
|
96
|
+
/** Local copies of the binding's unions (kept eve-free + import-cycle-free). */
|
|
97
|
+
type Visibility = "public" | "private";
|
|
98
|
+
type Trust = "internal" | "external";
|
|
99
|
+
/** `author_association` values that carry a deliberate access grant on the repo — the INTERNAL set. */
|
|
100
|
+
export declare const GITHUB_INTERNAL_ASSOCIATIONS: readonly string[];
|
|
101
|
+
/** `author_association` values that carry NO access grant — the EXCLUDED (external) set. This is
|
|
102
|
+
* GitHub's whole documented enum minus {@link GITHUB_INTERNAL_ASSOCIATIONS}, written out rather
|
|
103
|
+
* than derived by negation: see {@link githubAssociationRefutes} for why the difference matters. */
|
|
104
|
+
export declare const GITHUB_EXTERNAL_ASSOCIATIONS: readonly string[];
|
|
105
|
+
/**
|
|
106
|
+
* Does this `author_association` POSITIVELY refute the author's membership?
|
|
107
|
+
*
|
|
108
|
+
* 🔴 IT MATCHES THE EXCLUDED SET, IT DOES NOT NEGATE THE INTERNAL ONE, and the difference is the
|
|
109
|
+
* whole fail-open posture. An ABSENT association (a payload shape GitHub changed, a synthesised
|
|
110
|
+
* event) and an UNRECOGNISED one (a value GitHub adds later) both answer `false` — "not refuted",
|
|
111
|
+
* which is what `IngestedChatMessage.refuted` means by absence of evidence. Negating the internal
|
|
112
|
+
* set would read every unknown as external and take the agent off the air on a payload change.
|
|
113
|
+
*
|
|
114
|
+
* Case-sensitive on purpose: GitHub sends these values upper-case, and accepting a lower-case
|
|
115
|
+
* variant would be inventing a second spelling of a vendor enum.
|
|
116
|
+
*/
|
|
117
|
+
export declare function githubAssociationRefutes(authorAssociation: string | undefined): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* The author grain's evidence for one GitHub comment, in the core's own vocabulary
|
|
120
|
+
* (`IngestedChatMessage`) so access/external-policy.ts answers the exclusion question — this bundle
|
|
121
|
+
* never re-derives it.
|
|
122
|
+
*
|
|
123
|
+
* A `Bot` actor grades as the machine floor `relay` and is therefore NEVER excluded, which is the
|
|
124
|
+
* core's own ruling (access/taint-signal.ts `nonMemberChatTrust`): a bot is below peer standing but
|
|
125
|
+
* it is not a person outside the organisation, and withholding every app's comments would be an
|
|
126
|
+
* outage. Stating it as the tier rather than as a local `if` keeps ONE place deciding.
|
|
127
|
+
*
|
|
128
|
+
* A human author carries `refuted` only when {@link githubAssociationRefutes} says so. `trust` is
|
|
129
|
+
* left ABSENT for a human on purpose: this runs before the per-wake trust resolution, so naming a
|
|
130
|
+
* tier here would be a guess, and the refutation leg needs no tier.
|
|
131
|
+
*/
|
|
132
|
+
export declare function githubAuthorFacts(input: {
|
|
133
|
+
readonly authorAssociation?: string;
|
|
134
|
+
readonly actorType?: string;
|
|
135
|
+
}): IngestedChatMessage;
|
|
136
|
+
/**
|
|
137
|
+
* The CONVERSATION key the stop registry keys a GitHub turn by (OPE-898) — `owner/repo#number`.
|
|
138
|
+
*
|
|
139
|
+
* ONE derivation, used by BOTH sides of the affordance: {@link bindGitHub} writes it onto the anchor
|
|
140
|
+
* row at turn start, and the stop-comment leg (channels/github-stop.ts) rebuilds it from the webhook
|
|
141
|
+
* that carries the directive. Two spellings of this key would make the whole arm silently miss.
|
|
142
|
+
*
|
|
143
|
+
* It is NOT a surface key and never becomes one — the GitHub surface grain is the REPO, and this
|
|
144
|
+
* value names one issue or PR inside it. Returns undefined on any missing part: a partial key would
|
|
145
|
+
* collide across repos.
|
|
146
|
+
*/
|
|
147
|
+
export declare function githubConversationKey(owner: string | undefined, repo: string | undefined, number: number | undefined): string | undefined;
|
|
148
|
+
/**
|
|
149
|
+
* The ROOT comment id of the review thread this turn arrived in, or `undefined` (OPE-958). The read
|
|
150
|
+
* side of {@link GitHubIdentityInput.reviewThreadRootCommentId} — this file owns the attribute
|
|
151
|
+
* spelling on BOTH sides, so a rename cannot leave the reader looking for a key nobody writes.
|
|
152
|
+
*
|
|
153
|
+
* It reads the AUTH ATTRIBUTES rather than the composed binding on purpose. `RawIdentity.target` is
|
|
154
|
+
* eve's `GitHubReceiveTarget` and throws on a member it does not know, so the root cannot ride
|
|
155
|
+
* there, and no other binding field names a comment.
|
|
156
|
+
*
|
|
157
|
+
* Returns the id as a STRING of digits, which is what `ReadAdapter.readThread` takes as its thread
|
|
158
|
+
* id. A non-numeric value is treated as absent: a review thread id that is not a comment id would
|
|
159
|
+
* fetch nothing, and refusing honestly beats reading an empty thread.
|
|
160
|
+
*/
|
|
161
|
+
export declare function githubReviewThreadRoot(ctx: BindableContext): string | undefined;
|
|
162
|
+
export declare const bindGitHub: IdentityMapper;
|
|
163
|
+
export {};
|
|
164
|
+
//# sourceMappingURL=github-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-identity.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-identity.ts"],"names":[],"mappings":"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"}
|