@holon-run/agentinbox 0.7.0 → 1.0.0-beta.1

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 (33) hide show
  1. package/README.md +36 -7
  2. package/dist/src/adapters.js +55 -0
  3. package/dist/src/backend.js +4 -16
  4. package/dist/src/cli.js +329 -52
  5. package/dist/src/client.js +2 -2
  6. package/dist/src/http.js +479 -55
  7. package/dist/src/runtime_gate.js +49 -41
  8. package/dist/src/service.js +922 -200
  9. package/dist/src/source_hosts.js +75 -0
  10. package/dist/src/source_resolution.js +2 -2
  11. package/dist/src/source_schema.js +2 -2
  12. package/dist/src/sources/feishu.js +66 -28
  13. package/dist/src/sources/github.js +222 -36
  14. package/dist/src/sources/remote.js +50 -3
  15. package/dist/src/sources/remote_modules.js +100 -15
  16. package/dist/src/store.js +875 -110
  17. package/dist/src/terminal.js +33 -7
  18. package/dist/src/util.js +46 -1
  19. package/drizzle/migrations/{0000_initial.sql → 0000_v1_initial.sql} +182 -6
  20. package/drizzle/migrations/0002_host_scoped_lifecycle_retirements.sql +42 -0
  21. package/drizzle/migrations/0003_subscription_tracked_resource_indexes.sql +5 -0
  22. package/drizzle/schema.ts +98 -3
  23. package/package.json +2 -1
  24. package/scripts/release_smoke.sh +123 -0
  25. package/dist/src/sources/remote_profiles.js +0 -13
  26. package/drizzle/migrations/0001_inbox_items_source_occurred_at_idx.sql +0 -3
  27. package/drizzle/migrations/0002_subscription_cleanup_policy.sql +0 -45
  28. package/drizzle/migrations/0003_subscription_lifecycle_retirements.sql +0 -14
  29. package/drizzle/migrations/0004_source_idle_states.sql +0 -10
  30. package/drizzle/migrations/0005_timers.sql +0 -22
  31. package/drizzle/migrations/0006_terminal_runtime_pid.sql +0 -1
  32. package/drizzle/migrations/0007_activation_dispatch_last_notified_fingerprint.sql +0 -2
  33. package/drizzle/migrations/0008_inbox_item_sequence.sql +0 -21
@@ -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,7 +1,10 @@
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;
7
+ exports.canonicalGithubPullRequestRef = canonicalGithubPullRequestRef;
5
8
  exports.deriveGithubTrackedResource = deriveGithubTrackedResource;
6
9
  exports.githubSubscriptionShortcutSpec = githubSubscriptionShortcutSpec;
7
10
  exports.expandGithubSubscriptionShortcut = expandGithubSubscriptionShortcut;
@@ -48,6 +51,34 @@ class GithubUxcClient {
48
51
  options: { auth: input.auth },
49
52
  });
50
53
  }
54
+ async updateIssueState(input) {
55
+ await this.client.call({
56
+ endpoint: exports.GITHUB_ENDPOINT,
57
+ operation: "patch:/repos/{owner}/{repo}/issues/{issue_number}",
58
+ payload: {
59
+ owner: input.owner,
60
+ repo: input.repo,
61
+ issue_number: input.issueNumber,
62
+ state: input.state,
63
+ },
64
+ options: { auth: input.auth },
65
+ });
66
+ }
67
+ async mergePullRequest(input) {
68
+ await this.client.call({
69
+ endpoint: exports.GITHUB_ENDPOINT,
70
+ operation: "put:/repos/{owner}/{repo}/pulls/{pull_number}/merge",
71
+ payload: {
72
+ owner: input.owner,
73
+ repo: input.repo,
74
+ pull_number: input.pullNumber,
75
+ merge_method: input.mergeMethod ?? null,
76
+ commit_title: input.commitTitle ?? null,
77
+ commit_message: input.commitMessage ?? null,
78
+ },
79
+ options: { auth: input.auth },
80
+ });
81
+ }
51
82
  }
52
83
  exports.GithubUxcClient = GithubUxcClient;
