@holon-run/agentinbox 0.6.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.
Files changed (34) hide show
  1. package/README.md +32 -7
  2. package/dist/src/adapters.js +61 -6
  3. package/dist/src/backend.js +4 -16
  4. package/dist/src/cli.js +307 -53
  5. package/dist/src/client.js +2 -2
  6. package/dist/src/daemon.js +36 -2
  7. package/dist/src/http.js +467 -45
  8. package/dist/src/logging.js +112 -0
  9. package/dist/src/paths.js +1 -0
  10. package/dist/src/runtime_gate.js +394 -63
  11. package/dist/src/service.js +856 -154
  12. package/dist/src/source_hosts.js +75 -0
  13. package/dist/src/source_resolution.js +20 -20
  14. package/dist/src/source_schema.js +2 -2
  15. package/dist/src/sources/feishu.js +66 -28
  16. package/dist/src/sources/github.js +188 -24
  17. package/dist/src/sources/remote.js +44 -24
  18. package/dist/src/sources/{remote_profiles.js → remote_modules.js} +143 -58
  19. package/dist/src/store.js +861 -106
  20. package/dist/src/terminal.js +33 -7
  21. package/dist/src/util.js +46 -1
  22. package/drizzle/migrations/{0000_initial.sql → 0000_v1_initial.sql} +182 -6
  23. package/drizzle/schema.ts +93 -1
  24. package/package.json +2 -1
  25. package/scripts/iterm2_cursor_probe.py +8 -3
  26. package/scripts/release_smoke.sh +123 -0
  27. package/drizzle/migrations/0001_inbox_items_source_occurred_at_idx.sql +0 -3
  28. package/drizzle/migrations/0002_subscription_cleanup_policy.sql +0 -45
  29. package/drizzle/migrations/0003_subscription_lifecycle_retirements.sql +0 -14
  30. package/drizzle/migrations/0004_source_idle_states.sql +0 -10
  31. package/drizzle/migrations/0005_timers.sql +0 -22
  32. package/drizzle/migrations/0006_terminal_runtime_pid.sql +0 -1
  33. package/drizzle/migrations/0007_activation_dispatch_last_notified_fingerprint.sql +0 -2
  34. 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
+ }
@@ -4,7 +4,7 @@ exports.resolveSourceIdentity = resolveSourceIdentity;
4
4
  exports.resolveSourceSchema = resolveSourceSchema;
5
5
  exports.withResolvedIdentity = withResolvedIdentity;
6
6
  const paths_1 = require("./paths");
7
- const remote_profiles_1 = require("./sources/remote_profiles");
7
+ const remote_modules_1 = require("./sources/remote_modules");
8
8
  const source_schema_1 = require("./source_schema");
