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:
@@ -0,0 +1,145 @@
|
||||
import { parseAgentEnv } from "@code/env/agent";
|
||||
import { defineTool } from "@flue/runtime";
|
||||
import { ConvexHttpClient } from "convex/browser";
|
||||
import { makeFunctionReference } from "convex/server";
|
||||
import * as v from "valibot";
|
||||
|
||||
const listProjectsRef = makeFunctionReference<
|
||||
"query",
|
||||
{ organizationId: string; token: string },
|
||||
{ _id: string; name: string; description: string | null }[]
|
||||
>("signalRouting:listProjects");
|
||||
|
||||
const listEvidenceRef = makeFunctionReference<
|
||||
"query",
|
||||
{ organizationId: string; token: string },
|
||||
{ messageId: string; rawText: string; createdAt: number }[]
|
||||
>("signalRouting:listEvidence");
|
||||
|
||||
const createSignalRef = makeFunctionReference<
|
||||
"mutation",
|
||||
{
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
messageIds: string[];
|
||||
problemStatement: {
|
||||
title: string;
|
||||
summary: string;
|
||||
desiredOutcome: string;
|
||||
constraints: string[];
|
||||
};
|
||||
processedByAgentInstanceId: string;
|
||||
token: string;
|
||||
},
|
||||
{ signalId: string }
|
||||
>("signalRouting:createSignal");
|
||||
|
||||
const listWorksRef = makeFunctionReference<
|
||||
"query",
|
||||
{ organizationId: string; projectId: string; token: string },
|
||||
{ _id: string; title: string; objective: string; status: "proposed" }[]
|
||||
>("works:listProposedForAgent");
|
||||
|
||||
const createWorkRef = makeFunctionReference<
|
||||
"mutation",
|
||||
{ organizationId: string; signalId: string; token: string },
|
||||
{ created: boolean; workId: string }
|
||||
>("works:createFromSignal");
|
||||
|
||||
const attachSignalRef = makeFunctionReference<
|
||||
"mutation",
|
||||
{ organizationId: string; signalId: string; workId: string; token: string },
|
||||
{ attached: boolean; workId: string }
|
||||
>("works:attachSignalToWork");
|
||||
|
||||
export const createSliceOneTools = (
|
||||
organizationId: string,
|
||||
runtimeEnv: Record<string, string | undefined>
|
||||
) => {
|
||||
const env = parseAgentEnv(runtimeEnv);
|
||||
const client = new ConvexHttpClient(env.CONVEX_URL);
|
||||
const token = env.FLUE_DB_TOKEN;
|
||||
|
||||
return [
|
||||
defineTool({
|
||||
description: "List the projects available in this organization.",
|
||||
name: "list_projects",
|
||||
async run() {
|
||||
return await client.query(listProjectsRef, { organizationId, token });
|
||||
},
|
||||
}),
|
||||
defineTool({
|
||||
description:
|
||||
"List exact admitted user messages that have not yet been used as Signal evidence.",
|
||||
name: "list_signal_evidence",
|
||||
async run() {
|
||||
return await client.query(listEvidenceRef, { organizationId, token });
|
||||
},
|
||||
}),
|
||||
defineTool({
|
||||
description:
|
||||
"Create one structured Signal from exact message IDs. Use only for actionable intent.",
|
||||
input: v.object({
|
||||
messageIds: v.array(v.string()),
|
||||
problemStatement: v.object({
|
||||
constraints: v.array(v.string()),
|
||||
desiredOutcome: v.string(),
|
||||
summary: v.string(),
|
||||
title: v.string(),
|
||||
}),
|
||||
projectId: v.string(),
|
||||
}),
|
||||
name: "create_signal",
|
||||
async run({ input }) {
|
||||
return await client.mutation(createSignalRef, {
|
||||
messageIds: input.messageIds,
|
||||
organizationId,
|
||||
problemStatement: input.problemStatement,
|
||||
processedByAgentInstanceId: organizationId,
|
||||
projectId: input.projectId,
|
||||
token,
|
||||
});
|
||||
},
|
||||
}),
|
||||
defineTool({
|
||||
description:
|
||||
"List proposed Work for a project before deciding whether a new Signal belongs to existing Work.",
|
||||
input: v.object({ projectId: v.string() }),
|
||||
name: "list_proposed_work",
|
||||
async run({ input }) {
|
||||
return await client.query(listWorksRef, {
|
||||
organizationId,
|
||||
projectId: input.projectId,
|
||||
token,
|
||||
});
|
||||
},
|
||||
}),
|
||||
defineTool({
|
||||
description:
|
||||
"Create one proposed Work from a Signal. This is idempotent for the same Signal.",
|
||||
input: v.object({ signalId: v.string() }),
|
||||
name: "create_work_from_signal",
|
||||
async run({ input }) {
|
||||
return await client.mutation(createWorkRef, {
|
||||
organizationId,
|
||||
signalId: input.signalId,
|
||||
token,
|
||||
});
|
||||
},
|
||||
}),
|
||||
defineTool({
|
||||
description:
|
||||
"Attach a Signal to existing proposed Work when it describes the same desired outcome.",
|
||||
input: v.object({ signalId: v.string(), workId: v.string() }),
|
||||
name: "attach_signal_to_work",
|
||||
async run({ input }) {
|
||||
return await client.mutation(attachSignalRef, {
|
||||
organizationId,
|
||||
signalId: input.signalId,
|
||||
token,
|
||||
workId: input.workId,
|
||||
});
|
||||
},
|
||||
}),
|
||||
];
|
||||
};
|
||||
Reference in New Issue
Block a user