Merge branch 'kimi-code-v2' of https://github.com/MoonshotAI/kimi-code into kimi-code-v2

This commit is contained in:
_Kerman 2026-07-07 20:02:35 +08:00
commit 6710f1eb34
7 changed files with 50 additions and 71 deletions

View file

@ -28,7 +28,7 @@ Stages are ordered but not strictly linear: a test failure (stage 4) that reveal
End-to-end procedures that span the stages. Reach for these before reading the stage files individually.
- [Align (port `agent-core` → `agent-core-v2`)](align.md): split a v1 class into semantic units, fix each unit's domain / scope / Service / dependencies, then migrate the logic and tests. Use when the task is "move feature X from v1 to v2" or "port `IXxxService` to v2".
- [Server align (expose `agent-core-v2` over `server-v2`)](server-align.md): wire a v2 domain into `packages/server-v2` over `/api/v2` (native) and `/api/v1` (v1-compatible mirror), keep the wire schema byte-compatible with `packages/server` by sharing the `@moonshot-ai/protocol` schema, and isolate v1-only behavior in a `<domain>Legacy` edge adapter instead of distorting the native v2 Service. Use when the task is "expose the new v2 Service on the server", "port the v1 `/api/v1` routes to server-v2", or "keep server-v2 wire-compatible with `packages/server`".
- [Server align (expose `agent-core-v2` over `server-v2`)](server-align.md): wire a v2 domain into `packages/kap-server` over `/api/v2` (native) and `/api/v1` (v1-compatible mirror), keep the wire schema byte-compatible with `packages/server` by sharing the `@moonshot-ai/protocol` schema, and isolate v1-only behavior in a `<domain>Legacy` edge adapter instead of distorting the native v2 Service. Use when the task is "expose the new v2 Service on the server", "port the v1 `/api/v1` routes to server-v2", or "keep server-v2 wire-compatible with `packages/server`".
## Stages

View file

