@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.
Files changed (111) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +21 -0
  3. package/README.md +50 -2
  4. package/agent/channels/github-catch-up.ts +134 -0
  5. package/agent/channels/github-engagement.ts +381 -0
  6. package/agent/channels/github-identity.ts +376 -0
  7. package/agent/channels/github-installations.ts +152 -0
  8. package/agent/channels/github-message-ops.ts +62 -0
  9. package/agent/channels/github-origin-codec.ts +42 -0
  10. package/agent/channels/github-post-arm.ts +422 -0
  11. package/agent/channels/github-reaction-feedback.ts +489 -0
  12. package/agent/channels/github-reaction-scope.ts +312 -0
  13. package/agent/channels/github-read.ts +471 -0
  14. package/agent/channels/github-stop.ts +175 -0
  15. package/agent/channels/github-supervisor.ts +645 -0
  16. package/agent/channels/github-webhook-verifier.ts +56 -0
  17. package/agent/channels/github.ts +1458 -0
  18. package/agent/contributions.ts +31 -0
  19. package/agent/docs-generated.ts +15 -0
  20. package/agent/memory/memory.ts +265 -0
  21. package/agent/ops.ts +17 -0
  22. package/agent/provider-events.ts +114 -0
  23. package/agent/register.ts +130 -0
  24. package/agent/surface-parity.ts +116 -0
  25. package/dist/agent/channels/github-catch-up.d.ts +29 -0
  26. package/dist/agent/channels/github-catch-up.d.ts.map +1 -0
  27. package/dist/agent/channels/github-catch-up.js +105 -0
  28. package/dist/agent/channels/github-catch-up.js.map +1 -0
  29. package/dist/agent/channels/github-engagement.d.ts +223 -0
  30. package/dist/agent/channels/github-engagement.d.ts.map +1 -0
  31. package/dist/agent/channels/github-engagement.js +317 -0
  32. package/dist/agent/channels/github-engagement.js.map +1 -0
  33. package/dist/agent/channels/github-identity.d.ts +164 -0
  34. package/dist/agent/channels/github-identity.d.ts.map +1 -0
  35. package/dist/agent/channels/github-identity.js +295 -0
  36. package/dist/agent/channels/github-identity.js.map +1 -0
  37. package/dist/agent/channels/github-installations.d.ts +49 -0
  38. package/dist/agent/channels/github-installations.d.ts.map +1 -0
  39. package/dist/agent/channels/github-installations.js +114 -0
  40. package/dist/agent/channels/github-installations.js.map +1 -0
  41. package/dist/agent/channels/github-message-ops.d.ts +3 -0
  42. package/dist/agent/channels/github-message-ops.d.ts.map +1 -0
  43. package/dist/agent/channels/github-message-ops.js +58 -0
  44. package/dist/agent/channels/github-message-ops.js.map +1 -0
  45. package/dist/agent/channels/github-origin-codec.d.ts +7 -0
  46. package/dist/agent/channels/github-origin-codec.d.ts.map +1 -0
  47. package/dist/agent/channels/github-origin-codec.js +36 -0
  48. package/dist/agent/channels/github-origin-codec.js.map +1 -0
  49. package/dist/agent/channels/github-post-arm.d.ts +54 -0
  50. package/dist/agent/channels/github-post-arm.d.ts.map +1 -0
  51. package/dist/agent/channels/github-post-arm.js +339 -0
  52. package/dist/agent/channels/github-post-arm.js.map +1 -0
  53. package/dist/agent/channels/github-reaction-feedback.d.ts +97 -0
  54. package/dist/agent/channels/github-reaction-feedback.d.ts.map +1 -0
  55. package/dist/agent/channels/github-reaction-feedback.js +380 -0
  56. package/dist/agent/channels/github-reaction-feedback.js.map +1 -0
  57. package/dist/agent/channels/github-reaction-scope.d.ts +102 -0
  58. package/dist/agent/channels/github-reaction-scope.d.ts.map +1 -0
  59. package/dist/agent/channels/github-reaction-scope.js +296 -0
  60. package/dist/agent/channels/github-reaction-scope.js.map +1 -0
  61. package/dist/agent/channels/github-read.d.ts +135 -0
  62. package/dist/agent/channels/github-read.d.ts.map +1 -0
  63. package/dist/agent/channels/github-read.js +371 -0
  64. package/dist/agent/channels/github-read.js.map +1 -0
  65. package/dist/agent/channels/github-stop.d.ts +52 -0
  66. package/dist/agent/channels/github-stop.d.ts.map +1 -0
  67. package/dist/agent/channels/github-stop.js +145 -0
  68. package/dist/agent/channels/github-stop.js.map +1 -0
  69. package/dist/agent/channels/github-supervisor.d.ts +308 -0
  70. package/dist/agent/channels/github-supervisor.d.ts.map +1 -0
  71. package/dist/agent/channels/github-supervisor.js +339 -0
  72. package/dist/agent/channels/github-supervisor.js.map +1 -0
  73. package/dist/agent/channels/github-webhook-verifier.d.ts +5 -0
  74. package/dist/agent/channels/github-webhook-verifier.d.ts.map +1 -0
  75. package/dist/agent/channels/github-webhook-verifier.js +55 -0
  76. package/dist/agent/channels/github-webhook-verifier.js.map +1 -0
  77. package/dist/agent/channels/github.d.ts +139 -0
  78. package/dist/agent/channels/github.d.ts.map +1 -0
  79. package/dist/agent/channels/github.js +1232 -0
  80. package/dist/agent/channels/github.js.map +1 -0
  81. package/dist/agent/contributions.d.ts +12 -0
  82. package/dist/agent/contributions.d.ts.map +1 -0
  83. package/dist/agent/contributions.js +31 -0
  84. package/dist/agent/contributions.js.map +1 -0
  85. package/dist/agent/docs-generated.d.ts +3 -0
  86. package/dist/agent/docs-generated.d.ts.map +1 -0
  87. package/dist/agent/docs-generated.js +13 -0
  88. package/dist/agent/docs-generated.js.map +1 -0
  89. package/dist/agent/memory/memory.d.ts +54 -0
  90. package/dist/agent/memory/memory.d.ts.map +1 -0
  91. package/dist/agent/memory/memory.js +224 -0
  92. package/dist/agent/memory/memory.js.map +1 -0
  93. package/dist/agent/ops.d.ts +3 -0
  94. package/dist/agent/ops.d.ts.map +1 -0
  95. package/dist/agent/ops.js +16 -0
  96. package/dist/agent/ops.js.map +1 -0
  97. package/dist/agent/provider-events.d.ts +3 -0
  98. package/dist/agent/provider-events.d.ts.map +1 -0
  99. package/dist/agent/provider-events.js +112 -0
  100. package/dist/agent/provider-events.js.map +1 -0
  101. package/dist/agent/register.d.ts +4 -0
  102. package/dist/agent/register.d.ts.map +1 -0
  103. package/dist/agent/register.js +116 -0
  104. package/dist/agent/register.js.map +1 -0
  105. package/dist/agent/surface-parity.d.ts +3 -0
  106. package/dist/agent/surface-parity.d.ts.map +1 -0
  107. package/dist/agent/surface-parity.js +110 -0
  108. package/dist/agent/surface-parity.js.map +1 -0
  109. package/package.json +156 -4
  110. package/tsconfig.build.json +22 -0
  111. package/tsconfig.json +15 -0
