@holon-run/agentinbox 0.7.0 → 1.0.0-beta.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.
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveSourceRegistration = resolveSourceRegistration;
4
+ function resolveSourceRegistration(input) {
5
+ const config = input.config ?? {};
6
+ if (input.sourceType === "github_repo") {
7
+ return {
8
+ hostType: "github",
9
+ hostKey: `uxcAuth:${stringOrDefault(config.uxcAuth, input.configRef ?? "default")}`,
10
+ hostConfig: {
11
+ ...(valueOrUndefined(config.uxcAuth) ? { uxcAuth: config.uxcAuth } : {}),
12
+ },
13
+ streamKind: "repo_events",
14
+ streamKey: input.sourceKey,
15
+ streamConfig: config,
16
+ sourceType: input.sourceType,
17
+ };
18
+ }
19
+ if (input.sourceType === "github_repo_ci") {
20
+ return {
21
+ hostType: "github",
22
+ hostKey: `uxcAuth:${stringOrDefault(config.uxcAuth, input.configRef ?? "default")}`,
23
+ hostConfig: {
24
+ ...(valueOrUndefined(config.uxcAuth) ? { uxcAuth: config.uxcAuth } : {}),
25
+ },
26
+ streamKind: "ci_runs",
27
+ streamKey: input.sourceKey,
28
+ streamConfig: config,
29
+ sourceType: input.sourceType,
30
+ };
31
+ }
32
+ if (input.sourceType === "feishu_bot") {
33
+ return {
34
+ hostType: "feishu",
35
+ hostKey: `app:${stringOrDefault(config.appId, input.configRef ?? input.sourceKey)}`,
36
+ hostConfig: {
37
+ ...(valueOrUndefined(config.appId) ? { appId: config.appId } : {}),
38
+ ...(valueOrUndefined(config.appSecret) ? { appSecret: config.appSecret } : {}),
39
+ ...(valueOrUndefined(config.schemaUrl) ? { schemaUrl: config.schemaUrl } : {}),
40
+ ...(valueOrUndefined(config.replyInThread) ? { replyInThread: config.replyInThread } : {}),
41
+ ...(valueOrUndefined(config.uxcAuth) ? { uxcAuth: config.uxcAuth } : {}),
42
+ },
43
+ streamKind: "message_events",
44
+ streamKey: input.sourceKey,
45
+ streamConfig: config,
46
+ sourceType: input.sourceType,
47
+ };
48
+ }
49
+ if (input.sourceType === "local_event") {
50
+ return {
51
+ hostType: "local_event",
52
+ hostKey: input.sourceKey,
53
+ hostConfig: {},
54
+ streamKind: "events",
55
+ streamKey: input.sourceKey,
56
+ streamConfig: config,
57
+ sourceType: input.sourceType,
58
+ };
59
+ }
60
+ return {
61
+ hostType: "remote_source",
62
+ hostKey: input.sourceKey,
63
+ hostConfig: config,
64
+ streamKind: "default",
65
+ streamKey: input.sourceKey,
66
+ streamConfig: config,
67
+ sourceType: input.sourceType,
68
+ };
69
+ }
70
+ function stringOrDefault(value, fallback) {
71
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : fallback;
72
+ }
73
+ function valueOrUndefined(value) {
74
+ return value !== undefined && value !== null;
75
+ }
@@ -25,7 +25,7 @@ async function resolveSourceIdentity(source, context = {}) {
25
25
  if (source.sourceType !== "remote_source") {
26
26
  throw new Error(`unsupported source type for source resolution: ${source.sourceType}`);
27
27
  }
28
- const moduleRegistry = context.moduleRegistry ?? context.profileRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
28
+ const moduleRegistry = context.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
29
29
  const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
30
30
  const module = await moduleRegistry.resolve(source, homeDir);
31
31
  const capabilityDescription = typeof module.describeCapabilities === "function"
@@ -38,7 +38,7 @@ async function resolveSourceIdentity(source, context = {}) {
38
38
  };
39
39
  }