53
84
  class GithubDeliveryAdapter {
@@ -57,33 +88,131 @@ class GithubDeliveryAdapter {
57
88
  }
58
89
  async send(request, attempt) {
59
90
  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}`);
91
+ return invokeGithubDeliveryOperation(attempt, canonicalGithubOperationForSurface(attempt.surface), { body: payloadText }, this.client);
84
92
  }
85
93
  }
86
94
  exports.GithubDeliveryAdapter = GithubDeliveryAdapter;
95
+ function githubDeliveryOperationsForHandle(handle) {
96
+ if (handle.surface === "review_comment") {
97
+ return [{
98
+ name: "reply_in_review_thread",
99
+ title: "Reply In Review Thread",
100
+ inputSchema: deliveryBodySchema(),
101
+ canonicalTextAlias: true,
102
+ }];
103
+ }
104
+ if (handle.surface === "issue_comment") {
105
+ return [
106
+ {
107
+ name: "add_comment",
108
+ title: "Add Comment",
109
+ inputSchema: deliveryBodySchema(),
110
+ canonicalTextAlias: true,
111
+ },
112
+ {
113
+ name: "close_issue",
114
+ title: "Close Issue",
115
+ inputSchema: emptyObjectSchema(),
116
+ },
117
+ {
118
+ name: "reopen_issue",
119
+ title: "Reopen Issue",
120
+ inputSchema: emptyObjectSchema(),
121
+ },
122
+ ];
123
+ }
124
+ if (handle.surface === "pull_request_comment") {
125
+ return [
126
+ {
127
+ name: "add_comment",
128
+ title: "Add Comment",
129
+ inputSchema: deliveryBodySchema(),
130
+ canonicalTextAlias: true,
131
+ },
132
+ {
133
+ name: "close_issue",
134
+ title: "Close Pull Request",
135
+ inputSchema: emptyObjectSchema(),
136
+ },
137
+ {
138
+ name: "reopen_issue",
139
+ title: "Reopen Pull Request",
140
+ inputSchema: emptyObjectSchema(),
141
+ },
142
+ {
143
+ name: "merge_pull_request",
144
+ title: "Merge Pull Request",
145
+ inputSchema: {
146
+ type: "object",
147
+ additionalProperties: false,
148
+ properties: {
149
+ mergeMethod: { type: "string", enum: ["merge", "squash", "rebase"] },
150
+ commitTitle: { type: "string" },
151
+ commitMessage: { type: "string" },
152
+ },
153
+ },
154
+ },
155
+ ];
156
+ }
157
+ return [];
158
+ }
159
+ async function invokeGithubDeliveryOperation(handle, operation, input, client = new GithubUxcClient()) {
160
+ const target = parseTargetRef(handle.targetRef);
161
+ if (operation === "add_comment") {
162
+ const body = requireDeliveryBody(input, operation);
163
+ await client.createIssueComment({
164
+ owner: target.owner,
165
+ repo: target.repo,
166
+ issueNumber: target.number,
167
+ body,
168
+ });
169
+ return { status: "sent", note: "sent GitHub issue comment" };
170
+ }
171
+ if (operation === "reply_in_review_thread") {
172
+ const body = requireDeliveryBody(input, operation);
173
+ if (!handle.threadRef || !handle.threadRef.startsWith("review_comment:")) {
174
+ throw new Error("reply_in_review_thread requires threadRef=review_comment:<id>");
175
+ }
176
+ await client.replyToReviewComment({
177
+ owner: target.owner,
178
+ repo: target.repo,
179
+ pullNumber: target.number,
180
+ commentId: Number(handle.threadRef.slice("review_comment:".length)),
181
+ body,
182
+ });
183
+ return { status: "sent", note: "sent GitHub review comment reply" };
184
+ }
185
+ if (operation === "close_issue") {
186
+ await client.updateIssueState({
187
+ owner: target.owner,
188
+ repo: target.repo,
189
+ issueNumber: target.number,
190
+ state: "closed",
191
+ });
192
+ return { status: "sent", note: "closed GitHub issue" };
193
+ }
194
+ if (operation === "reopen_issue") {
195
+ await client.updateIssueState({
196
+ owner: target.owner,
197
+ repo: target.repo,
198
+ issueNumber: target.number,
199
+ state: "open",
200
+ });
201
+ return { status: "sent", note: "reopened GitHub issue" };
202
+ }
203
+ if (operation === "merge_pull_request") {
204
+ await client.mergePullRequest({
205
+ owner: target.owner,
206
+ repo: target.repo,
207
+ pullNumber: target.number,
208
+ mergeMethod: asMergeMethod(input.mergeMethod),
209
+ commitTitle: asString(input.commitTitle) ?? undefined,
210
+ commitMessage: asString(input.commitMessage) ?? undefined,
211
+ });
212
+ return { status: "sent", note: "merged GitHub pull request" };
213
+ }
214
+ throw new Error(`unknown GitHub delivery operation: ${operation}`);
215
+ }
87
216
  function normalizeGithubRepoEvent(source, config, raw) {
88
217
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
89
218
  return null;
@@ -154,7 +283,11 @@ function normalizeGithubRepoEvent(source, config, raw) {
154
283
  deliveryHandle,
155
284
  };
156
285
  }
157
- function deriveGithubTrackedResource(filter) {
286
+ function canonicalGithubPullRequestRef(source, number) {
287
+ const config = parseGithubSourceConfig(source);
288
+ return `repo:${config.owner}/${config.repo}:pr:${number}`;
289
+ }
290
+ function deriveGithubTrackedResource(filter, source) {
158
291
  const metadata = asRecord(filter.metadata);
159
292
  const payload = asRecord(filter.payload);
160
293
  const number = asNumber(metadata.number) ?? asNumber(payload.number) ?? asNumber(payload.ref);
@@ -162,13 +295,16 @@ function deriveGithubTrackedResource(filter) {
162
295
  if (!number || isPullRequest !== true) {
163
296
  return null;
164
297
  }
165
- return { ref: `pr:${number}` };
298
+ return { ref: canonicalGithubPullRequestRef(source, number) };
166
299
  }
167
300
  function githubSubscriptionShortcutSpec() {
168
301
  return [{
169
302
  name: "pr",
170
303
  description: "Follow one pull request and auto-retire when it closes.",
171
- argsSchema: [{ name: "number", type: "number", required: true, description: "Pull request number." }],
304
+ argsSchema: [
305
+ { name: "number", type: "number", required: true, description: "Pull request number." },
306
+ { name: "withCi", type: "boolean", required: false, description: "Also create a sibling ci_runs subscription under the same host and stream key." },
307
+ ],
172
308
  }];
173
309
  }
174
310
  function expandGithubSubscriptionShortcut(input) {
@@ -179,18 +315,32 @@ function expandGithubSubscriptionShortcut(input) {
179
315
  if (!number || !Number.isInteger(number) || number <= 0) {
180
316
  throw new Error("subscription shortcut pr requires a positive integer number");
181
317
  }
318
+ const trackedResourceRef = canonicalGithubPullRequestRef(input.source, number);
319
+ const withCi = input.args?.withCi === true;
182
320
  return {
183
- filter: {
184
- metadata: {
185
- number,
186
- isPullRequest: true,
321
+ members: [
322
+ {
323
+ streamKind: "repo_events",
324
+ filter: {
325
+ metadata: {
326
+ number,
327
+ isPullRequest: true,
328
+ },
329
+ },
330
+ trackedResourceRef,
331
+ cleanupPolicy: { mode: "on_terminal" },
187
332
  },
188
- },
189
- trackedResourceRef: `pr:${number}`,
190
- cleanupPolicy: { mode: "on_terminal" },
333
+ ...(withCi
334
+ ? [{
335
+ streamKind: "ci_runs",
336
+ trackedResourceRef,
337
+ cleanupPolicy: { mode: "on_terminal" },
338
+ }]
339
+ : []),
340
+ ],
191
341
  };
192
342
  }
193
- function projectGithubLifecycleSignal(rawPayload) {
343
+ function projectGithubLifecycleSignal(rawPayload, source) {
194
344
  const eventType = asString(rawPayload.type);
195
345
  if (eventType !== "PullRequestEvent") {
196
346
  return null;
@@ -206,7 +356,7 @@ function projectGithubLifecycleSignal(rawPayload) {
206
356
  }
207
357
  const merged = action === "merged" || asBoolean(rawPayload.merged) === true || asBoolean(pullRequest.merged) === true;
208
358
  return {
209
- ref: `pr:${number}`,
359
+ ref: canonicalGithubPullRequestRef(source, number),
210
360
  terminal: true,
211
361
  state: "closed",
212
362
  result: merged ? "merged" : "closed",
@@ -300,6 +450,42 @@ function stringifyDeliveryPayload(payload) {
300
450
  }
301
451
  return JSON.stringify(payload, null, 2);
302
452
  }
453
+ function canonicalGithubOperationForSurface(surface) {
454
+ if (surface === "issue_comment" || surface === "pull_request_comment") {
455
+ return "add_comment";
456
+ }
457
+ if (surface === "review_comment") {
458
+ return "reply_in_review_thread";
459
+ }
460
+ throw new Error(`deliver send only supports canonical GitHub text surfaces; use deliver invoke for ${surface}`);
461
+ }
462
+ function deliveryBodySchema() {
463
+ return {
464
+ type: "object",
465
+ additionalProperties: false,
466
+ required: ["body"],
467
+ properties: {
468
+ body: { type: "string", minLength: 1 },
469
+ },
470
+ };
471
+ }
472
+ function emptyObjectSchema() {
473
+ return {
474
+ type: "object",
475
+ additionalProperties: false,
476
+ properties: {},
477
+ };
478
+ }
479
+ function requireDeliveryBody(input, operation) {
480
+ const body = asString(input.body);
481
+ if (!body || body.trim().length === 0) {
482
+ throw new Error(`${operation} requires input.body`);
483
+ }
484
+ return body;
485
+ }
486
+ function asMergeMethod(value) {
487
+ return value === "merge" || value === "squash" || value === "rebase" ? value : undefined;
488
+ }
303
489
  function asRecord(value) {
304
490
  if (!value || typeof value !== "object" || Array.isArray(value)) {
305
491
  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
  }
@@ -185,11 +185,12 @@ class RemoteSourceRuntime {
185
185
  if (typeof module.expandSubscriptionShortcut !== "function") {
186
186
  return null;
187
187
  }
188
- return module.expandSubscriptionShortcut({
188
+ const expanded = module.expandSubscriptionShortcut({
189
189
  name: input.name,
190
190
  args: input.args,
191
191
  source: moduleInputSource(source),
192
192
  });
193
+ return normalizeExpandedSubscriptionPlan(expanded);
193
194
  }
194
195
  async deriveInlinePreview(source, item) {
195
196
  if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
@@ -201,6 +202,26 @@ class RemoteSourceRuntime {
201
202
  }
202
203
  return module.deriveInlinePreview(item, moduleInputSource(source));
203
204
  }
205
+ async deriveNotificationGrouping(source, item) {
206
+ if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
207
+ return null;
208
+ }
209
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
210
+ if (typeof module.deriveNotificationGrouping !== "function") {
211
+ return null;
212
+ }
213
+ return module.deriveNotificationGrouping(item, moduleInputSource(source));
214
+ }
215
+ async summarizeDigestThread(source, items, grouping) {
216
+ if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
217
+ return null;
218
+ }
219
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
220
+ if (typeof module.summarizeDigestThread !== "function") {
221
+ return null;
222
+ }
223
+ return module.summarizeDigestThread(items, moduleInputSource(source), grouping);
224
+ }
204
225
  async syncAll() {
205
226
  const sources = this.store
206
227
  .listSources()
@@ -348,6 +369,32 @@ class RemoteSourceRuntime {
348
369
  }
349
370
  }
350
371
  exports.RemoteSourceRuntime = RemoteSourceRuntime;
372
+ function normalizeExpandedSubscriptionPlan(value) {
373
+ if (!value) {
374
+ return null;
375
+ }
376
+ if (isExpandedSubscriptionPlan(value)) {
377
+ return {
378
+ members: value.members.map((member) => ({
379
+ streamKind: typeof member.streamKind === "string" ? member.streamKind : null,
380
+ filter: member.filter,
381
+ trackedResourceRef: member.trackedResourceRef ?? null,
382
+ cleanupPolicy: member.cleanupPolicy ?? null,
383
+ })),
384
+ };
385
+ }
386
+ return {
387
+ members: [{
388
+ streamKind: null,
389
+ filter: value.filter,
390
+ trackedResourceRef: value.trackedResourceRef ?? null,
391
+ cleanupPolicy: value.cleanupPolicy ?? null,
392
+ }],
393
+ };
394
+ }
395
+ function isExpandedSubscriptionPlan(value) {
396
+ return "members" in value && Array.isArray(value.members);
397
+ }
351
398
  function moduleInputSource(source) {
352
399
  if (source.sourceType !== "remote_source") {
353
400
  return source;
@@ -355,7 +402,7 @@ function moduleInputSource(source) {
355
402
  const config = asRecord(source.config);
356
403
  return {
357
404
  ...source,
358
- config: asRecord(config.profileConfig),
405
+ config: asRecord(config.moduleConfig),
359
406
  };
360
407
  }
361
408
  function defaultManagedBindingForSource(source) {