@holon-run/agentinbox 0.7.0 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -7
- package/dist/src/adapters.js +55 -0
- package/dist/src/backend.js +4 -16
- package/dist/src/cli.js +289 -48
- package/dist/src/client.js +2 -2
- package/dist/src/http.js +466 -45
- package/dist/src/runtime_gate.js +49 -41
- package/dist/src/service.js +800 -155
- package/dist/src/source_hosts.js +75 -0
- package/dist/src/source_resolution.js +2 -2
- package/dist/src/source_schema.js +2 -2
- package/dist/src/sources/feishu.js +66 -28
- package/dist/src/sources/github.js +188 -24
- package/dist/src/sources/remote.js +22 -2
- package/dist/src/sources/remote_modules.js +96 -11
- package/dist/src/store.js +861 -106
- package/dist/src/terminal.js +33 -7
- package/dist/src/util.js +46 -1
- package/drizzle/migrations/{0000_initial.sql → 0000_v1_initial.sql} +182 -6
- package/drizzle/schema.ts +93 -1
- package/package.json +2 -1
- package/scripts/release_smoke.sh +123 -0
- package/dist/src/sources/remote_profiles.js +0 -13
- package/drizzle/migrations/0001_inbox_items_source_occurred_at_idx.sql +0 -3
- package/drizzle/migrations/0002_subscription_cleanup_policy.sql +0 -45
- package/drizzle/migrations/0003_subscription_lifecycle_retirements.sql +0 -14
- package/drizzle/migrations/0004_source_idle_states.sql +0 -10
- package/drizzle/migrations/0005_timers.sql +0 -22
- package/drizzle/migrations/0006_terminal_runtime_pid.sql +0 -1
- package/drizzle/migrations/0007_activation_dispatch_last_notified_fingerprint.sql +0 -2
- 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 ??
|
|
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 ??
|
|
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: "
|
|
25
|
-
{ name: "
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
if (attempt.surface === "issue_comment" || attempt.surface === "pull_request_comment") {
|
|
62
|
-
await this.client.createIssueComment({
|
|
63
|
-
owner: target.owner,
|
|
64
|
-
repo: target.repo,
|
|
65
|
-
issueNumber: target.number,
|
|
66
|
-
body: payloadText,
|
|
67
|
-
});
|
|
68
|
-
return { status: "sent", note: "sent GitHub issue comment" };
|
|
69
|
-
}
|
|
70
|
-
if (attempt.surface === "review_comment") {
|
|
71
|
-
if (!attempt.threadRef || !attempt.threadRef.startsWith("review_comment:")) {
|
|
72
|
-
throw new Error("review_comment delivery requires threadRef=review_comment:<id>");
|
|
73
|
-
}
|
|
74
|
-
await this.client.replyToReviewComment({
|
|
75
|
-
owner: target.owner,
|
|
76
|
-
repo: target.repo,
|
|
77
|
-
pullNumber: target.number,
|
|
78
|
-
commentId: Number(attempt.threadRef.slice("review_comment:".length)),
|
|
79
|
-
body: payloadText,
|
|
80
|
-
});
|
|
81
|
-
return { status: "sent", note: "sent GitHub review comment reply" };
|
|
82
|
-
}
|
|
83
|
-
throw new Error(`unsupported GitHub surface: ${attempt.surface}`);
|
|
90
|
+
return invokeGithubDeliveryOperation(attempt, canonicalGithubOperationForSurface(attempt.surface), { body: payloadText }, this.client);
|
|
84
91
|
}
|
|
85
92
|
}
|
|
86
93
|
exports.GithubDeliveryAdapter = GithubDeliveryAdapter;
|
|
94
|
+
function githubDeliveryOperationsForHandle(handle) {
|
|
95
|
+
if (handle.surface === "review_comment") {
|
|
96
|
+
return [{
|
|
97
|
+
name: "reply_in_review_thread",
|
|
98
|
+
title: "Reply In Review Thread",
|
|
99
|
+
inputSchema: deliveryBodySchema(),
|
|
100
|
+
canonicalTextAlias: true,
|
|
101
|
+
}];
|
|
102
|
+
}
|
|
103
|
+
if (handle.surface === "issue_comment") {
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
name: "add_comment",
|
|
107
|
+
title: "Add Comment",
|
|
108
|
+
inputSchema: deliveryBodySchema(),
|
|
109
|
+
canonicalTextAlias: true,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "close_issue",
|
|
113
|
+
title: "Close Issue",
|
|
114
|
+
inputSchema: emptyObjectSchema(),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: "reopen_issue",
|
|
118
|
+
title: "Reopen Issue",
|
|
119
|
+
inputSchema: emptyObjectSchema(),
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
if (handle.surface === "pull_request_comment") {
|
|
124
|
+
return [
|
|
125
|
+
{
|
|
126
|
+
name: "add_comment",
|
|
127
|
+
title: "Add Comment",
|
|
128
|
+
inputSchema: deliveryBodySchema(),
|
|
129
|
+
canonicalTextAlias: true,
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: "close_issue",
|
|
133
|
+
title: "Close Pull Request",
|
|
134
|
+
inputSchema: emptyObjectSchema(),
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "reopen_issue",
|
|
138
|
+
title: "Reopen Pull Request",
|
|
139
|
+
inputSchema: emptyObjectSchema(),
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: "merge_pull_request",
|
|
143
|
+
title: "Merge Pull Request",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: "object",
|
|
146
|
+
additionalProperties: false,
|
|
147
|
+
properties: {
|
|
148
|
+
mergeMethod: { type: "string", enum: ["merge", "squash", "rebase"] },
|
|
149
|
+
commitTitle: { type: "string" },
|
|
150
|
+
commitMessage: { type: "string" },
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
}
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
async function invokeGithubDeliveryOperation(handle, operation, input, client = new GithubUxcClient()) {
|
|
159
|
+
const target = parseTargetRef(handle.targetRef);
|
|
160
|
+
if (operation === "add_comment") {
|
|
161
|
+
const body = requireDeliveryBody(input, operation);
|
|
162
|
+
await client.createIssueComment({
|
|
163
|
+
owner: target.owner,
|
|
164
|
+
repo: target.repo,
|
|
165
|
+
issueNumber: target.number,
|
|
166
|
+
body,
|
|
167
|
+
});
|
|
168
|
+
return { status: "sent", note: "sent GitHub issue comment" };
|
|
169
|
+
}
|
|
170
|
+
if (operation === "reply_in_review_thread") {
|
|
171
|
+
const body = requireDeliveryBody(input, operation);
|
|
172
|
+
if (!handle.threadRef || !handle.threadRef.startsWith("review_comment:")) {
|
|
173
|
+
throw new Error("reply_in_review_thread requires threadRef=review_comment:<id>");
|
|
174
|
+
}
|
|
175
|
+
await client.replyToReviewComment({
|
|
176
|
+
owner: target.owner,
|
|
177
|
+
repo: target.repo,
|
|
178
|
+
pullNumber: target.number,
|
|
179
|
+
commentId: Number(handle.threadRef.slice("review_comment:".length)),
|
|
180
|
+
body,
|
|
181
|
+
});
|
|
182
|
+
return { status: "sent", note: "sent GitHub review comment reply" };
|
|
183
|
+
}
|
|
184
|
+
if (operation === "close_issue") {
|
|
185
|
+
await client.updateIssueState({
|
|
186
|
+
owner: target.owner,
|
|
187
|
+
repo: target.repo,
|
|
188
|
+
issueNumber: target.number,
|
|
189
|
+
state: "closed",
|
|
190
|
+
});
|
|
191
|
+
return { status: "sent", note: "closed GitHub issue" };
|
|
192
|
+
}
|
|
193
|
+
if (operation === "reopen_issue") {
|
|
194
|
+
await client.updateIssueState({
|
|
195
|
+
owner: target.owner,
|
|
196
|
+
repo: target.repo,
|
|
197
|
+
issueNumber: target.number,
|
|
198
|
+
state: "open",
|
|
199
|
+
});
|
|
200
|
+
return { status: "sent", note: "reopened GitHub issue" };
|
|
201
|
+
}
|
|
202
|
+
if (operation === "merge_pull_request") {
|
|
203
|
+
await client.mergePullRequest({
|
|
204
|
+
owner: target.owner,
|
|
205
|
+
repo: target.repo,
|
|
206
|
+
pullNumber: target.number,
|
|
207
|
+
mergeMethod: asMergeMethod(input.mergeMethod),
|
|
208
|
+
commitTitle: asString(input.commitTitle) ?? undefined,
|
|
209
|
+
commitMessage: asString(input.commitMessage) ?? undefined,
|
|
210
|
+
});
|
|
211
|
+
return { status: "sent", note: "merged GitHub pull request" };
|
|
212
|
+
}
|
|
213
|
+
throw new Error(`unknown GitHub delivery operation: ${operation}`);
|
|
214
|
+
}
|
|
87
215
|
function normalizeGithubRepoEvent(source, config, raw) {
|
|
88
216
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
89
217
|
return null;
|
|
@@ -300,6 +428,42 @@ function stringifyDeliveryPayload(payload) {
|
|
|
300
428
|
}
|
|
301
429
|
return JSON.stringify(payload, null, 2);
|
|
302
430
|
}
|
|
431
|
+
function canonicalGithubOperationForSurface(surface) {
|
|
432
|
+
if (surface === "issue_comment" || surface === "pull_request_comment") {
|
|
433
|
+
return "add_comment";
|
|
434
|
+
}
|
|
435
|
+
if (surface === "review_comment") {
|
|
436
|
+
return "reply_in_review_thread";
|
|
437
|
+
}
|
|
438
|
+
throw new Error(`deliver send only supports canonical GitHub text surfaces; use deliver invoke for ${surface}`);
|
|
439
|
+
}
|
|
440
|
+
function deliveryBodySchema() {
|
|
441
|
+
return {
|
|
442
|
+
type: "object",
|
|
443
|
+
additionalProperties: false,
|
|
444
|
+
required: ["body"],
|
|
445
|
+
properties: {
|
|
446
|
+
body: { type: "string", minLength: 1 },
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
function emptyObjectSchema() {
|
|
451
|
+
return {
|
|
452
|
+
type: "object",
|
|
453
|
+
additionalProperties: false,
|
|
454
|
+
properties: {},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function requireDeliveryBody(input, operation) {
|
|
458
|
+
const body = asString(input.body);
|
|
459
|
+
if (!body || body.trim().length === 0) {
|
|
460
|
+
throw new Error(`${operation} requires input.body`);
|
|
461
|
+
}
|
|
462
|
+
return body;
|
|
463
|
+
}
|
|
464
|
+
function asMergeMethod(value) {
|
|
465
|
+
return value === "merge" || value === "squash" || value === "rebase" ? value : undefined;
|
|
466
|
+
}
|
|
303
467
|
function asRecord(value) {
|
|
304
468
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
305
469
|
return {};
|
|
@@ -65,7 +65,7 @@ class RemoteSourceRuntime {
|
|
|
65
65
|
constructor(store, appendSourceEvent, options) {
|
|
66
66
|
this.store = store;
|
|
67
67
|
this.appendSourceEvent = appendSourceEvent;
|
|
68
|
-
this.moduleRegistry = options?.moduleRegistry ??
|
|
68
|
+
this.moduleRegistry = options?.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
|
|
69
69
|
this.client = options?.client ?? new RpcUxcRemoteSourceClient();
|
|
70
70
|
this.homeDir = options?.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
|
|
71
71
|
}
|
|
@@ -201,6 +201,26 @@ class RemoteSourceRuntime {
|
|
|
201
201
|
}
|
|
202
202
|
return module.deriveInlinePreview(item, moduleInputSource(source));
|
|
203
203
|
}
|
|
204
|
+
async deriveNotificationGrouping(source, item) {
|
|
205
|
+
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
209
|
+
if (typeof module.deriveNotificationGrouping !== "function") {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return module.deriveNotificationGrouping(item, moduleInputSource(source));
|
|
213
|
+
}
|
|
214
|
+
async summarizeDigestThread(source, items, grouping) {
|
|
215
|
+
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
219
|
+
if (typeof module.summarizeDigestThread !== "function") {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return module.summarizeDigestThread(items, moduleInputSource(source), grouping);
|
|
223
|
+
}
|
|
204
224
|
async syncAll() {
|
|
205
225
|
const sources = this.store
|
|
206
226
|
.listSources()
|
|
@@ -355,7 +375,7 @@ function moduleInputSource(source) {
|
|
|
355
375
|
const config = asRecord(source.config);
|
|
356
376
|
return {
|
|
357
377
|
...source,
|
|
358
|
-
config: asRecord(config.
|
|
378
|
+
config: asRecord(config.moduleConfig),
|
|
359
379
|
};
|
|
360
380
|
}
|
|
361
381
|
function defaultManagedBindingForSource(source) {
|
|
@@ -12,7 +12,7 @@ const node_url_1 = require("node:url");
|
|
|
12
12
|
const github_1 = require("./github");
|
|
13
13
|
const github_ci_1 = require("./github_ci");
|
|
14
14
|
const feishu_1 = require("./feishu");
|
|
15
|
-
const REMOTE_USER_MODULE_ROOT_DIR = "source-
|
|
15
|
+
const REMOTE_USER_MODULE_ROOT_DIR = "source-modules";
|
|
16
16
|
const BUILTIN_MODULE_IDS = new Set(["builtin.github_repo", "builtin.github_repo_ci", "builtin.feishu_bot"]);
|
|
17
17
|
class RemoteSourceModuleRegistry {
|
|
18
18
|
moduleCache = new Map();
|
|
@@ -33,12 +33,12 @@ class RemoteSourceModuleRegistry {
|
|
|
33
33
|
}
|
|
34
34
|
async loadUserModule(source, homeDir) {
|
|
35
35
|
const config = source.config ?? {};
|
|
36
|
-
const
|
|
37
|
-
if (!
|
|
38
|
-
throw new Error("remote_source requires config.
|
|
36
|
+
const modulePath = asNonEmptyString(config.modulePath);
|
|
37
|
+
if (!modulePath) {
|
|
38
|
+
throw new Error("remote_source requires config.modulePath (module path)");
|
|
39
39
|
}
|
|
40
40
|
const moduleRoot = node_path_1.default.resolve(homeDir, REMOTE_USER_MODULE_ROOT_DIR);
|
|
41
|
-
const resolvedPath = resolveAndValidateModulePath(moduleRoot,
|
|
41
|
+
const resolvedPath = resolveAndValidateModulePath(moduleRoot, modulePath);
|
|
42
42
|
const cacheKey = `${resolvedPath}:${source.sourceKey}`;
|
|
43
43
|
const cached = this.moduleCache.get(cacheKey);
|
|
44
44
|
if (cached) {
|
|
@@ -96,20 +96,24 @@ function validateModuleContract(module, sourcePath) {
|
|
|
96
96
|
validateOptionalHook(module.deriveTrackedResource, "deriveTrackedResource", sourcePath);
|
|
97
97
|
validateOptionalHook(module.projectLifecycleSignal, "projectLifecycleSignal", sourcePath);
|
|
98
98
|
validateOptionalHook(module.deriveInlinePreview, "deriveInlinePreview", sourcePath);
|
|
99
|
+
validateOptionalHook(module.deriveNotificationGrouping, "deriveNotificationGrouping", sourcePath);
|
|
100
|
+
validateOptionalHook(module.listDeliveryOperations, "listDeliveryOperations", sourcePath);
|
|
101
|
+
validateOptionalHook(module.invokeDeliveryOperation, "invokeDeliveryOperation", sourcePath);
|
|
102
|
+
validateOptionalHook(module.summarizeDigestThread, "summarizeDigestThread", sourcePath);
|
|
99
103
|
}
|
|
100
104
|
function validateOptionalHook(value, name, sourcePath) {
|
|
101
105
|
if (value !== undefined && typeof value !== "function") {
|
|
102
106
|
throw new Error(`remote_source module ${name} must be a function when provided: ${sourcePath}`);
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
|
-
function resolveAndValidateModulePath(moduleRoot,
|
|
106
|
-
const resolved = node_path_1.default.isAbsolute(
|
|
107
|
-
? node_path_1.default.resolve(
|
|
108
|
-
: node_path_1.default.resolve(moduleRoot,
|
|
109
|
+
function resolveAndValidateModulePath(moduleRoot, modulePath) {
|
|
110
|
+
const resolved = node_path_1.default.isAbsolute(modulePath)
|
|
111
|
+
? node_path_1.default.resolve(modulePath)
|
|
112
|
+
: node_path_1.default.resolve(moduleRoot, modulePath);
|
|
109
113
|
const normalizedRoot = ensureTrailingSep(node_path_1.default.resolve(moduleRoot));
|
|
110
114
|
const normalizedResolved = node_path_1.default.resolve(resolved);
|
|
111
115
|
if (!normalizedResolved.startsWith(normalizedRoot) && normalizedResolved !== node_path_1.default.resolve(moduleRoot)) {
|
|
112
|
-
throw new Error(`remote_source
|
|
116
|
+
throw new Error(`remote_source modulePath must stay under ${moduleRoot}`);
|
|
113
117
|
}
|
|
114
118
|
return normalizedResolved;
|
|
115
119
|
}
|
|
@@ -131,6 +135,12 @@ function asNonEmptyString(value) {
|
|
|
131
135
|
}
|
|
132
136
|
const GITHUB_REPO_MODULE = {
|
|
133
137
|
id: "builtin.github_repo",
|
|
138
|
+
listDeliveryOperations(input) {
|
|
139
|
+
return (0, github_1.githubDeliveryOperationsForHandle)(input.handle);
|
|
140
|
+
},
|
|
141
|
+
async invokeDeliveryOperation(input) {
|
|
142
|
+
return (0, github_1.invokeGithubDeliveryOperation)(input.handle, input.operation, input.input);
|
|
143
|
+
},
|
|
134
144
|
describeCapabilities(source) {
|
|
135
145
|
const config = (0, github_1.parseGithubSourceConfig)(source);
|
|
136
146
|
return {
|
|
@@ -193,6 +203,54 @@ const GITHUB_REPO_MODULE = {
|
|
|
193
203
|
deriveTrackedResource(filter) {
|
|
194
204
|
return (0, github_1.deriveGithubTrackedResource)(filter);
|
|
195
205
|
},
|
|
206
|
+
deriveNotificationGrouping(item) {
|
|
207
|
+
const number = typeof item.metadata.number === "number" ? item.metadata.number : null;
|
|
208
|
+
if (!number) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const variant = item.eventVariant;
|
|
212
|
+
if (variant.startsWith("PullRequestReviewCommentEvent.")) {
|
|
213
|
+
return {
|
|
214
|
+
groupable: true,
|
|
215
|
+
resourceRef: `pr:${number}`,
|
|
216
|
+
eventFamily: "review_comments",
|
|
217
|
+
summaryHint: `review comments on PR #${number}`,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (variant.startsWith("PullRequestReviewEvent.")) {
|
|
221
|
+
return {
|
|
222
|
+
groupable: true,
|
|
223
|
+
resourceRef: `pr:${number}`,
|
|
224
|
+
eventFamily: "reviews",
|
|
225
|
+
summaryHint: `reviews on PR #${number}`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (variant.startsWith("IssueCommentEvent.")) {
|
|
229
|
+
return {
|
|
230
|
+
groupable: true,
|
|
231
|
+
resourceRef: item.metadata.isPullRequest === true ? `pr:${number}` : `issue:${number}`,
|
|
232
|
+
eventFamily: "comments",
|
|
233
|
+
summaryHint: item.metadata.isPullRequest === true ? `comments on PR #${number}` : `comments on issue #${number}`,
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
if (variant.startsWith("PullRequestEvent.closed")) {
|
|
237
|
+
return {
|
|
238
|
+
groupable: true,
|
|
239
|
+
resourceRef: `pr:${number}`,
|
|
240
|
+
eventFamily: "lifecycle",
|
|
241
|
+
summaryHint: `PR #${number} lifecycle updates`,
|
|
242
|
+
flushClass: "immediate",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
},
|
|
247
|
+
summarizeDigestThread(items, _source, grouping) {
|
|
248
|
+
if (!grouping.summaryHint || items.length === 0) {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
const count = items.length;
|
|
252
|
+
return `${count} ${grouping.summaryHint}`;
|
|
253
|
+
},
|
|
196
254
|
projectLifecycleSignal(rawPayload) {
|
|
197
255
|
return (0, github_1.projectGithubLifecycleSignal)(rawPayload);
|
|
198
256
|
},
|
|
@@ -298,6 +356,27 @@ const GITHUB_REPO_CI_MODULE = {
|
|
|
298
356
|
}
|
|
299
357
|
return parts.join(" ");
|
|
300
358
|
},
|
|
359
|
+
deriveNotificationGrouping(item) {
|
|
360
|
+
const workflowRunId = typeof item.metadata.workflowRunId === "number" ? item.metadata.workflowRunId : null;
|
|
361
|
+
if (!workflowRunId) {
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
const conclusion = asNonEmptyString(item.metadata.conclusion);
|
|
365
|
+
const status = asNonEmptyString(item.metadata.status);
|
|
366
|
+
return {
|
|
367
|
+
groupable: true,
|
|
368
|
+
resourceRef: `workflow_run:${workflowRunId}`,
|
|
369
|
+
eventFamily: "ci_updates",
|
|
370
|
+
summaryHint: `CI updates for run #${workflowRunId}`,
|
|
371
|
+
flushClass: conclusion || status === "completed" ? "immediate" : "normal",
|
|
372
|
+
};
|
|
373
|
+
},
|
|
374
|
+
summarizeDigestThread(items, _source, grouping) {
|
|
375
|
+
if (!grouping.summaryHint || items.length === 0) {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
return `${items.length} ${grouping.summaryHint}`;
|
|
379
|
+
},
|
|
301
380
|
validateConfig(source) {
|
|
302
381
|
(0, github_ci_1.parseGithubCiSourceConfig)(source);
|
|
303
382
|
},
|
|
@@ -351,6 +430,12 @@ const GITHUB_REPO_CI_MODULE = {
|
|
|
351
430
|
};
|
|
352
431
|
const FEISHU_BOT_MODULE = {
|
|
353
432
|
id: "builtin.feishu_bot",
|
|
433
|
+
listDeliveryOperations(input) {
|
|
434
|
+
return (0, feishu_1.feishuDeliveryOperationsForHandle)(input.handle);
|
|
435
|
+
},
|
|
436
|
+
async invokeDeliveryOperation(input) {
|
|
437
|
+
return (0, feishu_1.invokeFeishuDeliveryOperation)(input.handle, input.operation, input.input);
|
|
438
|
+
},
|
|
354
439
|
describeCapabilities() {
|
|
355
440
|
return {
|
|
356
441
|
sourceKind: "feishu_bot",
|
|
@@ -412,7 +497,7 @@ const FEISHU_BOT_MODULE = {
|
|
|
412
497
|
function moduleConfigForSource(source) {
|
|
413
498
|
const config = asRecord(source.config);
|
|
414
499
|
if (source.sourceType === "remote_source") {
|
|
415
|
-
return asRecord(config.
|
|
500
|
+
return asRecord(config.moduleConfig);
|
|
416
501
|
}
|
|
417
502
|
return config;
|
|
418
503
|
}
|