40
40
  async function resolveSourceSchema(source, context = {}) {
41
- const moduleRegistry = context.moduleRegistry ?? context.profileRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
41
+ const moduleRegistry = context.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
42
42
  const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
43
43
  const resolvedContext = { ...context, moduleRegistry, homeDir };
44
44
  const identity = await resolveSourceIdentity(source, resolvedContext);
@@ -21,8 +21,8 @@ const SOURCE_SCHEMAS = {
21
21
  payloadExamples: [],
22
22
  eventVariantExamples: [],
23
23
  configFields: [
24
- { name: "profilePath", type: "string", required: true, description: "Local module path under $AGENTINBOX_HOME/source-profiles." },
25
- { name: "profileConfig", type: "object", required: false, description: "Module-specific configuration passed to validate/spec/map hooks." },
24
+ { name: "modulePath", type: "string", required: true, description: "Local module path under $AGENTINBOX_HOME/source-modules." },
25
+ { name: "moduleConfig", type: "object", required: false, description: "Module-specific configuration passed to validate/spec/map hooks." },
26
26
  ],
27
27
  },
28
28
  github_repo: {
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.FeishuDeliveryAdapter = exports.FeishuUxcClient = exports.DEFAULT_FEISHU_EVENT_TYPES = exports.FEISHU_IM_SCHEMA_URL = exports.FEISHU_OPENAPI_ENDPOINT = void 0;
4
+ exports.feishuDeliveryOperationsForHandle = feishuDeliveryOperationsForHandle;
5
+ exports.invokeFeishuDeliveryOperation = invokeFeishuDeliveryOperation;
4
6
  exports.normalizeFeishuBotEvent = normalizeFeishuBotEvent;
5
7
  exports.parseFeishuSourceConfig = parseFeishuSourceConfig;
6
8
  const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
@@ -54,37 +56,73 @@ class FeishuDeliveryAdapter {
54
56
  this.client = client ?? new FeishuUxcClient();
55
57
  }
56
58
  async send(request, attempt) {
57
- const config = parseDeliveryConfig(request.payload);
58
- const message = normalizeDeliveryMessage(request.payload);
59
- if (attempt.surface === "message_reply") {
60
- await this.client.replyToMessage({
61
- endpoint: config.endpoint,
62
- schemaUrl: config.schemaUrl,
63
- auth: config.auth,
64
- messageId: attempt.targetRef,
65
- msgType: message.msgType,
66
- content: message.content,
67
- replyInThread: config.replyInThread,
68
- uuid: config.uuid,
69
- });
70
- return { status: "sent", note: "sent Feishu message reply" };
71
- }
72
- if (attempt.surface === "chat_message") {
73
- await this.client.sendChatMessage({
74
- endpoint: config.endpoint,
75
- schemaUrl: config.schemaUrl,
76
- auth: config.auth,
77
- chatId: attempt.targetRef,
78
- msgType: message.msgType,
79
- content: message.content,
80
- uuid: config.uuid,
81
- });
82
- return { status: "sent", note: "sent Feishu chat message" };
83
- }
84
- throw new Error(`unsupported Feishu surface: ${attempt.surface}`);
59
+ return invokeFeishuDeliveryOperation(attempt, "send_text", request.payload, this.client);
85
60
  }
86
61
  }
87
62
  exports.FeishuDeliveryAdapter = FeishuDeliveryAdapter;
63
+ function feishuDeliveryOperationsForHandle(handle) {
64
+ if (handle.surface !== "message_reply" && handle.surface !== "chat_message") {
65
+ return [];
66
+ }
67
+ return [{
68
+ name: "send_text",
69
+ title: handle.surface === "message_reply" ? "Reply With Text" : "Send Text Message",
70
+ inputSchema: {
71
+ type: "object",
72
+ additionalProperties: false,
73
+ required: ["text"],
74
+ properties: {
75
+ text: { type: "string", minLength: 1 },
76
+ endpoint: { type: "string", minLength: 1 },
77
+ schemaUrl: { type: "string", minLength: 1 },
78
+ schema_url: { type: "string", minLength: 1 },
79
+ uxcAuth: { type: "string", minLength: 1 },
80
+ auth: { type: "string", minLength: 1 },
81
+ replyInThread: { type: "boolean" },
82
+ reply_in_thread: { type: "boolean" },
83
+ uuid: { type: "string", minLength: 1 },
84
+ },
85
+ },
86
+ canonicalTextAlias: true,
87
+ }];
88
+ }
89
+ async function invokeFeishuDeliveryOperation(handle, operation, input, client = new FeishuUxcClient()) {
90
+ if (operation !== "send_text") {
91
+ throw new Error(`unknown Feishu delivery operation: ${operation}`);
92
+ }
93
+ const text = asString(input.text);
94
+ if (!text || text.trim().length === 0) {
95
+ throw new Error("send_text requires input.text");
96
+ }
97
+ const config = parseDeliveryConfig(input);
98
+ const message = normalizeDeliveryMessage({ text });
99
+ if (handle.surface === "message_reply") {
100
+ await client.replyToMessage({
101
+ endpoint: config.endpoint,
102
+ schemaUrl: config.schemaUrl,
103
+ auth: config.auth,
104
+ messageId: handle.targetRef,
105
+ msgType: message.msgType,
106
+ content: message.content,
107
+ replyInThread: config.replyInThread,
108
+ uuid: config.uuid,
109
+ });
110
+ return { status: "sent", note: "sent Feishu message reply" };
111
+ }
112
+ if (handle.surface === "chat_message") {
113
+ await client.sendChatMessage({
114
+ endpoint: config.endpoint,
115
+ schemaUrl: config.schemaUrl,
116
+ auth: config.auth,
117
+ chatId: handle.targetRef,
118
+ msgType: message.msgType,
119
+ content: message.content,
120
+ uuid: config.uuid,
121
+ });
122
+ return { status: "sent", note: "sent Feishu chat message" };
123
+ }
124
+ throw new Error(`deliver send only supports canonical Feishu text surfaces; use deliver invoke for ${handle.surface}`);
125
+ }
88
126
  function normalizeFeishuBotEvent(source, config, raw) {
89
127
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
90
128
  return null;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.GithubDeliveryAdapter = exports.GithubUxcClient = exports.DEFAULT_GITHUB_EVENT_TYPES = exports.GITHUB_ENDPOINT = void 0;
4
+ exports.githubDeliveryOperationsForHandle = githubDeliveryOperationsForHandle;
5
+ exports.invokeGithubDeliveryOperation = invokeGithubDeliveryOperation;
4
6
  exports.normalizeGithubRepoEvent = normalizeGithubRepoEvent;
5
7
  exports.deriveGithubTrackedResource = deriveGithubTrackedResource;
6
8
  exports.githubSubscriptionShortcutSpec = githubSubscriptionShortcutSpec;
@@ -48,6 +50,34 @@ class GithubUxcClient {
48
50
  options: { auth: input.auth },
49
51
  });
50
52
  }
