@hyperdrive.bot/fleet-server 0.3.148 → 0.3.150
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/dist/server/server/agent/tools/paseo-tools.d.ts +9 -0
- package/dist/server/server/agent/tools/paseo-tools.js +76 -1
- package/dist/server/server/agent/tools/read-only-surface.d.ts +7 -0
- package/dist/server/server/agent/tools/read-only-surface.js +8 -0
- package/dist/server/server/bootstrap.js +71 -1
- package/dist/server/server/config.js +4 -0
- package/dist/server/server/exports.d.ts +2 -0
- package/dist/server/server/exports.js +6 -0
- package/dist/server/server/ingestion/adapters.js +2 -0
- package/dist/server/server/ingestion/errors.d.ts +5 -1
- package/dist/server/server/ingestion/errors.js +8 -0
- package/dist/server/server/ingestion/health.js +10 -0
- package/dist/server/server/ingestion/self-hosted/gateway.d.ts +55 -0
- package/dist/server/server/ingestion/self-hosted/gateway.js +295 -0
- package/dist/server/server/ingestion/subscriptions/notification-prompt.d.ts +83 -0
- package/dist/server/server/ingestion/subscriptions/notification-prompt.js +96 -0
- package/dist/server/server/ingestion/subscriptions/notifier.d.ts +96 -0
- package/dist/server/server/ingestion/subscriptions/notifier.js +209 -0
- package/dist/server/server/ingestion/subscriptions/pending-store.d.ts +111 -0
- package/dist/server/server/ingestion/subscriptions/pending-store.js +254 -0
- package/dist/server/server/ingestion/subscriptions/poller.d.ts +73 -0
- package/dist/server/server/ingestion/subscriptions/poller.js +168 -0
- package/dist/server/server/ingestion/subscriptions/reader.d.ts +39 -0
- package/dist/server/server/ingestion/subscriptions/reader.js +30 -0
- package/dist/server/server/ingestion/subscriptions/store.d.ts +35 -0
- package/dist/server/server/ingestion/subscriptions/store.js +82 -0
- package/dist/server/server/ingestion/types.d.ts +14 -1
- package/dist/server/server/ingestion/types.js +1 -1
- package/dist/server/web-ui/_expo/static/js/web/{index-88d5f130a09403b86ae8c8c5fd1f97d4.js → index-4695e64c38d7ce76a8375d72b95c2c0c.js} +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-4695e64c38d7ce76a8375d72b95c2c0c.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-4695e64c38d7ce76a8375d72b95c2c0c.js.gz +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-88d5f130a09403b86ae8c8c5fd1f97d4.js.map.br → index-4695e64c38d7ce76a8375d72b95c2c0c.js.map.br} +0 -0
- package/dist/server/web-ui/_expo/static/js/web/{index-88d5f130a09403b86ae8c8c5fd1f97d4.js.map.gz → index-4695e64c38d7ce76a8375d72b95c2c0c.js.map.gz} +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-88d5f130a09403b86ae8c8c5fd1f97d4.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-88d5f130a09403b86ae8c8c5fd1f97d4.js.gz +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { scanMatchedSourceItems, windowStartFor } from "../backfill.js";
|
|
2
|
+
/** How often the poller reads its sources. */
|
|
3
|
+
export const DEFAULT_POLL_INTERVAL_MS = 300000;
|
|
4
|
+
/**
|
|
5
|
+
* How far back each poll reads.
|
|
6
|
+
*
|
|
7
|
+
* Wider than the interval on purpose: a daemon that was asleep, or a source
|
|
8
|
+
* whose aggregator buffer filled late, would otherwise leave a hole no later
|
|
9
|
+
* poll ever covers. Re-reading an item is free, because `PendingItemStore.append`
|
|
10
|
+
* ignores one whose `contentHash` is unchanged.
|
|
11
|
+
*/
|
|
12
|
+
export const POLL_WINDOW_DAYS = 1;
|
|
13
|
+
/**
|
|
14
|
+
* Reads each subscribed source ONCE per tick and fans the result out to every
|
|
15
|
+
* filter, and through them to every subscriber.
|
|
16
|
+
*
|
|
17
|
+
* The shape exists because of a measured cost: today two filters armed on one
|
|
18
|
+
* source cause two independent `listItems` walks, with no cache and no shared
|
|
19
|
+
* scan. With N subscribers that is N reads of the same aggregator buffer per
|
|
20
|
+
* window. Matching is pure string comparison over a dotted path, so fanning out
|
|
21
|
+
* in memory costs CPU while reading costs network, rate limit and latency.
|
|
22
|
+
* One read, many matches.
|
|
23
|
+
*
|
|
24
|
+
* It owns its own timer and touches the schedule engine not at all. A
|
|
25
|
+
* subscription is not a schedule: it must survive its target being archived,
|
|
26
|
+
* and `sweepOrphanedSchedules` would complete it terminally at the next boot.
|
|
27
|
+
*/
|
|
28
|
+
export class SubscriptionPoller {
|
|
29
|
+
constructor(options) {
|
|
30
|
+
this.timer = null;
|
|
31
|
+
/** Single-flight. A slow aggregator must not let two ticks overlap. */
|
|
32
|
+
this.running = false;
|
|
33
|
+
this.options = options;
|
|
34
|
+
this.intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
35
|
+
this.now = options.now ?? (() => Date.now());
|
|
36
|
+
}
|
|
37
|
+
start() {
|
|
38
|
+
if (this.timer)
|
|
39
|
+
return;
|
|
40
|
+
this.timer = setInterval(() => {
|
|
41
|
+
void this.tick().catch((error) => {
|
|
42
|
+
this.options.logger.warn({ err: error }, "Subscription poll tick failed");
|
|
43
|
+
});
|
|
44
|
+
}, this.intervalMs);
|
|
45
|
+
// Never keep the process alive for a poll.
|
|
46
|
+
this.timer.unref?.();
|
|
47
|
+
this.options.logger.info({ intervalMs: this.intervalMs }, "Subscription poller started");
|
|
48
|
+
}
|
|
49
|
+
stop() {
|
|
50
|
+
if (!this.timer)
|
|
51
|
+
return;
|
|
52
|
+
clearInterval(this.timer);
|
|
53
|
+
this.timer = null;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* One pass: read every subscribed source once, match, notify, then drain.
|
|
57
|
+
*
|
|
58
|
+
* The drain at the end is what makes a notice held back by `busy` or
|
|
59
|
+
* `debounced` eventually go out even when the source has gone quiet.
|
|
60
|
+
*/
|
|
61
|
+
async tick() {
|
|
62
|
+
if (this.running) {
|
|
63
|
+
return { sourcesRead: 0, filtersMatched: 0, notified: 0 };
|
|
64
|
+
}
|
|
65
|
+
this.running = true;
|
|
66
|
+
try {
|
|
67
|
+
return await this.runTick();
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
this.running = false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async runTick() {
|
|
74
|
+
const { subscriptions, filterResolver, sourceStore, gatewayResolver, logger } = this.options;
|
|
75
|
+
const armed = (await subscriptions.list()).filter((s) => s.status === "armed");
|
|
76
|
+
const summary = { sourcesRead: 0, filtersMatched: 0, notified: 0 };
|
|
77
|
+
if (armed.length === 0) {
|
|
78
|
+
return summary;
|
|
79
|
+
}
|
|
80
|
+
// Resolve the distinct filters once. Several subscribers commonly share one.
|
|
81
|
+
const filters = new Map();
|
|
82
|
+
for (const subscription of armed) {
|
|
83
|
+
if (filters.has(subscription.filterId))
|
|
84
|
+
continue;
|
|
85
|
+
const filter = await filterResolver(subscription.filterId);
|
|
86
|
+
if (!filter) {
|
|
87
|
+
logger.warn({ subscriptionId: subscription.id, filterId: subscription.filterId }, "Subscription points at a filter that no longer exists");
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (filter.status !== "armed")
|
|
91
|
+
continue;
|
|
92
|
+
filters.set(filter.id, filter);
|
|
93
|
+
}
|
|
94
|
+
// Group by source: this is the line that turns N reads into one.
|
|
95
|
+
const bySource = new Map();
|
|
96
|
+
for (const filter of filters.values()) {
|
|
97
|
+
const list = bySource.get(filter.sourceId);
|
|
98
|
+
if (list)
|
|
99
|
+
list.push(filter);
|
|
100
|
+
else
|
|
101
|
+
bySource.set(filter.sourceId, [filter]);
|
|
102
|
+
}
|
|
103
|
+
const now = this.now();
|
|
104
|
+
for (const [sourceId, sourceFilters] of bySource) {
|
|
105
|
+
const source = await sourceStore.get(sourceId);
|
|
106
|
+
if (!source) {
|
|
107
|
+
logger.warn({ sourceId }, "Subscribed filter points at a source that no longer exists");
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
let gateway;
|
|
111
|
+
try {
|
|
112
|
+
gateway = gatewayResolver(source.kind);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.warn({ err: error, sourceId, kind: source.kind }, "No gateway for source kind");
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
for (const filter of sourceFilters) {
|
|
119
|
+
try {
|
|
120
|
+
const { matched } = await scanMatchedSourceItems({
|
|
121
|
+
filter,
|
|
122
|
+
gateway,
|
|
123
|
+
externalUserId: source.externalUserId,
|
|
124
|
+
accountId: source.externalAccountId ?? "",
|
|
125
|
+
// Freshness is the pending store's job, keyed per subscriber, so
|
|
126
|
+
// this read deliberately answers "already handled?" with null for
|
|
127
|
+
// everything. Asking the shared ledger here would be a second,
|
|
128
|
+
// filter-wide answer to a question that is per subscription.
|
|
129
|
+
ledger: { get: () => Promise.resolve(null) },
|
|
130
|
+
sinceMs: windowStartFor(now, POLL_WINDOW_DAYS),
|
|
131
|
+
now: () => now,
|
|
132
|
+
});
|
|
133
|
+
summary.filtersMatched += 1;
|
|
134
|
+
if (matched.length === 0)
|
|
135
|
+
continue;
|
|
136
|
+
const results = await this.options.notifier.notifyForFilter(filter, matched);
|
|
137
|
+
summary.notified += results.filter((result) => result.outcome === "sent").length;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// A failing filter must not abandon its siblings on the same read.
|
|
141
|
+
logger.warn({ err: error, filterId: filter.id, sourceId }, "Subscription scan failed");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
summary.sourcesRead += 1;
|
|
145
|
+
}
|
|
146
|
+
const drained = await this.options.notifier.deliverAllPending();
|
|
147
|
+
summary.notified += drained.filter((result) => result.outcome === "sent").length;
|
|
148
|
+
// Retention runs on the TICK, not only at boot. Sweeping only at startup
|
|
149
|
+
// makes real retention `max(uptime, ttl)`, so a daemon up for forty days
|
|
150
|
+
// holds forty days of payloads while the docstring promises seven. That
|
|
151
|
+
// TTL is one of the three things containing a credential at rest, so it
|
|
152
|
+
// cannot depend on how often the process restarts. `IngestionService` does
|
|
153
|
+
// the same on its own health timer.
|
|
154
|
+
await this.sweep().catch((error) => {
|
|
155
|
+
this.options.logger.warn({ err: error }, "Subscription payload sweep failed");
|
|
156
|
+
});
|
|
157
|
+
return summary;
|
|
158
|
+
}
|
|
159
|
+
/** Delete pending payloads past their TTL. Called at boot, beside the other sweeps. */
|
|
160
|
+
async sweep() {
|
|
161
|
+
const removed = await this.options.pending.sweepExpired(this.now());
|
|
162
|
+
if (removed > 0) {
|
|
163
|
+
this.options.logger.info({ removed }, "Swept expired subscription payloads");
|
|
164
|
+
}
|
|
165
|
+
return removed;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=poller.js.map
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { StoredSubscription } from "@hyperdrive.bot/fleet-protocol/ingestion/subscription-types";
|
|
2
|
+
import type { PendingItem, PendingItemStore } from "./pending-store.js";
|
|
3
|
+
import type { SubscriptionStore } from "./store.js";
|
|
4
|
+
/**
|
|
5
|
+
* The READ half of subscriptions, and the only half a model ever holds.
|
|
6
|
+
*
|
|
7
|
+
* Deliberately not the notifier and not the stores themselves. A tool catalog
|
|
8
|
+
* that could arm a subscription, or trigger a delivery, would put the delivery
|
|
9
|
+
* policy inside a model's reach; this interface cannot do either. It exists as
|
|
10
|
+
* an interface rather than a concrete class so the tool layer depends on three
|
|
11
|
+
* methods instead of on the ingestion module.
|
|
12
|
+
*/
|
|
13
|
+
export interface SubscriptionReader {
|
|
14
|
+
/**
|
|
15
|
+
* Subscriptions the CALLER owns, never the daemon's whole set.
|
|
16
|
+
*
|
|
17
|
+
* `callerAgentId` is not decoration. Without it this is an enumeration step:
|
|
18
|
+
* one session lists every subscription on the box, reads another session's
|
|
19
|
+
* agent id, and then reads that session's stored payloads, credentials
|
|
20
|
+
* included. "A local MCP tool" means every agent on this daemon, which is
|
|
21
|
+
* not the same thing as the owner.
|
|
22
|
+
*/
|
|
23
|
+
list(callerAgentId: string | undefined): Promise<StoredSubscription[]>;
|
|
24
|
+
get(subscriptionId: string, callerAgentId: string | undefined): Promise<StoredSubscription | null>;
|
|
25
|
+
/**
|
|
26
|
+
* Stored items for the given keys, payload included.
|
|
27
|
+
*
|
|
28
|
+
* This is the ONLY path by which a persisted payload leaves the daemon, and
|
|
29
|
+
* it is a local MCP call: no client RPC exposes it. Keys that are not held
|
|
30
|
+
* are simply absent from the result, the same contract `resolveMatchedItems`
|
|
31
|
+
* already has, so a caller sees a shorter list rather than an exception.
|
|
32
|
+
*/
|
|
33
|
+
resolveItems(subscriptionId: string, itemKeys: readonly string[], callerAgentId: string | undefined): Promise<PendingItem[]>;
|
|
34
|
+
}
|
|
35
|
+
export declare function createSubscriptionReader(deps: {
|
|
36
|
+
subscriptions: SubscriptionStore;
|
|
37
|
+
pending: PendingItemStore;
|
|
38
|
+
}): SubscriptionReader;
|
|
39
|
+
//# sourceMappingURL=reader.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export function createSubscriptionReader(deps) {
|
|
2
|
+
/**
|
|
3
|
+
* Fails CLOSED when the caller is unknown.
|
|
4
|
+
*
|
|
5
|
+
* An absent `callerAgentId` means the tool catalog was built without a
|
|
6
|
+
* caller identity, and the safe reading of "I do not know who is asking" is
|
|
7
|
+
* "you own nothing", not "you own everything".
|
|
8
|
+
*/
|
|
9
|
+
const owns = (subscription, callerAgentId) => callerAgentId !== undefined && subscription.agentId === callerAgentId;
|
|
10
|
+
return {
|
|
11
|
+
list: async (callerAgentId) => (await deps.subscriptions.list()).filter((subscription) => owns(subscription, callerAgentId)),
|
|
12
|
+
get: async (subscriptionId, callerAgentId) => {
|
|
13
|
+
const subscription = await deps.subscriptions.get(subscriptionId);
|
|
14
|
+
if (!subscription || !owns(subscription, callerAgentId))
|
|
15
|
+
return null;
|
|
16
|
+
return subscription;
|
|
17
|
+
},
|
|
18
|
+
resolveItems: async (subscriptionId, itemKeys, callerAgentId) => {
|
|
19
|
+
// A subscription that no longer exists, or that belongs to someone else,
|
|
20
|
+
// resolves to nothing. Both answer the same way on purpose: a caller
|
|
21
|
+
// must not be able to tell "no such subscription" from "not yours" and
|
|
22
|
+
// use the difference to enumerate.
|
|
23
|
+
const subscription = await deps.subscriptions.get(subscriptionId);
|
|
24
|
+
if (!subscription || !owns(subscription, callerAgentId))
|
|
25
|
+
return [];
|
|
26
|
+
return deps.pending.resolve(subscriptionId, itemKeys);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=reader.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type StoredSubscription } from "@hyperdrive.bot/fleet-protocol/ingestion/subscription-types";
|
|
2
|
+
/**
|
|
3
|
+
* File-backed persistence for session subscriptions, one JSON file per
|
|
4
|
+
* subscription under `dir` (the caller supplies `join(paseoHome, "subscriptions")`).
|
|
5
|
+
*
|
|
6
|
+
* Structurally identical to `SourceStore` and `ScheduleStore`, deliberately: a
|
|
7
|
+
* third shape here would be a third set of atomicity and parse bugs to find.
|
|
8
|
+
*
|
|
9
|
+
* `StoredSubscriptionSchema` is strict and `list()` is readdir + Promise.all, so
|
|
10
|
+
* ONE unparseable file rejects the whole array. That is the same trade the
|
|
11
|
+
* source store makes, and it carries the same obligation: every field added to
|
|
12
|
+
* the schema later must carry `.default(...)`, or records written before it
|
|
13
|
+
* existed stop parsing and every subscription silently disappears. There is no
|
|
14
|
+
* migration step. `accountLabel` on `StoredSourceSchema` is the precedent.
|
|
15
|
+
*/
|
|
16
|
+
export declare class SubscriptionStore {
|
|
17
|
+
private readonly dir;
|
|
18
|
+
constructor(dir: string);
|
|
19
|
+
private filePath;
|
|
20
|
+
private ensureDir;
|
|
21
|
+
list(): Promise<StoredSubscription[]>;
|
|
22
|
+
get(id: string): Promise<StoredSubscription | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Every armed subscription pointing at `filterId`.
|
|
25
|
+
*
|
|
26
|
+
* The fan-out read. A filter with three armed subscribers returns three
|
|
27
|
+
* records here, and each one is notified on its own budget: this is the
|
|
28
|
+
* function that makes one filter serve many sessions.
|
|
29
|
+
*/
|
|
30
|
+
listArmedForFilter(filterId: string): Promise<StoredSubscription[]>;
|
|
31
|
+
create(subscription: Omit<StoredSubscription, "id">): Promise<StoredSubscription>;
|
|
32
|
+
put(subscription: StoredSubscription): Promise<void>;
|
|
33
|
+
delete(id: string): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { mkdir, readFile, readdir, rm } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { StoredSubscriptionSchema, } from "@hyperdrive.bot/fleet-protocol/ingestion/subscription-types";
|
|
5
|
+
import { writeJsonFileAtomic } from "../../atomic-file.js";
|
|
6
|
+
function generateSubscriptionId() {
|
|
7
|
+
return randomBytes(4).toString("hex");
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* File-backed persistence for session subscriptions, one JSON file per
|
|
11
|
+
* subscription under `dir` (the caller supplies `join(paseoHome, "subscriptions")`).
|
|
12
|
+
*
|
|
13
|
+
* Structurally identical to `SourceStore` and `ScheduleStore`, deliberately: a
|
|
14
|
+
* third shape here would be a third set of atomicity and parse bugs to find.
|
|
15
|
+
*
|
|
16
|
+
* `StoredSubscriptionSchema` is strict and `list()` is readdir + Promise.all, so
|
|
17
|
+
* ONE unparseable file rejects the whole array. That is the same trade the
|
|
18
|
+
* source store makes, and it carries the same obligation: every field added to
|
|
19
|
+
* the schema later must carry `.default(...)`, or records written before it
|
|
20
|
+
* existed stop parsing and every subscription silently disappears. There is no
|
|
21
|
+
* migration step. `accountLabel` on `StoredSourceSchema` is the precedent.
|
|
22
|
+
*/
|
|
23
|
+
export class SubscriptionStore {
|
|
24
|
+
constructor(dir) {
|
|
25
|
+
this.dir = dir;
|
|
26
|
+
}
|
|
27
|
+
filePath(id) {
|
|
28
|
+
return join(this.dir, `${id}.json`);
|
|
29
|
+
}
|
|
30
|
+
async ensureDir() {
|
|
31
|
+
await mkdir(this.dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
async list() {
|
|
34
|
+
await this.ensureDir();
|
|
35
|
+
const entries = await readdir(this.dir, { withFileTypes: true });
|
|
36
|
+
const subscriptions = await Promise.all(entries
|
|
37
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
|
|
38
|
+
.map(async (entry) => {
|
|
39
|
+
const content = await readFile(join(this.dir, entry.name), "utf-8");
|
|
40
|
+
return StoredSubscriptionSchema.parse(JSON.parse(content));
|
|
41
|
+
}));
|
|
42
|
+
return subscriptions.sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
43
|
+
}
|
|
44
|
+
async get(id) {
|
|
45
|
+
await this.ensureDir();
|
|
46
|
+
try {
|
|
47
|
+
const content = await readFile(this.filePath(id), "utf-8");
|
|
48
|
+
return StoredSubscriptionSchema.parse(JSON.parse(content));
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code === "ENOENT") {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Every armed subscription pointing at `filterId`.
|
|
59
|
+
*
|
|
60
|
+
* The fan-out read. A filter with three armed subscribers returns three
|
|
61
|
+
* records here, and each one is notified on its own budget: this is the
|
|
62
|
+
* function that makes one filter serve many sessions.
|
|
63
|
+
*/
|
|
64
|
+
async listArmedForFilter(filterId) {
|
|
65
|
+
const all = await this.list();
|
|
66
|
+
return all.filter((subscription) => subscription.filterId === filterId && subscription.status === "armed");
|
|
67
|
+
}
|
|
68
|
+
async create(subscription) {
|
|
69
|
+
const created = { ...subscription, id: generateSubscriptionId() };
|
|
70
|
+
await this.put(created);
|
|
71
|
+
return created;
|
|
72
|
+
}
|
|
73
|
+
async put(subscription) {
|
|
74
|
+
await this.ensureDir();
|
|
75
|
+
await writeJsonFileAtomic(this.filePath(subscription.id), subscription);
|
|
76
|
+
}
|
|
77
|
+
async delete(id) {
|
|
78
|
+
await this.ensureDir();
|
|
79
|
+
await rm(this.filePath(id), { force: true });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* `Record<SourceKind, ...>` registry in `adapters.ts` turns that silence into
|
|
15
15
|
* a compile error.
|
|
16
16
|
*/
|
|
17
|
-
export declare const SOURCE_CAPABLE_KINDS: readonly ["pipedream", "composio"];
|
|
17
|
+
export declare const SOURCE_CAPABLE_KINDS: readonly ["pipedream", "composio", "self-hosted"];
|
|
18
18
|
export type SourceKind = (typeof SOURCE_CAPABLE_KINDS)[number];
|
|
19
19
|
/** Where to reach an aggregator's remote MCP server for a single agent turn. */
|
|
20
20
|
export interface McpTarget {
|
|
@@ -101,9 +101,22 @@ export interface PipedreamConfig {
|
|
|
101
101
|
export interface ComposioConfig {
|
|
102
102
|
apiKey?: string | undefined;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Where this daemon's OWN source bridge lives, and the token that opens it.
|
|
106
|
+
*
|
|
107
|
+
* Unlike the aggregator configs above, both fields are user-supplied and
|
|
108
|
+
* user-hosted: paseo holds no platform credential for a self-hosted bridge, it
|
|
109
|
+
* holds a bearer for one endpoint the user runs. `configured` is false when
|
|
110
|
+
* either is missing, which is how the sources screen knows not to offer it.
|
|
111
|
+
*/
|
|
112
|
+
export interface SelfHostedConfig {
|
|
113
|
+
url?: string | undefined;
|
|
114
|
+
token?: string | undefined;
|
|
115
|
+
}
|
|
104
116
|
export interface IngestionConfig {
|
|
105
117
|
pipedream: PipedreamConfig;
|
|
106
118
|
composio: ComposioConfig;
|
|
119
|
+
selfHosted?: SelfHostedConfig | undefined;
|
|
107
120
|
}
|
|
108
121
|
/**
|
|
109
122
|
* A single-field object on purpose: later stories add a logger here without
|
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
* `Record<SourceKind, ...>` registry in `adapters.ts` turns that silence into
|
|
15
15
|
* a compile error.
|
|
16
16
|
*/
|
|
17
|
-
export const SOURCE_CAPABLE_KINDS = ["pipedream", "composio"];
|
|
17
|
+
export const SOURCE_CAPABLE_KINDS = ["pipedream", "composio", "self-hosted"];
|
|
18
18
|
//# sourceMappingURL=types.js.map
|