diff --git a/.agents/skills/agent-core-dev/SKILL.md b/.agents/skills/agent-core-dev/SKILL.md index e208f42fa..50411e938 100644 --- a/.agents/skills/agent-core-dev/SKILL.md +++ b/.agents/skills/agent-core-dev/SKILL.md @@ -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 `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 `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 diff --git a/.agents/skills/agent-core-dev/flags.md b/.agents/skills/agent-core-dev/flags.md index 11b9c2744..fe731fc3e 100644 --- a/.agents/skills/agent-core-dev/flags.md +++ b/.agents/skills/agent-core-dev/flags.md @@ -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//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//index.ts` barrel: ```ts -// src//index.ts -import './flag'; -export * from './flag'; +// src/index.ts +import './/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. diff --git a/.agents/skills/agent-core-dev/implement.md b/.agents/skills/agent-core-dev/implement.md index a3fcff01a..77a845773 100644 --- a/.agents/skills/agent-core-dev/implement.md +++ b/.agents/skills/agent-core-dev/implement.md @@ -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//.ts`: interface (with `_serviceBrand`) + `createDecorator` identity. -2. **Impl** — `src//Service.ts`: class with `@IX` constructor deps; top-level `registerScopedService(scope, IX, Impl, type, '')`. -3. **Barrel** — `src//index.ts`: re-export contract + impl (importing it runs the registration). -4. **Entry** — `src/index.ts`: add `export * from './/index';`. -5. **Tests** — see test.md. +1. **Contract leaf** — `src//.ts`: interface (with `_serviceBrand`) + `createDecorator` identity. +2. **Impl leaf** — `src//Service.ts`: class with `@IX` constructor deps; top-level `registerScopedService(scope, IX, Impl, type, '')`. +3. **Entry** — `src/index.ts`: load each leaf precisely — `export * from './/';` for the contract and `import './/Service';` for the impl (importing the impl runs the registration). **No `src//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. diff --git a/.agents/skills/agent-core-dev/orient.md b/.agents/skills/agent-core-dev/orient.md index 8d9755b14..3047c43d4 100644 --- a/.agents/skills/agent-core-dev/orient.md +++ b/.agents/skills/agent-core-dev/orient.md @@ -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 (Ln) — . `` Keep an existing `(cross-cutting)` label as-is; barrels omit the layer (`` `` domain barrel — … ``). Write the role as a responsibility ("drives the turn lifecycle"), not a symbol list. +- **Identity line first.** Start with `` `` domain (Ln) — . `` 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** (`.ts`) state the public contract + scope: which `IXxx` they define and what it is for. - **Impl files** (`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. diff --git a/.agents/skills/agent-core-dev/server-align.md b/.agents/skills/agent-core-dev/server-align.md index 02b6c3749..1632c3692 100644 --- a/.agents/skills/agent-core-dev/server-align.md +++ b/.agents/skills/agent-core-dev/server-align.md @@ -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/.ts` — the contract you must match. -- `packages/server-v2/src/routes/.ts` — the file you are writing (create it if missing). +- `packages/kap-server/src/routes/.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/.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/.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/Legacy/ ├── Legacy.ts ← contract: protocol-typed interface + decorator ├── 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/.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/.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/.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/.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: , request_id }`; - each declared error envelope `{ code: , 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. diff --git a/.agents/skills/agent-core-dev/service-authoring.md b/.agents/skills/agent-core-dev/service-authoring.md index 390aecdab..9907f87b1 100644 --- a/.agents/skills/agent-core-dev/service-authoring.md +++ b/.agents/skills/agent-core-dev/service-authoring.md @@ -13,15 +13,14 @@ One folder per domain, **camelCase**: `session/`, `sessionActivity/`, `contextMe ├── .ts ← pure function(s): no Service suffix, no class, no registration ├── .ts ← contribution file (common): registers into another domain's extension point ├── .contrib.ts ← contribution file (uncommon / ad-hoc) -├── .types.ts ← shared types that no single interface owns -└── index.ts ← barrel: re-exports everything; importing it runs the domain's registrations +└── .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 `.ts` + `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 `./`. +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 (`.ts`) and the impl leaf (`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 `.ts` + impl `Service.ts`; barrel `index.ts`. +- One folder per domain, camelCase; one service per file pair: contract `.ts` + impl `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`/`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`. diff --git a/.agents/skills/agent-core-dev/telemetry.md b/.agents/skills/agent-core-dev/telemetry.md index 122cd20cb..7db57c14e 100644 --- a/.agents/skills/agent-core-dev/telemetry.md +++ b/.agents/skills/agent-core-dev/telemetry.md @@ -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)