- 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
146 lines
4.4 KiB
TypeScript
146 lines
4.4 KiB
TypeScript
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,
|
|
});
|
|
},
|
|
}),
|
|
];
|
|
};
|