@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,296 @@
|
|
|
1
|
+
// THE GITHUB REACTION-POLL SCOPE (OPE-1540) โ the two gates that decide whether one delivery is
|
|
2
|
+
// allowed to spend REST calls looking for reactions, and the durable record the first gate reads.
|
|
3
|
+
//
|
|
4
|
+
// WHY A GATE EXISTS AT ALL. GitHub pushes no reaction webhook, so the feedback arm
|
|
5
|
+
// (channels/github-reaction-feedback.ts) POLLS: every signature-verified `issue_comment`/`issues`
|
|
6
|
+
// delivery made it read the issue and a comment page, and only then could it learn that the agent
|
|
7
|
+
// has never commented in that conversation and there is nothing to look at. That is 2โ3 installation
|
|
8
|
+
// REST calls per delivery, on the SAME installation token the reply path mints. A repository taking
|
|
9
|
+
// 400 deliveries an hour spent ~800โ1200 GETs an hour learning nothing, and the 429 pressure landed
|
|
10
|
+
// on the agent's own posts.
|
|
11
|
+
//
|
|
12
|
+
// โโ GATE 1: THE CONVERSATION MUST BE ONE THE AGENT IS IN, AND IT READS TWO RECORDS
|
|
13
|
+
//
|
|
14
|
+
// A reaction the arm can record is a reaction on the AGENT's own comment, so a conversation the agent
|
|
15
|
+
// has never commented in can hold none. Gate 1 answers "is the agent in this conversation" from two
|
|
16
|
+
// durable records, and admits on EITHER. Both are keyed store reads; neither costs a REST call.
|
|
17
|
+
//
|
|
18
|
+
// 1. THE POST CLAIM, written by this bundle's own reply path (channels/github-post-arm.ts
|
|
19
|
+
// `liveGitHubPost`). That is the one place a GitHub comment leaves THIS BUNDLE โ every `reply`
|
|
20
|
+
// tool call, every unified-window reply and every ambient-supervisor answer.
|
|
21
|
+
// 2. THE ENGAGEMENT ROW, written by the ยง2.5 seam (channels/github-engagement.ts) when somebody
|
|
22
|
+
// @mentions the app in a container.
|
|
23
|
+
//
|
|
24
|
+
// ๐ด THE SECOND READ EXISTS BECAUSE THIS BUNDLE IS NOT THE ONLY POSTER. eve's default posts the
|
|
25
|
+
// `input.requested` comment, which this bundle does not override, and it still posts a NATIVE
|
|
26
|
+
// terminal completion on the paths that fall back to it โ a review thread's inline reply, and a
|
|
27
|
+
// state with no post anchor (`channels/github.ts`). (An ordinary native terminal completion claims
|
|
28
|
+
// here now: it posts through `liveGitHubPost` since OPE-1681, so that example is no longer one.) Those comments carry the app's login and attract reactions exactly as a `reply`
|
|
29
|
+
// does, and NOTHING in this bundle sees them go out โ so a post-claim-only gate would refuse those
|
|
30
|
+
// conversations forever, not merely until the next reply. The engagement row is what covers them: the
|
|
31
|
+
// native leg wakes only on an @mention (which marks the row) or on a plain comment in a container
|
|
32
|
+
// already marked, so every conversation eve can post in has one.
|
|
33
|
+
//
|
|
34
|
+
// ๐ด IT ADMITS A LITTLE MORE THAN "HAS POSTED", ON PURPOSE. A container somebody @mentioned the app
|
|
35
|
+
// in where the app then said nothing is admitted and costs a poll. That is the safe direction โ the
|
|
36
|
+
// alternative is a reaction the deployment can never record โ and gate 2 caps what it can cost.
|
|
37
|
+
//
|
|
38
|
+
// ๐ด THE POST CLAIM IS A `notice_claim`, AND THE LIFETIME IS THE POINT. "The agent has commented
|
|
39
|
+
// here" never stops being true, so the record must never expire: a TTL would blind the poll to a
|
|
40
|
+
// quiet conversation's reactions the moment the TTL lapsed, and re-writing the mark needs a post
|
|
41
|
+
// nobody is going to make. `claimNotice`/`noticeClaimed` (operational/store.ts) is the tree's
|
|
42
|
+
// at-most-once claim with no TTL and no prune, which is exactly this shape. Growth is one row per
|
|
43
|
+
// conversation the agent has ever commented in โ bounded by conversations, never by deliveries.
|
|
44
|
+
//
|
|
45
|
+
// ๐ด WHAT THE CUT-OVER COSTS, STATED. Both records start from what already exists: engagement rows
|
|
46
|
+
// are already there for every container an @mention ever reached, and the post claim starts EMPTY. So
|
|
47
|
+
// a conversation the agent only ever answered ambiently โ no @mention, hence no engagement row โ and
|
|
48
|
+
// has not answered since this shipped is not polled until it answers there again. The reaction arm
|
|
49
|
+
// landed one day before this gate (OPE-1502, PR #587), so the window of reactions that can fall into
|
|
50
|
+
// that gap is one day wide. It is a real gap, not a theoretical one.
|
|
51
|
+
//
|
|
52
|
+
// FAIL-OPEN on a store error. An unreadable record answers "the agent is in this conversation", so a
|
|
53
|
+
// store blip costs REST calls rather than silently stopping the feedback signal. It is the opposite
|
|
54
|
+
// posture from the reaction DEDUPE next door (channels/github-reaction-feedback.ts), which fails
|
|
55
|
+
// CLOSED โ these two gates bound a COST, and that one bounds what the audit log claims happened.
|
|
56
|
+
//
|
|
57
|
+
// โโ GATE 2: A PER-REPOSITORY HOURLY BUDGET
|
|
58
|
+
//
|
|
59
|
+
// Gate 1 bounds the poll to conversations the agent is in, which is the big cut. It does NOT bound
|
|
60
|
+
// what one very busy conversation can spend: a hundred comments an hour in a pull request the agent
|
|
61
|
+
// replied to is a hundred polls. So a second gate caps the polls one REPOSITORY may run in one clock
|
|
62
|
+
// hour, and the trip is AUDITED โ a silently capped feedback signal reads exactly like a feature that
|
|
63
|
+
// stopped working.
|
|
64
|
+
//
|
|
65
|
+
// ๐ด THE COUNTER IS APPROXIMATE UNDER CONCURRENCY, AND THAT IS ACCEPTED. It is a read-then-write over
|
|
66
|
+
// the opaque `task_result` KV, not an atomic increment, so two deliveries racing on the same repo can
|
|
67
|
+
// both read N and write N+1 โ the hour's true count can exceed the budget by roughly the number of
|
|
68
|
+
// concurrent deliveries. A rate budget is a cost bound, not a security control: over-counting would
|
|
69
|
+
// drop feedback that should have been recorded, under-counting costs a few extra GETs. The cheap,
|
|
70
|
+
// non-atomic direction is the one whose failure mode is the harmless one.
|
|
71
|
+
//
|
|
72
|
+
// ๐ด THE TTL IS COUPLED, SO IT IS NOT A KNOB. `task_result`'s prune is ORG-WIDE and uses the CALLING
|
|
73
|
+
// writer's own ttl (operational/store.ts, the task_result section), so every consumer must pass the
|
|
74
|
+
// same six hours. {@link GITHUB_POLL_BUDGET_TTL_MS} exists to be that number and nothing else โ a
|
|
75
|
+
// shorter one here would start deleting `model/turn-state.ts`'s live rows on every delivery.
|
|
76
|
+
import { appendAudit, deploymentOrg, getOperationalStore, } from "@open-everywhere/core/agent/adapter-api.js";
|
|
77
|
+
import { githubConversationKey } from "./github-identity.js";
|
|
78
|
+
/** How many reaction polls one repository may run in one clock hour. Sized so an ordinarily busy
|
|
79
|
+
* repository never reaches it โ the gate is a ceiling on a runaway conversation, not a throttle on
|
|
80
|
+
* normal traffic. One ADMITTED poll costs up to 13 GETs (the issue read, one or two comment pages,
|
|
81
|
+
* and up to `GITHUB_REACTION_POLL_COMMENT_BUDGET` reaction reads), so this bounds a repository's
|
|
82
|
+
* worst hour to ~780 GETs on the installation token the reply path shares. The everyday cost is far
|
|
83
|
+
* below that: most polls read three. */
|
|
84
|
+
export const GITHUB_REACTION_POLL_HOURLY_BUDGET = 60;
|
|
85
|
+
/** The `task_result` retention every consumer of that table shares. See the header: it is coupled,
|
|
86
|
+
* not tunable. Six hours, the same window `model/turn-state.ts` and the three other consumers use. */
|
|
87
|
+
const GITHUB_POLL_BUDGET_TTL_MS = 6 * 60 * 60 * 1000;
|
|
88
|
+
/** The audit action a tripped budget writes. Queryable over the control plane's audit route, the
|
|
89
|
+
* same way `github.ambient_skipped` answers "why did the ambient leg stay quiet". */
|
|
90
|
+
export const GITHUB_REACTION_POLL_BUDGET_ACTION = "github.reaction_poll_budget";
|
|
91
|
+
/** The audit action an UNREADABLE CLAIM STORE writes, once per poll. The dedupe next door fails
|
|
92
|
+
* CLOSED, so a store that cannot answer "is this recorded" stops every feedback row for as long as
|
|
93
|
+
* it stays down โ and a silent zero reads exactly like a quiet week. This row is the signal. It
|
|
94
|
+
* carries the same audit ACTION shape as the budget trip above, so an operator reads both off the
|
|
95
|
+
* control plane's audit route; the GRAIN differs, and {@link auditGitHubReactionClaimUnreadable}
|
|
96
|
+
* says how. */
|
|
97
|
+
export const GITHUB_REACTION_CLAIM_UNREADABLE_ACTION = "github.reaction_claim_unreadable";
|
|
98
|
+
/** How long a reply may wait on the best-effort post claim below.
|
|
99
|
+
*
|
|
100
|
+
* ๐ด THE CEILING IS THE POINT, NOT THE NUMBER. `liveGitHubPost` marks the conversation AFTER the
|
|
101
|
+
* comment has already landed, so this write can only ever delay the reply tool's RETURN โ and with
|
|
102
|
+
* no ceiling a slow store held that return for as long as it liked, on the turn path. Two seconds is
|
|
103
|
+
* far more than a keyed claim needs and far less than a turn can notice. */
|
|
104
|
+
export const GITHUB_POSTED_MARK_TIMEOUT_MS = 2000;
|
|
105
|
+
/** How long the trip row's once-per-hour guard holds. It is the bucket width, not a dedupe window:
|
|
106
|
+
* the key already carries the hour, so this only has to outlive the hour it names. */
|
|
107
|
+
const BUDGET_AUDIT_TTL_MS = 2 * 60 * 60 * 1000;
|
|
108
|
+
/** The claim key for "the agent has commented in this conversation". `githubConversationKey` is the
|
|
109
|
+
* SAME `owner/repo#N` anchor the engagement registry and the stop registry address, so the mark and
|
|
110
|
+
* every other per-conversation record in this bundle name the container the same way. */
|
|
111
|
+
function githubPostedClaimKey(owner, repo, number) {
|
|
112
|
+
const anchor = githubConversationKey(owner, repo, number);
|
|
113
|
+
return anchor ? `github-posted:${anchor}` : undefined;
|
|
114
|
+
}
|
|
115
|
+
/** The hourly budget's key for one repository. The clock hour rides IN the key, so the counter needs
|
|
116
|
+
* no reset and a stale bucket simply stops being read. */
|
|
117
|
+
export function githubPollBudgetKey(owner, repo, atIso) {
|
|
118
|
+
return `github-reaction-poll-budget:${owner}/${repo}:${atIso.slice(0, 13)}`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Record that the agent has commented in this conversation. Best-effort and idempotent โ a second
|
|
122
|
+
* post in the same conversation re-claims a key that is already claimed, which the store answers
|
|
123
|
+
* false to and this function ignores. NEVER THROWS: a lost mark costs a later poll, never a reply.
|
|
124
|
+
*
|
|
125
|
+
* ๐ด IT IS ALSO BOUNDED, WHICH IS THE OTHER HALF OF THAT CONTRACT. The one caller is
|
|
126
|
+
* `liveGitHubPost`, on the reply tool's own path, and it calls this AFTER the comment has landed. An
|
|
127
|
+
* unbounded await therefore could not lose a reply but could hold the tool's return for as long as
|
|
128
|
+
* the store took โ a real hazard on a tree that already stalls in its post-turn closeout. So the
|
|
129
|
+
* write RACES {@link GITHUB_POSTED_MARK_TIMEOUT_MS}: a store slower than that is treated exactly
|
|
130
|
+
* like the store that throws, and costs a later poll rather than the turn.
|
|
131
|
+
*
|
|
132
|
+
* The abandoned write is not cancelled โ nothing here can cancel a store call โ so it carries its
|
|
133
|
+
* own settled handler and may still land after this returns. That is the harmless direction: the
|
|
134
|
+
* claim is idempotent and its only reader is the next poll.
|
|
135
|
+
*/
|
|
136
|
+
export async function markGitHubConversationPosted(owner, repo, number, atIso) {
|
|
137
|
+
const key = githubPostedClaimKey(owner, repo, number);
|
|
138
|
+
if (!key)
|
|
139
|
+
return;
|
|
140
|
+
let write;
|
|
141
|
+
try {
|
|
142
|
+
// The `.then(_, _)` is what keeps an abandoned rejection from surfacing as an unhandled one:
|
|
143
|
+
// after the race gives up, nothing else is holding this promise.
|
|
144
|
+
write = getOperationalStore()
|
|
145
|
+
.claimNotice(deploymentOrg(), key, atIso)
|
|
146
|
+
.then(() => undefined, () => undefined);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// A store that cannot even be resolved. The reply already landed; there is nothing to wait on.
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
// `unref` so the ceiling never keeps a host alive on its own, and `clearTimeout` so a write that
|
|
153
|
+
// beat it leaves no pending timer behind.
|
|
154
|
+
let timer;
|
|
155
|
+
const ceiling = new Promise((resolve) => {
|
|
156
|
+
timer = setTimeout(resolve, GITHUB_POSTED_MARK_TIMEOUT_MS);
|
|
157
|
+
timer.unref?.();
|
|
158
|
+
});
|
|
159
|
+
try {
|
|
160
|
+
await Promise.race([write, ceiling]);
|
|
161
|
+
}
|
|
162
|
+
finally {
|
|
163
|
+
clearTimeout(timer);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Is the agent IN this conversation โ has this bundle's reply path posted here, or did an @mention
|
|
168
|
+
* engage the container? Either answers yes; see the header for why both reads are needed.
|
|
169
|
+
*
|
|
170
|
+
* FAIL-OPEN on a store error, and on a delivery that names no container: an unanswerable question
|
|
171
|
+
* admits the poll, whose own parse then refuses a delivery that names nothing.
|
|
172
|
+
*/
|
|
173
|
+
export async function githubConversationInScope(surfaceKey, owner, repo, number) {
|
|
174
|
+
const key = githubPostedClaimKey(owner, repo, number);
|
|
175
|
+
const anchor = githubConversationKey(owner, repo, number);
|
|
176
|
+
if (!key || !anchor)
|
|
177
|
+
return true;
|
|
178
|
+
const org = deploymentOrg();
|
|
179
|
+
try {
|
|
180
|
+
if (await getOperationalStore().noticeClaimed(org, key))
|
|
181
|
+
return true;
|
|
182
|
+
// ๐ด THE STORE, NOT `getThreadEngagement`. That helper swallows its own failure and answers
|
|
183
|
+
// null, so an unreadable registry would be indistinguishable from an un-engaged container and
|
|
184
|
+
// this gate would fail CLOSED on a store blip โ the one direction the header forbids. Reading
|
|
185
|
+
// the store directly lets the catch below take the fail-open answer for BOTH records. The
|
|
186
|
+
// coordinates are the helper's own: `p.org` and `p.surfaceKey`, keyed by the container anchor.
|
|
187
|
+
return (await getOperationalStore().getEngagement(org, surfaceKey, anchor)) !== null;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Spend one poll from this repository's hourly budget.
|
|
195
|
+
*
|
|
196
|
+
* Admits and counts while the hour has room; refuses once the budget is spent, and writes ONE
|
|
197
|
+
* {@link GITHUB_REACTION_POLL_BUDGET_ACTION} row for that hour rather than one per refused delivery โ
|
|
198
|
+
* the refusals are the loud part of a busy hour, and an unthrottled row per delivery would be the
|
|
199
|
+
* same flood in the audit log that the budget exists to stop in GitHub's API.
|
|
200
|
+
*
|
|
201
|
+
* FAIL-OPEN on a store error: an unreadable counter admits the poll. A budget is a cost bound, so a
|
|
202
|
+
* store blip must never silence the feedback signal.
|
|
203
|
+
*/
|
|
204
|
+
export async function spendGitHubPollBudget(owner, repo, atIso, budget = GITHUB_REACTION_POLL_HOURLY_BUDGET) {
|
|
205
|
+
const org = deploymentOrg();
|
|
206
|
+
const key = githubPollBudgetKey(owner, repo, atIso);
|
|
207
|
+
let used = 0;
|
|
208
|
+
try {
|
|
209
|
+
const raw = await getOperationalStore().getTaskResult(org, key);
|
|
210
|
+
const parsed = raw ? JSON.parse(raw) : undefined;
|
|
211
|
+
used = typeof parsed?.polls === "number" && Number.isFinite(parsed.polls) ? parsed.polls : 0;
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
return { admitted: true, polls: 0 };
|
|
215
|
+
}
|
|
216
|
+
if (used >= budget) {
|
|
217
|
+
await auditBudgetTrip(org, owner, repo, atIso, used, budget);
|
|
218
|
+
return { admitted: false, polls: used };
|
|
219
|
+
}
|
|
220
|
+
const polls = used + 1;
|
|
221
|
+
try {
|
|
222
|
+
await getOperationalStore().putTaskResult(org, key, JSON.stringify({ polls }), atIso, GITHUB_POLL_BUDGET_TTL_MS);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
// The count is lost for this delivery; the poll still runs. Under-counting costs GETs, never a row.
|
|
226
|
+
}
|
|
227
|
+
return { admitted: true, polls };
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Write the one row that says this poll's claim store could not be read.
|
|
231
|
+
*
|
|
232
|
+
* ๐ด WHY IT EXISTS. The reaction dedupe (channels/github-reaction-feedback.ts) fails CLOSED on an
|
|
233
|
+
* unreadable claim โ an unknown answer counts as ALREADY RECORDED โ which is the right posture for
|
|
234
|
+
* what the audit log claims happened, and a silent one. A store that stays unreadable therefore
|
|
235
|
+
* records nothing at all, forever, and the operator sees a feature that looks healthy and reports
|
|
236
|
+
* zero. This row is the difference between "nobody reacted" and "the store is down".
|
|
237
|
+
*
|
|
238
|
+
* ONE ROW PER POLL, never one per reaction: a busy comment page holds dozens of reactions and one
|
|
239
|
+
* broken store would write a row for each. `skipped` carries the count instead, so the row is as
|
|
240
|
+
* loud as the flood would have been. That is a DIFFERENT GRAIN from the budget trip below, which
|
|
241
|
+
* `markEventSeen` throttles to one row per repository per hour โ this one has no throttle, because a
|
|
242
|
+
* poll that could not read the store is a distinct event and the count is the thing being reported.
|
|
243
|
+
*
|
|
244
|
+
* ๐ด WHAT BOUNDS THE VOLUME, HONESTLY. The poll reaches here only past gate 2, so an ordinary hour
|
|
245
|
+
* holds at most {@link GITHUB_REACTION_POLL_HOURLY_BUDGET} of these per repository โ but only WHILE
|
|
246
|
+
* THE BUDGET COUNTER IS WRITABLE. Gate 2 fails open on a store error, so a fault that breaks the
|
|
247
|
+
* `task_result` write while `audit_log` inserts still land gives one row per delivery instead. That
|
|
248
|
+
* is the same trade the gate already made, and both directions of it are a cost, never a lost row.
|
|
249
|
+
*
|
|
250
|
+
* ๐ด AND IT IS NOT A SIGNAL FOR EVERY OUTAGE. `appendAudit` writes through the SAME operational
|
|
251
|
+
* store whose claim read just threw, so a whole-store outage loses this row too โ best-effort, one
|
|
252
|
+
* retry, then a drop-counter tick and a `[open-everywhere:audit] dropped a โฆ` warn (core
|
|
253
|
+
* agent/audit/audit.ts). This row is what makes a PARTIAL fault legible โ a missing `notice_claim`
|
|
254
|
+
* table, a table-level grant โ and the audit drop counter is what makes the total one legible. The
|
|
255
|
+
* operator doc states the same split.
|
|
256
|
+
*
|
|
257
|
+
* Best-effort, like every other write in this module: nothing here may cost the ingress its ack.
|
|
258
|
+
*/
|
|
259
|
+
export async function auditGitHubReactionClaimUnreadable(owner, repo, atIso, skipped) {
|
|
260
|
+
try {
|
|
261
|
+
await appendAudit({
|
|
262
|
+
ts: atIso,
|
|
263
|
+
org: deploymentOrg(),
|
|
264
|
+
platform: "github",
|
|
265
|
+
workspace: owner,
|
|
266
|
+
surface: `github/${owner}/${repo}`,
|
|
267
|
+
action: GITHUB_REACTION_CLAIM_UNREADABLE_ACTION,
|
|
268
|
+
detail: { skipped, hour: atIso.slice(0, 13) },
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
// Nothing here may cost the ingress its ack.
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** The trip row, at most once per repository per hour. Both the throttle and the write are
|
|
276
|
+
* best-effort: a budget that cannot audit itself still refuses the poll. */
|
|
277
|
+
async function auditBudgetTrip(org, owner, repo, atIso, used, budget) {
|
|
278
|
+
try {
|
|
279
|
+
const first = await getOperationalStore().markEventSeen(org, `${githubPollBudgetKey(owner, repo, atIso)}:tripped`, atIso, BUDGET_AUDIT_TTL_MS);
|
|
280
|
+
if (!first)
|
|
281
|
+
return;
|
|
282
|
+
await appendAudit({
|
|
283
|
+
ts: atIso,
|
|
284
|
+
org,
|
|
285
|
+
platform: "github",
|
|
286
|
+
workspace: owner,
|
|
287
|
+
surface: `github/${owner}/${repo}`,
|
|
288
|
+
action: GITHUB_REACTION_POLL_BUDGET_ACTION,
|
|
289
|
+
detail: { budget, polls: used, hour: atIso.slice(0, 13) },
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// Nothing here may cost the ingress its ack.
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
//# sourceMappingURL=github-reaction-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-reaction-scope.js","sourceRoot":"","sources":["../../../agent/channels/github-reaction-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { type AdapterReadFetch, type BindableContext, type PrFetchFn, type RenderedLine, type RetryPolicy, type WakeReadAddress } from "@open-everywhere/core/agent/adapter-api.js";
|
|
2
|
+
/**
|
|
3
|
+
* The 429 budget ONE READ may spend, across every call it makes (OPE-1299).
|
|
4
|
+
*
|
|
5
|
+
* ๐ด THE DEFECT WAS MULTIPLICATION, NOT THE PER-CALL POLICY. `withRetryAfterLoose`'s budget is
|
|
6
|
+
* per call, and both reads here walk several: `readChannel` issues up to three (`issues.get`, then
|
|
7
|
+
* one or two `issues.comments.list` pages) and `readThread` up to {@link MAX_REVIEW_PAGES}
|
|
8
|
+
* `pulls.comments.list` pages (it fetches no issue object โ the review-comment walk is the whole
|
|
9
|
+
* read). Under the shared 60 s default that is ~3 and ~5 minutes of waiting for ONE `read_channel` /
|
|
10
|
+
* `read_thread` tool call, and nothing bounded the sum.
|
|
11
|
+
*
|
|
12
|
+
* So the whole read shares ONE deadline, read fresh per call ({@link walkRetry}). The first call
|
|
13
|
+
* still gets the full budget โ a single-call read is not tightened at all, which is why this costs
|
|
14
|
+
* no extra failures on the ordinary path โ and each later call gets only what is left. The worst
|
|
15
|
+
* case for a whole read becomes this number instead of a multiple of it.
|
|
16
|
+
*
|
|
17
|
+
* ๐ด THIS IS THE SEAM'S OWN POLICY, NOT A CALLER'S. `ReadAdapter.readChannel`/`readThread`
|
|
18
|
+
* (core's channels/channel-read.ts) take no options object, and every one of the six adapters
|
|
19
|
+
* already spends the trailing parameter on its own transport seam โ so widening the interface to
|
|
20
|
+
* carry a policy is a cross-provider change, and no production caller could pass one until it
|
|
21
|
+
* happened. Slack answered the same question the same way: `PROBE_OPERATIONS`
|
|
22
|
+
* (providers/slack/agent/channels/slack-web-api.ts) is a table the seam applies itself, with an
|
|
23
|
+
* explicit dependency still winning. `getJson`'s `retry` argument keeps that property here โ a test
|
|
24
|
+
* passes its own policy and this default never reaches it.
|
|
25
|
+
*
|
|
26
|
+
* 60 s, matching `DEFAULT_TOTAL_BUDGET_MS`: a read that 429s out degrades the turn's context rather
|
|
27
|
+
* than losing an answer, so the budget buys latency back without making a first attempt any more
|
|
28
|
+
* likely to fail.
|
|
29
|
+
*/
|
|
30
|
+
export declare const READ_WALK_BUDGET_MS = 60000;
|
|
31
|
+
/**
|
|
32
|
+
* The clock and sleep one walk runs on. Absent in production โ the adapter takes the real ones.
|
|
33
|
+
*
|
|
34
|
+
* ๐ด IT IS HERE SO THE SHARED BUDGET IS TESTABLE AT ALL. The claim this seam makes is about the
|
|
35
|
+
* SECOND and later calls of a walk, and a walk under the real clock can only be observed by waiting
|
|
36
|
+
* out its real waits. `github-read-test.ts` ยง 8 drives a clock that advances on each recorded sleep,
|
|
37
|
+
* which is what lets one assertion separate "one deadline for the walk" from "one budget per call".
|
|
38
|
+
*/
|
|
39
|
+
export interface ReadWalkSeam {
|
|
40
|
+
readonly now?: () => number;
|
|
41
|
+
readonly sleep?: (ms: number) => Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
/** The policy one call inside a walk takes: whatever is left of the walk's shared deadline. Zero is
|
|
44
|
+
* a real value the primitive honours โ at the deadline, a retry gives up instead of waiting. */
|
|
45
|
+
export declare function walkRetry(deadlineMs: number, seam?: ReadWalkSeam): RetryPolicy;
|
|
46
|
+
/** One GitHub conversation's address, parsed out of the container id. */
|
|
47
|
+
export interface GitHubContainer {
|
|
48
|
+
/** The repo owner, as GitHub spells it โ the API path segment, never a sanitized key segment. */
|
|
49
|
+
readonly owner: string;
|
|
50
|
+
readonly repo: string;
|
|
51
|
+
/** `owner/repo` โ the form every core SCM helper takes. */
|
|
52
|
+
readonly repoFull: string;
|
|
53
|
+
/** The issue or PR number. */
|
|
54
|
+
readonly number: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Parse `owner/repo#number` โ the container id {@link githubReadAdapter.wakeAddress} mints.
|
|
58
|
+
*
|
|
59
|
+
* Returns `undefined` on anything else rather than guessing a part. A partial address would fetch
|
|
60
|
+
* another repo's conversation of the same number, which is the silent mis-address the whole
|
|
61
|
+
* wake-container design refuses (core channels/wake-container.ts).
|
|
62
|
+
*/
|
|
63
|
+
export declare function parseGithubContainer(container: string): GitHubContainer | undefined;
|
|
64
|
+
/** The live transport. Injected in every read below so the whole adapter drives offline โ the same
|
|
65
|
+
* DI shape slack-read / discord-read take. */
|
|
66
|
+
/** THE package's ONE read transport. Exported so channels/github-reaction-feedback.ts borrows it
|
|
67
|
+
* rather than reaching for `globalThis.fetch` itself โ a second reach would be a second transport
|
|
68
|
+
* outside this module's retry seam, which `test:github-write-seam` ยง4 exists to refuse. */
|
|
69
|
+
export declare const liveGithubReadFetch: PrFetchFn;
|
|
70
|
+
/**
|
|
71
|
+
* `operation` is the vendor route name (OPE-1255) `withRetryAfterLoose`'s `RateLimitExhaustedError`
|
|
72
|
+
* reports on exhaustion โ this read-only transport was, until now, the ONE issue/PR reader in this
|
|
73
|
+
* package with no 429 backoff (`docs/extending/provider-rate-limits.md`).
|
|
74
|
+
*
|
|
75
|
+
* Exported so its retry behaviour is unit-tested directly (github-read-test.ts), the same way
|
|
76
|
+
* `github-rate-limit-test.ts` drives `githubWrite` directly rather than through a full reply.
|
|
77
|
+
*
|
|
78
|
+
* `retry` reaches no caller OUTSIDE this file โ `ReadAdapter.readChannel`/`readThread`
|
|
79
|
+
* (channels/channel-read.ts) take no options object. Since OPE-1299 the adapter itself passes one:
|
|
80
|
+
* each read builds a shared deadline and hands every call what is left of it
|
|
81
|
+
* ({@link READ_WALK_BUDGET_MS}, {@link walkRetry}), so a multi-call read is bounded as a whole.
|
|
82
|
+
*/
|
|
83
|
+
export declare function getJson<T>(fetchFn: PrFetchFn, url: string, token: string, operation: string, retry?: RetryPolicy): Promise<T>;
|
|
84
|
+
/** The raw shapes this adapter reads. Only the fields it renders are declared. */
|
|
85
|
+
interface RawGitHubUser {
|
|
86
|
+
readonly login?: string;
|
|
87
|
+
readonly type?: string;
|
|
88
|
+
}
|
|
89
|
+
interface RawComment {
|
|
90
|
+
readonly id?: number;
|
|
91
|
+
readonly body?: string | null;
|
|
92
|
+
readonly user?: RawGitHubUser;
|
|
93
|
+
readonly created_at?: string;
|
|
94
|
+
readonly in_reply_to_id?: number | null;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* The review comments belonging to ONE thread, oldest-first โ the thread's own root plus every
|
|
98
|
+
* comment that replies into it. PURE, and exported for the render test.
|
|
99
|
+
*
|
|
100
|
+
* GitHub sets `in_reply_to_id` to the thread's ROOT on every reply, not to the comment being
|
|
101
|
+
* answered, so one equality covers a thread of any depth. The root itself carries no
|
|
102
|
+
* `in_reply_to_id`, which is why it is matched by id.
|
|
103
|
+
*/
|
|
104
|
+
export declare function reviewThreadLines(comments: readonly RawComment[], rootId: string): RenderedLine[];
|
|
105
|
+
export declare const githubReadAdapter: {
|
|
106
|
+
platform: "github";
|
|
107
|
+
/**
|
|
108
|
+
* OPE-929 โ GitHub's own spelling of "which container and which thread is this turn in".
|
|
109
|
+
*
|
|
110
|
+
* The CONTAINER is the conversation key `owner/repo#number`, read off the binding's
|
|
111
|
+
* `anchorChannelId`: `bindGitHub` already mints it there through `githubConversationKey`, so this
|
|
112
|
+
* adds no second derivation. A turn with no conversation anchor (a supervisor seed, a degraded
|
|
113
|
+
* binding) reports none, and the caller refuses loudly rather than reading the repo's newest
|
|
114
|
+
* issue.
|
|
115
|
+
*
|
|
116
|
+
* The THREAD ROOT is present only on a REVIEW-THREAD turn โ an inline comment on a PR's diff.
|
|
117
|
+
* An issue comment and a PR TIMELINE comment are at the top level of their conversation, so they
|
|
118
|
+
* report no thread root and `read_thread` refuses them honestly. That is the same answer a
|
|
119
|
+
* base-channel Discord turn gets, and for the same reason.
|
|
120
|
+
*/
|
|
121
|
+
wakeAddress(ctx: BindableContext): WakeReadAddress | undefined;
|
|
122
|
+
/**
|
|
123
|
+
* The issue-or-PR conversation: its opening post, then its newest timeline comments. `channel` is
|
|
124
|
+
* the container id โ see {@link parseGithubContainer}.
|
|
125
|
+
*/
|
|
126
|
+
readChannel(ctx: BindableContext, channel: string, limit?: number, fetchFn?: PrFetchFn, walk?: ReadWalkSeam): Promise<AdapterReadFetch>;
|
|
127
|
+
/**
|
|
128
|
+
* One REVIEW THREAD on a PR โ the inline discussion `threadTs` roots. An issue has no review
|
|
129
|
+
* threads, so a read there comes back empty and the neutral render says so; it never invents a
|
|
130
|
+
* thread out of the timeline.
|
|
131
|
+
*/
|
|
132
|
+
readThread(ctx: BindableContext, channel: string, threadTs: string, limit?: number, fetchFn?: PrFetchFn, walk?: ReadWalkSeam): Promise<AdapterReadFetch>;
|
|
133
|
+
};
|
|
134
|
+
export {};
|
|
135
|
+
//# sourceMappingURL=github-read.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"github-read.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-read.ts"],"names":[],"mappings":"AAmEA,OAAO,EAQL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,4CAA4C,CAAC;AAapD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAE1C;;;;;;;GAOG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAChD;AAED;iGACiG;AACjG,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,GAAE,YAAiB,GAAG,WAAW,CAOlF;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,iGAAiG;IACjG,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,8BAA8B;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAWnF;AAED;+CAC+C;AAC/C;;4FAE4F;AAC5F,eAAO,MAAM,mBAAmB,EAAkC,SAAS,CAAC;AAE5E;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,CAInI;AAED,kFAAkF;AAClF,UAAU,aAAa;IACrB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AASD,UAAU,UAAU;IAClB,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,aAAa,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACzC;AAqJD;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAGjG;AAED,eAAO,MAAM,iBAAiB;IAC5B,QAAQ,EAAE,QAAQ;IAClB;;;;;;;;;;;;;OAaG;IACH,WAAW,MAAM,eAAe,GAAG,eAAe,GAAG,SAAS;IAM9D;;;OAGG;IACG,WAAW,MACV,eAAe,WACX,MAAM,UACP,MAAM,YACL,SAAS,SACZ,YAAY,GACjB,OAAO,CAAC,gBAAgB,CAAC;IAqB5B;;;;OAIG;IACG,UAAU,MACT,eAAe,WACX,MAAM,YACL,MAAM,UACR,MAAM,YACL,SAAS,SACZ,YAAY,GACjB,OAAO,CAAC,gBAAgB,CAAC;CAmB7B,CAAC"}
|