@hachej/boring-core 0.1.16 → 0.1.18

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.
@@ -3,7 +3,9 @@ import { ReactNode } from 'react';
3
3
  import { C as CoreFrontAuthPagesOverride } from '../../CoreFront-CDeLdfb0.js';
4
4
  import { WorkspaceAgentSession, WorkspaceAgentFrontProps } from '@hachej/boring-workspace/app/front';
5
5
 
6
- interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession = WorkspaceAgentSession> extends Omit<WorkspaceAgentFrontProps<TSession>, 'workspaceId'> {
6
+ interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession = WorkspaceAgentSession> extends Omit<WorkspaceAgentFrontProps<TSession>, 'workspaceId' | 'frontPluginHotReload' | 'hotReloadEnabled'> {
7
+ /** Core consumes plugins statically for now; app-level hot reload is explicitly unsupported. */
8
+ hotReload?: false;
7
9
  authPages?: CoreFrontAuthPagesOverride;
8
10
  cspNonce?: string;
9
11
  children?: ReactNode;
@@ -13,6 +15,6 @@ interface CoreWorkspaceAgentFrontProps<TSession extends WorkspaceAgentSession =
13
15
  loadingFallback?: ReactNode;
14
16
  bootPreloadPaths?: string[];
15
17
  }
16
- declare function CoreWorkspaceAgentFront<TSession extends WorkspaceAgentSession = WorkspaceAgentSession>({ authPages, cspNonce, children, workspaceRoute, workspaceIdParam, workspaceHref, loadingFallback, bootPreloadPaths, topBarLeft, topBarRight, appTitle, bridgeEndpoint, ...workspaceProps }: CoreWorkspaceAgentFrontProps<TSession>): react_jsx_runtime.JSX.Element;
18
+ declare function CoreWorkspaceAgentFront<TSession extends WorkspaceAgentSession = WorkspaceAgentSession>({ authPages, cspNonce, children, workspaceRoute, workspaceIdParam, workspaceHref, loadingFallback, bootPreloadPaths, topBarLeft, topBarRight, appTitle, bridgeEndpoint, hotReload, ...workspaceProps }: CoreWorkspaceAgentFrontProps<TSession>): react_jsx_runtime.JSX.Element;
17
19
 
18
20
  export { CoreWorkspaceAgentFront, type CoreWorkspaceAgentFrontProps };
@@ -89,7 +89,9 @@ function WorkspaceRoute({
89
89
  ...workspaceProps,
90
90
  workspaceId,
91
91
  requestHeaders,
92
- authHeaders
92
+ authHeaders,
93
+ frontPluginHotReload: false,
94
+ hotReloadEnabled: false
93
95
  }
94
96
  )
95
97
  }