53
+ async updateIssueState(input) {
54
+ await this.client.call({
55
+ endpoint: exports.GITHUB_ENDPOINT,
56
+ operation: "patch:/repos/{owner}/{repo}/issues/{issue_number}",
57
+ payload: {
58
+ owner: input.owner,
59
+ repo: input.repo,
60
+ issue_number: input.issueNumber,
61
+ state: input.state,
62
+ },
63
+ options: { auth: input.auth },
64
+ });
65
+ }
66
+ async mergePullRequest(input) {
67
+ await this.client.call({
68
+ endpoint: exports.GITHUB_ENDPOINT,
69
+ operation: "put:/repos/{owner}/{repo}/pulls/{pull_number}/merge",
70
+ payload: {
71
+ owner: input.owner,
72
+ repo: input.repo,
73
+ pull_number: input.pullNumber,
74
+ merge_method: input.mergeMethod ?? null,
75
+ commit_title: input.commitTitle ?? null,
76
+ commit_message: input.commitMessage ?? null,
77
+ },
78
+ options: { auth: input.auth },
79
+ });
80
+ }
51
81
  }
52
82
  exports.GithubUxcClient = GithubUxcClient;
53
83
  class GithubDeliveryAdapter {
@@ -57,33 +87,131 @@ class GithubDeliveryAdapter {
57
87
  }
58
88
  async send(request, attempt) {
59
89
  const payloadText = stringifyDeliveryPayload(request.payload);
60
- const target = parseTargetRef(attempt.targetRef);
61
- if (attempt.surface === "issue_comment" || attempt.surface === "pull_request_comment") {
62
- await this.client.createIssueComment({
63
- owner: target.owner,
64
- repo: target.repo,
65
- issueNumber: target.number,
66
- body: payloadText,
67
- });
68
- return { status: "sent", note: "sent GitHub issue comment" };
69
- }
70
- if (attempt.surface === "review_comment") {
71
- if (!attempt.threadRef || !attempt.threadRef.startsWith("review_comment:")) {
72
- throw new Error("review_comment delivery requires threadRef=review_comment:<id>");
73
- }
74
- await this.client.replyToReviewComment({
75
- owner: target.owner,
76
- repo: target.repo,
77
- pullNumber: target.number,
78
- commentId: Number(attempt.threadRef.slice("review_comment:".length)),
79
- body: payloadText,
80
- });
81
- return { status: "sent", note: "sent GitHub review comment reply" };
82
- }
83
- throw new Error(`unsupported GitHub surface: ${attempt.surface}`);
90
+ return invokeGithubDeliveryOperation(attempt, canonicalGithubOperationForSurface(attempt.surface), { body: payloadText }, this.client);
84
91
  }
85
92
  }
