@wopr-network/platform-core 1.72.4 → 1.74.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.
|
@@ -125,6 +125,15 @@ export class FleetManager {
|
|
|
125
125
|
}
|
|
126
126
|
const instance = await this.buildInstance(profile);
|
|
127
127
|
instance.emitCreated();
|
|
128
|
+
// Register in bot_instances DB table for billing
|
|
129
|
+
if (this.instanceRepo) {
|
|
130
|
+
try {
|
|
131
|
+
await this.instanceRepo.register(id, profile.tenantId, profile.name);
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
logger.warn("Failed to register bot instance in DB (non-fatal)", { id, err });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
128
137
|
return instance;
|
|
129
138
|
};
|
|
130
139
|
return hasExplicitId ? this.withLock(id, doCreate) : doCreate();
|
|
@@ -208,6 +217,15 @@ export class FleetManager {
|
|
|
208
217
|
if (this.networkPolicy) {
|
|
209
218
|
await this.networkPolicy.cleanupAfterRemoval(profile.tenantId);
|
|
210
219
|
}
|
|
220
|
+
// Remove from bot_instances DB table
|
|
221
|
+
if (this.instanceRepo) {
|
|
222
|
+
try {
|
|
223
|
+
await this.instanceRepo.deleteById(id);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
logger.warn("Failed to delete bot instance from DB (non-fatal)", { id, err });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
211
229
|
await this.store.delete(id);
|
|
212
230
|
logger.info(`Removed bot ${id}`);
|
|
213
231
|
this.emitEvent("bot.removed", id, profile.tenantId);
|
package/dist/server/container.js
CHANGED
|
@@ -80,9 +80,12 @@ export async function buildContainer(bootConfig) {
|
|
|
80
80
|
const profileStore = new ProfileStore(fleetDataDir);
|
|
81
81
|
const proxy = new ProxyManager();
|
|
82
82
|
const serviceKeyRepo = new DrizzleServiceKeyRepository(db);
|
|
83
|
+
const { DrizzleBotInstanceRepository } = await import("../fleet/drizzle-bot-instance-repository.js");
|
|
84
|
+
const botInstanceRepo = new DrizzleBotInstanceRepository(db);
|
|
83
85
|
const manager = new FleetManagerClass(docker, profileStore, undefined, // platformDiscovery
|
|
84
86
|
undefined, // networkPolicy
|
|
85
|
-
proxy
|
|
87
|
+
proxy, undefined, // commandBus
|
|
88
|
+
botInstanceRepo);
|
|
86
89
|
fleet = { manager, docker, proxy, profileStore, serviceKeyRepo };
|
|
87
90
|
}
|
|
88
91
|
// 9. Crypto services (when enabled)
|
package/dist/server/lifecycle.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* This module provides a standard interface for starting and stopping
|
|
6
6
|
* those tasks so bootPlatformServer can manage them uniformly.
|
|
7
7
|
*/
|
|
8
|
+
import { logger } from "../config/logger.js";
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
// startBackgroundServices
|
|
10
11
|
// ---------------------------------------------------------------------------
|
|
@@ -26,6 +27,33 @@ export async function startBackgroundServices(container) {
|
|
|
26
27
|
// Non-fatal — proxy sync will retry on next health tick
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
// Backfill bot_instances from YAML profiles (one-time sync on startup)
|
|
31
|
+
if (container.fleet) {
|
|
32
|
+
try {
|
|
33
|
+
const { DrizzleBotInstanceRepository } = await import("../fleet/drizzle-bot-instance-repository.js");
|
|
34
|
+
const botInstanceRepo = new DrizzleBotInstanceRepository(container.db);
|
|
35
|
+
const profiles = await container.fleet.profileStore.list();
|
|
36
|
+
let synced = 0;
|
|
37
|
+
for (const profile of profiles) {
|
|
38
|
+
const existing = await botInstanceRepo.getById(profile.id);
|
|
39
|
+
if (!existing) {
|
|
40
|
+
try {
|
|
41
|
+
await botInstanceRepo.register(profile.id, profile.tenantId, profile.name);
|
|
42
|
+
synced++;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Ignore duplicates / constraint violations
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (synced > 0) {
|
|
50
|
+
logger.info(`Backfilled ${synced} bot instances from profiles into DB`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
logger.warn("Failed to backfill bot_instances (non-fatal)", { error: String(err) });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
29
57
|
// Hot pool manager (if enabled)
|
|
30
58
|
if (container.hotPool) {
|
|
31
59
|
try {
|
|
@@ -36,6 +64,45 @@ export async function startBackgroundServices(container) {
|
|
|
36
64
|
// Non-fatal — pool will be empty but claiming falls back to cold create
|
|
37
65
|
}
|
|
38
66
|
}
|
|
67
|
+
// Runtime billing cron — daily $0.17/bot deduction (requires fleet + creditLedger)
|
|
68
|
+
if (container.fleet && container.creditLedger) {
|
|
69
|
+
try {
|
|
70
|
+
const { DrizzleBotInstanceRepository } = await import("../fleet/drizzle-bot-instance-repository.js");
|
|
71
|
+
const { DrizzleTenantAddonRepository } = await import("../monetization/addons/addon-repository.js");
|
|
72
|
+
const { startRuntimeScheduler } = await import("../monetization/credits/runtime-scheduler.js");
|
|
73
|
+
const botInstanceRepo = new DrizzleBotInstanceRepository(container.db);
|
|
74
|
+
const tenantAddonRepo = new DrizzleTenantAddonRepository(container.db);
|
|
75
|
+
const scheduler = startRuntimeScheduler({
|
|
76
|
+
ledger: container.creditLedger,
|
|
77
|
+
botInstanceRepo,
|
|
78
|
+
tenantAddonRepo,
|
|
79
|
+
});
|
|
80
|
+
handles.unsubscribes.push(scheduler.stop);
|
|
81
|
+
// Run immediately on startup (idempotent — skips if already billed today)
|
|
82
|
+
const { runRuntimeDeductions, buildResourceTierCosts } = await import("../monetization/credits/runtime-cron.js");
|
|
83
|
+
const { buildAddonCosts } = await import("../monetization/addons/addon-cron.js");
|
|
84
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
85
|
+
void runRuntimeDeductions({
|
|
86
|
+
ledger: container.creditLedger,
|
|
87
|
+
date: today,
|
|
88
|
+
getActiveBotCount: async (tenantId) => {
|
|
89
|
+
const bots = await botInstanceRepo.listByTenant(tenantId);
|
|
90
|
+
return bots.filter((b) => b.billingState === "active").length;
|
|
91
|
+
},
|
|
92
|
+
getResourceTierCosts: buildResourceTierCosts(botInstanceRepo, async (tenantId) => {
|
|
93
|
+
const bots = await botInstanceRepo.listByTenant(tenantId);
|
|
94
|
+
return bots.filter((b) => b.billingState === "active").map((b) => b.id);
|
|
95
|
+
}),
|
|
96
|
+
getAddonCosts: buildAddonCosts(tenantAddonRepo),
|
|
97
|
+
})
|
|
98
|
+
.then((result) => logger.info("Initial runtime deductions complete", result))
|
|
99
|
+
.catch((err) => logger.error("Initial runtime deductions failed", { error: String(err) }));
|
|
100
|
+
logger.info("Runtime billing scheduler started (daily $0.17/bot deduction)");
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
logger.warn("Failed to start runtime billing scheduler (non-fatal)", { error: String(err) });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
39
106
|
return handles;
|
|
40
107
|
}
|
|
41
108
|
// ---------------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -152,6 +152,16 @@ export class FleetManager {
|
|
|
152
152
|
|
|
153
153
|
const instance = await this.buildInstance(profile);
|
|
154
154
|
instance.emitCreated();
|
|
155
|
+
|
|
156
|
+
// Register in bot_instances DB table for billing
|
|
157
|
+
if (this.instanceRepo) {
|
|
158
|
+
try {
|
|
159
|
+
await this.instanceRepo.register(id, profile.tenantId, profile.name);
|
|
160
|
+
} catch (err) {
|
|
161
|
+
logger.warn("Failed to register bot instance in DB (non-fatal)", { id, err });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
155
165
|
return instance;
|
|
156
166
|
};
|
|
157
167
|
|
|
@@ -239,6 +249,15 @@ export class FleetManager {
|
|
|
239
249
|
await this.networkPolicy.cleanupAfterRemoval(profile.tenantId);
|
|
240
250
|
}
|
|
241
251
|
|
|
252
|
+
// Remove from bot_instances DB table
|
|
253
|
+
if (this.instanceRepo) {
|
|
254
|
+
try {
|
|
255
|
+
await this.instanceRepo.deleteById(id);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
logger.warn("Failed to delete bot instance from DB (non-fatal)", { id, err });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
242
261
|
await this.store.delete(id);
|
|
243
262
|
logger.info(`Removed bot ${id}`);
|
|
244
263
|
this.emitEvent("bot.removed", id, profile.tenantId);
|
package/src/server/container.ts
CHANGED
|
@@ -178,12 +178,17 @@ export async function buildContainer(bootConfig: BootConfig): Promise<PlatformCo
|
|
|
178
178
|
const profileStore: IProfileStore = new ProfileStore(fleetDataDir);
|
|
179
179
|
const proxy: ProxyManagerInterface = new ProxyManager();
|
|
180
180
|
const serviceKeyRepo: IServiceKeyRepository = new DrizzleServiceKeyRepository(db as never);
|
|
181
|
+
const { DrizzleBotInstanceRepository } = await import("../fleet/drizzle-bot-instance-repository.js");
|
|
182
|
+
const botInstanceRepo = new DrizzleBotInstanceRepository(db as never);
|
|
183
|
+
|
|
181
184
|
const manager: FleetManager = new FleetManagerClass(
|
|
182
185
|
docker,
|
|
183
186
|
profileStore,
|
|
184
187
|
undefined, // platformDiscovery
|
|
185
188
|
undefined, // networkPolicy
|
|
186
189
|
proxy,
|
|
190
|
+
undefined, // commandBus
|
|
191
|
+
botInstanceRepo,
|
|
187
192
|
);
|
|
188
193
|
|
|
189
194
|
fleet = { manager, docker, proxy, profileStore, serviceKeyRepo };
|
package/src/server/lifecycle.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* those tasks so bootPlatformServer can manage them uniformly.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
import { logger } from "../config/logger.js";
|
|
9
10
|
import type { PlatformContainer } from "./container.js";
|
|
10
11
|
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
@@ -40,6 +41,32 @@ export async function startBackgroundServices(container: PlatformContainer): Pro
|
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
|
|
44
|
+
// Backfill bot_instances from YAML profiles (one-time sync on startup)
|
|
45
|
+
if (container.fleet) {
|
|
46
|
+
try {
|
|
47
|
+
const { DrizzleBotInstanceRepository } = await import("../fleet/drizzle-bot-instance-repository.js");
|
|
48
|
+
const botInstanceRepo = new DrizzleBotInstanceRepository(container.db);
|
|
49
|
+
const profiles = await container.fleet.profileStore.list();
|
|
50
|
+
let synced = 0;
|
|
51
|
+
for (const profile of profiles) {
|
|
52
|
+
const existing = await botInstanceRepo.getById(profile.id);
|
|
53
|
+
if (!existing) {
|
|
54
|
+
try {
|
|
55
|
+
await botInstanceRepo.register(profile.id, profile.tenantId, profile.name);
|
|
56
|
+
synced++;
|
|
57
|
+
} catch {
|
|
58
|
+
// Ignore duplicates / constraint violations
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (synced > 0) {
|
|
63
|
+
logger.info(`Backfilled ${synced} bot instances from profiles into DB`);
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
logger.warn("Failed to backfill bot_instances (non-fatal)", { error: String(err) });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
43
70
|
// Hot pool manager (if enabled)
|
|
44
71
|
if (container.hotPool) {
|
|
45
72
|
try {
|
|
@@ -50,6 +77,49 @@ export async function startBackgroundServices(container: PlatformContainer): Pro
|
|
|
50
77
|
}
|
|
51
78
|
}
|
|
52
79
|
|
|
80
|
+
// Runtime billing cron — daily $0.17/bot deduction (requires fleet + creditLedger)
|
|
81
|
+
if (container.fleet && container.creditLedger) {
|
|
82
|
+
try {
|
|
83
|
+
const { DrizzleBotInstanceRepository } = await import("../fleet/drizzle-bot-instance-repository.js");
|
|
84
|
+
const { DrizzleTenantAddonRepository } = await import("../monetization/addons/addon-repository.js");
|
|
85
|
+
const { startRuntimeScheduler } = await import("../monetization/credits/runtime-scheduler.js");
|
|
86
|
+
|
|
87
|
+
const botInstanceRepo = new DrizzleBotInstanceRepository(container.db);
|
|
88
|
+
const tenantAddonRepo = new DrizzleTenantAddonRepository(container.db);
|
|
89
|
+
|
|
90
|
+
const scheduler = startRuntimeScheduler({
|
|
91
|
+
ledger: container.creditLedger,
|
|
92
|
+
botInstanceRepo,
|
|
93
|
+
tenantAddonRepo,
|
|
94
|
+
});
|
|
95
|
+
handles.unsubscribes.push(scheduler.stop);
|
|
96
|
+
|
|
97
|
+
// Run immediately on startup (idempotent — skips if already billed today)
|
|
98
|
+
const { runRuntimeDeductions, buildResourceTierCosts } = await import("../monetization/credits/runtime-cron.js");
|
|
99
|
+
const { buildAddonCosts } = await import("../monetization/addons/addon-cron.js");
|
|
100
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
101
|
+
void runRuntimeDeductions({
|
|
102
|
+
ledger: container.creditLedger,
|
|
103
|
+
date: today,
|
|
104
|
+
getActiveBotCount: async (tenantId) => {
|
|
105
|
+
const bots = await botInstanceRepo.listByTenant(tenantId);
|
|
106
|
+
return bots.filter((b) => b.billingState === "active").length;
|
|
107
|
+
},
|
|
108
|
+
getResourceTierCosts: buildResourceTierCosts(botInstanceRepo, async (tenantId) => {
|
|
109
|
+
const bots = await botInstanceRepo.listByTenant(tenantId);
|
|
110
|
+
return bots.filter((b) => b.billingState === "active").map((b) => b.id);
|
|
111
|
+
}),
|
|
112
|
+
getAddonCosts: buildAddonCosts(tenantAddonRepo),
|
|
113
|
+
})
|
|
114
|
+
.then((result) => logger.info("Initial runtime deductions complete", result))
|
|
115
|
+
.catch((err) => logger.error("Initial runtime deductions failed", { error: String(err) }));
|
|
116
|
+
|
|
117
|
+
logger.info("Runtime billing scheduler started (daily $0.17/bot deduction)");
|
|
118
|
+
} catch (err) {
|
|
119
|
+
logger.warn("Failed to start runtime billing scheduler (non-fatal)", { error: String(err) });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
53
123
|
return handles;
|
|
54
124
|
}
|
|
55
125
|
|