@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,116 @@
1
+ // GITHUB's surface-parity declaration (OPE-1499). See the core module's header
2
+ // (packages/core/agent/identity/surface-parity.ts) for what the three cell shapes mean.
3
+ //
4
+ // GitHub is a TICKETING surface. A repo holds issues and pull requests, not chat rooms, so the
5
+ // three cross-container verbs and the DM family are absent for a structural reason rather than an
6
+ // unbuilt one — and that distinction is what the reasons below carry.
7
+
8
+ import type { SurfaceParityDeclaration } from "@open-everywhere/core/agent/adapter-api.js";
9
+
10
+ export const GITHUB_SURFACE_PARITY: SurfaceParityDeclaration = {
11
+ // --- registration seams (§7.1) ---------------------------------------------------------------
12
+ "identity-mapper": "supported",
13
+ "capability-manifest": "supported",
14
+ "post-arm": "supported",
15
+ "wake-origin-codec": "supported",
16
+ "unified-action-plane": {
17
+ unsupported:
18
+ "an issue comment is markdown and carries no component plane at all — GitHub offers no tappable button a bot may render into a timeline comment, so there is nothing for a tap route to receive. The bridged approval posts as prose, which the question bridge already renders complete",
19
+ },
20
+ "origin-placement-carry": {
21
+ unsupported:
22
+ "the placement is INSIDE the conversation identity: a reply is a comment appended to the issue timeline and the issue IS `threadRef`'s thread key, so there is no 'where inside it' left to name",
23
+ },
24
+ "read-adapter": "supported",
25
+ // 🔴 A POLL, not a delivery, and the note says so because the latency is real. GitHub pushes no
26
+ // reaction webhook at all, so the arm reads the reactions on the agent's own comments when a
27
+ // later delivery names their conversation. Only an ADD is observable — the route returns the
28
+ // reactions that exist, never one that was taken away.
29
+ "reaction-feedback-ingest": { supported: "polled on the next delivery for that conversation — GitHub pushes no reaction webhook, so a removal is never observed" },
30
+ "ancillary-ops": "supported",
31
+ "ambient-engagement-seam": "supported",
32
+ // `githubWakeAddressing` (channels/github-engagement.ts) carries the admission's own `deliver`
33
+ // verdict to the `github.dispatched` row, so an engaged follow-up reads `thread-follow` and
34
+ // unowned issue chatter still reads `channel-watch`. The verdict is floored on
35
+ // `githubEngagedFollowUpReach`, so an engaged issue on a public or unknown-privacy repo, and an
36
+ // inline review comment, still write `channel-watch` — the engaged door is shut there. The wake
37
+ // envelope is untouched: the turn is judged, and the silence bar set, exactly as before
38
+ // (OPE-1752).
39
+ "engaged-row-promotion": "supported",
40
+ // The container and the thread coincide — an issue or pull request IS its own thread — so every
41
+ // wake this surface diverts is thread-scoped and consults ownership.
42
+ "owned-thread-ingress": "supported",
43
+
44
+ // --- wake legs and conversation shapes -------------------------------------------------------
45
+ "addressed-leg": "supported",
46
+ // The ambient supervisor, a different mechanism from the §2.5 registry: the domain opt-in and the
47
+ // operator vouch decide whether it runs, and the registry has never gated that. The registry does
48
+ // SUPPRESS it, though — `ambientDelivery` drops a comment in a container `stand_down` marked, and
49
+ // drops one the native leg now owns (OPE-1501).
50
+ "ambient-leg": { supported: "the ambient supervisor, not the §2.5 engagement registry" },
51
+ // `github-catch-up.ts` reads `/issues/:n/comments?since=` for the conversation the seat is woken
52
+ // on, over the same brokered conversation-read token `read_channel` uses. The vendor's own `since`
53
+ // filter does the narrowing, so a seat that woke a minute ago pays for a near-empty page.
54
+ "seat-catch-up": "supported",
55
+ // The supervisor wake fills the slot with ONE host-composed issue/PR title line, and pours it
56
+ // through the core seam (github-supervisor.ts). One line is still a pour: a `members-only` domain
57
+ // must get the same answer here as it gets on the catch-up read beside it.
58
+ // 🔴 BARE, NOT `{ supported: "…" }` — a noted cell is exempt from the overclaim arm, and this
59
+ // surface is half of the OPE-1800 class the arm exists to catch.
60
+ "seat-context-admission": "supported",
61
+ // A GitHub comment carries no component plane at all — there is no button to tap and no route for
62
+ // a tap to arrive on. See the roster row for what that costs and where it does NOT cost anything.
63
+ "approval-card": { unsupported: "an issue/PR comment carries no component plane, so there is no button to render and no route for a tap. A governed act that takes the venue walk is redirected to a card-capable room in the SAME domain, or refused when the domain holds none. The paths OUTSIDE that walk do not: the spend checkpoint (spend/checkpoint.ts `requireSessionContinuation`) fails OPEN here and records the crossing in an audit row instead" },
64
+ // THE TRACE ARRIVES EVEN THOUGH THE BUTTON CANNOT (OPE-1854). A judge-lifted act posts its
65
+ // notice here as a plain issue or pull-request comment through the shared road (`postJudgeLiftNotice`), on this
66
+ // surface's own `SurfaceMessageOps` transport. It carries the overrule as an INSTRUCTION in
67
+ // words, with the action id a reviewer finds the act by, because no tap can land here.
68
+ // 🔴 BARE, NOT `{ supported: "…" }`. A noted cell is exempt from the overclaim arm, and the
69
+ // arm is the point: drop this bundle's text transport and the notice silently stops arriving.
70
+ "judge-lift-notice": "supported",
71
+ "name-drop-mention": { unsupported: "not built yet — the ambient supervisor already reads every comment, so a name-drop match adds no wake" },
72
+ // 🔴 EVERY WAKE LEG ADMITS `created` ONLY, and that is the narrower true claim. The delivery DOES
73
+ // reach OE code — `wrapRoute` runs the fan-out, the installation leg and the reaction poll on an
74
+ // edited comment like any other — but no leg that can start a turn admits one: eve's route
75
+ // dispatches `issue_comment`/`pull_request_review_comment` with `action: "created"`,
76
+ // `extractGitHubAmbientEvent` reads the same created-only shape, and `parseGitHubStopComment`
77
+ // agrees.
78
+ "message-edit-wake": { unsupported: "every wake leg admits `action: \"created\"` only — eve's route, the ambient extractor and the stop leg each refuse an edited comment" },
79
+ // A review thread on a pull request (§4.6.1).
80
+ threads: "supported",
81
+ // A STANDING room (§4.7.1's sense), not §4.5's read_channel container — GitHub has the latter and
82
+ // `read_channel` is supported below. Every GitHub container is one issue or pull request, so it
83
+ // exists because somebody filed work and is about that work.
84
+ rooms: { unsupported: "no standing container — every GitHub container is one issue or pull request" },
85
+ dm: { unsupported: "no DM plane — GitHub carries no person-to-person message channel" },
86
+ mpim: { unsupported: "no DM plane — GitHub carries no person-to-person message channel" },
87
+
88
+ // --- core verbs (§7.2) -----------------------------------------------------------------------
89
+ reply: "supported",
90
+ edit_reply: "supported",
91
+ react: "supported",
92
+ no_reply: "supported",
93
+ read_channel: "supported",
94
+ read_thread: "supported",
95
+ // The work-search adapter is CORE-resident and ungated (packages/core/agent/scm/github-search.ts,
96
+ // `registerCoreWorkSearchAdapter`), not registered from this bundle — but the corpus is GitHub's.
97
+ search: { supported: "the core-resident adapter (`scm/github-search.ts`), registered ungated" },
98
+ // NOT a structural absence, and the reason matters: the transport exists (this bundle registers
99
+ // `SurfaceMessageOps`, and `approvals/chat-post.ts` posts through it). What refuses is the closed
100
+ // `RESOURCE_TYPES` table in `access/resource-grant.ts`, which carries no row for this surface, so
101
+ // no `channel` grant can be minted and `chatWriteMount` never arms the tool.
102
+ post_message: { unsupported: "not built yet — no `channel` row in `access/resource-grant.ts` — OPE-1507" },
103
+ dm_person: { unsupported: "no DM plane — GitHub carries no person-to-person message channel (§4.7.2)" },
104
+ set_dm_delivery: { unsupported: "no DM plane — the verb mounts only inside a 1:1 DM (§4.7.2)" },
105
+ // GitHub sends no webhook for a reaction at all, so the trigger is a comment whose ENTIRE body is
106
+ // the word `stop` — one thing only, never a magic string competing with real messages (§4.8).
107
+ stop: { supported: "a comment whose entire body is `stop` — GitHub sends no reaction webhook" },
108
+ // `react` IS wired, so this is not "no reaction primitive". GitHub's reaction set is the fixed
109
+ // eight (+1, -1, laugh, confused, heart, hooray, rocket, eyes) and none of them is a raised hand,
110
+ // so there is no glyph to place — github-message-ops.ts offers no `setReaction` for that reason
111
+ // rather than faking one against the wrong emoji.
112
+ ack_stop_glyph: { unsupported: "vendor limit — GitHub's fixed eight reactions carry no raised hand, so there is no ✋ to place" },
113
+ ack_cancel_note: "supported",
114
+ stand_down: { supported: "marks the issue/pull-request row; refuses when no row exists" },
115
+ post_file: { unsupported: "vendor limit — a GitHub comment carries no file-attachment API" },
116
+ };
@@ -0,0 +1,29 @@
1
+ import { type NormalizedMessage, type PrFetchFn, type SeatCatchUpFetch } from "@open-everywhere/core/agent/adapter-api.js";
2
+ /** Injected seam so the fetch drives offline in tests; defaults to the package's one read transport. */
3
+ export interface GitHubCatchUpDeps {
4
+ readonly fetchFn: PrFetchFn;
5
+ /** Resolve the brokered conversation-read token for a repo. Injected so the fetch drives with no
6
+ * credential store; the live default is the same resolver `githubReadAdapter` uses. */
7
+ readonly resolveToken?: (repoFull: string) => Promise<string>;
8
+ /** The bot's own login, so its prior comments read as `self` rather than as a foreign bot. Absent ⇒
9
+ * the seat's own posts arrive tagged `bot`, which is visible and honest, never a crash. */
10
+ readonly selfLogin?: string;
11
+ }
12
+ /** The fetch, with its seams exposed so the contract test drives it with no network. */
13
+ export declare function fetchGitHubCatchUpWindow(req: {
14
+ container: string;
15
+ sinceMs: number;
16
+ }, deps?: GitHubCatchUpDeps): Promise<readonly NormalizedMessage[]>;
17
+ /**
18
+ * 🔴 GITHUB STAMPS A COMMENT'S `created_at` TO THE SECOND, and this is the number the shared catch-up
19
+ * road widens both of its bounds by (OPE-1847, `core/agent/channels/seat-catch-up.ts`
20
+ * {@link SeatCatchUpDeclaration.timestampPrecisionMs}).
21
+ * It is the VENDOR'S UNIT, not a margin. Two comments posted inside one wall-clock second — which two
22
+ * API round trips easily are — come back carrying the SAME instant, so a window that ends strictly at
23
+ * the trigger's stamp drops the comment the seat woke to read. That is why this leg used to owe a
24
+ * two-second sleep (`core/scripts/e2e/scenarios.ts`, J-ENGAGED-github).
25
+ */
26
+ export declare const GITHUB_TIMESTAMP_PRECISION_MS = 1000;
27
+ /** GitHub's registered {@link SeatCatchUpFetch} — the one thing this surface contributes to the shared path. */
28
+ export declare const githubSeatCatchUpFetch: SeatCatchUpFetch;
29
+ //# sourceMappingURL=github-catch-up.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-catch-up.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-catch-up.ts"],"names":[],"mappings":"AAuBA,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACtB,MAAM,4CAA4C,CAAC;AAkDpD,wGAAwG;AACxG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B;4FACwF;IACxF,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9D;gGAC4F;IAC5F,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAUD,wFAAwF;AACxF,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAC3C,IAAI,GAAE,iBAA4B,GACjC,OAAO,CAAC,SAAS,iBAAiB,EAAE,CAAC,CAiBvC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,OAAQ,CAAC;AAEnD,gHAAgH;AAChH,eAAO,MAAM,sBAAsB,EAAE,gBAAwG,CAAC"}
@@ -0,0 +1,105 @@
1
+ // github-catch-up — GitHub's registered SEAT CATCH-UP FETCH (OPE-1726): the seat's own conversation
2
+ // since its last wake, normalized for core's shared catch-up path (channels/seat-catch-up.ts).
3
+ //
4
+ // WHAT "THE SEAT'S CONTAINER" IS HERE. A GitHub seat is bound to a repo and every wake arrives on ONE
5
+ // issue or pull request, so the history a catch-up owes is that conversation's timeline comments —
6
+ // the same container `read_channel` reads and the same one the reply lands in. The request's
7
+ // `container` is therefore the conversation key `owner/repo#number` the read adapter already mints.
8
+ //
9
+ // WHY A STANDALONE CALLER, not `githubReadAdapter.readChannel`: that method takes a live
10
+ // {@link BindableContext} and runs the turn-time domain-scope fence over it. A catch-up fires from a
11
+ // webhook route with no turn, and its container is the one the wake itself already admitted — the
12
+ // seat is bound to it — so there is no second scope question to ask. The CREDENTIAL is the same
13
+ // brokered conversation-read token the adapter uses, and the transport is the same `getJson` seam,
14
+ // so this read inherits the 429 backoff rather than opening a second, unretried path.
15
+ //
16
+ // `since` IS A VENDOR FILTER, and it is why this leg is cheap: GitHub's issue-comments endpoint takes
17
+ // an ISO timestamp and returns only what changed after it, so a seat that woke a minute ago pays for
18
+ // a near-empty page instead of walking the conversation. The shared path re-applies the same bound on
19
+ // its own side, so a vendor that returned more is trimmed there.
20
+ //
21
+ // An unparseable container yields `[]`. A credential/transport/read error propagates to core's
22
+ // shared seatCatchUp boundary, which keeps the wake alive and records `failed` (OPE-1842).
23
+ import { catchUpSender, resolveConversationReadToken, } from "@open-everywhere/core/agent/adapter-api.js";
24
+ import { getJson, liveGithubReadFetch, parseGithubContainer } from "./github-read.js";
25
+ const API_BASE = "https://api.github.com";
26
+ /** GitHub's page ceiling for the comments endpoint. ONE page: the catch-up window is "since the seat
27
+ * last woke", and a conversation that produced more than 100 comments in that window is one the cap
28
+ * in core would trim anyway. */
29
+ const CATCH_UP_PAGE_LIMIT = 100;
30
+ /**
31
+ * Normalize ONE timeline comment, applying the VISIBILITY GATE every fetch adapter owes core: a
32
+ * comment whose author GitHub does not name is dropped, because an unattributable body must never
33
+ * reach a seat. An `App`/`Bot` actor is KEPT and tagged `bot` — attributable machine awareness, which
34
+ * the domain's admission policy then accepts or refuses — and the bot's own login is what lets the
35
+ * seat recognise its own prior posts as `self`.
36
+ * Returns undefined for a dropped (unattributed, empty, or undateable) comment.
37
+ */
38
+ function normalizeComment(c, selfLogin) {
39
+ const login = c.user?.login;
40
+ if (!login)
41
+ return undefined; // unattributable ⇒ fail closed
42
+ const text = (c.body ?? "").trim();
43
+ if (text.length === 0)
44
+ return undefined;
45
+ const ms = Date.parse(c.created_at ?? "");
46
+ if (!Number.isFinite(ms))
47
+ return undefined;
48
+ const isBot = c.user?.type === "Bot";
49
+ const isSelf = selfLogin !== undefined && login.toLowerCase() === selfLogin.toLowerCase();
50
+ return {
51
+ id: String(c.id ?? `${login}@${c.created_at ?? ms}`),
52
+ ms,
53
+ // The bot's OWN posts carry no author id, matching every other surface's normalized shape.
54
+ ...(isSelf ? {} : { authorId: login }),
55
+ // THE SHARED SENDER RULE (OPE-1857): this surface contributes the vendor's own signal —
56
+ // GitHub's `user.type` is `Bot` for an app — and core decides what it means.
57
+ sender: catchUpSender({ self: isSelf, bot: isBot }),
58
+ // A timeline comment sits at the top level of its conversation — the same answer
59
+ // `githubReadAdapter.wakeAddress` gives (only an inline review comment is thread-scoped).
60
+ type: "channel",
61
+ text,
62
+ };
63
+ }
64
+ const liveDeps = {
65
+ fetchFn: liveGithubReadFetch,
66
+ // The GitHub App's comment author is `<app-slug>[bot]`; the deployment states its own slug, and an
67
+ // unset one degrades to "our posts look like a bot's", which the admission policy still admits
68
+ // under `wide`.
69
+ selfLogin: process.env.GITHUB_APP_SLUG ? `${process.env.GITHUB_APP_SLUG}[bot]` : undefined,
70
+ };
71
+ /** The fetch, with its seams exposed so the contract test drives it with no network. */
72
+ export async function fetchGitHubCatchUpWindow(req, deps = liveDeps) {
73
+ const target = parseGithubContainer(req.container);
74
+ if (!target)
75
+ return []; // not a conversation key ⇒ nothing addressable to read
76
+ const token = await (deps.resolveToken ?? resolveConversationReadToken)(target.repoFull);
77
+ const since = new Date(req.sinceMs).toISOString();
78
+ const url = `${API_BASE}/repos/${target.repoFull}/issues/${target.number}/comments?per_page=${CATCH_UP_PAGE_LIMIT}&since=${encodeURIComponent(since)}`;
79
+ const raw = await getJson(deps.fetchFn, url, token, "issues.listComments");
80
+ // A successful HTTP status with the wrong body shape is an attempted read failure, not evidence
81
+ // that the conversation was quiet. Keep the error fixed and content-free: the vendor body can
82
+ // contain arbitrary text and the shared audit row records only the error class/status.
83
+ if (!Array.isArray(raw))
84
+ throw new Error("GitHub catch-up returned a malformed response");
85
+ const out = [];
86
+ for (const c of raw) {
87
+ const n = normalizeComment(c, deps.selfLogin);
88
+ if (n)
89
+ out.push(n);
90
+ }
91
+ return out;
92
+ }
93
+ /**
94
+ * 🔴 GITHUB STAMPS A COMMENT'S `created_at` TO THE SECOND, and this is the number the shared catch-up
95
+ * road widens both of its bounds by (OPE-1847, `core/agent/channels/seat-catch-up.ts`
96
+ * {@link SeatCatchUpDeclaration.timestampPrecisionMs}).
97
+ * It is the VENDOR'S UNIT, not a margin. Two comments posted inside one wall-clock second — which two
98
+ * API round trips easily are — come back carrying the SAME instant, so a window that ends strictly at
99
+ * the trigger's stamp drops the comment the seat woke to read. That is why this leg used to owe a
100
+ * two-second sleep (`core/scripts/e2e/scenarios.ts`, J-ENGAGED-github).
101
+ */
102
+ export const GITHUB_TIMESTAMP_PRECISION_MS = 1_000;
103
+ /** GitHub's registered {@link SeatCatchUpFetch} — the one thing this surface contributes to the shared path. */
104
+ export const githubSeatCatchUpFetch = (req) => fetchGitHubCatchUpWindow({ container: req.container, sinceMs: req.sinceMs });
105
+ //# sourceMappingURL=github-catch-up.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-catch-up.js","sourceRoot":"","sources":["../../../agent/channels/github-catch-up.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,223 @@
1
+ import { type EngagementRow, type Partition } from "@open-everywhere/core/agent/adapter-api.js";
2
+ import type { GitHubAmbientAddressing } from "./github-supervisor.js";
3
+ /**
4
+ * The engagement-registry anchor for one GitHub container, or `undefined` when the delivery names
5
+ * no container number.
6
+ *
7
+ * 🔴 IT IS THE ONE PREDICATE THE MAPPER AND THE SEAM SHARE. `bindGitHub` publishes exactly this as
8
+ * `engagementKey`, so `stand_down`'s mark and this seam's read address the same row. Two spellings
9
+ * of "which container this is" is how a stop comes to mark a row no gate reads.
10
+ */
11
+ export declare function githubEngagementAnchor(owner: string | undefined, repo: string | undefined, num: number | undefined): string | undefined;
12
+ /** The partition one container's engagement row lives in — the REPO grain
13
+ * (`github/<owner>/<repo>`), which is what `resolvePartition` gives a bound GitHub session and
14
+ * therefore what `stand_down` writes under. It is built from THIS delivery's own facts, through the
15
+ * same `buildGitHubAuth` the wake dispatches, so the seam can never key a row the turn would not.
16
+ * Null on an unresolvable identity: the caller then keeps today's path, the fail-open posture every
17
+ * carriage leg on this surface takes. */
18
+ export declare function githubSeamPartition(f: GitHubSeamFacts): Partition | null;
19
+ /** The facts one comment delivery contributes to the seam. Both legs can build it — the native
20
+ * handler from eve's inbound context, the ambient leg from the raw webhook event — so neither
21
+ * re-states the policy below. */
22
+ export interface GitHubSeamFacts {
23
+ readonly owner: string;
24
+ readonly repo: string;
25
+ /** The issue or pull-request number the comment landed on. */
26
+ readonly number: number | undefined;
27
+ readonly isPrivate?: boolean;
28
+ /** The comment author's login — the principal this delivery binds. It rides the facts rather than
29
+ * being stubbed here so the partition the seam keys is built from the delivery's own identity. */
30
+ readonly actorLogin: string;
31
+ /** The comment @mentions this deployment's app (the caller's own `githubBotMentioned` verdict). */
32
+ readonly isMention: boolean;
33
+ /** The comment was written by a bot or by this app itself — never delivered (loop guard). */
34
+ readonly authorIsBot: boolean;
35
+ /** The container this comment landed in, in GitHub's own vocabulary — `issue`, `pull_request` or
36
+ * `review_thread`. The seam reads it for one question, in
37
+ * {@link githubEngagedFollowUpReach}: an INLINE review comment shares its pull request's anchor,
38
+ * so a review thread is not a container of its own. Required rather than optional, because a
39
+ * caller that forgets it would silently widen that arm. */
40
+ readonly conversationKind: string | undefined;
41
+ }
42
+ /**
43
+ * May the NON-MENTION arm of the seam reach this delivery at all? Read BEFORE any registry read, by
44
+ * both legs, so the two never disagree about which comments the native leg owns.
45
+ *
46
+ * TWO FLOORS. Each refuses on an unknown rather than admitting, and each is about REACH — whether
47
+ * this kind of delivery may wake at all — never about whether the container is engaged.
48
+ *
49
+ * • 🔴 THE REPO MUST BE POSITIVELY PRIVATE. An engaged container's plain comment wakes a FULL
50
+ * native turn: a sandbox checkout and a judged spend. On a public repo that lets ANY GitHub
51
+ * account buy one, by writing in an issue somebody once @mentioned the app in — the same
52
+ * drive-by spend and prompt-injection surface `classifyGitHubAmbientComment` step 5 refuses for,
53
+ * on the louder of the two legs. 🔴 THIS FLOOR IS STRICTER THAN THAT ONE, deliberately: step 5
54
+ * admits a VOUCHED public repo, and this one refuses every public repo. A vouch widens the
55
+ * ambient supervisor's judged reply; widening an uninvited NATIVE turn to a public repo is a
56
+ * bigger step that needs its own ruling.
57
+ * • 🔴 A REVIEW THREAD IS NOT A CONTAINER OF ITS OWN. {@link githubEngagementAnchor} collapses to
58
+ * `owner/repo#N`, so every INLINE review comment on a pull request reads the row that one
59
+ * @mention anywhere on that pull request engaged — and one review leaves dozens of them.
60
+ *
61
+ * AN @MENTION IS UNAFFECTED BY BOTH, on a public repo and inside a review thread alike: it is
62
+ * answered by the caller's own mention gate, which this seam never narrows.
63
+ */
64
+ export declare function githubEngagedFollowUpReach(f: GitHubSeamFacts): {
65
+ readonly reachable: boolean;
66
+ readonly reason: string;
67
+ };
68
+ /**
69
+ * What the seam says about one inbound comment. Four outcomes, each naming what the CALLER owes:
70
+ *
71
+ * • `unanchored` — the delivery names no container, so there is no row to address and the caller
72
+ * keeps today's path exactly.
73
+ * • `engage` — an @mention. The caller marks the container engaged ({@link markGitHubEngaged}); the
74
+ * mention itself is answered by the caller's own path, never by the seam.
75
+ * • `deliver` — a plain comment in an engaged container. The caller wakes a turn.
76
+ * • `hold` — a plain comment the seam does not admit. `stopped` says WHY, and it is the one field a
77
+ * caller must branch on: `stopped: true` means the agent was asked to stand down in this
78
+ * container, so the comment must be SUPPRESSED outright — including on the ambient leg, which
79
+ * does not otherwise read this registry. `stopped: false` means the container simply is not
80
+ * engaged, which is not a suppression: the caller keeps today's path.
81
+ */
82
+ export type GitHubEngagementVerdict = {
83
+ readonly kind: "unanchored";
84
+ readonly reason: string;
85
+ } | {
86
+ readonly kind: "engage";
87
+ readonly anchor: string;
88
+ } | {
89
+ readonly kind: "deliver";
90
+ readonly anchor: string;
91
+ readonly reason: string;
92
+ } | {
93
+ readonly kind: "hold";
94
+ readonly anchor: string;
95
+ readonly reason: string;
96
+ readonly stopped: boolean;
97
+ };
98
+ /** Injectable seams so the whole verdict is unit-tested without a store. */
99
+ export interface GitHubEngagementDeps {
100
+ readonly getEngagement?: (p: Partition, anchor: string) => Promise<EngagementRow | null>;
101
+ readonly markEngaged?: (p: Partition, anchor: string, atIso: string) => Promise<void>;
102
+ /** The mark's durable once-guard — see {@link markGitHubEngaged}. Injected so a test can drive a
103
+ * redelivered @mention without an operational store. */
104
+ readonly markEventSeen?: (org: string, key: string, atIso: string) => Promise<boolean>;
105
+ readonly now?: () => string;
106
+ }
107
+ /**
108
+ * Resolve the seam for one comment — READ-ONLY. The mark is a separate call
109
+ * ({@link markGitHubEngaged}) so exactly one caller writes the row: two writers is how one comment
110
+ * comes to engage a container twice, once on each leg.
111
+ *
112
+ * ORDERING. A mention is settled before any read: it is always answered by the caller's own path, so
113
+ * its verdict costs no store round-trip. Only a plain comment pays for a read, and it pays for
114
+ * exactly one — the ROW, not a boolean, because the row carries both facts the decision needs (is
115
+ * this engaged, and was the agent asked to stand down here) and two reads could disagree.
116
+ *
117
+ * THE DECISION ITSELF IS CORE'S. `shouldDeliverReply` owns the disqualifier order and the reasons;
118
+ * this function only translates GitHub facts into its input. Nothing here re-states the policy.
119
+ *
120
+ * FAIL-CLOSED on a store error: `getThreadEngagement` reads a failure as `null`, so an unreadable
121
+ * registry holds rather than delivers — the agent does not barge into a conversation it cannot prove
122
+ * it was invited to.
123
+ */
124
+ export declare function resolveGitHubEngagement(f: GitHubSeamFacts, deps?: GitHubEngagementDeps): Promise<GitHubEngagementVerdict>;
125
+ /**
126
+ * Mark one container ENGAGED on an @mention, ONCE PER COMMENT.
127
+ *
128
+ * `participating: true` is the re-invitation: it promotes the row and CLEARS a previous stand-down,
129
+ * which is what "an @mention brings the agent back" means on every surface that wires this seam.
130
+ *
131
+ * 🔴 THE MARK IS DEDUPED, AND THE STOP IS WHY. Clearing the flag is right for a person's second
132
+ * @mention and WRONG for GitHub redelivering the first one — a redelivered webhook would quietly
133
+ * lift a stand-down nobody asked to lift. `commentId` is the comment's own id, so the guard keys on
134
+ * the thing that must happen once. It is the DURABLE layer only, unlike Teams' front: this route
135
+ * holds no in-process dedupe object, and a serverless invocation is a fresh process, so an
136
+ * in-memory layer would catch almost nothing here. FAIL-OPEN on a store error, the same posture
137
+ * every dedupe in this repository takes — a dedupe prevents a double write, not a security hole.
138
+ *
139
+ * Best-effort throughout — `markThreadEngaged` swallows a store failure, because engagement
140
+ * bookkeeping must never fail a turn. Returns the anchor it marked, or null when it marked nothing.
141
+ */
142
+ export declare function markGitHubEngaged(f: GitHubSeamFacts, commentId: string | number | undefined, deps?: GitHubEngagementDeps): Promise<string | null>;
143
+ /**
144
+ * Is this comment MACHINE-AUTHORED — a GitHub App, a bot account, or this deployment's own reply?
145
+ *
146
+ * 🔴 ONE PREDICATE, AND IT MUST FAIL CLOSED ON AN UNRESOLVED SLUG. `githubBotMentioned` needs the
147
+ * app slug to recognise a mention at all, so before this seam a deployment with no `GITHUB_APP_SLUG`
148
+ * simply never woke natively. A plain comment now reaches the seam, and without the slug the app
149
+ * cannot tell its OWN reply from a person's — so "unknown" answers `true` and nothing is delivered,
150
+ * the same posture `classifyGitHubAmbientComment` takes with its own no-slug drop.
151
+ */
152
+ export declare function githubMachineAuthored(authorLogin: string | undefined, authorType: string | undefined, botSlug: string | undefined): boolean;
153
+ /**
154
+ * The NATIVE leg's one-line admission (`github.ts`'s mention gate): may this plain, non-mention
155
+ * comment run a turn?
156
+ *
157
+ * It exists because the native channel is where a GitHub turn is minted — the wake is the webhook
158
+ * itself, so admitting the comment through the mention gate IS the wake. The ambient leg owns the
159
+ * complementary halves above it (the STOP suppression, and dropping a comment this leg now owns), so
160
+ * this call only ever answers the delivery question, and it never writes.
161
+ *
162
+ * 🔴 THE REACH FLOORS RUN FIRST, above the registry read: a public repo and an inline review comment
163
+ * are refused whatever the row says ({@link githubEngagedFollowUpReach}). Putting them here rather
164
+ * than in {@link resolveGitHubEngagement} is deliberate — the ambient leg reads that function for the
165
+ * STOP, and a stood-down container must stay suppressed on a public repo too.
166
+ */
167
+ export declare function githubEngagedAdmits(f: GitHubSeamFacts, deps?: GitHubEngagementDeps): Promise<boolean>;
168
+ /**
169
+ * WHAT WOKE ONE AMBIENT-LEG DELIVERY, for the `github.dispatched` audit row (OPE-1752).
170
+ *
171
+ * 🔴 THE CLASSIFIER'S OWN WORD CANNOT ANSWER THIS, AND WRITING IT WAS THE DEFECT.
172
+ * `classifyGitHubAmbientComment` answers a narrower question — did this comment ADDRESS the agent —
173
+ * and an engaged follow-up did not, so it stays `channel-watch`. That is the right answer to its own
174
+ * question, and `channel-watch` is what makes the turn a JUDGED one. But `channel-watch` ALSO names
175
+ * unowned chatter in an un-enrolled issue, and the audit row is the one place those two must not
176
+ * read alike. This is the same promotion OPE-1510 made on Notion (`notionWakeAddressing`) and
177
+ * OPE-1578 made on Linear (`linearWakeAddressing`) — one concept, one spelling, across three
178
+ * surfaces.
179
+ *
180
+ * 🔴 THE MAPPING IS THE ROW'S ALONE, and nothing else has to be re-derived from it. The classifier's
181
+ * value reaches five places in `github.ts`, and TWO of them are behavioural: the `drop` return, the
182
+ * `githubAmbientLegAdmission` gate input, the seam facts' `isMention`, `directMention` — which is
183
+ * what the wake envelope and `singleWindowMentionPolicy` are built from — and this row. The gate
184
+ * input and `directMention` are the behavioural two, and NEITHER reads the PROMOTED value: both take
185
+ * the classifier's word directly, above this mapping. `github-supervisor.ts` derives the wake
186
+ * envelope's own `addressing`
187
+ * from `directMention` (`directMention ? "direct-mention" : "channel-watch"`), and the
188
+ * unified-window wake takes `singleWindowMentionPolicy(singleWindow, directMention, vouched)`.
189
+ * Neither reads this value. So the turn is judged the same, and the SILENCE BAR is what it always
190
+ * was, whatever this function returns.
191
+ *
192
+ * 🔴 A ROW THAT CANNOT TELL THEM APART REDS `J-ENGAGED-github` STEP (b) AGAINST A HEALTHY
193
+ * DEPLOYMENT. `gateEngagedWake` (packages/core/scripts/e2e/scenarios.ts) skips on an EMPTY dispatch
194
+ * read only, so a non-empty read carrying `channel-watch` is a hard gate failure, not an ignored
195
+ * value. Measured on deploy #203: the follow-up woke, dispatched and was ANSWERED (`reply.post`,
196
+ * 2026-09-12T20:27:25Z), and only the label disagreed. What the promotion DOES oblige is every
197
+ * reader that filtered the ambient leg by the literal `"channel-watch"` — `AMBIENT_LEG_ADDRESSINGS`
198
+ * (packages/core/scripts/e2e/engaged-rows.ts) is the pair such a reader takes.
199
+ *
200
+ * 🔴 IT REPORTS THE ENGAGED DOOR, AND THAT DOOR CARRIES A REACH FLOOR LINEAR'S DOES NOT. Linear's
201
+ * input is the bare seam verdict; GitHub's is `admission.engagedFollowUp`, which is
202
+ * `seam.kind === "deliver" && githubEngagedFollowUpReach(f).reachable`. So the promotion needs BOTH
203
+ * an enrolled container and a reachable one, and {@link githubEngagedFollowUpReach} refuses a public
204
+ * or unknown-privacy repo and an inline review comment. An engaged issue on a VOUCHED PUBLIC repo
205
+ * with ambient ON therefore still writes `channel-watch`: the classifier keeps that comment, but the
206
+ * engaged door is shut and the native leg would not have woken a turn for it either. That is the
207
+ * right answer — the row reports the door the delivery actually came through — and it is a narrower
208
+ * claim than Linear's, so do not read the two mappings as identical in reach.
209
+ *
210
+ * 🔴 IT REPORTS THE REGISTRY, NOT THE AMBIENT OPT-IN. `githubAmbientLegAdmission` resolves the seam
211
+ * for every kept comment, ABOVE the `githubAmbientEnabled` gate and independently of it, so on an
212
+ * ambient-ENABLED private repo an engaged issue's plain comment also reads `thread-follow` — the
213
+ * opt-in would have admitted it either way. The value therefore separates an engaged wake from an
214
+ * ambient one only where the domain has ambient OFF, which is the addressed-only posture the
215
+ * journeys drive.
216
+ *
217
+ * `engagedFollowUp` is the caller's own admission verdict, and it is the only door a plain comment
218
+ * has on a repo that never opted in. The `channel-watch` guard is belt-and-braces:
219
+ * `drop` returns before the row is written and can never reach here, and `direct` is an address
220
+ * rather than a follow-up.
221
+ */
222
+ export declare function githubWakeAddressing(addressing: GitHubAmbientAddressing, engagedFollowUp: boolean | undefined): GitHubAmbientAddressing | "thread-follow";
223
+ //# sourceMappingURL=github-engagement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-engagement.d.ts","sourceRoot":"","sources":["../../../agent/channels/github-engagement.ts"],"names":[],"mappings":"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"}