Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/studio/mocks/node-polyfills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,6 @@ export const watch = () => new EventEmitter();
export const readFile = async () => '';
export const writeFile = async () => {};
export const rename = async () => {};
export const renameSync = () => {};
export const createHash = () => ({ update: () => ({ digest: () => '' }) });
export const randomUUID = () => '00000000-0000-0000-0000-000000000000';
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

randomUUID polyfill currently returns a constant all-zero UUID. service-ai uses this to generate conversation/message IDs; returning a constant will cause ID collisions and can break any code that assumes IDs are unique (e.g., inserts keyed by id, UI lists keyed by id). Prefer delegating to globalThis.crypto.randomUUID() when available, or generate a simple unique/monotonic UUID fallback (e.g., counter + timestamp) so IDs remain stable but non-colliding in the browser build.

Suggested change
export const randomUUID = () => '00000000-0000-0000-0000-000000000000';
// randomUUID polyfill – prefer native crypto.randomUUID, then crypto.getRandomValues, then a monotonic fallback
let __objectstackUuidCounter = 0;
const __objectstackMonotonicUUID = (): string => {
const now = Date.now();
__objectstackUuidCounter = (__objectstackUuidCounter + 1) & 0xfffff; // wrap at 20 bits
const counter = __objectstackUuidCounter;
const random = Math.floor(Math.random() * 0xffffffff);
const timeHex = now.toString(16).padStart(12, '0');
const counterHex = counter.toString(16).padStart(5, '0');
const randomHex = random.toString(16).padStart(8, '0');
// Format to look like a UUID, but uniqueness is based on time + counter + randomness
return (
timeHex.slice(0, 8) + '-' +
timeHex.slice(8, 12) + '-' +
'4' + counterHex.slice(0, 3) + '-' + // version-like nibble
'a' + counterHex.slice(3, 5) + '-' + // variant-like nibble
randomHex + timeHex.slice(0, 4)
);
};
export const randomUUID = (): string => {
try {
const g: any = typeof globalThis !== 'undefined' ? globalThis : undefined;
const cryptoObj = g && g.crypto;
if (cryptoObj && typeof cryptoObj.randomUUID === 'function') {
return cryptoObj.randomUUID();
}
if (cryptoObj && typeof cryptoObj.getRandomValues === 'function') {
const bytes = new Uint8Array(16);
cryptoObj.getRandomValues(bytes);
// Per RFC 4122: set version to 4 and variant to RFC 4122
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const byteToHex: string[] = [];
for (let i = 0; i < 256; i++) {
byteToHex[i] = (i + 0x100).toString(16).slice(1);
}
return (
byteToHex[bytes[0]] +
byteToHex[bytes[1]] +
byteToHex[bytes[2]] +
byteToHex[bytes[3]] +
'-' +
byteToHex[bytes[4]] +
byteToHex[bytes[5]] +
'-' +
byteToHex[bytes[6]] +
byteToHex[bytes[7]] +
'-' +
byteToHex[bytes[8]] +
byteToHex[bytes[9]] +
'-' +
byteToHex[bytes[10]] +
byteToHex[bytes[11]] +
byteToHex[bytes[12]] +
byteToHex[bytes[13]] +
byteToHex[bytes[14]] +
byteToHex[bytes[15]]
);
}
} catch {
// Ignore and fall through to monotonic fallback
}
return __objectstackMonotonicUUID();
};

