Convex-only Slice 1: clients talk to Convex, Flue is a private worker

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.
This commit is contained in:
-Puter
2026-07-27 21:32:17 +05:30
parent 4c741fbe06
commit a5414d83bd
127 changed files with 1283 additions and 17784 deletions

View File

@@ -3,7 +3,6 @@ import { convexTest } from "convex-test";
import { anyApi } from "convex/server";
import { describe, expect, test } from "vitest";
import { internal } from "./_generated/api";
import schema from "./schema";
declare global {
@@ -14,211 +13,61 @@ declare global {
const modules = import.meta.glob("./**/*.ts");
const api = anyApi;
const identity = { tokenIdentifier: "https://convex.test|slice-one" };
describe("Slice 1 Work routing", () => {
test("creates one proposed Work and preserves exact source text", async () => {
const t = convexTest({ schema, modules });
const organization = await t
.withIdentity(identity)
.mutation(api.organizations.ensurePersonalOrganization, {});
const project = await t
.withIdentity(identity)
.mutation(internal.projects.persistPublicGitImport, {
remote: { defaultBranch: "main", documents: [], warnings: [] },
source: {
host: "github.com",
normalizedUrl: "https://github.com/example/slice-one",
projectName: "slice-one",
repositoryPath: "example/slice-one",
url: "https://github.com/example/slice-one",
},
userId: identity.tokenIdentifier,
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 rawText = " Add a phone-ready Slice 1 experience. ";
const message = await t
.withIdentity(identity)
.mutation(api.conversationMessages.beginUserMessage, {
clientRequestId: "slice-one-request",
organizationId: organization._id,
rawText,
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,
});
await t
.withIdentity(identity)
.mutation(api.conversationMessages.markAdmitted, {
clientRequestId: "slice-one-request",
organizationId: organization._id,
submissionId: "slice-one-submission",
const conversationId = await ctx.db.insert("conversations", {
createdAt: 1,
organizationId,
});
const signal = await t.mutation(api.signalRouting.createSignal, {
messageIds: [message.messageId],
organizationId: organization._id,
problemStatement: {
constraints: ["Mobile web first"],
desiredOutcome: "The Slice 1 loop works on a phone.",
summary: "The current product is not ready for phone testing.",
title: "Make Slice 1 phone-ready",
},
processedByAgentInstanceId: organization._id,
projectId: project.id,
token: env.FLUE_DB_TOKEN,
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: organization._id,
signalId: signal.signalId,
organizationId: fixture.organizationId,
signalId: fixture.signalId,
token: env.FLUE_DB_TOKEN,
});
const repeated = await t.mutation(api.works.createFromSignal, {
organizationId: organization._id,
signalId: signal.signalId,
const second = await t.mutation(api.works.createFromSignal, {
organizationId: fixture.organizationId,
signalId: fixture.signalId,
token: env.FLUE_DB_TOKEN,
});
expect(repeated).toEqual({ created: false, workId: first.workId });
const works = await t
.withIdentity(identity)
.query(api.works.listForProject, { projectId: project.id });
expect(works).toHaveLength(1);
expect(works[0]?.status).toBe("proposed");
expect(works[0]?.signals[0]?.sources[0]?.rawText).toBe(rawText);
});
test("keeps casual conversation out of Work", async () => {
const t = convexTest({ schema, modules });
const organization = await t
.withIdentity(identity)
.mutation(api.organizations.ensurePersonalOrganization, {});
const project = await t
.withIdentity(identity)
.mutation(internal.projects.persistPublicGitImport, {
remote: { defaultBranch: "main", documents: [], warnings: [] },
source: {
host: "git.openputer.com",
normalizedUrl: "https://git.openputer.com/puter/zopu-code",
projectName: "zopu-code",
repositoryPath: "puter/zopu-code",
url: "https://git.openputer.com/puter/zopu-code",
},
userId: identity.tokenIdentifier,
});
await t
.withIdentity(identity)
.mutation(api.conversationMessages.beginUserMessage, {
clientRequestId: "slice-one-casual",
organizationId: organization._id,
rawText: "Yo, how are you?",
});
await t
.withIdentity(identity)
.mutation(api.conversationMessages.markAdmitted, {
clientRequestId: "slice-one-casual",
organizationId: organization._id,
submissionId: "slice-one-casual-submission",
});
const works = await t
.withIdentity(identity)
.query(api.works.listForProject, { projectId: project.id });
expect(works).toEqual([]);
});
test("attaches another Signal to existing Work idempotently", async () => {
const t = convexTest({ schema, modules });
const organization = await t
.withIdentity(identity)
.mutation(api.organizations.ensurePersonalOrganization, {});
const project = await t
.withIdentity(identity)
.mutation(internal.projects.persistPublicGitImport, {
remote: { defaultBranch: "main", documents: [], warnings: [] },
source: {
host: "git.openputer.com",
normalizedUrl: "https://git.openputer.com/puter/zopu-code",
projectName: "zopu-code",
repositoryPath: "puter/zopu-code",
url: "https://git.openputer.com/puter/zopu-code",
},
userId: identity.tokenIdentifier,
});
const seedSignal = async (
clientRequestId: string,
rawText: string,
title: string
) => {
const message = await t
.withIdentity(identity)
.mutation(api.conversationMessages.beginUserMessage, {
clientRequestId,
organizationId: organization._id,
rawText,
});
await t
.withIdentity(identity)
.mutation(api.conversationMessages.markAdmitted, {
clientRequestId,
organizationId: organization._id,
submissionId: `${clientRequestId}-submission`,
});
return await t.mutation(api.signalRouting.createSignal, {
messageIds: [message.messageId],
organizationId: organization._id,
problemStatement: {
constraints: [],
desiredOutcome: "The Slice 1 phone flow is polished.",
summary: "The messages describe the same desired outcome.",
title,
},
processedByAgentInstanceId: organization._id,
projectId: project.id,
token: env.FLUE_DB_TOKEN,
});
};
const firstSignal = await seedSignal(
"slice-one-first",
"Polish the Slice 1 phone experience.",
"Polish Slice 1"
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()
);
const secondSignal = await seedSignal(
"slice-one-second",
"Make the same Slice 1 experience easier to inspect on mobile.",
"Improve Slice 1 inspection"
);
const created = await t.mutation(api.works.createFromSignal, {
organizationId: organization._id,
signalId: firstSignal.signalId,
token: env.FLUE_DB_TOKEN,
});
const firstAttach = await t.mutation(api.works.attachSignalToWork, {
organizationId: organization._id,
signalId: secondSignal.signalId,
token: env.FLUE_DB_TOKEN,
workId: created.workId,
});
const repeatedAttach = await t.mutation(api.works.attachSignalToWork, {
organizationId: organization._id,
signalId: secondSignal.signalId,
token: env.FLUE_DB_TOKEN,
workId: created.workId,
});
expect(firstAttach).toEqual({ attached: true, workId: created.workId });
expect(repeatedAttach).toEqual({
attached: false,
workId: created.workId,
});
const works = await t
.withIdentity(identity)
.query(api.works.listForProject, { projectId: project.id });
expect(works).toHaveLength(1);
expect(works[0]?.signals).toHaveLength(2);
expect(
works[0]?.events.filter(
(event: { readonly kind: string }) => event.kind === "signal.attached"
)
).toHaveLength(1);
expect(attachments).toHaveLength(1);
});
});