@holon-run/agentinbox 0.1.3 → 0.2.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 +1 -0
- package/dist/src/adapters.js +33 -2
- package/dist/src/cli.js +63 -14
- package/dist/src/http.js +85 -7
- package/dist/src/service.js +469 -16
- package/dist/src/source_resolution.js +100 -0
- package/dist/src/source_schema.js +23 -1
- package/dist/src/sources/github.js +73 -1
- package/dist/src/sources/remote.js +24 -0
- package/dist/src/sources/remote_profiles.js +133 -0
- package/dist/src/store.js +129 -6
- package/drizzle/migrations/0002_subscription_cleanup_policy.sql +45 -0
- package/drizzle/migrations/0003_subscription_lifecycle_retirements.sql +14 -0
- package/drizzle/migrations/0004_source_idle_states.sql +10 -0
- package/drizzle/schema.ts +24 -2
- package/package.json +2 -2
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveSourceIdentity = resolveSourceIdentity;
|
|
4
|
+
exports.resolveSourceSchema = resolveSourceSchema;
|
|
5
|
+
exports.withResolvedIdentity = withResolvedIdentity;
|
|
6
|
+
const paths_1 = require("./paths");
|
|
7
|
+
const remote_profiles_1 = require("./sources/remote_profiles");
|
|
8
|
+
const source_schema_1 = require("./source_schema");
|
|
9
|
+
async function resolveSourceIdentity(source, context = {}) {
|
|
10
|
+
if (source.sourceType === "local_event") {
|
|
11
|
+
return {
|
|
12
|
+
hostType: "local_event",
|
|
13
|
+
sourceKind: "local_event",
|
|
14
|
+
implementationId: "builtin.local_event",
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const builtinImplementationId = (0, remote_profiles_1.builtInProfileIdForSourceType)(source.sourceType);
|
|
18
|
+
if (builtinImplementationId) {
|
|
19
|
+
return {
|
|
20
|
+
hostType: "remote_source",
|
|
21
|
+
sourceKind: source.sourceType,
|
|
22
|
+
implementationId: builtinImplementationId,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (source.sourceType !== "remote_source") {
|
|
26
|
+
throw new Error(`unsupported source type for source resolution: ${source.sourceType}`);
|
|
27
|
+
}
|
|
28
|
+
const profileRegistry = context.profileRegistry ?? new remote_profiles_1.RemoteSourceProfileRegistry();
|
|
29
|
+
const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
|
|
30
|
+
const profile = await profileRegistry.resolve(source, homeDir);
|
|
31
|
+
const capabilityDescription = typeof profile.describeCapabilities === "function"
|
|
32
|
+
? profile.describeCapabilities(profileInputSource(source))
|
|
33
|
+
: undefined;
|
|
34
|
+
return {
|
|
35
|
+
hostType: "remote_source",
|
|
36
|
+
sourceKind: capabilityDescription?.sourceKind?.trim() || `remote:${profile.id}`,
|
|
37
|
+
implementationId: profile.id,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async function resolveSourceSchema(source, context = {}) {
|
|
41
|
+
const profileRegistry = context.profileRegistry ?? new remote_profiles_1.RemoteSourceProfileRegistry();
|
|
42
|
+
const homeDir = context.homeDir ?? (0, paths_1.resolveAgentInboxHome)(process.env);
|
|
43
|
+
const resolvedContext = { ...context, profileRegistry, homeDir };
|
|
44
|
+
const identity = await resolveSourceIdentity(source, resolvedContext);
|
|
45
|
+
const staticSchema = (0, source_schema_1.getSourceSchema)(source.sourceType);
|
|
46
|
+
let capabilityDescription;
|
|
47
|
+
let profile = null;
|
|
48
|
+
if (identity.hostType === "remote_source") {
|
|
49
|
+
profile = await profileRegistry.resolve(source, homeDir);
|
|
50
|
+
capabilityDescription = typeof profile.describeCapabilities === "function"
|
|
51
|
+
? profile.describeCapabilities(profileInputSource(source))
|
|
52
|
+
: undefined;
|
|
53
|
+
}
|
|
54
|
+
return withResolvedIdentity(source.sourceId, {
|
|
55
|
+
sourceType: staticSchema.sourceType,
|
|
56
|
+
metadataFields: capabilityDescription?.metadataFields ?? staticSchema.metadataFields,
|
|
57
|
+
payloadExamples: capabilityDescription?.payloadExamples ?? staticSchema.payloadExamples,
|
|
58
|
+
eventVariantExamples: capabilityDescription?.eventVariantExamples ?? staticSchema.eventVariantExamples,
|
|
59
|
+
configFields: capabilityDescription?.configSchema ?? staticSchema.configFields,
|
|
60
|
+
}, identity, identity.hostType === "remote_source"
|
|
61
|
+
? {
|
|
62
|
+
aliases: capabilityDescription?.aliases,
|
|
63
|
+
supportsTrackedResourceRef: typeof profile?.deriveTrackedResource === "function",
|
|
64
|
+
supportsLifecycleSignals: typeof profile?.projectLifecycleSignal === "function",
|
|
65
|
+
shortcuts: typeof profile?.listSubscriptionShortcuts === "function"
|
|
66
|
+
? profile.listSubscriptionShortcuts(profileInputSource(source))
|
|
67
|
+
: [],
|
|
68
|
+
}
|
|
69
|
+
: undefined);
|
|
70
|
+
}
|
|
71
|
+
function withResolvedIdentity(sourceId, schema, identity, capabilityMetadata) {
|
|
72
|
+
return {
|
|
73
|
+
sourceId,
|
|
74
|
+
sourceType: schema.sourceType,
|
|
75
|
+
metadataFields: schema.metadataFields,
|
|
76
|
+
payloadExamples: schema.payloadExamples,
|
|
77
|
+
eventVariantExamples: schema.eventVariantExamples,
|
|
78
|
+
configFields: schema.configFields,
|
|
79
|
+
hostType: identity.hostType,
|
|
80
|
+
sourceKind: identity.sourceKind,
|
|
81
|
+
implementationId: identity.implementationId,
|
|
82
|
+
...(capabilityMetadata?.aliases?.length ? { aliases: capabilityMetadata.aliases } : {}),
|
|
83
|
+
...(capabilityMetadata ? {
|
|
84
|
+
subscriptionSchema: {
|
|
85
|
+
supportsTrackedResourceRef: capabilityMetadata.supportsTrackedResourceRef ?? false,
|
|
86
|
+
supportsLifecycleSignals: capabilityMetadata.supportsLifecycleSignals ?? false,
|
|
87
|
+
shortcuts: capabilityMetadata.shortcuts ?? [],
|
|
88
|
+
},
|
|
89
|
+
} : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function profileInputSource(source) {
|
|
93
|
+
if (source.sourceType !== "remote_source") {
|
|
94
|
+
return source;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
...source,
|
|
98
|
+
config: (0, remote_profiles_1.profileConfigForSource)(source),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -32,6 +32,7 @@ const SOURCE_SCHEMAS = {
|
|
|
32
32
|
{ name: "action", type: "string", description: "GitHub event action suffix such as created." },
|
|
33
33
|
{ name: "author", type: "string|null", description: "Actor login for the event." },
|
|
34
34
|
{ name: "isPullRequest", type: "boolean", description: "Whether the event targets a pull request surface." },
|
|
35
|
+
{ name: "reviewState", type: "string|null", description: "Review decision state for PullRequestReviewEvent such as approved or changes_requested." },
|
|
35
36
|
{ name: "labels", type: "string[]", description: "Labels extracted from the issue or pull request." },
|
|
36
37
|
{ name: "mentions", type: "string[]", description: "Mention handles extracted from title/body/comment text." },
|
|
37
38
|
{ name: "number", type: "number|null", description: "Issue or pull request number when present." },
|
|
@@ -49,8 +50,29 @@ const SOURCE_SCHEMAS = {
|
|
|
49
50
|
issue: { number: 12, title: "Track filtering work" },
|
|
50
51
|
comment: { body: "@alpha please look" },
|
|
51
52
|
},
|
|
53
|
+
{
|
|
54
|
+
id: "1234567891",
|
|
55
|
+
type: "PullRequestReviewEvent",
|
|
56
|
+
action: "created",
|
|
57
|
+
actor: "Copilot",
|
|
58
|
+
pull_request: { number: 67, title: "feat: add remote module capability hooks" },
|
|
59
|
+
review: { state: "commented", body: "review summary" },
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "1234567892",
|
|
63
|
+
type: "PullRequestEvent",
|
|
64
|
+
action: "closed",
|
|
65
|
+
actor: "jolestar",
|
|
66
|
+
pull_request: { number: 72, title: "feat: add cleanup policy lifecycle engine", merged: true },
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
eventVariantExamples: [
|
|
70
|
+
"IssueCommentEvent.created",
|
|
71
|
+
"PullRequestEvent.opened",
|
|
72
|
+
"PullRequestEvent.closed",
|
|
73
|
+
"PullRequestReviewEvent.created",
|
|
74
|
+
"PullRequestReviewCommentEvent.created",
|
|
52
75
|
],
|
|
53
|
-
eventVariantExamples: ["IssueCommentEvent.created", "PullRequestEvent.opened", "PullRequestReviewCommentEvent.created"],
|
|
54
76
|
configFields: [
|
|
55
77
|
{ name: "owner", type: "string", required: true, description: "GitHub repository owner." },
|
|
56
78
|
{ name: "repo", type: "string", required: true, description: "GitHub repository name." },
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GithubDeliveryAdapter = exports.GithubUxcClient = exports.DEFAULT_GITHUB_EVENT_TYPES = exports.GITHUB_ENDPOINT = void 0;
|
|
4
4
|
exports.normalizeGithubRepoEvent = normalizeGithubRepoEvent;
|
|
5
|
+
exports.deriveGithubTrackedResource = deriveGithubTrackedResource;
|
|
6
|
+
exports.githubSubscriptionShortcutSpec = githubSubscriptionShortcutSpec;
|
|
7
|
+
exports.expandGithubSubscriptionShortcut = expandGithubSubscriptionShortcut;
|
|
8
|
+
exports.projectGithubLifecycleSignal = projectGithubLifecycleSignal;
|
|
5
9
|
exports.parseGithubSourceConfig = parseGithubSourceConfig;
|
|
6
10
|
const uxc_daemon_client_1 = require("@holon-run/uxc-daemon-client");
|
|
7
11
|
exports.GITHUB_ENDPOINT = "https://api.github.com";
|
|
@@ -9,6 +13,7 @@ exports.DEFAULT_GITHUB_EVENT_TYPES = [
|
|
|
9
13
|
"IssuesEvent",
|
|
10
14
|
"IssueCommentEvent",
|
|
11
15
|
"PullRequestEvent",
|
|
16
|
+
"PullRequestReviewEvent",
|
|
12
17
|
"PullRequestReviewCommentEvent",
|
|
13
18
|
];
|
|
14
19
|
class GithubUxcClient {
|
|
@@ -97,15 +102,18 @@ function normalizeGithubRepoEvent(source, config, raw) {
|
|
|
97
102
|
const repo = asRecord(event.repo);
|
|
98
103
|
const issue = asRecord(payload.issue);
|
|
99
104
|
const pullRequest = asRecord(payload.pull_request);
|
|
105
|
+
const review = asRecord(payload.review);
|
|
100
106
|
const comment = asRecord(payload.comment);
|
|
101
107
|
const action = asString(payload.action) ?? "observed";
|
|
102
108
|
const number = asNumber(issue.number) ?? asNumber(pullRequest.number);
|
|
103
109
|
const isPullRequest = eventType.startsWith("PullRequest");
|
|
104
110
|
const title = asString(issue.title) ?? asString(pullRequest.title) ?? null;
|
|
105
|
-
const
|
|
111
|
+
const reviewState = asString(review.state) ?? null;
|
|
112
|
+
const body = asString(comment.body) ?? asString(review.body) ?? asString(issue.body) ?? asString(pullRequest.body) ?? null;
|
|
106
113
|
const labels = extractLabels(issue.labels);
|
|
107
114
|
const mentions = extractMentions(title, body);
|
|
108
115
|
const url = asString(comment.html_url) ??
|
|
116
|
+
asString(review.html_url) ??
|
|
109
117
|
asString(issue.html_url) ??
|
|
110
118
|
asString(pullRequest.html_url) ??
|
|
111
119
|
asString(event.url);
|
|
@@ -125,6 +133,7 @@ function normalizeGithubRepoEvent(source, config, raw) {
|
|
|
125
133
|
number,
|
|
126
134
|
author: asString(actor.login) ?? null,
|
|
127
135
|
isPullRequest,
|
|
136
|
+
reviewState,
|
|
128
137
|
labels,
|
|
129
138
|
mentions,
|
|
130
139
|
title,
|
|
@@ -138,11 +147,71 @@ function normalizeGithubRepoEvent(source, config, raw) {
|
|
|
138
147
|
actor: actor.login ?? null,
|
|
139
148
|
issue,
|
|
140
149
|
pull_request: pullRequest,
|
|
150
|
+
review,
|
|
141
151
|
comment,
|
|
152
|
+
merged: asBoolean(pullRequest.merged),
|
|
142
153
|
},
|
|
143
154
|
deliveryHandle,
|
|
144
155
|
};
|
|
145
156
|
}
|
|
157
|
+
function deriveGithubTrackedResource(filter) {
|
|
158
|
+
const metadata = asRecord(filter.metadata);
|
|
159
|
+
const payload = asRecord(filter.payload);
|
|
160
|
+
const number = asNumber(metadata.number) ?? asNumber(payload.number) ?? asNumber(payload.ref);
|
|
161
|
+
const isPullRequest = asBoolean(metadata.isPullRequest);
|
|
162
|
+
if (!number || isPullRequest !== true) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
return { ref: `pr:${number}` };
|
|
166
|
+
}
|
|
167
|
+
function githubSubscriptionShortcutSpec() {
|
|
168
|
+
return [{
|
|
169
|
+
name: "pr",
|
|
170
|
+
description: "Follow one pull request and auto-retire when it closes.",
|
|
171
|
+
argsSchema: [{ name: "number", type: "number", required: true, description: "Pull request number." }],
|
|
172
|
+
}];
|
|
173
|
+
}
|
|
174
|
+
function expandGithubSubscriptionShortcut(input) {
|
|
175
|
+
if (input.name !== "pr") {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
const number = typeof input.args?.number === "number" ? input.args.number : null;
|
|
179
|
+
if (!number || !Number.isInteger(number) || number <= 0) {
|
|
180
|
+
throw new Error("subscription shortcut pr requires a positive integer number");
|
|
181
|
+
}
|
|
182
|
+
return {
|
|
183
|
+
filter: {
|
|
184
|
+
metadata: {
|
|
185
|
+
number,
|
|
186
|
+
isPullRequest: true,
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
trackedResourceRef: `pr:${number}`,
|
|
190
|
+
cleanupPolicy: { mode: "on_terminal" },
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function projectGithubLifecycleSignal(rawPayload) {
|
|
194
|
+
const eventType = asString(rawPayload.type);
|
|
195
|
+
if (eventType !== "PullRequestEvent") {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
const action = asString(rawPayload.action);
|
|
199
|
+
if (action !== "closed" && action !== "merged") {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
const pullRequest = asRecord(rawPayload.pull_request);
|
|
203
|
+
const number = asNumber(pullRequest.number);
|
|
204
|
+
if (!number) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
const merged = action === "merged" || asBoolean(rawPayload.merged) === true || asBoolean(pullRequest.merged) === true;
|
|
208
|
+
return {
|
|
209
|
+
ref: `pr:${number}`,
|
|
210
|
+
terminal: true,
|
|
211
|
+
state: "closed",
|
|
212
|
+
result: merged ? "merged" : "closed",
|
|
213
|
+
};
|
|
214
|
+
}
|
|
146
215
|
function buildGithubDeliveryHandle(config, eventType, number, comment) {
|
|
147
216
|
if (!number) {
|
|
148
217
|
return null;
|
|
@@ -243,6 +312,9 @@ function asString(value) {
|
|
|
243
312
|
function asNumber(value) {
|
|
244
313
|
return typeof value === "number" ? value : null;
|
|
245
314
|
}
|
|
315
|
+
function asBoolean(value) {
|
|
316
|
+
return typeof value === "boolean" ? value : null;
|
|
317
|
+
}
|
|
246
318
|
function asStringArray(value) {
|
|
247
319
|
if (!Array.isArray(value)) {
|
|
248
320
|
return null;
|
|
@@ -167,6 +167,30 @@ class RemoteSourceRuntime {
|
|
|
167
167
|
erroredSourceIds: Array.from(this.errorCounts.keys()).sort(),
|
|
168
168
|
};
|
|
169
169
|
}
|
|
170
|
+
async projectLifecycleSignal(source, rawPayload) {
|
|
171
|
+
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const profile = await this.profileRegistry.resolve(source, this.homeDir);
|
|
175
|
+
if (typeof profile.projectLifecycleSignal !== "function") {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return profile.projectLifecycleSignal(rawPayload, profileInputSource(source));
|
|
179
|
+
}
|
|
180
|
+
async expandSubscriptionShortcut(source, input) {
|
|
181
|
+
if (!REMOTE_SOURCE_TYPES.has(source.sourceType)) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const profile = await this.profileRegistry.resolve(source, this.homeDir);
|
|
185
|
+
if (typeof profile.expandSubscriptionShortcut !== "function") {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return profile.expandSubscriptionShortcut({
|
|
189
|
+
name: input.name,
|
|
190
|
+
args: input.args,
|
|
191
|
+
source: profileInputSource(source),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
170
194
|
async syncAll() {
|
|
171
195
|
const sources = this.store
|
|
172
196
|
.listSources()
|
|
@@ -90,6 +90,16 @@ function validateProfileContract(profile, sourcePath) {
|
|
|
90
90
|
if (typeof profile.mapRawEvent !== "function") {
|
|
91
91
|
throw new Error(`remote_source profile missing mapRawEvent(): ${sourcePath}`);
|
|
92
92
|
}
|
|
93
|
+
validateOptionalHook(profile.describeCapabilities, "describeCapabilities", sourcePath);
|
|
94
|
+
validateOptionalHook(profile.listSubscriptionShortcuts, "listSubscriptionShortcuts", sourcePath);
|
|
95
|
+
validateOptionalHook(profile.expandSubscriptionShortcut, "expandSubscriptionShortcut", sourcePath);
|
|
96
|
+
validateOptionalHook(profile.deriveTrackedResource, "deriveTrackedResource", sourcePath);
|
|
97
|
+
validateOptionalHook(profile.projectLifecycleSignal, "projectLifecycleSignal", sourcePath);
|
|
98
|
+
}
|
|
99
|
+
function validateOptionalHook(value, name, sourcePath) {
|
|
100
|
+
if (value !== undefined && typeof value !== "function") {
|
|
101
|
+
throw new Error(`remote_source profile ${name} must be a function when provided: ${sourcePath}`);
|
|
102
|
+
}
|
|
93
103
|
}
|
|
94
104
|
function resolveAndValidateProfilePath(profileRoot, profilePath) {
|
|
95
105
|
const resolved = node_path_1.default.isAbsolute(profilePath)
|
|
@@ -120,6 +130,71 @@ function asNonEmptyString(value) {
|
|
|
120
130
|
}
|
|
121
131
|
const GITHUB_REPO_PROFILE = {
|
|
122
132
|
id: "builtin.github_repo",
|
|
133
|
+
describeCapabilities(source) {
|
|
134
|
+
const config = (0, github_1.parseGithubSourceConfig)(source);
|
|
135
|
+
return {
|
|
136
|
+
sourceKind: "github_repo",
|
|
137
|
+
aliases: ["github_repo"],
|
|
138
|
+
configSchema: [
|
|
139
|
+
{ name: "owner", type: "string", required: true, description: "GitHub repository owner." },
|
|
140
|
+
{ name: "repo", type: "string", required: true, description: "GitHub repository name." },
|
|
141
|
+
{ name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
|
|
142
|
+
{ name: "eventTypes", type: "string[]", required: false, description: "Optional GitHub event type allowlist." },
|
|
143
|
+
{ name: "perPage", type: "number", required: false, description: `Repository events requested per poll. Default ${config.perPage ?? 10}.` },
|
|
144
|
+
{ name: "pollIntervalSecs", type: "number", required: false, description: `Polling interval in seconds. Default ${config.pollIntervalSecs ?? 30}.` },
|
|
145
|
+
],
|
|
146
|
+
metadataFields: [
|
|
147
|
+
{ name: "eventType", type: "string", description: "GitHub event type such as IssueCommentEvent." },
|
|
148
|
+
{ name: "action", type: "string", description: "GitHub event action suffix such as created." },
|
|
149
|
+
{ name: "author", type: "string|null", description: "Actor login for the event." },
|
|
150
|
+
{ name: "isPullRequest", type: "boolean", description: "Whether the event targets a pull request surface." },
|
|
151
|
+
{ name: "reviewState", type: "string|null", description: "Review decision state for PullRequestReviewEvent such as approved or changes_requested." },
|
|
152
|
+
{ name: "labels", type: "string[]", description: "Labels extracted from the issue or pull request." },
|
|
153
|
+
{ name: "mentions", type: "string[]", description: "Mention handles extracted from title/body/comment text." },
|
|
154
|
+
{ name: "number", type: "number|null", description: "Issue or pull request number when present." },
|
|
155
|
+
{ name: "repoFullName", type: "string", description: "Repository full name in owner/repo form." },
|
|
156
|
+
{ name: "title", type: "string|null", description: "Issue, pull request, or comment title." },
|
|
157
|
+
{ name: "body", type: "string|null", description: "Issue, pull request, or comment body text." },
|
|
158
|
+
{ name: "url", type: "string|null", description: "Primary GitHub HTML URL for the event target." },
|
|
159
|
+
],
|
|
160
|
+
payloadExamples: [
|
|
161
|
+
{
|
|
162
|
+
id: "1234567891",
|
|
163
|
+
type: "PullRequestReviewEvent",
|
|
164
|
+
action: "created",
|
|
165
|
+
actor: "Copilot",
|
|
166
|
+
pull_request: { number: 67, title: "feat: add remote module capability hooks" },
|
|
167
|
+
review: { state: "commented", body: "review summary" },
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "1234567892",
|
|
171
|
+
type: "PullRequestEvent",
|
|
172
|
+
action: "closed",
|
|
173
|
+
actor: "jolestar",
|
|
174
|
+
pull_request: { number: 72, title: "feat: add cleanup policy lifecycle engine", merged: true },
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
eventVariantExamples: [
|
|
178
|
+
"IssueCommentEvent.created",
|
|
179
|
+
"PullRequestEvent.opened",
|
|
180
|
+
"PullRequestEvent.closed",
|
|
181
|
+
"PullRequestReviewEvent.created",
|
|
182
|
+
"PullRequestReviewCommentEvent.created",
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
},
|
|
186
|
+
listSubscriptionShortcuts() {
|
|
187
|
+
return (0, github_1.githubSubscriptionShortcutSpec)();
|
|
188
|
+
},
|
|
189
|
+
expandSubscriptionShortcut(input) {
|
|
190
|
+
return (0, github_1.expandGithubSubscriptionShortcut)(input);
|
|
191
|
+
},
|
|
192
|
+
deriveTrackedResource(filter) {
|
|
193
|
+
return (0, github_1.deriveGithubTrackedResource)(filter);
|
|
194
|
+
},
|
|
195
|
+
projectLifecycleSignal(rawPayload) {
|
|
196
|
+
return (0, github_1.projectGithubLifecycleSignal)(rawPayload);
|
|
197
|
+
},
|
|
123
198
|
validateConfig(source) {
|
|
124
199
|
(0, github_1.parseGithubSourceConfig)(source);
|
|
125
200
|
},
|
|
@@ -164,6 +239,34 @@ const GITHUB_REPO_PROFILE = {
|
|
|
164
239
|
};
|
|
165
240
|
const GITHUB_REPO_CI_PROFILE = {
|
|
166
241
|
id: "builtin.github_repo_ci",
|
|
242
|
+
describeCapabilities() {
|
|
243
|
+
return {
|
|
244
|
+
sourceKind: "github_repo_ci",
|
|
245
|
+
aliases: ["github_repo_ci"],
|
|
246
|
+
configSchema: [
|
|
247
|
+
{ name: "owner", type: "string", required: true, description: "GitHub repository owner." },
|
|
248
|
+
{ name: "repo", type: "string", required: true, description: "GitHub repository name." },
|
|
249
|
+
{ name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
|
|
250
|
+
{ name: "pollIntervalSecs", type: "number", required: false, description: "Polling interval in seconds." },
|
|
251
|
+
{ name: "perPage", type: "number", required: false, description: "Workflow runs requested per poll." },
|
|
252
|
+
{ name: "eventFilter", type: "string", required: false, description: "Optional GitHub workflow event filter." },
|
|
253
|
+
{ name: "branch", type: "string", required: false, description: "Optional branch filter for workflow runs." },
|
|
254
|
+
{ name: "statusFilter", type: "string", required: false, description: "Optional workflow status filter." },
|
|
255
|
+
],
|
|
256
|
+
metadataFields: [
|
|
257
|
+
{ name: "name", type: "string|null", description: "Workflow run name." },
|
|
258
|
+
{ name: "status", type: "string", description: "Normalized workflow run status." },
|
|
259
|
+
{ name: "conclusion", type: "string|null", description: "Workflow run conclusion when completed." },
|
|
260
|
+
{ name: "event", type: "string|null", description: "GitHub trigger event for the workflow run." },
|
|
261
|
+
{ name: "headBranch", type: "string|null", description: "Head branch for the workflow run." },
|
|
262
|
+
{ name: "headSha", type: "string|null", description: "Head commit SHA for the workflow run." },
|
|
263
|
+
{ name: "actor", type: "string|null", description: "Actor login for the workflow run." },
|
|
264
|
+
{ name: "commitMessage", type: "string|null", description: "Head commit message when present." },
|
|
265
|
+
{ name: "htmlUrl", type: "string|null", description: "GitHub Actions run URL." },
|
|
266
|
+
],
|
|
267
|
+
eventVariantExamples: ["workflow_run.ci.completed.failure", "workflow_run.nightly_checks.observed"],
|
|
268
|
+
};
|
|
269
|
+
},
|
|
167
270
|
validateConfig(source) {
|
|
168
271
|
(0, github_ci_1.parseGithubCiSourceConfig)(source);
|
|
169
272
|
},
|
|
@@ -217,6 +320,36 @@ const GITHUB_REPO_CI_PROFILE = {
|
|
|
217
320
|
};
|
|
218
321
|
const FEISHU_BOT_PROFILE = {
|
|
219
322
|
id: "builtin.feishu_bot",
|
|
323
|
+
describeCapabilities() {
|
|
324
|
+
return {
|
|
325
|
+
sourceKind: "feishu_bot",
|
|
326
|
+
aliases: ["feishu_bot"],
|
|
327
|
+
configSchema: [
|
|
328
|
+
{ name: "appId", type: "string", required: true, description: "Feishu app ID." },
|
|
329
|
+
{ name: "appSecret", type: "string", required: true, description: "Feishu app secret." },
|
|
330
|
+
{ name: "eventTypes", type: "string[]", required: false, description: "Optional Feishu event type allowlist." },
|
|
331
|
+
{ name: "chatIds", type: "string[]", required: false, description: "Optional Feishu chat allowlist." },
|
|
332
|
+
{ name: "schemaUrl", type: "string", required: false, description: "Optional Feishu OpenAPI schema URL." },
|
|
333
|
+
{ name: "replyInThread", type: "boolean", required: false, description: "Reply in thread when sending outbound messages." },
|
|
334
|
+
{ name: "uxcAuth", type: "string", required: false, description: "Optional uxc auth profile." },
|
|
335
|
+
],
|
|
336
|
+
metadataFields: [
|
|
337
|
+
{ name: "eventType", type: "string", description: "Feishu event type." },
|
|
338
|
+
{ name: "chatId", type: "string", description: "Target chat ID." },
|
|
339
|
+
{ name: "chatType", type: "string|null", description: "Feishu chat type." },
|
|
340
|
+
{ name: "messageId", type: "string", description: "Feishu message ID." },
|
|
341
|
+
{ name: "messageType", type: "string", description: "Feishu message type such as text." },
|
|
342
|
+
{ name: "senderOpenId", type: "string|null", description: "Sender open_id when present." },
|
|
343
|
+
{ name: "senderType", type: "string|null", description: "Sender type." },
|
|
344
|
+
{ name: "mentions", type: "string[]", description: "Mention names extracted from the message." },
|
|
345
|
+
{ name: "mentionOpenIds", type: "string[]", description: "Mention open_ids extracted from the message." },
|
|
346
|
+
{ name: "content", type: "string|null", description: "Normalized message content string." },
|
|
347
|
+
{ name: "threadId", type: "string|null", description: "Thread or root message ID when present." },
|
|
348
|
+
{ name: "parentId", type: "string|null", description: "Parent message ID when present." },
|
|
349
|
+
],
|
|
350
|
+
eventVariantExamples: ["im.message.receive_v1.text"],
|
|
351
|
+
};
|
|
352
|
+
},
|
|
220
353
|
validateConfig(source) {
|
|
221
354
|
(0, feishu_1.parseFeishuSourceConfig)(source);
|
|
222
355
|
},
|
package/dist/src/store.js
CHANGED
|
@@ -16,6 +16,17 @@ function parseJson(value) {
|
|
|
16
16
|
}
|
|
17
17
|
return JSON.parse(value);
|
|
18
18
|
}
|
|
19
|
+
function earlierLifecycleRetirement(left, right) {
|
|
20
|
+
const leftAt = Date.parse(left.retireAt);
|
|
21
|
+
const rightAt = Date.parse(right.retireAt);
|
|
22
|
+
if (!Number.isNaN(leftAt) && !Number.isNaN(rightAt) && leftAt <= rightAt) {
|
|
23
|
+
return {
|
|
24
|
+
...left,
|
|
25
|
+
updatedAt: right.updatedAt,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
return right;
|
|
29
|
+
}
|
|
19
30
|
class AgentInboxStore {
|
|
20
31
|
dbPath;
|
|
21
32
|
db;
|
|
@@ -193,6 +204,38 @@ class AgentInboxStore {
|
|
|
193
204
|
const rows = this.getAll("select * from sources order by created_at asc");
|
|
194
205
|
return rows.map((row) => this.mapSource(row));
|
|
195
206
|
}
|
|
207
|
+
getSourceIdleState(sourceId) {
|
|
208
|
+
const row = this.getOne("select * from source_idle_states where source_id = ?", [sourceId]);
|
|
209
|
+
return row ? this.mapSourceIdleState(row) : null;
|
|
210
|
+
}
|
|
211
|
+
listSourceIdleStatesDue(cutoffIso) {
|
|
212
|
+
const rows = this.getAll("select * from source_idle_states where auto_pause_at <= ? and auto_paused_at is null order by auto_pause_at asc", [cutoffIso]);
|
|
213
|
+
return rows.map((row) => this.mapSourceIdleState(row));
|
|
214
|
+
}
|
|
215
|
+
upsertSourceIdleState(idleState) {
|
|
216
|
+
this.db.run(`
|
|
217
|
+
insert into source_idle_states (
|
|
218
|
+
source_id, idle_since, auto_pause_at, auto_paused_at, updated_at
|
|
219
|
+
) values (?, ?, ?, ?, ?)
|
|
220
|
+
on conflict(source_id) do update set
|
|
221
|
+
idle_since = excluded.idle_since,
|
|
222
|
+
auto_pause_at = excluded.auto_pause_at,
|
|
223
|
+
auto_paused_at = excluded.auto_paused_at,
|
|
224
|
+
updated_at = excluded.updated_at
|
|
225
|
+
`, [
|
|
226
|
+
idleState.sourceId,
|
|
227
|
+
idleState.idleSince,
|
|
228
|
+
idleState.autoPauseAt,
|
|
229
|
+
idleState.autoPausedAt ?? null,
|
|
230
|
+
idleState.updatedAt,
|
|
231
|
+
]);
|
|
232
|
+
this.persist();
|
|
233
|
+
return this.getSourceIdleState(idleState.sourceId);
|
|
234
|
+
}
|
|
235
|
+
deleteSourceIdleState(sourceId) {
|
|
236
|
+
this.db.run("delete from source_idle_states where source_id = ?", [sourceId]);
|
|
237
|
+
this.persist();
|
|
238
|
+
}
|
|
196
239
|
updateSourceDefinition(sourceId, input) {
|
|
197
240
|
const current = this.getSource(sourceId);
|
|
198
241
|
if (!current) {
|
|
@@ -245,6 +288,7 @@ class AgentInboxStore {
|
|
|
245
288
|
this.db.run("delete from stream_events where stream_id = ?", [stream.streamId]);
|
|
246
289
|
this.db.run("delete from streams where stream_id = ?", [stream.streamId]);
|
|
247
290
|
}
|
|
291
|
+
this.db.run("delete from source_idle_states where source_id = ?", [sourceId]);
|
|
248
292
|
this.db.run("delete from sources where source_id = ?", [sourceId]);
|
|
249
293
|
});
|
|
250
294
|
this.persist();
|
|
@@ -319,15 +363,15 @@ class AgentInboxStore {
|
|
|
319
363
|
insertSubscription(subscription) {
|
|
320
364
|
this.db.run(`
|
|
321
365
|
insert into subscriptions (
|
|
322
|
-
subscription_id, agent_id, source_id, filter_json,
|
|
366
|
+
subscription_id, agent_id, source_id, filter_json, tracked_resource_ref, cleanup_policy_json, start_policy, start_offset, start_time, created_at
|
|
323
367
|
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
324
368
|
`, [
|
|
325
369
|
subscription.subscriptionId,
|
|
326
370
|
subscription.agentId,
|
|
327
371
|
subscription.sourceId,
|
|
328
372
|
JSON.stringify(subscription.filter),
|
|
329
|
-
subscription.
|
|
330
|
-
subscription.
|
|
373
|
+
subscription.trackedResourceRef ?? null,
|
|
374
|
+
JSON.stringify(subscription.cleanupPolicy),
|
|
331
375
|
subscription.startPolicy,
|
|
332
376
|
subscription.startOffset ?? null,
|
|
333
377
|
subscription.startTime ?? null,
|
|
@@ -352,10 +396,63 @@ class AgentInboxStore {
|
|
|
352
396
|
if (!subscription) {
|
|
353
397
|
return null;
|
|
354
398
|
}
|
|
355
|
-
this.
|
|
399
|
+
this.inTransaction(() => {
|
|
400
|
+
this.db.run("delete from consumer_commits where consumer_id in (select consumer_id from consumers where subscription_id = ?)", [subscriptionId]);
|
|
401
|
+
this.db.run("delete from consumers where subscription_id = ?", [subscriptionId]);
|
|
402
|
+
this.db.run("delete from subscription_lifecycle_retirements where subscription_id = ?", [subscriptionId]);
|
|
403
|
+
this.db.run("delete from subscriptions where subscription_id = ?", [subscriptionId]);
|
|
404
|
+
});
|
|
356
405
|
this.persist();
|
|
357
406
|
return subscription;
|
|
358
407
|
}
|
|
408
|
+
getSubscriptionLifecycleRetirement(subscriptionId) {
|
|
409
|
+
const row = this.getOne("select * from subscription_lifecycle_retirements where subscription_id = ?", [subscriptionId]);
|
|
410
|
+
return row ? this.mapSubscriptionLifecycleRetirement(row) : null;
|
|
411
|
+
}
|
|
412
|
+
listSubscriptionLifecycleRetirements() {
|
|
413
|
+
const rows = this.getAll("select * from subscription_lifecycle_retirements order by retire_at asc, created_at asc");
|
|
414
|
+
return rows.map((row) => this.mapSubscriptionLifecycleRetirement(row));
|
|
415
|
+
}
|
|
416
|
+
listSubscriptionLifecycleRetirementsDue(cutoffIso) {
|
|
417
|
+
const rows = this.getAll("select * from subscription_lifecycle_retirements where retire_at <= ? order by retire_at asc, created_at asc", [cutoffIso]);
|
|
418
|
+
return rows.map((row) => this.mapSubscriptionLifecycleRetirement(row));
|
|
419
|
+
}
|
|
420
|
+
upsertSubscriptionLifecycleRetirement(retirement) {
|
|
421
|
+
const existing = this.getSubscriptionLifecycleRetirement(retirement.subscriptionId);
|
|
422
|
+
const next = existing
|
|
423
|
+
? earlierLifecycleRetirement(existing, retirement)
|
|
424
|
+
: retirement;
|
|
425
|
+
this.db.run(`
|
|
426
|
+
insert into subscription_lifecycle_retirements (
|
|
427
|
+
subscription_id, source_id, tracked_resource_ref, retire_at, terminal_state, terminal_result, terminal_occurred_at, created_at, updated_at
|
|
428
|
+
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
429
|
+
on conflict(subscription_id) do update set
|
|
430
|
+
source_id = excluded.source_id,
|
|
431
|
+
tracked_resource_ref = excluded.tracked_resource_ref,
|
|
432
|
+
retire_at = excluded.retire_at,
|
|
433
|
+
terminal_state = excluded.terminal_state,
|
|
434
|
+
terminal_result = excluded.terminal_result,
|
|
435
|
+
terminal_occurred_at = excluded.terminal_occurred_at,
|
|
436
|
+
created_at = excluded.created_at,
|
|
437
|
+
updated_at = excluded.updated_at
|
|
438
|
+
`, [
|
|
439
|
+
next.subscriptionId,
|
|
440
|
+
next.sourceId,
|
|
441
|
+
next.trackedResourceRef,
|
|
442
|
+
next.retireAt,
|
|
443
|
+
next.terminalState ?? null,
|
|
444
|
+
next.terminalResult ?? null,
|
|
445
|
+
next.terminalOccurredAt ?? null,
|
|
446
|
+
next.createdAt,
|
|
447
|
+
next.updatedAt,
|
|
448
|
+
]);
|
|
449
|
+
this.persist();
|
|
450
|
+
return this.getSubscriptionLifecycleRetirement(retirement.subscriptionId);
|
|
451
|
+
}
|
|
452
|
+
deleteSubscriptionLifecycleRetirement(subscriptionId) {
|
|
453
|
+
this.db.run("delete from subscription_lifecycle_retirements where subscription_id = ?", [subscriptionId]);
|
|
454
|
+
this.persist();
|
|
455
|
+
}
|
|
359
456
|
getActivationTarget(targetId) {
|
|
360
457
|
const row = this.getOne("select * from activation_targets where target_id = ?", [targetId]);
|
|
361
458
|
return row ? this.mapActivationTarget(row) : null;
|
|
@@ -507,6 +604,8 @@ class AgentInboxStore {
|
|
|
507
604
|
]);
|
|
508
605
|
this.db.run("delete from consumers where subscription_id = ?", [subscription.subscriptionId]);
|
|
509
606
|
}
|
|
607
|
+
this.db.run("delete from subscription_lifecycle_retirements where subscription_id in (select subscription_id from subscriptions where agent_id = ?)", [agentId]);
|
|
608
|
+
this.db.run("delete from source_idle_states where source_id in (select distinct source_id from subscriptions where agent_id = ?)", [agentId]);
|
|
510
609
|
this.db.run("delete from subscriptions where agent_id = ?", [agentId]);
|
|
511
610
|
this.db.run("delete from activation_dispatch_states where agent_id = ?", [agentId]);
|
|
512
611
|
this.db.run("delete from activation_targets where agent_id = ?", [agentId]);
|
|
@@ -895,6 +994,8 @@ class AgentInboxStore {
|
|
|
895
994
|
agents: this.count("agents"),
|
|
896
995
|
offlineAgents: this.count("agents where status = 'offline'"),
|
|
897
996
|
subscriptions: this.count("subscriptions"),
|
|
997
|
+
subscriptionLifecycleRetirements: this.count("subscription_lifecycle_retirements"),
|
|
998
|
+
sourceIdleStates: this.count("source_idle_states"),
|
|
898
999
|
inboxes: this.count("inboxes"),
|
|
899
1000
|
activationTargets: this.count("activation_targets"),
|
|
900
1001
|
offlineActivationTargets: this.count("activation_targets where status = 'offline'"),
|
|
@@ -959,6 +1060,15 @@ class AgentInboxStore {
|
|
|
959
1060
|
updatedAt: String(row.updated_at),
|
|
960
1061
|
};
|
|
961
1062
|
}
|
|
1063
|
+
mapSourceIdleState(row) {
|
|
1064
|
+
return {
|
|
1065
|
+
sourceId: String(row.source_id),
|
|
1066
|
+
idleSince: String(row.idle_since),
|
|
1067
|
+
autoPauseAt: String(row.auto_pause_at),
|
|
1068
|
+
autoPausedAt: row.auto_paused_at ? String(row.auto_paused_at) : null,
|
|
1069
|
+
updatedAt: String(row.updated_at),
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
962
1072
|
mapAgent(row) {
|
|
963
1073
|
return {
|
|
964
1074
|
agentId: String(row.agent_id),
|
|
@@ -984,14 +1094,27 @@ class AgentInboxStore {
|
|
|
984
1094
|
agentId: String(row.agent_id),
|
|
985
1095
|
sourceId: String(row.source_id),
|
|
986
1096
|
filter: parseJson(row.filter_json),
|
|
987
|
-
|
|
988
|
-
|
|
1097
|
+
trackedResourceRef: row.tracked_resource_ref ? String(row.tracked_resource_ref) : null,
|
|
1098
|
+
cleanupPolicy: parseJson(row.cleanup_policy_json),
|
|
989
1099
|
startPolicy: row.start_policy,
|
|
990
1100
|
startOffset: row.start_offset != null ? Number(row.start_offset) : null,
|
|
991
1101
|
startTime: row.start_time ? String(row.start_time) : null,
|
|
992
1102
|
createdAt: String(row.created_at),
|
|
993
1103
|
};
|
|
994
1104
|
}
|
|
1105
|
+
mapSubscriptionLifecycleRetirement(row) {
|
|
1106
|
+
return {
|
|
1107
|
+
subscriptionId: String(row.subscription_id),
|
|
1108
|
+
sourceId: String(row.source_id),
|
|
1109
|
+
trackedResourceRef: String(row.tracked_resource_ref),
|
|
1110
|
+
retireAt: String(row.retire_at),
|
|
1111
|
+
terminalState: row.terminal_state ? String(row.terminal_state) : null,
|
|
1112
|
+
terminalResult: row.terminal_result ? String(row.terminal_result) : null,
|
|
1113
|
+
terminalOccurredAt: row.terminal_occurred_at ? String(row.terminal_occurred_at) : null,
|
|
1114
|
+
createdAt: String(row.created_at),
|
|
1115
|
+
updatedAt: String(row.updated_at),
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
995
1118
|
mapActivationTarget(row) {
|
|
996
1119
|
if (String(row.kind) === "webhook") {
|
|
997
1120
|
const url = typeof row.url === "string" && row.url.trim().length > 0 ? row.url.trim() : null;
|