@@ -0,0 +1,54 @@
1
+ import { type RateLimitRetryOptions, type PostArm, type UnifiedWakeOrigin } from "@open-everywhere/core/agent/adapter-api.js";
2
+ type GitHubOrigin = Extract<UnifiedWakeOrigin, {
3
+ platform: "github";
4
+ }>;
5
+ /** Test-only reset — drops every held installation token. */
6
+ export declare function _resetGitHubInstallationTokenCache(): void;
7
+ /** Exported for the reaction-feedback poll (channels/github-reaction-feedback.ts, OPE-1502), which
8
+ * reads under the same App identity every other GitHub call in this bundle uses. */
9
+ export declare function installationTokenFor(origin: GitHubOrigin): Promise<string>;
10
+ /** GitHub's REST base. */
11
+ export declare const GITHUB_API_BASE = "https://api.github.com";
12
+ /** The subset of the global `fetch` this seam uses — injectable so a test drives it without GitHub. */
13
+ export type GitHubFetchLike = (input: string, init: RequestInit) => Promise<Response>;
14
+ /** Transport and retry seams. A test injects them; live code passes none and takes the shared
15
+ * defaults from `channels/rate-limit-retry.ts`. */
16
+ export interface GitHubWriteDeps {
17
+ readonly fetch?: GitHubFetchLike;
18
+ readonly retry?: Pick<RateLimitRetryOptions, "maxAttempts" | "capMs" | "fallbackMs" | "totalBudgetMs" | "sleep" | "now">;
19
+ }
20
+ /**
21
+ * Issue one GitHub REST write under an installation token, with the 429 backoff applied.
22
+ *
23
+ * `operation` is the vendor route name (`issues.createComment`), NOT the resolved path: it is what
24
+ * `RateLimitExhaustedError` reports, and a path would put an owner, a repo and an issue number into
25
+ * a failure that promises every field on it is safe to log.
26
+ */
27
+ export declare function githubWrite(input: {
28
+ readonly operation: string;
29
+ readonly method: "POST" | "PATCH";
30
+ readonly path: string;
31
+ readonly token: string;
32
+ readonly body: unknown;
33
+ }, deps?: GitHubWriteDeps): Promise<Response>;
34
+ /** THE ONE GitHub POST: an issue/PR-timeline comment via the GitHub REST API under a SHORT-LIVED
35
+ * installation token minted host-side from the App credentials. Both spines call it — the UNIFIED
36
+ * window (with a frozen wake origin) and the MID-TURN `reply` tool (with a binding-resolved target,
37
+ * OPE-811). The installation id rides the origin/target when the webhook carried one, with the scm
38
+ * config's owner map as the fallback — a missing/mismatched installation fails VISIBLE, never a
39
+ * comment into the wrong repo. */
40
+ /** Exported for github-message-ops.ts (OPE-899's `SurfaceMessageOps.postText`) — the SAME comment
41
+ * POST, never a second copy of the mint/fetch mechanics. */
42
+ export declare function liveGitHubPost(origin: GitHubOrigin, text: string): Promise<string>;
43
+ /** THE ONE GitHub EDIT (OPE-862): rewrite an existing issue/PR-timeline comment's body. `channelId`
44
+ * is the SAME composite "owner/repo" string {@link replyDedupeLocus}/{@link unifiedDedupeLocus} in
45
+ * reply-post.ts/unified-reply.ts already use for a GitHub locus — the arm parses it back apart
46
+ * rather than the EditLocus contract growing a second field, mirroring how those two dedupe keys
47
+ * are already built. The installation id rides no locus, so this resolves through the SAME
48
+ * owner→installation fallback map `liveGitHubPost` falls back to when a target carries none. */
49
+ /** Exported for github-message-ops.ts (OPE-899's `SurfaceMessageOps.editMessage`) — same reason as
50
+ * {@link liveGitHubPost}: one PATCH implementation, never two. */
51
+ export declare function liveGitHubEditComment(channelId: string, commentId: string, text: string): Promise<void>;
52
+ export declare const githubPostArm: PostArm;
53
+ export {};
54
+ //# sourceMappingURL=github-post-arm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-post-arm.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-post-arm.ts"],"names":[],"mappings":"AAiBA,OAAO,EAUL,KAAK,qBAAqB,EAE1B,KAAK,OAAO,EAIZ,KAAK,iBAAiB,EAEvB,MAAM,4CAA4C,CAAC;AAGpD,KAAK,YAAY,GAAG,OAAO,CAAC,iBAAiB,EAAE;IAAE,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAC;AAiEvE,6DAA6D;AAC7D,wBAAgB,kCAAkC,IAAI,IAAI,CAEzD;AAaD;qFACqF;AACrF,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAmBhF;AA0DD,0BAA0B;AAC1B,eAAO,MAAM,eAAe,2BAA2B,CAAC;AAExD,uGAAuG;AACvG,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AAEtF;oDACoD;AACpD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,qBAAqB,EAAE,aAAa,GAAG,OAAO,GAAG,YAAY,GAAG,eAAe,GAAG,OAAO,GAAG,KAAK,CAAC,CAAC;CAC1H;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE;IACL,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,EACD,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,QAAQ,CAAC,CAiBnB;AAED;;;;;mCAKmC;AACnC;6DAC6D;AAC7D,wBAAsB,cAAc,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CA+BxF;AAiBD;;;;;iGAKiG;AACjG;mEACmE;AACnE,wBAAsB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB7G;AAmDD,eAAO,MAAM,aAAa,EAAE,OA4C3B,CAAC"}
@@ -0,0 +1,339 @@
1
+ // github-post-arm — GitHub's POST arm for the post-arms registry (OPE-114 stage 1c). Absorbs, VERBATIM,
2
+ // GitHub's arms out of unified-reply.ts (liveGitHubPost + its installation-token minting/caching) and
3
+ // unified-redrive.ts (the github describe/container cases). The unified session runs on the synthetic
4
+ // `unified` channel, so this arm mints its OWN repo-scoped CONVERSATION-WRITE installation token
5
+ // (scm/mint.ts `CONVERSATION_WRITE_PERMISSIONS`) rather than using an eve github channel handle.
6
+ //
7
+ // 🔴 THE MID-TURN ARM RIDES THE SAME TRANSPORT (OPE-811). This file used to state that GitHub has no
8
+ // mid-turn reply arm "because eve exports no public proactive comment helper". That reasoned from the
9
+ // EVE ENTRY, not from what this deployment can do: `liveGitHubPost` below has posted every unified
10
+ // GitHub reply for weeks, and it needs no eve channel handle — it mints its own token and calls the
11
+ // REST comments endpoint. So the honest availability bar ("never expose a `reply` that would throw")
12
+ // is met, and `resolveTarget` / `dispatchReply` now sit here over the SAME function the unified spine
13
+ // uses. A GitHub supervisor turn therefore delivers through the same `reply` tool as a Slack seat.
14
+ // The SCM plane (mint/config) STAYS in core by doctrine (the single SCM implementation) — this arm
15
+ // reaches its App-token minting + config through the adapter-api barrel, alongside the post-arm /
16
+ // wake-origin / unified-reply contracts. See docs/extending/provider-bundles.md.
17
+ import { parsePrivateKey, signAppJwt, mintInstallationToken, CONVERSATION_WRITE_PERMISSIONS, loadGitHubAppConfig, resolveGitHubInstallationId, warmCredential, markNotApplied, withRetryAfter, } from "@open-everywhere/core/agent/adapter-api.js";
18
+ import { markGitHubConversationPosted } from "./github-reaction-scope.js";
19
+ /**
20
+ * One minted token per (installation, repo), held warm by the SHARED seam (core
21
+ * credentials/warm.ts, OPE-134). Installation tokens live 1h; re-minting per reply would be correct
22
+ * but wasteful. Keyed by installation id AND repo, because the token is down-scoped to
23
+ * `[origin.repo]` (buildMintBody): a domain spanning two repos under ONE installation must NOT serve
24
+ * repo-A's repo-scoped token for a repo-B reply (GitHub 404s). `re-mint` posture with the seam's
25
+ * 5-minute default margin — exactly the rhythm this arm ran before, now with per-key single-flight
26
+ * and, crucially, a LOUD `github.token_mint_failed` audit row on failure (previously a mint failure
27
+ * left nothing durable behind at all: the reply just threw).
28
+ */
29
+ const installationToken = warmCredential({
30
+ name: "github.installation-token",
31
+ platform: "github",
32
+ auditAction: "github.token_mint_failed",
33
+ refresh: "re-mint",
34
+ fallbackTtlSeconds: 3600,
35
+ // Non-secret by construction: an installation id and a repo name, the same key the local cache used.
36
+ key: (ctx) => `${ctx.installationId} ${ctx.repo}`,
37
+ mint: async (ctx) => {
38
+ const jwt = await signAppJwt(parsePrivateKey(ctx.privateKeyPem), ctx.appId, Math.floor(Date.now() / 1000));
39
+ // Repo-scoped CONVERSATION-WRITE set (scm/mint.ts): `issues:write` + `pull_requests:write` +
40
+ // `metadata:read`. NEVER `contents` — this arm must not be able to touch code.
41
+ //
42
+ // 🔴 BOTH WRITE FAMILIES, BECAUSE A CONVERSATION IS AN ISSUE OR A PR (OPE-1020). This file used
43
+ // to mint `{issues: "write"}` alone and call it "the narrowest permission that posts an
44
+ // issue/PR-timeline comment". That was WRONG for the PR half, and it made the agent MUTE on
45
+ // every GitHub PR thread: the wake fired, the turn ran, the spend was incurred, and the reply
46
+ // POST came back 403. Measured on deploy #118 — two plain issues took 201 and one PR took 403
47
+ // twice, same repo, same session, same endpoint. GitHub routes `/issues/{n}/comments` by the
48
+ // RESOURCE the number names, and lists that endpoint under BOTH permission families.
49
+ const minted = await mintInstallationToken({ jwt, installationId: ctx.installationId, repoNames: [ctx.repo], permissions: CONVERSATION_WRITE_PERMISSIONS });
50
+ // OPE-914: a 201 mint is NOT proof the token covers the repo it was asked for — GitHub can
51
+ // return a token whose `repositories` list omits the requested name (a stale/mismatched
52
+ // installation-repo mapping) without erroring the mint call itself, and the ONLY symptom
53
+ // downstream is a 403 on the comment POST that looks identical to a permissions gap. Check the
54
+ // grant here, at the one place that still holds the expected full_name, so a repo the
55
+ // installation does not actually cover fails LOUD (the warmCredential seam's own
56
+ // `github.token_mint_failed` audit row) instead of a confusing later 403.
57
+ const expected = `${ctx.owner}/${ctx.repo}`;
58
+ if (!minted.repositories.includes(expected)) {
59
+ throw new Error(`installation token for "${ctx.installationId}" does not cover "${expected}" — granted: [${minted.repositories.join(", ") || "none"}].`);
60
+ }
61
+ // GitHub states expiry as an ISO-8601 string. An unparseable one falls back to the declared
62
+ // 1h TTL rather than becoming a NaN deadline (which would compare false forever and re-mint on
63
+ // every single reply).
64
+ const parsed = Date.parse(minted.expiresAt);
65
+ return { value: minted.token, expiresAtMs: Number.isFinite(parsed) ? parsed : undefined };
66
+ },
67
+ });
68
+ /** Test-only reset — drops every held installation token. */
69
+ export function _resetGitHubInstallationTokenCache() {
70
+ installationToken.reset();
71
+ }
72
+ /** GitHub App credentials for the unified reply arm: the scm plane's env pair preferred, the eve
73
+ * channel's pair as fallback (same App in a normal deploy — two spellings, one identity). */
74
+ function githubAppCreds() {
75
+ const scm = loadGitHubAppConfig();
76
+ if (scm)
77
+ return { appId: scm.clientId ?? scm.appId, privateKeyPem: scm.privateKeyPem, installations: scm.installations };
78
+ const appId = process.env.GITHUB_APP_ID;
79
+ const privateKeyPem = process.env.GITHUB_APP_PRIVATE_KEY;
80
+ if (!appId || !privateKeyPem)
81
+ return null;
82
+ return { appId, privateKeyPem, installations: {} };
83
+ }
84
+ /** Exported for the reaction-feedback poll (channels/github-reaction-feedback.ts, OPE-1502), which
85
+ * reads under the same App identity every other GitHub call in this bundle uses. */
86
+ export async function installationTokenFor(origin) {
87
+ const creds = githubAppCreds();
88
+ if (!creds) {
89
+ // DEFINITIVE (OPE-825): no App credentials ⇒ no request ever left this host.
90
+ throw markNotApplied(new Error("reply couldn't post to GitHub — no App credentials (set OE_GITHUB_APP_* or GITHUB_APP_ID/GITHUB_APP_PRIVATE_KEY)."));
91
+ }
92
+ // The wake's own (webhook-supplied) installation id first; then the SCM plane's composed map —
93
+ // OE_GITHUB_INSTALLATIONS authoritative, `installation`-webhook-discovered installs augmenting it
94
+ // (scm/installations.ts), so an org that self-installed resolves without an env edit.
95
+ const installationId = origin.installationId !== undefined ? String(origin.installationId) : await resolveGitHubInstallationId(origin.owner, creds.installations);
96
+ if (!installationId) {
97
+ // DEFINITIVE (OPE-825): unresolved before any comment request is built — nothing left this host.
98
+ throw markNotApplied(new Error(`reply couldn't post to GitHub — no installation id for owner "${origin.owner}" (the wake carried none, OE_GITHUB_INSTALLATIONS has no entry, and no installation webhook has registered one).`));
99
+ }
100
+ // Key by (installation, repo): the minted token is scoped to exactly [origin.repo], so a sibling
101
+ // repo under the same installation needs its OWN token.
102
+ return installationToken.get({ appId: creds.appId, privateKeyPem: creds.privateKeyPem, installationId, owner: origin.owner, repo: origin.repo });
103
+ }
104
+ // --- THE GITHUB WRITE SEAM (OPE-1235) ----------------------------------------------------------
105
+ //
106
+ // THE ONE PLACE a GitHub CHANNEL write leaves this package. Before OPE-1235 the three writes below —
107
+ // the comment POST, the comment PATCH, the reaction POST — each called the global `fetch` with its
108
+ // own copy of the same four headers and no 429 handling at all. GitHub answers a rate-limited write
109
+ // with HTTP 429 and a `Retry-After` header, so every one of those writes failed on the FIRST
110
+ // refusal: the reply threw `reply couldn't post to GitHub — 429 …` and the answer was lost.
111
+ //
112
+ // 🔴 "THIS PACKAGE" IS THE HONEST SCOPE, AND THE COUNTER-EXAMPLE IS ONE LINE ABOVE EVERY CALL.
113
+ // `installationTokenFor` mints the token through core's `mintInstallationToken` (scm/mint.ts), which
114
+ // is a bare `fetch` POST to `api.github.com/app/installations/{id}/access_tokens` with NO backoff. It
115
+ // runs immediately before every write below, against the same vendor limiter. A 429 there loses the
116
+ // reply exactly as a 429 here used to — one call earlier. `scm/mint.ts` is a PROTECTED PATH
117
+ // (`scripts/upgrade-fence/manifest.ts`'s `github-permission-sets` rule), so the merge fence refuses
118
+ // EVERY diff that touches it, whoever authored it (OPE-1315). Closing this gap needs the
119
+ // orchestrator's explicit review-and-override.
120
+ // Core's OTHER SCM write plane files (issue-actions.ts, pr.ts, contents.ts, refs.ts) DO carry the
121
+ // backoff now, via `withRetryAfterLoose` (OPE-1253) — `docs/extending/provider-rate-limits.md`
122
+ // records the mint as the one remaining gap. Do not read the paragraph above as "the mint is
123
+ // covered".
124
+ //
125
+ // THE 403 SPELLING IS COVERED HERE NOW, AND ONLY HERE (OPE-1256). GitHub documents a rate-limit
126
+ // refusal as "403 Forbidden or 429 Too Many Requests", often with `Retry-After` on either. The shared
127
+ // primitive still branches on 429 for every other provider; this seam sets the per-seam opt-in
128
+ // `retryForbiddenWithRetryAfter`, which admits a 403 ONLY when it carries a readable `Retry-After`. A
129
+ // BARE 403 stays a permission refusal on every seam, this one included. GitHub's live 403 rate-limit
130
+ // response is still UNMEASURED — `isRateLimitedForbidden` in `channels/rate-limit-retry.ts` carries
131
+ // that residual and the argument for why the discriminator can under-fire but cannot misfire.
132
+ // STILL OUT OF SCOPE: GitHub's OTHER paths do not set the flag — core's SCM write plane (pr.ts,
133
+ // issue-actions.ts, contents.ts, refs.ts) and `github-read.ts` carry the 429 backoff only.
134
+ //
135
+ // One funnel fixes that once. It also removes the header triplication, which is the reason a fourth
136
+ // write would have grown a fourth copy with a fourth chance to omit something.
137
+ //
138
+ // WHAT IT DOES, and only this:
139
+ // • Builds the request — base URL, method, the four fixed headers, a JSON body.
140
+ // • Honours a 429's `Retry-After` through `channels/rate-limit-retry.js`, bounded in attempts, in
141
+ // one wait, and in total wait.
142
+ // • Ends an outlasted 429 in the NAMED `RateLimitExhaustedError`, which states the surface and the
143
+ // operation and carries no token, no body and no personal data.
144
+ // • Returns EVERY other response untouched — a 201, a 404, a 5xx. The callers below keep reading
145
+ // `res.ok` and `res.status` exactly as they did, so the OPE-825 status-on-the-error posture is
146
+ // unchanged. A transport throw still propagates unretried: GitHub may already have applied that
147
+ // write, and repeating it could double a comment.
148
+ //
149
+ // 🔴 NOTHING ELSE IN THIS PACKAGE'S `agent/` MAY CALL THE GLOBAL `fetch` DIRECTLY.
150
+ // `github-write-seam-test.ts` scans this package and reds when a bare `fetch(` appears in it. That
151
+ // scan reaches THIS PACKAGE ONLY: a call site that imports a core SCM helper bypasses the backoff
152
+ // with the scan still green, which is why the paragraph above names those helpers rather than
153
+ // trusting the gate to catch them.
154
+ //
155
+ // `github-read.ts` holds its own read-only transport (`PrFetchFn`) — a SEPARATE seam from this one,
156
+ // tracked as its own § 4 entry in the same scan, and it now carries the backoff too (OPE-1255, via
157
+ // `withRetryAfterLoose`). See `channels/rate-limit-retry.ts`'s doc comment on `withRetryAfterLoose`
158
+ // for why the loose adapter exists rather than `withRetryAfter` directly.
159
+ /** GitHub's REST base. */
160
+ export const GITHUB_API_BASE = "https://api.github.com";
161
+ /**
162
+ * Issue one GitHub REST write under an installation token, with the 429 backoff applied.
163
+ *
164
+ * `operation` is the vendor route name (`issues.createComment`), NOT the resolved path: it is what
165
+ * `RateLimitExhaustedError` reports, and a path would put an owner, a repo and an issue number into
166
+ * a failure that promises every field on it is safe to log.
167
+ */
168
+ export async function githubWrite(input, deps = {}) {
169
+ const send = () => (deps.fetch ?? globalThis.fetch)(`${GITHUB_API_BASE}${input.path}`, {
170
+ method: input.method,
171
+ headers: {
172
+ authorization: `Bearer ${input.token}`,
173
+ accept: "application/vnd.github+json",
174
+ "x-github-api-version": "2022-11-28",
175
+ "content-type": "application/json",
176
+ },
177
+ body: JSON.stringify(input.body),
178
+ });
179
+ // `retryForbiddenWithRetryAfter` is the PER-SEAM opt-in of OPE-1256, and this seam is the only
180
+ // place in the repo that sets it. GitHub is the one vendor that documents 403 as a rate-limit
181
+ // spelling; the primitive still refuses to retry a BARE 403 here or anywhere. The unverified half —
182
+ // GitHub's live 403 shape — and why it cannot misfire are written on `isRateLimitedForbidden`.
183
+ return withRetryAfter(send, { ...deps.retry, surface: "github", operation: input.operation, retryForbiddenWithRetryAfter: true });
184
+ }
185
+ /** THE ONE GitHub POST: an issue/PR-timeline comment via the GitHub REST API under a SHORT-LIVED
186
+ * installation token minted host-side from the App credentials. Both spines call it — the UNIFIED
187
+ * window (with a frozen wake origin) and the MID-TURN `reply` tool (with a binding-resolved target,
188
+ * OPE-811). The installation id rides the origin/target when the webhook carried one, with the scm
189
+ * config's owner map as the fallback — a missing/mismatched installation fails VISIBLE, never a
190
+ * comment into the wrong repo. */
191
+ /** Exported for github-message-ops.ts (OPE-899's `SurfaceMessageOps.postText`) — the SAME comment
192
+ * POST, never a second copy of the mint/fetch mechanics. */
193
+ export async function liveGitHubPost(origin, text) {
194
+ const token = await installationTokenFor(origin);
195
+ const res = await githubWrite({
196
+ operation: "issues.createComment",
197
+ method: "POST",
198
+ path: `/repos/${origin.owner}/${origin.repo}/issues/${origin.issueNumber}/comments`,
199
+ token,
200
+ body: { body: text },
201
+ });
202
+ if (!res.ok) {
203
+ // Token-free error (the response body can't contain our credential; keep it short anyway). The
204
+ // numeric HTTP status rides on the error (OPE-825) so the classifier can read it directly.
205
+ throw Object.assign(new Error(`reply couldn't post to GitHub — ${res.status} on ${origin.owner}/${origin.repo}#${origin.issueNumber}`), { status: res.status });
206
+ }
207
+ const json = (await res.json().catch(() => ({})));
208
+ // 🔴 ONE HALF OF THE REACTION POLL'S SCOPE RECORD (OPE-1540). This is the one place a GitHub
209
+ // comment leaves THIS BUNDLE, so it is where this bundle learns the agent is now in a conversation.
210
+ // It is NOT every comment the agent posts: eve's own poster still emits the `input.requested`
211
+ // comment, which this bundle does not override and this code never sees. (The native @mention
212
+ // leg's TERMINAL completion does claim here now — it posts through this function since OPE-1681.)
213
+ // The poll's gate therefore reads the ENGAGEMENT row as well — see
214
+ // channels/github-reaction-scope.ts, which states why one record cannot cover both posters. AFTER
215
+ // the post, and never a reason to fail one: the call never throws.
216
+ //
217
+ // 🔴 AND NEVER A REASON TO DELAY ONE EITHER. The comment has already landed when this runs, so the
218
+ // only thing this await can cost is the reply tool's RETURN — on the turn path, where an unbounded
219
+ // store call has no business. `markGitHubConversationPosted` owns that ceiling
220
+ // (`GITHUB_POSTED_MARK_TIMEOUT_MS`) rather than this call site, because the best-effort contract
221
+ // that says a lost mark costs a later poll and never a reply belongs with the mark.
222
+ await markGitHubConversationPosted(origin.owner, origin.repo, origin.issueNumber, new Date().toISOString());
223
+ return json.id !== undefined ? String(json.id) : "unknown";
224
+ }
225
+ /** The {@link GitHubOrigin} shape {@link liveGitHubPost} posts to, built from a mid-turn
226
+ * {@link ReplyTarget}. The two carry the same four facts under the same names, so this is a widening
227
+ * rather than a translation — stated as a function so a future divergence has one place to live. */
228
+ function originForTarget(target) {
229
+ return {
230
+ platform: "github",
231
+ owner: target.owner,
232
+ repo: target.repo,
233
+ issueNumber: target.issueNumber,
234
+ ...(target.installationId !== undefined ? { installationId: target.installationId } : {}),
235
+ };
236
+ }
237
+ /** THE ONE GitHub EDIT (OPE-862): rewrite an existing issue/PR-timeline comment's body. `channelId`
238
+ * is the SAME composite "owner/repo" string {@link replyDedupeLocus}/{@link unifiedDedupeLocus} in
239
+ * reply-post.ts/unified-reply.ts already use for a GitHub locus — the arm parses it back apart
240
+ * rather than the EditLocus contract growing a second field, mirroring how those two dedupe keys
241
+ * are already built. The installation id rides no locus, so this resolves through the SAME
242
+ * owner→installation fallback map `liveGitHubPost` falls back to when a target carries none. */
243
+ /** Exported for github-message-ops.ts (OPE-899's `SurfaceMessageOps.editMessage`) — same reason as
244
+ * {@link liveGitHubPost}: one PATCH implementation, never two. */
245
+ export async function liveGitHubEditComment(channelId, commentId, text) {
246
+ const [owner, repo] = channelId.split("/");
247
+ if (!owner || !repo) {
248
+ throw markNotApplied(new Error(`edit_reply couldn't parse the GitHub locus "${channelId}" — expected "owner/repo".`));
249
+ }
250
+ const token = await installationTokenFor({ platform: "github", owner, repo, issueNumber: 0 });
251
+ const res = await githubWrite({
252
+ operation: "issues.updateComment",
253
+ method: "PATCH",
254
+ path: `/repos/${owner}/${repo}/issues/comments/${commentId}`,
255
+ token,
256
+ body: { body: text },
257
+ });
258
+ if (!res.ok) {
259
+ throw Object.assign(new Error(`edit_reply couldn't edit the GitHub comment — ${res.status} on ${owner}/${repo} comment ${commentId}`), { status: res.status });
260
+ }
261
+ }
262
+ const GITHUB_REACTION_MAP = {
263
+ "+1": "+1",
264
+ thumbsup: "+1",
265
+ "-1": "-1",
266
+ thumbsdown: "-1",
267
+ laugh: "laugh",
268
+ laughing: "laugh",
269
+ joy: "laugh",
270
+ confused: "confused",
271
+ heart: "heart",
272
+ hooray: "hooray",
273
+ tada: "hooray",
274
+ rocket: "rocket",
275
+ eyes: "eyes",
276
+ };
277
+ async function liveGitHubReact(origin, emoji) {
278
+ const content = GITHUB_REACTION_MAP[emoji.toLowerCase()];
279
+ if (!content) {
280
+ // DEFINITIVE (OPE-825): decided before any byte left this host — the model's name has no mapping.
281
+ throw markNotApplied(new Error(`react couldn't post to GitHub — "${emoji}" has no GitHub reaction equivalent; GitHub only supports: ${[...new Set(Object.values(GITHUB_REACTION_MAP))].join(", ")}.`));
282
+ }
283
+ const token = await installationTokenFor(origin);
284
+ const res = await githubWrite({
285
+ operation: "issues.createReaction",
286
+ method: "POST",
287
+ path: `/repos/${origin.owner}/${origin.repo}/issues/${origin.issueNumber}/reactions`,
288
+ token,
289
+ body: { content },
290
+ });
291
+ if (!res.ok) {
292
+ throw Object.assign(new Error(`react couldn't post to GitHub — ${res.status} on ${origin.owner}/${origin.repo}#${origin.issueNumber}`), { status: res.status });
293
+ }
294
+ }
295
+ export const githubPostArm = {
296
+ platform: "github",
297
+ // MID-TURN (OPE-811): owner + repo + the issue/PR anchor, all off the binding's post-back target
298
+ // (github-identity.ts `bindGitHub`), never a tool argument. A PR timeline comment anchors on the
299
+ // SAME `/issues/{n}/comments` endpoint, so either anchor number resolves the one target shape.
300
+ resolveTarget: (bag) => {
301
+ const owner = bag.str("owner");
302
+ const repo = bag.str("repo");
303
+ const issueNumber = bag.num("issueNumber") ?? bag.num("pullRequestNumber");
304
+ if (!owner || !repo || issueNumber === undefined || !Number.isFinite(issueNumber) || issueNumber <= 0)
305
+ return null;
306
+ const installationId = bag.num("installationId");
307
+ return { platform: "github", owner, repo, issueNumber, ...(installationId !== undefined ? { installationId } : {}) };
308
+ },
309
+ dispatchReply: (target, text) => liveGitHubPost(originForTarget(target), text),
310
+ dispatchUnified: (origin, text, deps) => (deps.postGitHub ?? liveGitHubPost)(origin, text),
311
+ // EDIT (OPE-862): PATCH the issue/PR-timeline comment `liveGitHubPost` already returns a real id
312
+ // for — no sentinel-id prerequisite here, unlike Linear's ambient route.
313
+ editMessage: (locus, messageId, text) => liveGitHubEditComment(locus.channelId, messageId, text),
314
+ // REACT (OPE-862): the same conversation anchor `reply` posts into — GitHub has no message-level
315
+ // react target the binding carries, so this resolves owner/repo/issueNumber independently of
316
+ // resolveTarget (see ReactTarget's doc in post-arms.ts).
317
+ resolveReactTarget: (bag) => {
318
+ const owner = bag.str("owner");
319
+ const repo = bag.str("repo");
320
+ const issueNumber = bag.num("issueNumber") ?? bag.num("pullRequestNumber");
321
+ if (!owner || !repo || issueNumber === undefined || !Number.isFinite(issueNumber) || issueNumber <= 0)
322
+ return null;
323
+ const installationId = bag.num("installationId");
324
+ return { platform: "github", owner, repo, issueNumber, ...(installationId !== undefined ? { installationId } : {}) };
325
+ },
326
+ react: (target, emoji) => {
327
+ const t = target;
328
+ return liveGitHubReact({ platform: "github", owner: t.owner, repo: t.repo, issueNumber: t.issueNumber, ...(t.installationId !== undefined ? { installationId: t.installationId } : {}) }, emoji);
329
+ },
330
+ describeRedriveTarget: (origin) => {
331
+ const o = origin;
332
+ return `github ${o.owner}/${o.repo}#${o.issueNumber}`;
333
+ },
334
+ redriveContainerId: (origin) => {
335
+ const o = origin;
336
+ return `${o.owner}/${o.repo}`;
337
+ },
338
+ };
339
+ //# sourceMappingURL=github-post-arm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-post-arm.js","sourceRoot":"","sources":["../../../agent/channels/github-post-arm.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ import { type ReactionIngestOutcome, type PrFetchFn, type ReactionSinkDeps } from "@open-everywhere/core/agent/adapter-api.js";
2
+ /** The conversation one accepted delivery names — everything the poll needs and nothing else. */
3
+ export interface GitHubReactionPollTarget {
4
+ readonly owner: string;
5
+ readonly repo: string;
6
+ readonly number: number;
7
+ readonly installationId?: number;
8
+ readonly repoPrivate?: boolean;
9
+ }
10
+ /** One of the agent's timeline comments, narrowed to what the poll reads. */
11
+ export interface GitHubPolledComment {
12
+ readonly id: number;
13
+ readonly authorLogin?: string;
14
+ /** GitHub's `user.type` — `"Bot"` for an App's comment. */
15
+ readonly authorType?: string;
16
+ /** `reactions.total_count`. Zero ⇒ the arm never asks for the reactions. */
17
+ readonly reactionCount?: number;
18
+ }
19
+ /** One reaction on one of those comments. */
20
+ export interface GitHubPolledReaction {
21
+ /** GitHub's fixed eight-value `content` enum (`+1`, `-1`, `laugh`, `confused`, `heart`, `hooray`,
22
+ * `rocket`, `eyes`) — passed through as the emoji DATA, bucketed by core. */
23
+ readonly content: string;
24
+ /** WHO reacted. GitHub sends a `user.type` beside it, and this shape deliberately drops it: the
25
+ * agent's own reaction is identified by its EXACT login, and adding the type could only widen
26
+ * what counts as ours. That is the opposite of {@link GitHubPolledComment}, where the type is a
27
+ * second signal that must AGREE before a comment counts as the agent's. */
28
+ readonly userLogin?: string;
29
+ }
30
+ /** Injected seams, so parse + filter + dedupe + record is unit-tested with no live installation. */
31
+ export interface GitHubReactionFeedbackDeps {
32
+ readonly sink: ReactionSinkDeps;
33
+ /** The app's own bot login (`<slug>[bot]`). Absent ⇒ the arm polls nothing — see the header. */
34
+ readonly botLogin?: string;
35
+ readonly listComments: (target: GitHubReactionPollTarget) => Promise<readonly GitHubPolledComment[]>;
36
+ readonly listReactions: (target: GitHubReactionPollTarget, commentId: number) => Promise<readonly GitHubPolledReaction[]>;
37
+ /** Has this exact reaction already been recorded? The keyed dedupe's read half — see the header.
38
+ * {@link liveGitHubReactionRecorded} answers it off the durable claim store. */
39
+ readonly recorded: (partition: GitHubReactionPartition, key: string) => Promise<boolean>;
40
+ /** Claim this reaction as recorded. Called ONLY after the sink reports the row written, which is
41
+ * what makes a failed write retryable rather than lost. */
42
+ readonly markRecorded: (partition: GitHubReactionPartition, key: string, atIso: string) => Promise<void>;
43
+ /** GATE 1 (OPE-1540): is the agent IN this conversation — has this bundle posted here, or did an
44
+ * @mention engage the container? Refusing here is what makes a delivery in a conversation the
45
+ * agent is not in cost ZERO REST calls. */
46
+ readonly inScope: (target: GitHubReactionPollTarget, partition: GitHubReactionPartition) => Promise<boolean>;
47
+ /** GATE 2 (OPE-1540): spend one poll from this repository's hourly budget. False refuses the poll
48
+ * and the refusal is audited by the scope module, not here. */
49
+ readonly spendBudget: (target: GitHubReactionPollTarget, partition: GitHubReactionPartition, atIso: string) => Promise<boolean>;
50
+ /** Say out loud that {@link GitHubReactionFeedbackDeps.recorded} threw during this poll, and how
51
+ * many reactions the fail-closed dedupe skipped because of it. Called AT MOST ONCE per poll —
52
+ * see the write's own header in channels/github-reaction-scope.ts for why the count rides the row
53
+ * rather than the row riding each reaction. Absent ⇒ the poll writes nothing, which is what the
54
+ * unit stubs take. */
55
+ readonly auditClaimUnreadable?: (target: GitHubReactionPollTarget, partition: GitHubReactionPartition, atIso: string, skipped: number) => Promise<void>;
56
+ /** How many of the agent's own comments one poll examines, newest first. Bounds the REST cost of a
57
+ * long thread; defaults to {@link GITHUB_REACTION_POLL_COMMENT_BUDGET}. */
58
+ readonly commentBudget?: number;
59
+ readonly now?: () => string;
60
+ }
61
+ /** The partition coordinates the audit read and the ingest share. */
62
+ export interface GitHubReactionPartition {
63
+ readonly org: string;
64
+ readonly workspace: string;
65
+ readonly surfaceKey: string;
66
+ }
67
+ /** One poll examines at most this many of the agent's own comments, newest first. A conversation
68
+ * with more agent comments than this is one where the older ones stopped attracting reactions. */
69
+ export declare const GITHUB_REACTION_POLL_COMMENT_BUDGET = 10;
70
+ /**
71
+ * The conversation one delivery names, or null when the delivery names none. NEVER THROWS.
72
+ *
73
+ * It reads the SAME body the stop leg and the ambient leg read, and it accepts a delivery those two
74
+ * refuse — an `issues` event, or an `issue_comment` the agent itself wrote. The poll is about the
75
+ * CONVERSATION, so any accepted delivery naming one is a valid occasion to look.
76
+ */
77
+ export declare function parseGitHubReactionPollTarget(body: string, eventType: string): GitHubReactionPollTarget | null;
78
+ /** Is this comment the agent's own? `user.type === "Bot"` AND the configured `<slug>[bot]` login —
79
+ * both, because either alone is wrong: every App's comment is type `Bot`, and a human account may
80
+ * legally be named to look like one. */
81
+ export declare function githubCommentIsOwn(comment: GitHubPolledComment, botLogin: string | undefined): boolean;
82
+ /** The identity of one recorded reaction — a comment, a reactor and a glyph. The dedupe key, and the
83
+ * same three fields `liveReactionSink` writes into the audit row. */
84
+ export declare function githubReactionKey(messageId: string, reactorId: string, emoji: string): string;
85
+ /**
86
+ * Poll one conversation and record every reaction on the agent's own comments that is not recorded
87
+ * yet. Returns one outcome per NEW reaction — an empty array when there is nothing new, which is the
88
+ * ordinary case on a busy conversation.
89
+ *
90
+ * NEVER THROWS: a failed read or a failed sink must never cost the ingress its ack. A comment whose
91
+ * reactions cannot be read is skipped, and the rest of the poll continues.
92
+ */
93
+ export declare function routeGitHubReactionFeedback(target: GitHubReactionPollTarget, deps: GitHubReactionFeedbackDeps): Promise<readonly ReactionIngestOutcome[]>;
94
+ /** Production deps for the poll. `botLogin` is `<GITHUB_APP_SLUG>[bot]` — absent when the slug is
95
+ * unset, which is exactly the fail-closed case {@link routeGitHubReactionFeedback} refuses on. */
96
+ export declare function liveGitHubReactionFeedbackDeps(appSlug: string | undefined, fetchFn?: PrFetchFn): GitHubReactionFeedbackDeps;
97
+ //# sourceMappingURL=github-reaction-feedback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-reaction-feedback.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-reaction-feedback.ts"],"names":[],"mappings":"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"}