Copilot uses AI. Check for mistakes.
3 changes: 3 additions & 0 deletions apps/studio/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,11 @@
"@objectstack/plugin-auth": "workspace:*",
"@objectstack/plugin-msw": "workspace:*",
"@objectstack/plugin-security": "workspace:*",
"@objectstack/plugin-setup": "workspace:*",
"@objectstack/runtime": "workspace:*",
"@objectstack/service-ai": "workspace:*",
"@objectstack/service-analytics": "workspace:*",
"@objectstack/service-automation": "workspace:*",
"@objectstack/service-feed": "workspace:*",
"@objectstack/spec": "workspace:*",
"@radix-ui/react-avatar": "^1.1.11",
Expand Down
6 changes: 6 additions & 0 deletions apps/studio/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ import { createHonoApp } from '@objectstack/hono';
import { AuthPlugin } from '@objectstack/plugin-auth';
import { SecurityPlugin } from '@objectstack/plugin-security';
import { AuditPlugin } from '@objectstack/plugin-audit';
import { SetupPlugin } from '@objectstack/plugin-setup';
import { FeedServicePlugin } from '@objectstack/service-feed';
import { MetadataPlugin } from '@objectstack/metadata';
import { AIServicePlugin } from '@objectstack/service-ai';
import { AutomationServicePlugin } from '@objectstack/service-automation';
import { AnalyticsServicePlugin } from '@objectstack/service-analytics';
import { handle } from '@hono/node-server/vercel';
import { Hono } from 'hono';
import { cors } from 'hono/cors';
Expand Down Expand Up @@ -127,6 +130,9 @@ async function ensureKernel(): Promise<ObjectKernel> {
await kernel.use(new FeedServicePlugin());
await kernel.use(new MetadataPlugin({ watch: false }));
await kernel.use(new AIServicePlugin());
await kernel.use(new AutomationServicePlugin());
await kernel.use(new AnalyticsServicePlugin());
await kernel.use(new SetupPlugin());
Comment on lines 131 to +135
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetupPlugin is registered after plugins that contribute to the setupNav service (e.g., AIServicePlugin, and on the server also Auth/Security/Audit). Because SetupPlugin only registers setupNav during its own init, those earlier plugins won't be able to contribute navigation items, resulting in an empty/incomplete Setup App. Register SetupPlugin before any plugins that call ctx.getService('setupNav') (typically immediately after ObjectQLPlugin / manifest is available, and before Auth/Security/Audit/AI/etc).

Copilot uses AI. Check for mistakes.

// Broker shim — bridges HttpDispatcher → ObjectQL engine
(kernel as any).broker = createBrokerShim(kernel);
Expand Down
14 changes: 14 additions & 0 deletions apps/studio/src/mocks/createKernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { ObjectKernel, DriverPlugin, AppPlugin } from '@objectstack/runtime';
import { ObjectQLPlugin } from '@objectstack/objectql';
import { InMemoryDriver } from '@objectstack/driver-memory';
import { MSWPlugin } from '@objectstack/plugin-msw';
import { SetupPlugin } from '@objectstack/plugin-setup';
import { AutomationServicePlugin } from '@objectstack/service-automation';
import { AnalyticsServicePlugin } from '@objectstack/service-analytics';
import { MetadataPlugin } from '@objectstack/metadata';
import { AIServicePlugin } from '@objectstack/service-ai';
import { FeedServicePlugin } from '@objectstack/service-feed';
import { createBrokerShim } from '../lib/create-broker-shim';

// System object definitions — resolved via Vite aliases to plugin source (no runtime deps)
Expand Down Expand Up @@ -75,6 +81,14 @@ export async function createKernel(options: KernelOptions) {
await kernel.use(new AppPlugin(appConfig));
}

// Register services and plugins
await kernel.use(new FeedServicePlugin());
await kernel.use(new MetadataPlugin({ watch: false }));
await kernel.use(new AIServicePlugin());
await kernel.use(new AutomationServicePlugin());
await kernel.use(new AnalyticsServicePlugin());
await kernel.use(new SetupPlugin());
Comment on lines +85 to +90
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SetupPlugin is registered last, after AIServicePlugin / other services. Since contributing plugins attempt to call ctx.getService('setupNav') during their init, and setupNav is only registered by SetupPlugin.init, those contributions will be skipped and the Setup App will have no navigation. Register SetupPlugin before the plugins that contribute (at least before AIServicePlugin, and ideally before any service/plugin that adds Setup navigation).

Suggested change
await kernel.use(new FeedServicePlugin());
await kernel.use(new MetadataPlugin({ watch: false }));
await kernel.use(new AIServicePlugin());
await kernel.use(new AutomationServicePlugin());
await kernel.use(new AnalyticsServicePlugin());
await kernel.use(new SetupPlugin());
// SetupPlugin must be registered BEFORE services that contribute to Setup navigation
await kernel.use(new SetupPlugin());
await kernel.use(new FeedServicePlugin());
await kernel.use(new MetadataPlugin({ watch: false }));
await kernel.use(new AIServicePlugin());
await kernel.use(new AutomationServicePlugin());
await kernel.use(new AnalyticsServicePlugin());

Copilot uses AI. Check for mistakes.

// Protocol service is registered automatically by ObjectQLPlugin.init()
// via ObjectStackProtocolImplementation (which uses SchemaRegistry internally).
// Do NOT manually set 'protocol' on kernel.services — it would conflict with
Expand Down
9 changes: 9 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading