@holon-run/agentinbox 0.1.4 → 0.3.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,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveSourceIdentity = resolveSourceIdentity;
4
+ exports.resolveSourceSchema = resolveSourceSchema;
5
+ exports.withResolvedIdentity = withResolvedIdentity;
6
+ const paths_1 = require("./paths");
7
+ const remote_profiles_1 = require("./sources/remote_profiles");
8
+ const source_schema_1 = require("./source_schema");
9
+ async function resolveSourceIdentity(source, context = {}) {
10
+ if (source.sourceType === "local_event") {
11
+ return {
12
+ hostType: "local_event",
13
+ sourceKind: "local_event",
14
+ implementationId: "builtin.local_event",
15
+ };
16
+ }
17
+ const builtinImplementationId = (0, remote_profiles_1.builtInProfileIdForSourceType)(source.sourceType);
18
+ if (builtinImplementationId) {
19
+ return {
20
+ hostType: "remote_source",
21
+ sourceKind: source.sourceType,
22
+ implementationId: builtinImplementationId,
23
+ };
24
+ }
25
+ if (source.sourceType !== "remote_source") {
26
+ throw new Error(`unsupported source type for source resolution: ${source.sourceType}`);
27
+ }
28
+ const profileRegistry = context.profileRegistry ?? new remote_profiles_1.RemoteSourceProfileRegistry();
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))
33
+ : undefined;
34
+ return {
35
+ hostType: "remote_source",
36
+ sourceKind: capabilityDescription?.sourceKind?.trim() || `remote:${profile.id}`,
37
+ implementationId: profile.id,
38
+ };
39
+ }
40
+ async function resolveSourceSchema(source, context = {}) {
41
+ const profileRegistry = context.profileRegistry ?? new remote_profiles_1.RemoteSourceProfileRegistry();
42
+ const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
43
+ const resolvedContext = { ...context, profileRegistry, homeDir };
44
+ const identity = await resolveSourceIdentity(source, resolvedContext);
45
+ const staticSchema = (0, source_schema_1.getSourceSchema)(source.sourceType);
46
+ let capabilityDescription;
47
+ let profile = null;
48
+ if (identity.hostType === "remote_source") {
49
+ profile = await profileRegistry.resolve(source, homeDir);
50
+ capabilityDescription = typeof profile.describeCapabilities === "function"
51
+ ? profile.describeCapabilities(profileInputSource(source))
52
+ : undefined;
53
+ }
54
+ return withResolvedIdentity(source.sourceId, {
55
+ sourceType: staticSchema.sourceType,
56
+ metadataFields: capabilityDescription?.metadataFields ?? staticSchema.metadataFields,
57
+ payloadExamples: capabilityDescription?.payloadExamples ?? staticSchema.payloadExamples,
58
+ eventVariantExamples: capabilityDescription?.eventVariantExamples ?? staticSchema.eventVariantExamples,
59
+ configFields: capabilityDescription?.configSchema ?? staticSchema.configFields,
60
+ }, identity, identity.hostType === "remote_source"
61
+ ? {
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))
67
+ : [],
68
+ }
69
+ : undefined);
70
+ }
71
+ function withResolvedIdentity(sourceId, schema, identity, capabilityMetadata) {
72
+ return {
73
+ sourceId,
74
+ sourceType: schema.sourceType,
75
+ metadataFields: schema.metadataFields,
76
+ payloadExamples: schema.payloadExamples,
77
+ eventVariantExamples: schema.eventVariantExamples,
78
+ configFields: schema.configFields,
79
+ hostType: identity.hostType,
80
+ sourceKind: identity.sourceKind,
81
+ implementationId: identity.implementationId,
82
+ ...(capabilityMetadata?.aliases?.length ? { aliases: capabilityMetadata.aliases } : {}),
83
+ ...(capabilityMetadata ? {
84
+ subscriptionSchema: {
85
+ supportsTrackedResourceRef: capabilityMetadata.supportsTrackedResourceRef ?? false,
86
+ supportsLifecycleSignals: capabilityMetadata.supportsLifecycleSignals ?? false,
87
+ shortcuts: capabilityMetadata.shortcuts ?? [],
88
+ },
89
+ } : {}),
90
+ };
91
+ }
92
+ function profileInputSource(source) {
93
+ if (source.sourceType !== "remote_source") {
94
+ return source;
95
+ }
96
+ return {
97
+ ...source,
98
+ config: (0, remote_profiles_1.profileConfigForSource)(source),
99
+ };
100
+ }
@@ -32,6 +32,7 @@ const SOURCE_SCHEMAS = {
32
32
  { name: "action", type: "string", description: "GitHub event action suffix such as created." },
33
33
  { name: "author", type: "string|null", description: "Actor login for the event." },
34
34
  { name: "isPullRequest", type: "boolean", description: "Whether the event targets a pull request surface." },
35
+ { name: "reviewState", type: "string|null", description: "Review decision state for PullRequestReviewEvent such as approved or changes_requested." },
35
36
  { name: "labels", type: "string[]", description: "Labels extracted from the issue or pull request." },
36
37
  { name: "mentions", type: "string[]", description: "Mention handles extracted from title/body/comment text." },
37
38
  { name: "number", type: "number|null", description: "Issue or pull request number when present." },
@@ -49,8 +50,29 @@ const SOURCE_SCHEMAS = {
49
50
  issue: { number: 12, title: "Track filtering work" },
50
51
  comment: { body: "@alpha please look" },
51
52
  },
53
+ {
54
+ id: "1234567891",
55
+ type: "PullRequestReviewEvent",
56
+ action: "created",
57
+ actor: "Copilot",
58
+ pull_request: { number: 67, title: "feat: add remote module capability hooks" },
59
+ review: { state: "commented", body: "review summary" },
60
+ },
61
+ {
62
+ id: "1234567892",
63
+ type: "PullRequestEvent",
64
+ action: "closed",
65
+ actor: "jolestar",
66
+ pull_request: { number: 72, title: "feat: add cleanup policy lifecycle engine", merged: true },
67
+ },
68
+ ],
69
+ eventVariantExamples: [
70
+ "IssueCommentEvent.created",
71
+ "PullRequestEvent.opened",
72
+ "PullRequestEvent.closed",
73
+ "PullRequestReviewEvent.created",
74
+ "PullRequestReviewCommentEvent.created",
52
75
  ],