@@ -108,8 +110,14 @@ function CoreWorkspaceAgentFront({
108
110
  topBarRight = /* @__PURE__ */ jsx(DefaultTopBarRight, {}),
109
111
  appTitle = "Boring",
110
112
  bridgeEndpoint = "/api/v1/ui",
113
+ hotReload = false,
111
114
  ...workspaceProps
112
115
  }) {
116
+ if (hotReload !== false) {
117
+ throw new Error(
118
+ "CoreWorkspaceAgentFront does not support hotReload yet; use static plugin consumption or WorkspaceAgentFront for standalone hot reload."
119
+ );
120
+ }
113
121
  const resolvedLoadingFallback = loadingFallback ?? /* @__PURE__ */ jsx(
114
122
  WorkspaceLoadingPage,
115
123
  {
@@ -1,9 +1,10 @@
1
1
  import { RuntimeProvisioningContribution, RegisterAgentRoutesOptions } from '@hachej/boring-agent/server';
2
- import { CreateWorkspaceAgentServerOptions } from '@hachej/boring-workspace/app/server';
2
+ import { DirPluginEntry, CreateWorkspaceAgentServerOptions } from '@hachej/boring-workspace/app/server';
3
+ import { WorkspaceServerPlugin } from '@hachej/boring-workspace/server';
3
4
  import { FastifyInstance } from 'fastify';
4
5
  import { C as CoreConfig } from '../../index-COZa03RP.js';
5
- import { B as BetterAuthInstance, L as LoadConfigOptions } from '../../authHook-vsRhOvnh.js';
6
- import { D as Database, U as UserStore, W as WorkspaceStore } from '../../connection-CE7z-wBp.js';
6
+ import { B as BetterAuthInstance, L as LoadConfigOptions } from '../../authHook-C5ShLjAS.js';
7
+ import { D as Database, U as UserStore, W as WorkspaceStore } from '../../connection-jW1Xwcne.js';
7
8
  import { IncomingMessage, ServerResponse } from 'node:http';
8
9
  import 'better-auth';
9
10
  import 'drizzle-orm/postgres-js';
@@ -15,15 +16,24 @@ type CoreWorkspaceAgentServer = FastifyInstance & {
15
16
  userStore: UserStore;
16
17
  workspaceStore: WorkspaceStore;
17
18
  };
18
- type CoreWorkspaceAgentServerPlugin = NonNullable<CreateWorkspaceAgentServerOptions['plugins']>[number] & {
19
+ type CoreWorkspaceAgentServerPlugin = WorkspaceServerPlugin & {
19
20
  provisioning?: RuntimeProvisioningContribution;
20
21
  };
22
+ type CoreWorkspaceDirPluginEntry = Omit<DirPluginEntry, 'hotReload'> & {
23
+ /** Core consumes directory plugins statically; per-plugin hot reload is unsupported. */
24
+ hotReload?: false;
25
+ };
26
+ type CoreWorkspacePluginEntry = CoreWorkspaceAgentServerPlugin | CoreWorkspaceDirPluginEntry;
21
27
  interface CreateCoreWorkspaceAgentServerOptions extends Omit<RegisterAgentRoutesOptions, 'extraTools'> {
22
28
  appRoot?: string;
23
29
  config?: CoreConfig;
24
30
  loadConfigOptions?: LoadConfigOptions;
25
- plugins?: CoreWorkspaceAgentServerPlugin[];
31
+ plugins?: CoreWorkspacePluginEntry[];
26
32
  excludeDefaults?: CreateWorkspaceAgentServerOptions['excludeDefaults'];
33
+ defaultPluginPackages?: CreateWorkspaceAgentServerOptions['defaultPluginPackages'];
34
+ appPackageJsonPath?: CreateWorkspaceAgentServerOptions['appPackageJsonPath'];
35
+ /** Core consumes plugins statically for now; app-level hot reload is explicitly unsupported. */
36
+ hotReload?: false;
27
37
  forceProvisioning?: boolean;
28
38
  extraTools?: RegisterAgentRoutesOptions['extraTools'];
29
39
  systemPromptAppend?: string;
@@ -9,7 +9,7 @@ import {
9
9
  registerRoutes,
10
10
  registerSettingsRoutes,
11
11
  registerWorkspaceRoutes
12
- } from "../../chunk-RIEZ2IPH.js";
12
+ } from "../../chunk-V5CXMFHP.js";
13
13
  import {
14
14
  PostgresUserStore,
15
15
  PostgresWorkspaceStore,
@@ -28,7 +28,11 @@ import {
28
28
  } from "@hachej/boring-agent/server";
29
29
  import {
30
30
  collectWorkspaceAgentServerPlugins,
31
- provisionWorkspaceAgentServer
31
+ hasDirServerPlugin,
32
+ provisionWorkspaceAgentServer,
33
+ readWorkspacePluginPackagePiSnapshot,
34
+ resolveDefaultWorkspacePluginPackagePaths,
35
+ resolveOnePluginEntry
32
36
  } from "@hachej/boring-workspace/app/server";
33
37
  import {
34
38
  createInMemoryBridge,
@@ -67,7 +71,19 @@ var FRONTEND_AUTH_PAGES_SPA_ONLY = /* @__PURE__ */ new Set([
67
71
  function dedupeStrings(values) {
68
72
  return Array.from(new Set(values));
69
73
  }
70
- function mergeResourceLoaderOptions(base, override) {
74
+ function isDirPluginEntry(entry) {
75
+ return typeof entry === "object" && entry !== null && "dir" in entry;
76
+ }
77
+ function assertCoreStaticPluginEntries(entries) {
78
+ for (const entry of entries ?? []) {
79
+ if (isDirPluginEntry(entry) && entry.hotReload === true) {
80
+ throw new Error(
81
+ "createCoreWorkspaceAgentServer does not support hotReload yet; directory plugin entries must omit hotReload or set hotReload: false. Use createWorkspaceAgentServer for standalone hot reload."
82
+ );
83
+ }
84
+ }
85
+ }
86
+ function mergePiOptions(base, override) {
71
87
  if (!base && !override) return void 0;
72
88
  return {
73
89
  ...base,
@@ -76,10 +92,32 @@ function mergeResourceLoaderOptions(base, override) {
76
92
  ...base?.additionalSkillPaths ?? [],
77
93
  ...override?.additionalSkillPaths ?? []
78
94
  ]),
79
- piPackages: compactPiPackages([
80
- ...base?.piPackages ?? [],
81
- ...override?.piPackages ?? []
82
- ])
95
+ packages: compactPiPackages([
96
+ ...base?.packages ?? [],
97
+ ...override?.packages ?? []
98
+ ]),
99
+ extensionPaths: dedupeStrings([
100
+ ...base?.extensionPaths ?? [],
101
+ ...override?.extensionPaths ?? []
102
+ ]),
103
+ extensionFactories: [
104
+ ...base?.extensionFactories ?? [],
105
+ ...override?.extensionFactories ?? []
106
+ ]
107
+ };
108
+ }
109
+ function createUnavailableCorePluginBridge() {
110
+ const fail = () => {
111
+ throw new Error(
112
+ "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."
113
+ );
114
+ };
115
+ return {
116
+ getState: fail,
117
+ setState: fail,
118
+ postCommand: fail,
119
+ subscribeCommands: fail,
120
+ drainCommands: fail
83
121
  };
84
122
  }
85
123
  function contentType(filePath) {
@@ -334,6 +372,13 @@ async function registerCoreRoutes({
334
372
  await app.register(registerInviteRoutes);
335
373
  }
336
374
  async function createCoreWorkspaceAgentServer(options = {}) {
375
+ const requestedHotReload = options.hotReload;
376
+ if (requestedHotReload !== void 0 && requestedHotReload !== false) {
377
+ throw new Error(
378
+ "createCoreWorkspaceAgentServer does not support hotReload yet; use static plugin consumption or createWorkspaceAgentServer for standalone hot reload."
379
+ );
380
+ }
381
+ assertCoreStaticPluginEntries(options.plugins);
337
382
  const config = options.config ?? await loadConfig({
338
383
  allowMissingSecrets: process.env.NODE_ENV !== "production",
339
384
  ...options.loadConfigOptions
@@ -347,11 +392,44 @@ async function createCoreWorkspaceAgentServer(options = {}) {
347
392
  await registerFrontendAuthPages(app, appRoot);
348
393
  }
349
394
  await registerAuthProxy(app);
395
+ const defaultPluginPackagePaths = resolveDefaultWorkspacePluginPackagePaths({
396
+ workspaceRoot,
397
+ appPackageJsonPath: options.appPackageJsonPath,
398
+ defaultPluginPackages: options.defaultPluginPackages
399
+ });
400
+ const defaultPackagePiSnapshot = readWorkspacePluginPackagePiSnapshot(defaultPluginPackagePaths);
401
+ const { systemPromptAppend: defaultPackageSystemPromptAppend, ...defaultPackagePiOptions } = defaultPackagePiSnapshot;
402
+ const staticSystemPromptAppend = [options.systemPromptAppend, defaultPackageSystemPromptAppend].filter(Boolean).join("\n\n") || void 0;
403
+ const defaultPluginDirEntries = defaultPluginPackagePaths.map((dir) => ({ dir, hotReload: false })).filter((entry) => hasDirServerPlugin(entry));
404
+ const pluginEntries = [
405
+ ...defaultPluginDirEntries,
406
+ ...options.plugins ?? []
407
+ ];
408
+ const bridges = /* @__PURE__ */ new Map();
409
+ const getUiBridge = (workspaceId) => {
410
+ const safeWorkspaceId = validateWorkspaceIdSegment(workspaceId);
411
+ let bridge = bridges.get(safeWorkspaceId);
412
+ if (!bridge) {
413
+ bridge = createInMemoryBridge();
414
+ bridges.set(safeWorkspaceId, bridge);
415
+ }
416
+ return bridge;
417
+ };
418
+ const pluginResolveContext = {
419
+ workspaceRoot,
420
+ bridge: createUnavailableCorePluginBridge()
421
+ };
422
+ const resolvedPlugins = await Promise.all(
423
+ pluginEntries.map((entry) => resolveOnePluginEntry(
424
+ entry,
425
+ pluginResolveContext
426
+ ))
427
+ );
350
428
  const pluginCollection = collectWorkspaceAgentServerPlugins({
351
429
  workspaceRoot,
352
- systemPromptAppend: options.systemPromptAppend,
353
- resourceLoaderOptions: options.resourceLoaderOptions,
354
- plugins: options.plugins,
430
+ systemPromptAppend: staticSystemPromptAppend,
431
+ pi: mergePiOptions(options.pi, defaultPackagePiOptions),
432
+ plugins: resolvedPlugins,
355
433
  excludeDefaults: options.excludeDefaults
356
434
  });
357
435
  const provisionedWorkspaceRoots = /* @__PURE__ */ new Map();
@@ -372,45 +450,35 @@ async function createCoreWorkspaceAgentServer(options = {}) {
372
450
  return pending;
373
451
  };
374
452
  await ensureWorkspaceProvisioned(workspaceRoot);
375
- const bridges = /* @__PURE__ */ new Map();
376
- const getUiBridge = (workspaceId) => {
377
- const safeWorkspaceId = validateWorkspaceIdSegment(workspaceId);
378
- let bridge = bridges.get(safeWorkspaceId);
379
- if (!bridge) {
380
- bridge = createInMemoryBridge();
381
- bridges.set(safeWorkspaceId, bridge);
382
- }
383
- return bridge;
384
- };
385
453
  const resolveWorkspaceId = async (request) => options.getWorkspaceId ? await options.getWorkspaceId(request) : await resolveAuthorizedWorkspaceId(request, workspaceStore);
386
454
  const resolveRoot = async (workspaceId, request) => {
387
455
  const root = options.getWorkspaceRoot ? await options.getWorkspaceRoot(workspaceId, request) : await resolveWorkspaceRoot(workspaceRoot, workspaceId);
388
456
  await ensureWorkspaceProvisioned(root);
389
457
  return root;
390
458
  };
391
- const resourceLoaderOptionsByRoot = /* @__PURE__ */ new Map();
392
- const getPluginResourceLoaderOptions = (root) => {
459
+ const piOptionsByRoot = /* @__PURE__ */ new Map();
460
+ const getPluginPiOptions = (root) => {
393
461
  const resolvedRoot = path.resolve(root);
394
- if (resourceLoaderOptionsByRoot.has(resolvedRoot)) {
395
- return resourceLoaderOptionsByRoot.get(resolvedRoot);
462
+ if (piOptionsByRoot.has(resolvedRoot)) {
463
+ return piOptionsByRoot.get(resolvedRoot);
396
464
  }
397
465
  const scopedPluginCollection = collectWorkspaceAgentServerPlugins({
398
466
  workspaceRoot: resolvedRoot,
399
- systemPromptAppend: options.systemPromptAppend,
400
- resourceLoaderOptions: options.resourceLoaderOptions,
401
- plugins: options.plugins,
467
+ systemPromptAppend: staticSystemPromptAppend,
468
+ pi: mergePiOptions(options.pi, defaultPackagePiOptions),
469
+ plugins: resolvedPlugins,
402
470
  excludeDefaults: options.excludeDefaults
403
471
  });
404
- resourceLoaderOptionsByRoot.set(
472
+ piOptionsByRoot.set(
405
473
  resolvedRoot,
406
- scopedPluginCollection.agentOptions.resourceLoaderOptions
474
+ scopedPluginCollection.agentOptions.pi
407
475
  );
408
- return scopedPluginCollection.agentOptions.resourceLoaderOptions;
476
+ return scopedPluginCollection.agentOptions.pi;
409
477
  };
410
- const resolveResourceLoaderOptions = async (ctx) => {
411
- const pluginOptions = getPluginResourceLoaderOptions(ctx.workspaceRoot);
412
- const callerOptions = options.getResourceLoaderOptions ? await options.getResourceLoaderOptions(ctx) : void 0;
413
- return mergeResourceLoaderOptions(pluginOptions, callerOptions);
478
+ const resolvePiOptions = async (ctx) => {
479
+ const pluginOptions = getPluginPiOptions(ctx.workspaceRoot);
480
+ const callerOptions = options.getPi ? await options.getPi(ctx) : void 0;
481
+ return mergePiOptions(pluginOptions, callerOptions);
414
482
  };
415
483
  await app.register(registerAgentRoutes, {
416
484
  workspaceRoot,
@@ -424,8 +492,8 @@ async function createCoreWorkspaceAgentServer(options = {}) {
424
492
  ...pluginCollection.agentOptions.extraTools ?? []
425
493
  ],
426
494
  systemPromptAppend: pluginCollection.agentOptions.systemPromptAppend,
427
- resourceLoaderOptions: pluginCollection.agentOptions.resourceLoaderOptions,
428
- getResourceLoaderOptions: resolveResourceLoaderOptions,
495
+ pi: pluginCollection.agentOptions.pi,
496
+ getPi: resolvePiOptions,
429
497
  sessionNamespace: options.sessionNamespace,
430
498
  getSessionNamespace: options.getSessionNamespace,
431
499
  getExtraTools: async (ctx) => {
@@ -443,7 +511,8 @@ async function createCoreWorkspaceAgentServer(options = {}) {
443
511
  registerHealthRoute: options.registerHealthRoute ?? false
444
512
  });
445
513
  await app.register(uiRoutes, {
446
- getBridge: async (request) => getUiBridge(await resolveWorkspaceId(request))
514
+ getBridge: async (request) => getUiBridge(await resolveWorkspaceId(request)),
515
+ preserveStateKeys: pluginCollection.preservedUiStateKeys
447
516
  });
448
517
  for (const { routes } of pluginCollection.routeContributions) {
449
518
  await app.register(routes);
@@ -3,8 +3,34 @@ type BoringViteAlias = {
3
3
  replacement: string;
4
4
  };
5
5
  interface CreateBoringAppViteAliasesOptions {
6
- repoRoot: string;
6
+ /** Host app root (typically `__dirname` of vite.config). */
7
+ appRoot: string;
7
8
  }
8
- declare function createBoringAppViteAliases({ repoRoot, }: CreateBoringAppViteAliasesOptions): BoringViteAlias[];
9
+ /**
10
+ * Vite `resolve` config for a boring app:
11
+ *
12
+ * 1. React-family singleton aliases (`react`, `react-dom`,
13
+ * `react-dom/client`, `react/jsx-runtime`, `react/jsx-dev-runtime`)
14
+ * pinning every dependency to the host app's React copy. REQUIRED
15
+ * for hot-loaded plugin components that call hooks — without it,
16
+ * pnpm-hoisted React duplicates cause `Invalid hook call` errors.
17
+ *
18
+ * 2. `dedupe: ["react", "react-dom"]` for the same reason.
19
+ *
20
+ * Monorepo contributors who want HMR while editing `@hachej/boring-*`
21
+ * source files should run `tsup --watch` (or `turbo dev`) in each
22
+ * package they're editing. The consuming Vite server picks up dist
23
+ * changes through normal node_modules resolution — no special config
24
+ * needed here.
25
+ *
26
+ * export default defineConfig({
27
+ * plugins: [react(), tailwindcss()],
28
+ * resolve: createBoringAppViteAliases({ appRoot: __dirname }),
29
+ * })
30
+ */
31
+ declare function createBoringAppViteAliases(opts: CreateBoringAppViteAliasesOptions): {
32
+ alias: BoringViteAlias[];
33
+ dedupe: string[];
34
+ };
9
35
 
10
36
  export { type BoringViteAlias, type CreateBoringAppViteAliasesOptions, createBoringAppViteAliases };
@@ -2,31 +2,18 @@ import "../../chunk-MLKGABMK.js";
2
2
 
3
3
  // src/app/vite/index.ts
4
4
  import path from "path";
5
- function createBoringAppViteAliases({
6
- repoRoot
7
- }) {
8
- const coreSrc = path.resolve(repoRoot, "packages/core/src");
9
- const agentSrc = path.resolve(repoRoot, "packages/agent/src");
10
- const workspaceSrc = path.resolve(repoRoot, "packages/workspace/src");
11
- return [
12
- { find: "@hachej/boring-core/front/top-bar-slot", replacement: path.resolve(coreSrc, "front/components/TopBarSlot.tsx") },
13
- { find: "@hachej/boring-core/app/front/styles.css", replacement: path.resolve(coreSrc, "app/front/styles.css") },
14
- { find: /^@hachej\/boring-core\/app\/front$/, replacement: path.resolve(coreSrc, "app/front/index.ts") },
15
- { find: /^@hachej\/boring-core\/front$/, replacement: path.resolve(coreSrc, "front/index.ts") },
16
- { find: "@hachej/boring-core/theme.css", replacement: path.resolve(coreSrc, "front/theme.css") },
17
- { find: "@hachej/boring-agent/front/styles.css", replacement: path.resolve(agentSrc, "front/styles/globals.css") },
18
- { find: /^@hachej\/boring-agent\/front$/, replacement: path.resolve(agentSrc, "front/index.ts") },
19
- { find: /^@hachej\/boring-agent$/, replacement: path.resolve(agentSrc, "front/index.ts") },
20
- { find: "@hachej/boring-workspace/globals.css", replacement: path.resolve(workspaceSrc, "globals.css") },
21
- { find: /^@hachej\/boring-workspace\/shared$/, replacement: path.resolve(workspaceSrc, "shared/index.ts") },
22
- { find: /^@hachej\/boring-workspace\/app\/front$/, replacement: path.resolve(workspaceSrc, "app/front/index.ts") },
23
- { find: /^@hachej\/boring-workspace\/testing$/, replacement: path.resolve(workspaceSrc, "front/testing/index.ts") },
24
- { find: /^@hachej\/boring-workspace$/, replacement: path.resolve(workspaceSrc, "index.ts") },
25
- { find: "@/front/lib/", replacement: `${workspaceSrc}/front/lib/` },
26
- { find: "@/front/", replacement: `${agentSrc}/front/` },
27
- { find: "@/components/", replacement: `${workspaceSrc}/front/components/` },
28
- { find: "@/lib/", replacement: `${workspaceSrc}/front/lib/` }
29
- ];
5
+ function createBoringAppViteAliases(opts) {
6
+ const nodeModules = path.resolve(opts.appRoot, "node_modules");
7
+ return {
8
+ alias: [
9
+ { find: /^react$/, replacement: path.resolve(nodeModules, "react") },
10
+ { find: /^react-dom$/, replacement: path.resolve(nodeModules, "react-dom") },
11
+ { find: /^react-dom\/client$/, replacement: path.resolve(nodeModules, "react-dom/client.js") },
12
+ { find: /^react\/jsx-runtime$/, replacement: path.resolve(nodeModules, "react/jsx-runtime.js") },
13
+ { find: /^react\/jsx-dev-runtime$/, replacement: path.resolve(nodeModules, "react/jsx-dev-runtime.js") }
14
+ ],
15
+ dedupe: ["react", "react-dom"]
16
+ };
30
17
  }
31
18
  export {
32
19
  createBoringAppViteAliases
@@ -1,7 +1,7 @@
1
1
  import { C as CoreConfig, R as RuntimeConfig } from './index-COZa03RP.js';
2
2
  import { FastifyPluginAsync } from 'fastify';
3
3
  import { Auth } from 'better-auth';
4
- import { W as WorkspaceStore, D as Database } from './connection-CE7z-wBp.js';
4
+ import { W as WorkspaceStore, D as Database } from './connection-jW1Xwcne.js';
5
5
 
6
6
  interface LoadConfigOptions {
7
7
  tomlPath?: string;
@@ -2696,87 +2696,24 @@ var WorkspaceRuntimeSandboxHandleStore = class {
2696
2696
  }
2697
2697
  store;
2698
2698
  async get(workspaceId) {
2699
- const resource = await this.store.getWorkspaceRuntimeResource?.(workspaceId, SANDBOX_RESOURCE);
2700
- const handle = resourceToHandle(resource);
2701
- if (handle) return handle;
2702
- const runtime = await this.store.getWorkspaceRuntime(workspaceId);
2703
- return runtimeToHandle(runtime);
2699
+ const resource = await this.store.getWorkspaceRuntimeResource(workspaceId, SANDBOX_RESOURCE);
2700
+ return resourceToHandle(resource);
2704
2701
  }
2705
2702
  async put(record) {
2706
2703
  const seenAt = (/* @__PURE__ */ new Date()).toISOString();
2707
- const selector = {
2708
- ...SANDBOX_RESOURCE,
2709
- provider: record.provider ?? SANDBOX_RESOURCE.provider
2710
- };
2711
- const previousResource = await this.store.getWorkspaceRuntimeResource?.(
2704
+ await this.store.putWorkspaceRuntimeResource(
2712
2705
  record.workspaceId,
2713
- selector
2706
+ handleToResourceInput(record, seenAt)
2714
2707
  );
2715
- let resourceWritten = false;
2716
- if (this.store.putWorkspaceRuntimeResource) {
2717
- await this.store.putWorkspaceRuntimeResource(
2718
- record.workspaceId,
2719
- handleToResourceInput(record, seenAt)
2720
- );
2721
- resourceWritten = true;
2722
- }
2723
- try {
2724
- await this.store.putWorkspaceRuntime(record.workspaceId, {
2725
- sandboxProvider: record.provider ?? "vercel",
2726
- sandboxId: record.sandboxId,
2727
- sandboxSnapshotId: record.snapshotId ?? null,
2728
- sandboxCreatedAt: record.createdAt,
2729
- sandboxLastUsedAt: record.lastUsedAt,
2730
- sandboxLastSeenAt: seenAt,
2731
- state: "ready",
2732
- lastError: null,
2733
- lastErrorOp: null
2734
- });
2735
- } catch (error) {
2736
- if (resourceWritten) {
2737
- await this.restoreRuntimeResource(record.workspaceId, selector, previousResource);
2738
- }
2739
- throw error;
2740
- }
2741
- }
2742
- async restoreRuntimeResource(workspaceId, selector, previousResource) {
2743
- try {
2744
- if (previousResource && this.store.putWorkspaceRuntimeResource) {
2745
- await this.store.putWorkspaceRuntimeResource(
2746
- workspaceId,
2747
- resourceToInput(previousResource)
2748
- );
2749
- return;
2750
- }
2751
- await this.store.deleteWorkspaceRuntimeResource?.(workspaceId, selector);
2752
- } catch {
2753
- }
2754
2708
  }
2755
2709
  async delete(workspaceId) {
2756
- await this.store.deleteWorkspaceRuntimeResource?.(workspaceId, SANDBOX_RESOURCE);
2757
- const existing = await this.store.getWorkspaceRuntime(workspaceId);
2758
- if (!existing) return;
2759
- await this.store.putWorkspaceRuntime(workspaceId, {
2760
- sandboxId: null,
2761
- sandboxStatus: null,
2762
- sandboxSnapshotId: null,
2763
- sandboxCreatedAt: null,
2764
- sandboxLastUsedAt: null,
2765
- sandboxLastSeenAt: null,
2766
- sandboxExpiresAt: null
2767
- });
2710
+ await this.store.deleteWorkspaceRuntimeResource(workspaceId, SANDBOX_RESOURCE);
2768
2711
  }
2769
2712
  async list() {
2770
- if (this.store.listWorkspaceRuntimeResources) {
2771
- const resources = await this.store.listWorkspaceRuntimeResources();
2772
- const handles = resources.filter(
2773
- (resource) => resource.kind === SANDBOX_RESOURCE.kind && resource.purpose === SANDBOX_RESOURCE.purpose && resource.provider === SANDBOX_RESOURCE.provider && resource.state !== "deleted"
2774
- ).map((resource) => resourceToHandle(resource)).filter((record) => record !== null);
2775
- if (handles.length > 0) return handles;
2776
- }
2777
- if (!this.store.listWorkspaceRuntimes) return [];
2778
- const runtimes = await this.store.listWorkspaceRuntimes();
2779
- return runtimes.map((runtime) => runtimeToHandle(runtime)).filter((record) => record !== null);
2713
+ const resources = await this.store.listWorkspaceRuntimeResources();
2714
+ return resources.filter(
2715
+ (resource) => resource.kind === SANDBOX_RESOURCE.kind && resource.purpose === SANDBOX_RESOURCE.purpose && resource.provider === SANDBOX_RESOURCE.provider && resource.state !== "deleted"
2716
+ ).map((resource) => resourceToHandle(resource)).filter((record) => record !== null);
2780
2717
  }
2781
2718
  };
2782
2719
  function handleToResourceInput(record, seenAt) {
@@ -2796,28 +2733,6 @@ function handleToResourceInput(record, seenAt) {
2796
2733
  lastUsedAt: record.lastUsedAt
2797
2734
  };
2798
2735
  }
2799
- function resourceToInput(resource) {
2800
- return {
2801
- id: resource.id,
2802
- kind: resource.kind,
2803
- purpose: resource.purpose,
2804
- provider: resource.provider,
2805
- handleKind: resource.handleKind,
2806
- stableKey: resource.stableKey,
2807
- providerResourceId: resource.providerResourceId,
2808
- parentResourceId: resource.parentResourceId,
2809
- state: resource.state,
2810
- persistenceMode: resource.persistenceMode,
2811
- config: resource.config,
2812
- providerMeta: resource.providerMeta,
2813
- lastError: resource.lastError,
2814
- lastErrorCode: resource.lastErrorCode,
2815
- lastSeenAt: resource.lastSeenAt,
2816
- lastUsedAt: resource.lastUsedAt,
2817
- expiresAt: resource.expiresAt,
2818
- generation: resource.generation
2819
- };
2820
- }
2821
2736
  function resourceToHandle(resource) {
2822
2737
  if (!resource?.providerResourceId || resource.state === "deleted") return null;
2823
2738
  const snapshotId = typeof resource.providerMeta.snapshotId === "string" ? resource.providerMeta.snapshotId : void 0;
@@ -2834,16 +2749,6 @@ function resourceToHandle(resource) {
2834
2749
  lastUsedAt: resource.lastUsedAt ?? resource.updatedAt
2835
2750
  };
2836
2751
  }
2837
- function runtimeToHandle(runtime) {
2838
- if (!runtime?.sandboxId) return null;
2839
- return {
2840
- workspaceId: runtime.workspaceId,
2841
- sandboxId: runtime.sandboxId,
2842
- snapshotId: runtime.sandboxSnapshotId ?? void 0,
2843
- createdAt: runtime.sandboxCreatedAt ?? runtime.updatedAt,
2844
- lastUsedAt: runtime.sandboxLastUsedAt ?? runtime.updatedAt
2845
- };
2846
- }
2847
2752
 
2848
2753
  export {
2849
2754
  coreConfigSchema,
@@ -95,10 +95,10 @@ interface WorkspaceStore {
95
95
  }>>;
96
96
  getWorkspaceRuntime(workspaceId: string): Promise<WorkspaceRuntime | null>;
97
97
  putWorkspaceRuntime(workspaceId: string, state: Partial<WorkspaceRuntime>): Promise<WorkspaceRuntime>;
98
- getWorkspaceRuntimeResource?(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<WorkspaceRuntimeResource | null>;
99
- putWorkspaceRuntimeResource?(workspaceId: string, resource: WorkspaceRuntimeResourceInput): Promise<WorkspaceRuntimeResource>;
100
- deleteWorkspaceRuntimeResource?(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<void>;
101
- listWorkspaceRuntimeResources?(workspaceId?: string): Promise<WorkspaceRuntimeResource[]>;
98
+ getWorkspaceRuntimeResource(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<WorkspaceRuntimeResource | null>;
99
+ putWorkspaceRuntimeResource(workspaceId: string, resource: WorkspaceRuntimeResourceInput): Promise<WorkspaceRuntimeResource>;
100
+ deleteWorkspaceRuntimeResource(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<void>;
101
+ listWorkspaceRuntimeResources(workspaceId?: string): Promise<WorkspaceRuntimeResource[]>;
102
102
  retryWorkspaceRuntime(workspaceId: string): Promise<WorkspaceRuntime | null>;
103
103
  getUiState(userId: string, workspaceId: string): Promise<Record<string, unknown> | null>;
104
104
  putUiState(userId: string, workspaceId: string, state: Record<string, unknown>): Promise<void>;
@@ -1,5 +1,5 @@
1
- import { U as UserStore, W as WorkspaceStore } from '../../connection-CE7z-wBp.js';
2
- export { D as Database, c as createDatabase } from '../../connection-CE7z-wBp.js';
1
+ import { U as UserStore, W as WorkspaceStore } from '../../connection-jW1Xwcne.js';
2
+ export { D as Database, c as createDatabase } from '../../connection-jW1Xwcne.js';
3
3
  export { R as RunMigrationsOptions, r as runMigrations } from '../../migrate-D49JsATX.js';
4
4
  import { U as User, W as Workspace, E as ERROR_CODES, M as MemberRole, a as WorkspaceMember, b as WorkspaceInvite, c as WorkspaceRuntime, d as WorkspaceRuntimeResourceSelector, e as WorkspaceRuntimeResource, f as WorkspaceRuntimeResourceInput } from '../../index-COZa03RP.js';
5
5
  import { PostgresJsDatabase } from 'drizzle-orm/postgres-js';
@@ -1,13 +1,13 @@
1
- import { L as LoadConfigOptions } from '../authHook-vsRhOvnh.js';
2
- export { A as AuthHookOptions, B as BetterAuthInstance, C as CreateAuthOptions, a as authHook, b as buildRuntimeConfigPayload, c as createAuth, l as loadConfig, v as validateConfig, d as validatePasswordStrength } from '../authHook-vsRhOvnh.js';
1
+ import { L as LoadConfigOptions } from '../authHook-C5ShLjAS.js';
2
+ export { A as AuthHookOptions, B as BetterAuthInstance, C as CreateAuthOptions, a as authHook, b as buildRuntimeConfigPayload, c as createAuth, l as loadConfig, v as validateConfig, d as validatePasswordStrength } from '../authHook-C5ShLjAS.js';
3
3
  import { z } from 'zod';
4
- import { C as CoreConfig, M as MemberRole, c as WorkspaceRuntime, d as WorkspaceRuntimeResourceSelector, e as WorkspaceRuntimeResource, f as WorkspaceRuntimeResourceInput } from '../index-COZa03RP.js';
4
+ import { C as CoreConfig, M as MemberRole, d as WorkspaceRuntimeResourceSelector, e as WorkspaceRuntimeResource, f as WorkspaceRuntimeResourceInput } from '../index-COZa03RP.js';
5
5
  import * as fastify from 'fastify';
6
6
  import { FastifyInstance, FastifyPluginAsync, preHandlerHookHandler, FastifyRequest, FastifyReply } from 'fastify';
7
7
  import * as http from 'http';
8
8
  import { IncomingMessage } from 'node:http';
9
- import { C as CreateCoreAppOptions, D as Database, U as UserStore, W as WorkspaceStore, a as WorkspaceProvisioner } from '../connection-CE7z-wBp.js';
10
- export { A as AuthProvider, b as CapabilitiesContributor, P as ProvisionContext, d as ProvisionResult, c as createDatabase } from '../connection-CE7z-wBp.js';
9
+ import { C as CreateCoreAppOptions, D as Database, U as UserStore, W as WorkspaceStore, a as WorkspaceProvisioner } from '../connection-jW1Xwcne.js';
10
+ export { A as AuthProvider, b as CapabilitiesContributor, P as ProvisionContext, d as ProvisionResult, c as createDatabase } from '../connection-jW1Xwcne.js';
11
11
  import postgres from 'postgres';
12
12
  export { r as runMigrations } from '../migrate-D49JsATX.js';
13
13
  import 'better-auth';
@@ -374,20 +374,16 @@ interface WorkspaceSandboxHandleRecord {
374
374
  lastUsedAt: string;
375
375
  }
376
376
  interface WorkspaceRuntimeStoreLike {
377
- getWorkspaceRuntime(workspaceId: string): Promise<WorkspaceRuntime | null>;
378
- putWorkspaceRuntime(workspaceId: string, state: Partial<WorkspaceRuntime>): Promise<WorkspaceRuntime>;
379
- getWorkspaceRuntimeResource?(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<WorkspaceRuntimeResource | null>;
380
- putWorkspaceRuntimeResource?(workspaceId: string, resource: WorkspaceRuntimeResourceInput): Promise<WorkspaceRuntimeResource>;
381
- deleteWorkspaceRuntimeResource?(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<void>;
382
- listWorkspaceRuntimeResources?(workspaceId?: string): Promise<WorkspaceRuntimeResource[]>;
383
- listWorkspaceRuntimes?(): Promise<WorkspaceRuntime[]>;
377
+ getWorkspaceRuntimeResource(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<WorkspaceRuntimeResource | null>;
378
+ putWorkspaceRuntimeResource(workspaceId: string, resource: WorkspaceRuntimeResourceInput): Promise<WorkspaceRuntimeResource>;
379
+ deleteWorkspaceRuntimeResource(workspaceId: string, selector: WorkspaceRuntimeResourceSelector): Promise<void>;
380
+ listWorkspaceRuntimeResources(workspaceId?: string): Promise<WorkspaceRuntimeResource[]>;
384
381
  }
385
382
  declare class WorkspaceRuntimeSandboxHandleStore {
386
383
  private readonly store;
387
384
  constructor(store: WorkspaceRuntimeStoreLike);
388
385
  get(workspaceId: string): Promise<WorkspaceSandboxHandleRecord | null>;
389
386
  put(record: WorkspaceSandboxHandleRecord): Promise<void>;
390
- private restoreRuntimeResource;
391
387
  delete(workspaceId: string): Promise<void>;
392
388
  list(): Promise<WorkspaceSandboxHandleRecord[]>;
393
389
  }
@@ -24,7 +24,7 @@ import {
24
24
  requireWorkspaceMember,
25
25
  validateConfig,
26
26
  validatePasswordStrength
27
- } from "../chunk-RIEZ2IPH.js";
27
+ } from "../chunk-V5CXMFHP.js";
28
28
  import {
29
29
  createDatabase,
30
30
  runMigrations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-core",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
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.",
@@ -78,9 +78,9 @@
78
78
  "react-router-dom": "^7.14.2",
79
79
  "smol-toml": "^1.6.1",
80
80
  "zod": "^3.25.76",
81
- "@hachej/boring-agent": "0.1.16",
82
- "@hachej/boring-workspace": "0.1.16",
83
- "@hachej/boring-ui-kit": "0.1.16"
81
+ "@hachej/boring-agent": "0.1.18",
82
+ "@hachej/boring-workspace": "0.1.18",
83
+ "@hachej/boring-ui-kit": "0.1.18"
84
84
  },
85
85
  "devDependencies": {
86
86
  "@testing-library/jest-dom": "^6.9.1",