@hachej/boring-core 0.1.53 → 0.1.55

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.
@@ -267,3 +267,24 @@
267
267
  font-weight: 600;
268
268
  color: color-mix(in oklch, var(--foreground) 88%, transparent);
269
269
  }
270
+
271
+ /* Optional marketing/nav links rendered beside the brand in the public top bar
272
+ (ChatFirstPublicShellOptions.navLinks). The authed workspace is unaffected. */
273
+ .public-topbar-nav {
274
+ display: flex;
275
+ align-items: center;
276
+ gap: 1rem;
277
+ margin-left: 0.75rem;
278
+ }
279
+
280
+ .public-topbar-nav a {
281
+ font-size: 0.8125rem;
282
+ font-weight: 500;
283
+ text-decoration: none;
284
+ color: color-mix(in oklch, var(--foreground) 65%, transparent);
285
+ transition: color 0.15s ease;
286
+ }
287
+
288
+ .public-topbar-nav a:hover {
289
+ color: var(--foreground);
290
+ }
@@ -33,6 +33,15 @@ interface ChatFirstPublicShellOptions {
33
33
  * otherwise the fixed-position arrows overlay the open panel. Defaults to on.
34
34
  */
35
35
  showTeachingArrows?: boolean;
36
+ /**
37
+ * Marketing/navigation links rendered in the public (no-auth) top bar next to
38
+ * the brand — e.g. About, Pricing, Docs. The authenticated workspace top bar
39
+ * is unaffected. Use plain hrefs (router or full-page routes both work).
40
+ */
41
+ navLinks?: Array<{
42
+ label: string;
43
+ href: string;
44
+ }>;
36
45
  }
37
46
 
38
47
  type ChatEntryMode = 'auth-first' | 'chat-first';
@@ -425,7 +425,8 @@ function ChatFirstPublicShell({
425
425
  children: (appTitle?.[0] ?? "B").toUpperCase()
426
426
  }
427
427
  ),
428
- /* @__PURE__ */ jsx4("span", { className: "truncate text-[13px] font-medium leading-none tracking-tight text-foreground", children: appTitle })
428
+ /* @__PURE__ */ jsx4("span", { className: "truncate text-[13px] font-medium leading-none tracking-tight text-foreground", children: appTitle }),
429
+ publicShell?.navLinks?.length ? /* @__PURE__ */ jsx4("nav", { className: "public-topbar-nav", "aria-label": "Site", children: publicShell.navLinks.map((link) => /* @__PURE__ */ jsx4("a", { href: link.href, children: link.label }, link.href)) }) : null
429
430
  ] }),
430
431
  className: workspaceProps.className,
431
432
  surfaceButtonBottomOffset: 456,
@@ -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}$/;
@@ -271,13 +417,14 @@ function mergePiOptions(base, override) {
271
417
  function createUnavailableCorePluginBridge() {
272
418
  const fail = () => {
273
419
  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."
420
+ "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
421
  );
276
422
  };
277
423
  return {
278
424
  getState: fail,
279
425
  setState: fail,
280
426
  postCommand: fail,
427
+ emitUiEffect: fail,
281
428
  subscribeCommands: fail,
282
429
  drainCommands: fail
283
430
  };
@@ -313,6 +460,19 @@ async function pathIsFile(filePath) {
313
460
  return false;
314
461
  }
315
462
  }
