Move project and host queries into native MCP with host-owned OAuth/context, while retaining memory, skills, actions and publication plugins. Add nearby-model queries and explicit chat/task/learning model defaults. Adopt Responses HTTP/SSE and native steer/queue inbox delivery, preserving scoped session ownership, pending input edits and investigation communication. Disable local-provider discovery: Server /health allowed native vLLM to probe /v1/models on port 8000. A native before/after regression prevents recurrence. Validation: 170 focused Bun tests; service/plugin typechecks; native discovery, provider-discovery and input-inbox smoke checks; no paid model inference.
41 lines
3.1 KiB
TypeScript
41 lines
3.1 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { createPromptSuggestionService, parseGeneratedSuggestions, promptSuggestionRequestSchema } from "../../src/chat/prompt-suggestions.js";
|
|
import type { LocalAgentContext } from "../../src/context/localContext.js";
|
|
|
|
const activity = { id: "alert:pressure", kind: "alert" as const, title: "城南压力告警", occurred_at: "2026-10-09T07:00:00Z", origin: "backend" as const };
|
|
const context = { actorKey: "actor-a", projectKey: "project-a", projectId: "project-a", userId: "user-a", network: "supply", traceId: "trace-a" } satisfies LocalAgentContext;
|
|
const output = JSON.stringify({ suggestions: [{ question: "核对城南压力告警的监测证据与影响范围", activity_id: activity.id }] });
|
|
|
|
describe("Agent prompt suggestions", () => {
|
|
it("accepts only bounded activity snapshots and suggestions referencing supplied records", () => {
|
|
expect(promptSuggestionRequestSchema.safeParse({ activities: Array.from({ length: 13 }, (_, i) => ({ ...activity, id: String(i) })) }).success).toBe(false);
|
|
expect(promptSuggestionRequestSchema.safeParse({ activities: [activity, activity] }).success).toBe(false);
|
|
expect(parseGeneratedSuggestions(`\`\`\`json\n${output}\n\`\`\``, [activity])).toHaveLength(1);
|
|
expect(() => parseGeneratedSuggestions(output.replace(activity.id, "other-project-alert"), [activity])).toThrow("unknown activity ID");
|
|
expect(() => parseGeneratedSuggestions(JSON.stringify({ suggestions: [JSON.parse(output).suggestions[0], JSON.parse(output).suggestions[0]] }), [activity])).toThrow("duplicate suggestion");
|
|
});
|
|
|
|
it("coalesces inference for the same actor, project, model and activity without sharing other scopes", async () => {
|
|
const calls: string[] = [];
|
|
const generate = createPromptSuggestionService({ generateText: async (prompt) => { calls.push(prompt); return output; } });
|
|
const request = { activities: [activity] };
|
|
const [a, b] = await Promise.all([generate(context, request, "test/model"), generate(context, request, "test/model")]);
|
|
expect(a).toEqual(b);
|
|
expect(calls).toHaveLength(1);
|
|
expect(calls[0]).toContain(activity.title);
|
|
await generate({ ...context, actorKey: "actor-b" }, request, "test/model");
|
|
await generate({ ...context, projectKey: "project-b" }, request, "test/model");
|
|
await generate(context, request, "test/other-model");
|
|
await generate(context, { activities: [{ ...activity, title: "城北压力告警" }] }, "test/model");
|
|
expect(calls).toHaveLength(5);
|
|
});
|
|
|
|
it("does not cache malformed model output and supports generic questions without inventing activity references", async () => {
|
|
let calls = 0;
|
|
const generate = createPromptSuggestionService({ generateText: async () => ++calls === 1 ? "invalid JSON" : '{"suggestions":[{"question":"分析供水管网需要优先核对的监测指标","activity_id":null}]}' });
|
|
await expect(generate(context, { activities: [] }, "test/model")).rejects.toThrow();
|
|
expect((await generate(context, { activities: [] }, "test/model")).suggestions[0].activity_id).toBeNull();
|
|
expect(calls).toBe(2);
|
|
});
|
|
});
|