kimi-code/packages/klient/test/memory.test.ts
Haozhe 4cffd732c2
feat(klient): contract-driven facade with http/ipc/memory transports (#1768)
* feat(klient): contract-driven facade with http/ipc/memory transports

- add zod-validated contract sections (global/session/agent) under
  src/contract and a facade exposing global.*, session(id).*, agent(id).*
- select transport once at creation via subpath entries
  (@moonshot-ai/klient/http|ipc|memory); drop legacy channel/client/
  httpChannel/wsChannel/wsKlient/proxy implementations
- absorb packages/server-e2e into packages/klient test/e2e suites
  (dual-backend, legacy v1, v2 wire) and remove the server-e2e workspace
- expose model registry and catalog services on kap-server v2 RPC surface

* fix(klient): derive session status from agentActivityView

The engine retired its sessionActivity service in #1751 (session busy is
now derived from agent activity views), but the facade still called the
deleted wire channel and imported the deleted engine module, breaking
typecheck and every klient suite at import time.

- drop the sessionActivity contract/registry entries and mirror the
  agentActivityView service instead (agent scope)
- compose session status() client-side from the pending interaction lists
  and each agent's agentActivityView, keeping the retired service's
  precedence and typing SessionStatus locally in the facade
- replace the deleted-channel call in the v2 smoke suite with a
  sessionInteractionService probe
- fix the legacy image-file suite to wait with the harness's
  waitForSessionBusy
2026-07-16 16:43:09 +08:00

71 lines
2.7 KiB
TypeScript

import { rm } from 'node:fs/promises';
import { describe, expect, it } from 'vitest';
import { defineKlientConformance } from './helpers/conformance.js';
import { createKlient } from '../src/transports/memory/index.js';
import { createMemoryDispatcher } from '../src/transports/memory/dispatcher.js';
import { RPCError } from '../src/core/errors.js';
import { makeEngine } from './helpers/engine.js';
defineKlientConformance('memory', async () => {
const { homeDir, app } = await makeEngine();
const klient = createKlient({ scope: app });
return {
klient,
cleanup: async () => {
await klient.close();
app.dispose();
await rm(homeDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 25 });
},
};
});
describe('memory dispatcher specifics', () => {
it('rejects unknown services and methods with RPCError(40001)', async () => {
const { homeDir, app } = await makeEngine();
const dispatcher = createMemoryDispatcher(app);
await expect(dispatcher.call({}, 'noSuchService', 'get', [])).rejects.toMatchObject({
name: 'RPCError',
code: 40001,
});
await expect(dispatcher.call({}, 'sessionIndex', 'noSuchMethod', [])).rejects.toMatchObject({
name: 'RPCError',
code: 40001,
});
app.dispose();
await rm(homeDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 25 });
});
it('reads non-function members as properties', async () => {
const { homeDir, app } = await makeEngine();
const dispatcher = createMemoryDispatcher(app);
await expect(dispatcher.call({}, 'bootstrapService', 'platform', [])).resolves.toBe(
process.platform,
);
app.dispose();
await rm(homeDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 25 });
});
it('rejects session/agent scopes for now', async () => {
const { homeDir, app } = await makeEngine();
const dispatcher = createMemoryDispatcher(app);
await expect(
dispatcher.call({ sessionId: 's1' }, 'sessionIndex', 'list', [{}]),
).rejects.toBeInstanceOf(RPCError);
app.dispose();
await rm(homeDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 25 });
});
it('delivers wire-cloned payloads (no live object identity)', async () => {
const { homeDir, app } = await makeEngine();
const klient = createKlient({ scope: app });
const list = await klient.global.workspaces.list();
// Mutating the result must not affect what a second call returns.
(list as unknown[]).push({ id: 'polluted' });
const again = await klient.global.workspaces.list();
expect(again.some((w) => w.id === 'polluted')).toBe(false);
app.dispose();
await rm(homeDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 25 });
});
});