mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 14:16:12 +00:00
fix(coach): ask the capability before deferring a memory refresh, and pin test fixtures to the new quiet time
This commit is contained in:
1 parent
c56a05925c
commit
c7d659eb11
7 files changed
+49
-20
No files matched your search
@@ -190,7 +190,7 @@ export function AssistantSection() {
|
||||
>
|
||||
<SettingsToggle
|
||||
label={t(REASONING_ADMIN_KEYS.enabled)}
|
||||
description={t("admin.assistant.reasoning.description")}
|
||||
description={t(REASONING_ADMIN_KEYS.description)}
|
||||
checked={reasoningOn}
|
||||
onCheckedChange={(checked) =>
|
||||
mutation.mutate({ aiReasoningEnabled: checked })
|
||||
|
||||
@@ -97,7 +97,22 @@ describe("runCoachMemoryRefresh", () => {
|
||||
COACH_MEMORY_QUIET_MS - 10 * 60_000,
|
||||
);
|
||||
expect(extractAndStoreFacts).not.toHaveBeenCalled();
|
||||
expect(aiCapabilityForJob).not.toHaveBeenCalled();
|
||||
// The capability is asked first, so a switched-off Coach never re-queues.
|
||||
expect(aiCapabilityForJob).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("ends without re-queueing when the Coach is off, however fresh the conversation", async () => {
|
||||
vi.mocked(aiCapabilityForJob).mockResolvedValue({
|
||||
available: false,
|
||||
reason: "user_disabled",
|
||||
onDeviceAllowed: false,
|
||||
} as never);
|
||||
newestMessage.mockResolvedValue({
|
||||
createdAt: new Date(NOW.getTime() - 60_000),
|
||||
});
|
||||
await runCoachMemoryRefresh(payload, NOW);
|
||||
expect(enqueueCoachMemoryRefresh).not.toHaveBeenCalled();
|
||||
expect(extractAndStoreFacts).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("runs on a short conversation once it is quiet (no twenty-turn gate)", async () => {
|
||||
@@ -113,9 +128,14 @@ describe("runCoachMemoryRefresh", () => {
|
||||
});
|
||||
|
||||
it("does nothing for a conversation that is gone", async () => {
|
||||
vi.mocked(aiCapabilityForJob).mockResolvedValue({
|
||||
available: true,
|
||||
reason: null,
|
||||
onDeviceAllowed: true,
|
||||
});
|
||||
newestMessage.mockResolvedValue(null);
|
||||
await runCoachMemoryRefresh(payload, NOW);
|
||||
expect(aiCapabilityForJob).not.toHaveBeenCalled();
|
||||
expect(extractAndStoreFacts).not.toHaveBeenCalled();
|
||||
expect(enqueueCoachMemoryRefresh).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -121,8 +121,8 @@ describe("v1.41 contract keys", () => {
|
||||
it("produces the whole catalog", () => {
|
||||
// Every key laid down for the release but the `change_assumption` chip
|
||||
// label, which the dialog catalog's own test covers, plus the two
|
||||
// declined-proposal replies.
|
||||
expect(keys.size).toBe(59);
|
||||
// declined-proposal replies and the admin reasoning description.
|
||||
expect(keys.size).toBe(60);
|
||||
});
|
||||
|
||||
it.each(locales)("every catalog key resolves in %s", (locale) => {
|
||||
|
||||
@@ -61,6 +61,21 @@ export async function runCoachMemoryRefresh(
|
||||
// fallback body for every locale that has no reviewed one.
|
||||
const locale = payload.locale ?? "en";
|
||||
|
||||
// The `coach` capability first, before the quiet-time check and any transcript
|
||||
// read: a job for a Coach that is switched off ends here rather than
|
||||
// re-queueing itself until the conversation goes quiet. The chat route that
|
||||
// enqueued this admitted the Coach, but the operator, the person's Coach
|
||||
// switch or a consent can change while the job waits. Stored memory stays
|
||||
// as it is; only new model work stops. The chokepoint re-checks per step.
|
||||
const capability = await aiCapabilityForJob(userId, "coach");
|
||||
if (!capability.available) {
|
||||
annotate({
|
||||
action: { name: "coach.memory.refresh.skipped" },
|
||||
meta: { reason: capability.reason },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const quiet = await quietForMs(conversationId, userId, now);
|
||||
if (quiet === null) {
|
||||
annotate({
|
||||
@@ -81,19 +96,6 @@ export async function runCoachMemoryRefresh(
|
||||
return;
|
||||
}
|
||||
|
||||
// The `coach` capability before any transcript is read. The chat route that
|
||||
// enqueued this admitted the Coach, but the operator, the person's Coach
|
||||
// switch or a consent can change while the job waits. Stored memory stays
|
||||
// as it is; only new model work stops. The chokepoint re-checks per step.
|
||||
const capability = await aiCapabilityForJob(userId, "coach");
|
||||
if (!capability.available) {
|
||||
annotate({
|
||||
action: { name: "coach.memory.refresh.skipped" },
|
||||
meta: { reason: capability.reason },
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let summaryStatus = "error";
|
||||
try {
|
||||
const result = await refreshConversationSummary(conversationId, userId, {
|
||||
|
||||
@@ -128,4 +128,5 @@ export const REASONING_SETTING_KEYS = {
|
||||
export const REASONING_ADMIN_KEYS = {
|
||||
enabled: "admin.assistant.reasoning.enabled",
|
||||
max: "admin.assistant.reasoning.max",
|
||||
description: "admin.assistant.reasoning.description",
|
||||
} as const;
|
||||
@@ -101,6 +101,10 @@ async function seed(recordId: string): Promise<void> {
|
||||
category: "preference",
|
||||
confidence: 80,
|
||||
createdAt: new Date(NOW - DAY),
|
||||
// Pinned like `createdAt`: each state re-seeds the record, and the
|
||||
// facts read publishes `updatedAt`, so a wall-clock stamp would differ
|
||||
// between states for reasons that have nothing to do with AI.
|
||||
updatedAt: new Date(NOW - DAY),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -136,7 +136,9 @@ async function seedState(state: State) {
|
||||
data: { userId: user.id, kind: "weight", localDate, occurredAt },
|
||||
});
|
||||
|
||||
// Long enough that the rolling summary has turns to fold.
|
||||
// Long enough that the rolling summary has turns to fold, and over an hour
|
||||
// old so the memory refresh finds the conversation quiet and does its work
|
||||
// instead of deferring.
|
||||
const conversation = await prisma.coachConversation.create({
|
||||
data: { userId: user.id, title: "long conversation" },
|
||||
});
|
||||
@@ -145,7 +147,7 @@ async function seedState(state: State) {
|
||||
conversationId: conversation.id,
|
||||
role: i % 2 === 0 ? "user" : "assistant",
|
||||
encryptedContent: encryptToBytes(`turn ${i}: about sleep and walks`),
|
||||
createdAt: new Date(now - (40 - i) * 60_000),
|
||||
createdAt: new Date(now - (100 - i) * 60_000),
|
||||
})),
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user