@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,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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"}
|