kimi-code/packages/klient/test/memory.test.ts
Haozhe 8c766a6c30
feat(agent-core-v2): add the L3 unit layer and the Feature seam (#2678)
* feat(agent-core-v2): add the L3 unit layer and the Feature seam

- introduce the L3 Service/Fiber unit layer: the Service base class with this.provide/effect/on/get/ref capabilities, the fiber runtime with thenable FiberHandles, collection contribution points, and the per-scope-kind ScopeUnits materialization fold
- provide each scope's static registration batch as one atomic provideAll cascade transaction (waiting-area activation, sticky Failed on construction error)
- add the DI unit inspection surface: App-scope debug ledger / dependency graph / cascade history services and the kimi-inspect DI view
- add the Feature unit seam (IFeatureManager + feature assembly), port plan mode onto it, and add the contributed-command seam (agent-command domain + node-sdk RPC types)
- remove the legacy dep-graph tooling
- apply the header-only comment convention across src and test: strip non-header narration, keep the file header, tooling pragmas, and NOTE comments

* feat(kap-server): gate the event.di.* debug feed to kimi-inspect connections

- add an opt-in target set in SessionEventBroadcaster; the global fan-out
  now skips event.di.* frames for connections that never opted in, so
  kimi-web and other clients no longer receive the high-churn DI feed
- WsConnectionV1 opts a connection in when client_hello carries
  client_id 'kimi-inspect'; removeGlobalTarget drops the opt-in on close
- temporary gate until a client-declared event-type whitelist lands

* chore(agent-core-v2): fix oxlint errors in the DI unit layer

- build the live-ref container chain without aliasing this (no-this-alias)
- snapshot the materialized map with Array.from and document why the copy
  is required (no-useless-spread)

* test(klient): use string scope kinds in the lifecycle handle fakes

The engine's LifecycleScope is a string enum now; the facade test doubles
still returned the old numeric kinds and failed the handleWireSchema output
validation.

* build(nix): update the pnpmDeps fetch hash
2026-08-06 18:22:36 +08:00

72 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,
app,
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 });
});
});