@hachej/boring-core 0.1.54 → 0.1.56

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.
@@ -613,6 +613,7 @@ function HomeRedirect({
613
613
  location.search,
614
614
  location.hash
615
615
  );
616
+ if (session.isPending) return /* @__PURE__ */ jsx5(Fragment3, { children: resolvedLoadingFallback });
616
617
  if (!session.data?.user && chatEntryMode === "chat-first") {
617
618
  return /* @__PURE__ */ jsx5(
618
619
  ChatFirstPublicShell,
@@ -701,6 +702,7 @@ function WorkspaceRoute({
701
702
  [resolvedWorkspaceProps.fullPageBasePath, workspaceId]
702
703
  );
703
704
  if (!workspaceId) return /* @__PURE__ */ jsx5(Fragment3, { children: resolvedLoadingFallback });
705
+ if (session.isPending) return /* @__PURE__ */ jsx5(Fragment3, { children: resolvedLoadingFallback });
704
706
  if (!session.data?.user && chatEntryMode === "chat-first") {
705
707
  return /* @__PURE__ */ jsx5(
706
708
  ChatFirstPublicShell,
@@ -1,6 +1,6 @@
1
1
  import { RuntimeProvisioningContribution, RegisterAgentRoutesOptions } from '@hachej/boring-agent/server';
2
2
  import { DirPluginEntry, CreateWorkspaceAgentServerOptions } from '@hachej/boring-workspace/app/server';
3
- import { WorkspaceServerPlugin } from '@hachej/boring-workspace/server';
3
+ import { WorkspaceServerPlugin, WorkspaceBridgeOperationDefinition, WorkspaceBridgeHandler, WorkspaceBridgeRuntimeEnvOptions, WorkspaceBridgeCallRequest, WorkspaceBridgeCallResponse } from '@hachej/boring-workspace/server';
4
4
  import { FastifyInstance } from 'fastify';
5
5
  import { C as CoreConfig } from '../../types-Bb7I-83I.js';
6
6
  import { T as TelemetrySink } from '../../telemetry-DR18MeI0.js';
@@ -28,6 +28,16 @@ type CoreWorkspaceDirPluginEntry = Omit<DirPluginEntry, 'hotReload'> & {
28
28
  hotReload?: false;
29
29
  };
30
30
  type CoreWorkspacePluginEntry = CoreWorkspaceAgentServerPlugin | CoreWorkspaceDirPluginEntry;
31
+ type CoreWorkspaceBridgeExtraTool = NonNullable<RegisterAgentRoutesOptions['extraTools']>[number];
32
+ interface CoreWorkspaceBridgeExtraToolsContext {
33
+ workspaceId: string;
34
+ workspaceRoot: string;
35
+ callAsRuntime<TOutput = unknown>(request: WorkspaceBridgeCallRequest, options?: {
36
+ sessionId?: string;
37
+ signal?: AbortSignal;
38
+ }): Promise<WorkspaceBridgeCallResponse<TOutput>>;
39
+ }
40
+ type CoreWorkspaceBridgePiContext = CoreWorkspaceBridgeExtraToolsContext;
31
41
  interface CreateCoreWorkspaceAgentServerOptions extends Omit<RegisterAgentRoutesOptions, 'extraTools'> {
32
42
  appRoot?: string;
33
43
  config?: CoreConfig;
@@ -35,6 +45,17 @@ interface CreateCoreWorkspaceAgentServerOptions extends Omit<RegisterAgentRoutes
35
45
  plugins?: CoreWorkspacePluginEntry[];
36
46
  excludeDefaults?: CreateWorkspaceAgentServerOptions['excludeDefaults'];
37
47
  defaultPluginPackages?: CreateWorkspaceAgentServerOptions['defaultPluginPackages'];
48
+ workspaceBridge?: {
49
+ handlers?: Array<{
50
+ definition: WorkspaceBridgeOperationDefinition;
51
+ handler: WorkspaceBridgeHandler;
52
+ }>;
53
+ runtimeTokenSecret?: string;
54
+ runtimeRefreshTokenSecret?: string;
55
+ runtimeEnv?: WorkspaceBridgeRuntimeEnvOptions;
56
+ };
57
+ getWorkspaceBridgeExtraTools?: (ctx: CoreWorkspaceBridgeExtraToolsContext) => CoreWorkspaceBridgeExtraTool[] | Promise<CoreWorkspaceBridgeExtraTool[]>;
58
+ getWorkspaceBridgePi?: (ctx: CoreWorkspaceBridgePiContext) => AgentPiOptions | Promise<AgentPiOptions | undefined>;
38
59
  /**
39
60
  * Enable workspace plugin-authoring tooling/prompt for this app.
40
61
  * Defaults to false for full-app/core production composition; set true only
@@ -50,6 +71,7 @@ interface CreateCoreWorkspaceAgentServerOptions extends Omit<RegisterAgentRoutes
50
71
  /** Optional best-effort telemetry sink. Defaults to core's DB-backed env helper. */
51
72
  telemetry?: TelemetrySink;
52
73
  }
74
+ type AgentPiOptions = RegisterAgentRoutesOptions['pi'];
53
75
  declare function createCoreWorkspaceAgentServer(options?: CreateCoreWorkspaceAgentServerOptions): Promise<CoreWorkspaceAgentServer>;
54
76
 
55
77
  interface StartCoreWorkspaceAgentDevServerOptions {
@@ -37,6 +37,7 @@ import {
37
37
  registerAgentRoutes
38
38
  } from "@hachej/boring-agent/server";
39
39
  import {
40
+ assertWorkspaceBridgeHandlersTrusted,
40
41
  collectWorkspaceAgentServerPlugins,
41
42
  hasDirServerPlugin,
42
43
  omitPluginAuthoringProvisioning,
@@ -46,11 +47,156 @@ import {
46
47
  resolveOnePluginEntry
47
48
  } from "@hachej/boring-workspace/app/server";
48
49
  import {
49
- createInMemoryBridge,
50
50
  createWorkspaceUiTools,
51
51
  uiRoutes
52
52
  } from "@hachej/boring-workspace/server";
53
53
 
54
+ // src/app/server/coreWorkspaceBridge.ts
55
+ import {
56
+ createBrowserBridgeAuthPolicy,
57
+ createInMemoryBridge,
58
+ createWorkspaceBridgeRuntimeCore,
59
+ createWorkspaceBridgeRuntimeEnvContribution,
60
+ InMemoryWorkspaceBridgeIdempotencyStore,
61
+ InMemoryWorkspaceBridgeRuntimeRefreshTokenStore,
62
+ runWithWorkspaceBridgeIdempotency,
63
+ verifyWorkspaceBridgeRuntimeToken,
64
+ workspaceBridgeHttpRoutes
65
+ } from "@hachej/boring-workspace/server";
66
+ var MAX_SESSION_OWNER_CACHE = 5e3;
67
+ function createCoreWorkspaceBridge(options) {
68
+ const { resolveWorkspaceId, workspaceStore, validateWorkspaceId, agentSessionId } = options;
69
+ const bridges = /* @__PURE__ */ new Map();
70
+ const getBridge = (workspaceId) => {
71
+ const safeWorkspaceId = validateWorkspaceId(workspaceId);
72
+ let bridge = bridges.get(safeWorkspaceId);
73
+ if (!bridge) {
74
+ bridge = createInMemoryBridge();
75
+ bridges.set(safeWorkspaceId, bridge);
76
+ }
77
+ return bridge;
78
+ };
79
+ const runtimes = /* @__PURE__ */ new Map();
80
+ const getRuntime = (workspaceId) => {
81
+ const safeWorkspaceId = validateWorkspaceId(workspaceId);
82
+ let runtime = runtimes.get(safeWorkspaceId);
83
+ if (!runtime) {
84
+ const sessionOwners = /* @__PURE__ */ new Map();
85
+ const core = createWorkspaceBridgeRuntimeCore({
86
+ handlers: options.workspaceBridge?.handlers,
87
+ ownerWorkspaceId: safeWorkspaceId
88
+ });
89
+ runtime = {
90
+ registry: core.registry,
91
+ idempotencyStore: new InMemoryWorkspaceBridgeIdempotencyStore(),
92
+ refreshTokenStore: new InMemoryWorkspaceBridgeRuntimeRefreshTokenStore(),
93
+ sessionOwners
94
+ };
95
+ runtimes.set(safeWorkspaceId, runtime);
96
+ }
97
+ return runtime;
98
+ };
99
+ const resolveBridgeWorkspaceId = async (request) => {
100
+ const authHeader = request.headers.authorization;
101
+ if (options.workspaceBridge?.runtimeTokenSecret && typeof authHeader === "string" && authHeader.startsWith("Bearer ")) {
102
+ return verifyWorkspaceBridgeRuntimeToken(authHeader.slice("Bearer ".length), {
103
+ secret: options.workspaceBridge.runtimeTokenSecret
104
+ }).authContext.workspaceId;
105
+ }
106
+ return await resolveWorkspaceId(request);
107
+ };
108
+ const rememberSessionOwner = async (request) => {
109
+ const user = request.user;
110
+ if (!user?.id) return;
111
+ const sessionId = agentSessionId(request);
112
+ if (!sessionId) return;
113
+ const workspaceId = await resolveWorkspaceId(request);
114
+ const runtime = getRuntime(workspaceId);
115
+ runtime.sessionOwners.delete(sessionId);
116
+ runtime.sessionOwners.set(sessionId, user.id);
117
+ while (runtime.sessionOwners.size > MAX_SESSION_OWNER_CACHE) {
118
+ const oldest = runtime.sessionOwners.keys().next().value;
119
+ if (oldest === void 0) break;
120
+ runtime.sessionOwners.delete(oldest);
121
+ }
122
+ };
123
+ const callAsRuntime = async (workspaceId, request, callOptions) => {
124
+ const runtime = getRuntime(workspaceId);
125
+ const definition = runtime.registry.getDefinition(request.op);
126
+ if (!definition) {
127
+ return await runtime.registry.call(request, {
128
+ callerClass: "runtime",
129
+ workspaceId,
130
+ sessionId: callOptions?.sessionId,
131
+ capabilities: [],
132
+ actor: {
133
+ actorKind: "agent",
134
+ performedBy: { label: "agent-runtime" },
135
+ onBehalfOf: callOptions?.sessionId ? { label: `session:${callOptions.sessionId}` } : { label: `workspace:${workspaceId}` }
136
+ },
137
+ signal: callOptions?.signal,
138
+ emitUiEffect: (cmd) => getBridge(workspaceId).emitUiEffect(cmd)
139
+ });
140
+ }
141
+ const ownerPrincipalId = callOptions?.sessionId ? runtime.sessionOwners.get(callOptions.sessionId) : void 0;
142
+ const authContext = {
143
+ callerClass: "runtime",
144
+ workspaceId,
145
+ sessionId: callOptions?.sessionId,
146
+ capabilities: [...definition.requiredCapabilities],
147
+ actor: {
148
+ actorKind: "agent",
149
+ performedBy: { label: "agent-runtime" },
150
+ onBehalfOf: ownerPrincipalId ? { id: ownerPrincipalId, label: `user:${ownerPrincipalId}` } : callOptions?.sessionId ? { label: `session:${callOptions.sessionId}` } : { label: `workspace:${workspaceId}` }
151
+ },
152
+ signal: callOptions?.signal,
153
+ emitUiEffect: (cmd) => getBridge(workspaceId).emitUiEffect(cmd)
154
+ };
155
+ return await runWithWorkspaceBridgeIdempotency(runtime.idempotencyStore, {
156
+ definition,
157
+ request,
158
+ auth: authContext
159
+ }, async () => await runtime.registry.call(request, authContext));
160
+ };
161
+ const runtimeEnvContribution = options.workspaceBridge?.runtimeTokenSecret || options.workspaceBridge?.runtimeEnv ? {
162
+ id: "workspace-bridge-runtime-env",
163
+ getEnv: async (ctx) => {
164
+ const contribution = createWorkspaceBridgeRuntimeEnvContribution({
165
+ workspaceId: ctx.workspaceId,
166
+ runtimeMode: ctx.runtimeMode,
167
+ registry: getRuntime(ctx.workspaceId).registry,
168
+ runtimeTokenSecret: options.workspaceBridge?.runtimeTokenSecret,
169
+ runtimeRefreshTokenSecret: options.workspaceBridge?.runtimeRefreshTokenSecret,
170
+ runtimeEnv: options.workspaceBridge?.runtimeEnv
171
+ });
172
+ return contribution ? await contribution.getEnv(ctx) : {};
173
+ }
174
+ } : void 0;
175
+ const registerHttpRoutes = async (app) => {
176
+ await app.register(workspaceBridgeHttpRoutes, {
177
+ getRegistry: async (request) => getRuntime(await resolveBridgeWorkspaceId(request)).registry,
178
+ getIdempotencyStore: async (request) => getRuntime(await resolveBridgeWorkspaceId(request)).idempotencyStore,
179
+ runtimeTokenSecret: options.workspaceBridge?.runtimeTokenSecret,
180
+ runtimeRefreshTokenSecret: options.workspaceBridge?.runtimeRefreshTokenSecret,
181
+ getRuntimeRefreshTokenStore: (_request, claims) => getRuntime(claims.workspaceId).refreshTokenStore,
182
+ getOwnerWorkspaceId: async (request) => await resolveBridgeWorkspaceId(request),
183
+ browserAuthPolicy: createBrowserBridgeAuthPolicy({
184
+ getPrincipal: (input) => {
185
+ const user = input.request?.user;
186
+ return user?.id ? { userId: user.id, email: user.email ?? void 0 } : null;
187
+ },
188
+ authorizeWorkspace: async ({ principal, workspaceId, definition }) => ({
189
+ allowed: await workspaceStore.isMember(workspaceId, principal.userId),
190
+ capabilities: definition.requiredCapabilities
191
+ }),
192
+ allowedOrigins: options.corsOrigins,
193
+ requireCsrfHeader: true
194
+ })
195
+ });
196
+ };
197
+ return { getBridge, callAsRuntime, rememberSessionOwner, runtimeEnvContribution, registerHttpRoutes };
198
+ }
199
+
54
200
  // src/server/telemetry/db.ts
55
201
  var EVENT_NAME_PATTERN = /^[a-z][a-z0-9_-]*(?:\.[a-z][a-z0-9_-]*){0,8}$/;
56
202
  var SAFE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$/;
@@ -223,6 +369,16 @@ var FRONTEND_AUTH_PAGES_SPA_ONLY = /* @__PURE__ */ new Set([
223
369
  "/auth/callback/github",
224
370
  "/auth/callback/google"
225
371
  ]);
372
+ function normalizeOptionalPath(value) {
373
+ const trimmed = value?.trim();
374
+ return trimmed ? trimmed : void 0;
375
+ }
376
+ function inferSessionRootForWorkspaceRoot(workspaceRoot, runtimeMode) {
377
+ if (runtimeMode !== "vercel-sandbox") return void 0;
378
+ const resolvedRoot = path.resolve(workspaceRoot);
379
+ if (path.basename(resolvedRoot) !== "workspaces") return void 0;
380
+ return path.join(path.dirname(resolvedRoot), "pi-sessions");
381
+ }
226
382
  function resolveCoreLoadConfigOptions(options = {}, nodeEnv = process.env.NODE_ENV) {
227
383
  return {
228
384
  allowMissingSecrets: nodeEnv !== "production",
@@ -271,13 +427,14 @@ function mergePiOptions(base, override) {
271
427
  function createUnavailableCorePluginBridge() {
272
428
  const fail = () => {
273
429
  throw new Error(
274
- "Core static server plugins do not receive a workspace-scoped UiBridge yet. Use request-scoped UI tools/routes in core, or createWorkspaceAgentServer for standalone plugin bridge support."
430
+ "Core static server plugins do not receive a workspace-scoped WorkspaceBridge yet. Use request-scoped UI tools/routes in core, or createWorkspaceAgentServer for standalone plugin bridge support."
275
431
  );
276
432
  };
277
433
  return {
278
434
  getState: fail,
279
435
  setState: fail,
280
436
  postCommand: fail,
437
+ emitUiEffect: fail,
281
438
  subscribeCommands: fail,
282
439
  drainCommands: fail
283
440
  };
@@ -313,6 +470,19 @@ async function pathIsFile(filePath) {
313
470
  return false;
314
471
  }
315
472
  }
473
+ function agentSessionIdFromRequest(request) {
474
+ const url = request.url.split("?")[0] ?? request.url;
475
+ if (request.method === "POST" && url === "/api/v1/agent/chat") {
476
+ const body = request.body;
477
+ return typeof body?.sessionId === "string" && body.sessionId.trim() ? body.sessionId : void 0;
478
+ }
479
+ if (request.method === "POST" && url.endsWith("/followup") && url.startsWith("/api/v1/agent/chat/")) {
480
+ const params = request.params;
481
+ const sessionId = params?.sessionId ?? params?.id;
482
+ return typeof sessionId === "string" && sessionId.trim() ? sessionId : void 0;
483
+ }
484
+ return void 0;
485
+ }
316
486
  function toHeaders(source) {
317
487
  const headers = new Headers();
318
488
  for (const [key, value] of Object.entries(source)) {
@@ -575,6 +745,8 @@ async function createCoreWorkspaceAgentServer(options = {}) {
575
745
  const serveFrontend = options.serveFrontend ?? (process.env.NODE_ENV !== "development" && Boolean(appRoot));
576
746
  const pluginWorkspaceRoot = process.cwd();
577
747
  const workspaceRoot = options.workspaceRoot ?? process.env.BORING_AGENT_WORKSPACE_ROOT ?? process.cwd();
748
+ const agentRuntimeMode = options.runtimeModeAdapter?.id ?? options.mode ?? process.env.BORING_AGENT_MODE;
749
+ const sessionRoot = normalizeOptionalPath(options.sessionRoot) ?? normalizeOptionalPath(process.env.BORING_AGENT_SESSION_ROOT) ?? inferSessionRootForWorkspaceRoot(workspaceRoot, agentRuntimeMode);
578
750
  registerTelemetryHooks(app, telemetry);
579
751
  await registerCoreRoutes({ app, sql, db, userStore, workspaceStore });
580
752
  if (serveFrontend && appRoot) {
@@ -591,30 +763,24 @@ async function createCoreWorkspaceAgentServer(options = {}) {
591
763
  const defaultPackageRuntimePlugins = readWorkspacePluginPackageRuntimePlugins(defaultPluginPackagePaths);
592
764
  const { systemPromptAppend: defaultPackageSystemPromptAppend, ...defaultPackagePiOptions } = defaultPackagePiSnapshot;
593
765
  const staticSystemPromptAppend = [options.systemPromptAppend, defaultPackageSystemPromptAppend].filter(Boolean).join("\n\n") || void 0;
594
- const defaultPluginDirEntries = defaultPluginPackagePaths.map((dir) => ({ dir, hotReload: false })).filter((entry) => hasDirServerPlugin(entry));
766
+ const defaultPluginDirEntries = defaultPluginPackagePaths.map((dir) => ({ dir, hotReload: false, trust: "internal" })).filter((entry) => hasDirServerPlugin(entry));
595
767
  const pluginEntries = [
596
768
  ...defaultPluginDirEntries,
597
769
  ...options.plugins ?? []
598
770
  ];
599
- const bridges = /* @__PURE__ */ new Map();
600
- const getUiBridge = (workspaceId) => {
601
- const safeWorkspaceId = validateWorkspaceIdSegment(workspaceId);
602
- let bridge = bridges.get(safeWorkspaceId);
603
- if (!bridge) {
604
- bridge = createInMemoryBridge();
605
- bridges.set(safeWorkspaceId, bridge);
606
- }
607
- return bridge;
608
- };
609
771
  const pluginResolveContext = {
610
772
  workspaceRoot: pluginWorkspaceRoot,
611
773
  bridge: createUnavailableCorePluginBridge()
612
774
  };
613
775
  const resolvedPlugins = await Promise.all(
614
- pluginEntries.map((entry) => resolveOnePluginEntry(
615
- entry,
616
- pluginResolveContext
617
- ))
776
+ pluginEntries.map(async (entry) => {
777
+ const plugin = await resolveOnePluginEntry(
778
+ entry,
779
+ pluginResolveContext
780
+ );
781
+ assertWorkspaceBridgeHandlersTrusted(plugin, entry);
782
+ return plugin;
783
+ })
618
784
  );
619
785
  const externalPluginsEnabled = options.externalPlugins !== false;
620
786
  const installPluginAuthoring = externalPluginsEnabled && options.installPluginAuthoring === true;
@@ -631,6 +797,23 @@ async function createCoreWorkspaceAgentServer(options = {}) {
631
797
  const root = options.getWorkspaceRoot ? await options.getWorkspaceRoot(workspaceId, request) : await resolveWorkspaceRoot(workspaceRoot, workspaceId);
632
798
  return root;
633
799
  };
800
+ const coreBridge = createCoreWorkspaceBridge({
801
+ workspaceBridge: {
802
+ ...options.workspaceBridge,
803
+ handlers: [
804
+ ...options.workspaceBridge?.handlers ?? [],
805
+ ...pluginCollection.workspaceBridgeHandlers ?? []
806
+ ]
807
+ },
808
+ resolveWorkspaceId,
809
+ workspaceStore,
810
+ corsOrigins: app.config.cors.origins,
811
+ validateWorkspaceId: validateWorkspaceIdSegment,
812
+ agentSessionId: agentSessionIdFromRequest
813
+ });
814
+ app.addHook("preHandler", async (request) => {
815
+ await coreBridge.rememberSessionOwner(request);
816
+ });
634
817
  const workerBaseUrl = process.env.BORING_WORKER_BASE_URL?.trim();
635
818
  const remoteWorkerModeAdapter = workerBaseUrl ? createRemoteWorkerModeAdapter({ baseUrl: workerBaseUrl }) : void 0;
636
819
  const piOptionsByRoot = /* @__PURE__ */ new Map();
@@ -655,8 +838,13 @@ async function createCoreWorkspaceAgentServer(options = {}) {
655
838
  };
656
839
  const resolvePiOptions = async (ctx) => {
657
840
  const pluginOptions = remoteWorkerModeAdapter ? pluginCollection.agentOptions.pi : getPluginPiOptions(ctx.workspaceRoot);
841
+ const bridgePiOptions = options.getWorkspaceBridgePi ? await options.getWorkspaceBridgePi({
842
+ workspaceId: ctx.workspaceId,
843
+ workspaceRoot: ctx.workspaceRoot,
844
+ callAsRuntime: async (request, callOptions) => await coreBridge.callAsRuntime(ctx.workspaceId, request, callOptions)
845
+ }) : void 0;
658
846
  const callerOptions = options.getPi ? await options.getPi(ctx) : void 0;
659
- return mergePiOptions(pluginOptions, callerOptions);
847
+ return mergePiOptions(mergePiOptions(pluginOptions, bridgePiOptions), callerOptions);
660
848
  };
661
849
  app.get("/api/v1/workspace/meta", async (request, reply) => {
662
850
  try {
@@ -693,12 +881,19 @@ async function createCoreWorkspaceAgentServer(options = {}) {
693
881
  systemPromptAppend: pluginCollection.agentOptions.systemPromptAppend,
694
882
  pi: pluginCollection.agentOptions.pi,
695
883
  getPi: resolvePiOptions,
884
+ sessionRoot,
696
885
  getSessionNamespace: resolveSessionNamespace,
697
886
  getExtraTools: async (ctx) => {
698
887
  const callerTools = options.getExtraTools ? await options.getExtraTools(ctx) : [];
888
+ const bridgeTools = options.getWorkspaceBridgeExtraTools ? await options.getWorkspaceBridgeExtraTools({
889
+ workspaceId: ctx.workspaceId,
890
+ workspaceRoot: ctx.workspaceRoot,
891
+ callAsRuntime: async (request, callOptions) => await coreBridge.callAsRuntime(ctx.workspaceId, request, callOptions)
892
+ }) : [];
699
893
  return [
700
894
  ...callerTools,
701
- ...createWorkspaceUiTools(getUiBridge(ctx.workspaceId), {
895
+ ...bridgeTools,
896
+ ...createWorkspaceUiTools(coreBridge.getBridge(ctx.workspaceId), {
702
897
  workspaceRoot: ctx.workspaceFsCapability === "strong" ? ctx.workspaceRoot : void 0
703
898
  })
704
899
  ];
@@ -727,13 +922,18 @@ async function createCoreWorkspaceAgentServer(options = {}) {
727
922
  provisionWorkspace: options.provisionWorkspace,
728
923
  registerHealthRoute: options.registerHealthRoute ?? false,
729
924
  telemetry,
730
- metering: options.metering
925
+ metering: options.metering,
926
+ runtimeEnvContributions: [
927
+ ...options.runtimeEnvContributions ?? [],
928
+ ...coreBridge.runtimeEnvContribution ? [coreBridge.runtimeEnvContribution] : []
929
+ ]
731
930
  });
732
931
  await app.register(uiRoutes, {
733
932
  getWorkspaceId: resolveWorkspaceId,
734
- getBridge: async (request) => getUiBridge(await resolveWorkspaceId(request)),
933
+ getBridge: async (request) => coreBridge.getBridge(await resolveWorkspaceId(request)),
735
934
  preserveStateKeys: pluginCollection.preservedUiStateKeys
736
935
  });
936
+ await coreBridge.registerHttpRoutes(app);
737
937
  for (const { routes } of pluginCollection.routeContributions) {
738
938
  await app.register(routes);
739
939
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-core",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Foundation package for boring-ui-v2 apps: DB, auth, config, HTTP app factory, and frontend app shell.",
@@ -70,7 +70,7 @@
70
70
  "fastify-plugin": "^5.1.0",
71
71
  "isomorphic-dompurify": "^3.10.0",
72
72
  "lucide-react": "^1.8.0",
73
- "nodemailer": "^8.0.6",
73
+ "nodemailer": "^8.0.9",
74
74
  "pino": "^10.3.1",
75
75
  "postgres": "^3.4.9",
76
76
  "posthog-node": "^5.35.1",
@@ -79,9 +79,9 @@
79
79
  "react-router-dom": "^7.14.2",
80
80
  "smol-toml": "^1.6.1",
81
81
  "zod": "^3.25.76",
82
- "@hachej/boring-ui-kit": "0.1.54",
83
- "@hachej/boring-workspace": "0.1.54",
84
- "@hachej/boring-agent": "0.1.54"
82
+ "@hachej/boring-agent": "0.1.56",
83
+ "@hachej/boring-ui-kit": "0.1.56",
84
+ "@hachej/boring-workspace": "0.1.56"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@testing-library/jest-dom": "^6.9.1",