@naisys/hub 3.0.0-beta.36 → 3.0.0-beta.38

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.
@@ -1,6 +1,29 @@
1
- import { AgentPeekRequestSchema, AgentRunCommandRequestSchema, AgentRunPauseRequestSchema, AgentStartRequestSchema, AgentStopRequestSchema, HubEvents, } from "@naisys/hub-protocol";
1
+ import { hashToken } from "@naisys/common-node";
2
+ import { AgentPeekRequestSchema, AgentRunCommandRequestSchema, AgentRunPauseRequestSchema, AgentStartInboundSchema, AgentStopRequestSchema, HubEvents, } from "@naisys/hub-protocol";
3
+ import { randomBytes } from "crypto";
2
4
  /** Handles agent_start requests by routing them to the least-loaded eligible host */
3
5
  export function createHubAgentService(naisysServer, { hubDb }, logService, heartbeatService, sendMailService, hostRegistrar) {
6
+ /**
7
+ * Mint a fresh runtime API key for a user, rotating any prior key. Plaintext
8
+ * is returned only here and travels once over the AGENT_START message; the
9
+ * DB only stores the hash. Keys persist across hub restarts/crashes/updates;
10
+ * revocation lives on the user disable/archive/delete paths and on graceful
11
+ * AGENT_STOP.
12
+ */
13
+ async function issueRuntimeApiKey(userId) {
14
+ const token = randomBytes(32).toString("hex");
15
+ await hubDb.users.update({
16
+ where: { id: userId },
17
+ data: { api_key_hash: hashToken(token) },
18
+ });
19
+ return token;
20
+ }
21
+ async function revokeRuntimeApiKey(userId) {
22
+ await hubDb.users.update({
23
+ where: { id: userId },
24
+ data: { api_key_hash: null },
25
+ });
26
+ }
4
27
  /** Find the least-loaded eligible host for a given user */
5
28
  async function findBestHost(startUserId) {
6
29
  // Look up which hosts this user is assigned to
@@ -52,30 +75,61 @@ export function createHubAgentService(naisysServer, { hubDb }, logService, heart
52
75
  });
53
76
  return !!user?.enabled && !user?.archived;
54
77
  }
78
+ /** Run the start preconditions and pick a host. */
79
+ async function decideStartAgent(userId) {
80
+ if (!(await isAgentEnabled(userId))) {
81
+ return { kind: "fail", error: `Agent ${userId} is disabled` };
82
+ }
83
+ if (heartbeatService.findHostsForAgent(userId).length > 0) {
84
+ return { kind: "fail", error: `Agent ${userId} is already running` };
85
+ }
86
+ const bestHostId = await findBestHost(userId);
87
+ if (bestHostId === null) {
88
+ return {
89
+ kind: "fail",
90
+ error: `No eligible hosts are online for user ${userId}`,
91
+ };
92
+ }
93
+ return { kind: "go", bestHostId };
94
+ }
95
+ /**
96
+ * Issue a runtime key and send AGENT_START. Stranded keys from a failed
97
+ * send or failed response stay in the DB until the next AGENT_START for
98
+ * that user rotates them, or the user is disabled/archived/deleted — the
99
+ * agent process that would have used the key never started, so there's
100
+ * nobody to authenticate with it in the meantime.
101
+ */
102
+ async function dispatchAgentStart(args) {
103
+ const { bestHostId, payload, onResponse } = args;
104
+ const startUserId = payload.startUserId;
105
+ const runtimeApiKey = await issueRuntimeApiKey(startUserId);
106
+ const sent = naisysServer.sendMessage(bestHostId, HubEvents.AGENT_START, { ...payload, runtimeApiKey }, (response) => {
107
+ if (response.success) {
108
+ heartbeatService.addStartedAgent(bestHostId, startUserId);
109
+ }
110
+ onResponse(response);
111
+ });
112
+ return { sent };
113
+ }
55
114
  /** Try to start an agent on the best available host (fire-and-forget) */
