@holon-run/agentinbox 0.5.0 → 0.7.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,418 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.RemoteSourceModuleRegistry = void 0;
7
+ exports.builtInModuleIdForSourceType = builtInModuleIdForSourceType;
8
+ exports.moduleConfigForSource = moduleConfigForSource;
9
+ const node_fs_1 = __importDefault(require("node:fs"));
10
+ const node_path_1 = __importDefault(require("node:path"));
11
+ const node_url_1 = require("node:url");
12
+ const github_1 = require("./github");
13
+ const github_ci_1 = require("./github_ci");
14
+ const feishu_1 = require("./feishu");
15
+ const REMOTE_USER_MODULE_ROOT_DIR = "source-profiles";
16
+ const BUILTIN_MODULE_IDS = new Set(["builtin.github_repo", "builtin.github_repo_ci", "builtin.feishu_bot"]);
17
+ class RemoteSourceModuleRegistry {
18
+ moduleCache = new Map();
19
+ resolve(source, homeDir) {
20
+ if (source.sourceType === "github_repo") {
21
+ return Promise.resolve(GITHUB_REPO_MODULE);
22
+ }
23
+ if (source.sourceType === "github_repo_ci") {
24
+ return Promise.resolve(GITHUB_REPO_CI_MODULE);
25
+ }
26
+ if (source.sourceType === "feishu_bot") {
27
+ return Promise.resolve(FEISHU_BOT_MODULE);
28
+ }
29
+ if (source.sourceType !== "remote_source") {
30
+ throw new Error(`unsupported source type for remote module: ${source.sourceType}`);
31
+ }
32
+ return this.loadUserModule(source, homeDir);
33
+ }
34
+ async loadUserModule(source, homeDir) {
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)");
39
+ }
40
+ const moduleRoot = node_path_1.default.resolve(homeDir, REMOTE_USER_MODULE_ROOT_DIR);
41
+ const resolvedPath = resolveAndValidateModulePath(moduleRoot, profilePath);
42
+ const cacheKey = `${resolvedPath}:${source.sourceKey}`;
43
+ const cached = this.moduleCache.get(cacheKey);
44
+ if (cached) {
45
+ return cached;
46
+ }
47
+ if (!node_fs_1.default.existsSync(resolvedPath)) {
48
+ throw new Error(`remote_source module not found: ${resolvedPath}`);
49
+ }
50
+ const imported = await dynamicImportModule((0, node_url_1.pathToFileURL)(resolvedPath).href);
51
+ const module = (imported.default ?? imported);
52
+ if (!module || typeof module !== "object") {
53
+ throw new Error(`remote_source module must export default object: ${resolvedPath}`);
54
+ }
55
+ validateModuleContract(module, resolvedPath);
56
+ this.moduleCache.set(cacheKey, module);
57
+ return module;
58
+ }
59
+ }
60
+ exports.RemoteSourceModuleRegistry = RemoteSourceModuleRegistry;
61
+ async function dynamicImportModule(moduleUrl) {
62
+ const importer = new Function("url", "return import(url);");
63
+ return importer(moduleUrl);
64
+ }
65
+ function builtInModuleIdForSourceType(sourceType) {
66
+ if (sourceType === "github_repo") {
67
+ return "builtin.github_repo";
68
+ }
69
+ if (sourceType === "github_repo_ci") {
70
+ return "builtin.github_repo_ci";
71
+ }
72
+ if (sourceType === "feishu_bot") {
73
+ return "builtin.feishu_bot";
74
+ }
75
+ return null;
76
+ }
77
+ function validateModuleContract(module, sourcePath) {
78
+ if (!module.id || typeof module.id !== "string") {
79
+ throw new Error(`remote_source module id must be a non-empty string: ${sourcePath}`);
80
+ }
81
+ if (BUILTIN_MODULE_IDS.has(module.id)) {
82
+ throw new Error(`remote_source module id is reserved: ${module.id}`);
83
+ }
84
+ if (typeof module.validateConfig !== "function") {
85
+ throw new Error(`remote_source module missing validateConfig(): ${sourcePath}`);
86
+ }
87
+ if (typeof module.buildManagedSourceSpec !== "function") {
88
+ throw new Error(`remote_source module missing buildManagedSourceSpec(): ${sourcePath}`);
89
+ }
90
+ if (typeof module.mapRawEvent !== "function") {
91
+ throw new Error(`remote_source module missing mapRawEvent(): ${sourcePath}`);
92
+ }
93
+ validateOptionalHook(module.describeCapabilities, "describeCapabilities", sourcePath);
94
+ validateOptionalHook(module.listSubscriptionShortcuts, "listSubscriptionShortcuts", sourcePath);
95
+ validateOptionalHook(module.expandSubscriptionShortcut, "expandSubscriptionShortcut", sourcePath);
96
+ validateOptionalHook(module.deriveTrackedResource, "deriveTrackedResource", sourcePath);
97
+ validateOptionalHook(module.projectLifecycleSignal, "projectLifecycleSignal", sourcePath);
98
+ validateOptionalHook(module.deriveInlinePreview, "deriveInlinePreview", sourcePath);
99
+ }
100
+ function validateOptionalHook(value, name, sourcePath) {
101
+ if (value !== undefined && typeof value !== "function") {
102
+ throw new Error(`remote_source module ${name} must be a function when provided: ${sourcePath}`);
103
+ }
104
+ }
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
+ const normalizedRoot = ensureTrailingSep(node_path_1.default.resolve(moduleRoot));
110
+ const normalizedResolved = node_path_1.default.resolve(resolved);
111
+ if (!normalizedResolved.startsWith(normalizedRoot) && normalizedResolved !== node_path_1.default.resolve(moduleRoot)) {
112
+ throw new Error(`remote_source profilePath must stay under ${moduleRoot}`);
113
+ }
114
+ return normalizedResolved;
115
+ }
116
+ function ensureTrailingSep(input) {
117
+ return input.endsWith(node_path_1.default.sep) ? input : `${input}${node_path_1.default.sep}`;
118
+ }
119
+ function asRecord(value) {
120
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
121
+ return {};
122
+ }
123
+ return value;
124
+ }
125
+ function asNonEmptyString(value) {
126
+ if (typeof value !== "string") {
127
+ return null;
128
+ }
129
+ const trimmed = value.trim();
130
+ return trimmed.length > 0 ? trimmed : null;
131
+ }
132
+ const GITHUB_REPO_MODULE = {
133
+ id: "builtin.github_repo",
134
+ describeCapabilities(source) {
135
+ const config = (0, github_1.parseGithubSourceConfig)(source);
136
+ return {
137
+ sourceKind: "github_repo",
138
+ aliases: ["github_repo"],
139
+ configSchema: [
140
+ { name: "owner", type: "string", required: true, description: "GitHub repository owner." },
141
+ { name: "repo", type: "string", required: true, description: "GitHub repository name." },
142
+ { name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
143
+ { name: "eventTypes", type: "string[]", required: false, description: "Optional GitHub event type allowlist." },
144
+ { name: "perPage", type: "number", required: false, description: `Repository events requested per poll. Default ${config.perPage ?? 10}.` },
145
+ { name: "pollIntervalSecs", type: "number", required: false, description: `Polling interval in seconds. Default ${config.pollIntervalSecs ?? 30}.` },
146
+ ],
147
+ metadataFields: [
148
+ { name: "eventType", type: "string", description: "GitHub event type such as IssueCommentEvent." },
149
+ { name: "action", type: "string", description: "GitHub event action suffix such as created." },
150
+ { name: "author", type: "string|null", description: "Actor login for the event." },
151
+ { name: "isPullRequest", type: "boolean", description: "Whether the event targets a pull request surface." },
152
+ { name: "reviewState", type: "string|null", description: "Review decision state for PullRequestReviewEvent such as approved or changes_requested." },
153
+ { name: "labels", type: "string[]", description: "Labels extracted from the issue or pull request." },
154
+ { name: "mentions", type: "string[]", description: "Mention handles extracted from title/body/comment text." },
155
+ { name: "number", type: "number|null", description: "Issue or pull request number when present." },
156
+ { name: "repoFullName", type: "string", description: "Repository full name in owner/repo form." },
157
+ { name: "title", type: "string|null", description: "Issue, pull request, or comment title." },
158
+ { name: "body", type: "string|null", description: "Issue, pull request, or comment body text." },
159
+ { name: "url", type: "string|null", description: "Primary GitHub HTML URL for the event target." },
160
+ ],
161
+ payloadExamples: [
162
+ {
163
+ id: "1234567891",
164
+ type: "PullRequestReviewEvent",
165
+ action: "created",
166
+ actor: "Copilot",
167
+ pull_request: { number: 67, title: "feat: add remote module capability hooks" },
168
+ review: { state: "commented", body: "review summary" },
169
+ },
170
+ {
171
+ id: "1234567892",
172
+ type: "PullRequestEvent",
173
+ action: "closed",
174
+ actor: "jolestar",
175
+ pull_request: { number: 72, title: "feat: add cleanup policy lifecycle engine", merged: true },
176
+ },
177
+ ],
178
+ eventVariantExamples: [
179
+ "IssueCommentEvent.created",
180
+ "PullRequestEvent.opened",
181
+ "PullRequestEvent.closed",
182
+ "PullRequestReviewEvent.created",
183
+ "PullRequestReviewCommentEvent.created",
184
+ ],
185
+ };
186
+ },
187
+ listSubscriptionShortcuts() {
188
+ return (0, github_1.githubSubscriptionShortcutSpec)();
189
+ },
190
+ expandSubscriptionShortcut(input) {
191
+ return (0, github_1.expandGithubSubscriptionShortcut)(input);
192
+ },
193
+ deriveTrackedResource(filter) {
194
+ return (0, github_1.deriveGithubTrackedResource)(filter);
195
+ },
196
+ projectLifecycleSignal(rawPayload) {
197
+ return (0, github_1.projectGithubLifecycleSignal)(rawPayload);
198
+ },
199
+ validateConfig(source) {
200
+ (0, github_1.parseGithubSourceConfig)(source);
201
+ },
202
+ buildManagedSourceSpec(source) {
203
+ const config = (0, github_1.parseGithubSourceConfig)(source);
204
+ return {
205
+ endpoint: github_1.GITHUB_ENDPOINT,
206
+ operation_id: "get:/repos/{owner}/{repo}/events",
207
+ args: {
208
+ owner: config.owner,
209
+ repo: config.repo,
210
+ per_page: config.perPage ?? 10,
211
+ },
212
+ mode: "poll",
213
+ poll_config: {
214
+ interval_secs: config.pollIntervalSecs ?? 30,
215
+ extract_items_pointer: "",
216
+ checkpoint_strategy: {
217
+ type: "item_key",
218
+ item_key_pointer: "/id",
219
+ seen_window: 1024,
220
+ },
221
+ },
222
+ options: { auth: config.uxcAuth },
223
+ };
224
+ },
225
+ mapRawEvent(rawPayload, source) {
226
+ const config = (0, github_1.parseGithubSourceConfig)(source);
227
+ const normalized = (0, github_1.normalizeGithubRepoEvent)(source, config, rawPayload);
228
+ if (!normalized) {
229
+ return null;
230
+ }
231
+ return {
232
+ sourceNativeId: normalized.sourceNativeId,
233
+ eventVariant: normalized.eventVariant,
234
+ metadata: normalized.metadata ?? {},
235
+ rawPayload: normalized.rawPayload ?? rawPayload,
236
+ occurredAt: normalized.occurredAt,
237
+ deliveryHandle: normalized.deliveryHandle,
238
+ };
239
+ },
240
+ };
241
+ const GITHUB_REPO_CI_MODULE = {
242
+ id: "builtin.github_repo_ci",
243
+ describeCapabilities() {
244
+ return {
245
+ sourceKind: "github_repo_ci",
246
+ aliases: ["github_repo_ci"],
247
+ configSchema: [
248
+ { name: "owner", type: "string", required: true, description: "GitHub repository owner." },
249
+ { name: "repo", type: "string", required: true, description: "GitHub repository name." },
250
+ { name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
251
+ { name: "pollIntervalSecs", type: "number", required: false, description: "Polling interval in seconds." },
252
+ { name: "perPage", type: "number", required: false, description: "Workflow runs requested per poll." },
253
+ { name: "eventFilter", type: "string", required: false, description: "Optional GitHub workflow event filter." },
254
+ { name: "branch", type: "string", required: false, description: "Optional branch filter for workflow runs." },
255
+ { name: "statusFilter", type: "string", required: false, description: "Optional workflow status filter." },
256
+ ],
257
+ metadataFields: [
258
+ { name: "name", type: "string|null", description: "Workflow run name." },
259
+ { name: "status", type: "string", description: "Normalized workflow run status." },
260
+ { name: "conclusion", type: "string|null", description: "Workflow run conclusion when completed." },
261
+ { name: "event", type: "string|null", description: "GitHub trigger event for the workflow run." },
262
+ { name: "headBranch", type: "string|null", description: "Head branch for the workflow run." },
263
+ { name: "headSha", type: "string|null", description: "Head commit SHA for the workflow run." },
264
+ { name: "actor", type: "string|null", description: "Actor login for the workflow run." },
265
+ { name: "commitMessage", type: "string|null", description: "Head commit message when present." },
266
+ { name: "htmlUrl", type: "string|null", description: "GitHub Actions run URL." },
267
+ ],
268
+ eventVariantExamples: ["workflow_run.ci.completed.failure", "workflow_run.nightly_checks.observed"],
269
+ };
270
+ },
271
+ deriveInlinePreview(item) {
272
+ const repoFullName = asNonEmptyString(item.metadata.repoFullName);
273
+ const workflowName = asNonEmptyString(item.metadata.name)
274
+ ?? asNonEmptyString(item.metadata.displayTitle)
275
+ ?? asNonEmptyString(item.rawPayload.name)
276
+ ?? asNonEmptyString(item.rawPayload.display_title);
277
+ const headBranch = asNonEmptyString(item.metadata.headBranch) ?? asNonEmptyString(item.rawPayload.head_branch);
278
+ const conclusion = asNonEmptyString(item.metadata.conclusion) ?? asNonEmptyString(item.rawPayload.conclusion);
279
+ const status = asNonEmptyString(item.metadata.status) ?? asNonEmptyString(item.rawPayload.status);
280
+ const parts = [];
281
+ if (workflowName) {
282
+ parts.push(workflowName);
283
+ }
284
+ else {
285
+ parts.push("Workflow run");
286
+ }
287
+ if (conclusion) {
288
+ parts.push(`completed ${conclusion}`);
289
+ }
290
+ else if (status) {
291
+ parts.push(status);
292
+ }
293
+ if (repoFullName) {
294
+ parts.push(`for ${repoFullName}`);
295
+ }
296
+ if (headBranch) {
297
+ parts.push(`on ${headBranch}`);
298
+ }
299
+ return parts.join(" ");
300
+ },
301
+ validateConfig(source) {
302
+ (0, github_ci_1.parseGithubCiSourceConfig)(source);
303
+ },
304
+ buildManagedSourceSpec(source) {
305
+ const config = (0, github_ci_1.parseGithubCiSourceConfig)(source);
306
+ const args = {
307
+ owner: config.owner,
308
+ repo: config.repo,
309
+ per_page: config.perPage ?? github_ci_1.DEFAULT_GITHUB_CI_PER_PAGE,
310
+ };
311
+ if (config.eventFilter) {
312
+ args.event = config.eventFilter;
313
+ }
314
+ if (config.branch) {
315
+ args.branch = config.branch;
316
+ }
317
+ if (config.statusFilter) {
318
+ args.status = config.statusFilter;
319
+ }
320
+ return {
321
+ endpoint: github_ci_1.GITHUB_CI_ENDPOINT,
322
+ operation_id: "get:/repos/{owner}/{repo}/actions/runs",
323
+ args,
324
+ mode: "poll",
325
+ poll_config: {
326
+ interval_secs: config.pollIntervalSecs ?? github_ci_1.DEFAULT_GITHUB_CI_POLL_INTERVAL_SECS,
327
+ extract_items_pointer: "/workflow_runs",
328
+ checkpoint_strategy: {
329
+ type: "content_hash",
330
+ seen_window: 1024,
331
+ },
332
+ },
333
+ options: { auth: config.uxcAuth },
334
+ };
335
+ },
336
+ mapRawEvent(rawPayload, source) {
337
+ const config = (0, github_ci_1.parseGithubCiSourceConfig)(source);
338
+ const normalized = (0, github_ci_1.normalizeGithubWorkflowRunEvent)(source, config, rawPayload);
339
+ if (!normalized) {
340
+ return null;
341
+ }
342
+ return {
343
+ sourceNativeId: normalized.sourceNativeId,
344
+ eventVariant: normalized.eventVariant,
345
+ metadata: normalized.metadata ?? {},
346
+ rawPayload: normalized.rawPayload ?? rawPayload,
347
+ occurredAt: normalized.occurredAt,
348
+ deliveryHandle: normalized.deliveryHandle,
349
+ };
350
+ },
351
+ };
352
+ const FEISHU_BOT_MODULE = {
353
+ id: "builtin.feishu_bot",
354
+ describeCapabilities() {
355
+ return {
356
+ sourceKind: "feishu_bot",
357
+ aliases: ["feishu_bot"],
358
+ configSchema: [
359
+ { name: "appId", type: "string", required: true, description: "Feishu app ID." },
360
+ { name: "appSecret", type: "string", required: true, description: "Feishu app secret." },
361
+ { name: "eventTypes", type: "string[]", required: false, description: "Optional Feishu event type allowlist." },
362
+ { name: "chatIds", type: "string[]", required: false, description: "Optional Feishu chat allowlist." },
363
+ { name: "schemaUrl", type: "string", required: false, description: "Optional Feishu OpenAPI schema URL." },
364
+ { name: "replyInThread", type: "boolean", required: false, description: "Reply in thread when sending outbound messages." },
365
+ { name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
366
+ ],
367
+ metadataFields: [
368
+ { name: "eventType", type: "string", description: "Feishu event type." },
369
+ { name: "chatId", type: "string", description: "Target chat ID." },
370
+ { name: "chatType", type: "string|null", description: "Feishu chat type." },
371
+ { name: "messageId", type: "string", description: "Feishu message ID." },
372
+ { name: "messageType", type: "string", description: "Feishu message type such as text." },
373
+ { name: "senderOpenId", type: "string|null", description: "Sender open_id when present." },
374
+ { name: "senderType", type: "string|null", description: "Sender type." },
375
+ { name: "mentions", type: "string[]", description: "Mention names extracted from the message." },
376
+ { name: "mentionOpenIds", type: "string[]", description: "Mention open_ids extracted from the message." },
377
+ { name: "content", type: "string|null", description: "Normalized message content string." },
378
+ { name: "threadId", type: "string|null", description: "Thread or root message ID when present." },
379
+ { name: "parentId", type: "string|null", description: "Parent message ID when present." },
380
+ ],
381
+ eventVariantExamples: ["im.message.receive_v1.text"],
382
+ };
383
+ },
384
+ validateConfig(source) {
385
+ (0, feishu_1.parseFeishuSourceConfig)(source);
386
+ },
387
+ buildManagedSourceSpec(source) {
388
+ const config = (0, feishu_1.parseFeishuSourceConfig)(source);
389
+ return {
390
+ endpoint: config.endpoint ?? feishu_1.FEISHU_OPENAPI_ENDPOINT,
391
+ mode: "stream",
392
+ transport_hint: "feishu_long_connection",
393
+ options: { auth: config.uxcAuth },
394
+ };
395
+ },
396
+ mapRawEvent(rawPayload, source) {
397
+ const config = (0, feishu_1.parseFeishuSourceConfig)(source);
398
+ const normalized = (0, feishu_1.normalizeFeishuBotEvent)(source, config, rawPayload);
399
+ if (!normalized) {
400
+ return null;
401
+ }
402
+ return {
403
+ sourceNativeId: normalized.sourceNativeId,
404
+ eventVariant: normalized.eventVariant,
405
+ metadata: normalized.metadata ?? {},
406
+ rawPayload: normalized.rawPayload ?? rawPayload,
407
+ occurredAt: normalized.occurredAt,
408
+ deliveryHandle: normalized.deliveryHandle,
409
+ };
410
+ },
411
+ };
412
+ function moduleConfigForSource(source) {
413
+ const config = asRecord(source.config);
414
+ if (source.sourceType === "remote_source") {
415
+ return asRecord(config.profileConfig);
416
+ }
417
+ return config;
418
+ }