mirror of
https://github.com/MoonshotAI/kimi-code.git
synced 2026-08-06 15:26:26 +00:00
Compare commits
200 commits
@moonshot-
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9ec566e51 | ||
|
|
51ef78b8c9 | ||
|
|
f0614c53e5 | ||
|
|
794714ebef | ||
|
|
fa3325404b | ||
|
|
03aa66ca0c | ||
|
|
335588e259 | ||
|
|
e6e4ba2357 | ||
|
|
02c026d487 | ||
|
|
cfd14a1fe2 | ||
|
|
4d39f4fa6f | ||
|
|
8c766a6c30 | ||
|
|
ef61084009 | ||
|
|
7b2784b9b7 | ||
|
|
013203421d | ||
|
|
3c75a27da6 | ||
|
|
713bf1a5a2 | ||
|
|
34c4181437 | ||
|
|
7bd3fd9f6e | ||
|
|
d1ded01b7c | ||
|
|
68ba740ebf | ||
|
|
2b893733f9 | ||
|
|
510fbe7ec5 | ||
|
|
6f1cd7ca22 | ||
|
|
858812193a | ||
|
|
7c919f0376 | ||
|
|
53c832dfdf | ||
|
|
2b3e9a9f79 | ||
|
|
7a631522fb | ||
|
|
421e8f8065 | ||
|
|
3bd098b806 | ||
|
|
75fe068a01 | ||
|
|
f881cdd970 | ||
|
|
e3570280bd | ||
|
|
2a4990182d | ||
|
|
4e5f36aa66 | ||
|
|
541ddd2d89 | ||
|
|
e7d5a0aee7 | ||
|
|
2ee6e43124 | ||
|
|
8db7d42f23 | ||
|
|
98ee35afd2 | ||
|
|
119a33f7f1 | ||
|
|
74c321e4c6 | ||
|
|
96f77fe392 | ||
|
|
3126422757 | ||
|
|
aec755fad6 | ||
|
|
c32e661faa | ||
|
|
0abcd00f7f | ||
|
|
da6646bf57 | ||
|
|
278b6af19d | ||
|
|
85e4cf0346 | ||
|
|
c2e53aef6c | ||
|
|
2c3c5a5879 | ||
|
|
af43c5226d | ||
|
|
54c04bf03d | ||
|
|
4ac7240fff | ||
|
|
6cb615cd37 | ||
|
|
c39687318c | ||
|
|
f412e105b3 | ||
|
|
1328b32037 | ||
|
|
21185447fe | ||
|
|
c27a9f93a6 | ||
|
|
98ef0f0b2f | ||
|
|
6ba75a173b | ||
|
|
071b6a50d9 | ||
|
|
75395f6abb | ||
|
|
dfc55a5c97 | ||
|
|
29c9e2ab20 | ||
|
|
e6a655e101 | ||
|
|
3e425212b6 | ||
|
|
e22479a62e | ||
|
|
a5960b3905 | ||
|
|
bfa00807c9 | ||
|
|
7648874730 | ||
|
|
eaab2b6f28 | ||
|
|
6b56c11697 | ||
|
|
326e1fb6ce | ||
|
|
1f3f5dadaa | ||
|
|
302b2cd680 | ||
|
|
44d34bbd56 | ||
|
|
4c4df1bb05 | ||
|
|
32d693f644 | ||
|
|
071d56940f | ||
|
|
e111c878fd | ||
|
|
95a656ca61 | ||
|
|
ed7a4cc095 | ||
|
|
bb2919eb81 | ||
|
|
17dfd49768 | ||
|
|
5c0ec2938a | ||
|
|
f1a3475ad5 | ||
|
|
d8f455d694 | ||
|
|
479403e701 | ||
|
|
0f3b106c42 | ||
|
|
ea81c9a3c5 | ||
|
|
bc28e9d802 | ||
|
|
6d0a046488 | ||
|
|
d36f4c58f6 | ||
|
|
d10b1c1308 | ||
|
|
40172c7ca9 | ||
|
|
691ec4679e | ||
|
|
fa2c5ce18b | ||
|
|
1896d1a13a | ||
|
|
02d77b20d9 | ||
|
|
dbb69a2678 | ||
|
|
f8ec3d1656 | ||
|
|
b850c5f8f5 | ||
|
|
37d9bdc585 | ||
|
|
efac96c8a9 | ||
|
|
16c7189bd5 | ||
|
|
973e2a008c | ||
|
|
67dd03149f | ||
|
|
ceaa96942b | ||
|
|
f79fde2b90 | ||
|
|
d88b3775c9 | ||
|
|
de0ba9d065 | ||
|
|
d03a4886fd | ||
|
|
b0f43aea28 | ||
|
|
cdbd33c13c | ||
|
|
e556088458 | ||
|
|
425cfdf53f | ||
|
|
7e30add445 | ||
|
|
77618e38c3 | ||
|
|
29783e471a | ||
|
|
a77ee03829 | ||
|
|
086769bfad | ||
|
|
3b017821cf | ||
|
|
a9af42e698 | ||
|
|
8a45f10edd | ||
|
|
48bf3d4c28 | ||
|
|
d40d0d305d | ||
|
|
cc9b25e132 | ||
|
|
0cef160c4b | ||
|
|
bf8e967d5c | ||
|
|
7799bd7346 | ||
|
|
c497af60e6 | ||
|
|
f06eb5c60e | ||
|
|
0d00a07c02 | ||
|
|
3615b5da9f | ||
|
|
a2401cc1ed | ||
|
|
c2b2c4eb49 | ||
|
|
f4c3967a41 | ||
|
|
dad11ed44e | ||
|
|
7b62ed5b2c | ||
|
|
66f611aae9 | ||
|
|
d751b6796c | ||
|
|
5fdbdb4a22 | ||
|
|
527d485d92 | ||
|
|
ca38b7ed86 | ||
|
|
5240b5c83c | ||
|
|
188c0fcbf7 | ||
|
|
e0f2a41769 | ||
|
|
64f053cf46 | ||
|
|
c6291c3ad7 | ||
|
|
b32170b018 | ||
|
|
8bf5bacba9 | ||
|
|
8250e590f3 | ||
|
|
4c763f6763 | ||
|
|
ba921ca531 | ||
|
|
430cd382a8 | ||
|
|
ec88d352e8 | ||
|
|
b5efba7abc | ||
|
|
8e0dcf3049 | ||
|
|
0a1b5fa00f | ||
|
|
154e082488 | ||
|
|
576d650380 | ||
|
|
37eda4e59a | ||
|
|
a3699dd6aa | ||
|
|
d67a2003ab | ||
|
|
92576e4d85 | ||
|
|
e45832398d | ||
|
|
74da87a457 | ||
|
|
ce0e3ceb04 | ||
|
|
e070a580f0 | ||
|
|
a8f1ca3f10 | ||
|
|
beeb964393 | ||
|
|
6dd4fd3368 | ||
|
|
73eb5f89e0 | ||
|
|
115b0968ce | ||
|
|
71bcfba54a | ||
|
|
c2d7bebd04 | ||
|
|
efacf0452d | ||
|
|
c5b6103bb9 | ||
|
|
ad8cc85251 | ||
|
|
dde92fe5ab | ||
|
|
bcee3ac542 | ||
|
|
9223a37622 | ||
|
|
e8f2a077d3 | ||
|
|
f6f4192957 | ||
|
|
5ae60fa673 | ||
|
|
a05228c671 | ||
|
|
d71bf9e5a5 | ||
|
|
11c1683a1c | ||
|
|
df68995539 | ||
|
|
a3e773f90c | ||
|
|
8b9916c308 | ||
|
|
a41a09c33c | ||
|
|
4f3c7240c4 | ||
|
|
3086e47039 | ||
|
|
7d393b56fb | ||
|
|
ada523ae6a |
3074 changed files with 239630 additions and 118505 deletions
|
|
@ -14,7 +14,7 @@ description: Use when developing in packages/agent-core-v2 (the DI × Scope agen
|
|||
```text
|
||||
Orient → Design → Implement → Test → Verify
|
||||
│ │ │ │ │
|
||||
│ │ │ │ └─ lint:domain · typecheck · test · dep graph · red lines
|
||||
│ │ │ │ └─ lint:imports · typecheck · test · dep graph · red lines
|
||||
│ │ │ └─ test.md
|
||||
│ │ └─ implement.md (+ errors.md · flags.md · permission.md)
|
||||
│ └─ design.md
|
||||
|
|
@ -43,10 +43,10 @@ End-to-end procedures that span the stages. Reach for these before reading the s
|
|||
- Topic: [Config](config.md) — the section-registry model, App vs Session split, owning a config section, the TOML format, and the env overlay.
|
||||
- Topic: [Errors](errors.md) — co-located `XxxError`, the central code registry, wire serialization, boundary translation.
|
||||
- Topic: [Flags](flags.md) — `registerFlagDefinition`, `IFlagService.enabled(id)`, the `[experimental]` config section, resolution precedence.
|
||||
- Topic: [Permission](permission.md) — composable chain-of-responsibility kernel, policy registry + composer, `modes`/`agentTypes` metadata, `resolveExecution`/`accesses`.
|
||||
- Topic: [Permission](permission.md) — risk-only chain-of-responsibility kernel, harness constraints and product reviews as domain `onBeforeExecuteTool` veto listeners (`veto` / `allow` / `pass` / cold `waitUntil` factories), shared `toolApproval` round-trip, policy registry + composer, `modes`/`agentTypes` metadata, `resolveExecution`/`accesses`.
|
||||
- Topic: [Telemetry](telemetry.md) — emitting events via `ITelemetryService`, context propagation, and appender destinations (`ConsoleAppender` / `CloudAppender`).
|
||||
- [Stage 4 — Test](test.md): resolve the system under test by interface, pick `TestInstantiationService` vs `createScopedTestHost`, shared stubs, service groups, teardown.
|
||||
- [Stage 5 — Verify & submit](verify.md): `lint:domain`, `typecheck`, `test`, and the pre-submit checklist.
|
||||
- [Stage 5 — Verify & submit](verify.md): `lint:imports`, `typecheck`, `test`, and the pre-submit checklist.
|
||||
|
||||
## How to use this skill
|
||||
|
||||
|
|
@ -60,11 +60,11 @@ Invariants that hold across every stage. Each is expanded in the stage file note
|
|||
2. `@IX` decorates constructor parameters only; parameter order depends on construction (static-first for `createInstance`, `@IX`-first for scoped services). (service-authoring.md)
|
||||
3. Both interface and impl carry `_serviceBrand`; the `createDecorator` name is globally unique. (implement.md)
|
||||
4. Parent scope never depends on child scope — short-lived may inject long-lived, never the reverse. (orient.md)
|
||||
5. No cyclic dependencies — refactor (extract a third Service / use an event / re-scope); do not break the cycle with `Delayed`. (design.md, implement.md)
|
||||
5. No cyclic dependencies — refactor (extract a third Service / use an event / re-scope); activation timing does not break dependency cycles. (design.md, implement.md)
|
||||
6. `ServicesAccessor` is valid only during `invokeFunction` — never stash it for async use. (implement.md)
|
||||
7. Scope follows state identity — no `Map<sessionId, …>` at `App` to fake per-session state. (design.md)
|
||||
8. Foundational layers never know upstream ones; business code never depends on the edge layer (`gateway`/`rpc`). (design.md)
|
||||
9. Throw coded errors; register codes centrally; branch on `code` across the wire, never `instanceof`. (errors.md)
|
||||
10. Gate unreleased behavior behind a flag contributed via `registerFlagDefinition` and resolved through `IFlagService.enabled(id)`; no ad-hoc env toggles. (flags.md)
|
||||
11. Tests resolve the SUT by interface; shared stubs live under `test/`, never `src/`. (test.md)
|
||||
12. Config is the preference registry: only preferences that are persistable, schema'd, and user/operator-facing go in `IConfigService`. Domain-specific config (including env-only operational toggles) goes through `registerSection` + `envOverlay`. Facts → `IBootstrapService` (kept domain-agnostic — never add cron/flags/model state); session state → Session scope; constants → code. Business domains never call `IBootstrapService.getEnv()` directly. (config.md)
|
||||
12. Config is the preference registry: only preferences that are persistable, schema'd, and user/operator-facing go in `IConfigService`. Domain-specific config (including env-only operational toggles) goes through `registerConfigSection` + `envOverlay`. Facts → `IBootstrapService`, and host invocation arguments (CLI flags, host identity headers, prompt identity) → `BootstrapInput.args` / `IBootstrapService.args` — never new per-domain runtime-options services; domain runtime state (cron/flags/model) never goes onto `IBootstrapService`; session state → Session scope; constants → code. Business domains never call `IBootstrapService.getEnv()` directly. (config.md)
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ v1 is a **VSCode-style singleton container**: services self-register with `regis
|
|||
|
||||
| Concern | v1 (`agent-core`) | v2 (`agent-core-v2`) |
|
||||
|---|---|---|
|
||||
| Registration | `registerSingleton(IX, X, InstantiationType.Delayed)` | `registerScopedService(LifecycleScope.X, IX, X, InstantiationType.Delayed, 'domain')` |
|
||||
| DI import | `from '../../di'` | `from '#/_base/di/scope'` / `'#/_base/di/instantiation'` / `'#/_base/di/extensions'` / `'#/_base/di/lifecycle'` |
|
||||
| Lifetime | implicit singleton-per-container | explicit `LifecycleScope` (App/Session/Agent) — see orient.md |
|
||||
| Registration | `registerSingleton(IX, X, InstantiationType.Delayed)` | `registerScopedService(LifecycleScope.X, IX, X, ScopeActivation.OnDemand, 'domain')` |
|
||||
| DI import | `from '../../di'` | `from '#/_base/di/scope'` / `'#/_base/di/instantiation'` / `'#/_base/di/lifecycle'` |
|
||||
| Lifetime | implicit singleton-per-container | explicit `LifecycleScope` (App/Workspace/Session/Agent) — see orient.md |
|
||||
| Domain granularity | coarse (`session`, `tool`, `loop`) | fine, split by scope + responsibility |
|
||||
| Test import | `from '@moonshot-ai/agent-core/di/test'` | `from '#/_base/di/test'` |
|
||||
| Resolve SUT in tests | `ix.createInstance(Impl)` (common) | `ix.get(IX)` by interface — see test.md |
|
||||
|
|
@ -63,7 +63,7 @@ Worked example — v1 `ISessionService` (one class, ~600 lines) holds:
|
|||
- this session's metadata → **per-session** unit → v2 `sessionMetaStore` (`ISessionMetaStore`, Session);
|
||||
- this session's activity / status → **per-session** unit → v2 `sessionActivity`;
|
||||
- this session's context projection → **per-session** unit → v2 `sessionContext`;
|
||||
- child-agent lifecycle driven by a session → **per-session** unit → v2 `agentLifecycle`; create/close/archive/fork of the session itself → **global** unit → v2 `sessionLifecycle` (App).
|
||||
- child-agent lifecycle driven by a session → **per-session** unit → v2 `agentLifecycle`; create/close/archive/fork of the session itself → **per-workspace** unit → v2 `sessionLifecycle` (Workspace, one per live workspace handler).
|
||||
|
||||
A v1 class that maps cleanly to one v1 decorator often becomes **three to five** v2 Services. That is expected and correct — do not try to keep the v1 class shape.
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ Re-wire the dependencies you inventoried in step 1, now across the new v2 Servic
|
|||
- **Domain direction** — foundational layers must not know upstream ones. A cycle means a v1 relative import is now pointing the wrong way; extract a third Service or invert the notification into an event.
|
||||
- **Durable facts** — state changes that must be recorded / replayed / projected across agents go on the wire (`wireRecord`), not a direct call alone.
|
||||
|
||||
Run `lint:domain` (verify.md) as soon as the dependencies compile — it catches direction violations early.
|
||||
Run `lint:imports` (verify.md) as soon as the dependencies compile — it catches v1 imports and kosong boundary violations early.
|
||||
|
||||
### 7. Port the business logic
|
||||
|
||||
|
|
@ -157,9 +157,9 @@ import { InstantiationType, registerSingleton } from '../../di';
|
|||
registerSingleton(IXxxService, XxxService, InstantiationType.Delayed);
|
||||
|
||||
// v2
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
registerScopedService(LifecycleScope.Session, IXxxService, XxxService, InstantiationType.Delayed, 'xxx');
|
||||
import { LifecycleScope } from '#/app/scopes';
|
||||
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
|
||||
registerScopedService(LifecycleScope.Session, IXxxService, XxxService, ScopeActivation.OnDemand, 'xxx');
|
||||
```
|
||||
|
||||
**Imports:**
|
||||
|
|
@ -189,7 +189,7 @@ import { KimiError, type ErrorCode } from '#/_base/errors';
|
|||
Red lines:
|
||||
|
||||
- Do not copy a v1 file and "fix imports". Re-split first (steps 2–6); a straight copy carries v1's implicit-singleton assumptions into v2 and creates the `Map<sessionId, …>`-at-`App` anti-pattern.
|
||||
- Do not leave v1 relative imports (`from '../x/...'`) in v2 — use the `#/...` alias and respect the domain layers.
|
||||
- Do not leave v1 relative imports (`from '../x/...'`) in v2 — use the `#/...` alias.
|
||||
- Do not preserve a v1 behavior just because it exists; if the split reveals it was a workaround for the missing scope tree, drop it.
|
||||
|
||||
### 8. Port the tests
|
||||
|
|
@ -219,7 +219,7 @@ const svc = ix.get(IXxxService);
|
|||
Before submitting a port:
|
||||
|
||||
- [ ] Every piece of v1 state landed in a v2 Service whose scope matches its identity (no `Map<sessionId, …>` at `App`).
|
||||
- [ ] Each v1 dependency now points in the right scope and domain direction; `lint:domain` passes.
|
||||
- [ ] Each v1 dependency now points in the right scope direction; `lint:imports` passes.
|
||||
- [ ] Registrations use `registerScopedService` with an explicit scope and domain name; no `registerSingleton` remains.
|
||||
- [ ] Imports use the `#/...` alias; no v1 relative (`../../di`, `../../errors`) imports remain.
|
||||
- [ ] Errors are co-located coded errors; flags go through `IFlagService`.
|
||||
|
|
@ -232,4 +232,4 @@ Before submitting a port:
|
|||
- Decide scope from state identity before writing v2 code; the scope is fixed at registration.
|
||||
- Verify the domain mapping against current v2 `src/`; the table here is a starting point, not authority.
|
||||
- One Service owns state at exactly one lifetime; split global-view + per-instance into registry + per-instance.
|
||||
- A dependency cycle introduced by the port means a v1 import is now backwards — refactor, do not route around it with `Delayed`.
|
||||
- A dependency cycle introduced by the port means a v1 import is now backwards — refactor it; activation timing cannot break the cycle.
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ Keep the recommendation to the commit's footprint. If it keeps growing, that is
|
|||
|
||||
### 6. Verify
|
||||
|
||||
Point at the checks that cover the fix, per [verify.md](verify.md): `lint:domain`, `typecheck`, and the relevant `test`. Note the expected outcome rather than asserting you ran it if you did not.
|
||||
Point at the checks that cover the fix, per [verify.md](verify.md): `lint:imports`, `typecheck`, and the relevant `test`. Note the expected outcome rather than asserting you ran it if you did not.
|
||||
|
||||
## Output shape
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
How the `config` domain works and how a domain owns its configuration section. Covers the section-registry model, the App vs Session split, the TOML on-disk format, and the recipe for adding or migrating a config section.
|
||||
|
||||
The `config` domain is a thin registry + loader: it does **not** know the shape of any individual section. Each domain owns the schema (and, where needed, the TOML transform) for the config it consumes, registers the section into `IConfigRegistry`, and reads it through `IConfigService`. There is no whole-config object passed around.
|
||||
The `config` domain is a thin registry + loader: it does **not** know the shape of any individual section. Each domain owns the schema (and, where needed, the TOML transform) for the config it consumes, contributes the section (statically at module load via `registerConfigSection`, or at runtime as a `ConfigSectionContribution` collection record), and reads it through `IConfigService`. There is no whole-config object passed around.
|
||||
|
||||
## What belongs in Config
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ A value belongs in Config **iff** it satisfies all of:
|
|||
If it fails any rule, it is not Config:
|
||||
|
||||
- **Fact** (CI, platform, proxy, `HOME`) → a structured fact on
|
||||
`IBootstrapService` (the L1 startup snapshot), not Config.
|
||||
`IBootstrapService` (the startup snapshot), not Config.
|
||||
- **Derived convention** (`configPath`, `logsDir`) → `IBootstrapService` / code.
|
||||
- **Session runtime state** (active model, plan mode) → a Session-scoped
|
||||
service in the owning domain (e.g. `IProfileService`), not `config`.
|
||||
|
|
@ -42,9 +42,16 @@ If it fails any rule, it is not Config:
|
|||
|
||||
**`IBootstrapService` is domain-agnostic.** It holds only generic facts shared by
|
||||
all domains — the env bag, resolved paths, and host facts (`platform`, `arch`,
|
||||
`cwd`, `osHomeDir`, `isCI`, …). It must **never** hold state tied to a specific
|
||||
upper domain (no `cron`, no `flags`, no feature-specific fields): that couples
|
||||
the foundational layer to an upstream one.
|
||||
`cwd`, `osHomeDir`, `isCI`, …) — plus the host's process-level invocation
|
||||
arguments in `args` (explicit `agentFiles` / `skillDirs`, `requestHeaders`,
|
||||
prompt identity). `args` mirrors VS Code's `NativeParsedArgs` on the
|
||||
environment service: the host states them once via `BootstrapInput.args` at
|
||||
the composition root, and downstream services read them from
|
||||
`IBootstrapService.args` instead of through per-domain runtime-options
|
||||
services (do not add new `IXxxRuntimeOptions` services or seed functions for
|
||||
host parameters). What must **never** land on `IBootstrapService` is state
|
||||
tied to a specific upper domain (no `cron`, no `flags`, no feature-specific
|
||||
fields): that couples the foundational layer to an upstream one.
|
||||
|
||||
Any value that belongs to a specific domain — including env-only operational
|
||||
toggles (`KIMI_CRON_*`, `KIMI_CODE_EXPERIMENTAL_*`), model parameters, or feature
|
||||
|
|
@ -86,13 +93,15 @@ pass `ConfigTarget.Memory` for a per-run override that is never written to disk.
|
|||
|
||||
## Layout
|
||||
|
||||
- `src/config/config.ts` — `IConfigRegistry` / `IConfigService` tokens, `ConfigSection`, `ConfigEffectiveOverlay`, event types.
|
||||
- `src/config/configService.ts` — `ConfigRegistry` + `ConfigService` impl; self-registers at App scope.
|
||||
- `src/config/toml.ts` — generic snake_case ↔ camelCase machinery plus the registry-aware `transformTomlData` / `applySectionToToml` entry points. Per-domain normalization lives in the section owner's `configSection.ts` (registered as `fromToml` / `toToml`); this module stays free of any other domain's semantics.
|
||||
- `src/profile/thinking.ts` (owner domain, not `config`) — the `resolveThinkingEffort` helper; uses the authoritative `ThinkingConfig` from `configSection.ts`.
|
||||
- `src/config/configPure.ts` — `isPlainObject`, `deepMerge`, `omitUndefined`, `describeUnknownError`.
|
||||
- `src/app/config/config.ts` — `IConfigRegistry` / `IConfigService` tokens, `ConfigSection`, `ConfigEffectiveOverlay`, event types.
|
||||
- `src/app/config/configService.ts` — `ConfigRegistry` + `ConfigService` impl; self-registers at App scope. The registry is also the fold of the `ConfigSectionContribution` collection: it drains the module-level contributions at construction, then refolds incrementally (`added` → `registerSection`, `removed` → `unregisterSection`).
|
||||
- `src/app/config/configSectionContributions.ts` — the `ConfigSectionContribution` collection token (the runtime channel: a unit contributes with `this.provide(ConfigSectionContribution, …)`) plus the module-level `registerConfigSection` collector (the static channel, import = register).
|
||||
- `src/app/config/configOverlayContributions.ts` — the module-level `registerConfigOverlay` collector for `ConfigEffectiveOverlay`s (drained at construction like the sections).
|
||||
- `src/app/config/toml.ts` — generic snake_case ↔ camelCase machinery plus the registry-aware `transformTomlData` / `applySectionToToml` entry points. Per-domain normalization lives in the section owner's `configSection.ts` (registered as `fromToml` / `toToml`); this module stays free of any other domain's semantics.
|
||||
- `src/kosong/model/thinking.ts` (owner domain, not `config`) — the `resolveThinkingEffort` helper and the authoritative `ThinkingConfig` type (the `thinking` section itself registers from `src/app/kosongConfig/configSection.ts`).
|
||||
- `src/app/config/configPure.ts` — `isPlainObject`, `deepMerge`, `omitUndefined`, `describeUnknownError`.
|
||||
|
||||
A domain that owns a section keeps the schema in its own `configSection.ts` (e.g. `src/flag/flag.ts` for `experimental`, `src/profile/configSection.ts` for `thinking`, `src/loop/configSection.ts` for `loopControl`). A cross-section env overlay (e.g. the `KIMI_MODEL_*` synthesis) lives in the owning domain too (`src/provider/envOverlay.ts`) and is registered via `IConfigRegistry.registerEffectiveOverlay`.
|
||||
A domain that owns a section keeps the schema in its own `configSection.ts` (e.g. `src/app/flag/flag.ts` for `experimental`, `src/agent/loop/configSection.ts` for `loopControl`). Exception: kosong-owned sections (`providers`, `models`, `thinking`) — kosong is a pure, persistence-free abstraction layer that defines only the types (`src/kosong/{provider,model}`); the section constants, the zod schemas (re-derived from those types and compile-time pinned via `AssertExact<Equal<z.infer<typeof Schema>, Type>>`, see `_base/utils/typeEquality.ts`), the registrations, env bindings, and TOML transforms all live in the persistence wrapper `src/app/kosongConfig/configSection.ts`. (`modelCatalog` and `secondaryModel` have no kosong-side type at all — their sections are fully self-contained in `app/kosongConfig`, types derived from the schemas.) A cross-section env overlay (e.g. the `KIMI_MODEL_*` synthesis) lives in the wrapper too (`src/app/kosongConfig/envOverlay.ts`; the `[secondary_model]` derived-entry synthesis in `secondaryModelOverlay.ts`) and is registered via module-level `registerConfigOverlay`. The two-way sync between config sections and kosong's in-memory registries is owned by `IKosongConfigService` (`src/app/kosongConfig/kosongConfigService.ts`).
|
||||
|
||||
## Scope
|
||||
|
||||
|
|
@ -110,10 +119,15 @@ A config section is identified by a camelCase domain key (`'providers'`, `'think
|
|||
- `fromToml?: ConfigFromToml` — read-path transform (snake_case file value → in-memory shape). Defaults to a plain key-casing pass; owners register one when the on-disk shape needs custom normalization (record key preservation, nested object conversion, array entries, key renames, reshapes).
|
||||
- `toToml?: ConfigToToml` — write-path transform (in-memory value → snake_case file value). Defaults to a plain camelCase→snake_case key mapping.
|
||||
|
||||
Two contribution channels:
|
||||
|
||||
- **Static (import = register)** — the owning domain calls `registerConfigSection(domain, schema, options)` at the top level of its `configSection.ts`; `ConfigRegistry` drains the collected contributions when it is constructed. Every in-repo section uses this channel.
|
||||
- **Runtime (collection record)** — a unit contributes `this.provide(ConfigSectionContribution, { domain, schema, options })` (e.g. a feature assembled through `IFeatureManager`); the `ConfigRegistry` fold registers the section when the record lands and unregisters it when the record is withdrawn (provider disposed). User TOML values survive a withdrawal — they just stop being validated and effective.
|
||||
|
||||
Ownership rules:
|
||||
|
||||
- **One owner per section.** `registerSection` throws if a domain is registered twice.
|
||||
- **The domain that consumes a config owns its schema.** This is what keeps `config` (L2) from importing higher domains: `config` must not import `externalHooks` / `permissionRules` / `provider` / `kosong` / etc. for a section's schema. If a schema needs a domain's types, the schema lives in that domain.
|
||||
- **One owner per section.** `registerSection` throws if a domain is registered twice — the static channel fails fast when `ConfigRegistry` drains it; a conflicting runtime record is reported through `onUnexpectedError` and the first registration wins (the fold is an event path and never throws).
|
||||
- **The domain that consumes a config owns its schema.** This is what keeps `config` from depending on its consumers: `config` must not import `externalHooks` / `permissionRules` / `provider` / `kosong` / etc. for a section's schema. If a schema needs a domain's types, the schema lives in that domain.
|
||||
- **Demand-driven.** Do not register sections for config that no domain reads yet; a section appears (with its schema in the owning domain) only when a consumer appears.
|
||||
|
||||
## Env bindings
|
||||
|
|
@ -124,7 +138,7 @@ Declare the bindings with `envBindings(schema, { … })` — the field names are
|
|||
type-checked against the schema (no magic strings), and nested schemas recurse:
|
||||
|
||||
```ts
|
||||
registerSection('thinking', ThinkingConfigSchema, {
|
||||
registerConfigSection('thinking', ThinkingConfigSchema, {
|
||||
env: envBindings(ThinkingConfigSchema, {
|
||||
effort: 'KIMI_MODEL_THINKING_EFFORT',
|
||||
}),
|
||||
|
|
@ -132,7 +146,7 @@ registerSection('thinking', ThinkingConfigSchema, {
|
|||
|
||||
// nested / record section — outer key is a runtime constant, inner fields are
|
||||
// checked against the value schema:
|
||||
registerSection('providers', ProvidersSectionSchema, {
|
||||
registerConfigSection('providers', ProvidersSectionSchema, {
|
||||
env: envBindings(ProvidersSectionSchema, {
|
||||
[ENV_MODEL_PROVIDER_KEY]: envBindings(ProviderConfigSchema, {
|
||||
apiKey: 'KIMI_MODEL_API_KEY',
|
||||
|
|
@ -145,13 +159,26 @@ registerSection('providers', ProvidersSectionSchema, {
|
|||
```
|
||||
|
||||
Each field is an `EnvBinding` — a string (env var name) or
|
||||
`{ env, parse?, default? }`. IConfig resolves every field by
|
||||
`{ env, deprecatedEnv?, parse?, default? }`. IConfig resolves every field by
|
||||
`env > config.toml > default`, sets it on the effective value, and validates the
|
||||
section. Empty nested entries (no field resolved) are omitted, so a synthetic
|
||||
entry like `__kimi_env__` only appears when at least one of its env vars is set.
|
||||
When `deprecatedEnv` is set and `env` itself is absent or fails `parse`, the
|
||||
deprecated var still supplies the value and a warning diagnostic is reported —
|
||||
use it to rename an env var without breaking existing setups.
|
||||
|
||||
`stripEnv(value, rawSnake?)` removes env-derived fields before `set`/`replace`
|
||||
persists, so env overrides never leak into `config.toml`.
|
||||
`stripEnv(value, raw?, getEnv?)` removes env-derived fields before `set`/`replace`
|
||||
persists, so env overrides never leak into `config.toml`. `raw` is the section's
|
||||
env-free camelCase base (already `fromToml`-normalized), and `getEnv` reads the
|
||||
live env bag. For fields that are **both
|
||||
user-persistable and env-overridable**, register
|
||||
`stripEnv: stripEnvBoundFields(sectionEnvBindings)` (from `#/app/config/config`)
|
||||
— it derives the guard from the same bindings the read path uses: while a
|
||||
field's env var resolves to a value, writes restore the field's raw-base value
|
||||
(or drop it) instead of persisting an echoed env value; an env value that
|
||||
fails the binding's `parse` owns nothing, so writes pass through. Env-only
|
||||
fields/sections need no env check — strip them unconditionally (e.g. thinking's
|
||||
`forcedEffort`, cron's whole-section `() => undefined`).
|
||||
|
||||
Business domains read `config.get('section')`; they never read env directly, and
|
||||
never write their own env-merge logic.
|
||||
|
|
@ -164,21 +191,27 @@ never write their own env-merge logic.
|
|||
export const MySectionSchema = z.object({ /* ... */ });
|
||||
export type MySection = z.infer<typeof MySectionSchema>;
|
||||
```
|
||||
2. In the domain's service constructor, inject `IConfigRegistry` and register:
|
||||
2. Register it at the top level of the same module (import = register):
|
||||
```ts
|
||||
constructor(@IConfigRegistry registry: IConfigRegistry) {
|
||||
registry.registerSection(MY_SECTION, MySectionSchema, { defaultValue: {} });
|
||||
}
|
||||
// src/<domain>/configSection.ts
|
||||
import { registerConfigSection } from '#/app/config/configSectionContributions';
|
||||
|
||||
registerConfigSection(MY_SECTION, MySectionSchema, { defaultValue: {} });
|
||||
```
|
||||
Pick a service whose scope matches when the config is first needed. Registering from an Agent-scope service is fine — see "Late registration".
|
||||
3. Read it anywhere via `IConfigService`:
|
||||
`ConfigRegistry` drains module-level contributions when it is constructed, so the section exists before any consumer resolves `IConfigService` — no owning Service needs to be constructed first. Make sure `src/index.ts` imports the leaf so the top-level call runs.
|
||||
3. (Runtime variant) a dynamically loaded unit (e.g. one assembled through `IFeatureManager`) contributes the section as a collection record instead:
|
||||
```ts
|
||||
this.provide(ConfigSectionContribution, { domain: MY_SECTION, schema: MySectionSchema, options: { defaultValue: {} } });
|
||||
```
|
||||
The `ConfigRegistry` fold registers it incrementally and unregisters it when the unit is retracted (user TOML values survive) — see "Late registration".
|
||||
4. Read it anywhere via `IConfigService`:
|
||||
```ts
|
||||
constructor(@IConfigService private readonly config: IConfigService) {}
|
||||
// ...
|
||||
const value = this.config.get<MySection>(MY_SECTION);
|
||||
```
|
||||
4. React to edits by subscribing `IConfigService.onDidChange` and filtering on `e.domain === MY_SECTION` (see `FlagService`).
|
||||
5. Write it only through `IConfigService.set(domain, patch)` (merge) or `.replace(domain, value)` (wholesale). Never write `config.toml` directly.
|
||||
5. React to edits by subscribing `IConfigService.onDidChange` and filtering on `e.domain === MY_SECTION` (see `FlagService`).
|
||||
6. Write it only through `IConfigService.set(domain, patch)` (merge) or `.replace(domain, value)` (wholesale). Never write `config.toml` directly.
|
||||
|
||||
## Reads vs writes
|
||||
|
||||
|
|
@ -202,11 +235,11 @@ So `configure(...)` never overwrites the local file. Treat `config.toml` as the
|
|||
|
||||
## Late registration
|
||||
|
||||
`ConfigService` loads in its constructor (first `get(IConfigService)`). Domain services that register sections may be constructed later (especially Agent-scope services). To keep validation and defaults correct:
|
||||
`ConfigService` loads in its constructor (first `get(IConfigService)`). Static sections are drained before that, but a runtime-contributed section (a `ConfigSectionContribution` record) can register at any later moment. To keep validation and defaults correct:
|
||||
|
||||
- `IConfigRegistry` emits `onDidRegisterSection` whenever a section is registered.
|
||||
- `ConfigService` subscribes and, on registration, re-validates the already-loaded raw value for that domain, applies the default if the raw value is absent, re-runs the env overlay, and fires `onDidChange` if the effective value changed.
|
||||
- Before a section is registered, `get(domain)` returns the raw (transformed, unvalidated) value; consumers that need validated values should read after the owning service is constructed, or react to `onDidChange`.
|
||||
- `IConfigRegistry` emits `onDidRegisterSection` whenever a section is registered (and `onDidUnregisterSection` when a runtime record is withdrawn).
|
||||
- `ConfigService` subscribes and, on registration, re-validates the already-loaded raw value for that domain, applies the default if the raw value is absent, re-runs the env overlay, and fires `onDidChange` if the effective value changed. On unregistration it devalidates the domain — `get(domain)` falls back to the raw value.
|
||||
- Before a section is registered, `get(domain)` returns the raw (transformed, unvalidated) value; consumers that need validated values should read after the section lands, or react to `onDidChange`.
|
||||
|
||||
This means registration order is never a correctness concern — you do not need an eager bootstrap.
|
||||
|
||||
|
|
@ -214,56 +247,66 @@ This means registration order is never a correctness concern — you do not need
|
|||
|
||||
`config.toml` stores keys in **snake_case**; in-memory values are **camelCase**. `ConfigService` converts both ways by dispatching to each section's registered transform:
|
||||
|
||||
- **Read**: `transformTomlData(fileData, registry)` maps each top-level key to a domain and applies that domain's `fromToml` hook (or a plain key-casing pass when none is registered). Owner domains register their own normalization — e.g. provider `oauth`/`env`/`customHeaders`, permission `deny/allow/ask` → `rules`, `loop_control.max_steps_per_run` → `maxStepsPerTurn`, `experimental` keys preserved verbatim. When a section registers after the initial load, `ConfigService` re-applies its `fromToml` against the preserved snake_case raw value (see "Late registration"), so registration order is never a correctness concern.
|
||||
- **Read**: `transformTomlData(fileData, registry)` maps each top-level key to a domain and applies that domain's `fromToml` hook (or a plain key-casing pass when none is registered). Owner domains register their own normalization — e.g. provider `oauth`/`env`/`customHeaders`, permission `deny/allow/ask` → `rules`, `experimental` keys preserved verbatim. When a section registers after the initial load, `ConfigService` re-applies its `fromToml` against the preserved snake_case raw value (see "Late registration"), so registration order is never a correctness concern.
|
||||
- **Write**: `applySectionToToml(rawSnake, domain, value, registry)` applies the domain's `toToml` hook (or a plain camelCase→snake_case mapping) into a raw clone of the file, preserving unknown top-level keys and unknown sub-fields (lossless round-trip).
|
||||
|
||||
`ConfigService` keeps three views:
|
||||
`ConfigService` keeps four views:
|
||||
|
||||
- `rawSnake` — snake_case clone of the file; the write base, never carries the env overlay.
|
||||
- `raw` — camelCase, env-free; the read/set/replace base.
|
||||
- `effective` — validated `raw` plus the env overlay; what `get()` returns.
|
||||
- `validated` — validated `raw`, env-free; the base every live env re-application starts from, so a degraded or removed env value falls back to the file instead of a stale overlay.
|
||||
- `effective` — `validated` plus the env overlay, recomputed on load/set; `get()`/`getAll()` re-apply the overlay on a fresh `validated` copy per read rather than caching it.
|
||||
|
||||
### Renaming config keys and env vars (deprecations)
|
||||
|
||||
Renames are declared once on the section, never hand-rolled in `fromToml`:
|
||||
|
||||
```ts
|
||||
registerSection(MY_SECTION, MySectionSchema, {
|
||||
deprecations: [{ key: 'old_key', replacement: 'new_key' }], // snake_case, on-disk
|
||||
env: envBindings(MySectionSchema, {
|
||||
newKey: { env: 'KIMI_NEW_KEY', deprecatedEnv: 'KIMI_OLD_KEY', parse },
|
||||
}),
|
||||
});
|
||||
```
|
||||
|
||||
- A deprecated TOML key is **ignored** (its value no longer applies — the schema only knows the new key) and reports a warning `ConfigDiagnostic` while present; the file is never rewritten, so the warning is the migration guide. Diagnostics are recomputed on every load/reload and surface to clients via `IConfigService.diagnostics()` and `onDidChangeDiagnostics` (kap-server republishes them as the global `event.config.warning` WS event).
|
||||
- A deprecated env var still **resolves** as a fallback (new var first), with the same warning treatment, and `stripEnvBoundFields` treats it as env-owned for writes.
|
||||
- See `src/agent/loop/configSection.ts` for a worked example (`max_retries_per_step` → `max_attempts_per_step`).
|
||||
|
||||
### `KIMI_MODEL_*` env overlay
|
||||
|
||||
When `KIMI_MODEL_NAME` is set, the `provider` domain's `kimiModelEnvOverlay` (`src/provider/envOverlay.ts`) injects a reserved model alias (`__kimi_env_model__`) into `effective`, points `defaultModel` at it, and merges the request `modelOverrides`; the reserved provider (`__kimi_env__`) comes from the `providers` section env bindings. The overlay is registered via `IConfigRegistry.registerEffectiveOverlay` and applied **only to `effective`**, never to `rawSnake`, so it is never persisted. Its `strip` (plus the providers section `stripEnv`) is the final guard so a caller that read `effective` (with the overlay) cannot write the reserved entries or the shell API key back to disk. `config` itself only runs registered overlays — it does not know the `KIMI_MODEL_*` semantics.
|
||||
When `KIMI_MODEL_NAME` is set, the `kosongConfig` wrapper's `kimiModelEnvOverlay` (`src/app/kosongConfig/envOverlay.ts`) injects a reserved model alias (`__kimi_env_model__`) into `effective`, points `defaultModel` at it, and merges the request `modelOverrides`; the reserved provider (`__kimi_env__`) comes from the `providers` section env bindings. The overlay is registered via module-level `registerConfigOverlay` and applied **only to `effective`**, never to `rawSnake`, so it is never persisted. Its `strip` (plus the providers section `stripEnv`) is the final guard so a caller that read `effective` (with the overlay) cannot write the reserved entries or the shell API key back to disk. `config` itself only runs registered overlays — it does not know the `KIMI_MODEL_*` semantics.
|
||||
|
||||
## Owner-owned sections
|
||||
|
||||
`config` holds no monolithic config schema and no whole-config object. Every section is owned by the domain that consumes it: the schema (and any `fromToml` / `toToml` normalization and `stripEnv`) lives in that domain's `configSection.ts`, and the domain registers it via `IConfigRegistry.registerSection`. Cross-section env behavior (e.g. `KIMI_MODEL_*`) lives in an owner-registered `ConfigEffectiveOverlay`. To add a section, follow "Add a config section" above in the owning domain — never add schema or normalization to `config` itself.
|
||||
`config` holds no monolithic config schema and no whole-config object. Every section is owned by the domain that consumes it: the schema (and any `fromToml` / `toToml` normalization and `stripEnv`) lives in that domain's `configSection.ts`, and the domain contributes it via module-level `registerConfigSection` (or a runtime `ConfigSectionContribution` record). Cross-section env behavior (e.g. `KIMI_MODEL_*`) lives in an owner-registered `ConfigEffectiveOverlay` (module-level `registerConfigOverlay`). To add a section, follow "Add a config section" above in the owning domain — never add schema or normalization to `config` itself.
|
||||
|
||||
## Ownership map (current)
|
||||
## Ownership map (generated)
|
||||
|
||||
| Section | Owner | Layer | Status |
|
||||
|---|---|---|---|
|
||||
| `providers` | `provider` | L2 | owner-owned (`IProviderService` CRUD) |
|
||||
| `experimental` | `flag` | L3 | owner-owned |
|
||||
| `thinking` | `profile` | L4 | owner-owned |
|
||||
| `loopControl` | `loop` | L4 | owner-owned (read by `loop` + `profile`) |
|
||||
| `McpServerConfig` (type) | `mcp` | L5 | owner-owned (type only; not a registered section) |
|
||||
| `session` | `config` | L2 | in config |
|
||||
| `models` / `defaultModel` / `defaultProvider` | `kosong` | L1 | owner-owned (read by `ProviderManager`) |
|
||||
| `hooks` | `externalHooks` | L4 | owner-owned |
|
||||
| `permission` | `permissionRules` | L3 | owner-owned |
|
||||
| `background` | `background` | L5 | owner-owned |
|
||||
The authoritative, always-current list of registered sections — rendered in the on-disk `config.toml` shape, with owner file, scope, defaults, env bindings, and schema fields — is generated from the live registry:
|
||||
|
||||
- `packages/agent-core-v2/docs/config-manifest.toml` (checked in; do not edit by hand).
|
||||
- Regenerate with `pnpm --filter @moonshot-ai/agent-core-v2 gen:config-manifest` (add `--check` for a freshness check; `test/app/config/configManifest.test.ts` enforces it in CI).
|
||||
|
||||
`config` must not import from any of these owner domains; that is the whole reason the schemas, TOML normalization, and env overlays live with their owners.
|
||||
|
||||
## Layering & scope
|
||||
## Scope & dependencies
|
||||
|
||||
- `config` is **L2**. Domains that own sections import `config` (for `IConfigRegistry` / `IConfigService`) and must be at L2 or higher; lower layers need an entry in `ALLOWED_EXCEPTIONS` (e.g. `kosong>config`, `kosong>provider`).
|
||||
- Cross-domain type sharing for a config type may need an exception too (e.g. `plugin>mcp` for `McpServerConfig`). Prefer importing the type from the owning domain over re-declaring it.
|
||||
- `config` is a low-level capability: domains that own sections import `config` (for `IConfigRegistry` / `IConfigService`), never the reverse — section schemas live in the owning domain.
|
||||
- Cross-domain type sharing for a config type: prefer importing the type from the owning domain over re-declaring it (e.g. `plugin` imports `McpServerConfig` from the MCP config schema).
|
||||
- `IConfigRegistry` / `IConfigService` are **App**. Agent scope services may inject App services via ancestor lookup.
|
||||
- `config` never imports a higher domain and holds no section schemas of its own; if a section needs a type from another domain, that schema lives in that domain.
|
||||
|
||||
## Red lines (this topic)
|
||||
|
||||
- One owner per section; `registerSection` throws on duplicate domains.
|
||||
- `config` (L2) never imports a higher domain — keep section schemas in the owning domain.
|
||||
- One owner per section: a duplicate static registration throws when `ConfigRegistry` drains it; a conflicting runtime record is logged (`onUnexpectedError`) and the first registration wins.
|
||||
- `config` never imports the domains that consume it — keep section schemas in the owning domain.
|
||||
- Config is the **preference registry**: register only values that are preferences, persistable, schema'd, and user/operator-facing. Facts → `IBootstrapService`; session state → Session scope; constants → code.
|
||||
- Business domains read `config.get(...)` or structured `IBootstrapService` facts; never call `IBootstrapService.getEnv()` directly — only `config` reads the raw env bag to build overlays.
|
||||
- Keep `IBootstrapService` domain-agnostic: never add state tied to a specific upper domain (cron, flags, model params, …). Domain-specific config goes through `registerSection` + `envBindings`, read via `config.get(...)`.
|
||||
- Keep `IBootstrapService` domain-agnostic: host invocation arguments (CLI flags, host identity headers, prompt identity) go into `BootstrapInput.args` / `IBootstrapService.args` — never into new per-domain runtime-options services; domain runtime state (cron, flags, model params, …) never goes onto `IBootstrapService` at all. Domain-specific config goes through `registerConfigSection` + `envBindings`, read via `config.get(...)`.
|
||||
- Do not pass a whole config bag via options; read each section through `IConfigService`. There is no `KimiConfig` object — config is a registry of owner-owned sections.
|
||||
- `config.toml` is snake_case on disk, camelCase in memory — never write camelCase keys to disk, and never write to `config.toml` except through `IConfigService.set/replace`.
|
||||
- Reading config / calling `configure(...)` / switching model at runtime must not rewrite `config.toml`; runtime state lives in memory and the session wireRecord, not the file.
|
||||
- Never persist env overlays (`__kimi_env__` / `__kimi_env_model__` / shell API key / experimental env); overlays live only in `effective` / `Memory`.
|
||||
- Registering from an Agent-scope service is fine — the late-registration mechanism keeps validation correct; do not add an eager bootstrap.
|
||||
- Runtime contribution (a `ConfigSectionContribution` record from a unit at any scope) is fine — the late-registration mechanism keeps validation correct; the static channel needs no eager bootstrap (import = register, drained at `ConfigRegistry` construction).
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ A Service = a bundle of **state** + a set of **behaviors**, bound to a **lifetim
|
|||
| Scope | State identity (keyed by) | Lifetime |
|
||||
|---|---|---|
|
||||
| `App` | none (single global instance) | the process |
|
||||
| `Workspace` | `workspaceId` | one workspace handler (materialized once per workspace, never closed — dies with the process) |
|
||||
| `Session` | `sessionId` | one session |
|
||||
| `Agent` | `agentId` | one agent |
|
||||
|
||||
|
|
@ -33,6 +34,7 @@ A Service = a bundle of **state** + a set of **behaviors**, bound to a **lifetim
|
|||
**Q2. What is the identity of that state?**
|
||||
|
||||
- one global instance → **`App`**
|
||||
- one per workspace (shared by every session of that workspace) → **`Workspace`**
|
||||
- one per session → **`Session`**
|
||||
- one per agent → **`Agent`**
|
||||
- a mix (a global registry *and* per-instance state) → **split it** (see §3).
|
||||
|
|
@ -70,7 +72,7 @@ The standard split is "global registry / factory" + "per-instance":
|
|||
| Tier | Role | Naming tends to |
|
||||
|---|---|---|
|
||||
| `App` | global registry / catalog / factory — knows "all of them" and how to create one | `XxxStore` / `XxxRegistry` / `XxxCatalog` |
|
||||
| `Session` / `Agent` | one instance — only the state of "this one" | `XxxService` / `ISessionXxx` / `IAgentXxx` |
|
||||
| `Workspace` / `Session` / `Agent` | one instance — only the state of "this one" | `XxxService` / `IWorkspaceXxx` / `ISessionXxx` / `IAgentXxx` |
|
||||
|
||||
Canonical splits in the codebase:
|
||||
|
||||
|
|
@ -128,6 +130,8 @@ The three mechanisms above are also where a domain accepts new behavior without
|
|||
| Step into an operation in order / veto | a **hook** (`onWill`/`onDid`, `OrderedHookSlot`) | the owning scope |
|
||||
| Swap a backend (File ↔ DB ↔ S3) | a **Store / Storage token** at the byte layer (see persistence.md) | `App` (composition root) |
|
||||
|
||||
The standard shape of a "registry / catalog the domain queries" row is an L3 contribution point: the target domain owns a `collection<T>` token, contributors call `this.provide(token, record)` from a unit, and a fold service in the target domain injects the `CollectionView` (incremental `onDidChange`; provider death withdraws the record). The four in-repo seams are `ConfigSectionContribution` → `ConfigRegistry`, `AgentToolContribution` → `AgentToolActivationService`, `AgentProfileContribution` → `IAgentProfileRegistry`, and `WireModelContribution` → `WireService` (file-level pointers: `packages/agent-core-v2/AGENTS.md` §Units and contribution points).
|
||||
|
||||
Closed-for-modification means: the domain's own file is not where new scenarios branch. If a new scenario forces an edit here, an extension point is missing or misplaced.
|
||||
|
||||
## 5. Dependency direction
|
||||
|
|
@ -145,25 +149,17 @@ Add one anti-rot heuristic to keep the graph from collapsing into a clique:
|
|||
|
||||
Once a foundational component knows about an upstream scenario, it can no longer be reused by other scenarios and will almost always create a cycle.
|
||||
|
||||
### The natural layers of this repo
|
||||
### The boundaries of this repo
|
||||
|
||||
`agent-core-v2` is stratified into eight dependency layers, **L0–L7** (the `Ln` number in file headers — see orient.md for the full table and the representative domains). A domain at layer `L` may import only domains at layer `<= L`; lower layers never reach upward. `lint:domain` enforces this from the `DOMAIN_LAYER` map in `scripts/check-domain-layers.mjs`.
|
||||
`agent-core-v2` has no mechanical domain-layer numbering — dependency direction is the judgment rule above, applied per domain. What remains enforceable is a small set of specific boundaries (`lint:imports`, `scripts/check-import-boundaries.mjs`):
|
||||
|
||||
The tiers, from lowest to highest:
|
||||
- v2 never imports v1 (`@moonshot-ai/agent-core`).
|
||||
- The kosong subtree keeps its strict internal order (`contract ← protocol ← provider/model`, purity bans, the `provider/bases` registration boundary).
|
||||
|
||||
- **L0 — base infrastructure** (`_base`, errors, wire types).
|
||||
- **L1 — bridges & low-level capabilities** (logging, telemetry, event bus, environment, storage).
|
||||
- **L2 — data & cross-cutting capabilities** (records, config, providers, auth, workspace registry).
|
||||
- **L3 — registries & capabilities** (tools, permissions, flags, skills, plugins).
|
||||
- **L4 — agent behaviour** (turn, loop, prompt, profile, context, goal, plan, swarm).
|
||||
- **L5 — async lifecycle** (background, MCP, cron, sub-agent tools).
|
||||
- **L6 — coordination** (session, agent/session lifecycle, interactions, terminal).
|
||||
- **L7 — boundary / edge** (`gateway`, `rpc`, approval/question, the `*Legacy` v1 adapters).
|
||||
Two standing red lines on top of that:
|
||||
|
||||
Red lines:
|
||||
|
||||
- The **L0/L1 substrate** never imports a higher business layer.
|
||||
- Business logic never depends on the **L7 edge** layer — business code should not know REST / WebSocket exist.
|
||||
- The **base substrate** (`_base`, errors, wire types) never depends on any business domain.
|
||||
- Business logic never depends on the **edge** (`gateway`, `rpc`, the `*Legacy` v1 adapters) — business code should not know REST / WebSocket exist.
|
||||
- A cycle means knowledge was placed backwards: extract a third, more foundational Service, or invert the "notification" half into an event.
|
||||
|
||||
> Capability → orchestrator (e.g. `prompt → turn`) is allowed and present in this repo; the real red line is *inverted reuse* — a foundational / lower Service depending on a specific / upper one.
|
||||
|
|
@ -189,8 +185,9 @@ domain: `<name>` (owning scope: <Scope>)
|
|||
│ └─ (accessor) <ConsumerDomain> @<Scope> — <what they use me for>
|
||||
├─ exposes (interfaces I provide, by scope)
|
||||
│ ├─ App : <IXxxRegistry> — <role>
|
||||
│ ├─ Session : <ISessionXxx> — <role>
|
||||
│ └─ Agent : <IAgentXxx> — <role>
|
||||
│ ├─ Workspace : <IWorkspaceXxx> — <role>
|
||||
│ ├─ Session : <ISessionXxx> — <role>
|
||||
│ └─ Agent : <IAgentXxx> — <role>
|
||||
└─ depends (what I inject) tag = calling style
|
||||
└─ <DepDomain> @<Scope> direct/event/hook — <what for>
|
||||
```
|
||||
|
|
@ -228,42 +225,49 @@ Read it as:
|
|||
Worked example — `sessionLifecycle`:
|
||||
|
||||
```text
|
||||
domain: `sessionLifecycle` (owning scope: App)
|
||||
domain: `sessionLifecycle` (owning scope: Workspace)
|
||||
├─ serves (who uses me)
|
||||
│ ├─ (inject) — (none yet)
|
||||
│ ├─ (inject) — (none)
|
||||
│ └─ (accessor)
|
||||
│ ├─ sessionLegacy @App(edge) — v1-compatible create/fork/archive/…
|
||||
│ └─ gateway / rpc @App(edge) — native v2 session lifecycle actions
|
||||
├─ exposes (interfaces I provide, by scope)
|
||||
│ ├─ App : ISessionLifecycleService — owns the live session scope tree
|
||||
│ ├─ Workspace : ISessionLifecycleService — owns this workspace's live session scope tree
|
||||
│ ├─ Session : — — (per-session state lives in sessionMetadata / agentLifecycle / …)
|
||||
│ └─ Agent : — — (per-agent state lives in agentLifecycle)
|
||||
└─ depends (what I inject)
|
||||
├─ bootstrap @App direct — addresses session storage
|
||||
├─ hostEnvironment @App direct — gates scope creation on the probe
|
||||
├─ sessionIndex @App direct — persisted read model for cold resumes
|
||||
├─ storage @App direct — atomic docs + append logs
|
||||
├─ workspaceRegistry @App direct — resolves a session's workspace
|
||||
└─ event @App direct — broadcasts session-level facts (e.g. archived)
|
||||
├─ workspaceContext @Workspace seed — handler identity + persistence scope
|
||||
├─ bootstrap @App direct — addresses session storage
|
||||
├─ hostEnvironment @App direct — gates scope creation on the probe
|
||||
├─ sessionIndex @App direct — persisted read model for cold resumes
|
||||
├─ storage @App direct — atomic docs + append logs
|
||||
├─ workspaceDirs / workspaceSkillCatalog / workspaceMcp / …
|
||||
│ @Workspace direct — the handler's shared resource services
|
||||
└─ event @App direct — broadcasts session-level facts (e.g. archived)
|
||||
```
|
||||
|
||||
Cross-scope borrow for `sessionLifecycle`:
|
||||
|
||||
```text
|
||||
App scope
|
||||
SessionLifecycleService ──holds──┐
|
||||
GatewayService ───────────holds──┼──► IScopeHandle(sessionId)
|
||||
│
|
||||
│ accessor.get(ISessionMetadata) …
|
||||
│ └── resolve runs inside the Session scope
|
||||
▼
|
||||
Session scope (sessionId)
|
||||
sessionMetadata / agentLifecycle / … ← per-session services live here
|
||||
WorkspaceLifecycleService ──holds──► IScopeHandle(workspaceId) (one per live handler)
|
||||
│
|
||||
│ accessor.get(ISessionLifecycleService)
|
||||
│ └── resolve runs inside the Workspace scope
|
||||
▼
|
||||
Workspace scope (workspaceId)
|
||||
SessionLifecycleService ──holds──► IScopeHandle(sessionId)
|
||||
│
|
||||
│ accessor.get(ISessionMetadata) …
|
||||
│ └── resolve runs inside the Session scope
|
||||
▼
|
||||
Session scope (sessionId)
|
||||
sessionMetadata / agentLifecycle / … ← per-session services live here
|
||||
```
|
||||
|
||||
How the three lenses shaped it:
|
||||
|
||||
- **Scope (§2)** → the live registry of session scopes is process-wide, so it is App-scoped; per-session data stays in Session-scoped services, reached through the handle's `accessor`.
|
||||
- **Scope (§2)** → the live registry of one workspace's session scopes is per-handler, so it is Workspace-scoped; the process-wide handler registry lives in the App-scoped `workspaceLifecycle`; per-session data stays in Session-scoped services, reached through the handle's `accessor`.
|
||||
- **Dependency direction (§5)** → `sessionLifecycle` is consumed by the edge via `accessor` borrows; it never imports the edge. Every downward arrow lands on a peer or a more foundational Service.
|
||||
- **Extension points (§4)** → new per-session behavior plugs into the Session-scoped services (`sessionMetadata`, `agentLifecycle`, `sessionActivity`); new transports stay at the edge. Neither edits `sessionLifecycle`.
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ Before introducing `I{Domain}EntityService`, classify the persistence model:
|
|||
| **Append-log / event-sourced** | The authoritative record is "what happened" | `wireRecord`, `contextMemory`, `goal`, `plan`, `permission` transitions |
|
||||
| **Blob / key-value** | Large or content-addressed bytes | media offload, blob store |
|
||||
| **Indexed query / read model** | Derived, queryable view | `sessionIndex`, future `IQueryStore` projections |
|
||||
| **Registry / catalog** | Global or scoped known items | `workspaceRegistry`, `toolRegistry` |
|
||||
| **Registry / catalog** | Global or scoped known items | `workspace`, `toolRegistry` |
|
||||
| **Ephemeral runtime state** | No durable entity | active turn handle, pending interactions, terminal handles |
|
||||
|
||||
See [persistence.md](persistence.md) for the `Store → Storage → backend` rules. A domain EntityService is a business facade over those stores; it is not a replacement for the store layer.
|
||||
|
|
@ -82,7 +82,7 @@ The `session` domain owns only Session-level identity, metadata, lifecycle comma
|
|||
|---|---|---|
|
||||
| `sessionId`, `workspaceId`, `sessionDir`, `metaScope` | `sessionContext` | Seeded facts; no IO |
|
||||
| `SessionMeta` | `sessionMetadata` | Durable atomic document; entity-like |
|
||||
| Open session scope registry | `sessionLifecycle` | App-scope live handles; not the persisted entity table |
|
||||
| Open session scope registry | `sessionLifecycle` | Workspace-scope live handles, one registry per workspace handler (the process-wide handler registry is `workspaceLifecycle`); not the persisted entity table |
|
||||
| Session commands such as `archive()` | `session` | Orchestrates metadata, agent teardown, and events |
|
||||
| Persisted session list / get / count | `sessionIndex` | Backend-neutral read model |
|
||||
| Running / idle / awaiting status | `sessionActivity` | Derived from interactions and active turns; owns no state |
|
||||
|
|
@ -101,7 +101,7 @@ The `session` domain owns only Session-level identity, metadata, lifecycle comma
|
|||
| Background tasks | `background` |
|
||||
| Cron tasks | `cron` |
|
||||
| Pending approvals / questions | `interaction` / `approval` / `question` |
|
||||
| Workspace | `workspaceRegistry` |
|
||||
| Workspace | `workspace` |
|
||||
| Provider / config | `provider` / `config` |
|
||||
|
||||
Entity-service conclusion for `session`:
|
||||
|
|
|
|||
|
|
@ -6,10 +6,11 @@ The transport (`/api/v2` over HTTP + WS) lives in the **edge** layer (`gateway`/
|
|||
|
||||
## 1. The edge model
|
||||
|
||||
Three scopes, three URL shapes, one dispatcher:
|
||||
Four scopes, four URL shapes, one dispatcher:
|
||||
|
||||
```text
|
||||
GET|POST /api/v2/:sa Core
|
||||
GET|POST /api/v2/workspace/:workspace_id/:sa Workspace
|
||||
GET|POST /api/v2/session/:session_id/:sa Session
|
||||
GET|POST /api/v2/session/:session_id/agent/:agent_id/:sa Agent
|
||||
```
|
||||
|
|
@ -26,9 +27,10 @@ GET|POST /api/v2/session/:session_id/agent/:agent_id/:sa Agent
|
|||
```ts
|
||||
// actionMap — the allowlist; hides internal domain names.
|
||||
const actionMap = {
|
||||
core: { 'sessions:list': { service: ISessionIndex, method: 'list' }, ... },
|
||||
session: { 'session:read': { service: ISessionMetadata, method: 'read' }, ... },
|
||||
agent: { 'profile:getModel': { service: IProfileService, method: 'getModel' }, ... },
|
||||
core: { 'sessions:list': { service: ISessionIndex, method: 'list' }, ... },
|
||||
workspace: { 'skills:list': { service: IWorkspaceSkillCatalog, method: 'list' }, ... },
|
||||
session: { 'session:read': { service: ISessionMetadata, method: 'read' }, ... },
|
||||
agent: { 'profile:getModel': { service: IProfileService, method: 'getModel' }, ... },
|
||||
};
|
||||
```
|
||||
|
||||
|
|
@ -53,14 +55,14 @@ Read = `GET`, write = `POST`. `sid` = `session_id`, `aid` = `agent_id`.
|
|||
|
||||
| resource | action | Service.method | verb |
|
||||
|---|---|---|---|
|
||||
| `sessions` | `list` | ISessionIndex.list | GET |
|
||||
| `sessions` | `listRecent` | ISessionIndex.listRecent | GET |
|
||||
| `sessions` | `get` | ISessionIndex.get | GET |
|
||||
| `sessions` | `countActive` | ISessionIndex.countActive | GET |
|
||||
| `workspaces` | `list` | IWorkspaceRegistry.list | GET |
|
||||
| `workspaces` | `get` | IWorkspaceRegistry.get | GET |
|
||||
| `workspaces` | `createOrTouch` | IWorkspaceRegistry.createOrTouch | POST |
|
||||
| `workspaces` | `update` | IWorkspaceRegistry.update | POST |
|
||||
| `workspaces` | `delete` | IWorkspaceRegistry.delete | POST |
|
||||
| `sessions` | `count` | ISessionIndex.count | GET |
|
||||
| `workspaces` | `list` | IWorkspaceService.list | GET |
|
||||
| `workspaces` | `get` | IWorkspaceService.get | GET |
|
||||
| `workspaces` | `createOrTouch` | IWorkspaceService.createOrTouch | POST |
|
||||
| `workspaces` | `update` | IWorkspaceService.update | POST |
|
||||
| `workspaces` | `delete` | IWorkspaceService.delete | POST |
|
||||
| `config` | `get` / `getAll` / `inspect` | IConfigService.* | GET |
|
||||
| `config` | `set` / `replace` / `reload` | IConfigService.* | POST |
|
||||
| `providers` | `list` / `get` | IProviderService.* | GET |
|
||||
|
|
@ -90,7 +92,7 @@ Read = `GET`, write = `POST`. `sid` = `session_id`, `aid` = `agent_id`.
|
|||
| `questions` | `answer` | IQuestionService.answer | POST |
|
||||
| `interactions` | `listPending` | IInteractionService.listPending | GET |
|
||||
| `interactions` | `respond` | IInteractionService.respond | POST |
|
||||
| `workspace` | `setWorkDir` / `addAdditionalDir` / `removeAdditionalDir` / `resolve` | IWorkspaceContext.* | GET/POST |
|
||||
| `workspace` | `workDir` / `additionalDirs` / `resolve` | ISessionWorkspaceContext.* | GET |
|
||||
|
||||
### Agent (`/api/v2/session/:sid/agent/:aid/:resource:action`)
|
||||
|
||||
|
|
@ -103,7 +105,7 @@ Read = `GET`, write = `POST`. `sid` = `session_id`, `aid` = `agent_id`.
|
|||
| `tasks` | `list` / `get` / `readOutput` | IBackgroundService.* | GET |
|
||||
| `tasks` | `stop` / `detach` | IBackgroundService.* | POST |
|
||||
| `usage` | `status` | IUsageService.status | GET |
|
||||
| `context` | `status` | IAgentContextSizeService.get | GET |
|
||||
| `context` | `status` | IAgentTokenCountingService.get | GET |
|
||||
| `swarm` | `isActive` | ISwarmService.isActive | GET |
|
||||
| `swarm` | `enter` / `exit` | ISwarmService.* | POST |
|
||||
| `permission` | `getMode` | IPermissionModeService.mode | GET |
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ Gate not-yet-public features behind `IFlagService.enabled(id)`, per the reposito
|
|||
|
||||
## Layout
|
||||
|
||||
- `src/flag/flagRegistry.ts` — `IFlagRegistry` token + `FlagDefinitionInput` / `FlagId` / `FlagSurface` types + `registerFlagDefinition` / `getContributedFlags` (import-time contribution queue).
|
||||
- `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` — **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/multiServer/flag.ts`). The directory already names the domain, so the file is just `flag.ts`.
|
||||
- `src/app/flag/flagRegistry.ts` — `IFlagRegistry` token + `FlagDefinitionInput` / `FlagId` / `FlagSurface` types + `registerFlagDefinition` / `getContributedFlags` (import-time contribution queue).
|
||||
- `src/app/flag/flagRegistryService.ts` — `FlagRegistryService` impl; in-memory catalog seeded from import-time contributions; App scope.
|
||||
- `src/app/flag/flag.ts` — `IFlagService` token + resolver types (`ExperimentalFlagMap`, `ExperimentalFlagConfig`, `ExperimentalFlagSource`, `ExperimentalFeatureState`) + `EXPERIMENTAL_SECTION` (`experimental`) / `ExperimentalConfigSchema` (zod) + the module-level `registerConfigSection(EXPERIMENTAL_SECTION, …)` call that owns the section.
|
||||
- `src/app/flag/flagService.ts` — `FlagService` impl + `MASTER_ENV` (`KIMI_CODE_EXPERIMENTAL_FLAG`); reads definitions from `IFlagRegistry` and overrides from `IConfigService`; self-registers at App scope.
|
||||
- `src/app/flag/index.ts` — **removed (no barrel)**; `src/index.ts` imports the `flag` leafs precisely instead (e.g. `import './app/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/agent/toolSelect/flag.ts`). The directory already names the domain, so the file is just `flag.ts`.
|
||||
|
||||
## Public surface
|
||||
|
||||
|
|
@ -33,9 +33,9 @@ Highest wins; env is read live on every call (nothing cached):
|
|||
|
||||
## Config integration
|
||||
|
||||
- `FlagService` registers the `[experimental]` section into `IConfigRegistry` at construction (`registerSection('experimental', ExperimentalConfigSchema)`) and reads overrides from `IConfigService`.
|
||||
- The flag domain owns the `[experimental]` section: `src/app/flag/flag.ts` registers it at module load via `registerConfigSection(EXPERIMENTAL_SECTION, ExperimentalConfigSchema, { fromToml, toToml })` (import = register, drained by `ConfigRegistry` at construction); `FlagService` reads overrides from `IConfigService`.
|
||||
- It subscribes `IConfigService.onDidChange` and refreshes overrides whenever the `experimental` domain changes, so config edits apply live.
|
||||
- `IConfigRegistry.registerSection` throws if a domain is registered twice — `experimental` is owned exclusively by `FlagService`.
|
||||
- `ConfigRegistry.registerSection` throws if a domain is registered twice — `experimental` is owned exclusively by the flag domain.
|
||||
- `setConfigOverrides(overrides)` is an imperative escape hatch for tests and hosts without an `IConfigService`; hosts on `IConfigService` should set the `[experimental]` section instead.
|
||||
|
||||
Config shape:
|
||||
|
|
@ -54,7 +54,7 @@ Declare the definition in the owning domain's `flag.ts` and call `registerFlagDe
|
|||
`src/<domain>/flag.ts`:
|
||||
|
||||
```ts
|
||||
import { type FlagDefinitionInput, registerFlagDefinition } from '#/flag';
|
||||
import { type FlagDefinitionInput, registerFlagDefinition } from '#/app/flag/flagRegistry';
|
||||
|
||||
export const myFeatureFlag: FlagDefinitionInput = {
|
||||
id: 'my_feature',
|
||||
|
|
@ -94,8 +94,8 @@ if (!this.flags.enabled('my_feature')) return;
|
|||
|
||||
## Layering & scope
|
||||
|
||||
- Domain `flag` is registered at **L3**. It imports only `config` (L2) downward.
|
||||
- It cannot live in `_base` (L0): registering/reading the config section requires importing `config`, and L0 must not import L2.
|
||||
- Domain `flag` imports only `config` downward.
|
||||
- It cannot live in `_base`: registering/reading the config section requires importing `config`, and `_base` is pure infrastructure that must not know any business domain.
|
||||
- Scope: `IFlagRegistry` and `IFlagService` are both `App`. Env + config are process-global inputs, so there is no per-session/agent state. Flag definitions are contributed at **import time** (top-level `registerFlagDefinition` calls), so they are queued before any scope is created and drained when `FlagRegistryService` is first instantiated — before `IFlagService` is first resolved.
|
||||
- Tests build `FlagService` + `FlagRegistryService` directly with a real `ConfigRegistry`/`ConfigService` and an injected env map, then `register` the flags they exercise.
|
||||
|
||||
|
|
@ -105,4 +105,4 @@ if (!this.flags.enabled('my_feature')) return;
|
|||
- Contribute each flag from the **owning domain's** `flag.ts` (`src/<domain>/flag.ts`) via a top-level `registerFlagDefinition` call; there is no central catalog to edit. The directory names the domain, so the file is just `flag.ts`.
|
||||
- `env` must start with `KIMI_CODE_EXPERIMENTAL_`, be unique, and not equal `KIMI_CODE_EXPERIMENTAL_FLAG`; `id` must not be `flag`.
|
||||
- `FlagId` is `string` (decentralized registration) — do not reintroduce a central `FLAG_DEFINITIONS` array or a derived literal union.
|
||||
- `flag` lives at L3 and `App` scope — never in `_base`, never per-session.
|
||||
- `flag` lives at `App` scope — never in `_base`, never per-session.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ Write the contract leaf, implementation leaf (with its registration), and the pa
|
|||
## Standard recipe for a new `IXxxService`
|
||||
|
||||
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>')`.
|
||||
2. **Impl leaf** — `src/<domain>/<domain>Service.ts`: class with `@IX` constructor deps; top-level `registerScopedService(scope, IX, Impl, activation, '<domain>')`. The fourth argument is activation; the fifth is the 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.
|
||||
|
||||
|
|
@ -31,8 +31,8 @@ export const IGreeter: ServiceIdentifier<IGreeter> = createDecorator<IGreeter>('
|
|||
|
||||
```ts
|
||||
// greet/greetService.ts
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { LifecycleScope } from '#/app/scopes';
|
||||
import { registerScopedService, ScopeActivation } from '#/_base/di/scope';
|
||||
import { IGreeter } from './greet';
|
||||
|
||||
export class Greeter implements IGreeter {
|
||||
|
|
@ -41,11 +41,11 @@ export class Greeter implements IGreeter {
|
|||
}
|
||||
|
||||
registerScopedService(
|
||||
LifecycleScope.App, // lifetime: process-wide
|
||||
IGreeter, // identity
|
||||
Greeter, // implementation
|
||||
InstantiationType.Eager, // when to construct: immediately
|
||||
'greet', // domain name (for diagnostics)
|
||||
LifecycleScope.App, // lifetime: process-wide
|
||||
IGreeter, // identity
|
||||
Greeter, // implementation
|
||||
ScopeActivation.OnScopeCreated, // construct when the App scope is created
|
||||
'greet', // domain name (for diagnostics)
|
||||
);
|
||||
```
|
||||
|
||||
|
|
@ -95,10 +95,16 @@ const meta = accessor.get(ISessionMetadata); // type is ISessionMetadata
|
|||
|
||||
## §3 Scoped registration (not global)
|
||||
|
||||
Swap the `scope` argument to bind to a different tier:
|
||||
Swap the `scope` argument to bind to a different tier. Use `ScopeActivation.OnDemand` when the service should be constructed only on its first `get()`:
|
||||
|
||||
```ts
|
||||
registerScopedService(LifecycleScope.Session, ISessionMetadata, SessionMetadata, InstantiationType.Delayed, 'sessionMetadata');
|
||||
registerScopedService(
|
||||
LifecycleScope.Session,
|
||||
ISessionMetadata,
|
||||
SessionMetadata,
|
||||
ScopeActivation.OnDemand,
|
||||
'sessionMetadata',
|
||||
);
|
||||
```
|
||||
|
||||
Remember the visibility rule from orient.md: a service may inject services from its own scope or any ancestor; never from a descendant.
|
||||
|
|
@ -122,21 +128,49 @@ export class WSBroadcastService extends Disposable implements IWSBroadcastServic
|
|||
|
||||
- Extend `Disposable`, collect any `IDisposable` with `this._register(d)` (event subscriptions, `toDisposable(fn)`, etc.).
|
||||
- The container calls `dispose()` automatically when the service is torn down; child resources release in turn.
|
||||
- Disposal order is deterministic (orient.md): child scopes first, then reverse construction order within a scope.
|
||||
- Disposal order is deterministic (orient.md): child scopes first; within a scope the Ledger (`src/_base/lifecycle/`) tears entries down in strict reverse registration order, serially — `Disposable` / `DisposableStore` delegate to it.
|
||||
- Extend `Service` (from `#/_base/di/service`) instead when the unit needs capability calls on `this` (`provide` / `effect` / `on` / `get` / `ref`) — e.g. contributing a record to a `collection` token. `Service` extends `Disposable` (so `_register` is unchanged) and adds the two-phase construction protocol: `provide` / `on` / `effect` calls inside the constructor are buffered and flushed by the kernel after `Reflect.construct`; `get` / `ref` throw inside the constructor — dependencies stay constructor parameters. A manually `new`ed `Service` has no capabilities: every capability call throws.
|
||||
|
||||
## §5 Eager vs delayed instantiation
|
||||
## §5 Scope activation
|
||||
|
||||
`ScopeActivation` is the only construction-timing choice for scoped services:
|
||||
|
||||
```ts
|
||||
// Eager: constructed when the scope is created
|
||||
registerScopedService(LifecycleScope.App, ILogService, LogService, InstantiationType.Eager, 'log');
|
||||
|
||||
// Delayed: constructed on first get
|
||||
registerScopedService(LifecycleScope.App, IScopeRegistry, ScopeRegistry, InstantiationType.Delayed, 'gateway');
|
||||
export enum ScopeActivation {
|
||||
OnScopeCreated = 0,
|
||||
OnDemand = 1,
|
||||
}
|
||||
```
|
||||
|
||||
A `Delayed` service returns a **Proxy** that constructs the real instance on first property access. Listeners registered on its `onDid…` / `onWill…` events before construction are not lost — the container records them and replays the subscriptions once the instance exists.
|
||||
```ts
|
||||
// Default: construct the real instance while the App scope is created.
|
||||
registerScopedService(
|
||||
LifecycleScope.App,
|
||||
ILogService,
|
||||
LogService,
|
||||
ScopeActivation.OnScopeCreated,
|
||||
'log',
|
||||
);
|
||||
|
||||
> Rule of thumb: `Eager` for dependency-free, frequently-used, or "early side effect" services (e.g. `ILogService`); default to `Delayed` otherwise.
|
||||
// Construct the real instance on the first get(IScopeRegistry).
|
||||
registerScopedService(
|
||||
LifecycleScope.App,
|
||||
IScopeRegistry,
|
||||
ScopeRegistry,
|
||||
ScopeActivation.OnDemand,
|
||||
'gateway',
|
||||
);
|
||||
```
|
||||
|
||||
`ScopeActivation.OnScopeCreated` is the default fourth argument. Scope creation activates every registration using this mode, after constructing its dependencies. An eager constructor failure no longer fails scope creation: the unit lands in sticky `Failed` — scope creation succeeds, resolving the unit rethrows its error, and an explicit `update()` reloads it (see the bootstrap note below). Use it for ordinary services and for constructor side effects that must exist when the scope becomes ready.
|
||||
|
||||
`ScopeActivation.OnDemand` stores the descriptor without constructing the service. The first `get()` constructs and caches the real instance directly; later `get()` calls return that same instance. Use it only when construction should wait until the service is actually requested.
|
||||
|
||||
Both modes use the same dependency graph and reject cycles with `CyclicDependencyError`.
|
||||
|
||||
The complete registration signature is `registerScopedService(scope, id, ctor, activation = ScopeActivation.OnScopeCreated, domain?)`: activation is the fourth argument and domain is the fifth.
|
||||
|
||||
**Bootstrap shares the dynamic provide path.** Scope creation (`Scope.createApp` / `Scope.createChild` / `createScopedChildHandle` in `src/_base/di/scope.ts`) submits the scope kind's entire `registerScopedService` batch as ONE cascade transaction via `provideAll`: every token registers before the activation wave runs, so **registration order never matters**, and untracked transitive `createInstance` resolutions succeed inside the batch. A seed occupying a token (the `extra` tuple in `ScopeOptions`) overrides the static registration for that token. `activateScopeServices` is gone — there is no separate static activation path.
|
||||
|
||||
## §6 Using a service inside a plain function (`invokeFunction`)
|
||||
|
||||
|
|
@ -168,7 +202,7 @@ class TurnRunner {
|
|||
const runner = instantiation.createInstance(TurnRunner, 'hello', 1);
|
||||
```
|
||||
|
||||
Static params come first (you pass them), service params follow (the container fills them), then `Reflect.construct` builds the instance. This object is **not** placed in any scope's singleton cache — every call is a fresh instance.
|
||||
Static params come first (you pass them), service params follow (the container fills them), then `Reflect.construct` builds the instance. This object is **not** placed in any scope's singleton cache — every call is a fresh instance — and it is not tracked as a cascade unit either: `createInstance` products are cascade-exempt leaves that no cascade tears down or rebuilds; their owner disposes them.
|
||||
|
||||
> This is why service params must follow static params **for `createInstance`**: the container sorts by the parameter positions recorded via `@IX`. `_serviceBrand` lets the compiler tell the two kinds apart. Scoped services built by `registerScopedService` follow a different convention (`@IX` params first, optional static params after) — see service-authoring.md §constructor-conventions.
|
||||
|
||||
|
|
@ -204,7 +238,7 @@ Key points:
|
|||
- `instantiation.createChild(collection)` builds a child container whose parent pointer is the current container — so the child resolves upward to `App` services (the visibility rule).
|
||||
- Expose the child to the outside by wrapping it in a `ServicesAccessor` via `invokeFunction` (§6).
|
||||
|
||||
> Higher-level code usually calls `Scope.createChild(kind, id)` (it does the "filter descriptors + build child" for you). Drop to the manual `ServiceCollection` form only when you need explicit control.
|
||||
> Higher-level code usually calls `Scope.createChild(kind, id)` (it does the "filter descriptors + build child" for you, then submits the whole batch through `provideAll` as one cascade transaction — see §5). Drop to the manual `ServiceCollection` form only when you need explicit control; to change bindings on an already-created container, prefer `provide` / `unprovide` / `update` over rebuilding a collection. Before the static batch lands, the scope-creation point runs the kernel's `ScopeUnits` fold (`_base/di/scopeUnits.ts` — materializes the recipes contributed to `ScopeUnits(kind)` as per-scope units) and then the `ScopeOptions.assemble` hook — the session domain uses the hook to construct its seed-adapter units (`session/sessionSeed/sessionSeedAdapters.ts`) so their provided tokens exist before the session services activate.
|
||||
|
||||
## §9 Cyclic dependencies (forbidden — refactor)
|
||||
|
||||
|
|
@ -216,7 +250,7 @@ If A needs B while being created and B needs A while being created, the containe
|
|||
|
||||
### Why cycles are disallowed
|
||||
|
||||
- Scope layering makes normal dependencies a DAG (Agent → Session → App, resolving upward); a cycle is almost always a design smell.
|
||||
- Scope layering makes normal dependencies a DAG (Agent → Session → Workspace → App, resolving upward); a cycle is almost always a design smell.
|
||||
- "Making the cycle happen to work" turns construction order into an implicit contract — hard to debug.
|
||||
|
||||
v2's stance: **the dependency graph must be acyclic.**
|
||||
|
|
@ -227,9 +261,9 @@ v2's stance: **the dependency graph must be acyclic.**
|
|||
2. **Decouple with an event.** If A only needs to know about a change in B, have B emit via `IEventService` and A subscribe, rather than A holding a reference to B.
|
||||
3. **Re-partition scope.** One of them may belong at a different tier — moving it makes the cycle disappear.
|
||||
|
||||
### Delayed as a cycle-breaker (legacy escape hatch — forbidden)
|
||||
### Activation does not break cycles
|
||||
|
||||
A legacy mechanism lets a `Delayed` edge turn a "soft cycle" into a non-synchronous Proxy. **Do not use it to bypass cyclic dependencies** — it exists for historical compatibility, not to paper over your design. On `CyclicDependencyError`, refactor per the above.
|
||||
Both `ScopeActivation.OnScopeCreated` and `ScopeActivation.OnDemand` construct through the same synchronous dependency graph. Changing activation cannot make a cycle valid. On `CyclicDependencyError`, refactor per the above.
|
||||
|
||||
## Interface cheat sheet
|
||||
|
||||
|
|
@ -237,7 +271,7 @@ A legacy mechanism lets a `Delayed` edge turn a "soft cycle" into a non-synchron
|
|||
|---|---|---|
|
||||
| `createDecorator<T>(name)` → `ServiceIdentifier<T>` | §1 | identity (runtime key + compile-time type + param decorator) |
|
||||
| `@IService` | §2, §7 | declare a dependency on a constructor param |
|
||||
| `registerScopedService(scope, id, ctor, type, domain)` | §1, §3, §5 | bind an impl to a lifetime tier |
|
||||
| `registerScopedService(scope, id, ctor, activation, domain)` | §1, §3, §5 | bind an impl to a lifetime tier and construction time |
|
||||
| `ServicesAccessor.get(IX)` | §2, §6 | resolve an instance by interface |
|
||||
| `IInstantiationService.invokeFunction(fn, …)` | §6, §8 | obtain a temporary accessor inside a function |
|
||||
| `IInstantiationService.createInstance(ctor, …args)` | §7 | build a non-singleton object with deps injected |
|
||||
|
|
@ -245,6 +279,9 @@ A legacy mechanism lets a `Delayed` edge turn a "soft cycle" into a non-synchron
|
|||
| `getScopedServiceDescriptors(scope)` | §8 | retrieve all descriptors registered at a tier |
|
||||
| `Disposable` / `DisposableStore` / `IDisposable` | §4 | resource management and disposal |
|
||||
| `Scope` / `LifecycleScope` | §3, §8 | the lifetime tree |
|
||||
| `ScopeActivation` | §3, §5 | choose scope-created or first-`get()` construction |
|
||||
| `Service` (`_base/di/service`) | §4 | unit base class — `this.provide/effect/on/get/ref` capabilities, two-phase construction |
|
||||
| `collection<T>(name)` / `CollectionView<T>` (`_base/di/collection`) | §4 | contribution-point token + the fold's live view (provider death withdraws the record) |
|
||||
| `SyncDescriptor` | (tests / low-level) | package a constructor + static args into a pending descriptor |
|
||||
|
||||
> Legacy export (not used in v2, just recognize it): `refineServiceDecorator` is a VS Code leftover DI helper. v2 src/test has zero references; always use `registerScopedService`.
|
||||
|
|
@ -255,4 +292,4 @@ A legacy mechanism lets a `Delayed` edge turn a "soft cycle" into a non-synchron
|
|||
- `@IX` decorates constructor params only; parameter order depends on construction (static-first for `createInstance`, `@IX`-first for scoped services — see service-authoring.md).
|
||||
- Both interface and impl carry `_serviceBrand`; the `createDecorator` name is globally unique.
|
||||
- `ServicesAccessor` is valid only during `invokeFunction` — never stash it for async use.
|
||||
- No cyclic dependencies — refactor (extract / event / re-scope); do not break the cycle with `Delayed`.
|
||||
- No cyclic dependencies — refactor (extract / event / re-scope); activation does not change cycle detection.
|
||||
|
|
|
|||
|
|
@ -12,27 +12,31 @@ When writing business code you declare three things; the container handles the r
|
|||
|
||||
Classes talk only to interfaces and never care how an implementation is constructed.
|
||||
|
||||
## The three `LifecycleScope` tiers
|
||||
## The four `LifecycleScope` tiers
|
||||
|
||||
Lifetimes form a tree, from longest to shortest:
|
||||
|
||||
```text
|
||||
App (0) process-wide, single global instance
|
||||
└── Session (1) one session
|
||||
└── Agent (2) one agent
|
||||
App process-wide, single global instance
|
||||
└── Workspace one workspace handler (a materialized workspace root)
|
||||
└── Session one session
|
||||
└── Agent one agent
|
||||
```
|
||||
|
||||
```ts
|
||||
// src/app/scopes.ts — the business layer declares the tiers and their order;
|
||||
// the DI kernel only knows opaque string kinds plus the declared topology.
|
||||
export enum LifecycleScope {
|
||||
App = 0,
|
||||
Session = 1,
|
||||
Agent = 2,
|
||||
App = 'app',
|
||||
Workspace = 'workspace',
|
||||
Session = 'session',
|
||||
Agent = 'agent',
|
||||
}
|
||||
```
|
||||
|
||||
- A larger number = shorter life = closer to a leaf.
|
||||
- Later in the topology = shorter life = closer to a leaf.
|
||||
- "Singleton" means **one per scope**: `ILogService` is global once; each `Session` scope has its own `ISessionMetadata`.
|
||||
- `kind` strictly increases along the parent→child direction.
|
||||
- `kind` must advance along the declared topology in the parent→child direction.
|
||||
|
||||
### Visibility rule
|
||||
|
||||
|
|
@ -45,35 +49,30 @@ A child scope sees its ancestors; a parent never sees its children. Resolution w
|
|||
|
||||
### Disposal order
|
||||
|
||||
Deterministic: **child scopes die first; within one scope, instances dispose in reverse construction order** (last constructed, first disposed). Business code declares which tier it lives in and never disposes by hand.
|
||||
Deterministic: **child scopes die first; within one scope, teardown runs in strict reverse registration order, one entry at a time.** The mechanism is the Ledger (`src/_base/lifecycle/`): ordered effect bookkeeping, dual-track (sync + async disposers), serial reverse-order teardown (never parallel), with the teardown reason (`'scope-close' | 'cascade' | 'unload'`) passed through to every disposer. `Disposable` / `DisposableStore` (`src/_base/di/lifecycle.ts`) delegate to it — "reverse construction order" is a Ledger property, not a container convention. Business code declares which tier it lives in and never disposes by hand.
|
||||
|
||||
## The `(Ln)` layer number in headers
|
||||
## Dynamic DI: units and cascades
|
||||
|
||||
The `Ln` in a file-header identity line is the domain's **dependency layer** (L0–L7), **not** its `LifecycleScope`. They are easy to confuse because both are small integers, but they answer different questions:
|
||||
Registration is not the end of the story. Every unit a container tracks — static registrations and runtime `provide`s alike — lives in a small state machine owned by the scope's cascade engine (`src/_base/di/cascadeEngine.ts`, one per scope container, orchestrating tree-wide). Vocabulary you will meet in errors, tests, and the debug surface:
|
||||
|
||||
- `LifecycleScope` (App=0 / Session=1 / Agent=2) — **lifetime & visibility** (this stage).
|
||||
- Dependency layer `Ln` (L0–L7) — **who may import whom**: a domain at layer `L` may import only domains at layer `<= L`. Enforced by `lint:domain` from the authoritative `DOMAIN_LAYER` map in `scripts/check-domain-layers.mjs`.
|
||||
- **Unit states** — `Pending → Activating → Active`, plus `Unloading` during teardown and a sticky `Failed`. A construction failure parks the unit in `Failed` with no auto-retry: resolving it rethrows its error; an explicit `update()` reloads it.
|
||||
- **Waiting area** — a unit whose declared dependencies are missing sits `Pending` and auto-activates when they arrive, including cross-scope wake-up when an ancestor gains the token. An `ondemand` unit counts as available: consumers pull it transitively at materialization.
|
||||
- **Cascade transaction** — every `provide` / `unprovide` / `update` runs as one tree-wide transaction: contagion set from the persistent dependency graph (instance edges, child→parent across scopes) → abort hook → global reverse-topo teardown → apply the change → waiting-area recheck fixpoint → history ring. Static bootstrap shares this path: scope creation submits the kind's whole registration batch as one `provideAll`, so registration order never matters.
|
||||
|
||||
So a Session-scoped service is not "L1" — e.g. `session` is Session-scoped but lives at **L6**. When you write the header, read the number from the layer map, not from the scope.
|
||||
## Import boundaries
|
||||
|
||||
| Layer | Role | Representative domains |
|
||||
|---|---|---|
|
||||
| L0 | base infrastructure | `_base`, `errors`, `llmProtocol` |
|
||||
| L1 | bridges & low-level capabilities | `log`, `telemetry`, `event`, `environment`, `bootstrap`, `storage` |
|
||||
| L2 | data & cross-cutting capabilities | `records`, `wireRecord`, `config`, `provider`, `auth`, `workspaceRegistry` |
|
||||
| L3 | registries & capabilities | `tool`, `toolRegistry`, `permission*`, `flag`, `skill`, `plugin` |
|
||||
| L4 | agent behaviour | `turn`, `loop`, `prompt`, `profile`, `contextMemory`, `goal`, `plan`, `swarm` |
|
||||
| L5 | async lifecycle | `background`, `mcp`, `cron`, `agentTool` |
|
||||
| L6 | coordination | `session`, `agentLifecycle`, `sessionMetadata`, `interaction`, `terminal` |
|
||||
| L7 | boundary / edge | `gateway`, `rpc`, `approval`, `question`, `*Legacy` |
|
||||
There is no domain-layer numbering — a domain may import any other domain, guided by the dependency-direction judgment in design.md. The only mechanically enforced import boundaries are (`lint:imports`, `scripts/check-import-boundaries.mjs`):
|
||||
|
||||
- v2 never imports v1 (`@moonshot-ai/agent-core` or any subpath).
|
||||
- The kosong subtree (`src/kosong/{contract,protocol,provider,model}`) keeps its strict internal order (`contract ← protocol ← provider/model`), purity bans (no SDKs in `contract`/`protocol`), and the `provider/bases` registration boundary.
|
||||
|
||||
## File-header comment convention
|
||||
|
||||
`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. 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.
|
||||
- **Identity line first.** Start with `` `<domain>` domain — <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.** `workspace*.ts` = Workspace, `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, …)`.
|
||||
- **Contribution files** (`<targetDomain>.ts` / `<what>.contrib.ts`) state what they register into the target domain (e.g. "registers the `log` config section into `config`").
|
||||
|
|
@ -83,7 +82,7 @@ Impl file example (`sessionMetadataService.ts`):
|
|||
|
||||
```ts
|
||||
/**
|
||||
* `sessionMetadata` domain (L6) — `ISessionMetadata` implementation.
|
||||
* `sessionMetadata` domain — `ISessionMetadata` implementation.
|
||||
*
|
||||
* Persists the session metadata document (`state.json`) through the `storage`
|
||||
* access-pattern store (`IAtomicDocumentStore`), rooted at the `metaScope`
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ The target design for the agent-core permission system. Read this when touching
|
|||
|
||||
> **The permission system should be a composable, registrable chain of responsibility (a microkernel).** The kernel only runs the chain in order, first hit wins; concrete permission dimensions (policies) are contributed by their owning Domain Services through a registry; tools only declare standardized resource access (`accesses`) in `resolveExecution`, and generic dimensions consume that metadata.
|
||||
>
|
||||
> **The chain adjudicates risk only.** A policy node answers "how dangerous is this call, and may the user override that judgment?" — its `ask`/`deny` outcomes are always user-overridable. **Harness constraints are not permissions**: a mechanism that limits the agent for its own correctness (plan-mode write guard, AgentSwarm batch exclusivity, btw side-question fork, goal budget rejection) produces a hard deny with no ask channel and no per-call user exemption. Those live in their owning domains as `onBeforeExecuteTool` veto listeners that call `event.veto(...)` (precedent: `goalService.ts`'s budget/stale rejection). Product reviews (plan review, goal-start review) are likewise not permissions: the owning domain intercepts its tool with a cold `event.waitUntil(factory)` and drives the shared `IAgentToolApprovalService` round-trip itself, so the review only starts once no other listener vetoed the call.
|
||||
>
|
||||
> **Do not introduce Casbin** — the hard part here is *decision behavior* (continuations, side effects, RPC, state machines), not "match + scalar decision".
|
||||
|
||||
## 1. Problem definition
|
||||
|
|
@ -56,7 +58,7 @@ Casbin = single Strategy + data-driven. This design = multiple Strategies + chai
|
|||
|
||||
1. **The chain encodes "permission dimensions", not "tools".** Adding a tool does not lengthen the chain; only adding a dimension adds a node.
|
||||
2. **Two contribution paths:** high-frequency trivial specifics go through the **data path** (rules); low-frequency new dimensions with behavior go through the **code path** (policies).
|
||||
3. **Domain self-registration:** a domain that owns a dimension (plan/goal/swarm) registers its policy in DI, mirroring v2's existing "domain self-registers tools".
|
||||
3. **Guard/review off-chain, risk on-chain:** harness constraints and product reviews ship with their owning domain as `onBeforeExecuteTool` veto listeners (§5.4); risk dimensions contributed by a domain self-register as chain policies in DI, mirroring v2's "domain self-registers tools".
|
||||
4. **Tools declare resources; generic dimensions consume them:** bash/write/read only declare `accesses`; file/security dimensions judge centrally.
|
||||
|
||||
### 5.2 Core abstractions
|
||||
|
|
@ -106,23 +108,23 @@ Key points:
|
|||
|
||||
Most growth goes through the data path — node count is bounded by "kinds of behavior"; rule count grows with specifics (rule matching is a cheap Set/glob).
|
||||
|
||||
### 5.4 Domain self-registration
|
||||
### 5.4 Domain dimensions: guard/review via the executor veto event, policy registration for risk
|
||||
|
||||
Mirrors v2's "domain registers tools in its constructor". `PlanService` self-registers its dimensions:
|
||||
**Harness constraints and product reviews no longer live on the chain.** A domain that owns one registers an `onBeforeExecuteTool` veto listener and adjudicates through the event:
|
||||
|
||||
```ts
|
||||
// src/plan/planService.ts
|
||||
constructor(@IPermissionPolicyRegistry registry: IPermissionPolicyRegistry) {
|
||||
registry.register({ name: 'plan-mode-guard-deny', phase: 'guard',
|
||||
factory: a => new PlanModeGuardDenyPolicy(a.get(IPlanService)) });
|
||||
registry.register({ name: 'plan-mode-tool-approve', phase: 'mode',
|
||||
factory: a => new PlanModeToolApprovePolicy(a.get(IPlanService)) });
|
||||
registry.register({ name: 'exit-plan-mode-review-ask', phase: 'user-ask',
|
||||
factory: a => new ExitPlanModeReviewAskPolicy(a.get(IPlanService), a.get(IPermissionModeService)) });
|
||||
// src/plan/planService.ts — constructor
|
||||
constructor(@IAgentToolExecutorService executor, ...) {
|
||||
executor.onBeforeExecuteTool((event) => this.guardToolExecution(event));
|
||||
}
|
||||
```
|
||||
|
||||
A complex domain may register a single **composite** node externally and run a small internal chain, hiding its internal order from the global chain.
|
||||
- The veto event carries no id and no ordering contract. Listeners answer with `event.veto(result)` (first one wins, ends adjudication), `event.allow()` (final pass, ends everything including the permission gate's own listener), `event.pass(metadata)` (pass with an `executionMetadata` trace, ends nothing), or `event.waitUntil(factory)` (defer to a cold factory).
|
||||
- **Guard** (hard deny): call `event.veto(denyToolExecution(toolApproval.formatDenyMessage(...)))`. An immediate veto suppresses every pending `waitUntil` factory, so a deny can never be preceded by someone else's approval prompt.
|
||||
- **Review** (product approval): intercept the tool with `event.waitUntil(() => ...requestToolApproval(event, ask, origin))`. The factory is cold — the executor only invokes it after every listener ran without a veto or an allow, so the review's Interaction starts only once the call is otherwise clear to proceed; abstain (no statement) for every case you do not review so user rules still apply.
|
||||
- **Plain allow**: do NOT `allow()` casually — prefer putting the tool in `default-tool-approve`'s whitelist so user deny/ask rules keep their precedence; reserve `allow()` for cases like the plan-file write guard that must bypass even the permission chain.
|
||||
|
||||
**Risk dimensions contributed by a domain still go through the chain** (the registry path below): a domain whose state changes the *risk* verdict registers its policy via `IPermissionPolicyRegistry`, mirroring v2's "domain self-registers tools". A complex domain may register a single **composite** node externally and run a small internal chain, hiding its internal order from the global chain.
|
||||
|
||||
### 5.5 Tools declare resources at runtime (`resolveExecution` / `accesses`)
|
||||
|
||||
|
|
@ -170,37 +172,42 @@ Each new resource kind can pair with a generic dimension that consumes it; tools
|
|||
|
||||
### 5.6 Dimension ownership
|
||||
|
||||
| Dimension | Owner (who registers) | Type |
|
||||
| Dimension | Owner | Type |
|
||||
|---|---|---|
|
||||
| external hook veto | `externalHooks` domain | generic |
|
||||
| tool-batch exclusivity | `swarm` domain | domain-specific (ships with the AgentSwarm tool) |
|
||||
| runtime-mode posture | `permissionMode` domain | generic |
|
||||
| plan-mode constraints | `plan` domain | domain-specific |
|
||||
| goal-start approval | `goal` domain | domain-specific |
|
||||
| tool-batch exclusivity | `swarm` domain — `onBeforeExecuteTool` veto listener | harness constraint (off-chain) |
|
||||
| plan-mode write guard | `plan` domain — `onBeforeExecuteTool` veto listener | harness constraint (off-chain) |
|
||||
| plan review | `plan` domain — same listener's `waitUntil` + `toolApproval` | product review (off-chain) |
|
||||
| goal-start review | `goal` domain — veto listener's `waitUntil` + `toolApproval` | product review (off-chain) |
|
||||
| goal budget / stale rejection | `goal` domain — `onBeforeExecuteTool` veto listener | harness constraint (off-chain) |
|
||||
| btw tool disablement | `btw` domain — veto listener on the fork | harness constraint (off-chain) |
|
||||
| runtime-mode posture (auto/yolo) | `permissionMode` domain (chain nodes, pending the level×routing split) | generic |
|
||||
| static config rules | `permissionRules` domain | generic (data path) |
|
||||
| session approval memory | `permissionRules` domain | generic |
|
||||
| sensitive / special paths | generic "file-access/security" dimension | generic (consumes `accesses`) |
|
||||
| tool intrinsic risk | core permission | generic (consumes tool declarations) |
|
||||
| tool intrinsic risk | core permission (`default-tool-approve`) | generic (consumes tool declarations) |
|
||||
| workspace write trust | generic "file-access/security" dimension | generic (consumes `accesses`) |
|
||||
| fallback | core permission | generic |
|
||||
| approval round-trip | `toolApproval` domain — shared by gate asks and domain reviews | infrastructure |
|
||||
|
||||
Pattern: **specific dimensions ship with their owning domain + tool; generic dimensions register centrally and apply across tools via the declared `accesses`.**
|
||||
Pattern: **harness constraints and reviews ship with their owning domain as `onBeforeExecuteTool` veto listeners; risk dimensions ship as chain policies (self-registered once the registry lands); generic dimensions register centrally and apply across tools via the declared `accesses`.**
|
||||
|
||||
## 6. Evolution path
|
||||
|
||||
Incremental, not big-bang:
|
||||
|
||||
1. **Registry + Composer (zero behavior change).** Replace the 19 hardcoded `new`s in v2 `PermissionPolicyService` with reads from `IPermissionPolicyRegistry`; register existing policies as-is. Immediately gain multi-agent/mode selectable chains and an external registration entry.
|
||||
2. **Declarative modes.** Lift the mode guards in `YoloModeApprove` / `AutoModeApprove` into `modes` metadata.
|
||||
3. **Sink domain dimensions.** Move registration of plan/goal/swarm policies into their owning domain service constructors.
|
||||
1. ~~**Sink domain dimensions.**~~ **Done** — plan guard/review, goal-start review, swarm batch exclusivity, and btw deny-all moved out of the chain into their owning domains as `onBeforeExecuteTool` veto listeners (immediate `veto` / `allow` / `pass` statements plus cold `waitUntil` factories for approval round-trips); the shared approval round-trip was extracted to `IAgentToolApprovalService`; `registerPolicy` was removed (btw was its only production user). The chain now holds 12 risk-adjudication nodes only.
|
||||
2. **Level × routing split.** Separate "risk level" (read-only / read-write / yolo posture — what `yolo-mode-approve` really is) from "interaction routing" (what `auto-mode-approve` / `auto-mode-ask-user-question-deny` really are: route permission asks and reviews without the user). The routing layer lands on the `session/approval` broker; the three remaining mode policies leave the chain here.
|
||||
3. **Registry + Composer.** Replace the hardcoded `new`s in `PermissionPolicyService` with reads from `IPermissionPolicyRegistry`; lift mode guards into `modes` metadata. Chain shape becomes selectable per `(agent, mode)` and externally extensible.
|
||||
4. **(On demand) extend resource types.** When non-file resources (network/DB/shell) need structural dimensions, extend the `ToolResourceAccess` union.
|
||||
5. **(On demand) swap the matching kernel for Casbin.** Only when external rules genuinely need RBAC/ABAC semantics, swap the data-path rule-matching kernel for Casbin. Not before.
|
||||
|
||||
## Red lines (this topic)
|
||||
|
||||
- Do not introduce Casbin — decisions are behavior bundles, not scalar effects.
|
||||
- The chain adjudicates risk only. A node whose deny/ask the user cannot per-call exempt is a harness constraint: implement it as an `onBeforeExecuteTool` veto listener in the owning domain (`event.veto(...)` / `event.allow()`), never as a chain policy.
|
||||
- Product reviews (plan/goal) are not permissions either: the owning domain intercepts its tool with a cold `event.waitUntil(factory)` and drives `IAgentToolApprovalService` itself; the gate only handles chain asks.
|
||||
- The chain encodes dimensions, not tools: a new tool must not lengthen the chain.
|
||||
- New specifics go through the data path (rules); only new behavior goes through the code path (a policy node).
|
||||
- A domain that owns a dimension self-registers its policy in DI; do not centralize domain policies in core.
|
||||
- New specifics go through the data path (rules); only new risk behavior goes through the code path (a policy node).
|
||||
- Tools only declare `accesses`; generic dimensions consume them. kaos is the execution environment, not the permission abstraction.
|
||||
- Use `factory` (Agent-scope instantiation), not `instance`, for registered policies.
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ export interface IAppendLogStore {
|
|||
|
||||
## Platform primitives are deployment-coupled, not core abstractions
|
||||
|
||||
`hostFs` (local filesystem) is a **platform primitive** used only by local backends (`FileStorageService`, `LocalFileSystemBackend`, `LocalSkillCatalog`, `HostFolderBrowser`). It is **not** a core abstraction and must not appear in L2/L3 dependency graphs. A server deployment swaps those backends for DB / S3 implementations and never registers `hostFs`.
|
||||
`hostFs` (local filesystem) is a **platform primitive** used only by local backends (`FileStorageService`, `LocalFileSystemBackend`, `LocalSkillCatalog`, `HostFolderBrowser`). It is **not** a core abstraction and must not appear in business-domain dependency graphs. A server deployment swaps those backends for DB / S3 implementations and never registers `hostFs`.
|
||||
|
||||
## Red lines (this topic)
|
||||
|
||||
|
|
@ -199,6 +199,6 @@ export interface IAppendLogStore {
|
|||
- Name generic Stores by access pattern (`IAppendLogStore` / `IAtomicDocumentStore` / `IBlobStore`), never by business concept (`IRecordStore` / `IConfigStore`).
|
||||
- Business-specific Stores (unique query semantics) are named after the domain (`ISessionIndex`).
|
||||
- `IFileSystemStorageService` is the filesystem byte-layer interface; non-filesystem backends implement the **Store** interfaces directly. Route backends by binding a different Store implementation at the composition root, not by overloading `scope`.
|
||||
- `hostFs` is a local-only platform primitive; L2/L3 domains must not import `node:fs` or `hostFs` directly.
|
||||
- `hostFs` is a local-only platform primitive; business domains must not import `node:fs` or `hostFs` directly.
|
||||
- Only the file-backed bootstrap (`FileBootstrapService`) and file backends import `pathe`; business domains do not.
|
||||
- Do not create a pass-through `Store` that only forwards `read/write` — a Store must hide a real access-pattern concern, or it is noise; use `IFileSystemStorageService` directly instead.
|
||||
|
|
|
|||
|
|
@ -67,9 +67,9 @@ Self-check: "would a released v1 client get a byte-identical envelope from `pack
|
|||
|
||||
Resolve the v2 Service that will back the route. Two cases:
|
||||
|
||||
**Case A — the v2 native Service already matches the v1 contract.** Use it directly. Most data/command Services (`IConfigService`, `IWorkspaceRegistry`, `IApprovalService`, `IQuestionService`, `IFileStore`, …) land here: the route is a thin adapter that resolves the scope, calls the method, and wraps the result. Examples: `routes/config.ts`, `routes/messages.ts`, `routes/questions.ts`, `routes/files.ts`.
|
||||
**Case A — the v2 native Service already matches the v1 contract.** Use it directly. Most data/command Services (`IConfigService`, `IWorkspaceService`, `IApprovalService`, `IQuestionService`, `IFileStore`, …) land here: the route is a thin adapter that resolves the scope, calls the method, and wraps the result. Examples: `routes/config.ts`, `routes/messages.ts`, `routes/questions.ts`, `routes/files.ts`.
|
||||
|
||||
**Case B — the v1 contract needs behavior that would distort the v2 domain.** Introduce a **`*LegacyService`** — an L7 edge adapter that implements the v1 contract **on top of** the v2 native Service, leaving the native Service untouched. The v2 native Service keeps serving `/api/v2`; the LegacyService serves `/api/v1`.
|
||||
**Case B — the v1 contract needs behavior that would distort the v2 domain.** Introduce a **`*LegacyService`** — an edge adapter that implements the v1 contract **on top of** the v2 native Service, leaving the native Service untouched. The v2 native Service keeps serving `/api/v2`; the LegacyService serves `/api/v1`.
|
||||
|
||||
Reach for a LegacyService when **any** hold:
|
||||
|
||||
|
|
@ -109,6 +109,9 @@ export const IAgentPromptService: ServiceIdentifier<IAgentPromptService> =
|
|||
|
||||
```ts
|
||||
// promptService.ts — impl delegates to the native v2 Service
|
||||
import { LifecycleScope } from '#/app/scopes';
|
||||
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
|
||||
|
||||
constructor(@IAgentPromptService private readonly prompt: IAgentPromptService /*, ... */) {}
|
||||
// submit() builds v2-native input, calls the native Service, projects the result
|
||||
// back into the protocol PromptSubmitResult.
|
||||
|
|
@ -117,7 +120,7 @@ registerScopedService(
|
|||
LifecycleScope.Agent, // scope = the lifetime of the legacy state
|
||||
IAgentPromptService,
|
||||
AgentPromptLegacyService,
|
||||
InstantiationType.Delayed,
|
||||
ScopeActivation.OnDemand,
|
||||
'prompt',
|
||||
);
|
||||
```
|
||||
|
|
@ -125,7 +128,7 @@ registerScopedService(
|
|||
Conventions:
|
||||
|
||||
- **Name** the domain `<domain>Legacy` and the interface with the scope prefix, `I<Scope><Domain>LegacyService` (e.g. `prompt` / `IAgentPromptService`), per service-authoring.md.
|
||||
- **Header comment** must say it is an `L7 edge adapter` and name both the v1 contract it implements and the native v2 Service it leaves untouched (see `prompt.ts`).
|
||||
- **Header comment** must say it is an `edge adapter` and name both the v1 contract it implements and the native v2 Service it leaves untouched (see `prompt.ts`).
|
||||
- **Scope** = the lifetime of the *legacy* state it holds (the `prompt` queue is per-agent → `LifecycleScope.Agent`). Apply [orient.md](orient.md) / [design.md](design.md) normally — a LegacyService is not exempt from scope rules.
|
||||
- **Delegate, do not duplicate** business logic. The LegacyService translates the v1 contract into native-Service calls and translates results back; the real work stays in the native Service.
|
||||
- **Contract types come from the v1 wire schema homes** (the owning v2 domain contract or `kap-server/src/protocol`), so the interface cannot drift from the wire shape.
|
||||
|
|
@ -204,7 +207,7 @@ Where the route mirrors v1, the test is the regression guard for the schema-fide
|
|||
- `pnpm -C packages/kap-server test` — server routes green.
|
||||
- `pnpm -C packages/kap-server test` — server routes green (incl. any wire-schema guards).
|
||||
- `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.
|
||||
- `pnpm -C packages/agent-core-v2 run lint:imports` — the import boundaries (v1 ban, kosong subtree) still hold for a LegacyService.
|
||||
- `pnpm -C packages/klient test` (optionally with `KIMI_SERVER_URL` for the live legacy suites) when a v1 parity scenario exists.
|
||||
|
||||
## Worked example — porting v1 `/sessions/:sid/prompts`
|
||||
|
|
@ -233,11 +236,11 @@ Before submitting a server-align change:
|
|||
- [ ] Request and response schemas come from their owning home (the `agent-core-v2` domain contract or `packages/kap-server/src/protocol`); no inline re-declaration in server-v2.
|
||||
- [ ] Existing schema fields are unchanged in name, type, and semantics; only optional fields added (if any).
|
||||
- [ ] Native v2 Service left clean; v1-only behavior isolated in a `<domain>Legacy` / `I<Domain>LegacyService` edge adapter when the semantics diverge.
|
||||
- [ ] LegacyService registered with the correct `LifecycleScope` and a header comment naming it an L7 edge adapter + the native Service it preserves.
|
||||
- [ ] LegacyService registered with the correct `LifecycleScope` and a header comment naming it an edge adapter + the native Service it preserves.
|
||||
- [ ] Domain error codes registered in `agent-core-v2`; wire codes registered in `packages/kap-server/src/protocol`; route maps them in `sendMappedError`, matching v1's status codes and idempotent envelopes.
|
||||
- [ ] Route resolves the scope from the URL by `accessor.get(IX)`; no cached scope; finishes before disposal.
|
||||
- [ ] Tests assert the wire envelope + protocol shape; wire-shape guards added/updated where the route mirrors v1.
|
||||
- [ ] `lint:domain` passes; the LegacyService did not invert scope or domain direction.
|
||||
- [ ] `lint:imports` passes; the LegacyService did not invert scope direction.
|
||||
|
||||
## Red lines (this subskill)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ One folder per domain, **camelCase**: `session/`, `sessionActivity/`, `contextMe
|
|||
```
|
||||
|
||||
- **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.
|
||||
- **Scope is in the filename.** `workspace*.ts` = Workspace, `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` 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.
|
||||
|
|
@ -28,12 +28,12 @@ The package entry `src/index.ts` imports and `export *`s every domain's leaf fil
|
|||
|
||||
| Artifact | Rule | Example |
|
||||
|---|---|---|
|
||||
| Interface | `I` + scope prefix + PascalCase domain + role suffix. Scope prefix: `Session` / `Agent` / none (= App). Role suffix is usually `Service`. | `ISessionLogService`, `IAgentLoopService`, `ILogService` (App) |
|
||||
| Interface | `I` + scope prefix + PascalCase domain + role suffix. Scope prefix: `Workspace` / `Session` / `Agent` / none (= App). Role suffix is usually `Service`. | `IWorkspaceDirs`, `ISessionLogService`, `IAgentLoopService`, `ILogService` (App) |
|
||||
| Class | the interface name minus the leading `I`, plus `Service` if it does not already end in `Service`; `implements` the interface | `SessionLogService implements ISessionLogService`, `AppendLogStoreService implements IAppendLogStore` |
|
||||
| Decorator string | lowerCamelCase of the interface name minus the leading `I`; **globally unique and stable** (it surfaces in `CyclicDependencyError.path` and "no service registered" errors) | `createDecorator<ISessionLogService>('sessionLogService')` |
|
||||
| Model / non-service types | PascalCase, no `I` prefix | `SessionMeta`, `LogEntry`, `ConfigSection` |
|
||||
|
||||
The scope prefix makes a service's lifetime readable from its name. App services carry **no** prefix (App is the default, longest-lived tier); Session and Agent services always carry `Session` / `Agent`. The prefix applies to the interface, the class, and therefore the file names.
|
||||
The scope prefix makes a service's lifetime readable from its name. App services carry **no** prefix (App is the default, longest-lived tier); Workspace, Session and Agent services always carry `Workspace` / `Session` / `Agent`. The prefix applies to the interface, the class, and therefore the file names.
|
||||
|
||||
> Do **not** use the scope prefix to re-merge domains by lifetime. `IAgentEntityService`, `IAgentDataService`, and `ISessionEntityService` are still banned — the prefix marks lifetime, the rest of the name must still be the real owning domain (`IBackgroundTaskEntityService`, `ISessionMetadata`, `IPermissionRulesService`). See [domain-boundaries.md](domain-boundaries.md).
|
||||
|
||||
|
|
@ -137,8 +137,8 @@ Holds the concrete class(es) and the top-level registration. A typical impl:
|
|||
* … collaborators as roles ("logs through `log`") … Bound at App scope.
|
||||
*/
|
||||
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { LifecycleScope } from '#/app/scopes';
|
||||
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
|
||||
import { ILogService } from '#/log';
|
||||
|
||||
import { type Greeting, IGreeter } from './greet';
|
||||
|
|
@ -154,16 +154,18 @@ export class Greeter implements IGreeter {
|
|||
}
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.App, IGreeter, Greeter, InstantiationType.Eager, 'greet');
|
||||
registerScopedService(LifecycleScope.App, IGreeter, Greeter, ScopeActivation.OnScopeCreated, 'greet');
|
||||
```
|
||||
|
||||
What belongs here:
|
||||
|
||||
- **Imports** — `InstantiationType` from `'#/_base/di/extensions'`; `LifecycleScope` + `registerScopedService` from `'#/_base/di/scope'`; collaborators via the `#/<domain>` alias; the contract's types + decorator via a relative `./<domain>` import.
|
||||
- **Imports** — `LifecycleScope` + `ScopeActivation` + `registerScopedService` from `'#/_base/di/scope'`; collaborators via the `#/<domain>` alias; the contract's types + decorator via a relative `./<domain>` import.
|
||||
- **Class** — `XxxService implements IXxxService`, with `declare readonly _serviceBrand: undefined`.
|
||||
- **Helper classes / functions** used only by this impl (e.g. a built-in writer, an `extractError` helper) — co-located in the same file.
|
||||
- **Top-level `registerScopedService(...)`** — one per Service the file owns; importing the impl file runs the registration.
|
||||
|
||||
Base class: extend `Service` (from `#/_base/di/service`) when the unit needs capability calls on `this` — `provide` / `effect` / `on` / `get` / `ref` (e.g. contributing a record to a `collection` token). `Service` extends `Disposable`, so `_register` keeps working; constructor-time `provide` / `on` / `effect` calls are buffered and flushed by the kernel after construction, while `get` / `ref` throw inside the constructor (dependencies stay constructor parameters). Otherwise extend `Disposable` — both are full DI units; a service whose own members collide with the `Service` vocabulary (`name` / `state` / `config` / `get`) must stay on `Disposable` (leave a NOTE comment saying so).
|
||||
|
||||
## Constructor conventions
|
||||
|
||||
- Declare every dependency with `@IX` on a constructor parameter.
|
||||
|
|
@ -203,6 +205,17 @@ A scoped Service may expose a factory method that returns a **new** instance of
|
|||
- `readonly` public fields only for immutable exposed state; prefer a getter (`get level()`) when the value can change.
|
||||
- Keep state minimal — a Service owns only the state that matches its scope's identity (design.md §2). Anything else belongs in a different Service.
|
||||
|
||||
### Runtime state goes into the per-scope state container
|
||||
|
||||
Workspace/Session/Agent-scope Services register their runtime state into the scope's state container (`IWorkspaceStateService` / `ISessionStateService` / `IAgentStateService`, all over `_base`'s `StateRegistry`) instead of holding it in bare instance fields, so per-scope state lives in one observable place (`snapshot()` / `onDidChange`) and dies with the scope. Reference: `session/interaction/interactionService.ts`.
|
||||
|
||||
- Declare keys in the domain file and export them: `export const interactionPendingKey = defineState<Map<string, Pending>>('interaction.pending', () => new Map())` — `<domain>.<field>` naming, factory initializers.
|
||||
- Inject `@ISessionStateService private readonly states` (or the Agent token) and `this.states.register(key)` per key at the top of the constructor.
|
||||
- Replace the field with accessors: a getter for collections only mutated in place (`this.foo.add(...)` keeps working — the container stores references, never clones); add a setter routed through `states.set` for reassigned scalars. Call sites stay unchanged.
|
||||
- Values must be plain data: scalars, arrays, and literal objects/Maps/Sets built from them. Never register class instances, resource handles (disposables, abort controllers, Promise locks), or objects holding service references — the regression precedent: one registry key whose class instances reached the whole DI graph deep-copied to hundreds of MB on `snapshot()` and OOM-killed the server. This means registries whose entries carry resources (the tool registry, the task map, prompt queues) stay as instance fields alongside Emitters, hook slots, disposable slots, waiter arrays, caches, and queue instances.
|
||||
- `snapshot()` additionally recurses plain data only: values with a custom prototype collapse to a `'(ClassName)'` marker — a `_base`-level backstop, not a license to register resource-bearing values.
|
||||
- Durable, replayable state does NOT belong here — it stays on wire Models. The container is memory-only.
|
||||
|
||||
## Events
|
||||
|
||||
v2 has two distinct event mechanisms. Pick by audience:
|
||||
|
|
@ -234,7 +247,7 @@ Conventions:
|
|||
|
||||
- Back the public `Event<T>` with a private `Emitter<T>`, registered with `this._register(...)` so it disposes with the Service.
|
||||
- Naming: `onDid…` for "happened" (past tense, after the fact); `onWill…` for "about to happen" (may allow `waitUntil` participation / veto — see `AsyncEmitter` / `IWaitUntil` in `'#/_base/event'`).
|
||||
- The Delayed-instantiation Proxy preserves early `onDid…` / `onWill…` subscriptions (implement.md §5).
|
||||
- A service must be constructed before consumers can subscribe to its events. Use the default `OnScopeCreated` activation when subscriptions must be available as soon as the scope is ready.
|
||||
|
||||
### `IEventService` — global pub-sub bus
|
||||
|
||||
|
|
@ -307,8 +320,8 @@ export const IGreeter: ServiceIdentifier<IGreeter> = createDecorator<IGreeter>('
|
|||
|
||||
```ts
|
||||
// greet/greetService.ts
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope, registerScopedService } from '#/_base/di/scope';
|
||||
import { LifecycleScope } from '#/app/scopes';
|
||||
import { ScopeActivation, registerScopedService } from '#/_base/di/scope';
|
||||
import { type Greeting, IGreeter } from './greet';
|
||||
|
||||
export class Greeter implements IGreeter {
|
||||
|
|
@ -316,7 +329,7 @@ export class Greeter implements IGreeter {
|
|||
hello(): Greeting { return { message: 'hi' }; }
|
||||
}
|
||||
|
||||
registerScopedService(LifecycleScope.App, IGreeter, Greeter, InstantiationType.Eager, 'greet');
|
||||
registerScopedService(LifecycleScope.App, IGreeter, Greeter, ScopeActivation.OnScopeCreated, 'greet');
|
||||
```
|
||||
|
||||
```ts
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@
|
|||
|
||||
Telemetry infrastructure for agent-core-v2: how business services emit events, how context propagates, and how events reach a destination through appenders.
|
||||
|
||||
Telemetry is a **layer-1 root** domain (alongside `log`): pure `App` scope, stateless, no business-domain dependencies. It is a thin facade — enrichment, batching, and transport belong to the appenders, not to this layer.
|
||||
Telemetry is a **layer-1 root** domain (alongside `log`): the facade lives at `App` scope (a per-Agent ambient context service is bound at `Agent` scope), stateless, with no business-domain dependencies. It is a thin facade — enrichment, batching, and transport belong to the appenders, not to this layer.
|
||||
|
||||
## Where things live
|
||||
|
||||
- `src/app/telemetry/telemetry.ts`: contract — `ITelemetryService` (facade), `ITelemetryAppender` (destination), `TelemetryProperties`, `nullTelemetryAppender`, and `TelemetryServiceOptions`.
|
||||
- `src/app/telemetry/events.ts`: event registry — `telemetryEventDefinitions` pairs every business event's property type with review metadata (owner / purpose / per-property comment); the single source of truth for `track2`.
|
||||
- `src/app/telemetry/events.ts`: event registry — `telemetryEventDefinitions` pairs every business event's property type with review metadata (owner / purpose / per-property comment); the single source of truth for `track2`. Agent-scope events register with `defineAgentTelemetryEvent<P>` and compose the ambient `AgentTelemetryEventContext` (`agent_id`) into their wire schema; all other events register with `defineTelemetryEvent<P>`.
|
||||
- `src/app/telemetry/telemetryService.ts`: `TelemetryService` impl + `registerScopedService(LifecycleScope.App, …)`.
|
||||
- `src/app/telemetry/agentTelemetryContext.ts` + `agentTelemetryContextService.ts`: `IAgentTelemetryContextService` — Agent-scoped mutable request context (`mode` / `provider_type` / `protocol` / `turn_id` / `trace_id`) snapshot into turn telemetry at launch. Agent identity (`agent_id`) is not part of it — identity is bound by the Agent-scoped `ITelemetryService` view.
|
||||
- `src/app/telemetry/consoleAppender.ts`: `ConsoleAppender` — echoes events to a log function (dev / debug).
|
||||
- `src/app/telemetry/cloudAppender.ts`: `CloudAppender` — sanitizes + PII-cleans properties, batches + enriches + posts to the telemetry endpoint.
|
||||
- `src/app/telemetry/cloudTransport.ts`: `CloudTransport` — HTTP transport behind `CloudAppender`.
|
||||
|
|
@ -26,20 +27,20 @@ constructor(@ITelemetryService private readonly telemetry: ITelemetryService) {}
|
|||
this.telemetry.track2('cron_fired', { task_id: taskId, coalesced_count: 0, stale: false, buffered: false, recurring: true });
|
||||
```
|
||||
|
||||
`track2` is checked against the registry in `events.ts` at compile time: the event name must be a key of `telemetryEventDefinitions`, and the properties must match the registered interface exactly (extra or missing keys are compile errors). **New events must be registered first** — add a properties interface and a `defineTelemetryEvent<P>({ owner, comment, properties })` entry documenting every property. Naming: snake_case for events and properties, unit suffixes (`_ms` / `_count` / `_bytes`), no user content or file paths; `test/app/telemetry/events.test.ts` enforces the conventions. The low-level `track` remains for appender plumbing and tests only.
|
||||
`track2` is checked against the registry in `events.ts` at compile time: the event name must be a key of `telemetryEventDefinitions`, and the properties must match the registered interface exactly (extra or missing keys are compile errors). **New events must be registered first** — add a properties interface, then register it with `defineAgentTelemetryEvent<P>({ owner, comment, properties })` when every emission path goes through an Agent-scoped `ITelemetryService` view, or `defineTelemetryEvent<P>` otherwise (including events with any non-Agent emission path, e.g. `image_compress` from the kap-server prompt routes), documenting every property. For agent-scope events the registered interface is the business payload only: ambient `agent_id` is declared once in `AgentTelemetryEventContext` and composed into the wire schema, so it must not appear in the payload or at call sites. Naming: snake_case for events and properties, unit suffixes (`_ms` / `_count` / `_bytes`), no user content or file paths; `test/app/telemetry/events.test.ts` enforces the conventions. The low-level `track` remains for appender plumbing and tests only.
|
||||
|
||||
`TelemetryService.track` merges the bound context into the properties and fans the event out to every registered appender. A single throwing appender is isolated via `onUnexpectedError` and never blocks the rest.
|
||||
|
||||
### Context (sessionId / agentId / turnId)
|
||||
### Context (sessionId / agent_id / turn_id)
|
||||
|
||||
The service carries a bound context (`sessionId` / `agentId` / `turnId`) that is merged into every event. Bind it at construction or derive a scoped view:
|
||||
The root service carries a bound context (`sessionId`) that is merged into every event, and each Agent scope gets its own telemetry view seeded with `agent_id` (by `agentLifecycle`), so Agent-scoped services emit their identity without call-site plumbing. Mutable per-agent request context (`mode` / `provider_type` / `protocol` / `turn_id` / `trace_id`) lives in `IAgentTelemetryContextService` and is snapshot into a per-turn view at turn launch. Derive a scoped view with `withContext`:
|
||||
|
||||
```ts
|
||||
const child = telemetry.withContext({ agentId: 'main', turnId: 't1' });
|
||||
child.track2('tool_call', { turn_id: 1, tool_call_id: 'c1', tool_name: 'bash', outcome: 'success', duration_ms: 12 }); // carries sessionId + agentId + turnId
|
||||
const child = telemetry.withContext({ agent_id: 'agent-0' });
|
||||
child.track2('tool_call', { turn_id: 1, tool_call_id: 'c1', tool_name: 'bash', outcome: 'success', duration_ms: 12 }); // wire carries sessionId + agent_id
|
||||
```
|
||||
|
||||
`withContext(patch)` returns a new service sharing the same appenders; per-call properties override bound context on key collision. `setContext(patch)` mutates the bound context in place and propagates to appenders that implement `setContext`.
|
||||
`withContext(patch)` returns a lightweight forwarding view: transport state (appenders, enabled flag) stays with the root, so later `addAppender` / `setEnabled` calls apply to every view, and per-call properties override bound context on key collision. `setContext(patch)` on the root mutates the root context and propagates to appenders that implement `setContext`; on a view it mutates only that view's own context.
|
||||
|
||||
## Appenders (destinations)
|
||||
|
||||
|
|
@ -88,8 +89,9 @@ telemetry.addAppender(new CloudAppender({ // production
|
|||
## Red lines (this topic)
|
||||
|
||||
- Business services depend only on `ITelemetryService` — never import an appender class.
|
||||
- Telemetry is layer-1 root: do not inject any business-domain service into it, and do not move it off `App`.
|
||||
- Telemetry is layer-1 root: do not inject any business-domain service into it, and keep the facade at `App` scope (only the ambient context service binds at `Agent`).
|
||||
- Appenders are plain `ITelemetryAppender` objects, not DI Services — register them with `addAppender`, never via `registerScopedService`.
|
||||
- `track` is fire-and-forget and must not throw; appender `track` must be synchronous — buffer and send asynchronously via `flush` / `shutdown`.
|
||||
- Await `telemetry.shutdown()` before process exit when a buffering appender is registered.
|
||||
- Keep event names stable; register every business event in `events.ts` and emit via `track2` — properties must be JSON-serializable primitives (non-primitives are dropped with a warning by `CloudAppender`).
|
||||
- Agent identity is ambient: agent-scope events go through `defineAgentTelemetryEvent` and get `agent_id` from the scoped telemetry view — do not pass `agent_id` at business call sites (per-event identities such as `subagent_created` and the cron events are the exception).
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ Reach for this only when *which layer a service lives in* is itself the thing be
|
|||
|
||||
```ts
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { InstantiationType } from '#/_base/di/extensions';
|
||||
import { LifecycleScope } from '#/app/scopes';
|
||||
import {
|
||||
LifecycleScope,
|
||||
ScopeActivation,
|
||||
_clearScopedRegistryForTests,
|
||||
registerScopedService,
|
||||
} from '#/_base/di/scope';
|
||||
|
|
@ -94,7 +94,7 @@ describe('XxxService (scoped)', () => {
|
|||
LifecycleScope.Agent,
|
||||
IXxxService,
|
||||
XxxService,
|
||||
InstantiationType.Delayed,
|
||||
ScopeActivation.OnDemand,
|
||||
'xxx',
|
||||
);
|
||||
});
|
||||
|
|
@ -224,6 +224,14 @@ Do **not** add the system-under-test itself to the store. `TestInstantiationServ
|
|||
|
||||
Scope-host tests call `host.dispose()` in `afterEach` (or at the end of the `it`). Route teardown through the store so ordering is deterministic and nothing leaks when a test fails mid-way.
|
||||
|
||||
## Cascade: asserting unit state
|
||||
|
||||
The cascade engine's test vocabulary lives in two files: `test/_base/di/cascade.test.ts` (the mechanism matrix, including cross-scope orchestration) and `test/_base/di/provide.test.ts` (provide/unprovide semantics).
|
||||
|
||||
- **Assert unit states, not internals.** Every container exposes its engine as `container.cascade`: `stateOf(IX)` → `'Pending' | 'Activating' | 'Active' | 'Unloading' | 'Failed'`; `failureOf(IX)` → the sticky error of a `Failed` unit; `pendingSnapshot()` → the waiting-area contents.
|
||||
- **The waiting area parks units with unregistered dependencies** — a unit whose declared deps are missing stays `Pending` (no throw), so a test must seed the full dependency chain. Example: a root→agent chain with no session container must seed the session-scope dependency explicitly — `ix.set(ISessionStateService, new SessionStateService())` in `test/session/agentLifecycle/agentLifecycle.test.ts` — or the dependent unit never activates.
|
||||
- **Eager activation failure is sticky `Failed`, not a scope-creation throw.** Assert state + rethrow: `expect(ix.cascade.stateOf(IX)).toBe('Failed')`, then `expect(() => ix.invokeFunction((a) => a.get(IX))).toThrow(…)`. Do not expect scope/host creation itself to throw for a failing eager constructor.
|
||||
|
||||
## Assertions and naming
|
||||
|
||||
- One behavior per `it`; describe observable behavior (`child shadows parent registration`), not implementation (`calls _getOrCreateServiceInstance`).
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Run the guards and re-scan the red lines before submitting.
|
|||
|
||||
Run from the package (or with `--filter @moonshot-ai/agent-core-v2`):
|
||||
|
||||
- `pnpm --filter @moonshot-ai/agent-core-v2 lint:domain` — domain-layer / dependency-direction guard (`scripts/check-domain-layers.mjs`). Catches a domain importing a layer it must not.
|
||||
- `pnpm --filter @moonshot-ai/agent-core-v2 lint:imports` — import-boundary guard (`scripts/check-import-boundaries.mjs`). Catches v1 imports (`@moonshot-ai/agent-core`) and kosong subtree violations.
|
||||
- `pnpm --filter @moonshot-ai/agent-core-v2 typecheck` — `tsc -p tsconfig.json --noEmit`.
|
||||
- `pnpm --filter @moonshot-ai/agent-core-v2 test` — `vitest run`.
|
||||
|
||||
|
|
@ -27,6 +27,6 @@ Then re-read the [global red lines](SKILL.md#global-red-lines) once — they cat
|
|||
|
||||
## Red lines (this stage)
|
||||
|
||||
- Do not skip `lint:domain` — it is the only automated check for the dependency-direction rules.
|
||||
- Do not skip `lint:imports` — it is the only automated check for the v1-import ban and the kosong subtree rules.
|
||||
- Do not list internal packages in a changeset when the change enters the CLI bundle — list `@moonshot-ai/kimi-code` and describe the real change.
|
||||
- Never write a `major` changeset without explicit user confirmation.
|
||||
|
|
|
|||
|
|
@ -18,18 +18,25 @@ All other `@moonshot-ai/*` packages are treated as internal packages, including
|
|||
1. **Inspect the actual changes first.** Use `git status` / `git diff --name-only` to identify which packages were actually changed.
|
||||
2. **List packages that changesets can release.** If a changed package is ignored in `.changeset/config.json`, do not put that ignored package in frontmatter together with a non-ignored package; changesets rejects mixed ignored/non-ignored frontmatter.
|
||||
3. **Map ignored internal changes to the affected released package.** If an ignored internal package changes CLI output or behavior, list `@moonshot-ai/kimi-code` and describe the actual user-visible or release-artifact change in the changelog text.
|
||||
4. **Internal package source changes that enter the CLI bundle must manually list the CLI.** `@moonshot-ai/kimi-code` inline-bundles `@moonshot-ai/*` source, but those internal packages are devDependencies from the CLI's perspective, so changesets will not automatically propagate bumps. If a change enters the CLI output, list `@moonshot-ai/kimi-code`.
|
||||
- **Web app (`@moonshot-ai/kimi-web`) changes always enter the CLI bundle.** `@moonshot-ai/kimi-web` is ignored by changesets (see `.changeset/config.json`) and cannot be mixed with `@moonshot-ai/kimi-code` in one changeset frontmatter. Describe the web change in the changelog text, but list `@moonshot-ai/kimi-code` so the CLI release carries the bundled `dist-web` output.
|
||||
4. **Internal package source changes that enter the CLI bundle must manually list the CLI — when they get a changeset at all.** `@moonshot-ai/kimi-code` inline-bundles `@moonshot-ai/*` source, but those internal packages are devDependencies from the CLI's perspective, so changesets will not automatically propagate bumps. If a change enters the CLI output and is user-perceivable, list `@moonshot-ai/kimi-code`. See rule 6 for when to skip the changeset entirely.
|
||||
5. **Docs-only and tests-only changes usually do not need a changeset.** README, internal docs, and `test/` changes that do not enter package output do not trigger a CLI bump.
|
||||
6. `@moonshot-ai/vis` / `vis-server` / `vis-web` are ignored by changesets and should not be handled. `@moonshot-ai/kimi-inspect` (a private dev app that never ships) is likewise ignored and must never appear in a changeset frontmatter.
|
||||
6. **Skip changes users cannot perceive — write no changeset at all.** The CLI changelog is user-facing; a changeset is a changelog entry, not a shipping gate. Internal changes merged to `main` still ship in the next release triggered by any user-facing changeset, so skipping the changeset loses nothing. Do not write changesets for:
|
||||
- `agent-core-v2` internal architecture: new services, refactors, config-persistence or journal/wire mechanisms.
|
||||
- `kap-server` WebSocket / REST protocol changes consumed only by the bundled web UI, kimi-inspect, or other dev tooling (new endpoints, subscribe protocols, stream baselines).
|
||||
- Behavior that only takes effect on the experimental engine (e.g. experimental `kimi -p`), unless it exposes documented user configuration such as a `config.toml` section or env vars that also work on a shipped surface (TUI or `kimi web`).
|
||||
- When unsure whether users can perceive a change, ask before writing.
|
||||
7. `@moonshot-ai/vis` / `vis-server` / `vis-web` are ignored by changesets and should not be handled. `@moonshot-ai/kimi-inspect` (a private dev app that never ships) is likewise ignored and must never appear in a changeset frontmatter.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. List the changed packages and check whether each one is ignored by `.changeset/config.json`.
|
||||
2. Choose a bump level for each package.
|
||||
3. If an ignored internal package change enters the CLI bundle, put `@moonshot-ai/kimi-code` in frontmatter instead of mixing the ignored package into the same changeset.
|
||||
4. Create a short kebab-case file under `.changeset/`.
|
||||
5. Split unrelated changes into separate changesets; keep one logical change in one file.
|
||||
2. Decide whether the change is user-perceivable (Core Rule 6); if not, stop — no changeset.
|
||||
3. Choose a bump level for each package.
|
||||
4. If an ignored internal package change enters the CLI bundle, put `@moonshot-ai/kimi-code` in frontmatter instead of mixing the ignored package into the same changeset.
|
||||
5. Create a short kebab-case file under `.changeset/`.
|
||||
6. Split unrelated changes into separate changesets; keep one logical change in one file.
|
||||
|
||||
Before a release, review the accumulated `.changeset/` entries against Core Rule 6 and prune non-user-facing ones; the release PR regenerates from `.changeset/` on `main`, so deleting a changeset removes its changelog entry without affecting the shipped code.
|
||||
|
||||
Format:
|
||||
|
||||
|
|
@ -52,6 +59,8 @@ Format:
|
|||
|
||||
When in doubt between `patch` and `minor`: if the change improves an existing feature and the user-facing impact is small, choose `patch` even when the change is technically "new". Reserve `minor` for a substantial new capability that introduces something users could not do before.
|
||||
|
||||
New configuration surface is not automatically `minor`. Additions to an existing feature's configuration — env var overlays, config-file fallbacks, global defaults under per-item settings — are `patch`. Examples: a global default MCP timeout when per-server timeouts already exist; env-based credentials for a service already configurable in `config.toml`.
|
||||
|
||||
### Major Rule
|
||||
|
||||
Never write `major` on your own.
|
||||
|
|
@ -143,36 +152,6 @@ Only SDK source changed, and the CLI does not use it:
|
|||
Clarify session status typing for internal SDK callers.
|
||||
```
|
||||
|
||||
## Web app changes
|
||||
|
||||
`@moonshot-ai/kimi-web` is ignored by changesets and must **never** appear in a changeset frontmatter. Because the web app is bundled into the CLI release artifact, any web change that ships must list `@moonshot-ai/kimi-code` instead and describe the actual web-facing change in the text.
|
||||
|
||||
- Prefix the changelog entry text with `web: ` (for example `web: Fix the chat not scrolling to the bottom after sending a message.`) so the synced docs changelog can mark web UI entries. Apply this whenever the change is to the web project (`@moonshot-ai/kimi-web`).
|
||||
- If a PR ships a web UI feature backed by server API changes that exist solely to power that feature, prefer a single `web:` entry describing what the web user gets. Do not add a separate server-API changeset unless the API has independent user value (a public endpoint that SDK or server consumers call directly). The docs changelog sync also deduplicates this pattern, but catching it here avoids duplicate changesets.
|
||||
- Do not enumerate every micro-tweak; keep it to one sentence that captures what the web user gets.
|
||||
|
||||
Web-only fix:
|
||||
|
||||
```markdown
|
||||
---
|
||||
"@moonshot-ai/kimi-code": patch
|
||||
---
|
||||
|
||||
web: Fix the chat not scrolling to the bottom after sending a message.
|
||||
```
|
||||
|
||||
Web UI plus backing server APIs in the same PR (prefer a single `web:` entry; the API is plumbing):
|
||||
|
||||
```markdown
|
||||
---
|
||||
"@moonshot-ai/kimi-code": minor
|
||||
---
|
||||
|
||||
web: Add the server-hosted web UI, including chat layout and session list behaviors.
|
||||
```
|
||||
|
||||
Split into two changesets only when the API has independent user value on its own (for example, a public endpoint SDK consumers call directly). In that case add the web entry above plus a separate one such as `Add a public REST API to list archived sessions for SDK consumers.`
|
||||
|
||||
## `@moonshot-ai/pi-tui` changes
|
||||
|
||||
`@moonshot-ai/pi-tui` is a vendored fork that lives in `packages/pi-tui`. It is `private: true` and is never published, but it is **not** ignored by changesets: changesets versions it and writes `packages/pi-tui/CHANGELOG.md` so the fork keeps its own history. Because it is bundled into the CLI like other internal packages, it is an exception to Core Rule 4 — do **not** list `@moonshot-ai/kimi-code` for a change that only touches pi-tui.
|
||||
|
|
@ -211,6 +190,8 @@ Fix the transcript jumping to the top when scrolling up through history during s
|
|||
## Red Flags
|
||||
|
||||
- You are about to write `major` without asking the user.
|
||||
- You are writing a changeset for something users cannot perceive — `agent-core-v2` internals, `kap-server` WS/REST protocol plumbing, experimental-engine-only behavior. Skip the changeset instead (Core Rule 6).
|
||||
- A new env var overlay or config fallback for an existing feature is bumped `minor` — configuration additions to existing features are `patch`.
|
||||
- A new user-facing feature entry has no usage hint, or the hint runs to multiple lines and explains design rationale.
|
||||
- You guessed wording for a change you do not understand instead of asking the user whether you may dig into the repo.
|
||||
- Internal package source enters the CLI bundle, but `@moonshot-ai/kimi-code` is missing.
|
||||
|
|
@ -221,5 +202,3 @@ Fix the transcript jumping to the top when scrolling up through history during s
|
|||
- The CLI wording mentions internal package names, class names, or PR numbers.
|
||||
- The entry includes real internal identifiers instead of neutral placeholders.
|
||||
- A change that only touches `@moonshot-ai/pi-tui` lists `@moonshot-ai/kimi-code` instead of `@moonshot-ai/pi-tui`, or mixes both packages in one frontmatter.
|
||||
- A web app change entry is missing the `web: ` prefix.
|
||||
- A server/API changeset exists only to back a web feature that a `web:` changeset already describes (use one `web:` entry instead, unless the API has independent user value).
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ If the CLI changelog is not in the diff (for example an SDK-only release), stop
|
|||
|
||||
Process the version block exactly as `sync-changelog` does for the docs site, but only in memory:
|
||||
|
||||
- **Strip** (`sync-changelog` step 3): drop the H1, the `### Patch Changes` / `### Minor Changes` / `### Major Changes` subheadings, PR links, and commit-hash links; keep only each entry's body text. The `Thanks [@user](...)!` credit (including the multi-author form) must be removed every time. Within each entry, drop SDK-only and provider-internal sentences (SDK capability mapping / API exposure, provider wire-format mechanics, internal XML markers) and keep only the user-facing effect and required constraints.
|
||||
- **Strip** (`sync-changelog` step 3): drop the H1, the `### Patch Changes` / `### Minor Changes` / `### Major Changes` subheadings, PR links, and commit-hash links; keep only each entry's body text. The `Thanks [@user](...)!` credit (including the multi-author form) must be removed every time. Within each entry, drop SDK-only and provider-internal sentences (SDK capability mapping / API exposure, provider wire-format mechanics, internal XML markers, hook/event payload mechanics such as what an event reports or carries) and keep only the user-facing effect and required constraints.
|
||||
- **Merge and deduplicate** (`sync-changelog` step 4): merge micro-tweaks to the same surface into one higher-level entry; when three or more fixes target the same UI area or the same class of problem, merge them into one higher-level fix entry (do not merge broad or genuinely distinct fixes); and drop a server/API entry that only backs a web feature already listed.
|
||||
- **Classify** (`sync-changelog` step 4): bucket into Features / Bug Fixes / Polish / Refactors / Other; order within each section by reader value (in Polish, user-visible improvements before protocol/internal adjustments).
|
||||
- **Translate** (`sync-changelog` step 6): translate entry bodies to Chinese; keep one sentence per entry with a parallel rhythm within a section; section headings become 新功能 / 修复 / 优化 / 重构 / 其他.
|
||||
|
|
@ -48,6 +48,8 @@ If an upstream entry is not in English, flag it and stop (changeset entries must
|
|||
|
||||
Print the preview directly. Use `<version>(预览)` as the heading because the version is not released yet. Write `无` for empty sections. Do not write any file.
|
||||
|
||||
The preview is pasted into chat tools (for example Lark), where relative docs links do not resolve. Rewrite every docs link to its absolute published URL: map `../<path>.md[#anchor]` to `https://moonshotai.github.io/kimi-code/zh/<path>.html[#anchor]` — for example `../configuration/config-files.md#loop-control` → `https://moonshotai.github.io/kimi-code/zh/configuration/config-files.html#loop-control`. Never emit raw relative paths, and never wrap a link in backticks; code-style the link text inside the brackets instead ([`loop_control`](...)).
|
||||
|
||||
```
|
||||
发版 PR: <url>
|
||||
|
||||
|
|
|
|||
|
|
@ -115,18 +115,11 @@ Drop SDK-only and provider-internal detail. This changelog serves `@moonshot-ai/
|
|||
|
||||
- Drop sentences about how the SDK maps a capability, builds model aliases, or exposes a flag through an API such as `getExperimentalFeatures()` — that belongs in the SDK changelog, not here.
|
||||
- Drop provider / wire-format implementation mechanics (XML markers like `<tools_added>`, protocol field explanations, "the wire protocol is unchanged", cache-hit mechanics) unless they are the behavior a user perceives.
|
||||
- Drop hook/event payload mechanics — clauses about what extra fields an event payload carries or what an event reports in a specific case (for example "enrich hook payloads with the session title and client type", "`SessionEnd` reports `archive` when a session is archived"). Keep the new events or capability itself and how to configure it.
|
||||
- Keep the user-facing effect and any constraints users must follow (for example "question texts must be unique").
|
||||
|
||||
Do not change facts or drop a real user-facing behavior — only trim the internal-only scaffolding. For over-long, internal-heavy entries, this trim applies on the English page too, not only in translation.
|
||||
|
||||
Web UI prefix: if the entry is a web UI change, prefix the body text with `web: ` so readers can tell it affects the web UI:
|
||||
|
||||
```markdown
|
||||
- web: <body text>
|
||||
```
|
||||
|
||||
An entry counts as a web UI change when its upstream commit touches `apps/kimi-web/`. Check with `git show --name-only <hash>` (the commit hash is the one stripped above). `gen-changesets` writes this prefix for web changes, so it is usually already present in upstream — preserve it when it is there, and add it when a web entry lacks it. When a commit touches both web and non-web code, use `web:` only if the user-facing change described by the entry is in the web UI. Keep the `web:` prefix on the Chinese page too — it is a scope marker, not translated text.
|
||||
|
||||
Upstream language rule: `gen-changesets` requires changelog entries to be English. If the upstream CLI changelog contains a non-English entry, stop and report it to the user. Do not silently rewrite it while syncing docs.
|
||||
|
||||
Public-text rule: do not copy real internal endpoints, key names, account names, or service names into docs changelogs. Replace examples with neutral placeholders such as `example.com`, `example.test`, or `YOUR_API_KEY` while preserving the user-visible meaning.
|
||||
|
|
@ -135,13 +128,13 @@ Public-text rule: do not copy real internal endpoints, key names, account names,
|
|||
|
||||
Before classifying, merge related entries and drop redundant ones from the user-facing changelog:
|
||||
|
||||
- **Merge micro-tweaks to the same surface.** Collapse several small tweaks to the same UI area or feature into one concise entry at the higher level. For example, "change the composer's default height" and "change the composer's default font" merge into "Polish the composer's default styling." Use the most specific common ancestor (composer, settings page, tool card, and so on). Classify the merged entry by its combined effect, and keep the `web:` prefix if the combined change is still web-facing.
|
||||
- **Merge micro-tweaks to the same surface.** Collapse several small tweaks to the same UI area or feature into one concise entry at the higher level. For example, "change the composer's default height" and "change the composer's default font" merge into "Polish the composer's default styling." Use the most specific common ancestor (composer, settings page, tool card, and so on). Classify the merged entry by its combined effect
|
||||
- **Merge same-surface or same-kind fixes when you have three or more.** The `Bug Fixes` section tends to accumulate many narrow UI/polish fixes that read as noise when listed one by one. When three or more fixes target the same area (for example several tool cards in the TUI, or the web session/conversation surface) or the same class of problem (for example several "jumping/flickering/collapsing during streaming" fixes), merge them into one higher-level entry. Examples:
|
||||
- "Fix the Bash tool card collapsing...", "Fix the Edit tool card jumping in height...", "Fix the Edit tool card flickering while its result streams in" → "Fix several TUI tool cards jumping, flickering, or collapsing in height when results stream in or end with short output."
|
||||
- "Fix the collapsed sidebar not hiding...", "Stop the chat history from replaying its entrance animation...", "Fix tool components jumping the conversation when expanded/collapsed" → "web: Fix several layout and display glitches when switching sessions, including the collapsed sidebar not hiding, the chat history replaying its entrance animation, and tool components jumping the conversation."
|
||||
- Keep `web:` if the merged fixes are all web-facing. Classify as `Bug Fixes`.
|
||||
- "Fix the collapsed sidebar not hiding...", "Stop the chat history from replaying its entrance animation...", "Fix tool components jumping the conversation when expanded/collapsed" → "Fix several layout and display glitches when switching sessions, including the collapsed sidebar not hiding, the chat history replaying its entrance animation, and tool components jumping the conversation."
|
||||
- Classify the merged fixes as `Bug Fixes`.
|
||||
- **Do not over-merge.** Leave a fix standalone when it is broad, high-value, or genuinely distinct (for example model/provider tool-calling bugs, session-list corruption, file-completion gaps). Merging is for low-reader-value, similar-shape fixes that read as a wall of similar bullets.
|
||||
- **Drop server/API plumbing covered by a web entry.** If one entry adds a web UI feature (for example, an Archived sessions page) and another entry only adds the server or REST/WebSocket endpoints that exist solely to power that web feature, keep the `web:` entry and drop the API entry. CLI and web users perceive the web page; the backing API is implementation detail with no independent user value on this changelog. Keep the API entry only when it has independent user value — a new public endpoint that SDK or server consumers call directly, or a capability usable outside the web feature. When unsure, keep both and let the reviewer decide.
|
||||
- **Drop server/API plumbing covered by a web entry.** If one entry adds a web UI feature (for example, an Archived sessions page) and another entry only adds the server or REST/WebSocket endpoints that exist solely to power that web feature, keep the web UI entry and drop the API entry. CLI and web users perceive the web page; the backing API is implementation detail with no independent user value on this changelog. Keep the API entry only when it has independent user value — a new public endpoint that SDK or server consumers call directly, or a capability usable outside the web feature. When unsure, keep both and let the reviewer decide.
|
||||
|
||||
The docs changelog uses five section types:
|
||||
|
||||
|
|
@ -229,6 +222,8 @@ Example:
|
|||
- Update the native release workflow to use current GitHub artifact actions.
|
||||
```
|
||||
|
||||
Doc links: an entry that changes a documented config surface may end with a pointer to the docs page — `see [X](...) for details` (Chinese: `详见 [X](...)。`). Keep it a real Markdown link into the docs tree with a relative path (for example `../configuration/config-files.md#loop-control`). When the link text is a config key or another identifier, code-style the text inside the brackets: [`loop_control`](../configuration/config-files.md#loop-control). Never wrap the whole link in backticks — `` `[loop_control](...)` `` renders as raw inline code that exposes the relative path instead of a clickable link.
|
||||
|
||||
### 6. Translate The Increment Into Chinese
|
||||
|
||||
After updating the English page, translate only the newly added English content into `docs/zh/release-notes/changelog.md`.
|
||||
|
|
@ -281,7 +276,7 @@ Guidelines:
|
|||
- **Keep usage hints to one short clause**.
|
||||
- Bad: `传入 --allowed-host 以允许额外的 host。例如 ... (多句展开)`
|
||||
- Better: `例如 kimi web --allowed-host example.com。`
|
||||
- **Do not translate technical identifiers**: keep command names, flag names, file names, env vars, config keys, and the `web:` scope prefix as-is.
|
||||
- **Do not translate technical identifiers**: keep command names, flag names, file names, env vars, config keys as-is.
|
||||
- **Keep parallel rhythm within a section.** When several entries fix similar web surfaces (layout, animation, sizing), phrase them with a consistent structure (for example 修复 <问题>,现 <行为>) so the section reads as a tidy list rather than a mix of shapes.
|
||||
|
||||
Example — translating a feature entry:
|
||||
|
|
@ -322,6 +317,7 @@ Check:
|
|||
- PR links and commit hashes were stripped.
|
||||
- No `Thanks ...!` credit remains (remove it every time).
|
||||
- Real internal identifiers were replaced with neutral placeholders.
|
||||
- Doc links are real Markdown links (code-styled text inside the brackets when needed), never wrapped in backticks.
|
||||
- There are no empty sections.
|
||||
- Markdown indentation and blank lines are intact.
|
||||
|
||||
|
|
@ -426,7 +422,6 @@ Return the PR URL to the user when done.
|
|||
- The English docs changelog is the source of truth.
|
||||
- Never edit upstream `apps/kimi-code/CHANGELOG.md`.
|
||||
- Do not backfill unreleased `.changeset/*.md` drafts into the docs site.
|
||||
- Prefix web UI entries with `web: ` (when the upstream commit touches `apps/kimi-web/`), and keep the prefix on both the English and Chinese pages.
|
||||
- If upstream wording is wrong, leave upstream alone and fix it in a future changeset.
|
||||
- Always sync on a `docs/changelog-sync-*` branch and open a PR; never push changelog docs sync directly to `main`.
|
||||
- Wait for the human review checkpoint before committing, pushing, or opening a PR.
|
||||
|
|
@ -440,7 +435,7 @@ Return the PR URL to the user when done.
|
|||
| Leaving the `Thanks ...!` credit in docs | Remove it every time, including the multi-author form |
|
||||
| Leaving near-duplicate micro-tweaks as separate bullets | Merge small tweaks to the same surface into one higher-level entry (e.g. composer height + font → composer's default styling) |
|
||||
| Listing many narrow fixes to the same surface as separate bullets | When three or more fixes target the same UI area or the same class of problem, merge them into one higher-level fix entry; keep genuinely distinct or high-value fixes standalone |
|
||||
| Listing a server/API entry that only backs a web feature already listed | Drop the API entry and keep the `web:` entry, unless the API has independent user value |
|
||||
| Listing a server/API entry that only backs a web feature already listed | Drop the API entry and keep the web UI entry, unless the API has independent user value |
|
||||
| Rewording upstream English entries | Upstream is frozen; copy the body text unless the user explicitly asks otherwise |
|
||||
| Leaving English text untranslated in the Chinese page | The Chinese page must be fully Chinese except preserved technical terms |
|
||||
| Editing upstream changelog text | Do not edit upstream |
|
||||
|
|
@ -454,12 +449,13 @@ Return the PR URL to the user when done.
|
|||
| Leaving empty sections | Delete sections with no entries |
|
||||
| Putting everything under Other for convenience | Classify what can be classified first |
|
||||
| Translating tool names, command names, or config keys | Keep them as written |
|
||||
| Wrapping a whole doc link in backticks | Code-style the link text inside the brackets instead, so the link stays clickable: [`loop_control`](...) |
|
||||
| Keeping hook/event payload-mechanics clauses | Drop what an event reports or carries; keep the new capability and how to configure it |
|
||||
| Creating a changeset for docs sync | Do not create one |
|
||||
| Committing or pushing directly on `main` | Create `docs/changelog-sync-<version>`, commit there, then open a PR |
|
||||
| Committing or opening a PR before the user skips review or confirms review is done | Wait at the human review checkpoint |
|
||||
| Using curly quotes or half-width Chinese punctuation | Follow `docs/AGENTS.md` |
|
||||
| Omitting the release date from a version heading, or guessing it | Add ` (YYYY-MM-DD)` (full-width `()` in Chinese) taken from the published tag |
|
||||
| Forgetting or translating the `web:` prefix on web UI entries | Prefix web UI entries (commit touches `apps/kimi-web/`) with `web: ` on both pages; keep the prefix as-is when translating |
|
||||
|
||||
## Stop Signals
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ All other workspace packages are private internal packages, are not published to
|
|||
- `@moonshot-ai/kaos`
|
||||
- `@moonshot-ai/kimi-code-oauth`
|
||||
- `@moonshot-ai/kimi-telemetry`
|
||||
- `@moonshot-ai/kimi-web`
|
||||
- `@moonshot-ai/kosong`
|
||||
- `@moonshot-ai/migration-legacy`
|
||||
- `@moonshot-ai/protocol`
|
||||
|
|
|
|||
8
.github/workflows/_native-build.yml
vendored
8
.github/workflows/_native-build.yml
vendored
|
|
@ -86,11 +86,9 @@ jobs:
|
|||
echo "KIMI_CODE_BUILT_IN_CATALOG_FILE=$CATALOG_FILE" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Build Kimi web assets
|
||||
# The SEA blob step embeds apps/kimi-code/dist-web; build the web app
|
||||
# and stage its assets before producing the native executable.
|
||||
run: |
|
||||
pnpm --filter @moonshot-ai/kimi-web run build
|
||||
node apps/kimi-code/scripts/copy-web-assets.mjs
|
||||
# The SEA blob step embeds apps/kimi-code/dist-web. The bundle is
|
||||
# committed (synced from the code-app repo) — just verify it is in place.
|
||||
run: node apps/kimi-code/scripts/check-web-assets.mjs
|
||||
|
||||
- name: Build native executable (release profile, macOS signed)
|
||||
if: runner.os == 'macOS' && inputs.sign-macos
|
||||
|
|
|
|||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
|
@ -127,8 +127,6 @@ jobs:
|
|||
done
|
||||
- name: Typecheck VS Code extension
|
||||
run: pnpm --filter kimi-code run typecheck
|
||||
- name: Typecheck kimi-web (vue-tsc)
|
||||
run: pnpm --filter @moonshot-ai/kimi-web run typecheck
|
||||
- name: Typecheck vis-server
|
||||
run: pnpm --filter @moonshot-ai/vis-server run typecheck
|
||||
- name: Typecheck vis-web
|
||||
|
|
|
|||
3
.github/workflows/pkg-pr-new.yml
vendored
3
.github/workflows/pkg-pr-new.yml
vendored
|
|
@ -36,9 +36,6 @@ jobs:
|
|||
- name: Build package dependencies
|
||||
run: pnpm run build:packages
|
||||
|
||||
- name: Build Kimi web assets
|
||||
run: pnpm --filter @moonshot-ai/kimi-web run build
|
||||
|
||||
- name: Generate Kimi Code built-in catalog
|
||||
shell: bash
|
||||
run: |
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,5 @@
|
|||
node_modules/
|
||||
dist/
|
||||
dist-web/
|
||||
dist-single/
|
||||
dist-native/
|
||||
.tmp-api-extractor/
|
||||
|
|
|
|||
|
|
@ -90,6 +90,26 @@
|
|||
"eslint/no-console": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
// The stage-6 worker closure: these modules (and everything
|
||||
// packages/minidb/src/worker/ pulls in) are loaded by a bare
|
||||
// node:worker_threads Worker under Node's native type stripping with
|
||||
// `execArgv: ['--experimental-transform-types']`, which requires
|
||||
// explicit `.ts` import specifiers (the strip loader does not remap
|
||||
// `.js` -> `.ts`). Keep the exception scoped to exactly that closure.
|
||||
"files": [
|
||||
"packages/minidb/src/worker/**/*.ts",
|
||||
"packages/minidb/src/codec.ts",
|
||||
"packages/minidb/src/crc32.ts",
|
||||
"packages/minidb/src/trigram.ts",
|
||||
"packages/minidb/src/text-postings.ts",
|
||||
"packages/minidb/src/text-index/tokenize.ts",
|
||||
"packages/minidb/src/gen-codec.ts"
|
||||
],
|
||||
"rules": {
|
||||
"import/extensions": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["packages/kosong/src/providers/**/*.ts"],
|
||||
"rules": {
|
||||
|
|
@ -146,6 +166,7 @@
|
|||
],
|
||||
"ignorePatterns": [
|
||||
"dist/",
|
||||
"dist-web/",
|
||||
"coverage/",
|
||||
"node_modules/",
|
||||
"apps/*/scripts/",
|
||||
|
|
|
|||
18
AGENTS.md
18
AGENTS.md
|
|
@ -15,17 +15,21 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo
|
|||
## Project Map
|
||||
|
||||
- `apps/kimi-code`: the CLI / TUI application. It consumes core capabilities through `@moonshot-ai/kimi-code-sdk` and must not depend directly on `@moonshot-ai/agent-core`. When writing or modifying its terminal UI, use the `write-tui` skill (`.agents/skills/write-tui/SKILL.md`).
|
||||
- `apps/kimi-web`: the browser web UI, a peer to the TUI. Vue 3 + Vite + vue-i18n; talks to the server over REST + WebSocket under `/api/v1`. It must not depend on `@moonshot-ai/agent-core` (wire types are re-implemented locally). Debug against the two engines via the root `pnpm dev:v1` / `pnpm dev:v2` backend scripts — the dev Sidebar shows the active backend and switches it at runtime. See `apps/kimi-web/AGENTS.md`.
|
||||
- the browser web UI: **its source no longer lives in this repo.** It is developed in the code-app repo (`apps/web`) and shipped as the committed, prebuilt bundle `apps/kimi-code/dist-web` (gitignored, force-added), synced from code-app with `KIMI_CODE_REPO=<this checkout> pnpm run sync:web` — sync and commit the bundle in the same change whenever the web UI should ship differently. `apps/kimi-code/scripts/check-web-assets.mjs` guards packaging against a missing bundle. To hack on the web UI against this repo's server, run `pnpm dev:server` here and point code-app's `pnpm dev:web` at it via `KIMI_SERVER_URL`.
|
||||
- `apps/vis`, `apps/vis/server`, `apps/vis/web`: visual debugging tools for sessions and replays.
|
||||
- `apps/kimi-inspect`: web inspector for the v2 (kap-server) `/api/v2` surface — workspace/session browser, per-session chat, and live Service panels (data + trigger buttons) for the Session and Agent scopes. Built on its own old-klient-style channel layer (`src/channel/`: the VS Code `ProxyChannel` model — service-bound `IChannel`, HTTP `ProxyChannel` for calls routed to `/api/v2`, `WsChannel` over the shared `/api/v2/ws` socket for events), typed by `agent-core-v2` Service interfaces; `GET {rpcBasePath}/channels` loads every wire protocol 1:1 — probing `/api/v1/debug` first (dev, whitelist-free) and falling back to `/api/v2`. The Vite dev server proxies `/api` to a running kap-server (`KIMI_SERVER_URL`, default `http://127.0.0.1:58627`) and exposes `GET /__inspect/servers` (`vite/serverDiscovery.ts`), which scans the local kap-server instance registry (`~/.kimi-code/server/instances` + legacy `lock`) and the home token so the app can zero-config auto-connect and switch servers from the header dropdown at runtime.
|
||||
- `packages/agent-core`: the unified agent engine, including Agent, Session, profile, skills, tools, plan, permission, background, records, the in-process DI service layer (`src/services/`), and other core capabilities.
|
||||
- `apps/kimi-inspect`: web inspector for the kap-server `/api/v1/debug` RPC surface — workspace/session browser, per-session transcript chat, per-scope Service panels, and the DI unit inspection view. See `apps/kimi-inspect/AGENTS.md`.
|
||||
- `packages/agent-core`: the unified agent engine, including Agent, Session, profile, skills, tools, plan, permission, background, records, the in-process DI service layer (`src/services/`), and other core capabilities. See `packages/agent-core/AGENTS.md`.
|
||||
- `packages/agent-core-v2`: the DI × Scope agent engine (the v2 port behind kap-server). Four `LifecycleScope` tiers — `App` / `Workspace` / `Session` / `Agent` (`app/scopes.ts`) — plus the L3 unit layer (`Service`/`Fiber` units, collection contribution points, the Feature seam in `src/features/`); there is no App-level session lifecycle facade — callers compose `ISessionIndex` → `IWorkspaceLifecycleService.handlerFor` → the handler. See `packages/agent-core-v2/AGENTS.md` and use the `agent-core-dev` skill (`.agents/skills/agent-core-dev/SKILL.md`) when developing here.
|
||||
- `packages/node-sdk`: the public TypeScript SDK and harness.
|
||||
- `packages/kosong`: the LLM / provider abstraction layer.
|
||||
- `packages/kaos`: the execution environment and file/process abstractions.
|
||||
- `packages/oauth`: Kimi OAuth and managed auth utilities.
|
||||
- `packages/telemetry`: shared client-side telemetry infrastructure.
|
||||
- `packages/kap-server`: the Kimi Code server, backed by the DI × Scope agent engine (`@moonshot-ai/agent-core-v2`). Exposes sessions over REST + WebSocket (`/api/v1` and the native `/api/v2` RPC surface); bootstrapped from `src/start.ts` and consumed by `apps/kimi-code`. With `--debug-endpoints` on a loopback bind it additionally mounts `/api/v1/debug/*` — the same reflection dispatcher as `/api/v2` but without the channel whitelist (every scoped Service callable, `src/transport/registerDebugRoutes.ts`); internal only, repo dev scripts pass the flag.
|
||||
- `packages/klient`: the client SDK — a contract-driven facade over agent-core-v2 with aggregated `global.*` / `session(id).*` / `agent(id).*` methods, zod validation on every call, and klient-level typed event forwarding. Transport is chosen once at creation via subpath entry (`@moonshot-ai/klient/http|ipc|memory`); all three return the same `Klient`. The package also hosts the e2e suites: dual-backend session/agent suites (`test/e2e/dual/`, in-memory + in-process server), `/api/v2` wire tests (`test/e2e/v2/`), the legacy `/api/v1` live suites (`test/e2e/legacy/`), and the docker e2e runner (`pnpm --filter @moonshot-ai/klient docker:e2e`). See `packages/klient/AGENTS.md`.
|
||||
- `packages/transcript`: the isomorphic transcript rendering data layer — L1 agent-granular store, L2 idempotent operations, L3 `off/turn/block/delta` subscription granularity, L4 framework-free view registry, plus turn-cursor pagination. Pure TypeScript (browser-safe, no engine imports); the sole owner of the transcript contract types (`src/contract/`) and the op-batch sequencing contract. See `packages/transcript/AGENTS.md`.
|
||||
- `packages/kap-server`: the Kimi Code server, backed by `@moonshot-ai/agent-core-v2`; exposes sessions over REST + WebSocket (`/api/v1` + `/api/v1/ws`), plus the `/api/v1/debug/*` reflection RPC surface (`--debug-endpoints`, loopback bind + bearer auth). See `packages/kap-server/AGENTS.md`.
|
||||
- `packages/klient`: the client SDK — a contract-driven facade over agent-core-v2 (`global.*` / `session(id).*` / `agent(id).*`, zod-validated); transport via subpath entry (`@moonshot-ai/klient/ipc|memory`, both return the same `Klient`); also hosts the e2e suites. See `packages/klient/AGENTS.md`.
|
||||
- `packages/tree-sitter-bash`: a pure-TypeScript bash parser (no runtime deps, no wasm); `parse(source, { timeoutMs, maxNodes })` runs under a deterministic budget and returns a discriminated `ParseResult` — callers must treat aborted/hasError trees as "cannot analyze" and degrade. Parser only, no safety judgments; see the package README's "Known differences" section.
|
||||
- `packages/minidb`: the embedded JSON document store (`MiniDb`) behind kap-server's search index — snapshot + WAL persistence with an exclusive write lock, a larger-than-RAM full-text layer, and persistent index generations. See `packages/minidb/AGENTS.md`.
|
||||
|
||||
## Environment Requirements
|
||||
|
||||
|
|
@ -52,7 +56,6 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo
|
|||
- NO: `interface Options { user?: User | undefined }`
|
||||
- Internal methods with only a single parameter should not be turned into options objects just for stylistic uniformity.
|
||||
- Except for a package's `index.ts`, other `index.ts` files should prefer `export * from './module';`.
|
||||
- The `Agent` class in `packages/agent-core/src/agent` must be usable on its own. The constructor must not force the caller to create a `Session` instance, nor require an `agentId` or `session`. It may accept an optional `sessionId` as a request-config hint — for example mapped to the provider's `prompt_cache_key` — but the instance must not hold `sessionId`, and must not depend on the Session lifecycle, metadata, or parent/child relationship logic.
|
||||
- Do not add too many new test files. Prefer adding tests to the existing test file of the corresponding component or module.
|
||||
- When a test fails because of a user modification, default to fixing the test first; do not change the implementation to satisfy an old test unless the implementation truly has a bug.
|
||||
- Do not sacrifice code quality for external compatibility unless the user explicitly asks for it. Breaking changes go through changesets and a `major` bump, gated by the rule below.
|
||||
|
|
@ -65,6 +68,7 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo
|
|||
|
||||
- Hard rules that affect almost every task: update the root `AGENTS.md`.
|
||||
- Rules that only affect a specific directory: update the nearest sub-directory `AGENTS.md`.
|
||||
- Project-map entries stay at 1–2 sentences; deep package docs live in the package's own `AGENTS.md`.
|
||||
- Keep instruction updates focused and supported by code facts.
|
||||
|
||||
## Workflow Requirements
|
||||
|
|
@ -76,7 +80,7 @@ This is a TypeScript monorepo built for agent-assisted development. Keep the roo
|
|||
- When an AI agent opens or updates a PR, fill in `.github/pull_request_template.md` — link the related issue or explain the problem, then describe what changed. Do not leave placeholder text or submit a generic summary of the diff.
|
||||
- Do not submit vague AI-generated PR text. The human author must understand the change well enough to explain the code, edge cases, and why the approach fits this repository.
|
||||
- After finishing a task and before submitting a PR, you must run the `gen-changesets` skill (see `.agents/skills/gen-changesets/SKILL.md`) and generate a changeset under `.changeset/` according to its rules.
|
||||
- When generating a changeset, **never** decide on a `major` bump on your own. When you judge a change to meet the major criteria (breaking changes, incompatible user configuration, renamed or removed commands/arguments, changed behavior semantics, etc.), you must stop and explain it to the user and ask for confirmation. **Only write `major` after the user has explicitly agreed.** Otherwise default to `minor` (and fall back to `patch` if `minor` is unclear). See the "Hard rule: confirm with the user before writing `major`" section in `.agents/skills/gen-changesets/SKILL.md` for details.
|
||||
- When generating a changeset, **never** decide on a `major` bump on your own — stop, explain, and get explicit user confirmation first; default to `minor`, fall back to `patch`. See `.agents/skills/gen-changesets/SKILL.md`.
|
||||
- Prefer importing via `import ... from '#/...'`, which serves the same purpose as `import ... from '@/...'`.
|
||||
- Do not commit throwaway scratch or exploratory files. Never stage:
|
||||
- Agent working notes or handoff/summary documents (e.g. `HANDOVER-*.md`, `HANDOFF-*.md`, `handoff.md`).
|
||||
|
|
|
|||
|
|
@ -19,12 +19,6 @@ Install with the official script. No Node.js required.
|
|||
curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
|
||||
```
|
||||
|
||||
- **Homebrew (macOS/Linux)**:
|
||||
|
||||
```sh
|
||||
brew install kimi-code
|
||||
```
|
||||
|
||||
- **Windows (PowerShell)**:
|
||||
|
||||
```powershell
|
||||
|
|
|
|||
|
|
@ -22,12 +22,6 @@ Kimi Code CLI 是一个运行在终端里的 AI 编程 agent,可以帮你读
|
|||
curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash
|
||||
```
|
||||
|
||||
- **Homebrew(macOS / Linux)**:
|
||||
|
||||
```sh
|
||||
brew install kimi-code
|
||||
```
|
||||
|
||||
- **Windows(PowerShell)**:
|
||||
|
||||
```powershell
|
||||
|
|
|
|||
2
apps/kimi-code/.gitignore
vendored
2
apps/kimi-code/.gitignore
vendored
|
|
@ -8,4 +8,4 @@ agents/
|
|||
src/generated/vis-web-asset.ts
|
||||
|
||||
# Copied from packages/pi-tui/native at build time by scripts/copy-native-assets.mjs
|
||||
native/
|
||||
/native/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,317 @@
|
|||
# @moonshot-ai/kimi-code
|
||||
|
||||
## 0.34.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2646](https://github.com/MoonshotAI/kimi-code/pull/2646) [`3c75a27`](https://github.com/MoonshotAI/kimi-code/commit/3c75a27da66e522ae670ec8ce9093ea71d091d27) Thanks [@liruifengv](https://github.com/liruifengv)! - Show a cache-expiry reminder when resuming a long-idle session or submitting after a long idle stretch.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: When a model request fails and interrupts a conversation, a persistent failure card now stays in the session with one-click resume.
|
||||
|
||||
- [#2652](https://github.com/MoonshotAI/kimi-code/pull/2652) [`68ba740`](https://github.com/MoonshotAI/kimi-code/commit/68ba740ebfb3e32ad9abdb8607f48d4387cf6f69) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Add Windows support for the built-in Kimi Computer Use capability and show the underlying error when capability setup fails. Install it from `/plugins` on Windows x64.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Add a flat view to the sidebar session list.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2648](https://github.com/MoonshotAI/kimi-code/pull/2648) [`d1ded01`](https://github.com/MoonshotAI/kimi-code/commit/d1ded01b7c50c9847440f4645fe13f588becdc66) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Restore how the last turn ended (completed, cancelled, or failed) when a session is resumed after a server restart, so clients can still surface a previously failed turn instead of the session looking silently stopped.
|
||||
|
||||
- [#2666](https://github.com/MoonshotAI/kimi-code/pull/2666) [`335588e`](https://github.com/MoonshotAI/kimi-code/commit/335588e2594a61a767ce258b34b4049a32b18fe5) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Session listings keep how the last turn ended (completed, cancelled, or failed) across server restarts, so clients can mark previously failed sessions before they are opened.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix the model picker overflowing the screen when many models are available, leaving the bottom options unreachable.
|
||||
|
||||
- [#2639](https://github.com/MoonshotAI/kimi-code/pull/2639) [`8588121`](https://github.com/MoonshotAI/kimi-code/commit/858812193a267fcb9382e351137f892646ef79aa) Thanks [@7Sageer](https://github.com/7Sageer)! - /feedback now works for any signed-in user regardless of the active model; signed-out users are shown the sign-up page and GitHub Issues links instead.
|
||||
|
||||
- [#2675](https://github.com/MoonshotAI/kimi-code/pull/2675) [`34c4181`](https://github.com/MoonshotAI/kimi-code/commit/34c418143759a9e80cdda97e95e609c5a993916d) Thanks [@sailist](https://github.com/sailist)! - Fix kimi -p exiting right after the main turn instead of waiting for background tasks and subagents to finish.
|
||||
|
||||
- [#2694](https://github.com/MoonshotAI/kimi-code/pull/2694) [`02c026d`](https://github.com/MoonshotAI/kimi-code/commit/02c026d4871a14cd5e7b4b0e0ec71ba815f643df) Thanks [@sailist](https://github.com/sailist)! - Keep live sessions stable when an MCP server is removed from the workspace config or uninstalled with its plugin: its tools stay registered in open sessions but calls fail with a removal notice, and the MCP panel shows the removed status. Servers added mid-session — by a plugin install or a config edit — are not registered in open sessions; they take effect in new sessions or after `/new` or `/reload`.
|
||||
|
||||
- [#2647](https://github.com/MoonshotAI/kimi-code/pull/2647) [`7bd3fd9`](https://github.com/MoonshotAI/kimi-code/commit/7bd3fd9f6e6c10f88d33b85760631ad6212b5f58) Thanks [@sailist](https://github.com/sailist)! - Read UTF-16 LE/BE text files (with or without a BOM) by transcoding them to UTF-8 instead of refusing them as binary; the web UI file viewer displays them as text as well.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: The sidebar error marker now only appears when the last turn failed; manually cancelled sessions are no longer flagged.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix attachments being silently dropped when sent together with a skill command.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix a manually chosen thinking level being reset to the model default when the first message of a new session is a skill command.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix session renaming during IME composition — Enter no longer submits mid-composition and Esc no longer exits the editor while composing.
|
||||
|
||||
- [#2686](https://github.com/MoonshotAI/kimi-code/pull/2686) [`ef61084`](https://github.com/MoonshotAI/kimi-code/commit/ef610840098a57819d62d407f33256e14b512c77) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Use a compatible PowerShell for Windows Kimi Computer Use installation, provide actionable recovery for locked plugin files, and keep its marketplace name consistent after installation.
|
||||
|
||||
- [#2679](https://github.com/MoonshotAI/kimi-code/pull/2679) [`7b2784b`](https://github.com/MoonshotAI/kimi-code/commit/7b2784b9b7bf4749058da48923ecbbc8019eb7af) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Subagent tasks now show the model and thinking level they use.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix text selection while renaming a session or workspace — dragging no longer moves the whole list item.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix the chevron direction on the "show less" button of the changed-files summary card.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: During automatic retries after a failed model request, the working status now shows retry progress (attempt N of M) instead of looking unresponsive.
|
||||
|
||||
- [#2677](https://github.com/MoonshotAI/kimi-code/pull/2677) [`713bf1a`](https://github.com/MoonshotAI/kimi-code/commit/713bf1a5a2b388e4c5f9d3f471a728b8edbf5811) Thanks [@liruifengv](https://github.com/liruifengv)! - Fix resumed sessions rendering background task completion notifications as raw protocol text instead of a task status card.
|
||||
|
||||
- [#2692](https://github.com/MoonshotAI/kimi-code/pull/2692) [`03aa66c`](https://github.com/MoonshotAI/kimi-code/commit/03aa66ca0cca5880dc3a4a89e4f46d09acbe47ae) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Show browser extension links and activation steps after installing Kimi WebBridge.
|
||||
|
||||
- [#2645](https://github.com/MoonshotAI/kimi-code/pull/2645) [`2b89373`](https://github.com/MoonshotAI/kimi-code/commit/2b893733f9853dc0aaeb775d9670d277db8e0381) Thanks [@sailist](https://github.com/sailist)! - Fix the web UI opening the Documents folder instead of the requested file on Windows when the file path contains spaces.
|
||||
|
||||
- [#2697](https://github.com/MoonshotAI/kimi-code/pull/2697) [`e6e4ba2`](https://github.com/MoonshotAI/kimi-code/commit/e6e4ba2357cc659ebd0fd44c9492b498adc33d0e) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Fix the background-tasks and todos pills being pushed to the top of the window when the plan approval dialog expands.
|
||||
|
||||
## 0.33.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2407](https://github.com/MoonshotAI/kimi-code/pull/2407) [`0abcd00`](https://github.com/MoonshotAI/kimi-code/commit/0abcd00f7fd3e3cbf087509ffef1c54a6f8d396d) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Add Kimi Computer Use and Kimi WebBridge as built-in official marketplace entries in the v2 CLI. Installing from `/plugins` sets up the latest managed runtime and plugin together, reports incomplete manual steps, and supports retrying interrupted setup.
|
||||
|
||||
- [#2627](https://github.com/MoonshotAI/kimi-code/pull/2627) [`f881cdd`](https://github.com/MoonshotAI/kimi-code/commit/f881cdd97073475c43272ec5734bbc39290dd399) Thanks [@sailist](https://github.com/sailist)! - Run the CLI surfaces (interactive TUI, `kimi -p`, `kimi acp`, `kimi export`, `kimi provider`) on the agent-core-v2 engine by default. Set `KIMI_CODE_LEGACY_FLAG=1` to fall back to the legacy engine.
|
||||
|
||||
- [#2565](https://github.com/MoonshotAI/kimi-code/pull/2565) [`54c04bf`](https://github.com/MoonshotAI/kimi-code/commit/54c04bf03ddbeb46d02b2edb460ea091ae194509) Thanks [@7Sageer](https://github.com/7Sageer)! - `/fork` no longer switches to the forked session: the current session stays active and its background tasks keep running. Find the fork in `/sessions`.
|
||||
|
||||
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - Ask whether to trust the current folder on startup.
|
||||
|
||||
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Add and manage custom providers in settings.
|
||||
|
||||
- [#2599](https://github.com/MoonshotAI/kimi-code/pull/2599) [`541ddd2`](https://github.com/MoonshotAI/kimi-code/commit/541ddd2d898c4880a312874b1c539f85888bf0c1) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Overhaul the UI/UX and fix known issues.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2601](https://github.com/MoonshotAI/kimi-code/pull/2601) [`75fe068`](https://github.com/MoonshotAI/kimi-code/commit/75fe068a01261ff6b34f176530b338ec6a24918e) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Fix built-in capability availability and installed status in `/plugins`, preserve legacy WebBridge skills as backups during updates, and prevent Computer Use updates from duplicating or disconnecting MCP servers.
|
||||
|
||||
- [#2635](https://github.com/MoonshotAI/kimi-code/pull/2635) [`2b3e9a9`](https://github.com/MoonshotAI/kimi-code/commit/2b3e9a9f7910b0bb8050380068fa122c2c2cee91) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Rename the partner plugin marketplace tab to Curated and clarify that it contains third-party plugins from Kimi partners.
|
||||
|
||||
- [#2614](https://github.com/MoonshotAI/kimi-code/pull/2614) [`8db7d42`](https://github.com/MoonshotAI/kimi-code/commit/8db7d42f23472a692eb389a0e0e5a3e18aa1b94d) Thanks [@RealKai42](https://github.com/RealKai42)! - Add /bug as an alias for the /feedback slash command. Type /bug to submit feedback.
|
||||
|
||||
- [#2586](https://github.com/MoonshotAI/kimi-code/pull/2586) [`278b6af`](https://github.com/MoonshotAI/kimi-code/commit/278b6af19d8708ec0f3eb2696d62a8d8209d497d) Thanks [@7Sageer](https://github.com/7Sageer)! - Ensure the first request waits for MCP startup to finish while the interface still opens immediately.
|
||||
|
||||
- [#2620](https://github.com/MoonshotAI/kimi-code/pull/2620) [`2ee6e43`](https://github.com/MoonshotAI/kimi-code/commit/2ee6e431240a4a31034e0a403011dd6b2bfef9df) Thanks [@xpzouying](https://github.com/xpzouying)! - Fixed MCP OAuth re-authorization always failing with "Invalid redirect URI": the OAuth callback listener binds a random port per flow, but the dynamic client registration recorded the first flow's port, so every later interactive authorization was rejected at the authorization endpoint. A stale registration is now dropped automatically and the flow re-registers with the current callback URI.
|
||||
|
||||
- [#2596](https://github.com/MoonshotAI/kimi-code/pull/2596) [`c32e661`](https://github.com/MoonshotAI/kimi-code/commit/c32e661faa931df9fdc72e63230f3ebebc00dce5) Thanks [@xpzouying](https://github.com/xpzouying)! - MCP tool results now surface the spec-defined `structuredContent` field and `_meta` server metadata to the model as a serialized `<mcp-structured-result>` block, instead of silently dropping them. Servers that return their machine-readable contract in these fields work the same as on other MCP hosts.
|
||||
|
||||
- [#2612](https://github.com/MoonshotAI/kimi-code/pull/2612) [`e357028`](https://github.com/MoonshotAI/kimi-code/commit/e3570280bde775a153ee04388393506da7ac4cc1) Thanks [@7Sageer](https://github.com/7Sageer)! - Fix all tool calls failing with spawn EBADF on macOS when a skill folder contains a very large file tree.
|
||||
|
||||
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - Start the interactive TUI without creating a session.
|
||||
|
||||
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Show the signed-in account and plan usage.
|
||||
|
||||
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Set an emoji for the session title.
|
||||
|
||||
- [#2630](https://github.com/MoonshotAI/kimi-code/pull/2630) [`3bd098b`](https://github.com/MoonshotAI/kimi-code/commit/3bd098b80643c99eabdc602b767dbc53fc47cedd) Thanks [@liruifengv](https://github.com/liruifengv)! - web: Pin sessions to the top of the sidebar.
|
||||
|
||||
## 0.32.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2558](https://github.com/MoonshotAI/kimi-code/pull/2558) [`75395f6`](https://github.com/MoonshotAI/kimi-code/commit/75395f6abb17f83f30d16b51f4e060a639f43622) Thanks [@sailist](https://github.com/sailist)! - Add the TurnStarted, UserPromptQueued, TaskStarted, and SessionHeartbeat hook events, enrich hook payloads with the session title and client type, include the model and profile in SessionStart, and report SessionEnd as archive when a session is archived instead of exited. Configure the new events under [[hooks]] in config.toml.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2416](https://github.com/MoonshotAI/kimi-code/pull/2416) [`eaab2b6`](https://github.com/MoonshotAI/kimi-code/commit/eaab2b6f28c0b958edf8ab5ae5e78a4c0426af26) Thanks [@mangeshraut712](https://github.com/mangeshraut712)! - Fall back to the built-in models.dev catalog snapshot when the public catalog is unreachable, so Known third-party provider import still works offline or in blocked networks.
|
||||
|
||||
- [#2083](https://github.com/MoonshotAI/kimi-code/pull/2083) [`bfa0080`](https://github.com/MoonshotAI/kimi-code/commit/bfa00807c975fdc5b84dda32d47b16b09e8d42c1) Thanks [@StaR4y](https://github.com/StaR4y)! - web: Fix dark-mode monochrome controls and align the chat composer corner radius with the design system.
|
||||
|
||||
- [#2559](https://github.com/MoonshotAI/kimi-code/pull/2559) [`dfc55a5`](https://github.com/MoonshotAI/kimi-code/commit/dfc55a5c977dbff657e1da74ff5c2b9d488807be) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Render the "/login" already-logged-in confirmation in the success color instead of dim text, so the "Already logged in. Model configuration refreshed." message is clearly visible.
|
||||
|
||||
- [#2572](https://github.com/MoonshotAI/kimi-code/pull/2572) [`6ba75a1`](https://github.com/MoonshotAI/kimi-code/commit/6ba75a173b595904bc70d0d7161de2f9b964c961) Thanks [@sailist](https://github.com/sailist)! - Rename the `[loop_control] max_retries_per_step` config key to `max_attempts_per_step` and `max_steps_per_run` to `max_steps_per_turn`: on the v2 engine the old keys no longer take effect and a startup warning prompts the rename in `config.toml`. The `KIMI_LOOP_MAX_RETRIES_PER_STEP` env var is likewise deprecated in favor of `KIMI_LOOP_MAX_ATTEMPTS_PER_STEP` but keeps working with a warning.
|
||||
|
||||
- [#2585](https://github.com/MoonshotAI/kimi-code/pull/2585) [`c396873`](https://github.com/MoonshotAI/kimi-code/commit/c39687318c64bf8a305a10bf9ca86ef6ef2c6656) Thanks [@sailist](https://github.com/sailist)! - Fix submitting answers to interactive question prompts being rejected when the model provider returns tool call IDs containing colons (some OpenAI-compatible gateways).
|
||||
|
||||
- [#2562](https://github.com/MoonshotAI/kimi-code/pull/2562) [`071b6a5`](https://github.com/MoonshotAI/kimi-code/commit/071b6a50d9c2ce9c4b45dc4d58dac1101b8c4f52) Thanks [@sailist](https://github.com/sailist)! - Serve v1 message history from the server layer and drop the engine-side legacy message adapter; the /api/v1 message contract is unchanged.
|
||||
|
||||
- [#2562](https://github.com/MoonshotAI/kimi-code/pull/2562) [`071b6a5`](https://github.com/MoonshotAI/kimi-code/commit/071b6a50d9c2ce9c4b45dc4d58dac1101b8c4f52) Thanks [@sailist](https://github.com/sailist)! - Assemble the session snapshot endpoint from the engine's services for both cold and live sessions, and remove the KIMI_SNAPSHOT_READER, KIMI_SNAPSHOT_TIMEOUT_MS, and KIMI_SNAPSHOT_CACHE_LIMIT environment knobs.
|
||||
|
||||
- [#2563](https://github.com/MoonshotAI/kimi-code/pull/2563) [`2118544`](https://github.com/MoonshotAI/kimi-code/commit/21185447fe0f04dbe342bebb6c6d0b364fd43daa) Thanks [@sailist](https://github.com/sailist)! - Fix the context window limit showing as 0 in session status updates when no model is bound yet or the configured model no longer resolves; the limit now falls back to the default model or is omitted when unknown.
|
||||
|
||||
- [#2563](https://github.com/MoonshotAI/kimi-code/pull/2563) [`2118544`](https://github.com/MoonshotAI/kimi-code/commit/21185447fe0f04dbe342bebb6c6d0b364fd43daa) Thanks [@sailist](https://github.com/sailist)! - The `[token_counting]` strategy now only selects the reported context size: `estimated` keeps provider-reported usage out of the context-size display, and `measured` no longer gets stuck retrying an oversized compaction request until it fails.
|
||||
|
||||
- [#2563](https://github.com/MoonshotAI/kimi-code/pull/2563) [`2118544`](https://github.com/MoonshotAI/kimi-code/commit/21185447fe0f04dbe342bebb6c6d0b364fd43daa) Thanks [@sailist](https://github.com/sailist)! - Add a `[token_counting]` config section to choose how context token counts are derived: `measured+estimated` (default), `measured` (provider usage only), or `estimated` (heuristic only, for providers without usage reporting). Set `strategy` under `[token_counting]` in config.toml (or `KIMI_TOKEN_COUNTING_STRATEGY`) to switch.
|
||||
|
||||
## 0.31.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2410](https://github.com/MoonshotAI/kimi-code/pull/2410) [`f1a3475`](https://github.com/MoonshotAI/kimi-code/commit/f1a3475ad5d6540447496701aa75fd4b035ecb28) Thanks [@sailist](https://github.com/sailist)! - Fix sporadic "model is not configured" errors when starting kimi web, caused by the background provider-model refresh transiently clearing the model catalog while the first session was being created.
|
||||
|
||||
- [#2400](https://github.com/MoonshotAI/kimi-code/pull/2400) [`1f3f5da`](https://github.com/MoonshotAI/kimi-code/commit/1f3f5dadaaa4a1d705cc98aee1dbbef13680502c) Thanks [@7Sageer](https://github.com/7Sageer)! - Preserve the assistant's partial output when a turn is interrupted with Esc, and remind the model that the previous turn was deliberately interrupted.
|
||||
|
||||
- [#2415](https://github.com/MoonshotAI/kimi-code/pull/2415) [`5c0ec29`](https://github.com/MoonshotAI/kimi-code/commit/5c0ec2938ac3a01624b6503e5e5df80c9b08f46a) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Enable Monaco-based highlighting for code blocks, and fix line numbers overlapping or drifting out of alignment in fallback-rendered code blocks.
|
||||
|
||||
- [#2442](https://github.com/MoonshotAI/kimi-code/pull/2442) [`bb2919e`](https://github.com/MoonshotAI/kimi-code/commit/bb2919eb818a6cb51c71cbabf1bac9020131bce7) Thanks [@liruifengv](https://github.com/liruifengv)! - Reduce frequent full-screen redraws in the TUI.
|
||||
|
||||
- [#2125](https://github.com/MoonshotAI/kimi-code/pull/2125) [`e111c87`](https://github.com/MoonshotAI/kimi-code/commit/e111c878fd5cd07994e125b9e4e07e4069f01be1) Thanks [@bowenliang123](https://github.com/bowenliang123)! - web: Order permission modes from safest to most permissive across settings surfaces, and fix the swapped yolo/auto risk colors in the status panel and mobile settings.
|
||||
|
||||
- [#2459](https://github.com/MoonshotAI/kimi-code/pull/2459) [`326e1fb`](https://github.com/MoonshotAI/kimi-code/commit/326e1fb6ce59fbf2d6c7646e6d587759565814fd) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Fix chat code blocks rendering in the proportional UI font at the wrong size after the markdown renderer upgrade, and align the loading fallback with the highlighted block so the upgrade no longer shifts layout.
|
||||
|
||||
- [#2437](https://github.com/MoonshotAI/kimi-code/pull/2437) [`ed7a4cc`](https://github.com/MoonshotAI/kimi-code/commit/ed7a4cc095e1619e4dbb6c2c77c89a52e312b085) Thanks [@sailist](https://github.com/sailist)! - web: Make the @ file mention work in a new-session draft, before the first prompt creates the session.
|
||||
|
||||
- [#2437](https://github.com/MoonshotAI/kimi-code/pull/2437) [`ed7a4cc`](https://github.com/MoonshotAI/kimi-code/commit/ed7a4cc095e1619e4dbb6c2c77c89a52e312b085) Thanks [@sailist](https://github.com/sailist)! - web: Fix new sessions showing the thinking level (e.g. Max) while the first message actually ran with thinking off.
|
||||
|
||||
## 0.31.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2365](https://github.com/MoonshotAI/kimi-code/pull/2365) [`fa2c5ce`](https://github.com/MoonshotAI/kimi-code/commit/fa2c5ce18b70577fa3ada4eb8bdd4993891994ce) Thanks [@7Sageer](https://github.com/7Sageer)! - Add support for plugin-contributed custom agents, discovered automatically and available for sub-agent delegation. Ship an `agents/` directory in the plugin (or declare `agents` paths in the plugin manifest) to provide them.
|
||||
|
||||
- [#2314](https://github.com/MoonshotAI/kimi-code/pull/2314) [`02d77b2`](https://github.com/MoonshotAI/kimi-code/commit/02d77b20d941873563f14890e049ffe40cec76e4) Thanks [@7Sageer](https://github.com/7Sageer)! - Allow enabled plugins to contribute agent system-prompt instructions through `systemPrompt` or `systemPromptPath` in `kimi.plugin.json`, effective on both agent engines (the TUI, `kimi -p`, and `kimi web`).
|
||||
|
||||
- [#2232](https://github.com/MoonshotAI/kimi-code/pull/2232) [`efac96c`](https://github.com/MoonshotAI/kimi-code/commit/efac96c8a95a3c3ca4e1ae9bce38082498a02b2e) Thanks [@7Sageer](https://github.com/7Sageer)! - Support Markdown-defined custom agents on agent-core.
|
||||
|
||||
- [#2232](https://github.com/MoonshotAI/kimi-code/pull/2232) [`efac96c`](https://github.com/MoonshotAI/kimi-code/commit/efac96c8a95a3c3ca4e1ae9bce38082498a02b2e) Thanks [@7Sageer](https://github.com/7Sageer)! - Add the /secondary_model slash command to configure the secondary model used by subagents.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2382](https://github.com/MoonshotAI/kimi-code/pull/2382) [`40172c7`](https://github.com/MoonshotAI/kimi-code/commit/40172c7ca96ca981b043b793588dd32e898979fa) Thanks [@liruifengv](https://github.com/liruifengv)! - Fix request headers not being passed correctly on some requests.
|
||||
|
||||
- [#2379](https://github.com/MoonshotAI/kimi-code/pull/2379) [`691ec46`](https://github.com/MoonshotAI/kimi-code/commit/691ec4679ea19d6be8ac18f359088384ed3e446d) Thanks [@RealKai42](https://github.com/RealKai42)! - Remove the blocking `block`/`timeout` wait from the TaskOutput tool so checking a background task can no longer stall the conversation; it now always returns an immediate snapshot, and completion still arrives via automatic notification.
|
||||
|
||||
- [#2395](https://github.com/MoonshotAI/kimi-code/pull/2395) [`d10b1c1`](https://github.com/MoonshotAI/kimi-code/commit/d10b1c130813dbd6ee8c8599a6a98feb36aea67f) Thanks [@sailist](https://github.com/sailist)! - Fix sessions missing from the session picker when their cached metadata predates the archived flag.
|
||||
|
||||
## 0.30.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#2255](https://github.com/MoonshotAI/kimi-code/pull/2255) [`67dd031`](https://github.com/MoonshotAI/kimi-code/commit/67dd03149f36be91a0c081e70d8a2d721b0f1c64) Thanks [@he-yufeng](https://github.com/he-yufeng)! - Add a customizable footer status line, configured via `[status_line]` in `tui.toml`.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2313](https://github.com/MoonshotAI/kimi-code/pull/2313) [`de0ba9d`](https://github.com/MoonshotAI/kimi-code/commit/de0ba9d0654273ff6b028a7a561983ebee4e723e) Thanks [@starquakee](https://github.com/starquakee)! - Stop the turn after repeated invalid tool calls instead of retrying indefinitely.
|
||||
|
||||
- [#2147](https://github.com/MoonshotAI/kimi-code/pull/2147) [`29783e4`](https://github.com/MoonshotAI/kimi-code/commit/29783e471afcf7975852e496907646458264d2e6) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Show a quota note after installing official plugins that bill against plan quota (such as Kimi Datasource).
|
||||
|
||||
- [#2147](https://github.com/MoonshotAI/kimi-code/pull/2147) [`29783e4`](https://github.com/MoonshotAI/kimi-code/commit/29783e471afcf7975852e496907646458264d2e6) Thanks [@wbxl2000](https://github.com/wbxl2000)! - Show a notice when an official plugin used in the session has an update available. Run /plugins to install it.
|
||||
|
||||
- [#1857](https://github.com/MoonshotAI/kimi-code/pull/1857) [`cdbd33c`](https://github.com/MoonshotAI/kimi-code/commit/cdbd33c13c7f5cd4c49ec112ee4313b3938a7752) Thanks [@vinlee19](https://github.com/vinlee19)! - Fail fast when account quota or balance is exhausted instead of silently retrying for ~3 minutes.
|
||||
|
||||
- [#2294](https://github.com/MoonshotAI/kimi-code/pull/2294) [`425cfdf`](https://github.com/MoonshotAI/kimi-code/commit/425cfdf53f0fd3b01527f5fba87acff68f49f368) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Fix garbled line numbers in code blocks.
|
||||
|
||||
- [#2312](https://github.com/MoonshotAI/kimi-code/pull/2312) [`d03a488`](https://github.com/MoonshotAI/kimi-code/commit/d03a4886fdf7c35014c10079a3d417aeb0447d9a) Thanks [@sailist](https://github.com/sailist)! - Remove the 50 MB size limit on file uploads to the built-in server.
|
||||
|
||||
## 0.29.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2192](https://github.com/MoonshotAI/kimi-code/pull/2192) [`7799bd7`](https://github.com/MoonshotAI/kimi-code/commit/7799bd7346aaee11ec2b6d6883e1e4fe5ab10717) Thanks [@sailist](https://github.com/sailist)! - Hold per-agent runtime state of the experimental engine in the agent-scope state container, so it is observable in one place and disposed with the agent; state snapshots collapse class instances to name markers so resource graphs cannot exhaust memory during export.
|
||||
|
||||
- [#2119](https://github.com/MoonshotAI/kimi-code/pull/2119) [`f06eb5c`](https://github.com/MoonshotAI/kimi-code/commit/f06eb5c60e0a4e51162d1854dda1db41892b457c) Thanks [@pvzheroes125](https://github.com/pvzheroes125)! - Allow hosts to defer registered user-tool schemas until needed. Set `disclosure: "deferred"` when registering a tool.
|
||||
|
||||
- [#2192](https://github.com/MoonshotAI/kimi-code/pull/2192) [`7799bd7`](https://github.com/MoonshotAI/kimi-code/commit/7799bd7346aaee11ec2b6d6883e1e4fe5ab10717) Thanks [@sailist](https://github.com/sailist)! - Instantiate every registered service eagerly at scope creation on the experimental engine, following the dependency graph automatically, and drop the hand-maintained lists that resolved side-effect services one by one at startup.
|
||||
|
||||
- [#2120](https://github.com/MoonshotAI/kimi-code/pull/2120) [`0d00a07`](https://github.com/MoonshotAI/kimi-code/commit/0d00a07c02e334ca904077b2ea8c56cf58b44586) Thanks [@yicun](https://github.com/yicun)! - web: Fix copying selected chat text over plain HTTP from replacing the clipboard with an event placeholder.
|
||||
|
||||
- [#2210](https://github.com/MoonshotAI/kimi-code/pull/2210) [`0cef160`](https://github.com/MoonshotAI/kimi-code/commit/0cef160c4b900a3d78212cd5da4b80d335ea0b6f) Thanks [@chengluyu](https://github.com/chengluyu)! - Fix goal pursuit being interrupted when a goal turn reaches the per-turn step limit (`loop_control.max_steps_per_turn`); the limit now splits goal work into more continuation turns instead of pausing the goal.
|
||||
|
||||
- [#2153](https://github.com/MoonshotAI/kimi-code/pull/2153) [`c497af6`](https://github.com/MoonshotAI/kimi-code/commit/c497af60e6cd20aab05e590f98a28fb15dd3491d) Thanks [@chengluyu](https://github.com/chengluyu)! - Fix messages sent while a goal is running being rejected with a "Cannot launch a new turn while another turn is active" error; they are now steered into the active goal turn instead of being dropped.
|
||||
|
||||
- [#2192](https://github.com/MoonshotAI/kimi-code/pull/2192) [`7799bd7`](https://github.com/MoonshotAI/kimi-code/commit/7799bd7346aaee11ec2b6d6883e1e4fe5ab10717) Thanks [@sailist](https://github.com/sailist)! - Hold per-session runtime state of the experimental engine in the session-scope state container, so it is observable in one place and disposed with the session.
|
||||
|
||||
- [#2055](https://github.com/MoonshotAI/kimi-code/pull/2055) [`d40d0d3`](https://github.com/MoonshotAI/kimi-code/commit/d40d0d305d2866cb5ab8696e559e0813b5f92201) Thanks [@7Sageer](https://github.com/7Sageer)! - Fix /undo to restore conversation history, todo lists, plan mode, and task notifications consistently.
|
||||
|
||||
## 0.29.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2065](https://github.com/MoonshotAI/kimi-code/pull/2065) [`527d485`](https://github.com/MoonshotAI/kimi-code/commit/527d485d9296fe20f473a4a578d9e6a499c20cd9) Thanks [@7Sageer](https://github.com/7Sageer)! - Add global default MCP server timeouts in `config.toml` and env vars.
|
||||
|
||||
- [#2104](https://github.com/MoonshotAI/kimi-code/pull/2104) [`66f611a`](https://github.com/MoonshotAI/kimi-code/commit/66f611aae99887ad2076aa3482a0df5e415d3511) Thanks [@RealKai42](https://github.com/RealKai42)! - Fix loss of thinking content with OpenAI-compatible endpoints that return reasoning under a different field name (e.g. newer vLLM); the reasoning field is now detected per endpoint and echoed back on follow-up requests.
|
||||
|
||||
- [#2089](https://github.com/MoonshotAI/kimi-code/pull/2089) [`ca38b7e`](https://github.com/MoonshotAI/kimi-code/commit/ca38b7ed864ad5fa2b2e3c8b96d8a7b10a734445) Thanks [@liruifengv](https://github.com/liruifengv)! - Remove the toolbar tip that suggested trying the "superpowers" plugin.
|
||||
|
||||
- [#2064](https://github.com/MoonshotAI/kimi-code/pull/2064) [`7b62ed5`](https://github.com/MoonshotAI/kimi-code/commit/7b62ed5b2c2709719f360c01a2f513dee34ae179) Thanks [@7Sageer](https://github.com/7Sageer)! - Add experimental secondary-model bindings for newly spawned subagents, including per-agent model preferences and subagent-only model overrides.
|
||||
|
||||
- [#2096](https://github.com/MoonshotAI/kimi-code/pull/2096) [`5fdbdb4`](https://github.com/MoonshotAI/kimi-code/commit/5fdbdb4a22b86ae6f7ba7c775741689aaaf215f0) Thanks [@7Sageer](https://github.com/7Sageer)! - Add environment variables to configure the web search and web fetch services without OAuth login.
|
||||
|
||||
## 0.29.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1992](https://github.com/MoonshotAI/kimi-code/pull/1992) [`a8f1ca3`](https://github.com/MoonshotAI/kimi-code/commit/a8f1ca3f1016a3e84986f297367e833bc731ac39) Thanks [@RealKai42](https://github.com/RealKai42)! - Support selecting a thinking effort level from ACP clients: the thinking picker now lists the current model's declared levels (for example off / low / medium / high) instead of only an on/off toggle. Use the thinking selector in your ACP client (e.g. Zed) to pick a level; the legacy on/off values keep working.
|
||||
|
||||
- [#1735](https://github.com/MoonshotAI/kimi-code/pull/1735) [`ce0e3ce`](https://github.com/MoonshotAI/kimi-code/commit/ce0e3ceb04223bdaad8e8931bad46eff561055b6) Thanks [@7Sageer](https://github.com/7Sageer)! - Let custom agent files restrict which sub-agent types they may delegate to (v2 engine only).
|
||||
|
||||
- [#1735](https://github.com/MoonshotAI/kimi-code/pull/1735) [`ce0e3ce`](https://github.com/MoonshotAI/kimi-code/commit/ce0e3ceb04223bdaad8e8931bad46eff561055b6) Thanks [@7Sageer](https://github.com/7Sageer)! - Support custom agents defined as Markdown files with frontmatter, usable as the main agent or a sub-agent (v2 engine only).
|
||||
|
||||
- [#1735](https://github.com/MoonshotAI/kimi-code/pull/1735) [`ce0e3ce`](https://github.com/MoonshotAI/kimi-code/commit/ce0e3ceb04223bdaad8e8931bad46eff561055b6) Thanks [@7Sageer](https://github.com/7Sageer)! - Add global tool gating to constrain which tools agents may use, with a per-session override (v2 engine only).
|
||||
|
||||
- [#2012](https://github.com/MoonshotAI/kimi-code/pull/2012) [`d67a200`](https://github.com/MoonshotAI/kimi-code/commit/d67a2003abf2d8d802dcf24f806e0a811724b83e) Thanks [@sailist](https://github.com/sailist)! - Add a GET /api/v1/fs:content server endpoint that serves any file on the host by absolute path as raw content with Content-Type, ETag, and Range support.
|
||||
|
||||
- [#1999](https://github.com/MoonshotAI/kimi-code/pull/1999) [`4c763f6`](https://github.com/MoonshotAI/kimi-code/commit/4c763f6763acb67a73d133f7450d092e71d63692) Thanks [@RealKai42](https://github.com/RealKai42)! - Videos attached to a prompt — pasted in the TUI or uploaded in the web UI — now reach the model together with the prompt, with no extra tool round trip, and stay playable in the chat after a reload.
|
||||
|
||||
- [#1735](https://github.com/MoonshotAI/kimi-code/pull/1735) [`ce0e3ce`](https://github.com/MoonshotAI/kimi-code/commit/ce0e3ceb04223bdaad8e8931bad46eff561055b6) Thanks [@7Sageer](https://github.com/7Sageer)! - Support overriding the default main-agent system prompt with a user-level file for every session (v2 engine only).
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1997](https://github.com/MoonshotAI/kimi-code/pull/1997) [`74da87a`](https://github.com/MoonshotAI/kimi-code/commit/74da87a457c2964694a844dd22a4925f5113b167) Thanks [@sailist](https://github.com/sailist)! - Add agent.created and agent.disposed events to the server session event stream, and expose each agent's disposal time in the transcript API.
|
||||
|
||||
- [#2030](https://github.com/MoonshotAI/kimi-code/pull/2030) [`ec88d35`](https://github.com/MoonshotAI/kimi-code/commit/ec88d352e8f4dc5e8ffd1212f016138458f69893) Thanks [@RealKai42](https://github.com/RealKai42)! - Fix catalog-imported Claude models being wrongly locked into always-on thinking, and stop offering a misleading thinking Off option for models that cannot truly disable reasoning (such as Gemini 3). Also normalizes configured thinking effort values and unifies context-usage reporting.
|
||||
|
||||
- [#2015](https://github.com/MoonshotAI/kimi-code/pull/2015) [`b5efba7`](https://github.com/MoonshotAI/kimi-code/commit/b5efba7abcaf4041f81ec520097a61e6546e8c50) Thanks [@RealKai42](https://github.com/RealKai42)! - Import many more providers from the models.dev catalog: vendor SDKs like xai and openrouter now import instead of being refused (with a "guessed" note), deprecated and alpha models are filtered out, per-model gateway protocol and endpoint overrides are honored, and context limits are correct (input limit for compaction, total window for completion). Imports lacking a usable endpoint now ask for one via `--base-url` or a prompt.
|
||||
|
||||
- [#1993](https://github.com/MoonshotAI/kimi-code/pull/1993) [`37eda4e`](https://github.com/MoonshotAI/kimi-code/commit/37eda4e59aebc8ecafa91be3f43f971ed63963a3) Thanks [@RealKai42](https://github.com/RealKai42)! - Add environment variable overrides for agent loop and background task limits. Set KIMI_LOOP_MAX_STEPS_PER_TURN, KIMI_LOOP_MAX_RETRIES_PER_STEP, or KIMI_CODE_BACKGROUND_MAX_RUNNING_TASKS to take priority over the [loop_control] and [background] config.
|
||||
|
||||
- [#1993](https://github.com/MoonshotAI/kimi-code/pull/1993) [`37eda4e`](https://github.com/MoonshotAI/kimi-code/commit/37eda4e59aebc8ecafa91be3f43f971ed63963a3) Thanks [@RealKai42](https://github.com/RealKai42)! - Fix config environment overrides (such as KIMI_IMAGE_MAX_EDGE_PX or KIMI_SUBAGENT_TIMEOUT_MS) being persisted into config.toml by config API writes while the env var is set, and keeping the old value after the env var is changed to an invalid value or removed.
|
||||
|
||||
- [#2050](https://github.com/MoonshotAI/kimi-code/pull/2050) [`8250e59`](https://github.com/MoonshotAI/kimi-code/commit/8250e590f3ed5990c233ef5a2c7666468f0bcb05) Thanks [@sailist](https://github.com/sailist)! - Remove references to the non-existent `kimi resume` command from the scheduled-task tool descriptions.
|
||||
|
||||
- [#1970](https://github.com/MoonshotAI/kimi-code/pull/1970) [`6dd4fd3`](https://github.com/MoonshotAI/kimi-code/commit/6dd4fd33688b37904d5302436fc2daaf09d66c7d) Thanks [@sailist](https://github.com/sailist)! - Fix cancelled model requests being wrapped as retryable provider errors, so interrupting a request no longer triggers silent retries.
|
||||
|
||||
- [#1970](https://github.com/MoonshotAI/kimi-code/pull/1970) [`6dd4fd3`](https://github.com/MoonshotAI/kimi-code/commit/6dd4fd33688b37904d5302436fc2daaf09d66c7d) Thanks [@sailist](https://github.com/sailist)! - Send the session prompt cache key to OpenAI and OpenAI Responses providers, restoring provider-side prompt cache affinity that previously only reached Kimi and Anthropic.
|
||||
|
||||
- [#1999](https://github.com/MoonshotAI/kimi-code/pull/1999) [`4c763f6`](https://github.com/MoonshotAI/kimi-code/commit/4c763f6763acb67a73d133f7450d092e71d63692) Thanks [@RealKai42](https://github.com/RealKai42)! - Fix ReadMediaFile failing on videos when the provider has no file upload channel — such videos now fall back to inline delivery.
|
||||
|
||||
- [#1968](https://github.com/MoonshotAI/kimi-code/pull/1968) [`71bcfba`](https://github.com/MoonshotAI/kimi-code/commit/71bcfba54a6836f4b6d4e26babde67576b293a64) Thanks [@RealKai42](https://github.com/RealKai42)! - Fix sessions getting stuck on every turn with a provider "message must not be empty" error after a content-filtered response.
|
||||
|
||||
- [#2022](https://github.com/MoonshotAI/kimi-code/pull/2022) [`154e082`](https://github.com/MoonshotAI/kimi-code/commit/154e0824880c8573433e4ec7ada083744dbfe9f9) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Show transparent images over a checkerboard canvas so white and black content stays visible in both light and dark themes.
|
||||
|
||||
- [#1990](https://github.com/MoonshotAI/kimi-code/pull/1990) [`115b096`](https://github.com/MoonshotAI/kimi-code/commit/115b0968cefede7fac1494c6f0154ea5545a89da) Thanks [@liruifengv](https://github.com/liruifengv)! - Fix goal mode continuation prompts leaking into the transcript when resuming a session.
|
||||
|
||||
- [#1970](https://github.com/MoonshotAI/kimi-code/pull/1970) [`6dd4fd3`](https://github.com/MoonshotAI/kimi-code/commit/6dd4fd33688b37904d5302436fc2daaf09d66c7d) Thanks [@sailist](https://github.com/sailist)! - Rework the model wire layer in the experimental v2 engine into a small set of protocol bases plus declarative provider trait definitions, so adding a provider no longer means copying adapter code, and per-turn request intent (cache key, thinking effort, sampling) flows as request parameters instead of cloned model objects. The never-functional `[platforms]` config section and the `provider.platformId` field are removed; credential resolution is now a two-layer model → provider lookup.
|
||||
|
||||
- [#1976](https://github.com/MoonshotAI/kimi-code/pull/1976) [`e458323`](https://github.com/MoonshotAI/kimi-code/commit/e45832398d0d9cad98dbad1cbf1e5b103a20aace) Thanks [@liruifengv](https://github.com/liruifengv)! - Improve TUI performance and resume speed for long-running sessions.
|
||||
|
||||
- [#1991](https://github.com/MoonshotAI/kimi-code/pull/1991) [`92576e4`](https://github.com/MoonshotAI/kimi-code/commit/92576e4d850ada51a24e72fe76a83cc512df922a) Thanks [@7Sageer](https://github.com/7Sageer)! - Reconnect a dropped MCP server connection automatically when one of its tools is called, and retry the call once.
|
||||
|
||||
- [#1970](https://github.com/MoonshotAI/kimi-code/pull/1970) [`6dd4fd3`](https://github.com/MoonshotAI/kimi-code/commit/6dd4fd33688b37904d5302436fc2daaf09d66c7d) Thanks [@sailist](https://github.com/sailist)! - Add read-only model resolution inspection and a live connectivity probe to the server's RPC surface, reporting per-field value provenance (config, override, builtin, env, synthesized) for internal debugging tools.
|
||||
|
||||
- [#2015](https://github.com/MoonshotAI/kimi-code/pull/2015) [`b5efba7`](https://github.com/MoonshotAI/kimi-code/commit/b5efba7abcaf4041f81ec520097a61e6546e8c50) Thanks [@RealKai42](https://github.com/RealKai42)! - Fix thinking levels being offered for models that do not support them (e.g. phantom levels on Kimi K3): levels now come from each model's declared capabilities. Models that cannot disable reasoning (e.g. gpt-5) no longer offer an Off option, and turning thinking Off on models that support it (e.g. xai grok) now truly disables reasoning.
|
||||
|
||||
- [#1735](https://github.com/MoonshotAI/kimi-code/pull/1735) [`ce0e3ce`](https://github.com/MoonshotAI/kimi-code/commit/ce0e3ceb04223bdaad8e8931bad46eff561055b6) Thanks [@7Sageer](https://github.com/7Sageer)! - Warn when a tool allow/deny list entry can never match any tool, for example a misspelled name (v2 engine only).
|
||||
|
||||
- [#2005](https://github.com/MoonshotAI/kimi-code/pull/2005) [`a3699dd`](https://github.com/MoonshotAI/kimi-code/commit/a3699dd6aa7b41efd3129a117007d195282379fd) Thanks [@7Sageer](https://github.com/7Sageer)! - Add an `active` flag to each tool in the server's tool listing API.
|
||||
|
||||
- [#1995](https://github.com/MoonshotAI/kimi-code/pull/1995) [`73eb5f8`](https://github.com/MoonshotAI/kimi-code/commit/73eb5f89e06fb15d42c7585a147eb1c5caef0725) Thanks [@liruifengv](https://github.com/liruifengv)! - Remove red coloring from syntax highlighting in code previews and markdown code blocks.
|
||||
|
||||
- [#2014](https://github.com/MoonshotAI/kimi-code/pull/2014) [`576d650`](https://github.com/MoonshotAI/kimi-code/commit/576d65038035570bea90b58d5824bcd60ca11258) Thanks [@liruifengv](https://github.com/liruifengv)! - Add a reminder for third-party install sources to use the official installer in the update prompt.
|
||||
|
||||
## 0.28.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#934](https://github.com/MoonshotAI/kimi-code/pull/934) [`c5b6103`](https://github.com/MoonshotAI/kimi-code/commit/c5b6103bb9b0a163d48cbce0034c3fc7dea7c344) Thanks [@tt-a1i](https://github.com/tt-a1i)! - Allow ACP sessions to start with configured non-OAuth model credentials instead of requiring terminal login.
|
||||
|
||||
- [#1967](https://github.com/MoonshotAI/kimi-code/pull/1967) [`ad8cc85`](https://github.com/MoonshotAI/kimi-code/commit/ad8cc8525198a08bc1181cee9a15bbb4521cd9bc) Thanks [@sailist](https://github.com/sailist)! - Run web servers foreground-only end to end: the /web slash command now always starts a new server, and the `kimi web kill` / `kimi web ps` subcommands are removed — foreground servers stop with Ctrl+C. `kimi server kill` remains as a deprecated fallback that only stops servers started by a version before 0.28.0.
|
||||
|
||||
- [#1948](https://github.com/MoonshotAI/kimi-code/pull/1948) [`f6f4192`](https://github.com/MoonshotAI/kimi-code/commit/f6f4192957ace3f0cceb734a04b3b26b1d2f88be) Thanks [@sailist](https://github.com/sailist)! - Fix running subagents not observing permission mode switches made after they started.
|
||||
|
||||
## 0.28.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#1826](https://github.com/MoonshotAI/kimi-code/pull/1826) [`a41a09c`](https://github.com/MoonshotAI/kimi-code/commit/a41a09c33c8e432fbc306f5882692c967ed5ea17) Thanks [@sailist](https://github.com/sailist)! - Replace the `kimi server` command tree with `kimi web`: the server runs in the foreground (the background daemon and OS-service lifecycle commands are removed), and multiple servers can now share one home directory, each taking the next free port. Manage instances with `kimi web kill [server-id|all]`, `kimi web ps`, and `kimi web rotate-token`; any `kimi server …` invocation prints a deprecation notice and exits 1.
|
||||
|
||||
- [#1933](https://github.com/MoonshotAI/kimi-code/pull/1933) [`11c1683`](https://github.com/MoonshotAI/kimi-code/commit/11c1683a1cd2adab276562419d2d353629063d80) Thanks [@liruifengv](https://github.com/liruifengv)! - Thinking effort persists only levels below the model's top tier (max).
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#1867](https://github.com/MoonshotAI/kimi-code/pull/1867) [`3086e47`](https://github.com/MoonshotAI/kimi-code/commit/3086e4703992fbbe7a41379405ee243713ad9ced) Thanks [@RealKai42](https://github.com/RealKai42)! - Rename the stale "afk" reference to "auto" in the built-in MCP config skill guidance.
|
||||
|
||||
- [#1867](https://github.com/MoonshotAI/kimi-code/pull/1867) [`3086e47`](https://github.com/MoonshotAI/kimi-code/commit/3086e4703992fbbe7a41379405ee243713ad9ced) Thanks [@RealKai42](https://github.com/RealKai42)! - Correct the YOLO and Auto permission mode descriptions in CLI --help output and in the ACP session mode selector shown by IDE clients.
|
||||
|
||||
- [#1867](https://github.com/MoonshotAI/kimi-code/pull/1867) [`3086e47`](https://github.com/MoonshotAI/kimi-code/commit/3086e4703992fbbe7a41379405ee243713ad9ced) Thanks [@RealKai42](https://github.com/RealKai42)! - web: Correct the YOLO and Auto permission mode descriptions in the slash command list and the mobile permission sheet.
|
||||
|
||||
- [#1867](https://github.com/MoonshotAI/kimi-code/pull/1867) [`3086e47`](https://github.com/MoonshotAI/kimi-code/commit/3086e4703992fbbe7a41379405ee243713ad9ced) Thanks [@RealKai42](https://github.com/RealKai42)! - Fix the YOLO and Auto permission mode descriptions to match their actual behavior: YOLO auto-approves tool actions but the agent may still ask questions, while Auto is fully autonomous and never asks.
|
||||
|
||||
- [#1867](https://github.com/MoonshotAI/kimi-code/pull/1867) [`3086e47`](https://github.com/MoonshotAI/kimi-code/commit/3086e4703992fbbe7a41379405ee243713ad9ced) Thanks [@RealKai42](https://github.com/RealKai42)! - Correct the YOLO mode notice shown when replaying a session: tool actions are auto-approved, but the agent may still ask questions.
|
||||
|
||||
- [#1843](https://github.com/MoonshotAI/kimi-code/pull/1843) [`a3e773f`](https://github.com/MoonshotAI/kimi-code/commit/a3e773f90ce66abe6db229607440c20769537c93) Thanks [@7Sageer](https://github.com/7Sageer)! - Fix the web backend ignoring symbolic links when loading AGENTS.md files and reading files.
|
||||
|
||||
- [#1940](https://github.com/MoonshotAI/kimi-code/pull/1940) [`d71bf9e`](https://github.com/MoonshotAI/kimi-code/commit/d71bf9e5a56b5978316e715f7c131c784967d562) Thanks [@wbxl2000](https://github.com/wbxl2000)! - web: Add a note in the model switcher that switching models or thinking effort invalidates the existing prompt cache.
|
||||
|
||||
## 0.27.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
|||
29
apps/kimi-code/dist-web/assets/CodeBlockNode-ZZ-0lk3E.js
Normal file
29
apps/kimi-code/dist-web/assets/CodeBlockNode-ZZ-0lk3E.js
Normal file
File diff suppressed because one or more lines are too long
13
apps/kimi-code/dist-web/assets/DesignSystemView-BOD_23qT.js
Normal file
13
apps/kimi-code/dist-web/assets/DesignSystemView-BOD_23qT.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
apps/kimi-code/dist-web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Bold-Cx986IdX.woff2
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Bold-Cx986IdX.woff2
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Bold-Jm3AIy58.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Bold-Jm3AIy58.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Bold-waoOVXN0.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Bold-waoOVXN0.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Italic-3WenGoN9.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Italic-3WenGoN9.ttf
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Italic-BMLOBm91.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Italic-BMLOBm91.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Regular-B22Nviop.woff2
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Regular-B22Nviop.woff2
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Regular-Dr94JaBh.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Regular-Dr94JaBh.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Math-Italic-DA0__PXp.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Math-Italic-DA0__PXp.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Math-Italic-flOr_0UB.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Math-Italic-flOr_0UB.ttf
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Math-Italic-t53AETM-.woff2
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Math-Italic-t53AETM-.woff2
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size1-Regular-C195tn64.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size1-Regular-C195tn64.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf
Normal file
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff
Normal file
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size4-Regular-DWFBv043.ttf
Normal file
BIN
apps/kimi-code/dist-web/assets/KaTeX_Size4-Regular-DWFBv043.ttf
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
apps/kimi-code/dist-web/assets/NotoSansSC_wght_-BkPpiACN.woff2
Normal file
BIN
apps/kimi-code/dist-web/assets/NotoSansSC_wght_-BkPpiACN.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
1
apps/kimi-code/dist-web/assets/Tooltip-CPKMqLZA.js
Normal file
1
apps/kimi-code/dist-web/assets/Tooltip-CPKMqLZA.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
import{bQ as A,M as H,aU as b,bE as V,az as J,aL as O,s as Q,v as R,I as F,bJ as G,bL as K,aw as L,H as W,bb as Z,bB as ee,g as te,au as le,T as ae,as as B,bR as ne}from"./index-HRJ6xRtC.js";var k=(h,E,e)=>new Promise((o,p)=>{var i=a=>{try{d(e.next(a))}catch(c){p(c)}},y=a=>{try{d(e.throw(a))}catch(c){p(c)}},d=a=>a.done?o(a.value):Promise.resolve(a.value).then(i,y);d((e=e.apply(h,E)).next())});const oe=["id"],ie=["data-placement"],ue=A(H({__name:"Tooltip",props:{visible:{type:Boolean},anchorEl:{},content:{},placement:{},offset:{},originX:{},originY:{},id:{},isDark:{type:[Boolean,null]}},setup(h){var E;const e=h,o=b(null),p=b(null),i=b({transform:"translate3d(0px, 0px, 0px)",left:"0px",top:"0px"}),y=b({}),d=b((E=e.placement)!=null?E:"top"),a=b(!1);let c=null,$=null,T=null,C=null,w=null,s=0;function X(){return C?Promise.resolve(C):(w||(w=ne(()=>import("./floating-ui.dom-xGUaHE3m.js"),[]).then(l=>(C=l,l)).catch(l=>{throw w=null,l})),w)}function D(){c&&(c(),c=null),$=null,T=null}function P(l){return k(this,null,function*(){const t=e.anchorEl,n=o.value;if(!e.visible||!t||!n||$===t&&T===n)return;const{autoUpdate:r}=yield X();l()&&e.visible&&e.anchorEl===t&&o.value===n&&(D(),$=t,T=n,c=r(t,n,()=>{N().catch(()=>{_()})}))})}function N(){return k(this,null,function*(){var l,t;const n=e.anchorEl,r=o.value;if(!e.visible||!n||!r)return!1;const{arrow:u,computePosition:m,flip:v,offset:f,shift:x}=yield X();if(!e.visible||e.anchorEl!==n||o.value!==r)return!1;const g=[f((l=e.offset)!=null?l:6),v(),x({padding:6}),...p.value?[u({element:p.value,padding:4})]:[]],{x:S,y:j,placement:Y,middlewareData:z}=yield m(n,r,{placement:(t=e.placement)!=null?t:"top",middleware:g,strategy:"fixed"});if(!e.visible||e.anchorEl!==n||o.value!==r)return!1;if(i.value.transform=`translate3d(${Math.round(S)}px, ${Math.round(j)}px, 0)`,i.value.left="0px",i.value.top="0px",d.value=Y,z.arrow&&p.value){const{x:I,y:U}=z.arrow,q={top:"bottom",bottom:"top",left:"right",right:"left"}[Y.split("-")[0]];y.value={left:I!=null?`${I}px`:"",top:U!=null?`${U}px`:"",[q]:"-3px"}}return!0})}function _(){var l,t;const n=e.anchorEl,r=o.value;if(!n||!r)return!1;const u=n.getBoundingClientRect(),m=r.getBoundingClientRect(),v=(l=e.offset)!=null?l:6,f=(t=e.placement)!=null?t:"top";let x=u.left,g=u.top;return f==="bottom"?g=u.bottom+v:f==="left"?x=u.left-m.width-v:f==="right"?x=u.right+v:g=u.top-m.height-v,i.value.transform=`translate3d(${Math.round(Math.max(0,x))}px, ${Math.round(Math.max(0,g))}px, 0)`,i.value.left="0px",i.value.top="0px",d.value=f,y.value={},!0}V(()=>e.visible,l=>k(null,null,function*(){const t=++s;if(l){if(a.value=!1,yield B(),t!==s||!e.visible)return;if(e.anchorEl&&o.value)try{const n=e.anchorEl,r=o.value,u=n.getBoundingClientRect();if(!(yield N())||t!==s||!e.visible||e.anchorEl!==n||o.value!==r)return;const m=i.value.transform;if(e.originX!=null&&e.originY!=null){const v=Math.abs(Number(e.originX)-u.left),f=Math.abs(Number(e.originY)-u.top);if(Math.hypot(v,f)>120){if(i.value.transform=`translate3d(${Math.round(e.originX)}px, ${Math.round(e.originY)}px, 0)`,yield B(),t!==s||!e.visible||(a.value=!0,yield B(),t!==s||!e.visible))return;i.value.transform=m}else a.value=!0}else a.value=!0;yield P(()=>t===s)}catch{if(t!==s||!e.visible)return;if(a.value=_(),e.anchorEl&&o.value)try{yield P(()=>t===s)}catch{}}else a.value=!0}else a.value=!1,D()}));let M=0;return V([()=>e.anchorEl,()=>e.placement,()=>e.content],()=>k(null,null,function*(){const l=++M;if(e.visible&&e.anchorEl&&o.value){if(yield B(),l!==M||!e.visible||!e.anchorEl||!o.value)return;try{const t=yield N();if(l!==M||!e.visible||!e.anchorEl||!o.value)return;t||_()}catch{_()}yield P(()=>l===M)}})),J(()=>{s+=1,D()}),(l,t)=>(O(),Q(ae,{to:"body"},[R("div",{class:le(["markstream-vue",{dark:h.isDark}])},[F(te,{name:"tooltip",appear:""},{default:G(()=>[K(R("div",{id:e.id,ref_key:"tooltip",ref:o,style:L({position:"fixed",left:i.value.left,top:i.value.top,transform:i.value.transform,visibility:a.value?"visible":"hidden",pointerEvents:a.value?void 0:"none"}),class:"tooltip-element",role:"tooltip"},[W(Z(h.content)+" ",1),R("div",{ref_key:"arrowEl",ref:p,class:"tooltip-arrow","data-placement":d.value,style:L(y.value)},null,12,ie)],12,oe),[[ee,h.visible]])]),_:1})],2)]))}}),[["__scopeId","data-v-c606ee4c"]]);export{ue as default};
|
||||
|
|
@ -0,0 +1 @@
|
|||
function e(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}export{e as g};
|
||||
1
apps/kimi-code/dist-web/assets/abap-BdImnpbu.js
Normal file
1
apps/kimi-code/dist-web/assets/abap-BdImnpbu.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
import{g as p,r as u,d as a}from"./chunk-MOJQB5TN-Dk056XM2.js";import{p as f}from"./chunk-JWPE2WC7-K16PVern.js";import{_ as n,l as o}from"./mermaidParser.worker-BFSlSHEW.js";import{M as c,b as d}from"./cynefin-VYW2F7L2-C-EfYT07.js";var v=d().RailroadAbnf.parser.LangiumParser,l=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),i=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:i,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return l(e.element);case"AbnfOptionalGroup":return{type:"optional",element:l(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:l(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},w={parser:R,db:a,renderer:u,styles:p};export{w as diagram};
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue