@inkeep/agents-core 0.50.0 → 0.50.3

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.
Files changed (60) hide show
  1. package/dist/auth/auth-schema.d.ts +83 -83
  2. package/dist/auth/auth-validation-schemas.d.ts +148 -148
  3. package/dist/auth/auth.d.ts +20 -20
  4. package/dist/auth/auth.js +8 -9
  5. package/dist/auth/authz/sync.d.ts +22 -1
  6. package/dist/auth/authz/sync.js +59 -4
  7. package/dist/auth/permissions.d.ts +9 -9
  8. package/dist/client-exports.d.ts +11 -11
  9. package/dist/constants/models.d.ts +1 -0
  10. package/dist/constants/models.js +1 -0
  11. package/dist/constants/otel-attributes.d.ts +4 -0
  12. package/dist/constants/otel-attributes.js +4 -0
  13. package/dist/data-access/manage/agents.d.ts +46 -46
  14. package/dist/data-access/manage/agents.js +8 -6
  15. package/dist/data-access/manage/artifactComponents.d.ts +4 -4
  16. package/dist/data-access/manage/artifactComponents.js +3 -3
  17. package/dist/data-access/manage/contextConfigs.d.ts +8 -8
  18. package/dist/data-access/manage/dataComponents.js +2 -2
  19. package/dist/data-access/manage/functionTools.d.ts +8 -8
  20. package/dist/data-access/manage/scope-helpers.d.ts +25 -0
  21. package/dist/data-access/manage/scope-helpers.js +18 -0
  22. package/dist/data-access/manage/skills.d.ts +11 -11
  23. package/dist/data-access/manage/subAgentExternalAgentRelations.d.ts +6 -6
  24. package/dist/data-access/manage/subAgentExternalAgentRelations.js +13 -12
  25. package/dist/data-access/manage/subAgentRelations.d.ts +2 -2
  26. package/dist/data-access/manage/subAgentRelations.js +2 -2
  27. package/dist/data-access/manage/subAgents.d.ts +27 -27
  28. package/dist/data-access/manage/tools.d.ts +9 -9
  29. package/dist/data-access/manage/triggers.d.ts +2 -2
  30. package/dist/data-access/runtime/apiKeys.d.ts +8 -8
  31. package/dist/data-access/runtime/conversations.d.ts +27 -27
  32. package/dist/data-access/runtime/messages.d.ts +15 -15
  33. package/dist/data-access/runtime/scheduledTriggerInvocations.d.ts +3 -3
  34. package/dist/data-access/runtime/tasks.d.ts +7 -7
  35. package/dist/db/manage/manage-schema.d.ts +96 -96
  36. package/dist/db/runtime/runtime-schema.d.ts +38 -38
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.js +2 -1
  39. package/dist/middleware/authz-meta.d.ts +15 -0
  40. package/dist/middleware/authz-meta.js +11 -0
  41. package/dist/middleware/create-protected-route.d.ts +30 -0
  42. package/dist/middleware/create-protected-route.js +20 -0
  43. package/dist/middleware/index.d.ts +5 -0
  44. package/dist/middleware/index.js +6 -0
  45. package/dist/middleware/inherited-auth.d.ts +43 -0
  46. package/dist/middleware/inherited-auth.js +50 -0
  47. package/dist/middleware/no-auth.d.ts +6 -0
  48. package/dist/middleware/no-auth.js +11 -0
  49. package/dist/setup/index.d.ts +2 -0
  50. package/dist/setup/index.js +3 -0
  51. package/dist/setup/setup.d.ts +24 -0
  52. package/dist/setup/setup.js +506 -0
  53. package/dist/utils/in-process-fetch.d.ts +30 -0
  54. package/dist/utils/in-process-fetch.js +51 -0
  55. package/dist/utils/index.d.ts +2 -1
  56. package/dist/utils/index.js +2 -1
  57. package/dist/validation/dolt-schemas.d.ts +1 -1
  58. package/dist/validation/drizzle-schema-helpers.d.ts +3 -3
  59. package/dist/validation/schemas.d.ts +2065 -2065
  60. package/package.json +9 -1
