@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);
@@ -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)
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wopr-network/platform-core",
3
- "version": "1.72.4",
3
+ "version": "1.74.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -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);
@@ -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 };
@@ -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