463
+ function agentSessionIdFromRequest(request) {
464
+ const url = request.url.split("?")[0] ?? request.url;
465
+ if (request.method === "POST" && url === "/api/v1/agent/chat") {
466
+ const body = request.body;
467
+ return typeof body?.sessionId === "string" && body.sessionId.trim() ? body.sessionId : void 0;
468
+ }
469
+ if (request.method === "POST" && url.endsWith("/followup") && url.startsWith("/api/v1/agent/chat/")) {
470
+ const params = request.params;
471
+ const sessionId = params?.sessionId ?? params?.id;
472
+ return typeof sessionId === "string" && sessionId.trim() ? sessionId : void 0;
473
+ }
474
+ return void 0;
475
+ }
316
476
  function toHeaders(source) {
317
477
  const headers = new Headers();
318
478
  for (const [key, value] of Object.entries(source)) {
@@ -591,30 +751,24 @@ async function createCoreWorkspaceAgentServer(options = {}) {
591
751
  const defaultPackageRuntimePlugins = readWorkspacePluginPackageRuntimePlugins(defaultPluginPackagePaths);
592
752
  const { systemPromptAppend: defaultPackageSystemPromptAppend, ...defaultPackagePiOptions } = defaultPackagePiSnapshot;
593
753
  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));
754
+ const defaultPluginDirEntries = defaultPluginPackagePaths.map((dir) => ({ dir, hotReload: false, trust: "internal" })).filter((entry) => hasDirServerPlugin(entry));
595
755
  const pluginEntries = [
596
756
  ...defaultPluginDirEntries,
597
757
  ...options.plugins ?? []
598
758
  ];
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
759
  const pluginResolveContext = {
610
760
  workspaceRoot: pluginWorkspaceRoot,
611
761
  bridge: createUnavailableCorePluginBridge()
612
762
  };
613
763
  const resolvedPlugins = await Promise.all(
614
- pluginEntries.map((entry) => resolveOnePluginEntry(
615
- entry,
616
- pluginResolveContext
617
- ))
764
+ pluginEntries.map(async (entry) => {
765
+ const plugin = await resolveOnePluginEntry(
766
+ entry,
767
+ pluginResolveContext
768
+ );
769
+ assertWorkspaceBridgeHandlersTrusted(plugin, entry);
770
+ return plugin;
771
+ })
618
772
  );
619
773
  const externalPluginsEnabled = options.externalPlugins !== false;
620
774
  const installPluginAuthoring = externalPluginsEnabled && options.installPluginAuthoring === true;
@@ -631,6 +785,23 @@ async function createCoreWorkspaceAgentServer(options = {}) {
631
785
  const root = options.getWorkspaceRoot ? await options.getWorkspaceRoot(workspaceId, request) : await resolveWorkspaceRoot(workspaceRoot, workspaceId);
632
786
  return root;
633
787
  };
788
+ const coreBridge = createCoreWorkspaceBridge({
789
+ workspaceBridge: {
790
+ ...options.workspaceBridge,
791
+ handlers: [
792
+ ...options.workspaceBridge?.handlers ?? [],
793
+ ...pluginCollection.workspaceBridgeHandlers ?? []
794
+ ]
795
+ },
796
+ resolveWorkspaceId,
797
+ workspaceStore,
798
+ corsOrigins: app.config.cors.origins,
799
+ validateWorkspaceId: validateWorkspaceIdSegment,
800
+ agentSessionId: agentSessionIdFromRequest
801
+ });
802
+ app.addHook("preHandler", async (request) => {
803
+ await coreBridge.rememberSessionOwner(request);
804
+ });
634
805
  const workerBaseUrl = process.env.BORING_WORKER_BASE_URL?.trim();
635
806
  const remoteWorkerModeAdapter = workerBaseUrl ? createRemoteWorkerModeAdapter({ baseUrl: workerBaseUrl }) : void 0;
636
807
  const piOptionsByRoot = /* @__PURE__ */ new Map();
@@ -655,8 +826,13 @@ async function createCoreWorkspaceAgentServer(options = {}) {
655
826
  };
656
827
  const resolvePiOptions = async (ctx) => {
657
828
  const pluginOptions = remoteWorkerModeAdapter ? pluginCollection.agentOptions.pi : getPluginPiOptions(ctx.workspaceRoot);
829
+ const bridgePiOptions = options.getWorkspaceBridgePi ? await options.getWorkspaceBridgePi({
830
+ workspaceId: ctx.workspaceId,
831
+ workspaceRoot: ctx.workspaceRoot,
832
+ callAsRuntime: async (request, callOptions) => await coreBridge.callAsRuntime(ctx.workspaceId, request, callOptions)
833
+ }) : void 0;
658
834
  const callerOptions = options.getPi ? await options.getPi(ctx) : void 0;
659
- return mergePiOptions(pluginOptions, callerOptions);
835
+ return mergePiOptions(mergePiOptions(pluginOptions, bridgePiOptions), callerOptions);
660
836
  };
661
837
  app.get("/api/v1/workspace/meta", async (request, reply) => {
662
838
  try {
@@ -696,9 +872,15 @@ async function createCoreWorkspaceAgentServer(options = {}) {
696
872
  getSessionNamespace: resolveSessionNamespace,
697
873
  getExtraTools: async (ctx) => {
698
874
  const callerTools = options.getExtraTools ? await options.getExtraTools(ctx) : [];
875
+ const bridgeTools = options.getWorkspaceBridgeExtraTools ? await options.getWorkspaceBridgeExtraTools({
876
+ workspaceId: ctx.workspaceId,
877
+ workspaceRoot: ctx.workspaceRoot,
878
+ callAsRuntime: async (request, callOptions) => await coreBridge.callAsRuntime(ctx.workspaceId, request, callOptions)
879
+ }) : [];
699
880
  return [
700
881
  ...callerTools,
701
- ...createWorkspaceUiTools(getUiBridge(ctx.workspaceId), {
882
+ ...bridgeTools,
883
+ ...createWorkspaceUiTools(coreBridge.getBridge(ctx.workspaceId), {
702
884
  workspaceRoot: ctx.workspaceFsCapability === "strong" ? ctx.workspaceRoot : void 0
703
885
  })
704
886
  ];
@@ -727,13 +909,18 @@ async function createCoreWorkspaceAgentServer(options = {}) {
727
909
  provisionWorkspace: options.provisionWorkspace,
728
910
  registerHealthRoute: options.registerHealthRoute ?? false,
729
911
  telemetry,
730
- metering: options.metering
912
+ metering: options.metering,
913
+ runtimeEnvContributions: [
914
+ ...options.runtimeEnvContributions ?? [],
915
+ ...coreBridge.runtimeEnvContribution ? [coreBridge.runtimeEnvContribution] : []
916
+ ]
731
917
  });
732
918
  await app.register(uiRoutes, {
733
919
  getWorkspaceId: resolveWorkspaceId,
734
- getBridge: async (request) => getUiBridge(await resolveWorkspaceId(request)),
920
+ getBridge: async (request) => coreBridge.getBridge(await resolveWorkspaceId(request)),
735
921
  preserveStateKeys: pluginCollection.preservedUiStateKeys
736
922
  });
923
+ await coreBridge.registerHttpRoutes(app);
737
924
  for (const { routes } of pluginCollection.routeContributions) {
738
925
  await app.register(routes);
739
926
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-core",
3
- "version": "0.1.53",
3
+ "version": "0.1.55",
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.",
@@ -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-agent": "0.1.53",
83
- "@hachej/boring-workspace": "0.1.53",
84
- "@hachej/boring-ui-kit": "0.1.53"
82
+ "@hachej/boring-agent": "0.1.55",
83
+ "@hachej/boring-workspace": "0.1.55",
84
+ "@hachej/boring-ui-kit": "0.1.55"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@testing-library/jest-dom": "^6.9.1",