mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
d1b8cb20a3
- Found places: the strip shows only when the answer names one of them, the named ones first. A click on a card, or "Add to trip", opens the planner's place form filled in with the place on the day in focus, its Google details (photos, hours, description) loading beside it; "+ Day" and the day grid are gone. - Trip notes: remember_for_trip runs at once without a card. The assistant keeps lasting facts the user mentions (who travels, needs, likes, budget, pace) on its own and offers the unsure ones as a follow-up; the chat shows "Noted for this trip: ..." with the notes a tap away, and reads them again so the dialog never saves over the new note. - City walks: set_city_walk_visit checks a station off on its day (visited, skipped, or the mark taken back), with day_edit as in the day plan, a change card naming the station and its walk, and an undo that restores the status before while the station is as the change left it. - Card heads are a step smaller (32px tile, smaller title and count), and the dot beside "Working on it" in the header is gone.
91 lines
4.4 KiB
TypeScript
91 lines
4.4 KiB
TypeScript
/**
|
|
* The trip notes the assistant keeps: a note goes below what is there, the
|
|
* same note is not added twice, notes that would run past the limit are
|
|
* refused whole, and other trips' notes stay as they are.
|
|
*/
|
|
import type { AgentToolPort } from '../../../../src/nest/assistant/assistant-agent';
|
|
import {
|
|
noteOf,
|
|
REMEMBER_SPEC,
|
|
rememberForTrip,
|
|
rememberingPort,
|
|
} from '../../../../src/nest/assistant/assistant-memory';
|
|
import type { SettingsService } from '../../../../src/nest/settings/settings.service';
|
|
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
function settingsWith(prefs: Record<string, unknown>) {
|
|
const upsertSetting = vi.fn(async () => undefined);
|
|
const settings = { getUserSettings: vi.fn(async () => prefs), upsertSetting } as unknown as SettingsService;
|
|
return { settings, upsertSetting };
|
|
}
|
|
|
|
describe('the trip notes', () => {
|
|
it('ASTMEM-001: a note goes below the trip notes there are, and other trips keep theirs', async () => {
|
|
const { settings, upsertSetting } = settingsWith({ assistant_memory: { '4': 'Vegetarian', '9': 'Other trip' } });
|
|
const run = await rememberForTrip(settings, 1, 4, { note: ' We travel as two. ' });
|
|
expect(run).toEqual({ ok: true, value: { notes: 'Vegetarian\nWe travel as two.' } });
|
|
expect(upsertSetting).toHaveBeenCalledWith(1, 'assistant_memory', {
|
|
'4': 'Vegetarian\nWe travel as two.',
|
|
'9': 'Other trip',
|
|
});
|
|
});
|
|
|
|
it('ASTMEM-002: the first note starts them, and a note kept already is not added again', async () => {
|
|
const first = settingsWith({});
|
|
expect(await rememberForTrip(first.settings, 1, 4, { note: 'No museums' })).toEqual({
|
|
ok: true,
|
|
value: { notes: 'No museums' },
|
|
});
|
|
expect(first.upsertSetting).toHaveBeenCalledWith(1, 'assistant_memory', { '4': 'No museums' });
|
|
|
|
const again = settingsWith({ assistant_memory: { '4': 'No museums' } });
|
|
expect((await rememberForTrip(again.settings, 1, 4, { note: 'No museums' })).ok).toBe(true);
|
|
expect(again.upsertSetting).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('ASTMEM-003: nothing to keep, or no room left, is refused and writes nothing', async () => {
|
|
const empty = settingsWith({});
|
|
expect(await rememberForTrip(empty.settings, 1, 4, { note: ' ' })).toMatchObject({ ok: false });
|
|
const full = settingsWith({ assistant_memory: { '4': 'x'.repeat(1_990) } });
|
|
expect(await rememberForTrip(full.settings, 1, 4, { note: 'One more thing to keep' })).toMatchObject({
|
|
ok: false,
|
|
});
|
|
expect(empty.upsertSetting).not.toHaveBeenCalled();
|
|
expect(full.upsertSetting).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('ASTMEM-004: the note is read trimmed and capped, and the model is offered one short sentence', () => {
|
|
expect(noteOf({ note: 42 })).toBeNull();
|
|
expect(noteOf({ note: 'y'.repeat(600) })).toHaveLength(500);
|
|
expect(REMEMBER_SPEC.parameters).toMatchObject({ required: ['note'], additionalProperties: false });
|
|
});
|
|
|
|
it('ASTMEM-005: in a trip turn the note is kept at once as a read; other tools and a journey keep the port as it is', async () => {
|
|
const port: AgentToolPort = {
|
|
specs: () => [REMEMBER_SPEC],
|
|
kindOf: (name) => (name === 'remember_for_trip' || name === 'create_place' ? 'write' : null),
|
|
pinArgs: (_name, args) => ({ args: { ...args, tripId: 4 } }),
|
|
call: vi.fn(async () => ({ ok: true, value: 'port' })),
|
|
};
|
|
const remember = vi.fn(async () => ({ ok: true, value: { notes: 'Vegetarian' } }));
|
|
const kept = rememberingPort(port, remember);
|
|
expect(kept.kindOf('remember_for_trip')).toBe('read');
|
|
expect(kept.kindOf('create_place')).toBe('write');
|
|
expect(kept.kindOf('nope')).toBeNull();
|
|
expect(kept.pinArgs('remember_for_trip', { note: 'x' })).toEqual({ args: { note: 'x' } });
|
|
expect(kept.pinArgs('create_place', {})).toEqual({ args: { tripId: 4 } });
|
|
expect(await kept.call('remember_for_trip', { note: 'Vegetarian' })).toEqual({
|
|
ok: true,
|
|
value: { notes: 'Vegetarian' },
|
|
});
|
|
expect(await kept.call('create_place', {})).toEqual({ ok: true, value: 'port' });
|
|
expect(kept.specs()).toEqual([REMEMBER_SPEC]);
|
|
expect(remember).toHaveBeenCalledTimes(1);
|
|
expect(rememberingPort(port, null)).toBe(port);
|
|
// A session that does not hold the tool (a journey's) never gains it.
|
|
const journey = rememberingPort({ ...port, kindOf: () => null }, remember);
|
|
expect(journey.kindOf('remember_for_trip')).toBeNull();
|
|
});
|
|
});
|