@holon-run/agentinbox 1.0.0-beta.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 +5 -1
- package/dist/src/cli.js +40 -4
- package/dist/src/http.js +13 -10
- package/dist/src/service.js +122 -45
- package/dist/src/sources/github.js +34 -12
- package/dist/src/sources/remote.js +28 -1
- package/dist/src/sources/remote_modules.js +4 -4
- package/dist/src/store.js +14 -4
- 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 +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ manually or relying on the agent runtime to expose its own notification API.
|
|
|
66
66
|
|
|
67
67
|
## v1.0.0 Beta Upgrade Notes
|
|
68
68
|
|
|
69
|
-
`v1.0.0-beta.0`
|
|
69
|
+
`v1.0.0-beta.0` was the first release on the final v1 storage and API boundary.
|
|
70
70
|
|
|
71
71
|
- pre-v1 local databases are archived and replaced with a fresh v1 database
|
|
72
72
|
- old local state is not imported into the new v1 database
|
|
@@ -171,6 +171,10 @@ agentinbox inbox read --agent-id <agent_id>
|
|
|
171
171
|
agentinbox inbox ack --agent-id <agent_id> --through <last_entry_id>
|
|
172
172
|
```
|
|
173
173
|
|
|
174
|
+
The HTTP `POST /subscriptions` endpoint always returns
|
|
175
|
+
`{ "subscriptions": [...] }`, even when only one subscription is created. That
|
|
176
|
+
keeps shortcut expansion consistent for external callers.
|
|
177
|
+
|
|
174
178
|
When acking a reviewed batch, prefer `--through <last_entry_id>` over `--all`.
|
|
175
179
|
That preserves the boundary of the items you actually inspected. Use
|
|
176
180
|
`ack --all` only when you have intentionally verified that every current
|
package/dist/src/cli.js
CHANGED
|
@@ -19,8 +19,11 @@ const store_1 = require("./store");
|
|
|
19
19
|
const terminal_1 = require("./terminal");
|
|
20
20
|
const util_1 = require("./util");
|
|
21
21
|
async function main() {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
22
|
+
const rawArgs = process.argv.slice(2);
|
|
23
|
+
const detectedArgs = normalizeHelpArgs(rawArgs);
|
|
24
|
+
const normalized = shouldTreatJsonFlagAsNoop(detectedArgs)
|
|
25
|
+
? stripNoopJsonFlag(detectedArgs)
|
|
26
|
+
: detectedArgs;
|
|
24
27
|
const command = normalized[0];
|
|
25
28
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
26
29
|
printHelp(normalized.slice(1));
|
|
@@ -445,7 +448,7 @@ async function main() {
|
|
|
445
448
|
startOffset: parseOptionalNumber(takeFlagValue(normalized, "--start-offset")),
|
|
446
449
|
startTime: takeFlagValue(normalized, "--start-time") ?? undefined,
|
|
447
450
|
});
|
|
448
|
-
console.log((0, util_1.jsonResponse)(withCommandMetadata(response.data, selection)));
|
|
451
|
+
console.log((0, util_1.jsonResponse)(withCommandMetadata(normalizeSubscriptionAddOutput(response.data, Boolean(shortcutName)), selection)));
|
|
449
452
|
return;
|
|
450
453
|
}
|
|
451
454
|
if (command === "subscription" && normalized[1] === "list") {
|
|
@@ -767,7 +770,7 @@ async function main() {
|
|
|
767
770
|
printHelp([command]);
|
|
768
771
|
return;
|
|
769
772
|
}
|
|
770
|
-
throw new Error(`unknown command: ${
|
|
773
|
+
throw new Error(`unknown command: ${normalized.join(" ")}`);
|
|
771
774
|
}
|
|
772
775
|
async function runServe(args) {
|
|
773
776
|
const port = parseOptionalNumber(takeFlagValue(args, "--port"));
|
|
@@ -1000,6 +1003,26 @@ function normalizeHelpArgs(args) {
|
|
|
1000
1003
|
}
|
|
1001
1004
|
return [args[1], "--help"];
|
|
1002
1005
|
}
|
|
1006
|
+
function stripNoopJsonFlag(args) {
|
|
1007
|
+
return args.filter((arg) => arg !== "--json");
|
|
1008
|
+
}
|
|
1009
|
+
function shouldTreatJsonFlagAsNoop(args) {
|
|
1010
|
+
if (!args.includes("--json")) {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
const command = args[0];
|
|
1014
|
+
if (!command) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
1020
|
+
return command !== "help" &&
|
|
1021
|
+
command !== "version" &&
|
|
1022
|
+
command !== "--version" &&
|
|
1023
|
+
command !== "-v" &&
|
|
1024
|
+
command !== "serve";
|
|
1025
|
+
}
|
|
1003
1026
|
function hasHelpFlag(args) {
|
|
1004
1027
|
return args.includes("--help") || args.includes("-h");
|
|
1005
1028
|
}
|
|
@@ -1047,6 +1070,19 @@ function withCommandMetadata(data, selection) {
|
|
|
1047
1070
|
...(selection.warnings.length > 0 ? { warnings: selection.warnings } : {}),
|
|
1048
1071
|
};
|
|
1049
1072
|
}
|
|
1073
|
+
function normalizeSubscriptionAddOutput(data, shortcutUsed) {
|
|
1074
|
+
if (shortcutUsed) {
|
|
1075
|
+
return data;
|
|
1076
|
+
}
|
|
1077
|
+
const subscriptions = data.subscriptions;
|
|
1078
|
+
if (Array.isArray(subscriptions) &&
|
|
1079
|
+
subscriptions.length === 1 &&
|
|
1080
|
+
subscriptions[0] &&
|
|
1081
|
+
typeof subscriptions[0] === "object") {
|
|
1082
|
+
return subscriptions[0];
|
|
1083
|
+
}
|
|
1084
|
+
return data;
|
|
1085
|
+
}
|
|
1050
1086
|
function getRequiredTerminalContext() {
|
|
1051
1087
|
try {
|
|
1052
1088
|
return (0, terminal_1.detectTerminalContext)(process.env);
|
package/dist/src/http.js
CHANGED
|
@@ -29,6 +29,14 @@ const errorResponseSchema = {
|
|
|
29
29
|
error: { type: "string" },
|
|
30
30
|
},
|
|
31
31
|
};
|
|
32
|
+
const subscriptionCollectionSchema = {
|
|
33
|
+
type: "object",
|
|
34
|
+
required: ["subscriptions"],
|
|
35
|
+
additionalProperties: false,
|
|
36
|
+
properties: {
|
|
37
|
+
subscriptions: { type: "array", items: jsonObjectSchema },
|
|
38
|
+
},
|
|
39
|
+
};
|
|
32
40
|
const deliveryHandleSchema = {
|
|
33
41
|
type: "object",
|
|
34
42
|
additionalProperties: false,
|
|
@@ -1041,13 +1049,7 @@ function buildFastifyServer(service) {
|
|
|
1041
1049
|
},
|
|
1042
1050
|
},
|
|
1043
1051
|
response: {
|
|
1044
|
-
200:
|
|
1045
|
-
type: "object",
|
|
1046
|
-
required: ["subscriptions"],
|
|
1047
|
-
properties: {
|
|
1048
|
-
subscriptions: { type: "array", items: jsonObjectSchema },
|
|
1049
|
-
},
|
|
1050
|
-
},
|
|
1052
|
+
200: subscriptionCollectionSchema,
|
|
1051
1053
|
},
|
|
1052
1054
|
},
|
|
1053
1055
|
}, async (request) => {
|
|
@@ -1087,13 +1089,13 @@ function buildFastifyServer(service) {
|
|
|
1087
1089
|
},
|
|
1088
1090
|
},
|
|
1089
1091
|
response: {
|
|
1090
|
-
200:
|
|
1092
|
+
200: subscriptionCollectionSchema,
|
|
1091
1093
|
400: errorResponseSchema,
|
|
1092
1094
|
},
|
|
1093
1095
|
},
|
|
1094
1096
|
}, async (request) => {
|
|
1095
1097
|
const body = request.body;
|
|
1096
|
-
|
|
1098
|
+
const input = {
|
|
1097
1099
|
agentId: String(body.agentId),
|
|
1098
1100
|
sourceId: String(body.sourceId),
|
|
1099
1101
|
shortcut: body.shortcut && typeof body.shortcut === "object"
|
|
@@ -1110,7 +1112,8 @@ function buildFastifyServer(service) {
|
|
|
1110
1112
|
startPolicy: optionalString(body.startPolicy),
|
|
1111
1113
|
startOffset: typeof body.startOffset === "number" ? body.startOffset : undefined,
|
|
1112
1114
|
startTime: optionalString(body.startTime) ?? undefined,
|
|
1113
|
-
}
|
|
1115
|
+
};
|
|
1116
|
+
return { subscriptions: await service.registerSubscriptions(input) };
|
|
1114
1117
|
});
|
|
1115
1118
|
app.get("/subscriptions/:subscriptionId", {
|
|
1116
1119
|
schema: {
|
package/dist/src/service.js
CHANGED
|
@@ -707,26 +707,93 @@ class AgentInboxService {
|
|
|
707
707
|
return result;
|
|
708
708
|
}
|
|
709
709
|
async registerSubscription(input) {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
710
|
+
const expanded = await this.expandShortcutRegistration(input);
|
|
711
|
+
if (!expanded) {
|
|
712
|
+
return this.registerSingleSubscription(input);
|
|
713
|
+
}
|
|
714
|
+
if (expanded.plan.members.length !== 1) {
|
|
715
|
+
throw new Error(`subscription shortcut ${input.shortcut?.name ?? "unknown"} for source ${input.sourceId} expands to ${expanded.plan.members.length} subscriptions; use registerSubscriptions() instead`);
|
|
716
|
+
}
|
|
717
|
+
const subscriptions = await this.registerExpandedShortcutSubscriptions(input, expanded.source, expanded.plan);
|
|
718
|
+
return subscriptions[0];
|
|
719
|
+
}
|
|
720
|
+
async registerSubscriptions(input) {
|
|
721
|
+
const expanded = await this.expandShortcutRegistration(input);
|
|
722
|
+
if (expanded) {
|
|
723
|
+
return this.registerExpandedShortcutSubscriptions(input, expanded.source, expanded.plan);
|
|
724
|
+
}
|
|
725
|
+
return [await this.registerSingleSubscription(input)];
|
|
726
|
+
}
|
|
727
|
+
async expandShortcutRegistration(input) {
|
|
728
|
+
if (!input.shortcut) {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
const source = this.store.getSource(input.sourceId);
|
|
732
|
+
if (!source) {
|
|
733
|
+
throw new Error(`unknown source: ${input.sourceId}`);
|
|
734
|
+
}
|
|
735
|
+
if (hasSubscriptionFieldOverride(input)) {
|
|
736
|
+
throw new Error("subscription add shortcut does not allow filter, trackedResourceRef, or cleanupPolicy overrides");
|
|
737
|
+
}
|
|
738
|
+
const plan = await this.adapters.expandSubscriptionShortcut(source, input.shortcut);
|
|
739
|
+
if (!plan) {
|
|
740
|
+
throw new Error(`unknown subscription shortcut ${input.shortcut.name} for source ${input.sourceId}`);
|
|
741
|
+
}
|
|
742
|
+
return { source, plan };
|
|
743
|
+
}
|
|
744
|
+
async registerExpandedShortcutSubscriptions(input, source, plan) {
|
|
745
|
+
if (plan.members.length === 0) {
|
|
746
|
+
throw new Error(`subscription shortcut ${input.shortcut?.name ?? "unknown"} did not produce any subscriptions`);
|
|
747
|
+
}
|
|
748
|
+
const resolvedMembers = plan.members.map((member) => ({
|
|
749
|
+
source: this.resolveShortcutMemberSource(source, member),
|
|
750
|
+
member,
|
|
751
|
+
}));
|
|
752
|
+
const created = [];
|
|
753
|
+
try {
|
|
754
|
+
for (const { source: memberSource, member } of resolvedMembers) {
|
|
755
|
+
created.push(await this.registerSingleSubscription({
|
|
756
|
+
...input,
|
|
757
|
+
sourceId: memberSource.sourceId,
|
|
758
|
+
shortcut: undefined,
|
|
759
|
+
filter: member.filter,
|
|
760
|
+
trackedResourceRef: member.trackedResourceRef ?? null,
|
|
761
|
+
cleanupPolicy: member.cleanupPolicy ?? null,
|
|
762
|
+
}));
|
|
714
763
|
}
|
|
715
|
-
|
|
716
|
-
|
|
764
|
+
}
|
|
765
|
+
catch (error) {
|
|
766
|
+
await this.rollbackRegisteredSubscriptions(created);
|
|
767
|
+
throw error;
|
|
768
|
+
}
|
|
769
|
+
return created;
|
|
770
|
+
}
|
|
771
|
+
async rollbackRegisteredSubscriptions(subscriptions) {
|
|
772
|
+
for (const subscription of [...subscriptions].reverse()) {
|
|
773
|
+
try {
|
|
774
|
+
await this.removeSubscription(subscription.subscriptionId);
|
|
717
775
|
}
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
throw new Error(`unknown subscription shortcut ${input.shortcut.name} for source ${input.sourceId}`);
|
|
776
|
+
catch (error) {
|
|
777
|
+
console.warn(`failed to roll back subscription ${subscription.subscriptionId}:`, error);
|
|
721
778
|
}
|
|
722
|
-
return this.registerSubscription({
|
|
723
|
-
...input,
|
|
724
|
-
shortcut: undefined,
|
|
725
|
-
filter: expanded.filter,
|
|
726
|
-
trackedResourceRef: expanded.trackedResourceRef ?? null,
|
|
727
|
-
cleanupPolicy: expanded.cleanupPolicy,
|
|
728
|
-
});
|
|
729
779
|
}
|
|
780
|
+
}
|
|
781
|
+
resolveShortcutMemberSource(source, member) {
|
|
782
|
+
if (!member.streamKind || member.streamKind === source.streamKind) {
|
|
783
|
+
return source;
|
|
784
|
+
}
|
|
785
|
+
if (!source.hostId || !source.streamKey) {
|
|
786
|
+
throw new Error(`subscription shortcut ${source.sourceId} requires a host-scoped source to resolve ${member.streamKind}`);
|
|
787
|
+
}
|
|
788
|
+
const sibling = this.store.listSources().find((candidate) => candidate.hostId === source.hostId &&
|
|
789
|
+
candidate.streamKind === member.streamKind &&
|
|
790
|
+
candidate.streamKey === source.streamKey);
|
|
791
|
+
if (!sibling) {
|
|
792
|
+
throw new Error(`subscription shortcut ${source.sourceId} requires an existing sibling ${member.streamKind} stream under host ${source.hostId} with streamKey ${source.streamKey}`);
|
|
793
|
+
}
|
|
794
|
+
return sibling;
|
|
795
|
+
}
|
|
796
|
+
async registerSingleSubscription(input) {
|
|
730
797
|
this.getAgent(input.agentId);
|
|
731
798
|
const source = this.store.getSource(input.sourceId);
|
|
732
799
|
if (!source) {
|
|
@@ -755,17 +822,25 @@ class AgentInboxService {
|
|
|
755
822
|
};
|
|
756
823
|
this.ensureInboxForAgent(subscription.agentId);
|
|
757
824
|
this.store.insertSubscription(subscription);
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
825
|
+
try {
|
|
826
|
+
const stream = await this.ensureStreamForSource(source);
|
|
827
|
+
await this.backend.ensureConsumer({
|
|
828
|
+
streamId: stream.streamId,
|
|
829
|
+
subscriptionId: subscription.subscriptionId,
|
|
830
|
+
consumerKey: `subscription:${subscription.subscriptionId}`,
|
|
831
|
+
startPolicy: subscription.startPolicy,
|
|
832
|
+
startOffset: subscription.startOffset ?? null,
|
|
833
|
+
startTime: subscription.startTime ?? null,
|
|
834
|
+
});
|
|
835
|
+
this.store.deleteSourceIdleState(source.sourceId);
|
|
836
|
+
return subscription;
|
|
837
|
+
}
|
|
838
|
+
catch (error) {
|
|
839
|
+
this.store.deleteSubscription(subscription.subscriptionId);
|
|
840
|
+
this.clearSubscriptionRuntimeState(subscription);
|
|
841
|
+
this.refreshSourceIdleState(source.sourceId);
|
|
842
|
+
throw error;
|
|
843
|
+
}
|
|
769
844
|
}
|
|
770
845
|
async removeSubscription(subscriptionId) {
|
|
771
846
|
const subscription = this.getSubscription(subscriptionId);
|
|
@@ -1322,7 +1397,7 @@ class AgentInboxService {
|
|
|
1322
1397
|
});
|
|
1323
1398
|
}
|
|
1324
1399
|
for (const signal of lifecycleSignals.values()) {
|
|
1325
|
-
this.scheduleLifecycleRetirements(source,
|
|
1400
|
+
this.scheduleLifecycleRetirements(source, signal);
|
|
1326
1401
|
}
|
|
1327
1402
|
return {
|
|
1328
1403
|
subscriptionId: subscription.subscriptionId,
|
|
@@ -1483,31 +1558,33 @@ class AgentInboxService {
|
|
|
1483
1558
|
signals.set(normalized.ref, normalized);
|
|
1484
1559
|
}
|
|
1485
1560
|
}
|
|
1486
|
-
scheduleLifecycleRetirements(source,
|
|
1561
|
+
scheduleLifecycleRetirements(source, signal) {
|
|
1487
1562
|
if (!signal.terminal) {
|
|
1488
1563
|
return;
|
|
1489
1564
|
}
|
|
1490
|
-
if (!
|
|
1565
|
+
if (!source.hostId) {
|
|
1491
1566
|
return;
|
|
1492
1567
|
}
|
|
1493
1568
|
const signalOccurredAt = signal.occurredAt ?? (0, util_1.nowIso)();
|
|
1494
1569
|
const signalOccurredAtMs = Date.parse(signalOccurredAt);
|
|
1495
|
-
const
|
|
1496
|
-
|
|
1497
|
-
|
|
1570
|
+
for (const subscription of this.store.listSubscriptionsForHostTrackedResourceRef(source.hostId, signal.ref)) {
|
|
1571
|
+
const retireAt = lifecycleRetireAtForSignal(subscription.cleanupPolicy, signalOccurredAtMs);
|
|
1572
|
+
if (!retireAt) {
|
|
1573
|
+
continue;
|
|
1574
|
+
}
|
|
1575
|
+
const now = (0, util_1.nowIso)();
|
|
1576
|
+
this.store.upsertSubscriptionLifecycleRetirement({
|
|
1577
|
+
subscriptionId: subscription.subscriptionId,
|
|
1578
|
+
hostId: source.hostId,
|
|
1579
|
+
trackedResourceRef: signal.ref,
|
|
1580
|
+
retireAt,
|
|
1581
|
+
terminalState: signal.state ?? null,
|
|
1582
|
+
terminalResult: signal.result ?? null,
|
|
1583
|
+
terminalOccurredAt: signalOccurredAt,
|
|
1584
|
+
createdAt: now,
|
|
1585
|
+
updatedAt: now,
|
|
1586
|
+
});
|
|
1498
1587
|
}
|
|
1499
|
-
const now = (0, util_1.nowIso)();
|
|
1500
|
-
this.store.upsertSubscriptionLifecycleRetirement({
|
|
1501
|
-
subscriptionId: subscription.subscriptionId,
|
|
1502
|
-
sourceId: source.sourceId,
|
|
1503
|
-
trackedResourceRef: signal.ref,
|
|
1504
|
-
retireAt,
|
|
1505
|
-
terminalState: signal.state ?? null,
|
|
1506
|
-
terminalResult: signal.result ?? null,
|
|
1507
|
-
terminalOccurredAt: signalOccurredAt,
|
|
1508
|
-
createdAt: now,
|
|
1509
|
-
updatedAt: now,
|
|
1510
|
-
});
|
|
1511
1588
|
}
|
|
1512
1589
|
notifyInboxWatchers(agentId, items) {
|
|
1513
1590
|
const watchers = this.inboxWatchers.get(agentId);
|
|
@@ -4,6 +4,7 @@ exports.GithubDeliveryAdapter = exports.GithubUxcClient = exports.DEFAULT_GITHUB
|
|
|
4
4
|
exports.githubDeliveryOperationsForHandle = githubDeliveryOperationsForHandle;
|
|
5
5
|
exports.invokeGithubDeliveryOperation = invokeGithubDeliveryOperation;
|
|
6
6
|
exports.normalizeGithubRepoEvent = normalizeGithubRepoEvent;
|
|
7
|
+
exports.canonicalGithubPullRequestRef = canonicalGithubPullRequestRef;
|
|
7
8
|
exports.deriveGithubTrackedResource = deriveGithubTrackedResource;
|
|
8
9
|
exports.githubSubscriptionShortcutSpec = githubSubscriptionShortcutSpec;
|
|
9
10
|
exports.expandGithubSubscriptionShortcut = expandGithubSubscriptionShortcut;
|
|
@@ -282,7 +283,11 @@ function normalizeGithubRepoEvent(source, config, raw) {
|
|
|
282
283
|
deliveryHandle,
|
|
283
284
|
};
|
|
284
285
|
}
|
|
285
|
-
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) {
|
|
286
291
|
const metadata = asRecord(filter.metadata);
|
|
287
292
|
const payload = asRecord(filter.payload);
|
|
288
293
|
const number = asNumber(metadata.number) ?? asNumber(payload.number) ?? asNumber(payload.ref);
|
|
@@ -290,13 +295,16 @@ function deriveGithubTrackedResource(filter) {
|
|
|
290
295
|
if (!number || isPullRequest !== true) {
|
|
291
296
|
return null;
|
|
292
297
|
}
|
|
293
|
-
return { ref:
|
|
298
|
+
return { ref: canonicalGithubPullRequestRef(source, number) };
|
|
294
299
|
}
|
|
295
300
|
function githubSubscriptionShortcutSpec() {
|
|
296
301
|
return [{
|
|
297
302
|
name: "pr",
|
|
298
303
|
description: "Follow one pull request and auto-retire when it closes.",
|
|
299
|
-
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
|
+
],
|
|
300
308
|
}];
|
|
301
309
|
}
|
|
302
310
|
function expandGithubSubscriptionShortcut(input) {
|
|
@@ -307,18 +315,32 @@ function expandGithubSubscriptionShortcut(input) {
|
|
|
307
315
|
if (!number || !Number.isInteger(number) || number <= 0) {
|
|
308
316
|
throw new Error("subscription shortcut pr requires a positive integer number");
|
|
309
317
|
}
|
|
318
|
+
const trackedResourceRef = canonicalGithubPullRequestRef(input.source, number);
|
|
319
|
+
const withCi = input.args?.withCi === true;
|
|
310
320
|
return {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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" },
|
|
315
332
|
},
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
333
|
+
...(withCi
|
|
334
|
+
? [{
|
|
335
|
+
streamKind: "ci_runs",
|
|
336
|
+
trackedResourceRef,
|
|
337
|
+
cleanupPolicy: { mode: "on_terminal" },
|
|
338
|
+
}]
|
|
339
|
+
: []),
|
|
340
|
+
],
|
|
319
341
|
};
|
|
320
342
|
}
|
|
321
|
-
function projectGithubLifecycleSignal(rawPayload) {
|
|
343
|
+
function projectGithubLifecycleSignal(rawPayload, source) {
|
|
322
344
|
const eventType = asString(rawPayload.type);
|
|
323
345
|
if (eventType !== "PullRequestEvent") {
|
|
324
346
|
return null;
|
|
@@ -334,7 +356,7 @@ function projectGithubLifecycleSignal(rawPayload) {
|
|
|
334
356
|
}
|
|
335
357
|
const merged = action === "merged" || asBoolean(rawPayload.merged) === true || asBoolean(pullRequest.merged) === true;
|
|
336
358
|
return {
|
|
337
|
-
ref:
|
|
359
|
+
ref: canonicalGithubPullRequestRef(source, number),
|
|
338
360
|
terminal: true,
|
|
339
361
|
state: "closed",
|
|
340
362
|
result: merged ? "merged" : "closed",
|
|
@@ -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)) {
|
|
@@ -368,6 +369,32 @@ class RemoteSourceRuntime {
|
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
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
|
+
}
|
|
371
398
|
function moduleInputSource(source) {
|
|
372
399
|
if (source.sourceType !== "remote_source") {
|
|
373
400
|
return source;
|
|
@@ -200,8 +200,8 @@ const GITHUB_REPO_MODULE = {
|
|
|
200
200
|
expandSubscriptionShortcut(input) {
|
|
201
201
|
return (0, github_1.expandGithubSubscriptionShortcut)(input);
|
|
202
202
|
},
|
|
203
|
-
deriveTrackedResource(filter) {
|
|
204
|
-
return (0, github_1.deriveGithubTrackedResource)(filter);
|
|
203
|
+
deriveTrackedResource(filter, source) {
|
|
204
|
+
return (0, github_1.deriveGithubTrackedResource)(filter, source);
|
|
205
205
|
},
|
|
206
206
|
deriveNotificationGrouping(item) {
|
|
207
207
|
const number = typeof item.metadata.number === "number" ? item.metadata.number : null;
|
|
@@ -251,8 +251,8 @@ const GITHUB_REPO_MODULE = {
|
|
|
251
251
|
const count = items.length;
|
|
252
252
|
return `${count} ${grouping.summaryHint}`;
|
|
253
253
|
},
|
|
254
|
-
projectLifecycleSignal(rawPayload) {
|
|
255
|
-
return (0, github_1.projectGithubLifecycleSignal)(rawPayload);
|
|
254
|
+
projectLifecycleSignal(rawPayload, source) {
|
|
255
|
+
return (0, github_1.projectGithubLifecycleSignal)(rawPayload, source);
|
|
256
256
|
},
|
|
257
257
|
validateConfig(source) {
|
|
258
258
|
(0, github_1.parseGithubSourceConfig)(source);
|
package/dist/src/store.js
CHANGED
|
@@ -595,6 +595,16 @@ class AgentInboxStore {
|
|
|
595
595
|
const rows = this.getAll("select * from subscriptions where agent_id = ? order by created_at asc", [agentId]);
|
|
596
596
|
return rows.map((row) => this.mapSubscription(row));
|
|
597
597
|
}
|
|
598
|
+
listSubscriptionsForHostTrackedResourceRef(hostId, trackedResourceRef) {
|
|
599
|
+
const rows = this.getAll(`
|
|
600
|
+
select subscriptions.*
|
|
601
|
+
from subscriptions
|
|
602
|
+
inner join sources on sources.source_id = subscriptions.source_id
|
|
603
|
+
where sources.host_id = ? and subscriptions.tracked_resource_ref = ?
|
|
604
|
+
order by subscriptions.created_at asc
|
|
605
|
+
`, [hostId, trackedResourceRef]);
|
|
606
|
+
return rows.map((row) => this.mapSubscription(row));
|
|
607
|
+
}
|
|
598
608
|
deleteSubscription(subscriptionId) {
|
|
599
609
|
const subscription = this.getSubscription(subscriptionId);
|
|
600
610
|
if (!subscription) {
|
|
@@ -628,10 +638,10 @@ class AgentInboxStore {
|
|
|
628
638
|
: retirement;
|
|
629
639
|
this.db.run(`
|
|
630
640
|
insert into subscription_lifecycle_retirements (
|
|
631
|
-
subscription_id,
|
|
641
|
+
subscription_id, host_id, tracked_resource_ref, retire_at, terminal_state, terminal_result, terminal_occurred_at, created_at, updated_at
|
|
632
642
|
) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
633
643
|
on conflict(subscription_id) do update set
|
|
634
|
-
|
|
644
|
+
host_id = excluded.host_id,
|
|
635
645
|
tracked_resource_ref = excluded.tracked_resource_ref,
|
|
636
646
|
retire_at = excluded.retire_at,
|
|
637
647
|
terminal_state = excluded.terminal_state,
|
|
@@ -641,7 +651,7 @@ class AgentInboxStore {
|
|
|
641
651
|
updated_at = excluded.updated_at
|
|
642
652
|
`, [
|
|
643
653
|
next.subscriptionId,
|
|
644
|
-
next.
|
|
654
|
+
next.hostId,
|
|
645
655
|
next.trackedResourceRef,
|
|
646
656
|
next.retireAt,
|
|
647
657
|
next.terminalState ?? null,
|
|
@@ -1673,7 +1683,7 @@ class AgentInboxStore {
|
|
|
1673
1683
|
mapSubscriptionLifecycleRetirement(row) {
|
|
1674
1684
|
return {
|
|
1675
1685
|
subscriptionId: String(row.subscription_id),
|
|
1676
|
-
|
|
1686
|
+
hostId: String(row.host_id),
|
|
1677
1687
|
trackedResourceRef: String(row.tracked_resource_ref),
|
|
1678
1688
|
retireAt: String(row.retire_at),
|
|
1679
1689
|
terminalState: row.terminal_state ? String(row.terminal_state) : null,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
pragma foreign_keys=off;
|
|
2
|
+
|
|
3
|
+
create table if not exists subscription_lifecycle_retirements__new (
|
|
4
|
+
subscription_id text primary key,
|
|
5
|
+
host_id text not null,
|
|
6
|
+
tracked_resource_ref text not null,
|
|
7
|
+
retire_at text not null,
|
|
8
|
+
terminal_state text,
|
|
9
|
+
terminal_result text,
|
|
10
|
+
terminal_occurred_at text not null,
|
|
11
|
+
created_at text not null,
|
|
12
|
+
updated_at text not null
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
insert into subscription_lifecycle_retirements__new (
|
|
16
|
+
subscription_id,
|
|
17
|
+
host_id,
|
|
18
|
+
tracked_resource_ref,
|
|
19
|
+
retire_at,
|
|
20
|
+
terminal_state,
|
|
21
|
+
terminal_result,
|
|
22
|
+
terminal_occurred_at,
|
|
23
|
+
created_at,
|
|
24
|
+
updated_at
|
|
25
|
+
)
|
|
26
|
+
select
|
|
27
|
+
retirement.subscription_id,
|
|
28
|
+
coalesce(source.host_id, retirement.source_id),
|
|
29
|
+
retirement.tracked_resource_ref,
|
|
30
|
+
retirement.retire_at,
|
|
31
|
+
retirement.terminal_state,
|
|
32
|
+
retirement.terminal_result,
|
|
33
|
+
retirement.terminal_occurred_at,
|
|
34
|
+
retirement.created_at,
|
|
35
|
+
retirement.updated_at
|
|
36
|
+
from subscription_lifecycle_retirements as retirement
|
|
37
|
+
left join sources as source on source.source_id = retirement.source_id;
|
|
38
|
+
|
|
39
|
+
drop table subscription_lifecycle_retirements;
|
|
40
|
+
alter table subscription_lifecycle_retirements__new rename to subscription_lifecycle_retirements;
|
|
41
|
+
|
|
42
|
+
pragma foreign_keys=on;
|
package/drizzle/schema.ts
CHANGED
|
@@ -97,11 +97,14 @@ export const subscriptions = sqliteTable("subscriptions", {
|
|
|
97
97
|
startOffset: integer("start_offset"),
|
|
98
98
|
startTime: text("start_time"),
|
|
99
99
|
createdAt: text("created_at").notNull(),
|
|
100
|
-
})
|
|
100
|
+
}, (table) => ({
|
|
101
|
+
trackedResourceRefIdx: index("idx_subscriptions_tracked_resource_ref").on(table.trackedResourceRef),
|
|
102
|
+
trackedResourceRefSourceIdx: index("idx_subscriptions_tracked_resource_source").on(table.trackedResourceRef, table.sourceId),
|
|
103
|
+
}));
|
|
101
104
|
|
|
102
105
|
export const subscriptionLifecycleRetirements = sqliteTable("subscription_lifecycle_retirements", {
|
|
103
106
|
subscriptionId: text("subscription_id").primaryKey(),
|
|
104
|
-
|
|
107
|
+
hostId: text("host_id").notNull(),
|
|
105
108
|
trackedResourceRef: text("tracked_resource_ref").notNull(),
|
|
106
109
|
retireAt: text("retire_at").notNull(),
|
|
107
110
|
terminalState: text("terminal_state"),
|