9
9
  async function resolveSourceIdentity(source, context = {}) {
10
10
  if (source.sourceType === "local_event") {
@@ -14,7 +14,7 @@ async function resolveSourceIdentity(source, context = {}) {
14
14
  implementationId: "builtin.local_event",
15
15
  };
16
16
  }
17
- const builtinImplementationId = (0, remote_profiles_1.builtInProfileIdForSourceType)(source.sourceType);
17
+ const builtinImplementationId = (0, remote_modules_1.builtInModuleIdForSourceType)(source.sourceType);
18
18
  if (builtinImplementationId) {
19
19
  return {
20
20
  hostType: "remote_source",
@@ -25,30 +25,30 @@ 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 profileRegistry = context.profileRegistry ?? new remote_profiles_1.RemoteSourceProfileRegistry();
28
+ const moduleRegistry = context.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
29
29
  const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
30
- const profile = await profileRegistry.resolve(source, homeDir);
31
- const capabilityDescription = typeof profile.describeCapabilities === "function"
32
- ? profile.describeCapabilities(profileInputSource(source))
30
+ const module = await moduleRegistry.resolve(source, homeDir);
31
+ const capabilityDescription = typeof module.describeCapabilities === "function"
32
+ ? module.describeCapabilities(moduleInputSource(source))
33
33
  : undefined;
34
34
  return {
35
35
  hostType: "remote_source",
36
- sourceKind: capabilityDescription?.sourceKind?.trim() || `remote:${profile.id}`,
37
- implementationId: profile.id,
36
+ sourceKind: capabilityDescription?.sourceKind?.trim() || `remote:${module.id}`,
37
+ implementationId: module.id,
38
38
  };
39
39
  }
40
40
  async function resolveSourceSchema(source, context = {}) {
41
- const profileRegistry = context.profileRegistry ?? new remote_profiles_1.RemoteSourceProfileRegistry();
41
+ const moduleRegistry = context.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
42
42
  const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
43
- const resolvedContext = { ...context, profileRegistry, homeDir };
43
+ const resolvedContext = { ...context, moduleRegistry, homeDir };
44
44
  const identity = await resolveSourceIdentity(source, resolvedContext);
45
45
  const staticSchema = (0, source_schema_1.getSourceSchema)(source.sourceType);
46
46
  let capabilityDescription;
47
- let profile = null;
47
+ let module = null;
48
48
  if (identity.hostType === "remote_source") {
49
- profile = await profileRegistry.resolve(source, homeDir);
50
- capabilityDescription = typeof profile.describeCapabilities === "function"
51
- ? profile.describeCapabilities(profileInputSource(source))
49
+ module = await moduleRegistry.resolve(source, homeDir);
50
+ capabilityDescription = typeof module.describeCapabilities === "function"
51
+ ? module.describeCapabilities(moduleInputSource(source))
52
52
  : undefined;
53
53
  }
54
54
  return withResolvedIdentity(source.sourceId, {
@@ -60,10 +60,10 @@ async function resolveSourceSchema(source, context = {}) {
60
60
  }, identity, identity.hostType === "remote_source"
61
61
  ? {
62
62
  aliases: capabilityDescription?.aliases,
63
- supportsTrackedResourceRef: typeof profile?.deriveTrackedResource === "function",
64
- supportsLifecycleSignals: typeof profile?.projectLifecycleSignal === "function",
65
- shortcuts: typeof profile?.listSubscriptionShortcuts === "function"
66
- ? profile.listSubscriptionShortcuts(profileInputSource(source))
63
+ supportsTrackedResourceRef: typeof module?.deriveTrackedResource === "function",
64
+ supportsLifecycleSignals: typeof module?.projectLifecycleSignal === "function",
65
+ shortcuts: typeof module?.listSubscriptionShortcuts === "function"
66
+ ? module.listSubscriptionShortcuts(moduleInputSource(source))
67
67
  : [],
68
68
  }
69
69
  : undefined);
@@ -89,12 +89,12 @@ function withResolvedIdentity(sourceId, schema, identity, capabilityMetadata) {
89
89
  } : {}),
90
90
  };
91
91
  }
92
- function profileInputSource(source) {
92
+ function moduleInputSource(source) {
93
93
  if (source.sourceType !== "remote_source") {
94
94
  return source;
95
95
  }
96
96
  return {
97
97
  ...source,
98
- config: (0, remote_profiles_1.profileConfigForSource)(source),
98
+ config: (0, remote_modules_1.moduleConfigForSource)(source),
99
99
  };
100
100
  }
@@ -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 profile module path under $AGENTINBOX_HOME/source-profiles." },
25
- { name: "profileConfig", type: "object", required: false, description: "Profile-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 {};
@@ -4,7 +4,7 @@ exports.RemoteSourceRuntime = exports.RpcUxcRemoteSourceClient = void 0;
4
4
  const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
5
5
  const paths_1 = require("../paths");
6
6
  const util_1 = require("../util");
7
- const remote_profiles_1 = require("./remote_profiles");
7
+ const remote_modules_1 = require("./remote_modules");
8
8
  const REMOTE_SOURCE_TYPES = new Set([
9
9
  "remote_source",
10
10
  "github_repo",
@@ -55,7 +55,7 @@ exports.RpcUxcRemoteSourceClient = RpcUxcRemoteSourceClient;
55
55
  class RemoteSourceRuntime {
56
56
  store;
57
57
  appendSourceEvent;
58
- profileRegistry;
58
+ moduleRegistry;
59
59
  client;
60
60
  interval = null;
61
61
  inFlight = new Set();
@@ -65,7 +65,7 @@ class RemoteSourceRuntime {
65
65
  constructor(store, appendSourceEvent, options) {
66
66
  this.store = store;
67
67
  this.appendSourceEvent = appendSourceEvent;
68
- this.profileRegistry = options?.profileRegistry ?? new remote_profiles_1.RemoteSourceProfileRegistry();
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
  }
@@ -79,10 +79,10 @@ class RemoteSourceRuntime {
79
79
  if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
80
80
  return;
81
81
  }
82
- const profile = await this.profileRegistry.resolve(source, this.homeDir);
83
- const profileSource = profileInputSource(source);
84
- profile.validateConfig(profileSource);
85
- profile.buildManagedSourceSpec(profileSource);
82
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
83
+ const moduleSource = moduleInputSource(source);
84
+ module.validateConfig(moduleSource);
85
+ module.buildManagedSourceSpec(moduleSource);
86
86
  }
87
87
  async start() {
88
88
  if (this.interval) {
@@ -171,35 +171,55 @@ class RemoteSourceRuntime {
171
171
  if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
172
172
  return null;
173
173
  }
174
- const profile = await this.profileRegistry.resolve(source, this.homeDir);
175
- if (typeof profile.projectLifecycleSignal !== "function") {
174
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
175
+ if (typeof module.projectLifecycleSignal !== "function") {
176
176
  return null;
177
177
  }
178
- return profile.projectLifecycleSignal(rawPayload, profileInputSource(source));
178
+ return module.projectLifecycleSignal(rawPayload, moduleInputSource(source));
179
179
  }
180
180
  async expandSubscriptionShortcut(source, input) {
181
181
  if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
182
182
  return null;
183
183
  }
184
- const profile = await this.profileRegistry.resolve(source, this.homeDir);
185
- if (typeof profile.expandSubscriptionShortcut !== "function") {
184
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
185
+ if (typeof module.expandSubscriptionShortcut !== "function") {
186
186
  return null;
187
187
  }
188
- return profile.expandSubscriptionShortcut({
188
+ return module.expandSubscriptionShortcut({
189
189
  name: input.name,
190
190
  args: input.args,
191
- source: profileInputSource(source),
191
+ source: moduleInputSource(source),
192
192
  });
193
193
  }
194
194
  async deriveInlinePreview(source, item) {
195
195
  if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
196
196
  return null;
197
197
  }
198
- const profile = await this.profileRegistry.resolve(source, this.homeDir);
199
- if (typeof profile.deriveInlinePreview !== "function") {
198
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
199
+ if (typeof module.deriveInlinePreview !== "function") {
200
200
  return null;
201
201
  }
202
- return profile.deriveInlinePreview(item, profileInputSource(source));
202
+ return module.deriveInlinePreview(item, moduleInputSource(source));
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);
203
223
  }
204
224
  async syncAll() {
205
225
  const sources = this.store
@@ -255,10 +275,10 @@ class RemoteSourceRuntime {
255
275
  };
256
276
  }
257
277
  }
258
- const profile = await this.profileRegistry.resolve(source, this.homeDir);
259
- const profileSource = profileInputSource(source);
260
- profile.validateConfig(profileSource);
261
- const spec = profile.buildManagedSourceSpec(profileSource);
278
+ const module = await this.moduleRegistry.resolve(source, this.homeDir);
279
+ const moduleSource = moduleInputSource(source);
280
+ module.validateConfig(moduleSource);
281
+ const spec = module.buildManagedSourceSpec(moduleSource);
262
282
  const binding = managedBindingForSource(source);
263
283
  const ensured = await this.client.sourceEnsure({
264
284
  namespace: binding.namespace,
@@ -279,7 +299,7 @@ class RemoteSourceRuntime {
279
299
  if (Object.keys(rawPayload).length === 0) {
280
300
  continue;
281
301
  }
282
- const mapped = profile.mapRawEvent(rawPayload, profileSource);
302
+ const mapped = module.mapRawEvent(rawPayload, moduleSource);
283
303
  if (!mapped) {
284
304
  continue;
285
305
  }
@@ -348,14 +368,14 @@ class RemoteSourceRuntime {
348
368
  }
349
369
  }
350
370
  exports.RemoteSourceRuntime = RemoteSourceRuntime;
351
- function profileInputSource(source) {
371
+ function moduleInputSource(source) {
352
372
  if (source.sourceType !== "remote_source") {
353
373
  return source;
354
374
  }
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) {