@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.
Files changed (184) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +8 -8
  3. package/dist/adapters/commands/session-view.js +1 -1
  4. package/dist/adapters/commands/session-view.js.map +1 -1
  5. package/dist/adapters/discord/bot.d.ts.map +1 -1
  6. package/dist/adapters/discord/bot.js +1 -8
  7. package/dist/adapters/discord/bot.js.map +1 -1
  8. package/dist/adapters/github/activity.d.ts +3 -0
  9. package/dist/adapters/github/activity.d.ts.map +1 -0
  10. package/dist/adapters/github/activity.js +154 -0
  11. package/dist/adapters/github/activity.js.map +1 -0
  12. package/dist/adapters/github/bot.d.ts +10 -20
  13. package/dist/adapters/github/bot.d.ts.map +1 -1
  14. package/dist/adapters/github/bot.js +119 -342
  15. package/dist/adapters/github/bot.js.map +1 -1
  16. package/dist/adapters/github/client.d.ts +9 -19
  17. package/dist/adapters/github/client.d.ts.map +1 -1
  18. package/dist/adapters/github/client.js +28 -104
  19. package/dist/adapters/github/client.js.map +1 -1
  20. package/dist/adapters/github/context.d.ts.map +1 -1
  21. package/dist/adapters/github/context.js +29 -20
  22. package/dist/adapters/github/context.js.map +1 -1
  23. package/dist/adapters/github/github-ops.d.ts +2 -10
  24. package/dist/adapters/github/github-ops.d.ts.map +1 -1
  25. package/dist/adapters/github/github-ops.js +28 -91
  26. package/dist/adapters/github/github-ops.js.map +1 -1
  27. package/dist/adapters/github/policy.d.ts +19 -0
  28. package/dist/adapters/github/policy.d.ts.map +1 -0
  29. package/dist/adapters/github/policy.js +79 -0
  30. package/dist/adapters/github/policy.js.map +1 -0
  31. package/dist/adapters/github/tool-pack.d.ts +2 -2
  32. package/dist/adapters/github/tool-pack.d.ts.map +1 -1
  33. package/dist/adapters/github/tool-pack.js +4 -9
  34. package/dist/adapters/github/tool-pack.js.map +1 -1
  35. package/dist/adapters/github/tools/checks.d.ts.map +1 -1
  36. package/dist/adapters/github/tools/checks.js +6 -8
  37. package/dist/adapters/github/tools/checks.js.map +1 -1
  38. package/dist/adapters/github/tools/pr.d.ts.map +1 -1
  39. package/dist/adapters/github/tools/pr.js +7 -9
  40. package/dist/adapters/github/tools/pr.js.map +1 -1
  41. package/dist/adapters/github/types.d.ts +66 -67
  42. package/dist/adapters/github/types.d.ts.map +1 -1
  43. package/dist/adapters/github/types.js.map +1 -1
  44. package/dist/adapters/github/webhook.d.ts +2 -1
  45. package/dist/adapters/github/webhook.d.ts.map +1 -1
  46. package/dist/adapters/github/webhook.js +46 -4
  47. package/dist/adapters/github/webhook.js.map +1 -1
  48. package/dist/adapters/progressive-renderer.d.ts.map +1 -1
  49. package/dist/adapters/progressive-renderer.js +3 -0
  50. package/dist/adapters/progressive-renderer.js.map +1 -1
  51. package/dist/adapters/shared.d.ts +3 -1
  52. package/dist/adapters/shared.d.ts.map +1 -1
  53. package/dist/adapters/shared.js +45 -23
  54. package/dist/adapters/shared.js.map +1 -1
  55. package/dist/adapters/slack/blocks.d.ts.map +1 -1
  56. package/dist/adapters/slack/blocks.js +65 -2
  57. package/dist/adapters/slack/blocks.js.map +1 -1
  58. package/dist/adapters/slack/bot.d.ts +3 -0
  59. package/dist/adapters/slack/bot.d.ts.map +1 -1
  60. package/dist/adapters/slack/bot.js +76 -34
  61. package/dist/adapters/slack/bot.js.map +1 -1
  62. package/dist/adapters/slack/jev-context.js +2 -2
  63. package/dist/adapters/slack/jev-context.js.map +1 -1
  64. package/dist/adapters/slack/task-status.d.ts +3 -2
  65. package/dist/adapters/slack/task-status.d.ts.map +1 -1
  66. package/dist/adapters/slack/task-status.js +12 -15
  67. package/dist/adapters/slack/task-status.js.map +1 -1
  68. package/dist/adapters/telegram/bot.d.ts.map +1 -1
  69. package/dist/adapters/telegram/bot.js +1 -8
  70. package/dist/adapters/telegram/bot.js.map +1 -1
  71. package/dist/adapters/web/admin/portal.js +1 -1
  72. package/dist/adapters/web/admin/portal.js.map +1 -1
  73. package/dist/adapters/web/admin/provider-models.d.ts.map +1 -1
  74. package/dist/adapters/web/admin/provider-models.js +10 -3
  75. package/dist/adapters/web/admin/provider-models.js.map +1 -1
  76. package/dist/adapters/web/login/portal.js +6 -1
  77. package/dist/adapters/web/login/portal.js.map +1 -1
  78. package/dist/adapters/web/server.d.ts.map +1 -1
  79. package/dist/adapters/web/server.js +5 -3
  80. package/dist/adapters/web/server.js.map +1 -1
  81. package/dist/adapters/web/session-view/portal.d.ts +2 -1
  82. package/dist/adapters/web/session-view/portal.d.ts.map +1 -1
  83. package/dist/adapters/web/session-view/portal.js +18 -27
  84. package/dist/adapters/web/session-view/portal.js.map +1 -1
  85. package/dist/cli/migrate.d.ts.map +1 -1
  86. package/dist/cli/migrate.js +1 -2
  87. package/dist/cli/migrate.js.map +1 -1
  88. package/dist/cli/onboard.d.ts +0 -1
  89. package/dist/cli/onboard.d.ts.map +1 -1
  90. package/dist/cli/onboard.js +1 -7
  91. package/dist/cli/onboard.js.map +1 -1
  92. package/dist/env-manifest.d.ts.map +1 -1
  93. package/dist/env-manifest.js +24 -22
  94. package/dist/env-manifest.js.map +1 -1
  95. package/dist/events/index.d.ts +1 -0
  96. package/dist/events/index.d.ts.map +1 -1
  97. package/dist/events/index.js +11 -0
  98. package/dist/events/index.js.map +1 -1
  99. package/dist/events/scheduler.d.ts.map +1 -1
  100. package/dist/events/scheduler.js +3 -0
  101. package/dist/events/scheduler.js.map +1 -1
  102. package/dist/file-guards.d.ts +7 -0
  103. package/dist/file-guards.d.ts.map +1 -1
  104. package/dist/file-guards.js +63 -1
  105. package/dist/file-guards.js.map +1 -1
  106. package/dist/harness/mcp.d.ts.map +1 -1
  107. package/dist/harness/mcp.js +75 -31
  108. package/dist/harness/mcp.js.map +1 -1
  109. package/dist/harness/models.js +2 -2
  110. package/dist/harness/models.js.map +1 -1
  111. package/dist/harness/open-connector.d.ts.map +1 -1
  112. package/dist/harness/open-connector.js +14 -7
  113. package/dist/harness/open-connector.js.map +1 -1
  114. package/dist/harness/presenter.d.ts.map +1 -1
  115. package/dist/harness/presenter.js +2 -0
  116. package/dist/harness/presenter.js.map +1 -1
  117. package/dist/harness/prompt.d.ts +1 -1
  118. package/dist/harness/prompt.d.ts.map +1 -1
  119. package/dist/harness/prompt.js +13 -31
  120. package/dist/harness/prompt.js.map +1 -1
  121. package/dist/harness/runner.d.ts.map +1 -1
  122. package/dist/harness/runner.js +6 -9
  123. package/dist/harness/runner.js.map +1 -1
  124. package/dist/harness/session.d.ts.map +1 -1
  125. package/dist/harness/session.js +6 -0
  126. package/dist/harness/session.js.map +1 -1
  127. package/dist/harness/tools/event.d.ts.map +1 -1
  128. package/dist/harness/tools/event.js +19 -2
  129. package/dist/harness/tools/event.js.map +1 -1
  130. package/dist/harness/tools/generate-image.d.ts.map +1 -1
  131. package/dist/harness/tools/generate-image.js +17 -3
  132. package/dist/harness/tools/generate-image.js.map +1 -1
  133. package/dist/harness/tools/jev-browser.d.ts +1 -2
  134. package/dist/harness/tools/jev-browser.d.ts.map +1 -1
  135. package/dist/harness/tools/jev-browser.js +75 -50
  136. package/dist/harness/tools/jev-browser.js.map +1 -1
  137. package/dist/index.d.ts +2 -2
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +2 -2
  140. package/dist/index.js.map +1 -1
  141. package/dist/main.js +34 -45
  142. package/dist/main.js.map +1 -1
  143. package/dist/memory-capture/index.d.ts.map +1 -1
  144. package/dist/memory-capture/index.js +3 -3
  145. package/dist/memory-capture/index.js.map +1 -1
  146. package/dist/migrations/index.d.ts.map +1 -1
  147. package/dist/migrations/index.js +2 -0
  148. package/dist/migrations/index.js.map +1 -1
  149. package/dist/migrations/office-sessions.d.ts +3 -0
  150. package/dist/migrations/office-sessions.d.ts.map +1 -0
  151. package/dist/migrations/office-sessions.js +70 -0
  152. package/dist/migrations/office-sessions.js.map +1 -0
  153. package/dist/office/index.d.ts +0 -1
  154. package/dist/office/index.d.ts.map +1 -1
  155. package/dist/office/index.js +3 -5
  156. package/dist/office/index.js.map +1 -1
  157. package/dist/runtime/conversation-runtime.js +12 -12
  158. package/dist/runtime/conversation-runtime.js.map +1 -1
  159. package/dist/sandbox/provisioner.d.ts +1 -1
  160. package/dist/sandbox/provisioner.d.ts.map +1 -1
  161. package/dist/sandbox/provisioner.js +20 -5
  162. package/dist/sandbox/provisioner.js.map +1 -1
  163. package/dist/sessions/chat-history-sync.d.ts.map +1 -1
  164. package/dist/sessions/chat-history-sync.js +28 -40
  165. package/dist/sessions/chat-history-sync.js.map +1 -1
  166. package/dist/sessions/store.d.ts +2 -3
  167. package/dist/sessions/store.d.ts.map +1 -1
  168. package/dist/sessions/store.js +8 -14
  169. package/dist/sessions/store.js.map +1 -1
  170. package/dist/sessions/types.d.ts +6 -5
  171. package/dist/sessions/types.d.ts.map +1 -1
  172. package/dist/sessions/types.js.map +1 -1
  173. package/dist/types.d.ts +0 -1
  174. package/dist/types.d.ts.map +1 -1
  175. package/dist/types.js.map +1 -1
  176. package/package.json +8 -6
  177. package/dist/adapters/github/repo.d.ts +0 -8
  178. package/dist/adapters/github/repo.d.ts.map +0 -1
  179. package/dist/adapters/github/repo.js +0 -92
  180. package/dist/adapters/github/repo.js.map +0 -1
  181. package/dist/adapters/github/tools/sync.d.ts +0 -13
  182. package/dist/adapters/github/tools/sync.d.ts.map +0 -1
  183. package/dist/adapters/github/tools/sync.js +0 -29
  184. 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":"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"}
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 { fetchIsPr, fetchPrHeadBranch, GithubOps } from "./github-ops.js";
13
- import { cloneRepo, conversationRepoDir } from "./repo.js";
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.appSlug = null;
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
- client ??
112
- new GithubClient({
113
- appId: config.appId,
114
- privateKey: config.privateKey,
115
- installationId: config.installationId,
116
- });
117
- this.ops = new GithubOps(this.client, { workspace: config.workspace });
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 @${this.appSlug}[bot], polling ${this.watchedRepos.length} repo(s) every ${Math.round(this.config.pollIntervalMs / 1000)}s`);
75
+ log.logInfo(`GitHub bot started as @${user.login}, answering in ${this.config.policy.repos.join(", ")}`);
171
76
  }
172
77
  async stop() {
173
- this.stopping = true;
174
- this.pollPending = false;
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
- const conversationId = event.address.conversationId;
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: "open-trigger",
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
- requestPoll(debounceMs = REQUEST_POLL_DEBOUNCE_MS) {
268
- if (this.stopped || this.stopping)
151
+ async receive(delivery) {
152
+ if (this.stopped || !this.identity)
269
153
  return;
270
- if (this.activePoll) {
271
- this.pollPending = true;
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 activePoll;
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
- async pollWatchedRepos() {
303
- let changed = false;
304
- for (const repo of this.watchedRepos) {
305
- try {
306
- changed = (await this.pollRepo(repo)) || changed;
307
- }
308
- catch (err) {
309
- log.logWarning(`GitHub poll failed for ${repo.owner}/${repo.repo}`, errorMessage(err));
310
- }
311
- }
312
- if (changed)
313
- this.persistSyncState();
314
- }
315
- async pollRepo(repo) {
316
- const repoKey = `${repo.owner}/${repo.repo}`;
317
- const state = this.repoState.get(repoKey);
318
- if (!state)
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
- const since = new Date(Date.parse(state.cursor) - POLL_OVERLAP_MS).toISOString();
321
- let changed = false;
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 advanceFeed(state, seen, items, toIncoming) {
364
- let changed = false;
365
- for (const item of items) {
366
- if (item.updated_at > state.cursor) {
367
- state.cursor = item.updated_at;
368
- changed = true;
369
- }
370
- if (item.created_at < state.baseline || seen.has(item.id))
371
- continue;
372
- seen.add(item.id);
373
- changed = true;
374
- if (item.user.type === "Bot")
375
- continue;
376
- await this.handleIncoming(toIncoming(item));
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
- syncStateSnapshot() {
390
- const state = { repos: {} };
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
- return state;
401
- }
402
- persistSyncState() {
403
- ensureDirExists(dirname(this.config.syncStatePath));
404
- atomicWritePrivateFile(this.config.syncStatePath, JSON.stringify(this.syncStateSnapshot()));
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
- return this.appSlug ? new RegExp(`@${this.appSlug}(?![\\w-])`, "gi") : null;
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.rank >= REQUIRED_TRIGGER_RANK;
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
- rank = Math.max(rankOfPermission(role.role_name ?? ""), rankOfPermission(role.permission));
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
- rank,
260
+ allowed,
440
261
  expiresAt: Date.now() + PERMISSION_CACHE_TTL_MS,
441
262
  });
442
- return rank >= REQUIRED_TRIGGER_RANK;
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: mentioned, scopeFallback: "never" },
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
- await this.prepareConversation(item, conversationId, participating);
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 null;
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