Normalize the topology so web/desktop/mobile clients communicate only with Convex. Convex admits durable commands, dispatches private Flue turns through FLUE_URL, and stores the product-facing result before clients observe it. - Normalized relational schema: organizations, projects, conversations/turns/ messages/attachments, signals/sources/constraints, works/events/attachments. - Conversation turns queued in Convex; the agent runAction dispatches to Flue and persists assistant response, signals, and proposed work back into Convex. - Browser chat moved off the Flue transport: use-chat-agent now reads reactive Convex rows and sends through conversationMessages.send. - Fixed signed-in blank screen: gate all product queries on useConvexAuth (isAuthenticated && !isRefreshing) plus personal-org bootstrap. - Pinned react/react-dom to 19.2.8 via catalog to fix SSR dual-React crash. - Removed ~16.6k net lines: daemon, AgentOS/Orb, project issues/runs/artifacts, todos, browser Flue transport, mobile execution views, smoke scripts. Verified end-to-end on cheaptricks: connect repo, send actionable message, Flue turn completes, Signal + proposed Work created with exact provenance, persists across refresh.
74 lines
2.3 KiB
TypeScript
74 lines
2.3 KiB
TypeScript
import { env } from "@code/env/convex";
|
|
import { convexTest } from "convex-test";
|
|
import { anyApi } from "convex/server";
|
|
import { describe, expect, test } from "vitest";
|
|
|
|
import schema from "./schema";
|
|
|
|
declare global {
|
|
interface ImportMeta {
|
|
readonly glob: (pattern: string) => Record<string, () => Promise<unknown>>;
|
|
}
|
|
}
|
|
|
|
const modules = import.meta.glob("./**/*.ts");
|
|
const api = anyApi;
|
|
|
|
describe("works", () => {
|
|
test("creates one proposed Work and one normalized attachment", async () => {
|
|
const t = convexTest({ modules, schema });
|
|
const fixture = await t.run(async (ctx) => {
|
|
const organizationId = await ctx.db.insert("organizations", {
|
|
createdAt: 1,
|
|
createdBy: "user",
|
|
kind: "personal",
|
|
name: "Personal",
|
|
});
|
|
const projectId = await ctx.db.insert("projects", {
|
|
createdAt: 1,
|
|
name: "Zopu",
|
|
normalizedSourceUrl: "https://example.com/zopu",
|
|
organizationId,
|
|
repositoryPath: "puter/zopu",
|
|
sourceHost: "example.com",
|
|
sourceUrl: "https://example.com/zopu",
|
|
updatedAt: 1,
|
|
});
|
|
const conversationId = await ctx.db.insert("conversations", {
|
|
createdAt: 1,
|
|
organizationId,
|
|
});
|
|
const signalId = await ctx.db.insert("signals", {
|
|
conversationId,
|
|
createdAt: 1,
|
|
desiredOutcome: "A working Slice 1",
|
|
organizationId,
|
|
processedByAgentInstanceId: String(organizationId),
|
|
processedByAgentName: "zopu",
|
|
projectId,
|
|
sourceKey: "source-1",
|
|
summary: "Slice 1 needs work",
|
|
title: "Finish Slice 1",
|
|
});
|
|
return { organizationId, signalId };
|
|
});
|
|
|
|
const first = await t.mutation(api.works.createFromSignal, {
|
|
organizationId: fixture.organizationId,
|
|
signalId: fixture.signalId,
|
|
token: env.FLUE_DB_TOKEN,
|
|
});
|
|
const second = await t.mutation(api.works.createFromSignal, {
|
|
organizationId: fixture.organizationId,
|
|
signalId: fixture.signalId,
|
|
token: env.FLUE_DB_TOKEN,
|
|
});
|
|
expect(first.created).toBe(true);
|
|
expect(second).toEqual({ created: false, workId: first.workId });
|
|
const attachments = await t.run(async (ctx) =>
|
|
ctx.db.query("signalWorkAttachments").collect()
|
|
);
|
|
expect(attachments).toHaveLength(1);
|
|
});
|
|
});
|