56
115
  async function tryStartAgent(startUserId) {
57
116
  try {
58
- if (!(await isAgentEnabled(startUserId))) {
59
- logService.log(`[Hub:Agents] Auto-start: agent ${startUserId} is disabled or archived`);
60
- return false;
61
- }
62
- const bestHostId = await findBestHost(startUserId);
63
- if (bestHostId === null) {
64
- logService.log(`[Hub:Agents] Auto-start: no eligible host for user ${startUserId}`);
117
+ const decision = await decideStartAgent(startUserId);
118
+ if (decision.kind === "fail") {
119
+ logService.log(`[Hub:Agents] Auto-start: ${decision.error}`);
65
120
  return false;
66
121
  }
67
- const sent = naisysServer.sendMessage(bestHostId, HubEvents.AGENT_START, {
68
- startUserId,
69
- }, (response) => {
70
- if (response.success) {
71
- heartbeatService.addStartedAgent(bestHostId, startUserId);
72
- }
73
- else {
74
- logService.error(`[Hub:Agents] Auto-start failed for user ${startUserId}: ${response.error}`);
75
- }
122
+ const { sent } = await dispatchAgentStart({
123
+ bestHostId: decision.bestHostId,
124
+ payload: { startUserId },
125
+ onResponse: (response) => {
126
+ if (!response.success) {
127
+ logService.error(`[Hub:Agents] Auto-start failed for user ${startUserId}: ${response.error}`);
128
+ }
129
+ },
76
130
  });
77
131
  if (sent) {
78
- logService.log(`[Hub:Agents] Auto-start: sent start for user ${startUserId} to host ${bestHostId}`);
132
+ logService.log(`[Hub:Agents] Auto-start: sent start for user ${startUserId} to host ${decision.bestHostId}`);
79
133
  }
80
134
  return sent;
81
135
  }
@@ -86,51 +140,33 @@ export function createHubAgentService(naisysServer, { hubDb }, logService, heart
86
140
  }
87
141
  naisysServer.registerEvent(HubEvents.AGENT_START, async (hostId, data, ack) => {
88
142
  try {
89
- const parsed = AgentStartRequestSchema.parse(data);
90
- const requesterUserId = parsed.requesterUserId;
91
- if (!(await isAgentEnabled(parsed.startUserId))) {
92
- ack({
93
- success: false,
94
- error: `Agent ${parsed.startUserId} is disabled`,
95
- });
96
- return;
97
- }
98
- const bestHostId = await findBestHost(parsed.startUserId);
99
- if (bestHostId === null) {
100
- ack({
101
- success: false,
102
- error: `No eligible hosts are online for user ${parsed.startUserId}`,
103
- });
143
+ const parsed = AgentStartInboundSchema.parse(data);
144
+ const decision = await decideStartAgent(parsed.startUserId);
145
+ if (decision.kind === "fail") {
146
+ ack({ success: false, error: decision.error });
104
147
  return;
105
148
  }
106
- if (!requesterUserId) {
107
- ack({
108
- success: false,
109
- error: `Missing requesterUserId in agent_start request for user ${parsed.startUserId}`,
110
- });
111
- return;
112
- }
113
- // Forward the start request to the selected host
114
- const sent = naisysServer.sendMessage(bestHostId, HubEvents.AGENT_START, {
115
- startUserId: parsed.startUserId,
116
- taskDescription: parsed.taskDescription,
117
- sourceHostId: hostId,
118
- }, (response) => {
119
- if (response.success) {
120
- heartbeatService.addStartedAgent(bestHostId, parsed.startUserId);
121
- }
122
- // Reverse-ack with the response from the host (including success status and any error message) back to the original requester
123
- ack(response);
124
- // Send task description mail after successful start to avoid
125
- // orphaned mails from failed start attempts
126
- if (response.success && parsed.taskDescription) {
127
- void sendTaskMail(parsed.startUserId, requesterUserId, parsed.taskDescription);
128
- }
149
+ const { sent } = await dispatchAgentStart({
150
+ bestHostId: decision.bestHostId,
151
+ payload: {
152
+ startUserId: parsed.startUserId,
153
+ taskDescription: parsed.taskDescription,
154
+ sourceHostId: hostId,
155
+ },
156
+ onResponse: (response) => {
157
+ // Reverse-ack with the response from the host (including success status and any error message) back to the original requester
158
+ ack(response);
159
+ // Send task description mail after successful start to avoid
160
+ // orphaned mails from failed start attempts
161
+ if (response.success && parsed.taskDescription) {
162
+ void sendTaskMail(parsed.startUserId, parsed.requesterUserId, parsed.taskDescription);
163
+ }
164
+ },
129
165
  });
130
166
  if (!sent) {
131
167
  ack({
132
168
  success: false,
133
- error: `Failed to send to host ${bestHostId}`,
169
+ error: `Failed to send to host ${decision.bestHostId}`,
134
170
  });
135
171
  }
136
172
  }
@@ -176,6 +212,9 @@ export function createHubAgentService(naisysServer, { hubDb }, logService, heart
176
212
  }, (response) => {
177
213
  if (response.success) {
178
214
  heartbeatService.removeStoppedAgent(targetHostId, parsed.userId);
215
+ revokeRuntimeApiKey(parsed.userId).catch((err) => {
216
+ logService.error(`[Hub:Agents] Failed to revoke runtime key for user ${parsed.userId} on stop: ${err}`);
217
+ });
179
218
  }
180
219
  // Ack with the first response
181
220
  if (!acked) {
@@ -1,10 +1,8 @@
1
- import { mimeFromFilename } from "@naisys/common";
2
- import { extractBearerToken } from "@naisys/common-node";
3
- import { createHash, randomBytes } from "crypto";
4
- import { createReadStream, createWriteStream, existsSync, mkdirSync, renameSync, statSync, unlinkSync, } from "fs";
1
+ import { extractBearerToken, generateAttachmentPublicId, getHubAttachmentPath, hashToken, MAX_HUB_ATTACHMENT_SIZE, sendAttachmentResponse, } from "@naisys/common-node";
2
+ import { createHash } from "crypto";
3
+ import { createWriteStream, existsSync, mkdirSync, renameSync, unlinkSync, } from "fs";
5
4
  import { join } from "path";
6
5
  import { pipeline, Writable } from "stream";
7
- const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
8
6
  /**
9
7
  * HTTP attachment upload/download service.
10
8
  * Registers Fastify routes for `/hub/attachments` paths.
@@ -12,7 +10,9 @@ const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
12
10
  export function createHubAttachmentService(fastify, { hubDb }, logService) {
13
11
  const naisysFolder = process.env.NAISYS_FOLDER || "";
14
12
  async function resolveUserByApiKey(apiKey) {
15
- const user = await hubDb.users.findUnique({ where: { api_key: apiKey } });
13
+ const user = await hubDb.users.findUnique({
14
+ where: { api_key_hash: hashToken(apiKey) },
15
+ });
16
16
  return user?.id ?? null;
17
17
  }
18
18
  // Upload route — encapsulated so the raw content-type parser doesn't leak
@@ -49,9 +49,9 @@ export function createHubAttachmentService(fastify, { hubDb }, logService) {
49
49
  if (isNaN(fileSize) || fileSize <= 0) {
50
50
  return reply.code(400).send({ error: "Invalid filesize" });
51
51
  }
52
- if (fileSize > MAX_FILE_SIZE) {
52
+ if (fileSize > MAX_HUB_ATTACHMENT_SIZE) {
53
53
  return reply.code(413).send({
54
- error: `File too large. Max size: ${MAX_FILE_SIZE} bytes`,
54
+ error: `File too large. Max size: ${MAX_HUB_ATTACHMENT_SIZE} bytes`,
55
55
  });
56
56
  }
57
57
  const userId = await resolveUserByApiKey(apiKey);
@@ -71,7 +71,7 @@ export function createHubAttachmentService(fastify, { hubDb }, logService) {
71
71
  const sizeChecker = new Writable({
72
72
  write(chunk, _encoding, callback) {
73
73
  bytesWritten += chunk.length;
74
- if (bytesWritten > MAX_FILE_SIZE) {
74
+ if (bytesWritten > MAX_HUB_ATTACHMENT_SIZE) {
75
75
  callback(new Error("File exceeds size limit"));
76
76
  return;
77
77
  }
@@ -116,10 +116,8 @@ export function createHubAttachmentService(fastify, { hubDb }, logService) {
116
116
  error: `Hash mismatch. Expected: ${fileHash}, got: ${computedHash}`,
117
117
  });
118
118
  }
119
- // Move to content-addressable path: attachments/hub/<first2>/<next2>/<fullhash>
120
- const storageDir = join(naisysFolder, "attachments", "hub", computedHash.slice(0, 2), computedHash.slice(2, 4));
119
+ const { storageDir, storagePath } = getHubAttachmentPath(naisysFolder, computedHash);
121
120
  mkdirSync(storageDir, { recursive: true });
122
- const storagePath = join(storageDir, computedHash);
123
121
  if (existsSync(storagePath)) {
124
122
  // Dedup: identical file already on disk, discard temp
125
123
  try {
@@ -132,10 +130,9 @@ export function createHubAttachmentService(fastify, { hubDb }, logService) {
132
130
  else {
133
131
  renameSync(tmpPath, storagePath);
134
132
  }
135
- // Create DB record
136
133
  const record = await hubDb.attachments.create({
137
134
  data: {
138
- public_id: randomBytes(8).toString("base64url").slice(0, 10),
135
+ public_id: generateAttachmentPublicId(),
139
136
  filepath: storagePath,
140
137
  filename,
141
138
  file_size: bytesWritten,
@@ -174,22 +171,7 @@ export function createHubAttachmentService(fastify, { hubDb }, logService) {
174
171
  if (!attachment) {
175
172
  return reply.code(404).send({ error: "Attachment not found" });
176
173
  }
177
- if (!existsSync(attachment.filepath)) {
178
- return reply
179
- .code(404)
180
- .send({ error: "Attachment file missing from disk" });
181
- }
182
- const stat = statSync(attachment.filepath);
183
- const contentType = mimeFromFilename(attachment.filename);
184
- const disposition = contentType.startsWith("image/")
185
- ? "inline"
186
- : "attachment";
187
- reply
188
- .header("Content-Type", contentType)
189
- .header("Content-Disposition", `${disposition}; filename="${attachment.filename.replace(/"/g, '\\"')}"`)
190
- .header("Content-Length", stat.size);
191
- const readStream = createReadStream(attachment.filepath);
192
- return reply.send(readStream);
174
+ return sendAttachmentResponse(reply, attachment.filepath, attachment.filename);
193
175
  }
194
176
  fastify.get("/hub/attachments/:publicId/:filename", async (request, reply) => {
195
177
  try {
@@ -64,7 +64,9 @@ export function createHubHeartbeatService(naisysServer, { hubDb }, logService) {
64
64
  logService.error(`[Hub:Heartbeat] Error updating heartbeat for host ${hostId}: ${error}`);
65
65
  }
66
66
  });
67
- // Clean up tracking when a host disconnects
67
+ // Clean up tracking when a host disconnects. Runtime API keys are not
68
+ // touched here — they're rotated on the next AGENT_START and revoked
69
+ // explicitly on user disable/archive/delete.
68
70
  naisysServer.registerEvent(HubEvents.CLIENT_DISCONNECTED, (hostId) => {
69
71
  hostActiveAgents.delete(hostId);
70
72
  hostActiveSessions.delete(hostId);
@@ -10,7 +10,6 @@ export function createHubUserService(naisysServer, { hubDb }, logService) {
10
10
  enabled: true,
11
11
  config: true,
12
12
  lead_user_id: true,
13
- api_key: true,
14
13
  user_hosts: {
15
14
  select: { host_id: true },
16
15
  },
@@ -25,7 +24,6 @@ export function createHubUserService(naisysServer, { hubDb }, logService) {
25
24
  assignedHostIds: u.user_hosts.length > 0
26
25
  ? u.user_hosts.map((uh) => uh.host_id)
27
26
  : undefined,
28
- apiKey: u.api_key || undefined,
29
27
  }));
30
28
  return { success: true, users };
31
29
  }
package/dist/naisysHub.js CHANGED
@@ -1,4 +1,4 @@
1
- import { createDualLogger, cwdWithTilde, ensureDotEnv, expandNaisysFolder, promptSuperAdminPassword, runSetupWizard, } from "@naisys/common-node";
1
+ import { createDualLogger, cwdWithTilde, ensureDotEnv, expandNaisysFolder, promptResetSuperAdminPasskey, runSetupWizard, } from "@naisys/common-node";
2
2
  import { createHubDatabaseService } from "@naisys/hub-database";
3
3
  import { program } from "commander";
4
4
  import dotenv from "dotenv";
@@ -43,8 +43,9 @@ export const startHub = async (startupType, startSupervisor, plugins, startupAge
43
43
  await seedAgentConfigs(hubDatabaseService, logService, agentPath);
44
44
  // Create host registrar for tracking NAISYS instance connections
45
45
  const hostRegistrar = await createHostRegistrar(hubDatabaseService);
46
- // Create Fastify instance (TLS is handled by the reverse proxy)
47
- const fastify = Fastify({ pluginTimeout: 60_000 });
46
+ // trustProxy: TLS terminates at the reverse proxy, so honor X-Forwarded-*
47
+ // headers otherwise request.protocol reads the internal http hop.
48
+ const fastify = Fastify({ pluginTimeout: 60_000, trustProxy: true });
48
49
  // Register HTTP attachment upload/download routes
49
50
  createHubAttachmentService(fastify, hubDatabaseService, logService);
50
51
  // Attach Socket.IO to the underlying HTTP server.
@@ -87,15 +88,18 @@ export const startHub = async (startupType, startSupervisor, plugins, startupAge
87
88
  // Don't import the whole fastify web server module tree unless needed
88
89
  // Use variable to avoid compile-time type dependency on @naisys/supervisor (allows parallel builds)
89
90
  const supervisorModule = "@naisys/supervisor";
90
- const { supervisorPlugin } = (await import(supervisorModule));
91
- const superAdminPassword = wizardRan
92
- ? await promptSuperAdminPassword("Supervisor Setup")
93
- : undefined;
91
+ const { supervisorPlugin, bootstrapSupervisor } = (await import(supervisorModule));
92
+ const resetSuperAdminPasskey = wizardRan
93
+ ? await promptResetSuperAdminPasskey("Supervisor Setup", {
94
+ defaultReset: !process.argv.includes("--setup"),
95
+ })
96
+ : false;
97
+ // Bootstrap before plugin register so the operator prompt isn't bounded by pluginTimeout and doesn't interleave with hub connection logs.
98
+ await bootstrapSupervisor({ resetSuperAdminPasskey });
94
99
  await fastify.register(supervisorPlugin, {
95
100
  plugins,
96
101
  serverPort,
97
102
  hosted: true,
98
- superAdminPassword,
99
103
  });
100
104
  }
101
105
  // Start listening
@@ -104,6 +108,21 @@ export const startHub = async (startupType, startSupervisor, plugins, startupAge
104
108
  if (startupType === "hosted") {
105
109
  logService.disableConsole();
106
110
  }
111
+ let shutdownPromise = null;
112
+ async function runShutdown() {
113
+ try {
114
+ heartbeatService.cleanup();
115
+ await io.close();
116
+ await fastify.close();
117
+ }
118
+ finally {
119
+ await hubDatabaseService.disconnect();
120
+ }
121
+ }
122
+ const shutdown = () => {
123
+ shutdownPromise ??= runShutdown();
124
+ return shutdownPromise;
125
+ };
107
126
  // Hosted mode: parent process owns signal handling
108
127
  if (startupType === "standalone") {
109
128
  let shuttingDown = false;
@@ -115,8 +134,7 @@ export const startHub = async (startupType, startSupervisor, plugins, startupAge
115
134
  shuttingDown = true;
116
135
  logService.log(`[Hub] Shutting down (${signal})...`);
117
136
  try {
118
- await io.close();
119
- await fastify.close();
137
+ await shutdown();
120
138
  }
121
139
  catch (err) {
122
140
  console.error("[Hub] Error during shutdown:", err);
@@ -126,7 +144,7 @@ export const startHub = async (startupType, startSupervisor, plugins, startupAge
126
144
  process.on("SIGTERM", () => void handleShutdown("SIGTERM"));
127
145
  process.on("SIGINT", () => void handleShutdown("SIGINT"));
128
146
  }
129
- return { serverPort };
147
+ return { serverPort, shutdown };
130
148
  }
131
149
  catch (err) {
132
150
  console.error("[Hub] Failed to start hub server:", err);
@@ -1,6 +1,6 @@
1
1
  import { adminAgentConfig, toUrlSafeKey } from "@naisys/common";
2
2
  import { loadAgentConfigs } from "@naisys/common-node";
3
- import { randomBytes, randomUUID } from "crypto";
3
+ import { randomUUID } from "crypto";
4
4
  /** Seeds agent configs from YAML files into an empty database. Skips if users already exist. */
5
5
  export async function seedAgentConfigs({ hubDb }, logService, startupAgentPath) {
6
6
  // Check if users table already has rows (seed-once pattern)
@@ -38,7 +38,6 @@ async function seedUsersToDatabase(hubDb, logService, users) {
38
38
  username: safeUsername,
39
39
  title: user.config.title,
40
40
  config: JSON.stringify({ ...user.config, username: safeUsername }),
41
- api_key: randomBytes(32).toString("hex"),
42
41
  },
43
42
  });
44
43
  loaderIdToDbId.set(user.userId, dbUser.id);
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "@naisys/hub",
3
- "version": "3.0.0-beta.36",
3
+ "version": "3.0.0-beta.38",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@naisys/hub",
9
- "version": "3.0.0-beta.36",
9
+ "version": "3.0.0-beta.38",
10
10
  "dependencies": {
11
- "@naisys/common": "3.0.0-beta.36",
12
- "@naisys/common-node": "3.0.0-beta.36",
13
- "@naisys/hub-database": "3.0.0-beta.36",
14
- "@naisys/hub-protocol": "3.0.0-beta.36",
11
+ "@naisys/common": "3.0.0-beta.38",
12
+ "@naisys/common-node": "3.0.0-beta.38",
13
+ "@naisys/hub-database": "3.0.0-beta.38",
14
+ "@naisys/hub-protocol": "3.0.0-beta.38",
15
15
  "commander": "^14.0.3",
16
16
  "dotenv": "^17.3.1",
17
17
  "fastify": "^5.8.2",
@@ -24,7 +24,7 @@
24
24
  "node": ">=22.0.0"
25
25
  },
26
26
  "peerDependencies": {
27
- "@naisys/supervisor": "3.0.0-beta.36"
27
+ "@naisys/supervisor": "3.0.0-beta.38"
28
28
  },
29
29
  "peerDependenciesMeta": {
30
30
  "@naisys/supervisor": {
@@ -189,32 +189,32 @@
189
189
  "license": "MIT"
190
190
  },
191
191
  "node_modules/@naisys/common": {
192
- "version": "3.0.0-beta.36",
193
- "resolved": "https://registry.npmjs.org/@naisys/common/-/common-3.0.0-beta.36.tgz",
194
- "integrity": "sha512-a5EYqKvnL1Uy1pdslLZVMQERbo3ZtnMvhKa5B4GQJ/ziwM5zgMfbUFUEDniJRnta4dtIU5GxgzxBxmin+gMbhw==",
192
+ "version": "3.0.0-beta.38",
193
+ "resolved": "https://registry.npmjs.org/@naisys/common/-/common-3.0.0-beta.38.tgz",
194
+ "integrity": "sha512-YJIawcV12XNgzQz/QcM2oAYhu4O4V55tHm0JZrEdmBt2GS33VsGIVJqhDtg3sVWvtsDfh8QkK7EjwPEvgj7fzA==",
195
195
  "dependencies": {
196
196
  "semver": "^7.7.4",
197
197
  "zod": "^4.3.6"
198
198
  }
199
199
  },
200
200
  "node_modules/@naisys/common-node": {
201
- "version": "3.0.0-beta.36",
202
- "resolved": "https://registry.npmjs.org/@naisys/common-node/-/common-node-3.0.0-beta.36.tgz",
203
- "integrity": "sha512-pgzHJuh6OH6wiNU4PRV8sSNaBqWCYateD7+tWHnwvDwVOzwce8ttLNtsK+NQzwQqp+vjPAMpfJ9yMqoOttXgbA==",
201
+ "version": "3.0.0-beta.38",
202
+ "resolved": "https://registry.npmjs.org/@naisys/common-node/-/common-node-3.0.0-beta.38.tgz",
203
+ "integrity": "sha512-dNVvY+gWzBx4I+3chvNl74MgrcxVBFpPB+0acYSl7bcINvhqmbqLMYryR4AwOvU+dc09GDbpgugE6EWIQnn/zA==",
204
204
  "dependencies": {
205
- "@naisys/common": "3.0.0-beta.36",
205
+ "@naisys/common": "3.0.0-beta.38",
206
206
  "better-sqlite3": "^12.6.2",
207
207
  "js-yaml": "^4.1.1",
208
208
  "pino": "^10.3.1"
209
209
  }
210
210
  },
211
211
  "node_modules/@naisys/hub-database": {
212
- "version": "3.0.0-beta.36",
213
- "resolved": "https://registry.npmjs.org/@naisys/hub-database/-/hub-database-3.0.0-beta.36.tgz",
214
- "integrity": "sha512-FiJJCaOARun5jbTwX2VwXgEy5xDqlMi7X1ZSpxSn0jMv3dzniupCYYAYyO4muwXdFLNO2vMnhUdmhK/m2k7ouw==",
212
+ "version": "3.0.0-beta.38",
213
+ "resolved": "https://registry.npmjs.org/@naisys/hub-database/-/hub-database-3.0.0-beta.38.tgz",
214
+ "integrity": "sha512-h+e50Qjg389ewSKU2jAisexlmajEAK34lMz6FKWd8Hog/PI6v3BYKB/8wgL2HmI78apmHIjh1FRW3NQNF+D8rw==",
215
215
  "dependencies": {
216
- "@naisys/common": "3.0.0-beta.36",
217
- "@naisys/common-node": "3.0.0-beta.36",
216
+ "@naisys/common": "3.0.0-beta.38",
217
+ "@naisys/common-node": "3.0.0-beta.38",
218
218
  "@prisma/adapter-better-sqlite3": "^7.5.0",
219
219
  "@prisma/client": "^7.5.0",
220
220
  "better-sqlite3": "^12.6.2",
@@ -222,11 +222,11 @@
222
222
  }
223
223
  },
224
224
  "node_modules/@naisys/hub-protocol": {
225
- "version": "3.0.0-beta.36",
226
- "resolved": "https://registry.npmjs.org/@naisys/hub-protocol/-/hub-protocol-3.0.0-beta.36.tgz",
227
- "integrity": "sha512-JCxS982SjYUws3vg3O5XgoeoC0Pllhm7qLdqXTJpZY9v+SA5VGvu5/xvOURspNuIbpRayQkUx7cakTmQUQFKrw==",
225
+ "version": "3.0.0-beta.38",
226
+ "resolved": "https://registry.npmjs.org/@naisys/hub-protocol/-/hub-protocol-3.0.0-beta.38.tgz",
227
+ "integrity": "sha512-HLNcDmbdQVDeOxQlohoSrQdz/EXvNKx16BZVEovlwixuGCfINgweOkjtr4qypHO0peHPD0p1ayBa2QAjRrSY5Q==",
228
228
  "dependencies": {
229
- "@naisys/common": "3.0.0-beta.36",
229
+ "@naisys/common": "3.0.0-beta.38",
230
230
  "zod": "^4.3.6"
231
231
  }
232
232
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naisys/hub",
3
- "version": "3.0.0-beta.36",
3
+ "version": "3.0.0-beta.38",
4
4
  "description": "NAISYS Hub - Adds persistence and multi-instance coordination to NAISYS",
5
5
  "type": "module",
6
6
  "main": "dist/naisysHub.js",
@@ -31,7 +31,7 @@
31
31
  "!dist/**/*.d.ts.map"
32
32
  ],
33
33
  "peerDependencies": {
34
- "@naisys/supervisor": "3.0.0-beta.36"
34
+ "@naisys/supervisor": "3.0.0-beta.38"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@naisys/supervisor": {
@@ -39,10 +39,10 @@
39
39
  }
40
40
  },
41
41
  "dependencies": {
42
- "@naisys/common": "3.0.0-beta.36",
43
- "@naisys/common-node": "3.0.0-beta.36",
44
- "@naisys/hub-database": "3.0.0-beta.36",
45
- "@naisys/hub-protocol": "3.0.0-beta.36",
42
+ "@naisys/common": "3.0.0-beta.38",
43
+ "@naisys/common-node": "3.0.0-beta.38",
44
+ "@naisys/hub-database": "3.0.0-beta.38",
45
+ "@naisys/hub-protocol": "3.0.0-beta.38",
46
46
  "commander": "^14.0.3",
47
47
  "dotenv": "^17.3.1",
48
48
  "fastify": "^5.8.2",