@open-everywhere/provider-github 0.0.0-stage → 0.1.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/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,380 @@
|
|
|
1
|
+
// THE GITHUB REACTION-FEEDBACK ARM (OPE-1502) — a reaction on the agent's own comment becomes a
|
|
2
|
+
// durable feedback signal, the same way it already does on Slack and Discord.
|
|
3
|
+
//
|
|
4
|
+
// 🔴 GITHUB PUSHES NO REACTION WEBHOOK. Slack, Discord, Teams and Linear all DELIVER a reaction
|
|
5
|
+
// event; GitHub delivers none for an issue, a pull request or a comment. That is the same vendor
|
|
6
|
+
// fact the stop arm rests on (channels/github-stop.ts, which is a `stop` COMMENT for exactly this
|
|
7
|
+
// reason). So the inbound half here is a READ, not a delivery — and every design choice below falls
|
|
8
|
+
// out of that one difference.
|
|
9
|
+
//
|
|
10
|
+
// ── THE TRIGGER, AND WHAT IT COSTS
|
|
11
|
+
//
|
|
12
|
+
// The poll rides an inbound delivery this deployment already receives for the same conversation: an
|
|
13
|
+
// `issue_comment` or `issues` webhook names an issue, and the arm then reads the reactions on the
|
|
14
|
+
// agent's own comments in it. A reaction therefore reaches the seat on the NEXT delivery for that
|
|
15
|
+
// conversation, not the instant somebody clicks. That is a real latency, it is the honest ceiling of
|
|
16
|
+
// what GitHub offers, and it is what the bundle's parity declaration says out loud rather than
|
|
17
|
+
// claiming a parity this surface does not have.
|
|
18
|
+
//
|
|
19
|
+
// 🔴 TWO GATES RUN BEFORE THE FIRST REST CALL (OPE-1540), and channels/github-reaction-scope.ts owns
|
|
20
|
+
// both. The agent must be IN this conversation — a conversation it is not in can hold no reaction it
|
|
21
|
+
// could record — and the repository must have hourly budget left. Only then does the arm read
|
|
22
|
+
// anything. Before those gates every delivery paid the issue read and the comment page just to
|
|
23
|
+
// discover there was nothing to look at.
|
|
24
|
+
//
|
|
25
|
+
// 🔴 READING THE NEWEST COMMENTS TAKES AN EXTRA REQUEST, and getting this wrong is silent.
|
|
26
|
+
// `/issues/{n}/comments` returns OLDEST first and has no `direction` parameter — the measured vendor
|
|
27
|
+
// fact this bundle's read adapter is built on (channels/github-read.ts, its header). Asking page 1
|
|
28
|
+
// of a 250-comment thread would hand the poll the conversation's START, so the agent's recent
|
|
29
|
+
// replies would never be examined and the arm would report nothing while looking perfectly healthy.
|
|
30
|
+
// So the poll does what the read adapter does: it takes the issue's own `comments` COUNT, which
|
|
31
|
+
// names the last page directly, and reads that page.
|
|
32
|
+
//
|
|
33
|
+
// THE COST, per ADMITTED delivery — one the two gates above let through: one issue read for the
|
|
34
|
+
// count, one comment page (a second when that page is short and there is a page before it), plus one
|
|
35
|
+
// reactions read per agent comment the page already says carries a reaction — capped by
|
|
36
|
+
// {@link GITHUB_REACTION_POLL_COMMENT_BUDGET}. A conversation with no comments costs the issue read
|
|
37
|
+
// and nothing more. A REFUSED delivery costs no read at all.
|
|
38
|
+
//
|
|
39
|
+
// ── WHY ONLY `added`
|
|
40
|
+
//
|
|
41
|
+
// The reactions route returns the reactions that EXIST. It cannot report one that was taken away, so
|
|
42
|
+
// a poll can only ever see an add. The arm records `added` and never invents a `removed`: a
|
|
43
|
+
// retraction this surface cannot observe is better absent than guessed at.
|
|
44
|
+
//
|
|
45
|
+
// ── THE DEDUPE IS A KEY (OPE-1535)
|
|
46
|
+
//
|
|
47
|
+
// A poll re-reads reactions it has already recorded, so it must not write a second row for each. The
|
|
48
|
+
// arm therefore keeps a durable CLAIM per recorded reaction, keyed by the three fields that identify
|
|
49
|
+
// one: the comment, the reactor and the glyph ({@link githubReactionKey}). A poll asks the store
|
|
50
|
+
// about the exact reaction in front of it and skips the ones it already holds.
|
|
51
|
+
//
|
|
52
|
+
// 🔴 IT IS A `notice_claim`, AND THE LIFETIME IS THE WHOLE POINT. "This reaction was recorded" never
|
|
53
|
+
// stops being true, and a reaction stays visible to the poll for as long as its comment stays in the
|
|
54
|
+
// poll's view — which on a quiet conversation is forever. So the claim must never expire.
|
|
55
|
+
// `claimNotice`/`noticeClaimed` (operational/store.ts) is the tree's at-most-once claim with no TTL
|
|
56
|
+
// and no prune, which is exactly that shape. Growth is one row per recorded reaction — the same
|
|
57
|
+
// number of rows the audit log already holds for them, so the marginal cost is a copy of a thing the
|
|
58
|
+
// deployment already keeps.
|
|
59
|
+
//
|
|
60
|
+
// 🔴 WHY THE AUDIT LOG STOPPED BEING THE ANSWER. It was: the arm read back the newest reaction rows
|
|
61
|
+
// for the surface and skipped what it found. That read is a WINDOW, not a key — `readAudit` takes a
|
|
62
|
+
// LIMIT rather than a cursor, and the surface grain the rows carry is the REPOSITORY, so a repository
|
|
63
|
+
// with more recorded reactions than the window held would re-record one whose row had aged out as
|
|
64
|
+
// soon as a delivery brought the poll back to that conversation. The window's size decided
|
|
65
|
+
// correctness, which is the property a key does not have.
|
|
66
|
+
//
|
|
67
|
+
// 🔴 THE DEDUPE FAILS CLOSED, AND SAYS SO. An unreadable claim reads as ALREADY RECORDED, which
|
|
68
|
+
// bounds what the audit log claims happened — but a store that stays down then disables the feedback
|
|
69
|
+
// signal entirely, with zero rows and no error to look at. So a poll that could not read the claim
|
|
70
|
+
// store writes ONE `github.reaction_claim_unreadable` audit row naming how many reactions it skipped,
|
|
71
|
+
// the same way the budget gate audits its trip (channels/github-reaction-scope.ts).
|
|
72
|
+
//
|
|
73
|
+
// 🔴 THAT ROW ANSWERS A PARTIAL FAULT, NOT EVERY ONE. It is written through `appendAudit`, into the
|
|
74
|
+
// SAME operational store whose claim read just threw — so a whole-store outage loses the row as well,
|
|
75
|
+
// and shows up as the audit DROP COUNTER and its warn instead. The row is what makes the partial case
|
|
76
|
+
// legible: a missing `notice_claim` table, or a grant that covers `audit_log` and not it.
|
|
77
|
+
//
|
|
78
|
+
// 🔴 THE ORDER IS READ, RECORD, CLAIM — and it is chosen so nothing is ever LOST. The claim is
|
|
79
|
+
// written only after `ingestReaction` reports the row recorded, so a sink that fails leaves no claim
|
|
80
|
+
// and the next poll retries the reaction. Claiming first would make the dedupe a mutex across
|
|
81
|
+
// concurrent deliveries, but a failed write would then swallow the reaction permanently — the claim
|
|
82
|
+
// table has no release. Two cases stay open in the same benign direction, both of them a repeated
|
|
83
|
+
// audit row that over-reports the count and corrupts nothing: two deliveries polling the same
|
|
84
|
+
// conversation at the same instant, where both read "not recorded" before either writes; and a claim
|
|
85
|
+
// WRITE that fails after the row landed, which the next poll answers by recording the reaction again.
|
|
86
|
+
// The first was there before the key and is unchanged by it; the second is the price of the order.
|
|
87
|
+
//
|
|
88
|
+
// ── IDENTITY
|
|
89
|
+
//
|
|
90
|
+
// "The agent's own comment" is `<GITHUB_APP_SLUG>[bot]` — the login GitHub gives an App's comments,
|
|
91
|
+
// and the same self-identity channels/github-supervisor.ts drops its own replies by. 🔴 IT FAILS
|
|
92
|
+
// CLOSED: with no slug configured the arm polls nothing at all, because it cannot tell the agent's
|
|
93
|
+
// comments from anybody else's and reading every comment's reactions would record strangers' reactions
|
|
94
|
+
// to strangers' posts as feedback about the agent.
|
|
95
|
+
import { buildIngestedReaction, deploymentOrg, getOperationalStore, ingestReaction, liveReactionSink, resolvePartition, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
96
|
+
import { buildGitHubAuth } from "./github-identity.js";
|
|
97
|
+
import { GITHUB_API_BASE, installationTokenFor } from "./github-post-arm.js";
|
|
98
|
+
import { getJson, liveGithubReadFetch } from "./github-read.js";
|
|
99
|
+
import { auditGitHubReactionClaimUnreadable, githubConversationInScope, spendGitHubPollBudget } from "./github-reaction-scope.js";
|
|
100
|
+
/** The webhook families that name a conversation this arm can poll. Both carry `repository` and an
|
|
101
|
+
* issue number; a PR arrives as an `issue_comment` too, because GitHub models a PR as an issue for
|
|
102
|
+
* timeline comments. */
|
|
103
|
+
const POLL_EVENT_TYPES = new Set(["issue_comment", "issues"]);
|
|
104
|
+
/** One poll examines at most this many of the agent's own comments, newest first. A conversation
|
|
105
|
+
* with more agent comments than this is one where the older ones stopped attracting reactions. */
|
|
106
|
+
export const GITHUB_REACTION_POLL_COMMENT_BUDGET = 10;
|
|
107
|
+
function isObject(v) {
|
|
108
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
109
|
+
}
|
|
110
|
+
function str(v) {
|
|
111
|
+
return typeof v === "string" && v.length > 0 ? v : undefined;
|
|
112
|
+
}
|
|
113
|
+
function num(v) {
|
|
114
|
+
return typeof v === "number" && Number.isFinite(v) ? v : undefined;
|
|
115
|
+
}
|
|
116
|
+
function pick(o, k) {
|
|
117
|
+
return isObject(o) ? o[k] : undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The conversation one delivery names, or null when the delivery names none. NEVER THROWS.
|
|
121
|
+
*
|
|
122
|
+
* It reads the SAME body the stop leg and the ambient leg read, and it accepts a delivery those two
|
|
123
|
+
* refuse — an `issues` event, or an `issue_comment` the agent itself wrote. The poll is about the
|
|
124
|
+
* CONVERSATION, so any accepted delivery naming one is a valid occasion to look.
|
|
125
|
+
*/
|
|
126
|
+
export function parseGitHubReactionPollTarget(body, eventType) {
|
|
127
|
+
if (!POLL_EVENT_TYPES.has(eventType))
|
|
128
|
+
return null;
|
|
129
|
+
let ev;
|
|
130
|
+
try {
|
|
131
|
+
ev = JSON.parse(body);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (!isObject(ev))
|
|
137
|
+
return null;
|
|
138
|
+
const repository = ev["repository"];
|
|
139
|
+
const owner = str(pick(pick(repository, "owner"), "login"));
|
|
140
|
+
const repo = str(pick(repository, "name"));
|
|
141
|
+
const number = num(pick(ev["issue"], "number")) ?? num(pick(ev["pull_request"], "number"));
|
|
142
|
+
if (!owner || !repo || number === undefined)
|
|
143
|
+
return null;
|
|
144
|
+
const installationId = num(pick(ev["installation"], "id"));
|
|
145
|
+
const repoPrivate = pick(repository, "private");
|
|
146
|
+
return {
|
|
147
|
+
owner,
|
|
148
|
+
repo,
|
|
149
|
+
number,
|
|
150
|
+
...(installationId === undefined ? {} : { installationId }),
|
|
151
|
+
...(typeof repoPrivate === "boolean" ? { repoPrivate } : {}),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
/** Is this comment the agent's own? `user.type === "Bot"` AND the configured `<slug>[bot]` login —
|
|
155
|
+
* both, because either alone is wrong: every App's comment is type `Bot`, and a human account may
|
|
156
|
+
* legally be named to look like one. */
|
|
157
|
+
export function githubCommentIsOwn(comment, botLogin) {
|
|
158
|
+
const login = botLogin?.trim().toLowerCase();
|
|
159
|
+
if (!login)
|
|
160
|
+
return false;
|
|
161
|
+
if (comment.authorType?.toLowerCase() !== "bot")
|
|
162
|
+
return false;
|
|
163
|
+
return comment.authorLogin?.toLowerCase() === login;
|
|
164
|
+
}
|
|
165
|
+
/** The identity of one recorded reaction — a comment, a reactor and a glyph. The dedupe key, and the
|
|
166
|
+
* same three fields `liveReactionSink` writes into the audit row. */
|
|
167
|
+
export function githubReactionKey(messageId, reactorId, emoji) {
|
|
168
|
+
return [messageId, reactorId, emoji].join("\u001f");
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Poll one conversation and record every reaction on the agent's own comments that is not recorded
|
|
172
|
+
* yet. Returns one outcome per NEW reaction — an empty array when there is nothing new, which is the
|
|
173
|
+
* ordinary case on a busy conversation.
|
|
174
|
+
*
|
|
175
|
+
* NEVER THROWS: a failed read or a failed sink must never cost the ingress its ack. A comment whose
|
|
176
|
+
* reactions cannot be read is skipped, and the rest of the poll continues.
|
|
177
|
+
*/
|
|
178
|
+
export async function routeGitHubReactionFeedback(target, deps) {
|
|
179
|
+
// FAIL CLOSED on self-identity — see the header. Without it every reaction in the thread would
|
|
180
|
+
// read as feedback on the agent.
|
|
181
|
+
const botLogin = deps.botLogin?.trim().toLowerCase();
|
|
182
|
+
if (!botLogin)
|
|
183
|
+
return [];
|
|
184
|
+
// THE PARTITION IS RESOLVED FIRST, because the gates below are keyed by the same surface the rows
|
|
185
|
+
// are. It is PURE — no store, no network — so computing it ahead of a gate that may refuse costs
|
|
186
|
+
// nothing.
|
|
187
|
+
const partition = resolvePartition({
|
|
188
|
+
channel: { kind: "github" },
|
|
189
|
+
session: {
|
|
190
|
+
auth: {
|
|
191
|
+
current: buildGitHubAuth({
|
|
192
|
+
owner: target.owner,
|
|
193
|
+
repo: target.repo,
|
|
194
|
+
issueNumber: target.number,
|
|
195
|
+
// The POLL has no actor — it is the deployment looking, not a person acting. The reactor's
|
|
196
|
+
// own login rides each ingested reaction instead, which is where it belongs.
|
|
197
|
+
actorLogin: botLogin,
|
|
198
|
+
...(target.repoPrivate === undefined ? {} : { isPrivate: target.repoPrivate }),
|
|
199
|
+
}),
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
const coords = { org: partition.org, workspace: partition.workspace, surfaceKey: partition.surfaceKey };
|
|
204
|
+
// ── THE TWO SCOPE GATES (OPE-1540) ─────────────────────────────────────────────────────────────
|
|
205
|
+
//
|
|
206
|
+
// 🔴 BOTH RUN BEFORE THE FIRST REST CALL, which is the whole of the fix: the cost this bundle used
|
|
207
|
+
// to pay was paid to LEARN there was nothing to do. Gate 1 is the cheap one and the one that cuts
|
|
208
|
+
// the most, so it runs first — and a conversation it refuses spends no budget either, because a
|
|
209
|
+
// poll that never happens is not a poll this repository ran.
|
|
210
|
+
//
|
|
211
|
+
// Both FAIL OPEN on a throw, matching their live implementations: a gate that cannot answer costs
|
|
212
|
+
// REST calls, never a lost feedback signal. The `.catch` is also what keeps this function's
|
|
213
|
+
// never-throws contract true for an injected dep.
|
|
214
|
+
const atIso = deps.now?.() ?? new Date().toISOString();
|
|
215
|
+
if (!(await deps.inScope(target, coords).catch(() => true)))
|
|
216
|
+
return [];
|
|
217
|
+
if (!(await deps.spendBudget(target, coords, atIso).catch(() => true)))
|
|
218
|
+
return [];
|
|
219
|
+
const comments = await deps.listComments(target).catch(() => []);
|
|
220
|
+
const own = comments
|
|
221
|
+
.filter((c) => githubCommentIsOwn(c, botLogin) && (c.reactionCount ?? 0) > 0)
|
|
222
|
+
.slice(0, deps.commentBudget ?? GITHUB_REACTION_POLL_COMMENT_BUDGET);
|
|
223
|
+
if (own.length === 0)
|
|
224
|
+
return [];
|
|
225
|
+
// The keys this poll has already answered for. It is a memo over `deps.recorded`, not the dedupe
|
|
226
|
+
// itself: the durable claim is what decides, and this only stops one poll asking the store twice
|
|
227
|
+
// about a reaction the vendor happened to list twice.
|
|
228
|
+
const asked = new Set();
|
|
229
|
+
// How many reactions this poll skipped because the claim store could not answer. It is a COUNT,
|
|
230
|
+
// not a flag, because the row written at the end reports it — see below.
|
|
231
|
+
let claimUnreadable = 0;
|
|
232
|
+
const outcomes = [];
|
|
233
|
+
for (const comment of own) {
|
|
234
|
+
const reactions = await deps.listReactions(target, comment.id).catch(() => []);
|
|
235
|
+
for (const reaction of reactions) {
|
|
236
|
+
const reactorId = reaction.userLogin;
|
|
237
|
+
// The agent's OWN reactions ride the same route — its `react` verb places them. They are never
|
|
238
|
+
// feedback, and unlike the comment filter this one needs no type check: the login is exact.
|
|
239
|
+
if (!reactorId || reactorId.toLowerCase() === botLogin)
|
|
240
|
+
continue;
|
|
241
|
+
const messageId = String(comment.id);
|
|
242
|
+
const key = githubReactionKey(messageId, reactorId, reaction.content);
|
|
243
|
+
if (asked.has(key))
|
|
244
|
+
continue;
|
|
245
|
+
asked.add(key);
|
|
246
|
+
// FAIL-CLOSED on an unreadable claim: an unknown answer is treated as ALREADY RECORDED, so a
|
|
247
|
+
// store blip costs a delayed feedback row rather than a duplicate one. The opposite posture
|
|
248
|
+
// from the two scope gates above, and deliberately: those bound a COST, this one bounds what
|
|
249
|
+
// the audit log claims happened.
|
|
250
|
+
//
|
|
251
|
+
// 🔴 BUT IT IS NEVER SILENT. Failing closed on a store that stays down disables the whole
|
|
252
|
+
// feedback signal with zero rows and no error, which reads exactly like a week nobody reacted
|
|
253
|
+
// in. The throw is counted here and audited ONCE at the end of the poll.
|
|
254
|
+
const claimed = await deps.recorded(coords, key).catch(() => {
|
|
255
|
+
claimUnreadable++;
|
|
256
|
+
return true;
|
|
257
|
+
});
|
|
258
|
+
if (claimed)
|
|
259
|
+
continue;
|
|
260
|
+
const ingested = buildIngestedReaction({
|
|
261
|
+
surface: "github",
|
|
262
|
+
// Only ever an ADD — see the header. A poll cannot observe a retraction.
|
|
263
|
+
verb: "added",
|
|
264
|
+
reactorId,
|
|
265
|
+
emoji: reaction.content,
|
|
266
|
+
// The CONVERSATION is the channel grain a GitHub turn is keyed by; the COMMENT is the
|
|
267
|
+
// message the reaction landed on.
|
|
268
|
+
target: { channelId: `${target.owner}/${target.repo}#${target.number}`, messageId },
|
|
269
|
+
// Structurally true: the loop only ever reaches the agent's own comments.
|
|
270
|
+
onBotReply: true,
|
|
271
|
+
partition,
|
|
272
|
+
});
|
|
273
|
+
const outcome = await ingestReaction(ingested, deps.sink).catch(() => ({ recorded: false, onBotReply: true, reason: "sink failed" }));
|
|
274
|
+
outcomes.push(outcome);
|
|
275
|
+
// CLAIM ONLY WHAT LANDED — see the header's read/record/claim order. A sink that failed leaves
|
|
276
|
+
// no claim, so the next poll for this conversation tries the reaction again.
|
|
277
|
+
if (outcome.recorded)
|
|
278
|
+
await deps.markRecorded(coords, key, atIso).catch(() => undefined);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// ONE ROW PER POLL for an unreadable claim store, and only when one read actually threw. The count
|
|
282
|
+
// is what makes a single row as informative as the flood it replaces. Best-effort and swallowed,
|
|
283
|
+
// like every other write on this path — the never-throws contract holds through it.
|
|
284
|
+
if (claimUnreadable > 0)
|
|
285
|
+
await deps.auditClaimUnreadable?.(target, coords, atIso, claimUnreadable).catch(() => undefined);
|
|
286
|
+
return outcomes;
|
|
287
|
+
}
|
|
288
|
+
/** The durable claim key one recorded reaction occupies, namespaced by the SURFACE — `notice_claim`
|
|
289
|
+
* is keyed by org alone, so two repositories in one deployment would otherwise share a key space and
|
|
290
|
+
* a comment id collision across repos would read as a duplicate. */
|
|
291
|
+
function githubReactionClaimKey(p, key) {
|
|
292
|
+
return `github-reaction:${p.surfaceKey}\u001f${key}`;
|
|
293
|
+
}
|
|
294
|
+
/** The live dedupe's read half: has this exact reaction already been recorded? */
|
|
295
|
+
async function liveGitHubReactionRecorded(p, key) {
|
|
296
|
+
return getOperationalStore().noticeClaimed(deploymentOrg(), githubReactionClaimKey(p, key));
|
|
297
|
+
}
|
|
298
|
+
/** The live dedupe's write half. Idempotent — a re-claim of a held key answers false, which nothing
|
|
299
|
+
* here reads: the question this store answers is "is it claimed", never "who claimed it". */
|
|
300
|
+
async function liveGitHubReactionMarkRecorded(p, key, atIso) {
|
|
301
|
+
await getOperationalStore().claimNotice(deploymentOrg(), githubReactionClaimKey(p, key), atIso);
|
|
302
|
+
}
|
|
303
|
+
// --- THE LIVE READS ------------------------------------------------------------------------------
|
|
304
|
+
//
|
|
305
|
+
// Every GET below runs under the SAME short-lived installation token every other GitHub call in this
|
|
306
|
+
// bundle mints, through the SAME `getJson` and the SAME transport the read adapter uses — so they
|
|
307
|
+
// inherit its 429 backoff and add no second transport (channels/github-read.ts, and
|
|
308
|
+
// `test:github-write-seam` §4). None of them reads a comment BODY: the poll needs an author, a count
|
|
309
|
+
// and a glyph, and nothing else.
|
|
310
|
+
/** GitHub's own page ceiling for the comment list. The read adapter pages by the same number. */
|
|
311
|
+
const GITHUB_COMMENT_PAGE = 100;
|
|
312
|
+
function tokenFor(target) {
|
|
313
|
+
return installationTokenFor({
|
|
314
|
+
platform: "github",
|
|
315
|
+
owner: target.owner,
|
|
316
|
+
repo: target.repo,
|
|
317
|
+
issueNumber: target.number,
|
|
318
|
+
...(target.installationId === undefined ? {} : { installationId: target.installationId }),
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
/** Production deps for the poll. `botLogin` is `<GITHUB_APP_SLUG>[bot]` — absent when the slug is
|
|
322
|
+
* unset, which is exactly the fail-closed case {@link routeGitHubReactionFeedback} refuses on. */
|
|
323
|
+
export function liveGitHubReactionFeedbackDeps(appSlug, fetchFn = liveGithubReadFetch) {
|
|
324
|
+
const slug = appSlug?.trim();
|
|
325
|
+
return {
|
|
326
|
+
sink: liveReactionSink(),
|
|
327
|
+
...(slug ? { botLogin: `${slug}[bot]` } : {}),
|
|
328
|
+
listComments: async (target) => {
|
|
329
|
+
const token = await tokenFor(target);
|
|
330
|
+
// 🔴 THE LAST PAGE, NOT THE FIRST. `/issues/{n}/comments` returns OLDEST first and honours no
|
|
331
|
+
// `direction` — see the header. The issue object's own `comments` count names the last page
|
|
332
|
+
// directly, which is the same two-request shape channels/github-read.ts uses and for the same
|
|
333
|
+
// reason. Reading page 1 here would examine the conversation's START and find nothing.
|
|
334
|
+
const issue = await getJson(fetchFn, `${GITHUB_API_BASE}/repos/${target.owner}/${target.repo}/issues/${target.number}`, token, "issues.get");
|
|
335
|
+
const total = typeof issue.comments === "number" && issue.comments > 0 ? issue.comments : 0;
|
|
336
|
+
if (total === 0)
|
|
337
|
+
return [];
|
|
338
|
+
const page = (nth) => `${GITHUB_API_BASE}/repos/${target.owner}/${target.repo}/issues/${target.number}/comments?per_page=${GITHUB_COMMENT_PAGE}&page=${nth}`;
|
|
339
|
+
const lastPage = Math.max(1, Math.ceil(total / GITHUB_COMMENT_PAGE));
|
|
340
|
+
let raw = await getJson(fetchFn, page(lastPage), token, "issues.comments.list");
|
|
341
|
+
if (!Array.isArray(raw))
|
|
342
|
+
return [];
|
|
343
|
+
// A short last page can hold fewer of the agent's comments than the budget wants, so the page
|
|
344
|
+
// before it is prepended — the same fallback the read adapter makes, and enough for any budget
|
|
345
|
+
// up to the page size.
|
|
346
|
+
if (raw.length < GITHUB_REACTION_POLL_COMMENT_BUDGET && lastPage > 1) {
|
|
347
|
+
const previous = await getJson(fetchFn, page(lastPage - 1), token, "issues.comments.list");
|
|
348
|
+
if (Array.isArray(previous))
|
|
349
|
+
raw = [...previous, ...raw];
|
|
350
|
+
}
|
|
351
|
+
return raw
|
|
352
|
+
.filter((c) => typeof c.id === "number")
|
|
353
|
+
// NEWEST FIRST, so the budget's slice takes the agent's most recent comments — the ones a
|
|
354
|
+
// person is plausibly still reacting to.
|
|
355
|
+
.reverse()
|
|
356
|
+
.map((c) => ({
|
|
357
|
+
id: c.id,
|
|
358
|
+
...(c.user?.login ? { authorLogin: c.user.login } : {}),
|
|
359
|
+
...(c.user?.type ? { authorType: c.user.type } : {}),
|
|
360
|
+
...(typeof c.reactions?.total_count === "number" ? { reactionCount: c.reactions.total_count } : {}),
|
|
361
|
+
}));
|
|
362
|
+
},
|
|
363
|
+
listReactions: async (target, commentId) => {
|
|
364
|
+
const token = await tokenFor(target);
|
|
365
|
+
const raw = await getJson(fetchFn, `${GITHUB_API_BASE}/repos/${target.owner}/${target.repo}/issues/comments/${commentId}/reactions?per_page=100`, token, "reactions.listForIssueComment");
|
|
366
|
+
return raw
|
|
367
|
+
.filter((r) => typeof r.content === "string" && r.content.length > 0)
|
|
368
|
+
.map((r) => ({
|
|
369
|
+
content: r.content,
|
|
370
|
+
...(r.user?.login ? { userLogin: r.user.login } : {}),
|
|
371
|
+
}));
|
|
372
|
+
},
|
|
373
|
+
recorded: (partition, key) => liveGitHubReactionRecorded(partition, key),
|
|
374
|
+
markRecorded: (partition, key, atIso) => liveGitHubReactionMarkRecorded(partition, key, atIso),
|
|
375
|
+
inScope: (target, partition) => githubConversationInScope(partition.surfaceKey, target.owner, target.repo, target.number),
|
|
376
|
+
spendBudget: async (target, _partition, atIso) => (await spendGitHubPollBudget(target.owner, target.repo, atIso)).admitted,
|
|
377
|
+
auditClaimUnreadable: (target, _partition, atIso, skipped) => auditGitHubReactionClaimUnreadable(target.owner, target.repo, atIso, skipped),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
//# sourceMappingURL=github-reaction-feedback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-reaction-feedback.js","sourceRoot":"","sources":["../../../agent/channels/github-reaction-feedback.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/** How many reaction polls one repository may run in one clock hour. Sized so an ordinarily busy
|
|
2
|
+
* repository never reaches it — the gate is a ceiling on a runaway conversation, not a throttle on
|
|
3
|
+
* normal traffic. One ADMITTED poll costs up to 13 GETs (the issue read, one or two comment pages,
|
|
4
|
+
* and up to `GITHUB_REACTION_POLL_COMMENT_BUDGET` reaction reads), so this bounds a repository's
|
|
5
|
+
* worst hour to ~780 GETs on the installation token the reply path shares. The everyday cost is far
|
|
6
|
+
* below that: most polls read three. */
|
|
7
|
+
export declare const GITHUB_REACTION_POLL_HOURLY_BUDGET = 60;
|
|
8
|
+
/** The audit action a tripped budget writes. Queryable over the control plane's audit route, the
|
|
9
|
+
* same way `github.ambient_skipped` answers "why did the ambient leg stay quiet". */
|
|
10
|
+
export declare const GITHUB_REACTION_POLL_BUDGET_ACTION = "github.reaction_poll_budget";
|
|
11
|
+
/** The audit action an UNREADABLE CLAIM STORE writes, once per poll. The dedupe next door fails
|
|
12
|
+
* CLOSED, so a store that cannot answer "is this recorded" stops every feedback row for as long as
|
|
13
|
+
* it stays down — and a silent zero reads exactly like a quiet week. This row is the signal. It
|
|
14
|
+
* carries the same audit ACTION shape as the budget trip above, so an operator reads both off the
|
|
15
|
+
* control plane's audit route; the GRAIN differs, and {@link auditGitHubReactionClaimUnreadable}
|
|
16
|
+
* says how. */
|
|
17
|
+
export declare const GITHUB_REACTION_CLAIM_UNREADABLE_ACTION = "github.reaction_claim_unreadable";
|
|
18
|
+
/** How long a reply may wait on the best-effort post claim below.
|
|
19
|
+
*
|
|
20
|
+
* 🔴 THE CEILING IS THE POINT, NOT THE NUMBER. `liveGitHubPost` marks the conversation AFTER the
|
|
21
|
+
* comment has already landed, so this write can only ever delay the reply tool's RETURN — and with
|
|
22
|
+
* no ceiling a slow store held that return for as long as it liked, on the turn path. Two seconds is
|
|
23
|
+
* far more than a keyed claim needs and far less than a turn can notice. */
|
|
24
|
+
export declare const GITHUB_POSTED_MARK_TIMEOUT_MS = 2000;
|
|
25
|
+
/** The hourly budget's key for one repository. The clock hour rides IN the key, so the counter needs
|
|
26
|
+
* no reset and a stale bucket simply stops being read. */
|
|
27
|
+
export declare function githubPollBudgetKey(owner: string, repo: string, atIso: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Record that the agent has commented in this conversation. Best-effort and idempotent — a second
|
|
30
|
+
* post in the same conversation re-claims a key that is already claimed, which the store answers
|
|
31
|
+
* false to and this function ignores. NEVER THROWS: a lost mark costs a later poll, never a reply.
|
|
32
|
+
*
|
|
33
|
+
* 🔴 IT IS ALSO BOUNDED, WHICH IS THE OTHER HALF OF THAT CONTRACT. The one caller is
|
|
34
|
+
* `liveGitHubPost`, on the reply tool's own path, and it calls this AFTER the comment has landed. An
|
|
35
|
+
* unbounded await therefore could not lose a reply but could hold the tool's return for as long as
|
|
36
|
+
* the store took — a real hazard on a tree that already stalls in its post-turn closeout. So the
|
|
37
|
+
* write RACES {@link GITHUB_POSTED_MARK_TIMEOUT_MS}: a store slower than that is treated exactly
|
|
38
|
+
* like the store that throws, and costs a later poll rather than the turn.
|
|
39
|
+
*
|
|
40
|
+
* The abandoned write is not cancelled — nothing here can cancel a store call — so it carries its
|
|
41
|
+
* own settled handler and may still land after this returns. That is the harmless direction: the
|
|
42
|
+
* claim is idempotent and its only reader is the next poll.
|
|
43
|
+
*/
|
|
44
|
+
export declare function markGitHubConversationPosted(owner: string | undefined, repo: string | undefined, number: number | undefined, atIso: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Is the agent IN this conversation — has this bundle's reply path posted here, or did an @mention
|
|
47
|
+
* engage the container? Either answers yes; see the header for why both reads are needed.
|
|
48
|
+
*
|
|
49
|
+
* FAIL-OPEN on a store error, and on a delivery that names no container: an unanswerable question
|
|
50
|
+
* admits the poll, whose own parse then refuses a delivery that names nothing.
|
|
51
|
+
*/
|
|
52
|
+
export declare function githubConversationInScope(surfaceKey: string, owner: string | undefined, repo: string | undefined, number: number | undefined): Promise<boolean>;
|
|
53
|
+
/** What one budget check decided, and what it counted. `polls` is the count INCLUDING this one when
|
|
54
|
+
* the check admitted it, so a caller can log the pressure without a second read. */
|
|
55
|
+
export interface GitHubPollBudgetVerdict {
|
|
56
|
+
readonly admitted: boolean;
|
|
57
|
+
readonly polls: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Spend one poll from this repository's hourly budget.
|
|
61
|
+
*
|
|
62
|
+
* Admits and counts while the hour has room; refuses once the budget is spent, and writes ONE
|
|
63
|
+
* {@link GITHUB_REACTION_POLL_BUDGET_ACTION} row for that hour rather than one per refused delivery —
|
|
64
|
+
* the refusals are the loud part of a busy hour, and an unthrottled row per delivery would be the
|
|
65
|
+
* same flood in the audit log that the budget exists to stop in GitHub's API.
|
|
66
|
+
*
|
|
67
|
+
* FAIL-OPEN on a store error: an unreadable counter admits the poll. A budget is a cost bound, so a
|
|
68
|
+
* store blip must never silence the feedback signal.
|
|
69
|
+
*/
|
|
70
|
+
export declare function spendGitHubPollBudget(owner: string, repo: string, atIso: string, budget?: number): Promise<GitHubPollBudgetVerdict>;
|
|
71
|
+
/**
|
|
72
|
+
* Write the one row that says this poll's claim store could not be read.
|
|
73
|
+
*
|
|
74
|
+
* 🔴 WHY IT EXISTS. The reaction dedupe (channels/github-reaction-feedback.ts) fails CLOSED on an
|
|
75
|
+
* unreadable claim — an unknown answer counts as ALREADY RECORDED — which is the right posture for
|
|
76
|
+
* what the audit log claims happened, and a silent one. A store that stays unreadable therefore
|
|
77
|
+
* records nothing at all, forever, and the operator sees a feature that looks healthy and reports
|
|
78
|
+
* zero. This row is the difference between "nobody reacted" and "the store is down".
|
|
79
|
+
*
|
|
80
|
+
* ONE ROW PER POLL, never one per reaction: a busy comment page holds dozens of reactions and one
|
|
81
|
+
* broken store would write a row for each. `skipped` carries the count instead, so the row is as
|
|
82
|
+
* loud as the flood would have been. That is a DIFFERENT GRAIN from the budget trip below, which
|
|
83
|
+
* `markEventSeen` throttles to one row per repository per hour — this one has no throttle, because a
|
|
84
|
+
* poll that could not read the store is a distinct event and the count is the thing being reported.
|
|
85
|
+
*
|
|
86
|
+
* 🔴 WHAT BOUNDS THE VOLUME, HONESTLY. The poll reaches here only past gate 2, so an ordinary hour
|
|
87
|
+
* holds at most {@link GITHUB_REACTION_POLL_HOURLY_BUDGET} of these per repository — but only WHILE
|
|
88
|
+
* THE BUDGET COUNTER IS WRITABLE. Gate 2 fails open on a store error, so a fault that breaks the
|
|
89
|
+
* `task_result` write while `audit_log` inserts still land gives one row per delivery instead. That
|
|
90
|
+
* is the same trade the gate already made, and both directions of it are a cost, never a lost row.
|
|
91
|
+
*
|
|
92
|
+
* 🔴 AND IT IS NOT A SIGNAL FOR EVERY OUTAGE. `appendAudit` writes through the SAME operational
|
|
93
|
+
* store whose claim read just threw, so a whole-store outage loses this row too — best-effort, one
|
|
94
|
+
* retry, then a drop-counter tick and a `[open-everywhere:audit] dropped a …` warn (core
|
|
95
|
+
* agent/audit/audit.ts). This row is what makes a PARTIAL fault legible — a missing `notice_claim`
|
|
96
|
+
* table, a table-level grant — and the audit drop counter is what makes the total one legible. The
|
|
97
|
+
* operator doc states the same split.
|
|
98
|
+
*
|
|
99
|
+
* Best-effort, like every other write in this module: nothing here may cost the ingress its ack.
|
|
100
|
+
*/
|
|
101
|
+
export declare function auditGitHubReactionClaimUnreadable(owner: string, repo: string, atIso: string, skipped: number): Promise<void>;
|
|
102
|
+
//# sourceMappingURL=github-reaction-scope.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-reaction-scope.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-reaction-scope.ts"],"names":[],"mappings":"AAmFA;;;;;yCAKyC;AACzC,eAAO,MAAM,kCAAkC,KAAK,CAAC;AAMrD;sFACsF;AACtF,eAAO,MAAM,kCAAkC,gCAAgC,CAAC;AAEhF;;;;;gBAKgB;AAChB,eAAO,MAAM,uCAAuC,qCAAqC,CAAC;AAE1F;;;;;4EAK4E;AAC5E,eAAO,MAAM,6BAA6B,OAAO,CAAC;AAclD;2DAC2D;AAC3D,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAEtF;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6BhK;AAED;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAgBrK;AAED;qFACqF;AACrF,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,SAAqC,GAAG,OAAO,CAAC,uBAAuB,CAAC,CAsBrK;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,kCAAkC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAcnI"}
|