@@ -0,0 +1,50 @@
1
+ import { registerAuthzMeta } from "./authz-meta.js";
2
+ import { createMiddleware } from "hono/factory";
3
+
4
+ //#region src/middleware/inherited-auth.ts
5
+ /**
6
+ * Documentation-only permission marker for routes whose authentication
7
+ * is already enforced by a parent `app.use()` in createApp.ts.
8
+ *
9
+ * This does NOT perform any auth check itself — it only registers
10
+ * x-authz metadata so the OpenAPI spec accurately reflects the
11
+ * auth requirement.
12
+ *
13
+ * Use this when the route lives under a path that already has
14
+ * middleware applied (e.g. `/manage/tenants/*` has `manageApiKeyOrSessionAuth`).
15
+ */
16
+ const inheritedAuth = (meta) => {
17
+ const mw = createMiddleware(async (_c, next) => {
18
+ await next();
19
+ });
20
+ registerAuthzMeta(mw, meta);
21
+ return mw;
22
+ };
23
+ /**
24
+ * Marker for routes under `/manage/tenants/*` whose auth is handled
25
+ * by `manageApiKeyOrSessionAuth()` in createApp.ts.
26
+ *
27
+ * No auth check runs at the route level — this is purely for OpenAPI documentation.
28
+ */
29
+ const inheritedManageTenantAuth = () => inheritedAuth({
30
+ resource: "organization",
31
+ permission: "member",
32
+ description: "Requires organization membership. Auth is enforced by the manageApiKeyOrSessionAuth middleware in createApp.ts."
33
+ });
34
+ /**
35
+ * Marker for routes under `/run/*` whose auth is handled
36
+ * by `runApiKeyAuth()` in createApp.ts.
37
+ *
38
+ * No auth check runs at the route level — this is purely for OpenAPI documentation.
39
+ */
40
+ const inheritedRunApiKeyAuth = () => inheritedAuth({ description: "Requires a valid API key (Bearer token). Auth is enforced by runApiKeyAuth middleware in createApp.ts." });
41
+ /**
42
+ * Marker for routes under `/work-apps/*` whose auth is handled
43
+ * by `workAppsAuth()` in createApp.ts.
44
+ *
45
+ * No auth check runs at the route level — this is purely for OpenAPI documentation.
46
+ */
47
+ const inheritedWorkAppsAuth = () => inheritedAuth({ description: "Requires work-apps authentication (OIDC token or Slack signature). Auth is enforced by workAppsAuth middleware in createApp.ts." });
48
+
49
+ //#endregion
50
+ export { inheritedAuth, inheritedManageTenantAuth, inheritedRunApiKeyAuth, inheritedWorkAppsAuth };
@@ -0,0 +1,6 @@
1
+ import * as hono0 from "hono";
2
+
3
+ //#region src/middleware/no-auth.d.ts
4
+ declare const noAuth: () => hono0.MiddlewareHandler<any, string, {}, Response>;
5
+ //#endregion
6
+ export { noAuth };
@@ -0,0 +1,11 @@
1
+ import { createMiddleware } from "hono/factory";
2
+
3
+ //#region src/middleware/no-auth.ts
4
+ const noAuth = () => {
5
+ return createMiddleware(async (_c, next) => {
6
+ await next();
7
+ });
8
+ };
9
+
10
+ //#endregion
11
+ export { noAuth };
@@ -0,0 +1,2 @@
1
+ import { SetupConfig, SetupPushConfig, runSetup } from "./setup.js";
2
+ export { type SetupConfig, type SetupPushConfig, runSetup };
@@ -0,0 +1,3 @@
1
+ import { runSetup } from "./setup.js";
2
+
3
+ export { runSetup };
@@ -0,0 +1,24 @@
1
+ //#region src/setup/setup.d.ts
2
+ interface SetupPushConfig {
3
+ projectPath: string;
4
+ configPath: string;
5
+ apiKey?: string;
6
+ }
7
+ interface SetupConfig {
8
+ dockerComposeFile: string;
9
+ manageMigrateCommand: string;
10
+ runMigrateCommand: string;
11
+ authInitCommand: string;
12
+ pushProject?: SetupPushConfig;
13
+ devApiCommand?: string;
14
+ devUiCommand?: string;
15
+ apiHealthUrl?: string;
16
+ uiHealthUrl?: string;
17
+ isCloud?: boolean;
18
+ skipPush?: boolean;
19
+ /** If set, upgrades packages instead of just migrating on subsequent runs */
20
+ upgradeCommand?: string;
21
+ }
22
+ declare function runSetup(config: SetupConfig): Promise<void>;
23
+ //#endregion
24
+ export { SetupConfig, SetupPushConfig, runSetup };
@@ -0,0 +1,506 @@
1
+ import { loadEnvironmentFiles } from "../env.js";
2
+ import { copyFileSync, existsSync, writeFileSync } from "node:fs";
3
+ import dotenv from "dotenv";
4
+ import { generateKeyPairSync } from "node:crypto";
5
+ import { promisify } from "node:util";
6
+ import { exec, spawn } from "node:child_process";
7
+ import { readFile, writeFile } from "node:fs/promises";
8
+
9
+ //#region src/setup/setup.ts
10
+ const colors = {
11
+ reset: "\x1B[0m",
12
+ bright: "\x1B[1m",
13
+ dim: "\x1B[2m",
14
+ green: "\x1B[32m",
15
+ yellow: "\x1B[33m",
16
+ red: "\x1B[31m",
17
+ blue: "\x1B[34m",
18
+ cyan: "\x1B[36m"
19
+ };
20
+ function logStep(step, message) {
21
+ console.log(`${colors.bright}${colors.blue}[Step ${step}]${colors.reset} ${message}`);
22
+ }
23
+ function logSuccess(message) {
24
+ console.log(`${colors.green}✓${colors.reset} ${message}`);
25
+ }
26
+ function logError(message, error) {
27
+ console.error(`${colors.red}✗ ${message}${colors.reset}`);
28
+ if (error) {
29
+ const msg = error instanceof Error ? error.message : String(error);
30
+ console.error(`${colors.dim} Error details: ${msg}${colors.reset}`);
31
+ }
32
+ }
33
+ function logWarning(message) {
34
+ console.warn(`${colors.yellow}⚠${colors.reset} ${message}`);
35
+ }
36
+ function logInfo(message) {
37
+ console.log(`${colors.cyan}ℹ${colors.reset} ${message}`);
38
+ }
39
+ const execAsync = promisify(exec);
40
+ function isLocalDatabaseUrl(url) {
41
+ if (!url) return false;
42
+ return /[@/](localhost|127\.0\.0\.1)[:/]/.test(url);
43
+ }
44
+ function hasExternalDatabases() {
45
+ const manageUrl = process.env.INKEEP_AGENTS_MANAGE_DATABASE_URL;
46
+ const runUrl = process.env.INKEEP_AGENTS_RUN_DATABASE_URL;
47
+ if (!manageUrl || !runUrl) return false;
48
+ if (process.env.CI) return true;
49
+ return !isLocalDatabaseUrl(manageUrl) || !isLocalDatabaseUrl(runUrl);
50
+ }
51
+ const REQUIRED_PORTS = [
52
+ {
53
+ port: 5432,
54
+ service: "DoltgreSQL"
55
+ },
56
+ {
57
+ port: 5433,
58
+ service: "PostgreSQL"
59
+ },
60
+ {
61
+ port: 5434,
62
+ service: "SpiceDB PostgreSQL"
63
+ }
64
+ ];
65
+ async function checkPort(port) {
66
+ try {
67
+ await execAsync(`lsof -i :${port} -sTCP:LISTEN -t`, { timeout: 5e3 });
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+ async function getContainerOnPort(port) {
74
+ try {
75
+ const { stdout } = await execAsync(`docker ps --format '{{.Names}}' --filter "publish=${port}"`, { timeout: 5e3 });
76
+ const name = stdout.trim();
77
+ if (!name) return null;
78
+ let project = null;
79
+ try {
80
+ const { stdout: labelOut } = await execAsync(`docker inspect ${name} --format '{{index .Config.Labels "com.docker.compose.project"}}'`, { timeout: 5e3 });
81
+ project = labelOut.trim() || null;
82
+ } catch {}
83
+ return {
84
+ name,
85
+ project
86
+ };
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+ async function runPreflightChecks(composeFile) {
92
+ const issues = [];
93
+ const fixes = [];
94
+ try {
95
+ await execAsync("docker info", { timeout: 1e4 });
96
+ } catch {
97
+ if (hasExternalDatabases()) {
98
+ logWarning("Docker is not available, but database URLs point to external hosts");
99
+ logInfo("Assuming databases are managed externally.");
100
+ return {
101
+ passed: true,
102
+ dockerAvailable: false,
103
+ portsAvailable: false,
104
+ composeCmd: ""
105
+ };
106
+ }
107
+ logError("Docker is not running or not installed.");
108
+ logInfo(" Start Docker Desktop, or install Docker: https://docs.docker.com/get-docker/");
109
+ process.exit(1);
110
+ }
111
+ let composeCmd = "docker-compose";
112
+ try {
113
+ await execAsync("docker-compose version", { timeout: 5e3 });
114
+ } catch {
115
+ try {
116
+ await execAsync("docker compose version", { timeout: 5e3 });
117
+ composeCmd = "docker compose";
118
+ } catch {
119
+ if (hasExternalDatabases()) {
120
+ logWarning("Docker Compose not available, but database URLs point to external hosts");
121
+ logInfo("Assuming databases are managed externally.");
122
+ return {
123
+ passed: true,
124
+ dockerAvailable: true,
125
+ portsAvailable: false,
126
+ composeCmd
127
+ };
128
+ }
129
+ logError("Docker Compose is not installed.");
130
+ logInfo(" Install it: https://docs.docker.com/compose/install/");
131
+ process.exit(1);
132
+ }
133
+ }
134
+ let ownProjectPrefix;
135
+ try {
136
+ const { stdout } = await execAsync(`${composeCmd} -f ${composeFile} config --format json`, { timeout: 1e4 });
137
+ ownProjectPrefix = (JSON.parse(stdout).name || "").replace(/[^a-z0-9]/g, "");
138
+ } catch {
139
+ ownProjectPrefix = (process.cwd().split("/").pop() || "").replace(/[^a-z0-9]/g, "").toLowerCase();
140
+ }
141
+ const occupiedPorts = [];
142
+ await Promise.all(REQUIRED_PORTS.map(async ({ port, service }) => {
143
+ if (!await checkPort(port)) return;
144
+ const info = await getContainerOnPort(port);
145
+ if (!info) return;
146
+ if (info.name.replace(/[^a-z0-9]/g, "").toLowerCase().startsWith(ownProjectPrefix)) return;
147
+ occupiedPorts.push({
148
+ port,
149
+ service,
150
+ container: info.name,
151
+ project: info.project
152
+ });
153
+ }));
154
+ if (occupiedPorts.length > 0) {
155
+ issues.push(`Port${occupiedPorts.length > 1 ? "s" : ""} occupied by another Docker project:`);
156
+ for (const { port, service, container } of occupiedPorts) issues.push(` :${port} (${service}) → ${container}`);
157
+ const projectNames = new Set(occupiedPorts.map(({ project }) => project).filter((p) => p !== null));
158
+ for (const project of projectNames) fixes.push(` docker compose -p ${project} down`);
159
+ const standaloneContainers = occupiedPorts.filter(({ project }) => project === null).map(({ container }) => container);
160
+ for (const name of standaloneContainers) fixes.push(` docker rm -f ${name}`);
161
+ }
162
+ if (issues.length > 0) {
163
+ logWarning("Pre-flight check: port conflicts detected");
164
+ for (const issue of issues) console.log(`${colors.yellow} ${issue}${colors.reset}`);
165
+ console.log(`${colors.cyan} Fix:${colors.reset}`);
166
+ for (const fix of fixes) console.log(`${colors.cyan} ${fix}${colors.reset}`);
167
+ console.log();
168
+ logInfo("Databases on those ports will be used as-is. Continuing with setup...");
169
+ return {
170
+ passed: true,
171
+ dockerAvailable: true,
172
+ portsAvailable: false,
173
+ composeCmd
174
+ };
175
+ }
176
+ return {
177
+ passed: true,
178
+ dockerAvailable: true,
179
+ portsAvailable: true,
180
+ composeCmd
181
+ };
182
+ }
183
+ const SETUP_COMPLETE_FILE = ".setup-complete";
184
+ async function ensureEnvFile() {
185
+ if (!existsSync(".env") && existsSync(".env.example")) {
186
+ copyFileSync(".env.example", ".env");
187
+ logSuccess("Created .env from .env.example");
188
+ }
189
+ }
190
+ async function generateJwtKeys() {
191
+ const envContent = await readFile(".env", "utf-8").catch(() => "");
192
+ if (envContent.includes("INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=") && !envContent.includes("# INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=")) {
193
+ const match = envContent.match(/INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=(.+)/);
194
+ if (match && match[1].trim().length > 0) {
195
+ logInfo("JWT keys already configured, skipping generation");
196
+ return;
197
+ }
198
+ }
199
+ try {
200
+ const { privateKey, publicKey } = generateKeyPairSync("rsa", {
201
+ modulusLength: 2048,
202
+ privateKeyEncoding: {
203
+ type: "pkcs8",
204
+ format: "pem"
205
+ },
206
+ publicKeyEncoding: {
207
+ type: "spki",
208
+ format: "pem"
209
+ }
210
+ });
211
+ const privateKeyBase64 = Buffer.from(privateKey).toString("base64");
212
+ const publicKeyBase64 = Buffer.from(publicKey).toString("base64");
213
+ const lines = envContent.split("\n");
214
+ let privateKeyFound = false;
215
+ let publicKeyFound = false;
216
+ for (let i = 0; i < lines.length; i++) {
217
+ if (lines[i].startsWith("# INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=") || lines[i].startsWith("INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=")) {
218
+ lines[i] = `INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=${privateKeyBase64}`;
219
+ privateKeyFound = true;
220
+ }
221
+ if (lines[i].startsWith("# INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=") || lines[i].startsWith("INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=")) {
222
+ lines[i] = `INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=${publicKeyBase64}`;
223
+ publicKeyFound = true;
224
+ }
225
+ }
226
+ if (!privateKeyFound) lines.push(`INKEEP_AGENTS_TEMP_JWT_PRIVATE_KEY=${privateKeyBase64}`);
227
+ if (!publicKeyFound) lines.push(`INKEEP_AGENTS_TEMP_JWT_PUBLIC_KEY=${publicKeyBase64}`);
228
+ await writeFile(".env", lines.join("\n"));
229
+ logSuccess("JWT keys generated and added to .env");
230
+ } catch (error) {
231
+ logError("Failed to generate JWT keys - playground may not work", error);
232
+ logInfo("You can manually run: pnpm run generate-jwt-keys");
233
+ }
234
+ }
235
+ async function waitForDockerHealth(composeFile, serviceName, composeCmd, timeout = 3e4) {
236
+ const start = Date.now();
237
+ let lastError = null;
238
+ while (Date.now() - start < timeout) {
239
+ try {
240
+ const { stdout } = await execAsync(`docker inspect --format='{{.State.Health.Status}}' $(${composeCmd} -f ${composeFile} ps -q ${serviceName})`);
241
+ if (stdout.trim() === "healthy") return;
242
+ } catch (error) {
243
+ lastError = error;
244
+ }
245
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
246
+ }
247
+ const errorMsg = lastError instanceof Error ? lastError.message : String(lastError);
248
+ throw new Error(`${serviceName} not healthy after ${timeout}ms${lastError ? ` (last error: ${errorMsg})` : ""}`);
249
+ }
250
+ async function startDockerDatabases(composeFile, composeCmd) {
251
+ logInfo("Starting database containers...");
252
+ try {
253
+ await execAsync(`${composeCmd} -f ${composeFile} up -d`, { timeout: 12e4 });
254
+ logSuccess("Database containers started");
255
+ } catch (error) {
256
+ const combined = `${error instanceof Error ? error.message : String(error)}\n${error?.stderr || ""}`;
257
+ const isTimeout = error?.killed === true;
258
+ const isPortConflict = !isTimeout && (combined.includes("port is already allocated") || combined.includes("address already in use") || combined.includes("Bind for 0.0.0.0"));
259
+ if (isTimeout) {
260
+ logWarning("docker compose up timed out — containers may still be starting");
261
+ logInfo("Will check health status below...");
262
+ } else if (isPortConflict) {
263
+ logWarning("Database port conflict during startup (databases might already be running)");
264
+ logInfo("Continuing with setup...");
265
+ return;
266
+ } else {
267
+ logError("Failed to start database containers", error);
268
+ process.exit(1);
269
+ }
270
+ }
271
+ logInfo("Polling Docker health status...");
272
+ const [doltgresResult, postgresResult, spicedbResult] = await Promise.allSettled([
273
+ waitForDockerHealth(composeFile, "doltgres-db", composeCmd, 6e4),
274
+ waitForDockerHealth(composeFile, "postgres-db", composeCmd, 3e4),
275
+ waitForDockerHealth(composeFile, "spicedb-postgres", composeCmd, 3e4)
276
+ ]);
277
+ if (doltgresResult.status === "fulfilled") logSuccess("DoltgreSQL is healthy");
278
+ else logWarning(`DoltgreSQL health check timed out: ${doltgresResult.reason.message}`);
279
+ if (postgresResult.status === "fulfilled") logSuccess("PostgreSQL is healthy");
280
+ else logWarning(`PostgreSQL health check timed out: ${postgresResult.reason.message}`);
281
+ if (spicedbResult.status === "fulfilled") logSuccess("SpiceDB PostgreSQL is healthy");
282
+ else logWarning(`SpiceDB PostgreSQL health check timed out: ${spicedbResult.reason.message}`);
283
+ if (doltgresResult.status === "rejected" && postgresResult.status === "rejected") {
284
+ logError("Both primary databases failed health checks - cannot proceed");
285
+ process.exit(1);
286
+ }
287
+ logSuccess("Database health checks complete");
288
+ }
289
+ async function runMigrations(config) {
290
+ const isFirstRun = !existsSync(SETUP_COMPLETE_FILE);
291
+ if (config.upgradeCommand && !isFirstRun && process.env.SKIP_UPGRADE !== "true") {
292
+ logInfo("Running database migrations and upgrading packages");
293
+ try {
294
+ const { stdout } = await execAsync(config.upgradeCommand);
295
+ if (stdout) console.log(`${colors.dim} ${stdout.trim()}${colors.reset}`);
296
+ logSuccess("Upgrades completed");
297
+ } catch (error) {
298
+ logError("Failed to run upgrades", error);
299
+ logWarning("This may cause issues. Consider checking your database schema.");
300
+ }
301
+ return;
302
+ }
303
+ logInfo(isFirstRun ? "Fresh install detected - running migrations only" : "Running migrations...");
304
+ const [manageResult, runResult] = await Promise.allSettled([execAsync(config.manageMigrateCommand), execAsync(config.runMigrateCommand)]);
305
+ if (manageResult.status === "fulfilled") logSuccess("Manage database migrations completed");
306
+ else logWarning(`Manage migrations failed: ${manageResult.reason.message}`);
307
+ if (runResult.status === "fulfilled") logSuccess("Runtime database migrations completed");
308
+ else logWarning(`Runtime migrations failed: ${runResult.reason.message}`);
309
+ if (manageResult.status === "rejected" && runResult.status === "rejected") {
310
+ logError("Both database migrations failed");
311
+ process.exit(1);
312
+ }
313
+ if (manageResult.status === "fulfilled" && runResult.status === "fulfilled") writeFileSync(SETUP_COMPLETE_FILE, (/* @__PURE__ */ new Date()).toISOString());
314
+ else logWarning(`Partial migration success — ${SETUP_COMPLETE_FILE} not written so next run retries`);
315
+ }
316
+ async function initAuth(authInitCommand) {
317
+ if (process.env.INKEEP_AGENTS_MANAGE_UI_USERNAME && process.env.INKEEP_AGENTS_MANAGE_UI_PASSWORD && process.env.BETTER_AUTH_SECRET) {
318
+ logInfo("Initializing default organization and admin user...");
319
+ try {
320
+ await execAsync(authInitCommand);
321
+ logSuccess("Auth initialization complete");
322
+ } catch (error) {
323
+ logWarning(`Auth initialization failed - you may need to run manually: ${authInitCommand}`);
324
+ logInfo(`Error: ${error instanceof Error ? error.message : String(error)}`);
325
+ }
326
+ } else {
327
+ logWarning("Skipping auth initialization - credentials not configured");
328
+ logInfo("To create a default admin user, set in .env:");
329
+ logInfo(" INKEEP_AGENTS_MANAGE_UI_USERNAME=admin@example.com");
330
+ logInfo(" INKEEP_AGENTS_MANAGE_UI_PASSWORD=your-password");
331
+ logInfo(" BETTER_AUTH_SECRET=your-secret-key");
332
+ logInfo(`Then run: ${authInitCommand}`);
333
+ }
334
+ }
335
+ async function checkServerRunning(url, timeout = 5e3) {
336
+ try {
337
+ const response = await fetch(url, { signal: AbortSignal.timeout(timeout) });
338
+ return response.ok || response.status === 204;
339
+ } catch {
340
+ return false;
341
+ }
342
+ }
343
+ async function waitForServerReady(url, timeout = 18e4) {
344
+ const start = Date.now();
345
+ let lastError = null;
346
+ while (Date.now() - start < timeout) {
347
+ try {
348
+ const response = await fetch(url, { signal: AbortSignal.timeout(5e3) });
349
+ if (response.ok || response.status === 204) return;
350
+ lastError = `HTTP ${response.status}`;
351
+ } catch (error) {
352
+ if (error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError")) lastError = "fetch timeout (>5s per attempt)";
353
+ else lastError = error instanceof Error ? error.message : String(error);
354
+ }
355
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
356
+ }
357
+ throw new Error(`Server not ready at ${url} after ${timeout}ms. Last error: ${lastError}`);
358
+ }
359
+ async function startServersIfNeeded(config) {
360
+ const result = {
361
+ startedApi: false,
362
+ startedUi: false,
363
+ apiPid: null,
364
+ uiPid: null
365
+ };
366
+ if (!config.apiHealthUrl) return result;
367
+ if (await checkServerRunning(config.apiHealthUrl)) logSuccess("API server already running");
368
+ else if (config.devApiCommand) {
369
+ logInfo("Starting API server temporarily...");
370
+ const proc = spawn("sh", ["-c", config.devApiCommand], {
371
+ stdio: "ignore",
372
+ detached: true,
373
+ cwd: process.cwd()
374
+ });
375
+ proc.unref();
376
+ result.startedApi = true;
377
+ result.apiPid = proc.pid ?? null;
378
+ logSuccess(`API server process started (PID: ${proc.pid})`);
379
+ }
380
+ if (config.uiHealthUrl) {
381
+ if (await checkServerRunning(config.uiHealthUrl)) logSuccess("Dashboard already running");
382
+ else if (config.devUiCommand) {
383
+ logInfo("Starting Dashboard temporarily...");
384
+ const proc = spawn("sh", ["-c", config.devUiCommand], {
385
+ stdio: "ignore",
386
+ detached: true,
387
+ cwd: process.cwd()
388
+ });
389
+ proc.unref();
390
+ result.startedUi = true;
391
+ result.uiPid = proc.pid ?? null;
392
+ logSuccess(`Dashboard process started (PID: ${proc.pid})`);
393
+ }
394
+ }
395
+ const waitPromises = [];
396
+ if (result.startedApi && config.apiHealthUrl) {
397
+ logInfo(`Waiting for API at ${config.apiHealthUrl}...`);
398
+ waitPromises.push(waitForServerReady(config.apiHealthUrl));
399
+ }
400
+ if (result.startedUi && config.uiHealthUrl) {
401
+ logInfo(`Waiting for Dashboard at ${config.uiHealthUrl}...`);
402
+ waitPromises.push(waitForServerReady(config.uiHealthUrl));
403
+ }
404
+ if (waitPromises.length > 0) {
405
+ const results = await Promise.allSettled(waitPromises);
406
+ for (const r of results) if (r.status === "rejected") logWarning(`Server readiness check failed: ${r.reason.message}`);
407
+ }
408
+ return result;
409
+ }
410
+ function stopProcessGroup(pid, name) {
411
+ try {
412
+ process.kill(-pid, "SIGTERM");
413
+ } catch {}
414
+ try {
415
+ process.kill(pid, "SIGTERM");
416
+ } catch {}
417
+ setTimeout(() => {
418
+ try {
419
+ process.kill(-pid, "SIGKILL");
420
+ } catch {}
421
+ try {
422
+ process.kill(pid, "SIGKILL");
423
+ } catch {}
424
+ }, 2e3);
425
+ logSuccess(`${name} stopped`);
426
+ }
427
+ async function pushProject(pushConfig) {
428
+ logInfo(`Pushing project: ${pushConfig.projectPath}`);
429
+ const pushEnv = pushConfig.apiKey ? {
430
+ ...process.env,
431
+ INKEEP_CI: "true",
432
+ INKEEP_API_KEY: pushConfig.apiKey,
433
+ INKEEP_TENANT_ID: process.env.INKEEP_TENANT_ID || "default"
434
+ } : { ...process.env };
435
+ try {
436
+ const { stdout } = await execAsync(`pnpm inkeep push --project ${pushConfig.projectPath} --config ${pushConfig.configPath}`, { env: pushEnv });
437
+ if (stdout) console.log(`${colors.dim}${stdout.trim()}${colors.reset}`);
438
+ logSuccess("Project pushed successfully");
439
+ return true;
440
+ } catch (error) {
441
+ logError("Project push failed", error);
442
+ logWarning("The project may not have been seeded. You can manually run:");
443
+ logInfo(` pnpm inkeep push --project ${pushConfig.projectPath} --config ${pushConfig.configPath}`);
444
+ return false;
445
+ }
446
+ }
447
+ async function runSetup(config) {
448
+ console.log(`\n${colors.bright}=== Project Setup ===${colors.reset}\n`);
449
+ logStep(1, "Checking environment configuration");
450
+ await ensureEnvFile();
451
+ loadEnvironmentFiles();
452
+ dotenv.config();
453
+ if (!config.isCloud) {
454
+ const missing = [];
455
+ if (!process.env.INKEEP_AGENTS_MANAGE_DATABASE_URL) missing.push("INKEEP_AGENTS_MANAGE_DATABASE_URL");
456
+ if (!process.env.INKEEP_AGENTS_RUN_DATABASE_URL) missing.push("INKEEP_AGENTS_RUN_DATABASE_URL");
457
+ if (missing.length > 0) {
458
+ logError("Missing required database environment variables:");
459
+ for (const v of missing) logInfo(` - ${v}`);
460
+ logInfo("Check your .env file and ensure these variables are set.");
461
+ process.exit(1);
462
+ }
463
+ }
464
+ logStep(2, "Checking JWT keys");
465
+ await generateJwtKeys();
466
+ if (config.isCloud) logStep(3, "Cloud setup: Skipping Docker database startup");
467
+ else {
468
+ logStep(3, "Starting databases with Docker");
469
+ const preflight = await runPreflightChecks(config.dockerComposeFile);
470
+ if (preflight.dockerAvailable && preflight.portsAvailable) await startDockerDatabases(config.dockerComposeFile, preflight.composeCmd);
471
+ else if (preflight.dockerAvailable && !preflight.portsAvailable) logInfo("Skipping Docker startup — using databases on existing ports.");
472
+ }
473
+ logStep(4, "Running database migrations");
474
+ await runMigrations(config);
475
+ logStep(5, "Initializing authentication");
476
+ await initAuth(config.authInitCommand);
477
+ if (config.pushProject && !config.skipPush) {
478
+ const resolvedPush = {
479
+ ...config.pushProject,
480
+ apiKey: config.pushProject.apiKey || process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET
481
+ };
482
+ logStep(6, "Checking server availability");
483
+ const servers = await startServersIfNeeded(config);
484
+ let pushSuccess = false;
485
+ try {
486
+ logStep(7, "Pushing project to API");
487
+ pushSuccess = await pushProject(resolvedPush);
488
+ } finally {
489
+ if (servers.startedApi || servers.startedUi) {
490
+ logStep(8, "Cleaning up temporarily started servers");
491
+ if (servers.startedApi && servers.apiPid) stopProcessGroup(servers.apiPid, "API server");
492
+ if (servers.startedUi && servers.uiPid) stopProcessGroup(servers.uiPid, "Dashboard");
493
+ }
494
+ }
495
+ console.log(`\n${colors.bright}=== Setup Complete ===${colors.reset}\n`);
496
+ if (pushSuccess) logSuccess("All steps completed successfully!");
497
+ else logWarning("Setup completed with some warnings. See details above.");
498
+ } else {
499
+ console.log(`\n${colors.bright}=== Setup Complete ===${colors.reset}\n`);
500
+ logSuccess("Database setup completed!");
501
+ if (!config.pushProject) logInfo("No project push configured. Run \"pnpm dev\" to start development servers.");
502
+ }
503
+ }
504
+
505
+ //#endregion
506
+ export { runSetup };
@@ -0,0 +1,30 @@
1
+ //#region src/utils/in-process-fetch.d.ts
2
+ /**
3
+ * In-process fetch transport for internal self-calls.
4
+ *
5
+ * Routes requests through the Hono app's full middleware stack in-process
6
+ * rather than over the network. This guarantees same-instance execution,
7
+ * which is critical for features that rely on process-local state
8
+ * (e.g. the stream helper registry for real-time SSE streaming).
9
+ *
10
+ * Drop-in replacement for `fetch()` — same signature, same return type.
11
+ * Throws in production if the app hasn't been registered.
12
+ * Falls back to global `fetch` in test environments where the full app
13
+ * may not be initialized.
14
+ *
15
+ * **IMPORTANT**: Any code making internal A2A calls or self-referencing API
16
+ * calls within the agents service MUST use `getInProcessFetch()` instead of
17
+ * global `fetch`. Using regular `fetch` for same-service calls causes requests
18
+ * to leave the process and hit the load balancer, which may route them to a
19
+ * different instance — breaking features that depend on process-local state
20
+ * (e.g. stream helper registry, in-memory caches). This only manifests under
21
+ * load in multi-instance deployments and is extremely difficult to debug.
22
+ *
23
+ * @example
24
+ * import { getInProcessFetch } from '@inkeep/agents-core';
25
+ * const response = await getInProcessFetch()(url, init);
26
+ */
27
+ declare function registerAppFetch(fn: typeof fetch): void;
28
+ declare function getInProcessFetch(): typeof fetch;
29
+ //#endregion
30
+ export { getInProcessFetch, registerAppFetch };