86
93
  exports.GithubDeliveryAdapter = GithubDeliveryAdapter;
94
+ function githubDeliveryOperationsForHandle(handle) {
95
+ if (handle.surface === "review_comment") {
96
+ return [{
97
+ name: "reply_in_review_thread",
98
+ title: "Reply In Review Thread",
99
+ inputSchema: deliveryBodySchema(),
100
+ canonicalTextAlias: true,
101
+ }];
102
+ }
103
+ if (handle.surface === "issue_comment") {
104
+ return [
105
+ {
106
+ name: "add_comment",
107
+ title: "Add Comment",
108
+ inputSchema: deliveryBodySchema(),
109
+ canonicalTextAlias: true,
110
+ },
111
+ {
112
+ name: "close_issue",
113
+ title: "Close Issue",
114
+ inputSchema: emptyObjectSchema(),
115
+ },
116
+ {
117
+ name: "reopen_issue",
118
+ title: "Reopen Issue",
119
+ inputSchema: emptyObjectSchema(),
120
+ },
121
+ ];
122
+ }
123
+ if (handle.surface === "pull_request_comment") {
124
+ return [
125
+ {
126
+ name: "add_comment",
127
+ title: "Add Comment",
128
+ inputSchema: deliveryBodySchema(),
129
+ canonicalTextAlias: true,
130
+ },
131
+ {
132
+ name: "close_issue",
133
+ title: "Close Pull Request",
134
+ inputSchema: emptyObjectSchema(),
135
+ },
136
+ {
137
+ name: "reopen_issue",
138
+ title: "Reopen Pull Request",
139
+ inputSchema: emptyObjectSchema(),
140
+ },
141
+ {
142
+ name: "merge_pull_request",
143
+ title: "Merge Pull Request",
144
+ inputSchema: {
145
+ type: "object",
146
+ additionalProperties: false,
147
+ properties: {
148
+ mergeMethod: { type: "string", enum: ["merge", "squash", "rebase"] },
149
+ commitTitle: { type: "string" },
150
+ commitMessage: { type: "string" },
151
+ },
152
+ },
153
+ },
154
+ ];
155
+ }
156
+ return [];
157
+ }
158
+ async function invokeGithubDeliveryOperation(handle, operation, input, client = new GithubUxcClient()) {
159
+ const target = parseTargetRef(handle.targetRef);
160
+ if (operation === "add_comment") {
161
+ const body = requireDeliveryBody(input, operation);
162
+ await client.createIssueComment({
163
+ owner: target.owner,
164
+ repo: target.repo,
165
+ issueNumber: target.number,
166
+ body,
167
+ });
168
+ return { status: "sent", note: "sent GitHub issue comment" };
169
+ }
170
+ if (operation === "reply_in_review_thread") {
171
+ const body = requireDeliveryBody(input, operation);
172
+ if (!handle.threadRef || !handle.threadRef.startsWith("review_comment:")) {
173
+ throw new Error("reply_in_review_thread requires threadRef=review_comment:<id>");
174
+ }
175
+ await client.replyToReviewComment({
176
+ owner: target.owner,
177
+ repo: target.repo,
178
+ pullNumber: target.number,
179
+ commentId: Number(handle.threadRef.slice("review_comment:".length)),
180
+ body,
181
+ });
182
+ return { status: "sent", note: "sent GitHub review comment reply" };
183
+ }
184
+ if (operation === "close_issue") {
185
+ await client.updateIssueState({
186
+ owner: target.owner,
187
+ repo: target.repo,
188
+ issueNumber: target.number,
189
+ state: "closed",
190
+ });
191
+ return { status: "sent", note: "closed GitHub issue" };
192
+ }
193
+ if (operation === "reopen_issue") {
194
+ await client.updateIssueState({
195
+ owner: target.owner,
196
+ repo: target.repo,
197
+ issueNumber: target.number,
198
+ state: "open",
199
+ });
200
+ return { status: "sent", note: "reopened GitHub issue" };
201
+ }
202
+ if (operation === "merge_pull_request") {
203
+ await client.mergePullRequest({
204
+ owner: target.owner,
205
+ repo: target.repo,
206
+ pullNumber: target.number,
207
+ mergeMethod: asMergeMethod(input.mergeMethod),
208
+ commitTitle: asString(input.commitTitle) ?? undefined,
209
+ commitMessage: asString(input.commitMessage) ?? undefined,
210
+ });
211
+ return { status: "sent", note: "merged GitHub pull request" };
212
+ }
213
+ throw new Error(`unknown GitHub delivery operation: ${operation}`);
214
+ }
87
215
  function normalizeGithubRepoEvent(source, config, raw) {
88
216
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
89
217
  return null;
@@ -300,6 +428,42 @@ function stringifyDeliveryPayload(payload) {
300
428
  }
301
429
  return JSON.stringify(payload, null, 2);
302
430
  }