53
- eventVariantExamples: ["IssueCommentEvent.created", "PullRequestEvent.opened", "PullRequestReviewCommentEvent.created"],
54
76
  configFields: [
55
77
  { name: "owner", type: "string", required: true, description: "GitHub repository owner." },
56
78
  { name: "repo", type: "string", required: true, description: "GitHub repository name." },
@@ -2,6 +2,10 @@
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
4
  exports.normalizeGithubRepoEvent = normalizeGithubRepoEvent;
5
+ exports.deriveGithubTrackedResource = deriveGithubTrackedResource;
6
+ exports.githubSubscriptionShortcutSpec = githubSubscriptionShortcutSpec;
7
+ exports.expandGithubSubscriptionShortcut = expandGithubSubscriptionShortcut;
8
+ exports.projectGithubLifecycleSignal = projectGithubLifecycleSignal;
5
9
  exports.parseGithubSourceConfig = parseGithubSourceConfig;
6
10
  const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
7
11
  exports.GITHUB_ENDPOINT = "https://api.github.com";
@@ -9,6 +13,7 @@ exports.DEFAULT_GITHUB_EVENT_TYPES = [
9
13
  "IssuesEvent",
10
14
  "IssueCommentEvent",
11
15
  "PullRequestEvent",
16
+ "PullRequestReviewEvent",
12
17
  "PullRequestReviewCommentEvent",
13
18
  ];
14
19
  class GithubUxcClient {
@@ -97,15 +102,18 @@ function normalizeGithubRepoEvent(source, config, raw) {
97
102
  const repo = asRecord(event.repo);
98
103
  const issue = asRecord(payload.issue);
99
104
  const pullRequest = asRecord(payload.pull_request);
105
+ const review = asRecord(payload.review);
100
106
  const comment = asRecord(payload.comment);
101
107
  const action = asString(payload.action) ?? "observed";
102
108
  const number = asNumber(issue.number) ?? asNumber(pullRequest.number);
103
109
  const isPullRequest = eventType.startsWith("PullRequest");
104
110
  const title = asString(issue.title) ?? asString(pullRequest.title) ?? null;
105
- const body = asString(comment.body) ?? asString(issue.body) ?? asString(pullRequest.body) ?? null;
111
+ const reviewState = asString(review.state) ?? null;
112
+ const body = asString(comment.body) ?? asString(review.body) ?? asString(issue.body) ?? asString(pullRequest.body) ?? null;
106
113
  const labels = extractLabels(issue.labels);
107
114
  const mentions = extractMentions(title, body);
108
115
  const url = asString(comment.html_url) ??
116
+ asString(review.html_url) ??
109
117
  asString(issue.html_url) ??
110
118
  asString(pullRequest.html_url) ??
111
119
  asString(event.url);
@@ -125,6 +133,7 @@ function normalizeGithubRepoEvent(source, config, raw) {
125
133
  number,
126
134
  author: asString(actor.login) ?? null,
127
135
  isPullRequest,
136
+ reviewState,
128
137
  labels,
129
138
  mentions,
130
139
  title,
@@ -138,11 +147,71 @@ function normalizeGithubRepoEvent(source, config, raw) {
138
147
  actor: actor.login ?? null,
139
148
  issue,
140
149
  pull_request: pullRequest,
150
+ review,
141
151
  comment,
152
+ merged: asBoolean(pullRequest.merged),
142
153
  },
143
154
  deliveryHandle,
144
155
  };
145
156
  }
157
+ function deriveGithubTrackedResource(filter) {
158
+ const metadata = asRecord(filter.metadata);
159
+ const payload = asRecord(filter.payload);
160
+ const number = asNumber(metadata.number) ?? asNumber(payload.number) ?? asNumber(payload.ref);
161
+ const isPullRequest = asBoolean(metadata.isPullRequest);
162
+ if (!number || isPullRequest !== true) {
163
+ return null;
164
+ }
165
+ return { ref: `pr:${number}` };
166
+ }
167
+ function githubSubscriptionShortcutSpec() {
168
+ return [{
169
+ name: "pr",
170
+ description: "Follow one pull request and auto-retire when it closes.",
171
+ argsSchema: [{ name: "number", type: "number", required: true, description: "Pull request number." }],
172
+ }];
173
+ }
174
+ function expandGithubSubscriptionShortcut(input) {
175
+ if (input.name !== "pr") {
176
+ return null;
177
+ }
178
+ const number = typeof input.args?.number === "number" ? input.args.number : null;
179
+ if (!number || !Number.isInteger(number) || number <= 0) {
180
+ throw new Error("subscription shortcut pr requires a positive integer number");
181
+ }
182
+ return {
183
+ filter: {
184
+ metadata: {
185
+ number,
186
+ isPullRequest: true,
187
+ },
188
+ },
189
+ trackedResourceRef: `pr:${number}`,
190
+ cleanupPolicy: { mode: "on_terminal" },
191
+ };
192
+ }
193
+ function projectGithubLifecycleSignal(rawPayload) {
194
+ const eventType = asString(rawPayload.type);
195
+ if (eventType !== "PullRequestEvent") {
196
+ return null;
197
+ }
198
+ const action = asString(rawPayload.action);
199
+ if (action !== "closed" && action !== "merged") {
200
+ return null;
201
+ }
202
+ const pullRequest = asRecord(rawPayload.pull_request);
203
+ const number = asNumber(pullRequest.number);
204
+ if (!number) {
205
+ return null;
206
+ }
207
+ const merged = action === "merged" || asBoolean(rawPayload.merged) === true || asBoolean(pullRequest.merged) === true;
208
+ return {
209
+ ref: `pr:${number}`,
210
+ terminal: true,
211
+ state: "closed",
212
+ result: merged ? "merged" : "closed",
213
+ };
214
+ }
146
215
  function buildGithubDeliveryHandle(config, eventType, number, comment) {
147
216
  if (!number) {
148
217
  return null;
@@ -243,6 +312,9 @@ function asString(value) {
243
312
  function asNumber(value) {
244
313
  return typeof value === "number" ? value : null;
245
314
  }
315
+ function asBoolean(value) {
316
+ return typeof value === "boolean" ? value : null;
317
+ }
246
318
  function asStringArray(value) {
247
319
  if (!Array.isArray(value)) {
248
320
  return null;
@@ -167,6 +167,30 @@ class RemoteSourceRuntime {
167
167
  erroredSourceIds: Array.from(this.errorCounts.keys()).sort(),
168
168
  };
169
169
  }
170
+ async projectLifecycleSignal(source, rawPayload) {
171
+ if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
172
+ return null;
173
+ }
174
+ const profile = await this.profileRegistry.resolve(source, this.homeDir);
175
+ if (typeof profile.projectLifecycleSignal !== "function") {
176
+ return null;
177
+ }
178
+ return profile.projectLifecycleSignal(rawPayload, profileInputSource(source));
179
+ }
180
+ async expandSubscriptionShortcut(source, input) {
181
+ if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
182
+ return null;
183
+ }
184
+ const profile = await this.profileRegistry.resolve(source, this.homeDir);
185
+ if (typeof profile.expandSubscriptionShortcut !== "function") {
186
+ return null;
187
+ }
188
+ return profile.expandSubscriptionShortcut({
189
+ name: input.name,
190
+ args: input.args,
191
+ source: profileInputSource(source),
192
+ });
193
+ }
170
194
  async syncAll() {
171
195
  const sources = this.store
172
196
  .listSources()
@@ -90,6 +90,16 @@ function validateProfileContract(profile, sourcePath) {
90
90
  if (typeof profile.mapRawEvent !== "function") {
91
91
  throw new Error(`remote_source profile missing mapRawEvent(): ${sourcePath}`);
92
92
  }
93
+ validateOptionalHook(profile.describeCapabilities, "describeCapabilities", sourcePath);
94
+ validateOptionalHook(profile.listSubscriptionShortcuts, "listSubscriptionShortcuts", sourcePath);
95
+ validateOptionalHook(profile.expandSubscriptionShortcut, "expandSubscriptionShortcut", sourcePath);
96
+ validateOptionalHook(profile.deriveTrackedResource, "deriveTrackedResource", sourcePath);
97
+ validateOptionalHook(profile.projectLifecycleSignal, "projectLifecycleSignal", sourcePath);
98
+ }
99
+ function validateOptionalHook(value, name, sourcePath) {
100
+ if (value !== undefined && typeof value !== "function") {
101
+ throw new Error(`remote_source profile ${name} must be a function when provided: ${sourcePath}`);
102
+ }
93
103
  }
94
104
  function resolveAndValidateProfilePath(profileRoot, profilePath) {
95
105
  const resolved = node_path_1.default.isAbsolute(profilePath)
@@ -120,6 +130,71 @@ function asNonEmptyString(value) {
120
130
  }
121
131
  const GITHUB_REPO_PROFILE = {
122
132
  id: "builtin.github_repo",
133
+ describeCapabilities(source) {
134
+ const config = (0, github_1.parseGithubSourceConfig)(source);
135
+ return {
136
+ sourceKind: "github_repo",
137
+ aliases: ["github_repo"],
138
+ configSchema: [
139
+ { name: "owner", type: "string", required: true, description: "GitHub repository owner." },
140
+ { name: "repo", type: "string", required: true, description: "GitHub repository name." },
141
+ { name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
142
+ { name: "eventTypes", type: "string[]", required: false, description: "Optional GitHub event type allowlist." },
143
+ { name: "perPage", type: "number", required: false, description: `Repository events requested per poll. Default ${config.perPage ?? 10}.` },
144
+ { name: "pollIntervalSecs", type: "number", required: false, description: `Polling interval in seconds. Default ${config.pollIntervalSecs ?? 30}.` },
145
+ ],
146
+ metadataFields: [
147
+ { name: "eventType", type: "string", description: "GitHub event type such as IssueCommentEvent." },
148
+ { name: "action", type: "string", description: "GitHub event action suffix such as created." },
149
+ { name: "author", type: "string|null", description: "Actor login for the event." },
150
+ { name: "isPullRequest", type: "boolean", description: "Whether the event targets a pull request surface." },
151
+ { name: "reviewState", type: "string|null", description: "Review decision state for PullRequestReviewEvent such as approved or changes_requested." },
152
+ { name: "labels", type: "string[]", description: "Labels extracted from the issue or pull request." },
153
+ { name: "mentions", type: "string[]", description: "Mention handles extracted from title/body/comment text." },
154
+ { name: "number", type: "number|null", description: "Issue or pull request number when present." },
155
+ { name: "repoFullName", type: "string", description: "Repository full name in owner/repo form." },
156
+ { name: "title", type: "string|null", description: "Issue, pull request, or comment title." },
157
+ { name: "body", type: "string|null", description: "Issue, pull request, or comment body text." },
158
+ { name: "url", type: "string|null", description: "Primary GitHub HTML URL for the event target." },
159
+ ],
160
+ payloadExamples: [
161
+ {
162
+ id: "1234567891",
163
+ type: "PullRequestReviewEvent",
164
+ action: "created",
165
+ actor: "Copilot",
166
+ pull_request: { number: 67, title: "feat: add remote module capability hooks" },
167
+ review: { state: "commented", body: "review summary" },
168
+ },
169
+ {
170
+ id: "1234567892",
171
+ type: "PullRequestEvent",
172
+ action: "closed",
173
+ actor: "jolestar",
174
+ pull_request: { number: 72, title: "feat: add cleanup policy lifecycle engine", merged: true },
175
+ },
176
+ ],
177
+ eventVariantExamples: [
178
+ "IssueCommentEvent.created",
179
+ "PullRequestEvent.opened",
180
+ "PullRequestEvent.closed",
181
+ "PullRequestReviewEvent.created",
182
+ "PullRequestReviewCommentEvent.created",
183
+ ],
184
+ };
185
+ },
186
+ listSubscriptionShortcuts() {
187
+ return (0, github_1.githubSubscriptionShortcutSpec)();
188
+ },
189
+ expandSubscriptionShortcut(input) {
190
+ return (0, github_1.expandGithubSubscriptionShortcut)(input);
191
+ },
192
+ deriveTrackedResource(filter) {
193
+ return (0, github_1.deriveGithubTrackedResource)(filter);
194
+ },
195
+ projectLifecycleSignal(rawPayload) {
196
+ return (0, github_1.projectGithubLifecycleSignal)(rawPayload);
197
+ },
123
198
  validateConfig(source) {
124
199
  (0, github_1.parseGithubSourceConfig)(source);
125
200
  },
@@ -164,6 +239,34 @@ const GITHUB_REPO_PROFILE = {
164
239
  };
165
240
  const GITHUB_REPO_CI_PROFILE = {
166
241
  id: "builtin.github_repo_ci",
242
+ describeCapabilities() {
243
+ return {
244
+ sourceKind: "github_repo_ci",
245
+ aliases: ["github_repo_ci"],
246
+ configSchema: [
247
+ { name: "owner", type: "string", required: true, description: "GitHub repository owner." },
248
+ { name: "repo", type: "string", required: true, description: "GitHub repository name." },
249
+ { name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
250
+ { name: "pollIntervalSecs", type: "number", required: false, description: "Polling interval in seconds." },
251
+ { name: "perPage", type: "number", required: false, description: "Workflow runs requested per poll." },
252
+ { name: "eventFilter", type: "string", required: false, description: "Optional GitHub workflow event filter." },
253
+ { name: "branch", type: "string", required: false, description: "Optional branch filter for workflow runs." },
254
+ { name: "statusFilter", type: "string", required: false, description: "Optional workflow status filter." },
255
+ ],
256
+ metadataFields: [
257
+ { name: "name", type: "string|null", description: "Workflow run name." },
258
+ { name: "status", type: "string", description: "Normalized workflow run status." },
259
+ { name: "conclusion", type: "string|null", description: "Workflow run conclusion when completed." },
260
+ { name: "event", type: "string|null", description: "GitHub trigger event for the workflow run." },
261
+ { name: "headBranch", type: "string|null", description: "Head branch for the workflow run." },
262
+ { name: "headSha", type: "string|null", description: "Head commit SHA for the workflow run." },
263
+ { name: "actor", type: "string|null", description: "Actor login for the workflow run." },
264
+ { name: "commitMessage", type: "string|null", description: "Head commit message when present." },
265
+ { name: "htmlUrl", type: "string|null", description: "GitHub Actions run URL." },
266
+ ],
267
+ eventVariantExamples: ["workflow_run.ci.completed.failure", "workflow_run.nightly_checks.observed"],
268
+ };
269
+ },
167
270
  validateConfig(source) {
168
271
  (0, github_ci_1.parseGithubCiSourceConfig)(source);
169
272
  },
@@ -217,6 +320,36 @@ const GITHUB_REPO_CI_PROFILE = {
217
320
  };
218
321
  const FEISHU_BOT_PROFILE = {
219
322
  id: "builtin.feishu_bot",
323
+ describeCapabilities() {
324
+ return {
325
+ sourceKind: "feishu_bot",
326
+ aliases: ["feishu_bot"],
327
+ configSchema: [
328
+ { name: "appId", type: "string", required: true, description: "Feishu app ID." },
329
+ { name: "appSecret", type: "string", required: true, description: "Feishu app secret." },
330
+ { name: "eventTypes", type: "string[]", required: false, description: "Optional Feishu event type allowlist." },
331
+ { name: "chatIds", type: "string[]", required: false, description: "Optional Feishu chat allowlist." },
332
+ { name: "schemaUrl", type: "string", required: false, description: "Optional Feishu OpenAPI schema URL." },
333
+ { name: "replyInThread", type: "boolean", required: false, description: "Reply in thread when sending outbound messages." },
334
+ { name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
335
+ ],
336
+ metadataFields: [
337
+ { name: "eventType", type: "string", description: "Feishu event type." },
338
+ { name: "chatId", type: "string", description: "Target chat ID." },
339
+ { name: "chatType", type: "string|null", description: "Feishu chat type." },
340
+ { name: "messageId", type: "string", description: "Feishu message ID." },
341
+ { name: "messageType", type: "string", description: "Feishu message type such as text." },
342
+ { name: "senderOpenId", type: "string|null", description: "Sender open_id when present." },
343
+ { name: "senderType", type: "string|null", description: "Sender type." },
344
+ { name: "mentions", type: "string[]", description: "Mention names extracted from the message." },
345
+ { name: "mentionOpenIds", type: "string[]", description: "Mention open_ids extracted from the message." },
346
+ { name: "content", type: "string|null", description: "Normalized message content string." },
347
+ { name: "threadId", type: "string|null", description: "Thread or root message ID when present." },
348
+ { name: "parentId", type: "string|null", description: "Parent message ID when present." },
349
+ ],
350
+ eventVariantExamples: ["im.message.receive_v1.text"],
351
+ };
352
+ },
220
353
  validateConfig(source) {
221
354
  (0, feishu_1.parseFeishuSourceConfig)(source);
222
355
  },