@ -10,7 +10,7 @@ Gate not-yet-public features behind `IFlagService.enabled(id)`, per the reposito
- `src/flag/flagRegistryService.ts``FlagRegistryService` impl; in-memory catalog seeded from import-time contributions; App scope.
- `src/flag/flag.ts``IFlagService` token + resolver types (`ExperimentalFlagMap`, `ExperimentalFlagConfig`, `ExperimentalFlagSource`, `ExperimentalFeatureState`) + `ExperimentalConfigSchema` / `ExperimentalConfig` (zod).
- `src/flag/flagService.ts``FlagService` impl + `MASTER_ENV` (`KIMI_CODE_EXPERIMENTAL_FLAG`) + `EXPERIMENTAL_SECTION` (`experimental`); reads definitions from `IFlagRegistry`; self-registers at App scope.
- `src/flag/index.ts`barrel; re-exported by `src/index.ts` at the L3 block.
- `src/flag/index.ts`**removed (no barrel)**; `src/index.ts` imports the `flag` leafs precisely instead (e.g. `import './flag/flagService'`).
- `src/<domain>/flag.ts` — each domain that owns a flag declares it here and calls `registerFlagDefinition` at the module top level (e.g. `src/microCompaction/flag.ts`). The directory already names the domain, so the file is just `flag.ts`.
## Public surface
@ -68,15 +68,14 @@ export const myFeatureFlag: FlagDefinitionInput = {
registerFlagDefinition(myFeatureFlag);
```
Then load it from the domain barrel so the top-level call runs at import time:
Then ensure the package entry `src/index.ts` imports the flag leaf precisely so the top-level call runs at import time — there is no `src/<domain>/index.ts` barrel:
```ts
// src/<domain>/index.ts
import './flag';
export * from './flag';
// src/index.ts
import './<domain>/flag';
```
`src/index.ts` already re-exports every domain barrel, so the contribution runs during bootstrap, before any scope is created — and therefore before any consumer resolves `IFlagService`.
`src/index.ts` imports every domain's leaf files precisely (one line per leaf), so the contribution runs during bootstrap, before any scope is created — and therefore before any consumer resolves `IFlagService`.
- `env` must start with `KIMI_CODE_EXPERIMENTAL_`, be unique, and not equal `KIMI_CODE_EXPERIMENTAL_FLAG`.
- `id` must not be `flag`. A duplicate `id` throws when `FlagRegistryService` drains the contributions.

View file

@ -1,14 +1,13 @@
# Stage 3 — Implement
Write the contract, implementation, registration, barrel, and entry. Each section below introduces one DI building block as you need it. Source lives in `src/_base/di/`.
Write the contract leaf, implementation leaf (with its registration), and the package-entry lines that load them. Each section below introduces one DI building block as you need it. Source lives in `src/_base/di/`.
## Standard recipe for a new `IXxxService`
1. **Contract**`src/<domain>/<domain>.ts`: interface (with `_serviceBrand`) + `createDecorator` identity.
2. **Impl**`src/<domain>/<domain>Service.ts`: class with `@IX` constructor deps; top-level `registerScopedService(scope, IX, Impl, type, '<domain>')`.
3. **Barrel**`src/<domain>/index.ts`: re-export contract + impl (importing it runs the registration).
4. **Entry**`src/index.ts`: add `export * from './<domain>/index';`.
5. **Tests** — see test.md.
1. **Contract leaf**`src/<domain>/<domain>.ts`: interface (with `_serviceBrand`) + `createDecorator` identity.
2. **Impl leaf**`src/<domain>/<domain>Service.ts`: class with `@IX` constructor deps; top-level `registerScopedService(scope, IX, Impl, type, '<domain>')`.
3. **Entry**`src/index.ts`: load each leaf precisely — `export * from './<domain>/<domain>';` for the contract and `import './<domain>/<domain>Service';` for the impl (importing the impl runs the registration). **No `src/<domain>/index.ts` barrel.**
4. **Tests** — see test.md.
There is **no central wiring file**: bindings live in each domain's impl file and are collected through import side effects.
@ -52,16 +51,12 @@ registerScopedService(
The scope a class binds to is an **intrinsic property of the class**, decided at the registration point, not the call site.
```ts
// greet/index.ts
export * from './greet';
export * from './greetService'; // importing this line runs registerScopedService
```
Then add one line to the package entry `src/index.ts`:
The impl's top-level `registerScopedService` runs as soon as the module is imported. There is no `greet/index.ts` barrel — instead, add the leafs to the package entry `src/index.ts`, one line per leaf:
```ts
export * from './greet/index';
// src/index.ts
export * from './greet/greet';
import './greet/greetService'; // this import runs registerScopedService
```
Anyone can now `accessor.get(IGreeter)` the single global instance.

View file

@ -72,7 +72,7 @@ So a Session-scoped service is not "L1" — e.g. `session` is Session-scoped but
`packages/agent-core-v2/AGENTS.md` mandates a header-only comment style:
- **Header only.** Comments live solely in the top-of-file `/** */` block — never beside functions, methods, or statements. The code is the source of truth for *how*; the header states *what the module exposes and the responsibility it owns*.
- **Identity line first.** Start with `` `<domain>` domain (Ln) — <one-line role>. `` Keep an existing `(cross-cutting)` label as-is; barrels omit the layer (`` `<domain>` domain barrel — … ``). Write the role as a responsibility ("drives the turn lifecycle"), not a symbol list.
- **Identity line first.** Start with `` `<domain>` domain (Ln) — <one-line role>. `` Keep an existing `(cross-cutting)` label as-is. Write the role as a responsibility ("drives the turn lifecycle"), not a symbol list.
- **Scope is in the filename.** `session*.ts` = Session, `agent*.ts` = Agent, no prefix = App (see service-authoring.md). State the same scope in the header so the two never drift.
- **Interface files** (`<name>.ts`) state the public contract + scope: which `IXxx` they define and what it is for.
- **Impl files** (`<name>Service.ts`) add collaborators + scope: list every imported cross-domain collaborator as a role ("persists records through `records`"); read scope from `registerScopedService(LifecycleScope.X, …)`.
@ -104,17 +104,6 @@ Contribution file example (`config.ts` inside `log/`):
*/
```
Barrel example:
```ts
/**
* `sessionMetadata` domain barrel — re-exports the session metadata contract
* (`sessionMetadata`) and its scoped service (`sessionMetadataService`).
* Importing this barrel registers the `ISessionMetadata` binding into the scope
* registry.
*/
```
## Red lines (this stage)
- Import via the `#/...` alias (mapped to `src/`); never reach into another domain's internals by relative path.

View file

@ -1,6 +1,6 @@
# Subskill — Server align (expose `agent-core-v2` over `server-v2`)
Wire a v2 domain into `packages/server-v2`, and — when the endpoint already exists in `packages/server` (v1) — keep the wire shape **byte-for-byte compatible**. This is the server-side counterpart of [align.md](align.md): `align.md` ports v1 *business logic* into v2; this file exposes the v2 result over HTTP / WS, reusing the v1 wire contract where it already exists.
Wire a v2 domain into `packages/kap-server`, and — when the endpoint already exists in `packages/server` (v1) — keep the wire shape **byte-for-byte compatible**. This is the server-side counterpart of [align.md](align.md): `align.md` ports v1 *business logic* into v2; this file exposes the v2 result over HTTP / WS, reusing the v1 wire contract where it already exists.
Use this when the task is "expose the new v2 Service on the server", "port the v1 `/sessions/:sid/...` routes to server-v2", or "make server-v2 speak the same `/api/v1` contract as `packages/server`".
@ -8,8 +8,8 @@ Use this when the task is "expose the new v2 Service on the server", "port the v
`server-v2` serves **two HTTP surfaces** off the same `agent-core-v2` scope tree:
- **`/api/v2/:sa`** — the native v2 RPC surface, driven by the `actionMap` allowlist (`packages/server-v2/src/transport/actionMap.ts`). One `resource:action` segment maps to one `Service.method`. New v2-native capabilities land here. See [edge-exposure.md](edge-exposure.md).
- **`/api/v1/...`** — the v1-compatible surface, hand-written routes in `packages/server-v2/src/routes/*.ts` that **mirror `packages/server/src/routes/*.ts` path-for-path and schema-for-schema**, mounted by `registerApiV1Routes.ts`. This exists so existing v1 clients keep working against server-v2 unchanged.
- **`/api/v2/:sa`** — the native v2 RPC surface, driven by the `actionMap` allowlist (`packages/kap-server/src/transport/actionMap.ts`). One `resource:action` segment maps to one `Service.method`. New v2-native capabilities land here. See [edge-exposure.md](edge-exposure.md).
- **`/api/v1/...`** — the v1-compatible surface, hand-written routes in `packages/kap-server/src/routes/*.ts` that **mirror `packages/server/src/routes/*.ts` path-for-path and schema-for-schema**, mounted by `registerApiV1Routes.ts`. This exists so existing v1 clients keep working against server-v2 unchanged.
The two surfaces can point at **different Services** for the same feature. v2's native `IAgentPromptService` serves `/api/v2`; a v1-shaped `IAgentPromptLegacyService` serves `/api/v1`. Keeping them separate is what lets v2's domain design stay clean while the wire stays compatible.
@ -38,13 +38,13 @@ Pick surface → Read the v1 route (if any) → Reuse / add the protocol schema
Apply the decision above. For a v1-matched endpoint, open **both** files side by side:
- `packages/server/src/routes/<resource>.ts` — the contract you must match.
- `packages/server-v2/src/routes/<resource>.ts` — the file you are writing (create it if missing).
- `packages/kap-server/src/routes/<resource>.ts` — the file you are writing (create it if missing).
The v1 route file is the **spec**. Do not re-derive the wire shape from memory or from the v2 domain model.
### 2. Reuse (or add) the protocol schema
The wire schema lives in **`@moonshot-ai/protocol`** under `packages/protocol/src/rest/<resource>.ts` (e.g. `promptSubmissionSchema`, `promptListResponseSchema`, `configResponseSchema`). Both `packages/server` and `packages/server-v2` import from it — that single import is what guarantees the two servers speak the same shape.
The wire schema lives in **`@moonshot-ai/protocol`** under `packages/protocol/src/rest/<resource>.ts` (e.g. `promptSubmissionSchema`, `promptListResponseSchema`, `configResponseSchema`). Both `packages/server` and `packages/kap-server` import from it — that single import is what guarantees the two servers speak the same shape.
Actions:
@ -60,7 +60,7 @@ When the endpoint matches a `packages/server` endpoint, the request and response
- ❌ **Renaming** a field, **changing** its type, **tightening** its validation, or **changing its meaning** is a wire break — do not do it in a mirror route. If the v2 domain genuinely needs a different shape, that shape belongs on `/api/v2`, not on the `/api/v1` mirror.
- ❌ Re-declaring the schema inline in server-v2 (even if it "looks identical") is forbidden — it drifts. One schema, one home: `packages/protocol`.
Self-check: "would a client talking to `packages/server` get a byte-identical envelope from `packages/server-v2` for the same request?" If you cannot answer yes from the shared schema, the route is wrong.
Self-check: "would a client talking to `packages/server` get a byte-identical envelope from `packages/kap-server` for the same request?" If you cannot answer yes from the shared schema, the route is wrong.
### 3. Choose native Service vs LegacyService
@ -87,8 +87,7 @@ A LegacyService is a normal v2 Service (service-authoring.md) with one extra con
packages/agent-core-v2/src/<domain>Legacy/
├── <domain>Legacy.ts ← contract: protocol-typed interface + decorator
├── <domain>LegacyService.ts ← impl: delegates to the native v2 Service(s)
├── errors.ts ← v1-compatible error codes (KimiError codes)
└── index.ts ← barrel: import './errors'; export contract + impl
└── errors.ts ← v1-compatible error codes (KimiError codes)
```
Skeleton (matches `promptLegacy/`):
@ -132,7 +131,7 @@ Conventions:
### 4. Wire the route / actionMap entry
**For `/api/v1` (mirror):** add a route file under `packages/server-v2/src/routes/<resource>.ts` using `defineRoute`, then register it in `registerApiV1Routes.ts`. Resolve the scope from the URL (`session_id` → Session scope, agent → Agent scope via `IAgentLifecycleService.getHandle`), then `accessor.get(IX)` the native or Legacy Service. Mirror the v1 file's verbs, paths (`:sid` / `{session_id}`), and `parseActionSuffix` actions (`:steer`, `:abort`) exactly.
**For `/api/v1` (mirror):** add a route file under `packages/kap-server/src/routes/<resource>.ts` using `defineRoute`, then register it in `registerApiV1Routes.ts`. Resolve the scope from the URL (`session_id` → Session scope, agent → Agent scope via `IAgentLifecycleService.getHandle`), then `accessor.get(IX)` the native or Legacy Service. Mirror the v1 file's verbs, paths (`:sid` / `{session_id}`), and `parseActionSuffix` actions (`:steer`, `:abort`) exactly.
```ts
const route = defineRoute(
@ -191,7 +190,7 @@ Match the v1 route's status codes and idempotent-conflict envelopes (e.g. `promp
### 6. Test against the v1 wire shape
Add a `packages/server-v2/test/<resource>.test.ts` that boots the server and hits the route. Assert on the **envelope + protocol shape**, not on the v2 domain internals:
Add a `packages/kap-server/test/<resource>.test.ts` that boots the server and hits the route. Assert on the **envelope + protocol shape**, not on the v2 domain internals:
- success envelope `{ code: 0, data: <protocol shape>, request_id }`;
- each declared error envelope `{ code: <ErrorCode>, msg, data, request_id }`;
@ -201,7 +200,7 @@ Where the route mirrors v1, the test is the regression guard for the schema-fide
### 7. Verify
- `pnpm -C packages/server-v2 test` — server routes green.
- `pnpm -C packages/kap-server test` — server routes green.
- `pnpm -C packages/protocol test` — schema tests green (incl. any new `rest-*.test.ts`).
- `pnpm -C packages/agent-core-v2 test` — native + Legacy Service tests green.
- `pnpm -C packages/agent-core-v2 run lint:domain` — a LegacyService is still inside the domain layers (edge adapter, L7); it must not pull business code into the edge or invert scope direction.

View file

@ -13,15 +13,14 @@ One folder per domain, **camelCase**: `session/`, `sessionActivity/`, `contextMe
├── <concern>.ts ← pure function(s): no Service suffix, no class, no registration
├── <targetDomain>.ts ← contribution file (common): registers into another domain's extension point
├── <what>.contrib.ts ← contribution file (uncommon / ad-hoc)
├── <domain>.types.ts ← shared types that no single interface owns
└── index.ts ← barrel: re-exports everything; importing it runs the domain's registrations
└── <domain>.types.ts ← shared types that no single interface owns
```
- **Strictly one service per file.** An interface file holds exactly one injectable interface and exactly one `createDecorator(...)`; an impl file holds exactly one service implementation class and exactly one `registerScopedService(...)`. No exceptions for "tightly-coupled" groups: even same-scope collaborators each get their own `<name>.ts` + `<name>Service.ts` pair.
- **Scope is in the filename.** `session*.ts` = Session, `agent*.ts` = Agent, no scope prefix = App (see [Naming](#naming)). The header comment restates the same scope.
- A domain therefore has as many impl files as it has services (e.g. `logService.ts` for the App `ILogService`, `sessionLogService.ts` for the Session `ISessionLogService`). See [Multi-Service domains](#multi-service-domains).
The package entry `src/index.ts` re-exports each domain barrel so that importing the package runs every registration side effect.
The package entry `src/index.ts` imports and `export *`s every domain's leaf files precisely (one line per leaf), so importing the package still runs every `registerScopedService(...)` side effect — exactly as the old per-domain barrels did.
## Naming
@ -259,26 +258,29 @@ A domain may define several Services. Each Service gets its own pair of files re
- **Different scopes** → the scope prefix in the Service name makes this obvious (`logService.ts` for App `ILogService`, `sessionLogService.ts` for Session `ISessionLogService`).
- **Same interface, multiple role tokens** (e.g. `IAtomicDocumentStore` and `IAtomicTomlDocumentStore` share one interface type but are distinct DI tokens) → each token is its own Service identity and must be registered and resolved independently.
The barrel (`index.ts`) re-exports every contract/impl pair so consumers still import the domain's surface from `./<domain>`.
There is no `index.ts` barrel: consumers import each contract/impl from its precise leaf path (e.g. `import { ILogService } from '#/log/log'`), never the domain directory.
## The barrel (`index.ts`)
## No barrel — the package entry loads leafs precisely
Re-export the contract, the impl(s), and any public helper modules:
A domain has **no `index.ts` barrel**. Its files are the contract leaf (`<name>.ts`) and the impl leaf (`<name>Service.ts`), and consumers import the precise file — never the directory:
```ts
/**
* `greet` domain barrel — re-exports the greet contract (`greet`) and its
* scoped service (`greetService`). Importing this barrel registers the
* `IGreeter` binding into the scope registry.
*/
export * from './greet';
export * from './greetService';
import { IGreeter, type Greeting } from '#/greet/greet';
```
- Always export the impl file — importing it is what runs `registerScopedService(...)`.
- Export helper modules only if they are part of the domain's public surface.
- The file-header comment states which bindings importing the barrel registers.
Self-registration is unchanged: `greetService.ts` keeps its top-level `registerScopedService(...)`. The package entry `src/index.ts` loads the domain's leafs precisely — `export *` for the contract, a side-effect `import` for the impl — one line per leaf:
```ts
// src/index.ts
export * from './greet/greet';
import './greet/greetService';
```
Importing the package therefore fires every `register*` side effect, exactly as the old per-domain barrels did. When you add a new domain, write the contract + impl leafs (with their top-level `register*`), then add the leaf path(s) to `src/index.ts`. **Do not create an `index.ts`.**
- Load the impl file too — its top-level `registerScopedService(...)` only runs when the module is imported.
- `export *` helper modules only if they are part of the domain's public surface.
- Each leaf's file-header comment still names the domain, scope, and (for impls) the `register*` binding it owns.
## Comments
@ -317,20 +319,15 @@ export class Greeter implements IGreeter {
registerScopedService(LifecycleScope.App, IGreeter, Greeter, InstantiationType.Eager, 'greet');
```
```ts
// greet/index.ts
export * from './greet';
export * from './greetService';
```
```ts
// src/index.ts
export * from './greet/index';
export * from './greet/greet';
import './greet/greetService';
```
## Red lines (this topic)
- One folder per domain, camelCase; one service per file pair: contract `<name>.ts` + impl `<name>Service.ts`; barrel `index.ts`.
- One folder per domain, camelCase; one service per file pair: contract `<name>.ts` + impl `<name>Service.ts`; **no `index.ts` barrel**`src/index.ts` loads each leaf file precisely.
- Exactly one injectable interface and one `createDecorator(...)` per contract file.
- Exactly one service implementation class and one `registerScopedService(...)` per impl file.
- `IXxxService` / `XxxService` naming; decorator string is lowerCamelCase, globally unique, and stable.
@ -340,5 +337,5 @@ export * from './greet/index';
- `createInstance` objects put static parameters before service parameters; scoped services put `@IX` parameters first (static params need defaults).
- Never `new` a `@IService`-carrying Service — except inside an explicit factory method, which is not a DI request.
- Events: typed per-Service event → `Event<T>`/`Emitter` from `'#/_base/event'`; cross-domain broadcast → `IEventService` from `'#/event'`.
- Barrel must export the impl file so its registration side effect runs.
- `src/index.ts` must import/export every leaf file (including the impl) so each `register*` side effect runs.
- File-header comment only; methods/fields carry no comments by default; stubs throw `NotImplementedError`.

View file

@ -11,7 +11,7 @@ Telemetry is a **layer-1 root** domain (alongside `log`): pure `App` scope, stat
- `src/telemetry/consoleAppender.ts`: `ConsoleAppender` — echoes events to a log function (dev / debug).
- `src/telemetry/cloudAppender.ts`: `CloudAppender` — batches + enriches + posts to the telemetry endpoint.
- `src/telemetry/cloudTransport.ts`: `CloudTransport` — HTTP transport behind `CloudAppender`.
- `src/telemetry/index.ts`: barrel.
- `src/telemetry/index.ts`: **removed (no barrel)**; `src/index.ts` imports the telemetry leafs precisely (e.g. `import './telemetry/telemetryService'`).
## Emitting events (business services)