feat: Slice 1 polish - MiMo V2.5 model, visible reasoning, image attachments, mobile keyboard fix

- Switch conversation agent to xiaomi/mimo-v2.5 (multimodal: text + image)
- Render native reasoning parts as live 'Thinking trace' (streaming open,
  collapsed after completion); inline <think> extraction for streaming models
- Image attachments: picker (up to 4, 10MB each), base64 to Flue
  AgentPromptImage, authenticated blob-URL replay for historical images
- Mobile keyboard viewport fix: visual-viewport hook, fixed shell,
  interactive-widget=resizes-content, header pinned, composer follows keyboard
- Conversation to Signal to proposed Work: Convex persistence, Effect
  validation in @code/work-os, Work cards with exact source provenance
- Streamdown markdown + Mermaid chart rendering in chat messages
- Flue tool turns hidden, reasoning-containing turns remain visible
- Frontend regression tests: keyboard viewport, responsive shell,
  attachment overflow, authenticated images, reasoning traces, transforms
- .env.example updated to xiaomi/mimo-v2.5 config
This commit is contained in:
-Puter
2026-07-27 12:22:54 +05:30
parent 58ff7942bd
commit d8383a788e
55 changed files with 6487 additions and 2006 deletions

View File

@@ -0,0 +1,267 @@
import { env } from "@code/env/convex";
import {
signalAttachedEvent,
workDraftFromSignal,
workProposedEventFromSignal,
} from "@code/work-os";
import { ConvexError, v } from "convex/values";
import { Effect } from "effect";
import type { Doc, Id } from "./_generated/dataModel";
import { mutation, type MutationCtx, query } from "./_generated/server";
import { requireProjectMember } from "./authz";
const requireAgent = (token: string) => {
if (token !== env.FLUE_DB_TOKEN) {
throw new ConvexError("Invalid agent control token");
}
};
const requireSignal = async (
ctx: MutationCtx,
organizationId: Id<"organizations">,
signalId: Id<"signals">
): Promise<Doc<"signals">> => {
const signal = await ctx.db.get(signalId);
if (!signal || signal.organizationId !== organizationId) {
throw new ConvexError("Signal not found");
}
if (!signal.projectId) {
throw new ConvexError("Signal must belong to a project");
}
return signal;
};
const requireWork = async (
ctx: MutationCtx,
organizationId: Id<"organizations">,
workId: Id<"works">
): Promise<Doc<"works">> => {
const work = await ctx.db.get(workId);
if (!work || work.organizationId !== organizationId) {
throw new ConvexError("Work not found");
}
return work;
};
const attachSignal = async (
ctx: MutationCtx,
signal: Doc<"signals">,
work: Doc<"works">
): Promise<boolean> => {
const event = await Effect.runPromise(
signalAttachedEvent({
signal: {
organizationId: String(signal.organizationId),
projectId: String(signal.projectId),
signalId: String(signal._id),
title: signal.problemStatement.title,
},
work: {
organizationId: String(work.organizationId),
projectId: String(work.projectId),
workId: String(work._id),
},
})
).catch((error: unknown) => {
throw new ConvexError(
error instanceof Error ? error.message : "Invalid Work attachment"
);
});
const existing = await ctx.db
.query("signalWorkAttachments")
.withIndex("by_signal_and_work", (q) =>
q.eq("signalId", signal._id).eq("workId", work._id)
)
.unique();
if (existing) {
return false;
}
const createdAt = Date.now();
await ctx.db.insert("signalWorkAttachments", {
createdAt,
organizationId: work.organizationId,
projectId: work.projectId,
signalId: signal._id,
workId: work._id,
});
await ctx.db.insert("workEvents", {
createdAt,
data: event.data,
idempotencyKey: event.idempotencyKey,
kind: event.kind,
organizationId: work.organizationId,
projectId: work.projectId,
workId: work._id,
});
await ctx.db.patch(work._id, { updatedAt: createdAt });
return true;
};
export const listProposedForAgent = query({
args: {
organizationId: v.id("organizations"),
projectId: v.id("projects"),
token: v.string(),
},
handler: async (ctx, args) => {
requireAgent(args.token);
const project = await ctx.db.get(args.projectId);
if (!project || project.organizationId !== args.organizationId) {
throw new ConvexError("Project not found");
}
return await ctx.db
.query("works")
.withIndex("by_project_and_createdAt", (q) =>
q.eq("projectId", args.projectId)
)
.order("desc")
.take(50);
},
});
export const createFromSignal = mutation({
args: {
organizationId: v.id("organizations"),
signalId: v.id("signals"),
token: v.string(),
},
handler: async (ctx, args) => {
requireAgent(args.token);
const signal = await requireSignal(ctx, args.organizationId, args.signalId);
const existingAttachment = await ctx.db
.query("signalWorkAttachments")
.withIndex("by_signal", (q) => q.eq("signalId", signal._id))
.first();
if (existingAttachment) {
return { created: false, workId: existingAttachment.workId };
}
const draft = await Effect.runPromise(
workDraftFromSignal(signal.problemStatement)
).catch((error: unknown) => {
throw new ConvexError(
error instanceof Error ? error.message : "Invalid Work proposal"
);
});
const createdAt = Date.now();
const event = await Effect.runPromise(
workProposedEventFromSignal({
organizationId: String(signal.organizationId),
projectId: String(signal.projectId),
signalId: String(signal._id),
title: draft.title,
})
).catch((error: unknown) => {
throw new ConvexError(
error instanceof Error ? error.message : "Invalid Work event"
);
});
const workId = await ctx.db.insert("works", {
createdAt,
objective: draft.objective,
organizationId: signal.organizationId,
projectId: signal.projectId as Id<"projects">,
status: "proposed",
title: draft.title,
updatedAt: createdAt,
});
await ctx.db.insert("signalWorkAttachments", {
createdAt,
organizationId: signal.organizationId,
projectId: signal.projectId as Id<"projects">,
signalId: signal._id,
workId,
});
await ctx.db.insert("workEvents", {
createdAt,
data: event.data,
idempotencyKey: event.idempotencyKey,
kind: event.kind,
organizationId: signal.organizationId,
projectId: signal.projectId as Id<"projects">,
workId,
});
return { created: true, workId };
},
});
export const attachSignalToWork = mutation({
args: {
organizationId: v.id("organizations"),
signalId: v.id("signals"),
token: v.string(),
workId: v.id("works"),
},
handler: async (ctx, args) => {
requireAgent(args.token);
const signal = await requireSignal(ctx, args.organizationId, args.signalId);
const work = await requireWork(ctx, args.organizationId, args.workId);
return {
attached: await attachSignal(ctx, signal, work),
workId: work._id,
};
},
});
export const listForProject = query({
args: { projectId: v.id("projects") },
handler: async (ctx, args) => {
await requireProjectMember(ctx, args.projectId);
const works = await ctx.db
.query("works")
.withIndex("by_project_and_createdAt", (q) =>
q.eq("projectId", args.projectId)
)
.order("desc")
.take(100);
return await Promise.all(
works.map(async (work) => {
const attachments = await ctx.db
.query("signalWorkAttachments")
.withIndex("by_work", (q) => q.eq("workId", work._id))
.collect();
const signals = await Promise.all(
attachments.map(async (attachment) => {
const signal = await ctx.db.get(attachment.signalId);
if (!signal) {
return null;
}
const sources = await ctx.db
.query("signalSources")
.withIndex("by_signal_and_ordinal", (q) =>
q.eq("signalId", signal._id)
)
.collect();
return {
createdAt: signal.createdAt,
signalId: signal._id,
summary: signal.problemStatement.summary,
sources: sources
.sort((a, b) => a.ordinal - b.ordinal)
.map((source) => ({
createdAt: source.sourceCreatedAt,
messageId: source.messageId,
rawText: source.rawTextSnapshot,
submissionId: source.submissionId ?? null,
})),
title: signal.problemStatement.title,
};
})
);
const events = await ctx.db
.query("workEvents")
.withIndex("by_work_and_createdAt", (q) => q.eq("workId", work._id))
.order("desc")
.collect();
return {
...work,
events,
signals: signals.filter((signal) => signal !== null),
};
})
);
},
});