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