@holon-run/agentinbox 0.6.0 → 1.0.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -7
- package/dist/src/adapters.js +61 -6
- package/dist/src/backend.js +4 -16
- package/dist/src/cli.js +307 -53
- package/dist/src/client.js +2 -2
- package/dist/src/daemon.js +36 -2
- package/dist/src/http.js +467 -45
- package/dist/src/logging.js +112 -0
- package/dist/src/paths.js +1 -0
- package/dist/src/runtime_gate.js +394 -63
- package/dist/src/service.js +856 -154
- package/dist/src/source_hosts.js +75 -0
- package/dist/src/source_resolution.js +20 -20
- 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 +44 -24
- package/dist/src/sources/{remote_profiles.js → remote_modules.js} +143 -58
- 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/iterm2_cursor_probe.py +8 -3
- package/scripts/release_smoke.sh +123 -0
- 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
|
+
}
|
|
@@ -4,7 +4,7 @@ exports.resolveSourceIdentity = resolveSourceIdentity;
|
|
|
4
4
|
exports.resolveSourceSchema = resolveSourceSchema;
|
|
5
5
|
exports.withResolvedIdentity = withResolvedIdentity;
|
|
6
6
|
const paths_1 = require("./paths");
|
|
7
|
-
const
|
|
7
|
+
const remote_modules_1 = require("./sources/remote_modules");
|
|
8
8
|
const source_schema_1 = require("./source_schema");
|
|
9
9
|
async function resolveSourceIdentity(source, context = {}) {
|
|
10
10
|
if (source.sourceType === "local_event") {
|
|
@@ -14,7 +14,7 @@ async function resolveSourceIdentity(source, context = {}) {
|
|
|
14
14
|
implementationId: "builtin.local_event",
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
const builtinImplementationId = (0,
|
|
17
|
+
const builtinImplementationId = (0, remote_modules_1.builtInModuleIdForSourceType)(source.sourceType);
|
|
18
18
|
if (builtinImplementationId) {
|
|
19
19
|
return {
|
|
20
20
|
hostType: "remote_source",
|
|
@@ -25,30 +25,30 @@ async function resolveSourceIdentity(source, context = {}) {
|
|
|
25
25
|
if (source.sourceType !== "remote_source") {
|
|
26
26
|
throw new Error(`unsupported source type for source resolution: ${source.sourceType}`);
|
|
27
27
|
}
|
|
28
|
-
const
|
|
28
|
+
const moduleRegistry = context.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
|
|
29
29
|
const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
|
|
30
|
-
const
|
|
31
|
-
const capabilityDescription = typeof
|
|
32
|
-
?
|
|
30
|
+
const module = await moduleRegistry.resolve(source, homeDir);
|
|
31
|
+
const capabilityDescription = typeof module.describeCapabilities === "function"
|
|
32
|
+
? module.describeCapabilities(moduleInputSource(source))
|
|
33
33
|
: undefined;
|
|
34
34
|
return {
|
|
35
35
|
hostType: "remote_source",
|
|
36
|
-
sourceKind: capabilityDescription?.sourceKind?.trim() || `remote:${
|
|
37
|
-
implementationId:
|
|
36
|
+
sourceKind: capabilityDescription?.sourceKind?.trim() || `remote:${module.id}`,
|
|
37
|
+
implementationId: module.id,
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
async function resolveSourceSchema(source, context = {}) {
|
|
41
|
-
const
|
|
41
|
+
const moduleRegistry = context.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
|
|
42
42
|
const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
|
|
43
|
-
const resolvedContext = { ...context,
|
|
43
|
+
const resolvedContext = { ...context, moduleRegistry, homeDir };
|
|
44
44
|
const identity = await resolveSourceIdentity(source, resolvedContext);
|
|
45
45
|
const staticSchema = (0, source_schema_1.getSourceSchema)(source.sourceType);
|
|
46
46
|
let capabilityDescription;
|
|
47
|
-
let
|
|
47
|
+
let module = null;
|
|
48
48
|
if (identity.hostType === "remote_source") {
|
|
49
|
-
|
|
50
|
-
capabilityDescription = typeof
|
|
51
|
-
?
|
|
49
|
+
module = await moduleRegistry.resolve(source, homeDir);
|
|
50
|
+
capabilityDescription = typeof module.describeCapabilities === "function"
|
|
51
|
+
? module.describeCapabilities(moduleInputSource(source))
|
|
52
52
|
: undefined;
|
|
53
53
|
}
|
|
54
54
|
return withResolvedIdentity(source.sourceId, {
|
|
@@ -60,10 +60,10 @@ async function resolveSourceSchema(source, context = {}) {
|
|
|
60
60
|
}, identity, identity.hostType === "remote_source"
|
|
61
61
|
? {
|
|
62
62
|
aliases: capabilityDescription?.aliases,
|
|
63
|
-
supportsTrackedResourceRef: typeof
|
|
64
|
-
supportsLifecycleSignals: typeof
|
|
65
|
-
shortcuts: typeof
|
|
66
|
-
?
|
|
63
|
+
supportsTrackedResourceRef: typeof module?.deriveTrackedResource === "function",
|
|
64
|
+
supportsLifecycleSignals: typeof module?.projectLifecycleSignal === "function",
|
|
65
|
+
shortcuts: typeof module?.listSubscriptionShortcuts === "function"
|
|
66
|
+
? module.listSubscriptionShortcuts(moduleInputSource(source))
|
|
67
67
|
: [],
|
|
68
68
|
}
|
|
69
69
|
: undefined);
|
|
@@ -89,12 +89,12 @@ function withResolvedIdentity(sourceId, schema, identity, capabilityMetadata) {
|
|
|
89
89
|
} : {}),
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
|
-
function
|
|
92
|
+
function moduleInputSource(source) {
|
|
93
93
|
if (source.sourceType !== "remote_source") {
|
|
94
94
|
return source;
|
|
95
95
|
}
|
|
96
96
|
return {
|
|
97
97
|
...source,
|
|
98
|
-
config: (0,
|
|
98
|
+
config: (0, remote_modules_1.moduleConfigForSource)(source),
|
|
99
99
|
};
|
|
100
100
|
}
|
|
@@ -21,8 +21,8 @@ const SOURCE_SCHEMAS = {
|
|
|
21
21
|
payloadExamples: [],
|
|
22
22
|
eventVariantExamples: [],
|
|
23
23
|
configFields: [
|
|
24
|
-
{ name: "
|
|
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 {};
|
|
@@ -4,7 +4,7 @@ exports.RemoteSourceRuntime = exports.RpcUxcRemoteSourceClient = void 0;
|
|
|
4
4
|
const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
|
|
5
5
|
const paths_1 = require("../paths");
|
|
6
6
|
const util_1 = require("../util");
|
|
7
|
-
const
|
|
7
|
+
const remote_modules_1 = require("./remote_modules");
|
|
8
8
|
const REMOTE_SOURCE_TYPES = new Set([
|
|
9
9
|
"remote_source",
|
|
10
10
|
"github_repo",
|
|
@@ -55,7 +55,7 @@ exports.RpcUxcRemoteSourceClient = RpcUxcRemoteSourceClient;
|
|
|
55
55
|
class RemoteSourceRuntime {
|
|
56
56
|
store;
|
|
57
57
|
appendSourceEvent;
|
|
58
|
-
|
|
58
|
+
moduleRegistry;
|
|
59
59
|
client;
|
|
60
60
|
interval = null;
|
|
61
61
|
inFlight = new Set();
|
|
@@ -65,7 +65,7 @@ class RemoteSourceRuntime {
|
|
|
65
65
|
constructor(store, appendSourceEvent, options) {
|
|
66
66
|
this.store = store;
|
|
67
67
|
this.appendSourceEvent = appendSourceEvent;
|
|
68
|
-
this.
|
|
68
|
+
this.moduleRegistry = options?.moduleRegistry ?? new remote_modules_1.RemoteSourceModuleRegistry();
|
|
69
69
|
this.client = options?.client ?? new RpcUxcRemoteSourceClient();
|
|
70
70
|
this.homeDir = options?.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
|
|
71
71
|
}
|
|
@@ -79,10 +79,10 @@ class RemoteSourceRuntime {
|
|
|
79
79
|
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
80
80
|
return;
|
|
81
81
|
}
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
83
|
+
const moduleSource = moduleInputSource(source);
|
|
84
|
+
module.validateConfig(moduleSource);
|
|
85
|
+
module.buildManagedSourceSpec(moduleSource);
|
|
86
86
|
}
|
|
87
87
|
async start() {
|
|
88
88
|
if (this.interval) {
|
|
@@ -171,35 +171,55 @@ class RemoteSourceRuntime {
|
|
|
171
171
|
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
172
172
|
return null;
|
|
173
173
|
}
|
|
174
|
-
const
|
|
175
|
-
if (typeof
|
|
174
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
175
|
+
if (typeof module.projectLifecycleSignal !== "function") {
|
|
176
176
|
return null;
|
|
177
177
|
}
|
|
178
|
-
return
|
|
178
|
+
return module.projectLifecycleSignal(rawPayload, moduleInputSource(source));
|
|
179
179
|
}
|
|
180
180
|
async expandSubscriptionShortcut(source, input) {
|
|
181
181
|
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
182
182
|
return null;
|
|
183
183
|
}
|
|
184
|
-
const
|
|
185
|
-
if (typeof
|
|
184
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
185
|
+
if (typeof module.expandSubscriptionShortcut !== "function") {
|
|
186
186
|
return null;
|
|
187
187
|
}
|
|
188
|
-
return
|
|
188
|
+
return module.expandSubscriptionShortcut({
|
|
189
189
|
name: input.name,
|
|
190
190
|
args: input.args,
|
|
191
|
-
source:
|
|
191
|
+
source: moduleInputSource(source),
|
|
192
192
|
});
|
|
193
193
|
}
|
|
194
194
|
async deriveInlinePreview(source, item) {
|
|
195
195
|
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
196
196
|
return null;
|
|
197
197
|
}
|
|
198
|
-
const
|
|
199
|
-
if (typeof
|
|
198
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
199
|
+
if (typeof module.deriveInlinePreview !== "function") {
|
|
200
200
|
return null;
|
|
201
201
|
}
|
|
202
|
-
return
|
|
202
|
+
return module.deriveInlinePreview(item, moduleInputSource(source));
|
|
203
|
+
}
|
|
204
|
+
async deriveNotificationGrouping(source, item) {
|
|
205
|
+
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
209
|
+
if (typeof module.deriveNotificationGrouping !== "function") {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
return module.deriveNotificationGrouping(item, moduleInputSource(source));
|
|
213
|
+
}
|
|
214
|
+
async summarizeDigestThread(source, items, grouping) {
|
|
215
|
+
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
219
|
+
if (typeof module.summarizeDigestThread !== "function") {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return module.summarizeDigestThread(items, moduleInputSource(source), grouping);
|
|
203
223
|
}
|
|
204
224
|
async syncAll() {
|
|
205
225
|
const sources = this.store
|
|
@@ -255,10 +275,10 @@ class RemoteSourceRuntime {
|
|
|
255
275
|
};
|
|
256
276
|
}
|
|
257
277
|
}
|
|
258
|
-
const
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
const spec =
|
|
278
|
+
const module = await this.moduleRegistry.resolve(source, this.homeDir);
|
|
279
|
+
const moduleSource = moduleInputSource(source);
|
|
280
|
+
module.validateConfig(moduleSource);
|
|
281
|
+
const spec = module.buildManagedSourceSpec(moduleSource);
|
|
262
282
|
const binding = managedBindingForSource(source);
|
|
263
283
|
const ensured = await this.client.sourceEnsure({
|
|
264
284
|
namespace: binding.namespace,
|
|
@@ -279,7 +299,7 @@ class RemoteSourceRuntime {
|
|
|
279
299
|
if (Object.keys(rawPayload).length === 0) {
|
|
280
300
|
continue;
|
|
281
301
|
}
|
|
282
|
-
const mapped =
|
|
302
|
+
const mapped = module.mapRawEvent(rawPayload, moduleSource);
|
|
283
303
|
if (!mapped) {
|
|
284
304
|
continue;
|
|
285
305
|
}
|
|
@@ -348,14 +368,14 @@ class RemoteSourceRuntime {
|
|
|
348
368
|
}
|
|
349
369
|
}
|
|
350
370
|
exports.RemoteSourceRuntime = RemoteSourceRuntime;
|
|
351
|
-
function
|
|
371
|
+
function moduleInputSource(source) {
|
|
352
372
|
if (source.sourceType !== "remote_source") {
|
|
353
373
|
return source;
|
|
354
374
|
}
|
|
355
375
|
const config = asRecord(source.config);
|
|
356
376
|
return {
|
|
357
377
|
...source,
|
|
358
|
-
config: asRecord(config.
|
|
378
|
+
config: asRecord(config.moduleConfig),
|
|
359
379
|
};
|
|
360
380
|
}
|
|
361
381
|
function defaultManagedBindingForSource(source) {
|