@geminixiang/mikan 1.0.0-beta.85 → 1.0.0-beta.86
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/CHANGELOG.md +36 -0
- package/README.md +8 -8
- package/dist/adapters/commands/session-view.js +1 -1
- package/dist/adapters/commands/session-view.js.map +1 -1
- package/dist/adapters/discord/bot.d.ts.map +1 -1
- package/dist/adapters/discord/bot.js +1 -8
- package/dist/adapters/discord/bot.js.map +1 -1
- package/dist/adapters/github/activity.d.ts +3 -0
- package/dist/adapters/github/activity.d.ts.map +1 -0
- package/dist/adapters/github/activity.js +154 -0
- package/dist/adapters/github/activity.js.map +1 -0
- package/dist/adapters/github/bot.d.ts +10 -20
- package/dist/adapters/github/bot.d.ts.map +1 -1
- package/dist/adapters/github/bot.js +119 -342
- package/dist/adapters/github/bot.js.map +1 -1
- package/dist/adapters/github/client.d.ts +9 -19
- package/dist/adapters/github/client.d.ts.map +1 -1
- package/dist/adapters/github/client.js +28 -104
- package/dist/adapters/github/client.js.map +1 -1
- package/dist/adapters/github/context.d.ts.map +1 -1
- package/dist/adapters/github/context.js +29 -20
- package/dist/adapters/github/context.js.map +1 -1
- package/dist/adapters/github/github-ops.d.ts +2 -10
- package/dist/adapters/github/github-ops.d.ts.map +1 -1
- package/dist/adapters/github/github-ops.js +28 -91
- package/dist/adapters/github/github-ops.js.map +1 -1
- package/dist/adapters/github/policy.d.ts +19 -0
- package/dist/adapters/github/policy.d.ts.map +1 -0
- package/dist/adapters/github/policy.js +79 -0
- package/dist/adapters/github/policy.js.map +1 -0
- package/dist/adapters/github/tool-pack.d.ts +2 -2
- package/dist/adapters/github/tool-pack.d.ts.map +1 -1
- package/dist/adapters/github/tool-pack.js +4 -9
- package/dist/adapters/github/tool-pack.js.map +1 -1
- package/dist/adapters/github/tools/checks.d.ts.map +1 -1
- package/dist/adapters/github/tools/checks.js +6 -8
- package/dist/adapters/github/tools/checks.js.map +1 -1
- package/dist/adapters/github/tools/pr.d.ts.map +1 -1
- package/dist/adapters/github/tools/pr.js +7 -9
- package/dist/adapters/github/tools/pr.js.map +1 -1
- package/dist/adapters/github/types.d.ts +66 -67
- package/dist/adapters/github/types.d.ts.map +1 -1
- package/dist/adapters/github/types.js.map +1 -1
- package/dist/adapters/github/webhook.d.ts +2 -1
- package/dist/adapters/github/webhook.d.ts.map +1 -1
- package/dist/adapters/github/webhook.js +46 -4
- package/dist/adapters/github/webhook.js.map +1 -1
- package/dist/adapters/progressive-renderer.d.ts.map +1 -1
- package/dist/adapters/progressive-renderer.js +3 -0
- package/dist/adapters/progressive-renderer.js.map +1 -1
- package/dist/adapters/shared.d.ts +3 -1
- package/dist/adapters/shared.d.ts.map +1 -1
- package/dist/adapters/shared.js +45 -23
- package/dist/adapters/shared.js.map +1 -1
- package/dist/adapters/slack/blocks.d.ts.map +1 -1
- package/dist/adapters/slack/blocks.js +65 -2
- package/dist/adapters/slack/blocks.js.map +1 -1
- package/dist/adapters/slack/bot.d.ts +3 -0
- package/dist/adapters/slack/bot.d.ts.map +1 -1
- package/dist/adapters/slack/bot.js +76 -34
- package/dist/adapters/slack/bot.js.map +1 -1
- package/dist/adapters/slack/jev-context.js +2 -2
- package/dist/adapters/slack/jev-context.js.map +1 -1
- package/dist/adapters/slack/task-status.d.ts +3 -2
- package/dist/adapters/slack/task-status.d.ts.map +1 -1
- package/dist/adapters/slack/task-status.js +12 -15
- package/dist/adapters/slack/task-status.js.map +1 -1
- package/dist/adapters/telegram/bot.d.ts.map +1 -1
- package/dist/adapters/telegram/bot.js +1 -8
- package/dist/adapters/telegram/bot.js.map +1 -1
- package/dist/adapters/web/admin/portal.js +1 -1
- package/dist/adapters/web/admin/portal.js.map +1 -1
- package/dist/adapters/web/admin/provider-models.d.ts.map +1 -1
- package/dist/adapters/web/admin/provider-models.js +10 -3
- package/dist/adapters/web/admin/provider-models.js.map +1 -1
- package/dist/adapters/web/login/portal.js +6 -1
- package/dist/adapters/web/login/portal.js.map +1 -1
- package/dist/adapters/web/server.d.ts.map +1 -1
- package/dist/adapters/web/server.js +5 -3
- package/dist/adapters/web/server.js.map +1 -1
- package/dist/adapters/web/session-view/portal.d.ts +2 -1
- package/dist/adapters/web/session-view/portal.d.ts.map +1 -1
- package/dist/adapters/web/session-view/portal.js +18 -27
- package/dist/adapters/web/session-view/portal.js.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +1 -2
- package/dist/cli/migrate.js.map +1 -1
- package/dist/cli/onboard.d.ts +0 -1
- package/dist/cli/onboard.d.ts.map +1 -1
- package/dist/cli/onboard.js +1 -7
- package/dist/cli/onboard.js.map +1 -1
- package/dist/env-manifest.d.ts.map +1 -1
- package/dist/env-manifest.js +24 -22
- package/dist/env-manifest.js.map +1 -1
- package/dist/events/index.d.ts +1 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +11 -0
- package/dist/events/index.js.map +1 -1
- package/dist/events/scheduler.d.ts.map +1 -1
- package/dist/events/scheduler.js +3 -0
- package/dist/events/scheduler.js.map +1 -1
- package/dist/file-guards.d.ts +7 -0
- package/dist/file-guards.d.ts.map +1 -1
- package/dist/file-guards.js +63 -1
- package/dist/file-guards.js.map +1 -1
- package/dist/harness/mcp.d.ts.map +1 -1
- package/dist/harness/mcp.js +75 -31
- package/dist/harness/mcp.js.map +1 -1
- package/dist/harness/models.js +2 -2
- package/dist/harness/models.js.map +1 -1
- package/dist/harness/open-connector.d.ts.map +1 -1
- package/dist/harness/open-connector.js +14 -7
- package/dist/harness/open-connector.js.map +1 -1
- package/dist/harness/presenter.d.ts.map +1 -1
- package/dist/harness/presenter.js +2 -0
- package/dist/harness/presenter.js.map +1 -1
- package/dist/harness/prompt.d.ts +1 -1
- package/dist/harness/prompt.d.ts.map +1 -1
- package/dist/harness/prompt.js +13 -31
- package/dist/harness/prompt.js.map +1 -1
- package/dist/harness/runner.d.ts.map +1 -1
- package/dist/harness/runner.js +6 -9
- package/dist/harness/runner.js.map +1 -1
- package/dist/harness/session.d.ts.map +1 -1
- package/dist/harness/session.js +6 -0
- package/dist/harness/session.js.map +1 -1
- package/dist/harness/tools/event.d.ts.map +1 -1
- package/dist/harness/tools/event.js +19 -2
- package/dist/harness/tools/event.js.map +1 -1
- package/dist/harness/tools/generate-image.d.ts.map +1 -1
- package/dist/harness/tools/generate-image.js +17 -3
- package/dist/harness/tools/generate-image.js.map +1 -1
- package/dist/harness/tools/jev-browser.d.ts +1 -2
- package/dist/harness/tools/jev-browser.d.ts.map +1 -1
- package/dist/harness/tools/jev-browser.js +75 -50
- package/dist/harness/tools/jev-browser.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/main.js +34 -45
- package/dist/main.js.map +1 -1
- package/dist/memory-capture/index.d.ts.map +1 -1
- package/dist/memory-capture/index.js +3 -3
- package/dist/memory-capture/index.js.map +1 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/migrations/index.js +2 -0
- package/dist/migrations/index.js.map +1 -1
- package/dist/migrations/office-sessions.d.ts +3 -0
- package/dist/migrations/office-sessions.d.ts.map +1 -0
- package/dist/migrations/office-sessions.js +70 -0
- package/dist/migrations/office-sessions.js.map +1 -0
- package/dist/office/index.d.ts +0 -1
- package/dist/office/index.d.ts.map +1 -1
- package/dist/office/index.js +3 -5
- package/dist/office/index.js.map +1 -1
- package/dist/runtime/conversation-runtime.js +12 -12
- package/dist/runtime/conversation-runtime.js.map +1 -1
- package/dist/sandbox/provisioner.d.ts +1 -1
- package/dist/sandbox/provisioner.d.ts.map +1 -1
- package/dist/sandbox/provisioner.js +20 -5
- package/dist/sandbox/provisioner.js.map +1 -1
- package/dist/sessions/chat-history-sync.d.ts.map +1 -1
- package/dist/sessions/chat-history-sync.js +28 -40
- package/dist/sessions/chat-history-sync.js.map +1 -1
- package/dist/sessions/store.d.ts +2 -3
- package/dist/sessions/store.d.ts.map +1 -1
- package/dist/sessions/store.js +8 -14
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/types.d.ts +6 -5
- package/dist/sessions/types.d.ts.map +1 -1
- package/dist/sessions/types.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +8 -6
- package/dist/adapters/github/repo.d.ts +0 -8
- package/dist/adapters/github/repo.d.ts.map +0 -1
- package/dist/adapters/github/repo.js +0 -92
- package/dist/adapters/github/repo.js.map +0 -1
- package/dist/adapters/github/tools/sync.d.ts +0 -13
- package/dist/adapters/github/tools/sync.d.ts.map +0 -1
- package/dist/adapters/github/tools/sync.js +0 -29
- package/dist/adapters/github/tools/sync.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/bot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"bot.d.ts","sourceRoot":"","sources":["../../../src/adapters/github/bot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,YAAY,EACZ,qBAAqB,EACrB,aAAa,EACb,iBAAiB,EAClB,MAAM,gBAAgB,CAAC;AAaxB,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAQ5C,OAAO,KAAK,EAAU,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAE3E,OAAO,KAAK,EAEV,mBAAmB,EACnB,SAAS,EACT,eAAe,EACf,gBAAgB,EAMhB,qBAAqB,EACtB,MAAM,YAAY,CAAC;AAOpB,eAAO,MAAM,wBAAwB,YAAa,MAAM,KAAG,MAAoC,CAAC;AA+ChG,qBAAa,kBAAmB,YAAW,YAAY;IACrD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAY;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC;IACxB,OAAO,CAAC,QAAQ,CAAoC;IACpD,OAAO,CAAC,MAAM,CAA0C;IACxD,OAAO,CAAC,eAAe,CAA8D;IACrF,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,OAAO,CAAQ;IAEvB,YAAY,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,SAAS,EAKtF;IAED,IAAI,YAAY,IAAI,WAAW,CAAC,gBAAgB,CAAC,CAEhD;IAED,IAAI,aAAa,IAAI,mBAAmB,GAAG,SAAS,CAEnD;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAW3B;IAEK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1B;IAEK,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAQhE;IAEK,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAG5E;IAEK,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBlF;IAED,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAM9C;IAED,gBAAgB,IAAI,aAAa,CAWhC;IAEK,WAAW,CAAC,GAAG,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK3E;IAEK,aAAa,CAAC,GAAG,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhF;IAED,SAAS,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAErD;IAED,cAAc,CAAC,cAAc,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,IAAI,CAErE;IAED,OAAO,CAAC,QAAQ;IASV,OAAO,CAAC,QAAQ,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5D;IAED,OAAO,CAAC,OAAO;IAIf,OAAO,CAAC,SAAS;IAiBjB,OAAO,CAAC,gBAAgB;YAUV,cAAc;IAiC5B,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,MAAM;IAId,OAAO,CAAC,eAAe;YAIT,oBAAoB;YAwBpB,cAAc;YA0Dd,mBAAmB;YAwBnB,mBAAmB;YA+BnB,eAAe;CA2B9B"}
|
|
@@ -1,44 +1,18 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { dirname } from "node:path";
|
|
3
|
-
import { Type } from "typebox";
|
|
4
2
|
import * as log from "../../log.js";
|
|
5
|
-
import { ensureDirExists, readJsonSchemaFileIfExists } from "../../file-guards.js";
|
|
6
|
-
import { atomicWritePrivateFile } from "../../file-guards.js";
|
|
7
3
|
import { resolveChatSessionKey } from "../../sessions/session-key.js";
|
|
8
4
|
import { appendBotResponseLog, appendChannelLog, MessagingEventQueue, splitText, } from "../shared.js";
|
|
9
5
|
import { matchMagicWord, processMessageIntake } from "../intake.js";
|
|
6
|
+
import { readGithubActivity } from "./activity.js";
|
|
10
7
|
import { GithubClient, GITHUB_MAX_COMMENT_LENGTH, githubRetry } from "./client.js";
|
|
11
8
|
import { createGithubAdapters } from "./context.js";
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
9
|
+
import { GithubOps } from "./github-ops.js";
|
|
10
|
+
import { permissionMeets, repoIsAllowed, userIsAllowed } from "./policy.js";
|
|
14
11
|
import { buildGithubConversationId, createConversationEvent, createOfficeAddress, parseGithubConversationId, } from "../../office/index.js";
|
|
15
12
|
import { GITHUB_ISSUE_BODY_TS, githubReviewCommentTs, parseReviewCommentTs } from "./ids.js";
|
|
16
13
|
import { errorMessage } from "../../unknown-values.js";
|
|
17
|
-
const SyncStateSchema = Type.Object({
|
|
18
|
-
repos: Type.Record(Type.String(), Type.Object({
|
|
19
|
-
baseline: Type.String(),
|
|
20
|
-
cursor: Type.String(),
|
|
21
|
-
seenComments: Type.Array(Type.Number()),
|
|
22
|
-
seenIssues: Type.Array(Type.Number()),
|
|
23
|
-
seenReviewComments: Type.Optional(Type.Array(Type.Number())),
|
|
24
|
-
})),
|
|
25
|
-
});
|
|
26
|
-
const POLL_OVERLAP_MS = 5 * 60 * 1000;
|
|
27
|
-
const MAX_SEEN_IDS = 5000;
|
|
28
|
-
const REQUEST_POLL_DEBOUNCE_MS = 1500;
|
|
29
|
-
const PERMISSION_RANK = {
|
|
30
|
-
none: 0,
|
|
31
|
-
read: 1,
|
|
32
|
-
triage: 2,
|
|
33
|
-
write: 3,
|
|
34
|
-
maintain: 4,
|
|
35
|
-
admin: 5,
|
|
36
|
-
};
|
|
37
|
-
const REQUIRED_TRIGGER_RANK = PERMISSION_RANK.write;
|
|
38
|
-
function rankOfPermission(name) {
|
|
39
|
-
return PERMISSION_RANK[name] ?? 0;
|
|
40
|
-
}
|
|
41
14
|
const PERMISSION_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
15
|
+
const BODY_TRIGGER_WINDOW_MS = 10 * 60 * 1000;
|
|
42
16
|
export const formatGithubContinuation = (partNum) => `*(continued ${partNum})*`;
|
|
43
17
|
const GITHUB_REACTIONS = {
|
|
44
18
|
"+1": "+1",
|
|
@@ -55,137 +29,54 @@ const GITHUB_REACTIONS = {
|
|
|
55
29
|
eyes: "eyes",
|
|
56
30
|
saluting_face: "eyes",
|
|
57
31
|
};
|
|
58
|
-
function parseRepoList(repos) {
|
|
59
|
-
return repos.map((entry) => {
|
|
60
|
-
const [owner, repo, ...rest] = entry.split("/");
|
|
61
|
-
if (!owner || !repo || rest.length > 0) {
|
|
62
|
-
throw new Error(`Invalid GITHUB_REPOS entry (expected owner/repo): ${entry}`);
|
|
63
|
-
}
|
|
64
|
-
return { owner: owner.toLowerCase(), repo: repo.toLowerCase() };
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
function pruneSeen(seen) {
|
|
68
|
-
if (seen.size <= MAX_SEEN_IDS)
|
|
69
|
-
return;
|
|
70
|
-
let toDrop = seen.size - MAX_SEEN_IDS / 2;
|
|
71
|
-
for (const id of seen) {
|
|
72
|
-
if (toDrop-- <= 0)
|
|
73
|
-
break;
|
|
74
|
-
seen.delete(id);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function issueNumberFromUrl(issueUrl) {
|
|
78
|
-
const match = /\/issues\/(\d+)$/.exec(issueUrl);
|
|
79
|
-
if (!match) {
|
|
80
|
-
throw new Error(`Cannot parse issue number from ${issueUrl}`);
|
|
81
|
-
}
|
|
82
|
-
return Number(match[1]);
|
|
83
|
-
}
|
|
84
|
-
function prNumberFromUrl(pullRequestUrl) {
|
|
85
|
-
const match = /\/pulls\/(\d+)$/.exec(pullRequestUrl);
|
|
86
|
-
if (!match) {
|
|
87
|
-
throw new Error(`Cannot parse PR number from ${pullRequestUrl}`);
|
|
88
|
-
}
|
|
89
|
-
return Number(match[1]);
|
|
90
|
-
}
|
|
91
32
|
const MAX_DIFF_HUNK_CHARS = 1500;
|
|
92
33
|
const MAX_THREAD_TURNS = 10;
|
|
93
34
|
const MAX_THREAD_TURN_CHARS = 500;
|
|
35
|
+
function bodyTriggerNote(activity) {
|
|
36
|
+
switch (activity.kind) {
|
|
37
|
+
case "assigned":
|
|
38
|
+
return `[Assigned to you by @${activity.sender.login}]`;
|
|
39
|
+
case "review_requested":
|
|
40
|
+
return `[Review requested by @${activity.sender.login}]`;
|
|
41
|
+
case "opened":
|
|
42
|
+
case "comment":
|
|
43
|
+
case "review_comment":
|
|
44
|
+
return null;
|
|
45
|
+
default:
|
|
46
|
+
return activity.kind;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
94
49
|
export class GithubMessagingBot {
|
|
95
50
|
constructor(handler, config, client) {
|
|
96
|
-
this.
|
|
97
|
-
this.botEmail = null;
|
|
98
|
-
this.watchedRepos = [];
|
|
51
|
+
this.identity = null;
|
|
99
52
|
this.queues = new Map();
|
|
100
|
-
this.repoState = new Map();
|
|
101
53
|
this.permissionCache = new Map();
|
|
54
|
+
this.bodyTriggers = new Map();
|
|
102
55
|
this.stopped = true;
|
|
103
|
-
this.stopping = false;
|
|
104
|
-
this.activePoll = null;
|
|
105
|
-
this.pollPending = false;
|
|
106
|
-
this.pollIntervalTimer = null;
|
|
107
|
-
this.requestPollTimer = null;
|
|
108
56
|
this.handler = handler;
|
|
109
57
|
this.config = config;
|
|
110
|
-
this.client =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
58
|
+
this.client = client ?? new GithubClient({ token: config.token });
|
|
59
|
+
this.ops = new GithubOps(this.client);
|
|
60
|
+
}
|
|
61
|
+
get capabilities() {
|
|
62
|
+
return this.config.policy.capabilities;
|
|
63
|
+
}
|
|
64
|
+
get agentIdentity() {
|
|
65
|
+
return this.identity ?? undefined;
|
|
118
66
|
}
|
|
119
67
|
async start() {
|
|
68
|
+
const user = await this.client.getAuthenticatedUser();
|
|
69
|
+
this.identity = {
|
|
70
|
+
login: user.login,
|
|
71
|
+
email: `${user.id}+${user.login}@users.noreply.github.com`,
|
|
72
|
+
};
|
|
120
73
|
this.stopped = false;
|
|
121
|
-
this.stopping = false;
|
|
122
|
-
this.appSlug = await this.client.getAppSlug();
|
|
123
|
-
try {
|
|
124
|
-
const botUserId = await this.client.getUserId(`${this.appSlug}[bot]`);
|
|
125
|
-
this.botEmail = `${botUserId}+${this.appSlug}[bot]@users.noreply.github.com`;
|
|
126
|
-
}
|
|
127
|
-
catch {
|
|
128
|
-
this.botEmail = `${this.appSlug}[bot]@users.noreply.github.com`;
|
|
129
|
-
}
|
|
130
|
-
this.watchedRepos =
|
|
131
|
-
this.config.repos.length > 0
|
|
132
|
-
? parseRepoList(this.config.repos)
|
|
133
|
-
: (await this.client.listInstallationRepositories()).map((repository) => ({
|
|
134
|
-
owner: repository.owner.login.toLowerCase(),
|
|
135
|
-
repo: repository.name.toLowerCase(),
|
|
136
|
-
}));
|
|
137
|
-
if (this.watchedRepos.length === 0) {
|
|
138
|
-
log.logWarning("GitHub: installation has no repositories; nothing to poll");
|
|
139
|
-
}
|
|
140
|
-
const persisted = this.loadSyncState();
|
|
141
|
-
const now = new Date().toISOString();
|
|
142
|
-
for (const repo of this.watchedRepos) {
|
|
143
|
-
const repoKey = `${repo.owner}/${repo.repo}`;
|
|
144
|
-
const saved = persisted?.repos[repoKey];
|
|
145
|
-
this.repoState.set(repoKey, saved
|
|
146
|
-
? {
|
|
147
|
-
baseline: saved.baseline,
|
|
148
|
-
cursor: saved.cursor,
|
|
149
|
-
seenComments: new Set(saved.seenComments),
|
|
150
|
-
seenIssues: new Set(saved.seenIssues),
|
|
151
|
-
seenReviewComments: new Set(saved.seenReviewComments ?? []),
|
|
152
|
-
}
|
|
153
|
-
: {
|
|
154
|
-
baseline: now,
|
|
155
|
-
cursor: now,
|
|
156
|
-
seenComments: new Set(),
|
|
157
|
-
seenIssues: new Set(),
|
|
158
|
-
seenReviewComments: new Set(),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
this.persistSyncState();
|
|
162
|
-
if (this.stopping)
|
|
163
|
-
return;
|
|
164
|
-
this.stopped = false;
|
|
165
|
-
this.pollIntervalTimer = setInterval(() => {
|
|
166
|
-
void this.poll();
|
|
167
|
-
}, this.config.pollIntervalMs);
|
|
168
|
-
this.pollIntervalTimer.unref();
|
|
169
74
|
log.logConnected("GitHub");
|
|
170
|
-
log.logInfo(`GitHub bot started as @${
|
|
75
|
+
log.logInfo(`GitHub bot started as @${user.login}, answering in ${this.config.policy.repos.join(", ")}`);
|
|
171
76
|
}
|
|
172
77
|
async stop() {
|
|
173
|
-
this.
|
|
174
|
-
this.
|
|
175
|
-
if (this.pollIntervalTimer)
|
|
176
|
-
clearInterval(this.pollIntervalTimer);
|
|
177
|
-
if (this.requestPollTimer)
|
|
178
|
-
clearTimeout(this.requestPollTimer);
|
|
179
|
-
this.pollIntervalTimer = null;
|
|
180
|
-
this.requestPollTimer = null;
|
|
181
|
-
try {
|
|
182
|
-
if (this.activePoll)
|
|
183
|
-
await this.activePoll;
|
|
184
|
-
}
|
|
185
|
-
finally {
|
|
186
|
-
this.stopped = true;
|
|
187
|
-
await Promise.all([...this.queues.values()].map((queue) => queue.close()));
|
|
188
|
-
}
|
|
78
|
+
this.stopped = true;
|
|
79
|
+
await Promise.all([...this.queues.values()].map((queue) => queue.close()));
|
|
189
80
|
}
|
|
190
81
|
async postMessage(channel, text) {
|
|
191
82
|
const ref = parseGithubConversationId(channel);
|
|
@@ -220,14 +111,7 @@ export class GithubMessagingBot {
|
|
|
220
111
|
enqueueEvent(event) {
|
|
221
112
|
if (this.stopped)
|
|
222
113
|
return false;
|
|
223
|
-
|
|
224
|
-
const queue = this.getQueue(conversationId);
|
|
225
|
-
if (queue.size() >= 5) {
|
|
226
|
-
log.logWarning(`Event queue full for ${conversationId}, discarding: ${event.text.substring(0, 50)}`);
|
|
227
|
-
return false;
|
|
228
|
-
}
|
|
229
|
-
log.logInfo(`Enqueueing event for ${conversationId}: ${event.text.substring(0, 50)}`);
|
|
230
|
-
return queue.enqueue(() => {
|
|
114
|
+
return this.getQueue(event.address.conversationId).offerEvent(event, () => {
|
|
231
115
|
const context = createGithubAdapters(event, this);
|
|
232
116
|
return this.handler.handleEvent(event, this, context);
|
|
233
117
|
});
|
|
@@ -235,7 +119,7 @@ export class GithubMessagingBot {
|
|
|
235
119
|
getMessagingInfo() {
|
|
236
120
|
return {
|
|
237
121
|
name: "github",
|
|
238
|
-
trustModel: "
|
|
122
|
+
trustModel: "membership",
|
|
239
123
|
formattingGuide: "## GitHub Formatting (GitHub Flavored Markdown)\n" +
|
|
240
124
|
"Standard Markdown plus tables, task lists, fenced code blocks, and ```suggestion blocks.\n" +
|
|
241
125
|
"Reference issues/PRs as #123 and users as @login (mentions notify people — use sparingly).",
|
|
@@ -264,147 +148,85 @@ export class GithubMessagingBot {
|
|
|
264
148
|
}
|
|
265
149
|
return queue;
|
|
266
150
|
}
|
|
267
|
-
|
|
268
|
-
if (this.stopped || this.
|
|
151
|
+
async receive(delivery) {
|
|
152
|
+
if (this.stopped || !this.identity)
|
|
269
153
|
return;
|
|
270
|
-
|
|
271
|
-
|
|
154
|
+
const activity = readGithubActivity(delivery);
|
|
155
|
+
if (!activity)
|
|
272
156
|
return;
|
|
273
|
-
}
|
|
274
|
-
if (this.requestPollTimer)
|
|
275
|
-
return;
|
|
276
|
-
this.requestPollTimer = setTimeout(() => {
|
|
277
|
-
this.requestPollTimer = null;
|
|
278
|
-
void this.poll();
|
|
279
|
-
}, debounceMs);
|
|
280
|
-
this.requestPollTimer.unref();
|
|
281
|
-
}
|
|
282
|
-
async poll() {
|
|
283
|
-
if (this.stopped || this.stopping)
|
|
284
|
-
return;
|
|
285
|
-
if (this.activePoll) {
|
|
286
|
-
this.pollPending = true;
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const activePoll = this.pollWatchedRepos();
|
|
290
|
-
this.activePoll = activePoll;
|
|
291
157
|
try {
|
|
292
|
-
await
|
|
293
|
-
}
|
|
294
|
-
finally {
|
|
295
|
-
this.activePoll = null;
|
|
296
|
-
if (this.pollPending) {
|
|
297
|
-
this.pollPending = false;
|
|
298
|
-
this.requestPoll();
|
|
299
|
-
}
|
|
158
|
+
await this.handleActivity(activity);
|
|
300
159
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
160
|
+
catch (err) {
|
|
161
|
+
log.logWarning(`GitHub: failed to handle ${delivery.event} for ${activity.repo.owner}/${activity.repo.repo}#${activity.number}`, errorMessage(err));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
isAgent(login) {
|
|
165
|
+
return login.toLowerCase() === this.identity?.login.toLowerCase();
|
|
166
|
+
}
|
|
167
|
+
triggerOf(activity, conversationId) {
|
|
168
|
+
switch (activity.kind) {
|
|
169
|
+
case "assigned":
|
|
170
|
+
return activity.target !== null && this.isAgent(activity.target) ? "assign" : null;
|
|
171
|
+
case "review_requested":
|
|
172
|
+
return activity.target !== null && this.isAgent(activity.target) ? "review" : null;
|
|
173
|
+
case "opened":
|
|
174
|
+
return this.isMentioned(activity.text) ? "mention" : null;
|
|
175
|
+
case "comment":
|
|
176
|
+
case "review_comment":
|
|
177
|
+
if (this.isMentioned(activity.text))
|
|
178
|
+
return "mention";
|
|
179
|
+
return this.isParticipating(conversationId) ? "followup" : null;
|
|
180
|
+
default:
|
|
181
|
+
return activity.kind;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
claimBodyTrigger(conversationId) {
|
|
185
|
+
const now = Date.now();
|
|
186
|
+
for (const [key, expiresAt] of this.bodyTriggers) {
|
|
187
|
+
if (expiresAt <= now)
|
|
188
|
+
this.bodyTriggers.delete(key);
|
|
189
|
+
}
|
|
190
|
+
if (this.bodyTriggers.has(conversationId))
|
|
319
191
|
return false;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const issues = (await this.client.listIssuesSince(repo.owner, repo.repo, since)) ?? [];
|
|
323
|
-
changed =
|
|
324
|
-
(await this.advanceFeed(state, state.seenIssues, issues, (issue) => ({
|
|
325
|
-
ref: { ...repo, number: issue.number },
|
|
326
|
-
ts: GITHUB_ISSUE_BODY_TS,
|
|
327
|
-
user: issue.user.login,
|
|
328
|
-
text: `# ${issue.title}\n\n${issue.body ?? ""}`.trim(),
|
|
329
|
-
createdAt: issue.created_at,
|
|
330
|
-
isPr: Boolean(issue.pull_request),
|
|
331
|
-
}))) || changed;
|
|
332
|
-
const comments = (await this.client.listIssueCommentsSince(repo.owner, repo.repo, since)) ?? [];
|
|
333
|
-
changed =
|
|
334
|
-
(await this.advanceFeed(state, state.seenComments, comments, (comment) => ({
|
|
335
|
-
ref: { ...repo, number: issueNumberFromUrl(comment.issue_url) },
|
|
336
|
-
ts: String(comment.id),
|
|
337
|
-
user: comment.user.login,
|
|
338
|
-
text: comment.body,
|
|
339
|
-
createdAt: comment.created_at,
|
|
340
|
-
}))) || changed;
|
|
341
|
-
const reviewComments = (await this.client.listPullReviewCommentsSince(repo.owner, repo.repo, since)) ?? [];
|
|
342
|
-
changed =
|
|
343
|
-
(await this.advanceFeed(state, state.seenReviewComments, reviewComments, (comment) => ({
|
|
344
|
-
ref: { ...repo, number: prNumberFromUrl(comment.pull_request_url) },
|
|
345
|
-
ts: githubReviewCommentTs(comment.id),
|
|
346
|
-
user: comment.user.login,
|
|
347
|
-
text: comment.body,
|
|
348
|
-
createdAt: comment.created_at,
|
|
349
|
-
isPr: true,
|
|
350
|
-
review: {
|
|
351
|
-
commentId: comment.id,
|
|
352
|
-
path: comment.path,
|
|
353
|
-
line: comment.line,
|
|
354
|
-
diffHunk: comment.diff_hunk,
|
|
355
|
-
inReplyToId: comment.in_reply_to_id,
|
|
356
|
-
},
|
|
357
|
-
}))) || changed;
|
|
358
|
-
pruneSeen(state.seenComments);
|
|
359
|
-
pruneSeen(state.seenIssues);
|
|
360
|
-
pruneSeen(state.seenReviewComments);
|
|
361
|
-
return changed;
|
|
192
|
+
this.bodyTriggers.set(conversationId, now + BODY_TRIGGER_WINDOW_MS);
|
|
193
|
+
return true;
|
|
362
194
|
}
|
|
363
|
-
async
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
378
|
-
return changed;
|
|
379
|
-
}
|
|
380
|
-
loadSyncState() {
|
|
381
|
-
try {
|
|
382
|
-
return readJsonSchemaFileIfExists(this.config.syncStatePath, SyncStateSchema, (detail) => `Malformed GitHub sync state at ${this.config.syncStatePath}: ${detail}`);
|
|
383
|
-
}
|
|
384
|
-
catch (err) {
|
|
385
|
-
log.logWarning("GitHub: ignoring unreadable sync state", errorMessage(err));
|
|
386
|
-
return undefined;
|
|
195
|
+
async handleActivity(activity) {
|
|
196
|
+
const { policy } = this.config;
|
|
197
|
+
if (activity.sender.isBot || this.isAgent(activity.sender.login))
|
|
198
|
+
return;
|
|
199
|
+
if (!repoIsAllowed(policy, activity.repo))
|
|
200
|
+
return;
|
|
201
|
+
const ref = { owner: activity.repo.owner, repo: activity.repo.repo, number: activity.number };
|
|
202
|
+
const conversationId = buildGithubConversationId(ref);
|
|
203
|
+
const trigger = this.triggerOf(activity, conversationId);
|
|
204
|
+
if (trigger === null || !policy.triggers.has(trigger))
|
|
205
|
+
return;
|
|
206
|
+
if (!userIsAllowed(policy, activity.sender.login)) {
|
|
207
|
+
log.logInfo(`GitHub: ignoring ${conversationId} from ${activity.sender.login} (not in GITHUB_USERS)`);
|
|
208
|
+
return;
|
|
387
209
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
for (const [repoKey, watermark] of this.repoState) {
|
|
392
|
-
state.repos[repoKey] = {
|
|
393
|
-
baseline: watermark.baseline,
|
|
394
|
-
cursor: watermark.cursor,
|
|
395
|
-
seenComments: [...watermark.seenComments],
|
|
396
|
-
seenIssues: [...watermark.seenIssues],
|
|
397
|
-
seenReviewComments: [...watermark.seenReviewComments],
|
|
398
|
-
};
|
|
210
|
+
if (!(await this.hasTriggerPermission(ref, activity.sender.login))) {
|
|
211
|
+
log.logInfo(`GitHub: ignoring ${conversationId} from ${activity.sender.login} (below ${policy.minPermission} permission)`);
|
|
212
|
+
return;
|
|
399
213
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
214
|
+
if (activity.ts === GITHUB_ISSUE_BODY_TS && !this.claimBodyTrigger(conversationId))
|
|
215
|
+
return;
|
|
216
|
+
const note = bodyTriggerNote(activity);
|
|
217
|
+
await this.handleIncoming({
|
|
218
|
+
ref,
|
|
219
|
+
ts: activity.ts,
|
|
220
|
+
user: activity.sender.login,
|
|
221
|
+
text: note ? `${note}\n\n${activity.text}` : activity.text,
|
|
222
|
+
createdAt: activity.createdAt,
|
|
223
|
+
addressed: trigger !== "followup",
|
|
224
|
+
review: activity.review,
|
|
225
|
+
});
|
|
405
226
|
}
|
|
406
227
|
mentionPattern() {
|
|
407
|
-
|
|
228
|
+
const login = this.identity?.login;
|
|
229
|
+
return login ? new RegExp(`@${login}(?![\\w-])`, "gi") : null;
|
|
408
230
|
}
|
|
409
231
|
isMentioned(text) {
|
|
410
232
|
const pattern = this.mentionPattern();
|
|
@@ -421,36 +243,28 @@ export class GithubMessagingBot {
|
|
|
421
243
|
return existsSync(this.office(conversationId).logPath);
|
|
422
244
|
}
|
|
423
245
|
async hasTriggerPermission(ref, user) {
|
|
424
|
-
const cacheKey = `${ref.owner}/${ref.repo}#${user}`;
|
|
246
|
+
const cacheKey = `${ref.owner}/${ref.repo}#${user.toLowerCase()}`;
|
|
425
247
|
const cached = this.permissionCache.get(cacheKey);
|
|
426
|
-
if (cached && Date.now() < cached.expiresAt)
|
|
427
|
-
return cached.
|
|
428
|
-
|
|
429
|
-
let rank;
|
|
248
|
+
if (cached && Date.now() < cached.expiresAt)
|
|
249
|
+
return cached.allowed;
|
|
250
|
+
let allowed;
|
|
430
251
|
try {
|
|
431
252
|
const role = await githubRetry(() => this.client.getCollaboratorPermission(ref.owner, ref.repo, user));
|
|
432
|
-
|
|
253
|
+
allowed = permissionMeets(this.config.policy, role);
|
|
433
254
|
}
|
|
434
255
|
catch (err) {
|
|
435
256
|
log.logWarning(`GitHub: permission lookup failed for ${user} on ${ref.owner}/${ref.repo}; denying trigger`, errorMessage(err));
|
|
436
257
|
return false;
|
|
437
258
|
}
|
|
438
259
|
this.permissionCache.set(cacheKey, {
|
|
439
|
-
|
|
260
|
+
allowed,
|
|
440
261
|
expiresAt: Date.now() + PERMISSION_CACHE_TTL_MS,
|
|
441
262
|
});
|
|
442
|
-
return
|
|
263
|
+
return allowed;
|
|
443
264
|
}
|
|
444
265
|
async handleIncoming(item) {
|
|
445
266
|
const conversationId = buildGithubConversationId(item.ref);
|
|
446
|
-
const mentioned = this.isMentioned(item.text);
|
|
447
267
|
const participating = this.isParticipating(conversationId);
|
|
448
|
-
if (!mentioned && !participating)
|
|
449
|
-
return;
|
|
450
|
-
if (!(await this.hasTriggerPermission(item.ref, item.user))) {
|
|
451
|
-
log.logInfo(`GitHub: ignoring ${conversationId} comment from ${item.user} (below write permission)`);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
268
|
const cleanedText = this.stripMention(item.text);
|
|
455
269
|
const sessionKey = resolveChatSessionKey({
|
|
456
270
|
conversationId,
|
|
@@ -475,7 +289,7 @@ export class GithubMessagingBot {
|
|
|
475
289
|
await processMessageIntake({
|
|
476
290
|
eventBase,
|
|
477
291
|
addressed: true,
|
|
478
|
-
magicWord: { text: cleanedText, addressed:
|
|
292
|
+
magicWord: { text: cleanedText, addressed: item.addressed, scopeFallback: "never" },
|
|
479
293
|
busyPolicy: "queue",
|
|
480
294
|
logEntryBase: {
|
|
481
295
|
date: item.createdAt,
|
|
@@ -491,7 +305,9 @@ export class GithubMessagingBot {
|
|
|
491
305
|
this.logToFile(conversationId, entry);
|
|
492
306
|
},
|
|
493
307
|
processAttachments: async () => {
|
|
494
|
-
|
|
308
|
+
if (!participating && item.ts !== GITHUB_ISSUE_BODY_TS) {
|
|
309
|
+
await this.logIssueContext(item.ref, conversationId, item.createdAt);
|
|
310
|
+
}
|
|
495
311
|
return [];
|
|
496
312
|
},
|
|
497
313
|
queueKey: conversationId,
|
|
@@ -501,18 +317,6 @@ export class GithubMessagingBot {
|
|
|
501
317
|
createContext: (event) => createGithubAdapters(event, this),
|
|
502
318
|
});
|
|
503
319
|
}
|
|
504
|
-
async prepareConversation(item, conversationId, participating) {
|
|
505
|
-
let isPrHint = item.isPr;
|
|
506
|
-
if (!participating && item.ts !== GITHUB_ISSUE_BODY_TS) {
|
|
507
|
-
const issue = await this.logIssueContext(item.ref, conversationId, item.createdAt);
|
|
508
|
-
if (issue && isPrHint === undefined)
|
|
509
|
-
isPrHint = Boolean(issue.pull_request);
|
|
510
|
-
}
|
|
511
|
-
if (!existsSync(conversationRepoDir(this.office(conversationId)))) {
|
|
512
|
-
const isPr = isPrHint ?? (await fetchIsPr(this.client, item.ref));
|
|
513
|
-
await this.ensureRepoClone(item.ref, conversationId, isPr);
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
320
|
async formatReviewMessage(ref, review, cleanedText) {
|
|
517
321
|
const location = review.line !== null ? `${review.path}:${review.line}` : review.path;
|
|
518
322
|
const parts = [`[PR review comment ${githubReviewCommentTs(review.commentId)} on ${location}]`];
|
|
@@ -556,7 +360,7 @@ export class GithubMessagingBot {
|
|
|
556
360
|
}
|
|
557
361
|
catch (err) {
|
|
558
362
|
log.logWarning(`GitHub: could not fetch issue context for ${conversationId}`, errorMessage(err));
|
|
559
|
-
return
|
|
363
|
+
return;
|
|
560
364
|
}
|
|
561
365
|
const triggerMs = Date.parse(triggerCreatedAt);
|
|
562
366
|
const contextDate = new Date((Number.isFinite(triggerMs) ? triggerMs : Date.now()) - 1000);
|
|
@@ -569,33 +373,6 @@ export class GithubMessagingBot {
|
|
|
569
373
|
attachments: [],
|
|
570
374
|
isMessagingBot: false,
|
|
571
375
|
});
|
|
572
|
-
return issue;
|
|
573
|
-
}
|
|
574
|
-
async ensureRepoClone(ref, conversationId, isPr) {
|
|
575
|
-
const office = this.office(conversationId);
|
|
576
|
-
const dir = conversationRepoDir(office);
|
|
577
|
-
if (existsSync(dir))
|
|
578
|
-
return;
|
|
579
|
-
try {
|
|
580
|
-
office.ensure();
|
|
581
|
-
const token = await this.client.createScopedInstallationToken(ref.repo, {
|
|
582
|
-
contents: "read",
|
|
583
|
-
});
|
|
584
|
-
const prHeadBranch = isPr ? await fetchPrHeadBranch(this.client, ref) : undefined;
|
|
585
|
-
await cloneRepo({
|
|
586
|
-
url: `https://github.com/${ref.owner}/${ref.repo}.git`,
|
|
587
|
-
dir,
|
|
588
|
-
token,
|
|
589
|
-
botLogin: `${this.appSlug}[bot]`,
|
|
590
|
-
botEmail: this.botEmail ?? `${this.appSlug}[bot]@users.noreply.github.com`,
|
|
591
|
-
prNumber: isPr ? ref.number : undefined,
|
|
592
|
-
prHeadBranch,
|
|
593
|
-
});
|
|
594
|
-
log.logInfo(`[${conversationId}] Cloned ${ref.owner}/${ref.repo}${isPr ? ` and checked out PR #${ref.number} head as ${prHeadBranch ?? `pr-${ref.number}`}` : ""}`);
|
|
595
|
-
}
|
|
596
|
-
catch (err) {
|
|
597
|
-
log.logWarning(`GitHub: repo clone failed for ${conversationId}`, errorMessage(err));
|
|
598
|
-
}
|
|
599
376
|
}
|
|
600
377
|
}
|
|
601
378
|
//# sourceMappingURL=bot.js.map
|