431
+ function canonicalGithubOperationForSurface(surface) {
432
+ if (surface === "issue_comment" || surface === "pull_request_comment") {
433
+ return "add_comment";
434
+ }
435
+ if (surface === "review_comment") {
436
+ return "reply_in_review_thread";
437
+ }
438
+ throw new Error(`deliver send only supports canonical GitHub text surfaces; use deliver invoke for ${surface}`);
439
+ }
440
+ function deliveryBodySchema() {
441
+ return {
442
+ type: "object",
443
+ additionalProperties: false,
444
+ required: ["body"],
445
+ properties: {
446
+ body: { type: "string", minLength: 1 },
447
+ },
448
+ };
449
+ }
450
+ function emptyObjectSchema() {
451
+ return {
452
+ type: "object",
453
+ additionalProperties: false,
454
+ properties: {},
455
+ };
456
+ }
457
+ function requireDeliveryBody(input, operation) {
458
+ const body = asString(input.body);
459
+ if (!body || body.trim().length === 0) {
460
+ throw new Error(`${operation} requires input.body`);
461
+ }
462
+ return body;
463
+ }
464
+ function asMergeMethod(value) {
465
+ return value === "merge" || value === "squash" || value === "rebase" ? value : undefined;
466
+ }
303
467
  function asRecord(value) {
304
468
  if (!value || typeof value !== "object" || Array.isArray(value)) {
305
469
  return {};
@@ -65,7 +65,7 @@ class RemoteSourceRuntime {
65
65
  constructor(store, appendSourceEvent, options) {
66
66
  this.store = store;
67
67
  this.appendSourceEvent = appendSourceEvent;
68
- this.moduleRegistry = options?.moduleRegistry ?? options?.profileRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
68
+ this.moduleRegistry = options?.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
69
69
  this.client = options?.client ?? new RpcUxcRemoteSourceClient();
70
70
  this.homeDir = options?.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
71
71
  }
@@ -201,6 +201,26 @@ class RemoteSourceRuntime {
201
201
  }
202
202
  return module.deriveInlinePreview(item, moduleInputSource(source));
203
203
  }
204
+ async deriveNotificationGrouping(source, item) {
205
+ if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
206
+ return null;
207
+ }
208
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
209
+ if (typeof module.deriveNotificationGrouping !== "function") {
210
+ return null;
211
+ }
212
+ return module.deriveNotificationGrouping(item, moduleInputSource(source));
213
+ }
214
+ async summarizeDigestThread(source, items, grouping) {
215
+ if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
216
+ return null;
217
+ }
218
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
219
+ if (typeof module.summarizeDigestThread !== "function") {
220
+ return null;
221
+ }
222
+ return module.summarizeDigestThread(items, moduleInputSource(source), grouping);
223
+ }
204
224
  async syncAll() {
205
225
  const sources = this.store
206
226
  .listSources()
@@ -355,7 +375,7 @@ function moduleInputSource(source) {
355
375
  const config = asRecord(source.config);
356
376
  return {
357
377
  ...source,
358
- config: asRecord(config.profileConfig),
378
+ config: asRecord(config.moduleConfig),
359
379
  };
360
380
  }
361
381
  function defaultManagedBindingForSource(source) {
@@ -12,7 +12,7 @@ const node_url_1 = require("node:url");
12
12
  const github_1 = require("./github");
13
13
  const github_ci_1 = require("./github_ci");
14
14
  const feishu_1 = require("./feishu");
15
- const REMOTE_USER_MODULE_ROOT_DIR = "source-profiles";
15
+ const REMOTE_USER_MODULE_ROOT_DIR = "source-modules";
16
16
  const BUILTIN_MODULE_IDS = new Set(["builtin.github_repo", "builtin.github_repo_ci", "builtin.feishu_bot"]);
17
17
  class RemoteSourceModuleRegistry {
18
18
  moduleCache = new Map();
@@ -33,12 +33,12 @@ class RemoteSourceModuleRegistry {
33
33
  }
34
34
  async loadUserModule(source, homeDir) {
35
35
  const config = source.config ?? {};
36
- const profilePath = asNonEmptyString(config.profilePath);
37
- if (!profilePath) {
38
- throw new Error("remote_source requires config.profilePath (module path)");
36
+ const modulePath = asNonEmptyString(config.modulePath);
37
+ if (!modulePath) {
38
+ throw new Error("remote_source requires config.modulePath (module path)");
39
39
  }
40
40
  const moduleRoot = node_path_1.default.resolve(homeDir, REMOTE_USER_MODULE_ROOT_DIR);
41
- const resolvedPath = resolveAndValidateModulePath(moduleRoot, profilePath);
41
+ const resolvedPath = resolveAndValidateModulePath(moduleRoot, modulePath);
42
42
  const cacheKey = `${resolvedPath}:${source.sourceKey}`;
43
43
  const cached = this.moduleCache.get(cacheKey);
44
44
  if (cached) {
@@ -96,20 +96,24 @@ function validateModuleContract(module, sourcePath) {
96
96
  validateOptionalHook(module.deriveTrackedResource, "deriveTrackedResource", sourcePath);
97
97
  validateOptionalHook(module.projectLifecycleSignal, "projectLifecycleSignal", sourcePath);
98
98
  validateOptionalHook(module.deriveInlinePreview, "deriveInlinePreview", sourcePath);
99
+ validateOptionalHook(module.deriveNotificationGrouping, "deriveNotificationGrouping", sourcePath);
100
+ validateOptionalHook(module.listDeliveryOperations, "listDeliveryOperations", sourcePath);
101
+ validateOptionalHook(module.invokeDeliveryOperation, "invokeDeliveryOperation", sourcePath);
102
+ validateOptionalHook(module.summarizeDigestThread, "summarizeDigestThread", sourcePath);
99
103
  }
100
104
  function validateOptionalHook(value, name, sourcePath) {
101
105
  if (value !== undefined && typeof value !== "function") {
102
106
  throw new Error(`remote_source module ${name} must be a function when provided: ${sourcePath}`);
103
107
  }
104
108
  }
105
- function resolveAndValidateModulePath(moduleRoot, profilePath) {
106
- const resolved = node_path_1.default.isAbsolute(profilePath)
107
- ? node_path_1.default.resolve(profilePath)
108
- : node_path_1.default.resolve(moduleRoot, profilePath);
109
+ function resolveAndValidateModulePath(moduleRoot, modulePath) {
110
+ const resolved = node_path_1.default.isAbsolute(modulePath)
111
+ ? node_path_1.default.resolve(modulePath)
112
+ : node_path_1.default.resolve(moduleRoot, modulePath);
109
113
  const normalizedRoot = ensureTrailingSep(node_path_1.default.resolve(moduleRoot));
110
114
  const normalizedResolved = node_path_1.default.resolve(resolved);
111
115
  if (!normalizedResolved.startsWith(normalizedRoot) && normalizedResolved !== node_path_1.default.resolve(moduleRoot)) {
112
- throw new Error(`remote_source profilePath must stay under ${moduleRoot}`);
116
+ throw new Error(`remote_source modulePath must stay under ${moduleRoot}`);
113
117
  }
114
118
  return normalizedResolved;
115
119
  }
@@ -131,6 +135,12 @@ function asNonEmptyString(value) {
131
135
  }
132
136
  const GITHUB_REPO_MODULE = {
133
137
  id: "builtin.github_repo",
138
+ listDeliveryOperations(input) {
139
+ return (0, github_1.githubDeliveryOperationsForHandle)(input.handle);
140
+ },
141
+ async invokeDeliveryOperation(input) {
142
+ return (0, github_1.invokeGithubDeliveryOperation)(input.handle, input.operation, input.input);
143
+ },
134
144
  describeCapabilities(source) {
135
145
  const config = (0, github_1.parseGithubSourceConfig)(source);
136
146
  return {
@@ -193,6 +203,54 @@ const GITHUB_REPO_MODULE = {
193
203
  deriveTrackedResource(filter) {
194
204
  return (0, github_1.deriveGithubTrackedResource)(filter);
195
205
  },
206
+ deriveNotificationGrouping(item) {
207
+ const number = typeof item.metadata.number === "number" ? item.metadata.number : null;
208
+ if (!number) {
209
+ return null;
210
+ }
211
+ const variant = item.eventVariant;
212
+ if (variant.startsWith("PullRequestReviewCommentEvent.")) {
213
+ return {
214
+ groupable: true,
215
+ resourceRef: `pr:${number}`,
216
+ eventFamily: "review_comments",
217
+ summaryHint: `review comments on PR #${number}`,
218
+ };
219
+ }
220
+ if (variant.startsWith("PullRequestReviewEvent.")) {
221
+ return {
222
+ groupable: true,
223
+ resourceRef: `pr:${number}`,
224
+ eventFamily: "reviews",
225
+ summaryHint: `reviews on PR #${number}`,
226
+ };
227
+ }
228
+ if (variant.startsWith("IssueCommentEvent.")) {
229
+ return {
230
+ groupable: true,
231
+ resourceRef: item.metadata.isPullRequest === true ? `pr:${number}` : `issue:${number}`,
232
+ eventFamily: "comments",
233
+ summaryHint: item.metadata.isPullRequest === true ? `comments on PR #${number}` : `comments on issue #${number}`,
234
+ };
235
+ }
236
+ if (variant.startsWith("PullRequestEvent.closed")) {
237
+ return {
238
+ groupable: true,
239
+ resourceRef: `pr:${number}`,
240
+ eventFamily: "lifecycle",
241
+ summaryHint: `PR #${number} lifecycle updates`,
242
+ flushClass: "immediate",
243
+ };
244
+ }
245
+ return null;
246
+ },
247
+ summarizeDigestThread(items, _source, grouping) {
248
+ if (!grouping.summaryHint || items.length === 0) {
249
+ return null;
250
+ }
251
+ const count = items.length;
252
+ return `${count} ${grouping.summaryHint}`;
253
+ },
196
254
  projectLifecycleSignal(rawPayload) {
197
255
  return (0, github_1.projectGithubLifecycleSignal)(rawPayload);
198
256
  },
@@ -298,6 +356,27 @@ const GITHUB_REPO_CI_MODULE = {
298
356
  }
299
357
  return parts.join(" ");
300
358
  },
359
+ deriveNotificationGrouping(item) {
360
+ const workflowRunId = typeof item.metadata.workflowRunId === "number" ? item.metadata.workflowRunId : null;
361
+ if (!workflowRunId) {
362
+ return null;
363
+ }
364
+ const conclusion = asNonEmptyString(item.metadata.conclusion);
365
+ const status = asNonEmptyString(item.metadata.status);
366
+ return {
367
+ groupable: true,
368
+ resourceRef: `workflow_run:${workflowRunId}`,
369
+ eventFamily: "ci_updates",
370
+ summaryHint: `CI updates for run #${workflowRunId}`,
371
+ flushClass: conclusion || status === "completed" ? "immediate" : "normal",
372
+ };
373
+ },
374
+ summarizeDigestThread(items, _source, grouping) {
375
+ if (!grouping.summaryHint || items.length === 0) {
376
+ return null;
377
+ }
378
+ return `${items.length} ${grouping.summaryHint}`;
379
+ },
301
380
  validateConfig(source) {
302
381
  (0, github_ci_1.parseGithubCiSourceConfig)(source);
303
382
  },
@@ -351,6 +430,12 @@ const GITHUB_REPO_CI_MODULE = {
351
430
  };
352
431
  const FEISHU_BOT_MODULE = {
353
432
  id: "builtin.feishu_bot",
433
+ listDeliveryOperations(input) {
434
+ return (0, feishu_1.feishuDeliveryOperationsForHandle)(input.handle);
435
+ },
436
+ async invokeDeliveryOperation(input) {
437
+ return (0, feishu_1.invokeFeishuDeliveryOperation)(input.handle, input.operation, input.input);
438
+ },
354
439
  describeCapabilities() {
355
440
  return {
356
441
  sourceKind: "feishu_bot",
@@ -412,7 +497,7 @@ const FEISHU_BOT_MODULE = {
412
497
  function moduleConfigForSource(source) {
413
498
  const config = asRecord(source.config);
414
499
  if (source.sourceType === "remote_source") {
415
- return asRecord(config.profileConfig);
500
+ return asRecord(config.moduleConfig);
416
501
  }
417
502
  return config;
418
503
  }